1 /**
2 * Copyright (c) 2006-2019 LOVE Development Team
3 *
4 * This software is provided 'as-is', without any express or implied
5 * warranty. In no event will the authors be held liable for any damages
6 * arising from the use of this software.
7 *
8 * Permission is granted to anyone to use this software for any purpose,
9 * including commercial applications, and to alter it and redistribute it
10 * freely, subject to the following restrictions:
11 *
12 * 1. The origin of this software must not be misrepresented; you must not
13 * claim that you wrote the original software. If you use this software
14 * in a product, an acknowledgment in the product documentation would be
15 * appreciated but is not required.
16 * 2. Altered source versions must be plainly marked as such, and must not be
17 * misrepresented as being the original software.
18 * 3. This notice may not be removed or altered from any source distribution.
19 **/
20
21 #include "Text.h"
22 #include "Graphics.h"
23
24 #include <algorithm>
25
26 namespace love
27 {
28 namespace graphics
29 {
30
31 love::Type Text::type("Text", &Drawable::type);
32
Text(Font * font,const std::vector<Font::ColoredString> & text)33 Text::Text(Font *font, const std::vector<Font::ColoredString> &text)
34 : font(font)
35 , vertexAttributes(Font::vertexFormat, 0)
36 , vertex_buffer(nullptr)
37 , vert_offset(0)
38 , texture_cache_id((uint32) -1)
39 {
40 set(text);
41 }
42
~Text()43 Text::~Text()
44 {
45 delete vertex_buffer;
46 }
47
uploadVertices(const std::vector<Font::GlyphVertex> & vertices,size_t vertoffset)48 void Text::uploadVertices(const std::vector<Font::GlyphVertex> &vertices, size_t vertoffset)
49 {
50 size_t offset = vertoffset * sizeof(Font::GlyphVertex);
51 size_t datasize = vertices.size() * sizeof(Font::GlyphVertex);
52
53 // If we haven't created a VBO or the vertices are too big, make a new one.
54 if (datasize > 0 && (!vertex_buffer || (offset + datasize) > vertex_buffer->getSize()))
55 {
56 // Make it bigger than necessary to reduce potential future allocations.
57 size_t newsize = size_t((offset + datasize) * 1.5);
58
59 if (vertex_buffer != nullptr)
60 newsize = std::max(size_t(vertex_buffer->getSize() * 1.5), newsize);
61
62 auto gfx = Module::getInstance<Graphics>(Module::M_GRAPHICS);
63 Buffer *new_buffer = gfx->newBuffer(newsize, nullptr, BUFFER_VERTEX, vertex::USAGE_DYNAMIC, 0);
64
65 if (vertex_buffer != nullptr)
66 vertex_buffer->copyTo(0, vertex_buffer->getSize(), new_buffer, 0);
67
68 delete vertex_buffer;
69 vertex_buffer = new_buffer;
70
71 vertexBuffers.set(0, vertex_buffer, 0);
72 }
73
74 if (vertex_buffer != nullptr && datasize > 0)
75 {
76 uint8 *bufferdata = (uint8 *) vertex_buffer->map();
77 memcpy(bufferdata + offset, &vertices[0], datasize);
78 // We unmap when we draw, to avoid unnecessary full map()/unmap() calls.
79 }
80 }
81
regenerateVertices()82 void Text::regenerateVertices()
83 {
84 // If the font's texture cache was invalidated then we need to recreate the
85 // text's vertices, since glyph texcoords might have changed.
86 if (font->getTextureCacheID() != texture_cache_id)
87 {
88 std::vector<TextData> textdata = text_data;
89
90 clear();
91
92 for (const TextData &t : textdata)
93 addTextData(t);
94
95 texture_cache_id = font->getTextureCacheID();
96 }
97 }
98
addTextData(const TextData & t)99 void Text::addTextData(const TextData &t)
100 {
101 std::vector<Font::GlyphVertex> vertices;
102 std::vector<Font::DrawCommand> new_commands;
103
104 Font::TextInfo text_info;
105
106 Colorf constantcolor = Colorf(1.0f, 1.0f, 1.0f, 1.0f);
107
108 // We only have formatted text if the align mode is valid.
109 if (t.align == Font::ALIGN_MAX_ENUM)
110 new_commands = font->generateVertices(t.codepoints, constantcolor, vertices, 0.0f, Vector2(0.0f, 0.0f), &text_info);
111 else
112 new_commands = font->generateVerticesFormatted(t.codepoints, constantcolor, t.wrap, t.align, vertices, &text_info);
113
114 size_t voffset = vert_offset;
115
116 // Must be before the early exit below.
117 if (!t.append_vertices)
118 {
119 voffset = 0;
120 vert_offset = 0;
121 draw_commands.clear();
122 text_data.clear();
123 }
124
125 if (vertices.empty())
126 return;
127
128 if (t.use_matrix)
129 t.matrix.transformXY(&vertices[0], &vertices[0], (int) vertices.size());
130
131 uploadVertices(vertices, voffset);
132
133 if (!new_commands.empty())
134 {
135 // The start vertex should be adjusted to account for the vertex offset.
136 for (Font::DrawCommand &cmd : new_commands)
137 cmd.startvertex += (int) voffset;
138
139 auto firstcmd = new_commands.begin();
140
141 // If the first draw command in the new list has the same texture as the
142 // last one in the existing list we're building and its vertices are
143 // in-order, we can combine them (saving a draw call.)
144 if (!draw_commands.empty())
145 {
146 auto prevcmd = draw_commands.back();
147 if (prevcmd.texture == firstcmd->texture && (prevcmd.startvertex + prevcmd.vertexcount) == firstcmd->startvertex)
148 {
149 draw_commands.back().vertexcount += firstcmd->vertexcount;
150 ++firstcmd;
151 }
152 }
153
154 // Append the new draw commands to the list we're building.
155 draw_commands.insert(draw_commands.end(), firstcmd, new_commands.end());
156 }
157
158 vert_offset = voffset + vertices.size();
159
160 text_data.push_back(t);
161 text_data.back().text_info = text_info;
162
163 // Font::generateVertices can invalidate the font's texture cache.
164 if (font->getTextureCacheID() != texture_cache_id)
165 regenerateVertices();
166 }
167
set(const std::vector<Font::ColoredString> & text)168 void Text::set(const std::vector<Font::ColoredString> &text)
169 {
170 return set(text, -1.0f, Font::ALIGN_MAX_ENUM);
171 }
172
set(const std::vector<Font::ColoredString> & text,float wrap,Font::AlignMode align)173 void Text::set(const std::vector<Font::ColoredString> &text, float wrap, Font::AlignMode align)
174 {
175 if (text.empty() || (text.size() == 1 && text[0].str.empty()))
176 return clear();
177
178 Font::ColoredCodepoints codepoints;
179 Font::getCodepointsFromString(text, codepoints);
180
181 addTextData({codepoints, wrap, align, {}, false, false, Matrix4()});
182 }
183
add(const std::vector<Font::ColoredString> & text,const Matrix4 & m)184 int Text::add(const std::vector<Font::ColoredString> &text, const Matrix4 &m)
185 {
186 return addf(text, -1.0f, Font::ALIGN_MAX_ENUM, m);
187 }
188
addf(const std::vector<Font::ColoredString> & text,float wrap,Font::AlignMode align,const Matrix4 & m)189 int Text::addf(const std::vector<Font::ColoredString> &text, float wrap, Font::AlignMode align, const Matrix4 &m)
190 {
191 Font::ColoredCodepoints codepoints;
192 Font::getCodepointsFromString(text, codepoints);
193
194 addTextData({codepoints, wrap, align, {}, true, true, m});
195
196 return (int) text_data.size() - 1;
197 }
198
clear()199 void Text::clear()
200 {
201 text_data.clear();
202 draw_commands.clear();
203 texture_cache_id = font->getTextureCacheID();
204 vert_offset = 0;
205 }
206
setFont(Font * f)207 void Text::setFont(Font *f)
208 {
209 font.set(f);
210
211 // Invalidate the texture cache ID since the font is different. We also have
212 // to re-upload all the vertices based on the new font's textures.
213 texture_cache_id = (uint32) -1;
214 regenerateVertices();
215 }
216
getFont() const217 Font *Text::getFont() const
218 {
219 return font.get();
220 }
221
getWidth(int index) const222 int Text::getWidth(int index) const
223 {
224 if (index < 0)
225 index = std::max((int) text_data.size() - 1, 0);
226
227 if (index >= (int) text_data.size())
228 return 0;
229
230 return text_data[index].text_info.width;
231 }
232
getHeight(int index) const233 int Text::getHeight(int index) const
234 {
235 if (index < 0)
236 index = std::max((int) text_data.size() - 1, 0);
237
238 if (index >= (int) text_data.size())
239 return 0;
240
241 return text_data[index].text_info.height;
242 }
243
draw(Graphics * gfx,const Matrix4 & m)244 void Text::draw(Graphics *gfx, const Matrix4 &m)
245 {
246 if (vertex_buffer == nullptr || draw_commands.empty())
247 return;
248
249 gfx->flushStreamDraws();
250
251 if (Shader::isDefaultActive())
252 Shader::attachDefault(Shader::STANDARD_DEFAULT);
253
254 if (Shader::current)
255 Shader::current->checkMainTextureType(TEXTURE_2D, false);
256
257 // Re-generate the text if the Font's texture cache was invalidated.
258 if (font->getTextureCacheID() != texture_cache_id)
259 regenerateVertices();
260
261 int totalverts = 0;
262 for (const Font::DrawCommand &cmd : draw_commands)
263 totalverts = std::max(cmd.startvertex + cmd.vertexcount, totalverts);
264
265 vertex_buffer->unmap(); // Make sure all pending data is flushed to the GPU.
266
267 Graphics::TempTransform transform(gfx, m);
268
269 for (const Font::DrawCommand &cmd : draw_commands)
270 gfx->drawQuads(cmd.startvertex / 4, cmd.vertexcount / 4, vertexAttributes, vertexBuffers, cmd.texture);
271 }
272
273 } // graphics
274 } // love
275