// Copyright (c) 2011 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. // A class to emulate GLES2 over command buffers. #include "../client/gles2_implementation.h" #include #include #include #include "../client/mapped_memory.h" #include "../client/program_info_manager.h" #include "../common/gles2_cmd_utils.h" #include "../common/id_allocator.h" #include "../common/trace_event.h" #if defined(__native_client__) && !defined(GLES2_SUPPORT_CLIENT_SIDE_ARRAYS) #define GLES2_SUPPORT_CLIENT_SIDE_ARRAYS #endif #if defined(GPU_CLIENT_DEBUG) #include "ui/gfx/gl/gl_switches.h" #include "base/command_line.h" #endif namespace gpu { namespace gles2 { // A 32-bit and 64-bit compatible way of converting a pointer to a GLuint. static GLuint ToGLuint(const void* ptr) { return static_cast(reinterpret_cast(ptr)); } // An id handler for non-shared ids. class NonSharedIdHandler : public IdHandlerInterface { public: NonSharedIdHandler() { } virtual ~NonSharedIdHandler() { } // Overridden from IdHandlerInterface. virtual void MakeIds(GLuint id_offset, GLsizei n, GLuint* ids) { if (id_offset == 0) { for (GLsizei ii = 0; ii < n; ++ii) { ids[ii] = id_allocator_.AllocateID(); } } else { for (GLsizei ii = 0; ii < n; ++ii) { ids[ii] = id_allocator_.AllocateIDAtOrAbove(id_offset); id_offset = ids[ii] + 1; } } } // Overridden from IdHandlerInterface. virtual bool FreeIds(GLsizei n, const GLuint* ids) { for (GLsizei ii = 0; ii < n; ++ii) { id_allocator_.FreeID(ids[ii]); } return true; } // Overridden from IdHandlerInterface. virtual bool MarkAsUsedForBind(GLuint id) { return id == 0 ? true : id_allocator_.MarkAsUsed(id); } private: IdAllocator id_allocator_; }; // An id handler for non-shared ids that are never reused. class NonSharedNonReusedIdHandler : public IdHandlerInterface { public: NonSharedNonReusedIdHandler() : last_id_(0) { } virtual ~NonSharedNonReusedIdHandler() { } // Overridden from IdHandlerInterface. virtual void MakeIds(GLuint id_offset, GLsizei n, GLuint* ids) { for (GLsizei ii = 0; ii < n; ++ii) { ids[ii] = ++last_id_ + id_offset; } } // Overridden from IdHandlerInterface. virtual bool FreeIds(GLsizei /* n */, const GLuint* /* ids */) { // Ids are never freed. return true; } // Overridden from IdHandlerInterface. virtual bool MarkAsUsedForBind(GLuint /* id */) { // This is only used for Shaders and Programs which have no bind. return false; } private: GLuint last_id_; }; // An id handler for shared ids. class SharedIdHandler : public IdHandlerInterface { public: SharedIdHandler( GLES2Implementation* gles2, id_namespaces::IdNamespaces id_namespace) : gles2_(gles2), id_namespace_(id_namespace) { } virtual ~SharedIdHandler() { } virtual void MakeIds(GLuint id_offset, GLsizei n, GLuint* ids) { gles2_->GenSharedIdsCHROMIUM(id_namespace_, id_offset, n, ids); } virtual bool FreeIds(GLsizei n, const GLuint* ids) { gles2_->DeleteSharedIdsCHROMIUM(id_namespace_, n, ids); return true; } virtual bool MarkAsUsedForBind(GLuint /* id */) { // This has no meaning for shared resources. return true; } private: GLES2Implementation* gles2_; id_namespaces::IdNamespaces id_namespace_; }; // An id handler for shared ids that requires ids are made before using and // that only the context that created the id can delete it. // Assumes the service will enforce that non made ids generate an error. class StrictSharedIdHandler : public IdHandlerInterface { public: StrictSharedIdHandler( GLES2Implementation* gles2, id_namespaces::IdNamespaces id_namespace) : gles2_(gles2), id_namespace_(id_namespace) { } virtual ~StrictSharedIdHandler() { } virtual void MakeIds(GLuint id_offset, GLsizei n, GLuint* ids) { for (GLsizei ii = 0; ii < n; ++ii) { ids[ii] = GetId(id_offset); } } virtual bool FreeIds(GLsizei n, const GLuint* ids) { // OpenGL sematics. If any id is bad none of them get freed. for (GLsizei ii = 0; ii < n; ++ii) { GLuint id = ids[ii]; if (id != 0) { ResourceIdSet::iterator it = used_ids_.find(id); if (it == used_ids_.end()) { return false; } } } for (GLsizei ii = 0; ii < n; ++ii) { GLuint id = ids[ii]; if (id != 0) { ResourceIdSet::iterator it = used_ids_.find(id); if (it != used_ids_.end()) { used_ids_.erase(it); free_ids_.push(id); } } } return true; } virtual bool MarkAsUsedForBind(GLuint /* id */) { // This has no meaning for shared resources. return true; } private: static const GLsizei kNumIdsToGet = 2048; typedef std::queue ResourceIdQueue; typedef std::set ResourceIdSet; GLuint GetId(GLuint id_offset) { if (free_ids_.empty()) { GLuint ids[kNumIdsToGet]; gles2_->GenSharedIdsCHROMIUM(id_namespace_, id_offset, kNumIdsToGet, ids); for (GLsizei ii = 0; ii < kNumIdsToGet; ++ii) { free_ids_.push(ids[ii]); } } GLuint id = free_ids_.front(); free_ids_.pop(); used_ids_.insert(id); return id; } bool FreeId(GLuint id) { ResourceIdSet::iterator it = used_ids_.find(id); if (it == used_ids_.end()) { return false; } used_ids_.erase(it); free_ids_.push(id); return true; } GLES2Implementation* gles2_; id_namespaces::IdNamespaces id_namespace_; ResourceIdSet used_ids_; ResourceIdQueue free_ids_; }; #ifndef _MSC_VER const GLsizei StrictSharedIdHandler::kNumIdsToGet; #endif static GLsizei RoundUpToMultipleOf4(GLsizei size) { return (size + 3) & ~3; } // This class tracks VertexAttribPointers and helps emulate client side buffers. // // The way client side buffers work is we shadow all the Vertex Attribs so we // know which ones are pointing to client side buffers. // // At Draw time, for any attribs pointing to client side buffers we copy them // to a special VBO and reset the actual vertex attrib pointers to point to this // VBO. // // This also means we have to catch calls to query those values so that when // an attrib is a client side buffer we pass the info back the user expects. class ClientSideBufferHelper { public: // Info about Vertex Attributes. This is used to track what the user currently // has bound on each Vertex Attribute so we can simulate client side buffers // at glDrawXXX time. class VertexAttribInfo { public: VertexAttribInfo() : enabled_(false), buffer_id_(0), size_(4), type_(GL_FLOAT), normalized_(GL_FALSE), pointer_(NULL), gl_stride_(0) { } bool enabled() const { return enabled_; } void set_enabled(bool enabled) { enabled_ = enabled; } GLuint buffer_id() const { return buffer_id_; } GLenum type() const { return type_; } GLint size() const { return size_; } GLsizei stride() const { return gl_stride_; } GLboolean normalized() const { return normalized_; } const GLvoid* pointer() const { return pointer_; } bool IsClientSide() const { return buffer_id_ == 0; } void SetInfo( GLuint buffer_id, GLint size, GLenum type, GLboolean normalized, GLsizei gl_stride, const GLvoid* pointer) { buffer_id_ = buffer_id; size_ = size; type_ = type; normalized_ = normalized; gl_stride_ = gl_stride; pointer_ = pointer; } private: // Whether or not this attribute is enabled. bool enabled_; // The id of the buffer. 0 = client side buffer. GLuint buffer_id_; // Number of components (1, 2, 3, 4). GLint size_; // GL_BYTE, GL_FLOAT, etc. See glVertexAttribPointer. GLenum type_; // GL_TRUE or GL_FALSE GLboolean normalized_; // The pointer/offset into the buffer. const GLvoid* pointer_; // The stride that will be used to access the buffer. This is the bogus GL // stride where 0 = compute the stride based on size and type. GLsizei gl_stride_; }; ClientSideBufferHelper(GLuint max_vertex_attribs, GLuint array_buffer_id, GLuint element_array_buffer_id) : max_vertex_attribs_(max_vertex_attribs), num_client_side_pointers_enabled_(0), array_buffer_id_(array_buffer_id), array_buffer_size_(0), array_buffer_offset_(0), element_array_buffer_id_(element_array_buffer_id), element_array_buffer_size_(0), collection_buffer_size_(0) { vertex_attrib_infos_.reset(new VertexAttribInfo[max_vertex_attribs]); } bool HaveEnabledClientSideBuffers() const { return num_client_side_pointers_enabled_ > 0; } void SetAttribEnable(GLuint index, bool enabled) { if (index < max_vertex_attribs_) { VertexAttribInfo& info = vertex_attrib_infos_[index]; if (info.enabled() != enabled) { if (info.IsClientSide()) { num_client_side_pointers_enabled_ += enabled ? 1 : -1; } info.set_enabled(enabled); } } } void SetAttribPointer( GLuint buffer_id, GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void* ptr) { if (index < max_vertex_attribs_) { VertexAttribInfo& info = vertex_attrib_infos_[index]; if (info.IsClientSide() && info.enabled()) { --num_client_side_pointers_enabled_; } info.SetInfo(buffer_id, size, type, normalized, stride, ptr); if (info.IsClientSide() && info.enabled()) { ++num_client_side_pointers_enabled_; } } } // Gets the Attrib pointer for an attrib but only if it's a client side // pointer. Returns true if it got the pointer. bool GetAttribPointer(GLuint index, GLenum pname, void** ptr) const { const VertexAttribInfo* info = GetAttribInfo(index); if (info && pname == GL_VERTEX_ATTRIB_ARRAY_POINTER) { *ptr = const_cast(info->pointer()); return true; } return false; } // Gets an attrib info if it's in range and it's client side. const VertexAttribInfo* GetAttribInfo(GLuint index) const { if (index < max_vertex_attribs_) { VertexAttribInfo* info = &vertex_attrib_infos_[index]; if (info->IsClientSide()) { return info; } } return NULL; } // Collects the data into the collection buffer and returns the number of // bytes collected. GLsizei CollectData(const void* data, GLsizei bytes_per_element, GLsizei real_stride, GLsizei num_elements) { GLsizei bytes_needed = bytes_per_element * num_elements; if (collection_buffer_size_ < bytes_needed) { collection_buffer_.reset(new int8[bytes_needed]); collection_buffer_size_ = bytes_needed; } const int8* src = static_cast(data); int8* dst = collection_buffer_.get(); int8* end = dst + bytes_per_element * num_elements; for (; dst < end; src += real_stride, dst += bytes_per_element) { memcpy(dst, src, bytes_per_element); } return bytes_needed; } // Returns true if buffers were setup. void SetupSimualtedClientSideBuffers( GLES2Implementation* gl, GLES2CmdHelper* gl_helper, GLsizei num_elements) { GLsizei total_size = 0; // Compute the size of the buffer we need. for (GLuint ii = 0; ii < max_vertex_attribs_; ++ii) { VertexAttribInfo& info = vertex_attrib_infos_[ii]; if (info.IsClientSide() && info.enabled()) { size_t bytes_per_element = GLES2Util::GetGLTypeSizeForTexturesAndBuffers(info.type()) * info.size(); total_size += RoundUpToMultipleOf4( bytes_per_element * num_elements); } } gl_helper->BindBuffer(GL_ARRAY_BUFFER, array_buffer_id_); array_buffer_offset_ = 0; if (total_size > array_buffer_size_) { gl->BufferData(GL_ARRAY_BUFFER, total_size, NULL, GL_DYNAMIC_DRAW); array_buffer_size_ = total_size; } for (GLuint ii = 0; ii < max_vertex_attribs_; ++ii) { VertexAttribInfo& info = vertex_attrib_infos_[ii]; if (info.IsClientSide() && info.enabled()) { size_t bytes_per_element = GLES2Util::GetGLTypeSizeForTexturesAndBuffers(info.type()) * info.size(); GLsizei real_stride = info.stride() ? info.stride() : static_cast(bytes_per_element); GLsizei bytes_collected = CollectData( info.pointer(), bytes_per_element, real_stride, num_elements); gl->BufferSubData( GL_ARRAY_BUFFER, array_buffer_offset_, bytes_collected, collection_buffer_.get()); gl_helper->VertexAttribPointer( ii, info.size(), info.type(), info.normalized(), 0, array_buffer_offset_); array_buffer_offset_ += RoundUpToMultipleOf4(bytes_collected); GPU_DCHECK_LE(array_buffer_offset_, array_buffer_size_); } } } // Copies in indices to the service and returns the highest index accessed + 1 GLsizei SetupSimulatedIndexBuffer( GLES2Implementation* gl, GLES2CmdHelper* gl_helper, GLsizei count, GLenum type, const void* indices) { gl_helper->BindBuffer(GL_ELEMENT_ARRAY_BUFFER, element_array_buffer_id_); GLsizei bytes_per_element = GLES2Util::GetGLTypeSizeForTexturesAndBuffers(type); GLsizei bytes_needed = bytes_per_element * count; if (bytes_needed > element_array_buffer_size_) { element_array_buffer_size_ = bytes_needed; gl->BufferData(GL_ELEMENT_ARRAY_BUFFER, bytes_needed, NULL, GL_DYNAMIC_DRAW); } gl->BufferSubData(GL_ELEMENT_ARRAY_BUFFER, 0, bytes_needed, indices); GLsizei max_index = -1; switch (type) { case GL_UNSIGNED_BYTE: { const uint8* src = static_cast(indices); for (GLsizei ii = 0; ii < count; ++ii) { if (src[ii] > max_index) { max_index = src[ii]; } } break; } case GL_UNSIGNED_SHORT: { const uint16* src = static_cast(indices); for (GLsizei ii = 0; ii < count; ++ii) { if (src[ii] > max_index) { max_index = src[ii]; } } break; } default: break; } return max_index + 1; } private: GLuint max_vertex_attribs_; GLuint num_client_side_pointers_enabled_; GLuint array_buffer_id_; GLsizei array_buffer_size_; GLsizei array_buffer_offset_; GLuint element_array_buffer_id_; GLsizei element_array_buffer_size_; scoped_array vertex_attrib_infos_; GLsizei collection_buffer_size_; scoped_array collection_buffer_; DISALLOW_COPY_AND_ASSIGN(ClientSideBufferHelper); }; #if !defined(_MSC_VER) const size_t GLES2Implementation::kMaxSizeOfSimpleResult; #endif COMPILE_ASSERT(gpu::kInvalidResource == 0, INVALID_RESOURCE_NOT_0_AS_GL_EXPECTS); GLES2Implementation::GLES2Implementation( GLES2CmdHelper* helper, size_t transfer_buffer_size, void* transfer_buffer, int32 transfer_buffer_id, bool share_resources, bool bind_generates_resource) : helper_(helper), transfer_buffer_( kStartingOffset, transfer_buffer_size - kStartingOffset, helper, static_cast(transfer_buffer) + kStartingOffset), transfer_buffer_id_(transfer_buffer_id), pack_alignment_(4), unpack_alignment_(4), unpack_flip_y_(false), active_texture_unit_(0), bound_framebuffer_(0), bound_renderbuffer_(0), bound_array_buffer_id_(0), bound_element_array_buffer_id_(0), client_side_array_id_(0), client_side_element_array_id_(0), error_bits_(0), debug_(false), sharing_resources_(share_resources), bind_generates_resource_(bind_generates_resource) { GPU_CLIENT_LOG_CODE_BLOCK({ debug_ = CommandLine::ForCurrentProcess()->HasSwitch( switches::kEnableGPUClientLogging); }); // Allocate space for simple GL results. result_buffer_ = transfer_buffer; result_shm_offset_ = 0; memset(&reserved_ids_, 0, sizeof(reserved_ids_)); mapped_memory_.reset(new MappedMemoryManager(helper_)); SetSharedMemoryChunkSizeMultiple(1024 * 1024 * 2); if (share_resources) { if (!bind_generates_resource) { buffer_id_handler_.reset( new StrictSharedIdHandler(this, id_namespaces::kBuffers)); framebuffer_id_handler_.reset( new StrictSharedIdHandler(this, id_namespaces::kFramebuffers)); renderbuffer_id_handler_.reset( new StrictSharedIdHandler(this, id_namespaces::kRenderbuffers)); program_and_shader_id_handler_.reset( new StrictSharedIdHandler(this, id_namespaces::kProgramsAndShaders)); texture_id_handler_.reset( new StrictSharedIdHandler(this, id_namespaces::kTextures)); } else { buffer_id_handler_.reset( new SharedIdHandler(this, id_namespaces::kBuffers)); framebuffer_id_handler_.reset( new SharedIdHandler(this, id_namespaces::kFramebuffers)); renderbuffer_id_handler_.reset( new SharedIdHandler(this, id_namespaces::kRenderbuffers)); program_and_shader_id_handler_.reset( new SharedIdHandler(this, id_namespaces::kProgramsAndShaders)); texture_id_handler_.reset( new SharedIdHandler(this, id_namespaces::kTextures)); } } else { buffer_id_handler_.reset(new NonSharedIdHandler()); framebuffer_id_handler_.reset(new NonSharedIdHandler()); renderbuffer_id_handler_.reset(new NonSharedIdHandler()); program_and_shader_id_handler_.reset(new NonSharedNonReusedIdHandler()); texture_id_handler_.reset(new NonSharedIdHandler()); } static const GLenum pnames[] = { GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, GL_MAX_CUBE_MAP_TEXTURE_SIZE, GL_MAX_FRAGMENT_UNIFORM_VECTORS, GL_MAX_RENDERBUFFER_SIZE, GL_MAX_TEXTURE_IMAGE_UNITS, GL_MAX_TEXTURE_SIZE, GL_MAX_VARYING_VECTORS, GL_MAX_VERTEX_ATTRIBS, GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS, GL_MAX_VERTEX_UNIFORM_VECTORS, GL_NUM_COMPRESSED_TEXTURE_FORMATS, GL_NUM_SHADER_BINARY_FORMATS, }; util_.set_num_compressed_texture_formats( gl_state_.num_compressed_texture_formats); util_.set_num_shader_binary_formats( gl_state_.num_shader_binary_formats); GetMultipleIntegervCHROMIUM( pnames, arraysize(pnames), &gl_state_.max_combined_texture_image_units, sizeof(gl_state_)); texture_units_.reset( new TextureUnit[gl_state_.max_combined_texture_image_units]); program_info_manager_.reset(ProgramInfoManager::Create(sharing_resources_)); #if defined(GLES2_SUPPORT_CLIENT_SIDE_ARRAYS) buffer_id_handler_->MakeIds( kClientSideArrayId, arraysize(reserved_ids_), &reserved_ids_[0]); client_side_buffer_helper_.reset(new ClientSideBufferHelper( gl_state_.max_vertex_attribs, reserved_ids_[0], reserved_ids_[1])); #endif } GLES2Implementation::~GLES2Implementation() { #if defined(GLES2_SUPPORT_CLIENT_SIDE_ARRAYS) DeleteBuffers(arraysize(reserved_ids_), &reserved_ids_[0]); #endif } void GLES2Implementation::SetSharedMemoryChunkSizeMultiple( unsigned int multiple) { mapped_memory_->set_chunk_size_multiple(multiple); } void GLES2Implementation::FreeUnusedSharedMemory() { mapped_memory_->FreeUnused(); } void GLES2Implementation::WaitForCmd() { TRACE_EVENT0("gpu", "GLES2::WaitForCmd"); helper_->CommandBufferHelper::Finish(); } GLenum GLES2Implementation::GetError() { GPU_CLIENT_LOG("[" << this << "] glGetError()"); GLenum err = GetGLError(); GPU_CLIENT_LOG("returned " << GLES2Util::GetStringError(err)); return err; } GLenum GLES2Implementation::GetGLError() { TRACE_EVENT0("gpu", "GLES2::GetGLError"); // Check the GL error first, then our wrapped error. typedef gles2::GetError::Result Result; Result* result = GetResultAs(); *result = GL_NO_ERROR; helper_->GetError(result_shm_id(), result_shm_offset()); WaitForCmd(); GLenum error = *result; if (error == GL_NO_ERROR && error_bits_ != 0) { for (uint32 mask = 1; mask != 0; mask = mask << 1) { if ((error_bits_ & mask) != 0) { error = GLES2Util::GLErrorBitToGLError(mask); break; } } } if (error != GL_NO_ERROR) { // There was an error, clear the corresponding wrapped error. error_bits_ &= ~GLES2Util::GLErrorToErrorBit(error); } return error; } void GLES2Implementation::SetGLError(GLenum error, const char* msg) { GPU_CLIENT_LOG("[" << this << "] Client Synthesized Error: " << GLES2Util::GetStringError(error) << ": " << msg); if (msg) { last_error_ = msg; } error_bits_ |= GLES2Util::GLErrorToErrorBit(error); } void GLES2Implementation::GetBucketContents(uint32 bucket_id, std::vector* data) { TRACE_EVENT0("gpu", "GLES2::GetBucketContents"); GPU_DCHECK(data); typedef cmd::GetBucketSize::Result Result; Result* result = GetResultAs(); *result = 0; helper_->GetBucketSize(bucket_id, result_shm_id(), result_shm_offset()); WaitForCmd(); uint32 size = *result; data->resize(size); if (size > 0u) { uint32 max_size = transfer_buffer_.GetLargestFreeOrPendingSize(); uint32 offset = 0; while (size) { uint32 part_size = std::min(max_size, size); void* buffer = transfer_buffer_.Alloc(part_size); helper_->GetBucketData( bucket_id, offset, part_size, transfer_buffer_id_, transfer_buffer_.GetOffset(buffer)); WaitForCmd(); memcpy(&(*data)[offset], buffer, part_size); transfer_buffer_.FreePendingToken(buffer, helper_->InsertToken()); offset += part_size; size -= part_size; } // Free the bucket. This is not required but it does free up the memory. // and we don't have to wait for the result so from the client's perspective // it's cheap. helper_->SetBucketSize(bucket_id, 0); } } void GLES2Implementation::SetBucketContents( uint32 bucket_id, const void* data, size_t size) { GPU_DCHECK(data); helper_->SetBucketSize(bucket_id, size); if (size > 0u) { uint32 max_size = transfer_buffer_.GetLargestFreeOrPendingSize(); uint32 offset = 0; while (size) { uint32 part_size = std::min(static_cast(max_size), size); void* buffer = transfer_buffer_.Alloc(part_size); memcpy(buffer, static_cast(data) + offset, part_size); helper_->SetBucketData( bucket_id, offset, part_size, transfer_buffer_id_, transfer_buffer_.GetOffset(buffer)); transfer_buffer_.FreePendingToken(buffer, helper_->InsertToken()); offset += part_size; size -= part_size; } } } void GLES2Implementation::SetBucketAsCString( uint32 bucket_id, const char* str) { // NOTE: strings are passed NULL terminated. That means the empty // string will have a size of 1 and no-string will have a size of 0 if (str) { SetBucketContents(bucket_id, str, strlen(str) + 1); } else { helper_->SetBucketSize(bucket_id, 0); } } bool GLES2Implementation::GetBucketAsString( uint32 bucket_id, std::string* str) { GPU_DCHECK(str); std::vector data; // NOTE: strings are passed NULL terminated. That means the empty // string will have a size of 1 and no-string will have a size of 0 GetBucketContents(bucket_id, &data); if (data.empty()) { return false; } str->assign(&data[0], &data[0] + data.size() - 1); return true; } void GLES2Implementation::SetBucketAsString( uint32 bucket_id, const std::string& str) { // NOTE: strings are passed NULL terminated. That means the empty // string will have a size of 1 and no-string will have a size of 0 SetBucketContents(bucket_id, str.c_str(), str.size() + 1); } bool GLES2Implementation::GetHelper(GLenum pname, GLint* params) { switch (pname) { case GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS: *params = gl_state_.max_combined_texture_image_units; return true; case GL_MAX_CUBE_MAP_TEXTURE_SIZE: *params = gl_state_.max_cube_map_texture_size; return true; case GL_MAX_FRAGMENT_UNIFORM_VECTORS: *params = gl_state_.max_fragment_uniform_vectors; return true; case GL_MAX_RENDERBUFFER_SIZE: *params = gl_state_.max_renderbuffer_size; return true; case GL_MAX_TEXTURE_IMAGE_UNITS: *params = gl_state_.max_texture_image_units; return true; case GL_MAX_TEXTURE_SIZE: *params = gl_state_.max_texture_size; return true; case GL_MAX_VARYING_VECTORS: *params = gl_state_.max_varying_vectors; return true; case GL_MAX_VERTEX_ATTRIBS: *params = gl_state_.max_vertex_attribs; return true; case GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS: *params = gl_state_.max_vertex_texture_image_units; return true; case GL_MAX_VERTEX_UNIFORM_VECTORS: *params = gl_state_.max_vertex_uniform_vectors; return true; case GL_NUM_COMPRESSED_TEXTURE_FORMATS: *params = gl_state_.num_compressed_texture_formats; return true; case GL_NUM_SHADER_BINARY_FORMATS: *params = gl_state_.num_shader_binary_formats; return true; case GL_ARRAY_BUFFER_BINDING: if (bind_generates_resource_) { *params = bound_array_buffer_id_; return true; } return false; case GL_ELEMENT_ARRAY_BUFFER_BINDING: if (bind_generates_resource_) { *params = bound_element_array_buffer_id_; return true; } return false; case GL_ACTIVE_TEXTURE: *params = active_texture_unit_ + GL_TEXTURE0; return true; case GL_TEXTURE_BINDING_2D: if (bind_generates_resource_) { *params = texture_units_[active_texture_unit_].bound_texture_2d; return true; } return false; case GL_TEXTURE_BINDING_CUBE_MAP: if (bind_generates_resource_) { *params = texture_units_[active_texture_unit_].bound_texture_cube_map; return true; } return false; case GL_FRAMEBUFFER_BINDING: if (bind_generates_resource_) { *params = bound_framebuffer_; return true; } return false; case GL_RENDERBUFFER_BINDING: if (bind_generates_resource_) { *params = bound_renderbuffer_; return true; } return false; default: return false; } } bool GLES2Implementation::GetBooleanvHelper(GLenum pname, GLboolean* params) { // TODO(gman): Make this handle pnames that return more than 1 value. GLint value; if (!GetHelper(pname, &value)) { return false; } *params = static_cast(value); return true; } bool GLES2Implementation::GetFloatvHelper(GLenum pname, GLfloat* params) { // TODO(gman): Make this handle pnames that return more than 1 value. GLint value; if (!GetHelper(pname, &value)) { return false; } *params = static_cast(value); return true; } bool GLES2Implementation::GetIntegervHelper(GLenum pname, GLint* params) { return GetHelper(pname, params); } void GLES2Implementation::DrawElements( GLenum mode, GLsizei count, GLenum type, const void* indices) { GPU_CLIENT_LOG("[" << this << "] glDrawElements(" << GLES2Util::GetStringDrawMode(mode) << ", " << count << ", " << GLES2Util::GetStringIndexType(type) << ", " << static_cast(indices) << ")"); if (count < 0) { SetGLError(GL_INVALID_VALUE, "glDrawElements: count less than 0."); return; } if (count == 0) { return; } #if defined(GLES2_SUPPORT_CLIENT_SIDE_ARRAYS) bool have_client_side = client_side_buffer_helper_->HaveEnabledClientSideBuffers(); GLsizei num_elements = 0; GLuint offset = ToGLuint(indices); if (bound_element_array_buffer_id_ == 0) { // Index buffer is client side array. // Copy to buffer, scan for highest index. num_elements = client_side_buffer_helper_->SetupSimulatedIndexBuffer( this, helper_, count, type, indices); offset = 0; } else { // Index buffer is GL buffer. Ask the service for the highest vertex // that will be accessed. Note: It doesn't matter if another context // changes the contents of any of the buffers. The service will still // validate the indices. We just need to know how much to copy across. if (have_client_side) { num_elements = GetMaxValueInBufferCHROMIUM( bound_element_array_buffer_id_, count, type, ToGLuint(indices)) + 1; } } if (have_client_side) { client_side_buffer_helper_->SetupSimualtedClientSideBuffers( this, helper_, num_elements); } helper_->DrawElements(mode, count, type, offset); if (have_client_side) { // Restore the user's current binding. helper_->BindBuffer(GL_ARRAY_BUFFER, bound_array_buffer_id_); } if (bound_element_array_buffer_id_ == 0) { // Restore the element array binding. helper_->BindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); } #else helper_->DrawElements(mode, count, type, ToGLuint(indices)); #endif } void GLES2Implementation::Flush() { GPU_CLIENT_LOG("[" << this << "] glFlush()"); // Insert the cmd to call glFlush helper_->Flush(); // Flush our command buffer // (tell the service to execute up to the flush cmd.) helper_->CommandBufferHelper::Flush(); } void GLES2Implementation::Finish() { GPU_CLIENT_LOG("[" << this << "] glFinish()"); TRACE_EVENT0("gpu", "GLES2::Finish"); // Insert the cmd to call glFinish helper_->Finish(); // Finish our command buffer // (tell the service to execute up to the Finish cmd and wait for it to // execute.) helper_->CommandBufferHelper::Finish(); } void GLES2Implementation::SwapBuffers() { GPU_CLIENT_LOG("[" << this << "] glSwapBuffers()"); // TODO(piman): Strictly speaking we'd want to insert the token after the // swap, but the state update with the updated token might not have happened // by the time the SwapBuffer callback gets called, forcing us to synchronize // with the GPU process more than needed. So instead, make it happen before. // All it means is that we could be slightly looser on the kMaxSwapBuffers // semantics if the client doesn't use the callback mechanism, and by chance // the scheduler yields between the InsertToken and the SwapBuffers. swap_buffers_tokens_.push(helper_->InsertToken()); helper_->SwapBuffers(); helper_->CommandBufferHelper::Flush(); // Wait if we added too many swap buffers. Add 1 to kMaxSwapBuffers to // compensate for TODO above. if (swap_buffers_tokens_.size() > kMaxSwapBuffers + 1) { helper_->WaitForToken(swap_buffers_tokens_.front()); swap_buffers_tokens_.pop(); } } void GLES2Implementation::GenSharedIdsCHROMIUM( GLuint namespace_id, GLuint id_offset, GLsizei n, GLuint* ids) { GPU_CLIENT_LOG("[" << this << "] glGenSharedIdsCHROMIUMTextures(" << namespace_id << ", " << id_offset << ", " << n << ", " << static_cast(ids) << ")"); GPU_CLIENT_LOG_CODE_BLOCK({ for (GLsizei i = 0; i < n; ++i) { GPU_CLIENT_LOG(" " << i << ": " << ids[i]); } }); TRACE_EVENT0("gpu", "GLES2::GenSharedIdsCHROMIUM"); GLsizei max_size = transfer_buffer_.GetLargestFreeOrPendingSize(); GLsizei max_num_per = max_size / sizeof(ids[0]); while (n) { GLsizei num = std::min(n, max_num_per); GLint* id_buffer = transfer_buffer_.AllocTyped(num); helper_->GenSharedIdsCHROMIUM( namespace_id, id_offset, num, transfer_buffer_id_, transfer_buffer_.GetOffset(id_buffer)); WaitForCmd(); memcpy(ids, id_buffer, sizeof(*ids) * num); transfer_buffer_.FreePendingToken(id_buffer, helper_->InsertToken()); n -= num; ids += num; } } void GLES2Implementation::DeleteSharedIdsCHROMIUM( GLuint namespace_id, GLsizei n, const GLuint* ids) { GPU_CLIENT_LOG("[" << this << "] glDeleteSharedIdsCHROMIUM(" << namespace_id << ", " << n << ", " << static_cast(ids) << ")"); GPU_CLIENT_LOG_CODE_BLOCK({ for (GLsizei i = 0; i < n; ++i) { GPU_CLIENT_LOG(" " << i << ": " << ids[i]); } }); TRACE_EVENT0("gpu", "GLES2::DeleteSharedIdsCHROMIUM"); GLsizei max_size = transfer_buffer_.GetLargestFreeOrPendingSize(); GLsizei max_num_per = max_size / sizeof(ids[0]); while (n) { GLsizei num = std::min(n, max_num_per); GLint* id_buffer = transfer_buffer_.AllocTyped(num); memcpy(id_buffer, ids, sizeof(*ids) * num); helper_->DeleteSharedIdsCHROMIUM( namespace_id, num, transfer_buffer_id_, transfer_buffer_.GetOffset(id_buffer)); WaitForCmd(); transfer_buffer_.FreePendingToken(id_buffer, helper_->InsertToken()); n -= num; ids += num; } } void GLES2Implementation::RegisterSharedIdsCHROMIUM( GLuint namespace_id, GLsizei n, const GLuint* ids) { GPU_CLIENT_LOG("[" << this << "] glRegisterSharedIdsCHROMIUM(" << namespace_id << ", " << n << ", " << static_cast(ids) << ")"); GPU_CLIENT_LOG_CODE_BLOCK({ for (GLsizei i = 0; i < n; ++i) { GPU_CLIENT_LOG(" " << i << ": " << ids[i]); } }); TRACE_EVENT0("gpu", "GLES2::RegisterSharedIdsCHROMIUM"); GLsizei max_size = transfer_buffer_.GetLargestFreeOrPendingSize(); GLsizei max_num_per = max_size / sizeof(ids[0]); while (n) { GLsizei num = std::min(n, max_num_per); GLint* id_buffer = transfer_buffer_.AllocTyped(n); memcpy(id_buffer, ids, sizeof(*ids) * n); helper_->RegisterSharedIdsCHROMIUM( namespace_id, n, transfer_buffer_id_, transfer_buffer_.GetOffset(id_buffer)); WaitForCmd(); transfer_buffer_.FreePendingToken(id_buffer, helper_->InsertToken()); n -= num; ids += num; } } void GLES2Implementation::BindAttribLocation( GLuint program, GLuint index, const char* name) { GPU_CLIENT_LOG("[" << this << "] glBindAttribLocation(" << program << ", " << index << ", " << name << ")"); SetBucketAsString(kResultBucketId, name); helper_->BindAttribLocationBucket(program, index, kResultBucketId); helper_->SetBucketSize(kResultBucketId, 0); } void GLES2Implementation::GetVertexAttribPointerv( GLuint index, GLenum pname, void** ptr) { GPU_CLIENT_LOG("[" << this << "] glGetVertexAttribPointer(" << index << ", " << GLES2Util::GetStringVertexPointer(pname) << ", " << static_cast(ptr) << ")"); #if defined(GLES2_SUPPORT_CLIENT_SIDE_ARRAYS) // If it's a client side buffer the client has the data. if (client_side_buffer_helper_->GetAttribPointer(index, pname, ptr)) { return; } #endif // defined(GLES2_SUPPORT_CLIENT_SIDE_ARRAYS) TRACE_EVENT0("gpu", "GLES2::GetVertexAttribPointerv"); typedef gles2::GetVertexAttribPointerv::Result Result; Result* result = GetResultAs(); result->SetNumResults(0); helper_->GetVertexAttribPointerv( index, pname, result_shm_id(), result_shm_offset()); WaitForCmd(); result->CopyResult(ptr); GPU_CLIENT_LOG_CODE_BLOCK({ for (int32 i = 0; i < result->GetNumResults(); ++i) { GPU_CLIENT_LOG(" " << i << ": " << result->GetData()[i]); } }); } bool GLES2Implementation::DeleteProgramHelper(GLuint program) { if (!program_and_shader_id_handler_->FreeIds(1, &program)) { SetGLError( GL_INVALID_VALUE, "glDeleteProgram: id not created by this context."); return false; } program_info_manager_->DeleteInfo(program); helper_->DeleteProgram(program); Flush(); return true; } bool GLES2Implementation::DeleteShaderHelper(GLuint shader) { if (!program_and_shader_id_handler_->FreeIds(1, &shader)) { SetGLError( GL_INVALID_VALUE, "glDeleteShader: id not created by this context."); return false; } program_info_manager_->DeleteInfo(shader); helper_->DeleteShader(shader); Flush(); return true; } GLint GLES2Implementation::GetAttribLocationHelper( GLuint program, const char* name) { typedef GetAttribLocationBucket::Result Result; Result* result = GetResultAs(); *result = -1; SetBucketAsCString(kResultBucketId, name); helper_->GetAttribLocationBucket( program, kResultBucketId, result_shm_id(), result_shm_offset()); WaitForCmd(); helper_->SetBucketSize(kResultBucketId, 0); return *result; } GLint GLES2Implementation::GetAttribLocation( GLuint program, const char* name) { GPU_CLIENT_LOG("[" << this << "] glGetAttribLocation(" << program << ", " << name << ")"); TRACE_EVENT0("gpu", "GLES2::GetAttribLocation"); GLint loc = program_info_manager_->GetAttribLocation(this, program, name); GPU_CLIENT_LOG("returned " << loc); return loc; } GLint GLES2Implementation::GetUniformLocationHelper( GLuint program, const char* name) { typedef GetUniformLocationBucket::Result Result; Result* result = GetResultAs(); *result = -1; SetBucketAsCString(kResultBucketId, name); helper_->GetUniformLocationBucket(program, kResultBucketId, result_shm_id(), result_shm_offset()); WaitForCmd(); helper_->SetBucketSize(kResultBucketId, 0); return *result; } GLint GLES2Implementation::GetUniformLocation( GLuint program, const char* name) { GPU_CLIENT_LOG("[" << this << "] glGetUniformLocation(" << program << ", " << name << ")"); TRACE_EVENT0("gpu", "GLES2::GetUniformLocation"); GLint loc = program_info_manager_->GetUniformLocation(this, program, name); GPU_CLIENT_LOG("returned " << loc); return loc; } bool GLES2Implementation::GetProgramivHelper( GLuint program, GLenum pname, GLint* params) { return program_info_manager_->GetProgramiv(this, program, pname, params); } void GLES2Implementation::LinkProgram(GLuint program) { GPU_CLIENT_LOG("[" << this << "] glLinkProgram(" << program << ")"); helper_->LinkProgram(program); program_info_manager_->CreateInfo(program); } void GLES2Implementation::ShaderBinary( GLsizei n, const GLuint* shaders, GLenum binaryformat, const void* binary, GLsizei length) { GPU_CLIENT_LOG("[" << this << "] glShaderBinary(" << n << ", " << static_cast(shaders) << ", " << GLES2Util::GetStringEnum(binaryformat) << ", " << static_cast(binary) << ", " << length << ")"); if (n < 0) { SetGLError(GL_INVALID_VALUE, "glShaderBinary n < 0."); return; } if (length < 0) { SetGLError(GL_INVALID_VALUE, "glShaderBinary length < 0."); return; } GLsizei shader_id_size = n * sizeof(*shaders); int8* buffer = transfer_buffer_.AllocTyped(shader_id_size + length); void* shader_ids = buffer; void* shader_data = buffer + shader_id_size; memcpy(shader_ids, shaders, shader_id_size); memcpy(shader_data, binary, length); helper_->ShaderBinary( n, transfer_buffer_id_, transfer_buffer_.GetOffset(shader_ids), binaryformat, transfer_buffer_id_, transfer_buffer_.GetOffset(shader_data), length); transfer_buffer_.FreePendingToken(buffer, helper_->InsertToken()); } void GLES2Implementation::PixelStorei(GLenum pname, GLint param) { GPU_CLIENT_LOG("[" << this << "] glPixelStorei(" << GLES2Util::GetStringPixelStore(pname) << ", " << param << ")"); switch (pname) { case GL_PACK_ALIGNMENT: pack_alignment_ = param; break; case GL_UNPACK_ALIGNMENT: unpack_alignment_ = param; break; case GL_UNPACK_FLIP_Y_CHROMIUM: unpack_flip_y_ = (param != 0); return; default: break; } helper_->PixelStorei(pname, param); } void GLES2Implementation::VertexAttribPointer( GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void* ptr) { GPU_CLIENT_LOG("[" << this << "] glVertexAttribPointer(" << index << ", " << size << ", " << GLES2Util::GetStringVertexAttribType(type) << ", " << GLES2Util::GetStringBool(normalized) << ", " << stride << ", " << static_cast(ptr) << ")"); #if defined(GLES2_SUPPORT_CLIENT_SIDE_ARRAYS) // Record the info on the client side. client_side_buffer_helper_->SetAttribPointer( bound_array_buffer_id_, index, size, type, normalized, stride, ptr); if (bound_array_buffer_id_ != 0) { // Only report NON client side buffers to the service. helper_->VertexAttribPointer(index, size, type, normalized, stride, ToGLuint(ptr)); } #else // !defined(GLES2_SUPPORT_CLIENT_SIDE_ARRAYS) helper_->VertexAttribPointer(index, size, type, normalized, stride, ToGLuint(ptr)); #endif // !defined(GLES2_SUPPORT_CLIENT_SIDE_ARRAYS) } void GLES2Implementation::ShaderSource( GLuint shader, GLsizei count, const char** source, const GLint* length) { GPU_CLIENT_LOG("[" << this << "] glShaderSource(" << shader << ", " << count << ", " << static_cast(source) << ", " << static_cast(length) << ")"); GPU_CLIENT_LOG_CODE_BLOCK({ for (GLsizei ii = 0; ii < count; ++ii) { if (source[ii]) { if (length && length[ii] >= 0) { std::string str(source[ii], length[ii]); GPU_CLIENT_LOG(" " << ii << ": ---\n" << str << "\n---"); } else { GPU_CLIENT_LOG(" " << ii << ": ---\n" << source[ii] << "\n---"); } } else { GPU_CLIENT_LOG(" " << ii << ": NULL"); } } }); if (count < 0) { SetGLError(GL_INVALID_VALUE, "glShaderSource count < 0"); return; } if (shader == 0) { SetGLError(GL_INVALID_VALUE, "glShaderSource shader == 0"); return; } // Compute the total size. uint32 total_size = 1; for (GLsizei ii = 0; ii < count; ++ii) { if (source[ii]) { total_size += (length && length[ii] >= 0) ? static_cast(length[ii]) : strlen(source[ii]); } } // Concatenate all the strings in to a bucket on the service. helper_->SetBucketSize(kResultBucketId, total_size); uint32 max_size = transfer_buffer_.GetLargestFreeOrPendingSize(); uint32 offset = 0; for (GLsizei ii = 0; ii <= count; ++ii) { const char* src = ii < count ? source[ii] : ""; if (src) { uint32 size = ii < count ? (length ? static_cast(length[ii]) : strlen(src)) : 1; while (size) { uint32 part_size = std::min(size, max_size); void* buffer = transfer_buffer_.Alloc(part_size); memcpy(buffer, src, part_size); helper_->SetBucketData(kResultBucketId, offset, part_size, transfer_buffer_id_, transfer_buffer_.GetOffset(buffer)); transfer_buffer_.FreePendingToken(buffer, helper_->InsertToken()); offset += part_size; src += part_size; size -= part_size; } } } GPU_DCHECK_EQ(total_size, offset); helper_->ShaderSourceBucket(shader, kResultBucketId); helper_->SetBucketSize(kResultBucketId, 0); } void GLES2Implementation::BufferData( GLenum target, GLsizeiptr size, const void* data, GLenum usage) { GPU_CLIENT_LOG("[" << this << "] glBufferData(" << GLES2Util::GetStringBufferTarget(target) << ", " << size << ", " << static_cast(data) << ", " << GLES2Util::GetStringBufferUsage(usage) << ")"); GLsizeiptr max_size = transfer_buffer_.GetLargestFreeOrPendingSize(); if (size > max_size || !data) { helper_->BufferData(target, size, 0, 0, usage); if (data != NULL) { BufferSubData(target, 0, size, data); } return; } void* buffer = transfer_buffer_.Alloc(size); memcpy(buffer, data, size); helper_->BufferData( target, size, transfer_buffer_id_, transfer_buffer_.GetOffset(buffer), usage); transfer_buffer_.FreePendingToken(buffer, helper_->InsertToken()); } void GLES2Implementation::BufferSubData( GLenum target, GLintptr offset, GLsizeiptr size, const void* data) { GPU_CLIENT_LOG("[" << this << "] glBufferSubData(" << GLES2Util::GetStringBufferTarget(target) << ", " << offset << ", " << size << ", " << static_cast(data) << ")"); if (size == 0) { return; } if (size < 0) { SetGLError(GL_INVALID_VALUE, "glBufferSubData: size < 0"); return; } const int8* source = static_cast(data); GLsizeiptr max_size = transfer_buffer_.GetLargestFreeOrPendingSize(); while (size) { GLsizeiptr part_size = std::min(size, max_size); void* buffer = transfer_buffer_.Alloc(part_size); memcpy(buffer, source, part_size); helper_->BufferSubData(target, offset, part_size, transfer_buffer_id_, transfer_buffer_.GetOffset(buffer)); transfer_buffer_.FreePendingToken(buffer, helper_->InsertToken()); offset += part_size; source += part_size; size -= part_size; } } void GLES2Implementation::CompressedTexImage2D( GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLsizei image_size, const void* data) { GPU_CLIENT_LOG("[" << this << "] glCompressedTexImage2D(" << GLES2Util::GetStringTextureTarget(target) << ", " << level << ", " << GLES2Util::GetStringCompressedTextureFormat(internalformat) << ", " << width << ", " << height << ", " << border << ", " << image_size << ", " << static_cast(data) << ")"); if (width < 0 || height < 0 || level < 0) { SetGLError(GL_INVALID_VALUE, "glCompressedTexImage2D dimension < 0"); return; } if (height == 0 || width == 0) { return; } SetBucketContents(kResultBucketId, data, image_size); helper_->CompressedTexImage2DBucket( target, level, internalformat, width, height, border, kResultBucketId); // Free the bucket. This is not required but it does free up the memory. // and we don't have to wait for the result so from the client's perspective // it's cheap. helper_->SetBucketSize(kResultBucketId, 0); } void GLES2Implementation::CompressedTexSubImage2D( GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei image_size, const void* data) { GPU_CLIENT_LOG("[" << this << "] glCompressedTexSubImage2D(" << GLES2Util::GetStringTextureTarget(target) << ", " << level << ", " << xoffset << ", " << yoffset << ", " << width << ", " << height << ", " << GLES2Util::GetStringCompressedTextureFormat(format) << ", " << image_size << ", " << static_cast(data) << ")"); if (width < 0 || height < 0 || level < 0) { SetGLError(GL_INVALID_VALUE, "glCompressedTexSubImage2D dimension < 0"); return; } SetBucketContents(kResultBucketId, data, image_size); helper_->CompressedTexSubImage2DBucket( target, level, xoffset, yoffset, width, height, format, kResultBucketId); // Free the bucket. This is not required but it does free up the memory. // and we don't have to wait for the result so from the client's perspective // it's cheap. helper_->SetBucketSize(kResultBucketId, 0); } bool GLES2Implementation::CopyRectToBufferFlipped( const void* pixels, GLsizei width, GLsizei height, GLenum format, GLenum type, void* buffer) { if (width == 0 || height == 0) { return true; } uint32 temp_size; if (!GLES2Util::ComputeImageDataSize( width, 1, format, type, unpack_alignment_, &temp_size)) { SetGLError(GL_INVALID_VALUE, "glTexSubImage2D: size to large"); return false; } GLsizeiptr unpadded_row_size = temp_size; if (!GLES2Util::ComputeImageDataSize( width, 2, format, type, unpack_alignment_, &temp_size)) { SetGLError(GL_INVALID_VALUE, "glTexSubImage2D: size to large"); return false; } GLsizeiptr padded_row_size = temp_size - unpadded_row_size; if (padded_row_size < 0 || unpadded_row_size < 0) { SetGLError(GL_INVALID_VALUE, "glTexSubImage2D: size to large"); return false; } const int8* source = static_cast(pixels); int8* dest = static_cast(buffer) + padded_row_size * (height - 1); for (; height; --height) { memcpy(dest, source, unpadded_row_size); dest -= padded_row_size; source += padded_row_size; } return true; } void GLES2Implementation::TexImage2D( GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const void* pixels) { GPU_CLIENT_LOG("[" << this << "] glTexImage2D(" << GLES2Util::GetStringTextureTarget(target) << ", " << level << ", " << GLES2Util::GetStringTextureInternalFormat(internalformat) << ", " << width << ", " << height << ", " << border << ", " << GLES2Util::GetStringTextureFormat(format) << ", " << GLES2Util::GetStringPixelType(type) << ", " << static_cast(pixels) << ")"); if (level < 0 || height < 0 || width < 0) { SetGLError(GL_INVALID_VALUE, "glTexImage2D dimension < 0"); return; } uint32 size; if (!GLES2Util::ComputeImageDataSize( width, height, format, type, unpack_alignment_, &size)) { SetGLError(GL_INVALID_VALUE, "glTexImage2D: image size too large"); return; } // Check if we can send it all at once. unsigned int max_size = transfer_buffer_.GetLargestFreeOrPendingSize(); if (size > max_size || !pixels) { // No, so send it using TexSubImage2D. helper_->TexImage2D( target, level, internalformat, width, height, border, format, type, 0, 0); if (pixels) { TexSubImage2DImpl( target, level, 0, 0, width, height, format, type, pixels, GL_TRUE); } return; } void* buffer = transfer_buffer_.Alloc(size); bool copy_success = true; if (unpack_flip_y_) { copy_success = CopyRectToBufferFlipped( pixels, width, height, format, type, buffer); } else { memcpy(buffer, pixels, size); } if (copy_success) { helper_->TexImage2D( target, level, internalformat, width, height, border, format, type, transfer_buffer_id_, transfer_buffer_.GetOffset(buffer)); } transfer_buffer_.FreePendingToken(buffer, helper_->InsertToken()); } void GLES2Implementation::TexSubImage2D( GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const void* pixels) { GPU_CLIENT_LOG("[" << this << "] glTexSubImage2D(" << GLES2Util::GetStringTextureTarget(target) << ", " << level << ", " << xoffset << ", " << yoffset << ", " << width << ", " << height << ", " << GLES2Util::GetStringTextureFormat(format) << ", " << GLES2Util::GetStringPixelType(type) << ", " << static_cast(pixels) << ")"); TexSubImage2DImpl( target, level, xoffset, yoffset, width, height, format, type, pixels, GL_FALSE); } void GLES2Implementation::TexSubImage2DImpl( GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const void* pixels, GLboolean internal) { if (level < 0 || height < 0 || width < 0) { SetGLError(GL_INVALID_VALUE, "glTexSubImage2D dimension < 0"); return; } if (height == 0 || width == 0) { return; } const int8* source = static_cast(pixels); GLsizeiptr max_size = transfer_buffer_.GetLargestFreeOrPendingSize(); uint32 temp_size; if (!GLES2Util::ComputeImageDataSize( width, 1, format, type, unpack_alignment_, &temp_size)) { SetGLError(GL_INVALID_VALUE, "glTexSubImage2D: size to large"); return; } GLsizeiptr unpadded_row_size = temp_size; if (!GLES2Util::ComputeImageDataSize( width, 2, format, type, unpack_alignment_, &temp_size)) { SetGLError(GL_INVALID_VALUE, "glTexSubImage2D: size to large"); return; } GLsizeiptr padded_row_size = temp_size - unpadded_row_size; if (padded_row_size < 0 || unpadded_row_size < 0) { SetGLError(GL_INVALID_VALUE, "glTexSubImage2D: size to large"); return; } GLsizei original_height = height; if (padded_row_size <= max_size) { // Transfer by rows. GLint max_rows = max_size / std::max(padded_row_size, static_cast(1)); while (height) { GLint num_rows = std::min(height, max_rows); GLsizeiptr part_size = (num_rows - 1) * padded_row_size + unpadded_row_size; void* buffer = transfer_buffer_.Alloc(part_size); GLint y; if (unpack_flip_y_) { CopyRectToBufferFlipped( source, width, num_rows, format, type, buffer); // GPU_DCHECK(copy_success); // can't check this because bot fails! y = original_height - yoffset - num_rows; } else { memcpy(buffer, source, part_size); y = yoffset; } helper_->TexSubImage2D( target, level, xoffset, y, width, num_rows, format, type, transfer_buffer_id_, transfer_buffer_.GetOffset(buffer), internal); transfer_buffer_.FreePendingToken(buffer, helper_->InsertToken()); yoffset += num_rows; source += num_rows * padded_row_size; height -= num_rows; } } else { // Transfer by sub rows. Because GL has no maximum texture dimensions. uint32 temp; GLES2Util::ComputeImageDataSize( 1, 1, format, type, unpack_alignment_, &temp); GLsizeiptr element_size = temp; max_size -= max_size % element_size; GLint max_sub_row_pixels = max_size / element_size; for (; height; --height) { GLint temp_width = width; GLint temp_xoffset = xoffset; const int8* row_source = source; while (temp_width) { GLint num_pixels = std::min(width, max_sub_row_pixels); GLsizeiptr part_size = num_pixels * element_size; void* buffer = transfer_buffer_.Alloc(part_size); memcpy(buffer, row_source, part_size); GLint y = unpack_flip_y_ ? (original_height - yoffset - 1) : yoffset; helper_->TexSubImage2D( target, level, temp_xoffset, y, num_pixels, 1, format, type, transfer_buffer_id_, transfer_buffer_.GetOffset(buffer), internal); transfer_buffer_.FreePendingToken(buffer, helper_->InsertToken()); row_source += part_size; temp_xoffset += num_pixels; temp_width -= num_pixels; } ++yoffset; source += padded_row_size; } } } bool GLES2Implementation::GetActiveAttribHelper( GLuint program, GLuint index, GLsizei bufsize, GLsizei* length, GLint* size, GLenum* type, char* name) { // Clear the bucket so if the command fails nothing will be in it. helper_->SetBucketSize(kResultBucketId, 0); typedef gles2::GetActiveAttrib::Result Result; Result* result = static_cast(result_buffer_); // Set as failed so if the command fails we'll recover. result->success = false; helper_->GetActiveAttrib(program, index, kResultBucketId, result_shm_id(), result_shm_offset()); WaitForCmd(); if (result->success) { if (size) { *size = result->size; } if (type) { *type = result->type; } if (length || name) { std::vector str; GetBucketContents(kResultBucketId, &str); GLsizei max_size = std::min(static_cast(bufsize) - 1, std::max(static_cast(0), str.size() - 1)); if (length) { *length = max_size; } if (name && bufsize > 0) { memcpy(name, &str[0], max_size); name[max_size] = '\0'; } } } return result->success != 0; } void GLES2Implementation::GetActiveAttrib( GLuint program, GLuint index, GLsizei bufsize, GLsizei* length, GLint* size, GLenum* type, char* name) { GPU_CLIENT_LOG("[" << this << "] glGetActiveAttrib(" << program << ", " << index << ", " << bufsize << ", " << static_cast(length) << ", " << static_cast(size) << ", " << static_cast(type) << ", " << static_cast(name) << ", "); if (bufsize < 0) { SetGLError(GL_INVALID_VALUE, "glGetActiveAttrib: bufsize < 0"); return; } TRACE_EVENT0("gpu", "GLES2::GetActiveAttrib"); bool success = program_info_manager_->GetActiveAttrib( this, program, index, bufsize, length, size, type, name); if (success) { if (size) { GPU_CLIENT_LOG(" size: " << *size); } if (type) { GPU_CLIENT_LOG(" type: " << GLES2Util::GetStringEnum(*type)); } if (name) { GPU_CLIENT_LOG(" name: " << name); } } } bool GLES2Implementation::GetActiveUniformHelper( GLuint program, GLuint index, GLsizei bufsize, GLsizei* length, GLint* size, GLenum* type, char* name) { // Clear the bucket so if the command fails nothing will be in it. helper_->SetBucketSize(kResultBucketId, 0); typedef gles2::GetActiveUniform::Result Result; Result* result = static_cast(result_buffer_); // Set as failed so if the command fails we'll recover. result->success = false; helper_->GetActiveUniform(program, index, kResultBucketId, result_shm_id(), result_shm_offset()); WaitForCmd(); if (result->success) { if (size) { *size = result->size; } if (type) { *type = result->type; } if (length || name) { std::vector str; GetBucketContents(kResultBucketId, &str); GLsizei max_size = std::min(static_cast(bufsize) - 1, std::max(static_cast(0), str.size() - 1)); if (length) { *length = max_size; } if (name && bufsize > 0) { memcpy(name, &str[0], max_size); name[max_size] = '\0'; } } } return result->success != 0; } void GLES2Implementation::GetActiveUniform( GLuint program, GLuint index, GLsizei bufsize, GLsizei* length, GLint* size, GLenum* type, char* name) { GPU_CLIENT_LOG("[" << this << "] glGetActiveUniform(" << program << ", " << index << ", " << bufsize << ", " << static_cast(length) << ", " << static_cast(size) << ", " << static_cast(type) << ", " << static_cast(name) << ", "); if (bufsize < 0) { SetGLError(GL_INVALID_VALUE, "glGetActiveUniform: bufsize < 0"); return; } TRACE_EVENT0("gpu", "GLES2::GetActiveUniform"); bool success = program_info_manager_->GetActiveUniform( this, program, index, bufsize, length, size, type, name); if (success) { if (size) { GPU_CLIENT_LOG(" size: " << *size); } if (type) { GPU_CLIENT_LOG(" type: " << GLES2Util::GetStringEnum(*type)); } if (name) { GPU_CLIENT_LOG(" name: " << name); } } } void GLES2Implementation::GetAttachedShaders( GLuint program, GLsizei maxcount, GLsizei* count, GLuint* shaders) { GPU_CLIENT_LOG("[" << this << "] glGetAttachedShaders(" << program << ", " << maxcount << ", " << static_cast(count) << ", " << static_cast(shaders) << ", "); if (maxcount < 0) { SetGLError(GL_INVALID_VALUE, "glGetAttachedShaders: maxcount < 0"); return; } TRACE_EVENT0("gpu", "GLES2::GetAttachedShaders"); typedef gles2::GetAttachedShaders::Result Result; uint32 size = Result::ComputeSize(maxcount); Result* result = transfer_buffer_.AllocTyped(size); result->SetNumResults(0); helper_->GetAttachedShaders( program, transfer_buffer_id_, transfer_buffer_.GetOffset(result), size); int32 token = helper_->InsertToken(); WaitForCmd(); if (count) { *count = result->GetNumResults(); } result->CopyResult(shaders); GPU_CLIENT_LOG_CODE_BLOCK({ for (int32 i = 0; i < result->GetNumResults(); ++i) { GPU_CLIENT_LOG(" " << i << ": " << result->GetData()[i]); } }); transfer_buffer_.FreePendingToken(result, token); } void GLES2Implementation::GetShaderPrecisionFormat( GLenum shadertype, GLenum precisiontype, GLint* range, GLint* precision) { GPU_CLIENT_LOG("[" << this << "] glGetShaderPrecisionFormat(" << GLES2Util::GetStringShaderType(shadertype) << ", " << GLES2Util::GetStringShaderPrecision(precisiontype) << ", " << static_cast(range) << ", " << static_cast(precision) << ", "); TRACE_EVENT0("gpu", "GLES2::GetShaderPrecisionFormat"); typedef gles2::GetShaderPrecisionFormat::Result Result; Result* result = static_cast(result_buffer_); result->success = false; helper_->GetShaderPrecisionFormat( shadertype, precisiontype, result_shm_id(), result_shm_offset()); WaitForCmd(); if (result->success) { if (range) { range[0] = result->min_range; range[1] = result->max_range; GPU_CLIENT_LOG(" min_range: " << range[0]); GPU_CLIENT_LOG(" min_range: " << range[1]); } if (precision) { precision[0] = result->precision; GPU_CLIENT_LOG(" min_range: " << precision[0]); } } } const GLubyte* GLES2Implementation::GetString(GLenum name) { GPU_CLIENT_LOG("[" << this << "] glGetString(" << GLES2Util::GetStringStringType(name) << ")"); const char* result = NULL; // Clears the bucket so if the command fails nothing will be in it. helper_->SetBucketSize(kResultBucketId, 0); helper_->GetString(name, kResultBucketId); std::string str; if (GetBucketAsString(kResultBucketId, &str)) { // Adds extensions implemented on client side only. switch (name) { case GL_EXTENSIONS: str += std::string(str.empty() ? "" : " ") + "GL_CHROMIUM_map_sub " "GL_CHROMIUM_flipy"; break; default: break; } // Because of WebGL the extensions can change. We have to cache each unique // result since we don't know when the client will stop referring to a // previous one it queries. GLStringMap::iterator it = gl_strings_.find(name); if (it == gl_strings_.end()) { std::set strings; std::pair insert_result = gl_strings_.insert(std::make_pair(name, strings)); GPU_DCHECK(insert_result.second); it = insert_result.first; } std::set& string_set = it->second; std::set::const_iterator sit = string_set.find(str); if (sit != string_set.end()) { result = sit->c_str(); } else { std::pair::const_iterator, bool> insert_result = string_set.insert(str); GPU_DCHECK(insert_result.second); result = insert_result.first->c_str(); } } GPU_CLIENT_LOG(" returned " << static_cast(result)); return reinterpret_cast(result); } void GLES2Implementation::GetUniformfv( GLuint program, GLint location, GLfloat* params) { GPU_CLIENT_LOG("[" << this << "] glGetUniformfv(" << program << ", " << location << ", " << static_cast(params) << ")"); TRACE_EVENT0("gpu", "GLES2::GetUniformfv"); typedef gles2::GetUniformfv::Result Result; Result* result = static_cast(result_buffer_); result->SetNumResults(0); helper_->GetUniformfv( program, location, result_shm_id(), result_shm_offset()); WaitForCmd(); result->CopyResult(params); GPU_CLIENT_LOG_CODE_BLOCK({ for (int32 i = 0; i < result->GetNumResults(); ++i) { GPU_CLIENT_LOG(" " << i << ": " << result->GetData()[i]); } }); } void GLES2Implementation::GetUniformiv( GLuint program, GLint location, GLint* params) { GPU_CLIENT_LOG("[" << this << "] glGetUniformiv(" << program << ", " << location << ", " << static_cast(params) << ")"); TRACE_EVENT0("gpu", "GLES2::GetUniformiv"); typedef gles2::GetUniformiv::Result Result; Result* result = static_cast(result_buffer_); result->SetNumResults(0); helper_->GetUniformiv( program, location, result_shm_id(), result_shm_offset()); WaitForCmd(); static_cast(result_buffer_)->CopyResult(params); GPU_CLIENT_LOG_CODE_BLOCK({ for (int32 i = 0; i < result->GetNumResults(); ++i) { GPU_CLIENT_LOG(" " << i << ": " << result->GetData()[i]); } }); } void GLES2Implementation::ReadPixels( GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, void* pixels) { GPU_CLIENT_LOG("[" << this << "] glReadPixels(" << xoffset << ", " << yoffset << ", " << width << ", " << height << ", " << GLES2Util::GetStringReadPixelFormat(format) << ", " << GLES2Util::GetStringPixelType(type) << ", " << static_cast(pixels) << ")"); if (width < 0 || height < 0) { SetGLError(GL_INVALID_VALUE, "glReadPixels: dimensions < 0"); return; } if (width == 0 || height == 0) { return; } // glReadPixel pads the size of each row of pixels by an amount specified by // glPixelStorei. So, we have to take that into account both in the fact that // the pixels returned from the ReadPixel command will include that padding // and that when we copy the results to the user's buffer we need to not // write those padding bytes but leave them as they are. TRACE_EVENT0("gpu", "GLES2::ReadPixels"); typedef gles2::ReadPixels::Result Result; Result* result = static_cast(result_buffer_); int8* dest = reinterpret_cast(pixels); GLsizeiptr max_size = transfer_buffer_.GetLargestFreeOrPendingSize(); uint32 temp_size; if (!GLES2Util::ComputeImageDataSize( width, 1, format, type, pack_alignment_, &temp_size)) { SetGLError(GL_INVALID_VALUE, "glReadPixels: size too large."); return; } GLsizeiptr unpadded_row_size = temp_size; if (!GLES2Util::ComputeImageDataSize( width, 2, format, type, pack_alignment_, &temp_size)) { SetGLError(GL_INVALID_VALUE, "glReadPixels: size too large."); return; } GLsizeiptr padded_row_size = temp_size - unpadded_row_size; if (padded_row_size < 0 || unpadded_row_size < 0) { SetGLError(GL_INVALID_VALUE, "glReadPixels: size too large."); return; } // Check if we have enough space to transfer at least an entire row. if (padded_row_size <= max_size) { // Transfer by rows. // The max rows we can transfer. GLint max_rows = max_size / std::max(padded_row_size, static_cast(1)); while (height) { // Compute how many rows to transfer. GLint num_rows = std::min(height, max_rows); // Compute how much space those rows will take. The last row will not // include padding. GLsizeiptr part_size = unpadded_row_size + padded_row_size * (num_rows - 1); void* buffer = transfer_buffer_.Alloc(part_size); *result = 0; // mark as failed. helper_->ReadPixels( xoffset, yoffset, width, num_rows, format, type, transfer_buffer_id_, transfer_buffer_.GetOffset(buffer), result_shm_id(), result_shm_offset()); WaitForCmd(); if (*result != 0) { // We have to copy 1 row at a time to avoid writing pad bytes. const int8* src = static_cast(buffer); for (GLint yy = 0; yy < num_rows; ++yy) { memcpy(dest, src, unpadded_row_size); dest += padded_row_size; src += padded_row_size; } } transfer_buffer_.FreePendingToken(buffer, helper_->InsertToken()); // If it was not marked as successful exit. if (*result == 0) { return; } yoffset += num_rows; height -= num_rows; } } else { // Transfer by sub rows. Because GL has no maximum texture dimensions. GLES2Util::ComputeImageDataSize( 1, 1, format, type, pack_alignment_, &temp_size); GLsizeiptr element_size = temp_size; max_size -= max_size % element_size; GLint max_sub_row_pixels = max_size / element_size; for (; height; --height) { GLint temp_width = width; GLint temp_xoffset = xoffset; int8* row_dest = dest; while (temp_width) { GLint num_pixels = std::min(width, max_sub_row_pixels); GLsizeiptr part_size = num_pixels * element_size; void* buffer = transfer_buffer_.Alloc(part_size); *result = 0; // mark as failed. helper_->ReadPixels( temp_xoffset, yoffset, temp_width, 1, format, type, transfer_buffer_id_, transfer_buffer_.GetOffset(buffer), result_shm_id(), result_shm_offset()); WaitForCmd(); if (*result != 0) { memcpy(row_dest, buffer, part_size); } transfer_buffer_.FreePendingToken(buffer, helper_->InsertToken()); // If it was not marked as successful exit. if (*result == 0) { return; } row_dest += part_size; temp_xoffset += num_pixels; temp_width -= num_pixels; } ++yoffset; dest += padded_row_size; } } } void GLES2Implementation::ActiveTexture(GLenum texture) { GPU_CLIENT_LOG("[" << this << "] glActiveTexture(" << GLES2Util::GetStringEnum(texture) << ")"); GLuint texture_index = texture - GL_TEXTURE0; if (texture_index >= static_cast(gl_state_.max_combined_texture_image_units)) { SetGLError(GL_INVALID_ENUM, "glActiveTexture: texture_unit out of range."); return; } active_texture_unit_ = texture_index; helper_->ActiveTexture(texture); } // NOTE #1: On old versions of OpenGL, calling glBindXXX with an unused id // generates a new resource. On newer versions of OpenGL they don't. The code // related to binding below will need to change if we switch to the new OpenGL // model. Specifically it assumes a bind will succeed which is always true in // the old model but possibly not true in the new model if another context has // deleted the resource. void GLES2Implementation::BindBufferHelper( GLenum target, GLuint buffer) { // TODO(gman): See note #1 above. switch (target) { case GL_ARRAY_BUFFER: bound_array_buffer_id_ = buffer; break; case GL_ELEMENT_ARRAY_BUFFER: bound_element_array_buffer_id_ = buffer; break; default: break; } // TODO(gman): There's a bug here. If the target is invalid the ID will not be // used even though it's marked it as used here. buffer_id_handler_->MarkAsUsedForBind(buffer); } void GLES2Implementation::BindFramebufferHelper( GLenum target, GLuint framebuffer) { // TODO(gman): See note #1 above. switch (target) { case GL_FRAMEBUFFER: bound_framebuffer_ = framebuffer; break; default: break; } // TODO(gman): There's a bug here. If the target is invalid the ID will not be // used even though it's marked it as used here. framebuffer_id_handler_->MarkAsUsedForBind(framebuffer); } void GLES2Implementation::BindRenderbufferHelper( GLenum target, GLuint renderbuffer) { // TODO(gman): See note #1 above. switch (target) { case GL_RENDERBUFFER: bound_renderbuffer_ = renderbuffer; break; default: break; } // TODO(gman): There's a bug here. If the target is invalid the ID will not be // used even though it's marked it as used here. renderbuffer_id_handler_->MarkAsUsedForBind(renderbuffer); } void GLES2Implementation::BindTextureHelper(GLenum target, GLuint texture) { // TODO(gman): See note #1 above. TextureUnit& unit = texture_units_[active_texture_unit_]; switch (target) { case GL_TEXTURE_2D: unit.bound_texture_2d = texture; break; case GL_TEXTURE_CUBE_MAP: unit.bound_texture_cube_map = texture; break; default: break; } // TODO(gman): There's a bug here. If the target is invalid the ID will not be // used. even though it's marked it as used here. texture_id_handler_->MarkAsUsedForBind(texture); } #if defined(GLES2_SUPPORT_CLIENT_SIDE_ARRAYS) bool GLES2Implementation::IsBufferReservedId(GLuint id) { for (size_t ii = 0; ii < arraysize(reserved_ids_); ++ii) { if (id == reserved_ids_[ii]) { return true; } } return false; } #else bool GLES2Implementation::IsBufferReservedId(GLuint /* id */) { return false; } #endif void GLES2Implementation::DeleteBuffersHelper( GLsizei n, const GLuint* buffers) { if (!buffer_id_handler_->FreeIds(n, buffers)) { SetGLError( GL_INVALID_VALUE, "glDeleteBuffers: id not created by this context."); return; } for (GLsizei ii = 0; ii < n; ++ii) { if (buffers[ii] == bound_array_buffer_id_) { bound_array_buffer_id_ = 0; } if (buffers[ii] == bound_element_array_buffer_id_) { bound_element_array_buffer_id_ = 0; } } helper_->DeleteBuffersImmediate(n, buffers); Flush(); } void GLES2Implementation::DeleteFramebuffersHelper( GLsizei n, const GLuint* framebuffers) { if (!framebuffer_id_handler_->FreeIds(n, framebuffers)) { SetGLError( GL_INVALID_VALUE, "glDeleteFramebuffers: id not created by this context."); return; } for (GLsizei ii = 0; ii < n; ++ii) { if (framebuffers[ii] == bound_framebuffer_) { bound_framebuffer_ = 0; } } helper_->DeleteFramebuffersImmediate(n, framebuffers); Flush(); } void GLES2Implementation::DeleteRenderbuffersHelper( GLsizei n, const GLuint* renderbuffers) { if (!renderbuffer_id_handler_->FreeIds(n, renderbuffers)) { SetGLError( GL_INVALID_VALUE, "glDeleteRenderbuffers: id not created by this context."); return; } for (GLsizei ii = 0; ii < n; ++ii) { if (renderbuffers[ii] == bound_renderbuffer_) { bound_renderbuffer_ = 0; } } helper_->DeleteRenderbuffersImmediate(n, renderbuffers); Flush(); } void GLES2Implementation::DeleteTexturesHelper( GLsizei n, const GLuint* textures) { if (!texture_id_handler_->FreeIds(n, textures)) { SetGLError( GL_INVALID_VALUE, "glDeleteTextures: id not created by this context."); return; } for (GLsizei ii = 0; ii < n; ++ii) { for (GLint tt = 0; tt < gl_state_.max_combined_texture_image_units; ++tt) { TextureUnit& unit = texture_units_[active_texture_unit_]; if (textures[ii] == unit.bound_texture_2d) { unit.bound_texture_2d = 0; } if (textures[ii] == unit.bound_texture_cube_map) { unit.bound_texture_cube_map = 0; } } } helper_->DeleteTexturesImmediate(n, textures); Flush(); } void GLES2Implementation::DisableVertexAttribArray(GLuint index) { GPU_CLIENT_LOG( "[" << this << "] glDisableVertexAttribArray(" << index << ")"); #if defined(GLES2_SUPPORT_CLIENT_SIDE_ARRAYS) client_side_buffer_helper_->SetAttribEnable(index, false); #endif helper_->DisableVertexAttribArray(index); } void GLES2Implementation::EnableVertexAttribArray(GLuint index) { GPU_CLIENT_LOG("[" << this << "] glEnableVertexAttribArray(" << index << ")"); #if defined(GLES2_SUPPORT_CLIENT_SIDE_ARRAYS) client_side_buffer_helper_->SetAttribEnable(index, true); #endif helper_->EnableVertexAttribArray(index); } void GLES2Implementation::DrawArrays(GLenum mode, GLint first, GLsizei count) { GPU_CLIENT_LOG("[" << this << "] glDrawArrays(" << GLES2Util::GetStringDrawMode(mode) << ", " << first << ", " << count << ")"); if (count < 0) { SetGLError(GL_INVALID_VALUE, "glDrawArrays: count < 0"); return; } #if defined(GLES2_SUPPORT_CLIENT_SIDE_ARRAYS) bool have_client_side = client_side_buffer_helper_->HaveEnabledClientSideBuffers(); if (have_client_side) { client_side_buffer_helper_->SetupSimualtedClientSideBuffers( this, helper_, first + count); } #endif helper_->DrawArrays(mode, first, count); #if defined(GLES2_SUPPORT_CLIENT_SIDE_ARRAYS) if (have_client_side) { // Restore the user's current binding. helper_->BindBuffer(GL_ARRAY_BUFFER, bound_array_buffer_id_); } #endif } #if defined(GLES2_SUPPORT_CLIENT_SIDE_ARRAYS) bool GLES2Implementation::GetVertexAttribHelper( GLuint index, GLenum pname, uint32* param) { const ClientSideBufferHelper::VertexAttribInfo* info = client_side_buffer_helper_->GetAttribInfo(index); if (!info) { return false; } switch (pname) { case GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING: *param = info->buffer_id(); break; case GL_VERTEX_ATTRIB_ARRAY_ENABLED: *param = info->enabled(); break; case GL_VERTEX_ATTRIB_ARRAY_SIZE: *param = info->size(); break; case GL_VERTEX_ATTRIB_ARRAY_STRIDE: *param = info->stride(); break; case GL_VERTEX_ATTRIB_ARRAY_TYPE: *param = info->type(); break; case GL_VERTEX_ATTRIB_ARRAY_NORMALIZED: *param = info->normalized(); break; case GL_CURRENT_VERTEX_ATTRIB: return false; // pass through to service side. default: SetGLError(GL_INVALID_ENUM, "glGetVertexAttrib: invalid enum"); break; } return true; } #endif // GLES2_SUPPORT_CLIENT_SIDE_ARRAYS void GLES2Implementation::GetVertexAttribfv( GLuint index, GLenum pname, GLfloat* params) { GPU_CLIENT_LOG("[" << this << "] glGetVertexAttribfv(" << index << ", " << GLES2Util::GetStringVertexAttribute(pname) << ", " << static_cast(params) << ")"); #if defined(GLES2_SUPPORT_CLIENT_SIDE_ARRAYS) uint32 value = 0; if (GetVertexAttribHelper(index, pname, &value)) { *params = static_cast(value); return; } #endif TRACE_EVENT0("gpu", "GLES2::GetVertexAttribfv"); typedef GetVertexAttribfv::Result Result; Result* result = GetResultAs(); result->SetNumResults(0); helper_->GetVertexAttribfv( index, pname, result_shm_id(), result_shm_offset()); WaitForCmd(); result->CopyResult(params); GPU_CLIENT_LOG_CODE_BLOCK({ for (int32 i = 0; i < result->GetNumResults(); ++i) { GPU_CLIENT_LOG(" " << i << ": " << result->GetData()[i]); } }); } void GLES2Implementation::GetVertexAttribiv( GLuint index, GLenum pname, GLint* params) { GPU_CLIENT_LOG("[" << this << "] glGetVertexAttribiv(" << index << ", " << GLES2Util::GetStringVertexAttribute(pname) << ", " << static_cast(params) << ")"); #if defined(GLES2_SUPPORT_CLIENT_SIDE_ARRAYS) uint32 value = 0; if (GetVertexAttribHelper(index, pname, &value)) { *params = value; return; } #endif TRACE_EVENT0("gpu", "GLES2::GetVertexAttribiv"); typedef GetVertexAttribiv::Result Result; Result* result = GetResultAs(); result->SetNumResults(0); helper_->GetVertexAttribiv( index, pname, result_shm_id(), result_shm_offset()); WaitForCmd(); result->CopyResult(params); GPU_CLIENT_LOG_CODE_BLOCK({ for (int32 i = 0; i < result->GetNumResults(); ++i) { GPU_CLIENT_LOG(" " << i << ": " << result->GetData()[i]); } }); } GLboolean GLES2Implementation::EnableFeatureCHROMIUM( const char* feature) { GPU_CLIENT_LOG("[" << this << "] glEnableFeatureCHROMIUM(" << feature << ")"); TRACE_EVENT0("gpu", "GLES2::EnableFeatureCHROMIUM"); typedef EnableFeatureCHROMIUM::Result Result; Result* result = GetResultAs(); *result = 0; SetBucketAsCString(kResultBucketId, feature); helper_->EnableFeatureCHROMIUM( kResultBucketId, result_shm_id(), result_shm_offset()); WaitForCmd(); helper_->SetBucketSize(kResultBucketId, 0); GPU_CLIENT_LOG(" returned " << GLES2Util::GetStringBool(*result)); return *result; } void* GLES2Implementation::MapBufferSubDataCHROMIUM( GLuint target, GLintptr offset, GLsizeiptr size, GLenum access) { GPU_CLIENT_LOG("[" << this << "] glMapBufferSubDataCHROMIUM(" << target << ", " << offset << ", " << size << ", " << GLES2Util::GetStringEnum(access) << ")"); // NOTE: target is NOT checked because the service will check it // and we don't know what targets are valid. if (access != GL_WRITE_ONLY) { SetGLError(GL_INVALID_ENUM, "MapBufferSubDataCHROMIUM: bad access mode"); return NULL; } if (offset < 0 || size < 0) { SetGLError(GL_INVALID_VALUE, "MapBufferSubDataCHROMIUM: bad range"); return NULL; } int32 shm_id; unsigned int shm_offset; void* mem = mapped_memory_->Alloc(size, &shm_id, &shm_offset); if (!mem) { SetGLError(GL_OUT_OF_MEMORY, "MapBufferSubDataCHROMIUM: out of memory"); return NULL; } std::pair result = mapped_buffers_.insert(std::make_pair( mem, MappedBuffer( access, shm_id, mem, shm_offset, target, offset, size))); GPU_DCHECK(result.second); GPU_CLIENT_LOG(" returned " << mem); return mem; } void GLES2Implementation::UnmapBufferSubDataCHROMIUM(const void* mem) { GPU_CLIENT_LOG( "[" << this << "] glUnmapBufferSubDataCHROMIUM(" << mem << ")"); MappedBufferMap::iterator it = mapped_buffers_.find(mem); if (it == mapped_buffers_.end()) { SetGLError( GL_INVALID_VALUE, "UnmapBufferSubDataCHROMIUM: buffer not mapped"); return; } const MappedBuffer& mb = it->second; helper_->BufferSubData( mb.target, mb.offset, mb.size, mb.shm_id, mb.shm_offset); mapped_memory_->FreePendingToken(mb.shm_memory, helper_->InsertToken()); helper_->CommandBufferHelper::Flush(); mapped_buffers_.erase(it); } void* GLES2Implementation::MapTexSubImage2DCHROMIUM( GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, GLenum access) { GPU_CLIENT_LOG("[" << this << "] glMapTexSubImage2DCHROMIUM(" << target << ", " << level << ", " << xoffset << ", " << yoffset << ", " << width << ", " << height << ", " << GLES2Util::GetStringTextureFormat(format) << ", " << GLES2Util::GetStringPixelType(type) << ", " << GLES2Util::GetStringEnum(access) << ")"); if (access != GL_WRITE_ONLY) { SetGLError(GL_INVALID_ENUM, "MapTexSubImage2DCHROMIUM: bad access mode"); return NULL; } // NOTE: target is NOT checked because the service will check it // and we don't know what targets are valid. if (level < 0 || xoffset < 0 || yoffset < 0 || width < 0 || height < 0) { SetGLError(GL_INVALID_VALUE, "MapTexSubImage2DCHROMIUM: bad dimensions"); return NULL; } uint32 size; if (!GLES2Util::ComputeImageDataSize( width, height, format, type, unpack_alignment_, &size)) { SetGLError( GL_INVALID_VALUE, "MapTexSubImage2DCHROMIUM: image size too large"); return NULL; } int32 shm_id; unsigned int shm_offset; void* mem = mapped_memory_->Alloc(size, &shm_id, &shm_offset); if (!mem) { SetGLError(GL_OUT_OF_MEMORY, "MapTexSubImage2DCHROMIUM: out of memory"); return NULL; } std::pair result = mapped_textures_.insert(std::make_pair( mem, MappedTexture( access, shm_id, mem, shm_offset, target, level, xoffset, yoffset, width, height, format, type))); GPU_DCHECK(result.second); GPU_CLIENT_LOG(" returned " << mem); return mem; } void GLES2Implementation::UnmapTexSubImage2DCHROMIUM(const void* mem) { GPU_CLIENT_LOG( "[" << this << "] glUnmapTexSubImage2DCHROMIUM(" << mem << ")"); MappedTextureMap::iterator it = mapped_textures_.find(mem); if (it == mapped_textures_.end()) { SetGLError( GL_INVALID_VALUE, "UnmapTexSubImage2DCHROMIUM: texture not mapped"); return; } const MappedTexture& mt = it->second; helper_->TexSubImage2D( mt.target, mt.level, mt.xoffset, mt.yoffset, mt.width, mt.height, mt.format, mt.type, mt.shm_id, mt.shm_offset, GL_FALSE); mapped_memory_->FreePendingToken(mt.shm_memory, helper_->InsertToken()); helper_->CommandBufferHelper::Flush(); mapped_textures_.erase(it); } void GLES2Implementation::ResizeCHROMIUM(GLuint width, GLuint height) { GPU_CLIENT_LOG("[" << this << "] glResizeCHROMIUM(" << width << ", " << height << ")"); helper_->ResizeCHROMIUM(width, height); } const GLchar* GLES2Implementation::GetRequestableExtensionsCHROMIUM() { GPU_CLIENT_LOG("[" << this << "] glGetRequestableExtensionsCHROMIUM()"); TRACE_EVENT0("gpu", "GLES2Implementation::GetRequestableExtensionsCHROMIUM()"); const char* result = NULL; // Clear the bucket so if the command fails nothing will be in it. helper_->SetBucketSize(kResultBucketId, 0); helper_->GetRequestableExtensionsCHROMIUM(kResultBucketId); std::string str; if (GetBucketAsString(kResultBucketId, &str)) { // The set of requestable extensions shrinks as we enable // them. Because we don't know when the client will stop referring // to a previous one it queries (see GetString) we need to cache // the unique results. std::set::const_iterator sit = requestable_extensions_set_.find(str); if (sit != requestable_extensions_set_.end()) { result = sit->c_str(); } else { std::pair::const_iterator, bool> insert_result = requestable_extensions_set_.insert(str); GPU_DCHECK(insert_result.second); result = insert_result.first->c_str(); } } GPU_CLIENT_LOG(" returned " << result); return reinterpret_cast(result); } void GLES2Implementation::RequestExtensionCHROMIUM(const char* extension) { GPU_CLIENT_LOG("[" << this << "] glRequestExtensionCHROMIUM(" << extension << ")"); SetBucketAsCString(kResultBucketId, extension); helper_->RequestExtensionCHROMIUM(kResultBucketId); helper_->SetBucketSize(kResultBucketId, 0); } void GLES2Implementation::RateLimitOffscreenContextCHROMIUM() { GPU_CLIENT_LOG("[" << this << "] glRateLimitOffscreenCHROMIUM()"); // Wait if this would add too many rate limit tokens. if (rate_limit_tokens_.size() == kMaxSwapBuffers) { helper_->WaitForToken(rate_limit_tokens_.front()); rate_limit_tokens_.pop(); } rate_limit_tokens_.push(helper_->InsertToken()); } void GLES2Implementation::GetMultipleIntegervCHROMIUM( const GLenum* pnames, GLuint count, GLint* results, GLsizeiptr size) { GPU_CLIENT_LOG("[" << this << "] glGetMultipleIntegervCHROMIUM(" << static_cast(pnames) << ", " << count << ", " << results << ", " << size << ")"); GPU_CLIENT_LOG_CODE_BLOCK({ for (GLuint i = 0; i < count; ++i) { GPU_CLIENT_LOG( " " << i << ": " << GLES2Util::GetStringGLState(pnames[i])); } }); int num_results = 0; for (GLuint ii = 0; ii < count; ++ii) { int num = util_.GLGetNumValuesReturned(pnames[ii]); if (!num) { SetGLError(GL_INVALID_ENUM, "glGetMultipleIntegervCHROMIUM: bad pname"); return; } num_results += num; } if (static_cast(size) != num_results * sizeof(GLint)) { SetGLError(GL_INVALID_VALUE, "glGetMultipleIntegervCHROMIUM: bad size"); return; } for (int ii = 0; ii < num_results; ++ii) { if (results[ii] != 0) { SetGLError(GL_INVALID_VALUE, "glGetMultipleIntegervCHROMIUM: results not set to zero."); return; } } uint32 size_needed = count * sizeof(pnames[0]) + num_results * sizeof(results[0]); void* buffer = transfer_buffer_.Alloc(size_needed); GLenum* pnames_buffer = static_cast(buffer); void* results_buffer = pnames_buffer + count; memcpy(pnames_buffer, pnames, count * sizeof(GLenum)); memset(results_buffer, 0, num_results * sizeof(GLint)); helper_->GetMultipleIntegervCHROMIUM( transfer_buffer_id_, transfer_buffer_.GetOffset(pnames_buffer), count, transfer_buffer_id_, transfer_buffer_.GetOffset(results_buffer), size); WaitForCmd(); memcpy(results, results_buffer, size); // TODO(gman): We should be able to free without a token. transfer_buffer_.FreePendingToken(buffer, helper_->InsertToken()); GPU_CLIENT_LOG(" returned"); GPU_CLIENT_LOG_CODE_BLOCK({ for (int i = 0; i < num_results; ++i) { GPU_CLIENT_LOG(" " << i << ": " << (results[i])); } }); } void GLES2Implementation::GetProgramInfoCHROMIUMHelper( GLuint program, std::vector* result) { GPU_DCHECK(result); // Clear the bucket so if the command fails nothing will be in it. helper_->SetBucketSize(kResultBucketId, 0); helper_->GetProgramInfoCHROMIUM(program, kResultBucketId); GetBucketContents(kResultBucketId, result); } void GLES2Implementation::GetProgramInfoCHROMIUM( GLuint program, GLsizei bufsize, GLsizei* size, void* info) { if (bufsize < 0) { SetGLError(GL_INVALID_VALUE, "glProgramInfoCHROMIUM: bufsize less than 0."); return; } if (size == NULL) { SetGLError(GL_INVALID_VALUE, "glProgramInfoCHROMIUM: size is null."); return; } // Make sure they've set size to 0 else the value will be undefined on // lost context. GPU_DCHECK(*size == 0); std::vector result; GetProgramInfoCHROMIUMHelper(program, &result); if (result.empty()) { return; } *size = result.size(); if (!info) { return; } if (static_cast(bufsize) < result.size()) { SetGLError(GL_INVALID_OPERATION, "glProgramInfoCHROMIUM: bufsize is too small for result."); return; } memcpy(info, &result[0], result.size()); } GLuint GLES2Implementation::CreateStreamTextureCHROMIUM(GLuint texture) { GPU_CLIENT_LOG("[" << this << "] CreateStreamTextureCHROMIUM(" << texture << ")"); TRACE_EVENT0("gpu", "GLES2::CreateStreamTextureCHROMIUM"); typedef CreateStreamTextureCHROMIUM::Result Result; Result* result = GetResultAs(); *result = GL_ZERO; helper_->CreateStreamTextureCHROMIUM(texture, result_shm_id(), result_shm_offset()); WaitForCmd(); return *result; } void GLES2Implementation::DestroyStreamTextureCHROMIUM(GLuint texture) { GPU_CLIENT_LOG("[" << this << "] DestroyStreamTextureCHROMIUM(" << texture << ")"); TRACE_EVENT0("gpu", "GLES2::DestroyStreamTextureCHROMIUM"); helper_->DestroyStreamTextureCHROMIUM(texture); } } // namespace gles2 } // namespace gpu