GBDK 2020 Docs
4.3.0
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 pitfalls.
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.
In general avoid reading from VRAM since that memory is not accessible at all times. If GBDK a API function which reads from VRAM (such as get_bkg_tile_xy()) is called during a video mode when VRAM is not accessible, then that function call will delay until VRAM becomes accessible again. This can cause unnecessary slowdowns when running programs on the Game Boy. It is also not supported by GBDK on the NES platform.
Instead it is better to store things such as map data in general purpose RAM which does not have video mode access limitations.
For more information about video modes and VRAM access see the pan docs:
https://gbdev.io/pandocs/STAT.html#stat-modes
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 its 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() { ... }
).--fsigned-char
(via lcc) for SDCC--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
.There are a some scenarios where the compiler will warn about overflows with constants. They often have to do with mixed signedness between constants and variables. To avoid problems use care about whether or not constants are explicitly defined as unsigned and what type of variables they are used with.
WARNING: overflow in implicit constant conversion
127
. #define TOO_LARGE_CONST 255 int8_t signed_var = TOO_LARGE_CONST;
254
which is less than the 255
, the max value for a unsigned 8 bit char variable. #define CONST_UNSIGNED 127u #define CONST_SIGNED 127 uint8_t unsigned_var = (CONST_SIGNED + CONST_UNSIGNED);
u
when the constant is intended for unsigned operations. #define CONST_UNSIGNED 127u #define CONST_ALSO_UNSIGNED 127u // <-- Added "u", now no warning uint8_t unsigned_var = (CONST_UNSIGNED + CONST_ALSO_UNSIGNED);
Parameters (chars, ints, etc) to printf / sprintf should always be explicitly cast to avoid type related parameter passing issues.
For example, below will result in the likely unintended output:
Instead this will give the intended output:
In standard C when chars
are passed to a function with variadic arguments (varargs, those declared 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 desirable 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:
Some functions that accept varargs:
Also See:
For many applications C is fast enough but in intensive functions are sometimes better written in assembly. This section deals with interfacing your core C program with fast assembly sub routines.
When functions are written assembly it's generally better to not mix the inline ASM with C code and instead write the whole function in assembly.
If they are mixed then descriptive named labels should not be used for inline ASM. This is due to descriptive labels interfering with the expected scope of the reusable local labels generated from the compiled C code. The compiler will not detect this problem and the resulting code may fail to execute correctly without warning.
Instead use reusable local symbols/labels (for example 1$:
). To learn more about them check the SDAS manual section "1.3.3 Reusable Symbols"
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/areas for code, data and variables is more noticeable in assembler. GBDK and SDCC define a number of default ones. The order they are linked is determined by crt0.s and is currently as follows for the Game Boy and related clones.
_HEADER
: For the Game Boy header_CODE
: CODE is specified as after BASE, but is placed before it due to how the linker works._HOME
_BASE
_CODE_0
_INITIALIZER
: Constant data used to init RAM data_LIT
_GSINIT
: Code used to init RAM data_GSFINAL
_CODE_x
Places code in ROM other than Bank 0
, where x is the 16kB bank number._DATA
: Uninitialized RAM data_BSS
_INITIALIZED
: Initialized RAM data_HEAP
: placed after _INITIALIZED
so that all spare memory is available for the malloc routines.STACK
: at the end of WRAMThe following is primarily oriented toward the Game Boy and related clones (sm83 devices), other targets such as sms/gg may vary.
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.
Functions can be marked with OLDCALL
which will cause them to use the __sdcccall(0)
calling convention (the format used prior to SDCC 4.2 & GBDK-2020 4.1.0).
Starting with SDCC 4.2 and GBDK-2020 4.1.0 the new default calling convention is__sdcccall(1)
.
For additional details about the calling convetions, see sections SM83 calling conventions
and Z80, Z180 and Z80N calling conventions
in the SDCC manual.
gbz80
/sm83
generally share this subheading with z80
(Game Boy is partially a sub-port of z80 in SDCC). https://sdcc.sourceforge.net/doc/sdccman.pdf#subsection.4.3.9The following is primarily oriented toward the Game Boy and related clones (sm83 devices), other targets such as sms/gg may vary.
Key Points:
Callee
(to skip the pushed Caller
Bank and additional Trampoline
Return Address)__sdcccall(1)
, or __sdcccall(0)
for OLDCALL
)Terminology:
Caller
: the code which is calling the requested functionCallee
: the function to be called (declared as BANKED
or __banked
)Trampoline
: The intermediary which performs the bank switching and does hand-off between Caller
and Callee
during the call and then return.Banked Call Trampoline
__sdcc_bcall_ehl
trampoline is used by default__sdcccall(1)
(default) or __sdcccall(0)
for OLDCALL
--legacy-banking
is specified to SDCC the __sdcc_bcall
trampoline is used.__sdcccall(0)
Process for a banked call (using __sdcc_bcall_ehl
, the default)
sm83
(GB/AP/DUCK): skip first 4 bytesz80
(GG/SMS/etc): skip first 3 bytes__sdcccall(1)
, or __sdcccall(0)
for OLDCALL
)