It is currently Sun Jan 21, 2018 11:35 am

All times are UTC - 7 hours





Post new topic Reply to topic  [ 106 posts ]  Go to page Previous  1, 2, 3, 4, 5 ... 8  Next
Author Message
PostPosted: Tue Mar 24, 2015 5:40 pm 
Offline
User avatar

Joined: Mon Oct 06, 2014 12:37 am
Posts: 187
Tsutarja wrote:
Is there a syntax guide for NESASM somewhere? I haven't been able to find one. I feel like there might be some functions in NESASM that I don't know about.


Depending on where you downloaded NESASM, you may or may not have this text file, that's included.
I've attached it to this post, for your convenience. (You may need to open it in a programming utility.)


Attachments:
usage.txt [13 KiB]
Downloaded 63 times
Top
 Profile  
 
PostPosted: Mon Mar 30, 2015 12:53 am 
Offline
User avatar

Joined: Sun Oct 12, 2014 11:06 am
Posts: 123
Location: Finland
I've been trying to get the palette stuff working, but every time I come up with a idea on how to do it, I run into some problem that I can't solve... I may need some example code to further explain the whole fading thing.

_________________
UP SIDE DOWN A B A B B A B A Hidari migi
L R L R STOP & DASH & UP & TALK Ijou nashi


Top
 Profile  
 
PostPosted: Mon Mar 30, 2015 9:13 am 
Offline
User avatar

