crash or error when using GPUFrameBuffer.read_color(... data=data) #94202

Closed
opened 2021-12-17 20:25:41 +01:00 by Jakub Uhlik · 7 comments

System Information
Operating system: macOS-10.13.6-x86_64-i386-64bit 64 Bits
as well on macOS 10.15.7

Blender Version
Broken: version: 3.0.0, branch: master, commit date: 2021-12-02 18:35, hash: f1cca30557
Worked: ?

Short description of error
crash or error when using GPUFrameBuffer.read_color(... data=data) i.e. when buffer is created before calling

Exact steps for others to reproduce the error
what i want to do: draw something offscreen, then get color and depth buffers, draw to viewport processed in fragment shader

following code works, read_color is used without premade buffer, buffer returned is further used in texture (sorry for not so minimal example, i removed most of not relevant shader code)

import bpy
import gpu
import numpy as np
from gpu_extras.batch import batch_for_shader
from mathutils import Matrix


vert_3d = '''
in vec3 position;
in vec4 color;
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
uniform float size = 3.0;
uniform float alpha = 1.0;
out vec4 v_color;

void main()
{
    gl_Position = projection * view * model * vec4(position, 1.0);
    gl_PointSize = size;
    v_color = vec4(vec3(color.rgb), alpha);
}
'''
frag_3d = '''
in vec4 v_color;
out vec4 fragColor;

void main()
{
    vec4 f_color = v_color;
    fragColor = blender_srgb_to_framebuffer_space(f_color);
}
'''

vert_2d = '''
uniform mat4 ModelViewProjectionMatrix;
in vec2 pos;
out vec2 uv;
void main()
{
    gl_Position = ModelViewProjectionMatrix * vec4(pos.xy, 1.0f, 1.0f);
    uv = pos.xy;
}
'''
frag_2d = '''
in vec2 uv;
uniform sampler2D color;
uniform sampler2D depth;
uniform float near;
uniform float far;
out vec4 fragColor;

// source/blender/blenlib/intern/math_color.c
float linearrgb_to_srgb(float c)
{
    if (c < 0.0031308f) {
        return (c < 0.0f) ? 0.0f : c * 12.92f;
    }
    return 1.055f * pow(c, 1.0f / 2.4f) - 0.055f;
}
float srgb_to_linearrgb(float c)
{
    if (c < 0.04045f) {
        return (c < 0.0f) ? 0.0f : c * (1.0f / 12.92f);
    }
    return pow((c + 0.055f) * (1.0f / 1.055f), 2.4f);
}

vec4 convert_color(vec4 c)
{
    return vec4(srgb_to_linearrgb(c.r), srgb_to_linearrgb(c.g), srgb_to_linearrgb(c.b), c.a);
}

float linearize_depth(float d, float near, float far)
{
    return near * far / (far + d * (near - far));
}

void main()
{
    float d = texture(depth, uv).x;
    d = linearize_depth(d, near, far);
	d = (d == 1.0) ? 0.0 : d;
    vec4 rgba = texture(color, uv);
    fragColor = convert_color(vec4(vec3(d / 10), rgba.a));
}
'''


shader = gpu.types.GPUShader(vert_3d, frag_3d, )
n = 10000
vs = np.random.normal(0, 2, (n, 3), )
vs = vs.astype(np.float32)
cs = np.random.random((n, 4), )
cs = cs.astype(np.float32)
cs[:, 3] = 1.0
batch = batch_for_shader(shader, 'POINTS', {"position": vs, "color": cs, })


def redraw():
    for w in bpy.context.window_manager.windows:
        for a in w.screen.areas:
            if(a.type == 'VIEW_3D'):
                a.tag_redraw()


def draw_pre_view(self, context, ):
    context = bpy.context
    scene = context.scene
    region_data = context.region_data
    view = region_data.view_matrix
    projection = region_data.window_matrix
    
    viewport_info = gpu.state.viewport_get()
    width = viewport_info[2]
    height = viewport_info[3]
    
    if(self.offscreen is None):
        offscreen = gpu.types.GPUOffScreen(width, height)
        self.offscreen = offscreen
    else:
        offscreen = self.offscreen
        if(offscreen.width != width or offscreen.height != height):
            offscreen.free()
            offscreen = gpu.types.GPUOffScreen(width, height)
            self.offscreen = offscreen
    
    with offscreen.bind():
        fb = gpu.state.active_framebuffer_get()
        fb.clear(color=(0.0, 0.0, 0.0, 0.0), depth=1.0, stencil=0, )
        
        gpu.state.program_point_size_set(True)
        gpu.state.depth_test_set('LESS_EQUAL')
        gpu.state.blend_set('ALPHA')
        
        shader.bind()
        
        region_data = bpy.context.region_data
        model = Matrix()
        view = region_data.view_matrix
        projection = region_data.window_matrix
        shader.uniform_float("model", model)
        shader.uniform_float("view", view)
        shader.uniform_float("projection", projection)
        shader.uniform_float("size", 6)
        shader.uniform_float("alpha", 1.0)
        
        with gpu.matrix.push_pop():
            gpu.matrix.load_matrix(Matrix.Identity(4))
            gpu.matrix.load_projection_matrix(Matrix.Identity(4))
            batch.draw(shader)
        
        gpu.state.program_point_size_set(False)
        gpu.state.depth_test_set('NONE')
        gpu.state.blend_set('NONE')
        
        self.buffer = fb.read_color(0, 0, width, height, 4, 0, 'FLOAT', )
        self.depth = fb.read_depth(0, 0, width, height, )


