Commit cff667d9 authored by Emircan Uysaler's avatar Emircan Uysaler Committed by Commit Bot

Use async GPU readbacks in canvas.captureStream()

This CL replaces the existing blocking call to SkImage::readPixels() with
GLHelper::ReadbackTextureAsync() and GLHelper::CreateReadbackPipelineYUV()
callbacks for texture backed images.
In order to use for these calls, a GLHelper instance is lazily created and
owned by blink::WebGraphicsContext3DProvider.

Bug: 727385
Cq-Include-Trybots: master.tryserver.chromium.linux:linux_layout_tests_slimming_paint_v2
Change-Id: Iad8561183afddff0794038213ee22cec3e04ed76
Reviewed-on: https://chromium-review.googlesource.com/756474
Commit-Queue: Emircan Uysaler <emircan@chromium.org>
Reviewed-by: default avatarKenneth Russell <kbr@chromium.org>
Reviewed-by: default avatarKentaro Hara <haraken@chromium.org>
Reviewed-by: default avatarYuri Wiitala <miu@chromium.org>
Reviewed-by: default avatarJustin Novosad <junov@chromium.org>
Reviewed-by: default avatardanakj <danakj@chromium.org>
Cr-Commit-Position: refs/heads/master@{#516979}
parent 7783ecbd
...@@ -28,6 +28,7 @@ ...@@ -28,6 +28,7 @@
#include "gpu/command_buffer/common/mailbox.h" #include "gpu/command_buffer/common/mailbox.h"
#include "gpu/command_buffer/common/mailbox_holder.h" #include "gpu/command_buffer/common/mailbox_holder.h"
#include "third_party/skia/include/core/SkRegion.h" #include "third_party/skia/include/core/SkRegion.h"
#include "third_party/skia/include/gpu/GrTypes.h"
#include "ui/gfx/geometry/point.h" #include "ui/gfx/geometry/point.h"
#include "ui/gfx/geometry/rect.h" #include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/size.h" #include "ui/gfx/geometry/size.h"
...@@ -297,6 +298,8 @@ class GLHelper::CopyTextureToImpl ...@@ -297,6 +298,8 @@ class GLHelper::CopyTextureToImpl
GLHelper::ScalerInterface* scaler() const override; GLHelper::ScalerInterface* scaler() const override;
bool IsFlippingOutput() const override;
void ReadbackYUV(const gpu::Mailbox& mailbox, void ReadbackYUV(const gpu::Mailbox& mailbox,
const gpu::SyncToken& sync_token, const gpu::SyncToken& sync_token,
const gfx::Size& src_texture_size, const gfx::Size& src_texture_size,
...@@ -956,6 +959,13 @@ gfx::Size I420Converter::GetChromaPlaneTextureSize( ...@@ -956,6 +959,13 @@ gfx::Size I420Converter::GetChromaPlaneTextureSize(
(output_size.height() + 1) / 2); (output_size.height() + 1) / 2);
} }
// static
uint32_t ReadbackYUVInterface::GetGrGLBackendStateChanges() {
return kTextureBinding_GrGLBackendState | kView_GrGLBackendState |
kVertex_GrGLBackendState | kProgram_GrGLBackendState |
kRenderTarget_GrGLBackendState;
}
namespace { namespace {
I420ConverterImpl::I420ConverterImpl(GLES2Interface* gl, I420ConverterImpl::I420ConverterImpl(GLES2Interface* gl,
...@@ -1117,6 +1127,10 @@ GLHelper::CopyTextureToImpl::ReadbackYUVImpl::scaler() const { ...@@ -1117,6 +1127,10 @@ GLHelper::CopyTextureToImpl::ReadbackYUVImpl::scaler() const {
return scaler_.get(); return scaler_.get();
} }
bool GLHelper::CopyTextureToImpl::ReadbackYUVImpl::IsFlippingOutput() const {
return I420ConverterImpl::IsFlippingOutput();
}
void GLHelper::CopyTextureToImpl::ReadbackYUVImpl::ReadbackYUV( void GLHelper::CopyTextureToImpl::ReadbackYUVImpl::ReadbackYUV(
const gpu::Mailbox& mailbox, const gpu::Mailbox& mailbox,
const gpu::SyncToken& sync_token, const gpu::SyncToken& sync_token,
...@@ -1217,6 +1231,26 @@ std::unique_ptr<ReadbackYUVInterface> GLHelper::CreateReadbackPipelineYUV( ...@@ -1217,6 +1231,26 @@ std::unique_ptr<ReadbackYUVInterface> GLHelper::CreateReadbackPipelineYUV(
use_mrt); use_mrt);
} }
ReadbackYUVInterface* GLHelper::GetReadbackPipelineYUV(
bool vertically_flip_texture) {
ReadbackYUVInterface* yuv_reader = nullptr;
if (vertically_flip_texture) {
if (!shared_readback_yuv_flip_) {
shared_readback_yuv_flip_ = CreateReadbackPipelineYUV(
vertically_flip_texture, true /* use_mrt */);
}
yuv_reader = shared_readback_yuv_flip_.get();
} else {
if (!shared_readback_yuv_noflip_) {
shared_readback_yuv_noflip_ = CreateReadbackPipelineYUV(
vertically_flip_texture, true /* use_mrt */);
}
yuv_reader = shared_readback_yuv_noflip_.get();
}
DCHECK(!yuv_reader->scaler());
return yuv_reader;
}
GLHelperReadbackSupport* GLHelper::GetReadbackSupport() { GLHelperReadbackSupport* GLHelper::GetReadbackSupport() {
LazyInitReadbackSupportImpl(); LazyInitReadbackSupportImpl();
return readback_support_.get(); return readback_support_.get();
......
...@@ -433,6 +433,10 @@ class VIZ_COMMON_EXPORT GLHelper { ...@@ -433,6 +433,10 @@ class VIZ_COMMON_EXPORT GLHelper {
bool vertically_flip_texture, bool vertically_flip_texture,
bool use_mrt); bool use_mrt);
// Returns a ReadbackYUVInterface instance that is lazily created and owned by
// this class. |use_mrt| is always true for these instances.
ReadbackYUVInterface* GetReadbackPipelineYUV(bool vertically_flip_texture);
// Returns the maximum number of draw buffers available, // Returns the maximum number of draw buffers available,
// 0 if GL_EXT_draw_buffers is not available. // 0 if GL_EXT_draw_buffers is not available.
GLint MaxDrawBuffers(); GLint MaxDrawBuffers();
...@@ -462,6 +466,8 @@ class VIZ_COMMON_EXPORT GLHelper { ...@@ -462,6 +466,8 @@ class VIZ_COMMON_EXPORT GLHelper {
std::unique_ptr<CopyTextureToImpl> copy_texture_to_impl_; std::unique_ptr<CopyTextureToImpl> copy_texture_to_impl_;
std::unique_ptr<GLHelperScaling> scaler_impl_; std::unique_ptr<GLHelperScaling> scaler_impl_;
std::unique_ptr<GLHelperReadbackSupport> readback_support_; std::unique_ptr<GLHelperReadbackSupport> readback_support_;
std::unique_ptr<ReadbackYUVInterface> shared_readback_yuv_flip_;
std::unique_ptr<ReadbackYUVInterface> shared_readback_yuv_noflip_;
private: private:
DISALLOW_COPY_AND_ASSIGN(GLHelper); DISALLOW_COPY_AND_ASSIGN(GLHelper);
...@@ -527,7 +533,7 @@ class VIZ_COMMON_EXPORT I420Converter { ...@@ -527,7 +533,7 @@ class VIZ_COMMON_EXPORT I420Converter {
// //
// TODO(crbug/754872): DEPRECATED. This will be removed soon, in favor of // TODO(crbug/754872): DEPRECATED. This will be removed soon, in favor of
// I420Converter and readback implementation in GLRendererCopier. // I420Converter and readback implementation in GLRendererCopier.
class ReadbackYUVInterface { class VIZ_COMMON_EXPORT ReadbackYUVInterface {
public: public:
ReadbackYUVInterface() {} ReadbackYUVInterface() {}
virtual ~ReadbackYUVInterface() {} virtual ~ReadbackYUVInterface() {}
...@@ -539,6 +545,9 @@ class ReadbackYUVInterface { ...@@ -539,6 +545,9 @@ class ReadbackYUVInterface {
// Returns the currently-set scaler, or null. // Returns the currently-set scaler, or null.
virtual GLHelper::ScalerInterface* scaler() const = 0; virtual GLHelper::ScalerInterface* scaler() const = 0;
// Returns true if the converter will vertically-flip the output.
virtual bool IsFlippingOutput() const = 0;
// Transforms a RGBA texture into I420 planar form, and then reads it back // Transforms a RGBA texture into I420 planar form, and then reads it back
// from the GPU into system memory. See the GLHelper::ScalerInterface::Scale() // from the GPU into system memory. See the GLHelper::ScalerInterface::Scale()
// method comments for the meaning/semantics of |src_texture_size| and // method comments for the meaning/semantics of |src_texture_size| and
...@@ -551,7 +560,7 @@ class ReadbackYUVInterface { ...@@ -551,7 +560,7 @@ class ReadbackYUVInterface {
// 4. Read-back the planar data, copying it into the given output // 4. Read-back the planar data, copying it into the given output
// destination. |paste_location| specifies the where to place the output // destination. |paste_location| specifies the where to place the output
// pixels: Rect(paste_location.origin(), output_rect.size()). // pixels: Rect(paste_location.origin(), output_rect.size()).
// 5. Run callback with true on success, false on failure (with no output // 5. Run |callback| with true on success, false on failure (with no output
// modified). // modified).
virtual void ReadbackYUV(const gpu::Mailbox& mailbox, virtual void ReadbackYUV(const gpu::Mailbox& mailbox,
const gpu::SyncToken& sync_token, const gpu::SyncToken& sync_token,
...@@ -565,6 +574,10 @@ class ReadbackYUVInterface { ...@@ -565,6 +574,10 @@ class ReadbackYUVInterface {
unsigned char* v_plane_data, unsigned char* v_plane_data,
const gfx::Point& paste_location, const gfx::Point& paste_location,
const base::Callback<void(bool)>& callback) = 0; const base::Callback<void(bool)>& callback) = 0;
// Returns the bitwise ORed set of GL backend state change that can be used to
// restore the GL state after ReadbackYUV() calls.
static uint32_t GetGrGLBackendStateChanges();
}; };
} // namespace viz } // namespace viz
......
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
#include "build/build_config.h" #include "build/build_config.h"
#include "content/browser/webrtc/webrtc_content_browsertest_base.h" #include "content/browser/webrtc/webrtc_content_browsertest_base.h"
#include "content/public/common/content_switches.h" #include "content/public/common/content_switches.h"
#include "content/shell/common/shell_switches.h"
#include "media/base/media_switches.h" #include "media/base/media_switches.h"
#include "media/base/test_data_util.h" #include "media/base/test_data_util.h"
#include "media/mojo/features.h" #include "media/mojo/features.h"
...@@ -70,6 +71,9 @@ class WebRtcCaptureFromElementBrowserTest ...@@ -70,6 +71,9 @@ class WebRtcCaptureFromElementBrowserTest
// Allow experimental canvas features. // Allow experimental canvas features.
base::CommandLine::ForCurrentProcess()->AppendSwitch( base::CommandLine::ForCurrentProcess()->AppendSwitch(
switches::kEnableExperimentalCanvasFeatures); switches::kEnableExperimentalCanvasFeatures);
// Allow window.internals for simulating context loss.
base::CommandLine::ForCurrentProcess()->AppendSwitch(
switches::kExposeInternalsForTesting);
} }
private: private:
...@@ -78,7 +82,7 @@ class WebRtcCaptureFromElementBrowserTest ...@@ -78,7 +82,7 @@ class WebRtcCaptureFromElementBrowserTest
IN_PROC_BROWSER_TEST_F(WebRtcCaptureFromElementBrowserTest, IN_PROC_BROWSER_TEST_F(WebRtcCaptureFromElementBrowserTest,
VerifyCanvas2DCaptureColor) { VerifyCanvas2DCaptureColor) {
MakeTypicalCall("testCanvas2DCaptureColors();", MakeTypicalCall("testCanvas2DCaptureColors(true);",
kCanvasCaptureColorTestHtmlFile); kCanvasCaptureColorTestHtmlFile);
} }
...@@ -88,7 +92,7 @@ IN_PROC_BROWSER_TEST_F(WebRtcCaptureFromElementBrowserTest, ...@@ -88,7 +92,7 @@ IN_PROC_BROWSER_TEST_F(WebRtcCaptureFromElementBrowserTest,
// TODO(crbug.com/706009): Make this test pass on mac. Behavior is not buggy // TODO(crbug.com/706009): Make this test pass on mac. Behavior is not buggy
// (verified manually) on mac, but for some reason this test fails on the mac // (verified manually) on mac, but for some reason this test fails on the mac
// bot. // bot.
MakeTypicalCall("testCanvasWebGLCaptureColors();", MakeTypicalCall("testCanvasWebGLCaptureColors(true);",
kCanvasCaptureColorTestHtmlFile); kCanvasCaptureColorTestHtmlFile);
#endif #endif
} }
...@@ -143,6 +147,31 @@ IN_PROC_BROWSER_TEST_P(WebRtcCaptureFromElementBrowserTest, ...@@ -143,6 +147,31 @@ IN_PROC_BROWSER_TEST_P(WebRtcCaptureFromElementBrowserTest,
kVideoAudioHtmlFile); kVideoAudioHtmlFile);
} }
// Enable these tests once https://crbug.com/785558 is fixed.
#if defined(OS_ANDROID)
#define MAYBE_CaptureFromCanvas2DHandlesContextLoss \
DISABLED_CaptureFromCanvas2DHandlesContextLoss
#define MAYBE_CaptureFromOpaqueCanvas2DHandlesContextLoss \
DISABLED_CaptureFromOpaqueCanvas2DHandlesContextLoss
#else
#define MAYBE_CaptureFromCanvas2DHandlesContextLoss \
CaptureFromCanvas2DHandlesContextLoss
#define MAYBE_CaptureFromOpaqueCanvas2DHandlesContextLoss \
CaptureFromOpaqueCanvas2DHandlesContextLoss
#endif
IN_PROC_BROWSER_TEST_F(WebRtcCaptureFromElementBrowserTest,
MAYBE_CaptureFromCanvas2DHandlesContextLoss) {
MakeTypicalCall("testCanvas2DContextLoss(true);",
kCanvasCaptureColorTestHtmlFile);
}
IN_PROC_BROWSER_TEST_F(WebRtcCaptureFromElementBrowserTest,
MAYBE_CaptureFromOpaqueCanvas2DHandlesContextLoss) {
MakeTypicalCall("testCanvas2DContextLoss(false);",
kCanvasCaptureColorTestHtmlFile);
}
INSTANTIATE_TEST_CASE_P(, INSTANTIATE_TEST_CASE_P(,
WebRtcCaptureFromElementBrowserTest, WebRtcCaptureFromElementBrowserTest,
testing::ValuesIn(kFileAndTypeParameters)); testing::ValuesIn(kFileAndTypeParameters));
......
...@@ -45,11 +45,13 @@ class CONTENT_EXPORT CanvasCaptureHandler final ...@@ -45,11 +45,13 @@ class CONTENT_EXPORT CanvasCaptureHandler final
static std::unique_ptr<CanvasCaptureHandler> CreateCanvasCaptureHandler( static std::unique_ptr<CanvasCaptureHandler> CreateCanvasCaptureHandler(
const blink::WebSize& size, const blink::WebSize& size,
double frame_rate, double frame_rate,
const scoped_refptr<base::SingleThreadTaskRunner>& io_task_runner, scoped_refptr<base::SingleThreadTaskRunner> io_task_runner,
blink::WebMediaStreamTrack* track); blink::WebMediaStreamTrack* track);
// blink::WebCanvasCaptureHandler Implementation. // blink::WebCanvasCaptureHandler implementation.
void SendNewFrame(const SkImage* image) override; void SendNewFrame(
sk_sp<SkImage> image,
blink::WebGraphicsContext3DProvider* context_provider) override;
bool NeedsNewFrame() const override; bool NeedsNewFrame() const override;
// Functions called by media::VideoCapturerSource implementation. // Functions called by media::VideoCapturerSource implementation.
...@@ -60,7 +62,6 @@ class CONTENT_EXPORT CanvasCaptureHandler final ...@@ -60,7 +62,6 @@ class CONTENT_EXPORT CanvasCaptureHandler final
const media::VideoCapturerSource::RunningCallback& running_callback); const media::VideoCapturerSource::RunningCallback& running_callback);
void RequestRefreshFrame(); void RequestRefreshFrame();
void StopVideoCapture(); void StopVideoCapture();
blink::WebSize GetSourceSize() const { return size_; }
private: private:
// A VideoCapturerSource instance is created, which is responsible for handing // A VideoCapturerSource instance is created, which is responsible for handing
...@@ -70,10 +71,37 @@ class CONTENT_EXPORT CanvasCaptureHandler final ...@@ -70,10 +71,37 @@ class CONTENT_EXPORT CanvasCaptureHandler final
CanvasCaptureHandler( CanvasCaptureHandler(
const blink::WebSize& size, const blink::WebSize& size,
double frame_rate, double frame_rate,
const scoped_refptr<base::SingleThreadTaskRunner>& io_task_runner, scoped_refptr<base::SingleThreadTaskRunner> io_task_runner,
blink::WebMediaStreamTrack* track); blink::WebMediaStreamTrack* track);
void CreateNewFrame(const SkImage* image); // Helper functions to read pixel content.
void ReadARGBPixelsSync(sk_sp<SkImage> image);
void ReadARGBPixelsAsync(
sk_sp<SkImage> image,
blink::WebGraphicsContext3DProvider* context_provider);
void ReadYUVPixelsAsync(
sk_sp<SkImage> image,
blink::WebGraphicsContext3DProvider* context_provider);
void OnARGBPixelsReadAsync(sk_sp<SkImage> image,
scoped_refptr<media::VideoFrame> temp_argb_frame,
base::TimeTicks this_frame_ticks,
bool flip,
bool success);
void OnYUVPixelsReadAsync(sk_sp<SkImage> image,
scoped_refptr<media::VideoFrame> yuv_frame,
base::TimeTicks this_frame_ticks,
bool success);
scoped_refptr<media::VideoFrame> ConvertToYUVFrame(
bool is_opaque,
bool flip,
const uint8_t* source_ptr,
const gfx::Size& image_size,
int stride,
SkColorType source_color_type);
void SendFrame(scoped_refptr<media::VideoFrame> video_frame,
base::TimeTicks this_frame_ticks);
void AddVideoCapturerSourceToVideoTrack( void AddVideoCapturerSourceToVideoTrack(
std::unique_ptr<media::VideoCapturerSource> source, std::unique_ptr<media::VideoCapturerSource> source,
blink::WebMediaStreamTrack* web_track); blink::WebMediaStreamTrack* web_track);
...@@ -84,14 +112,8 @@ class CONTENT_EXPORT CanvasCaptureHandler final ...@@ -84,14 +112,8 @@ class CONTENT_EXPORT CanvasCaptureHandler final
media::VideoCaptureFormat capture_format_; media::VideoCaptureFormat capture_format_;
bool ask_for_new_frame_; bool ask_for_new_frame_;
const blink::WebSize size_;
gfx::Size last_size;
std::vector<uint8_t> temp_data_;
size_t temp_data_stride_;
SkImageInfo image_info_;
media::VideoFramePool frame_pool_; media::VideoFramePool frame_pool_;
base::Optional<base::TimeTicks> first_frame_ticks_;
scoped_refptr<media::VideoFrame> last_frame_; scoped_refptr<media::VideoFrame> last_frame_;
const scoped_refptr<base::SingleThreadTaskRunner> io_task_runner_; const scoped_refptr<base::SingleThreadTaskRunner> io_task_runner_;
......
...@@ -98,8 +98,6 @@ class CanvasCaptureHandlerTest ...@@ -98,8 +98,6 @@ class CanvasCaptureHandlerTest
else else
EXPECT_EQ(media::PIXEL_FORMAT_YV12A, video_frame->format()); EXPECT_EQ(media::PIXEL_FORMAT_YV12A, video_frame->format());
EXPECT_EQ(video_frame->timestamp().InMilliseconds(),
(estimated_capture_time - base::TimeTicks()).InMilliseconds());
const gfx::Size& size = video_frame->visible_rect().size(); const gfx::Size& size = video_frame->visible_rect().size();
EXPECT_EQ(expected_width, size.width()); EXPECT_EQ(expected_width, size.width());
EXPECT_EQ(expected_height, size.height()); EXPECT_EQ(expected_height, size.height());
...@@ -191,8 +189,8 @@ TEST_P(CanvasCaptureHandlerTest, GetFormatsStartAndStop) { ...@@ -191,8 +189,8 @@ TEST_P(CanvasCaptureHandlerTest, GetFormatsStartAndStop) {
canvas_capture_handler_->SendNewFrame( canvas_capture_handler_->SendNewFrame(
GenerateTestImage(testing::get<0>(GetParam()), GenerateTestImage(testing::get<0>(GetParam()),
testing::get<1>(GetParam()), testing::get<1>(GetParam()),
testing::get<2>(GetParam())) testing::get<2>(GetParam())),
.get()); nullptr);
run_loop.Run(); run_loop.Run();
source->StopCapture(); source->StopCapture();
...@@ -213,11 +211,12 @@ TEST_P(CanvasCaptureHandlerTest, VerifyFrame) { ...@@ -213,11 +211,12 @@ TEST_P(CanvasCaptureHandlerTest, VerifyFrame) {
EXPECT_CALL(*this, DoOnRunning(true)).Times(1); EXPECT_CALL(*this, DoOnRunning(true)).Times(1);
media::VideoCaptureParams params; media::VideoCaptureParams params;
source->StartCapture( source->StartCapture(
params, base::Bind(&CanvasCaptureHandlerTest::OnVerifyDeliveredFrame, params,
base::Unretained(this), opaque_frame, width, height), base::Bind(&CanvasCaptureHandlerTest::OnVerifyDeliveredFrame,
base::Unretained(this), opaque_frame, width, height),
base::Bind(&CanvasCaptureHandlerTest::OnRunning, base::Unretained(this))); base::Bind(&CanvasCaptureHandlerTest::OnRunning, base::Unretained(this)));
canvas_capture_handler_->SendNewFrame( canvas_capture_handler_->SendNewFrame(
GenerateTestImage(opaque_frame, width, height).get()); GenerateTestImage(opaque_frame, width, height), nullptr);
run_loop.RunUntilIdle(); run_loop.RunUntilIdle();
} }
......
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
#include "content/renderer/webgraphicscontext3d_provider_impl.h" #include "content/renderer/webgraphicscontext3d_provider_impl.h"
#include "components/viz/common/gl_helper.h"
#include "gpu/command_buffer/client/context_support.h" #include "gpu/command_buffer/client/context_support.h"
#include "services/ui/public/cpp/gpu/context_provider_command_buffer.h" #include "services/ui/public/cpp/gpu/context_provider_command_buffer.h"
...@@ -34,6 +35,10 @@ GrContext* WebGraphicsContext3DProviderImpl::GetGrContext() { ...@@ -34,6 +35,10 @@ GrContext* WebGraphicsContext3DProviderImpl::GetGrContext() {
return provider_->GrContext(); return provider_->GrContext();
} }
void WebGraphicsContext3DProviderImpl::InvalidateGrContext(uint32_t state) {
return provider_->InvalidateGrContext(state);
}
const gpu::Capabilities& WebGraphicsContext3DProviderImpl::GetCapabilities() const gpu::Capabilities& WebGraphicsContext3DProviderImpl::GetCapabilities()
const { const {
return provider_->ContextCapabilities(); return provider_->ContextCapabilities();
...@@ -44,6 +49,14 @@ const gpu::GpuFeatureInfo& WebGraphicsContext3DProviderImpl::GetGpuFeatureInfo() ...@@ -44,6 +49,14 @@ const gpu::GpuFeatureInfo& WebGraphicsContext3DProviderImpl::GetGpuFeatureInfo()
return provider_->GetGpuFeatureInfo(); return provider_->GetGpuFeatureInfo();
} }
viz::GLHelper* WebGraphicsContext3DProviderImpl::GetGLHelper() {
if (!gl_helper_) {
gl_helper_ = std::make_unique<viz::GLHelper>(provider_->ContextGL(),
provider_->ContextSupport());
}
return gl_helper_.get();
}
bool WebGraphicsContext3DProviderImpl::IsSoftwareRendering() const { bool WebGraphicsContext3DProviderImpl::IsSoftwareRendering() const {
return software_rendering_; return software_rendering_;
} }
......
...@@ -14,12 +14,16 @@ ...@@ -14,12 +14,16 @@
namespace gpu { namespace gpu {
namespace gles2 { namespace gles2 {
class GLES2Interface; class GLES2Interface;
} } // namespace gles2
} } // namespace gpu
namespace ui { namespace ui {
class ContextProviderCommandBuffer; class ContextProviderCommandBuffer;
} } // namespace ui
namespace viz {
class GLHelper;
} // namespace viz
namespace content { namespace content {
...@@ -36,8 +40,10 @@ class CONTENT_EXPORT WebGraphicsContext3DProviderImpl ...@@ -36,8 +40,10 @@ class CONTENT_EXPORT WebGraphicsContext3DProviderImpl
bool BindToCurrentThread() override; bool BindToCurrentThread() override;
gpu::gles2::GLES2Interface* ContextGL() override; gpu::gles2::GLES2Interface* ContextGL() override;
GrContext* GetGrContext() override; GrContext* GetGrContext() override;
void InvalidateGrContext(uint32_t state) override;
const gpu::Capabilities& GetCapabilities() const override; const gpu::Capabilities& GetCapabilities() const override;
const gpu::GpuFeatureInfo& GetGpuFeatureInfo() const override; const gpu::GpuFeatureInfo& GetGpuFeatureInfo() const override;
viz::GLHelper* GetGLHelper() override;
bool IsSoftwareRendering() const override; bool IsSoftwareRendering() const override;
void SetLostContextCallback(const base::Closure&) override; void SetLostContextCallback(const base::Closure&) override;
void SetErrorMessageCallback( void SetErrorMessageCallback(
...@@ -53,6 +59,7 @@ class CONTENT_EXPORT WebGraphicsContext3DProviderImpl ...@@ -53,6 +59,7 @@ class CONTENT_EXPORT WebGraphicsContext3DProviderImpl
void OnContextLost() override; void OnContextLost() override;
scoped_refptr<ui::ContextProviderCommandBuffer> provider_; scoped_refptr<ui::ContextProviderCommandBuffer> provider_;
std::unique_ptr<viz::GLHelper> gl_helper_;
const bool software_rendering_; const bool software_rendering_;
base::Closure context_lost_callback_; base::Closure context_lost_callback_;
......
...@@ -29,7 +29,7 @@ const MAX_ALPHA = 255; ...@@ -29,7 +29,7 @@ const MAX_ALPHA = 255;
const TOLERANCE = 10; const TOLERANCE = 10;
// This function draws a colored rectangle on the canvas. // This function draws a colored rectangle on the canvas.
function draw(canvasId, contextType, colorRgba) { function draw(canvasId, contextType, colorRgba, alphaContext) {
return new Promise(function(resolve, reject) { return new Promise(function(resolve, reject) {
// Wrapping the update in requestAnimationFrame is required for this to be // Wrapping the update in requestAnimationFrame is required for this to be
// a regression test for crbug.com/702446. requestAnimationFrame exposes // a regression test for crbug.com/702446. requestAnimationFrame exposes
...@@ -37,7 +37,7 @@ function draw(canvasId, contextType, colorRgba) { ...@@ -37,7 +37,7 @@ function draw(canvasId, contextType, colorRgba) {
// frame clear that is caused by the {preserveDrawingBuffer: false} option // frame clear that is caused by the {preserveDrawingBuffer: false} option
// on webgl contexts. // on webgl contexts.
requestAnimationFrame(function() { requestAnimationFrame(function() {
var context = canvasId.getContext(contextType); var context = canvasId.getContext(contextType, {alpha : alphaContext});
if (contextType == '2d') { if (contextType == '2d') {
context.clearRect(0, 0, canvasId.clientWidth, canvasId.clientHeight); context.clearRect(0, 0, canvasId.clientWidth, canvasId.clientHeight);
context.fillStyle = 'rgba(' + colorRgba.join() + ')'; context.fillStyle = 'rgba(' + colorRgba.join() + ')';
...@@ -69,23 +69,24 @@ function* getPixelGenerator(pixelData) { ...@@ -69,23 +69,24 @@ function* getPixelGenerator(pixelData) {
} }
// This function checks the color correctness of the whole video frame. // This function checks the color correctness of the whole video frame.
function isVideoColor(videoId, canvasId, colorRgba) { function isVideoColor(videoId, canvasId, colorRgba, alphaContext) {
var pixelIterator = getPixelGenerator(getPixels(videoId, canvasId)); var pixelIterator = getPixelGenerator(getPixels(videoId, canvasId));
for (var pixelRgba of pixelIterator) { for (var pixelRgba of pixelIterator) {
if (!isPixelColor(pixelRgba, colorRgba)) if (!isPixelColor(pixelRgba, colorRgba, alphaContext))
return false; return false;
} }
return true; return true;
} }
// This function checks the rgba color of a single pixel in the video. // This function checks the rgba color of a single pixel in the video.
function isPixelColor(pixel, color) { function isPixelColor(pixel, color, alphaContext) {
var expectedColor = color.slice(0); var expectedColor = color.slice(0);
// The css rgba() function takes an alpha channel (a) such as 0 <= a <= 1, // The css rgba() function takes an alpha channel (a) such as 0 <= a <= 1,
// but context.getImageData() returns array of rgba data with alpha // but context.getImageData() returns array of rgba data with alpha
// channel (a') such as 0 <= a' <= 255. // channel (a') such as 0 <= a' <= 255.
var checkLength = alphaContext ? color.length : color.length - 1;
expectedColor[expectedColor.length - 1] *= MAX_ALPHA; expectedColor[expectedColor.length - 1] *= MAX_ALPHA;
for (var i = 0; i < color.length; i++) { for (var i = 0; i < checkLength; i++) {
if (Math.abs(pixel[i] - expectedColor[i]) > TOLERANCE) { if (Math.abs(pixel[i] - expectedColor[i]) > TOLERANCE) {
console.log('Expected ' + expectedColor + ', got ' + pixel); console.log('Expected ' + expectedColor + ', got ' + pixel);
return false; return false;
...@@ -103,74 +104,93 @@ function connectStream(contextType) { ...@@ -103,74 +104,93 @@ function connectStream(contextType) {
} }
// This function runs the canvas capture rgba color checks. // This function runs the canvas capture rgba color checks.
function testCanvas2DCaptureColors() { function testCanvas2DCaptureColors(alphaContext) {
connectStream('2d'); connectStream('2d');
doCanvasCaptureAndCheckRgba('2d', RED) doCanvasCaptureAndCheckRgba('2d', RED, alphaContext)
.then(function() { .then(function() {
return doCanvasCaptureAndCheckRgba('2d', GREEN); return doCanvasCaptureAndCheckRgba('2d', GREEN, alphaContext);
}) })
.then(function() { .then(function() {
return doCanvasCaptureAndCheckRgba('2d', BLUE); return doCanvasCaptureAndCheckRgba('2d', BLUE, alphaContext);
}) })
.then(function() { .then(function() {
return doCanvasCaptureAndCheckRgba('2d', RED_WITH_ALPHA); return doCanvasCaptureAndCheckRgba('2d', RED_WITH_ALPHA, alphaContext);
}) })
.then(function() { .then(function() {
return doCanvasCaptureAndCheckRgba('2d', GREEN_WITH_ALPHA); return doCanvasCaptureAndCheckRgba('2d', GREEN_WITH_ALPHA,
alphaContext);
}) })
.then(function() { .then(function() {
return doCanvasCaptureAndCheckRgba('2d', BLUE_WITH_ALPHA); return doCanvasCaptureAndCheckRgba('2d', BLUE_WITH_ALPHA, alphaContext);
}) })
.catch(function(err) { .catch(function(err) { window.domAutomationController.send(err); })
window.domAutomationController.send(err); .then(function() { window.domAutomationController.send('OK'); });
})
.then(function() {
window.domAutomationController.send('OK');
});
} }
function testCanvasWebGLCaptureColors() { function testCanvasWebGLCaptureColors(alphaContext) {
connectStream('webgl'); connectStream('webgl');
doCanvasCaptureAndCheckRgba('webgl', RED) doCanvasCaptureAndCheckRgba('webgl', RED, alphaContext)
.then(function() { .then(function() {
return doCanvasCaptureAndCheckRgba('webgl', GREEN); return doCanvasCaptureAndCheckRgba('webgl', GREEN, alphaContext);
}) })
.then(function() { .then(function() {
return doCanvasCaptureAndCheckRgba('webgl', BLUE); return doCanvasCaptureAndCheckRgba('webgl', BLUE, alphaContext);
}) })
.then(function() { .then(function() {
return doCanvasCaptureAndCheckRgba('webgl', RED_WITH_ALPHA); return doCanvasCaptureAndCheckRgba('webgl', RED_WITH_ALPHA,
alphaContext);
}) })
.then(function() { .then(function() {
return doCanvasCaptureAndCheckRgba('webgl', GREEN_WITH_ALPHA); return doCanvasCaptureAndCheckRgba('webgl', GREEN_WITH_ALPHA,
alphaContext);
}) })
.then(function() { .then(function() {
return doCanvasCaptureAndCheckRgba('webgl', BLUE_WITH_ALPHA); return doCanvasCaptureAndCheckRgba('webgl', BLUE_WITH_ALPHA,
alphaContext);
}) })
.catch(function(err) { .catch(function(err) { window.domAutomationController.send(err); })
window.domAutomationController.send(err); .then(function() { window.domAutomationController.send('OK'); });
}
// Forces a context loss between captured frames and checks if capture continues
// as expected.
function testCanvas2DContextLoss(alphaContext) {
var contextType = '2d';
connectStream(contextType);
var canvas = document.getElementById('canvas-' + contextType);
doCanvasCaptureAndCheckRgba(contextType, RED, alphaContext)
.then(function() {
window.internals.loseSharedGraphicsContext3D();
// For the canvas to realize its Graphics context was lost we must try
// to use the contents of the canvas.
var imageData = canvas.getContext(contextType).getImageData(0, 0, 1, 1);
return doCanvasCaptureAndCheckRgba(contextType, BLUE, alphaContext);
}) })
.then(function() { .then(function() {
window.domAutomationController.send('OK'); window.domAutomationController.send('OK');
}, function(err) {
window.domAutomationController.send(err);
}); });
} }
// This function fills a canvas with one rgba color and canvas-captures it // This function fills a canvas with one rgba color and canvas-captures it
// to a video element. We then snapshot the video element to // to a video element. We then snapshot the video element to
// another canvas and checks the color is what we expect. // another canvas and checks the color is what we expect.
function doCanvasCaptureAndCheckRgba(contextType, colorRgba) { function doCanvasCaptureAndCheckRgba(contextType, colorRgba, alphaContext) {
return new Promise(function(resolve, reject) { return new Promise(function(resolve, reject) {
var canvas = document.getElementById('canvas-' + contextType); var canvas = document.getElementById('canvas-' + contextType);
var video = document.getElementById('canvas-' + contextType + '-local-view'); var video = document.getElementById('canvas-' + contextType + '-local-view');
var snapshotCanvas = document.getElementById('local-view-canvas'); var snapshotCanvas = document.getElementById('local-view-canvas');
draw(canvas, contextType, colorRgba) draw(canvas, contextType, colorRgba, alphaContext)
.then(function() { .then(function() {
return waitFor('Verify the canvas color is as expected', return waitFor('Verify the canvas color is as expected',
function() { function() {
return isVideoColor(video, snapshotCanvas, colorRgba); return isVideoColor(video, snapshotCanvas, colorRgba,
alphaContext);
}); });
}) })
.catch(function(err) { .catch(function(err) {
......
...@@ -583,7 +583,7 @@ void HTMLCanvasElement::NotifyListenersCanvasChanged() { ...@@ -583,7 +583,7 @@ void HTMLCanvasElement::NotifyListenersCanvasChanged() {
source_image->PaintImageForCurrentFrame().GetSkImage(); source_image->PaintImageForCurrentFrame().GetSkImage();
for (CanvasDrawListener* listener : listeners_) { for (CanvasDrawListener* listener : listeners_) {
if (listener->NeedsNewFrame()) { if (listener->NeedsNewFrame()) {
listener->SendNewFrame(image); listener->SendNewFrame(image, source_image->ContextProviderWrapper());
} }
} }
} }
......
...@@ -4,15 +4,18 @@ ...@@ -4,15 +4,18 @@
#include "core/html/canvas/CanvasDrawListener.h" #include "core/html/canvas/CanvasDrawListener.h"
#include "platform/graphics/WebGraphicsContext3DProviderWrapper.h"
#include "third_party/skia/include/core/SkImage.h" #include "third_party/skia/include/core/SkImage.h"
#include <memory>
namespace blink { namespace blink {
CanvasDrawListener::~CanvasDrawListener() {} CanvasDrawListener::~CanvasDrawListener() {}
void CanvasDrawListener::SendNewFrame(sk_sp<SkImage> image) { void CanvasDrawListener::SendNewFrame(
handler_->SendNewFrame(image.get()); sk_sp<SkImage> image,
WeakPtr<WebGraphicsContext3DProviderWrapper> context_provider) {
handler_->SendNewFrame(
image, context_provider ? context_provider->ContextProvider() : nullptr);
} }
bool CanvasDrawListener::NeedsNewFrame() const { bool CanvasDrawListener::NeedsNewFrame() const {
......
...@@ -5,20 +5,24 @@ ...@@ -5,20 +5,24 @@
#ifndef CanvasDrawListener_h #ifndef CanvasDrawListener_h
#define CanvasDrawListener_h #define CanvasDrawListener_h
#include <memory>
#include "core/CoreExport.h" #include "core/CoreExport.h"
#include "platform/heap/Handle.h" #include "platform/heap/Handle.h"
#include "platform/wtf/WeakPtr.h"
#include "public/platform/WebCanvasCaptureHandler.h" #include "public/platform/WebCanvasCaptureHandler.h"
#include "third_party/skia/include/core/SkRefCnt.h" #include "third_party/skia/include/core/SkRefCnt.h"
#include <memory>
class SkImage; class SkImage;
namespace blink { namespace blink {
class WebGraphicsContext3DProviderWrapper;
class CORE_EXPORT CanvasDrawListener : public GarbageCollectedMixin { class CORE_EXPORT CanvasDrawListener : public GarbageCollectedMixin {
public: public:
virtual ~CanvasDrawListener(); virtual ~CanvasDrawListener();
virtual void SendNewFrame(sk_sp<SkImage>); virtual void SendNewFrame(sk_sp<SkImage>,
WeakPtr<WebGraphicsContext3DProviderWrapper>);
bool NeedsNewFrame() const; bool NeedsNewFrame() const;
void RequestFrame(); void RequestFrame();
......
...@@ -5,7 +5,6 @@ ...@@ -5,7 +5,6 @@
#include "modules/mediacapturefromelement/OnRequestCanvasDrawListener.h" #include "modules/mediacapturefromelement/OnRequestCanvasDrawListener.h"
#include "third_party/skia/include/core/SkImage.h" #include "third_party/skia/include/core/SkImage.h"
#include <memory>
namespace blink { namespace blink {
...@@ -21,9 +20,11 @@ OnRequestCanvasDrawListener* OnRequestCanvasDrawListener::Create( ...@@ -21,9 +20,11 @@ OnRequestCanvasDrawListener* OnRequestCanvasDrawListener::Create(
return new OnRequestCanvasDrawListener(std::move(handler)); return new OnRequestCanvasDrawListener(std::move(handler));
} }
void OnRequestCanvasDrawListener::SendNewFrame(sk_sp<SkImage> image) { void OnRequestCanvasDrawListener::SendNewFrame(
sk_sp<SkImage> image,
WeakPtr<WebGraphicsContext3DProviderWrapper> context_provider) {
frame_capture_requested_ = false; frame_capture_requested_ = false;
CanvasDrawListener::SendNewFrame(std::move(image)); CanvasDrawListener::SendNewFrame(image, context_provider);
} }
} // namespace blink } // namespace blink
...@@ -5,11 +5,12 @@ ...@@ -5,11 +5,12 @@
#ifndef OnRequestCanvasDrawListener_h #ifndef OnRequestCanvasDrawListener_h
#define OnRequestCanvasDrawListener_h #define OnRequestCanvasDrawListener_h
#include <memory>
#include "core/html/canvas/CanvasDrawListener.h" #include "core/html/canvas/CanvasDrawListener.h"
#include "platform/heap/Handle.h" #include "platform/heap/Handle.h"
#include "platform/wtf/WeakPtr.h"
#include "public/platform/WebCanvasCaptureHandler.h" #include "public/platform/WebCanvasCaptureHandler.h"
#include "third_party/skia/include/core/SkRefCnt.h" #include "third_party/skia/include/core/SkRefCnt.h"
#include <memory>
namespace blink { namespace blink {
...@@ -22,7 +23,8 @@ class OnRequestCanvasDrawListener final ...@@ -22,7 +23,8 @@ class OnRequestCanvasDrawListener final
~OnRequestCanvasDrawListener() override; ~OnRequestCanvasDrawListener() override;
static OnRequestCanvasDrawListener* Create( static OnRequestCanvasDrawListener* Create(
std::unique_ptr<WebCanvasCaptureHandler>); std::unique_ptr<WebCanvasCaptureHandler>);
void SendNewFrame(sk_sp<SkImage>) override; void SendNewFrame(sk_sp<SkImage>,
WeakPtr<WebGraphicsContext3DProviderWrapper>) override;
void Trace(blink::Visitor* visitor) override {} void Trace(blink::Visitor* visitor) override {}
......
...@@ -35,9 +35,11 @@ TimedCanvasDrawListener* TimedCanvasDrawListener::Create( ...@@ -35,9 +35,11 @@ TimedCanvasDrawListener* TimedCanvasDrawListener::Create(
return listener; return listener;
} }
void TimedCanvasDrawListener::SendNewFrame(sk_sp<SkImage> image) { void TimedCanvasDrawListener::SendNewFrame(
sk_sp<SkImage> image,
WeakPtr<WebGraphicsContext3DProviderWrapper> context_provider) {
frame_capture_requested_ = false; frame_capture_requested_ = false;
CanvasDrawListener::SendNewFrame(std::move(image)); CanvasDrawListener::SendNewFrame(image, context_provider);
} }
void TimedCanvasDrawListener::RequestFrameTimerFired(TimerBase*) { void TimedCanvasDrawListener::RequestFrameTimerFired(TimerBase*) {
......
...@@ -5,12 +5,14 @@ ...@@ -5,12 +5,14 @@
#ifndef TimedCanvasDrawListener_h #ifndef TimedCanvasDrawListener_h
#define TimedCanvasDrawListener_h #define TimedCanvasDrawListener_h
#include <memory>
#include "core/html/canvas/CanvasDrawListener.h" #include "core/html/canvas/CanvasDrawListener.h"
#include "platform/Timer.h" #include "platform/Timer.h"
#include "platform/heap/Handle.h" #include "platform/heap/Handle.h"
#include "platform/wtf/WeakPtr.h"
#include "public/platform/WebCanvasCaptureHandler.h" #include "public/platform/WebCanvasCaptureHandler.h"
#include "third_party/skia/include/core/SkRefCnt.h" #include "third_party/skia/include/core/SkRefCnt.h"
#include <memory>
namespace blink { namespace blink {
class ExecutionContext; class ExecutionContext;
...@@ -26,7 +28,8 @@ class TimedCanvasDrawListener final ...@@ -26,7 +28,8 @@ class TimedCanvasDrawListener final
std::unique_ptr<WebCanvasCaptureHandler>, std::unique_ptr<WebCanvasCaptureHandler>,
double frame_rate, double frame_rate,
ExecutionContext*); ExecutionContext*);
void SendNewFrame(sk_sp<SkImage>) override; void SendNewFrame(sk_sp<SkImage>,
WeakPtr<WebGraphicsContext3DProviderWrapper>) override;
void Trace(blink::Visitor* visitor) override {} void Trace(blink::Visitor* visitor) override {}
......
...@@ -41,6 +41,7 @@ class WebGraphicsContext3DProviderForTests ...@@ -41,6 +41,7 @@ class WebGraphicsContext3DProviderForTests
// Not used by WebGL code. // Not used by WebGL code.
GrContext* GetGrContext() override { return nullptr; } GrContext* GetGrContext() override { return nullptr; }
void InvalidateGrContext(uint32_t state) override {}
bool BindToCurrentThread() override { return false; } bool BindToCurrentThread() override { return false; }
const gpu::Capabilities& GetCapabilities() const override { const gpu::Capabilities& GetCapabilities() const override {
return capabilities_; return capabilities_;
...@@ -48,6 +49,7 @@ class WebGraphicsContext3DProviderForTests ...@@ -48,6 +49,7 @@ class WebGraphicsContext3DProviderForTests
const gpu::GpuFeatureInfo& GetGpuFeatureInfo() const override { const gpu::GpuFeatureInfo& GetGpuFeatureInfo() const override {
return gpu_feature_info_; return gpu_feature_info_;
} }
viz::GLHelper* GetGLHelper() override { return nullptr; }
void SetLostContextCallback(const base::Closure&) {} void SetLostContextCallback(const base::Closure&) {}
void SetErrorMessageCallback( void SetErrorMessageCallback(
const base::Callback<void(const char*, int32_t id)>&) {} const base::Callback<void(const char*, int32_t id)>&) {}
......
...@@ -25,6 +25,9 @@ class FakeWebGraphicsContext3DProvider : public WebGraphicsContext3DProvider { ...@@ -25,6 +25,9 @@ class FakeWebGraphicsContext3DProvider : public WebGraphicsContext3DProvider {
} }
GrContext* GetGrContext() override { return gr_context_.get(); } GrContext* GetGrContext() override { return gr_context_.get(); }
void InvalidateGrContext(uint32_t state) override {
gr_context_->resetContext(state);
}
const gpu::Capabilities& GetCapabilities() const override { const gpu::Capabilities& GetCapabilities() const override {
return capabilities_; return capabilities_;
...@@ -34,6 +37,8 @@ class FakeWebGraphicsContext3DProvider : public WebGraphicsContext3DProvider { ...@@ -34,6 +37,8 @@ class FakeWebGraphicsContext3DProvider : public WebGraphicsContext3DProvider {
return gpu_feature_info_; return gpu_feature_info_;
} }
viz::GLHelper* GetGLHelper() override { return nullptr; }
bool IsSoftwareRendering() const override { return false; } bool IsSoftwareRendering() const override { return false; }
gpu::gles2::GLES2Interface* ContextGL() override { return gl_; } gpu::gles2::GLES2Interface* ContextGL() override { return gl_; }
......
...@@ -7,15 +7,19 @@ ...@@ -7,15 +7,19 @@
#include "WebCommon.h" #include "WebCommon.h"
class SkImage; #include "third_party/skia/include/core/SkImage.h"
#include "third_party/skia/include/core/SkRefCnt.h"
namespace blink { namespace blink {
class WebGraphicsContext3DProvider;
// Platform interface of a CanvasCaptureHandler. // Platform interface of a CanvasCaptureHandler.
class BLINK_PLATFORM_EXPORT WebCanvasCaptureHandler { class BLINK_PLATFORM_EXPORT WebCanvasCaptureHandler {
public: public:
virtual ~WebCanvasCaptureHandler() = default; virtual ~WebCanvasCaptureHandler() = default;
virtual void SendNewFrame(const SkImage*) {} virtual void SendNewFrame(sk_sp<SkImage>,
blink::WebGraphicsContext3DProvider*) {}
virtual bool NeedsNewFrame() const { return false; } virtual bool NeedsNewFrame() const { return false; }
}; };
......
...@@ -44,6 +44,10 @@ class GLES2Interface; ...@@ -44,6 +44,10 @@ class GLES2Interface;
} }
} }
namespace viz {
class GLHelper;
}
namespace blink { namespace blink {
class WebGraphicsContext3DProvider { class WebGraphicsContext3DProvider {
...@@ -53,8 +57,12 @@ class WebGraphicsContext3DProvider { ...@@ -53,8 +57,12 @@ class WebGraphicsContext3DProvider {
virtual gpu::gles2::GLES2Interface* ContextGL() = 0; virtual gpu::gles2::GLES2Interface* ContextGL() = 0;
virtual bool BindToCurrentThread() = 0; virtual bool BindToCurrentThread() = 0;
virtual GrContext* GetGrContext() = 0; virtual GrContext* GetGrContext() = 0;
virtual void InvalidateGrContext(uint32_t state) = 0;
virtual const gpu::Capabilities& GetCapabilities() const = 0; virtual const gpu::Capabilities& GetCapabilities() const = 0;
virtual const gpu::GpuFeatureInfo& GetGpuFeatureInfo() const = 0; virtual const gpu::GpuFeatureInfo& GetGpuFeatureInfo() const = 0;
// Creates a viz::GLHelper after first call and returns that instance. This
// method cannot return null.
virtual viz::GLHelper* GetGLHelper() = 0;
// Returns true if the context is driven by software emulation of GL. In // Returns true if the context is driven by software emulation of GL. In
// this scenario, the compositor would not be using GPU. // this scenario, the compositor would not be using GPU.
......
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