OpenTTD
allegro_v.cpp
Go to the documentation of this file.
1 /* $Id: allegro_v.cpp 27167 2015-02-22 23:06:45Z frosch $ */
2 
3 /*
4  * This file is part of OpenTTD.
5  * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
6  * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
7  * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
8  */
9 
17 #ifdef WITH_ALLEGRO
18 
19 #include "../stdafx.h"
20 #include "../openttd.h"
21 #include "../gfx_func.h"
22 #include "../rev.h"
23 #include "../blitter/factory.hpp"
24 #include "../network/network.h"
25 #include "../core/random_func.hpp"
26 #include "../core/math_func.hpp"
27 #include "allegro_v.h"
28 #include <allegro.h>
29 
30 #include "../safeguards.h"
31 
32 #ifdef _DEBUG
33 /* Allegro replaces SEGV/ABRT signals meaning that the debugger will never
34  * be triggered, so rereplace the signals and make the debugger useful. */
35 #include <signal.h>
36 #endif
37 
38 static FVideoDriver_Allegro iFVideoDriver_Allegro;
39 
40 static BITMAP *_allegro_screen;
41 
42 #define MAX_DIRTY_RECTS 100
43 static PointDimension _dirty_rects[MAX_DIRTY_RECTS];
44 static int _num_dirty_rects;
45 
46 void VideoDriver_Allegro::MakeDirty(int left, int top, int width, int height)
47 {
48  if (_num_dirty_rects < MAX_DIRTY_RECTS) {
49  _dirty_rects[_num_dirty_rects].x = left;
50  _dirty_rects[_num_dirty_rects].y = top;
51  _dirty_rects[_num_dirty_rects].width = width;
52  _dirty_rects[_num_dirty_rects].height = height;
53  }
54  _num_dirty_rects++;
55 }
56 
57 static void DrawSurfaceToScreen()
58 {
59  int n = _num_dirty_rects;
60  if (n == 0) return;
61 
62  _num_dirty_rects = 0;
63  if (n > MAX_DIRTY_RECTS) {
64  blit(_allegro_screen, screen, 0, 0, 0, 0, _allegro_screen->w, _allegro_screen->h);
65  return;
66  }
67 
68  for (int i = 0; i < n; i++) {
69  blit(_allegro_screen, screen, _dirty_rects[i].x, _dirty_rects[i].y, _dirty_rects[i].x, _dirty_rects[i].y, _dirty_rects[i].width, _dirty_rects[i].height);
70  }
71 }
72 
73 
74 static void UpdatePalette(uint start, uint count)
75 {
76  static PALETTE pal;
77 
78  uint end = start + count;
79  for (uint i = start; i != end; i++) {
80  pal[i].r = _cur_palette.palette[i].r / 4;
81  pal[i].g = _cur_palette.palette[i].g / 4;
82  pal[i].b = _cur_palette.palette[i].b / 4;
83  pal[i].filler = 0;
84  }
85 
86  set_palette_range(pal, start, end - 1, 1);
87 }
88 
89 static void InitPalette()
90 {
91  UpdatePalette(0, 256);
92 }
93 
94 static void CheckPaletteAnim()
95 {
96  if (_cur_palette.count_dirty != 0) {
98 
99  switch (blitter->UsePaletteAnimation()) {
102  break;
103 
105  blitter->PaletteAnimate(_cur_palette);
106  break;
107 
109  break;
110 
111  default:
112  NOT_REACHED();
113  }
115  }
116 }
117 
118 static const Dimension default_resolutions[] = {
119  { 640, 480},
120  { 800, 600},
121  {1024, 768},
122  {1152, 864},
123  {1280, 800},
124  {1280, 960},
125  {1280, 1024},
126  {1400, 1050},
127  {1600, 1200},
128  {1680, 1050},
129  {1920, 1200}
130 };
131 
132 static void GetVideoModes()
133 {
134  /* Need to set a gfx_mode as there is NO other way to autodetect for
135  * cards ourselves... and we need a card to get the modes. */
136  set_gfx_mode(_fullscreen ? GFX_AUTODETECT_FULLSCREEN : GFX_AUTODETECT_WINDOWED, 640, 480, 0, 0);
137 
138  GFX_MODE_LIST *mode_list = get_gfx_mode_list(gfx_driver->id);
139  if (mode_list == NULL) {
140  memcpy(_resolutions, default_resolutions, sizeof(default_resolutions));
141  _num_resolutions = lengthof(default_resolutions);
142  return;
143  }
144 
145  GFX_MODE *modes = mode_list->mode;
146 
147  int n = 0;
148  for (int i = 0; modes[i].bpp != 0; i++) {
149  uint w = modes[i].width;
150  uint h = modes[i].height;
151  if (w >= 640 && h >= 480) {
152  int j;
153  for (j = 0; j < n; j++) {
154  if (_resolutions[j].width == w && _resolutions[j].height == h) break;
155  }
156 
157  if (j == n) {
158  _resolutions[j].width = w;
159  _resolutions[j].height = h;
160  if (++n == lengthof(_resolutions)) break;
161  }
162  }
163  }
164  _num_resolutions = n;
165  SortResolutions(_num_resolutions);
166 
167  destroy_gfx_mode_list(mode_list);
168 }
169 
170 static void GetAvailableVideoMode(uint *w, uint *h)
171 {
172  /* No video modes, so just try it and see where it ends */
173  if (_num_resolutions == 0) return;
174 
175  /* is the wanted mode among the available modes? */
176  for (int i = 0; i != _num_resolutions; i++) {
177  if (*w == _resolutions[i].width && *h == _resolutions[i].height) return;
178  }
179 
180  /* use the closest possible resolution */
181  int best = 0;
182  uint delta = Delta(_resolutions[0].width, *w) * Delta(_resolutions[0].height, *h);
183  for (int i = 1; i != _num_resolutions; ++i) {
184  uint newdelta = Delta(_resolutions[i].width, *w) * Delta(_resolutions[i].height, *h);
185  if (newdelta < delta) {
186  best = i;
187  delta = newdelta;
188  }
189  }
190  *w = _resolutions[best].width;
191  *h = _resolutions[best].height;
192 }
193 
194 static bool CreateMainSurface(uint w, uint h)
195 {
197  if (bpp == 0) usererror("Can't use a blitter that blits 0 bpp for normal visuals");
198  set_color_depth(bpp);
199 
200  GetAvailableVideoMode(&w, &h);
201  if (set_gfx_mode(_fullscreen ? GFX_AUTODETECT_FULLSCREEN : GFX_AUTODETECT_WINDOWED, w, h, 0, 0) != 0) {
202  DEBUG(driver, 0, "Allegro: Couldn't allocate a window to draw on '%s'", allegro_error);
203  return false;
204  }
205 
206  /* The size of the screen might be bigger than the part we can actually draw on!
207  * So calculate the size based on the top, bottom, left and right */
208  _allegro_screen = create_bitmap_ex(bpp, screen->cr - screen->cl, screen->cb - screen->ct);
209  _screen.width = _allegro_screen->w;
210  _screen.height = _allegro_screen->h;
211  _screen.pitch = ((byte*)screen->line[1] - (byte*)screen->line[0]) / (bpp / 8);
212  _screen.dst_ptr = _allegro_screen->line[0];
213 
214  /* Initialise the screen so we don't blit garbage to the screen */
215  memset(_screen.dst_ptr, 0, _screen.height * _screen.pitch);
216 
217  /* Set the mouse at the place where we expect it */
218  poll_mouse();
219  _cursor.pos.x = mouse_x;
220  _cursor.pos.y = mouse_y;
221 
223 
224  InitPalette();
225 
226  char caption[32];
227  seprintf(caption, lastof(caption), "OpenTTD %s", _openttd_revision);
228  set_window_title(caption);
229 
230  enable_hardware_cursor();
231  select_mouse_cursor(MOUSE_CURSOR_ARROW);
232  show_mouse(_allegro_screen);
233 
234  GameSizeChanged();
235 
236  return true;
237 }
238 
239 bool VideoDriver_Allegro::ClaimMousePointer()
240 {
241  select_mouse_cursor(MOUSE_CURSOR_NONE);
242  show_mouse(NULL);
243  disable_hardware_cursor();
244  return true;
245 }
246 
247 struct VkMapping {
248  uint16 vk_from;
249  byte vk_count;
250  byte map_to;
251 };
252 
253 #define AS(x, z) {x, 0, z}
254 #define AM(x, y, z, w) {x, y - x, z}
255 
256 static const VkMapping _vk_mapping[] = {
257  /* Pageup stuff + up/down */
258  AM(KEY_PGUP, KEY_PGDN, WKC_PAGEUP, WKC_PAGEDOWN),
259  AS(KEY_UP, WKC_UP),
260  AS(KEY_DOWN, WKC_DOWN),
261  AS(KEY_LEFT, WKC_LEFT),
262  AS(KEY_RIGHT, WKC_RIGHT),
263 
264  AS(KEY_HOME, WKC_HOME),
265  AS(KEY_END, WKC_END),
266 
267  AS(KEY_INSERT, WKC_INSERT),
268  AS(KEY_DEL, WKC_DELETE),
269 
270  /* Map letters & digits */
271  AM(KEY_A, KEY_Z, 'A', 'Z'),
272  AM(KEY_0, KEY_9, '0', '9'),
273 
274  AS(KEY_ESC, WKC_ESC),
275  AS(KEY_PAUSE, WKC_PAUSE),
276  AS(KEY_BACKSPACE, WKC_BACKSPACE),
277 
278  AS(KEY_SPACE, WKC_SPACE),
279  AS(KEY_ENTER, WKC_RETURN),
280  AS(KEY_TAB, WKC_TAB),
281 
282  /* Function keys */
283  AM(KEY_F1, KEY_F12, WKC_F1, WKC_F12),
284 
285  /* Numeric part. */
286  AM(KEY_0_PAD, KEY_9_PAD, '0', '9'),
287  AS(KEY_SLASH_PAD, WKC_NUM_DIV),
288  AS(KEY_ASTERISK, WKC_NUM_MUL),
289  AS(KEY_MINUS_PAD, WKC_NUM_MINUS),
290  AS(KEY_PLUS_PAD, WKC_NUM_PLUS),
291  AS(KEY_ENTER_PAD, WKC_NUM_ENTER),
292  AS(KEY_DEL_PAD, WKC_DELETE),
293 
294  /* Other non-letter keys */
295  AS(KEY_SLASH, WKC_SLASH),
296  AS(KEY_SEMICOLON, WKC_SEMICOLON),
297  AS(KEY_EQUALS, WKC_EQUALS),
298  AS(KEY_OPENBRACE, WKC_L_BRACKET),
299  AS(KEY_BACKSLASH, WKC_BACKSLASH),
300  AS(KEY_CLOSEBRACE, WKC_R_BRACKET),
301 
302  AS(KEY_QUOTE, WKC_SINGLEQUOTE),
303  AS(KEY_COMMA, WKC_COMMA),
304  AS(KEY_MINUS, WKC_MINUS),
305  AS(KEY_STOP, WKC_PERIOD),
306  AS(KEY_TILDE, WKC_BACKQUOTE),
307 };
308 
309 static uint32 ConvertAllegroKeyIntoMy(WChar *character)
310 {
311  int scancode;
312  int unicode = ureadkey(&scancode);
313 
314  const VkMapping *map;
315  uint key = 0;
316 
317  for (map = _vk_mapping; map != endof(_vk_mapping); ++map) {
318  if ((uint)(scancode - map->vk_from) <= map->vk_count) {
319  key = scancode - map->vk_from + map->map_to;
320  break;
321  }
322  }
323 
324  if (key_shifts & KB_SHIFT_FLAG) key |= WKC_SHIFT;
325  if (key_shifts & KB_CTRL_FLAG) key |= WKC_CTRL;
326  if (key_shifts & KB_ALT_FLAG) key |= WKC_ALT;
327 #if 0
328  DEBUG(driver, 0, "Scancode character pressed %u", scancode);
329  DEBUG(driver, 0, "Unicode character pressed %u", unicode);
330 #endif
331 
332  *character = unicode;
333  return key;
334 }
335 
336 static const uint LEFT_BUTTON = 0;
337 static const uint RIGHT_BUTTON = 1;
338 
339 static void PollEvent()
340 {
341  poll_mouse();
342 
343  bool mouse_action = false;
344 
345  /* Mouse buttons */
346  static int prev_button_state;
347  if (prev_button_state != mouse_b) {
348  uint diff = prev_button_state ^ mouse_b;
349  while (diff != 0) {
350  uint button = FindFirstBit(diff);
351  ClrBit(diff, button);
352  if (HasBit(mouse_b, button)) {
353  /* Pressed mouse button */
354  if (_rightclick_emulate && (key_shifts & KB_CTRL_FLAG)) {
355  button = RIGHT_BUTTON;
356  ClrBit(diff, RIGHT_BUTTON);
357  }
358  switch (button) {
359  case LEFT_BUTTON:
360  _left_button_down = true;
361  break;
362 
363  case RIGHT_BUTTON:
364  _right_button_down = true;
365  _right_button_clicked = true;
366  break;
367 
368  default:
369  /* ignore rest */
370  break;
371  }
372  } else {
373  /* Released mouse button */
374  if (_rightclick_emulate) {
375  _right_button_down = false;
376  _left_button_down = false;
377  _left_button_clicked = false;
378  } else if (button == LEFT_BUTTON) {
379  _left_button_down = false;
380  _left_button_clicked = false;
381  } else if (button == RIGHT_BUTTON) {
382  _right_button_down = false;
383  }
384  }
385  }
386  prev_button_state = mouse_b;
387  mouse_action = true;
388  }
389 
390  /* Mouse movement */
391  if (_cursor.UpdateCursorPosition(mouse_x, mouse_y, false)) {
392  position_mouse(_cursor.pos.x, _cursor.pos.y);
393  }
394  if (_cursor.delta.x != 0 || _cursor.delta.y) mouse_action = true;
395 
396  static int prev_mouse_z = 0;
397  if (prev_mouse_z != mouse_z) {
398  _cursor.wheel = (prev_mouse_z - mouse_z) < 0 ? -1 : 1;
399  prev_mouse_z = mouse_z;
400  mouse_action = true;
401  }
402 
403  if (mouse_action) HandleMouseEvents();
404 
405  poll_keyboard();
406  if ((key_shifts & KB_ALT_FLAG) && (key[KEY_ENTER] || key[KEY_F])) {
407  ToggleFullScreen(!_fullscreen);
408  } else if (keypressed()) {
409  WChar character;
410  uint keycode = ConvertAllegroKeyIntoMy(&character);
411  HandleKeypress(keycode, character);
412  }
413 }
414 
419 int _allegro_instance_count = 0;
420 
421 const char *VideoDriver_Allegro::Start(const char * const *parm)
422 {
423  if (_allegro_instance_count == 0 && install_allegro(SYSTEM_AUTODETECT, &errno, NULL)) {
424  DEBUG(driver, 0, "allegro: install_allegro failed '%s'", allegro_error);
425  return "Failed to set up Allegro";
426  }
427  _allegro_instance_count++;
428 
429  install_timer();
430  install_mouse();
431  install_keyboard();
432 
433 #if defined _DEBUG
434 /* Allegro replaces SEGV/ABRT signals meaning that the debugger will never
435  * be triggered, so rereplace the signals and make the debugger useful. */
436  signal(SIGABRT, NULL);
437  signal(SIGSEGV, NULL);
438 #endif
439 
440 #if defined(DOS)
441  /* Force DOS builds to ALWAYS use full screen as
442  * it can't do windowed. */
443  _fullscreen = true;
444 #endif
445 
446  GetVideoModes();
447  if (!CreateMainSurface(_cur_resolution.width, _cur_resolution.height)) {
448  return "Failed to set up Allegro video";
449  }
451  set_close_button_callback(HandleExitGameRequest);
452 
453  return NULL;
454 }
455 
457 {
458  if (--_allegro_instance_count == 0) allegro_exit();
459 }
460 
461 #if defined(UNIX) || defined(__OS2__) || defined(PSP) || defined(DOS)
462 # include <sys/time.h> /* gettimeofday */
463 
464 static uint32 GetTime()
465 {
466  struct timeval tim;
467 
468  gettimeofday(&tim, NULL);
469  return tim.tv_usec / 1000 + tim.tv_sec * 1000;
470 }
471 #else
472 static uint32 GetTime()
473 {
474  return GetTickCount();
475 }
476 #endif
477 
478 
480 {
481  uint32 cur_ticks = GetTime();
482  uint32 last_cur_ticks = cur_ticks;
483  uint32 next_tick = cur_ticks + MILLISECONDS_PER_TICK;
484 
485  CheckPaletteAnim();
486 
487  for (;;) {
488  uint32 prev_cur_ticks = cur_ticks; // to check for wrapping
489  InteractiveRandom(); // randomness
490 
491  PollEvent();
492  if (_exit_game) return;
493 
494 #if defined(_DEBUG)
495  if (_shift_pressed)
496 #else
497  /* Speedup when pressing tab, except when using ALT+TAB
498  * to switch to another application */
499  if (key[KEY_TAB] && (key_shifts & KB_ALT_FLAG) == 0)
500 #endif
501  {
502  if (!_networking && _game_mode != GM_MENU) _fast_forward |= 2;
503  } else if (_fast_forward & 2) {
504  _fast_forward = 0;
505  }
506 
507  cur_ticks = GetTime();
508  if (cur_ticks >= next_tick || (_fast_forward && !_pause_mode) || cur_ticks < prev_cur_ticks) {
509  _realtime_tick += cur_ticks - last_cur_ticks;
510  last_cur_ticks = cur_ticks;
511  next_tick = cur_ticks + MILLISECONDS_PER_TICK;
512 
513  bool old_ctrl_pressed = _ctrl_pressed;
514 
515  _ctrl_pressed = !!(key_shifts & KB_CTRL_FLAG);
516  _shift_pressed = !!(key_shifts & KB_SHIFT_FLAG);
517 
518  /* determine which directional keys are down */
519  _dirkeys =
520  (key[KEY_LEFT] ? 1 : 0) |
521  (key[KEY_UP] ? 2 : 0) |
522  (key[KEY_RIGHT] ? 4 : 0) |
523  (key[KEY_DOWN] ? 8 : 0);
524 
525  if (old_ctrl_pressed != _ctrl_pressed) HandleCtrlChanged();
526 
527  GameLoop();
528 
529  UpdateWindows();
530  CheckPaletteAnim();
531  DrawSurfaceToScreen();
532  } else {
533  CSleep(1);
535  DrawMouseCursor();
536  DrawSurfaceToScreen();
537  }
538  }
539 }
540 
541 bool VideoDriver_Allegro::ChangeResolution(int w, int h)
542 {
543  return CreateMainSurface(w, h);
544 }
545 
546 bool VideoDriver_Allegro::ToggleFullscreen(bool fullscreen)
547 {
548 #ifdef DOS
549  return false;
550 #else
551  _fullscreen = fullscreen;
552  GetVideoModes(); // get the list of available video modes
553  if (_num_resolutions == 0 || !this->ChangeResolution(_cur_resolution.width, _cur_resolution.height)) {
554  /* switching resolution failed, put back full_screen to original status */
555  _fullscreen ^= true;
556  return false;
557  }
558  return true;
559 #endif
560 }
561 
563 {
564  return CreateMainSurface(_screen.width, _screen.height);
565 }
566 
567 #endif /* WITH_ALLEGRO */