1 // Copyright 2016 The SwiftShader Authors. All Rights Reserved. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 // Texture.cpp: Implements the Texture class and its derived classes 16 // Texture2D and TextureCubeMap. Implements GL texture objects and related 17 // functionality. [OpenGL ES 2.0.24] section 3.7 page 63. 18 19 #include "Texture.h" 20 21 #include "main.h" 22 #include "mathutil.h" 23 #include "Framebuffer.h" 24 #include "Device.hpp" 25 #include "libEGL/Display.h" 26 #include "common/Surface.hpp" 27 #include "common/debug.h" 28 29 #include <algorithm> 30 31 namespace es1 32 { 33 34 Texture::Texture(GLuint name) : egl::Texture(name) 35 { 36 mMinFilter = GL_NEAREST_MIPMAP_LINEAR; 37 mMagFilter = GL_LINEAR; 38 mWrapS = GL_REPEAT; 39 mWrapT = GL_REPEAT; 40 mMaxAnisotropy = 1.0f; 41 generateMipmap = GL_FALSE; 42 cropRectU = 0; 43 cropRectV = 0; 44 cropRectW = 0; 45 cropRectH = 0; 46 47 resource = new sw::Resource(0); 48 } 49 50 Texture::~Texture() 51 { 52 resource->destruct(); 53 } 54 55 sw::Resource *Texture::getResource() const 56 { 57 return resource; 58 } 59 60 // Returns true on successful filter state update (valid enum parameter) 61 bool Texture::setMinFilter(GLenum filter) 62 { 63 switch(filter) 64 { 65 case GL_NEAREST_MIPMAP_NEAREST: 66 case GL_LINEAR_MIPMAP_NEAREST: 67 case GL_NEAREST_MIPMAP_LINEAR: 68 case GL_LINEAR_MIPMAP_LINEAR: 69 if(getTarget() == GL_TEXTURE_EXTERNAL_OES) 70 { 71 return false; 72 } 73 // Fall through 74 case GL_NEAREST: 75 case GL_LINEAR: 76 mMinFilter = filter; 77 return true; 78 default: 79 return false; 80 } 81 } 82 83 // Returns true on successful filter state update (valid enum parameter) 84 bool Texture::setMagFilter(GLenum filter) 85 { 86 switch(filter) 87 { 88 case GL_NEAREST: 89 case GL_LINEAR: 90 mMagFilter = filter; 91 return true; 92 default: 93 return false; 94 } 95 } 96 97 // Returns true on successful wrap state update (valid enum parameter) 98 bool Texture::setWrapS(GLenum wrap) 99 { 100 switch(wrap) 101 { 102 case GL_REPEAT: 103 case GL_MIRRORED_REPEAT_OES: 104 if(getTarget() == GL_TEXTURE_EXTERNAL_OES) 105 { 106 return false; 107 } 108 // Fall through 109 case GL_CLAMP_TO_EDGE: 110 mWrapS = wrap; 111 return true; 112 default: 113 return false; 114 } 115 } 116 117 // Returns true on successful wrap state update (valid enum parameter) 118 bool Texture::setWrapT(GLenum wrap) 119 { 120 switch(wrap) 121 { 122 case GL_REPEAT: 123 case GL_MIRRORED_REPEAT_OES: 124 if(getTarget() == GL_TEXTURE_EXTERNAL_OES) 125 { 126 return false; 127 } 128 // Fall through 129 case GL_CLAMP_TO_EDGE: 130 mWrapT = wrap; 131 return true; 132 default: 133 return false; 134 } 135 } 136 137 // Returns true on successful max anisotropy update (valid anisotropy value) 138 bool Texture::setMaxAnisotropy(float textureMaxAnisotropy) 139 { 140 textureMaxAnisotropy = std::min(textureMaxAnisotropy, MAX_TEXTURE_MAX_ANISOTROPY); 141 142 if(textureMaxAnisotropy < 1.0f) 143 { 144 return false; 145 } 146 147 if(mMaxAnisotropy != textureMaxAnisotropy) 148 { 149 mMaxAnisotropy = textureMaxAnisotropy; 150 } 151 152 return true; 153 } 154 155 void Texture::setGenerateMipmap(GLboolean enable) 156 { 157 generateMipmap = enable; 158 } 159 160 void Texture::setCropRect(GLint u, GLint v, GLint w, GLint h) 161 { 162 cropRectU = u; 163 cropRectV = v; 164 cropRectW = w; 165 cropRectH = h; 166 } 167 168 GLenum Texture::getMinFilter() const 169 { 170 return mMinFilter; 171 } 172 173 GLenum Texture::getMagFilter() const 174 { 175 return mMagFilter; 176 } 177 178 GLenum Texture::getWrapS() const 179 { 180 return mWrapS; 181 } 182 183 GLenum Texture::getWrapT() const 184 { 185 return mWrapT; 186 } 187 188 GLfloat Texture::getMaxAnisotropy() const 189 { 190 return mMaxAnisotropy; 191 } 192 193 GLboolean Texture::getGenerateMipmap() const 194 { 195 return generateMipmap; 196 } 197 198 GLint Texture::getCropRectU() const 199 { 200 return cropRectU; 201 } 202 203 GLint Texture::getCropRectV() const 204 { 205 return cropRectV; 206 } 207 208 GLint Texture::getCropRectW() const 209 { 210 return cropRectW; 211 } 212 213 GLint Texture::getCropRectH() const 214 { 215 return cropRectH; 216 } 217 218 egl::Image *Texture::createSharedImage(GLenum target, unsigned int level) 219 { 220 egl::Image *image = getRenderTarget(target, level); // Increments reference count 221 222 if(image) 223 { 224 image->markShared(); 225 } 226 227 return image; 228 } 229 230 void Texture::setImage(egl::Context *context, GLenum format, GLenum type, GLint unpackAlignment, const void *pixels, egl::Image *image) 231 { 232 if(pixels && image) 233 { 234 egl::Image::UnpackInfo unpackInfo; 235 unpackInfo.alignment = unpackAlignment; 236 image->loadImageData(context, 0, 0, 0, image->getWidth(), image->getHeight(), 1, format, type, unpackInfo, pixels); 237 } 238 } 239 240 void Texture::setCompressedImage(GLsizei imageSize, const void *pixels, egl::Image *image) 241 { 242 if(pixels && image) 243 { 244 image->loadCompressedData(0, 0, 0, image->getWidth(), image->getHeight(), 1, imageSize, pixels); 245 } 246 } 247 248 void Texture::subImage(egl::Context *context, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, GLint unpackAlignment, const void *pixels, egl::Image *image) 249 { 250 if(!image) 251 { 252 return error(GL_INVALID_OPERATION); 253 } 254 255 if(width + xoffset > image->getWidth() || height + yoffset > image->getHeight()) 256 { 257 return error(GL_INVALID_VALUE); 258 } 259 260 if(IsCompressed(image->getFormat())) 261 { 262 return error(GL_INVALID_OPERATION); 263 } 264 265 if(format != image->getFormat()) 266 { 267 return error(GL_INVALID_OPERATION); 268 } 269 270 if(pixels) 271 { 272 egl::Image::UnpackInfo unpackInfo; 273 unpackInfo.alignment = unpackAlignment; 274 image->loadImageData(context, xoffset, yoffset, 0, width, height, 1, format, type, unpackInfo, pixels); 275 } 276 } 277 278 void Texture::subImageCompressed(GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, const void *pixels, egl::Image *image) 279 { 280 if(!image) 281 { 282 return error(GL_INVALID_OPERATION); 283 } 284 285 if(width + xoffset > image->getWidth() || height + yoffset > image->getHeight()) 286 { 287 return error(GL_INVALID_VALUE); 288 } 289 290 if(format != image->getFormat()) 291 { 292 return error(GL_INVALID_OPERATION); 293 } 294 295 if(pixels) 296 { 297 image->loadCompressedData(xoffset, yoffset, 0, width, height, 1, imageSize, pixels); 298 } 299 } 300 301 bool Texture::copy(egl::Image *source, const sw::Rect &sourceRect, GLenum destFormat, GLint xoffset, GLint yoffset, egl::Image *dest) 302 { 303 Device *device = getDevice(); 304 305 sw::SliceRect destRect(xoffset, yoffset, xoffset + (sourceRect.x1 - sourceRect.x0), yoffset + (sourceRect.y1 - sourceRect.y0), 0); 306 sw::SliceRect sourceSliceRect(sourceRect); 307 bool success = device->stretchRect(source, &sourceSliceRect, dest, &destRect, false); 308 309 if(!success) 310 { 311 return error(GL_OUT_OF_MEMORY, false); 312 } 313 314 return true; 315 } 316 317 bool Texture::isMipmapFiltered() const 318 { 319 switch(mMinFilter) 320 { 321 case GL_NEAREST: 322 case GL_LINEAR: 323 return false; 324 case GL_NEAREST_MIPMAP_NEAREST: 325 case GL_LINEAR_MIPMAP_NEAREST: 326 case GL_NEAREST_MIPMAP_LINEAR: 327 case GL_LINEAR_MIPMAP_LINEAR: 328 return true; 329 default: UNREACHABLE(mMinFilter); 330 } 331 332 return false; 333 } 334 335 Texture2D::Texture2D(GLuint name) : Texture(name) 336 { 337 for(int i = 0; i < IMPLEMENTATION_MAX_TEXTURE_LEVELS; i++) 338 { 339 image[i] = nullptr; 340 } 341 342 mSurface = nullptr; 343 344 mColorbufferProxy = nullptr; 345 mProxyRefs = 0; 346 } 347 348 Texture2D::~Texture2D() 349 { 350 for(int i = 0; i < IMPLEMENTATION_MAX_TEXTURE_LEVELS; i++) 351 { 352 if(image[i]) 353 { 354 image[i]->unbind(this); 355 image[i] = nullptr; 356 } 357 } 358 359 if(mSurface) 360 { 361 mSurface->setBoundTexture(nullptr); 362 mSurface = nullptr; 363 } 364 365 mColorbufferProxy = nullptr; 366 } 367 368 // We need to maintain a count of references to renderbuffers acting as 369 // proxies for this texture, so that we do not attempt to use a pointer 370 // to a renderbuffer proxy which has been deleted. 371 void Texture2D::addProxyRef(const Renderbuffer *proxy) 372 { 373 mProxyRefs++; 374 } 375 376 void Texture2D::releaseProxy(const Renderbuffer *proxy) 377 { 378 if(mProxyRefs > 0) 379 { 380 mProxyRefs--; 381 } 382 383 if(mProxyRefs == 0) 384 { 385 mColorbufferProxy = nullptr; 386 } 387 } 388 389 void Texture2D::sweep() 390 { 391 int imageCount = 0; 392 393 for(int i = 0; i < IMPLEMENTATION_MAX_TEXTURE_LEVELS; i++) 394 { 395 if(image[i] && image[i]->isChildOf(this)) 396 { 397 if(!image[i]->hasSingleReference()) 398 { 399 return; 400 } 401 402 imageCount++; 403 } 404 } 405 406 if(imageCount == referenceCount) 407 { 408 destroy(); 409 } 410 } 411 412 GLenum Texture2D::getTarget() const 413 { 414 return GL_TEXTURE_2D; 415 } 416 417 GLsizei Texture2D::getWidth(GLenum target, GLint level) const 418 { 419 ASSERT(target == GL_TEXTURE_2D); 420 return image[level] ? image[level]->getWidth() : 0; 421 } 422 423 GLsizei Texture2D::getHeight(GLenum target, GLint level) const 424 { 425 ASSERT(target == GL_TEXTURE_2D); 426 return image[level] ? image[level]->getHeight() : 0; 427 } 428 429 GLenum Texture2D::getFormat(GLenum target, GLint level) const 430 { 431 ASSERT(target == GL_TEXTURE_2D); 432 return image[level] ? image[level]->getFormat() : GL_NONE; 433 } 434 435 GLenum Texture2D::getType(GLenum target, GLint level) const 436 { 437 ASSERT(target == GL_TEXTURE_2D); 438 return image[level] ? image[level]->getType() : GL_NONE; 439 } 440 441 sw::Format Texture2D::getInternalFormat(GLenum target, GLint level) const 442 { 443 ASSERT(target == GL_TEXTURE_2D); 444 return image[level] ? image[level]->getInternalFormat() : sw::FORMAT_NULL; 445 } 446 447 int Texture2D::getLevelCount() const 448 { 449 ASSERT(isSamplerComplete()); 450 int levels = 0; 451 452 while(levels < IMPLEMENTATION_MAX_TEXTURE_LEVELS && image[levels]) 453 { 454 levels++; 455 } 456 457 return levels; 458 } 459 460 void Texture2D::setImage(egl::Context *context, GLint level, GLsizei width, GLsizei height, GLenum format, GLenum type, GLint unpackAlignment, const void *pixels) 461 { 462 if(image[level]) 463 { 464 image[level]->release(); 465 } 466 467 image[level] = egl::Image::create(this, width, height, format, type); 468 469 if(!image[level]) 470 { 471 return error(GL_OUT_OF_MEMORY); 472 } 473 474 Texture::setImage(context, format, type, unpackAlignment, pixels, image[level]); 475 } 476 477 void Texture2D::bindTexImage(gl::Surface *surface) 478 { 479 switch(surface->getInternalFormat()) 480 { 481 case sw::FORMAT_A8R8G8B8: 482 case sw::FORMAT_A8B8G8R8: 483 case sw::FORMAT_X8B8G8R8: 484 case sw::FORMAT_X8R8G8B8: 485 break; 486 default: 487 UNIMPLEMENTED(); 488 return; 489 } 490 491 for(int level = 0; level < IMPLEMENTATION_MAX_TEXTURE_LEVELS; level++) 492 { 493 if(image[level]) 494 { 495 image[level]->release(); 496 image[level] = nullptr; 497 } 498 } 499 500 image[0] = surface->getRenderTarget(); 501 502 mSurface = surface; 503 mSurface->setBoundTexture(this); 504 } 505 506 void Texture2D::releaseTexImage() 507 { 508 for(int level = 0; level < IMPLEMENTATION_MAX_TEXTURE_LEVELS; level++) 509 { 510 if(image[level]) 511 { 512 image[level]->release(); 513 image[level] = nullptr; 514 } 515 } 516 } 517 518 void Texture2D::setCompressedImage(GLint level, GLenum format, GLsizei width, GLsizei height, GLsizei imageSize, const void *pixels) 519 { 520 if(image[level]) 521 { 522 image[level]->release(); 523 } 524 525 image[level] = egl::Image::create(this, width, height, format, GL_UNSIGNED_BYTE); 526 527 if(!image[level]) 528 { 529 return error(GL_OUT_OF_MEMORY); 530 } 531 532 Texture::setCompressedImage(imageSize, pixels, image[level]); 533 } 534 535 void Texture2D::subImage(egl::Context *context, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, GLint unpackAlignment, const void *pixels) 536 { 537 Texture::subImage(context, xoffset, yoffset, width, height, format, type, unpackAlignment, pixels, image[level]); 538 } 539 540 void Texture2D::subImageCompressed(GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, const void *pixels) 541 { 542 Texture::subImageCompressed(xoffset, yoffset, width, height, format, imageSize, pixels, image[level]); 543 } 544 545 void Texture2D::copyImage(GLint level, GLenum format, GLint x, GLint y, GLsizei width, GLsizei height, Framebuffer *source) 546 { 547 egl::Image *renderTarget = source->getRenderTarget(); 548 549 if(!renderTarget) 550 { 551 ERR("Failed to retrieve the render target."); 552 return error(GL_OUT_OF_MEMORY); 553 } 554 555 if(image[level]) 556 { 557 image[level]->release(); 558 } 559 560 image[level] = egl::Image::create(this, width, height, format, GL_UNSIGNED_BYTE); 561 562 if(!image[level]) 563 { 564 return error(GL_OUT_OF_MEMORY); 565 } 566 567 if(width != 0 && height != 0) 568 { 569 sw::Rect sourceRect = {x, y, x + width, y + height}; 570 sourceRect.clip(0, 0, source->getColorbuffer()->getWidth(), source->getColorbuffer()->getHeight()); 571 572 copy(renderTarget, sourceRect, format, 0, 0, image[level]); 573 } 574 575 renderTarget->release(); 576 } 577 578 void Texture2D::copySubImage(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height, Framebuffer *source) 579 { 580 if(!image[level]) 581 { 582 return error(GL_INVALID_OPERATION); 583 } 584 585 if(xoffset + width > image[level]->getWidth() || yoffset + height > image[level]->getHeight()) 586 { 587 return error(GL_INVALID_VALUE); 588 } 589 590 egl::Image *renderTarget = source->getRenderTarget(); 591 592 if(!renderTarget) 593 { 594 ERR("Failed to retrieve the render target."); 595 return error(GL_OUT_OF_MEMORY); 596 } 597 598 sw::Rect sourceRect = {x, y, x + width, y + height}; 599 sourceRect.clip(0, 0, source->getColorbuffer()->getWidth(), source->getColorbuffer()->getHeight()); 600 601 copy(renderTarget, sourceRect, image[level]->getFormat(), xoffset, yoffset, image[level]); 602 603 renderTarget->release(); 604 } 605 606 void Texture2D::setSharedImage(egl::Image *sharedImage) 607 { 608 sharedImage->addRef(); 609 610 if(image[0]) 611 { 612 image[0]->release(); 613 } 614 615 image[0] = sharedImage; 616 } 617 618 // Tests for 2D texture sampling completeness. [OpenGL ES 2.0.24] section 3.8.2 page 85. 619 bool Texture2D::isSamplerComplete() const 620 { 621 if(!image[0]) 622 { 623 return false; 624 } 625 626 GLsizei width = image[0]->getWidth(); 627 GLsizei height = image[0]->getHeight(); 628 629 if(width <= 0 || height <= 0) 630 { 631 return false; 632 } 633 634 if(isMipmapFiltered()) 635 { 636 if(!generateMipmap && !isMipmapComplete()) 637 { 638 return false; 639 } 640 } 641 642 return true; 643 } 644 645 // Tests for 2D texture (mipmap) completeness. [OpenGL ES 2.0.24] section 3.7.10 page 81. 646 bool Texture2D::isMipmapComplete() const 647 { 648 GLsizei width = image[0]->getWidth(); 649 GLsizei height = image[0]->getHeight(); 650 651 int q = log2(std::max(width, height)); 652 653 for(int level = 1; level <= q; level++) 654 { 655 if(!image[level]) 656 { 657 return false; 658 } 659 660 if(image[level]->getFormat() != image[0]->getFormat()) 661 { 662 return false; 663 } 664 665 if(image[level]->getType() != image[0]->getType()) 666 { 667 return false; 668 } 669 670 if(image[level]->getWidth() != std::max(1, width >> level)) 671 { 672 return false; 673 } 674 675 if(image[level]->getHeight() != std::max(1, height >> level)) 676 { 677 return false; 678 } 679 } 680 681 return true; 682 } 683 684 bool Texture2D::isCompressed(GLenum target, GLint level) const 685 { 686 return IsCompressed(getFormat(target, level)); 687 } 688 689 bool Texture2D::isDepth(GLenum target, GLint level) const 690 { 691 return IsDepthTexture(getFormat(target, level)); 692 } 693 694 void Texture2D::generateMipmaps() 695 { 696 if(!image[0]) 697 { 698 return; // FIXME: error? 699 } 700 701 unsigned int q = log2(std::max(image[0]->getWidth(), image[0]->getHeight())); 702 703 for(unsigned int i = 1; i <= q; i++) 704 { 705 if(image[i]) 706 { 707 image[i]->release(); 708 } 709 710 image[i] = egl::Image::create(this, std::max(image[0]->getWidth() >> i, 1), std::max(image[0]->getHeight() >> i, 1), image[0]->getFormat(), image[0]->getType()); 711 712 if(!image[i]) 713 { 714 return error(GL_OUT_OF_MEMORY); 715 } 716 717 getDevice()->stretchRect(image[i - 1], 0, image[i], 0, true); 718 } 719 } 720 721 void Texture2D::autoGenerateMipmaps() 722 { 723 if(generateMipmap && image[0]->hasDirtyMipmaps()) 724 { 725 generateMipmaps(); 726 image[0]->cleanMipmaps(); 727 } 728 } 729 730 egl::Image *Texture2D::getImage(unsigned int level) 731 { 732 return image[level]; 733 } 734 735 Renderbuffer *Texture2D::getRenderbuffer(GLenum target) 736 { 737 if(target != GL_TEXTURE_2D) 738 { 739 return error(GL_INVALID_OPERATION, (Renderbuffer*)nullptr); 740 } 741 742 if(!mColorbufferProxy) 743 { 744 mColorbufferProxy = new Renderbuffer(name, new RenderbufferTexture2D(this)); 745 } 746 747 return mColorbufferProxy; 748 } 749 750 egl::Image *Texture2D::getRenderTarget(GLenum target, unsigned int level) 751 { 752 ASSERT(target == GL_TEXTURE_2D); 753 ASSERT(level < IMPLEMENTATION_MAX_TEXTURE_LEVELS); 754 755 if(image[level]) 756 { 757 image[level]->addRef(); 758 } 759 760 return image[level]; 761 } 762 763 bool Texture2D::isShared(GLenum target, unsigned int level) const 764 { 765 ASSERT(target == GL_TEXTURE_2D); 766 ASSERT(level < IMPLEMENTATION_MAX_TEXTURE_LEVELS); 767 768 if(mSurface) // Bound to an EGLSurface 769 { 770 return true; 771 } 772 773 if(!image[level]) 774 { 775 return false; 776 } 777 778 return image[level]->isShared(); 779 } 780 781 TextureExternal::TextureExternal(GLuint name) : Texture2D(name) 782 { 783 mMinFilter = GL_LINEAR; 784 mMagFilter = GL_LINEAR; 785 mWrapS = GL_CLAMP_TO_EDGE; 786 mWrapT = GL_CLAMP_TO_EDGE; 787 } 788 789 TextureExternal::~TextureExternal() 790 { 791 } 792 793 GLenum TextureExternal::getTarget() const 794 { 795 return GL_TEXTURE_EXTERNAL_OES; 796 } 797 798 } 799 800 egl::Image *createBackBuffer(int width, int height, sw::Format format, int multiSampleDepth) 801 { 802 if(width > es1::IMPLEMENTATION_MAX_RENDERBUFFER_SIZE || height > es1::IMPLEMENTATION_MAX_RENDERBUFFER_SIZE) 803 { 804 ERR("Invalid parameters: %dx%d", width, height); 805 return nullptr; 806 } 807 808 return egl::Image::create(width, height, format, multiSampleDepth, false); 809 } 810 811 egl::Image *createDepthStencil(int width, int height, sw::Format format, int multiSampleDepth) 812 { 813 if(width > es1::IMPLEMENTATION_MAX_RENDERBUFFER_SIZE || height > es1::IMPLEMENTATION_MAX_RENDERBUFFER_SIZE) 814 { 815 ERR("Invalid parameters: %dx%d", width, height); 816 return nullptr; 817 } 818 819 bool lockable = true; 820 821 switch(format) 822 { 823 // case sw::FORMAT_D15S1: 824 case sw::FORMAT_D24S8: 825 case sw::FORMAT_D24X8: 826 // case sw::FORMAT_D24X4S4: 827 case sw::FORMAT_D24FS8: 828 case sw::FORMAT_D32: 829 case sw::FORMAT_D16: 830 lockable = false; 831 break; 832 // case sw::FORMAT_S8_LOCKABLE: 833 // case sw::FORMAT_D16_LOCKABLE: 834 case sw::FORMAT_D32F_LOCKABLE: 835 // case sw::FORMAT_D32_LOCKABLE: 836 case sw::FORMAT_DF24S8: 837 case sw::FORMAT_DF16S8: 838 lockable = true; 839 break; 840 default: 841 UNREACHABLE(format); 842 } 843 844 egl::Image *surface = egl::Image::create(width, height, format, multiSampleDepth, lockable); 845 846 if(!surface) 847 { 848 ERR("Out of memory"); 849 return nullptr; 850 } 851 852 return surface; 853 } 854