LCOV - code coverage report
Current view: top level - source/gui - GUIRenderer.cpp (source / functions) Hit Total Coverage
Test: 0 A.D. test coverage report Lines: 0 143 0.0 %
Date: 2022-06-14 00:41:00 Functions: 0 6 0.0 %

          Line data    Source code
       1             : /* Copyright (C) 2022 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 "GUIRenderer.h"
      21             : 
      22             : #include "graphics/Canvas2D.h"
      23             : #include "graphics/TextureManager.h"
      24             : #include "gui/CGUI.h"
      25             : #include "gui/CGUISprite.h"
      26             : #include "gui/GUIMatrix.h"
      27             : #include "gui/SettingTypes/CGUIColor.h"
      28             : #include "i18n/L10n.h"
      29             : #include "lib/tex/tex.h"
      30             : #include "lib/utf8.h"
      31             : #include "ps/CLogger.h"
      32             : #include "ps/CStrInternStatic.h"
      33             : #include "ps/Filesystem.h"
      34             : #include "renderer/Renderer.h"
      35             : 
      36             : using namespace GUIRenderer;
      37             : 
      38           0 : DrawCalls::DrawCalls()
      39             : {
      40           0 : }
      41             : 
      42             : // DrawCalls needs to be copyable, so it can be used in other copyable types.
      43             : // But actually copying data is hard, since we'd need to avoid losing track of
      44             : // who owns various pointers, so instead we just return an empty list.
      45             : // The list should get filled in again (by GUIRenderer::UpdateDrawCallCache)
      46             : // before it's used for rendering. (TODO: Is this class actually used safely
      47             : // in practice?)
      48             : 
      49           0 : DrawCalls::DrawCalls(const DrawCalls&)
      50           0 :     : std::vector<SDrawCall>()
      51             : {
      52           0 : }
      53             : 
      54           0 : DrawCalls& DrawCalls::operator=(const DrawCalls&)
      55             : {
      56           0 :     return *this;
      57             : }
      58             : 
      59             : 
      60           0 : void GUIRenderer::UpdateDrawCallCache(const CGUI& pGUI, DrawCalls& Calls, const CStr& SpriteName, const CRect& Size, std::map<CStr, std::unique_ptr<const CGUISprite>>& Sprites)
      61             : {
      62             :     // This is called only when something has changed (like the size of the
      63             :     // sprite), so it doesn't need to be particularly efficient.
      64             : 
      65             :     // Clean up the old data
      66           0 :     Calls.clear();
      67             : 
      68             :     // If this object has zero size, there's nothing to render. (This happens
      69             :     // with e.g. tooltips that have zero size before they're first drawn, so
      70             :     // it isn't necessarily an error.)
      71           0 :     if (Size.left == Size.right && Size.top == Size.bottom)
      72             :         return;
      73             : 
      74           0 :     std::map<CStr, std::unique_ptr<const CGUISprite>>::iterator it(Sprites.find(SpriteName));
      75           0 :     if (it == Sprites.end())
      76             :     {
      77             :         /*
      78             :          * Sprite not found. Check whether this a special sprite,
      79             :          * and if so create a new sprite:
      80             :          * "stretched:filename.ext" - stretched image
      81             :          * "stretched:grayscale:filename.ext" - stretched grayscale image.
      82             :          * "cropped:0.5, 0.25"    - stretch this ratio (x,y) of the top left of the image
      83             :          * "color:r g b a"        - solid color
      84             :          *     > "textureAsMask"  - when using color, use the (optional) texture alpha channel as mask.
      85             :          * These can be combined, but they must be separated by a ":"
      86             :          * so you can have a white overlay over an stretched grayscale image with:
      87             :          * "grayscale:color:255 255 255 100:stretched:filename.ext"
      88             :          */
      89             :         // Check that this can be a special sprite.
      90           0 :         if (SpriteName.ReverseFind(":") == -1 && SpriteName.Find("color(") == -1)
      91             :         {
      92           0 :             LOGERROR("Trying to use a sprite that doesn't exist (\"%s\").", SpriteName.c_str());
      93           0 :             return;
      94             :         }
      95             : 
      96           0 :         auto sprite = std::make_unique<CGUISprite>();
      97           0 :         VfsPath TextureName = VfsPath("art/textures/ui") / wstring_from_utf8(SpriteName.AfterLast(":"));
      98           0 :         if (SpriteName.Find("stretched:") != -1)
      99             :         {
     100             :             // TODO: Should check (nicely) that this is a valid file?
     101           0 :             auto image = std::make_unique<SGUIImage>();
     102             : 
     103           0 :             image->m_TextureName = TextureName;
     104           0 :             if (SpriteName.Find("grayscale:") != -1)
     105             :             {
     106           0 :                 image->m_Effects = std::make_shared<SGUIImageEffects>();
     107           0 :                 image->m_Effects->m_Greyscale = true;
     108             :             }
     109             : 
     110           0 :             sprite->AddImage(std::move(image));
     111             :         }
     112           0 :         else if (SpriteName.Find("cropped:") != -1)
     113             :         {
     114             :             // TODO: Should check (nicely) that this is a valid file?
     115           0 :             auto image = std::make_unique<SGUIImage>();
     116             : 
     117           0 :             const bool centered = SpriteName.Find("center:") != -1;
     118             : 
     119           0 :             CStr info = SpriteName.AfterLast("cropped:").BeforeFirst(":");
     120           0 :             double xRatio = info.BeforeFirst(",").ToDouble();
     121           0 :             double yRatio = info.AfterLast(",").ToDouble();
     122           0 :             const CRect percentSize = centered
     123           0 :                 ? CRect(50 - 50 / xRatio, 50 - 50 / yRatio, 50 + 50 / xRatio, 50 + 50 / yRatio)
     124           0 :                 : CRect(0, 0, 100 / xRatio, 100 / yRatio);
     125           0 :             image->m_TextureSize = CGUISize(CRect(0, 0, 0, 0), percentSize);
     126           0 :             image->m_TextureName = TextureName;
     127             : 
     128           0 :             if (SpriteName.Find("grayscale:") != -1)
     129             :             {
     130           0 :                 image->m_Effects = std::make_shared<SGUIImageEffects>();
     131           0 :                 image->m_Effects->m_Greyscale = true;
     132             :             }
     133             : 
     134           0 :             sprite->AddImage(std::move(image));
     135             :         }
     136           0 :         if (SpriteName.Find("color:") != -1)
     137             :         {
     138           0 :             CStrW value = wstring_from_utf8(SpriteName.AfterLast("color:").BeforeFirst(":"));
     139             : 
     140           0 :             auto image = std::make_unique<SGUIImage>();
     141           0 :             CGUIColor* color;
     142             : 
     143             :             // If we are using a mask, this is an effect.
     144             :             // Otherwise we can fallback to the "back color" attribute
     145             :             // TODO: we are assuming there is a filename here.
     146           0 :             if (SpriteName.Find("textureAsMask:") != -1)
     147             :             {
     148           0 :                 image->m_TextureName = TextureName;
     149           0 :                 image->m_Effects = std::make_shared<SGUIImageEffects>();
     150           0 :                 color = &image->m_Effects->m_SolidColor;
     151             :             }
     152             :             else
     153           0 :                 color = &image->m_BackColor;
     154             : 
     155             :             // Check color is valid
     156           0 :             if (!CGUI::ParseString<CGUIColor>(&pGUI, value, *color))
     157             :             {
     158           0 :                 LOGERROR("GUI: Error parsing sprite 'color' (\"%s\")", utf8_from_wstring(value));
     159           0 :                 return;
     160             :             }
     161             : 
     162           0 :             sprite->AddImage(std::move(image));
     163             :         }
     164             : 
     165           0 :         if (sprite->m_Images.empty())
     166             :         {
     167           0 :             LOGERROR("Trying to use a sprite that doesn't exist (\"%s\").", SpriteName.c_str());
     168           0 :             return;
     169             :         }
     170             :         
     171           0 :         it = Sprites.emplace(SpriteName, std::move(sprite)).first;
     172             :     }
     173             : 
     174           0 :     Calls.reserve(it->second->m_Images.size());
     175             : 
     176             :     // Iterate through all the sprite's images, loading the texture and
     177             :     // calculating the texture coordinates
     178           0 :     std::vector<std::unique_ptr<SGUIImage>>::const_iterator cit;
     179           0 :     for (cit = it->second->m_Images.begin(); cit != it->second->m_Images.end(); ++cit)
     180             :     {
     181           0 :         SDrawCall Call(cit->get()); // pointers are safe since we never modify sprites/images after startup
     182             : 
     183           0 :         CRect ObjectSize = (*cit)->m_Size.GetSize(Size);
     184             : 
     185           0 :         if (ObjectSize.GetWidth() == 0.0 || ObjectSize.GetHeight() == 0.0)
     186             :         {
     187             :             // Zero sized object. Don't report as an error, since it's common for e.g. hitpoint bars.
     188           0 :             continue; // i.e. don't continue with this image
     189             :         }
     190             : 
     191           0 :         Call.m_Vertices = ObjectSize;
     192           0 :         if ((*cit)->m_RoundCoordinates)
     193             :         {
     194             :             // Round the vertex coordinates to integers, to avoid ugly filtering artifacts
     195           0 :             Call.m_Vertices.left = (int)(Call.m_Vertices.left + 0.5f);
     196           0 :             Call.m_Vertices.right = (int)(Call.m_Vertices.right + 0.5f);
     197           0 :             Call.m_Vertices.top = (int)(Call.m_Vertices.top + 0.5f);
     198           0 :             Call.m_Vertices.bottom = (int)(Call.m_Vertices.bottom + 0.5f);
     199             :         }
     200             : 
     201           0 :         bool hasTexture = false;
     202           0 :         if (!(*cit)->m_TextureName.empty())
     203             :         {
     204           0 :             CTextureProperties textureProps(g_L10n.LocalizePath((*cit)->m_TextureName));
     205           0 :             textureProps.SetAddressMode((*cit)->m_AddressMode);
     206           0 :             textureProps.SetIgnoreQuality(true);
     207           0 :             CTexturePtr texture = g_Renderer.GetTextureManager().CreateTexture(textureProps);
     208           0 :             texture->Prefetch();
     209           0 :             hasTexture = true;
     210           0 :             Call.m_Texture = texture;
     211           0 :             Call.m_ObjectSize = ObjectSize;
     212             :         }
     213             : 
     214           0 :         Call.m_BackColor = &(*cit)->m_BackColor;
     215           0 :         Call.m_GrayscaleFactor = 0.0f;
     216           0 :         if (!hasTexture)
     217             :         {
     218           0 :             Call.m_ColorAdd = *Call.m_BackColor;
     219           0 :             Call.m_ColorMultiply = CColor(0.0f, 0.0f, 0.0f, 0.0f);
     220           0 :             Call.m_Texture = g_Renderer.GetTextureManager().GetTransparentTexture();
     221             :         }
     222           0 :         else if ((*cit)->m_Effects)
     223             :         {
     224           0 :             if ((*cit)->m_Effects->m_AddColor != CGUIColor())
     225             :             {
     226           0 :                 const CColor color = (*cit)->m_Effects->m_AddColor;
     227           0 :                 Call.m_ColorAdd = CColor(color.r, color.g, color.b, 0.0f);
     228           0 :                 Call.m_ColorMultiply = CColor(1.0f, 1.0f, 1.0f, 1.0f);
     229             :             }
     230           0 :             else if ((*cit)->m_Effects->m_Greyscale)
     231             :             {
     232           0 :                 Call.m_ColorAdd = CColor(0.0f, 0.0f, 0.0f, 0.0f);
     233           0 :                 Call.m_ColorMultiply = CColor(1.0f, 1.0f, 1.0f, 1.0f);
     234           0 :                 Call.m_GrayscaleFactor = 1.0f;
     235             :             }
     236           0 :             else if ((*cit)->m_Effects->m_SolidColor != CGUIColor())
     237             :             {
     238           0 :                 const CColor color = (*cit)->m_Effects->m_SolidColor;
     239           0 :                 Call.m_ColorAdd = CColor(color.r, color.g, color.b, 0.0f);
     240           0 :                 Call.m_ColorMultiply = CColor(0.0f, 0.0f, 0.0f, color.a);
     241             :             }
     242             :             else /* Slight confusion - why no effects? */
     243             :             {
     244           0 :                 Call.m_ColorAdd = CColor(0.0f, 0.0f, 0.0f, 0.0f);
     245           0 :                 Call.m_ColorMultiply = CColor(1.0f, 1.0f, 1.0f, 1.0f);
     246             :             }
     247             :         }
     248             :         else
     249             :         {
     250           0 :             Call.m_ColorAdd = CColor(0.0f, 0.0f, 0.0f, 0.0f);
     251           0 :             Call.m_ColorMultiply = CColor(1.0f, 1.0f, 1.0f, 1.0f);
     252             :         }
     253             : 
     254           0 :         Calls.push_back(Call);
     255             :     }
     256             : }
     257             : 
     258           0 : CRect SDrawCall::ComputeTexCoords() const
     259             : {
     260           0 :     float TexWidth = m_Texture->GetWidth();
     261           0 :     float TexHeight = m_Texture->GetHeight();
     262             : 
     263           0 :     if (!TexWidth || !TexHeight)
     264           0 :         return CRect(0, 0, 1, 1);
     265             : 
     266             :     // Textures are positioned by defining a rectangular block of the
     267             :     // texture (usually the whole texture), and a rectangular block on
     268             :     // the screen. The texture is positioned to make those blocks line up.
     269             : 
     270             :     // Get the screen's position/size for the block
     271           0 :     CRect BlockScreen = m_Image->m_TextureSize.GetSize(m_ObjectSize);
     272             : 
     273           0 :     if (m_Image->m_FixedHAspectRatio)
     274           0 :         BlockScreen.right = BlockScreen.left + BlockScreen.GetHeight() * m_Image->m_FixedHAspectRatio;
     275             : 
     276             :     // Get the texture's position/size for the block:
     277           0 :     CRect BlockTex;
     278             : 
     279             :     // "real_texture_placement" overrides everything
     280           0 :     if (m_Image->m_TexturePlacementInFile != CRect())
     281           0 :         BlockTex = m_Image->m_TexturePlacementInFile;
     282             :     // Use the whole texture
     283             :     else
     284           0 :         BlockTex = CRect(0, 0, TexWidth, TexHeight);
     285             : 
     286             :     // When rendering, BlockTex will be transformed onto BlockScreen.
     287             :     // Also, TexCoords will be transformed onto ObjectSize (giving the
     288             :     // UV coords at each vertex of the object). We know everything
     289             :     // except for TexCoords, so calculate it:
     290             : 
     291           0 :     CVector2D translation(BlockTex.TopLeft()-BlockScreen.TopLeft());
     292           0 :     float ScaleW = BlockTex.GetWidth()/BlockScreen.GetWidth();
     293           0 :     float ScaleH = BlockTex.GetHeight()/BlockScreen.GetHeight();
     294             : 
     295           0 :     CRect TexCoords (
     296             :                 // Resize (translating to/from the origin, so the
     297             :                 // topleft corner stays in the same place)
     298           0 :                 (m_ObjectSize-m_ObjectSize.TopLeft())
     299           0 :                 .Scale(ScaleW, ScaleH)
     300           0 :                 + m_ObjectSize.TopLeft()
     301             :                 // Translate from BlockTex to BlockScreen
     302             :                 + translation
     303           0 :     );
     304             : 
     305             :     // The tex coords need to be scaled so that (texwidth,texheight) is
     306             :     // mapped onto (1,1)
     307           0 :     TexCoords.left   /= TexWidth;
     308           0 :     TexCoords.right  /= TexWidth;
     309           0 :     TexCoords.top    /= TexHeight;
     310           0 :     TexCoords.bottom /= TexHeight;
     311             : 
     312           0 :     return TexCoords;
     313             : }
     314             : 
     315           0 : void GUIRenderer::Draw(DrawCalls& Calls, CCanvas2D& canvas)
     316             : {
     317           0 :     if (Calls.empty())
     318             :         return;
     319             : 
     320             :     // Called every frame, to draw the object (based on cached calculations)
     321             : 
     322             :     // Iterate through each DrawCall, and execute whatever drawing code is being called
     323           0 :     for (DrawCalls::const_iterator cit = Calls.begin(); cit != Calls.end(); ++cit)
     324             :     {
     325             :         // A hack to get a correct backend texture size.
     326           0 :         cit->m_Texture->UploadBackendTextureIfNeeded(g_Renderer.GetDeviceCommandContext());
     327             : 
     328           0 :         CRect texCoords = cit->ComputeTexCoords().Scale(
     329           0 :             cit->m_Texture->GetWidth(), cit->m_Texture->GetHeight());
     330             : 
     331             :         // Ensure the quad has the correct winding order
     332           0 :         CRect rect = cit->m_Vertices;
     333           0 :         if (rect.right < rect.left)
     334             :         {
     335           0 :             std::swap(rect.right, rect.left);
     336           0 :             std::swap(texCoords.right, texCoords.left);
     337             :         }
     338           0 :         if (rect.bottom < rect.top)
     339             :         {
     340           0 :             std::swap(rect.bottom, rect.top);
     341           0 :             std::swap(texCoords.bottom, texCoords.top);
     342             :         }
     343             : 
     344           0 :         canvas.DrawTexture(cit->m_Texture,
     345           0 :             rect, texCoords, cit->m_ColorMultiply, cit->m_ColorAdd, cit->m_GrayscaleFactor);
     346             :     }
     347             : }

Generated by: LCOV version 1.13