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 }