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 }