Whats the recommended specifications of a "spritesheet" for .png Knobs?

I’m looking at using bitmap graphics (PNGs) for knobs, lookandfeel, an arbitrary 128 frames of square images with transparent background.

What is the best layout/specification for a spritesheet that would work well in a JUCE projects?
(I’ve seen files that are just long vertical list of each frame 0-127, and I’ve seen files which are several rows.)

You need custom code for it anyway, since Juce doesn’t have an image sheet based knob/look’n’feel class. So you can write the code based on how you’ve made the sheet. (Or vice versa.)

1 Like

So I simply need to know the number of bytes between each frame, and then write my own custom code based on that.

Just knowing the pixel offset is enough. In the lookAndFeel you do a drawImage() call and workout the offset based on slider position.

One workaround is to subclass a slider, and hold the image in that class. Then in lookandfeel, dynamic cast the slider reference to your subclass and use the image.

2 Likes

A vertical stack of frames may be a little better for the cache/memory access patterns than a “grid” layout, since the pixel data of successive lines are close by in memory in that case.
Just select the right subimage using a line offest.

Some thoughts about the approach (just skip it if the pros/cons of using prerendered gfx are familiar):

Note that depending on your image size, this can get large, especially if you consider high DPI support.
Let’s assume your knob is 64 x 64 pixels large, and you store it in RGBA format. That gives you 16kb of data per frame. For 128 steps, that’s 2MB. If you store them as 128 x 128 internally for high dpi support up to 200%, you get 64kb/frame and a total of 8MB. You will also need to resample the image to native resolution each time the knob is drawn.
If you do not use OpenGL, JUCE will use the CPU to perform the resampling on Windows. This is a very slow operation. It may be fine if your UI is only composed of very few elements which are rather small, but if you have e.g. 32 of those, all of them are automated in a plugin, you suddenly got 32 image resamplings at 60Hz or more, with pretty random memory access.
In many DAWs, your plugins UI shares the thread with other visible plugin editors and the host itself, so this is a thing to be careful about (benchmark!).

Some tips:

  • Cache the resampled version of your texture atlas for the target DPI internally to avoid repeated resampling (you can do it on demand, doing it once is probably not that problematic).
  • Use a combination of pre-rendered backdrop image and programmatic rendering (vector graphics), e.g. try to render the position of the knob using a few simple paths.
  • Consider doing it all with vector graphics, create juce::Path objects and draw those (may be better or worse, really depends).
  • Avoid alpha blending

In general, the approach of using texture atlases is a little out of date. In many cases realtime rendering using vector graphics is preferable, because it avoids the memory access and cache miss bottleneck, it works at all DPIs, and it isn’t stepped/quantized for a bunch of possible values (128 in your example).

I obviously can’t say what’s the best solution in your case, just hoping these considerations may be helpful for someone who’s new to the topic.

2 Likes

Also worth noting is that the image resampling is (by design) the lowest quality possible on Windows, which means if you’re not scaling by a nice integer factor (like 2 for instance) it will look like absolute garbage. Naively using OpenGL does not solve the issue either, as I think what’s happening is the rescale is still done on the CPU and GPU is just being used as a straight blitter.

Are you certain? I was under the assumption that some of the blitting operations and alpha blending are among the few things that actually profit from opengl on windows. It may depend on how exactly go about it, perhaps it applys to component compositing but not to other, more manual operations…

Things may have changed since I last checked, but when I did a naive “just attach OpenGL” to try and solve the crappy resampling in Windows it made no difference. It was however an awfully long time ago and 2 major JUCE versions since I checked this, so perhaps it’s no longer the case. Or of course it may be that I was making a mistake somewhere along the line. In the end I decided to use the AVIR resampler that’s in @RolandMR’s Gin module with a simple hand-rolled caching solution.

all good, thanks for the hint in any case! Just attaching OpenGL rarely helps, I fully agree. It tends to hide the problem due to threading, but causes all kinds of surprise concurrency issues among many others… For the places where it matters I’ve created proper custom solutions too, to the point of writing my own curve drawing software rasterizer recently :wink: let’s just hope that something that is closer to CoreGraphics on mac will arrive in JUCE for windows at some point. OpenGL has caused me so much trouble when it comes to compatibility and plugin particularities that I avoid it completely by now… Even though I never really required anything beyond an OpenGL 2.1 featureset :wink:

2 Likes

This is a Feature Request I would vote with very much pleasure!

Regarding knobs represented with bitmap frames, we use an odd number of frames rather than an even number, so in your case I’d go either with 127 or 129 rather than 128.

The reason is that with an odd number of frames, the middle frame points perfectly upward, at 12 o’clock, which is often a meaningful position for a lot of parameters.
With an even number of frames instead, the center position of the knob is never represented faithfully because there is no actual frame perfectly centered in the angle spanned by the knob’s notch.

It’s easy to see the problem if we play with small numbers: imagine you have to represent a knob with only 3 frames: you get one frame for min position, one for max position and one for center position.
Now, if you increase that to 4 frames, you still have min and max, and the remaining two split the angle in three equal sectors, meaning that neither of them points up.

1 Like

Would I need to know the file format of PNG then, as I have to calculate the pixel offset. I guess png files have a header, but they have lossless compression. Does JUCE interpret the the .png file when its imported and uncompress it into memory for me etc? I’m just trying to figure out how I’d get the pixel offset.

The Juce Image class contains the uncompressed bitmap data and has methods to get the width and height of the bitmap.

1 Like

Good to know JUCE is doing some of the heavy lifting for me.

@jcomusic is CPU load a big deal? We’re just drawing new graphics when a slider is changed, its not hindering the processBlock for more than a moment… is that correct?

Your processBlock shouldn’t have anything to directly do with the graphics drawing in any case. However, you should take into account some worst case scenarios like the user having automated every parameter in your plugin in the host application. Your GUI would have to follow those automations, potentially causing a ton of needed redraws.

Ah yes good point, automations, that makes sense.

I’ll look into trying to throw some of the burden off onto the GPU. Apparently there is a something in JUCE that helps me with that.

Rule of thumb: if your hosts UI starts to drop FPS or even freezes once you have 4 windows of your plugin open at 2x dpi, with as many ui elements animated as possible, you may want to optimize. The recent changes in JUCE to limit repaint rates to screen FPS helped a lot with these issues, by the way.

1 Like

thanks for that testing tip, I’ll put my plugin on 4 tracks and open each to see if it drops frames when I animate.