Joined: Sat Feb 12, 2005 9:43 pm
Posts: 10233
Location: Rio de Janeiro - Brazil
I just coded (which means I haven't tested it!) a sample fading system, which hopefully will give you some ideas. Fading is actually a sequence of events, and the first thing we need to take care of is the animation itself: Are we fading? In or out? Is it time to change the brightness? Here's the code that handles this part:

Code:
   lda #$10 ;assume we're fading in and the step is positive
   ldx CurrentBrightness ;compare the current brightness...
   cpx TargetBrightness ;...against the target brightness
   beq DoneFading ;skip fading if the target was already reached
   bmi UpdateDelay ;skip the next instruction if we're indeed fading in
   lda #$f0 ;oops, we're actually fading out, make the step negative
UpdateDelay:
   dec FadeDelay ;decrement the frame counter
   bne DoneFading ;skip fading if the delay isn't over yet
   clc ;prepare for addition
   adc CurrentBrightness ;change the brightness level...
   sta CurrentBrightness ;...according to the step value
   jsr GenerateModifiedPalette ;generete the modified palette
   lda #$04 ;reset the delay...
   sta FadeDelay ;...to the value of your choice
DoneFading:

The nice thing about this system is that all you need to do in order to fade is set a new brightness target. Every time you change the target, this code will detect that it's different from the current brightness and will automatically animate towards the target. Call this once per frame. Obviously, you have to initialize CurrentBrightness, TargetBrightness and FadeDelay to the correct values on power up, but after that you only need to change TargetBrightness whenever you need to fade.

Note that this also allows you to fade to white, not only black. Here are the common values you'll use for different brightness settings (current and target):

Code:
$C0 (i.e. -$40) - all black;
$00 - normal brightness;
$40 (i.e. +$40) - all white;

The next step is actually modifying the colors. The code above calls the "GenerateModifiedPalette" function, which is this:

Code:
GenerateModifiedPalette:
   lda BackgroundColor ;get the background color
   jsr ModifyColor ;modify its brightness...
   sta ModifiedBackgroundColor ;...and save
   ldx #23 ;repeat the following for the other 24 colors
ModifyPalette:
   lda Palette, x ;get the color
   jsr ModifyColor ;modify its brightness...
   sta ModifiedPalette, x ;...and save
   dex ;move on to the next color
   bpl ModifyPalette ;repeat if there are still colors left
   rts ;return

This is just a loop to modify all 25 colors. Storing the palette like this is a personal choice of mine, because I find it silly to use 32 bytes for colors when the NES can only display 25. You can obviously modify this if you prefer the other way. Anyway, the actual color modification happens here:

Code:
ModifyColor:
   clc ;prepare for addition
   adc CurrentBrightness ;change the brightness
   cmp #$0d ;compare the result against the stupid "forbidden color"
   beq UseBlack ;use black if the result is the "forbidden color"
   cmp #$c0 ;check if the result is too dark
   bcs UseBlack ;use black if the result is too dark
   cmp #$40 ;check if the result is too light
   bcs UseWhite ;use white if the result is too light
   rts ;return the modified color
UseBlack:
   lda #$0f ;use black
   rts ;return black
UseWhite:
   lda #$30 ;use white
   rts ;return white

This is all there's to it. All that's left now is to copy the processed palette to VRAM during VBlank, but I'm sure everyone knows how to do this, so I won't make this post any longer with something so trivial.

Note that this system is pretty versatile. Just as easily as you can fade to black, normal or white, you can fade to the intermediary steps. For example, you could use a target brightness of $E0 to make a room darker but not quite black, for when characters destroy light bulbs or something like that. Another possibility would be to set both CurrentBrightness and TargetBrightness (so that the change is instantaneous, without any animation) to $30, and manually call GenerateModifiedPalette to make everything very bright but not quite white, for effects like lightning storms.

I used the very simple concept of "subtracting or adding $10" to modify the colors, but you could still use the same overall idea with other types of color modification if you wanted. Personally, I prefer something smoother, but the vast majority of games appear to settle for this basic method.

EDIT: Crap, just noticed a mistake. In the first block of code I trashed the status flags when loading the default step value. I fixed it now. Hopefully I didn't make any more mistakes.

EDIT: Opps, another little mistake. I used bpl instead of bmi. Fixed it.

EDIT: Tested everything in the 6502 simulator and it appears to work fine now.


Top
 Profile  
 
PostPosted: Tue Mar 31, 2015 2:20 am 
Offline
User avatar

Joined: Sun Oct 12, 2014 11:06 am
Posts: 123
Location: Finland
Hmm... The fading itself works, but the colours and scrolling positions get messed up. I have no idea what causes this.

Code:
http://pastebin.com/uZVR2379

_________________
UP SIDE DOWN A B A B B A B A Hidari migi
L R L R STOP & DASH & UP & TALK Ijou nashi


Top
 Profile  
 
PostPosted: Tue Mar 31, 2015 7:25 am 
Offline
User avatar

Joined: Sat Feb 12, 2005 9:43 pm
Posts: 10233
Location: Rio de Janeiro - Brazil
The only thing obviously wrong I see is the way the palette is being copied to VRAM. It looks like you first copy the background color, and then copy the remaining colors in groups of 3, but incrementing X 4 times each iteration. You are compensating for the fact that you have only 25 colors instead of 32 the wrong way. You're skipping one entry in the array every 3, causing a misalignment with the hardware palette and not filling all of it. What I do in this case is write the background color again every 3 writes. Something like this:

Code:
LDA PPUStatus
 LDA #$3F
 STA PPUAddr
 LDA #$00
 STA PPUAddr
 LDX #$00
 LDY bg_colour_modified

NMIPalLoop:
 STY PPUData
 LDA palette_modified, x
 STA PPUData
 INX
 LDA palette_modified, x
 STA PPUData
 INX
 LDA palette_modified, x
 STA PPUData
 INX
 CPX #$18
 BNE NMIPalLoop
 LDA #$00
 STA <palette_update_flag


I didn't notice anything that could be affecting the scroll (see EDIT below). Let's see what happens when you fix this.

I did notice a few unnecessary things. For example, I don't see any need to initialize the modified palettes with the same colors as the unmodified palette, considering that in the beginning the modified palette will most likely be all black, so you're filling it just to have it overwritten with blacks soon after. It would make sense to manually call BrightnessModify as part of the palette initialization process though, to make sure that the correct palette will be properly sent to the PPU as soon as possible:

Code:
 ;initialize the brightness
 LDA #$C0
 STA <brightness_current
 LDA #$00
 STA <brightness_target

 ;initialize speed and delay
 LDA #$02
 STA <fade_speed
 STA <fade_delay

 ;generate the data for the VRAM update
 JSR BrightnessModify

 ;request the VRAM update
 LDA #$01
 STA <palette_update_flag

If you don't do something like this, you'll end up with an unitialized palette on screen for a few frames, instead of the first brightness level. After this, the system is ready to function on it's own.

BTW, making the delay configurable was a nice touch. Makes the fading system even more versatile! =)

EDIT: The "ScrollUpdate" is before the palette update. Setting the scroll should be the very last PPU operation in the NMI, because anything you do with $2006/$2007 will mess up the scroll. You're also missing a $2000 write to select which name table rendering should start in. This write should be near (before or after doesn't matter) the $2005 writes.


Top
 Profile  
 
PostPosted: Tue Mar 31, 2015 9:32 am 
Offline
User avatar

Joined: Sun Oct 12, 2014 11:06 am
Posts: 123
Location: Finland
Got that one working now. I'm not sure if the fade looks correct now (at least to me it does seem odd somehow)

