325 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			325 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
// Copyright 2019 The MediaPipe Authors.
 | 
						|
//
 | 
						|
// Licensed under the Apache License, Version 2.0 (the "License");
 | 
						|
// you may not use this file except in compliance with the License.
 | 
						|
// You may obtain a copy of the License at
 | 
						|
//
 | 
						|
//      http://www.apache.org/licenses/LICENSE-2.0
 | 
						|
//
 | 
						|
// Unless required by applicable law or agreed to in writing, software
 | 
						|
// distributed under the License is distributed on an "AS IS" BASIS,
 | 
						|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
						|
// See the License for the specific language governing permissions and
 | 
						|
// limitations under the License.
 | 
						|
 | 
						|
#include "mediapipe/gpu/gpu_buffer_multi_pool.h"
 | 
						|
 | 
						|
#include <tuple>
 | 
						|
 | 
						|
#include "absl/memory/memory.h"
 | 
						|
#include "absl/synchronization/mutex.h"
 | 
						|
#include "mediapipe/framework/port/logging.h"
 | 
						|
#include "mediapipe/gpu/gpu_shared_data_internal.h"
 | 
						|
 | 
						|
#ifdef __APPLE__
 | 
						|
#include "CoreFoundation/CFBase.h"
 | 
						|
#include "mediapipe/objc/CFHolder.h"
 | 
						|
#endif  // __APPLE__
 | 
						|
 | 
						|
