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 }