Text Flickering on Window Resize in SDL2

I encountered a problem with text rendering in my application. I discovered that when changing the window height (winHeight), the text from the texture starts to flicker slightly only in the height position. I am looking for a solution that ensures stable text positioning regardless of the window size. Could you advise me on what kind of error I might be looking for in the code?

// API functions
Sub *sub_createWindow( int width, int height, const char *title, const char *color )
{
   setlocale( LC_ALL, "en_US.UTF-8" );

   Sub *sub = malloc( sizeof( Sub ) );
   if( !sub )
   {
      fprintf( stderr, "Memory allocation failed for Sub structure.\n" );
      return NULL;
   }

   memset( sub, 0, sizeof( Sub ) );
   sub->winWidth   = width;
   sub->winHeight  = height;
   sub->background = color;

   if( SDL_Init( SDL_INIT_VIDEO ) != 0 )
   {
      fprintf( stderr, "Unable to initialize SDL: %s\n", SDL_GetError() );
      free( sub );
      return NULL;
   }

   if( TTF_Init() == -1 )
   {
      fprintf( stderr, "Unable to initialize SDL_ttf: %s\n", TTF_GetError() );
      SDL_Quit();
      free( sub );
      return NULL;
   }

   sub->window = SDL_CreateWindow( title, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, width, height, SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE );
   if( !sub->window )
   {
      fprintf( stderr, "Could not create window: %s\n", SDL_GetError() );
      SDL_Quit();
      free( sub );
      return NULL;
   }

   sub->renderer = SDL_CreateRenderer( sub->window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC );
   if( !sub->renderer )
   {
      fprintf( stderr, "Could not create renderer: %s\n", SDL_GetError() );
      SDL_DestroyWindow( sub->window );
      SDL_Quit();
      free( sub );
      return NULL;
   }

   sub->fontFixed = TTF_OpenFont( "9x18.pcf.gz", 18 );
   if( !sub->fontFixed )
   {
      fprintf( stderr, "Failed to load font Fixed: %s\n", TTF_GetError() );
      SDL_DestroyRenderer( sub->renderer );
      SDL_DestroyWindow( sub->window );
      TTF_Quit();
      SDL_Quit();
      free( sub );
      return NULL;
   }

   /**/
   sub->fontWidth   = 9;
   sub->fontHeight  = 18;
   sub->fontDescent = 4;

   SDL_SetWindowMinimumSize( sub->window, sub->fontWidth, sub->fontHeight );

   sub->numLines = sub->winWidth * sub->winHeight;
   sub->textTextures = ( SDL_Texture ** ) malloc( sub->numLines * sizeof( SDL_Texture * ) );
   for( int i = 0; i < sub->numLines; i++ )
   {
      sub->textTextures[ i ] = NULL;
   }

   return sub;
}

void sub_destroyWindow( Sub *sub )
{
   if( sub )
   {
      if( sub->textTextures )
      {
         for( int i = 0; i < sub->numLines; i++ )
         {
            if( sub->textTextures[ i ] != NULL )
            {
               SDL_DestroyTexture( sub->textTextures[ i ] );
               sub->textTextures[ i ] = NULL;
            }
         }
         free( sub->textTextures );
         sub->textTextures = NULL;
      }

      if( sub->fontFixed != NULL )
      {
         TTF_CloseFont( sub->fontFixed );
         sub->fontFixed = NULL;
      }

      if( sub->renderer != NULL )
      {
         SDL_DestroyRenderer( sub->renderer );
         sub->renderer = NULL;
      }

      if( sub->window != NULL )
      {
         SDL_DestroyWindow( sub->window );
         sub->window = NULL;
      }

      TTF_Quit();
      SDL_Quit();

      free( sub );
      sub = NULL;
   }
}

void sub_beginDraw( Sub *sub )
{
   if( sub->texturesNeedUpdate )
   {
      for( int i = 0; i < sub->numLines; i++ )
      {
         if( sub->textTextures[ i ] != NULL )
         {
            SDL_DestroyTexture( sub->textTextures[ i ] );
            sub->textTextures[ i ] = NULL;
         }
      }

      sub->numLines = sub->winWidth * sub->winHeight;
      sub->textTextures = ( SDL_Texture ** ) malloc( sub->numLines * sizeof( SDL_Texture * ) );
      for( int i = 0; i < sub->numLines; i++ )
      {
         sub->textTextures[ i ] = NULL;
      }

      sub->texturesNeedUpdate = F;
   }

   SDL_Color bgColor = { 255, 255, 255, 255 };
   if( sub->background && strlen( sub->background ) > 0 )
   {
      bgColor = sub_hexToColor( sub->background );
   }

   SDL_SetRenderDrawColor( sub->renderer, bgColor.r, bgColor.g, bgColor.b, bgColor.a );
   SDL_RenderClear( sub->renderer );
}

void sub_endDraw( Sub *sub )
{
   SDL_RenderPresent( sub->renderer );
}