https://www.youtube.com/watch?v=S28xVlw ... e=youtu.be

I probably should next make the background data compression/decompression system, graphics buffer and background update part for my NMI.

_________________
UP SIDE DOWN A B A B B A B A Hidari migi
L R L R STOP & DASH & UP & TALK Ijou nashi


Top
 Profile  
 
PostPosted: Tue Mar 31, 2015 9:53 am 
Offline
User avatar

Joined: Sat Feb 12, 2005 9:43 pm
Posts: 10233
Location: Rio de Janeiro - Brazil
Tsutarja wrote:
https://www.youtube.com/watch?v=S28xVlwj9Vc&feature=youtu.be

Looks good to me (especially combined with the wobbling effect!), except for the gray(?) screen at the very beginning. Did you do the initialization as I suggested, with forced palette generation and update?

Quote:
I probably should next make the background data compression/decompression system, graphics buffer and background update part for my NMI.

Are you going for a generic NMI that will be used throughout the whole program? Just keep an eye on how much CPU time you're spending on it. For larger data transfers, you should consider some degree of loop unrolling.


Top
 Profile  
 
PostPosted: Tue Mar 31, 2015 10:46 pm 
Offline
User avatar

Joined: Sun Oct 12, 2014 11:06 am
Posts: 123
Location: Finland
tokumaru wrote:
Looks good to me (especially combined with the wobbling effect!), except for the gray(?) screen at the very beginning. Did you do the initialization as I suggested, with forced palette generation and update?


Yeah I did try it, but I couldn't get it to work. I don't know whether its that I did it wrong, or that I don't do it early enough (that's why I posted from the beginning to the palette initialization)

Code:
RESET:
 SEI
 CLD
 LDX #$40
 STX $4017
 LDX #$FF
 TXS
 INX
 STX PPUCtrl         ; Disable NMI
 STX PPUMask         ; Disable rendering
 STX DCMIRQ         ; Disable DPCM

VBwait1:         ; First PPU warm up wait
 BIT PPUStatus
 BPL VBwait1

ClearMem:         ; Clear internal memory
 LDA #$FF
 STA $0200, x         ; Set OAM to #$FF to render sprites off screen
 LDA #$00
 STA $0000, x         ; Clear Zero Page
 STA $0100, x         ; Clear Stack
 STA $0300, x         ; Clear Sound Engine RAM
 STA $0400, x         ; Clear Graphics Buffer RAM
 STA $0500, x
 STA $0600, x
 STA $0700, x
 DEX
 CPX #$00
 BNE ClearMem

 LDA #LOW(BgTitle00)
 STA <pointerLo
 LDA #HIGH(BgTitle00)
 STA <pointerHi

VBwait2:         ; Second PPU warm up wait
 BIT PPUStatus
 BPL VBwait2

 LDX #$00
 LDA BgColour, x
 STA bg_colour_original

PaletteLoad:
 LDA Palette, x
 STA palette_original, x
 INX
 CPX #$17
 BNE PaletteLoad

 LDA PPUStatus
 LDA #$3F
 STA PPUAddr
 LDA #$00
 STA PPUAddr
 LDX #$00
 LDY bg_colour_original

PalInit:
 STY PPUData
 LDA palette_original, x
 STA PPUData
 INX
 LDA palette_original, x
 STA PPUData
 INX
 LDA palette_original, x
 STA PPUData
 INX
 CPX #$18
 BNE PalInit


tokumaru wrote:
Are you going for a generic NMI that will be used throughout the whole program? Just keep an eye on how much CPU time you're spending on it. For larger data transfers, you should consider some degree of loop unrolling.


Yeah, I will use the same NMI throughout the whole game. I will probably do a tiles to metatiles and metatiles to screens type of compression. That probably doesn't take that much time since many games use it (?). I probably don't need to update more than one or two vertical background rows at once (and that probably split up over few frames) plus few tiles caused by other objects. During transitions I'll probably just turn off the PPU and load the background during CPU time and turn on the PPU when done. I've read that some people do that, but I'm not sure if that's recommend or not.

_________________
UP SIDE DOWN A B A B B A B A Hidari migi
L R L R STOP & DASH & UP & TALK Ijou nashi


Top
 Profile  
 
PostPosted: Tue Mar 31, 2015 11:26 pm 
Offline
User avatar

