|
ForumsSega Master System / Mark III / Game GearSG-1000 / SC-3000 / SF-7000 / OMV |
Home - Forums - Games - Scans - Maps - Cheats - Credits Music - Videos - Development - Hacks - Translations - Homebrew |
Author | Message |
---|---|
|
When Changing Palette Colors Goes Wrong
Posted: Mon Sep 25, 2023 12:54 am
|
Hello again, folks!
I'm really getting back into stride with my project but I've come across a bizarre issue after assembling my latest code. On paper, I should have the same blue background I've always had and only my text should be flickering between a main color and a secondary color, doing this for 16 frames before shifting up another shade of gray until the text is eventually white. What actually happened when I loaded the ROM was a black background and my text going nuts with random colors, all while the game seems to be running at half speed. Below is a link to the code on github for those who wish to review all of the changes, but the main culprits I have my eye on for this are -IntroStory_AnimateRow -IntroStory_Frame and -DrawPalette IntroStory_AnimateRow:
ld a,$15 ;load dark gray as first intro color ld (intro_color),a ;store in RAM ld c,$00 ;C register acts as a frame counter --: ld (cur_pal+$04),a ;A register still has main color, so store it at text color index -: call IntroStory_Frame ;do a frame inc c ;and count another frame ld a,$0F ;check to see if we're on the 16th frame cp c jr nz,+ ;if not, alternate between main and sub color ld a,(intro_color) ;if we are on even 16th frame, brighten the main color ccf adc $15 ;by adding $15 ld (intro_color),a ;and storing this new color cp $54 ;then check to see if we're done. jr nc,-- ;If we aren't done, then continue the loop, otherwise ret ;exit +: ld a,c rra ;move the low bit of the frame counter into carry to check if even or odd jr nc,- ;if even frame, use main color next frame ld a,(cur_pal+$04) scf sbc $15 jr nz,++ ld a,$20 ++: ld (cur_pal+$04),a jr - IntroStory_Frame:
call WaitForVBlank call DrawPalette call PSGFrame ret and DrawPalette:
call PrepareBKGLoadPalette ld b,$0F jp DrawPalette_Norm DrawPalette_Norm: ld hl,cur_pal ld c,VDPData otir Hopefully someone with a keener eye than myself can spot what might be the cause of the slowdown and random colors flashing. github link: https://github.com/Cereza64/Final-Fantasy-SMS Brief note: While I was in the middle of typing this out, I realized that DrawPalette was actually overwriting my palette load from before, so for now I have a band-aid on the problem by just setting the first 4 cur_pal RAM addresses with blue...and come to think of it reloading the whole background palette every frame might be what's causing the slowdown too...I'll have to tweak that and see. |
|
|
Quick Follow-Up
Posted: Mon Sep 25, 2023 1:04 am
|
DrawPalette WAS in fact causing the slowdown. It may be a routine from the original game that I might have to cut out due to system differences.
I have since edited my code to only change the text color index instead of rewriting all of the color indexes every frame (what a stupid thing to do lol) |
|
|
Posted: Mon Sep 25, 2023 2:02 am |
Are you maybe missing some rets in your subroutines?
In particular in either DrawPalette or DrawPalette_Norm, but maybe in numerous other places? Without a ret more code than you anticipated will execute, which might be why you are apparently running into timing issues with functions that should run very quickly. |
|
|
Posted: Mon Sep 25, 2023 2:18 am |
I'm not missing any rets in the code which would do this. The slow runtime was an issue with writing 16 palette indexes over and over again each frame, an issue I've since fixed. However, I'm still trying to figure out why my colors seem to be random instead of dark gray & blue -> light gray & dark gray -> light gray & white |
|
|
Posted: Mon Sep 25, 2023 2:43 am |
So with the code that you have in github right now, how does execution proceed once the otir in DrawPalette_Norm has finished? It looks to me that it would just drop right through to whatever is next in memory, which may or may not be SetVDPAddress, depending on what the linker did.
You ought to be able to easily write 16 palette entries in vblank. If you really can't you must be super tight on timing anyway.
If there really is a problem with calling an unintended SetVDPAddress that could conceivably cause something like this. Sorry for the speculation, but I'm traveling atm so can't try running myself for a few days at least. I'd step through with a debugger though to see what's causing the problem. |
|
|
Posted: Mon Sep 25, 2023 2:53 am |
Good point. I'll take a look tomorrow and see if a ret would fix DrawPalette_Norm, but right now my code is not using it. I'll definitely take a look under the hood while the code is running to see why im getting random colors. Thanks for the suggestions and safe travels! |
|
|
Posted: Mon Sep 25, 2023 5:46 am |
Few mistakes (only check few lines)
No ret in drawpalette (which is called) = fall into setvdpaddress Remove JP (no utility) Push/pop af utility in vdp writing? Before any optimisation/factorisation, better to duplicate the code, test it step by step with a debbuger then optimise and retest. even more if you're not sure of how things works. |
|
|
Posted: Mon Sep 25, 2023 10:13 pm |
Good News!
You guys were absolutely right with missing a ret at the end of DrawPalette_Norm. I put that in and it worked perfectly (as far as that subroutine goes). As for everything else, I've been going through my code with a fine-tooth comb and managed to get everything nearly perfect. The code starts, flickers through the colors in order and gets right to the last frame aaaaaaand...ends on gray instead of white. Running through the debugger, I've isolated the issue to it being my frame counter off by 1. The original game will show dark gray text on frame 1 while my current code shows dark gray text at frame 2. I'm not exactly sure what is causing this delay and I'm hoping someone with better eyes than me can see what might be causing this problem, as the frame counter being off by 1 is why the text stops animating at the wrong point. I also updated my github with the latest version and I also update the changelog with each upload too so you can see what my current issues are. Thank you guys for the helpful tips! |
|
|
Changing just one row of text?
Posted: Thu Sep 28, 2023 3:15 am
|
I'm curious if anyone knows how to make only one row of text fade into view instead of all rows of text at once.
The original NES version uses the 3 different palettes available to swap between an "invisible" palette, a "fading in" palette, and a "full white" palette. I remember Phantasy Star's intro also has text that fades in row by row instead of all at once, so I at least know that what I'm hoping for is possible. Any help with this is greatly appreciated |
|
|
Posted: Thu Sep 28, 2023 10:11 am |
you might use the sprites palette for the BG to have a 'fade-in' transition using the same charset
so basically you draw the text twice, using the two palettes anything more than this would likely require two charsets - or to draw directly to tiles to create the transition on the fly |
|
|
Posted: Thu Sep 28, 2023 6:06 pm |
You could use line interrupts with a single palette change to fade text in line by line. It’s tricky though. For a pure text intro, using two font sets is easier. | |
|
Posted: Thu Sep 28, 2023 6:25 pm |
Would it be possible to perhaps have every line start at a particular palette index and then have each row change into the next index over on the palette for it to start animating? Is that what you mean by line interrupts with single palette change? I'm just not super sure multiple font sets is feasible with my current set up as id need 3 sets, two that wont be affected by the animating row (invisible and set at white) and then one more set for the animating row. |
|
|
Posted: Thu Sep 28, 2023 7:11 pm |
You wouldn't need "invisible" if you wrote to the tile map one row of text at a time. You need the timing to fade and hold rows of text in any case so might as well couple that to the writing each line of text to display. sverx's suggestion of using the upper palette to quickly switch between fadable and fixed colours is how I'd approach it too, simple enough solution I reckon. |
|
|
Posted: Fri Sep 29, 2023 3:44 pm |
P.S. did you ever figure out this out-by-one issue? I'm jetlagged to s**t right now and looking for some non safety-critical tasks to keep my mind from imploding in on itself so if you hadn't figured it out yet I will take a look. I tried doing it by eyeball on my phone a few days ago but couldn't quite manage it, but now I'm back I have the tools to help properly. |
|
|
Posted: Fri Sep 29, 2023 5:09 pm |
I did, and in fact I misidentified the issue. Turns out the only thing wrong was the fact that I did a jr on no carry set when i needed to jr on carry set. It just made it branch to the second color first instead of what I wanted. I'm trying to figure out the other stuff now. I made a second font set but I'm having trouble loading it in after the first set for some reason. Should just be as simple as calling a similar routine again starting at where the last one left off copying to VRAM, right? |
|
|
Posted: Fri Sep 29, 2023 5:46 pm |
Nice one! And fwiw I constantly get turned around by checking the carry flag, it's one of those things I've still not managed to cement properly in my muscle memory so have to think very hard about it still!
Yes should be. At the moment you have a routine called `LoadFont` that once completed should leave the VDP pointer after the last tile copied, so doing another VDP copy operation should append more data after that point – assuming that's actually where you want the next tileset to go. If not, you need to call `SetVDPAddress` with a new starting location of course. You should be able to see the result in the tile viewer in Emulicious. I was building up a few notes when I was trying to step through your routine before, but here I think I'd drip feed two things in particular: 1. I think you might be over-extracting routines in places. For instance you have a `PrepareLoadFont` which is actually just a set VDP address with the address hardcoded to $0000 and a `LoadFont` which is actually just a VDP copy with the source data hardcoded. You might be just as well to stick with using the already parameterised `SetVDPAddress` and `CopyToVDP` directly in your calling code. I think it would make it easier to read and also it would save some cycles. 2. Not super important, but while we're talking about your `SetVDPAddress` you might find that in many examples of others' code, setting the VDP address (or writing to the VDP control port) in particular is implemented as a fast reset vector, e.g. `rst $08`, etc. This is because it's used so frequently and often in moderately time critical situations and the `rst p` instructions are slightly quicker to execute than regular calls. I'd say it's also quite common to see set VDP address implemented as a macro alternatively. Take a look at SMSLib's CRT0 and you'll see that sverx has two reset vectors: one at $08 which copies HL to the VDP control port (just like your SetVDPAddress) and another at $18 which does the same for the data port. In his `rst $08` vector he clobbers the C register rather than pushing to the stack (for speed over safety) although if you didn't want to clobber A you could alternatively use ex af, af' which takes 4 t-states to execute as opposed to push af's 11 and pop af's 10. |
|
|
Posted: Fri Sep 29, 2023 6:06 pm |
This is definitely a good list of things to keep in mind. I'll have to take some time and optimize now since almost everything in my current code is serviceable. Assuming I can get the tiles to load, how about the stringmap? Should I make a new table that starts with 00 or do I continue where the first one left off? Just append more onto the old stringmap file? |
|
|
Posted: Fri Sep 29, 2023 7:01 pm |
Depends how you're implementing your second tileset really. I think you ought to be able to re-use the same stringmaptable if you are clever about the offsets you use for your second tileset. For example, it looks like you have somewhat less than 128 text tiles defined at the moment, so maybe you could load the second tileset in from 128 ($80) onwards and have your text display routine set bit 7 for each character. Alternatively setting bit 8 (bit 0 of the second byte of each tile reference) will give you tilesets which are 256 tiles big, but you might run into conflict with your sprite tiles that way. Or, as sverx suggested, setting bit 11 (bit 3 of the second byte) will reuse the existing tiles but with the upper palette so in that case no need even for a duplicate tilset. Alternatively you could of course duplicate the stringmaptable, but start it from a different offset. In that case you'd need to also duplicate the string definitions, referencing your new stringmaptable too, which will use up approximately twice as much ROM space for your string data, and feels to me like the wrong approach vs. either a mathematical relationships between your two tilesets or using the upper palette. |
|
|
Posted: Fri Sep 29, 2023 7:07 pm |
I completely forgot I could even set the tiles to use the upper palette just by setting the right bit lol. That sounds like a much better plan. Let's hope I can make that work. |
|
|
The Plan...
Posted: Mon Oct 02, 2023 8:19 pm
|
So I'm mildly stumped on how to properly accomplish the task I've set out to do, and I'd like to make sure that what I'm going to attempt will actually work before I try to no avail.
Right now I have the game drawing all of the text to the screen, which then cycles through colors to fade in all at once. In order to isolate the effect to one line at a time, I'd need to draw each line individually, then run the color cycling code, then put another layer of the same text over top that uses the sprite palette which does not get altered. The questions I have regarding execution are 1. What is the most efficient way to break down each line? Is it simply doing strings.intro1:
.stringmap FontIconsStringmap, "<5>The world is veiled in<br><br>" strings.intro2: .stringmap FontIconsStringmap, "<4>darkness. The wind stops,<br><br>" etc., or is there a better way of breaking the lines down so that they can be more efficiently drawn by the code (since drawing would need to happen in between vblank now) 2. If I can use the same font tileset and just set the sprite palette bit, how do I make sure that it sits on top of the animating text? Also, how would I go about setting this bit when I'm using a stringmap? I did move SetVDPAddress to $10 and change all "call Set VDPAddress" code to "rst $10" so at least that will be more efficient. |
|
|
Posted: Mon Oct 02, 2023 9:15 pm |
I’d make a label for each line of text, and then put all the labels in a table. That way the code has to work through the table to work through all the lines. I’d have a few RAM variables for the current table address and a counter, and maybe also put the VRAM write address before each text string.
If your font mapping is to bytes or words, either way you need to set the sprite palette bit at bit 11 of the word in VRAM. Just reuse the write address when you draw a second time. Top tip: if you moved SetVDPAddress to $10 then WLA DX will let you write “rst SetVDPAddress“ as well as “rst $10”. |
|
|
Posted: Tue Oct 03, 2023 5:52 am |
I guess an alternative way could be to use the delimiters within the text to control when the palette fade happens, either using the existing <br> delimiter in conjunction with a special routine, like "DrawTextWithPaletteFades", or with a new delimiter that triggers the palette fade for the line being drawn perhaps?
In either approach (this one or Maxim's) I think having a single routine which draws a line of text, then fades the palette, then redraws the same line but with the fixed palette would be a good building block. So in pseudocode in my approach, I'd maybe look to have something along the lines of: function DrawLineWithPaletteFades(ptr_line_of_text, starting_line): // draw the line with the fadeable palette WaitForVBlank(); SetVDPAddress($3800 + 64 * starting_line); var p = ptr_line_of_text; while (*p != END_OF_LINE_CHARACTER): WriteByteToVdp(*p); WriteByteToVdp(fadeable_palette_attribute); // e.g. bit 11 set p++; // delay if necessary DoPaletteFadingOnEntireScreen(); // delay if necessary // redraw the line with the fixed palette so that it doesn't fade next time WaitForVBlank(); SetVDPAddress($3800 + 64 * starting_line); var p = ptr_line_of_text; while (*p != END_OF_LINE_CHARACTER): WriteByteToVdp(*p); WriteByteToVdp(non_fadeable_palette_attribute); // e.g. bit 11 reset p++; // return the current text pointer so we could use it to draw more text return p; function DrawTextWithPaletteFades(ptr_text, len_text): var p = ptr_text; var line = 0; while (p < ptr_text + len_text): p = DrawLineWithPaletteFades(p, line); line += 2; // double spacing So the function DrawLineWithPaletteFades above is the main thing, and I would suggest is the approach needed either with the explicit way of drawing the lines one by one that Maxim suggests or with my second function which attempts to do it automatically based on a chunk of text to write to the screen. The second function DrawTextWithPaletteFades here just runs through a contiguous sequence of lines and draws them one at a time. I've handled the double line spacing by incrementing a line counter by 2, instead of with a <br><br> sequence, I had imagined replacing that with a single <br> instead (in retrospect, this separates content from layout somewhat so might be a good idea generally). |
|
|
Posted: Wed Oct 11, 2023 11:52 pm Last edited by badnest on Thu Oct 12, 2023 1:45 am; edited 1 time in total |
I'm gonna go for a different take on this -- I think the whole thing could be accomplished much easier with line interrupts, with the whole text always on the tilemap with the same color. Here's how I would do it:
In vblank, the text color is set to white and the line counter is setup to interrupt in the line just before the first line of text to receive the palette effect. In this first hblank interrupt, the text color is changed to one of the flashing colors, and the line counter is setup for another interrupt to occur after the line of text (~+16 raster lines). In this second hblank interrupt the text color is set to blue and the line counter is turned off. This repeats until the effect is done for the line of text, then a RAM variable gets changed so the vblank now sets up the first interrupt 16 raster lines further down. |
|
|
Posted: Thu Oct 12, 2023 12:03 am |
Sounds complicated but well worth trying out. I haven't worked with line interrupts at all though so I'm not sure how well I could implement them. I will try my best though! Thank you for the tip |
|
|
Posted: Thu Oct 12, 2023 6:35 am |
The idea isn't bad, but there's an issue: when the hblank interrupt gets fired, it's too late to set for the next interrupt to occur after a different amount of lines. So you would probably use it this way instead: set the interrupt counter to fire every 16 lines and in your handler check if it's time to switch to some color - or alternatively have an array of colors to apply (in RAM so that you can manipulate that when needed) and have the hblank interrupt handler apply a palette change using the next value in the array. |
|
|
Posted: Mon Oct 16, 2023 5:35 am |
You're right, my mistake. Though I've been experimenting with tricking the VDP by setting up the line counter one interrupt in advance. I've got it down to this: Vblank: Line counter is setup to interrupt every line. This will trigger line interrupts 1 and 2. 1st Hblank int (line 0): Text color is set to white, line counter is setup to trigger on the row before the effect, this will trigger interrupt 3. 2nd Hblank int (line 1): Text color is reduntantly set to white, line counter is setup to trigger the row after the effect, this will trigger interrupt 4. 3rd Hblank int: Text color is set to flashing effect color, line counter is disabled. 4th Hblank int: Text color is set to background color, line counter is redundantly disabled. I've attached a quick and dirty POC. The hblank routine is at $01C1. |
|
|
Posted: Mon Oct 16, 2023 7:40 am |
Yes, that's how you do it. Great work! :) | |
|
Posted: Thu Oct 19, 2023 11:06 pm |
any possibility you could include the source file as well? I'm still pretty new at coding in assembly here and the comments and breakdown of the code would help me tremendously in learning what all the moving parts are doing as well as what I need beyond the line interrupt handler to accomplish this for my own project. |
|
|
Posted: Fri Oct 20, 2023 1:50 am |
Like I said it's a quick and dirty POC. There's a small timing issue so noise appears at the top of the screen, and the source is not commented. But since you're interested I'll comment the relevant parts and upload a zip file when I get some time, probably within a couple days or so. | |
|
Posted: Fri Oct 20, 2023 2:52 am |
Thank you! Yeah, I've been flying by the seat of my pants with assembly coding a game. I've worked in C before but never to make games (at least none with graphics) so I'm also learning how to code for an entire system and play with the graphics. The will to succeed and put out a game is far stronger than the little voice who has doubts though. |
|
|
Posted: Sat Oct 21, 2023 5:51 pm |
There's nothing very relevant to the subject in main.asm, besides copying d_raster to ram so it can be altered. d_raster is the data that the hblank routine takes in, it's explained in data.asm. The real action is in blanking.asm.
|
|