DEADSOFTWARE

sdl1/2: fix invalid window title
[d2df-sdl.git] / src / game / sdl / g_system.pas
index 25e95eca8841d5a987f5e87bb218bc0cb4f26fb9..0df28dc724dc5b391f37dc85b7a8373a12070727 100644 (file)
@@ -17,13 +17,6 @@ unit g_system;
 
 interface
 
-  (* To fix:
-   *   - Joystick support
-   *   - Window resizing using SDL_VIDEORESIZE
-   *   -- Linux: GL drawing area have wrong size
-   *   -- OSX: GL context are recreated
-   *)
-
   uses Utils;
 
   (* --- Utils --- *)
@@ -31,8 +24,8 @@ interface
   procedure sys_Delay (ms: Integer);
 
   (* --- Graphics --- *)
-  function sys_GetDispalyModes (bpp: Integer): SSArray;
-  function sys_SetDisplayMode (w, h, bpp: Integer; fullscreen: Boolean): Boolean;
+  function sys_GetDisplayModes (bpp: Integer): SSArray;
+  function sys_SetDisplayMode (w, h, bpp: Integer; fullscreen, maximized: Boolean): Boolean;
   procedure sys_EnableVSync (yes: Boolean);
   procedure sys_Repaint;
 
@@ -47,18 +40,27 @@ interface
 implementation
 
   uses
-    SysUtils, SDL, GL,
-    e_log, e_graphics, e_input,
-    g_options, g_window, g_console, g_game, g_menu, g_gui, g_main;
+    SysUtils, SDL, Math,
+    {$INCLUDE ../nogl/noGLuses.inc}
+    e_log, e_graphics, e_input, e_sound,
+    g_options, g_window, g_console, g_game, g_menu, g_gui, g_main, g_basic;
+
+  const
+    GameTitle = 'Doom 2D: Forever (SDL 1.2, %s)';
 
   var
+    userResize: Boolean;
+    modeResize: Integer;
     screen: PSDL_Surface;
+    JoystickHandle: array [0..e_MaxJoys - 1] of PSDL_Joystick;
+    JoystickHatState: array [0..e_MaxJoys - 1, 0..e_MaxJoyHats - 1, HAT_LEFT..HAT_DOWN] of Boolean;
+    JoystickZeroAxes: array [0..e_MaxJoys - 1, 0..e_MaxJoyAxes - 1] of Integer;
 
   (* --------- Utils --------- *)
 
   function sys_GetTicks (): Int64;
   begin
-    Result := SDL_GetTicks()
+    result := SDL_GetTicks()
   end;
 
   procedure sys_Delay (ms: Integer);
@@ -68,12 +70,47 @@ implementation
 
   (* --------- Graphics --------- *)
 
+  function LoadGL: Boolean;
+  begin
+    result := true;
+    {$IFDEF NOGL_INIT}
+    nogl_Init;
+    if glRenderToFBO and (not nogl_ExtensionSupported('GL_OES_framebuffer_object')) then
+    {$ELSE}
+    if glRenderToFBO and (not Load_GL_ARB_framebuffer_object) then
+    {$ENDIF}
+    begin
+      e_LogWriteln('GL: framebuffer objects not supported; disabling FBO rendering');
+      glRenderToFBO := false;
+    end;
+  end;
+
+  procedure FreeGL;
+  begin
+    {$IFDEF NOGL_INIT}
+    nogl_Quit();
+    {$ENDIF}
+  end;
+
   procedure UpdateSize (w, h: Integer);
   begin
     gWinSizeX := w;
     gWinSizeY := h;
-    gWinRealPosX := 0;
-    gWinRealPosY := 0;
+    gRC_Width := w;
+    gRC_Height := h;
+    if glRenderToFBO then
+    begin
+      // store real window size in gWinSize, downscale resolution now
+      w := round(w / r_pixel_scale);
+      h := round(h / r_pixel_scale);
+      if not e_ResizeFramebuffer(w, h) then
+      begin
+        e_LogWriteln('GL: could not create framebuffer, falling back to --no-fbo');
+        glRenderToFBO := False;
+        w := gWinSizeX;
+        h := gWinSizeY;
+      end;
+    end;
     gScreenWidth := w;
     gScreenHeight := h;
     {$IFDEF ENABLE_HOLMES}
@@ -87,29 +124,46 @@ implementation
     g_Game_ClearLoading;
   end;
 
