| OLD | NEW | 
|    1 /* |    1 /* | 
|    2  * libjingle |    2  *  Copyright 2015 The WebRTC project authors. All Rights Reserved. | 
|    3  * Copyright 2014 Google Inc. |  | 
|    4  * |    3  * | 
|    5  * Redistribution and use in source and binary forms, with or without |    4  *  Use of this source code is governed by a BSD-style license | 
|    6  * modification, are permitted provided that the following conditions are met: |    5  *  that can be found in the LICENSE file in the root of the source | 
|    7  * |    6  *  tree. An additional intellectual property rights grant can be found | 
|    8  *  1. Redistributions of source code must retain the above copyright notice, |    7  *  in the file PATENTS.  All contributing project authors may | 
|    9  *     this list of conditions and the following disclaimer. |    8  *  be found in the AUTHORS file in the root of the source tree. | 
|   10  *  2. Redistributions in binary form must reproduce the above copyright notice, |  | 
|   11  *     this list of conditions and the following disclaimer in the documentation |  | 
|   12  *     and/or other materials provided with the distribution. |  | 
|   13  *  3. The name of the author may not be used to endorse or promote products |  | 
|   14  *     derived from this software without specific prior written permission. |  | 
|   15  * |  | 
|   16  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED |  | 
|   17  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF |  | 
|   18  * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO |  | 
|   19  * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |  | 
|   20  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |  | 
|   21  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; |  | 
|   22  * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, |  | 
|   23  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR |  | 
|   24  * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF |  | 
|   25  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |  | 
|   26  */ |    9  */ | 
|   27  |   10  | 
|   28 #if !defined(__has_feature) || !__has_feature(objc_arc) |  | 
|   29 #error "This file requires ARC support." |  | 
|   30 #endif |  | 
|   31  |  | 
|   32 #import "RTCOpenGLVideoRenderer.h" |   11 #import "RTCOpenGLVideoRenderer.h" | 
|   33  |   12  | 
|   34 #include <string.h> |   13 #include <string.h> | 
|   35  |   14  | 
|   36 #include "webrtc/base/scoped_ptr.h" |   15 #include "webrtc/base/scoped_ptr.h" | 
|   37  |   16  | 
|   38 #if TARGET_OS_IPHONE |   17 #if TARGET_OS_IPHONE | 
|   39 #import <OpenGLES/ES3/gl.h> |   18 #import <OpenGLES/ES3/gl.h> | 
|   40 #else |   19 #else | 
|   41 #import <OpenGL/gl3.h> |   20 #import <OpenGL/gl3.h> | 
|   42 #endif |   21 #endif | 
|   43  |   22  | 
|   44 #import "RTCI420Frame.h" |   23 #import "RTCVideoFrame.h" | 
|   45  |   24  | 
|   46 // TODO(tkchin): check and log openGL errors. Methods here return BOOLs in |   25 // TODO(tkchin): check and log openGL errors. Methods here return BOOLs in | 
|   47 // anticipation of that happening in the future. |   26 // anticipation of that happening in the future. | 
|   48  |   27  | 
|   49 #if TARGET_OS_IPHONE |   28 #if TARGET_OS_IPHONE | 
|   50 #define RTC_PIXEL_FORMAT GL_LUMINANCE |   29 #define RTC_PIXEL_FORMAT GL_LUMINANCE | 
|   51 #define SHADER_VERSION |   30 #define SHADER_VERSION | 
|   52 #define VERTEX_SHADER_IN "attribute" |   31 #define VERTEX_SHADER_IN "attribute" | 
|   53 #define VERTEX_SHADER_OUT "varying" |   32 #define VERTEX_SHADER_OUT "varying" | 
|   54 #define FRAGMENT_SHADER_IN "varying" |   33 #define FRAGMENT_SHADER_IN "varying" | 
| (...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after  Loading... | 
|   95   "    u = u - 0.5;\n" |   74   "    u = u - 0.5;\n" | 
|   96   "    v = v - 0.5;\n" |   75   "    v = v - 0.5;\n" | 
|   97   "    r = y + 1.403 * v;\n" |   76   "    r = y + 1.403 * v;\n" | 
|   98   "    g = y - 0.344 * u - 0.714 * v;\n" |   77   "    g = y - 0.344 * u - 0.714 * v;\n" | 
|   99   "    b = y + 1.770 * u;\n" |   78   "    b = y + 1.770 * u;\n" | 
|  100   "    " FRAGMENT_SHADER_COLOR " = vec4(r, g, b, 1.0);\n" |   79   "    " FRAGMENT_SHADER_COLOR " = vec4(r, g, b, 1.0);\n" | 
|  101   "  }\n"; |   80   "  }\n"; | 
|  102  |   81  | 
|  103 // Compiles a shader of the given |type| with GLSL source |source| and returns |   82 // Compiles a shader of the given |type| with GLSL source |source| and returns | 
|  104 // the shader handle or 0 on error. |   83 // the shader handle or 0 on error. | 
|  105 GLuint CreateShader(GLenum type, const GLchar* source) { |   84 GLuint CreateShader(GLenum type, const GLchar *source) { | 
|  106   GLuint shader = glCreateShader(type); |   85   GLuint shader = glCreateShader(type); | 
|  107   if (!shader) { |   86   if (!shader) { | 
|  108     return 0; |   87     return 0; | 
|  109   } |   88   } | 
|  110   glShaderSource(shader, 1, &source, NULL); |   89   glShaderSource(shader, 1, &source, NULL); | 
|  111   glCompileShader(shader); |   90   glCompileShader(shader); | 
|  112   GLint compileStatus = GL_FALSE; |   91   GLint compileStatus = GL_FALSE; | 
|  113   glGetShaderiv(shader, GL_COMPILE_STATUS, &compileStatus); |   92   glGetShaderiv(shader, GL_COMPILE_STATUS, &compileStatus); | 
|  114   if (compileStatus == GL_FALSE) { |   93   if (compileStatus == GL_FALSE) { | 
|  115     glDeleteShader(shader); |   94     glDeleteShader(shader); | 
| (...skipping 41 matching lines...) Expand 10 before | Expand all | Expand 10 after  Loading... | 
|  157  |  136  | 
|  158 // |kNumTextures| must not exceed 8, which is the limit in OpenGLES2. Two sets |  137 // |kNumTextures| must not exceed 8, which is the limit in OpenGLES2. Two sets | 
|  159 // of 3 textures are used here, one for each of the Y, U and V planes. Having |  138 // of 3 textures are used here, one for each of the Y, U and V planes. Having | 
|  160 // two sets alleviates CPU blockage in the event that the GPU is asked to render |  139 // two sets alleviates CPU blockage in the event that the GPU is asked to render | 
|  161 // to a texture that is already in use. |  140 // to a texture that is already in use. | 
|  162 static const GLsizei kNumTextureSets = 2; |  141 static const GLsizei kNumTextureSets = 2; | 
|  163 static const GLsizei kNumTextures = 3 * kNumTextureSets; |  142 static const GLsizei kNumTextures = 3 * kNumTextureSets; | 
|  164  |  143  | 
|  165 @implementation RTCOpenGLVideoRenderer { |  144 @implementation RTCOpenGLVideoRenderer { | 
|  166 #if TARGET_OS_IPHONE |  145 #if TARGET_OS_IPHONE | 
|  167   EAGLContext* _context; |  146   EAGLContext *_context; | 
|  168 #else |  147 #else | 
|  169   NSOpenGLContext* _context; |  148   NSOpenGLContext *_context; | 
|  170 #endif |  149 #endif | 
|  171   BOOL _isInitialized; |  150   BOOL _isInitialized; | 
|  172   NSUInteger _currentTextureSet; |  151   NSUInteger _currentTextureSet; | 
|  173   // Handles for OpenGL constructs. |  152   // Handles for OpenGL constructs. | 
|  174   GLuint _textures[kNumTextures]; |  153   GLuint _textures[kNumTextures]; | 
|  175   GLuint _program; |  154   GLuint _program; | 
|  176 #if !TARGET_OS_IPHONE |  155 #if !TARGET_OS_IPHONE | 
|  177   GLuint _vertexArray; |  156   GLuint _vertexArray; | 
|  178 #endif |  157 #endif | 
|  179   GLuint _vertexBuffer; |  158   GLuint _vertexBuffer; | 
|  180   GLint _position; |  159   GLint _position; | 
|  181   GLint _texcoord; |  160   GLint _texcoord; | 
|  182   GLint _ySampler; |  161   GLint _ySampler; | 
|  183   GLint _uSampler; |  162   GLint _uSampler; | 
|  184   GLint _vSampler; |  163   GLint _vSampler; | 
|  185   // Used to create a non-padded plane for GPU upload when we receive padded |  164   // Used to create a non-padded plane for GPU upload when we receive padded | 
|  186   // frames. |  165   // frames. | 
|  187   rtc::scoped_ptr<uint8_t[]> _planeBuffer; |  166   rtc::scoped_ptr<uint8_t[]> _planeBuffer; | 
|  188 } |  167 } | 
|  189  |  168  | 
 |  169 @synthesize lastDrawnFrame = _lastDrawnFrame; | 
 |  170  | 
|  190 + (void)initialize { |  171 + (void)initialize { | 
|  191   // Disable dithering for performance. |  172   // Disable dithering for performance. | 
|  192   glDisable(GL_DITHER); |  173   glDisable(GL_DITHER); | 
|  193 } |  174 } | 
|  194  |  175  | 
|  195 #if TARGET_OS_IPHONE |  176 #if TARGET_OS_IPHONE | 
|  196 - (instancetype)initWithContext:(EAGLContext*)context { |  177 - (instancetype)initWithContext:(EAGLContext *)context { | 
|  197 #else |  178 #else | 
|  198 - (instancetype)initWithContext:(NSOpenGLContext*)context { |  179 - (instancetype)initWithContext:(NSOpenGLContext *)context { | 
|  199 #endif |  180 #endif | 
|  200   NSAssert(context != nil, @"context cannot be nil"); |  181   NSAssert(context != nil, @"context cannot be nil"); | 
|  201   if (self = [super init]) { |  182   if (self = [super init]) { | 
|  202     _context = context; |  183     _context = context; | 
|  203   } |  184   } | 
|  204   return self; |  185   return self; | 
|  205 } |  186 } | 
|  206  |  187  | 
|  207 - (BOOL)drawFrame:(RTCI420Frame*)frame { |  188 - (BOOL)drawFrame:(RTCVideoFrame *)frame { | 
|  208   if (!_isInitialized) { |  189   if (!_isInitialized) { | 
|  209     return NO; |  190     return NO; | 
|  210   } |  191   } | 
|  211   if (_lastDrawnFrame == frame) { |  192   if (_lastDrawnFrame == frame) { | 
|  212     return NO; |  193     return NO; | 
|  213   } |  194   } | 
|  214   [self ensureGLContext]; |  195   [self ensureGLContext]; | 
|  215   glClear(GL_COLOR_BUFFER_BIT); |  196   glClear(GL_COLOR_BUFFER_BIT); | 
|  216   if (frame) { |  197   if (frame) { | 
|  217     if (![self updateTextureSizesForFrame:frame] || |  198     if (![self updateTextureSizesForFrame:frame] || | 
| (...skipping 100 matching lines...) Expand 10 before | Expand all | Expand 10 after  Loading... | 
|  318     glActiveTexture(GL_TEXTURE0 + i); |  299     glActiveTexture(GL_TEXTURE0 + i); | 
|  319     glBindTexture(GL_TEXTURE_2D, _textures[i]); |  300     glBindTexture(GL_TEXTURE_2D, _textures[i]); | 
|  320     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); |  301     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); | 
|  321     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); |  302     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); | 
|  322     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); |  303     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); | 
|  323     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); |  304     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); | 
|  324   } |  305   } | 
|  325   return YES; |  306   return YES; | 
|  326 } |  307 } | 
|  327  |  308  | 
|  328 - (BOOL)updateTextureSizesForFrame:(RTCI420Frame*)frame { |  309 - (BOOL)updateTextureSizesForFrame:(RTCVideoFrame *)frame { | 
|  329   if (frame.height == _lastDrawnFrame.height && |  310   if (frame.height == _lastDrawnFrame.height && | 
|  330       frame.width == _lastDrawnFrame.width && |  311       frame.width == _lastDrawnFrame.width && | 
|  331       frame.chromaWidth == _lastDrawnFrame.chromaWidth && |  312       frame.chromaWidth == _lastDrawnFrame.chromaWidth && | 
|  332       frame.chromaHeight == _lastDrawnFrame.chromaHeight) { |  313       frame.chromaHeight == _lastDrawnFrame.chromaHeight) { | 
|  333     return YES; |  314     return YES; | 
|  334   } |  315   } | 
|  335   GLsizei lumaWidth = frame.width; |  316   GLsizei lumaWidth = frame.width; | 
|  336   GLsizei lumaHeight = frame.height; |  317   GLsizei lumaHeight = frame.height; | 
|  337   GLsizei chromaWidth = frame.chromaWidth; |  318   GLsizei chromaWidth = frame.chromaWidth; | 
|  338   GLsizei chromaHeight = frame.chromaHeight; |  319   GLsizei chromaHeight = frame.chromaHeight; | 
| (...skipping 22 matching lines...) Expand all  Loading... | 
|  361     glTexImage2D(GL_TEXTURE_2D, |  342     glTexImage2D(GL_TEXTURE_2D, | 
|  362                  0, |  343                  0, | 
|  363                  RTC_PIXEL_FORMAT, |  344                  RTC_PIXEL_FORMAT, | 
|  364                  chromaWidth, |  345                  chromaWidth, | 
|  365                  chromaHeight, |  346                  chromaHeight, | 
|  366                  0, |  347                  0, | 
|  367                  RTC_PIXEL_FORMAT, |  348                  RTC_PIXEL_FORMAT, | 
|  368                  GL_UNSIGNED_BYTE, |  349                  GL_UNSIGNED_BYTE, | 
|  369                  0); |  350                  0); | 
|  370   } |  351   } | 
|  371   if (frame.yPitch != frame.width || frame.uPitch != frame.chromaWidth || |  352   if ((NSUInteger)frame.yPitch != frame.width || | 
|  372       frame.vPitch != frame.chromaWidth) { |  353       (NSUInteger)frame.uPitch != frame.chromaWidth || | 
 |  354       (NSUInteger)frame.vPitch != frame.chromaWidth) { | 
|  373     _planeBuffer.reset(new uint8_t[frame.width * frame.height]); |  355     _planeBuffer.reset(new uint8_t[frame.width * frame.height]); | 
|  374   } else { |  356   } else { | 
|  375     _planeBuffer.reset(); |  357     _planeBuffer.reset(); | 
|  376   } |  358   } | 
|  377   return YES; |  359   return YES; | 
|  378 } |  360 } | 
|  379  |  361  | 
|  380 - (void)uploadPlane:(const uint8_t*)plane |  362 - (void)uploadPlane:(const uint8_t *)plane | 
|  381             sampler:(GLint)sampler |  363             sampler:(GLint)sampler | 
|  382              offset:(NSUInteger)offset |  364              offset:(NSUInteger)offset | 
|  383               width:(NSUInteger)width |  365               width:(size_t)width | 
|  384              height:(NSUInteger)height |  366              height:(size_t)height | 
|  385              stride:(NSInteger)stride { |  367              stride:(int32_t)stride { | 
|  386   glActiveTexture(GL_TEXTURE0 + offset); |  368   glActiveTexture(GL_TEXTURE0 + offset); | 
|  387   // When setting texture sampler uniforms, the texture index is used not |  369   // When setting texture sampler uniforms, the texture index is used not | 
|  388   // the texture handle. |  370   // the texture handle. | 
|  389   glUniform1i(sampler, offset); |  371   glUniform1i(sampler, offset); | 
|  390 #if TARGET_OS_IPHONE |  372 #if TARGET_OS_IPHONE | 
|  391   BOOL hasUnpackRowLength = _context.API == kEAGLRenderingAPIOpenGLES3; |  373   BOOL hasUnpackRowLength = _context.API == kEAGLRenderingAPIOpenGLES3; | 
|  392 #else |  374 #else | 
|  393   BOOL hasUnpackRowLength = YES; |  375   BOOL hasUnpackRowLength = YES; | 
|  394 #endif |  376 #endif | 
|  395   const uint8_t* uploadPlane = plane; |  377   const uint8_t *uploadPlane = plane; | 
|  396   if (stride != width) { |  378   if ((size_t)stride != width) { | 
|  397    if (hasUnpackRowLength) { |  379    if (hasUnpackRowLength) { | 
|  398       // GLES3 allows us to specify stride. |  380       // GLES3 allows us to specify stride. | 
|  399       glPixelStorei(GL_UNPACK_ROW_LENGTH, stride); |  381       glPixelStorei(GL_UNPACK_ROW_LENGTH, stride); | 
|  400       glTexImage2D(GL_TEXTURE_2D, |  382       glTexImage2D(GL_TEXTURE_2D, | 
|  401                    0, |  383                    0, | 
|  402                    RTC_PIXEL_FORMAT, |  384                    RTC_PIXEL_FORMAT, | 
|  403                    width, |  385                    width, | 
|  404                    height, |  386                    height, | 
|  405                    0, |  387                    0, | 
|  406                    RTC_PIXEL_FORMAT, |  388                    RTC_PIXEL_FORMAT, | 
|  407                    GL_UNSIGNED_BYTE, |  389                    GL_UNSIGNED_BYTE, | 
|  408                    uploadPlane); |  390                    uploadPlane); | 
|  409       glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); |  391       glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); | 
|  410       return; |  392       return; | 
|  411     } else { |  393     } else { | 
|  412       // Make an unpadded copy and upload that instead. Quick profiling showed |  394       // Make an unpadded copy and upload that instead. Quick profiling showed | 
|  413       // that this is faster than uploading row by row using glTexSubImage2D. |  395       // that this is faster than uploading row by row using glTexSubImage2D. | 
|  414       uint8_t* unpaddedPlane = _planeBuffer.get(); |  396       uint8_t *unpaddedPlane = _planeBuffer.get(); | 
|  415       for (NSUInteger y = 0; y < height; ++y) { |  397       for (size_t y = 0; y < height; ++y) { | 
|  416         memcpy(unpaddedPlane + y * width, plane + y * stride, width); |  398         memcpy(unpaddedPlane + y * width, plane + y * stride, width); | 
|  417       } |  399       } | 
|  418       uploadPlane = unpaddedPlane; |  400       uploadPlane = unpaddedPlane; | 
|  419     } |  401     } | 
|  420   } |  402   } | 
|  421   glTexImage2D(GL_TEXTURE_2D, |  403   glTexImage2D(GL_TEXTURE_2D, | 
|  422                0, |  404                0, | 
|  423                RTC_PIXEL_FORMAT, |  405                RTC_PIXEL_FORMAT, | 
|  424                width, |  406                width, | 
|  425                height, |  407                height, | 
|  426                0, |  408                0, | 
|  427                RTC_PIXEL_FORMAT, |  409                RTC_PIXEL_FORMAT, | 
|  428                GL_UNSIGNED_BYTE, |  410                GL_UNSIGNED_BYTE, | 
|  429                uploadPlane); |  411                uploadPlane); | 
|  430 } |  412 } | 
|  431  |  413  | 
|  432 - (BOOL)updateTextureDataForFrame:(RTCI420Frame*)frame { |  414 - (BOOL)updateTextureDataForFrame:(RTCVideoFrame *)frame { | 
|  433   NSUInteger textureOffset = _currentTextureSet * 3; |  415   NSUInteger textureOffset = _currentTextureSet * 3; | 
|  434   NSAssert(textureOffset + 3 <= kNumTextures, @"invalid offset"); |  416   NSAssert(textureOffset + 3 <= kNumTextures, @"invalid offset"); | 
|  435  |  417  | 
|  436   [self uploadPlane:frame.yPlane |  418   [self uploadPlane:frame.yPlane | 
|  437             sampler:_ySampler |  419             sampler:_ySampler | 
|  438              offset:textureOffset |  420              offset:textureOffset | 
|  439               width:frame.width |  421               width:frame.width | 
|  440              height:frame.height |  422              height:frame.height | 
|  441              stride:frame.yPitch]; |  423              stride:frame.yPitch]; | 
|  442  |  424  | 
| (...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after  Loading... | 
|  476 #endif |  458 #endif | 
|  477     return NO; |  459     return NO; | 
|  478   } |  460   } | 
|  479   glBindBuffer(GL_ARRAY_BUFFER, _vertexBuffer); |  461   glBindBuffer(GL_ARRAY_BUFFER, _vertexBuffer); | 
|  480   glBufferData(GL_ARRAY_BUFFER, sizeof(gVertices), gVertices, GL_DYNAMIC_DRAW); |  462   glBufferData(GL_ARRAY_BUFFER, sizeof(gVertices), gVertices, GL_DYNAMIC_DRAW); | 
|  481  |  463  | 
|  482   // Read position attribute from |gVertices| with size of 2 and stride of 4 |  464   // Read position attribute from |gVertices| with size of 2 and stride of 4 | 
|  483   // beginning at the start of the array. The last argument indicates offset |  465   // beginning at the start of the array. The last argument indicates offset | 
|  484   // of data within |gVertices| as supplied to the vertex buffer. |  466   // of data within |gVertices| as supplied to the vertex buffer. | 
|  485   glVertexAttribPointer( |  467   glVertexAttribPointer( | 
|  486       _position, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(GLfloat), (void*)0); |  468       _position, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(GLfloat), (void *)0); | 
|  487   glEnableVertexAttribArray(_position); |  469   glEnableVertexAttribArray(_position); | 
|  488  |  470  | 
|  489   // Read texcoord attribute from |gVertices| with size of 2 and stride of 4 |  471   // Read texcoord attribute from |gVertices| with size of 2 and stride of 4 | 
|  490   // beginning at the first texcoord in the array. The last argument indicates |  472   // beginning at the first texcoord in the array. The last argument indicates | 
|  491   // offset of data within |gVertices| as supplied to the vertex buffer. |  473   // offset of data within |gVertices| as supplied to the vertex buffer. | 
|  492   glVertexAttribPointer(_texcoord, |  474   glVertexAttribPointer(_texcoord, | 
|  493                         2, |  475                         2, | 
|  494                         GL_FLOAT, |  476                         GL_FLOAT, | 
|  495                         GL_FALSE, |  477                         GL_FALSE, | 
|  496                         4 * sizeof(GLfloat), |  478                         4 * sizeof(GLfloat), | 
|  497                         (void*)(2 * sizeof(GLfloat))); |  479                         (void *)(2 * sizeof(GLfloat))); | 
|  498   glEnableVertexAttribArray(_texcoord); |  480   glEnableVertexAttribArray(_texcoord); | 
|  499  |  481  | 
|  500   return YES; |  482   return YES; | 
|  501 } |  483 } | 
|  502  |  484  | 
|  503 @end |  485 @end | 
| OLD | NEW |