Tornado 2.0 ships with a unified Display Manager. At the time we made Brutalism this was not the case, so every effect had to implement its own.
In this section we will take a look at the custom display I wrote for the splash screen shown while the demo is loading.
If you remember from the Displays chapter, every display needs to comply with the API and expose the following functions:
Init
: Allocates resources for the display and performs initialisation.Flip
: Kicks off the copy to Chip RAM and swaps the double buffer active instance.Forefront
: Prepares the Chip RAM areas that contain the planar data, copies the sprites to Chip RAM and sets up the Copper list pointers.Get
: Returns a chunky buffer that your effect can write to.
The splash display extends the API and adds a few more functions. The AmigaOS dependent parts of the display are implemented in the amiga/splash.c
file in the Tornado source directory, and deal with opening screens and allocating buffers as well as sound replay.
Let's take a look now at the display implementation in the splash
example:
This is just a very thin layer on top of the amiga/splash.c
code:
We have a non-standard Init
function here because this display also does asset management.
void display_init_splash(const char *const *splashFiles, int numFiles,
int sizeX, int sizeY, int offset, int depth,
int tornadoOptions) {
_chunky = splash_init(splashFiles, numFiles, sizeX, sizeY, offset, depth,
tornadoOptions);
if (!_chunky) {
fprintf(stderr, "FATAL - Cannot initialise splash screen. Aborting.\n");
exit(EXIT_FAILURE);
}
_fb.w = sizeX;
_fb.h = sizeY;
_fb.p.pix8 = _chunky;
}
The Get
function returns a pointer to the chanvas that contains the chuky buffer information:
t_canvas *display_get_splash() { return &_fb; }
void display_waitvbl_splash(int num_frames) {
for (int i = 0; i < num_frames; i++) {
splash_wait_vbl();
}
}
Forefront
calls the underlying hardware-dependent implementation. We also have a helper
function to set the palette and calculate where the palette ends in a file converted with png2raw
.
void display_forefront_splash() { splash_forefront(); }
void display_set_palette_splash(void *palette) { splash_set_palette(palette); }
int display_get_palette_skip_splash(void *pal) {
int *source = pal;
return (((ENDI4(source[0]) >> 16) * 3 * sizeof(int)) + (2 * sizeof(int)));
}
Last but not least we have two wrappers around the Show
and Swap Screens
functions and the End
call, which fades out the image and the music and frees up the resources.
void display_show_splash() { splash_show(); }
void display_flip_splash() { splash_swap_screens(); }
void display_end_splash(uint32_t *pal) {
splash_fadeout(pal, 50);
splash_end();
}
The SDL/Posix implementatin is self contained in the sdl_posix/display_splash.c
file in the splash
example.
The set_palette
function converts a palette array in LoadRGB32 format to something we can use in our SDL target, also taking care of the endianness.
void display_set_palette_splash(void *pal) {
uint32_t *source = (uint32_t *)pal;
int numCols;
int startCols;
numCols = (ENDI4(source[0])) >> 16;
int k = 0;
source++;
for (int i = 0; i < numCols; i++) {
unsigned int r = source[k++];
unsigned int g = source[k++];
unsigned int b = source[k++];
_pal256_splash[i] = ((ENDI4(r)) & 0xff000000) >> 8;
_pal256_splash[i] |= ((ENDI4(g)) & 0xff000000) >> 16;
_pal256_splash[i] |= ((ENDI4(b)) & 0xff000000) >> 24;
}
}
The Init
function loads the associated assets and initialises the SDL window.
void display_init_splash(const char *const *splashFiles, int numFiles,
int sizeX, int sizeY, int offset, int depth,
int tornadoOptions) {
_fb_splash.w = sizeX;
_fb_splash.h = sizeY;
_fb_splash.bypp = 1;
_fb_splash.p.pixels =
tndo_malloc(_fb_splash.w * _fb_splash.h * _fb_splash.bypp, 0);
if (numFiles > 0) {
splashAssets = (void **)tndo_malloc(sizeof(void *) * numFiles, 0);
splashSizes = (int *)tndo_malloc(sizeof(int) * numFiles, 0);
if (!loadAssets(&splashAssets[0], &splashFiles[0], &splashSizes[0],
numFiles, tornadoOptions, 0)) {
fprintf(stderr, "Failed to load splash asserts. Aborting");
abort();
}
}
if (sizeX > 320 || sizeY > 180) {
splashHires = 1;
}
if (numFiles > 0) {
display_set_palette_splash(splashAssets[0]);
}
dev_window_output_init(flip_delay, 0);
}
WaitVBL
, Forefront
and End
are stubs and only exist to satisfy the API.
void display_waitvbl_splash(int num_frames) {}
void display_forefront_splash() {}
void display_end_splash(uint32_t *pal) {}
Because the raw files pack the palette and the pixel data together, we use this function to know where the palette ends.
int display_get_palette_skip_splash(void *pal) {
uint32_t *source = (uint32_t *)pal;
int numCols = (ENDI4(source[0])) >> 16;
return ((numCols * 3 * sizeof(uint32_t)) + (2 * sizeof(uint32_t)));
}
As previously, we just return a pointer to the canvas struct.
t_canvas *display_get_splash() { return &_fb_splash; }
The Flip
function on SDL needs to convert a colour index array into RGB data that SDL can consume. We also double the size if the screen resolution is 320 and centre it if it's a 16:9 mode.
You will notice that there's no actual centering in the hires case. The reason for that is that in the SDL/posix target the whole demo loads and initialises in a second so you don't even have the chance to see the loading screen, thus I didn't bother. The animation, which is in low res, is shown on both targets, so I wanted that to look ok on both.
void display_flip_splash() {
int x, y;
int y_delta;
t_canvas *out = dev_window_get_canvas();
tndo_assert((_fb_splash.w) <= out->w);
tndo_assert((_fb_splash.h) <= out->h);
if (splashHires) {
for (y = 0; y < _fb_splash.h; y++) {
unsigned int *dst_a = out->p.pix32 + y * out->w;
for (x = 0; x < _fb_splash.w; x++) {
unsigned int og = _fb_splash.p.pix8[y * _fb_splash.w + x];
unsigned int c = _pal256_splash[og];
dst_a[x] = c;
}
}
} else {
// This splash screen is 16:9. Centre it.
y_delta = ((out->h / 2) - SCREEN_H) / 2;
for (y = 0; y < _fb_splash.h; y++) {
unsigned int *dst_a = out->p.pix32 + (y + y_delta) * 2 * out->w;
unsigned int *dst_b = out->p.pix32 + (((y + y_delta) * 2) + 1) * out->w;
for (x = 0; x < _fb_splash.w; x++) {
unsigned int og = _fb_splash.p.pix8[y * _fb_splash.w + x];
unsigned int c = _pal256_splash[og];
dst_a[x * 2] = c;
dst_a[x * 2 + 1] = c;
dst_b[x * 2] = c;
dst_b[x * 2 + 1] = c;
}
}
}
dev_window_output_flip();
}
Show Splash
copies the image data to the chunky buffer, sets the palette and forces a flip.
void display_show_splash() {
unsigned char *source = (unsigned char *)splashAssets[0];
// Skip palette
source += display_get_palette_skip_splash(splashAssets[0]);
display_set_palette_splash(splashAssets[0]);
memcpy(_fb_splash.p.pix8, source, _fb_splash.w * _fb_splash.h);
display_flip_splash();
}
We need to make sure we link these object files into our demo. In order to do that we add them in the Makefile.
#################################################################################
# Tornado root directory
#################################################################################
TORNADO_BASE = ../..
#################################################################################
# Your demo code comes here
#################################################################################
LOCAL_INCDIR = ./src
# Demo objects
DEMO_OBJS += demo.o
# Anim replay and custom display
DEMO_OBJS += anim_play.o
DEMO_OBJS += amiga/display_splash.o
# Save all object and source files to tmp.
BUILDDIR = /tmp/build-amiga
# Remove asserts while compiling.
CCFLAGS += -DNDEBUG
TARGET = ${SHARED}/splash.68k
# Remove for final release
CCFLAGS += -D__DEBUG_CODE
# Tndo malloc debug.
#CCFLAGS += -DTNDO_MEMORY_DEBUG
#################################################################################
# Do NOT add anything below this line!!!
#################################################################################
include $(TORNADO_BASE)/tornado.mk