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.
6 #include <GLES2/gl2ext.h>
14 #include "ppapi/c/pp_errors.h"
15 #include "ppapi/c/ppb_console.h"
16 #include "ppapi/c/ppb_opengles2.h"
17 #include "ppapi/cpp/graphics_3d.h"
18 #include "ppapi/cpp/graphics_3d_client.h"
19 #include "ppapi/cpp/input_event.h"
20 #include "ppapi/cpp/instance.h"
21 #include "ppapi/cpp/module.h"
22 #include "ppapi/cpp/rect.h"
23 #include "ppapi/cpp/var.h"
24 #include "ppapi/cpp/video_decoder.h"
25 #include "ppapi/utility/completion_callback_factory.h"
27 // VP8 is more likely to work on different versions of Chrome. Undefine this
29 #define USE_VP8_TESTDATA_INSTEAD_OF_H264
32 // Use assert as a poor-man's CHECK, even in non-debug mode.
33 // Since <assert.h> redefines assert on every inclusion (it doesn't use
34 // include-guards), make sure this is the last file #include'd in this file.
38 // Assert |context_| isn't holding any GL Errors. Done as a macro instead of a
39 // function to preserve line number information in the failure message.
40 #define assertNoGLError() assert(!gles2_if_->GetError(context_->pp_resource()));
45 Shader() : program(0), texcoord_scale_location(0) {}
49 GLint texcoord_scale_location
;
55 struct PendingPicture
{
56 PendingPicture(Decoder
* decoder
, const PP_VideoPicture
& picture
)
57 : decoder(decoder
), picture(picture
) {}
61 PP_VideoPicture picture
;
64 class MyInstance
: public pp::Instance
, public pp::Graphics3DClient
{
66 MyInstance(PP_Instance instance
, pp::Module
* module
);
67 virtual ~MyInstance();
69 // pp::Instance implementation.
70 virtual void DidChangeView(const pp::Rect
& position
,
71 const pp::Rect
& clip_ignored
);
72 virtual bool HandleInputEvent(const pp::InputEvent
& event
);
74 // pp::Graphics3DClient implementation.
75 virtual void Graphics3DContextLost() {
76 // TODO(vrk/fischman): Properly reset after a lost graphics context. In
77 // particular need to delete context_ and re-create textures.
78 // Probably have to recreate the decoder from scratch, because old textures
79 // can still be outstanding in the decoder!
80 assert(false && "Unexpectedly lost graphics context");
83 void PaintPicture(Decoder
* decoder
, const PP_VideoPicture
& picture
);
86 // Log an error to the developer console and stderr by creating a temporary
87 // object of this type and streaming to it. Example usage:
88 // LogError(this).s() << "Hello world: " << 42;
91 LogError(MyInstance
* instance
) : instance_(instance
) {}
93 const std::string
& msg
= stream_
.str();
94 instance_
->console_if_
->Log(
95 instance_
->pp_instance(), PP_LOGLEVEL_ERROR
, pp::Var(msg
).pp_var());
96 std::cerr
<< msg
<< std::endl
;
98 // Impl note: it would have been nicer to have LogError derive from
99 // std::ostringstream so that it can be streamed to directly, but lookup
100 // rules turn streamed string literals to hex pointers on output.
101 std::ostringstream
& s() { return stream_
; }
104 MyInstance
* instance_
;
105 std::ostringstream stream_
;
108 void InitializeDecoders();
110 // GL-related functions.
112 void CreateGLObjects();
113 void Create2DProgramOnce();
114 void CreateRectangleARBProgramOnce();
115 void CreateExternalOESProgramOnce();
116 Shader
CreateProgram(const char* vertex_shader
, const char* fragment_shader
);
117 void CreateShader(GLuint program
, GLenum type
, const char* source
, int size
);
118 void PaintNextPicture();
119 void PaintFinished(int32_t result
);
121 pp::Size plugin_size_
;
123 // When decode outpaces render, we queue up decoded pictures for later
125 typedef std::queue
<PendingPicture
> PendingPictureQueue
;
126 PendingPictureQueue pending_pictures_
;
128 int num_frames_rendered_
;
129 PP_TimeTicks first_frame_delivered_ticks_
;
130 PP_TimeTicks last_swap_request_ticks_
;
131 PP_TimeTicks swap_ticks_
;
132 pp::CompletionCallbackFactory
<MyInstance
> callback_factory_
;
135 const PPB_Console
* console_if_
;
136 const PPB_Core
* core_if_
;
137 const PPB_OpenGLES2
* gles2_if_
;
140 pp::Graphics3D
* context_
;
141 typedef std::vector
<Decoder
*> DecoderList
;
142 DecoderList video_decoders_
;
144 // Shader program to draw GL_TEXTURE_2D target.
146 // Shader program to draw GL_TEXTURE_RECTANGLE_ARB target.
147 Shader shader_rectangle_arb_
;
148 // Shader program to draw GL_TEXTURE_EXTERNAL_OES target.
149 Shader shader_external_oes_
;
154 Decoder(MyInstance
* instance
, int id
, const pp::Graphics3D
& graphics_3d
);
157 int id() const { return id_
; }
158 bool flushing() const { return flushing_
; }
159 bool resetting() const { return resetting_
; }
162 void RecyclePicture(const PP_VideoPicture
& picture
);
164 PP_TimeTicks
GetAverageLatency() {
165 return num_pictures_
? total_latency_
/ num_pictures_
: 0;
169 void InitializeDone(int32_t result
);
171 void DecodeNextFrame();
172 void DecodeDone(int32_t result
);
173 void PictureReady(int32_t result
, PP_VideoPicture picture
);
174 void FlushDone(int32_t result
);
175 void ResetDone(int32_t result
);
177 MyInstance
* instance_
;
180 pp::VideoDecoder
* decoder_
;
181 pp::CompletionCallbackFactory
<Decoder
> callback_factory_
;
183 size_t encoded_data_next_pos_to_decode_
;
184 int next_picture_id_
;
188 const PPB_Core
* core_if_
;
189 static const int kMaxDecodeDelay
= 128;
190 PP_TimeTicks decode_time_
[kMaxDecodeDelay
];
191 PP_TimeTicks total_latency_
;
195 #if defined USE_VP8_TESTDATA_INSTEAD_OF_H264
197 // VP8 is stored in an IVF container.
198 // Helpful description: http://wiki.multimedia.cx/index.php?title=IVF
200 static void GetNextFrame(size_t* start_pos
, size_t* end_pos
) {
201 size_t current_pos
= *start_pos
;
202 if (current_pos
== 0)
203 current_pos
= 32; // Skip stream header.
204 uint32_t frame_size
= kData
[current_pos
] + (kData
[current_pos
+ 1] << 8) +
205 (kData
[current_pos
+ 2] << 16) +
206 (kData
[current_pos
+ 3] << 24);
207 current_pos
+= 12; // Skip frame header.
208 *start_pos
= current_pos
;
209 *end_pos
= current_pos
+ frame_size
;
212 #else // !USE_VP8_TESTDATA_INSTEAD_OF_H264
214 // Returns true if the current position is at the start of a NAL unit.
215 static bool LookingAtNAL(const unsigned char* encoded
, size_t pos
) {
216 // H264 frames start with 0, 0, 0, 1 in our test data.
217 return pos
+ 3 < kDataLen
&& encoded
[pos
] == 0 && encoded
[pos
+ 1] == 0 &&
218 encoded
[pos
+ 2] == 0 && encoded
[pos
+ 3] == 1;
221 static void GetNextFrame(size_t* start_pos
, size_t* end_pos
) {
222 assert(LookingAtNAL(kData
, *start_pos
));
223 *end_pos
= *start_pos
;
225 while (*end_pos
< kDataLen
&& !LookingAtNAL(kData
, *end_pos
)) {
230 #endif // USE_VP8_TESTDATA_INSTEAD_OF_H264
232 Decoder::Decoder(MyInstance
* instance
,
234 const pp::Graphics3D
& graphics_3d
)
235 : instance_(instance
),
237 decoder_(new pp::VideoDecoder(instance
)),
238 callback_factory_(this),
239 encoded_data_next_pos_to_decode_(0),
245 core_if_
= static_cast<const PPB_Core
*>(
246 pp::Module::Get()->GetBrowserInterface(PPB_CORE_INTERFACE
));
248 #if defined USE_VP8_TESTDATA_INSTEAD_OF_H264
249 const PP_VideoProfile kBitstreamProfile
= PP_VIDEOPROFILE_VP8_ANY
;
251 const PP_VideoProfile kBitstreamProfile
= PP_VIDEOPROFILE_H264MAIN
;
254 assert(!decoder_
->is_null());
255 decoder_
->Initialize(graphics_3d
,
257 PP_HARDWAREACCELERATION_WITHFALLBACK
,
258 callback_factory_
.NewCallback(&Decoder::InitializeDone
));
261 Decoder::~Decoder() {
265 void Decoder::InitializeDone(int32_t result
) {
267 assert(result
== PP_OK
);
271 void Decoder::Start() {
274 encoded_data_next_pos_to_decode_
= 0;
276 // Register callback to get the first picture. We call GetPicture again in
277 // PictureReady to continuously receive pictures as they're decoded.
278 decoder_
->GetPicture(
279 callback_factory_
.NewCallbackWithOutput(&Decoder::PictureReady
));
281 // Start the decode loop.
285 void Decoder::Reset() {
289 decoder_
->Reset(callback_factory_
.NewCallback(&Decoder::ResetDone
));
292 void Decoder::RecyclePicture(const PP_VideoPicture
& picture
) {
294 decoder_
->RecyclePicture(picture
);
297 void Decoder::DecodeNextFrame() {
299 if (encoded_data_next_pos_to_decode_
<= kDataLen
) {
300 // If we've just reached the end of the bitstream, flush and wait.
301 if (!flushing_
&& encoded_data_next_pos_to_decode_
== kDataLen
) {
303 decoder_
->Flush(callback_factory_
.NewCallback(&Decoder::FlushDone
));
307 // Find the start of the next frame.
308 size_t start_pos
= encoded_data_next_pos_to_decode_
;
310 GetNextFrame(&start_pos
, &end_pos
);
311 encoded_data_next_pos_to_decode_
= end_pos
;
312 // Decode the frame. On completion, DecodeDone will call DecodeNextFrame
313 // to implement a decode loop.
314 uint32_t size
= static_cast<uint32_t>(end_pos
- start_pos
);
315 decode_time_
[next_picture_id_
% kMaxDecodeDelay
] = core_if_
->GetTimeTicks();
316 decoder_
->Decode(next_picture_id_
++,
319 callback_factory_
.NewCallback(&Decoder::DecodeDone
));
323 void Decoder::DecodeDone(int32_t result
) {
325 // Break out of the decode loop on abort.
326 if (result
== PP_ERROR_ABORTED
)
328 assert(result
== PP_OK
);
329 if (!flushing_
&& !resetting_
)
333 void Decoder::PictureReady(int32_t result
, PP_VideoPicture picture
) {
335 // Break out of the get picture loop on abort.
336 if (result
== PP_ERROR_ABORTED
)
338 assert(result
== PP_OK
);
341 PP_TimeTicks latency
= core_if_
->GetTimeTicks() -
342 decode_time_
[picture
.decode_id
% kMaxDecodeDelay
];
343 total_latency_
+= latency
;
345 decoder_
->GetPicture(
346 callback_factory_
.NewCallbackWithOutput(&Decoder::PictureReady
));
347 instance_
->PaintPicture(this, picture
);
350 void Decoder::FlushDone(int32_t result
) {
352 assert(result
== PP_OK
|| result
== PP_ERROR_ABORTED
);
357 void Decoder::ResetDone(int32_t result
) {
359 assert(result
== PP_OK
);
366 MyInstance::MyInstance(PP_Instance instance
, pp::Module
* module
)
367 : pp::Instance(instance
),
368 pp::Graphics3DClient(this),
370 num_frames_rendered_(0),
371 first_frame_delivered_ticks_(-1),
372 last_swap_request_ticks_(-1),
374 callback_factory_(this),
376 console_if_
= static_cast<const PPB_Console
*>(
377 pp::Module::Get()->GetBrowserInterface(PPB_CONSOLE_INTERFACE
));
378 core_if_
= static_cast<const PPB_Core
*>(
379 pp::Module::Get()->GetBrowserInterface(PPB_CORE_INTERFACE
));
380 gles2_if_
= static_cast<const PPB_OpenGLES2
*>(
381 pp::Module::Get()->GetBrowserInterface(PPB_OPENGLES2_INTERFACE
));
383 RequestInputEvents(PP_INPUTEVENT_CLASS_MOUSE
);
386 MyInstance::~MyInstance() {
390 PP_Resource graphics_3d
= context_
->pp_resource();
391 if (shader_2d_
.program
)
392 gles2_if_
->DeleteProgram(graphics_3d
, shader_2d_
.program
);
393 if (shader_rectangle_arb_
.program
)
394 gles2_if_
->DeleteProgram(graphics_3d
, shader_rectangle_arb_
.program
);
395 if (shader_external_oes_
.program
)
396 gles2_if_
->DeleteProgram(graphics_3d
, shader_external_oes_
.program
);
398 for (DecoderList::iterator it
= video_decoders_
.begin();
399 it
!= video_decoders_
.end();
406 void MyInstance::DidChangeView(const pp::Rect
& position
,
407 const pp::Rect
& clip_ignored
) {
408 if (position
.width() == 0 || position
.height() == 0)
410 if (plugin_size_
.width()) {
411 assert(position
.size() == plugin_size_
);
414 plugin_size_
= position
.size();
416 // Initialize graphics.
418 InitializeDecoders();
421 bool MyInstance::HandleInputEvent(const pp::InputEvent
& event
) {
422 switch (event
.GetType()) {
423 case PP_INPUTEVENT_TYPE_MOUSEDOWN
: {
424 pp::MouseInputEvent
mouse_event(event
);
425 // Reset all decoders on mouse down.
426 if (mouse_event
.GetButton() == PP_INPUTEVENT_MOUSEBUTTON_LEFT
) {
428 for (size_t i
= 0; i
< video_decoders_
.size(); i
++) {
429 if (!video_decoders_
[i
]->resetting())
430 video_decoders_
[i
]->Reset();
441 void MyInstance::InitializeDecoders() {
442 assert(video_decoders_
.empty());
443 // Create two decoders with ids 0 and 1.
444 video_decoders_
.push_back(new Decoder(this, 0, *context_
));
445 video_decoders_
.push_back(new Decoder(this, 1, *context_
));
448 void MyInstance::PaintPicture(Decoder
* decoder
,
449 const PP_VideoPicture
& picture
) {
450 if (first_frame_delivered_ticks_
== -1)
451 assert((first_frame_delivered_ticks_
= core_if_
->GetTimeTicks()) != -1);
453 pending_pictures_
.push(PendingPicture(decoder
, picture
));
458 void MyInstance::PaintNextPicture() {
459 assert(!is_painting_
);
462 const PendingPicture
& next
= pending_pictures_
.front();
463 Decoder
* decoder
= next
.decoder
;
464 const PP_VideoPicture
& picture
= next
.picture
;
468 int half_width
= plugin_size_
.width() / 2;
469 int half_height
= plugin_size_
.height() / 2;
470 if (decoder
->id() != 0) {
475 PP_Resource graphics_3d
= context_
->pp_resource();
476 if (picture
.texture_target
== GL_TEXTURE_2D
) {
477 Create2DProgramOnce();
478 gles2_if_
->UseProgram(graphics_3d
, shader_2d_
.program
);
479 gles2_if_
->Uniform2f(
480 graphics_3d
, shader_2d_
.texcoord_scale_location
, 1.0, 1.0);
481 } else if (picture
.texture_target
== GL_TEXTURE_RECTANGLE_ARB
) {
482 CreateRectangleARBProgramOnce();
483 gles2_if_
->UseProgram(graphics_3d
, shader_rectangle_arb_
.program
);
484 gles2_if_
->Uniform2f(graphics_3d
,
485 shader_rectangle_arb_
.texcoord_scale_location
,
486 picture
.texture_size
.width
,
487 picture
.texture_size
.height
);
489 assert(picture
.texture_target
== GL_TEXTURE_EXTERNAL_OES
);
490 CreateExternalOESProgramOnce();
491 gles2_if_
->UseProgram(graphics_3d
, shader_external_oes_
.program
);
492 gles2_if_
->Uniform2f(
493 graphics_3d
, shader_external_oes_
.texcoord_scale_location
, 1.0, 1.0);
496 gles2_if_
->Viewport(graphics_3d
, x
, y
, half_width
, half_height
);
497 gles2_if_
->ActiveTexture(graphics_3d
, GL_TEXTURE0
);
498 gles2_if_
->BindTexture(
499 graphics_3d
, picture
.texture_target
, picture
.texture_id
);
500 gles2_if_
->DrawArrays(graphics_3d
, GL_TRIANGLE_STRIP
, 0, 4);
502 gles2_if_
->UseProgram(graphics_3d
, 0);
504 last_swap_request_ticks_
= core_if_
->GetTimeTicks();
505 context_
->SwapBuffers(
506 callback_factory_
.NewCallback(&MyInstance::PaintFinished
));
509 void MyInstance::PaintFinished(int32_t result
) {
510 assert(result
== PP_OK
);
511 swap_ticks_
+= core_if_
->GetTimeTicks() - last_swap_request_ticks_
;
512 is_painting_
= false;
513 ++num_frames_rendered_
;
514 if (num_frames_rendered_
% 50 == 0) {
515 double elapsed
= core_if_
->GetTimeTicks() - first_frame_delivered_ticks_
;
516 double fps
= (elapsed
> 0) ? num_frames_rendered_
/ elapsed
: 1000;
517 double ms_per_swap
= (swap_ticks_
* 1e3
) / num_frames_rendered_
;
518 double secs_average_latency
= 0;
519 for (DecoderList::iterator it
= video_decoders_
.begin();
520 it
!= video_decoders_
.end();
522 secs_average_latency
+= (*it
)->GetAverageLatency();
523 secs_average_latency
/= video_decoders_
.size();
524 double ms_average_latency
= 1000 * secs_average_latency
;
525 LogError(this).s() << "Rendered frames: " << num_frames_rendered_
527 << ", with average ms/swap of: " << ms_per_swap
528 << ", with average latency (ms) of: "
529 << ms_average_latency
;
532 // If the decoders were reset, this will be empty.
533 if (pending_pictures_
.empty())
536 const PendingPicture
& next
= pending_pictures_
.front();
537 Decoder
* decoder
= next
.decoder
;
538 const PP_VideoPicture
& picture
= next
.picture
;
539 decoder
->RecyclePicture(picture
);
540 pending_pictures_
.pop();
542 // Keep painting as long as we have pictures.
543 if (!pending_pictures_
.empty())
547 void MyInstance::InitGL() {
548 assert(plugin_size_
.width() && plugin_size_
.height());
549 is_painting_
= false;
552 int32_t context_attributes
[] = {
553 PP_GRAPHICS3DATTRIB_ALPHA_SIZE
, 8,
554 PP_GRAPHICS3DATTRIB_BLUE_SIZE
, 8,
555 PP_GRAPHICS3DATTRIB_GREEN_SIZE
, 8,
556 PP_GRAPHICS3DATTRIB_RED_SIZE
, 8,
557 PP_GRAPHICS3DATTRIB_DEPTH_SIZE
, 0,
558 PP_GRAPHICS3DATTRIB_STENCIL_SIZE
, 0,
559 PP_GRAPHICS3DATTRIB_SAMPLES
, 0,
560 PP_GRAPHICS3DATTRIB_SAMPLE_BUFFERS
, 0,
561 PP_GRAPHICS3DATTRIB_WIDTH
, plugin_size_
.width(),
562 PP_GRAPHICS3DATTRIB_HEIGHT
, plugin_size_
.height(),
563 PP_GRAPHICS3DATTRIB_NONE
,
565 context_
= new pp::Graphics3D(this, context_attributes
);
566 assert(!context_
->is_null());
567 assert(BindGraphics(*context_
));
570 gles2_if_
->ClearColor(context_
->pp_resource(), 1, 0, 0, 1);
571 gles2_if_
->Clear(context_
->pp_resource(), GL_COLOR_BUFFER_BIT
);
578 void MyInstance::CreateGLObjects() {
579 // Assign vertex positions and texture coordinates to buffers for use in
581 static const float kVertices
[] = {
582 -1, -1, -1, 1, 1, -1, 1, 1, // Position coordinates.
583 0, 1, 0, 0, 1, 1, 1, 0, // Texture coordinates.
587 gles2_if_
->GenBuffers(context_
->pp_resource(), 1, &buffer
);
588 gles2_if_
->BindBuffer(context_
->pp_resource(), GL_ARRAY_BUFFER
, buffer
);
590 gles2_if_
->BufferData(context_
->pp_resource(),
598 static const char kVertexShader
[] =
599 "varying vec2 v_texCoord; \n"
600 "attribute vec4 a_position; \n"
601 "attribute vec2 a_texCoord; \n"
602 "uniform vec2 v_scale; \n"
605 " v_texCoord = v_scale * a_texCoord; \n"
606 " gl_Position = a_position; \n"
609 void MyInstance::Create2DProgramOnce() {
610 if (shader_2d_
.program
)
612 static const char kFragmentShader2D
[] =
613 "precision mediump float; \n"
614 "varying vec2 v_texCoord; \n"
615 "uniform sampler2D s_texture; \n"
618 " gl_FragColor = texture2D(s_texture, v_texCoord); \n"
620 shader_2d_
= CreateProgram(kVertexShader
, kFragmentShader2D
);
624 void MyInstance::CreateRectangleARBProgramOnce() {
625 if (shader_rectangle_arb_
.program
)
627 static const char kFragmentShaderRectangle
[] =
628 "#extension GL_ARB_texture_rectangle : require\n"
629 "precision mediump float; \n"
630 "varying vec2 v_texCoord; \n"
631 "uniform sampler2DRect s_texture; \n"
634 " gl_FragColor = texture2DRect(s_texture, v_texCoord).rgba; \n"
636 shader_rectangle_arb_
=
637 CreateProgram(kVertexShader
, kFragmentShaderRectangle
);
641 void MyInstance::CreateExternalOESProgramOnce() {
642 if (shader_external_oes_
.program
)
644 static const char kFragmentShaderExternal
[] =
645 "#extension GL_OES_EGL_image_external : require\n"
646 "precision mediump float; \n"
647 "varying vec2 v_texCoord; \n"
648 "uniform samplerExternalOES s_texture; \n"
651 " gl_FragColor = texture2D(s_texture, v_texCoord); \n"
653 shader_external_oes_
= CreateProgram(kVertexShader
, kFragmentShaderExternal
);
657 Shader
MyInstance::CreateProgram(const char* vertex_shader
,
658 const char* fragment_shader
) {
661 // Create shader program.
662 shader
.program
= gles2_if_
->CreateProgram(context_
->pp_resource());
664 shader
.program
, GL_VERTEX_SHADER
, vertex_shader
, strlen(vertex_shader
));
665 CreateShader(shader
.program
,
668 strlen(fragment_shader
));
669 gles2_if_
->LinkProgram(context_
->pp_resource(), shader
.program
);
670 gles2_if_
->UseProgram(context_
->pp_resource(), shader
.program
);
671 gles2_if_
->Uniform1i(
672 context_
->pp_resource(),
673 gles2_if_
->GetUniformLocation(
674 context_
->pp_resource(), shader
.program
, "s_texture"),
678 shader
.texcoord_scale_location
= gles2_if_
->GetUniformLocation(
679 context_
->pp_resource(), shader
.program
, "v_scale");
681 GLint pos_location
= gles2_if_
->GetAttribLocation(
682 context_
->pp_resource(), shader
.program
, "a_position");
683 GLint tc_location
= gles2_if_
->GetAttribLocation(
684 context_
->pp_resource(), shader
.program
, "a_texCoord");
687 gles2_if_
->EnableVertexAttribArray(context_
->pp_resource(), pos_location
);
688 gles2_if_
->VertexAttribPointer(
689 context_
->pp_resource(), pos_location
, 2, GL_FLOAT
, GL_FALSE
, 0, 0);
690 gles2_if_
->EnableVertexAttribArray(context_
->pp_resource(), tc_location
);
691 gles2_if_
->VertexAttribPointer(
692 context_
->pp_resource(),
698 static_cast<float*>(0) + 8); // Skip position coordinates.
700 gles2_if_
->UseProgram(context_
->pp_resource(), 0);
705 void MyInstance::CreateShader(GLuint program
,
709 GLuint shader
= gles2_if_
->CreateShader(context_
->pp_resource(), type
);
710 gles2_if_
->ShaderSource(context_
->pp_resource(), shader
, 1, &source
, &size
);
711 gles2_if_
->CompileShader(context_
->pp_resource(), shader
);
712 gles2_if_
->AttachShader(context_
->pp_resource(), program
, shader
);
713 gles2_if_
->DeleteShader(context_
->pp_resource(), shader
);
716 // This object is the global object representing this plugin library as long
718 class MyModule
: public pp::Module
{
720 MyModule() : pp::Module() {}
721 virtual ~MyModule() {}
723 virtual pp::Instance
* CreateInstance(PP_Instance instance
) {
724 return new MyInstance(instance
, this);
728 } // anonymous namespace
731 // Factory function for your specialization of the Module object.
732 Module
* CreateModule() {
733 return new MyModule();