diff --git a/PyroRHI/Api/IDevice.hpp b/PyroRHI/Api/IDevice.hpp index 2ce2995..68361b9 100644 --- a/PyroRHI/Api/IDevice.hpp +++ b/PyroRHI/Api/IDevice.hpp @@ -466,15 +466,12 @@ namespace PyroshockStudios { PYRO_NODISCARD virtual DeviceSize ImageSizeRequirements(Image image) const = 0; /** - * @brief Returns the required row pitch (stride in bytes) for a specific subresource of the image. + * @brief Returns the ImageUploadSlice for a specific subresource of the image. * - * The row pitch is the number of bytes the GPU expects between consecutive rows in a buffer - * when copying to or from this image subresource. It must be used when performing - * buffer-to-image or image-to-buffer copies to avoid out-of-bounds errors. - * - * Row width is the minimal width that needs to be queried **INCLUDING** the format size. For a buffer-image copy, this is the extent of your copy region. + * @param slice Subresource of the image to upload to + * @param region Optional box where the upload is targetted towards. By default, the entire extent of the image is assumed. */ - PYRO_NODISCARD virtual u32 ImageSubresourceRowPitch(Image image, u32 rowWidth, ImageSlice slice = {}) const = 0; + PYRO_NODISCARD virtual ImageUploadSlice ImageUploadRequirements(Image image, ImageSlice slice, eastl::optional region = eastl::nullopt) const = 0; /** * @brief - REQUIRES ACCELERATION STRUCTURE SUPPORT - diff --git a/PyroRHI/Api/Types.hpp b/PyroRHI/Api/Types.hpp index c1cce14..728d887 100644 --- a/PyroRHI/Api/Types.hpp +++ b/PyroRHI/Api/Types.hpp @@ -943,6 +943,18 @@ namespace PyroshockStudios { #define PYRO_IMAGE_SLICE_RESOLVE_LEVELS(subSlice, fullLevelCount) \ ((subSlice.levelCount == PYRO_REMAINING_MIP_LEVELS) ? (fullLevelCount - subSlice.baseMipLevel) : subSlice.levelCount) + struct ImageUploadSlice { + DeviceSize size = {}; + u32 uploadPitch = {}; + u32 blockSize = {}; + u32 elementWidth = {}; + u32 elementHeight = {}; + u32 depth = {}; + u32 uploadOffsetAlignment = {}; + + PYRO_NODISCARD eastl::string ToString(usize indentation = 0) const; + }; + struct DrawArgumentBuffer { u32 vertexCount = {}; u32 instanceCount = {}; diff --git a/PyroRHI/ToString.hpp b/PyroRHI/ToString.hpp index b4bd635..ff98361 100644 --- a/PyroRHI/ToString.hpp +++ b/PyroRHI/ToString.hpp @@ -578,6 +578,11 @@ namespace PyroshockStudios { inline eastl::string ImageSlice::ToString(usize indentation) const { return eastl::string().sprintf("{ mip=%u, layer=%u }", mipLevel, arrayLayer); } + inline eastl::string ImageUploadSlice::ToString(usize indentation) const { + return eastl::string().sprintf("{ size=%ull, uploadPitch=%u, blockSize=%u, elementWidth=%u, elementHeight=%u, depth=%u, uploadOffsetAlignment=%u }", + size, uploadPitch, blockSize, elementWidth, elementHeight, depth, uploadOffsetAlignment); + } + inline eastl::string Access::ToString(usize indentation) const { diff --git a/RHIDX12/Api/CommandBuffer.cpp b/RHIDX12/Api/CommandBuffer.cpp index 65ec8f2..a9f40c3 100644 --- a/RHIDX12/Api/CommandBuffer.cpp +++ b/RHIDX12/Api/CommandBuffer.cpp @@ -54,25 +54,38 @@ namespace PyroshockStudios { const auto& dst = mDevice->ResourcePool().Get(info.image); auto blockInfo = RHIUtil::GetFormatBlockInfo(dst.info.format); - u32 bufferRowPitch = (info.rowPitch / blockInfo.bytesPerBlock * blockInfo.blockWidth); + + // calculate the padded height (in blocks) to know the true byte size of one slice + u32 elementHeight = (info.imageExtent.height + blockInfo.blockHeight - 1) / blockInfo.blockHeight; + + // calculate exactly how many BYTES a single array slice takes up in the staging buffer + u64 layerByteSize = static_cast(info.rowPitch) * elementHeight * info.imageExtent.depth; + for (UINT j = 0; j < PYRO_IMAGE_SLICE_RESOLVE_LAYERS(info.imageSlice, dst.info.arrayLayerCount); ++j) { UINT dstSubresource = D3D12CalcSubresource(info.imageSlice.mipLevel, info.imageSlice.baseArrayLayer + j, 0, dst.info.mipLevelCount, dst.info.arrayLayerCount); + D3D12_PLACED_SUBRESOURCE_FOOTPRINT footprint = {}; - UINT numRows = {}; - UINT64 rowSizesInBytes = {}; - UINT64 requiredSize = {}; - mDevice->InternalDevice()->GetCopyableFootprints(&dst.desc, dstSubresource, 1, info.bufferOffset, - &footprint, &numRows, &rowSizesInBytes, &requiredSize); - ASSERT(PYRO_VERIFY_ALIGNMENT(info.rowPitch, footprint.Footprint.RowPitch), "Row Pitch MUST be aligned to device requirements!"); - footprint.Footprint.RowPitch = info.rowPitch; + // Use the DXGI format from the destination texture desc + footprint.Footprint.Format = dst.desc.Format; + + // Exact pixel dimensions requested footprint.Footprint.Width = info.imageExtent.width; footprint.Footprint.Height = info.imageExtent.height; footprint.Footprint.Depth = info.imageExtent.depth; - footprint.Offset = bufferRowPitch * footprint.Footprint.Height * footprint.Footprint.Depth * j; + + footprint.Footprint.RowPitch = info.rowPitch; + + // start at the requested buffer offset, and advance by the exact BYTE size of each layer + footprint.Offset = info.bufferOffset + (layerByteSize * j); + + ASSERT(PYRO_VERIFY_ALIGNMENT(footprint.Offset, D3D12_TEXTURE_DATA_PLACEMENT_ALIGNMENT), "Buffer Offset MUST be 512-byte aligned!"); + CD3DX12_TEXTURE_COPY_LOCATION Dst(dst.resource.Get(), dstSubresource); CD3DX12_TEXTURE_COPY_LOCATION Src(src.resource.Get(), footprint); + mCommandList->CopyTextureRegion(&Dst, info.imageOffset.x, info.imageOffset.y, info.imageOffset.z, &Src, nullptr); } + gDx12Context->FlushDebugMessages(); } diff --git a/RHIDX12/Api/Device.cpp b/RHIDX12/Api/Device.cpp index bcbd4dd..b6053c5 100644 --- a/RHIDX12/Api/Device.cpp +++ b/RHIDX12/Api/Device.cpp @@ -165,7 +165,7 @@ namespace PyroshockStudios { gDx12Context->FlushDebugMessages(); return resourceAllocInfo.SizeInBytes; } - u32 D3DDevice::ImageSubresourceRowPitch(Image image, u32 rowWidth, ImageSlice slice) const { + ImageUploadSlice D3DDevice::ImageUploadRequirements(Image image, ImageSlice slice, eastl::optional region) const { auto& img = mResourcePool->Get(image); UINT dstSubresource = D3D12CalcSubresource(slice.mipLevel, slice.arrayLayer, 0, img.info.mipLevelCount, img.info.arrayLayerCount); D3D12_PLACED_SUBRESOURCE_FOOTPRINT footprint = {}; @@ -175,7 +175,39 @@ namespace PyroshockStudios { mDevice->GetCopyableFootprints(&img.desc, dstSubresource, 1, 0, &footprint, &numRows, &rowSizesInBytes, &requiredSize); gDx12Context->FlushDebugMessages(); - return footprint.Footprint.RowPitch; + + + auto blockInfo = RHIUtil::GetFormatBlockInfo(img.info.format); + + Extent3D mipExtent = img.info.size; + if (slice.mipLevel > 0) { + u32 divisions = 1 << slice.mipLevel; + mipExtent.width /= divisions; + mipExtent.height /= divisions; + mipExtent.depth /= divisions; + if (mipExtent.width == 0) + mipExtent.width = 1; + if (mipExtent.height == 0) + mipExtent.height = 1; + if (mipExtent.depth == 0) + mipExtent.depth = 1; + } + + Box3D box = region.value_or(Box3D::Cut(mipExtent)); + u32 elementWidth = (box.width + blockInfo.blockWidth - 1) / blockInfo.blockWidth; + u32 elementHeight = (box.height + blockInfo.blockHeight - 1) / blockInfo.blockHeight; + + ImageUploadSlice uploadSlice; + uploadSlice.uploadPitch = footprint.Footprint.RowPitch; + uploadSlice.blockSize = blockInfo.bytesPerBlock; + uploadSlice.elementWidth = elementWidth; + uploadSlice.elementHeight = elementHeight; + uploadSlice.depth = box.depth; + uploadSlice.uploadOffsetAlignment = D3D12_TEXTURE_DATA_PLACEMENT_ALIGNMENT; + + uploadSlice.size = static_cast(uploadSlice.uploadPitch) * elementHeight * box.depth; + + return uploadSlice; } AccelerationStructureBuildSizesInfo D3DDevice::BlasSizeRequirements(const BlasBuildInfo& info) const { if (!mDevice5) { diff --git a/RHIDX12/Api/Device.hpp b/RHIDX12/Api/Device.hpp index 5cc286f..3e05074 100644 --- a/RHIDX12/Api/Device.hpp +++ b/RHIDX12/Api/Device.hpp @@ -118,7 +118,7 @@ namespace PyroshockStudios { u8* BufferHostAddress(Buffer buffer) const override; BlasAddress BlasInstanceAddress(BlasId blas) const override; DeviceSize ImageSizeRequirements(Image image) const override; - u32 ImageSubresourceRowPitch(Image image, u32 rowWidth, ImageSlice slice) const override; + ImageUploadSlice ImageUploadRequirements(Image image, ImageSlice slice, eastl::optional region) const override; AccelerationStructureBuildSizesInfo BlasSizeRequirements(const BlasBuildInfo& info) const override; AccelerationStructureBuildSizesInfo TlasSizeRequirements(const TlasBuildInfo& info) const override; diff --git a/RHIVulkan/Api/CommandBuffer.cpp b/RHIVulkan/Api/CommandBuffer.cpp index 6f1e01c..ae51fab 100644 --- a/RHIVulkan/Api/CommandBuffer.cpp +++ b/RHIVulkan/Api/CommandBuffer.cpp @@ -129,13 +129,14 @@ namespace PyroshockStudios::RHIVulkan { ImplImageSlot& imageSlot = mDevice->Slot(info.image); CheckIfSwapchainReference(imageSlot); + auto blockInfo = RHIUtil::GetFormatBlockInfo(imageSlot.info.format); + u32 alignedBufferHeight = (info.imageExtent.height + blockInfo.blockHeight - 1) / blockInfo.blockHeight * blockInfo.blockHeight; - auto blockInfo = RHIUtil::GetFormatBlockInfo(imageSlot.info.format); const VkBufferImageCopy region = { .bufferOffset = info.bufferOffset, - .bufferRowLength = info.rowPitch == 0 ? 0 : (info.rowPitch / blockInfo.bytesPerBlock * blockInfo.blockWidth ), - .bufferImageHeight = info.imageExtent.height, + .bufferRowLength = info.rowPitch == 0 ? 0 : (info.rowPitch / blockInfo.bytesPerBlock * blockInfo.blockWidth), + .bufferImageHeight = alignedBufferHeight, .imageSubresource = { .aspectMask = imageSlot.aspectFlags, .mipLevel = info.imageSlice.mipLevel, @@ -145,6 +146,7 @@ namespace PyroshockStudios::RHIVulkan { .imageOffset = { (int32_t)info.imageOffset.x, (int32_t)info.imageOffset.y, (int32_t)info.imageOffset.z }, .imageExtent = { info.imageExtent.width, info.imageExtent.height, info.imageExtent.depth } }; + vkCmdCopyBufferToImage(mCommandBuffer, mDevice->Slot(info.buffer).vkBuffer, imageSlot.vkImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion); } diff --git a/RHIVulkan/Api/Device.cpp b/RHIVulkan/Api/Device.cpp index fd19f05..308aba4 100644 --- a/RHIVulkan/Api/Device.cpp +++ b/RHIVulkan/Api/Device.cpp @@ -1309,17 +1309,44 @@ namespace PyroshockStudios { return static_cast(Slot(blas).deviceAddress); } - DeviceSize VulkanDevice::ImageSizeRequirements(Image image) const { auto& img = Slot(image); VkMemoryRequirements requirements; vkGetImageMemoryRequirements(mDevice, img.vkImage, &requirements); return requirements.size; } - u32 VulkanDevice::ImageSubresourceRowPitch(Image image, u32 rowWidth, ImageSlice slice) const { - return PYRO_ALIGN(rowWidth, mPhysicalDeviceProperties.limits.optimalBufferCopyRowPitchAlignment); - } + ImageUploadSlice VulkanDevice::ImageUploadRequirements(Image image, ImageSlice slice, eastl::optional region) const { + auto& img = Slot(image); + auto blockInfo = RHIUtil::GetFormatBlockInfo(img.info.format); + + Extent3D mipExtent = img.info.size; + if (slice.mipLevel > 0) { + u32 divisions = 1 << slice.mipLevel; + mipExtent.width = std::max(1u, mipExtent.width / divisions); + mipExtent.height = std::max(1u, mipExtent.height / divisions); + mipExtent.depth = std::max(1u, mipExtent.depth / divisions); + } + + Box3D box = region.value_or(Box3D::Cut(mipExtent)); + + u32 elementWidth = (box.width + blockInfo.blockWidth - 1) / blockInfo.blockWidth; + u32 elementHeight = (box.height + blockInfo.blockHeight - 1) / blockInfo.blockHeight; + + u32 rowWidthBytes = elementWidth * blockInfo.bytesPerBlock; + + ImageUploadSlice uploadSlice; + uploadSlice.uploadPitch = PYRO_ALIGN(rowWidthBytes, mPhysicalDeviceProperties.limits.optimalBufferCopyRowPitchAlignment); + uploadSlice.blockSize = blockInfo.bytesPerBlock; + uploadSlice.elementWidth = elementWidth; + uploadSlice.elementHeight = elementHeight; + uploadSlice.depth = box.depth; + uploadSlice.uploadOffsetAlignment = mPhysicalDeviceProperties.limits.optimalBufferCopyOffsetAlignment; + + uploadSlice.size = static_cast(uploadSlice.uploadPitch) * uploadSlice.elementHeight * uploadSlice.depth; + + return uploadSlice; + } void VulkanDevice::CreateAccelerationStructureBuildInfo( const eastl::span& tlasBuildInfos, const eastl::span& blasBuildInfos, diff --git a/RHIVulkan/Api/Device.hpp b/RHIVulkan/Api/Device.hpp index 21e44b9..12018f9 100644 --- a/RHIVulkan/Api/Device.hpp +++ b/RHIVulkan/Api/Device.hpp @@ -101,7 +101,7 @@ namespace PyroshockStudios { BlasAddress BlasInstanceAddress(BlasId blas) const override; DeviceSize ImageSizeRequirements(Image image) const override; - u32 ImageSubresourceRowPitch(Image image, u32 rowWidth, ImageSlice slice) const override; + ImageUploadSlice ImageUploadRequirements(Image image, ImageSlice slice, eastl::optional region) const override; AccelerationStructureBuildSizesInfo BlasSizeRequirements(const BlasBuildInfo& info) const override; AccelerationStructureBuildSizesInfo TlasSizeRequirements(const TlasBuildInfo& info) const override; diff --git a/tests/RHITestChassis/FuzzImageUploadImpl.hpp b/tests/RHITestChassis/FuzzImageUploadImpl.hpp new file mode 100644 index 0000000..9f9be3d --- /dev/null +++ b/tests/RHITestChassis/FuzzImageUploadImpl.hpp @@ -0,0 +1,341 @@ +#include "Helpers/ValidationFixture.hpp" +#include + +using namespace PyroshockStudios; +using namespace PyroshockStudios::RHI; +using namespace PyroshockStudios::Types; +using namespace ::testing; + +struct ImageCopyFuzzParams { + eastl::string testName; + Format format; + Extent3D imageSize; + ImageDimensions dimensions; + u32 totalMipLevels; + u32 totalArrayLayers; + + u32 targetMip; + u32 targetLayer; + Offset3D copyOffset; + Extent3D copyExtent; + + // Google Test will automatically look for this operator when printing failures + friend std::ostream& operator<<(std::ostream& os, const ImageCopyFuzzParams& p) { + os << "{\n" + << " Base Size: [" << p.imageSize.width << ", " << p.imageSize.height << ", " << p.imageSize.depth << "]\n" + << " Mip/Layer: [" << p.targetMip << " / " << p.targetLayer << "]\n" + << " Copy Offset: [" << p.copyOffset.x << ", " << p.copyOffset.y << ", " << p.copyOffset.z << "]\n" + << " Copy Extent: [" << p.copyExtent.width << ", " << p.copyExtent.height << ", " << p.copyExtent.depth << "]\n" + << "}"; + return os; + } +}; + +class RHI_ImageCopyFuzzing : public RHI_CONTEXT_FIXTURE_NAME, + public WithParamInterface { +}; + +TEST_P(RHI_ImageCopyFuzzing, UploadStressTest) { + const ImageCopyFuzzParams& params = GetParam(); + + ImageInfo info = {}; + info.size = params.imageSize; + info.dimensions = params.dimensions; + info.format = params.format; + info.mipLevelCount = params.totalMipLevels; + info.arrayLayerCount = params.totalArrayLayers; + info.usage = ImageUsageFlagBits::TRANSFER_DST; + info.name = params.testName; + + TRACK_RHI_PARAMETER(info); + Image image = mDevice->CreateImage(info); + ASSERT_TRUE(mDevice->IsImageValid(image)); + TRACK_RHI_HANDLE(image); + + // get upload requirements for the specific mip/layer slice + ImageUploadSlice uploadSlice = mDevice->ImageUploadRequirements( + image, + ImageSlice{ .mipLevel = params.targetMip, .arrayLayer = params.targetLayer }); + TRACK_RHI_PARAMETER(uploadSlice); + + BufferInfo bufferInfo = {}; + bufferInfo.size = uploadSlice.size; + bufferInfo.usage = BufferUsageFlagBits::TRANSFER_SRC; + bufferInfo.allocationDomain = MemoryAllocationDomain::HostStaging; + bufferInfo.initialLayout = BufferLayout::TransferSrc; + bufferInfo.name = "FuzzStagingBuffer"; + + TRACK_RHI_PARAMETER(bufferInfo); + Buffer stagingBuffer = mDevice->CreateBuffer(bufferInfo); + ASSERT_TRUE(mDevice->IsBufferValid(stagingBuffer)); + TRACK_RHI_HANDLE(stagingBuffer); + + ICommandQueue* queue = mDevice->GetCommandQueues()[0]; + TRACK_RHI_HANDLE(queue); + ICommandBuffer* commands = queue->GetCommandBuffer({ .name = "Staging commands" }); + TRACK_RHI_HANDLE(commands); + + ImageMemoryBarrierInfo barrierInfo{ + .image = image, + .dstAccess = AccessConsts::TRANSFER_WRITE, + .dstLayout = ImageLayout::TransferDst, + }; + TRACK_RHI_PARAMETER(barrierInfo); + commands->ImageBarrier(barrierInfo); + + CopyBufferToImageInfo copyInfo{ + .buffer = stagingBuffer, + .bufferOffset = 0, + .image = image, + .imageSlice = { + .mipLevel = params.targetMip, + .baseArrayLayer = params.targetLayer, + .layerCount = 1, + }, + .imageOffset = params.copyOffset, + .imageExtent = params.copyExtent, + .rowPitch = uploadSlice.uploadPitch, + }; + + TRACK_RHI_PARAMETER(copyInfo); + commands->CopyBufferToImage(copyInfo); + commands->Complete(); + + mDevice->SubmitQueue({ .queue = queue, .commands = { &commands, 1 } }); + queue->WaitIdle(); + + mDevice->Destroy(stagingBuffer); + mDevice->Destroy(image); +} + +INSTANTIATE_TEST_SUITE_P( + BufferToImageCopies, + RHI_ImageCopyFuzzing, + Values( + // ------------------------------------------------------------------------- + // 1. NON-POWER OF TWO (NPOT) + // ------------------------------------------------------------------------- + ImageCopyFuzzParams{ + "NPOT_OddDimensions", + Format::RGBA8Unorm, + { 137, 63, 1 }, + ImageDimensions::e2D, + 1, + 1, + 0, + 0, + { 0, 0, 0 }, + { 137, 63, 1 }, + }, + ImageCopyFuzzParams{ + "NPOT_PrimeNumbers", + Format::R8Unorm, + { 257, 127, 1 }, + ImageDimensions::e2D, + 1, + 1, + 0, + 0, + { 0, 0, 0 }, + { 257, 127, 1 }, + }, + + // ------------------------------------------------------------------------- + // 2. BLOCK COMPRESSED FORMATS (BC1/BC3/etc.) + // Block compressed formats require specific row pitch alignments and + // dimensions are often padded to 4x4 blocks internally. + // ------------------------------------------------------------------------- + ImageCopyFuzzParams{ + "Compressed_BC1_Standard", + Format::BC1RGBUnormBlock, + { 256, 256, 1 }, + ImageDimensions::e2D, + 1, + 1, + 0, + 0, + { 0, 0, 0 }, + { 256, 256, 1 }, + }, + ImageCopyFuzzParams{ + "Compressed_BC3_NPOT", Format::BC3UnormBlock, + { 130, 62, 1 }, ImageDimensions::e2D, 1, 1, + 0, 0, { 0, 0, 0 }, { 130, 62, 1 }, // Driver must handle block padding internally + }, + + // ------------------------------------------------------------------------- + // 3. MIP LEVELS + // Uploading to a tiny 1x1 or 2x2 mip level deep in the chain. + // ------------------------------------------------------------------------- + ImageCopyFuzzParams{ + "Mip_DeepLevel_1x1", + Format::RGBA8Unorm, + { 1024, 1024, 1 }, + ImageDimensions::e2D, + 11, + 1, // 1024x1024 goes down to 1x1 at mip 10 + 10, + 0, + { 0, 0, 0 }, + { 1, 1, 1 }, + }, + ImageCopyFuzzParams{ + "Mip_MidLevel_NPOT", Format::RGBA8Unorm, + { 1023, 1024, 1 }, ImageDimensions::e2D, 11, 1, + 3, 0, { 0, 0, 0 }, { 127, 128, 1 }, // Mip 3 is 127x128 + }, + + // ------------------------------------------------------------------------- + // 4. OFFSETS AND PARTIAL COPIES + // Uploading data into a sub-region of the image. + // ------------------------------------------------------------------------- + ImageCopyFuzzParams{ + "Offset_CenterRegion", Format::RGBA8Unorm, + { 512, 512, 1 }, ImageDimensions::e2D, 1, 1, + 0, 0, + { 128, 128, 0 }, // Offset into the image + { 256, 256, 1 }, // Extent of the copy (filling the center) + }, + ImageCopyFuzzParams{ + "Offset_UnalignedEdge", + Format::RGBA8Unorm, + { 256, 256, 1 }, + ImageDimensions::e2D, + 1, + 1, + 0, + 0, + { 13, 17, 0 }, // Weird offset + { 64, 64, 1 }, + }, + + // ------------------------------------------------------------------------- + // 5. TEXTURE ARRAYS & 3D TEXTURES + // ------------------------------------------------------------------------- + ImageCopyFuzzParams{ + "Array_DeepLayer", + Format::RGBA8Unorm, + { 256, 256, 1 }, + ImageDimensions::e2D, + 1, + 16, // 16 layers + 0, + 15, // Uploading to the last layer + { 0, 0, 0 }, + { 256, 256, 1 }, + }, + ImageCopyFuzzParams{ + "Volume_3D_Texture", Format::RGBA8Unorm, + { 64, 64, 64 }, ImageDimensions::e3D, 1, 1, // 3D texture + 0, 0, + { 0, 0, 16 }, { 64, 64, 16 }, // Copying a "slab" into the middle of the 3D volume + }, + ImageCopyFuzzParams{ + "ExtremeAspect_Wide", + Format::RGBA8Unorm, + { 8192, 1, 1 }, + ImageDimensions::e2D, + 1, + 1, + 0, + 0, + { 0, 0, 0 }, + { 8192, 1, 1 }, + }, + ImageCopyFuzzParams{ + "ExtremeAspect_Tall", + Format::RGBA8Unorm, + { 1, 8192, 1 }, + ImageDimensions::e2D, + 1, + 1, + 0, + 0, + { 0, 0, 0 }, + { 1, 8192, 1 }, + }, + + // ------------------------------------------------------------------------- + // 7. COMPRESSION + MIP LEVELS + NPOT + // The ultimate test for block padding logic. + // Base is 137x63. Mip 1 halves and floors to 68x31. + // Both dimensions are not perfectly divisible by 4, forcing the driver + // to handle partial blocks at the edges. + // ------------------------------------------------------------------------- + ImageCopyFuzzParams{ + "Compressed_BC1_Mip_NPOT", + Format::BC1RGBUnormBlock, + { 137, 63, 1 }, + ImageDimensions::e2D, + 3, + 1, + 1, + 0, + { 0, 0, 0 }, + { 68, 31, 1 }, + }, + + // ------------------------------------------------------------------------- + // 8. COMPRESSION + SUB-REGION OFFSETS + // Vulkan SPEC requires imageOffset to be a multiple of the block size (4x4). + // Extents must also be a multiple of the block size unless touching the edge. + // ------------------------------------------------------------------------- + ImageCopyFuzzParams{ + "Compressed_BC3_SubRegion", Format::BC3UnormBlock, + { 256, 256, 1 }, ImageDimensions::e2D, 1, 1, + 0, 0, + { 16, 24, 0 }, // Valid offset (Multiple of 4) + { 64, 32, 1 }, // Valid extent (Multiple of 4) + }, + ImageCopyFuzzParams{ + "Compressed_BC1_SingleBlock", Format::BC1RGBUnormBlock, + { 1024, 1024, 1 }, ImageDimensions::e2D, 1, 1, + 0, 0, + { 512, 512, 0 }, // Deep in the texture + { 4, 4, 1 }, // Exactly one BC1 block (8 bytes total) + }, + + // ------------------------------------------------------------------------- + // 9. THE KITCHEN SINK (3D + Mip + NPOT + Offset) + // Base: 257 x 127 x 63. Target: Mip 2. + // Mip 0: 257x127x63 | Mip 1: 128x63x31 | Mip 2: 64x31x15 + // ------------------------------------------------------------------------- + ImageCopyFuzzParams{ + "Volume_3D_Mip_Subregion_NPOT", Format::R8Unorm, // 1-byte pixel highlights alignment issues easily + { 257, 127, 63 }, ImageDimensions::e3D, 8, 1, + 2, 0, + { 16, 8, 4 }, // Offset into Mip 2 + { 32, 15, 6 }, // Extent matching a sub-cube inside Mip 2 + }, + + // ------------------------------------------------------------------------- + // 10. LARGE PIXEL FORMATS + // Tests math limits for buffer allocations and row pitch when texels are huge. + // RGBA32Float is 16 bytes per texel. + // ------------------------------------------------------------------------- + ImageCopyFuzzParams{ + "Format_Large_RGBA32Float", + Format::RGBA32Sfloat, + { 1023, 1023, 1 }, + ImageDimensions::e2D, + 1, + 1, + 0, + 0, + { 0, 0, 0 }, + { 1023, 1023, 1 }, + }, + + // ------------------------------------------------------------------------- + // 11. TINY OPERATIONS + // ------------------------------------------------------------------------- + ImageCopyFuzzParams{ + "SinglePixel_Offset", Format::RGBA8Unorm, + { 1024, 1024, 1 }, ImageDimensions::e2D, 1, 1, + 0, 0, + { 511, 733, 0 }, // Random weird coordinate + { 1, 1, 1 }, // Just uploading 4 bytes! + }), + [](const TestParamInfo& info) { + return std::string(info.param.testName.c_str()); + }); \ No newline at end of file diff --git a/tests/RHITestChassis/ValidateCommandBufferImpl.hpp b/tests/RHITestChassis/ValidateCommandBufferImpl.hpp index 10b4380..d5cbd61 100644 --- a/tests/RHITestChassis/ValidateCommandBufferImpl.hpp +++ b/tests/RHITestChassis/ValidateCommandBufferImpl.hpp @@ -16,961 +16,951 @@ using namespace std::chrono_literals; using namespace PyroshockStudios; using namespace PyroshockStudios::RHI; using namespace PyroshockStudios::Types; -// TEST_F(RHI_CONTEXT_FIXTURE_NAME, CommandsTransferCopyBufferToBufferSucceeds) { -// BufferInfo bufferInfo{}; -// bufferInfo.size = 2048; -// bufferInfo.usage = BufferUsageFlagBits::TRANSFER_SRC | BufferUsageFlagBits::TRANSFER_DST; -// TRACK_RHI_PARAMETER(bufferInfo); -// -// Buffer src = mDevice->CreateBuffer(bufferInfo); -// TRACK_RHI_HANDLE(src); -// Buffer dst = mDevice->CreateBuffer(bufferInfo); -// TRACK_RHI_HANDLE(dst); -// -// auto* cb = mDevice->GetCommandQueues()[0]->GetCommandBuffer({}); -// TRACK_RHI_HANDLE(cb); // cb is an ICommandBuffer*, which is a handle/pointer -// -// // Copy a subregion with offsets -// CopyBufferToBufferInfo copyInfo{}; -// copyInfo.srcBuffer = src; -// copyInfo.dstBuffer = dst; -// copyInfo.srcOffset = 256; // copy from 256 bytes in -// copyInfo.dstOffset = 512; // copy to 512 bytes in -// copyInfo.size = 512; // copy 512 bytes � fits easily within 2048 -// TRACK_RHI_PARAMETER(copyInfo); -// -// -// EXPECT_NO_FATAL_FAILURE(cb->BufferBarrier({ -// .buffer = src, -// .srcAccess = AccessConsts::NONE, -// .dstAccess = AccessConsts::TRANSFER_READ, -// .srcLayout = BufferLayout::Undefined, -// .dstLayout = BufferLayout::TransferSrc, -// })); // Note: Anonymous structs don't get a macro, but their contents are covered by main structs/handles -// EXPECT_NO_FATAL_FAILURE(cb->BufferBarrier({ -// .buffer = dst, -// .srcAccess = AccessConsts::NONE, -// .dstAccess = AccessConsts::TRANSFER_WRITE, -// .srcLayout = BufferLayout::Undefined, -// .dstLayout = BufferLayout::TransferDst, -// })); -// EXPECT_NO_FATAL_FAILURE(cb->CopyBufferToBuffer(copyInfo)); -// -// cb->Complete(); -// mDevice->GetCommandQueues()[0]->SubmitCommandBuffer(cb); -// mDevice->SubmitQueue({ .queue = mDevice->GetCommandQueues()[0] }); // queue is a pointer/handle -// mDevice->WaitIdle(); -// -// mDevice->DestroyBuffer(src); -// mDevice->DestroyBuffer(dst); -// } -// -// TEST_F(RHI_CONTEXT_FIXTURE_NAME, CommandsTransferCopyBufferToImageSucceeds) { -// ImageInfo imageInfo{}; -// imageInfo.dimensions = ImageDimensions::e3D; -// imageInfo.size = { 16, 16, 4 }; -// imageInfo.format = Format::RGBA8Unorm; -// imageInfo.usage = ImageUsageFlagBits::TRANSFER_DST; -// TRACK_RHI_PARAMETER(imageInfo); -// -// Image image = mDevice->CreateImage(imageInfo); -// TRACK_RHI_HANDLE(image); -// -// auto* cb = mDevice->GetCommandQueues()[0]->GetCommandBuffer({}); -// TRACK_RHI_HANDLE(cb); -// -// CopyBufferToImageInfo info{}; -// info.image = image; -// info.imageExtent = { 8, 4, 2 }; // copy smaller region (half width) -// info.imageOffset = { 4, 4, 1 }; // offset to middle of image -// info.rowPitch = 8 * RHIUtil::GetFormatSize(imageInfo.format); -// info.rowPitch = mDevice->ImageSubresourceRowPitch(image, info.rowPitch); -// -// info.bufferOffset = PYRO_ALIGN(128, mDevice->Properties().bufferImageCopyOffsetAlignment); -// BufferInfo bufferInfo{}; -// bufferInfo.size = PYRO_ALIGN(info.rowPitch * info.imageExtent.height * info.imageExtent.depth + info.bufferOffset, 256); -// bufferInfo.usage = BufferUsageFlagBits::TRANSFER_SRC; -// TRACK_RHI_PARAMETER(bufferInfo); -// -// Buffer buffer = mDevice->CreateBuffer(bufferInfo); -// TRACK_RHI_HANDLE(buffer); -// info.buffer = buffer; -// TRACK_RHI_PARAMETER(info); -// -// -// EXPECT_NO_FATAL_FAILURE(cb->BufferBarrier({ -// .buffer = buffer, -// .srcAccess = AccessConsts::NONE, -// .dstAccess = AccessConsts::TRANSFER_READ, -// .srcLayout = BufferLayout::Undefined, -// .dstLayout = BufferLayout::TransferSrc, -// })); -// EXPECT_NO_FATAL_FAILURE(cb->ImageBarrier({ -// .image = image, -// .srcAccess = AccessConsts::NONE, -// .dstAccess = AccessConsts::TRANSFER_WRITE, -// .srcLayout = ImageLayout::Undefined, -// .dstLayout = ImageLayout::TransferDst, -// })); -// EXPECT_NO_FATAL_FAILURE(cb->CopyBufferToImage(info)); -// -// cb->Complete(); -// mDevice->GetCommandQueues()[0]->SubmitCommandBuffer(cb); -// mDevice->SubmitQueue({ .queue = mDevice->GetCommandQueues()[0] }); -// mDevice->WaitIdle(); -// -// mDevice->DestroyBuffer(buffer); -// mDevice->DestroyImage(image); -// } -// TEST_F(RHI_CONTEXT_FIXTURE_NAME, CommandsTransferCopyImageToBufferSucceeds) { -// ImageInfo srcImageInfo{}; -// srcImageInfo.dimensions = ImageDimensions::e3D; -// srcImageInfo.size = { 16, 16, 4 }; -// srcImageInfo.format = Format::RGBA8Unorm; -// srcImageInfo.usage = ImageUsageFlagBits::TRANSFER_SRC; -// TRACK_RHI_PARAMETER(srcImageInfo); -// -// Image srcImage = mDevice->CreateImage(srcImageInfo); -// TRACK_RHI_HANDLE(srcImage); -// -// auto* cb = mDevice->GetCommandQueues()[0]->GetCommandBuffer({}); -// TRACK_RHI_HANDLE(cb); -// -// CopyImageToBufferInfo info{}; -// info.image = srcImage; -// info.imageExtent = { 8, 4, 2 }; // smaller region -// info.imageOffset = { 2, 3, 1 }; // subregion of the image -// info.rowPitch = 8 * RHIUtil::GetFormatSize(srcImageInfo.format); -// info.rowPitch = mDevice->ImageSubresourceRowPitch(srcImage, info.rowPitch); -// -// // Calculate safe buffer size = rowPitch * height * depth + offset margin -// BufferInfo bufferInfo{}; -// const u64 regionBytes = static_cast(info.rowPitch) * info.imageExtent.height * info.imageExtent.depth; -// info.bufferOffset = PYRO_ALIGN(128, mDevice->Properties().bufferImageCopyOffsetAlignment); -// bufferInfo.size = PYRO_ALIGN(regionBytes + info.bufferOffset, 256); // <-- ensure valid total size -// bufferInfo.usage = BufferUsageFlagBits::TRANSFER_DST; -// TRACK_RHI_PARAMETER(bufferInfo); -// -// Buffer dstBuffer = mDevice->CreateBuffer(bufferInfo); -// TRACK_RHI_HANDLE(dstBuffer); -// info.buffer = dstBuffer; -// TRACK_RHI_PARAMETER(info); -// -// EXPECT_NO_FATAL_FAILURE(cb->ImageBarrier({ -// .image = srcImage, -// .srcAccess = AccessConsts::NONE, -// .dstAccess = AccessConsts::TRANSFER_READ, -// .srcLayout = ImageLayout::Undefined, -// .dstLayout = ImageLayout::TransferSrc, -// })); -// EXPECT_NO_FATAL_FAILURE(cb->BufferBarrier({ -// .buffer = dstBuffer, -// .srcAccess = AccessConsts::NONE, -// .dstAccess = AccessConsts::TRANSFER_WRITE, -// .srcLayout = BufferLayout::Undefined, -// .dstLayout = BufferLayout::TransferDst, -// })); -// -// EXPECT_NO_FATAL_FAILURE(cb->CopyImageToBuffer(info)); -// -// cb->Complete(); -// mDevice->GetCommandQueues()[0]->SubmitCommandBuffer(cb); -// mDevice->SubmitQueue({ .queue = mDevice->GetCommandQueues()[0] }); -// mDevice->WaitIdle(); -// -// mDevice->DestroyImage(srcImage); -// mDevice->DestroyBuffer(dstBuffer); -// } -// -// TEST_F(RHI_CONTEXT_FIXTURE_NAME, CommandsTransferCopyImageToImageSucceeds) { -// ImageInfo srcImageInfo{}; -// srcImageInfo.size = { 16, 16, 1 }; -// srcImageInfo.format = Format::RGBA8Unorm; -// srcImageInfo.usage = ImageUsageFlagBits::TRANSFER_SRC; -// TRACK_RHI_PARAMETER(srcImageInfo); -// Image srcImage = mDevice->CreateImage(srcImageInfo); -// TRACK_RHI_HANDLE(srcImage); -// -// ImageInfo dstImageInfo{}; -// dstImageInfo.size = { 16, 16, 1 }; -// dstImageInfo.format = Format::RGBA8Unorm; -// dstImageInfo.usage = ImageUsageFlagBits::TRANSFER_DST; -// TRACK_RHI_PARAMETER(dstImageInfo); -// Image dstImage = mDevice->CreateImage(dstImageInfo); -// TRACK_RHI_HANDLE(dstImage); -// -// auto* cb = mDevice->GetCommandQueues()[0]->GetCommandBuffer({}); -// TRACK_RHI_HANDLE(cb); -// -// CopyImageToImageInfo info{}; -// info.srcImage = srcImage; -// info.dstImage = dstImage; -// info.srcOffset = { 4, 4, 0 }; // copy from center -// info.dstOffset = { 2, 2, 0 }; // copy to different offset -// info.extent = { 8, 8, 1 }; // partial region -// TRACK_RHI_PARAMETER(info); -// -// EXPECT_NO_FATAL_FAILURE(cb->ImageBarrier({ -// .image = srcImage, -// .srcAccess = AccessConsts::NONE, -// .dstAccess = AccessConsts::TRANSFER_READ, -// .srcLayout = ImageLayout::Undefined, -// .dstLayout = ImageLayout::TransferSrc, -// })); -// EXPECT_NO_FATAL_FAILURE(cb->ImageBarrier({ -// .image = dstImage, -// .srcAccess = AccessConsts::NONE, -// .dstAccess = AccessConsts::TRANSFER_WRITE, -// .srcLayout = ImageLayout::Undefined, -// .dstLayout = ImageLayout::TransferDst, -// })); -// EXPECT_NO_FATAL_FAILURE(cb->CopyImageToImage(info)); -// -// cb->Complete(); -// mDevice->GetCommandQueues()[0]->SubmitCommandBuffer(cb); -// mDevice->SubmitQueue({ .queue = mDevice->GetCommandQueues()[0] }); -// mDevice->WaitIdle(); -// -// mDevice->DestroyImage(srcImage); -// mDevice->DestroyImage(dstImage); -// } -// -// -// TEST_F(RHI_CONTEXT_FIXTURE_NAME, CommandsTransferClearUAVBufferSucceeds) { -// BufferInfo bufferInfo{}; -// bufferInfo.size = 1024; -// bufferInfo.usage = BufferUsageFlagBits::UNORDERED_ACCESS; -// bufferInfo.initialLayout = BufferLayout::UnorderedAccess; -// TRACK_RHI_PARAMETER(bufferInfo); -// -// Buffer buffer = mDevice->CreateBuffer(bufferInfo); -// TRACK_RHI_HANDLE(buffer); -// auto uav = mDevice->CreateUnorderedAccess(BufferResourceInfo{ .buffer = buffer }); -// TRACK_RHI_HANDLE(uav); -// -// auto* cb = mDevice->GetCommandQueues()[0]->GetCommandBuffer({}); -// TRACK_RHI_HANDLE(cb); -// -// ClearUnorderedAccessViewInfo info{}; -// info.view = uav; -// info.clearValue = { 0.0f, 0.0f, 0.0f, 0.0f }; -// TRACK_RHI_PARAMETER(info); -// -// -// EXPECT_NO_FATAL_FAILURE(cb->BufferBarrier({ -// .buffer = buffer, -// .srcAccess = AccessConsts::NONE, -// .dstAccess = AccessConsts::CLEAR_WRITE, -// .srcLayout = BufferLayout::UnorderedAccess, -// .dstLayout = BufferLayout::UnorderedAccess, -// })); -// EXPECT_NO_FATAL_FAILURE(cb->ClearUnorderedAccessView(info)); -// -// cb->Complete(); -// mDevice->GetCommandQueues()[0]->SubmitCommandBuffer(cb); -// mDevice->SubmitQueue({ .queue = mDevice->GetCommandQueues()[0] }); -// mDevice->WaitIdle(); -// -// mDevice->DestroyUnorderedAccess(uav); -// mDevice->DestroyBuffer(buffer); -// } -// -// TEST_F(RHI_CONTEXT_FIXTURE_NAME, CommandsTransferClearUAVImageSucceeds) { -// ImageInfo imageInfo{}; -// imageInfo.size = { 256, 256, 1 }; -// imageInfo.format = Format::RGBA16Unorm; -// imageInfo.usage = ImageUsageFlagBits::UNORDERED_ACCESS; -// imageInfo.name = ""; -// TRACK_RHI_PARAMETER(imageInfo); -// -// Image image = mDevice->CreateImage(imageInfo); -// TRACK_RHI_HANDLE(image); -// auto uav = mDevice->CreateUnorderedAccess(ImageResourceInfo{ .image = image }); -// TRACK_RHI_HANDLE(uav); -// -// auto* cb = mDevice->GetCommandQueues()[0]->GetCommandBuffer({}); -// TRACK_RHI_HANDLE(cb); -// -// ClearUnorderedAccessViewInfo info{}; -// info.view = uav; -// info.clearValue = { 0.1f, 0.2f, 0.3f, 0.4f }; -// TRACK_RHI_PARAMETER(info); -// -// -// EXPECT_NO_FATAL_FAILURE(cb->ImageBarrier({ -// .image = image, -// .srcAccess = AccessConsts::NONE, -// .dstAccess = AccessConsts::CLEAR_WRITE, -// .srcLayout = ImageLayout::Undefined, -// .dstLayout = ImageLayout::UnorderedAccess, -// })); -// EXPECT_NO_FATAL_FAILURE(cb->ClearUnorderedAccessView(info)); -// -// cb->Complete(); -// mDevice->GetCommandQueues()[0]->SubmitCommandBuffer(cb); -// mDevice->SubmitQueue({ .queue = mDevice->GetCommandQueues()[0] }); -// mDevice->WaitIdle(); -// -// mDevice->DestroyUnorderedAccess(uav); -// mDevice->DestroyImage(image); -// } -// -// -// TEST_F(RHI_CONTEXT_FIXTURE_NAME, MultiCommandBufferSyncSubmit) { -// // Create a small 2D image and a buffer we can copy from -// ImageInfo imageInfo{}; -// imageInfo.size = { 16, 16, 1 }; -// imageInfo.format = Format::RGBA8Unorm; -// imageInfo.usage = ImageUsageFlagBits::TRANSFER_DST | ImageUsageFlagBits::UNORDERED_ACCESS; -// TRACK_RHI_PARAMETER(imageInfo); -// Image image = mDevice->CreateImage(imageInfo); -// TRACK_RHI_HANDLE(image); -// -// // Prepare the buffer (sized for a small subregion copy like other tests) -// CopyBufferToImageInfo info{}; -// info.image = image; -// info.imageExtent = { 16, 16, 1 }; -// info.rowPitch = static_cast(info.imageExtent.width * RHIUtil::GetFormatSize(imageInfo.format)); -// info.rowPitch = mDevice->ImageSubresourceRowPitch(image, info.rowPitch); -// -// BufferInfo bufferInfo{}; -// bufferInfo.size = static_cast(info.rowPitch) * info.imageExtent.height * info.imageExtent.depth; -// bufferInfo.usage = BufferUsageFlagBits::TRANSFER_SRC; -// TRACK_RHI_PARAMETER(bufferInfo); -// Buffer buffer = mDevice->CreateBuffer(bufferInfo); -// TRACK_RHI_HANDLE(buffer); -// info.buffer = buffer; -// TRACK_RHI_PARAMETER(info); -// -// // Create a UAV for the image (used in the second command buffer) -// auto uav = mDevice->CreateUnorderedAccess(ImageResourceInfo{ .image = image }); -// TRACK_RHI_HANDLE(uav); -// -// // Get the queue & prepare two command buffers -// ICommandQueue* queue = mDevice->GetCommandQueues()[0]; -// TRACK_RHI_HANDLE(queue); -// auto* cbCopy = queue->GetCommandBuffer({ .name = "Copy Commands" }); -// TRACK_RHI_HANDLE(cbCopy); -// auto* cbClear = queue->GetCommandBuffer({ .name = "Clear Commands" }); -// TRACK_RHI_HANDLE(cbClear); -// -// // --- Command buffer A: copy buffer -> image --- -// EXPECT_NO_FATAL_FAILURE(cbCopy->BufferBarrier({ -// .buffer = buffer, -// .srcAccess = AccessConsts::NONE, -// .dstAccess = AccessConsts::TRANSFER_READ, -// .srcLayout = BufferLayout::Undefined, -// .dstLayout = BufferLayout::TransferSrc, -// })); -// EXPECT_NO_FATAL_FAILURE(cbCopy->ImageBarrier({ -// .image = image, -// .srcAccess = AccessConsts::NONE, -// .dstAccess = AccessConsts::TRANSFER_WRITE, -// .srcLayout = ImageLayout::Undefined, -// .dstLayout = ImageLayout::TransferDst, -// })); -// EXPECT_NO_FATAL_FAILURE(cbCopy->CopyBufferToImage(info)); -// cbCopy->Complete(); -// -// // Submit the copy command buffer to the queue (but do not SubmitQueue yet) -// queue->SubmitCommandBuffer(cbCopy); -// -// // --- Command buffer B: transition to UAV and clear --- -// // The image is expected to be in TransferDst after cbCopy. We now transition it -// // to UnorderedAccess for the clear. Follow the same style as your other tests. -// ClearUnorderedAccessViewInfo clearInfo{}; -// clearInfo.view = uav; -// clearInfo.clearValue = { 0.25f, 0.5f, 0.75f, 1.0f }; -// TRACK_RHI_PARAMETER(clearInfo); -// -// -// EXPECT_NO_FATAL_FAILURE(cbClear->ImageBarrier({ -// .image = image, -// .srcAccess = AccessConsts::TRANSFER_WRITE, -// .dstAccess = AccessConsts::CLEAR_WRITE, -// .srcLayout = ImageLayout::TransferDst, -// .dstLayout = ImageLayout::UnorderedAccess, -// })); -// EXPECT_NO_FATAL_FAILURE(cbClear->ClearUnorderedAccessView(clearInfo)); -// cbClear->Complete(); -// -// // Submit the clear command buffer to the queue -// queue->SubmitCommandBuffer(cbClear); -// -// // Now flush the queue (this should submit both cmd buffers in the order they were queued) -// mDevice->SubmitQueue({ .queue = queue }); -// -// // Wait for work to finish and cleanup -// mDevice->WaitIdle(); -// -// mDevice->DestroyUnorderedAccess(uav); -// mDevice->DestroyBuffer(buffer); -// mDevice->DestroyImage(image); -// } -// -// TEST_F(RHI_CONTEXT_FIXTURE_NAME, MultiThreadedCommandBufferRecordingSubmitOrder) { -// // --- Resource setup --- -// ImageInfo imageInfo{}; -// imageInfo.size = { 16, 16, 1 }; -// imageInfo.format = Format::RGBA8Unorm; -// imageInfo.usage = ImageUsageFlagBits::TRANSFER_DST | ImageUsageFlagBits::UNORDERED_ACCESS; -// TRACK_RHI_PARAMETER(imageInfo); -// Image image = mDevice->CreateImage(imageInfo); -// TRACK_RHI_HANDLE(image); -// -// CopyBufferToImageInfo copyInfo{}; -// copyInfo.image = image; -// copyInfo.imageExtent = { 16, 16, 1 }; -// copyInfo.rowPitch = static_cast(copyInfo.imageExtent.width * RHIUtil::GetFormatSize(imageInfo.format)); -// copyInfo.rowPitch = mDevice->ImageSubresourceRowPitch(image, copyInfo.rowPitch); -// -// BufferInfo bufferInfo{}; -// bufferInfo.size = static_cast(copyInfo.rowPitch) * copyInfo.imageExtent.height * copyInfo.imageExtent.depth; -// bufferInfo.usage = BufferUsageFlagBits::TRANSFER_SRC; -// TRACK_RHI_PARAMETER(bufferInfo); -// Buffer buffer = mDevice->CreateBuffer(bufferInfo); -// TRACK_RHI_HANDLE(buffer); -// copyInfo.buffer = buffer; -// TRACK_RHI_PARAMETER(copyInfo); -// -// auto uav = mDevice->CreateUnorderedAccess(ImageResourceInfo{ .image = image }); -// TRACK_RHI_HANDLE(uav); -// -// ICommandQueue* queue = mDevice->GetCommandQueues()[0]; -// TRACK_RHI_HANDLE(queue); -// -// // --- Prepare two command buffers --- -// auto* cbCopy = queue->GetCommandBuffer({ .name = "Parallel Copy CB" }); -// TRACK_RHI_HANDLE(cbCopy); -// auto* cbClear = queue->GetCommandBuffer({ .name = "Parallel Clear CB" }); -// TRACK_RHI_HANDLE(cbClear); -// -// // --- Multithreaded recording --- -// std::latch recordStart(1); -// std::atomic copyDone = false; -// std::atomic clearDone = false; -// -// std::jthread copyThread([&](std::stop_token) { -// recordStart.wait(); -// -// EXPECT_NO_FATAL_FAILURE(cbCopy->BufferBarrier({ -// .buffer = buffer, -// .srcAccess = AccessConsts::NONE, -// .dstAccess = AccessConsts::TRANSFER_READ, -// .srcLayout = BufferLayout::Undefined, -// .dstLayout = BufferLayout::TransferSrc, -// })); -// EXPECT_NO_FATAL_FAILURE(cbCopy->ImageBarrier({ -// .image = image, -// .srcAccess = AccessConsts::NONE, -// .dstAccess = AccessConsts::TRANSFER_WRITE, -// .srcLayout = ImageLayout::Undefined, -// .dstLayout = ImageLayout::TransferDst, -// })); -// EXPECT_NO_FATAL_FAILURE(cbCopy->CopyBufferToImage(copyInfo)); -// cbCopy->Complete(); -// copyDone = true; -// }); -// -// std::jthread clearThread([&](std::stop_token) { -// recordStart.wait(); -// -// ClearUnorderedAccessViewInfo clearInfo{}; -// clearInfo.view = uav; -// clearInfo.clearValue = { 0.25f, 0.5f, 0.75f, 1.0f }; -// TRACK_RHI_PARAMETER(clearInfo); -// -// -// EXPECT_NO_FATAL_FAILURE(cbClear->ImageBarrier({ -// .image = image, -// .srcAccess = AccessConsts::TRANSFER_WRITE, -// .dstAccess = AccessConsts::CLEAR_WRITE, -// .srcLayout = ImageLayout::TransferDst, -// .dstLayout = ImageLayout::UnorderedAccess, -// })); -// EXPECT_NO_FATAL_FAILURE(cbClear->ClearUnorderedAccessView(clearInfo)); -// cbClear->Complete(); -// clearDone = true; -// }); -// -// // Start both recording threads simultaneously -// recordStart.count_down(); -// -// // Wait for both threads to complete recording -// using namespace std::chrono_literals; -// const auto start = std::chrono::steady_clock::now(); -// while ((!copyDone || !clearDone) && std::chrono::steady_clock::now() - start < 5s) -// std::this_thread::sleep_for(10ms); -// -// ASSERT_TRUE(copyDone && clearDone) << "Command buffer recording did not complete within timeout."; -// copyThread.request_stop(); -// clearThread.request_stop(); -// -// // --- Sequential submission order check --- -// // Submit copy first, clear second -// queue->SubmitCommandBuffer(cbCopy); -// queue->SubmitCommandBuffer(cbClear); -// -// // Flush queue (submit all) -// mDevice->SubmitQueue({ .queue = queue }); -// -// // Wait for completion -// mDevice->WaitIdle(); -// -// // --- Cleanup --- -// mDevice->DestroyUnorderedAccess(uav); -// mDevice->DestroyBuffer(buffer); -// mDevice->DestroyImage(image); -// } -// -// -// TEST_F(RHI_CONTEXT_FIXTURE_NAME, CommandsUpdateBuffer64kbSucceeds) { -// BufferInfo bufferInfo{}; -// bufferInfo.size = 65536; -// bufferInfo.usage = BufferUsageFlagBits::TRANSFER_DST; -// bufferInfo.initialLayout = BufferLayout::TransferDst; -// TRACK_RHI_PARAMETER(bufferInfo); -// -// Buffer buffer = mDevice->CreateBuffer(bufferInfo); -// TRACK_RHI_HANDLE(buffer); -// -// auto* cb = mDevice->GetCommandQueues()[0]->GetCommandBuffer({}); -// TRACK_RHI_HANDLE(cb); -// -// eastl::vector data(65536, static_cast(76)); -// -// EXPECT_NO_FATAL_FAILURE(cb->UpdateBuffer({ -// .buffer = buffer, -// .region = { .size = data.size() }, -// .data = data.data(), -// })); -// -// cb->Complete(); -// mDevice->GetCommandQueues()[0]->SubmitCommandBuffer(cb); -// mDevice->SubmitQueue({ .queue = mDevice->GetCommandQueues()[0] }); -// mDevice->WaitIdle(); -// -// mDevice->DestroyBuffer(buffer); -// } -// -// -// -// TEST_F(RHI_CONTEXT_FIXTURE_NAME, CommandsUpdateBuffer64kbSucceedsWithOffset) { -// BufferInfo bufferInfo{}; -// bufferInfo.size = 65536 + 256; -// bufferInfo.usage = BufferUsageFlagBits::TRANSFER_DST; -// bufferInfo.initialLayout = BufferLayout::TransferDst; -// TRACK_RHI_PARAMETER(bufferInfo); -// -// Buffer buffer = mDevice->CreateBuffer(bufferInfo); -// TRACK_RHI_HANDLE(buffer); -// -// auto* cb = mDevice->GetCommandQueues()[0]->GetCommandBuffer({}); -// TRACK_RHI_HANDLE(cb); -// -// eastl::vector data(65536, static_cast(76)); -// -// EXPECT_NO_FATAL_FAILURE(cb->UpdateBuffer({ -// .buffer = buffer, -// .region = { .offset = 256, .size = data.size() }, -// .data = data.data(), -// })); -// -// cb->Complete(); -// mDevice->GetCommandQueues()[0]->SubmitCommandBuffer(cb); -// mDevice->SubmitQueue({ .queue = mDevice->GetCommandQueues()[0] }); -// mDevice->WaitIdle(); -// -// mDevice->DestroyBuffer(buffer); -// } -// -// TEST_F(RHI_CONTEXT_FIXTURE_NAME, CommandsTimestampQueryPoolResetSetSuccess) { -// ImageInfo imageInfo{}; -// imageInfo.size = { 256, 256, 1 }; -// imageInfo.format = Format::RGBA16Unorm; -// imageInfo.usage = ImageUsageFlagBits::UNORDERED_ACCESS; -// imageInfo.name = "uav test"; -// TRACK_RHI_PARAMETER(imageInfo); -// -// Image image = mDevice->CreateImage(imageInfo); -// TRACK_RHI_HANDLE(image); -// ITimestampQueryPool* qp = mDevice->CreateTimestampQueryPool({ .queryCount = 2, .name = " qp" }); -// TRACK_RHI_HANDLE(qp); -// auto uav = mDevice->CreateUnorderedAccess(ImageResourceInfo{ .image = image }); -// TRACK_RHI_HANDLE(uav); -// -// auto* cb = mDevice->GetCommandQueues()[0]->GetCommandBuffer({}); -// TRACK_RHI_HANDLE(cb); -// -// ClearUnorderedAccessViewInfo info{}; -// info.view = uav; -// info.clearValue = { 0.1f, 0.2f, 0.3f, 0.4f }; -// TRACK_RHI_PARAMETER(info); -// -// -// EXPECT_NO_FATAL_FAILURE(cb->InvalidateTimestampQuery({ .queryPool = qp, .queryCount = 2 })); -// -// EXPECT_NO_FATAL_FAILURE(cb->WriteTimestamp({ -// .queryPool = qp, -// .stage = PipelineStageFlagBits::CLEAR, -// .queryIndex = 0, -// })); -// EXPECT_NO_FATAL_FAILURE(cb->ImageBarrier({ -// .image = image, -// .srcAccess = AccessConsts::NONE, -// .dstAccess = AccessConsts::CLEAR_WRITE, -// .srcLayout = ImageLayout::Undefined, -// .dstLayout = ImageLayout::UnorderedAccess, -// })); -// EXPECT_NO_FATAL_FAILURE(cb->ClearUnorderedAccessView(info)); -// -// EXPECT_NO_FATAL_FAILURE(cb->WriteTimestamp({ -// .queryPool = qp, -// .stage = PipelineStageFlagBits::CLEAR, -// .queryIndex = 1, -// })); -// -// cb->Complete(); -// mDevice->GetCommandQueues()[0]->SubmitCommandBuffer(cb); -// mDevice->SubmitQueue({ .queue = mDevice->GetCommandQueues()[0] }); -// mDevice->WaitIdle(); -// -// eastl::span stamps; -// EXPECT_NO_FATAL_FAILURE(stamps = qp->GetTimestamps(0, 2)); -// EXPECT_NE(stamps[0], ~(0ULL)); -// EXPECT_NE(stamps[1], ~(0ULL)); -// ASSERT_GE(stamps[1], stamps[0]); -// -// mDevice->DestroyUnorderedAccess(uav); -// mDevice->DestroyImage(image); -// mDevice->DestroyTimestampQueryPool(qp); -// } -// -// -// TEST_F(RHI_CONTEXT_FIXTURE_NAME, CommandsRecycleSuccess) { -// static constexpr i32 NUM_CYCLES = 16; -// -// ICommandQueue* cq = mDevice->GetCommandQueues()[0]; -// TRACK_RHI_HANDLE(cq); -// -// eastl::vector> toDestroyCb{}; -// for (i32 i = 0; i < NUM_CYCLES; ++i) { -// BufferInfo bufferInfo{}; -// bufferInfo.usage = BufferUsageFlagBits::TRANSFER_DST; -// bufferInfo.name = "dst buffer in flight #" + eastl::to_string(i); -// bufferInfo.size = 256; -// TRACK_RHI_PARAMETER(bufferInfo); -// Buffer dstBuffer = mDevice->CreateBuffer(bufferInfo); -// TRACK_RHI_HANDLE(dstBuffer); -// ASSERT_TRUE(mDevice->IsValid(dstBuffer)); -// toDestroyCb.emplace_back([=]() { mDevice->DestroyImmediately(dstBuffer); }); -// -// eastl::vector data(static_cast(bufferInfo.size)); -// -// ICommandBuffer* cb = cq->GetCommandBuffer({ .name = "recycled commands #" + eastl::to_string(i) }); -// TRACK_RHI_HANDLE(cb); -// -// eastl::string framename1 = "Record frame #" + eastl::to_string(i); -// eastl::string framename = "Transition Resources " + eastl::to_string(i); -// cb->BeginLabel({ .name = framename1 }); -// cb->BeginLabel({ .name = framename }); -// EXPECT_NO_FATAL_FAILURE(cb->BufferBarrier({ -// .buffer = dstBuffer, -// .srcAccess = AccessConsts::NONE, -// .dstAccess = AccessConsts::TRANSFER_WRITE, -// .srcLayout = BufferLayout::Undefined, -// .dstLayout = BufferLayout::TransferDst, -// })); -// cb->EndLabel(); -// -// UpdateBufferInfo info{}; -// info.buffer = dstBuffer; -// info.data = data.data(); -// TRACK_RHI_PARAMETER(info); -// -// EXPECT_NO_FATAL_FAILURE(cb->UpdateBuffer(info)); -// cb->EndLabel(); -// -// cb->Complete(); -// cq->SubmitCommandBuffer(cb); -// mDevice->SubmitQueue({ .queue = cq }); -// } -// mDevice->WaitIdle(); -// for (auto destroy : toDestroyCb) { -// destroy(); -// } -// } -// -// TEST_F(RHI_CONTEXT_FIXTURE_NAME, CommandsClearColorPass) { -// ICommandQueue* q = mDevice->GetCommandQueues()[0]; -// TRACK_RHI_HANDLE(q); -// -// ImageInfo srcImageInfo{}; -// srcImageInfo.dimensions = ImageDimensions::e2D; -// srcImageInfo.size = { 16, 16, 1 }; -// srcImageInfo.format = Format::RGBA8Unorm; -// srcImageInfo.usage = ImageUsageFlagBits::RENDER_TARGET; -// srcImageInfo.name = "color target"; -// TRACK_RHI_PARAMETER(srcImageInfo); -// Image srcImage = mDevice->CreateImage(srcImageInfo); -// TRACK_RHI_HANDLE(srcImage); -// ASSERT_TRUE(mDevice->IsValid(srcImage)); -// -// RenderTarget srcTarget = mDevice->CreateRenderTarget({ .image = srcImage, .flags = RenderTargetFlagBits::COLOR_TARGET, .name = "render target" }); -// TRACK_RHI_HANDLE(srcTarget); -// -// ICommandBuffer* cb = q->GetCommandBuffer({}); -// TRACK_RHI_HANDLE(cb); -// -// EXPECT_NO_FATAL_FAILURE(cb->ImageBarrier({ -// .image = srcImage, -// .srcAccess = AccessConsts::NONE, -// .dstAccess = AccessConsts::COLOR_ATTACHMENT_OUTPUT_WRITE, -// .srcLayout = ImageLayout::Undefined, -// .dstLayout = ImageLayout::RenderTarget, -// })); -// -// RenderPassBeginInfo rpBeginInfo{}; -// rpBeginInfo.colorAttachments = { -// ColorAttachmentInfo{ -// .target = srcTarget, -// .loadOp = AttachmentLoadOp::Clear, -// .clearValue = { 1.0f, 1.0f, 1.0f, 1.0f } }, -// }; -// rpBeginInfo.renderArea = { .x = 0, .y = 0, .width = 16, .height = 16 }; -// TRACK_RHI_PARAMETER(rpBeginInfo); -// -// EXPECT_NO_FATAL_FAILURE(cb->BeginRenderPass(rpBeginInfo)); -// -// EXPECT_NO_FATAL_FAILURE(cb->EndRenderPass()); -// -// cb->Complete(); -// mDevice->GetCommandQueues()[0]->SubmitCommandBuffer(cb); -// mDevice->SubmitQueue({ .queue = mDevice->GetCommandQueues()[0] }); -// mDevice->WaitIdle(); -// -// mDevice->DestroyImmediately(srcTarget); -// mDevice->DestroyImmediately(srcImage); -// } -// -// -// TEST_F(RHI_CONTEXT_FIXTURE_NAME, CommandsClearColorPassThenDontCareLoad) { -// ICommandQueue* q = mDevice->GetCommandQueues()[0]; -// TRACK_RHI_HANDLE(q); -// -// ImageInfo srcImageInfo{}; -// srcImageInfo.dimensions = ImageDimensions::e2D; -// srcImageInfo.size = { 16, 16, 1 }; -// srcImageInfo.format = Format::RGBA8Unorm; -// srcImageInfo.usage = ImageUsageFlagBits::RENDER_TARGET; -// srcImageInfo.name = "color target"; -// TRACK_RHI_PARAMETER(srcImageInfo); -// Image srcImage = mDevice->CreateImage(srcImageInfo); -// TRACK_RHI_HANDLE(srcImage); -// ASSERT_TRUE(mDevice->IsValid(srcImage)); -// -// RenderTarget srcTarget = mDevice->CreateRenderTarget({ .image = srcImage, .flags = RenderTargetFlagBits::COLOR_TARGET, .name = "render target" }); -// TRACK_RHI_HANDLE(srcTarget); -// -// ICommandBuffer* cb = q->GetCommandBuffer({}); -// TRACK_RHI_HANDLE(cb); -// -// EXPECT_NO_FATAL_FAILURE(cb->ImageBarrier({ -// .image = srcImage, -// .srcAccess = AccessConsts::NONE, -// .dstAccess = AccessConsts::COLOR_ATTACHMENT_OUTPUT_WRITE, -// .srcLayout = ImageLayout::Undefined, -// .dstLayout = ImageLayout::RenderTarget, -// })); -// -// RenderPassBeginInfo rpBeginInfo1{}; -// rpBeginInfo1.colorAttachments = { -// ColorAttachmentInfo{ -// .target = srcTarget, -// .loadOp = AttachmentLoadOp::Clear, -// .storeOp = AttachmentStoreOp::Store, -// .clearValue = { 1.0f, 1.0f, 1.0f, 1.0f } }, -// }; -// rpBeginInfo1.renderArea = { .x = 0, .y = 0, .width = 16, .height = 16 }; -// TRACK_RHI_PARAMETER(rpBeginInfo1); -// -// EXPECT_NO_FATAL_FAILURE(cb->BeginRenderPass(rpBeginInfo1)); -// -// EXPECT_NO_FATAL_FAILURE(cb->EndRenderPass()); -// -// -// RenderPassBeginInfo rpBeginInfo2{}; -// rpBeginInfo2.colorAttachments = { -// ColorAttachmentInfo{ -// .target = srcTarget, -// .loadOp = AttachmentLoadOp::DontCare, -// }, -// }; -// rpBeginInfo2.renderArea = { .x = 0, .y = 0, .width = 16, .height = 16 }; -// TRACK_RHI_PARAMETER(rpBeginInfo2); -// -// EXPECT_NO_FATAL_FAILURE(cb->BeginRenderPass(rpBeginInfo2)); -// -// EXPECT_NO_FATAL_FAILURE(cb->EndRenderPass()); -// -// cb->Complete(); -// mDevice->GetCommandQueues()[0]->SubmitCommandBuffer(cb); -// mDevice->SubmitQueue({ .queue = mDevice->GetCommandQueues()[0] }); -// mDevice->WaitIdle(); -// -// mDevice->DestroyImmediately(srcTarget); -// mDevice->DestroyImmediately(srcImage); -// } -// -// TEST_F(RHI_CONTEXT_FIXTURE_NAME, CommandsClearColorPassMSAAResolve) { -// ICommandQueue* q = mDevice->GetCommandQueues()[0]; -// TRACK_RHI_HANDLE(q); -// -// ImageInfo srcImageMSInfo{}; -// srcImageMSInfo.dimensions = ImageDimensions::e2D; -// srcImageMSInfo.size = { 64, 64, 1 }; -// srcImageMSInfo.format = Format::RGBA8Unorm; -// srcImageMSInfo.usage = ImageUsageFlagBits::RENDER_TARGET; -// srcImageMSInfo.name = "color target MS"; -// srcImageMSInfo.sampleCount = HIGHEST_BIT(mDevice->Properties().msaaSupportColorTarget); -// if (srcImageMSInfo.sampleCount == RasterizationSamples::e1) { -// GTEST_LOG_(INFO) << "Device does not support MSAA, skipping test..."; -// return; -// } -// TRACK_RHI_PARAMETER(srcImageMSInfo); -// Image srcImageMS = mDevice->CreateImage(srcImageMSInfo); -// TRACK_RHI_HANDLE(srcImageMS); -// ASSERT_TRUE(mDevice->IsValid(srcImageMS)); -// -// ImageInfo dstImageResolveInfo{}; -// dstImageResolveInfo.dimensions = ImageDimensions::e2D; -// dstImageResolveInfo.size = { 64, 64, 1 }; -// dstImageResolveInfo.format = Format::RGBA8Unorm; -// dstImageResolveInfo.usage = ImageUsageFlagBits::RENDER_TARGET; -// dstImageResolveInfo.name = "color target (resolve)"; -// TRACK_RHI_PARAMETER(dstImageResolveInfo); -// Image dstImageResolve = mDevice->CreateImage(dstImageResolveInfo); -// TRACK_RHI_HANDLE(dstImageResolve); -// ASSERT_TRUE(mDevice->IsValid(dstImageResolve)); -// -// RenderTarget srcTargetMS = mDevice->CreateRenderTarget({ .image = srcImageMS, .flags = RenderTargetFlagBits::COLOR_TARGET, .name = "render target MS" }); -// TRACK_RHI_HANDLE(srcTargetMS); -// RenderTarget dstTargetResolve = mDevice->CreateRenderTarget({ .image = dstImageResolve, .flags = RenderTargetFlagBits::COLOR_TARGET, .name = "render target Resolve" }); -// TRACK_RHI_HANDLE(dstTargetResolve); -// -// ICommandBuffer* cb = q->GetCommandBuffer({}); -// TRACK_RHI_HANDLE(cb); -// -// EXPECT_NO_FATAL_FAILURE(cb->ImageBarrier({ -// .image = srcImageMS, -// .srcAccess = AccessConsts::NONE, -// .dstAccess = AccessConsts::COLOR_ATTACHMENT_OUTPUT_WRITE, -// .srcLayout = ImageLayout::Undefined, -// .dstLayout = ImageLayout::RenderTarget, -// })); -// EXPECT_NO_FATAL_FAILURE(cb->ImageBarrier({ -// .image = dstImageResolve, -// .srcAccess = AccessConsts::NONE, -// .dstAccess = AccessConsts::RESOLVE_WRITE, -// .srcLayout = ImageLayout::Undefined, -// .dstLayout = ImageLayout::RenderTarget, -// })); -// -// RenderPassBeginInfo rpBeginInfo{}; -// rpBeginInfo.colorAttachments = { -// ColorAttachmentInfo{ -// .target = srcTargetMS, -// .loadOp = AttachmentLoadOp::Clear, -// .clearValue = { 0.64f, 0.25f, 0.86f, 1.0f }, -// .resolve = eastl::make_optional(AttachmentResolveInfo{ .target = dstTargetResolve }), -// }, -// }; -// rpBeginInfo.renderArea = { .x = 0, .y = 0, .width = 64, .height = 64 }; -// TRACK_RHI_PARAMETER(rpBeginInfo); -// -// EXPECT_NO_FATAL_FAILURE(cb->BeginRenderPass(rpBeginInfo)); -// -// EXPECT_NO_FATAL_FAILURE(cb->EndRenderPass()); -// -// cb->Complete(); -// mDevice->GetCommandQueues()[0]->SubmitCommandBuffer(cb); -// mDevice->SubmitQueue({ .queue = mDevice->GetCommandQueues()[0] }); -// mDevice->WaitIdle(); -// -// mDevice->DestroyImmediately(srcTargetMS); -// mDevice->DestroyImmediately(dstTargetResolve); -// mDevice->DestroyImmediately(srcImageMS); -// mDevice->DestroyImmediately(dstImageResolve); -// } -// -// -// TEST_F(RHI_CONTEXT_FIXTURE_NAME, CommandsBlitImageArray) { -// auto* q = mDevice->GetCommandQueues()[0]; -// TRACK_RHI_HANDLE(q); -// -// // Create images! -// ImageInfo imageInfo{}; -// imageInfo.format = Format::RGBA8Unorm; -// imageInfo.size = { 256, 256, 1 }; -// imageInfo.usage = ImageUsageFlagBits::BLIT_SRC; -// imageInfo.arrayLayerCount = 6; -// TRACK_RHI_PARAMETER(imageInfo); -// Image src = mDevice->CreateImage(imageInfo); -// TRACK_RHI_HANDLE(src); -// -// imageInfo.arrayLayerCount = 8; -// imageInfo.usage = ImageUsageFlagBits::BLIT_DST; -// TRACK_RHI_PARAMETER(imageInfo); // Update tracker for modified imageInfo -// Image graphicsDest = mDevice->CreateImage(imageInfo); -// TRACK_RHI_HANDLE(graphicsDest); -// -// -// ICommandBuffer* graphicsCommands = q->GetCommandBuffer({}); -// TRACK_RHI_HANDLE(graphicsCommands); -// -// // we are going to blit from [2, 6) to [4, 8) -// BlitImageToImageInfo blitInfo{}; -// blitInfo.srcImage = src; -// blitInfo.dstImage = graphicsDest; -// blitInfo.srcImageBox = Box3D::Cut({ imageInfo.size.width, imageInfo.size.height, 1 }); -// blitInfo.dstImageBox = Box3D::Cut({ imageInfo.size.width, imageInfo.size.height, 1 }); -// blitInfo.srcImageSlice.baseArrayLayer = 2; -// blitInfo.srcImageSlice.layerCount = 4; -// blitInfo.dstImageSlice.baseArrayLayer = 4; -// blitInfo.dstImageSlice.layerCount = 4; -// TRACK_RHI_PARAMETER(blitInfo); -// -// EXPECT_NO_THROW(graphicsCommands->ImageBarrier({ -// .image = src, -// .srcAccess = AccessConsts::NONE, -// .dstAccess = AccessConsts::BLIT_READ, -// .srcLayout = ImageLayout::Undefined, -// .dstLayout = ImageLayout::BlitSrc, -// })); -// -// EXPECT_NO_THROW(graphicsCommands->ImageBarrier({ -// .image = graphicsDest, -// .srcAccess = AccessConsts::NONE, -// .dstAccess = AccessConsts::BLIT_WRITE, -// .srcLayout = ImageLayout::Undefined, -// .dstLayout = ImageLayout::BlitDst, -// })); -// EXPECT_NO_THROW(graphicsCommands->BlitImageToImage(blitInfo)); -// -// graphicsCommands->Complete(); -// q->SubmitCommandBuffer(graphicsCommands); -// -// -// mDevice->SubmitQueue({ .queue = q }); -// -// mDevice->WaitIdle(); -// mDevice->DestroyImage(src); -// mDevice->DestroyImage(graphicsDest); -// } -// -// -// +TEST_F(RHI_CONTEXT_FIXTURE_NAME, CommandsTransferCopyBufferToBufferSucceeds) { + BufferInfo bufferInfo{}; + bufferInfo.size = 2048; + bufferInfo.usage = BufferUsageFlagBits::TRANSFER_SRC | BufferUsageFlagBits::TRANSFER_DST; + TRACK_RHI_PARAMETER(bufferInfo); + + Buffer src = mDevice->CreateBuffer(bufferInfo); + TRACK_RHI_HANDLE(src); + Buffer dst = mDevice->CreateBuffer(bufferInfo); + TRACK_RHI_HANDLE(dst); + + auto* cb = mDevice->GetCommandQueues()[0]->GetCommandBuffer({}); + TRACK_RHI_HANDLE(cb); // cb is an ICommandBuffer*, which is a handle/pointer + + // Copy a subregion with offsets + CopyBufferToBufferInfo copyInfo{}; + copyInfo.srcBuffer = src; + copyInfo.dstBuffer = dst; + copyInfo.srcOffset = 256; // copy from 256 bytes in + copyInfo.dstOffset = 512; // copy to 512 bytes in + copyInfo.size = 512; // copy 512 bytes fits easily within 2048 + TRACK_RHI_PARAMETER(copyInfo); + + + EXPECT_NO_FATAL_FAILURE(cb->BufferBarrier({ + .buffer = src, + .srcAccess = AccessConsts::NONE, + .dstAccess = AccessConsts::TRANSFER_READ, + .srcLayout = BufferLayout::Undefined, + .dstLayout = BufferLayout::TransferSrc, + })); // Note: Anonymous structs don't get a macro, but their contents are covered by main structs/handles + EXPECT_NO_FATAL_FAILURE(cb->BufferBarrier({ + .buffer = dst, + .srcAccess = AccessConsts::NONE, + .dstAccess = AccessConsts::TRANSFER_WRITE, + .srcLayout = BufferLayout::Undefined, + .dstLayout = BufferLayout::TransferDst, + })); + EXPECT_NO_FATAL_FAILURE(cb->CopyBufferToBuffer(copyInfo)); + + cb->Complete(); + mDevice->SubmitQueue({ .queue = mDevice->GetCommandQueues()[0], .commands = { &cb, 1 } }); // queue is a pointer/handle + mDevice->WaitIdle(); + + mDevice->DestroyBuffer(src); + mDevice->DestroyBuffer(dst); +} + +TEST_F(RHI_CONTEXT_FIXTURE_NAME, CommandsTransferCopyBufferToImageSucceeds) { + ImageInfo imageInfo{}; + imageInfo.dimensions = ImageDimensions::e3D; + imageInfo.size = { 16, 16, 4 }; + imageInfo.format = Format::RGBA8Unorm; + imageInfo.usage = ImageUsageFlagBits::TRANSFER_DST; + TRACK_RHI_PARAMETER(imageInfo); + + Image image = mDevice->CreateImage(imageInfo); + TRACK_RHI_HANDLE(image); + + auto* cb = mDevice->GetCommandQueues()[0]->GetCommandBuffer({}); + TRACK_RHI_HANDLE(cb); + + CopyBufferToImageInfo info{}; + info.image = image; + info.imageExtent = { 8, 4, 2 }; // copy smaller region (half width) + info.imageOffset = { 4, 4, 1 }; // offset to middle of image + auto requirements = mDevice->ImageUploadRequirements(image, ImageSlice{ .mipLevel = 0, .arrayLayer = 0 }); + info.rowPitch = requirements.uploadPitch; + + info.bufferOffset = PYRO_ALIGN(128, mDevice->Properties().bufferImageCopyOffsetAlignment); + BufferInfo bufferInfo{}; + bufferInfo.size = PYRO_ALIGN(info.rowPitch * info.imageExtent.height * info.imageExtent.depth + info.bufferOffset, 256); + bufferInfo.usage = BufferUsageFlagBits::TRANSFER_SRC; + TRACK_RHI_PARAMETER(bufferInfo); + + Buffer buffer = mDevice->CreateBuffer(bufferInfo); + TRACK_RHI_HANDLE(buffer); + info.buffer = buffer; + TRACK_RHI_PARAMETER(info); + + + EXPECT_NO_FATAL_FAILURE(cb->BufferBarrier({ + .buffer = buffer, + .srcAccess = AccessConsts::NONE, + .dstAccess = AccessConsts::TRANSFER_READ, + .srcLayout = BufferLayout::Undefined, + .dstLayout = BufferLayout::TransferSrc, + })); + EXPECT_NO_FATAL_FAILURE(cb->ImageBarrier({ + .image = image, + .srcAccess = AccessConsts::NONE, + .dstAccess = AccessConsts::TRANSFER_WRITE, + .srcLayout = ImageLayout::Undefined, + .dstLayout = ImageLayout::TransferDst, + })); + EXPECT_NO_FATAL_FAILURE(cb->CopyBufferToImage(info)); + + cb->Complete(); + + mDevice->SubmitQueue({ .queue = mDevice->GetCommandQueues()[0], .commands = { &cb, 1 } }); + mDevice->WaitIdle(); + + mDevice->DestroyBuffer(buffer); + mDevice->DestroyImage(image); +} +TEST_F(RHI_CONTEXT_FIXTURE_NAME, CommandsTransferCopyImageToBufferSucceeds) { + ImageInfo srcImageInfo{}; + srcImageInfo.dimensions = ImageDimensions::e3D; + srcImageInfo.size = { 16, 16, 4 }; + srcImageInfo.format = Format::RGBA8Unorm; + srcImageInfo.usage = ImageUsageFlagBits::TRANSFER_SRC; + TRACK_RHI_PARAMETER(srcImageInfo); + + Image srcImage = mDevice->CreateImage(srcImageInfo); + TRACK_RHI_HANDLE(srcImage); + + auto* cb = mDevice->GetCommandQueues()[0]->GetCommandBuffer({}); + TRACK_RHI_HANDLE(cb); + + CopyImageToBufferInfo info{}; + info.image = srcImage; + info.imageExtent = { 8, 4, 2 }; // smaller region + info.imageOffset = { 2, 3, 1 }; // subregion of the image + auto requirements = mDevice->ImageUploadRequirements(srcImage, ImageSlice{ .mipLevel = 0, .arrayLayer = 0 }); + info.rowPitch = requirements.uploadPitch; + + // Calculate safe buffer size = rowPitch * height * depth + offset margin + BufferInfo bufferInfo{}; + const u64 regionBytes = static_cast(info.rowPitch) * info.imageExtent.height * info.imageExtent.depth; + info.bufferOffset = PYRO_ALIGN(128, mDevice->Properties().bufferImageCopyOffsetAlignment); + bufferInfo.size = PYRO_ALIGN(regionBytes + info.bufferOffset, 256); // <-- ensure valid total size + bufferInfo.usage = BufferUsageFlagBits::TRANSFER_DST; + TRACK_RHI_PARAMETER(bufferInfo); + + Buffer dstBuffer = mDevice->CreateBuffer(bufferInfo); + TRACK_RHI_HANDLE(dstBuffer); + info.buffer = dstBuffer; + TRACK_RHI_PARAMETER(info); + + EXPECT_NO_FATAL_FAILURE(cb->ImageBarrier({ + .image = srcImage, + .srcAccess = AccessConsts::NONE, + .dstAccess = AccessConsts::TRANSFER_READ, + .srcLayout = ImageLayout::Undefined, + .dstLayout = ImageLayout::TransferSrc, + })); + EXPECT_NO_FATAL_FAILURE(cb->BufferBarrier({ + .buffer = dstBuffer, + .srcAccess = AccessConsts::NONE, + .dstAccess = AccessConsts::TRANSFER_WRITE, + .srcLayout = BufferLayout::Undefined, + .dstLayout = BufferLayout::TransferDst, + })); + + EXPECT_NO_FATAL_FAILURE(cb->CopyImageToBuffer(info)); + + cb->Complete(); + + mDevice->SubmitQueue({ .queue = mDevice->GetCommandQueues()[0], .commands = { &cb, 1 } }); + mDevice->WaitIdle(); + + mDevice->DestroyImage(srcImage); + mDevice->DestroyBuffer(dstBuffer); +} + +TEST_F(RHI_CONTEXT_FIXTURE_NAME, CommandsTransferCopyImageToImageSucceeds) { + ImageInfo srcImageInfo{}; + srcImageInfo.size = { 16, 16, 1 }; + srcImageInfo.format = Format::RGBA8Unorm; + srcImageInfo.usage = ImageUsageFlagBits::TRANSFER_SRC; + TRACK_RHI_PARAMETER(srcImageInfo); + Image srcImage = mDevice->CreateImage(srcImageInfo); + TRACK_RHI_HANDLE(srcImage); + + ImageInfo dstImageInfo{}; + dstImageInfo.size = { 16, 16, 1 }; + dstImageInfo.format = Format::RGBA8Unorm; + dstImageInfo.usage = ImageUsageFlagBits::TRANSFER_DST; + TRACK_RHI_PARAMETER(dstImageInfo); + Image dstImage = mDevice->CreateImage(dstImageInfo); + TRACK_RHI_HANDLE(dstImage); + + auto* cb = mDevice->GetCommandQueues()[0]->GetCommandBuffer({}); + TRACK_RHI_HANDLE(cb); + + CopyImageToImageInfo info{}; + info.srcImage = srcImage; + info.dstImage = dstImage; + info.srcOffset = { 4, 4, 0 }; // copy from center + info.dstOffset = { 2, 2, 0 }; // copy to different offset + info.extent = { 8, 8, 1 }; // partial region + TRACK_RHI_PARAMETER(info); + + EXPECT_NO_FATAL_FAILURE(cb->ImageBarrier({ + .image = srcImage, + .srcAccess = AccessConsts::NONE, + .dstAccess = AccessConsts::TRANSFER_READ, + .srcLayout = ImageLayout::Undefined, + .dstLayout = ImageLayout::TransferSrc, + })); + EXPECT_NO_FATAL_FAILURE(cb->ImageBarrier({ + .image = dstImage, + .srcAccess = AccessConsts::NONE, + .dstAccess = AccessConsts::TRANSFER_WRITE, + .srcLayout = ImageLayout::Undefined, + .dstLayout = ImageLayout::TransferDst, + })); + EXPECT_NO_FATAL_FAILURE(cb->CopyImageToImage(info)); + + cb->Complete(); + + mDevice->SubmitQueue({ .queue = mDevice->GetCommandQueues()[0], .commands = { &cb, 1 } }); + mDevice->WaitIdle(); + + mDevice->DestroyImage(srcImage); + mDevice->DestroyImage(dstImage); +} + + +TEST_F(RHI_CONTEXT_FIXTURE_NAME, CommandsTransferClearUAVBufferSucceeds) { + BufferInfo bufferInfo{}; + bufferInfo.size = 1024; + bufferInfo.usage = BufferUsageFlagBits::UNORDERED_ACCESS; + bufferInfo.initialLayout = BufferLayout::UnorderedAccess; + TRACK_RHI_PARAMETER(bufferInfo); + + Buffer buffer = mDevice->CreateBuffer(bufferInfo); + TRACK_RHI_HANDLE(buffer); + auto uav = mDevice->CreateUnorderedAccess(BufferResourceInfo{ .buffer = buffer }); + TRACK_RHI_HANDLE(uav); + + auto* cb = mDevice->GetCommandQueues()[0]->GetCommandBuffer({}); + TRACK_RHI_HANDLE(cb); + + ClearUnorderedAccessViewInfo info{}; + info.view = uav; + info.clearValue = { 0.0f, 0.0f, 0.0f, 0.0f }; + TRACK_RHI_PARAMETER(info); + + + EXPECT_NO_FATAL_FAILURE(cb->BufferBarrier({ + .buffer = buffer, + .srcAccess = AccessConsts::NONE, + .dstAccess = AccessConsts::CLEAR_WRITE, + .srcLayout = BufferLayout::UnorderedAccess, + .dstLayout = BufferLayout::UnorderedAccess, + })); + EXPECT_NO_FATAL_FAILURE(cb->ClearUnorderedAccessView(info)); + + cb->Complete(); + + mDevice->SubmitQueue({ .queue = mDevice->GetCommandQueues()[0], .commands = { &cb, 1 } }); + mDevice->WaitIdle(); + + mDevice->DestroyUnorderedAccess(uav); + mDevice->DestroyBuffer(buffer); +} + +TEST_F(RHI_CONTEXT_FIXTURE_NAME, CommandsTransferClearUAVImageSucceeds) { + ImageInfo imageInfo{}; + imageInfo.size = { 256, 256, 1 }; + imageInfo.format = Format::RGBA16Unorm; + imageInfo.usage = ImageUsageFlagBits::UNORDERED_ACCESS; + imageInfo.name = ""; + TRACK_RHI_PARAMETER(imageInfo); + + Image image = mDevice->CreateImage(imageInfo); + TRACK_RHI_HANDLE(image); + auto uav = mDevice->CreateUnorderedAccess(ImageResourceInfo{ .image = image }); + TRACK_RHI_HANDLE(uav); + + auto* cb = mDevice->GetCommandQueues()[0]->GetCommandBuffer({}); + TRACK_RHI_HANDLE(cb); + + ClearUnorderedAccessViewInfo info{}; + info.view = uav; + info.clearValue = { 0.1f, 0.2f, 0.3f, 0.4f }; + TRACK_RHI_PARAMETER(info); + + + EXPECT_NO_FATAL_FAILURE(cb->ImageBarrier({ + .image = image, + .srcAccess = AccessConsts::NONE, + .dstAccess = AccessConsts::CLEAR_WRITE, + .srcLayout = ImageLayout::Undefined, + .dstLayout = ImageLayout::UnorderedAccess, + })); + EXPECT_NO_FATAL_FAILURE(cb->ClearUnorderedAccessView(info)); + + cb->Complete(); + + mDevice->SubmitQueue({ .queue = mDevice->GetCommandQueues()[0], .commands = { &cb, 1 } }); + mDevice->WaitIdle(); + + mDevice->DestroyUnorderedAccess(uav); + mDevice->DestroyImage(image); +} + + +TEST_F(RHI_CONTEXT_FIXTURE_NAME, MultiCommandBufferSyncSubmit) { + // Create a small 2D image and a buffer we can copy from + ImageInfo imageInfo{}; + imageInfo.size = { 16, 16, 1 }; + imageInfo.format = Format::RGBA8Unorm; + imageInfo.usage = ImageUsageFlagBits::TRANSFER_DST | ImageUsageFlagBits::UNORDERED_ACCESS; + TRACK_RHI_PARAMETER(imageInfo); + Image image = mDevice->CreateImage(imageInfo); + TRACK_RHI_HANDLE(image); + + // Prepare the buffer (sized for a small subregion copy like other tests) + CopyBufferToImageInfo info{}; + info.image = image; + info.imageExtent = { 16, 16, 1 }; + auto requirements = mDevice->ImageUploadRequirements(image, ImageSlice{ .mipLevel = 0, .arrayLayer = 0 }); + info.rowPitch = requirements.uploadPitch; + + BufferInfo bufferInfo{}; + bufferInfo.size = static_cast(info.rowPitch) * info.imageExtent.height * info.imageExtent.depth; + bufferInfo.usage = BufferUsageFlagBits::TRANSFER_SRC; + TRACK_RHI_PARAMETER(bufferInfo); + Buffer buffer = mDevice->CreateBuffer(bufferInfo); + TRACK_RHI_HANDLE(buffer); + info.buffer = buffer; + TRACK_RHI_PARAMETER(info); + + // Create a UAV for the image (used in the second command buffer) + auto uav = mDevice->CreateUnorderedAccess(ImageResourceInfo{ .image = image }); + TRACK_RHI_HANDLE(uav); + + // Get the queue & prepare two command buffers + ICommandQueue* queue = mDevice->GetCommandQueues()[0]; + TRACK_RHI_HANDLE(queue); + auto* cbCopy = queue->GetCommandBuffer({ .name = "Copy Commands" }); + TRACK_RHI_HANDLE(cbCopy); + auto* cbClear = queue->GetCommandBuffer({ .name = "Clear Commands" }); + TRACK_RHI_HANDLE(cbClear); + + // --- Command buffer A: copy buffer -> image --- + EXPECT_NO_FATAL_FAILURE(cbCopy->BufferBarrier({ + .buffer = buffer, + .srcAccess = AccessConsts::NONE, + .dstAccess = AccessConsts::TRANSFER_READ, + .srcLayout = BufferLayout::Undefined, + .dstLayout = BufferLayout::TransferSrc, + })); + EXPECT_NO_FATAL_FAILURE(cbCopy->ImageBarrier({ + .image = image, + .srcAccess = AccessConsts::NONE, + .dstAccess = AccessConsts::TRANSFER_WRITE, + .srcLayout = ImageLayout::Undefined, + .dstLayout = ImageLayout::TransferDst, + })); + EXPECT_NO_FATAL_FAILURE(cbCopy->CopyBufferToImage(info)); + cbCopy->Complete(); + + // --- Command buffer B: transition to UAV and clear --- + // The image is expected to be in TransferDst after cbCopy. We now transition it + // to UnorderedAccess for the clear. Follow the same style as your other tests. + ClearUnorderedAccessViewInfo clearInfo{}; + clearInfo.view = uav; + clearInfo.clearValue = { 0.25f, 0.5f, 0.75f, 1.0f }; + TRACK_RHI_PARAMETER(clearInfo); + + + EXPECT_NO_FATAL_FAILURE(cbClear->ImageBarrier({ + .image = image, + .srcAccess = AccessConsts::TRANSFER_WRITE, + .dstAccess = AccessConsts::CLEAR_WRITE, + .srcLayout = ImageLayout::TransferDst, + .dstLayout = ImageLayout::UnorderedAccess, + })); + EXPECT_NO_FATAL_FAILURE(cbClear->ClearUnorderedAccessView(clearInfo)); + cbClear->Complete(); + + // Submit the command buffers to the queue + eastl::array commands = { cbCopy, cbClear }; + mDevice->SubmitQueue({ .queue = queue, .commands = commands }); + + // Wait for work to finish and cleanup + mDevice->WaitIdle(); + + mDevice->DestroyUnorderedAccess(uav); + mDevice->DestroyBuffer(buffer); + mDevice->DestroyImage(image); +} + +TEST_F(RHI_CONTEXT_FIXTURE_NAME, MultiThreadedCommandBufferRecordingSubmitOrder) { + // --- Resource setup --- + ImageInfo imageInfo{}; + imageInfo.size = { 16, 16, 1 }; + imageInfo.format = Format::RGBA8Unorm; + imageInfo.usage = ImageUsageFlagBits::TRANSFER_DST | ImageUsageFlagBits::UNORDERED_ACCESS; + TRACK_RHI_PARAMETER(imageInfo); + Image image = mDevice->CreateImage(imageInfo); + TRACK_RHI_HANDLE(image); + + CopyBufferToImageInfo copyInfo{}; + copyInfo.image = image; + copyInfo.imageExtent = { 16, 16, 1 }; + auto requirements = mDevice->ImageUploadRequirements(image, ImageSlice{ .mipLevel = 0, .arrayLayer = 0 }); + copyInfo.rowPitch = requirements.uploadPitch; + + BufferInfo bufferInfo{}; + bufferInfo.size = static_cast(copyInfo.rowPitch) * copyInfo.imageExtent.height * copyInfo.imageExtent.depth; + bufferInfo.usage = BufferUsageFlagBits::TRANSFER_SRC; + TRACK_RHI_PARAMETER(bufferInfo); + Buffer buffer = mDevice->CreateBuffer(bufferInfo); + TRACK_RHI_HANDLE(buffer); + copyInfo.buffer = buffer; + TRACK_RHI_PARAMETER(copyInfo); + + auto uav = mDevice->CreateUnorderedAccess(ImageResourceInfo{ .image = image }); + TRACK_RHI_HANDLE(uav); + + ICommandQueue* queue = mDevice->GetCommandQueues()[0]; + TRACK_RHI_HANDLE(queue); + + // --- Prepare two command buffers --- + auto* cbCopy = queue->GetCommandBuffer({ .name = "Parallel Copy CB" }); + TRACK_RHI_HANDLE(cbCopy); + auto* cbClear = queue->GetCommandBuffer({ .name = "Parallel Clear CB" }); + TRACK_RHI_HANDLE(cbClear); + + // --- Multithreaded recording --- + std::latch recordStart(1); + std::atomic copyDone = false; + std::atomic clearDone = false; + + std::jthread copyThread([&](std::stop_token) { + recordStart.wait(); + + EXPECT_NO_FATAL_FAILURE(cbCopy->BufferBarrier({ + .buffer = buffer, + .srcAccess = AccessConsts::NONE, + .dstAccess = AccessConsts::TRANSFER_READ, + .srcLayout = BufferLayout::Undefined, + .dstLayout = BufferLayout::TransferSrc, + })); + EXPECT_NO_FATAL_FAILURE(cbCopy->ImageBarrier({ + .image = image, + .srcAccess = AccessConsts::NONE, + .dstAccess = AccessConsts::TRANSFER_WRITE, + .srcLayout = ImageLayout::Undefined, + .dstLayout = ImageLayout::TransferDst, + })); + EXPECT_NO_FATAL_FAILURE(cbCopy->CopyBufferToImage(copyInfo)); + cbCopy->Complete(); + copyDone = true; + }); + + std::jthread clearThread([&](std::stop_token) { + recordStart.wait(); + + ClearUnorderedAccessViewInfo clearInfo{}; + clearInfo.view = uav; + clearInfo.clearValue = { 0.25f, 0.5f, 0.75f, 1.0f }; + TRACK_RHI_PARAMETER(clearInfo); + + + EXPECT_NO_FATAL_FAILURE(cbClear->ImageBarrier({ + .image = image, + .srcAccess = AccessConsts::TRANSFER_WRITE, + .dstAccess = AccessConsts::CLEAR_WRITE, + .srcLayout = ImageLayout::TransferDst, + .dstLayout = ImageLayout::UnorderedAccess, + })); + EXPECT_NO_FATAL_FAILURE(cbClear->ClearUnorderedAccessView(clearInfo)); + cbClear->Complete(); + clearDone = true; + }); + + // Start both recording threads simultaneously + recordStart.count_down(); + + // Wait for both threads to complete recording + using namespace std::chrono_literals; + const auto start = std::chrono::steady_clock::now(); + while ((!copyDone || !clearDone) && std::chrono::steady_clock::now() - start < 5s) + std::this_thread::sleep_for(10ms); + + ASSERT_TRUE(copyDone && clearDone) << "Command buffer recording did not complete within timeout."; + copyThread.request_stop(); + clearThread.request_stop(); + + // --- Sequential submission order check --- + // Submit copy first, clear second + eastl::array submits = { cbCopy, cbClear }; + + // Submit the clear command buffer to the queue + // Now flush the queue (this should submit both cmd buffers in the order they were queued) + mDevice->SubmitQueue({ .queue = queue, .commands = submits }); + + // Flush queue (submit all) + mDevice->SubmitQueue({ .queue = queue }); + + // Wait for completion + mDevice->WaitIdle(); + + // --- Cleanup --- + mDevice->DestroyUnorderedAccess(uav); + mDevice->DestroyBuffer(buffer); + mDevice->DestroyImage(image); +} + + +TEST_F(RHI_CONTEXT_FIXTURE_NAME, CommandsUpdateBuffer64kbSucceeds) { + BufferInfo bufferInfo{}; + bufferInfo.size = 65536; + bufferInfo.usage = BufferUsageFlagBits::TRANSFER_DST; + bufferInfo.initialLayout = BufferLayout::TransferDst; + TRACK_RHI_PARAMETER(bufferInfo); + + Buffer buffer = mDevice->CreateBuffer(bufferInfo); + TRACK_RHI_HANDLE(buffer); + + auto* cb = mDevice->GetCommandQueues()[0]->GetCommandBuffer({}); + TRACK_RHI_HANDLE(cb); + + eastl::vector data(65536, static_cast(76)); + + EXPECT_NO_FATAL_FAILURE(cb->UpdateBuffer({ + .buffer = buffer, + .region = { .size = data.size() }, + .data = data.data(), + })); + + cb->Complete(); + + mDevice->SubmitQueue({ .queue = mDevice->GetCommandQueues()[0], .commands = { &cb, 1 } }); + mDevice->WaitIdle(); + + mDevice->DestroyBuffer(buffer); +} + + + +TEST_F(RHI_CONTEXT_FIXTURE_NAME, CommandsUpdateBuffer64kbSucceedsWithOffset) { + BufferInfo bufferInfo{}; + bufferInfo.size = 65536 + 256; + bufferInfo.usage = BufferUsageFlagBits::TRANSFER_DST; + bufferInfo.initialLayout = BufferLayout::TransferDst; + TRACK_RHI_PARAMETER(bufferInfo); + + Buffer buffer = mDevice->CreateBuffer(bufferInfo); + TRACK_RHI_HANDLE(buffer); + + auto* cb = mDevice->GetCommandQueues()[0]->GetCommandBuffer({}); + TRACK_RHI_HANDLE(cb); + + eastl::vector data(65536, static_cast(76)); + + EXPECT_NO_FATAL_FAILURE(cb->UpdateBuffer({ + .buffer = buffer, + .region = { .offset = 256, .size = data.size() }, + .data = data.data(), + })); + + cb->Complete(); + + mDevice->SubmitQueue({ .queue = mDevice->GetCommandQueues()[0], .commands = { &cb, 1 } }); + mDevice->WaitIdle(); + + mDevice->DestroyBuffer(buffer); +} + +TEST_F(RHI_CONTEXT_FIXTURE_NAME, CommandsTimestampQueryPoolResetSetSuccess) { + ImageInfo imageInfo{}; + imageInfo.size = { 256, 256, 1 }; + imageInfo.format = Format::RGBA16Unorm; + imageInfo.usage = ImageUsageFlagBits::UNORDERED_ACCESS; + imageInfo.name = "uav test"; + TRACK_RHI_PARAMETER(imageInfo); + + Image image = mDevice->CreateImage(imageInfo); + TRACK_RHI_HANDLE(image); + ITimestampQueryPool* qp = mDevice->CreateTimestampQueryPool({ .queryCount = 2, .name = " qp" }); + TRACK_RHI_HANDLE(qp); + auto uav = mDevice->CreateUnorderedAccess(ImageResourceInfo{ .image = image }); + TRACK_RHI_HANDLE(uav); + + auto* cb = mDevice->GetCommandQueues()[0]->GetCommandBuffer({}); + TRACK_RHI_HANDLE(cb); + + ClearUnorderedAccessViewInfo info{}; + info.view = uav; + info.clearValue = { 0.1f, 0.2f, 0.3f, 0.4f }; + TRACK_RHI_PARAMETER(info); + + + EXPECT_NO_FATAL_FAILURE(cb->InvalidateTimestampQuery({ .queryPool = qp, .queryCount = 2 })); + + EXPECT_NO_FATAL_FAILURE(cb->WriteTimestamp({ + .queryPool = qp, + .stage = PipelineStageFlagBits::CLEAR, + .queryIndex = 0, + })); + EXPECT_NO_FATAL_FAILURE(cb->ImageBarrier({ + .image = image, + .srcAccess = AccessConsts::NONE, + .dstAccess = AccessConsts::CLEAR_WRITE, + .srcLayout = ImageLayout::Undefined, + .dstLayout = ImageLayout::UnorderedAccess, + })); + EXPECT_NO_FATAL_FAILURE(cb->ClearUnorderedAccessView(info)); + + EXPECT_NO_FATAL_FAILURE(cb->WriteTimestamp({ + .queryPool = qp, + .stage = PipelineStageFlagBits::CLEAR, + .queryIndex = 1, + })); + + cb->Complete(); + + mDevice->SubmitQueue({ .queue = mDevice->GetCommandQueues()[0], .commands = { &cb, 1 } }); + mDevice->WaitIdle(); + + eastl::span stamps; + EXPECT_NO_FATAL_FAILURE(stamps = qp->GetTimestamps(0, 2)); + EXPECT_NE(stamps[0], ~(0ULL)); + EXPECT_NE(stamps[1], ~(0ULL)); + ASSERT_GE(stamps[1], stamps[0]); + + mDevice->DestroyUnorderedAccess(uav); + mDevice->DestroyImage(image); + mDevice->DestroyTimestampQueryPool(qp); +} + + +TEST_F(RHI_CONTEXT_FIXTURE_NAME, CommandsRecycleSuccess) { + static constexpr i32 NUM_CYCLES = 16; + + ICommandQueue* cq = mDevice->GetCommandQueues()[0]; + TRACK_RHI_HANDLE(cq); + + eastl::vector> toDestroyCb{}; + for (i32 i = 0; i < NUM_CYCLES; ++i) { + BufferInfo bufferInfo{}; + bufferInfo.usage = BufferUsageFlagBits::TRANSFER_DST; + bufferInfo.name = "dst buffer in flight #" + eastl::to_string(i); + bufferInfo.size = 256; + TRACK_RHI_PARAMETER(bufferInfo); + Buffer dstBuffer = mDevice->CreateBuffer(bufferInfo); + TRACK_RHI_HANDLE(dstBuffer); + ASSERT_TRUE(mDevice->IsValid(dstBuffer)); + toDestroyCb.emplace_back([=]() { mDevice->DestroyImmediately(dstBuffer); }); + + eastl::vector data(static_cast(bufferInfo.size)); + + ICommandBuffer* cb = cq->GetCommandBuffer({ .name = "recycled commands #" + eastl::to_string(i) }); + TRACK_RHI_HANDLE(cb); + + eastl::string framename1 = "Record frame #" + eastl::to_string(i); + eastl::string framename = "Transition Resources " + eastl::to_string(i); + cb->BeginLabel({ .name = framename1 }); + cb->BeginLabel({ .name = framename }); + EXPECT_NO_FATAL_FAILURE(cb->BufferBarrier({ + .buffer = dstBuffer, + .srcAccess = AccessConsts::NONE, + .dstAccess = AccessConsts::TRANSFER_WRITE, + .srcLayout = BufferLayout::Undefined, + .dstLayout = BufferLayout::TransferDst, + })); + cb->EndLabel(); + + UpdateBufferInfo info{}; + info.buffer = dstBuffer; + info.data = data.data(); + TRACK_RHI_PARAMETER(info); + + EXPECT_NO_FATAL_FAILURE(cb->UpdateBuffer(info)); + cb->EndLabel(); + + cb->Complete(); + mDevice->SubmitQueue({ .queue = cq, .commands = { &cb, 1 } }); + } + mDevice->WaitIdle(); + for (auto destroy : toDestroyCb) { + destroy(); + } +} + +TEST_F(RHI_CONTEXT_FIXTURE_NAME, CommandsClearColorPass) { + ICommandQueue* q = mDevice->GetCommandQueues()[0]; + TRACK_RHI_HANDLE(q); + + ImageInfo srcImageInfo{}; + srcImageInfo.dimensions = ImageDimensions::e2D; + srcImageInfo.size = { 16, 16, 1 }; + srcImageInfo.format = Format::RGBA8Unorm; + srcImageInfo.usage = ImageUsageFlagBits::RENDER_TARGET; + srcImageInfo.name = "color target"; + TRACK_RHI_PARAMETER(srcImageInfo); + Image srcImage = mDevice->CreateImage(srcImageInfo); + TRACK_RHI_HANDLE(srcImage); + ASSERT_TRUE(mDevice->IsValid(srcImage)); + + RenderTarget srcTarget = mDevice->CreateRenderTarget({ .image = srcImage, .flags = RenderTargetFlagBits::COLOR_TARGET, .name = "render target" }); + TRACK_RHI_HANDLE(srcTarget); + + ICommandBuffer* cb = q->GetCommandBuffer({}); + TRACK_RHI_HANDLE(cb); + + EXPECT_NO_FATAL_FAILURE(cb->ImageBarrier({ + .image = srcImage, + .srcAccess = AccessConsts::NONE, + .dstAccess = AccessConsts::COLOR_ATTACHMENT_OUTPUT_WRITE, + .srcLayout = ImageLayout::Undefined, + .dstLayout = ImageLayout::RenderTarget, + })); + + RenderPassBeginInfo rpBeginInfo{}; + rpBeginInfo.colorAttachments = { + ColorAttachmentInfo{ + .target = srcTarget, + .loadOp = AttachmentLoadOp::Clear, + .clearValue = { 1.0f, 1.0f, 1.0f, 1.0f } }, + }; + rpBeginInfo.renderArea = { .x = 0, .y = 0, .width = 16, .height = 16 }; + TRACK_RHI_PARAMETER(rpBeginInfo); + + EXPECT_NO_FATAL_FAILURE(cb->BeginRenderPass(rpBeginInfo)); + + EXPECT_NO_FATAL_FAILURE(cb->EndRenderPass()); + + cb->Complete(); + + mDevice->SubmitQueue({ .queue = mDevice->GetCommandQueues()[0], .commands = { &cb, 1 } }); + mDevice->WaitIdle(); + + mDevice->DestroyImmediately(srcTarget); + mDevice->DestroyImmediately(srcImage); +} + + +TEST_F(RHI_CONTEXT_FIXTURE_NAME, CommandsClearColorPassThenDontCareLoad) { + ICommandQueue* q = mDevice->GetCommandQueues()[0]; + TRACK_RHI_HANDLE(q); + + ImageInfo srcImageInfo{}; + srcImageInfo.dimensions = ImageDimensions::e2D; + srcImageInfo.size = { 16, 16, 1 }; + srcImageInfo.format = Format::RGBA8Unorm; + srcImageInfo.usage = ImageUsageFlagBits::RENDER_TARGET; + srcImageInfo.name = "color target"; + TRACK_RHI_PARAMETER(srcImageInfo); + Image srcImage = mDevice->CreateImage(srcImageInfo); + TRACK_RHI_HANDLE(srcImage); + ASSERT_TRUE(mDevice->IsValid(srcImage)); + + RenderTarget srcTarget = mDevice->CreateRenderTarget({ .image = srcImage, .flags = RenderTargetFlagBits::COLOR_TARGET, .name = "render target" }); + TRACK_RHI_HANDLE(srcTarget); + + ICommandBuffer* cb = q->GetCommandBuffer({}); + TRACK_RHI_HANDLE(cb); + + EXPECT_NO_FATAL_FAILURE(cb->ImageBarrier({ + .image = srcImage, + .srcAccess = AccessConsts::NONE, + .dstAccess = AccessConsts::COLOR_ATTACHMENT_OUTPUT_WRITE, + .srcLayout = ImageLayout::Undefined, + .dstLayout = ImageLayout::RenderTarget, + })); + + RenderPassBeginInfo rpBeginInfo1{}; + rpBeginInfo1.colorAttachments = { + ColorAttachmentInfo{ + .target = srcTarget, + .loadOp = AttachmentLoadOp::Clear, + .storeOp = AttachmentStoreOp::Store, + .clearValue = { 1.0f, 1.0f, 1.0f, 1.0f } }, + }; + rpBeginInfo1.renderArea = { .x = 0, .y = 0, .width = 16, .height = 16 }; + TRACK_RHI_PARAMETER(rpBeginInfo1); + + EXPECT_NO_FATAL_FAILURE(cb->BeginRenderPass(rpBeginInfo1)); + + EXPECT_NO_FATAL_FAILURE(cb->EndRenderPass()); + + + RenderPassBeginInfo rpBeginInfo2{}; + rpBeginInfo2.colorAttachments = { + ColorAttachmentInfo{ + .target = srcTarget, + .loadOp = AttachmentLoadOp::DontCare, + }, + }; + rpBeginInfo2.renderArea = { .x = 0, .y = 0, .width = 16, .height = 16 }; + TRACK_RHI_PARAMETER(rpBeginInfo2); + + EXPECT_NO_FATAL_FAILURE(cb->BeginRenderPass(rpBeginInfo2)); + + EXPECT_NO_FATAL_FAILURE(cb->EndRenderPass()); + + cb->Complete(); + + mDevice->SubmitQueue({ .queue = mDevice->GetCommandQueues()[0], .commands = { &cb, 1 } }); + mDevice->WaitIdle(); + + mDevice->DestroyImmediately(srcTarget); + mDevice->DestroyImmediately(srcImage); +} + +TEST_F(RHI_CONTEXT_FIXTURE_NAME, CommandsClearColorPassMSAAResolve) { + ICommandQueue* q = mDevice->GetCommandQueues()[0]; + TRACK_RHI_HANDLE(q); + + ImageInfo srcImageMSInfo{}; + srcImageMSInfo.dimensions = ImageDimensions::e2D; + srcImageMSInfo.size = { 64, 64, 1 }; + srcImageMSInfo.format = Format::RGBA8Unorm; + srcImageMSInfo.usage = ImageUsageFlagBits::RENDER_TARGET; + srcImageMSInfo.name = "color target MS"; + srcImageMSInfo.sampleCount = HIGHEST_BIT(mDevice->Properties().msaaSupportColorTarget); + if (srcImageMSInfo.sampleCount == RasterizationSamples::e1) { + GTEST_LOG_(INFO) << "Device does not support MSAA, skipping test..."; + return; + } + TRACK_RHI_PARAMETER(srcImageMSInfo); + Image srcImageMS = mDevice->CreateImage(srcImageMSInfo); + TRACK_RHI_HANDLE(srcImageMS); + ASSERT_TRUE(mDevice->IsValid(srcImageMS)); + + ImageInfo dstImageResolveInfo{}; + dstImageResolveInfo.dimensions = ImageDimensions::e2D; + dstImageResolveInfo.size = { 64, 64, 1 }; + dstImageResolveInfo.format = Format::RGBA8Unorm; + dstImageResolveInfo.usage = ImageUsageFlagBits::RENDER_TARGET; + dstImageResolveInfo.name = "color target (resolve)"; + TRACK_RHI_PARAMETER(dstImageResolveInfo); + Image dstImageResolve = mDevice->CreateImage(dstImageResolveInfo); + TRACK_RHI_HANDLE(dstImageResolve); + ASSERT_TRUE(mDevice->IsValid(dstImageResolve)); + + RenderTarget srcTargetMS = mDevice->CreateRenderTarget({ .image = srcImageMS, .flags = RenderTargetFlagBits::COLOR_TARGET, .name = "render target MS" }); + TRACK_RHI_HANDLE(srcTargetMS); + RenderTarget dstTargetResolve = mDevice->CreateRenderTarget({ .image = dstImageResolve, .flags = RenderTargetFlagBits::COLOR_TARGET, .name = "render target Resolve" }); + TRACK_RHI_HANDLE(dstTargetResolve); + + ICommandBuffer* cb = q->GetCommandBuffer({}); + TRACK_RHI_HANDLE(cb); + + EXPECT_NO_FATAL_FAILURE(cb->ImageBarrier({ + .image = srcImageMS, + .srcAccess = AccessConsts::NONE, + .dstAccess = AccessConsts::COLOR_ATTACHMENT_OUTPUT_WRITE, + .srcLayout = ImageLayout::Undefined, + .dstLayout = ImageLayout::RenderTarget, + })); + EXPECT_NO_FATAL_FAILURE(cb->ImageBarrier({ + .image = dstImageResolve, + .srcAccess = AccessConsts::NONE, + .dstAccess = AccessConsts::RESOLVE_WRITE, + .srcLayout = ImageLayout::Undefined, + .dstLayout = ImageLayout::RenderTarget, + })); + + RenderPassBeginInfo rpBeginInfo{}; + rpBeginInfo.colorAttachments = { + ColorAttachmentInfo{ + .target = srcTargetMS, + .loadOp = AttachmentLoadOp::Clear, + .clearValue = { 0.64f, 0.25f, 0.86f, 1.0f }, + .resolve = eastl::make_optional(AttachmentResolveInfo{ .target = dstTargetResolve }), + }, + }; + rpBeginInfo.renderArea = { .x = 0, .y = 0, .width = 64, .height = 64 }; + TRACK_RHI_PARAMETER(rpBeginInfo); + + EXPECT_NO_FATAL_FAILURE(cb->BeginRenderPass(rpBeginInfo)); + + EXPECT_NO_FATAL_FAILURE(cb->EndRenderPass()); + + cb->Complete(); + + mDevice->SubmitQueue({ .queue = mDevice->GetCommandQueues()[0], .commands = { &cb, 1 } }); + mDevice->WaitIdle(); + + mDevice->DestroyImmediately(srcTargetMS); + mDevice->DestroyImmediately(dstTargetResolve); + mDevice->DestroyImmediately(srcImageMS); + mDevice->DestroyImmediately(dstImageResolve); +} + + +TEST_F(RHI_CONTEXT_FIXTURE_NAME, CommandsBlitImageArray) { + auto* q = mDevice->GetCommandQueues()[0]; + TRACK_RHI_HANDLE(q); + + // Create images! + ImageInfo imageInfo{}; + imageInfo.format = Format::RGBA8Unorm; + imageInfo.size = { 256, 256, 1 }; + imageInfo.usage = ImageUsageFlagBits::BLIT_SRC; + imageInfo.arrayLayerCount = 6; + TRACK_RHI_PARAMETER(imageInfo); + Image src = mDevice->CreateImage(imageInfo); + TRACK_RHI_HANDLE(src); + + imageInfo.arrayLayerCount = 8; + imageInfo.usage = ImageUsageFlagBits::BLIT_DST; + TRACK_RHI_PARAMETER(imageInfo); // Update tracker for modified imageInfo + Image graphicsDest = mDevice->CreateImage(imageInfo); + TRACK_RHI_HANDLE(graphicsDest); + + + ICommandBuffer* graphicsCommands = q->GetCommandBuffer({}); + TRACK_RHI_HANDLE(graphicsCommands); + + // we are going to blit from [2, 6) to [4, 8) + BlitImageToImageInfo blitInfo{}; + blitInfo.srcImage = src; + blitInfo.dstImage = graphicsDest; + blitInfo.srcImageBox = Box3D::Cut({ imageInfo.size.width, imageInfo.size.height, 1 }); + blitInfo.dstImageBox = Box3D::Cut({ imageInfo.size.width, imageInfo.size.height, 1 }); + blitInfo.srcImageSlice.baseArrayLayer = 2; + blitInfo.srcImageSlice.layerCount = 4; + blitInfo.dstImageSlice.baseArrayLayer = 4; + blitInfo.dstImageSlice.layerCount = 4; + TRACK_RHI_PARAMETER(blitInfo); + + EXPECT_NO_THROW(graphicsCommands->ImageBarrier({ + .image = src, + .srcAccess = AccessConsts::NONE, + .dstAccess = AccessConsts::BLIT_READ, + .srcLayout = ImageLayout::Undefined, + .dstLayout = ImageLayout::BlitSrc, + })); + + EXPECT_NO_THROW(graphicsCommands->ImageBarrier({ + .image = graphicsDest, + .srcAccess = AccessConsts::NONE, + .dstAccess = AccessConsts::BLIT_WRITE, + .srcLayout = ImageLayout::Undefined, + .dstLayout = ImageLayout::BlitDst, + })); + EXPECT_NO_THROW(graphicsCommands->BlitImageToImage(blitInfo)); + + graphicsCommands->Complete(); + + mDevice->SubmitQueue({ .queue = q, .commands = { &graphicsCommands, 1 } }); + mDevice->WaitIdle(); + mDevice->DestroyImage(src); + mDevice->DestroyImage(graphicsDest); +} TEST_F(RHI_CONTEXT_FIXTURE_NAME, CommandsSuccessfullyDoRayQueryCSH) { struct SimpleVertex { @@ -1147,7 +1137,7 @@ TEST_F(RHI_CONTEXT_FIXTURE_NAME, CommandsSuccessfullyDoRayQueryCSH) { cmd->Complete(); // 11. Submit and Wait - mDevice->SubmitQueue({ .queue = queue, .commands = {&cmd, 1} }); + mDevice->SubmitQueue({ .queue = queue, .commands = { &cmd, 1 } }); queue->WaitIdle(); // Now run the ray tracing @@ -1209,7 +1199,7 @@ TEST_F(RHI_CONTEXT_FIXTURE_NAME, CommandsSuccessfullyDoRayQueryCSH) { EXPECT_NO_FATAL_FAILURE(cmd->CopyBufferToBuffer(copyReadbackInfo)); cmd->Complete(); - mDevice->SubmitQueue({ .queue = queue, .commands = {&cmd, 1} }); + mDevice->SubmitQueue({ .queue = queue, .commands = { &cmd, 1 } }); queue->WaitIdle(); // expect the ray to intersect!!! diff --git a/tests/RHITestChassis/ValidateCommandQueueImpl.hpp b/tests/RHITestChassis/ValidateCommandQueueImpl.hpp index a72629f..9359f85 100644 --- a/tests/RHITestChassis/ValidateCommandQueueImpl.hpp +++ b/tests/RHITestChassis/ValidateCommandQueueImpl.hpp @@ -383,8 +383,8 @@ TEST_F(RHI_CONTEXT_FIXTURE_NAME, SingleQueueDestroyDeferredSuccess) { info.image = srcImage; info.imageExtent = srcImageInfo.size; bufferInfo.size = mDevice->ImageSizeRequirements(srcImage); - info.rowPitch = bufferInfo.size / srcImageInfo.size.height / srcImageInfo.size.depth; - info.rowPitch = mDevice->ImageSubresourceRowPitch(srcImage, info.rowPitch); + auto requirements = mDevice->ImageUploadRequirements(srcImage, ImageSlice{ .mipLevel = 0, .arrayLayer = 0 }); + info.rowPitch = requirements.uploadPitch; bufferInfo.size = srcImageInfo.size.height * srcImageInfo.size.depth * info.rowPitch; TRACK_RHI_PARAMETER(bufferInfo); @@ -608,8 +608,8 @@ TEST_F(RHI_CONTEXT_FIXTURE_NAME, MultiQueueResourceTransferSuccess) { copyInfo.image = srcImage; copyInfo.imageExtent = srcImageInfo.size; bufferInfo.size = mDevice->ImageSizeRequirements(srcImage); - copyInfo.rowPitch = bufferInfo.size / srcImageInfo.size.height / srcImageInfo.size.depth; - copyInfo.rowPitch = mDevice->ImageSubresourceRowPitch(srcImage, copyInfo.rowPitch); + auto requirements = mDevice->ImageUploadRequirements(srcImage, ImageSlice{ .mipLevel = 0, .arrayLayer = 0 }); + copyInfo.rowPitch = requirements.uploadPitch; bufferInfo.size = srcImageInfo.size.height * srcImageInfo.size.depth * copyInfo.rowPitch; TRACK_RHI_PARAMETER(bufferInfo); diff --git a/tests/RHITestChassis/ValidateDeviceCreationImpl.hpp b/tests/RHITestChassis/ValidateDeviceCreationImpl.hpp index 73d73c7..16adbf7 100644 --- a/tests/RHITestChassis/ValidateDeviceCreationImpl.hpp +++ b/tests/RHITestChassis/ValidateDeviceCreationImpl.hpp @@ -20,7 +20,7 @@ TEST_F(RHI_CONTEXT_FIXTURE_NAME, ValidateDeviceCreation) { EXPECT_GE(props.bufferImageCopyOffsetAlignment, 1u); EXPECT_GE(props.minStorageBufferOffsetAlignment, 1u); EXPECT_GE(props.minUniformBufferOffsetAlignment, 1u); - EXPECT_GT(props.minLineWidth, 0.0f); + EXPECT_GE(props.minLineWidth, 0.0f); // it can be 0 on some systems, e.g. Intel UHD 600 series EXPECT_GE(props.maxLineWidth, props.minLineWidth); EXPECT_GE(props.maxSamplerAnisotropy, 1); EXPECT_GE(props.graphicsQueueCount, 1); diff --git a/tests/RHIValidationTestDX12/Impl.cpp b/tests/RHIValidationTestDX12/Impl.cpp index cc13dd3..f9811a6 100644 --- a/tests/RHIValidationTestDX12/Impl.cpp +++ b/tests/RHIValidationTestDX12/Impl.cpp @@ -10,6 +10,7 @@ mCreateInfo.options[1] = { .optionIndex = -1 }; // clang-format on +#include #include #include #include @@ -24,5 +25,9 @@ #include -extern "C" { __declspec(dllexport) extern const UINT D3D12SDKVersion = 618; } -extern "C" { __declspec(dllexport) extern const char* D3D12SDKPath = ".\\RHI\\D3D12\\"; } +extern "C" { +__declspec(dllexport) extern const UINT D3D12SDKVersion = 618; +} +extern "C" { +__declspec(dllexport) extern const char* D3D12SDKPath = ".\\RHI\\D3D12\\"; +} diff --git a/tests/RHIValidationTestVulkan/Impl.cpp b/tests/RHIValidationTestVulkan/Impl.cpp index d81f24c..7b0aba8 100644 --- a/tests/RHIValidationTestVulkan/Impl.cpp +++ b/tests/RHIValidationTestVulkan/Impl.cpp @@ -12,6 +12,7 @@ // clang-format on +#include #include #include #include diff --git a/vendor/DirectX-Headers b/vendor/DirectX-Headers index ce005b3..3337475 160000 --- a/vendor/DirectX-Headers +++ b/vendor/DirectX-Headers @@ -1 +1 @@ -Subproject commit ce005b3fdb8a13aac8c484704bc7858b0bef7d20 +Subproject commit 33374754f65baac0500dda6187e371136357246f diff --git a/vendor/volk b/vendor/volk index 4f3bcee..94e52eb 160000 --- a/vendor/volk +++ b/vendor/volk @@ -1 +1 @@ -Subproject commit 4f3bcee79618a9abe79f4c717c50379197c77512 +Subproject commit 94e52eb242b84785dae5e1383ae5ffa56870492e