void sub_drawTextFixed( Sub *sub, int col, int row, const wchar_t *string, const char *colorString )
{

   int x = col * sub->fontWidth;
   int y = row * sub->fontHeight;

   SDL_Color bgColor = { 255, 255, 255, 255 };
   SDL_Color fgColor = { 0, 0, 0, 255 };

   if( colorString && strlen( colorString ) > 0 )
   {
      const char *separator = strchr( colorString, '/' );
      if( separator && ( separator - colorString == 6 ) && strlen( separator + 1 ) == 6 )
      {
         char bgColorStr[ 7 ];
         strncpy( bgColorStr, colorString, 6 );
         bgColorStr[ 6 ] = '\0';

         bgColor = sub_hexToColor( bgColorStr );
         fgColor = sub_hexToColor( separator + 1 );
      }
   }

   size_t len = wcslen( string );
   if( len == 0 )
   {
      SDL_Rect rect = { x, y, sub->fontWidth, sub->fontHeight };
      SDL_SetRenderDrawColor( sub->renderer, bgColor.r, bgColor.g, bgColor.b, bgColor.a );
      SDL_RenderFillRect( sub->renderer, &rect );
      return;
   }

   // Drawing a background for text
   SDL_Rect rect = { x, y, sub->fontWidth * len, sub->fontHeight };
   SDL_SetRenderDrawColor( sub->renderer, bgColor.r, bgColor.g, bgColor.b, bgColor.a );
   SDL_RenderFillRect( sub->renderer, &rect );

   // Buffering texture for a given line if it doesn't already exist
   if( sub->textTextures[ row ] == NULL )
   {
      size_t utf8Len = len * 4 + 1;
      char *utf8Text = ( char * ) malloc( utf8Len );

      if( utf8Text )
      {
         size_t result = wcstombs( utf8Text, string, utf8Len );
         if( result == ( size_t ) -1 )
         {
            printf( "Error converting wide string to multibyte string.\n" );
            free( utf8Text );
            return;
         }

         // Rendering text surface in UTF-8
         SDL_Surface *textSurface = TTF_RenderUTF8_Shaded( sub->fontFixed, utf8Text, fgColor, bgColor );
         if( textSurface )
         {
            sub->textTextures[ row ] = SDL_CreateTextureFromSurface( sub->renderer, textSurface );
            SDL_FreeSurface(textSurface);
         }
         else
         {
            printf( "Error rendering string: %s\n", TTF_GetError() );
         }

         free( utf8Text );
      }
   }

   // Render the cached texture, if any
   if( sub->textTextures[ row ] )
   {
      SDL_Rect textRect = { x, y, 0, 0 };
      SDL_QueryTexture( sub->textTextures[ row ], NULL, NULL, &textRect.w, &textRect.h );
      SDL_RenderCopy( sub->renderer, sub->textTextures[ row ], NULL, &textRect );
   }
}

int sub_procEvent( Sub *sub )
{
   SDL_Event event;
   if( SDL_WaitEvent( &event ) )
   {
      do
      {
         switch( event.type )
         {
            case SDL_QUIT:
               return SDL_QUIT;

            case SDL_KEYDOWN:
               return event.key.keysym.sym;

         case SDL_WINDOWEVENT:
            if( event.window.event == SDL_WINDOWEVENT_RESIZED )
            {
               int newWidth;
               int newHeight;
               SDL_GetWindowSize( sub->window, &newWidth, &newHeight );

               sub->winWidth = newWidth;
               sub->winHeight = newHeight;

               // Mark textures for re-creation
               sub->texturesNeedUpdate = T;
               return 0;
            }
            break;
         default:
            break;
         }
      }
      while( SDL_PollEvent( &event ) );
   }
   return 0;
}

Does it happen only while resizing the window or does the problem persist even after you’ve stopped resizing?

What do you mean by “flicker only in the height position”?

Yes, the problem occurs exclusively while resizing the window’s height (winHeight). Once the resizing stops, everything looks fine.

By “flicker only in the height position,” I mean that the text flickers only in the vertical direction, not horizontally.

Flicker for me is when light (or graphics) switch quickly between on and off (or the brightness is unstable). Do you mean that it flickers only when you change the height of the window, or do you mean that the y-coordinate of the text changes when you resize the window?

Okay, I did some testing and I can see the graphics “jumping” a bit on the y-axis while resizing the window height. This is on Linux (X11 / MATE). I see the same behavior with both SDL2 and SDL3.

I also noticed that the issue is much more noticeable if I enable vsync. On one computer (where I have an older version of SDL2 installed) I didn’t notice the issue at all when vsync was disabled. On another computer (with a relatively new version of SDL installed) there was still some glitches with vsync turned off but it happened much less frequently.

I have no idea how to fix this. Personally I have come to accept that there can be various graphical glitches when resizing an SDL window. It’s unfortunate but for games that are rarely resized I don’t think it’s a big deal (although it looks a bit unprofessional).

Thank you for taking the time to test and for sharing your observations! It’s interesting that the issue seems more pronounced with vsync enabled and that different versions of SDL have varying behaviors. I’m also using SDL2 on Linux (X11), so it’s good to know that this could be a broader issue rather than something specific to my code.

It’s not certain that the SDL version made a difference. It’s possible that differences in hardware, drivers, system settings, etc. is what makes it behave slightly different. I don’t know.

I was able to get it done, sorry for any confusion. You can find the complete code here.

I would be happy to hear any feedback and suggestions regarding my project. So far, it’s a file manager in SDL2. Any comments or opinions would be greatly appreciated. Link above.