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