Index: ppapi/tests/test_video_decoder.cc |
diff --git a/ppapi/tests/test_video_decoder.cc b/ppapi/tests/test_video_decoder.cc |
index 4279894f1c1bfd65cd070bbe1eccf59ecdb70605..d0a0bbb91f2d1cd1d9afb10c2f6680fd93a1aca4 100644 |
--- a/ppapi/tests/test_video_decoder.cc |
+++ b/ppapi/tests/test_video_decoder.cc |
@@ -4,34 +4,585 @@ |
#include "ppapi/tests/test_video_decoder.h" |
-#include "ppapi/c/dev/ppb_video_decoder_dev.h" |
+#include <cstring> |
+#include <fstream> |
+#include <iostream> |
+ |
+#include "ppapi/c/dev/pp_graphics_3d_dev.h" |
+#include "ppapi/c/dev/ppb_buffer_dev.h" |
#include "ppapi/c/dev/ppb_testing_dev.h" |
-#include "ppapi/c/ppb_var.h" |
+#include "ppapi/c/pp_errors.h" |
+#include "ppapi/cpp/dev/context_3d_dev.h" |
+#include "ppapi/cpp/dev/surface_3d_dev.h" |
+#include "ppapi/cpp/dev/video_decoder_dev.h" |
+#include "ppapi/lib/gl/include/GLES2/gl2.h" |
#include "ppapi/tests/testing_instance.h" |
REGISTER_TEST_CASE(VideoDecoder); |
bool TestVideoDecoder::Init() { |
- video_decoder_interface_ = reinterpret_cast<PPB_VideoDecoder_Dev const*>( |
- pp::Module::Get()->GetBrowserInterface(PPB_VIDEODECODER_DEV_INTERFACE)); |
- var_interface_ = reinterpret_cast<PPB_Var const*>( |
- pp::Module::Get()->GetBrowserInterface(PPB_VAR_INTERFACE)); |
- return video_decoder_interface_ && var_interface_ && InitTestingInterface(); |
+ return InitTestingInterface(); |
} |
void TestVideoDecoder::RunTest() { |
- instance_->LogTest("Create", TestCreate()); |
+ instance_->LogTest("Configurations", TestConfigurations()); |
+ instance_->LogTest("H264", TestH264()); |
} |
void TestVideoDecoder::QuitMessageLoop() { |
testing_interface_->QuitMessageLoop(instance_->pp_instance()); |
} |
-std::string TestVideoDecoder::TestCreate() { |
- PP_Resource decoder = video_decoder_interface_->Create( |
- instance_->pp_instance(), NULL); |
- if (decoder == 0) { |
- return "Error creating the decoder"; |
- } |
+std::string TestVideoDecoder::TestConfigurations() { |
+ std::vector<uint32_t> empty_config; |
+ std::vector<uint32_t> configs; |
+ configs = pp::VideoDecoder::GetConfigs(instance_, empty_config); |
PASS(); |
} |
+ |
+std::string TestVideoDecoder::TestH264() { |
+ PASS(); |
+} |
+ |
+// Pull-based video source to read video data from a file. |
+class TestVideoSource { |
+ public: |
+ TestVideoSource() |
+ : file_length_(0), |
+ offset_(0) {} |
+ |
+ ~TestVideoSource() {} |
+ |
+ bool Open(const std::string& url) { |
+ // TODO(vmr): Use file_util::ReadFileToString or equivalent to read the file |
+ // if one-shot reading is used. |
+ std::ifstream* file = |
+ new std::ifstream(url.c_str(), |
+ std::ios::in | std::ios::binary | std::ios::ate); |
+ if (!file->good()) { |
+ delete file; |
+ return false; |
+ } |
+ file->seekg(0, std::ios::end); |
+ uint32_t length = file->tellg(); |
+ file->seekg(0, std::ios::beg); |
+ mem_ = new uint8_t[length]; |
+ file->read(reinterpret_cast<char*>(mem_), length); |
+ file_length_ = length; |
+ file->close(); |
+ delete file; |
+ return true; |
+ } |
+ |
+ // Reads next packet from the input stream. |
+ // Returns number of read bytes on success, 0 on when there was no valid data |
+ // to be read and -1 if user gave NULL or too small buffer. |
+ // TODO(vmr): Modify to differentiate between errors and EOF. |
+ int32_t Read(uint8_t* target_mem, uint32_t size) { |
+ if (!target_mem) |
+ return -1; |
+ uint8_t* unit_begin = NULL; |
+ uint8_t* unit_end = NULL; |
+ uint8_t* ptr = mem_ + offset_; |
+ while (offset_ + 4 < file_length_) { |
+ if (ptr[0] == 0 && ptr[1] == 0 && ptr[2] == 0 && ptr[3] == 1) { |
+ // start code found |
+ if (!unit_begin) { |
+ unit_begin = ptr; |
+ } else { |
+ // back-up 1 byte. |
+ unit_end = ptr; |
+ break; |
+ } |
+ } |
+ ptr++; |
+ offset_++; |
+ } |
+ if (unit_begin && offset_ + 4 == file_length_) { |
+ // Last unit. Set the unit_end to point to the last byte. |
+ unit_end = ptr + 4; |
+ offset_ += 4; |
+ } else if (!unit_begin || !unit_end) { |
+ // No unit start codes found in buffer. |
+ return 0; |
+ } |
+ if (static_cast<int32_t>(size) >= unit_end - unit_begin) { |
+ memcpy(target_mem, unit_begin, unit_end - unit_begin); |
+ return unit_end - unit_begin; |
+ } |
+ // Rewind to the beginning start code if there is one as it should be |
+ // returned with next Read(). |
+ offset_ = unit_begin - mem_; |
+ return -1; |
+ } |
+ |
+ private: |
+ uint32_t file_length_; |
+ uint32_t offset_; |
+ uint8_t* mem_; |
+}; |
+ |
+LocalVideoBitstreamSource::LocalVideoBitstreamSource(std::string filename) |
+ : file_(filename), |
+ video_source_(new TestVideoSource()), |
+ video_source_open_(false) { |
+} |
+ |
+LocalVideoBitstreamSource::~LocalVideoBitstreamSource() { |
+ delete video_source_; |
+} |
+ |
+bool LocalVideoBitstreamSource::GetBitstreamUnit( |
+ void* target_mem, |
+ uint32_t target_mem_size_in_bytes, |
+ int32_t* unit_size_in_bytes) { |
+ if (!video_source_open_) { |
+ if (!video_source_->Open(file_)) { |
+ return false; |
+ } |
+ video_source_open_ = true; |
+ } |
+ int32_t read_bytes = video_source_->Read(static_cast<uint8_t*>(target_mem), |
+ target_mem_size_in_bytes); |
+ if (read_bytes <= 0) { |
+ return false; |
+ } |
+ *unit_size_in_bytes = read_bytes; |
+ return true; |
+} |
+ |
+// Constants used by VideoDecoderClient. |
+static const int32_t kBitstreamBufferCount = 3; |
+static const int32_t kBitstreamBufferSize = 256 * 1024 * 1024; |
+static const int32_t kDefaultWidth = 640; |
+static const int32_t kDefaultHeight = 480; |
+ |
+bool VideoDecoderClient::Initialize() { |
+ // Default implementation just assumes everything is set up. |
+ if (!InitializeVideoBitstreamInterface()) { |
+ return false; |
+ } |
+ if (!display_->Initialize(kDefaultWidth, kDefaultHeight)) { |
+ return false; |
+ } |
+ video_decoder_ = new pp::VideoDecoder(instance_, decoder_config_, this); |
+ if (!video_decoder_) { |
+ return false; |
+ } |
+ ChangeState(kInitialized); |
+ return true; |
+} |
+ |
+bool VideoDecoderClient::Run() { |
+ assert(state_ == kInitialized); |
+ // Start the streaming by dispatching the first buffers one by one. |
+ for (std::map<int32_t, PP_VideoBitstreamBuffer_Dev>::iterator it = |
+ bitstream_buffers_.begin(); |
+ it == bitstream_buffers_.end(); |
+ it++) { |
+ ReadAndDispatchBitstreamUnit((*it).first); |
+ } |
+ // Once streaming has been started, we're running. |
+ ChangeState(kRunning); |
+ return true; |
+} |
+ |
+bool VideoDecoderClient::Stop() { |
+ assert(state_ == kRunning); |
+ // Stop the playback. |
+ ChangeState(kInitialized); |
+ return true; |
+} |
+ |
+bool VideoDecoderClient::Flush() { |
+ assert(state_ == kRunning); |
+ // Issue the flush request. |
+ video_decoder_->Flush(cb_factory_.NewCallback( |
+ &VideoDecoderClient::OnUserFlushDone, state_)); |
+ ChangeState(kFlushing); |
+ return true; |
+} |
+ |
+bool VideoDecoderClient::Teardown() { |
+ assert(state_ == kInitialized); |
+ // Teardown the resources. |
+ ChangeState(kCreated); |
+ return true; |
+} |
+ |
+void VideoDecoderClient::ProvidePictureBuffers( |
+ uint32_t requested_num_of_buffers, |
+ const std::vector<uint32_t>& buffer_properties) { |
+ std::vector<PP_GLESBuffer_Dev> buffers; |
+ for (uint32_t i = 0; i < requested_num_of_buffers; i++) { |
+ PP_GLESBuffer_Dev gles_buffer; |
+ if (!display_->ProvideGLESPictureBuffer(buffer_properties, &gles_buffer)) { |
+ // TODO(vmr): Handle error properly. |
+ return; |
+ } |
+ buffers.push_back(gles_buffer); |
+ } |
+ video_decoder_->AssignGLESBuffers(buffers.size(), buffers); |
+} |
+ |
+void VideoDecoderClient::DismissPictureBuffer(int32_t picture_buffer_id) { |
+ if (!display_->DismissPictureBuffer(picture_buffer_id)) { |
+ // TODO(vmr): Handle error properly. |
+ return; |
+ } |
+} |
+ |
+void VideoDecoderClient::PictureReady(const PP_Picture_Dev& picture) { |
+ display_->DrawPicture(picture, cb_factory_.NewCallback( |
+ &VideoDecoderClient::OnDrawPictureDone, picture.picture_buffer_id)); |
+} |
+ |
+void VideoDecoderClient::NotifyEndOfStream() { |
+ end_of_stream_ = true; |
+ video_decoder_->Flush(cb_factory_.NewCallback( |
+ &VideoDecoderClient::OnEOSFlushDone)); |
+} |
+ |
+void VideoDecoderClient::NotifyError(PP_VideoDecodeError_Dev error) { |
+ video_decoder_->Flush(cb_factory_.NewCallback( |
+ &VideoDecoderClient::OnEOSFlushDone)); |
+} |
+ |
+int32_t VideoDecoderClient::GetUniqueId() { |
+ // Not exactly unique in the current form but close enough for use case. |
+ return next_id_++; |
+} |
+ |
+void VideoDecoderClient::OnResourcesAcquired() { |
+ // We're running normally. |
+ ChangeState(kRunning); |
+} |
+ |
+void VideoDecoderClient::OnBitstreamBufferProcessed( |
+ int32_t result, |
+ int32_t bitstream_buffer_id) { |
+ // Reuse each bitstream buffer that has been processed by reading data into it |
+ // as long as there is more and pass that for decoding. |
+ ReadAndDispatchBitstreamUnit(bitstream_buffer_id); |
+} |
+ |
+void VideoDecoderClient::OnDrawPictureDone(int32_t result, |
+ int32_t picture_buffer_id) { |
+ video_decoder_->ReusePictureBuffer(picture_buffer_id); |
+} |
+ |
+void VideoDecoderClient::OnUserFlushDone(int32_t result, |
+ State target_state) { |
+ assert(state_ == kFlushing); |
+ // It was a Flush request, return to the running state. |
+ ChangeState(target_state); |
+} |
+ |
+void VideoDecoderClient::OnEOSFlushDone(int32_t result) { |
+ assert(end_of_stream_); |
+ // It was end of stream flush. |
+ video_decoder_->Abort(cb_factory_.NewCallback( |
+ &VideoDecoderClient::OnAbortDone)); |
+} |
+ |
+void VideoDecoderClient::OnAbortDone(int32_t result) { |
+ // We're done. |
+} |
+ |
+bool VideoDecoderClient::InitializeVideoBitstreamInterface() { |
+ buffer_if_ = static_cast<const struct PPB_Buffer_Dev*>( |
+ pp::Module::Get()->GetBrowserInterface(PPB_BUFFER_DEV_INTERFACE)); |
+ if (!buffer_if_) { |
+ return false; |
+ } |
+ // Allocate |kBitstreamBufferCount| bitstream buffers of |
+ // |kBitstreamBufferSize| bytes. |
+ for (int32_t i = 0; i < kBitstreamBufferCount; i++) { |
+ PP_VideoBitstreamBuffer_Dev bitstream_buffer; |
+ bitstream_buffer.data = buffer_if_->Create(instance_->pp_instance(), |
+ kBitstreamBufferSize); |
+ if (bitstream_buffer.data == 0) { |
+ return false; |
+ } |
+ bitstream_buffer.size = 0; |
+ bitstream_buffer.id = GetUniqueId(); |
+ bitstream_buffers_[bitstream_buffer.id] = bitstream_buffer; |
+ } |
+ return true; |
+} |
+ |
+bool VideoDecoderClient::ReadAndDispatchBitstreamUnit( |
+ int32_t bitstream_buffer_id) { |
+ // Get the target memory and read the bitstream unit into it. |
+ if (bitstream_buffers_.find(bitstream_buffer_id) == |
+ bitstream_buffers_.end()) { |
+ return false; |
+ } |
+ PP_VideoBitstreamBuffer_Dev bitstream_buffer = |
+ bitstream_buffers_[bitstream_buffer_id]; |
+ void* target_mem = buffer_if_->Map(bitstream_buffer.data); |
+ if (target_mem == NULL) { |
+ return false; |
+ } |
+ uint32_t size_in_bytes = 0; |
+ if (!buffer_if_->Describe(bitstream_buffer.data, &size_in_bytes)) { |
+ return false; |
+ } |
+ bool success = video_source_->GetBitstreamUnit(target_mem, size_in_bytes, |
+ &bitstream_buffer.size); |
+ if (!success) { |
+ return false; |
+ } |
+ // Dispatch the bitstream unit to the decoder. |
+ success = video_decoder_->Decode( |
+ bitstream_buffers_[bitstream_buffer_id], |
+ cb_factory_.NewCallback( |
+ &VideoDecoderClient::OnBitstreamBufferProcessed, |
+ bitstream_buffer_id)); |
+ return success; |
+} |
+ |
+void VideoDecoderClient::ChangeState(State to_state) { |
+ state_ = to_state; |
+} |
+ |
+int32_t VideoDecoderClient::next_id_ = 1; |
+ |
+// Pass-through vertex shader. |
+static const char kVertexShader[] = |
+ "precision highp float; precision highp int;\n" |
+ "varying vec2 interp_tc;\n" |
+ "\n" |
+ "attribute vec4 in_pos;\n" |
+ "attribute vec2 in_tc;\n" |
+ "\n" |
+ "void main() {\n" |
+ " interp_tc = in_tc;\n" |
+ " gl_Position = in_pos;\n" |
+ "}\n"; |
+ |
+// Color shader for EGLImage. |
+static const char kFragmentShaderEgl[] = |
+ "precision mediump float;\n" |
+ "precision mediump int;\n" |
+ "varying vec2 interp_tc;\n" |
+ "\n" |
+ "uniform sampler2D tex;\n" |
+ "\n" |
+ "void main() {\n" |
+ " gl_FragColor = texture2D(tex, interp_tc);\n" |
+ "}\n"; |
+ |
+// Buffer size for compile errors. |
+static const unsigned int kShaderErrorSize = 4096; |
+ |
+void GLES2Display::Graphics3DContextLost() { |
+ assert(!"GLES2: Unexpectedly lost graphics context"); |
+} |
+ |
+bool GLES2Display::Initialize(int32_t width, int32_t height) { |
+ if (!InitGL(640, 480)) { |
+ return false; |
+ } |
+ ProgramShaders(); |
+ return true; |
+} |
+ |
+bool GLES2Display::ProvideGLESPictureBuffer( |
+ const std::vector<uint32_t>& buffer_properties, |
+ PP_GLESBuffer_Dev* picture_buffer) { |
+ GLuint texture; |
+ // Generate texture and bind (effectively allocate) it. |
+ gles2_if_->GenTextures(context_->pp_resource(), 1, &texture); |
+ gles2_if_->BindTexture(context_->pp_resource(), GL_TEXTURE_2D, texture); |
+ picture_buffer->context = 0; // TODO(vmr): Get proper context id. |
+ picture_buffer->texture_id = texture; |
+ picture_buffer->info.id = VideoDecoderClient::GetUniqueId(); |
+ picture_buffer->info.size.width = width_; |
+ picture_buffer->info.size.height = height_; |
+ // Store the values into the map for GLES buffers. |
+ gles_buffers_[picture_buffer->info.id] = *picture_buffer; |
+ assertNoGLError(); |
+ return true; |
+} |
+ |
+bool GLES2Display::DismissPictureBuffer(int32_t picture_buffer_id) { |
+ gles2_if_->DeleteTextures(context_->pp_resource(), 1, |
+ &gles_buffers_[picture_buffer_id].texture_id); |
+ gles_buffers_.erase(picture_buffer_id); |
+ return true; |
+} |
+ |
+bool GLES2Display::DrawPicture(const PP_Picture_Dev& picture, |
+ pp::CompletionCallback completion_callback) { |
+ // Decoder has finished decoding picture into the texture, we'll have to just |
+ // draw the texture to the color buffer and swap the surfaces. |
+ // Clear the color buffer. |
+ gles2_if_->Clear(context_->pp_resource(), GL_COLOR_BUFFER_BIT | |
+ GL_DEPTH_BUFFER_BIT); |
+ // Load the texture into texture unit 0. |
+ gles2_if_->ActiveTexture(context_->pp_resource(), GL_TEXTURE0); |
+ gles2_if_->BindTexture(context_->pp_resource(), GL_TEXTURE_2D, |
+ gles_buffers_[picture.picture_buffer_id].texture_id); |
+ // Draw the texture. |
+ gles2_if_->DrawArrays(context_->pp_resource(), GL_TRIANGLE_STRIP, 0, 4); |
+ // Force the execution of pending commands. |
+ // TODO(vmr): Do we have to do this? Can we rely command buffer to execute the |
+ // commands without Finish call? |
+ gles2_if_->Finish(context_->pp_resource()); |
+ assertNoGLError(); |
+ |
+ int32_t error = surface_->SwapBuffers(completion_callback); |
+ if (error != PP_OK) { |
+ return false; |
+ } |
+ assertNoGLError(); |
+ return true; |
+} |
+ |
+void GLES2Display::assertNoGLError() { |
+ assert(!gles2_if_->GetError(context_->pp_resource())); |
+} |
+ |
+bool GLES2Display::InitGL(int width, int height) { |
+ width_ = width; |
+ height_ = height; |
+ assert(width_ && height_); |
+ gles2_if_ = static_cast<const struct PPB_OpenGLES2_Dev*>( |
+ pp::Module::Get()->GetBrowserInterface(PPB_OPENGLES2_DEV_INTERFACE)); |
+ // Firstly, we need OpenGL ES context associated with the display our plugin |
+ // is rendering to. |
+ if (context_) delete(context_); |
+ context_ = new pp::Context3D_Dev(*instance_, 0, pp::Context3D_Dev(), NULL); |
+ assert(!context_->is_null()); |
+ // Then we need surface bound to our fresh context. We'll be actually drawing |
+ // on this surface and swapping that surface to refresh the displayable data |
+ // of the plugin. |
+ int32_t surface_attributes[] = { |
+ PP_GRAPHICS3DATTRIB_WIDTH, width_, |
+ PP_GRAPHICS3DATTRIB_HEIGHT, height_, |
+ PP_GRAPHICS3DATTRIB_NONE |
+ }; |
+ if (surface_) delete(surface_); |
+ surface_ = new pp::Surface3D_Dev(*instance_, 0, surface_attributes); |
+ assert(!surface_->is_null()); |
+ int32_t bind_error = context_->BindSurfaces(*surface_, *surface_); |
+ if (!bind_error) { |
+ assert(bind_error); |
+ } |
+ assertNoGLError(); |
+ |
+ bool success = instance_->BindGraphics(*surface_); |
+ if (!success) { |
+ assert(success); |
+ } |
+ // Clear the color buffer with opaque white for starters. |
+ gles2_if_->ClearColor(context_->pp_resource(), 1.0, 1.0, 1.0, 0.0); |
+ gles2_if_->Clear(context_->pp_resource(), GL_COLOR_BUFFER_BIT); |
+ // Set the viewport to match the whole GL window. |
+ gles2_if_->Viewport(context_->pp_resource(), 0, 0, width_, height_); |
+ assertNoGLError(); |
+ return true; |
+} |
+ |
+void GLES2Display::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); |
+ |
+ int result = GL_FALSE; |
+ gles2_if_->GetShaderiv( |
+ context_->pp_resource(), shader, GL_COMPILE_STATUS, &result); |
+ if (!result) { |
+ char log[kShaderErrorSize]; |
+ int len = 0; |
+ gles2_if_->GetShaderInfoLog(context_->pp_resource(), shader, |
+ kShaderErrorSize - 1, &len, log); |
+ log[len] = 0; |
+ assert(result); |
+ } |
+ gles2_if_->AttachShader(context_->pp_resource(), program, shader); |
+ gles2_if_->DeleteShader(context_->pp_resource(), shader); |
+} |
+ |
+void GLES2Display::LinkProgram(const PPB_OpenGLES2_Dev* gles2_if_ ) { |
+ gles2_if_->LinkProgram(context_->pp_resource(), program_); |
+ int result = GL_FALSE; |
+ gles2_if_->GetProgramiv(context_->pp_resource(), program_, GL_LINK_STATUS, |
+ &result); |
+ if (!result) { |
+ char log[kShaderErrorSize]; |
+ int len = 0; |
+ gles2_if_->GetProgramInfoLog(context_->pp_resource(), program_, |
+ kShaderErrorSize - 1, &len, log); |
+ log[len] = 0; |
+ assert(result); |
+ } |
+ gles2_if_->UseProgram(context_->pp_resource(), program_); |
+} |
+ |
+void GLES2Display::ProgramShaders() { |
+ // Vertices for a full screen quad. |
+ static const float kVertices[] = { |
+ -1.f, 1.f, |
+ -1.f, -1.f, |
+ 1.f, 1.f, |
+ 1.f, -1.f, |
+ }; |
+ |
+ // Texture Coordinates mapping the entire texture for EGL image. |
+ static const float kTextureCoordsEgl[] = { |
+ 0, 1, |
+ 0, 0, |
+ 1, 1, |
+ 1, 0, |
+ }; |
+ program_ = gles2_if_->CreateProgram(context_->pp_resource()); |
+ |
+ // Create shader for EGL image |
+ CreateShader(program_, GL_VERTEX_SHADER, |
+ kVertexShader, sizeof(kVertexShader)); |
+ CreateShader(program_, GL_FRAGMENT_SHADER, |
+ kFragmentShaderEgl, sizeof(kFragmentShaderEgl)); |
+ LinkProgram(gles2_if_); |
+ |
+ assertNoGLError(); |
+ // Bind parameters. |
+ gles2_if_->Uniform1i(context_->pp_resource(), gles2_if_-> |
+ GetUniformLocation(context_->pp_resource(), program_, |
+ "tex"), 0); |
+ gles2_if_->GenBuffers(context_->pp_resource(), 1, &vertex_); |
+ gles2_if_->BindBuffer(context_->pp_resource(), GL_ARRAY_BUFFER, |
+ vertex_); |
+ gles2_if_->BufferData(context_->pp_resource(), GL_ARRAY_BUFFER, |
+ 8 * sizeof(kVertices[0]), kVertices, GL_STREAM_DRAW); |
+ |
+ assertNoGLError(); |
+ int pos_location = gles2_if_->GetAttribLocation(context_->pp_resource(), |
+ program_, "in_pos"); |
+ gles2_if_->EnableVertexAttribArray(context_->pp_resource(), pos_location); |
+ gles2_if_->VertexAttribPointer(context_->pp_resource(), pos_location, 2, |
+ GL_FLOAT, GL_FALSE, 0, 0); |
+ |
+ assertNoGLError(); |
+ gles2_if_->GenBuffers(context_->pp_resource(), 1, &fragment_); |
+ gles2_if_->BindBuffer(context_->pp_resource(), GL_ARRAY_BUFFER, |
+ fragment_); |
+ gles2_if_->BufferData(context_->pp_resource(), GL_ARRAY_BUFFER, |
+ 8 * sizeof(kTextureCoordsEgl[0]), |
+ kTextureCoordsEgl, GL_STREAM_DRAW); |
+ assertNoGLError(); |
+ int tc_location = gles2_if_->GetAttribLocation(context_->pp_resource(), |
+ program_, "in_tc"); |
+ gles2_if_->EnableVertexAttribArray(context_->pp_resource(), tc_location); |
+ gles2_if_->VertexAttribPointer(context_->pp_resource(), tc_location, 2, |
+ GL_FLOAT, GL_FALSE, 0, kTextureCoordsEgl); |
+ gles2_if_->VertexAttribPointer(context_->pp_resource(), tc_location, 2, |
+ GL_FLOAT, GL_FALSE, 0, 0); |
+ gles2_if_->Enable(context_->pp_resource(), GL_DEPTH_TEST); |
+ assertNoGLError(); |
+} |
+ |