[Windows] Automated build.
[0ad.git] / source / gui / GUIRenderer.cpp
blob2531f01d606e82cd404d8a64935e0c1e83b18b05
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"
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"
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
46 // in practice?)
48 DrawCalls::DrawCalls(const DrawCalls&)
49 : std::vector<SDrawCall>()
53 DrawCalls& DrawCalls::operator=(const DrawCalls&)
55 return *this;
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
65 Calls.clear();
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)
71 return;
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());
92 return;
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>();
140 CGUIColor* color;
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;
151 else
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));
158 return;
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());
167 return;
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);
207 texture->Prefetch();
208 hasTexture = true;
209 Call.m_Texture = texture;
210 Call.m_ObjectSize = ObjectSize;
213 Call.m_BackColor = &(*cit)->m_BackColor;
214 Call.m_GrayscaleFactor = 0.0f;
215 if (!hasTexture)
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);
247 else
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:
276 CRect BlockTex;
278 // "real_texture_placement" overrides everything
279 if (m_Image->m_TexturePlacementInFile != CRect())
280 BlockTex = m_Image->m_TexturePlacementInFile;
281 // Use the whole texture
282 else
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();
294 CRect TexCoords (
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
301 + translation
304 // The tex coords need to be scaled so that (texwidth,texheight) is
305 // mapped onto (1,1)
306 TexCoords.left /= TexWidth;
307 TexCoords.right /= TexWidth;
308 TexCoords.top /= TexHeight;
309 TexCoords.bottom /= TexHeight;
311 return TexCoords;
314 void GUIRenderer::Draw(DrawCalls& Calls, CCanvas2D& canvas)
316 if (Calls.empty())
317 return;
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);