Rendering text in OpenGL off screen

Hello I try to draw some text in the GPU offscreen.

Somehow the text is skewed and blf.aspect does not seem to have any effect.
Does anyone have an idea ?? Ideally i would like to draw text in normalized device coordinates.
There the text is way way tooo big.

MacOS High Sierra, NVIDIA driver GTX 750, Blender git,
GL Version shows 4.1 but glBegin is not supported ? Do i need to enable gl extensions on osx GL_HInt ?

Here is my script

import bpy
import gpu
import bgl
import blf
import random
from mathutils import Matrix

IMAGE_NAME = "Generated Image"
WIDTH = 500
HEIGHT = 500

offscreen = gpu.types.GPUOffScreen(WIDTH, HEIGHT)

with offscreen.bind():
    bgl.glClearColor(0.2, 0.2, 0.2, 1.0)
    bgl.glClear(bgl.GL_COLOR_BUFFER_BIT)

    version = bgl.glGetString(bgl.GL_VERSION)
    print(version)

    with gpu.matrix.push_pop():
    
        font_id = 0
        blf.position(font_id, 0, 0, 0)
        #blf.aspect(font_id, 0.9)
        blf.size(font_id, 50, 72)
        blf.color(font_id, 1.0, 1.0, 1.0, 1.0)
        blf.draw(font_id, "Offscreen World")
    
    buffer = bgl.Buffer(bgl.GL_BYTE, WIDTH * HEIGHT * 4)
    bgl.glReadBuffer(bgl.GL_BACK)
    bgl.glReadPixels(0, 0, WIDTH, HEIGHT, bgl.GL_RGBA, bgl.GL_UNSIGNED_BYTE, buffer)

offscreen.free()

if not IMAGE_NAME in bpy.data.images:
    bpy.data.images.new(IMAGE_NAME, WIDTH, HEIGHT)
image = bpy.data.images[IMAGE_NAME]
image.scale(WIDTH, HEIGHT)
image.pixels = [v / 255 for v in buffer]

I play a bit with the offscreen renderings, and I faced the same kind of problem as you.
I came across this solution:
https://blender.stackexchange.com/questions/153697/gpu-python-module-why-drawed-pixels-are-shifted-in-the-result-image

The problem is related to the projection matrix. I quote :

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)

Small comment in passing, the new foreach_set method applicable to pixels is available since version 2.83 and is much faster, especially if the buffer is in float.

All of this applied to your code:

import bpy
import gpu
import bgl
import blf
import random
from mathutils import Matrix

IMAGE_NAME = "Generated Image"
WIDTH = 500
HEIGHT = 500

offscreen = gpu.types.GPUOffScreen(WIDTH, HEIGHT)

with offscreen.bind():
    bgl.glClearColor(0.2, 0.2, 0.2, 1.0)
    bgl.glClear(bgl.GL_COLOR_BUFFER_BIT)

    version = bgl.glGetString(bgl.GL_VERSION)
    print(version)

    with gpu.matrix.push_pop():
        
        projection_matrix = Matrix.Diagonal( (2.0 / WIDTH, 2.0 / HEIGHT, 1.0) )
        projection_matrix = Matrix.Translation( (-1.0, -1.0, 0.0) ) @ projection_matrix.to_4x4()
        gpu.matrix.load_projection_matrix(projection_matrix)
    
        font_id = 0
        blf.position(font_id, 0, 0, 0)
        #blf.aspect(font_id, 0.9)
        blf.size(font_id, 50, 72)
        blf.color(font_id, 1.0, 1.0, 1.0, 1.0)
        blf.draw(font_id, "Offscreen World")
    
    buffer = bgl.Buffer(bgl.GL_FLOAT, WIDTH * HEIGHT * 4)
    bgl.glReadBuffer(bgl.GL_BACK)
    bgl.glReadPixels(0, 0, WIDTH, HEIGHT, bgl.GL_RGBA, bgl.GL_FLOAT, buffer)

