Infinite "hall-o-mirrors" effect with scaled screen

So, in an attempt to shrink my entire 1280x720 game screen 10%, I tried to scale it with DrawScaledObject(), like so:

screen.DrawScaledObject(64, 36, 0.9, 0.9, screen)
screen.SwapBuffers()

But when I play I get just the outside ring of the screen infinitely repeated down to a tiny center point. It reacts to me changing things at the edges, they quickly propagate down into the center, a million 10% shrunken screens, all piled on top of each other.

Why??? What’s really going on? How can I get what I want?

You can’t draw a bitmap/screen onto itself. If you want to scale down, then you’d need to first draw to a bitmap, then draw that scaled to the screen.

you may want to try creating a bitmap the size of your scale, create a screen region and scale that into the bitmap then write the bitmap to the screen. Using the same object as source and destination has mixed results but Can be useful to do some single step fade outs

I’m not sure why you thought this would work other than an intense desire to avoid having to re-locate everything( understandable).
It would only work if you could write the screen to a bitmap, clear the screen and then draw the bitmap at 90%. You might get 3FPS on a Roku3.
On a 2XS? fergetaboutit.

“TheEndless” wrote:
You can’t draw a bitmap/screen onto itself. If you want to scale down, then you’d need to first draw to a bitmap, then draw that scaled to the screen.Sure you can :face_with_tongue: - although i won’t consider the behavior guaranteed across models.
It’s pretty much what you get when connecting camcorder live to a TV and then point the camera at the screen: https://www.youtube.com/watch?v=Y-gqMTt3IUg
(@squirreltown - there be some lemonade in that?)

Maybe something can be done by “cutting” piece of roScreen with roRegion and do most all drawing to that roRegion instead of the screen? I have never used it - but drawing to a region directly affects the parent bitmap, no?

Alright, I added a bitmap just after creating my screen, called “sizer”, and set all my regions to draw to sizer instead of to screen.
I also added a boolean flag called “shrink” to my global system “sy” variables, which can be changed in the game settings.
Then, just before screen.SwapBuffers() I have:

	IF m.sy.shrink THEN screen.DrawScaledObject(128, 72, 0.8, 0.8, sizer) ELSE screen.DrawObject(0, 0, sizer)
	screen.SwapBuffers()

And it works great, exactly as wanted! :grinning_face_with_smiling_eyes:

My plan is to offer this for those 17 people out there who play on an old CRT and have too much overscan and can’t see the edges of the game which are sometimes important, rather than never being able to use the outside areas for needful things.

“Komag” wrote:
Alright, I added a bitmap just after creating my screen, called “sizer”, and set all my regions to draw to sizer instead of to screen.
I also added a boolean flag called “shrink” to my global system “sy” variables, which can be changed in the game settings.
Then, just before screen.SwapBuffers() I have:

	IF m.sy.shrink THEN screen.DrawScaledObject(128, 72, 0.8, 0.8, sizer) ELSE screen.DrawObject(0, 0, sizer)
	screen.SwapBuffers()

And it works great, exactly as wanted! :grinning_face_with_smiling_eyes:

My plan is to offer this for those 17 people out there who play on an old CRT and have too much overscan and can’t see the edges of the game which are sometimes important, rather than never being able to use the outside areas for needful things.
I’m sure you’ve already noticed, but drawing to a bitmap first, then to the screen will effectively cut your framerate in half. If you’re already being governed down to 60fps anyway by SwapBuffers, then it shouldn’t be an issue, but worth noting, especially on the slower boxes (I’m looking at you Roku 1).

No, I haven’t noticed that, but perhaps there is some performance impact and I’m just not seeing it. How can I measure it? The way my cycle works it doesn’t seem like it would cut the framerate in half, I still have do drawAll() on the compositors and SwapBuffers() on the screen regardless. I may be making a newbie mistake in my understanding of how it all works though.

“Komag” wrote:
No, I haven’t noticed that, but perhaps there is some performance impact and I’m just not seeing it. How can I measure it? The way my cycle works it doesn’t seem like it would cut the framerate in half, I still have do drawAll() on the compositors and SwapBuffers() on the screen regardless. I may be making a newbie mistake in my understanding of how it all works though.
If you haven’t noticed an impact, then it doesn’t really matter. I monitor my framerate by counting the passes through my event loop and outputting the number of frames every seconds.. something along the lines of…

fpsTimer = CreateObject("roTimespan")
frames = 0
While True
    ' increment the frame counter
    frames = frames + 1

    ' Do screen updates
    screen.SwapBuffers()

    If fpsTimer.TotalMilliseconds() >= 1000 Then
        ? "FPS:"; frames
        ' Reset the timer and frame counter
        fpsTimer.Mark()
        frames = 0
    End If
End While

DISCLAIMER: If you have any level of OCD, doing this is probably a bad idea. I regularly find myself getting distracted trying to get the framerate up one or two points.

