Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

640 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

cuda-link

cuda-link

License: MIT Platform: Windows codecov

Zero-copy GPU texture transfer between TouchDesigner and Python processes using CUDA IPC.

Overview

This component enables zero-copy GPU texture sharing between TouchDesigner and Python processes using CUDA Inter-Process Communication (IPC). It eliminates CPU memory copies for real-time AI pipelines, video processing, and other GPU-accelerated workflows.

Key Features

  • Zero-copy GPU transfer - Textures stay on GPU, no CPU memory copies
  • Bidirectional IPC - TD → Python (input capture) AND Python → TD (AI output display)
  • Low-overhead IPC - Sub-100 µs GPU-side export at up to 1080p, microsecond-scale D2H when needed (see Performance below and docs/BENCHMARKS.md)
  • Ring buffer architecture - N-slot pipeline prevents producer/consumer blocking
  • GPU-side synchronization - CUDA IPC events eliminate CPU polling
  • Triple output modes - PyTorch tensors (GPU, zero-copy), CuPy arrays (GPU, zero-copy), or numpy arrays (CPU, D2H copy)
  • Production-ready - Tested at 30+ FPS for hours, handles dynamic resolution changes

Performance

Measured on RTX 4090 / PCIe 4.0 x16 / Windows 11 / driver 596.36. All Python-side.

Operation p50 Notes
export_frame() — 512×512 RGBA float32 24 µs Standalone, EXPORT_SYNC=1; GPU D2D + stream_synchronize
export_frame() — 1080p RGBA uint8, async+graphs (Python Exporter default) 13.9 µs −31.3 µs (69%) vs blocking baseline 45.2 µs; TD Sender default = blocking (v1.10.1+) ≈ 45 µs; see docs/BENCHMARKS.md
export_frame() — 1080p RGBA float32 106 µs Standalone, EXPORT_SYNC=1
export_frame() — 4K RGBA float32 357 µs Standalone, EXPORT_SYNC=1
get_frame_numpy() D2H — 512×512 float32 0.18 ms Standalone, ~22 GB/s
get_frame_numpy() D2H — 1080p float32 1.32 ms Standalone, ~24 GB/s PCIe 4.0
get_frame_numpy() D2H — 4K float32 5.7 ms Standalone, ~21 GB/s PCIe 4.0
get_frame() / get_frame_cupy() GPU <5 µs Zero-copy tensor/array view, no D2H
IPC notification latency ~136–286 µs Producer publish → consumer detect (cross-process)
Initialization ~50–100 µs One-time IPC handle opening

