Rendering a Buffer of Pixels to the Screen

Searched through the forums for a way to do this and couldn’t find anything conclusive.

Just as a note: I’m still rather new to programming in JUCE; most of my graphics programming experience has been in C with SDL2 or messing around in Turbo C on old MSDOS systems just to see what I could do.

In SDL2, you can draw pixel data into a Uint32 buffer in the “background”, so to speak, and then copy that buffer all at once by with SDL_UpdateTexture() and then SDL_RenderCopy()

I wasn’t able to find anything directly similar in JUCE but I was able to hack together this method

juce::SoftwareImageType temp;

if (screenBuffer == nullptr) return;

screenImage = juce::Image(temp.create(juce::Image::ARGB, SCREEN_WIDTH, SCREEN_HEIGHT, true));

if (screenImage.isValid())
{
    juce::Image::BitmapData pixelMap(screenImage, juce::Image::BitmapData::writeOnly);

    memcpy(pixelMap.data, screenBuffer, SCREEN_AREA * sizeof *screenBuffer);
}

which copies the buffer to an image which can then my drawn to the window with drawImageWithin

My question is for those more experienced with JUCE’s more niche functions: is there a better/faster way to do this? I’ve looked over the JUCE website, the JUCE forum, and the source code and haven’t been able to find anything that inherently does what I’m looking for, so I just wanted to check if this was the “right” way to render an array of pixel data

For a full example of code that uses this method:

class MainComponent : public juce::Component
{
public:
MainComponent::MainComponent()
{
    screenBuffer = new uint32_t[SCREEN_AREA];
    memset(screenBuffer, 0, SCREEN_AREA * sizeof *screenBuffer);

    setSize (600, 400);

    for (int y = 50; y < 150; y++)
        for (int x = 100; x < 200; x++)
            *(screenBuffer + x + (y * SCREEN_WIDTH)) = 0xFFFFFFFF;

    copyScreenBuffer();
}

MainComponent::~MainComponent()
{
    if (screenBuffer != nullptr)
    {
        delete[] screenBuffer;
        screenBuffer = nullptr;
    }
}

//==============================================================================
void MainComponent::paint (juce::Graphics& g)
{
    g.fillAll (juce::Colours::blue);

    g.setImageResamplingQuality(juce::Graphics::lowResamplingQuality);

    g.drawImageWithin(screenImage, 0, 0, getWidth(), getHeight(),
        juce::RectanglePlacement::stretchToFit, false);
}

void MainComponent::resized()
{
}

void MainComponent::copyScreenBuffer()
{
    juce::SoftwareImageType temp;

    if (screenBuffer == nullptr) return;

    screenImage = juce::Image(temp.create(juce::Image::ARGB, SCREEN_WIDTH, SCREEN_HEIGHT, true));
    
    if (screenImage.isValid())
    {
        juce::Image::BitmapData pixelMap(screenImage, juce::Image::BitmapData::writeOnly);

        memcpy(pixelMap.data, screenBuffer, SCREEN_AREA * sizeof *screenBuffer);
        repaint(getBounds());
    }
}

private:
    juce::Image screenImage;

    uint32_t *screenBuffer  = nullptr;

    const int SCREEN_WIDTH  = 300,
              SCREEN_HEIGHT = 200,
              SCREEN_AREA   = SCREEN_WIDTH * SCREEN_HEIGHT;

    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(MainComponent)
};

Which draws:

(obviously this is an overly simplified example and the surrounding code can be more fleshed out in a full program)

1 Like

Yes, the faster way would be to use OpenGL and use its functions to copy your frame buffer into a texture and then render that as a quad.

Your frame buffer size and mention of SDL piqued my interest. Are you attempting to do C64 stuff?

1 Like

You can do this in a couple of different ways:

Image image { Image::PixelFormat::ARGB, 512, 512, false };

{
    Graphics g { image };

    g.fillRect(...)
}

{
    image.setPixelAt(...)
}

{
    Image::BitmapData data { image, Image::BitmapData::readWrite };
    
    for (int y = 0; y < data.height; y++)
    {
        auto* line = data.getLinePointer(y);

        ...
    }
}

OpenGL would speed up the ‘image to screen’ process as @reFX suggested.

1 Like

You need to be careful with this. The data in the juce::Image::BitmapData object may be laid out differently in memory than your screenBuffer. In particular, the rows may be slightly larger in order to nicely align them in memory.

It would be more correct to loop through the rows of the screenBuffer and copy them one-by-one, taking into account the stride of the row in the BitmapData. Alternatively, do your drawing into the BitmapData’s memory directly.

Thanks for that! Coincidentally, that’s actually what I initially did when I was trying to figure out how to do this :sweat_smile:

