1 /* 2 Copyright (c) 2019-2024 Timur Gafarov. 3 4 Boost Software License - Version 1.0 - August 17th, 2003 5 6 Permission is hereby granted, free of charge, to any person or organization 7 obtaining a copy of the software and accompanying documentation covered by 8 this license (the "Software") to use, reproduce, display, distribute, 9 execute, and transmit the Software, and to prepare derivative works of the 10 Software, and to permit third-parties to whom the Software is furnished to 11 do so, all subject to the following: 12 13 The copyright notices in the Software and this entire statement, including 14 the above license grant, this restriction and the following disclaimer, 15 must be included in all copies of the Software, in whole or in part, and 16 all derivative works of the Software, unless such copies or derivative 17 works are solely in the form of machine-executable object code generated by 18 a source language processor. 19 20 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 23 SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 24 FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 25 ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 26 DEALINGS IN THE SOFTWARE. 27 */ 28 module main; 29 30 import core.stdc.stdlib; 31 import std.stdio; 32 import std.conv; 33 import std.math; 34 import std.random; 35 import std.string; 36 import std.file: readText; 37 38 import bindbc.wgpu; 39 import bindbc.sdl; 40 import loader = bindbc.loader.sharedlib; 41 42 void quit(string message = "") 43 { 44 if (message.length) 45 writeln(message); 46 core.stdc.stdlib.exit(1); 47 } 48 49 void main(string[] args) 50 { 51 auto wgpuSupport = loadWGPU(); 52 writeln("wgpuSupport: ", wgpuSupport); 53 54 auto sdlSupport = loadSDL(); 55 writeln("sdlSupport: ", sdlSupport); 56 57 if (loader.errors.length) 58 { 59 writeln("Loader errors:"); 60 foreach(info; loader.errors) 61 { 62 writeln(to!string(info.error), ": ", to!string(info.message)); 63 } 64 } 65 66 version(OSX) 67 { 68 SDL_SetHint(SDL_HINT_RENDER_DRIVER, toStringz("metal")); 69 } 70 71 if (SDL_Init(SDL_INIT_EVERYTHING) != 0) 72 quit("Error: failed to init SDL: " ~ to!string(SDL_GetError())); 73 writeln("SDL OK"); 74 75 debug 76 { 77 WGPULogLevel logLevel = WGPULogLevel.Debug; // WGPULogLevel.Trace 78 } 79 else 80 { 81 WGPULogLevel logLevel = WGPULogLevel.Warn; 82 } 83 84 wgpuSetLogLevel(logLevel); 85 wgpuSetLogCallback(&logCallback, null); 86 writeln("Log OK"); 87 88 WGPUInstanceDescriptor instanceDesc; 89 WGPUInstance instance = wgpuCreateInstance(&instanceDesc); 90 writeln("Instance OK"); 91 92 uint winWidth = 1280; 93 uint winHeight = 720; 94 SDL_Window* sdlWindow = SDL_CreateWindow(toStringz("WGPU"), 95 SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 96 winWidth, winHeight, 97 SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE); 98 writeln("Window OK"); 99 100 SDL_SysWMinfo wmInfo; 101 SDL_VERSION(&wmInfo.version_); 102 if(SDL_GetWindowWMInfo(sdlWindow, &wmInfo) != SDL_TRUE) 103 { 104 quit("Error: failed to init SDL: " ~ to!string(SDL_GetError())); 105 } 106 writeln("Subsystem: ", wmInfo.subsystem); 107 WGPUSurface surface = createSurface(instance, sdlWindow, wmInfo); 108 109 WGPUAdapter adapter; 110 WGPURequestAdapterOptions adapterOpts = { 111 nextInChain: null, 112 compatibleSurface: surface 113 }; 114 wgpuInstanceRequestAdapter(instance, &adapterOpts, &requestAdapterCallback, cast(void*)&adapter); 115 writeln("Adapter OK"); 116 117 WGPUDevice device; 118 WGPUDeviceExtras deviceExtras = { 119 chain: { 120 next: null, 121 sType: cast(WGPUSType)WGPUNativeSType.DeviceExtras 122 }, 123 tracePath: null, 124 }; 125 WGPURequiredLimits limits = { 126 nextInChain: null, 127 limits: { 128 } 129 }; 130 WGPUDeviceDescriptor deviceDesc = { 131 nextInChain: cast(const(WGPUChainedStruct)*)&deviceExtras, 132 requiredFeatureCount: 0, 133 requiredFeatures: null, 134 requiredLimits: &limits 135 }; 136 137 wgpuAdapterRequestDevice(adapter, &deviceDesc, &requestDeviceCallback, cast(void*)&device); 138 writeln("Device OK"); 139 140 const(char)* shaderText = readText("data/shader.wgsl").toStringz; 141 WGPUShaderModuleWGSLDescriptor wgslDescriptor = { 142 chain: { 143 next: null, 144 sType: WGPUSType.ShaderModuleWGSLDescriptor 145 }, 146 code: shaderText 147 }; 148 WGPUShaderModuleDescriptor shaderSource = { 149 nextInChain: cast(const(WGPUChainedStruct)*)&wgslDescriptor, 150 label: toStringz("shader.wgsl") 151 }; 152 WGPUShaderModule shaderModule = wgpuDeviceCreateShaderModule(device, &shaderSource); 153 writeln("Shader OK"); 154 155 WGPUBindGroupLayoutDescriptor bglDesc = { 156 label: "bind group layout", 157 entries: null, 158 entryCount: 0 159 }; 160 WGPUBindGroupLayout bindGroupLayout = wgpuDeviceCreateBindGroupLayout(device, &bglDesc); 161 WGPUBindGroupDescriptor bgDesc = { 162 label: "bind group", 163 layout: bindGroupLayout, 164 entries: null, 165 entryCount: 0 166 }; 167 WGPUBindGroup bindGroup = wgpuDeviceCreateBindGroup(device, &bgDesc); 168 WGPUBindGroupLayout[1] bindGroupLayouts = [ bindGroupLayout ]; 169 writeln("Bind group OK"); 170 171 WGPUPipelineLayoutDescriptor plDesc = { 172 bindGroupLayouts: bindGroupLayouts.ptr, 173 bindGroupLayoutCount: bindGroupLayouts.length 174 }; 175 WGPUPipelineLayout pipelineLayout = wgpuDeviceCreatePipelineLayout(device, &plDesc); 176 writeln("Pipeline layout OK"); 177 178 WGPUSurfaceCapabilities surfaceCapabilities; 179 wgpuSurfaceGetCapabilities(surface, adapter, &surfaceCapabilities); 180 WGPUTextureFormat surfaceFormat = surfaceCapabilities.formats[0]; 181 writeln(surfaceFormat); 182 183 WGPUBlendState blend = { 184 color: { 185 srcFactor: WGPUBlendFactor.One, 186 dstFactor: WGPUBlendFactor.Zero, 187 operation: WGPUBlendOperation.Add 188 }, 189 alpha: { 190 srcFactor: WGPUBlendFactor.One, 191 dstFactor: WGPUBlendFactor.Zero, 192 operation: WGPUBlendOperation.Add 193 } 194 }; 195 WGPUColorTargetState cts = { 196 format: surfaceFormat, 197 blend: &blend, 198 writeMask: WGPUColorWriteMask.All 199 }; 200 WGPUFragmentState fs = { 201 module_: shaderModule, 202 entryPoint: "fs_main", 203 targetCount: 1, 204 targets: &cts 205 }; 206 WGPURenderPipelineDescriptor rpDesc = { 207 label: "Render pipeline", 208 layout: pipelineLayout, 209 vertex: { 210 module_: shaderModule, 211 entryPoint: "vs_main", 212 bufferCount: 0, 213 buffers: null 214 }, 215 primitive: { 216 topology: WGPUPrimitiveTopology.TriangleList, 217 stripIndexFormat: WGPUIndexFormat.Undefined, 218 frontFace: WGPUFrontFace.CCW, 219 cullMode: WGPUCullMode.None 220 }, 221 multisample: { 222 count: 1, 223 mask: ~0, 224 alphaToCoverageEnabled: false 225 }, 226 fragment: &fs, 227 depthStencil: null 228 }; 229 WGPURenderPipeline pipeline = wgpuDeviceCreateRenderPipeline(device, &rpDesc); 230 writeln("Render pipeline OK"); 231 232 void configureSurface(uint w, uint h) { 233 WGPUSurfaceConfiguration config = { 234 nextInChain: null, 235 device: device, 236 format: surfaceFormat, 237 usage: WGPUTextureUsage.RenderAttachment, 238 viewFormatCount: 0, 239 viewFormats: null, 240 alphaMode: WGPUCompositeAlphaMode.Auto, 241 width: w, 242 height: h, 243 presentMode: WGPUPresentMode.Fifo 244 }; 245 wgpuSurfaceConfigure(surface, &config); 246 } 247 248 configureSurface(winWidth, winHeight); 249 250 bool running = true; 251 while(running) 252 { 253 SDL_Event event; 254 while(SDL_PollEvent(&event)) 255 { 256 switch (event.type) 257 { 258 case SDL_WINDOWEVENT: 259 if (event.window.event == SDL_WINDOWEVENT_SIZE_CHANGED) 260 { 261 winWidth = event.window.data1; 262 winHeight = event.window.data2; 263 writeln("Resize: ", winWidth, "x", winHeight); 264 configureSurface(winWidth, winHeight); 265 } 266 break; 267 case SDL_KEYUP: 268 const key = event.key.keysym.scancode; 269 if (key == 41) // Esc 270 { 271 running = false; 272 } 273 break; 274 case SDL_QUIT: 275 running = false; 276 break; 277 default: 278 break; 279 } 280 } 281 282 // wgpu crashes when rendering to minimized window 283 auto winFlags = SDL_GetWindowFlags(sdlWindow); 284 auto isMinimized = winFlags & SDL_WINDOW_MINIMIZED; 285 if (isMinimized) 286 continue; 287 288 WGPUSurfaceTexture surfaceTexture; 289 wgpuSurfaceGetCurrentTexture(surface, &surfaceTexture); 290 if (surfaceTexture.status != WGPUSurfaceGetCurrentTextureStatus.Success) 291 continue; 292 293 WGPUTextureView nextTextureView = wgpuTextureCreateView(surfaceTexture.texture, null); 294 if (!nextTextureView) 295 continue; 296 297 298 WGPUCommandEncoderDescriptor ceDesc = { 299 label: "Command Encoder" 300 }; 301 WGPUCommandEncoder encoder = wgpuDeviceCreateCommandEncoder(device, &ceDesc); 302 303 WGPURenderPassColorAttachment colorAttachment = { 304 view: nextTextureView, 305 depthSlice: WGPU_DEPTH_SLICE_UNDEFINED, 306 resolveTarget: null, 307 loadOp: WGPULoadOp.Clear, 308 storeOp: WGPUStoreOp.Store, 309 clearValue: WGPUColor(0.5, 0.5, 0.5, 1.0) 310 }; 311 WGPURenderPassDescriptor passDesc = { 312 colorAttachments: &colorAttachment, 313 colorAttachmentCount: 1, 314 depthStencilAttachment: null 315 }; 316 WGPURenderPassEncoder renderPass = wgpuCommandEncoderBeginRenderPass(encoder, &passDesc); 317 318 wgpuRenderPassEncoderSetPipeline(renderPass, pipeline); 319 wgpuRenderPassEncoderSetBindGroup(renderPass, 0, bindGroup, 0, null); 320 wgpuRenderPassEncoderDraw(renderPass, 3, 1, 0, 0); 321 wgpuRenderPassEncoderEnd(renderPass); 322 wgpuRenderPassEncoderRelease(renderPass); 323 324 WGPUQueue queue = wgpuDeviceGetQueue(device); 325 WGPUCommandBufferDescriptor cmdbufDesc = { label: null }; 326 WGPUCommandBuffer cmdBuffer = wgpuCommandEncoderFinish(encoder, &cmdbufDesc); 327 328 wgpuQueueSubmit(queue, 1, &cmdBuffer); 329 330 wgpuSurfacePresent(surface); 331 332 wgpuCommandBufferRelease(cmdBuffer); 333 wgpuCommandEncoderRelease(encoder); 334 wgpuTextureViewRelease(nextTextureView); 335 wgpuTextureRelease(surfaceTexture.texture); 336 } 337 338 SDL_Quit(); 339 } 340 341 WGPUSurface createSurface(WGPUInstance instance, SDL_Window* window, SDL_SysWMinfo wmInfo) 342 { 343 WGPUSurface surface; 344 version(Windows) 345 { 346 if (wmInfo.subsystem == SDL_SYSWM_WINDOWS) 347 { 348 auto win_hwnd = wmInfo.info.win.window; 349 auto win_hinstance = wmInfo.info.win.hinstance; 350 WGPUSurfaceDescriptorFromWindowsHWND sfdHwnd = { 351 chain: { 352 next: null, 353 sType: WGPUSType.SurfaceDescriptorFromWindowsHWND 354 }, 355 hinstance: win_hinstance, 356 hwnd: win_hwnd 357 }; 358 WGPUSurfaceDescriptor sfd = { 359 label: null, 360 nextInChain: cast(const(WGPUChainedStruct)*)&sfdHwnd 361 }; 362 surface = wgpuInstanceCreateSurface(instance, &sfd); 363 } 364 else 365 { 366 quit("Unsupported subsystem, sorry"); 367 } 368 } 369 else version(linux) 370 { 371 if (wmInfo.subsystem == SDL_SYSWM_WAYLAND) 372 { 373 // TODO: support Wayland 374 quit("Unsupported subsystem, sorry"); 375 } 376 // System might use XCB so SDL_SysWMinfo will contain subsystem SDL_SYSWM_UNKNOWN. Although, X11 still can be used to create surface 377 else 378 { 379 auto x11_display = wmInfo.info.x11.display; 380 auto x11_window = wmInfo.info.x11.window; 381 WGPUSurfaceDescriptorFromXlibWindow sfdX11 = { 382 chain: { 383 next: null, 384 sType: WGPUSType.SurfaceDescriptorFromXlibWindow 385 }, 386 display: x11_display, 387 window: x11_window 388 }; 389 WGPUSurfaceDescriptor sfd = { 390 label: null, 391 nextInChain: cast(const(WGPUChainedStruct)*)&sfdX11 392 }; 393 surface = wgpuInstanceCreateSurface(instance, &sfd); 394 } 395 } 396 else version(OSX) 397 { 398 // Needs test! 399 SDL_Renderer* renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_PRESENTVSYNC); 400 auto metalLayer = SDL_RenderGetMetalLayer(renderer); 401 402 WGPUSurfaceDescriptorFromMetalLayer sfdMetal = { 403 chain: { 404 next: null, 405 sType: WGPUSType.SurfaceDescriptorFromMetalLayer 406 }, 407 layer: metalLayer 408 }; 409 WGPUSurfaceDescriptor sfd = { 410 label: null, 411 nextInChain: cast(const(WGPUChainedStruct)*)&sfdMetal 412 }; 413 surface = wgpuInstanceCreateSurface(instance, &sfd); 414 415 SDL_DestroyRenderer(renderer); 416 } 417 return surface; 418 } 419 420 extern(C) 421 { 422 void logCallback(WGPULogLevel level, const(char)* msg, void* user_data) 423 { 424 const(char)[] level_message; 425 switch(level) 426 { 427 case WGPULogLevel.Off: level_message = "off"; break; 428 case WGPULogLevel.Error: level_message = "error"; break; 429 case WGPULogLevel.Warn: level_message = "warn"; break; 430 case WGPULogLevel.Info: level_message = "info"; break; 431 case WGPULogLevel.Debug: level_message = "debug"; break; 432 case WGPULogLevel.Trace: level_message = "trace"; break; 433 default: level_message = "-"; break; 434 } 435 writeln("WebGPU ", level_message, ": ", to!string(msg)); 436 } 437 438 void requestAdapterCallback(WGPURequestAdapterStatus status, WGPUAdapter adapter, const(char)* message, void* userdata) 439 { 440 if (status == WGPURequestAdapterStatus.Success) 441 *cast(WGPUAdapter*)userdata = adapter; 442 else 443 { 444 writeln(status); 445 writeln(to!string(message)); 446 } 447 } 448 449 void requestDeviceCallback(WGPURequestDeviceStatus status, WGPUDevice device, const(char)* message, void* userdata) 450 { 451 if (status == WGPURequestDeviceStatus.Success) 452 *cast(WGPUDevice*)userdata = device; 453 else 454 { 455 writeln(status); 456 writeln(to!string(message)); 457 } 458 } 459 }