Search code examples
c++sdl-2sdl-ttf

SDL Transfer texture using SDL_TEXTUREACCESS_STREAMING


I'm using SDL2 with SDL_TEXTUREACCESS_STREAMING without OpenGL and I want to transfer/combine some Textures from my function to my main loop. So, I decided to change my Access type from STREAMING to TARGET, use SDL_SetRenderTarget with SDL_RenderCopy and again put the Access type to STREAMING, but when I preview it with the SDL_RenderPresent not is showing the sended texture.

Here is my function to Open the textures:

TUserFunc(
    void, OpenTTF, Reference font, int32_t align, int32_t x, int32_t y, Reference txt, int32_t scl)
{    
    int tgt_w = g_w;
    int tgt_h = g_h; 
 
    TTF_Font * ttfFont = TTF_OpenFont(pu->refToAstr(CP_THREAD_ACP, font).c_str(), scl);
    if(ttfFont == nullptr) { 
        SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "SDL ERROR", "Error cargando la direccion de las fuentes", NULL);
        quitGame();
    } 
    
    SDL_Surface * surfaceText = TTF_RenderText_Blended(ttfFont, (pu->refToAstr(CP_THREAD_ACP, txt).c_str()), color);
    if(surfaceText == nullptr){
        SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "SDL ERROR", "Error creando Superficie", NULL);
        quitGame();
    } 
    
    textureText = SDL_CreateTextureFromSurface(g_renderer,surfaceText); 
    if(textureText == nullptr){
        SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "SDL ERROR", "Error creando Texturas", NULL);
        quitGame();
    } 

    SDL_Rect rectangle;
    rectangle.x = x;
    rectangle.y = y;
    rectangle.w = surfaceText->w;
    rectangle.h = surfaceText->h;

//The Problem Start from here

    unlockTarget(); 
    g_target = SDL_CreateTexture(g_renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_TARGET, tgt_w, tgt_h);

    SDL_SetRenderTarget(g_renderer, g_target);
    SDL_RenderCopy(g_renderer, textureText, NULL, &rectangle);
    SDL_SetRenderTarget(g_renderer, NULL);
     
    g_target = SDL_CreateTexture(g_renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, tgt_w, tgt_h);
    lockTarget();
}

lockTarget() and unlockTarget() it's for a simple way to lock and unlock the texture, and how you can see, I use the CreateTexture to change to TARGET, put my SetRenderTarget and RenderCopy and again use the CreateTexture to change back to STREAMING

And this is my main loop to show the graphics:

TUserFunc(void, Flip)
{       

    if(g_target){
        unlockTarget();
        SDL_RenderCopy(g_renderer, g_target, nullptr, nullptr); 
        lockTarget();
    }     
    SDL_RenderPresent(g_renderer);
}

but if I add this, the texture is showed, but not same texture, I mean, not is combined with g_target

TUserFunc(void, Flip)
{       

    if(g_target){
        unlockTarget();
        SDL_RenderCopy(g_renderer, g_target, nullptr, nullptr); 
        lockTarget();
    }    
    if(textureText){
        unlockTarget();
        SDL_RenderCopy(g_renderer, textureText, nullptr, nullptr); 
        lockTarget();
    }
    SDL_RenderPresent(g_renderer);
}

Here you can see the other functions to have a better view about the problem:

TUserFunc(bool, Init...)
{
...
        // This is the main and unique call to g_target
    g_target = SDL_CreateTexture(g_renderer, SDL_PIXELFORMAT_ARGB8888,
            SDL_TEXTUREACCESS_STREAMING, w, h);
    SDL_SetTextureBlendMode(g_target, SDL_BLENDMODE_NONE);
...
}

void lockTarget()
{
    if(g_target) SDL_LockTexture(g_target, nullptr, (void**)&g_pix, &g_pitch);
    if(textureText) SDL_LockTexture(textureText, nullptr, (void**)&g_pix, &g_pitch);
}

void unlockTarget()
{
    if(g_target) SDL_UnlockTexture(g_target);
    if(textureText) SDL_UnlockTexture(textureText);
}

Solution

  • The problem

    This can't work:

    unlockTarget(); 
    g_target = SDL_CreateTexture(g_renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_TARGET, tgt_w, tgt_h);
    
    SDL_SetRenderTarget(g_renderer, g_target);
    SDL_RenderCopy(g_renderer, textureText, NULL, &rectangle);
    SDL_SetRenderTarget(g_renderer, NULL);
     
    g_target = SDL_CreateTexture(g_renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, tgt_w, tgt_h);
    lockTarget();
    

    You create a texture, render another one onto it, and then proceed to get rid of its pointer and create a new one.

    SDL_CreateTexture, as its name implies, creates a texture.

    With this line:

    g_target = SDL_CreateTexture(g_renderer, SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, tgt_w, tgt_h);
    

    You are essentially throwing away whatever g_target held and replaces it with a new, empty texture with STREAMING access.

    I use the CreateTexture to change to TARGET, put my SetRenderTarget and RenderCopy and again use the CreateTexture to change back to STREAMING

    This simply not how it works. You can't change a texture's access mode once it has been created. All that SDL_CreateTexture does is creating yet another texture.

    As a result, all you do is leaking memory and get non-working code.

    The clean solution

    An SDL_Texture, according to the documentation (archive), is:

    A structure that contains an efficient, driver-specific representation of pixel data.

    You aren't supposed to do anything with SDL_Textures besides rendering them or rendering other textures onto it. Changing their access mode in real time is not possible.

    If you really need to render textures on top of others and then access their pixel data, then use SDL_Surfaces (archive):

    A structure that contains a collection of pixels used in software blitting.

    You can blit those on top of others as well, using SDL_BlitSurface. Plus, you can access SDL_Surfaces' pixel data for further editing.

    And only THEN, where you are DONE touching to pixel data, convert it into a texture using SDL_CreateTextureFromSurface and render it.

    It might be slower than trying to mess directly with textures, but at least it works and is meant to be done.

    Sure, STREAMING mode exists on textures for a reason, but all you can do with it is hacky trickery to get things poorly done where using surfaces would have been much better.

    And besides, you can't change a texture's access mode from TARGET to STREAMING as I have already said, so you don't really have a choice here. You need to use surfaces.