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