Fix rendering buffer handling
[mercenary-reloaded.git] / src / libsdl / opengl.c
index 40cf6bb..f795cf2 100644 (file)
 #include <stdio.h>
 #include <stdint.h>
 #include <stdlib.h>
+#include <math.h>
 #include <errno.h>
+#include "print.h"
 #include "opengl.h"
 #include <GL/glew.h>
 
-static uint8_t *text_rgb = NULL;
-static GLuint text_name;
+#define MAX_OSD 2
+static uint8_t *legacy_rgb = NULL;
+static uint8_t *benson_rgb = NULL;
+static uint8_t *osd_rgba[MAX_OSD] = { NULL, NULL };
+static GLuint legacy_name;
+static GLuint benson_name;
+static GLuint osd_name[MAX_OSD];
 static int screen_width, screen_height;
 static int image_width, image_height;
+static int osd_width[MAX_OSD], osd_height[MAX_OSD];
 static int texture_size;
+static int osd_size[MAX_OSD];
 
 /* alloc and init an image texture with size that is greater than the power of two */
 int init_opengl(int _image_width, int _image_height)
@@ -41,20 +50,72 @@ int init_opengl(int _image_width, int _image_height)
        /* generate texture */
        for (texture_size = 1; texture_size <= image_width || texture_size <= image_height; texture_size *= 2)
                ;
-       text_rgb = calloc(texture_size * texture_size * 10, 3);
-       if (!text_rgb) {
-               fprintf(stderr, "Failed to allocate texture\n");
+
+       legacy_rgb = calloc(texture_size * texture_size, 3);
+       if (!legacy_rgb) {
+               print_error("Failed to allocate texture\n");
                rc = -ENOMEM;
                goto error;
        }
-       glClearColor(0.0, 0.0, 0.0, 1.0);
        glShadeModel(GL_FLAT);
        glPixelStorei(GL_UNPACK_ALIGNMENT, 1); /* bytes */
-       glGenTextures(1, &text_name);
-       glBindTexture(GL_TEXTURE_2D, text_name);
+       glGenTextures(1, &legacy_name);
+       glBindTexture(GL_TEXTURE_2D, legacy_name);
+       glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
+       glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
+       glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, texture_size, texture_size, 0, GL_RGB, GL_UNSIGNED_BYTE, legacy_rgb);
+
+       benson_rgb = calloc(texture_size * texture_size, 3);
+       if (!benson_rgb) {
+               print_error("Failed to allocate texture\n");
+               rc = -ENOMEM;
+               goto error;
+       }
+
+       glShadeModel(GL_FLAT);
+       glPixelStorei(GL_UNPACK_ALIGNMENT, 1); /* bytes */
+       glGenTextures(1, &benson_name);
+       glBindTexture(GL_TEXTURE_2D, benson_name);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
-       glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, texture_size, texture_size, 0, GL_RGB, GL_UNSIGNED_BYTE, text_rgb);
+       glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, texture_size, texture_size, 0, GL_RGB, GL_UNSIGNED_BYTE, benson_rgb);
+
+       return 0;
+
+error:
+       exit_opengl();
+       return rc;
+}
+
+/* alloc and init an osd texture with size that is greater than the power of two */
+int init_osd(int num, int _osd_width, int _osd_height)
+{
+       int rc;
+
+       if (num < 0 || num >= MAX_OSD) {
+               print_error("given OSD number out of range");
+               rc = -ENOMEM;
+               goto error;
+       }
+
+       osd_width[num] = _osd_width;
+       osd_height[num] = _osd_height;
+
+       /* generate texture */
+       for (osd_size[num] = 1; osd_size[num] <= osd_width[num] || osd_size[num] <= osd_height[num]; osd_size[num] *= 2)
+               ;
+       osd_rgba[num] = calloc(osd_size[num] * osd_size[num], 4);
+       if (!osd_rgba[num]) {
+               print_error("Failed to allocate texture\n");
+               rc = -ENOMEM;
+               goto error;
+       }
+       glPixelStorei(GL_UNPACK_ALIGNMENT, 1); /* bytes */
+       glGenTextures(1, &osd_name[num]);
+       glBindTexture(GL_TEXTURE_2D, osd_name[num]);
+       glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
+       glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
+       glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, osd_size[num], osd_size[num], 0, GL_RGBA, GL_UNSIGNED_BYTE, osd_rgba[num]);
 
        return 0;
 