def draw_post_pixel(self, context, ):
    context = bpy.context
    scene = context.scene
    region_data = context.region_data
    view = region_data.view_matrix
    projection = region_data.window_matrix
    space = context.space_data
    
    viewport_info = gpu.state.viewport_get()
    width = viewport_info[2]
    height = viewport_info[3]
    
    image = gpu.types.GPUTexture((width, height), format='RGBA32F', data=self.buffer, )
    depth = gpu.types.GPUTexture((width, height), format='DEPTH_COMPONENT32F', data=self.depth, )
    
    coords = ((0, 0), (1, 0), (1, 1), (0, 1))
    shader = gpu.types.GPUShader(vert_2d, frag_2d)
    batch = batch_for_shader(shader, 'TRI_FAN', {"pos": coords, }, )
    position = (0, 0)
    
    gpu.state.blend_set('ALPHA')
    
    with gpu.matrix.push_pop():
        gpu.matrix.translate(position)
        gpu.matrix.scale((width, height))
        
        shader.bind()
        shader.uniform_sampler("color", image)
        shader.uniform_sampler("depth", depth)
        shader.uniform_float("near", space.clip_start)
        shader.uniform_float("far", space.clip_end)
        
        batch.draw(shader)
    
    gpu.state.blend_set('NONE')


class ModalFramebuffer(bpy.types.Operator):
    bl_idname = "view3d.modal_framebuffer"
    bl_label = "Modal Framebuffer"

    def __init__(self):
        print("start..")
    
    def __del__(self):
        redraw()
        print("end.")
    
    def modal(self, context, event):
        if(event.type in {'ESC', }):
            print("exiting..")
            bpy.types.SpaceView3D.draw_handler_remove(self.handle_pre_view, 'WINDOW')
            bpy.types.SpaceView3D.draw_handler_remove(self.handle_post_pixel, 'WINDOW')
            if(self.offscreen is not None):
                self.offscreen.free()
                self.offscreen = None
            return {'CANCELLED'}
        
        return {'PASS_THROUGH'}
    
    def invoke(self, context, event):
        print("invoke..")
        
        self.offscreen = None
        self.handle_pre_view = bpy.types.SpaceView3D.draw_handler_add(draw_pre_view, (self, context), 'WINDOW', 'PRE_VIEW')
        self.handle_post_pixel = bpy.types.SpaceView3D.draw_handler_add(draw_post_pixel, (self, context), 'WINDOW', 'POST_PIXEL')
        
        context.window_manager.modal_handler_add(self)
        
        redraw()
        return {'RUNNING_MODAL'}


def register():
    bpy.utils.register_class(ModalFramebuffer)


def unregister():
    bpy.utils.unregister_class(ModalFramebuffer)


if __name__ == "__main__":
    register()
    bpy.ops.view3d.modal_framebuffer('INVOKE_DEFAULT')

for performance reasons (on fullscreen it is no longer acceptable) i wanted to NOT create new buffers and textures on each redraw, hence this code


import bpy
import gpu
import numpy as np
from gpu_extras.batch import batch_for_shader
from mathutils import Matrix


vert_3d = '''
in vec3 position;
in vec4 color;
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
uniform float size = 3.0;
uniform float alpha = 1.0;
out vec4 v_color;

void main()
{
    gl_Position = projection * view * model * vec4(position, 1.0);
    gl_PointSize = size;
    v_color = vec4(vec3(color.rgb), alpha);
}
'''
frag_3d = '''
in vec4 v_color;
out vec4 fragColor;

void main()
{
    vec4 f_color = v_color;
    fragColor = blender_srgb_to_framebuffer_space(f_color);
}
'''

vert_2d = '''
uniform mat4 ModelViewProjectionMatrix;
in vec2 pos;
out vec2 uv;
void main()
{
    gl_Position = ModelViewProjectionMatrix * vec4(pos.xy, 1.0f, 1.0f);
    uv = pos.xy;
}
'''
frag_2d = '''
in vec2 uv;
uniform sampler2D color;
uniform sampler2D depth;
uniform float near;
uniform float far;
out vec4 fragColor;

// source/blender/blenlib/intern/math_color.c
float linearrgb_to_srgb(float c)
{
    if (c < 0.0031308f) {
        return (c < 0.0f) ? 0.0f : c * 12.92f;
    }
    return 1.055f * pow(c, 1.0f / 2.4f) - 0.055f;
}
float srgb_to_linearrgb(float c)
{
    if (c < 0.04045f) {
        return (c < 0.0f) ? 0.0f : c * (1.0f / 12.92f);
    }
    return pow((c + 0.055f) * (1.0f / 1.055f), 2.4f);
}

vec4 convert_color(vec4 c)
{
    return vec4(srgb_to_linearrgb(c.r), srgb_to_linearrgb(c.g), srgb_to_linearrgb(c.b), c.a);
}

float linearize_depth(float d, float near, float far)
{
    return near * far / (far + d * (near - far));
}

void main()
{
    float d = texture(depth, uv).x;
    d = linearize_depth(d, near, far);
	d = (d == 1.0) ? 0.0 : d;
    vec4 rgba = texture(color, uv);
    fragColor = convert_color(vec4(vec3(d / 10), rgba.a));
}
'''


shader = gpu.types.GPUShader(vert_3d, frag_3d, )
n = 10000
vs = np.random.normal(0, 2, (n, 3), )
vs = vs.astype(np.float32)
cs = np.random.random((n, 4), )
cs = cs.astype(np.float32)
cs[:, 3] = 1.0
batch = batch_for_shader(shader, 'POINTS', {"position": vs, "color": cs, })


def redraw():
    for w in bpy.context.window_manager.windows:
        for a in w.screen.areas:
            if(a.type == 'VIEW_3D'):
                a.tag_redraw()


