20

I want to draw pixels on the monitor which change frequently after certain parameters. E.G. if a Red and Green Pixel collide, they would both vanish, etc.

In every frame I have to manipulate about 100 - 1000 pixels. I have a multi-threaded approach here, which wont give me 30FPS (what I want). Currently I store a Pixel array in the RAM which contains all Pixels and have a SDL_Surface. When a pixel in the array changes, it gets changed in the Surface too and is then after all manipulation is done gets blitted to the screen. My current approach is too slow and I did a bit of thinking on how I could increase the Speed.

My current thoughts are:

  • Use OpenGL to do the pixel manipulation directly on the GPU, which some forums tells me that this is way slower than my current approach as "this is not how a GPU works"
  • Don't store a pixel array, store a BMP in RAM directly, manipulate that and then move it to an SDL_Surface or SDL_Texture

Are there any other approaches on how I could manipulate Pixels in a fast manner?

genpfault
  • 51,148
  • 11
  • 85
  • 139
Nidhoegger
  • 4,973
  • 4
  • 36
  • 81
  • 5
    I think that pixel manipulation itself is not the cause of low FPS. If you randomly change every pixel on screen (not just 1K of them) you should get way above 30FPS. [Here](http://stackoverflow.com/a/24170211/833188) says that you should use `SDL_Texture` for performance. Have you tried that? Have you profiled your code anyway? – Sga Oct 23 '15 at 14:27
  • I have a `SDL_Texture` with `TEXTURE_STREAMING`, then Lock the Texture, do the manipulation on the obtained pixel array and then unlock it when done. – Nidhoegger Oct 23 '15 at 14:30
  • Only profiling could tell where is the bottleneck – Sga Oct 23 '15 at 15:14
  • But before I implement my methods again again I wanted to know if someone knows the magic ;). But then ill just benchmark it. Thank! – Nidhoegger Oct 23 '15 at 16:02
  • Use SDL_RenderDrawPoint? – Martin G Oct 24 '15 at 04:06

1 Answers1

20

SDL_CreateTexture() w/SDL_TEXTUREACCESS_STREAMING + SDL_UpdateTexture() seems to work well enough with the right pixel format.

On my system using the default renderer:

Renderer name: direct3d
Texture formats:
SDL_PIXELFORMAT_ARGB8888
SDL_PIXELFORMAT_YV12
SDL_PIXELFORMAT_IYUV

(though the opengl info is the same:)

Renderer name: opengl
Texture formats:
SDL_PIXELFORMAT_ARGB8888
SDL_PIXELFORMAT_YV12
SDL_PIXELFORMAT_IYUV

SDL_PIXELFORMAT_ARGB8888 gives me ~1ms/frame:

// g++ main.cpp `pkg-config --cflags --libs sdl2`
#include <SDL.h>
#include <iostream>
#include <iomanip>
#include <vector>
#include <algorithm>
#include <chrono>

void UpdateFrameTiming( std::ostream& os = std::cout, float period = 2.0f )
{
    static unsigned int frames = 0;
    frames++;
    static auto start = std::chrono::steady_clock::now();
    auto end = std::chrono::steady_clock::now();

    auto seconds = std::chrono::duration< float >( end - start ).count();
    if( seconds >= period )
    {
        os
            << frames << " frames in "
            << std::setprecision( 1 ) << std::fixed << seconds << " seconds = "
            << std::setprecision( 1 ) << std::fixed << frames / seconds << " FPS ("
            << std::setprecision( 3 ) << std::fixed << seconds / frames * 1000.0 << " ms/frame)\n";
        frames = 0;
        start = end;
    }
}

