// Copyright 2014 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "content/browser/compositor/buffer_queue.h" #include "content/browser/compositor/image_transport_factory.h" #include "content/browser/gpu/browser_gpu_memory_buffer_manager.h" #include "content/common/gpu/client/context_provider_command_buffer.h" #include "content/common/gpu/client/gl_helper.h" #include "gpu/GLES2/gl2extchromium.h" #include "gpu/command_buffer/client/gles2_interface.h" #include "gpu/command_buffer/service/image_factory.h" #include "third_party/skia/include/core/SkRect.h" #include "third_party/skia/include/core/SkRegion.h" #include "ui/gfx/gpu_memory_buffer.h" #include "ui/gfx/skia_util.h" namespace content { BufferQueue::BufferQueue( scoped_refptr context_provider, unsigned int texture_target, unsigned int internalformat, GLHelper* gl_helper, BrowserGpuMemoryBufferManager* gpu_memory_buffer_manager, int surface_id) : context_provider_(context_provider), fbo_(0), allocated_count_(0), texture_target_(texture_target), internal_format_(internalformat), gl_helper_(gl_helper), gpu_memory_buffer_manager_(gpu_memory_buffer_manager), surface_id_(surface_id) {} BufferQueue::~BufferQueue() { FreeAllSurfaces(); gpu::gles2::GLES2Interface* gl = context_provider_->ContextGL(); if (fbo_) gl->DeleteFramebuffers(1, &fbo_); } void BufferQueue::Initialize() { gpu::gles2::GLES2Interface* gl = context_provider_->ContextGL(); gl->GenFramebuffers(1, &fbo_); } void BufferQueue::BindFramebuffer() { gpu::gles2::GLES2Interface* gl = context_provider_->ContextGL(); gl->BindFramebuffer(GL_FRAMEBUFFER, fbo_); if (!current_surface_.texture) { current_surface_ = GetNextSurface(); gl->FramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, texture_target_, current_surface_.texture, 0); } } void BufferQueue::CopyBufferDamage(int texture, int source_texture, const gfx::Rect& new_damage, const gfx::Rect& old_damage) { gl_helper_->CopySubBufferDamage( texture_target_, texture, source_texture, SkRegion(gfx::RectToSkIRect(new_damage)), SkRegion(gfx::RectToSkIRect(old_damage))); } void BufferQueue::UpdateBufferDamage(const gfx::Rect& damage) { displayed_surface_.damage.Union(damage); for (size_t i = 0; i < available_surfaces_.size(); i++) available_surfaces_[i].damage.Union(damage); for (std::deque::iterator it = in_flight_surfaces_.begin(); it != in_flight_surfaces_.end(); ++it) it->damage.Union(damage); } void BufferQueue::SwapBuffers(const gfx::Rect& damage) { if (damage != gfx::Rect(size_)) { // We must have a frame available to copy from. DCHECK(!in_flight_surfaces_.empty() || displayed_surface_.texture); unsigned int texture_id = !in_flight_surfaces_.empty() ? in_flight_surfaces_.back().texture : displayed_surface_.texture; CopyBufferDamage(current_surface_.texture, texture_id, damage, current_surface_.damage); } UpdateBufferDamage(damage); current_surface_.damage = gfx::Rect(); in_flight_surfaces_.push_back(current_surface_); current_surface_.texture = 0; current_surface_.image = 0; // Some things reset the framebuffer (CopySubBufferDamage, some GLRenderer // paths), so ensure we restore it here. context_provider_->ContextGL()->BindFramebuffer(GL_FRAMEBUFFER, fbo_); } void BufferQueue::Reshape(const gfx::Size& size, float scale_factor) { if (size == size_) return; // TODO(ccameron): This assert is being hit on Mac try jobs. Determine if that // is cause for concern or if it is benign. // http://crbug.com/524624 #if !defined(OS_MACOSX) DCHECK(!current_surface_.texture); #endif size_ = size; // TODO: add stencil buffer when needed. gpu::gles2::GLES2Interface* gl = context_provider_->ContextGL(); gl->BindFramebuffer(GL_FRAMEBUFFER, fbo_); gl->FramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, texture_target_, 0, 0); FreeAllSurfaces(); } void BufferQueue::RecreateBuffers() { // We need to recreate the buffers, for whatever reason the old ones are not // presentable on the device anymore. // Unused buffers can be freed directly, they will be re-allocated as needed. // Any in flight, current or displayed surface must be replaced. for (size_t i = 0; i < available_surfaces_.size(); i++) FreeSurface(&available_surfaces_[i]); available_surfaces_.clear(); for (auto& surface : in_flight_surfaces_) surface = RecreateBuffer(&surface); current_surface_ = RecreateBuffer(¤t_surface_); displayed_surface_ = RecreateBuffer(&displayed_surface_); if (current_surface_.texture) { // If we have a texture bound, we will need to re-bind it. gpu::gles2::GLES2Interface* gl = context_provider_->ContextGL(); gl->BindFramebuffer(GL_FRAMEBUFFER, fbo_); gl->FramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, texture_target_, current_surface_.texture, 0); } } BufferQueue::AllocatedSurface BufferQueue::RecreateBuffer( AllocatedSurface* surface) { if (!surface->texture) return *surface; AllocatedSurface new_surface = GetNextSurface(); new_surface.damage = surface->damage; // Copy the entire texture. CopyBufferDamage(new_surface.texture, surface->texture, gfx::Rect(), gfx::Rect(size_)); FreeSurface(surface); return new_surface; } void BufferQueue::PageFlipComplete() { DCHECK(!in_flight_surfaces_.empty()); if (displayed_surface_.texture) available_surfaces_.push_back(displayed_surface_); displayed_surface_ = in_flight_surfaces_.front(); in_flight_surfaces_.pop_front(); } void BufferQueue::FreeAllSurfaces() { FreeSurface(&displayed_surface_); FreeSurface(¤t_surface_); // This is intentionally not emptied since the swap buffers acks are still // expected to arrive. for (auto& surface : in_flight_surfaces_) FreeSurface(&surface); for (size_t i = 0; i < available_surfaces_.size(); i++) FreeSurface(&available_surfaces_[i]); available_surfaces_.clear(); } void BufferQueue::FreeSurface(AllocatedSurface* surface) { if (surface->texture) { gpu::gles2::GLES2Interface* gl = context_provider_->ContextGL(); gl->BindTexture(texture_target_, surface->texture); gl->ReleaseTexImage2DCHROMIUM(texture_target_, surface->image); gl->DeleteTextures(1, &surface->texture); gl->DestroyImageCHROMIUM(surface->image); surface->image = 0; surface->texture = 0; allocated_count_--; } } BufferQueue::AllocatedSurface BufferQueue::GetNextSurface() { if (!available_surfaces_.empty()) { AllocatedSurface surface = available_surfaces_.back(); available_surfaces_.pop_back(); return surface; } unsigned int texture = 0; gpu::gles2::GLES2Interface* gl = context_provider_->ContextGL(); gl->GenTextures(1, &texture); if (!texture) return AllocatedSurface(); // We don't want to allow anything more than triple buffering. DCHECK_LT(allocated_count_, 4U); scoped_ptr buffer( gpu_memory_buffer_manager_->AllocateGpuMemoryBufferForScanout( size_, gpu::ImageFactory::DefaultBufferFormatForImageFormat( internal_format_), surface_id_)); if (!buffer.get()) { gl->DeleteTextures(1, &texture); DLOG(ERROR) << "Failed to allocate GPU memory buffer"; return AllocatedSurface(); } unsigned int id = gl->CreateImageCHROMIUM( buffer->AsClientBuffer(), size_.width(), size_.height(), internal_format_); if (!id) { LOG(ERROR) << "Failed to allocate backing image surface"; gl->DeleteTextures(1, &texture); return AllocatedSurface(); } allocated_count_++; gl->BindTexture(texture_target_, texture); gl->BindTexImage2DCHROMIUM(texture_target_, id); return AllocatedSurface(buffer.Pass(), texture, id, gfx::Rect(size_)); } BufferQueue::AllocatedSurface::AllocatedSurface() : texture(0), image(0) {} BufferQueue::AllocatedSurface::AllocatedSurface( scoped_ptr buffer, unsigned int texture, unsigned int image, const gfx::Rect& rect) : buffer(buffer.release()), texture(texture), image(image), damage(rect) {} BufferQueue::AllocatedSurface::~AllocatedSurface() {} } // namespace content