def draw_pre_view(self, context, ):
    context = bpy.context
    scene = context.scene
    region_data = context.region_data
    view = region_data.view_matrix
    projection = region_data.window_matrix
    
    viewport_info = gpu.state.viewport_get()
    width = viewport_info[2]
    height = viewport_info[3]
    
    if(self.offscreen is None):
        offscreen = gpu.types.GPUOffScreen(width, height)
        self.offscreen = offscreen
        
        self.color_buffer = gpu.types.Buffer('FLOAT', (width, height, 4))
        self.depth_buffer = gpu.types.Buffer('FLOAT', (width, height, 1))
        self.color_tex = gpu.types.GPUTexture((width, height), format='RGBA32F', data=self.color_buffer, )
        self.depth_tex = gpu.types.GPUTexture((width, height), format='DEPTH_COMPONENT32F', data=self.depth_buffer, )
    else:
        offscreen = self.offscreen
        if(offscreen.width != width or offscreen.height != height):
            offscreen.free()
            offscreen = gpu.types.GPUOffScreen(width, height)
            self.offscreen = offscreen
            
            self.color_buffer = gpu.types.Buffer('FLOAT', (width, height, 4))
            self.depth_buffer = gpu.types.Buffer('FLOAT', (width, height, 1))
            self.color_tex = gpu.types.GPUTexture((width, height), format='RGBA32F', data=self.color_buffer, )
            self.depth_tex = gpu.types.GPUTexture((width, height), format='DEPTH_COMPONENT32F', data=self.depth_buffer, )
    
    with offscreen.bind():
        fb = gpu.state.active_framebuffer_get()
        fb.clear(color=(0.0, 0.0, 0.0, 0.0), depth=1.0, stencil=0, )
        
        gpu.state.program_point_size_set(True)
        gpu.state.depth_test_set('LESS_EQUAL')
        gpu.state.blend_set('ALPHA')
        
        shader.bind()
        
        region_data = bpy.context.region_data
        model = Matrix()
        view = region_data.view_matrix
        projection = region_data.window_matrix
        shader.uniform_float("model", model)
        shader.uniform_float("view", view)
        shader.uniform_float("projection", projection)
        shader.uniform_float("size", 6)
        shader.uniform_float("alpha", 1.0)
        
        with gpu.matrix.push_pop():
            gpu.matrix.load_matrix(Matrix.Identity(4))
            gpu.matrix.load_projection_matrix(Matrix.Identity(4))
            batch.draw(shader)
        
        gpu.state.program_point_size_set(False)
        gpu.state.depth_test_set('NONE')
        gpu.state.blend_set('NONE')
        
        fb.read_color(0, 0, width, height, 4, 0, 'FLOAT', data=self.color_buffer, )
        fb.read_depth(0, 0, width, height, data=self.depth_buffer, )


def draw_post_pixel(self, context, ):
    context = bpy.context
    scene = context.scene
    region_data = context.region_data
    view = region_data.view_matrix
    projection = region_data.window_matrix
    space = context.space_data
    
    viewport_info = gpu.state.viewport_get()
    width = viewport_info[2]
    height = viewport_info[3]
    
    coords = ((0, 0), (1, 0), (1, 1), (0, 1))
    shader = gpu.types.GPUShader(vert_2d, frag_2d)
    batch = batch_for_shader(shader, 'TRI_FAN', {"pos": coords, }, )
    position = (0, 0)
    
    gpu.state.blend_set('ALPHA')
    
    with gpu.matrix.push_pop():
        gpu.matrix.translate(position)
        gpu.matrix.scale((width, height))
        
        shader.bind()
        shader.uniform_sampler("color", self.color_tex)
        shader.uniform_sampler("depth", self.depth_tex)
        shader.uniform_float("near", space.clip_start)
        shader.uniform_float("far", space.clip_end)
        
        batch.draw(shader)
    
    gpu.state.blend_set('NONE')


class ModalFramebuffer(bpy.types.Operator):
    bl_idname = "view3d.modal_framebuffer"
    bl_label = "Modal Framebuffer"

    def __init__(self):
        print("start..")
    
    def __del__(self):
        redraw()
        print("end.")
    
    def modal(self, context, event):
        if(event.type in {'ESC', }):
            print("exiting..")
            bpy.types.SpaceView3D.draw_handler_remove(self.handle_pre_view, 'WINDOW')
            bpy.types.SpaceView3D.draw_handler_remove(self.handle_post_pixel, 'WINDOW')
            
            if(self.offscreen is not None):
                self.offscreen.free()
                self.offscreen = None
            
            self.color_buffer = None
            self.depth_buffer = None
            self.color_tex = None
            self.depth_tex = None
            
            return {'CANCELLED'}
        
        return {'PASS_THROUGH'}
    
    def invoke(self, context, event):
        print("invoke..")
        
        self.offscreen = None
        
        self.color_buffer = None
        self.depth_buffer = None
        self.color_tex = None
        self.depth_tex = None
        
        self.handle_pre_view = bpy.types.SpaceView3D.draw_handler_add(draw_pre_view, (self, context), 'WINDOW', 'PRE_VIEW')
        self.handle_post_pixel = bpy.types.SpaceView3D.draw_handler_add(draw_post_pixel, (self, context), 'WINDOW', 'POST_PIXEL')
        
        context.window_manager.modal_handler_add(self)
        
        redraw()
        return {'RUNNING_MODAL'}


def register():
    bpy.utils.register_class(ModalFramebuffer)


def unregister():
    bpy.utils.unregister_class(ModalFramebuffer)


if __name__ == "__main__":
    register()
    bpy.ops.view3d.modal_framebuffer('INVOKE_DEFAULT')

