From 3fe3ee520de01b7b5b01250d1d46993168cc08c8 Mon Sep 17 00:00:00 2001 From: Adam Sawicki Date: Wed, 4 Nov 2020 14:16:44 +0100 Subject: [PATCH] Added Allocator::CreateResource1, AllocateMemory1 --- src/D3D12MemAlloc.cpp | 309 +++++++++++++++++++++++++++++++++++++++++- src/D3D12MemAlloc.h | 34 +++++ src/Doxyfile | 6 +- src/Tests.cpp | 39 ++++++ 4 files changed, 381 insertions(+), 7 deletions(-) diff --git a/src/D3D12MemAlloc.cpp b/src/D3D12MemAlloc.cpp index d5ad4b0..a7ab321 100644 --- a/src/D3D12MemAlloc.cpp +++ b/src/D3D12MemAlloc.cpp @@ -2462,6 +2462,9 @@ public: ~AllocatorPimpl(); ID3D12Device* GetDevice() const { return m_Device; } +#ifdef __ID3D12Device4_INTERFACE_DEFINED__ + ID3D12Device4* GetDevice4() const { return m_Device4; } +#endif // Shortcut for "Allocation Callbacks", because this function is called so often. const ALLOCATION_CALLBACKS& GetAllocs() const { return m_AllocationCallbacks; } const D3D12_FEATURE_DATA_D3D12_OPTIONS& GetD3D12Options() const { return m_D3D12Options; } @@ -2479,11 +2482,31 @@ public: REFIID riidResource, void** ppvResource); +#ifdef __ID3D12Device4_INTERFACE_DEFINED__ + HRESULT CreateResource1( + const ALLOCATION_DESC* pAllocDesc, + const D3D12_RESOURCE_DESC* pResourceDesc, + D3D12_RESOURCE_STATES InitialResourceState, + const D3D12_CLEAR_VALUE *pOptimizedClearValue, + ID3D12ProtectedResourceSession *pProtectedSession, + Allocation** ppAllocation, + REFIID riidResource, + void** ppvResource); +#endif // #ifdef __ID3D12Device4_INTERFACE_DEFINED__ + HRESULT AllocateMemory( const ALLOCATION_DESC* pAllocDesc, const D3D12_RESOURCE_ALLOCATION_INFO* pAllocInfo, Allocation** ppAllocation); +#ifdef __ID3D12Device4_INTERFACE_DEFINED__ + HRESULT AllocateMemory1( + const ALLOCATION_DESC* pAllocDesc, + const D3D12_RESOURCE_ALLOCATION_INFO* pAllocInfo, + ID3D12ProtectedResourceSession *pProtectedSession, + Allocation** ppAllocation); +#endif // #ifdef __ID3D12Device4_INTERFACE_DEFINED__ + HRESULT CreateAliasingResource( Allocation* pAllocation, UINT64 AllocationLocalOffset, @@ -2534,9 +2557,12 @@ private: const bool m_UseMutex; const bool m_AlwaysCommitted; ID3D12Device* m_Device; // AddRef +#ifdef __ID3D12Device4_INTERFACE_DEFINED__ + ID3D12Device4* m_Device4 = NULL; // AddRef, optional +#endif IDXGIAdapter* m_Adapter; // AddRef #if D3D12MA_DXGI_1_4 - IDXGIAdapter3* m_Adapter3; // AddRef, optional + IDXGIAdapter3* m_Adapter3 = NULL; // AddRef, optional #endif UINT64 m_PreferredBlockSize; ALLOCATION_CALLBACKS m_AllocationCallbacks; @@ -2573,6 +2599,19 @@ private: REFIID riidResource, void** ppvResource); +#ifdef __ID3D12Device4_INTERFACE_DEFINED__ + HRESULT AllocateCommittedResource1( + const ALLOCATION_DESC* pAllocDesc, + const D3D12_RESOURCE_DESC* pResourceDesc, + const D3D12_RESOURCE_ALLOCATION_INFO& resAllocInfo, + D3D12_RESOURCE_STATES InitialResourceState, + const D3D12_CLEAR_VALUE *pOptimizedClearValue, + ID3D12ProtectedResourceSession *pProtectedSession, + Allocation** ppAllocation, + REFIID riidResource, + void** ppvResource); +#endif // #ifdef __ID3D12Device4_INTERFACE_DEFINED__ + // Allocates and registers new heap without any resources placed in it, as dedicated allocation. // Creates and returns Allocation object. HRESULT AllocateHeap( @@ -2580,6 +2619,14 @@ private: const D3D12_RESOURCE_ALLOCATION_INFO& allocInfo, Allocation** ppAllocation); +#ifdef __ID3D12Device4_INTERFACE_DEFINED__ + HRESULT AllocateHeap1( + const ALLOCATION_DESC* pAllocDesc, + const D3D12_RESOURCE_ALLOCATION_INFO& allocInfo, + ID3D12ProtectedResourceSession *pProtectedSession, + Allocation** ppAllocation); +#endif // #ifdef __ID3D12Device4_INTERFACE_DEFINED__ + /* If SupportsResourceHeapTier2(): 0: D3D12_HEAP_TYPE_DEFAULT @@ -4061,9 +4108,6 @@ AllocatorPimpl::AllocatorPimpl(const ALLOCATION_CALLBACKS& allocationCallbacks, m_AlwaysCommitted((desc.Flags & ALLOCATOR_FLAG_ALWAYS_COMMITTED) != 0), m_Device(desc.pDevice), m_Adapter(desc.pAdapter), -#if D3D12MA_DXGI_1_4 - m_Adapter3(NULL), -#endif m_PreferredBlockSize(desc.PreferredBlockSize != 0 ? desc.PreferredBlockSize : D3D12MA_DEFAULT_BLOCK_SIZE), m_AllocationCallbacks(allocationCallbacks), m_CurrentFrameIndex(0), @@ -4099,6 +4143,10 @@ HRESULT AllocatorPimpl::Init(const ALLOCATOR_DESC& desc) desc.pAdapter->QueryInterface(&m_Adapter3); #endif +#ifdef __ID3D12Device4_INTERFACE_DEFINED__ + m_Device->QueryInterface(&m_Device4); +#endif + HRESULT hr = m_Adapter->GetDesc(&m_AdapterDesc); if(FAILED(hr)) { @@ -4141,6 +4189,9 @@ HRESULT AllocatorPimpl::Init(const ALLOCATOR_DESC& desc) AllocatorPimpl::~AllocatorPimpl() { +#ifdef __ID3D12Device4_INTERFACE_DEFINED__ + SAFE_RELEASE(m_Device4); +#endif #if D3D12MA_DXGI_1_4 SAFE_RELEASE(m_Adapter3); #endif @@ -4314,6 +4365,55 @@ HRESULT AllocatorPimpl::CreateResource( } } +#ifdef __ID3D12Device4_INTERFACE_DEFINED__ +HRESULT AllocatorPimpl::CreateResource1( + const ALLOCATION_DESC* pAllocDesc, + const D3D12_RESOURCE_DESC* pResourceDesc, + D3D12_RESOURCE_STATES InitialResourceState, + const D3D12_CLEAR_VALUE *pOptimizedClearValue, + ID3D12ProtectedResourceSession *pProtectedSession, + Allocation** ppAllocation, + REFIID riidResource, + void** ppvResource) +{ + // Fall back to old implementation + if(pProtectedSession == NULL) + { + return CreateResource(pAllocDesc, pResourceDesc, InitialResourceState, pOptimizedClearValue, ppAllocation, riidResource, ppvResource); + } + + *ppAllocation = NULL; + if(ppvResource) + { + *ppvResource = NULL; + } + + // In current implementation it must always be allocated as committed. + if(pAllocDesc->CustomPool != NULL || + (pAllocDesc->Flags & ALLOCATION_FLAG_NEVER_ALLOCATE) != 0) + { + return E_INVALIDARG; + } + + D3D12_RESOURCE_DESC finalResourceDesc = *pResourceDesc; + D3D12_RESOURCE_ALLOCATION_INFO resAllocInfo = GetResourceAllocationInfo(finalResourceDesc); + resAllocInfo.Alignment = D3D12MA_MAX(resAllocInfo.Alignment, D3D12MA_DEBUG_ALIGNMENT); + D3D12MA_ASSERT(IsPow2(resAllocInfo.Alignment)); + D3D12MA_ASSERT(resAllocInfo.SizeInBytes > 0); + + return AllocateCommittedResource1( + pAllocDesc, + &finalResourceDesc, + resAllocInfo, + InitialResourceState, + pOptimizedClearValue, + pProtectedSession, + ppAllocation, + riidResource, + ppvResource); +} +#endif // #ifdef __ID3D12Device4_INTERFACE_DEFINED__ + HRESULT AllocatorPimpl::AllocateMemory( const ALLOCATION_DESC* pAllocDesc, const D3D12_RESOURCE_ALLOCATION_INFO* pAllocInfo, @@ -4384,6 +4484,37 @@ HRESULT AllocatorPimpl::AllocateMemory( } } +#ifdef __ID3D12Device4_INTERFACE_DEFINED__ +HRESULT AllocatorPimpl::AllocateMemory1( + const ALLOCATION_DESC* pAllocDesc, + const D3D12_RESOURCE_ALLOCATION_INFO* pAllocInfo, + ID3D12ProtectedResourceSession *pProtectedSession, + Allocation** ppAllocation) +{ + // Fall back to old implementation + if(pProtectedSession == NULL) + { + return AllocateMemory(pAllocDesc, pAllocInfo, ppAllocation); + } + + *ppAllocation = NULL; + + // In current implementation it must always be allocated as separate CreateHeap1. + if(pAllocDesc->CustomPool != NULL || + (pAllocDesc->Flags & ALLOCATION_FLAG_NEVER_ALLOCATE) != 0) + { + return E_INVALIDARG; + } + + if(!IsHeapTypeValid(pAllocDesc->HeapType)) + { + return E_INVALIDARG; + } + + return AllocateHeap1(pAllocDesc, *pAllocInfo, pProtectedSession, ppAllocation); +} +#endif // #ifdef __ID3D12Device4_INTERFACE_DEFINED__ + HRESULT AllocatorPimpl::CreateAliasingResource( Allocation* pAllocation, UINT64 AllocationLocalOffset, @@ -4557,6 +4688,77 @@ HRESULT AllocatorPimpl::AllocateCommittedResource( return hr; } +#ifdef __ID3D12Device4_INTERFACE_DEFINED__ +HRESULT AllocatorPimpl::AllocateCommittedResource1( + const ALLOCATION_DESC* pAllocDesc, + const D3D12_RESOURCE_DESC* pResourceDesc, + const D3D12_RESOURCE_ALLOCATION_INFO& resAllocInfo, + D3D12_RESOURCE_STATES InitialResourceState, + const D3D12_CLEAR_VALUE *pOptimizedClearValue, + ID3D12ProtectedResourceSession *pProtectedSession, + Allocation** ppAllocation, + REFIID riidResource, + void** ppvResource) +{ + if(m_Device4 == NULL) + { + return E_NOINTERFACE; + } + + if((pAllocDesc->Flags & ALLOCATION_FLAG_NEVER_ALLOCATE) != 0) + { + return E_OUTOFMEMORY; + } + + if((pAllocDesc->Flags & ALLOCATION_FLAG_WITHIN_BUDGET) != 0) + { + Budget budget = {}; + GetBudgetForHeapType(budget, pAllocDesc->HeapType); + if(budget.UsageBytes + resAllocInfo.SizeInBytes > budget.BudgetBytes) + { + return E_OUTOFMEMORY; + } + } + + D3D12_HEAP_PROPERTIES heapProps = {}; + heapProps.Type = pAllocDesc->HeapType; + + const D3D12_HEAP_FLAGS heapFlags = pAllocDesc->ExtraHeapFlags; + + ID3D12Resource* res = NULL; + HRESULT hr = m_Device4->CreateCommittedResource1( + &heapProps, heapFlags, pResourceDesc, InitialResourceState, + pOptimizedClearValue, pProtectedSession, IID_PPV_ARGS(&res)); + if(SUCCEEDED(hr)) + { + if(ppvResource != NULL) + { + hr = res->QueryInterface(riidResource, ppvResource); + } + if(SUCCEEDED(hr)) + { + const BOOL wasZeroInitialized = TRUE; + Allocation* alloc = m_AllocationObjectAllocator.Allocate(this, resAllocInfo.SizeInBytes, wasZeroInitialized); + alloc->InitCommitted(pAllocDesc->HeapType); + alloc->SetResource(res, pResourceDesc); + + *ppAllocation = alloc; + + RegisterCommittedAllocation(*ppAllocation, pAllocDesc->HeapType); + + const UINT heapTypeIndex = HeapTypeToIndex(pAllocDesc->HeapType); + m_Budget.AddAllocation(heapTypeIndex, resAllocInfo.SizeInBytes); + m_Budget.m_BlockBytes[heapTypeIndex] += resAllocInfo.SizeInBytes; + } + else + { + res->Release(); + } + } + return hr; +} +#endif // #ifdef __ID3D12Device4_INTERFACE_DEFINED__ + HRESULT AllocatorPimpl::AllocateHeap( const ALLOCATION_DESC* pAllocDesc, const D3D12_RESOURCE_ALLOCATION_INFO& allocInfo, @@ -4603,6 +4805,60 @@ HRESULT AllocatorPimpl::AllocateHeap( return hr; } +#ifdef __ID3D12Device4_INTERFACE_DEFINED__ +HRESULT AllocatorPimpl::AllocateHeap1( + const ALLOCATION_DESC* pAllocDesc, + const D3D12_RESOURCE_ALLOCATION_INFO& allocInfo, + ID3D12ProtectedResourceSession *pProtectedSession, + Allocation** ppAllocation) +{ + *ppAllocation = nullptr; + + if(m_Device4 == NULL) + { + return E_NOINTERFACE; + } + + if((pAllocDesc->Flags & ALLOCATION_FLAG_NEVER_ALLOCATE) != 0) + { + return E_OUTOFMEMORY; + } + + if((pAllocDesc->Flags & ALLOCATION_FLAG_WITHIN_BUDGET) != 0) + { + Budget budget = {}; + GetBudgetForHeapType(budget, pAllocDesc->HeapType); + if(budget.UsageBytes + allocInfo.SizeInBytes > budget.BudgetBytes) + { + return E_OUTOFMEMORY; + } + } + + D3D12_HEAP_FLAGS heapFlags = pAllocDesc->ExtraHeapFlags; + + D3D12_HEAP_DESC heapDesc = {}; + heapDesc.SizeInBytes = allocInfo.SizeInBytes; + heapDesc.Properties.Type = pAllocDesc->HeapType; + heapDesc.Alignment = allocInfo.Alignment; + heapDesc.Flags = heapFlags; + + ID3D12Heap* heap = nullptr; + HRESULT hr = m_Device4->CreateHeap1(&heapDesc, pProtectedSession, __uuidof(*heap), (void**)&heap); + if(SUCCEEDED(hr)) + { + const BOOL wasZeroInitialized = TRUE; + (*ppAllocation) = m_AllocationObjectAllocator.Allocate(this, allocInfo.SizeInBytes, wasZeroInitialized); + (*ppAllocation)->InitHeap(pAllocDesc->HeapType, heap); + RegisterCommittedAllocation(*ppAllocation, pAllocDesc->HeapType); + + const UINT heapTypeIndex = HeapTypeToIndex(pAllocDesc->HeapType); + m_Budget.AddAllocation(heapTypeIndex, allocInfo.SizeInBytes); + m_Budget.m_BlockBytes[heapTypeIndex] += allocInfo.SizeInBytes; + } + return hr; +} +#endif // #ifdef __ID3D12Device4_INTERFACE_DEFINED__ + UINT AllocatorPimpl::CalcDefaultPoolCount() const { if(SupportsResourceHeapTier2()) @@ -5472,6 +5728,27 @@ HRESULT Allocator::CreateResource( return m_Pimpl->CreateResource(pAllocDesc, pResourceDesc, InitialResourceState, pOptimizedClearValue, ppAllocation, riidResource, ppvResource); } +#ifdef __ID3D12Device4_INTERFACE_DEFINED__ +HRESULT Allocator::CreateResource1( + const ALLOCATION_DESC* pAllocDesc, + const D3D12_RESOURCE_DESC* pResourceDesc, + D3D12_RESOURCE_STATES InitialResourceState, + const D3D12_CLEAR_VALUE *pOptimizedClearValue, + ID3D12ProtectedResourceSession *pProtectedSession, + Allocation** ppAllocation, + REFIID riidResource, + void** ppvResource) +{ + if(!pAllocDesc || !pResourceDesc || !ppAllocation) + { + D3D12MA_ASSERT(0 && "Invalid arguments passed to Allocator::CreateResource1."); + return E_INVALIDARG; + } + D3D12MA_DEBUG_GLOBAL_MUTEX_LOCK + return m_Pimpl->CreateResource1(pAllocDesc, pResourceDesc, InitialResourceState, pOptimizedClearValue, pProtectedSession, ppAllocation, riidResource, ppvResource); +} +#endif // #ifdef __ID3D12Device4_INTERFACE_DEFINED__ + HRESULT Allocator::AllocateMemory( const ALLOCATION_DESC* pAllocDesc, const D3D12_RESOURCE_ALLOCATION_INFO* pAllocInfo, @@ -5493,6 +5770,30 @@ HRESULT Allocator::AllocateMemory( return m_Pimpl->AllocateMemory(pAllocDesc, pAllocInfo, ppAllocation); } +#ifdef __ID3D12Device4_INTERFACE_DEFINED__ +HRESULT Allocator::AllocateMemory1( + const ALLOCATION_DESC* pAllocDesc, + const D3D12_RESOURCE_ALLOCATION_INFO* pAllocInfo, + ID3D12ProtectedResourceSession *pProtectedSession, + Allocation** ppAllocation) +{ + if(!pAllocDesc || + !pAllocInfo || + !ppAllocation || + !(pAllocInfo->Alignment == 0 || + pAllocInfo->Alignment == D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT || + pAllocInfo->Alignment == D3D12_DEFAULT_MSAA_RESOURCE_PLACEMENT_ALIGNMENT) || + pAllocInfo->SizeInBytes == 0 || + pAllocInfo->SizeInBytes % (64ull * 1024) != 0) + { + D3D12MA_ASSERT(0 && "Invalid arguments passed to Allocator::AllocateMemory."); + return E_INVALIDARG; + } + D3D12MA_DEBUG_GLOBAL_MUTEX_LOCK + return m_Pimpl->AllocateMemory1(pAllocDesc, pAllocInfo, pProtectedSession, ppAllocation); +} +#endif // #ifdef __ID3D12Device4_INTERFACE_DEFINED__ + HRESULT Allocator::CreateAliasingResource( Allocation* pAllocation, UINT64 AllocationLocalOffset, diff --git a/src/D3D12MemAlloc.h b/src/D3D12MemAlloc.h index 7ac8cf2..9382948 100644 --- a/src/D3D12MemAlloc.h +++ b/src/D3D12MemAlloc.h @@ -1253,6 +1253,25 @@ public: REFIID riidResource, void** ppvResource); +#ifdef __ID3D12Device4_INTERFACE_DEFINED__ + /** \brief Similar to Allocator::CreateResource, but supports additional parameter `pProtectedSession`. + + If `pProtectedSession` is not null, current implementation always creates the resource as committed + using `ID3D12Device4::CreateCommittedResource1`. + + To work correctly, `ID3D12Device4` interface must be available in the current system. Otherwise, `E_NOINTERFACE` is returned. + */ + HRESULT CreateResource1( + const ALLOCATION_DESC* pAllocDesc, + const D3D12_RESOURCE_DESC* pResourceDesc, + D3D12_RESOURCE_STATES InitialResourceState, + const D3D12_CLEAR_VALUE *pOptimizedClearValue, + ID3D12ProtectedResourceSession *pProtectedSession, + Allocation** ppAllocation, + REFIID riidResource, + void** ppvResource); +#endif // #ifdef __ID3D12Device4_INTERFACE_DEFINED__ + /** \brief Allocates memory without creating any resource placed in it. This function is similar to `ID3D12Device::CreateHeap`, but it may really assign @@ -1277,6 +1296,21 @@ public: const D3D12_RESOURCE_ALLOCATION_INFO* pAllocInfo, Allocation** ppAllocation); +#ifdef __ID3D12Device4_INTERFACE_DEFINED__ + /** \brief Similar to Allocator::AllocateMemory, but supports additional parameter `pProtectedSession`. + + If `pProtectedSession` is not null, current implementation always creates separate heap + using `ID3D12Device4::CreateHeap1`. + + To work correctly, `ID3D12Device4` interface must be available in the current system. Otherwise, `E_NOINTERFACE` is returned. + */ + HRESULT AllocateMemory1( + const ALLOCATION_DESC* pAllocDesc, + const D3D12_RESOURCE_ALLOCATION_INFO* pAllocInfo, + ID3D12ProtectedResourceSession *pProtectedSession, + Allocation** ppAllocation); +#endif // #ifdef __ID3D12Device4_INTERFACE_DEFINED__ + /** \brief Creates a new resource in place of an existing allocation. This is useful for memory aliasing. \param pAllocation Existing allocation indicating the memory where the new resource should be created. diff --git a/src/Doxyfile b/src/Doxyfile index 1bd8947..81f5850 100644 --- a/src/Doxyfile +++ b/src/Doxyfile @@ -2161,7 +2161,7 @@ ENABLE_PREPROCESSING = YES # The default value is: NO. # This tag requires that the tag ENABLE_PREPROCESSING is set to YES. -MACRO_EXPANSION = NO +MACRO_EXPANSION = YES # If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES then # the macro expansion is limited to the macros specified with the PREDEFINED and @@ -2169,7 +2169,7 @@ MACRO_EXPANSION = NO # The default value is: NO. # This tag requires that the tag ENABLE_PREPROCESSING is set to YES. -EXPAND_ONLY_PREDEF = NO +EXPAND_ONLY_PREDEF = YES # If the SEARCH_INCLUDES tag is set to YES, the include files in the # INCLUDE_PATH will be searched if a #include is found. @@ -2201,7 +2201,7 @@ INCLUDE_FILE_PATTERNS = # recursively expanded use the := operator instead of the = operator. # This tag requires that the tag ENABLE_PREPROCESSING is set to YES. -PREDEFINED = +PREDEFINED = __ID3D12Device4_INTERFACE_DEFINED__ # If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this # tag can be used to specify a list of macro names that should be expanded. The diff --git a/src/Tests.cpp b/src/Tests.cpp index d37aa28..c075834 100644 --- a/src/Tests.cpp +++ b/src/Tests.cpp @@ -1335,6 +1335,44 @@ static void TestMultithreading(const TestContext& ctx) } } +static void TestDevice4(const TestContext& ctx) +{ + wprintf(L"Test ID3D12Device4\n"); + + CComPtr dev4; + CHECK_HR(ctx.device->QueryInterface(&dev4)); + + D3D12_PROTECTED_RESOURCE_SESSION_DESC sessionDesc = {}; + CComPtr session; + CHECK_HR(dev4->CreateProtectedResourceSession(&sessionDesc, IID_PPV_ARGS(&session))); + + // Create a buffer + + D3D12_RESOURCE_DESC resourceDesc; + FillResourceDescForBuffer(resourceDesc, 1024); + + D3D12MA::ALLOCATION_DESC allocDesc = {}; + allocDesc.HeapType = D3D12_HEAP_TYPE_DEFAULT; + + D3D12MA::Allocation* alloc = nullptr; + CComPtr bufRes; + CHECK_HR(ctx.allocator->CreateResource1(&allocDesc, &resourceDesc, + D3D12_RESOURCE_STATE_COMMON, NULL, + session, &alloc, IID_PPV_ARGS(&bufRes))); + AllocationUniquePtr bufAllocPtr{alloc}; + + // Create a heap + + D3D12_RESOURCE_ALLOCATION_INFO heapAllocInfo = { + D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT * 100, // SizeInBytes + D3D12_DEFAULT_RESOURCE_PLACEMENT_ALIGNMENT, // Alignment + }; + + CHECK_HR(ctx.allocator->AllocateMemory1(&allocDesc, &heapAllocInfo, session, &alloc)); + AllocationUniquePtr heapAllocPtr{alloc}; + +} + static void TestGroupVirtual(const TestContext& ctx) { TestVirtualBlocks(ctx); @@ -1355,6 +1393,7 @@ static void TestGroupBasics(const TestContext& ctx) TestTransfer(ctx); TestZeroInitialized(ctx); TestMultithreading(ctx); + TestDevice4(ctx); } void Test(const TestContext& ctx)