1 /* Copyright (C) 2022 Wildfire Games.
2 * This file is part of 0 A.D.
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.
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.
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/>.
18 #include "precompiled.h"
20 #include "GUIRenderer.h"
22 #include "graphics/Canvas2D.h"
23 #include "graphics/TextureManager.h"
25 #include "gui/CGUISprite.h"
26 #include "gui/SettingTypes/CGUIColor.h"
27 #include "i18n/L10n.h"
28 #include "lib/tex/tex.h"
30 #include "ps/CLogger.h"
31 #include "ps/CStrInternStatic.h"
32 #include "ps/Filesystem.h"
33 #include "renderer/Renderer.h"
35 using namespace GUIRenderer
;
37 DrawCalls::DrawCalls()
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
48 DrawCalls::DrawCalls(const DrawCalls
&)
49 : std::vector
<SDrawCall
>()
53 DrawCalls
& DrawCalls::operator=(const DrawCalls
&)
59 void GUIRenderer::UpdateDrawCallCache(const CGUI
& pGUI
, DrawCalls
& Calls
, const CStr
& SpriteName
, const CRect
& Size
, std::map
<CStr
, std::unique_ptr
<const CGUISprite
>>& Sprites
)
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.
64 // Clean up the old data
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 if (Size
.left
== Size
.right
&& Size
.top
== Size
.bottom
)
73 std::map
<CStr
, std::unique_ptr
<const CGUISprite
>>::iterator
it(Sprites
.find(SpriteName
));
74 if (it
== Sprites
.end())
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"
88 // Check that this can be a special sprite.
89 if (SpriteName
.ReverseFind(":") == -1 && SpriteName
.Find("color(") == -1)
91 LOGERROR("Trying to use a sprite that doesn't exist (\"%s\").", SpriteName
.c_str());
95 auto sprite
= std::make_unique
<CGUISprite
>();
96 VfsPath TextureName
= VfsPath("art/textures/ui") / wstring_from_utf8(SpriteName
.AfterLast(":"));
97 if (SpriteName
.Find("stretched:") != -1)
99 // TODO: Should check (nicely) that this is a valid file?
100 auto image
= std::make_unique
<SGUIImage
>();
102 image
->m_TextureName
= TextureName
;
103 if (SpriteName
.Find("grayscale:") != -1)
105 image
->m_Effects
= std::make_shared
<SGUIImageEffects
>();
106 image
->m_Effects
->m_Greyscale
= true;
109 sprite
->AddImage(std::move(image
));
111 else if (SpriteName
.Find("cropped:") != -1)
113 // TODO: Should check (nicely) that this is a valid file?
114 auto image
= std::make_unique
<SGUIImage
>();
116 const bool centered
= SpriteName
.Find("center:") != -1;
118 CStr info
= SpriteName
.AfterLast("cropped:").BeforeFirst(":");
119 double xRatio
= info
.BeforeFirst(",").ToDouble();
120 double yRatio
= info
.AfterLast(",").ToDouble();
121 const CRect percentSize
= centered
122 ? CRect(50 - 50 / xRatio
, 50 - 50 / yRatio
, 50 + 50 / xRatio
, 50 + 50 / yRatio
)
123 : CRect(0, 0, 100 / xRatio
, 100 / yRatio
);
124 image
->m_TextureSize
= CGUISize(CRect(0, 0, 0, 0), percentSize
);
125 image
->m_TextureName
= TextureName
;
127 if (SpriteName
.Find("grayscale:") != -1)
129 image
->m_Effects
= std::make_shared
<SGUIImageEffects
>();
130 image
->m_Effects
->m_Greyscale
= true;
133 sprite
->AddImage(std::move(image
));
135 if (SpriteName
.Find("color:") != -1)
137 CStrW value
= wstring_from_utf8(SpriteName
.AfterLast("color:").BeforeFirst(":"));
139 auto image
= std::make_unique
<SGUIImage
>();
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 if (SpriteName
.Find("textureAsMask:") != -1)
147 image
->m_TextureName
= TextureName
;
148 image
->m_Effects
= std::make_shared
<SGUIImageEffects
>();
149 color
= &image
->m_Effects
->m_SolidColor
;
152 color
= &image
->m_BackColor
;
154 // Check color is valid
155 if (!CGUI::ParseString
<CGUIColor
>(&pGUI
, value
, *color
))
157 LOGERROR("GUI: Error parsing sprite 'color' (\"%s\")", utf8_from_wstring(value
));
161 sprite
->AddImage(std::move(image
));
164 if (sprite
->m_Images
.empty())
166 LOGERROR("Trying to use a sprite that doesn't exist (\"%s\").", SpriteName
.c_str());
170 it
= Sprites
.emplace(SpriteName
, std::move(sprite
)).first
;
173 Calls
.reserve(it
->second
->m_Images
.size());
175 // Iterate through all the sprite's images, loading the texture and
176 // calculating the texture coordinates
177 std::vector
<std::unique_ptr
<SGUIImage
>>::const_iterator cit
;
178 for (cit
= it
->second
->m_Images
.begin(); cit
!= it
->second
->m_Images
.end(); ++cit
)
180 SDrawCall
Call(cit
->get()); // pointers are safe since we never modify sprites/images after startup
182 CRect ObjectSize
= (*cit
)->m_Size
.GetSize(Size
);
184 if (ObjectSize
.GetWidth() == 0.0 || ObjectSize
.GetHeight() == 0.0)
186 // Zero sized object. Don't report as an error, since it's common for e.g. hitpoint bars.
187 continue; // i.e. don't continue with this image
190 Call
.m_Vertices
= ObjectSize
;
191 if ((*cit
)->m_RoundCoordinates
)
193 // Round the vertex coordinates to integers, to avoid ugly filtering artifacts
194 Call
.m_Vertices
.left
= (int)(Call
.m_Vertices
.left
+ 0.5f
);
195 Call
.m_Vertices
.right
= (int)(Call
.m_Vertices
.right
+ 0.5f
);
196 Call
.m_Vertices
.top
= (int)(Call
.m_Vertices
.top
+ 0.5f
);
197 Call
.m_Vertices
.bottom
= (int)(Call
.m_Vertices
.bottom
+ 0.5f
);
200 bool hasTexture
= false;
201 if (!(*cit
)->m_TextureName
.empty())
203 CTextureProperties
textureProps(g_L10n
.LocalizePath((*cit
)->m_TextureName
));
204 textureProps
.SetAddressMode((*cit
)->m_AddressMode
);
205 textureProps
.SetIgnoreQuality(true);
206 CTexturePtr texture
= g_Renderer
.GetTextureManager().CreateTexture(textureProps
);
209 Call
.m_Texture
= texture
;
210 Call
.m_ObjectSize
= ObjectSize
;
213 Call
.m_BackColor
= &(*cit
)->m_BackColor
;
214 Call
.m_GrayscaleFactor
= 0.0f
;
217 Call
.m_ColorAdd
= *Call
.m_BackColor
;
218 Call
.m_ColorMultiply
= CColor(0.0f
, 0.0f
, 0.0f
, 0.0f
);
219 Call
.m_Texture
= g_Renderer
.GetTextureManager().GetTransparentTexture();
221 else if ((*cit
)->m_Effects
)
223 if ((*cit
)->m_Effects
->m_AddColor
!= CGUIColor())
225 const CColor color
= (*cit
)->m_Effects
->m_AddColor
;
226 Call
.m_ColorAdd
= CColor(color
.r
, color
.g
, color
.b
, 0.0f
);
227 Call
.m_ColorMultiply
= CColor(1.0f
, 1.0f
, 1.0f
, 1.0f
);
229 else if ((*cit
)->m_Effects
->m_Greyscale
)
231 Call
.m_ColorAdd
= CColor(0.0f
, 0.0f
, 0.0f
, 0.0f
);
232 Call
.m_ColorMultiply
= CColor(1.0f
, 1.0f
, 1.0f
, 1.0f
);
233 Call
.m_GrayscaleFactor
= 1.0f
;
235 else if ((*cit
)->m_Effects
->m_SolidColor
!= CGUIColor())
237 const CColor color
= (*cit
)->m_Effects
->m_SolidColor
;
238 Call
.m_ColorAdd
= CColor(color
.r
, color
.g
, color
.b
, 0.0f
);
239 Call
.m_ColorMultiply
= CColor(0.0f
, 0.0f
, 0.0f
, color
.a
);
241 else /* Slight confusion - why no effects? */
243 Call
.m_ColorAdd
= CColor(0.0f
, 0.0f
, 0.0f
, 0.0f
);
244 Call
.m_ColorMultiply
= CColor(1.0f
, 1.0f
, 1.0f
, 1.0f
);
249 Call
.m_ColorAdd
= CColor(0.0f
, 0.0f
, 0.0f
, 0.0f
);
250 Call
.m_ColorMultiply
= CColor(1.0f
, 1.0f
, 1.0f
, 1.0f
);
253 Calls
.push_back(Call
);
257 CRect
SDrawCall::ComputeTexCoords() const
259 float TexWidth
= m_Texture
->GetWidth();
260 float TexHeight
= m_Texture
->GetHeight();
262 if (!TexWidth
|| !TexHeight
)
263 return CRect(0, 0, 1, 1);
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.
269 // Get the screen's position/size for the block
270 CRect BlockScreen
= m_Image
->m_TextureSize
.GetSize(m_ObjectSize
);
272 if (m_Image
->m_FixedHAspectRatio
)
273 BlockScreen
.right
= BlockScreen
.left
+ BlockScreen
.GetHeight() * m_Image
->m_FixedHAspectRatio
;
275 // Get the texture's position/size for the block:
278 // "real_texture_placement" overrides everything
279 if (m_Image
->m_TexturePlacementInFile
!= CRect())
280 BlockTex
= m_Image
->m_TexturePlacementInFile
;
281 // Use the whole texture
283 BlockTex
= CRect(0, 0, TexWidth
, TexHeight
);
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:
290 CVector2D
translation(BlockTex
.TopLeft()-BlockScreen
.TopLeft());
291 float ScaleW
= BlockTex
.GetWidth()/BlockScreen
.GetWidth();
292 float ScaleH
= BlockTex
.GetHeight()/BlockScreen
.GetHeight();
295 // Resize (translating to/from the origin, so the
296 // topleft corner stays in the same place)
297 (m_ObjectSize
-m_ObjectSize
.TopLeft())
298 .Scale(ScaleW
, ScaleH
)
299 + m_ObjectSize
.TopLeft()
300 // Translate from BlockTex to BlockScreen
304 // The tex coords need to be scaled so that (texwidth,texheight) is
306 TexCoords
.left
/= TexWidth
;
307 TexCoords
.right
/= TexWidth
;
308 TexCoords
.top
/= TexHeight
;
309 TexCoords
.bottom
/= TexHeight
;
314 void GUIRenderer::Draw(DrawCalls
& Calls
, CCanvas2D
& canvas
)
319 // Called every frame, to draw the object (based on cached calculations)
321 // Iterate through each DrawCall, and execute whatever drawing code is being called
322 for (DrawCalls::const_iterator cit
= Calls
.begin(); cit
!= Calls
.end(); ++cit
)
324 // A hack to get a correct backend texture size.
325 cit
->m_Texture
->UploadBackendTextureIfNeeded(g_Renderer
.GetDeviceCommandContext());
327 CRect texCoords
= cit
->ComputeTexCoords().Scale(
328 cit
->m_Texture
->GetWidth(), cit
->m_Texture
->GetHeight());
330 // Ensure the quad has the correct winding order
331 CRect rect
= cit
->m_Vertices
;
332 if (rect
.right
< rect
.left
)
334 std::swap(rect
.right
, rect
.left
);
335 std::swap(texCoords
.right
, texCoords
.left
);
337 if (rect
.bottom
< rect
.top
)
339 std::swap(rect
.bottom
, rect
.top
);
340 std::swap(texCoords
.bottom
, texCoords
.top
);
343 canvas
.DrawTexture(cit
->m_Texture
,
344 rect
, texCoords
, cit
->m_ColorMultiply
, cit
->m_ColorAdd
, cit
->m_GrayscaleFactor
);