LCOV - code coverage report
Current view: top level - source/renderer - ShadowMap.cpp (source / functions) Hit Total Coverage
Test: 0 A.D. test coverage report Lines: 28 354 7.9 %
Date: 2023-01-19 00:18:29 Functions: 8 24 33.3 %

          Line data    Source code
       1             : /* Copyright (C) 2023 Wildfire Games.
       2             :  * This file is part of 0 A.D.
       3             :  *
       4             :  * 0 A.D. is free software: you can redistribute it and/or modify
       5             :  * it under the terms of the GNU General Public License as published by
       6             :  * the Free Software Foundation, either version 2 of the License, or
       7             :  * (at your option) any later version.
       8             :  *
       9             :  * 0 A.D. is distributed in the hope that it will be useful,
      10             :  * but WITHOUT ANY WARRANTY; without even the implied warranty of
      11             :  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      12             :  * GNU General Public License for more details.
      13             :  *
      14             :  * You should have received a copy of the GNU General Public License
      15             :  * along with 0 A.D.  If not, see <http://www.gnu.org/licenses/>.
      16             :  */
      17             : 
      18             : #include "precompiled.h"
      19             : 
      20             : #include "ShadowMap.h"
      21             : 
      22             : #include "graphics/Camera.h"
      23             : #include "graphics/LightEnv.h"
      24             : #include "graphics/ShaderManager.h"
      25             : #include "lib/bits.h"
      26             : #include "maths/BoundingBoxAligned.h"
      27             : #include "maths/Brush.h"
      28             : #include "maths/Frustum.h"
      29             : #include "maths/MathUtil.h"
      30             : #include "maths/Matrix3D.h"
      31             : #include "ps/CLogger.h"
      32             : #include "ps/ConfigDB.h"
      33             : #include "ps/CStrInternStatic.h"
      34             : #include "ps/Profile.h"
      35             : #include "ps/VideoMode.h"
      36             : #include "renderer/backend/IDevice.h"
      37             : #include "renderer/backend/ITexture.h"
      38             : #include "renderer/DebugRenderer.h"
      39             : #include "renderer/Renderer.h"
      40             : #include "renderer/RenderingOptions.h"
      41             : #include "renderer/SceneRenderer.h"
      42             : 
      43             : #include <array>
      44             : 
      45             : namespace
      46             : {
      47             : 
      48             : constexpr int MAX_CASCADE_COUNT = 4;
      49             : 
      50             : constexpr float DEFAULT_SHADOWS_CUTOFF_DISTANCE = 300.0f;
      51             : constexpr float DEFAULT_CASCADE_DISTANCE_RATIO = 1.7f;
      52             : 
      53             : } // anonymous namespace
      54             : 
      55             : /**
      56             :  * Struct ShadowMapInternals: Internal data for the ShadowMap implementation
      57             :  */
      58          12 : struct ShadowMapInternals
      59             : {
      60             :     std::unique_ptr<Renderer::Backend::IFramebuffer> Framebuffer;
      61             :     std::unique_ptr<Renderer::Backend::ITexture> Texture;
      62             : 
      63             :     // bit depth for the depth texture
      64             :     int DepthTextureBits;
      65             :     // width, height of shadow map
      66             :     int Width, Height;
      67             :     // Shadow map quality (-1 - Low, 0 - Medium, 1 - High, 2 - Very High)
      68             :     int QualityLevel;
      69             :     // used width, height of shadow map
      70             :     int EffectiveWidth, EffectiveHeight;
      71             : 
      72             :     // Transform world space into light space; calculated on SetupFrame
      73             :     CMatrix3D LightTransform;
      74             : 
      75             :     // transform light space into world space
      76             :     CMatrix3D InvLightTransform;
      77             :     CBoundingBoxAligned ShadowReceiverBound;
      78             : 
      79             :     int CascadeCount;
      80             :     float CascadeDistanceRatio;
      81             :     float ShadowsCutoffDistance;
      82             :     bool ShadowsCoverMap;
      83             : 
      84          24 :     struct Cascade
      85             :     {
      86             :         // transform light space into projected light space
      87             :         // in projected light space, the shadowbound box occupies the [-1..1] cube
      88             :         // calculated on BeginRender, after the final shadow bounds are known
      89             :         CMatrix3D LightProjection;
      90             :         float Distance;
      91             :         CBoundingBoxAligned FrustumBBAA;
      92             :         CBoundingBoxAligned ConvexBounds;
      93             :         CBoundingBoxAligned ShadowRenderBound;
      94             :         // Bounding box of shadowed objects in the light space.
      95             :         CBoundingBoxAligned ShadowCasterBound;
      96             :         // Transform world space into texture space of the shadow map;
      97             :         // calculated on BeginRender, after the final shadow bounds are known
      98             :         CMatrix3D TextureMatrix;
      99             :         // View port of the shadow texture where the cascade should be rendered.
     100             :         SViewPort ViewPort;
     101             :     };
     102             :     std::array<Cascade, MAX_CASCADE_COUNT> Cascades;
     103             : 
     104             :     // Camera transformed into light space
     105             :     CCamera LightspaceCamera;
     106             : 
     107             :     // Some drivers (at least some Intel Mesa ones) appear to handle alpha testing
     108             :     // incorrectly when the FBO has only a depth attachment.
     109             :     // When m_ShadowAlphaFix is true, we use DummyTexture to store a useless
     110             :     // alpha texture which is attached to the FBO as a workaround.
     111             :     std::unique_ptr<Renderer::Backend::ITexture> DummyTexture;
     112             : 
     113             :     // Copy of renderer's standard view camera, saved between
     114             :     // BeginRender and EndRender while we replace it with the shadow camera
     115             :     CCamera SavedViewCamera;
     116             : 
     117             :     void CalculateShadowMatrices(const int cascade);
     118             :     void CreateTexture();
     119             :     void UpdateCascadesParameters();
     120             : };
     121             : 
     122           6 : void ShadowMapInternals::UpdateCascadesParameters()
     123             : {
     124           6 :     CascadeCount = 1;
     125           6 :     CFG_GET_VAL("shadowscascadecount", CascadeCount);
     126             : 
     127           6 :     if (CascadeCount < 1 || CascadeCount > MAX_CASCADE_COUNT || g_VideoMode.GetBackendDevice()->GetBackend() == Renderer::Backend::Backend::GL_ARB)
     128           0 :         CascadeCount = 1;
     129             : 
     130           6 :     ShadowsCoverMap = false;
     131           6 :     CFG_GET_VAL("shadowscovermap", ShadowsCoverMap);
     132           6 : }
     133             : 
     134           0 : void CalculateBoundsForCascade(
     135             :     const CCamera& camera, const CMatrix3D& lightTransform,
     136             :     const float nearPlane, const float farPlane, CBoundingBoxAligned* bbaa,
     137             :     CBoundingBoxAligned* frustumBBAA)
     138             : {
     139           0 :     frustumBBAA->SetEmpty();
     140             : 
     141             :     // We need to calculate a circumscribed sphere for the camera to
     142             :     // create a rotation stable bounding box.
     143           0 :     const CVector3D cameraIn = camera.m_Orientation.GetIn();
     144           0 :     const CVector3D cameraTranslation = camera.m_Orientation.GetTranslation();
     145           0 :     const CVector3D centerNear = cameraTranslation + cameraIn * nearPlane;
     146           0 :     const CVector3D centerDist = cameraTranslation + cameraIn * farPlane;
     147             : 
     148             :     // We can solve 3D problem in 2D space, because the frustum is
     149             :     // symmetric by 2 planes. Than means we can use only one corner
     150             :     // to find a circumscribed sphere.
     151           0 :     CCamera::Quad corners;
     152             : 
     153           0 :     camera.GetViewQuad(nearPlane, corners);
     154           0 :     for (CVector3D& corner : corners)
     155           0 :         corner = camera.GetOrientation().Transform(corner);
     156           0 :     const CVector3D cornerNear = corners[0];
     157           0 :     for (const CVector3D& corner : corners)
     158           0 :         *frustumBBAA += lightTransform.Transform(corner);
     159             : 
     160           0 :     camera.GetViewQuad(farPlane, corners);
     161           0 :     for (CVector3D& corner : corners)
     162           0 :         corner = camera.GetOrientation().Transform(corner);
     163           0 :     const CVector3D cornerDist = corners[0];
     164           0 :     for (const CVector3D& corner : corners)
     165           0 :         *frustumBBAA += lightTransform.Transform(corner);
     166             : 
     167             :     // We solve 2D case for the right trapezoid.
     168           0 :     const float firstBase = (cornerNear - centerNear).Length();
     169           0 :     const float secondBase = (cornerDist - centerDist).Length();
     170           0 :     const float height = (centerDist - centerNear).Length();
     171           0 :     const float distanceToCenter =
     172           0 :         (height * height + secondBase * secondBase - firstBase * firstBase) * 0.5f / height;
     173             : 
     174           0 :     CVector3D position = cameraTranslation + cameraIn * (nearPlane + distanceToCenter);
     175           0 :     const float radius = (cornerNear - position).Length();
     176             : 
     177             :     // We need to convert the bounding box to the light space.
     178           0 :     position = lightTransform.Rotate(position);
     179             : 
     180           0 :     const float insets = 0.2f;
     181           0 :     *bbaa = CBoundingBoxAligned(position, position);
     182           0 :     bbaa->Expand(radius);
     183           0 :     bbaa->Expand(insets);
     184           0 : }
     185             : 
     186           6 : ShadowMap::ShadowMap()
     187             : {
     188           6 :     m = new ShadowMapInternals;
     189           6 :     m->Framebuffer = 0;
     190           6 :     m->Width = 0;
     191           6 :     m->Height = 0;
     192           6 :     m->QualityLevel = 0;
     193           6 :     m->EffectiveWidth = 0;
     194           6 :     m->EffectiveHeight = 0;
     195           6 :     m->DepthTextureBits = 0;
     196             :     // DepthTextureBits: 24/32 are very much faster than 16, on GeForce 4 and FX;
     197             :     // but they're very much slower on Radeon 9800.
     198             :     // In both cases, the default (no specified depth) is fast, so we just use
     199             :     // that by default and hope it's alright. (Otherwise, we'd probably need to
     200             :     // do some kind of hardware detection to work out what to use.)
     201             : 
     202             :     // Avoid using uninitialised values in AddShadowedBound if SetupFrame wasn't called first
     203           6 :     m->LightTransform.SetIdentity();
     204             : 
     205           6 :     m->UpdateCascadesParameters();
     206           6 : }
     207             : 
     208          12 : ShadowMap::~ShadowMap()
     209             : {
     210           6 :     m->Framebuffer.reset();
     211           6 :     m->Texture.reset();
     212           6 :     m->DummyTexture.reset();
     213             : 
     214           6 :     delete m;
     215           6 : }
     216             : 
     217             : // Force the texture/buffer/etc to be recreated, particularly when the renderer's
     218             : // size has changed
     219           0 : void ShadowMap::RecreateTexture()
     220             : {
     221           0 :     m->Framebuffer.reset();
     222           0 :     m->Texture.reset();
     223           0 :     m->DummyTexture.reset();
     224             : 
     225           0 :     m->UpdateCascadesParameters();
     226             : 
     227             :     // (Texture will be constructed in next SetupFrame)
     228           0 : }
     229             : 
     230             : // SetupFrame: camera and light direction for this frame
     231           0 : void ShadowMap::SetupFrame(const CCamera& camera, const CVector3D& lightdir)
     232             : {
     233           0 :     if (!m->Texture)
     234           0 :         m->CreateTexture();
     235             : 
     236           0 :     CVector3D x(0, 1, 0), eyepos;
     237             : 
     238           0 :     CVector3D z = lightdir;
     239           0 :     z.Normalize();
     240           0 :     x -= z * z.Dot(x);
     241           0 :     if (x.Length() < 0.001)
     242             :     {
     243             :         // this is invoked if the camera and light directions almost coincide
     244             :         // assumption: light direction has a significant Z component
     245           0 :         x = CVector3D(1.0, 0.0, 0.0);
     246           0 :         x -= z * z.Dot(x);
     247             :     }
     248           0 :     x.Normalize();
     249           0 :     CVector3D y = z.Cross(x);
     250             : 
     251             :     // X axis perpendicular to light direction, flowing along with view direction
     252           0 :     m->LightTransform._11 = x.X;
     253           0 :     m->LightTransform._12 = x.Y;
     254           0 :     m->LightTransform._13 = x.Z;
     255             : 
     256             :     // Y axis perpendicular to light and view direction
     257           0 :     m->LightTransform._21 = y.X;
     258           0 :     m->LightTransform._22 = y.Y;
     259           0 :     m->LightTransform._23 = y.Z;
     260             : 
     261             :     // Z axis is in direction of light
     262           0 :     m->LightTransform._31 = z.X;
     263           0 :     m->LightTransform._32 = z.Y;
     264           0 :     m->LightTransform._33 = z.Z;
     265             : 
     266             :     // eye is at the origin of the coordinate system
     267           0 :     m->LightTransform._14 = -x.Dot(eyepos);
     268           0 :     m->LightTransform._24 = -y.Dot(eyepos);
     269           0 :     m->LightTransform._34 = -z.Dot(eyepos);
     270             : 
     271           0 :     m->LightTransform._41 = 0.0;
     272           0 :     m->LightTransform._42 = 0.0;
     273           0 :     m->LightTransform._43 = 0.0;
     274           0 :     m->LightTransform._44 = 1.0;
     275             : 
     276           0 :     m->LightTransform.GetInverse(m->InvLightTransform);
     277           0 :     m->ShadowReceiverBound.SetEmpty();
     278             : 
     279           0 :     m->LightspaceCamera = camera;
     280           0 :     m->LightspaceCamera.m_Orientation = m->LightTransform * camera.m_Orientation;
     281           0 :     m->LightspaceCamera.UpdateFrustum();
     282             : 
     283           0 :     m->ShadowsCutoffDistance = DEFAULT_SHADOWS_CUTOFF_DISTANCE;
     284           0 :     m->CascadeDistanceRatio = DEFAULT_CASCADE_DISTANCE_RATIO;
     285           0 :     CFG_GET_VAL("shadowscutoffdistance", m->ShadowsCutoffDistance);
     286           0 :     CFG_GET_VAL("shadowscascadedistanceratio", m->CascadeDistanceRatio);
     287           0 :     m->CascadeDistanceRatio = Clamp(m->CascadeDistanceRatio, 1.1f, 16.0f);
     288             : 
     289           0 :     m->Cascades[GetCascadeCount() - 1].Distance = m->ShadowsCutoffDistance;
     290           0 :     for (int cascade = GetCascadeCount() - 2; cascade >= 0; --cascade)
     291           0 :         m->Cascades[cascade].Distance = m->Cascades[cascade + 1].Distance / m->CascadeDistanceRatio;
     292             : 
     293           0 :     if (GetCascadeCount() == 1 || m->ShadowsCoverMap)
     294             :     {
     295           0 :         m->Cascades[0].ViewPort =
     296           0 :             SViewPort{1, 1, m->EffectiveWidth - 2, m->EffectiveHeight - 2};
     297           0 :         if (m->ShadowsCoverMap)
     298           0 :             m->Cascades[0].Distance = camera.GetFarPlane();
     299             :     }
     300             :     else
     301             :     {
     302           0 :         for (int cascade = 0; cascade < GetCascadeCount(); ++cascade)
     303             :         {
     304           0 :             const int offsetX = (cascade & 0x1) ? m->EffectiveWidth / 2 : 0;
     305           0 :             const int offsetY = (cascade & 0x2) ? m->EffectiveHeight / 2 : 0;
     306           0 :             m->Cascades[cascade].ViewPort =
     307           0 :                 SViewPort{offsetX + 1, offsetY + 1,
     308           0 :                 m->EffectiveWidth / 2 - 2, m->EffectiveHeight / 2 - 2};
     309             :         }
     310             :     }
     311             : 
     312           0 :     for (int cascadeIdx = 0; cascadeIdx < GetCascadeCount(); ++cascadeIdx)
     313             :     {
     314           0 :         ShadowMapInternals::Cascade& cascade = m->Cascades[cascadeIdx];
     315             : 
     316           0 :         const float nearPlane = cascadeIdx > 0 ?
     317           0 :             m->Cascades[cascadeIdx - 1].Distance : camera.GetNearPlane();
     318           0 :         const float farPlane = cascade.Distance;
     319             : 
     320           0 :         CalculateBoundsForCascade(camera, m->LightTransform,
     321             :             nearPlane, farPlane, &cascade.ConvexBounds, &cascade.FrustumBBAA);
     322           0 :         cascade.ShadowCasterBound.SetEmpty();
     323             :     }
     324           0 : }
     325             : 
     326             : // AddShadowedBound: add a world-space bounding box to the bounds of shadowed
     327             : // objects
     328           0 : void ShadowMap::AddShadowCasterBound(const int cascade, const CBoundingBoxAligned& bounds)
     329             : {
     330           0 :     CBoundingBoxAligned lightspacebounds;
     331             : 
     332           0 :     bounds.Transform(m->LightTransform, lightspacebounds);
     333           0 :     m->Cascades[cascade].ShadowCasterBound += lightspacebounds;
     334           0 : }
     335             : 
     336           0 : void ShadowMap::AddShadowReceiverBound(const CBoundingBoxAligned& bounds)
     337             : {
     338           0 :     CBoundingBoxAligned lightspacebounds;
     339             : 
     340           0 :     bounds.Transform(m->LightTransform, lightspacebounds);
     341           0 :     m->ShadowReceiverBound += lightspacebounds;
     342           0 : }
     343             : 
     344           0 : CFrustum ShadowMap::GetShadowCasterCullFrustum(const int cascade)
     345             : {
     346             :     // Get the bounds of all objects that can receive shadows
     347           0 :     CBoundingBoxAligned bound = m->ShadowReceiverBound;
     348             : 
     349             :     // Intersect with the camera frustum, so the shadow map doesn't have to get
     350             :     // stretched to cover the off-screen parts of large models
     351           0 :     bound.IntersectFrustumConservative(m->Cascades[cascade].FrustumBBAA.ToFrustum());
     352             : 
     353             :     // ShadowBound might have been empty to begin with, producing an empty result
     354           0 :     if (bound.IsEmpty())
     355             :     {
     356             :         // CFrustum can't easily represent nothingness, so approximate it with
     357             :         // a single point which won't match many objects
     358           0 :         bound += CVector3D(0.0f, 0.0f, 0.0f);
     359           0 :         return bound.ToFrustum();
     360             :     }
     361             : 
     362             :     // Extend the bounds a long way towards the light source, to encompass
     363             :     // all objects that might cast visible shadows.
     364             :     // (The exact constant was picked entirely arbitrarily.)
     365           0 :     bound[0].Z -= 1000.f;
     366             : 
     367           0 :     CFrustum frustum = bound.ToFrustum();
     368           0 :     frustum.Transform(m->InvLightTransform);
     369           0 :     return frustum;
     370             : }
     371             : 
     372             : // CalculateShadowMatrices: calculate required matrices for shadow map generation - the light's
     373             : // projection and transformation matrices
     374           0 : void ShadowMapInternals::CalculateShadowMatrices(const int cascade)
     375             : {
     376           0 :     CBoundingBoxAligned& shadowRenderBound = Cascades[cascade].ShadowRenderBound;
     377           0 :     shadowRenderBound = Cascades[cascade].ConvexBounds;
     378             : 
     379           0 :     if (ShadowsCoverMap)
     380             :     {
     381             :         // Start building the shadow map to cover all objects that will receive shadows
     382           0 :         CBoundingBoxAligned receiverBound = ShadowReceiverBound;
     383             : 
     384             :         // Intersect with the camera frustum, so the shadow map doesn't have to get
     385             :         // stretched to cover the off-screen parts of large models
     386           0 :         receiverBound.IntersectFrustumConservative(LightspaceCamera.GetFrustum());
     387             : 
     388             :         // Intersect with the shadow caster bounds, because there's no point
     389             :         // wasting space around the edges of the shadow map that we're not going
     390             :         // to draw into
     391           0 :         shadowRenderBound[0].X = std::max(receiverBound[0].X, Cascades[cascade].ShadowCasterBound[0].X);
     392           0 :         shadowRenderBound[0].Y = std::max(receiverBound[0].Y, Cascades[cascade].ShadowCasterBound[0].Y);
     393           0 :         shadowRenderBound[1].X = std::min(receiverBound[1].X, Cascades[cascade].ShadowCasterBound[1].X);
     394           0 :         shadowRenderBound[1].Y = std::min(receiverBound[1].Y, Cascades[cascade].ShadowCasterBound[1].Y);
     395             :     }
     396           0 :     else if (CascadeCount > 1)
     397             :     {
     398             :         // We need to offset the cascade to its place on the texture.
     399           0 :         const CVector3D size = (shadowRenderBound[1] - shadowRenderBound[0]) * 0.5f;
     400           0 :         if (!(cascade & 0x1))
     401           0 :             shadowRenderBound[1].X += size.X * 2.0f;
     402             :         else
     403           0 :             shadowRenderBound[0].X -= size.X * 2.0f;
     404           0 :         if (!(cascade & 0x2))
     405           0 :             shadowRenderBound[1].Y += size.Y * 2.0f;
     406             :         else
     407           0 :             shadowRenderBound[0].Y -= size.Y * 2.0f;
     408             :     }
     409             : 
     410             :     // Set the near and far planes to include just the shadow casters,
     411             :     // so we make full use of the depth texture's range. Add a bit of a
     412             :     // delta so we don't accidentally clip objects that are directly on
     413             :     // the planes.
     414           0 :     shadowRenderBound[0].Z = Cascades[cascade].ShadowCasterBound[0].Z - 2.f;
     415           0 :     shadowRenderBound[1].Z = Cascades[cascade].ShadowCasterBound[1].Z + 2.f;
     416             : 
     417             :     // Setup orthogonal projection (lightspace -> clip space) for shadowmap rendering
     418           0 :     CVector3D scale = shadowRenderBound[1] - shadowRenderBound[0];
     419           0 :     CVector3D shift = (shadowRenderBound[1] + shadowRenderBound[0]) * -0.5;
     420             : 
     421           0 :     if (scale.X < 1.0)
     422           0 :         scale.X = 1.0;
     423           0 :     if (scale.Y < 1.0)
     424           0 :         scale.Y = 1.0;
     425           0 :     if (scale.Z < 1.0)
     426           0 :         scale.Z = 1.0;
     427             : 
     428           0 :     scale.X = 2.0 / scale.X;
     429           0 :     scale.Y = 2.0 / scale.Y;
     430           0 :     scale.Z = 2.0 / scale.Z;
     431             : 
     432             :     // make sure a given world position falls on a consistent shadowmap texel fractional offset
     433           0 :     float offsetX = fmod(shadowRenderBound[0].X - LightTransform._14, 2.0f/(scale.X*EffectiveWidth));
     434           0 :     float offsetY = fmod(shadowRenderBound[0].Y - LightTransform._24, 2.0f/(scale.Y*EffectiveHeight));
     435             : 
     436           0 :     CMatrix3D& lightProjection = Cascades[cascade].LightProjection;
     437           0 :     lightProjection.SetZero();
     438           0 :     lightProjection._11 = scale.X;
     439           0 :     lightProjection._14 = (shift.X + offsetX) * scale.X;
     440           0 :     lightProjection._22 = scale.Y;
     441           0 :     lightProjection._24 = (shift.Y + offsetY) * scale.Y;
     442           0 :     lightProjection._33 = scale.Z;
     443           0 :     lightProjection._34 = shift.Z * scale.Z;
     444           0 :     lightProjection._44 = 1.0;
     445             : 
     446             :     // Calculate texture matrix by creating the clip space to texture coordinate matrix
     447             :     // and then concatenating all matrices that have been calculated so far
     448             : 
     449           0 :     float texscalex = scale.X * 0.5f * (float)EffectiveWidth / (float)Width;
     450           0 :     float texscaley = scale.Y * 0.5f * (float)EffectiveHeight / (float)Height;
     451           0 :     float texscalez = scale.Z * 0.5f;
     452             : 
     453           0 :     CMatrix3D lightToTex;
     454           0 :     lightToTex.SetZero();
     455           0 :     lightToTex._11 = texscalex;
     456           0 :     lightToTex._14 = (offsetX - shadowRenderBound[0].X) * texscalex;
     457           0 :     lightToTex._22 = texscaley;
     458           0 :     lightToTex._24 = (offsetY - shadowRenderBound[0].Y) * texscaley;
     459           0 :     lightToTex._33 = texscalez;
     460           0 :     lightToTex._34 = -shadowRenderBound[0].Z * texscalez;
     461           0 :     lightToTex._44 = 1.0;
     462             : 
     463           0 :     if (g_VideoMode.GetBackendDevice()->GetBackend() == Renderer::Backend::Backend::VULKAN)
     464             :     {
     465           0 :         CMatrix3D flip;
     466           0 :         flip.SetIdentity();
     467           0 :         flip._22 = -1.0f;
     468           0 :         flip._24 = 1.0;
     469           0 :         lightToTex = flip * lightToTex;
     470             :     }
     471             : 
     472           0 :     Cascades[cascade].TextureMatrix = lightToTex * LightTransform;
     473           0 : }
     474             : 
     475             : // Create the shadow map
     476           0 : void ShadowMapInternals::CreateTexture()
     477             : {
     478             :     // Cleanup
     479           0 :     Framebuffer.reset();
     480           0 :     Texture.reset();
     481           0 :     DummyTexture.reset();
     482             : 
     483           0 :     Renderer::Backend::IDevice* backendDevice = g_VideoMode.GetBackendDevice();
     484             : 
     485           0 :     CFG_GET_VAL("shadowquality", QualityLevel);
     486             : 
     487             :     // Get shadow map size as next power of two up from view width/height.
     488             :     int shadowMapSize;
     489           0 :     switch (QualityLevel)
     490             :     {
     491             :     // Low
     492           0 :     case -1:
     493           0 :         shadowMapSize = 512;
     494           0 :         break;
     495             :     // High
     496           0 :     case 1:
     497           0 :         shadowMapSize = 2048;
     498           0 :         break;
     499             :     // Ultra
     500           0 :     case 2:
     501           0 :         shadowMapSize = std::max(round_up_to_pow2(std::max(g_Renderer.GetWidth(), g_Renderer.GetHeight())), 4096);
     502           0 :         break;
     503             :     // Medium as is
     504           0 :     default:
     505           0 :         shadowMapSize = 1024;
     506           0 :         break;
     507             :     }
     508             : 
     509             :     // Clamp to the maximum texture size.
     510           0 :     shadowMapSize = std::min(
     511           0 :         shadowMapSize, static_cast<int>(backendDevice->GetCapabilities().maxTextureSize));
     512             : 
     513           0 :     Width = Height = shadowMapSize;
     514             : 
     515             :     // Since we're using a framebuffer object, the whole texture is available
     516           0 :     EffectiveWidth = Width;
     517           0 :     EffectiveHeight = Height;
     518             : 
     519             :     const char* formatName;
     520           0 :     Renderer::Backend::Format backendFormat = Renderer::Backend::Format::UNDEFINED;
     521             : #if CONFIG2_GLES
     522             :     formatName = "Format::D24_UNORM";
     523             :     backendFormat = Renderer::Backend::Format::D24_UNORM;
     524             : #else
     525           0 :     switch (DepthTextureBits)
     526             :     {
     527           0 :     case 16: formatName = "Format::D16_UNORM"; backendFormat = Renderer::Backend::Format::D16_UNORM; break;
     528           0 :     case 24: formatName = "Format::D24_UNORM"; backendFormat = Renderer::Backend::Format::D24_UNORM; break;
     529           0 :     case 32: formatName = "Format::D32_SFLOAT"; backendFormat = Renderer::Backend::Format::D32_SFLOAT; break;
     530           0 :     default:
     531           0 :         formatName = "Default";
     532           0 :         backendFormat = backendDevice->GetPreferredDepthStencilFormat(
     533             :             Renderer::Backend::ITexture::Usage::SAMPLED |
     534             :                 Renderer::Backend::ITexture::Usage::DEPTH_STENCIL_ATTACHMENT,
     535           0 :             true, false);
     536           0 :         break;
     537             :     }
     538             : #endif
     539           0 :     ENSURE(formatName);
     540             : 
     541           0 :     LOGMESSAGE("Creating shadow texture (size %dx%d) (format = %s)",
     542             :         Width, Height, formatName);
     543             : 
     544           0 :     if (g_RenderingOptions.GetShadowAlphaFix())
     545             :     {
     546           0 :         DummyTexture = backendDevice->CreateTexture2D("ShadowMapDummy",
     547             :             Renderer::Backend::ITexture::Usage::COLOR_ATTACHMENT,
     548           0 :             Renderer::Backend::Format::R8G8B8A8_UNORM, Width, Height,
     549           0 :             Renderer::Backend::Sampler::MakeDefaultSampler(
     550             :                 Renderer::Backend::Sampler::Filter::NEAREST,
     551           0 :                 Renderer::Backend::Sampler::AddressMode::CLAMP_TO_EDGE));
     552             :     }
     553             : 
     554             :     Renderer::Backend::Sampler::Desc samplerDesc =
     555             :         Renderer::Backend::Sampler::MakeDefaultSampler(
     556             : #if CONFIG2_GLES
     557             :             // GLES doesn't do depth comparisons, so treat it as a
     558             :             // basic unfiltered depth texture
     559             :             Renderer::Backend::Sampler::Filter::NEAREST,
     560             : #else
     561             :             // Use LINEAR to trigger automatic PCF on some devices.
     562             :             Renderer::Backend::Sampler::Filter::LINEAR,
     563             : #endif
     564           0 :             Renderer::Backend::Sampler::AddressMode::CLAMP_TO_EDGE);
     565             :     // Enable automatic depth comparisons
     566           0 :     samplerDesc.compareEnabled = true;
     567           0 :     samplerDesc.compareOp = Renderer::Backend::CompareOp::LESS_OR_EQUAL;
     568             : 
     569           0 :     Texture = backendDevice->CreateTexture2D("ShadowMapDepth",
     570             :         Renderer::Backend::ITexture::Usage::SAMPLED |
     571             :             Renderer::Backend::ITexture::Usage::DEPTH_STENCIL_ATTACHMENT,
     572           0 :         backendFormat, Width, Height, samplerDesc);
     573             : 
     574           0 :     const bool useDummyTexture = g_RenderingOptions.GetShadowAlphaFix();
     575             : 
     576             :     // In case we used ShadowAlphaFix, we ought to clear the unused
     577             :     // color buffer too, else Mali 400 drivers get confused.
     578             :     // Might as well clear stencil too for completeness.
     579           0 :     Renderer::Backend::SColorAttachment colorAttachment{};
     580           0 :     colorAttachment.texture = DummyTexture.get();
     581           0 :     colorAttachment.loadOp = Renderer::Backend::AttachmentLoadOp::CLEAR;
     582           0 :     colorAttachment.storeOp = Renderer::Backend::AttachmentStoreOp::DONT_CARE;
     583           0 :     colorAttachment.clearColor = CColor{0.0f, 0.0f, 0.0f, 0.0f};
     584             : 
     585           0 :     Renderer::Backend::SDepthStencilAttachment depthStencilAttachment{};
     586           0 :     depthStencilAttachment.texture = Texture.get();
     587           0 :     depthStencilAttachment.loadOp = Renderer::Backend::AttachmentLoadOp::CLEAR;
     588           0 :     depthStencilAttachment.storeOp = Renderer::Backend::AttachmentStoreOp::STORE;
     589             : 
     590           0 :     Framebuffer = backendDevice->CreateFramebuffer("ShadowMapFramebuffer",
     591           0 :         useDummyTexture ? &colorAttachment : nullptr, &depthStencilAttachment);
     592           0 :     if (!Framebuffer)
     593             :     {
     594           0 :         LOGERROR("Failed to create shadows framebuffer");
     595             : 
     596             :         // Disable shadow rendering (but let the user try again if they want).
     597           0 :         g_RenderingOptions.SetShadows(false);
     598             :     }
     599           0 : }
     600             : 
     601           0 : void ShadowMap::BeginRender(
     602             :     Renderer::Backend::IDeviceCommandContext* deviceCommandContext)
     603             : {
     604           0 :     ENSURE(m->Framebuffer);
     605           0 :     deviceCommandContext->BeginFramebufferPass(m->Framebuffer.get());
     606             : 
     607           0 :     m->SavedViewCamera = g_Renderer.GetSceneRenderer().GetViewCamera();
     608           0 : }
     609             : 
     610           0 : void ShadowMap::PrepareCamera(
     611             :     Renderer::Backend::IDeviceCommandContext* deviceCommandContext, const int cascade)
     612             : {
     613           0 :     m->CalculateShadowMatrices(cascade);
     614             : 
     615           0 :     Renderer::Backend::IDeviceCommandContext::Rect viewportRect{};
     616           0 :     viewportRect.width = m->EffectiveWidth;
     617           0 :     viewportRect.height = m->EffectiveHeight;
     618           0 :     deviceCommandContext->SetViewports(1, &viewportRect);
     619             : 
     620           0 :     CCamera camera = m->SavedViewCamera;
     621           0 :     camera.SetProjection(m->Cascades[cascade].LightProjection);
     622           0 :     camera.GetOrientation() = m->InvLightTransform;
     623           0 :     g_Renderer.GetSceneRenderer().SetViewCamera(camera);
     624             : 
     625           0 :     const SViewPort& cascadeViewPort = m->Cascades[cascade].ViewPort;
     626             :     Renderer::Backend::IDeviceCommandContext::Rect scissorRect;
     627           0 :     scissorRect.x = cascadeViewPort.m_X;
     628           0 :     scissorRect.y = cascadeViewPort.m_Y;
     629           0 :     scissorRect.width = cascadeViewPort.m_Width;
     630           0 :     scissorRect.height = cascadeViewPort.m_Height;
     631           0 :     deviceCommandContext->SetScissors(1, &scissorRect);
     632           0 : }
     633             : 
     634           0 : void ShadowMap::EndRender(
     635             :     Renderer::Backend::IDeviceCommandContext* deviceCommandContext)
     636             : {
     637           0 :     deviceCommandContext->SetScissors(0, nullptr);
     638             : 
     639           0 :     deviceCommandContext->EndFramebufferPass();
     640             : 
     641           0 :     g_Renderer.GetSceneRenderer().SetViewCamera(m->SavedViewCamera);
     642           0 : }
     643             : 
     644           0 : void ShadowMap::BindTo(
     645             :     Renderer::Backend::IDeviceCommandContext* deviceCommandContext,
     646             :     Renderer::Backend::IShaderProgram* shader) const
     647             : {
     648           0 :     const int32_t shadowTexBindingSlot = shader->GetBindingSlot(str_shadowTex);
     649           0 :     if (shadowTexBindingSlot < 0 || !m->Texture)
     650           0 :         return;
     651             : 
     652           0 :     deviceCommandContext->SetTexture(shadowTexBindingSlot, m->Texture.get());
     653           0 :     deviceCommandContext->SetUniform(
     654           0 :         shader->GetBindingSlot(str_shadowScale), m->Width, m->Height, 1.0f / m->Width, 1.0f / m->Height);
     655           0 :     const CVector3D cameraForward = g_Renderer.GetSceneRenderer().GetCullCamera().GetOrientation().GetIn();
     656           0 :     deviceCommandContext->SetUniform(
     657           0 :         shader->GetBindingSlot(str_cameraForward), cameraForward.X, cameraForward.Y, cameraForward.Z,
     658           0 :         cameraForward.Dot(g_Renderer.GetSceneRenderer().GetCullCamera().GetOrientation().GetTranslation()));
     659             : 
     660           0 :     if (GetCascadeCount() == 1)
     661             :     {
     662           0 :         deviceCommandContext->SetUniform(
     663           0 :             shader->GetBindingSlot(str_shadowTransform),
     664           0 :             m->Cascades[0].TextureMatrix.AsFloatArray());
     665           0 :         deviceCommandContext->SetUniform(
     666           0 :             shader->GetBindingSlot(str_shadowDistance), m->Cascades[0].Distance);
     667             :     }
     668             :     else
     669             :     {
     670           0 :         std::vector<float> shadowDistances;
     671           0 :         std::vector<CMatrix3D> shadowTransforms;
     672           0 :         for (const ShadowMapInternals::Cascade& cascade : m->Cascades)
     673             :         {
     674           0 :             shadowDistances.emplace_back(cascade.Distance);
     675           0 :             shadowTransforms.emplace_back(cascade.TextureMatrix);
     676             :         }
     677           0 :         deviceCommandContext->SetUniform(
     678           0 :             shader->GetBindingSlot(str_shadowTransform),
     679             :             PS::span<const float>(
     680           0 :                 shadowTransforms[0]._data,
     681           0 :                 shadowTransforms[0].AsFloatArray().size() * GetCascadeCount()));
     682           0 :         deviceCommandContext->SetUniform(
     683           0 :             shader->GetBindingSlot(str_shadowDistance),
     684           0 :             PS::span<const float>(shadowDistances.data(), shadowDistances.size()));
     685             :     }
     686             : }
     687             : 
     688             : // Depth texture bits
     689           0 : int ShadowMap::GetDepthTextureBits() const
     690             : {
     691           0 :     return m->DepthTextureBits;
     692             : }
     693             : 
     694           0 : void ShadowMap::SetDepthTextureBits(int bits)
     695             : {
     696           0 :     if (bits != m->DepthTextureBits)
     697             :     {
     698           0 :         m->Texture.reset();
     699           0 :         m->Width = m->Height = 0;
     700             : 
     701           0 :         m->DepthTextureBits = bits;
     702             :     }
     703           0 : }
     704             : 
     705           0 : void ShadowMap::RenderDebugBounds()
     706             : {
     707             :     // Render various shadow bounds:
     708             :     //  Yellow = bounds of objects in view frustum that receive shadows
     709             :     //  Red = culling frustum used to find potential shadow casters
     710             :     //  Blue = frustum used for rendering the shadow map
     711             : 
     712           0 :     const CMatrix3D transform = g_Renderer.GetSceneRenderer().GetViewCamera().GetViewProjection() * m->InvLightTransform;
     713             : 
     714           0 :     g_Renderer.GetDebugRenderer().DrawBoundingBox(
     715           0 :         m->ShadowReceiverBound, CColor(1.0f, 1.0f, 0.0f, 1.0f), transform, true);
     716             : 
     717           0 :     for (int cascade = 0; cascade < GetCascadeCount(); ++cascade)
     718             :     {
     719           0 :         g_Renderer.GetDebugRenderer().DrawBoundingBox(
     720           0 :             m->Cascades[cascade].ShadowRenderBound, CColor(0.0f, 0.0f, 1.0f, 0.10f), transform);
     721           0 :         g_Renderer.GetDebugRenderer().DrawBoundingBox(
     722           0 :             m->Cascades[cascade].ShadowRenderBound, CColor(0.0f, 0.0f, 1.0f, 0.5f), transform, true);
     723             : 
     724           0 :         const CFrustum frustum = GetShadowCasterCullFrustum(cascade);
     725             :         // We don't have a function to create a brush directly from a frustum, so use
     726             :         // the ugly approach of creating a large cube and then intersecting with the frustum
     727           0 :         const CBoundingBoxAligned dummy(CVector3D(-1e4, -1e4, -1e4), CVector3D(1e4, 1e4, 1e4));
     728           0 :         CBrush brush(dummy);
     729           0 :         CBrush frustumBrush;
     730           0 :         brush.Intersect(frustum, frustumBrush);
     731             : 
     732           0 :         g_Renderer.GetDebugRenderer().DrawBrush(frustumBrush, CColor(1.0f, 0.0f, 0.0f, 0.1f));
     733           0 :         g_Renderer.GetDebugRenderer().DrawBrush(frustumBrush, CColor(1.0f, 0.0f, 0.0f, 0.1f), true);
     734             :     }
     735           0 : }
     736             : 
     737           0 : int ShadowMap::GetCascadeCount() const
     738             : {
     739             : #if CONFIG2_GLES
     740             :     return 1;
     741             : #else
     742           0 :     return m->ShadowsCoverMap ? 1 : m->CascadeCount;
     743             : #endif
     744           3 : }

Generated by: LCOV version 1.13