Added support for resource tight alignment

Added macro D3D12MA_TIGHT_ALIGNMENT_SUPPORTED, flag ALLOCATOR_FLAG_DONT_USE_TIGHT_ALIGNMENT, function Allocator::IsTightAlignmentSupported.
diff --git a/include/D3D12MemAlloc.h b/include/D3D12MemAlloc.h
index 2dc31c7..737e6ae 100644
--- a/include/D3D12MemAlloc.h
+++ b/include/D3D12MemAlloc.h
@@ -1053,6 +1053,8 @@
     It can also be disabled for a single allocation by using #ALLOCATION_FLAG_STRATEGY_MIN_TIME.

     */

     ALLOCATOR_FLAG_DONT_PREFER_SMALL_BUFFERS_COMMITTED = 0x10,

+    /** TODO document... */

+    ALLOCATOR_FLAG_DONT_USE_TIGHT_ALIGNMENT = 0x20,

 };

 

 /// \brief Parameters of created Allocator object. To be used with CreateAllocator().

@@ -1127,6 +1129,15 @@
     `#define D3D12MA_OPTIONS16_SUPPORTED 1` is needed for the compilation of this library. Otherwise the flag is always false.

     */

     BOOL IsGPUUploadHeapSupported() const;

+    /** \brief Returns true if resource tight alignment is supported on the current system.

+    When supported, it is automatically used by the library, unless

+    #ALLOCATOR_FLAG_DONT_USE_TIGHT_ALIGNMENT flag was specified on allocator creation.

+

+    This flag is fetched from `D3D12_FEATURE_DATA_TIGHT_ALIGNMENT::SupportTier`.

+

+    `#define D3D12MA_TIGHT_ALIGNMENT_SUPPORTED 1` is needed for the compilation of this library. Otherwise the flag is always false.

+    */

