74fcfe3789a567f4138e51be4d49912c8f5d3036
[mercenary-reloaded.git] / src / libsdl / opengl.c
1 /* OpenGL rendering
2  *
3  * (C) 2018 by Andreas Eversberg <jolly@eversberg.eu>
4  * All Rights Reserved
5  *
6  * This program is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
18  */
19
20 #include <stdio.h>
21 #include <stdint.h>
22 #include <stdlib.h>
23 #include <math.h>
24 #include <errno.h>
25 #include "print.h"
26 #include "opengl.h"
27 #include <GL/glew.h>
28
29 #define MAX_OSD 2
30 static uint8_t *legacy_rgb = NULL;
31 static uint8_t *benson_rgb = NULL;
32 static uint8_t *osd_rgba[MAX_OSD] = { NULL, NULL };
33 static GLuint legacy_name;
34 static GLuint benson_name;
35 static GLuint osd_name[MAX_OSD];
36 static int screen_width, screen_height;
37 static int image_width, image_height;
38 static int osd_width[MAX_OSD], osd_height[MAX_OSD];
39 static int texture_size;
40 static int osd_size[MAX_OSD];
41
42 /* alloc and init an image texture with size that is greater than the power of two */
43 int init_opengl(int _image_width, int _image_height)
44 {
45         int rc;
46
47         image_width = _image_width;
48         image_height = _image_height;
49
50         /* generate texture */
51         for (texture_size = 1; texture_size <= image_width || texture_size <= image_height; texture_size *= 2)
52                 ;
53
54         legacy_rgb = calloc(texture_size * texture_size, 3);
55         if (!legacy_rgb) {
56                 print_error("Failed to allocate texture\n");
57                 rc = -ENOMEM;
58                 goto error;
59         }
60         glShadeModel(GL_FLAT);
61         glPixelStorei(GL_UNPACK_ALIGNMENT, 1); /* bytes */
62         glGenTextures(1, &legacy_name);
63         glBindTexture(GL_TEXTURE_2D, legacy_name);
64         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
65         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
66         glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, texture_size, texture_size, 0, GL_RGB, GL_UNSIGNED_BYTE, legacy_rgb);
67
68         benson_rgb = calloc(texture_size * texture_size, 3);
69         if (!benson_rgb) {
70                 print_error("Failed to allocate texture\n");
71                 rc = -ENOMEM;
72                 goto error;
73         }
74
75         glShadeModel(GL_FLAT);
76         glPixelStorei(GL_UNPACK_ALIGNMENT, 1); /* bytes */
77         glGenTextures(1, &benson_name);
78         glBindTexture(GL_TEXTURE_2D, benson_name);
79         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
80         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
81         glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, texture_size, texture_size, 0, GL_RGB, GL_UNSIGNED_BYTE, benson_rgb);
82
83         return 0;
84
85 error:
86         exit_opengl();
87         return rc;
88 }
89
90 /* alloc and init an osd texture with size that is greater than the power of two */
91 int init_osd(int num, int _osd_width, int _osd_height)
92 {
93         int rc;
94
95         if (num < 0 || num >= MAX_OSD) {
96                 print_error("given OSD number out of range");
97                 rc = -ENOMEM;
98                 goto error;
99         }
100
101         osd_width[num] = _osd_width;
102         osd_height[num] = _osd_height;
103
104         /* generate texture */
105         for (osd_size[num] = 1; osd_size[num] <= osd_width[num] || osd_size[num] <= osd_height[num]; osd_size[num] *= 2)
106                 ;
107         osd_rgba[num] = calloc(osd_size[num] * osd_size[num], 4);
108         if (!osd_rgba[num]) {
109                 print_error("Failed to allocate texture\n");
110                 rc = -ENOMEM;
111                 goto error;
112         }
113         glPixelStorei(GL_UNPACK_ALIGNMENT, 1); /* bytes */
114         glGenTextures(1, &osd_name[num]);
115         glBindTexture(GL_TEXTURE_2D, osd_name[num]);
116         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
117         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
118         glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, osd_size[num], osd_size[num], 0, GL_RGBA, GL_UNSIGNED_BYTE, osd_rgba[num]);
119
120         return 0;
121
122 error:
123         return rc;
124 }
125
126 /* set clip planes so that the image portion of the image texture is centered and pixels are rectengular */
127 void resize_opengl(int _screen_width, int _screen_height)
128 {
129         screen_width = _screen_width;
130         screen_height = _screen_height;
131 }
132
133 void opengl_copy_last(void)
134 {
135         // FIXME: is it ok to copy full window height??? or just the current viewport
136         glReadBuffer(GL_FRONT);
137         glCopyPixels(0, 0, screen_width, screen_height, GL_COLOR);
138         glReadBuffer(GL_BACK);
139 }
140
141 void opengl_clear(void)
142 {
143         glClearColor(0.0, 0.0, 0.0, 1.0);
144         glClear(GL_COLOR_BUFFER_BIT);
145 }
146
147 /* set viewport for legacy image */
148 void opengl_viewport_legacy(int split)
149 {
150         int view_x, view_y;
151         int view_width, view_height;
152         int new_width, new_height;
153
154         if (!split) {
155                 view_x = 0;
156                 view_y = 0;
157                 view_width = screen_width;
158                 view_height = screen_height;
159         } else {
160                 view_x = 0;
161                 view_y = screen_height / 2;
162                 view_width = screen_width;
163                 view_height = screen_height / 2;
164         }
165
166         /* avoid division by zero, if one dimension is too small */
167         if (view_width < 1 || view_height < 1)
168                 return;
169
170         /* calculate a viewport that has apect of image_width:image_height */
171         if (view_height * image_width > view_width * image_height) {
172                 new_height = view_width * image_height / image_width;
173                 view_y = view_y + view_height / 2 - new_height / 2;
174                 view_height = new_height;
175         } else if (view_height * image_width < view_width * image_height) {
176                 new_width = view_height * image_width / image_height;
177                 view_x = view_x + view_width / 2 - new_width / 2;
178                 view_width = new_width;
179         }
180
181         /* avoid views that are too small */
182         if (view_width < 1 || view_height < 1)
183                 return;
184
185         /* viewport and projection matrix */
186         glViewport((GLsizei)view_x, (GLsizei)view_y, (GLsizei)view_width, (GLsizei)view_height);
187         glMatrixMode(GL_PROJECTION);
188         glLoadIdentity();
189         glOrtho(0.0, 1.0, 1.0, 0.0, -1.0, 1.0);
190         glMatrixMode(GL_MODELVIEW);
191 }
192
193 /* render legacy image texture */
194 void opengl_blit_legacy(uint8_t *rgb, int filter)
195 {
196         double width = (double)image_width / (double)texture_size;
197         double height = (double)image_height / (double)texture_size;
198
199         glEnable(GL_TEXTURE_2D);
200         glBindTexture(GL_TEXTURE_2D, legacy_name);
201         glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);  /* no modulation with color */
202         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, (filter) ? GL_LINEAR : GL_NEAREST);
203         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, (filter) ? GL_LINEAR : GL_NEAREST);
204         glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, image_width, image_height, GL_RGB, GL_UNSIGNED_BYTE, rgb);
205         glBegin(GL_QUADS);
206         glTexCoord2f(0.0, 0.0);
207         glVertex3f(0.0, 0.0, 0.0);
208         glTexCoord2f(width, 0.0);
209         glVertex3f(1.0, 0.0, 0.0);
210         glTexCoord2f(width, height);
211         glVertex3f(1.0, 1.0, 0.0);
212         glTexCoord2f(0.0, height);
213         glVertex3f(0.0, 1.0, 0.0);
214         glEnd();
215         glDisable(GL_TEXTURE_2D);
216 }
217
218 /* set viewport for improved rendering */
219 void opengl_viewport_improved(int split, int benson_at_line, double fov, double benson_size)
220 {
221         int view_x, view_y;
222         int view_width, view_height;
223         int new_width, new_height;
224
225         /* center view, or put it in the top half of the window */
226         if (!split) {
227                 view_x = 0;
228                 view_y = 0;
229                 view_width = screen_width;
230                 view_height = screen_height;
231         } else {
232                 view_x = 0;
233                 view_y = 0;
234                 view_width = screen_width;
235                 view_height = screen_height / 2;
236         }
237
238         /* avoid division by zero, if one dimension is too small */
239         if (view_width < 1 || view_height < 1)
240                 return;
241
242         /* calculate a viewport that has apect of image_width:image_height */
243         if (view_height * image_width > view_width * image_height) {
244                 new_height = view_width * image_height / image_width;
245                 view_y = view_y + view_height / 2 - new_height / 2;
246                 view_height = new_height;
247         } else if (view_height * image_width < view_width * image_height) {
248                 new_width = view_height * image_width / image_height;
249                 view_x = view_x + view_width / 2 - new_width / 2;
250                 view_width = new_width;
251         }
252
253         /* avoid views that are too small */
254         if (view_width < 1 || view_height < 1)
255                 return;
256
257         /* viewport and projection matrix */
258         glViewport((GLsizei)view_x, (GLsizei)view_y, (GLsizei)view_width, (GLsizei)view_height);
259         glMatrixMode(GL_PROJECTION);
260         glLoadIdentity();
261
262         /* calculate field-of-view */
263         double slope = tan(fov / 360 * M_PI);
264         /* make frustum to center the view in the game view above benson */
265         double left = -slope;
266         double right = slope;
267         double benson_start_at_position = ((double)image_height - (double)benson_at_line) * benson_size;
268         double top = slope * ((double)image_height - benson_start_at_position) / (double)image_width;
269         double bottom = -slope * ((double)image_height * 2.0 - ((double)image_height - benson_start_at_position)) / (double)image_width;
270         glFrustum(left, right, bottom, top, 1.0, 5000000000.0);
271         glMatrixMode(GL_MODELVIEW);
272 }
273
274 /* render only benson */
275 void opengl_blit_benson(uint8_t *rgb, int filter, int benson_at_line, double fov, double benson_size, int pixel_size)
276 {
277         double border = (benson_size != 1.0) ? (double)pixel_size : 0.0;
278         double texture_left = border / (double)texture_size;
279         double texture_right = ((double)image_width - border) / (double)texture_size;
280         double texture_top = ((double)benson_at_line + border) / (double)texture_size;
281         double texture_bottom = ((double)image_height -border) / (double)texture_size;
282         double benson_start_at_position = ((double)image_height - (double)benson_at_line) * benson_size;
283         double benson_top = -((double)image_height - benson_start_at_position) / (double)image_width;
284         double benson_bottom = -((double)image_height * 2.0 - ((double)image_height - benson_start_at_position)) / (double)image_width;
285
286         /* calculate field-of-view */
287         double slope = tan(fov / 360 * M_PI);
288
289         /* render benson */
290         rgb += image_width * benson_at_line * 3;
291         glEnable(GL_TEXTURE_2D);
292         glBindTexture(GL_TEXTURE_2D, benson_name);
293         glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);  /* no modulation with color */
294         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, (filter) ? GL_LINEAR : GL_NEAREST);
295         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, (filter) ? GL_LINEAR : GL_NEAREST);
296         glTexSubImage2D(GL_TEXTURE_2D, 0, 0, benson_at_line, image_width, image_height - benson_at_line, GL_RGB, GL_UNSIGNED_BYTE, rgb);
297         glBegin(GL_QUADS);
298         glTexCoord2f(texture_left, texture_bottom);
299         glVertex3f(-100.0 * benson_size, 100.0 * benson_bottom, -100.0 / slope);
300         glTexCoord2f(texture_right, texture_bottom);
301         glVertex3f(100.0 * benson_size, 100.0 * benson_bottom, -100.0 / slope);
302         glTexCoord2f(texture_right, texture_top);
303         glVertex3f(100.0 * benson_size, 100.0 * benson_top, -100.0 / slope);
304         glTexCoord2f(texture_left, texture_top);
305         glVertex3f(-100.0 * benson_size, 100.0 * benson_top, -100.0 / slope);
306         glEnd();
307         glDisable(GL_TEXTURE_2D);
308 }
309
310 /* render osd texture */
311 void opengl_blit_osd(int num, uint8_t *rgba, int filter, int benson_at_line, double fov, double benson_size, double scale_x, double scale_y, double offset_x, double offset_y)
312 {
313         double texture_left = 0.0;
314         double texture_right = (double)osd_width[num] / (double)osd_size[num];
315         double texture_top = 0.0;
316         double texture_bottom = (double)osd_height[num] / (double)osd_size[num];
317         double benson_start_at_position;
318         double osd_left = 0.0;
319         double osd_right = 1.0;
320         double osd_top = 0.0;
321         double osd_bottom = 1.0;
322         double slope, range, center;
323         if (fov) {
324                 osd_left = -1.0;
325                 osd_right = 1.0;
326                 benson_start_at_position = ((double)image_height - (double)benson_at_line) * benson_size;
327                 osd_top = ((double)image_height - benson_start_at_position) / (double)image_width;
328                 osd_bottom = -((double)image_height * 2.0 - ((double)image_height - benson_start_at_position)) / (double)image_width;
329                 /* calculate field-of-view */
330                 slope = tan(fov / 360 * M_PI);
331         }
332         range = (osd_right - osd_left) / 2.0;
333         center = (osd_right + osd_left) / 2.0;
334         osd_left = (osd_left - center) * scale_x + center + range * offset_x;
335         osd_right = (osd_right - center) * scale_x + center + range * offset_x;
336         range = (osd_bottom - osd_top) / 2.0;
337         center = (osd_bottom + osd_top) / 2.0;
338         osd_top = (osd_top - center) * scale_y + center + range * offset_y;
339         osd_bottom = (osd_bottom - center) * scale_y + center + range * offset_y;
340
341         glEnable(GL_BLEND);
342         glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
343         glEnable(GL_TEXTURE_2D);
344         glBindTexture(GL_TEXTURE_2D, osd_name[num]);
345         glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);  /* no modulation with color */
346         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, (filter) ? GL_LINEAR : GL_NEAREST);
347         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, (filter) ? GL_LINEAR : GL_NEAREST);
348         glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, osd_width[num], osd_height[num], GL_RGBA, GL_UNSIGNED_BYTE, rgba);
349         glBegin(GL_QUADS);
350         if (fov) {
351                 /* perspective viewport */
352                 glTexCoord2f(texture_left, texture_bottom);
353                 glVertex3f(100.0 * osd_left, 100.0 * osd_bottom, -100.0 / slope);
354                 glTexCoord2f(texture_right, texture_bottom);
355                 glVertex3f(100.0 * osd_right, 100.0 * osd_bottom, -100.0 / slope);
356                 glTexCoord2f(texture_right, texture_top);
357                 glVertex3f(100.0 * osd_right, 100.0 * osd_top, -100.0 / slope);
358                 glTexCoord2f(texture_left, texture_top);
359                 glVertex3f(100.0 * osd_left, 100.0 * osd_top, -100.0 / slope);
360         } else {
361                 /* orthogonal viewport */
362                 glTexCoord2f(texture_left, texture_top);
363                 glVertex3f(osd_left, osd_top, 0.0);
364                 glTexCoord2f(texture_right, texture_top);
365                 glVertex3f(osd_right, osd_top, 0.0);
366                 glTexCoord2f(texture_right, texture_bottom);
367                 glVertex3f(osd_right, osd_bottom, 0.0);
368                 glTexCoord2f(texture_left, texture_bottom);
369                 glVertex3f(osd_left, osd_bottom, 0.0);
370         }
371         glEnd();
372         glDisable(GL_TEXTURE_2D);
373         glDisable(GL_BLEND);
374 }
375
376 /* set color and opacity */
377 void opengl_render_color(double r, double g, double b, double a)
378 {
379         if (a < 1.0) {
380                 glEnable(GL_BLEND);
381                 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
382                 glColor4d(r, g, b, a);
383         } else {
384                 glDisable(GL_BLEND);
385                 glColor3d(r, g, b);
386         }
387 }
388
389 /* render polygon */
390 void opengl_render_polygon(double *x, double *y, double *z, int count, int cull_face)
391 {
392         int i;
393
394         if (cull_face) {
395                 glEnable(GL_CULL_FACE);
396                 glFrontFace(GL_CW);
397                 glCullFace(GL_BACK);
398         }
399         glBegin(GL_POLYGON);
400         for (i = 0; i < count; i++)
401                 glVertex3d(x[i], y[i], -z[i]);
402         glEnd();
403         if (cull_face)
404                 glDisable(GL_CULL_FACE);
405 }
406
407 /* render polygon, but make sure any size of it is visible */
408 void opengl_render_polygon_and_line(double *x, double *y, double *z, int count)
409 {
410         int i;
411
412         glBegin(GL_POLYGON);
413         for (i = 0; i < count; i++)
414                 glVertex3d(x[i], y[i], -z[i]);
415         glEnd();
416         glBegin(GL_LINE_LOOP);
417         for (i = 0; i < count; i++)
418                 glVertex3d(x[i], y[i], -z[i]);
419         glEnd();
420 }
421
422 /* render line */
423 void opengl_render_line(double x1, double y1, double z1, double x2, double y2, double z2, double size)
424 {
425         if (size == 0.0) {
426                 glBegin(GL_LINES);
427                 glVertex3f(x1, y1, -z1);
428                 glVertex3f(x2, y2, -z2);
429                 glEnd();
430                 return;
431         }
432 }
433
434 /* render point */
435 void opengl_render_point(double x, double y, double z, double size)
436 {
437         if (size == 0.0) {
438                 glBegin(GL_POINTS);
439                 glVertex3f(x, y, -z);
440                 glEnd();
441                 return;
442         }
443 }
444
445 /* free image texture */
446 void exit_opengl(void)
447 {
448         int i;
449
450         if (legacy_rgb) {
451                 free(legacy_rgb);
452                 legacy_rgb = NULL;
453         }
454         if (benson_rgb) {
455                 free(benson_rgb);
456                 benson_rgb = NULL;
457         }
458         for (i = 0; i < MAX_OSD; i++) {
459                 if (osd_rgba[i]) {
460                         free(osd_rgba[i]);
461                         osd_rgba[i] = NULL;
462                 }
463         }
464 }
465