Render game graphics using OpenGL
[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 static uint8_t *legacy_rgb = NULL;
30 static uint8_t *benson_rgb = NULL;
31 static GLuint legacy_name;
32 static GLuint benson_name;
33 static int screen_width, screen_height;
34 static int image_width, image_height;
35 static int texture_size;
36
37 /* alloc and init an image texture with size that is greater than the power of two */
38 int init_opengl(int _image_width, int _image_height)
39 {
40         int rc;
41
42         image_width = _image_width;
43         image_height = _image_height;
44
45         /* generate texture */
46         for (texture_size = 1; texture_size <= image_width || texture_size <= image_height; texture_size *= 2)
47                 ;
48
49         legacy_rgb = calloc(texture_size * texture_size, 3);
50         if (!legacy_rgb) {
51                 print_error("Failed to allocate texture\n");
52                 rc = -ENOMEM;
53                 goto error;
54         }
55         glShadeModel(GL_FLAT);
56         glPixelStorei(GL_UNPACK_ALIGNMENT, 1); /* bytes */
57         glGenTextures(1, &legacy_name);
58         glBindTexture(GL_TEXTURE_2D, legacy_name);
59         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
60         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
61         glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, texture_size, texture_size, 0, GL_RGB, GL_UNSIGNED_BYTE, legacy_rgb);
62
63         benson_rgb = calloc(texture_size * texture_size, 3);
64         if (!benson_rgb) {
65                 print_error("Failed to allocate texture\n");
66                 rc = -ENOMEM;
67                 goto error;
68         }
69
70         glShadeModel(GL_FLAT);
71         glPixelStorei(GL_UNPACK_ALIGNMENT, 1); /* bytes */
72         glGenTextures(1, &benson_name);
73         glBindTexture(GL_TEXTURE_2D, benson_name);
74         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
75         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
76         glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, texture_size, texture_size, 0, GL_RGB, GL_UNSIGNED_BYTE, benson_rgb);
77
78         return 0;
79
80 error:
81         exit_opengl();
82         return rc;
83 }
84
85 /* set clip planes so that the image portion of the image texture is centered and pixels are rectengular */
86 void resize_opengl(int _screen_width, int _screen_height)
87 {
88         screen_width = _screen_width;
89         screen_height = _screen_height;
90 }
91
92 void opengl_copy_last(void)
93 {
94         // FIXME: is it ok to copy full window height??? or just the current viewport
95         glReadBuffer(GL_FRONT);
96         glCopyPixels(0, 0, screen_width, screen_height, GL_COLOR);
97         glReadBuffer(GL_BACK);
98 }
99
100 void opengl_clear(void)
101 {
102         glClearColor(0.0, 0.0, 0.0, 1.0);
103         glClear(GL_COLOR_BUFFER_BIT);
104 }
105
106 /* set viewport for legacy image */
107 void opengl_viewport_legacy(int split)
108 {
109         int view_x, view_y;
110         int view_width, view_height;
111         int new_width, new_height;
112
113         if (!split) {
114                 view_x = 0;
115                 view_y = 0;
116                 view_width = screen_width;
117                 view_height = screen_height;
118         } else {
119                 view_x = 0;
120                 view_y = screen_height / 2;
121                 view_width = screen_width;
122                 view_height = screen_height / 2;
123         }
124
125         /* avoid division by zero, if one dimension is too small */
126         if (view_width < 1 || view_height < 1)
127                 return;
128
129         /* calculate a viewport that has apect of image_width:image_height */
130         if (view_height * image_width > view_width * image_height) {
131                 new_height = view_width * image_height / image_width;
132                 view_y = view_y + view_height / 2 - new_height / 2;
133                 view_height = new_height;
134         } else if (view_height * image_width < view_width * image_height) {
135                 new_width = view_height * image_width / image_height;
136                 view_x = view_x + view_width / 2 - new_width / 2;
137                 view_width = new_width;
138         }
139
140         /* avoid views that are too small */
141         if (view_width < 1 || view_height < 1)
142                 return;
143
144         /* viewport and projection matrix */
145         glViewport((GLsizei)view_x, (GLsizei)view_y, (GLsizei)view_width, (GLsizei)view_height);
146         glMatrixMode(GL_PROJECTION);
147         glLoadIdentity();
148         glOrtho(0.0, 1.0, 1.0, 0.0, -1.0, 1.0);
149         glMatrixMode(GL_MODELVIEW);
150 }
151
152 /* render legacy image texture */
153 void opengl_blit_legacy(uint8_t *rgb, int filter)
154 {
155         double width = (double)image_width / (double)texture_size;
156         double height = (double)image_height / (double)texture_size;
157
158         glEnable(GL_TEXTURE_2D);
159         glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);  /* no modulation with color */
160         glBindTexture(GL_TEXTURE_2D, legacy_name);
161         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, (filter) ? GL_LINEAR : GL_NEAREST);
162         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, (filter) ? GL_LINEAR : GL_NEAREST);
163         glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, image_width, image_height, GL_RGB, GL_UNSIGNED_BYTE, rgb);
164         glBegin(GL_QUADS);
165         glTexCoord2f(0.0, 0.0);
166         glVertex3f(0.0, 0.0, 0.0);
167         glTexCoord2f(width, 0.0);
168         glVertex3f(1.0, 0.0, 0.0);
169         glTexCoord2f(width, height);
170         glVertex3f(1.0, 1.0, 0.0);
171         glTexCoord2f(0.0, height);
172         glVertex3f(0.0, 1.0, 0.0);
173         glEnd();
174         glDisable(GL_TEXTURE_2D);
175 }
176
177 /* set viewport for improved rendering */
178 void opengl_viewport_improved(int split, int benson_at_line, double fov, double benson_size)
179 {
180         int view_x, view_y;
181         int view_width, view_height;
182         int new_width, new_height;
183
184         /* center view, or put it in the top half of the window */
185         if (!split) {
186                 view_x = 0;
187                 view_y = 0;
188                 view_width = screen_width;
189                 view_height = screen_height;
190         } else {
191                 view_x = 0;
192                 view_y = 0;
193                 view_width = screen_width;
194                 view_height = screen_height / 2;
195         }
196
197         /* avoid division by zero, if one dimension is too small */
198         if (view_width < 1 || view_height < 1)
199                 return;
200
201         /* calculate a viewport that has apect of image_width:image_height */
202         if (view_height * image_width > view_width * image_height) {
203                 new_height = view_width * image_height / image_width;
204                 view_y = view_y + view_height / 2 - new_height / 2;
205                 view_height = new_height;
206         } else if (view_height * image_width < view_width * image_height) {
207                 new_width = view_height * image_width / image_height;
208                 view_x = view_x + view_width / 2 - new_width / 2;
209                 view_width = new_width;
210         }
211
212         /* avoid views that are too small */
213         if (view_width < 1 || view_height < 1)
214                 return;
215
216         /* viewport and projection matrix */
217         glViewport((GLsizei)view_x, (GLsizei)view_y, (GLsizei)view_width, (GLsizei)view_height);
218         glMatrixMode(GL_PROJECTION);
219         glLoadIdentity();
220
221         /* calculate field-of-view */
222         double slope = tan(fov / 360 * M_PI);
223         /* make frustum to center the view in the game view above benson */
224         double left = -slope;
225         double right = slope;
226         double benson_start_at_position = ((double)image_height - (double)benson_at_line) * benson_size;
227         double top = slope * ((double)image_height - benson_start_at_position) / (double)image_width;
228         double bottom = -slope * ((double)image_height * 2.0 - ((double)image_height - benson_start_at_position)) / (double)image_width;
229         glFrustum(left, right, bottom, top, 1.0, 5000000000.0);
230         glMatrixMode(GL_MODELVIEW);
231 }
232
233 /* render only benson */
234 void opengl_blit_benson(uint8_t *rgb, int filter, int benson_at_line, double fov, double benson_size, int pixel_size)
235 {
236         double border = (benson_size != 1.0) ? (double)pixel_size : 0.0;
237         double texture_left = border / (double)texture_size;
238         double texture_right = ((double)image_width - border) / (double)texture_size;
239         double texture_top = ((double)benson_at_line + border) / (double)texture_size;
240         double texture_bottom = ((double)image_height -border) / (double)texture_size;
241         double benson_start_at_position = ((double)image_height - (double)benson_at_line) * benson_size;
242         double benson_top = -((double)image_height - benson_start_at_position) / (double)image_width;
243         double benson_bottom = -((double)image_height * 2.0 - ((double)image_height - benson_start_at_position)) / (double)image_width;
244
245         /* calculate field-of-view */
246         double slope = tan(fov / 360 * M_PI);
247
248         /* render benson */
249         rgb += image_width * benson_at_line * 3;
250         glEnable(GL_TEXTURE_2D);
251         glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);  /* no modulation with color */
252         glBindTexture(GL_TEXTURE_2D, benson_name);
253         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, (filter) ? GL_LINEAR : GL_NEAREST);
254         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, (filter) ? GL_LINEAR : GL_NEAREST);
255         glTexSubImage2D(GL_TEXTURE_2D, 0, 0, benson_at_line, image_width, image_height - benson_at_line, GL_RGB, GL_UNSIGNED_BYTE, rgb);
256         glBegin(GL_QUADS);
257         glTexCoord2f(texture_left, texture_bottom);
258         glVertex3f(-100.0 * benson_size, 100.0 * benson_bottom, -100.0 / slope);
259         glTexCoord2f(texture_right, texture_bottom);
260         glVertex3f(100.0 * benson_size, 100.0 * benson_bottom, -100.0 / slope);
261         glTexCoord2f(texture_right, texture_top);
262         glVertex3f(100.0 * benson_size, 100.0 * benson_top, -100.0 / slope);
263         glTexCoord2f(texture_left, texture_top);
264         glVertex3f(-100.0 * benson_size, 100.0 * benson_top, -100.0 / slope);
265         glEnd();
266         glDisable(GL_TEXTURE_2D);
267 }
268
269 static double transparency;
270
271 /* start rendering scene */
272 void opengl_transparency_set(double _transparency)
273 {
274         if (transparency)
275                 glDisable(GL_BLEND);
276
277         transparency = _transparency;
278
279         /* for debugging use transparent rendering */
280         if (transparency) {
281                 glEnable(GL_BLEND);
282                 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
283         }
284 }
285
286 void opengl_render_color(double r, double g, double b)
287 {
288         if (transparency)
289                 glColor4d(r, g, b, 1.0 - transparency);
290         else
291                 glColor3d(r, g, b);
292 }
293
294 /* render polygon */
295 void opengl_render_polygon(double *x, double *y, double *z, int count, int cull_face)
296 {
297         int i;
298
299         if (cull_face) {
300                 glEnable(GL_CULL_FACE);
301                 glFrontFace(GL_CW);
302                 glCullFace(GL_BACK);
303         }
304         glBegin(GL_POLYGON);
305         for (i = 0; i < count; i++)
306                 glVertex3d(x[i], y[i], -z[i]);
307         glEnd();
308         if (cull_face)
309                 glDisable(GL_CULL_FACE);
310 }
311
312 /* render polygon, but make sure any size of it is visible */
313 void opengl_render_polygon_and_line(double *x, double *y, double *z, int count)
314 {
315         int i;
316
317         glBegin(GL_POLYGON);
318         for (i = 0; i < count; i++)
319                 glVertex3d(x[i], y[i], -z[i]);
320         glEnd();
321         glBegin(GL_LINE_LOOP);
322         for (i = 0; i < count; i++)
323                 glVertex3d(x[i], y[i], -z[i]);
324         glEnd();
325 }
326
327 /* render line */
328 void opengl_render_line(double x1, double y1, double z1, double x2, double y2, double z2, double size)
329 {
330         if (size == 0.0) {
331                 glBegin(GL_LINES);
332                 glVertex3f(x1, y1, -z1);
333                 glVertex3f(x2, y2, -z2);
334                 glEnd();
335                 return;
336         }
337 }
338
339 /* render point */
340 void opengl_render_point(double x, double y, double z, double size)
341 {
342         if (size == 0.0) {
343                 glBegin(GL_POINTS);
344                 glVertex3f(x, y, -z);
345                 glEnd();
346                 return;
347         }
348 }
349
350 /* free image texture */
351 void exit_opengl(void)
352 {
353         if (legacy_rgb) {
354                 free(legacy_rgb);
355                 legacy_rgb = NULL;
356         }
357         if (benson_rgb) {
358                 free(benson_rgb);
359                 benson_rgb = NULL;
360         }
361 }
362