Programming Linux Games phần 9 pdf

42 248 0
Programming Linux Games phần 9 pdf

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

Thông tin tài liệu

GAMING WITH THE LINUX CONSOLE 327 Personally I think this design is seriously misguided (a program can easily leave the console in an unusable state), but it’s entrenched at this point. It might be a good idea to create a script to restore the framebuffer’s state, in case you find yourself without a working display after a failed program run. Some programs partially overcome the problem by opening a new virtual terminal before setting the video mode and then switching back to the old one when they exit. How Video Scanning Works A video mode can be described by several key properties. Users and even programmers usually just speak of video modes in terms of resolution and color depth, with an occasional mention of refresh rate. A more precise way of describing a video mode is to list the various time intervals involved in the video scanning process. This information has to be provided at some point, regardless of how it is presented to the user or programmer; the CRTC unit needs it in order to scan an image on the display. Although these numbers may seem mysterious at first, they’re pretty simple to work with once you understand what they mean. Figure 8–1 illustrates the different components involved in a single video refresh. The monitor’s electron beam (see the beginning of Chapter 4) starts in the upper left corner, sweeping from left to right repeatedly as it moves from the top of the screen to the bottom. Each horizontal sweep is called a scanline. The process begins with a few wasted sweeps. This period is called the upper margin, and it serves to get the electron beam in position for the actual image-drawing process. The upper margin is measured in scanlines. Now that the beam is positioned at the upper left corner of the visible portion of the monitor, it can start producing pixels. It proceeds to generate a complete image by performing a number of sweeps equal to the vertical resolution of the display mode (480 scanlines, in the case of Figure 8–1). Each sweep begins with a blanking period (dead zone) called the left margin and ends with another blanking period called the right margin. These two margins are measured in pixels, and actual drawing takes place between them. Immediately after the right margin, a brief horizontal sync pulse from the video card instructs the 328 CHAPTER 8 Upper margin (33 hidden scanlines) Left margin (48 hidden pixels) Right margin (16 hidden pixels) Lower margin (10 hidden scanlines) X resolution (640 pixels) Y resolution (480 pixels) Vertical retrace pulse (time equiv. to 2 scanlines) Horizontal retrace pulse (time equiv. to 96 pixels) Electron beam scanning direction Figure 8–1: Components used to describe video timings monitor to begin a new scanline. The interval between the sync pulse and the start of the next scanline is known as the horizontal retrace. A complete refresh ends with a lower margin and a final vertical sync pulse, both timed in scanlines. After receiving the vertical sync pulse, the monitor prepares for another refresh by moving the electron beam back to the upper left corner. The time between the start of the lower margin and the end of the subsequent upper margin is known as the vertical retrace. (This is an ideal time to update video memory, since the video hardware isn’t updating the display.) GAMING WITH THE LINUX CONSOLE 329 To fully describe a video mode, then, we need the following pieces of information: dotclock Dotclock frequency (the time it takes to draw one pixel) hres Horizontal resolution (in dotclocks) vres Vertical resolution (in dotclocks) hsync Horizontal sync pulse length (in dotclocks) vsync Vertical sync pulse length (in dotclocks) left Left margin (in dotclocks) right Right margin (in dotclocks) upper Upper margin (in scanlines) lower Lower margin (in scanlines) We can use this information to calculate another important attribute of a video mode: its refresh rate. A high-quality video mode should refresh at least 75 times per second to reduce eye strain. Let’s calculate the refresh rate of the video mode illustrated in Figure 8–1, which has a dotclock frequency of 25.175 MHz (an arbitrary value particular to this mode; these timings are from a standard video mode database). If there are 25.175 million dotclocks per second, each dotclock lasts for approximately 39,722 picoseconds (one picosecond is 10 −12 seconds). Each complete scanline (including both margins and the sync pulse), then, takes (48 + 640 + 16 + 96) × 39, 722 = 31, 777, 600 picoseconds to draw. The entire refresh consists of 33 + 480 + 10 scanlines plus 2 scanlines for the vertical sync pulse, which comes to a total of 525 scanlines. If each scanline lasts for 31,777,600 picoseconds, the entire process takes about 0.01668 seconds to complete, for a refresh rate of 59.9 Hz (usually rounded up to 60 Hz in documentation). This is not a very good refresh rate, but it’s acceptable for some applications. 330 CHAPTER 8 The Mode Database If the current framebuffer device is capable of mode switching and you know the exact timings for the mode you’d like to set, changing video modes is a matter of a single ioctl call. You’ll see that in the next example. But finding the right timings can be a bit of a hassle. As I already mentioned, the framebuffer device system (more specifically the fbset utility) keeps a database of valid mode timings in the file /etc/fb.modes. Although the structure of this file leaves a bit to be desired from a parser-writing point of view, it’s easy enough to figure out. fb.modes is a plain text database containing one or more mode sections. Here’s a sample mode section from my copy of fb.modes: mode "640x480-60" # D: 25.175 MHz, H: 31.469 kHz, V: 59.94 Hz geometry 640 480 640 480 8 timings 39722 48 16 33 10 96 2 endmode Lines beginning with a hash (#) are comments, lines beginning with geometry specify the mode’s geometry (physical resolution, virtual resolution, and color depth), and lines beginning with timings specify the mode’s timing parameters. It is the responsibility of the system administrator (or whoever installed the fbset utility) to provide correct modes and timings for the local system. The default mode database distributed with fbset is valid for most video cards, and in all honesty most people don’t bother to change it. It contains reasonable values for most video cards and monitors. fbmodedb.c and fbmodedb.h contain code for parsing /etc/fb.modes. You can find the code in the listings archive; it’s not interesting enough to include here. An Example We’re ready to set a video mode. We’ll use fbmodedb.c to handle the /etc/fb.modes database, and the FBIOPUT VSCREENINFO ioctl to convey our desired mode to the framebuffer driver. Here’s the code: GAMING WITH THE LINUX CONSOLE 331 Code Listing 8–2 (modeswitch.c) /* An example of framebuffer mode switching. */ #include <stdio.h> #include <stdlib.h> #include <fcntl.h> #include <errno.h> #include <string.h> #include <unistd.h> #include <asm/page.h> #include <sys/mman.h> #include <sys/ioctl.h> #include <asm/page.h> #include <linux/fb.h> #include "fbmodedb.h" int main(int argc, char *argv[]) { char *fbname; int fbdev; struct fb_fix_screeninfo fixed_info; struct fb_var_screeninfo var_info, old_var_info; FbModeline *modelist; FbModeline *selected; u_int8_t pixel_r, pixel_g, pixel_b; int x, y; u_int32_t pixel_value; void *framebuffer; int framebuffer_size; int ppc_fix; /* Let the user specify an alternate framebuffer device on the command line. Default to /dev/fb0. */ if (argc >= 2) fbname = argv[1]; else fbname = "/dev/fb0"; printf("Using framebuffer device %s.\n", fbname); 332 CHAPTER 8 /* Open the framebuffer device. */ fbdev = open(fbname, O_RDWR); if (fbdev < 0) { printf("Error opening %s.\n", fbname); return 1; } /* Get the variable screen info. */ if (ioctl(fbdev, FBIOGET_VSCREENINFO, &var_info) < 0) { printf("Unable to retrieve variable screen info: %s\n", strerror(errno)); close(fbdev); return 1; } /* Back up this info so we can restore it later. */ old_var_info = var_info; /* Load the modes database. */ modelist = FB_ParseModeDB("/etc/fb.modes"); if (modelist == NULL) { printf("Unable to load /etc/fb.modes.\n"); close(fbdev); return 1; } /* Switch into a 640x480 mode. Take the first one we find. */ selected = modelist; while (selected != NULL) { if (selected->xres == 640 && selected->yres == 480) break; selected = selected->next; } if (selected == NULL) { printf("No 640x480 modes found in /etc/fb.modes.\n"); FB_FreeModeDB(modelist); close(fbdev); return 1; } GAMING WITH THE LINUX CONSOLE 333 /* Copy the timing data into the variable info structure. */ var_info.xres = selected->xres; var_info.yres = selected->yres; var_info.xres_virtual = var_info.xres; var_info.yres_virtual = var_info.yres; var_info.pixclock = selected->dotclock; var_info.left_margin = selected->left; var_info.right_margin = selected->right; var_info.upper_margin = selected->upper; var_info.lower_margin = selected->lower; var_info.hsync_len = selected->hslen; var_info.vsync_len = selected->vslen; /* Ask for 16bpp. */ var_info.bits_per_pixel = 16; /* This is a bitmask of sync flags. */ var_info.sync = selected->hsync * FB_SYNC_HOR_HIGH_ACT + selected->vsync * FB_SYNC_VERT_HIGH_ACT + selected->csync * FB_SYNC_COMP_HIGH_ACT + selected->extsync * FB_SYNC_EXT; /* This is a bitmask of mode attributes. */ var_info.vmode = selected->laced * FB_VMODE_INTERLACED + selected->doublescan * FB_VMODE_DOUBLE; var_info.activate = FB_ACTIVATE_NOW; /* Set the mode with an ioctl. It may not accept the exact parameters we provide, in which case it will edit the structure. If our selection is completely unacceptable, the ioctl will fail. */ if (ioctl(fbdev, FBIOPUT_VSCREENINFO, &var_info) < 0) { printf("Unable to set variable screen info: %s\n", strerror(errno)); close(fbdev); return 1; } printf("Mode switch ioctl succeeded.\n"); printf("Got resolution %ix%i @ %ibpp.\n", var_info.xres, 334 CHAPTER 8 var_info.yres, var_info.bits_per_pixel); /* Retrieve the fixed screen info. */ if (ioctl(fbdev, FBIOGET_FSCREENINFO, &fixed_info) < 0) { printf("Unable to retrieve fixed screen info: %s\n", strerror(errno)); close(fbdev); return 1; } /* Now memory-map the framebuffer. According to the SDL source code, it’s necessary to compensate for a buggy mmap implementation on the PowerPC. This should not be a problem for other architectures. (This fix is lifted from SDL_fbvideo.c) */ ppc_fix = (((long)fixed_info.smem_start) - ((long) fixed_info.smem_start & ~(PAGE_SIZE-1))); framebuffer_size = fixed_info.smem_len + ppc_fix; framebuffer = mmap(NULL, framebuffer_size, PROT_READ | PROT_WRITE, MAP_SHARED, fbdev, 0); if (framebuffer == NULL) { printf("Unable to mmap framebuffer: %s\n", strerror(errno)); close(fbdev); return 1; } printf("Mapped framebuffer.\n"); if ((fixed_info.visual == FB_VISUAL_TRUECOLOR) && (var_info.bits_per_pixel == 16)) { /* White pixel. */ pixel_r = 0xFF; pixel_g = 0xFF; pixel_b = 0xFF; GAMING WITH THE LINUX CONSOLE 335 /* We used this same pixel-packing technique back when we were working with SDL. */ pixel_value = (((pixel_r >> (8-var_info.red.length)) << var_info.red.offset) + ((pixel_g >> (8-var_info.green.length)) << var_info.green.offset) + ((pixel_b >> (8-var_info.blue.length)) << var_info.blue.offset)); /* Draw a pixel in the center of the screen. */ x = var_info.xres / 2 + var_info.xoffset; y = var_info.yres / 2 + var_info.yoffset; *((u_int16_t *)framebuffer + fixed_info.line_length/2 * y + x) = (u_int16_t)pixel_value; } else { printf("Unsupported visual. (Asked for 16bpp.)\n"); pixel_value = 0; } /* Wait a few seconds. */ sleep(5); /* Restore the old video mode. */ old_var_info.activate = FB_ACTIVATE_NOW; if (ioctl(fbdev, FBIOPUT_VSCREENINFO, &old_var_info) < 0) { printf("Warning: Unable to restore video mode: %s\n", strerror(errno)); } /* Close the fbdev. */ munmap(framebuffer, framebuffer_size); close(fbdev); return 0; } We start off by opening the framebuffer device and reading information about the display, just as we did in Listing 8–1. Instead of trying to adapt to the 336 CHAPTER 8 existing video mode, however, we try to force a 640x480, 16-bit mode based on the mode data in /etc/fb.modes. The routines in fbmodedb.c make this easy. 1 FB ParseModeDB takes the name of a framebuffer mode database file (almost always /etc/fb.modes) and returns a linked list of FbModeline mode structures (defined in fbmodedb.h). To set one of these modes, we simply have to copy the FbModeline values into a fb var screeninfo structure and call an appropriate ioctl. If the ioctl succeeds, we’re in business. If it fails, we might have better luck with a different mode. In this program, though, we just exit on failure. After we plot a pixel and wait a few seconds for the user to observe our masterpiece, we restore the old mode by calling the FBIOPUT VSCREENINFO ioctl on the original fb var screeninfo structure (with the FB ACTIVATE NOW flag set). This should never fail, since those values were reported by the driver in the first place, and they are known to be valid. Note that this program is much simpler now that we support only 16-bit Hicolor drawing. Writing cross-mode video code is tedious and time-consuming. However, this restriction means that the user must have a framebuffer capable of switching to a 16-bit mode; VESA framebuffers cannot switch modes at all. You should compile this program together with fbmodedb.c: $ gcc -W -Wall -pedantic fbmodedb.c modeswitch.c -o modeswitch This whole process feels a bit “messy,” and truth be told, it has a rather high failure rate. Another approach to mode switching is to shell to the fbset program (with popen), passing the desired mode on the command line. For instance, popen("fbset -g 1024 768 1024 768 16", "r") would attempt to set a 1024x768, 16-bit video mode. Be sure to close popen pipes with pclose. The return code of the program is reported by pclose. The only other problem is how to restore the original video mode when you’re finished. One possible option is to invoke the fbset utility to enter a video mode 1 Permission is granted to use fbmodedb.c and fbmodedb.h in any program, so long as credit is given in the source code. I originally wrote it for inclusion in SDL. [...]... K_ALT) = "Left Alt"; K_ALTGR) = "Right Alt"; } /* Manually plug in keys that the kernel doesn’t normally map correctly */ keynames[ 29] = "Left control"; keynames [97 ] = "Right control"; keynames[125] = "Left Linux key"; /* usually mislabelled */ keynames[126] = "Right Linux key"; /* with a Windows(tm) logo */ keynames[127] = "Application key"; } The main addition to this program is the init keymaps... /* Checks whether or not the given file descriptor is associated with a local keyboard Returns 1 if it is, 0 if not (or if something prevented us from checking) */ int is_keyboard(int fd) { int data; /* See if the keyboard driver groks this file descriptor */ data = 0; if (ioctl(fd, KDGKBTYPE, &data) != 0) return 0; GAMING WITH THE LINUX CONSOLE /* In... enters the blocking read GAMING WITH THE LINUX CONSOLE 3 49 In case this simple event interface doesn’t fit your application’s needs, GPM also provides a high-level callback interface based on regions of interest (ROIs) This interface is primarily intended for adding mouse support to existing character mode applications, and it’s not particularly relevant to game programming The mechanism is discussed... immediately apparent I’m not here to preach about the virtues of free software, though, so I’ll leave it at that This concludes our discussion of framebuffer console programming If you survived this chapter, you now know how to write games for the Linux framebuffer console, complete with fast video, keyboard, and mouse processing I still recommend SDL for most things, but it’s fun to do things at a lower level... us of both presses and releases and saves us from having to interpret scancode sequences (which are a bit obtuse) If you’re trying to port an old DOS game to Linux, raw mode might be a better bet (since it’ll give you the same scancodes as these games used to read from the hardware directly) To put the keyboard in mediumraw mode, we first need its file descriptor This is actually the file descriptor of...GAMING WITH THE LINUX CONSOLE 337 and to use a backed-up copy of the fb var screeninfo structure to restore the original mode later on Use the Source, Luke! The Linux framebuffer interface is not very well documented In order to gain a clear enough understanding of the API to write this section... { keymap[keycode] = KVAL(entry.kb_value); keynames[keycode] = "(letter)"; } /* Since the arrow keys are useful in games, we’ll pick them out of the swarm While we’re at it, we’ll grab Enter, Ctrl, and Alt */ if (entry.kb_value == K_LEFT) keynames[keycode] = "Left arrow"; GAMING WITH THE LINUX CONSOLE if (entry.kb_value == keynames[keycode] if (entry.kb_value == keynames[keycode] if (entry.kb_value ==... rather enjoyable; blazing a trail through uncharted and undocumented territory is what programming is all about The best framebuffer reference I came across is the SDL source code Its framebuffer-handling routines are well written, widely tested, and fairly comprehensive Although it certainly is possible to pick up framebuffer programming by studying the fb.h header and the small amount of documentation included... them as a single keysym, even though they have different keycodes), so we handle these and other oddball keys by hand Hopefully, you’ve now seen enough of the Linux console keyboard interface to implement keyboard input for framebuffer console games As a closing remark, I’ll suggest that locking up the user’s keyboard is a very bad idea, and so it would probably be wise to install signal handlers and... sufficient for getting your feet wet with fbcon game programming, but more advanced projects usually require mouse input as well There are two options: implement a complete mouse driver yourself (reading and processing data packets for multiple types of mice) or require the user to run the GPM console mouse server I recommend the latter—it comes with nearly every Linux distribution, and it supports almost every . fb.modes: mode "640x480-60" # D: 25.175 MHz, H: 31.4 69 kHz, V: 59. 94 Hz geometry 640 480 640 480 8 timings 397 22 48 16 33 10 96 2 endmode Lines beginning with a hash (#) are comments, lines. for approximately 39, 722 picoseconds (one picosecond is 10 −12 seconds). Each complete scanline (including both margins and the sync pulse), then, takes (48 + 640 + 16 + 96 ) × 39, 722 = 31, 777,. picoseconds, the entire process takes about 0.01668 seconds to complete, for a refresh rate of 59. 9 Hz (usually rounded up to 60 Hz in documentation). This is not a very good refresh rate, but

Ngày đăng: 06/08/2014, 09:20

Từ khóa liên quan

Tài liệu cùng người dùng

  • Đang cập nhật ...

Tài liệu liên quan