Monday, July 4, 2022

devkitSMS Programming Sample V

In the previous post, we checked out devkitSMS Programming Setup II. The devkitSMS provides tools and code to support many homebrew development projects for the Sega Master System and Sega Game Gear.

The latest version of the devkitSMS requires an updated version of Small Device Cross Compiler SDCC 4.x and supports new and updated features including the ability to page both code and content transparently.

Let's check it out!

Software
Follow all instructions from the previous post: this documents how to setup the pre-requisite software.
Note: ensure you have downloaded and installed the devkitSMS and Small Device C Compiler [SDCC].

Also ensure that pcmenc is installed in the SDCC bin folder as this utility is used to play WAV audio files.

Demo
All demos in this devkitSMS programming sample are based on the simple Hello World program the previous post that utilizes Visual Studio 2015 on Windows but can be deployed cross platform on Windows and Linux.

Create folder C:\DevkitExample. Create the following sub-folders: crt0, dev, lib, tmp. Note: all content now preferred to reside under content sub-folder beneath the main dev folder. All content also built using scripts.


Development
Launch Visual Studio 2015. File | New | Project... | Visual C++ | Win32 | Win32 Project

 Name:  Game
 Location:  C:\DevkitExample\dev
 Create directory for solution  UNCHECKED
OK

 Application type:  Console application
 Additional options:  Empty project CHECKED
Finish

Ensure Visual Studio 2015 is configured as per instructions here to automate build process from Ctrl+1 hot key. Create folders beneath dev for .vscode and devkit. Import all code from here including build the scripts.

main.c
#include "main.h"
void main( void )
{
  devkit_SMS_init();
  devkit_SMS_setSpritePaletteColor( 0, devkit_RGB( 3, 3, 3 ) );
  devkit_SMS_displayOn();
  for( ;; )
  {
    devkit_SMS_waitForVBlank();
  }
}

Graphics
Follow all instructions from the previous post to download and install BMP2Tile utility to convert graphics into Sega Master System format. Download new gfxcomp_stm.dll STM compressed format to output directory.

Splash
Here is an example of background tiles that will exhaust the full 256 x 192 resolution e.g. the Splash screen:

Follow the same settings as per here but this time use this script to automate the corresponding formatting:

build.bat
bmp2tile.exe raw/splash.bmp ^
  -savetiles "splash (tiles).psgcompr" -removedupes -nomirror -planar -tileoffset 128 ^
  -savetilemap "splash (tilemap).stmcompr" ^
  -savepalette "splash (palette).bin" ^
  -fullpalette -exit

 Tiles:  Remove duplicates  Checked
 Tiles:  Planar tile output  Checked
 Tilemap:  Use sprite palette  Unchecked
 Tilemap:  In front of sprites  Unchecked
 Palette:  Output hex (SMS)  Checked

Opaque
Candy Kid, which is a Pacman clone, uses opaque background tiles to obscure the main sprite as he goes thru the exits. This technique renders a white 16x16 tile but uses sprite palette bitwise OR with tile priority.

tile_manager.c
static void draw_tile_priority( unsigned char offset, unsigned char x, unsigned char y )
{
  unsigned int priority = devkit_TILE_PRIORITY() | devkit_TILE_USE_SPRITE_PALETTE();

  devkit_SMS_setNextTileatXY( x + 0, y + 0 );
  devkit_SMS_setTile( ( *game_tiles__tilemap__bin + offset + 0 ) | priority );
}

IMPORTANT: the sprite palette color in this example must be black to match background despite white tiles.

Tools
In previous years, Gimp was used to convert graphics as Indexed [16] mode to meet the 4bpp Sega Master System compatible requirements. However, paint.net is great to output PNG files for BMP online converter:
 Color:  4 (indexed)
 With rows direction:  Bottom - Top (default)
 Quantization equal to:  8
 Using dithering:  No


Scrolling
Scrolling in video games involves moving graphics across a display, either horizontally and/or vertically such that the layout does not change but rather moves the view across as an image larger than the whole screen.

