SDL_Texture
objects are stored as close as possible to video card memory and therefore can easily be accelerated by your GPU. Resizing, alpha blending, anti-aliasing and almost any compute-heavy operation can harshly be affected by this performance boost. If your program needs to run a per-pixel logic on your textures, you are encouraged to convert your textures into surfaces temporarily. Achieving a workaround with streaming textures is also possible.
Edit:
Since this answer recieves quite the attention, I'd like to elaborate my suggestion.
If you prefer to use Texture -> Surface -> Texture
workflow to apply your per-pixel operation, make sure you cache your final texture unless you need to recalculate it on every render cycle. Textures in this solution are created with SDL_TEXTUREACCESS_STATIC
flag.
Streaming textures (creation flag is SDL_TEXTUREACCESS_STREAMING
) are encouraged for use cases where source of the pixel data is network, a device, a frameserver or some other source that is beyond SDL applications' full reach and when it is apparent that caching frames from source is inefficient or would not work.
It is possible to render on top of textures if they are created with SDL_TEXTUREACCESS_TARGET
flag. This limits the source of the draw operation to other textures although this might already be what you required in the first place. "Textures as render targets" is one of the newest and least widely supported feature of SDL2.
Nerd info for curious readers:
Due to the nature of SDL implementation, the first two methods depend on application level read and copy operations, though they are optimized for suggested scenarios and fast enough for realtime applications.
Copying data from application level is almost always slow when compared to post-processing on GPU. If your requirements are more strict than what SDL can provide and your logic does not depend on some outer pixel data source, it would be sensible to allocate raw OpenGL textures painted from you SDL surfaces and apply shaders (GPU logic) to them.
Shaders are written in GLSL, a language which compiles into GPU assembly. Hardware/GPU Acceleration actually refers to code parallelized on GPU cores and using shaders is the prefered way to achieve that for rendering purposes.
Attention! Using raw OpenGL textures and shaders in conjunction with SDL rendering functions and structures might cause some unexpected conflicts or loss of flexibility provided by the library.
TLDR;
It is faster to render and operate on textures than surfaces although modifying them can sometimes be cumborsome.
TL;DR The comment from @kelter pointed me in the right direction and another stack overflow question gave me a solution: it works if you first Blit to a 32bpp surface and then BlitScaled to another 32bpp surface. That worked for 8 and 24 bit depth pngs, 32 bit were invisible again another stack overflow question suggested first filling the surface before blitting.
An updated get_texture function:
SDL_Texture * get_texture(
SDL_Renderer * pRenderer,
std::string image_filename) {
SDL_Texture * result = NULL;
SDL_Surface * pSurface = IMG_Load(image_filename.c_str());
if (pSurface == NULL) {
printf("Error image load: %s\n", IMG_GetError());
}
else {
const int limit = 1024;
int width = pSurface->w;
int height = pSurface->h;
if ((width > limit) ||
(height > limit)) {
SDL_Rect sourceDimensions;
sourceDimensions.x = 0;
sourceDimensions.y = 0;
sourceDimensions.w = width;
sourceDimensions.h = height;
float scale = (float)limit / (float)width;
float scaleH = (float)limit / (float)height;
if (scaleH < scale) {
scale = scaleH;
}
SDL_Rect targetDimensions;
targetDimensions.x = 0;
targetDimensions.y = 0;
targetDimensions.w = (int)(width * scale);
targetDimensions.h = (int)(height * scale);
// create a 32 bits per pixel surface to Blit the image to first, before BlitScaled
// https://stackoverflow.com/questions/33850453/sdl2-blit-scaled-from-a-palettized-8bpp-surface-gives-error-blit-combination/33944312
SDL_Surface *p32BPPSurface = SDL_CreateRGBSurface(
pSurface->flags,
sourceDimensions.w,
sourceDimensions.h,
32,
pSurface->format->Rmask,
pSurface->format->Gmask,
pSurface->format->Bmask,
pSurface->format->Amask);
if (SDL_BlitSurface(pSurface, NULL, p32BPPSurface, NULL) < 0) {
printf("Error did not blit surface: %s\n", SDL_GetError());
}
else {
// create another 32 bits per pixel surface are the desired scale
SDL_Surface *pScaleSurface = SDL_CreateRGBSurface(
p32BPPSurface->flags,
targetDimensions.w,
targetDimensions.h,
p32BPPSurface->format->BitsPerPixel,
p32BPPSurface->format->Rmask,
p32BPPSurface->format->Gmask,
p32BPPSurface->format->Bmask,
p32BPPSurface->format->Amask);
// 32 bit per pixel surfaces (loaded from the original file) won't scale down with BlitScaled, suggestion to first fill the surface
// 8 and 24 bit depth pngs did not require this
// https://stackoverflow.com/questions/20587999/sdl-blitscaled-doesnt-work
SDL_FillRect(pScaleSurface, &targetDimensions, SDL_MapRGBA(pScaleSurface->format, 255, 0, 0, 255));
if (SDL_BlitScaled(p32BPPSurface, NULL, pScaleSurface, NULL) < 0) {
printf("Error did not scale surface: %s\n", SDL_GetError());
SDL_FreeSurface(pScaleSurface);
pScaleSurface = NULL;
}
else {
SDL_FreeSurface(pSurface);
pSurface = pScaleSurface;
width = pSurface->w;
height = pSurface->h;
}
}
SDL_FreeSurface(p32BPPSurface);
p32BPPSurface = NULL;
}
SDL_Texture * pTexture = SDL_CreateTextureFromSurface(pRenderer, pSurface);
if (pTexture == NULL) {
printf("Error image load: %s\n", SDL_GetError());
}
else {
SDL_SetTextureBlendMode(
pTexture,
SDL_BLENDMODE_BLEND);
result = pTexture;
}
SDL_FreeSurface(pSurface);
pSurface = NULL;
}
return result;
}
The comment from @kelter had me look more closely at the surface pixel formats, bitmaps were working at 24bpp, pngs were being loaded at 8bpp and not working. Tried changing the target surface to 24 or 32 bpp but that didn't help. We had generated the png with auto-detected bit depth, setting it to 8 or 24 and performing the BlitScaled on a surface with the same bits-per-pixel worked although it didn't work for 32. Googling the blit conversion error lead to the question and answer from @Petruza.
Update Was a bit quick writing up this answer, the original solution handled bmp and 8 and 24 bit pngs but 32 bit pngs weren't rendering. @Retired Ninja answer to another question about Blit_Scaled suggested filling the surface before calling the function and that sorts it, there's another question related to setting alpha on new surfaces that may be relevant to this (particularily if you needed transparency) but filling with a solid colour is enough for me... for now.
Best Answer
First I need to clarify some misconceptions: The texture based rendering does not work as the old surface rendering did. While you can use
SDL_Surface
s as source or destination,SDL_Texture
s are meant to be used as source for rendering and the complimentarySDL_Renderer
is used as destination. Generally you will have to choose between the old rendering framework that is done entirely on CPU and the new one that goes for GPU, but mixing is possible.So for you questions:
SDL_Renderer
, and is always better to pre-load surfaces on textures.Lastly you should use textures whenever you can. The surface use is a exception, use it when you either have to use intensive pixel manipulation or have to deal with legacy code.