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 "TextRenderer.h"
21 :
22 : #include "graphics/Font.h"
23 : #include "graphics/FontManager.h"
24 : #include "graphics/ShaderProgram.h"
25 : #include "graphics/TextureManager.h"
26 : #include "maths/Matrix3D.h"
27 : #include "ps/CStrIntern.h"
28 : #include "ps/CStrInternStatic.h"
29 : #include "renderer/Renderer.h"
30 :
31 : #include <errno.h>
32 :
33 : namespace
34 : {
35 :
36 : // We can't draw chars more than vertices, currently we use 4 vertices per char.
37 : constexpr size_t MAX_CHAR_COUNT_PER_BATCH = 65536 / 4;
38 :
39 : } // anonymous namespace
40 :
41 0 : CTextRenderer::CTextRenderer()
42 : {
43 0 : ResetTranslate();
44 0 : SetCurrentColor(CColor(1.0f, 1.0f, 1.0f, 1.0f));
45 0 : SetCurrentFont(str_sans_10);
46 0 : }
47 :
48 0 : void CTextRenderer::ResetTranslate(const CVector2D& translate)
49 : {
50 0 : m_Translate = translate;
51 0 : m_Dirty = true;
52 0 : }
53 :
54 0 : void CTextRenderer::Translate(float x, float y)
55 : {
56 0 : m_Translate += CVector2D{x, y};
57 0 : m_Dirty = true;
58 0 : }
59 :
60 0 : void CTextRenderer::SetClippingRect(const CRect& rect)
61 : {
62 0 : m_Clipping = rect;
63 0 : }
64 :
65 0 : void CTextRenderer::SetCurrentColor(const CColor& color)
66 : {
67 0 : if (m_Color != color)
68 : {
69 0 : m_Color = color;
70 0 : m_Dirty = true;
71 : }
72 0 : }
73 :
74 0 : void CTextRenderer::SetCurrentFont(CStrIntern font)
75 : {
76 0 : if (font != m_FontName)
77 : {
78 0 : m_FontName = font;
79 0 : m_Font = g_Renderer.GetFontManager().LoadFont(font);
80 0 : m_Dirty = true;
81 : }
82 0 : }
83 :
84 0 : void CTextRenderer::PrintfAdvance(const wchar_t* fmt, ...)
85 : {
86 0 : wchar_t buf[1024] = {0};
87 :
88 : va_list args;
89 0 : va_start(args, fmt);
90 0 : int ret = vswprintf(buf, ARRAY_SIZE(buf)-1, fmt, args);
91 0 : va_end(args);
92 :
93 0 : if (ret < 0)
94 0 : debug_printf("CTextRenderer::Printf vswprintf failed (buffer size exceeded?) - return value %d, errno %d\n", ret, errno);
95 :
96 0 : PutAdvance(buf);
97 0 : }
98 :
99 :
100 0 : void CTextRenderer::PrintfAt(float x, float y, const wchar_t* fmt, ...)
101 : {
102 0 : wchar_t buf[1024] = {0};
103 :
104 : va_list args;
105 0 : va_start(args, fmt);
106 0 : int ret = vswprintf(buf, ARRAY_SIZE(buf)-1, fmt, args);
107 0 : va_end(args);
108 :
109 0 : if (ret < 0)
110 0 : debug_printf("CTextRenderer::PrintfAt vswprintf failed (buffer size exceeded?) - return value %d, errno %d\n", ret, errno);
111 :
112 0 : Put(x, y, buf);
113 0 : }
114 :
115 0 : void CTextRenderer::PutAdvance(const wchar_t* buf)
116 : {
117 0 : Put(0.0f, 0.0f, buf);
118 :
119 : int w, h;
120 0 : m_Font->CalculateStringSize(buf, w, h);
121 0 : Translate((float)w, 0.0f);
122 0 : }
123 :
124 0 : void CTextRenderer::Put(float x, float y, const wchar_t* buf)
125 : {
126 0 : if (buf[0] == 0)
127 0 : return; // empty string; don't bother storing
128 :
129 0 : PutString(x, y, new std::wstring(buf), true);
130 : }
131 :
132 0 : void CTextRenderer::Put(float x, float y, const char* buf)
133 : {
134 0 : if (buf[0] == 0)
135 0 : return; // empty string; don't bother storing
136 :
137 0 : PutString(x, y, new std::wstring(wstring_from_utf8(buf)), true);
138 : }
139 :
140 0 : void CTextRenderer::Put(float x, float y, const std::wstring* buf)
141 : {
142 0 : if (buf->empty())
143 0 : return; // empty string; don't bother storing
144 :
145 0 : PutString(x, y, buf, false);
146 : }
147 :
148 0 : void CTextRenderer::PutString(float x, float y, const std::wstring* buf, bool owned)
149 : {
150 0 : if (!m_Font)
151 0 : return; // invalid font; can't render
152 :
153 0 : if (m_Clipping != CRect())
154 : {
155 : float x0, y0, x1, y1;
156 0 : m_Font->GetGlyphBounds(x0, y0, x1, y1);
157 0 : if (y + y1 < m_Clipping.top)
158 0 : return;
159 0 : if (y + y0 > m_Clipping.bottom)
160 0 : return;
161 : }
162 :
163 : // If any state has changed since the last batch, start a new batch
164 0 : if (m_Dirty)
165 : {
166 0 : SBatch batch;
167 0 : batch.chars = 0;
168 0 : batch.translate = m_Translate;
169 0 : batch.color = m_Color;
170 0 : batch.font = m_Font;
171 0 : m_Batches.push_back(batch);
172 0 : m_Dirty = false;
173 : }
174 :
175 : // Push a new run onto the latest batch
176 0 : SBatchRun run;
177 0 : run.x = x;
178 0 : run.y = y;
179 0 : m_Batches.back().runs.push_back(run);
180 0 : m_Batches.back().runs.back().text = buf;
181 0 : m_Batches.back().runs.back().owned = owned;
182 0 : m_Batches.back().chars += buf->size();
183 : }
184 :
185 : struct SBatchCompare
186 : {
187 0 : bool operator()(const CTextRenderer::SBatch& a, const CTextRenderer::SBatch& b)
188 : {
189 0 : if (a.font != b.font)
190 0 : return a.font < b.font;
191 : // TODO: is it worth sorting by color/translate too?
192 0 : return false;
193 : }
194 : };
195 :
196 0 : void CTextRenderer::Render(
197 : Renderer::Backend::IDeviceCommandContext* deviceCommandContext,
198 : Renderer::Backend::IShaderProgram* shader,
199 : const CVector2D& transformScale, const CVector2D& translation)
200 : {
201 0 : std::vector<u16> indices;
202 0 : std::vector<CVector2D> positions;
203 0 : std::vector<CVector2D> uvs;
204 :
205 : // Try to merge non-consecutive batches that share the same font/color/translate:
206 : // sort the batch list by font, then merge the runs of adjacent compatible batches
207 0 : m_Batches.sort(SBatchCompare());
208 0 : for (std::list<SBatch>::iterator it = m_Batches.begin(); it != m_Batches.end(); )
209 : {
210 0 : std::list<SBatch>::iterator next = std::next(it);
211 0 : if (next != m_Batches.end() && it->chars + next->chars <= MAX_CHAR_COUNT_PER_BATCH && it->font == next->font && it->color == next->color && it->translate == next->translate)
212 : {
213 0 : it->chars += next->chars;
214 0 : it->runs.splice(it->runs.end(), next->runs);
215 0 : m_Batches.erase(next);
216 : }
217 : else
218 0 : ++it;
219 : }
220 :
221 0 : const int32_t texBindingSlot = shader->GetBindingSlot(str_tex);
222 0 : const int32_t translationBindingSlot = shader->GetBindingSlot(str_translation);
223 0 : const int32_t colorAddBindingSlot = shader->GetBindingSlot(str_colorAdd);
224 0 : const int32_t colorMulBindingSlot = shader->GetBindingSlot(str_colorMul);
225 :
226 0 : bool translationChanged = false;
227 :
228 0 : CTexture* lastTexture = nullptr;
229 0 : for (std::list<SBatch>::iterator it = m_Batches.begin(); it != m_Batches.end(); ++it)
230 : {
231 0 : SBatch& batch = *it;
232 :
233 0 : const CFont::GlyphMap& glyphs = batch.font->GetGlyphs();
234 :
235 0 : if (lastTexture != batch.font->GetTexture().get())
236 : {
237 0 : lastTexture = batch.font->GetTexture().get();
238 0 : lastTexture->UploadBackendTextureIfNeeded(deviceCommandContext);
239 0 : deviceCommandContext->SetTexture(texBindingSlot, lastTexture->GetBackendTexture());
240 : }
241 :
242 0 : if (batch.translate.X != 0.0f || batch.translate.Y != 0.0f)
243 : {
244 : const CVector2D localTranslation =
245 0 : translation + CVector2D(batch.translate.X * transformScale.X, batch.translate.Y * transformScale.Y);
246 0 : deviceCommandContext->SetUniform(translationBindingSlot, localTranslation.AsFloatArray());
247 0 : translationChanged = true;
248 : }
249 :
250 : // ALPHA-only textures will have .rgb sampled as 0, so we need to
251 : // replace it with white (but not affect RGBA textures)
252 0 : if (batch.font->HasRGB())
253 0 : deviceCommandContext->SetUniform(colorAddBindingSlot, 0.0f, 0.0f, 0.0f, 0.0f);
254 : else
255 0 : deviceCommandContext->SetUniform(colorAddBindingSlot, batch.color.r, batch.color.g, batch.color.b, 0.0f);
256 :
257 0 : deviceCommandContext->SetUniform(colorMulBindingSlot, batch.color.AsFloatArray());
258 :
259 0 : positions.resize(std::min(MAX_CHAR_COUNT_PER_BATCH, batch.chars) * 4);
260 0 : uvs.resize(std::min(MAX_CHAR_COUNT_PER_BATCH, batch.chars) * 4);
261 0 : indices.resize(std::min(MAX_CHAR_COUNT_PER_BATCH, batch.chars) * 6);
262 :
263 0 : size_t idx = 0;
264 :
265 0 : auto flush = [deviceCommandContext, &idx, &positions, &uvs, &indices]() -> void
266 0 : {
267 0 : if (idx == 0)
268 0 : return;
269 :
270 0 : deviceCommandContext->SetVertexBufferData(
271 0 : 0, positions.data(), positions.size() * sizeof(positions[0]));
272 0 : deviceCommandContext->SetVertexBufferData(
273 0 : 1, uvs.data(), uvs.size() * sizeof(uvs[0]));
274 0 : deviceCommandContext->SetIndexBufferData(
275 0 : indices.data(), indices.size() * sizeof(indices[0]));
276 :
277 0 : deviceCommandContext->DrawIndexed(0, idx * 6, 0);
278 0 : idx = 0;
279 0 : };
280 :
281 0 : for (std::list<SBatchRun>::iterator runit = batch.runs.begin(); runit != batch.runs.end(); ++runit)
282 : {
283 0 : SBatchRun& run = *runit;
284 0 : i16 x = run.x;
285 0 : i16 y = run.y;
286 0 : for (size_t i = 0; i < run.text->size(); ++i)
287 : {
288 0 : const CFont::GlyphData* g = glyphs.get((*run.text)[i]);
289 :
290 0 : if (!g)
291 0 : g = glyphs.get(0xFFFD); // Use the missing glyph symbol
292 0 : if (!g) // Missing the missing glyph symbol - give up
293 0 : continue;
294 :
295 0 : uvs[idx*4].X = g->u1;
296 0 : uvs[idx*4].Y = g->v0;
297 0 : positions[idx*4].X = g->x1 + x;
298 0 : positions[idx*4].Y = g->y0 + y;
299 :
300 0 : uvs[idx*4+1].X = g->u0;
301 0 : uvs[idx*4+1].Y = g->v0;
302 0 : positions[idx*4+1].X = g->x0 + x;
303 0 : positions[idx*4+1].Y = g->y0 + y;
304 :
305 0 : uvs[idx*4+2].X = g->u0;
306 0 : uvs[idx*4+2].Y = g->v1;
307 0 : positions[idx*4+2].X = g->x0 + x;
308 0 : positions[idx*4+2].Y = g->y1 + y;
309 :
310 0 : uvs[idx*4+3].X = g->u1;
311 0 : uvs[idx*4+3].Y = g->v1;
312 0 : positions[idx*4+3].X = g->x1 + x;
313 0 : positions[idx*4+3].Y = g->y1 + y;
314 :
315 0 : indices[idx*6+0] = static_cast<u16>(idx*4+0);
316 0 : indices[idx*6+1] = static_cast<u16>(idx*4+1);
317 0 : indices[idx*6+2] = static_cast<u16>(idx*4+2);
318 0 : indices[idx*6+3] = static_cast<u16>(idx*4+2);
319 0 : indices[idx*6+4] = static_cast<u16>(idx*4+3);
320 0 : indices[idx*6+5] = static_cast<u16>(idx*4+0);
321 :
322 0 : x += g->xadvance;
323 :
324 0 : ++idx;
325 0 : if (idx == MAX_CHAR_COUNT_PER_BATCH)
326 0 : flush();
327 : }
328 : }
329 :
330 0 : flush();
331 : }
332 :
333 0 : m_Batches.clear();
334 :
335 0 : if (translationChanged)
336 0 : deviceCommandContext->SetUniform(translationBindingSlot, translation.AsFloatArray());
337 3 : }
|