+    BOOL IsTightAlignmentSupported() const;

     /** \brief Returns total amount of memory of specific segment group, in bytes.

     

     \param memorySegmentGroup use `DXGI_MEMORY_SEGMENT_GROUP_LOCAL` or DXGI_MEMORY_SEGMENT_GROUP_NON_LOCAL`.

diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 23e0941..6134ea3 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -198,3 +198,11 @@
         target_compile_definitions(D3D12Sample PRIVATE D3D12MA_OPTIONS16_SUPPORTED=1)
     endif()
 endif()
+
+option(D3D12MA_TIGHT_ALIGNMENT_SUPPORTED "Set if using Agility SDK 1.716.0-preview or newer that defines D3D12_FEATURE_DATA_TIGHT_ALIGNMENT." OFF)
+if(D3D12MA_TIGHT_ALIGNMENT_SUPPORTED)
+    target_compile_definitions(D3D12MemoryAllocator PRIVATE D3D12MA_TIGHT_ALIGNMENT_SUPPORTED=1)
+    if(${D3D12MA_BUILD_SAMPLE} AND ${WIN32})
+        target_compile_definitions(D3D12Sample PRIVATE D3D12MA_TIGHT_ALIGNMENT_SUPPORTED=1)
+    endif()
+endif()
diff --git a/src/D3D12MemAlloc.cpp b/src/D3D12MemAlloc.cpp
index 6962f0b..7eb79f0 100644
--- a/src/D3D12MemAlloc.cpp
+++ b/src/D3D12MemAlloc.cpp
@@ -5911,6 +5911,8 @@
     BOOL IsCacheCoherentUMA() const { return m_D3D12Architecture.CacheCoherentUMA; }

     bool SupportsResourceHeapTier2() const { return m_D3D12Options.ResourceHeapTier >= D3D12_RESOURCE_HEAP_TIER_2; }

     bool IsGPUUploadHeapSupported() const { return m_GPUUploadHeapSupported != FALSE; }

+    bool IsTightAlignmentSupported() const { return m_TightAlignmentSupported != FALSE; }

+    bool IsTightAlignmentEnabled() const { return IsTightAlignmentSupported() && m_UseTightAlignment; }

     bool UseMutex() const { return m_UseMutex; }

     AllocationObjectAllocator& GetAllocationObjectAllocator() { return m_AllocationObjectAllocator; }

     UINT GetCurrentFrameIndex() const { return m_CurrentFrameIndex.load(); }

@@ -5998,6 +6000,7 @@
     const bool m_AlwaysCommitted;

     const bool m_MsaaAlwaysCommitted;

     const bool m_PreferSmallBuffersCommitted;

+    const bool m_UseTightAlignment;

     bool m_DefaultPoolsNotZeroed = false;

     ID3D12Device* m_Device; // AddRef

 #ifdef __ID3D12Device1_INTERFACE_DEFINED__

@@ -6022,6 +6025,7 @@
     DXGI_ADAPTER_DESC m_AdapterDesc;

     D3D12_FEATURE_DATA_D3D12_OPTIONS m_D3D12Options;

     BOOL m_GPUUploadHeapSupported = FALSE;

+    BOOL m_TightAlignmentSupported = FALSE;

     D3D12_FEATURE_DATA_ARCHITECTURE m_D3D12Architecture;

     AllocationObjectAllocator m_AllocationObjectAllocator;

 

@@ -6089,7 +6093,8 @@
     : m_UseMutex((desc.Flags & ALLOCATOR_FLAG_SINGLETHREADED) == 0),

     m_AlwaysCommitted((desc.Flags & ALLOCATOR_FLAG_ALWAYS_COMMITTED) != 0),

     m_MsaaAlwaysCommitted((desc.Flags & ALLOCATOR_FLAG_MSAA_TEXTURES_ALWAYS_COMMITTED) != 0),

-    m_PreferSmallBuffersCommitted((desc.Flags & ALLOCATOR_FLAG_DONT_PREFER_SMALL_BUFFERS_COMMITTED) == 0),

+    m_PreferSmallBuffersCommitted((desc.Flags& ALLOCATOR_FLAG_DONT_PREFER_SMALL_BUFFERS_COMMITTED) == 0),

+    m_UseTightAlignment((desc.Flags & ALLOCATOR_FLAG_DONT_USE_TIGHT_ALIGNMENT) == 0),

     m_Device(desc.pDevice),

     m_Adapter(desc.pAdapter),

     m_PreferredBlockSize(desc.PreferredBlockSize != 0 ? desc.PreferredBlockSize : D3D12MA_DEFAULT_BLOCK_SIZE),

@@ -6177,6 +6182,20 @@
     }

 #endif

 

+    // You must define macro `#define D3D12MA_TIGHT_ALIGNMENT_SUPPORTED 1` to enable resource tight alignment!

+    // Unfortunately there is no way to programmatically check if the included <d3d12.h> defines D3D12_FEATURE_DATA_TIGHT_ALIGNMENT or not.

+    // Main interfaces have respective macros like __ID3D12Device4_INTERFACE_DEFINED__, but structures like this do not.

+#if D3D12MA_TIGHT_ALIGNMENT_SUPPORTED

+    {

+        D3D12_FEATURE_DATA_TIGHT_ALIGNMENT tightAlignment = {};

+        hr = m_Device->CheckFeatureSupport(D3D12_FEATURE_D3D12_TIGHT_ALIGNMENT, &tightAlignment, sizeof(tightAlignment));

+        if (SUCCEEDED(hr))

+        {

+            m_TightAlignmentSupported = tightAlignment.SupportTier >= D3D12_TIGHT_ALIGNMENT_TIER_1;

+        }

+    }

+#endif

+

     hr = m_Device->CheckFeatureSupport(D3D12_FEATURE_ARCHITECTURE, &m_D3D12Architecture, sizeof(m_D3D12Architecture));

     if (FAILED(hr))

     {

@@ -6861,6 +6880,9 @@
 

                 json.WriteString(L"GPUUploadHeapSupported");

                 json.WriteBool(m_GPUUploadHeapSupported != FALSE);

+

+                json.WriteString(L"TightAlignmentSupported");

+                json.WriteBool(m_TightAlignmentSupported != FALSE);

             }

             json.EndObject();

         }

@@ -7623,8 +7645,19 @@
 D3D12_RESOURCE_ALLOCATION_INFO AllocatorPimpl::GetResourceAllocationInfo(D3D12_RESOURCE_DESC_T& inOutResourceDesc) const

 {

 #ifdef __ID3D12Device1_INTERFACE_DEFINED__

-    /* Optional optimization: Microsoft documentation says:

-    https://docs.microsoft.com/en-us/windows/win32/api/d3d12/nf-d3d12-id3d12device-getresourceallocationinfo

+

+#if D3D12MA_TIGHT_ALIGNMENT_SUPPORTED

+    if (IsTightAlignmentEnabled() &&

+        // Don't allow USE_TIGHT_ALIGNMENT together with ALLOW_CROSS_ADAPTER as there is a D3D Debug Layer error:

+        // D3D12 ERROR: ID3D12Device::GetResourceAllocationInfo: D3D12_RESOURCE_DESC::Flag D3D12_RESOURCE_FLAG_USE_TIGHT_ALIGNMENT will be ignored since D3D12_RESOURCE_FLAG_ALLOW_CROSS_ADAPTER is set. [ STATE_CREATION ERROR #599: CREATERESOURCE_INVALIDMISCFLAGS]

+        (inOutResourceDesc.Flags & D3D12_RESOURCE_FLAG_ALLOW_CROSS_ADAPTER) == 0)

+    {

+        inOutResourceDesc.Flags |= D3D12_RESOURCE_FLAG_USE_TIGHT_ALIGNMENT;

+    }

+#endif // #if D3D12MA_TIGHT_ALIGNMENT_SUPPORTED

+

+    /* Optional optimization: Microsoft documentation of the ID3D12Device::

+    GetResourceAllocationInfo function says:

 

     Your application can forgo using GetResourceAllocationInfo for buffer resources

     (D3D12_RESOURCE_DIMENSION_BUFFER). Buffers have the same size on all adapters,

@@ -7632,12 +7665,14 @@
     D3D12_RESOURCE_DESC::Width.

     */

     if (inOutResourceDesc.Alignment == 0 &&

-        inOutResourceDesc.Dimension == D3D12_RESOURCE_DIMENSION_BUFFER)

