Skip to content

Output TOP textures read as all-zero (alpha included) via as_tensor / as_dlpack while a CHOP derived from the same TOP reads the correct value — TouchPy 0.12.1, TouchDesigner 2025.33230, RTX 3090 #91

Description

@manitonga

Summary

On this machine, every output TOP texture imported by TouchPy 0.12.1
(as_tensor, as_dlpack, any CUDA stream, any frame) is entirely zero
— alpha included — while the same instance's CHOP output derived from
the same TOP carries the correct value, so the engine is cooking. A
minimal host on the official SDK (Direct3D 12), same machine, same
.tox, same TouchDesigner build, reads the expected pixels and reports
TEInstanceDoesTextureOwnershipTransfer() == true for this
instance. TouchPy 0.12.1 does not perform that ownership transfer on
output textures (TEInstanceGetVulkanTextureTransfer is commented out,
cmdTransitionImageLayout is never called). Full reproducer attached.

Environment

item value
touchpy 0.12.1 (PyPI wheel, cp312-win_amd64), bundled TouchEngine.dll ProductVersion 2023.12230
TouchDesigner Build 2025.33230 (C:\Program Files\Derivative\TouchDesigner), licensed
Python 3.12.10, dedicated venv (touchpy + numpy 2.5.3 + torch 2.11.0+cu128)
GPU NVIDIA GeForce RTX 3090, CUDA device 0 (selected by TouchPy, per its own log) ; an AMD iGPU and a Meta virtual display adapter are also present
OS Windows 11 Pro 10.0.26200

Component

Minimal .tox (attached, plus the TouchDesigner script that rebuilds it):

svp_banc_minimal (Base COMP)
  custom String par `Message` = 'Studio-VP'
  text1      Text TOP, text expr = parent().par.Message.eval()   (256x256)
  out1       Out TOP  <- text1
  constant1  Constant TOP (1, 0.5, 0.25, 1)
  out2       Out TOP  <- constant1
  analyze1   Analyze TOP <- text1  (op=average, channel=luminance)
  topto1     TOP to CHOP (top=analyze1)
  out_chop1  Out CHOP <- topto1

Verified inside TouchDesigner before saving (construire_tox.releve.txt):

PIXEL text1 (128,128) : (1, 1, 1, 1)
PIXEL out1  (128,128) : (1, 1, 1, 1)
PIXEL out2  (128,128) : (1, 0.498, 0.251, 1)
out_chop1 r g b a     : 0.0235   (= mean luminance of text1, 1 sample)

What TouchPy returns

sonde_texture.py (attached) loads the .tox with Comp() defaults,
polls loaded(), calls start(), and at frames 5 / 30 / 90 reads:

path frame 5 frame 30 frame 90
A out_tops['out1'].as_tensor(True) all zero all zero all zero
B set_cuda_stream(torch.cuda.current_stream().cuda_stream) then A all zero all zero all zero
C set_cuda_stream(comp.cuda_stream()) (= 0) then A all zero all zero all zero
D torch.from_dlpack(out1.as_dlpack(True)) all zero all zero all zero
E tensor from A re-read 2 frames later all zero all zero all zero
F out_tops['out2'].as_tensor(True) (constant colour, no font) all zero all zero all zero
G out_chops['out_chop1'].as_numpy() 0.0235294 ×4 0.0235294 ×4 0.0235294 ×4

"all zero" means: tensor [4, 256, 256] torch.uint8 on cuda:0,
min = max = sum = 0, count_nonzero = 0 / 262144, alpha plane also 0.
cuda_memory() reports a valid pointer, shape [4,256,256], strides
[65536,256,1], UInt8. No CUDA error: line is ever printed; TouchPy's
own log shows Texture Created, width: 256, height: 256, format: VK_FORMAT_B8G8R8A8_UNORM, Size: 262144, Memory Type Index: 1 for each
Out TOP. Full JSON in sonde_texture.passage3.json.

So the engine instance loaded by TouchPy does cook text1 (G equals
the value TouchDesigner itself reports, at three different frames), and
the constant-colour TOP is just as empty as the text one, but every
texture import path yields a buffer that was never written.

Control: the official SDK reads the same texture correctly

A minimal C++ host on TouchEngine-Windows (Direct3D 12 context,
TETimeExternal, sequence copied from the SDK example's
DX12Renderer::updateOutputImage: TEInstanceGetTextureTransfer
open the shared fence → queue->Wait(fence, waitValue) → copy →
TEInstanceAddTextureTransfer) on the same machine, same .tox, same
TouchDesigner build, returns for op/out1: 256×256
DXGI_FORMAT_B8G8R8A8_UNORM, 8 736 non-zero bytes, 1 257 fully white
pixels, centre pixel (255,255,255,255); after changing Message,
3 993 of 65 536 pixels differ. TEInstanceDoesTextureOwnershipTransfer()
reports true for this instance. Exit code 0. Host source and its
JSON record (hote_a1.cpp, releve_a1.passage1.json) are in the
attached archive.

What I checked in the sources (reading, not measured)

In source/toplink.cpp, OutTopLink::onOutputTextureChange() uses
TEInstanceGetTextureTransfer(); the TEInstanceGetVulkanTextureTransfer()
block is commented out and Texture::cmdTransitionImageLayout() is never
called, so no image memory barrier / ownership acquire is performed on
output textures. TEVulkan.h in the SDK says
TEInstanceDoesVulkanTextureOwnershipTransfer() "returns true if the
instance requires Vulkan ownership transfer via image memory barriers …
must be queried after TEEventInstanceReady". I could not query it from
Python, so this is only a hypothesis consistent with the table above.

Observed separately (not part of this report's verdict): process dies at exit

After unload() (log: Instance unloaded: Success, Comp destroyed)
the interpreter exits with -1073741819 (0xC0000005). With a Comp
that never loaded a .tox the exit was 0xC0000409 with
[Vulkan Loader] ERROR: vkDestroyFence: Invalid device. Mentioned only
because it was observed during the same runs; it is independent of the
texture problem (the JSON above is written before unload()). Happy to
open a dedicated issue if useful.

Reproduce

python -m venv .venv-touchengine
.venv-touchengine\Scripts\pip install touchpy==0.12.1 numpy torch --index-url https://download.pytorch.org/whl/cu128
.venv-touchengine\Scripts\python.exe sonde_texture.py     # from the folder containing scripts/touchengine/svp_banc_minimal.tox

touchpy-issue-reproducer.zip

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions