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