Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(14)

Side by Side Diff: ppapi/examples/video_decode/video_decode.cc

Issue 270213004: Implement Pepper PPB_VideoDecoder interface. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: DCHECK that shm bufffers are free after Flush/Reset. Created 6 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
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.
4
5 #include <stdio.h>
6 #include <string.h>
7
8 #include <iostream>
9 #include <queue>
10 #include <sstream>
11
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"
27
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.
31 #undef NDEBUG
32 #include <assert.h>
33
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()));
37
38 namespace {
39
40 struct Shader {
41 Shader() : program(0), texcoord_scale_location(0) {}
42 ~Shader() {}
43
44 GLuint program;
45 GLint texcoord_scale_location;
46 };
47
48 class Decoder;
49 class MyInstance;
50
51 class MyInstance : public pp::Instance, public pp::Graphics3DClient {
52 public:
53 MyInstance(PP_Instance instance, pp::Module* module);
54 virtual ~MyInstance();
55
56 // pp::Instance implementation.
57 virtual void DidChangeView(const pp::Rect& position,
58 const pp::Rect& clip_ignored);
59
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");
67 }
68
69 void PaintPicture(Decoder* decoder, const PP_VideoPicture& picture);
70
71 private:
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;
75 class LogError {
76 public:
77 LogError(MyInstance* instance) : instance_(instance) {}
78 ~LogError() {
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;
83 }
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_; }
88
89 private:
90 MyInstance* instance_;
91 std::ostringstream stream_;
92 };
93
94 void InitializeDecoders();
95
96 // GL-related functions.
97 void InitGL();
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);
104
105 pp::Size plugin_size_;
106 bool is_painting_;
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_;
111
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_;
117
118 // Unowned pointers.
119 const PPB_Console* console_if_;
120 const PPB_Core* core_if_;
121 const PPB_OpenGLES2* gles2_if_;
122
123 // Owned data.
124 pp::Graphics3D* context_;
125 typedef std::vector<Decoder*> DecoderList;
126 DecoderList video_decoders_;
127
128 // Shader program to draw GL_TEXTURE_2D target.
129 Shader shader_2d_;
130 // Shader program to draw GL_TEXTURE_RECTANGLE_ARB target.
131 Shader shader_rectangle_arb_;
132 };
133
134 class Decoder {
135 public:
136 Decoder(MyInstance* instance, int id, const pp::Graphics3D& graphics_3d);
137 ~Decoder();
138
139 int id() const { return id_; }
140 bool decoding() const { return !flushing_ && !resetting_; }
141
142 void Seek(int frame);
143 void RecyclePicture(const PP_VideoPicture& picture);
144
145 private:
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);
153
154 MyInstance* instance_;
155 int id_;
156
157 pp::VideoDecoder* decoder_;
158 pp::CompletionCallbackFactory<Decoder> callback_factory_;
159
160 size_t encoded_data_next_pos_to_decode_;
161 int next_picture_id_;
162 int seek_frame_;
163 bool flushing_;
164 bool resetting_;
165 };
166
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;
172 }
173
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;
178 *end_pos += 4;
179 while (*end_pos < kDataLen && !LookingAtNAL(kData, *end_pos)) {
180 ++*end_pos;
181 }
182 }
183
184 Decoder::Decoder(MyInstance* instance,
185 int id,
186 const pp::Graphics3D& graphics_3d)
187 : instance_(instance),
188 id_(id),
189 decoder_(new pp::VideoDecoder(instance)),
190 callback_factory_(this),
191 encoded_data_next_pos_to_decode_(0),
192 next_picture_id_(0),
193 seek_frame_(0),
194 flushing_(false),
195 resetting_(false) {
196 assert(!decoder_->is_null());
197 const PP_VideoProfile profile = PP_VIDEOPROFILE_H264MAIN;
198 decoder_->Initialize(graphics_3d,
199 profile,
200 PP_FALSE /* allow_software_fallback */,
201 callback_factory_.NewCallback(&Decoder::InitializeDone));
202 }
203
204 Decoder::~Decoder() {
205 delete decoder_;
206 }
207
208 void Decoder::InitializeDone(int32_t result) {
209 assert(decoder_);
210 assert(result == PP_OK);
211 assert(decoding());
212 Start(0);
213 }
214
215 void Decoder::Start(int frame) {
216 assert(decoder_);
217
218 // Skip to |frame|.
219 size_t start_pos = 0;
220 size_t end_pos = 0;
221 for (int i = frame; i > 0; i--)
Ami GONE FROM CHROMIUM 2014/05/28 20:50:56 nit: why counting backwards??
bbudge 2014/05/29 00:03:34 Not sure what I was thinking. Done.
222 GetNextFrame(&start_pos, &end_pos);
223 encoded_data_next_pos_to_decode_ = end_pos;
224
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));
229
230 // Start the decode loop.
231 DecodeNextFrame();
232 }
233
234 void Decoder::Seek(int frame) {
235 assert(decoder_);
236 seek_frame_ = frame;
Ami GONE FROM CHROMIUM 2014/05/28 20:50:56 write-only var? (Seek() seems like dead code)
bbudge 2014/05/29 00:03:34 It is. I was working on implementing Seek to test
237 resetting_ = true;
238 decoder_->Reset(callback_factory_.NewCallback(&Decoder::ResetDone));
239 }
240
241 void Decoder::RecyclePicture(const PP_VideoPicture& picture) {
242 assert(decoder_);
243 decoder_->RecyclePicture(picture);
244 }
245
246 void Decoder::DecodeNextFrame() {
247 assert(decoder_);
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) {
251 flushing_ = true;
252 decoder_->Flush(callback_factory_.NewCallback(&Decoder::FlushDone));
253 return;
254 }
255
256 // Find the start of the next frame.
257 size_t start_pos = encoded_data_next_pos_to_decode_;
258 size_t end_pos;
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_++,
265 size,
266 kData + start_pos,
267 callback_factory_.NewCallback(&Decoder::DecodeDone));
268 }
269 }
270
271 void Decoder::DecodeDone(int32_t result) {
272 assert(decoder_);
273 // Break out of the decode loop on abort.
274 if (result == PP_ERROR_ABORTED)
275 return;
276 assert(result == PP_OK);
277 if (decoding())
278 DecodeNextFrame();
279 }
280
281 void Decoder::PictureReady(int32_t result, PP_VideoPicture picture) {
282 assert(decoder_);
283 // Break out of the get picture loop on abort.
284 if (result == PP_ERROR_ABORTED)
285 return;
286 assert(result == PP_OK);
287 decoder_->GetPicture(
288 callback_factory_.NewCallbackWithOutput(&Decoder::PictureReady));
289 instance_->PaintPicture(this, picture);
290 }
291
292 void Decoder::FlushDone(int32_t result) {
293 assert(decoder_);
294 assert(result == PP_OK || result == PP_ERROR_ABORTED);
295 flushing_ = false;
296 }
297
298 void Decoder::ResetDone(int32_t result) {
299 assert(decoder_);
300 assert(result == PP_OK);
301 resetting_ = false;
302 }
303
304 MyInstance::MyInstance(PP_Instance instance, pp::Module* module)
305 : pp::Instance(instance),
306 pp::Graphics3DClient(this),
307 is_painting_(false),
308 num_frames_rendered_(0),
309 first_frame_delivered_ticks_(-1),
310 last_swap_request_ticks_(-1),
311 swap_ticks_(0),
312 callback_factory_(this),
313 context_(NULL) {
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);
Ami GONE FROM CHROMIUM 2014/05/28 20:50:56 Did you mean to tie mouse-clicks to seeking someho
bbudge 2014/05/29 00:03:34 Yes, I was trying to use the plugin area as a crud
321 }
322
323 MyInstance::~MyInstance() {
324 if (!context_)
325 return;
326
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);
332
333 for (DecoderList::iterator it = video_decoders_.begin();
334 it != video_decoders_.end();
335 ++it)
336 delete *it;
337
338 delete context_;
339 }
340
341 void MyInstance::DidChangeView(const pp::Rect& position,
342 const pp::Rect& clip_ignored) {
343 if (position.width() == 0 || position.height() == 0)
344 return;
345 if (plugin_size_.width()) {
346 assert(position.size() == plugin_size_);
347 return;
348 }
349 plugin_size_ = position.size();
350
351 // Initialize graphics.
352 InitGL();
353 InitializeDecoders();
354 }
355
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_));
361 }
362
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);
367 if (is_painting_) {
368 pictures_pending_paint_.push(std::make_pair(decoder, picture));
369 return;
370 }
371
372 assert(!is_painting_);
373 is_painting_ = true;
374 int x = 0;
375 int y = 0;
376 int half_width = plugin_size_.width() / 2;
377 int half_height = plugin_size_.height() / 2;
378 if (decoder->id() != 0) {
379 x = half_width;
380 y = half_height;
381 }
382
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);
389 } else {
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);
397 }
398
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);
404
405 gles2_if_->UseProgram(graphics_3d, 0);
406
407 last_swap_request_ticks_ = core_if_->GetTimeTicks();
408 assert(PP_OK_COMPLETIONPENDING ==
409 context_->SwapBuffers(callback_factory_.NewCallback(
410 &MyInstance::PaintFinished, decoder, picture)));
411 }
412
413 void MyInstance::PaintFinished(int32_t result,
414 Decoder* decoder,
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_
425 << ", fps: " << fps
426 << ", with average ms/swap of: " << ms_per_swap;
427 }
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);
435 }
436 }
437
438 void MyInstance::InitGL() {
439 assert(plugin_size_.width() && plugin_size_.height());
440 is_painting_ = false;
441
442 assert(!context_);
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,
455 };
456 context_ = new pp::Graphics3D(this, context_attributes);
457 assert(!context_->is_null());
458 assert(BindGraphics(*context_));
459
460 // Clear color bit.
461 gles2_if_->ClearColor(context_->pp_resource(), 1, 0, 0, 1);
462 gles2_if_->Clear(context_->pp_resource(), GL_COLOR_BUFFER_BIT);
463
464 assertNoGLError();
465
466 CreateGLObjects();
467 }
468
469 void MyInstance::CreateGLObjects() {
470 // Assign vertex positions and texture coordinates to buffers for use in
471 // shader program.
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.
475 };
476
477 GLuint buffer;
478 gles2_if_->GenBuffers(context_->pp_resource(), 1, &buffer);
479 gles2_if_->BindBuffer(context_->pp_resource(), GL_ARRAY_BUFFER, buffer);
480
481 gles2_if_->BufferData(context_->pp_resource(),
482 GL_ARRAY_BUFFER,
483 sizeof(kVertices),
484 kVertices,
485 GL_STATIC_DRAW);
486 assertNoGLError();
487 }
488
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"
494 "void main() \n"
495 "{ \n"
496 " v_texCoord = v_scale * a_texCoord; \n"
497 " gl_Position = a_position; \n"
498 "}";
499
500 void MyInstance::Create2DProgramOnce() {
501 if (shader_2d_.program)
502 return;
503 static const char kFragmentShader2D[] =
504 "precision mediump float; \n"
505 "varying vec2 v_texCoord; \n"
506 "uniform sampler2D s_texture; \n"
507 "void main() \n"
508 "{"
509 " gl_FragColor = texture2D(s_texture, v_texCoord); \n"
510 "}";
511 shader_2d_ = CreateProgram(kVertexShader, kFragmentShader2D);
512 assertNoGLError();
513 }
514
515 void MyInstance::CreateRectangleARBProgramOnce() {
516 if (shader_rectangle_arb_.program)
517 return;
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"
523 "void main() \n"
524 "{"
525 " gl_FragColor = texture2DRect(s_texture, v_texCoord).rgba; \n"
526 "}";
527 shader_rectangle_arb_ =
528 CreateProgram(kVertexShader, kFragmentShaderRectangle);
529 }
530
531 Shader MyInstance::CreateProgram(const char* vertex_shader,
532 const char* fragment_shader) {
533 Shader shader;
534
535 // Create shader program.
536 shader.program = gles2_if_->CreateProgram(context_->pp_resource());
537 CreateShader(
538 shader.program, GL_VERTEX_SHADER, vertex_shader, strlen(vertex_shader));
539 CreateShader(shader.program,
540 GL_FRAGMENT_SHADER,
541 fragment_shader,
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"),
549 0);
550 assertNoGLError();
551
552 shader.texcoord_scale_location = gles2_if_->GetUniformLocation(
553 context_->pp_resource(), shader.program, "v_scale");
554
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");
559 assertNoGLError();
560
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(),
567 tc_location,
568 2,
569 GL_FLOAT,
570 GL_FALSE,
571 0,
572 static_cast<float*>(0) + 8); // Skip position coordinates.
573
574 gles2_if_->UseProgram(context_->pp_resource(), 0);
575 assertNoGLError();
576 return shader;
577 }
578
579 void MyInstance::CreateShader(GLuint program,
580 GLenum type,
581 const char* source,
582 int size) {
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);
588 }
589
590 // This object is the global object representing this plugin library as long
591 // as it is loaded.
592 class MyModule : public pp::Module {
593 public:
594 MyModule() : pp::Module() {}
595 virtual ~MyModule() {}
596
597 virtual pp::Instance* CreateInstance(PP_Instance instance) {
598 return new MyInstance(instance, this);
599 }
600 };
601
602 } // anonymous namespace
603
604 namespace pp {
605 // Factory function for your specialization of the Module object.
606 Module* CreateModule() {
607 return new MyModule();
608 }
609 } // namespace pp
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698