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

Generated by: LCOV version 1.13