the only difference is, buffers and textures are created on first run or when viewport dimensions are changed

running second code result in either instant crash, sometime with log, sometimes without, if log is created it contains this:

0   Blender                             0x0000000110374307 BLI_system_backtrace + 55
1   Blender                             0x00000001063e0cf8 sig_handle_crash + 392
2   libsystem_platform.dylib            0x00007fff5702af5a _sigtramp + 26
3   Blender                             0x0000000106ffab31 Struct_properties_next + 17
4   Blender                             0x000000010ec4225f meth_dealloc + 15
5   Blender                             0x000000010ecefe95 call_function + 837
6   Blender                             0x000000010ecec75a _PyEval_EvalFrameDefault + 27114
7   Blender                             0x000000010ebfee35 function_code_fastcall + 229
8   Blender                             0x00000001070b4917 cb_region_draw + 39
9   Blender                             0x00000001072314ae ED_region_draw_cb_draw + 78
10  Blender                             0x0000000106ae8137 DRW_draw_render_loop_ex + 1479
11  Blender                             0x00000001075eba08 view3d_main_region_draw + 136
12  Blender                             0x00000001070c4851 ED_region_do_draw + 337
13  Blender                             0x000000010697162d wm_draw_update + 1757
14  Blender                             0x000000010696e5e0 WM_main + 48
15  Blender                             0x00000001063dd4eb main + 907
16  libdyld.dylib                       0x00007fff56d1c015 start + 1
17  ???                                 0x0000000000000004 0x0 + 4

less often it results in series of errors in terminal:

Traceback (most recent call last):
  File "/crash.py", line 169, in draw_pre_view
TypeError: read_color() argument 8 must be Buffer, not TypeError
Traceback (most recent call last):
  File "/crash.py", line 169, in draw_pre_view
TypeError: read_color() argument 8 must be Buffer, not builtin_function_or_method
Traceback (most recent call last):
  File "/crash.py", line 169, in draw_pre_view
TypeError: read_color() argument 8 must be Buffer, not builtin_function_or_method
Traceback (most recent call last):
  File "/crash.py", line 169, in draw_pre_view
TypeError: read_color() argument 8 must be Buffer, not TypeError
Traceback (most recent call last):
  File "/crash.py", line 169, in draw_pre_view
TypeError: read_color() argument 8 must be Buffer, not builtin_function_or_method
Traceback (most recent call last):
  File "/crash.py", line 169, in draw_pre_view
TypeError: read_color() argument 8 must be Buffer, not builtin_function_or_method
Traceback (most recent call last):
  File "/crash.py", line 169, in draw_pre_view
TypeError: read_color() argument 8 must be Buffer, not TypeError
Traceback (most recent call last):
  File "/crash.py", line 169, in draw_pre_view
TypeError: read_color() argument 8 must be Buffer, not builtin_function_or_method
Traceback (most recent call last):
  File "/crash.py", line 169, in draw_pre_view
TypeError: read_color() argument 8 must be Buffer, not builtin_function_or_method
Traceback (most recent call last):
  File "/crash.py", line 169, in draw_pre_view
TypeError: read_color() argument 8 must be Buffer, not TypeError
Traceback (most recent call last):
  File "/crash.py", line 169, in draw_pre_view
