OLD | NEW |
---|---|
(Empty) | |
1 /* | |
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 | |
18 // |kNumTextures| must not exceed 8, which is the limit in OpenGLES2. Two sets | |
19 // of 3 textures are used here, one for each of the Y, U and V planes. Having | |
20 // two sets alleviates CPU blockage in the event that the GPU is asked to render | |
21 // to a texture that is already in use. | |
22 static const GLsizei kNumTextureSets = 2; | |
23 static const GLsizei kNumTextures = 3 * kNumTextureSets; | |
sakal
2016/08/03 10:52:59
I would prefer 3 be a constant like kNumTexturesPe
tkchin_webrtc
2016/08/03 17:53:19
Done.
| |
24 | |
25 // Fragment shader converts YUV values from input textures into a final RGB | |
26 // pixel. The conversion formula is from http://www.fourcc.org/fccyvrgb.php. | |
27 static const char kI420FragmentShaderSource[] = | |
28 SHADER_VERSION | |
29 "precision highp float;" | |
30 FRAGMENT_SHADER_IN " vec2 v_texcoord;\n" | |
31 "uniform lowp sampler2D s_textureY;\n" | |
32 "uniform lowp sampler2D s_textureU;\n" | |
33 "uniform lowp sampler2D s_textureV;\n" | |
34 FRAGMENT_SHADER_OUT | |
35 "void main() {\n" | |
36 " float y, u, v, r, g, b;\n" | |
37 " y = " FRAGMENT_SHADER_TEXTURE "(s_textureY, v_texcoord).r;\n" | |
38 " u = " FRAGMENT_SHADER_TEXTURE "(s_textureU, v_texcoord).r;\n" | |
39 " v = " FRAGMENT_SHADER_TEXTURE "(s_textureV, v_texcoord).r;\n" | |
40 " u = u - 0.5;\n" | |
41 " v = v - 0.5;\n" | |
42 " r = y + 1.403 * v;\n" | |
43 " g = y - 0.344 * u - 0.714 * v;\n" | |
44 " b = y + 1.770 * u;\n" | |
45 " " FRAGMENT_SHADER_COLOR " = vec4(r, g, b, 1.0);\n" | |
46 " }\n"; | |
47 | |
48 @implementation RTCI420Shader { | |
49 BOOL _hasUnpackRowLength; | |
50 GLint _currentTextureSet; | |
51 // Handles for OpenGL constructs. | |
52 GLuint _textures[kNumTextures]; | |
53 GLuint _i420Program; | |
54 GLuint _vertexArray; | |
55 GLuint _vertexBuffer; | |
56 GLint _ySampler; | |
57 GLint _uSampler; | |
58 GLint _vSampler; | |
59 // Used to create a non-padded plane for GPU upload when we receive padded | |
60 // frames. | |
61 std::vector<uint8_t> _planeBuffer; | |
62 } | |
63 | |
64 - (instancetype)initWithContext:(GlContextType *)context { | |
65 if (self = [super init]) { | |
66 #if TARGET_OS_IPHONE | |
67 _hasUnpackRowLength = (context.API == kEAGLRenderingAPIOpenGLES3); | |
68 #else | |
69 _hasUnpackRowLength = YES; | |
70 #endif | |
71 glPixelStorei(GL_UNPACK_ALIGNMENT, 1); | |
72 if (![self setupI420Program] || ![self setupTextures] || | |
73 !RTCSetupVerticesForProgram(_i420Program, &_vertexBuffer, &_vertexArray) ) { | |
74 self = nil; | |
75 } | |
76 } | |
77 return self; | |
78 } | |
79 | |
80 - (void)dealloc { | |
81 if (_i420Program != 0) { | |
sakal
2016/08/03 10:52:59
These if statements are unnecessary. OpenGL silent
tkchin_webrtc
2016/08/03 17:53:19
Done.
| |
82 glDeleteProgram(_i420Program); | |
83 } | |
84 if (_textures[0] != 0) { | |
85 glDeleteTextures(kNumTextures, _textures); | |
86 } | |
87 if (_vertexBuffer != 0) { | |
88 glDeleteBuffers(1, &_vertexBuffer); | |
89 } | |
90 if (_vertexArray != 0) { | |
91 glDeleteVertexArrays(1, &_vertexArray); | |
92 } | |
93 } | |
94 | |
95 - (BOOL)setupI420Program { | |
96 _i420Program = RTCCreateProgramFromFragmentSource(kI420FragmentShaderSource); | |
97 if (!_i420Program) { | |
98 return NO; | |
99 } | |
100 _ySampler = glGetUniformLocation(_i420Program, "s_textureY"); | |
101 _uSampler = glGetUniformLocation(_i420Program, "s_textureU"); | |
102 _vSampler = glGetUniformLocation(_i420Program, "s_textureV"); | |
103 | |
104 return (_ySampler >= 0 && _uSampler >= 0 && _vSampler >= 0); | |
105 } | |
106 | |
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 glActiveTexture(GL_TEXTURE0 + i); | |
sakal
2016/08/03 10:52:59
I don't think this is needed. We can just set up t
tkchin_webrtc
2016/08/03 17:53:19
Done.
| |
112 glBindTexture(GL_TEXTURE_2D, _textures[i]); | |
113 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); | |
114 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); | |
115 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); | |
116 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); | |
117 } | |
118 return YES; | |
119 } | |
120 | |
121 - (BOOL)drawFrame:(RTCVideoFrame*)frame { | |
122 glUseProgram(_i420Program); | |
123 if (![self updateTextureDataForFrame:frame]) { | |
124 return NO; | |
125 } | |
126 #if !TARGET_OS_IPHONE | |
127 glBindVertexArray(_vertexArray); | |
128 #endif | |
129 glBindBuffer(GL_ARRAY_BUFFER, _vertexBuffer); | |
130 glDrawArrays(GL_TRIANGLE_FAN, 0, 4); | |
131 | |
132 return YES; | |
133 } | |
134 | |
135 - (void)uploadPlane:(const uint8_t *)plane | |
136 sampler:(GLint)sampler | |
137 offset:(GLint)offset | |
138 width:(size_t)width | |
139 height:(size_t)height | |
140 stride:(int32_t)stride { | |
141 glActiveTexture(static_cast<GLenum>(GL_TEXTURE0 + offset)); | |
sakal
2016/08/03 10:52:59
Is it not possible to just always use GL_TEXTURE0-
tkchin_webrtc
2016/08/03 17:53:19
See comment at top of file.
| |
142 glBindTexture(GL_TEXTURE_2D, _textures[offset]); | |
143 | |
144 // When setting texture sampler uniforms, the texture index is used not | |
145 // the texture handle. | |
146 glUniform1i(sampler, offset); | |
147 const uint8_t *uploadPlane = plane; | |
148 if ((size_t)stride != width) { | |
149 if (_hasUnpackRowLength) { | |
150 // GLES3 allows us to specify stride. | |
151 glPixelStorei(GL_UNPACK_ROW_LENGTH, stride); | |
152 glTexImage2D(GL_TEXTURE_2D, | |
153 0, | |
154 RTC_PIXEL_FORMAT, | |
155 static_cast<GLsizei>(width), | |
156 static_cast<GLsizei>(height), | |
157 0, | |
158 RTC_PIXEL_FORMAT, | |
159 GL_UNSIGNED_BYTE, | |
160 uploadPlane); | |
161 glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); | |
162 return; | |
163 } else { | |
164 // Make an unpadded copy and upload that instead. Quick profiling showed | |
165 // that this is faster than uploading row by row using glTexSubImage2D. | |
166 uint8_t *unpaddedPlane = _planeBuffer.data(); | |
167 for (size_t y = 0; y < height; ++y) { | |
168 memcpy(unpaddedPlane + y * width, plane + y * stride, width); | |
169 } | |
170 uploadPlane = unpaddedPlane; | |
171 } | |
172 } | |
173 glTexImage2D(GL_TEXTURE_2D, | |
174 0, | |
175 RTC_PIXEL_FORMAT, | |
176 static_cast<GLsizei>(width), | |
177 static_cast<GLsizei>(height), | |
178 0, | |
179 RTC_PIXEL_FORMAT, | |
180 GL_UNSIGNED_BYTE, | |
181 uploadPlane); | |
182 } | |
183 | |
184 - (BOOL)updateTextureDataForFrame:(RTCVideoFrame *)frame { | |
185 GLint textureOffset = _currentTextureSet * 3; | |
186 NSAssert(textureOffset + 3 <= kNumTextures, @"invalid offset"); | |
187 | |
188 if (frame.yPitch != static_cast<int32_t>(frame.width) || | |
189 frame.uPitch != static_cast<int32_t>(frame.chromaWidth) || | |
190 frame.vPitch != static_cast<int32_t>(frame.chromaWidth)) { | |
191 _planeBuffer.resize(frame.width * frame.height); | |
192 } | |
193 | |
194 [self uploadPlane:frame.yPlane | |
195 sampler:_ySampler | |
196 offset:textureOffset | |
197 width:frame.width | |
198 height:frame.height | |
199 stride:frame.yPitch]; | |
200 | |
201 [self uploadPlane:frame.uPlane | |
202 sampler:_uSampler | |
203 offset:textureOffset + 1 | |
204 width:frame.chromaWidth | |
205 height:frame.chromaHeight | |
206 stride:frame.uPitch]; | |
207 | |
208 [self uploadPlane:frame.vPlane | |
209 sampler:_vSampler | |
210 offset:textureOffset + 2 | |
211 width:frame.chromaWidth | |
212 height:frame.chromaHeight | |
213 stride:frame.vPitch]; | |
214 | |
215 _currentTextureSet = (_currentTextureSet + 1) % kNumTextureSets; | |
216 return YES; | |
217 } | |
218 | |
219 @end | |
OLD | NEW |