Display every other line of an image

Hi,

is there a version of image:draw() that only renders every other line of an image? I couldn't see anything listed in the sdk docs, but it sounds like one of those graphical tasks that would be easy to support and have a performance improvement over doing it procedurally.

Yes, you can do that in several ways.

image.drawFaded will do it if you specify kDitherTypeHorizontalLine (or vertical) and 50% alpha.

I often find it faster (if that matters) to draw the image with a stencil—and the stencil can be an 8x8 bit pattern instead of an actual image:

graphics.setStencilPattern

This pattern should give you alternating lines...

{ 0xff, 0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0x00 } 

You just set the stencil before you draw, then disable it afterwards (using setColor). So only two added lines of code in addition to your draw code.

Lastly, you can define a dither pattern for drawing, which I think (it's late!) performs well like Stencil, better than drawFaded:

graphics.setDitherPattern

Specify kDitherTypeHorizontalLine and 50% alpha. Then, to turn the dither back off, you can set to kDitherTypeNone. So two added lines of code, same as using a stencil.

Also note—if you're using sprites, a sprite can have a stencilPattern (or stencilImage) just like a draw can:

sprite.setStencilPattern

3 Likes

Hi AdamsImmersive,

thanks for the ideas. These all seemed to work well, though they hit my fps a little too hard.

I think maybe I didn't explain the question properly. I wasn't looking for the graphical effect of drawing every other line and having every other line as a full transparency, what I was after was a routine that literally only draws every other line, like interlacing back in the old days. I'd expect such a routine to be faster than image:draw as it's only rendering every other line.

I think all your solutions depend on selectively applying transparency whilst fully drawing the image.

I find I can stencil the entire screen while keeping a decent framerate, so alternating should be workable--and the stencilPattern method would facilitate that (just reverse the hex items in the table). BUT of course it's cumulative with everything else your'e doing.

And I'm still not sure of your intent: if you want a full-speed interlace flicker, that would be 50 to 60 fps (PAL or NTSC) and that seems unlikely to be reliable, especially since any variation would be super noticeable as a "hitch" in the flicker. C may be the best bet for extra performance if you hit a wall.

I don't think the system is wasting time "drawing" the missing pixels, although someone at Panic would know for sure. Good luck!

If you just want the whole game—every element—to never draw every other line, be warned that this would cut the game's contrast in half. But if that's the intent, I would try stenciling (or dithering) a black fill over every frame, and not address the scanlines at all anywhere else. That WOULD be drawing the missing pixels—but if it happened to test out at good performance anyway, that would be great.

Thanks, I'll try some fps saving shenanigans. Your examples above certainly gave some great results, very interesting ways to add shading to images :slight_smile:

1 Like

Looking forward to what you come up with!

FWIW, if you're not using every other line anyway, you could consider rendering everything as 2x2 chunky pixels, which I think would boost performance. 25% as many pixels, on a 200x120 screen. That's setScale(2):

display.setScale

Not sure if this helps your situation, but the screen refreshes only the rows containing dirty pixels.

~75 dirty rows = 200Hz
~175 dirty rows = 60Hz
~240 dirty rows = 50Hz

If you're using the sprite system, then you can achieve partial redrawing with little effort.

There's the cost of preparing the graphics buffer of the current update, and the cost of refreshing the screen at the end of every update. A little more time spent preparing (like blanking even lines with a stencil) could see reduction in the time spent refreshing the screen (because even lines would never be updated).

True interlacing, alternating which lines are drawn each update, would be the same speed as a full screen refresh because every row would be changing.

It sounds like you've decided to optimise, but are you sure where you're spending time?

I'm trying to do a rotating cylinder as the level for my game.

Screenshot 2023-01-12 at 17.48.27

I'd like to have the background of the cylinder show through the holes in the cylinder in the foreground, and as the background is a darker version of the foreground I could get away with drawing every other line of the background. Currently I'm struggling to make 30 fps, so any way to optimise the drawing would be a great help to me.

1 Like

Looks really cool!

Random ideas:

• Sometimes hard coding something—like maybe the darker versions of all the tiles—is worth trying. You could pre-generate them in code at launch, from the originals. (Or make separate custom art for them.)

• Could you save 2 or 3 rows of interior tiles at the bottom, by having a permanent "fade to darkness" at the bottom of the interior? Might even be a fixed image or stencil, with soft dithered edges.

What does the sampler say about where you are spending time? Device sampling is preferable.

I'd guess there's a bunch of maths involved if you're drawing the tiles warped like that, but confirm with the sampler.

If so: it's good news, because looping algorithms and maths calls are relatively easy to optimise.

I'm also interested to know if you're drawing straight to the buffer/screen, or if you're using the sprite system in any way?

Regarding the suggestions above to hard-coding things, I would go all-in: pre-render every tile at every rotation/warp/distortion and draw them all as sprites. So you're not calculating any warping at all.

1 Like

I think you two have read my mind! Yes, everything's pre-rendered, the slants on each side, the merging of similar bricks together, etc. I have 4 slants x 16 merge combinations x 4 brick types = 256 different images, and now I'm prerendering the background images too that's 256 more!

I'm drawing straight to an image that's the size of the screen (I was tempted to use a vertical jump for vertical scroll) but I could just as easily render straight to the screen, if that might save some processing.

I don't use sprites. As the horizontal scroll rotates the whole cylinder I need to redraw the screen every time so I just add my character and any particle effects, etc. needed as final image draws after the background is done.

1 Like

Adam, I'm tempted to have a 'fade to darkness' style background. When I tried that before, I was able to keep my fps locked to a respectable 30 fps, but I like the idea of the player being able to see the other side of the cylinder, so (s)he can see potentially useful stuff that's currently off-screen.

If I can't find a solution I'll reluctantly go back to just rendering the front, but having the back show through is simply cool! I don't want to lose it!

(I must sound like a broken record.)

Where is your code spending its time in sampler? You need to measure before you know where you need to optimise.

Sprite system might not make any big improvement given that - in terms of rows - everything seems to be changing each update.

I like seeing tha back side too. What I has been thinking was: maybe it could be only partially rendered: the top several rows in back, rendered just as they are now. Then a few rows darkening to black, and then full black at the bottom. Any rows in full black can then be skipped.

I only render background bricks that should be visible, so nothing below the character's level ever gets shown, for example.

Matt, I've looked at the device settings, can see that my memory usage appears to be fine but cpu is being hit hard. Is there somewhere I should be looking for more granularity?

Oh I think I found the sampler you were talking about.

Pretty good tool, so the front of the cylinder is being drawn by drawBrick (17 %) and the back by drawShadowBrick (1.1%) so it looks like my initial plan to optimise the shadow drawing isn't really worth doing.

I was surprised by getCoOrds taking up 7%, I might be able to precalculate those and look them up. Beyond that I assume draw at 36% is the actual routine that draws an image, and my main update() function is at line 100 in gamescreen, so that 18% might not be easily improvable

1 Like

So to get back to my original question, and using the output from the sampler to supply more context, it looks to me like my biggest cpu hit is from the C routine 'draw' (understandably). As a percentage of my draw commands are for 'shadow' images a version of the draw routine that only renders every other line would save me some cpu. I don't think such a routine exists in the current library, but is it the sort of thing I could add myself?

Just to confirm, these results are from device? I don't trust results from Simulator as they're not the same.

You can use the > arrow to open up each line of the sampler results to go deeper. It's important to know exactly what is being drawn so you can try to reduce it.

You're quick to dismiss the 18% in your gamescreen code, but this would be my focus.

I'd look at the top three results until I understood exactly why they were taking that much CPU time. And then I'd work to reduce them.

Spend more time in the sampler, there are different views, different ways to expand and sort the information, and ways to add your own entries by wrapping sections of code so you can find the exact lines that are taking the most time.

If you wanted to add a custom draw function you'd have to do it in C because the Lua functions mostly call C functions anyway. There's an example in the SDK/C folder.

I understand the desire to try to "fix" this by writing more code, but I disagree with this approach. At least with the current info you've provided.

Optimising this sort of thing is great fun, and can/will educate any future code you write. I'm here to help whenever you need it.

1 Like