Forums

Sega Master System / Mark III / Game Gear
SG-1000 / SC-3000 / SF-7000 / OMV
Home - Forums - Games - Scans - Maps - Cheats - Credits
Music - Videos - Development - Hacks - Translations - Homebrew

View topic - PW's untitled SMS homebrew game

Reply to topic
Author Message
  • Joined: 10 Aug 2023
  • Posts: 31
Reply with quote
PW's untitled SMS homebrew game
Post Posted: Sat Feb 03, 2024 10:36 pm
Hello!

Since last year I've been working on a Sega Master System homebrew game. I used to call it "ninja girl", but then I changed it to a girl holding a laser rifle so I have no idea anymore. I'm intending to make an action game somewhere in the vein of Megaman and Contra. No idea if I can get there, but I'm enjoying the ups and downs so far.

I often post updates, screenshots and clips on Twitter/X under @pw_32x (https://twitter.com/pw_32x). I also lurk on the SMS Power Discord. I thought I'd finally post here in case I wanted to post longer form stuff.

As for the project itself, it uses devkitSMS. I've also got a in-progress engine and a suite of converters and tools I've developed over the years. Everything is open source and up at https://github.com/pw32x/ninjagirl with increasingly outdated documentation. It's not really meant for human consumption, but it's there. The repository also includes a daily versions folder with in-progress roms.

Thanks!

Random in-development shots:

  View user's profile Send private message
  • Joined: 28 Jan 2017
  • Posts: 556
  • Location: Málaga, Spain
Reply with quote
Post Posted: Sun Feb 04, 2024 8:28 am
"Psychic world 2 - carnage in sonic land" would be great!!! (based on images)

Jokes apart:

1. Hey, nice graphics!!!!
2. Why so many historical rom files? git can handle these files also!!!
3. BuildMaster.dll seems to have a virus!!!! (at least, that is what Windows defender) says.
4. Ok, the platform code is working, but now is when the hardest part comes (enemies, collisions, powerups, more screens, a history, etc.) do not hesitate to ask for specific "platformer in ansi c using devkitsms" questions... there are so many complex things to fulfill!!!!

Regards
  View user's profile Send private message
  • Joined: 15 Jul 2022
  • Posts: 29
Reply with quote
Post Posted: Sun Feb 04, 2024 12:17 pm
I've been watching your progress on Twitter/X for some time now.
It's very promising!

Thanks for making the code repository available to the public. It's always good to have it available and learn how you do it.
  View user's profile Send private message
  • Joined: 10 Aug 2023
  • Posts: 31
Reply with quote
Post Posted: Sun Feb 04, 2024 1:53 pm
eruiz00 wrote

1. Hey, nice graphics!!!!


Thanks! It's a hodge-podge of background graphics from older unreleased projects and new sprite graphics. The background graphics will need to be revisited someday. I'm the slowest pixel artist ever so who knows when that will happen.

eruiz00 wrote

2. Why so many historical rom files? git can handle these files also!!!


I like the idea of just picking a version randomly in a folder and see what the progress looked like then. Do I need dozens of rom files in a folder? Probably not. Is it fun to have them around? Definitely.

eruiz00 wrote

3. BuildMaster.dll seems to have a virus!!!! (at least, that is what Windows defender) says.


Strange. A local scan doesn't reveal anything.

I'm still iffy about submitting prebuilt binaries. People would probably expect a proper release of some kind. I might just remove them all to eliminate confusion/hassle.

KingQuack wrote

Thanks for making the code repository available to the public. It's always good to have it available and learn how you do it.


Sure thing! It's in no way, shape or form meant to be some kind of Unity for SMS games, but better to have something out there as an example or a base to build upon.
  View user's profile Send private message
  • Site Admin
  • Joined: 19 Oct 1999
  • Posts: 14723
  • Location: London
Reply with quote
Post Posted: Sun Feb 04, 2024 7:58 pm
It’s kind of a big no-no to put binaries in Git source control… but it’s also kind of common. It just means you retain all that data in the repo history forever.
  View user's profile Send private message Visit poster's website
  • Joined: 15 Aug 2019
  • Posts: 258
  • Location: Lancashire UK
Reply with quote
Post Posted: Fri Feb 09, 2024 2:00 pm
Hey buddy, been following your posts on Twitter with interest. Glad to see more details about this one going on the forum. In terms of title suggestions you could always open it up as a bit of a competition on social media to drive some engagement but I have a couple I'd throw in the hat.

It's a girl with a laser so how about Laser-Girl. I know its an obvious one but catchy. Alternatively Laser-Bird, Laser-Wife, you get the gist. Good luck bud
  View user's profile Send private message Visit poster's website
  • Joined: 01 Feb 2014
  • Posts: 875
Reply with quote
Post Posted: Sat Feb 10, 2024 6:44 am
Very nice graphics. That cartoony style plays well to the natural strengths of the Master System. You could even emphasize it by using slightly more saturated colors on the sprites. At the moment they appear more muted than the backgrounds which seems a bit backwards, as you normally want the characters to stand out.

Regarding the name: If this had been released in the SMS's homeland back in the day, Laser Rifle Ninja Girl no Daibōken would have been considered a perfectly reasonable name, but when bringing it to the west, that would probably have been shortened to just Laser Girl, which I think has a very nice ring to it.
  View user's profile Send private message
  • Joined: 10 Aug 2023
  • Posts: 31
Reply with quote
Post Posted: Mon Mar 18, 2024 3:38 am
Continuing from https://www.smspower.org/forums/15228-DevkitSMSDevelopYourHomebrewInC?start=750#...
discussing performance improvements to my sprite drawing routine. I didn't want to pollute the main devkitSMS thread, so I'm moving the discussion here.

I've tried new variations on the DrawUtils_DrawBatched function. Unfortunately, none of them have been faster. I'll list them out below.

Breaking out the xtile param into more explicit steps:


void DrawUtils_DrawBatched(void)
{
   const u8* runner = (const u8*)DrawUtils_currentSpriteStrips;

DrawUtils_DrawBatched_loop:

        u16 y = DrawUtils_screenY + (s8)*(runner);

        u16 xtileParam = (DrawUtils_screenX + (s8)*(runner + 1));
        xtileParam <<= 8;
        xtileParam |= *(runner + 2) + DrawUtils_vdpTileIndex;


        switch (*(runner + 3))
        {
        case 0: return;
        case 1:     DrawUtils_addSprite(y, xtileParam);
            break;
        case 2:     DrawUtils_addTwoAdjoiningSprites(y, xtileParam);
            break; 
        case 3:     DrawUtils_addThreeAdjoiningSprites(y, xtileParam);
            break;
        case 4:     DrawUtils_addFourAdjoiningSprites(y, xtileParam);
            break;
        }

        runner += 4;

    goto DrawUtils_DrawBatched_loop;
}



Using a pointer to array to run through the values.


#define PARAM_COMBINER(x, tile) ((x<<8)|tile)

void DrawUtils_DrawBatched(void)
{
   const u8* runner = (const u8*)DrawUtils_currentSpriteStrips;

DrawUtils_DrawBatched_loop:

        u16 y = DrawUtils_screenY + (s16)*(runner++);

        u16 xtileParam = (DrawUtils_screenX + (s16)*(runner++)) << 8;
        xtileParam |= *(runner++) + DrawUtils_vdpTileIndex;


        switch (*(runner++))
        {
        case 0: return;
        case 1:     DrawUtils_addSprite(y, xtileParam);
            break;
        case 2:     DrawUtils_addTwoAdjoiningSprites(y, xtileParam);
            break; 
        case 3:     DrawUtils_addThreeAdjoiningSprites(y, xtileParam);
            break;
        case 4:     DrawUtils_addFourAdjoiningSprites(y, xtileParam);
            break;
        }

    goto DrawUtils_DrawBatched_loop;
}





An array of function calls indexed by the number of sprites to draw


void (*drawSprite[]) (unsigned int y, unsigned int x_tile) __naked __preserves_regs(d,e,iyh,iyl) __sdcccall(1) =
{
    NULL, // do nothing for 0 sprites
    DrawUtils_addSprite,
    DrawUtils_addTwoAdjoiningSprites,
    DrawUtils_addThreeAdjoiningSprites,
    DrawUtils_addFourAdjoiningSprites,
};



#define PARAM_COMBINER(x, tile) ((x<<8)|tile)

void DrawUtils_DrawBatched(void)
{
   const BatchedAnimationSpriteStrip* runner = DrawUtils_currentSpriteStrips;

DrawUtils_DrawBatched_loop:

        if (!runner->count)
            return;

        u16 xtileParam = PARAM_COMBINER(DrawUtils_screenX + runner->xOffset, runner->tileIndex + DrawUtils_vdpTileIndex);
        u16 y = DrawUtils_screenY + runner->yOffset;

        drawSprite[runner->count](y, xtileParam);

        runner++;

    goto DrawUtils_DrawBatched_loop;
}




Working with four byte arrays, one for each strip parameter.


void DrawUtils_DrawBatched(void)
{
    const s8* yOffsets = ((const s8*)DrawUtils_currentSpriteStrips);
    const s8* xOffsets = ((const s8*)DrawUtils_currentSpriteStrips) + 1;
    const u8* tileIndexes = ((const s8*)DrawUtils_currentSpriteStrips) + 2;
    const u8* spriteCounts = ((const s8*)DrawUtils_currentSpriteStrips) + 3;

DrawUtils_DrawBatched_loop:

        u16 xtileParam = PARAM_COMBINER(DrawUtils_screenX + *xOffsets, *tileIndexes + DrawUtils_vdpTileIndex);
        u16 y = DrawUtils_screenY + *yOffsets;

        switch (*spriteCounts)
        {
        case 1:     DrawUtils_addSprite(y, xtileParam);
            break;
        case 2:     DrawUtils_addTwoAdjoiningSprites(y, xtileParam);
            break; 
        case 3:     DrawUtils_addThreeAdjoiningSprites(y, xtileParam);
            break;
        case 4:     DrawUtils_addFourAdjoiningSprites(y, xtileParam);
            break;
        }

        yOffsets += 4;
        xOffsets += 4;
        tileIndexes += 4;
        spriteCounts += 4;

        if (!*spriteCounts)
            return;

    goto DrawUtils_DrawBatched_loop;
}



Combination of 4 byte arrays and an array of function pointers.



void (*drawSprite[]) (unsigned int y, unsigned int x_tile) __naked __preserves_regs(d,e,iyh,iyl) __sdcccall(1) =
{
    NULL, // do nothing for 0 sprites
    DrawUtils_addSprite,
    DrawUtils_addTwoAdjoiningSprites,
    DrawUtils_addThreeAdjoiningSprites,
    DrawUtils_addFourAdjoiningSprites,
};



#define PARAM_COMBINER(x, tile) ((x<<8)|tile)

void DrawUtils_DrawBatched(void)
{
    const s8* yOffsets = ((const s8*)DrawUtils_currentSpriteStrips);
    const s8* xOffsets = ((const s8*)DrawUtils_currentSpriteStrips) + 1;
    const u8* tileIndexes = ((const s8*)DrawUtils_currentSpriteStrips) + 2;
    const u8* spriteCounts = ((const s8*)DrawUtils_currentSpriteStrips) + 3;

DrawUtils_DrawBatched_loop:

        if (!*spriteCounts)
            return;

        drawSprite[*spriteCounts](DrawUtils_screenY + *yOffsets, PARAM_COMBINER(DrawUtils_screenX + *xOffsets, *tileIndexes + DrawUtils_vdpTileIndex));

        yOffsets += 4;
        xOffsets += 4;
        tileIndexes += 4;
        spriteCounts += 4;

    goto DrawUtils_DrawBatched_loop;
}


It's late so I haven't collected the generated assembly. I'll do that tomorrow if it's necessary.
  View user's profile Send private message
  • Joined: 05 Sep 2013
  • Posts: 3793
  • Location: Stockholm, Sweden
Reply with quote
Post Posted: Mon Mar 18, 2024 9:18 am
in general, SDCC performs better when the functions are small and the local variables are kept to a minimum - often one or max two chars or one int. Sometimes using a single local variable with a short lifespan is the best option as it doesn't even get into RAM - SDCC optimizes this using a register.

example:
void foo (void) {
  unsigned char i;
  for (i=0;i<4;i++)
    do_something(i, other_param);
}


I often use a small test.c source file to test how SDCC behaves with different approaches, for instance what would change if in the example above I declare the i variable static? Sure, you need to be able to read the generated Z80 code to exactly see what's going on, but you could anyway measure the different performances using Emulicious' profiler - you're sort of 'blind' testing but that's better than nothing at all.

Regarding using the switch statement - yes, sometimes a block of IF-ELSE-IF performs better, especially when there are just a few different possible values and if they're spread.

For instance here:
switch (k) {
  case 0: do_something(); break;
  case 12: do_something_else(); break;
  case 45: do_something_else_2(); break;
}


I would expect either SDCC turning this into the same code that a block of IF-ELSE-IF would generate or generate sub-optimal code. I'd bet on the former, but it's better to try and see what comes out.
  View user's profile Send private message Visit poster's website
  • Joined: 10 Aug 2023
  • Posts: 31
Reply with quote
Post Posted: Wed Mar 20, 2024 1:10 am
More adventures in the project's optimization journey.

_OUTI

So last night I updated devkitSMS and it went smoothly, except for the custom parts I made that gave me a slight head scratching.

I had added new _OUTI routines for 192 and 256 bytes in crt0_sms.s.


_OUTI256::                              ; _OUTI256 label points to a block of 192 OUTI and a RET
        .rept 64
        outi
        .endm
_OUTI192::                              ; _OUTI192 label points to a block of 192 OUTI and a RET
        .rept 64
        outi
        .endm
_OUTI128::                              ; _OUTI128 label points to a block of 128 OUTI and a RET
        .rept 64
        outi
        .endm
_OUTI64::                               ; _OUTI64 label points to a block of 64 OUTI and a RET
        .rept 32
        outi
        .endm
_OUTI32::                               ; _OUTI32 label points to a block of 32 OUTI and a RET
        .rept 32
        outi
        .endm
_outi_block::                           ; _outi_block label points to END of OUTI block
        ret


But strangely trying to use them wouldn't work correctly. I had made corresponding UNSAFE_SMS_VRAMmemcpy functions. Everything looked correct but trying to use them in my sprite-tiles-to-vdp update code it would be off by one sprite. It would upload 2 sprites instead of 3, 1 instead of 2. I thought maybe the linker was somehow off or maybe the _OUTI256 routine was pushing against some other region of rom. In the end I used toxa's idea to declare the OUTI functions as void* and used nested OUTI128 calls. I didn't need the new 192/256 versions anymore so I saved that much in rom space. Everything started working again.

SDCC

This morning I upgraded my version of SDCC from 4.3 to 4.4. The time spent in DrawUtils_DrawBatched more than doubled, which was shocking. I didn't have the patience to start investigating so I just reverted back to 4.3.

Adjoining Sprites

Before I wanted to try out the devkitSMS metasprite routines, I thought I'd try the new versions of the adjoining sprite functions. Here as well I was really surprised at the performance drop, which DrawUtils_DrawBatched went from just 5% self time to 15%. With the new devkitSMS, its adjoining sprites functions and my modified versions are basically identical, so having a big difference was really odd.

The difference was how the functions were called. Previously I was calling the My_add*AdjoiningSprites_f() functions directly, where I had switched to the SMS_add*AdjoiningSprites macros. For some reason calling the macros tripled the self time of DrawUtils_DrawBatched. When I changed it to use the SMS_addTwoAdjoiningSprites_f functions directly, the performance reverted back to normal.

I've included the C/Asm listings of the DrawUtils_DrawBatched for functions and macros in case someone wanted to take a look.

DrawUtils using functions
https://www.dropbox.com/scl/fi/j0uecrwe8bn12xvkr0w60/DrawUtils_functions.asm?rlk...

DrawUtils using macros
https://www.dropbox.com/scl/fi/kle66zjm0x97w6xcfjh8p/DrawUtils_macros.asm?rlkey=...

I'm not sure what to make of it. You'd think there shouldn't be a difference, but there is.

The adventure continues!
  View user's profile Send private message
  • Joined: 05 Sep 2013
  • Posts: 3793
  • Location: Stockholm, Sweden
Reply with quote
Post Posted: Wed Mar 20, 2024 8:02 am
pw wrote
I had added new _OUTI routines for 192 and 256 bytes in crt0_sms.s.

But strangely trying to use them wouldn't work correctly.


I suspect it's because there's only 512 bytes reserved to crt0 by default, unless you tell the linker to reserve more space.
Good you solved that in some other way :)

