NES Programming Blog

Discuss technical or other issues relating to programming the Nintendo Entertainment System, Famicom, or compatible systems. See the NESdev wiki for more information.

Moderator: Moderators

User avatar
NESHomebrew
Formerly WhatULive4
Posts: 418
Joined: Fri Oct 30, 2009 4:43 am
Contact:

Re: NES Programming Blog

Post by NESHomebrew »

I saw your video. Should be useful for beginners, I did find the loud clicking a bit distracting, I'm not sure if the mic was picking that up, or if it is a part of your recording software.
User avatar
dougeff
Posts: 3078
Joined: Fri May 08, 2015 7:17 pm

Re: NES Programming Blog

Post by dougeff »

I made a video about using the debugging tools of FCEUX. I probably forgot about a dozen things to mention, but it was getting pretty long.

https://youtu.be/d2XkJQFs0OQ
nesdoug.com -- blog/tutorial on programming for the NES
User avatar
dougeff
Posts: 3078
Joined: Fri May 08, 2015 7:17 pm

Re: NES Programming Blog

Post by dougeff »

I've updated every example code (except for the Spacy Shooty game code, which I plan to rewrite from scratch).

Most of the changes are cosmetic (make comments easier to read), or just trying to make the code more stable.
-added a second v-blank wait in the startup code, before writing to PPU. Which I must have accidentally removed and never put back in.
-moved 'things that need to be done every frame' (like sprite DMA) to NMI code
-added a write to a000 (mirroring) to the init code for my MMC3 examples
-removed the copy of nes.lib from every zip, which apparently was completely unnecessary to keep a copy of, since cc65 is able to find its own copy

At some point, I will also update the Spacy Shooty example code.
nesdoug.com -- blog/tutorial on programming for the NES
User avatar
Diskover
Posts: 219
Joined: Thu Nov 24, 2011 7:16 am
Contact:

Re: NES Programming Blog

Post by Diskover »

dougeff wrote:I've updated every example code (except for the Spacy Shooty game code, which I plan to rewrite from scratch).

Most of the changes are cosmetic (make comments easier to read), or just trying to make the code more stable.
-added a second v-blank wait in the startup code, before writing to PPU. Which I must have accidentally removed and never put back in.
-moved 'things that need to be done every frame' (like sprite DMA) to NMI code
-added a write to a000 (mirroring) to the init code for my MMC3 examples
-removed the copy of nes.lib from every zip, which apparently was completely unnecessary to keep a copy of, since cc65 is able to find its own copy

At some point, I will also update the Spacy Shooty example code.
Great! :beer:
User avatar
dougeff
Posts: 3078
Joined: Fri May 08, 2015 7:17 pm

Re: NES Programming Blog

Post by dougeff »

I've decided to finally do some testing with structs vs cc65.

If you declare the actual struct in the global space, it puts them in the BSS, and actually takes about as much time to access as any other variable...

Code: Select all

struct foo {
	unsigned char X;
	int Y;
	int Z;
};

struct foo B;

void main (void){
	B.X = 4;
	B.Y = 5;
}

compiles to ...

Code: Select all

lda     #$04
	sta     _B

	ldx     #$00
	lda     #$05
	sta     _B+1
	stx     _B+1+1

If, however, you put the struct in the local space, it puts them in the C stack.

Code: Select all

void main (void){
	struct foo C;

	C.X = 3;
	C.Y = 4;
}
compiles to...

Code: Select all

	jsr     decsp5
	lda     #$03
	ldy     #$00
	sta     (sp),y

	iny
	lda     #$04
	sta     (sp),y
	lda     #$00
	iny
	sta     (sp),y
Conclusions, just like variables are faster in cc65 if decared globally, structs seem to also be faster if declared globally. And, much better than I thought.
nesdoug.com -- blog/tutorial on programming for the NES
User avatar
thefox
Posts: 3134
Joined: Mon Jan 03, 2005 10:36 am
Location: 🇫🇮
Contact:

Re: NES Programming Blog

Post by thefox »

Yeah, structs by itself are not too bad. It's arrays of structs indexed by non-constants that can be problematic, e.g.:

Code: Select all

struct foo {
   unsigned char X;
   int Y;
   int Z;
};

struct foo B[5];
unsigned char i;

void main (void){
   for ( i = 0; i < 5; ++i ) {
       B[i].X = 4;
       B[i].Y = 123;
   }
}
The above code has to generate code to multiply the index by the struct size (5) to index the array.

"Structs of arrays" is better:

Code: Select all

struct foo {
   unsigned char X[5];
   int Y[5];
   int Z[5];
};

struct foo B;
unsigned char i;

void main (void){
   for ( i = 0; i < 5; ++i ) {
       B.X[i] = 4;
       B.Y[i] = 123;
   }
}
However, this code still has the problem that the 16-bit Y and Z need a multiplication by 2 to access them. Splitting them into separate byte-sized YLo, YHi, ZLo, ZHi members could generate more optimal code, but that in turn would complicate the actual use of those members (say, if you want to add or assign a value to "YLo, YHi").
Download STREEMERZ for NES from fauxgame.com! — Some other stuff I've done: fo.aspekt.fi
User avatar
dougeff
Posts: 3078
Joined: Fri May 08, 2015 7:17 pm

