1 /* 2 Copyright (c) 2019-2023 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 WGPUTextureFormat surfaceFormat = wgpuSurfaceGetPreferredFormat(surface, adapter); 179 writeln(surfaceFormat); 180 181 WGPUBlendState blend = { 182 color: { 183 srcFactor: WGPUBlendFactor.One, 184 dstFactor: WGPUBlendFactor.Zero, 185 operation: WGPUBlendOperation.Add 186 }, 187 alpha: { 188 srcFactor: WGPUBlendFactor.One, 189 dstFactor: WGPUBlendFactor.Zero, 190 operation: WGPUBlendOperation.Add 191 } 192 }; 193 WGPUColorTargetState cts = { 194 format: surfaceFormat, 195 blend: &blend, 196 writeMask: WGPUColorWriteMask.All 197 }; 198 WGPUFragmentState fs = { 199 module_: shaderModule, 200 entryPoint: "fs_main", 201 targetCount: 1, 202 targets: &cts 203 }; 204 WGPURenderPipelineDescriptor rpDesc = { 205 label: "Render pipeline", 206 layout: pipelineLayout, 207 vertex: { 208 module_: shaderModule, 209 entryPoint: "vs_main", 210 bufferCount: 0, 211 buffers: null 212 }, 213 primitive: { 214 topology: WGPUPrimitiveTopology.TriangleList, 215 stripIndexFormat: WGPUIndexFormat.Undefined, 216 frontFace: WGPUFrontFace.CCW, 217 cullMode: WGPUCullMode.None 218 }, 219 multisample: { 220 count: 1, 221 mask: ~0, 222 alphaToCoverageEnabled: false 223 }, 224 fragment: &fs, 225 depthStencil: null 226 }; 227 WGPURenderPipeline pipeline = wgpuDeviceCreateRenderPipeline(device, &rpDesc); 228 writeln("Render pipeline OK"); 229 230 void configureSurface(uint w, uint h) { 231 WGPUSurfaceConfiguration config = { 232 nextInChain: null, 233 device: device, 234 format: surfaceFormat, 235 usage: WGPUTextureUsage.RenderAttachment, 236 viewFormatCount: 0, 237 viewFormats: null, 238 alphaMode: WGPUCompositeAlphaMode.Auto, 239 width: w, 240 height: h, 241 presentMode: WGPUPresentMode.Fifo 242 }; 243 wgpuSurfaceConfigure(surface, &config); 244 } 245 246 configureSurface(winWidth, winHeight); 247 248 bool running = true; 249 while(running) 250 { 251 SDL_Event event; 252 while(SDL_PollEvent(&event)) 253 { 254 switch (event.type) 255 { 256 case SDL_WINDOWEVENT: 257 if (event.window.event == SDL_WINDOWEVENT_SIZE_CHANGED) 258 { 259 winWidth = event.window.data1; 260 winHeight = event.window.data2; 261 writeln("Resize: ", winWidth, "x", winHeight); 262 configureSurface(winWidth, winHeight); 263 } 264 break; 265 case SDL_KEYUP: 266 const key = event.key.keysym.scancode; 267 if (key == 41) // Esc 268 { 269 running = false; 270 } 271 break; 272 case SDL_QUIT: 273 running = false; 274 break; 275 default: 276 break; 277 } 278 } 279 280 // wgpu crashes when rendering to minimized window 281 auto winFlags = SDL_GetWindowFlags(sdlWindow); 282 auto isMinimized = winFlags & SDL_WINDOW_MINIMIZED; 283 if (isMinimized) 284 continue; 285 286 WGPUSurfaceTexture surfaceTexture; 287 wgpuSurfaceGetCurrentTexture(surface, &surfaceTexture); 288 if (surfaceTexture.status != WGPUSurfaceGetCurrentTextureStatus.Success) 289 continue; 290 291 WGPUTextureView nextTextureView = wgpuTextureCreateView(surfaceTexture.texture, null); 292 if (!nextTextureView) 293 continue; 294 295 296 WGPUCommandEncoderDescriptor ceDesc = { 297 label: "Command Encoder" 298 }; 299 WGPUCommandEncoder encoder = wgpuDeviceCreateCommandEncoder(device, &ceDesc); 300 301 WGPURenderPassColorAttachment colorAttachment = { 302 view: nextTextureView, 303 resolveTarget: null, 304 loadOp: WGPULoadOp.Clear, 305 storeOp: WGPUStoreOp.Store, 306 clearValue: WGPUColor(0.5, 0.5, 0.5, 1.0) 307 }; 308 WGPURenderPassDescriptor passDesc = { 309 colorAttachments: &colorAttachment, 310 colorAttachmentCount: 1, 311 depthStencilAttachment: null 312 }; 313 WGPURenderPassEncoder renderPass = wgpuCommandEncoderBeginRenderPass(encoder, &passDesc); 314 315 wgpuRenderPassEncoderSetPipeline(renderPass, pipeline); 316 wgpuRenderPassEncoderSetBindGroup(renderPass, 0, bindGroup, 0, null); 317 wgpuRenderPassEncoderDraw(renderPass, 3, 1, 0, 0); 318 wgpuRenderPassEncoderEnd(renderPass); 319 wgpuTextureViewRelease(nextTextureView); 320 321 WGPUQueue queue = wgpuDeviceGetQueue(device); 322 WGPUCommandBufferDescriptor cmdbufDesc = { label: null }; 323 WGPUCommandBuffer cmdBuffer = wgpuCommandEncoderFinish(encoder, &cmdbufDesc); 324 wgpuQueueSubmit(queue, 1, &cmdBuffer); 325 wgpuSurfacePresent(surface); 326 } 327 328 SDL_Quit(); 329 } 330 331 WGPUSurface createSurface(WGPUInstance instance, SDL_Window* window, SDL_SysWMinfo wmInfo) 332 { 333 WGPUSurface surface; 334 version(Windows) 335 { 336 if (wmInfo.subsystem == SDL_SYSWM_WINDOWS) 337 { 338 auto win_hwnd = wmInfo.info.win.window; 339 auto win_hinstance = wmInfo.info.win.hinstance; 340 WGPUSurfaceDescriptorFromWindowsHWND sfdHwnd = { 341 chain: { 342 next: null, 343 sType: WGPUSType.SurfaceDescriptorFromWindowsHWND 344 }, 345 hinstance: win_hinstance, 346 hwnd: win_hwnd 347 }; 348 WGPUSurfaceDescriptor sfd = { 349 label: null, 350 nextInChain: cast(const(WGPUChainedStruct)*)&sfdHwnd 351 }; 352 surface = wgpuInstanceCreateSurface(instance, &sfd); 353 } 354 else 355 { 356 quit("Unsupported subsystem, sorry"); 357 } 358 } 359 else version(linux) 360 { 361 if (wmInfo.subsystem == SDL_SYSWM_WAYLAND) 362 { 363 // TODO: support Wayland 364 quit("Unsupported subsystem, sorry"); 365 } 366 // System might use XCB so SDL_SysWMinfo will contain subsystem SDL_SYSWM_UNKNOWN. Although, X11 still can be used to create surface 367 else 368 { 369 auto x11_display = wmInfo.info.x11.display; 370 auto x11_window = wmInfo.info.x11.window; 371 WGPUSurfaceDescriptorFromXlibWindow sfdX11 = { 372 chain: { 373 next: null, 374 sType: WGPUSType.SurfaceDescriptorFromXlibWindow 375 }, 376 display: x11_display, 377 window: x11_window 378 }; 379 WGPUSurfaceDescriptor sfd = { 380 label: null, 381 nextInChain: cast(const(WGPUChainedStruct)*)&sfdX11 382 }; 383 surface = wgpuInstanceCreateSurface(instance, &sfd); 384 } 385 } 386 else version(OSX) 387 { 388 // Needs test! 389 SDL_Renderer* renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_PRESENTVSYNC); 390 auto metalLayer = SDL_RenderGetMetalLayer(renderer); 391 392 WGPUSurfaceDescriptorFromMetalLayer sfdMetal = { 393 chain: { 394 next: null, 395 sType: WGPUSType.SurfaceDescriptorFromMetalLayer 396 }, 397 layer: metalLayer 398 }; 399 WGPUSurfaceDescriptor sfd = { 400 label: null, 401 nextInChain: cast(const(WGPUChainedStruct)*)&sfdMetal 402 }; 403 surface = wgpuInstanceCreateSurface(instance, &sfd); 404 405 SDL_DestroyRenderer(renderer); 406 } 407 return surface; 408 } 409 410 extern(C) 411 { 412 void logCallback(WGPULogLevel level, const(char)* msg, void* user_data) 413 { 414 const(char)[] level_message; 415 switch(level) 416 { 417 case WGPULogLevel.Off: level_message = "off"; break; 418 case WGPULogLevel.Error: level_message = "error"; break; 419 case WGPULogLevel.Warn: level_message = "warn"; break; 420 case WGPULogLevel.Info: level_message = "info"; break; 421 case WGPULogLevel.Debug: level_message = "debug"; break; 422 case WGPULogLevel.Trace: level_message = "trace"; break; 423 default: level_message = "-"; break; 424 } 425 writeln("WebGPU ", level_message, ": ", to!string(msg)); 426 } 427 428 void requestAdapterCallback(WGPURequestAdapterStatus status, WGPUAdapter adapter, const(char)* message, void* userdata) 429 { 430 if (status == WGPURequestAdapterStatus.Success) 431 *cast(WGPUAdapter*)userdata = adapter; 432 else 433 { 434 writeln(status); 435 writeln(to!string(message)); 436 } 437 } 438 439 void requestDeviceCallback(WGPURequestDeviceStatus status, WGPUDevice device, const(char)* message, void* userdata) 440 { 441 if (status == WGPURequestDeviceStatus.Success) 442 *cast(WGPUDevice*)userdata = device; 443 else 444 { 445 writeln(status); 446 writeln(to!string(message)); 447 } 448 } 449 }