Grauw’s RPG in development

Pagina 9/25
2 | 3 | 4 | 5 | 6 | 7 | 8 | | 10 | 11 | 12 | 13 | 14

Van sd_snatcher

Prophet (3332)

afbeelding van sd_snatcher

04-04-2018, 15:31

MOA wrote:

I did a test with a custom ISR, using im 2, to see what happens if ISR always makes sure s#2 is selected, as it reduces the VDP wait code to something much tighter:

; with our custom ISR, s#2 is always selected instead of s#0
WaitReady:
	in a,(VDP_PORT_1)
	rra
	jr c,WaitReady

It speeds things up quite a bit, so you can try it out

I used this approach in FireHawk HDD, and use it whenever I can to optimize the games I patch. A lot of CPU is wasted without this.

But in FireHawk I didn't need to use IM2, since it runs on MSX-DOS. I just had to capture the 0038h INT handler then.

But my tip is to avoid to hardcode the ISR to the status register-2 like you did. The best approach is to code the interrupt handler like this:

NEWINTDHL:
	push af,bc
	ld a,(RG15SA)
	ld bc,(VDP.DW)
	inc c
	ld b,a
	xor a
	ld (RG15SA),a
	out (c),a
	ld a,15|128
	out (c),a
	RST KEYINT
	di
	out (c),b
	ei
	out (c),a
	pop bc,af
	ret

This way the interrupt handler will be reentrant safe and won't conflict with other extensions present in the system.

But don't forget to update RG15SAV whenever you directly write to the VDP register 15. In fact, don't ever forget to update the respective status register variables in RAM whenever you write to *any* VDP registers without WRTVDP. Otherwise it might be a huge source of headaches and weird freezing when other extensions are present in the MSX.

Side comment: It's unbelievable that whoever wrote the MSX2 BIOS forgot to include exactly this kind of status register handling in the BIOS own interrupt handler. Either the guy was under a lot of pressure to deliver the project in short time, or he had absolutely no experience in writing proper interrupt handlers.

This code inside the BIOS would have saved a lot of programmer headaches, and would have allowed many newbie MSX programmers to easily create much more efficient blitter/raster effect routines on the MSX>=2.

Van Grauw

Ascended (9273)

afbeelding van Grauw

04-04-2018, 00:54

I thought about this, problem with:

	RST KEYINT
	di

Is that interrupts are enabled for a short while, enabling fast-arriving interrupts to saturate the stack space. Truth that, normally interrupts won’t occur so frequently that it would become a real problem, but we’re talking about other extensions present (let’s say, MIDI)… But I guess it’s acceptable, I’m hard pressed to think of a better alternative.

	ei
	out (c),a
	pop bc,af
	ret

Here, too, I would at least delay the ei until before the ret.

p.s. Pushed 36 new commits, now with joystick support, selecting display frequency with SELECT, quitting the game with ESC, fading, and weather control pressure plates in the level Big smile.

Van sd_snatcher

Prophet (3332)

afbeelding van sd_snatcher

04-04-2018, 01:07

Yes, this code is not optimized. It was just meant as an example.

I originally wrote the ei after the POPs, but went back and edited it out.

The case is that the interrupt handler will have to be tuned according to the scenario you need. If you really expect frequent interrupts, then its safer to make it a bit slower and place the EI before the RET to avoid stack trashing. But if you want to prioritize quick interrupt response (for screensplits, i.e.), it's best to enable the interrupts as quick as possible.

The scenario I imagined used a reentrant protection flag inside HKEYINT, so quick interrupts would be handled quickly without processing all the game interrupt code every time.

EDIT: Anyway, if interrupts are coming from the MIDI interface quicker than the interrupt handler can handle, there are many other places in the current BIOS interrupt handler path where it will crash anyway. The diskBIOS is one of them, not to mention the MSX-Audio BIOS and RS-232 BIOS.

Van Grauw

Ascended (9273)

afbeelding van Grauw

04-04-2018, 01:05

Yeah indeed you could add a flag to guard against stack problems, easy better alternative actually, a helpful suggestion Smile.

Note it’s only needed to call the BIOS ISR for vblank and unhandled interrupts, e.g. if you set a line interrupt you can return directly after processing it and bypass all this. So it’s an infrequent occurrence and doesn’t need to be particularly fast.

Van sd_snatcher

Prophet (3332)

afbeelding van sd_snatcher

04-04-2018, 01:14

Quote:

if you set a line interrupt you can return directly after processing it and bypass all this. So it’s an infrequent occurrence and doesn’t need to be particularly fast.