+  function GetTitle (): AnsiString;
+    var info: AnsiString;
+  begin
+    info := g_GetBuildHash(false);
+    if info = 'custom build' then
+      info := info + ' by ' + g_GetBuilderName() + ' ' + GAME_BUILDDATE + ' ' + GAME_BUILDTIME;
+    result := Format(GameTitle, [info]);
+  end;
+
   function InitWindow (w, h, bpp: Integer; fullScreen: Boolean): Boolean;
-    var flags: Uint32;
+    var flags: Uint32; title: AnsiString;
   begin
-    e_LogWritefln('InitWindow %s %s %s %s', [w, h, bpp, fullscreen]);
-    Result := False;
+    e_LogWritefln('InitWindow %s %s %s %s', [w, h, bpp, fullScreen]);
+    result := false;
     SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8);
     SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8);
     SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8);
     SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 16);
     SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
     SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8); // lights; it is enough to have 1-bit stencil buffer for lighting, but...
-    flags := SDL_OPENGL or SDL_VIDEORESIZE;
-    if fullScreen then
-      flags := flags or SDL_FULLSCREEN;
+    flags := SDL_OPENGL;
+    if fullScreen then flags := flags or SDL_FULLSCREEN;
+    if userResize then flags := flags or SDL_VIDEORESIZE;
     if (screen = nil) or (SDL_VideoModeOk(w, h, bpp, flags) <> 0) then
     begin
       SDL_FreeSurface(screen);
       screen := SDL_SetVideoMode(w, h, bpp, flags);
       if screen <> nil then
       begin
-        SDL_WM_SetCaption('Doom 2D: Forever (SDL 1.2)', nil);
+        if not LoadGL then
+        begin
+          e_LogWriteln('GL: unable to load OpenGL functions', TMsgType.Fatal);
+          exit;
+        end;
+        title := GetTitle();
+        SDL_WM_SetCaption(PChar(title), nil);
+        gFullScreen := fullscreen;
+        gRC_FullScreen := fullscreen;
         UpdateSize(w, h);
-        Result := True
+        result := True
       end
     end
     else
@@ -128,16 +182,16 @@ implementation
     (* ??? *)
   end;
 
-  function sys_GetDispalyModes (bpp: Integer): SSArray;
+  function sys_GetDisplayModes (bpp: Integer): SSArray;
     var m: PPSDL_Rect; f: TSDL_PixelFormat; i, count: Integer;
   begin
-    SetLength(result, 0);
+    result := nil;
     FillChar(f, sizeof(f), 0);
     f.palette := nil;
     f.BitsPerPixel := bpp;
     f.BytesPerPixel := (bpp + 7) div 8;
     m := SDL_ListModes(@f, SDL_OPENGL or SDL_FULLSCREEN);
-    if (m <> NIL) and (IntPtr(m) <> -1) then
+    if (m <> NIL) and (UIntPtr(m) <> UIntPtr(-1)) then
     begin
       count := 0;
       while m[count] <> nil do inc(count);
@@ -147,11 +201,148 @@ implementation
     end
   end;
 
-  function sys_SetDisplayMode (w, h, bpp: Integer; fullscreen: Boolean): Boolean;
+  function sys_SetDisplayMode (w, h, bpp: Integer; fullscreen, maximized: Boolean): Boolean;
   begin
     result := InitWindow(w, h, bpp, fullscreen)
   end;
 
