blob: e0c96422690d30c509776e7889a10eccb1c3ff8f [file] [log] [blame]
tkchin04dbb342016-08-08 03:10:07 -07001/*
2 * Copyright 2016 The WebRTC project authors. All Rights Reserved.
3 *
4 * Use of this source code is governed by a BSD-style license
5 * that can be found in the LICENSE file in the root of the source
6 * tree. An additional intellectual property rights grant can be found
7 * in the file PATENTS. All contributing project authors may
8 * be found in the AUTHORS file in the root of the source tree.
9 */
10
11#import "RTCShader.h"
12
13#include <vector>
14
15#import "RTCShader+Private.h"
16#import "WebRTC/RTCVideoFrame.h"
17
magjedfb372f02016-08-10 07:58:29 -070018#include "webrtc/base/optional.h"
19#include "webrtc/common_video/rotation.h"
20
tkchin04dbb342016-08-08 03:10:07 -070021// |kNumTextures| must not exceed 8, which is the limit in OpenGLES2. Two sets
22// of 3 textures are used here, one for each of the Y, U and V planes. Having
23// two sets alleviates CPU blockage in the event that the GPU is asked to render
24// to a texture that is already in use.
25static const GLsizei kNumTextureSets = 2;
26static const GLsizei kNumTexturesPerSet = 3;
27static const GLsizei kNumTextures = kNumTexturesPerSet * kNumTextureSets;
28
29// Fragment shader converts YUV values from input textures into a final RGB
30// pixel. The conversion formula is from http://www.fourcc.org/fccyvrgb.php.
31static const char kI420FragmentShaderSource[] =
32 SHADER_VERSION
33 "precision highp float;"
34 FRAGMENT_SHADER_IN " vec2 v_texcoord;\n"
35 "uniform lowp sampler2D s_textureY;\n"
36 "uniform lowp sampler2D s_textureU;\n"
37 "uniform lowp sampler2D s_textureV;\n"
38 FRAGMENT_SHADER_OUT
39 "void main() {\n"
40 " float y, u, v, r, g, b;\n"
41 " y = " FRAGMENT_SHADER_TEXTURE "(s_textureY, v_texcoord).r;\n"
42 " u = " FRAGMENT_SHADER_TEXTURE "(s_textureU, v_texcoord).r;\n"
43 " v = " FRAGMENT_SHADER_TEXTURE "(s_textureV, v_texcoord).r;\n"
44 " u = u - 0.5;\n"
45 " v = v - 0.5;\n"
46 " r = y + 1.403 * v;\n"
47 " g = y - 0.344 * u - 0.714 * v;\n"
48 " b = y + 1.770 * u;\n"
49 " " FRAGMENT_SHADER_COLOR " = vec4(r, g, b, 1.0);\n"
50 " }\n";
51
52@implementation RTCI420Shader {
53 BOOL _hasUnpackRowLength;
54 GLint _currentTextureSet;
55 // Handles for OpenGL constructs.
56 GLuint _textures[kNumTextures];
57 GLuint _i420Program;
58 GLuint _vertexArray;
59 GLuint _vertexBuffer;
60 GLint _ySampler;
61 GLint _uSampler;
62 GLint _vSampler;
magjedfb372f02016-08-10 07:58:29 -070063 // Store current rotation and only upload new vertex data when rotation
64 // changes.
65 rtc::Optional<webrtc::VideoRotation> _currentRotation;
tkchin04dbb342016-08-08 03:10:07 -070066 // Used to create a non-padded plane for GPU upload when we receive padded
67 // frames.
68 std::vector<uint8_t> _planeBuffer;
69}
70
71- (instancetype)initWithContext:(GlContextType *)context {
72 if (self = [super init]) {
73#if TARGET_OS_IPHONE
74 _hasUnpackRowLength = (context.API == kEAGLRenderingAPIOpenGLES3);
75#else
76 _hasUnpackRowLength = YES;
77#endif
78 glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
79 if (![self setupI420Program] || ![self setupTextures] ||
80 !RTCSetupVerticesForProgram(_i420Program, &_vertexBuffer, &_vertexArray)) {
81 self = nil;
82 }
83 }
84 return self;
85}
86
87- (void)dealloc {
88 glDeleteProgram(_i420Program);
89 glDeleteTextures(kNumTextures, _textures);
90 glDeleteBuffers(1, &_vertexBuffer);
91 glDeleteVertexArrays(1, &_vertexArray);
92}
93
94- (BOOL)setupI420Program {
95 _i420Program = RTCCreateProgramFromFragmentSource(kI420FragmentShaderSource);
96 if (!_i420Program) {
97 return NO;
98 }
99 _ySampler = glGetUniformLocation(_i420Program, "s_textureY");
100 _uSampler = glGetUniformLocation(_i420Program, "s_textureU");
101 _vSampler = glGetUniformLocation(_i420Program, "s_textureV");
102
103 return (_ySampler >= 0 && _uSampler >= 0 && _vSampler >= 0);
104}
105
106- (BOOL)setupTextures {
107 glGenTextures(kNumTextures, _textures);
108 // Set parameters for each of the textures we created.
109 for (GLsizei i = 0; i < kNumTextures; i++) {
110 glBindTexture(GL_TEXTURE_2D, _textures[i]);
111 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
112 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
113 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
114 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
115 }
116 return YES;
117}
118
119- (BOOL)drawFrame:(RTCVideoFrame*)frame {
120 glUseProgram(_i420Program);
121 if (![self updateTextureDataForFrame:frame]) {
122 return NO;
123 }
124#if !TARGET_OS_IPHONE
125 glBindVertexArray(_vertexArray);
126#endif
127 glBindBuffer(GL_ARRAY_BUFFER, _vertexBuffer);
magjedfb372f02016-08-10 07:58:29 -0700128 if (!_currentRotation || frame.rotation != *_currentRotation) {
129 _currentRotation = rtc::Optional<webrtc::VideoRotation>(
130 static_cast<webrtc::VideoRotation>(frame.rotation));
131 RTCSetVertexData(*_currentRotation);
132 }
tkchin04dbb342016-08-08 03:10:07 -0700133 glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
134
135 return YES;
136}
137
138- (void)uploadPlane:(const uint8_t *)plane
139 sampler:(GLint)sampler
140 offset:(GLint)offset
141 width:(size_t)width
142 height:(size_t)height
143 stride:(int32_t)stride {
144 glActiveTexture(static_cast<GLenum>(GL_TEXTURE0 + offset));
145 glBindTexture(GL_TEXTURE_2D, _textures[offset]);
146
147 // When setting texture sampler uniforms, the texture index is used not
148 // the texture handle.
149 glUniform1i(sampler, offset);
150 const uint8_t *uploadPlane = plane;
151 if ((size_t)stride != width) {
152 if (_hasUnpackRowLength) {
153 // GLES3 allows us to specify stride.
154 glPixelStorei(GL_UNPACK_ROW_LENGTH, stride);
155 glTexImage2D(GL_TEXTURE_2D,
156 0,
157 RTC_PIXEL_FORMAT,
158 static_cast<GLsizei>(width),
159 static_cast<GLsizei>(height),
160 0,
161 RTC_PIXEL_FORMAT,
162 GL_UNSIGNED_BYTE,
163 uploadPlane);
164 glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
165 return;
166 } else {
167 // Make an unpadded copy and upload that instead. Quick profiling showed
168 // that this is faster than uploading row by row using glTexSubImage2D.
169 uint8_t *unpaddedPlane = _planeBuffer.data();
170 for (size_t y = 0; y < height; ++y) {
171 memcpy(unpaddedPlane + y * width, plane + y * stride, width);
172 }
173 uploadPlane = unpaddedPlane;
174 }
175 }
176 glTexImage2D(GL_TEXTURE_2D,
177 0,
178 RTC_PIXEL_FORMAT,
179 static_cast<GLsizei>(width),
180 static_cast<GLsizei>(height),
181 0,
182 RTC_PIXEL_FORMAT,
183 GL_UNSIGNED_BYTE,
184 uploadPlane);
185}
186
187- (BOOL)updateTextureDataForFrame:(RTCVideoFrame *)frame {
188 GLint textureOffset = _currentTextureSet * 3;
189 NSAssert(textureOffset + 3 <= kNumTextures, @"invalid offset");
190
191 if (frame.yPitch != static_cast<int32_t>(frame.width) ||
192 frame.uPitch != static_cast<int32_t>(frame.chromaWidth) ||
193 frame.vPitch != static_cast<int32_t>(frame.chromaWidth)) {
194 _planeBuffer.resize(frame.width * frame.height);
195 }
196
197 [self uploadPlane:frame.yPlane
198 sampler:_ySampler
199 offset:textureOffset
200 width:frame.width
201 height:frame.height
202 stride:frame.yPitch];
203
204 [self uploadPlane:frame.uPlane
205 sampler:_uSampler
206 offset:textureOffset + 1
207 width:frame.chromaWidth
208 height:frame.chromaHeight
209 stride:frame.uPitch];
210
211 [self uploadPlane:frame.vPlane
212 sampler:_vSampler
213 offset:textureOffset + 2
214 width:frame.chromaWidth
215 height:frame.chromaHeight
216 stride:frame.vPitch];
217
218 _currentTextureSet = (_currentTextureSet + 1) % kNumTextureSets;
219 return YES;
220}
221
222@end