1 module example; 2 3 import gfx.core.log; 4 import gfx.core.rc; 5 import gfx.graal; 6 import gfx.graal.buffer; 7 import gfx.graal.cmd; 8 import gfx.graal.device; 9 import gfx.graal.format; 10 import gfx.graal.image; 11 import gfx.graal.memory; 12 import gfx.graal.presentation; 13 import gfx.graal.queue; 14 import gfx.graal.renderpass; 15 import gfx.graal.sync; 16 import gfx.graal.types; 17 import gfx.window; 18 19 import std.algorithm; 20 import std.exception; 21 import std.stdio; 22 import std.typecons; 23 import std.traits : isArray; 24 import std.datetime : Duration; 25 26 immutable gfxExLog = LogTag("GFX-EX"); 27 28 struct FpsProbe 29 { 30 import std.datetime.stopwatch : StopWatch; 31 32 private StopWatch sw; 33 private size_t lastUsecs; 34 private size_t lastFc; 35 private size_t fc; 36 37 void start() { sw.start(); } 38 39 bool running() { return sw.running(); } 40 41 void stop() 42 { 43 sw.stop(); 44 lastUsecs = 0; 45 lastFc = 0; 46 fc = 0; 47 } 48 49 void tick() { fc += 1; } 50 51 size_t framecount() { return fc; } 52 53 float computeFps() { 54 const usecs = sw.peek().total!"usecs"(); 55 const fps = 1000_000f * (fc - lastFc) / (usecs-lastUsecs); 56 lastFc = fc; 57 lastUsecs = usecs; 58 return fps; 59 } 60 } 61 62 struct Timer 63 { 64 import std.datetime.stopwatch : StopWatch; 65 66 private StopWatch sw; 67 private size_t fc; 68 69 void start() { sw.start(); } 70 void stop() { sw.stop(); } 71 72 auto frame() { 73 static struct FrameTimer 74 { 75 StopWatch* sw; 76 this(StopWatch* sw) 77 { 78 this.sw = sw; 79 sw.start(); 80 } 81 ~this() 82 { 83 if (this.sw) { 84 sw.stop(); 85 } 86 } 87 } 88 ++fc; 89 return FrameTimer(&sw); 90 } 91 92 @property Duration totalDur() 93 { 94 return sw.peek; 95 } 96 97 @property Duration avgDur() 98 { 99 return sw.peek / fc; 100 } 101 102 @property size_t framecount() 103 { 104 return fc; 105 } 106 } 107 108 shared static this() 109 { 110 import gfx.core.log : Severity, severity; 111 severity = Severity.trace; 112 113 debug(rc) { 114 import gfx.core.rc : rcPrintStack, rcTypeRegex; 115 rcPrintStack = true; 116 rcTypeRegex = "^GlDevice$"; 117 } 118 } 119 120 class Example : Disposable 121 { 122 import gfx.math.proj : NDC; 123 124 string title; 125 string[] args; 126 Rc!Display display; 127 Window window; 128 Rc!Instance instance; 129 NDC ndc; 130 PhysicalDevice physicalDevice; 131 Rc!Device device; 132 Queue graphicsQueue; 133 Queue presentQueue; 134 uint[2] surfaceSize; 135 bool hasAlpha; 136 Rc!Swapchain swapchain; 137 Rc!Semaphore imageAvailableSem; 138 Rc!Semaphore renderingFinishSem; 139 uint frameNumber; 140 FrameData[] frameDatas; 141 FpsProbe probe; 142 143 // garbage collection 144 // it is sometimes desirable to delete objects still in use in a command 145 // buffer (this happens when rebuilding the swapchain for example) 146 // each entry is given an optional fence to check for the command buffer 147 // completion, and if the fence is null, it checks that the garbage 148 // was not emitted more than maxFcAge frames ago. 149 GarbageEntry garbageEntries; 150 GarbageEntry lastGarbageEntry; 151 enum maxFcAge = 4; 152 153 class GarbageEntry 154 { 155 GarbageEntry next; 156 uint fc; 157 Fence fence; 158 IAtomicRefCounted resource; 159 } 160 161 this (string title, string[] args=[]) 162 { 163 this.title = title; 164 this.args = args; 165 } 166 167 override void dispose() 168 { 169 probe.stop(); 170 if (device) { 171 device.waitIdle(); 172 } 173 while (garbageEntries) { 174 if (garbageEntries.fence) { 175 garbageEntries.fence.wait(); 176 releaseObj(garbageEntries.fence); 177 } 178 releaseObj(garbageEntries.resource); 179 garbageEntries = garbageEntries.next; 180 } 181 releaseArr(frameDatas); 182 // the rest is checked with Rc, so it is safe to call unload even 183 // if object is invalid 184 imageAvailableSem.unload(); 185 renderingFinishSem.unload(); 186 swapchain.unload(); 187 device.unload(); 188 // if (window) window.close(); 189 instance.unload(); 190 display.unload(); 191 } 192 193 void prepare() 194 { 195 bool noVulkan = false; 196 bool noGl3 = false; 197 bool noWayland = false; 198 foreach (a; args) { 199 if (a == "--no-vulkan" || a == "nv") { 200 noVulkan = true; 201 } 202 else if (a == "--no-gl3" || a == "ng") { 203 noGl3 = true; 204 } 205 else if (a == "--no-wayland" || a == "nw") { 206 noWayland = true; 207 } 208 } 209 210 import std.algorithm : remove; 211 212 DisplayCreateInfo createInfo; 213 if (noVulkan) { 214 createInfo.backendCreateOrder = 215 createInfo.backendCreateOrder.remove(Backend.vulkan); 216 } 217 if (noGl3) { 218 createInfo.backendCreateOrder = 219 createInfo.backendCreateOrder.remove(Backend.gl3); 220 } 221 if (noWayland) { 222 createInfo.linuxDisplayCreateOrder = 223 createInfo.linuxDisplayCreateOrder.remove(LinuxDisplay.wayland); 224 } 225 226 // Create a display for the running platform 227 // The instance is created by the display. Backend is chosen at runtime 228 // depending on detected API support. (i.e. Vulkan is preferred) 229 display = createDisplay(createInfo); 230 instance = display.instance; 231 ndc = instance.apiProps.ndc; 232 233 // Create a window. The surface is created during the call to show. 234 window = display.createWindow(title); 235 window.show(640, 480); 236 surfaceSize = [640, 480]; 237 238 window.onResize = (uint w, uint h) 239 { 240 if (w != surfaceSize[0] || h != surfaceSize[1]) { 241 surfaceSize = [ w, h ]; 242 rebuildSwapchain(); 243 } 244 }; 245 246 debug { 247 alias Sev = gfx.graal.Severity; 248 instance.setDebugCallback((Sev sev, string msg) { 249 import std.stdio : writefln; 250 if (sev >= Sev.warning) { 251 writefln("Gfx backend %s message: %s", sev, msg); 252 } 253 if (sev == Sev.error) { 254 // debug break; 255 asm { int 0x03; } 256 } 257 }); 258 } 259 260 // The rest of the preparation. 261 prepareDevice(); 262 prepareSync(); 263 prepareSwapchain(null); 264 prepareRenderPass(); 265 prepareFramebuffers(); 266 267 probe.start(); 268 } 269 270 Duration timeElapsed() 271 { 272 assert(probe.sw.running(), "stopwatch isn't running!!"); 273 return probe.sw.peek(); 274 } 275 276 void prepareDevice() 277 { 278 auto graphicsQueueIndex = uint.max; 279 auto presentQueueIndex = uint.max; 280 281 bool checkDevice(PhysicalDevice dev) { 282 if (dev.softwareRendering) return false; 283 foreach (size_t i, qf; dev.queueFamilies) { 284 const qi = cast(uint)i; 285 const graphics = qf.cap & QueueCap.graphics; 286 const present = dev.supportsSurface(qi, window.surface); 287 if (graphics && present) { 288 graphicsQueueIndex = qi; 289 presentQueueIndex = qi; 290 return true; 291 } 292 if (graphics) graphicsQueueIndex = qi; 293 if (present) presentQueueIndex = qi; 294 } 295 return graphicsQueueIndex != uint.max && presentQueueIndex != uint.max; 296 } 297 foreach (pd; instance.devices) { 298 if (checkDevice(pd)) { 299 auto qrs = [ QueueRequest(graphicsQueueIndex, [ 0.5f ]) ]; 300 if (graphicsQueueIndex != presentQueueIndex) { 301 qrs ~= QueueRequest(presentQueueIndex, [ 0.5f ]); 302 } 303 physicalDevice = pd; 304 device = pd.open(qrs); 305 graphicsQueue = device.getQueue(graphicsQueueIndex, 0); 306 presentQueue = device.getQueue(presentQueueIndex, 0); 307 break; 308 } 309 } 310 } 311 312 void prepareSync() 313 { 314 imageAvailableSem = device.createSemaphore(); 315 renderingFinishSem = device.createSemaphore(); 316 } 317 318 void prepareSwapchain(Swapchain former=null) 319 { 320 gfxExLog.infof("building swapchain %sx%s", surfaceSize[0], surfaceSize[1]); 321 322 const surfCaps = physicalDevice.surfaceCaps(window.surface); 323 enforce(surfCaps.usage & ImageUsage.transferDst, "TransferDst not supported by surface"); 324 enforce(surfCaps.usage & ImageUsage.colorAttachment, "ColorAttachment not supported by surface"); 325 const usage = ImageUsage.colorAttachment | ImageUsage.transferDst; 326 const numImages = max(2, surfCaps.minImages); 327 enforce(surfCaps.maxImages == 0 || surfCaps.maxImages >= numImages); 328 const f = chooseFormat(physicalDevice, window.surface); 329 330 CompositeAlpha ca; 331 if (surfCaps.supportedAlpha & CompositeAlpha.preMultiplied) { 332 ca = CompositeAlpha.preMultiplied; 333 } 334 else if (surfCaps.supportedAlpha & CompositeAlpha.inherit) { 335 ca = CompositeAlpha.inherit; 336 } 337 else if (surfCaps.supportedAlpha & CompositeAlpha.postMultiplied) { 338 ca = CompositeAlpha.postMultiplied; 339 } 340 else { 341 ca = CompositeAlpha.opaque; 342 } 343 hasAlpha = ca != CompositeAlpha.opaque; 344 345 foreach (i; 0..2) { 346 surfaceSize[i] = clamp(surfaceSize[i], surfCaps.minSize[i], surfCaps.maxSize[i]); 347 } 348 const pm = choosePresentMode(physicalDevice, window.surface); 349 350 swapchain = device.createSwapchain(window.surface, pm, numImages, f, surfaceSize, usage, ca, former); 351 } 352 353 void prepareRenderPass() 354 {} 355 356 /// Data that is duplicated for every frame in the swapchain 357 /// This typically include framebuffer and command pool. 358 abstract class FrameData : AtomicRefCounted 359 { 360 Rc!Fence fence; // to keep track of when command processing is done 361 Rc!CommandPool cmdPool; 362 363 ImageBase swcColor; 364 uint[2] size; 365 366 this(ImageBase swcColor) 367 { 368 this.fence = device.createFence(Yes.signaled); 369 this.cmdPool = device.createCommandPool(graphicsQueue.index); 370 371 this.swcColor = swcColor; 372 const dims = swcColor.info.dims; 373 size = [dims.width, dims.height]; 374 } 375 376 override void dispose() 377 { 378 fence.unload(); 379 cmdPool.unload(); 380 } 381 } 382 383 /// Instantiate FrameData implementation for the given swapchain color image. 384 /// tempBuf is a helper that can be used to transfer data, change images layout... 385 /// it is submitted and waited for shortly after FrameData is built. 386 abstract FrameData makeFrameData(ImageBase swcColor, CommandBuffer tempBuf); 387 388 void prepareFramebuffers() 389 { 390 auto swcImages = swapchain.images; 391 frameDatas = new FrameData[swcImages.length]; 392 393 auto tempBuf = rc(new RaiiCmdBuf); 394 395 foreach(i, img; swcImages) { 396 frameDatas[i] = retainObj(makeFrameData(img, tempBuf.get)); 397 } 398 } 399 400 /// Record buffer implementation for the current frame. 401 /// Returns the submissions for the graphics queue 402 /// the first submission that renders to the swapchain image must 403 /// wait for imageAvailableSem 404 /// the last submission must signal renderingFinishSem 405 abstract Submission[] recordCmds(FrameData frameData); 406 407 /// build a submission for the simplest cases with one submission 408 final Submission[] simpleSubmission(PrimaryCommandBuffer[] cmdBufs) 409 { 410 return [ 411 Submission ( 412 [ StageWait(imageAvailableSem, PipelineStage.transfer) ], 413 [ renderingFinishSem.obj ], cmdBufs 414 ) 415 ]; 416 } 417 418 void render() 419 { 420 import gfx.graal.error : OutOfDateException; 421 422 const acq = swapchain.acquireNextImage(imageAvailableSem.obj); 423 424 if (acq.hasIndex) { 425 auto frameData = frameDatas[acq.index]; 426 frameData.fence.wait(); 427 frameData.fence.reset(); 428 429 auto submissions = recordCmds(frameData); 430 431 graphicsQueue.submit(submissions, frameData.fence); 432 433 try { 434 presentQueue.present( 435 [ renderingFinishSem.obj ], 436 [ PresentRequest(swapchain, acq.index) ] 437 ); 438 } 439 catch (OutOfDateException ex) { 440 // The swapchain became out of date between acquire and present. 441 // Rare, but can happen 442 gfxExLog.errorf("error during presentation: %s", ex.msg); 443 gfxExLog.errorf("acquisition was %s", acq.state); 444 rebuildSwapchain(); 445 return; 446 } 447 } 448 449 if (acq.swapchainNeedsRebuild) { 450 rebuildSwapchain(); 451 } 452 } 453 454 void rebuildSwapchain() 455 { 456 foreach (imgData; frameDatas) { 457 gc(imgData, imgData.fence); 458 } 459 releaseArr(frameDatas); 460 prepareSwapchain(swapchain.obj); 461 prepareFramebuffers(); 462 } 463 464 void frameTick() 465 { 466 frameNumber += 1; 467 collectGarbage(); 468 469 enum reportPeriod = 300; 470 probe.tick(); 471 if (probe.framecount % reportPeriod == 0) { 472 gfxExLog.infof("FPS = %s", probe.computeFps()); 473 } 474 } 475 476 void gc(IAtomicRefCounted resource, Fence fence=null) 477 { 478 auto entry = new GarbageEntry; 479 entry.resource = retainObj(resource); 480 if (fence) entry.fence = retainObj(fence); 481 entry.fc = frameNumber; 482 483 if (lastGarbageEntry) { 484 lastGarbageEntry.next = entry; 485 lastGarbageEntry = entry; 486 } 487 else { 488 assert(!garbageEntries); 489 garbageEntries = entry; 490 lastGarbageEntry = entry; 491 } 492 } 493 494 void collectGarbage() 495 { 496 while (garbageEntries) { 497 if ((garbageEntries.fence && garbageEntries.fence.signaled) || 498 (garbageEntries.fc+maxFcAge < frameNumber)) 499 { 500 if (garbageEntries.fence) releaseObj(garbageEntries.fence); 501 releaseObj(garbageEntries.resource); 502 garbageEntries = garbageEntries.next; 503 } 504 else { 505 break; 506 } 507 } 508 if (!garbageEntries) lastGarbageEntry = null; 509 } 510 511 // Following functions are general utility that can be used by subclassing 512 // examples. 513 514 /// Find a format supported by the device for the given tiling and features 515 Format findSupportedFormat(in Format[] candidates, in ImageTiling tiling, in FormatFeatures features) 516 { 517 foreach (f; candidates) { 518 const props = physicalDevice.formatProperties(f); 519 if (tiling == ImageTiling.optimal && 520 (props.optimalTiling & features) == features) { 521 return f; 522 } 523 if (tiling == ImageTiling.linear && 524 (props.linearTiling & features) == features) { 525 return f; 526 } 527 } 528 throw new Exception("could not find supported format"); 529 } 530 531 /// Find a supported depth format 532 Format findDepthFormat() { 533 return findSupportedFormat( 534 [ Format.d32_sFloat, Format.d32s8_sFloat, Format.d24s8_uNorm, Format.d16_uNorm, Format.d16s8_uNorm ], 535 ImageTiling.optimal, FormatFeatures.depthStencilAttachment 536 ); 537 } 538 539 /// Find a supported stencil format 540 Format findStencilFormat() { 541 return findSupportedFormat( 542 [ Format.s8_uInt, Format.d16s8_uNorm, Format.d24s8_uNorm, Format.d32s8_sFloat ], 543 ImageTiling.optimal, FormatFeatures.depthStencilAttachment 544 ); 545 } 546 547 /// Return the index of a memory type supporting all of props, 548 /// or uint.max if none was found. 549 uint findMemType(MemoryRequirements mr, MemProps props) 550 { 551 const devMemProps = physicalDevice.memoryProperties; 552 foreach (i, mt; devMemProps.types) { 553 if ((mr.memTypeMask & (1 << i)) != 0 && (mt.props & props) == props) { 554 return cast(uint)i; 555 } 556 } 557 return uint.max; 558 } 559 560 561 /// Create a buffer for usage, bind memory of dataSize with memProps 562 /// Return null if no memory type can be found 563 final Buffer createBuffer(size_t dataSize, BufferUsage usage, MemProps props) 564 { 565 auto buf = device.createBuffer( usage, dataSize ).rc; 566 567 const mr = buf.memoryRequirements; 568 const memTypeInd = findMemType(mr, props); 569 if (memTypeInd == uint.max) return null; 570 571 auto mem = device.allocateMemory(memTypeInd, mr.size).rc; 572 buf.bindMemory(mem, 0); 573 574 return buf.giveAway(); 575 } 576 577 /// Create a buffer, binds memory to it, and leave content undefined 578 /// The buffer will be host visible and host coherent such as content 579 /// can be updated without staging buffer 580 final Buffer createDynamicBuffer(size_t dataSize, BufferUsage usage) 581 { 582 return createBuffer(dataSize, usage, MemProps.hostVisible | MemProps.hostCoherent); 583 } 584 585 /// Create a buffer, and bind it with memory filled with data. 586 /// The bound memory will be deviceLocal, without guarantee to be host visible. 587 final Buffer createStaticBuffer(const(void)[] data, BufferUsage usage) 588 { 589 const dataSize = data.length; 590 591 // On embedded gpus, device local memory is often also host visible. 592 // Attempting to create one that way. 593 if (physicalDevice.type != DeviceType.discreteGpu) { 594 auto buf = createBuffer( 595 dataSize, usage, 596 MemProps.hostVisible | MemProps.hostCoherent | MemProps.deviceLocal 597 ).rc; 598 if (buf) { 599 auto mm = buf.boundMemory.map(0, dataSize); 600 mm[] = data; 601 return buf.giveAway(); 602 } 603 } 604 605 // did not happen :-( 606 // will go the usual way: staging buffer then device local buffer 607 608 // create staging buffer 609 auto stagingBuf = enforce(createBuffer( 610 dataSize, BufferUsage.transferSrc, MemProps.hostVisible | MemProps.hostCoherent 611 )).rc; 612 613 // populate data 614 { 615 auto mm = stagingBuf.boundMemory.map(0, dataSize); 616 mm[] = data; 617 } 618 619 // create actual buffer 620 auto buf = enforce(createBuffer( 621 dataSize, usage | BufferUsage.transferDst, MemProps.deviceLocal 622 )).rc; 623 624 auto b = rc(new RaiiCmdBuf); 625 626 // copy from staging buffer 627 copyBuffer(stagingBuf, buf, dataSize, b.cmdBuf); 628 629 // return data 630 return buf.giveAway(); 631 } 632 633 /// ditto 634 Buffer createStaticBuffer(T)(const(T)[] data, BufferUsage usage) 635 if (!is(T == void)) 636 { 637 return createStaticBuffer(untypeSlice(data), usage); 638 } 639 640 /// ditto 641 Buffer createStaticBuffer(T)(in T data, BufferUsage usage) 642 if (!isArray!T) 643 { 644 const start = cast(const(void)*)&data; 645 return createStaticBuffer(start[0 .. data.sizeof], usage); 646 } 647 648 bool bindImageMemory(Image img, MemProps props=MemProps.deviceLocal) { 649 const mr = img.memoryRequirements; 650 const memTypeInd = findMemType(mr, props); 651 if (memTypeInd == uint.max) return false; 652 653 auto mem = device.allocateMemory(memTypeInd, mr.size).rc; 654 img.bindMemory(mem, 0); 655 return true; 656 } 657 658 /// create an image to be used as texture 659 Image createTextureImage(const(void)[] data, in ImageInfo info) 660 { 661 const FormatFeatures requirement = FormatFeatures.sampledImage; 662 const formatProps = physicalDevice.formatProperties(info.format); 663 enforce( (formatProps.optimalTiling & requirement) == requirement ); 664 665 // create staging buffer 666 auto stagingBuf = enforce(createBuffer( 667 data.length, BufferUsage.transferSrc, MemProps.hostVisible | MemProps.hostCoherent 668 )).rc; 669 670 // populate data to buffer 671 { 672 auto mm = stagingBuf.boundMemory.map(0, data.length); 673 mm[] = data; 674 } 675 676 // create an image 677 auto img = enforce(device.createImage( 678 info.withUsage(info.usage | ImageUsage.sampled | ImageUsage.transferDst) 679 )).rc; 680 681 // allocate memory image 682 if (!bindImageMemory(img)) return null; 683 684 { 685 auto b = rc(new RaiiCmdBuf); 686 687 b.cmdBuf.pipelineBarrier( 688 trans(PipelineStage.topOfPipe, PipelineStage.transfer), [], [ 689 ImageMemoryBarrier( 690 trans(Access.none, Access.transferWrite), 691 trans(ImageLayout.undefined, ImageLayout.transferDstOptimal), 692 trans(queueFamilyIgnored, queueFamilyIgnored), 693 img, ImageSubresourceRange(ImageAspect.color) 694 ) 695 ] 696 ); 697 copyBufferToImage(stagingBuf, img, b.cmdBuf); 698 } 699 700 return img.giveAway(); 701 } 702 703 /// Create an image for depth attachment usage 704 Image createDepthImage(uint width, uint height) 705 { 706 // find the format of the image 707 const f = findDepthFormat(); 708 709 // create an image 710 auto img = enforce(device.createImage( 711 ImageInfo.d2(width, height).withFormat(f).withUsage(ImageUsage.depthStencilAttachment) 712 )).rc; 713 714 // allocate memory image 715 if (!bindImageMemory(img)) return null; 716 717 return img.giveAway(); 718 } 719 720 /// Create an image for stencil attachment usage 721 Image createStencilImage(uint width, uint height) 722 { 723 // assume s8_uInt is supported 724 const f = findStencilFormat(); 725 // create an image 726 auto img = enforce(device.createImage( 727 ImageInfo.d2(width, height).withFormat(f).withUsage(ImageUsage.depthStencilAttachment) 728 )).rc; 729 730 // allocate memory image 731 if (!bindImageMemory(img)) return null; 732 733 return img.giveAway(); 734 } 735 736 final void recordImageLayoutBarrier(CommandBuffer cmdBuf, ImageBase img, Trans!ImageLayout layout) 737 { 738 const info = img.info; 739 740 if (info.usage & ImageUsage.colorAttachment) 741 { 742 cmdBuf.pipelineBarrier( 743 trans(PipelineStage.colorAttachmentOutput, PipelineStage.colorAttachmentOutput), [], 744 [ ImageMemoryBarrier( 745 trans(Access.none, Access.colorAttachmentWrite), 746 layout, 747 trans(queueFamilyIgnored, queueFamilyIgnored), 748 img, ImageSubresourceRange(ImageAspect.color) 749 ) ] 750 ); 751 } 752 else if (info.usage & ImageUsage.depthStencilAttachment) 753 { 754 const hasDepth = formatDesc(info.format).surfaceType.depthBits > 0; 755 const hasStencil = formatDesc(info.format).surfaceType.stencilBits > 0; 756 auto aspect = ImageAspect.none; 757 if (hasDepth) aspect |= ImageAspect.depth; 758 if (hasStencil) aspect |= ImageAspect.stencil; 759 cmdBuf.pipelineBarrier( 760 trans(PipelineStage.topOfPipe, PipelineStage.earlyFragmentTests), [], [ 761 ImageMemoryBarrier( 762 trans( 763 Access.none, 764 Access.depthStencilAttachmentRead | Access.depthStencilAttachmentWrite 765 ), 766 layout, 767 trans(queueFamilyIgnored, queueFamilyIgnored), 768 img, ImageSubresourceRange(aspect) 769 ) 770 ] 771 ); 772 } 773 else { 774 import std.format : format; 775 throw new Exception( 776 format("don't know how to record memory barrier for image usage %s", info.usage) 777 ); 778 } 779 } 780 781 /// copy the content of one buffer to another 782 /// srcBuf and dstBuf must support transferSrc and transferDst respectively. 783 final void copyBuffer(Buffer srcBuf, Buffer dstBuf, size_t size, CommandBuffer cmdBuf) 784 { 785 cmdBuf.copyBuffer(trans(srcBuf, dstBuf), [CopyRegion(trans!size_t(0, 0), size)]); 786 } 787 788 /// copy the content of one buffer to an image. 789 /// the image layout must be transferDstOptimal buffer the call 790 final void copyBufferToImage(Buffer srcBuf, Image dstImg, CommandBuffer cmdBuf) 791 { 792 const dims = dstImg.info.dims; 793 794 BufferImageCopy region; 795 region.extent = [dims.width, dims.height, dims.depth]; 796 const regions = (®ion)[0 .. 1]; 797 cmdBuf.copyBufferToImage(srcBuf, dstImg, ImageLayout.transferDstOptimal, regions); 798 } 799 800 /// Utility command buffer for a one time submission that automatically submit 801 /// when disposed. 802 /// Generally used for transfer operations, or image layout change. 803 final class RaiiCmdBuf : AtomicRefCounted 804 { 805 Rc!CommandPool pool; 806 PrimaryCommandBuffer cmdBuf; 807 808 this() { 809 this.pool = device.createCommandPool(graphicsQueue.index); 810 this.cmdBuf = this.pool.allocatePrimary(1)[0]; 811 this.cmdBuf.begin(CommandBufferUsage.oneTimeSubmit); 812 } 813 814 override void dispose() { 815 this.cmdBuf.end(); 816 graphicsQueue.submit([ 817 Submission([], [], (&this.cmdBuf)[0 .. 1]) 818 ], null); 819 graphicsQueue.waitIdle(); 820 auto cb = cast(CommandBuffer)this.cmdBuf; 821 this.pool.free((&cb)[0 .. 1]); 822 this.pool.unload(); 823 } 824 825 @property CommandBuffer get() 826 { 827 return cmdBuf; 828 } 829 } 830 } 831 832 /// Return a format suitable for the surface. 833 /// - if supported by the surface Format.rgba8_uNorm 834 /// - otherwise the first format with uNorm numeric format 835 /// - otherwise the first format 836 Format chooseFormat(PhysicalDevice pd, Surface surface) 837 { 838 const formats = pd.surfaceFormats(surface); 839 enforce(formats.length, "Could not get surface formats"); 840 if (formats.length == 1 && formats[0] == Format.undefined) { 841 return Format.rgba8_uNorm; 842 } 843 foreach(f; formats) { 844 if (f == Format.rgba8_uNorm) { 845 return f; 846 } 847 } 848 foreach(f; formats) { 849 if (f.formatDesc.numFormat == NumFormat.uNorm) { 850 return f; 851 } 852 } 853 return formats[0]; 854 } 855 856 PresentMode choosePresentMode(PhysicalDevice pd, Surface surface) 857 { 858 // auto modes = pd.surfacePresentModes(surface); 859 // if (modes.canFind(PresentMode.mailbox)) { 860 // return PresentMode.mailbox; 861 // } 862 assert(pd.surfacePresentModes(surface).canFind(PresentMode.fifo)); 863 return PresentMode.fifo; 864 }