1 /* 2 Copyright (c) 2019-2022 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) == -1) 72 quit("Error: failed to init SDL: " ~ to!string(SDL_GetError())); 73 74 debug 75 { 76 WGPULogLevel logLevel = WGPULogLevel.Debug; // WGPULogLevel.Trace 77 } 78 else 79 { 80 WGPULogLevel logLevel = WGPULogLevel.Warn; 81 } 82 83 wgpuSetLogLevel(logLevel); 84 wgpuSetLogCallback(&logCallback, null); 85 86 WGPUInstanceDescriptor instanceDesc; 87 WGPUInstance instance = wgpuCreateInstance(&instanceDesc); 88 89 uint winWidth = 1280; 90 uint winHeight = 720; 91 SDL_Window* sdlWindow = SDL_CreateWindow(toStringz("WGPU"), 92 SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 93 winWidth, winHeight, 94 SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE); 95 96 SDL_SysWMinfo wmInfo; 97 SDL_GetWindowWMInfo(sdlWindow, &wmInfo); 98 writeln("Subsystem: ", wmInfo.subsystem); 99 WGPUSurface surface = createSurface(instance, wmInfo); 100 101 WGPUAdapter adapter; 102 WGPURequestAdapterOptions adapterOpts = { 103 nextInChain: null, 104 compatibleSurface: surface 105 }; 106 wgpuInstanceRequestAdapter(instance, &adapterOpts, &requestAdapterCallback, cast(void*)&adapter); 107 writeln("Adapter OK"); 108 109 WGPUDevice device; 110 WGPUDeviceExtras deviceExtras = { 111 chain: { 112 next: null, 113 sType: cast(WGPUSType)WGPUNativeSType.DeviceExtras 114 }, 115 // nativeFeatures: WGPUNativeFeature.TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES, 116 // label: "Device", 117 tracePath: null, 118 }; 119 WGPURequiredLimits limits = { 120 nextInChain: null, 121 limits: { 122 maxBindGroups: 1, 123 124 // Why are these necessary under Windows? 125 minUniformBufferOffsetAlignment: 256, 126 minStorageBufferOffsetAlignment: 256, 127 maxInterStageShaderComponents: 60, 128 maxInterStageShaderVariables: 16 129 } 130 }; 131 WGPUDeviceDescriptor deviceDesc = { 132 nextInChain: cast(const(WGPUChainedStruct)*)&deviceExtras, 133 requiredFeaturesCount: 0, 134 requiredFeatures: null, 135 requiredLimits: &limits 136 }; 137 138 wgpuAdapterRequestDevice(adapter, &deviceDesc, &requestDeviceCallback, cast(void*)&device); 139 writeln("Device OK"); 140 141 const(char)* shaderText = readText("data/shader.wgsl").toStringz; 142 WGPUShaderModuleWGSLDescriptor wgslDescriptor = { 143 chain: { 144 next: null, 145 sType: WGPUSType.ShaderModuleWGSLDescriptor 146 }, 147 code: shaderText 148 }; 149 WGPUShaderModuleDescriptor shaderSource = { 150 nextInChain: cast(const(WGPUChainedStruct)*)&wgslDescriptor, 151 label: toStringz("shader.wgsl") 152 }; 153 WGPUShaderModule shaderModule = wgpuDeviceCreateShaderModule(device, &shaderSource); 154 writeln("Shader OK"); 155 156 WGPUBindGroupLayoutDescriptor bglDesc = { 157 label: "bind group layout", 158 entries: null, 159 entryCount: 0 160 }; 161 WGPUBindGroupLayout bindGroupLayout = wgpuDeviceCreateBindGroupLayout(device, &bglDesc); 162 WGPUBindGroupDescriptor bgDesc = { 163 label: "bind group", 164 layout: bindGroupLayout, 165 entries: null, 166 entryCount: 0 167 }; 168 WGPUBindGroup bindGroup = wgpuDeviceCreateBindGroup(device, &bgDesc); 169 WGPUBindGroupLayout[1] bindGroupLayouts = [ bindGroupLayout ]; 170 writeln("Bind group OK"); 171 172 WGPUPipelineLayoutDescriptor plDesc = { 173 bindGroupLayouts: bindGroupLayouts.ptr, 174 bindGroupLayoutCount: bindGroupLayouts.length 175 }; 176 WGPUPipelineLayout pipelineLayout = wgpuDeviceCreatePipelineLayout(device, &plDesc); 177 writeln("Pipeline layout OK"); 178 179 WGPUTextureFormat swapChainFormat = wgpuSurfaceGetPreferredFormat(surface, adapter); 180 writeln(swapChainFormat); 181 182 WGPUBlendState blend = { 183 color: { 184 srcFactor: WGPUBlendFactor.One, 185 dstFactor: WGPUBlendFactor.Zero, 186 operation: WGPUBlendOperation.Add 187 }, 188 alpha: { 189 srcFactor: WGPUBlendFactor.One, 190 dstFactor: WGPUBlendFactor.Zero, 191 operation: WGPUBlendOperation.Add 192 } 193 }; 194 WGPUColorTargetState cts = { 195 format: swapChainFormat, 196 blend: &blend, 197 writeMask: WGPUColorWriteMask.All 198 }; 199 WGPUFragmentState fs = { 200 module_: shaderModule, 201 entryPoint: "fs_main", 202 targetCount: 1, 203 targets: &cts 204 }; 205 WGPURenderPipelineDescriptor rpDesc = { 206 label: "Render pipeline", 207 layout: pipelineLayout, 208 vertex: { 209 module_: shaderModule, 210 entryPoint: "vs_main", 211 bufferCount: 0, 212 buffers: null 213 }, 214 primitive: { 215 topology: WGPUPrimitiveTopology.TriangleList, 216 stripIndexFormat: WGPUIndexFormat.Undefined, 217 frontFace: WGPUFrontFace.CCW, 218 cullMode: WGPUCullMode.None 219 }, 220 multisample: { 221 count: 1, 222 mask: ~0, 223 alphaToCoverageEnabled: false 224 }, 225 fragment: &fs, 226 depthStencil: null 227 }; 228 WGPURenderPipeline pipeline = wgpuDeviceCreateRenderPipeline(device, &rpDesc); 229 writeln("Render pipeline OK"); 230 231 WGPUSwapChain createSwapChain(uint w, uint h) { 232 WGPUSwapChainDescriptorExtras swcDescExtras = { 233 chain: { 234 next: null, 235 sType: cast(WGPUSType)WGPUNativeSType.SwapChainDescriptorExtras 236 }, 237 alphaMode: WGPUCompositeAlphaMode.Auto, 238 viewFormatCount: 0, 239 viewFormats: null 240 }; 241 242 WGPUSwapChainDescriptor swcDesc = { 243 nextInChain: cast(WGPUChainedStruct*)&swcDescExtras, 244 usage: WGPUTextureUsage.RenderAttachment, 245 format: swapChainFormat, 246 width: w, 247 height: h, 248 presentMode: WGPUPresentMode.Fifo 249 }; 250 return wgpuDeviceCreateSwapChain(device, surface, &swcDesc); 251 } 252 253 WGPUSwapChain swapChain = createSwapChain(winWidth, winHeight); 254 255 bool running = true; 256 while(running) 257 { 258 SDL_Event event; 259 while(SDL_PollEvent(&event)) 260 { 261 switch (event.type) 262 { 263 case SDL_WINDOWEVENT: 264 if (event.window.event == SDL_WINDOWEVENT_SIZE_CHANGED) 265 { 266 winWidth = event.window.data1; 267 winHeight = event.window.data2; 268 writeln(winWidth, "x", winHeight); 269 swapChain = createSwapChain(winWidth, winHeight); 270 } 271 break; 272 case SDL_KEYUP: 273 const key = event.key.keysym.scancode; 274 if (key == 41) // Esc 275 { 276 running = false; 277 } 278 break; 279 case SDL_QUIT: 280 running = false; 281 break; 282 default: 283 break; 284 } 285 } 286 287 // wgpu crashes when rendering to minimized window 288 auto winFlags = SDL_GetWindowFlags(sdlWindow); 289 auto isMinimized = winFlags & SDL_WINDOW_MINIMIZED; 290 if (isMinimized) 291 continue; 292 293 WGPUTextureView nextTextureView = wgpuSwapChainGetCurrentTextureView(swapChain); 294 if (!nextTextureView) 295 continue; 296 297 WGPUCommandEncoderDescriptor ceDesc = { 298 label: "Command Encoder" 299 }; 300 WGPUCommandEncoder encoder = wgpuDeviceCreateCommandEncoder(device, &ceDesc); 301 302 WGPURenderPassColorAttachment colorAttachment = { 303 view: nextTextureView, 304 resolveTarget: null, 305 loadOp: WGPULoadOp.Clear, 306 storeOp: WGPUStoreOp.Store, 307 clearValue: WGPUColor(0.5, 0.5, 0.5, 1.0) 308 }; 309 WGPURenderPassDescriptor passDesc = { 310 colorAttachments: &colorAttachment, 311 colorAttachmentCount: 1, 312 depthStencilAttachment: null 313 }; 314 WGPURenderPassEncoder renderPass = wgpuCommandEncoderBeginRenderPass(encoder, &passDesc); 315 316 wgpuRenderPassEncoderSetPipeline(renderPass, pipeline); 317 wgpuRenderPassEncoderSetBindGroup(renderPass, 0, bindGroup, 0, null); 318 wgpuRenderPassEncoderDraw(renderPass, 3, 1, 0, 0); 319 wgpuRenderPassEncoderEnd(renderPass); 320 wgpuTextureViewDrop(nextTextureView); 321 322 WGPUQueue queue = wgpuDeviceGetQueue(device); 323 WGPUCommandBufferDescriptor cmdbufDesc = { label: null }; 324 WGPUCommandBuffer cmdBuffer = wgpuCommandEncoderFinish(encoder, &cmdbufDesc); 325 wgpuQueueSubmit(queue, 1, &cmdBuffer); 326 wgpuSwapChainPresent(swapChain); 327 } 328 329 SDL_Quit(); 330 } 331 332 WGPUSurface createSurface(WGPUInstance instance, SDL_SysWMinfo wmInfo) 333 { 334 WGPUSurface surface; 335 version(Windows) 336 { 337 if (wmInfo.subsystem == SDL_SYSWM_WINDOWS) 338 { 339 auto win_hwnd = wmInfo.info.win.window; 340 auto win_hinstance = wmInfo.info.win.hinstance; 341 WGPUSurfaceDescriptorFromWindowsHWND sfdHwnd = { 342 chain: { 343 next: null, 344 sType: WGPUSType.SurfaceDescriptorFromWindowsHWND 345 }, 346 hinstance: win_hinstance, 347 hwnd: win_hwnd 348 }; 349 WGPUSurfaceDescriptor sfd = { 350 label: null, 351 nextInChain: cast(const(WGPUChainedStruct)*)&sfdHwnd 352 }; 353 surface = wgpuInstanceCreateSurface(instance, &sfd); 354 } 355 else 356 { 357 quit("Unsupported subsystem, sorry"); 358 } 359 } 360 else version(linux) 361 { 362 // Needs test! 363 // System might use XCB so SDL_SysWMinfo will contain subsystem SDL_SYSWM_UNKNOWN. Although, X11 still can be used to create surface 364 if (wmInfo.subsystem == SDL_SYSWM_WAYLAND) 365 { 366 // TODO: support Wayland 367 quit("Unsupported subsystem, sorry"); 368 } 369 else 370 { 371 auto x11_display = wmInfo.info.x11.display; 372 auto x11_window = wmInfo.info.x11.window; 373 WGPUSurfaceDescriptorFromXlibWindow sfdX11 = { 374 chain: { 375 next: null, 376 sType: WGPUSType.SurfaceDescriptorFromXlibWindow 377 }, 378 display: x11_display, 379 window: x11_window 380 }; 381 WGPUSurfaceDescriptor sfd = { 382 label: null, 383 nextInChain: cast(const(WGPUChainedStruct)*)&sfdX11 384 }; 385 surface = wgpuInstanceCreateSurface(instance, &sfd); 386 } 387 } 388 else version(OSX) 389 { 390 // Needs test! 391 SDL_Renderer* renderer = SDL_CreateRenderer(window.sdlWindow, -1, SDL_RENDERER_PRESENTVSYNC); 392 auto metalLayer = SDL_RenderGetMetalLayer(renderer); 393 394 WGPUSurfaceDescriptorFromMetalLayer sfdMetal = { 395 chain: { 396 next: null, 397 sType: WGPUSType.SurfaceDescriptorFromMetalLayer 398 }, 399 layer: metalLayer 400 }; 401 WGPUSurfaceDescriptor sfd = { 402 label: null, 403 nextInChain: cast(const(WGPUChainedStruct)*)&sfdMetal 404 }; 405 surface = wgpuInstanceCreateSurface(instance, &sfd); 406 407 SDL_DestroyRenderer(renderer); 408 } 409 return surface; 410 } 411 412 extern(C) 413 { 414 void logCallback(WGPULogLevel level, const(char)* msg, void* user_data) 415 { 416 const (char)[] level_message; 417 switch(level) 418 { 419 case WGPULogLevel.Off:level_message = "off";break; 420 case WGPULogLevel.Error:level_message = "error";break; 421 case WGPULogLevel.Warn:level_message = "warn";break; 422 case WGPULogLevel.Info:level_message = "info";break; 423 case WGPULogLevel.Debug:level_message = "debug";break; 424 case WGPULogLevel.Trace:level_message = "trace";break; 425 default: level_message = "-"; 426 } 427 writeln("WebGPU ", level_message, ": ", to!string(msg)); 428 } 429 430 void requestAdapterCallback(WGPURequestAdapterStatus status, WGPUAdapter adapter, const(char)* message, void* userdata) 431 { 432 if (status == WGPURequestAdapterStatus.Success) 433 *cast(WGPUAdapter*)userdata = adapter; 434 else 435 { 436 writeln(status); 437 writeln(to!string(message)); 438 } 439 } 440 441 void requestDeviceCallback(WGPURequestDeviceStatus status, WGPUDevice device, const(char)* message, void* userdata) 442 { 443 if (status == WGPURequestDeviceStatus.Success) 444 *cast(WGPUDevice*)userdata = device; 445 else 446 { 447 writeln(status); 448 writeln(to!string(message)); 449 } 450 } 451 }