| // 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 <stdint.h> | |
| #include <limits> | |
| #include "base/format_macros.h" | |
| #include "base/logging.h" | |
| #include "base/numerics/safe_math.h" | |
| #include "base/strings/stringprintf.h" | |
| #include "base/threading/thread_task_runner_handle.h" | |
| #include "base/trace_event/memory_dump_manager.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 "gpu/command_buffer/service/transform_feedback_manager.h" | |
| #include "ui/gl/gl_bindings.h" | |
| #include "ui/gl/gl_version_info.h" | |
| #include "ui/gl/trace_util.h" | |
| namespace gpu { | |
| namespace gles2 { | |
| namespace { | |
| static const GLsizeiptr kDefaultMaxBufferSize = 1u << 30; // 1GB | |
| } | |
| BufferManager::BufferManager(MemoryTracker* memory_tracker, | |
| FeatureInfo* feature_info) | |
| : memory_type_tracker_( | |
| new MemoryTypeTracker(memory_tracker)), | |
| memory_tracker_(memory_tracker), | |
| feature_info_(feature_info), | |
| max_buffer_size_(kDefaultMaxBufferSize), | |
| allow_buffers_on_multiple_targets_(false), | |
| allow_fixed_attribs_(false), | |
| buffer_count_(0), | |
| primitive_restart_fixed_index_(0), | |
| lost_context_(false), | |
| use_client_side_arrays_for_stream_buffers_( | |
| feature_info | |
| ? feature_info->workarounds() | |
| .use_client_side_arrays_for_stream_buffers | |
| : 0) { | |
| // When created from InProcessCommandBuffer, we won't have a |memory_tracker_| | |
| // so don't register a dump provider. | |
| if (memory_tracker_) { | |
| base::trace_event::MemoryDumpManager::GetInstance()->RegisterDumpProvider( | |
| this, "gpu::BufferManager", base::ThreadTaskRunnerHandle::Get()); | |
| } | |
| } | |
| BufferManager::~BufferManager() { | |
| DCHECK(buffers_.empty()); | |
| CHECK_EQ(buffer_count_, 0u); | |
| base::trace_event::MemoryDumpManager::GetInstance()->UnregisterDumpProvider( | |
| this); | |
| } | |
| void BufferManager::MarkContextLost() { | |
| lost_context_ = true; | |
| } | |
| void BufferManager::Destroy() { | |
| buffers_.clear(); | |
| DCHECK_EQ(0u, memory_type_tracker_->GetMemRepresented()); | |
| } | |
| void BufferManager::CreateBuffer(GLuint client_id, GLuint service_id) { | |
| scoped_refptr<Buffer> buffer(new Buffer(this, service_id)); | |
| std::pair<BufferMap::iterator, bool> 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_type_tracker_->TrackMemFree(buffer->size()); | |
| --buffer_count_; | |
| } | |
| Buffer::MappedRange::MappedRange( | |
| GLintptr offset, GLsizeiptr size, GLenum access, void* pointer, | |
| scoped_refptr<gpu::Buffer> shm, unsigned int shm_offset) | |
| : offset(offset), | |
| size(size), | |
| access(access), | |
| pointer(pointer), | |
| shm(shm), | |
| shm_offset(shm_offset) { | |
| DCHECK(pointer); | |
| DCHECK(shm.get() && GetShmPointer()); | |
| } | |
| Buffer::MappedRange::~MappedRange() { | |
| } | |
| void* Buffer::MappedRange::GetShmPointer() const { | |
| DCHECK(shm.get()); | |
| return shm->GetDataAddress(shm_offset, static_cast<unsigned int>(size)); | |
| } | |
| Buffer::Buffer(BufferManager* manager, GLuint service_id) | |
| : manager_(manager), | |
| size_(0), | |
| deleted_(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_->lost_context_) { | |
| GLuint id = service_id(); | |
| glDeleteBuffersARB(1, &id); | |
| } | |
| RemoveMappedRange(); | |
| manager_->StopTracking(this); | |
| manager_ = nullptr; | |
| } | |
| } | |
| const GLvoid* Buffer::StageShadow(bool use_shadow, | |
| GLsizeiptr size, | |
| const GLvoid* data) { | |
| shadow_.clear(); | |
| if (use_shadow) { | |
| if (data) { | |
| shadow_.insert(shadow_.begin(), | |
| static_cast<const uint8_t*>(data), | |
| static_cast<const uint8_t*>(data) + size); | |
| } else { | |
| shadow_.resize(size); | |
| memset(shadow_.data(), 0, static_cast<size_t>(size)); | |
| } | |
| return shadow_.data(); | |
| } else { | |
| return data; | |
| } | |
| } | |
| void Buffer::SetInfo(GLsizeiptr size, | |
| GLenum usage, | |
| bool use_shadow, | |
| bool is_client_side_array) { | |
| usage_ = usage; | |
| is_client_side_array_ = is_client_side_array; | |
| ClearCache(); | |
| // Shadow must have been setup already. | |
| DCHECK_EQ(shadow_.size(), static_cast<size_t>(use_shadow ? size : 0u)); | |
| size_ = size; | |
| mapped_range_.reset(nullptr); | |
| } | |
| bool Buffer::CheckRange(GLintptr offset, GLsizeiptr size) const { | |
| if (offset < 0 || offset > std::numeric_limits<int32_t>::max() || | |
| size < 0 || size > std::numeric_limits<int32_t>::max()) { | |
| return false; | |
| } | |
| base::CheckedNumeric<int32_t> max = offset; | |
| max += size; | |
| return max.IsValid() && max.ValueOrDefault(0) <= size_; | |
| } | |
| void Buffer::SetRange(GLintptr offset, GLsizeiptr size, const GLvoid * data) { | |
| DCHECK(CheckRange(offset, size)); | |
| if (!shadow_.empty()) { | |
| DCHECK_LE(static_cast<size_t>(offset + size), shadow_.size()); | |
| memcpy(shadow_.data() + offset, data, size); | |
| ClearCache(); | |
| } | |
| } | |
| const void* Buffer::GetRange(GLintptr offset, GLsizeiptr size) const { | |
| if (shadow_.empty()) { | |
| return NULL; | |
| } | |
| if (!CheckRange(offset, size)) { | |
| return NULL; | |
| } | |
| DCHECK_LE(static_cast<size_t>(offset + size), shadow_.size()); | |
| return shadow_.data() + offset; | |
| } | |
| void Buffer::ClearCache() { | |
| range_set_.clear(); | |
| } | |
| template <typename T> | |
| GLuint GetMaxValue(const void* data, GLuint offset, GLsizei count, | |
| GLuint primitive_restart_index) { | |
| GLuint max_value = 0; | |
| const T* element = | |
| reinterpret_cast<const T*>(static_cast<const int8_t*>(data) + offset); | |
| const T* end = element + count; | |
| for (; element < end; ++element) { | |
| if (*element > max_value) { | |
| if (*element == primitive_restart_index) { | |
| continue; | |
| } | |
| max_value = *element; | |
| } | |
| } | |
| return max_value; | |
| } | |
| bool Buffer::GetMaxValueForRange( | |
| GLuint offset, GLsizei count, GLenum type, bool primitive_restart_enabled, | |
| GLuint* max_value) { | |
| GLuint primitive_restart_index = 0; | |
| if (primitive_restart_enabled) { | |
| switch (type) { | |
| case GL_UNSIGNED_BYTE: | |
| primitive_restart_index = 0xFF; | |
| break; | |
| case GL_UNSIGNED_SHORT: | |
| primitive_restart_index = 0xFFFF; | |
| break; | |
| case GL_UNSIGNED_INT: | |
| primitive_restart_index = 0xFFFFFFFF; | |
| break; | |
| default: | |
| NOTREACHED(); // should never get here by validation. | |
| break; | |
| } | |
| } | |
| Range range(offset, count, type, primitive_restart_enabled); | |
| RangeToMaxValueMap::iterator it = range_set_.find(range); | |
| if (it != range_set_.end()) { | |
| *max_value = it->second; | |
| return true; | |
| } | |
| // Optimization. If: | |
| // - primitive restart is enabled | |
| // - we don't have an entry in the range set for these parameters | |
| // for the situation when primitive restart is enabled | |
| // - we do have an entry in the range set for these parameters for | |
| // the situation when primitive restart is disabled | |
| // - this entry is less than the primitive restart index | |
| // Then we can repurpose this entry for the situation when primitive | |
| // restart is enabled. Otherwise, we need to compute the max index | |
| // from scratch. | |
| if (primitive_restart_enabled) { | |
| Range disabled_range(offset, count, type, false); | |
| RangeToMaxValueMap::iterator it = range_set_.find(disabled_range); | |
| if (it != range_set_.end() && it->second < primitive_restart_index) { | |
| // This reuses the max value for the case where primitive | |
| // restart is enabled. | |
| range_set_.insert(std::make_pair(range, it->second)); | |
| *max_value = it->second; | |
| return true; | |
| } | |
| } | |
| uint32_t size; | |
| if (!SafeMultiplyUint32( | |
| count, GLES2Util::GetGLTypeSizeForBuffers(type), &size)) { | |
| return false; | |
| } | |
| if (!SafeAddUint32(offset, size, &size)) { | |
| return false; | |
| } | |
| if (size > static_cast<uint32_t>(size_)) { | |
| return false; | |
| } | |
| if (shadow_.empty()) { | |
| return false; | |
| } | |
| // Scan the range for the max value and store | |
| GLuint max_v = 0; | |
| switch (type) { | |
| case GL_UNSIGNED_BYTE: | |
| max_v = GetMaxValue<uint8_t>(shadow_.data(), offset, count, | |
| primitive_restart_index); | |
| 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<uint16_t>(shadow_.data(), offset, count, | |
| primitive_restart_index); | |
| 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<uint32_t>(shadow_.data(), offset, count, | |
| primitive_restart_index); | |
| 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; | |
| } | |
| void Buffer::SetMappedRange(GLintptr offset, GLsizeiptr size, GLenum access, | |
| void* pointer, scoped_refptr<gpu::Buffer> shm, | |
| unsigned int shm_offset) { | |
| mapped_range_.reset( | |
| new MappedRange(offset, size, access, pointer, shm, shm_offset)); | |
| } | |
| void Buffer::RemoveMappedRange() { | |
| mapped_range_.reset(nullptr); | |
| } | |
| 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; | |
| } | |
| bool BufferManager::UseShadowBuffer(GLenum target, GLenum usage) { | |
| const bool is_client_side_array = IsUsageClientSideArray(usage); | |
| // feature_info_ can be null in some unit tests. | |
| const bool support_fixed_attribs = | |
| !feature_info_ || feature_info_->gl_version_info().SupportsFixedType(); | |
| // TODO(zmo): Don't shadow buffer data on ES3. crbug.com/491002. | |
| return ( | |
| target == GL_ELEMENT_ARRAY_BUFFER || allow_buffers_on_multiple_targets_ || | |
| (allow_fixed_attribs_ && !support_fixed_attribs) || is_client_side_array); | |
| } | |
| void BufferManager::SetInfo(Buffer* buffer, | |
| GLenum target, | |
| GLsizeiptr size, | |
| GLenum usage, | |
| bool use_shadow) { | |
| DCHECK(buffer); | |
| memory_type_tracker_->TrackMemFree(buffer->size()); | |
| buffer->SetInfo(size, usage, use_shadow, IsUsageClientSideArray(usage)); | |
| memory_type_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; | |
| } | |
| if (size > max_buffer_size_) { | |
| ERRORSTATE_SET_GL_ERROR(error_state, GL_OUT_OF_MEMORY, "glBufferData", | |
| "cannot allocate more than 1GB."); | |
| return; | |
| } | |
| Buffer* buffer = GetBufferInfoForTarget(context_state, target); | |
| if (!buffer) { | |
| ERRORSTATE_SET_GL_ERROR( | |
| error_state, GL_INVALID_VALUE, "glBufferData", "unknown buffer"); | |
| return; | |
| } | |
| if (!memory_type_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); | |
| if (context_state->bound_transform_feedback.get()) { | |
| // buffer size might have changed, and on Desktop GL lower than 4.2, | |
| // we might need to reset transform feedback buffer range. | |
| context_state->bound_transform_feedback->OnBufferData(target, buffer); | |
| } | |
| } | |
| void BufferManager::DoBufferData( | |
| ErrorState* error_state, | |
| Buffer* buffer, | |
| GLenum target, | |
| GLsizeiptr size, | |
| GLenum usage, | |
| const GLvoid* data) { | |
| // Stage the shadow buffer first if we are using a shadow buffer so that we | |
| // validate what we store internally. | |
| const bool use_shadow = UseShadowBuffer(buffer->initial_target(), usage); | |
| data = buffer->StageShadow(use_shadow, size, data); | |
| ERRORSTATE_COPY_REAL_GL_ERRORS_TO_WRAPPER(error_state, "glBufferData"); | |
| if (IsUsageClientSideArray(usage)) { | |
| GLsizei empty_size = UseNonZeroSizeForClientSideArrayBuffer() ? 1 : 0; | |
| glBufferData(target, empty_size, nullptr, usage); | |
| } else { | |
| if (data || !size) { | |
| glBufferData(target, size, data, usage); | |
| } else { | |
| std::unique_ptr<char[]> zero(new char[size]); | |
| memset(zero.get(), 0, size); | |
| glBufferData(target, size, zero.get(), usage); | |
| } | |
| } | |
| GLenum error = ERRORSTATE_PEEK_GL_ERROR(error_state, "glBufferData"); | |
| if (error != GL_NO_ERROR) { | |
| DCHECK_EQ(static_cast<GLenum>(GL_OUT_OF_MEMORY), error); | |
| size = 0; | |
| // TODO(zmo): This doesn't seem correct. There might be shadow data from | |
| // a previous successful BufferData() call. | |
| buffer->StageShadow(false, 0, nullptr); // Also clear the shadow. | |
| return; | |
| } | |
| SetInfo(buffer, target, size, usage, use_shadow); | |
| } | |
| void BufferManager::ValidateAndDoBufferSubData( | |
| ContextState* context_state, GLenum target, GLintptr offset, GLsizeiptr size, | |
| const GLvoid * data) { | |
| Buffer* buffer = RequestBufferAccess( | |
| context_state, target, offset, size, "glBufferSubData"); | |
| if (!buffer) { | |
| return; | |
| } | |
| DoBufferSubData(buffer, target, offset, size, data); | |
| } | |
| void BufferManager::DoBufferSubData( | |
| Buffer* buffer, GLenum target, GLintptr offset, GLsizeiptr size, | |
| const GLvoid* data) { | |
| buffer->SetRange(offset, size, data); | |
| if (!buffer->IsClientSideArray()) { | |
| glBufferSubData(target, offset, size, data); | |
| } | |
| } | |
| void BufferManager::ValidateAndDoCopyBufferSubData( | |
| ContextState* context_state, GLenum readtarget, GLenum writetarget, | |
| GLintptr readoffset, GLintptr writeoffset, GLsizeiptr size) { | |
| const char* func_name = "glCopyBufferSubData"; | |
| Buffer* readbuffer = RequestBufferAccess( | |
| context_state, readtarget, readoffset, size, func_name); | |
| if (!readbuffer) | |
| return; | |
| Buffer* writebuffer = RequestBufferAccess( | |
| context_state, writetarget, writeoffset, size, func_name); | |
| if (!writebuffer) | |
| return; | |
| ErrorState* error_state = context_state->GetErrorState(); | |
| if (readbuffer == writebuffer && | |
| ((writeoffset >= readoffset && writeoffset < readoffset + size) || | |
| (readoffset >= writeoffset && readoffset < writeoffset + size))) { | |
| ERRORSTATE_SET_GL_ERROR(error_state, GL_INVALID_VALUE, func_name, | |
| "read/write ranges overlap"); | |
| return; | |
| } | |
| if (!allow_buffers_on_multiple_targets_) { | |
| if ((readbuffer->initial_target() == GL_ELEMENT_ARRAY_BUFFER && | |
| writebuffer->initial_target() != GL_ELEMENT_ARRAY_BUFFER) || | |
| (writebuffer->initial_target() == GL_ELEMENT_ARRAY_BUFFER && | |
| readbuffer->initial_target() != GL_ELEMENT_ARRAY_BUFFER)) { | |
| ERRORSTATE_SET_GL_ERROR(error_state, GL_INVALID_OPERATION, func_name, | |
| "copying between ELEMENT_ARRAY_BUFFER and another buffer type"); | |
| return; | |
| } | |
| } | |
| DoCopyBufferSubData(readbuffer, readtarget, readoffset, | |
| writebuffer, writetarget, writeoffset, size); | |
| } | |
| void BufferManager::DoCopyBufferSubData( | |
| Buffer* readbuffer, | |
| GLenum readtarget, | |
| GLintptr readoffset, | |
| Buffer* writebuffer, | |
| GLenum writetarget, | |
| GLintptr writeoffset, | |
| GLsizeiptr size) { | |
| DCHECK(readbuffer); | |
| DCHECK(writebuffer); | |
| if (writebuffer->shadowed()) { | |
| const void* data = readbuffer->GetRange(readoffset, size); | |
| DCHECK(data); | |
| writebuffer->SetRange(writeoffset, size, data); | |
| } | |
| glCopyBufferSubData(readtarget, writetarget, readoffset, writeoffset, size); | |
| } | |
| 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; | |
| } | |
| } | |
| void BufferManager::SetPrimitiveRestartFixedIndexIfNecessary(GLenum type) { | |
| GLuint index = 0; | |
| switch (type) { | |
| case GL_UNSIGNED_BYTE: | |
| index = 0xFF; | |
| break; | |
| case GL_UNSIGNED_SHORT: | |
| index = 0xFFFF; | |
| break; | |
| case GL_UNSIGNED_INT: | |
| index = 0xFFFFFFFF; | |
| break; | |
| default: | |
| NOTREACHED(); // should never get here by validation. | |
| break; | |
| } | |
| if (primitive_restart_fixed_index_ != index) { | |
| glPrimitiveRestartIndex(index); | |
| primitive_restart_fixed_index_ = index; | |
| } | |
| } | |
| bool BufferManager::OnMemoryDump(const base::trace_event::MemoryDumpArgs& args, | |
| base::trace_event::ProcessMemoryDump* pmd) { | |
| using base::trace_event::MemoryAllocatorDump; | |
| using base::trace_event::MemoryDumpLevelOfDetail; | |
| if (args.level_of_detail == MemoryDumpLevelOfDetail::BACKGROUND) { | |
| std::string dump_name = | |
| base::StringPrintf("gpu/gl/buffers/share_group_0x%" PRIX64 "", | |
| memory_tracker_->ShareGroupTracingGUID()); | |
| MemoryAllocatorDump* dump = pmd->CreateAllocatorDump(dump_name); | |
| dump->AddScalar(MemoryAllocatorDump::kNameSize, | |
| MemoryAllocatorDump::kUnitsBytes, mem_represented()); | |
| // Early out, no need for more detail in a BACKGROUND dump. | |
| return true; | |
| } | |
| const uint64_t share_group_tracing_guid = | |
| memory_tracker_->ShareGroupTracingGUID(); | |
| for (const auto& buffer_entry : buffers_) { | |
| const auto& client_buffer_id = buffer_entry.first; | |
| const auto& buffer = buffer_entry.second; | |
| std::string dump_name = base::StringPrintf( | |
| "gpu/gl/buffers/share_group_0x%" PRIX64 "/buffer_0x%" PRIX32, | |
| share_group_tracing_guid, client_buffer_id); | |
| MemoryAllocatorDump* dump = pmd->CreateAllocatorDump(dump_name); | |
| dump->AddScalar(MemoryAllocatorDump::kNameSize, | |
| MemoryAllocatorDump::kUnitsBytes, | |
| static_cast<uint64_t>(buffer->size())); | |
| auto* mapped_range = buffer->GetMappedRange(); | |
| if (!mapped_range) | |
| continue; | |
| auto shared_memory_guid = | |
| mapped_range->shm->backing()->shared_memory_handle().GetGUID(); | |
| if (!shared_memory_guid.is_empty()) { | |
| pmd->CreateSharedMemoryOwnershipEdge(dump->guid(), shared_memory_guid, | |
| 0 /* importance */); | |
| } else { | |
| auto guid = gl::GetGLBufferGUIDForTracing(share_group_tracing_guid, | |
| client_buffer_id); | |
| pmd->CreateSharedGlobalAllocatorDump(guid); | |
| pmd->AddOwnershipEdge(dump->guid(), guid); | |
| } | |
| } | |
| return true; | |
| } | |
| Buffer* BufferManager::RequestBufferAccess(ContextState* context_state, | |
| GLenum target, | |
| GLintptr offset, | |
| GLsizeiptr size, | |
| const char* func_name) { | |
| DCHECK(context_state); | |
| ErrorState* error_state = context_state->GetErrorState(); | |
| Buffer* buffer = GetBufferInfoForTarget(context_state, target); | |
| if (!RequestBufferAccess(error_state, buffer, func_name, | |
| "bound to target 0x%04x", target)) { | |
| return nullptr; | |
| } | |
| if (!buffer->CheckRange(offset, size)) { | |
| std::string msg = base::StringPrintf( | |
| "bound to target 0x%04x : offset/size out of range", target); | |
| ERRORSTATE_SET_GL_ERROR( | |
| error_state, GL_INVALID_VALUE, func_name, msg.c_str()); | |
| return nullptr; | |
| } | |
| return buffer; | |
| } | |
| Buffer* BufferManager::RequestBufferAccess(ContextState* context_state, | |
| GLenum target, | |
| const char* func_name) { | |
| DCHECK(context_state); | |
| ErrorState* error_state = context_state->GetErrorState(); | |
| Buffer* buffer = GetBufferInfoForTarget(context_state, target); | |
| return RequestBufferAccess( | |
| error_state, buffer, func_name, | |
| "bound to target 0x%04x", target) ? buffer : nullptr; | |
| } | |
| bool BufferManager::RequestBufferAccess(ErrorState* error_state, | |
| Buffer* buffer, | |
| const char* func_name, | |
| const char* error_message_format, ...) { | |
| DCHECK(error_state); | |
| va_list varargs; | |
| va_start(varargs, error_message_format); | |
| bool result = RequestBufferAccessV(error_state, buffer, func_name, | |
| error_message_format, varargs); | |
| va_end(varargs); | |
| return result; | |
| } | |
| bool BufferManager::RequestBufferAccess(ErrorState* error_state, | |
| Buffer* buffer, | |
| GLintptr offset, | |
| GLsizeiptr size, | |
| const char* func_name, | |
| const char* error_message) { | |
| if (!RequestBufferAccess(error_state, buffer, func_name, error_message)) { | |
| return false; | |
| } | |
| if (!buffer->CheckRange(offset, size)) { | |
| std::string msg = base::StringPrintf( | |
| "%s : offset/size out of range", error_message); | |
| ERRORSTATE_SET_GL_ERROR( | |
| error_state, GL_INVALID_OPERATION, func_name, msg.c_str()); | |
| return false; | |
| } | |
| return true; | |
| } | |
| bool BufferManager::RequestBuffersAccess( | |
| ErrorState* error_state, | |
| const IndexedBufferBindingHost* bindings, | |
| const std::vector<GLsizeiptr>& variable_sizes, | |
| GLsizei count, | |
| const char* func_name, | |
| const char* message_tag) { | |
| DCHECK(error_state); | |
| DCHECK(bindings); | |
| for (size_t ii = 0; ii < variable_sizes.size(); ++ii) { | |
| if (variable_sizes[ii] == 0) | |
| continue; | |
| Buffer* buffer = bindings->GetBufferBinding(ii); | |
| if (!buffer) { | |
| std::string msg = base::StringPrintf( | |
| "%s : no buffer bound at index %zu", message_tag, ii); | |
| ERRORSTATE_SET_GL_ERROR( | |
| error_state, GL_INVALID_OPERATION, func_name, msg.c_str()); | |
| return false; | |
| } | |
| if (buffer->GetMappedRange()) { | |
| std::string msg = base::StringPrintf( | |
| "%s : buffer is mapped at index %zu", message_tag, ii); | |
| ERRORSTATE_SET_GL_ERROR( | |
| error_state, GL_INVALID_OPERATION, func_name, msg.c_str()); | |
| return false; | |
| } | |
| GLsizeiptr size = bindings->GetEffectiveBufferSize(ii); | |
| base::CheckedNumeric<GLsizeiptr> required_size = variable_sizes[ii]; | |
| required_size *= count; | |
| if (size < required_size.ValueOrDefault( | |
| std::numeric_limits<GLsizeiptr>::max())) { | |
| std::string msg = base::StringPrintf( | |
| "%s : buffer or buffer range not large enough at index %zu", | |
| message_tag, ii); | |
| ERRORSTATE_SET_GL_ERROR( | |
| error_state, GL_INVALID_OPERATION, func_name, msg.c_str()); | |
| return false; | |
| } | |
| } | |
| return true; | |
| } | |
| bool BufferManager::RequestBufferAccessV(ErrorState* error_state, | |
| Buffer* buffer, | |
| const char* func_name, | |
| const char* error_message_format, | |
| va_list varargs) { | |
| DCHECK(error_state); | |
| if (!buffer || buffer->IsDeleted()) { | |
| std::string message_tag = base::StringPrintV(error_message_format, varargs); | |
| std::string msg = base::StringPrintf("%s : no buffer", message_tag.c_str()); | |
| ERRORSTATE_SET_GL_ERROR(error_state, GL_INVALID_OPERATION, func_name, | |
| msg.c_str()); | |
| return false; | |
| } | |
| if (buffer->GetMappedRange()) { | |
| std::string message_tag = base::StringPrintV(error_message_format, varargs); | |
| std::string msg = base::StringPrintf("%s : buffer is mapped", | |
| message_tag.c_str()); | |
| ERRORSTATE_SET_GL_ERROR(error_state, GL_INVALID_OPERATION, func_name, | |
| msg.c_str()); | |
| return false; | |
| } | |
| return true; | |
| } | |
| } // namespace gles2 | |
| } // namespace gpu |