Joined: Sun Sep 19, 2004 9:28 pm
Posts: 3195
Location: Mountain View, CA, USA
You're missing a bit PPUStatus prior to VBWait1. Reference: http://wiki.nesdev.com/w/index.php/PPU_power_up_state

Small optimisation: get rid of cpx #$00 -- it serves no purpose. dex will set the zero flag when the register/effective address value decremented becomes zero. 65xxx CPUs are fantastic for this sort of thing. :-)

Off-the-cuff guess: maybe you get a grey screen is because you're spending a lot of CPU time (memory initialisation, etc.) without having pre-set all palette entries screen to black (and nametable + attribute tables to something that will use such a palette, e.g. $00 or maybe $FF for everything). That's my guess anyway. Some emulators choose to show things as "grey" until that's done. You might try setting $2006 to $0000 and $2005 as well, to see if maybe that settles things a bit.

That said: I doubt this is the full program. I do not see anywhere where you enable background or sprites by adjusting PPUMASK, so I can't even tell what is going on between the point where you turn off the screen and where you start manipulating the palette in real-time.

It should be very easy to find out what is taking up all the time ("grey screen") using an emulator like FCEUX. Step through your code gradually piece by piece and you'll figure it out.


Top
 Profile  
 
PostPosted: Wed Apr 01, 2015 5:58 am 
Offline
User avatar

Joined: Sat Feb 12, 2005 9:43 pm
Posts: 10233
Location: Rio de Janeiro - Brazil
Tsutarja wrote:
Yeah I did try it, but I couldn't get it to work. I don't know whether its that I did it wrong, or that I don't do it early enough (that's why I posted from the beginning to the palette initialization)

Well, this code is not what I suggested at all. This is just uploading the original palette to the PPU. Why would you do this, if you want to start out all black? What I suggested was: copy the original palette from ROM to RAM (this part is OK), then initialize all fading variables (current brightness, target brightness, delay and speed) then call BrightnessModify to force the generation of the processed palette and finally set the flag that requests a palette update, to force the modified palette to be uploaded the next VBlank.

These last 2 operations have to be forced because normally they only happen when the delay expires and the system advances to the next brightness level, but you need to get the very first palette up somehow. The alternative would be to initialize the delay to 1 instead of the full value, and call BrightnessControl, so the delay expires immediately (triggering the generation of the modified palette and the request for an update). For that to work you'd also have to initialize the current brightness to one level before than what you really want to start with (in this case that would be $b0), otherwise you'll never see the first palette.

Quote:
I will probably do a tiles to metatiles and metatiles to screens type of compression. That probably doesn't take that much time since many games use it (?).

Depending on the size of your metatiles, this could still be a lot of data. If they are the common 16x16-pixel kind, that means each screen will take 240 bytes. An 8KB PRG-ROM bank would only be able to hold 34 such screens. Larger metatiles, such as the ones used in Mega Man (32x32 pixels), are much better in terms of compression. There's always metatiles of metatiles, but the complexity this adds to map decoding sometimes scares people off!

Quote:
During transitions I'll probably just turn off the PPU and load the background during CPU time and turn on the PPU when done. I've read that some people do that, but I'm not sure if that's recommend or not.

That's OK as long as the NMI handler is well structured enough to not get in the way of big updates. It's not advisable to turn NMIs off, mainly because of music, so the NMI handler should be able to detect that it interrupted a large PPU operation and that's it's not supposed to touch the PPU at all in that case.


Top
 Profile  
 
PostPosted: Wed Apr 01, 2015 11:37 pm 
Offline
User avatar

Joined: Sun Oct 12, 2014 11:06 am
Posts: 123
Location: Finland
Okay, I managed to get the "grey screen time" at the startup down to 2 frames, which seems to be what many games have.

tokumaru wrote:
There's always metatiles of metatiles, but the complexity this adds to map decoding sometimes scares people off!


Another question is: How much more time does it cost to decode over just one metatile compression, and is it worth it? I think there was a article about data compression and data buffering somewhere, but I can't remember where I found it.

By the way, how are attributes stored? Are they in the background data or are they in a separate table?

_________________
UP SIDE DOWN A B A B B A B A Hidari migi
L R L R STOP & DASH & UP & TALK Ijou nashi


Top
 Profile  
 
PostPosted: Thu Apr 02, 2015 7:25 am 
Offline
User avatar

Joined: Sat Feb 12, 2005 9:43 pm
Posts: 10233
Location: Rio de Janeiro - Brazil
Tsutarja wrote:
Okay, I managed to get the "grey screen time" at the startup down to 2 frames, which seems to be what many games have.

