1/*
2 *  Copyright 2017 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 "RTCI420TextureCache.h"
12
13#if TARGET_OS_IPHONE
14#import <OpenGLES/ES3/gl.h>
15#else
16#import <OpenGL/gl3.h>
17#endif
18
19#import "base/RTCI420Buffer.h"
20#import "base/RTCVideoFrameBuffer.h"
21
22#include <vector>
23
24// Two sets of 3 textures are used here, one for each of the Y, U and V planes. Having two sets
25// alleviates CPU blockage in the event that the GPU is asked to render to a texture that is already
26// in use.
27static const GLsizei kNumTextureSets = 2;
28static const GLsizei kNumTexturesPerSet = 3;
29static const GLsizei kNumTextures = kNumTexturesPerSet * kNumTextureSets;
30
31@implementation RTCI420TextureCache {
32  BOOL _hasUnpackRowLength;
33  GLint _currentTextureSet;
34  // Handles for OpenGL constructs.
35  GLuint _textures[kNumTextures];
36  // Used to create a non-padded plane for GPU upload when we receive padded frames.
37  std::vector<uint8_t> _planeBuffer;
38}
39
40- (GLuint)yTexture {
41  return _textures[_currentTextureSet * kNumTexturesPerSet];
42}
43
44- (GLuint)uTexture {
45  return _textures[_currentTextureSet * kNumTexturesPerSet + 1];
46}
47
48- (GLuint)vTexture {
49  return _textures[_currentTextureSet * kNumTexturesPerSet + 2];
50}
51
52- (instancetype)initWithContext:(GlContextType *)context {
53  if (self = [super init]) {
54#if TARGET_OS_IPHONE
55    _hasUnpackRowLength = (context.API == kEAGLRenderingAPIOpenGLES3);
56#else
57    _hasUnpackRowLength = YES;
58#endif
59    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
60
61    [self setupTextures];
62  }
63  return self;
64}
65
66- (void)dealloc {
67  glDeleteTextures(kNumTextures, _textures);
68}
69
70- (void)setupTextures {
71  glGenTextures(kNumTextures, _textures);
72  // Set parameters for each of the textures we created.
73  for (GLsizei i = 0; i < kNumTextures; i++) {
74    glBindTexture(GL_TEXTURE_2D, _textures[i]);
75    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
76    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
77    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
78    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
79  }
80}
81
82- (void)uploadPlane:(const uint8_t *)plane
83            texture:(GLuint)texture
84              width:(size_t)width
85             height:(size_t)height
86             stride:(int32_t)stride {
87  glBindTexture(GL_TEXTURE_2D, texture);
88
89  const uint8_t *uploadPlane = plane;
90  if ((size_t)stride != width) {
91   if (_hasUnpackRowLength) {
92      // GLES3 allows us to specify stride.
93      glPixelStorei(GL_UNPACK_ROW_LENGTH, stride);
94      glTexImage2D(GL_TEXTURE_2D,
95                   0,
96                   RTC_PIXEL_FORMAT,
97                   static_cast<GLsizei>(width),
98                   static_cast<GLsizei>(height),
99                   0,
100                   RTC_PIXEL_FORMAT,
101                   GL_UNSIGNED_BYTE,
102                   uploadPlane);
103      glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
104      return;
105    } else {
106      // Make an unpadded copy and upload that instead. Quick profiling showed
107      // that this is faster than uploading row by row using glTexSubImage2D.
108      uint8_t *unpaddedPlane = _planeBuffer.data();
109      for (size_t y = 0; y < height; ++y) {
110        memcpy(unpaddedPlane + y * width, plane + y * stride, width);
111      }
112      uploadPlane = unpaddedPlane;
113    }
114  }
115  glTexImage2D(GL_TEXTURE_2D,
116               0,
117               RTC_PIXEL_FORMAT,
118               static_cast<GLsizei>(width),
119               static_cast<GLsizei>(height),
120               0,
121               RTC_PIXEL_FORMAT,
122               GL_UNSIGNED_BYTE,
123               uploadPlane);
124}
125
126- (void)uploadFrameToTextures:(RTC_OBJC_TYPE(RTCVideoFrame) *)frame {
127  _currentTextureSet = (_currentTextureSet + 1) % kNumTextureSets;
128
129  id<RTC_OBJC_TYPE(RTCI420Buffer)> buffer = [frame.buffer toI420];
130
131  const int chromaWidth = buffer.chromaWidth;
132  const int chromaHeight = buffer.chromaHeight;
133  if (buffer.strideY != frame.width || buffer.strideU != chromaWidth ||
134      buffer.strideV != chromaWidth) {
135    _planeBuffer.resize(buffer.width * buffer.height);
136  }
137
138  [self uploadPlane:buffer.dataY
139            texture:self.yTexture
140              width:buffer.width
141             height:buffer.height
142             stride:buffer.strideY];
143
144  [self uploadPlane:buffer.dataU
145            texture:self.uTexture
146              width:chromaWidth
147             height:chromaHeight
148             stride:buffer.strideU];
149
150  [self uploadPlane:buffer.dataV
151            texture:self.vTexture
152              width:chromaWidth
153             height:chromaHeight
154             stride:buffer.strideV];
155}
156
157@end
158