“EnTerr” wrote:
Maybe something can be done by “cutting” piece of roScreen with roRegion and do most all drawing to that roRegion instead of the screen? I have never used it - but drawing to a region directly affects the parent bitmap, no?
@TheEndless - whatever i said, was that crazy talk? Inquiring mind wants to know

“Komag” wrote:
My plan is to offer this for those 17 people out there who play on an old CRT and have too much overscan and can’t see the edges of the game which are sometimes important, rather than never being able to use the outside areas for needful things.
17 is the most random number but i think there are slightly more TVs affected by overscan. My guess is that something like 95% of all sets have overscan=on and you should be prepared of losing 5% on each side almost everywhere. Overscan is the default setting for all TVs for “hysterical” reasons. Theoretically that can be turned off for many TVs but only the most avid gamers know about that setting. Not to mention that (older?!) 720p TVs don’t have that. And that doing that on some Samsungs is so hacky, i could not believe when i read it first in forums - i was sure it’s an urban legend (works only with HDMI 1 and you have to rename the port to “PC”, which has magic effect).

“EnTerr” wrote:

“EnTerr” wrote:
Maybe something can be done by “cutting” piece of roScreen with roRegion and do most all drawing to that roRegion instead of the screen? I have never used it - but drawing to a region directly affects the parent bitmap, no?
@TheEndless - whatever i said, was that crazy talk? Inquiring mind wants to know
A region effectively just offsets your x and y and clips draws to the width and height of the region. Otherwise it’s identical to drawing directly to the bitmap/screen. Using a region of roScreen would result in the same “hall-o-mirrors” effect.

“TheEndless” wrote:
A region effectively just offsets your x and y and clips draws to the width and height of the region. Otherwise it’s identical to drawing directly to the bitmap/screen. Using a region of roScreen would result in the same “hall-o-mirrors” effect.
The point is to address the overscan issue at large, not the infini-mirror symptom. Avoid drawing to a bitmap first and then copy to screen. I was thinking


scr = createObject("roScreen", ...)
if should_compensate_for_overscan:
    'trim 5% on each side
    scr = createObject("roRegion", scr, 1280 * 0.05, 720 * 0.05, 1280 * 0.90, 720 * 0.90) 
end if

Now that takes care of the translation and i don’t know if roRegion has any built-in means for scaling when drawn to?
Alternatively, is DrawScaledObject(x, y, 1, 1, bmp) notably slower than DrawObject(x, y, bmp) (i.e. if DrawScaledObject is optimized not to do any math for 1:1 “scaling”… i think you know where i am heading with that) ?

Actually I already basically do that EnTerr. From the beginning I’ve drawn everything to a region called mainR (which goes to screen), which is a few pixels smaller than screen, for just that reason. But there is no scaling there, I just have mainR that is 1216x704 rather than 1280x720. My game is a grid of 64x64 squares, so within 1216x704 I get 19 x 11 squares, and everything I’ve done for the past 8 months is based on that setup.

(I’m not aware of any other scaling than DrawScaledObject().)

And, while implementing early this morning, I thought about testing DrawScaledObject(x, y, 1, 1, bmp) vs DrawObject(x, y, bmp), as it would make the code a bit more straightforward if they perform the same since we could simply adjust the scale variables according to need.

With HTDV overscan, which is only a little bit, my game works fine. But now that I’ve tested on a couple old CRTs that have really horrendous overscan, I can’t even see most of the outside squares in the game! I may decide to screw it and ignore CRT users, but if I can do this with minimal/no performance impact, I’d like to be accommodating. Since I don’t have to support classic Rokus, the bottom of the barrel for performance now is my Roku HD 2500, followed closely by my more recent Roku 1.

“EnTerr” wrote:

“TheEndless” wrote:
A region effectively just offsets your x and y and clips draws to the width and height of the region. Otherwise it’s identical to drawing directly to the bitmap/screen. Using a region of roScreen would result in the same “hall-o-mirrors” effect.
The point is to address the overscan issue at large, not the infini-mirror symptom. Avoid drawing to a bitmap first and then copy to screen. I was thinking


scr = createObject("roScreen", ...)
if should_compensate_for_overscan:
    'trim 5% on each side
    scr = createObject("roRegion", scr, 1280 * 0.05, 720 * 0.05, 1280 * 0.90, 720 * 0.90) 
end if

Now that takes care of the translation and i don’t know if roRegion has any built-in means for scaling when drawn to?
This would give you an overscan adjusted region to draw to, but aside from giving you a target, it doesn’t help with scaling. Going this route would require scaling all of the bitmaps either before or while drawing to the region. Since it sounds like Komag is using the compositor, that would cause a real headache, as it would require scaling the sprites when you create them. If he can afford the performance hit from drawing to a full screen bitmap and scaling that down instead, that’s a far easier route to take.

“EnTerr” wrote:
Alternatively, is DrawScaledObject(x, y, 1, 1, bmp) notably slower than DrawObject(x, y, bmp) (i.e. if DrawScaledObject is optimized not to do any math for 1:1 “scaling”… i think you know where i am heading with that) ?
I haven’t tested this in a long time, but my framework code explicitly checks for a 1.0 scaling factor, so I’m guessing I saw a significant enough difference between DrawObject and DrawScaledObject, even at 1:1, that I optimized it in my code.