+  (* --------- Joystick --------- *)
+
+  procedure HandleJoyButton (var ev: TSDL_JoyButtonEvent);
+    var down: Boolean; key: Integer;
+  begin
+    if (ev.which < e_MaxJoys) and (ev.button < e_MaxJoyBtns) then
+    begin
+      key := e_JoyButtonToKey(ev.which, ev.button);
+      down := ev.type_ = SDL_JOYBUTTONDOWN;
+      if g_dbg_input then
+        e_LogWritefln('Input Debug: jbutton, joy=%s, button=%s, keycode=%s, press=%s', [ev.which, ev.button, key, down]);
+      e_KeyUpDown(key, down);
+      g_Console_ProcessBind(key, down)
+    end
+    else
+    begin
+      if g_dbg_input then
+      begin
+        down := ev.type_ = SDL_JOYBUTTONDOWN;
+        e_LogWritefln('Input Debug: NOT IN RANGE! jbutton, joy=%s, button=%s, press=%s', [ev.which, ev.button, down])
+      end
+    end
+  end;
+
+  procedure HandleJoyAxis (var ev: TSDL_JoyAxisEvent);
+    var key, minuskey: Integer;
+  begin
+    if (ev.which < e_MaxJoys) and (ev.axis < e_MaxJoyAxes) then
+    begin
+      key := e_JoyAxisToKey(ev.which, ev.axis, AX_PLUS);
+      minuskey := e_JoyAxisToKey(ev.which, ev.axis, AX_MINUS);
+
+      if g_dbg_input then
+          e_LogWritefln('Input Debug: jaxis, joy=%s, axis=%s, value=%s, zeroaxes=%s, deadzone=%s', [ev.which, ev.axis, ev.value, JoystickZeroAxes[ev.which, ev.axis], e_JoystickDeadzones[ev.which]]);
+
+      if ev.value < JoystickZeroAxes[ev.which, ev.axis] - e_JoystickDeadzones[ev.which] then
+      begin
+        if (e_KeyPressed(key)) then
+        begin
+          e_KeyUpDown(key, False);
+          g_Console_ProcessBind(key, False)
+        end;
+        e_KeyUpDown(minuskey, True);
+        g_Console_ProcessBind(minuskey, True)
+      end
+      else if ev.value > JoystickZeroAxes[ev.which, ev.axis] + e_JoystickDeadzones[ev.which] then
+      begin
+        if (e_KeyPressed(minuskey)) then
+        begin
+          e_KeyUpDown(minuskey, False);
+          g_Console_ProcessBind(minuskey, False)
+        end;
+        e_KeyUpDown(key, True);
+        g_Console_ProcessBind(key, True)
+      end
+      else
+      begin
+        if (e_KeyPressed(minuskey)) then
+        begin
+          e_KeyUpDown(minuskey, False);
+          g_Console_ProcessBind(minuskey, False)
+        end;
+        if (e_KeyPressed(key)) then
+        begin
+          e_KeyUpDown(key, False);
+          g_Console_ProcessBind(key, False)
+        end
+      end
+    end
+    else
+    begin
+      if g_dbg_input then
+        e_LogWritefln('Input Debug: NOT IN RANGE! jaxis, joy=%s, axis=%s, value=%s, zeroaxes=%s, deadzone=%s', [ev.which, ev.axis, ev.value, JoystickZeroAxes[ev.which, ev.axis], e_JoystickDeadzones[ev.which]])
+    end
+  end;
+
+  procedure HandleJoyHat (var ev: TSDL_JoyHatEvent);
+    var
+      down: Boolean;
+      i, key: Integer;
+      hat: array [HAT_LEFT..HAT_DOWN] of Boolean;
+  begin
+    if (ev.which < e_MaxJoys) and (ev.hat < e_MaxJoyHats) then
+    begin
+      if g_dbg_input then
+        e_LogWritefln('Input Debug: jhat, joy=%s, hat=%s, value=%s', [ev.which, ev.hat, ev.value]);
+      hat[HAT_UP] := LongBool(ev.value and SDL_HAT_UP);
+      hat[HAT_DOWN] := LongBool(ev.value and SDL_HAT_DOWN);
+      hat[HAT_LEFT] := LongBool(ev.value and SDL_HAT_LEFT);
+      hat[HAT_RIGHT] := LongBool(ev.value and SDL_HAT_RIGHT);
+      for i := HAT_LEFT to HAT_DOWN do
+      begin
+        if JoystickHatState[ev.which, ev.hat, i] <> hat[i] then
+        begin
+          down := hat[i];
+          key := e_JoyHatToKey(ev.which, ev.hat, i);
+          e_KeyUpDown(key, down);
+          g_Console_ProcessBind(key, down)
+        end
+      end;
+      JoystickHatState[ev.which, ev.hat] := hat
+    end
+    else
+    begin
+      if g_dbg_input then
+        e_LogWritefln('Input Debug: NOT IN RANGE! jhat, joy=%s, hat=%s, value=%s', [ev.which, ev.hat, ev.value])
+    end
+  end;
+
+  procedure AddJoystick (which: Integer);
+    var i: Integer;
+  begin
+    assert(which < e_MaxJoys);
+    JoystickHandle[which] := SDL_JoystickOpen(which);
+    if JoystickHandle[which] <> nil then
+    begin
+      e_LogWritefln('Added Joystick %s', [which]);
+      e_JoystickAvailable[which] := True;
+      for i := 0 to Min(SDL_JoystickNumAxes(JoystickHandle[which]), e_MaxJoyAxes) - 1 do
+        JoystickZeroAxes[which, i] := SDL_JoystickGetAxis(JoystickHandle[which], i)
+    end
+    else
+    begin
+      e_LogWritefln('Failed to open Joystick %s', [which])
+    end
+  end;
+
+  procedure RemoveJoystick (which: Integer);
+  begin
+    assert(which < e_MaxJoys);
+    e_LogWritefln('Remove Joystick %s', [which]);
+    e_JoystickAvailable[which] := False;
+    if JoystickHandle[which] <> nil then
+      SDL_JoystickClose(JoystickHandle[which]);
+    JoystickHandle[which] := nil
+  end;
+
   (* --------- Input --------- *)
 
   function Key2Stub (key: Integer): Integer;
