1 module buffer;
2 
3 import deferred;
4 
5 import gfx.core;
6 import gfx.graal;
7 import gfx.math;
8 
9 struct P3N3Vertex
10 {
11     FVec3 pos;
12     FVec3 normal;
13 }
14 
15 struct P2T2Vertex
16 {
17     FVec2 pos;
18     FVec2 texCoord;
19 }
20 
21 /// per frame UBO for the geom pipeline
22 struct GeomFrameUbo
23 {
24     FMat4 viewProj = FMat4.identity;
25 }
26 /// per draw call UBO for the geom pipeline
27 struct GeomModelData
28 {
29     FMat4 model = FMat4.identity;
30     FVec4 color = fvec(0, 0, 0, 1);
31     float[4] pad; // 32 bytes alignment for dynamic offset
32 }
33 static assert(GeomModelData.alignof / 32 == 0);
34 /// ditto
35 struct GeomModelUbo
36 {
37     GeomModelData[3] data;
38 }
39 static assert(GeomModelUbo.alignof / 32 == 0);
40 
41 /// per frame UBO for the light pipeline
42 struct LightFrameUbo
43 {
44     FVec4 viewerPos = fvec(0, 0, 0, 1);
45 }
46 /// per draw call UBO for the light pipeline
47 struct LightModelUbo
48 {
49     FMat4 modelViewProj = FMat4.identity;
50     FVec4 position = fvec(0, 0, 0, 1);
51     /// color in RGB, luminosity in A
52     FVec4 colAndLum = fvec(0, 0, 0, 1);
53     float[8] pad; // 32 bytes alignment for dynamic offset
54 }
55 static assert(LightModelUbo.alignof / 32 == 0);
56 
57 struct MeshBuffer
58 {
59     Rc!Buffer indexBuf;
60     Rc!Buffer vertexBuf;
61     Interval!size_t indices;
62     Interval!size_t vertices;
63 
64     @property uint indicesCount()
65     {
66         return cast(uint)indices.length / ushort.sizeof;
67     }
68 
69     void bindIndex(CommandBuffer cmd)
70     {
71         cmd.bindIndexBuffer(indexBuf, indices.start, IndexType.u16);
72     }
73 
74     VertexBinding vertexBinding()
75     {
76         return VertexBinding(
77             vertexBuf.obj, vertices.start,
78         );
79     }
80 
81     void release()
82     {
83         indexBuf.unload();
84         vertexBuf.unload();
85     }
86 }
87 
88 
89 struct DynBuffer(T)
90 {
91     Rc!Buffer buffer;
92     MemoryMap mmap;
93     size_t len;
94 
95     this(DeferredExample ex, size_t len, BufferUsage usage)
96     {
97         buffer = ex.createDynamicBuffer(len * T.sizeof, usage);
98         mmap = buffer.boundMemory.map();
99         this.len = len;
100     }
101 
102     ~this()
103     {
104         mmap = MemoryMap.init;
105         buffer.unload();
106     }
107 
108     @property T[] data()
109     {
110         return mmap.view!(T[])()[];
111     }
112 
113     void addToMemorySet(ref MappedMemorySet mms)
114     {
115         mmap.addToSet(mms);
116     }
117 
118     BufferDescriptor descriptor(in size_t start=0, in size_t size=0)
119     {
120         return buffer.descriptor(start*T.sizeof, size*T.sizeof);
121     }
122 }
123 
124 class DeferredBuffers : AtomicRefCounted
125 {
126     MeshBuffer hiResSphere;
127     // normals pointing inwards to render "bulbs" with point light inside
128     MeshBuffer invertedSphere;
129     MeshBuffer loResSphere;
130     MeshBuffer square;
131 
132     DynBuffer!GeomFrameUbo geomFrameUbo;
133     DynBuffer!GeomModelUbo geomModelUbo;
134     DynBuffer!LightFrameUbo lightFrameUbo;
135     DynBuffer!LightModelUbo lightModelUbo;
136 
137     this(DeferredExample ex, size_t saucerCount)
138     {
139         prepareMeshBuffers(ex);
140         prepareUboBuffers(ex, saucerCount);
141     }
142 
143     override void dispose()
144     {
145         import std.algorithm : move;
146 
147         hiResSphere.release();
148         invertedSphere.release();
149         loResSphere.release();
150         square.release();
151         move(geomFrameUbo);
152         move(geomModelUbo);
153         move(lightFrameUbo);
154         move(lightModelUbo);
155     }
156 
157     final void prepareMeshBuffers(DeferredExample ex)
158     {
159         import std.algorithm : map;
160         import std.array : array;
161 
162         const hiRes = buildUvSpheroid(fvec(0, 0, 0), 1f, 1f, 15);
163         const loRes = buildUvSpheroid(fvec(0, 0, 0), 1f, 1f, 6);
164 
165         const invertedVtx = hiRes.vertices.map!(v => P3N3Vertex(v.pos, -v.normal)).array;
166 
167         const ushort[] squareIndices = [ 0, 1, 2, 0, 2, 3 ];
168         const squareVertices = [
169             P2T2Vertex(fvec(-1, -1), fvec(0, 0)),
170             P2T2Vertex(fvec(-1, 1), fvec(0, 1)),
171             P2T2Vertex(fvec(1, 1), fvec(1, 1)),
172             P2T2Vertex(fvec(1, -1), fvec(1, 0)),
173         ];
174 
175         Rc!Buffer indexBuf = ex.createStaticBuffer(
176             hiRes.indices ~ loRes.indices ~ squareIndices,
177             BufferUsage.index
178         );
179 
180         const hiResVData = cast(const(ubyte)[])hiRes.vertices;
181         const invertedVData = cast(const(ubyte)[])invertedVtx;
182         const loResVData = cast(const(ubyte)[])loRes.vertices.map!(v => v.pos).array;
183         const squareVData = cast(const(ubyte)[])squareVertices;
184         Rc!Buffer vertexBuf = ex.createStaticBuffer(
185             hiResVData ~ invertedVData ~ loResVData ~ squareVData,
186             BufferUsage.vertex);
187 
188         const hiResI = hiRes.indices.length * ushort.sizeof;
189         const invertedI = hiResI;
190         const loResI = invertedI + loRes.indices.length * ushort.sizeof;
191         const squareI = loResI + squareIndices.length * ushort.sizeof;
192 
193         const hiResV = hiRes.vertices.length * P3N3Vertex.sizeof;
194         const invertedV = 2 * hiResV;
195         const loResV = invertedV + loRes.vertices.length * FVec3.sizeof;
196         const squareV = loResV + squareVertices.length * P2T2Vertex.sizeof;
197 
198         hiResSphere.indexBuf = indexBuf;
199         hiResSphere.vertexBuf = vertexBuf;
200         hiResSphere.indices = interval(0, hiResI);
201         hiResSphere.vertices = interval(0, hiResV);
202 
203         invertedSphere.indexBuf = indexBuf;
204         invertedSphere.vertexBuf = vertexBuf;
205         invertedSphere.indices = interval(0, hiResI);
206         invertedSphere.vertices = interval(hiResV, invertedV);
207 
208         loResSphere.indexBuf = indexBuf;
209         loResSphere.vertexBuf = vertexBuf;
210         loResSphere.indices = interval(hiResI, loResI);
211         loResSphere.vertices = interval(invertedV, loResV);
212 
213         square.indexBuf = indexBuf;
214         square.vertexBuf = vertexBuf;
215         square.indices = interval(loResI, squareI);
216         square.vertices = interval(loResV, squareV);
217     }
218 
219     final void prepareUboBuffers(DeferredExample ex, size_t saucerCount)
220     {
221         geomFrameUbo = DynBuffer!GeomFrameUbo(ex, 1, BufferUsage.uniform);
222         geomModelUbo = DynBuffer!GeomModelUbo(ex, saucerCount, BufferUsage.uniform);
223         lightFrameUbo = DynBuffer!LightFrameUbo(ex, 1, BufferUsage.uniform);
224         lightModelUbo = DynBuffer!LightModelUbo(ex, saucerCount, BufferUsage.uniform);
225     }
226 
227     final void flush(Device device)
228     {
229         MappedMemorySet mms;
230         geomModelUbo.addToMemorySet(mms);
231         geomFrameUbo.addToMemorySet(mms);
232         lightModelUbo.addToMemorySet(mms);
233         lightFrameUbo.addToMemorySet(mms);
234         device.flushMappedMemory(mms);
235     }
236 }
237 
238 struct Mesh
239 {
240     ushort[] indices;
241     P3N3Vertex[] vertices;
242 }
243 
244 Mesh buildUvSpheroid(in FVec3 center, in float radius, in float height,
245         in uint latDivs = 8)
246 {
247     import std.array : uninitializedArray;
248     import std.math : PI, cos, sin;
249 
250     const longDivs = latDivs * 2;
251     const totalVertices = 2 + (latDivs - 1) * longDivs;
252     const totalIndices = 3 * longDivs * (2 + 2 * (latDivs - 2));
253 
254     auto vertices = uninitializedArray!(P3N3Vertex[])(totalVertices);
255 
256     size_t ind = 0;
257     void unitVertex(in FVec3 pos)
258     {
259         const v = fvec(radius * pos.xy, height * pos.z);
260         vertices[ind++] = P3N3Vertex(center + v, normalize(v));
261     }
262 
263     const latAngle = PI / latDivs;
264     const longAngle = 2 * PI / longDivs;
265 
266     // north pole
267     unitVertex(fvec(0, 0, 1));
268     // latitudes
269     foreach (lat; 1 .. latDivs)
270     {
271         const alpha = latAngle * lat;
272         const z = cos(alpha);
273         const sa = sin(alpha);
274         foreach (lng; 0 .. longDivs)
275         {
276             const beta = longAngle * lng;
277             const x = cos(beta) * sa;
278             const y = sin(beta) * sa;
279 
280             unitVertex(fvec(x, y, z));
281         }
282     }
283     // south pole
284     unitVertex(fvec(0, 0, -1));
285     assert(ind == totalVertices);
286 
287     // build ccw triangle faces
288     auto indices = uninitializedArray!(ushort[])(totalIndices);
289     ind = 0;
290     void face(in size_t v0, in size_t v1, in size_t v2)
291     {
292         indices[ind++] = cast(ushort) v0;
293         indices[ind++] = cast(ushort) v1;
294         indices[ind++] = cast(ushort) v2;
295     }
296 
297     size_t left(size_t lng)
298     {
299         return lng;
300     }
301 
302     size_t right(size_t lng)
303     {
304         return lng == longDivs - 1 ? 0 : lng + 1;
305     }
306 
307     // northern div triangles
308     foreach (lng; 0 .. longDivs)
309     {
310         const pole = 0;
311         const bot = 1;
312         face(pole, bot + left(lng), bot + right(lng));
313     }
314     // middle divs rectangles
315     foreach (lat; 0 .. latDivs - 2)
316     {
317         const top = 1 + lat * longDivs;
318         const bot = top + longDivs;
319         foreach (lng; 0 .. longDivs)
320         {
321             const l = left(lng);
322             const r = right(lng);
323 
324             face(top + l, bot + l, bot + r);
325             face(top + l, bot + r, top + r);
326         }
327     }
328     // southern div triangles
329     foreach (lng; 0 .. longDivs)
330     {
331         const pole = totalVertices - 1;
332         const top = 1 + (latDivs - 2) * longDivs;
333         face(pole, top + right(lng), top + left(lng));
334     }
335     assert(ind == totalIndices);
336 
337     return Mesh(indices, vertices);
338 }