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