Re: NES Programming Blog

Post by dougeff »

Things I figured out this weekend, and will be corrected on my blog.

I wrote the code for most of the pages very quickly, and occasionally I would get error messages from the cc65 compiler. "converting pointer to int without a cast" "incompatible pointer type" etc...and I didn't know what caused them, but I slapped an (int) on there, and the error message went away. But, it didn't look right to me, and I could never find any example code that required type casting to fix error messages...so it bothered me a bit.

Well, the reason I never found example code to match what I was doing, was because I was doing things wrong. The ASM code was correct, so I assumed I had correctly addressed the issue, but once I saw the correct answer...I see that I hadn't.

example...

Code: Select all

const int AllBackgrounds[] = {(int) &n1,(int) &n2,(int) &n3,(int) &n4 };
I slapped some (int)'s on there because it gave me error messages...but what I really wanted was this...

Code: Select all

const unsigned char * const All_Backgrounds[]={n1,n2,n3,n4};
and the companion piece...

Code: Select all

UnRLE(BGDaddress);
I believe the error was that my prototype said this...

Code: Select all

void __fastcall__ UnRLE(int data);
and, what I really wanted was this...

Code: Select all

void __fastcall__ UnRLE(const unsigned char *data);
because, what I'm really doing with the code, is an array of constant pointers to an array of constant characters. And, what I'm really passing to the function is a pointer to an array.

This will be fixed soon on the blog example code.

Further, I don't think I've fully tested 'controller 2' input code. All my example code only tests 'controller 1'. I will have to do that as well.
EDIT, I tested it. Works fine.
Last edited by dougeff on Tue Sep 06, 2016 3:13 pm, edited 1 time in total.
nesdoug.com -- blog/tutorial on programming for the NES
User avatar
dougeff
Posts: 3078
Joined: Fri May 08, 2015 7:17 pm

Re: NES Programming Blog

Post by dougeff »

I've updated every example code on the blog. As usual, if anyone spots any outrageous bugs or bad programming practices, let me know. Thanks.

Here's a quick link to the Spacy Shooty source code...

http://dl.dropboxusercontent.com/s/70f8 ... Spacy4.zip
nesdoug.com -- blog/tutorial on programming for the NES
User avatar
dougeff
Posts: 3078
Joined: Fri May 08, 2015 7:17 pm

Re: NES Programming Blog

Post by dougeff »

Update (10-17-2016) I updated reset.s in every file, to make sure that initlib and copydata were included. Also changed, added Wait_Vblank(); to several files, just before rendering was turned on, to fix 1 frame of misaligned screens. Finally, changed the .cfg file on the MMC3 examples, to include the missing segments that I had deleted.

See here for further discussion on missing 'copydata'...causing errors.

viewtopic.php?f=10&t=14947
nesdoug.com -- blog/tutorial on programming for the NES
User avatar
dougeff
Posts: 3078
Joined: Fri May 08, 2015 7:17 pm

Re: NES Programming Blog

Post by dougeff »

Update Feb 9, 2017

I changed every .cfg file to include a "ONCE" segment, so it will compile with the latest version of cc65.

I added a makefile for Linux users, and people who prefer Gnu Make to .bat files. Well, Linux users will have to edit the makefile slightly. I originally wrote them on a Linux computer, but then brought them over to my Windows computer, and edited them to work there...
...anyway, Linux users will have to uncomment out the lines rm *.o and comment the lines del *.o. (etc for .nes files under CLEAN:)


UNRELATED SIDENOTE:
I wrote a 6502 disassembler in python. I might post it in a few weeks.
nesdoug.com -- blog/tutorial on programming for the NES
tepples
Posts: 22705
Joined: Sun Sep 19, 2004 11:12 pm
Location: NE Indiana, USA (NTSC)
Contact:

Re: NES Programming Blog

Post by tepples »

If you are using Make from MSYS, you'll probably have GNU Coreutils, which includes rm. For other things that tend to vary, such as presence or absence of .exe in the name of a native executable produced by the linker, you can use the presence or absence of environment variable COMSPEC to set makefile variables.

See Writing portable makefiles.
User avatar
DRW
Posts: 2225
Joined: Sat Sep 07, 2013 2:59 pm

Re: NES Programming Blog

Post by DRW »

