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