@@ -259,14 +450,24 @@ implementation
       e_KeyUpDown(key, down);
       g_Console_ProcessBind(key, down);
     end
-    else if gConsoleShow or gChatShow or (g_ActiveWindow <> nil) then
+    else
     begin
-      KeyPress(key)
+      g_Console_ProcessBindRepeat(key)
     end;
     if down and IsValid1251(ev.keysym.unicode) and IsPrintable1251(ch) then
       CharPress(ch)
   end;
 
+  procedure HandleResize (var ev: TSDL_ResizeEvent);
+  begin
+    if g_dbg_input then
+      e_LogWritefln('Input Debug: SDL_VIDEORESIZE %s %s', [ev.w, ev.h]);
+    if modeResize = 1 then
+      UpdateSize(ev.w, ev.h)
+    else if modeResize > 1 then
+      InitWindow(ev.w, ev.h, gBPP, gFullscreen)
+  end;
+
   function sys_HandleInput (): Boolean;
     var ev: TSDL_Event;
   begin
@@ -275,8 +476,13 @@ implementation
     begin
       case ev.type_ of
         SDL_QUITEV: result := true;
-        SDL_VIDEORESIZE: InitWindow(ev.resize.w, ev.resize.h, gBPP, gFullscreen);
+        SDL_VIDEORESIZE: HandleResize(ev.resize);
         SDL_KEYUP, SDL_KEYDOWN: HandleKeyboard(ev.key);
+        SDL_JOYBUTTONDOWN, SDL_JOYBUTTONUP: HandleJoyButton(ev.jbutton);
+        SDL_JOYAXISMOTION: HandleJoyAxis(ev.jaxis);
+        SDL_JOYHATMOTION: HandleJoyHat(ev.jhat);
+        SDL_VIDEOEXPOSE: sys_Repaint;
+        SDL_ACTIVEEVENT: e_MuteChannels((ev.active.gain = 0) and gMuteWhenInactive);
       end
     end
   end;
@@ -291,25 +497,38 @@ implementation
   (* --------- Init --------- *)
 
   procedure sys_Init;
-    var flags: Uint32; ok: Boolean;
+    var flags: Uint32; i: Integer;
   begin
+    e_WriteLog('Init SDL', TMsgType.Notify);
     flags := SDL_INIT_VIDEO or SDL_INIT_AUDIO or
              SDL_INIT_TIMER or SDL_INIT_JOYSTICK
              (*or SDL_INIT_NOPARACHUTE*);
     if SDL_Init(flags) <> 0 then
-      raise Exception.Create('SDL: Init failed: ' + SDL_GetError());
-    ok := InitWindow(gScreenWidth, gScreenHeight, gBPP, gFullScreen);
-    if not ok then
-      raise Exception.Create('SDL: failed to set videomode: ' + SDL_GetError);
+      raise Exception.Create('SDL: Init failed: ' + SDL_GetError);
     SDL_EnableUNICODE(1);
     SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL);
+    for i := 0 to e_MaxJoys - 1 do
+      AddJoystick(i)
   end;
 
   procedure sys_Final;
+    var i: Integer;
   begin
     e_WriteLog('Releasing SDL', TMsgType.Notify);
-    SDL_FreeSurface(screen);
+    for i := 0 to e_MaxJoys - 1 do
+      RemoveJoystick(i);
+    if screen <> nil then
+    begin
+      FreeGL;
+      SDL_FreeSurface(screen)
+    end;
     SDL_Quit
   end;
 
+initialization
+  (* window resize are broken both on linux and osx, so disabled by default *)
+  conRegVar('sdl_allow_resize', @userResize, 'allow to resize window by user', 'allow to resize window by user');
+  conRegVar('sdl_resize_action', @modeResize, 'set window resize mode (0: ignore, 1: change, 2: reset)', '');
+  userResize := false;
+  modeResize := 0;
 end.