// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "gpu/command_buffer/service/buffer_manager.h" #include #include "base/logging.h" #include "base/trace_event/trace_event.h" #include "gpu/command_buffer/common/gles2_cmd_utils.h" #include "gpu/command_buffer/service/context_state.h" #include "gpu/command_buffer/service/error_state.h" #include "gpu/command_buffer/service/feature_info.h" #include "gpu/command_buffer/service/memory_tracking.h" #include "ui/gl/gl_bindings.h" #include "ui/gl/gl_implementation.h" namespace gpu { namespace gles2 { BufferManager::BufferManager( MemoryTracker* memory_tracker, FeatureInfo* feature_info) : memory_tracker_( new MemoryTypeTracker(memory_tracker, MemoryTracker::kManaged)), feature_info_(feature_info), allow_buffers_on_multiple_targets_(false), allow_fixed_attribs_(false), buffer_count_(0), have_context_(true), use_client_side_arrays_for_stream_buffers_( feature_info ? feature_info->workarounds( ).use_client_side_arrays_for_stream_buffers : 0) { } BufferManager::~BufferManager() { DCHECK(buffers_.empty()); CHECK_EQ(buffer_count_, 0u); } void BufferManager::Destroy(bool have_context) { have_context_ = have_context; buffers_.clear(); DCHECK_EQ(0u, memory_tracker_->GetMemRepresented()); } void BufferManager::CreateBuffer(GLuint client_id, GLuint service_id) { scoped_refptr buffer(new Buffer(this, service_id)); std::pair result = buffers_.insert(std::make_pair(client_id, buffer)); DCHECK(result.second); } Buffer* BufferManager::GetBuffer( GLuint client_id) { BufferMap::iterator it = buffers_.find(client_id); return it != buffers_.end() ? it->second.get() : NULL; } void BufferManager::RemoveBuffer(GLuint client_id) { BufferMap::iterator it = buffers_.find(client_id); if (it != buffers_.end()) { Buffer* buffer = it->second.get(); buffer->MarkAsDeleted(); buffers_.erase(it); } } void BufferManager::StartTracking(Buffer* /* buffer */) { ++buffer_count_; } void BufferManager::StopTracking(Buffer* buffer) { memory_tracker_->TrackMemFree(buffer->size()); --buffer_count_; } Buffer::MappedRange::MappedRange( GLintptr offset, GLsizeiptr size, GLenum access, void* pointer, scoped_refptr shm) : offset(offset), size(size), access(access), pointer(pointer), shm(shm) { DCHECK(pointer); DCHECK(shm.get() && GetShmPointer()); } Buffer::MappedRange::~MappedRange() { } void* Buffer::MappedRange::GetShmPointer() const { DCHECK(shm.get()); return shm->GetDataAddress(static_cast(offset), static_cast(size)); } Buffer::Buffer(BufferManager* manager, GLuint service_id) : manager_(manager), size_(0), deleted_(false), shadowed_(false), is_client_side_array_(false), service_id_(service_id), initial_target_(0), usage_(GL_STATIC_DRAW) { manager_->StartTracking(this); } Buffer::~Buffer() { if (manager_) { if (manager_->have_context_) { GLuint id = service_id(); glDeleteBuffersARB(1, &id); } manager_->StopTracking(this); manager_ = NULL; } } void Buffer::SetInfo( GLsizeiptr size, GLenum usage, bool shadow, const GLvoid* data, bool is_client_side_array) { usage_ = usage; is_client_side_array_ = is_client_side_array; ClearCache(); if (size != size_ || shadow != shadowed_) { shadowed_ = shadow; size_ = size; if (shadowed_) { shadow_.reset(new int8[size]); } else { shadow_.reset(); } } if (shadowed_) { if (data) { memcpy(shadow_.get(), data, size); } else { memset(shadow_.get(), 0, size); } } mapped_range_.reset(nullptr); } bool Buffer::CheckRange( GLintptr offset, GLsizeiptr size) const { int32 end = 0; return offset >= 0 && size >= 0 && offset <= std::numeric_limits::max() && size <= std::numeric_limits::max() && SafeAddInt32(offset, size, &end) && end <= size_; } bool Buffer::SetRange( GLintptr offset, GLsizeiptr size, const GLvoid * data) { if (!CheckRange(offset, size)) { return false; } if (shadowed_) { memcpy(shadow_.get() + offset, data, size); ClearCache(); } return true; } const void* Buffer::GetRange( GLintptr offset, GLsizeiptr size) const { if (!shadowed_) { return NULL; } if (!CheckRange(offset, size)) { return NULL; } return shadow_.get() + offset; } void Buffer::ClearCache() { range_set_.clear(); } template GLuint GetMaxValue(const void* data, GLuint offset, GLsizei count) { GLuint max_value = 0; const T* element = reinterpret_cast( static_cast(data) + offset); const T* end = element + count; for (; element < end; ++element) { if (*element > max_value) { max_value = *element; } } return max_value; } bool Buffer::GetMaxValueForRange( GLuint offset, GLsizei count, GLenum type, GLuint* max_value) { Range range(offset, count, type); RangeToMaxValueMap::iterator it = range_set_.find(range); if (it != range_set_.end()) { *max_value = it->second; return true; } uint32 size; if (!SafeMultiplyUint32( count, GLES2Util::GetGLTypeSizeForTexturesAndBuffers(type), &size)) { return false; } if (!SafeAddUint32(offset, size, &size)) { return false; } if (size > static_cast(size_)) { return false; } if (!shadowed_) { return false; } // Scan the range for the max value and store GLuint max_v = 0; switch (type) { case GL_UNSIGNED_BYTE: max_v = GetMaxValue(shadow_.get(), offset, count); break; case GL_UNSIGNED_SHORT: // Check we are not accessing an odd byte for a 2 byte value. if ((offset & 1) != 0) { return false; } max_v = GetMaxValue(shadow_.get(), offset, count); break; case GL_UNSIGNED_INT: // Check we are not accessing a non aligned address for a 4 byte value. if ((offset & 3) != 0) { return false; } max_v = GetMaxValue(shadow_.get(), offset, count); break; default: NOTREACHED(); // should never get here by validation. break; } range_set_.insert(std::make_pair(range, max_v)); *max_value = max_v; return true; } bool BufferManager::GetClientId(GLuint service_id, GLuint* client_id) const { // This doesn't need to be fast. It's only used during slow queries. for (BufferMap::const_iterator it = buffers_.begin(); it != buffers_.end(); ++it) { if (it->second->service_id() == service_id) { *client_id = it->first; return true; } } return false; } bool BufferManager::IsUsageClientSideArray(GLenum usage) { return usage == GL_STREAM_DRAW && use_client_side_arrays_for_stream_buffers_; } bool BufferManager::UseNonZeroSizeForClientSideArrayBuffer() { return feature_info_.get() && feature_info_->workarounds() .use_non_zero_size_for_client_side_stream_buffers; } void BufferManager::SetInfo(Buffer* buffer, GLenum target, GLsizeiptr size, GLenum usage, const GLvoid* data) { DCHECK(buffer); memory_tracker_->TrackMemFree(buffer->size()); const bool is_client_side_array = IsUsageClientSideArray(usage); const bool support_fixed_attribs = gfx::GetGLImplementation() == gfx::kGLImplementationEGLGLES2; // TODO(zmo): Don't shadow buffer data on ES3. crbug.com/491002. const bool shadow = target == GL_ELEMENT_ARRAY_BUFFER || allow_buffers_on_multiple_targets_ || (allow_fixed_attribs_ && !support_fixed_attribs) || is_client_side_array; buffer->SetInfo(size, usage, shadow, data, is_client_side_array); memory_tracker_->TrackMemAlloc(buffer->size()); } void BufferManager::ValidateAndDoBufferData( ContextState* context_state, GLenum target, GLsizeiptr size, const GLvoid* data, GLenum usage) { ErrorState* error_state = context_state->GetErrorState(); if (!feature_info_->validators()->buffer_target.IsValid(target)) { ERRORSTATE_SET_GL_ERROR_INVALID_ENUM( error_state, "glBufferData", target, "target"); return; } if (!feature_info_->validators()->buffer_usage.IsValid(usage)) { ERRORSTATE_SET_GL_ERROR_INVALID_ENUM( error_state, "glBufferData", usage, "usage"); return; } if (size < 0) { ERRORSTATE_SET_GL_ERROR( error_state, GL_INVALID_VALUE, "glBufferData", "size < 0"); return; } Buffer* buffer = GetBufferInfoForTarget(context_state, target); if (!buffer) { ERRORSTATE_SET_GL_ERROR( error_state, GL_INVALID_VALUE, "glBufferData", "unknown buffer"); return; } if (!memory_tracker_->EnsureGPUMemoryAvailable(size)) { ERRORSTATE_SET_GL_ERROR( error_state, GL_OUT_OF_MEMORY, "glBufferData", "out of memory"); return; } DoBufferData(error_state, buffer, target, size, usage, data); } void BufferManager::DoBufferData( ErrorState* error_state, Buffer* buffer, GLenum target, GLsizeiptr size, GLenum usage, const GLvoid* data) { // Clear the buffer to 0 if no initial data was passed in. scoped_ptr zero; if (!data) { zero.reset(new int8[size]); memset(zero.get(), 0, size); data = zero.get(); } ERRORSTATE_COPY_REAL_GL_ERRORS_TO_WRAPPER(error_state, "glBufferData"); if (IsUsageClientSideArray(usage)) { GLsizei empty_size = UseNonZeroSizeForClientSideArrayBuffer() ? 1 : 0; glBufferData(target, empty_size, NULL, usage); } else { glBufferData(target, size, data, usage); } GLenum error = ERRORSTATE_PEEK_GL_ERROR(error_state, "glBufferData"); if (error == GL_NO_ERROR) { SetInfo(buffer, target, size, usage, data); } else { SetInfo(buffer, target, 0, usage, NULL); } } void BufferManager::ValidateAndDoBufferSubData( ContextState* context_state, GLenum target, GLintptr offset, GLsizeiptr size, const GLvoid * data) { ErrorState* error_state = context_state->GetErrorState(); Buffer* buffer = GetBufferInfoForTarget(context_state, target); if (!buffer) { ERRORSTATE_SET_GL_ERROR(error_state, GL_INVALID_VALUE, "glBufferSubData", "unknown buffer"); return; } DoBufferSubData(error_state, buffer, target, offset, size, data); } void BufferManager::DoBufferSubData( ErrorState* error_state, Buffer* buffer, GLenum target, GLintptr offset, GLsizeiptr size, const GLvoid* data) { if (!buffer->SetRange(offset, size, data)) { ERRORSTATE_SET_GL_ERROR( error_state, GL_INVALID_VALUE, "glBufferSubData", "out of range"); return; } if (!buffer->IsClientSideArray()) { glBufferSubData(target, offset, size, data); } } void BufferManager::ValidateAndDoGetBufferParameteri64v( ContextState* context_state, GLenum target, GLenum pname, GLint64* params) { Buffer* buffer = GetBufferInfoForTarget(context_state, target); if (!buffer) { ERRORSTATE_SET_GL_ERROR( context_state->GetErrorState(), GL_INVALID_OPERATION, "glGetBufferParameteri64v", "no buffer bound for target"); return; } switch (pname) { case GL_BUFFER_SIZE: *params = buffer->size(); break; case GL_BUFFER_MAP_LENGTH: { const Buffer::MappedRange* mapped_range = buffer->GetMappedRange(); *params = mapped_range ? mapped_range->size : 0; break; } case GL_BUFFER_MAP_OFFSET: { const Buffer::MappedRange* mapped_range = buffer->GetMappedRange(); *params = mapped_range ? mapped_range->offset : 0; break; } default: NOTREACHED(); } } void BufferManager::ValidateAndDoGetBufferParameteriv( ContextState* context_state, GLenum target, GLenum pname, GLint* params) { Buffer* buffer = GetBufferInfoForTarget(context_state, target); if (!buffer) { ERRORSTATE_SET_GL_ERROR( context_state->GetErrorState(), GL_INVALID_OPERATION, "glGetBufferParameteriv", "no buffer bound for target"); return; } switch (pname) { case GL_BUFFER_SIZE: *params = buffer->size(); break; case GL_BUFFER_USAGE: *params = buffer->usage(); break; case GL_BUFFER_ACCESS_FLAGS: { const Buffer::MappedRange* mapped_range = buffer->GetMappedRange(); *params = mapped_range ? mapped_range->access : 0; break; } case GL_BUFFER_MAPPED: *params = buffer->GetMappedRange() == nullptr ? false : true; break; default: NOTREACHED(); } } bool BufferManager::SetTarget(Buffer* buffer, GLenum target) { if (!allow_buffers_on_multiple_targets_) { // After being bound to ELEMENT_ARRAY_BUFFER target, a buffer cannot be // bound to any other targets except for COPY_READ/WRITE_BUFFER target; // After being bound to non ELEMENT_ARRAY_BUFFER target, a buffer cannot // be bound to ELEMENT_ARRAY_BUFFER target. // Note that we don't force the WebGL 2 rule that a buffer bound to // TRANSFORM_FEEDBACK_BUFFER target should not be bound to any other // targets, because that is not a security threat, so we only enforce it // in the WebGL2RenderingContextBase. switch (buffer->initial_target()) { case GL_ELEMENT_ARRAY_BUFFER: switch (target) { case GL_ARRAY_BUFFER: case GL_PIXEL_PACK_BUFFER: case GL_PIXEL_UNPACK_BUFFER: case GL_TRANSFORM_FEEDBACK_BUFFER: case GL_UNIFORM_BUFFER: return false; default: break; } break; case GL_ARRAY_BUFFER: case GL_COPY_READ_BUFFER: case GL_COPY_WRITE_BUFFER: case GL_PIXEL_PACK_BUFFER: case GL_PIXEL_UNPACK_BUFFER: case GL_TRANSFORM_FEEDBACK_BUFFER: case GL_UNIFORM_BUFFER: if (target == GL_ELEMENT_ARRAY_BUFFER) { return false; } break; default: break; } } if (buffer->initial_target() == 0) buffer->set_initial_target(target); return true; } // Since one BufferManager can be shared by multiple decoders, ContextState is // passed in each time and not just passed in during initialization. Buffer* BufferManager::GetBufferInfoForTarget( ContextState* state, GLenum target) const { switch (target) { case GL_ARRAY_BUFFER: return state->bound_array_buffer.get(); case GL_ELEMENT_ARRAY_BUFFER: return state->vertex_attrib_manager->element_array_buffer(); case GL_COPY_READ_BUFFER: return state->bound_copy_read_buffer.get(); case GL_COPY_WRITE_BUFFER: return state->bound_copy_write_buffer.get(); case GL_PIXEL_PACK_BUFFER: return state->bound_pixel_pack_buffer.get(); case GL_PIXEL_UNPACK_BUFFER: return state->bound_pixel_unpack_buffer.get(); case GL_TRANSFORM_FEEDBACK_BUFFER: return state->bound_transform_feedback_buffer.get(); case GL_UNIFORM_BUFFER: return state->bound_uniform_buffer.get(); default: NOTREACHED(); return nullptr; } } } // namespace gles2 } // namespace gpu