]> git.localhorst.tv Git - blank.git/blob - src/graphics/render.cpp
"streamlined" model/VAO handling
[blank.git] / src / graphics / render.cpp
1 #include "BlendedSprite.hpp"
2 #include "FixedText.hpp"
3 #include "Font.hpp"
4 #include "Format.hpp"
5 #include "MessageBox.hpp"
6 #include "Text.hpp"
7 #include "Texture.hpp"
8 #include "Viewport.hpp"
9
10 #include <algorithm>
11 #include <cstring>
12 #include <memory>
13 #include <stdexcept>
14
15
16 namespace blank {
17
18 Font::Font(const char *src, int size, long index)
19 : handle(TTF_OpenFontIndex(src, size, index)) {
20         if (!handle) {
21                 throw std::runtime_error(TTF_GetError());
22         }
23 }
24
25 Font::~Font() {
26         if (handle) {
27                 TTF_CloseFont(handle);
28         }
29 }
30
31 Font::Font(Font &&other) noexcept
32 : handle(other.handle) {
33         other.handle = nullptr;
34 }
35
36 Font &Font::operator =(Font &&other) noexcept {
37         std::swap(handle, other.handle);
38         return *this;
39 }
40
41
42 int Font::Style() const noexcept {
43         return TTF_GetFontStyle(handle);
44 }
45
46 void Font::Style(int s) const noexcept {
47         TTF_SetFontStyle(handle, s);
48 }
49
50 int Font::Outline() const noexcept {
51         return TTF_GetFontOutline(handle);
52 }
53
54 void Font::Outline(int px) noexcept {
55         TTF_SetFontOutline(handle, px);
56 }
57
58
59 int Font::Hinting() const noexcept {
60         return TTF_GetFontHinting(handle);
61 }
62
63 void Font::Hinting(int h) const noexcept {
64         TTF_SetFontHinting(handle, h);
65 }
66
67 bool Font::Kerning() const noexcept {
68         return TTF_GetFontKerning(handle);
69 }
70
71 void Font::Kerning(bool b) noexcept {
72         TTF_SetFontKerning(handle, b);
73 }
74
75
76 int Font::Height() const noexcept {
77         return TTF_FontHeight(handle);
78 }
79
80 int Font::Ascent() const noexcept {
81         return TTF_FontAscent(handle);
82 }
83
84 int Font::Descent() const noexcept {
85         return TTF_FontDescent(handle);
86 }
87
88 int Font::LineSkip() const noexcept {
89         return TTF_FontLineSkip(handle);
90 }
91
92
93 const char *Font::FamilyName() const noexcept {
94         return TTF_FontFaceFamilyName(handle);
95 }
96
97 const char *Font::StyleName() const noexcept {
98         return TTF_FontFaceStyleName(handle);
99 }
100
101
102 bool Font::HasGlyph(Uint16 c) const noexcept {
103         return TTF_GlyphIsProvided(handle, c);
104 }
105
106
107 glm::tvec2<int> Font::TextSize(const char *text) const {
108         glm::tvec2<int> size;
109         if (TTF_SizeUTF8(handle, text, &size.x, &size.y) != 0) {
110                 throw std::runtime_error(TTF_GetError());
111         }
112         return size;
113 }
114
115 Texture Font::Render(const char *text) const {
116         Texture tex;
117         Render(text, tex);
118         return tex;
119 }
120
121 void Font::Render(const char *text, Texture &tex) const {
122         SDL_Surface *srf = TTF_RenderUTF8_Blended(handle, text, { 0xFF, 0xFF, 0xFF, 0xFF });
123         if (!srf) {
124                 throw std::runtime_error(TTF_GetError());
125         }
126         tex.Bind();
127         tex.Data(*srf, false);
128         tex.FilterLinear();
129         SDL_FreeSurface(srf);
130 }
131
132
133 void Format::ReadPixelFormat(const SDL_PixelFormat &fmt) {
134         if (fmt.BytesPerPixel == 4) {
135                 if (fmt.Amask == 0xFF) {
136                         if (fmt.Rmask == 0xFF00) {
137                                 format = GL_BGRA;
138                         } else {
139                                 format = GL_RGBA;
140                         }
141                         type = GL_UNSIGNED_INT_8_8_8_8;
142                 } else {
143                         if (fmt.Rmask == 0xFF) {
144                                 format = GL_RGBA;
145                         } else {
146                                 format = GL_BGRA;
147                         }
148                         type = GL_UNSIGNED_INT_8_8_8_8_REV;
149                 }
150                 internal = GL_RGBA8;
151         } else {
152                 if (fmt.Rmask == 0xFF) {
153                         format = GL_RGB;
154                 } else {
155                         format = GL_BGR;
156                 }
157                 type = GL_UNSIGNED_BYTE;
158                 internal = GL_RGB8;
159         }
160 }
161
162
163 MessageBox::MessageBox(const Font &f)
164 : font(f)
165 , lines()
166 , max_lines(10)
167 , pos(0.0f)
168 , adv(0.0f, font.LineSkip(), 0.0f)
169 , bg(1.0f, 1.0f, 1.0f, 0.0f)
170 , fg(1.0f, 1.0f, 1.0f, 1.0f)
171 , grav(Gravity::NORTH_WEST) {
172
173 }
174
175 void MessageBox::Position(const glm::vec3 &p, Gravity g) noexcept {
176         pos = p;
177         grav = g;
178         if (get_y(g) == Align::END) {
179                 adv.y = -font.LineSkip();
180         } else {
181                 adv.y = font.LineSkip();
182         }
183         for (Text &txt : lines) {
184                 txt.Pivot(g);
185         }
186 }
187
188 void MessageBox::PushLine(const char *text) {
189         lines.emplace_front();
190         Text &txt = lines.front();
191         txt.Set(font, text);
192         txt.Pivot(grav);
193
194         while (lines.size() > max_lines) {
195                 lines.pop_back();
196         }
197 }
198
199 void MessageBox::Render(Viewport &viewport) noexcept {
200         BlendedSprite &prog = viewport.SpriteProgram();
201         prog.SetBG(bg);
202         prog.SetFG(fg);
203         viewport.SetCursor(pos, grav);
204         for (Text &txt : lines) {
205                 prog.SetM(viewport.Cursor());
206                 txt.Render(viewport);
207                 viewport.MoveCursor(adv);
208         }
209 }
210
211
212 Text::Text() noexcept
213 : tex()
214 , sprite()
215 , size(0.0f)
216 , pivot(Gravity::NORTH_WEST)
217 , dirty(false) {
218
219 }
220
221 FixedText::FixedText() noexcept
222 : Text()
223 , bg(1.0f, 1.0f, 1.0f, 0.0f)
224 , fg(1.0f, 1.0f, 1.0f, 1.0f)
225 , pos(0.0f)
226 , grav(Gravity::NORTH_WEST)
227 , visible(false) {
228
229 }
230
231 void Text::Set(const Font &font, const char *text) {
232         font.Render(text, tex);
233         size = font.TextSize(text);
234         dirty = true;
235 }
236
237 namespace {
238
239 SpriteModel::Buffer sprite_buf;
240
241 }
242
243 void Text::Update() {
244         sprite_buf.LoadRect(size.x, size.y, align(pivot, size));
245         sprite.Update(sprite_buf);
246         dirty = false;
247 }
248
249 void FixedText::Render(Viewport &viewport) noexcept {
250         BlendedSprite &prog = viewport.SpriteProgram();
251         viewport.SetCursor(pos, grav);
252         prog.SetM(viewport.Cursor());
253         prog.SetBG(bg);
254         prog.SetFG(fg);
255         Text::Render(viewport);
256 }
257
258 void Text::Render(Viewport &viewport) noexcept {
259         if (dirty) {
260                 Update();
261         }
262         BlendedSprite &prog = viewport.SpriteProgram();
263         prog.SetTexture(tex);
264         sprite.Draw();
265 }
266
267
268 Texture::Texture()
269 : handle(0)
270 , width(0)
271 , height(0) {
272         glGenTextures(1, &handle);
273 }
274
275 Texture::~Texture() {
276         if (handle != 0) {
277                 glDeleteTextures(1, &handle);
278         }
279 }
280
281 Texture::Texture(Texture &&other) noexcept
282 : handle(other.handle) {
283         other.handle = 0;
284         width = other.width;
285         height = other.height;
286 }
287
288 Texture &Texture::operator =(Texture &&other) noexcept {
289         std::swap(handle, other.handle);
290         width = other.width;
291         height = other.height;
292         return *this;
293 }
294
295
296 void Texture::Bind() noexcept {
297         glBindTexture(GL_TEXTURE_2D, handle);
298 }
299
300 namespace {
301         bool ispow2(unsigned int i) {
302                 // don't care about i == 0 here
303                 return !(i & (i - 1));
304         }
305 }
306
307 void Texture::Data(const SDL_Surface &srf, bool pad2) noexcept {
308         Format format;
309         format.ReadPixelFormat(*srf.format);
310
311         if (!pad2 || (ispow2(srf.w) && ispow2(srf.h))) {
312                 int align = UnpackAlignmentFromPitch(srf.pitch);
313
314                 int pitch = (srf.w * srf.format->BytesPerPixel + align - 1) / align * align;
315                 if (srf.pitch - pitch >= align) {
316                         UnpackRowLength(srf.pitch / srf.format->BytesPerPixel);
317                 } else {
318                         UnpackRowLength(0);
319                 }
320
321                 Data(srf.w, srf.h, format, srf.pixels);
322
323                 UnpackRowLength(0);
324         } else if (srf.w > (1 << 30) || srf.h > (1 << 30)) {
325 #ifndef NDEBUG
326                 throw std::runtime_error("texture too large");
327 #endif
328         } else {
329                 GLsizei width = 1, height = 1;
330                 while (width < srf.w) {
331                         width <<= 1;
332                 }
333                 while (height < srf.h) {
334                         height <<= 1;
335                 }
336                 size_t pitch = width * srf.format->BytesPerPixel;
337                 size_t size = pitch * height;
338                 size_t row_pad = pitch - srf.pitch;
339                 std::unique_ptr<unsigned char[]> data(new unsigned char[size]);
340                 unsigned char *src = reinterpret_cast<unsigned char *>(srf.pixels);
341                 unsigned char *dst = data.get();
342                 for (int row = 0; row < srf.h; ++row) {
343                         std::memcpy(dst, src, srf.pitch);
344                         src += srf.pitch;
345                         dst += srf.pitch;
346                         std::memset(dst, 0, row_pad);
347                         dst += row_pad;
348                 }
349                 std::memset(dst, 0, (height - srf.h) * pitch);
350                 UnpackAlignmentFromPitch(pitch);
351                 Data(width, height, format, data.get());
352         }
353
354         UnpackAlignment(4);
355 }
356
357 void Texture::Data(GLsizei w, GLsizei h, const Format &format, GLvoid *data) noexcept {
358         glTexImage2D(
359                 GL_TEXTURE_2D,
360                 0, format.internal,
361                 w, h,
362                 0,
363                 format.format, format.type,
364                 data
365         );
366         width = w;
367         height = h;
368 }
369
370
371 void Texture::FilterNearest() noexcept {
372         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
373         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
374 }
375
376 void Texture::FilterLinear() noexcept {
377         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
378         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
379 }
380
381 void Texture::FilterTrilinear() noexcept {
382         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
383         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
384         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
385         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
386         glGenerateMipmap(GL_TEXTURE_2D);
387 }
388
389
390 void Texture::UnpackAlignment(GLint i) noexcept {
391         glPixelStorei(GL_UNPACK_ALIGNMENT, i);
392 }
393
394 int Texture::UnpackAlignmentFromPitch(int pitch) noexcept {
395         int align = 8;
396         while (pitch % align) {
397                 align >>= 1;
398         }
399         UnpackAlignment(align);
400         return align;
401 }
402
403 void Texture::UnpackRowLength(GLint i) noexcept {
404         glPixelStorei(GL_UNPACK_ROW_LENGTH, i);
405 }
406
407 }