@@ -65,66 +126,333 @@ error:
 /* set clip planes so that the image portion of the image texture is centered and pixels are rectengular */
 void resize_opengl(int _screen_width, int _screen_height)
 {
-       double width_border = 1.0;
-       double height_border = 1.0;
-
-       if (_screen_width < 1 || _screen_height < 1)
-               return;
        screen_width = _screen_width;
        screen_height = _screen_height;
+}
+
+void opengl_clear(void)
+{
+       /* clear screen */
+       glClearColor(0.0, 0.0, 0.0, 1.0);
+       glClear(GL_COLOR_BUFFER_BIT);
+}
+
+/* set viewport for legacy image */
+void opengl_viewport_legacy(int split)
+{
+       int view_x, view_y;
+       int view_width, view_height;
+       int new_width, new_height;
 
-       if (image_width * screen_height > screen_width * image_height) {
-               height_border = (double)(image_width * screen_height) / (double)(screen_width * image_height);
+       if (!split) {
+               view_x = 0;
+               view_y = 0;
+               view_width = screen_width;
+               view_height = screen_height;
+       } else {
+               view_x = 0;
+               view_y = screen_height / 2;
+               view_width = screen_width;
+               view_height = screen_height / 2;
        }
-       if (image_width * screen_height < screen_width * image_height) {
-               width_border = (double)(screen_width * image_height / (double)(image_width * screen_height));
+
+       /* avoid division by zero, if one dimension is too small */
+       if (view_width < 1 || view_height < 1)
+               return;
+
+       /* calculate a viewport that has apect of image_width:image_height */
+       if (view_height * image_width > view_width * image_height) {
+               new_height = view_width * image_height / image_width;
+               view_y = view_y + view_height / 2 - new_height / 2;
+               view_height = new_height;
+       } else if (view_height * image_width < view_width * image_height) {
+               new_width = view_height * image_width / image_height;
+               view_x = view_x + view_width / 2 - new_width / 2;
+               view_width = new_width;
        }
 
-       /* viewport and projection matrix */
-       glViewport(0, 0, (GLsizei)screen_width, (GLsizei)screen_height);
+       /* avoid views that are too small */
+       if (view_width < 1 || view_height < 1)
+               return;
+
+       /* viewport and projection matrix to view rectangle */
+       glViewport((GLsizei)view_x, (GLsizei)view_y, (GLsizei)view_width, (GLsizei)view_height);
        glMatrixMode(GL_PROJECTION);
        glLoadIdentity();
-
-       double width = (double)image_width / (double)texture_size;
-       double height = (double)image_height / (double)texture_size;
-       glOrtho(
-                       -width * (width_border - 1.0) / 2,
-                       width / 2 + width * width_border / 2,
-                       height / 2 + height * height_border / 2,
-                       -height * (height_border - 1.0) / 2,
-                       -1.0, 1.0);
+       glOrtho(0.0, 1.0, 1.0, 0.0, -1.0, 1.0);
        glMatrixMode(GL_MODELVIEW);
 }
 
-/* render image texture */
-void render_opengl(uint8_t *rgb, int filter)
+/* render legacy image texture */
+void opengl_blit_legacy(uint8_t *rgb, int filter)
 {
-       glClear(GL_COLOR_BUFFER_BIT);
+       double width = (double)image_width / (double)texture_size;
+       double height = (double)image_height / (double)texture_size;
+
        glEnable(GL_TEXTURE_2D);
-       glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);  /* no modulation with color */
-       glBindTexture(GL_TEXTURE_2D, text_name);
+       glBindTexture(GL_TEXTURE_2D, legacy_name);
+       glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);  /* no modulation with color */
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, (filter) ? GL_LINEAR : GL_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, (filter) ? GL_LINEAR : GL_NEAREST);
        glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, image_width, image_height, GL_RGB, GL_UNSIGNED_BYTE, rgb);
        glBegin(GL_QUADS);
        glTexCoord2f(0.0, 0.0);
        glVertex3f(0.0, 0.0, 0.0);
