1 module stencil;
2 
3 import example;
4 
5 import gfx.core;
6 import gfx.graal;
7 import gfx.window;
8 
9 import std.exception;
10 import std.stdio;
11 import std.typecons;
12 
13 class StencilExample : Example
14 {
15     Rc!RenderPass renderPass;
16     Rc!Pipeline stencilWritePipeline;
17     Rc!DescriptorSetLayout stencilWriteDSL;
18     Rc!PipelineLayout stencilWriteLayout;
19     DescriptorSet descriptorSet;
20     Rc!DescriptorPool descPool;
21 
22     Rc!Pipeline solidPipeline;
23 
24     Rc!Buffer vertBuf;
25     Rc!Buffer indBuf;
26     Rc!Image chessboard;
27     Rc!ImageView chessboardView;
28     Rc!Sampler sampler;
29 
30     struct VertexP2T2 {
31         float[2] position;
32         float[2] texCoord;
33     }
34     immutable square = [
35         VertexP2T2([-1.0,  1.0], [0.0, 1.0]),
36         VertexP2T2([-1.0, -1.0], [0.0, 0.0]),
37         VertexP2T2([ 1.0, -1.0], [1.0, 0.0]),
38         VertexP2T2([ 1.0,  1.0], [1.0, 1.0]),
39     ];
40     immutable squareLen = square.length * VertexP2T2.sizeof;
41     immutable ushort[] squareIndices = [
42         0, 1, 2,    0, 2, 3
43     ];
44 
45 
46     struct VertexP2C3 {
47         float[2] position;
48         float[3] color;
49     }
50     immutable triangle = [
51         VertexP2C3([-1.0,  1.0], [1.0, 0.0, 0.0]),
52         VertexP2C3([ 1.0,  1.0], [0.0, 1.0, 0.0]),
53         VertexP2C3([ 0.0, -1.0], [0.0, 0.0, 1.0]),
54     ];
55     immutable triangleLen = triangle.length * VertexP2C3.sizeof;
56 
57     this(string[] args) {
58         super("Stencil", args);
59     }
60 
61     override void dispose() {
62         if (device) {
63             device.waitIdle();
64         }
65         renderPass.unload();
66         stencilWritePipeline.unload();
67         stencilWriteDSL.unload();
68         stencilWriteLayout.unload();
69         descPool.unload();
70 
71         solidPipeline.unload();
72 
73         vertBuf.unload();
74         indBuf.unload();
75         chessboard.unload();
76         chessboardView.unload();
77         sampler.unload();
78 
79         super.dispose();
80     }
81 
82     override void prepare()
83     {
84         super.prepare();
85         prepareChessboard();
86         prepareBuffer();
87         preparePipeline();
88         prepareDescriptorSet();
89     }
90 
91     void prepareChessboard()
92     {
93         auto data = new ubyte[32*32];
94         foreach (r; 0 .. 32) {
95             foreach (c; 0 .. 32) {
96                 immutable oddR = (r/4)%2 != 0;
97                 immutable oddC = (c/4)%2 != 0;
98                 data[r*32 + c] = oddR == oddC ? 0xff : 0x00;
99             }
100         }
101         chessboard = createTextureImage(
102             cast(const(void)[])data, ImageInfo.d2(32, 32).withFormat(Format.r8_uNorm)
103         );
104         chessboardView = chessboard.createView(
105             ImageType.d2, ImageSubresourceRange(ImageAspect.color), Swizzle.identity
106         );
107         import gfx.core.typecons : none;
108         sampler = device.createSampler(SamplerInfo(
109             Filter.nearest, Filter.nearest, Filter.nearest,
110             [WrapMode.repeat, WrapMode.repeat, WrapMode.repeat],
111             none!float, 0f, [0f, 0f]
112         ));
113     }
114 
115     void prepareBuffer()
116     {
117         auto data = new ubyte[squareLen + triangleLen];
118         data[0 .. squareLen] = cast(immutable(ubyte)[])square;
119         data[squareLen .. squareLen+triangleLen] = cast(immutable(ubyte[]))triangle;
120         vertBuf = createStaticBuffer(data, BufferUsage.vertex);
121 
122         indBuf = createStaticBuffer(squareIndices, BufferUsage.index);
123     }
124 
125     override void prepareRenderPass()
126     {
127         const attachments = [
128             AttachmentDescription(
129                 swapchain.format, 1,
130                 AttachmentOps(LoadOp.clear, StoreOp.store),
131                 AttachmentOps(LoadOp.dontCare, StoreOp.dontCare),
132                 trans(ImageLayout.undefined, ImageLayout.presentSrc),
133                 No.mayAlias
134             ),
135             AttachmentDescription(
136                 findStencilFormat(), 1,
137                 AttachmentOps(LoadOp.dontCare, StoreOp.dontCare),
138                 AttachmentOps(LoadOp.dontCare, StoreOp.dontCare),
139                 trans(ImageLayout.undefined, ImageLayout.depthStencilAttachmentOptimal),
140                 No.mayAlias
141             ),
142         ];
143         const subpasses = [
144             // subpass 1: write stencil buffer from texture
145             // subpass 2: render with stencil masking
146             SubpassDescription(
147                 [], [], some(AttachmentRef(1, ImageLayout.depthStencilAttachmentOptimal)), []
148             ),
149             SubpassDescription(
150                 [], [ AttachmentRef(0, ImageLayout.colorAttachmentOptimal) ],
151                 some(AttachmentRef(1, ImageLayout.depthStencilAttachmentOptimal)), []
152             )
153         ];
154         const dependencies = [
155             SubpassDependency(
156                 trans!uint(0, 1),
157                 trans(PipelineStage.lateFragmentTests, PipelineStage.earlyFragmentTests),
158                 trans(Access.depthStencilAttachmentWrite, Access.depthStencilAttachmentRead)
159             )
160         ];
161 
162         renderPass = device.createRenderPass(attachments, subpasses, dependencies);
163     }
164 
165     class StencilFrameData : FrameData
166     {
167         PrimaryCommandBuffer cmdBuf;
168         Rc!Image stencil;
169         Rc!Framebuffer framebuffer;
170 
171         this(ImageBase swcColor, CommandBuffer tempBuf)
172         {
173             super(swcColor);
174             cmdBuf = cmdPool.allocatePrimary(1)[0];
175             stencil = this.outer.createStencilImage(size[0], size[1]);
176 
177             auto colorView = swcColor.createView(
178                 ImageType.d2, ImageSubresourceRange(ImageAspect.color), Swizzle.identity
179             ).rc;
180             auto stencilView = stencil.createView(
181                 ImageType.d2, ImageSubresourceRange(ImageAspect.stencil), Swizzle.identity
182             ).rc;
183 
184             this.framebuffer = this.outer.device.createFramebuffer(this.outer.renderPass, [
185                 colorView.obj, stencilView.obj
186             ], size[0], size[1], 1);
187         }
188 
189         override void dispose()
190         {
191             framebuffer.unload();
192             stencil.unload();
193             cmdPool.free([ cast(CommandBuffer)cmdBuf ]);
194             super.dispose();
195         }
196     }
197 
198     override FrameData makeFrameData(ImageBase swcColor, CommandBuffer tempBuf)
199     {
200         return new StencilFrameData(swcColor, tempBuf);
201     }
202 
203     void preparePipeline()
204     {
205         const stencilSpv = [
206             import("stencil.vert.spv"), import("stencil.frag.spv")
207         ];
208         auto swVs = device.createShaderModule(
209             cast(immutable(uint)[])stencilSpv[0], "main"
210         ).rc;
211         auto swFs = device.createShaderModule(
212             cast(immutable(uint)[])stencilSpv[1], "main"
213         ).rc;
214         stencilWriteDSL = device.createDescriptorSetLayout([
215             PipelineLayoutBinding(0, DescriptorType.combinedImageSampler, 1, ShaderStage.fragment),
216         ]);
217         stencilWriteLayout = device.createPipelineLayout([ stencilWriteDSL.obj ], []);
218 
219         PipelineInfo swInfo;
220         swInfo.shaders.vertex = swVs;
221         swInfo.shaders.fragment = swFs;
222         swInfo.inputBindings = [
223             VertexInputBinding(0, VertexP2T2.sizeof, No.instanced)
224         ];
225         swInfo.inputAttribs = [
226             VertexInputAttrib(0, 0, Format.rg32_sFloat, VertexP2T2.position.offsetof),
227             VertexInputAttrib(1, 0, Format.rg32_sFloat, VertexP2T2.texCoord.offsetof),
228         ];
229         swInfo.assembly = InputAssembly(Primitive.triangleList, No.primitiveRestart);
230         swInfo.rasterizer = Rasterizer(
231             PolygonMode.fill, Cull.none, FrontFace.ccw, No.depthClamp,
232             none!DepthBias, 1f
233         );
234         swInfo.viewports = [
235             ViewportConfig(
236                 Viewport(0, 0, cast(float)surfaceSize[0], cast(float)surfaceSize[1]),
237                 Rect(0, 0, surfaceSize[0], surfaceSize[1])
238             )
239         ];
240         const sos1 = StencilOpState(
241             StencilOp.replace, StencilOp.replace, StencilOp.replace, CompareOp.always,
242             0x01, 0x01, 0x01
243         );
244         swInfo.stencilInfo = StencilInfo(
245             Yes.enabled, sos1, sos1
246         );
247         swInfo.layout = stencilWriteLayout;
248         swInfo.renderPass = renderPass;
249         swInfo.subpassIndex = 0;
250 
251 
252         const solidSpv = [
253             import("solid.vert.spv"), import("solid.frag.spv")
254         ];
255         auto solVs = device.createShaderModule(
256             cast(immutable(uint)[])solidSpv[0], "main"
257         ).rc;
258         auto solFs = device.createShaderModule(
259             cast(immutable(uint)[])solidSpv[1], "main"
260         ).rc;
261         auto solPL = device.createPipelineLayout([], []).rc;
262 
263         PipelineInfo solInfo;
264         solInfo.shaders.vertex = solVs;
265         solInfo.shaders.fragment = solFs;
266         solInfo.inputBindings = [
267             VertexInputBinding(0, VertexP2C3.sizeof, No.instanced)
268         ];
269         solInfo.inputAttribs = [
270             VertexInputAttrib(0, 0, Format.rg32_sFloat, VertexP2C3.position.offsetof),
271             VertexInputAttrib(1, 0, Format.rgb32_sFloat, VertexP2C3.color.offsetof),
272         ];
273         solInfo.assembly = InputAssembly(Primitive.triangleList, No.primitiveRestart);
274         solInfo.rasterizer = Rasterizer(
275             PolygonMode.fill, Cull.none, FrontFace.ccw, No.depthClamp,
276             none!DepthBias, 1f
277         );
278         solInfo.viewports = [
279             ViewportConfig(
280                 Viewport(0, 0, cast(float)surfaceSize[0], cast(float)surfaceSize[1]),
281                 Rect(0, 0, surfaceSize[0], surfaceSize[1])
282             )
283         ];
284         solInfo.blendInfo = ColorBlendInfo(
285             none!LogicOp, [ ColorBlendAttachment.solid() ], [ 0f, 0f, 0f, 0f ]
286         );
287         const sos2 = StencilOpState(
288             StencilOp.keep, StencilOp.keep, StencilOp.keep, CompareOp.equal,
289             0x01, 0x01, 0x01
290         );
291         solInfo.stencilInfo = StencilInfo(
292             Yes.enabled, sos2, sos2
293         );
294         solInfo.layout = solPL;
295         solInfo.renderPass = renderPass;
296         solInfo.subpassIndex = 1;
297 
298         auto pls = device.createPipelines( [ swInfo, solInfo ] );
299         stencilWritePipeline = pls[0];
300         solidPipeline = pls[1];
301     }
302 
303     void prepareDescriptorSet() {
304         const poolSizes = [
305             DescriptorPoolSize(DescriptorType.combinedImageSampler, 1),
306         ];
307         descPool = device.createDescriptorPool(1, poolSizes);
308         descriptorSet = descPool.allocate([ stencilWriteDSL.obj ])[0];
309 
310         auto writes = [
311             WriteDescriptorSet(descriptorSet, 0, 0, DescriptorWrite.make(
312                 DescriptorType.combinedImageSampler,
313                 chessboardView.descriptorWithSampler(ImageLayout.undefined, sampler),
314             )),
315         ];
316         device.updateDescriptorSets(writes, []);
317     }
318 
319 
320     override Submission[] recordCmds(FrameData frameData)
321     {
322         auto sfd = cast(StencilFrameData)frameData;
323 
324         const cv = ClearColorValues(0.6f, 0.6f, 0.6f, hasAlpha ? 0.5f : 1f);
325         const dsv = ClearDepthStencilValues(0f, 0);
326 
327         auto buf = sfd.cmdBuf;
328 
329         buf.begin(CommandBufferUsage.oneTimeSubmit);
330 
331         buf.beginRenderPass(
332             renderPass, sfd.framebuffer,
333             Rect(0, 0, surfaceSize[0], surfaceSize[1]),
334             [ ClearValues(cv), ClearValues(dsv) ]
335         );
336 
337             buf.bindPipeline(stencilWritePipeline);
338             buf.bindIndexBuffer(indBuf, 0, IndexType.u16);
339             buf.bindVertexBuffers(0, [ VertexBinding(vertBuf, 0) ]);
340             buf.bindDescriptorSets(
341                 PipelineBindPoint.graphics, stencilWriteLayout, 0,
342                 [ descriptorSet ], []
343             );
344             buf.drawIndexed(cast(uint)squareIndices.length, 1, 0, 0, 0);
345 
346         buf.nextSubpass();
347 
348             buf.bindPipeline(solidPipeline);
349             buf.bindVertexBuffers(0, [ VertexBinding(vertBuf, squareLen) ]);
350             buf.draw(3, 1, 0, 0);
351 
352         buf.endRenderPass();
353 
354         buf.end();
355 
356         return simpleSubmission([ buf ]);
357     }
358 
359 }
360 
361 int main(string[] args)
362 {
363     try {
364         auto example = new StencilExample(args);
365         example.prepare();
366         scope(exit) example.dispose();
367 
368         example.window.onKeyOn = (KeyEvent ev)
369         {
370             if (ev.sym == KeySym.escape) {
371                 example.window.closeFlag = true;
372             }
373         };
374 
375         while (!example.window.closeFlag) {
376             example.display.pollAndDispatch();
377             example.render();
378             example.frameTick();
379         }
380 
381         return 0;
382     }
383     catch(Exception ex) {
384         stderr.writeln("error occured: ", ex.msg);
385         return 1;
386     }
387 }