1 /*
2 * OpenBOR - http://www.chronocrash.com
3 * -----------------------------------------------------------------------
4 * All rights reserved, see LICENSE in OpenBOR root for details.
5 *
6 * Copyright (c) 2004 - 2014 OpenBOR Team
7 */
8
9 /************************ OpenGL video backend *************************/
10 /*
11 * Video backend that uses OpenGL as a cross-platform API for optimized
12 * hardware blitting and scaling.
13 */
14
15 #ifdef OPENGL
16
17 #include "sdlport.h"
18 #include "savedata.h"
19 #include "opengl.h"
20 #include "video.h"
21 #include "loadgl.h"
22 #include <math.h>
23
24 #define nextpowerof2(x) pow(2,ceil(log(x)/log(2)))
25 #define abs(x) ((x<0)?-(x):(x))
26
27 static SDL_GLContext context = NULL;
28
29 static GLuint gltexture[3];
30 static GLint textureColorFormat[3];
31 static GLint texturePixelFormat[3];
32
33 static int viewportWidth, viewportHeight; // dimensions of display area
34 static int textureWidth, textureHeight; // dimensions of game screen and GL texture 0
35 static int displayWidth, displayHeight;
36
37 static GLfloat tcx, tcy; // maximum x and y texture coords in floating-point form
38 static GLuint shaderProgram; // fragment shader program
39
40 // use some variables declared in video.c that are common to both backends
41 extern int stretch;
42 extern int nativeWidth, nativeHeight;
43 extern SDL_Window* window;
44
45 #define FRAGMENT_SHADER_COMMON \
46 "uniform sampler2D tex;\n" \
47 "uniform float brightness;\n" \
48 "uniform float gamma;\n" \
49 "uniform vec2 texDims;\n" \
50 "uniform float scaleFactor;\n" \
51 "vec3 applyCorrection(vec3 color)\n" \
52 "{\n" \
53 " if (gamma > 0.0)\n" \
54 " color = 1.0 - ((1.0 - color) * (1.0 - (color * gamma)));\n" \
55 " else\n" \
56 " color = color * (1.0 - ((1.0 - color) * -gamma));\n" \
57 " if (brightness > 0.0)\n" \
58 " color = mix(color, vec3(1.0), brightness);\n" \
59 " else\n" \
60 " color = mix(color, vec3(0.0), -brightness);\n" \
61 " return color;\n" \
62 "}\n"
63
64 #define FRAGMENT_SHADER_RGB_BASIC \
65 "void main()\n" \
66 "{\n" \
67 " vec3 color = texture2D(tex, gl_TexCoord[0].xy).xyz;\n" \
68 " gl_FragColor.xyz = applyCorrection(color);\n" \
69 " gl_FragColor.w = 1.0;\n" \
70 "}\n"
71
72 #define FRAGMENT_SHADER_RGB_HIGH_QUALITY \
73 "vec3 getPixel(vec2 coord)\n" \
74 "{\n" \
75 " vec2 texel = coord * texDims;\n" \
76 " float region_range = 0.5 - 0.5 / scaleFactor;\n" \
77 " vec2 distFromCenter = fract(texel) - 0.5;\n" \
78 " vec2 f = (distFromCenter - clamp(distFromCenter, -region_range, region_range)) * scaleFactor + 0.5;\n" \
79 " return texture2D(tex, (floor(texel) + f) / texDims).xyz;\n" \
80 "}\n" \
81 "void main()\n" \
82 "{\n" \
83 " vec3 color = getPixel(gl_TexCoord[0].xy).xyz;\n" \
84 " gl_FragColor.xyz = applyCorrection(color);\n" \
85 " gl_FragColor.w = 1.0;\n" \
86 "}\n"
87
88 #define FRAGMENT_SHADER_YUV \
89 "uniform sampler2D utex;\n" \
90 "uniform sampler2D vtex;\n" \
91 "\n" \
92 "vec3 yuv2rgb(vec3 yuv)\n" \
93 "{\n" \
94 " yuv += vec3(-.0625, -.5, -.5);\n" \
95 " vec3 rgb;\n" \
96 " rgb.x = dot(yuv, vec3(1.164, 0, 1.596));\n" \
97 " rgb.y = dot(yuv, vec3(1.164, -.391, -.813));\n" \
98 " rgb.z = dot(yuv, vec3(1.164, 2.018, 0));\n" \
99 " return rgb;\n" \
100 "}\n" \
101 "\n" \
102 "void main()\n" \
103 "{\n" \
104 " vec3 yuv;\n" \
105 " yuv.x = texture2D(tex, gl_TexCoord[0].xy).x; // Y\n" \
106 " yuv.y = texture2D(utex, gl_TexCoord[0].xy).x; // U\n" \
107 " yuv.z = texture2D(vtex, gl_TexCoord[0].xy).x; // V\n" \
108 " gl_FragColor.xyz = applyCorrection(yuv2rgb(yuv));\n" \
109 " gl_FragColor.w = 1.0;\n" \
110 "}\n"
111
112
113 static const char *fragmentShaderSourceHighQualityRGB =
114 FRAGMENT_SHADER_COMMON FRAGMENT_SHADER_RGB_HIGH_QUALITY;
115 static const char *fragmentShaderSourceYUV =
116 FRAGMENT_SHADER_COMMON FRAGMENT_SHADER_YUV;
117 /*static const char *vertexShaderSource =
118 "varying vec2 texCoord;\n"
119 "void main()\n"
120 "{\n"
121 " gl_TexCoord[0] = gl_MultiTexCoord0;\n"
122 " gl_Position = ftransform();\n"
123 "}\n";*/
124
125 // calculates scale factors and offsets based on viewport and texture sizes
video_gl_setup_screen()126 void video_gl_setup_screen()
127 {
128 // set up the viewport
129 glViewport(0, 0, viewportWidth, viewportHeight);
130
131 // set up orthographic projection
132 glMatrixMode(GL_PROJECTION);
133 glLoadIdentity();
134 glOrtho(0, viewportWidth-1, 0, viewportHeight-1, -1, 1);
135
136 // reset the model view
137 glMatrixMode(GL_MODELVIEW);
138 glLoadIdentity();
139 }
140
141 // initializes the OpenGL texture for the game screen
video_gl_init_texture(int index,int width,int height,int bytesPerPixel)142 void video_gl_init_texture(int index, int width, int height, int bytesPerPixel)
143 {
144 int allocTextureWidth, allocTextureHeight;
145 SDL_Surface *surface;
146
147 // set texture format
148 switch(bytesPerPixel)
149 {
150 case 1: // Y/U/V (not indexed!)
151 textureColorFormat[index] = GL_RED;
152 texturePixelFormat[index] = GL_UNSIGNED_BYTE;
153 break;
154 case 2: // BGR565
155 textureColorFormat[index] = GL_RGB;
156 texturePixelFormat[index] = GL_UNSIGNED_SHORT_5_6_5_REV;
157 break;
158 case 4: // XBGR8888
159 textureColorFormat[index] = GL_RGBA;
160 texturePixelFormat[index] = GL_UNSIGNED_BYTE;
161 break;
162 default:
163 assert(!"unknown bytes per pixel; should be 1, 2, or 4");
164 }
165
166 // enable 2D textures
167 glActiveTexture(GL_TEXTURE0 + index);
168 glEnable(GL_TEXTURE_2D);
169
170 // use non-power-of-two texture dimensions if they are available
171 if(SDL_GL_ExtensionSupported("GL_ARB_texture_non_power_of_two"))
172 {
173 allocTextureWidth = width;
174 allocTextureHeight = height;
175 }
176 else
177 {
178 allocTextureWidth = nextpowerof2(width);
179 allocTextureHeight = nextpowerof2(height);
180 }
181
182 // calculate maximum texture coordinates
183 tcx = (width == 0) ? 0 : (float)width / (float)allocTextureWidth;
184 tcy = (height == 0) ? 0 : (float)height / (float)allocTextureHeight;
185
186 // allocate a temporary surface to initialize the texture with
187 surface = SDL_CreateRGBSurface(0, allocTextureWidth, allocTextureHeight, bytesPerPixel * 8, 0,0,0,0);
188
189 // create texture object
190 glDeleteTextures(1, &gltexture[index]);
191 glGenTextures(1, &gltexture[index]);
192 glBindTexture(GL_TEXTURE_2D, gltexture[index]);
193 glTexImage2D(GL_TEXTURE_2D, 0, textureColorFormat[index], allocTextureWidth, allocTextureHeight,
194 0, textureColorFormat[index], texturePixelFormat[index], surface->pixels);
195 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
196 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
197
198 // free up the temporary surface
199 SDL_FreeSurface(surface);
200 surface = NULL;
201 }
202
video_gl_set_mode(s_videomodes videomodes)203 int video_gl_set_mode(s_videomodes videomodes)
204 {
205 GLint maxTextureSize;
206
207 displayWidth = textureWidth = videomodes.hRes;
208 displayHeight = textureHeight = videomodes.vRes;
209
210 // use the current monitor resolution in fullscreen mode to prevent aspect ratio distortion
211 viewportWidth = savedata.fullscreen ? nativeWidth : (int)(videomodes.hRes * MAX(0.25,videomodes.hScale));
212 viewportHeight = savedata.fullscreen ? nativeHeight : (int)(videomodes.vRes * MAX(0.25,videomodes.vScale));
213
214 // zero width/height means close the window, not make it enormous!
215 if((viewportWidth == 0) || (viewportHeight == 0)) return 0;
216
217 // set up OpenGL double buffering
218 if(SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1) != 0)
219 {
220 printf("Can't set up OpenGL double buffering (%s)...", SDL_GetError());
221 goto error;
222 }
223
224 // create an OpenGL compatibility context, not a core or ES context
225 #ifndef WIN // except on Windows, where some Nvidia drivers really don't like us doing this
226 SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_COMPATIBILITY);
227 #endif
228
229 // get window and initialize OpenGL context
230 SetVideoMode(viewportWidth, viewportHeight, 0, true);
231 if(!window)
232 {
233 printf("Failed to create OpenGL-compatible window (%s)...", SDL_GetError());
234 goto error;
235 }
236 if((context = SDL_GL_GetCurrentContext()))
237 SDL_GL_DeleteContext(context);
238 context = SDL_GL_CreateContext(window);
239
240 // make sure the context was created successfully
241 if(!context)
242 {
243 printf("OpenGL initialization failed (%s)...", SDL_GetError());
244 goto error;
245 }
246
247 // update viewport size based on actual dimensions
248 if(SDL_GL_MakeCurrent(window, context) < 0)
249 {
250 printf("MakeCurrent on OpenGL context failed (%s)...", SDL_GetError());
251 goto error;
252 }
253
254 // try to disable vertical retrace syncing (VSync)
255 if(SDL_GL_SetSwapInterval(!!savedata.vsync) < 0)
256 {
257 printf("Warning: can't disable vertical retrace sync (%s)...\n", SDL_GetError());
258 }
259
260 #ifdef LOADGL
261 // load OpenGL functions dynamically in Linux/Windows/OSX
262 if(LoadGLFunctions() == 0) goto error;
263 #endif
264
265 if(!SDL_GL_ExtensionSupported("GL_ARB_fragment_shader"))
266 {
267 printf("OpenGL fragment shaders not supported...");
268 goto error;
269 }
270
271 // reject the Mesa software renderer (swrast) because it's slow
272 if(stricmp((const char*)glGetString(GL_RENDERER), "Software Rasterizer") == 0)
273 {
274 printf("Not going to use the Mesa software renderer...");
275 goto error;
276 }
277
278 // don't try to create a texture larger than the maximum allowed dimensions
279 glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxTextureSize);
280 if((textureWidth > maxTextureSize) || (textureHeight > maxTextureSize))
281 {
282 printf("Unable to create a %ix%i OpenGL texture (max texture size %i)...", textureWidth, textureHeight, maxTextureSize);
283 goto error;
284 }
285
286 // set background to black
287 glClearColor(0.0, 0.0, 0.0, 0.0);
288 glClear(GL_COLOR_BUFFER_BIT);
289
290 // disable unneeded features
291 glDisable(GL_BLEND);
292 glDisable(GL_LIGHTING);
293 glDisable(GL_DEPTH_TEST);
294 glDisable(GL_DITHER);
295
296 // initialize texture object
297 video_gl_init_texture(0, textureWidth, textureHeight, videomodes.pixel);
298
299 // set up offsets, scale factors, and viewport
300 video_gl_setup_screen();
301
302 // set up a GLSL fragment shader if supported
303 GLuint fragmentShader = glCreateShaderObjectARB(GL_FRAGMENT_SHADER_ARB);
304 glShaderSourceARB(fragmentShader, 1, &fragmentShaderSourceHighQualityRGB, NULL);
305 glCompileShaderARB(fragmentShader);
306 shaderProgram = glCreateProgramObjectARB();
307 glAttachObjectARB(shaderProgram, fragmentShader);
308 glLinkProgramARB(shaderProgram);
309 glUseProgramObjectARB(shaderProgram);
310 glUniform1iARB(glGetUniformLocationARB(shaderProgram, "tex"), 0);
311 glUniform2fARB(glGetUniformLocationARB(shaderProgram, "texDims"), textureWidth, textureHeight);
312 float scaleFactor = MIN((float)viewportWidth / textureWidth, (float)viewportHeight / textureHeight);
313 glUniform1fARB(glGetUniformLocationARB(shaderProgram, "scaleFactor"), ceilf(scaleFactor));
314 video_gl_set_color_correction(savedata.gamma, savedata.brightness);
315
316 opengl = 1;
317 return 1;
318 error:
319 printf("falling back to SDL video backend\n");
320 opengl = 0;
321 savedata.usegl = 0;
322 return 0;
323 }
324
video_gl_clearscreen()325 void video_gl_clearscreen()
326 {
327 // clear both buffers in a double-buffered setup
328 glClear(GL_COLOR_BUFFER_BIT);
329 SDL_GL_SwapWindow(window);
330 glClear(GL_COLOR_BUFFER_BIT);
331 SDL_GL_SwapWindow(window);
332 }
333
video_gl_draw_quad(int x,int y,int width,int height)334 void video_gl_draw_quad(int x, int y, int width, int height)
335 {
336 glClear(GL_COLOR_BUFFER_BIT);
337 glActiveTexture(GL_TEXTURE0);
338 glBegin(GL_QUADS);
339 // Top left
340 glMultiTexCoord2f(GL_TEXTURE0, 0.0, tcy);
341 glVertex2i(x, y);
342
343 // Top right
344 glMultiTexCoord2f(GL_TEXTURE0, tcx, tcy);
345 glVertex2i(x+width-1, y);
346
347 // Bottom right
348 glMultiTexCoord2f(GL_TEXTURE0, tcx, 0.0);
349 glVertex2i(x+width-1, y+height-1);
350
351 // Bottom left
352 glMultiTexCoord2f(GL_TEXTURE0, 0.0, 0.0);
353 glVertex2i(x, y+height-1);
354 glEnd();
355 }
356
render()357 void render()
358 {
359 // determine x and y scale factors
360 float texScale = MIN((float)viewportWidth/(float)textureWidth, (float)viewportHeight/(float)textureHeight);
361
362 // determine on-screen dimensions
363 int scaledWidth = (int)(displayWidth * texScale);
364 int scaledHeight = (int)(displayHeight * texScale);
365
366 // determine offsets
367 int xOffset = (viewportWidth - scaledWidth) / 2;
368 int yOffset = (viewportHeight - scaledHeight) / 2;
369
370 // render the quad
371 if(stretch)
372 video_gl_draw_quad(0, 0, viewportWidth, viewportHeight);
373 else
374 video_gl_draw_quad(xOffset, yOffset, scaledWidth, scaledHeight);
375 }
376
video_gl_copy_screen(s_videosurface * surface)377 int video_gl_copy_screen(s_videosurface* surface)
378 {
379 // update texture contents with new surface contents
380 glActiveTexture(GL_TEXTURE0);
381 glBindTexture(GL_TEXTURE_2D, gltexture[0]);
382 glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, textureWidth, textureHeight, textureColorFormat[0],
383 texturePixelFormat[0], surface->data);
384
385 // set linear filtering
386 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
387 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
388
389 // render the frame
390 render();
391
392 // display the rendered frame on the screen
393 SDL_GL_SwapWindow(window);
394
395 return 1;
396 }
397
398 // Set the brightness and gamma values
399 int lastGamma = 0, lastBrightness = 0;
video_gl_set_color_correction(int gamma,int brightness)400 void video_gl_set_color_correction(int gamma, int brightness)
401 {
402 lastGamma = gamma;
403 lastBrightness = brightness;
404 GLint brightnessLocation, gammaLocation;
405 if(gamma < -256) gamma = -256;
406 if(gamma > 256) gamma = 256;
407 if(brightness < -256) brightness = -256;
408 if(brightness > 256) brightness = 256;
409
410 glUseProgramObjectARB(shaderProgram);
411 brightnessLocation = glGetUniformLocationARB(shaderProgram, "brightness");
412 glUniform1fARB(brightnessLocation, brightness / 256.0);
413 gammaLocation = glGetUniformLocationARB(shaderProgram, "gamma");
414 glUniform1fARB(gammaLocation, gamma / 256.0);
415 }
416
417 // set up YUV mode
video_gl_setup_yuv_overlay(const yuv_video_mode * mode)418 int video_gl_setup_yuv_overlay(const yuv_video_mode *mode)
419 {
420 textureWidth = mode->width;
421 textureHeight = mode->height;
422 displayWidth = mode->display_width;
423 displayHeight = mode->display_height;
424 video_gl_init_texture(0, mode->width, mode->height, 1);
425 video_gl_init_texture(1, mode->width/2, mode->height/2, 1);
426 video_gl_init_texture(2, mode->width/2, mode->height/2, 1);
427
428 // set up shader to do YUV->RGB conversion
429 GLuint fragmentShader = glCreateShaderObjectARB(GL_FRAGMENT_SHADER_ARB);
430 glShaderSourceARB(fragmentShader, 1, &fragmentShaderSourceYUV, NULL);
431 glCompileShaderARB(fragmentShader);
432 shaderProgram = glCreateProgramObjectARB();
433 glAttachObjectARB(shaderProgram, fragmentShader);
434 glLinkProgramARB(shaderProgram);
435 glUseProgramObjectARB(shaderProgram);
436 glUniform1iARB(glGetUniformLocationARB(shaderProgram, "tex"), 0);
437 glUniform1iARB(glGetUniformLocationARB(shaderProgram, "utex"), 1);
438 glUniform1iARB(glGetUniformLocationARB(shaderProgram, "vtex"), 2);
439 video_gl_set_color_correction(lastGamma, lastBrightness);
440 return 1;
441 }
442
443 // render the frame
video_gl_prepare_yuv_frame(yuv_frame * frame)444 int video_gl_prepare_yuv_frame(yuv_frame *frame)
445 {
446 glActiveTexture(GL_TEXTURE0);
447 glBindTexture(GL_TEXTURE_2D, gltexture[0]);
448 glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, textureWidth, textureHeight,
449 textureColorFormat[0], texturePixelFormat[0], frame->lum);
450 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
451 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
452 glActiveTexture(GL_TEXTURE1);
453 glBindTexture(GL_TEXTURE_2D, gltexture[1]);
454 glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, textureWidth/2, textureHeight/2,
455 textureColorFormat[1], texturePixelFormat[1], frame->cr);
456 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
457 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
458 glActiveTexture(GL_TEXTURE2);
459 glBindTexture(GL_TEXTURE_2D, gltexture[2]);
460 glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, textureWidth/2, textureHeight/2,
461 textureColorFormat[2], texturePixelFormat[2], frame->cb);
462 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
463 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
464 render();
465 return 1;
466 }
467
468 // display the rendered frame on the screen
video_gl_display_yuv_frame(void)469 int video_gl_display_yuv_frame(void)
470 {
471 SDL_GL_SwapWindow(window);
472 return 1;
473 }
474
475 #endif
476