OLD | NEW |
1 /* | 1 /* |
2 * Copyright 2016 The WebRTC project authors. All Rights Reserved. | 2 * Copyright 2016 The WebRTC project authors. All Rights Reserved. |
3 * | 3 * |
4 * Use of this source code is governed by a BSD-style license | 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 | 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 | 6 * tree. An additional intellectual property rights grant can be found |
7 * in the file PATENTS. All contributing project authors may | 7 * in the file PATENTS. All contributing project authors may |
8 * be found in the AUTHORS file in the root of the source tree. | 8 * be found in the AUTHORS file in the root of the source tree. |
9 */ | 9 */ |
10 | 10 |
11 #import "RTCShader.h" | 11 #import "RTCShader.h" |
12 | 12 |
13 #include <vector> | 13 #import "RTCI420TextureCache.h" |
14 | |
15 #import "RTCShader+Private.h" | 14 #import "RTCShader+Private.h" |
16 #import "WebRTC/RTCLogging.h" | 15 #import "WebRTC/RTCLogging.h" |
17 #import "WebRTC/RTCVideoFrame.h" | 16 #import "WebRTC/RTCVideoFrame.h" |
18 | 17 |
19 #include "webrtc/base/optional.h" | 18 #include "webrtc/base/optional.h" |
20 | 19 |
21 // |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. | |
25 static const GLsizei kNumTextureSets = 2; | |
26 static const GLsizei kNumTexturesPerSet = 3; | |
27 static const GLsizei kNumTextures = kNumTexturesPerSet * kNumTextureSets; | |
28 | |
29 // Fragment shader converts YUV values from input textures into a final RGB | 20 // 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. | 21 // pixel. The conversion formula is from http://www.fourcc.org/fccyvrgb.php. |
31 static const char kI420FragmentShaderSource[] = | 22 static const char kI420FragmentShaderSource[] = |
32 SHADER_VERSION | 23 SHADER_VERSION |
33 "precision highp float;" | 24 "precision highp float;" |
34 FRAGMENT_SHADER_IN " vec2 v_texcoord;\n" | 25 FRAGMENT_SHADER_IN " vec2 v_texcoord;\n" |
35 "uniform lowp sampler2D s_textureY;\n" | 26 "uniform lowp sampler2D s_textureY;\n" |
36 "uniform lowp sampler2D s_textureU;\n" | 27 "uniform lowp sampler2D s_textureU;\n" |
37 "uniform lowp sampler2D s_textureV;\n" | 28 "uniform lowp sampler2D s_textureV;\n" |
38 FRAGMENT_SHADER_OUT | 29 FRAGMENT_SHADER_OUT |
39 "void main() {\n" | 30 "void main() {\n" |
40 " float y, u, v, r, g, b;\n" | 31 " float y, u, v, r, g, b;\n" |
41 " y = " FRAGMENT_SHADER_TEXTURE "(s_textureY, v_texcoord).r;\n" | 32 " y = " FRAGMENT_SHADER_TEXTURE "(s_textureY, v_texcoord).r;\n" |
42 " u = " FRAGMENT_SHADER_TEXTURE "(s_textureU, v_texcoord).r;\n" | 33 " u = " FRAGMENT_SHADER_TEXTURE "(s_textureU, v_texcoord).r;\n" |
43 " v = " FRAGMENT_SHADER_TEXTURE "(s_textureV, v_texcoord).r;\n" | 34 " v = " FRAGMENT_SHADER_TEXTURE "(s_textureV, v_texcoord).r;\n" |
44 " u = u - 0.5;\n" | 35 " u = u - 0.5;\n" |
45 " v = v - 0.5;\n" | 36 " v = v - 0.5;\n" |
46 " r = y + 1.403 * v;\n" | 37 " r = y + 1.403 * v;\n" |
47 " g = y - 0.344 * u - 0.714 * v;\n" | 38 " g = y - 0.344 * u - 0.714 * v;\n" |
48 " b = y + 1.770 * u;\n" | 39 " b = y + 1.770 * u;\n" |
49 " " FRAGMENT_SHADER_COLOR " = vec4(r, g, b, 1.0);\n" | 40 " " FRAGMENT_SHADER_COLOR " = vec4(r, g, b, 1.0);\n" |
50 " }\n"; | 41 " }\n"; |
51 | 42 |
52 @implementation RTCI420Shader { | 43 @implementation RTCI420Shader { |
53 BOOL _hasUnpackRowLength; | 44 RTCI420TextureCache* textureCache; |
54 GLint _currentTextureSet; | |
55 // Handles for OpenGL constructs. | 45 // Handles for OpenGL constructs. |
56 GLuint _textures[kNumTextures]; | |
57 GLuint _i420Program; | 46 GLuint _i420Program; |
58 GLuint _vertexArray; | 47 GLuint _vertexArray; |
59 GLuint _vertexBuffer; | 48 GLuint _vertexBuffer; |
60 GLint _ySampler; | 49 GLint _ySampler; |
61 GLint _uSampler; | 50 GLint _uSampler; |
62 GLint _vSampler; | 51 GLint _vSampler; |
63 // Store current rotation and only upload new vertex data when rotation | 52 // Store current rotation and only upload new vertex data when rotation |
64 // changes. | 53 // changes. |
65 rtc::Optional<RTCVideoRotation> _currentRotation; | 54 rtc::Optional<RTCVideoRotation> _currentRotation; |
66 // Used to create a non-padded plane for GPU upload when we receive padded | |
67 // frames. | |
68 std::vector<uint8_t> _planeBuffer; | |
69 } | 55 } |
70 | 56 |
71 - (instancetype)initWithContext:(GlContextType *)context { | 57 - (instancetype)initWithContext:(GlContextType *)context { |
72 if (self = [super init]) { | 58 if (self = [super init]) { |
73 #if TARGET_OS_IPHONE | 59 textureCache = [[RTCI420TextureCache alloc] initWithContext:context]; |
74 _hasUnpackRowLength = (context.API == kEAGLRenderingAPIOpenGLES3); | |
75 #else | |
76 _hasUnpackRowLength = YES; | |
77 #endif | |
78 glPixelStorei(GL_UNPACK_ALIGNMENT, 1); | 60 glPixelStorei(GL_UNPACK_ALIGNMENT, 1); |
79 if (![self setupI420Program] || ![self setupTextures] || | 61 if (![self setupI420Program] || |
80 !RTCSetupVerticesForProgram(_i420Program, &_vertexBuffer, &_vertexArray)
) { | 62 !RTCSetupVerticesForProgram(_i420Program, &_vertexBuffer, &_vertexArray)
) { |
81 RTCLog(@"Failed to initialize RTCI420Shader."); | 63 RTCLog(@"Failed to initialize RTCI420Shader."); |
82 self = nil; | 64 self = nil; |
83 } | 65 } |
84 } | 66 } |
85 return self; | 67 return self; |
86 } | 68 } |
87 | 69 |
88 - (void)dealloc { | 70 - (void)dealloc { |
89 glDeleteProgram(_i420Program); | 71 glDeleteProgram(_i420Program); |
90 glDeleteTextures(kNumTextures, _textures); | |
91 glDeleteBuffers(1, &_vertexBuffer); | 72 glDeleteBuffers(1, &_vertexBuffer); |
92 glDeleteVertexArrays(1, &_vertexArray); | 73 glDeleteVertexArrays(1, &_vertexArray); |
93 } | 74 } |
94 | 75 |
95 - (BOOL)setupI420Program { | 76 - (BOOL)setupI420Program { |
96 _i420Program = RTCCreateProgramFromFragmentSource(kI420FragmentShaderSource); | 77 _i420Program = RTCCreateProgramFromFragmentSource(kI420FragmentShaderSource); |
97 if (!_i420Program) { | 78 if (!_i420Program) { |
98 return NO; | 79 return NO; |
99 } | 80 } |
100 _ySampler = glGetUniformLocation(_i420Program, "s_textureY"); | 81 _ySampler = glGetUniformLocation(_i420Program, "s_textureY"); |
101 _uSampler = glGetUniformLocation(_i420Program, "s_textureU"); | 82 _uSampler = glGetUniformLocation(_i420Program, "s_textureU"); |
102 _vSampler = glGetUniformLocation(_i420Program, "s_textureV"); | 83 _vSampler = glGetUniformLocation(_i420Program, "s_textureV"); |
103 | 84 |
104 return (_ySampler >= 0 && _uSampler >= 0 && _vSampler >= 0); | 85 return (_ySampler >= 0 && _uSampler >= 0 && _vSampler >= 0); |
105 } | 86 } |
106 | 87 |
107 - (BOOL)setupTextures { | |
108 glGenTextures(kNumTextures, _textures); | |
109 // Set parameters for each of the textures we created. | |
110 for (GLsizei i = 0; i < kNumTextures; i++) { | |
111 glBindTexture(GL_TEXTURE_2D, _textures[i]); | |
112 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); | |
113 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); | |
114 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); | |
115 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); | |
116 } | |
117 return YES; | |
118 } | |
119 | |
120 - (BOOL)drawFrame:(RTCVideoFrame*)frame { | 88 - (BOOL)drawFrame:(RTCVideoFrame*)frame { |
121 glUseProgram(_i420Program); | 89 glUseProgram(_i420Program); |
122 if (![self updateTextureDataForFrame:frame]) { | 90 |
123 return NO; | 91 [textureCache uploadFrameToTextures:frame]; |
124 } | 92 |
125 #if !TARGET_OS_IPHONE | 93 #if !TARGET_OS_IPHONE |
126 glBindVertexArray(_vertexArray); | 94 glBindVertexArray(_vertexArray); |
127 #endif | 95 #endif |
| 96 |
| 97 glActiveTexture(GL_TEXTURE0); |
| 98 glBindTexture(GL_TEXTURE_2D, textureCache.yTexture); |
| 99 glUniform1i(_ySampler, 0); |
| 100 |
| 101 glActiveTexture(GL_TEXTURE1); |
| 102 glBindTexture(GL_TEXTURE_2D, textureCache.uTexture); |
| 103 glUniform1i(_uSampler, 1); |
| 104 |
| 105 glActiveTexture(GL_TEXTURE2); |
| 106 glBindTexture(GL_TEXTURE_2D, textureCache.vTexture); |
| 107 glUniform1i(_vSampler, 2); |
| 108 |
128 glBindBuffer(GL_ARRAY_BUFFER, _vertexBuffer); | 109 glBindBuffer(GL_ARRAY_BUFFER, _vertexBuffer); |
129 if (!_currentRotation || frame.rotation != *_currentRotation) { | 110 if (!_currentRotation || frame.rotation != *_currentRotation) { |
130 _currentRotation = rtc::Optional<RTCVideoRotation>(frame.rotation); | 111 _currentRotation = rtc::Optional<RTCVideoRotation>(frame.rotation); |
131 RTCSetVertexData(*_currentRotation); | 112 RTCSetVertexData(*_currentRotation); |
132 } | 113 } |
133 glDrawArrays(GL_TRIANGLE_FAN, 0, 4); | 114 glDrawArrays(GL_TRIANGLE_FAN, 0, 4); |
134 | 115 |
135 return YES; | 116 return YES; |
136 } | 117 } |
137 | 118 |
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 const int chromaWidth = (frame.width + 1) / 2; | |
192 const int chromaHeight = (frame.height + 1) / 2; | |
193 if (frame.strideY != frame.width || | |
194 frame.strideU != chromaWidth || | |
195 frame.strideV != chromaWidth) { | |
196 _planeBuffer.resize(frame.width * frame.height); | |
197 } | |
198 | |
199 [self uploadPlane:frame.dataY | |
200 sampler:_ySampler | |
201 offset:textureOffset | |
202 width:frame.width | |
203 height:frame.height | |
204 stride:frame.strideY]; | |
205 | |
206 [self uploadPlane:frame.dataU | |
207 sampler:_uSampler | |
208 offset:textureOffset + 1 | |
209 width:chromaWidth | |
210 height:chromaHeight | |
211 stride:frame.strideU]; | |
212 | |
213 [self uploadPlane:frame.dataV | |
214 sampler:_vSampler | |
215 offset:textureOffset + 2 | |
216 width:chromaWidth | |
217 height:chromaHeight | |
218 stride:frame.strideV]; | |
219 | |
220 _currentTextureSet = (_currentTextureSet + 1) % kNumTextureSets; | |
221 return YES; | |
222 } | |
223 | |
224 @end | 119 @end |
OLD | NEW |