offscreen.free()

if not IMAGE_NAME in bpy.data.images:
    bpy.data.images.new(IMAGE_NAME, WIDTH, HEIGHT)
image = bpy.data.images[IMAGE_NAME]
image.scale(WIDTH, HEIGHT)
image.pixels.foreach_set(buffer)

Good luck !

Hi klinkhammer,

i was able to solve it by uniformly scaling the matrix.

then position is between -1000 and 1000.

gpu.matrix.scale_uniform(0.001)
font_id = 0
blf.position(font_id, -1000, 0, 0)
blf.size(font_id, 15, 300)
blf.color(font_id, 0.6 , 0.6, 0.6, 1.0)
blf.draw(font_id, "180°")

strangely

does not work for me ( leaves me with a grey image )

Thanks for your answer !!

    image.pixels.foreach_set(buffer)

Leaves a uniform image because if the buffer is in byte, you have to divide the values by 255.

Change the buffer to float may solve the problem (and make the operation even faster)

    buffer = bgl.Buffer(bgl.GL_FLOAT, WIDTH * HEIGHT * 4)
    bgl.glReadBuffer(bgl.GL_BACK)
    bgl.glReadPixels(0, 0, WIDTH, HEIGHT, bgl.GL_RGBA, bgl.GL_FLOAT, buffer)

Yes with floats it works and it is about 5-10 ( felt ) times faster excellent !!

I also have trouble using glLineWidth which has no effect at all. Do i need to enable something ??

import bpy
import gpu
import bgl
import blf 
import random
from mathutils import Matrix
from gpu_extras.presets import draw_circle_2d
from gpu_extras.batch import batch_for_shader
import os
from math import cos, sin, pi

IMAGE_NAME = "Generated Image"
WIDTH = 500
HEIGHT = 500
RING_AMOUNT = 5

offscreen = gpu.types.GPUOffScreen(WIDTH, HEIGHT)

with offscreen.bind():
    bgl.glClearColor(0.3, 0.3, 0.3, 1.0)
    bgl.glClear(bgl.GL_COLOR_BUFFER_BIT)

    version = bgl.glGetString(bgl.GL_VERSION)
    print(version)

    with gpu.matrix.push_pop():
        # reset matrices -> use normalized device coordinates [-1, 1]
        gpu.matrix.load_matrix(Matrix.Identity(4))
        gpu.matrix.load_projection_matrix(Matrix.Identity(4))
    
        bgl.glEnable(bgl.GL_BLEND)
        bgl.glEnable(bgl.GL_LINE_SMOOTH)
        bgl.glLineWidth(2.0)
#circles       
        for i in range(RING_AMOUNT):
            draw_circle_2d(
                (0.0, 0.0), (0.1, 0.1, 0.1, 1.0), ((0.2 * i) + 0.2)*0.95, 50)

        bgl.glLineWidth(1.0)
        bgl.glDisable(bgl.GL_BLEND)
    
    buffer = bgl.Buffer(bgl.GL_FLOAT, WIDTH * HEIGHT * 4)
    bgl.glReadBuffer(bgl.GL_BACK)
    bgl.glReadPixels(0, 0, WIDTH, HEIGHT, bgl.GL_RGBA, bgl.GL_FLOAT, buffer)

offscreen.free()

if not IMAGE_NAME in bpy.data.images:
    bpy.data.images.new(IMAGE_NAME, WIDTH, HEIGHT)
image = bpy.data.images[IMAGE_NAME]
image.scale(WIDTH, HEIGHT)
image.pixels.foreach_get(buffer)

I also noticed that when working with custom Gizmos … line_width seems also not working with that .
Can you confirm that or is it a bug … at least on MacOS

Sorry, but I don’t know the API well enough to help you with this.

https://developer.blender.org/T76806

Thanks for the clarification