Page 6 of 7
Re: NES Programming Blog
Posted: Tue May 10, 2016 1:08 am
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.
Re: NES Programming Blog
Posted: Mon May 16, 2016 5:12 pm
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
Re: NES Programming Blog
Posted: Fri Aug 12, 2016 7:19 pm
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.
Re: NES Programming Blog
Posted: Sun Aug 14, 2016 4:41 am
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!

Re: NES Programming Blog
Posted: Sun Aug 14, 2016 8:10 pm
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.
Re: NES Programming Blog
Posted: Mon Aug 15, 2016 8:18 am
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").
Re: NES Programming Blog
Posted: Tue Sep 06, 2016 3:38 am
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...
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.
Re: NES Programming Blog
Posted: Tue Sep 06, 2016 2:53 pm
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
Re: NES Programming Blog
Posted: Mon Oct 17, 2016 3:47 pm
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
Re: NES Programming Blog
Posted: Thu Feb 09, 2017 2:46 pm
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.
Re: NES Programming Blog
Posted: Thu Feb 09, 2017 3:29 pm
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.
Re: NES Programming Blog
Posted: Fri Feb 10, 2017 6:31 am
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.
Re: NES Programming Blog
Posted: Fri Feb 10, 2017 10:19 am
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.
Re: NES Programming Blog
Posted: Fri Feb 10, 2017 10:57 am
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.
Re: NES Programming Blog
Posted: Sat Feb 11, 2017 3:22 am
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.)