Programming for the NES in C or C++

Discuss technical or other issues relating to programming the Nintendo Entertainment System, Famicom, or compatible systems.

Moderator: Moderators

Nessie
Posts: 134
Joined: Mon Sep 20, 2004 11:13 am
Location: Sweden
Contact:

Post by Nessie »

Depending on how capable the assembler is, advanced macros can sometimes be a great substitute for real C code without hurting performance as much.
However, since C is such a ubiquitous language, it automatically comes with many other benefits e.g. reusable code, large toolset with syntax highlighting, static analysis, etc
User avatar
tokumaru
Posts: 12106
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Post by tokumaru »

Hamtaro126 wrote:But in the very distant future, Maybe it is possible if we keep learning everything about 65xx ASM by then...
The problem here is not knowledge, it's time and motivation. Lots of people here know the NES better than most of the professional developers from back in the day (we've been studying it for over 10 years, while they only focused on it for short periods), and we know everything there is to know about game concepts like scrolling, collision detection, and everything else necessary for a big-scale game.

However, coding a big game like SMB3 demands a lot of time, something we don't have because of our real-life jobs and bills to pay. When we do have the time, we lack the motivation, because it's such a huge undertaking anyway and we are not offered any sort of compensation for it.
slobu
Posts: 275
Joined: Tue Jul 12, 2011 10:58 am

Post by slobu »

It seems creating SMB3 comparable games is not a pure ASM vs. C thing then. It's a motivation thing.

Also, I suspect that we all know why higher level languages exist. Why EDLIN gave way to EDIT. If more savvy programmers worked on better C libraries then the difference between C and ASM performance would decrease. As long as we think C *OR* ASM - high or low - they'll always be a self-justifying difference in output.

The best way to convince a beginner or higher level language programmer to convert to ASM is to recommend something that allows for ASM but retains its higher level language underpinnings for faster development. This is what Fred Quimby did with Batari BASIC for the Atari 2600. It's a gateway drug to assembly - easy results and easy integration of assembly when needed.

NESICIDE should offer the best chance for high level language programmers to start NES development. Especially ones who expect a development environment to just work out of the box.

Here's a promising C library but I'm not sure how it works with the cc65 included in NESICIDE:
http://kkfos.aspekt.fi/projects/nes/kne ... -for-cc65/
User avatar
Bregalad
Posts: 8036
Joined: Fri Nov 12, 2004 2:49 pm
Location: Caen, France

Post by Bregalad »

Well maybe it's a bit too early for me to talk about this but I currently have a project where I'm going to write an entire game in assembly, and port it entirely in C, and this could be a benchmark for compilers. Before anyone is calling me crazy I'm talking about a simple puzzle game here, not something like SMB3. Also I'm at about 50% of the development stage of the assembly version.

The C version will have to use almost everything the C language offers too (complex data types, routines with loads of arguments etc...). This could be used as a benchmark for C compilers for how well they perform.

The reason to do this for a whole game instead of just some piece of code is that it will give constructive results. If you just use a random piece of code as a benchmark, it will not be sinificative enough to tell if the downgrade of performance done by the comiler is handicapping for a game or not.

First I'd try with CC65, but then I'd like to either port SDCC to the 6502, or do an unofficial port of CC65 which will have better performance. I don't know if I'll be able to do it, in fact I'll probably have some trouble as I have no experience in writing compilers, and this is a complex subject.

Ideally I think a program written in C should at the very worst be 1/3 less efficient than it's assembly counterpart, which should be acceptable in most cases. When it's not acceptable, anyone can still continue to use assembly.

However I have quite some experience both in programming in C, and definitely a whole load of experience programming in 6502 assembly, so maybe I'm one of the few people that could handle such a project so I'll try.

So yes this seems ambitious, but for some reason I think this should be useful to the NESDev communality to be able to program in C without being afraid that the resulting compiled code will eventually suck.
Useless, lumbering half-wits don't scare us.
User avatar
tokumaru
Posts: 12106
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Post by tokumaru »

Bregalad wrote:I currently have a project where I'm going to write an entire game in assembly, and port it entirely in C
I think this experiment would work better if you made the C version first, and then ported it to assembly. Since C is more restricted than assembly, the program will have to be structured in a certain way for it to work, and when porting it to assembly you can obey the same structure. If you start will all the freedom of assembly, the C version will probably have to be completely redesigned. i think a comparison would be more meaningful if both programs had the same underlying architecture, and only the actual tasks were performed in either C or assembly.
User avatar
Bregalad
Posts: 8036
Joined: Fri Nov 12, 2004 2:49 pm
Location: Caen, France

