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
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 reportsTEInstanceDoesTextureOwnershipTransfer() == truefor thisinstance. TouchPy 0.12.1 does not perform that ownership transfer on
output textures (
TEInstanceGetVulkanTextureTransferis commented out,cmdTransitionImageLayoutis never called). Full reproducer attached.Environment
cp312-win_amd64), bundledTouchEngine.dllProductVersion 2023.12230C:\Program Files\Derivative\TouchDesigner), licensedComponent
Minimal
.tox(attached, plus the TouchDesigner script that rebuilds it):Verified inside TouchDesigner before saving (
construire_tox.releve.txt):What TouchPy returns
sonde_texture.py(attached) loads the.toxwithComp()defaults,polls
loaded(), callsstart(), and at frames 5 / 30 / 90 reads:out_tops['out1'].as_tensor(True)set_cuda_stream(torch.cuda.current_stream().cuda_stream)then Aset_cuda_stream(comp.cuda_stream())(= 0) then Atorch.from_dlpack(out1.as_dlpack(True))out_tops['out2'].as_tensor(True)(constant colour, no font)out_chops['out_chop1'].as_numpy()"all zero" means: tensor
[4, 256, 256]torch.uint8oncuda: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. NoCUDA error:line is ever printed; TouchPy'sown log shows
Texture Created, width: 256, height: 256, format: VK_FORMAT_B8G8R8A8_UNORM, Size: 262144, Memory Type Index: 1for eachOut TOP. Full JSON in
sonde_texture.passage3.json.So the engine instance loaded by TouchPy does cook
text1(G equalsthe 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'sDX12Renderer::updateOutputImage:TEInstanceGetTextureTransfer→open the shared fence →
queue->Wait(fence, waitValue)→ copy →TEInstanceAddTextureTransfer) on the same machine, same.tox, sameTouchDesigner build, returns for
op/out1: 256×256DXGI_FORMAT_B8G8R8A8_UNORM, 8 736 non-zero bytes, 1 257 fully whitepixels, centre pixel
(255,255,255,255); after changingMessage,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 theattached archive.
What I checked in the sources (reading, not measured)
In
source/toplink.cpp,OutTopLink::onOutputTextureChange()usesTEInstanceGetTextureTransfer(); theTEInstanceGetVulkanTextureTransfer()block is commented out and
Texture::cmdTransitionImageLayout()is nevercalled, so no image memory barrier / ownership acquire is performed on
output textures.
TEVulkan.hin the SDK saysTEInstanceDoesVulkanTextureOwnershipTransfer()"returns true if theinstance 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 aCompthat never loaded a
.toxthe exit was0xC0000409with[Vulkan Loader] ERROR: vkDestroyFence: Invalid device. Mentioned onlybecause it was observed during the same runs; it is independent of the
texture problem (the JSON above is written before
unload()). Happy toopen a dedicated issue if useful.
Reproduce
touchpy-issue-reproducer.zip