-       glTexCoord2f(1.0, 0.0);
+       glTexCoord2f(width, 0.0);
        glVertex3f(1.0, 0.0, 0.0);
-       glTexCoord2f(1.0, 1.0);
+       glTexCoord2f(width, height);
        glVertex3f(1.0, 1.0, 0.0);
-       glTexCoord2f(0.0, 1.0);
+       glTexCoord2f(0.0, height);
        glVertex3f(0.0, 1.0, 0.0);
        glEnd();
        glDisable(GL_TEXTURE_2D);
 }
 
+/* set viewport for improved rendering */
+void opengl_viewport_improved(int split, int benson_at_line, double fov, double benson_size)
+{
+       int view_x, view_y;
+       int view_width, view_height;
+       int new_width, new_height;
+
+       /* center view, or put it in the top half of the window */
+       if (!split) {
+               view_x = 0;
+               view_y = 0;
+               view_width = screen_width;
+               view_height = screen_height;
+       } else {
+               view_x = 0;
+               view_y = 0;
+               view_width = screen_width;
+               view_height = screen_height / 2;
+       }
+
+       /* avoid division by zero, if one dimension is too small */
+       if (view_width < 1 || view_height < 1)
+               return;
+
+       /* calculate a viewport that has apect of image_width:image_height */
+       if (view_height * image_width > view_width * image_height) {
+               new_height = view_width * image_height / image_width;
+               view_y = view_y + view_height / 2 - new_height / 2;
+               view_height = new_height;
+       } else if (view_height * image_width < view_width * image_height) {
+               new_width = view_height * image_width / image_height;
+               view_x = view_x + view_width / 2 - new_width / 2;
+               view_width = new_width;
+       }
+
+       /* avoid views that are too small */
+       if (view_width < 1 || view_height < 1)
+               return;
+
+       /* viewport and projection matrix */
+       glViewport((GLsizei)view_x, (GLsizei)view_y, (GLsizei)view_width, (GLsizei)view_height);
+       glMatrixMode(GL_PROJECTION);
+       glLoadIdentity();
+
+       /* calculate field-of-view */
+       double slope = tan(fov / 360 * M_PI);
+       /* make frustum to center the view in the game view above benson */
+       double left = -slope;
+       double right = slope;
+       double benson_start_at_position = ((double)image_height - (double)benson_at_line) * benson_size;
+       double top = slope * ((double)image_height - benson_start_at_position) / (double)image_width;
+       double bottom = -slope * ((double)image_height * 2.0 - ((double)image_height - benson_start_at_position)) / (double)image_width;
+       glFrustum(left, right, bottom, top, 1.0, 5000000000.0);
+       glMatrixMode(GL_MODELVIEW);
+}
+
+/* render only benson */
+void opengl_blit_benson(uint8_t *rgb, int filter, int benson_at_line, double fov, double benson_size, int pixel_size)
+{
+       double border = (benson_size != 1.0) ? (double)pixel_size : 0.0;
+       double texture_left = border / (double)texture_size;
+       double texture_right = ((double)image_width - border) / (double)texture_size;
+       double texture_top = ((double)benson_at_line + border) / (double)texture_size;
+       double texture_bottom = ((double)image_height -border) / (double)texture_size;
+       double benson_start_at_position = ((double)image_height - (double)benson_at_line) * benson_size;
+       double benson_top = -((double)image_height - benson_start_at_position) / (double)image_width;
+       double benson_bottom = -((double)image_height * 2.0 - ((double)image_height - benson_start_at_position)) / (double)image_width;
+
+       /* calculate field-of-view */
+       double slope = tan(fov / 360 * M_PI);
+
+       /* render benson */
+       rgb += image_width * benson_at_line * 3;
+       glEnable(GL_TEXTURE_2D);
+       glBindTexture(GL_TEXTURE_2D, benson_name);
+       glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);  /* no modulation with color */
+       glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, (filter) ? GL_LINEAR : GL_NEAREST);
+       glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, (filter) ? GL_LINEAR : GL_NEAREST);
+       glTexSubImage2D(GL_TEXTURE_2D, 0, 0, benson_at_line, image_width, image_height - benson_at_line, GL_RGB, GL_UNSIGNED_BYTE, rgb);
+       glBegin(GL_QUADS);
+       glTexCoord2f(texture_left, texture_bottom);
+       glVertex3f(-100.0 * benson_size, 100.0 * benson_bottom, -100.0 / slope);
+       glTexCoord2f(texture_right, texture_bottom);
+       glVertex3f(100.0 * benson_size, 100.0 * benson_bottom, -100.0 / slope);
+       glTexCoord2f(texture_right, texture_top);
+       glVertex3f(100.0 * benson_size, 100.0 * benson_top, -100.0 / slope);
+       glTexCoord2f(texture_left, texture_top);
+       glVertex3f(-100.0 * benson_size, 100.0 * benson_top, -100.0 / slope);
+       glEnd();
+       glDisable(GL_TEXTURE_2D);
+}
+
+/* render osd texture */
+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)
+{
+       double texture_left = 0.0;
+       double texture_right = (double)osd_width[num] / (double)osd_size[num];
+       double texture_top = 0.0;
+       double texture_bottom = (double)osd_height[num] / (double)osd_size[num];
+       double benson_start_at_position;
+       double osd_left = 0.0;
+       double osd_right = 1.0;
+       double osd_top = 0.0;
+       double osd_bottom = 1.0;
+       double slope, range, center;
+       if (fov) {
+               osd_left = -1.0;
+               osd_right = 1.0;
+               benson_start_at_position = ((double)image_height - (double)benson_at_line) * benson_size;
+               osd_top = ((double)image_height - benson_start_at_position) / (double)image_width;
+               osd_bottom = -((double)image_height * 2.0 - ((double)image_height - benson_start_at_position)) / (double)image_width;
+               /* calculate field-of-view */
+               slope = tan(fov / 360 * M_PI);
+       }
+       range = (osd_right - osd_left) / 2.0;
+       center = (osd_right + osd_left) / 2.0;
+       osd_left = (osd_left - center) * scale_x + center + range * offset_x;
+       osd_right = (osd_right - center) * scale_x + center + range * offset_x;
+       range = (osd_bottom - osd_top) / 2.0;
+       center = (osd_bottom + osd_top) / 2.0;
+       osd_top = (osd_top - center) * scale_y + center + range * offset_y;
+       osd_bottom = (osd_bottom - center) * scale_y + center + range * offset_y;
+
+       glEnable(GL_BLEND);
+       glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+       glEnable(GL_TEXTURE_2D);
+       glBindTexture(GL_TEXTURE_2D, osd_name[num]);
+       glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);  /* no modulation with color */
+       glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, (filter) ? GL_LINEAR : GL_NEAREST);
+       glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, (filter) ? GL_LINEAR : GL_NEAREST);
+       glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, osd_width[num], osd_height[num], GL_RGBA, GL_UNSIGNED_BYTE, rgba);
+       glBegin(GL_QUADS);
+       if (fov) {
+               /* perspective viewport */
+               glTexCoord2f(texture_left, texture_bottom);
+               glVertex3f(100.0 * osd_left, 100.0 * osd_bottom, -100.0 / slope);
+               glTexCoord2f(texture_right, texture_bottom);
+               glVertex3f(100.0 * osd_right, 100.0 * osd_bottom, -100.0 / slope);
+               glTexCoord2f(texture_right, texture_top);
+               glVertex3f(100.0 * osd_right, 100.0 * osd_top, -100.0 / slope);
+               glTexCoord2f(texture_left, texture_top);
+               glVertex3f(100.0 * osd_left, 100.0 * osd_top, -100.0 / slope);
+       } else {
+               /* orthogonal viewport */
+               glTexCoord2f(texture_left, texture_top);
+               glVertex3f(osd_left, osd_top, 0.0);
+               glTexCoord2f(texture_right, texture_top);
+               glVertex3f(osd_right, osd_top, 0.0);
+               glTexCoord2f(texture_right, texture_bottom);
+               glVertex3f(osd_right, osd_bottom, 0.0);
+               glTexCoord2f(texture_left, texture_bottom);
+               glVertex3f(osd_left, osd_bottom, 0.0);
+       }
+       glEnd();
+       glDisable(GL_TEXTURE_2D);
+       glDisable(GL_BLEND);
+}
+
+/* set color and opacity */
+void opengl_render_color(double r, double g, double b, double a)
+{
+       if (a < 1.0) {
+               glEnable(GL_BLEND);
+               glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+               glColor4d(r, g, b, a);
+       } else {
+               glDisable(GL_BLEND);
+               glColor3d(r, g, b);
+       }
+}
+
+/* render polygon */
+void opengl_render_polygon(double *x, double *y, double *z, int count, int cull_face)
+{
+       int i;
+
+       if (cull_face) {
+               glEnable(GL_CULL_FACE);
+               glFrontFace(GL_CW);
+               glCullFace(GL_BACK);
+       }
+       glBegin(GL_POLYGON);
+       for (i = 0; i < count; i++)
+               glVertex3d(x[i], y[i], -z[i]);
+       glEnd();
+       if (cull_face)
+               glDisable(GL_CULL_FACE);
+}
+
+/* render polygon, but make sure any size of it is visible */
+void opengl_render_polygon_and_line(double *x, double *y, double *z, int count)
+{
+       int i;
+
+       glBegin(GL_POLYGON);
+       for (i = 0; i < count; i++)
+               glVertex3d(x[i], y[i], -z[i]);
+       glEnd();
+       glBegin(GL_LINE_LOOP);
+       for (i = 0; i < count; i++)
+               glVertex3d(x[i], y[i], -z[i]);
+       glEnd();
+}
+
+/* render line */
+void opengl_render_line(double x1, double y1, double z1, double x2, double y2, double z2, double size)
+{
+       if (size == 0.0) {
+               glBegin(GL_LINES);
+               glVertex3f(x1, y1, -z1);
+               glVertex3f(x2, y2, -z2);
+               glEnd();
+               return;
+       }
+}
+
+/* render point */
+void opengl_render_point(double x, double y, double z, double size)
+{
+       if (size == 0.0) {
+               glBegin(GL_POINTS);
+               glVertex3f(x, y, -z);
+               glEnd();
+               return;
+       }
+}
+
 /* free image texture */
 void exit_opengl(void)
 {
-       if (text_rgb) {
-               free(text_rgb);
-               text_rgb = NULL;
+       int i;
+
+       if (legacy_rgb) {
+               free(legacy_rgb);
+               legacy_rgb = NULL;
+       }
+       if (benson_rgb) {
+               free(benson_rgb);
+               benson_rgb = NULL;
+       }
+       for (i = 0; i < MAX_OSD; i++) {
+               if (osd_rgba[i]) {
+                       free(osd_rgba[i]);
+                       osd_rgba[i] = NULL;
+               }
        }
 }