Skip to content

Fix Blender 5.2 texture readback corruption - #67

Open
ThinkWithPbody wants to merge 2 commits into
maybites:masterfrom
ThinkWithPbody:agent/fix-blender-5-2-texture-readback
Open

Fix Blender 5.2 texture readback corruption#67
ThinkWithPbody wants to merge 2 commits into
maybites:masterfrom
ThinkWithPbody:agent/fix-blender-5-2-texture-readback

Conversation

@ThinkWithPbody

Copy link
Copy Markdown

Summary

  • remove the unconditional NumPy transpose from Spout texture readback
  • remove the same transpose from NDI texture readback
  • preserve the existing is_flipped behavior

This fixes the diagonal/corrupted output reported with Blender 5.2, where the buffer returned by GPUTexture.read() already has the expected layout.

Fixes #63.

Validation

  • python -m py_compile fbs/spout/SpoutServer.py fbs/ndi/NDIServer.py
  • git diff --check
  • manual Blender 5.2 testing confirmed correct output in Solid and EEVEE views

Cycles

We were unable to produce scene output from Cycles. The shared frame remained black except for viewport UI overlays, although Blender's own Cycles viewport rendered the scene. Camera movement updated the overlays, and changing exposure had no effect. This PR does not attempt to change the capture architecture or address that separate limitation.

Review note

This patch was produced using agentic coding with OpenAI Codex. Although the final code change is intentionally very small, it needs human review before merging.

@ThinkWithPbody
ThinkWithPbody marked this pull request as ready for review August 10, 2026 20:12
@maybites maybites self-assigned this Aug 23, 2026
@maybites

Copy link
Copy Markdown
Owner

Thanks for tracking this down, and for flagging the Codex provenance up front. I verified the mechanism independently and tested on macOS. Your diagnosis is correct and the fix works.

Confirmed on macOS / Blender 5.2 / NDI — clean output with the patch applied.

That's worth calling out because this issue reads as Windows-only, and it isn't. FrameBufferSharingServer.create() only branches by platform for the SPOUT type. The else branch returns NDIServer on every platform, so macOS and Linux NDI users hit the same corruption on 5.2. I reproduced the shear on macOS before applying the patch.

Root cause

Blender commit 89a0750 (Campbell Barton, 2026-04-08) rewrites pygpu_buffer_strides_calc:

-  r_strides[0] = GPU_texture_dataformat_size(format);
-  for (int i = 1; i < shape_len; i++) {
-    r_strides[i] = r_strides[i - 1] * shape[i - 1];
+  Py_ssize_t stride = GPU_texture_dataformat_size(format);
+  for (int i = shape_len; i-- > 0;) {
+    r_strides[i] = stride;  stride *= shape[i];

For shape (h, w, c) the old code emitted strides (1, h, h*w) while reporting a C-order shape. Those are Fortran strides. For (6,4,4):

strides
old (1, 6, 24), byte-identical to np.empty(shape, order='F').strides
new (16, 4, 1), C-contiguous

So numpy genuinely returned a transposed array on older Blender and .transpose() undid it exactly. On 5.2 the same call scrambles an already-correct buffer.

It shears rather than crashing because h*w*c is invariant under transposition, so the reshape on the next line succeeds silently and every byte lands at the wrong stride.

Branch check: blender-v5.2-release and main contain the commit; blender-v5.1-release and blender-v5.0-release do not. That matches @FellowSufferer reporting 5.1.2 as clean.

What's needed before merge

The transpose is load-bearing on 5.1 and earlier, so removing it unconditionally trades the 5.2 bug for the same bug on every older version. The README claims support back to 4.x, and the affected surface is wider than this PR suggests: NDI on all platforms plus Spout on Windows.

I have not tested 5.1 with the patch applied, so that regression is a prediction from the stride arithmetic rather than something I've observed. But the arithmetic is unambiguous and I'd want the guard in place regardless.

Please gate the transpose, something like:

image_array = np.asarray(texture.read(), dtype=np.uint8)
if not _C_ORDER_BUFFER:
    image_array = image_array.transpose()
image_array = image_array.reshape(self.height, self.width, 4)

Detecting it at runtime (buf.strides[-1] == buf.itemsize) is preferable to pinning bpy.app.version >= (5, 2, 0), since it survives a backport. d42e8af already established a version-aware pattern in this repo.

With that guard added, this is good to merge.

Minor notes

  • SpoutServer.py:48 still reads "This is the correct way to handle Blender's Buffer object", which is now backwards. Please point it at the Blender commit instead, that comment will otherwise get deleted as noise someday and take the context with it.
  • NDIServer.py:82-87 reshapes only inside the is_flipped branch and ends with a "Flatten back to 1D array" comment with no code under it. Harmless since line 90 flattens either way, but you're already editing this function.
  • On the channel-padding variant proposed in Strange behaviour #63: I'd leave it out. GPUOffScreen defaults to RGBA8 so the hardcoded 4 is correct today, and defensive reshaping would hide a future bug rather than surface it. Better to keep this PR to one change.
  • SyphonMetalServer passes the buffer through with no transpose and isn't touched here. Whether it was affected in either direction is a separate question I haven't settled, so it stays out of this PR.

Scoping out the Cycles black frame is the right call, that's capture architecture rather than buffer layout.

Finally, for those in #63 who reported the patch not helping: the __pycache__/restart explanation is plausible and worth ruling out first. There's a second possibility though. Anyone on 5.1 applying this patch would watch it break a setup that was working. From the outside those two failures look identical.

@ThinkWithPbody

ThinkWithPbody commented Aug 24, 2026

Copy link
Copy Markdown
Author

Addressed the pre-5.2 compatibility feedback in e7c03cc: Spout and NDI now transpose only when the runtime buffer is not C-order, the Blender stride-change comment is updated, and the dangling NDI comment is removed.

Validation:

  • Python syntax compilation passed for both sender files.
  • git diff --check passed.
  • Manual Blender 5.1.2 and 5.2.0 test passed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Strange behaviour

2 participants