Best way to deal with mirrored UV y-coordinate with OpenGL


When switching between OpenGL and Vulkan APIs a texture I’m drawing flips on the y-axis. I think this is because OpenGL uses reversed Y-axis for UV coordinates compared to DirectX and Vulkan. So how can I best handle this difference? Is there some global boolean I can read and then change my UV generations, or should I use #ifdef OPENGL in my shaders and flip it there? I remember Ogre having a render system function that returned true if the y-axis of textures was flipped. Is there something similar for bs::framework?


You can use ct::RenderAPI::getCapabilities(), which returns RenderAPICapabilities which has a conventions field, which contains the UV and NDC axis conventions used.


I got around to finally using this method. Here’s my code for checking if UVs need flipping:

    return bs::ct::RenderAPI::instance().getCapabilities(0).conventions.uvYAxis ==


I discovered something strange:
Changing the UV y-component on Linux between OpenGL and Vulkan (with that method works fine), but on Windows, DirectX API is not working correctly. It reports that the UVs are not flipped, but I have to set the UVs the same way as with OpenGL, for some reason. Vulkan works the same on Windows and Linux.
So I had to change my “is flipped” check to be line this:

    return bs::ct::RenderAPI::instance().getCapabilities(0).conventions.uvYAxis ==
               bs::Conventions::Axis::Up ||
           bs::ct::RenderAPI::instance().getCapabilities(0).renderAPIName == "D3D11RenderAPI";

And I have no idea why that is…


Depending if its relevant for your particular shader, the direction of the NDC y axis could also cause a flip. This information is also available in the conventions struct.

In OpenGL UV Y is flipped, NDC Y is not.
In DirectX UV Y is not flipped, neither is NDC Y.
In Vulkan UV Y is not flipped, but NDC Y is.

That would be my first guess, but it could be something else.


With some testing I found the values of those variables:

  uvYAxis = bs::Conventions::Axis::Up, 
  ndcYAxis = bs::Conventions::Axis::Up,

  uvYAxis = bs::Conventions::Axis::Down, 
  ndcYAxis = bs::Conventions::Axis::Down, 

uvyaxis: down
ndcyaxis: up

uvyaxis: up
ndcyaxis: up

uvyaxis: down
ndcyaxis: down

Armed with this knowledge I updated the flipped check to be:

    const auto capabilities = bs::ct::RenderAPI::instance().getCapabilities(0);

    return capabilities.conventions.ndcYAxis != bs::Conventions::Axis::Down;

Which seems to work fine for all of those platform / API combinations I tested.