Added different textures for each ptolemaic camel rank
[0ad.git] / source / gui / GUIRenderer.cpp
blob95311584e85e200989e0d3daf78436de8182e093
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"
25 #include "lib/ogl.h"
26 #include "lib/utf8.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
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(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
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;
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?
87 SGUIImage Image;
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;
96 else
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));
102 Image.m_Size = ca;
103 Image.m_TextureSize = ca;
105 CGUISprite Sprite;
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?
116 SGUIImage Image;
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));
127 Image.m_Size = ca;
128 Image.m_TextureSize = cb;
130 CGUISprite Sprite;
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));
141 CColor color;
143 // Check colour is valid
144 if (!GUI<CColor>::ParseString(value, color))
146 LOGERROR(L"GUI: Error parsing sprite 'colour' (\"%ls\")", value.c_str());
147 return;
150 SGUIImage image;
152 image.m_BackColor = color;
154 CClientArea ca(CRect(0, 0, 0, 0), CRect(0, 0, 100, 100));
155 image.m_Size = ca;
156 image.m_TextureSize = ca;
158 CGUISprite Sprite;
159 Sprite.AddImage(image);
161 Sprites[SpriteName] = Sprite;
163 it = Sprites.find(SpriteName);
164 ENSURE(it != Sprites.end()); // The insertion above shouldn't fail
166 else
168 // Otherwise, just complain and give up:
169 LOGERROR(L"Trying to use a sprite that doesn't exist (\"%hs\").", SpriteName.c_str());
170 return;
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);
206 texture->Prefetch();
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;
215 else
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
237 // the alpha channel
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);
250 else
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:
280 CRect BlockTex;
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;
291 if (cols == 0)
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
299 else
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();
311 CRect TexCoords (
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
318 + translation
321 // The tex coords need to be scaled so that (texwidth,texheight) is
322 // mapped onto (1,1)
323 TexCoords.left /= TexWidth;
324 TexCoords.right /= TexWidth;
325 TexCoords.top /= TexHeight;
326 TexCoords.bottom /= TexHeight;
328 return TexCoords;
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();
339 glDisable(GL_BLEND);
341 // Set LOD bias so mipmapped textures are prettier
342 #if CONFIG2_GLES
343 #warning TODO: implement GUI LOD bias for GLES
344 #else
345 glTexEnvf(GL_TEXTURE_FILTER_CONTROL, GL_TEXTURE_LOD_BIAS, -1.f);
346 #endif
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);
363 glEnable(GL_BLEND);
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);
390 #undef ADD
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);
396 else
398 shader->Uniform(str_color, cit->m_BackColor);
400 if (cit->m_EnableBlending)
402 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
403 glEnable(GL_BLEND);
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);
430 data.clear();
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);
439 #undef ADD
442 cit->m_Shader->EndPass();
444 glDisable(GL_BLEND);
447 #if CONFIG2_GLES
448 #warning TODO: implement GUI LOD bias for GLES
449 #else
450 glTexEnvf(GL_TEXTURE_FILTER_CONTROL, GL_TEXTURE_LOD_BIAS, 0.f);
451 #endif