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 }