Skip to content

Shared Memory

v0.1.0New

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.

  • Two-way operation: create and write a buffer (Send) or connect to an existing one (Receive)
  • Per-frame writes driven by the Active toggle — 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
  • Windows is the primary supported platform. The operator uses Python’s multiprocessing.shared_memory module (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 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.
  • 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.

Streaming a render to an external ML process

Section titled “Streaming a render to an external ML process”
  1. Connect your render TOP to the operator’s input.
  2. On the Sharedmem page, set Buffer Name to a unique identifier (e.g. td_render_buffer).
  3. Set Mode to Send (Create).
  4. Set Width, Height, and Channels to match the dimensions of your source TOP.
  5. Pulse Connect — the status field at the top of the page will confirm the connection.
  6. Toggle Active to On. The operator will write a new frame to shared memory every TD frame.
  7. In your external Python process, open the buffer by name:
    from multiprocessing import shared_memory
    shm = shared_memory.SharedMemory(name='td_render_buffer', create=False)
  1. In your external process, create and write a named shared memory buffer with image data.
  2. Place a Shared Memory LOP in your network (no input needed).
  3. On the Sharedmem page, set Buffer Name to match the name used by the external process.
  4. Set Mode to Receive (Connect).
  5. Set Width, Height, and Channels to match the data layout written by the external process.
  6. Pulse Connect.
  7. 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”
  1. Set Mode to Receive (Connect) and connect as described above.
  2. Under Change Detection, toggle Enable Change Detection to On.
  3. Choose a Hash MethodMD5 is the most accurate; Checksum is the fastest.
  4. Set Callback Target to a COMP in your project that has an ImageChanged method defined. When the buffer’s content changes between frames, that method is called automatically.
  • Match dimensions exactly. The buffer size is calculated as Width × Height × Channels bytes. 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 Checksum if you need faster but less collision-resistant detection, or only enable change detection when you specifically need the ImageChanged callback.

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.

Status (Status) op('shared_mem').par.Status Str
Default:
"" (Empty String)
Buffer Config Header
Buffer Name (Memname) op('shared_mem').par.Memname Str
Default:
"" (Empty String)
Mode (Mode) op('shared_mem').par.Mode Menu
Default:
send
Options:
send, receive
Width (Width) op('shared_mem').par.Width Int
Default:
0
Range:
1 to 4096
Slider Range:
1 to 4096
Height (Height) op('shared_mem').par.Height Int
Default:
0
Range:
1 to 4096
Slider Range:
1 to 4096
Channels (Numchans) op('shared_mem').par.Numchans Int
Default:
0
Range:
1 to 4
Slider Range:
1 to 4
Control Header
Active (Play) op('shared_mem').par.Play Toggle
Default:
False
Connect (Connect) op('shared_mem').par.Connect Pulse
Default:
False
Disconnect (Disconnect) op('shared_mem').par.Disconnect Pulse
Default:
False
Change Detection Header
Enable Change Detection (Enablechangedetect) op('shared_mem').par.Enablechangedetect Toggle
Default:
False
Hash Method (Hashmethod) op('shared_mem').par.Hashmethod Menu
Default:
md5
Options:
md5, python_hash, checksum
Callback Target (Callbacktarget) op('shared_mem').par.Callbacktarget COMP
Default:
"" (Empty String)
Debug Header
Print Debug Info (Printinfo) op('shared_mem').par.Printinfo Toggle
Default:
False
Frame Count (Framecount) op('shared_mem').par.Framecount Int
Default:
0
Range:
0 to 1
Slider Range:
0 to 1
Buffer Size (Buffersize) op('shared_mem').par.Buffersize Str
Default:
"" (Empty String)
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