Horizontal scrolling was very popular for action, run-and-gun and shoot/beat 'em up games whereas vertical scrolling was used in top down shooters. The devkitSMS tutorial has both vertical and horizontal examples:

Horizontal
Here is the devkitSMS tutorial horizontal scrolling "Ice Hockey" example using the SMS_loadTileMap() API.


scroll_manager.c
void engine_scroll_manager_update_right( unsigned char delta )
{
  unsigned char *src = NULL;
  unsigned char ytile = 0;
  unsigned int index = 0;
  const unsigned int size = 2;

  unsigned int x = 0;
  unsigned char y = 0;

  if( scrollRightDivided8 < scrollWidth )
  {
    scroll -= delta;
    scrollRight += delta;
    scrollRightDivided8 = scrollRight / 8;
    devkit_SMS_setBGScrollX( scroll );

    if( ( scrollRight % 8 ) == delta )
    {
      for( ytile = 1; ytile < Y_TILE_MAX; ytile++ )
      {
        x = X_TILE_MAX + scrollRightDivided8;
        y = ytile - 1;
        index = ( ( BG_TILE_WIDTH*ytile ) + ( X_TILE_MAX + scrollRightDivided8 ) ) * 2;
        src = ( void * ) &hockey__tilemap__bin[ index ];
        devkit_SMS_loadTileMap( x, y, src, size );
      }
    }
  }
}

Vertical
Here is the devkitSMS tutorial vertical scrolling "Marilyn" [MM] example using the SMS_loadTileMap() API.


scroll_manager.c
void engine_scroll_manager_down()
{
  struct_scroll_object *so = &global_scroll_object;
  unsigned int index;
  unsigned int y;

  if( so->scroll < so->height )
  {
    so->scroll++;
    devkit_SMS_setBGScrollY( so->scroll );

    if( ( so->scroll % 8 ) == 0 )
    {
      y = ( 24 + ( so->scroll / 8 ) ) % 28;
      index = ( 24 + ( so->scroll / 8 ) ) * 32 * 2;
      devkit_SMS_loadTileMap( 0, y, ( void * ) &MM__tilemap__bin[ index ], so->size );
    }
  }
}

Vertical II
Here is another vertical scrolling example which prefers SMS_setNextTileatXY() after loading all the tiles.


map_manager.c
static void draw_map_row()
{
  struct_map_object *map_data = &global_map_object;
  unsigned char i, j;
  unsigned char y;
  unsigned char *map_char;
  unsigned int base_tile, tile;
  unsigned char buffer[ 16 ];

  decompress_map_row( buffer );
  for( i = 2, y = map_data->background_y, base_tile = 256; i; i--, y++, base_tile++ )
  {
    devkit_SMS_setNextTileatXY( 0, y );
    for( j = 16, map_char = buffer; j; j--, map_char++ )
    {
      tile = base_tile + ( *map_char << 2 );
      devkit_SMS_setTile( tile );
      devkit_SMS_setTile( tile + 2 );
    }
  }
}

PCM Samples
The Sega Master System is able to play high quality Pulse-Code Modulation [PCM] samples with the pcmenc tool built by Maxim from the SMS Power! community which means WAV files can be converted and played.

Single Bank
Ensure the pcmenc executable currently lives in ~/SDCC/bin folder and accessible from $PATH. Launch the terminal and execute the following command to convert an input "test" WAV file to pcmenc compatible file:
 if exist "SoundFx.wav" pcmenc -rto 1 -dt1 12 -dt2 12 -dt3 423 SoundFx.wav

This assumes that the converted WAV file will be less than 16KB; the maximum size for Sega Master System bank. If not then the WAV file will need to be converted to multiple WAV files + converted [example below].

Here is an example of converting an input WAV file using pcmenc to be SMS compatible for code integration.
Contains the engine code to init and play the converted WAV file as PCM sample on the Sega Master System.

sample_manager.c
void engine_sample_manager_init( const void *psginit );
void engine_sample_manager_play( const void *sample );

riff_manager.c
Public API that game code will call as wrapper around the sample manager to actually play the PCM sample.

