// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include <string.h> #include <iostream> #include <sstream> #include <list> #include <map> #include <set> #include <vector> #include "ppapi/c/pp_errors.h" #include "ppapi/c/ppb_console.h" #include "ppapi/c/ppb_opengles2.h" #include "ppapi/cpp/dev/video_decoder_client_dev.h" #include "ppapi/cpp/dev/video_decoder_dev.h" #include "ppapi/cpp/graphics_3d.h" #include "ppapi/cpp/graphics_3d_client.h" #include "ppapi/cpp/instance.h" #include "ppapi/cpp/module.h" #include "ppapi/cpp/rect.h" #include "ppapi/cpp/var.h" #include "ppapi/examples/video_decode/testdata.h" #include "ppapi/lib/gl/include/GLES2/gl2.h" #include "ppapi/lib/gl/include/GLES2/gl2ext.h" #include "ppapi/utility/completion_callback_factory.h" // Use assert as a poor-man's CHECK, even in non-debug mode. // Since <assert.h> redefines assert on every inclusion (it doesn't use // include-guards), make sure this is the last file #include'd in this file. #undef NDEBUG #include <assert.h> // Assert |context_| isn't holding any GL Errors. Done as a macro instead of a // function to preserve line number information in the failure message. #define assertNoGLError() \ assert(!gles2_if_->GetError(context_->pp_resource())); namespace { struct PictureBufferInfo { PP_PictureBuffer_Dev buffer; GLenum texture_target; }; struct Shader { Shader() : program(0), texcoord_scale_location(0) {} GLuint program; GLint texcoord_scale_location; }; class VideoDecodeDemoInstance : public pp::Instance, public pp::Graphics3DClient, public pp::VideoDecoderClient_Dev { public: VideoDecodeDemoInstance(PP_Instance instance, pp::Module* module); virtual ~VideoDecodeDemoInstance(); // pp::Instance implementation (see PPP_Instance). virtual void DidChangeView(const pp::Rect& position, const pp::Rect& clip_ignored); // pp::Graphics3DClient implementation. virtual void Graphics3DContextLost() { // TODO(vrk/fischman): Properly reset after a lost graphics context. In // particular need to delete context_ and re-create textures. // Probably have to recreate the decoder from scratch, because old textures // can still be outstanding in the decoder! assert(false && "Unexpectedly lost graphics context"); } // pp::VideoDecoderClient_Dev implementation. virtual void ProvidePictureBuffers( PP_Resource decoder, uint32_t req_num_of_bufs, const PP_Size& dimensions, uint32_t texture_target); virtual void DismissPictureBuffer(PP_Resource decoder, int32_t picture_buffer_id); virtual void PictureReady(PP_Resource decoder, const PP_Picture_Dev& picture); virtual void NotifyError(PP_Resource decoder, PP_VideoDecodeError_Dev error); private: enum { kNumConcurrentDecodes = 7, kNumDecoders = 2 }; // Baked into viewport rendering. // A single decoder's client interface. class DecoderClient { public: DecoderClient(VideoDecodeDemoInstance* gles2, pp::VideoDecoder_Dev* decoder); ~DecoderClient(); void DecodeNextNALUs(); // Per-decoder implementation of part of pp::VideoDecoderClient_Dev. void ProvidePictureBuffers( uint32_t req_num_of_bufs, PP_Size dimensions, uint32_t texture_target); void DismissPictureBuffer(int32_t picture_buffer_id); const PictureBufferInfo& GetPictureBufferInfoById(int id); pp::VideoDecoder_Dev* decoder() { return decoder_; } private: void DecodeNextNALU(); static void GetNextNALUBoundary(size_t start_pos, size_t* end_pos); void DecoderBitstreamDone(int32_t result, int bitstream_buffer_id); void DecoderFlushDone(int32_t result); VideoDecodeDemoInstance* gles2_; pp::VideoDecoder_Dev* decoder_; pp::CompletionCallbackFactory<DecoderClient> callback_factory_; int next_picture_buffer_id_; int next_bitstream_buffer_id_; size_t encoded_data_next_pos_to_decode_; std::set<int> bitstream_ids_at_decoder_; // Map of texture buffers indexed by buffer id. typedef std::map<int, PictureBufferInfo> PictureBufferMap; PictureBufferMap picture_buffers_by_id_; // Map of bitstream buffers indexed by id. typedef std::map<int, pp::Buffer_Dev*> BitstreamBufferMap; BitstreamBufferMap bitstream_buffers_by_id_; }; // Initialize Video Decoders. void InitializeDecoders(); // GL-related functions. void InitGL(); GLuint CreateTexture(int32_t width, int32_t height, GLenum texture_target); void CreateGLObjects(); void Create2DProgramOnce(); void CreateRectangleARBProgramOnce(); Shader CreateProgram(const char* vertex_shader, const char* fragment_shader); void CreateShader(GLuint program, GLenum type, const char* source, int size); void DeleteTexture(GLuint id); void PaintFinished(int32_t result, PP_Resource decoder, int picture_buffer_id); // Log an error to the developer console and stderr (though the latter may be // closed due to sandboxing or blackholed for other reasons) by creating a // temporary of this type and streaming to it. Example usage: // LogError(this).s() << "Hello world: " << 42; class LogError { public: LogError(VideoDecodeDemoInstance* demo) : demo_(demo) {} ~LogError() { const std::string& msg = stream_.str(); demo_->console_if_->Log(demo_->pp_instance(), PP_LOGLEVEL_ERROR, pp::Var(msg).pp_var()); std::cerr << msg << std::endl; } // Impl note: it would have been nicer to have LogError derive from // std::ostringstream so that it can be streamed to directly, but lookup // rules turn streamed string literals to hex pointers on output. std::ostringstream& s() { return stream_; } private: VideoDecodeDemoInstance* demo_; // Unowned. std::ostringstream stream_; }; pp::Size plugin_size_; bool is_painting_; // When decode outpaces render, we queue up decoded pictures for later // painting. Elements are <decoder,picture>. std::list<std::pair<PP_Resource, PP_Picture_Dev> > pictures_pending_paint_; int num_frames_rendered_; PP_TimeTicks first_frame_delivered_ticks_; PP_TimeTicks last_swap_request_ticks_; PP_TimeTicks swap_ticks_; pp::CompletionCallbackFactory<VideoDecodeDemoInstance> callback_factory_; // Unowned pointers. const PPB_Console* console_if_; const PPB_Core* core_if_; const PPB_OpenGLES2* gles2_if_; // Owned data. pp::Graphics3D* context_; typedef std::map<int, DecoderClient*> Decoders; Decoders video_decoders_; // Shader program to draw GL_TEXTURE_2D target. Shader shader_2d_; // Shader program to draw GL_TEXTURE_RECTANGLE_ARB target. Shader shader_rectangle_arb_; }; VideoDecodeDemoInstance::DecoderClient::DecoderClient( VideoDecodeDemoInstance* gles2, pp::VideoDecoder_Dev* decoder) : gles2_(gles2), decoder_(decoder), callback_factory_(this), next_picture_buffer_id_(0), next_bitstream_buffer_id_(0), encoded_data_next_pos_to_decode_(0) { } VideoDecodeDemoInstance::DecoderClient::~DecoderClient() { delete decoder_; decoder_ = NULL; for (BitstreamBufferMap::iterator it = bitstream_buffers_by_id_.begin(); it != bitstream_buffers_by_id_.end(); ++it) { delete it->second; } bitstream_buffers_by_id_.clear(); for (PictureBufferMap::iterator it = picture_buffers_by_id_.begin(); it != picture_buffers_by_id_.end(); ++it) { gles2_->DeleteTexture(it->second.buffer.texture_id); } picture_buffers_by_id_.clear(); } VideoDecodeDemoInstance::VideoDecodeDemoInstance(PP_Instance instance, pp::Module* module) : pp::Instance(instance), pp::Graphics3DClient(this), pp::VideoDecoderClient_Dev(this), is_painting_(false), num_frames_rendered_(0), first_frame_delivered_ticks_(-1), swap_ticks_(0), callback_factory_(this), context_(NULL) { assert((console_if_ = static_cast<const PPB_Console*>( module->GetBrowserInterface(PPB_CONSOLE_INTERFACE)))); assert((core_if_ = static_cast<const PPB_Core*>( module->GetBrowserInterface(PPB_CORE_INTERFACE)))); assert((gles2_if_ = static_cast<const PPB_OpenGLES2*>( module->GetBrowserInterface(PPB_OPENGLES2_INTERFACE)))); } VideoDecodeDemoInstance::~VideoDecodeDemoInstance() { if (shader_2d_.program) gles2_if_->DeleteProgram(context_->pp_resource(), shader_2d_.program); if (shader_rectangle_arb_.program) { gles2_if_->DeleteProgram( context_->pp_resource(), shader_rectangle_arb_.program); } for (Decoders::iterator it = video_decoders_.begin(); it != video_decoders_.end(); ++it) { delete it->second; } video_decoders_.clear(); delete context_; } void VideoDecodeDemoInstance::DidChangeView( const pp::Rect& position, const pp::Rect& clip_ignored) { if (position.width() == 0 || position.height() == 0) return; if (plugin_size_.width()) { assert(position.size() == plugin_size_); return; } plugin_size_ = position.size(); // Initialize graphics. InitGL(); InitializeDecoders(); } void VideoDecodeDemoInstance::InitializeDecoders() { assert(video_decoders_.empty()); for (int i = 0; i < kNumDecoders; ++i) { DecoderClient* client = new DecoderClient( this, new pp::VideoDecoder_Dev( this, *context_, PP_VIDEODECODER_H264PROFILE_MAIN)); assert(!client->decoder()->is_null()); assert(video_decoders_.insert(std::make_pair( client->decoder()->pp_resource(), client)).second); client->DecodeNextNALUs(); } } void VideoDecodeDemoInstance::DecoderClient::DecoderBitstreamDone( int32_t result, int bitstream_buffer_id) { assert(bitstream_ids_at_decoder_.erase(bitstream_buffer_id) == 1); BitstreamBufferMap::iterator it = bitstream_buffers_by_id_.find(bitstream_buffer_id); assert(it != bitstream_buffers_by_id_.end()); delete it->second; bitstream_buffers_by_id_.erase(it); DecodeNextNALUs(); } void VideoDecodeDemoInstance::DecoderClient::DecoderFlushDone(int32_t result) { assert(result == PP_OK); // Check that each bitstream buffer ID we handed to the decoder got handed // back to us. assert(bitstream_ids_at_decoder_.empty()); delete decoder_; decoder_ = NULL; } static bool LookingAtNAL(const unsigned char* encoded, size_t pos) { return pos + 3 < kDataLen && encoded[pos] == 0 && encoded[pos + 1] == 0 && encoded[pos + 2] == 0 && encoded[pos + 3] == 1; } void VideoDecodeDemoInstance::DecoderClient::GetNextNALUBoundary( size_t start_pos, size_t* end_pos) { assert(LookingAtNAL(kData, start_pos)); *end_pos = start_pos; *end_pos += 4; while (*end_pos + 3 < kDataLen && !LookingAtNAL(kData, *end_pos)) { ++*end_pos; } if (*end_pos + 3 >= kDataLen) { *end_pos = kDataLen; return; } } void VideoDecodeDemoInstance::DecoderClient::DecodeNextNALUs() { while (encoded_data_next_pos_to_decode_ <= kDataLen && bitstream_ids_at_decoder_.size() < kNumConcurrentDecodes) { DecodeNextNALU(); } } void VideoDecodeDemoInstance::DecoderClient::DecodeNextNALU() { if (encoded_data_next_pos_to_decode_ == kDataLen) { ++encoded_data_next_pos_to_decode_; pp::CompletionCallback cb = callback_factory_.NewCallback( &VideoDecodeDemoInstance::DecoderClient::DecoderFlushDone); decoder_->Flush(cb); return; } size_t start_pos = encoded_data_next_pos_to_decode_; size_t end_pos; GetNextNALUBoundary(start_pos, &end_pos); pp::Buffer_Dev* buffer = new pp::Buffer_Dev(gles2_, end_pos - start_pos); PP_VideoBitstreamBuffer_Dev bitstream_buffer; int id = ++next_bitstream_buffer_id_; bitstream_buffer.id = id; bitstream_buffer.size = end_pos - start_pos; bitstream_buffer.data = buffer->pp_resource(); memcpy(buffer->data(), kData + start_pos, end_pos - start_pos); assert(bitstream_buffers_by_id_.insert(std::make_pair(id, buffer)).second); pp::CompletionCallback cb = callback_factory_.NewCallback( &VideoDecodeDemoInstance::DecoderClient::DecoderBitstreamDone, id); assert(bitstream_ids_at_decoder_.insert(id).second); encoded_data_next_pos_to_decode_ = end_pos; decoder_->Decode(bitstream_buffer, cb); } void VideoDecodeDemoInstance::ProvidePictureBuffers(PP_Resource decoder, uint32_t req_num_of_bufs, const PP_Size& dimensions, uint32_t texture_target) { DecoderClient* client = video_decoders_[decoder]; assert(client); client->ProvidePictureBuffers(req_num_of_bufs, dimensions, texture_target); } void VideoDecodeDemoInstance::DecoderClient::ProvidePictureBuffers( uint32_t req_num_of_bufs, PP_Size dimensions, uint32_t texture_target) { std::vector<PP_PictureBuffer_Dev> buffers; for (uint32_t i = 0; i < req_num_of_bufs; ++i) { PictureBufferInfo info; info.buffer.size = dimensions; info.texture_target = texture_target; info.buffer.texture_id = gles2_->CreateTexture( dimensions.width, dimensions.height, info.texture_target); int id = ++next_picture_buffer_id_; info.buffer.id = id; buffers.push_back(info.buffer); assert(picture_buffers_by_id_.insert(std::make_pair(id, info)).second); } decoder_->AssignPictureBuffers(buffers); } const PictureBufferInfo& VideoDecodeDemoInstance::DecoderClient::GetPictureBufferInfoById( int id) { PictureBufferMap::iterator it = picture_buffers_by_id_.find(id); assert(it != picture_buffers_by_id_.end()); return it->second; } void VideoDecodeDemoInstance::DismissPictureBuffer(PP_Resource decoder, int32_t picture_buffer_id) { DecoderClient* client = video_decoders_[decoder]; assert(client); client->DismissPictureBuffer(picture_buffer_id); } void VideoDecodeDemoInstance::DecoderClient::DismissPictureBuffer( int32_t picture_buffer_id) { gles2_->DeleteTexture(GetPictureBufferInfoById( picture_buffer_id).buffer.texture_id); picture_buffers_by_id_.erase(picture_buffer_id); } void VideoDecodeDemoInstance::PictureReady(PP_Resource decoder, const PP_Picture_Dev& picture) { if (first_frame_delivered_ticks_ == -1) assert((first_frame_delivered_ticks_ = core_if_->GetTimeTicks()) != -1); if (is_painting_) { pictures_pending_paint_.push_back(std::make_pair(decoder, picture)); return; } DecoderClient* client = video_decoders_[decoder]; assert(client); const PictureBufferInfo& info = client->GetPictureBufferInfoById(picture.picture_buffer_id); assert(!is_painting_); is_painting_ = true; int x = 0; int y = 0; if (client != video_decoders_.begin()->second) { x = plugin_size_.width() / kNumDecoders; y = plugin_size_.height() / kNumDecoders; } if (info.texture_target == GL_TEXTURE_2D) { Create2DProgramOnce(); gles2_if_->UseProgram(context_->pp_resource(), shader_2d_.program); gles2_if_->Uniform2f( context_->pp_resource(), shader_2d_.texcoord_scale_location, 1.0, 1.0); } else { assert(info.texture_target == GL_TEXTURE_RECTANGLE_ARB); CreateRectangleARBProgramOnce(); gles2_if_->UseProgram( context_->pp_resource(), shader_rectangle_arb_.program); gles2_if_->Uniform2f(context_->pp_resource(), shader_rectangle_arb_.texcoord_scale_location, info.buffer.size.width, info.buffer.size.height); } gles2_if_->Viewport(context_->pp_resource(), x, y, plugin_size_.width() / kNumDecoders, plugin_size_.height() / kNumDecoders); gles2_if_->ActiveTexture(context_->pp_resource(), GL_TEXTURE0); gles2_if_->BindTexture( context_->pp_resource(), info.texture_target, info.buffer.texture_id); gles2_if_->DrawArrays(context_->pp_resource(), GL_TRIANGLE_STRIP, 0, 4); gles2_if_->UseProgram(context_->pp_resource(), 0); pp::CompletionCallback cb = callback_factory_.NewCallback( &VideoDecodeDemoInstance::PaintFinished, decoder, info.buffer.id); last_swap_request_ticks_ = core_if_->GetTimeTicks(); assert(context_->SwapBuffers(cb) == PP_OK_COMPLETIONPENDING); } void VideoDecodeDemoInstance::NotifyError(PP_Resource decoder, PP_VideoDecodeError_Dev error) { LogError(this).s() << "Received error: " << error; assert(false && "Unexpected error; see stderr for details"); } // This object is the global object representing this plugin library as long // as it is loaded. class VideoDecodeDemoModule : public pp::Module { public: VideoDecodeDemoModule() : pp::Module() {} virtual ~VideoDecodeDemoModule() {} virtual pp::Instance* CreateInstance(PP_Instance instance) { return new VideoDecodeDemoInstance(instance, this); } }; void VideoDecodeDemoInstance::InitGL() { assert(plugin_size_.width() && plugin_size_.height()); is_painting_ = false; assert(!context_); int32_t context_attributes[] = { PP_GRAPHICS3DATTRIB_ALPHA_SIZE, 8, PP_GRAPHICS3DATTRIB_BLUE_SIZE, 8, PP_GRAPHICS3DATTRIB_GREEN_SIZE, 8, PP_GRAPHICS3DATTRIB_RED_SIZE, 8, PP_GRAPHICS3DATTRIB_DEPTH_SIZE, 0, PP_GRAPHICS3DATTRIB_STENCIL_SIZE, 0, PP_GRAPHICS3DATTRIB_SAMPLES, 0, PP_GRAPHICS3DATTRIB_SAMPLE_BUFFERS, 0, PP_GRAPHICS3DATTRIB_WIDTH, plugin_size_.width(), PP_GRAPHICS3DATTRIB_HEIGHT, plugin_size_.height(), PP_GRAPHICS3DATTRIB_NONE, }; context_ = new pp::Graphics3D(this, context_attributes); assert(!context_->is_null()); assert(BindGraphics(*context_)); // Clear color bit. gles2_if_->ClearColor(context_->pp_resource(), 1, 0, 0, 1); gles2_if_->Clear(context_->pp_resource(), GL_COLOR_BUFFER_BIT); assertNoGLError(); CreateGLObjects(); } void VideoDecodeDemoInstance::PaintFinished(int32_t result, PP_Resource decoder, int picture_buffer_id) { assert(result == PP_OK); swap_ticks_ += core_if_->GetTimeTicks() - last_swap_request_ticks_; is_painting_ = false; ++num_frames_rendered_; if (num_frames_rendered_ % 50 == 0) { double elapsed = core_if_->GetTimeTicks() - first_frame_delivered_ticks_; double fps = (elapsed > 0) ? num_frames_rendered_ / elapsed : 1000; double ms_per_swap = (swap_ticks_ * 1e3) / num_frames_rendered_; LogError(this).s() << "Rendered frames: " << num_frames_rendered_ << ", fps: " << fps << ", with average ms/swap of: " << ms_per_swap; } DecoderClient* client = video_decoders_[decoder]; if (client && client->decoder()) client->decoder()->ReusePictureBuffer(picture_buffer_id); if (!pictures_pending_paint_.empty()) { std::pair<PP_Resource, PP_Picture_Dev> decoder_picture = pictures_pending_paint_.front(); pictures_pending_paint_.pop_front(); PictureReady(decoder_picture.first, decoder_picture.second); } } GLuint VideoDecodeDemoInstance::CreateTexture(int32_t width, int32_t height, GLenum texture_target) { GLuint texture_id; gles2_if_->GenTextures(context_->pp_resource(), 1, &texture_id); assertNoGLError(); // Assign parameters. gles2_if_->ActiveTexture(context_->pp_resource(), GL_TEXTURE0); gles2_if_->BindTexture(context_->pp_resource(), texture_target, texture_id); gles2_if_->TexParameteri( context_->pp_resource(), texture_target, GL_TEXTURE_MIN_FILTER, GL_NEAREST); gles2_if_->TexParameteri( context_->pp_resource(), texture_target, GL_TEXTURE_MAG_FILTER, GL_NEAREST); gles2_if_->TexParameterf( context_->pp_resource(), texture_target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); gles2_if_->TexParameterf( context_->pp_resource(), texture_target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); if (texture_target == GL_TEXTURE_2D) { gles2_if_->TexImage2D( context_->pp_resource(), texture_target, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); } assertNoGLError(); return texture_id; } void VideoDecodeDemoInstance::DeleteTexture(GLuint id) { gles2_if_->DeleteTextures(context_->pp_resource(), 1, &id); } void VideoDecodeDemoInstance::CreateGLObjects() { // Assign vertex positions and texture coordinates to buffers for use in // shader program. static const float kVertices[] = { -1, 1, -1, -1, 1, 1, 1, -1, // Position coordinates. 0, 1, 0, 0, 1, 1, 1, 0, // Texture coordinates. }; GLuint buffer; gles2_if_->GenBuffers(context_->pp_resource(), 1, &buffer); gles2_if_->BindBuffer(context_->pp_resource(), GL_ARRAY_BUFFER, buffer); gles2_if_->BufferData(context_->pp_resource(), GL_ARRAY_BUFFER, sizeof(kVertices), kVertices, GL_STATIC_DRAW); assertNoGLError(); } static const char kVertexShader[] = "varying vec2 v_texCoord; \n" "attribute vec4 a_position; \n" "attribute vec2 a_texCoord; \n" "uniform vec2 v_scale; \n" "void main() \n" "{ \n" " v_texCoord = v_scale * a_texCoord; \n" " gl_Position = a_position; \n" "}"; void VideoDecodeDemoInstance::Create2DProgramOnce() { if (shader_2d_.program) return; static const char kFragmentShader2D[] = "precision mediump float; \n" "varying vec2 v_texCoord; \n" "uniform sampler2D s_texture; \n" "void main() \n" "{" " gl_FragColor = texture2D(s_texture, v_texCoord); \n" "}"; shader_2d_ = CreateProgram(kVertexShader, kFragmentShader2D); assertNoGLError(); } void VideoDecodeDemoInstance::CreateRectangleARBProgramOnce() { if (shader_rectangle_arb_.program) return; static const char kFragmentShaderRectangle[] = "#extension GL_ARB_texture_rectangle : require\n" "precision mediump float; \n" "varying vec2 v_texCoord; \n" "uniform sampler2DRect s_texture; \n" "void main() \n" "{" " gl_FragColor = texture2DRect(s_texture, v_texCoord).rgba; \n" "}"; shader_rectangle_arb_ = CreateProgram(kVertexShader, kFragmentShaderRectangle); } Shader VideoDecodeDemoInstance::CreateProgram(const char* vertex_shader, const char* fragment_shader) { Shader shader; // Create shader program. shader.program = gles2_if_->CreateProgram(context_->pp_resource()); CreateShader(shader.program, GL_VERTEX_SHADER, vertex_shader, strlen(vertex_shader)); CreateShader(shader.program, GL_FRAGMENT_SHADER, fragment_shader, strlen(fragment_shader)); gles2_if_->LinkProgram(context_->pp_resource(), shader.program); gles2_if_->UseProgram(context_->pp_resource(), shader.program); gles2_if_->Uniform1i( context_->pp_resource(), gles2_if_->GetUniformLocation( context_->pp_resource(), shader.program, "s_texture"), 0); assertNoGLError(); shader.texcoord_scale_location = gles2_if_->GetUniformLocation( context_->pp_resource(), shader.program, "v_scale"); GLint pos_location = gles2_if_->GetAttribLocation( context_->pp_resource(), shader.program, "a_position"); GLint tc_location = gles2_if_->GetAttribLocation( context_->pp_resource(), shader.program, "a_texCoord"); assertNoGLError(); gles2_if_->EnableVertexAttribArray(context_->pp_resource(), pos_location); gles2_if_->VertexAttribPointer(context_->pp_resource(), pos_location, 2, GL_FLOAT, GL_FALSE, 0, 0); gles2_if_->EnableVertexAttribArray(context_->pp_resource(), tc_location); gles2_if_->VertexAttribPointer( context_->pp_resource(), tc_location, 2, GL_FLOAT, GL_FALSE, 0, static_cast<float*>(0) + 8); // Skip position coordinates. gles2_if_->UseProgram(context_->pp_resource(), 0); assertNoGLError(); return shader; } void VideoDecodeDemoInstance::CreateShader( GLuint program, GLenum type, const char* source, int size) { GLuint shader = gles2_if_->CreateShader(context_->pp_resource(), type); gles2_if_->ShaderSource(context_->pp_resource(), shader, 1, &source, &size); gles2_if_->CompileShader(context_->pp_resource(), shader); gles2_if_->AttachShader(context_->pp_resource(), program, shader); gles2_if_->DeleteShader(context_->pp_resource(), shader); } } // anonymous namespace namespace pp { // Factory function for your specialization of the Module object. Module* CreateModule() { return new VideoDecodeDemoModule(); } } // namespace pp