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