IMPORTANT:
I've found that is usually safest to init the riff manager before playing each riff to avoid unwanted noise!

Multiple Banks
When the converted WAV file is greater than 16KB then use Audacity to convert the riff to multiple WAV files. Download, install and launch Audacity. Open WAV file. Divide into multiple smaller files using these settings:

 Project Rate (Hz)  44100
 Snap To  Off
 Selection Start  000,000,000 samples
 Length CHECKED  000,050,000 samples
 Audio Position  000,000,000 samples

Hit Ctrl+B | Enter "01". Repeat as per number of banks required at 50,000 samples intervals for example:
 Riff 01  Selection Start  000,000,000 samples  Length CHECKED  000,050,000 samples
 Riff 02  Selection Start  000,050,000 samples  Length CHECKED  000,050,000 samples
 Riff 03  Selection Start  000,100,000 samples  Length CHECKED  000,050,000 samples
 Riff 04  Selection Start  000,150,000 samples  Length CHECKED  000,050,000 samples

File menu | Export Multiple. Choose export location | Export | OK. Execute the following script to convert:
if exist "01.wav" pcmenc -rto 1 -dt1 12 -dt2 12 -dt3 423 01.wav
if exist "02.wav" pcmenc -rto 1 -dt1 12 -dt2 12 -dt3 423 02.wav
if exist "03.wav" pcmenc -rto 1 -dt1 12 -dt2 12 -dt3 423 03.wav
if exist "04.wav" pcmenc -rto 1 -dt1 12 -dt2 12 -dt3 423 04.wav

Finally, rename the *.wav.pcmenc files to more code friendly names and copy the files to parent banks folder with each *.wav.pcmenc file in its own bank. Use this utiltiy to automate this task and generate the scripts.

Invoke the riff manager similar to single bank example except iterate thru all banks that are required to play riff. The only current drawback with this technique is the main thread blocks as the riff plays synchronously!


Banked Code
SDCC 4.x supports banked code which means it is now possible to page in and out code in a transparent manner. The banked code should be written in separate source files that get allocated in separate code banks. To summarize, banked code means that total code will not be restricted to 32KB maximum size.


Method
1. Put all banked code in separate source files marking banked functions using __banked SDCC keyword.
 banked_code_1.h  banked_code_1.c
 #ifndef _BANK_1_H_
 #define _BANK_1_H_

 int banked_code_1() __banked;

 #endif//_BANK_1_H_
 #include "banked_code_1.h"

 int banked_code_1() __banked
 {
 	return 1;
 }

2. Compile each banked code source file into a specific separate code segment as part of the build script.
 sdcc --debug -c -mz80 --codeseg BANK1 banked_code_1.c
 sdcc --debug -c -mz80 --codeseg BANK2 banked_code_2.c
 sdcc --debug -c -mz80 --codeseg BANK3 banked_code_3.c

3. Use the newly provided crt0b_sms.rel as the first module into your linker call instead of crt0_sms.rel.
4. Instruct the linker to place the banked code at address 0x4000 + the virtual address of the bank which will be calculated as bank number * 2^16 i.e. code segment BANK1 at 0x14000, BANK2 at 0x24000 etc.
 sdcc --debug -o output.ihx -mz80 --no-std-crt0 --data-loc 0xC000 \
 -Wl-b_BANK1=0x14000 \
 -Wl-b_BANK2=0x24000 \
 -Wl-b_BANK3=0x34000 \
 ../crt0/crt0b_sms.rel \
 ../lib/SMSlib.lib \
 main.rel \
 banks/banked_code_1.rel \
 banks/banked_code_2.rel \
 banks/banked_code_3.rel \

5. Finally use the new makesms tool to convert the ihx output to sms format instead of the ihx2sms tool.
 makesms output.ihx output.sms


Summary
Armed with all this knowledge, we are now in an excellent position to upgrade any existing game projects for Sega Master System to latest version of devkitSMS on SDCC 4.x built cross platform for Windows and Linux!

No comments:

Post a Comment