@nrutils/node-red-utils 0.0.0-stage → 0.1.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 +15 -0
- package/LICENSE +190 -0
- package/README.md +181 -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 +414 -0
- package/node-red-popup/README.md +110 -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 +438 -0
- package/node-red-worker/NodeRedWorkerRuntime.js +651 -0
- package/node-red-worker/README.md +265 -0
- package/node-red-worker/workerRuntimeLoader.js +55 -0
- package/package.json +70 -5
|
@@ -0,0 +1,1066 @@
|
|
|
1
|
+
#include <napi.h>
|
|
2
|
+
#include <vulkan/vulkan.h>
|
|
3
|
+
#include <unordered_map>
|
|
4
|
+
#include <vector>
|
|
5
|
+
#include <string>
|
|
6
|
+
#include <chrono>
|
|
7
|
+
#include <cstring>
|
|
8
|
+
#include <thread>
|
|
9
|
+
#include <mutex>
|
|
10
|
+
|
|
11
|
+
static VkInstance g_instance = VK_NULL_HANDLE;
|
|
12
|
+
static VkPhysicalDevice g_physicalDevice = VK_NULL_HANDLE;
|
|
13
|
+
static VkDevice g_device = VK_NULL_HANDLE;
|
|
14
|
+
static VkQueue g_queue = VK_NULL_HANDLE;
|
|
15
|
+
static uint32_t g_queueFamily = 0;
|
|
16
|
+
static uint32_t g_memoryType = UINT32_MAX;
|
|
17
|
+
static VkCommandPool g_commandPool = VK_NULL_HANDLE;
|
|
18
|
+
|
|
19
|
+
struct BufferInfo {
|
|
20
|
+
VkBuffer buffer;
|
|
21
|
+
VkDeviceMemory memory;
|
|
22
|
+
void* mapped;
|
|
23
|
+
size_t size;
|
|
24
|
+
uint32_t refCount;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
struct KernelInfo {
|
|
28
|
+
VkShaderModule shader;
|
|
29
|
+
VkPipelineLayout pipelineLayout;
|
|
30
|
+
VkDescriptorSetLayout descLayout;
|
|
31
|
+
VkPipeline pipeline;
|
|
32
|
+
VkDescriptorPool descPool;
|
|
33
|
+
VkDescriptorSet descSet;
|
|
34
|
+
uint32_t bindingCount;
|
|
35
|
+
uint32_t pushConstantSize;
|
|
36
|
+
std::vector<uint32_t> lastBufferHandles;
|
|
37
|
+
// Specialization constant support
|
|
38
|
+
std::vector<uint8_t> spirvData;
|
|
39
|
+
uint32_t specConstantCount;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
static std::unordered_map<uint32_t, BufferInfo> g_buffers;
|
|
43
|
+
static std::unordered_map<uint32_t, KernelInfo> g_kernels;
|
|
44
|
+
static uint32_t g_nextHandle = 1;
|
|
45
|
+
|
|
46
|
+
static uint64_t g_dispatchesCompleted = 0;
|
|
47
|
+
static uint64_t g_lastDispatchUs = 0;
|
|
48
|
+
|
|
49
|
+
static VkQueryPool g_timestampPool = VK_NULL_HANDLE;
|
|
50
|
+
static float g_timestampPeriod = 0.0f; // nanoseconds per tick
|
|
51
|
+
static uint64_t g_lastGpuExecutionNs = 0;
|
|
52
|
+
|
|
53
|
+
static int g_refCount = 0;
|
|
54
|
+
static std::mutex g_deviceMutex;
|
|
55
|
+
static std::mutex g_queueMutex;
|
|
56
|
+
|
|
57
|
+
// ---------------------------------------------------------------------------
|
|
58
|
+
|
|
59
|
+
static uint64_t NowUs() {
|
|
60
|
+
return std::chrono::duration_cast<std::chrono::microseconds>(
|
|
61
|
+
std::chrono::steady_clock::now().time_since_epoch()).count();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// ---------------------------------------------------------------------------
|
|
65
|
+
|
|
66
|
+
Napi::Value OpenDevice(const Napi::CallbackInfo& info) {
|
|
67
|
+
Napi::Env env = info.Env();
|
|
68
|
+
std::lock_guard<std::mutex> lock(g_deviceMutex);
|
|
69
|
+
|
|
70
|
+
if (g_refCount > 0) {
|
|
71
|
+
g_refCount++;
|
|
72
|
+
// Return existing device info without re-creating
|
|
73
|
+
VkPhysicalDeviceProperties props{};
|
|
74
|
+
vkGetPhysicalDeviceProperties(g_physicalDevice, &props);
|
|
75
|
+
VkPhysicalDeviceMemoryProperties memProps{};
|
|
76
|
+
vkGetPhysicalDeviceMemoryProperties(g_physicalDevice, &memProps);
|
|
77
|
+
uint64_t budgetBytes = 0;
|
|
78
|
+
for (uint32_t i = 0; i < memProps.memoryHeapCount; i++) {
|
|
79
|
+
if (memProps.memoryHeaps[i].flags & VK_MEMORY_HEAP_DEVICE_LOCAL_BIT)
|
|
80
|
+
budgetBytes += memProps.memoryHeaps[i].size;
|
|
81
|
+
}
|
|
82
|
+
auto vkVer = [](uint32_t v) {
|
|
83
|
+
return std::to_string(VK_VERSION_MAJOR(v)) + "." +
|
|
84
|
+
std::to_string(VK_VERSION_MINOR(v)) + "." +
|
|
85
|
+
std::to_string(VK_VERSION_PATCH(v));
|
|
86
|
+
};
|
|
87
|
+
Napi::Object obj = Napi::Object::New(env);
|
|
88
|
+
obj.Set("deviceName", Napi::String::New(env, props.deviceName));
|
|
89
|
+
obj.Set("driverVersion", Napi::String::New(env, vkVer(props.driverVersion)));
|
|
90
|
+
obj.Set("apiVersion", Napi::String::New(env, vkVer(props.apiVersion)));
|
|
91
|
+
obj.Set("memoryBudgetMB", Napi::Number::New(env, (double)(budgetBytes / (1024 * 1024))));
|
|
92
|
+
return obj;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
VkApplicationInfo appInfo{};
|
|
96
|
+
appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
|
|
97
|
+
appInfo.apiVersion = VK_API_VERSION_1_2;
|
|
98
|
+
|
|
99
|
+
VkInstanceCreateInfo ici{};
|
|
100
|
+
ici.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
|
|
101
|
+
ici.pApplicationInfo = &appInfo;
|
|
102
|
+
|
|
103
|
+
VkResult r = vkCreateInstance(&ici, nullptr, &g_instance);
|
|
104
|
+
if (r != VK_SUCCESS) {
|
|
105
|
+
Napi::Error::New(env, "vkCreateInstance failed: " + std::to_string(r)).ThrowAsJavaScriptException();
|
|
106
|
+
return env.Undefined();
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
uint32_t devCount = 0;
|
|
110
|
+
vkEnumeratePhysicalDevices(g_instance, &devCount, nullptr);
|
|
111
|
+
if (devCount == 0) {
|
|
112
|
+
Napi::Error::New(env, "No Vulkan physical devices found").ThrowAsJavaScriptException();
|
|
113
|
+
return env.Undefined();
|
|
114
|
+
}
|
|
115
|
+
std::vector<VkPhysicalDevice> devs(devCount);
|
|
116
|
+
vkEnumeratePhysicalDevices(g_instance, &devCount, devs.data());
|
|
117
|
+
|
|
118
|
+
// Prefer a hardware device; use a software renderer (llvmpipe) only if nothing else exists
|
|
119
|
+
for (auto pd : devs) {
|
|
120
|
+
VkPhysicalDeviceProperties props{};
|
|
121
|
+
vkGetPhysicalDeviceProperties(pd, &props);
|
|
122
|
+
if (props.deviceType == VK_PHYSICAL_DEVICE_TYPE_CPU) continue;
|
|
123
|
+
g_physicalDevice = pd;
|
|
124
|
+
break;
|
|
125
|
+
}
|
|
126
|
+
if (g_physicalDevice == VK_NULL_HANDLE) {
|
|
127
|
+
g_physicalDevice = devs[0];
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
uint32_t qfCount = 0;
|
|
131
|
+
vkGetPhysicalDeviceQueueFamilyProperties(g_physicalDevice, &qfCount, nullptr);
|
|
132
|
+
std::vector<VkQueueFamilyProperties> qfs(qfCount);
|
|
133
|
+
vkGetPhysicalDeviceQueueFamilyProperties(g_physicalDevice, &qfCount, qfs.data());
|
|
134
|
+
|
|
135
|
+
bool found = false;
|
|
136
|
+
for (uint32_t i = 0; i < qfCount; i++) {
|
|
137
|
+
if (qfs[i].queueFlags & VK_QUEUE_COMPUTE_BIT) {
|
|
138
|
+
g_queueFamily = i;
|
|
139
|
+
found = true;
|
|
140
|
+
break;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
if (!found) {
|
|
144
|
+
Napi::Error::New(env, "No compute queue family found").ThrowAsJavaScriptException();
|
|
145
|
+
return env.Undefined();
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
float prio = 1.0f;
|
|
149
|
+
VkDeviceQueueCreateInfo qci{};
|
|
150
|
+
qci.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
|
|
151
|
+
qci.queueFamilyIndex = g_queueFamily;
|
|
152
|
+
qci.queueCount = 1;
|
|
153
|
+
qci.pQueuePriorities = &prio;
|
|
154
|
+
|
|
155
|
+
VkDeviceCreateInfo dci{};
|
|
156
|
+
dci.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
|
|
157
|
+
dci.queueCreateInfoCount = 1;
|
|
158
|
+
dci.pQueueCreateInfos = &qci;
|
|
159
|
+
|
|
160
|
+
r = vkCreateDevice(g_physicalDevice, &dci, nullptr, &g_device);
|
|
161
|
+
if (r != VK_SUCCESS) {
|
|
162
|
+
Napi::Error::New(env, "vkCreateDevice failed: " + std::to_string(r)).ThrowAsJavaScriptException();
|
|
163
|
+
return env.Undefined();
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
vkGetDeviceQueue(g_device, g_queueFamily, 0, &g_queue);
|
|
167
|
+
|
|
168
|
+
VkPhysicalDeviceMemoryProperties memProps{};
|
|
169
|
+
vkGetPhysicalDeviceMemoryProperties(g_physicalDevice, &memProps);
|
|
170
|
+
|
|
171
|
+
// On unified memory, look for HOST_VISIBLE | HOST_COHERENT | DEVICE_LOCAL
|
|
172
|
+
const uint32_t desired =
|
|
173
|
+
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
|
|
174
|
+
VK_MEMORY_PROPERTY_HOST_COHERENT_BIT |
|
|
175
|
+
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
|
|
176
|
+
for (uint32_t i = 0; i < memProps.memoryTypeCount; i++) {
|
|
177
|
+
if ((memProps.memoryTypes[i].propertyFlags & desired) == desired) {
|
|
178
|
+
g_memoryType = i;
|
|
179
|
+
break;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
// If no fully unified type, fall back to HOST_VISIBLE | HOST_COHERENT
|
|
183
|
+
if (g_memoryType == UINT32_MAX) {
|
|
184
|
+
const uint32_t fallback =
|
|
185
|
+
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
|
|
186
|
+
VK_MEMORY_PROPERTY_HOST_COHERENT_BIT;
|
|
187
|
+
for (uint32_t i = 0; i < memProps.memoryTypeCount; i++) {
|
|
188
|
+
if ((memProps.memoryTypes[i].propertyFlags & fallback) == fallback) {
|
|
189
|
+
g_memoryType = i;
|
|
190
|
+
break;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
if (g_memoryType == UINT32_MAX) {
|
|
195
|
+
Napi::Error::New(env, "No suitable memory type found").ThrowAsJavaScriptException();
|
|
196
|
+
return env.Undefined();
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
VkCommandPoolCreateInfo cpci{};
|
|
200
|
+
cpci.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
|
|
201
|
+
cpci.queueFamilyIndex = g_queueFamily;
|
|
202
|
+
cpci.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
|
|
203
|
+
r = vkCreateCommandPool(g_device, &cpci, nullptr, &g_commandPool);
|
|
204
|
+
if (r != VK_SUCCESS) {
|
|
205
|
+
Napi::Error::New(env, "vkCreateCommandPool failed: " + std::to_string(r)).ThrowAsJavaScriptException();
|
|
206
|
+
return env.Undefined();
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Query the timestamp period from device properties
|
|
210
|
+
VkPhysicalDeviceProperties tsProps{};
|
|
211
|
+
vkGetPhysicalDeviceProperties(g_physicalDevice, &tsProps);
|
|
212
|
+
g_timestampPeriod = tsProps.limits.timestampPeriod;
|
|
213
|
+
|
|
214
|
+
// Create timestamp query pool (2 queries per dispatch: before + after)
|
|
215
|
+
if (g_timestampPeriod > 0.0f) {
|
|
216
|
+
VkQueryPoolCreateInfo qpci{};
|
|
217
|
+
qpci.sType = VK_STRUCTURE_TYPE_QUERY_POOL_CREATE_INFO;
|
|
218
|
+
qpci.queryType = VK_QUERY_TYPE_TIMESTAMP;
|
|
219
|
+
qpci.queryCount = 2; // start + end
|
|
220
|
+
r = vkCreateQueryPool(g_device, &qpci, nullptr, &g_timestampPool);
|
|
221
|
+
if (r != VK_SUCCESS) {
|
|
222
|
+
g_timestampPool = VK_NULL_HANDLE; // non-fatal, just disable timestamps
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
VkPhysicalDeviceProperties props{};
|
|
227
|
+
vkGetPhysicalDeviceProperties(g_physicalDevice, &props);
|
|
228
|
+
|
|
229
|
+
uint64_t budgetBytes = 0;
|
|
230
|
+
for (uint32_t i = 0; i < memProps.memoryHeapCount; i++) {
|
|
231
|
+
if (memProps.memoryHeaps[i].flags & VK_MEMORY_HEAP_DEVICE_LOCAL_BIT) {
|
|
232
|
+
budgetBytes += memProps.memoryHeaps[i].size;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
auto vkVer = [](uint32_t v) {
|
|
237
|
+
return std::to_string(VK_VERSION_MAJOR(v)) + "." +
|
|
238
|
+
std::to_string(VK_VERSION_MINOR(v)) + "." +
|
|
239
|
+
std::to_string(VK_VERSION_PATCH(v));
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
Napi::Object obj = Napi::Object::New(env);
|
|
243
|
+
obj.Set("deviceName", Napi::String::New(env, props.deviceName));
|
|
244
|
+
obj.Set("driverVersion", Napi::String::New(env, vkVer(props.driverVersion)));
|
|
245
|
+
obj.Set("apiVersion", Napi::String::New(env, vkVer(props.apiVersion)));
|
|
246
|
+
obj.Set("memoryBudgetMB", Napi::Number::New(env, (double)(budgetBytes / (1024 * 1024))));
|
|
247
|
+
g_refCount = 1;
|
|
248
|
+
return obj;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// ---------------------------------------------------------------------------
|
|
252
|
+
|
|
253
|
+
Napi::Value CreateBuffer(const Napi::CallbackInfo& info) {
|
|
254
|
+
Napi::Env env = info.Env();
|
|
255
|
+
if (info.Length() < 1 || !info[0].IsNumber()) {
|
|
256
|
+
Napi::Error::New(env, "createBuffer(byteSize: number)").ThrowAsJavaScriptException();
|
|
257
|
+
return env.Undefined();
|
|
258
|
+
}
|
|
259
|
+
size_t byteSize = (size_t)info[0].As<Napi::Number>().Uint32Value();
|
|
260
|
+
|
|
261
|
+
VkBufferCreateInfo bci{};
|
|
262
|
+
bci.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
|
|
263
|
+
bci.size = byteSize;
|
|
264
|
+
bci.usage = VK_BUFFER_USAGE_STORAGE_BUFFER_BIT;
|
|
265
|
+
bci.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
|
266
|
+
|
|
267
|
+
BufferInfo bi{};
|
|
268
|
+
bi.size = byteSize;
|
|
269
|
+
|
|
270
|
+
VkResult r = vkCreateBuffer(g_device, &bci, nullptr, &bi.buffer);
|
|
271
|
+
if (r != VK_SUCCESS) {
|
|
272
|
+
Napi::Error::New(env, "vkCreateBuffer failed: " + std::to_string(r)).ThrowAsJavaScriptException();
|
|
273
|
+
return env.Undefined();
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
VkMemoryRequirements memReq{};
|
|
277
|
+
vkGetBufferMemoryRequirements(g_device, bi.buffer, &memReq);
|
|
278
|
+
|
|
279
|
+
VkMemoryAllocateInfo mai{};
|
|
280
|
+
mai.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
|
|
281
|
+
mai.allocationSize = memReq.size;
|
|
282
|
+
mai.memoryTypeIndex = g_memoryType;
|
|
283
|
+
|
|
284
|
+
r = vkAllocateMemory(g_device, &mai, nullptr, &bi.memory);
|
|
285
|
+
if (r != VK_SUCCESS) {
|
|
286
|
+
vkDestroyBuffer(g_device, bi.buffer, nullptr);
|
|
287
|
+
Napi::Error::New(env, "vkAllocateMemory failed: " + std::to_string(r)).ThrowAsJavaScriptException();
|
|
288
|
+
return env.Undefined();
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
r = vkBindBufferMemory(g_device, bi.buffer, bi.memory, 0);
|
|
292
|
+
if (r != VK_SUCCESS) {
|
|
293
|
+
vkFreeMemory(g_device, bi.memory, nullptr);
|
|
294
|
+
vkDestroyBuffer(g_device, bi.buffer, nullptr);
|
|
295
|
+
Napi::Error::New(env, "vkBindBufferMemory failed: " + std::to_string(r)).ThrowAsJavaScriptException();
|
|
296
|
+
return env.Undefined();
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
r = vkMapMemory(g_device, bi.memory, 0, byteSize, 0, &bi.mapped);
|
|
300
|
+
if (r != VK_SUCCESS) {
|
|
301
|
+
vkFreeMemory(g_device, bi.memory, nullptr);
|
|
302
|
+
vkDestroyBuffer(g_device, bi.buffer, nullptr);
|
|
303
|
+
Napi::Error::New(env, "vkMapMemory failed: " + std::to_string(r)).ThrowAsJavaScriptException();
|
|
304
|
+
return env.Undefined();
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
uint32_t handle = g_nextHandle++;
|
|
308
|
+
bi.refCount = 1;
|
|
309
|
+
g_buffers[handle] = bi;
|
|
310
|
+
|
|
311
|
+
// No destructor callback — Vulkan owns this memory; addon frees in destroyBuffer
|
|
312
|
+
auto ab = Napi::ArrayBuffer::New(env, bi.mapped, byteSize);
|
|
313
|
+
|
|
314
|
+
Napi::Object obj = Napi::Object::New(env);
|
|
315
|
+
obj.Set("handle", Napi::Number::New(env, handle));
|
|
316
|
+
obj.Set("buffer", ab);
|
|
317
|
+
return obj;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// ---------------------------------------------------------------------------
|
|
321
|
+
|
|
322
|
+
Napi::Value DestroyBuffer(const Napi::CallbackInfo& info) {
|
|
323
|
+
Napi::Env env = info.Env();
|
|
324
|
+
if (info.Length() < 1 || !info[0].IsNumber()) {
|
|
325
|
+
Napi::Error::New(env, "destroyBuffer(handle: number)").ThrowAsJavaScriptException();
|
|
326
|
+
return env.Undefined();
|
|
327
|
+
}
|
|
328
|
+
uint32_t handle = info[0].As<Napi::Number>().Uint32Value();
|
|
329
|
+
auto it = g_buffers.find(handle);
|
|
330
|
+
if (it == g_buffers.end()) return env.Undefined();
|
|
331
|
+
if (--it->second.refCount > 0) return env.Undefined();
|
|
332
|
+
|
|
333
|
+
vkUnmapMemory(g_device, it->second.memory);
|
|
334
|
+
vkDestroyBuffer(g_device, it->second.buffer, nullptr);
|
|
335
|
+
vkFreeMemory(g_device, it->second.memory, nullptr);
|
|
336
|
+
g_buffers.erase(it);
|
|
337
|
+
return env.Undefined();
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// ---------------------------------------------------------------------------
|
|
341
|
+
|
|
342
|
+
Napi::Value RegisterKernel(const Napi::CallbackInfo& info) {
|
|
343
|
+
Napi::Env env = info.Env();
|
|
344
|
+
if (info.Length() < 4) {
|
|
345
|
+
Napi::Error::New(env, "registerKernel(spirvBuffer, bindingCount, pushConstantSize, workgroupSizeX[, specConstantCount[, specValuesBuffer]])").ThrowAsJavaScriptException();
|
|
346
|
+
return env.Undefined();
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
auto spirvAb = info[0].As<Napi::ArrayBuffer>();
|
|
350
|
+
uint32_t bindingCount = info[1].As<Napi::Number>().Uint32Value();
|
|
351
|
+
uint32_t pushConstantSize = info[2].As<Napi::Number>().Uint32Value();
|
|
352
|
+
// workgroupSizeX is baked into the SPIR-V; recorded for documentation purposes
|
|
353
|
+
// uint32_t workgroupSizeX = info[3].As<Napi::Number>().Uint32Value();
|
|
354
|
+
|
|
355
|
+
// Optional specialization constant params
|
|
356
|
+
uint32_t specCount = 0;
|
|
357
|
+
void* specData = nullptr;
|
|
358
|
+
size_t specDataSize = 0;
|
|
359
|
+
if (info.Length() >= 5 && info[4].IsNumber()) {
|
|
360
|
+
specCount = info[4].As<Napi::Number>().Uint32Value();
|
|
361
|
+
}
|
|
362
|
+
if (info.Length() >= 6 && info[5].IsArrayBuffer()) {
|
|
363
|
+
auto specAb = info[5].As<Napi::ArrayBuffer>();
|
|
364
|
+
specData = specAb.Data();
|
|
365
|
+
specDataSize = specAb.ByteLength();
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
KernelInfo ki{};
|
|
369
|
+
ki.bindingCount = bindingCount;
|
|
370
|
+
ki.pushConstantSize = pushConstantSize;
|
|
371
|
+
ki.specConstantCount = specCount;
|
|
372
|
+
|
|
373
|
+
// Store SPIR-V for later respecialization
|
|
374
|
+
const uint8_t* spirvBuf = reinterpret_cast<const uint8_t*>(spirvAb.Data());
|
|
375
|
+
size_t spirvLen = spirvAb.ByteLength();
|
|
376
|
+
ki.spirvData.assign(spirvBuf, spirvBuf + spirvLen);
|
|
377
|
+
|
|
378
|
+
VkShaderModuleCreateInfo smci{};
|
|
379
|
+
smci.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
|
|
380
|
+
smci.codeSize = spirvLen;
|
|
381
|
+
smci.pCode = reinterpret_cast<const uint32_t*>(spirvBuf);
|
|
382
|
+
|
|
383
|
+
VkResult r = vkCreateShaderModule(g_device, &smci, nullptr, &ki.shader);
|
|
384
|
+
if (r != VK_SUCCESS) {
|
|
385
|
+
Napi::Error::New(env, "vkCreateShaderModule failed: " + std::to_string(r)).ThrowAsJavaScriptException();
|
|
386
|
+
return env.Undefined();
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
std::vector<VkDescriptorSetLayoutBinding> bindings(bindingCount);
|
|
390
|
+
for (uint32_t i = 0; i < bindingCount; i++) {
|
|
391
|
+
bindings[i].binding = i;
|
|
392
|
+
bindings[i].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
|
|
393
|
+
bindings[i].descriptorCount = 1;
|
|
394
|
+
bindings[i].stageFlags = VK_SHADER_STAGE_COMPUTE_BIT;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
VkDescriptorSetLayoutCreateInfo dslci{};
|
|
398
|
+
dslci.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
|
|
399
|
+
dslci.bindingCount = bindingCount;
|
|
400
|
+
dslci.pBindings = bindings.data();
|
|
401
|
+
|
|
402
|
+
r = vkCreateDescriptorSetLayout(g_device, &dslci, nullptr, &ki.descLayout);
|
|
403
|
+
if (r != VK_SUCCESS) {
|
|
404
|
+
vkDestroyShaderModule(g_device, ki.shader, nullptr);
|
|
405
|
+
Napi::Error::New(env, "vkCreateDescriptorSetLayout failed: " + std::to_string(r)).ThrowAsJavaScriptException();
|
|
406
|
+
return env.Undefined();
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
VkPushConstantRange pcr{};
|
|
410
|
+
pcr.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT;
|
|
411
|
+
pcr.offset = 0;
|
|
412
|
+
pcr.size = pushConstantSize > 0 ? pushConstantSize : 4; // Vulkan requires size > 0
|
|
413
|
+
|
|
414
|
+
VkPipelineLayoutCreateInfo plci{};
|
|
415
|
+
plci.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
|
|
416
|
+
plci.setLayoutCount = 1;
|
|
417
|
+
plci.pSetLayouts = &ki.descLayout;
|
|
418
|
+
if (pushConstantSize > 0) {
|
|
419
|
+
plci.pushConstantRangeCount = 1;
|
|
420
|
+
plci.pPushConstantRanges = &pcr;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
r = vkCreatePipelineLayout(g_device, &plci, nullptr, &ki.pipelineLayout);
|
|
424
|
+
if (r != VK_SUCCESS) {
|
|
425
|
+
vkDestroyDescriptorSetLayout(g_device, ki.descLayout, nullptr);
|
|
426
|
+
vkDestroyShaderModule(g_device, ki.shader, nullptr);
|
|
427
|
+
Napi::Error::New(env, "vkCreatePipelineLayout failed: " + std::to_string(r)).ThrowAsJavaScriptException();
|
|
428
|
+
return env.Undefined();
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// Build specialization info if spec constants are present
|
|
432
|
+
std::vector<VkSpecializationMapEntry> specEntries;
|
|
433
|
+
VkSpecializationInfo specInfo{};
|
|
434
|
+
std::vector<uint8_t> specDataCopy;
|
|
435
|
+
if (specCount > 0 && specDataSize > 0) {
|
|
436
|
+
specEntries.resize(specCount);
|
|
437
|
+
for (uint32_t i = 0; i < specCount; i++) {
|
|
438
|
+
specEntries[i].constantID = i;
|
|
439
|
+
specEntries[i].offset = i * 4; // 4 bytes (u32) per constant
|
|
440
|
+
specEntries[i].size = 4;
|
|
441
|
+
}
|
|
442
|
+
// Copy spec data so it stays valid for the pipeline create call
|
|
443
|
+
specDataCopy.assign(reinterpret_cast<const uint8_t*>(specData),
|
|
444
|
+
reinterpret_cast<const uint8_t*>(specData) + specDataSize);
|
|
445
|
+
specInfo.mapEntryCount = specCount;
|
|
446
|
+
specInfo.pMapEntries = specEntries.data();
|
|
447
|
+
specInfo.dataSize = specDataCopy.size();
|
|
448
|
+
specInfo.pData = specDataCopy.data();
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
VkPipelineShaderStageCreateInfo stage{};
|
|
452
|
+
stage.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
|
|
453
|
+
stage.stage = VK_SHADER_STAGE_COMPUTE_BIT;
|
|
454
|
+
stage.module = ki.shader;
|
|
455
|
+
stage.pName = "main";
|
|
456
|
+
if (specCount > 0 && specDataSize > 0) {
|
|
457
|
+
stage.pSpecializationInfo = &specInfo;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
VkComputePipelineCreateInfo cpci{};
|
|
461
|
+
cpci.sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO;
|
|
462
|
+
cpci.stage = stage;
|
|
463
|
+
cpci.layout = ki.pipelineLayout;
|
|
464
|
+
|
|
465
|
+
r = vkCreateComputePipelines(g_device, VK_NULL_HANDLE, 1, &cpci, nullptr, &ki.pipeline);
|
|
466
|
+
if (r != VK_SUCCESS) {
|
|
467
|
+
vkDestroyPipelineLayout(g_device, ki.pipelineLayout, nullptr);
|
|
468
|
+
vkDestroyDescriptorSetLayout(g_device, ki.descLayout, nullptr);
|
|
469
|
+
vkDestroyShaderModule(g_device, ki.shader, nullptr);
|
|
470
|
+
Napi::Error::New(env, "vkCreateComputePipelines failed: " + std::to_string(r)).ThrowAsJavaScriptException();
|
|
471
|
+
return env.Undefined();
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
VkDescriptorPoolSize poolSize{};
|
|
475
|
+
poolSize.type = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
|
|
476
|
+
poolSize.descriptorCount = bindingCount > 0 ? bindingCount : 1;
|
|
477
|
+
|
|
478
|
+
VkDescriptorPoolCreateInfo dpci{};
|
|
479
|
+
dpci.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
|
|
480
|
+
dpci.maxSets = 1;
|
|
481
|
+
dpci.poolSizeCount = 1;
|
|
482
|
+
dpci.pPoolSizes = &poolSize;
|
|
483
|
+
|
|
484
|
+
r = vkCreateDescriptorPool(g_device, &dpci, nullptr, &ki.descPool);
|
|
485
|
+
if (r != VK_SUCCESS) {
|
|
486
|
+
vkDestroyPipeline(g_device, ki.pipeline, nullptr);
|
|
487
|
+
vkDestroyPipelineLayout(g_device, ki.pipelineLayout, nullptr);
|
|
488
|
+
vkDestroyDescriptorSetLayout(g_device, ki.descLayout, nullptr);
|
|
489
|
+
vkDestroyShaderModule(g_device, ki.shader, nullptr);
|
|
490
|
+
Napi::Error::New(env, "vkCreateDescriptorPool failed: " + std::to_string(r)).ThrowAsJavaScriptException();
|
|
491
|
+
return env.Undefined();
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
VkDescriptorSetAllocateInfo dsai{};
|
|
495
|
+
dsai.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
|
|
496
|
+
dsai.descriptorPool = ki.descPool;
|
|
497
|
+
dsai.descriptorSetCount = 1;
|
|
498
|
+
dsai.pSetLayouts = &ki.descLayout;
|
|
499
|
+
|
|
500
|
+
r = vkAllocateDescriptorSets(g_device, &dsai, &ki.descSet);
|
|
501
|
+
if (r != VK_SUCCESS) {
|
|
502
|
+
vkDestroyDescriptorPool(g_device, ki.descPool, nullptr);
|
|
503
|
+
vkDestroyPipeline(g_device, ki.pipeline, nullptr);
|
|
504
|
+
vkDestroyPipelineLayout(g_device, ki.pipelineLayout, nullptr);
|
|
505
|
+
vkDestroyDescriptorSetLayout(g_device, ki.descLayout, nullptr);
|
|
506
|
+
vkDestroyShaderModule(g_device, ki.shader, nullptr);
|
|
507
|
+
Napi::Error::New(env, "vkAllocateDescriptorSets failed: " + std::to_string(r)).ThrowAsJavaScriptException();
|
|
508
|
+
return env.Undefined();
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
uint32_t handle = g_nextHandle++;
|
|
512
|
+
g_kernels[handle] = ki;
|
|
513
|
+
return Napi::Number::New(env, handle);
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
// ---------------------------------------------------------------------------
|
|
517
|
+
|
|
518
|
+
Napi::Value RespecializeKernel(const Napi::CallbackInfo& info) {
|
|
519
|
+
Napi::Env env = info.Env();
|
|
520
|
+
if (info.Length() < 2 || !info[0].IsNumber() || !info[1].IsArrayBuffer()) {
|
|
521
|
+
Napi::Error::New(env, "respecializeKernel(kernelHandle, specValuesBuffer)").ThrowAsJavaScriptException();
|
|
522
|
+
return env.Undefined();
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
uint32_t handle = info[0].As<Napi::Number>().Uint32Value();
|
|
526
|
+
auto specAb = info[1].As<Napi::ArrayBuffer>();
|
|
527
|
+
|
|
528
|
+
auto kit = g_kernels.find(handle);
|
|
529
|
+
if (kit == g_kernels.end()) {
|
|
530
|
+
Napi::Error::New(env, "respecializeKernel: invalid kernel handle").ThrowAsJavaScriptException();
|
|
531
|
+
return env.Undefined();
|
|
532
|
+
}
|
|
533
|
+
KernelInfo& ki = kit->second;
|
|
534
|
+
|
|
535
|
+
if (ki.specConstantCount == 0 || ki.spirvData.empty()) {
|
|
536
|
+
Napi::Error::New(env, "respecializeKernel: kernel has no specialization constants").ThrowAsJavaScriptException();
|
|
537
|
+
return env.Undefined();
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
// Build new shader module from stored SPIR-V
|
|
541
|
+
VkShaderModuleCreateInfo smci{};
|
|
542
|
+
smci.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
|
|
543
|
+
smci.codeSize = ki.spirvData.size();
|
|
544
|
+
smci.pCode = reinterpret_cast<const uint32_t*>(ki.spirvData.data());
|
|
545
|
+
|
|
546
|
+
VkShaderModule newShader = VK_NULL_HANDLE;
|
|
547
|
+
VkResult r = vkCreateShaderModule(g_device, &smci, nullptr, &newShader);
|
|
548
|
+
if (r != VK_SUCCESS) {
|
|
549
|
+
Napi::Error::New(env, "respecializeKernel: vkCreateShaderModule failed: " + std::to_string(r)).ThrowAsJavaScriptException();
|
|
550
|
+
return env.Undefined();
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
// Build specialization info from new values
|
|
554
|
+
uint32_t specCount = ki.specConstantCount;
|
|
555
|
+
std::vector<VkSpecializationMapEntry> specEntries(specCount);
|
|
556
|
+
for (uint32_t i = 0; i < specCount; i++) {
|
|
557
|
+
specEntries[i].constantID = i;
|
|
558
|
+
specEntries[i].offset = i * 4;
|
|
559
|
+
specEntries[i].size = 4;
|
|
560
|
+
}
|
|
561
|
+
VkSpecializationInfo specInfo{};
|
|
562
|
+
specInfo.mapEntryCount = specCount;
|
|
563
|
+
specInfo.pMapEntries = specEntries.data();
|
|
564
|
+
specInfo.dataSize = specAb.ByteLength();
|
|
565
|
+
specInfo.pData = specAb.Data();
|
|
566
|
+
|
|
567
|
+
VkPipelineShaderStageCreateInfo stage{};
|
|
568
|
+
stage.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
|
|
569
|
+
stage.stage = VK_SHADER_STAGE_COMPUTE_BIT;
|
|
570
|
+
stage.module = newShader;
|
|
571
|
+
stage.pName = "main";
|
|
572
|
+
stage.pSpecializationInfo = &specInfo;
|
|
573
|
+
|
|
574
|
+
VkComputePipelineCreateInfo cpci{};
|
|
575
|
+
cpci.sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO;
|
|
576
|
+
cpci.stage = stage;
|
|
577
|
+
cpci.layout = ki.pipelineLayout;
|
|
578
|
+
|
|
579
|
+
VkPipeline newPipeline = VK_NULL_HANDLE;
|
|
580
|
+
r = vkCreateComputePipelines(g_device, VK_NULL_HANDLE, 1, &cpci, nullptr, &newPipeline);
|
|
581
|
+
vkDestroyShaderModule(g_device, newShader, nullptr);
|
|
582
|
+
if (r != VK_SUCCESS) {
|
|
583
|
+
Napi::Error::New(env, "respecializeKernel: vkCreateComputePipelines failed: " + std::to_string(r)).ThrowAsJavaScriptException();
|
|
584
|
+
return env.Undefined();
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
// Swap old pipeline for new one (no caching — swap strategy as specified)
|
|
588
|
+
vkDestroyPipeline(g_device, ki.pipeline, nullptr);
|
|
589
|
+
ki.pipeline = newPipeline;
|
|
590
|
+
|
|
591
|
+
return env.Undefined();
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
// ---------------------------------------------------------------------------
|
|
595
|
+
|
|
596
|
+
Napi::Value DestroyKernel(const Napi::CallbackInfo& info) {
|
|
597
|
+
Napi::Env env = info.Env();
|
|
598
|
+
if (info.Length() < 1 || !info[0].IsNumber()) return env.Undefined();
|
|
599
|
+
uint32_t handle = info[0].As<Napi::Number>().Uint32Value();
|
|
600
|
+
auto it = g_kernels.find(handle);
|
|
601
|
+
if (it == g_kernels.end()) return env.Undefined();
|
|
602
|
+
auto& ki = it->second;
|
|
603
|
+
ki.lastBufferHandles.clear();
|
|
604
|
+
vkDestroyDescriptorPool(g_device, ki.descPool, nullptr);
|
|
605
|
+
vkDestroyPipeline(g_device, ki.pipeline, nullptr);
|
|
606
|
+
vkDestroyPipelineLayout(g_device, ki.pipelineLayout, nullptr);
|
|
607
|
+
vkDestroyDescriptorSetLayout(g_device, ki.descLayout, nullptr);
|
|
608
|
+
vkDestroyShaderModule(g_device, ki.shader, nullptr);
|
|
609
|
+
g_kernels.erase(it);
|
|
610
|
+
return env.Undefined();
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
// ---------------------------------------------------------------------------
|
|
614
|
+
|
|
615
|
+
struct DispatchCtx {
|
|
616
|
+
VkDevice device;
|
|
617
|
+
VkFence fence;
|
|
618
|
+
VkCommandBuffer cmd;
|
|
619
|
+
VkCommandPool pool;
|
|
620
|
+
uint64_t submitUs;
|
|
621
|
+
bool hasTimestamps;
|
|
622
|
+
};
|
|
623
|
+
|
|
624
|
+
class DispatchWorker : public Napi::AsyncWorker {
|
|
625
|
+
public:
|
|
626
|
+
DispatchWorker(Napi::Env env, Napi::Promise::Deferred deferred, DispatchCtx ctx)
|
|
627
|
+
: Napi::AsyncWorker(env), deferred_(deferred), ctx_(ctx) {}
|
|
628
|
+
|
|
629
|
+
void Execute() override {
|
|
630
|
+
VkResult r = vkWaitForFences(ctx_.device, 1, &ctx_.fence, VK_TRUE, 5000000000ULL);
|
|
631
|
+
uint64_t doneUs = NowUs();
|
|
632
|
+
elapsed_ = doneUs - ctx_.submitUs;
|
|
633
|
+
if (r == VK_TIMEOUT) {
|
|
634
|
+
SetError("GPU dispatch timed out after 5 seconds");
|
|
635
|
+
} else if (r != VK_SUCCESS) {
|
|
636
|
+
SetError("vkWaitForFences failed: " + std::to_string(r));
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
void OnOK() override {
|
|
641
|
+
vkDestroyFence(ctx_.device, ctx_.fence, nullptr);
|
|
642
|
+
vkFreeCommandBuffers(ctx_.device, ctx_.pool, 1, &ctx_.cmd);
|
|
643
|
+
g_dispatchesCompleted++;
|
|
644
|
+
g_lastDispatchUs = elapsed_;
|
|
645
|
+
if (ctx_.hasTimestamps) {
|
|
646
|
+
uint64_t timestamps[2] = {0, 0};
|
|
647
|
+
VkResult tr = vkGetQueryPoolResults(ctx_.device, g_timestampPool, 0, 2,
|
|
648
|
+
sizeof(timestamps), timestamps, sizeof(uint64_t),
|
|
649
|
+
VK_QUERY_RESULT_64_BIT | VK_QUERY_RESULT_WAIT_BIT);
|
|
650
|
+
if (tr == VK_SUCCESS && timestamps[1] > timestamps[0]) {
|
|
651
|
+
g_lastGpuExecutionNs = (uint64_t)((timestamps[1] - timestamps[0]) * g_timestampPeriod);
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
deferred_.Resolve(Env().Undefined());
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
void OnError(const Napi::Error& e) override {
|
|
658
|
+
vkDestroyFence(ctx_.device, ctx_.fence, nullptr);
|
|
659
|
+
vkFreeCommandBuffers(ctx_.device, ctx_.pool, 1, &ctx_.cmd);
|
|
660
|
+
deferred_.Reject(e.Value());
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
private:
|
|
664
|
+
Napi::Promise::Deferred deferred_;
|
|
665
|
+
DispatchCtx ctx_;
|
|
666
|
+
uint64_t elapsed_ = 0;
|
|
667
|
+
};
|
|
668
|
+
|
|
669
|
+
// ---------------------------------------------------------------------------
|
|
670
|
+
|
|
671
|
+
Napi::Value Dispatch(const Napi::CallbackInfo& info) {
|
|
672
|
+
Napi::Env env = info.Env();
|
|
673
|
+
if (info.Length() < 6) {
|
|
674
|
+
Napi::Error::New(env, "dispatch(kernelHandle, bufferHandles[], pushConstantsBuffer, groupsX, groupsY, groupsZ)").ThrowAsJavaScriptException();
|
|
675
|
+
return env.Undefined();
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
uint32_t kernelHandle = info[0].As<Napi::Number>().Uint32Value();
|
|
679
|
+
auto bufHandlesArr = info[1].As<Napi::Array>();
|
|
680
|
+
auto pcAb = info[2].As<Napi::ArrayBuffer>();
|
|
681
|
+
uint32_t groupsX = info[3].As<Napi::Number>().Uint32Value();
|
|
682
|
+
uint32_t groupsY = info[4].As<Napi::Number>().Uint32Value();
|
|
683
|
+
uint32_t groupsZ = info[5].As<Napi::Number>().Uint32Value();
|
|
684
|
+
|
|
685
|
+
auto kit = g_kernels.find(kernelHandle);
|
|
686
|
+
if (kit == g_kernels.end()) {
|
|
687
|
+
Napi::Error::New(env, "Invalid kernel handle").ThrowAsJavaScriptException();
|
|
688
|
+
return env.Undefined();
|
|
689
|
+
}
|
|
690
|
+
KernelInfo& ki = kit->second;
|
|
691
|
+
|
|
692
|
+
uint32_t numBufs = bufHandlesArr.Length();
|
|
693
|
+
std::vector<uint32_t> currentHandles(numBufs);
|
|
694
|
+
for (uint32_t i = 0; i < numBufs; i++) {
|
|
695
|
+
currentHandles[i] = bufHandlesArr.Get(i).As<Napi::Number>().Uint32Value();
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
bool buffersChanged = (currentHandles != ki.lastBufferHandles);
|
|
699
|
+
|
|
700
|
+
if (buffersChanged) {
|
|
701
|
+
std::vector<VkDescriptorBufferInfo> bufInfos(numBufs);
|
|
702
|
+
std::vector<VkWriteDescriptorSet> writes(numBufs);
|
|
703
|
+
|
|
704
|
+
for (uint32_t i = 0; i < numBufs; i++) {
|
|
705
|
+
auto bit = g_buffers.find(currentHandles[i]);
|
|
706
|
+
if (bit == g_buffers.end()) {
|
|
707
|
+
Napi::Error::New(env, "Invalid buffer handle: " + std::to_string(currentHandles[i])).ThrowAsJavaScriptException();
|
|
708
|
+
return env.Undefined();
|
|
709
|
+
}
|
|
710
|
+
bufInfos[i].buffer = bit->second.buffer;
|
|
711
|
+
bufInfos[i].offset = 0;
|
|
712
|
+
bufInfos[i].range = bit->second.size;
|
|
713
|
+
|
|
714
|
+
writes[i].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
|
|
715
|
+
writes[i].dstSet = ki.descSet;
|
|
716
|
+
writes[i].dstBinding = i;
|
|
717
|
+
writes[i].descriptorCount = 1;
|
|
718
|
+
writes[i].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
|
|
719
|
+
writes[i].pBufferInfo = &bufInfos[i];
|
|
720
|
+
}
|
|
721
|
+
if (numBufs > 0)
|
|
722
|
+
vkUpdateDescriptorSets(g_device, numBufs, writes.data(), 0, nullptr);
|
|
723
|
+
ki.lastBufferHandles = std::move(currentHandles);
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
VkCommandBufferAllocateInfo cbai{};
|
|
727
|
+
cbai.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
|
|
728
|
+
cbai.commandPool = g_commandPool;
|
|
729
|
+
cbai.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
|
|
730
|
+
cbai.commandBufferCount = 1;
|
|
731
|
+
|
|
732
|
+
VkCommandBuffer cmd = VK_NULL_HANDLE;
|
|
733
|
+
VkResult r = vkAllocateCommandBuffers(g_device, &cbai, &cmd);
|
|
734
|
+
if (r != VK_SUCCESS) {
|
|
735
|
+
Napi::Error::New(env, "vkAllocateCommandBuffers failed: " + std::to_string(r)).ThrowAsJavaScriptException();
|
|
736
|
+
return env.Undefined();
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
VkCommandBufferBeginInfo cbbi{};
|
|
740
|
+
cbbi.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
|
|
741
|
+
cbbi.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
|
|
742
|
+
vkBeginCommandBuffer(cmd, &cbbi);
|
|
743
|
+
|
|
744
|
+
vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, ki.pipeline);
|
|
745
|
+
vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, ki.pipelineLayout, 0, 1, &ki.descSet, 0, nullptr);
|
|
746
|
+
if (ki.pushConstantSize > 0 && pcAb.ByteLength() > 0) {
|
|
747
|
+
vkCmdPushConstants(cmd, ki.pipelineLayout, VK_SHADER_STAGE_COMPUTE_BIT, 0, ki.pushConstantSize, pcAb.Data());
|
|
748
|
+
}
|
|
749
|
+
// Reset and write timestamps around dispatch
|
|
750
|
+
if (g_timestampPool != VK_NULL_HANDLE) {
|
|
751
|
+
vkCmdResetQueryPool(cmd, g_timestampPool, 0, 2);
|
|
752
|
+
vkCmdWriteTimestamp(cmd, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, g_timestampPool, 0);
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
vkCmdDispatch(cmd, groupsX, groupsY, groupsZ);
|
|
756
|
+
|
|
757
|
+
if (g_timestampPool != VK_NULL_HANDLE) {
|
|
758
|
+
vkCmdWriteTimestamp(cmd, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, g_timestampPool, 1);
|
|
759
|
+
}
|
|
760
|
+
vkEndCommandBuffer(cmd);
|
|
761
|
+
|
|
762
|
+
VkFenceCreateInfo fci{};
|
|
763
|
+
fci.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
|
|
764
|
+
VkFence fence = VK_NULL_HANDLE;
|
|
765
|
+
r = vkCreateFence(g_device, &fci, nullptr, &fence);
|
|
766
|
+
if (r != VK_SUCCESS) {
|
|
767
|
+
vkFreeCommandBuffers(g_device, g_commandPool, 1, &cmd);
|
|
768
|
+
Napi::Error::New(env, "vkCreateFence failed: " + std::to_string(r)).ThrowAsJavaScriptException();
|
|
769
|
+
return env.Undefined();
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
VkSubmitInfo si{};
|
|
773
|
+
si.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
|
|
774
|
+
si.commandBufferCount = 1;
|
|
775
|
+
si.pCommandBuffers = &cmd;
|
|
776
|
+
|
|
777
|
+
uint64_t submitUs = NowUs();
|
|
778
|
+
{
|
|
779
|
+
std::lock_guard<std::mutex> qlock(g_queueMutex);
|
|
780
|
+
r = vkQueueSubmit(g_queue, 1, &si, fence);
|
|
781
|
+
}
|
|
782
|
+
if (r != VK_SUCCESS) {
|
|
783
|
+
vkDestroyFence(g_device, fence, nullptr);
|
|
784
|
+
vkFreeCommandBuffers(g_device, g_commandPool, 1, &cmd);
|
|
785
|
+
Napi::Error::New(env, "vkQueueSubmit failed: " + std::to_string(r)).ThrowAsJavaScriptException();
|
|
786
|
+
return env.Undefined();
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
auto deferred = Napi::Promise::Deferred::New(env);
|
|
790
|
+
DispatchCtx ctx{ g_device, fence, cmd, g_commandPool, submitUs, g_timestampPool != VK_NULL_HANDLE };
|
|
791
|
+
auto* worker = new DispatchWorker(env, deferred, ctx);
|
|
792
|
+
worker->Queue();
|
|
793
|
+
return deferred.Promise();
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
|
|
797
|
+
Napi::Value DispatchChain(const Napi::CallbackInfo& info) {
|
|
798
|
+
Napi::Env env = info.Env();
|
|
799
|
+
if (info.Length() < 1 || !info[0].IsArray()) {
|
|
800
|
+
Napi::Error::New(env, "dispatchChain(specs[])").ThrowAsJavaScriptException();
|
|
801
|
+
return env.Undefined();
|
|
802
|
+
}
|
|
803
|
+
auto specs = info[0].As<Napi::Array>();
|
|
804
|
+
uint32_t chainLen = specs.Length();
|
|
805
|
+
if (chainLen == 0) {
|
|
806
|
+
auto deferred = Napi::Promise::Deferred::New(env);
|
|
807
|
+
deferred.Resolve(env.Undefined());
|
|
808
|
+
return deferred.Promise();
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
// Allocate ONE command buffer for the entire chain
|
|
812
|
+
VkCommandBufferAllocateInfo cbai{};
|
|
813
|
+
cbai.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
|
|
814
|
+
cbai.commandPool = g_commandPool;
|
|
815
|
+
cbai.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
|
|
816
|
+
cbai.commandBufferCount = 1;
|
|
817
|
+
|
|
818
|
+
VkCommandBuffer cmd = VK_NULL_HANDLE;
|
|
819
|
+
VkResult r = vkAllocateCommandBuffers(g_device, &cbai, &cmd);
|
|
820
|
+
if (r != VK_SUCCESS) {
|
|
821
|
+
Napi::Error::New(env, "dispatchChain: vkAllocateCommandBuffers failed: " + std::to_string(r)).ThrowAsJavaScriptException();
|
|
822
|
+
return env.Undefined();
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
VkCommandBufferBeginInfo cbbi{};
|
|
826
|
+
cbbi.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
|
|
827
|
+
cbbi.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
|
|
828
|
+
vkBeginCommandBuffer(cmd, &cbbi);
|
|
829
|
+
|
|
830
|
+
// Timestamp: start of entire chain
|
|
831
|
+
if (g_timestampPool != VK_NULL_HANDLE) {
|
|
832
|
+
vkCmdResetQueryPool(cmd, g_timestampPool, 0, 2);
|
|
833
|
+
vkCmdWriteTimestamp(cmd, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, g_timestampPool, 0);
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
for (uint32_t s = 0; s < chainLen; s++) {
|
|
837
|
+
auto spec = specs.Get(s).As<Napi::Object>();
|
|
838
|
+
uint32_t kernelHandle = spec.Get("kernel").As<Napi::Number>().Uint32Value();
|
|
839
|
+
auto bufArr = spec.Get("buffers").As<Napi::Array>();
|
|
840
|
+
auto pcAb = spec.Get("pushConstants").As<Napi::ArrayBuffer>();
|
|
841
|
+
uint32_t gx = spec.Get("groupsX").As<Napi::Number>().Uint32Value();
|
|
842
|
+
uint32_t gy = spec.Has("groupsY") ? spec.Get("groupsY").As<Napi::Number>().Uint32Value() : 1;
|
|
843
|
+
uint32_t gz = spec.Has("groupsZ") ? spec.Get("groupsZ").As<Napi::Number>().Uint32Value() : 1;
|
|
844
|
+
|
|
845
|
+
auto kit = g_kernels.find(kernelHandle);
|
|
846
|
+
if (kit == g_kernels.end()) {
|
|
847
|
+
vkEndCommandBuffer(cmd);
|
|
848
|
+
vkFreeCommandBuffers(g_device, g_commandPool, 1, &cmd);
|
|
849
|
+
Napi::Error::New(env, "dispatchChain: invalid kernel handle at index " + std::to_string(s)).ThrowAsJavaScriptException();
|
|
850
|
+
return env.Undefined();
|
|
851
|
+
}
|
|
852
|
+
KernelInfo& ki = kit->second;
|
|
853
|
+
|
|
854
|
+
// Insert compute->compute memory barrier between dispatches
|
|
855
|
+
if (s > 0) {
|
|
856
|
+
VkMemoryBarrier barrier{};
|
|
857
|
+
barrier.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER;
|
|
858
|
+
barrier.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT;
|
|
859
|
+
barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
|
|
860
|
+
vkCmdPipelineBarrier(cmd,
|
|
861
|
+
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
|
|
862
|
+
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
|
|
863
|
+
0, 1, &barrier, 0, nullptr, 0, nullptr);
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
// Update descriptor sets for this dispatch
|
|
867
|
+
uint32_t numBufs = bufArr.Length();
|
|
868
|
+
std::vector<uint32_t> currentHandles(numBufs);
|
|
869
|
+
for (uint32_t i = 0; i < numBufs; i++) {
|
|
870
|
+
currentHandles[i] = bufArr.Get(i).As<Napi::Number>().Uint32Value();
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
bool buffersChanged = (currentHandles != ki.lastBufferHandles);
|
|
874
|
+
if (buffersChanged) {
|
|
875
|
+
std::vector<VkDescriptorBufferInfo> bufInfos(numBufs);
|
|
876
|
+
std::vector<VkWriteDescriptorSet> writes(numBufs);
|
|
877
|
+
for (uint32_t i = 0; i < numBufs; i++) {
|
|
878
|
+
auto bit = g_buffers.find(currentHandles[i]);
|
|
879
|
+
if (bit == g_buffers.end()) {
|
|
880
|
+
vkEndCommandBuffer(cmd);
|
|
881
|
+
vkFreeCommandBuffers(g_device, g_commandPool, 1, &cmd);
|
|
882
|
+
Napi::Error::New(env, "dispatchChain: invalid buffer handle " + std::to_string(currentHandles[i]) + " at spec index " + std::to_string(s)).ThrowAsJavaScriptException();
|
|
883
|
+
return env.Undefined();
|
|
884
|
+
}
|
|
885
|
+
bufInfos[i].buffer = bit->second.buffer;
|
|
886
|
+
bufInfos[i].offset = 0;
|
|
887
|
+
bufInfos[i].range = bit->second.size;
|
|
888
|
+
|
|
889
|
+
writes[i].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
|
|
890
|
+
writes[i].dstSet = ki.descSet;
|
|
891
|
+
writes[i].dstBinding = i;
|
|
892
|
+
writes[i].descriptorCount = 1;
|
|
893
|
+
writes[i].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
|
|
894
|
+
writes[i].pBufferInfo = &bufInfos[i];
|
|
895
|
+
}
|
|
896
|
+
if (numBufs > 0)
|
|
897
|
+
vkUpdateDescriptorSets(g_device, numBufs, writes.data(), 0, nullptr);
|
|
898
|
+
ki.lastBufferHandles = std::move(currentHandles);
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
// Record commands for this dispatch
|
|
902
|
+
vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, ki.pipeline);
|
|
903
|
+
vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, ki.pipelineLayout, 0, 1, &ki.descSet, 0, nullptr);
|
|
904
|
+
if (ki.pushConstantSize > 0 && pcAb.ByteLength() > 0) {
|
|
905
|
+
vkCmdPushConstants(cmd, ki.pipelineLayout, VK_SHADER_STAGE_COMPUTE_BIT, 0, ki.pushConstantSize, pcAb.Data());
|
|
906
|
+
}
|
|
907
|
+
vkCmdDispatch(cmd, gx, gy, gz);
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
// Timestamp: end of entire chain
|
|
911
|
+
if (g_timestampPool != VK_NULL_HANDLE) {
|
|
912
|
+
vkCmdWriteTimestamp(cmd, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, g_timestampPool, 1);
|
|
913
|
+
}
|
|
914
|
+
vkEndCommandBuffer(cmd);
|
|
915
|
+
|
|
916
|
+
VkFenceCreateInfo fci{};
|
|
917
|
+
fci.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
|
|
918
|
+
VkFence fence = VK_NULL_HANDLE;
|
|
919
|
+
r = vkCreateFence(g_device, &fci, nullptr, &fence);
|
|
920
|
+
if (r != VK_SUCCESS) {
|
|
921
|
+
vkFreeCommandBuffers(g_device, g_commandPool, 1, &cmd);
|
|
922
|
+
Napi::Error::New(env, "dispatchChain: vkCreateFence failed: " + std::to_string(r)).ThrowAsJavaScriptException();
|
|
923
|
+
return env.Undefined();
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
VkSubmitInfo si{};
|
|
927
|
+
si.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
|
|
928
|
+
si.commandBufferCount = 1;
|
|
929
|
+
si.pCommandBuffers = &cmd;
|
|
930
|
+
|
|
931
|
+
uint64_t submitUs = NowUs();
|
|
932
|
+
{
|
|
933
|
+
std::lock_guard<std::mutex> qlock(g_queueMutex);
|
|
934
|
+
r = vkQueueSubmit(g_queue, 1, &si, fence);
|
|
935
|
+
}
|
|
936
|
+
if (r != VK_SUCCESS) {
|
|
937
|
+
vkDestroyFence(g_device, fence, nullptr);
|
|
938
|
+
vkFreeCommandBuffers(g_device, g_commandPool, 1, &cmd);
|
|
939
|
+
Napi::Error::New(env, "dispatchChain: vkQueueSubmit failed: " + std::to_string(r)).ThrowAsJavaScriptException();
|
|
940
|
+
return env.Undefined();
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
auto deferred = Napi::Promise::Deferred::New(env);
|
|
944
|
+
DispatchCtx ctx{ g_device, fence, cmd, g_commandPool, submitUs, g_timestampPool != VK_NULL_HANDLE };
|
|
945
|
+
auto* worker = new DispatchWorker(env, deferred, ctx);
|
|
946
|
+
worker->Queue();
|
|
947
|
+
return deferred.Promise();
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
|
|
951
|
+
Napi::Value RefBuffer(const Napi::CallbackInfo& info) {
|
|
952
|
+
Napi::Env env = info.Env();
|
|
953
|
+
if (info.Length() < 1 || !info[0].IsNumber()) {
|
|
954
|
+
Napi::Error::New(env, "refBuffer(handle: number)").ThrowAsJavaScriptException();
|
|
955
|
+
return env.Undefined();
|
|
956
|
+
}
|
|
957
|
+
uint32_t handle = info[0].As<Napi::Number>().Uint32Value();
|
|
958
|
+
auto it = g_buffers.find(handle);
|
|
959
|
+
if (it == g_buffers.end()) {
|
|
960
|
+
Napi::Error::New(env, "refBuffer: unknown handle " + std::to_string(handle)).ThrowAsJavaScriptException();
|
|
961
|
+
return env.Undefined();
|
|
962
|
+
}
|
|
963
|
+
it->second.refCount++;
|
|
964
|
+
return env.Undefined();
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
Napi::Value GetBufferView(const Napi::CallbackInfo& info) {
|
|
968
|
+
Napi::Env env = info.Env();
|
|
969
|
+
if (info.Length() < 1 || !info[0].IsNumber()) {
|
|
970
|
+
Napi::Error::New(env, "getBufferView(handle: number)").ThrowAsJavaScriptException();
|
|
971
|
+
return env.Undefined();
|
|
972
|
+
}
|
|
973
|
+
uint32_t handle = info[0].As<Napi::Number>().Uint32Value();
|
|
974
|
+
auto it = g_buffers.find(handle);
|
|
975
|
+
if (it == g_buffers.end()) {
|
|
976
|
+
Napi::Error::New(env, "getBufferView: unknown handle " + std::to_string(handle)).ThrowAsJavaScriptException();
|
|
977
|
+
return env.Undefined();
|
|
978
|
+
}
|
|
979
|
+
auto& bi = it->second;
|
|
980
|
+
return Napi::ArrayBuffer::New(env, bi.mapped, bi.size);
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
// ---------------------------------------------------------------------------
|
|
984
|
+
|
|
985
|
+
Napi::Value DestroyDevice(const Napi::CallbackInfo& info) {
|
|
986
|
+
Napi::Env env = info.Env();
|
|
987
|
+
std::lock_guard<std::mutex> lock(g_deviceMutex);
|
|
988
|
+
if (g_device == VK_NULL_HANDLE) return env.Undefined();
|
|
989
|
+
if (--g_refCount > 0) return env.Undefined();
|
|
990
|
+
|
|
991
|
+
vkQueueWaitIdle(g_queue);
|
|
992
|
+
|
|
993
|
+
for (auto& [h, bi] : g_buffers) {
|
|
994
|
+
vkUnmapMemory(g_device, bi.memory);
|
|
995
|
+
vkDestroyBuffer(g_device, bi.buffer, nullptr);
|
|
996
|
+
vkFreeMemory(g_device, bi.memory, nullptr);
|
|
997
|
+
}
|
|
998
|
+
g_buffers.clear();
|
|
999
|
+
|
|
1000
|
+
for (auto& [h, ki] : g_kernels) {
|
|
1001
|
+
vkDestroyDescriptorPool(g_device, ki.descPool, nullptr);
|
|
1002
|
+
vkDestroyPipeline(g_device, ki.pipeline, nullptr);
|
|
1003
|
+
vkDestroyPipelineLayout(g_device, ki.pipelineLayout, nullptr);
|
|
1004
|
+
vkDestroyDescriptorSetLayout(g_device, ki.descLayout, nullptr);
|
|
1005
|
+
vkDestroyShaderModule(g_device, ki.shader, nullptr);
|
|
1006
|
+
}
|
|
1007
|
+
g_kernels.clear();
|
|
1008
|
+
|
|
1009
|
+
if (g_timestampPool != VK_NULL_HANDLE) {
|
|
1010
|
+
vkDestroyQueryPool(g_device, g_timestampPool, nullptr);
|
|
1011
|
+
g_timestampPool = VK_NULL_HANDLE;
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
if (g_commandPool != VK_NULL_HANDLE) {
|
|
1015
|
+
vkDestroyCommandPool(g_device, g_commandPool, nullptr);
|
|
1016
|
+
g_commandPool = VK_NULL_HANDLE;
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
vkDestroyDevice(g_device, nullptr);
|
|
1020
|
+
g_device = VK_NULL_HANDLE;
|
|
1021
|
+
|
|
1022
|
+
vkDestroyInstance(g_instance, nullptr);
|
|
1023
|
+
g_instance = VK_NULL_HANDLE;
|
|
1024
|
+
g_physicalDevice = VK_NULL_HANDLE;
|
|
1025
|
+
g_queue = VK_NULL_HANDLE;
|
|
1026
|
+
g_memoryType = UINT32_MAX;
|
|
1027
|
+
|
|
1028
|
+
return env.Undefined();
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
// ---------------------------------------------------------------------------
|
|
1032
|
+
|
|
1033
|
+
Napi::Value Stats(const Napi::CallbackInfo& info) {
|
|
1034
|
+
Napi::Env env = info.Env();
|
|
1035
|
+
uint64_t totalBytes = 0;
|
|
1036
|
+
for (auto& [h, bi] : g_buffers) totalBytes += bi.size;
|
|
1037
|
+
|
|
1038
|
+
Napi::Object obj = Napi::Object::New(env);
|
|
1039
|
+
obj.Set("buffersAllocated", Napi::Number::New(env, (double)g_buffers.size()));
|
|
1040
|
+
obj.Set("buffersBytes", Napi::Number::New(env, (double)totalBytes));
|
|
1041
|
+
obj.Set("kernelsRegistered", Napi::Number::New(env, (double)g_kernels.size()));
|
|
1042
|
+
obj.Set("dispatchesCompleted", Napi::Number::New(env, (double)g_dispatchesCompleted));
|
|
1043
|
+
obj.Set("lastDispatchUs", Napi::Number::New(env, (double)g_lastDispatchUs));
|
|
1044
|
+
obj.Set("lastGpuExecutionUs", Napi::Number::New(env, (double)(g_lastGpuExecutionNs / 1000)));
|
|
1045
|
+
return obj;
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
// ---------------------------------------------------------------------------
|
|
1049
|
+
|
|
1050
|
+
Napi::Object Init(Napi::Env env, Napi::Object exports) {
|
|
1051
|
+
exports.Set("openDevice", Napi::Function::New(env, OpenDevice));
|
|
1052
|
+
exports.Set("createBuffer", Napi::Function::New(env, CreateBuffer));
|
|
1053
|
+
exports.Set("destroyBuffer", Napi::Function::New(env, DestroyBuffer));
|
|
1054
|
+
exports.Set("registerKernel", Napi::Function::New(env, RegisterKernel));
|
|
1055
|
+
exports.Set("respecializeKernel",Napi::Function::New(env, RespecializeKernel));
|
|
1056
|
+
exports.Set("destroyKernel", Napi::Function::New(env, DestroyKernel));
|
|
1057
|
+
exports.Set("dispatch", Napi::Function::New(env, Dispatch));
|
|
1058
|
+
exports.Set("dispatchChain", Napi::Function::New(env, DispatchChain));
|
|
1059
|
+
exports.Set("destroyDevice", Napi::Function::New(env, DestroyDevice));
|
|
1060
|
+
exports.Set("stats", Napi::Function::New(env, Stats));
|
|
1061
|
+
exports.Set("refBuffer", Napi::Function::New(env, RefBuffer));
|
|
1062
|
+
exports.Set("getBufferView", Napi::Function::New(env, GetBufferView));
|
|
1063
|
+
return exports;
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
NODE_API_MODULE(gpu_addon, Init)
|