| // Copyright 2017 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 "ash/components/fast_ink/fast_ink_view.h" |
| |
| #include <GLES2/gl2.h> |
| #include <GLES2/gl2ext.h> |
| #include <GLES2/gl2extchromium.h> |
| |
| #include <memory> |
| |
| #include "base/threading/thread_task_runner_handle.h" |
| #include "cc/base/math_util.h" |
| #include "cc/trees/layer_tree_frame_sink.h" |
| #include "cc/trees/layer_tree_frame_sink_client.h" |
| #include "components/viz/common/gpu/context_provider.h" |
| #include "components/viz/common/hit_test/hit_test_region_list.h" |
| #include "components/viz/common/quads/compositor_frame.h" |
| #include "components/viz/common/quads/texture_draw_quad.h" |
| #include "gpu/command_buffer/client/gles2_interface.h" |
| #include "gpu/command_buffer/client/gpu_memory_buffer_manager.h" |
| #include "ui/aura/env.h" |
| #include "ui/aura/window.h" |
| #include "ui/aura/window_observer.h" |
| #include "ui/aura/window_tree_host.h" |
| #include "ui/base/layout.h" |
| #include "ui/gfx/geometry/dip_util.h" |
| #include "ui/gfx/geometry/rect.h" |
| #include "ui/gfx/gpu_memory_buffer.h" |
| #include "ui/views/widget/widget.h" |
| |
| namespace fast_ink { |
| namespace { |
| |
| gfx::Rect BufferRectFromScreenRect( |
| const gfx::Transform& screen_to_buffer_transform, |
| const gfx::Size& buffer_size, |
| const gfx::Rect& screen_rect) { |
| gfx::Rect buffer_rect = cc::MathUtil::MapEnclosingClippedRect( |
| screen_to_buffer_transform, screen_rect); |
| buffer_rect.Intersect(gfx::Rect(buffer_size)); |
| return buffer_rect; |
| } |
| |
| } // namespace |
| |
| FastInkView::ScopedPaint::ScopedPaint( |
| gfx::GpuMemoryBuffer* gpu_memory_buffer, |
| const gfx::Transform& screen_to_buffer_transform, |
| const gfx::Rect& screen_rect) |
| : gpu_memory_buffer_(gpu_memory_buffer), |
| buffer_rect_(BufferRectFromScreenRect(screen_to_buffer_transform, |
| gpu_memory_buffer->GetSize(), |
| screen_rect)), |
| canvas_(buffer_rect_.size(), 1.0f, false) { |
| canvas_.Translate(-buffer_rect_.OffsetFromOrigin()); |
| canvas_.Transform(screen_to_buffer_transform); |
| } |
| |
| FastInkView::ScopedPaint::~ScopedPaint() { |
| if (buffer_rect_.IsEmpty()) |
| return; |
| |
| { |
| TRACE_EVENT0("ui", "FastInkView::ScopedPaint::Map"); |
| |
| if (!gpu_memory_buffer_->Map()) { |
| LOG(ERROR) << "Failed to map GPU memory buffer"; |
| return; |
| } |
| } |
| |
| // Copy result to GPU memory buffer. This is effectively a memcpy and unlike |
| // drawing to the buffer directly this ensures that the buffer is never in a |
| // state that would result in flicker. |
| { |
| TRACE_EVENT1("ui", "FastInkView::ScopedPaint::Copy", "buffer_rect", |
| buffer_rect_.ToString()); |
| |
| uint8_t* data = static_cast<uint8_t*>(gpu_memory_buffer_->memory(0)); |
| int stride = gpu_memory_buffer_->stride(0); |
| canvas_.GetBitmap().readPixels( |
| SkImageInfo::MakeN32Premul(buffer_rect_.width(), buffer_rect_.height()), |
| data + buffer_rect_.y() * stride + buffer_rect_.x() * 4, stride, 0, 0); |
| } |
| |
| { |
| TRACE_EVENT0("ui", "FastInkView::UpdateBuffer::Unmap"); |
| |
| // Unmap to flush writes to buffer. |
| gpu_memory_buffer_->Unmap(); |
| } |
| } |
| |
| struct FastInkView::Resource { |
| Resource() = default; |
| ~Resource() { |
| // context_provider might be null in unit tests when ran with --mash |
| // TODO(kaznacheev) Have MASH provide a context provider for tests |
| // when https://crbug/772562 is fixed |
| if (!context_provider) |
| return; |
| gpu::gles2::GLES2Interface* gles2 = context_provider->ContextGL(); |
| if (sync_token.HasData()) |
| gles2->WaitSyncTokenCHROMIUM(sync_token.GetConstData()); |
| if (texture) |
| gles2->DeleteTextures(1, &texture); |
| if (image) |
| gles2->DestroyImageCHROMIUM(image); |
| } |
| scoped_refptr<viz::ContextProvider> context_provider; |
| uint32_t texture = 0; |
| uint32_t image = 0; |
| gpu::Mailbox mailbox; |
| gpu::SyncToken sync_token; |
| bool damaged = true; |
| }; |
| |
| class FastInkView::LayerTreeFrameSinkHolder |
| : public cc::LayerTreeFrameSinkClient, |
| public aura::WindowObserver { |
| public: |
| LayerTreeFrameSinkHolder(FastInkView* view, |
| std::unique_ptr<cc::LayerTreeFrameSink> frame_sink) |
| : view_(view), frame_sink_(std::move(frame_sink)) { |
| frame_sink_->BindToClient(this); |
| } |
| ~LayerTreeFrameSinkHolder() override { |
| if (frame_sink_) |
| frame_sink_->DetachFromClient(); |
| if (root_window_) |
| root_window_->RemoveObserver(this); |
| } |
| |
| // Delete frame sink after having reclaimed all exported resources. |
| // TODO(reveman): Find a better way to handle deletion of in-flight resources. |
| // https://crbug.com/765763 |
| static void DeleteWhenLastResourceHasBeenReclaimed( |
| std::unique_ptr<LayerTreeFrameSinkHolder> holder) { |
| if (holder->last_frame_size_in_pixels_.IsEmpty()) { |
| // Delete sink holder immediately if no frame has been submitted. |
| DCHECK(holder->exported_resources_.empty()); |
| return; |
| } |
| |
| // Submit an empty frame to ensure that pending release callbacks will be |
| // processed in a finite amount of time. |
| viz::CompositorFrame frame; |
| frame.metadata.begin_frame_ack.source_id = |
| viz::BeginFrameArgs::kManualSourceId; |
| frame.metadata.begin_frame_ack.sequence_number = |
| viz::BeginFrameArgs::kStartingFrameNumber; |
| frame.metadata.begin_frame_ack.has_damage = true; |
| frame.metadata.device_scale_factor = |
| holder->last_frame_device_scale_factor_; |
| std::unique_ptr<viz::RenderPass> pass = viz::RenderPass::Create(); |
| pass->SetNew(1, gfx::Rect(holder->last_frame_size_in_pixels_), |
| gfx::Rect(holder->last_frame_size_in_pixels_), |
| gfx::Transform()); |
| frame.render_pass_list.push_back(std::move(pass)); |
| holder->frame_sink_->SubmitCompositorFrame(std::move(frame)); |
| |
| // Delete sink holder immediately if not waiting for exported resources to |
| // be reclaimed. |
| if (holder->exported_resources_.empty()) |
| return; |
| |
| // Delete sink holder immediately if native window is already gone. |
| aura::Window* window = holder->view_->GetWidget()->GetNativeView(); |
| if (!window) |
| return; |
| |
| aura::Window* root_window = window->GetRootWindow(); |
| holder->root_window_ = root_window; |
| holder->view_ = nullptr; |
| |
| // If we have exported resources to reclaim then extend the lifetime of |
| // holder by adding it as a root window observer. The holder will delete |
| // itself when the root window is removed or when all exported resources |
| // have been reclaimed. |
| root_window->AddObserver(holder.release()); |
| } |
| |
| void SubmitCompositorFrame(viz::CompositorFrame frame, |
| viz::ResourceId resource_id, |
| std::unique_ptr<Resource> resource) { |
| exported_resources_[resource_id] = std::move(resource); |
| last_frame_size_in_pixels_ = frame.size_in_pixels(); |
| last_frame_device_scale_factor_ = frame.metadata.device_scale_factor; |
| frame_sink_->SubmitCompositorFrame(std::move(frame)); |
| } |
| |
| void DamageExportedResources() { |
| for (auto& entry : exported_resources_) |
| entry.second->damaged = true; |
| } |
| |
| // Overridden from cc::LayerTreeFrameSinkClient: |
| void SetBeginFrameSource(viz::BeginFrameSource* source) override {} |
| base::Optional<viz::HitTestRegionList> BuildHitTestData() override { |
| return {}; |
| } |
| void ReclaimResources( |
| const std::vector<viz::ReturnedResource>& resources) override { |
| for (auto& entry : resources) { |
| auto it = exported_resources_.find(entry.id); |
| DCHECK(it != exported_resources_.end()); |
| std::unique_ptr<Resource> resource = std::move(it->second); |
| exported_resources_.erase(it); |
| resource->sync_token = entry.sync_token; |
| if (view_ && !entry.lost) |
| view_->ReclaimResource(std::move(resource)); |
| } |
| |
| if (root_window_ && exported_resources_.empty()) |
| ScheduleDelete(); |
| } |
| void SetTreeActivationCallback(const base::Closure& callback) override {} |
| void DidReceiveCompositorFrameAck() override { |
| if (view_) |
| view_->DidReceiveCompositorFrameAck(); |
| } |
| void DidPresentCompositorFrame( |
| uint32_t presentation_token, |
| const gfx::PresentationFeedback& feedback) override { |
| if (view_) |
| view_->DidPresentCompositorFrame(feedback); |
| } |
| void DidLoseLayerTreeFrameSink() override { |
| exported_resources_.clear(); |
| if (root_window_) |
| ScheduleDelete(); |
| } |
| void OnDraw(const gfx::Transform& transform, |
| const gfx::Rect& viewport, |
| bool resourceless_software_draw, |
| bool skip_draw) override {} |
| void SetMemoryPolicy(const cc::ManagedMemoryPolicy& policy) override {} |
| void SetExternalTilePriorityConstraints( |
| const gfx::Rect& viewport_rect, |
| const gfx::Transform& transform) override {} |
| |
| // Overridden from aura::WindowObserver: |
| void OnWindowDestroying(aura::Window* window) override { |
| root_window_->RemoveObserver(this); |
| root_window_ = nullptr; |
| // Make sure frame sink never outlives aura. |
| frame_sink_->DetachFromClient(); |
| frame_sink_.reset(); |
| ScheduleDelete(); |
| } |
| |
| private: |
| void ScheduleDelete() { |
| if (delete_pending_) |
| return; |
| delete_pending_ = true; |
| base::ThreadTaskRunnerHandle::Get()->DeleteSoon(FROM_HERE, this); |
| } |
| |
| FastInkView* view_; |
| std::unique_ptr<cc::LayerTreeFrameSink> frame_sink_; |
| base::flat_map<viz::ResourceId, std::unique_ptr<Resource>> |
| exported_resources_; |
| gfx::Size last_frame_size_in_pixels_; |
| float last_frame_device_scale_factor_ = 1.0f; |
| aura::Window* root_window_ = nullptr; |
| bool delete_pending_ = false; |
| |
| DISALLOW_COPY_AND_ASSIGN(LayerTreeFrameSinkHolder); |
| }; |
| |
| FastInkView::FastInkView(aura::Window* container, |
| const PresentationCallback& presentation_callback) |
| : presentation_callback_(presentation_callback), weak_ptr_factory_(this) { |
| widget_.reset(new views::Widget); |
| views::Widget::InitParams params; |
| params.type = views::Widget::InitParams::TYPE_WINDOW_FRAMELESS; |
| params.name = "FastInkOverlay"; |
| params.accept_events = false; |
| params.activatable = views::Widget::InitParams::ACTIVATABLE_NO; |
| params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; |
| params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW; |
| params.parent = container; |
| params.layer_type = ui::LAYER_SOLID_COLOR; |
| |
| gfx::Rect screen_bounds = container->GetRootWindow()->GetBoundsInScreen(); |
| widget_->Init(params); |
| widget_->Show(); |
| widget_->SetContentsView(this); |
| widget_->SetBounds(screen_bounds); |
| set_owned_by_client(); |
| |
| // Take the root transform and apply this during buffer update instead of |
| // leaving this up to the compositor. The benefit is that HW requirements |
| // for being able to take advantage of overlays and direct scanout are |
| // reduced significantly. Frames are submitted to the compositor with the |
| // inverse transform to cancel out the transformation that would otherwise |
| // be done by the compositor. |
| screen_to_buffer_transform_ = |
| widget_->GetNativeWindow()->GetHost()->GetRootTransform(); |
| |
| buffer_size_ = gfx::ToEnclosedRect(cc::MathUtil::MapClippedRect( |
| screen_to_buffer_transform_, |
| gfx::RectF(screen_bounds.width(), |
| screen_bounds.height()))) |
| .size(); |
| |
| // Create a single GPU memory buffer. Content will be written into this |
| // buffer without any buffering. The result is that we might be modifying |
| // the buffer while it's being displayed. This provides minimal latency |
| // but with potential tearing. Note that we have to draw into a temporary |
| // surface and copy it into GPU memory buffer to avoid flicker. |
| gpu_memory_buffer_ = |
| widget_->GetNativeWindow() |
| ->env() |
| ->context_factory() |
| ->GetGpuMemoryBufferManager() |
| ->CreateGpuMemoryBuffer(buffer_size_, |
| SK_B32_SHIFT ? gfx::BufferFormat::RGBA_8888 |
| : gfx::BufferFormat::BGRA_8888, |
| gfx::BufferUsage::SCANOUT_CPU_READ_WRITE, |
| gpu::kNullSurfaceHandle); |
| LOG_IF(ERROR, !gpu_memory_buffer_) << "Failed to create GPU memory buffer"; |
| |
| frame_sink_holder_ = std::make_unique<LayerTreeFrameSinkHolder>( |
| this, widget_->GetNativeView()->CreateLayerTreeFrameSink()); |
| } |
| |
| FastInkView::~FastInkView() { |
| LayerTreeFrameSinkHolder::DeleteWhenLastResourceHasBeenReclaimed( |
| std::move(frame_sink_holder_)); |
| } |
| |
| void FastInkView::UpdateSurface(const gfx::Rect& content_rect, |
| const gfx::Rect& damage_rect, |
| bool auto_refresh) { |
| content_rect_ = content_rect; |
| damage_rect_.Union(damage_rect); |
| auto_refresh_ = auto_refresh; |
| pending_compositor_frame_ = true; |
| |
| if (!damage_rect.IsEmpty()) { |
| frame_sink_holder_->DamageExportedResources(); |
| for (auto& resource : returned_resources_) |
| resource->damaged = true; |
| } |
| |
| if (!pending_compositor_frame_ack_) |
| SubmitCompositorFrame(); |
| } |
| |
| void FastInkView::SubmitCompositorFrame() { |
| TRACE_EVENT1("ui", "FastInkView::SubmitCompositorFrame", "damage", |
| damage_rect_.ToString()); |
| |
| float device_scale_factor = widget_->GetLayer()->device_scale_factor(); |
| gfx::Rect output_rect(gfx::ConvertSizeToPixel( |
| device_scale_factor, |
| widget_->GetNativeView()->GetBoundsInScreen().size())); |
| |
| gfx::Rect quad_rect; |
| gfx::Rect damage_rect; |
| // Continuously redraw the full output rectangle when in auto-refresh mode. |
| // This is necessary in order to allow single buffered updates without having |
| // buffer changes outside the contents area cause artifacts. |
| if (auto_refresh_) { |
| quad_rect = gfx::Rect(buffer_size_); |
| damage_rect = gfx::Rect(output_rect); |
| } else { |
| // Use minimal quad and damage rectangles when auto-refresh mode is off. |
| quad_rect = BufferRectFromScreenRect(screen_to_buffer_transform_, |
| buffer_size_, content_rect_); |
| damage_rect = gfx::ConvertRectToPixel(device_scale_factor, damage_rect_); |
| damage_rect.Intersect(output_rect); |
| pending_compositor_frame_ = false; |
| } |
| damage_rect_ = gfx::Rect(); |
| |
| std::unique_ptr<Resource> resource; |
| // Reuse returned resource if available. |
| if (!returned_resources_.empty()) { |
| resource = std::move(returned_resources_.back()); |
| returned_resources_.pop_back(); |
| } |
| |
| // Create new resource if needed. |
| if (!resource) |
| resource = std::make_unique<Resource>(); |
| |
| if (resource->damaged) { |
| // Acquire context provider for resource if needed. |
| // Note: We make no attempts to recover if the context provider is later |
| // lost. It is expected that this class is short-lived and requiring a |
| // new instance to be created in lost context situations is acceptable and |
| // keeps the code simple. |
| if (!resource->context_provider) { |
| resource->context_provider = widget_->GetNativeWindow() |
| ->env() |
| ->context_factory() |
| ->SharedMainThreadContextProvider(); |
| if (!resource->context_provider) { |
| LOG(ERROR) << "Failed to acquire a context provider"; |
| return; |
| } |
| } |
| |
| gpu::gles2::GLES2Interface* gles2 = resource->context_provider->ContextGL(); |
| |
| if (resource->sync_token.HasData()) { |
| gles2->WaitSyncTokenCHROMIUM(resource->sync_token.GetConstData()); |
| resource->sync_token = gpu::SyncToken(); |
| } |
| |
| if (resource->texture) { |
| gles2->ActiveTexture(GL_TEXTURE0); |
| gles2->BindTexture(GL_TEXTURE_2D, resource->texture); |
| } else { |
| gles2->GenTextures(1, &resource->texture); |
| gles2->ActiveTexture(GL_TEXTURE0); |
| gles2->BindTexture(GL_TEXTURE_2D, resource->texture); |
| gles2->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); |
| gles2->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); |
| gles2->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); |
| gles2->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); |
| gles2->ProduceTextureDirectCHROMIUM(resource->texture, |
| resource->mailbox.name); |
| } |
| |
| if (resource->image) { |
| gles2->ReleaseTexImage2DCHROMIUM(GL_TEXTURE_2D, resource->image); |
| } else { |
| resource->image = gles2->CreateImageCHROMIUM( |
| gpu_memory_buffer_->AsClientBuffer(), buffer_size_.width(), |
| buffer_size_.height(), SK_B32_SHIFT ? GL_RGBA : GL_BGRA_EXT); |
| if (!resource->image) { |
| LOG(ERROR) << "Failed to create image"; |
| return; |
| } |
| } |
| gles2->BindTexImage2DCHROMIUM(GL_TEXTURE_2D, resource->image); |
| gles2->GenSyncTokenCHROMIUM(resource->sync_token.GetData()); |
| |
| resource->damaged = false; |
| } |
| |
| viz::TransferableResource transferable_resource; |
| transferable_resource.id = next_resource_id_++; |
| transferable_resource.format = viz::RGBA_8888; |
| transferable_resource.filter = GL_LINEAR; |
| transferable_resource.size = buffer_size_; |
| transferable_resource.mailbox_holder = gpu::MailboxHolder( |
| resource->mailbox, resource->sync_token, GL_TEXTURE_2D); |
| // Use HW overlay if continuous updates are expected. |
| transferable_resource.is_overlay_candidate = auto_refresh_; |
| |
| gfx::Transform target_to_buffer_transform(screen_to_buffer_transform_); |
| target_to_buffer_transform.Scale(1.f / device_scale_factor, |
| 1.f / device_scale_factor); |
| |
| gfx::Transform buffer_to_target_transform; |
| bool rv = target_to_buffer_transform.GetInverse(&buffer_to_target_transform); |
| DCHECK(rv); |
| |
| const int kRenderPassId = 1; |
| std::unique_ptr<viz::RenderPass> render_pass = viz::RenderPass::Create(); |
| render_pass->SetNew(kRenderPassId, output_rect, damage_rect, |
| buffer_to_target_transform); |
| |
| viz::SharedQuadState* quad_state = |
| render_pass->CreateAndAppendSharedQuadState(); |
| quad_state->SetAll( |
| buffer_to_target_transform, |
| /*quad_layer_rect=*/output_rect, |
| /*visible_quad_layer_rect=*/output_rect, |
| /*clip_rect=*/gfx::Rect(), |
| /*is_clipped=*/false, /*are_contents_opaque=*/false, /*opacity=*/1.f, |
| /*blend_mode=*/SkBlendMode::kSrcOver, /*sorting_context_id=*/0); |
| |
| viz::CompositorFrame frame; |
| // TODO(eseckler): FastInkView should use BeginFrames and set the ack |
| // accordingly. |
| frame.metadata.begin_frame_ack = |
| viz::BeginFrameAck::CreateManualAckWithDamage(); |
| frame.metadata.device_scale_factor = device_scale_factor; |
| |
| if (!presentation_callback_.is_null()) { |
| // If overflow happens, we increase it again. |
| if (!++presentation_token_) |
| ++presentation_token_; |
| frame.metadata.frame_token = presentation_token_; |
| frame.metadata.request_presentation_feedback = true; |
| } |
| |
| viz::TextureDrawQuad* texture_quad = |
| render_pass->CreateAndAppendDrawQuad<viz::TextureDrawQuad>(); |
| float vertex_opacity[4] = {1.0f, 1.0f, 1.0f, 1.0f}; |
| gfx::RectF uv_crop(quad_rect); |
| uv_crop.Scale(1.f / buffer_size_.width(), 1.f / buffer_size_.height()); |
| texture_quad->SetNew(quad_state, quad_rect, quad_rect, |
| /*needs_blending=*/true, transferable_resource.id, |
| /*premultiplied_alpha=*/true, uv_crop.origin(), |
| uv_crop.bottom_right(), |
| /*background_color=*/SK_ColorTRANSPARENT, vertex_opacity, |
| /*y_flipped=*/false, |
| /*nearest_neighbor=*/false, |
| /*secure_output_only=*/false); |
| texture_quad->set_resource_size_in_pixels(transferable_resource.size); |
| frame.resource_list.push_back(transferable_resource); |
| |
| DCHECK(!pending_compositor_frame_ack_); |
| pending_compositor_frame_ack_ = true; |
| |
| frame.render_pass_list.push_back(std::move(render_pass)); |
| frame_sink_holder_->SubmitCompositorFrame( |
| std::move(frame), transferable_resource.id, std::move(resource)); |
| } |
| |
| void FastInkView::SubmitPendingCompositorFrame() { |
| if (pending_compositor_frame_ && !pending_compositor_frame_ack_) |
| SubmitCompositorFrame(); |
| } |
| |
| void FastInkView::DidReceiveCompositorFrameAck() { |
| pending_compositor_frame_ack_ = false; |
| if (pending_compositor_frame_) { |
| base::ThreadTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, base::BindOnce(&FastInkView::SubmitPendingCompositorFrame, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| } |
| |
| void FastInkView::DidPresentCompositorFrame( |
| const gfx::PresentationFeedback& feedback) { |
| DCHECK(!presentation_callback_.is_null()); |
| presentation_callback_.Run(feedback); |
| } |
| |
| void FastInkView::ReclaimResource(std::unique_ptr<Resource> resource) { |
| returned_resources_.push_back(std::move(resource)); |
| } |
| |
| } // namespace fast_ink |