// Copyright (c) 2012 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/renderer/pepper/pepper_graphics_2d_host.h" #include #include #include "base/bind.h" #include "base/location.h" #include "base/logging.h" #include "base/numerics/checked_math.h" #include "base/single_thread_task_runner.h" #include "base/stl_util.h" #include "base/threading/thread_task_runner_handle.h" #include "base/trace_event/trace_event.h" #include "build/build_config.h" #include "cc/paint/paint_flags.h" #include "components/viz/client/client_shared_bitmap_manager.h" #include "components/viz/common/gpu/context_provider.h" #include "components/viz/common/quads/shared_bitmap.h" #include "content/public/renderer/render_thread.h" #include "content/public/renderer/renderer_ppapi_host.h" #include "content/renderer/pepper/gfx_conversion.h" #include "content/renderer/pepper/pepper_plugin_instance_impl.h" #include "content/renderer/pepper/plugin_instance_throttler_impl.h" #include "content/renderer/pepper/ppb_image_data_impl.h" #include "content/renderer/render_thread_impl.h" #include "gpu/GLES2/gl2extchromium.h" #include "gpu/command_buffer/client/gles2_interface.h" #include "gpu/command_buffer/common/capabilities.h" #include "gpu/command_buffer/common/gpu_memory_buffer_support.h" #include "gpu/ipc/common/gpu_memory_buffer_support.h" #include "ppapi/c/pp_bool.h" #include "ppapi/c/pp_errors.h" #include "ppapi/c/pp_rect.h" #include "ppapi/c/pp_resource.h" #include "ppapi/host/dispatch_host_message.h" #include "ppapi/host/host_message_context.h" #include "ppapi/host/ppapi_host.h" #include "ppapi/proxy/ppapi_messages.h" #include "ppapi/shared_impl/ppb_view_shared.h" #include "ppapi/thunk/enter.h" #include "services/ui/public/cpp/gpu/context_provider_command_buffer.h" #include "skia/ext/platform_canvas.h" #include "third_party/khronos/GLES2/gl2.h" #include "third_party/khronos/GLES2/gl2ext.h" #include "third_party/skia/include/core/SkBitmap.h" #include "third_party/skia/include/core/SkSwizzle.h" #include "ui/gfx/blit.h" #include "ui/gfx/geometry/point_conversions.h" #include "ui/gfx/geometry/rect.h" #include "ui/gfx/geometry/rect_conversions.h" #include "ui/gfx/geometry/size_conversions.h" #include "ui/gfx/scoped_ns_graphics_context_save_gstate_mac.h" #include "ui/gfx/skia_util.h" #if defined(OS_MACOSX) #include "base/mac/scoped_cftyperef.h" #endif using ppapi::thunk::EnterResourceNoLock; using ppapi::thunk::PPB_ImageData_API; namespace content { namespace { const int64_t kOffscreenCallbackDelayMs = 1000 / 30; // 30 fps // Converts a rect inside an image of the given dimensions. The rect may be // NULL to indicate it should be the entire image. If the rect is outside of // the image, this will do nothing and return false. bool ValidateAndConvertRect(const PP_Rect* rect, int image_width, int image_height, gfx::Rect* dest) { if (!rect) { // Use the entire image area. *dest = gfx::Rect(0, 0, image_width, image_height); } else { // Validate the passed-in area. if (rect->point.x < 0 || rect->point.y < 0 || rect->size.width <= 0 || rect->size.height <= 0) return false; // Check the max bounds, being careful of overflow. if (static_cast(rect->point.x) + static_cast(rect->size.width) > static_cast(image_width)) return false; if (static_cast(rect->point.y) + static_cast(rect->size.height) > static_cast(image_height)) return false; *dest = gfx::Rect( rect->point.x, rect->point.y, rect->size.width, rect->size.height); } return true; } // Converts ImageData from PP_IMAGEDATAFORMAT_BGRA_PREMUL to // PP_IMAGEDATAFORMAT_RGBA_PREMUL, or reverse. It's assumed that the // destination image is always mapped (so will have non-NULL data). void ConvertImageData(PPB_ImageData_Impl* src_image, const SkIRect& src_rect, PPB_ImageData_Impl* dest_image, const SkRect& dest_rect) { ImageDataAutoMapper auto_mapper(src_image); DCHECK(src_image->format() != dest_image->format()); DCHECK(PPB_ImageData_Impl::IsImageDataFormatSupported(src_image->format())); DCHECK(PPB_ImageData_Impl::IsImageDataFormatSupported(dest_image->format())); SkBitmap src_bitmap(src_image->GetMappedBitmap()); SkBitmap dest_bitmap(dest_image->GetMappedBitmap()); if (src_rect.width() == src_image->width() && dest_rect.width() == dest_image->width()) { // Fast path if the full frame can be converted at once. SkSwapRB( dest_bitmap.getAddr32(static_cast(dest_rect.fLeft), static_cast(dest_rect.fTop)), src_bitmap.getAddr32(static_cast(src_rect.fLeft), static_cast(src_rect.fTop)), src_rect.width() * src_rect.height()); } else { // Slow path where we convert line by line. for (int y = 0; y < src_rect.height(); y++) { SkSwapRB( dest_bitmap.getAddr32(static_cast(dest_rect.fLeft), static_cast(dest_rect.fTop + y)), src_bitmap.getAddr32(static_cast(src_rect.fLeft), static_cast(src_rect.fTop + y)), src_rect.width()); } } } } // namespace struct PepperGraphics2DHost::QueuedOperation { enum Type { PAINT, SCROLL, REPLACE, TRANSFORM }; QueuedOperation(Type t) : type(t), paint_x(0), paint_y(0), scroll_dx(0), scroll_dy(0) {} Type type; // Valid when type == PAINT. scoped_refptr paint_image; int paint_x, paint_y; gfx::Rect paint_src_rect; // Valid when type == SCROLL. gfx::Rect scroll_clip_rect; int scroll_dx, scroll_dy; // Valid when type == REPLACE. scoped_refptr replace_image; // Valid when type == TRANSFORM float scale; gfx::PointF translation; }; // static PepperGraphics2DHost* PepperGraphics2DHost::Create( RendererPpapiHost* host, PP_Instance instance, PP_Resource resource, const PP_Size& size, PP_Bool is_always_opaque, scoped_refptr backing_store) { PepperGraphics2DHost* resource_host = new PepperGraphics2DHost(host, instance, resource); if (!resource_host->Init(size.width, size.height, PP_ToBool(is_always_opaque), backing_store)) { delete resource_host; return nullptr; } return resource_host; } PepperGraphics2DHost::PepperGraphics2DHost(RendererPpapiHost* host, PP_Instance instance, PP_Resource resource) : ResourceHost(host->GetPpapiHost(), instance, resource), renderer_ppapi_host_(host), bound_instance_(nullptr), need_flush_ack_(false), offscreen_flush_pending_(false), is_always_opaque_(false), scale_(1.0f), is_running_in_process_(host->IsRunningInProcess()) {} PepperGraphics2DHost::~PepperGraphics2DHost() { // Delete textures owned by PepperGraphics2DHost, but not those sent to the // compositor, since those will be deleted by ReleaseTextureCallback() when it // runs. while (main_thread_context_ && !recycled_texture_copies_.empty()) { uint32_t texture_id = recycled_texture_copies_.back().id; main_thread_context_->ContextGL()->DeleteTextures(1, &texture_id); recycled_texture_copies_.pop_back(); } // Unbind from the instance when destroyed if we're still bound. if (bound_instance_) bound_instance_->BindGraphics(bound_instance_->pp_instance(), 0); } bool PepperGraphics2DHost::Init( int width, int height, bool is_always_opaque, scoped_refptr backing_store) { // The underlying PPB_ImageData_Impl will validate the dimensions. image_data_ = backing_store; if (!image_data_->Init(PPB_ImageData_Impl::GetNativeImageDataFormat(), width, height, true) || !image_data_->Map()) { image_data_ = nullptr; return false; } is_always_opaque_ = is_always_opaque; scale_ = 1.0f; return true; } int32_t PepperGraphics2DHost::OnResourceMessageReceived( const IPC::Message& msg, ppapi::host::HostMessageContext* context) { PPAPI_BEGIN_MESSAGE_MAP(PepperGraphics2DHost, msg) PPAPI_DISPATCH_HOST_RESOURCE_CALL(PpapiHostMsg_Graphics2D_PaintImageData, OnHostMsgPaintImageData) PPAPI_DISPATCH_HOST_RESOURCE_CALL(PpapiHostMsg_Graphics2D_Scroll, OnHostMsgScroll) PPAPI_DISPATCH_HOST_RESOURCE_CALL(PpapiHostMsg_Graphics2D_ReplaceContents, OnHostMsgReplaceContents) PPAPI_DISPATCH_HOST_RESOURCE_CALL_0(PpapiHostMsg_Graphics2D_Flush, OnHostMsgFlush) PPAPI_DISPATCH_HOST_RESOURCE_CALL(PpapiHostMsg_Graphics2D_SetScale, OnHostMsgSetScale) PPAPI_DISPATCH_HOST_RESOURCE_CALL(PpapiHostMsg_Graphics2D_SetLayerTransform, OnHostMsgSetLayerTransform) PPAPI_DISPATCH_HOST_RESOURCE_CALL(PpapiHostMsg_Graphics2D_ReadImageData, OnHostMsgReadImageData) PPAPI_END_MESSAGE_MAP() return PP_ERROR_FAILED; } bool PepperGraphics2DHost::IsGraphics2DHost() { return true; } bool PepperGraphics2DHost::ReadImageData(PP_Resource image, const PP_Point* top_left) { // Get and validate the image object to paint into. EnterResourceNoLock enter(image, true); if (enter.failed()) return false; PPB_ImageData_Impl* image_resource = static_cast(enter.object()); if (!PPB_ImageData_Impl::IsImageDataFormatSupported(image_resource->format())) return false; // Must be in the right format. // Validate the bitmap position. int x = top_left->x; if (x < 0 || static_cast(x) + static_cast(image_resource->width()) > image_data_->width()) return false; int y = top_left->y; if (y < 0 || static_cast(y) + static_cast(image_resource->height()) > image_data_->height()) return false; ImageDataAutoMapper auto_mapper(image_resource); if (!auto_mapper.is_valid()) return false; SkIRect src_irect = {x, y, x + image_resource->width(), y + image_resource->height()}; SkRect dest_rect = {SkIntToScalar(0), SkIntToScalar(0), SkIntToScalar(image_resource->width()), SkIntToScalar(image_resource->height())}; if (image_resource->format() != image_data_->format()) { // Convert the image data if the format does not match. ConvertImageData(image_data_.get(), src_irect, image_resource, dest_rect); } else { SkCanvas* dest_canvas = image_resource->GetCanvas(); // We want to replace the contents of the bitmap rather than blend. SkPaint paint; paint.setBlendMode(SkBlendMode::kSrc); dest_canvas->drawBitmapRect( image_data_->GetMappedBitmap(), src_irect, dest_rect, &paint); } return true; } bool PepperGraphics2DHost::BindToInstance( PepperPluginInstanceImpl* new_instance) { if (new_instance && new_instance->pp_instance() != pp_instance()) return false; // Can't bind other instance's contexts. if (bound_instance_ == new_instance) return true; // Rebinding the same device, nothing to do. if (bound_instance_ && new_instance) return false; // Can't change a bound device. if (!new_instance) { // When the device is detached, we'll not get any more paint callbacks so // we need to clear the list, but we still want to issue any pending // callbacks to the plugin. if (need_flush_ack_) ScheduleOffscreenFlushAck(); } else { // Devices being replaced, redraw the plugin. new_instance->InvalidateRect(gfx::Rect()); } cached_bitmap_.reset(); composited_output_modified_ = true; bound_instance_ = new_instance; return true; } // The |backing_bitmap| must be clipped to the |plugin_rect| to avoid painting // outside the plugin area. This can happen if the plugin has been resized since // PaintImageData verified the image is within the plugin size. void PepperGraphics2DHost::Paint(blink::WebCanvas* canvas, const gfx::Rect& plugin_rect, const gfx::Rect& paint_rect) { TRACE_EVENT0("pepper", "PepperGraphics2DHost::Paint"); ImageDataAutoMapper auto_mapper(image_data_.get()); SkBitmap backing_bitmap = image_data_->GetMappedBitmap(); gfx::Rect invalidate_rect = plugin_rect; invalidate_rect.Intersect(paint_rect); SkRect sk_invalidate_rect = gfx::RectToSkRect(invalidate_rect); cc::PaintCanvasAutoRestore auto_restore(canvas, true); canvas->clipRect(sk_invalidate_rect); gfx::Size pixel_image_size(image_data_->width(), image_data_->height()); gfx::Size image_size = gfx::ScaleToFlooredSize(pixel_image_size, scale_); PepperPluginInstance* plugin_instance = renderer_ppapi_host_->GetPluginInstance(pp_instance()); if (!plugin_instance) return; if (plugin_instance->IsFullPagePlugin()) { // When we're resizing a window with a full-frame plugin, the plugin may // not yet have bound a new device, which will leave parts of the // background exposed if the window is getting larger. We want this to // show white (typically less jarring) rather than black or uninitialized. // We don't do this for non-full-frame plugins since we specifically want // the page background to show through. cc::PaintCanvasAutoRestore auto_restore(canvas, true); SkRect image_data_rect = gfx::RectToSkRect(gfx::Rect(plugin_rect.origin(), image_size)); canvas->clipRect(image_data_rect, SkClipOp::kDifference); cc::PaintFlags flags; flags.setBlendMode(SkBlendMode::kSrc); flags.setColor(SK_ColorWHITE); canvas->drawRect(sk_invalidate_rect, flags); } cc::PaintFlags flags; if (is_always_opaque_) { // When we know the device is opaque, we can disable blending for slightly // more optimized painting. flags.setBlendMode(SkBlendMode::kSrc); } SkPoint pixel_origin(PointToSkPoint(plugin_rect.origin())); if (scale_ != 1.0f && scale_ > 0.0f) { canvas->scale(scale_, scale_); pixel_origin.scale(1.0f / scale_); } canvas->drawBitmap(backing_bitmap, pixel_origin.x(), pixel_origin.y(), &flags); } void PepperGraphics2DHost::ViewInitiatedPaint() { TRACE_EVENT0("pepper", "PepperGraphics2DHost::ViewInitiatedPaint"); if (need_flush_ack_) { SendFlushAck(); need_flush_ack_ = false; } } float PepperGraphics2DHost::GetScale() const { return scale_; } bool PepperGraphics2DHost::IsAlwaysOpaque() const { return is_always_opaque_; } gfx::Size PepperGraphics2DHost::Size() const { if (!image_data_.get()) return gfx::Size(); return gfx::Size(image_data_->width(), image_data_->height()); } void PepperGraphics2DHost::ClearCache() { cached_bitmap_.reset(); } int32_t PepperGraphics2DHost::OnHostMsgPaintImageData( ppapi::host::HostMessageContext* context, const ppapi::HostResource& image_data, const PP_Point& top_left, bool src_rect_specified, const PP_Rect& src_rect) { EnterResourceNoLock enter(image_data.host_resource(), true); if (enter.failed()) return PP_ERROR_BADRESOURCE; PPB_ImageData_Impl* image_resource = static_cast(enter.object()); QueuedOperation operation(QueuedOperation::PAINT); operation.paint_image = image_resource; if (!ValidateAndConvertRect(src_rect_specified ? &src_rect : nullptr, image_resource->width(), image_resource->height(), &operation.paint_src_rect)) return PP_ERROR_BADARGUMENT; // Validate the bitmap position using the previously-validated rect, there // should be no painted area outside of the image. int64_t x64 = static_cast(top_left.x); int64_t y64 = static_cast(top_left.y); if (x64 + static_cast(operation.paint_src_rect.x()) < 0 || x64 + static_cast(operation.paint_src_rect.right()) > image_data_->width()) return PP_ERROR_BADARGUMENT; if (y64 + static_cast(operation.paint_src_rect.y()) < 0 || y64 + static_cast(operation.paint_src_rect.bottom()) > image_data_->height()) return PP_ERROR_BADARGUMENT; operation.paint_x = top_left.x; operation.paint_y = top_left.y; queued_operations_.push_back(operation); return PP_OK; } int32_t PepperGraphics2DHost::OnHostMsgScroll( ppapi::host::HostMessageContext* context, bool clip_specified, const PP_Rect& clip, const PP_Point& amount) { QueuedOperation operation(QueuedOperation::SCROLL); if (!ValidateAndConvertRect(clip_specified ? &clip : nullptr, image_data_->width(), image_data_->height(), &operation.scroll_clip_rect)) return PP_ERROR_BADARGUMENT; // If we're being asked to scroll by more than the clip rect size, just // ignore this scroll command and say it worked. int32_t dx = amount.x; int32_t dy = amount.y; if (dx <= -image_data_->width() || dx >= image_data_->width() || dy <= -image_data_->height() || dy >= image_data_->height()) return PP_ERROR_BADARGUMENT; operation.scroll_dx = dx; operation.scroll_dy = dy; queued_operations_.push_back(operation); return PP_OK; } int32_t PepperGraphics2DHost::OnHostMsgReplaceContents( ppapi::host::HostMessageContext* context, const ppapi::HostResource& image_data) { EnterResourceNoLock enter(image_data.host_resource(), true); if (enter.failed()) return PP_ERROR_BADRESOURCE; PPB_ImageData_Impl* image_resource = static_cast(enter.object()); if (!PPB_ImageData_Impl::IsImageDataFormatSupported(image_resource->format())) return PP_ERROR_BADARGUMENT; if (image_resource->width() != image_data_->width() || image_resource->height() != image_data_->height()) return PP_ERROR_BADARGUMENT; QueuedOperation operation(QueuedOperation::REPLACE); operation.replace_image = image_resource; queued_operations_.push_back(operation); return PP_OK; } int32_t PepperGraphics2DHost::OnHostMsgFlush( ppapi::host::HostMessageContext* context) { // Don't allow more than one pending flush at a time. if (HasPendingFlush()) return PP_ERROR_INPROGRESS; PP_Resource old_image_data = 0; flush_reply_context_ = context->MakeReplyMessageContext(); if (is_running_in_process_) return Flush(nullptr); // Reuse image data when running out of process. int32_t result = Flush(&old_image_data); if (old_image_data) { // If the Graphics2D has an old image data it's not using any more, send // it back to the plugin for possible re-use. See ppb_image_data_proxy.cc // for a description how this process works. ppapi::HostResource old_image_data_host_resource; old_image_data_host_resource.SetHostResource(pp_instance(), old_image_data); host()->Send(new PpapiMsg_PPBImageData_NotifyUnusedImageData( ppapi::API_ID_PPB_IMAGE_DATA, old_image_data_host_resource)); } return result; } int32_t PepperGraphics2DHost::OnHostMsgSetScale( ppapi::host::HostMessageContext* context, float scale) { if (scale > 0.0f) { scale_ = scale; return PP_OK; } return PP_ERROR_BADARGUMENT; } int32_t PepperGraphics2DHost::OnHostMsgSetLayerTransform( ppapi::host::HostMessageContext* context, float scale, const PP_FloatPoint& translation) { if (scale < 0.0f) return PP_ERROR_BADARGUMENT; QueuedOperation operation(QueuedOperation::TRANSFORM); operation.scale = scale; operation.translation = gfx::PointF(translation.x, translation.y); queued_operations_.push_back(operation); return PP_OK; } int32_t PepperGraphics2DHost::OnHostMsgReadImageData( ppapi::host::HostMessageContext* context, PP_Resource image, const PP_Point& top_left) { context->reply_msg = PpapiPluginMsg_Graphics2D_ReadImageDataAck(); return ReadImageData(image, &top_left) ? PP_OK : PP_ERROR_FAILED; } void PepperGraphics2DHost::ReleaseSoftwareCallback( std::unique_ptr bitmap, const gfx::Size& bitmap_size, const gpu::SyncToken& sync_token, bool lost_resource) { cached_bitmap_.reset(); // Only keep around a cached bitmap if the plugin is currently drawing (has // need_flush_ack_ set). if (need_flush_ack_ && bound_instance_) cached_bitmap_ = std::move(bitmap); cached_bitmap_size_ = bitmap_size; } // static void PepperGraphics2DHost::ReleaseTextureCallback( base::WeakPtr host, scoped_refptr context, uint32_t id, const gpu::SyncToken& sync_token, bool lost) { if (sync_token.HasData()) context->ContextGL()->WaitSyncTokenCHROMIUM(sync_token.GetConstData()); if (host && !lost) { // Recycle the texture to be used in future frames. This moves it from // the busy textures list to the recycled list. auto it = host->texture_copies_.begin(); for (; it != host->texture_copies_.end(); ++it) { if (it->id == id) { host->recycled_texture_copies_.push_back(*it); host->texture_copies_.erase(it); break; } } return; } // The otherwise, the texture can not be reused so remove it from the busy // texture list and delete it. if (host) { auto matches_id = [id](const TextureInfo& info) { return info.id == id; }; base::EraseIf(host->texture_copies_, matches_id); } context->ContextGL()->DeleteTextures(1, &id); } bool PepperGraphics2DHost::PrepareTransferableResource( viz::TransferableResource* transferable_resource, std::unique_ptr* release_callback) { // Reuse the |main_thread_context_| if it is not lost. If it is lost, we // can't reuse the texture ids, they are invalid. If the compositing mode // changed, the context will be lost also, so we get both together. if (!main_thread_context_ || main_thread_context_->ContextGL()->GetGraphicsResetStatusKHR() != GL_NO_ERROR) { texture_copies_.clear(); recycled_texture_copies_.clear(); main_thread_context_ = nullptr; if (!is_gpu_compositing_disabled_) { RenderThreadImpl* rti = RenderThreadImpl::current(); is_gpu_compositing_disabled_ = rti->IsGpuCompositingDisabled(); if (!is_gpu_compositing_disabled_) { // Using gpu compositing. main_thread_context_ = rti->SharedMainThreadContextProvider(); } else { // Just switched to software compositing. Force us to send the // frame to the compositor again even if not changed. composited_output_modified_ = true; } } } if (!composited_output_modified_) return false; // Context creation failed, so we're unable to give this frame to the // compositor. Try again next time. if (!is_gpu_compositing_disabled_ && !main_thread_context_) return false; // When gpu compositing, the compositor expects gpu resources, so we copy the // |image_data_| into a texture. if (main_thread_context_) { auto* gl = main_thread_context_->ContextGL(); // The bitmap in |image_data_| uses the skia N32 byte order. constexpr bool bitmap_is_bgra = kN32_SkColorType == kBGRA_8888_SkColorType; const bool texture_can_be_bgra = main_thread_context_->ContextCapabilities().texture_format_bgra8888; const bool upload_bgra = bitmap_is_bgra && texture_can_be_bgra; const uint32_t format = upload_bgra ? GL_BGRA_EXT : GL_RGBA; RenderThreadImpl* rti = RenderThreadImpl::current(); bool overlays_supported = rti->IsGpuMemoryBufferCompositorResourcesEnabled() && main_thread_context_->ContextCapabilities().texture_storage_image; bool overlay_candidate = false; uint32_t texture_target = GL_TEXTURE_2D; uint32_t storage_format = 0; if (overlays_supported) { if (upload_bgra) { texture_target = gpu::GetBufferTextureTarget( gfx::BufferUsage::SCANOUT, gfx::BufferFormat::BGRA_8888, main_thread_context_->ContextCapabilities()); storage_format = GL_BGRA8_EXT; overlay_candidate = true; } else { texture_target = gpu::GetBufferTextureTarget( gfx::BufferUsage::SCANOUT, gfx::BufferFormat::RGBA_8888, main_thread_context_->ContextCapabilities()); storage_format = GL_RGBA8_OES; overlay_candidate = true; } } const gfx::Size size(image_data_->width(), image_data_->height()); uint32_t texture_id = 0; gpu::Mailbox gpu_mailbox; while (!recycled_texture_copies_.empty()) { if (recycled_texture_copies_.back().size == size) { texture_id = recycled_texture_copies_.back().id; gpu_mailbox = recycled_texture_copies_.back().mailbox; recycled_texture_copies_.pop_back(); gl->BindTexture(texture_target, texture_id); break; } uint32_t id = recycled_texture_copies_.back().id; main_thread_context_->ContextGL()->DeleteTextures(1, &id); recycled_texture_copies_.pop_back(); } if (!texture_id) { gl->GenTextures(1, &texture_id); gl->BindTexture(texture_target, texture_id); gl->TexParameteri(texture_target, GL_TEXTURE_MIN_FILTER, GL_LINEAR); gl->TexParameteri(texture_target, GL_TEXTURE_MAG_FILTER, GL_LINEAR); gl->TexParameteri(texture_target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); gl->TexParameteri(texture_target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); if (overlay_candidate) { gl->TexStorage2DImageCHROMIUM(texture_target, storage_format, GL_SCANOUT_CHROMIUM, size.width(), size.height()); } else { gl->TexImage2D(texture_target, 0, format, size.width(), size.height(), 0, format, GL_UNSIGNED_BYTE, nullptr); } gl->GenMailboxCHROMIUM(gpu_mailbox.name); gl->ProduceTextureDirectCHROMIUM(texture_id, gpu_mailbox.name); } TextureInfo info; info.id = texture_id; info.mailbox = gpu_mailbox; info.size = size; texture_copies_.push_back(std::move(info)); void* src = image_data_->Map(); // Convert to RGBA if we can't upload BGRA. This is slow sad times. std::unique_ptr swizzled; if (bitmap_is_bgra != upload_bgra) { size_t num_pixels = (base::CheckedNumeric(image_data_->width()) * image_data_->height()) .ValueOrDie(); swizzled = std::make_unique(num_pixels); SkSwapRB(swizzled.get(), static_cast(src), num_pixels); src = swizzled.get(); } gl->TexSubImage2D(texture_target, 0, 0, 0, size.width(), size.height(), format, GL_UNSIGNED_BYTE, src); image_data_->Unmap(); swizzled.reset(); gpu::SyncToken sync_token; gl->GenUnverifiedSyncTokenCHROMIUM(sync_token.GetData()); gl->BindTexture(texture_target, 0); *transferable_resource = viz::TransferableResource::MakeGLOverlay( std::move(gpu_mailbox), GL_LINEAR, texture_target, std::move(sync_token), size, overlay_candidate); *release_callback = viz::SingleReleaseCallback::Create( base::Bind(&ReleaseTextureCallback, this->AsWeakPtr(), main_thread_context_, texture_id)); composited_output_modified_ = false; return true; } gfx::Size pixel_image_size(image_data_->width(), image_data_->height()); std::unique_ptr shared_bitmap; if (cached_bitmap_) { if (cached_bitmap_size_ == pixel_image_size) shared_bitmap = std::move(cached_bitmap_); else cached_bitmap_.reset(); } if (!shared_bitmap) { shared_bitmap = RenderThreadImpl::current() ->shared_bitmap_manager() ->AllocateSharedBitmap(pixel_image_size); } if (!shared_bitmap) return false; void* src = image_data_->Map(); memcpy(shared_bitmap->pixels(), src, viz::SharedBitmap::CheckedSizeInBytes(pixel_image_size)); image_data_->Unmap(); *transferable_resource = viz::TransferableResource::MakeSoftware( shared_bitmap->id(), shared_bitmap->sequence_number(), pixel_image_size); *release_callback = viz::SingleReleaseCallback::Create(base::Bind( &PepperGraphics2DHost::ReleaseSoftwareCallback, this->AsWeakPtr(), base::Passed(&shared_bitmap), pixel_image_size)); composited_output_modified_ = false; return true; } void PepperGraphics2DHost::AttachedToNewLayer() { composited_output_modified_ = true; } int32_t PepperGraphics2DHost::Flush(PP_Resource* old_image_data) { bool done_replace_contents = false; bool no_update_visible = true; bool is_plugin_visible = true; for (size_t i = 0; i < queued_operations_.size(); i++) { QueuedOperation& operation = queued_operations_[i]; gfx::Rect op_rect; switch (operation.type) { case QueuedOperation::TRANSFORM: ExecuteTransform(operation.scale, operation.translation, &op_rect); break; case QueuedOperation::PAINT: ExecutePaintImageData(operation.paint_image.get(), operation.paint_x, operation.paint_y, operation.paint_src_rect, &op_rect); break; case QueuedOperation::SCROLL: ExecuteScroll(operation.scroll_clip_rect, operation.scroll_dx, operation.scroll_dy, &op_rect); break; case QueuedOperation::REPLACE: // Since the out parameter |old_image_data| takes ownership of the // reference, if there are more than one ReplaceContents calls queued // the first |old_image_data| will get overwritten and leaked. So we // only supply this for the first call. ExecuteReplaceContents( operation.replace_image.get(), &op_rect, done_replace_contents ? nullptr : old_image_data); done_replace_contents = true; break; } // For correctness with accelerated compositing, we must issue an invalidate // on the full op_rect even if it is partially or completely off-screen. // However, if we issue an invalidate for a clipped-out region, WebKit will // do nothing and we won't get any ViewFlushedPaint calls, leaving our // callback stranded. So we still need to check whether the repainted area // is visible to determine how to deal with the callback. if (bound_instance_ && !op_rect.IsEmpty()) { gfx::Point scroll_delta(operation.scroll_dx, operation.scroll_dy); // In use-zoom-for-dsf mode, the viewport (thus cc) uses native // pixels, so the damage and rects have to be scaled. gfx::Rect op_rect_in_viewport = op_rect; ConvertToLogicalPixels(scale_, &op_rect, nullptr); if (!ConvertToLogicalPixels(scale_ / viewport_to_dip_scale_, &op_rect_in_viewport, operation.type == QueuedOperation::SCROLL ? &scroll_delta : nullptr)) { // Conversion requires falling back to InvalidateRect. operation.type = QueuedOperation::PAINT; } gfx::Rect clip = PP_ToGfxRect(bound_instance_->view_data().clip_rect); is_plugin_visible = !clip.IsEmpty(); // Set |no_update_visible| to false if the change overlaps the visible // area. if (!gfx::IntersectRects(clip, op_rect).IsEmpty()) { no_update_visible = false; } // Notify the plugin of the entire change (op_rect), even if it is // partially or completely off-screen. if (operation.type == QueuedOperation::SCROLL) { bound_instance_->ScrollRect( scroll_delta.x(), scroll_delta.y(), op_rect_in_viewport); } else { if (!op_rect_in_viewport.IsEmpty()) bound_instance_->InvalidateRect(op_rect_in_viewport); } composited_output_modified_ = true; } } queued_operations_.clear(); if (!bound_instance_) { // As promised in the API, we always schedule callback when unbound. ScheduleOffscreenFlushAck(); } else if (no_update_visible && is_plugin_visible && bound_instance_->view_data().is_page_visible) { // There's nothing visible to invalidate so just schedule the callback to // execute in the next round of the message loop. ScheduleOffscreenFlushAck(); } else { need_flush_ack_ = true; } if (bound_instance_ && bound_instance_->throttler() && bound_instance_->throttler()->needs_representative_keyframe()) { bound_instance_->throttler()->OnImageFlush(image_data_->GetMappedBitmap()); } return PP_OK_COMPLETIONPENDING; } void PepperGraphics2DHost::ExecuteTransform(const float& scale, const gfx::PointF& translate, gfx::Rect* invalidated_rect) { bound_instance_->SetGraphics2DTransform(scale, translate); *invalidated_rect = gfx::Rect(0, 0, image_data_->width(), image_data_->height()); } void PepperGraphics2DHost::ExecutePaintImageData(PPB_ImageData_Impl* image, int x, int y, const gfx::Rect& src_rect, gfx::Rect* invalidated_rect) { // Ensure the source image is mapped to read from it. ImageDataAutoMapper auto_mapper(image); if (!auto_mapper.is_valid()) return; // Portion within the source image to cut out. SkIRect src_irect = {src_rect.x(), src_rect.y(), src_rect.right(), src_rect.bottom()}; // Location within the backing store to copy to. *invalidated_rect = src_rect; invalidated_rect->Offset(x, y); SkRect dest_rect = {SkIntToScalar(invalidated_rect->x()), SkIntToScalar(invalidated_rect->y()), SkIntToScalar(invalidated_rect->right()), SkIntToScalar(invalidated_rect->bottom())}; if (image->format() != image_data_->format()) { // Convert the image data if the format does not match. ConvertImageData(image, src_irect, image_data_.get(), dest_rect); } else { // We're guaranteed to have a mapped canvas since we mapped it in Init(). SkCanvas* backing_canvas = image_data_->GetCanvas(); // We want to replace the contents of the bitmap rather than blend. SkPaint paint; paint.setBlendMode(SkBlendMode::kSrc); backing_canvas->drawBitmapRect( image->GetMappedBitmap(), src_irect, dest_rect, &paint); } } void PepperGraphics2DHost::ExecuteScroll(const gfx::Rect& clip, int dx, int dy, gfx::Rect* invalidated_rect) { gfx::ScrollCanvas(image_data_->GetCanvas(), clip, gfx::Vector2d(dx, dy)); *invalidated_rect = clip; } void PepperGraphics2DHost::ExecuteReplaceContents(PPB_ImageData_Impl* image, gfx::Rect* invalidated_rect, PP_Resource* old_image_data) { if (image->format() != image_data_->format()) { DCHECK(image->width() == image_data_->width() && image->height() == image_data_->height()); // Convert the image data if the format does not match. SkIRect src_irect = {0, 0, image->width(), image->height()}; SkRect dest_rect = {SkIntToScalar(0), SkIntToScalar(0), SkIntToScalar(image_data_->width()), SkIntToScalar(image_data_->height())}; ConvertImageData(image, src_irect, image_data_.get(), dest_rect); } else { // The passed-in image may not be mapped in our process, and we need to // guarantee that the current backing store is always mapped. if (!image->Map()) return; if (old_image_data) *old_image_data = image_data_->GetReference(); image_data_ = image; } *invalidated_rect = gfx::Rect(0, 0, image_data_->width(), image_data_->height()); } void PepperGraphics2DHost::SendFlushAck() { host()->SendReply(flush_reply_context_, PpapiPluginMsg_Graphics2D_FlushAck()); } void PepperGraphics2DHost::SendOffscreenFlushAck() { DCHECK(offscreen_flush_pending_); // We must clear this flag before issuing the callback. It will be // common for the plugin to issue another invalidate in response to a flush // callback, and we don't want to think that a callback is already pending. offscreen_flush_pending_ = false; SendFlushAck(); } void PepperGraphics2DHost::ScheduleOffscreenFlushAck() { offscreen_flush_pending_ = true; base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( FROM_HERE, base::BindOnce(&PepperGraphics2DHost::SendOffscreenFlushAck, AsWeakPtr()), base::TimeDelta::FromMilliseconds(kOffscreenCallbackDelayMs)); } bool PepperGraphics2DHost::HasPendingFlush() const { return need_flush_ack_ || offscreen_flush_pending_; } // static bool PepperGraphics2DHost::ConvertToLogicalPixels(float scale, gfx::Rect* op_rect, gfx::Point* delta) { if (scale == 1.0f || scale <= 0.0f) return true; gfx::Rect original_rect = *op_rect; // Take the enclosing rectangle after scaling so a rectangle scaled down then // scaled back up by the inverse scale would fully contain the entire area // affected by the original rectangle. *op_rect = gfx::ScaleToEnclosingRect(*op_rect, scale); if (delta) { gfx::Point original_delta = *delta; float inverse_scale = 1.0f / scale; *delta = gfx::ScaleToFlooredPoint(*delta, scale); gfx::Rect inverse_scaled_rect = gfx::ScaleToEnclosingRect(*op_rect, inverse_scale); if (original_rect != inverse_scaled_rect) return false; gfx::Point inverse_scaled_point = gfx::ScaleToFlooredPoint(*delta, inverse_scale); if (original_delta != inverse_scaled_point) return false; } return true; } } // namespace content