@nrutils/node-red-utils 0.0.0-stage → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +20 -0
- package/LICENSE +190 -0
- package/README.md +187 -2
- package/index.js +50 -0
- package/node-red-base/NodeRedNodeBase.js +143 -0
- package/node-red-base/README.md +215 -0
- package/node-red-editorcomms/EditorCommsBackend.js +155 -0
- package/node-red-editorcomms/EditorCommsFrontend.js +105 -0
- package/node-red-editorcomms/README.md +164 -0
- package/node-red-gpu/GpuBuffer.js +29 -0
- package/node-red-gpu/Kernel.js +35 -0
- package/node-red-gpu/README.md +220 -0
- package/node-red-gpu/backends/CpuBackend.js +109 -0
- package/node-red-gpu/backends/VulkanBackend.js +232 -0
- package/node-red-gpu/errors.js +48 -0
- package/node-red-gpu/index.js +6 -0
- package/node-red-gpu/log.js +18 -0
- package/node-red-gpu/native/binding.gyp +14 -0
- package/node-red-gpu/native/src/gpu_addon.cpp +1066 -0
- package/node-red-gpu/nodeRedGpuMain.js +39 -0
- package/node-red-gpu/nodeRedGpuWorker.js +242 -0
- package/node-red-gpu/transpiler/emitGlsl.js +497 -0
- package/node-red-gpu/transpiler/glslNames.js +60 -0
- package/node-red-gpu/transpiler/index.js +81 -0
- package/node-red-gpu/transpiler/inferTypes.js +302 -0
- package/node-red-gpu/transpiler/validate.js +461 -0
- package/node-red-popup/NodeRedPopupBackend.js +69 -0
- package/node-red-popup/NodeRedPopupFrontend.js +508 -0
- package/node-red-popup/README.md +113 -0
- package/node-red-pubsub-bus/NodeRedPubsubBus.js +617 -0
- package/node-red-pubsub-bus/NodeRedPubsubBusWorkerProxy.js +239 -0
- package/node-red-pubsub-bus/README.md +201 -0
- package/node-red-resourceendpoint/NodeRedResourceEndpoint.js +205 -0
- package/node-red-resourceendpoint/README.md +119 -0
- package/node-red-singleton/NodeRedSingletonBackend.js +30 -0
- package/node-red-singleton/NodeRedSingletonFrontend.js +54 -0
- package/node-red-singleton/README.md +72 -0
- package/node-red-worker/NodeRedWorker.js +443 -0
- package/node-red-worker/NodeRedWorkerRuntime.js +659 -0
- package/node-red-worker/README.md +273 -0
- package/node-red-worker/workerRuntimeLoader.js +55 -0
- package/package.json +70 -5
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const { logBackendOnce, fallbackMessage } = require('./log.js');
|
|
5
|
+
|
|
6
|
+
const BACKEND = process.env.NODE_RED_GPU_BACKEND || 'auto';
|
|
7
|
+
|
|
8
|
+
const nodeRedGpu = (superclass) => class extends superclass {
|
|
9
|
+
constructor(RED, thisNode, config) {
|
|
10
|
+
super(RED, thisNode, config);
|
|
11
|
+
|
|
12
|
+
// Keep the SPIR-V cache in the userDir (-u). Worker threads get a copy of process.env.
|
|
13
|
+
if (!process.env.NODE_RED_GPU_CACHE && RED.settings?.userDir) {
|
|
14
|
+
process.env.NODE_RED_GPU_CACHE = path.join(RED.settings.userDir, 'gpu-cache');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
let initError = null;
|
|
18
|
+
try {
|
|
19
|
+
if (BACKEND === 'vulkan' || BACKEND === 'auto') {
|
|
20
|
+
const { VulkanBackend } = require('./backends/VulkanBackend.js');
|
|
21
|
+
const vb = new VulkanBackend();
|
|
22
|
+
logBackendOnce((m) => RED.log.info(m), `node-red-gpu: using Vulkan device "${vb._device.deviceName}"`);
|
|
23
|
+
vb.destroyDevice();
|
|
24
|
+
}
|
|
25
|
+
} catch (err) {
|
|
26
|
+
if (BACKEND === 'vulkan') initError = err;
|
|
27
|
+
else logBackendOnce((m) => RED.log.warn(m), fallbackMessage(err));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (initError) {
|
|
31
|
+
thisNode.status({ fill: 'red', shape: 'dot', text: initError.message });
|
|
32
|
+
thisNode.error(initError);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
thisNode.on('close', (done) => done());
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
module.exports = { nodeRedGpu };
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { CpuBackend } = require('./backends/CpuBackend.js');
|
|
4
|
+
const { Kernel } = require('./Kernel.js');
|
|
5
|
+
const { GpuBuffer } = require('./GpuBuffer.js');
|
|
6
|
+
const { transpile } = require('./transpiler/index.js');
|
|
7
|
+
const { GpuCompileError } = require('./errors.js');
|
|
8
|
+
const { logBackendOnce, fallbackMessage } = require('./log.js');
|
|
9
|
+
const { isMainThread } = require('worker_threads');
|
|
10
|
+
|
|
11
|
+
// JS versions of the GLSL-only math calls, so JS kernels also run on the CPU backend
|
|
12
|
+
const MATH_SHIMS = Object.assign(Object.create(Math), {
|
|
13
|
+
clamp: (x, lo, hi) => Math.min(Math.max(x, lo), hi),
|
|
14
|
+
exp2: (x) => 2 ** x,
|
|
15
|
+
mix: (a, b, t) => a * (1 - t) + b * t,
|
|
16
|
+
step: (edge, x) => (x < edge ? 0 : 1),
|
|
17
|
+
smoothstep: (e0, e1, x) => {
|
|
18
|
+
const t = Math.min(Math.max((x - e0) / (e1 - e0), 0), 1);
|
|
19
|
+
return t * t * (3 - 2 * t);
|
|
20
|
+
},
|
|
21
|
+
fract: (x) => x - Math.floor(x),
|
|
22
|
+
mod: (x, y) => x - y * Math.floor(x / y),
|
|
23
|
+
});
|
|
24
|
+
const CASTS = {
|
|
25
|
+
float: (x) => Math.fround(x),
|
|
26
|
+
int: (x) => x | 0,
|
|
27
|
+
uint: (x) => x >>> 0,
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/** Rebuild the kernel source with the shims in scope, for the CPU backend. */
|
|
31
|
+
function makeCpuKernel(fn) {
|
|
32
|
+
const source = typeof fn === 'function' ? fn.toString() : fn;
|
|
33
|
+
return new Function('Math', 'float', 'int', 'uint', `return (${source});`)(MATH_SHIMS, CASTS.float, CASTS.int, CASTS.uint);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const BACKEND = process.env.NODE_RED_GPU_BACKEND || 'auto';
|
|
37
|
+
|
|
38
|
+
function makeBackend() {
|
|
39
|
+
if (BACKEND === 'cpu') return new CpuBackend();
|
|
40
|
+
if (BACKEND === 'vulkan') {
|
|
41
|
+
const { VulkanBackend } = require('./backends/VulkanBackend.js');
|
|
42
|
+
return new VulkanBackend();
|
|
43
|
+
}
|
|
44
|
+
if (BACKEND === 'auto') {
|
|
45
|
+
try {
|
|
46
|
+
const { VulkanBackend } = require('./backends/VulkanBackend.js');
|
|
47
|
+
return new VulkanBackend();
|
|
48
|
+
} catch (err) {
|
|
49
|
+
// In a worker thread the nodeRedGpu mixin on the main thread has already logged the reason
|
|
50
|
+
if (isMainThread) logBackendOnce(console.warn, fallbackMessage(err));
|
|
51
|
+
return new CpuBackend();
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
throw new Error(`Unknown GPU backend: ${BACKEND}`);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const nodeRedGpuWorker = () => {
|
|
58
|
+
const backend = makeBackend();
|
|
59
|
+
const kernels = new Set();
|
|
60
|
+
const buffers = new Set();
|
|
61
|
+
const pool = new Map(); // byteSize → [GpuBuffer, GpuBuffer, ...]
|
|
62
|
+
const DTYPE_BYTES = { u8: 1, i8: 1, u16: 2, i16: 2, u32: 4, i32: 4, f32: 4, f64: 8 };
|
|
63
|
+
const DTYPE_CTOR = { u8: Uint8Array, i8: Int8Array, u16: Uint16Array, i16: Int16Array, u32: Uint32Array, i32: Int32Array, f32: Float32Array, f64: Float64Array };
|
|
64
|
+
|
|
65
|
+
function registerKernel(name, options, requiredPushConstants = Object.keys(options.pushConstants || {})) {
|
|
66
|
+
const handle = backend.registerKernel(name, options);
|
|
67
|
+
const kernel = new Kernel(handle, backend, name, requiredPushConstants);
|
|
68
|
+
kernels.add(kernel);
|
|
69
|
+
return kernel;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function registerJsKernel(name, options) {
|
|
73
|
+
const { kernel: fn, buffers: bufSpec = [], constants = {}, workgroupSize = 256, specConstants = {} } = options;
|
|
74
|
+
|
|
75
|
+
// Transpile JS kernel → GLSL 450
|
|
76
|
+
const { glsl: glslSource, gpuOnlyCalls } = transpile(fn, {
|
|
77
|
+
buffers: bufSpec,
|
|
78
|
+
constants,
|
|
79
|
+
workgroupSize,
|
|
80
|
+
specConstants,
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
// The CPU runs threads one after another, so it cannot honor these calls
|
|
84
|
+
if (backend instanceof CpuBackend && gpuOnlyCalls.size > 0) {
|
|
85
|
+
throw new GpuCompileError(`Kernel '${name}' uses ${[...gpuOnlyCalls].join(', ')}, which needs the Vulkan backend. The CPU backend cannot run it.`);
|
|
86
|
+
}
|
|
87
|
+
const cpuKernel = makeCpuKernel(fn);
|
|
88
|
+
|
|
89
|
+
// Build the registerKernel options from the JS kernel spec
|
|
90
|
+
// Shared buffers are GLSL shared variables — they don't get Vulkan SSBO bindings
|
|
91
|
+
const bindings = bufSpec.filter(b => !b.shared).map(b => b.access || 'readwrite');
|
|
92
|
+
const specConstantSet = new Set(
|
|
93
|
+
Array.isArray(specConstants) ? specConstants : Object.keys(specConstants)
|
|
94
|
+
);
|
|
95
|
+
// Spec constants live in VkSpecializationInfo, not the push_constant block
|
|
96
|
+
const pushConstants = {};
|
|
97
|
+
for (const [k, v] of Object.entries(constants)) {
|
|
98
|
+
if (!specConstantSet.has(k)) pushConstants[k] = v;
|
|
99
|
+
}
|
|
100
|
+
pushConstants.count = 'u32';
|
|
101
|
+
// A dispatch must pass the constants and count; countY and countZ (for 2D and 3D) may be left out
|
|
102
|
+
const requiredPushConstants = Object.keys(pushConstants);
|
|
103
|
+
pushConstants.countY = 'u32';
|
|
104
|
+
pushConstants.countZ = 'u32';
|
|
105
|
+
|
|
106
|
+
// Auto-generate cpuFallback from the original JS function.
|
|
107
|
+
// Supports 1D, 2D, and 3D dispatch via pc.count / pc.countY / pc.countZ.
|
|
108
|
+
const cpuFallback = ({ buffers: cpuBufs, pushConstants: pc, specConstants: spec }) => {
|
|
109
|
+
const countX = pc.count;
|
|
110
|
+
const countY = pc.countY || 1;
|
|
111
|
+
const countZ = pc.countZ || 1;
|
|
112
|
+
const ctx = {
|
|
113
|
+
thread: { x: 0, y: 0, z: 0, localX: 0, localY: 0, localZ: 0 },
|
|
114
|
+
// Spec constants are read like the other constants
|
|
115
|
+
constants: { ...spec, ...pc },
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
// Rebuild the full buffer array, inserting locally-allocated typed arrays for
|
|
119
|
+
// shared buffers (which have no backing SSBO in cpuBufs).
|
|
120
|
+
let ssboBufIdx = 0;
|
|
121
|
+
const allBufs = bufSpec.map(buf => {
|
|
122
|
+
if (buf.shared) {
|
|
123
|
+
return new (DTYPE_CTOR[buf.dtype])(buf.count);
|
|
124
|
+
}
|
|
125
|
+
return cpuBufs[ssboBufIdx++];
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
// Find the output buffer (first readwrite or writeonly) in the full buf list
|
|
129
|
+
const outIdx = bufSpec.findIndex(b => !b.shared && (b.access === 'readwrite' || b.access === 'writeonly'));
|
|
130
|
+
const outBuf = outIdx >= 0 ? allBufs[outIdx] : allBufs[0];
|
|
131
|
+
for (let z = 0; z < countZ; z++) {
|
|
132
|
+
ctx.thread.z = z;
|
|
133
|
+
for (let y = 0; y < countY; y++) {
|
|
134
|
+
ctx.thread.y = y;
|
|
135
|
+
for (let x = 0; x < countX; x++) {
|
|
136
|
+
ctx.thread.x = x;
|
|
137
|
+
const result = cpuKernel.apply(ctx, allBufs);
|
|
138
|
+
if (result !== undefined) {
|
|
139
|
+
const idx = x + y * countX + z * countX * countY;
|
|
140
|
+
outBuf[idx] = result;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
return registerKernel(name, {
|
|
148
|
+
source: glslSource,
|
|
149
|
+
language: 'glsl',
|
|
150
|
+
bindings,
|
|
151
|
+
pushConstants,
|
|
152
|
+
workgroupSize,
|
|
153
|
+
cpuFallback,
|
|
154
|
+
specConstants,
|
|
155
|
+
}, requiredPushConstants);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function createBuffer(count, dtype, options) {
|
|
159
|
+
const byteSize = count * DTYPE_BYTES[dtype];
|
|
160
|
+
const pooled = pool.get(byteSize);
|
|
161
|
+
if (pooled && pooled.length > 0) {
|
|
162
|
+
const buf = pooled.pop();
|
|
163
|
+
if (pooled.length === 0) pool.delete(byteSize);
|
|
164
|
+
// Re-type the view if dtype changed
|
|
165
|
+
if (buf.dtype !== dtype) {
|
|
166
|
+
const Ctor = DTYPE_CTOR[dtype];
|
|
167
|
+
buf._handle.view = new Ctor(buf._handle.view.buffer);
|
|
168
|
+
buf._handle.dtype = dtype;
|
|
169
|
+
buf._handle.count = count;
|
|
170
|
+
}
|
|
171
|
+
buf._destroyed = false;
|
|
172
|
+
buffers.add(buf);
|
|
173
|
+
return buf;
|
|
174
|
+
}
|
|
175
|
+
const handle = backend.createBuffer(count, dtype, options);
|
|
176
|
+
const buf = new GpuBuffer(handle, backend);
|
|
177
|
+
buffers.add(buf);
|
|
178
|
+
return buf;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function releaseBuffer(gpuBuffer) {
|
|
182
|
+
if (gpuBuffer._destroyed) return;
|
|
183
|
+
gpuBuffer._destroyed = true;
|
|
184
|
+
buffers.delete(gpuBuffer);
|
|
185
|
+
const byteSize = gpuBuffer.byteLength;
|
|
186
|
+
let list = pool.get(byteSize);
|
|
187
|
+
if (!list) { list = []; pool.set(byteSize, list); }
|
|
188
|
+
list.push(gpuBuffer);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function exportBuffer(gpuBuffer) {
|
|
192
|
+
backend.refBuffer(gpuBuffer._handle);
|
|
193
|
+
return {
|
|
194
|
+
_gpuHandle: gpuBuffer._handle._addonHandle,
|
|
195
|
+
dtype: gpuBuffer.dtype,
|
|
196
|
+
count: gpuBuffer.count,
|
|
197
|
+
byteLength: gpuBuffer.byteLength,
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function importBuffer(ref) {
|
|
202
|
+
const handle = backend.importBuffer(ref);
|
|
203
|
+
const buf = new GpuBuffer(handle, backend);
|
|
204
|
+
buffers.add(buf);
|
|
205
|
+
return buf;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
async function dispatchChain(specs) {
|
|
209
|
+
const handleSpecs = specs.map(({ kernel, ...rest }, i) => {
|
|
210
|
+
if (!(kernel instanceof Kernel)) throw new TypeError(`dispatchChain: spec ${i} needs a kernel from registerKernel or registerJsKernel in 'kernel'`);
|
|
211
|
+
kernel._checkPushConstants(rest.pushConstants);
|
|
212
|
+
return { ...rest, kernelHandle: kernel._handle };
|
|
213
|
+
});
|
|
214
|
+
return backend.dispatchChain(handleSpecs);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function stats() {
|
|
218
|
+
return backend.stats();
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
async function close() {
|
|
222
|
+
for (const buf of buffers) buf.destroy();
|
|
223
|
+
// Flush pooled buffers
|
|
224
|
+
for (const [, list] of pool) {
|
|
225
|
+
for (const buf of list) {
|
|
226
|
+
buf._destroyed = false; // un-flag so destroy() works
|
|
227
|
+
buf.destroy();
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
pool.clear();
|
|
231
|
+
for (const kernel of kernels) kernel.destroy();
|
|
232
|
+
backend.destroyDevice();
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function respecializeKernel(kernel, specValues) {
|
|
236
|
+
backend.respecializeKernel(kernel._handle, specValues);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
return { registerKernel, registerJsKernel, respecializeKernel, createBuffer, releaseBuffer, exportBuffer, importBuffer, dispatchChain, stats, close };
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
module.exports = { nodeRedGpuWorker };
|