Yep! The FireHawk HDD interrupt handler does exactly that for line interrupts. Smile

But there's an advantage to call the DOS ISR: you get direct access to the BIOS from HKEYI/HTIMI for free. This way you can call everything you need from the BIOS from there once per frame (read joysticks or the keyboard, i.e.) without having to resort to many expensive CALSLT calls all the time. It saves a lot of CPU and is 100% compliant to the coding guidelines.

Van MOA

Champion (293)

afbeelding van MOA

04-04-2018, 04:13

Grauw wrote:
MOA wrote:

My final results (43+% free CPU time when scrolling diagonally):

Nice Smile. So let’s see if I got it right how you got there...

3% frame time from inlining stuff. Esp. in the sprite attribute update it seems there’s over a whole percent gained. I think it might be a good idea to start using macros for my getter functions.

2% frame time from copy wait loops defaulting to status register 2.

2% frame time from... other general optimisations. I especially see 1% gain in the player move update / collision handling. More inlining?

Mostly inlining, some unrolling and some optimizations using faster registers (ix and iy are easy to work with, but very slow), some optimizations related to multiply (if you store width and height as width * 2 and height * 2 you can simplify some stuff). It's probably easier to diff against my changes, so I will zip it up and send it to you if you can tell me where to send it to.

Grauw wrote:
MOA wrote:

The boost to idle mode is because you have a minor bug in your code: you still do tile collision testing when the player doesn't move and I fixed it in my local version.

Actually that’s intentional... I don’t optimise for those cases and just always run it, because it reduces the amount of variation in code paths, and I need to optimise for the worst case anyway. I prefer things to just always run so that I have a constant budget and the meters don’t jump so much. I might even start doing all the scrolling copies when idling!

Maybe at some point if I want to do some framerate-dependent things (like doing more tile animations if you’re idle) I would optimise those things, but for now I think it’s more beneficial to have the frame budget allocation be constant, so I can be sure there will be no frame drops.

I changed it because I was confused that walking against an object was cheaper CPU wise than not moving at all. Also it didn't incur an additional cost to the worst case code path, so why not choose the optimal path instead? When coding for a portable device like the Nintendo Switch, you want to save battery life whenever you can. When your computer is plugged into a socket it will still save some electricity and thus reduce your bill Smile

To illustrate:

ReadInput:
  ld a,(Input)
  or a
  ret z ; early exit... no input -> bad because it's reducing speed of worst case code path
 .
 .


ReadDirection:
  ld a,(Input)
  .
  .
NoInput:
  jr Continue ; bad also, because you know delta x and y are zero... I would jr to Skip instead

Continue:
  ; do calculations with delta x and delta y
Skip:
  ret

Van Grauw

Ascended (9273)

afbeelding van Grauw

04-04-2018, 10:06

Email is in my profile Smile.

Yeah the map size, I was thinking of either just hardcoding it to 64x64 or have only a few size options. But I'll wait deciding until I've actually gotten the scale right and made some maps.

Also for testing player collision, the cheapest way is to actually not calculate the tile position from x/y coordinates at all, I can just determine the tile data pointer of the character once at the start of the level (when teleporting), and then offset the position as the player moves about and test the surrounding tiles (inc / dec for x, add / sub width*2 for y). So maybe it’s not so bad to just keep the multiplication and size flexibility.

Van edoz

Prophet (2298)

afbeelding van edoz

04-04-2018, 10:07

This is a nice project! I like your graphics!

Van DarkSchneider

Paladin (938)

afbeelding van DarkSchneider

04-04-2018, 13:10

@sd_snatcher I see you are in my group, those who like to follow the standards. I also recommended to copy the VDP.RD and WR to somewhere in RAM and use them instead hardcoding the values.

About the IM2 ISR, for a MSX-DOS environment I think it is not safe to read S#0, because it will be reset and bypassing the system ISR is not recommended, 0038H should be called from the IM2 ISR. It is not the same to set S#2 at first into the ISR, and then do all the commands work with S#2 already set?

Van msd

Paragon (1397)

afbeelding van msd

04-04-2018, 13:32

Quote:

@sd_snatcher I see you are in my group, those who like to follow the standards. I also recommended to copy the VDP.RD and WR to somewhere in RAM and use them instead hardcoding the values.

IIRC on msx2+ (or was in turbo r) the ports for the VDP are standardized. (should verify this in the msx2+ standard). If this is the case you don't need to store the VDP ports in ram.

Pagina 9/25
2 | 3 | 4 | 5 | 6 | 7 | 8 | | 10 | 11 | 12 | 13 | 14