1 module texture;
2 
3 import example;
4 
5 import gfx.core;
6 import gfx.graal;
7 import gfx.window;
8 
9 import gl3n.linalg : mat4, mat3, vec3, vec4;
10 
11 import std.exception;
12 import std.stdio;
13 import std.typecons;
14 import std.math;
15 
16 class TextureExample : Example
17 {
18     Rc!RenderPass renderPass;
19     Rc!Pipeline pipeline;
20     Rc!PipelineLayout layout;
21     ushort[] indices;
22     Rc!Buffer vertBuf;
23     Rc!Buffer indBuf;
24     Rc!Buffer matBuf;
25     Rc!Buffer ligBuf;
26     Rc!Image texImg;
27     Rc!ImageView texView;
28     Rc!Sampler texSampler;
29     Rc!DescriptorPool descPool;
30     Rc!DescriptorSetLayout setLayout;
31     DescriptorSet set;
32 
33     struct Vertex {
34         float[3] position;
35         float[3] normal;
36         float[2] tex;
37     }
38 
39     struct Matrices {
40         float[4][4] mvp;
41         float[4][4] normal;
42     }
43 
44     enum maxLights = 5;
45 
46     struct Light {
47         float[4] direction;
48         float[4] color;
49     }
50 
51     struct Lights {
52         Light[maxLights] lights;
53         uint num;
54     }
55 
56     this(string[] args) {
57         super("Texture", args);
58     }
59 
60     override void dispose()
61     {
62         if (device) {
63             device.waitIdle();
64         }
65         vertBuf.unload();
66         indBuf.unload();
67         matBuf.unload();
68         ligBuf.unload();
69         texImg.unload();
70         texView.unload();
71         texSampler.unload();
72         setLayout.unload();
73         descPool.unload();
74         layout.unload();
75         pipeline.unload();
76         renderPass.unload();
77         super.dispose();
78     }
79 
80     override void prepare()
81     {
82         super.prepare();
83         prepareBuffers();
84         prepareTexture();
85         preparePipeline();
86         prepareDescriptorSet();
87     }
88 
89     void prepareBuffers()
90     {
91         import gfx.genmesh.cube : genCube;
92         import gfx.genmesh.algorithm : indexCollectMesh, triangulate, vertices;
93         import gfx.genmesh.poly : quad;
94         import std.algorithm : map;
95 
96         auto crate = genCube()
97                 .map!(f => quad(
98                     Vertex( f[0].p, f[0].n, [ 0f, 0f ] ),
99                     Vertex( f[1].p, f[1].n, [ 0f, 1f ] ),
100                     Vertex( f[2].p, f[2].n, [ 1f, 1f ] ),
101                     Vertex( f[3].p, f[3].n, [ 1f, 0f ] ),
102                 ))
103                 .triangulate()
104                 .vertices()
105                 .indexCollectMesh();
106 
107         auto normalize(in float[4] vec) {
108             return vec4(vec).normalized().vector;
109         }
110         const lights = Lights( [
111             Light(normalize([1.0, 1.0, -1.0, 0.0]),    [0.8, 0.5, 0.2, 1.0]),
112             Light(normalize([-1.0, 1.0, -1.0, 0.0]),    [0.2, 0.5, 0.8, 1.0]),
113             Light.init, Light.init, Light.init
114         ], 2);
115 
116         indices = crate.indices;
117         vertBuf = createStaticBuffer(crate.vertices, BufferUsage.vertex);
118         indBuf = createStaticBuffer(crate.indices, BufferUsage.index);
119 
120         matBuf = createDynamicBuffer(Matrices.sizeof, BufferUsage.uniform);
121         ligBuf = createStaticBuffer(lights, BufferUsage.uniform);
122     }
123 
124     void prepareTexture()
125     {
126         import img : ImageFormat, ImgImage = Image;
127         auto img = ImgImage.loadFromView!("crate.jpg")(ImageFormat.argb);
128         texImg = createTextureImage(
129             cast(const(void)[])img.data, ImageInfo.d2(img.width, img.height).withFormat(Format.rgba8_uNorm)
130         );
131         // argb swizzling
132         version(LittleEndian) {
133             const swizzle = Swizzle.bgra;
134         }
135         else {
136             const swizzle = Swizzle.argb;
137         }
138         texView = texImg.createView(ImageType.d2, ImageSubresourceRange(ImageAspect.color), swizzle);
139 
140         import gfx.core.typecons : some;
141 
142         texSampler = device.createSampler(SamplerInfo(
143             Filter.linear, Filter.linear, Filter.nearest,
144             [WrapMode.repeat, WrapMode.repeat, WrapMode.repeat],
145             some(16f), 0f, [0f, 0f]
146         ));
147     }
148 
149     override void prepareRenderPass()
150     {
151         const attachments = [
152             AttachmentDescription(swapchain.format, 1,
153                 AttachmentOps(LoadOp.clear, StoreOp.store),
154                 AttachmentOps(LoadOp.dontCare, StoreOp.dontCare),
155                 trans(ImageLayout.undefined, ImageLayout.presentSrc),
156                 No.mayAlias
157             )
158         ];
159         const subpasses = [
160             SubpassDescription(
161                 [], [ AttachmentRef(0, ImageLayout.colorAttachmentOptimal) ],
162                 none!AttachmentRef, []
163             )
164         ];
165 
166         renderPass = device.createRenderPass(attachments, subpasses, []);
167     }
168 
169     class TextureFrameData : FrameData
170     {
171         PrimaryCommandBuffer cmdBuf;
172         Rc!Framebuffer framebuffer;
173 
174         this(ImageBase swcColor, CommandBuffer tempBuf)
175         {
176             super(swcColor);
177             cmdBuf = cmdPool.allocatePrimary(1)[0];
178 
179             auto colorView = swcColor.createView(
180                 ImageType.d2, ImageSubresourceRange(ImageAspect.color), Swizzle.identity
181             ).rc;
182 
183             this.framebuffer = this.outer.device.createFramebuffer(this.outer.renderPass, [
184                 colorView.obj
185             ], size[0], size[1], 1);
186         }
187 
188         override void dispose()
189         {
190             framebuffer.unload();
191             cmdPool.free([ cast(CommandBuffer)cmdBuf ]);
192             super.dispose();
193         }
194     }
195 
196     void preparePipeline()
197     {
198         const spv = [
199             import("shader.vert.spv"), import("shader.frag.spv")
200         ];
201         auto vtxShader = device.createShaderModule(
202             cast(immutable(uint)[])spv[0], "main"
203         ).rc;
204         auto fragShader = device.createShaderModule(
205             cast(immutable(uint)[])spv[1], "main"
206         ).rc;
207 
208         const layoutBindings = [
209             PipelineLayoutBinding(0, DescriptorType.uniformBuffer, 1, ShaderStage.vertex),
210             PipelineLayoutBinding(1, DescriptorType.uniformBuffer, 1, ShaderStage.fragment),
211             PipelineLayoutBinding(2, DescriptorType.combinedImageSampler, 1, ShaderStage.fragment),
212         ];
213 
214         setLayout = device.createDescriptorSetLayout(layoutBindings);
215         layout = device.createPipelineLayout([ setLayout.obj ], []);
216 
217         PipelineInfo info;
218         info.shaders.vertex = vtxShader;
219         info.shaders.fragment = fragShader;
220         info.inputBindings = [
221             VertexInputBinding(0, Vertex.sizeof, No.instanced)
222         ];
223         info.inputAttribs = [
224             VertexInputAttrib(0, 0, Format.rgb32_sFloat, 0),
225             VertexInputAttrib(1, 0, Format.rgb32_sFloat, Vertex.normal.offsetof),
226             VertexInputAttrib(2, 0, Format.rg32_sFloat, Vertex.tex.offsetof),
227         ];
228         info.assembly = InputAssembly(Primitive.triangleList, No.primitiveRestart);
229         info.rasterizer = Rasterizer(
230             PolygonMode.fill, Cull.back, FrontFace.ccw, No.depthClamp,
231             none!DepthBias, 1f
232         );
233         info.viewports = [
234             ViewportConfig(
235                 Viewport(0, 0, cast(float)surfaceSize[0], cast(float)surfaceSize[1]),
236                 Rect(0, 0, surfaceSize[0], surfaceSize[1])
237             )
238         ];
239         info.blendInfo = ColorBlendInfo(
240             none!LogicOp, [
241                 ColorBlendAttachment(No.enabled,
242                     BlendState(trans(BlendFactor.one, BlendFactor.zero), BlendOp.add),
243                     BlendState(trans(BlendFactor.one, BlendFactor.zero), BlendOp.add),
244                     ColorMask.all
245                 )
246             ],
247             [ 0f, 0f, 0f, 0f ]
248         );
249         info.layout = layout;
250         info.renderPass = renderPass;
251         info.subpassIndex = 0;
252 
253         auto pls = device.createPipelines( [info] );
254         pipeline = pls[0];
255     }
256 
257     void prepareDescriptorSet()
258     {
259         const poolSizes = [
260             DescriptorPoolSize(DescriptorType.uniformBuffer, 2),
261             DescriptorPoolSize(DescriptorType.combinedImageSampler, 1)
262         ];
263         descPool = device.createDescriptorPool(1, poolSizes);
264         set = descPool.allocate([ setLayout.obj ])[0];
265 
266         auto writes = [
267             WriteDescriptorSet(set, 0, 0, DescriptorWrite.make(
268                 DescriptorType.uniformBuffer,
269                 matBuf.descriptor(),
270             )),
271 
272             WriteDescriptorSet(set, 1, 0, DescriptorWrite.make(
273                 DescriptorType.uniformBuffer,
274                 ligBuf.descriptor(),
275             )),
276 
277             WriteDescriptorSet(set, 2, 0, DescriptorWrite.make(
278                 DescriptorType.combinedImageSampler,
279                 texView.descriptorWithSampler(ImageLayout.undefined, texSampler),
280             )),
281         ];
282         device.updateDescriptorSets(writes, []);
283     }
284 
285     void updateMatrices(in Matrices mat)
286     {
287         auto mm = matBuf.boundMemory.map();
288         auto v = mm.view!(Matrices[])(0, 1);
289         v[0] = mat;
290         MappedMemorySet mms;
291         mm.addToSet(mms);
292         device.flushMappedMemory(mms);
293     }
294 
295     override FrameData makeFrameData(ImageBase swcColor, CommandBuffer tempBuf)
296     {
297         return new TextureFrameData(swcColor, tempBuf);
298     }
299 
300     override Submission[] recordCmds(FrameData frameData)
301     {
302         auto tfd = cast(TextureFrameData)frameData;
303 
304         const cv = ClearColorValues(0.6f, 0.6f, 0.6f, hasAlpha ? 0.5f : 1f);
305 
306         auto buf = tfd.cmdBuf;
307 
308         //buf.reset();
309         buf.begin(CommandBufferUsage.oneTimeSubmit);
310 
311         buf.beginRenderPass(
312             renderPass, tfd.framebuffer,
313             Rect(0, 0, surfaceSize[0], surfaceSize[1]), [ ClearValues(cv) ]
314         );
315 
316         buf.bindPipeline(pipeline);
317         buf.bindVertexBuffers(0, [ VertexBinding(vertBuf, 0) ]);
318         buf.bindIndexBuffer(indBuf, 0, IndexType.u16);
319         buf.bindDescriptorSets(PipelineBindPoint.graphics, layout, 0, [set], []);
320         buf.drawIndexed(cast(uint)indices.length, 1, 0, 0, 0);
321 
322         buf.endRenderPass();
323 
324         buf.end();
325 
326         return simpleSubmission([ buf ]);
327     }
328 
329 }
330 
331 /// correction matrix for the vulkan coordinate system
332 // (gl3n is made with opengl in mind)
333 mat4 correctionMatrix() pure
334 {
335     return mat4(
336         1f, 0f, 0f, 0f,
337         0f, -1f, 0f, 0f,
338         0f, 0f, 0.5f, 0.5f,
339         0f, 0f, 0f, 1f,
340     );
341 }
342 
343 int main(string[] args)
344 {
345     try {
346         auto example = new TextureExample(args);
347         example.prepare();
348         scope(exit) example.dispose();
349 
350         example.window.onKeyOn = (KeyEvent ev)
351         {
352             if (ev.sym == KeySym.escape) {
353                 example.window.closeFlag = true;
354             }
355         };
356 
357         // 6 RPM at 60 FPS
358         const puls = 6 * 2*PI / 3600f;
359         auto angle = 0f;
360         const view = mat4.look_at(vec3(0, -5, 3), vec3(0, 0, 0), vec3(0, 0, 1));
361         const proj = mat4.perspective(640, 480, 45, 1, 10);
362         const viewProj = correctionMatrix() * proj*view;
363 
364         while (!example.window.closeFlag) {
365             const model = mat4.rotation(angle, vec3(0, 0, 1));
366             const mvp = viewProj*model;
367             const normals = model.inverse().transposed();
368             angle += puls;
369 
370             example.updateMatrices( TextureExample.Matrices(
371                 mvp.transposed().matrix,
372                 normals.transposed().matrix
373             ) );
374 
375             example.render();
376             example.frameTick();
377             example.display.pollAndDispatch();
378         }
379 
380         return 0;
381     }
382     catch(Exception ex) {
383         stderr.writeln("error occured: ", ex.msg);
384         return 1;
385     }
386 }