--- Frodo4/Src/Display_SDL.h 2003/07/14 13:59:02 1.3 +++ Frodo4/Src/Display_SDL.h 2010/04/23 11:12:05 1.11 @@ -2,7 +2,7 @@ * Display_SDL.h - C64 graphics display, emulator window handling, * SDL specific stuff * - * Frodo (C) 1994-1997,2002-2003 Christian Bauer + * Frodo Copyright (C) Christian Bauer * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -25,6 +25,14 @@ #include +#ifdef ENABLE_OPENGL +#include +#endif + + +// Display dimensions including drive LEDs etc. +static const int FRAME_WIDTH = DISPLAY_X; +static const int FRAME_HEIGHT = DISPLAY_Y + 16; // Display surface static SDL_Surface *screen = NULL; @@ -37,6 +45,9 @@ static C64Display *c64_disp; static struct sigaction pulse_sa; static itimerval pulse_tv; +// SDL joysticks +static SDL_Joystick *joy[2] = {NULL, NULL}; + // Colors for speedometer/drive LEDs enum { black = 0, @@ -49,6 +60,19 @@ enum { PALETTE_SIZE = 21 }; +#ifdef ENABLE_OPENGL + +// Display texture dimensions +static const int TEXTURE_SIZE = 512; // smallest power-of-two that fits DISPLAY_X/Y + +// Texture object for VIC palette +static GLuint palette_tex; + +// Texture object for VIC display +static GLuint vic_tex; + +#endif + /* C64 keyboard matrix: @@ -73,19 +97,65 @@ enum { int init_graphics(void) { // Init SDL - if (SDL_Init(SDL_INIT_VIDEO) < 0) { + if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK | SDL_INIT_AUDIO) < 0) { fprintf(stderr, "Couldn't initialize SDL (%s)\n", SDL_GetError()); return 0; } - // Open window - SDL_WM_SetCaption(VERSION_STRING, "Frodo"); - screen = SDL_SetVideoMode(DISPLAY_X, DISPLAY_Y + 17, 8, SDL_DOUBLEBUF); - return 1; } +#ifdef ENABLE_OPENGL +/* + * Set direct projection (GL coordinates = window pixel coordinates) + */ + +static void set_projection() +{ + int width = SDL_GetVideoSurface()->w; + int height = SDL_GetVideoSurface()->h; + + glViewport(0, 0, width, height); + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + + float aspect = float(width) / float(height); + const float want_aspect = float(FRAME_WIDTH) / float(FRAME_HEIGHT); + int left, right, top, bottom; + if (aspect > want_aspect) { + // Window too wide, center horizontally + top = 0; bottom = FRAME_HEIGHT; + int diff = (int(FRAME_WIDTH * aspect / want_aspect) - FRAME_WIDTH) / 2; + left = -diff; + right = FRAME_WIDTH + diff; + } else { + // Window too high, center vertically + left = 0; right = FRAME_WIDTH; + int diff = (int(FRAME_HEIGHT * want_aspect / aspect) - FRAME_HEIGHT) / 2; + top = -diff; + bottom = FRAME_HEIGHT + diff; + } + glOrtho(left, right, bottom, top, -1, 1); + + glClear(GL_COLOR_BUFFER_BIT); +} + + +/* + * User resized video display (only possible with OpenGL) + */ + +static void video_resized(int width, int height) +{ + uint32 flags = (ThePrefs.DisplayType == DISPTYPE_SCREEN ? SDL_FULLSCREEN : 0); + flags |= (SDL_ANYFORMAT | SDL_OPENGL | SDL_RESIZABLE); + SDL_SetVideoMode(width, height, 16, flags); + set_projection(); +} +#endif + + /* * Display constructor */ @@ -95,6 +165,157 @@ C64Display::C64Display(C64 *the_c64) : T quit_requested = false; speedometer_string[0] = 0; + // Open window + SDL_WM_SetCaption(VERSION_STRING, "Frodo"); + uint32 flags = (ThePrefs.DisplayType == DISPTYPE_SCREEN ? SDL_FULLSCREEN : 0); + +#ifdef ENABLE_OPENGL + + flags |= (SDL_ANYFORMAT | SDL_OPENGL | SDL_RESIZABLE); + SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 5); + SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 5); + SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 5); + SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); + SDL_Surface *real_screen = SDL_SetVideoMode(FRAME_WIDTH * 2, FRAME_HEIGHT * 2, 16, flags); + if (!real_screen) { + fprintf(stderr, "Couldn't initialize OpenGL video output (%s)\n", SDL_GetError()); + exit(1); + } + + // VIC display and UI elements are rendered into an off-screen surface + screen = SDL_CreateRGBSurface(SDL_SWSURFACE, FRAME_WIDTH, FRAME_HEIGHT, 8, 0xff, 0xff, 0xff, 0xff); + + // We need OpenGL 2.0 or higher + GLenum err = glewInit(); + if (err != GLEW_OK) { + fprintf(stderr, "Couldn't initialize GLEW (%s)\n", glewGetErrorString(err)); + exit(1); + } + + if (!glewIsSupported("GL_VERSION_2_0")) { + fprintf(stderr, "Frodo requires OpenGL 2.0 or higher\n"); + exit(1); + } + + // Set direct projection + set_projection(); + + // Set GL state + glShadeModel(GL_FLAT); + glDisable(GL_DITHER); + glColor4f(1.0, 1.0, 1.0, 1.0); + + // Create fragment shader for emulating a paletted texture + GLuint shader = glCreateShader(GL_FRAGMENT_SHADER_ARB); + const char * src = + "uniform sampler2D screen;" + "uniform sampler1D palette;" + "uniform float texSize;" + "void main()" + "{" +#if 0 + // Nearest neighbour + " vec4 idx = texture2D(screen, gl_TexCoord[0].st);" + " gl_FragColor = texture1D(palette, idx.r);" +#else + // Linear interpolation + // (setting the GL_TEXTURE_MAG_FILTER to GL_LINEAR would interpolate + // the color indices which is not what we want; we need to manually + // interpolate the palette values instead) + " const float texel = 1.0 / texSize;" + " vec2 st = gl_TexCoord[0].st - vec2(texel * 0.5, texel * 0.5);" + " vec4 idx00 = texture2D(screen, st);" + " vec4 idx01 = texture2D(screen, st + vec2(0, texel));" + " vec4 idx10 = texture2D(screen, st + vec2(texel, 0));" + " vec4 idx11 = texture2D(screen, st + vec2(texel, texel));" + " float s1 = fract(st.s * texSize);" + " float s0 = 1.0 - s1;" + " float t1 = fract(st.t * texSize);" + " float t0 = 1.0 - t1;" + " vec4 color00 = texture1D(palette, idx00.r) * s0 * t0;" + " vec4 color01 = texture1D(palette, idx01.r) * s0 * t1;" + " vec4 color10 = texture1D(palette, idx10.r) * s1 * t0;" + " vec4 color11 = texture1D(palette, idx11.r) * s1 * t1;" + " gl_FragColor = color00 + color01 + color10 + color11;" +#endif + "}"; + glShaderSource(shader, 1, &src, NULL); + glCompileShader(shader); + + GLint status; + glGetShaderiv(shader, GL_COMPILE_STATUS, &status); + if (status == GL_FALSE) { + GLint logLength = 0; + glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &logLength); + if (logLength > 0) { + GLchar *log = (GLchar *)malloc(logLength); + GLint actual; + glGetShaderInfoLog(shader, logLength, &actual, log); + fprintf(stderr, "%s\n", log); + exit(1); + } + } + + GLuint program = glCreateProgram(); + glAttachShader(program, shader); + glLinkProgram(program); + glUseProgram(program); + + glUniform1f(glGetUniformLocation(program, "texSize"), float(TEXTURE_SIZE)); + + // Create VIC display texture (8-bit color index in the red channel) + uint8 *tmp = (uint8 *)malloc(TEXTURE_SIZE * TEXTURE_SIZE); + memset(tmp, 0, TEXTURE_SIZE * TEXTURE_SIZE); + + glGenTextures(1, &vic_tex); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, vic_tex); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); // don't interpolate color index values + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, TEXTURE_SIZE, TEXTURE_SIZE, 0, GL_RED, GL_UNSIGNED_BYTE, tmp); + glUniform1i(glGetUniformLocation(program, "screen"), 0); + + free(tmp); + + // Create VIC palette texture + tmp = (uint8 *)malloc(256 * 3); + memset(tmp, 0xff, 256 * 3); + for (int i=0; i<16; ++i) { + tmp[i*3+0] = palette_red[i & 0x0f]; + tmp[i*3+1] = palette_green[i & 0x0f]; + tmp[i*3+2] = palette_blue[i & 0x0f]; + } + tmp[fill_gray*3+0] = tmp[fill_gray*3+1] = tmp[fill_gray*3+2] = 0xd0; + tmp[shine_gray*3+0] = tmp[shine_gray*3+1] = tmp[shine_gray*3+2] = 0xf0; + tmp[shadow_gray*3+0] = tmp[shadow_gray*3+1] = tmp[shadow_gray*3+2] = 0x80; + tmp[red*3+0] = 0xf0; + tmp[red*3+1] = tmp[red*3+2] = 0; + tmp[green*3+1] = 0xf0; + tmp[green*3+0] = tmp[green*3+2] = 0; + + glGenTextures(1, &palette_tex); + glActiveTexture(GL_TEXTURE1); + glBindTexture(GL_TEXTURE_1D, palette_tex); + glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); // don't interpolate palette entries + glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexImage1D(GL_TEXTURE_1D, 0, GL_RGBA8, 256, 0, GL_RGB, GL_UNSIGNED_BYTE, tmp); + glUniform1i(glGetUniformLocation(program, "palette"), 1); + + free(tmp); + +#else + + flags |= (SDL_HWSURFACE | SDL_DOUBLEBUF); + screen = SDL_SetVideoMode(FRAME_WIDTH, FRAME_HEIGHT, 8, flags); + +#endif + + // Hide mouse pointer in fullscreen mode + if (ThePrefs.DisplayType == DISPTYPE_SCREEN) + SDL_ShowCursor(0); + // LEDs off for (int i=0; i<4; i++) led_state[i] = old_led_state[i] = LED_OFF; @@ -119,7 +340,15 @@ C64Display::C64Display(C64 *the_c64) : T C64Display::~C64Display() { + pulse_tv.it_interval.tv_sec = 0; + pulse_tv.it_interval.tv_usec = 0; + pulse_tv.it_value.tv_sec = 0; + pulse_tv.it_value.tv_usec = 0; + setitimer(ITIMER_REAL, &pulse_tv, NULL); + SDL_Quit(); + + c64_disp = NULL; } @@ -129,6 +358,7 @@ C64Display::~C64Display() void C64Display::NewPrefs(Prefs *prefs) { + // Unused, we handle fullscreen/window mode switches in PollKeyboard() } @@ -190,8 +420,30 @@ void C64Display::Update(void) draw_string(screen, DISPLAY_X * 4/5 + 8, DISPLAY_Y + 4, "D\x12 11", black, fill_gray); draw_string(screen, 24, DISPLAY_Y + 4, speedometer_string, black, fill_gray); +#ifdef ENABLE_OPENGL + // Load screen to texture + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, vic_tex); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, FRAME_WIDTH, FRAME_HEIGHT, GL_RED, GL_UNSIGNED_BYTE, screen->pixels); + + // Draw textured rectangle + glBegin(GL_QUADS); + glTexCoord2f(0.0, 0.0); + glVertex2f(0.0, 0.0); + glTexCoord2f(float(FRAME_WIDTH) / TEXTURE_SIZE, 0.0); + glVertex2f(float(FRAME_WIDTH), 0.0); + glTexCoord2f(float(FRAME_WIDTH) / TEXTURE_SIZE, float(FRAME_HEIGHT) / TEXTURE_SIZE); + glVertex2f(float(FRAME_WIDTH), float(FRAME_HEIGHT)); + glTexCoord2f(0.0, float(FRAME_HEIGHT) / TEXTURE_SIZE); + glVertex2f(0.0, float(FRAME_HEIGHT)); + glEnd(); + + // Update display + SDL_GL_SwapBuffers(); +#else // Update display SDL_Flip(screen); +#endif } @@ -426,11 +678,27 @@ void C64Display::PollKeyboard(uint8 *key switch (event.key.keysym.sym) { case SDLK_F9: // F9: Invoke SAM - SAM(TheC64); + if (ThePrefs.DisplayType == DISPTYPE_WINDOW) // don't invoke in fullscreen mode + SAM(TheC64); break; - case SDLK_F10: // F10: Quit - quit_requested = true; + case SDLK_F10: // F10: Prefs/Quit + TheC64->Pause(); + if (ThePrefs.DisplayType == DISPTYPE_SCREEN) { // exit fullscreen mode + SDL_WM_ToggleFullScreen(SDL_GetVideoSurface()); + SDL_ShowCursor(1); + } + + if (!TheApp->RunPrefsEditor()) { + quit_requested = true; + } else { + if (ThePrefs.DisplayType == DISPTYPE_SCREEN) { // enter fullscreen mode + SDL_ShowCursor(0); + SDL_WM_ToggleFullScreen(SDL_GetVideoSurface()); + } + } + + TheC64->Resume(); break; case SDLK_F11: // F11: NMI (Restore) @@ -472,6 +740,13 @@ void C64Display::PollKeyboard(uint8 *key translate_key(event.key.keysym.sym, true, key_matrix, rev_matrix, joystick); break; +#ifdef ENABLE_OPENGL + // Window resized + case SDL_VIDEORESIZE: + video_resized(event.resize.w, event.resize.h); + break; +#endif + // Quit Frodo case SDL_QUIT: quit_requested = true; @@ -492,6 +767,80 @@ bool C64Display::NumLock(void) /* + * Open/close joystick drivers given old and new state of + * joystick preferences + */ + +void C64::open_close_joystick(int port, int oldjoy, int newjoy) +{ + if (oldjoy != newjoy) { + joy_minx[port] = joy_miny[port] = 32767; // Reset calibration + joy_maxx[port] = joy_maxy[port] = -32768; + if (newjoy) { + joy[port] = SDL_JoystickOpen(newjoy - 1); + if (joy[port] == NULL) + fprintf(stderr, "Couldn't open joystick %d\n", port + 1); + } else { + if (joy[port]) { + SDL_JoystickClose(joy[port]); + joy[port] = NULL; + } + } + } +} + +void C64::open_close_joysticks(int oldjoy1, int oldjoy2, int newjoy1, int newjoy2) +{ + open_close_joystick(0, oldjoy1, newjoy1); + open_close_joystick(1, oldjoy2, newjoy2); +} + + +/* + * Poll joystick port, return CIA mask + */ + +uint8 C64::poll_joystick(int port) +{ + uint8 j = 0xff; + + if (port == 0 && (joy[0] || joy[1])) + SDL_JoystickUpdate(); + + if (joy[port]) { + int x = SDL_JoystickGetAxis(joy[port], 0), y = SDL_JoystickGetAxis(joy[port], 1); + + if (x > joy_maxx[port]) + joy_maxx[port] = x; + if (x < joy_minx[port]) + joy_minx[port] = x; + if (y > joy_maxy[port]) + joy_maxy[port] = y; + if (y < joy_miny[port]) + joy_miny[port] = y; + + if (joy_maxx[port] - joy_minx[port] < 100 || joy_maxy[port] - joy_miny[port] < 100) + return 0xff; + + if (x < (joy_minx[port] + (joy_maxx[port]-joy_minx[port])/3)) + j &= 0xfb; // Left + else if (x > (joy_minx[port] + 2*(joy_maxx[port]-joy_minx[port])/3)) + j &= 0xf7; // Right + + if (y < (joy_miny[port] + (joy_maxy[port]-joy_miny[port])/3)) + j &= 0xfe; // Up + else if (y > (joy_miny[port] + 2*(joy_maxy[port]-joy_miny[port])/3)) + j &= 0xfd; // Down + + if (SDL_JoystickGetButton(joy[port], 0)) + j &= 0xef; // Button + } + + return j; +} + + +/* * Allocate C64 colors */ @@ -521,7 +870,7 @@ void C64Display::InitColors(uint8 *color * Show a requester (error message) */ -long int ShowRequester(char *a,char *b,char *) +long int ShowRequester(const char *a, const char *b, const char *) { printf("%s: %s\n", a, b); return 1;