1 /* Copyright (C) 2013 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/ShaderManager.h"
23 #include "graphics/TextureManager.h"
24 #include "gui/GUIutil.h"
27 #include "lib/res/h_mgr.h"
28 #include "lib/tex/tex.h"
29 #include "ps/CLogger.h"
30 #include "ps/Filesystem.h"
31 #include "renderer/Renderer.h"
34 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(DrawCalls
&Calls
, const CStr
& SpriteName
, const CRect
&Size
, int CellID
, std::map
<CStr
, 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
)
74 std::map
<CStr
, CGUISprite
>::iterator
it (Sprites
.find(SpriteName
));
75 if (it
== Sprites
.end())
77 // Sprite not found. Check whether this a special sprite:
78 // "stretched:filename.ext" - stretched image
79 // "stretched:grayscale:filename.ext" - stretched grayscale image
80 // "cropped:(0.5, 0.25)" - stretch this ratio (x,y) of the top left of the image
81 // "colour:r g b a" - solid colour
83 // and if so, try to create it as a new sprite.
84 if (SpriteName
.substr(0, 10) == "stretched:")
86 // TODO: Should check (nicely) that this is a valid file?
89 // Allow grayscale images for disabled portraits
90 if (SpriteName
.substr(10, 10) == "grayscale:")
92 Image
.m_TextureName
= VfsPath("art/textures/ui") / wstring_from_utf8(SpriteName
.substr(20));
93 Image
.m_Effects
= new SGUIImageEffects
;
94 Image
.m_Effects
->m_Greyscale
= true;
98 Image
.m_TextureName
= VfsPath("art/textures/ui") / wstring_from_utf8(SpriteName
.substr(10));
101 CClientArea
ca(CRect(0, 0, 0, 0), CRect(0, 0, 100, 100));
103 Image
.m_TextureSize
= ca
;
106 Sprite
.AddImage(Image
);
108 Sprites
[SpriteName
] = Sprite
;
110 it
= Sprites
.find(SpriteName
);
111 ENSURE(it
!= Sprites
.end()); // The insertion above shouldn't fail
113 else if (SpriteName
.substr(0, 8) == "cropped:")
115 // TODO: Should check (nicely) that this is a valid file?
118 double xRatio
= SpriteName
.BeforeFirst(",").AfterLast("(").ToDouble();
119 double yRatio
= SpriteName
.BeforeFirst(")").AfterLast(",").ToDouble();
121 int PathStart
= SpriteName
.Find(")") + 1;
123 Image
.m_TextureName
= VfsPath("art/textures/ui") / wstring_from_utf8(SpriteName
.substr(PathStart
));
125 CClientArea
ca(CRect(0, 0, 0, 0), CRect(0, 0, 100, 100));
126 CClientArea
cb(CRect(0, 0, 0, 0), CRect(0, 0, 100/xRatio
, 100/yRatio
));
128 Image
.m_TextureSize
= cb
;
131 Sprite
.AddImage(Image
);
133 Sprites
[SpriteName
] = Sprite
;
135 it
= Sprites
.find(SpriteName
);
136 ENSURE(it
!= Sprites
.end()); // The insertion above shouldn't fail
138 else if (SpriteName
.substr(0, 7) == "colour:")
140 CStrW value
= wstring_from_utf8(SpriteName
.substr(7));
143 // Check colour is valid
144 if (!GUI
<CColor
>::ParseString(value
, color
))
146 LOGERROR(L
"GUI: Error parsing sprite 'colour' (\"%ls\")", value
.c_str());
152 image
.m_BackColor
= color
;
154 CClientArea
ca(CRect(0, 0, 0, 0), CRect(0, 0, 100, 100));
156 image
.m_TextureSize
= ca
;
159 Sprite
.AddImage(image
);
161 Sprites
[SpriteName
] = Sprite
;
163 it
= Sprites
.find(SpriteName
);
164 ENSURE(it
!= Sprites
.end()); // The insertion above shouldn't fail
168 // Otherwise, just complain and give up:
169 LOGERROR(L
"Trying to use a sprite that doesn't exist (\"%hs\").", SpriteName
.c_str());
174 Calls
.reserve(it
->second
.m_Images
.size());
176 // Iterate through all the sprite's images, loading the texture and
177 // calculating the texture coordinates
178 std::vector
<SGUIImage
>::const_iterator cit
;
179 for (cit
= it
->second
.m_Images
.begin(); cit
!= it
->second
.m_Images
.end(); ++cit
)
181 SDrawCall
Call(&*cit
); // pointers are safe since we never modify sprites/images after startup
183 CRect ObjectSize
= cit
->m_Size
.GetClientArea(Size
);
185 if (ObjectSize
.GetWidth() == 0.0 || ObjectSize
.GetHeight() == 0.0)
187 // Zero sized object. Don't report as an error, since it's common for e.g. hitpoint bars.
188 continue; // i.e. don't continue with this image
191 Call
.m_Vertices
= ObjectSize
;
192 if (cit
->m_RoundCoordinates
)
194 // Round the vertex coordinates to integers, to avoid ugly filtering artifacts
195 Call
.m_Vertices
.left
= (int)(Call
.m_Vertices
.left
+ 0.5f
);
196 Call
.m_Vertices
.right
= (int)(Call
.m_Vertices
.right
+ 0.5f
);
197 Call
.m_Vertices
.top
= (int)(Call
.m_Vertices
.top
+ 0.5f
);
198 Call
.m_Vertices
.bottom
= (int)(Call
.m_Vertices
.bottom
+ 0.5f
);
201 if (! cit
->m_TextureName
.empty())
203 CTextureProperties
textureProps(cit
->m_TextureName
);
204 textureProps
.SetWrap(cit
->m_WrapMode
);
205 CTexturePtr texture
= g_Renderer
.GetTextureManager().CreateTexture(textureProps
);
207 Call
.m_HasTexture
= true;
208 Call
.m_Texture
= texture
;
210 Call
.m_EnableBlending
= false; // will be overridden if the texture has an alpha channel
212 Call
.m_ObjectSize
= ObjectSize
;
213 Call
.m_CellID
= CellID
;
217 Call
.m_HasTexture
= false;
218 // Enable blending if it's transparent (allowing a little error in the calculations)
219 Call
.m_EnableBlending
= !(fabs(cit
->m_BackColor
.a
- 1.0f
) < 0.0000001f
);
222 Call
.m_BackColor
= cit
->m_BackColor
;
223 Call
.m_BorderColor
= cit
->m_Border
? cit
->m_BorderColor
: CColor();
224 Call
.m_DeltaZ
= cit
->m_DeltaZ
;
226 if (!Call
.m_HasTexture
)
228 Call
.m_Shader
= g_Renderer
.GetShaderManager().LoadEffect(str_gui_solid
);
230 else if (cit
->m_Effects
)
232 if (cit
->m_Effects
->m_AddColor
!= CColor())
234 Call
.m_Shader
= g_Renderer
.GetShaderManager().LoadEffect(str_gui_add
);
235 Call
.m_ShaderColorParameter
= cit
->m_Effects
->m_AddColor
;
236 // Always enable blending if something's being subtracted from
238 if (cit
->m_Effects
->m_AddColor
.a
< 0.f
)
239 Call
.m_EnableBlending
= true;
241 else if (cit
->m_Effects
->m_Greyscale
)
243 Call
.m_Shader
= g_Renderer
.GetShaderManager().LoadEffect(str_gui_grayscale
);
245 else /* Slight confusion - why no effects? */
247 Call
.m_Shader
= g_Renderer
.GetShaderManager().LoadEffect(str_gui_basic
);
252 Call
.m_Shader
= g_Renderer
.GetShaderManager().LoadEffect(str_gui_basic
);
255 Calls
.push_back(Call
);
259 CRect
SDrawCall::ComputeTexCoords() const
261 float TexWidth
= m_Texture
->GetWidth();
262 float TexHeight
= m_Texture
->GetHeight();
264 if (!TexWidth
|| !TexHeight
)
266 return CRect(0, 0, 1, 1);
269 // Textures are positioned by defining a rectangular block of the
270 // texture (usually the whole texture), and a rectangular block on
271 // the screen. The texture is positioned to make those blocks line up.
273 // Get the screen's position/size for the block
274 CRect BlockScreen
= m_Image
->m_TextureSize
.GetClientArea(m_ObjectSize
);
276 if (m_Image
->m_FixedHAspectRatio
)
277 BlockScreen
.right
= BlockScreen
.left
+ BlockScreen
.GetHeight() * m_Image
->m_FixedHAspectRatio
;
279 // Get the texture's position/size for the block:
282 // "real_texture_placement" overrides everything
283 if (m_Image
->m_TexturePlacementInFile
!= CRect())
285 BlockTex
= m_Image
->m_TexturePlacementInFile
;
287 // Check whether this sprite has "cell_size" set (and non-zero)
288 else if ((int)m_Image
->m_CellSize
.cx
)
290 int cols
= (int)TexWidth
/ (int)m_Image
->m_CellSize
.cx
;
292 cols
= 1; // avoid divide-by-zero
293 int col
= m_CellID
% cols
;
294 int row
= m_CellID
/ cols
;
295 BlockTex
= CRect(m_Image
->m_CellSize
.cx
*col
, m_Image
->m_CellSize
.cy
*row
,
296 m_Image
->m_CellSize
.cx
*(col
+1), m_Image
->m_CellSize
.cy
*(row
+1));
298 // Use the whole texture
300 BlockTex
= CRect(0, 0, TexWidth
, TexHeight
);
302 // When rendering, BlockTex will be transformed onto BlockScreen.
303 // Also, TexCoords will be transformed onto ObjectSize (giving the
304 // UV coords at each vertex of the object). We know everything
305 // except for TexCoords, so calculate it:
307 CPos
translation (BlockTex
.TopLeft()-BlockScreen
.TopLeft());
308 float ScaleW
= BlockTex
.GetWidth()/BlockScreen
.GetWidth();
309 float ScaleH
= BlockTex
.GetHeight()/BlockScreen
.GetHeight();
312 // Resize (translating to/from the origin, so the
313 // topleft corner stays in the same place)
314 (m_ObjectSize
-m_ObjectSize
.TopLeft())
315 .Scale(ScaleW
, ScaleH
)
316 + m_ObjectSize
.TopLeft()
317 // Translate from BlockTex to BlockScreen
321 // The tex coords need to be scaled so that (texwidth,texheight) is
323 TexCoords
.left
/= TexWidth
;
324 TexCoords
.right
/= TexWidth
;
325 TexCoords
.top
/= TexHeight
;
326 TexCoords
.bottom
/= TexHeight
;
331 void GUIRenderer::Draw(DrawCalls
&Calls
, float Z
)
333 // Called every frame, to draw the object (based on cached calculations)
335 // TODO: batching by shader/texture/etc would be nice
337 CMatrix3D matrix
= GetDefaultGuiMatrix();
341 // Set LOD bias so mipmapped textures are prettier
343 #warning TODO: implement GUI LOD bias for GLES
345 glTexEnvf(GL_TEXTURE_FILTER_CONTROL
, GL_TEXTURE_LOD_BIAS
, -1.f
);
348 // Iterate through each DrawCall, and execute whatever drawing code is being called
349 for (DrawCalls::const_iterator cit
= Calls
.begin(); cit
!= Calls
.end(); ++cit
)
351 cit
->m_Shader
->BeginPass();
352 CShaderProgramPtr shader
= cit
->m_Shader
->GetShader();
353 shader
->Uniform(str_transform
, matrix
);
355 if (cit
->m_HasTexture
)
357 shader
->Uniform(str_color
, cit
->m_ShaderColorParameter
);
358 shader
->BindTexture(str_tex
, cit
->m_Texture
);
360 if (cit
->m_EnableBlending
|| cit
->m_Texture
->HasAlpha()) // (shouldn't call HasAlpha before BindTexture)
362 glBlendFunc(GL_SRC_ALPHA
, GL_ONE_MINUS_SRC_ALPHA
);
366 CRect TexCoords
= cit
->ComputeTexCoords();
368 // Ensure the quad has the correct winding order, and update texcoords to match
369 CRect Verts
= cit
->m_Vertices
;
370 if (Verts
.right
< Verts
.left
)
372 std::swap(Verts
.right
, Verts
.left
);
373 std::swap(TexCoords
.right
, TexCoords
.left
);
375 if (Verts
.bottom
< Verts
.top
)
377 std::swap(Verts
.bottom
, Verts
.top
);
378 std::swap(TexCoords
.bottom
, TexCoords
.top
);
381 std::vector
<float> data
;
382 #define ADD(u, v, x, y, z) STMT(data.push_back(u); data.push_back(v); data.push_back(x); data.push_back(y); data.push_back(z))
383 ADD(TexCoords
.left
, TexCoords
.bottom
, Verts
.left
, Verts
.bottom
, Z
+ cit
->m_DeltaZ
);
384 ADD(TexCoords
.right
, TexCoords
.bottom
, Verts
.right
, Verts
.bottom
, Z
+ cit
->m_DeltaZ
);
385 ADD(TexCoords
.right
, TexCoords
.top
, Verts
.right
, Verts
.top
, Z
+ cit
->m_DeltaZ
);
387 ADD(TexCoords
.right
, TexCoords
.top
, Verts
.right
, Verts
.top
, Z
+ cit
->m_DeltaZ
);
388 ADD(TexCoords
.left
, TexCoords
.top
, Verts
.left
, Verts
.top
, Z
+ cit
->m_DeltaZ
);
389 ADD(TexCoords
.left
, TexCoords
.bottom
, Verts
.left
, Verts
.bottom
, Z
+ cit
->m_DeltaZ
);
392 shader
->TexCoordPointer(GL_TEXTURE0
, 2, GL_FLOAT
, 5*sizeof(float), &data
[0]);
393 shader
->VertexPointer(3, GL_FLOAT
, 5*sizeof(float), &data
[2]);
394 glDrawArrays(GL_TRIANGLES
, 0, 6);
398 shader
->Uniform(str_color
, cit
->m_BackColor
);
400 if (cit
->m_EnableBlending
)
402 glBlendFunc(GL_SRC_ALPHA
, GL_ONE_MINUS_SRC_ALPHA
);
406 // Ensure the quad has the correct winding order
407 CRect Verts
= cit
->m_Vertices
;
408 if (Verts
.right
< Verts
.left
)
409 std::swap(Verts
.right
, Verts
.left
);
410 if (Verts
.bottom
< Verts
.top
)
411 std::swap(Verts
.bottom
, Verts
.top
);
413 std::vector
<float> data
;
414 #define ADD(x, y, z) STMT(data.push_back(x); data.push_back(y); data.push_back(z))
415 ADD(Verts
.left
, Verts
.bottom
, Z
+ cit
->m_DeltaZ
);
416 ADD(Verts
.right
, Verts
.bottom
, Z
+ cit
->m_DeltaZ
);
417 ADD(Verts
.right
, Verts
.top
, Z
+ cit
->m_DeltaZ
);
419 ADD(Verts
.right
, Verts
.top
, Z
+ cit
->m_DeltaZ
);
420 ADD(Verts
.left
, Verts
.top
, Z
+ cit
->m_DeltaZ
);
421 ADD(Verts
.left
, Verts
.bottom
, Z
+ cit
->m_DeltaZ
);
423 shader
->VertexPointer(3, GL_FLOAT
, 3*sizeof(float), &data
[0]);
424 glDrawArrays(GL_TRIANGLES
, 0, 6);
426 if (cit
->m_BorderColor
!= CColor())
428 shader
->Uniform(str_color
, cit
->m_BorderColor
);
431 ADD(Verts
.left
+ 0.5f
, Verts
.top
+ 0.5f
, Z
+ cit
->m_DeltaZ
);
432 ADD(Verts
.right
- 0.5f
, Verts
.top
+ 0.5f
, Z
+ cit
->m_DeltaZ
);
433 ADD(Verts
.right
- 0.5f
, Verts
.bottom
- 0.5f
, Z
+ cit
->m_DeltaZ
);
434 ADD(Verts
.left
+ 0.5f
, Verts
.bottom
- 0.5f
, Z
+ cit
->m_DeltaZ
);
436 shader
->VertexPointer(3, GL_FLOAT
, 3*sizeof(float), &data
[0]);
437 glDrawArrays(GL_LINE_LOOP
, 0, 4);
442 cit
->m_Shader
->EndPass();
448 #warning TODO: implement GUI LOD bias for GLES
450 glTexEnvf(GL_TEXTURE_FILTER_CONTROL
, GL_TEXTURE_LOD_BIAS
, 0.f
);