Fbo + gpgpu

I have 2+ pass rendering where the first stage generates the positions of the vertices of a rectangular mesh and stores them in a 2D float texture. The shaders look this way:

vertex:

#version 330 core
out vec2 vfTexCoords;

const float halfSide = 1.0f;
const vec2[] pos = vec2[ 4 ]
(
    vec2( -halfSide, -halfSide ),
    vec2(  halfSide, -halfSide ),
    vec2(  halfSide,  halfSide ),
    vec2( -halfSide,  halfSide )
);

const vec2[] tex = vec2[ 4 ]
(
    vec2( 0.0f, 0.0f ),
    vec2( 1.0f, 0.0f ),
    vec2( 1.0f, 1.0f ),
    vec2( 0.0f, 1.0f )
);

void main()
{
    vfTexCoords = tex[ gl_VertexID ];
    gl_Position = vec4( pos[ gl_VertexID ], 0.0f, 1.0f );
}

fragment:

in vec2 vfTexCoords;

layout (location = 0) out vec3 pos;

void main()
{
    pos = vec3( vfTexCoords, 0.0f );
}

The output 2D float texture is resized to the dimensions of the rectangular vertices mesh and is bind it to one of the color attachments of the FBO. Thus the FBO size is inherited from the texture size.

The viewport is setup to have the same dimensions as the size of the rectangular mesh:

glViewport( 0, 0, horizontalVerticesCount, verticalVerticesCount );

So, now both the viewport and the texture have the same size and after:

glDrawArrays( GL_TRIANGLE_FAN, 0, 4 );

… the output vertices’ positions should range from vec3(0.0, 0.0, 0.0) to vec3(1.0, 1.0, 0.0), since texture coords vary from 0.0, 0.0 to 1.0, 1.0.

What is strange is that the x and y values of the output vertex positions from the fragment shader start from non-zero and their max value is below 1.0.
For a 2 x 2 mesh:
bottom-left: vec3( 0.25, 0.25, 0.0 ) ( 0.5 / mesh_side = 0.5 / 2 = 0.25 )
top-right: vec3( 0.75, 0.75, 0.0 )

For a 16 x 16 mesh:
bottom-left: vec3( 0.03125, 0.03125 , 0.0 ) ( 0.5 / mesh_side = 0.5 / 16 = 0.03125 )
top-right: vec3( 0.96875, 0.96875, 0.0 )

Having the vertex shader positions set from vec3(-1.0, -1.0, 0.0) to vec3(1.0, 1.0, 0.0) should mean that I am “drawing” over the entire viewport.
Modifying the vertex shader’s positions results in different output values from the fragment shader.

What is wrong with the code above and why the drawing starts from 0.5 / rect_mesh_side_length?

In the absence of multi-sampling, varyings are typically interpolated at the centre of each fragment, not the corners.