#include "gtk_2_3_compat.h" #include #include #include #include #include "gtk_display.h" #include "gtk_display_driver_opengl.h" #include "gtk_shader_parameters.h" #include "shaders/shader_helpers.h" static const GLchar *stock_vertex_shader_110 = "#version 110\n" "attribute vec2 in_position;\n" "attribute vec2 in_texcoord;\n" "varying vec2 texcoord;\n" "void main()\n" "{\n" " gl_Position = vec4 (in_position, 0.0, 1.0);\n" " texcoord = in_texcoord;\n" "}\n"; static const GLchar *stock_vertex_shader_140 = "#version 140\n" "in vec2 in_position;\n" "in vec2 in_texcoord;\n" "out vec2 texcoord;\n" "void main()\n" "{\n" " gl_Position = vec4 (in_position, 0.0, 1.0);\n" " texcoord = in_texcoord;\n" "}\n"; static const GLchar *stock_fragment_shader_110 = "#version 110\n" "uniform sampler2D texmap;\n" "varying vec2 texcoord;\n" "void main()\n" "{\n" " gl_FragColor = texture2D(texmap, texcoord);\n" "}\n"; static const GLchar *stock_fragment_shader_140 = "#version 140\n" "uniform sampler2D texmap;\n" "out vec4 fragcolor;\n" "in vec2 texcoord;\n" "void main()\n" "{\n" " fragcolor = texture2D(texmap, texcoord);\n" "}\n"; static GLfloat coords[] = { -1.0f, -1.0f, 1.0f, -1.0f, -1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, }; static void S9xViewportCallback (int src_width, int src_height, int viewport_x, int viewport_y, int viewport_width, int viewport_height, int *out_x, int *out_y, int *out_width, int *out_height) { S9xApplyAspect (src_width, src_height, viewport_width, viewport_height); *out_x = src_width + viewport_x; *out_y = src_height + viewport_y; *out_width = viewport_width; *out_height = viewport_height; } S9xOpenGLDisplayDriver::S9xOpenGLDisplayDriver (Snes9xWindow *window, Snes9xConfig *config) { this->window = window; this->config = config; this->drawing_area = GTK_WIDGET (window->drawing_area); } void S9xOpenGLDisplayDriver::update (int width, int height, int yoffset) { uint8 *final_buffer = NULL; int final_pitch; void *pbo_map = NULL; int x, y, w, h; GtkAllocation allocation; gtk_widget_get_allocation (drawing_area, &allocation); if (output_window_width != allocation.width || output_window_height != allocation.height) { resize (); } #if GTK_CHECK_VERSION(3,10,0) int gdk_scale_factor = gdk_window_get_scale_factor (gdk_window); allocation.width *= gdk_scale_factor; allocation.height *= gdk_scale_factor; #endif if (!legacy) glActiveTexture (GL_TEXTURE0); glBindTexture (GL_TEXTURE_2D, texmap); GLint filter = Settings.BilinearFilter ? GL_LINEAR : GL_NEAREST; glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filter); glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filter); GLint clamp = (using_glsl_shaders || !npot) ? GL_CLAMP_TO_BORDER : GL_CLAMP_TO_EDGE; glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, clamp); glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, clamp); glClear (GL_COLOR_BUFFER_BIT); if (config->scale_method > 0) { uint8 *src_buffer = (uint8 *) padded_buffer[0]; int src_pitch = image_width * image_bpp; uint8 *dst_buffer; int dst_pitch; src_buffer += (src_pitch * yoffset); dst_buffer = (uint8 *) padded_buffer[1]; dst_pitch = scaled_max_width * image_bpp; final_buffer = (uint8 *) padded_buffer[1]; final_pitch = scaled_max_width * image_bpp; S9xFilter (src_buffer, src_pitch, dst_buffer, dst_pitch, width, height); } else { final_buffer = (uint8 *) padded_buffer[0]; final_pitch = image_width * image_bpp; final_buffer += (final_pitch * yoffset); } x = width; y = height; w = allocation.width; h = allocation.height; S9xApplyAspect (x, y, w, h); glViewport (x, allocation.height - y - h, w, h); window->set_mouseable_area (x, y, w, h); update_texture_size (width, height); if (using_pbos) { if (config->pbo_format == 16) { glBindBuffer (GL_PIXEL_UNPACK_BUFFER, pbo); glBufferData (GL_PIXEL_UNPACK_BUFFER, width * height * 2, NULL, GL_STREAM_DRAW); pbo_map = glMapBuffer (GL_PIXEL_UNPACK_BUFFER, GL_WRITE_ONLY); for (int y = 0; y < height; y++) { memcpy ((uint8 *) pbo_map + (width * y * 2), final_buffer + (y * final_pitch), width * image_bpp); } glUnmapBuffer (GL_PIXEL_UNPACK_BUFFER); glPixelStorei (GL_UNPACK_ROW_LENGTH, width); glTexSubImage2D (GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, BUFFER_OFFSET (0)); glBindBuffer (GL_PIXEL_UNPACK_BUFFER, 0); } else /* 32-bit color */ { glBindBuffer (GL_PIXEL_UNPACK_BUFFER, pbo); glBufferData (GL_PIXEL_UNPACK_BUFFER, width * height * 4, NULL, GL_STREAM_DRAW); pbo_map = glMapBuffer (GL_PIXEL_UNPACK_BUFFER, GL_WRITE_ONLY); /* Pixel swizzling in software */ S9xSetEndianess (ENDIAN_NORMAL); S9xConvert (final_buffer, pbo_map, final_pitch, width * 4, width, height, 32); glUnmapBuffer (GL_PIXEL_UNPACK_BUFFER); glPixelStorei (GL_UNPACK_ROW_LENGTH, width); glTexSubImage2D (GL_TEXTURE_2D, 0, 0, 0, width, height, GL_BGRA, GL_UNSIGNED_BYTE, BUFFER_OFFSET (0)); glBindBuffer (GL_PIXEL_UNPACK_BUFFER, 0); } } else { glPixelStorei (GL_UNPACK_ROW_LENGTH, final_pitch / image_bpp); glTexSubImage2D (GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, final_buffer); } if (using_glsl_shaders) { glsl_shader->render (texmap, width, height, x, allocation.height - y - h, w, h, S9xViewportCallback); swap_buffers (); return; } if (legacy) { glVertexPointer (2, GL_FLOAT, 0, coords); glTexCoordPointer (2, GL_FLOAT, 0, &coords[8]); glDrawArrays (GL_TRIANGLE_STRIP, 0, 4); } else { glUseProgram (stock_program); glBindBuffer (GL_ARRAY_BUFFER, coord_buffer); glEnableVertexAttribArray (0); glVertexAttribPointer (0, 2, GL_FLOAT, GL_FALSE, 0, BUFFER_OFFSET (0)); glEnableVertexAttribArray (1); glVertexAttribPointer (1, 2, GL_FLOAT, GL_FALSE, 0, BUFFER_OFFSET (32)); glDrawArrays (GL_TRIANGLE_STRIP, 0, 4); glDisableVertexAttribArray (1); glDisableVertexAttribArray (0); } swap_buffers (); } void *S9xOpenGLDisplayDriver::get_parameters () { if (using_glsl_shaders && glsl_shader) { return (void *) &glsl_shader->param; } return NULL; } void S9xOpenGLDisplayDriver::save (const char *filename) { if (using_glsl_shaders && glsl_shader) { glsl_shader->save(filename); } } void S9xOpenGLDisplayDriver::clear_buffers () { memset (buffer[0], 0, image_padded_size); memset (buffer[1], 0, scaled_padded_size); glPixelStorei (GL_UNPACK_ROW_LENGTH, scaled_max_width); glTexSubImage2D (GL_TEXTURE_2D, 0, 0, 0, scaled_max_width, scaled_max_height, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, buffer[1]); } void S9xOpenGLDisplayDriver::update_texture_size (int width, int height) { if (width != texture_width || height != texture_height) { if (npot) { glBindTexture (GL_TEXTURE_2D, texmap); if (using_pbos && config->pbo_format == 32) { glTexImage2D (GL_TEXTURE_2D, 0, 4, width, height, 0, GL_BGRA, GL_UNSIGNED_BYTE, NULL); } else { glTexImage2D (GL_TEXTURE_2D, 0, GL_RGB565, width, height, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, NULL); } coords[9] = 1.0f; coords[10] = 1.0f; coords[11] = 1.0f; coords[14] = 1.0f; } else { coords[9] = height / 1024.0f; coords[10] = width / 1024.0f; coords[11] = height / 1024.0f; coords[14] = width / 1024.0f; } texture_width = width; texture_height = height; if (!legacy) { glBindBuffer (GL_ARRAY_BUFFER, coord_buffer); glBufferData (GL_ARRAY_BUFFER, sizeof (GLfloat) * 16, coords, GL_STATIC_DRAW); glBindBuffer (GL_ARRAY_BUFFER, 0); } } } int S9xOpenGLDisplayDriver::load_shaders (const char *shader_file) { int length = strlen (shader_file); if ((length > 6 && !strcasecmp(shader_file + length - 6, ".glslp")) || (length > 5 && !strcasecmp(shader_file + length - 5, ".glsl"))) { glsl_shader = new GLSLShader; if (glsl_shader->load_shader ((char *) shader_file)) { using_glsl_shaders = true; npot = true; if (glsl_shader->param.size () > 0) window->enable_widget ("shader_parameters_item", TRUE); return 1; } delete glsl_shader; } return 0; } int S9xOpenGLDisplayDriver::opengl_defaults () { npot = false; using_pbos = false; if (config->use_pbos) { using_pbos = true; } using_glsl_shaders = false; glsl_shader = NULL; if (config->use_shaders) { if (legacy || !load_shaders (config->fragment_shader)) { config->use_shaders = false; } } texture_width = 1024; texture_height = 1024; if (config->npot_textures) { npot = true; } if (legacy) { glEnableClientState (GL_VERTEX_ARRAY); glEnableClientState (GL_TEXTURE_COORD_ARRAY); glEnable (GL_TEXTURE_2D); glMatrixMode (GL_PROJECTION); glLoadIdentity (); glMatrixMode (GL_MODELVIEW); glLoadIdentity (); } else { stock_program = glCreateProgram (); GLuint vertex_shader = glCreateShader (GL_VERTEX_SHADER); GLuint fragment_shader = glCreateShader (GL_FRAGMENT_SHADER); if (version < 30) { glShaderSource (vertex_shader, 1, &stock_vertex_shader_110, NULL); glShaderSource (fragment_shader, 1, &stock_fragment_shader_110, NULL); } else { glShaderSource (vertex_shader, 1, &stock_vertex_shader_140, NULL); glShaderSource (fragment_shader, 1, &stock_fragment_shader_140, NULL); } glCompileShader (vertex_shader); glAttachShader (stock_program, vertex_shader); glCompileShader (fragment_shader); glAttachShader (stock_program, fragment_shader); glBindAttribLocation (stock_program, 0, "in_position"); glBindAttribLocation (stock_program, 1, "in_texcoord"); glLinkProgram (stock_program); glUseProgram (stock_program); glDeleteShader (vertex_shader); glDeleteShader (fragment_shader); GLint texture_uniform = glGetUniformLocation (stock_program, "texmap"); glUniform1i (texture_uniform, 0); if (core) { GLuint vao; glGenVertexArrays (1, &vao); glBindVertexArray (vao); } glGenBuffers (1, &coord_buffer); glBindBuffer (GL_ARRAY_BUFFER, coord_buffer); glBufferData (GL_ARRAY_BUFFER, sizeof (GLfloat) * 16, coords, GL_STATIC_DRAW); glBindBuffer (GL_ARRAY_BUFFER, 0); } if (config->use_pbos) { glGenBuffers (1, &pbo); glGenTextures (1, &texmap); glBindTexture (GL_TEXTURE_2D, texmap); glTexImage2D (GL_TEXTURE_2D, 0, config->pbo_format == 16 ? GL_RGB565 : 4, texture_width, texture_height, 0, config->pbo_format == 16 ? GL_RGB : GL_BGRA, config->pbo_format == 16 ? GL_UNSIGNED_SHORT_5_6_5 : GL_UNSIGNED_BYTE, NULL); glBindBuffer (GL_PIXEL_UNPACK_BUFFER, pbo); glBufferData (GL_PIXEL_UNPACK_BUFFER, texture_width * texture_height * 3, NULL, GL_STREAM_DRAW); glBindBuffer (GL_PIXEL_UNPACK_BUFFER, 0); } else { glGenTextures (1, &texmap); glBindTexture (GL_TEXTURE_2D, texmap); glTexImage2D (GL_TEXTURE_2D, 0, GL_RGB565, texture_width, texture_height, 0, GL_RGB, GL_UNSIGNED_SHORT_5_6_5, NULL); } glClearColor (0.0, 0.0, 0.0, 0.0); return 1; } void S9xOpenGLDisplayDriver::refresh (int width, int height) { resize (); } void S9xOpenGLDisplayDriver::resize () { context->resize (); context->swap_interval (config->sync_to_vblank); output_window_width = context->width; output_window_height = context->height; } int S9xOpenGLDisplayDriver::create_context () { gdk_window = gtk_widget_get_window (drawing_area); #ifdef GDK_WINDOWING_WAYLAND if (GDK_IS_WAYLAND_WINDOW (gdk_window)) { context = &wl; } #endif #ifdef GDK_WINDOWING_X11 if (GDK_IS_X11_WINDOW (gdk_window)) { context = &glx; } #endif if (!context->attach (drawing_area)) return 0; if (!context->create_context ()) return 0; output_window_width = context->width; output_window_height = context->height; context->make_current (); legacy = false; version = epoxy_gl_version (); if (version < 20) { printf ("OpenGL version is only %d.%d. Recommended version is 2.0.\n", version / 10, version % 10); legacy = true; } int profile_mask = 0; glGetIntegerv (GL_CONTEXT_PROFILE_MASK, &profile_mask); if (profile_mask & GL_CONTEXT_CORE_PROFILE_BIT) core = true; else core = false; return 1; } int S9xOpenGLDisplayDriver::init () { initialized = false; if (!create_context ()) { return -1; } if (!opengl_defaults ()) { return -1; } buffer[0] = malloc (image_padded_size); buffer[1] = malloc (scaled_padded_size); clear_buffers (); padded_buffer[0] = (void *) (((uint8 *) buffer[0]) + image_padded_offset); padded_buffer[1] = (void *) (((uint8 *) buffer[1]) + scaled_padded_offset); GFX.Screen = (uint16 *) padded_buffer[0]; GFX.Pitch = image_width * image_bpp; context->swap_interval (config->sync_to_vblank); initialized = true; return 0; } uint16 *S9xOpenGLDisplayDriver::get_next_buffer () { return (uint16 *) padded_buffer[0]; } void S9xOpenGLDisplayDriver::push_buffer (uint16 *src) { memmove (padded_buffer[0], src, image_size); } uint16 *S9xOpenGLDisplayDriver::get_current_buffer () { return (uint16 *) padded_buffer[0]; } void S9xOpenGLDisplayDriver::swap_buffers () { context->swap_buffers (); if (config->sync_every_frame) { usleep (0); glFinish (); } } void S9xOpenGLDisplayDriver::deinit () { if (!initialized) return; if (using_glsl_shaders) { window->enable_widget ("shader_parameters_item", FALSE); gtk_shader_parameters_dialog_close (); glsl_shader->destroy(); delete glsl_shader; } GFX.Screen = NULL; padded_buffer[0] = NULL; padded_buffer[1] = NULL; free (buffer[0]); free (buffer[1]); if (using_pbos) { glBindBuffer (GL_PIXEL_UNPACK_BUFFER, 0); glDeleteBuffers (1, &pbo); } glDeleteTextures (1, &texmap); } int S9xOpenGLDisplayDriver::query_availability () { GdkDisplay *gdk_display = gdk_display_get_default (); #ifdef GDK_WINDOWING_WAYLAND if (GDK_IS_WAYLAND_DISPLAY (gdk_display)) { return 1; } #endif #ifdef GDK_WINDOWING_X11 if (GDK_IS_X11_DISPLAY (gdk_display)) { Display *dpy = GDK_DISPLAY_XDISPLAY (gdk_display); if (glXQueryExtension (dpy, NULL, NULL) == True) { return 1; } } #endif if (gui_config->hw_accel == HWA_OPENGL) gui_config->hw_accel = HWA_NONE; return 0; }