Requirements

  • OS: Windows 10/11 (CUDA IPC is Windows-only)
  • CUDA: 12.x (tested with 12.4)
  • GPU: NVIDIA GPU with CUDA compute capability 3.5+
  • TouchDesigner: 2022.x or later (for producer side)
  • Python: 3.11+ recommended (for consumer side; matches TouchDesigner's bundled interpreter). The pure-Python fallback wheel also installs on 3.9+ — see Distribution.

Python Dependencies

Required: None (pure ctypes CUDA wrapper)

Optional:

  • torch>=2.0 - For zero-copy GPU tensor output (recommended for AI pipelines)
  • cupy-cuda12x>=12.0 - For zero-copy GPU array output (CuPy/JAX workflows)
  • numpy>=1.21 - For CPU array output (for OpenCV, etc.)

Quick Start

1. TouchDesigner Side (Exporter)

Option A: Use the .tox component (recommended)

  1. Drag TOXES/CUDAIPCLink_v1.12.1.tox into your TD network
  2. Wire your source TOP to the input In TOP
  3. Set Ipcmemname parameter (e.g., "my_texture_ipc")
  4. Enable Active toggle

The component displays its transfer state in the read-only Status custom parameter: "<W>x<H> <dtype> <ch>ch" during active transfer, "WARNING: ..." or "ERROR: ..." on faults, and "Idle" when inactive. A warning_emitter Script TOP inside the COMP also shows a local warning badge when the component is open. See td_exporter/HELP_DOC.md for per-parameter documentation.

Option B: Build from source

See docs/TOX_BUILD_GUIDE.md for step-by-step assembly.

Option C: Library mode (cleaner .tox — fewer Text DATs)

For a leaner .tox (package installed once into a Python environment TD can see, instead of 14 mirror Text DATs), see Distribution → For TouchDesigner Integration.

2. Python Side (Importer)

Install the package

cuda-link ships prebuilt wheels — no compiler needed on your machine (see ADR-0013). Two supported targets: a system Python 3.11 install, or a dedicated venv (e.g. StreamDiffusionTD's pinned 3.11.9). Never pip-install directly into TouchDesigner's own bundled Python.

# Guided installer (recommended) — auto-downloads the wheel matching your
# target interpreter's version from GitHub Releases:
cd C:\path\to\CUDA_IPC
python scripts\install_td_library.py --mode 2 --venv D:\path\to\your\venv
# or: python scripts\install_td_library.py   (interactive menu — pick your target)

Manual wheel download, editable source installs, and PyPI: see Distribution → For Python Consumers.

Use in your Python script

from cuda_link import Importer, ImportSpec, ImportOutcome

importer = Importer.open(
    ImportSpec(
        shm_name="my_texture_ipc",
        shape=(1080, 1920, 4),  # height, width, channels (RGBA) — or None for auto-detect
        dtype="float32",         # "float32", "float16", "uint8" — or None for auto-detect
        timeout_ms=5000.0,       # Wait up to 5s for producer to appear (default)
    )
)

# Option 1: Get torch.Tensor (GPU, zero-copy)
result = importer.get_frame()
if result.outcome is ImportOutcome.NEW_FRAME:
    tensor = result.frame  # torch.Tensor on GPU, shape (1080, 1920, 4)
    # Use directly in AI model:
    # output = model(tensor)

# Option 2: Get numpy array (CPU, involves D2H copy)
result = importer.get_frame_numpy()
if result.outcome is ImportOutcome.NEW_FRAME:
    array = result.frame  # numpy.ndarray on CPU
    # Use in OpenCV, PIL, etc.:
    # cv2.imwrite("frame.png", array)

# Option 3: Get CuPy array (GPU, zero-copy)
result = importer.get_frame_cupy()
if result.outcome is ImportOutcome.NEW_FRAME:
    cupy_arr = result.frame  # cupy.ndarray on GPU
    # Use in CuPy/JAX workflows

# Context manager (recommended — ensures cleanup on exit)
with Importer.open(ImportSpec(shm_name="my_texture_ipc")) as importer:
    for _ in range(100):
        result = importer.get_frame()
        if result.outcome is ImportOutcome.NEW_FRAME:
            tensor = result.frame

# Explicit cleanup
importer.close()

▶ Runnable version: examples/03_td_to_pytorch_pipeline.py — spawns its own demo producer, so no TouchDesigner is needed to try it.

3. Python → TouchDesigner (AI Output)

Send AI-generated frames back to TD for display:

from cuda_link import Exporter, FrameSpec, GpuFrame

with Exporter.open(
    FrameSpec(
        shm_name="ai_output_ipc",  # Must match TD Receiver's Ipcmemname parameter
        height=512, width=512,
        channels=4, dtype="uint8",
        num_slots=2,               # Ring buffer slots (double-buffering)
    )
) as exporter:
    # Export each AI-generated frame (~10-20μs overhead at 512x512).
    # producer_stream arms cross-stream ordering so the D2D copy on the
    # non-blocking IPC stream is ordered after your kernel writes.
    # PyTorch: torch.cuda.current_stream().cuda_stream
    # CuPy:    cupy.cuda.get_current_stream().ptr
    stream_handle = torch.cuda.current_stream().cuda_stream
    exporter.export(GpuFrame(
        ptr=output_tensor.data_ptr(),
        size=output_tensor.nbytes,
        producer_stream=stream_handle,
    ))

On the TD side, set CUDAIPCExtension Mode to Receiver with matching Ipcmemname.

▶ Runnable version: examples/07_python_to_td_exporter_and_benchmark.py — includes a per-frame export() cost benchmark (p50/p95/p99).

Architecture

Direction A: TD (Producer) → Python (Consumer)
──────────────────────────────────────────────
CUDAIPCExtension facade
  └── TDSenderEngine (thin TD adapter)   Importer
        │ cuda_memory() → GpuFrame         │ get_frame() / get_frame_numpy()
        │ delegates to Exporter            │ Waits on IPC event
        └─→ SharedMemory ←─────────────────┘

Direction B: Python (Producer) → TD (Consumer)
───────────────────────────────────────────────
Exporter                           CUDAIPCExtension facade
  │ export(GpuFrame(ptr, size))      └── TDReceiverEngine
  │ cudaMemcpy D2D → ring buf             │ import_frame(script_top)
  └─→ SharedMemory ←──────────────────────┘ copyCUDAMemory()

Both directions share the same v0.5.0 binary protocol.

The TD extension uses a facade-with-delegation pattern: CUDAIPCExtension (~300 LOC) holds either a TDSenderEngine or TDReceiverEngine and delegates all work to it. TDSenderEngine is a thin TD-only adapter (~415 LOC) over the canonical Exporter — it owns pixel-format bridging, the cuda_memory()GpuFrame translation, dynamic geometry reopen, and HolderBarrier lifecycle; all GPU ring-buffer logic delegates to Exporter. Mode switches replace the engine entirely — zero cross-mode state leak. All TouchDesigner runtime access (ownerComp.par.*, top.cudaMemory(), copyCUDAMemory()) goes through the TDHost/TOPHandle adapter seam, making the engine logic testable without a TD runtime.

Ring Buffer (3 Slots)

The system uses a 3-slot ring buffer to allow producer and consumer to work in parallel:

  • Slot 0: Producer writes frame N
  • Slot 1: Producer writes frame N+1 while consumer reads frame N
  • Slot 2: Producer writes frame N+2 while consumer reads frame N+1
  • Wraps back to Slot 0 for frame N+3

This prevents blocking - producer never waits for consumer, consumer is always 1 frame behind.

SharedMemory Protocol (433 bytes for 3 slots)

[0-3]     magic "CIPD" (4B)       - Protocol validation (0x43495044)
[4-11]    version (8B)             - Increments on TD re-initialization
[12-15]   num_slots (4B)           - Number of ring buffer slots (3)
[16-19]   write_idx (4B)           - Current write index (atomic counter)

Per slot (128 bytes each):
[20+slot*128 : 84+slot*128]   cudaIpcMemHandle_t (64B)  - GPU memory handle
[84+slot*128 : 148+slot*128]  cudaIpcEventHandle_t (64B) - GPU event handle

[20+NUM_SLOTS*128]        shutdown_flag (1B)   - Reasserted to 0 every frame; set to 1 on exit
[21+NUM_SLOTS*128]        metadata (20B)       - width/height/num_comps/dtype/buffer_size
[41+NUM_SLOTS*128]        timestamp (8B)       - Producer perf_counter() for latency

For 3 slots: 20 + (3 × 128) + 1 + 20 + 8 = 433 bytes

Documentation

  • TOX Build Guide - Step-by-step .tox assembly in TouchDesigner
  • Architecture - Protocol spec, ring buffer design, GPU sync
  • Integration Examples - TD→PyTorch, TD→OpenCV, multi-stream
  • Runnable Examples - 8 standalone, heavily-commented scripts (examples/) — each spawns its own demo producer, so they run without TouchDesigner

Testing

The suite lives in tests/ split into five purpose-named packages:

tests/
  core/         protocol layer — SHM layout, format negotiation, ports, activation barriers
  cuda/         CUDA runtime seam — IPC wrapper, errcheck, handle guards, NVML, probe scripts
  td/           TouchDesigner integration — TDHost, Sender, Receiver, bootstrap, config
  integration/  end-to-end pipeline, round-trip data integrity, deprecation shims
  support/      tooling — env checks, console shutdown, wrapper-sync drift guard

Run the full test suite:

cd C:\path\to\CUDA_IPC

# Run a whole category (no CUDA needed)
pytest tests/core/ -v

# Single-file examples
pytest tests/core/test_shm_protocol.py -v      # protocol tests (no CUDA needed)
pytest tests/cuda/test_cuda_ipc_wrapper.py -v  # CUDA unit tests (requires GPU)

# All tests
pytest tests/ -v

# Skip slow multi-process tests
pytest tests/ -v -m "not slow"

Randomized ordering

pytest-randomly is active via addopts — every run uses a fresh seed and tests must be order-independent. To reproduce a specific failure:

# --randomly-seed prints at the top of each run (e.g. "Using --randomly-seed=12345")
pytest tests/ -m "not requires_cuda" -p randomly --randomly-seed=12345

Disable for a single run: add -p no:randomly.

Coverage gate

pytest tests/ -m "not requires_cuda" --cov=cuda_link --cov-report=term-missing

The gate is fail_under = 76 (branch-coverage-aware, baseline 79.46% measured 2026-07-12) in [tool.coverage.report] in pyproject.toml. For line-level inspection add --cov-report=html and open htmlcov/index.html.

Test doubles

Shared fakes are consolidated in tests/fakes/ and resolved automatically via pythonpath in pyproject.toml. Import them directly:

from fakes import FakeShmAdapter, FakeTDHost, FakeTOPHandle, make_connected_importer

Benchmarks

All results on RTX 4090 / PCIe 4.0 x16 / Windows 11 / driver 596.36. RGBA (4-channel) frames.

Key highlights:

  • export_frame() Python Exporter async+graphs13.9 µs p50 at 1080p uint8 (RTX 4090 / PCIe 4.0); −27.3 µs (60%) vs blocking baseline from eliminating cudaStreamSynchronize; −4.0 µs (22%) more from CUDA graph collapsing 3 WDDM submissions → 1; full A→D = −31.3 µs (69%). TD Sender default = blocking since v1.10.1 (Cell A ≈ 45 µs / Cell C ≈ 29 µs @ 1080p uint8; prevents source-buffer lifetime race → CUDA 719). Blocking baseline range: 24–357 µs p50 (512×512 → 4K float32, EXPORT_SYNC=1).
  • get_frame_numpy() D2H — 0.18 ms p50 (512×512) → 5.7 ms (4K) at ~22–24 GB/s PCIe 4.0. With opt-in CUDALINK_D2H_PIPELINED=1 and 5 ms consumer work: 1080p −6% / −309 µs, 4K −20% / −1276 µs.
  • Full IPC roundtrip — IPC notification latency ~136–286 µs cross-process (resolution-independent signaling).
  • vs CPU SharedMemory — ~3.4× faster E2E at 1080p, ~2.1× at 512×512. Producer write 4–19× faster (no CPU transit). With get_frame() / get_frame_cupy() (zero-copy), the consumer read collapses to <5 µs.
  • Receiver hot-path optimizations (v1.10.2) — receiver skips idle Script-TOP cooks when no new frame is queued (P11, reduces observable cook counts); import-hot-path _TorchBackend/_CupyBackend allocations cached across calls (P8, reduces per-frame GC pressure).

Full tables, per-resolution breakdowns, and CUDA Graphs A/B comparison: docs/BENCHMARKS.md

Performance Tuning (env vars)

The six variables most consumers reach for:

Variable Default Effect
CUDALINK_EXPORT_SYNC 0 (Python API) / 1 (TD Sender) Block CPU on the IPC stream after each export_frame(). Python Exporter API default: 0 (async) — the CUDA IPC event provides correct cross-process GPU ordering. TD Sender default: 1 (blocking) — the TD source is a transient cook-scoped TOP texture reclaimed right after the cook, so the D2D read must finish first (CUDA 719 guard; see CHANGELOG 1.10.1).
CUDALINK_DOORBELL 0 R2 Win32 named-event doorbell (opt-in). Producer signals a Win32 event after each publish_frame(); consumer blocks on it instead of poll-sleeping. Sub-300 µs notify latency, ~zero idle CPU. Single-consumer, Windows-only.
CUDALINK_WAIT_BACKEND auto R5 native notification-wait backend (compiled into the core wheel on Windows, ADR-0012). auto uses it when available with silent fallback; python forces the pre-R5 path; native skips the auto gate. Target: p50 < 10 µs vs 136–286 µs poll-sleep baseline.
CUDALINK_D2H_PIPELINED 0 Opt-in pipelined D2H for get_frame_numpy() — overlaps the copy with consumer CPU work at the cost of +1 frame latency. Gain: 1080p −6% / 4K −20% with 5 ms consumer work.
CUDALINK_IMPORT_RECONNECT 1 Enable automatic reconnect in the importer when the SHM segment disappears (e.g., producer restart). Set to 0 to raise immediately on a missing segment.
CUDALINK_TD_USE_GRAPHS 0 CUDA Graphs for the TouchDesigner-side CUDAIPCExtension Sender — collapses 3 WDDM kernel-mode submissions into 1. Auto-disabled on CUDA runtimes older than 11.3; falls back to the legacy stream path on any capture or launch failure.

Full reference (~40 variables, including TD-internal, profiling, and adaptive-barrier knobs): docs/ENV_VARS.md.

For GPU-timeline profiling (Nsight Systems / Nsight Compute / compute-sanitizer) see docs/PROFILING.md.

Troubleshooting

"SharedMemory not found"

Cause: Python importer started before TD exporter initialized.

Solution: Ensure the TD component is active before starting the Python process. If starting both together, use timeout_ms to give the producer time to initialize:

from cuda_link import Importer, ImportSpec
importer = Importer.open(ImportSpec(shm_name="my_project_ipc", timeout_ms=10000.0))  # Wait up to 10s

"CUDA IPC overhead unexpectedly high"

Cause: In standalone Python processes (WDDM), export_frame() with EXPORT_SYNC=1 typically measures 24–357 µs p50 (512×512 → 4K float32 RGBA, RTX 4090 / PCIe 4.0). Values 2–5× higher than these baselines may indicate GPU driver overhead, context contention, or PCIe bandwidth sharing with other D2H workloads.

Solution: Compare against the baseline numbers in docs/BENCHMARKS.md, which documents exact reproduction commands using the tracked scripts/profiling/ tooling (e.g. python scripts/profiling/profile_export.py --frames 2000 --export-profile).

"Version mismatch" or stale frames

Cause: TD re-exported IPC handles (network reset, resolution change).

Solution: The importer automatically detects version changes and re-opens handles. No action needed.

GPU memory leak

Cause: Importer not cleaned up properly.

Solution: Use the context manager pattern for automatic cleanup:

from cuda_link import Importer, ImportSpec, ImportOutcome
with Importer.open(ImportSpec(shm_name="my_project_ipc")) as importer:
    # importer.close() is called automatically on exit
    result = importer.get_frame()
    if result.outcome is ImportOutcome.NEW_FRAME:
        tensor = result.frame

Or call importer.close() explicitly in a finally block.

Distribution

cuda-link uses a dual distribution model to support both use cases:

For Python Consumers (StreamDiffusion, AI/ML pipelines)

cuda-link is Windows-only and ships as prebuilt wheels — see ADR-0013. Every tagged release publishes two wheel assets:

  • cuda_link-<version>-cp311-cp311-win_amd64.whl — Python 3.11 (covers both StreamDiffusionTD's pinned 3.11.9 and TouchDesigner's bundled 3.11.10), carrying the compiled _native_waiter wait-backend accelerator.
  • cuda_link-<version>-py3-none-any.whl — every other Python version. Pure Python; the native accelerator's benefit is marginal (<1–5%), so this loses almost nothing.

No local compiler is required for either. Two supported install scenarios in practice: a system Python 3.11 install, or a dedicated venv (e.g. StreamDiffusionTD's).

Method 1: Guided installer (recommended)

git clone https://github.com/forkni/cuda-link.git
cd cuda-link

# Auto-downloads the wheel matching your target interpreter's version from
# GitHub Releases — no --wheel or --build flag needed for the common case:
python scripts\install_td_library.py --mode 2 --venv D:\path\to\your\venv    # dedicated venv
python scripts\install_td_library.py --mode 4 --python C:\Python311\python.exe  # system Python
python scripts\install_td_library.py   # interactive menu

# Force reinstall to update:
python scripts\install_td_library.py --mode 2 --venv D:\path\to\your\venv

Method 2: Manual download from GitHub Releases

# Download the wheel matching your interpreter from
# https://github.com/forkni/cuda-link/releases, then:
pip install "cuda_link-1.12.1-cp311-cp311-win_amd64.whl[torch]"   # PyTorch GPU tensors
pip install "cuda_link-1.12.1-cp311-cp311-win_amd64.whl[cupy]"    # CuPy GPU arrays
pip install "cuda_link-1.12.1-cp311-cp311-win_amd64.whl[numpy]"   # NumPy CPU arrays
pip install "cuda_link-1.12.1-cp311-cp311-win_amd64.whl[all]"     # All output modes

# Force reinstall to update:
pip install --force-reinstall "cuda_link-1.12.1-cp311-cp311-win_amd64.whl[torch]"

Method 3: Editable install from source (for development)

git clone https://github.com/forkni/cuda-link.git
cd cuda-link
pip install -e ".[torch]"   # Changes to src/cuda_link/ apply immediately, no rebuild needed
pip install -e ".[all]"     # All output modes

Method 4: Build from source (dev-only, requires MSVC)

git clone https://github.com/forkni/cuda-link.git
cd cuda-link
utils\build_wheel.cmd            # Native wheel — requires an MSVC C++17 toolchain
utils\build_wheel.cmd nowaiter   # Fallback wheel — no compiler needed
# Then: python scripts\install_td_library.py --build --mode 2 --venv D:\path\to\your\venv

End users should not need this method — see Method 1 and 2 above.

Method 5: From PyPI (coming soon)

# pip install cuda-link[torch]

Usage:

from cuda_link import Importer, ImportSpec, ImportOutcome

importer = Importer.open(ImportSpec(shm_name="my_project_ipc"))
result = importer.get_frame()
if result.outcome is ImportOutcome.NEW_FRAME:
    tensor = result.frame  # torch.Tensor, GPU zero-copy

The cuda-link package contains only the consumer-side Python code (src/cuda_link/). The TouchDesigner extension is distributed separately.

For TouchDesigner Integration

Option A: Use the .tox component (recommended)

Drag TOXES/CUDAIPCLink_v1.12.1.tox into your TouchDesigner network.

Older versions: Previous .tox releases are available as downloadable assets on the GitHub Releases page — pick the tag matching the TouchDesigner build you target.

Option B: Build from source

Follow the manual build guide at docs/TOX_BUILD_GUIDE.md to assemble the .tox from td_exporter/ source files.

Option C: Library mode (cleaner .tox — fewer Text DATs)

Install cuda_link into a Python environment TouchDesigner can see. The CUDALinkBootstrap DAT then loads the package automatically — the 14 mirror Text DATs (Env, SHMProtocol, Exporter, Importer, …) are no longer needed in the .tox. Run the multi-target installer (one-time):

install_td_library.cmd             REM interactive menu — auto-downloads the matching
                                    REM prebuilt wheel; no build step needed (see ADR-0013)

Install modes (python scripts/install_td_library.py --help):

Mode Flag Description
1 --target DIR Install into a custom folder; set CUDALINK_LIB_PATH=DIR before launching TD
2 --venv DIR Install into an existing venv that TD is configured to use
3 --conda ENV Install into a conda environment
4 --python EXE Install into a parallel Python; auto-writes TD Preferences — no env var needed
5 --td-python EXE Install directly into TD's bundled Python (app.pythonExecutable)

Mode 4 (recommended for most setups): auto-discovers both the registered system Python (py -3) and the TouchDesigner install path; sets Python64 Path in TD Preferences so library mode activates on the next TD launch with zero env-var configuration.

REM Non-interactive mode 4 (auto-discover Python + TD):
install_td_library.cmd --mode 4 --non-interactive

REM Dry-run to preview what would be written:
install_td_library.cmd --mode 4 --dry-run

The TDHost/TDConfig/TDSender/TDReceiver glue DATs remain in the COMP unchanged. If CUDALINK_LIB_PATH is unset and mode 4 was not used, the bootstrap no-ops and the classic mirror DATs take over silently. See docs/TOX_BUILD_GUIDE.md for full instructions.

The TouchDesigner extension (td_exporter/) is not included in the pip package because it uses TD-specific APIs (parent(), op(), me, COMP-scoped imports) that cannot run outside TouchDesigner.

Use Cases

Use Case TD Side Python Side
TD → Python (StreamDiffusion, AI pipelines) .tox Sender mode install_td_library.py --mode 2/4 (see above)
Python → TD (AI output display) .tox Receiver mode install_td_library.py --mode 2/4 (see above)
TD → TD (two instances communicating) .tox on both sides Not needed

Both sides communicate through the 433-byte SharedMemory protocol — zero import dependencies between TD and Python code.


Changelog

See CHANGELOG.md for the full history.


Contributing

  1. Fork the repo and branch off development (master is release-only, promoted via .github/workflows/merge-development-to-main.yml).
  2. pip install -e ".[all]" for an editable install with every output backend.
  3. Run the checks CI enforces before opening a PR: pytest tests/ -v (see Testing for the coverage gate) and pyrefly for static typing.
  4. Use conventional-commit messages (feat:, fix:, docs:, …).
  5. Open the PR against development. CI runs tests, typecheck, and docs validation, and Claude's automated reviewer leaves feedback.

Use plain git/gh for your fork — the scripts/git/* wrappers in this repo are the maintainer's local automation, not a contributor requirement.


License

MIT License - See LICENSE file

Credits

Original implementation by Forkni (forkni@gmail.com). Extracted and refactored from the StreamDiffusionTD project.

About

Zero-copy bidirectional GPU texture sharing between TouchDesigner and Python via CUDA IPC. Sub-microsecond per-frame overhead with ring buffer architecture and GPU-side synchronization.

Topics

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages