PyAPI: Improving and extending Py_buffer support #114429
Labels
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
No Assignees
1 Participants
Notifications
Due Date
No due date set.
Dependencies
No dependencies set.
Reference: blender/blender#114429
Loading…
Reference in New Issue
No description provided.
Delete Branch "%!s(<nil>)"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Background and rationale
I would like to work on extending and improving the Py_buffer support of Blender's Python API. Having worked on improving the performance of the FBX IO addon by making much use of the existing buffer support, these changes are primarily changes that would have made the Python API easier for me to use or would make further performance improvements to the FBX IO addon possible. None of the changes are specific to the FBX IO addon and they should be useful to other addon/script developers.
I'm not too experienced with C (and even less so with C++), so I anticipate that progress will be slow and that reviews of PRs will definitely identify areas to be improved, but I've made working builds of a few of these changes already to make sure these are things I am capable of working on, though further work is required before they're at a state that I think could be reviewed.
The
foreach_get
andforeach_set
functions available forbpy_prop_collection
andbpy_prop_array
types (foreach_getset
andpyprop_array_foreach_getset
inbpy_rna.cc
) provide fast access to props of the collection items or the array data. Given a standard Python sequence such as alist
, the C code for these functions iterates the data into/out of the sequence. This can be considerably faster than iterating through thebpy_prop_collection
orbpy_prop_array
in Python code, however, further performance can be achieved by passing in a Python object that supports the Buffer Protocol (see https://docs.python.org/3/c-api/buffer.html and https://peps.python.org/pep-3118/) and is of a type compatible with the type of the property or array being accessed. This is because the data can be copied into/out of the buffer more efficiently withmemcpy
. Reading the data in and out of buffers can especially be useful for IO that imports/exports data as arrays of bytes or for using libraries that work with entire arrays of data at a time, such as NumPy or PIL.There are currently limitations to the types of buffers that can be used and there's no clear way to tell through the Python API what type a buffer needs to have to be compatible with the collection attribute being accessed.
Additionally, there are functions available through the Python API that take a sequence as an argument but make no use of the fact that the sequence could be a compatible buffer, and there are functions that return a sequence where it may be beneficial to allow a Python addon developer to instead write the result directly into a buffer. These functions could see considerable performance increases from supporting buffers.
A few utility functions for buffers already exist at the end of
py_capi_utils.cc
, I intend to extend these and update thebpy_prop_collection
andbpy_prop_array
functions to use these utility functions like the existing buffer code used by idprop arrays.Main goals
A shorter overview of the main changes I want to make, described in more detail further below:
bpy_prop_array
,bpy_prop_collection
and idprop arrays.int
andlong
may be the same size on some systems, but some of the current Python API only acceptsint
buffers when along
buffer would also be compatible on some systems.ctypes
arrays use standard-size buffer formats, making them incompatible with current API..subtype
.Proposed changes
Improvements to existing code
Unify buffer parsing code between id_prop arrays, bpy_prop_array foreach_get/set and bpy_prop_collection foreach_get/set.
Check buffer length/shape matches the length/shape of the collection/array.
PyObject
matches, this is usually the same as the length of a 1D buffer, though it technically might not be.PyBUF_ND
so thatshape
information is provided by the buffer.bpy_rna.cc
.Support buffers of any type that is the correct kind of data (bool/integer/unsigned integer/floating-point) and the correct itemsize.
int
andlong
or buffers can be 'standard size' which may not match native sizes, it would be beneficial to not require that a buffer's type code exactly matches the data, so long as it's the same kind of data and the same size.PyC_StructFmt_type_is_float_any
, though extra functions for signed/unsigned integers and for working with 'standard size' would need to be added.New features
Support non-C-contiguous and PIL-style (
suboffsets
) buffers in foreach_get/foreach_set.PyBUF_SIMPLE
flag. It's not difficult to support F-contiguity, non-contiguity or evensuboffsets
by using thePyBuffer_FromContiguous
andPyBuffer_ToContiguous
functions from Python's C API to convert the buffer data to/from C-contiguous arrays. For any-contiguity and PIL-style support, the buffer request flags would need to includePyBUF_FULL
orPyBUF_FULL_RO
depending on whether the buffer needs to be writable.Add support for buffers that contain byte-order/size/alignment information in their format strings (e.g.
ctypes
arrays).struct
module syntax with extensions from PEP 3118, which also allow numeric prefixes and multi-element formats. These would be unusual to come across, but formats such as"fd"
or"1L"
(same as"L"
, but with an explicit repeat count) are possible which would either be parsed incorrectly ("fd"
->'f'
) or rejected as invalid ("1L"
->'1'
) by the current code.struct
module byte-order/size/alignment prefixes isPyC_StructFmt_type_from_str
."1L"
and"L:my_long_name:"
, which are valid formats, would not be supported, though I haven't seen them used.Add a function or property that can be used to accurately produce a compatible buffer for a specific property.
subtype
of anIntProperty
, but there doesn't appear to be a way to get the size in bytes of a property's type. The size in bytes can in some cases be guessed from thehard_min
andhard_max
of the property, but this is not reliable, for example, color properties usually have ahard_min
of0.0
and can have ahard_max
of1.0
.data_type
has a specific C type. These C types do still need to be figured out by developers that are using Blender's Python API, which could be improved upon so that each developer doesn't need to create their own mapping fromdata_type
to C type, but the consistency of the C types of attributes is very helpful.Or, instead of the previous feature, cast incompatible buffers to the correct type instead of falling back to accessing them as a sequence
numpy.ndarray.astype(new_type)
.Add buffer support for functions that take sequence arguments, such as
VertexGroup.add
andMesh.normals_split_custom_set
.pyrna_py_to_array
to take advantage of arguments that are buffers for a performance boost.bpy_prop_array
such asmy_image.pixels = my_buffer
would also take advantage ofmy_buffer
being a buffer and run at the same speed asbpy_prop_array.foreach_set
when the buffer is compatible.pyrna_py_to_array_index
is similar topyrna_py_to_array
, so it may also be useful to add buffer support to in the case of setting elements of multi-dimensional arrays.Add
foreach_get/set
support tobool
bpy_prop_array
.bool
arrays where performance would be a concern, but I couldn't see any other reason for whybool
arrays were not supported when I was looking at the code I would need to work on for some of the other changes.float
andint
arrays and it's a change that isn't really connected to any of the other planned changes, I've already made a PR for this change, though I do need to update it: !106492Further extensions
These are possible further extensions that I haven't looked into the details of and may be beyond my current capabilities or may not be possible.
Add
as_buffer()
function, similar to callingas_pointer()
on the first element of a collection, but for the entire collection's contents along with typing.foreach_get
is that it always copies data, and in some cases converts from one type to another, e.g. 8-bit integer attributes convert toint
.vec3f
:"T{f:x:f:y:f:z:}"
or"T{fff}"
or"T{(3)f}"
. Or the much largerBezTriple
:"T{(3,3)f:vec:f:tilt:f:weight:f:radius:c:ipo:B:h1:B:h2:B:f1:B:f2:B:f3:c:hide:c:easing:f:back:f:amplitude:f:period:=c:auto_handle_type:(3)c:_pad:}"
or"T{(3,3)ffffcBBBBBccfffc(3)c}"
.Add
to_buffer
function similar toforeach_get
, but creating and returning a compatible buffer object filled with the requested data.memoryview
is Python's built-in type for representing a view of a buffer so could be the object type returned if it is preferable over a NumPy or ctypes array. A Pythonarray.array
is also a buffer but doesn't support thebool
type.foreach_get
instead, e.g.foreach_get("co", None)
that sees theNone
argument and then creates and returns amemoryview
of abytearray
containing the data.Add support for functions that would normally return a new list/tuple, to instead return into a provided buffer (or sequence for increased compatibility).
normals_list = my_shape_key.normals_polygon_get()
->my_shape_key.normals_polygon_get(normals=my_buffer)
or, in the case that the function usually takes arguments or has multiple return values,groups_list, num_groups = my_mesh.calc_smooth_groups(use_bitflags=True)
->num_groups = my_mesh.calc_smooth_groups(use_bitflags=True, poly_groups=my_buffer)
.