1 | /** |
2 | * Copyright (c) 2006-2023 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 | |
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 | |
43 | Text::~Text() |
44 | { |
45 | delete vertex_buffer; |
46 | } |
47 | |
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 | |
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 | |
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 | if (!t.append_vertices) |
117 | { |
118 | voffset = 0; |
119 | vert_offset = 0; |
120 | draw_commands.clear(); |
121 | text_data.clear(); |
122 | } |
123 | |
124 | if (t.use_matrix && !vertices.empty()) |
125 | t.matrix.transformXY(vertices.data(), vertices.data(), (int) vertices.size()); |
126 | |
127 | uploadVertices(vertices, voffset); |
128 | |
129 | if (!new_commands.empty()) |
130 | { |
131 | // The start vertex should be adjusted to account for the vertex offset. |
132 | for (Font::DrawCommand &cmd : new_commands) |
133 | cmd.startvertex += (int) voffset; |
134 | |
135 | auto firstcmd = new_commands.begin(); |
136 | |
137 | // If the first draw command in the new list has the same texture as the |
138 | // last one in the existing list we're building and its vertices are |
139 | // in-order, we can combine them (saving a draw call.) |
140 | if (!draw_commands.empty()) |
141 | { |
142 | auto prevcmd = draw_commands.back(); |
143 | if (prevcmd.texture == firstcmd->texture && (prevcmd.startvertex + prevcmd.vertexcount) == firstcmd->startvertex) |
144 | { |
145 | draw_commands.back().vertexcount += firstcmd->vertexcount; |
146 | ++firstcmd; |
147 | } |
148 | } |
149 | |
150 | // Append the new draw commands to the list we're building. |
151 | draw_commands.insert(draw_commands.end(), firstcmd, new_commands.end()); |
152 | } |
153 | |
154 | vert_offset = voffset + vertices.size(); |
155 | |
156 | text_data.push_back(t); |
157 | text_data.back().text_info = text_info; |
158 | |
159 | // Font::generateVertices can invalidate the font's texture cache. |
160 | if (font->getTextureCacheID() != texture_cache_id) |
161 | regenerateVertices(); |
162 | } |
163 | |
164 | void Text::set(const std::vector<Font::ColoredString> &text) |
165 | { |
166 | return set(text, -1.0f, Font::ALIGN_MAX_ENUM); |
167 | } |
168 | |
169 | void Text::set(const std::vector<Font::ColoredString> &text, float wrap, Font::AlignMode align) |
170 | { |
171 | if (text.empty() || (text.size() == 1 && text[0].str.empty())) |
172 | return clear(); |
173 | |
174 | Font::ColoredCodepoints codepoints; |
175 | Font::getCodepointsFromString(text, codepoints); |
176 | |
177 | addTextData({codepoints, wrap, align, {}, false, false, Matrix4()}); |
178 | } |
179 | |
180 | int Text::add(const std::vector<Font::ColoredString> &text, const Matrix4 &m) |
181 | { |
182 | return addf(text, -1.0f, Font::ALIGN_MAX_ENUM, m); |
183 | } |
184 | |
185 | int Text::addf(const std::vector<Font::ColoredString> &text, float wrap, Font::AlignMode align, const Matrix4 &m) |
186 | { |
187 | Font::ColoredCodepoints codepoints; |
188 | Font::getCodepointsFromString(text, codepoints); |
189 | |
190 | addTextData({codepoints, wrap, align, {}, true, true, m}); |
191 | |
192 | return (int) text_data.size() - 1; |
193 | } |
194 | |
195 | void Text::clear() |
196 | { |
197 | text_data.clear(); |
198 | draw_commands.clear(); |
199 | texture_cache_id = font->getTextureCacheID(); |
200 | vert_offset = 0; |
201 | } |
202 | |
203 | void Text::setFont(Font *f) |
204 | { |
205 | font.set(f); |
206 | |
207 | // Invalidate the texture cache ID since the font is different. We also have |
208 | // to re-upload all the vertices based on the new font's textures. |
209 | texture_cache_id = (uint32) -1; |
210 | regenerateVertices(); |
211 | } |
212 | |
213 | Font *Text::getFont() const |
214 | { |
215 | return font.get(); |
216 | } |
217 | |
218 | int Text::getWidth(int index) const |
219 | { |
220 | if (index < 0) |
221 | index = std::max((int) text_data.size() - 1, 0); |
222 | |
223 | if (index >= (int) text_data.size()) |
224 | return 0; |
225 | |
226 | return text_data[index].text_info.width; |
227 | } |
228 | |
229 | int Text::getHeight(int index) const |
230 | { |
231 | if (index < 0) |
232 | index = std::max((int) text_data.size() - 1, 0); |
233 | |
234 | if (index >= (int) text_data.size()) |
235 | return 0; |
236 | |
237 | return text_data[index].text_info.height; |
238 | } |
239 | |
240 | void Text::draw(Graphics *gfx, const Matrix4 &m) |
241 | { |
242 | if (vertex_buffer == nullptr || draw_commands.empty()) |
243 | return; |
244 | |
245 | gfx->flushStreamDraws(); |
246 | |
247 | if (Shader::isDefaultActive()) |
248 | Shader::attachDefault(Shader::STANDARD_DEFAULT); |
249 | |
250 | if (Shader::current) |
251 | Shader::current->checkMainTextureType(TEXTURE_2D, false); |
252 | |
253 | // Re-generate the text if the Font's texture cache was invalidated. |
254 | if (font->getTextureCacheID() != texture_cache_id) |
255 | regenerateVertices(); |
256 | |
257 | int totalverts = 0; |
258 | for (const Font::DrawCommand &cmd : draw_commands) |
259 | totalverts = std::max(cmd.startvertex + cmd.vertexcount, totalverts); |
260 | |
261 | vertex_buffer->unmap(); // Make sure all pending data is flushed to the GPU. |
262 | |
263 | Graphics::TempTransform transform(gfx, m); |
264 | |
265 | for (const Font::DrawCommand &cmd : draw_commands) |
266 | gfx->drawQuads(cmd.startvertex / 4, cmd.vertexcount / 4, vertexAttributes, vertexBuffers, cmd.texture); |
267 | } |
268 | |
269 | } // graphics |
270 | } // love |
271 | |