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