1 module gfx.graal.memory; 2 3 import gfx.core.rc; 4 import gfx.graal.device; 5 6 /// Properties of memory allocated by the device 7 enum MemProps { 8 deviceLocal = 0x01, 9 hostVisible = 0x02, 10 hostCoherent = 0x04, 11 hostCached = 0x08, 12 lazilyAllocated = 0x10, 13 } 14 15 /// Structure representing all heaps and types of memory from a device. 16 /// A device can have different heaps each supporting different types. 17 struct MemoryProperties { 18 MemoryHeap[] heaps; 19 MemoryType[] types; 20 } 21 22 struct MemoryHeap { 23 size_t size; 24 bool deviceLocal; 25 } 26 27 struct MemoryType { 28 MemProps props; 29 uint heapIndex; 30 } 31 32 struct MemoryRequirements { 33 /// minimal allocation requirement 34 size_t size; 35 /// alignment required when binding the resource to a memory with offset 36 size_t alignment; 37 /// mask where each bit is set if the corresponding memory type is supported. 38 /// For example if the resource supports types 0 and 2 from MemoryProperties, 39 /// memTypeMask will be 00000101 40 size_t memTypeMask; 41 } 42 43 /// Holds a memory mapping to host visible memory. 44 /// Memory is unmapped when object goes out of scope. 45 /// It also acts as a void[], and allows to get a typed slice view on the data. 46 struct MemoryMap 47 { 48 import std.traits : isDynamicArray; 49 50 private DeviceMemory dm; 51 private size_t offset; 52 private void[] data; 53 54 private this(DeviceMemory dm, in size_t offset, in size_t size) 55 { 56 this.dm = dm; 57 this.offset = offset; 58 this.data = dm.mapRaw(offset, size)[0 .. size]; 59 } 60 61 @disable this(this); 62 63 ~this() 64 { 65 // should handle dtor of MemoryMap.init 66 if (dm) dm.unmapRaw(); 67 } 68 69 void addToSet(ref MappedMemorySet set) 70 { 71 set.addMM(MappedMemorySet.MM(dm, offset, data.length)); 72 } 73 74 /// Get a typed view on the memory map that support slice and indexing operations. 75 /// Params: 76 /// offset = the offset to the requested memory in bytes 77 /// count = the number of elements of type T.init[0] to be mapped 78 /// Warning: offset and count are not in the same units. 79 /// This is necessary in order to allow a memory block to hold several arrays 80 /// of different element types. 81 auto view(T)(in size_t offset=0, in size_t count=size_t.max) 82 if (isDynamicArray!T) 83 { 84 alias Elem = typeof(T.init[0]); 85 const len = count == size_t.max ? data.length : count*Elem.sizeof; 86 return MemoryMapArrayView!Elem(cast(T)(data[offset .. offset+len])); 87 } 88 89 size_t opDollar() { 90 return data.length; 91 } 92 93 size_t[2] opSlice(size_t beg, size_t end) { 94 return [beg, end]; 95 } 96 97 void[] opIndex() { 98 return data; 99 } 100 void[] opIndex(in size_t[2] slice) { 101 return data[ slice[0] .. slice[1] ]; 102 } 103 104 void opIndexAssign(in void[] vals) { 105 data[] = vals; 106 } 107 void opIndexAssign(in void[] vals, size_t[2] slice) { 108 data[slice[0] .. slice[1]] = vals; 109 } 110 } 111 112 private struct MemoryMapArrayView(T) 113 { 114 private T[] data; 115 116 size_t opDollar() { 117 return data.length; 118 } 119 120 size_t[2] opSlice(size_t beg, size_t end) { 121 return [beg, end]; 122 } 123 124 T[] opIndex() { 125 return data; 126 } 127 T[] opIndex(in size_t[2] slice) { 128 return data[ slice[0] .. slice[1] ]; 129 } 130 131 void opIndexAssign(in T[] vals) { 132 data[] = vals; 133 } 134 void opIndexAssign(in T[] vals, size_t[2] slice) { 135 data[slice[0] .. slice[1]] = vals; 136 } 137 138 static if (!is(T == void)) { 139 T opIndex(size_t index) { 140 return data[index]; 141 } 142 void opIndexAssign(in T val, size_t ind) { 143 data[ind] = val; 144 } 145 void opIndexAssign(in T val, size_t[2] slice) { 146 data[slice[0] .. slice[1]] = val; 147 } 148 } 149 } 150 151 152 interface DeviceMemory : AtomicRefCounted 153 { 154 @property uint typeIndex(); 155 @property MemProps props(); 156 @property size_t size(); 157 158 /// Map device memory to host visible memory. 159 /// Params: 160 /// offset = the offset to the requested memory in bytes 161 /// size = the size of the mapping in bytes. 162 void* mapRaw(in size_t offset, in size_t size); 163 void unmapRaw(); 164 165 /// Produce a scoped memory map. 166 /// The the memory will be unmapped when the object goes out of scope. 167 /// The is an untyped memory holder. In order to access the memory, call 168 /// view with the right type parameter. 169 /// Params: 170 /// offset = the offset to the requested memory in bytes 171 /// count = the number of bytes to be mapped 172 final auto map(in size_t offset=0, in size_t sz=size_t.max) 173 { 174 const len = sz==size_t.max ? this.size : sz; 175 return MemoryMap(this, offset, sz); 176 } 177 } 178 179 180 181 /// cast a typed slice into a blob of bytes 182 /// (same representation; no copy is made) 183 void[] untypeSlice(T)(T[] slice) if(!is(T == const)) 184 { 185 if (slice.length == 0) return []; 186 auto loc = cast(void*)slice.ptr; 187 return loc[0 .. slice.length*T.sizeof]; 188 } 189 190 /// ditto 191 const(void)[] untypeSlice(T)(const(T)[] slice) 192 { 193 if (slice.length == 0) return []; 194 auto loc = cast(const(void)*)slice.ptr; 195 return loc[0 .. slice.length*T.sizeof]; 196 } 197 198 /// cast a blob of bytes into a typed slice 199 T[] retypeSlice(T)(void[] slice) if (!is(T == const)) 200 in { 201 assert (!slice.length || (slice.length % T.sizeof) == 0); 202 } 203 body { 204 if(slice.length == 0) return []; 205 auto loc = cast(T*)slice.ptr; 206 return loc[0 .. slice.length / T.sizeof]; 207 } 208 209 /// ditto 210 const(T)[] retypeSlice(T)(const(void)[] slice) 211 in { 212 assert (!slice.length || (slice.length % T.sizeof) == 0); 213 } 214 body { 215 if(slice.length == 0) return []; 216 auto loc = cast(const(T)*)slice.ptr; 217 return loc[0 .. slice.length / T.sizeof]; 218 } 219 220 unittest { 221 int[] slice = [1, 2, 3, 4]; 222 auto bytes = cast(ubyte[])untypeSlice(slice); 223 auto ints = retypeSlice!int(bytes); 224 assert(bytes.length == 16); 225 version(LittleEndian) { 226 assert(bytes == [ 227 1, 0, 0, 0, 228 2, 0, 0, 0, 229 3, 0, 0, 0, 230 4, 0, 0, 0, 231 ]); 232 } 233 else { 234 assert(bytes == [ 235 0, 0, 0, 1, 236 0, 0, 0, 2, 237 0, 0, 0, 3, 238 0, 0, 0, 4, 239 ]); 240 } 241 assert(ints.length == 4); 242 assert(ints == slice); 243 assert(ints.ptr == slice.ptr); 244 } 245 246 /// cast an array of typed slices to another array of blob of bytes 247 /// an allocation is performed for the top container (the array of arrays) 248 /// but the underlying data is moved without allocation 249 void[][] untypeSlices(T)(T[][] slices) if (!is(T == const)) { 250 void[][] res = new void[][slices.length]; 251 foreach(i, s; slices) { 252 res[i] = untypeSlice(s); 253 } 254 return res; 255 } 256 257 /// ditto 258 const(void)[][] untypeSlices(T)(const(T)[][] slices) { 259 const(void)[][] res = new const(void)[][slices.length]; 260 foreach(i, s; slices) { 261 res[i] = untypeSlice(s); 262 } 263 return res; 264 } 265 266 unittest { 267 int[][] texels = [ [1, 2], [3, 4] ]; 268 auto bytes = cast(ubyte[][])untypeSlices(texels); 269 assert(bytes.length == 2); 270 assert(bytes[0].length == 8); 271 assert(bytes[1].length == 8); 272 version(LittleEndian) { 273 assert(bytes == [ 274 [ 1, 0, 0, 0, 275 2, 0, 0, 0, ], 276 [ 3, 0, 0, 0, 277 4, 0, 0, 0, ], 278 ]); 279 } 280 else { 281 assert(bytes == [ 282 [ 0, 0, 0, 1, 283 0, 0, 0, 2, ], 284 [ 0, 0, 0, 3, 285 0, 0, 0, 4, ], 286 ]); 287 } 288 }