+        inOutResourceDesc.Dimension == D3D12_RESOURCE_DIMENSION_BUFFER &&

+        !IsTightAlignmentEnabled())

     {

         return {

             AlignUp<UINT64>(inOutResourceDesc.Width, D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT), // SizeInBytes

             D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT }; // Alignment

     }

+

 #endif // #ifdef __ID3D12Device1_INTERFACE_DEFINED__

 

 #if D3D12MA_USE_SMALL_RESOURCE_PLACEMENT_ALIGNMENT

@@ -9552,6 +9587,11 @@
     return m_Pimpl->IsGPUUploadHeapSupported();

 }

 

+BOOL Allocator::IsTightAlignmentSupported() const

+{

+    return m_Pimpl->IsTightAlignmentSupported();

+}

+

 UINT64 Allocator::GetMemoryCapacity(UINT memorySegmentGroup) const

 {

     return m_Pimpl->GetMemoryCapacity(memorySegmentGroup);

diff --git a/src/D3D12Sample.cpp b/src/D3D12Sample.cpp
index e39e3fe..ea88ee9 100644
--- a/src/D3D12Sample.cpp
+++ b/src/D3D12Sample.cpp
@@ -438,6 +438,11 @@
     UINT* pNumRows = reinterpret_cast<UINT*>(pRowSizesInBytes + NumSubresources);

 

     D3D12_RESOURCE_DESC Desc = pDestinationResource->GetDesc();

+    

+    // Needed because of the D3D Debug Layer error:

+    // D3D12 ERROR: ID3D12Device::GetCopyableFootprints: D3D12_RESOURCE_DESC::Alignment is invalid. The value is 16. When D3D12_RESOURCE_DESC::Flag bit for D3D12_RESOURCE_FLAG_USE_TIGHT_ALIGNMENT is set, Alignment must be 0. [ STATE_CREATION ERROR #721: CREATERESOURCE_INVALIDALIGNMENT]

+    Desc.Alignment = 0;

+    

     ID3D12Device* pDevice;

     pDestinationResource->GetDevice(__uuidof(*pDevice), reinterpret_cast<void**>(&pDevice));

     pDevice->GetCopyableFootprints(&Desc, FirstSubresource, NumSubresources, IntermediateOffset, pLayouts, pNumRows, pRowSizesInBytes, &RequiredSize);

@@ -1052,8 +1057,7 @@
     CHECK_HR( g_Allocator->CreateResource(

         &vertexBufferAllocDesc,

         &vertexBufferResourceDesc, // resource description for a buffer

-        D3D12_RESOURCE_STATE_COPY_DEST, // we will start this heap in the copy destination state since we will copy data

-                                        // from the upload heap to this heap

+        D3D12_RESOURCE_STATE_COMMON,

         nullptr, // optimized clear value must be null for this type of resource. used for render targets and depth/stencil buffers

         &g_VertexBufferAllocation,

         IID_PPV_ARGS(&vertexBufferPtr)) );

@@ -1165,7 +1169,7 @@
     CHECK_HR( g_Allocator->CreateResource(

         &indexBufferAllocDesc,

         &indexBufferResourceDesc, // resource description for a buffer

-        D3D12_RESOURCE_STATE_COPY_DEST, // start in the copy destination state

+        D3D12_RESOURCE_STATE_COMMON,

         nullptr, // optimized clear value must be null for this type of resource

         &g_IndexBufferAllocation,

         IID_PPV_ARGS(&g_IndexBuffer)) );

diff --git a/src/Tests.cpp b/src/Tests.cpp
index bd065eb..40388f9 100644
--- a/src/Tests.cpp
+++ b/src/Tests.cpp
@@ -51,6 +51,17 @@
 static constexpr CONFIG_TYPE ConfigType = CONFIG_TYPE_AVERAGE;

 static const char* FREE_ORDER_NAMES[] = { "FORWARD", "BACKWARD", "RANDOM", };

 

+// Indexes match enum D3D12_HEAP_TYPE.

+static const WCHAR* const HEAP_TYPE_NAMES[] =

+{

+    L"",

+    L"DEFAULT",

+    L"UPLOAD",

+    L"READBACK",

+    L"CUSTOM",

+    L"GPU_UPLOAD",

+};

+

 static void CurrentTimeToStr(std::string& out)

 {

     time_t rawTime; time(&rawTime);

@@ -2990,6 +3001,55 @@
 #endif

 }

 

+static void TestTightAlignment(const TestContext& ctx)

+{

+#if D3D12MA_TIGHT_ALIGNMENT_SUPPORTED

+    using namespace D3D12MA;

+

+    wprintf(L"Test resource tight alignment\n");

+

+    if(!ctx.allocator->IsTightAlignmentSupported())

+    {

+        wprintf(L"    Skipped due to tight alignment not supported.\n");

+        return;

+    }

+

+    // Use a custom heap to make sure our small buffers are not created as committed.

+    POOL_DESC poolDesc = {};

+    poolDesc.BlockSize = 1024 * 1024;

+    poolDesc.MinBlockCount = poolDesc.MaxBlockCount = 1;

+

+    D3D12_RESOURCE_DESC resDesc;

+    FillResourceDescForBuffer(resDesc, 16);

+

+    const D3D12_HEAP_TYPE heapTypes[] = { D3D12_HEAP_TYPE_DEFAULT, D3D12_HEAP_TYPE_UPLOAD };

+    for (auto heapType : heapTypes)

+    {

+        poolDesc.HeapProperties.Type = heapType;

+        ComPtr<Pool> pool;

+        CHECK_HR(ctx.allocator->CreatePool(&poolDesc, &pool));

+

+        ALLOCATION_DESC allocDesc = {};

+        allocDesc.CustomPool = pool.Get();

+

+        ComPtr<Allocation> allocs[2] = {};

+

+        for (size_t i = 0; i < _countof(allocs); ++i)

+        {

+            CHECK_HR(ctx.allocator->CreateResource(&allocDesc, &resDesc,

+                D3D12_RESOURCE_STATE_COMMON, NULL, &allocs[i], IID_NULL, NULL));

+            CHECK_BOOL(allocs[i] && allocs[i]->GetResource());

+        }

+        

+        // Print the offset of the 2nd buffer.

+        wprintf(L"    In D3D12_HEAP_TYPE_%s, a %llu B buffer was aligned to %llu B.\n",

+            HEAP_TYPE_NAMES[(size_t)heapType],

+            resDesc.Width,

+            allocs[1]->GetOffset());

+    }

+#endif

+}

+

 static void TestVirtualBlocks(const TestContext& ctx)

 {

     wprintf(L"Test virtual blocks\n");

@@ -4257,6 +4317,7 @@
 #endif

 

     TestGPUUploadHeap(ctx);

+    TestTightAlignment(ctx);

 

     FILE* file;

     fopen_s(&file, "Results.csv", "w");