1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "remoting/client/plugin/pepper_video_renderer_3d.h"
9 #include "base/callback_helpers.h"
10 #include "base/stl_util.h"
11 #include "ppapi/c/pp_codecs.h"
12 #include "ppapi/c/ppb_opengles2.h"
13 #include "ppapi/c/ppb_video_decoder.h"
14 #include "ppapi/cpp/instance.h"
15 #include "ppapi/lib/gl/include/GLES2/gl2.h"
16 #include "ppapi/lib/gl/include/GLES2/gl2ext.h"
17 #include "remoting/proto/video.pb.h"
18 #include "remoting/protocol/performance_tracker.h"
19 #include "remoting/protocol/session_config.h"
23 // The implementation here requires this minimum number of pictures from the
24 // video decoder interface to work.
25 const uint32_t kMinimumPictureCount
= 3;
27 class PepperVideoRenderer3D::PendingPacket
{
29 PendingPacket(scoped_ptr
<VideoPacket
> packet
, const base::Closure
& done
)
30 : packet_(packet
.Pass()),
36 const VideoPacket
* packet() const { return packet_
.get(); }
39 scoped_ptr
<VideoPacket
> packet_
;
40 base::ScopedClosureRunner done_runner_
;
44 class PepperVideoRenderer3D::Picture
{
46 Picture(pp::VideoDecoder
* decoder
, PP_VideoPicture picture
)
47 : decoder_(decoder
), picture_(picture
) {}
48 ~Picture() { decoder_
->RecyclePicture(picture_
); }
50 const PP_VideoPicture
& picture() { return picture_
; }
53 pp::VideoDecoder
* decoder_
;
54 PP_VideoPicture picture_
;
57 PepperVideoRenderer3D::FrameDecodeTimestamp::FrameDecodeTimestamp(
59 base::TimeTicks decode_started_time
)
60 : frame_id(frame_id
), decode_started_time(decode_started_time
) {}
62 PepperVideoRenderer3D::PepperVideoRenderer3D() : callback_factory_(this) {}
64 PepperVideoRenderer3D::~PepperVideoRenderer3D() {
66 gles2_if_
->DeleteProgram(graphics_
.pp_resource(), shader_program_
);
68 STLDeleteElements(&pending_packets_
);
71 bool PepperVideoRenderer3D::Initialize(
72 pp::Instance
* instance
,
73 const ClientContext
& context
,
74 EventHandler
* event_handler
,
75 protocol::PerformanceTracker
* perf_tracker
) {
76 DCHECK(event_handler
);
77 DCHECK(!event_handler_
);
79 event_handler_
= event_handler
;
80 perf_tracker_
= perf_tracker
;
82 const int32_t context_attributes
[] = {
83 PP_GRAPHICS3DATTRIB_ALPHA_SIZE
, 8,
84 PP_GRAPHICS3DATTRIB_BLUE_SIZE
, 8,
85 PP_GRAPHICS3DATTRIB_GREEN_SIZE
, 8,
86 PP_GRAPHICS3DATTRIB_RED_SIZE
, 8,
87 PP_GRAPHICS3DATTRIB_DEPTH_SIZE
, 0,
88 PP_GRAPHICS3DATTRIB_STENCIL_SIZE
, 0,
89 PP_GRAPHICS3DATTRIB_SAMPLES
, 0,
90 PP_GRAPHICS3DATTRIB_SAMPLE_BUFFERS
, 0,
91 PP_GRAPHICS3DATTRIB_WIDTH
, 640,
92 PP_GRAPHICS3DATTRIB_HEIGHT
, 480,
93 PP_GRAPHICS3DATTRIB_NONE
,
95 graphics_
= pp::Graphics3D(instance
, context_attributes
);
97 if (graphics_
.is_null()) {
98 LOG(WARNING
) << "Graphics3D interface is not available.";
101 if (!instance
->BindGraphics(graphics_
)) {
102 LOG(WARNING
) << "Failed to bind Graphics3D.";
106 // Fetch the GLES2 interface to use to render frames.
107 gles2_if_
= static_cast<const PPB_OpenGLES2
*>(
108 pp::Module::Get()->GetBrowserInterface(PPB_OPENGLES2_INTERFACE
));
111 video_decoder_
= pp::VideoDecoder(instance
);
112 if (video_decoder_
.is_null()) {
113 LOG(WARNING
) << "VideoDecoder interface is not available.";
117 PP_Resource graphics_3d
= graphics_
.pp_resource();
119 gles2_if_
->ClearColor(graphics_3d
, 1, 0, 0, 1);
120 gles2_if_
->Clear(graphics_3d
, GL_COLOR_BUFFER_BIT
);
122 // Assign vertex positions and texture coordinates to buffers for use in
124 static const float kVertices
[] = {
125 -1, -1, -1, 1, 1, -1, 1, 1, // Position coordinates.
126 0, 1, 0, 0, 1, 1, 1, 0, // Texture coordinates.
130 gles2_if_
->GenBuffers(graphics_3d
, 1, &buffer
);
131 gles2_if_
->BindBuffer(graphics_3d
, GL_ARRAY_BUFFER
, buffer
);
132 gles2_if_
->BufferData(graphics_3d
, GL_ARRAY_BUFFER
, sizeof(kVertices
),
133 kVertices
, GL_STATIC_DRAW
);
140 void PepperVideoRenderer3D::OnViewChanged(const pp::View
& view
) {
141 pp::Size size
= view
.GetRect().size();
142 float scale
= view
.GetDeviceScale();
143 view_size_
.set(ceilf(size
.width() * scale
), ceilf(size
.height() * scale
));
144 graphics_
.ResizeBuffers(view_size_
.width(), view_size_
.height());
146 force_repaint_
= true;
150 void PepperVideoRenderer3D::EnableDebugDirtyRegion(bool enable
) {
151 debug_dirty_region_
= enable
;
154 void PepperVideoRenderer3D::OnSessionConfig(
155 const protocol::SessionConfig
& config
) {
156 PP_VideoProfile video_profile
= PP_VIDEOPROFILE_VP8_ANY
;
157 switch (config
.video_config().codec
) {
158 case protocol::ChannelConfig::CODEC_VP8
:
159 video_profile
= PP_VIDEOPROFILE_VP8_ANY
;
161 case protocol::ChannelConfig::CODEC_VP9
:
162 video_profile
= PP_VIDEOPROFILE_VP9_ANY
;
168 bool supports_video_decoder_1_1
=
169 pp::Module::Get()->GetBrowserInterface(
170 PPB_VIDEODECODER_INTERFACE_1_1
) != NULL
;
171 int32_t result
= video_decoder_
.Initialize(
172 graphics_
, video_profile
, PP_HARDWAREACCELERATION_WITHFALLBACK
,
173 supports_video_decoder_1_1
? kMinimumPictureCount
: 0,
174 callback_factory_
.NewCallback(&PepperVideoRenderer3D::OnInitialized
));
175 CHECK_EQ(result
, PP_OK_COMPLETIONPENDING
)
176 << "video_decoder_.Initialize() returned " << result
;
179 protocol::VideoStub
* PepperVideoRenderer3D::GetVideoStub() {
183 void PepperVideoRenderer3D::ProcessVideoPacket(scoped_ptr
<VideoPacket
> packet
,
184 const base::Closure
& done
) {
185 base::ScopedClosureRunner
done_runner(done
);
187 perf_tracker_
->RecordVideoPacketStats(*packet
);
189 // Don't need to do anything if the packet is empty. Host sends empty video
190 // packets when the screen is not changing.
191 if (!packet
->data().size())
194 bool resolution_changed
= false;
196 if (packet
->format().has_screen_width() &&
197 packet
->format().has_screen_height()) {
198 webrtc::DesktopSize
frame_size(packet
->format().screen_width(),
199 packet
->format().screen_height());
200 if (!frame_size_
.equals(frame_size
)) {
201 frame_size_
= frame_size
;
202 resolution_changed
= true;
206 if (packet
->format().has_x_dpi() && packet
->format().has_y_dpi()) {
207 webrtc::DesktopVector
frame_dpi(packet
->format().x_dpi(),
208 packet
->format().y_dpi());
209 if (!frame_dpi_
.equals(frame_dpi
)) {
210 frame_dpi_
= frame_dpi
;
211 resolution_changed
= true;
215 if (resolution_changed
)
216 event_handler_
->OnVideoSize(frame_size_
, frame_dpi_
);
218 // Process the frame shape, if supplied.
219 if (packet
->has_use_desktop_shape()) {
220 if (packet
->use_desktop_shape()) {
221 scoped_ptr
<webrtc::DesktopRegion
> shape(new webrtc::DesktopRegion
);
222 for (int i
= 0; i
< packet
->desktop_shape_rects_size(); ++i
) {
223 Rect remoting_rect
= packet
->desktop_shape_rects(i
);
224 shape
->AddRect(webrtc::DesktopRect::MakeXYWH(
225 remoting_rect
.x(), remoting_rect
.y(), remoting_rect
.width(),
226 remoting_rect
.height()));
228 if (!frame_shape_
|| !frame_shape_
->Equals(*shape
)) {
229 frame_shape_
= shape
.Pass();
230 event_handler_
->OnVideoShape(frame_shape_
.get());
232 } else if (frame_shape_
) {
233 frame_shape_
= nullptr;
234 event_handler_
->OnVideoShape(nullptr);
238 // Report the dirty region, for debugging, if requested.
239 if (debug_dirty_region_
) {
240 webrtc::DesktopRegion dirty_region
;
241 for (int i
= 0; i
< packet
->dirty_rects_size(); ++i
) {
242 Rect remoting_rect
= packet
->dirty_rects(i
);
243 dirty_region
.AddRect(webrtc::DesktopRect::MakeXYWH(
244 remoting_rect
.x(), remoting_rect
.y(),
245 remoting_rect
.width(), remoting_rect
.height()));
247 event_handler_
->OnVideoFrameDirtyRegion(dirty_region
);
250 pending_packets_
.push_back(
251 new PendingPacket(packet
.Pass(), done_runner
.Release()));
255 void PepperVideoRenderer3D::OnInitialized(int32_t result
) {
256 // Assume that VP8 and VP9 codecs are always supported by the browser.
257 CHECK_EQ(result
, PP_OK
) << "VideoDecoder::Initialize() failed: " << result
;
258 initialization_finished_
= true;
260 // Start decoding in case a frame was received during decoder initialization.
264 void PepperVideoRenderer3D::DecodeNextPacket() {
265 if (!initialization_finished_
|| decode_pending_
|| pending_packets_
.empty())
269 frame_decode_timestamps_
.push_back(
270 FrameDecodeTimestamp(latest_frame_id_
, base::TimeTicks::Now()));
272 const VideoPacket
* packet
= pending_packets_
.front()->packet();
274 int32_t result
= video_decoder_
.Decode(
275 latest_frame_id_
, packet
->data().size(), packet
->data().data(),
276 callback_factory_
.NewCallback(&PepperVideoRenderer3D::OnDecodeDone
));
277 CHECK_EQ(result
, PP_OK_COMPLETIONPENDING
);
278 decode_pending_
= true;
281 void PepperVideoRenderer3D::OnDecodeDone(int32_t result
) {
282 DCHECK(decode_pending_
);
283 decode_pending_
= false;
285 if (result
!= PP_OK
) {
286 LOG(ERROR
) << "VideoDecoder::Decode() returned " << result
;
287 event_handler_
->OnVideoDecodeError();
291 delete pending_packets_
.front();
292 pending_packets_
.pop_front();
298 void PepperVideoRenderer3D::GetNextPicture() {
299 if (get_picture_pending_
)
303 video_decoder_
.GetPicture(callback_factory_
.NewCallbackWithOutput(
304 &PepperVideoRenderer3D::OnPictureReady
));
305 CHECK_EQ(result
, PP_OK_COMPLETIONPENDING
);
306 get_picture_pending_
= true;
309 void PepperVideoRenderer3D::OnPictureReady(int32_t result
,
310 PP_VideoPicture picture
) {
311 DCHECK(get_picture_pending_
);
312 get_picture_pending_
= false;
314 if (result
!= PP_OK
) {
315 LOG(ERROR
) << "VideoDecoder::GetPicture() returned " << result
;
316 event_handler_
->OnVideoDecodeError();
320 CHECK(!frame_decode_timestamps_
.empty());
321 const FrameDecodeTimestamp
& frame_timer
= frame_decode_timestamps_
.front();
323 if (picture
.decode_id
!= frame_timer
.frame_id
) {
325 << "Received a video packet that didn't contain a complete frame.";
326 event_handler_
->OnVideoDecodeError();
330 base::TimeDelta decode_time
=
331 base::TimeTicks::Now() - frame_timer
.decode_started_time
;
332 perf_tracker_
->RecordDecodeTime(decode_time
.InMilliseconds());
334 frame_decode_timestamps_
.pop_front();
336 next_picture_
.reset(new Picture(&video_decoder_
, picture
));
342 void PepperVideoRenderer3D::PaintIfNeeded() {
343 bool need_repaint
= next_picture_
|| (force_repaint_
&& current_picture_
);
344 if (paint_pending_
|| !need_repaint
)
348 current_picture_
= next_picture_
.Pass();
350 force_repaint_
= false;
351 latest_paint_started_time_
= base::TimeTicks::Now();
353 const PP_VideoPicture
& picture
= current_picture_
->picture();
354 PP_Resource graphics_3d
= graphics_
.pp_resource();
356 EnsureProgramForTexture(picture
.texture_target
);
358 gles2_if_
->UseProgram(graphics_3d
, shader_program_
);
360 // Calculate v_scale passed to the vertex shader.
361 double scale_x
= picture
.visible_rect
.size
.width
;
362 double scale_y
= picture
.visible_rect
.size
.height
;
363 if (picture
.texture_target
!= GL_TEXTURE_RECTANGLE_ARB
) {
364 scale_x
/= picture
.texture_size
.width
;
365 scale_y
/= picture
.texture_size
.height
;
367 gles2_if_
->Uniform2f(graphics_3d
, shader_texcoord_scale_location_
,
370 // Set viewport position & dimensions.
371 gles2_if_
->Viewport(graphics_3d
, 0, 0, view_size_
.width(),
372 view_size_
.height());
374 // Select the texture unit GL_TEXTURE0.
375 gles2_if_
->ActiveTexture(graphics_3d
, GL_TEXTURE0
);
377 // Select the texture.
378 gles2_if_
->BindTexture(graphics_3d
, picture
.texture_target
,
381 // Select linear filter in case the texture needs to be scaled.
382 gles2_if_
->TexParameteri(graphics_3d
, picture
.texture_target
,
383 GL_TEXTURE_MIN_FILTER
, GL_LINEAR
);
385 // When view dimensions are a multiple of the frame size then use
386 // nearest-neighbor scaling to achieve crisper image. Linear filter is used in
388 GLint mag_filter
= GL_LINEAR
;
389 if (view_size_
.width() % picture
.visible_rect
.size
.width
== 0 &&
390 view_size_
.height() % picture
.visible_rect
.size
.height
== 0) {
391 mag_filter
= GL_NEAREST
;
393 gles2_if_
->TexParameteri(graphics_3d
, picture
.texture_target
,
394 GL_TEXTURE_MAG_FILTER
, mag_filter
);
396 // Render texture by drawing a triangle strip with 4 vertices.
397 gles2_if_
->DrawArrays(graphics_3d
, GL_TRIANGLE_STRIP
, 0, 4);
401 // Request PPAPI display the queued texture.
402 int32_t result
= graphics_
.SwapBuffers(
403 callback_factory_
.NewCallback(&PepperVideoRenderer3D::OnPaintDone
));
404 CHECK_EQ(result
, PP_OK_COMPLETIONPENDING
);
405 paint_pending_
= true;
408 void PepperVideoRenderer3D::OnPaintDone(int32_t result
) {
409 CHECK_EQ(result
, PP_OK
) << "Graphics3D::SwapBuffers() failed";
411 paint_pending_
= false;
412 base::TimeDelta paint_time
=
413 base::TimeTicks::Now() - latest_paint_started_time_
;
414 perf_tracker_
->RecordPaintTime(paint_time
.InMilliseconds());
418 void PepperVideoRenderer3D::EnsureProgramForTexture(uint32_t texture_target
) {
419 static const char kVertexShader
[] =
420 "varying vec2 v_texCoord; \n"
421 "attribute vec4 a_position; \n"
422 "attribute vec2 a_texCoord; \n"
423 "uniform vec2 v_scale; \n"
426 " v_texCoord = v_scale * a_texCoord; \n"
427 " gl_Position = a_position; \n"
430 static const char kFragmentShader2D
[] =
431 "precision mediump float; \n"
432 "varying vec2 v_texCoord; \n"
433 "uniform sampler2D s_texture; \n"
436 " gl_FragColor = texture2D(s_texture, v_texCoord); \n"
439 static const char kFragmentShaderRectangle
[] =
440 "#extension GL_ARB_texture_rectangle : require\n"
441 "precision mediump float; \n"
442 "varying vec2 v_texCoord; \n"
443 "uniform sampler2DRect s_texture; \n"
446 " gl_FragColor = texture2DRect(s_texture, v_texCoord).rgba; \n"
449 static const char kFragmentShaderExternal
[] =
450 "#extension GL_OES_EGL_image_external : require\n"
451 "precision mediump float; \n"
452 "varying vec2 v_texCoord; \n"
453 "uniform samplerExternalOES s_texture; \n"
456 " gl_FragColor = texture2D(s_texture, v_texCoord); \n"
459 // Initialize shader program only if texture type has changed.
460 if (current_shader_program_texture_target_
!= texture_target
) {
461 current_shader_program_texture_target_
= texture_target
;
463 if (texture_target
== GL_TEXTURE_2D
) {
464 CreateProgram(kVertexShader
, kFragmentShader2D
);
465 } else if (texture_target
== GL_TEXTURE_RECTANGLE_ARB
) {
466 CreateProgram(kVertexShader
, kFragmentShaderRectangle
);
467 } else if (texture_target
== GL_TEXTURE_EXTERNAL_OES
) {
468 CreateProgram(kVertexShader
, kFragmentShaderExternal
);
470 LOG(FATAL
) << "Unknown texture target: " << texture_target
;
475 void PepperVideoRenderer3D::CreateProgram(const char* vertex_shader
,
476 const char* fragment_shader
) {
477 PP_Resource graphics_3d
= graphics_
.pp_resource();
479 gles2_if_
->DeleteProgram(graphics_3d
, shader_program_
);
481 // Create shader program.
482 shader_program_
= gles2_if_
->CreateProgram(graphics_3d
);
483 CreateShaderProgram(GL_VERTEX_SHADER
, vertex_shader
);
484 CreateShaderProgram(GL_FRAGMENT_SHADER
, fragment_shader
);
485 gles2_if_
->LinkProgram(graphics_3d
, shader_program_
);
486 gles2_if_
->UseProgram(graphics_3d
, shader_program_
);
487 gles2_if_
->Uniform1i(
489 gles2_if_
->GetUniformLocation(graphics_3d
, shader_program_
, "s_texture"),
493 shader_texcoord_scale_location_
= gles2_if_
->GetUniformLocation(
494 graphics_3d
, shader_program_
, "v_scale");
496 GLint pos_location
= gles2_if_
->GetAttribLocation(
497 graphics_3d
, shader_program_
, "a_position");
498 GLint tc_location
= gles2_if_
->GetAttribLocation(
499 graphics_3d
, shader_program_
, "a_texCoord");
502 // Construct the vertex array for DrawArrays(), using the buffer created in
504 gles2_if_
->EnableVertexAttribArray(graphics_3d
, pos_location
);
505 gles2_if_
->VertexAttribPointer(graphics_3d
, pos_location
, 2, GL_FLOAT
,
507 gles2_if_
->EnableVertexAttribArray(graphics_3d
, tc_location
);
508 gles2_if_
->VertexAttribPointer(
509 graphics_3d
, tc_location
, 2, GL_FLOAT
, GL_FALSE
, 0,
510 static_cast<float*>(0) + 8); // Skip position coordinates.
512 gles2_if_
->UseProgram(graphics_3d
, 0);
517 void PepperVideoRenderer3D::CreateShaderProgram(int type
, const char* source
) {
518 int size
= strlen(source
);
519 GLuint shader
= gles2_if_
->CreateShader(graphics_
.pp_resource(), type
);
520 gles2_if_
->ShaderSource(graphics_
.pp_resource(), shader
, 1, &source
, &size
);
521 gles2_if_
->CompileShader(graphics_
.pp_resource(), shader
);
522 gles2_if_
->AttachShader(graphics_
.pp_resource(), shader_program_
, shader
);
523 gles2_if_
->DeleteShader(graphics_
.pp_resource(), shader
);
526 void PepperVideoRenderer3D::CheckGLError() {
527 GLenum error
= gles2_if_
->GetError(graphics_
.pp_resource());
528 CHECK_EQ(error
, static_cast<GLenum
>(GL_NO_ERROR
)) << "GL error: " << error
;
531 } // namespace remoting