TypeError: read_color() argument 8 must be Buffer, not builtin_function_or_method
**System Information** Operating system: macOS-10.13.6-x86_64-i386-64bit 64 Bits as well on macOS 10.15.7 **Blender Version** Broken: version: 3.0.0, branch: master, commit date: 2021-12-02 18:35, hash: `f1cca30557` Worked: ? **Short description of error** crash or error when using GPUFrameBuffer.read_color(... data=data) i.e. when buffer is created before calling **Exact steps for others to reproduce the error** what i want to do: draw something offscreen, then get color and depth buffers, draw to viewport processed in fragment shader following code works, `read_color` is used without premade buffer, buffer returned is further used in texture (sorry for not so minimal example, i removed most of not relevant shader code) ``` import bpy import gpu import numpy as np from gpu_extras.batch import batch_for_shader from mathutils import Matrix vert_3d = ''' in vec3 position; in vec4 color; uniform mat4 model; uniform mat4 view; uniform mat4 projection; uniform float size = 3.0; uniform float alpha = 1.0; out vec4 v_color; void main() { gl_Position = projection * view * model * vec4(position, 1.0); gl_PointSize = size; v_color = vec4(vec3(color.rgb), alpha); } ''' frag_3d = ''' in vec4 v_color; out vec4 fragColor; void main() { vec4 f_color = v_color; fragColor = blender_srgb_to_framebuffer_space(f_color); } ''' vert_2d = ''' uniform mat4 ModelViewProjectionMatrix; in vec2 pos; out vec2 uv; void main() { gl_Position = ModelViewProjectionMatrix * vec4(pos.xy, 1.0f, 1.0f); uv = pos.xy; } ''' frag_2d = ''' in vec2 uv; uniform sampler2D color; uniform sampler2D depth; uniform float near; uniform float far; out vec4 fragColor; // source/blender/blenlib/intern/math_color.c float linearrgb_to_srgb(float c) { if (c < 0.0031308f) { return (c < 0.0f) ? 0.0f : c * 12.92f; } return 1.055f * pow(c, 1.0f / 2.4f) - 0.055f; } float srgb_to_linearrgb(float c) { if (c < 0.04045f) { return (c < 0.0f) ? 0.0f : c * (1.0f / 12.92f); } return pow((c + 0.055f) * (1.0f / 1.055f), 2.4f); } vec4 convert_color(vec4 c) { return vec4(srgb_to_linearrgb(c.r), srgb_to_linearrgb(c.g), srgb_to_linearrgb(c.b), c.a); } float linearize_depth(float d, float near, float far) { return near * far / (far + d * (near - far)); } void main() { float d = texture(depth, uv).x; d = linearize_depth(d, near, far); d = (d == 1.0) ? 0.0 : d; vec4 rgba = texture(color, uv); fragColor = convert_color(vec4(vec3(d / 10), rgba.a)); } ''' shader = gpu.types.GPUShader(vert_3d, frag_3d, ) n = 10000 vs = np.random.normal(0, 2, (n, 3), ) vs = vs.astype(np.float32) cs = np.random.random((n, 4), ) cs = cs.astype(np.float32) cs[:, 3] = 1.0 batch = batch_for_shader(shader, 'POINTS', {"position": vs, "color": cs, }) def redraw(): for w in bpy.context.window_manager.windows: for a in w.screen.areas: if(a.type == 'VIEW_3D'): a.tag_redraw() def draw_pre_view(self, context, ): context = bpy.context scene = context.scene region_data = context.region_data view = region_data.view_matrix projection = region_data.window_matrix viewport_info = gpu.state.viewport_get() width = viewport_info[2] height = viewport_info[3] if(self.offscreen is None): offscreen = gpu.types.GPUOffScreen(width, height) self.offscreen = offscreen else: offscreen = self.offscreen if(offscreen.width != width or offscreen.height != height): offscreen.free() offscreen = gpu.types.GPUOffScreen(width, height) self.offscreen = offscreen with offscreen.bind(): fb = gpu.state.active_framebuffer_get() fb.clear(color=(0.0, 0.0, 0.0, 0.0), depth=1.0, stencil=0, ) gpu.state.program_point_size_set(True) gpu.state.depth_test_set('LESS_EQUAL') gpu.state.blend_set('ALPHA') shader.bind() region_data = bpy.context.region_data model = Matrix() view = region_data.view_matrix projection = region_data.window_matrix shader.uniform_float("model", model) shader.uniform_float("view", view) shader.uniform_float("projection", projection) shader.uniform_float("size", 6) shader.uniform_float("alpha", 1.0) with gpu.matrix.push_pop(): gpu.matrix.load_matrix(Matrix.Identity(4)) gpu.matrix.load_projection_matrix(Matrix.Identity(4)) batch.draw(shader) gpu.state.program_point_size_set(False) gpu.state.depth_test_set('NONE') gpu.state.blend_set('NONE') self.buffer = fb.read_color(0, 0, width, height, 4, 0, 'FLOAT', ) self.depth = fb.read_depth(0, 0, width, height, ) def draw_post_pixel(self, context, ): context = bpy.context scene = context.scene region_data = context.region_data view = region_data.view_matrix projection = region_data.window_matrix space = context.space_data viewport_info = gpu.state.viewport_get() width = viewport_info[2] height = viewport_info[3] image = gpu.types.GPUTexture((width, height), format='RGBA32F', data=self.buffer, ) depth = gpu.types.GPUTexture((width, height), format='DEPTH_COMPONENT32F', data=self.depth, ) coords = ((0, 0), (1, 0), (1, 1), (0, 1)) shader = gpu.types.GPUShader(vert_2d, frag_2d) batch = batch_for_shader(shader, 'TRI_FAN', {"pos": coords, }, ) position = (0, 0) gpu.state.blend_set('ALPHA') with gpu.matrix.push_pop(): gpu.matrix.translate(position) gpu.matrix.scale((width, height)) shader.bind() shader.uniform_sampler("color", image) shader.uniform_sampler("depth", depth) shader.uniform_float("near", space.clip_start) shader.uniform_float("far", space.clip_end) batch.draw(shader) gpu.state.blend_set('NONE') class ModalFramebuffer(bpy.types.Operator): bl_idname = "view3d.modal_framebuffer" bl_label = "Modal Framebuffer" def __init__(self): print("start..") def __del__(self): redraw() print("end.") def modal(self, context, event): if(event.type in {'ESC', }): print("exiting..") bpy.types.SpaceView3D.draw_handler_remove(self.handle_pre_view, 'WINDOW') bpy.types.SpaceView3D.draw_handler_remove(self.handle_post_pixel, 'WINDOW') if(self.offscreen is not None): self.offscreen.free() self.offscreen = None return {'CANCELLED'} return {'PASS_THROUGH'} def invoke(self, context, event): print("invoke..") self.offscreen = None self.handle_pre_view = bpy.types.SpaceView3D.draw_handler_add(draw_pre_view, (self, context), 'WINDOW', 'PRE_VIEW') self.handle_post_pixel = bpy.types.SpaceView3D.draw_handler_add(draw_post_pixel, (self, context), 'WINDOW', 'POST_PIXEL') context.window_manager.modal_handler_add(self) redraw() return {'RUNNING_MODAL'} def register(): bpy.utils.register_class(ModalFramebuffer) def unregister(): bpy.utils.unregister_class(ModalFramebuffer) if __name__ == "__main__": register() bpy.ops.view3d.modal_framebuffer('INVOKE_DEFAULT') ``` for performance reasons (on fullscreen it is no longer acceptable) i wanted to NOT create new buffers and textures on each redraw, hence this code ``` import bpy import gpu import numpy as np from gpu_extras.batch import batch_for_shader from mathutils import Matrix vert_3d = ''' in vec3 position; in vec4 color; uniform mat4 model; uniform mat4 view; uniform mat4 projection; uniform float size = 3.0; uniform float alpha = 1.0; out vec4 v_color; void main() { gl_Position = projection * view * model * vec4(position, 1.0); gl_PointSize = size; v_color = vec4(vec3(color.rgb), alpha); } ''' frag_3d = ''' in vec4 v_color; out vec4 fragColor; void main() { vec4 f_color = v_color; fragColor = blender_srgb_to_framebuffer_space(f_color); } ''' vert_2d = ''' uniform mat4 ModelViewProjectionMatrix; in vec2 pos; out vec2 uv; void main() { gl_Position = ModelViewProjectionMatrix * vec4(pos.xy, 1.0f, 1.0f); uv = pos.xy; } ''' frag_2d = ''' in vec2 uv; uniform sampler2D color; uniform sampler2D depth; uniform float near; uniform float far; out vec4 fragColor; // source/blender/blenlib/intern/math_color.c float linearrgb_to_srgb(float c) { if (c < 0.0031308f) { return (c < 0.0f) ? 0.0f : c * 12.92f; } return 1.055f * pow(c, 1.0f / 2.4f) - 0.055f; } float srgb_to_linearrgb(float c) { if (c < 0.04045f) { return (c < 0.0f) ? 0.0f : c * (1.0f / 12.92f); } return pow((c + 0.055f) * (1.0f / 1.055f), 2.4f); } vec4 convert_color(vec4 c) { return vec4(srgb_to_linearrgb(c.r), srgb_to_linearrgb(c.g), srgb_to_linearrgb(c.b), c.a); } float linearize_depth(float d, float near, float far) { return near * far / (far + d * (near - far)); } void main() { float d = texture(depth, uv).x; d = linearize_depth(d, near, far); d = (d == 1.0) ? 0.0 : d; vec4 rgba = texture(color, uv); fragColor = convert_color(vec4(vec3(d / 10), rgba.a)); } ''' shader = gpu.types.GPUShader(vert_3d, frag_3d, ) n = 10000 vs = np.random.normal(0, 2, (n, 3), ) vs = vs.astype(np.float32) cs = np.random.random((n, 4), ) cs = cs.astype(np.float32) cs[:, 3] = 1.0 batch = batch_for_shader(shader, 'POINTS', {"position": vs, "color": cs, }) def redraw(): for w in bpy.context.window_manager.windows: for a in w.screen.areas: if(a.type == 'VIEW_3D'): a.tag_redraw() def draw_pre_view(self, context, ): context = bpy.context scene = context.scene region_data = context.region_data view = region_data.view_matrix projection = region_data.window_matrix viewport_info = gpu.state.viewport_get() width = viewport_info[2] height = viewport_info[3] if(self.offscreen is None): offscreen = gpu.types.GPUOffScreen(width, height) self.offscreen = offscreen self.color_buffer = gpu.types.Buffer('FLOAT', (width, height, 4)) self.depth_buffer = gpu.types.Buffer('FLOAT', (width, height, 1)) self.color_tex = gpu.types.GPUTexture((width, height), format='RGBA32F', data=self.color_buffer, ) self.depth_tex = gpu.types.GPUTexture((width, height), format='DEPTH_COMPONENT32F', data=self.depth_buffer, ) else: offscreen = self.offscreen if(offscreen.width != width or offscreen.height != height): offscreen.free() offscreen = gpu.types.GPUOffScreen(width, height) self.offscreen = offscreen self.color_buffer = gpu.types.Buffer('FLOAT', (width, height, 4)) self.depth_buffer = gpu.types.Buffer('FLOAT', (width, height, 1)) self.color_tex = gpu.types.GPUTexture((width, height), format='RGBA32F', data=self.color_buffer, ) self.depth_tex = gpu.types.GPUTexture((width, height), format='DEPTH_COMPONENT32F', data=self.depth_buffer, ) with offscreen.bind(): fb = gpu.state.active_framebuffer_get() fb.clear(color=(0.0, 0.0, 0.0, 0.0), depth=1.0, stencil=0, ) gpu.state.program_point_size_set(True) gpu.state.depth_test_set('LESS_EQUAL') gpu.state.blend_set('ALPHA') shader.bind() region_data = bpy.context.region_data model = Matrix() view = region_data.view_matrix projection = region_data.window_matrix shader.uniform_float("model", model) shader.uniform_float("view", view) shader.uniform_float("projection", projection) shader.uniform_float("size", 6) shader.uniform_float("alpha", 1.0) with gpu.matrix.push_pop(): gpu.matrix.load_matrix(Matrix.Identity(4)) gpu.matrix.load_projection_matrix(Matrix.Identity(4)) batch.draw(shader) gpu.state.program_point_size_set(False) gpu.state.depth_test_set('NONE') gpu.state.blend_set('NONE') fb.read_color(0, 0, width, height, 4, 0, 'FLOAT', data=self.color_buffer, ) fb.read_depth(0, 0, width, height, data=self.depth_buffer, ) def draw_post_pixel(self, context, ): context = bpy.context scene = context.scene region_data = context.region_data view = region_data.view_matrix projection = region_data.window_matrix space = context.space_data viewport_info = gpu.state.viewport_get() width = viewport_info[2] height = viewport_info[3] coords = ((0, 0), (1, 0), (1, 1), (0, 1)) shader = gpu.types.GPUShader(vert_2d, frag_2d) batch = batch_for_shader(shader, 'TRI_FAN', {"pos": coords, }, ) position = (0, 0) gpu.state.blend_set('ALPHA') with gpu.matrix.push_pop(): gpu.matrix.translate(position) gpu.matrix.scale((width, height)) shader.bind() shader.uniform_sampler("color", self.color_tex) shader.uniform_sampler("depth", self.depth_tex) shader.uniform_float("near", space.clip_start) shader.uniform_float("far", space.clip_end) batch.draw(shader) gpu.state.blend_set('NONE') class ModalFramebuffer(bpy.types.Operator): bl_idname = "view3d.modal_framebuffer" bl_label = "Modal Framebuffer" def __init__(self): print("start..") def __del__(self): redraw() print("end.") def modal(self, context, event): if(event.type in {'ESC', }): print("exiting..") bpy.types.SpaceView3D.draw_handler_remove(self.handle_pre_view, 'WINDOW') bpy.types.SpaceView3D.draw_handler_remove(self.handle_post_pixel, 'WINDOW') if(self.offscreen is not None): self.offscreen.free() self.offscreen = None self.color_buffer = None self.depth_buffer = None self.color_tex = None self.depth_tex = None return {'CANCELLED'} return {'PASS_THROUGH'} def invoke(self, context, event): print("invoke..") self.offscreen = None self.color_buffer = None self.depth_buffer = None self.color_tex = None self.depth_tex = None self.handle_pre_view = bpy.types.SpaceView3D.draw_handler_add(draw_pre_view, (self, context), 'WINDOW', 'PRE_VIEW') self.handle_post_pixel = bpy.types.SpaceView3D.draw_handler_add(draw_post_pixel, (self, context), 'WINDOW', 'POST_PIXEL') context.window_manager.modal_handler_add(self) redraw() return {'RUNNING_MODAL'} def register(): bpy.utils.register_class(ModalFramebuffer) def unregister(): bpy.utils.unregister_class(ModalFramebuffer) if __name__ == "__main__": register() bpy.ops.view3d.modal_framebuffer('INVOKE_DEFAULT') ``` the only difference is, buffers and textures are created on first run or when viewport dimensions are changed running second code result in either instant crash, sometime with log, sometimes without, if log is created it contains this: ``` 0 Blender 0x0000000110374307 BLI_system_backtrace + 55 1 Blender 0x00000001063e0cf8 sig_handle_crash + 392 2 libsystem_platform.dylib 0x00007fff5702af5a _sigtramp + 26 3 Blender 0x0000000106ffab31 Struct_properties_next + 17 4 Blender 0x000000010ec4225f meth_dealloc + 15 5 Blender 0x000000010ecefe95 call_function + 837 6 Blender 0x000000010ecec75a _PyEval_EvalFrameDefault + 27114 7 Blender 0x000000010ebfee35 function_code_fastcall + 229 8 Blender 0x00000001070b4917 cb_region_draw + 39 9 Blender 0x00000001072314ae ED_region_draw_cb_draw + 78 10 Blender 0x0000000106ae8137 DRW_draw_render_loop_ex + 1479 11 Blender 0x00000001075eba08 view3d_main_region_draw + 136 12 Blender 0x00000001070c4851 ED_region_do_draw + 337 13 Blender 0x000000010697162d wm_draw_update + 1757 14 Blender 0x000000010696e5e0 WM_main + 48 15 Blender 0x00000001063dd4eb main + 907 16 libdyld.dylib 0x00007fff56d1c015 start + 1 17 ??? 0x0000000000000004 0x0 + 4 ``` less often it results in series of errors in terminal: ``` Traceback (most recent call last): File "/crash.py", line 169, in draw_pre_view TypeError: read_color() argument 8 must be Buffer, not TypeError Traceback (most recent call last): File "/crash.py", line 169, in draw_pre_view TypeError: read_color() argument 8 must be Buffer, not builtin_function_or_method Traceback (most recent call last): File "/crash.py", line 169, in draw_pre_view TypeError: read_color() argument 8 must be Buffer, not builtin_function_or_method Traceback (most recent call last): File "/crash.py", line 169, in draw_pre_view TypeError: read_color() argument 8 must be Buffer, not TypeError Traceback (most recent call last): File "/crash.py", line 169, in draw_pre_view TypeError: read_color() argument 8 must be Buffer, not builtin_function_or_method Traceback (most recent call last): File "/crash.py", line 169, in draw_pre_view TypeError: read_color() argument 8 must be Buffer, not builtin_function_or_method Traceback (most recent call last): File "/crash.py", line 169, in draw_pre_view TypeError: read_color() argument 8 must be Buffer, not TypeError Traceback (most recent call last): File "/crash.py", line 169, in draw_pre_view TypeError: read_color() argument 8 must be Buffer, not builtin_function_or_method Traceback (most recent call last): File "/crash.py", line 169, in draw_pre_view TypeError: read_color() argument 8 must be Buffer, not builtin_function_or_method Traceback (most recent call last): File "/crash.py", line 169, in draw_pre_view TypeError: read_color() argument 8 must be Buffer, not TypeError Traceback (most recent call last): File "/crash.py", line 169, in draw_pre_view TypeError: read_color() argument 8 must be Buffer, not builtin_function_or_method ```
Author

