Coordinate system swap chain vs. offscreen rendering


as far as I know, Vulkan has a right hand coordinate system for NDC contrary to OpenGLs left hand system. However, when rendering to an offscreen target instead of swapchain apparently a left hand system has to be used AND the front face has to be reversed.

Can you point me to the paragraph in the standard that explains the differing coordinate systems of swapchain and offscreen rendering targets?

Thank you

No. Because such a paragraph doesn’t exist. Vulkan does not make a distinction between rendering to a swapchain image and rendering to a non-swapchain image. There is nothing in pipeline construction or the render pass architecture that allows you to even specify whether a particular attachment is a swapchain image. There is nothing in secondary command buffer recording that allows you to specify which attachments are swapchain images. Therefore, it’s pretty much impossible for a Vulkan implementation to radically change how rendering works when rendering to a swapchain image.

You are either encountering a driver bug or you’re misunderstanding what you’re seeing. My guess is that you’re reading back an off-screen buffer on the CPU and are misinterpreting the order of the rows of data.

1 Like

I mean in this tutorial, the necessity of step 2:

Why is this only necessary for offscreen targets?

Most likely because he is flipping y, which as a side effect flips the face of all the geometry. For offscreen buffers he keeps the y be, but has the images “upside down”:

The result is rendered to the offscreen framebuffers flipped.

Any reason why he does that? Like that texture coordinates are oriented differently than screen cooridnates?

I am not that good of a mind reader. Best ask the author. It makes kinda sense though to only limit coordinate shenanigans only to where it matters.

@Alfonse_Reinheart For a little clarification what is done:

The original OpenGL engine renders first to two FBOs with a CCW winding order. The texture attachments are then bound to the final step which renders to the main Framebuffer. This final step renders a quad with NDC coordinates (-1,-1) to (1,1). NDC coordinate (-1,-1) maps to texture coordinate (0,0), NDC coordinate (1,1) maps to texture coordinate (1,1). These texture coordinates are then used to read from the former texture attachments.

At first I thought it would be enough to enable KHR_VK_maintainance1 and flip the viewport y axis. But when I reenabled the offscreen targets I suddenly had to use the unflipped y axis and opposite winding order like in the linked page. This is why I thought Vulkan treats offscreen render targets differently.

Ok, some more questions:

I assume that texture coordinate (s,t) = (0,0) for an image corresponds to (0,0) in the image and (s,t)=(1,1) corresponds to (width,height) in the image, right? And that when rendering to an image gl_FragCoord.xy corresponds to (x,y) in the image, right? However, in Vulkan gl_FragCoord.y grows downward in screenspace, so the top-right corner would need texture coordinate (s,t)=(1,0) to make up for the flip, right?

Btw will the maintenance extension+y flip save me from reversing gl_FragCoord.y instances?

Yea (in case it is normalized).

gl_FragCoord should return the framebuffer coordinates, i.e. whatever is the result of the viewport transform (with the twist you get the center of the fragment, e.g. 42.5). Yea, it will match the (integer) image coordinate, minus the fractional part.

Normalized texel coordinates match the orientation of image coordinates, except they are normalized.

downward in screenspace

There’s no screenspace if there is no screen. Swapchain (on all known platforms thus far) will show (0,0) image coordinate pixel to the upper-left corner of the window (plus minus pre-transform). But if there is no window, then there is no “up” and “down”.

so the top-right corner would need texture coordinate (s,t)=(1,0) to make up for the flip, right?

Depends what you are going for and how you are doing it. Just don’t overthink it.

If you are texturing random triangle it does not matter. You can flip the triangle however you like and it it is textured the same (flipping only changes the position of the vertices).

If you want to overlay some UI directly to screen, then the above applies. Whatever you want to show in upper-left corner of the window has to be assigned to gl_FragColor at (0.5, 0.5) framebuffer coordinates. How you achieve that is up to you.

Btw will the maintenance extension+y flip save me from reversing gl_FragCoord.y instances?

It’s what it does. Changing the viewport transform changes the framebuffer coordinates.

