1 // Copyright (c) 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.
12 #include "ppapi/c/pp_errors.h"
13 #include "ppapi/c/ppb_console.h"
14 #include "ppapi/c/ppb_opengles2.h"
15 #include "ppapi/cpp/graphics_3d.h"
16 #include "ppapi/cpp/graphics_3d_client.h"
17 #include "ppapi/cpp/input_event.h"
18 #include "ppapi/cpp/instance.h"
19 #include "ppapi/cpp/module.h"
20 #include "ppapi/cpp/rect.h"
21 #include "ppapi/cpp/var.h"
22 #include "ppapi/cpp/video_decoder.h"
23 #include "ppapi/examples/video_decode/testdata.h"
24 #include "ppapi/lib/gl/include/GLES2/gl2.h"
25 #include "ppapi/lib/gl/include/GLES2/gl2ext.h"
26 #include "ppapi/utility/completion_callback_factory.h"
28 // Use assert as a poor-man's CHECK, even in non-debug mode.
29 // Since <assert.h> redefines assert on every inclusion (it doesn't use
30 // include-guards), make sure this is the last file #include'd in this file.
34 // Assert |context_| isn't holding any GL Errors. Done as a macro instead of a
35 // function to preserve line number information in the failure message.
36 #define assertNoGLError() assert(!gles2_if_->GetError(context_->pp_resource()));
41 Shader() : program(0), texcoord_scale_location(0) {}
45 GLint texcoord_scale_location
;
51 class MyInstance
: public pp::Instance
, public pp::Graphics3DClient
{
53 MyInstance(PP_Instance instance
, pp::Module
* module
);
54 virtual ~MyInstance();
56 // pp::Instance implementation.
57 virtual void DidChangeView(const pp::Rect
& position
,
58 const pp::Rect
& clip_ignored
);
60 // pp::Graphics3DClient implementation.
61 virtual void Graphics3DContextLost() {
62 // TODO(vrk/fischman): Properly reset after a lost graphics context. In
63 // particular need to delete context_ and re-create textures.
64 // Probably have to recreate the decoder from scratch, because old textures
65 // can still be outstanding in the decoder!
66 assert(false && "Unexpectedly lost graphics context");
69 void PaintPicture(Decoder
* decoder
, const PP_VideoPicture
& picture
);
72 // Log an error to the developer console and stderr by creating a temporary
73 // object of this type and streaming to it. Example usage:
74 // LogError(this).s() << "Hello world: " << 42;
77 LogError(MyInstance
* instance
) : instance_(instance
) {}
79 const std::string
& msg
= stream_
.str();
80 instance_
->console_if_
->Log(
81 instance_
->pp_instance(), PP_LOGLEVEL_ERROR
, pp::Var(msg
).pp_var());
82 std::cerr
<< msg
<< std::endl
;
84 // Impl note: it would have been nicer to have LogError derive from
85 // std::ostringstream so that it can be streamed to directly, but lookup
86 // rules turn streamed string literals to hex pointers on output.
87 std::ostringstream
& s() { return stream_
; }
90 MyInstance
* instance_
;
91 std::ostringstream stream_
;
94 void InitializeDecoders();
96 // GL-related functions.
98 void CreateGLObjects();
99 void Create2DProgramOnce();
100 void CreateRectangleARBProgramOnce();
101 Shader
CreateProgram(const char* vertex_shader
, const char* fragment_shader
);
102 void CreateShader(GLuint program
, GLenum type
, const char* source
, int size
);
103 void PaintFinished(int32_t result
, Decoder
* decoder
, PP_VideoPicture picture
);
105 pp::Size plugin_size_
;
107 // When decode outpaces render, we queue up decoded pictures for later
108 // painting. Elements are <decoder,picture>.
109 typedef std::queue
<std::pair
<Decoder
*, PP_VideoPicture
> > PictureQueue
;
110 PictureQueue pictures_pending_paint_
;
112 int num_frames_rendered_
;
113 PP_TimeTicks first_frame_delivered_ticks_
;
114 PP_TimeTicks last_swap_request_ticks_
;
115 PP_TimeTicks swap_ticks_
;
116 pp::CompletionCallbackFactory
<MyInstance
> callback_factory_
;
119 const PPB_Console
* console_if_
;
120 const PPB_Core
* core_if_
;
121 const PPB_OpenGLES2
* gles2_if_
;
124 pp::Graphics3D
* context_
;
125 typedef std::vector
<Decoder
*> DecoderList
;
126 DecoderList video_decoders_
;
128 // Shader program to draw GL_TEXTURE_2D target.
130 // Shader program to draw GL_TEXTURE_RECTANGLE_ARB target.
131 Shader shader_rectangle_arb_
;
136 Decoder(MyInstance
* instance
, int id
, const pp::Graphics3D
& graphics_3d
);
139 int id() const { return id_
; }
140 bool decoding() const { return !flushing_
&& !resetting_
; }
142 void Seek(int frame
);
143 void RecyclePicture(const PP_VideoPicture
& picture
);
146 void InitializeDone(int32_t result
);
147 void Start(int frame
);
148 void DecodeNextFrame();
149 void DecodeDone(int32_t result
);
150 void PictureReady(int32_t result
, PP_VideoPicture picture
);
151 void FlushDone(int32_t result
);
152 void ResetDone(int32_t result
);
154 MyInstance
* instance_
;
157 pp::VideoDecoder
* decoder_
;
158 pp::CompletionCallbackFactory
<Decoder
> callback_factory_
;
160 size_t encoded_data_next_pos_to_decode_
;
161 int next_picture_id_
;
167 // Returns true if the current position is at the start of a NAL unit.
168 static bool LookingAtNAL(const unsigned char* encoded
, size_t pos
) {
169 // H264 frames start with 0, 0, 0, 1 in our test data.
170 return pos
+ 3 < kDataLen
&& encoded
[pos
] == 0 && encoded
[pos
+ 1] == 0 &&
171 encoded
[pos
+ 2] == 0 && encoded
[pos
+ 3] == 1;
174 // Find the start and end of the next frame.
175 static void GetNextFrame(size_t* start_pos
, size_t* end_pos
) {
176 assert(LookingAtNAL(kData
, *start_pos
));
177 *end_pos
= *start_pos
;
179 while (*end_pos
< kDataLen
&& !LookingAtNAL(kData
, *end_pos
)) {
184 Decoder::Decoder(MyInstance
* instance
,
186 const pp::Graphics3D
& graphics_3d
)
187 : instance_(instance
),
189 decoder_(new pp::VideoDecoder(instance
)),
190 callback_factory_(this),
191 encoded_data_next_pos_to_decode_(0),
196 assert(!decoder_
->is_null());
197 const PP_VideoProfile profile
= PP_VIDEOPROFILE_H264MAIN
;
198 decoder_
->Initialize(graphics_3d
,
200 PP_FALSE
/* allow_software_fallback */,
201 callback_factory_
.NewCallback(&Decoder::InitializeDone
));
204 Decoder::~Decoder() {
208 void Decoder::InitializeDone(int32_t result
) {
210 assert(result
== PP_OK
);
215 void Decoder::Start(int frame
) {
219 size_t start_pos
= 0;
221 for (int i
= 0; i
< frame
; i
++)
222 GetNextFrame(&start_pos
, &end_pos
);
223 encoded_data_next_pos_to_decode_
= end_pos
;
225 // Register callback to get the first picture. We call GetPicture again in
226 // PictureReady to continuously receive pictures as they're decoded.
227 decoder_
->GetPicture(
228 callback_factory_
.NewCallbackWithOutput(&Decoder::PictureReady
));
230 // Start the decode loop.
234 void Decoder::Seek(int frame
) {
238 decoder_
->Reset(callback_factory_
.NewCallback(&Decoder::ResetDone
));
241 void Decoder::RecyclePicture(const PP_VideoPicture
& picture
) {
243 decoder_
->RecyclePicture(picture
);
246 void Decoder::DecodeNextFrame() {
248 if (encoded_data_next_pos_to_decode_
<= kDataLen
) {
249 // If we've just reached the end of the bitstream, flush and wait.
250 if (!flushing_
&& encoded_data_next_pos_to_decode_
== kDataLen
) {
252 decoder_
->Flush(callback_factory_
.NewCallback(&Decoder::FlushDone
));
256 // Find the start of the next frame.
257 size_t start_pos
= encoded_data_next_pos_to_decode_
;
259 GetNextFrame(&start_pos
, &end_pos
);
260 encoded_data_next_pos_to_decode_
= end_pos
;
261 // Decode the frame. On completion, DecodeDone will call DecodeNextFrame
262 // to implement a decode loop.
263 uint32_t size
= static_cast<uint32_t>(end_pos
- start_pos
);
264 decoder_
->Decode(next_picture_id_
++,
267 callback_factory_
.NewCallback(&Decoder::DecodeDone
));
271 void Decoder::DecodeDone(int32_t result
) {
273 // Break out of the decode loop on abort.
274 if (result
== PP_ERROR_ABORTED
)
276 assert(result
== PP_OK
);
281 void Decoder::PictureReady(int32_t result
, PP_VideoPicture picture
) {
283 // Break out of the get picture loop on abort.
284 if (result
== PP_ERROR_ABORTED
)
286 assert(result
== PP_OK
);
287 decoder_
->GetPicture(
288 callback_factory_
.NewCallbackWithOutput(&Decoder::PictureReady
));
289 instance_
->PaintPicture(this, picture
);
292 void Decoder::FlushDone(int32_t result
) {
294 assert(result
== PP_OK
|| result
== PP_ERROR_ABORTED
);
298 void Decoder::ResetDone(int32_t result
) {
300 assert(result
== PP_OK
);
304 MyInstance::MyInstance(PP_Instance instance
, pp::Module
* module
)
305 : pp::Instance(instance
),
306 pp::Graphics3DClient(this),
308 num_frames_rendered_(0),
309 first_frame_delivered_ticks_(-1),
310 last_swap_request_ticks_(-1),
312 callback_factory_(this),
314 assert((console_if_
= static_cast<const PPB_Console
*>(
315 module
->GetBrowserInterface(PPB_CONSOLE_INTERFACE
))));
316 assert((core_if_
= static_cast<const PPB_Core
*>(
317 module
->GetBrowserInterface(PPB_CORE_INTERFACE
))));
318 assert((gles2_if_
= static_cast<const PPB_OpenGLES2
*>(
319 module
->GetBrowserInterface(PPB_OPENGLES2_INTERFACE
))));
320 RequestInputEvents(PP_INPUTEVENT_CLASS_MOUSE
);
323 MyInstance::~MyInstance() {
327 PP_Resource graphics_3d
= context_
->pp_resource();
328 if (shader_2d_
.program
)
329 gles2_if_
->DeleteProgram(graphics_3d
, shader_2d_
.program
);
330 if (shader_rectangle_arb_
.program
)
331 gles2_if_
->DeleteProgram(graphics_3d
, shader_rectangle_arb_
.program
);
333 for (DecoderList::iterator it
= video_decoders_
.begin();
334 it
!= video_decoders_
.end();
341 void MyInstance::DidChangeView(const pp::Rect
& position
,
342 const pp::Rect
& clip_ignored
) {
343 if (position
.width() == 0 || position
.height() == 0)
345 if (plugin_size_
.width()) {
346 assert(position
.size() == plugin_size_
);
349 plugin_size_
= position
.size();
351 // Initialize graphics.
353 InitializeDecoders();
356 void MyInstance::InitializeDecoders() {
357 assert(video_decoders_
.empty());
358 // Create two decoders with ids 0 and 1.
359 video_decoders_
.push_back(new Decoder(this, 0, *context_
));
360 video_decoders_
.push_back(new Decoder(this, 1, *context_
));
363 void MyInstance::PaintPicture(Decoder
* decoder
,
364 const PP_VideoPicture
& picture
) {
365 if (first_frame_delivered_ticks_
== -1)
366 assert((first_frame_delivered_ticks_
= core_if_
->GetTimeTicks()) != -1);
368 pictures_pending_paint_
.push(std::make_pair(decoder
, picture
));
372 assert(!is_painting_
);
376 int half_width
= plugin_size_
.width() / 2;
377 int half_height
= plugin_size_
.height() / 2;
378 if (decoder
->id() != 0) {
383 PP_Resource graphics_3d
= context_
->pp_resource();
384 if (picture
.texture_target
== GL_TEXTURE_2D
) {
385 Create2DProgramOnce();
386 gles2_if_
->UseProgram(graphics_3d
, shader_2d_
.program
);
387 gles2_if_
->Uniform2f(
388 graphics_3d
, shader_2d_
.texcoord_scale_location
, 1.0, 1.0);
390 assert(picture
.texture_target
== GL_TEXTURE_RECTANGLE_ARB
);
391 CreateRectangleARBProgramOnce();
392 gles2_if_
->UseProgram(graphics_3d
, shader_rectangle_arb_
.program
);
393 gles2_if_
->Uniform2f(graphics_3d
,
394 shader_rectangle_arb_
.texcoord_scale_location
,
395 picture
.texture_size
.width
,
396 picture
.texture_size
.height
);
399 gles2_if_
->Viewport(graphics_3d
, x
, y
, half_width
, half_height
);
400 gles2_if_
->ActiveTexture(graphics_3d
, GL_TEXTURE0
);
401 gles2_if_
->BindTexture(
402 graphics_3d
, picture
.texture_target
, picture
.texture_id
);
403 gles2_if_
->DrawArrays(graphics_3d
, GL_TRIANGLE_STRIP
, 0, 4);
405 gles2_if_
->UseProgram(graphics_3d
, 0);
407 last_swap_request_ticks_
= core_if_
->GetTimeTicks();
408 assert(PP_OK_COMPLETIONPENDING
==
409 context_
->SwapBuffers(callback_factory_
.NewCallback(
410 &MyInstance::PaintFinished
, decoder
, picture
)));
413 void MyInstance::PaintFinished(int32_t result
,
415 PP_VideoPicture picture
) {
416 assert(result
== PP_OK
);
417 swap_ticks_
+= core_if_
->GetTimeTicks() - last_swap_request_ticks_
;
418 is_painting_
= false;
419 ++num_frames_rendered_
;
420 if (num_frames_rendered_
% 50 == 0) {
421 double elapsed
= core_if_
->GetTimeTicks() - first_frame_delivered_ticks_
;
422 double fps
= (elapsed
> 0) ? num_frames_rendered_
/ elapsed
: 1000;
423 double ms_per_swap
= (swap_ticks_
* 1e3
) / num_frames_rendered_
;
424 LogError(this).s() << "Rendered frames: " << num_frames_rendered_
426 << ", with average ms/swap of: " << ms_per_swap
;
428 decoder
->RecyclePicture(picture
);
429 // Keep painting as long as we have pictures.
430 if (!pictures_pending_paint_
.empty()) {
431 std::pair
<Decoder
*, PP_VideoPicture
> pending
=
432 pictures_pending_paint_
.front();
433 pictures_pending_paint_
.pop();
434 PaintPicture(pending
.first
, pending
.second
);
438 void MyInstance::InitGL() {
439 assert(plugin_size_
.width() && plugin_size_
.height());
440 is_painting_
= false;
443 int32_t context_attributes
[] = {
444 PP_GRAPHICS3DATTRIB_ALPHA_SIZE
, 8,
445 PP_GRAPHICS3DATTRIB_BLUE_SIZE
, 8,
446 PP_GRAPHICS3DATTRIB_GREEN_SIZE
, 8,
447 PP_GRAPHICS3DATTRIB_RED_SIZE
, 8,
448 PP_GRAPHICS3DATTRIB_DEPTH_SIZE
, 0,
449 PP_GRAPHICS3DATTRIB_STENCIL_SIZE
, 0,
450 PP_GRAPHICS3DATTRIB_SAMPLES
, 0,
451 PP_GRAPHICS3DATTRIB_SAMPLE_BUFFERS
, 0,
452 PP_GRAPHICS3DATTRIB_WIDTH
, plugin_size_
.width(),
453 PP_GRAPHICS3DATTRIB_HEIGHT
, plugin_size_
.height(),
454 PP_GRAPHICS3DATTRIB_NONE
,
456 context_
= new pp::Graphics3D(this, context_attributes
);
457 assert(!context_
->is_null());
458 assert(BindGraphics(*context_
));
461 gles2_if_
->ClearColor(context_
->pp_resource(), 1, 0, 0, 1);
462 gles2_if_
->Clear(context_
->pp_resource(), GL_COLOR_BUFFER_BIT
);
469 void MyInstance::CreateGLObjects() {
470 // Assign vertex positions and texture coordinates to buffers for use in
472 static const float kVertices
[] = {
473 -1, 1, -1, -1, 1, 1, 1, -1, // Position coordinates.
474 0, 1, 0, 0, 1, 1, 1, 0, // Texture coordinates.
478 gles2_if_
->GenBuffers(context_
->pp_resource(), 1, &buffer
);
479 gles2_if_
->BindBuffer(context_
->pp_resource(), GL_ARRAY_BUFFER
, buffer
);
481 gles2_if_
->BufferData(context_
->pp_resource(),
489 static const char kVertexShader
[] =
490 "varying vec2 v_texCoord; \n"
491 "attribute vec4 a_position; \n"
492 "attribute vec2 a_texCoord; \n"
493 "uniform vec2 v_scale; \n"
496 " v_texCoord = v_scale * a_texCoord; \n"
497 " gl_Position = a_position; \n"
500 void MyInstance::Create2DProgramOnce() {
501 if (shader_2d_
.program
)
503 static const char kFragmentShader2D
[] =
504 "precision mediump float; \n"
505 "varying vec2 v_texCoord; \n"
506 "uniform sampler2D s_texture; \n"
509 " gl_FragColor = texture2D(s_texture, v_texCoord); \n"
511 shader_2d_
= CreateProgram(kVertexShader
, kFragmentShader2D
);
515 void MyInstance::CreateRectangleARBProgramOnce() {
516 if (shader_rectangle_arb_
.program
)
518 static const char kFragmentShaderRectangle
[] =
519 "#extension GL_ARB_texture_rectangle : require\n"
520 "precision mediump float; \n"
521 "varying vec2 v_texCoord; \n"
522 "uniform sampler2DRect s_texture; \n"
525 " gl_FragColor = texture2DRect(s_texture, v_texCoord).rgba; \n"
527 shader_rectangle_arb_
=
528 CreateProgram(kVertexShader
, kFragmentShaderRectangle
);
531 Shader
MyInstance::CreateProgram(const char* vertex_shader
,
532 const char* fragment_shader
) {
535 // Create shader program.
536 shader
.program
= gles2_if_
->CreateProgram(context_
->pp_resource());
538 shader
.program
, GL_VERTEX_SHADER
, vertex_shader
, strlen(vertex_shader
));
539 CreateShader(shader
.program
,
542 strlen(fragment_shader
));
543 gles2_if_
->LinkProgram(context_
->pp_resource(), shader
.program
);
544 gles2_if_
->UseProgram(context_
->pp_resource(), shader
.program
);
545 gles2_if_
->Uniform1i(
546 context_
->pp_resource(),
547 gles2_if_
->GetUniformLocation(
548 context_
->pp_resource(), shader
.program
, "s_texture"),
552 shader
.texcoord_scale_location
= gles2_if_
->GetUniformLocation(
553 context_
->pp_resource(), shader
.program
, "v_scale");
555 GLint pos_location
= gles2_if_
->GetAttribLocation(
556 context_
->pp_resource(), shader
.program
, "a_position");
557 GLint tc_location
= gles2_if_
->GetAttribLocation(
558 context_
->pp_resource(), shader
.program
, "a_texCoord");
561 gles2_if_
->EnableVertexAttribArray(context_
->pp_resource(), pos_location
);
562 gles2_if_
->VertexAttribPointer(
563 context_
->pp_resource(), pos_location
, 2, GL_FLOAT
, GL_FALSE
, 0, 0);
564 gles2_if_
->EnableVertexAttribArray(context_
->pp_resource(), tc_location
);
565 gles2_if_
->VertexAttribPointer(
566 context_
->pp_resource(),
572 static_cast<float*>(0) + 8); // Skip position coordinates.
574 gles2_if_
->UseProgram(context_
->pp_resource(), 0);
579 void MyInstance::CreateShader(GLuint program
,
583 GLuint shader
= gles2_if_
->CreateShader(context_
->pp_resource(), type
);
584 gles2_if_
->ShaderSource(context_
->pp_resource(), shader
, 1, &source
, &size
);
585 gles2_if_
->CompileShader(context_
->pp_resource(), shader
);
586 gles2_if_
->AttachShader(context_
->pp_resource(), program
, shader
);
587 gles2_if_
->DeleteShader(context_
->pp_resource(), shader
);
590 // This object is the global object representing this plugin library as long
592 class MyModule
: public pp::Module
{
594 MyModule() : pp::Module() {}
595 virtual ~MyModule() {}
597 virtual pp::Instance
* CreateInstance(PP_Instance instance
) {
598 return new MyInstance(instance
, this);
602 } // anonymous namespace
605 // Factory function for your specialization of the Module object.
606 Module
* CreateModule() {
607 return new MyModule();