pw wrote
I've included the C/Asm listings of the DrawUtils_DrawBatched for functions and macros in case someone wanted to take a look.

DrawUtils using functions
https://www.dropbox.com/scl/fi/j0uecrwe8bn12xvkr0w60/DrawUtils_functions.asm?rlk...

DrawUtils using macros
https://www.dropbox.com/scl/fi/kle66zjm0x97w6xcfjh8p/DrawUtils_macros.asm?rlkey=...

I'm not sure what to make of it. You'd think there shouldn't be a difference, but there is.


I've never seen SDCC generate such bad code before. It's full of useless shuffling... and honestly it's hard for me to tell why it's doing so.
Lacking any better idea, I would suggest you try removing the goto and the label. So use
for (;;) {
  <your code here>
}
instead, see if it improves.
  View user's profile Send private message Visit poster's website
  • Joined: 05 Sep 2013
  • Posts: 3793
  • Location: Stockholm, Sweden
Reply with quote
Post Posted: Wed Mar 20, 2024 8:20 am
also I noticed your local variables x,y,tile don't really help because you're recalculating them at each loop, so you could try this:

void DrawUtils_DrawBatched(void)
{
   const BatchedAnimationSpriteStrip* runner = DrawUtils_currentSpriteStrips;

  for (;;) {   // endless loop

    switch (runner->count)
    {
    case 1:     SMS_addSprite(DrawUtils_screenX + runner->xOffset, DrawUtils_screenY + runner->yOffset, runner->tileIndex + DrawUtils_vdpTileIndex);
        break;
    case 2:     SMS_addTwoAdjoiningSprites(DrawUtils_screenX + runner->xOffset, DrawUtils_screenY + runner->yOffset, runner->tileIndex + DrawUtils_vdpTileIndex);
        break; 
    case 3:     SMS_addThreeAdjoiningSprites(DrawUtils_screenX + runner->xOffset, DrawUtils_screenY + runner->yOffset, runner->tileIndex + DrawUtils_vdpTileIndex);
        break;
    case 4:     SMS_addFourAdjoiningSprites(DrawUtils_screenX + runner->xOffset, DrawUtils_screenY + runner->yOffset, runner->tileIndex + DrawUtils_vdpTileIndex);
        break;
    }

    runner++;

    if (!runner->count)
        return;

   }  // endless loop terminator
}
  View user's profile Send private message Visit poster's website
Reply to topic



Back to the top of this page

Back to SMS Power!