OverviewFilesCommitsIssuesDocsReleasesPeers

ifsrenderer-linux commits / 84e5810

Stop marshalling four million tuples: upload is 13x faster
Pure Python is not a goal — if compiled code buys performance, take it. Noted in the design notes, correcting what was written an hour earlier. The first thing that principle pointed at needed no native code at all. ifs_splat.upload measured at 1M splats: before 1952 ms corners.tolist() 739 ms + tris.tolist() 445 ms = 61% after 150 ms Both GPUVertBuf.attr_fill and GPUIndexBuf accept numpy arrays directly, verified by probe. The .tolist() calls were building four million Python tuples for an API that then unpacks them again — marshalling, not computation. Worth generalising: check for that shape before reaching for a native extension. Python overhead in this codebase is mostly at the numpy/bpy boundary, and the boundary usually has an array-shaped door already. test_splat_shading 7/7. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
84e5810b54a9a0bb740bd56bd52ab081344129f7
by plankatron <fleeky@gmail.com> · 2026-08-18T14:54:06.000Z

Changed files (2) +21 -7

blender/NOTES-splat-rendering.md +14 -4
……@@ -14,10 +14,20 @@ a reason the PLY writer is attractive — treat it as *information about those
1414projects*, not as a requirement on ours. We can depend on compiled packages if
1515there is ever a reason to.
1616
17There is still a good reason to stay pure Python, and it is not the ToS: the
18add-on installs from a zip with no build step, on any machine, and every
19dependency we avoid is one that cannot break on someone's Blender. That is a
20preference we can trade away deliberately, rather than a rule.
17**And pure Python is not a goal.** Corrected 2026-08-18: if compiled code buys
18performance, take it. Zero-dependency install is a convenience, not a constraint,
19and it loses to speed.
20
21The first place that principle points is not native code, though. Measured on
22`ifs_splat.upload` at 1M splats:
23
24 before 1952 ms of which corners.tolist() 739 ms + tris.tolist() 445 ms
25 after 150 ms numpy passed straight to attr_fill / GPUIndexBuf
26
2713x, by deleting two `.tolist()` calls that built four million Python tuples for
28an API that accepts arrays directly. Worth checking for that shape before
29reaching for a native extension — the Python overhead here was marshalling, not
30computation, and marshalling is usually removable without leaving Python.
2131
2232---
2333
blender/ifs_splat.py +7 -3
……@@ -179,9 +179,13 @@ def upload(positions, colors, radius, width=SPLAT_WIDTH):
179179 shader_format.attr_add(id="corner", comp_type='F32', len=2, fetch_mode='FLOAT')
180180 shader_format.attr_add(id="splat_index", comp_type='I32', len=1, fetch_mode='INT')
181181 vbo = gpu.types.GPUVertBuf(shader_format, len=count * 4)
182 vbo.attr_fill(id="corner", data=corners.tolist())
183 vbo.attr_fill(id="splat_index", data=indices.tolist())
184 ibo = gpu.types.GPUIndexBuf(type='TRIS', seq=tris.tolist())
182 # numpy STRAIGHT in, no .tolist(). Measured at 1M splats: the two list
183 # conversions were 739 ms + 445 ms of a 1952 ms upload — 61% of the total,
184 # spent building four million Python tuples that the API then unpacks again.
185 # Both attr_fill and GPUIndexBuf accept arrays directly.
186 vbo.attr_fill(id="corner", data=corners)
187 vbo.attr_fill(id="splat_index", data=indices)
188 ibo = gpu.types.GPUIndexBuf(type='TRIS', seq=tris)
185189 batch = gpu.types.GPUBatch(type='TRIS', buf=vbo, elem=ibo)
186190
187191 return SplatCloud(batch, _to_texture(pos_rows, width), _to_texture(col_rows, width),