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