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: rBf1cca3055776
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