Commit 7ca92c28 authored by kylechar's avatar kylechar Committed by Commit Bot

ozone: Headless with osmesa GL outputs to file.

Add a custom GLSurfaceOSMesaPng that outputs to file. Unfortunately the
GPU sandbox will block this from writing to file, so it only works
without the GPU sandbox.

Bug: 783792
Change-Id: I868702aa5dab81bffb8ea4867d659225255e5d50
Reviewed-on: https://chromium-review.googlesource.com/758608
Commit-Queue: kylechar <kylechar@chromium.org>
Reviewed-by: default avatarRobert Kroeger <rjkroege@chromium.org>
Reviewed-by: default avatarZhenyao Mo <zmo@chromium.org>
Cr-Commit-Position: refs/heads/master@{#516061}
parent b317a798
......@@ -152,6 +152,7 @@ static const char* const kSwitchNames[] = {
#endif
#if defined(USE_OZONE)
switches::kOzonePlatform,
switches::kOzoneDumpFile,
#endif
#if defined(USE_X11)
switches::kX11Display,
......
......@@ -33,5 +33,9 @@ source_set("common") {
"//ui/gl:gl_features",
]
data_deps = [
"//third_party/mesa:osmesa",
]
visibility = [ "//ui/ozone/platform/*" ]
}
......@@ -8,6 +8,8 @@ source_set("headless") {
sources = [
"client_native_pixmap_factory_headless.cc",
"client_native_pixmap_factory_headless.h",
"gl_surface_osmesa_png.cc",
"gl_surface_osmesa_png.h",
"headless_surface_factory.cc",
"headless_surface_factory.h",
"headless_window.cc",
......
// 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 "ui/ozone/platform/headless/gl_surface_osmesa_png.h"
#include <vector>
#include "base/files/file_util.h"
#include "base/task_scheduler/post_task.h"
#include "ui/gfx/codec/png_codec.h"
namespace ui {
namespace {
constexpr int kBytesPerPixelBGRA = 4;
void WritePngToFile(const base::FilePath& path,
std::vector<unsigned char> png_data) {
DCHECK(!path.empty());
base::WriteFile(path, reinterpret_cast<const char*>(png_data.data()),
png_data.size());
}
} // namespace
GLSurfaceOSMesaPng::GLSurfaceOSMesaPng(base::FilePath output_path)
: GLSurfaceOSMesa(
gl::GLSurfaceFormat(gl::GLSurfaceFormat::PIXEL_LAYOUT_BGRA),
gfx::Size(1, 1)),
output_path_(output_path) {}
bool GLSurfaceOSMesaPng::IsOffscreen() {
return false;
}
gfx::SwapResult GLSurfaceOSMesaPng::SwapBuffers() {
if (!output_path_.empty())
WriteBufferToPng();
return gfx::SwapResult::SWAP_ACK;
}
GLSurfaceOSMesaPng::~GLSurfaceOSMesaPng() {
Destroy();
}
void GLSurfaceOSMesaPng::WriteBufferToPng() {
// TODO(crbug.com/783792): Writing the PNG to a file won't work with the GPU
// sandbox. This will produce no output unless --no-sandbox is used. Make this
// work with a file handle passed from browser process and possibly change
// output to be video.
gfx::Size size = GetSize();
std::vector<unsigned char> png_data;
if (gfx::PNGCodec::Encode(static_cast<unsigned char*>(GetHandle()),
gfx::PNGCodec::FORMAT_BGRA, size,
size.width() * kBytesPerPixelBGRA,
false /* discard_transparency */,
std::vector<gfx::PNGCodec::Comment>(), &png_data)) {
base::PostTaskWithTraits(
FROM_HERE,
{base::MayBlock(), base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
base::Bind(&WritePngToFile, output_path_, base::Passed(&png_data)));
}
}
} // namespace ui
// 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.
#ifndef UI_OZONE_PLATFORM_HEADLESS_GL_SURFACE_OSMESA_PNG_H_
#define UI_OZONE_PLATFORM_HEADLESS_GL_SURFACE_OSMESA_PNG_H_
#include "base/files/file_path.h"
#include "base/macros.h"
#include "ui/gl/gl_surface_osmesa.h"
namespace ui {
class GLSurfaceOSMesaPng : public gl::GLSurfaceOSMesa {
public:
explicit GLSurfaceOSMesaPng(base::FilePath output_path);
// gl::GLSurfaceOSMesa:
bool IsOffscreen() override;
gfx::SwapResult SwapBuffers() override;
private:
~GLSurfaceOSMesaPng() override;
// Write contents of buffer out to PNG file at |output_path_|.
void WriteBufferToPng();
// If not empty then buffer contents will be written out to this location on
// swap.
base::FilePath output_path_;
DISALLOW_COPY_AND_ASSIGN(GLSurfaceOSMesaPng);
};
} // namespace ui
#endif // UI_OZONE_PLATFORM_HEADLESS_GL_SURFACE_OSMESA_PNG_H_
......@@ -9,6 +9,7 @@
#include "base/location.h"
#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "base/strings/string_number_conversions.h"
#include "base/task_scheduler/post_task.h"
#include "build/build_config.h"
#include "third_party/skia/include/core/SkCanvas.h"
......@@ -18,6 +19,7 @@
#include "ui/gfx/skia_util.h"
#include "ui/gfx/vsync_provider.h"
#include "ui/ozone/common/gl_ozone_osmesa.h"
#include "ui/ozone/platform/headless/gl_surface_osmesa_png.h"
#include "ui/ozone/platform/headless/headless_window.h"
#include "ui/ozone/platform/headless/headless_window_manager.h"
#include "ui/ozone/public/surface_ozone_canvas.h"
......@@ -37,7 +39,7 @@ void WriteDataToFile(const base::FilePath& location, const SkBitmap& bitmap) {
// TODO(altimin): Find a proper way to capture rendering output.
class FileSurface : public SurfaceOzoneCanvas {
public:
explicit FileSurface(const base::FilePath& location) : location_(location) {}
explicit FileSurface(const base::FilePath& location) : base_path_(location) {}
~FileSurface() override {}
// SurfaceOzoneCanvas overrides:
......@@ -47,7 +49,7 @@ class FileSurface : public SurfaceOzoneCanvas {
}
sk_sp<SkSurface> GetSurface() override { return surface_; }
void PresentCanvas(const gfx::Rect& damage) override {
if (location_.empty())
if (base_path_.empty())
return;
SkBitmap bitmap;
bitmap.allocPixels(surface_->getCanvas()->imageInfo());
......@@ -58,7 +60,7 @@ class FileSurface : public SurfaceOzoneCanvas {
base::PostTaskWithTraits(
FROM_HERE,
{base::MayBlock(), base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
base::Bind(&WriteDataToFile, location_, bitmap));
base::Bind(&WriteDataToFile, base_path_, bitmap));
}
}
std::unique_ptr<gfx::VSyncProvider> CreateVSyncProvider() override {
......@@ -66,7 +68,7 @@ class FileSurface : public SurfaceOzoneCanvas {
}
private:
base::FilePath location_;
base::FilePath base_path_;
sk_sp<SkSurface> surface_;
};
......@@ -103,20 +105,46 @@ class TestPixmap : public gfx::NativePixmap {
DISALLOW_COPY_AND_ASSIGN(TestPixmap);
};
} // namespace
class GLOzoneOSMesaHeadless : public GLOzoneOSMesa {
public:
explicit GLOzoneOSMesaHeadless(HeadlessSurfaceFactory* surface_factory)
: surface_factory_(surface_factory) {}
HeadlessSurfaceFactory::HeadlessSurfaceFactory()
: HeadlessSurfaceFactory(nullptr) {}
~GLOzoneOSMesaHeadless() override = default;
HeadlessSurfaceFactory::HeadlessSurfaceFactory(
HeadlessWindowManager* window_manager)
: window_manager_(window_manager) {
// GLOzone:
scoped_refptr<gl::GLSurface> CreateViewGLSurface(
gfx::AcceleratedWidget window) override {
return gl::InitializeGLSurface(
new GLSurfaceOSMesaPng(surface_factory_->GetPathForWidget(window)));
}
private:
HeadlessSurfaceFactory* const surface_factory_;
DISALLOW_COPY_AND_ASSIGN(GLOzoneOSMesaHeadless);
};
} // namespace
HeadlessSurfaceFactory::HeadlessSurfaceFactory(base::FilePath base_path)
: base_path_(base_path) {
CheckBasePath();
#if !defined(OS_FUCHSIA)
osmesa_implementation_ = std::make_unique<GLOzoneOSMesa>();
osmesa_implementation_ = std::make_unique<GLOzoneOSMesaHeadless>(this);
#endif
}
HeadlessSurfaceFactory::~HeadlessSurfaceFactory() {}
HeadlessSurfaceFactory::~HeadlessSurfaceFactory() = default;
base::FilePath HeadlessSurfaceFactory::GetPathForWidget(
gfx::AcceleratedWidget widget) {
if (base_path_.empty() || base_path_ == base::FilePath("/dev/null"))
return base_path_;
// Disambiguate multiple window output files with the window id.
return base_path_.Append(base::IntToString(widget) + ".png");
}
std::vector<gl::GLImplementation>
HeadlessSurfaceFactory::GetAllowedGLImplementations() {
......@@ -139,8 +167,7 @@ GLOzone* HeadlessSurfaceFactory::GetGLOzone(
std::unique_ptr<SurfaceOzoneCanvas>
HeadlessSurfaceFactory::CreateCanvasForWidget(gfx::AcceleratedWidget widget) {
HeadlessWindow* window = window_manager_->GetWindow(widget);
return base::WrapUnique<SurfaceOzoneCanvas>(new FileSurface(window->path()));
return std::make_unique<FileSurface>(GetPathForWidget(widget));
}
scoped_refptr<gfx::NativePixmap> HeadlessSurfaceFactory::CreateNativePixmap(
......@@ -151,4 +178,16 @@ scoped_refptr<gfx::NativePixmap> HeadlessSurfaceFactory::CreateNativePixmap(
return new TestPixmap(format);
}
void HeadlessSurfaceFactory::CheckBasePath() const {
if (base_path_.empty())
return;
if (!DirectoryExists(base_path_) && !base::CreateDirectory(base_path_) &&
base_path_ != base::FilePath("/dev/null"))
PLOG(FATAL) << "Unable to create output directory";
if (!base::PathIsWritable(base_path_))
PLOG(FATAL) << "Unable to write to output location";
}
} // namespace ui
......@@ -8,20 +8,20 @@
#include <memory>
#include <vector>
#include "base/files/file_path.h"
#include "base/macros.h"
#include "ui/ozone/public/gl_ozone.h"
#include "ui/ozone/public/surface_factory_ozone.h"
namespace ui {
class HeadlessWindowManager;
class HeadlessSurfaceFactory : public SurfaceFactoryOzone {
public:
HeadlessSurfaceFactory();
explicit HeadlessSurfaceFactory(HeadlessWindowManager* window_manager);
explicit HeadlessSurfaceFactory(base::FilePath base_path);
~HeadlessSurfaceFactory() override;
base::FilePath GetPathForWidget(gfx::AcceleratedWidget widget);
// SurfaceFactoryOzone:
std::vector<gl::GLImplementation> GetAllowedGLImplementations() override;
GLOzone* GetGLOzone(gl::GLImplementation implementation) override;
......@@ -34,7 +34,10 @@ class HeadlessSurfaceFactory : public SurfaceFactoryOzone {
gfx::BufferUsage usage) override;
private:
HeadlessWindowManager* window_manager_;
void CheckBasePath() const;
// Base path for window output PNGs.
base::FilePath base_path_;
std::unique_ptr<GLOzone> osmesa_implementation_;
......
......@@ -6,8 +6,6 @@
#include <string>
#include "base/files/file_path.h"
#include "base/strings/string_number_conversions.h"
#include "ui/events/platform/platform_event_source.h"
#include "ui/ozone/platform/headless/headless_window_manager.h"
#include "ui/platform_window/platform_window_delegate.h"
......@@ -26,15 +24,6 @@ HeadlessWindow::~HeadlessWindow() {
manager_->RemoveWindow(widget_, this);
}
base::FilePath HeadlessWindow::path() {
base::FilePath base_path = manager_->base_path();
if (base_path.empty() || base_path == base::FilePath("/dev/null"))
return base_path;
// Disambiguate multiple window output files with the window id.
return base_path.Append(base::IntToString(widget_));
}
gfx::Rect HeadlessWindow::GetBounds() {
return bounds_;
}
......
......@@ -5,7 +5,6 @@
#ifndef UI_OZONE_PLATFORM_HEADLESS_HEADLESS_WINDOW_H_
#define UI_OZONE_PLATFORM_HEADLESS_HEADLESS_WINDOW_H_
#include "base/files/file_path.h"
#include "base/macros.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/native_widget_types.h"
......@@ -23,9 +22,6 @@ class HeadlessWindow : public PlatformWindow {
const gfx::Rect& bounds);
~HeadlessWindow() override;
// Path for image file for this window.
base::FilePath path();
// PlatformWindow:
gfx::Rect GetBounds() override;
void SetBounds(const gfx::Rect& bounds) override;
......
......@@ -4,29 +4,14 @@
#include "ui/ozone/platform/headless/headless_window_manager.h"
#include "base/files/file_util.h"
#include "base/location.h"
namespace ui {
HeadlessWindowManager::HeadlessWindowManager(
const base::FilePath& dump_location)
: location_(dump_location) {}
HeadlessWindowManager::HeadlessWindowManager() = default;
HeadlessWindowManager::~HeadlessWindowManager() {
DCHECK(thread_checker_.CalledOnValidThread());
}
void HeadlessWindowManager::Initialize() {
if (location_.empty())
return;
if (!DirectoryExists(location_) && !base::CreateDirectory(location_) &&
location_ != base::FilePath("/dev/null"))
PLOG(FATAL) << "unable to create output directory";
if (!base::PathIsWritable(location_))
PLOG(FATAL) << "unable to write to output location";
}
int32_t HeadlessWindowManager::AddWindow(HeadlessWindow* window) {
return windows_.Add(window);
}
......@@ -41,8 +26,4 @@ HeadlessWindow* HeadlessWindowManager::GetWindow(int32_t window_id) {
return windows_.Lookup(window_id);
}
base::FilePath HeadlessWindowManager::base_path() const {
return location_;
}
} // namespace ui
......@@ -22,12 +22,9 @@ class HeadlessWindow;
class HeadlessWindowManager {
public:
explicit HeadlessWindowManager(const base::FilePath& dump_location);
HeadlessWindowManager();
~HeadlessWindowManager();
// Initialize (mainly check that we have a place to write output to).
void Initialize();
// Register a new window. Returns the window id.
int32_t AddWindow(HeadlessWindow* window);
......@@ -37,12 +34,7 @@ class HeadlessWindowManager {
// Find a window object by id;
HeadlessWindow* GetWindow(int32_t window_id);
// User-supplied path for images.
base::FilePath base_path() const;
private:
base::FilePath location_;
base::IDMap<HeadlessWindow*> windows_;
base::ThreadChecker thread_checker_;
......
......@@ -33,8 +33,8 @@ namespace {
// sure that the PlatformEventSource has an instance while in unit tests.
class HeadlessPlatformEventSource : public ui::PlatformEventSource {
public:
HeadlessPlatformEventSource() {}
~HeadlessPlatformEventSource() override {}
HeadlessPlatformEventSource() = default;
~HeadlessPlatformEventSource() override = default;
private:
DISALLOW_COPY_AND_ASSIGN(HeadlessPlatformEventSource);
......@@ -43,9 +43,9 @@ class HeadlessPlatformEventSource : public ui::PlatformEventSource {
// OzonePlatform for headless mode
class OzonePlatformHeadless : public OzonePlatform {
public:
OzonePlatformHeadless(const base::FilePath& dump_file)
explicit OzonePlatformHeadless(const base::FilePath& dump_file)
: file_path_(dump_file) {}
~OzonePlatformHeadless() override {}
~OzonePlatformHeadless() override = default;
// OzonePlatform:
ui::SurfaceFactoryOzone* GetSurfaceFactoryOzone() override {
......@@ -69,8 +69,8 @@ class OzonePlatformHeadless : public OzonePlatform {
std::unique_ptr<PlatformWindow> CreatePlatformWindow(
PlatformWindowDelegate* delegate,
const gfx::Rect& bounds) override {
return base::WrapUnique<PlatformWindow>(
new HeadlessWindow(delegate, window_manager_.get(), bounds));
return std::make_unique<HeadlessWindow>(delegate, window_manager_.get(),
bounds);
}
std::unique_ptr<display::NativeDisplayDelegate> CreateNativeDisplayDelegate()
override {
......@@ -78,24 +78,23 @@ class OzonePlatformHeadless : public OzonePlatform {
}
void InitializeUI(const InitParams& params) override {
window_manager_.reset(new HeadlessWindowManager(file_path_));
window_manager_->Initialize();
surface_factory_.reset(new HeadlessSurfaceFactory(window_manager_.get()));
window_manager_ = std::make_unique<HeadlessWindowManager>();
surface_factory_ = std::make_unique<HeadlessSurfaceFactory>(file_path_);
// This unbreaks tests that create their own.
if (!PlatformEventSource::GetInstance())
platform_event_source_.reset(new HeadlessPlatformEventSource);
platform_event_source_ = std::make_unique<HeadlessPlatformEventSource>();
KeyboardLayoutEngineManager::SetKeyboardLayoutEngine(
std::make_unique<StubKeyboardLayoutEngine>());
overlay_manager_.reset(new StubOverlayManager());
overlay_manager_ = std::make_unique<StubOverlayManager>();
input_controller_ = CreateStubInputController();
cursor_factory_ozone_.reset(new BitmapCursorFactoryOzone);
cursor_factory_ozone_ = std::make_unique<BitmapCursorFactoryOzone>();
gpu_platform_support_host_.reset(CreateStubGpuPlatformSupportHost());
}
void InitializeGPU(const InitParams& params) override {
if (!surface_factory_)
surface_factory_.reset(new HeadlessSurfaceFactory());
surface_factory_ = std::make_unique<HeadlessSurfaceFactory>(file_path_);
}
private:
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment