Feature: In-Code strategies for handling dual port ram overflow

Started by jaymzjulian, October 28, 2019, 01:32:27 AM

Previous topic - Next topic

jaymzjulian

Some of this is kind of blue sky, so please feel free to tell me to go to hell on this one if I'm not understanding things well ;).  Also, some of this might be already possible now - I'm still learning this system, obv, so apologies if I've missed something bleedingly obvious :).

So I'm trying to develop a game with vaugely complex objects, and I end up hitting the display list overflow issue a lot, and I was thinking about ways this could be handled, and I came up with two seperate ideas - one that would be probably quite easy for your to implement on your side, if things work how I think they do, and one that would be actually reasonably difficult, but would mean people wouldn't have to handle it on the basic code side (For my part, I'm quite happy to handle it on the basic code side)

Part 1 of the simple implementation I was thinking of, is as a first step, instead of stopping the program simply just fail to draw the overflowed objects - this probably should be a settable flag.  I guess the mental model idea is kind of like when I overflow a sprite multiplexor on another platform, I simply lose the excess sprites, not drop the game ;).  My running theory is this would be a reasonably easy flag - in the case of some of my experiments, I can potentially then just choose to drop the Z distance and recover that way the next frame.

Part 2 of this is to expose enough flag to the basic side so that I can disable rendering those successfully rendered objects next frame, halving the "effective" render rate, but allowing me to display still an entire scene, even if there is flickering on more complex scenes.  (I already do this with my tron cycles experiment - because my cycle object is around 300 line segments, I actually use a single cycle object, and multiplex it between each of the "player" objects each frame.  It'd essentially be nice to have enough data coming back to be able to do this more intelligently... especially since I'd like to have 4, rather than 2, of them, and I think I can do it within the current limits most of the time some LOD stuff).

So, that's the "simple" version - I was thinking last night though about an idea for a more complex approch, which is consider the display list a "paged" entity - that is, store an entire larger display list on the vectrex32 side, and then feed the vectrex "pages" of this at a time - an initial simple implementation would be once per frame, which means the basic program is still responsible for setting a "reasonable" framerate for their object count, but you have "relatively" simple code that happens at the WaitForFrame that looks like this:


dualportram:
  byte semaphore_a
  byte semaphore_b
vec32ram:
  byte last_object_placed
  byte buffer_pos

# 6809 renders in a loop as it usually does, but incremenets its semaphore when its complete
# singlaing that the vec32 can now swap the display lists
6809:
  wait_until vec32_semaphore != 6809_semaphore
  reset_pen
  render_display_list
  6809_semaphore++

# vec32 sends the 6809 a page at a time of draw operations,
# until either all draws are sent, or the ram is full, in which case it continues from
# that point last time.  Note this _will_ cause a little overdraw when it drops back for one
# frame, but I think that will be visually okay!
vec32:
  wait_until vec32_semaphore == 6809_semaphore
  while (last_object_placed % object_list_length) != 0 and buffer_pos < max_buffer_len:
     add_command_to_buffer
     buffer_pos++
     last_object_played++
  inc vec32_semaphore

This way, users could just throw "as many" objects as they want at the system, and it would "handle" it just by adding more flicker, which I think is a reasonable sacrifice in this case, and certainly better than hard crash ;)

"Functionally", as an aside, this would be basically the same as what I'd be doing in the basic code, of course, but my _inclination_ is that it would acutally work much better on the c++ side rather than the basic side - but honestly, if you give me enough insight on the basic side to handle it there, I'll probably develop it into a library of sorts and share it over on the code swap forum....

Thanks for reading this either way - this is mostly just me spitballing, in any case, since my game is way off playable yet ;).

Vectrex32

Another post that I didn't see until today. :-( Sorry.

Regarding option 1 (not drawing the overflowed items): this is basically ignoring an error. My philosophy is that errors should blow up your program so that you notice them at development time. And even after your code ships, errors should "blow up" the program (assuming no lives are at stake) so that they are noticed. It's better than having erroneous output that the user assumes is correct. I think there's a reasonable argument for leaving ASSERT statements in your release code!

If your game doesn't fit into DPRAM, the best thing is to notice it and fix it before release. The second best thing is to hear about it after release from a player who knows what he saw and knows it was a bug.

Part 2 suggests that you would dynamically change the effective render rate. That's another thing that I'm uncomfortable about. It's better to understand the maximum load your program can produce, and have deterministic ways of handling it.

Your idea about paging the display list is very intriguing. I like it a lot. It would be a challenge to implement, so my question is, is it really necessary? If the DPRAM is full, isn't the Vectrex already drawing more than it can do in 1/30th second?

- Bob

jaymzjulian

Quote from: Vectrex32 on November 06, 2019, 08:13:00 PM
Your idea about paging the display list is very intriguing. I like it a lot. It would be a challenge to implement, so my question is, is it really necessary? If the DPRAM is full, isn't the Vectrex already drawing more than it can do in 1/30th second?

- Bob

So essentially my position is that I'm willing to take the hit to below 30fps in some cases in order to deal with a particularly busy scene.   

Part of the issue, to be honest, is I don't really have a deterministic way to work out how much I'm actually rendering - I actually have my code currently logging how many trail segments it's rendering before it crashes, and the answer is anywhere from 32 to 100, depending on where the camera is - i could actually implement a version of this better myself if I had some kind of function that says "is the display list i'm proposing too large" so that I can cull it back myself, since I know what the objects are ;).  Am I correct in thinking that the DPRAM copy only happens on WaitForFrame, in which case it might actually be possible to provide such a function, though it'd have to perform all of the transforms so might be a pretty serious performance hit?  I may well be wrong on this one ;).

The vaugely interesting part of this, is I actually have "important" and "unimportant" objects, and I'd be happy to drop the unimportant objects if the scene gets too busy - but what I don't really have, is a way to actually calculate what is "too busy".  currently, i'm solving this by interleaving the objects between frames - but that's obviously a pretty blunt hammer.  if I had a way to just stop rendering when DP ram is full, rather than crash out, I could just drop, say, the floor or the distant computer player bikes, when it can't be rendered (you don't need to see them if they're in the distance, you only _really_ need their trails!) just by ordering my display list such that this happens.  I can even do vaugely clever things like re-order it each frame, so that we get some flickering of less important objects instead of just invisible (though, they're not gameplay components, just decoration, so....).  This ends up being similar behaviour to sprite multiplexors on other platforms. 

So I'd argue that explicitly handling the drawing this way does not represent a bug - failing to draw an essential gameplay component does represent a bug, of course, though, so I'd still potentially be in trouble if i had dissapearing trails, but that's mitigatable as well by doing the dyamaic draw order changing.  Having unexpected conditions happen should absolutely blow up - but I argue that having an expected condition that I have a known handler for is no bad thing when dealing with unknown user inputs!

Vectrex32

So if I understand correctly, you want to be able to survive a DPRAM overflow so that you can rearrange things in the next frame to fix it.

OK. I'll give that some thought.

- Bob

jaymzjulian

Correct - even more ideal would be if I could re-arrange it _before_ it gets sent over, but a one frame glitch is survivable

EDIT: was almost thinking a function isCurrentListDisplayable(), returning true or false, and then I could cull objects until that returns true.  Then WaitForFrame() could still crash if that wasn't done, because things weren't handled, so that is still absolutely a bug!

Vectrex32

I'm implementing an ON ERROR CALL .... mechanism for this.

- Bob


jaymzjulian

One request - and i'll happily live without this, but you know, while you're doing the work ;) - it'd be nice if i could find out as well how far through the display list it go.  I can totally live without that if it's hard, but it's on my "would be very nice" list