Added subscriber: @JakubUhlik

Added subscriber: @JakubUhlik

Added subscriber: @mano-wii

Added subscriber: @mano-wii

Changed status from 'Needs Triage' to: 'Confirmed'

Changed status from 'Needs Triage' to: 'Confirmed'

Thanks for the report. I found the problem in the Blender code.
But for the record, please try to simplify the code a bit more before reporting.
Walls of Text don't look good in bug reports (see https://wiki.blender.org/wiki/Process/Bug_Reports#Avoid_Walls_of_Text).

The script in the example could be simplified to:

import gpu

offscreen = None
color_buffer = None
depth_buffer = None
color_tex = None
depth_tex = None

def offscreen_get(width, height):
    global offscreen
    global color_buffer
    global depth_buffer
    global color_tex
    global depth_tex

    if (offscreen is None) or (offscreen.width != width or offscreen.height != height):
        if offscreen:
            offscreen.free()
        offscreen = gpu.types.GPUOffScreen(width, height)
        
        color_buffer = gpu.types.Buffer('FLOAT', (width, height, 4))
        depth_buffer = gpu.types.Buffer('FLOAT', (width, height, 1))
        color_tex = gpu.types.GPUTexture((width, height), format='RGBA32F', data=color_buffer)
        depth_tex = gpu.types.GPUTexture((width, height), format='DEPTH_COMPONENT32F', data=depth_buffer)

    return offscreen


