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, TexelBufferView; 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 TexelBufferView createTexelView(Format format, size_t offset, size_t size) { 158 assert(false, "not implemented"); 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 import std.format : format; 487 488 GLenum glAttachment; 489 switch (isr.aspect) { 490 case ImageAspect.color: 491 glAttachment = GL_COLOR_ATTACHMENT0 + colorNum++; 492 break; 493 case ImageAspect.depth: 494 glAttachment = GL_DEPTH_ATTACHMENT; 495 break; 496 case ImageAspect.stencil: 497 glAttachment = GL_STENCIL_ATTACHMENT; 498 break; 499 case ImageAspect.depthStencil: 500 glAttachment = GL_DEPTH_STENCIL_ATTACHMENT; 501 break; 502 default: 503 assert(false, format("unsupported image aspect: %s", isr.aspect)); 504 } 505 506 final switch(glType) { 507 case GlImgType.tex: 508 if (layers > 1 && isr.layers == 1) { 509 gl.FramebufferTextureLayer( 510 target, glAttachment, name, cast(GLint)isr.firstLevel, cast(GLint)isr.firstLayer 511 ); 512 } 513 else { 514 final switch (type) { 515 case ImageType.d1: 516 case ImageType.d1Array: 517 gl.FramebufferTexture1D( 518 target, glAttachment, GL_TEXTURE_1D, name, cast(GLint)isr.firstLevel 519 ); 520 break; 521 case ImageType.d2: 522 case ImageType.d2Array: 523 gl.FramebufferTexture2D( 524 target, glAttachment, GL_TEXTURE_2D, name, cast(GLint)isr.firstLevel 525 ); 526 break; 527 case ImageType.d3: 528 gl.FramebufferTexture3D( 529 target, glAttachment, GL_TEXTURE_3D, name, 530 cast(GLint)isr.firstLevel, cast(GLint)isr.firstLayer 531 ); 532 break; 533 case ImageType.cube: 534 case ImageType.cubeArray: 535 const layer = GL_TEXTURE_CUBE_MAP_POSITIVE_X+cast(GLenum)isr.firstLayer; 536 gl.FramebufferTexture2D( 537 target, glAttachment, layer, name, cast(GLint)isr.firstLevel 538 ); 539 break; 540 } 541 } 542 break; 543 case GlImgType.renderBuf: 544 gl.FramebufferRenderbuffer(target, glAttachment, GL_RENDERBUFFER, name); 545 break; 546 } 547 } 548 } 549 550 551 552 final class GlSampler : Sampler 553 { 554 import gfx.bindings.opengl.gl : Gl, GLenum, GLint, GLfloat, GLuint; 555 import gfx.core.rc : atomicRcCode, Rc; 556 import gfx.graal.image : BorderColor, isInt, SamplerInfo; 557 import gfx.graal.pipeline : CompareOp; 558 import gfx.gl3 : GlInfo, GlShare; 559 import gfx.gl3.conv : toGl, toGlMag, toGlMin; 560 561 mixin(atomicRcCode); 562 563 private Rc!Device _dev; 564 private Gl gl; 565 private GlInfo glInfo; 566 private SamplerInfo _info; 567 private GLuint _name; 568 569 this(Device dev, GlShare share, in SamplerInfo info) 570 { 571 _dev = dev; 572 gl = share.gl; 573 glInfo = share.info; 574 _info = info; 575 576 if (glInfo.samplerObject) { 577 gl.GenSamplers(1, &_name); 578 579 setupSampler!( 580 (GLenum pname, GLint param) { gl.SamplerParameteri(_name, pname, param); }, 581 (GLenum pname, GLfloat param) { gl.SamplerParameterf(_name, pname, param); }, 582 (GLenum pname, const(GLint)* param) { gl.SamplerParameteriv(_name, pname, param); }, 583 (GLenum pname, const(GLfloat)* param) { gl.SamplerParameterfv(_name, pname, param); }, 584 )(info); 585 } 586 } 587 588 override void dispose() { 589 if (glInfo.samplerObject) { 590 gl.DeleteSamplers(1, &_name); 591 } 592 _dev.unload(); 593 } 594 595 override @property Device device() { 596 return _dev; 597 } 598 599 void bind (GLuint target, GLuint unit) { 600 if (glInfo.samplerObject) { 601 gl.BindSampler(unit, _name); 602 } 603 else { 604 setupSampler!( 605 (GLenum pname, GLint param) { gl.TexParameteri(target, pname, param); }, 606 (GLenum pname, GLfloat param) { gl.TexParameterf(target, pname, param); }, 607 (GLenum pname, const(GLint)* param) { gl.TexParameteriv(target, pname, param); }, 608 (GLenum pname, const(GLfloat)* param) { gl.TexParameterfv(target, pname, param); }, 609 )(_info); 610 } 611 } 612 } 613 614 private void setupSampler(alias fi, alias ff, alias fiv, alias ffv)(in SamplerInfo glInfo) 615 { 616 import gfx.bindings.opengl.gl : GL_TEXTURE_MAX_ANISOTROPY, 617 GL_TEXTURE_MIN_FILTER, 618 GL_TEXTURE_MAG_FILTER, 619 GL_TEXTURE_WRAP_S, 620 GL_TEXTURE_WRAP_T, 621 GL_TEXTURE_WRAP_R, 622 GL_TEXTURE_LOD_BIAS, 623 GL_TEXTURE_MAX_LOD, GL_TEXTURE_MIN_LOD, 624 GL_TEXTURE_BORDER_COLOR, 625 GL_TEXTURE_COMPARE_MODE, 626 GL_TEXTURE_COMPARE_FUNC, 627 GL_COMPARE_REF_TO_TEXTURE, GL_NONE; 628 import gfx.gl3.conv : toGl, toGlMin, toGlMag; 629 import gfx.graal.image : BorderColor, isInt; 630 import gfx.graal.pipeline : CompareOp; 631 632 import std.algorithm : each; 633 glInfo.anisotropy.save.each!(m => ff(GL_TEXTURE_MAX_ANISOTROPY, m)); 634 635 const min = toGlMin(glInfo.minFilter, glInfo.mipmapFilter); 636 const mag = toGlMag(glInfo.magFilter); 637 fi(GL_TEXTURE_MIN_FILTER, min); 638 fi(GL_TEXTURE_MAG_FILTER, mag); 639 640 fi(GL_TEXTURE_WRAP_S, toGl(glInfo.wrapMode[0])); 641 fi(GL_TEXTURE_WRAP_T, toGl(glInfo.wrapMode[1])); 642 fi(GL_TEXTURE_WRAP_R, toGl(glInfo.wrapMode[2])); 643 644 import std.math : isNaN; 645 if (!glInfo.lodBias.isNaN) { 646 ff(GL_TEXTURE_LOD_BIAS, glInfo.lodBias); 647 } 648 if (!glInfo.lodRange[0].isNaN) { 649 ff(GL_TEXTURE_MIN_LOD, glInfo.lodRange[0]); 650 ff(GL_TEXTURE_MAX_LOD, glInfo.lodRange[1]); 651 } 652 653 import gfx.core.typecons : ifNone, ifSome; 654 glInfo.compare.save.ifSome!((CompareOp op) { 655 fi(GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE); 656 fi(GL_TEXTURE_COMPARE_FUNC, toGl(op)); 657 }).ifNone!(() { 658 fi(GL_TEXTURE_COMPARE_MODE, GL_NONE); 659 }); 660 661 if (glInfo.borderColor.isInt) { 662 int[4] color; 663 switch (glInfo.borderColor) { 664 case BorderColor.intTransparent: 665 color = [ 0, 0, 0, 0]; 666 break; 667 case BorderColor.intBlack: 668 color = [ 0, 0, 0, int.max ]; 669 break; 670 case BorderColor.intWhite: 671 color = [ int.max, int.max, int.max, int.max ]; 672 break; 673 default: break; 674 } 675 fiv(GL_TEXTURE_BORDER_COLOR, &color[0]); 676 } 677 else { 678 float[4] color; 679 switch (glInfo.borderColor) { 680 case BorderColor.intTransparent: 681 color = [ 0f, 0f, 0f, 0f]; 682 break; 683 case BorderColor.intBlack: 684 color = [ 0f, 0f, 0f, 1f ]; 685 break; 686 case BorderColor.intWhite: 687 color = [ 1f, 1f, 1f, 1f ]; 688 break; 689 default: break; 690 } 691 ffv(GL_TEXTURE_BORDER_COLOR, &color[0]); 692 } 693 }