Img2Num C++ (Internal Developer Docs) dev
API Documentation
Loading...
Searching...
No Matches
gpu.h
1#ifndef GPU_H
2#define GPU_H
3
4#if defined(__EMSCRIPTEN__)
5#include <emscripten/emscripten.h>
6#include <emscripten/html5.h>
7#endif
8
9#include <fstream>
10#include <iterator>
11#include <map>
12#include <sstream>
13#include <string>
14#include <webgpu/webgpu_cpp.h>
15
16// auto generated by tools/embed_shaders.py
17#include "internal/log.h"
18
19#include <embedded_shaders.h>
20
21class GPU {
22 private:
23 wgpu::Instance instance;
24 wgpu::Adapter adapter;
25 wgpu::Device device;
26 wgpu::Queue queue;
27
28 bool adapter_ready = false;
29 bool device_ready = false;
30 bool gpu_initialized = false;
31
32 GPU() = default;
33
34 bool validate_device() {
35 if (!device)
36 return false;
37
38 // Try creating a trivial buffer to ensure the device is usable
39 wgpu::BufferDescriptor desc = {};
40 desc.size = 4;
41 desc.usage = wgpu::BufferUsage::CopyDst;
42
43 wgpu::Buffer test = device.CreateBuffer(&desc);
44 return test != nullptr;
45 }
46
47 public:
48 // makes a single global instance that other files can reference
49 static GPU& getClassInstance() {
50 static GPU gpuInstance;
51 return gpuInstance;
52 }
53
54 const wgpu::Device& get_device() {
55 return device;
56 }
57
58 const wgpu::Instance& get_instance() {
59 return instance;
60 }
61
62 const wgpu::Queue& get_queue() {
63 return queue;
64 }
65
66 bool is_initialized() {
67 return gpu_initialized;
68 }
69
70 // Delete copy constructor and assignment operator to prevent duplication
71 GPU(const GPU&) = delete;
72 GPU& operator=(const GPU&) = delete;
73 GPU(GPU&&) = delete;
74 GPU& operator=(GPU&&) = delete;
75
76 std::string readWGSLFile(std::string_view shader_id) {
77 // A simple linear search over 9 items is blazingly fast in C++
78 for (const auto& entry : embedded_shaders::shaders) {
79 if (entry.id == shader_id) {
80 // Safely convert the string_view back to a std::string for Dawn
81 return std::string(entry.source);
82 }
83 }
84
85 // Always good to handle the "not found" case gracefully!
86 return "";
87 }
88
89 wgpu::ComputePipeline createPipeline(const std::string& filename, const std::string& label) {
90 wgpu::ShaderSourceWGSL wgsl;
91 std::string shaderCode = readWGSLFile(filename);
92 wgsl.code = shaderCode.c_str();
93 wgpu::ShaderModuleDescriptor md = {};
94 md.nextInChain = &wgsl;
95 md.label = label.c_str();
96 wgpu::ShaderModule sm = device.CreateShaderModule(&md);
97
98 wgpu::ComputePipelineDescriptor cpd = {};
99 cpd.compute.module = sm;
100 cpd.compute.entryPoint = "main";
101 return device.CreateComputePipeline(&cpd);
102 };
103
104 static uint32_t getAlignedBytesPerRow(uint32_t width, uint32_t bytesPerPixel = 4) {
105 uint32_t unaligned = width * bytesPerPixel;
106 uint32_t align = 256;
107 return (unaligned + align - 1) & ~(align - 1);
108 };
109
110 static void printShaderError(wgpu::ShaderModule shaderModule) {
111 shaderModule.GetCompilationInfo(
112 // Callback Mode (New API Requirement)
113 wgpu::CallbackMode::AllowProcessEvents,
114 // Callback Lambda
115 [](wgpu::CompilationInfoRequestStatus status, const wgpu::CompilationInfo* info) {
116 if (status != wgpu::CompilationInfoRequestStatus::Success || !info)
117 return;
118
119 for (uint32_t i = 0; i < info->messageCount; ++i) {
120 const auto& msg = info->messages[i];
121 IMG2NUM_LOG_INFO(
122 "Shader Error [{}] Line {}:{} - {}",
123 msg.type == wgpu::CompilationMessageType::Error ? "ERR" : "WARN",
124 msg.lineNum, msg.linePos,
125 std::string_view(
126 msg.message.data ? msg.message.data : "", msg.message.length
127 )
128 );
129 }
130 }
131 );
132 };
133
134 void init_gpu() {
135 if (gpu_initialized)
136 return;
137
138 wgpu::InstanceDescriptor instanceDesc = {};
139 instance = wgpu::CreateInstance(&instanceDesc);
140
141 if (!instance) {
142 IMG2NUM_LOG_INFO("Fatal: WebGPU instance creation failed.");
143 return;
144 }
145
146 // ---------------------------------------------------------
147 // 1. Get Adapter
148 // ---------------------------------------------------------
149 IMG2NUM_LOG_INFO("Requesting Adapter...");
150 adapter_ready = false;
151
152 instance.RequestAdapter(
153 nullptr,
154 wgpu::CallbackMode::AllowProcessEvents, // <--- ALLOW EVENTS
155 [this](wgpu::RequestAdapterStatus status, wgpu::Adapter a, wgpu::StringView msg) {
156 if (status == wgpu::RequestAdapterStatus::Success) {
157 adapter = std::move(a);
158 IMG2NUM_LOG_INFO("Adapter Acquired");
159 } else {
160 IMG2NUM_LOG_INFO(
161 "Adapter Failed: {}", std::string_view(msg.data ? msg.data : "", msg.length)
162 );
163 }
164 adapter_ready = true; // Unblock the loop
165 }
166 );
167
168 // WAIT LOOP: Yield to browser so it can actually find the adapter
169 while (!adapter_ready) {
170 instance.ProcessEvents();
171#if defined(__EMSCRIPTEN__)
172 emscripten_sleep(10); // Sleep 10ms
173#endif
174 }
175
176 if (!adapter) {
177 IMG2NUM_LOG_INFO("Fatal: Could not get WebGPU Adapter.");
178 return;
179 }
180
181 // ---------------------------------------------------------
182 // 2. Get Device
183 // ---------------------------------------------------------
184 IMG2NUM_LOG_INFO("Requesting Device...");
185 device_ready = false;
186
187 wgpu::DeviceDescriptor deviceDesc = {};
188 deviceDesc.SetUncapturedErrorCallback([](const wgpu::Device&, wgpu::ErrorType type,
189 wgpu::StringView msg) {
190 // 1. Safely extract the string using the provided length
191 std::string err_str = (msg.data && msg.length > 0) ? std::string(msg.data, msg.length)
192 : "Unknown Error (Null message)";
193
194 // 2. Print it safely
195 IMG2NUM_LOG_INFO(
196 "\n[WEBGPU FATAL ERROR] Type: {} | Msg: {}\n", static_cast<uint32_t>(type), err_str
197 );
198 });
199 deviceDesc.SetDeviceLostCallback(
200 wgpu::CallbackMode::AllowProcessEvents,
201 [](const wgpu::Device&, wgpu::DeviceLostReason reason, wgpu::StringView msg) {
202 std::string err_msg = (msg.data && msg.length > 0)
203 ? std::string(msg.data, msg.length)
204 : "Unknown device lost reason";
205 // 1. Ignore teardown cancellations so we don't log during static destruction
206 if (reason == wgpu::DeviceLostReason::CallbackCancelled) {
207 return;
208 }
209 IMG2NUM_LOG_INFO(
210 "[DEVICE LOST] Reason: {} Msg: {}", static_cast<int>(reason), err_msg
211 );
212 }
213 );
214
215 // Set maximum possible device hardware memory
216 wgpu::Limits supportedLimits;
217 wgpu::Limits requiredLimits;
218 if (adapter.GetLimits(&supportedLimits)) {
219 // nominally the device Buffer limit is 256MB
220
221 // Copy the adapter's physical limits over to your requested limits
222 requiredLimits = supportedLimits;
223
224 IMG2NUM_LOG_INFO("maxBufferSize: {}", requiredLimits.maxBufferSize);
225 IMG2NUM_LOG_INFO(
226 "maxStorageBufferBindingSize: {}", requiredLimits.maxStorageBufferBindingSize
227 );
228
229 deviceDesc.requiredLimits = &requiredLimits;
230 }
231
232 adapter.RequestDevice(
233 &deviceDesc,
234 wgpu::CallbackMode::AllowProcessEvents, // <--- ALLOW EVENTS
235 [this](wgpu::RequestDeviceStatus status, wgpu::Device d, wgpu::StringView msg) {
236 if (status == wgpu::RequestDeviceStatus::Success) {
237 device = std::move(d);
238 IMG2NUM_LOG_INFO("Device Acquired");
239 } else {
240 IMG2NUM_LOG_INFO(
241 "Device Failed: {}", msg.data && msg.length > 0
242 ? std::string(msg.data, msg.length)
243 : "Unknown error"
244 );
245 }
246 device_ready = true; // Unblock the loop
247 }
248 );
249
250 // WAIT LOOP
251 while (!device_ready) {
252 instance.ProcessEvents();
253#if defined(__EMSCRIPTEN__)
254 emscripten_sleep(10);
255#endif
256 }
257
258 if (!device) {
259 IMG2NUM_LOG_INFO("Fatal: Could not get WebGPU Device.");
260 return;
261 }
262
263 if (!validate_device()) {
264 IMG2NUM_LOG_INFO("Fatal: Could not get WebGPU Device.");
265 return;
266 }
267
268 queue = device.GetQueue();
269 gpu_initialized = true;
270 IMG2NUM_LOG_INFO("GPU Fully Initialized.");
271 };
272
273 ~GPU() {
274 device = nullptr;
275 adapter = nullptr;
276 queue = nullptr;
277 instance = nullptr;
278 gpu_initialized = false;
279 };
280};
281
282#endif
Definition gpu.h:21