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