-
Notifications
You must be signed in to change notification settings - Fork 64
/
Copy pathIDeviceMemoryAllocation.h
217 lines (188 loc) · 9.63 KB
/
IDeviceMemoryAllocation.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
// Copyright (C) 2018-2023 - DevSH Graphics Programming Sp. z O.O.
// This file is part of the "Nabla Engine".
// For conditions of distribution and use, see copyright notice in nabla.h
#ifndef _NBL_VIDEO_I_DRIVER_MEMORY_ALLOCATION_H_INCLUDED_
#define _NBL_VIDEO_I_DRIVER_MEMORY_ALLOCATION_H_INCLUDED_
#include "nbl/core/IReferenceCounted.h"
#include "nbl/core/util/bitflag.h"
#include "nbl/video/EApiType.h"
namespace nbl::video
{
//fwd decl
class ILogicalDevice;
//! Class corresponding to VkDeviceMemory and emulating them on OpenGL
/** This class replaces and takes over the functionality from the
old-alpha-version IGPUMappedBuffer class.
TO COPY BETWEEN MEMORY ALLOCATIONS you need to have them bound to
one or two IGPUBuffers and execute IVideoDriver::copyBuffer between them.
We only support persistently mapped buffers with ARB_buffer_storage.
Please don't ask us to support Buffer Orphaning. */
class IDeviceMemoryAllocation : public virtual core::IReferenceCounted
{
friend class IDeviceMemoryAllocator;
friend class ILogicalDevice;
public:
//! Access flags for how the application plans to use mapped memory (if any)
/** When you create the memory you can allow for it to be mapped (be given a pointer)
for reading and writing directly from it, however the driver needs to know up-front
about what you will do with it when allocating the memory so that it is allocated
from the correct heap.
If you don't match your creation and mapping flags the you will get errors and undefined behaviour. */
enum E_MAPPING_CPU_ACCESS_FLAGS : uint8_t
{
EMCAF_NO_MAPPING_ACCESS=0x0u,
EMCAF_READ=0x1u,
EMCAF_WRITE=0x2u,
EMCAF_READ_AND_WRITE=(EMCAF_READ|EMCAF_WRITE)
};
//! Memory allocate flags
enum E_MEMORY_ALLOCATE_FLAGS : uint8_t
{
EMAF_NONE = 0x00000000,
// EMAF_DEVICE_MASK_BIT = 0x00000001, // We'll just deduce it in the future from it being provided
EMAF_DEVICE_ADDRESS_BIT = 0x00000002,
// EMAF_DEVICE_ADDRESS_CAPTURE_REPLAY_BIT = 0x00000004, // See notes in VulkanSpec and IDeviceMemoryAllocator::SAllocateInfo
};
enum E_MEMORY_PROPERTY_FLAGS : uint16_t
{
EMPF_NONE = 0,
EMPF_DEVICE_LOCAL_BIT = 0x00000001,
EMPF_HOST_READABLE_BIT = 0x00000002,
EMPF_HOST_WRITABLE_BIT = 0x00000004,
EMPF_HOST_COHERENT_BIT = 0x00000008,
EMPF_HOST_CACHED_BIT = 0x000000010,
//EMPF_LAZILY_ALLOCATED_BIT = 0x00000020,
//EMPF_PROTECTED_BIT = 0x00000040,
//EMPF_DEVICE_COHERENT_BIT_AMD = 0x00000080,
//EMPF_DEVICE_UNCACHED_BIT_AMD = 0x00000100,
//EMPF_RDMA_CAPABLE_BIT_NV = 0x00000200,
};
//
enum E_MEMORY_HEAP_FLAGS : uint32_t
{
EMHF_NONE = 0,
EMHF_DEVICE_LOCAL_BIT = 0x00000001,
EMHF_MULTI_INSTANCE_BIT = 0x00000002,
};
//! Flags for imported/exported allocation
enum E_EXTERNAL_HANDLE_TYPE : uint32_t
{
EHT_NONE = 0,
EHT_OPAQUE_WIN32 = 0x00000002,
EHT_OPAQUE_WIN32_KMT = 0x00000004,
EHT_D3D11_TEXTURE = 0x00000008,
EHT_D3D11_TEXTURE_KMT = 0x00000010,
EHT_D3D12_HEAP = 0x00000020,
EHT_D3D12_RESOURCE = 0x00000040,
EHT_HOST_MAPPED_FOREIGN_MEMORY = 0x00000100,
};
//
const ILogicalDevice* getOriginDevice() const {return m_originDevice;}
//!
E_API_TYPE getAPIType() const;
//! Whether the allocation was made for a specific resource and is supposed to only be bound to that resource.
inline bool isDedicated() const {return m_params.dedicated;}
//! Returns the size of the memory allocation
inline size_t getAllocationSize() const {return m_params.allocationSize;}
//!
inline core::bitflag<E_MEMORY_ALLOCATE_FLAGS> getAllocateFlags() const { return m_params.allocateFlags; }
//!
inline core::bitflag<E_MEMORY_PROPERTY_FLAGS> getMemoryPropertyFlags() const { return m_params.memoryPropertyFlags; }
//! Utility function, tells whether the allocation can be mapped (whether mapMemory will ever return anything other than nullptr)
inline bool isMappable() const {return m_params.memoryPropertyFlags.hasFlags(EMPF_HOST_READABLE_BIT)|| m_params.memoryPropertyFlags.hasFlags(EMPF_HOST_WRITABLE_BIT);}
//! Utility function, tell us if writes by the CPU or GPU need extra visibility operations to become visible for reading on the other processor
/** Only execute flushes or invalidations if the allocation requires them, and batch them (flush one combined range instead of two or more)
for greater efficiency. To execute a flush or invalidation, use IDriver::flushMappedAllocationRanges and IDriver::invalidateMappedAllocationRanges respectively. */
inline bool haveToMakeVisible() const
{
return !m_params.memoryPropertyFlags.hasFlags(EMPF_HOST_COHERENT_BIT);
}
//!
struct MemoryRange
{
size_t offset = 0ull;
size_t length = 0ull;
};
inline void* map(const MemoryRange& range, const core::bitflag<E_MAPPING_CPU_ACCESS_FLAGS> accessHint=IDeviceMemoryAllocation::EMCAF_READ_AND_WRITE)
{
if (isCurrentlyMapped())
return nullptr;
if(accessHint.hasFlags(EMCAF_READ) && !m_params.memoryPropertyFlags.hasFlags(EMPF_HOST_READABLE_BIT))
return nullptr;
if(accessHint.hasFlags(EMCAF_WRITE) && !m_params.memoryPropertyFlags.hasFlags(EMPF_HOST_WRITABLE_BIT))
return nullptr;
m_mappedPtr = reinterpret_cast<uint8_t*>(map_impl(range,accessHint));
if (m_mappedPtr)
m_mappedPtr -= range.offset;
m_mappedRange = m_mappedPtr ? range:MemoryRange{};
m_currentMappingAccess = m_mappedPtr ? EMCAF_NO_MAPPING_ACCESS:accessHint;
return m_mappedPtr;
}
// returns true on success, false on failure
inline bool unmap()
{
if (!isCurrentlyMapped())
return false;
if (!unmap_impl())
return false;
m_mappedPtr = nullptr;
m_mappedRange = {};
m_currentMappingAccess = EMCAF_NO_MAPPING_ACCESS;
return true;
}
//!
inline bool isCurrentlyMapped() const { return m_mappedPtr; }
//! Only valid if `isCurrentlyMapped` is true
inline const MemoryRange& getMappedRange() const { return m_mappedRange; }
//! returns current mapping access based on latest mapMemory's "accessHint", has no effect on Nabla's Vulkan Backend
inline core::bitflag<E_MAPPING_CPU_ACCESS_FLAGS> getCurrentMappingAccess() const {return m_currentMappingAccess;}
//! Gets internal pointer.
/** It is best you use a GPU Fence to ensure any operations that you have queued up which are or will be writing to this memory
or reading from it have completed before you start using the returned pointer. Otherwise this will result in a race condition.
WARNING: UNMAP will invalidate pointer!
WARNING: NEED TO FENCE BEFORE USE!
@returns Internal pointer with 0 offset into the memory allocation, so the address that it is pointing to may be unsafe
to access without an offset if a memory range (if a subrange not starting at 0 was mapped). */
inline void* getMappedPointer() { return m_mappedPtr; }
//! Constant variant of getMappedPointer
inline const void* getMappedPointer() const { return m_mappedPtr; }
struct SInfo
{
uint64_t allocationSize = 0;
core::bitflag<IDeviceMemoryAllocation::E_MEMORY_ALLOCATE_FLAGS> allocateFlags = IDeviceMemoryAllocation::EMAF_NONE;
// Handle Type for external resources
IDeviceMemoryAllocation::E_EXTERNAL_HANDLE_TYPE externalHandleType = IDeviceMemoryAllocation::EHT_NONE;
//! Imports the given handle if externalHandle != nullptr && externalHandleType != EHT_NONE
//! Creates exportable memory if externalHandle == nullptr && externalHandleType != EHT_NONE
void* externalHandle = nullptr;
};
struct SCreationParams: SInfo
{
core::bitflag<E_MEMORY_PROPERTY_FLAGS> memoryPropertyFlags = E_MEMORY_PROPERTY_FLAGS::EMPF_NONE;
const bool dedicated = false;
};
protected:
inline void setPostDestroyCleanup(std::unique_ptr<struct ICleanup>&& cleanup)
{
m_postDestroyCleanup = std::move(cleanup);
}
IDeviceMemoryAllocation(
const ILogicalDevice* originDevice, SCreationParams&& params = {})
: m_originDevice(originDevice)
, m_params(std::move(params))
, m_mappedPtr(nullptr)
, m_mappedRange{ 0, 0 }
, m_currentMappingAccess(EMCAF_NO_MAPPING_ACCESS)
{}
virtual void* map_impl(const MemoryRange& range, const core::bitflag<E_MAPPING_CPU_ACCESS_FLAGS> accessHint) = 0;
virtual bool unmap_impl() = 0;
const ILogicalDevice* m_originDevice = nullptr;
SCreationParams m_params = {};
uint8_t* m_mappedPtr = nullptr;
MemoryRange m_mappedRange = {};
core::bitflag<E_MAPPING_CPU_ACCESS_FLAGS> m_currentMappingAccess = EMCAF_NO_MAPPING_ACCESS;
std::unique_ptr<struct ICleanup> m_postDestroyCleanup = nullptr;
};
NBL_ENUM_ADD_BITWISE_OPERATORS(IDeviceMemoryAllocation::E_MEMORY_PROPERTY_FLAGS)
} // end namespace nbl::video
#endif