I had based this on the juce_Image.cpp code but I thought copying the whole buffer at once would be more efficient. I didn’t consider the line stride could be a different size, so I’ll change it back to looping through row by row

Aahh I had a feeling OpenGL probably had a way to do it; one of these days I’ll make it through one of the several OpenGL tutorials that I’ve started xD

And actually close! I’m writing a sampler inspired by The Commodore Amiga/Protracker (borrowing some inspiration from Fasttracker as well)



The ASTRIID logo and mouse cursor are regular PNGs and the keyboard is obvs the JUCE MIDI keyboard component, but all the other graphics are done with screen buffer classes

My SDL project that I’m working on is an editor/converter for Super Nintendo BRR samples (as well as WAV, AIF, amiga .IFF and raw PCM, Fairlight CMI .VC, mu-law encoded Linn drum/Drumulator samples)

4 Likes

Thanks again @oli1 and @kerfuffle

Was able to clean up my function a lot so now it looks like:

	screenImage = { juce::Image::PixelFormat::ARGB, SCREEN_WIDTH, SCREEN_HEIGHT, false };

    juce::Image::BitmapData pixelMap(screenBuffer, juce::Image::BitmapData::writeOnly);

	for (int y = 0; y < pixelMap.height; y++)
		memcpy(pixelMap.getLinePointer(y), screenBuffer + (y * pixelMap.width), (size_t)pixelMap.lineStride);

@kerfuffle is it safer to use pixelMap.width and pixelMap.height variables or should I use the SCREEN_WIDTH and SCREEN_HEIGHT variables that I had used to setup the screenBuffer dynamic array with? I used the pixelMap’s variables here to sync up with the lineStride variable, but I do worry that it could cause a buffer overrun in the screenBuffer array, which makes me think the SCREEN_WIDTH and SCREEN_HEIGHT variables might be safer.

Not all too familiar with working with the Image::BitmapData class just yet so I figured I’d double check which was the “correct” way to do it

1 Like

Is it vital to you to use this off-screen bitmap and copy it via an Image to the screen? Because the things you draw are so simple (Amiga style windows & scrollbars, etc.) that you can easily do the same with g.fillRect, g.drawRect, etc. and thus avoid this frame-buffer stuff completely.

Honestly? Because I already had the code for it all set up from a separate project and I didn’t get around to rewriting those parts :rofl: I like the pixelized aesthetic of the waveform graphic and I was more focused on getting the waveform all synced with the processor end of JUCE than redoing all the windowing

I’ve since realized that drawing the image to the screen in JUCE isn’t as fast as doing the similar method in SDL with SDL_UpdateTexture (takes anywhere from 3-12ms just for the drawImageWithin function according to JUCE’s hi res millisecond counter)

I’ll probably go back and rewrite most of it with the g.fillRect functions like you said and then try to get the pixel-waveform drawn with OpenGL

Still, this was a fun experiment to get running even if it ended up being a bit of a dead end. I appreciate all your help!

I would use SCREEN_WIDTH and SCREEN_HEIGHT because you know for a fact what they are. But I don’t think it really matters in the end.

Also, while I like the Amiga aesthetic, your interpretation of it isn’t entirely correct. :wink: The F-E bar on the left was specific for drive windows and measured the amount of free space on the disk, but it wasn’t present in regular windows. The bar on the right wouldn’t have had the vertical stripes but would be a scrollbar too. And it’s the wrong font for the 1.x era of Workbench. :wink: (And to be super nitpicky: ProTracker didn’t use the standard Amiga windows.)

Oh I’m aware haha I wasn’t going for a 1:1 of the aesthetic, but more-so just heavily inspired by the look of Workbench 1.x :wink: main reason why I didn’t keep the right hand scrollbar is bc I didn’t want less…intuitive users to think that was a direction you could scroll in since I was only adding in side to side scrolling xD more of an amiga-inspired sampler than an amiga-emulating sampler

1 Like

Thanks again @reFX , @kerfuffle , and @oli1 for your help. Got a mostly-working demo of it up and running :slight_smile: still got some bugs to work out and I gotta clean up the code (especially the hacky bits) but I’m pretty proud of how it came out! Thought y’all might want to check out how it sounds

5 Likes

YO! This is so cool! My 1200 is awaiting repair but this has got me so nostalgic!

1 Like

Aahhh so jealous of that A1200!! I love my A500 but an A1200 is the dream (largely bc I’m constantly running out of RAM in Protracker or OctaMED even with the RAM expanded to a whopping 1MB :rofl:)

If you ever get yours up and running and need a sampler module to sample on it, I’ve also made an Arduino project that speeds up the Arduino’s ADC to act as an 8bit sampler for the amiga’s LPT port :grin:

It’s pretty fun running my synths into it and making my own Amiga samples!