int main( int, char** )
{
    SDL_Init( SDL_INIT_EVERYTHING );
    SDL_Window* window = SDL_CreateWindow( "SDL", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 600, 600, SDL_WINDOW_SHOWN );
    SDL_Renderer* renderer = SDL_CreateRenderer( window, -1, SDL_RENDERER_ACCELERATED );
    SDL_SetHint( SDL_HINT_RENDER_SCALE_QUALITY, "1" );
    
    // dump renderer info
    SDL_RendererInfo info;
    SDL_GetRendererInfo( renderer, &info );
    std::cout << "Renderer name: " << info.name << '\n';
    std::cout << "Texture formats: " << '\n';
    for( Uint32 i = 0; i < info.num_texture_formats; i++ )
    {
        std::cout << SDL_GetPixelFormatName( info.texture_formats[i] ) << '\n';
    }

    // create texture
    const unsigned int texWidth = 1024;
    const unsigned int texHeight = 1024;
    SDL_Texture* texture = SDL_CreateTexture( renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, texWidth, texHeight );
    std::vector< unsigned char > pixels( texWidth * texHeight * 4, 0 );

    bool useLocktexture = false;

    // main loop
    bool running = true;
    while( running )
    {
        SDL_SetRenderDrawColor( renderer, 0, 0, 0, SDL_ALPHA_OPAQUE );
        SDL_RenderClear( renderer );

        // handle events
        SDL_Event ev;
        while( SDL_PollEvent( &ev ) )
        {
            if( ( SDL_QUIT == ev.type ) ||
                ( SDL_KEYDOWN == ev.type && SDL_SCANCODE_ESCAPE == ev.key.keysym.scancode ) )
            {
                running = false;
                break;
            }

            if( SDL_KEYDOWN == ev.type && SDL_SCANCODE_L == ev.key.keysym.scancode )
            {
                useLocktexture = !useLocktexture;
                std::cout << "Using " << ( useLocktexture ? "SDL_LockTexture() + std::copy_n()" : "SDL_UpdateTexture()" ) << '\n';
            }
        }
        
        // splat down some random pixels
        for( unsigned int i = 0; i < 1000; i++ )
        {
            const unsigned int x = rand() % texWidth;
            const unsigned int y = rand() % texHeight;

            const unsigned int offset = ( texWidth * y * 4 ) + x * 4;
            pixels[ offset + 0 ] = rand() % 256;        // b
            pixels[ offset + 1 ] = rand() % 256;        // g
            pixels[ offset + 2 ] = rand() % 256;        // r
            pixels[ offset + 3 ] = SDL_ALPHA_OPAQUE;    // a
        }

        // update texture
        if( useLocktexture )
        {
            unsigned char* lockedPixels = nullptr;
            int pitch = 0;
            SDL_LockTexture( texture, nullptr, reinterpret_cast< void** >( &lockedPixels ), &pitch );
            std::copy_n( pixels.data(), pixels.size(), lockedPixels );
            SDL_UnlockTexture( texture );
        }
        else
        {
            SDL_UpdateTexture( texture, nullptr, pixels.data(), texWidth * 4 );
        }

        SDL_RenderCopy( renderer, texture, nullptr, nullptr );
        SDL_RenderPresent( renderer );
        
        UpdateFrameTiming();
    }

    SDL_DestroyRenderer( renderer );
    SDL_DestroyWindow( window );
    SDL_Quit();

    return 0;
}

Make sure you don't have have vsync enabled (forced in the driver, running a compositor, etc.) or else all your frame times will be ~16ms (or whatever your display refresh is set to).

genpfault
  • 51,148
  • 11
  • 85
  • 139
  • May I ask what system you use? Here on an i5 with intel HD 2000 I get about 10ms per frame on an i7 2600k with GTX 560 I get 3ms. Both running gentoo linux. Thx! – Nidhoegger Oct 26 '15 at 16:00
  • @Nidhoegger: i7 2600 @ 3.4GHz, Win7 with a AMD FirePro V4800. – genpfault Oct 26 '15 at 16:40
  • 1
    @Nidhoegger: What does the `SDL_GetRendererInfo()` dump display on your system(s)? – genpfault Oct 26 '15 at 16:43
  • Same as yours (openGL). I am on Linux, but Texture formats are the same (ARGB8888, YV12, IYUV). I am exploring atm if the frametimes come from my multimonitor setup. Maybe the nVidia driver is not as good as I thought with that kinds of things (proprietary, not nouveau) – Nidhoegger Oct 27 '15 at 10:57
  • Is your approach faster than simply filling an 'SDL_Point' array then drawing from it using SDL_RenderDrawPoints? – john_who_is_doe Mar 10 '21 at 15:17
  • @kidbro: There's probably a cross-over point vs. the number of points you're drawing and if the SDL_Renderer backend in use is configured/able to batch draw-calls. `SDL_RenderDrawPoints()` doesn't let you vary the color either, so you'd have to sort points by color & have multiple draw-calls anyway. – genpfault Mar 10 '21 at 15:52