Shared Memory
The Shared Memory LOP creates a zero-copy data channel between TouchDesigner and external processes using OS-level shared memory. In Send mode it writes each frame’s TOP image into a named buffer that any external process (Python ML worker, inference engine, etc.) can read directly. In Receive mode it reads a buffer that an external process has written and surfaces the result as a live TOP output. Because the data never leaves RAM, latency is minimal even for large image buffers.
Key Features
Section titled “Key Features”- Two-way operation: create and write a buffer (Send) or connect to an existing one (Receive)
- Per-frame writes driven by the
Activetoggle — no pulse required once running - Optional change detection with three hash algorithms to fire a callback only when image content actually changes
- Read-only status indicators for buffer size and frame count
- Automatic cleanup of owned buffers when the operator is deleted or the project closes
Requirements
Section titled “Requirements”- Windows is the primary supported platform. The operator uses Python’s
multiprocessing.shared_memorymodule (Python 3.8+), which is bundled with TouchDesigner’s Python environment and requires no additional installation. - The external process that consumes or produces the buffer must also use
multiprocessing.shared_memory(or a compatible shared memory API) with the same buffer name.
Input/Output
Section titled “Input/Output”Inputs
Section titled “Inputs”- Input 0 (TOP) — The source image to write into shared memory. Only consumed in Send mode. Connect any TOP (render, video, etc.) that you want to stream to an external process.
Outputs
Section titled “Outputs”- Output 0 (TOP) — Displays the image received from the shared memory buffer. Only active in Receive mode. In Send mode the output is a 1×1 transparent pixel.
Usage Examples
Section titled “Usage Examples”Streaming a render to an external ML process
Section titled “Streaming a render to an external ML process”- Connect your render TOP to the operator’s input.
- On the Sharedmem page, set Buffer Name to a unique identifier (e.g.
td_render_buffer). - Set Mode to
Send (Create). - Set Width, Height, and Channels to match the dimensions of your source TOP.
- Pulse Connect — the status field at the top of the page will confirm the connection.
- Toggle Active to On. The operator will write a new frame to shared memory every TD frame.
- In your external Python process, open the buffer by name:
from multiprocessing import shared_memoryshm = shared_memory.SharedMemory(name='td_render_buffer', create=False)
Receiving data from an external process
Section titled “Receiving data from an external process”- In your external process, create and write a named shared memory buffer with image data.
- Place a Shared Memory LOP in your network (no input needed).
- On the Sharedmem page, set Buffer Name to match the name used by the external process.
- Set Mode to
Receive (Connect). - Set Width, Height, and Channels to match the data layout written by the external process.
- Pulse Connect.
- Toggle Active to On. The operator’s output TOP will update each frame with the contents of the buffer.
Using change detection to trigger downstream logic
Section titled “Using change detection to trigger downstream logic”- Set Mode to
Receive (Connect)and connect as described above. - Under Change Detection, toggle Enable Change Detection to On.
- Choose a Hash Method —
MD5is the most accurate;Checksumis the fastest. - Set Callback Target to a COMP in your project that has an
ImageChangedmethod defined. When the buffer’s content changes between frames, that method is called automatically.
Best Practices
Section titled “Best Practices”- Match dimensions exactly. The buffer size is calculated as
Width × Height × Channelsbytes. If the external process writes a different size, the operator will detect a size mismatch and attempt to unlink and recreate the buffer (Send mode) or log a warning (Receive mode). Always configure Width, Height, and Channels before pulsing Connect. - Use a unique Buffer Name per connection. The name is system-wide. Two operators or processes with the same name on the same machine will collide.
- In Receive mode, start the external sender first. If you pulse Connect before the external process has created the buffer, the operator will log a “not found” warning. Pulse Connect again once the sender is running, or re-enable Active — the operator will retry on the next connect attempt.
- Toggle Active off when not needed. The operator writes or reads on every frame while Active is on. Disabling Active pauses the per-frame work without releasing the buffer, so you can resume cleanly.
- Change detection adds CPU cost. MD5 hashing a large image every frame is measurable. Use
Checksumif you need faster but less collision-resistant detection, or only enable change detection when you specifically need theImageChangedcallback.
Troubleshooting
Section titled “Troubleshooting”Status shows an error after a crash or force-quit. The OS may retain the shared memory segment from the previous run. Pulse Disconnect then Connect to unlink the stale segment and create a fresh one. In Send mode the operator will automatically try to unlink and recreate on reconnect.
Receive mode cannot find the buffer. The sender has not created the buffer yet, or the name is misspelled. Check that the Buffer Name matches exactly (case-sensitive on all platforms). Pulse Connect again once the sender is confirmed running.
Output TOP is black or 1×1.
In Receive mode this means getBuffer() returned None — the operator is not connected. Check the status field and pulse Connect. Also verify that Width, Height, and Channels match the data layout the sender is writing.
Image looks corrupted or shifted. The Width × Height × Channels dimensions do not match the actual data layout in the buffer. The operator reads the buffer as a flat byte array and reshapes it using the parameters you set — any mismatch will produce visual noise or a shifted image.
ImageChanged callback is not firing.
Verify that the COMP set in Callback Target has a method literally named ImageChanged (exact spelling). Also confirm Enable Change Detection is On and Active is On.
Parameters
Section titled “Parameters”Sharedmem
Section titled “Sharedmem”op('shared_mem').par.Status Str - Default:
"" (Empty String)
op('shared_mem').par.Memname Str - Default:
"" (Empty String)
op('shared_mem').par.Width Int - Default:
0- Range:
- 1 to 4096
- Slider Range:
- 1 to 4096
op('shared_mem').par.Height Int - Default:
0- Range:
- 1 to 4096
- Slider Range:
- 1 to 4096
op('shared_mem').par.Numchans Int - Default:
0- Range:
- 1 to 4
- Slider Range:
- 1 to 4
op('shared_mem').par.Play Toggle - Default:
False
op('shared_mem').par.Connect Pulse - Default:
False
op('shared_mem').par.Disconnect Pulse - Default:
False
op('shared_mem').par.Enablechangedetect Toggle - Default:
False
op('shared_mem').par.Callbacktarget COMP - Default:
"" (Empty String)
op('shared_mem').par.Printinfo Toggle - Default:
False
op('shared_mem').par.Framecount Int - Default:
0- Range:
- 0 to 1
- Slider Range:
- 0 to 1
op('shared_mem').par.Buffersize Str - Default:
"" (Empty String)
Changelog
Section titled “Changelog”v0.1.02026-03-12
- Remove input image size detection that overwrote Width/Height params causing frame oscillation - Reset shError flag when Play activates so connection works without manual Connect pulse
- Initial commit