def draw_pre_view():
    offscreen = offscreen_get(width, height)
    with offscreen.bind():
        fb = gpu.state.active_framebuffer_get() 
        fb.read_color(0, 0, width, height, 4, 0, 'FLOAT', data=color_buffer)
        #fb.read_depth(0, 0, width, height, data=depth_buffer, )

handle_pre_view = bpy.types.SpaceView3D.draw_handler_add(draw_pre_view, (), 'WINDOW', 'PRE_VIEW')
Thanks for the report. I found the problem in the Blender code. But for the record, please try to simplify the code a bit more before reporting. Walls of Text don't look good in bug reports (see https://wiki.blender.org/wiki/Process/Bug_Reports#Avoid_Walls_of_Text). The script in the example could be simplified to: ``` import gpu offscreen = None color_buffer = None depth_buffer = None color_tex = None depth_tex = None def offscreen_get(width, height): global offscreen global color_buffer global depth_buffer global color_tex global depth_tex if (offscreen is None) or (offscreen.width != width or offscreen.height != height): if offscreen: offscreen.free() offscreen = gpu.types.GPUOffScreen(width, height) color_buffer = gpu.types.Buffer('FLOAT', (width, height, 4)) depth_buffer = gpu.types.Buffer('FLOAT', (width, height, 1)) color_tex = gpu.types.GPUTexture((width, height), format='RGBA32F', data=color_buffer) depth_tex = gpu.types.GPUTexture((width, height), format='DEPTH_COMPONENT32F', data=depth_buffer) return offscreen def draw_pre_view(): offscreen = offscreen_get(width, height) with offscreen.bind(): fb = gpu.state.active_framebuffer_get() fb.read_color(0, 0, width, height, 4, 0, 'FLOAT', data=color_buffer) #fb.read_depth(0, 0, width, height, data=depth_buffer, ) handle_pre_view = bpy.types.SpaceView3D.draw_handler_add(draw_pre_view, (), 'WINDOW', 'PRE_VIEW') ```