That's probably because of the VBlanks we're required to wait so the PPU can warm up. What you could do is separate the gray frames and the fade in with a few intentional black frames. Half a second (30 frames), maybe. That should help players dissociate the initial flash from the actual fading effect.

Quote:
Another question is: How much more time does it cost to decode over just one metatile compression, and is it worth it?

That depends entirely on how you're doing it. The absolute simplest way would be to decode it all to RAM beforehand, in which case speed would be completely irrelevant. This is only an option if you have extra RAM in the cartridge, though.

If you're decoding the data in real-time, then the amount of optimization in the code will make a big difference. Unrolled code would probably be fast enough to use in real-time without problems.

I think this is something you're gonna have to do the math and decide what the best course of action is yourself. Are your levels really big? If not, it might make more sense to use more PRG-ROM and keep the data formats simple.

Quote:
By the way, how are attributes stored? Are they in the background data or are they in a separate table?

This is completely up to you, but to me it makes sense to keep palette attributes as part of the metatile, along with collision information and all other attributes.


Top
 Profile  
 
PostPosted: Sun Apr 05, 2015 4:52 am 
Offline
User avatar

Joined: Sun Oct 12, 2014 11:06 am
Posts: 123
Location: Finland
I think I have now come up with a decent idea for a graphics buffer.
Every time data is written to the buffer, bg_update_flag will be incremented. Data is written in "length, start address, data" order. Every time a byte is written to the buffer, buffer_offset will be incremented. During NMI bg_update_flag is compared to #$00. If true, graphics updates are skipped. If false, data buffer will be read. Byte indicating length is copied to RAM and decremented after every write to the PPU. At the same time buffer_offset_nmi is incremented. When length reaches zero, bg_update_flag is decremented and compared to #$00. Reading the buffer will continue until bg_update_flag reaches zero. At this point both buffer offsets are reset to zero and graphics updates end.

Do you think that this would be a good way to do this? I haven't thought of the data compression, metatiles, etc. much yet, since the data is uncompressed in the buffer anyway. I will get to that once the buffer itself works.

_________________
UP SIDE DOWN A B A B B A B A Hidari migi
L R L R STOP & DASH & UP & TALK Ijou nashi


Top
 Profile  
 
PostPosted: Sun Apr 05, 2015 8:22 am 
Offline
User avatar

Joined: Sat Feb 12, 2005 9:43 pm
Posts: 10233
Location: Rio de Janeiro - Brazil
Tsutarja wrote:
Do you think that this would be a good way to do this?

Sounds like a good system, just a bit slow. If the data transfer loop is like this:

Code:
CopyByte:
  lda Buffer, x ;4 cycles
  sta $2007 ;4 cycles
  dex ;2 cycles
  bne CopyByte ;3 cycles

...it will take 13 CPU cycles to copy each byte. If you were doing only this during VBlank, you'd be able to transfer about 170 bytes. Since this is NOT the only thing you'll be doing (there's aways the sprite DMA, setting the scroll and other tasks), you can probably transfer around 100 bytes per VBlank. If that's enough for you (and it should be unless you're scrolling at ridiculous speeds or animating CHR-RAM), no worries. Just keep in mind that you have to store data in the buffer backwards so you can count down and index data using the same register, otherwise the copy loop would be slower than 13 cycles per iteration.

If you are however animating CHR-RAM, then that's definitely too slow, since 100 bytes are less than 7 tiles. In this case you'd need more advanced unrolling techniques.

Quote:
I haven't thought of the data compression, metatiles, etc. much yet, since the data is uncompressed in the buffer anyway. I will get to that once the buffer itself works.

Yeah, ideally these things are completely separate. As long as the decompression routines output data in the format you've chosen for the buffers, everything will connect just fine.


Top
 Profile  
 
PostPosted: Sun Apr 05, 2015 9:25 am 
Offline
User avatar

Joined: Sun Oct 12, 2014 11:06 am
Posts: 123
Location: Finland
If it's not too complex, I could try to use faster method for reading the buffer. I don't know if I'm going to animate CHR-RAM, but I guess I should make the code fast enough in case I need to use it.

_________________
UP SIDE DOWN A B A B B A B A Hidari migi
L R L R STOP & DASH & UP & TALK Ijou nashi


Top
 Profile  
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 106 posts ]  Go to page Previous  1, 2, 3, 4, 5 ... 8  Next

All times are UTC - 7 hours


Who is online

Users browsing this forum: No registered users and 1 guest


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to:  
Powered by phpBB® Forum Software © phpBB Group