“TheEndless” wrote:
This would give you an overscan adjusted region to draw to, but aside from giving you a target, it doesn’t help with scaling. Going this route would require scaling all of the bitmaps either before or while drawing to the region. Since it sounds like Komag is using the compositor, that would cause a real headache, as it would require scaling the sprites when you create them. If he can afford the performance hit from drawing to a full screen bitmap and scaling that down instead, that’s a far easier route to take.
If we are in agreement that the prevailing number of TVs overscan, then it makes sense to (re)design the game so that all imagery happens in the safe region between (64, 36) and (1152, 648) and leave 5% fringes empty (black or some decorative frame).

In other words, since overscan is the dominant mode - target that from the get-go and handle non-overscan (full resolution) as the exception.

Have you actually measured how much overscan HDTVs do? From what I’ve measured it’s very minor compared to CRTs, not as much as Roku says for the “safe zone” - I think they’re just being conservative.

I tried the FPS code, I like it.

And I just got a used Roku 3 ($50), HOLY CRAP it’s fast!!! All this time trudging along working with classic Rokus and other models trying to squeeze performance. I just compared my Roku HD 2500 - first room in game I get around 4000 fps, Roku 3 gets f-r-i-c-k-i-n 80,000 fps!!! I piled on as much activity as I could in the game and it never dipped below 50,000, far above a drawable 60 fps in-game with lots of room to spare. :mrgreen:

Yes, i did some research for “Tangrams” earlier:

  • What i measured was <5%.

  • Roku RTFM recommends allowing 5% edges for “action safe” zone and 10% for “title safe”.

  • FireTV UX guidelines say 90% safe zone, 5% margins.

  • Android TV docs are confused, in one place showing 5% margins and right after that speaking in pixels give vertical margin minimum at 27px/1080 = 2.5%. In another place, “10% margin on all sides of your layout. This translates into … a 27dp margin on the top and bottom” - where “dp” is “density-independent pixels” Android PITA, which after some research translates to vertical margin 36px on 720p TV (=5%).

  • Wikipedia discussed the lack of standard

After weighing in all this, i settled on 5% per side.

Re FPS, your numbers can’t be right. You can’t get >60 FPS on Roku… ever.

“Komag” wrote:
And I just got a used Roku 3 ($50), HOLY CRAP it’s fast!!! All this time trudging along working with classic Rokus and other models trying to squeeze performance. I just compared my Roku HD 2500 - first room in game I get around 4000 fps, Roku 3 gets f-r-i-c-k-i-n 80,000 fps!!! I piled on as much activity as I could in the game and it never dipped below 50,000, far above a drawable 60 fps in-game with lots of room to spare. :mrgreen:
Sounds like you didn’t implement it quite right. As EnTerr noted, it’s impossible to get more than 60 fps on any Roku. SwapBuffers() requires 16ms as a minimum.

“EnTerr” wrote:
In other words, since overscan is the dominant mode - target that from the get-go and handle non-overscan (full resolution) as the exception.
Agreed, but if he’s already coded the majority of his game, then that sounds like a v2 optimization. Drawing to an roBitmap and scaling that also gives him the ability to code in a user configurable overscan adjustment.

“EnTerr” wrote:
FireTV UX guidelines say 90% safe zone, 5% margins.
Interestingly, the FireTV has an overscan adjustment built in via the Display->Calibrate Display setting.

“EnTerr” wrote:
Android TV docs are confused
Perhaps even more interestingly, Android TV doesn’t even provide an option for setting the display resolution, let alone adjust for overscan.

Oh I know drawable 60fps is the max that can be displayed. I should call these “cycles” rather than frames. My program uses a tick counter, and essentially what’s happening is that it ticks up to 24ms, then does “everything” including swap buffers, resetting the tick counter. So Roku 3 is mostly counting 80,000 cycles in-between frames, around 18 cycles per ms I counted.

My “everything” takes about 7ms on Roku 3 (so it misses counting cycles for that long), about 20ms on Roku 2 XS, and longer on weaker boxes. If it takes longer than 24ms, the game will appear slow, but can be run on 2x speed where it waits till 48ms and moves everything twice as far instead. There is also a 4x speed that waits till 96ms and moves everything 4x as far, so the slowest Rokus can still keep up with overall game speed.

I programmed the whole system before I understood anything about easing functions or even heard of them, and a lot of the system designs rely on the tick counting mechanism for proper timing and interaction. I’ve entertained the idea of going back and re-factoring almost everything, but the prospect makes me shudder, and it works quite well as-is.

After some more testing, I’ve discovered that drawing everything to a bitmap and then scaling that (or even just drawing that without any scaling) to the screen was a pretty big performance hit on anything other than Roku 3.

It varied a lot between different models, but in all cases it’s much better to just draw to screen normally, sometimes about 10% difference, other times as much as double (or 50%, depending on which way you’re comparing!).