SDL2: 快速像素操作

20

我想在显示器上绘制像素,并根据一定参数频繁地改变它们。例如,如果红色像素和绿色像素碰撞,则它们都会消失等。

每帧我需要处理大约100到1000个像素。我采用了多线程的方法,但无法达到30FPS(我所需的)。目前,我在RAM中存储了一个Pixel数组,其中包含所有像素和一个SDL_Surface。当数组中的像素发生变化时,它也会在表面上发生变化,然后在所有操作完成后被 blitted 到屏幕上。我的当前方法速度太慢了,我思考了一下如何提高速度。

我目前的想法是:

  • 使用OpenGL直接在GPU上进行像素操作,但一些论坛告诉我这比我的当前方法要慢得多,因为“这不是GPU的工作方式”。
  • 不要存储像素数组,直接在RAM中存储BMP,对其进行操作,然后将其移动到SDL_SurfaceSDL_Texture中。

还有其他方法可以快速处理像素吗?


5
我认为像素操作本身并不是低帧率的原因。如果你随机改变屏幕上的每个像素(而不仅仅是其中的1K个像素),应该可以获得超过30FPS的帧率。 这里建议使用SDL_Texture来提高性能。你尝试过吗?你已经对你的代码进行了性能分析吗? - Sga
我有一个带有TEXTURE_STREAMINGSDL_Texture,然后锁定纹理,在获取到的像素数组上进行操作,完成后解锁它。 - Nidhoegger
只有性能分析才能告诉我们瓶颈在哪里。 - Sga
但在我再次实现我的方法之前,我想知道是否有人知道其中的奥秘。但是然后我会进行基准测试。谢谢! - Nidhoegger
使用SDL_RenderDrawPoint函数吗? - Martin G
1个回答

20

SDL_CreateTexture() 使用 SDL_TEXTUREACCESS_STREAMING 来创建纹理,并配合正确的像素格式,似乎能够良好地工作。

在我的系统上使用默认渲染器:

Renderer name: direct3d
Texture formats:
SDL_PIXELFORMAT_ARGB8888
SDL_PIXELFORMAT_YV12
SDL_PIXELFORMAT_IYUV
< p >(尽管 < code > opengl 的信息是相同的:)
Renderer name: opengl
Texture formats:
SDL_PIXELFORMAT_ARGB8888
SDL_PIXELFORMAT_YV12
SDL_PIXELFORMAT_IYUV

SDL_PIXELFORMAT_ARGB8888 可以让我每帧运行大约 1 毫秒:

// 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;
}

确保您没有启用垂直同步(在驱动程序中强制启用、运行合成器等),否则所有帧时间都将为约16ms(或您的显示器刷新率设置为其他值)。


请问您使用的是什么系统?我在一台搭载Intel HD 2000的i5上每帧大约需要10毫秒,在一台搭载GTX 560的i7 2600k上只需要3毫秒。两台电脑都运行着Gentoo Linux。谢谢! - Nidhoegger
@Nidhoegger:i7 2600 @ 3.4GHz,搭载AMD FirePro V4800的Win7。 - genpfault
1
@Nidhoegger:SDL_GetRendererInfo()转储在你的系统上显示了什么? - genpfault
和你的一样(openGL)。我正在使用Linux,但纹理格式相同(ARGB8888,YV12,IYUV)。目前我正在探索是否是由于我的多监视器设置导致了帧时间的问题。也许nVidia驱动程序在这种情况下并没有我想象中那么好(专有的,而不是nouveau)。 - Nidhoegger
你的方法比简单地填充一个“SDL_Point”数组,然后使用SDL_RenderDrawPoints绘制它更快吗? - john_who_is_doe
@kidbro:可能存在一个交叉点,与您正在绘制的点数有关,以及使用的SDL_Renderer后端是否配置/能够批处理绘图调用。SDL_RenderDrawPoints()也不允许您改变颜色,因此您必须按颜色对点进行排序,并且无论如何都要进行多个绘图调用。 - genpfault

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接