SDL2: Fast Pixel Manipulation
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 PrintFrameTiming(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();
float seconds = std::chrono::duration_cast< std::chrono::duration<float> >(end - start).count();
if( seconds > period )
{
float spf = seconds / frames;
os
<< frames << " frames in "
<< std::setprecision(1) << std::fixed << seconds << " seconds = "
<< std::setprecision(1) << std::fixed << 1.0f / spf << " FPS ("
<< std::setprecision(3) << std::fixed << spf * 1000.0f << " 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 );
PrintFrameTiming();
}
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).
SDL2 pixel manipulation
You created a texture, but you haven't drawn it yet. In order to draw it on a surface, you still need to call SDL_RenderCopy
.
How to Upscale window or renderer in sdl2 for pixelated look?
To do this, the nearest
scaling must be set (default in SDL2), which does not use antialiasing. If so, you can use SDL_SetHint
by setting hint SDL_HINT_RENDER_SCALE_QUALITY
to nearest
(or 0
). If you now render a small texture in a large enough area (much larger than the texture size), you will see large pixels in the window.
If, on the other hand, you have large textures (just like in the linked thread), or you just want to render the entire frame pixelated, you can do this by rendering the contents of the frame on a low-resolution auxiliary texture (serving as the back buffer), and after rendering the entire frame, rendering the back buffer in the window. The buffer texture will be stretched across the entire window and the pixelation will then be visible.
I used this method for the Fairtris game which renders the image in NES-like resolution. Internal back buffer texture has resolution of 256×240 pixels and is rendered in a window of any size, maintaining the required proportions (4:3
, so slightly stretched horizontally). However, in this game I used linear scaling to make the image smoother.
To do this you need to:
- remember that the
nearest
scaling must be set, - create a renderer with the
SDL_RENDERER_TARGETTEXTURE
flag, - create back buffer texture with low resolution (e.g. 256×240) and with
SDL_TEXTUREACCESS_TARGET
flag.
When rendering a frame, you need to:
- set the renderer target to the backbuffer texture with
SDL_SetRenderTarget
, - render everything the frame should contain using the renderer and back buffer size (e.g. 256×240),
- bring the renderer target back to the window using
SDL_SetRenderTarget
again.
You can resize the back buffer texture at any time if you want a smaller area (zoom in effect, so larger pixels on the screen) or a larger area (zoom out effect, so smaller pixels on the screen) in the frame. To do this, you will most likely have to destroy and recreate the backbuffer texture with a different size. Or you can create a big backbuffer texture with an extra margin and when rendering, use a smaller or bigger area of it — this will avoid redundant memory operations.
At this point, you have the entire frame in an auxiliary texture that you can render in the window. To render it in a window, use the SDL_RenderCopy
function, specifying the renderer handle and back buffer texture handle (rects should not be given so that the texture will be rendered completely over the entire window area), and finally SDL_RenderPresent
.
If you need to render in window the frame respecting the aspect ratio, get the current window size with SDL_GetWindowSize
and calculate the target area taking into account the aspect ratio of the back buffer texture and the window proportions (portrait and landscape). However, before rendering the back buffer texture in the window, first clean the window with SDL_RenderClear
so that the remaining areas of the window (black bars) are filled with black.
SDL2 reading RGB values of a pixel from psdl_surface
Values of local variables are random (garbage) and should be initialized. pixels
variable is a pointer. In the assignment pixels^:= uInt32(surface^.pixels);
you do not initialize the variable, but write data to some random memory location.
The correct initialization is
pixels := PuInt32(surface^.pixels);
Related Topics
How to Print Utf-8 Strings to Std::Cout on Windows
Is Sizeof(*Ptr) Undefined Behavior When Pointing to Invalid Memory
G++ How to Get Warning on Ignoring Function Return Value
Valgrind Memory Leak Errors When Using Pthread_Create
Avoiding Denormal Values in C++
How to Copy/Paste from the Clipboard in C++
Should I Use Shared_Ptr or Unique_Ptr
When Is #Include <New> Library Required in C++
Header File Included Only Once in Entire Program
Understanding the Dangers of Sprintf(...)
Static Analysis Tool to Detect Abi Breaks in C++
Function Composition in C++/C++11
C++ Automatic Factory Registration of Derived Types
What's the Right Way to Use the Rand() Function in C++
Is It Legal to Use the Increment Operator in a C++ Function Call
Clean C++ Granular Friend Equivalent? (Answer: Attorney-Client Idiom)