blob: 447a0a7e46003eefa160bf43d7f15e0a603bca8f [file] [log] [blame]
tkchin@webrtc.org1732a592014-05-19 23:26:01 +00001/*
2 * libjingle
jlmiller@webrtc.org5f93d0a2015-01-20 21:36:13 +00003 * Copyright 2014 Google Inc.
tkchin@webrtc.org1732a592014-05-19 23:26:01 +00004 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 *
8 * 1. Redistributions of source code must retain the above copyright notice,
9 * this list of conditions and the following disclaimer.
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 */
27
28#if !defined(__has_feature) || !__has_feature(objc_arc)
29#error "This file requires ARC support."
30#endif
31
tkchin@webrtc.orgacca6752014-05-30 22:26:06 +000032#import "RTCOpenGLVideoRenderer.h"
tkchin@webrtc.org1732a592014-05-19 23:26:01 +000033
magjed@webrtc.orgc2008a02015-03-06 16:58:54 +000034#include <string.h>
35
36#include "webrtc/base/scoped_ptr.h"
37
tkchin@webrtc.orgacca6752014-05-30 22:26:06 +000038#if TARGET_OS_IPHONE
tkchin@webrtc.org1732a592014-05-19 23:26:01 +000039#import <OpenGLES/ES2/gl.h>
tkchin@webrtc.orgacca6752014-05-30 22:26:06 +000040#else
41#import <OpenGL/gl3.h>
42#endif
43
tkchin@webrtc.org1732a592014-05-19 23:26:01 +000044#import "RTCI420Frame.h"
45
46// TODO(tkchin): check and log openGL errors. Methods here return BOOLs in
47// anticipation of that happening in the future.
48
tkchin@webrtc.orgacca6752014-05-30 22:26:06 +000049#if TARGET_OS_IPHONE
50#define RTC_PIXEL_FORMAT GL_LUMINANCE
51#define SHADER_VERSION
52#define VERTEX_SHADER_IN "attribute"
53#define VERTEX_SHADER_OUT "varying"
54#define FRAGMENT_SHADER_IN "varying"
55#define FRAGMENT_SHADER_OUT
56#define FRAGMENT_SHADER_COLOR "gl_FragColor"
57#define FRAGMENT_SHADER_TEXTURE "texture2D"
58#else
59#define RTC_PIXEL_FORMAT GL_RED
60#define SHADER_VERSION "#version 150\n"
61#define VERTEX_SHADER_IN "in"
62#define VERTEX_SHADER_OUT "out"
63#define FRAGMENT_SHADER_IN "in"
64#define FRAGMENT_SHADER_OUT "out vec4 fragColor;\n"
65#define FRAGMENT_SHADER_COLOR "fragColor"
66#define FRAGMENT_SHADER_TEXTURE "texture"
67#endif
tkchin@webrtc.org1732a592014-05-19 23:26:01 +000068
magjed@webrtc.orgc2008a02015-03-06 16:58:54 +000069void CopyPlane(uint8_t* dst,
70 int dst_stride,
71 const uint8_t* src,
72 int src_stride,
73 int width,
74 int height) {
75 for (int y = 0; y < height; ++y) {
76 memcpy(dst + y * dst_stride, src + y * src_stride, width);
77 }
78}
79
tkchin@webrtc.org1732a592014-05-19 23:26:01 +000080// Vertex shader doesn't do anything except pass coordinates through.
tkchin@webrtc.orgacca6752014-05-30 22:26:06 +000081static const char kVertexShaderSource[] =
82 SHADER_VERSION
83 VERTEX_SHADER_IN " vec2 position;\n"
84 VERTEX_SHADER_IN " vec2 texcoord;\n"
85 VERTEX_SHADER_OUT " vec2 v_texcoord;\n"
86 "void main() {\n"
87 " gl_Position = vec4(position.x, position.y, 0.0, 1.0);\n"
88 " v_texcoord = texcoord;\n"
89 "}\n";
tkchin@webrtc.org1732a592014-05-19 23:26:01 +000090
91// Fragment shader converts YUV values from input textures into a final RGB
92// pixel. The conversion formula is from http://www.fourcc.org/fccyvrgb.php.
tkchin@webrtc.orgacca6752014-05-30 22:26:06 +000093static const char kFragmentShaderSource[] =
94 SHADER_VERSION
95 "precision highp float;"
96 FRAGMENT_SHADER_IN " vec2 v_texcoord;\n"
97 "uniform lowp sampler2D s_textureY;\n"
98 "uniform lowp sampler2D s_textureU;\n"
99 "uniform lowp sampler2D s_textureV;\n"
100 FRAGMENT_SHADER_OUT
101 "void main() {\n"
102 " float y, u, v, r, g, b;\n"
103 " y = " FRAGMENT_SHADER_TEXTURE "(s_textureY, v_texcoord).r;\n"
104 " u = " FRAGMENT_SHADER_TEXTURE "(s_textureU, v_texcoord).r;\n"
105 " v = " FRAGMENT_SHADER_TEXTURE "(s_textureV, v_texcoord).r;\n"
106 " u = u - 0.5;\n"
107 " v = v - 0.5;\n"
108 " r = y + 1.403 * v;\n"
109 " g = y - 0.344 * u - 0.714 * v;\n"
110 " b = y + 1.770 * u;\n"
111 " " FRAGMENT_SHADER_COLOR " = vec4(r, g, b, 1.0);\n"
112 " }\n";
tkchin@webrtc.org1732a592014-05-19 23:26:01 +0000113
114// Compiles a shader of the given |type| with GLSL source |source| and returns
115// the shader handle or 0 on error.
116GLuint CreateShader(GLenum type, const GLchar* source) {
117 GLuint shader = glCreateShader(type);
118 if (!shader) {
119 return 0;
120 }
121 glShaderSource(shader, 1, &source, NULL);
122 glCompileShader(shader);
123 GLint compileStatus = GL_FALSE;
124 glGetShaderiv(shader, GL_COMPILE_STATUS, &compileStatus);
125 if (compileStatus == GL_FALSE) {
126 glDeleteShader(shader);
127 shader = 0;
128 }
129 return shader;
130}
131
132// Links a shader program with the given vertex and fragment shaders and
133// returns the program handle or 0 on error.
134GLuint CreateProgram(GLuint vertexShader, GLuint fragmentShader) {
135 if (vertexShader == 0 || fragmentShader == 0) {
136 return 0;
137 }
138 GLuint program = glCreateProgram();
139 if (!program) {
140 return 0;
141 }
142 glAttachShader(program, vertexShader);
143 glAttachShader(program, fragmentShader);
144 glLinkProgram(program);
145 GLint linkStatus = GL_FALSE;
146 glGetProgramiv(program, GL_LINK_STATUS, &linkStatus);
147 if (linkStatus == GL_FALSE) {
148 glDeleteProgram(program);
149 program = 0;
150 }
151 return program;
152}
153
154// When modelview and projection matrices are identity (default) the world is
155// contained in the square around origin with unit size 2. Drawing to these
156// coordinates is equivalent to drawing to the entire screen. The texture is
157// stretched over that square using texture coordinates (u, v) that range
158// from (0, 0) to (1, 1) inclusive. Texture coordinates are flipped vertically
159// here because the incoming frame has origin in upper left hand corner but
160// OpenGL expects origin in bottom left corner.
161const GLfloat gVertices[] = {
tkchin@webrtc.orgacca6752014-05-30 22:26:06 +0000162 // X, Y, U, V.
163 -1, -1, 0, 1, // Bottom left.
164 1, -1, 1, 1, // Bottom right.
165 1, 1, 1, 0, // Top right.
166 -1, 1, 0, 0, // Top left.
tkchin@webrtc.org1732a592014-05-19 23:26:01 +0000167};
168
169// |kNumTextures| must not exceed 8, which is the limit in OpenGLES2. Two sets
170// of 3 textures are used here, one for each of the Y, U and V planes. Having
171// two sets alleviates CPU blockage in the event that the GPU is asked to render
172// to a texture that is already in use.
173static const GLsizei kNumTextureSets = 2;
174static const GLsizei kNumTextures = 3 * kNumTextureSets;
175
tkchin@webrtc.orgacca6752014-05-30 22:26:06 +0000176@implementation RTCOpenGLVideoRenderer {
177#if TARGET_OS_IPHONE
tkchin@webrtc.org1732a592014-05-19 23:26:01 +0000178 EAGLContext* _context;
tkchin@webrtc.orgacca6752014-05-30 22:26:06 +0000179#else
180 NSOpenGLContext* _context;
181#endif
tkchin@webrtc.org1732a592014-05-19 23:26:01 +0000182 BOOL _isInitialized;
183 NSUInteger _currentTextureSet;
184 // Handles for OpenGL constructs.
185 GLuint _textures[kNumTextures];
186 GLuint _program;
tkchin@webrtc.orgacca6752014-05-30 22:26:06 +0000187#if !TARGET_OS_IPHONE
188 GLuint _vertexArray;
189#endif
tkchin@webrtc.org1732a592014-05-19 23:26:01 +0000190 GLuint _vertexBuffer;
191 GLint _position;
192 GLint _texcoord;
193 GLint _ySampler;
194 GLint _uSampler;
195 GLint _vSampler;
196}
197
198+ (void)initialize {
199 // Disable dithering for performance.
200 glDisable(GL_DITHER);
201}
202
tkchin@webrtc.orgacca6752014-05-30 22:26:06 +0000203#if TARGET_OS_IPHONE
tkchin@webrtc.org1732a592014-05-19 23:26:01 +0000204- (instancetype)initWithContext:(EAGLContext*)context {
tkchin@webrtc.orgacca6752014-05-30 22:26:06 +0000205#else
206- (instancetype)initWithContext:(NSOpenGLContext*)context {
207#endif
tkchin@webrtc.org1732a592014-05-19 23:26:01 +0000208 NSAssert(context != nil, @"context cannot be nil");
209 if (self = [super init]) {
210 _context = context;
211 }
212 return self;
213}
214
215- (BOOL)drawFrame:(RTCI420Frame*)frame {
216 if (!_isInitialized) {
217 return NO;
218 }
219 if (_lastDrawnFrame == frame) {
220 return NO;
221 }
222 [self ensureGLContext];
tkchin@webrtc.org1732a592014-05-19 23:26:01 +0000223 glClear(GL_COLOR_BUFFER_BIT);
tkchin@webrtc.org90750482014-09-02 20:50:00 +0000224 if (frame) {
225 if (![self updateTextureSizesForFrame:frame] ||
226 ![self updateTextureDataForFrame:frame]) {
227 return NO;
228 }
tkchin@webrtc.orgacca6752014-05-30 22:26:06 +0000229#if !TARGET_OS_IPHONE
tkchin@webrtc.org90750482014-09-02 20:50:00 +0000230 glBindVertexArray(_vertexArray);
tkchin@webrtc.orgacca6752014-05-30 22:26:06 +0000231#endif
tkchin@webrtc.org90750482014-09-02 20:50:00 +0000232 glBindBuffer(GL_ARRAY_BUFFER, _vertexBuffer);
233 glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
234 }
tkchin@webrtc.orgacca6752014-05-30 22:26:06 +0000235#if !TARGET_OS_IPHONE
236 [_context flushBuffer];
237#endif
tkchin@webrtc.org1732a592014-05-19 23:26:01 +0000238 _lastDrawnFrame = frame;
239 return YES;
240}
241
242- (void)setupGL {
243 if (_isInitialized) {
244 return;
245 }
246 [self ensureGLContext];
247 if (![self setupProgram]) {
248 return;
249 }
250 if (![self setupTextures]) {
251 return;
252 }
253 if (![self setupVertices]) {
254 return;
255 }
256 glUseProgram(_program);
257 glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
tkchin@webrtc.org1732a592014-05-19 23:26:01 +0000258 _isInitialized = YES;
259}
260
261- (void)teardownGL {
262 if (!_isInitialized) {
263 return;
264 }
265 [self ensureGLContext];
266 glDeleteProgram(_program);
267 _program = 0;
268 glDeleteTextures(kNumTextures, _textures);
269 glDeleteBuffers(1, &_vertexBuffer);
270 _vertexBuffer = 0;
tkchin@webrtc.orgacca6752014-05-30 22:26:06 +0000271#if !TARGET_OS_IPHONE
272 glDeleteVertexArrays(1, &_vertexArray);
273#endif
tkchin@webrtc.org1732a592014-05-19 23:26:01 +0000274 _isInitialized = NO;
275}
276
277#pragma mark - Private
278
279- (void)ensureGLContext {
tkchin@webrtc.orgacca6752014-05-30 22:26:06 +0000280 NSAssert(_context, @"context shouldn't be nil");
281#if TARGET_OS_IPHONE
tkchin@webrtc.org1732a592014-05-19 23:26:01 +0000282 if ([EAGLContext currentContext] != _context) {
tkchin@webrtc.org1732a592014-05-19 23:26:01 +0000283 [EAGLContext setCurrentContext:_context];
284 }
tkchin@webrtc.orgacca6752014-05-30 22:26:06 +0000285#else
286 if ([NSOpenGLContext currentContext] != _context) {
287 [_context makeCurrentContext];
288 }
289#endif
tkchin@webrtc.org1732a592014-05-19 23:26:01 +0000290}
291
292- (BOOL)setupProgram {
293 NSAssert(!_program, @"program already set up");
294 GLuint vertexShader = CreateShader(GL_VERTEX_SHADER, kVertexShaderSource);
tkchin@webrtc.orgacca6752014-05-30 22:26:06 +0000295 NSAssert(vertexShader, @"failed to create vertex shader");
tkchin@webrtc.org1732a592014-05-19 23:26:01 +0000296 GLuint fragmentShader =
297 CreateShader(GL_FRAGMENT_SHADER, kFragmentShaderSource);
tkchin@webrtc.orgacca6752014-05-30 22:26:06 +0000298 NSAssert(fragmentShader, @"failed to create fragment shader");
tkchin@webrtc.org1732a592014-05-19 23:26:01 +0000299 _program = CreateProgram(vertexShader, fragmentShader);
300 // Shaders are created only to generate program.
301 if (vertexShader) {
302 glDeleteShader(vertexShader);
303 }
304 if (fragmentShader) {
305 glDeleteShader(fragmentShader);
306 }
307 if (!_program) {
308 return NO;
309 }
310 _position = glGetAttribLocation(_program, "position");
311 _texcoord = glGetAttribLocation(_program, "texcoord");
312 _ySampler = glGetUniformLocation(_program, "s_textureY");
313 _uSampler = glGetUniformLocation(_program, "s_textureU");
314 _vSampler = glGetUniformLocation(_program, "s_textureV");
315 if (_position < 0 || _texcoord < 0 || _ySampler < 0 || _uSampler < 0 ||
316 _vSampler < 0) {
317 return NO;
318 }
319 return YES;
320}
321
322- (BOOL)setupTextures {
323 glGenTextures(kNumTextures, _textures);
324 // Set parameters for each of the textures we created.
325 for (GLsizei i = 0; i < kNumTextures; i++) {
326 glActiveTexture(GL_TEXTURE0 + i);
327 glBindTexture(GL_TEXTURE_2D, _textures[i]);
328 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
329 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
330 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
331 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
332 }
333 return YES;
334}
335
336- (BOOL)updateTextureSizesForFrame:(RTCI420Frame*)frame {
337 if (frame.height == _lastDrawnFrame.height &&
338 frame.width == _lastDrawnFrame.width &&
339 frame.chromaWidth == _lastDrawnFrame.chromaWidth &&
340 frame.chromaHeight == _lastDrawnFrame.chromaHeight) {
341 return YES;
342 }
343 GLsizei lumaWidth = frame.width;
344 GLsizei lumaHeight = frame.height;
345 GLsizei chromaWidth = frame.chromaWidth;
346 GLsizei chromaHeight = frame.chromaHeight;
347 for (GLint i = 0; i < kNumTextureSets; i++) {
348 glActiveTexture(GL_TEXTURE0 + i * 3);
349 glTexImage2D(GL_TEXTURE_2D,
350 0,
tkchin@webrtc.orgacca6752014-05-30 22:26:06 +0000351 RTC_PIXEL_FORMAT,
tkchin@webrtc.org1732a592014-05-19 23:26:01 +0000352 lumaWidth,
353 lumaHeight,
354 0,
tkchin@webrtc.orgacca6752014-05-30 22:26:06 +0000355 RTC_PIXEL_FORMAT,
tkchin@webrtc.org1732a592014-05-19 23:26:01 +0000356 GL_UNSIGNED_BYTE,
357 0);
tkchin@webrtc.org1732a592014-05-19 23:26:01 +0000358 glActiveTexture(GL_TEXTURE0 + i * 3 + 1);
359 glTexImage2D(GL_TEXTURE_2D,
360 0,
tkchin@webrtc.orgacca6752014-05-30 22:26:06 +0000361 RTC_PIXEL_FORMAT,
tkchin@webrtc.org1732a592014-05-19 23:26:01 +0000362 chromaWidth,
363 chromaHeight,
364 0,
tkchin@webrtc.orgacca6752014-05-30 22:26:06 +0000365 RTC_PIXEL_FORMAT,
tkchin@webrtc.org1732a592014-05-19 23:26:01 +0000366 GL_UNSIGNED_BYTE,
367 0);
tkchin@webrtc.org1732a592014-05-19 23:26:01 +0000368 glActiveTexture(GL_TEXTURE0 + i * 3 + 2);
369 glTexImage2D(GL_TEXTURE_2D,
370 0,
tkchin@webrtc.orgacca6752014-05-30 22:26:06 +0000371 RTC_PIXEL_FORMAT,
tkchin@webrtc.org1732a592014-05-19 23:26:01 +0000372 chromaWidth,
373 chromaHeight,
374 0,
tkchin@webrtc.orgacca6752014-05-30 22:26:06 +0000375 RTC_PIXEL_FORMAT,
tkchin@webrtc.org1732a592014-05-19 23:26:01 +0000376 GL_UNSIGNED_BYTE,
377 0);
378 }
379 return YES;
380}
381
382- (BOOL)updateTextureDataForFrame:(RTCI420Frame*)frame {
383 NSUInteger textureOffset = _currentTextureSet * 3;
384 NSAssert(textureOffset + 3 <= kNumTextures, @"invalid offset");
magjed@webrtc.orgc2008a02015-03-06 16:58:54 +0000385
386 // TODO(magjed): Remove this frame copy, BUG=1128.
387 rtc::scoped_ptr<uint8_t[]> tmp;
388 if (frame.yPitch != frame.width || frame.uPitch != frame.chromaWidth ||
389 frame.vPitch != frame.chromaWidth) {
390 tmp.reset(new uint8_t[frame.width * frame.height]);
391 }
392
393 const uint8_t* yPlane = frame.yPlane;
394 if (frame.yPitch != frame.width) {
395 yPlane = tmp.get();
396 CopyPlane(tmp.get(), frame.width, frame.yPlane, frame.yPitch, frame.width,
397 frame.height);
398 }
tkchin@webrtc.org1732a592014-05-19 23:26:01 +0000399
400 glActiveTexture(GL_TEXTURE0 + textureOffset);
401 // When setting texture sampler uniforms, the texture index is used not
402 // the texture handle.
403 glUniform1i(_ySampler, textureOffset);
404 glTexImage2D(GL_TEXTURE_2D,
405 0,
tkchin@webrtc.orgacca6752014-05-30 22:26:06 +0000406 RTC_PIXEL_FORMAT,
tkchin@webrtc.org1732a592014-05-19 23:26:01 +0000407 frame.width,
408 frame.height,
409 0,
tkchin@webrtc.orgacca6752014-05-30 22:26:06 +0000410 RTC_PIXEL_FORMAT,
tkchin@webrtc.org1732a592014-05-19 23:26:01 +0000411 GL_UNSIGNED_BYTE,
magjed@webrtc.orgc2008a02015-03-06 16:58:54 +0000412 yPlane);
413
414 const uint8_t* uPlane = frame.uPlane;
415 if (frame.uPitch != frame.chromaWidth) {
416 uPlane = tmp.get();
417 CopyPlane(tmp.get(), frame.chromaWidth, frame.uPlane, frame.uPitch,
418 frame.chromaWidth, frame.chromaHeight);
419 }
tkchin@webrtc.org1732a592014-05-19 23:26:01 +0000420
421 glActiveTexture(GL_TEXTURE0 + textureOffset + 1);
422 glUniform1i(_uSampler, textureOffset + 1);
423 glTexImage2D(GL_TEXTURE_2D,
424 0,
tkchin@webrtc.orgacca6752014-05-30 22:26:06 +0000425 RTC_PIXEL_FORMAT,
tkchin@webrtc.org1732a592014-05-19 23:26:01 +0000426 frame.chromaWidth,
427 frame.chromaHeight,
428 0,
tkchin@webrtc.orgacca6752014-05-30 22:26:06 +0000429 RTC_PIXEL_FORMAT,
tkchin@webrtc.org1732a592014-05-19 23:26:01 +0000430 GL_UNSIGNED_BYTE,
magjed@webrtc.orgc2008a02015-03-06 16:58:54 +0000431 uPlane);
432
433 const uint8_t* vPlane = frame.vPlane;
434 if (frame.vPitch != frame.chromaWidth) {
435 vPlane = tmp.get();
436 CopyPlane(tmp.get(), frame.chromaWidth, frame.vPlane, frame.vPitch,
437 frame.chromaWidth, frame.chromaHeight);
438 }
tkchin@webrtc.org1732a592014-05-19 23:26:01 +0000439
440 glActiveTexture(GL_TEXTURE0 + textureOffset + 2);
441 glUniform1i(_vSampler, textureOffset + 2);
442 glTexImage2D(GL_TEXTURE_2D,
443 0,
tkchin@webrtc.orgacca6752014-05-30 22:26:06 +0000444 RTC_PIXEL_FORMAT,
tkchin@webrtc.org1732a592014-05-19 23:26:01 +0000445 frame.chromaWidth,
446 frame.chromaHeight,
447 0,
tkchin@webrtc.orgacca6752014-05-30 22:26:06 +0000448 RTC_PIXEL_FORMAT,
tkchin@webrtc.org1732a592014-05-19 23:26:01 +0000449 GL_UNSIGNED_BYTE,
magjed@webrtc.orgc2008a02015-03-06 16:58:54 +0000450 vPlane);
tkchin@webrtc.org1732a592014-05-19 23:26:01 +0000451
452 _currentTextureSet = (_currentTextureSet + 1) % kNumTextureSets;
453 return YES;
454}
455
456- (BOOL)setupVertices {
tkchin@webrtc.orgacca6752014-05-30 22:26:06 +0000457#if !TARGET_OS_IPHONE
458 NSAssert(!_vertexArray, @"vertex array already set up");
459 glGenVertexArrays(1, &_vertexArray);
460 if (!_vertexArray) {
461 return NO;
462 }
463 glBindVertexArray(_vertexArray);
464#endif
tkchin@webrtc.org1732a592014-05-19 23:26:01 +0000465 NSAssert(!_vertexBuffer, @"vertex buffer already set up");
466 glGenBuffers(1, &_vertexBuffer);
467 if (!_vertexBuffer) {
tkchin@webrtc.orgacca6752014-05-30 22:26:06 +0000468#if !TARGET_OS_IPHONE
469 glDeleteVertexArrays(1, &_vertexArray);
470 _vertexArray = 0;
471#endif
tkchin@webrtc.org1732a592014-05-19 23:26:01 +0000472 return NO;
473 }
474 glBindBuffer(GL_ARRAY_BUFFER, _vertexBuffer);
475 glBufferData(GL_ARRAY_BUFFER, sizeof(gVertices), gVertices, GL_DYNAMIC_DRAW);
476
477 // Read position attribute from |gVertices| with size of 2 and stride of 4
478 // beginning at the start of the array. The last argument indicates offset
479 // of data within |gVertices| as supplied to the vertex buffer.
480 glVertexAttribPointer(
481 _position, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(GLfloat), (void*)0);
482 glEnableVertexAttribArray(_position);
483
484 // Read texcoord attribute from |gVertices| with size of 2 and stride of 4
485 // beginning at the first texcoord in the array. The last argument indicates
486 // offset of data within |gVertices| as supplied to the vertex buffer.
487 glVertexAttribPointer(_texcoord,
488 2,
489 GL_FLOAT,
490 GL_FALSE,
491 4 * sizeof(GLfloat),
492 (void*)(2 * sizeof(GLfloat)));
493 glEnableVertexAttribArray(_texcoord);
494
495 return YES;
496}
497
498@end