blob: f1e2cb22e5ca06cea348d586897167c4c133c32b [file] [log] [blame]
// Copyright 2015 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 "apps/moterm/gl_helper.h"
#ifndef GL_GLEXT_PROTOTYPES
#define GL_GLEXT_PROTOTYPES
#endif
#include <GLES2/gl2.h>
#include <GLES2/gl2extmojo.h>
#include <MGL/mgl.h>
#include "base/bind.h"
#include "base/location.h"
#include "base/message_loop/message_loop.h"
#include "base/task_runner.h"
#include "mojo/public/cpp/application/connect.h"
#include "mojo/public/interfaces/application/shell.mojom.h"
#include "mojo/services/geometry/cpp/geometry_util.h"
#include "mojo/services/surfaces/cpp/surfaces_utils.h"
// Maximum number of (live) textures to keep around.
const size_t kMaxTextures = 10;
GlHelper::GlHelper(Client* client,
mojo::Shell* shell,
GLint texture_format,
bool flipped,
const mojo::Size& initial_size)
: client_(client),
texture_format_(texture_format),
flipped_(flipped),
returner_binding_(this),
next_surface_size_(initial_size),
mgl_context_(MGL_NO_CONTEXT),
next_frame_id_(0),
frame_texture_(0),
id_namespace_(0),
local_id_(0),
next_resource_id_(0),
weak_factory_(this) {
mojo::ServiceProviderPtr native_viewport_service_provider;
shell->ConnectToApplication("mojo:native_viewport_service",
GetProxy(&native_viewport_service_provider),
nullptr);
mojo::ConnectToService(native_viewport_service_provider.get(), &gpu_);
mojo::ServiceProviderPtr surfaces_service_provider;
shell->ConnectToApplication("mojo:surfaces_service",
GetProxy(&surfaces_service_provider), nullptr);
mojo::ConnectToService(surfaces_service_provider.get(), &surface_);
surface_->GetIdNamespace(base::Bind(&GlHelper::GetIdNamespaceCallback,
weak_factory_.GetWeakPtr()));
mojo::ResourceReturnerPtr returner_ptr;
returner_binding_.Bind(GetProxy(&returner_ptr));
surface_->SetResourceReturner(returner_ptr.Pass());
}
GlHelper::~GlHelper() {
DCHECK(!frame_texture_);
if (mgl_context_ != MGL_NO_CONTEXT)
MGLDestroyContext(mgl_context_);
}
void GlHelper::SetSurfaceSize(const mojo::Size& surface_size) {
next_surface_size_ = surface_size;
}
void GlHelper::MakeCurrent() {
EnsureContext();
}
void GlHelper::StartFrame() {
DCHECK(!frame_texture_);
EnsureContext();
EnsureSurface();
TextureInfo texture_info = GetTexture();
DCHECK(texture_info.texture);
frame_texture_ = texture_info.texture;
// It's already bound.
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
}
uint32_t GlHelper::EndFrame() {
DCHECK(frame_texture_);
mojo::Rect size_rect;
size_rect.width = current_surface_size_.width;
size_rect.height = current_surface_size_.height;
GLbyte mailbox[GL_MAILBOX_SIZE_CHROMIUM];
glGenMailboxCHROMIUM(mailbox);
glProduceTextureCHROMIUM(GL_TEXTURE_2D, mailbox);
GLuint sync_point = glInsertSyncPointCHROMIUM();
mojo::FramePtr frame = mojo::Frame::New();
// Frame resources:
frame->resources.push_back(mojo::TransferableResource::New());
mojo::TransferableResource* resource = frame->resources[0].get();
resource->id = next_resource_id_++;
textures_pending_return_.push_back(
TextureInfo(resource->id, frame_texture_, current_surface_size_));
frame_texture_ = 0;
// TODO(vtl): This is wrong, but doesn't seem to have an effect.
resource->format = mojo::ResourceFormat::RGBA_8888;
resource->filter = GL_LINEAR;
resource->size = current_surface_size_.Clone();
mojo::MailboxHolderPtr mailbox_holder = mojo::MailboxHolder::New();
mailbox_holder->mailbox = mojo::Mailbox::New();
for (int i = 0; i < GL_MAILBOX_SIZE_CHROMIUM; ++i)
mailbox_holder->mailbox->name.push_back(mailbox[i]);
mailbox_holder->texture_target = GL_TEXTURE_2D;
mailbox_holder->sync_point = sync_point;
resource->mailbox_holder = mailbox_holder.Pass();
resource->is_repeated = false;
resource->is_software = false;
// Frame passes:
frame->passes.push_back(mojo::CreateDefaultPass(1, size_rect));
mojo::Pass* pass = frame->passes[0].get();
pass->quads.push_back(mojo::Quad::New());
mojo::Quad* quad = pass->quads[0].get();
quad->material = mojo::Material::TEXTURE_CONTENT;
quad->rect = size_rect.Clone();
quad->opaque_rect = size_rect.Clone();
quad->visible_rect = size_rect.Clone();
quad->needs_blending = true;
quad->shared_quad_state_index = 0;
quad->texture_quad_state = mojo::TextureQuadState::New();
mojo::TextureQuadState* texture_state = quad->texture_quad_state.get();
texture_state->resource_id = resource->id;
texture_state->premultiplied_alpha = true;
texture_state->uv_top_left = mojo::PointF::New();
texture_state->uv_bottom_right = mojo::PointF::New();
texture_state->uv_bottom_right->x = 1.f;
texture_state->uv_bottom_right->y = 1.f;
texture_state->background_color = mojo::Color::New();
texture_state->background_color->rgba = 0;
for (int i = 0; i < 4; ++i)
texture_state->vertex_opacity.push_back(1.f);
texture_state->flipped = flipped_;
pass->shared_quad_states.push_back(
mojo::CreateDefaultSQS(current_surface_size_));
surface_->SubmitFrame(local_id_, frame.Pass(),
base::Bind(&GlHelper::SubmitFrameCallback,
weak_factory_.GetWeakPtr(), next_frame_id_));
return next_frame_id_++;
}
GLuint GlHelper::GetFrameTexture() {
DCHECK(frame_texture_);
return frame_texture_;
}
void GlHelper::ReturnResources(
mojo::Array<mojo::ReturnedResourcePtr> resources) {
DCHECK(!frame_texture_);
if (mgl_context_ == MGL_NO_CONTEXT) {
DCHECK(textures_pending_return_.empty());
return;
}
MGLMakeCurrent(mgl_context_);
// Note: This quadratic nested loop is OK, since we expect both |resources|
// and |textures_pending_return_| to be small (and |resources| should
// usually have just a single element).
for (size_t i = 0; i < resources.size(); ++i) {
mojo::ReturnedResourcePtr resource = resources[i].Pass();
DCHECK_EQ(resource->count, 1);
bool found = false;
for (size_t j = 0; j < textures_pending_return_.size(); j++) {
const TextureInfo& texture_info = textures_pending_return_[j];
if (texture_info.resource_id == resource->id) {
glWaitSyncPointCHROMIUM(resource->sync_point);
ReturnTexture(texture_info);
textures_pending_return_.erase(textures_pending_return_.begin() + j);
found = true;
break;
}
}
if (!found) {
// If we don't texture ID for it, assume we lost the context.
// TODO(vtl): This may leak (but currently we don't know if the texture is
// still valid).
DVLOG(1) << "Returned texture not found (context lost?)";
}
}
}
void GlHelper::EnsureContext() {
DCHECK(!frame_texture_);
if (mgl_context_ == MGL_NO_CONTEXT) {
DCHECK(textures_pending_return_.empty());
mojo::CommandBufferPtr command_buffer;
gpu_->CreateOffscreenGLES2Context(mojo::GetProxy(&command_buffer));
mgl_context_ = MGLCreateContext(
MGL_API_VERSION_GLES2,
command_buffer.PassInterface().PassHandle().release().value(), nullptr,
&GlHelper::OnContextLostThunk, this,
mojo::Environment::GetDefaultAsyncWaiter());
CHECK_NE(mgl_context_, MGL_NO_CONTEXT);
}
MGLMakeCurrent(mgl_context_);
}
void GlHelper::EnsureSurface() {
DCHECK(!frame_texture_);
DCHECK_NE(mgl_context_, MGL_NO_CONTEXT);
if (local_id_) {
if (current_surface_size_ == next_surface_size_)
return;
surface_->DestroySurface(local_id_);
ClearTextures();
}
local_id_++;
surface_->CreateSurface(local_id_);
current_surface_size_ = next_surface_size_;
if (id_namespace_) {
// Don't call the client in the nested context.
base::MessageLoop::current()->task_runner()->PostTask(
FROM_HERE, base::Bind(&GlHelper::CallOnSurfaceIdChanged,
weak_factory_.GetWeakPtr()));
}
}
void GlHelper::CallOnSurfaceIdChanged() {
DCHECK(id_namespace_ && local_id_);
auto qualified_id = mojo::SurfaceId::New();
qualified_id->id_namespace = id_namespace_;
qualified_id->local = local_id_;
client_->OnSurfaceIdChanged(qualified_id.Pass());
}
GlHelper::TextureInfo GlHelper::GetTexture() {
DCHECK_NE(mgl_context_, MGL_NO_CONTEXT);
if (!textures_.empty()) {
TextureInfo rv = textures_.front();
DCHECK(rv.size == current_surface_size_);
textures_.pop_front();
glBindTexture(GL_TEXTURE_2D, rv.texture);
return rv;
}
GLuint texture = 0;
glGenTextures(1, &texture);
DCHECK(texture);
glBindTexture(GL_TEXTURE_2D, texture);
glTexImage2D(GL_TEXTURE_2D, 0, texture_format_, current_surface_size_.width,
current_surface_size_.height, 0, texture_format_,
GL_UNSIGNED_BYTE, nullptr);
return TextureInfo(0, texture, current_surface_size_);
}
void GlHelper::ReturnTexture(const TextureInfo& texture_info) {
DCHECK_NE(mgl_context_, MGL_NO_CONTEXT);
DCHECK_NE(texture_info.texture, 0u);
if (texture_info.size == current_surface_size_ &&
textures_.size() < kMaxTextures)
textures_.push_back(texture_info); // TODO(vtl): Is |push_front()| better?
else
glDeleteTextures(1, &texture_info.texture);
}
void GlHelper::ClearTextures() {
DCHECK_NE(mgl_context_, MGL_NO_CONTEXT);
for (const auto& texture_info : textures_)
glDeleteTextures(1, &texture_info.texture);
textures_.clear();
}
void GlHelper::GetIdNamespaceCallback(uint32_t id_namespace) {
id_namespace_ = id_namespace;
if (local_id_) {
// We're in a callback, so we can just call the client directly.
CallOnSurfaceIdChanged();
}
}
// static
void GlHelper::OnContextLostThunk(void* self) {
static_cast<GlHelper*>(self)->OnContextLost();
}
void GlHelper::OnContextLost() {
// We shouldn't get this while we're processing a frame.
DCHECK(!frame_texture_);
DCHECK_NE(mgl_context_, MGL_NO_CONTEXT);
MGLDestroyContext(mgl_context_);
mgl_context_ = MGL_NO_CONTEXT;
// TODO(vtl): We don't know if any of those textures will be valid when
// returned (if they are), so assume they aren't.
textures_pending_return_.clear();
// We're in a callback, so we can just call the client directly.
client_->OnContextLost();
}
void GlHelper::SubmitFrameCallback(uint32_t frame_id) {
// We're in a callback, so we can just call the client directly.
client_->OnFrameDisplayed(frame_id);
}