├── .cargo └── config.toml ├── .github └── workflows │ └── build.yaml ├── .gitignore ├── CLAUDE.md ├── CMakeLists.txt ├── Cargo.lock ├── Cargo.toml ├── CppProperties.json ├── CrashAutoSave.NewProject.1.toe ├── Info.plist ├── README.md ├── bacon.toml ├── justfile ├── plugins ├── chop │ ├── euro-filter │ │ ├── Cargo.toml │ │ └── src │ │ │ ├── filter.rs │ │ │ └── lib.rs │ ├── filter │ │ ├── Cargo.toml │ │ └── src │ │ │ └── lib.rs │ ├── generator │ │ ├── Cargo.toml │ │ └── src │ │ │ └── lib.rs │ ├── monome-grid │ │ ├── Cargo.toml │ │ └── src │ │ │ └── lib.rs │ ├── python │ │ ├── Cargo.toml │ │ └── src │ │ │ └── lib.rs │ └── wasm │ │ ├── Cargo.toml │ │ └── src │ │ └── lib.rs ├── dat │ ├── dynamic_menu │ │ ├── Cargo.toml │ │ └── src │ │ │ └── lib.rs │ └── filter │ │ ├── Cargo.toml │ │ └── src │ │ └── lib.rs ├── sop │ └── generator-sop │ │ ├── Cargo.toml │ │ └── src │ │ ├── lib.rs │ │ └── shapes.rs └── top │ ├── bevy-top │ ├── Cargo.toml │ └── src │ │ └── lib.rs │ ├── cpu-memory-top │ ├── Cargo.toml │ └── src │ │ ├── frame_queue.rs │ │ └── lib.rs │ ├── cuda │ ├── Cargo.toml │ └── src │ │ └── lib.rs │ └── stylegan-http │ ├── Cargo.toml │ └── src │ └── lib.rs ├── src └── lib.rs ├── td-rs-autocxx-build ├── Cargo.toml └── src │ └── lib.rs ├── td-rs-base ├── Cargo.toml ├── build.rs └── src │ ├── CPlusPlus_Common.h │ ├── GL_Extensions.h │ ├── RustBase.h │ ├── RustPy.h │ ├── chop.rs │ ├── cuda.rs │ ├── cxx.rs │ ├── dat.rs │ ├── gltypes.h │ ├── lib.rs │ ├── param.rs │ ├── py.rs │ ├── sop.rs │ └── top.rs ├── td-rs-chop ├── Cargo.toml ├── build.rs └── src │ ├── CHOP_CPlusPlusBase.h │ ├── RustChopPlugin.cpp │ ├── RustChopPlugin.h │ ├── cxx.rs │ ├── lib.rs │ └── prelude.rs ├── td-rs-dat ├── Cargo.toml ├── build.rs └── src │ ├── DAT_CPlusPlusBase.h │ ├── RustDatPlugin.cpp │ ├── RustDatPlugin.h │ ├── cxx.rs │ ├── lib.rs │ ├── prelude.rs │ └── table.rs ├── td-rs-derive-py ├── Cargo.toml ├── src │ └── lib.rs └── tests │ ├── parameter_macro │ └── pass.rs │ └── parameter_macro_test.rs ├── td-rs-derive ├── Cargo.toml ├── src │ └── lib.rs └── tests │ ├── parameter_macro │ └── pass.rs │ └── parameter_macro_test.rs ├── td-rs-sop ├── Cargo.toml ├── build.rs └── src │ ├── RustSopPlugin.cpp │ ├── RustSopPlugin.h │ ├── SOP_CPlusPlusBase.h │ ├── cxx.rs │ ├── lib.rs │ └── prelude.rs ├── td-rs-top ├── Cargo.toml ├── build.rs └── src │ ├── RustTopPlugin.cpp │ ├── RustTopPlugin.h │ ├── TOP_CPlusPlusBase.h │ ├── cuda.rs │ ├── cxx.rs │ ├── lib.rs │ └── prelude.rs ├── td-rs-xtask ├── Cargo.toml ├── msvc │ ├── RustOp.sln │ ├── RustOp.vcxproj │ └── RustOp.vcxproj.user ├── src │ ├── config.rs │ ├── lib.rs │ ├── macos │ │ └── mod.rs │ ├── metadata.rs │ ├── util.rs │ └── windows │ │ └── mod.rs └── xcode │ └── project.pbxproj ├── td-rs.toml └── xtask ├── Cargo.toml └── src └── main.rs /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [alias] 2 | # Building the xtask package in release mode is normally not necessary, but if 3 | # you're going to compile other plugins in release mode then you'd need to 4 | # recompile serde(-derive) because the xtask packages needs serde to parse the 5 | # `bundler.toml` config file. To avoid needing to compile these expensive crates 6 | # twice, we'll default to also running the xtask target in release mode. 7 | xtask = "run --package xtask --release --" 8 | xtask-debug = "run --package xtask --" 9 | -------------------------------------------------------------------------------- /.github/workflows/build.yaml: -------------------------------------------------------------------------------- 1 | on: [push, pull_request] 2 | 3 | name: Build 4 | 5 | jobs: 6 | build: 7 | name: Build 8 | strategy: 9 | matrix: 10 | os: [ macos-latest, windows-latest ] 11 | runs-on: ${{ matrix.os }} 12 | steps: 13 | - uses: actions/checkout@v2 14 | - uses: actions-rs/toolchain@v1 15 | with: 16 | toolchain: nightly 17 | override: true 18 | - uses: actions-rs/cargo@v1 19 | with: 20 | command: build 21 | args: --workspace --exclude=python-chop --exclude=bevy-top --exclude=cuda --exclude=td-rs-derive-py 22 | # - uses: actions-rs/cargo@v1 23 | # with: 24 | # command: xtask 25 | # args: build cpu-memory-top 26 | # - name: Check `cargo fmt` was run 27 | # run: | 28 | # cd protocol_codegen 29 | # cargo fmt -- --check 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .DS_Store 3 | /target 4 | include/ 5 | build/ 6 | .vs/ 7 | Debug/ 8 | Release/ 9 | 10 | RustCHOP.xcodeproj/project.xcworkspace/xcuserdata 11 | *.xcodeproj/ 12 | cmake-build* 13 | output.plist 14 | 15 | venv/ 16 | vendor/ -------------------------------------------------------------------------------- /CLAUDE.md: -------------------------------------------------------------------------------- 1 | # CLAUDE.md 2 | 3 | This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. 4 | 5 | ## Project Overview 6 | 7 | **td-rs** is an experimental Rust framework for creating TouchDesigner plugins. It provides Rust bindings for TouchDesigner's C++ plugin API, enabling development of CHOPs, TOPs, SOPs, and DATs in Rust instead of C++. 8 | 9 | ## Architecture 10 | 11 | ### Workspace Structure 12 | - **`td-rs-base/`** - Core traits, types, and shared functionality for all plugin types 13 | - **`td-rs-chop/`**, **`td-rs-dat/`**, **`td-rs-sop/`**, **`td-rs-top/`** - Operator-specific frameworks 14 | - **`td-rs-derive/`** - Procedural macros for parameter generation 15 | - **`td-rs-xtask/`** - Build system and plugin management tools 16 | - **`plugins/`** - Example plugins organized by operator type 17 | 18 | ### Plugin Development Pattern 19 | Each plugin follows this structure: 20 | 1. Define parameter struct with `#[derive(Params)]` 21 | 2. Implement required traits: `OpNew`, `OpInfo`, `Op`, and operator-specific trait (`Chop`, `Top`, etc.) 22 | 3. Use `chop_plugin!()`, `top_plugin!()`, etc. macro to register 23 | 4. Configure `Cargo.toml` with `crate-type = ["staticlib"]` and `package.metadata.td-rs.type` 24 | 25 | ### Key Traits 26 | - **`OpInfo`** - Plugin metadata (name, version, inputs/outputs) 27 | - **`OpNew`** - Constructor 28 | - **`Op`** - Base functionality (parameters, pulse handling) 29 | - **`Chop`/`Top`/`Sop`/`Dat`** - Operator-specific execution logic 30 | 31 | ## Development Commands 32 | 33 | Use `just` (justfile) for all build operations: 34 | 35 | ```bash 36 | # Build a specific plugin 37 | just build 38 | 39 | # Install plugin to TouchDesigner plugins directory 40 | just install 41 | 42 | # Watch mode development (requires bacon) 43 | just dev 44 | 45 | # List all available plugins 46 | just list-plugins 47 | ``` 48 | 49 | ### Plugin locations: 50 | - **Windows**: `$HOME\OneDrive\Documents\Derivative\Plugins\` 51 | - **macOS**: `$HOME/Library/Application Support/Derivative/TouchDesigner099/Plugins` 52 | 53 | ## Testing 54 | 55 | No centralized test suite - each plugin can have individual tests. Use standard `cargo test` in plugin directories. 56 | 57 | ## Platform Requirements 58 | 59 | ### Windows 60 | - MSVC toolchain with Clang support 61 | - May require setting `LIBCLANG_PATH` environment variable 62 | - Target: `x86_64-pc-windows-msvc` 63 | 64 | ### macOS 65 | - Xcode with command line tools 66 | - Target: `aarch64-apple-darwin` (Apple Silicon) 67 | 68 | ## Current Development Status - Bevy TOP Plugin 69 | 70 | ### 🚧 Active Issues (December 2024) 71 | 72 | **PRIMARY FOCUS**: Debugging bevy-top plugin CUDA-Vulkan-Bevy interop pipeline 73 | 74 | **CRITICAL FIXES COMPLETED** ✅: 75 | 1. **Format Pipeline Overhaul**: Fixed sRGB vs linear format mismatches 76 | - `BGRA8Fixed` → `Bgra8Unorm` (linear) for base textures 77 | - `Bgra8UnormSrgb` views for Bevy camera compatibility 78 | - Dynamic format detection instead of hardcoded `Bgra8UnormSrgb` 79 | 80 | 2. **API Call Ordering**: Fixed "input before output" crash 81 | - Get input CUDA arrays BEFORE `beginCUDAOperations()` (matches C++ sample) 82 | - Proper CUDA array validation timing 83 | 84 | 3. **Bytes-per-pixel Calculations**: Fixed hardcoded assumptions 85 | - `get_bytes_per_pixel()` function for all TouchDesigner formats 86 | - Dynamic width calculations instead of `width * 4` 87 | 88 | **CURRENT DETECTIVE WORK** 🕵️‍♀️: 89 | 4. **Row Pitch Alignment Issues**: `cudaErrorInvalidPitchValue` 90 | - **Problem**: Vulkan external memory row pitch ≠ CUDA alignment requirements 91 | - **Root Cause**: GPU drivers align texture rows (256/512-byte boundaries) 92 | - **Solution**: Query actual Vulkan row pitch via `get_image_subresource_layout()` 93 | - **Status**: Mega debug logging added, testing alignment fixes 94 | 95 | 5. **Pending Investigation**: Segfault when input connected before output 96 | - May be related to row pitch/external memory lifecycle issues 97 | 98 | ### 🧬 Technical Deep Dive - CUDA-Vulkan Pipeline 99 | 100 | **Architecture**: TouchDesigner → CUDA → Vulkan External Memory → Bevy → Back to CUDA → TouchDesigner 101 | 102 | **The Problem**: Hardware-level memory layout assumptions 103 | - **TouchDesigner**: Provides CUDA arrays with unknown pitch 104 | - **Vulkan**: Creates external memory with driver-aligned row pitch 105 | - **CUDA**: Strict alignment requirements for `cudaMemcpy2D` operations 106 | - **Mismatch**: `width * bytes_per_pixel` ≠ actual driver row pitch 107 | 108 | **Detective Evidence**: 109 | ```rust 110 | // What we calculate: 512 * 4 = 2048 bytes 111 | // What Vulkan actually uses: 2304 bytes (512-byte aligned) 112 | // What CUDA needs: Aligned pitch values 113 | ``` 114 | 115 | **Current Fix Strategy**: 116 | - Query real Vulkan row pitch via `vk::get_image_subresource_layout()` 117 | - Align pitch to CUDA requirements (512-byte boundaries) 118 | - Use actual pitch in `cudaMemcpy2DFromArray`/`cudaMemcpy2DToArray` 119 | 120 | ## Important Implementation Details 121 | 122 | 1. **FFI Safety**: Uses autocxx for C++ bridge generation and careful Pin<> usage 123 | 2. **Parameter System**: Derive macros generate TouchDesigner-compatible parameters automatically 124 | 3. **Memory Management**: Rust structs are managed via opaque pointers in C++ layer 125 | 4. **Optional Features**: `python` (PyO3 integration), `tracing` (logging), `tokio` (async support) 126 | 5. **Alpha Status**: Experimental - expect breaking changes and potential instability 127 | 128 | ## Working with Plugins 129 | 130 | When creating new plugins: 131 | 1. Copy existing plugin structure from `/plugins/` examples 132 | 2. Update `Cargo.toml` metadata for operator type 133 | 3. Implement required traits following established patterns 134 | 4. Use parameter derive macros for TouchDesigner integration 135 | 5. Test with `just dev ` for rapid iteration -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # This is intended only for use with CLion 2 | 3 | cmake_minimum_required(VERSION 3.25) 4 | project(td_rs) 5 | 6 | set(CMAKE_CXX_STANDARD 14) 7 | 8 | include_directories(.) 9 | include_directories(target/aarch64-apple-darwin/cxxbridge/rust) 10 | include_directories(target/aarch64-apple-darwin/cxxbridge/td-rs-base/src) 11 | include_directories(target/aarch64-apple-darwin/cxxbridge/td-rs-chop/src) 12 | include_directories(target/aarch64-apple-darwin/cxxbridge/td-rs-param/src) 13 | include_directories(target/cxxbridge/rust) 14 | include_directories(target/cxxbridge/td-rs-base/src) 15 | include_directories(target/cxxbridge/td-rs-chop/src) 16 | include_directories(target/cxxbridge/td-rs-param/src) 17 | include_directories(td-rs-base/src) 18 | include_directories(td-rs-base/src/operator_input) 19 | include_directories(td-rs-base/src/parameter_manager) 20 | include_directories(td-rs-chop/src) 21 | include_directories(td-rs-sop/src) 22 | 23 | add_executable(td_rs 24 | td-rs-base/src/CPlusPlus_Common.h 25 | td-rs-chop/src/CHOP_CPlusPlusBase.h 26 | td-rs-chop/src/RustChopPlugin.cpp 27 | td-rs-chop/src/RustChopPlugin.h 28 | td-rs-base/src/RustBase.h 29 | td-rs-sop/src/RustSopPlugin.h 30 | td-rs-top/src/RustTopPlugin.cpp 31 | td-rs-top/src/TOP_CPlusPlusBase.h 32 | td-rs-top/src/RustTopPlugin.h td-rs-dat/src/RustDatPlugin.cpp td-rs-sop/src/RustSopPlugin.cpp td-rs-base/src/RustPy.h) 33 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "td-rs" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | 8 | [build-dependencies] 9 | 10 | [workspace] 11 | members = [ 12 | "plugins/chop/euro-filter", 13 | "plugins/chop/filter", 14 | "plugins/chop/generator", 15 | "plugins/chop/monome-grid", 16 | "plugins/chop/python", 17 | "plugins/chop/wasm", 18 | "plugins/dat/filter", 19 | "plugins/dat/dynamic_menu", 20 | "plugins/sop/generator-sop", 21 | "plugins/top/bevy-top", 22 | "plugins/top/cpu-memory-top", 23 | "plugins/top/cuda", 24 | "plugins/top/stylegan-http", 25 | "td-rs-autocxx-build", 26 | "td-rs-base", 27 | "td-rs-chop", 28 | "td-rs-dat", 29 | "td-rs-derive", 30 | "td-rs-derive-py", 31 | "td-rs-sop", 32 | "td-rs-top", 33 | "td-rs-xtask", 34 | "xtask", 35 | ] 36 | -------------------------------------------------------------------------------- /CppProperties.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "inheritEnvironments": [ 5 | "msvc_x64" 6 | ], 7 | "name": "x64-Release", 8 | "includePath": [ 9 | "${env.INCLUDE}", 10 | "${workspaceRoot}\\**" 11 | ], 12 | "defines": [ 13 | "WIN32", 14 | "NDEBUG", 15 | "UNICODE", 16 | "_UNICODE" 17 | ], 18 | "intelliSenseMode": "windows-msvc-x64" 19 | } 20 | ] 21 | } -------------------------------------------------------------------------------- /CrashAutoSave.NewProject.1.toe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tychedelia/td-rs/18925ee2a38fc3b7d5a855332b785a3930c731c9/CrashAutoSave.NewProject.1.toe -------------------------------------------------------------------------------- /Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | NSHumanReadableCopyright 22 | Copyright © 2021 Derivative. All rights reserved. 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `td-rs` 🎨👩‍💻 ![example workflow](https://github.com/tychedelia/td-rs/actions/workflows/build.yaml/badge.svg) 2 | 3 | Experiments integrating Rust into TouchDesigner's plugin framework. 4 | 5 | ## Version compatibility 6 | 7 | The library is currently intended to be used with TouchDesigner version `2023.12000`. 8 | 9 | ## Getting started 10 | 11 | Fork and clone this repository. Plugins are built using the build system described below. New plugins 12 | can be added by creating a new directory in `plugins/` and adding it to root `Cargo.toml` as a workspace 13 | member. 14 | 15 | A plugin's `Cargo.toml` should have the following properties: 16 | - `name` - The name of the plugin. This should be unique across all plugins. 17 | - `lib` - The type of crate. This should be set to `staticlib`. The name of the lib should be the same as the 18 | `package.name` property, but with underscores instead of hyphens. 19 | - `package.metadata.td-rs` - The `type` should be set to the operator type, e.g. `top`, `chop`, `sop`, `dat`. 20 | This is used by the build system to generate the correct C++ code. 21 | - A dependency on the parent chop crate, e.g. `td-rs-chop = { path = "../../../td-rs-chop" }`. 22 | 23 | All plugins must call their plugin constructor macro in their `lib.rs` file. For example, a `chop` plugin 24 | would call `chop_plugin!(PluginName)`. This macro will generate the necessary FFI code to register the plugin 25 | with TouchDesigner. 26 | 27 | See example plugins for reference. A good starting point can be just to copy an existing plugin. 28 | 29 | ### Features 30 | 31 | The following features are available for all parent operator dependencies: 32 | - `python` - Enable Python support. This can be used in combination with `td-rs-derive-py` to generate 33 | Python bindings for the plugin. 34 | - `tracing` - Enable tracing support using the [`tracing`](https://crates.io/crates/tracing) crate. This 35 | can be used to log messages to the TouchDesigner console. 36 | - `tokio` - Enable Tokio support. This can be used to spawn asynchronous tasks from the plugin from the shared 37 | Tokio runtime exported as `RUNTIME`. 38 | 39 | ## ⚠️ Status ⚠️ 40 | 41 | This project should be considered in **alpha** status. It is not yet ready for production use, however 42 | is mostly stable and usable for experimentation. Please file an issue if you encounter any problems, 43 | as it is our goal to make this project viable for production use. 44 | 45 | In particular, users may experience any of the following: 46 | - Crashes 47 | - Memory leaks 48 | - Missing APIs 49 | - Performance issues 50 | - Incomplete documentation 51 | - Breaking changes 52 | - Violations of Rust's aliasing rules leading to [scary things](https://predr.ag/blog/falsehoods-programmers-believe-about-undefined-behavior/) 53 | 54 | In other words, no warranty is provided, express or implied. 55 | 56 | ## Structure 57 | 58 | Using `autocxx` we generate a C++ interface or "bridge" to our Rust library, which is then compiled 59 | into a C++ plugin that can be loaded in TouchDesigner. 60 | 61 | ## Build 62 | 63 | `cargo-xtask` is used for the build framework. A [`justfile`](./justfile) is also provided. 64 | 65 | ### Dev/Watch Command 66 | 67 | You can use [bacon](https://dystroy.org/bacon) to watch for changes and build + install for you. 68 | 69 | 1. `cargo install bacon` 70 | 2. `just dev ` or `bacon dev -- ` 71 | 72 | ### `cargo-xtask` 73 | 74 | - `cargo xtask build $PLUGIN` - Build the plugin for the current platform. 75 | - `cargo xtask install $PLUGIN` - Install a built plugin to the TouchDesigner plugins directory. 76 | - `cargo xtask list-plugins` - List all available plugins. 77 | 78 | ### Windows 79 | 80 | #### Dependencies 81 | - TouchDesigner, installed in the default location (`C:\Program Files\Derivative\TouchDesigner`). 82 | - MSVC toolchain (Desktop C++, including Clang from additional components). Note: it may be necessary to set the 83 | `LIBCLANG_PATH` environment variable to the path of the Clang DLL. This can be found in the Visual Studio 84 | installation directory, e.g. `C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Tools\Llvm\bin`. 85 | - Rust `x86_64-pc-windows-msvc` target. 86 | 87 | ### macOS 88 | 89 | Currently only supports aarch64 (Apple Silicon) builds. Submit a PR if you'd like to add support for x86_64. 90 | 91 | #### Dependencies 92 | - TouchDesigner, installed in the default location (`/Applications/TouchDesigner.app`). 93 | - Xcode (installable from App Store). 94 | 95 | --- 96 | TouchDesigner is a registered trademark of Derivative Inc. This project is not affiliated with or endorsed 97 | by Derivative Inc. -------------------------------------------------------------------------------- /bacon.toml: -------------------------------------------------------------------------------- 1 | [jobs.dev] 2 | command = [ 3 | "cargo", "xtask", 4 | "build", 5 | ] 6 | need_stdout = true 7 | background = false 8 | on_success = "job:install" 9 | 10 | [jobs.install] 11 | command = [ 12 | "cargo", "xtask", 13 | "install", 14 | ] 15 | need_stdout = true 16 | background = false -------------------------------------------------------------------------------- /justfile: -------------------------------------------------------------------------------- 1 | build PLUGIN: 2 | cargo xtask build {{PLUGIN}} 3 | 4 | install PLUGIN: 5 | cargo xtask install {{PLUGIN}} 6 | 7 | dev PLUGIN: 8 | bacon dev -- {{PLUGIN}} 9 | 10 | list-plugins: 11 | cargo xtask list-plugins -------------------------------------------------------------------------------- /plugins/chop/euro-filter/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "euro-filter-chop" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [package.metadata.td-rs] 7 | type = "chop" 8 | 9 | [lib] 10 | name = "euro_filter_chop" 11 | crate-type = ["staticlib"] 12 | 13 | [dependencies] 14 | td-rs-chop = { path = "../../../td-rs-chop" } 15 | td-rs-derive = { path = "../../../td-rs-derive" } -------------------------------------------------------------------------------- /plugins/chop/euro-filter/src/filter.rs: -------------------------------------------------------------------------------- 1 | use std::f64::consts::PI; 2 | 3 | pub struct LowPassFilter { 4 | first_filtering: bool, 5 | hat_x_prev: f64, 6 | hat_x: f64, 7 | } 8 | 9 | impl LowPassFilter { 10 | pub fn new() -> Self { 11 | Self { 12 | first_filtering: true, 13 | hat_x_prev: 0.0, 14 | hat_x: 0.0, 15 | } 16 | } 17 | 18 | pub fn filter(&mut self, value: f64, alpha: f64) -> f64 { 19 | if self.first_filtering { 20 | self.first_filtering = false; 21 | self.hat_x_prev = value; 22 | } 23 | self.hat_x = alpha * value + (1.0 - alpha) * self.hat_x_prev; 24 | self.hat_x_prev = self.hat_x; 25 | self.hat_x 26 | } 27 | 28 | pub fn hat_x_prev(&self) -> f64 { 29 | self.hat_x_prev 30 | } 31 | } 32 | 33 | pub struct OneEuroImpl { 34 | first_filtering: bool, 35 | rate: f64, 36 | min_cut_off: f64, 37 | beta: f64, 38 | x_filt: LowPassFilter, 39 | d_cut_off: f64, 40 | dx_filt: LowPassFilter, 41 | } 42 | 43 | impl OneEuroImpl { 44 | pub fn new(rate: f64, min_cut_off: f64, beta: f64, d_cut_off: f64) -> Self { 45 | Self { 46 | first_filtering: true, 47 | rate, 48 | min_cut_off, 49 | beta, 50 | x_filt: LowPassFilter::new(), 51 | d_cut_off, 52 | dx_filt: LowPassFilter::new(), 53 | } 54 | } 55 | 56 | pub fn change_input(&mut self, rate: f64, min_cut_off: f64, beta: f64, d_cut_off: f64) { 57 | self.rate = rate; 58 | self.min_cut_off = min_cut_off; 59 | self.beta = beta; 60 | self.d_cut_off = d_cut_off; 61 | } 62 | 63 | pub fn filter(&mut self, x: f64) -> f64 { 64 | let dx = if self.first_filtering { 65 | 0.0 66 | } else { 67 | (x - self.x_filt.hat_x_prev()) * self.rate 68 | }; 69 | self.first_filtering = false; 70 | let edx = self.dx_filt.filter(dx, Self::alpha(self.d_cut_off)); 71 | let cut_off = self.min_cut_off + self.beta * edx.abs(); 72 | self.x_filt.filter(x, Self::alpha(cut_off)) 73 | } 74 | 75 | fn alpha(cutoff: f64) -> f64 { 76 | let tau = 1.0 / (2.0 * PI * cutoff); 77 | let te = 1.0 / cutoff; 78 | 1.0 / (1.0 + tau / te) 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /plugins/chop/euro-filter/src/lib.rs: -------------------------------------------------------------------------------- 1 | use crate::filter::OneEuroImpl; 2 | 3 | use td_rs_chop::*; 4 | use td_rs_derive::Params; 5 | 6 | mod filter; 7 | 8 | #[derive(Params, Default, Clone, Debug)] 9 | struct EuroFilterChopParams { 10 | #[param(label = "Cutoff Frequency (Hz)", page = "EuroFilter")] 11 | min_cutoff: f64, 12 | #[param(label = "Speed Coefficient", page = "EuroFilter")] 13 | beta: f64, 14 | #[param(label = "Slope Cutoff Frequency (Hz)", page = "EuroFilter")] 15 | d_cutoff: f64, 16 | } 17 | 18 | /// Struct representing our CHOP's state 19 | #[derive(Default)] 20 | pub struct EuroFilterChop { 21 | filters: Vec, 22 | params: EuroFilterChopParams, 23 | } 24 | 25 | impl OpNew for EuroFilterChop { 26 | fn new(_info: NodeInfo) -> Self { 27 | Default::default() 28 | } 29 | } 30 | 31 | impl OpInfo for EuroFilterChop { 32 | const OPERATOR_TYPE: &'static str = "Eurofilter"; 33 | const OPERATOR_LABEL: &'static str = "Euro Filter"; 34 | const MIN_INPUTS: usize = 1; 35 | const MAX_INPUTS: usize = 1; 36 | } 37 | 38 | impl Op for EuroFilterChop { 39 | fn params_mut(&mut self) -> Option> { 40 | Some(Box::new(&mut self.params)) 41 | } 42 | } 43 | 44 | impl Chop for EuroFilterChop { 45 | fn channel_name(&self, index: usize, _inputs: &OperatorInputs) -> String { 46 | format!("chan{}", index) 47 | } 48 | 49 | fn execute(&mut self, output: &mut ChopOutput, inputs: &OperatorInputs) { 50 | if let Some(input) = &inputs.input(0) { 51 | for filter in &mut self.filters { 52 | filter.change_input( 53 | input.num_samples() as f64, 54 | self.params.min_cutoff, 55 | self.params.beta, 56 | self.params.d_cutoff, 57 | ); 58 | } 59 | 60 | for _ in self.filters.len()..input.num_channels() { 61 | self.filters.push(OneEuroImpl::new( 62 | input.num_samples() as f64, 63 | self.params.min_cutoff, 64 | self.params.beta, 65 | self.params.d_cutoff, 66 | )); 67 | } 68 | 69 | let mut input_sample_idx = 0; 70 | for i in 0..output.num_channels() { 71 | for j in 0..output.num_samples() { 72 | input_sample_idx = (input_sample_idx + 1) % input.num_samples(); 73 | output[i][j] = self.filters[i].filter(input[i][input_sample_idx] as f64) as f32; 74 | } 75 | } 76 | } 77 | } 78 | 79 | fn general_info(&self, _inputs: &OperatorInputs) -> ChopGeneralInfo { 80 | ChopGeneralInfo { 81 | cook_every_frame: false, 82 | cook_every_frame_if_asked: true, 83 | timeslice: true, 84 | input_match_index: 0, 85 | } 86 | } 87 | 88 | fn output_info(&self, _inputs: &OperatorInputs) -> Option { 89 | None 90 | } 91 | } 92 | 93 | chop_plugin!(EuroFilterChop); 94 | -------------------------------------------------------------------------------- /plugins/chop/filter/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "filter-chop" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [package.metadata.td-rs] 7 | type = "chop" 8 | 9 | [lib] 10 | name = "filter_chop" 11 | crate-type = ["staticlib"] 12 | 13 | [dependencies] 14 | td-rs-chop = { path = "../../../td-rs-chop" } 15 | td-rs-derive = { path = "../../../td-rs-derive" } -------------------------------------------------------------------------------- /plugins/chop/filter/src/lib.rs: -------------------------------------------------------------------------------- 1 | use td_rs_chop::*; 2 | use td_rs_derive::Params; 3 | 4 | #[derive(Params, Default, Clone)] 5 | struct FilterChopParams { 6 | #[param(label = "Apply Scale", page = "Filter")] 7 | apply_scale: bool, 8 | #[param(label = "Scale", page = "Filter", min = - 10.0, max = 10.0)] 9 | scale: f32, 10 | #[param(label = "Apply Offset", page = "Filter")] 11 | apply_offset: bool, 12 | #[param(label = "Offset", page = "Filter", min = - 10.0, max = 10.0)] 13 | offset: f32, 14 | } 15 | 16 | /// Struct representing our CHOP's state 17 | pub struct FilterChop { 18 | params: FilterChopParams, 19 | } 20 | 21 | impl OpNew for FilterChop { 22 | fn new(_info: NodeInfo) -> Self { 23 | Self { 24 | params: FilterChopParams { 25 | apply_scale: true, 26 | scale: 1.0, 27 | apply_offset: false, 28 | offset: 0.0, 29 | }, 30 | } 31 | } 32 | } 33 | 34 | impl OpInfo for FilterChop { 35 | const OPERATOR_TYPE: &'static str = "Basicfilter"; 36 | const OPERATOR_LABEL: &'static str = "Basic Filter"; 37 | const MIN_INPUTS: usize = 1; 38 | const MAX_INPUTS: usize = 1; 39 | } 40 | 41 | impl Op for FilterChop { 42 | fn params_mut(&mut self) -> Option> { 43 | Some(Box::new(&mut self.params)) 44 | } 45 | } 46 | 47 | impl Chop for FilterChop { 48 | fn execute(&mut self, output: &mut ChopOutput, inputs: &OperatorInputs) { 49 | let params = inputs.params(); 50 | params.enable_param("Scale", true); 51 | params.enable_param("Offset", true); 52 | 53 | if let Some(input) = &inputs.input(0) { 54 | for i in 0..output.num_channels() { 55 | for j in 0..output.num_samples() { 56 | output[i][j] = input[i][j] * self.params.scale + self.params.offset; 57 | } 58 | } 59 | } 60 | } 61 | 62 | fn general_info(&self, _inputs: &OperatorInputs) -> ChopGeneralInfo { 63 | ChopGeneralInfo { 64 | cook_every_frame: false, 65 | cook_every_frame_if_asked: false, 66 | timeslice: false, 67 | input_match_index: 0, 68 | } 69 | } 70 | 71 | fn channel_name(&self, index: usize, _inputs: &OperatorInputs) -> String { 72 | format!("chan{}", index) 73 | } 74 | } 75 | 76 | chop_plugin!(FilterChop); 77 | -------------------------------------------------------------------------------- /plugins/chop/generator/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "generator-chop" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [package.metadata.td-rs] 7 | type = "chop" 8 | 9 | [lib] 10 | name = "generator_chop" 11 | crate-type = ["staticlib"] 12 | 13 | [dependencies] 14 | td-rs-chop = { path = "../../../td-rs-chop", features = ["tracing"] } 15 | td-rs-derive = { path = "../../../td-rs-derive" } 16 | tracing = "0.1" -------------------------------------------------------------------------------- /plugins/chop/generator/src/lib.rs: -------------------------------------------------------------------------------- 1 | use td_rs_chop::param::MenuParam; 2 | use td_rs_chop::*; 3 | use td_rs_derive::{Param, Params}; 4 | 5 | #[derive(Param, Default, Clone, Debug)] 6 | enum Operation { 7 | #[default] 8 | Add, 9 | Multiply, 10 | Power, 11 | } 12 | 13 | #[derive(Params, Default, Clone, Debug)] 14 | struct GeneratorChopParams { 15 | #[param(label = "Length", page = "Generator")] 16 | length: u32, 17 | #[param(label = "Number of Channels", page = "Generator", min = -10.0, max = 10.0)] 18 | num_channels: u32, 19 | #[param(label = "Apply Scale", page = "Generator")] 20 | apply_scale: bool, 21 | #[param(label = "Scale", page = "Generator")] 22 | scale: f32, 23 | #[param(label = "Operation", page = "Generator")] 24 | operation: Operation, 25 | } 26 | 27 | /// Struct representing our CHOP's state 28 | #[derive(Debug)] 29 | pub struct GeneratorChop { 30 | params: GeneratorChopParams, 31 | } 32 | 33 | /// Impl block providing default constructor for plugin 34 | impl OpNew for GeneratorChop { 35 | fn new(_info: NodeInfo) -> Self { 36 | Self { 37 | params: GeneratorChopParams { 38 | length: 0, 39 | num_channels: 0, 40 | apply_scale: false, 41 | scale: 1.0, 42 | operation: Operation::Add, 43 | }, 44 | } 45 | } 46 | } 47 | 48 | impl OpInfo for GeneratorChop { 49 | const OPERATOR_LABEL: &'static str = "Basic Generator"; 50 | const OPERATOR_TYPE: &'static str = "Basicgenerator"; 51 | } 52 | 53 | impl Op for GeneratorChop { 54 | fn params_mut(&mut self) -> Option> { 55 | Some(Box::new(&mut self.params)) 56 | } 57 | } 58 | 59 | impl Chop for GeneratorChop { 60 | #[tracing::instrument] 61 | fn execute(&mut self, output: &mut ChopOutput, inputs: &OperatorInputs) { 62 | let params = inputs.params(); 63 | params.enable_param("Scale", self.params.apply_scale); 64 | 65 | tracing::info!("Executing chop with params: {:?}", self.params); 66 | for i in 0..output.num_channels() { 67 | for j in 0..output.num_samples() { 68 | let cur_value = match self.params.operation { 69 | Operation::Add => (i as f32) + (j as f32), 70 | Operation::Multiply => (i as f32) * (j as f32), 71 | Operation::Power => (i as f32).powf(j as f32), 72 | }; 73 | let scale = if self.params.apply_scale { 74 | self.params.scale 75 | } else { 76 | 1.0 77 | }; 78 | let cur_value = cur_value * scale; 79 | output[i][j] = cur_value; 80 | } 81 | } 82 | } 83 | 84 | fn general_info(&self, _inputs: &OperatorInputs) -> ChopGeneralInfo { 85 | ChopGeneralInfo { 86 | cook_every_frame: false, 87 | cook_every_frame_if_asked: false, 88 | timeslice: false, 89 | input_match_index: 0, 90 | } 91 | } 92 | 93 | fn channel_name(&self, index: usize, _inputs: &OperatorInputs) -> String { 94 | format!("chan{}", index) 95 | } 96 | 97 | fn output_info(&self, _inputs: &OperatorInputs) -> Option { 98 | Some(ChopOutputInfo { 99 | num_channels: self.params.num_channels, 100 | num_samples: self.params.length, 101 | start_index: 0, 102 | ..Default::default() 103 | }) 104 | } 105 | } 106 | 107 | chop_plugin!(GeneratorChop); 108 | -------------------------------------------------------------------------------- /plugins/chop/monome-grid/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "monome-grid" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [package.metadata.td-rs] 7 | type = "chop" 8 | 9 | [lib] 10 | name = "monome_grid" 11 | crate-type = ["staticlib"] 12 | 13 | [dependencies] 14 | td-rs-chop = { path = "../../../td-rs-chop" } 15 | td-rs-derive = { path = "../../../td-rs-derive" } 16 | monome-rs = "1.1.3" -------------------------------------------------------------------------------- /plugins/chop/monome-grid/src/lib.rs: -------------------------------------------------------------------------------- 1 | use monome::{KeyDirection, Monome, MonomeDevice, MonomeEvent}; 2 | use td_rs_chop::cxx::OP_Inputs; 3 | use td_rs_chop::*; 4 | use td_rs_derive::{Param, Params}; 5 | 6 | #[derive(Param, Default, Clone, Debug)] 7 | enum Operation { 8 | #[default] 9 | Add, 10 | Multiply, 11 | Power, 12 | } 13 | 14 | #[derive(Params, Default, Clone, Debug, Eq, PartialEq)] 15 | struct MonomeGridParams { 16 | #[param(label = "Prefix", page = "Grid", default = "/touchdesigner")] 17 | prefix: String, 18 | #[param(label = "Hold", page = "Grid")] 19 | hold: bool, 20 | } 21 | 22 | /// Struct representing our CHOP's state 23 | #[derive(Debug)] 24 | pub struct MonomeGrid { 25 | params: MonomeGridParams, 26 | prev_params: MonomeGridParams, 27 | device: Option, 28 | grid: [bool; 128], 29 | } 30 | 31 | /// Impl block providing default constructor for plugin 32 | impl OpNew for MonomeGrid { 33 | fn new(_info: NodeInfo) -> Self { 34 | Self { 35 | params: MonomeGridParams { 36 | prefix: "/touchdesigner".to_string(), 37 | hold: false, 38 | }, 39 | prev_params: Default::default(), 40 | device: None, 41 | grid: [false; 128], 42 | } 43 | } 44 | } 45 | 46 | impl OpInfo for MonomeGrid { 47 | const OPERATOR_LABEL: &'static str = "Monome Grid"; 48 | const OPERATOR_TYPE: &'static str = "Monomegrid"; 49 | } 50 | 51 | impl Op for MonomeGrid { 52 | fn params_mut(&mut self) -> Option> { 53 | Some(Box::new(&mut self.params)) 54 | } 55 | } 56 | 57 | impl Chop for MonomeGrid { 58 | fn execute(&mut self, output: &mut ChopOutput, inputs: &OperatorInputs) { 59 | if self.params != self.prev_params || self.device.is_none() { 60 | self.prev_params = self.params.clone(); 61 | let device = match Monome::new(&self.params.prefix) { 62 | Ok(device) => device, 63 | Err(err) => { 64 | self.set_error(&format!("Error connecting to monome: {}", err)); 65 | return; 66 | } 67 | }; 68 | self.device = Some(device); 69 | } 70 | 71 | if let Some(ref mut device) = &mut self.device { 72 | while let Some(event) = device.poll() { 73 | match event { 74 | MonomeEvent::GridKey { x, y, direction } => { 75 | let index = (y * 16 + x) as usize; 76 | if self.params.hold { 77 | if matches!(direction, KeyDirection::Down) { 78 | self.grid[index] = !self.grid[index]; 79 | } 80 | } else { 81 | self.grid[index] = !self.grid[index]; 82 | } 83 | } 84 | _ => {} 85 | } 86 | } 87 | 88 | device.set_all(&self.grid); 89 | } 90 | 91 | for i in 0..output.num_channels() { 92 | output[i][0] = if self.grid[i] { 1.0 } else { 0.0 }; 93 | } 94 | } 95 | 96 | fn general_info(&self, _inputs: &OperatorInputs) -> ChopGeneralInfo { 97 | ChopGeneralInfo { 98 | cook_every_frame: true, 99 | cook_every_frame_if_asked: true, 100 | timeslice: false, 101 | input_match_index: 0, 102 | } 103 | } 104 | 105 | fn channel_name(&self, index: usize, _inputs: &OperatorInputs) -> String { 106 | format!("grid{}", index) 107 | } 108 | 109 | fn output_info(&self, _inputs: &OperatorInputs) -> Option { 110 | Some(ChopOutputInfo { 111 | num_channels: 128, 112 | num_samples: 1, 113 | start_index: 0, 114 | ..Default::default() 115 | }) 116 | } 117 | } 118 | 119 | chop_plugin!(MonomeGrid); 120 | -------------------------------------------------------------------------------- /plugins/chop/python/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "python-chop" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [package.metadata.td-rs] 7 | type = "chop" 8 | 9 | [lib] 10 | name = "python_chop" 11 | crate-type = ["staticlib"] 12 | 13 | [dependencies] 14 | pyo3 = { git = "https://github.com/tychedelia/pyo3", branch = "td-rs", features = ["abi3-py311"] } 15 | td-rs-chop = { path = "../../../td-rs-chop", features = ["python"] } 16 | td-rs-derive = { path = "../../../td-rs-derive" } 17 | td-rs-derive-py = { path = "../../../td-rs-derive-py" } 18 | -------------------------------------------------------------------------------- /plugins/chop/python/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![feature(min_specialization)] 2 | 3 | use td_rs_chop::prelude::*; 4 | use td_rs_derive::*; 5 | use td_rs_derive_py::PyOp; 6 | 7 | #[derive(Param, Default, Debug)] 8 | enum PythonChopShape { 9 | #[default] 10 | Sine, 11 | Square, 12 | Ramp, 13 | } 14 | 15 | #[derive(Params, Default, Debug)] 16 | struct PythonChopParams { 17 | #[param(label="Speed", min=-10.0, max=10.0, default=1.0)] 18 | speed: f32, 19 | #[param(label="Scale", min=-10.0, max=10.0, default=1.0)] 20 | scale: f32, 21 | #[param(label = "Shape")] 22 | shape: PythonChopShape, 23 | #[param(label = "Reset")] 24 | reset: Pulse, 25 | } 26 | 27 | #[derive(PyOp)] 28 | #[pyclass(unsendable)] 29 | pub struct PythonChop { 30 | info: NodeInfo, 31 | #[pyo3(get, set)] 32 | speed: f32, 33 | #[pyo3(get)] 34 | execute_count: u32, 35 | offset: f32, 36 | params: PythonChopParams, 37 | } 38 | 39 | #[pymethods] 40 | impl PythonChop { 41 | pub fn reset(&mut self) { 42 | self.offset = 0.0; 43 | } 44 | } 45 | 46 | impl OpNew for PythonChop { 47 | fn new(info: NodeInfo) -> Self { 48 | Self { 49 | info, 50 | speed: 1.0, 51 | execute_count: 0, 52 | offset: 0.0, 53 | params: Default::default(), 54 | } 55 | } 56 | } 57 | 58 | impl OpInfo for PythonChop { 59 | const OPERATOR_TYPE: &'static str = "Customsignalpython"; 60 | const OPERATOR_LABEL: &'static str = "Custom Signal Python"; 61 | const MIN_INPUTS: usize = 0; 62 | const MAX_INPUTS: usize = 1; 63 | const PYTHON_CALLBACKS_DAT: &'static str = " 64 | # This is an example callbacks DAT. 65 | # 66 | # op - The OP that is doing the callback 67 | # curSpeed - The current speed value the node will be using. 68 | # 69 | # Change the 0.0 to make the speed get adjusted by this callback. 70 | def getSpeedAdjust(op, curSpeed): 71 | return curSpeed + 0.0 72 | "; 73 | } 74 | 75 | impl Op for PythonChop { 76 | fn params_mut(&mut self) -> Option> { 77 | Some(Box::new(&mut self.params)) 78 | } 79 | 80 | fn info_dat(&self) -> Option> { 81 | Some(Box::new(self)) 82 | } 83 | 84 | fn info_chop(&self) -> Option> { 85 | Some(Box::new(self)) 86 | } 87 | 88 | fn pulse_pressed(&mut self, name: &str) { 89 | if name == "Reset" { 90 | self.reset(); 91 | } 92 | } 93 | } 94 | 95 | impl Chop for PythonChop { 96 | fn channel_name(&self, _index: usize, _input: &OperatorInputs) -> String { 97 | "chan1".to_string() 98 | } 99 | 100 | fn execute(&mut self, output: &mut ChopOutput, inputs: &OperatorInputs) { 101 | self.execute_count += 1; 102 | if inputs.num_inputs() > 0 { 103 | inputs.params().enable_param("Speed", false); 104 | inputs.params().enable_param("Reset", false); 105 | inputs.params().enable_param("Shape", false); 106 | if let Some(input) = inputs.input(0) { 107 | let num_samples = output.num_samples(); 108 | let num_channels = output.num_channels(); 109 | for channel in 0..num_channels { 110 | let input_channel = input.channel(channel); 111 | let output_channel = output.channel_mut(channel); 112 | for sample in 0..num_samples { 113 | output_channel[sample] = input_channel[sample] * self.params.scale; 114 | } 115 | } 116 | } 117 | } else { 118 | inputs.params().enable_param("Speed", true); 119 | inputs.params().enable_param("Reset", true); 120 | inputs.params().enable_param("Shape", true); 121 | // Apply Python class modifications 122 | self.params.speed *= self.speed; 123 | 124 | Python::with_gil(|py| { 125 | self.info.context().call_python_callback( 126 | py, 127 | "getSpeedAdjust", 128 | (self.speed,), 129 | None, 130 | |py, res| { 131 | if let Ok(speed) = res.extract::(py) { 132 | self.params.speed *= speed; 133 | } 134 | }, 135 | ) 136 | }) 137 | .unwrap(); 138 | 139 | let phase = 2.0 * std::f32::consts::PI / output.num_channels() as f32; 140 | let num_samples = output.num_samples(); 141 | let num_channels = output.num_channels(); 142 | let step = self.params.speed * 0.01; 143 | for channel in 0..num_channels { 144 | let mut offset = self.offset + phase * channel as f32; 145 | let v = match self.params.shape { 146 | PythonChopShape::Sine => offset.sin(), 147 | PythonChopShape::Square => { 148 | if (offset % 1.0).abs() > 0.5 { 149 | 1.0 150 | } else { 151 | 0.0 152 | } 153 | } 154 | PythonChopShape::Ramp => (offset % 1.0).abs(), 155 | }; 156 | let v = v * self.params.scale; 157 | 158 | let output_channel = output.channel_mut(channel); 159 | for sample in 0..num_samples { 160 | output_channel[sample] = v; 161 | offset += step; 162 | } 163 | } 164 | self.offset += step * num_samples as f32; 165 | } 166 | } 167 | 168 | fn general_info(&self, _inputs: &OperatorInputs) -> ChopGeneralInfo { 169 | ChopGeneralInfo { 170 | cook_every_frame: false, 171 | cook_every_frame_if_asked: true, 172 | timeslice: true, 173 | input_match_index: 0, 174 | } 175 | } 176 | 177 | fn output_info(&self, input: &OperatorInputs) -> Option { 178 | if input.num_inputs() > 0 { 179 | None 180 | } else { 181 | Some(ChopOutputInfo { 182 | num_channels: 1, 183 | sample_rate: 120.0, 184 | num_samples: 1, 185 | start_index: 0, 186 | }) 187 | } 188 | } 189 | } 190 | 191 | impl InfoChop for PythonChop { 192 | fn size(&self) -> usize { 193 | 2 194 | } 195 | 196 | fn channel(&self, index: usize) -> (String, f32) { 197 | match index { 198 | 0 => ("execute_count".to_string(), self.execute_count as f32), 199 | 1 => ("offset".to_string(), self.offset), 200 | _ => panic!("Invalid channel index"), 201 | } 202 | } 203 | } 204 | 205 | impl InfoDat for PythonChop { 206 | fn size(&self) -> (u32, u32) { 207 | (2, 2) 208 | } 209 | 210 | fn entry(&self, index: usize, entry_index: usize) -> String { 211 | match (index, entry_index) { 212 | (0, 0) => "executeCount".to_string(), 213 | (0, 1) => "offset".to_string(), 214 | (1, 0) => self.execute_count.to_string(), 215 | (1, 1) => self.offset.to_string(), 216 | (_, _) => panic!("Invalid entry index"), 217 | } 218 | } 219 | } 220 | 221 | chop_plugin!(PythonChop); 222 | -------------------------------------------------------------------------------- /plugins/chop/wasm/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "wasm-chop" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [package.metadata.td-rs] 7 | type = "chop" 8 | 9 | [lib] 10 | name = "wasm_chop" 11 | crate-type = ["staticlib"] 12 | 13 | [dependencies] 14 | td-rs-chop = { path = "../../../td-rs-chop" } 15 | td-rs-derive = { path = "../../../td-rs-derive" } 16 | wasmtime = "10.0.1" 17 | wasmprinter = "0.2.60" 18 | -------------------------------------------------------------------------------- /plugins/chop/wasm/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![feature(min_specialization)] 2 | use wasmtime::{Engine, Linker, Module, Store}; 3 | 4 | use td_rs_chop::*; 5 | use td_rs_derive::Params; 6 | 7 | #[derive(Params, Default, Clone)] 8 | struct WasmChopParams { 9 | #[param(label = "Apply Scale", page = "Filter")] 10 | apply_scale: bool, 11 | #[param(label = "Scale", page = "Filter", min = - 10.0, max = 10.0)] 12 | scale: f32, 13 | #[param(label = "Wasm", page = "Wasm")] 14 | wasm: FileParam, 15 | } 16 | 17 | pub struct WasmChop { 18 | params: WasmChopParams, 19 | engine: Engine, 20 | module: Option, 21 | } 22 | 23 | impl OpNew for WasmChop { 24 | fn new(_info: NodeInfo) -> Self { 25 | Self { 26 | params: WasmChopParams { 27 | ..Default::default() 28 | }, 29 | engine: Engine::default(), 30 | module: None, 31 | } 32 | } 33 | } 34 | 35 | impl OpInfo for WasmChop { 36 | const OPERATOR_TYPE: &'static str = "Wasm"; 37 | const OPERATOR_LABEL: &'static str = "Wasm"; 38 | const MIN_INPUTS: usize = 1; 39 | const MAX_INPUTS: usize = 1; 40 | } 41 | 42 | impl Op for WasmChop { 43 | fn params_mut(&mut self) -> Option> { 44 | Some(Box::new(&mut self.params)) 45 | } 46 | 47 | fn info_dat(&self) -> Option> { 48 | Some(Box::new(self)) 49 | } 50 | } 51 | 52 | impl InfoDat for WasmChop { 53 | fn entry(&self, _index: usize, _entry_index: usize) -> String { 54 | let wasm_file = &self.params.wasm; 55 | if wasm_file.exists() && wasm_file.is_file() { 56 | wasmprinter::print_file(wasm_file.as_path()).expect("Failed to print wasm file") 57 | } else { 58 | "".to_string() 59 | } 60 | } 61 | 62 | fn size(&self) -> (u32, u32) { 63 | (1, 1) 64 | } 65 | } 66 | 67 | impl Chop for WasmChop { 68 | fn execute(&mut self, output: &mut ChopOutput, inputs: &OperatorInputs) { 69 | let params = inputs.params(); 70 | params.enable_param("Wasm", false); 71 | params.enable_param("Scale", self.params.apply_scale); 72 | 73 | if let Some(input) = &inputs.input(0) { 74 | params.enable_param("Wasm", true); 75 | 76 | let wasm_file = &self.params.wasm; 77 | if wasm_file.exists() && wasm_file.is_file() { 78 | let module = Module::from_file(&self.engine.clone(), wasm_file.as_path()) 79 | .expect("Failed to load wasm file"); 80 | self.module = Some(module); 81 | } 82 | 83 | if let Some(module) = &self.module { 84 | let mut linker = Linker::new(&self.engine.clone()); 85 | 86 | let scale = self.params.scale; 87 | linker.func_wrap("env", "scale", move || scale).unwrap(); 88 | 89 | let mut store = Store::new(&self.engine.clone(), ()); 90 | let instance = match linker.instantiate(&mut store, module) { 91 | Ok(instance) => instance, 92 | Err(e) => { 93 | self.set_error(&format!("Failed to instantiate module: {}", e)); 94 | return; 95 | } 96 | }; 97 | let execute = instance 98 | .get_typed_func::<(u32, u32, f32), f32>(&mut store, "execute") 99 | .expect("Failed to get execute function"); 100 | 101 | for i in 0..output.num_channels() { 102 | for j in 0..output.num_samples() { 103 | let res = execute 104 | .call(&mut store, (i as u32, j as u32, input[i][j])) 105 | .expect("Failed to call execute function"); 106 | output[i][j] = res; 107 | } 108 | } 109 | } 110 | } 111 | } 112 | 113 | fn general_info(&self, _inputs: &OperatorInputs) -> ChopGeneralInfo { 114 | ChopGeneralInfo { 115 | cook_every_frame: false, 116 | cook_every_frame_if_asked: false, 117 | timeslice: false, 118 | input_match_index: 0, 119 | } 120 | } 121 | 122 | fn channel_name(&self, index: usize, _inputs: &OperatorInputs) -> String { 123 | format!("chan{}", index) 124 | } 125 | } 126 | 127 | chop_plugin!(WasmChop); 128 | -------------------------------------------------------------------------------- /plugins/dat/dynamic_menu/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "dynamic-menu" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [package.metadata.td-rs] 7 | type = "dat" 8 | 9 | [lib] 10 | name = "dynamic_menu" 11 | crate-type = ["staticlib"] 12 | 13 | [dependencies] 14 | td-rs-dat = { path = "../../../td-rs-dat" } 15 | td-rs-derive = { path = "../../../td-rs-derive" } -------------------------------------------------------------------------------- /plugins/dat/dynamic_menu/src/lib.rs: -------------------------------------------------------------------------------- 1 | use td_rs_dat::chop::ChopInput; 2 | use td_rs_dat::*; 3 | use td_rs_derive::{Param, Params}; 4 | 5 | #[derive(Params, Default, Clone, Debug)] 6 | struct DynamicMenuDatParams { 7 | #[param(label = "Menu")] 8 | menu: DynamicMenuParam, 9 | } 10 | 11 | /// Struct representing our DAT's state 12 | pub struct DynamicMenuDat { 13 | params: DynamicMenuDatParams, 14 | } 15 | 16 | impl OpNew for DynamicMenuDat { 17 | fn new(_info: NodeInfo) -> Self { 18 | Self { 19 | params: Default::default(), 20 | } 21 | } 22 | } 23 | 24 | impl OpInfo for DynamicMenuDat { 25 | const OPERATOR_TYPE: &'static str = "Dynamicmenu"; 26 | const OPERATOR_LABEL: &'static str = "Dynamic Menu"; 27 | const MIN_INPUTS: usize = 1; 28 | // This Dat takes no input 29 | const MAX_INPUTS: usize = 1; 30 | } 31 | 32 | impl Op for DynamicMenuDat { 33 | fn params_mut(&mut self) -> Option> { 34 | Some(Box::new(&mut self.params)) 35 | } 36 | } 37 | 38 | impl Dat for DynamicMenuDat { 39 | fn general_info(&self, _inputs: &OperatorInputs) -> DatGeneralInfo { 40 | DatGeneralInfo { 41 | cook_every_frame: false, 42 | cook_every_frame_if_asked: false, 43 | } 44 | } 45 | 46 | fn execute(&mut self, output: DatOutput, inputs: &OperatorInputs) { 47 | if let Some(input) = inputs.input(0) { 48 | match input.dat_type() { 49 | DatType::Text => { 50 | if let Some(output_text) = &self.params.menu.0 { 51 | output 52 | .text() 53 | .set_text(&format!("Selected: {}", output_text)); 54 | } else { 55 | output.text().set_text(""); 56 | } 57 | } 58 | _ => self.set_warning("Input must be a text DAT"), 59 | } 60 | } 61 | } 62 | 63 | fn build_dynamic_menu( 64 | &mut self, 65 | inputs: &OperatorInputs, 66 | menu_info: &mut DynamicMenuInfo, 67 | ) { 68 | if menu_info.param_name() == "Menu" { 69 | if let Some(input) = inputs.input(0) { 70 | match input.dat_type() { 71 | DatType::Text => { 72 | let text = input.text(); 73 | let labels = text 74 | .split('\n') 75 | .map(|s| s.to_string()) 76 | .collect::>(); 77 | for label in labels { 78 | let name = label.replace(" ", ""); 79 | menu_info.add_menu_entry(&name, &label); 80 | } 81 | } 82 | _ => self.set_warning("Input must be a text DAT"), 83 | } 84 | } 85 | } 86 | } 87 | } 88 | 89 | impl DynamicMenuDat {} 90 | 91 | dat_plugin!(DynamicMenuDat); 92 | -------------------------------------------------------------------------------- /plugins/dat/filter/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "filter-dat" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [package.metadata.td-rs] 7 | type = "dat" 8 | 9 | [lib] 10 | name = "filter_dat" 11 | crate-type = ["staticlib"] 12 | 13 | [dependencies] 14 | td-rs-dat = { path = "../../../td-rs-dat" } 15 | td-rs-derive = { path = "../../../td-rs-derive" } -------------------------------------------------------------------------------- /plugins/dat/filter/src/lib.rs: -------------------------------------------------------------------------------- 1 | use td_rs_dat::*; 2 | use td_rs_derive::{Param, Params}; 3 | 4 | #[derive(Param, Default, Clone, Debug)] 5 | enum FilterType { 6 | UpperCamelCase, 7 | #[default] 8 | LowerCase, 9 | UpperCase, 10 | } 11 | 12 | #[derive(Params, Default, Clone, Debug)] 13 | struct FilterDatParams { 14 | #[param(label = "Case", page = "Filter")] 15 | case: FilterType, 16 | #[param(label = "Keep Spaces", page = "Filter")] 17 | keep_spaces: bool, 18 | } 19 | 20 | /// Struct representing our DAT's state 21 | pub struct FilterDat { 22 | params: FilterDatParams, 23 | } 24 | 25 | impl OpNew for FilterDat { 26 | fn new(_info: NodeInfo) -> Self { 27 | Self { 28 | params: Default::default(), 29 | } 30 | } 31 | } 32 | 33 | impl OpInfo for FilterDat { 34 | const OPERATOR_TYPE: &'static str = "Basicfilter"; 35 | const OPERATOR_LABEL: &'static str = "Basic Filter"; 36 | const MIN_INPUTS: usize = 1; 37 | // This Dat takes no input 38 | const MAX_INPUTS: usize = 1; 39 | } 40 | 41 | impl Op for FilterDat { 42 | fn params_mut(&mut self) -> Option> { 43 | Some(Box::new(&mut self.params)) 44 | } 45 | } 46 | 47 | impl Dat for FilterDat { 48 | fn general_info(&self, _inputs: &OperatorInputs) -> DatGeneralInfo { 49 | DatGeneralInfo { 50 | cook_every_frame: false, 51 | cook_every_frame_if_asked: false, 52 | } 53 | } 54 | 55 | fn execute(&mut self, output: DatOutput, inputs: &OperatorInputs) { 56 | if let Some(input) = inputs.input(0) { 57 | match input.dat_type() { 58 | DatType::Table => self.execute_table(output, input), 59 | DatType::Text => self.execute_text(output, input), 60 | } 61 | } 62 | } 63 | } 64 | 65 | impl FilterDat { 66 | fn execute_table(&mut self, output: DatOutput, input: &DatInput) { 67 | let mut output = output.table::(); 68 | let [rows, cols] = input.table_size(); 69 | output.set_table_size(rows, cols); 70 | for row in 0..rows { 71 | for col in 0..cols { 72 | if let Some(cell) = input.cell(row, col) { 73 | match self.params.case { 74 | FilterType::UpperCamelCase => { 75 | let formatted = to_camel_case(cell, self.params.keep_spaces); 76 | output[[row, col]] = formatted; 77 | } 78 | FilterType::LowerCase => { 79 | let formatted = to_lower_case(cell, self.params.keep_spaces); 80 | output[[row, col]] = formatted; 81 | } 82 | FilterType::UpperCase => { 83 | let formatted = to_upper_case(cell, self.params.keep_spaces); 84 | output[[row, col]] = formatted; 85 | } 86 | } 87 | } 88 | } 89 | } 90 | } 91 | 92 | fn execute_text(&mut self, output: DatOutput, input: &DatInput) { 93 | let mut output = output.text(); 94 | match self.params.case { 95 | FilterType::UpperCamelCase => { 96 | let formatted = to_camel_case(input.text(), self.params.keep_spaces); 97 | output.set_text(&formatted); 98 | } 99 | FilterType::LowerCase => { 100 | let formatted = to_lower_case(input.text(), self.params.keep_spaces); 101 | output.set_text(&formatted); 102 | } 103 | FilterType::UpperCase => { 104 | let formatted = to_upper_case(input.text(), self.params.keep_spaces); 105 | output.set_text(&formatted); 106 | } 107 | } 108 | } 109 | } 110 | 111 | pub fn to_camel_case(s: &str, keep_spaces: bool) -> String { 112 | let mut out = String::new(); 113 | let mut next_upper = true; 114 | 115 | for c in s.chars() { 116 | if c.is_whitespace() { 117 | next_upper = true; 118 | if keep_spaces { 119 | out.push(c); 120 | } 121 | } else if next_upper { 122 | out.push(c.to_ascii_uppercase()); 123 | next_upper = false; 124 | } else { 125 | out.push(c.to_ascii_lowercase()); 126 | } 127 | } 128 | 129 | out 130 | } 131 | 132 | pub fn change_case(s: &str, keep_spaces: bool, upper: bool) -> String { 133 | let mut out = String::new(); 134 | 135 | for c in s.chars() { 136 | if keep_spaces || !c.is_whitespace() { 137 | out.push(if upper { 138 | c.to_ascii_uppercase() 139 | } else { 140 | c.to_ascii_lowercase() 141 | }); 142 | } 143 | } 144 | 145 | out 146 | } 147 | 148 | pub fn to_upper_case(s: &str, keep_spaces: bool) -> String { 149 | change_case(s, keep_spaces, true) 150 | } 151 | 152 | pub fn to_lower_case(s: &str, keep_spaces: bool) -> String { 153 | change_case(s, keep_spaces, false) 154 | } 155 | 156 | dat_plugin!(FilterDat); 157 | -------------------------------------------------------------------------------- /plugins/sop/generator-sop/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "generator-sop" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [package.metadata.td-rs] 7 | type = "sop" 8 | 9 | [lib] 10 | name = "generator_sop" 11 | crate-type = ["staticlib"] 12 | 13 | [dependencies] 14 | td-rs-sop = { path = "../../../td-rs-sop" } 15 | td-rs-derive = { path = "../../../td-rs-derive" } 16 | -------------------------------------------------------------------------------- /plugins/sop/generator-sop/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod shapes; 2 | 3 | use td_rs_derive::{Param, Params}; 4 | use td_rs_sop::*; 5 | 6 | #[derive(Param, Default)] 7 | enum Shape { 8 | #[default] 9 | Point, 10 | Line, 11 | Square, 12 | Cube, 13 | } 14 | 15 | #[derive(Params)] 16 | struct GeneratorSopParams { 17 | #[param(label = "Shape")] 18 | shape: Shape, 19 | #[param(label = "Color")] 20 | color: Color, 21 | #[param(label = "GPU Direct")] 22 | gpu_direct: bool, 23 | } 24 | 25 | struct GeneratorSop { 26 | params: GeneratorSopParams, 27 | shape_gen: shapes::ShapeGenerator, 28 | } 29 | 30 | impl OpNew for GeneratorSop { 31 | fn new(_info: NodeInfo) -> Self { 32 | Self { 33 | params: GeneratorSopParams { 34 | shape: Shape::default(), 35 | color: (0, 0, 0, 0).into(), 36 | gpu_direct: false, 37 | }, 38 | shape_gen: shapes::ShapeGenerator {}, 39 | } 40 | } 41 | } 42 | 43 | impl OpInfo for GeneratorSop { 44 | const OPERATOR_TYPE: &'static str = "Generator"; 45 | const OPERATOR_LABEL: &'static str = "Generator"; 46 | const MIN_INPUTS: usize = 0; 47 | const MAX_INPUTS: usize = 0; 48 | } 49 | 50 | impl Op for GeneratorSop { 51 | fn params_mut(&mut self) -> Option> { 52 | Some(Box::new(&mut self.params)) 53 | } 54 | } 55 | 56 | impl Sop for GeneratorSop { 57 | fn general_info(&self, _input: &OperatorInputs) -> SopGeneralInfo { 58 | SopGeneralInfo { 59 | cook_every_frame: false, 60 | cook_every_frame_if_asked: false, 61 | direct_to_gpu: self.params.gpu_direct, 62 | } 63 | } 64 | 65 | fn execute(&mut self, output: &mut SopOutput, _inputs: &OperatorInputs) { 66 | match self.params.shape { 67 | Shape::Point => self.shape_gen.output_dot(output), 68 | Shape::Line => self.shape_gen.output_line(output), 69 | Shape::Square => self.shape_gen.output_square(output), 70 | Shape::Cube => self.shape_gen.output_cube(output), 71 | } 72 | 73 | for i in 0..output.num_points() { 74 | output.set_color(&self.params.color, i); 75 | } 76 | 77 | output.set_bounding_box((-1, -1, -1, 1, 1, 1)); 78 | } 79 | 80 | fn execute_vbo(&mut self, output: SopVboOutput, _inputs: &OperatorInputs) { 81 | let mut output = match self.params.shape { 82 | Shape::Point => { 83 | let mut output = output.alloc_all(1, 1, 1, BufferMode::Static); 84 | self.shape_gen.output_dot_vbo(&mut output); 85 | output 86 | } 87 | Shape::Line => { 88 | let mut output = output.alloc_all( 89 | shapes::THE_LINE_NUM_PTS, 90 | shapes::THE_LINE_NUM_PTS, 91 | 1, 92 | BufferMode::Static, 93 | ); 94 | self.shape_gen.output_line_vbo(&mut output); 95 | output 96 | } 97 | Shape::Square => { 98 | let mut output = output.alloc_all( 99 | shapes::THE_SQUARE_NUM_PTS, 100 | shapes::THE_SQUARE_NUM_PRIM * 3, 101 | 1, 102 | BufferMode::Static, 103 | ); 104 | self.shape_gen.output_square_vbo(&mut output); 105 | output 106 | } 107 | Shape::Cube => { 108 | let mut output = output.alloc_all( 109 | shapes::THE_CUBE_NUM_PTS, 110 | shapes::THE_CUBE_NUM_PRIM * 3, 111 | 1, 112 | BufferMode::Static, 113 | ); 114 | self.shape_gen.output_cube_vbo(&mut output); 115 | output 116 | } 117 | }; 118 | 119 | let colors = output.colors(); 120 | let num_vertices = output.state.vertices; 121 | for i in 0..num_vertices { 122 | colors[i] = self.params.color.clone(); 123 | } 124 | output.set_bounding_box((-1, -1, -1, 1, 1, 1)); 125 | let _output = output.update_complete(); 126 | } 127 | } 128 | 129 | sop_plugin!(GeneratorSop); 130 | -------------------------------------------------------------------------------- /plugins/sop/generator-sop/src/shapes.rs: -------------------------------------------------------------------------------- 1 | use td_rs_sop::*; 2 | 3 | pub(crate) const THE_CUBE_NUM_PTS: usize = 24; 4 | pub(crate) const THE_CUBE_NUM_PRIM: usize = 12; 5 | pub(crate) const THE_SQUARE_NUM_PTS: usize = 4; 6 | pub(crate) const THE_SQUARE_NUM_PRIM: usize = 2; 7 | pub(crate) const THE_LINE_NUM_PTS: usize = 2; 8 | 9 | pub const THE_CUBE_POS: [Position; THE_CUBE_NUM_PTS] = [ 10 | // front 11 | Position::new(-1.0, -1.0, 1.0), 12 | Position::new(-1.0, 1.0, 1.0), 13 | Position::new(1.0, -1.0, 1.0), 14 | Position::new(1.0, 1.0, 1.0), 15 | // back 16 | Position::new(-1.0, -1.0, -1.0), 17 | Position::new(-1.0, 1.0, -1.0), 18 | Position::new(1.0, -1.0, -1.0), 19 | Position::new(1.0, 1.0, -1.0), 20 | // top 21 | Position::new(-1.0, 1.0, -1.0), 22 | Position::new(1.0, 1.0, -1.0), 23 | Position::new(-1.0, 1.0, 1.0), 24 | Position::new(1.0, 1.0, 1.0), 25 | // bottom 26 | Position::new(-1.0, -1.0, -1.0), 27 | Position::new(1.0, -1.0, -1.0), 28 | Position::new(-1.0, -1.0, 1.0), 29 | Position::new(1.0, -1.0, 1.0), 30 | // right 31 | Position::new(1.0, -1.0, -1.0), 32 | Position::new(1.0, -1.0, 1.0), 33 | Position::new(1.0, 1.0, -1.0), 34 | Position::new(1.0, 1.0, 1.0), 35 | // left 36 | Position::new(-1.0, -1.0, -1.0), 37 | Position::new(-1.0, -1.0, 1.0), 38 | Position::new(-1.0, 1.0, -1.0), 39 | Position::new(-1.0, 1.0, 1.0), 40 | ]; 41 | 42 | pub const THE_CUBE_NORMALS: [Vec3; THE_CUBE_NUM_PTS] = [ 43 | // front 44 | Vec3::new(0.0, 0.0, 1.0), 45 | Vec3::new(0.0, 0.0, 1.0), 46 | Vec3::new(0.0, 0.0, 1.0), 47 | Vec3::new(0.0, 0.0, 1.0), 48 | // back 49 | Vec3::new(0.0, 0.0, -1.0), 50 | Vec3::new(0.0, 0.0, -1.0), 51 | Vec3::new(0.0, 0.0, -1.0), 52 | Vec3::new(0.0, 0.0, -1.0), 53 | // top 54 | Vec3::new(0.0, 1.0, 0.0), 55 | Vec3::new(0.0, 1.0, 0.0), 56 | Vec3::new(0.0, 1.0, 0.0), 57 | Vec3::new(0.0, 1.0, 0.0), 58 | // bottom 59 | Vec3::new(0.0, -1.0, 0.0), 60 | Vec3::new(0.0, -1.0, 0.0), 61 | Vec3::new(0.0, -1.0, 0.0), 62 | Vec3::new(0.0, -1.0, 0.0), 63 | // right 64 | Vec3::new(1.0, 0.0, 0.0), 65 | Vec3::new(1.0, 0.0, 0.0), 66 | Vec3::new(1.0, 0.0, 0.0), 67 | Vec3::new(1.0, 0.0, 0.0), 68 | // left 69 | Vec3::new(-1.0, 0.0, 0.0), 70 | Vec3::new(-1.0, 0.0, 0.0), 71 | Vec3::new(-1.0, 0.0, 0.0), 72 | Vec3::new(-1.0, 0.0, 0.0), 73 | ]; 74 | 75 | pub const THE_CUBE_VERTICES: [u32; THE_CUBE_NUM_PRIM * 3] = [ 76 | // front 77 | 0, 1, 2, 3, 2, 1, // back 78 | 6, 5, 4, 5, 6, 7, // top 79 | 8, 9, 10, 11, 10, 9, // bottom 80 | 14, 13, 12, 13, 14, 15, // right 81 | 16, 17, 18, 19, 18, 17, // left 82 | 22, 21, 20, 21, 22, 23, 83 | ]; 84 | 85 | pub const THE_CUBE_TEXTURE: [TexCoord; THE_CUBE_NUM_PTS] = [ 86 | // front 87 | TexCoord::new(2.0 / 3.0, 0.0, 0.0), 88 | TexCoord::new(2.0 / 3.0, 0.5, 0.0), 89 | TexCoord::new(1.0, 0.0, 0.0), 90 | TexCoord::new(1.0, 0.5, 0.0), 91 | // back 92 | TexCoord::new(0.0 / 3.0, 0.5, 0.0), 93 | TexCoord::new(0.0 / 3.0, 0.0, 0.0), 94 | TexCoord::new(1.0 / 3.0, 0.5, 0.0), 95 | TexCoord::new(1.0 / 3.0, 0.0, 0.0), 96 | // top 97 | TexCoord::new(2.0 / 3.0, 1.0, 0.0), 98 | TexCoord::new(1.0, 1.0, 0.0), 99 | TexCoord::new(2.0 / 3.0, 0.5, 0.0), 100 | TexCoord::new(1.0, 0.5, 0.0), 101 | // bottom 102 | TexCoord::new(1.0 / 3.0, 0.5, 0.0), 103 | TexCoord::new(2.0 / 3.0, 0.5, 0.0), 104 | TexCoord::new(1.0 / 3.0, 1.0, 0.0), 105 | TexCoord::new(2.0 / 3.0, 1.0, 0.0), 106 | // right 107 | TexCoord::new(2.0 / 3.0, 0.0, 0.0), 108 | TexCoord::new(1.0 / 3.0, 0.0, 0.0), 109 | TexCoord::new(2.0 / 3.0, 0.5, 0.0), 110 | TexCoord::new(1.0 / 3.0, 0.5, 0.0), 111 | // left 112 | TexCoord::new(1.0 / 3.0, 1.0, 0.0), 113 | TexCoord::new(0.0 / 3.0, 1.0, 0.0), 114 | TexCoord::new(1.0 / 3.0, 0.5, 0.0), 115 | TexCoord::new(0.0 / 3.0, 0.5, 0.0), 116 | ]; 117 | 118 | // Square descriptors 119 | pub const THE_SQUARE_POS: [Position; THE_SQUARE_NUM_PTS] = [ 120 | Position::new(-1.0, -1.0, 0.0), 121 | Position::new(-1.0, 1.0, 0.0), 122 | Position::new(1.0, -1.0, 0.0), 123 | Position::new(1.0, 1.0, 0.0), 124 | ]; 125 | 126 | pub const THE_SQUARE_NORMALS: [Vec3; THE_SQUARE_NUM_PTS] = [ 127 | Vec3::new(0.0, 0.0, 1.0), 128 | Vec3::new(0.0, 0.0, 1.0), 129 | Vec3::new(0.0, 0.0, 1.0), 130 | Vec3::new(0.0, 0.0, 1.0), 131 | ]; 132 | 133 | pub const THE_SQUARE_VERTICES: [u32; THE_SQUARE_NUM_PRIM * 3] = [0, 1, 2, 3, 2, 1]; 134 | 135 | pub const THE_SQUARE_TEXTURE: [TexCoord; THE_SQUARE_NUM_PTS] = [ 136 | TexCoord::new(0.0, 0.0, 0.0), 137 | TexCoord::new(0.0, 1.0, 0.0), 138 | TexCoord::new(1.0, 0.0, 0.0), 139 | TexCoord::new(1.0, 1.0, 0.0), 140 | ]; 141 | 142 | // Line descriptors 143 | pub const THE_LINE_POS: [Position; THE_LINE_NUM_PTS] = [ 144 | Position::new(-1.0, -1.0, -1.0), 145 | Position::new(1.0, 1.0, 1.0), 146 | ]; 147 | 148 | pub const THE_LINE_NORMALS: [Vec3; THE_LINE_NUM_PTS] = 149 | [Vec3::new(-1.0, 0.0, 1.0), Vec3::new(-1.0, 0.0, 1.0)]; 150 | 151 | pub const THE_LINE_VERTICES: [u32; THE_LINE_NUM_PTS] = [0, 1]; 152 | 153 | pub const THE_LINE_TEXTURE: [TexCoord; THE_LINE_NUM_PTS] = 154 | [TexCoord::new(0.0, 0.0, 0.0), TexCoord::new(1.0, 1.0, 0.0)]; 155 | 156 | // Point descriptors 157 | pub const THE_POINT_POS: Position = Position::new(0.0, 0.0, 0.0); 158 | pub const THE_POINT_NORMAL: Vec3 = Vec3::new(0.0, 0.0, 1.0); 159 | pub const THE_POINT_TEXTURE: TexCoord = TexCoord::new(0.0, 0.0, 0.0); 160 | 161 | pub(crate) struct ShapeGenerator {} 162 | 163 | impl ShapeGenerator { 164 | pub fn output_dot(&self, output: &mut SopOutput) { 165 | output.add_point(THE_POINT_POS); 166 | output.set_normal(THE_POINT_NORMAL, 0); 167 | output.add_particle_system(1, 0); 168 | // output.set_tex_coord(&THE_POINT_TEXTURE, 1, 0); 169 | } 170 | 171 | pub fn output_line(&self, output: &mut SopOutput) { 172 | output.add_points(&THE_LINE_POS); 173 | output.set_normals(&THE_LINE_NORMALS, 0); 174 | output.add_line(&THE_LINE_VERTICES); 175 | // output.set_tex_coords(&THE_LINE_TEXTURE, 1, 0); 176 | } 177 | 178 | pub fn output_square(&self, output: &mut SopOutput) { 179 | output.add_points(&THE_SQUARE_POS); 180 | output.set_normals(&THE_SQUARE_NORMALS, 0); 181 | output.add_triangles(&THE_SQUARE_VERTICES); 182 | output.set_tex_coords(&THE_SQUARE_TEXTURE, 1, 0); 183 | } 184 | 185 | pub fn output_cube(&self, output: &mut SopOutput) { 186 | output.add_points(&THE_CUBE_POS); 187 | output.set_normals(&THE_CUBE_NORMALS, 0); 188 | output.add_triangles(&THE_CUBE_VERTICES); 189 | output.set_tex_coords(&THE_CUBE_TEXTURE, 1, 0); 190 | } 191 | 192 | pub fn output_dot_vbo(&mut self, output: &mut SopVboOutput) { 193 | output.positions()[0] = THE_POINT_POS; 194 | output.normals()[0] = THE_POINT_NORMAL; 195 | output.tex_coords()[0] = THE_POINT_TEXTURE; 196 | } 197 | 198 | pub fn output_line_vbo(&mut self, output: &mut SopVboOutput) { 199 | output.positions().clone_from_slice(&THE_LINE_POS); 200 | output.normals().clone_from_slice(&THE_LINE_NORMALS); 201 | output.tex_coords().clone_from_slice(&THE_LINE_TEXTURE); 202 | output 203 | .add_lines(THE_LINE_NUM_PTS) 204 | .clone_from_slice(&THE_LINE_VERTICES); 205 | } 206 | 207 | pub fn output_square_vbo(&mut self, output: &mut SopVboOutput) { 208 | output.positions().clone_from_slice(&THE_SQUARE_POS); 209 | // Here we manually invert the normals as per original C++ logic 210 | for (i, normal) in output.normals().iter_mut().enumerate() { 211 | *normal = &THE_SQUARE_NORMALS[i] * -1.0; 212 | } 213 | output.tex_coords().clone_from_slice(&THE_SQUARE_TEXTURE); 214 | output 215 | .add_triangles(THE_SQUARE_NUM_PRIM) 216 | .clone_from_slice(&THE_SQUARE_VERTICES); 217 | } 218 | 219 | pub fn output_cube_vbo(&mut self, output: &mut SopVboOutput) { 220 | output.positions().clone_from_slice(&THE_CUBE_POS); 221 | for (i, normal) in output.normals().iter_mut().enumerate() { 222 | *normal = &THE_CUBE_NORMALS[i] * -1.0; 223 | } 224 | output.tex_coords().clone_from_slice(&THE_CUBE_TEXTURE); 225 | output 226 | .add_triangles(THE_CUBE_NUM_PRIM) 227 | .clone_from_slice(&THE_CUBE_VERTICES); 228 | } 229 | } 230 | -------------------------------------------------------------------------------- /plugins/top/bevy-top/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "bevy-top" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [package.metadata.td-rs] 7 | type = "top" 8 | 9 | [lib] 10 | name = "bevy_top" 11 | crate-type = ["staticlib"] 12 | 13 | [dependencies] 14 | td-rs-top = { path = "../../../td-rs-top", features = ["cuda"] } 15 | td-rs-derive = { path = "../../../td-rs-derive" } 16 | bevy = { version = "0.16", default-features = false, features = [ 17 | "std", 18 | "async_executor", 19 | "android-game-activity", 20 | "android_shared_stdcxx", 21 | "animation", 22 | "bevy_asset", 23 | "bevy_audio", 24 | "bevy_color", 25 | "bevy_core_pipeline", 26 | "bevy_gilrs", 27 | "bevy_gizmos", 28 | "bevy_gltf", 29 | "bevy_input_focus", 30 | "bevy_log", 31 | "bevy_mesh_picking_backend", 32 | "bevy_pbr", 33 | "bevy_picking", 34 | "bevy_render", 35 | "bevy_scene", 36 | "bevy_sprite", 37 | "bevy_sprite_picking_backend", 38 | "bevy_state", 39 | "bevy_text", 40 | "bevy_ui", 41 | "bevy_ui_picking_backend", 42 | "bevy_window", 43 | "bevy_winit", 44 | "custom_cursor", 45 | "default_font", 46 | "hdr", 47 | "png", 48 | "smaa_luts", 49 | "sysinfo_plugin", 50 | "tonemapping_luts", 51 | "vorbis", 52 | "webgl2", 53 | "x11", 54 | ] } 55 | ash = "0.38" 56 | cudarc = { version = "0.16.4", features = ["runtime", "nvrtc", "driver", "cuda-12080", "dynamic-linking"], default-features = false } 57 | anyhow = "1.0" 58 | wgpu = "24.0" -------------------------------------------------------------------------------- /plugins/top/cpu-memory-top/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "cpu-memory-top" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [package.metadata.td-rs] 7 | type = "top" 8 | 9 | [lib] 10 | name = "cpu_memory_top" 11 | crate-type = ["staticlib"] 12 | 13 | [dependencies] 14 | td-rs-top = { path = "../../../td-rs-top" } 15 | td-rs-derive = { path = "../../../td-rs-derive" } 16 | bytemuck = "1.14" -------------------------------------------------------------------------------- /plugins/top/cpu-memory-top/src/frame_queue.rs: -------------------------------------------------------------------------------- 1 | use std::collections::VecDeque; 2 | use std::sync::{Arc, Mutex}; 3 | use td_rs_top::{TopBuffer, TopBufferFlags, TopContext, UploadInfo}; 4 | 5 | pub struct BufferInfo { 6 | pub buf: Option, 7 | pub upload_info: UploadInfo, 8 | } 9 | 10 | pub struct FrameQueue { 11 | context: Arc>, 12 | updated_buffers: Mutex>, 13 | } 14 | 15 | impl FrameQueue { 16 | pub fn new(context: Arc>) -> Self { 17 | Self { 18 | context, 19 | updated_buffers: Mutex::new(VecDeque::new()), 20 | } 21 | } 22 | 23 | pub fn get_buffer_to_update( 24 | &mut self, 25 | byte_size: usize, 26 | flags: TopBufferFlags, 27 | ) -> Option { 28 | let mut buffers = self.updated_buffers.lock().unwrap(); 29 | 30 | const MAX_QUEUE_SIZE: usize = 2; 31 | 32 | // If we've already reached the max queue size, replace the oldest buffer 33 | if buffers.len() >= MAX_QUEUE_SIZE { 34 | let old_buf = buffers.pop_front()?.buf; 35 | 36 | if let Some(b) = &old_buf { 37 | if b.size() < byte_size || b.size() > byte_size * 2 || b.flags() != flags { 38 | let mut ctx = self.context.lock().unwrap(); 39 | return Some(ctx.create_output_buffer(byte_size, flags)); 40 | } 41 | return old_buf; 42 | } 43 | } 44 | let mut ctx = self.context.lock().unwrap(); 45 | Some(ctx.create_output_buffer(byte_size, flags)) 46 | } 47 | 48 | pub fn update_complete(&self, buf_info: BufferInfo) { 49 | let mut buffers = self.updated_buffers.lock().unwrap(); 50 | buffers.push_back(buf_info); 51 | } 52 | 53 | pub fn update_cancelled(&self, buf: Arc) { 54 | drop(buf); 55 | } 56 | 57 | pub fn get_buffer_to_upload(&self) -> Option { 58 | let mut buffers = self.updated_buffers.lock().unwrap(); 59 | buffers.pop_front() 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /plugins/top/cpu-memory-top/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod frame_queue; 2 | 3 | use crate::frame_queue::FrameQueue; 4 | use std::sync::{Arc, Mutex}; 5 | use td_rs_derive::Params; 6 | use td_rs_top::*; 7 | 8 | #[derive(Params, Default, Clone, Debug)] 9 | struct CpuMemoryTopParams { 10 | #[param(label = "Brightness", min = 0.0, max = 1.0)] 11 | brightness: f64, 12 | #[param(label = "Speed", min = -10.0, max = 10.0, default = 1.0)] 13 | speed: f64, 14 | #[param(label = "Reset")] 15 | reset: Pulse, 16 | } 17 | 18 | /// Struct representing our SOP's state 19 | pub struct CpuMemoryTop { 20 | execute_count: u32, 21 | step: f64, 22 | params: CpuMemoryTopParams, 23 | pub ctx: Arc>, 24 | pub frame_queue: FrameQueue, 25 | previous_result: Option, 26 | } 27 | 28 | impl CpuMemoryTop { 29 | fn fill_buffer( 30 | buf: &mut TopBuffer, 31 | byte_offset: usize, 32 | width: usize, 33 | height: usize, 34 | step: f64, 35 | brightness: f64, 36 | ) { 37 | let required_size = byte_offset + width * height * 4 * std::mem::size_of::(); 38 | assert!(buf.size() >= required_size); 39 | 40 | let byte_slice: &mut [f32] = &mut buf.data_mut()[byte_offset..]; 41 | let mem: &mut [f32] = bytemuck::cast_slice_mut(byte_slice); 42 | 43 | let xstep = 44 | ((step as isize).wrapping_rem(width as isize)).rem_euclid(width as isize) as usize; 45 | let ystep = 46 | ((step as isize).wrapping_rem(height as isize)).rem_euclid(height as isize) as usize; 47 | 48 | for y in 0..height { 49 | for x in 0..width { 50 | let pixel = &mut mem[4 * (y * width + x)..4 * (y * width + x + 1)]; 51 | 52 | // RGBA 53 | pixel[0] = if x > xstep { brightness as f32 } else { 0.0 }; 54 | pixel[1] = if y > ystep { brightness as f32 } else { 0.0 }; 55 | pixel[2] = ((xstep % 50) as f32 / 50.0) * brightness as f32; 56 | pixel[3] = 1.0; 57 | } 58 | } 59 | } 60 | 61 | fn fill_and_upload( 62 | &mut self, 63 | output: &mut TopOutput, 64 | speed: f64, 65 | width: usize, 66 | height: usize, 67 | tex_dim: TexDim, 68 | mut num_layers: usize, 69 | color_buffer_index: usize, 70 | ) { 71 | let depth = match tex_dim { 72 | TexDim::E2DArray | TexDim::E3D => num_layers, 73 | _ => 1, 74 | }; 75 | 76 | if tex_dim == TexDim::ECube { 77 | num_layers = 6; 78 | }; 79 | let info = UploadInfo { 80 | buffer_offset: 0, 81 | texture_desc: TextureDesc { 82 | tex_dim, 83 | width, 84 | height, 85 | pixel_format: PixelFormat::RGBA32Float, 86 | aspect_x: 0.0, 87 | depth, 88 | aspect_y: 0.0, 89 | }, 90 | first_pixel: Default::default(), 91 | color_buffer_index, 92 | }; 93 | 94 | let layer_bytes = 95 | (info.texture_desc.width * info.texture_desc.height * 4 * std::mem::size_of::()) 96 | as u64; 97 | let byte_size = layer_bytes * num_layers as u64; 98 | let mut ctx = self.ctx.lock().unwrap(); 99 | let mut buf = ctx.create_output_buffer(byte_size as usize, TopBufferFlags::None); 100 | 101 | let mut byte_offset = 0; 102 | for _ in 0..num_layers { 103 | self.step += speed; 104 | Self::fill_buffer( 105 | &mut buf, 106 | byte_offset as usize, 107 | info.texture_desc.width, 108 | info.texture_desc.height, 109 | self.step, 110 | self.params.brightness, 111 | ); 112 | byte_offset += layer_bytes; 113 | } 114 | 115 | output.upload_buffer(&mut buf, &info); 116 | } 117 | } 118 | 119 | impl TopNew for CpuMemoryTop { 120 | fn new(_info: NodeInfo, context: TopContext) -> Self { 121 | let ctx = Arc::new(Mutex::new(context)); 122 | Self { 123 | frame_queue: FrameQueue::new(ctx.clone()), 124 | execute_count: 0, 125 | ctx, 126 | params: CpuMemoryTopParams::default(), 127 | step: 0.0, 128 | previous_result: None, 129 | } 130 | } 131 | } 132 | 133 | impl OpInfo for CpuMemoryTop { 134 | const OPERATOR_LABEL: &'static str = "CPU Mem Sample"; 135 | const OPERATOR_TYPE: &'static str = "Cpumemsample"; 136 | const OPERATOR_ICON: &'static str = "CPM"; 137 | const MAX_INPUTS: usize = 1; 138 | const MIN_INPUTS: usize = 0; 139 | } 140 | 141 | impl TopInfo for CpuMemoryTop { 142 | const EXECUTE_MODE: ExecuteMode = ExecuteMode::Cpu; 143 | } 144 | 145 | impl Op for CpuMemoryTop { 146 | fn params_mut(&mut self) -> Option> { 147 | Some(Box::new(&mut self.params)) 148 | } 149 | 150 | fn pulse_pressed(&mut self, name: &str) { 151 | if name == "Reset" { 152 | self.step = 0.0; 153 | } 154 | } 155 | } 156 | 157 | impl Top for CpuMemoryTop { 158 | fn execute(&mut self, mut output: TopOutput, input: &OperatorInputs) { 159 | self.execute_count += 1; 160 | 161 | if let Some(buf_info) = self.frame_queue.get_buffer_to_upload() { 162 | if let Some(mut buf) = buf_info.buf { 163 | output.upload_buffer(&mut buf, &buf_info.upload_info) 164 | } 165 | } 166 | 167 | self.fill_and_upload(&mut output, self.params.speed, 256, 256, TexDim::E2D, 1, 0); 168 | 169 | if let Some(input) = input.input(0) { 170 | let download_opts = DownloadOptions::default(); 171 | let res = input.download_texture(download_opts); 172 | if let Some(prev) = &mut self.previous_result { 173 | let upload_info = UploadInfo { 174 | color_buffer_index: 3, 175 | texture_desc: prev.texture_desc(), 176 | ..Default::default() 177 | }; 178 | let mut ctx = self.ctx.lock().unwrap(); 179 | let mut buf = ctx.create_output_buffer(prev.size(), TopBufferFlags::None); 180 | buf.data_mut::().copy_from_slice(prev.data()); 181 | output.upload_buffer(&mut buf, &upload_info); 182 | } 183 | 184 | self.previous_result = Some(res); 185 | } 186 | } 187 | } 188 | 189 | top_plugin!(CpuMemoryTop); 190 | -------------------------------------------------------------------------------- /plugins/top/cuda/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "cuda" 3 | version = "0.1.0" 4 | edition = "2024" 5 | 6 | [package.metadata.td-rs] 7 | type = "top" 8 | 9 | [lib] 10 | name = "cuda" 11 | crate-type = ["staticlib"] 12 | 13 | [dependencies] 14 | td-rs-top = { path = "../../../td-rs-top", features = ["cuda"] } 15 | td-rs-derive = { path = "../../../td-rs-derive" } 16 | cudarc = { version = "0.16.4", features = ["runtime", "nvrtc", "driver", "cuda-12080", "dynamic-linking"], default-features = false } 17 | anyhow = "1.0" -------------------------------------------------------------------------------- /plugins/top/stylegan-http/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "stylegan-http" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [package.metadata.td-rs] 7 | type = "top" 8 | 9 | [lib] 10 | name = "stylegan_http" 11 | crate-type = ["staticlib"] 12 | 13 | 14 | [dependencies] 15 | td-rs-top = { path = "../../../td-rs-top", features = ["tokio"] } 16 | td-rs-derive = { path = "../../../td-rs-derive" } 17 | tokio = "1" 18 | reqwest = { version = "0.11", default-features = false } 19 | futures = "0.3" 20 | anyhow = "1" 21 | tracing = "0.1" 22 | tracing-subscriber = "0.3" -------------------------------------------------------------------------------- /plugins/top/stylegan-http/src/lib.rs: -------------------------------------------------------------------------------- 1 | use futures::task::noop_waker_ref; 2 | use std::collections::VecDeque; 3 | use std::future; 4 | use std::future::Future; 5 | use std::pin::{pin, Pin}; 6 | use std::sync::Arc; 7 | use std::task::Poll; 8 | use td_rs_derive::Params; 9 | use td_rs_top::*; 10 | use tokio::task::JoinHandle; 11 | use tracing::{error, info}; 12 | 13 | const WIDTH: usize = 1024; 14 | const HEIGHT: usize = 1024; 15 | const MAX_TASKS: usize = 10; 16 | 17 | #[derive(Params, Default, Clone, Debug)] 18 | struct StyleganHttpTopParams { 19 | #[param(label = "Ip Address", page = "Config", default = "localhost")] 20 | ip: String, 21 | #[param(label = "Port", page = "Config", default = 5000)] 22 | port: u16, 23 | #[param(label = "Seed", page = "Stylegan")] 24 | seed: u16, 25 | #[param(label = "Blocing", page = "Config")] 26 | blocking: bool, 27 | #[param( 28 | label = "X Feature", 29 | page = "Stylegan", 30 | min = 0.0, 31 | max = 512.0, 32 | clamp = true 33 | )] 34 | x_feature: u32, 35 | #[param( 36 | label = "X Range", 37 | page = "Stylegan", 38 | min = 0.0, 39 | max = 512.0, 40 | clamp = true 41 | )] 42 | x_range: f32, 43 | #[param( 44 | label = "Y Feature", 45 | page = "Stylegan", 46 | min = 0.0, 47 | max = 512.0, 48 | clamp = true 49 | )] 50 | y_feature: u32, 51 | #[param( 52 | label = "Y Range", 53 | page = "Stylegan", 54 | min = 0.0, 55 | max = 512.0, 56 | clamp = true 57 | )] 58 | y_range: f32, 59 | #[param( 60 | label = "Z Feature", 61 | page = "Stylegan", 62 | min = 0.0, 63 | max = 512.0, 64 | clamp = true 65 | )] 66 | z_feature: u32, 67 | #[param( 68 | label = "Z Range", 69 | page = "Stylegan", 70 | min = 0.0, 71 | max = 512.0, 72 | clamp = true 73 | )] 74 | z_range: f32, 75 | } 76 | 77 | pub type Task = JoinHandle>>; 78 | 79 | /// Struct representing our SOP's state 80 | pub struct StyleganHttpTop { 81 | params: StyleganHttpTopParams, 82 | execute_count: u32, 83 | context: TopContext, 84 | tasks: VecDeque, 85 | last_req: Option, 86 | } 87 | 88 | impl Future for StyleganHttpTop { 89 | type Output = Option>; 90 | 91 | // Poll internal tasks,s 92 | fn poll(mut self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> Poll { 93 | let req = self.params_as_req(); 94 | if self.last_req.as_ref() != Some(&req) && self.tasks.len() < MAX_TASKS { 95 | self.tasks 96 | .push_back(tokio::spawn(Self::request_image(req.clone()))); 97 | self.last_req = Some(req); 98 | }; 99 | 100 | // While we have tasks, poll them 101 | // If they're ready, return the image 102 | // If they're failed, throw them away 103 | // If they're not ready, reinsert them at the beginning 104 | while let Some(mut task) = self.tasks.pop_front() { 105 | // Pin'n'poll 106 | match Pin::new(&mut task).poll(cx) { 107 | Poll::Ready(Ok(Ok(image))) => { 108 | return Poll::Ready(Some(image)); 109 | } 110 | Poll::Ready(Ok(Err(_))) | Poll::Ready(Err(_)) => { 111 | self.set_warning(&format!("Error fetching image")); 112 | continue; 113 | } 114 | Poll::Pending => { 115 | self.tasks.insert(0, task); 116 | if !self.params.blocking { 117 | return Poll::Ready(None); 118 | } 119 | } 120 | } 121 | } 122 | 123 | Poll::Ready(None) 124 | } 125 | } 126 | 127 | #[derive(Default, Clone, Debug, PartialEq)] 128 | struct ImageReq { 129 | url: String, 130 | seed: u16, 131 | x: u32, 132 | x_range: f32, 133 | y: u32, 134 | y_range: f32, 135 | z: u32, 136 | z_range: f32, 137 | } 138 | 139 | impl StyleganHttpTop { 140 | fn get_image(&mut self) -> Option> { 141 | RUNTIME.block_on(self) 142 | } 143 | 144 | fn params_as_req(&self) -> ImageReq { 145 | ImageReq { 146 | url: format!( 147 | "http://{ip}:{port}", 148 | ip = self.params.ip, 149 | port = self.params.port 150 | ), 151 | seed: self.params.seed, 152 | x: self.params.x_feature, 153 | x_range: self.params.x_range, 154 | y: self.params.y_feature, 155 | y_range: self.params.y_range, 156 | z: self.params.z_feature, 157 | z_range: self.params.z_range, 158 | } 159 | } 160 | 161 | async fn request_image(image_req: ImageReq) -> anyhow::Result> { 162 | let ImageReq { 163 | url, 164 | seed, 165 | x, 166 | x_range, 167 | y, 168 | y_range, 169 | z, 170 | z_range, 171 | } = image_req; 172 | let bytes = reqwest::get(format!( 173 | "{url}?seed={seed}&x={x}&x_range={x_range}&y={y}&y_range={y_range}&z={z}&z_range={z_range}" 174 | )) 175 | .await? 176 | .bytes() 177 | .await? 178 | .to_vec(); 179 | 180 | Ok(bytes) 181 | } 182 | } 183 | 184 | impl TopNew for StyleganHttpTop { 185 | fn new(_info: NodeInfo, context: TopContext) -> Self { 186 | Self { 187 | params: Default::default(), 188 | execute_count: 0, 189 | context, 190 | tasks: VecDeque::with_capacity(MAX_TASKS), 191 | last_req: None, 192 | } 193 | } 194 | } 195 | 196 | impl OpInfo for StyleganHttpTop { 197 | const OPERATOR_LABEL: &'static str = "Stylegan Http"; 198 | const OPERATOR_TYPE: &'static str = "Styleganhttpn"; 199 | const MAX_INPUTS: usize = 0; 200 | const MIN_INPUTS: usize = 0; 201 | } 202 | 203 | impl TopInfo for StyleganHttpTop { 204 | const EXECUTE_MODE: ExecuteMode = ExecuteMode::Cpu; 205 | } 206 | 207 | impl Op for StyleganHttpTop { 208 | fn params_mut(&mut self) -> Option> { 209 | Some(Box::new(&mut self.params)) 210 | } 211 | 212 | fn pulse_pressed(&mut self, name: &str) { 213 | if name == "Reset" {} 214 | } 215 | } 216 | 217 | impl Top for StyleganHttpTop { 218 | fn general_info(&self, _input: &OperatorInputs) -> TopGeneralInfo { 219 | TopGeneralInfo { 220 | cook_every_frame: false, 221 | cook_every_frame_if_asked: true, 222 | input_size_index: 0, 223 | } 224 | } 225 | 226 | fn execute(&mut self, mut output: TopOutput, input: &OperatorInputs) { 227 | self.execute_count += 1; 228 | 229 | if let Some(mut image) = self.get_image() { 230 | if image.len() < WIDTH * HEIGHT * 4 { 231 | self.set_warning(&format!( 232 | "Image size mismatch, expected 1024x1024x4, got {len:?}", 233 | len = image.len() 234 | )); 235 | return; 236 | } 237 | 238 | // kick off another request optimistically 239 | self.get_image(); 240 | let mut buf = self 241 | .context 242 | .create_output_buffer(image.len(), TopBufferFlags::None); 243 | buf.data_mut().copy_from_slice(image.as_slice()); 244 | 245 | let info = UploadInfo { 246 | buffer_offset: 0, 247 | texture_desc: TextureDesc { 248 | tex_dim: TexDim::E2D, 249 | width: WIDTH, 250 | height: HEIGHT, 251 | pixel_format: PixelFormat::BGRA8Fixed, 252 | aspect_x: 0.0, 253 | depth: 1, 254 | aspect_y: 0.0, 255 | }, 256 | first_pixel: FirstPixel::TopLeft, 257 | color_buffer_index: 0, 258 | }; 259 | 260 | output.upload_buffer(&mut buf, &info); 261 | } 262 | } 263 | } 264 | 265 | top_plugin!(StyleganHttpTop); 266 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /td-rs-autocxx-build/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "td-rs-autocxx-build" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | autocxx-build = { git = "https://github.com/tychedelia/autocxx.git", branch = "main" } 8 | miette = { version="5", features = [ "fancy" ] } 9 | -------------------------------------------------------------------------------- /td-rs-autocxx-build/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub fn build(output: &str, include_base: bool) -> miette::Result<()> { 2 | let python_enabled = std::env::var("CARGO_FEATURE_PYTHON").is_ok(); 3 | 4 | let path = std::path::PathBuf::from("src"); 5 | let mut incs = vec![path]; 6 | let mut clang_args = vec![]; 7 | 8 | if include_base { 9 | let base_path = std::path::PathBuf::from("../td-rs-base/src"); 10 | incs.push(base_path); 11 | } 12 | 13 | println!("python_enabled: {}", python_enabled); 14 | 15 | if python_enabled { 16 | if cfg!(windows) { 17 | incs.push(std::path::PathBuf::from( 18 | "C:\\Program Files\\Derivative\\TouchDesigner\\Samples\\CPlusPlus\\3rdParty\\Python\\Include" 19 | )); 20 | incs.push(std::path::PathBuf::from( 21 | "C:\\Program Files\\Derivative\\TouchDesigner\\Samples\\CPlusPlus\\3rdParty\\Python\\Include\\PC" 22 | )); 23 | } else { 24 | incs.push(std::path::PathBuf::from( 25 | "/Applications/TouchDesigner.app/Contents/Frameworks/Python.framework/Headers", 26 | )); 27 | }; 28 | clang_args.push("-DPYTHON_ENABLED"); 29 | } 30 | 31 | if cfg!(windows) { 32 | clang_args.push("-std=c++17"); 33 | clang_args.push("-D_CRT_USE_BUILTIN_OFFSETOF"); 34 | } 35 | 36 | let b = autocxx_build::Builder::new("src/cxx.rs", &incs) 37 | .extra_clang_args(&clang_args) 38 | .auto_allowlist(true); 39 | 40 | let mut b = b.build()?; 41 | if python_enabled { 42 | b.define("PYTHON_ENABLED", None); 43 | } 44 | 45 | b.flag_if_supported("-std=c++17"); 46 | 47 | if !cfg!(windows) { 48 | b.flag("-Wno-unused-parameter") 49 | .flag("-Wno-reorder-ctor") 50 | .flag("-Wno-mismatched-tags") 51 | .flag("-Wno-unused-private-field"); 52 | } 53 | 54 | b.compile(output); 55 | println!("cargo:rerun-if-changed=src/cxx.rs"); 56 | Ok(()) 57 | } 58 | -------------------------------------------------------------------------------- /td-rs-base/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "td-rs-base" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [lib] 7 | name = "td_rs_base" 8 | crate-type = ["lib", "staticlib"] 9 | 10 | [dependencies] 11 | autocxx = { git = "https://github.com/tychedelia/autocxx.git" } 12 | cxx = "1.0.78" 13 | rgb = "0.8.36" 14 | ref-cast = "1.0" 15 | auto_ops = "0.3.0" 16 | derive_more = { version = "1", features = ["full"] } 17 | pyo3 = { git = "https://github.com/tychedelia/pyo3", branch = "td-rs", features = ["abi3-py311"], optional = true } 18 | tracing-base = { package = "tracing", version = "0.1", optional = true} 19 | tracing-subscriber = { version = "0.3", optional = true } 20 | tokio-core = { package = "tokio", version = "1", optional = true } 21 | anyhow = "1.0" 22 | cudarc = { version = "0.16.4", optional = true, features = ["runtime", "nvrtc", "driver", "cuda-12080", "dynamic-linking"], default-features = false } 23 | 24 | [build-dependencies] 25 | td-rs-autocxx-build = { path = "../td-rs-autocxx-build" } 26 | autocxx-build = { git = "https://github.com/tychedelia/autocxx.git" } 27 | miette = { version = "5", features = [ "fancy" ] } 28 | 29 | [features] 30 | default = [] 31 | python = ["pyo3"] 32 | tracing = ["tracing-base", "tracing-subscriber", "tracing-subscriber/env-filter"] 33 | tokio = ["tokio-core", "tokio-core/rt-multi-thread"] 34 | cuda = ["cudarc"] -------------------------------------------------------------------------------- /td-rs-base/build.rs: -------------------------------------------------------------------------------- 1 | fn main() -> miette::Result<()> { 2 | td_rs_autocxx_build::build("td-rs-base", false) 3 | } 4 | -------------------------------------------------------------------------------- /td-rs-base/src/GL_Extensions.h: -------------------------------------------------------------------------------- 1 | // Stub file for simpler CHOP usage than an OpenGLTOP 2 | #include -------------------------------------------------------------------------------- /td-rs-base/src/RustBase.h: -------------------------------------------------------------------------------- 1 | #include "CPlusPlus_Common.h" 2 | #include 3 | #include 4 | #include 5 | 6 | #ifndef TD_RS_RUSTBASE_H 7 | #define TD_RS_RUSTBASE_H 8 | 9 | void setString(TD::OP_String *dest, const char *src) { dest->setString(src); } 10 | 11 | uint64_t getDownloadDataSize(TD::OP_SmartRef &result) { 12 | uint64_t size = result->size; 13 | return size; 14 | } 15 | 16 | void* getDownloadData(TD::OP_SmartRef &result) { 17 | void* data = result->getData(); 18 | return data; 19 | } 20 | 21 | TD::OP_TextureDesc getDownloadTextureDesc(TD::OP_SmartRef &result) { 22 | TD::OP_TextureDesc desc = result->textureDesc; 23 | return desc; 24 | } 25 | 26 | void releaseDownloadResult(TD::OP_SmartRef &result) { 27 | result.release(); 28 | } 29 | 30 | const char* getBuildDynamicMenuInfoNames(TD::OP_BuildDynamicMenuInfo &info) { 31 | return info.name; 32 | } 33 | 34 | // CUDA helper functions for base types 35 | TD::OP_TextureDesc getCUDAArrayInfoTextureDesc(const TD::OP_CUDAArrayInfo &info) { 36 | return info.textureDesc; 37 | } 38 | 39 | void* getCUDAArrayInfoArray(const TD::OP_CUDAArrayInfo &info) { 40 | return info.cudaArray; 41 | } 42 | 43 | void* getCUDAAcquireInfoStream(const TD::OP_CUDAAcquireInfo &info) { 44 | return info.stream; 45 | } 46 | 47 | TD::OP_CUDAAcquireInfo createCUDAAcquireInfo(void* stream) { 48 | TD::OP_CUDAAcquireInfo info; 49 | info.stream = static_cast(stream); 50 | return info; 51 | } 52 | 53 | // Helper function to get texture descriptor from TOP input 54 | TD::OP_TextureDesc getTOPInputTextureDesc(const TD::OP_TOPInput &input) { 55 | return input.textureDesc; 56 | } 57 | 58 | #endif // TD_RS_RUSTBASE_H 59 | -------------------------------------------------------------------------------- /td-rs-base/src/RustPy.h: -------------------------------------------------------------------------------- 1 | #include "CPlusPlus_Common.h" 2 | #include 3 | #ifdef PYTHON_ENABLED 4 | #include 5 | #endif 6 | #include 7 | #include 8 | 9 | #ifndef TD_RS_RUSTPY_H 10 | #define TD_RS_RUSTPY_H 11 | 12 | #ifdef PYTHON_ENABLED 13 | TD::PY_Context* getPyContext(TD::PY_Struct *pyStruct) { 14 | return pyStruct->context; 15 | } 16 | 17 | void setPyInfo(TD::OP_CustomOPInfo &opInfo, void *pymethods, size_t size, void *pygetsets, size_t getsetsize) { 18 | if (size == 0) { 19 | opInfo.pythonMethods = nullptr; 20 | } else { 21 | opInfo.pythonMethods = static_cast(pymethods); 22 | } 23 | 24 | if (getsetsize == 0) { 25 | opInfo.pythonGetSets = nullptr; 26 | } else { 27 | opInfo.pythonGetSets = static_cast(pygetsets); 28 | } 29 | } 30 | #else 31 | 32 | std::unique_ptr getPyContext(TD::PY_Struct *pyStruct) { 33 | return nullptr; 34 | } 35 | 36 | void setPyInfo(TD::OP_CustomOPInfo &opInfo, void *pymethods, size_t size, void *pygetsets, size_t getsetsize) { 37 | std::cout << "Python is not enabled" << std::endl; 38 | } 39 | 40 | #endif 41 | 42 | #endif //TD_RS_RUSTPY_H 43 | -------------------------------------------------------------------------------- /td-rs-base/src/chop.rs: -------------------------------------------------------------------------------- 1 | use crate::cxx::OP_CHOPInput; 2 | use crate::{GetInput, OperatorInputs}; 3 | use ref_cast::RefCast; 4 | use std::ops::Index; 5 | 6 | /// A chop input. 7 | #[repr(transparent)] 8 | #[derive(RefCast)] 9 | pub struct ChopInput { 10 | input: OP_CHOPInput, 11 | } 12 | 13 | impl ChopInput { 14 | /// Get the number of channels in this input. 15 | pub fn num_channels(&self) -> usize { 16 | self.input.numChannels as usize 17 | } 18 | 19 | /// Get the number of samples in this input. 20 | pub fn num_samples(&self) -> usize { 21 | self.input.numSamples as usize 22 | } 23 | 24 | /// Get a channel. 25 | pub fn channel(&self, index: usize) -> &[f32] { 26 | if index >= self.num_channels() { 27 | panic!("index out of bounds"); 28 | } 29 | 30 | unsafe { 31 | std::slice::from_raw_parts( 32 | *self.input.channelData.add(index), 33 | self.input.numSamples as usize, 34 | ) 35 | } 36 | } 37 | } 38 | 39 | impl<'cook> GetInput<'cook, ChopInput> for OperatorInputs<'cook, ChopInput> { 40 | fn num_inputs(&self) -> usize { 41 | self.inputs.getNumInputs() as usize 42 | } 43 | 44 | fn input(&self, index: usize) -> Option<&'cook ChopInput> { 45 | let input = self.inputs.getInputCHOP(index as i32); 46 | if input.is_null() { 47 | None 48 | } else { 49 | Some(ChopInput::ref_cast(unsafe { &*input })) 50 | } 51 | } 52 | } 53 | 54 | impl Index for ChopInput { 55 | type Output = [f32]; 56 | 57 | fn index(&self, index: usize) -> &Self::Output { 58 | self.channel(index) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /td-rs-base/src/cuda.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug)] 2 | pub struct CudaContext { 3 | context: Option>, 4 | } 5 | 6 | impl Default for CudaContext { 7 | fn default() -> Self { 8 | Self { context: None } 9 | } 10 | } 11 | 12 | impl CudaContext { 13 | pub fn new(device_ordinal: usize) -> Result { 14 | let context = cudarc::driver::CudaContext::new(device_ordinal) 15 | .map_err(|e| anyhow::anyhow!("Failed to create CUDA context: {:?}", e))?; 16 | Ok(Self { 17 | context: Some(context), 18 | }) 19 | } 20 | 21 | pub fn cudarc_context(&self) -> Option<&std::sync::Arc> { 22 | self.context.as_ref() 23 | } 24 | 25 | pub fn default_stream(&self) -> Option> { 26 | self.context.as_ref().map(|ctx| ctx.default_stream()) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /td-rs-base/src/cxx.rs: -------------------------------------------------------------------------------- 1 | use autocxx::prelude::*; 2 | 3 | include_cpp! { 4 | #include "CPlusPlus_Common.h" 5 | #include "RustBase.h" 6 | safety!(unsafe) 7 | generate!("TD::OP_ParameterManager") 8 | generate!("TD::OP_String") 9 | generate_pod!("TD::OP_CustomOPInfo") 10 | generate_pod!("TD::OP_CHOPInput") 11 | generate!("TD::OP_SOPInput") 12 | generate_pod!("TD::OP_NumericParameter") 13 | generate_pod!("TD::OP_StringParameter") 14 | generate!("TD::OP_Inputs") 15 | generate_pod!("TD::OP_InfoDATSize") 16 | generate_pod!("TD::OP_InfoCHOPChan") 17 | generate_pod!("TD::Vector") 18 | generate_pod!("TD::Position") 19 | generate_pod!("TD::Color") 20 | generate_pod!("TD::TexCoord") 21 | generate_pod!("TD::BoundingBox") 22 | generate_pod!("TD::SOP_NormalInfo") 23 | generate_pod!("TD::SOP_ColorInfo") 24 | generate_pod!("TD::SOP_TextureInfo") 25 | generate_pod!("TD::SOP_CustomAttribData") 26 | generate_pod!("TD::SOP_PrimitiveInfo") 27 | generate_pod!("TD::OP_DATInput") 28 | generate_pod!("TD::OP_NodeInfo") 29 | generate!("TD::OP_Context") 30 | generate!("TD::OP_TOPInput") 31 | generate_pod!("TD::OP_TOPInputDownloadOptions") 32 | generate_pod!("TD::PY_Struct") 33 | generate_pod!("TD::PY_GetInfo") 34 | generate!("TD::PY_Context") 35 | generate_pod!("TD::OP_TOPInputDownloadOptions") 36 | generate!("TD::OP_SmartRef") 37 | generate_pod!("TD::OP_TextureDesc") 38 | generate_pod!("TD::OP_TexDim") 39 | generate!("TD::OP_BuildDynamicMenuInfo") 40 | // CUDA types (opaque pointers due to non-POD members) 41 | generate!("TD::OP_CUDAArrayInfo") 42 | generate!("TD::OP_CUDAAcquireInfo") 43 | // util fns 44 | generate!("setString") 45 | 46 | generate!("getDownloadDataSize") 47 | generate!("getDownloadData") 48 | generate!("getDownloadTextureDesc") 49 | generate!("releaseDownloadResult") 50 | generate!("getBuildDynamicMenuInfoNames") 51 | 52 | // CUDA helper functions for base types 53 | generate!("getCUDAArrayInfoTextureDesc") 54 | generate!("getCUDAArrayInfoArray") 55 | generate!("getTOPInputTextureDesc") 56 | generate!("getCUDAAcquireInfoStream") 57 | generate!("createCUDAAcquireInfo") 58 | 59 | // Custom ops 60 | generate!("TD::OP_CustomOPInstance") 61 | generate!("TD::CHOP_CPlusPlusBase") 62 | generate!("TD::DAT_CPlusPlusBase") 63 | generate!("TD::SOP_CPlusPlusBase") 64 | generate!("TD::TOP_CPlusPlusBase") 65 | } 66 | 67 | #[cfg(feature = "python")] 68 | mod python { 69 | use autocxx::prelude::*; 70 | include_cpp! { 71 | #include "CPlusPlus_Common.h" 72 | #include "RustPy.h" 73 | name!(python_ffi) 74 | safety!(unsafe) 75 | extern_cpp_type!("TD::OP_CustomOPInfo", crate::cxx::OP_CustomOPInfo) 76 | pod!("TD::OP_CustomOPInfo") 77 | extern_cpp_type!("TD::PY_Context", crate::cxx::PY_Context) 78 | extern_cpp_type!("TD::PY_Struct", crate::cxx::PY_Struct) 79 | generate!("getPyContext") 80 | generate!("setPyInfo") 81 | } 82 | 83 | pub use python_ffi::getPyContext; 84 | pub use python_ffi::setPyInfo; 85 | } 86 | 87 | pub trait AsPlugin { 88 | type Plugin; 89 | fn as_plugin(&self) -> &Self::Plugin; 90 | fn as_plugin_mut(&mut self) -> std::pin::Pin<&mut Self::Plugin>; 91 | } 92 | 93 | pub use ffi::TD::*; 94 | pub use ffi::*; 95 | #[cfg(feature = "python")] 96 | pub use python::*; 97 | -------------------------------------------------------------------------------- /td-rs-base/src/dat.rs: -------------------------------------------------------------------------------- 1 | use crate::cxx::OP_DATInput; 2 | use crate::{GetInput, OperatorInputs}; 3 | use ref_cast::RefCast; 4 | 5 | /// A dat input. 6 | #[repr(transparent)] 7 | #[derive(RefCast)] 8 | pub struct DatInput { 9 | input: OP_DATInput, 10 | } 11 | 12 | pub enum DatType { 13 | Table, 14 | Text, 15 | } 16 | 17 | impl DatInput { 18 | pub fn dat_type(&self) -> DatType { 19 | if self.input.isTable { 20 | DatType::Table 21 | } else { 22 | DatType::Text 23 | } 24 | } 25 | 26 | pub fn num_rows(&self) -> usize { 27 | self.input.numRows as usize 28 | } 29 | 30 | pub fn num_cols(&self) -> usize { 31 | self.input.numCols as usize 32 | } 33 | 34 | pub fn table_size(&self) -> [usize; 2] { 35 | let rows = self.num_rows(); 36 | let cols = self.num_cols(); 37 | [rows, cols] 38 | } 39 | 40 | pub fn cell(&self, row: usize, col: usize) -> Option<&str> { 41 | if row >= self.num_rows() || col >= self.num_cols() { 42 | None 43 | } else { 44 | let cell = self.input.getCell(row as i32, col as i32); 45 | if cell.is_null() { 46 | None 47 | } else { 48 | Some( 49 | unsafe { std::ffi::CStr::from_ptr(cell) } 50 | .to_str() 51 | .expect("invalid utf8"), 52 | ) 53 | } 54 | } 55 | } 56 | 57 | pub fn text(&self) -> &str { 58 | self.cell(0, 0).unwrap() 59 | } 60 | } 61 | 62 | impl<'cook> GetInput<'cook, DatInput> for OperatorInputs<'cook, DatInput> { 63 | fn num_inputs(&self) -> usize { 64 | self.inputs.getNumInputs() as usize 65 | } 66 | 67 | fn input(&self, index: usize) -> Option<&'cook DatInput> { 68 | let input = self.inputs.getInputDAT(index as i32); 69 | if input.is_null() { 70 | None 71 | } else { 72 | Some(DatInput::ref_cast(unsafe { &*input })) 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /td-rs-base/src/gltypes.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright: (c) 2010-2012 Apple Inc. All rights reserved. 3 | */ 4 | #ifndef __gltypes_h_ 5 | #define __gltypes_h_ 6 | 7 | #include 8 | 9 | typedef uint32_t GLbitfield; 10 | typedef uint8_t GLboolean; 11 | typedef int8_t GLbyte; 12 | typedef float GLclampf; 13 | typedef uint32_t GLenum; 14 | typedef float GLfloat; 15 | typedef int32_t GLint; 16 | typedef int16_t GLshort; 17 | typedef int32_t GLsizei; 18 | typedef uint8_t GLubyte; 19 | typedef uint32_t GLuint; 20 | typedef uint16_t GLushort; 21 | typedef void GLvoid; 22 | 23 | #if !defined(GL_VERSION_2_0) 24 | typedef char GLchar; 25 | #endif 26 | #if !defined(GL_ARB_shader_objects) 27 | typedef char GLcharARB; 28 | typedef void *GLhandleARB; 29 | #endif 30 | typedef double GLdouble; 31 | typedef double GLclampd; 32 | #if !defined(ARB_ES2_compatibility) && !defined(GL_VERSION_4_1) 33 | typedef int32_t GLfixed; 34 | #endif 35 | #if !defined(GL_ARB_half_float_vertex) && !defined(GL_VERSION_3_0) 36 | typedef uint16_t GLhalf; 37 | #endif 38 | #if !defined(GL_ARB_half_float_pixel) 39 | typedef uint16_t GLhalfARB; 40 | #endif 41 | #if !defined(GL_ARB_sync) && !defined(GL_VERSION_3_2) 42 | typedef int64_t GLint64; 43 | typedef struct __GLsync *GLsync; 44 | typedef uint64_t GLuint64; 45 | #endif 46 | #if !defined(GL_EXT_timer_query) 47 | typedef int64_t GLint64EXT; 48 | typedef uint64_t GLuint64EXT; 49 | #endif 50 | #if !defined(GL_VERSION_1_5) 51 | typedef intptr_t GLintptr; 52 | typedef intptr_t GLsizeiptr; 53 | #endif 54 | #if !defined(GL_ARB_vertex_buffer_object) 55 | typedef intptr_t GLintptrARB; 56 | typedef intptr_t GLsizeiptrARB; 57 | #endif 58 | 59 | #endif 60 | -------------------------------------------------------------------------------- /td-rs-base/src/py.rs: -------------------------------------------------------------------------------- 1 | use crate::Op; 2 | use pyo3::ffi::PyGetSetDef; 3 | 4 | pub trait PyOp: Op {} 5 | 6 | pub trait PyGetSets { 7 | fn get_get_sets() -> &'static [pyo3::ffi::PyGetSetDef]; 8 | } 9 | 10 | impl PyGetSets for T { 11 | default fn get_get_sets() -> &'static [PyGetSetDef] { 12 | &[] 13 | } 14 | } 15 | 16 | pub trait PyMethods { 17 | fn get_methods() -> &'static [pyo3::ffi::PyMethodDef]; 18 | } 19 | 20 | impl PyMethods for T { 21 | default fn get_methods() -> &'static [pyo3::ffi::PyMethodDef] { 22 | &[] 23 | } 24 | } 25 | 26 | pub(crate) unsafe fn py_op_info( 27 | op_info: std::pin::Pin<&mut crate::cxx::OP_CustomOPInfo>, 28 | ) { 29 | let methods = T::get_methods(); 30 | let m_len = methods.len(); 31 | let m_arr = methods.as_ptr() as *mut autocxx::prelude::c_void; 32 | let get_sets = T::get_get_sets(); 33 | let gs_len = get_sets.len(); 34 | let gs_arr = get_sets.as_ptr() as *mut autocxx::prelude::c_void; 35 | crate::cxx::setPyInfo(op_info, m_arr, m_len, gs_arr, gs_len); 36 | } 37 | -------------------------------------------------------------------------------- /td-rs-chop/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "td-rs-chop" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [lib] 7 | name = "td_rs_chop" 8 | crate-type = ["lib", "staticlib"] 9 | 10 | [dependencies] 11 | autocxx = { git = "https://github.com/tychedelia/autocxx.git" } 12 | cxx = "1.0.78" 13 | td-rs-base = { path = "../td-rs-base" } 14 | tracing-base = { package = "tracing", version = "0.1", optional = true } 15 | tracing-subscriber = { version = "0.2", optional = true } 16 | pyo3 = { git = "https://github.com/tychedelia/pyo3", branch = "td-rs", features = ["abi3-py311"], optional = true } 17 | 18 | [build-dependencies] 19 | td-rs-autocxx-build = { path = "../td-rs-autocxx-build" } 20 | autocxx-build = { git = "https://github.com/tychedelia/autocxx.git" } 21 | miette = { version="5", features = [ "fancy" ] } 22 | 23 | [features] 24 | default = [] 25 | python = ["td-rs-base/python", "dep:pyo3"] 26 | tracing = ["td-rs-base/tracing", "tracing-base", "tracing-subscriber"] 27 | tokio = ["td-rs-base/tokio"] -------------------------------------------------------------------------------- /td-rs-chop/build.rs: -------------------------------------------------------------------------------- 1 | fn main() -> miette::Result<()> { 2 | td_rs_autocxx_build::build("td-rs-chop", true) 3 | } 4 | -------------------------------------------------------------------------------- /td-rs-chop/src/RustChopPlugin.cpp: -------------------------------------------------------------------------------- 1 | #include "RustChopPlugin.h" 2 | #include "CPlusPlus_Common.h" 3 | #include 4 | #ifdef PYTHON_ENABLED 5 | #include 6 | #endif 7 | 8 | extern "C" { 9 | 10 | RustChopPlugin *chop_new(const OP_NodeInfo &info); 11 | void chop_get_plugin_info_impl(OP_CustomOPInfo &opInfo); 12 | 13 | DLLEXPORT 14 | void FillCHOPPluginInfo(CHOP_PluginInfo *info) { 15 | info->apiVersion = CHOPCPlusPlusAPIVersion; 16 | auto opInfo = &info->customOPInfo; 17 | chop_get_plugin_info_impl(*opInfo); 18 | #ifdef PYTHON_ENABLED 19 | opInfo->pythonVersion->setString(PY_VERSION); 20 | #endif 21 | } 22 | 23 | DLLEXPORT 24 | CHOP_CPlusPlusBase *CreateCHOPInstance(const OP_NodeInfo *info) { 25 | return chop_new(*info); 26 | } 27 | 28 | DLLEXPORT 29 | void DestroyCHOPInstance(CHOP_CPlusPlusBase *instance) { 30 | delete (RustChopPlugin *)instance; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /td-rs-chop/src/RustChopPlugin.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include "CHOP_CPlusPlusBase.h" 3 | #include "CPlusPlus_Common.h" 4 | #include 5 | 6 | #ifndef TD_RS_RUSTCHOP_H 7 | #define TD_RS_RUSTCHOP_H 8 | 9 | using namespace TD; 10 | 11 | class ChopPlugin : public CHOP_CPlusPlusBase { 12 | public: 13 | virtual ~ChopPlugin() {}; 14 | 15 | void getGeneralInfo(CHOP_GeneralInfo *info, const OP_Inputs *inputs, void *reserved1) override { 16 | this->getGeneralInfo(*info, *inputs); 17 | } 18 | 19 | virtual void getGeneralInfo(CHOP_GeneralInfo &info, const OP_Inputs &inputs) {} 20 | 21 | bool getOutputInfo(CHOP_OutputInfo *info, const OP_Inputs *inputs, void *reserved1) override { 22 | return this->getOutputInfo(*info, *inputs); 23 | } 24 | 25 | virtual bool getOutputInfo(CHOP_OutputInfo &info, const OP_Inputs &inputs) { 26 | return false; 27 | } 28 | 29 | void getChannelName(int32_t index, OP_String *name, const OP_Inputs *inputs, void *reserved1) override { 30 | this->getChannelName(index, *name, *inputs); 31 | } 32 | 33 | virtual void getChannelName(int32_t index, OP_String &name, const OP_Inputs &inputs) {} 34 | 35 | void execute(CHOP_Output *outputs, const OP_Inputs *inputs, void *reserved1) override { 36 | this->execute(*outputs, *inputs); 37 | }; 38 | 39 | virtual void execute(CHOP_Output &outputs, const OP_Inputs &inputs) {} 40 | 41 | virtual int32_t getNumInfoCHOPChans(void *reserved1) override { 42 | return this->getNumInfoCHOPChans(); 43 | } 44 | 45 | virtual int32_t getNumInfoCHOPChans() { 46 | return 0; 47 | } 48 | 49 | void getInfoCHOPChan(int32_t index, OP_InfoCHOPChan *chan, void *reserved1) override { 50 | OP_String *name = chan->name; 51 | float v = 0.f; 52 | float *value = &v; 53 | this->getInfoCHOPChan(index, *name, *value); 54 | chan->name = name; 55 | chan->value = *value; 56 | } 57 | 58 | virtual void getInfoCHOPChan(int32_t index, OP_String &name, float &value) {} 59 | 60 | bool getInfoDATSize(OP_InfoDATSize *infoSize, void *reserved1) override { 61 | return this->getInfoDATSize(*infoSize); 62 | } 63 | 64 | virtual bool getInfoDATSize(OP_InfoDATSize &infoSize) { 65 | return false; 66 | } 67 | 68 | void getInfoDATEntries(int32_t index, int32_t nEntries, OP_InfoDATEntries *entries, void *reserved1) override { 69 | for (int i = 0; i < nEntries; i++) { 70 | auto entry = entries->values[i]; 71 | this->getInfoDATEntry(index, i, *entry); 72 | } 73 | } 74 | 75 | virtual void getInfoDATEntry(int32_t index, int32_t entryIndex, OP_String &entry) {} 76 | 77 | void getWarningString(OP_String *warning, void *reserved1) override { 78 | this->getWarningString(*warning); 79 | }; 80 | 81 | virtual void getWarningString(OP_String &warning) {} 82 | 83 | void getErrorString(OP_String *error, void *reserved1) override { 84 | this->getErrorString(*error); 85 | }; 86 | 87 | virtual void getErrorString(OP_String &error) {} 88 | 89 | void getInfoPopupString(OP_String *popup, void *reserved1) override { 90 | this->getInfoPopupString(*popup); 91 | }; 92 | 93 | virtual void getInfoPopupString(OP_String &popup) {} 94 | 95 | void setupParameters(OP_ParameterManager *manager, void *reserved1) override { 96 | this->setupParameters(*manager); 97 | }; 98 | 99 | virtual void setupParameters(OP_ParameterManager &manager) {} 100 | 101 | void pulsePressed(const char *name, void *reserved1) override { 102 | this->pulsePressed(name); 103 | }; 104 | 105 | virtual void pulsePressed(const char *name) {} 106 | 107 | virtual void buildDynamicMenu(const OP_Inputs *inputs, 108 | OP_BuildDynamicMenuInfo *info, 109 | void *reserved1) { 110 | this->buildDynamicMenu(*inputs, *info); 111 | } 112 | 113 | virtual void buildDynamicMenu(const OP_Inputs &inputs, 114 | OP_BuildDynamicMenuInfo &info) {} 115 | }; 116 | 117 | class RustChopPlugin : public ChopPlugin { 118 | public: 119 | virtual ~RustChopPlugin() {}; 120 | 121 | virtual void* inner() const = 0; 122 | 123 | virtual void* innerMut() = 0; 124 | 125 | virtual void getGeneralInfo(CHOP_GeneralInfo &info, const OP_Inputs &inputs) = 0; 126 | 127 | virtual bool getOutputInfo(CHOP_OutputInfo &info, const OP_Inputs &inputs) = 0; 128 | 129 | virtual void getChannelName(int32_t index, OP_String &name, const OP_Inputs &inputs) = 0; 130 | 131 | virtual void execute(CHOP_Output &outputs, const OP_Inputs &inputs) = 0; 132 | 133 | virtual int32_t getNumInfoCHOPChans() = 0; 134 | 135 | virtual void getInfoCHOPChan(int32_t index, OP_String &name, float &value) = 0; 136 | 137 | virtual bool getInfoDATSize(OP_InfoDATSize &infoSize) = 0; 138 | 139 | virtual void getInfoDATEntry(int32_t index, int32_t entryIndex, OP_String &entry) = 0; 140 | 141 | virtual void getWarningString(OP_String &warning) = 0; 142 | 143 | virtual void getErrorString(OP_String &error) = 0; 144 | 145 | virtual void getInfoPopupString(OP_String &popup) = 0; 146 | 147 | virtual void setupParameters(OP_ParameterManager &manager) = 0; 148 | 149 | virtual void pulsePressed(const char *name) = 0; 150 | 151 | virtual void buildDynamicMenu(const OP_Inputs &inputs, 152 | OP_BuildDynamicMenuInfo &info) = 0; 153 | }; 154 | 155 | #endif //TD_RS_RUSTCHOP_H 156 | -------------------------------------------------------------------------------- /td-rs-chop/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![feature(min_specialization)] 2 | 3 | use std::fmt::Formatter; 4 | use std::ops::{Index, IndexMut}; 5 | use std::pin::Pin; 6 | 7 | pub use td_rs_base::chop::*; 8 | pub use td_rs_base::*; 9 | 10 | pub mod cxx; 11 | pub mod prelude; 12 | 13 | #[derive(Debug, Default)] 14 | pub struct ChopOutputInfo { 15 | pub num_channels: u32, 16 | pub num_samples: u32, 17 | pub sample_rate: f32, 18 | pub start_index: usize, 19 | } 20 | 21 | #[derive(Debug, Default)] 22 | pub struct ChopGeneralInfo { 23 | pub cook_every_frame: bool, 24 | pub cook_every_frame_if_asked: bool, 25 | pub timeslice: bool, 26 | pub input_match_index: i32, 27 | } 28 | 29 | /// A wrapper around a `ChopOutput` that provides a safe interface to the 30 | /// underlying C++ object and writing to the output buffer. 31 | pub struct ChopOutput<'cook> { 32 | output: Pin<&'cook mut cxx::CHOP_Output>, 33 | } 34 | 35 | impl std::fmt::Debug for ChopOutput<'_> { 36 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 37 | f.debug_struct("ChopOutput") 38 | .field("num_channels", &self.num_channels()) 39 | .field("num_samples", &self.num_samples()) 40 | .field("sample_rate", &self.sample_rate()) 41 | .field("start_index", &self.start_index()) 42 | .finish() 43 | } 44 | } 45 | 46 | impl<'cook> ChopOutput<'cook> { 47 | /// Create a new `ChopOutput` from a pinning reference to a 48 | /// `ChopOutput`. 49 | pub fn new(output: Pin<&'cook mut cxx::CHOP_Output>) -> ChopOutput<'cook> { 50 | Self { output } 51 | } 52 | 53 | /// Get the number of channels in the output buffer. 54 | pub fn num_channels(&self) -> usize { 55 | self.output.numChannels as usize 56 | } 57 | 58 | /// Get the number of samples in the output buffer. 59 | pub fn num_samples(&self) -> usize { 60 | self.output.numSamples as usize 61 | } 62 | 63 | /// Get the sample rate of the output buffer. 64 | pub fn sample_rate(&self) -> u32 { 65 | self.output.sampleRate as u32 66 | } 67 | 68 | /// Get the start index of the output buffer. 69 | pub fn start_index(&self) -> usize { 70 | self.output.startIndex as usize 71 | } 72 | 73 | pub fn channel(&self, index: usize) -> &[f32] { 74 | if index >= self.num_channels() { 75 | panic!("Channel index out of bounds"); 76 | } 77 | 78 | unsafe { 79 | let channel_ptr = *self.output.channels.add(index); 80 | std::slice::from_raw_parts(channel_ptr, self.num_samples()) 81 | } 82 | } 83 | 84 | pub fn channel_mut(&mut self, index: usize) -> &mut [f32] { 85 | if index >= self.num_channels() { 86 | panic!("Channel index out of bounds"); 87 | } 88 | 89 | unsafe { 90 | let channel_ptr = *self.output.channels.add(index); 91 | std::slice::from_raw_parts_mut(channel_ptr, self.num_samples()) 92 | } 93 | } 94 | } 95 | 96 | impl Index for ChopOutput<'_> { 97 | type Output = [f32]; 98 | 99 | fn index(&self, index: usize) -> &Self::Output { 100 | self.channel(index) 101 | } 102 | } 103 | 104 | impl IndexMut for ChopOutput<'_> { 105 | fn index_mut(&mut self, index: usize) -> &mut Self::Output { 106 | self.channel_mut(index) 107 | } 108 | } 109 | 110 | /// Trait for defining a custom operator. 111 | pub trait Chop: Op { 112 | fn channel_name(&self, _index: usize, _input: &OperatorInputs) -> String { 113 | String::from("") 114 | } 115 | 116 | fn execute(&mut self, output: &mut ChopOutput, input: &OperatorInputs); 117 | 118 | fn general_info(&self, input: &OperatorInputs) -> ChopGeneralInfo; 119 | 120 | fn output_info(&self, _input: &OperatorInputs) -> Option { 121 | None 122 | } 123 | 124 | fn build_dynamic_menu( 125 | &self, 126 | inputs: &OperatorInputs, 127 | menu_info: &mut DynamicMenuInfo, 128 | ) { 129 | } 130 | } 131 | 132 | #[macro_export] 133 | macro_rules! chop_plugin { 134 | ($plugin_ty:ty) => { 135 | use td_rs_chop::cxx::c_void; 136 | use td_rs_chop::cxx::OP_CustomOPInfo; 137 | use td_rs_chop::NodeInfo; 138 | 139 | #[no_mangle] 140 | pub extern "C" fn chop_get_plugin_info_impl( 141 | mut op_info: std::pin::Pin<&mut OP_CustomOPInfo>, 142 | ) { 143 | unsafe { 144 | td_rs_chop::op_info::<$plugin_ty>(op_info); 145 | } 146 | } 147 | 148 | #[no_mangle] 149 | pub extern "C" fn chop_new_impl(info: NodeInfo) -> Box { 150 | op_init(); 151 | Box::new(<$plugin_ty>::new(info)) 152 | } 153 | }; 154 | } 155 | -------------------------------------------------------------------------------- /td-rs-chop/src/prelude.rs: -------------------------------------------------------------------------------- 1 | pub use crate::cxx::AsPlugin; 2 | pub use crate::*; 3 | 4 | #[cfg(feature = "python")] 5 | pub use pyo3::impl_::pyclass::PyClassImpl; 6 | #[cfg(feature = "python")] 7 | pub use pyo3::prelude::*; 8 | #[cfg(feature = "python")] 9 | pub use std::pin::Pin; 10 | -------------------------------------------------------------------------------- /td-rs-dat/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "td-rs-dat" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [lib] 7 | name = "td_rs_dat" 8 | crate-type = ["lib", "staticlib"] 9 | 10 | [dependencies] 11 | autocxx = { git = "https://github.com/tychedelia/autocxx.git" } 12 | cxx = "1.0.78" 13 | td-rs-base = { path = "../td-rs-base" } 14 | ref-cast = "1.0" 15 | sparsevec = "0.2.0" 16 | tracing-base = { package = "tracing", version = "0.1", optional = true } 17 | tracing-subscriber = { version = "0.2", optional = true } 18 | pyo3 = { git = "https://github.com/tychedelia/pyo3", branch = "td-rs", features = ["abi3-py311"], optional = true } 19 | 20 | [build-dependencies] 21 | td-rs-autocxx-build = { path = "../td-rs-autocxx-build" } 22 | autocxx-build = { git = "https://github.com/tychedelia/autocxx.git" } 23 | miette = { version="5", features = [ "fancy" ] } 24 | 25 | [features] 26 | default = [] 27 | python = ["td-rs-base/python", "dep:pyo3"] 28 | tracing = ["td-rs-base/tracing", "tracing-base", "tracing-subscriber"] 29 | tokio = ["td-rs-base/tokio"] 30 | -------------------------------------------------------------------------------- /td-rs-dat/build.rs: -------------------------------------------------------------------------------- 1 | fn main() -> miette::Result<()> { 2 | td_rs_autocxx_build::build("td-rs-dat", true) 3 | } 4 | -------------------------------------------------------------------------------- /td-rs-dat/src/RustDatPlugin.cpp: -------------------------------------------------------------------------------- 1 | #include "CPlusPlus_Common.h" 2 | #include "RustDatPlugin.h" 3 | #include 4 | #ifdef PYTHON_ENABLED 5 | #include 6 | #endif 7 | 8 | 9 | extern "C" { 10 | 11 | RustDatPlugin *dat_new(const OP_NodeInfo &info); 12 | void dat_get_plugin_info_impl(OP_CustomOPInfo &opInfo); 13 | 14 | DLLEXPORT 15 | void FillDATPluginInfo(DAT_PluginInfo *info) { 16 | info->apiVersion = DATCPlusPlusAPIVersion; 17 | auto opInfo = &info->customOPInfo; 18 | dat_get_plugin_info_impl(*opInfo); 19 | #ifdef PYTHON_ENABLED 20 | opInfo->pythonVersion->setString(PY_VERSION); 21 | #endif 22 | } 23 | 24 | DLLEXPORT 25 | DAT_CPlusPlusBase *CreateDATInstance(const OP_NodeInfo *info) { 26 | return dat_new(*info); 27 | } 28 | 29 | DLLEXPORT 30 | void DestroyDATInstance(DAT_CPlusPlusBase *instance) { 31 | delete (RustDatPlugin *) instance; 32 | } 33 | 34 | } -------------------------------------------------------------------------------- /td-rs-dat/src/RustDatPlugin.h: -------------------------------------------------------------------------------- 1 | #include "CPlusPlus_Common.h" 2 | #include "DAT_CPlusPlusBase.h" 3 | #include 4 | #include 5 | 6 | #ifndef TD_RS_RUSTDAT_H 7 | #define TD_RS_RUSTDAT_H 8 | 9 | using namespace TD; 10 | 11 | class DatPlugin : public DAT_CPlusPlusBase { 12 | public: 13 | virtual ~DatPlugin(){}; 14 | 15 | void getGeneralInfo(DAT_GeneralInfo *info, const OP_Inputs *inputs, 16 | void *reserved1) { 17 | this->getGeneralInfo(*info, *inputs); 18 | } 19 | 20 | virtual void getGeneralInfo(DAT_GeneralInfo &info, const OP_Inputs &inputs) {} 21 | 22 | void execute(DAT_Output *outputs, const OP_Inputs *inputs, void *reserved1) { 23 | this->execute(*outputs, *inputs); 24 | } 25 | 26 | virtual void execute(DAT_Output &outputs, const OP_Inputs &inputs) {} 27 | 28 | int32_t getNumInfoCHOPChans(void *reserved1) { 29 | return this->getNumInfoCHOPChans(); 30 | } 31 | 32 | virtual int32_t getNumInfoCHOPChans() { return 0; } 33 | 34 | void getInfoCHOPChan(int32_t index, OP_InfoCHOPChan *chan, 35 | void *reserved1) override { 36 | OP_String *name = chan->name; 37 | float v = 0.f; 38 | float *value = &v; 39 | this->getInfoCHOPChan(index, *name, *value); 40 | chan->name = name; 41 | chan->value = *value; 42 | } 43 | 44 | virtual void getInfoCHOPChan(int32_t index, OP_String &name, float &value) {} 45 | 46 | bool getInfoDATSize(OP_InfoDATSize *infoSize, void *reserved1) { 47 | return this->getInfoDATSize(*infoSize); 48 | } 49 | 50 | virtual bool getInfoDATSize(OP_InfoDATSize &infoSize) { return false; } 51 | 52 | void getInfoDATEntries(int32_t index, int32_t nEntries, 53 | OP_InfoDATEntries *entries, void *reserved1) override { 54 | for (int i = 0; i < nEntries; i++) { 55 | auto entry = entries->values[i]; 56 | this->getInfoDATEntry(index, i, *entry); 57 | } 58 | } 59 | 60 | virtual void getInfoDATEntry(int32_t index, int32_t entryIndex, 61 | OP_String &entry) {} 62 | 63 | void getWarningString(OP_String *warning, void *reserved1) override { 64 | this->getWarningString(*warning); 65 | }; 66 | 67 | virtual void getWarningString(OP_String &warning) {} 68 | 69 | void getErrorString(OP_String *error, void *reserved1) override { 70 | this->getErrorString(*error); 71 | }; 72 | 73 | virtual void getErrorString(OP_String &error) {} 74 | 75 | void getInfoPopupString(OP_String *popup, void *reserved1) override { 76 | this->getInfoPopupString(*popup); 77 | }; 78 | 79 | virtual void getInfoPopupString(OP_String &popup) {} 80 | 81 | void setupParameters(OP_ParameterManager *manager, void *reserved1) override { 82 | this->setupParameters(*manager); 83 | }; 84 | 85 | virtual void setupParameters(OP_ParameterManager &manager) {} 86 | 87 | void pulsePressed(const char *name, void *reserved1) override { 88 | this->pulsePressed(name); 89 | }; 90 | 91 | virtual void pulsePressed(const char *name) {} 92 | 93 | virtual void buildDynamicMenu(const OP_Inputs *inputs, 94 | OP_BuildDynamicMenuInfo *info, 95 | void *reserved1) { 96 | this->buildDynamicMenu(*inputs, *info); 97 | } 98 | 99 | virtual void buildDynamicMenu(const OP_Inputs &inputs, 100 | OP_BuildDynamicMenuInfo &info) {} 101 | }; 102 | 103 | class RustDatPlugin : public DatPlugin { 104 | public: 105 | virtual ~RustDatPlugin(){}; 106 | 107 | virtual void *inner() const = 0; 108 | 109 | virtual void *innerMut() = 0; 110 | 111 | virtual void getGeneralInfo(DAT_GeneralInfo &info, 112 | const OP_Inputs &inputs) = 0; 113 | 114 | virtual void execute(DAT_Output &outputs, const OP_Inputs &inputs) = 0; 115 | 116 | virtual int32_t getNumInfoCHOPChans() = 0; 117 | 118 | virtual void getInfoCHOPChan(int32_t index, OP_String &name, 119 | float &value) = 0; 120 | 121 | virtual bool getInfoDATSize(OP_InfoDATSize &infoSize) = 0; 122 | 123 | virtual void getInfoDATEntry(int32_t index, int32_t entryIndex, 124 | OP_String &entry) = 0; 125 | 126 | virtual void getWarningString(OP_String &warning) = 0; 127 | 128 | virtual void getErrorString(OP_String &error) = 0; 129 | 130 | virtual void getInfoPopupString(OP_String &popup) = 0; 131 | 132 | virtual void setupParameters(OP_ParameterManager &manager) = 0; 133 | 134 | virtual void pulsePressed(const char *name) = 0; 135 | 136 | virtual void buildDynamicMenu(const OP_Inputs &inputs, 137 | OP_BuildDynamicMenuInfo &info) = 0; 138 | }; 139 | 140 | #endif // TD_RS_RUSTDAT_H 141 | -------------------------------------------------------------------------------- /td-rs-dat/src/cxx.rs: -------------------------------------------------------------------------------- 1 | #![allow(non_snake_case)] 2 | #![allow(ambiguous_glob_reexports)] 3 | 4 | use crate::{Dat, DatOutput}; 5 | use autocxx::prelude::*; 6 | use autocxx::subclass::*; 7 | 8 | use std::ffi::CString; 9 | 10 | use std::pin::Pin; 11 | use td_rs_base::{param::ParameterManager, DynamicMenuInfo, NodeInfo, OperatorInputs}; 12 | 13 | include_cpp! { 14 | #include "DAT_CPlusPlusBase.h" 15 | #include "RustDatPlugin.h" 16 | safety!(unsafe) 17 | extern_cpp_type!("TD::OP_ParameterManager", td_rs_base::cxx::OP_ParameterManager) 18 | extern_cpp_type!("TD::OP_String", td_rs_base::cxx::OP_String) 19 | extern_cpp_type!("TD::OP_InfoDATSize", td_rs_base::cxx::OP_InfoDATSize) 20 | extern_cpp_type!("TD::OP_InfoCHOPChan", td_rs_base::cxx::OP_InfoCHOPChan) 21 | extern_cpp_type!("TD::OP_Inputs", td_rs_base::cxx::OP_Inputs) 22 | extern_cpp_type!("TD::OP_CustomOPInfo", td_rs_base::cxx::OP_CustomOPInfo) 23 | extern_cpp_type!("TD::OP_BuildDynamicMenuInfo", td_rs_base::cxx::OP_BuildDynamicMenuInfo) 24 | pod!("TD::OP_CustomOPInfo") 25 | generate!("TD::DAT_Output") 26 | generate_pod!("TD::DAT_GeneralInfo") 27 | } 28 | 29 | pub use autocxx::c_void; 30 | pub use ffi::TD::*; 31 | pub use ffi::*; 32 | pub use td_rs_base::cxx::*; 33 | 34 | extern "C" { 35 | // SAFETY: `dat_new_impl` is only ever called from Rust compiled 36 | // at the same time as the plugin, so the types are guaranteed to 37 | // match 38 | #[allow(improper_ctypes)] 39 | fn dat_new_impl(info: NodeInfo) -> Box; 40 | } 41 | 42 | // SAFETY: This can only be used with pointers returned from getNodeInstance() and 43 | // should not be used in plugin code. 44 | pub unsafe fn plugin_cast(plugin: *mut c_void) -> &'static mut RustDatPluginImplCpp { 45 | &mut *(plugin as *mut RustDatPluginImplCpp) 46 | } 47 | 48 | impl AsPlugin for RustDatPluginImplCpp { 49 | type Plugin = RustDatPlugin; 50 | 51 | fn as_plugin(&self) -> &Self::Plugin { 52 | self.As_RustDatPlugin() 53 | } 54 | 55 | fn as_plugin_mut(&mut self) -> Pin<&mut Self::Plugin> { 56 | // Safety: self can't be moved during the lifetime of 'cook. 57 | unsafe { Pin::new_unchecked(self).As_RustDatPlugin_mut() } 58 | } 59 | } 60 | 61 | #[subclass(superclass("RustDatPlugin"))] 62 | pub struct RustDatPluginImpl { 63 | inner: Box, 64 | } 65 | 66 | #[no_mangle] 67 | extern "C" fn dat_new(info: &'static OP_NodeInfo) -> *mut RustDatPluginImplCpp { 68 | unsafe { 69 | let info = NodeInfo::new(info); 70 | RustDatPluginImpl::new_cpp_owned(RustDatPluginImpl { 71 | inner: dat_new_impl(info), 72 | cpp_peer: CppSubclassCppPeerHolder::Empty, 73 | }) 74 | .into_raw() 75 | } 76 | } 77 | 78 | impl RustDatPlugin_methods for RustDatPluginImpl { 79 | fn inner(&self) -> *mut c_void { 80 | self.inner.as_ref() as *const dyn Dat as *mut c_void 81 | } 82 | 83 | fn innerMut(&mut self) -> *mut c_void { 84 | self.inner.as_mut() as *mut dyn Dat as *mut c_void 85 | } 86 | 87 | fn getGeneralInfo(&mut self, mut info: Pin<&mut DAT_GeneralInfo>, inputs: &OP_Inputs) { 88 | #[cfg(feature = "tracing")] 89 | let _span = { tracing_base::trace_span!("getGeneralInfo").entered() }; 90 | let input = OperatorInputs::new(inputs); 91 | if let Some(params) = self.inner.params_mut() { 92 | params.update(&input.params()); 93 | } 94 | let gen_info = self.inner.general_info(&input); 95 | info.cookEveryFrame = gen_info.cook_every_frame; 96 | info.cookEveryFrameIfAsked = gen_info.cook_every_frame_if_asked; 97 | } 98 | 99 | fn execute(&mut self, outputs: Pin<&mut DAT_Output>, inputs: &OP_Inputs) { 100 | #[cfg(feature = "tracing")] 101 | let span = { 102 | tracing_base::trace_span!("execute").entered(); 103 | }; 104 | let input = OperatorInputs::new(inputs); 105 | let output = DatOutput::new(outputs); 106 | if let Some(params) = self.inner.params_mut() { 107 | params.update(&input.params()); 108 | } 109 | self.inner.execute(output, &input); 110 | } 111 | 112 | fn getNumInfoCHOPChans(&mut self) -> i32 { 113 | #[cfg(feature = "tracing")] 114 | let _span = { tracing_base::trace_span!("getNumInfoCHOPChans").entered() }; 115 | if let Some(info_chop) = self.inner.info_chop() { 116 | info_chop.size() as i32 117 | } else { 118 | 0 119 | } 120 | } 121 | 122 | fn getInfoCHOPChan(&mut self, index: i32, name: Pin<&mut OP_String>, mut value: Pin<&mut f32>) { 123 | #[cfg(feature = "tracing")] 124 | let _span = { tracing_base::trace_span!("getInfoCHOPChan").entered() }; 125 | if let Some(info_chop) = self.inner.info_chop() { 126 | let (info_name, info_value) = info_chop.channel(index as usize); 127 | unsafe { 128 | let new_string = CString::new(info_name.as_str()).unwrap(); 129 | let new_string_ptr = new_string.as_ptr(); 130 | name.setString(new_string_ptr); 131 | } 132 | value.set(info_value); 133 | } 134 | } 135 | 136 | fn getInfoDATSize(&mut self, mut info: Pin<&mut OP_InfoDATSize>) -> bool { 137 | #[cfg(feature = "tracing")] 138 | let _span = { tracing_base::trace_span!("getInfoDATSize").entered() }; 139 | if let Some(info_dat) = self.inner.info_dat() { 140 | let (rows, cols) = info_dat.size(); 141 | info.rows = rows as i32; 142 | info.cols = cols as i32; 143 | true 144 | } else { 145 | false 146 | } 147 | } 148 | 149 | fn getInfoDATEntry(&mut self, index: i32, entryIndex: i32, entry: Pin<&mut OP_String>) { 150 | #[cfg(feature = "tracing")] 151 | let _span = { tracing_base::trace_span!("getInfoDATEntry").entered() }; 152 | if let Some(info_dat) = self.inner.info_dat() { 153 | let entry_str = info_dat.entry(index as usize, entryIndex as usize); 154 | if entry_str.is_empty() { 155 | return; 156 | } 157 | unsafe { 158 | let new_string = CString::new(entry_str.as_str()).unwrap(); 159 | let new_string_ptr = new_string.as_ptr(); 160 | entry.setString(new_string_ptr); 161 | } 162 | } 163 | } 164 | 165 | fn getWarningString(&mut self, warning: Pin<&mut OP_String>) { 166 | unsafe { 167 | let new_string = CString::new(self.inner.warning()).unwrap(); 168 | let new_string_ptr = new_string.as_ptr(); 169 | warning.setString(new_string_ptr); 170 | } 171 | } 172 | 173 | fn getErrorString(&mut self, error: Pin<&mut OP_String>) { 174 | unsafe { 175 | let new_string = CString::new(self.inner.error()).unwrap(); 176 | let new_string_ptr = new_string.as_ptr(); 177 | error.setString(new_string_ptr); 178 | } 179 | } 180 | 181 | fn getInfoPopupString(&mut self, info: Pin<&mut OP_String>) { 182 | unsafe { 183 | let new_string = CString::new(self.inner.info()).unwrap(); 184 | let new_string_ptr = new_string.as_ptr(); 185 | info.setString(new_string_ptr); 186 | } 187 | } 188 | 189 | fn setupParameters(&mut self, manager: Pin<&mut OP_ParameterManager>) { 190 | #[cfg(feature = "tracing")] 191 | let _span = { tracing_base::trace_span!("setupParameters").entered() }; 192 | let params = self.inner.params_mut(); 193 | if let Some(params) = params { 194 | let mut manager = ParameterManager::new(manager); 195 | params.register(&mut manager); 196 | } 197 | } 198 | 199 | unsafe fn pulsePressed(&mut self, name: *const std::ffi::c_char) { 200 | #[cfg(feature = "tracing")] 201 | let _span = { tracing_base::trace_span!("pulsePressed").entered() }; 202 | self.inner 203 | .pulse_pressed(std::ffi::CStr::from_ptr(name).to_str().unwrap()); 204 | } 205 | 206 | fn buildDynamicMenu(&mut self, inputs: &OP_Inputs, info: Pin<&mut OP_BuildDynamicMenuInfo>) { 207 | #[cfg(feature = "tracing")] 208 | let _span = { tracing_base::trace_span!("buildDynamicMenu").entered() }; 209 | let input = OperatorInputs::new(inputs); 210 | let mut info = DynamicMenuInfo::new(info); 211 | self.inner.build_dynamic_menu(&input, &mut info); 212 | } 213 | } 214 | -------------------------------------------------------------------------------- /td-rs-dat/src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::ffi::CString; 2 | use std::ops::{Index, IndexMut}; 3 | use std::pin::Pin; 4 | 5 | use td_rs_base::chop::ChopInput; 6 | pub use td_rs_base::dat::*; 7 | pub use td_rs_base::param::OperatorParams; 8 | pub use td_rs_base::*; 9 | 10 | pub mod cxx; 11 | pub mod prelude; 12 | 13 | #[derive(Debug, Default)] 14 | pub struct DatGeneralInfo { 15 | pub cook_every_frame: bool, 16 | pub cook_every_frame_if_asked: bool, 17 | } 18 | 19 | pub struct DatOutput<'cook> { 20 | output: Pin<&'cook mut cxx::DAT_Output>, 21 | } 22 | 23 | impl<'cook> DatOutput<'cook> { 24 | pub fn new(output: Pin<&'cook mut cxx::DAT_Output>) -> Self { 25 | Self { output } 26 | } 27 | 28 | pub fn table + Default>(mut self) -> DatTableOutput<'cook, T> { 29 | self.output 30 | .as_mut() 31 | .setOutputDataType(cxx::DAT_OutDataType::Table); 32 | let table = Vec::new(); 33 | let mut table_out = DatTableOutput { 34 | output: self.output, 35 | table, 36 | }; 37 | table_out 38 | .table 39 | .resize(table_out.table_size().iter().product(), T::default()); 40 | table_out 41 | } 42 | 43 | pub fn text(mut self) -> DatTextOutput<'cook> { 44 | self.output 45 | .as_mut() 46 | .setOutputDataType(cxx::DAT_OutDataType::Text); 47 | DatTextOutput { 48 | output: self.output, 49 | } 50 | } 51 | } 52 | 53 | pub struct DatTableOutput<'cook, T> { 54 | output: Pin<&'cook mut cxx::DAT_Output>, 55 | table: Vec, 56 | } 57 | 58 | impl<'cook, T> DatTableOutput<'cook, T> 59 | where 60 | T: CellType<'cook> + Default, 61 | { 62 | pub fn get(&self, row: usize, col: usize) -> &T { 63 | T::get(self, row, col) 64 | } 65 | 66 | pub fn set(&mut self, row: usize, col: usize, value: T) { 67 | T::set(self, row, col, value) 68 | } 69 | 70 | pub fn table_size(&self) -> [usize; 2] { 71 | let mut rows = 0; 72 | let mut cols = 0; 73 | unsafe { 74 | self.output.as_ref().getTableSize(&mut rows, &mut cols); 75 | } 76 | [rows as usize, cols as usize] 77 | } 78 | 79 | pub fn set_table_size(&mut self, rows: usize, cols: usize) { 80 | self.output.as_mut().setTableSize(rows as i32, cols as i32); 81 | self.table.resize(rows * cols, T::default()); 82 | } 83 | } 84 | 85 | /// A type which can be used as a cell in a DAT table. Should not be implemented manually or used 86 | /// directly. 87 | pub trait CellType<'cook> 88 | where 89 | Self: Clone, 90 | { 91 | /// Get a reference to the value of this cell from the table. 92 | fn get<'a>(table: &'a DatTableOutput<'cook, Self>, row: usize, col: usize) -> &'a Self; 93 | /// Set the value of this cell in the table. 94 | fn set(table: &mut DatTableOutput, row: usize, col: usize, value: Self); 95 | } 96 | 97 | impl<'cook> CellType<'cook> for f64 { 98 | fn get<'a>(table: &'a DatTableOutput<'cook, Self>, row: usize, col: usize) -> &'a Self { 99 | let mut out = f64::default(); 100 | let [rows, _] = table.table_size(); 101 | let offset = row * rows + col; 102 | unsafe { 103 | table 104 | .output 105 | .as_ref() 106 | .getCellDouble(row as i32, col as i32, &mut out); 107 | } 108 | let ptr = table.table.as_ptr(); 109 | 110 | unsafe { 111 | let y = ptr.add(offset) as *mut f64; 112 | *y = out; 113 | } 114 | 115 | &table.table[offset] 116 | } 117 | 118 | fn set(table: &mut DatTableOutput, row: usize, col: usize, value: Self) { 119 | let [rows, _] = table.table_size(); 120 | let offset = row * rows + col; 121 | table.table[offset] = value; 122 | table 123 | .output 124 | .as_mut() 125 | .setCellDouble(row as i32, col as i32, value); 126 | } 127 | } 128 | 129 | impl<'cook> CellType<'cook> for i32 { 130 | fn get<'a>(table: &'a DatTableOutput<'cook, Self>, row: usize, col: usize) -> &'a Self { 131 | let mut out = i32::default(); 132 | let [rows, _] = table.table_size(); 133 | let offset = row * rows + col; 134 | unsafe { 135 | table 136 | .output 137 | .as_ref() 138 | .getCellInt(row as i32, col as i32, &mut out); 139 | } 140 | let ptr = table.table.as_ptr(); 141 | 142 | unsafe { 143 | let y = ptr.add(offset) as *mut i32; 144 | *y = out; 145 | } 146 | 147 | &table.table[offset] 148 | } 149 | 150 | fn set(table: &mut DatTableOutput, row: usize, col: usize, value: Self) { 151 | let rows = table.table_size()[0]; 152 | let offset = row * rows + col; 153 | table.table[offset] = value; 154 | table 155 | .output 156 | .as_mut() 157 | .setCellInt(row as i32, col as i32, value); 158 | } 159 | } 160 | 161 | impl<'cook> CellType<'cook> for String { 162 | fn get<'a>(table: &'a DatTableOutput<'cook, Self>, row: usize, col: usize) -> &'a Self { 163 | let rows = table.table_size()[0]; 164 | let offset = row * rows + col; 165 | let out = unsafe { 166 | let out = table.output.as_ref().getCellString(row as i32, col as i32); 167 | std::ffi::CStr::from_ptr(out).to_str().unwrap() 168 | }; 169 | 170 | let ptr = table.table.as_ptr(); 171 | 172 | unsafe { 173 | let y = ptr.add(offset) as *mut &str; 174 | *y = out; 175 | } 176 | 177 | &table.table[offset] 178 | } 179 | 180 | fn set(table: &mut DatTableOutput, row: usize, col: usize, value: Self) { 181 | let rows = table.table_size()[0]; 182 | let offset = row * rows + col; 183 | table.table[offset] = value.clone(); 184 | let cstr = std::ffi::CString::new(value).unwrap(); 185 | unsafe { 186 | table 187 | .output 188 | .as_mut() 189 | .setCellString(row as i32, col as i32, cstr.as_ptr()); 190 | } 191 | } 192 | } 193 | 194 | impl<'cook, T> Index<[usize; 2]> for DatTableOutput<'cook, T> 195 | where 196 | T: CellType<'cook> + Default, 197 | { 198 | type Output = T; 199 | 200 | fn index(&self, index: [usize; 2]) -> &Self::Output { 201 | let [row, col] = index; 202 | self.get(row, col) 203 | } 204 | } 205 | 206 | impl<'cook, T> IndexMut<[usize; 2]> for DatTableOutput<'cook, T> 207 | where 208 | T: CellType<'cook> + Default, 209 | { 210 | fn index_mut(&mut self, index: [usize; 2]) -> &mut Self::Output { 211 | let [row, col] = index; 212 | let [rows, _] = self.table_size(); 213 | let out = T::default(); 214 | self.table[row * rows + col] = out; 215 | //self.set(row, col, out); 216 | &mut self.table[row * rows + col] 217 | } 218 | } 219 | 220 | pub struct DatTextOutput<'cook> { 221 | output: Pin<&'cook mut cxx::DAT_Output>, 222 | } 223 | 224 | impl<'cook> DatTextOutput<'cook> { 225 | pub fn set_text(&mut self, text: &str) { 226 | unsafe { 227 | let c_str = CString::new(text).unwrap(); 228 | self.output.as_mut().setText(c_str.as_ptr()); 229 | } 230 | } 231 | } 232 | 233 | pub trait Dat: Op { 234 | fn general_info(&self, _input: &OperatorInputs) -> DatGeneralInfo { 235 | DatGeneralInfo::default() 236 | } 237 | 238 | fn execute(&mut self, _output: DatOutput, _input: &OperatorInputs) { 239 | // Do nothing by default. 240 | } 241 | 242 | fn build_dynamic_menu( 243 | &mut self, 244 | inputs: &OperatorInputs, 245 | menu_info: &mut DynamicMenuInfo, 246 | ) { 247 | } 248 | } 249 | 250 | #[macro_export] 251 | macro_rules! dat_plugin { 252 | ($plugin_ty:ty) => { 253 | use td_rs_dat::cxx::c_void; 254 | use td_rs_dat::cxx::OP_CustomOPInfo; 255 | use td_rs_dat::NodeInfo; 256 | 257 | #[no_mangle] 258 | pub extern "C" fn dat_get_plugin_info_impl( 259 | mut op_info: std::pin::Pin<&mut OP_CustomOPInfo>, 260 | ) { 261 | unsafe { 262 | td_rs_dat::op_info::<$plugin_ty>(op_info); 263 | } 264 | } 265 | 266 | #[no_mangle] 267 | pub extern "C" fn dat_new_impl(info: NodeInfo) -> Box { 268 | op_init(); 269 | Box::new(<$plugin_ty>::new(info)) 270 | } 271 | }; 272 | } 273 | -------------------------------------------------------------------------------- /td-rs-dat/src/prelude.rs: -------------------------------------------------------------------------------- 1 | pub use crate::cxx::AsPlugin; 2 | pub use crate::*; 3 | 4 | #[cfg(feature = "python")] 5 | pub use pyo3::impl_::pyclass::PyClassImpl; 6 | #[cfg(feature = "python")] 7 | pub use pyo3::prelude::*; 8 | #[cfg(feature = "python")] 9 | pub use std::pin::Pin; 10 | -------------------------------------------------------------------------------- /td-rs-dat/src/table.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::ops::{Index, IndexMut}; 3 | 4 | pub struct Table { 5 | size: [usize; 2], 6 | r: HashMap<[usize; 2], T>, 7 | w: HashMap<[usize; 2], T>, 8 | } 9 | 10 | impl Table { 11 | pub fn new(size: [usize; 2]) -> Self { 12 | Self { 13 | size, 14 | r: HashMap::new(), 15 | w: HashMap::new(), 16 | } 17 | } 18 | 19 | pub fn size(&self) -> [usize; 2] { 20 | self.size 21 | } 22 | 23 | pub fn contains_key(&self, index: [usize; 2]) -> bool { 24 | self.r.contains_key(&index) || self.w.contains_key(&index) 25 | } 26 | 27 | pub fn for_each(&self, mut f: F) 28 | where 29 | F: FnMut(&[usize; 2], &T), 30 | { 31 | for (k, v) in self.w.iter() { 32 | f(k, v); 33 | } 34 | } 35 | 36 | pub fn resize(&mut self, size: [usize; 2]) { 37 | self.size = size; 38 | } 39 | } 40 | 41 | impl Index<[usize; 2]> for Table { 42 | type Output = T; 43 | 44 | fn index(&self, index: [usize; 2]) -> &Self::Output { 45 | let [row, col] = index; 46 | if row >= self.size[0] || col >= self.size[1] { 47 | panic!("Index out of bounds: {:?}", index); 48 | } 49 | 50 | if self.w.contains_key(&index) { 51 | return &self.w[&index]; 52 | } 53 | 54 | &self.r[&index] 55 | } 56 | } 57 | 58 | impl IndexMut<[usize; 2]> for Table 59 | where 60 | T: Default, 61 | { 62 | fn index_mut(&mut self, index: [usize; 2]) -> &mut Self::Output { 63 | let [row, col] = index; 64 | if row >= self.size[0] || col >= self.size[1] { 65 | panic!("Index out of bounds: {:?}", index); 66 | } 67 | 68 | self.w.entry(index).or_insert_with(|| T::default()) 69 | } 70 | } 71 | 72 | #[cfg(test)] 73 | mod test { 74 | use std::sync::Arc; 75 | 76 | #[test] 77 | fn test_table() { 78 | let mut table = super::Table::new([2, 2]); 79 | table[[0, 0]] = 1; 80 | assert_eq!(table[[0, 0]], 1); 81 | } 82 | 83 | #[test] 84 | fn test_table_default() { 85 | let table = super::Table::::new([2, 2]); 86 | assert_eq!(table[[0, 0]], 0); 87 | } 88 | 89 | #[test] 90 | fn test_table_default_2() { 91 | let mut table = super::Table::::new([2, 2]); 92 | table[[0, 0]] = 1; 93 | assert_eq!(table[[0, 0]], 1); 94 | assert_eq!(table[[0, 1]], 0); 95 | } 96 | 97 | #[test] 98 | fn test_table_for_each() { 99 | let mut table = super::Table::new([2, 2]); 100 | table[[0, 0]] = 1; 101 | table[[0, 1]] = 2; 102 | table[[1, 0]] = 3; 103 | table[[1, 1]] = 4; 104 | let mut sum = 0; 105 | table.for_each(|_, v| sum += v); 106 | assert_eq!(sum, 10); 107 | } 108 | 109 | #[test] 110 | fn test_table_resize() { 111 | let mut table = super::Table::new([2, 2]); 112 | table[[0, 0]] = 1; 113 | table[[0, 1]] = 2; 114 | table[[1, 0]] = 3; 115 | table[[1, 1]] = 4; 116 | table.resize([3, 3]); 117 | assert_eq!(table[[0, 0]], 1); 118 | assert_eq!(table[[0, 1]], 2); 119 | assert_eq!(table[[1, 0]], 3); 120 | assert_eq!(table[[1, 1]], 4); 121 | assert_eq!(table[[2, 0]], 0); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /td-rs-derive-py/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "td-rs-derive-py" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [lib] 7 | proc-macro = true 8 | 9 | [dependencies] 10 | td-rs-base = { path = "../td-rs-base", features = ["python"] } 11 | syn = { version = "1.0", features = ["full"] } 12 | quote = "1.0" 13 | proc-macro2 = "1.0" 14 | 15 | [dev-dependencies] 16 | trybuild = "1.0" 17 | rgb = "0.8.36" 18 | -------------------------------------------------------------------------------- /td-rs-derive-py/tests/parameter_macro/pass.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused)] 2 | 3 | use td_rs_base::*; 4 | use td_rs_derive_py::*; 5 | 6 | enum TestEnum { 7 | Hi, 8 | Hello, 9 | Goodbye, 10 | } 11 | 12 | #[py_op] 13 | struct TestParameter { 14 | float2: f32, 15 | float3: f64, 16 | int: i16, 17 | int2: u32, 18 | int3: i64, 19 | hi: String, 20 | menu: TestEnum, 21 | // rgb: rgb::RGB, 22 | } 23 | 24 | fn main() { 25 | let mut param = TestParameter { 26 | // Initialize fields 27 | float2: 0.0, 28 | float3: 0.0, 29 | int: 0, 30 | int2: 0, 31 | int3: 0, 32 | hi: "".to_string(), 33 | menu: TestEnum::Hi, 34 | }; 35 | } 36 | -------------------------------------------------------------------------------- /td-rs-derive-py/tests/parameter_macro_test.rs: -------------------------------------------------------------------------------- 1 | use trybuild::TestCases; 2 | 3 | #[test] 4 | fn parameter_macro_tests() { 5 | let t = TestCases::new(); 6 | 7 | // Test case for successful expansion 8 | t.pass("tests/parameter_macro/pass.rs"); 9 | 10 | // Test case for expected error 11 | // t.compile_fail("tests/parameter_macro/fail.rs"); 12 | } 13 | -------------------------------------------------------------------------------- /td-rs-derive/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "td-rs-derive" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [lib] 7 | proc-macro = true 8 | 9 | [dependencies] 10 | td-rs-base = { path = "../td-rs-base" } 11 | syn = { version = "1.0", features = ["full"] } 12 | quote = "1.0" 13 | proc-macro2 = "1.0" 14 | 15 | [dev-dependencies] 16 | trybuild = "1.0" 17 | rgb = "0.8.36" 18 | -------------------------------------------------------------------------------- /td-rs-derive/tests/parameter_macro/pass.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused)] 2 | 3 | use td_rs_base::*; 4 | use td_rs_derive::*; 5 | 6 | #[derive(Param)] 7 | enum TestEnum { 8 | Hi, 9 | Hello, 10 | Goodbye, 11 | } 12 | 13 | #[derive(Params)] 14 | struct TestParameter { 15 | #[param(label = "Hi")] 16 | float2: f32, 17 | float3: f64, 18 | int: i16, 19 | int2: u32, 20 | int3: i64, 21 | hi: String, 22 | menu: TestEnum, 23 | // rgb: rgb::RGB, 24 | } 25 | 26 | fn main() { 27 | let mut param = TestParameter { 28 | // Initialize fields 29 | float2: 0.0, 30 | float3: 0.0, 31 | int: 0, 32 | int2: 0, 33 | int3: 0, 34 | hi: "".to_string(), 35 | menu: TestEnum::Hi, 36 | }; 37 | 38 | assert_eq!( 39 | TestEnum::names(), 40 | [ 41 | String::from("Hi"), 42 | String::from("Hello"), 43 | String::from("Goodbye") 44 | ] 45 | ); 46 | } 47 | -------------------------------------------------------------------------------- /td-rs-derive/tests/parameter_macro_test.rs: -------------------------------------------------------------------------------- 1 | use trybuild::TestCases; 2 | 3 | #[test] 4 | fn parameter_macro_tests() { 5 | let t = TestCases::new(); 6 | 7 | // Test case for successful expansion 8 | t.pass("tests/parameter_macro/pass.rs"); 9 | 10 | // Test case for expected error 11 | // t.compile_fail("tests/parameter_macro/fail.rs"); 12 | } 13 | -------------------------------------------------------------------------------- /td-rs-sop/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "td-rs-sop" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [lib] 7 | name = "td_rs_sop" 8 | crate-type = ["lib", "staticlib"] 9 | 10 | [dependencies] 11 | autocxx = { git = "https://github.com/tychedelia/autocxx.git" } 12 | cxx = "1.0.78" 13 | td-rs-base = { path = "../td-rs-base" } 14 | ref-cast = "1.0" 15 | tracing-base = { package = "tracing", version = "0.1", optional = true } 16 | tracing-subscriber = { version = "0.2", optional = true } 17 | pyo3 = { git = "https://github.com/tychedelia/pyo3", branch = "td-rs", features = ["abi3-py311"], optional = true } 18 | 19 | [build-dependencies] 20 | td-rs-autocxx-build = { path = "../td-rs-autocxx-build" } 21 | autocxx-build = { git = "https://github.com/tychedelia/autocxx.git" } 22 | miette = { version="5", features = [ "fancy" ] } 23 | 24 | [features] 25 | default = [] 26 | python = ["td-rs-base/python", "dep:pyo3"] 27 | tracing = ["td-rs-base/tracing", "tracing-base", "tracing-subscriber"] 28 | tokio = ["td-rs-base/tokio"] 29 | -------------------------------------------------------------------------------- /td-rs-sop/build.rs: -------------------------------------------------------------------------------- 1 | fn main() -> miette::Result<()> { 2 | td_rs_autocxx_build::build("td-rs-sop", true) 3 | } 4 | -------------------------------------------------------------------------------- /td-rs-sop/src/RustSopPlugin.cpp: -------------------------------------------------------------------------------- 1 | #include "CPlusPlus_Common.h" 2 | #include "RustSopPlugin.h" 3 | #include 4 | #ifdef PYTHON_ENABLED 5 | #include 6 | #endif 7 | 8 | extern "C" { 9 | 10 | RustSopPlugin *sop_new(const OP_NodeInfo &info); 11 | void sop_get_plugin_info_impl(OP_CustomOPInfo &opInfo); 12 | 13 | DLLEXPORT 14 | void FillSOPPluginInfo(SOP_PluginInfo *info) { 15 | info->apiVersion = SOPCPlusPlusAPIVersion; 16 | auto opInfo = &info->customOPInfo; 17 | sop_get_plugin_info_impl(*opInfo); 18 | #ifdef PYTHON_ENABLED 19 | opInfo->pythonVersion->setString(PY_VERSION); 20 | #endif 21 | } 22 | 23 | DLLEXPORT 24 | SOP_CPlusPlusBase *CreateSOPInstance(const OP_NodeInfo *info) { 25 | return sop_new(*info); 26 | } 27 | 28 | DLLEXPORT 29 | void DestroySOPInstance(SOP_CPlusPlusBase *instance) { 30 | delete (RustSopPlugin *) instance; 31 | } 32 | 33 | } -------------------------------------------------------------------------------- /td-rs-sop/src/RustSopPlugin.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include "SOP_CPlusPlusBase.h" 3 | #include "CPlusPlus_Common.h" 4 | #include 5 | 6 | #ifndef TD_RS_RUSTSOP_H 7 | #define TD_RS_RUSTSOP_H 8 | 9 | using namespace TD; 10 | 11 | class SopPlugin : public SOP_CPlusPlusBase { 12 | public: 13 | virtual ~SopPlugin() {}; 14 | 15 | void getGeneralInfo(SOP_GeneralInfo *info, const OP_Inputs *inputs, void *reserved1) override { 16 | this->getGeneralInfo(*info, *inputs); 17 | } 18 | 19 | virtual void getGeneralInfo(SOP_GeneralInfo &info, const OP_Inputs &inputs) {} 20 | 21 | 22 | void execute(SOP_Output *outputs, const OP_Inputs *inputs, void *reserved1) override { 23 | this->execute(*outputs, *inputs); 24 | } 25 | 26 | virtual void execute(SOP_Output &outputs, const OP_Inputs &inputs) {} 27 | 28 | void executeVBO(SOP_VBOOutput *output, const OP_Inputs *inputs, void *reserved1) override { 29 | if (output == nullptr) { 30 | return; 31 | } 32 | this->executeVBO(*output, *inputs); 33 | }; 34 | 35 | virtual void executeVBO(SOP_VBOOutput &output, const OP_Inputs &inputs) {} 36 | 37 | 38 | int32_t getNumInfoCHOPChans(void *reserved1) override { 39 | return this->getNumInfoCHOPChans(); 40 | } 41 | 42 | virtual int32_t getNumInfoCHOPChans() { 43 | return 0; 44 | } 45 | 46 | void getInfoCHOPChan(int32_t index, OP_InfoCHOPChan *chan, void *reserved1) override { 47 | OP_String *name = chan->name; 48 | float v = 0.f; 49 | float *value = &v; 50 | this->getInfoCHOPChan(index, *name, *value); 51 | chan->name = name; 52 | chan->value = *value; 53 | } 54 | 55 | virtual void getInfoCHOPChan(int32_t index, OP_String &name, float &value) {} 56 | 57 | bool getInfoDATSize(OP_InfoDATSize *infoSize, void *reserved1) override { 58 | return this->getInfoDATSize(*infoSize); 59 | } 60 | 61 | virtual bool getInfoDATSize(OP_InfoDATSize &infoSize) { 62 | return false; 63 | } 64 | 65 | void getInfoDATEntries(int32_t index, int32_t nEntries, OP_InfoDATEntries *entries, void *reserved1) override { 66 | for (int i = 0; i < nEntries; i++) { 67 | auto entry = entries->values[i]; 68 | this->getInfoDATEntry(index, i, *entry); 69 | } 70 | } 71 | 72 | virtual void getInfoDATEntry(int32_t index, int32_t entryIndex, OP_String &entry) {} 73 | 74 | void getWarningString(OP_String *warning, void *reserved1) override { 75 | this->getWarningString(*warning); 76 | }; 77 | 78 | virtual void getWarningString(OP_String &warning) {} 79 | 80 | void getErrorString(OP_String *error, void *reserved1) override { 81 | this->getErrorString(*error); 82 | }; 83 | 84 | virtual void getErrorString(OP_String &error) {} 85 | 86 | void getInfoPopupString(OP_String *popup, void *reserved1) override { 87 | this->getInfoPopupString(*popup); 88 | }; 89 | 90 | virtual void getInfoPopupString(OP_String &popup) {} 91 | 92 | void setupParameters(OP_ParameterManager *manager, void *reserved1) override { 93 | this->setupParameters(*manager); 94 | }; 95 | 96 | virtual void setupParameters(OP_ParameterManager &manager) {} 97 | 98 | void pulsePressed(const char *name, void *reserved1) override { 99 | this->pulsePressed(name); 100 | }; 101 | 102 | virtual void pulsePressed(const char *name) {} 103 | 104 | virtual void buildDynamicMenu(const OP_Inputs *inputs, 105 | OP_BuildDynamicMenuInfo *info, 106 | void *reserved1) { 107 | this->buildDynamicMenu(*inputs, *info); 108 | } 109 | 110 | virtual void buildDynamicMenu(const OP_Inputs &inputs, 111 | OP_BuildDynamicMenuInfo &info) {} 112 | }; 113 | 114 | class RustSopPlugin : public SopPlugin { 115 | public: 116 | virtual ~RustSopPlugin() {}; 117 | 118 | virtual void* inner() const = 0; 119 | 120 | virtual void* innerMut() = 0; 121 | 122 | virtual void getGeneralInfo(SOP_GeneralInfo &info, const OP_Inputs &inputs) = 0; 123 | 124 | virtual void execute(SOP_Output &outputs, const OP_Inputs &inputs) = 0; 125 | 126 | virtual void executeVBO(SOP_VBOOutput &output, const OP_Inputs &inputs) = 0; 127 | 128 | virtual int32_t getNumInfoCHOPChans() = 0; 129 | 130 | virtual void getInfoCHOPChan(int32_t index, OP_String &name, float &value) = 0; 131 | 132 | virtual bool getInfoDATSize(OP_InfoDATSize &infoSize) = 0; 133 | 134 | virtual void getInfoDATEntry(int32_t index, int32_t entryIndex, OP_String &entry) = 0; 135 | 136 | virtual void getWarningString(OP_String &warning) = 0; 137 | 138 | virtual void getErrorString(OP_String &error) = 0; 139 | 140 | virtual void getInfoPopupString(OP_String &popup) = 0; 141 | 142 | virtual void setupParameters(OP_ParameterManager &manager) = 0; 143 | 144 | virtual void pulsePressed(const char *name) = 0; 145 | 146 | virtual void buildDynamicMenu(const OP_Inputs &inputs, 147 | OP_BuildDynamicMenuInfo &info) = 0; 148 | 149 | }; 150 | 151 | #endif //TD_RS_RUSTSOP_H 152 | -------------------------------------------------------------------------------- /td-rs-sop/src/prelude.rs: -------------------------------------------------------------------------------- 1 | pub use crate::cxx::AsPlugin; 2 | pub use crate::*; 3 | 4 | #[cfg(feature = "python")] 5 | pub use pyo3::impl_::pyclass::PyClassImpl; 6 | #[cfg(feature = "python")] 7 | pub use pyo3::prelude::*; 8 | #[cfg(feature = "python")] 9 | pub use std::pin::Pin; 10 | -------------------------------------------------------------------------------- /td-rs-top/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "td-rs-top" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [lib] 7 | name = "td_rs_top" 8 | crate-type = ["lib", "staticlib"] 9 | 10 | [dependencies] 11 | autocxx = { git = "https://github.com/tychedelia/autocxx.git" } 12 | cxx = "1.0.78" 13 | td-rs-base = { path = "../td-rs-base" } 14 | ref-cast = "1.0" 15 | tracing-base = { package = "tracing", version = "0.1", optional = true } 16 | tracing-subscriber = { version = "0.2", optional = true } 17 | pyo3 = { git = "https://github.com/tychedelia/pyo3", branch = "td-rs", features = ["abi3-py311"], optional = true } 18 | cudarc = { version = "0.16.4", optional = true, features = ["runtime", "nvrtc", "driver", "cuda-12080", "dynamic-linking"], default-features = false } 19 | anyhow = "1.0" 20 | 21 | [build-dependencies] 22 | td-rs-autocxx-build = { path = "../td-rs-autocxx-build" } 23 | autocxx-build = { git = "https://github.com/tychedelia/autocxx.git" } 24 | miette = { version="5", features = [ "fancy" ] } 25 | 26 | [features] 27 | default = [] 28 | python = ["td-rs-base/python", "dep:pyo3"] 29 | tracing = ["td-rs-base/tracing", "tracing-base", "tracing-subscriber"] 30 | tokio = ["td-rs-base/tokio"] 31 | cuda = ["cudarc", "td-rs-base/cuda"] -------------------------------------------------------------------------------- /td-rs-top/build.rs: -------------------------------------------------------------------------------- 1 | fn main() -> miette::Result<()> { 2 | td_rs_autocxx_build::build("td-rs-chop", true) 3 | } 4 | -------------------------------------------------------------------------------- /td-rs-top/src/RustTopPlugin.cpp: -------------------------------------------------------------------------------- 1 | #include "RustTopPlugin.h" 2 | #include "CPlusPlus_Common.h" 3 | #include 4 | #ifdef PYTHON_ENABLED 5 | #include 6 | #endif 7 | 8 | 9 | extern "C" { 10 | 11 | RustTopPlugin *top_new(const OP_NodeInfo &info, TOP_Context &context); 12 | TOP_ExecuteMode top_get_plugin_info_impl(OP_CustomOPInfo &opInfo); 13 | 14 | DLLEXPORT 15 | void FillTOPPluginInfo(TOP_PluginInfo *info) { 16 | info->apiVersion = TOPCPlusPlusAPIVersion; 17 | auto opInfo = &info->customOPInfo; 18 | auto mode = top_get_plugin_info_impl(*opInfo); 19 | info->executeMode = mode; 20 | #ifdef PYTHON_ENABLED 21 | opInfo->pythonVersion->setString(PY_VERSION); 22 | #endif 23 | } 24 | 25 | DLLEXPORT 26 | TOP_CPlusPlusBase *CreateTOPInstance(const OP_NodeInfo *info, TOP_Context *context) { 27 | return top_new(*info, *context); 28 | } 29 | 30 | DLLEXPORT 31 | void DestroyTOPInstance(TOP_CPlusPlusBase *instance) { 32 | delete (RustTopPlugin *)instance; 33 | } 34 | } 35 | 36 | void* getBufferData(TD::OP_SmartRef &buffer) { 37 | void* data = buffer->data; 38 | return data; 39 | } 40 | 41 | uint64_t getBufferSize(const TD::OP_SmartRef &buffer) { 42 | uint64_t size = buffer->size; 43 | return size; 44 | } 45 | 46 | TD::TOP_BufferFlags getBufferFlags(const TD::OP_SmartRef &buffer) { 47 | TD::TOP_BufferFlags flags = buffer->flags; 48 | return flags; 49 | } 50 | 51 | void releaseBuffer(TD::OP_SmartRef &buffer) { 52 | // buffer may have been moved, so we need to check if it's valid 53 | if (buffer) { 54 | buffer.release(); 55 | } 56 | } 57 | 58 | // CUDA helper functions (TOP-specific only) 59 | 60 | TD::OP_TextureDesc getCUDAOutputInfoTextureDesc(const TD::TOP_CUDAOutputInfo &info) { 61 | return info.textureDesc; 62 | } 63 | 64 | void* getCUDAOutputInfoStream(const TD::TOP_CUDAOutputInfo &info) { 65 | return info.stream; 66 | } 67 | 68 | uint32_t getCUDAOutputInfoColorBufferIndex(const TD::TOP_CUDAOutputInfo &info) { 69 | return info.colorBufferIndex; 70 | } 71 | 72 | // Helper to create TOP_CUDAOutputInfo from primitive parameters 73 | TD::TOP_CUDAOutputInfo createCUDAOutputInfo(void* stream, 74 | uint32_t width, uint32_t height, uint32_t depth, 75 | int32_t texDim, int32_t pixelFormat, 76 | float aspectX, float aspectY, 77 | uint32_t colorBufferIndex) { 78 | TD::TOP_CUDAOutputInfo info; 79 | info.stream = static_cast(stream); 80 | 81 | info.textureDesc.width = width; 82 | info.textureDesc.height = height; 83 | info.textureDesc.depth = depth; 84 | info.textureDesc.texDim = static_cast(texDim); 85 | info.textureDesc.pixelFormat = static_cast(pixelFormat); 86 | info.textureDesc.aspectX = aspectX; 87 | info.textureDesc.aspectY = aspectY; 88 | 89 | info.colorBufferIndex = colorBufferIndex; 90 | 91 | return info; 92 | } 93 | 94 | 95 | // Helper for CUDA context operations 96 | bool beginCUDAOperations(TD::TOP_Context* context) { 97 | if (!context) return false; 98 | return context->beginCUDAOperations(nullptr); 99 | } 100 | 101 | void endCUDAOperations(TD::TOP_Context* context) { 102 | if (context) { 103 | context->endCUDAOperations(nullptr); 104 | } 105 | } 106 | 107 | -------------------------------------------------------------------------------- /td-rs-top/src/RustTopPlugin.h: -------------------------------------------------------------------------------- 1 | #include "CPlusPlus_Common.h" 2 | #include "TOP_CPlusPlusBase.h" 3 | 4 | #ifndef TD_RS_RUSTTOPPLUGIN_H 5 | #define TD_RS_RUSTTOPPLUGIN_H 6 | 7 | void* getBufferData(TD::OP_SmartRef &buffer); 8 | 9 | uint64_t getBufferSize(const TD::OP_SmartRef &buffer); 10 | 11 | TD::TOP_BufferFlags getBufferFlags(const TD::OP_SmartRef &buffer); 12 | 13 | void releaseBuffer(TD::OP_SmartRef &buffer); 14 | 15 | // CUDA helper functions (TOP-specific) 16 | TD::OP_TextureDesc getCUDAOutputInfoTextureDesc(const TD::TOP_CUDAOutputInfo &info); 17 | void* getCUDAOutputInfoStream(const TD::TOP_CUDAOutputInfo &info); 18 | uint32_t getCUDAOutputInfoColorBufferIndex(const TD::TOP_CUDAOutputInfo &info); 19 | TD::TOP_CUDAOutputInfo createCUDAOutputInfo(void* stream, 20 | uint32_t width, uint32_t height, uint32_t depth, 21 | int32_t texDim, int32_t pixelFormat, 22 | float aspectX, float aspectY, 23 | uint32_t colorBufferIndex); 24 | 25 | 26 | // Helper for CUDA context operations 27 | bool beginCUDAOperations(TD::TOP_Context* context); 28 | void endCUDAOperations(TD::TOP_Context* context); 29 | 30 | using namespace TD; 31 | 32 | class TopPlugin : public TOP_CPlusPlusBase { 33 | public: 34 | virtual ~TopPlugin() {}; 35 | 36 | void getGeneralInfo(TOP_GeneralInfo *info, const OP_Inputs *inputs, 37 | void *reserved1) override { 38 | this->getGeneralInfo(*info, *inputs); 39 | } 40 | 41 | virtual void getGeneralInfo(TOP_GeneralInfo &info, const OP_Inputs &inputs) {} 42 | 43 | virtual void execute(TOP_Output* output, const OP_Inputs* inputs, void* reserved1) override { 44 | this->execute(*output, *inputs); 45 | } 46 | 47 | virtual void execute(TOP_Output &output, const OP_Inputs &inputs) {} 48 | 49 | int32_t getNumInfoCHOPChans(void *reserved1) override { 50 | return this->getNumInfoCHOPChans(); 51 | } 52 | 53 | virtual int32_t getNumInfoCHOPChans() { return 0; } 54 | 55 | void getInfoCHOPChan(int32_t index, OP_InfoCHOPChan *chan, 56 | void *reserved1) override { 57 | OP_String *name = chan->name; 58 | float v = 0.f; 59 | float *value = &v; 60 | this->getInfoCHOPChan(index, *name, *value); 61 | chan->name = name; 62 | chan->value = *value; 63 | } 64 | 65 | virtual void getInfoCHOPChan(int32_t index, OP_String &name, float &value) {} 66 | 67 | bool getInfoDATSize(OP_InfoDATSize *infoSize, void *reserved1) override { 68 | return this->getInfoDATSize(*infoSize); 69 | } 70 | 71 | virtual bool getInfoDATSize(OP_InfoDATSize &infoSize) { return false; } 72 | 73 | void getInfoDATEntries(int32_t index, int32_t nEntries, 74 | OP_InfoDATEntries *entries, void *reserved1) override { 75 | for (int i = 0; i < nEntries; i++) { 76 | auto entry = entries->values[i]; 77 | this->getInfoDATEntry(index, i, *entry); 78 | } 79 | } 80 | 81 | virtual void getInfoDATEntry(int32_t index, int32_t entryIndex, 82 | OP_String &entry) {} 83 | 84 | void getWarningString(OP_String *warning, void *reserved1) override { 85 | this->getWarningString(*warning); 86 | }; 87 | 88 | virtual void getWarningString(OP_String &warning) {} 89 | 90 | void getErrorString(OP_String *error, void *reserved1) override { 91 | this->getErrorString(*error); 92 | }; 93 | 94 | virtual void getErrorString(OP_String &error) {} 95 | 96 | void getInfoPopupString(OP_String *popup, void *reserved1) override { 97 | this->getInfoPopupString(*popup); 98 | }; 99 | 100 | virtual void getInfoPopupString(OP_String &popup) {} 101 | 102 | void setupParameters(OP_ParameterManager *manager, void *reserved1) override { 103 | this->setupParameters(*manager); 104 | }; 105 | 106 | virtual void setupParameters(OP_ParameterManager &manager) {} 107 | 108 | void pulsePressed(const char *name, void *reserved1) override { 109 | this->pulsePressed(name); 110 | }; 111 | 112 | virtual void pulsePressed(const char *name) {} 113 | 114 | virtual void buildDynamicMenu(const OP_Inputs *inputs, 115 | OP_BuildDynamicMenuInfo *info, 116 | void *reserved1) { 117 | this->buildDynamicMenu(*inputs, *info); 118 | } 119 | 120 | virtual void buildDynamicMenu(const OP_Inputs &inputs, 121 | OP_BuildDynamicMenuInfo &info) {} 122 | }; 123 | 124 | class RustTopPlugin : public TopPlugin { 125 | public: 126 | virtual ~RustTopPlugin() {}; 127 | 128 | virtual void* inner() const = 0; 129 | 130 | virtual void* innerMut() = 0; 131 | 132 | virtual void getGeneralInfo(TOP_GeneralInfo &info, 133 | const OP_Inputs &inputs) = 0; 134 | 135 | virtual int32_t getNumInfoCHOPChans() = 0; 136 | 137 | virtual void execute(TOP_Output &output, const OP_Inputs &inputs) = 0; 138 | 139 | virtual void getInfoCHOPChan(int32_t index, OP_String &name, 140 | float &value) = 0; 141 | 142 | virtual bool getInfoDATSize(OP_InfoDATSize &infoSize) = 0; 143 | 144 | virtual void getInfoDATEntry(int32_t index, int32_t entryIndex, 145 | OP_String &entry) = 0; 146 | 147 | virtual void getWarningString(OP_String &warning) = 0; 148 | 149 | virtual void getErrorString(OP_String &error) = 0; 150 | 151 | virtual void getInfoPopupString(OP_String &popup) = 0; 152 | 153 | virtual void setupParameters(OP_ParameterManager &manager) = 0; 154 | 155 | virtual void pulsePressed(const char *name) = 0; 156 | 157 | virtual void buildDynamicMenu(const OP_Inputs &inputs, 158 | OP_BuildDynamicMenuInfo &info) = 0; 159 | }; 160 | 161 | #endif //TD_RS_RUSTTOPPLUGIN_H 162 | -------------------------------------------------------------------------------- /td-rs-top/src/cuda.rs: -------------------------------------------------------------------------------- 1 | use crate::cxx; 2 | #[cfg(feature = "cuda")] 3 | use cudarc::runtime::sys; 4 | use std::sync::Arc; 5 | 6 | // Re-export CudaArrayInfo from td-rs-base 7 | #[cfg(feature = "cuda")] 8 | pub use td_rs_base::top::CudaArrayInfo; 9 | 10 | #[derive(Debug)] 11 | pub struct CudaAcquireInfo { 12 | pub stream: sys::cudaStream_t, 13 | } 14 | 15 | impl Default for CudaAcquireInfo { 16 | fn default() -> Self { 17 | Self { 18 | stream: std::ptr::null_mut(), 19 | } 20 | } 21 | } 22 | 23 | #[derive(Debug)] 24 | pub struct CudaOutputInfo { 25 | pub stream: sys::cudaStream_t, 26 | pub texture_desc: crate::TextureDesc, 27 | pub color_buffer_index: u32, 28 | } 29 | 30 | impl Default for CudaOutputInfo { 31 | fn default() -> Self { 32 | Self { 33 | stream: std::ptr::null_mut(), 34 | texture_desc: crate::TextureDesc::default(), 35 | color_buffer_index: 0, 36 | } 37 | } 38 | } 39 | 40 | #[derive(Debug)] 41 | pub struct CudaSurface { 42 | surface: sys::cudaSurfaceObject_t, 43 | _marker: std::marker::PhantomData<()>, 44 | } 45 | 46 | impl CudaSurface { 47 | pub unsafe fn from_external_array(array: *mut sys::cudaArray) -> Result { 48 | if array.is_null() { 49 | return Err(anyhow::anyhow!("Invalid CUDA array pointer")); 50 | } 51 | 52 | let mut surface = 0; 53 | let desc = sys::cudaResourceDesc { 54 | resType: sys::cudaResourceType::cudaResourceTypeArray, 55 | res: sys::cudaResourceDesc__bindgen_ty_1 { 56 | array: sys::cudaResourceDesc__bindgen_ty_1__bindgen_ty_1 { array }, 57 | }, 58 | }; 59 | 60 | let result = sys::cudaCreateSurfaceObject(&mut surface, &desc); 61 | if result != sys::cudaError::cudaSuccess { 62 | return Err(anyhow::anyhow!( 63 | "CUDA surface creation failed: {:?}", 64 | result 65 | )); 66 | } 67 | 68 | Ok(Self { 69 | surface, 70 | _marker: std::marker::PhantomData, 71 | }) 72 | } 73 | 74 | pub fn handle(&self) -> sys::cudaSurfaceObject_t { 75 | self.surface 76 | } 77 | } 78 | 79 | impl Drop for CudaSurface { 80 | fn drop(&mut self) { 81 | if self.surface != 0 { 82 | unsafe { 83 | sys::cudaDestroySurfaceObject(self.surface); 84 | } 85 | } 86 | } 87 | } 88 | 89 | #[derive(Debug, Default)] 90 | pub struct SurfaceCache { 91 | surfaces: std::collections::HashMap<*mut sys::cudaArray, CudaSurface>, 92 | } 93 | 94 | impl SurfaceCache { 95 | pub fn new() -> Self { 96 | Self { 97 | surfaces: std::collections::HashMap::new(), 98 | } 99 | } 100 | 101 | pub unsafe fn get_or_create( 102 | &mut self, 103 | array: *mut sys::cudaArray, 104 | ) -> Result<&CudaSurface, anyhow::Error> { 105 | if !self.surfaces.contains_key(&array) { 106 | let surface = CudaSurface::from_external_array(array)?; 107 | self.surfaces.insert(array, surface); 108 | } 109 | Ok(self.surfaces.get(&array).unwrap()) 110 | } 111 | 112 | pub fn cleanup_invalid(&mut self, valid_arrays: &[*mut sys::cudaArray]) { 113 | self.surfaces.retain(|&k, _| valid_arrays.contains(&k)); 114 | } 115 | 116 | pub fn clear(&mut self) { 117 | self.surfaces.clear(); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /td-rs-top/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod cxx; 2 | 3 | #[cfg(feature = "cuda")] 4 | pub mod cuda; 5 | 6 | pub use ::cxx::UniquePtr; 7 | 8 | pub use autocxx::prelude::*; 9 | use std::pin::Pin; 10 | use td_rs_base::chop::ChopInput; 11 | pub use td_rs_base::top::*; 12 | pub use td_rs_base::*; 13 | 14 | #[cfg(feature = "cuda")] 15 | pub use cuda::*; 16 | use td_rs_base::cxx::{OP_PixelFormat, OP_TexDim}; 17 | 18 | pub mod prelude; 19 | 20 | pub struct TopOutput<'cook> { 21 | output: Pin<&'cook mut cxx::TOP_Output>, 22 | } 23 | 24 | impl<'cook> TopOutput<'cook> { 25 | pub fn new(output: Pin<&'cook mut cxx::TOP_Output>) -> TopOutput<'cook> { 26 | Self { output } 27 | } 28 | 29 | /// Upload a CPU buffer for CPU execution mode 30 | pub fn upload_buffer(&mut self, buffer: &mut TopBuffer, info: &UploadInfo) { 31 | let info = crate::cxx::TOP_UploadInfo { 32 | bufferOffset: info.buffer_offset as u64, 33 | textureDesc: crate::cxx::OP_TextureDesc { 34 | aspectX: info.texture_desc.aspect_x, 35 | aspectY: info.texture_desc.aspect_y, 36 | depth: info.texture_desc.depth as u32, 37 | height: info.texture_desc.height as u32, 38 | width: info.texture_desc.width as u32, 39 | texDim: OP_TexDim::from(&info.texture_desc.tex_dim), 40 | pixelFormat: (&info.texture_desc.pixel_format).into(), 41 | reserved: Default::default(), 42 | }, 43 | firstPixel: match info.first_pixel { 44 | FirstPixel::BottomLeft => cxx::TOP_FirstPixel::BottomLeft, 45 | FirstPixel::TopLeft => cxx::TOP_FirstPixel::TopLeft, 46 | }, 47 | colorBufferIndex: info.color_buffer_index as u32, 48 | reserved: Default::default(), 49 | }; 50 | 51 | // uploadBuffer takes ownership of the buffer 52 | let buf = std::mem::replace(&mut buffer.buffer, UniquePtr::null()); 53 | unsafe { 54 | self.output 55 | .as_mut() 56 | .uploadBuffer(buf.into_raw(), &info, std::ptr::null_mut()) 57 | }; 58 | } 59 | 60 | /// Create a CUDA array for CUDA execution mode 61 | #[cfg(feature = "cuda")] 62 | pub fn create_cuda_array( 63 | &mut self, 64 | info: &crate::cuda::CudaOutputInfo, 65 | ) -> Result { 66 | use crate::cxx; 67 | 68 | // Use C++ helper to construct TOP_CUDAOutputInfo from primitive parameters 69 | moveit! { let mut cuda_info = unsafe { cxx::createCUDAOutputInfo( 70 | info.stream as *mut cxx::c_void, 71 | info.texture_desc.width as u32, 72 | info.texture_desc.height as u32, 73 | info.texture_desc.depth as u32, 74 | OP_TexDim::from(&info.texture_desc.tex_dim) as i32, 75 | OP_PixelFormat::from(&info.texture_desc.pixel_format) as i32, 76 | info.texture_desc.aspect_x, 77 | info.texture_desc.aspect_y, 78 | info.color_buffer_index 79 | ) } }; 80 | 81 | let array_info = unsafe { 82 | self.output 83 | .as_mut() 84 | .createCUDAArray(cuda_info.as_ref().get_ref(), std::ptr::null_mut()) 85 | }; 86 | 87 | // Convert to our safe wrapper 88 | td_rs_base::top::CudaArrayInfo::new(array_info) 89 | } 90 | } 91 | 92 | pub trait TopNew { 93 | fn new(info: NodeInfo, context: TopContext) -> Self; 94 | } 95 | 96 | pub trait TopInfo { 97 | const EXECUTE_MODE: ExecuteMode; 98 | } 99 | 100 | #[derive(Debug, Default)] 101 | pub struct TopGeneralInfo { 102 | pub cook_every_frame: bool, 103 | pub cook_every_frame_if_asked: bool, 104 | pub input_size_index: i32, 105 | } 106 | 107 | pub enum ExecuteMode { 108 | Cpu, 109 | Cuda, 110 | } 111 | pub struct TopContext { 112 | context: Pin<&'static mut cxx::TOP_Context>, 113 | } 114 | 115 | impl TopContext { 116 | pub fn new(context: Pin<&'static mut cxx::TOP_Context>) -> Self { 117 | Self { context } 118 | } 119 | 120 | pub fn create_output_buffer(&mut self, size: usize, flags: TopBufferFlags) -> TopBuffer { 121 | let flags = match flags { 122 | TopBufferFlags::None => cxx::TOP_BufferFlags::None, 123 | TopBufferFlags::Readable => cxx::TOP_BufferFlags::Readable, 124 | }; 125 | let buf = unsafe { 126 | self.context 127 | .as_mut() 128 | .createOutputBuffer(size as u64, flags, std::ptr::null_mut()) 129 | }; 130 | TopBuffer::new(buf) 131 | } 132 | 133 | /// Begin CUDA operations - makes CUDA array pointers valid 134 | #[cfg(feature = "cuda")] 135 | pub fn begin_cuda_operations(&mut self) -> bool { 136 | unsafe { cxx::beginCUDAOperations(self.context.as_mut().get_unchecked_mut()) } 137 | } 138 | 139 | /// End CUDA operations - invalidates CUDA array pointers 140 | #[cfg(feature = "cuda")] 141 | pub fn end_cuda_operations(&mut self) { 142 | unsafe { cxx::endCUDAOperations(self.context.as_mut().get_unchecked_mut()) } 143 | } 144 | } 145 | 146 | #[derive(Debug, Default, Eq, PartialEq)] 147 | pub enum TopBufferFlags { 148 | #[default] 149 | None, 150 | Readable, 151 | } 152 | 153 | pub struct TopBuffer { 154 | buffer: UniquePtr, 155 | } 156 | 157 | impl TopBuffer { 158 | pub fn new(buffer: UniquePtr) -> Self { 159 | Self { buffer } 160 | } 161 | 162 | pub fn size(&self) -> usize { 163 | crate::cxx::getBufferSize(&self.buffer) as usize 164 | } 165 | 166 | pub fn data_mut(&mut self) -> &mut [T] { 167 | let size = self.size(); 168 | let data = crate::cxx::getBufferData(self.buffer.pin_mut()); 169 | unsafe { std::slice::from_raw_parts_mut(data as *mut T, size) } 170 | } 171 | 172 | pub fn flags(&self) -> TopBufferFlags { 173 | let flags = crate::cxx::getBufferFlags(&self.buffer); 174 | match flags { 175 | cxx::TOP_BufferFlags::None => TopBufferFlags::None, 176 | cxx::TOP_BufferFlags::Readable => TopBufferFlags::Readable, 177 | } 178 | } 179 | } 180 | 181 | impl Drop for TopBuffer { 182 | fn drop(&mut self) { 183 | if self.buffer.is_null() { 184 | return; 185 | } 186 | cxx::releaseBuffer(self.buffer.pin_mut()) 187 | } 188 | } 189 | 190 | #[derive(Debug, Default)] 191 | pub enum FirstPixel { 192 | #[default] 193 | BottomLeft, 194 | TopLeft, 195 | } 196 | 197 | #[derive(Debug, Default)] 198 | pub struct UploadInfo { 199 | pub buffer_offset: usize, 200 | pub texture_desc: TextureDesc, 201 | pub first_pixel: FirstPixel, 202 | pub color_buffer_index: usize, 203 | } 204 | 205 | pub trait Top: Op { 206 | fn general_info(&self, _input: &OperatorInputs) -> TopGeneralInfo { 207 | TopGeneralInfo::default() 208 | } 209 | 210 | fn execute(&mut self, _output: TopOutput, _input: &OperatorInputs) {} 211 | 212 | fn build_dynamic_menu( 213 | &mut self, 214 | inputs: &OperatorInputs, 215 | menu_info: &mut DynamicMenuInfo, 216 | ) { 217 | } 218 | } 219 | 220 | #[macro_export] 221 | macro_rules! top_plugin { 222 | ($plugin_ty:ty) => { 223 | use td_rs_top::cxx::c_void; 224 | use td_rs_top::cxx::OP_CustomOPInfo; 225 | use td_rs_top::NodeInfo; 226 | use td_rs_top::TopContext; 227 | 228 | #[no_mangle] 229 | pub extern "C" fn top_get_plugin_info_impl( 230 | mut op_info: std::pin::Pin<&mut OP_CustomOPInfo>, 231 | ) -> cxx::TOP_ExecuteMode { 232 | unsafe { 233 | td_rs_top::op_info::<$plugin_ty>(op_info); 234 | match <$plugin_ty>::EXECUTE_MODE { 235 | td_rs_top::ExecuteMode::Cuda => cxx::TOP_ExecuteMode::CUDA, 236 | td_rs_top::ExecuteMode::Cpu => cxx::TOP_ExecuteMode::CPUMem, 237 | } 238 | } 239 | } 240 | 241 | #[no_mangle] 242 | pub extern "C" fn top_new_impl(info: NodeInfo, context: TopContext) -> Box { 243 | op_init(); 244 | Box::new(<$plugin_ty>::new(info, context)) 245 | } 246 | }; 247 | } 248 | -------------------------------------------------------------------------------- /td-rs-top/src/prelude.rs: -------------------------------------------------------------------------------- 1 | pub use crate::cxx::AsPlugin; 2 | pub use crate::*; 3 | 4 | #[cfg(feature = "python")] 5 | pub use pyo3::impl_::pyclass::PyClassImpl; 6 | #[cfg(feature = "python")] 7 | pub use pyo3::prelude::*; 8 | #[cfg(feature = "python")] 9 | pub use std::pin::Pin; 10 | -------------------------------------------------------------------------------- /td-rs-xtask/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "td-rs-xtask" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | anyhow = "1" 10 | fs_extra = "1" 11 | serde = { version = "1", features = ["derive"] } 12 | serde_json = "1" 13 | toml = "0.8" 14 | cargo_metadata = "0.15.4" 15 | homedir = "0.2" -------------------------------------------------------------------------------- /td-rs-xtask/msvc/RustOp.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.1.32319.34 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "RustOp", "RustOp.vcxproj", "{3F5BEECD-FA36-459F-91B8-BB481A67EF44}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|x64 = Debug|x64 11 | Release|x64 = Release|x64 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {3F5BEECD-FA36-459F-91B8-BB481A67EF44}.Debug|x64.ActiveCfg = Release|x64 15 | {3F5BEECD-FA36-459F-91B8-BB481A67EF44}.Debug|x64.Build.0 = Release|x64 16 | {3F5BEECD-FA36-459F-91B8-BB481A67EF44}.Release|x64.ActiveCfg = Release|x64 17 | {3F5BEECD-FA36-459F-91B8-BB481A67EF44}.Release|x64.Build.0 = Release|x64 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {24D28F69-BD3C-4E02-8720-07536D0DC619} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /td-rs-xtask/msvc/RustOp.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | x64 7 | 8 | 9 | Release 10 | x64 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | {3F5BEECD-FA36-459F-91B8-BB481A67EF44} 25 | RustOp 26 | Win32Proj 27 | 10.0 28 | RustOp 29 | 30 | 31 | 32 | DynamicLibrary 33 | Unicode 34 | true 35 | v143 36 | 37 | 38 | DynamicLibrary 39 | Unicode 40 | v143 41 | 42 | 43 | 44 | {{ CUDA_IMPORT_PROPS }} 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | <_ProjectFileVersion>10.0.40219.1 55 | $(SolutionDir)$(Configuration)\ 56 | $(Configuration)\ 57 | false 58 | $(SolutionDir)$(Configuration)\ 59 | $(Configuration)\ 60 | false 61 | 62 | 63 | 64 | Disabled 65 | WIN32;_DEBUG;_WINDOWS;_USRDLL;$(PreprocessorDefinitions);%(PreprocessorDefinitions) 66 | EnableFastChecks 67 | MultiThreadedDebugDLL 68 | 69 | 70 | Level3 71 | ProgramDatabase 72 | stdcpp17 73 | false 74 | .\td-rs-base\src;{{ CUDA_INCLUDE_DIRS }};$(AdditionalIncludeDirectories) 75 | 76 | 77 | true 78 | Windows 79 | {{ CUDA_LIB_DIRS }};$(AdditionalLibraryDirectories) 80 | {{ CUDA_LIBS }};synchronization.lib;python311.lib;bcrypt.lib;UserEnv.Lib;Ws2_32.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;msvcrt.lib;ntdll.lib;.\target\x86_64-pc-windows-msvc\release\{{ OP_LIB_NAME }};$(PLUGIN);%(AdditionalDependencies) 81 | 82 | 83 | 84 | 85 | WIN32;NDEBUG;_WINDOWS;_USRDLL;$(PreprocessorDefinitions);%(PreprocessorDefinitions) 86 | MultiThreadedDLL 87 | 88 | 89 | Level3 90 | ProgramDatabase 91 | .\td-rs-base\src;{{ CUDA_INCLUDE_DIRS }};$(AdditionalIncludeDirectories) 92 | 93 | 94 | true 95 | Windows 96 | true 97 | true 98 | {{ CUDA_LIB_DIRS }};$(AdditionalLibraryDirectories) 99 | {{ CUDA_LIBS }};synchronization.lib;python311.lib;bcrypt.lib;UserEnv.Lib;Ws2_32.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;msvcrt.lib;ntdll.lib;opengl32.lib;dwmapi.lib;uxtheme.lib;dinput8.lib;dxguid.lib;setupapi.lib;winmm.lib;imm32.lib;oleacc.lib;version.lib;xinput.lib;hid.lib;d3d12.lib;dxgi.lib;d3dcompiler.lib;pdh.lib;psapi.lib;powrprof.lib;uiautomationcore.lib;windowsapp.lib;propsys.lib;.\target\x86_64-pc-windows-msvc\release\{{ OP_LIB_NAME }};$(PLUGIN);%(AdditionalDependencies) 100 | 101 | 102 | 103 | 104 | {{ CUDA_IMPORT_TARGETS }} 105 | 106 | 107 | -------------------------------------------------------------------------------- /td-rs-xtask/msvc/RustOp.vcxproj.user: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | -------------------------------------------------------------------------------- /td-rs-xtask/src/config.rs: -------------------------------------------------------------------------------- 1 | #[cfg(target_os = "windows")] 2 | #[derive(serde::Deserialize, Debug)] 3 | pub struct WindowsConfig { 4 | pub(crate) plugin_folder: String, 5 | pub(crate) python_include_dir: String, 6 | pub(crate) python_lib_dir: String, 7 | } 8 | 9 | #[cfg(target_os = "macos")] 10 | #[derive(serde::Deserialize, Debug)] 11 | pub struct MacOsConfig { 12 | pub(crate) python_include_dir: String, 13 | pub(crate) plugin_folder: String, 14 | } 15 | 16 | #[derive(serde::Deserialize, Debug)] 17 | pub struct Config { 18 | #[cfg(target_os = "windows")] 19 | pub(crate) windows: WindowsConfig, 20 | #[cfg(target_os = "macos")] 21 | pub(crate) macos: MacOsConfig, 22 | } 23 | 24 | pub(crate) fn read_config() -> Config { 25 | let config_path = std::path::Path::new("td-rs.toml"); 26 | let config_file = std::fs::read_to_string(config_path).expect("Could not read td-rs.toml"); 27 | let config = toml::from_str(&config_file).expect("Could not parse td-rs.toml"); 28 | process_config(config) 29 | } 30 | 31 | fn process_config(mut config: Config) -> Config { 32 | // special handling for $HOME in path 33 | #[cfg(target_os = "windows")] 34 | if config.windows.plugin_folder.contains("$HOME") { 35 | let home_dir = homedir::get_my_home() 36 | .expect("Could not get home directory") 37 | .expect("Could not get home directory"); 38 | let plugin_folder = config.windows.plugin_folder.replace( 39 | "$HOME", 40 | home_dir 41 | .to_str() 42 | .expect("Could not convert home directory to string"), 43 | ); 44 | config.windows.plugin_folder = plugin_folder; 45 | } 46 | #[cfg(target_os = "macos")] 47 | if config.macos.plugin_folder.contains("$HOME") { 48 | let home_dir = homedir::get_my_home() 49 | .expect("Could not get home directory") 50 | .expect("Could not get home directory"); 51 | let plugin_folder = config.macos.plugin_folder.replace( 52 | "$HOME", 53 | home_dir 54 | .to_str() 55 | .expect("Could not convert home directory to string"), 56 | ); 57 | config.macos.plugin_folder = plugin_folder; 58 | } 59 | 60 | config 61 | } 62 | -------------------------------------------------------------------------------- /td-rs-xtask/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod config; 2 | #[cfg(target_os = "macos")] 3 | mod macos; 4 | mod metadata; 5 | mod util; 6 | #[cfg(target_os = "windows")] 7 | mod windows; 8 | 9 | #[cfg(target_os = "macos")] 10 | use crate::macos::{build_plugin, install_plugin}; 11 | #[cfg(target_os = "windows")] 12 | use crate::windows::{build_plugin, install_plugin}; 13 | use anyhow::Context; 14 | use std::env; 15 | 16 | use std::process::Command; 17 | 18 | pub use anyhow::Result; 19 | 20 | const PLUGIN_HOME: &str = "target/plugin"; 21 | 22 | pub fn build(packages: &[&str], args: &[&str]) -> Result<()> { 23 | let package_args = packages.iter().flat_map(|package| ["-p", package]); 24 | let mut cmd = Command::new("cargo") 25 | .arg("build") 26 | .args(package_args) 27 | .args(args) 28 | .spawn() 29 | .with_context(|| format!("Could not call cargo to build {}", packages.join(", ")))?; 30 | let status = cmd.wait()?; 31 | if !status.success() { 32 | anyhow::bail!("Could not build {}", packages.join(", ")); 33 | } else { 34 | Ok(()) 35 | } 36 | } 37 | 38 | pub fn main() -> anyhow::Result<()> { 39 | let cmd = env::args() 40 | .nth(1) 41 | .with_context(|| "must provide command as first argument")?; 42 | 43 | let config = config::read_config(); 44 | 45 | match cmd.as_str() { 46 | "build" | "install" => { 47 | let plugin = env::args() 48 | .nth(2) 49 | .with_context(|| "must provide plugin as second argument")?; 50 | let plugin_type = metadata::plugin_type(&plugin); 51 | 52 | match cmd.as_str() { 53 | "build" => build_plugin(&config, &plugin, plugin_type)?, 54 | "install" => install_plugin(&config, &plugin, plugin_type)?, 55 | _ => {} 56 | } 57 | } 58 | "list-plugins" => { 59 | let plugins = metadata::list_plugins()?; 60 | println!("Available Plugins:"); 61 | for plugin in plugins { 62 | println!(" - {}", plugin); 63 | } 64 | } 65 | _ => { 66 | return Err(anyhow::anyhow!("command must be 'build'")); 67 | } 68 | } 69 | 70 | Ok(()) 71 | } 72 | -------------------------------------------------------------------------------- /td-rs-xtask/src/macos/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::config::Config; 2 | use crate::metadata::PluginType; 3 | use crate::util::ToTitleCase; 4 | use crate::{build, PLUGIN_HOME}; 5 | use anyhow::Context; 6 | use fs_extra::dir::CopyOptions; 7 | use std::path::{Path, PathBuf}; 8 | use std::process::Command; 9 | 10 | pub(crate) fn install_plugin( 11 | config: &Config, 12 | plugin: &str, 13 | _plugin_type: PluginType, 14 | ) -> anyhow::Result<()> { 15 | let plugin = &plugin.replace('-', "_"); 16 | let plugin_target_path = plugin_target_path(plugin).join(format!("{plugin}.plugin")); 17 | let td_plugin_folder = &config.macos.plugin_folder; 18 | std::fs::create_dir_all(&td_plugin_folder).context("Could not create plugin directory")?; 19 | println!( 20 | "Installing plugin {:?} to {}", 21 | plugin_target_path, td_plugin_folder 22 | ); 23 | fs_extra::dir::copy( 24 | &plugin_target_path, 25 | td_plugin_folder, 26 | &CopyOptions::new().overwrite(true), 27 | ) 28 | .context("Could not move plugin to TouchDesigner plugin directory")?; 29 | Ok(()) 30 | } 31 | 32 | pub(crate) fn build_plugin( 33 | config: &Config, 34 | plugin: &str, 35 | plugin_type: PluginType, 36 | ) -> anyhow::Result<()> { 37 | let target = if cfg!(target_arch = "x86_64") { 38 | "x86_64-apple-darwin" 39 | } else { 40 | "aarch64-apple-darwin" 41 | }; 42 | build( 43 | &[plugin, plugin_type.to_plugin_name()], 44 | &["--release", &format!("--target={target}")], 45 | )?; 46 | 47 | let is_python_enabled = crate::metadata::is_python_enabled(plugin, &plugin_type); 48 | let plugin = &plugin.replace('-', "_"); 49 | let path = pbxproj_path(plugin); 50 | 51 | println!("Writing xcode project to {:?}", path); 52 | write_xcodeproj(target, plugin, &plugin_type, &path)?; 53 | println!("Building xcode project"); 54 | build_xcode(config, plugin, is_python_enabled)?; 55 | println!("Moving plugin to {:?}", PLUGIN_HOME); 56 | move_plugin(plugin, &path)?; 57 | Ok(()) 58 | } 59 | 60 | fn move_plugin(plugin: &str, path: &PathBuf) -> anyhow::Result<()> { 61 | fs_extra::dir::remove(path.parent().unwrap()) 62 | .context("Could not remove xcode project directory")?; 63 | let plugin_build_path = format!("build/Release/{plugin}.plugin"); 64 | let plugin_target_path = plugin_target_path(plugin); 65 | std::fs::create_dir_all(&plugin_target_path).context("Could not create plugin directory")?; 66 | fs_extra::dir::remove(plugin_target_path.join(format!("{plugin}.plugin"))) 67 | .context("Could not remove plugin directory")?; 68 | fs_extra::dir::move_dir(plugin_build_path, &plugin_target_path, &CopyOptions::new()) 69 | .context("Could not move plugin to target directory")?; 70 | Ok(()) 71 | } 72 | 73 | fn plugin_target_path(plugin: &str) -> PathBuf { 74 | let plugin_target_path = Path::new(PLUGIN_HOME).join(plugin); 75 | plugin_target_path 76 | } 77 | 78 | fn pbxproj_path(plugin: &str) -> PathBuf { 79 | let mut path = PathBuf::from(format!("{plugin}.xcodeproj")); 80 | path.push("project.pbxproj"); 81 | path 82 | } 83 | 84 | fn build_xcode(config: &Config, plugin: &str, is_python_enabled: bool) -> anyhow::Result<()> { 85 | let mut cmd = Command::new("xcodebuild") 86 | .arg("-project") 87 | .arg(format!("./{plugin}.xcodeproj")) 88 | .arg("clean") 89 | .arg("build") 90 | .arg(format!( 91 | "PYTHON_INCLUDE_DIR={}", 92 | config.macos.python_include_dir 93 | )) 94 | .arg(if is_python_enabled { 95 | "EXTRA_CFLAGS=-DPYTHON_ENABLED" 96 | } else { 97 | "FOO=BAR" 98 | }) 99 | .spawn() 100 | .expect("ls command failed to start"); 101 | if !cmd.wait()?.success() { 102 | anyhow::bail!("Could not build xcode project"); 103 | } 104 | Ok(()) 105 | } 106 | 107 | fn write_xcodeproj( 108 | target: &str, 109 | plugin: &str, 110 | plugin_type: &PluginType, 111 | path: &PathBuf, 112 | ) -> anyhow::Result<()> { 113 | std::fs::create_dir_all(path.parent().unwrap()) 114 | .context("Could not create xcode project directory")?; 115 | let short_title = plugin_type.to_short_name().to_title_case(); 116 | let short_upper = plugin_type.to_short_name().to_uppercase(); 117 | let op_path = plugin_type.to_plugin_name(); 118 | 119 | let project = std::fs::read_to_string("td-rs-xtask/xcode/project.pbxproj") 120 | .expect("Could not read xcode project") 121 | .replace("{{ LIB_NAME }}", &format!("lib{plugin}.a")) 122 | .replace( 123 | "{{ LIB_PATH }}", 124 | &format!("target/{}/release/lib{plugin}.a", target), 125 | ) 126 | .replace("{{ PLUGIN_FILE_NAME }}", &format!("{plugin}.plugin")) 127 | .replace("{{ PLUGIN_NAME }}", &plugin) 128 | .replace("{{ PLUGIN_PRODUCT_NAME }}", &plugin) 129 | .replace( 130 | "{{ TD_OP_H_PATH }}", 131 | &format!("{}/src/{}_CPlusPlusBase.h", op_path, short_upper), 132 | ) 133 | .replace( 134 | "{{ TD_OP_H_NAME }}", 135 | &format!("{}_CPlusPlusBase.h", short_upper), 136 | ) 137 | .replace( 138 | "{{ PLUGIN_CPP_NAME }}", 139 | &format!("Rust{}Plugin.cpp", short_title), 140 | ) 141 | .replace( 142 | "{{ PLUGIN_CPP_PATH }}", 143 | &format!("{}/src/Rust{}Plugin.cpp", op_path, short_title), 144 | ) 145 | .replace( 146 | "{{ PLUGIN_H_NAME }}", 147 | &format!("Rust{}Plugin.h", short_title), 148 | ) 149 | .replace( 150 | "{{ PLUGIN_H_PATH }}", 151 | &format!("{}/src/Rust{}Plugin.h", op_path, short_title), 152 | ) 153 | .replace( 154 | "{{ OP_LIB_NAME }}", 155 | &format!("lib{}.a", op_path.replace("-", "_")), 156 | ) 157 | .replace( 158 | "{{ OP_LIB_PATH }}", 159 | &format!( 160 | "target/{}/release/lib{}.a", 161 | target, 162 | op_path.replace("-", "_") 163 | ), 164 | ); 165 | std::fs::write(path, project)?; 166 | Ok(()) 167 | } 168 | -------------------------------------------------------------------------------- /td-rs-xtask/src/metadata.rs: -------------------------------------------------------------------------------- 1 | use cargo_metadata::{Metadata, MetadataCommand, Package}; 2 | use std::path::Path; 3 | 4 | pub enum PluginType { 5 | Chop, 6 | Sop, 7 | Dat, 8 | Top, 9 | } 10 | 11 | impl PluginType { 12 | pub(crate) fn to_plugin_name(&self) -> &str { 13 | match self { 14 | Self::Chop => "td-rs-chop", 15 | Self::Sop => "td-rs-sop", 16 | Self::Dat => "td-rs-dat", 17 | Self::Top => "td-rs-top", 18 | } 19 | } 20 | 21 | pub(crate) fn to_short_name(&self) -> &str { 22 | match self { 23 | Self::Chop => "chop", 24 | Self::Sop => "sop", 25 | Self::Dat => "dat", 26 | Self::Top => "top", 27 | } 28 | } 29 | } 30 | 31 | pub fn plugin_type(plugin: &str) -> PluginType { 32 | let package_name = plugin; 33 | let metadata = fetch_cargo_metadata(); 34 | let package = metadata 35 | .packages 36 | .into_iter() 37 | .find(|package| package.name == package_name); 38 | 39 | if let Some(package) = package { 40 | let plugin_type = package 41 | .metadata 42 | .get("td-rs") 43 | .expect("Didn't find td-rs metadata in Cargo.toml. Please add [package.metadata.td-rs] to your cargo manifest to specify the type of plugin.") 44 | .get("type") 45 | .expect("Could not find type in td-rs metadata. Please add [package.metadata.td-rs.type] to your cargo manifest to specify the type of plugin.") 46 | .as_str() 47 | .expect("Could not parse type in td-rs metadata. Please add [package.metadata.td-rs.type] as a string to your cargo manifest to specify the type of plugin."); 48 | match plugin_type { 49 | "chop" => PluginType::Chop, 50 | "sop" => PluginType::Sop, 51 | "dat" => PluginType::Dat, 52 | "top" => PluginType::Top, 53 | _ => panic!("Unknown plugin type: {}", plugin_type), 54 | } 55 | } else { 56 | panic!("Package not found: {}", package_name); 57 | } 58 | } 59 | 60 | fn fetch_cargo_metadata() -> Metadata { 61 | MetadataCommand::new() 62 | .exec() 63 | .expect("Failed to fetch cargo metadata") 64 | } 65 | 66 | pub(crate) fn fetch_cargo_workspace_package(package: &str) -> anyhow::Result { 67 | let package_name = package; 68 | let metadata = fetch_cargo_metadata(); 69 | let package = metadata 70 | .packages 71 | .into_iter() 72 | .find(|package| package.name == package_name); 73 | package.ok_or(anyhow::anyhow!("Package not found: {}", package_name)) 74 | } 75 | 76 | #[cfg(not(target_os = "windows"))] 77 | fn adjust_canonicalization>(p: P) -> String { 78 | p.as_ref().display().to_string() 79 | } 80 | 81 | #[cfg(target_os = "windows")] 82 | fn adjust_canonicalization>(p: P) -> String { 83 | const VERBATIM_PREFIX: &str = r#"\\?\"#; 84 | let p = p.as_ref().display().to_string(); 85 | if p.starts_with(VERBATIM_PREFIX) { 86 | p[VERBATIM_PREFIX.len()..].to_string() 87 | } else { 88 | p 89 | } 90 | } 91 | 92 | pub(crate) fn list_plugins() -> anyhow::Result> { 93 | let meta = fetch_cargo_metadata(); 94 | let plugin_dir = adjust_canonicalization( 95 | Path::new("./plugins") 96 | .canonicalize() 97 | .expect("Could not canonicalize plugin dir"), 98 | ); 99 | println!("Plugin dir: {:?}\n", plugin_dir); 100 | let ws_members = meta 101 | .workspace_packages() 102 | .iter() 103 | .filter(|package| package.manifest_path.starts_with(&plugin_dir)) 104 | .map(|package| package.name.clone()) 105 | .collect::>(); 106 | Ok(ws_members) 107 | } 108 | 109 | pub fn is_python_enabled(plugin: &str, plugin_type: &PluginType) -> bool { 110 | let pkg = crate::metadata::fetch_cargo_workspace_package(plugin).unwrap(); 111 | let parent_dep = pkg 112 | .dependencies 113 | .iter() 114 | .find(|dep| dep.name == plugin_type.to_plugin_name()) 115 | .expect("Could not find plugin dependency"); 116 | parent_dep 117 | .features 118 | .iter() 119 | .find(|feature| feature == &"python") 120 | .is_some() 121 | } 122 | 123 | pub fn is_cuda_enabled(plugin: &str, plugin_type: &PluginType) -> bool { 124 | let pkg = crate::metadata::fetch_cargo_workspace_package(plugin).unwrap(); 125 | let parent_dep = pkg 126 | .dependencies 127 | .iter() 128 | .find(|dep| dep.name == plugin_type.to_plugin_name()) 129 | .expect("Could not find plugin dependency"); 130 | parent_dep 131 | .features 132 | .iter() 133 | .find(|feature| feature == &"cuda") 134 | .is_some() 135 | } 136 | -------------------------------------------------------------------------------- /td-rs-xtask/src/util.rs: -------------------------------------------------------------------------------- 1 | pub trait ToTitleCase { 2 | fn to_title_case(&self) -> String; 3 | } 4 | 5 | impl ToTitleCase for str { 6 | fn to_title_case(&self) -> String { 7 | let mut c = self.chars(); 8 | match c.next() { 9 | None => String::new(), 10 | Some(f) => f.to_uppercase().collect::() + c.as_str(), 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /td-rs-xtask/src/windows/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::config::Config; 2 | use crate::metadata::PluginType; 3 | use crate::util::ToTitleCase; 4 | use crate::{build, PLUGIN_HOME}; 5 | use anyhow::Context; 6 | use fs_extra::dir::CopyOptions; 7 | use std::io::Read; 8 | use std::path::{Path, PathBuf}; 9 | use std::process::{Command, Stdio}; 10 | 11 | pub(crate) fn install_plugin( 12 | config: &Config, 13 | plugin: &str, 14 | _plugin_type: PluginType, 15 | ) -> anyhow::Result<()> { 16 | let plugin_target_path = plugin_target_path(plugin); 17 | let td_plugin_folder = &config.windows.plugin_folder; 18 | println!( 19 | "Installing plugin {:?} to {}", 20 | plugin_target_path, td_plugin_folder 21 | ); 22 | fs_extra::copy_items( 23 | &[&plugin_target_path], 24 | td_plugin_folder, 25 | &CopyOptions::new().overwrite(true), 26 | )?; 27 | Ok(()) 28 | } 29 | 30 | pub(crate) fn build_plugin( 31 | config: &Config, 32 | plugin: &str, 33 | plugin_type: PluginType, 34 | ) -> anyhow::Result<()> { 35 | let target = "x86_64-pc-windows-msvc"; 36 | build( 37 | &[plugin, plugin_type.to_plugin_name()], 38 | &["--release", &format!("--target={target}")], 39 | )?; 40 | 41 | let solution_name = "RustOp"; 42 | let files = [ 43 | format!("{solution_name}.sln"), 44 | format!("{solution_name}.vcxproj"), 45 | format!("{solution_name}.vcxproj.user"), 46 | ]; 47 | 48 | println!("Write solution"); 49 | let to_copy: Vec = files 50 | .iter() 51 | .map(|x| format!("./td-rs-xtask/msvc/{x}")) 52 | .collect(); 53 | 54 | println!("Run msbuild"); 55 | fs_extra::copy_items(&to_copy, ".", &CopyOptions::new().overwrite(true))?; 56 | 57 | let short_title = plugin_type.to_short_name().to_title_case(); 58 | let short_upper = plugin_type.to_short_name().to_uppercase(); 59 | let op_path = plugin_type.to_plugin_name(); 60 | let is_cuda_enabled = crate::metadata::is_cuda_enabled(plugin, &plugin_type); 61 | 62 | // Configure CUDA settings 63 | let (cuda_import_props, cuda_import_targets, cuda_include_dirs, cuda_lib_dirs, cuda_libs) = 64 | if is_cuda_enabled { 65 | ( 66 | "", 67 | "", 68 | "$(CudaToolkitIncludeDir)", 69 | "C:\\Program Files\\NVIDIA GPU Computing Toolkit\\CUDA\\v12.8\\lib\\x64", 70 | "cuda.lib;cudart.lib;nvrtc.lib", 71 | ) 72 | } else { 73 | ("", "", "", "", "") 74 | }; 75 | 76 | let vcxproj = std::fs::read_to_string(format!("./{solution_name}.vcxproj"))? 77 | .replace( 78 | "{{ OP_LIB_NAME }}", 79 | &format!("{}.lib", op_path.replace("-", "_")), 80 | ) 81 | .replace( 82 | "{{ TD_OP_H_PATH }}", 83 | &format!("{}/src/{}_CPlusPlusBase.h", op_path, short_upper), 84 | ) 85 | .replace( 86 | "{{ PLUGIN_H_PATH }}", 87 | &format!("{}/src/Rust{}Plugin.h", op_path, short_title), 88 | ) 89 | .replace( 90 | "{{ PLUGIN_CPP_PATH }}", 91 | &format!("{}/src/Rust{}Plugin.cpp", op_path, short_title), 92 | ) 93 | .replace("{{ CUDA_IMPORT_PROPS }}", cuda_import_props) 94 | .replace("{{ CUDA_IMPORT_TARGETS }}", cuda_import_targets) 95 | .replace("{{ CUDA_INCLUDE_DIRS }}", cuda_include_dirs) 96 | .replace("{{ CUDA_LIB_DIRS }}", cuda_lib_dirs) 97 | .replace("{{ CUDA_LIBS }}", cuda_libs); 98 | std::fs::write(format!("./{solution_name}.vcxproj"), vcxproj)?; 99 | 100 | let is_python_enabled = crate::metadata::is_python_enabled(plugin, &plugin_type); 101 | run_msbuild(config, &target, &plugin, is_python_enabled)?; 102 | fs_extra::remove_items(&files)?; 103 | 104 | println!("Move plugin to target"); 105 | move_plugin(&plugin, &plugin_type)?; 106 | 107 | Ok(()) 108 | } 109 | 110 | fn move_plugin(plugin: &str, _plugin_type: &PluginType) -> anyhow::Result<()> { 111 | let dll_name = "RustOp"; 112 | 113 | let plugin_build_path = format!("./Release/{dll_name}.dll"); 114 | let plugin_target_path = plugin_target_path(plugin); 115 | std::fs::create_dir_all(&plugin_target_path.parent().unwrap()) 116 | .context("Could not create plugin directory")?; 117 | std::fs::copy(&plugin_build_path, &plugin_target_path).context(format!( 118 | "Could not move plugin to target directory {:?}->{:?}", 119 | plugin_build_path, plugin_target_path 120 | ))?; 121 | Ok(()) 122 | } 123 | 124 | fn plugin_target_path(plugin: &str) -> PathBuf { 125 | let plugin_target_path = Path::new(PLUGIN_HOME) 126 | .join(plugin) 127 | .join(format!("{plugin}.dll")); 128 | plugin_target_path 129 | } 130 | 131 | fn run_msbuild( 132 | config: &Config, 133 | target: &str, 134 | plugin: &str, 135 | is_python_enabled: bool, 136 | ) -> anyhow::Result<()> { 137 | let msbuild = find_msbuild()?; 138 | let msbuild = msbuild.to_str().expect("Could not find msbuild"); 139 | let lib = format!("{}.lib", plugin.replace("-", "_")); 140 | let py_include = &config.windows.python_include_dir; 141 | let py_lib = &config.windows.python_lib_dir; 142 | let mut cmd = Command::new(msbuild) 143 | .arg(format!("/p:AdditionalIncludeDirectories={py_include}")) 144 | .arg(format!("/p:AdditionalLibraryDirectories={py_lib}")) 145 | .arg(if is_python_enabled { 146 | "/p:PreprocessorDefinitions=PYTHON_ENABLED" 147 | } else { 148 | "" 149 | }) 150 | .arg("/p:Configuration=Release") 151 | .arg("/t:Rebuild") 152 | .arg("/p:Platform=x64") 153 | .arg(format!("/p:Plugin=.\\target\\{target}\\release\\{lib}")) 154 | .spawn()?; 155 | 156 | let status = cmd.wait()?; 157 | if !status.success() { 158 | anyhow::bail!("Couldn't run msbuild"); 159 | } 160 | Ok(()) 161 | } 162 | 163 | fn find_msbuild() -> anyhow::Result { 164 | let cmd = r#"&"${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe" -latest -prerelease -products * -requires Microsoft.Component.MSBuild -find MSBuild\**\Bin\MSBuild.exe"#; 165 | let mut cmd = Command::new("powershell.exe") 166 | .arg(cmd) 167 | .stdout(Stdio::piped()) 168 | .spawn()?; 169 | let status = cmd.wait()?; 170 | if !status.success() { 171 | anyhow::bail!("Could not find msbuild"); 172 | } else { 173 | let mut stdout = cmd.stdout.take().expect("Couldn't read stdout"); 174 | let mut path = String::new(); 175 | stdout.read_to_string(&mut path)?; 176 | let path = PathBuf::from(&path.trim_end()); 177 | 178 | if !path.exists() { 179 | anyhow::bail!("Unknown msbuild path {:?}", path); 180 | } 181 | 182 | Ok(path) 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /td-rs.toml: -------------------------------------------------------------------------------- 1 | [windows] 2 | plugin_folder = "$HOME\\Documents\\Derivative\\Plugins\\" 3 | python_include_dir = "\"C:\\Program Files\\Derivative\\TouchDesigner\\Samples\\CPlusPlus\\3rdParty\\Python\\Include\";\"C:\\Program Files\\Derivative\\TouchDesigner\\Samples\\CPlusPlus\\3rdParty\\Python\\Include\\PC\"" 4 | python_lib_dir = "C:\\Program Files\\Derivative\\TouchDesigner\\Samples\\CPlusPlus\\3rdParty\\Python\\lib\\x64" 5 | 6 | [macos] 7 | plugin_folder = "$HOME/Library/Application Support/Derivative/TouchDesigner099/Plugins" 8 | python_include_dir = "/Applications/TouchDesigner.app/Contents/Frameworks/Python.framework/Headers" -------------------------------------------------------------------------------- /xtask/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "xtask" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | td-rs-xtask = { path = "../td-rs-xtask" } 8 | -------------------------------------------------------------------------------- /xtask/src/main.rs: -------------------------------------------------------------------------------- 1 | fn main() -> td_rs_xtask::Result<()> { 2 | td_rs_xtask::main() 3 | } 4 | --------------------------------------------------------------------------------