Changing framebuffer coordinates changes the facingness of triangles though.

1 Like

Put it this way. The texture coordinate (0, 0) represents the first texel of the first row of the texture. What that means with regard to up and down depends entirely on how you map it with respect to what you’re rendering.

If you’re drawing a quad on the screen, then it has an upper-left corner in screen space. You can map a texture to this quad such that the first texel of the first row lands in the upper-left corner of the quad in screen space. To do this, the vertex you expect to be its upper-left corner should have the texture coordinate (0, 0).

You could also map it such that the first texel of the last row of the quad is the upper-left corner in screen space. You would do this by using the texture coordinate (0, 1).

I’m still struggling with this. I use the original data/render commands from the OpenGL code but with the maintenance extension and the viewport y flip / front face flip. A large portion of my offscreen rendered textures is upside down.

Can you imagine any other viewport related calculations that won’t just work out of the box after using the above when going from GL to Vulkan?

Ok, I did the following test now:
Render a full screen quad to an offscreen target with enabled viewport flip. Color output was the NDC x/y coordinate (normalized to (0,1) ).

My assumption would be that if the viewport flip caused the system to behave like OpenGL that NDC=(-1,-1) would map to pixel (0,0) in the offscreen target texture and NDC=(1,1) to pixel (width,height). Instead, NDC=(-1,-1) mapped to (0,height) and NDC=(1,1) mapped to (width,0). I checked the coordinates with the resource view of NVIDIA Nsight Debugger.

Rendering to a swapchain image instead gives the desired results.

Ok, now I’m totally confused:
As far as I can tell the viewport transform in Vulkan
is the same as in OpenGL:

So which of the many transformations in the pipeline is flipped in Vulkan compared to OpenGL? If they are all the same, what causes the y-flip when porting from OpenGL to Vulkan?

There’s no up or down offscreen. Upside down relative to what??

Yes, you could easily use pre-flip coordinates in the shader in some math.

It maps to whatever the viewport transform says it maps. The math is h/2×y+oy+h/2. If the height is positive and oy=0 then y=-1 maps to 0. If the height is negative and oy=h (flipped viewport) then y=-1 maps to height in framebuffer coordinates.

When using swapchain, whatever is at (0,0) image coordinates ends up in upper-left corner of the window. In OpenGL it ends up in lower-left corner of the window instead. If it is offscreen, then there is no window, and no corners.

You remind me why I hate OpenGL so much. When you stop overthinking it and disabuse your brain of unnecessarily abstract concepts like “up” and “down”, the more it becomes clear it is just elementary school math for which you do not need three doctorates to understand.

1 Like

And this mapping is performed AFTER viewport transform, yes?

Yes, it happens when vkQueuePresent. Well, technically it happens never. It is just how the presentation engine interprets the image\memory. Typically for historical reasons, all presentation engines interpret the image the way that its “lower” coordinate ends up in upper line of the window. OpenGL was just different.

1 Like

Ok, with your combined posts I think I know what’s going on. Thank you!

Summary: Only the swapchain image renders are flipped now. Shaders using gl_FragCoord.y are flipped when they render to the swapchain images.

@krOoze Btw can you point me to the paragraph in the standard where this is detailed? I assume the same goes for texture uploads right?

Vulkan Swapchain:


The origin of the native orientation of the surface coordinate system is not specified in the Vulkan specification; it depends on the platform. For most platforms the origin is by default upper-left, meaning the pixel of the presented VkImage at coordinates (0,0) would appear at the upper left pixel of the platform surface (assuming VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR, and the display standing the right way up).

Everything else about coordinate systems:

Fixed-Function Vertex Post-Processing

For texture uploads with vkCmdCopyBufferToImage there’s a chapter about the addressing scheme: Buffer and Image Addressing. In short, lowest buffer memory address ends up at (0,0,0) of the Image. Similarly for mapped linear Image as described at vkGetImageSubresourceLayout.

Oftentimes image files also assume upper-left corner origin, so that has to be kept in mind.

1 Like