1 /// ImageBase module
2 module gfx.graal.image;
3 
4 import gfx.core.rc;
5 import gfx.core.typecons;
6 import gfx.graal.format;
7 import gfx.graal.memory;
8 import gfx.graal.pipeline : CompareOp, ImageDescriptor,
9         SamplerDescriptor, ImageSamplerDescriptor;
10 
11 import std.typecons : Flag;
12 
13 struct ImageInfo
14 {
15     ImageType type;
16     ImageDims dims;
17     Format format;
18     ImageUsage usage;
19     ImageTiling tiling;
20     uint layers=1;
21     uint samples=1;
22     uint levels=1;
23 
24     static ImageInfo d1 (in uint width) {
25         return ImageInfo(
26             ImageType.d1,
27             ImageDims(width, 1, 1)
28         );
29     }
30     static ImageInfo d2 (in uint width, in uint height) {
31         return ImageInfo(
32             ImageType.d2,
33             ImageDims(width, height, 1)
34         );
35     }
36     static ImageInfo d3 (in uint width, in uint height, in uint depth) {
37         return ImageInfo(
38             ImageType.d3,
39             ImageDims(width, height, depth)
40         );
41     }
42     static ImageInfo cube (in uint width, in uint height) {
43         return ImageInfo(
44             ImageType.cube,
45             ImageDims(width, height, 6)
46         );
47     }
48     static ImageInfo d1Array (in uint width, in uint layers) {
49         auto ii = ImageInfo(
50             ImageType.d1Array,
51             ImageDims(width, 1, 1)
52         );
53         ii.layers = layers;
54         return ii;
55     }
56     static ImageInfo d2Array (in uint width, uint height, in uint layers) {
57         auto ii = ImageInfo(
58             ImageType.d2Array,
59             ImageDims(width, height, 1)
60         );
61         ii.layers = layers;
62         return ii;
63     }
64     static ImageInfo cubeArray (in uint width, in uint height, in uint layers) {
65         auto ii = ImageInfo(
66             ImageType.cubeArray,
67             ImageDims(width, height, 1)
68         );
69         ii.layers = layers;
70         return ii;
71     }
72 
73     ImageInfo withFormat(in Format format) const {
74         ImageInfo ii = this;
75         ii.format = format;
76         return ii;
77     }
78     ImageInfo withUsage(in ImageUsage usage) const {
79         ImageInfo ii = this;
80         ii.usage = usage;
81         return ii;
82     }
83     ImageInfo withTiling(in ImageTiling tiling) const {
84         ImageInfo ii = this;
85         ii.tiling = tiling;
86         return ii;
87     }
88     ImageInfo withSamples(in uint samples) const {
89         ImageInfo ii = this;
90         ii.samples = samples;
91         return ii;
92     }
93     ImageInfo withLevels(in uint levels) const {
94         ImageInfo ii = this;
95         ii.levels = levels;
96         return ii;
97     }
98 }
99 
100 enum ImageType {
101     d1, d1Array,
102     d2, d2Array,
103     d3, cube, cubeArray
104 }
105 
106 bool isCube(in ImageType it) {
107     return it == ImageType.cube || it == ImageType.cubeArray;
108 }
109 
110 bool isArray(in ImageType it) {
111     return it == ImageType.d1Array || it == ImageType.d2Array || it == ImageType.cubeArray;
112 }
113 
114 enum CubeFace {
115     none,
116     posX, negX,
117     posY, negY,
118     posZ, negZ,
119 }
120 
121 /// an array of faces in the order that is expected during cube initialization
122 immutable cubeFaces = [
123     CubeFace.posX, CubeFace.negX,
124     CubeFace.posY, CubeFace.negY,
125     CubeFace.posZ, CubeFace.negZ,
126 ];
127 
128 struct ImageDims
129 {
130     uint width;
131     uint height=1;
132     uint depth=1;
133 }
134 
135 enum ImageUsage {
136     none = 0,
137     transferSrc             = 0x01,
138     transferDst             = 0x02,
139     sampled                 = 0x04,
140     storage                 = 0x08,
141     colorAttachment         = 0x10,
142     depthStencilAttachment  = 0x20,
143     transientAttachment     = 0x40,
144     inputAttachment         = 0x80,
145 
146     transfer                = transferSrc | transferDst,
147 }
148 
149 enum ImageLayout {
150 	undefined                       = 0,
151 	general                         = 1,
152 	colorAttachmentOptimal          = 2,
153 	depthStencilAttachmentOptimal   = 3,
154 	depthStencilReadOnlyOptimal     = 4,
155 	shaderReadOnlyOptimal           = 5,
156 	transferSrcOptimal              = 6,
157 	transferDstOptimal              = 7,
158 	preinitialized                  = 8,
159     presentSrc                      = 1_000_001_002, // TODO impl actual mapping to vulkan
160 }
161 
162 enum ImageTiling {
163     optimal,
164     linear,
165 }
166 
167 enum ImageAspect {
168     color           = 0x01,
169     depth           = 0x02,
170     stencil         = 0x04,
171     depthStencil    = depth | stencil,
172     none            = 0x00,
173 }
174 
175 
176 struct ImageSubresourceLayer
177 {
178     ImageAspect aspect  = ImageAspect.color;
179     uint mipLevel       = 0;
180     uint firstLayer     = 0;
181     uint layers         = 1;
182 }
183 
184 struct ImageSubresourceRange
185 {
186     ImageAspect aspect  = ImageAspect.color;
187     size_t firstLevel   = 0;
188     size_t levels       = 1;
189     size_t firstLayer   = 0;
190     size_t layers       = 1;
191 }
192 
193 enum CompSwizzle : ubyte
194 {
195     identity,
196     zero, one,
197     r, g, b, a,
198 }
199 
200 struct Swizzle
201 {
202     private CompSwizzle[4] rep;
203 
204     this(in CompSwizzle r, in CompSwizzle g, in CompSwizzle b, in CompSwizzle a)
205     {
206         rep = [r, g, b, a];
207     }
208 
209     static @property Swizzle identity() {
210         return Swizzle(
211             CompSwizzle.identity, CompSwizzle.identity,
212             CompSwizzle.identity, CompSwizzle.identity
213         );
214     }
215 
216     static @property Swizzle one() {
217         return Swizzle(
218             CompSwizzle.one, CompSwizzle.one,
219             CompSwizzle.one, CompSwizzle.one
220         );
221     }
222 
223     static @property Swizzle zero() {
224         return Swizzle(
225             CompSwizzle.zero, CompSwizzle.zero,
226             CompSwizzle.zero, CompSwizzle.zero
227         );
228     }
229 
230     static @property Swizzle opDispatch(string name)() {
231         bool isSwizzleIdent() {
232             foreach (char c; name) {
233                 switch (c) {
234                 case 'r': break;
235                 case 'g': break;
236                 case 'b': break;
237                 case 'a': break;
238                 case 'i': break;
239                 case 'o': break;
240                 case 'z': break;
241                 default: return false;
242                 }
243             }
244             return true;
245         }
246         CompSwizzle getComp(char c) {
247             switch (c) {
248             case 'r': return CompSwizzle.r;
249             case 'g': return CompSwizzle.g;
250             case 'b': return CompSwizzle.b;
251             case 'a': return CompSwizzle.a;
252             case 'i': return CompSwizzle.identity;
253             case 'o': return CompSwizzle.one;
254             case 'z': return CompSwizzle.zero;
255             default: assert(false);
256             }
257         }
258 
259         static assert(name.length == 4, "Error: Swizzle."~name~". Swizzle identifier must have four components.");
260         static assert(isSwizzleIdent(), "Wrong swizzle identifier: Swizzle."~name);
261         return Swizzle(
262             getComp(name[0]), getComp(name[1]), getComp(name[2]), getComp(name[3])
263         );
264     }
265 
266     size_t opDollar() const { return 4; }
267     CompSwizzle opIndex(size_t ind) const { return rep[ind]; }
268     const(CompSwizzle)[] opIndex() const return { return rep[]; }
269     size_t[2] opSlice(size_t dim)(size_t start, size_t end) const {
270         return [start, end];
271     }
272     const(CompSwizzle)[] opIndex(size_t[2] slice) const return {
273         return rep[slice[0] .. slice[1]];
274     }
275 }
276 
277 ///
278 unittest {
279     assert(!__traits(compiles, Swizzle.rrr));
280     assert(!__traits(compiles, Swizzle.qwer));
281 
282     assert(Swizzle.rgba == Swizzle(CompSwizzle.r, CompSwizzle.g, CompSwizzle.b, CompSwizzle.a));
283     assert(Swizzle.rrbb == Swizzle(CompSwizzle.r, CompSwizzle.r, CompSwizzle.b, CompSwizzle.b));
284     assert(Swizzle.aaag == Swizzle(CompSwizzle.a, CompSwizzle.a, CompSwizzle.a, CompSwizzle.g));
285     assert(Swizzle.iiii == Swizzle.identity);
286     assert(Swizzle.oooo == Swizzle.one);
287     assert(Swizzle.zzzz == Swizzle.zero);
288 }
289 
290 interface ImageBase
291 {
292     @property ImageInfo info();
293 
294     // TODO: deduce view type from subrange and image type
295     ImageView createView(ImageType viewtype, ImageSubresourceRange isr, Swizzle swizzle);
296 }
297 
298 interface Image : ImageBase, IAtomicRefCounted
299 {
300     import gfx.graal.device : Device;
301 
302     /// Get the parent device
303     @property Device device();
304 
305     @property MemoryRequirements memoryRequirements();
306     /// The image keeps a reference of the device memory
307     void bindMemory(DeviceMemory mem, in size_t offset);
308     /// The memory bound to this image
309     @property DeviceMemory boundMemory();
310 }
311 
312 interface ImageView : IAtomicRefCounted
313 {
314     @property ImageBase image();
315     @property ImageSubresourceRange subresourceRange();
316     @property Swizzle swizzle();
317 
318     /// Build a descriptor to this image resource
319     final ImageDescriptor descriptor(ImageLayout layout)
320     {
321         return ImageDescriptor(this, layout);
322     }
323 
324     /// Build a descriptor to this image resource combined with a sampler
325     final ImageSamplerDescriptor descriptorWithSampler(ImageLayout layout, Sampler sampler)
326     {
327         return ImageSamplerDescriptor(this, layout, sampler);
328     }
329 }
330 
331 /// Type of filter for texture sampling
332 enum Filter {
333     /// nearest sample is used
334     nearest,
335     /// sample is interpolated with neighboors
336     linear,
337 }
338 
339 /// Specifies how texture coordinates outside the range `[0, 1]` are handled.
340 enum WrapMode {
341     /// Repeat the texture. That is, sample the coordinate modulo `1.0`.
342     repeat,
343     /// Mirror the texture. Like tile, but uses abs(coord) before the modulo.
344     mirrorRepeat,
345     /// Clamp the texture to the value at `0.0` or `1.0` respectively.
346     clamp,
347     /// Use border color.
348     border,
349 }
350 
351 enum BorderColor
352 {
353     floatTransparent    = 0,
354     intTransparent      = 1,
355     floatBlack          = 2,
356     intBlack            = 3,
357     floatWhite          = 4,
358     intWhite            = 5,
359 }
360 
361 @property bool isFloat(in BorderColor color) pure {
362     return (cast(int)color & 0x01) == 0;
363 }
364 @property bool isInt(in BorderColor color) pure {
365     return (cast(int)color & 0x01) == 1;
366 }
367 @property bool isTransparent(in BorderColor color) pure {
368     return (cast(int)color & 0x06) == 0;
369 }
370 @property bool isBlack(in BorderColor color) pure {
371     return (cast(int)color & 0x06) == 2;
372 }
373 @property bool isWhite(in BorderColor color) pure {
374     return (cast(int)color & 0x06) == 4;
375 }
376 
377 /// Structure holding texture sampler information.
378 /// It is used to create samplers.
379 struct SamplerInfo {
380     /// The minification filter
381     Filter minFilter;
382     /// The magnification filter
383     Filter magFilter;
384     /// The filter used between mipmap levels
385     Filter mipmapFilter;
386     /// The wrap mode for look-ups outside of [ 0 , 1 ] in the three axis
387     WrapMode[3] wrapMode;
388     /// Enables anisotropy filtering. The value set serves as maximum covering fragments.
389     Option!float anisotropy;
390     float lodBias = 0f;
391     float[2] lodRange = [ 0f, 0f ];
392     /// Enables a comparison operation during lookup of depth/stencil based textures.
393     /// Mostly useful for shadow maps.
394     Option!CompareOp compare;
395     BorderColor borderColor;
396     /// If set, lookup is done in texel space rather than normalized coordinates.
397     Flag!"unnormalizeCoords" unnormalizeCoords;
398 
399     /// Initializes a non-filtering SamplerInfo
400     static @property SamplerInfo nearest() {
401         return SamplerInfo.init;
402     }
403 
404     /// Initializes a bilinear filtering SamplerInfo
405     static @property SamplerInfo bilinear() {
406         SamplerInfo si;
407         si.minFilter = Filter.linear;
408         si.magFilter = Filter.linear;
409         return si;
410     }
411 
412     /// Initializes a trilinear filtering SamplerInfo (that is also linear between mipmap levels)
413     static @property SamplerInfo trilinear() {
414         SamplerInfo si;
415         si.minFilter = Filter.linear;
416         si.magFilter = Filter.linear;
417         si.mipmapFilter = Filter.linear;
418         return si;
419     }
420 
421     /// Enables comparison operation for depth/stencil texture look-ups.
422     /// Use this with shadow samplers.
423     SamplerInfo withCompareOp(in CompareOp op) const {
424         SamplerInfo si = this;
425         si.compare = some(op);
426         return si;
427     }
428 
429     /// Set the wrap mode for the 3 axis
430     SamplerInfo withWrapMode(in WrapMode mode) const {
431         SamplerInfo si = this;
432         si.wrapMode = [ mode, mode, mode ];
433         return si;
434     }
435 }
436 
437 interface Sampler : IAtomicRefCounted
438 {
439     import gfx.graal.device : Device;
440 
441     /// Get the parent device
442     @property Device device();
443 
444     /// Get a descriptor to this resource
445     final SamplerDescriptor descriptor()
446     {
447         return SamplerDescriptor(this);
448     }
449 }