GPU Python module: why drawed pixels are shifted in the result image? I'm using Python gpu module to render some simple rectangles in a 2D image.

The script retrieves current render settings:

def render_size( scene ):
    render = scene.render
    percent = render.resolution_percentage * 0.01
    dim_x = render.resolution_x * percent
    dim_y = render.resolution_y * percent
    return round(dim_x), round(dim_y)


Then calculates triangles based on some coordinates data and uses the following to make an offscreen rendering and get the result as pixel buffer:

def render_offscreen( dim_x, dim_y, vertices, indices, color = (0, 0.5, 0.5, 1.0) ):

    offscreen = gpu.types.GPUOffScreen( dim_x, dim_y )

    with offscreen.bind():

        shader = gpu.shader.from_builtin('2D_UNIFORM_COLOR')
        batch = batch_for_shader(shader, 'TRIS', {"pos": vertices}, indices=indices)

        shader.bind()

        shader.uniform_float("color", color)
        batch.draw(shader)

        buffer = bgl.Buffer(bgl.GL_BYTE, dim_x * dim_y * 4)
        bgl.glReadBuffer(bgl.GL_BACK)
        bgl.glReadPixels(0, 0, dim_x, dim_y, bgl.GL_RGBA, bgl.GL_UNSIGNED_BYTE, buffer)

    offscreen.free()

    return buffer


Last, it transfers the buffer into an image:

def buffer_to_image( dim_x, dim_y, image_name, buffer ):

    if not image_name in bpy.data.images:
        bpy.data.images.new(image_name, dim_x, dim_y)

    image = bpy.data.images[image_name]
    image.scale(dim_x, dim_y)
    image.pixels = [v / 255 for v in buffer]

    return image


The problem is coordinates are shifted in the result. What should be here (1821, 981) is (1881, 1002), and that seems to be around 3.x% of the input values (I mean, this shift seems to be proportional along the axis):



What am I missing?

The complete code is here:
I was lucky enough to be drived to the solution by dr.sybren, hypersomniac and mano_wii!!

The solution is to setup the projection matrix appropriately to translate wanted coordinates in pixels (in my case 1920x1080) to normalized coordinated between -1 and 1. 

So before running the batch, we need to reduce the scale by half the dimensions and translate the projection by -1 in x and y:

projection_matrix = Matrix.Diagonal( (2.0 / dim_x, 2.0 / dim_y, 1.0) )
projection_matrix = Matrix.Translation( (-1.0, -1.0, 0.0) ) @ projection_matrix.to_4x4()
gpu.matrix.load_projection_matrix(projection_matrix)


The complete code part is:

def render_offscreen( dim_x, dim_y, vertices, indices, color = (0, 0.5, 0.5, 1.0) ):

    offscreen = gpu.types.GPUOffScreen( dim_x, dim_y )

    with offscreen.bind():

        with gpu.matrix.push_pop():            
            projection_matrix = Matrix.Diagonal( (2.0 / dim_x, 2.0 / dim_y, 1.0) )
            projection_matrix = Matrix.Translation( (-1.0, -1.0, 0.0) ) @ projection_matrix.to_4x4()

            gpu.matrix.load_projection_matrix(projection_matrix)

            shader = gpu.shader.from_builtin('2D_UNIFORM_COLOR')
            batch = batch_for_shader(shader, 'TRIS', {"pos": vertices}, indices=indices)

            shader.bind()
            shader.uniform_float("color", color)
            batch.draw(shader)

            buffer = bgl.Buffer(bgl.GL_BYTE, dim_x * dim_y * 4) 
            bgl.glReadBuffer(bgl.GL_BACK)

            bgl.glReadPixels(0, 0, dim_x, dim_y, bgl.GL_RGBA, bgl.GL_UNSIGNED_BYTE, buffer)

    offscreen.free()

    return buffer