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