473 lines
21 KiB
Plaintext
473 lines
21 KiB
Plaintext
|
|
HACKING ON THE GNUBOY SOURCE TREE
|
|
|
|
|
|
BASIC INFO
|
|
|
|
In preparation for the first release, I'm putting together a simple
|
|
document to aid anyone interested in playing around with or improving
|
|
the gnuboy source. First of all, before working on anything, you
|
|
should know my policies as maintainer. I'm happy to accept contributed
|
|
code, but there are a few guidelines:
|
|
|
|
* Obviously, all code must be able to be distributed under the GNU
|
|
GPL. This means that your terms of use for the code must be equivalent
|
|
to or weaker than those of the GPL. Public domain and MIT-style
|
|
licenses are perfectly fine for new code that doesn't incorporate
|
|
existing parts of gnuboy, e.g. libraries, but anything derived from or
|
|
built upon the GPL'd code can only be distributed under GPL. When in
|
|
doubt, read COPYING.
|
|
|
|
* Please stick to a coding and naming convention similar to the
|
|
existing code. I can reformat contributions if I need to when
|
|
integrating them, but it makes it much easier if that's already done
|
|
by the coder. In particular, indentions are a single tab (char 9), and
|
|
all symbols are all lowercase, except for macros which are all
|
|
uppercase.
|
|
|
|
* All code must be completely deterministic and consistent across all
|
|
platforms. this results in the two following rules...
|
|
|
|
* No floating point code whatsoever. Use fixed point or better yet
|
|
exact analytical integer methods as opposed to any approximation.
|
|
|
|
* No threads. Emulation with threads is a poor approximation if done
|
|
sloppily, and it's slow anyway even if done right since things must be
|
|
kept synchronous. Also, threads are not portable. Just say no to
|
|
threads.
|
|
|
|
* All non-portable code belongs in the sys/ or asm/ trees. #ifdef
|
|
should be avoided except for general conditionally-compiled code, as
|
|
opposed to little special cases for one particular cpu or operating
|
|
system. (i.e. #ifdef USE_ASM is ok, #ifdef __i386__ is NOT!)
|
|
|
|
* That goes for *nix code too. gnuboy is written in ANSI C, and I'm
|
|
not going to go adding K&R function declarations or #ifdef's to make
|
|
sure the standard library is functional. If your system is THAT
|
|
broken, fix the system, don't "fix" the emulator.
|
|
|
|
* Please no feature-creep. If something can be done through an
|
|
external utility or front-end, or through clever use of the rc
|
|
subsystem, don't add extra code to the main program.
|
|
|
|
* On that note, the modules in the sys/ tree serve the singular
|
|
purpose of implementing calls necessary to get input and display
|
|
graphics (and eventually sound). Unlike in poorly-designed emulators,
|
|
they are not there to give every different target platform its own gui
|
|
and different set of key bindings.
|
|
|
|
* Furthermore, the main loop is not in the platform-specific code, and
|
|
it will never be. Windows people, put your code that would normally go
|
|
in a message loop in ev_refresh and/or sys_sleep!
|
|
|
|
* Commented code is welcome but not required.
|
|
|
|
* I prefer asm in AT&T syntax (the style used by *nix assemblers and
|
|
likewise DJGPP) as opposed to Intel/NASM/etc style. If you really must
|
|
use a different style, I can convert it, but I don't want to add extra
|
|
dependencies on nonstandard assemblers to the build process. Also,
|
|
portable C versions of all code should be available.
|
|
|
|
* Have fun with it. If my demands stifle your creativity, feel free to
|
|
fork your own projects. I can always adapt and merge code later if
|
|
your rogue ideas are good enough. :)
|
|
|
|
OK, enough of that. Now for the fun part...
|
|
|
|
|
|
THE SOURCE TREE STRUCTURE
|
|
|
|
[documentation]
|
|
README - general information related to using gnuboy
|
|
INSTALL - compiling and installation instructions
|
|
HACKING - this file, obviously
|
|
COPYING - the gnu gpl, grants freedom under condition of preseving it
|
|
|
|
[build files]
|
|
Version - doubles as a C and makefile include, identifies version number
|
|
Rules - generic build rules to be included by makefiles
|
|
Makefile.* - system-specific makefiles
|
|
configure* - script for generating *nix makefiles
|
|
|
|
[non-portable code]
|
|
sys/*/* - hardware and software platform-specific code
|
|
asm/*/* - optimized asm versions of some code, not used yet
|
|
asm/*/asm.h - header specifying which functions are replaced by asm
|
|
asm/i386/asmnames.h - #defines to fix _ prefix brain damage on DOS/Windows
|
|
|
|
[main emulator stuff]
|
|
main.c - entry point, event handler...basically a mess
|
|
loader.c - handles file io for rom and ram
|
|
emu.c - another mess, basically the frame loop that calls state.c
|
|
debug.c - currently just cpu trace, eventually interactive debugging
|
|
hw.c - interrupt generation, gamepad state, dma, etc.
|
|
mem.c - memory mapper, read and write operations
|
|
fastmem.h - short static functions that will inline for fast memory io
|
|
regs.h - macros for accessing hardware registers
|
|
save.c - savestate handling
|
|
|
|
[cpu subsystem]
|
|
cpu.c - main cpu emulation
|
|
cpuregs.h - macros for cpu registers and flags
|
|
cpucore.h - data tables for cpu emulation
|
|
asm/i386/cpu.s - entire cpu core, rewritten in asm
|
|
|
|
[graphics subsystem]
|
|
fb.h - abstract framebuffer definition, extern from platform-specifics
|
|
lcd.c - main control of refresh procedure
|
|
lcd.h - vram, palette, and internal structures for refresh
|
|
asm/i386/lcd.s - asm versions of a few critical functions
|
|
lcdc.c - lcdc phase transitioning
|
|
|
|
[input subsystem]
|
|
input.h - internal keycode definitions, etc.
|
|
keytables.c - translations between key names and internal keycodes
|
|
events.c - event queue
|
|
|
|
[resource/config subsystem]
|
|
rc.h - structure defs
|
|
rccmds.c - command parser/processor
|
|
rcvars.c - variable exports and command to set rcvars
|
|
rckeys.c - keybindingds
|
|
|
|
[misc code]
|
|
path.c - path searching
|
|
split.c - general purpose code to split strings into argv-style arrays
|
|
|
|
|
|
OVERVIEW OF PROGRAM FLOW
|
|
|
|
The initial entry point main() main.c, which will process the command
|
|
line, call the system/video initialization routines, load the
|
|
rom/sram, and pass control to the main loop in emu.c. Note that the
|
|
system-specific main() hook has been removed since it is not needed.
|
|
|
|
There have been significant changes to gnuboy's main loop since the
|
|
original 0.8.0 release. The former state.c is no more, and the new
|
|
code that takes its place, in lcdc.c, is now called from the cpu loop,
|
|
which although slightly unfortunate for performance reasons, is
|
|
necessary to handle some strange special cases.
|
|
|
|
Still, unlike some emulators, gnuboy's main loop is not the cpu
|
|
emulation loop. Instead, a main loop in emu.c which handles video
|
|
refresh, polling events, sleeping between frames, etc. calls
|
|
cpu_emulate passing it an idea number of cycles to run. The actual
|
|
number of cycles for which the cpu runs will vary slightly depending
|
|
on the length of the final instruction processed, but it should never
|
|
be more than 8 or 9 beyond the ideal cycle count passed, and the
|
|
actual number will be returned to the calling function in case it
|
|
needs this information. The cpu code now takes care of all timer and
|
|
lcdc events in its main loop, so the caller no longer needs to be
|
|
aware of such things.
|
|
|
|
Note that all cycle counts are measured in CGB double speed MACHINE
|
|
cycles (2**21 Hz), NOT hardware clock cycles (2**23 Hz). This is
|
|
necessary because the cpu speed can be switched between single and
|
|
double speed during a single call to cpu_emulate. When running in
|
|
single speed or DMG mode, all instruction lengths are doubled.
|
|
|
|
As for the LCDC state, things are much simpler now. No more huge
|
|
glorious state table, no more P/Q/R, just a couple simple functions.
|
|
Aside from the number of cycles left before the next state change, all
|
|
the state information fits nicely in the locations the Game Boy itself
|
|
provides for it -- the LCDC, STAT, and LY registers.
|
|
|
|
If the special cases for the last line of VBLANK look strange to you,
|
|
good. There's some weird stuff going on here. According to documents
|
|
I've found, LY changes from 153 to 0 early in the last line, then
|
|
remains at 0 until the end of the first visible scanline. I don't
|
|
recall finding any roms that rely on this behavior, but I implemented
|
|
it anyway.
|
|
|
|
That covers the basics. As for flow of execution, here's a simplified
|
|
call tree that covers most of the significant function calls taking
|
|
place in normal operation:
|
|
|
|
main sys/
|
|
\_ real_main main.c
|
|
|_ sys_init sys/
|
|
|_ vid_init sys/
|
|
|_ loader_init loader.c
|
|
|_ emu_reset emu.c
|
|
\_ emu_run emu.c
|
|
|_ cpu_emulate cpu.c
|
|
| |_ div_advance cpu.c *
|
|
| |_ timer_advance cpu.c *
|
|
| |_ lcdc_advance cpu.c *
|
|
| | \_ lcdc_trans lcdc.c
|
|
| | |_ lcd_refreshline lcd.c
|
|
| | |_ stat_change lcdc.c
|
|
| | | \_ lcd_begin lcd.c
|
|
| | \_ stat_trigger lcdc.c
|
|
| \_ sound_advance cpu.c *
|
|
|_ vid_end sys/
|
|
|_ sys_elapsed sys/
|
|
|_ sys_sleep sys/
|
|
|_ vid_begin sys/
|
|
\_ doevents main.c
|
|
|
|
(* included in cpu.c so they can inline; also in cpu.s)
|
|
|
|
|
|
MEMORY READ/WRITE MAP
|
|
|
|
Whenever possible, gnuboy avoids emulating memory reads and writes
|
|
with a function call. To this end, two pointer tables are kept -- one
|
|
for reading, the other for writing. They are indexed by bits 12-15 of
|
|
the address in Game Boy memory space, and yield a base pointer from
|
|
which the whole address can be used as an offset to access Game Boy
|
|
memory with no function calls whatsoever. For regions that cannot be
|
|
accessed without function calls, the pointer in the table is NULL.
|
|
|
|
For example, reading from address addr can be accomplished by testing
|
|
to make sure mbc.rmap[addr>>12] is not NULL, then simply reading
|
|
mbc.rmap[addr>>12][addr].
|
|
|
|
And for the disbelievers in this optimization, here are some numbers
|
|
to compare. First, FFL2 with memory tables disabled:
|
|
|
|
% cumulative self self total
|
|
time seconds seconds calls us/call us/call name
|
|
28.69 0.57 0.57 refresh_2
|
|
13.17 0.84 0.26 4307863 0.06 0.06 mem_read
|
|
11.63 1.07 0.23 cpu_emulate
|
|
|
|
Now, with memory tables enabled:
|
|
|
|
38.86 0.66 0.66 refresh_2
|
|
8.42 0.80 0.14 156380 0.91 0.91 spr_enum
|
|
6.76 0.91 0.11 483134 0.24 1.31 lcdc_trans
|
|
6.16 1.02 0.10 cpu_emulate
|
|
.
|
|
.
|
|
.
|
|
0.59 1.61 0.01 216497 0.05 0.05 mem_read
|
|
|
|
As you can see, not only does mem_read take up (proportionally) 1/20
|
|
as much time, since it is rarely called, but the main cpu loop in
|
|
cpu_emulate also runs considerably faster with all the function call
|
|
overhead and cache misses avoided.
|
|
|
|
These tests were performed on K6-2/450 with the assembly cores
|
|
enabled; your milage may vary. Regardless, however, I think it's clear
|
|
that using the address mapping tables is quite a worthwhile
|
|
optimization.
|
|
|
|
|
|
LCD RENDERING CORE DESIGN
|
|
|
|
The LCD core presently used in gnuboy is very much a high-level one,
|
|
performing the task of rasterizing scanlines as many independent steps
|
|
rather than one big loop, as is often seen in other emulators and the
|
|
original gnuboy LCD core. In some ways, this is a bit of a tradeoff --
|
|
there's a good deal of overhead in rebuilding the tile pattern cache
|
|
for roms that change their tile patterns frequently, such as full
|
|
motion video demos. Even still, I consider the method we're presently
|
|
using far superior to generating the output display directly from the
|
|
gameboy tiledata -- in the vast majority of roms, tiles are changed so
|
|
infrequently that the overhead is irrelevant. Even if the tiles are
|
|
changed rapidly, the only chance for overhead beyond what would be
|
|
present in a monolithic rendering loop lies in (host cpu) cache misses
|
|
and the possibility that we might (tile pattern) cache a tile that has
|
|
changed but that will never actually be used, or that will only be
|
|
used in one orientation (horizontally and vertically flipped versions
|
|
of all tiles are cached as well). Such tile caching issues could be
|
|
addressed in the long term if they cause a problem, but I don't see it
|
|
hurting performance too significantly at the present. As for host cpu
|
|
cache miss issues, I find that putting multiple data decoding and
|
|
rendering steps together in a single loop harms performance much more
|
|
significantly than building a 256k (pattern) cache table, on account
|
|
of interfering with branch prediction, register allocation, and so on.
|
|
|
|
Well, with those justifications given, let's proceed to the steps
|
|
involved in rendering a scanline:
|
|
|
|
updatepatpix() - updates tile pattern cache.
|
|
|
|
tilebuf() - reads gb tile memory according to its complicated tile
|
|
addressing system which can be changed via the LCDC register, and
|
|
outputs nice linear arrays of the actual tile indices used in the
|
|
background and window on the present line.
|
|
|
|
Before continuing, let me explain the output format used by the
|
|
following functions. There is a byte array scan.buf, accessible by
|
|
macro as BUF, which is the output buffer for the line. The structure
|
|
of this array is simple: it is composed of 6 bpp gameboy color
|
|
numbers, where the bits 0-1 are the color number from the tile, bits
|
|
2-4 are the (cgb or dmg) palette index, and bit 5 is 0 for background
|
|
or window, 1 for sprite.
|
|
|
|
What is the justification for using a strange format like this, rather
|
|
than raw host color numbers for output? Well, believe it or not, it
|
|
improves performance. It's already necessary to have the gameboy color
|
|
numbers available for use in sprite priority. And, when running in
|
|
mono gb mode, building this output data is VERY fast -- it's just a
|
|
matter of doing 64 bit copies from the tile pattern cache to the
|
|
output buffer.
|
|
|
|
Furthermore, using a unified output format like this eliminates the
|
|
need to have separate rendering functions for each host color depth or
|
|
mode. We just call a one-line function to apply a palette to the
|
|
output buffer as we copy it to the video display, and we're done. And,
|
|
if you're not convinced about performance, just do some profiling.
|
|
You'll see that the vast majority of the graphics time is spent in the
|
|
one-line copy function (render_[124] depending on bytes per pixel),
|
|
even when using the fast asm versions of those routines. That is to
|
|
say, any overhead in the following functions is for all intents and
|
|
purposes irrelevant to performance. With that said, here they are:
|
|
|
|
bg_scan() - expands the background layer to the output buffer.
|
|
|
|
wnd_scan() - expands the window layer.
|
|
|
|
spr_scan() - expands the sprites. Note that this requires spr_enum()
|
|
to have been called already to build a list of which sprites are
|
|
visible on the current scanline and sort them by priority.
|
|
|
|
It should be noted that the background and window functions also have
|
|
color counterparts, which are considerably slower due to merging of
|
|
palette data. At this point, they're staying down around 8% time
|
|
according to the profiler, so I don't see a major need to rewrite them
|
|
anytime soon. It should be considered, however, that a different
|
|
intermediate format could be used for gbc, or that asm versions of
|
|
these two routines could be written, in the long term.
|
|
|
|
Finally, some notes on palettes. You may be wondering why the 6 bpp
|
|
intermediate output can't be used directly on 256-color display
|
|
targets. After all, that would give a huge performance boost. The
|
|
problem, however, is that the gameboy palette can change midscreen,
|
|
whereas none of the presently targetted host systems can handle such a
|
|
thing, much less do it portably. For color roms, using our own
|
|
internal color mappings in addition to the host system palette is
|
|
essential. For details on how this is accomplished, read palette.c.
|
|
|
|
Now, in the long term, it MAY be possible to use the 6 bpp color
|
|
"almost" directly for mono roms. Note that I say almost. The idea is
|
|
this. Using the color number as an index into a table is slow. It
|
|
takes an extra read and causes various pipeline stalls depending on
|
|
the host cpu architecture. But, since there are relatively few
|
|
possible mono palettes, it may actually be possible to set up the host
|
|
palette in a clever way so as to cover all the possibilities, then use
|
|
some fancy arithmetic or bit-twiddling to convert without a lookup
|
|
table -- and this could presumably be done 4 pixels at a time with
|
|
32bit operations. This area remains to be explored, but if it works,
|
|
it might end up being the last hurdle to getting realtime emulation
|
|
working on very low-end systems like i486.
|
|
|
|
|
|
SOUND
|
|
|
|
Rather than processing sound after every few instructions (and thus
|
|
killing the cache coherency), we update sound in big chunks. Yet this
|
|
in no way affects precise sound timing, because sound_mix is always
|
|
called before reading or writing a sound register, and at the end of
|
|
each frame.
|
|
|
|
The main sound module interfaces with the system-specific code through
|
|
one structure, pcm, and a few functions: pcm_init, pcm_close, and
|
|
pcm_submit. While the first two should be obvious, pcm_submit needs
|
|
some explaining. Whenever realtime sound output is operational,
|
|
pcm_submit is responsible for timing, and should not return until it
|
|
has successfully processed all the data in its input buffer (pcm.buf).
|
|
On *nix sound devices, this typically means just waiting for the write
|
|
syscall to return, but on systems such as DOS where low level IO must
|
|
be handled in the program, pcm_submit needs to delay until the current
|
|
position in the DMA buffer has advanced sufficiently to make space for
|
|
the new samples, then copy them.
|
|
|
|
For special sound output implementations like write-to-file or the
|
|
dummy sound device, pcm_submit should write the data immediately and
|
|
return 0, indicating to the caller that other methods must be used for
|
|
timing. On real sound devices that are presently functional,
|
|
pcm_submit should return 1, regardless of whether it buffered or
|
|
actually wrote the sound data.
|
|
|
|
And yes, for unices without OSS, we hope to add piped audio output
|
|
soon. Perhaps Sun audio device and a few others as well.
|
|
|
|
|
|
OPTIMIZED ASSEMBLY CODE
|
|
|
|
A lot can be said on this matter. Nothing has been said yet.
|
|
|
|
|
|
INTERACTIVE DEBUGGER
|
|
|
|
Apologies, there is no interactive debugger in gnuboy at present. I'm
|
|
still working out the design for it. In the long run, it should be
|
|
integrated with the rc subsystem, kinda like a cross between gdb and
|
|
Quake's ever-famous console. Whether it will require a terminal device
|
|
or support the graphical display remains to be determined.
|
|
|
|
In the mean time, you can use the debug trace code already
|
|
implemented. Just "set trace 1" from your gnuboy.rc or the command
|
|
line. Read debug.c for info on how to interpret the output, which is
|
|
condensed as much as possible and not quite self-explanatory.
|
|
|
|
|
|
PORTING
|
|
|
|
On all systems on which it is available, the gnu compiler should
|
|
probably be used. Writing code specific to non-free compilers makes it
|
|
impossible for free software users to actively contribute. On the
|
|
other hand, compiler-specific code should always be kept to a minimum,
|
|
to make porting to or from non-gnu compilers easier.
|
|
|
|
Porting to new cpu architectures should not be necessary. Just make
|
|
sure you unset IS_LITTLE_ENDIAN in the makefiles to enable the big
|
|
endian default if the target system is big endian. If you do have
|
|
problems building on certain cpus, however, let us know. Eventually,
|
|
we will also want asm cpu and graphics code for popular host cpus, but
|
|
this can wait, since the c code should be sufficiently fast on most
|
|
platforms.
|
|
|
|
The bulk of porting efforts will probably be spent on adding support
|
|
for new operating systems, and on systems with multiple video (or
|
|
sound, once that's implemented) architectures, new interfaces for
|
|
those. In general, the operating system interface code goes in a
|
|
directory under sys/ named for the os (e.g. sys/nix/ for *nix
|
|
systems), and display interfaces likewise go in their respective
|
|
directories under sys/ (e.g. sys/x11/ for the x window system
|
|
interface).
|
|
|
|
For guidelines in writing new system and display interface modules, i
|
|
recommend reading the files in the sys/dos/, sys/svga/, and sys/nix/
|
|
directories. These are some of the simpler versions (aside from the
|
|
tricky dos keyboard handling), as opposed to all the mess needed for
|
|
x11 support.
|
|
|
|
Also, please be aware that the existing system and display interface
|
|
modules are somewhat primitive; they are designed to be as quick and
|
|
sloppy as possible while still functioning properly. Eventually they
|
|
will be greatly improved.
|
|
|
|
Finally, remember your obligations under the GNU GPL. If you produce
|
|
any binaries that are compiled strictly from the source you received,
|
|
and you intend to release those, you *must* also release the exact
|
|
sources you used to produce those binaries. This is not pseudo-free
|
|
software like Snes9x where binaries usually appear before the latest
|
|
source, and where the source only compiles on one or two platforms;
|
|
this is true free software, and the source to all binaries always
|
|
needs to be available at the same time or sooner than the
|
|
corresponding binaries, if binaries are to be released at all. This of
|
|
course applies to all releases, not just new ports, but from
|
|
experience i find that ports people usually need the most reminding.
|
|
|
|
|
|
EPILOGUE
|
|
|
|
That's it for now. More info will eventually follow. Happy hacking!
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|