thefox wrote:However, this code still has the problem that the 16-bit Y and Z need a multiplication by 2 to access them.
Isn't multiplication of 2, 4, 8, 16 etc. unproblematic since the compiler can turn it into a simple bit shift? So, an array of ints shouldn't be that much of an issue. At least it's not comparable to the access complexity of an array of a struct.
thefox wrote:Splitting them into separate byte-sized YLo, YHi, ZLo, ZHi members could generate more optimal code, but that in turn would complicate the actual use of those members (say, if you want to add or assign a value to "YLo, YHi").
Yeah, I would highly adivse against that. If you happen to need an integer in an NES game (which should be more the exception than the rule) let the compiler handle it. Don't fiddle around with two byte values if they are supposed to represent a single number.
My game "City Trouble":
Gameplay video: https://youtu.be/Eee0yurkIW4
Download (ROM, manual, artworks): http://www.denny-r-walter.de/city.html
User avatar
thefox
Posts: 3134
Joined: Mon Jan 03, 2005 10:36 am
Location: 🇫🇮
Contact:

Re: NES Programming Blog

Post by thefox »

DRW wrote:
thefox wrote:However, this code still has the problem that the 16-bit Y and Z need a multiplication by 2 to access them.
Isn't multiplication of 2, 4, 8, 16 etc. unproblematic since the compiler can turn it into a simple bit shift? So, an array of ints shouldn't be that much of an issue. At least it's not comparable to the access complexity of an array of a struct.
Yeah it's not a huge problem, but non-optimal nevertheless.
Download STREEMERZ for NES from fauxgame.com! — Some other stuff I've done: fo.aspekt.fi
User avatar
rainwarrior
Posts: 8731
Joined: Sun Jan 22, 2012 12:03 pm
Location: Canada
Contact:

Re: NES Programming Blog

Post by rainwarrior »

DRW wrote:
thefox wrote:However, this code still has the problem that the 16-bit Y and Z need a multiplication by 2 to access them.
Isn't multiplication of 2, 4, 8, 16 etc. unproblematic since the compiler can turn it into a simple bit shift? So, an array of ints shouldn't be that much of an issue. At least it's not comparable to the access complexity of an array of a struct.
It's not just a bit shift. If you can do an array access with an 8-bit index, it can just go into X or Y. If the index is wider, it can't do that anymore, and you get a 16-bit shift plus a 16-bit add operation on a temporary pointer, and then on top of that the array access becomes indirect.

Here's an example:

Code: Select all

unsigned char ac[35];
unsigned int  ai[35];

void index_test()
{
	static unsigned char i;

	// 1.
	i = index();
	ac[i] = 5; // 8-bit index on 8-bit array

	// 2.
	i = index();
	ac[i*2] = 6; // index is promoted to 16-bit int

	// 3.
	i = index() * 2;
	ac[i] = 7; // index was implicitly cast back to 8-bit before use

	// 4.
	i = index();
	ai[i] = 8; // index is promoted to 16-bit int by implicit mulitplication by 2
}
And the generated assembly:

Code: Select all

; 1.
; i = index();
	jsr     _index
	sta     L0017
; ac[i] = 5;
	ldy     L0017
	lda     #$05
	sta     _ac,y

; 2.
; i = index();
	jsr     _index
	sta     L0017
; ac[i*2] = 6;
	ldx     #$00
	lda     L0017
	asl     a
	bcc     L3763
	inx
	clc
L3763:	adc     #<(_ac)
	sta     ptr1
	txa
	adc     #>(_ac)
	sta     ptr1+1
	lda     #$06
	ldy     #$00
	sta     (ptr1),y

; 3.
; i = index() * 2;
	jsr     _index
	asl     a
	sta     L0017
; ac[i] = 7;
	ldy     L0017
	lda     #$07
	sta     _ac,y

; 4.
; i = index();
	jsr     _index
	sta     L0017
; ai[i] = 8;
	ldx     #$00
	lda     L0017
	asl     a
	bcc     L3764
	inx
	clc
L3764:	adc     #<(_ai)
	sta     ptr1
	txa
	adc     #>(_ai)
	sta     ptr1+1
	lda     #$08
	ldy     #$00
	sta     (ptr1),y
	iny
	lda     #$00
	sta     (ptr1),y
The difference between examples 2 and 3 especially shows how helpful it can be to undo integer promotion before accessing the array with it. With example 4, once you use arrays of 16-bit (or larger) types all indexed access becomes full 16-bit indirection, and you can't really do anything to stop that.

So... not as bad as a multiplication, but if you're looking to reduce some of your overhead, it's actually not a terrible idea to "manually" pack striped arrays. A syntax vs convenience tradeoff, though you could simplify the syntax with macros.
User avatar
DRW
Posts: 2225
Joined: Sat Sep 07, 2013 2:59 pm

Re: NES Programming Blog

Post by DRW »

O.k., yeah, that makes sense. Maybe I could have optimized some stuff with this knowledge in my game because the x position of each character was an integer.
(y was a byte because I had a status bar at the top, so I could simply declare that every sprite that has a position within the status bar is declared as out of screen and I didn't render these sprites at all.)
My game "City Trouble":
Gameplay video: https://youtu.be/Eee0yurkIW4
Download (ROM, manual, artworks): http://www.denny-r-walter.de/city.html
Post Reply