This issue was referenced by 63b9e5378b

This issue was referenced by 63b9e5378b81fc1a184210c7f3ccf3e452ffbbe4

This issue was referenced by 2e5aecf557

This issue was referenced by 2e5aecf557fb6bc9fe9c652e692905feb1bde484

Changed status from 'Confirmed' to: 'Resolved'

Changed status from 'Confirmed' to: 'Resolved'
Germano Cavalcante self-assigned this 2022-01-18 23:23:46 +01:00
Sign in to join this conversation.
No Label
Interest
Alembic
Interest
Animation & Rigging
Interest
Asset Browser
Interest
Asset Browser Project Overview
Interest
Audio
Interest
Automated Testing
Interest
Blender Asset Bundle
Interest
BlendFile
Interest
Collada
Interest
Compatibility
Interest
Compositing
Interest
Core
Interest
Cycles
Interest
Dependency Graph
Interest
Development Management
Interest
EEVEE
Interest
EEVEE & Viewport
Interest
Freestyle
Interest
Geometry Nodes
Interest
Grease Pencil
Interest
ID Management
Interest
Images & Movies
Interest
Import Export
Interest
Line Art
Interest
Masking
Interest
Metal
Interest
Modeling
Interest
Modifiers
Interest
Motion Tracking
Interest
Nodes & Physics
Interest
OpenGL
Interest
Overlay
Interest
Overrides
Interest
Performance
Interest
Physics
Interest
Pipeline, Assets & IO
Interest
Platforms, Builds & Tests
Interest
Python API
Interest
Render & Cycles
Interest
Render Pipeline
Interest
Sculpt, Paint & Texture
Interest
Text Editor
Interest
Translations
Interest
Triaging
Interest
Undo
Interest
USD
Interest
User Interface
Interest
UV Editing
Interest
VFX & Video
Interest
Video Sequencer
Interest
Virtual Reality
Interest
Vulkan
Interest
Wayland
Interest
Workbench
Interest: X11
Legacy
Blender 2.8 Project
Legacy
Milestone 1: Basic, Local Asset Browser
Legacy
OpenGL Error
Meta
Good First Issue
Meta
Papercut
Meta
Retrospective
Meta
Security
Module
Animation & Rigging
Module
Core
Module
Development Management
Module
EEVEE & Viewport
Module
Grease Pencil
Module
Modeling
Module
Nodes & Physics
Module
Pipeline, Assets & IO
Module
Platforms, Builds & Tests
Module
Python API
Module
Render & Cycles
Module
Sculpt, Paint & Texture
Module
Triaging
Module
User Interface
Module
VFX & Video
Platform
FreeBSD
Platform
Linux
Platform
macOS
Platform
Windows
Priority
High
Priority
Low
Priority
Normal
Priority
Unbreak Now!
Status
Archived
Status
Confirmed
Status
Duplicate
Status
Needs Info from Developers
Status
Needs Information from User
Status
Needs Triage
Status
Resolved
Type
Bug
Type
Design
Type
Known Issue
Type
Patch
Type
Report
Type
To Do
No Milestone
No project
3 Participants
Notifications
Due Date
The due date is invalid or out of range. Please use the format 'yyyy-mm-dd'.

No due date set.

Dependencies

No dependencies set.

Reference: blender/blender#94202
No description provided.