blob: e156569a57a57f20eaafd21cdbc393af7dc67293 [file] [log] [blame]
// Copyright 2018 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 "chrome/browser/vr/win/graphics_delegate_win.h"
#include "base/numerics/math_constants.h"
#include "content/public/browser/gpu_utils.h"
#include "content/public/common/gpu_stream_constants.h"
#include "gpu/command_buffer/client/gles2_interface.h"
#include "gpu/command_buffer/client/gles2_lib.h"
#include "mojo/public/cpp/system/platform_handle.h"
namespace vr {
namespace {
constexpr float kZNear = 0.1f;
constexpr float kZFar = 10000.0f;
} // namespace
GraphicsDelegateWin::GraphicsDelegateWin() {}
GraphicsDelegateWin::~GraphicsDelegateWin() {}
bool GraphicsDelegateWin::InitializeOnMainThread() {
gpu::GpuChannelEstablishFactory* factory =
content::GetGpuChannelEstablishFactory();
scoped_refptr<gpu::GpuChannelHost> host = factory->EstablishGpuChannelSync();
gpu::ContextCreationAttribs attributes;
attributes.alpha_size = -1;
attributes.red_size = 8;
attributes.green_size = 8;
attributes.blue_size = 8;
attributes.stencil_size = 0;
attributes.depth_size = 0;
attributes.samples = 0;
attributes.sample_buffers = 0;
attributes.bind_generates_resource = false;
context_provider_ = base::MakeRefCounted<ws::ContextProviderCommandBuffer>(
host, factory->GetGpuMemoryBufferManager(), content::kGpuStreamIdDefault,
content::kGpuStreamPriorityUI, gpu::kNullSurfaceHandle,
GURL(std::string("chrome://gpu/VrUiWin")), false /* automatic flushes */,
false /* support locking */, false /* support grcontext */,
gpu::SharedMemoryLimits::ForMailboxContext(), attributes,
ws::command_buffer_metrics::ContextType::XR_COMPOSITING);
gpu_memory_buffer_manager_ = factory->GetGpuMemoryBufferManager();
return true;
}
void GraphicsDelegateWin::InitializeOnGLThread() {
DCHECK(context_provider_);
if (context_provider_->BindToCurrentThread() == gpu::ContextResult::kSuccess)
gl_ = context_provider_->ContextGL();
}
bool GraphicsDelegateWin::BindContext() {
if (!gl_)
return false;
gles2::SetGLContext(gl_);
return true;
}
void GraphicsDelegateWin::ClearContext() {
gles2::SetGLContext(nullptr);
}
gfx::Rect GraphicsDelegateWin::GetTextureSize() {
int width = info_->leftEye->renderWidth + info_->rightEye->renderWidth;
int height =
std::max(info_->leftEye->renderHeight, info_->rightEye->renderWidth);
return gfx::Rect(width, height);
}
void GraphicsDelegateWin::PreRender() {
if (!gl_)
return;
BindContext();
gfx::Rect size = GetTextureSize();
// Create a memory buffer, and an image referencing that memory buffer.
if (!EnsureMemoryBuffer(size.width(), size.height()))
return;
// Create a texture id, and associate it with our image.
gl_->GenTextures(1, &dest_texture_id_);
gl_->BindTexture(GL_TEXTURE_2D, dest_texture_id_);
gl_->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
gl_->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
gl_->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
gl_->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
gl_->BindTexImage2DCHROMIUM(GL_TEXTURE_2D, image_id_);
gl_->BindTexture(GL_TEXTURE_2D, 0);
// Bind our image/texture/memory buffer as the draw framebuffer.
gl_->GenFramebuffers(1, &draw_frame_buffer_);
gl_->BindFramebuffer(GL_DRAW_FRAMEBUFFER, draw_frame_buffer_);
gl_->FramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
GL_TEXTURE_2D, dest_texture_id_, 0);
}
void GraphicsDelegateWin::PostRender() {
// Unbind the drawing buffer.
gl_->BindFramebuffer(GL_FRAMEBUFFER, 0);
gl_->DeleteFramebuffers(1, &draw_frame_buffer_);
gl_->BindTexture(GL_TEXTURE_2D, dest_texture_id_);
gl_->ReleaseTexImage2DCHROMIUM(GL_TEXTURE_2D, image_id_);
gl_->DeleteTextures(1, &dest_texture_id_);
gl_->BindTexture(GL_TEXTURE_2D, 0);
dest_texture_id_ = 0;
draw_frame_buffer_ = 0;
// Flush.
gl_->ShallowFlushCHROMIUM();
ClearContext();
}
mojo::ScopedHandle GraphicsDelegateWin::GetTexture() {
// Hand out the gpu memory buffer.
mojo::ScopedHandle handle;
if (!gpu_memory_buffer_) {
return handle;
}
gfx::GpuMemoryBufferHandle gpu_handle = gpu_memory_buffer_->CloneHandle();
return mojo::WrapPlatformFile(gpu_handle.dxgi_handle.GetHandle());
}
gfx::RectF GraphicsDelegateWin::GetLeft() {
gfx::Rect size = GetTextureSize();
return gfx::RectF(
0, 0, static_cast<float>(info_->leftEye->renderWidth) / size.width(),
static_cast<float>(info_->leftEye->renderHeight) / size.height());
}
gfx::RectF GraphicsDelegateWin::GetRight() {
gfx::Rect size = GetTextureSize();
return gfx::RectF(
static_cast<float>(info_->leftEye->renderWidth) / size.width(), 0,
static_cast<float>(info_->rightEye->renderWidth) / size.width(),
static_cast<float>(info_->rightEye->renderHeight) / size.height());
}
void GraphicsDelegateWin::Cleanup() {
context_provider_ = nullptr;
}
bool GraphicsDelegateWin::EnsureMemoryBuffer(int width, int height) {
if (last_width_ != width || last_height_ != height || !gpu_memory_buffer_) {
if (!gpu_memory_buffer_manager_)
return false;
if (image_id_) {
gl_->DestroyImageCHROMIUM(image_id_);
image_id_ = 0;
}
gpu_memory_buffer_ = gpu_memory_buffer_manager_->CreateGpuMemoryBuffer(
gfx::Size(width, height), gfx::BufferFormat::RGBA_8888,
gfx::BufferUsage::SCANOUT, gpu::kNullSurfaceHandle);
if (!gpu_memory_buffer_)
return false;
last_width_ = width;
last_height_ = height;
image_id_ = gl_->CreateImageCHROMIUM(gpu_memory_buffer_->AsClientBuffer(),
width, height, GL_RGBA);
if (!image_id_) {
gpu_memory_buffer_ = nullptr;
return false;
}
}
return true;
}
void GraphicsDelegateWin::ResetMemoryBuffer() {
// Stop using a memory buffer if we had an error submitting with it.
gpu_memory_buffer_ = nullptr;
}
void GraphicsDelegateWin::SetVRDisplayInfo(
device::mojom::VRDisplayInfoPtr info) {
info_ = std::move(info);
}
FovRectangles GraphicsDelegateWin::GetRecommendedFovs() {
DCHECK(info_);
FovRectangle left = {
info_->leftEye->fieldOfView->leftDegrees,
info_->leftEye->fieldOfView->rightDegrees,
info_->leftEye->fieldOfView->downDegrees,
info_->leftEye->fieldOfView->upDegrees,
};
FovRectangle right = {
info_->rightEye->fieldOfView->leftDegrees,
info_->rightEye->fieldOfView->rightDegrees,
info_->rightEye->fieldOfView->downDegrees,
info_->rightEye->fieldOfView->upDegrees,
};
return std::pair<FovRectangle, FovRectangle>(left, right);
}
float GraphicsDelegateWin::GetZNear() {
return kZNear;
}
namespace {
CameraModel CameraModelViewProjFromVREyeParameters(
const device::mojom::VREyeParametersPtr& eye_params,
gfx::Transform head_from_world) {
CameraModel model = {};
gfx::Transform eye_from_head;
// We have offsets of the eyes in head space, so invert the translation to
// calculate the transform from head space to eye space. For example,
// (0, 0, 0) in head space is (-offset.x, -offset.y, -offset.z) in eye space,
// and (offset.x, offset.y, offset.z) in head space is (0, 0, 0) in eye space.
eye_from_head.Translate3d(-eye_params->offset[0], -eye_params->offset[1],
-eye_params->offset[2]);
model.view_matrix = eye_from_head * head_from_world;
float up_tan =
tanf(eye_params->fieldOfView->upDegrees * base::kPiFloat / 180.0);
float left_tan =
tanf(eye_params->fieldOfView->leftDegrees * base::kPiFloat / 180.0);
float right_tan =
tanf(eye_params->fieldOfView->rightDegrees * base::kPiFloat / 180.0);
float down_tan =
tanf(eye_params->fieldOfView->downDegrees * base::kPiFloat / 180.0);
float x_scale = 2.0f / (left_tan + right_tan);
float y_scale = 2.0f / (up_tan + down_tan);
// clang-format off
model.proj_matrix =
gfx::Transform(x_scale, 0, -((left_tan - right_tan) * x_scale * 0.5), 0,
0, y_scale, ((up_tan - down_tan) * y_scale * 0.5), 0,
0, 0, (kZFar + kZNear) / (kZNear - kZFar),
2 * kZFar * kZNear / (kZNear - kZFar),
0, 0, -1, 0);
// clang-format on
model.view_proj_matrix = model.proj_matrix * model.view_matrix;
return model;
}
} // namespace
RenderInfo GraphicsDelegateWin::GetRenderInfo(FrameType frame_type,
const gfx::Transform& head_pose) {
RenderInfo info;
info.head_pose = head_pose;
CameraModel left =
CameraModelViewProjFromVREyeParameters(info_->leftEye, head_pose);
left.eye_type = kLeftEye;
left.viewport = gfx::Rect(0, 0, info_->leftEye->renderWidth,
info_->leftEye->renderHeight);
info.left_eye_model = left;
CameraModel right =
CameraModelViewProjFromVREyeParameters(info_->rightEye, head_pose);
right.eye_type = kRightEye;
right.viewport =
gfx::Rect(info_->leftEye->renderWidth, 0, info_->rightEye->renderWidth,
info_->rightEye->renderHeight);
info.right_eye_model = right;
cached_info_ = info;
return info;
}
RenderInfo GraphicsDelegateWin::GetOptimizedRenderInfoForFovs(
const FovRectangles& fovs) {
RenderInfo info = cached_info_;
// TODO(billorr): consider optimizing overlays to save texture size.
// For now, we use a full-size texture when we could get by with less.
return info;
}
void GraphicsDelegateWin::InitializeBuffers() {
// No-op since we intiailize buffers elsewhere.
}
void GraphicsDelegateWin::PrepareBufferForWebXr() {
// Desktop doesn't render WebXR through the browser renderer.
DCHECK(prepared_drawing_buffer_ == DrawingBufferMode::kNone);
prepared_drawing_buffer_ = DrawingBufferMode::kWebXr;
}
void GraphicsDelegateWin::PrepareBufferForWebXrOverlayElements() {
// No-op. We reuse the same buffer for overlays and other content, which is
// intialized in PreRender.
DCHECK(prepared_drawing_buffer_ == DrawingBufferMode::kNone);
prepared_drawing_buffer_ = DrawingBufferMode::kWebXrOverlayElements;
}
void GraphicsDelegateWin::PrepareBufferForContentQuadLayer(
const gfx::Transform& quad_transform) {
// Content quad is never ready - unused on desktop.
NOTREACHED();
}
void GraphicsDelegateWin::PrepareBufferForBrowserUi() {
gl_->ClearColor(0, 1, 0, 1);
gl_->Clear(GL_COLOR_BUFFER_BIT);
DCHECK(prepared_drawing_buffer_ == DrawingBufferMode::kNone);
prepared_drawing_buffer_ = DrawingBufferMode::kBrowserUi;
}
void GraphicsDelegateWin::OnFinishedDrawingBuffer() {
DCHECK(prepared_drawing_buffer_ != DrawingBufferMode::kNone);
prepared_drawing_buffer_ = DrawingBufferMode::kNone;
}
void GraphicsDelegateWin::GetWebXrDrawParams(int* texture_id,
Transform* uv_transform) {
// Reporting a texture_id of 0 will skip texture copies.
*texture_id = 0;
}
bool GraphicsDelegateWin::IsContentQuadReady() {
// The content quad is not used. If we integrate with the views framework at
// some point, we may return true later.
return false;
}
void GraphicsDelegateWin::ResumeContentRendering() {
NOTREACHED();
}
void GraphicsDelegateWin::BufferBoundsChanged(
const gfx::Size& content_buffer_size,
const gfx::Size& overlay_buffer_size) {
// Allows the browser to specify size, but we just use headset default always.
NOTREACHED();
}
void GraphicsDelegateWin::GetContentQuadDrawParams(Transform* uv_transform,
float* border_x,
float* border_y) {
NOTREACHED();
}
int GraphicsDelegateWin::GetContentBufferWidth() {
// Called when rendering AlertDialogs, which we don't do.
NOTREACHED();
return 0;
}
// These methods return true when succeeded.
bool GraphicsDelegateWin::Initialize(
const scoped_refptr<gl::GLSurface>& surface) {
// Commandbuffer intialization is split between the main thread and the render
// thread. Additionally, it can be async, so we can't really do intialization
// here - instead, we are initalized earlier.
NOTREACHED();
return false;
}
bool GraphicsDelegateWin::RunInSkiaContext(base::OnceClosure callback) {
// TODO(billorr): Support multiple contexts in a share group. For now just
// share one context.
std::move(callback).Run();
return true;
}
void GraphicsDelegateWin::SetFrameDumpFilepathBase(std::string& filepath_base) {
// We don't support saving filepaths currently.
// TODO(billorr): Support this if/when needed for tests.
}
} // namespace vr