SDL2 access render framebuffer

5.7k views Asked by At

I am trying to access the framebuffer of an SDL_Renderer as an array of Uint32* pixel to pass to Libretro's display function.

I created this minimal example to show two methods of accessing SDL's framebuffer that I have seen online, neither of which work.

#include <iostream>
#include "SDL.h"

const int WIDTH = 50;
const int HEIGHT = 40;
int frameIndex = 0;

int main(int argc, char *argv[]) {
    SDL_Init(SDL_INIT_VIDEO);

    auto win = SDL_CreateWindow("Framebuffer test", SDL_WINDOWPOS_CENTERED | SDL_WINDOW_OPENGL, SDL_WINDOWPOS_CENTERED, WIDTH, HEIGHT, 0);

    auto renderer = SDL_CreateRenderer(win, -1, SDL_RENDERER_ACCELERATED);

    auto pixels = new uint32_t[WIDTH * HEIGHT]; // width x height x SDL_PIXELFORMAT_RGBA8888
    auto surface = SDL_CreateRGBSurface(SDL_WINDOW_SHOWN, WIDTH, HEIGHT, 32, 0xff000000, 0x00ff0000, 0x0000ff00, 0x000000ff);

    // Rect to draw on the screen
    SDL_Rect rect;
    rect.x = 0;
    rect.y = 0;
    rect.w = 10;
    rect.h = 10;

    while (true) {
        SDL_Event e;
        if (SDL_PollEvent(&e)) {
            if (e.type == SDL_QUIT) {
                break;
            }
        }

        SDL_RenderClear(renderer);

        SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255);
        SDL_RenderDrawRect(renderer, &rect);
        SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);

        // Access framebuffer: method 1
        SDL_RenderReadPixels(renderer, nullptr, SDL_PIXELFORMAT_ARGB8888, pixels, 1);
        auto surfacePixels = (Uint32*) surface->pixels;
        for (int i = 0; i < WIDTH * HEIGHT; ++i)
            surfacePixels[i] = pixels[i];
        SDL_LockSurface(surface);
        SDL_SaveBMP(surface, ("screen1_" + std::to_string(frameIndex) + ".bmp").c_str()); // Output buffer
        SDL_UnlockSurface(surface);

        // Access frame buffer: method 2
        auto surf = SDL_GetWindowSurface(win);
        SDL_LockSurface(surf);
        SDL_SaveBMP(surf, ("screen2_" + std::to_string(frameIndex) + ".bmp").c_str()); // Output buffer
        SDL_UnlockSurface(surf);
        std::cout << "Screenshot: " << frameIndex++ << std::endl;

        SDL_RenderPresent(renderer);

        if (frameIndex >= 20)
            break;
    }

    SDL_FreeSurface(surface);
    SDL_DestroyRenderer(renderer);
    SDL_DestroyWindow(win);

    SDL_Quit();

    return 0;
}

Window / desired output

Window

Method 1 result

Method 1

Method 2 result

Method 2

I am expecting to see a black screen with a white square, but in the first method I see noise and in the second method I see a black screen. What am I doing wrong?

1

There are 1 answers

1
keltar On BEST ANSWER

Your SDL_RenderReadPixels pitch parameter is 1. Pitch is byte length of a single image row. If each pixel is 4 bytes wide, and each row is 1 byte... image is 1/4 pixel wide? Sounds problemmatic. Correct pitch is 4 * WIDTH (plus padding, but you don't have it with 32bit formats).

Other remarks:

SDL_CreateRGBSurface first parameter is "flags - the flags are unused and should be set to 0", not SDL_WINDOW_SHOWN. Changes nothing as it is ignored, but it is at least misleading.

SDL_GetWindowSurface can't be used if there is a renderer associated with the window. Documentation says "You may not combine this with 3D or the rendering API on this window".

SDL_LockSurface should be before accessing surface pixels, with unlock after it. On most surfaces locking is not required so you don't see anything different, but locking before SaveBMP is logically incorrect.