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 }