blob: 73ea1986b2140a3ffb87c5e23c7ab2174bac0a10 [file] [log] [blame]
// 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 "components/viz/service/display_embedder/software_output_device_win.h"
#include "base/bind.h"
#include "base/memory/unsafe_shared_memory_region.h"
#include "base/threading/thread_checker.h"
#include "base/win/windows_version.h"
#include "components/viz/common/display/use_layered_window.h"
#include "components/viz/common/resources/resource_sizes.h"
#include "components/viz/service/display_embedder/output_device_backing.h"
#include "mojo/public/cpp/system/platform_handle.h"
#include "services/viz/privileged/interfaces/compositing/layered_window_updater.mojom.h"
#include "skia/ext/platform_canvas.h"
#include "skia/ext/skia_utils_win.h"
#include "third_party/skia/include/core/SkCanvas.h"
#include "ui/gfx/gdi_util.h"
#include "ui/gfx/skia_util.h"
#include "ui/gfx/win/hwnd_util.h"
#include "ui/gl/vsync_provider_win.h"
namespace viz {
namespace {
// Shared base class for Windows SoftwareOutputDevice implementations.
class SoftwareOutputDeviceWinBase : public SoftwareOutputDevice {
public:
explicit SoftwareOutputDeviceWinBase(HWND hwnd) : hwnd_(hwnd) {
vsync_provider_ = std::make_unique<gl::VSyncProviderWin>(hwnd);
}
~SoftwareOutputDeviceWinBase() override {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK(!in_paint_);
}
HWND hwnd() const { return hwnd_; }
// SoftwareOutputDevice implementation.
void Resize(const gfx::Size& viewport_pixel_size,
float scale_factor) override;
SkCanvas* BeginPaint(const gfx::Rect& damage_rect) override;
void EndPaint() override;
// Called from Resize() if |viewport_pixel_size_| has changed.
virtual void ResizeDelegated() = 0;
// Called from BeginPaint() and should return an SkCanvas.
virtual SkCanvas* BeginPaintDelegated() = 0;
// Called from EndPaint() if there is damage.
virtual void EndPaintDelegated(const gfx::Rect& damage_rect) = 0;
private:
const HWND hwnd_;
bool in_paint_ = false;
THREAD_CHECKER(thread_checker_);
DISALLOW_COPY_AND_ASSIGN(SoftwareOutputDeviceWinBase);
};
void SoftwareOutputDeviceWinBase::Resize(const gfx::Size& viewport_pixel_size,
float scale_factor) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK(!in_paint_);
if (viewport_pixel_size_ == viewport_pixel_size)
return;
viewport_pixel_size_ = viewport_pixel_size;
ResizeDelegated();
}
SkCanvas* SoftwareOutputDeviceWinBase::BeginPaint(
const gfx::Rect& damage_rect) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK(!in_paint_);
damage_rect_ = damage_rect;
in_paint_ = true;
return BeginPaintDelegated();
}
void SoftwareOutputDeviceWinBase::EndPaint() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK(in_paint_);
in_paint_ = false;
gfx::Rect intersected_damage_rect = damage_rect_;
intersected_damage_rect.Intersect(gfx::Rect(viewport_pixel_size_));
if (intersected_damage_rect.IsEmpty())
return;
EndPaintDelegated(intersected_damage_rect);
}
// SoftwareOutputDevice implementation that draws directly to the provided HWND.
// The backing buffer for paint is shared for all instances of this class.
class SoftwareOutputDeviceWinDirect : public SoftwareOutputDeviceWinBase,
public OutputDeviceBacking::Client {
public:
SoftwareOutputDeviceWinDirect(HWND hwnd, OutputDeviceBacking* backing);
~SoftwareOutputDeviceWinDirect() override;
// SoftwareOutputDeviceWinBase implementation.
void ResizeDelegated() override;
SkCanvas* BeginPaintDelegated() override;
void EndPaintDelegated(const gfx::Rect& damage_rect) override;
// OutputDeviceBacking::Client implementation.
const gfx::Size& GetViewportPixelSize() const override {
return viewport_pixel_size_;
}
void ReleaseCanvas() override { canvas_.reset(); }
private:
OutputDeviceBacking* const backing_;
std::unique_ptr<SkCanvas> canvas_;
DISALLOW_COPY_AND_ASSIGN(SoftwareOutputDeviceWinDirect);
};
SoftwareOutputDeviceWinDirect::SoftwareOutputDeviceWinDirect(
HWND hwnd,
OutputDeviceBacking* backing)
: SoftwareOutputDeviceWinBase(hwnd), backing_(backing) {
backing_->RegisterClient(this);
}
SoftwareOutputDeviceWinDirect::~SoftwareOutputDeviceWinDirect() {
backing_->UnregisterClient(this);
}
void SoftwareOutputDeviceWinDirect::ResizeDelegated() {
canvas_.reset();
backing_->ClientResized();
}
SkCanvas* SoftwareOutputDeviceWinDirect::BeginPaintDelegated() {
if (!canvas_) {
// Share pixel backing with other SoftwareOutputDeviceWinDirect instances.
// All work happens on the same thread so this is safe.
base::UnsafeSharedMemoryRegion* region =
backing_->GetSharedMemoryRegion(viewport_pixel_size_);
if (region && region->IsValid()) {
canvas_ = skia::CreatePlatformCanvasWithSharedSection(
viewport_pixel_size_.width(), viewport_pixel_size_.height(), true,
region->GetPlatformHandle(), skia::CRASH_ON_FAILURE);
}
}
return canvas_.get();
}
void SoftwareOutputDeviceWinDirect::EndPaintDelegated(
const gfx::Rect& damage_rect) {
if (!canvas_)
return;
HDC dib_dc = skia::GetNativeDrawingContext(canvas_.get());
HDC hdc = ::GetDC(hwnd());
RECT src_rect = damage_rect.ToRECT();
skia::CopyHDC(dib_dc, hdc, damage_rect.x(), damage_rect.y(),
canvas_->imageInfo().isOpaque(), src_rect,
canvas_->getTotalMatrix());
::ReleaseDC(hwnd(), hdc);
}
// SoftwareOutputDevice implementation that uses layered window API to draw
// indirectly. Since UpdateLayeredWindow() is blocked by the GPU sandbox an
// implementation of mojom::LayeredWindowUpdater in the browser process handles
// calling UpdateLayeredWindow. Pixel backing is in a SharedMemoryRegion so no
// copying between processes is required.
class SoftwareOutputDeviceWinProxy : public SoftwareOutputDeviceWinBase {
public:
SoftwareOutputDeviceWinProxy(
HWND hwnd,
mojom::LayeredWindowUpdaterPtr layered_window_updater);
~SoftwareOutputDeviceWinProxy() override = default;
// SoftwareOutputDevice implementation.
void OnSwapBuffers(SwapBuffersCallback swap_ack_callback) override;
// SoftwareOutputDeviceWinBase implementation.
void ResizeDelegated() override;
SkCanvas* BeginPaintDelegated() override;
void EndPaintDelegated(const gfx::Rect& rect) override;
private:
// Runs |swap_ack_callback_| after draw has happened.
void DrawAck();
mojom::LayeredWindowUpdaterPtr layered_window_updater_;
std::unique_ptr<SkCanvas> canvas_;
bool waiting_on_draw_ack_ = false;
base::OnceClosure swap_ack_callback_;
DISALLOW_COPY_AND_ASSIGN(SoftwareOutputDeviceWinProxy);
};
SoftwareOutputDeviceWinProxy::SoftwareOutputDeviceWinProxy(
HWND hwnd,
mojom::LayeredWindowUpdaterPtr layered_window_updater)
: SoftwareOutputDeviceWinBase(hwnd),
layered_window_updater_(std::move(layered_window_updater)) {
DCHECK(layered_window_updater_.is_bound());
}
void SoftwareOutputDeviceWinProxy::OnSwapBuffers(
SwapBuffersCallback swap_ack_callback) {
DCHECK(swap_ack_callback_.is_null());
// We aren't waiting on DrawAck() and can immediately run the callback.
if (!waiting_on_draw_ack_) {
task_runner_->PostTask(
FROM_HERE,
base::BindOnce(std::move(swap_ack_callback), viewport_pixel_size_));
return;
}
swap_ack_callback_ =
base::BindOnce(std::move(swap_ack_callback), viewport_pixel_size_);
}
void SoftwareOutputDeviceWinProxy::ResizeDelegated() {
canvas_.reset();
size_t required_bytes;
if (!ResourceSizes::MaybeSizeInBytes(
viewport_pixel_size_, ResourceFormat::RGBA_8888, &required_bytes)) {
DLOG(ERROR) << "Invalid viewport size " << viewport_pixel_size_.ToString();
return;
}
base::UnsafeSharedMemoryRegion region =
base::UnsafeSharedMemoryRegion::Create(required_bytes);
if (!region.IsValid()) {
DLOG(ERROR) << "Failed to allocate " << required_bytes << " bytes";
return;
}
// The SkCanvas maps shared memory on creation and unmaps on destruction.
canvas_ = skia::CreatePlatformCanvasWithSharedSection(
viewport_pixel_size_.width(), viewport_pixel_size_.height(), true,
region.GetPlatformHandle(), skia::CRASH_ON_FAILURE);
// Transfer region ownership to the browser process.
layered_window_updater_->OnAllocatedSharedMemory(viewport_pixel_size_,
std::move(region));
}
SkCanvas* SoftwareOutputDeviceWinProxy::BeginPaintDelegated() {
return canvas_.get();
}
void SoftwareOutputDeviceWinProxy::EndPaintDelegated(
const gfx::Rect& damage_rect) {
DCHECK(!waiting_on_draw_ack_);
if (!canvas_)
return;
layered_window_updater_->Draw(base::BindOnce(
&SoftwareOutputDeviceWinProxy::DrawAck, base::Unretained(this)));
waiting_on_draw_ack_ = true;
TRACE_EVENT_ASYNC_BEGIN0("viz", "SoftwareOutputDeviceWinProxy::Draw", this);
}
void SoftwareOutputDeviceWinProxy::DrawAck() {
DCHECK(waiting_on_draw_ack_);
DCHECK(!swap_ack_callback_.is_null());
TRACE_EVENT_ASYNC_END0("viz", "SoftwareOutputDeviceWinProxy::Draw", this);
waiting_on_draw_ack_ = false;
std::move(swap_ack_callback_).Run();
}
} // namespace
std::unique_ptr<SoftwareOutputDevice> CreateSoftwareOutputDeviceWin(
HWND hwnd,
OutputDeviceBacking* backing,
mojom::DisplayClient* display_client) {
if (NeedsToUseLayerWindow(hwnd)) {
DCHECK(display_client);
// Setup mojom::LayeredWindowUpdater implementation in the browser process
// to draw to the HWND.
mojom::LayeredWindowUpdaterPtr layered_window_updater;
display_client->CreateLayeredWindowUpdater(
mojo::MakeRequest(&layered_window_updater));
return std::make_unique<SoftwareOutputDeviceWinProxy>(
hwnd, std::move(layered_window_updater));
} else {
return std::make_unique<SoftwareOutputDeviceWinDirect>(hwnd, backing);
}
}
} // namespace viz