namespace mediapipe {
 | 
						|
 | 
						|
// Keep this many buffers allocated for a given frame size.
 | 
						|
static constexpr int kKeepCount = 2;
 | 
						|
// The maximum size of the GpuBufferMultiPool. When the limit is reached, the
 | 
						|
// oldest BufferSpec will be dropped.
 | 
						|
static constexpr int kMaxPoolCount = 10;
 | 
						|
// Time in seconds after which an inactive buffer can be dropped from the pool.
 | 
						|
// Currently only used with CVPixelBufferPool.
 | 
						|
static constexpr float kMaxInactiveBufferAge = 0.25;
 | 
						|
// Skip allocating a buffer pool until at least this many requests have been
 | 
						|
// made for a given BufferSpec.
 | 
						|
static constexpr int kMinRequestsBeforePool = 2;
 | 
						|
// Do a deeper flush every this many requests.
 | 
						|
static constexpr int kRequestCountScrubInterval = 50;
 | 
						|
 | 
						|
#if MEDIAPIPE_GPU_BUFFER_USE_CV_PIXEL_BUFFER
 | 
						|
 | 
						|
CvPixelBufferPoolWrapper::CvPixelBufferPoolWrapper(
 | 
						|
    const GpuBufferMultiPool::BufferSpec& spec, CFTimeInterval maxAge) {
 | 
						|
  OSType cv_format = CVPixelFormatForGpuBufferFormat(spec.format);
 | 
						|
  CHECK_NE(cv_format, -1) << "unsupported pixel format";
 | 
						|
  pool_ = MakeCFHolderAdopting(
 | 
						|
      /* keep count is 0 because the age param keeps buffers around anyway */
 | 
						|
      CreateCVPixelBufferPool(spec.width, spec.height, cv_format, 0, maxAge));
 | 
						|
}
 | 
						|
 | 
						|
GpuBuffer CvPixelBufferPoolWrapper::GetBuffer(std::function<void(void)> flush) {
 | 
						|
  CVPixelBufferRef buffer;
 | 
						|
  int threshold = 1;
 | 
						|
  NSMutableDictionary* auxAttributes =
 | 
						|
      [NSMutableDictionary dictionaryWithCapacity:1];
 | 
						|
  CVReturn err;
 | 
						|
  bool tried_flushing = false;
 | 
						|
  while (1) {
 | 
						|
    auxAttributes[(id)kCVPixelBufferPoolAllocationThresholdKey] = @(threshold);
 | 
						|
    err = CVPixelBufferPoolCreatePixelBufferWithAuxAttributes(
 | 
						|
        kCFAllocatorDefault, *pool_, (__bridge CFDictionaryRef)auxAttributes,
 | 
						|
        &buffer);
 | 
						|
    if (err != kCVReturnWouldExceedAllocationThreshold) break;
 | 
						|
    if (flush && !tried_flushing) {
 | 
						|
      // Call the flush function to potentially release old holds on buffers
 | 
						|
      // and try again to create a pixel buffer.
 | 
						|
      // This is used to flush CV texture caches, which may retain buffers until
 | 
						|
      // flushed.
 | 
						|
      flush();
 | 
						|
      tried_flushing = true;
 | 
						|
    } else {
 | 
						|
      ++threshold;
 | 
						|
    }
 | 
						|
  }
 | 
						|
  CHECK(!err) << "Error creating pixel buffer: " << err;
 | 
						|
  count_ = threshold;
 | 
						|
  return GpuBuffer(MakeCFHolderAdopting(buffer));
 | 
						|
}
 | 
						|
 | 
						|
std::string CvPixelBufferPoolWrapper::GetDebugString() const {
 | 
						|
  auto description = MakeCFHolderAdopting(CFCopyDescription(*pool_));
 | 
						|
  return [(__bridge NSString*)*description UTF8String];
 | 
						|
}
 | 
						|
 | 
						|
void CvPixelBufferPoolWrapper::Flush() { CVPixelBufferPoolFlush(*pool_, 0); }
 | 
						|
 | 
						|
GpuBufferMultiPool::SimplePool GpuBufferMultiPool::MakeSimplePool(
 | 
						|
    const GpuBufferMultiPool::BufferSpec& spec) {
 | 
						|
  return std::make_shared<CvPixelBufferPoolWrapper>(spec,
 | 
						|
                                                    kMaxInactiveBufferAge);
 | 
						|
}
 | 
						|
 | 
						|
GpuBuffer GpuBufferMultiPool::GetBufferWithoutPool(const BufferSpec& spec) {
 | 
						|
  OSType cv_format = CVPixelFormatForGpuBufferFormat(spec.format);
 | 
						|
  CHECK_NE(cv_format, -1) << "unsupported pixel format";
 | 
						|
  CVPixelBufferRef buffer;
 | 
						|
  CVReturn err = CreateCVPixelBufferWithoutPool(spec.width, spec.height,
 | 
						|
                                                cv_format, &buffer);
 | 
						|
  CHECK(!err) << "Error creating pixel buffer: " << err;
 | 
						|
  return GpuBuffer(MakeCFHolderAdopting(buffer));
 | 
						|
}
 | 
						|
 | 
						|
void GpuBufferMultiPool::FlushTextureCaches() {
 | 
						|
  absl::MutexLock lock(&mutex_);
 | 
						|
  for (const auto& cache : texture_caches_) {
 | 
						|
#if TARGET_OS_OSX
 | 
						|
    CVOpenGLTextureCacheFlush(*cache, 0);
 | 
						|
#else
 | 
						|
    CVOpenGLESTextureCacheFlush(*cache, 0);
 | 
						|
#endif  // TARGET_OS_OSX
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
// Turning this on disables the pixel buffer pools when using the simulator.
 | 
						|
// It is no longer necessary, since the helper code now supports non-contiguous
 | 
						|
// buffers. We leave the code in for now for the sake of documentation.
 | 
						|
#define FORCE_CONTIGUOUS_PIXEL_BUFFER_ON_IPHONE_SIMULATOR 0
 | 
						|
 | 
						|
GpuBuffer GpuBufferMultiPool::GetBufferFromSimplePool(
 | 
						|
    BufferSpec spec, const GpuBufferMultiPool::SimplePool& pool) {
 | 
						|
#if TARGET_IPHONE_SIMULATOR && FORCE_CONTIGUOUS_PIXEL_BUFFER_ON_IPHONE_SIMULATOR
 | 
						|
  // On the simulator, syncing the texture with the pixelbuffer does not work,
 | 
						|
  // and we have to use glReadPixels. Since GL_UNPACK_ROW_LENGTH is not
 | 
						|
  // available in OpenGL ES 2, we should create the buffer so the pixels are
 | 
						|
  // contiguous.
 | 
						|
  //
 | 
						|
  // TODO: verify if we can use kIOSurfaceBytesPerRow to force the
 | 
						|
  // pool to give us contiguous data.
 | 
						|
  return GetBufferWithoutPool(spec);
 | 
						|
#else
 | 
						|
  return pool->GetBuffer([this]() { FlushTextureCaches(); });
 | 
						|
#endif  // TARGET_IPHONE_SIMULATOR
 | 
						|
}
 | 
						|
 | 
						|
#else
 | 
						|
 | 
						|
GpuBufferMultiPool::SimplePool GpuBufferMultiPool::MakeSimplePool(
 | 
						|
    const BufferSpec& spec) {
 | 
						|
  return GlTextureBufferPool::Create(spec.width, spec.height, spec.format,
 | 
						|
                                     kKeepCount);
 | 
						|
}
 | 
						|
 | 
						|
GpuBuffer GpuBufferMultiPool::GetBufferWithoutPool(const BufferSpec& spec) {
 | 
						|
  return GpuBuffer(
 | 
						|
      GlTextureBuffer::Create(spec.width, spec.height, spec.format));
 | 
						|
}
 | 
						|
 | 
						|
GpuBuffer GpuBufferMultiPool::GetBufferFromSimplePool(
 | 
						|
    BufferSpec spec, const GpuBufferMultiPool::SimplePool& pool) {
 | 
						|
  return GpuBuffer(pool->GetBuffer());
 | 
						|
}
 | 
						|
 | 
						|
#endif  // MEDIAPIPE_GPU_BUFFER_USE_CV_PIXEL_BUFFER
 | 
						|
 | 
						|
void GpuBufferMultiPool::EntryList::Prepend(Entry* entry) {
 | 
						|
  if (head_ == nullptr) {
 | 
						|
    head_ = tail_ = entry;
 | 
						|
  } else {
 | 
						|
    entry->next = head_;
 | 
						|
    head_->prev = entry;
 | 
						|
    head_ = entry;
 | 
						|
  }
 | 
						|
  ++size_;
 | 
						|
}
 | 
						|
 | 
						|
void GpuBufferMultiPool::EntryList::Append(Entry* entry) {
 | 
						|
  if (tail_ == nullptr) {
 | 
						|
    head_ = tail_ = entry;
 | 
						|
  } else {
 | 
						|
    tail_->next = entry;
 | 
						|
    entry->prev = tail_;
 | 
						|
    tail_ = entry;
 | 
						|
  }
 | 
						|
  ++size_;
 | 
						|
}
 | 
						|
 | 
						|
void GpuBufferMultiPool::EntryList::Remove(Entry* entry) {
 | 
						|
  if (entry == head_) {
 | 
						|
    head_ = entry->next;
 | 
						|
  } else {
 | 
						|
    entry->prev->next = entry->next;
 | 
						|
  }
 | 
						|
  if (entry == tail_) {
 | 
						|
    tail_ = entry->prev;
 | 
						|
  } else {
 | 
						|
    entry->next->prev = entry->prev;
 | 
						|
  }
 | 
						|
  entry->prev = nullptr;
 | 
						|
  entry->next = nullptr;
 | 
						|
  --size_;
 | 
						|
}
 | 
						|
 | 
						|
void GpuBufferMultiPool::EntryList::InsertAfter(Entry* entry, Entry* after) {
 | 
						|
  if (after != nullptr) {
 | 
						|
    entry->next = after->next;
 | 
						|
    if (entry->next) entry->next->prev = entry;
 | 
						|
    entry->prev = after;
 | 
						|
    after->next = entry;
 | 
						|
    ++size_;
 | 
						|
  } else
 | 
						|
    Prepend(entry);
 | 
						|
}
 | 
						|
 | 
						|
void GpuBufferMultiPool::Evict(std::vector<SimplePool>* evicted) {
 | 
						|
  // Remove excess entries.
 | 
						|
  while (entry_list_.size() > kMaxPoolCount) {
 | 
						|
    Entry* victim = entry_list_.tail();
 | 
						|
    evicted->emplace_back(std::move(victim->pool));
 | 
						|
    entry_list_.Remove(victim);
 | 
						|
    pools_.erase(victim->spec);
 | 
						|
  }
 | 
						|
  // Every kRequestCountScrubInterval requests, halve the request counts, and
 | 
						|
  // remove entries which have fallen to 0.
 | 
						|
  // This keeps sporadic requests from accumulating and eventually exceeding
 | 
						|
  // the minimum request threshold for allocating a pool. Also, it means that
 | 
						|
  // if the request regimen changes (e.g. a graph was always requesting a large
 | 
						|
  // size, but then switches to a small size to save memory or CPU), the pool
 | 
						|
  // can quickly adapt to it.
 | 
						|
  if (total_request_count_ >= kRequestCountScrubInterval) {
 | 
						|
    total_request_count_ = 0;
 | 
						|
    VLOG(2) << "begin pool scrub";
 | 
						|
    for (Entry* entry = entry_list_.head(); entry != nullptr;) {
 | 
						|
      VLOG(2) << "entry for: " << entry->spec.width << "x" << entry->spec.height
 | 
						|
              << " request_count: " << entry->request_count
 | 
						|
              << " has pool: " << (entry->pool != nullptr);
 | 
						|
      entry->request_count /= 2;
 | 
						|
      Entry* next = entry->next;
 | 
						|
      if (entry->request_count == 0) {
 | 
						|
        evicted->emplace_back(std::move(entry->pool));
 | 
						|
        entry_list_.Remove(entry);
 | 
						|
        pools_.erase(entry->spec);
 | 
						|
      }
 | 
						|
      entry = next;
 | 
						|
    }
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
GpuBufferMultiPool::SimplePool GpuBufferMultiPool::RequestPool(
 | 
						|
    const BufferSpec& key) {
 | 
						|
  SimplePool pool;
 | 
						|
  std::vector<SimplePool> evicted;
 | 
						|
  {
 | 
						|
    absl::MutexLock lock(&mutex_);
 | 
						|
    auto pool_it = pools_.find(key);
 | 
						|
    Entry* entry;
 | 
						|
    if (pool_it == pools_.end()) {
 | 
						|
      std::tie(pool_it, std::ignore) =
 | 
						|
          pools_.emplace(std::piecewise_construct, std::forward_as_tuple(key),
 | 
						|
                         std::forward_as_tuple(key));
 | 
						|
      entry = &pool_it->second;
 | 
						|
      CHECK_EQ(entry->request_count, 0);
 | 
						|
      entry->request_count = 1;
 | 
						|
      entry_list_.Append(entry);
 | 
						|
      if (entry->prev != nullptr) CHECK_GE(entry->prev->request_count, 1);
 | 
						|
    } else {
 | 
						|
      entry = &pool_it->second;
 | 
						|
      ++entry->request_count;
 | 
						|
      Entry* larger = entry->prev;
 | 
						|
      while (larger != nullptr &&
 | 
						|
             larger->request_count < entry->request_count) {
 | 
						|
        larger = larger->prev;
 | 
						|
      }
 | 
						|
      if (larger != entry->prev) {
 | 
						|
        entry_list_.Remove(entry);
 | 
						|
        entry_list_.InsertAfter(entry, larger);
 | 
						|
      }
 | 
						|
    }
 | 
						|
    if (!entry->pool && entry->request_count >= kMinRequestsBeforePool) {
 | 
						|
      entry->pool = MakeSimplePool(key);
 | 
						|
    }
 | 
						|
    pool = entry->pool;
 | 
						|
    ++total_request_count_;
 | 
						|
    Evict(&evicted);
 | 
						|
  }
 | 
						|
  // Evicted pools, and their buffers, will be released without holding the
 | 
						|
  // lock.
 | 
						|
  return pool;
 | 
						|
}
 | 
						|
 | 
						|
GpuBuffer GpuBufferMultiPool::GetBuffer(int width, int height,
 | 
						|
                                        GpuBufferFormat format) {
 | 
						|
  BufferSpec key(width, height, format);
 | 
						|
  SimplePool pool = RequestPool(key);
 | 
						|
  if (pool) {
 | 
						|
    // Note: we release our multipool lock before accessing the simple pool.
 | 
						|
    return GetBufferFromSimplePool(key, pool);
 | 
						|
  } else {
 | 
						|
    return GetBufferWithoutPool(key);
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
GpuBufferMultiPool::~GpuBufferMultiPool() {
 | 
						|
#ifdef __APPLE__
 | 
						|
  CHECK_EQ(texture_caches_.size(), 0)
 | 
						|
      << "Failed to unregister texture caches before deleting pool";
 | 
						|
#endif  // defined(__APPLE__)
 | 
						|
}
 | 
						|
 | 
						|
#ifdef __APPLE__
 | 
						|
void GpuBufferMultiPool::RegisterTextureCache(CVTextureCacheType cache) {
 | 
						|
  absl::MutexLock lock(&mutex_);
 | 
						|
 | 
						|
  CHECK(std::find(texture_caches_.begin(), texture_caches_.end(), cache) ==
 | 
						|
        texture_caches_.end())
 | 
						|
      << "Attempting to register a texture cache twice";
 | 
						|
  texture_caches_.emplace_back(cache);
 | 
						|
}
 | 
						|
 | 
						|
void GpuBufferMultiPool::UnregisterTextureCache(CVTextureCacheType cache) {
 | 
						|
  absl::MutexLock lock(&mutex_);
 | 
						|
 | 
						|
  auto it = std::find(texture_caches_.begin(), texture_caches_.end(), cache);
 | 
						|
  CHECK(it != texture_caches_.end())
 | 
						|
      << "Attempting to unregister an unknown texture cache";
 | 
						|
  texture_caches_.erase(it);
 | 
						|
}
 | 
						|
#endif  // defined(__APPLE__)
 | 
						|
 | 
						|
}  // namespace mediapipe
 |