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 }