Post by Bregalad »

I don't know maybe you're right.
I've already started the game in assembly - the idea to port it to C and make a benchmark out of it only came later.

I think I could port it in C without changing the entire structure. It's not like if I were using lots of tricks - even when I code in assembly I usually stuck to standard structure where functions calls themselves with some arguments, and it's extremely rare I have a function returning more than one value - if this happens I'll just use global variables or anyother alternative to port it directly in C without changing the structure too much.

The reason I don't start with C is so I don't feel like I'm doing the compiler's job when I translate into assembly, which could influence my in my style of assembly writing.
Useless, lumbering half-wits don't scare us.
Shiru
Posts: 1161
Joined: Sat Jan 23, 2010 11:41 pm

Post by Shiru »

Games of SMB3 scale are very difficult for homebrew devs not because of programming (which is maybe a bit tricky, but doable), but because of the scale, amount and quality of the content - things that homebrew devs not very good at, because they mostly programmers. SMB3 wouldn't be that great without these components.
slobu
Posts: 275
Joined: Tue Jul 12, 2011 10:58 am

Post by slobu »

@Bregalad: Maybe when you start the C version you could use NESICIDE. I'm sure your feedback from a seasoned developers point of view would be valuable. I've already got the n00b developers point of view covered :p
User avatar
thefox
Posts: 3139
Joined: Mon Jan 03, 2005 10:36 am
Location: Tampere, Finland
Contact:

Post by thefox »

slobu wrote:Here's a promising C library but I'm not sure how it works with the cc65 included in NESICIDE:
http://kkfos.aspekt.fi/projects/nes/kne ... -for-cc65/
My library is kind of low level, it mostly just exposes the NES hardware registers to the C code in an easy to use way together with some support routines like controller reading. It's OK for quickly prototyping some stuff (I have written various tests using it myself, much faster than it would have taken to write them in assembly), but for game development right now it's better to use a higher level library, like the one provided by Shiru in his C examples.

That said I have sometimes been thinking about expanding the library with more "high level" stuff, but that's probably not going to happen until I actually need that stuff somewhere. :)
JimDaBim
Posts: 85
Joined: Wed Jun 20, 2012 6:40 am

Post by JimDaBim »

Dwedit wrote:Zooming Secretary is written in C, and has source code available.
Neat. Is this game completely written in C, without any self-written Assembly?
Disch wrote:Much, much simpler than C.
Sorry, but I won't believe that.

By the way, does Assembly have functions and the ability to re-use code? Can I play around with one and the same program or is anything that I write fixed until I tediously change many parts in the source code?

One example: If I have a game character that is made of various sprites, like Mega Man, can I write a function in Assembly that moves all the sprite objects with the same call? Or do I have to program the movement of each sprite object individually? Like when I decide that the character shall not consist of two, but of three sprite objects. In C, I would just edit the drawing routine:

Code: Select all

void MoveCharacter(int x, int y)
{
    MoveSprite(spr1, x, y);
    MoveSprite(spr2, x, y);

    // The character shall consist of three sprites now,
    // so I add this:
    MoveSprite(spr3, x, y);
}
My other logic wouldn't change, I would always just call

Code: Select all

MoveCharacter(30, 122);
no matter if my character consists of two, three or 100 single sprites.

Is something like that possible with Assembly? Or do I have to call all the steps for movement for each sprite individually and if I add one sprite, I have to add 20 more commands in my code?
User avatar
rainwarrior
Posts: 8062
Joined: Sun Jan 22, 2012 12:03 pm
Location: Canada
Contact:

Post by rainwarrior »

6502 has the instructions JSR (jump to subroutine) and RTS (return from subroutine). JSR pushes the current program counter and jumps to your subroutine; RTS does the reverse, returning to the next instruction.

A C function will use these when compiled. Store your arguments somewhere (in registers or somewhere in memory), JSR to the function's code, then "return" will store the return values somewhere and RTS.

So yes, the hardware has a concept of a subroutine.
User avatar
tokumaru
Posts: 12106
Joined: Sat Feb 12, 2005 9:43 pm
Location: Rio de Janeiro - Brazil

Post by tokumaru »

