1 module gfx.gl3.resource;
2 
3 package:
4 
5 import gfx.bindings.opengl.gl : Gl, GLenum, GLuint;
6 import gfx.graal.buffer : Buffer;
7 import gfx.graal.image : Image, ImageView, Sampler, SamplerInfo;
8 import gfx.graal.memory : DeviceMemory;
9 
10 final class GlDeviceMemory : DeviceMemory
11 {
12     import gfx.core.rc : atomicRcCode;
13     import gfx.graal.memory : MemProps;
14 
15     //mixin(atomicRcCode);
16     private size_t _refCount=0;
17     import std.typecons : Flag, Yes;
18 
19     public final shared override @property size_t refCountShared() const
20     {
21         import core.atomic : atomicLoad;
22         return atomicLoad(_refCount);
23     }
24 
25     public final shared override shared(typeof(this)) retainShared()
26     {
27         import core.atomic : atomicOp;
28         immutable rc = atomicOp!"+="(_refCount, 1);
29         debug(rc) {
30             import std.experimental.logger : logf;
31             logf("retain %s: %s -> %s", typeof(this).stringof, rc-1, rc);
32         }
33         return this;
34     }
35 
36     public final shared override shared(typeof(this)) releaseShared(in Flag!"disposeOnZero" disposeOnZero = Yes.disposeOnZero)
37     {
38         import core.atomic : atomicOp;
39         immutable rc = atomicOp!"-="(_refCount, 1);
40 
41         debug(rc) {
42             import std.experimental.logger : logf;
43             logf("release %s: %s -> %s", typeof(this).stringof, rc+1, rc);
44         }
45         if (rc == 0 && disposeOnZero) {
46             debug(rc) {
47                 import std.experimental.logger : logf;
48                 logf("dispose %s", typeof(this).stringof);
49             }
50             synchronized(this) {
51                 // cast shared away
52                 import gfx.core.rc : Disposable;
53                 auto obj = cast(Disposable)this;
54                 obj.dispose();
55             }
56             return null;
57         }
58         else {
59             return this;
60         }
61     }
62 
63     public final shared override shared(typeof(this)) rcLockShared()
64     {
65         import core.atomic : atomicLoad, cas;
66         while (1) {
67             immutable c = atomicLoad(_refCount);
68 
69             if (c == 0) {
70                 debug(rc) {
71                     import std.experimental.logger : logf;
72                     logf("rcLock %s: %s", typeof(this).stringof, c);
73                 }
74                 return null;
75             }
76             if (cas(&_refCount, c, c+1)) {
77                 debug(rc) {
78                     import std.experimental.logger : logf;
79                     logf("rcLock %s: %s", typeof(this).stringof, c+1);
80                 }
81                 return this;
82             }
83         }
84     }
85 
86     private uint _typeIndex;
87     private MemProps _props;
88     private size_t _size;
89     private GlBuffer _buffer;
90 
91 
92     this (in uint typeIndex, in MemProps props, in size_t size) {
93         _typeIndex = typeIndex;
94         _props = props;
95         _size = size;
96     }
97 
98     override void dispose() {
99     }
100 
101     override @property uint typeIndex() {
102         return _typeIndex;
103     }
104     override @property MemProps props() {
105         return _props;
106     }
107     override @property size_t size() {
108         return _size;
109     }
110 
111     override void* mapRaw(in size_t offset, in size_t size) {
112         import std.exception : enforce;
113         enforce(_buffer, "GL backend does not support mapping without bound buffer");
114         return _buffer.map(offset, size);
115     }
116 
117     override void unmapRaw() {
118         import std.exception : enforce;
119         enforce(_buffer, "GL backend does not support mapping without bound buffer");
120         _buffer.unmap();
121     }
122 }
123 
124 
125 final class GlBuffer : Buffer
126 {
127     import gfx.bindings.opengl.gl;
128     import gfx.core.rc : atomicRcCode, Rc;
129     import gfx.gl3 : GlInfo, GlShare;
130     import gfx.graal.buffer : BufferUsage, BufferView;
131     import gfx.graal.format : Format;
132     import gfx.graal.memory : DeviceMemory, MemoryRequirements;
133 
134     mixin(atomicRcCode);
135 
136     private GlInfo glInfo;
137     private Gl gl;
138     private BufferUsage _usage;
139     private size_t _size;
140     private GLuint _name;
141     private GLbitfield _accessFlags;
142     private Rc!GlDeviceMemory _mem;
143 
144     this(GlShare share, in BufferUsage usage, in size_t size) {
145         import gfx.gl3.conv : toGl;
146         gl = share.gl;
147         glInfo = share.info;
148         _usage = usage;
149         _size = size;
150         gl.GenBuffers(1, &_name);
151 
152         import gfx.gl3.error : glCheck;
153         glCheck(gl, "generating buffer");
154     }
155 
156     override void dispose() {
157         gl.DeleteBuffers(1, &_name);
158         _name = 0;
159         _mem.unload();
160     }
161 
162     @property GLuint name() const {
163         return _name;
164     }
165 
166     override @property size_t size() {
167         return _size;
168     }
169     override @property BufferUsage usage() {
170         return _usage;
171     }
172     override @property MemoryRequirements memoryRequirements() {
173         import gfx.graal.memory : MemProps;
174         MemoryRequirements mr;
175         mr.alignment = 4;
176         mr.size = _size;
177         mr.memTypeMask = 1;
178         return mr;
179     }
180 
181     override void bindMemory(DeviceMemory mem, in size_t offset) {
182         import gfx.gl3.error : glCheck;
183         _mem = cast(GlDeviceMemory)mem;
184         _mem._buffer = this;
185 
186         const props = mem.props;
187 
188         gl.BindBuffer(GL_ARRAY_BUFFER, _name);
189 
190         if (glInfo.bufferStorage) {
191             GLbitfield flags = 0;
192             if (props.hostVisible) flags |= (GL_MAP_READ_BIT | GL_MAP_WRITE_BIT);
193             // if (props.hostCoherent) flags |= GL_MAP_COHERENT_BIT;
194             gl.BufferStorage(GL_ARRAY_BUFFER, cast(GLsizeiptr)_size, null, flags);
195             glCheck(gl, "buffer storage");
196         }
197         else {
198             const glUsage = GL_STATIC_DRAW; //?
199             gl.BufferData(GL_ARRAY_BUFFER, cast(GLsizeiptr)_size, null, glUsage);
200             glCheck(gl, "buffer data");
201         }
202 
203 
204         gl.BindBuffer(GL_ARRAY_BUFFER, 0);
205     }
206 
207     override @property DeviceMemory boundMemory() {
208         return _mem.obj;
209     }
210 
211     override BufferView createView(Format format, size_t offset, size_t size) {
212         return null;
213     }
214 
215     private void* map(in size_t offset, in size_t size) {
216         import gfx.gl3.error : glCheck;
217         const props = _mem.props;
218 
219         GLbitfield flags = 0;
220         if (props.hostVisible) flags |= (GL_MAP_READ_BIT | GL_MAP_WRITE_BIT);
221         // if (props.hostCoherent) flags |= GL_MAP_COHERENT_BIT;
222         // else flags |= GL_MAP_FLUSH_EXPLICIT_BIT;
223 
224         gl.BindBuffer(GL_ARRAY_BUFFER, _name);
225         auto ptr = gl.MapBufferRange(GL_ARRAY_BUFFER, cast(GLintptr)offset, cast(GLsizeiptr)size, flags);
226         glCheck(gl, "buffer map");
227         gl.BindBuffer(GL_ARRAY_BUFFER, 0);
228 
229         return ptr;
230     }
231 
232     private void unmap() {
233         import gfx.gl3.error : glCheck;
234         gl.BindBuffer(GL_ARRAY_BUFFER, _name);
235         gl.UnmapBuffer(GL_ARRAY_BUFFER);
236         glCheck(gl, "buffer unmap");
237         gl.BindBuffer(GL_ARRAY_BUFFER, 0);
238     }
239 }
240 
241 enum GlImgType {
242     tex, renderBuf
243 }
244 
245 final class GlImage : Image
246 {
247     import gfx.bindings.opengl.gl : Gl, GLenum, GLuint;
248     import gfx.core.rc : atomicRcCode, Rc;
249     import gfx.graal.cmd : BufferImageCopy;
250     import gfx.graal.format : Format, NumFormat;
251     import gfx.graal.image;
252     import gfx.graal.memory : MemoryRequirements;
253     import gfx.gl3 : GlInfo, GlShare;
254 
255     mixin(atomicRcCode);
256 
257     private ImageInfo _info;
258     private NumFormat _numFormat;
259 
260     private GlImgType _glType;
261     private GLuint _name;
262     private GLenum _glTexTarget;
263     private GLenum _glFormat;
264     private Gl gl;
265     private GlInfo glInfo;
266     private Rc!GlDeviceMemory _mem;
267 
268     this(GlShare share, ImageInfo info)
269     {
270         import gfx.graal.format : formatDesc;
271         import std.exception : enforce;
272         _info = info;
273         _numFormat = formatDesc(info.format).numFormat;
274 
275         gl = share.gl;
276         glInfo = share.info;
277 
278         const notRB = ~(ImageUsage.colorAttachment | ImageUsage.depthStencilAttachment);
279         if ((info.usage & notRB) == ImageUsage.none && info.tiling == ImageTiling.optimal) {
280             _glType = GlImgType.renderBuf;
281             enforce(
282                 _info.type == ImageType.d2,
283                 "Gfx-GL3: ImageUsage indicates the use of a RenderBuffer, which only supports 2D images"
284             );
285             gl.GenRenderbuffers(1, &_name);
286         }
287         else {
288             _glType = GlImgType.tex;
289             gl.GenTextures(1, &_name);
290         }
291 
292         import gfx.gl3.conv : toGlImgFmt, toGlTexTarget;
293         _glFormat = toGlImgFmt(_info.format);
294         _glTexTarget = toGlTexTarget(_info.type, _info.samples > 1);
295     }
296 
297     @property GLuint name() {
298         return _name;
299     }
300 
301     @property GLenum texTarget() {
302         return _glTexTarget;
303     }
304 
305     @property GlImgType glType() {
306         return _glType;
307     }
308 
309     @property NumFormat numFormat() {
310         return _numFormat;
311     }
312 
313     override void dispose() {
314         final switch(_glType) {
315         case GlImgType.tex:
316             gl.DeleteTextures(1, &_name);
317             break;
318         case GlImgType.renderBuf:
319             gl.DeleteRenderbuffers(1, &_name);
320             break;
321         }
322         _mem.unload();
323     }
324 
325     override @property ImageInfo info() {
326         return _info;
327     }
328 
329     override ImageView createView(ImageType viewType, ImageSubresourceRange isr, Swizzle swizzle)
330     {
331         return new GlImageView(this, viewType, isr, swizzle);
332     }
333 
334     override @property MemoryRequirements memoryRequirements() {
335         import gfx.graal.format : formatDesc, totalBits;
336         import gfx.graal.memory : MemProps;
337 
338         const fd = formatDesc(_info.format);
339 
340         MemoryRequirements mr;
341         mr.alignment = 4;
342         mr.size = _info.dims.width * _info.dims.height * _info.dims.depth *
343                   _info.layers * fd.surfaceType.totalBits / 8;
344         mr.memTypeMask = 1;
345         return mr;
346     }
347 
348     void bindMemory(DeviceMemory mem, in size_t offset)
349     {
350         import gfx.bindings.opengl.gl : GL_RENDERBUFFER, GL_TRUE, GLsizei;
351 
352         _mem = cast(GlDeviceMemory)mem; // ignored
353 
354         final switch(_glType) {
355         case GlImgType.tex:
356             gl.BindTexture(_glTexTarget, _name);
357             if (glInfo.textureStorage || (_info.samples > 1 && glInfo.textureStorageMS)) {
358                 final switch (_info.type) {
359                 case ImageType.d1:
360                     gl.TexStorage1D(_glTexTarget, _info.levels, _glFormat, _info.dims.width);
361                     break;
362                 case ImageType.d1Array:
363                     gl.TexStorage2D(_glTexTarget, _info.levels, _glFormat, _info.dims.width, _info.layers);
364                     break;
365                 case ImageType.d2:
366                     if (_info.samples <= 1)
367                         gl.TexStorage2D(_glTexTarget, _info.levels, _glFormat, _info.dims.width, _info.dims.height);
368                     else
369                         gl.TexStorage2DMultisample(
370                             _glTexTarget, _info.samples, _glFormat, _info.dims.width, _info.dims.height, GL_TRUE
371                         );
372                     break;
373                 case ImageType.d2Array:
374                     if (_info.samples <= 1)
375                         gl.TexStorage3D(_glTexTarget, _info.levels, _glFormat, _info.dims.width, _info.dims.height, _info.layers);
376                     else
377                         gl.TexStorage3DMultisample(
378                             _glTexTarget, _info.samples, _glFormat, _info.dims.width, _info.dims.height, _info.layers, GL_TRUE
379                         );
380                     break;
381                 case ImageType.d3:
382                     gl.TexStorage3D(_glTexTarget, _info.levels, _glFormat, _info.dims.width, _info.dims.height, _info.dims.depth);
383                     break;
384                 case ImageType.cube:
385                     gl.TexStorage2D(_glTexTarget, _info.levels, _glFormat, _info.dims.width, _info.dims.height);
386                     break;
387                 case ImageType.cubeArray:
388                     gl.TexStorage3D(_glTexTarget, _info.levels, _glFormat, _info.dims.width, _info.dims.height, _info.layers*6);
389                     break;
390                 }
391             }
392             else {
393 
394                 GLsizei width = _info.dims.width;
395                 GLsizei height = _info.dims.height;
396                 GLsizei depth = _info.dims.depth;
397 
398                 foreach (l; 0.._info.levels) {
399 
400                     final switch (_info.type) {
401                     case ImageType.d1:
402                         gl.TexImage1D(_glTexTarget, l, _glFormat, width, 0, 0, 0, null);
403                         break;
404                     case ImageType.d1Array:
405                         gl.TexImage2D(_glTexTarget, l, _glFormat, width, _info.layers, 0, 0, 0, null);
406                         break;
407                     case ImageType.d2:
408                         if (_info.samples <= 1)
409                             gl.TexImage2D(_glTexTarget, l, _glFormat, width, height, 0, 0, 0, null);
410                         else
411                             gl.TexImage2DMultisample(_glTexTarget, _info.samples, _glFormat, width, height, GL_TRUE);
412                         break;
413                     case ImageType.d2Array:
414                         if (_info.samples <= 1)
415                             gl.TexImage3D(_glTexTarget, l, _glFormat, width, height, _info.layers, 0, 0, 0, null);
416                         else
417                             gl.TexImage3DMultisample(
418                                 _glTexTarget, _info.samples, _glFormat, width, height, _info.layers, GL_TRUE
419                             );
420                         break;
421                     case ImageType.d3:
422                         gl.TexImage3D(_glTexTarget, l, _glFormat, width, height, depth, 0, 0, 0, null);
423                         break;
424                     case ImageType.cube:
425                         gl.TexImage2D(_glTexTarget, l, _glFormat, width, height, 0, 0, 0, null);
426                         break;
427                     case ImageType.cubeArray:
428                         gl.TexImage3D(_glTexTarget, l, _glFormat, width, height, _info.layers*6, 0, 0, 0, null);
429                         break;
430                     }
431 
432                     if (width > 1) width /= 2;
433                     if (height > 1) height /= 2;
434                     if (depth > 1) depth /= 2;
435                 }
436             }
437             gl.BindTexture(_glTexTarget, 0);
438             break;
439 
440         case GlImgType.renderBuf:
441             gl.BindRenderbuffer(GL_RENDERBUFFER, _name);
442             if (_info.samples > 1) {
443                 gl.RenderbufferStorageMultisample(
444                     GL_RENDERBUFFER, _info.samples, _glFormat, _info.dims.width, _info.dims.height
445                 );
446             }
447             else {
448                 gl.RenderbufferStorage(GL_RENDERBUFFER, _glFormat, _info.dims.width, _info.dims.height);
449             }
450             gl.BindRenderbuffer(GL_RENDERBUFFER, 0);
451             break;
452         }
453 
454         import gfx.gl3.error : glCheck;
455         glCheck(gl, "bind image memory");
456     }
457 
458     void texSubImage(BufferImageCopy region) {
459         import gfx.gl3.conv : toSubImgFmt, toSubImgType;
460         gl.TexSubImage2D(
461             _glTexTarget, region.imageLayers.mipLevel, region.offset[0],
462             region.offset[1], region.extent[0], region.extent[1], toSubImgFmt(_info.format),
463             toSubImgType(_info.format), null
464         );
465     }
466 }
467 
468 final class GlImageView : ImageView
469 {
470     import gfx.core.rc : atomicRcCode, Rc;
471     import gfx.gl3 : GlInfo;
472     import gfx.graal.image : ImageBase, ImageDims, ImageSubresourceRange,
473                              ImageType, Swizzle;
474 
475     //mixin(atomicRcCode);
476     private size_t _refCount=0;
477     import std.typecons : Flag, Yes;
478 
479     public final shared override @property size_t refCountShared() const
480     {
481         import core.atomic : atomicLoad;
482         return atomicLoad(_refCount);
483     }
484 
485     public final shared override shared(typeof(this)) retainShared()
486     {
487         import core.atomic : atomicOp;
488         immutable rc = atomicOp!"+="(_refCount, 1);
489         debug(rc) {
490             import std.experimental.logger : logf;
491             logf("retain %s: %s -> %s", typeof(this).stringof, rc-1, rc);
492         }
493         return this;
494     }
495 
496     public final shared override shared(typeof(this)) releaseShared(in Flag!"disposeOnZero" disposeOnZero = Yes.disposeOnZero)
497     {
498         import core.atomic : atomicOp;
499         immutable rc = atomicOp!"-="(_refCount, 1);
500 
501         debug(rc) {
502             import std.experimental.logger : logf;
503             logf("release %s: %s -> %s", typeof(this).stringof, rc+1, rc);
504         }
505         if (rc == 0 && disposeOnZero) {
506             debug(rc) {
507                 import std.experimental.logger : logf;
508                 logf("dispose %s", typeof(this).stringof);
509             }
510             synchronized(this) {
511                 // cast shared away
512                 import gfx.core.rc : Disposable;
513                 auto obj = cast(Disposable)this;
514                 obj.dispose();
515             }
516             return null;
517         }
518         else {
519             return this;
520         }
521     }
522 
523     public final shared override shared(typeof(this)) rcLockShared()
524     {
525         import core.atomic : atomicLoad, cas;
526         while (1) {
527             immutable c = atomicLoad(_refCount);
528 
529             if (c == 0) {
530                 debug(rc) {
531                     import std.experimental.logger : logf;
532                     logf("rcLock %s: %s", typeof(this).stringof, c);
533                 }
534                 return null;
535             }
536             if (cas(&_refCount, c, c+1)) {
537                 debug(rc) {
538                     import std.experimental.logger : logf;
539                     logf("rcLock %s: %s", typeof(this).stringof, c+1);
540                 }
541                 return this;
542             }
543         }
544     }
545 
546     private Gl gl;
547     private GlInfo glInfo;
548     private Rc!GlImage img;
549     private ImageDims imgDims;
550     private ImageType type;
551     private uint layers;
552     private ImageSubresourceRange isr;
553     private Swizzle swzl;
554 
555     private GlImgType glType;
556     package GLuint name;
557     package GLuint target;
558 
559     this(GlImage img, ImageType type, ImageSubresourceRange isr, Swizzle swizzle) {
560         this.img = img;
561         this.gl = img.gl;
562         this.glInfo = img.glInfo;
563         this.imgDims = img.info.dims;
564         this.type = type;
565         this.layers = img.info.layers;
566         this.isr = isr;
567         this.swzl = swizzle;
568 
569         glType = img._glType;
570         name = img._name;
571         if (glType == GlImgType.tex) {
572             import gfx.gl3.conv : toGlTexTarget;
573             target = toGlTexTarget(type, img._info.samples > 1);
574         }
575     }
576 
577     override void dispose() {
578         img.unload();
579     }
580 
581     override @property ImageBase image() {
582         return img.obj;
583     }
584     override @property ImageSubresourceRange subresourceRange() {
585         return isr;
586     }
587     override @property Swizzle swizzle() {
588         return swzl;
589     }
590 
591     void attachToFbo(GLenum target, ref uint colorNum) {
592         import gfx.bindings.opengl.gl : GLint,
593                 GL_RENDERBUFFER, GL_COLOR_ATTACHMENT0,
594                 GL_DEPTH_ATTACHMENT, GL_STENCIL_ATTACHMENT,
595                 GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_CUBE_MAP_POSITIVE_X,
596                 GL_TEXTURE_1D, GL_TEXTURE_2D, GL_TEXTURE_3D;
597         import gfx.graal.image : ImageAspect;
598 
599         GLenum glAttachment;
600         final switch (isr.aspect) {
601         case ImageAspect.color:
602             glAttachment = GL_COLOR_ATTACHMENT0 + colorNum++;
603             break;
604         case ImageAspect.depth:
605             glAttachment = GL_DEPTH_ATTACHMENT;
606             break;
607         case ImageAspect.stencil:
608             glAttachment = GL_STENCIL_ATTACHMENT;
609             break;
610         case ImageAspect.depthStencil:
611             glAttachment = GL_DEPTH_STENCIL_ATTACHMENT;
612             break;
613         }
614 
615         final switch(glType) {
616         case GlImgType.tex:
617             if (layers > 1 && isr.layers == 1) {
618                 gl.FramebufferTextureLayer(
619                     target, glAttachment, name, cast(GLint)isr.firstLevel, cast(GLint)isr.firstLayer
620                 );
621             }
622             else {
623                 final switch (type) {
624                 case ImageType.d1:
625                 case ImageType.d1Array:
626                     gl.FramebufferTexture1D(
627                         target, glAttachment, GL_TEXTURE_1D, name, cast(GLint)isr.firstLevel
628                     );
629                     break;
630                 case ImageType.d2:
631                 case ImageType.d2Array:
632                     gl.FramebufferTexture2D(
633                         target, glAttachment, GL_TEXTURE_2D, name, cast(GLint)isr.firstLevel
634                     );
635                     break;
636                 case ImageType.d3:
637                     gl.FramebufferTexture3D(
638                         target, glAttachment, GL_TEXTURE_3D, name,
639                         cast(GLint)isr.firstLevel, cast(GLint)isr.firstLayer
640                     );
641                     break;
642                 case ImageType.cube:
643                 case ImageType.cubeArray:
644                     const layer = GL_TEXTURE_CUBE_MAP_POSITIVE_X+cast(GLenum)isr.firstLayer;
645                     gl.FramebufferTexture2D(
646                         target, glAttachment, layer, name, cast(GLint)isr.firstLevel
647                     );
648                     break;
649                 }
650             }
651             break;
652         case GlImgType.renderBuf:
653             gl.FramebufferRenderbuffer(target, glAttachment, GL_RENDERBUFFER, name);
654             break;
655         }
656     }
657 }
658 
659 
660 
661 final class GlSampler : Sampler
662 {
663     import gfx.bindings.opengl.gl : Gl, GLenum, GLint, GLfloat, GLuint;
664     import gfx.core.rc : atomicRcCode;
665     import gfx.graal.image : BorderColor, isInt, SamplerInfo;
666     import gfx.graal.pipeline : CompareOp;
667     import gfx.gl3 : GlInfo, GlShare;
668     import gfx.gl3.conv : toGl, toGlMag, toGlMin;
669 
670     mixin(atomicRcCode);
671 
672     private Gl gl;
673     private GlInfo glInfo;
674     private SamplerInfo _info;
675     private GLuint _name;
676 
677     this(GlShare share, in SamplerInfo info)
678     {
679         gl = share.gl;
680         glInfo = share.info;
681         _info = info;
682 
683         if (glInfo.samplerObject) {
684             gl.GenSamplers(1, &_name);
685 
686             setupSampler!(
687                 (GLenum pname, GLint param) { gl.SamplerParameteri(_name, pname, param); },
688                 (GLenum pname, GLfloat param) { gl.SamplerParameterf(_name, pname, param); },
689                 (GLenum pname, const(GLint)* param) { gl.SamplerParameteriv(_name, pname, param); },
690                 (GLenum pname, const(GLfloat)* param) { gl.SamplerParameterfv(_name, pname, param); },
691             )(info);
692         }
693     }
694 
695     override void dispose() {
696         if (glInfo.samplerObject) {
697             gl.DeleteSamplers(1, &_name);
698         }
699     }
700 
701     void bind (GLuint target, GLuint unit) {
702         if (glInfo.samplerObject) {
703             gl.BindSampler(unit, _name);
704         }
705         else {
706             setupSampler!(
707                 (GLenum pname, GLint param) { gl.TexParameteri(target, pname, param); },
708                 (GLenum pname, GLfloat param) { gl.TexParameterf(target, pname, param); },
709                 (GLenum pname, const(GLint)* param) { gl.TexParameteriv(target, pname, param); },
710                 (GLenum pname, const(GLfloat)* param) { gl.TexParameterfv(target, pname, param); },
711             )(_info);
712         }
713     }
714 }
715 
716 private void setupSampler(alias fi, alias ff, alias fiv, alias ffv)(in SamplerInfo glInfo)
717 {
718     import gfx.bindings.opengl.gl : GL_TEXTURE_MAX_ANISOTROPY,
719                                     GL_TEXTURE_MIN_FILTER,
720                                     GL_TEXTURE_MAG_FILTER,
721                                     GL_TEXTURE_WRAP_S,
722                                     GL_TEXTURE_WRAP_T,
723                                     GL_TEXTURE_WRAP_R,
724                                     GL_TEXTURE_LOD_BIAS,
725                                     GL_TEXTURE_MAX_LOD, GL_TEXTURE_MIN_LOD,
726                                     GL_TEXTURE_BORDER_COLOR,
727                                     GL_TEXTURE_COMPARE_MODE,
728                                     GL_TEXTURE_COMPARE_FUNC,
729                                     GL_COMPARE_REF_TO_TEXTURE, GL_NONE;
730     import gfx.gl3.conv : toGl, toGlMin, toGlMag;
731     import gfx.graal.image : BorderColor, isInt;
732     import gfx.graal.pipeline : CompareOp;
733 
734     import std.algorithm : each;
735     glInfo.anisotropy.save.each!(m => ff(GL_TEXTURE_MAX_ANISOTROPY, m));
736 
737     const min = toGlMin(glInfo.minFilter, glInfo.mipmapFilter);
738     const mag = toGlMag(glInfo.magFilter);
739     fi(GL_TEXTURE_MIN_FILTER, min);
740     fi(GL_TEXTURE_MAG_FILTER, mag);
741 
742     fi(GL_TEXTURE_WRAP_S, toGl(glInfo.wrapMode[0]));
743     fi(GL_TEXTURE_WRAP_T, toGl(glInfo.wrapMode[1]));
744     fi(GL_TEXTURE_WRAP_R, toGl(glInfo.wrapMode[2]));
745 
746     import std.math : isNaN;
747     if (!glInfo.lodBias.isNaN) {
748         ff(GL_TEXTURE_LOD_BIAS, glInfo.lodBias);
749     }
750     if (!glInfo.lodRange[0].isNaN) {
751         ff(GL_TEXTURE_MIN_LOD, glInfo.lodRange[0]);
752         ff(GL_TEXTURE_MAX_LOD, glInfo.lodRange[1]);
753     }
754 
755     import gfx.core.typecons : ifNone, ifSome;
756     glInfo.compare.save.ifSome!((CompareOp op) {
757         fi(GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE);
758         fi(GL_TEXTURE_COMPARE_FUNC, toGl(op));
759     }).ifNone!(() {
760         fi(GL_TEXTURE_COMPARE_MODE, GL_NONE);
761     });
762 
763     if (glInfo.borderColor.isInt) {
764         int[4] color;
765         switch (glInfo.borderColor) {
766         case BorderColor.intTransparent:
767             color = [ 0, 0, 0, 0];
768             break;
769         case BorderColor.intBlack:
770             color = [ 0, 0, 0, int.max ];
771             break;
772         case BorderColor.intWhite:
773             color = [ int.max, int.max, int.max, int.max ];
774             break;
775         default: break;
776         }
777         fiv(GL_TEXTURE_BORDER_COLOR, &color[0]);
778     }
779     else {
780         float[4] color;
781         switch (glInfo.borderColor) {
782         case BorderColor.intTransparent:
783             color = [ 0f, 0f, 0f, 0f];
784             break;
785         case BorderColor.intBlack:
786             color = [ 0f, 0f, 0f, 1f ];
787             break;
788         case BorderColor.intWhite:
789             color = [ 1f, 1f, 1f, 1f ];
790             break;
791         default: break;
792         }
793         ffv(GL_TEXTURE_BORDER_COLOR, &color[0]);
794     }
795 }