Rotation angle for SDL_RenderCopyEx

I’m using SDL2 version 2.0.10, with a Direct3D 11 renderer.

I’m simply trying to rotate a SDL_Texture, so I’m calling SDL_RenderCopyEx. For example, if I want to rotate 90 degrees, I call it like so :

float rotationAngle = 90.0f; // 90 degree clock-wise, I assumed
SDL_RenderCopyEx(renderer, texture, NULL, &destRect, rotationAngle , NULL, SDL_FLIP_NONE);

When I did that, I realized that the texture seems to rotate counter-clockwise (it was rotating to the left, instead of the right, like I thought it would do).

I found nothing online or in the documentation specifically explaining how the angles are supposed to work, but all tutorials I’ve found about rotation in SDL2 seemed to assume a rotation to the right, from 0.0f to 360.0f.

After lots of tests, I finally came up with a working (albeit very patchy) solution :

float rotationAngle = 90.0f; // The real rotation angle I want to apply

float rotationAngleFixed = rotationAngle + 180.0f;
// 360 is interpreted as 0, so we need to change it slightly if we want a 180-ish degree rotation
if(rotationAngleFixed == 360.0f)
  rotationAngleFixed -= 0.01f;
SDL_RenderCopyEx(renderer, texture, NULL, &destRect, rotationAngleFixed, NULL, SDL_FLIP_NONE);

This works all the time. And I very much dislike the fact that I’m about to push this to otehr developers.

Why do I need to do that? Is that a bug, or an undocumented feature?

The documentation for SDL_RenderCopyEx() explicitly states: “an angle in degrees that indicates the rotation that will be applied to dstrect, rotating it in a clockwise direction”. I have confirmed that a positive angle results in a clockwise rotation using the OpenGL renderer; I haven’t tried the Direct3D 11 renderer but I would be extremely surprised if such a serious bug has gone undetected.

If the rotation appears to be in the wrong direction I would look for an issue elsewhere, such as a World Transform flipping one of the axes.

I would be surprised too.

I also thought about the world matrix being flipped, but that doesn’t seem to be the case. I would have had other textures drawn flipped if that was the case.

Also, when I add 180.0f to the angle I’m passing to SDL_RenderCopyEx, it works for all angle values, except 180.0f (i.e. 360.0f), that I slightly need to fix to be “correct”.

That is really strange. For now, the OpenGL renderer doesn’t work for our project, but I’ll try Direct3D 9 and the software renderer to see if I get different results.

Okay, I investigated further.

I tried the Direct3D 9 renderer, but I get a D3DERR_INVALIDCALL when SDL2 is calling IDirect3D9_CreateDevice on my second renderer, so I can’t test.

The software renderer, though, works as expected, including the rotation. Meaning that if I set the rotation to 90.0f, I’m getting a 90 degrees rotation on the screen.

I’ll try to create a minimal complete example to try to reproduce the problem. If I’m still getting it, I’ll fille a bug in Bugzilla.

More investigation.

I created that Minimal Reproductible Example.

// Using SDL and standard IO
#include <SDL.h>
#include <SDL_image.h>
#include <stdio.h>
#include <stdbool.h>

//Screen dimension constants
const int SCREEN_WIDTH = 640;
const int SCREEN_HEIGHT = 480;

//The window we'll be rendering to
SDL_Window* gWindow = NULL;
SDL_Renderer* gRenderer = NULL;

//Event handler
SDL_Event e;

// =============================================================================

SDL_Texture* loadTexture( const char* path )
{
  //The final texture
  SDL_Texture* newTexture = NULL;

  //Load image at specified path
  SDL_Surface* loadedSurface = IMG_Load(path);
  if( loadedSurface == NULL )
  {
      printf( "Unable to load image %s! SDL_image Error: %s\n", path, IMG_GetError() );
  }
  else
  {
    //Create texture from surface pixels
    newTexture = SDL_CreateTextureFromSurface( gRenderer, loadedSurface );
    if( newTexture == NULL )
    {
      printf( "Unable to create texture from %s! SDL Error: %s\n", path, SDL_GetError() );
    }

    //Get rid of old loaded surface
    SDL_FreeSurface( loadedSurface );
  }

  return newTexture;
}


// Starts up SDL and creates window
bool Init(void)
{
  //Initialization flag
  bool success = true;

  //Initialize SDL
  if( SDL_Init( SDL_INIT_VIDEO ) < 0 )
  {
    printf( "SDL could not initialize! SDL_Error: %s\n", SDL_GetError() );
    success = false;
  }
  else
  {
//    SDL_SetHint(SDL_HINT_RENDER_DRIVER, "software");
//    SDL_SetHint(SDL_HINT_RENDER_DRIVER, "opengl");
//    SDL_SetHint(SDL_HINT_RENDER_DRIVER, "direct3d");
    SDL_SetHint(SDL_HINT_RENDER_DRIVER, "direct3d11");

    // Create window
    gWindow = SDL_CreateWindow( "SDL Tutorial", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, SCREEN_WIDTH, SCREEN_HEIGHT, SDL_WINDOW_SHOWN );
    if( gWindow == NULL )
    {
      printf( "Window could not be created! SDL_Error: %s\n", SDL_GetError() );
      success = false;
    }
    else
    {
      gRenderer = SDL_CreateRenderer(gWindow, -1, SDL_RENDERER_ACCELERATED );
      if( gRenderer == NULL )
      {
        printf( "Renderer could not be created! SDL Error: %s\n", SDL_GetError() );
        success = false;
      }
    }
  }

  return success;
}

void Finit(void)
{
  // Destroy window
  SDL_DestroyWindow( gWindow );
  gWindow = NULL;

  //Quit SDL subsystems
  SDL_Quit();
}

// =============================================================================

int main( int argc, char* args[] )
{
  //Initialize SDL
  if(!Init())
  {
    printf("Init() failed\n");
  }
  else
  {
    SDL_Texture* arrowTexture = loadTexture(".\\images\\UpArrow.png");
    SDL_Rect destination;

    if(!arrowTexture )
    {
      printf("No texture");
    }
    else
    {
      // Main Loop
      bool exit = false;
      SDL_QueryTexture(arrowTexture, NULL, NULL, &destination.w, &destination.h);
      destination.x = (SCREEN_WIDTH /2) - (destination.w /2) ;
      destination.y = (SCREEN_HEIGHT /2) - (destination.h /2) ;

      while(!exit)
      {
        SDL_RenderCopyEx(gRenderer, arrowTexture, NULL, &destination, 90.0f, NULL, SDL_FLIP_NONE);
        SDL_RenderPresent(gRenderer);

        //Handle events on queue
        while( SDL_PollEvent( &e ) != 0 )
        {
          //User requests quit
          if( e.type == SDL_QUIT )
          {
            exit = true;
          }
        }
      }
    }
  }

  Finit();
  return 0;
}

The texture I’m loading is a red arrow pointing up. I’m calling SDL_RenderCopyEx with a rotation of 90 degrees.

If I set the SDL_HINT_RENDER_DRIVER to anything other than direct3d11, I get a rotation of 90 degrees, as I was expecting.

With the Direct 3D 11 renderer, the arrow points to the left.

That seems to be a bug. I’ll report it to Bugzilla.

Reported :

LibSDL Bugzilla - BugID 4860

The problem was blocking production here, so I debugged it and submitted a patch.

See the link to the bug in my previous post.

I hope this fixes the problem.