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