JimDaBim wrote:Neat. Is this game completely written in C, without any self-written Assembly?
I believe the source code is available, so you can check it out yourself.
By the way, does Assembly have functions and the ability to re-use code?
The code is as reusable as you make it, like any other programming language.
One example: If I have a game character that is made of various sprites, like Mega Man, can I write a function in Assembly that moves all the sprite objects with the same call?
Sure.
Or do I have to program the movement of each sprite object individually?
Newbies often do that, but that's because they're newbies. =)
Like when I decide that the character shall not consist of two, but of three sprite objects. In C, I would just edit the drawing routine:
This is fine, although you'd usually avoid this much function calling in a system as weak as the NES, no matter if you are working in C or in ASM.
if I add one sprite, I have to add 20 more commands in my code?
Like I said, you typically wouldn't call a function for each individual sprite because that would be too slow. You'd probably have a loop, something like "from sprite X to sprite X+Y do this", in which case you'd just modify the range of the loop and it would cost you no extra commands at all if you wanted to handle more sprites.

Most beginner tutorials use assembly in a very specific way, with lots of hardcoded commands, but don't let that fool you - assembly is as versatile and as reusable as any other language. Maybe even more, because it isn't constrained to data types and logic blocks. You can even apply OOP concepts fairly easily.
tepples
Posts: 22345
Joined: Sun Sep 19, 2004 11:12 pm
Location: NE Indiana, USA (NTSC)
Contact:

Post by tepples »

JimDaBim wrote:By the way, does Assembly have functions and the ability to re-use code?
Assembly language has subroutines, which do the same thing as functions in C. For example, functions in C compile to subroutines in assembly language. It also has jump tables, which can be turned into method calls.
One example: If I have a game character that is made of various sprites, like Mega Man, can I write a function in Assembly that moves all the sprite objects with the same call?
Yes. What typically happens is that you have a subroutine that takes X, Y, and frame arguments, calculates where each of the sprites that make up a frame go in relationship to the given coordinate, and writes all the sprites to a display list. The display list lists all sprites that are considered visible during this frame, and it grows from 1 element to up to 64 over the course of each frame before it's copied to the video chip.
The character shall consist of three sprites now
So you add the third sprite to the list of the sprites that make up each frame, and the subroutine reads the longer list.
User avatar
Disch
Posts: 1848
Joined: Wed Nov 10, 2004 6:47 pm

Post by Disch »

Here are some problems that I see with compiling C for the NES:

1) 6502 has a concept of a call stack, but only partially. JSR/RTS only record the PC, they do not push/pop local variables or function parameters. The 256 bytes on the 6502 is probably not enough space to handle such a call stack, so the compiled code will have to simulate its own stack.

This means that local variables and function parameters are all going to have to be accessed indirectly. This poses several problems on the 6502:

-) LDA (n),Y is 5-6 cycles. LDA abs is 4 cycles. This means for memory accesses alone, you're already running 25-50% slower.
-) Y has to be reserved as the new stack pointer, which means you have one less general purpose register. Those quick nested loops you can write in raw assembly now become cumbersome because C will have to use zero page memory for additional loop counters instead of Y. INY = 2 cycles vs. INC zp = ?5? cycles (recalling these cycle counts from memory, feel free to double check).
-) Since Y is the new stack pointer, if you want to use Y for any other purpose you have to back it up and restore it. Since Y is required for most indirect accesses, that means any pointer dereferencing now entails a STY/LDY/LDY, in addition to the actual dereference itself. Note that the STY+LDY+LDY actually takes longer than LDA (n),Y, so that's over a 100% slowdown.

Granted these can be optimized away, but not completely. And a bunch of those little slowdowns add up real fast.

2) Common C practices would murder performance. Things like using 'int' for general variables, which every C programmer does. If you are adhering to C standards, an int has to be at least 16 bits. Can you imagine what kind of slowdown (and memory consumption!) you'd get in an NES program if every variable is 16 bits wide?




Really, for a C program to really be effective on the NES, it would have to be written with an understanding not only of how the NES architecture operates, but also how the compiler operates. You'd have to do things like use lots of globals, minimize the number of parameters passed to functions, etc, etc... to really get peak performance. Note that these are things you're generally not really supposed to do in C.
User avatar
rainwarrior
Posts: 8062
Joined: Sun Jan 22, 2012 12:03 pm
Location: Canada
Contact:

Post by rainwarrior »

It's not that hard to optimize around the virtual stack. If you keep your functions void() and manage your temporary/local variables on the ZP/BSS yourself, this takes care of most of it without much effort.

Recursion is a problem, but there are still solutions for that.


And yes of course you should learn good practices for the platform you target. The CC65 manual will tell you to use char instead of int unless you need it, etc. (Or read shiru's guide.) Also, there's not a lot of harm in writing it quickly and inefficiently at first and optimizing later when you actually have performance problems.
Last edited by rainwarrior on Thu Jun 21, 2012 3:43 pm, edited 1 time in total.
Post Reply