This indeed sounds like a job for target textures, they’re even hardware accelerated. Beware though that you have to check with SDL_GetRendererInfo
if the renderer supports them. They should be available on a wide range of system configurations. It’s mostly old Windows graphics drivers that will not offer them. There may be a few quirks depending on which backend you will use. For example, Direct3D will require you to redraw the target texture after the window size changed.
Here’s a simple example of what Naith explained.
[details=combine_with_targettexture.c]
#include "SDL.h"
static struct {
SDL_Window * handle;
int width, height;
SDL_Renderer * renderer;
SDL_Texture * texture1;
SDL_Texture * texture2;
SDL_Texture * texture_target;
} Window;
static void draw_to_target_texture()
{
/* Direct the draw commands to the target texture. */
SDL_SetRenderTarget(Window.renderer, Window.texture_target);
/* It's always a good idea to clear the whole thing first. */
SDL_SetRenderDrawColor(Window.renderer, 0, 0, 0, 0);
SDL_RenderClear(Window.renderer);
/* Let's copy the other textures onto the target texture. */
SDL_RenderCopy(Window.renderer, Window.texture1, NULL, NULL);
SDL_RenderCopy(Window.renderer, Window.texture2, NULL, NULL);
/* Resetting to the default render target which is the frame buffer
that gets displayed on screen. */
SDL_SetRenderTarget(Window.renderer, NULL);
}
int main(int argc, char * argv[])
{
int i;
int done = 0;
char title[256] = {0};
SDL_RendererInfo info;
SDL_Surface * tmpsurface;
SDL_Rect src_rect, dst_rect;
int renderer_has_target_texture_support = 0;
SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS);
Window.handle = SDL_CreateWindow("Loading...",
SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 800, 500, SDL_WINDOW_RESIZABLE);
Window.renderer = SDL_CreateRenderer(Window.handle, -1, 0);
SDL_GetWindowSize(Window.handle, &Window.width, &Window.height);
/* Checking if this renderer supports target textures */
SDL_GetRendererInfo(Window.renderer, &info);
renderer_has_target_texture_support = info.flags & SDL_RENDERER_TARGETTEXTURE;
SDL_Log("Renderer %s started.", info.name);
SDL_strlcat(title, info.name, sizeof(title));
if (!renderer_has_target_texture_support) {
SDL_Log(" Renderer has no target texture support!");
return 1;
}
SDL_strlcat(title, ": Combine With Target Texture Example", sizeof(title));
SDL_SetWindowTitle(Window.handle, title);
/* Image loading part. */
tmpsurface = SDL_LoadBMP("image_1.bmp");
if (tmpsurface == NULL) {
SDL_Log("Could not load image_1.bmp");
return 1;
} else {
/* We'll save these values and used them for the target texture width and height */
src_rect.w = tmpsurface->w;
src_rect.h = tmpsurface->h;
Window.texture1 = SDL_CreateTextureFromSurface(Window.renderer, tmpsurface);
SDL_SetTextureBlendMode(Window.texture1, SDL_BLENDMODE_BLEND);
SDL_FreeSurface(tmpsurface);
}
tmpsurface = SDL_LoadBMP("image_2.bmp");
if (tmpsurface == NULL) {
SDL_Log("Could not load image_2.bmp");
return 1;
} else {
Window.texture2 = SDL_CreateTextureFromSurface(Window.renderer, tmpsurface);
SDL_SetTextureBlendMode(Window.texture2, SDL_BLENDMODE_BLEND);
SDL_FreeSurface(tmpsurface);
}
/* Creation of the target texture. */
Window.texture_target = SDL_CreateTexture(Window.renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_TARGET, src_rect.w, src_rect.h);
/* Blend mode defaults to NONE, but we want it to blend it with the blue background */
SDL_SetTextureBlendMode(Window.texture_target, SDL_BLENDMODE_BLEND);
/* Drawing the other two textures to the target texture. */
draw_to_target_texture();
while (!done) {
SDL_Event e;
while (SDL_PollEvent(&e)) {
if (e.type == SDL_QUIT) {
done = 1;
} else if (e.type == SDL_KEYUP) {
Uint32 sym = e.key.keysym.sym;
if (sym == SDLK_ESCAPE) {
done = 1;
} else if (sym == SDLK_f) {
if (SDL_GetWindowFlags(Window.handle) & SDL_WINDOW_FULLSCREEN) {
SDL_SetWindowFullscreen(Window.handle, SDL_FALSE);
} else {
SDL_SetWindowFullscreen(Window.handle, SDL_WINDOW_FULLSCREEN_DESKTOP);
}
}
} else if (e.type == SDL_WINDOWEVENT) {
if (e.window.event == SDL_WINDOWEVENT_RESIZED || e.window.event == SDL_WINDOWEVENT_SIZE_CHANGED) {
SDL_GetWindowSize(Window.handle, &Window.width, &Window.height);
/* Some backends *cough*direct3d*cough* reset when the window size changes.
Target textures are not saved and have to be redrawn. */
draw_to_target_texture();
}
}
}
SDL_SetRenderDrawColor(Window.renderer, 0, 0, 200, 0);
SDL_RenderClear(Window.renderer);
SDL_RenderCopy(Window.renderer, Window.texture_target, NULL, NULL);
SDL_RenderPresent(Window.renderer);
SDL_Delay(3);
}
SDL_DestroyTexture(Window.texture1);
SDL_DestroyTexture(Window.texture2);
SDL_DestroyTexture(Window.texture_target);
SDL_DestroyRenderer(Window.renderer);
SDL_DestroyWindow(Window.handle);
SDL_Quit();
return 0;
}
```[/details]
There's also the option to do the combining with `SDL_Surface`s. This doesn't offer GPU acceleration or the other renderer options, but it will be supported on all systems. If you just need to prepare something at the start of your program and never change it, this may be a practical solution.
[details=combine_with_surface.c]
```C
#include "SDL.h"
static struct {
SDL_Window * handle;
int width, height;
SDL_Renderer * renderer;
SDL_Texture * texture1;
} Window;
int main(int argc, char * argv[])
{
int i;
int done = 0;
char title[256] = {0};
SDL_RendererInfo info;
SDL_Surface * surface1;
SDL_Surface * surface2;
SDL_Rect src_rect, dst_rect;
SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS);
Window.handle = SDL_CreateWindow("Loading...",
SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 800, 500, SDL_WINDOW_RESIZABLE);
Window.renderer = SDL_CreateRenderer(Window.handle, -1, 0);
SDL_GetWindowSize(Window.handle, &Window.width, &Window.height);
SDL_GetRendererInfo(Window.renderer, &info);
SDL_Log("Renderer %s started.", info.name);
SDL_strlcat(title, info.name, sizeof(title));
SDL_strlcat(title, ": Combine With Surface Example", sizeof(title));
SDL_SetWindowTitle(Window.handle, title);
/* Image loading part. */
surface1 = SDL_LoadBMP("image_1.bmp");
if (surface1 == NULL) {
SDL_Log("Could not load image_1.bmp");
return 1;
}
surface2 = SDL_LoadBMP("image_2.bmp");
if (surface2 == NULL) {
SDL_Log("Could not load image_2.bmp");
return 1;
}
/* Drawing the second surface to the first one. */
dst_rect.x = 0;
dst_rect.y = 0;
dst_rect.w = surface1->w;
dst_rect.h = surface1->h;
SDL_BlitScaled(surface2, NULL, surface1, &dst_rect);
/* Creation of the texture from the combined surface. */
Window.texture1 = SDL_CreateTextureFromSurface(Window.renderer, surface1);
/* The blend mode gets copied from the surface, but let's just force it here. */
SDL_SetTextureBlendMode(Window.texture1, SDL_BLENDMODE_BLEND);
SDL_FreeSurface(surface1);
SDL_FreeSurface(surface2);
while (!done) {
SDL_Event e;
while (SDL_PollEvent(&e)) {
if (e.type == SDL_QUIT) {
done = 1;
} else if (e.type == SDL_KEYUP) {
Uint32 sym = e.key.keysym.sym;
if (sym == SDLK_ESCAPE) {
done = 1;
} else if (sym == SDLK_f) {
if (SDL_GetWindowFlags(Window.handle) & SDL_WINDOW_FULLSCREEN) {
SDL_SetWindowFullscreen(Window.handle, SDL_FALSE);
} else {
SDL_SetWindowFullscreen(Window.handle, SDL_WINDOW_FULLSCREEN_DESKTOP);
}
}
} else if (e.type == SDL_WINDOWEVENT) {
if (e.window.event == SDL_WINDOWEVENT_RESIZED || e.window.event == SDL_WINDOWEVENT_SIZE_CHANGED) {
SDL_GetWindowSize(Window.handle, &Window.width, &Window.height);
}
}
}
SDL_SetRenderDrawColor(Window.renderer, 0, 0, 200, 0);
SDL_RenderClear(Window.renderer);
SDL_RenderCopy(Window.renderer, Window.texture1, NULL, NULL);
SDL_RenderPresent(Window.renderer);
SDL_Delay(3);
}
SDL_DestroyTexture(Window.texture1);
SDL_DestroyRenderer(Window.renderer);
SDL_DestroyWindow(Window.handle);
SDL_Quit();
return 0;
}
```[/details]
Since you specifically said "sum" you may also want to check out the `SDL_BLENDMODE_ADD` blend mode. This does adding instead of alpha blending.