|
GBDK 2020 Docs
4.0.6
API Documentation for GBDK 2020
|
Writing games and other programs with GBDK will be much easier with a basic understanding of the C language. In particular, understanding how to use C on "Embedded Platforms" (small computing systems, such as the Game Boy) can help you write better code (smaller, faster, less error prone) and avoid common pitfals.
In addition to understanding the C language it's important to learn how the Game Boy hardware works. What it is capable of doing, what it isn't able to do, and what resources are available to work with. A good way to do this is by reading the Pandocs and checking out the awesome_gb list.
The following guidelines can result in better code for the Game Boy, even though some of the guidance may be contrary to typical advice for general purpose computers that have more resources and speed.
Important: The old GBTD/GBMB fails to include the const keyword when exporting to C source files for GBDK. That causes arrays to be created in RAM instead of ROM, which wastes RAM, uses a lot of ROM to initialize the RAM arrays and slows the compiler down a lot.
__Use of toxa's updated GBTD/GBMB is highly recommended.__
If you wish to use the original tools, you must add the const keyword every time the graphics are re-exported to C source files.
int8_t, uint8_t, int16_t, uint16_t, int32_t, uint32_t and bool. These are standard types defined in stdint.h (#include <stdint.h>) and stdbool.h (#include <stdbool.h>).const keyword: Use const for arrays, structs and variables with read-only (constant) data. It will reduce ROM, RAM and CPU usage significantly. Non-const values are loaded from ROM into RAM inefficiently, and there is no benefit in loading them into the limited available RAM if they aren't going to be changed.const pointers and variables:const uint8_t * some_pointer;uint8_t * const some_pointer;const uint8_t * const some_pointer;someStruct->var = x; someStruct++) to loop through arrays of structs instead of using indexing each time in the loop someStruct[i].var = x.__critical { } block. See http://sdcc.sourceforge.net/doc/sdccman.pdf#section.3.9U, L and UL postfixes can be used.U specifies that the constant is unsignedL specifies that the constant is long.--fsigned-char for the old behavior, this option flag is included by default when compiling through lcc.fixed) is included with GBDK when precision greater than whole numbers is required for 8 bit range values (since floating point is not included in GBDK).See the "Simple Physics" sub-pixel example project.
Code example:
fixed player[2]; ... // Modify player position using it's 16 bit representation player[0].w += player_speed_x; player[1].w += player_speed_y; ... // Use only the upper 8 bits for setting the sprite position move_sprite(0, player[0].h ,player[1].h);
#include .c source files into other .c source files. Instead create .h header files for them and include those. https://www.tutorialspoint.com/cprogramming/c_header_files.htmn /= 4u will be optimized to n >>= 2.(n % 8) will be optimized to (n & 0x7).BCD example project included with GBDK.inline keyword, such as inline uint8_t myFunction() { ... })--max-allocs-per-node flag with large values, such as 50000. --opt-code-speed has a much smaller effect.--max-allocs-per-node 50000, but it must be turned on for your own code. lcc ... -Wf--max-allocs-per-node50000 or sdcc ... --max-allocs-per-node 50000).--opt-code-speed or --opt-code-size.In standard C when chars are passed to a function with variadic arguments (varargs, those delcared with ... as a parameter), such as printf(), those chars get automatically promoted to ints. For an 8 bit cpu such as the Game Boy's, this is not as efficient or desireable in most cases. So the default SDCC behavior, which GBDK-2020 expects, is that chars will remain chars and not get promoted to ints when explicitly cast as chars while calling a varargs function.
For example:
unsigned char i = 0x5A;
// NO:
// The char will get promoted to an int, producing incorrect printf output
// The output will be: 5A 00
printf("%hx %hx", i, i);
// YES:
// The char will remain a char and printf output will be as expected
// The output will be: 5A 5A
printf("%hx %hx", (unsigned char)i, (unsigned char)i);
Some functions that accept varargs:
Also See:
For many applications C is fast enough but in intensive functions are sometimes better written in assembler. This section deals with interfacing your core C program with fast assembly sub routines.
sdcc in common with almost all C compilers prepends a '_' to any function names. For example the function printf(...) begins at the label _printf::. Note that all functions are declared global.
The parameters to a function are pushed in right to left order with no aligning - so a byte takes up a byte on the stack instead of the more natural word. So for example the function int store_byte( uint16_t addr, uint8_t byte) would push 'byte' onto the stack first then addr using a total of three bytes. As the return address is also pushed, the stack would contain:
At SP+0 - the return address At SP+2 - addr At SP+4 - byte
Note that the arguments that are pushed first are highest in the stack due to how the Game Boy's stack grows downwards.
The function returns in DE.
C normally expects registers to be preserved across a function call. However in the case above as DE is used as the return value and HL is used for anything, only BC needs to be preserved.
Getting at C variables is slightly tricky due to how local variables are allocated on the stack. However you shouldn't be using the local variables of a calling function in any case. Global variables can be accessed by name by adding an underscore.
The use of segments for code, data and variables is more noticeable in assembler. GBDK and SDCC define a number of default segments - _CODE, _DATA and _BSS. Two extra segments _HEADER and _HEAP exist for the Game Boy header and malloc heap respectively.
The order these segments are linked together is determined by crt0.s and is currently _CODE in ROM, then _DATA, _BSS, _HEAP in WRAM, with STACK at the top of WRAM. _HEAP is placed after _BSS so that all spare memory is available for the malloc routines. To place code in other than the first two banks, use the segments _CODE_x where x is the 16kB bank number.
As the _BSS segment occurs outside the ROM area you can only use .ds to reserve space in it.
While you don't have to use the _CODE and _DATA distinctions in assembler you may wish to do so consistancy.