| // 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/texture_manager.h" | |
| #include <stddef.h> | |
| #include <stdint.h> | |
| #include <algorithm> | |
| #include <set> | |
| #include <utility> | |
| #include "base/bits.h" | |
| #include "base/format_macros.h" | |
| #include "base/lazy_instance.h" | |
| #include "base/metrics/histogram_macros.h" | |
| #include "base/strings/stringprintf.h" | |
| #include "base/threading/thread_task_runner_handle.h" | |
| #include "base/trace_event/memory_dump_manager.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/framebuffer_manager.h" | |
| #include "gpu/command_buffer/service/gl_stream_texture_image.h" | |
| #include "gpu/command_buffer/service/gles2_cmd_decoder.h" | |
| #include "gpu/command_buffer/service/mailbox_manager.h" | |
| #include "gpu/command_buffer/service/memory_tracking.h" | |
| #include "gpu/command_buffer/service/progress_reporter.h" | |
| #include "gpu/command_buffer/service/service_discardable_manager.h" | |
| #include "ui/gl/gl_context.h" | |
| #include "ui/gl/gl_implementation.h" | |
| #include "ui/gl/gl_state_restorer.h" | |
| #include "ui/gl/gl_version_info.h" | |
| #include "ui/gl/trace_util.h" | |
| using base::trace_event::MemoryAllocatorDump; | |
| using base::trace_event::MemoryDumpLevelOfDetail; | |
| namespace gpu { | |
| namespace gles2 { | |
| namespace { | |
| // This should contain everything to uniquely identify a Texture. | |
| const char TextureTag[] = "|Texture|"; | |
| struct TextureSignature { | |
| GLenum target_; | |
| GLint level_; | |
| GLenum min_filter_; | |
| GLenum mag_filter_; | |
| GLenum wrap_r_; | |
| GLenum wrap_s_; | |
| GLenum wrap_t_; | |
| GLenum usage_; | |
| GLenum internal_format_; | |
| GLenum compare_func_; | |
| GLenum compare_mode_; | |
| GLsizei width_; | |
| GLsizei height_; | |
| GLsizei depth_; | |
| GLfloat max_lod_; | |
| GLfloat min_lod_; | |
| GLint base_level_; | |
| GLint border_; | |
| GLint max_level_; | |
| GLenum format_; | |
| GLenum type_; | |
| bool has_image_; | |
| bool can_render_; | |
| bool can_render_to_; | |
| bool npot_; | |
| bool emulating_rgb_; | |
| // Since we will be hashing this signature structure, the padding must be | |
| // zero initialized. Although the C++11 specifications specify that this is | |
| // true, we will use a constructor with a memset to further enforce it instead | |
| // of relying on compilers adhering to this deep dark corner specification. | |
| TextureSignature(GLenum target, | |
| GLint level, | |
| const SamplerState& sampler_state, | |
| GLenum usage, | |
| GLenum internal_format, | |
| GLsizei width, | |
| GLsizei height, | |
| GLsizei depth, | |
| GLint base_level, | |
| GLint border, | |
| GLint max_level, | |
| GLenum format, | |
| GLenum type, | |
| bool has_image, | |
| bool can_render, | |
| bool can_render_to, | |
| bool npot, | |
| bool emulating_rgb) { | |
| memset(this, 0, sizeof(TextureSignature)); | |
| target_ = target; | |
| level_ = level; | |
| min_filter_ = sampler_state.min_filter; | |
| mag_filter_ = sampler_state.mag_filter; | |
| wrap_r_ = sampler_state.wrap_r; | |
| wrap_s_ = sampler_state.wrap_s; | |
| wrap_t_ = sampler_state.wrap_t; | |
| usage_ = usage; | |
| internal_format_ = internal_format; | |
| compare_func_ = sampler_state.compare_func; | |
| compare_mode_ = sampler_state.compare_mode; | |
| width_ = width; | |
| height_ = height; | |
| depth_ = depth; | |
| max_lod_ = sampler_state.max_lod; | |
| min_lod_ = sampler_state.min_lod; | |
| base_level_ = base_level; | |
| border_ = border; | |
| max_level_ = max_level; | |
| format_ = format; | |
| type_ = type; | |
| has_image_ = has_image; | |
| can_render_ = can_render; | |
| can_render_to_ = can_render_to; | |
| npot_ = npot; | |
| emulating_rgb_ = emulating_rgb; | |
| } | |
| }; | |
| class FormatTypeValidator { | |
| public: | |
| FormatTypeValidator() { | |
| static const FormatType kSupportedFormatTypes[] = { | |
| // ES2. | |
| {GL_RGB, GL_RGB, GL_UNSIGNED_BYTE}, | |
| {GL_RGB, GL_RGB, GL_UNSIGNED_SHORT_5_6_5}, | |
| {GL_RGBA, GL_RGBA, GL_UNSIGNED_BYTE}, | |
| {GL_RGBA, GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4}, | |
| {GL_RGBA, GL_RGBA, GL_UNSIGNED_SHORT_5_5_5_1}, | |
| {GL_LUMINANCE_ALPHA, GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE}, | |
| {GL_LUMINANCE, GL_LUMINANCE, GL_UNSIGNED_BYTE}, | |
| {GL_ALPHA, GL_ALPHA, GL_UNSIGNED_BYTE}, | |
| // ES3. | |
| {GL_R8, GL_RED, GL_UNSIGNED_BYTE}, | |
| {GL_R8_SNORM, GL_RED, GL_BYTE}, | |
| {GL_R16F, GL_RED, GL_HALF_FLOAT}, | |
| {GL_R16F, GL_RED, GL_FLOAT}, | |
| {GL_R32F, GL_RED, GL_FLOAT}, | |
| {GL_R8UI, GL_RED_INTEGER, GL_UNSIGNED_BYTE}, | |
| {GL_R8I, GL_RED_INTEGER, GL_BYTE}, | |
| {GL_R16UI, GL_RED_INTEGER, GL_UNSIGNED_SHORT}, | |
| {GL_R16I, GL_RED_INTEGER, GL_SHORT}, | |
| {GL_R32UI, GL_RED_INTEGER, GL_UNSIGNED_INT}, | |
| {GL_R32I, GL_RED_INTEGER, GL_INT}, | |
| {GL_RG8, GL_RG, GL_UNSIGNED_BYTE}, | |
| {GL_RG8_SNORM, GL_RG, GL_BYTE}, | |
| {GL_RG16F, GL_RG, GL_HALF_FLOAT}, | |
| {GL_RG16F, GL_RG, GL_FLOAT}, | |
| {GL_RG32F, GL_RG, GL_FLOAT}, | |
| {GL_RG8UI, GL_RG_INTEGER, GL_UNSIGNED_BYTE}, | |
| {GL_RG8I, GL_RG_INTEGER, GL_BYTE}, | |
| {GL_RG16UI, GL_RG_INTEGER, GL_UNSIGNED_SHORT}, | |
| {GL_RG16I, GL_RG_INTEGER, GL_SHORT}, | |
| {GL_RG32UI, GL_RG_INTEGER, GL_UNSIGNED_INT}, | |
| {GL_RG32I, GL_RG_INTEGER, GL_INT}, | |
| {GL_RGB8, GL_RGB, GL_UNSIGNED_BYTE}, | |
| {GL_SRGB8, GL_RGB, GL_UNSIGNED_BYTE}, | |
| {GL_RGB565, GL_RGB, GL_UNSIGNED_BYTE}, | |
| {GL_RGB565, GL_RGB, GL_UNSIGNED_SHORT_5_6_5}, | |
| {GL_RGB8_SNORM, GL_RGB, GL_BYTE}, | |
| {GL_R11F_G11F_B10F, GL_RGB, GL_UNSIGNED_INT_10F_11F_11F_REV}, | |
| {GL_R11F_G11F_B10F, GL_RGB, GL_HALF_FLOAT}, | |
| {GL_R11F_G11F_B10F, GL_RGB, GL_FLOAT}, | |
| {GL_RGB9_E5, GL_RGB, GL_UNSIGNED_INT_5_9_9_9_REV}, | |
| {GL_RGB9_E5, GL_RGB, GL_HALF_FLOAT}, | |
| {GL_RGB9_E5, GL_RGB, GL_FLOAT}, | |
| {GL_RGB16F, GL_RGB, GL_HALF_FLOAT}, | |
| {GL_RGB16F, GL_RGB, GL_FLOAT}, | |
| {GL_RGB32F, GL_RGB, GL_FLOAT}, | |
| {GL_RGB8UI, GL_RGB_INTEGER, GL_UNSIGNED_BYTE}, | |
| {GL_RGB8I, GL_RGB_INTEGER, GL_BYTE}, | |
| {GL_RGB16UI, GL_RGB_INTEGER, GL_UNSIGNED_SHORT}, | |
| {GL_RGB16I, GL_RGB_INTEGER, GL_SHORT}, | |
| {GL_RGB32UI, GL_RGB_INTEGER, GL_UNSIGNED_INT}, | |
| {GL_RGB32I, GL_RGB_INTEGER, GL_INT}, | |
| {GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE}, | |
| {GL_SRGB8_ALPHA8, GL_RGBA, GL_UNSIGNED_BYTE}, | |
| {GL_RGBA8_SNORM, GL_RGBA, GL_BYTE}, | |
| {GL_RGB5_A1, GL_RGBA, GL_UNSIGNED_BYTE}, | |
| {GL_RGB5_A1, GL_RGBA, GL_UNSIGNED_SHORT_5_5_5_1}, | |
| {GL_RGB5_A1, GL_RGBA, GL_UNSIGNED_INT_2_10_10_10_REV}, | |
| {GL_RGBA4, GL_RGBA, GL_UNSIGNED_BYTE}, | |
| {GL_RGBA4, GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4}, | |
| {GL_RGB10_A2, GL_RGBA, GL_UNSIGNED_INT_2_10_10_10_REV}, | |
| {GL_RGBA16F, GL_RGBA, GL_HALF_FLOAT}, | |
| {GL_RGBA16F, GL_RGBA, GL_FLOAT}, | |
| {GL_RGBA32F, GL_RGBA, GL_FLOAT}, | |
| {GL_RGBA8UI, GL_RGBA_INTEGER, GL_UNSIGNED_BYTE}, | |
| {GL_RGBA8I, GL_RGBA_INTEGER, GL_BYTE}, | |
| {GL_RGB10_A2UI, GL_RGBA_INTEGER, GL_UNSIGNED_INT_2_10_10_10_REV}, | |
| {GL_RGBA16UI, GL_RGBA_INTEGER, GL_UNSIGNED_SHORT}, | |
| {GL_RGBA16I, GL_RGBA_INTEGER, GL_SHORT}, | |
| {GL_RGBA32I, GL_RGBA_INTEGER, GL_INT}, | |
| {GL_RGBA32UI, GL_RGBA_INTEGER, GL_UNSIGNED_INT}, | |
| {GL_DEPTH_COMPONENT16, GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT}, | |
| {GL_DEPTH_COMPONENT16, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT}, | |
| {GL_DEPTH_COMPONENT24, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT}, | |
| {GL_DEPTH_COMPONENT32F, GL_DEPTH_COMPONENT, GL_FLOAT}, | |
| {GL_DEPTH24_STENCIL8, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8}, | |
| {GL_DEPTH32F_STENCIL8, GL_DEPTH_STENCIL, | |
| GL_FLOAT_32_UNSIGNED_INT_24_8_REV}, | |
| // Exposed by GL_APPLE_texture_format_BGRA8888 for TexStorage* | |
| // TODO(kainino): this actually exposes it for (Copy)TexImage* as well, | |
| // which is incorrect. crbug.com/663086 | |
| {GL_BGRA8_EXT, GL_BGRA_EXT, GL_UNSIGNED_BYTE}, | |
| // Exposed by GL_APPLE_texture_format_BGRA8888 and | |
| // GL_EXT_texture_format_BGRA8888 | |
| {GL_BGRA_EXT, GL_BGRA_EXT, GL_UNSIGNED_BYTE}, | |
| // Exposed by GL_EXT_texture_norm16 | |
| {GL_R16_EXT, GL_RED, GL_UNSIGNED_SHORT}, | |
| }; | |
| static const FormatType kSupportedFormatTypesES2Only[] = { | |
| // Exposed by GL_OES_texture_float and GL_OES_texture_half_float | |
| {GL_RGB, GL_RGB, GL_FLOAT}, | |
| {GL_RGBA, GL_RGBA, GL_FLOAT}, | |
| {GL_LUMINANCE_ALPHA, GL_LUMINANCE_ALPHA, GL_FLOAT}, | |
| {GL_LUMINANCE, GL_LUMINANCE, GL_FLOAT}, | |
| {GL_ALPHA, GL_ALPHA, GL_FLOAT}, | |
| {GL_RGB, GL_RGB, GL_HALF_FLOAT_OES}, | |
| {GL_RGBA, GL_RGBA, GL_HALF_FLOAT_OES}, | |
| {GL_LUMINANCE_ALPHA, GL_LUMINANCE_ALPHA, GL_HALF_FLOAT_OES}, | |
| {GL_LUMINANCE, GL_LUMINANCE, GL_HALF_FLOAT_OES}, | |
| {GL_ALPHA, GL_ALPHA, GL_HALF_FLOAT_OES}, | |
| // Exposed by GL_ANGLE_depth_texture | |
| {GL_DEPTH_COMPONENT, GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT}, | |
| {GL_DEPTH_COMPONENT, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT}, | |
| {GL_DEPTH_STENCIL, GL_DEPTH_STENCIL, GL_UNSIGNED_INT_24_8}, | |
| // Exposed by GL_EXT_sRGB | |
| {GL_SRGB, GL_SRGB, GL_UNSIGNED_BYTE}, | |
| {GL_SRGB_ALPHA, GL_SRGB_ALPHA, GL_UNSIGNED_BYTE}, | |
| // Exposed by GL_EXT_texture_rg | |
| {GL_RED, GL_RED, GL_UNSIGNED_BYTE}, | |
| {GL_RG, GL_RG, GL_UNSIGNED_BYTE}, | |
| {GL_RED, GL_RED, GL_FLOAT}, | |
| {GL_RG, GL_RG, GL_FLOAT}, | |
| {GL_RED, GL_RED, GL_HALF_FLOAT_OES}, | |
| {GL_RG, GL_RG, GL_HALF_FLOAT_OES}, | |
| }; | |
| for (size_t ii = 0; ii < arraysize(kSupportedFormatTypes); ++ii) { | |
| supported_combinations_.insert(kSupportedFormatTypes[ii]); | |
| } | |
| for (size_t ii = 0; ii < arraysize(kSupportedFormatTypesES2Only); ++ii) { | |
| supported_combinations_es2_only_.insert(kSupportedFormatTypesES2Only[ii]); | |
| } | |
| } | |
| // This may be accessed from multiple threads. | |
| bool IsValid(ContextType context_type, GLenum internal_format, GLenum format, | |
| GLenum type) const { | |
| FormatType query = { internal_format, format, type }; | |
| if (supported_combinations_.find(query) != supported_combinations_.end()) { | |
| return true; | |
| } | |
| if (context_type == CONTEXT_TYPE_OPENGLES2 || | |
| context_type == CONTEXT_TYPE_WEBGL1) { | |
| if (supported_combinations_es2_only_.find(query) != | |
| supported_combinations_es2_only_.end()) { | |
| return true; | |
| } | |
| } | |
| return false; | |
| } | |
| private: | |
| // TODO(zmo): once std::tuple is allowed, switch over to that. | |
| struct FormatType { | |
| GLenum internal_format; | |
| GLenum format; | |
| GLenum type; | |
| }; | |
| struct FormatTypeCompare { | |
| bool operator() (const FormatType& lhs, const FormatType& rhs) const { | |
| return (lhs.internal_format < rhs.internal_format || | |
| ((lhs.internal_format == rhs.internal_format) && | |
| (lhs.format < rhs.format)) || | |
| ((lhs.internal_format == rhs.internal_format) && | |
| (lhs.format == rhs.format) && | |
| (lhs.type < rhs.type))); | |
| } | |
| }; | |
| // This class needs to be thread safe, so once supported_combinations_ | |
| // are initialized in the constructor, it should never be modified later. | |
| std::set<FormatType, FormatTypeCompare> supported_combinations_; | |
| std::set<FormatType, FormatTypeCompare> supported_combinations_es2_only_; | |
| }; | |
| static const Texture::CompatibilitySwizzle kSwizzledFormats[] = { | |
| {GL_ALPHA, GL_RED, GL_ZERO, GL_ZERO, GL_ZERO, GL_RED}, | |
| {GL_LUMINANCE, GL_RED, GL_RED, GL_RED, GL_RED, GL_ONE}, | |
| {GL_LUMINANCE_ALPHA, GL_RG, GL_RED, GL_RED, GL_RED, GL_GREEN}, | |
| }; | |
| // static | |
| const Texture::CompatibilitySwizzle* GetCompatibilitySwizzle(GLenum format) { | |
| size_t count = arraysize(kSwizzledFormats); | |
| for (size_t i = 0; i < count; ++i) { | |
| if (kSwizzledFormats[i].format == format) | |
| return &kSwizzledFormats[i]; | |
| } | |
| return nullptr; | |
| } | |
| GLenum GetSwizzleForChannel(GLenum channel, | |
| const Texture::CompatibilitySwizzle* swizzle) { | |
| if (!swizzle) | |
| return channel; | |
| switch (channel) { | |
| case GL_ZERO: | |
| return GL_ZERO; | |
| case GL_ONE: | |
| return GL_ONE; | |
| case GL_RED: | |
| return swizzle->red; | |
| case GL_GREEN: | |
| return swizzle->green; | |
| case GL_BLUE: | |
| return swizzle->blue; | |
| case GL_ALPHA: | |
| return swizzle->alpha; | |
| default: | |
| NOTREACHED(); | |
| return GL_NONE; | |
| } | |
| } | |
| bool SizedFormatAvailable(const FeatureInfo* feature_info, | |
| bool immutable, | |
| GLenum internal_format) { | |
| if (immutable) | |
| return true; | |
| if (feature_info->feature_flags().ext_texture_norm16 && | |
| internal_format == GL_R16_EXT) { | |
| return true; | |
| } | |
| if ((feature_info->feature_flags().chromium_image_ycbcr_420v && | |
| internal_format == GL_RGB_YCBCR_420V_CHROMIUM) || | |
| (feature_info->feature_flags().chromium_image_ycbcr_422 && | |
| internal_format == GL_RGB_YCBCR_422_CHROMIUM)) { | |
| return true; | |
| } | |
| // TODO(dshwang): check if it's possible to remove | |
| // CHROMIUM_color_buffer_float_rgb. crbug.com/329605 | |
| if (feature_info->feature_flags().chromium_color_buffer_float_rgb && | |
| internal_format == GL_RGB32F) { | |
| return true; | |
| } | |
| if (feature_info->feature_flags().chromium_color_buffer_float_rgba && | |
| internal_format == GL_RGBA32F) { | |
| return true; | |
| } | |
| // RGBA16F textures created as WebGL 2 backbuffers (in GLES3 contexts) may be | |
| // shared with compositor GLES2 contexts for compositing. | |
| // https://crbug.com/777750 | |
| if (feature_info->feature_flags().enable_texture_half_float_linear && | |
| internal_format == GL_RGBA16F) { | |
| return true; | |
| } | |
| return feature_info->IsWebGL2OrES3Context(); | |
| } | |
| // A 32-bit and 64-bit compatible way of converting a pointer to a GLuint. | |
| GLuint ToGLuint(const void* ptr) { | |
| return static_cast<GLuint>(reinterpret_cast<size_t>(ptr)); | |
| } | |
| base::LazyInstance<const FormatTypeValidator>::Leaky g_format_type_validator = | |
| LAZY_INSTANCE_INITIALIZER; | |
| class ScopedResetPixelUnpackBuffer{ | |
| public: | |
| explicit ScopedResetPixelUnpackBuffer(ContextState* state) | |
| : buffer_(nullptr) { | |
| buffer_ = state->bound_pixel_unpack_buffer.get(); | |
| if (buffer_) { | |
| glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); | |
| } | |
| } | |
| ~ScopedResetPixelUnpackBuffer() { | |
| if (buffer_) { | |
| glBindBuffer(GL_PIXEL_UNPACK_BUFFER, buffer_->service_id()); | |
| } | |
| } | |
| private: | |
| Buffer* buffer_; | |
| }; | |
| } // namespace anonymous | |
| DecoderTextureState::DecoderTextureState( | |
| const GpuDriverBugWorkarounds& workarounds) | |
| : tex_image_failed(false), | |
| texsubimage_faster_than_teximage( | |
| workarounds.texsubimage_faster_than_teximage), | |
| force_cube_map_positive_x_allocation( | |
| workarounds.force_cube_map_positive_x_allocation), | |
| force_cube_complete(workarounds.force_cube_complete), | |
| force_int_or_srgb_cube_texture_complete( | |
| workarounds.force_int_or_srgb_cube_texture_complete), | |
| unpack_alignment_workaround_with_unpack_buffer( | |
| workarounds.unpack_alignment_workaround_with_unpack_buffer), | |
| unpack_overlapping_rows_separately_unpack_buffer( | |
| workarounds.unpack_overlapping_rows_separately_unpack_buffer), | |
| unpack_image_height_workaround_with_unpack_buffer( | |
| workarounds.unpack_image_height_workaround_with_unpack_buffer) {} | |
| TextureManager::DestructionObserver::DestructionObserver() {} | |
| TextureManager::DestructionObserver::~DestructionObserver() {} | |
| TextureManager::~TextureManager() { | |
| for (unsigned int i = 0; i < destruction_observers_.size(); i++) | |
| destruction_observers_[i]->OnTextureManagerDestroying(this); | |
| DCHECK(textures_.empty()); | |
| // If this triggers, that means something is keeping a reference to | |
| // a Texture belonging to this. | |
| CHECK_EQ(texture_count_, 0u); | |
| DCHECK_EQ(0, num_unsafe_textures_); | |
| DCHECK_EQ(0, num_uncleared_mips_); | |
| DCHECK_EQ(0, num_images_); | |
| base::trace_event::MemoryDumpManager::GetInstance()->UnregisterDumpProvider( | |
| this); | |
| } | |
| void TextureManager::MarkContextLost() { | |
| have_context_ = false; | |
| } | |
| void TextureManager::Destroy() { | |
| // Retreive any outstanding unlocked textures from the discardable manager so | |
| // we can clean them up here. | |
| discardable_manager_->OnTextureManagerDestruction(this); | |
| while (!textures_.empty()) { | |
| textures_.erase(textures_.begin()); | |
| if (progress_reporter_) | |
| progress_reporter_->ReportProgress(); | |
| } | |
| for (int ii = 0; ii < kNumDefaultTextures; ++ii) { | |
| default_textures_[ii] = NULL; | |
| if (progress_reporter_) | |
| progress_reporter_->ReportProgress(); | |
| } | |
| if (have_context_) { | |
| glDeleteTextures(arraysize(black_texture_ids_), black_texture_ids_); | |
| } | |
| DCHECK_EQ(0u, memory_type_tracker_->GetMemRepresented()); | |
| } | |
| TextureBase::TextureBase(GLuint service_id) | |
| : service_id_(service_id), target_(GL_NONE), mailbox_manager_(nullptr) {} | |
| TextureBase::~TextureBase() { | |
| DCHECK_EQ(nullptr, mailbox_manager_); | |
| } | |
| void TextureBase::SetTarget(GLenum target) { | |
| DCHECK_EQ(0u, target_); // you can only set this once. | |
| target_ = target; | |
| } | |
| void TextureBase::DeleteFromMailboxManager() { | |
| if (mailbox_manager_) { | |
| mailbox_manager_->TextureDeleted(this); | |
| mailbox_manager_ = nullptr; | |
| } | |
| } | |
| void TextureBase::SetMailboxManager(MailboxManager* mailbox_manager) { | |
| DCHECK(!mailbox_manager_ || mailbox_manager_ == mailbox_manager); | |
| mailbox_manager_ = mailbox_manager; | |
| } | |
| TexturePassthrough::TexturePassthrough(GLuint service_id, GLenum target) | |
| : TextureBase(service_id), | |
| have_context_(true), | |
| level_images_(target == GL_TEXTURE_CUBE_MAP ? 6 : 1) { | |
| TextureBase::SetTarget(target); | |
| } | |
| TexturePassthrough::~TexturePassthrough() { | |
| DeleteFromMailboxManager(); | |
| if (have_context_) { | |
| glDeleteTextures(1, &service_id_); | |
| } | |
| } | |
| void TexturePassthrough::MarkContextLost() { | |
| have_context_ = false; | |
| } | |
| void TexturePassthrough::SetLevelImage(GLenum target, | |
| GLint level, | |
| gl::GLImage* image) { | |
| size_t face_idx = GLES2Util::GLTargetToFaceIndex(target); | |
| DCHECK(face_idx < level_images_.size()); | |
| DCHECK(level >= 0); | |
| // Don't allocate space for the images until needed | |
| if (static_cast<GLint>(level_images_[face_idx].size()) <= level) { | |
| level_images_[face_idx].resize(level + 1); | |
| } | |
| level_images_[face_idx][level] = image; | |
| } | |
| gl::GLImage* TexturePassthrough::GetLevelImage(GLenum target, | |
| GLint level) const { | |
| if (GLES2Util::GLFaceTargetToTextureTarget(target) != target_) { | |
| return nullptr; | |
| } | |
| size_t face_idx = GLES2Util::GLTargetToFaceIndex(target); | |
| DCHECK(face_idx < level_images_.size()); | |
| DCHECK(level >= 0); | |
| if (static_cast<GLint>(level_images_[face_idx].size()) < level) { | |
| return nullptr; | |
| } | |
| return level_images_[face_idx][level].get(); | |
| } | |
| Texture::Texture(GLuint service_id) | |
| : TextureBase(service_id), | |
| memory_tracking_ref_(NULL), | |
| owned_service_id_(service_id), | |
| cleared_(true), | |
| num_uncleared_mips_(0), | |
| num_npot_faces_(0), | |
| usage_(GL_NONE), | |
| base_level_(0), | |
| max_level_(1000), | |
| swizzle_r_(GL_RED), | |
| swizzle_g_(GL_GREEN), | |
| swizzle_b_(GL_BLUE), | |
| swizzle_a_(GL_ALPHA), | |
| max_level_set_(-1), | |
| texture_complete_(false), | |
| cube_complete_(false), | |
| completeness_dirty_(false), | |
| npot_(false), | |
| has_been_bound_(false), | |
| framebuffer_attachment_count_(0), | |
| immutable_(false), | |
| has_images_(false), | |
| estimated_size_(0), | |
| can_render_condition_(CAN_RENDER_ALWAYS), | |
| texture_max_anisotropy_initialized_(false), | |
| compatibility_swizzle_(nullptr), | |
| emulating_rgb_(false) {} | |
| Texture::~Texture() { | |
| DeleteFromMailboxManager(); | |
| } | |
| void Texture::AddTextureRef(TextureRef* ref) { | |
| DCHECK(refs_.find(ref) == refs_.end()); | |
| refs_.insert(ref); | |
| if (!memory_tracking_ref_) { | |
| memory_tracking_ref_ = ref; | |
| GetMemTracker()->TrackMemAlloc(estimated_size()); | |
| } | |
| } | |
| void Texture::RemoveTextureRef(TextureRef* ref, bool have_context) { | |
| if (memory_tracking_ref_ == ref) { | |
| GetMemTracker()->TrackMemFree(estimated_size()); | |
| memory_tracking_ref_ = NULL; | |
| } | |
| size_t result = refs_.erase(ref); | |
| DCHECK_EQ(result, 1u); | |
| if (refs_.empty()) { | |
| if (have_context) | |
| glDeleteTextures(1, &owned_service_id_); | |
| delete this; | |
| } else if (memory_tracking_ref_ == NULL) { | |
| // TODO(piman): tune ownership semantics for cross-context group shared | |
| // textures. | |
| memory_tracking_ref_ = *refs_.begin(); | |
| GetMemTracker()->TrackMemAlloc(estimated_size()); | |
| } | |
| } | |
| MemoryTypeTracker* Texture::GetMemTracker() { | |
| DCHECK(memory_tracking_ref_); | |
| return memory_tracking_ref_->manager()->GetMemTracker(); | |
| } | |
| Texture::LevelInfo::LevelInfo() | |
| : target(0), | |
| level(-1), | |
| internal_format(0), | |
| width(0), | |
| height(0), | |
| depth(0), | |
| border(0), | |
| format(0), | |
| type(0), | |
| image_state(UNBOUND), | |
| estimated_size(0), | |
| internal_workaround(false) {} | |
| Texture::LevelInfo::LevelInfo(const LevelInfo& rhs) | |
| : cleared_rect(rhs.cleared_rect), | |
| target(rhs.target), | |
| level(rhs.level), | |
| internal_format(rhs.internal_format), | |
| width(rhs.width), | |
| height(rhs.height), | |
| depth(rhs.depth), | |
| border(rhs.border), | |
| format(rhs.format), | |
| type(rhs.type), | |
| image(rhs.image), | |
| image_state(rhs.image_state), | |
| estimated_size(rhs.estimated_size), | |
| internal_workaround(rhs.internal_workaround) {} | |
| Texture::LevelInfo::~LevelInfo() { | |
| } | |
| Texture::FaceInfo::FaceInfo() | |
| : num_mip_levels(0) { | |
| } | |
| Texture::FaceInfo::FaceInfo(const FaceInfo& other) = default; | |
| Texture::FaceInfo::~FaceInfo() { | |
| } | |
| Texture::CanRenderCondition Texture::GetCanRenderCondition() const { | |
| if (target_ == 0) | |
| return CAN_RENDER_ALWAYS; | |
| if (target_ != GL_TEXTURE_EXTERNAL_OES) { | |
| if (face_infos_.empty() || | |
| static_cast<size_t>(base_level_) >= face_infos_[0].level_infos.size()) { | |
| return CAN_RENDER_NEVER; | |
| } | |
| const Texture::LevelInfo& first_face = | |
| face_infos_[0].level_infos[base_level_]; | |
| if (first_face.width == 0 || | |
| first_face.height == 0 || | |
| first_face.depth == 0) { | |
| return CAN_RENDER_NEVER; | |
| } | |
| } | |
| if (target_ == GL_TEXTURE_CUBE_MAP && !cube_complete()) | |
| return CAN_RENDER_NEVER; | |
| // Texture may be renderable, but it depends on the sampler it's used with, | |
| // the context that's using it, and the extensions available. | |
| return CAN_RENDER_NEEDS_VALIDATION; | |
| } | |
| bool Texture::CanRender(const FeatureInfo* feature_info) const { | |
| return CanRenderWithSampler(feature_info, sampler_state()); | |
| } | |
| bool Texture::CanRenderWithSampler(const FeatureInfo* feature_info, | |
| const SamplerState& sampler_state) const { | |
| switch (can_render_condition_) { | |
| case CAN_RENDER_ALWAYS: | |
| return true; | |
| case CAN_RENDER_NEVER: | |
| return false; | |
| case CAN_RENDER_NEEDS_VALIDATION: | |
| break; | |
| } | |
| bool needs_mips = sampler_state.min_filter != GL_NEAREST && | |
| sampler_state.min_filter != GL_LINEAR; | |
| if (target_ == GL_TEXTURE_EXTERNAL_OES) { | |
| if (needs_mips) { | |
| return false; | |
| } | |
| if (sampler_state.wrap_s != GL_CLAMP_TO_EDGE || | |
| sampler_state.wrap_t != GL_CLAMP_TO_EDGE) { | |
| return false; | |
| } | |
| return true; | |
| } | |
| if (needs_mips && !texture_complete()) { | |
| return false; | |
| } | |
| if ((sampler_state.min_filter != GL_NEAREST && | |
| sampler_state.min_filter != GL_NEAREST_MIPMAP_NEAREST) || | |
| sampler_state.mag_filter != GL_NEAREST) { | |
| DCHECK(!face_infos_.empty()); | |
| DCHECK_LT(static_cast<size_t>(base_level_), | |
| face_infos_[0].level_infos.size()); | |
| const Texture::LevelInfo& first_level = | |
| face_infos_[0].level_infos[base_level_]; | |
| if ((GLES2Util::GetChannelsForFormat(first_level.internal_format) & | |
| (GLES2Util::kDepth | GLES2Util::kStencil)) != 0) { | |
| if (sampler_state.compare_mode == GL_NONE) { | |
| // In ES2 with OES_depth_texture, such limitation isn't specified. | |
| if (feature_info->IsWebGL2OrES3Context()) { | |
| return false; | |
| } | |
| } | |
| } else if (feature_info->validators()->compressed_texture_format.IsValid( | |
| first_level.internal_format)) { | |
| // TODO(zmo): The assumption that compressed textures are all filterable | |
| // may not be true in the future. | |
| } else { | |
| if (!Texture::TextureFilterable(feature_info, first_level.internal_format, | |
| first_level.type, immutable_)) { | |
| return false; | |
| } | |
| } | |
| } | |
| if (!feature_info->IsWebGL2OrES3Context()) { | |
| bool is_npot_compatible = !needs_mips && | |
| sampler_state.wrap_s == GL_CLAMP_TO_EDGE && | |
| sampler_state.wrap_t == GL_CLAMP_TO_EDGE; | |
| if (!is_npot_compatible) { | |
| if (target_ == GL_TEXTURE_RECTANGLE_ARB) | |
| return false; | |
| else if (npot()) | |
| return feature_info->feature_flags().npot_ok; | |
| } | |
| } | |
| return true; | |
| } | |
| void Texture::AddToSignature( | |
| const FeatureInfo* feature_info, | |
| GLenum target, | |
| GLint level, | |
| std::string* signature) const { | |
| DCHECK(feature_info); | |
| DCHECK(signature); | |
| DCHECK_GE(level, 0); | |
| size_t face_index = GLES2Util::GLTargetToFaceIndex(target); | |
| DCHECK_LT(static_cast<size_t>(face_index), | |
| face_infos_.size()); | |
| DCHECK_LT(static_cast<size_t>(level), | |
| face_infos_[face_index].level_infos.size()); | |
| const Texture::LevelInfo& info = | |
| face_infos_[face_index].level_infos[level]; | |
| TextureSignature signature_data(target, | |
| level, | |
| sampler_state_, | |
| usage_, | |
| info.internal_format, | |
| info.width, | |
| info.height, | |
| info.depth, | |
| base_level_, | |
| info.border, | |
| max_level_, | |
| info.format, | |
| info.type, | |
| info.image.get() != NULL, | |
| CanRender(feature_info), | |
| CanRenderTo(feature_info, level), | |
| npot_, | |
| emulating_rgb_); | |
| signature->append(TextureTag, sizeof(TextureTag)); | |
| signature->append(reinterpret_cast<const char*>(&signature_data), | |
| sizeof(signature_data)); | |
| } | |
| void Texture::MarkMipmapsGenerated() { | |
| for (size_t ii = 0; ii < face_infos_.size(); ++ii) { | |
| const Texture::FaceInfo& face_info = face_infos_[ii]; | |
| const Texture::LevelInfo& level0_info = face_info.level_infos[base_level_]; | |
| GLsizei width = level0_info.width; | |
| GLsizei height = level0_info.height; | |
| GLsizei depth = level0_info.depth; | |
| GLenum target = target_ == GL_TEXTURE_CUBE_MAP ? | |
| GLES2Util::IndexToGLFaceTarget(ii) : target_; | |
| const GLsizei num_mips = face_info.num_mip_levels; | |
| for (GLsizei level = base_level_ + 1; | |
| level < base_level_ + num_mips; ++level) { | |
| width = std::max(1, width >> 1); | |
| height = std::max(1, height >> 1); | |
| depth = target == GL_TEXTURE_2D_ARRAY ? depth : std::max(1, depth >> 1); | |
| SetLevelInfo(target, level, level0_info.internal_format, | |
| width, height, depth, level0_info.border, level0_info.format, | |
| level0_info.type, gfx::Rect(width, height)); | |
| } | |
| } | |
| } | |
| void Texture::SetTarget(GLenum target, GLint max_levels) { | |
| TextureBase::SetTarget(target); | |
| size_t num_faces = (target == GL_TEXTURE_CUBE_MAP) ? 6 : 1; | |
| face_infos_.resize(num_faces); | |
| for (size_t ii = 0; ii < num_faces; ++ii) { | |
| face_infos_[ii].level_infos.resize(max_levels); | |
| } | |
| if (target == GL_TEXTURE_EXTERNAL_OES || target == GL_TEXTURE_RECTANGLE_ARB) { | |
| sampler_state_.min_filter = GL_LINEAR; | |
| sampler_state_.wrap_s = sampler_state_.wrap_t = GL_CLAMP_TO_EDGE; | |
| } | |
| if (target == GL_TEXTURE_EXTERNAL_OES) { | |
| immutable_ = true; | |
| } | |
| Update(); | |
| UpdateCanRenderCondition(); | |
| } | |
| bool Texture::CanGenerateMipmaps(const FeatureInfo* feature_info) const { | |
| if ((npot() && !feature_info->feature_flags().npot_ok) || | |
| face_infos_.empty() || | |
| target_ == GL_TEXTURE_EXTERNAL_OES || | |
| target_ == GL_TEXTURE_RECTANGLE_ARB) { | |
| return false; | |
| } | |
| if (static_cast<size_t>(base_level_) >= face_infos_[0].level_infos.size()) { | |
| return false; | |
| } | |
| // Can't generate mips for depth or stencil textures. | |
| const Texture::LevelInfo& base = face_infos_[0].level_infos[base_level_]; | |
| uint32_t channels = GLES2Util::GetChannelsForFormat(base.format); | |
| if (channels & (GLES2Util::kDepth | GLES2Util::kStencil)) { | |
| return false; | |
| } | |
| if (!feature_info->validators()->texture_unsized_internal_format.IsValid( | |
| base.internal_format)) { | |
| if (!Texture::ColorRenderable(feature_info, base.internal_format, | |
| immutable_) || | |
| !Texture::TextureFilterable(feature_info, base.internal_format, | |
| base.type, | |
| immutable_)) { | |
| return false; | |
| } | |
| } | |
| for (size_t ii = 0; ii < face_infos_.size(); ++ii) { | |
| const LevelInfo& info = face_infos_[ii].level_infos[base_level_]; | |
| if ((info.target == 0) || | |
| feature_info->validators()->compressed_texture_format.IsValid( | |
| info.internal_format) || | |
| info.image.get()) { | |
| return false; | |
| } | |
| } | |
| if (face_infos_.size() == 6 && !cube_complete()) { | |
| return false; | |
| } | |
| return true; | |
| } | |
| bool Texture::TextureIsNPOT(GLsizei width, | |
| GLsizei height, | |
| GLsizei depth) { | |
| return (GLES2Util::IsNPOT(width) || | |
| GLES2Util::IsNPOT(height) || | |
| GLES2Util::IsNPOT(depth)); | |
| } | |
| bool Texture::TextureFaceComplete(const Texture::LevelInfo& first_face, | |
| size_t face_index, | |
| GLenum target, | |
| GLenum internal_format, | |
| GLsizei width, | |
| GLsizei height, | |
| GLsizei depth, | |
| GLenum format, | |
| GLenum type) { | |
| bool complete = (target != 0 && depth == 1); | |
| if (face_index != 0) { | |
| complete &= (width == first_face.width && | |
| height == first_face.height && | |
| internal_format == first_face.internal_format && | |
| format == first_face.format && | |
| type == first_face.type); | |
| } | |
| return complete; | |
| } | |
| bool Texture::TextureMipComplete(const Texture::LevelInfo& base_level_face, | |
| GLenum target, | |
| GLint level_diff, | |
| GLenum internal_format, | |
| GLsizei width, | |
| GLsizei height, | |
| GLsizei depth, | |
| GLenum format, | |
| GLenum type) { | |
| bool complete = (target != 0); | |
| if (level_diff > 0) { | |
| const GLsizei mip_width = std::max(1, base_level_face.width >> level_diff); | |
| const GLsizei mip_height = | |
| std::max(1, base_level_face.height >> level_diff); | |
| const GLsizei mip_depth = target == GL_TEXTURE_2D_ARRAY ? | |
| base_level_face.depth : | |
| std::max(1, base_level_face.depth >> level_diff); | |
| complete &= (width == mip_width && | |
| height == mip_height && | |
| depth == mip_depth && | |
| internal_format == base_level_face.internal_format && | |
| format == base_level_face.format && | |
| type == base_level_face.type); | |
| } | |
| return complete; | |
| } | |
| // static | |
| bool Texture::ColorRenderable(const FeatureInfo* feature_info, | |
| GLenum internal_format, | |
| bool immutable) { | |
| if (feature_info->validators()->texture_unsized_internal_format.IsValid( | |
| internal_format)) { | |
| return internal_format != GL_ALPHA && internal_format != GL_LUMINANCE && | |
| internal_format != GL_LUMINANCE_ALPHA && | |
| internal_format != GL_SRGB_EXT; | |
| } | |
| return SizedFormatAvailable(feature_info, immutable, internal_format) && | |
| feature_info->validators() | |
| ->texture_sized_color_renderable_internal_format.IsValid( | |
| internal_format); | |
| } | |
| // static | |
| bool Texture::TextureFilterable(const FeatureInfo* feature_info, | |
| GLenum internal_format, | |
| GLenum type, | |
| bool immutable) { | |
| if (feature_info->validators()->texture_unsized_internal_format.IsValid( | |
| internal_format)) { | |
| switch (type) { | |
| case GL_FLOAT: | |
| return feature_info->feature_flags().enable_texture_float_linear; | |
| case GL_HALF_FLOAT_OES: | |
| return feature_info->feature_flags().enable_texture_half_float_linear; | |
| default: | |
| // GL_HALF_FLOAT is ES3 only and should only be used with sized formats. | |
| return true; | |
| } | |
| } | |
| return SizedFormatAvailable(feature_info, immutable, internal_format) && | |
| feature_info->validators() | |
| ->texture_sized_texture_filterable_internal_format.IsValid( | |
| internal_format); | |
| } | |
| void Texture::SetLevelClearedRect(GLenum target, | |
| GLint level, | |
| const gfx::Rect& cleared_rect) { | |
| DCHECK_GE(level, 0); | |
| size_t face_index = GLES2Util::GLTargetToFaceIndex(target); | |
| DCHECK_LT(static_cast<size_t>(face_index), | |
| face_infos_.size()); | |
| DCHECK_LT(static_cast<size_t>(level), | |
| face_infos_[face_index].level_infos.size()); | |
| Texture::LevelInfo& info = | |
| face_infos_[face_index].level_infos[level]; | |
| UpdateMipCleared(&info, info.width, info.height, cleared_rect); | |
| UpdateCleared(); | |
| } | |
| void Texture::SetLevelCleared(GLenum target, GLint level, bool cleared) { | |
| DCHECK_GE(level, 0); | |
| size_t face_index = GLES2Util::GLTargetToFaceIndex(target); | |
| DCHECK_LT(static_cast<size_t>(face_index), face_infos_.size()); | |
| DCHECK_LT(static_cast<size_t>(level), | |
| face_infos_[face_index].level_infos.size()); | |
| Texture::LevelInfo& info = face_infos_[face_index].level_infos[level]; | |
| UpdateMipCleared(&info, info.width, info.height, | |
| cleared ? gfx::Rect(info.width, info.height) : gfx::Rect()); | |
| UpdateCleared(); | |
| } | |
| void Texture::UpdateCleared() { | |
| if (face_infos_.empty()) { | |
| return; | |
| } | |
| const bool cleared = (num_uncleared_mips_ == 0); | |
| // If texture is uncleared and is attached to a framebuffer, | |
| // that framebuffer must be marked possibly incomplete. | |
| if (!cleared && IsAttachedToFramebuffer()) { | |
| IncAllFramebufferStateChangeCount(); | |
| } | |
| UpdateSafeToRenderFrom(cleared); | |
| } | |
| void Texture::UpdateSafeToRenderFrom(bool cleared) { | |
| if (cleared_ == cleared) | |
| return; | |
| cleared_ = cleared; | |
| int delta = cleared ? -1 : +1; | |
| for (RefSet::iterator it = refs_.begin(); it != refs_.end(); ++it) | |
| (*it)->manager()->UpdateSafeToRenderFrom(delta); | |
| } | |
| void Texture::UpdateMipCleared(LevelInfo* info, | |
| GLsizei width, | |
| GLsizei height, | |
| const gfx::Rect& cleared_rect) { | |
| bool was_cleared = info->cleared_rect == gfx::Rect(info->width, info->height); | |
| info->width = width; | |
| info->height = height; | |
| info->cleared_rect = cleared_rect; | |
| bool cleared = info->cleared_rect == gfx::Rect(info->width, info->height); | |
| if (cleared == was_cleared) | |
| return; | |
| int delta = cleared ? -1 : +1; | |
| num_uncleared_mips_ += delta; | |
| for (RefSet::iterator it = refs_.begin(); it != refs_.end(); ++it) | |
| (*it)->manager()->UpdateUnclearedMips(delta); | |
| } | |
| void Texture::UpdateCanRenderCondition() { | |
| can_render_condition_ = GetCanRenderCondition(); | |
| } | |
| void Texture::UpdateHasImages() { | |
| if (face_infos_.empty()) | |
| return; | |
| bool has_images = false; | |
| for (size_t ii = 0; ii < face_infos_.size(); ++ii) { | |
| for (size_t jj = 0; jj < face_infos_[ii].level_infos.size(); ++jj) { | |
| const Texture::LevelInfo& info = face_infos_[ii].level_infos[jj]; | |
| if (info.image.get() != NULL) { | |
| has_images = true; | |
| break; | |
| } | |
| } | |
| } | |
| if (has_images_ == has_images) | |
| return; | |
| has_images_ = has_images; | |
| int delta = has_images ? +1 : -1; | |
| for (RefSet::iterator it = refs_.begin(); it != refs_.end(); ++it) | |
| (*it)->manager()->UpdateNumImages(delta); | |
| } | |
| void Texture::UpdateEmulatingRGB() { | |
| for (const FaceInfo& face_info : face_infos_) { | |
| for (const LevelInfo& level_info : face_info.level_infos) { | |
| if (level_info.image && level_info.image->EmulatingRGB()) { | |
| emulating_rgb_ = true; | |
| return; | |
| } | |
| } | |
| } | |
| emulating_rgb_ = false; | |
| } | |
| void Texture::IncAllFramebufferStateChangeCount() { | |
| for (RefSet::iterator it = refs_.begin(); it != refs_.end(); ++it) | |
| (*it)->manager()->IncFramebufferStateChangeCount(); | |
| } | |
| void Texture::UpdateBaseLevel(GLint base_level) { | |
| if (base_level_ == base_level) | |
| return; | |
| base_level_ = base_level; | |
| UpdateNumMipLevels(); | |
| } | |
| void Texture::UpdateMaxLevel(GLint max_level) { | |
| if (max_level_ == max_level) | |
| return; | |
| max_level_ = max_level; | |
| UpdateNumMipLevels(); | |
| } | |
| void Texture::UpdateNumMipLevels() { | |
| if (face_infos_.empty()) | |
| return; | |
| GLint base_level = base_level_; | |
| GLint max_level = max_level_; | |
| if (immutable_) { | |
| GLint levels = GetImmutableLevels(); | |
| DCHECK_LE(1, levels); | |
| DCHECK_LE(0, base_level_); | |
| base_level = std::min(base_level_, levels - 1); | |
| max_level = std::max(base_level, max_level_); | |
| max_level = std::min(max_level, levels - 1); | |
| } | |
| GLint max_num_mip_levels = std::max(0, max_level - base_level + 1); | |
| for (size_t ii = 0; ii < face_infos_.size(); ++ii) { | |
| Texture::FaceInfo& face_info = face_infos_[ii]; | |
| if (static_cast<size_t>(base_level) >= face_info.level_infos.size()) | |
| continue; | |
| const Texture::LevelInfo& info = face_info.level_infos[base_level]; | |
| face_info.num_mip_levels = std::min( | |
| max_num_mip_levels, TextureManager::ComputeMipMapCount( | |
| target_, info.width, info.height, info.depth)); | |
| } | |
| // mipmap-completeness needs to be re-evaluated. | |
| completeness_dirty_ = true; | |
| Update(); | |
| UpdateCanRenderCondition(); | |
| } | |
| void Texture::SetLevelInfo(GLenum target, | |
| GLint level, | |
| GLenum internal_format, | |
| GLsizei width, | |
| GLsizei height, | |
| GLsizei depth, | |
| GLint border, | |
| GLenum format, | |
| GLenum type, | |
| const gfx::Rect& cleared_rect) { | |
| DCHECK_GE(level, 0); | |
| size_t face_index = GLES2Util::GLTargetToFaceIndex(target); | |
| DCHECK_LT(static_cast<size_t>(face_index), | |
| face_infos_.size()); | |
| DCHECK_LT(static_cast<size_t>(level), | |
| face_infos_[face_index].level_infos.size()); | |
| DCHECK_GE(width, 0); | |
| DCHECK_GE(height, 0); | |
| DCHECK_GE(depth, 0); | |
| Texture::LevelInfo& info = | |
| face_infos_[face_index].level_infos[level]; | |
| // Update counters only if any attributes have changed. Counters are | |
| // comparisons between the old and new values so it must be done before any | |
| // assignment has been done to the LevelInfo. | |
| if (info.target != target || info.internal_format != internal_format || | |
| info.width != width || info.height != height || info.depth != depth || | |
| info.format != format || info.type != type || info.internal_workaround) { | |
| if (level == base_level_) { | |
| // Calculate the mip level count. | |
| face_infos_[face_index].num_mip_levels = std::min( | |
| std::max(0, max_level_ - base_level_ + 1), | |
| TextureManager::ComputeMipMapCount(target_, width, height, depth)); | |
| // Update NPOT face count for the first level. | |
| bool prev_npot = TextureIsNPOT(info.width, info.height, info.depth); | |
| bool now_npot = TextureIsNPOT(width, height, depth); | |
| if (prev_npot != now_npot) | |
| num_npot_faces_ += now_npot ? 1 : -1; | |
| } | |
| // Signify that at least one of the mips has changed. | |
| completeness_dirty_ = true; | |
| } | |
| info.target = target; | |
| info.level = level; | |
| info.internal_format = internal_format; | |
| info.depth = depth; | |
| info.border = border; | |
| info.format = format; | |
| info.type = type; | |
| info.image = 0; | |
| info.stream_texture_image = 0; | |
| info.image_state = UNBOUND; | |
| info.internal_workaround = false; | |
| UpdateMipCleared(&info, width, height, cleared_rect); | |
| estimated_size_ -= info.estimated_size; | |
| GLES2Util::ComputeImageDataSizes( | |
| width, height, depth, format, type, 4, &info.estimated_size, NULL, NULL); | |
| estimated_size_ += info.estimated_size; | |
| max_level_set_ = std::max(max_level_set_, level); | |
| Update(); | |
| UpdateCleared(); | |
| UpdateCanRenderCondition(); | |
| UpdateHasImages(); | |
| if (IsAttachedToFramebuffer()) { | |
| // TODO(gman): If textures tracked which framebuffers they were attached to | |
| // we could just mark those framebuffers as not complete. | |
| IncAllFramebufferStateChangeCount(); | |
| } | |
| } | |
| void Texture::SetStreamTextureServiceId(GLuint service_id) { | |
| GLuint new_service_id = service_id ? service_id : owned_service_id_; | |
| // Take no action if this isn't an OES_EXTERNAL texture. | |
| if (target_ && target_ != GL_TEXTURE_EXTERNAL_OES) | |
| return; | |
| if (service_id_ != new_service_id) { | |
| service_id_ = new_service_id; | |
| IncrementManagerServiceIdGeneration(); | |
| if (gl::GLContext* context = gl::GLContext::GetCurrent()) { | |
| // It would be preferable to pass in the decoder, and ask it to do this | |
| // instead. However, there are several cases, such as TextureDefinition, | |
| // that show up without a clear context owner. So, instead, we use the | |
| // current state's state restorer. | |
| if (gl::GLStateRestorer* restorer = context->GetGLStateRestorer()) | |
| restorer->RestoreAllExternalTextureBindingsIfNeeded(); | |
| } | |
| } | |
| } | |
| void Texture::MarkLevelAsInternalWorkaround(GLenum target, GLint level) { | |
| DCHECK_GE(level, 0); | |
| size_t face_index = GLES2Util::GLTargetToFaceIndex(target); | |
| DCHECK_LT(static_cast<size_t>(face_index), | |
| face_infos_.size()); | |
| DCHECK_LT(static_cast<size_t>(level), | |
| face_infos_[face_index].level_infos.size()); | |
| Texture::LevelInfo& info = | |
| face_infos_[face_index].level_infos[level]; | |
| info.internal_workaround = true; | |
| completeness_dirty_ = true; | |
| Update(); | |
| UpdateCanRenderCondition(); | |
| } | |
| bool Texture::ValidForTexture( | |
| GLint target, | |
| GLint level, | |
| GLint xoffset, | |
| GLint yoffset, | |
| GLint zoffset, | |
| GLsizei width, | |
| GLsizei height, | |
| GLsizei depth) const { | |
| size_t face_index = GLES2Util::GLTargetToFaceIndex(target); | |
| if (level >= 0 && face_index < face_infos_.size() && | |
| static_cast<size_t>(level) < face_infos_[face_index].level_infos.size()) { | |
| const LevelInfo& info = face_infos_[face_index].level_infos[level]; | |
| int32_t max_x; | |
| int32_t max_y; | |
| int32_t max_z; | |
| return xoffset >= 0 && | |
| yoffset >= 0 && | |
| zoffset >= 0 && | |
| width >= 0 && | |
| height >= 0 && | |
| depth >= 0 && | |
| SafeAddInt32(xoffset, width, &max_x) && | |
| SafeAddInt32(yoffset, height, &max_y) && | |
| SafeAddInt32(zoffset, depth, &max_z) && | |
| max_x <= info.width && | |
| max_y <= info.height && | |
| max_z <= info.depth; | |
| } | |
| return false; | |
| } | |
| bool Texture::GetLevelSize( | |
| GLint target, GLint level, | |
| GLsizei* width, GLsizei* height, GLsizei* depth) const { | |
| DCHECK(width); | |
| DCHECK(height); | |
| size_t face_index = GLES2Util::GLTargetToFaceIndex(target); | |
| if (level >= 0 && face_index < face_infos_.size() && | |
| static_cast<size_t>(level) < face_infos_[face_index].level_infos.size()) { | |
| const LevelInfo& info = face_infos_[face_index].level_infos[level]; | |
| if (info.target != 0) { | |
| *width = info.width; | |
| *height = info.height; | |
| if (depth) | |
| *depth = info.depth; | |
| return true; | |
| } | |
| } | |
| return false; | |
| } | |
| bool Texture::GetLevelType( | |
| GLint target, GLint level, GLenum* type, GLenum* internal_format) const { | |
| DCHECK(type); | |
| DCHECK(internal_format); | |
| size_t face_index = GLES2Util::GLTargetToFaceIndex(target); | |
| if (level >= 0 && face_index < face_infos_.size() && | |
| static_cast<size_t>(level) < face_infos_[face_index].level_infos.size()) { | |
| const LevelInfo& info = face_infos_[face_index].level_infos[level]; | |
| if (info.target != 0) { | |
| *type = info.type; | |
| *internal_format = info.internal_format; | |
| return true; | |
| } | |
| } | |
| return false; | |
| } | |
| GLenum Texture::SetParameteri( | |
| const FeatureInfo* feature_info, GLenum pname, GLint param) { | |
| DCHECK(feature_info); | |
| if (target_ == GL_TEXTURE_EXTERNAL_OES || | |
| target_ == GL_TEXTURE_RECTANGLE_ARB) { | |
| if (pname == GL_TEXTURE_MIN_FILTER && | |
| (param != GL_NEAREST && param != GL_LINEAR)) | |
| return GL_INVALID_ENUM; | |
| if ((pname == GL_TEXTURE_WRAP_S || pname == GL_TEXTURE_WRAP_T) && | |
| param != GL_CLAMP_TO_EDGE) | |
| return GL_INVALID_ENUM; | |
| } | |
| switch (pname) { | |
| case GL_TEXTURE_MIN_LOD: | |
| case GL_TEXTURE_MAX_LOD: | |
| { | |
| GLfloat fparam = static_cast<GLfloat>(param); | |
| return SetParameterf(feature_info, pname, fparam); | |
| } | |
| case GL_TEXTURE_MIN_FILTER: | |
| if (!feature_info->validators()->texture_min_filter_mode.IsValid(param)) { | |
| return GL_INVALID_ENUM; | |
| } | |
| sampler_state_.min_filter = param; | |
| break; | |
| case GL_TEXTURE_MAG_FILTER: | |
| if (!feature_info->validators()->texture_mag_filter_mode.IsValid(param)) { | |
| return GL_INVALID_ENUM; | |
| } | |
| sampler_state_.mag_filter = param; | |
| break; | |
| case GL_TEXTURE_WRAP_R: | |
| if (!feature_info->validators()->texture_wrap_mode.IsValid(param)) { | |
| return GL_INVALID_ENUM; | |
| } | |
| sampler_state_.wrap_r = param; | |
| break; | |
| case GL_TEXTURE_WRAP_S: | |
| if (!feature_info->validators()->texture_wrap_mode.IsValid(param)) { | |
| return GL_INVALID_ENUM; | |
| } | |
| sampler_state_.wrap_s = param; | |
| break; | |
| case GL_TEXTURE_WRAP_T: | |
| if (!feature_info->validators()->texture_wrap_mode.IsValid(param)) { | |
| return GL_INVALID_ENUM; | |
| } | |
| sampler_state_.wrap_t = param; | |
| break; | |
| case GL_TEXTURE_COMPARE_FUNC: | |
| if (!feature_info->validators()->texture_compare_func.IsValid(param)) { | |
| return GL_INVALID_ENUM; | |
| } | |
| sampler_state_.compare_func = param; | |
| break; | |
| case GL_TEXTURE_COMPARE_MODE: | |
| if (!feature_info->validators()->texture_compare_mode.IsValid(param)) { | |
| return GL_INVALID_ENUM; | |
| } | |
| sampler_state_.compare_mode = param; | |
| break; | |
| case GL_TEXTURE_BASE_LEVEL: | |
| if (param < 0) { | |
| return GL_INVALID_VALUE; | |
| } | |
| UpdateBaseLevel(param); | |
| break; | |
| case GL_TEXTURE_MAX_LEVEL: | |
| if (param < 0) { | |
| return GL_INVALID_VALUE; | |
| } | |
| UpdateMaxLevel(param); | |
| break; | |
| case GL_TEXTURE_MAX_ANISOTROPY_EXT: | |
| if (param < 1) { | |
| return GL_INVALID_VALUE; | |
| } | |
| break; | |
| case GL_TEXTURE_USAGE_ANGLE: | |
| if (!feature_info->validators()->texture_usage.IsValid(param)) { | |
| return GL_INVALID_ENUM; | |
| } | |
| usage_ = param; | |
| break; | |
| case GL_TEXTURE_SWIZZLE_R: | |
| if (!feature_info->validators()->texture_swizzle.IsValid(param)) { | |
| return GL_INVALID_ENUM; | |
| } | |
| swizzle_r_ = param; | |
| break; | |
| case GL_TEXTURE_SWIZZLE_G: | |
| if (!feature_info->validators()->texture_swizzle.IsValid(param)) { | |
| return GL_INVALID_ENUM; | |
| } | |
| swizzle_g_ = param; | |
| break; | |
| case GL_TEXTURE_SWIZZLE_B: | |
| if (!feature_info->validators()->texture_swizzle.IsValid(param)) { | |
| return GL_INVALID_ENUM; | |
| } | |
| swizzle_b_ = param; | |
| break; | |
| case GL_TEXTURE_SWIZZLE_A: | |
| if (!feature_info->validators()->texture_swizzle.IsValid(param)) { | |
| return GL_INVALID_ENUM; | |
| } | |
| swizzle_a_ = param; | |
| break; | |
| case GL_TEXTURE_SRGB_DECODE_EXT: | |
| if (!feature_info->validators()->texture_srgb_decode_ext.IsValid(param)) { | |
| return GL_INVALID_ENUM; | |
| } | |
| break; | |
| case GL_TEXTURE_IMMUTABLE_FORMAT: | |
| case GL_TEXTURE_IMMUTABLE_LEVELS: | |
| return GL_INVALID_ENUM; | |
| default: | |
| NOTREACHED(); | |
| return GL_INVALID_ENUM; | |
| } | |
| Update(); | |
| UpdateCleared(); | |
| UpdateCanRenderCondition(); | |
| return GL_NO_ERROR; | |
| } | |
| GLenum Texture::SetParameterf( | |
| const FeatureInfo* feature_info, GLenum pname, GLfloat param) { | |
| switch (pname) { | |
| case GL_TEXTURE_MIN_FILTER: | |
| case GL_TEXTURE_MAG_FILTER: | |
| case GL_TEXTURE_WRAP_R: | |
| case GL_TEXTURE_WRAP_S: | |
| case GL_TEXTURE_WRAP_T: | |
| case GL_TEXTURE_COMPARE_FUNC: | |
| case GL_TEXTURE_COMPARE_MODE: | |
| case GL_TEXTURE_BASE_LEVEL: | |
| case GL_TEXTURE_MAX_LEVEL: | |
| case GL_TEXTURE_USAGE_ANGLE: | |
| case GL_TEXTURE_SWIZZLE_R: | |
| case GL_TEXTURE_SWIZZLE_G: | |
| case GL_TEXTURE_SWIZZLE_B: | |
| case GL_TEXTURE_SWIZZLE_A: { | |
| GLint iparam = static_cast<GLint>(std::round(param)); | |
| return SetParameteri(feature_info, pname, iparam); | |
| } | |
| case GL_TEXTURE_MIN_LOD: | |
| sampler_state_.min_lod = param; | |
| break; | |
| case GL_TEXTURE_MAX_LOD: | |
| sampler_state_.max_lod = param; | |
| break; | |
| case GL_TEXTURE_MAX_ANISOTROPY_EXT: | |
| if (param < 1.f) { | |
| return GL_INVALID_VALUE; | |
| } | |
| break; | |
| case GL_TEXTURE_IMMUTABLE_FORMAT: | |
| case GL_TEXTURE_IMMUTABLE_LEVELS: | |
| return GL_INVALID_ENUM; | |
| default: | |
| NOTREACHED(); | |
| return GL_INVALID_ENUM; | |
| } | |
| return GL_NO_ERROR; | |
| } | |
| void Texture::Update() { | |
| // Update npot status. | |
| // Assume GL_TEXTURE_EXTERNAL_OES textures are npot, all others | |
| npot_ = (target_ == GL_TEXTURE_EXTERNAL_OES) || (num_npot_faces_ > 0); | |
| if (!completeness_dirty_) | |
| return; | |
| if (face_infos_.empty() || | |
| static_cast<size_t>(base_level_) >= face_infos_[0].level_infos.size()) { | |
| texture_complete_ = false; | |
| cube_complete_ = false; | |
| return; | |
| } | |
| // Update texture_complete and cube_complete status. | |
| const Texture::FaceInfo& first_face = face_infos_[0]; | |
| const Texture::LevelInfo& first_level = first_face.level_infos[base_level_]; | |
| const GLsizei levels_needed = first_face.num_mip_levels; | |
| texture_complete_ = | |
| max_level_set_ >= (levels_needed - 1) && max_level_set_ >= 0; | |
| cube_complete_ = (face_infos_.size() == 6) && | |
| (first_level.width == first_level.height) && | |
| (first_level.width > 0); | |
| if (first_level.width == 0 || first_level.height == 0) { | |
| texture_complete_ = false; | |
| } | |
| bool texture_level0_complete = true; | |
| if (cube_complete_) { | |
| for (size_t ii = 0; ii < face_infos_.size(); ++ii) { | |
| const Texture::LevelInfo& face_base_level = | |
| face_infos_[ii].level_infos[base_level_]; | |
| if (face_base_level.internal_workaround || | |
| !TextureFaceComplete(first_level, | |
| ii, | |
| face_base_level.target, | |
| face_base_level.internal_format, | |
| face_base_level.width, | |
| face_base_level.height, | |
| face_base_level.depth, | |
| face_base_level.format, | |
| face_base_level.type)) { | |
| texture_level0_complete = false; | |
| break; | |
| } | |
| } | |
| } | |
| cube_complete_ &= texture_level0_complete; | |
| bool texture_mips_complete = true; | |
| if (texture_complete_) { | |
| for (size_t ii = 0; ii < face_infos_.size() && texture_mips_complete; | |
| ++ii) { | |
| const Texture::FaceInfo& face_info = face_infos_[ii]; | |
| const Texture::LevelInfo& base_level_info = | |
| face_info.level_infos[base_level_]; | |
| for (GLsizei jj = 1; jj < levels_needed; ++jj) { | |
| const Texture::LevelInfo& level_info = | |
| face_infos_[ii].level_infos[base_level_ + jj]; | |
| if (!TextureMipComplete(base_level_info, | |
| level_info.target, | |
| jj, // level - base_level_ | |
| level_info.internal_format, | |
| level_info.width, | |
| level_info.height, | |
| level_info.depth, | |
| level_info.format, | |
| level_info.type)) { | |
| texture_mips_complete = false; | |
| break; | |
| } | |
| } | |
| } | |
| } | |
| texture_complete_ &= texture_mips_complete; | |
| completeness_dirty_ = false; | |
| } | |
| bool Texture::ClearRenderableLevels(GLES2Decoder* decoder) { | |
| DCHECK(decoder); | |
| if (cleared_) { | |
| return true; | |
| } | |
| for (size_t ii = 0; ii < face_infos_.size(); ++ii) { | |
| const Texture::FaceInfo& face_info = face_infos_[ii]; | |
| for (GLint jj = base_level_; | |
| jj < base_level_ + face_info.num_mip_levels; ++jj) { | |
| const Texture::LevelInfo& info = face_info.level_infos[jj]; | |
| if (info.target != 0) { | |
| if (!ClearLevel(decoder, info.target, jj)) { | |
| return false; | |
| } | |
| } | |
| } | |
| } | |
| UpdateSafeToRenderFrom(true); | |
| return true; | |
| } | |
| void Texture::SetImmutable(bool immutable) { | |
| if (immutable_ == immutable) | |
| return; | |
| immutable_ = immutable; | |
| UpdateNumMipLevels(); | |
| } | |
| GLint Texture::GetImmutableLevels() const { | |
| if (!immutable_) | |
| return 0; | |
| GLint levels = 0; | |
| DCHECK(face_infos_.size() > 0); | |
| for (size_t ii = 0; ii < face_infos_[0].level_infos.size(); ++ii) { | |
| const Texture::LevelInfo& info = face_infos_[0].level_infos[ii]; | |
| if (info.target != 0) | |
| levels++; | |
| } | |
| return levels; | |
| } | |
| gfx::Rect Texture::GetLevelClearedRect(GLenum target, GLint level) const { | |
| size_t face_index = GLES2Util::GLTargetToFaceIndex(target); | |
| if (face_index >= face_infos_.size() || | |
| level >= static_cast<GLint>(face_infos_[face_index].level_infos.size())) { | |
| return gfx::Rect(); | |
| } | |
| const Texture::LevelInfo& info = face_infos_[face_index].level_infos[level]; | |
| return info.cleared_rect; | |
| } | |
| bool Texture::IsLevelCleared(GLenum target, GLint level) const { | |
| size_t face_index = GLES2Util::GLTargetToFaceIndex(target); | |
| if (face_index >= face_infos_.size() || | |
| level < 0 || | |
| level >= static_cast<GLint>(face_infos_[face_index].level_infos.size())) { | |
| return true; | |
| } | |
| const Texture::LevelInfo& info = face_infos_[face_index].level_infos[level]; | |
| return info.cleared_rect == gfx::Rect(info.width, info.height); | |
| } | |
| bool Texture::IsLevelPartiallyCleared(GLenum target, GLint level) const { | |
| size_t face_index = GLES2Util::GLTargetToFaceIndex(target); | |
| if (face_index >= face_infos_.size() || | |
| level < 0 || | |
| level >= static_cast<GLint>(face_infos_[face_index].level_infos.size())) { | |
| return false; | |
| } | |
| const Texture::LevelInfo& info = face_infos_[face_index].level_infos[level]; | |
| return (info.cleared_rect != gfx::Rect(info.width, info.height) && | |
| info.cleared_rect != gfx::Rect()); | |
| } | |
| void Texture::InitTextureMaxAnisotropyIfNeeded(GLenum target) { | |
| if (texture_max_anisotropy_initialized_) | |
| return; | |
| texture_max_anisotropy_initialized_ = true; | |
| GLfloat params[] = { 1.0f }; | |
| glTexParameterfv(target, GL_TEXTURE_MAX_ANISOTROPY_EXT, params); | |
| } | |
| bool Texture::ClearLevel( | |
| GLES2Decoder* decoder, GLenum target, GLint level) { | |
| DCHECK(decoder); | |
| size_t face_index = GLES2Util::GLTargetToFaceIndex(target); | |
| if (face_index >= face_infos_.size() || level < 0 || | |
| level >= static_cast<GLint>(face_infos_[face_index].level_infos.size())) { | |
| return true; | |
| } | |
| Texture::LevelInfo& info = face_infos_[face_index].level_infos[level]; | |
| DCHECK(target == info.target); | |
| if (info.target == 0 || | |
| info.cleared_rect == gfx::Rect(info.width, info.height) || | |
| info.width == 0 || info.height == 0 || info.depth == 0) { | |
| return true; | |
| } | |
| if (info.target == GL_TEXTURE_3D || info.target == GL_TEXTURE_2D_ARRAY) { | |
| // For 3D textures, we always clear the entire texture. | |
| DCHECK(info.cleared_rect == gfx::Rect()); | |
| bool cleared = decoder->ClearLevel3D( | |
| this, info.target, info.level, | |
| TextureManager::AdjustTexFormat(decoder->GetFeatureInfo(), info.format), | |
| info.type, info.width, info.height, info.depth); | |
| if (!cleared) | |
| return false; | |
| } else { | |
| if (decoder->IsCompressedTextureFormat(info.internal_format)) { | |
| // An uncleared level of a compressed texture can only occur when | |
| // allocating the texture with TexStorage2D. In this case the level | |
| // is cleared just before a call to CompressedTexSubImage2D, to avoid | |
| // having to clear a sub-rectangle of a compressed texture, which | |
| // would be problematic. | |
| DCHECK(IsImmutable()); | |
| DCHECK(info.cleared_rect == gfx::Rect()); | |
| bool cleared = decoder->ClearCompressedTextureLevel( | |
| this, info.target, info.level, info.internal_format, | |
| info.width, info.height); | |
| if (!cleared) | |
| return false; | |
| } else { | |
| // Clear all remaining sub regions. | |
| const int x[] = { | |
| 0, info.cleared_rect.x(), info.cleared_rect.right(), info.width}; | |
| const int y[] = { | |
| 0, info.cleared_rect.y(), info.cleared_rect.bottom(), info.height}; | |
| for (size_t j = 0; j < 3; ++j) { | |
| for (size_t i = 0; i < 3; ++i) { | |
| // Center of nine patch is already cleared. | |
| if (j == 1 && i == 1) | |
| continue; | |
| gfx::Rect rect(x[i], y[j], x[i + 1] - x[i], y[j + 1] - y[j]); | |
| if (rect.IsEmpty()) | |
| continue; | |
| // NOTE: It seems kind of gross to call back into the decoder for this | |
| // but only the decoder knows all the state (like unpack_alignment_) | |
| // that's needed to be able to call GL correctly. | |
| bool cleared = decoder->ClearLevel( | |
| this, info.target, info.level, | |
| TextureManager::AdjustTexFormat(decoder->GetFeatureInfo(), | |
| info.format), | |
| info.type, rect.x(), rect.y(), rect.width(), rect.height()); | |
| if (!cleared) | |
| return false; | |
| } | |
| } | |
| } | |
| } | |
| UpdateMipCleared(&info, info.width, info.height, | |
| gfx::Rect(info.width, info.height)); | |
| return true; | |
| } | |
| void Texture::SetLevelImageInternal(GLenum target, | |
| GLint level, | |
| gl::GLImage* image, | |
| GLStreamTextureImage* stream_texture_image, | |
| ImageState state) { | |
| DCHECK(!stream_texture_image || stream_texture_image == image); | |
| DCHECK_GE(level, 0); | |
| size_t face_index = GLES2Util::GLTargetToFaceIndex(target); | |
| DCHECK_LT(static_cast<size_t>(face_index), face_infos_.size()); | |
| DCHECK_LT(static_cast<size_t>(level), | |
| face_infos_[face_index].level_infos.size()); | |
| Texture::LevelInfo& info = face_infos_[face_index].level_infos[level]; | |
| DCHECK_EQ(info.target, target); | |
| DCHECK_EQ(info.level, level); | |
| info.image = image; | |
| info.stream_texture_image = stream_texture_image; | |
| info.image_state = state; | |
| UpdateCanRenderCondition(); | |
| UpdateHasImages(); | |
| UpdateEmulatingRGB(); | |
| } | |
| void Texture::SetLevelImage(GLenum target, | |
| GLint level, | |
| gl::GLImage* image, | |
| ImageState state) { | |
| SetStreamTextureServiceId(0); | |
| SetLevelImageInternal(target, level, image, nullptr, state); | |
| } | |
| void Texture::SetLevelStreamTextureImage(GLenum target, | |
| GLint level, | |
| GLStreamTextureImage* image, | |
| ImageState state, | |
| GLuint service_id) { | |
| SetStreamTextureServiceId(service_id); | |
| SetLevelImageInternal(target, level, image, image, state); | |
| } | |
| void Texture::SetLevelImageState(GLenum target, GLint level, ImageState state) { | |
| DCHECK_GE(level, 0); | |
| size_t face_index = GLES2Util::GLTargetToFaceIndex(target); | |
| DCHECK_LT(static_cast<size_t>(face_index), face_infos_.size()); | |
| DCHECK_LT(static_cast<size_t>(level), | |
| face_infos_[face_index].level_infos.size()); | |
| Texture::LevelInfo& info = face_infos_[face_index].level_infos[level]; | |
| DCHECK_EQ(info.target, target); | |
| DCHECK_EQ(info.level, level); | |
| info.image_state = state; | |
| } | |
| const Texture::LevelInfo* Texture::GetLevelInfo(GLint target, | |
| GLint level) const { | |
| if (target != GL_TEXTURE_2D && target != GL_TEXTURE_EXTERNAL_OES && | |
| target != GL_TEXTURE_RECTANGLE_ARB) { | |
| return NULL; | |
| } | |
| size_t face_index = GLES2Util::GLTargetToFaceIndex(target); | |
| if (level >= 0 && face_index < face_infos_.size() && | |
| static_cast<size_t>(level) < face_infos_[face_index].level_infos.size()) { | |
| const LevelInfo& info = face_infos_[face_index].level_infos[level]; | |
| if (info.target != 0) | |
| return &info; | |
| } | |
| return NULL; | |
| } | |
| gl::GLImage* Texture::GetLevelImage(GLint target, | |
| GLint level, | |
| ImageState* state) const { | |
| const LevelInfo* info = GetLevelInfo(target, level); | |
| if (!info) | |
| return nullptr; | |
| if (state) | |
| *state = info->image_state; | |
| return info->image.get(); | |
| } | |
| gl::GLImage* Texture::GetLevelImage(GLint target, GLint level) const { | |
| return GetLevelImage(target, level, nullptr); | |
| } | |
| GLStreamTextureImage* Texture::GetLevelStreamTextureImage(GLint target, | |
| GLint level) const { | |
| const LevelInfo* info = GetLevelInfo(target, level); | |
| if (!info) | |
| return nullptr; | |
| return info->stream_texture_image.get(); | |
| } | |
| void Texture::DumpLevelMemory(base::trace_event::ProcessMemoryDump* pmd, | |
| uint64_t client_tracing_id, | |
| const std::string& dump_name) const { | |
| for (uint32_t face_index = 0; face_index < face_infos_.size(); ++face_index) { | |
| const auto& level_infos = face_infos_[face_index].level_infos; | |
| for (uint32_t level_index = 0; level_index < level_infos.size(); | |
| ++level_index) { | |
| // Skip levels with no size. Textures will have empty levels for all | |
| // potential mip levels which are not in use. | |
| if (!level_infos[level_index].estimated_size) | |
| continue; | |
| // If a level has a GLImage, ask the GLImage to dump itself. | |
| // If a level does not have a GLImage bound to it, then dump the | |
| // texture allocation also as the storage is not provided by the | |
| // GLImage in that case. | |
| if (level_infos[level_index].image) { | |
| level_infos[level_index].image->OnMemoryDump( | |
| pmd, client_tracing_id, | |
| base::StringPrintf("%s/face_%d/level_%d", dump_name.c_str(), | |
| face_index, level_index)); | |
| } else { | |
| MemoryAllocatorDump* dump = pmd->CreateAllocatorDump(base::StringPrintf( | |
| "%s/face_%d/level_%d", dump_name.c_str(), face_index, level_index)); | |
| dump->AddScalar( | |
| MemoryAllocatorDump::kNameSize, MemoryAllocatorDump::kUnitsBytes, | |
| static_cast<uint64_t>(level_infos[level_index].estimated_size)); | |
| } | |
| } | |
| } | |
| } | |
| bool Texture::CanRenderTo(const FeatureInfo* feature_info, GLint level) const { | |
| if (target_ == GL_TEXTURE_EXTERNAL_OES || target_ == 0) | |
| return false; | |
| DCHECK_LT(0u, face_infos_.size()); | |
| // In GLES2, cube completeness is not required for framebuffer completeness. | |
| // However, it is required if command buffer is implemented on top of | |
| // recent OpenGL core versions or OpenGL ES 3.0+. Therefore, for consistency, | |
| // it is better to deviate from ES2 spec and require cube completeness all | |
| // the time. | |
| if (face_infos_.size() == 6 && !cube_complete()) | |
| return false; | |
| DCHECK(level >= 0 && | |
| level < static_cast<GLint>(face_infos_[0].level_infos.size())); | |
| if (level > base_level_ && !texture_complete()) { | |
| return false; | |
| } | |
| GLenum internal_format = face_infos_[0].level_infos[level].internal_format; | |
| bool color_renderable = ColorRenderable(feature_info, internal_format, | |
| immutable_); | |
| bool depth_renderable = feature_info->validators()-> | |
| texture_depth_renderable_internal_format.IsValid(internal_format); | |
| bool stencil_renderable = feature_info->validators()-> | |
| texture_stencil_renderable_internal_format.IsValid(internal_format); | |
| return (color_renderable || depth_renderable || stencil_renderable); | |
| } | |
| GLenum Texture::GetCompatibilitySwizzleForChannel(GLenum channel) { | |
| return GetSwizzleForChannel(channel, compatibility_swizzle_); | |
| } | |
| void Texture::SetCompatibilitySwizzle(const CompatibilitySwizzle* swizzle) { | |
| if (compatibility_swizzle_ == swizzle) | |
| return; | |
| compatibility_swizzle_ = swizzle; | |
| glTexParameteri(target_, GL_TEXTURE_SWIZZLE_R, | |
| GetSwizzleForChannel(swizzle_r_, swizzle)); | |
| glTexParameteri(target_, GL_TEXTURE_SWIZZLE_G, | |
| GetSwizzleForChannel(swizzle_g_, swizzle)); | |
| glTexParameteri(target_, GL_TEXTURE_SWIZZLE_B, | |
| GetSwizzleForChannel(swizzle_b_, swizzle)); | |
| glTexParameteri(target_, GL_TEXTURE_SWIZZLE_A, | |
| GetSwizzleForChannel(swizzle_a_, swizzle)); | |
| } | |
| void Texture::ApplyFormatWorkarounds(FeatureInfo* feature_info) { | |
| if (feature_info->gl_version_info().is_desktop_core_profile) { | |
| if (static_cast<size_t>(base_level_) >= face_infos_[0].level_infos.size()) | |
| return; | |
| const Texture::LevelInfo& info = face_infos_[0].level_infos[base_level_]; | |
| SetCompatibilitySwizzle(GetCompatibilitySwizzle(info.format)); | |
| } | |
| } | |
| bool Texture::EmulatingRGB() { | |
| return emulating_rgb_; | |
| } | |
| TextureRef::TextureRef(TextureManager* manager, | |
| GLuint client_id, | |
| Texture* texture) | |
| : manager_(manager), | |
| texture_(texture), | |
| client_id_(client_id), | |
| num_observers_(0), | |
| force_context_lost_(false) { | |
| DCHECK(manager_); | |
| DCHECK(texture_); | |
| texture_->AddTextureRef(this); | |
| manager_->StartTracking(this); | |
| } | |
| scoped_refptr<TextureRef> TextureRef::Create(TextureManager* manager, | |
| GLuint client_id, | |
| GLuint service_id) { | |
| return new TextureRef(manager, client_id, new Texture(service_id)); | |
| } | |
| TextureRef::~TextureRef() { | |
| manager_->StopTracking(this); | |
| texture_->RemoveTextureRef( | |
| this, force_context_lost_ ? false : manager_->have_context_); | |
| manager_ = NULL; | |
| } | |
| void TextureRef::ForceContextLost() { | |
| force_context_lost_ = true; | |
| } | |
| TextureManager::TextureManager(MemoryTracker* memory_tracker, | |
| FeatureInfo* feature_info, | |
| GLint max_texture_size, | |
| GLint max_cube_map_texture_size, | |
| GLint max_rectangle_texture_size, | |
| GLint max_3d_texture_size, | |
| GLint max_array_texture_layers, | |
| bool use_default_textures, | |
| ProgressReporter* progress_reporter, | |
| ServiceDiscardableManager* discardable_manager) | |
| : memory_type_tracker_(new MemoryTypeTracker(memory_tracker)), | |
| memory_tracker_(memory_tracker), | |
| feature_info_(feature_info), | |
| max_texture_size_(max_texture_size), | |
| max_cube_map_texture_size_(max_cube_map_texture_size), | |
| max_rectangle_texture_size_(max_rectangle_texture_size), | |
| max_3d_texture_size_(max_3d_texture_size), | |
| max_array_texture_layers_(max_array_texture_layers), | |
| max_levels_(ComputeMipMapCount(GL_TEXTURE_2D, | |
| max_texture_size, | |
| max_texture_size, | |
| 0)), | |
| max_cube_map_levels_(ComputeMipMapCount(GL_TEXTURE_CUBE_MAP, | |
| max_cube_map_texture_size, | |
| max_cube_map_texture_size, | |
| 0)), | |
| max_3d_levels_(ComputeMipMapCount(GL_TEXTURE_3D, | |
| max_3d_texture_size, | |
| max_3d_texture_size, | |
| max_3d_texture_size)), | |
| use_default_textures_(use_default_textures), | |
| num_unsafe_textures_(0), | |
| num_uncleared_mips_(0), | |
| num_images_(0), | |
| texture_count_(0), | |
| have_context_(true), | |
| current_service_id_generation_(0), | |
| progress_reporter_(progress_reporter), | |
| discardable_manager_(discardable_manager) { | |
| for (int ii = 0; ii < kNumDefaultTextures; ++ii) { | |
| black_texture_ids_[ii] = 0; | |
| } | |
| } | |
| void TextureManager::AddFramebufferManager( | |
| FramebufferManager* framebuffer_manager) { | |
| framebuffer_managers_.push_back(framebuffer_manager); | |
| } | |
| void TextureManager::RemoveFramebufferManager( | |
| FramebufferManager* framebuffer_manager) { | |
| for (unsigned int i = 0; i < framebuffer_managers_.size(); ++i) { | |
| if (framebuffer_managers_[i] == framebuffer_manager) { | |
| std::swap(framebuffer_managers_[i], framebuffer_managers_.back()); | |
| framebuffer_managers_.pop_back(); | |
| return; | |
| } | |
| } | |
| NOTREACHED(); | |
| } | |
| void TextureManager::Initialize() { | |
| // Reset PIXEL_UNPACK_BUFFER to avoid unrelated GL error on some GL drivers. | |
| if (feature_info_->gl_version_info().is_es3_capable) { | |
| glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); | |
| } | |
| // TODO(gman): The default textures have to be real textures, not the 0 | |
| // texture because we simulate non shared resources on top of shared | |
| // resources and all contexts that share resource share the same default | |
| // texture. | |
| default_textures_[kTexture2D] = CreateDefaultAndBlackTextures( | |
| GL_TEXTURE_2D, &black_texture_ids_[kTexture2D]); | |
| default_textures_[kCubeMap] = CreateDefaultAndBlackTextures( | |
| GL_TEXTURE_CUBE_MAP, &black_texture_ids_[kCubeMap]); | |
| if (feature_info_->IsWebGL2OrES3Context()) { | |
| default_textures_[kTexture3D] = CreateDefaultAndBlackTextures( | |
| GL_TEXTURE_3D, &black_texture_ids_[kTexture3D]); | |
| default_textures_[kTexture2DArray] = CreateDefaultAndBlackTextures( | |
| GL_TEXTURE_2D_ARRAY, &black_texture_ids_[kTexture2DArray]); | |
| } | |
| if (feature_info_->feature_flags().oes_egl_image_external || | |
| feature_info_->feature_flags().nv_egl_stream_consumer_external) { | |
| default_textures_[kExternalOES] = CreateDefaultAndBlackTextures( | |
| GL_TEXTURE_EXTERNAL_OES, &black_texture_ids_[kExternalOES]); | |
| } | |
| if (feature_info_->feature_flags().arb_texture_rectangle) { | |
| default_textures_[kRectangleARB] = CreateDefaultAndBlackTextures( | |
| GL_TEXTURE_RECTANGLE_ARB, &black_texture_ids_[kRectangleARB]); | |
| } | |
| // 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::TextureManager", base::ThreadTaskRunnerHandle::Get()); | |
| } | |
| } | |
| scoped_refptr<TextureRef> | |
| TextureManager::CreateDefaultAndBlackTextures( | |
| GLenum target, | |
| GLuint* black_texture) { | |
| static uint8_t black[] = {0, 0, 0, 255}; | |
| // Sampling a texture not associated with any EGLImage sibling will return | |
| // black values according to the spec. | |
| bool needs_initialization = (target != GL_TEXTURE_EXTERNAL_OES); | |
| bool needs_faces = (target == GL_TEXTURE_CUBE_MAP); | |
| bool is_3d_or_2d_array_target = (target == GL_TEXTURE_3D || | |
| target == GL_TEXTURE_2D_ARRAY); | |
| // Make default textures and texture for replacing non-renderable textures. | |
| GLuint ids[2]; | |
| const int num_ids = use_default_textures_ ? 2 : 1; | |
| glGenTextures(num_ids, ids); | |
| for (int ii = 0; ii < num_ids; ++ii) { | |
| glBindTexture(target, ids[ii]); | |
| if (needs_initialization) { | |
| if (needs_faces) { | |
| for (int jj = 0; jj < GLES2Util::kNumFaces; ++jj) { | |
| glTexImage2D(GLES2Util::IndexToGLFaceTarget(jj), 0, GL_RGBA, 1, 1, 0, | |
| GL_RGBA, GL_UNSIGNED_BYTE, black); | |
| } | |
| } else { | |
| if (is_3d_or_2d_array_target) { | |
| glTexImage3D(target, 0, GL_RGBA, 1, 1, 1, 0, GL_RGBA, | |
| GL_UNSIGNED_BYTE, black); | |
| } else { | |
| glTexImage2D(target, 0, GL_RGBA, 1, 1, 0, GL_RGBA, | |
| GL_UNSIGNED_BYTE, black); | |
| } | |
| } | |
| } | |
| } | |
| glBindTexture(target, 0); | |
| scoped_refptr<TextureRef> default_texture; | |
| if (use_default_textures_) { | |
| default_texture = TextureRef::Create(this, 0, ids[1]); | |
| SetTarget(default_texture.get(), target); | |
| if (needs_faces) { | |
| for (int ii = 0; ii < GLES2Util::kNumFaces; ++ii) { | |
| SetLevelInfo(default_texture.get(), GLES2Util::IndexToGLFaceTarget(ii), | |
| 0, GL_RGBA, 1, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, | |
| gfx::Rect(1, 1)); | |
| } | |
| } else { | |
| if (needs_initialization) { | |
| SetLevelInfo(default_texture.get(), GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 1, | |
| 0, GL_RGBA, GL_UNSIGNED_BYTE, gfx::Rect(1, 1)); | |
| } else { | |
| SetLevelInfo(default_texture.get(), GL_TEXTURE_EXTERNAL_OES, 0, GL_RGBA, | |
| 1, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, gfx::Rect(1, 1)); | |
| } | |
| } | |
| } | |
| *black_texture = ids[0]; | |
| return default_texture; | |
| } | |
| bool TextureManager::ValidForTarget( | |
| GLenum target, GLint level, GLsizei width, GLsizei height, GLsizei depth) { | |
| if (level < 0 || level >= MaxLevelsForTarget(target)) | |
| return false; | |
| GLsizei max_size = MaxSizeForTarget(target) >> level; | |
| GLsizei max_depth = | |
| (target == GL_TEXTURE_2D_ARRAY ? max_array_texture_layers() : max_size); | |
| return width >= 0 && | |
| height >= 0 && | |
| depth >= 0 && | |
| width <= max_size && | |
| height <= max_size && | |
| depth <= max_depth && | |
| (level == 0 || feature_info_->feature_flags().npot_ok || | |
| (!GLES2Util::IsNPOT(width) && | |
| !GLES2Util::IsNPOT(height) && | |
| !GLES2Util::IsNPOT(depth))) && | |
| (target != GL_TEXTURE_CUBE_MAP || (width == height && depth == 1)) && | |
| (target != GL_TEXTURE_2D || (depth == 1)); | |
| } | |
| void TextureManager::SetTarget(TextureRef* ref, GLenum target) { | |
| DCHECK(ref); | |
| ref->texture()->SetTarget(target, MaxLevelsForTarget(target)); | |
| } | |
| void TextureManager::SetLevelClearedRect(TextureRef* ref, | |
| GLenum target, | |
| GLint level, | |
| const gfx::Rect& cleared_rect) { | |
| DCHECK(ref); | |
| ref->texture()->SetLevelClearedRect(target, level, cleared_rect); | |
| } | |
| void TextureManager::SetLevelCleared(TextureRef* ref, | |
| GLenum target, | |
| GLint level, | |
| bool cleared) { | |
| DCHECK(ref); | |
| ref->texture()->SetLevelCleared(target, level, cleared); | |
| } | |
| bool TextureManager::ClearRenderableLevels( | |
| GLES2Decoder* decoder, TextureRef* ref) { | |
| DCHECK(ref); | |
| return ref->texture()->ClearRenderableLevels(decoder); | |
| } | |
| bool TextureManager::ClearTextureLevel( | |
| GLES2Decoder* decoder, TextureRef* ref, | |
| GLenum target, GLint level) { | |
| DCHECK(ref); | |
| Texture* texture = ref->texture(); | |
| if (texture->num_uncleared_mips() == 0) { | |
| return true; | |
| } | |
| bool result = texture->ClearLevel(decoder, target, level); | |
| texture->UpdateCleared(); | |
| return result; | |
| } | |
| void TextureManager::SetLevelInfo(TextureRef* ref, | |
| GLenum target, | |
| GLint level, | |
| GLenum internal_format, | |
| GLsizei width, | |
| GLsizei height, | |
| GLsizei depth, | |
| GLint border, | |
| GLenum format, | |
| GLenum type, | |
| const gfx::Rect& cleared_rect) { | |
| DCHECK(gfx::Rect(width, height).Contains(cleared_rect)); | |
| DCHECK(ref); | |
| Texture* texture = ref->texture(); | |
| texture->GetMemTracker()->TrackMemFree(texture->estimated_size()); | |
| texture->SetLevelInfo(target, level, internal_format, width, height, depth, | |
| border, format, type, cleared_rect); | |
| texture->GetMemTracker()->TrackMemAlloc(texture->estimated_size()); | |
| discardable_manager_->OnTextureSizeChanged(ref->client_id(), this, | |
| texture->estimated_size()); | |
| } | |
| Texture* TextureManager::Produce(TextureRef* ref) { | |
| DCHECK(ref); | |
| return ref->texture(); | |
| } | |
| TextureRef* TextureManager::Consume( | |
| GLuint client_id, | |
| Texture* texture) { | |
| DCHECK(client_id); | |
| scoped_refptr<TextureRef> ref(new TextureRef(this, client_id, texture)); | |
| bool result = textures_.insert(std::make_pair(client_id, ref)).second; | |
| DCHECK(result); | |
| return ref.get(); | |
| } | |
| void TextureManager::SetParameteri( | |
| const char* function_name, ErrorState* error_state, | |
| TextureRef* ref, GLenum pname, GLint param) { | |
| DCHECK(error_state); | |
| DCHECK(ref); | |
| Texture* texture = ref->texture(); | |
| GLenum result = texture->SetParameteri(feature_info_.get(), pname, param); | |
| if (result != GL_NO_ERROR) { | |
| if (result == GL_INVALID_ENUM) { | |
| ERRORSTATE_SET_GL_ERROR_INVALID_ENUM( | |
| error_state, function_name, param, "param"); | |
| } else { | |
| ERRORSTATE_SET_GL_ERROR_INVALID_PARAMI( | |
| error_state, result, function_name, pname, param); | |
| } | |
| } else { | |
| switch (pname) { | |
| case GL_TEXTURE_SWIZZLE_R: | |
| case GL_TEXTURE_SWIZZLE_G: | |
| case GL_TEXTURE_SWIZZLE_B: | |
| case GL_TEXTURE_SWIZZLE_A: | |
| glTexParameteri(texture->target(), pname, | |
| texture->GetCompatibilitySwizzleForChannel(param)); | |
| break; | |
| default: | |
| glTexParameteri(texture->target(), pname, param); | |
| break; | |
| } | |
| } | |
| } | |
| void TextureManager::SetParameterf( | |
| const char* function_name, ErrorState* error_state, | |
| TextureRef* ref, GLenum pname, GLfloat param) { | |
| DCHECK(error_state); | |
| DCHECK(ref); | |
| Texture* texture = ref->texture(); | |
| GLenum result = texture->SetParameterf(feature_info_.get(), pname, param); | |
| if (result != GL_NO_ERROR) { | |
| if (result == GL_INVALID_ENUM) { | |
| ERRORSTATE_SET_GL_ERROR_INVALID_ENUM( | |
| error_state, function_name, pname, "pname"); | |
| } else { | |
| ERRORSTATE_SET_GL_ERROR_INVALID_PARAMF( | |
| error_state, result, function_name, pname, param); | |
| } | |
| } else { | |
| glTexParameterf(texture->target(), pname, param); | |
| } | |
| } | |
| void TextureManager::MarkMipmapsGenerated(TextureRef* ref) { | |
| DCHECK(ref); | |
| Texture* texture = ref->texture(); | |
| texture->GetMemTracker()->TrackMemFree(texture->estimated_size()); | |
| texture->MarkMipmapsGenerated(); | |
| texture->GetMemTracker()->TrackMemAlloc(texture->estimated_size()); | |
| } | |
| TextureRef* TextureManager::CreateTexture( | |
| GLuint client_id, GLuint service_id) { | |
| DCHECK_NE(0u, service_id); | |
| scoped_refptr<TextureRef> ref(TextureRef::Create( | |
| this, client_id, service_id)); | |
| std::pair<TextureMap::iterator, bool> result = | |
| textures_.insert(std::make_pair(client_id, ref)); | |
| DCHECK(result.second); | |
| return ref.get(); | |
| } | |
| TextureRef* TextureManager::GetTexture( | |
| GLuint client_id) const { | |
| TextureMap::const_iterator it = textures_.find(client_id); | |
| return it != textures_.end() ? it->second.get() : NULL; | |
| } | |
| scoped_refptr<TextureRef> TextureManager::TakeTexture(GLuint client_id) { | |
| auto it = textures_.find(client_id); | |
| if (it == textures_.end()) | |
| return nullptr; | |
| scoped_refptr<TextureRef> ref = it->second; | |
| textures_.erase(it); | |
| return ref; | |
| } | |
| void TextureManager::ReturnTexture(scoped_refptr<TextureRef> texture_ref) { | |
| GLuint client_id = texture_ref->client_id(); | |
| // If we've generated a replacement texture due to "bind generates resource", | |
| // behavior, just delete the resource being returned. | |
| TextureMap::iterator it = textures_.find(client_id); | |
| if (it != textures_.end()) { | |
| // Reset the client id so it doesn't interfere with the generated resource. | |
| texture_ref->reset_client_id(); | |
| return; | |
| } | |
| textures_.emplace(client_id, std::move(texture_ref)); | |
| } | |
| void TextureManager::RemoveTexture(GLuint client_id) { | |
| TextureMap::iterator it = textures_.find(client_id); | |
| if (it != textures_.end()) { | |
| discardable_manager_->OnTextureDeleted(client_id, this); | |
| it->second->reset_client_id(); | |
| textures_.erase(it); | |
| } | |
| } | |
| void TextureManager::StartTracking(TextureRef* ref) { | |
| Texture* texture = ref->texture(); | |
| ++texture_count_; | |
| num_uncleared_mips_ += texture->num_uncleared_mips(); | |
| if (!texture->SafeToRenderFrom()) | |
| ++num_unsafe_textures_; | |
| if (texture->HasImages()) | |
| ++num_images_; | |
| } | |
| void TextureManager::StopTracking(TextureRef* ref) { | |
| if (ref->num_observers()) { | |
| for (unsigned int i = 0; i < destruction_observers_.size(); i++) { | |
| destruction_observers_[i]->OnTextureRefDestroying(ref); | |
| } | |
| DCHECK_EQ(ref->num_observers(), 0); | |
| } | |
| Texture* texture = ref->texture(); | |
| --texture_count_; | |
| if (texture->HasImages()) { | |
| DCHECK_NE(0, num_images_); | |
| --num_images_; | |
| } | |
| if (!texture->SafeToRenderFrom()) { | |
| DCHECK_NE(0, num_unsafe_textures_); | |
| --num_unsafe_textures_; | |
| } | |
| num_uncleared_mips_ -= texture->num_uncleared_mips(); | |
| DCHECK_GE(num_uncleared_mips_, 0); | |
| if (ref->client_id()) | |
| discardable_manager_->OnTextureDeleted(ref->client_id(), this); | |
| } | |
| MemoryTypeTracker* TextureManager::GetMemTracker() { | |
| return memory_type_tracker_.get(); | |
| } | |
| Texture* TextureManager::GetTextureForServiceId(GLuint service_id) const { | |
| // This doesn't need to be fast. It's only used during slow queries. | |
| for (TextureMap::const_iterator it = textures_.begin(); | |
| it != textures_.end(); ++it) { | |
| Texture* texture = it->second->texture(); | |
| if (texture->service_id() == service_id) | |
| return texture; | |
| } | |
| return NULL; | |
| } | |
| GLsizei TextureManager::ComputeMipMapCount(GLenum target, | |
| GLsizei width, | |
| GLsizei height, | |
| GLsizei depth) { | |
| switch (target) { | |
| case GL_TEXTURE_EXTERNAL_OES: | |
| case GL_TEXTURE_RECTANGLE_ARB: | |
| return 1; | |
| case GL_TEXTURE_3D: | |
| return 1 + | |
| base::bits::Log2Floor(std::max(std::max(width, height), depth)); | |
| default: | |
| return 1 + | |
| base::bits::Log2Floor(std::max(width, height)); | |
| } | |
| } | |
| void TextureManager::SetLevelImage(TextureRef* ref, | |
| GLenum target, | |
| GLint level, | |
| gl::GLImage* image, | |
| Texture::ImageState state) { | |
| DCHECK(ref); | |
| ref->texture()->SetLevelImage(target, level, image, state); | |
| } | |
| void TextureManager::SetLevelStreamTextureImage(TextureRef* ref, | |
| GLenum target, | |
| GLint level, | |
| GLStreamTextureImage* image, | |
| Texture::ImageState state, | |
| GLuint service_id) { | |
| DCHECK(ref); | |
| ref->texture()->SetLevelStreamTextureImage(target, level, image, state, | |
| service_id); | |
| } | |
| void TextureManager::SetLevelImageState(TextureRef* ref, | |
| GLenum target, | |
| GLint level, | |
| Texture::ImageState state) { | |
| DCHECK(ref); | |
| ref->texture()->SetLevelImageState(target, level, state); | |
| } | |
| size_t TextureManager::GetSignatureSize() const { | |
| return sizeof(TextureTag) + sizeof(TextureSignature); | |
| } | |
| void TextureManager::AddToSignature( | |
| TextureRef* ref, | |
| GLenum target, | |
| GLint level, | |
| std::string* signature) const { | |
| ref->texture()->AddToSignature(feature_info_.get(), target, level, signature); | |
| } | |
| void TextureManager::UpdateSafeToRenderFrom(int delta) { | |
| num_unsafe_textures_ += delta; | |
| DCHECK_GE(num_unsafe_textures_, 0); | |
| } | |
| void TextureManager::UpdateUnclearedMips(int delta) { | |
| num_uncleared_mips_ += delta; | |
| DCHECK_GE(num_uncleared_mips_, 0); | |
| } | |
| void TextureManager::UpdateNumImages(int delta) { | |
| num_images_ += delta; | |
| DCHECK_GE(num_images_, 0); | |
| } | |
| void TextureManager::IncFramebufferStateChangeCount() { | |
| for (unsigned int i = 0; i < framebuffer_managers_.size(); ++i) { | |
| framebuffer_managers_[i]->IncFramebufferStateChangeCount(); | |
| } | |
| } | |
| bool TextureManager::ValidateTextureParameters( | |
| ErrorState* error_state, const char* function_name, bool tex_image_call, | |
| GLenum format, GLenum type, GLint internal_format, GLint level) { | |
| const Validators* validators = feature_info_->validators(); | |
| if (!validators->texture_format.IsValid(format)) { | |
| ERRORSTATE_SET_GL_ERROR_INVALID_ENUM( | |
| error_state, function_name, format, "format"); | |
| return false; | |
| } | |
| if (!validators->pixel_type.IsValid(type)) { | |
| ERRORSTATE_SET_GL_ERROR_INVALID_ENUM( | |
| error_state, function_name, type, "type"); | |
| return false; | |
| } | |
| // For TexSubImage calls, internal_format isn't part of the parameters. | |
| // So the validation is not necessary for TexSubImage. | |
| if (tex_image_call && | |
| !validators->texture_internal_format.IsValid(internal_format)) { | |
| std::string msg = std::string("invalid internal_format ") + | |
| GLES2Util::GetStringEnum(internal_format); | |
| ERRORSTATE_SET_GL_ERROR(error_state, GL_INVALID_VALUE, function_name, | |
| msg.c_str()); | |
| return false; | |
| } | |
| if (!g_format_type_validator.Get().IsValid(feature_info_->context_type(), | |
| internal_format, format, type)) { | |
| std::string msg = std::string( | |
| "invalid internalformat/format/type combination ") + | |
| GLES2Util::GetStringEnum(internal_format) + std::string("/") + | |
| GLES2Util::GetStringEnum(format) + std::string("/") + | |
| GLES2Util::GetStringEnum(type); | |
| ERRORSTATE_SET_GL_ERROR(error_state, GL_INVALID_OPERATION, function_name, | |
| msg.c_str()); | |
| return false; | |
| } | |
| if (!feature_info_->IsWebGL2OrES3Context()) { | |
| uint32_t channels = GLES2Util::GetChannelsForFormat(format); | |
| if ((channels & (GLES2Util::kDepth | GLES2Util::kStencil)) != 0 && level) { | |
| ERRORSTATE_SET_GL_ERROR( | |
| error_state, GL_INVALID_OPERATION, function_name, | |
| (std::string("invalid format ") + GLES2Util::GetStringEnum(format) + | |
| " for level != 0").c_str()); | |
| return false; | |
| } | |
| } | |
| return true; | |
| } | |
| // Gets the texture id for a given target. | |
| TextureRef* TextureManager::GetTextureInfoForTarget( | |
| ContextState* state, GLenum target) { | |
| TextureUnit& unit = state->texture_units[state->active_texture_unit]; | |
| TextureRef* texture = NULL; | |
| switch (target) { | |
| case GL_TEXTURE_2D: | |
| texture = unit.bound_texture_2d.get(); | |
| break; | |
| case GL_TEXTURE_CUBE_MAP: | |
| case GL_TEXTURE_CUBE_MAP_POSITIVE_X: | |
| case GL_TEXTURE_CUBE_MAP_NEGATIVE_X: | |
| case GL_TEXTURE_CUBE_MAP_POSITIVE_Y: | |
| case GL_TEXTURE_CUBE_MAP_NEGATIVE_Y: | |
| case GL_TEXTURE_CUBE_MAP_POSITIVE_Z: | |
| case GL_TEXTURE_CUBE_MAP_NEGATIVE_Z: | |
| texture = unit.bound_texture_cube_map.get(); | |
| break; | |
| case GL_TEXTURE_EXTERNAL_OES: | |
| texture = unit.bound_texture_external_oes.get(); | |
| break; | |
| case GL_TEXTURE_RECTANGLE_ARB: | |
| texture = unit.bound_texture_rectangle_arb.get(); | |
| break; | |
| case GL_TEXTURE_3D: | |
| texture = unit.bound_texture_3d.get(); | |
| break; | |
| case GL_TEXTURE_2D_ARRAY: | |
| texture = unit.bound_texture_2d_array.get(); | |
| break; | |
| default: | |
| NOTREACHED(); | |
| return NULL; | |
| } | |
| return texture; | |
| } | |
| TextureRef* TextureManager::GetTextureInfoForTargetUnlessDefault( | |
| ContextState* state, GLenum target) { | |
| TextureRef* texture = GetTextureInfoForTarget(state, target); | |
| if (!texture) | |
| return NULL; | |
| if (texture == GetDefaultTextureInfo(target)) | |
| return NULL; | |
| return texture; | |
| } | |
| bool TextureManager::ValidateTexImage( | |
| ContextState* state, | |
| const char* function_name, | |
| const DoTexImageArguments& args, | |
| TextureRef** texture_ref) { | |
| ErrorState* error_state = state->GetErrorState(); | |
| const Validators* validators = feature_info_->validators(); | |
| if (((args.command_type == DoTexImageArguments::kTexImage2D) && | |
| !validators->texture_target.IsValid(args.target)) || | |
| ((args.command_type == DoTexImageArguments::kTexImage3D) && | |
| !validators->texture_3_d_target.IsValid(args.target))) { | |
| ERRORSTATE_SET_GL_ERROR_INVALID_ENUM( | |
| error_state, function_name, args.target, "target"); | |
| return false; | |
| } | |
| // TODO(ccameron): Add a separate texture from |texture_target| for | |
| // [Compressed]Tex[Sub]Image2D and related functions. | |
| // http://crbug.com/536854 | |
| if (args.target == GL_TEXTURE_RECTANGLE_ARB) { | |
| ERRORSTATE_SET_GL_ERROR_INVALID_ENUM( | |
| error_state, function_name, args.target, "target"); | |
| return false; | |
| } | |
| if (feature_info_->IsWebGL1OrES2Context()) { | |
| switch (args.format) { | |
| case GL_DEPTH_COMPONENT: | |
| case GL_DEPTH_STENCIL: | |
| if (args.target != GL_TEXTURE_2D) { | |
| ERRORSTATE_SET_GL_ERROR( | |
| error_state, GL_INVALID_OPERATION, function_name, | |
| "invalid target for depth/stencil textures"); | |
| return false; | |
| } | |
| break; | |
| default: | |
| break; | |
| } | |
| } | |
| if (!ValidateTextureParameters( | |
| error_state, function_name, true, args.format, args.type, | |
| args.internal_format, args.level)) { | |
| return false; | |
| } | |
| if (!ValidForTarget(args.target, args.level, | |
| args.width, args.height, args.depth) || | |
| args.border != 0) { | |
| ERRORSTATE_SET_GL_ERROR( | |
| error_state, GL_INVALID_VALUE, function_name, | |
| "dimensions out of range"); | |
| return false; | |
| } | |
| if ((GLES2Util::GetChannelsForFormat(args.format) & | |
| (GLES2Util::kDepth | GLES2Util::kStencil)) != 0 && args.pixels | |
| && !feature_info_->IsWebGL2OrES3Context()) { | |
| ERRORSTATE_SET_GL_ERROR( | |
| error_state, GL_INVALID_OPERATION, | |
| function_name, "can not supply data for depth or stencil textures"); | |
| return false; | |
| } | |
| TextureRef* local_texture_ref = GetTextureInfoForTarget(state, args.target); | |
| if (!local_texture_ref) { | |
| ERRORSTATE_SET_GL_ERROR( | |
| error_state, GL_INVALID_OPERATION, function_name, | |
| "unknown texture for target"); | |
| return false; | |
| } | |
| if (local_texture_ref->texture()->IsImmutable()) { | |
| ERRORSTATE_SET_GL_ERROR( | |
| error_state, GL_INVALID_OPERATION, function_name, | |
| "texture is immutable"); | |
| return false; | |
| } | |
| Buffer* buffer = state->bound_pixel_unpack_buffer.get(); | |
| if (buffer) { | |
| if (buffer->GetMappedRange()) { | |
| ERRORSTATE_SET_GL_ERROR( | |
| error_state, GL_INVALID_OPERATION, function_name, | |
| "pixel unpack buffer should not be mapped to client memory"); | |
| return false; | |
| } | |
| base::CheckedNumeric<uint32_t> size = args.pixels_size; | |
| GLuint offset = ToGLuint(args.pixels); | |
| size += offset; | |
| if (!size.IsValid()) { | |
| ERRORSTATE_SET_GL_ERROR( | |
| error_state, GL_INVALID_VALUE, function_name, | |
| "size + offset overflow"); | |
| return false; | |
| } | |
| uint32_t buffer_size = static_cast<uint32_t>(buffer->size()); | |
| if (buffer_size < size.ValueOrDefault(0)) { | |
| ERRORSTATE_SET_GL_ERROR( | |
| error_state, GL_INVALID_OPERATION, function_name, | |
| "pixel unpack buffer is not large enough"); | |
| return false; | |
| } | |
| size_t type_size = GLES2Util::GetGLTypeSizeForTextures(args.type); | |
| DCHECK_LT(0u, type_size); | |
| if (offset % type_size != 0) { | |
| ERRORSTATE_SET_GL_ERROR( | |
| error_state, GL_INVALID_OPERATION, function_name, | |
| "offset is not evenly divisible by elements"); | |
| return false; | |
| } | |
| } | |
| if (!memory_type_tracker_->EnsureGPUMemoryAvailable(args.pixels_size)) { | |
| ERRORSTATE_SET_GL_ERROR(error_state, GL_OUT_OF_MEMORY, function_name, | |
| "out of memory"); | |
| return false; | |
| } | |
| // Write the TextureReference since this is valid. | |
| *texture_ref = local_texture_ref; | |
| return true; | |
| } | |
| void TextureManager::DoCubeMapWorkaround( | |
| DecoderTextureState* texture_state, | |
| ContextState* state, | |
| DecoderFramebufferState* framebuffer_state, | |
| TextureRef* texture_ref, | |
| const char* function_name, | |
| const DoTexImageArguments& args) { | |
| // This workaround code does not work with an unpack buffer bound. | |
| ScopedResetPixelUnpackBuffer scoped_reset_pbo(state); | |
| std::vector<GLenum> undefined_faces; | |
| Texture* texture = texture_ref->texture(); | |
| if (texture_state->force_cube_complete || | |
| texture_state->force_int_or_srgb_cube_texture_complete) { | |
| int width = 0; | |
| int height = 0; | |
| for (unsigned i = 0; i < 6; i++) { | |
| GLenum target = static_cast<GLenum>(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i); | |
| bool defined = texture->GetLevelSize( | |
| target, args.level, &width, &height, nullptr); | |
| if (!defined && target != args.target) | |
| undefined_faces.push_back(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i); | |
| } | |
| } else { | |
| DCHECK(args.target != GL_TEXTURE_CUBE_MAP_POSITIVE_X); | |
| int width = 0; | |
| int height = 0; | |
| if (!texture->GetLevelSize(GL_TEXTURE_CUBE_MAP_POSITIVE_X, args.level, | |
| &width, &height, nullptr)) { | |
| undefined_faces.push_back(GL_TEXTURE_CUBE_MAP_POSITIVE_X); | |
| } | |
| } | |
| if (!memory_type_tracker_->EnsureGPUMemoryAvailable( | |
| (undefined_faces.size() + 1) * args.pixels_size)) { | |
| ERRORSTATE_SET_GL_ERROR(state->GetErrorState(), GL_OUT_OF_MEMORY, | |
| function_name, "out of memory"); | |
| return; | |
| } | |
| DoTexImageArguments new_args = args; | |
| std::unique_ptr<char[]> zero(new char[args.pixels_size]); | |
| memset(zero.get(), 0, args.pixels_size); | |
| for (GLenum face : undefined_faces) { | |
| new_args.target = face; | |
| new_args.pixels = zero.get(); | |
| DoTexImage(texture_state, state, framebuffer_state, | |
| function_name, texture_ref, new_args); | |
| texture->MarkLevelAsInternalWorkaround(face, args.level); | |
| } | |
| } | |
| void TextureManager::ValidateAndDoTexImage( | |
| DecoderTextureState* texture_state, | |
| ContextState* state, | |
| DecoderFramebufferState* framebuffer_state, | |
| const char* function_name, | |
| const DoTexImageArguments& args) { | |
| TextureRef* texture_ref; | |
| if (!ValidateTexImage(state, function_name, args, &texture_ref)) { | |
| return; | |
| } | |
| Buffer* buffer = state->bound_pixel_unpack_buffer.get(); | |
| // ValidateTexImage is passed already. | |
| Texture* texture = texture_ref->texture(); | |
| bool need_cube_map_workaround = | |
| !feature_info_->IsWebGL2OrES3Context() && | |
| texture->target() == GL_TEXTURE_CUBE_MAP && | |
| (texture_state->force_cube_complete || | |
| (texture_state->force_cube_map_positive_x_allocation && | |
| args.target != GL_TEXTURE_CUBE_MAP_POSITIVE_X)); | |
| // Force integer or srgb cube map texture complete, see crbug.com/712117. | |
| need_cube_map_workaround = | |
| need_cube_map_workaround || | |
| (texture->target() == GL_TEXTURE_CUBE_MAP && | |
| texture_state->force_int_or_srgb_cube_texture_complete && | |
| (GLES2Util::IsIntegerFormat(args.internal_format) || | |
| GLES2Util::GetColorEncodingFromInternalFormat(args.internal_format) == | |
| GL_SRGB)); | |
| if (need_cube_map_workaround && !buffer) { | |
| DoCubeMapWorkaround(texture_state, state, framebuffer_state, | |
| texture_ref, function_name, args); | |
| } | |
| if (texture_state->unpack_overlapping_rows_separately_unpack_buffer && | |
| buffer) { | |
| ContextState::Dimension dimension = | |
| (args.command_type == DoTexImageArguments::kTexImage3D) | |
| ? ContextState::k3D | |
| : ContextState::k2D; | |
| const PixelStoreParams unpack_params(state->GetUnpackParams(dimension)); | |
| if (unpack_params.row_length != 0 && | |
| unpack_params.row_length < args.width) { | |
| // The rows overlap in unpack memory. Upload the texture row by row to | |
| // work around driver bug. | |
| ReserveTexImageToBeFilled(texture_state, state, framebuffer_state, | |
| function_name, texture_ref, args); | |
| DoTexSubImageArguments sub_args = { | |
| args.target, args.level, 0, 0, 0, args.width, args.height, args.depth, | |
| args.format, args.type, args.pixels, args.pixels_size, args.padding, | |
| args.command_type == DoTexImageArguments::kTexImage3D | |
| ? DoTexSubImageArguments::kTexSubImage3D | |
| : DoTexSubImageArguments::kTexSubImage2D}; | |
| DoTexSubImageRowByRowWorkaround(texture_state, state, sub_args, | |
| unpack_params); | |
| SetLevelCleared(texture_ref, args.target, args.level, true); | |
| return; | |
| } | |
| } | |
| if (args.command_type == DoTexImageArguments::kTexImage3D && | |
| texture_state->unpack_image_height_workaround_with_unpack_buffer && | |
| buffer) { | |
| ContextState::Dimension dimension = ContextState::k3D; | |
| const PixelStoreParams unpack_params(state->GetUnpackParams(dimension)); | |
| if (unpack_params.image_height != 0 && | |
| unpack_params.image_height != args.height) { | |
| ReserveTexImageToBeFilled(texture_state, state, framebuffer_state, | |
| function_name, texture_ref, args); | |
| DoTexSubImageArguments sub_args = { | |
| args.target, | |
| args.level, | |
| 0, | |
| 0, | |
| 0, | |
| args.width, | |
| args.height, | |
| args.depth, | |
| args.format, | |
| args.type, | |
| args.pixels, | |
| args.pixels_size, | |
| args.padding, | |
| DoTexSubImageArguments::kTexSubImage3D}; | |
| DoTexSubImageLayerByLayerWorkaround(texture_state, state, sub_args, | |
| unpack_params); | |
| SetLevelCleared(texture_ref, args.target, args.level, true); | |
| return; | |
| } | |
| } | |
| if (texture_state->unpack_alignment_workaround_with_unpack_buffer && buffer) { | |
| uint32_t buffer_size = static_cast<uint32_t>(buffer->size()); | |
| if (buffer_size - args.pixels_size - ToGLuint(args.pixels) < args.padding) { | |
| // In ValidateTexImage(), we already made sure buffer size is no less | |
| // than offset + pixels_size. | |
| ReserveTexImageToBeFilled(texture_state, state, framebuffer_state, | |
| function_name, texture_ref, args); | |
| DoTexSubImageArguments sub_args = { | |
| args.target, args.level, 0, 0, 0, args.width, args.height, args.depth, | |
| args.format, args.type, args.pixels, args.pixels_size, args.padding, | |
| args.command_type == DoTexImageArguments::kTexImage3D ? | |
| DoTexSubImageArguments::kTexSubImage3D : | |
| DoTexSubImageArguments::kTexSubImage2D}; | |
| DoTexSubImageWithAlignmentWorkaround(texture_state, state, sub_args); | |
| SetLevelCleared(texture_ref, args.target, args.level, true); | |
| return; | |
| } | |
| } | |
| DoTexImage(texture_state, state, framebuffer_state, | |
| function_name, texture_ref, args); | |
| } | |
| void TextureManager::ReserveTexImageToBeFilled( | |
| DecoderTextureState* texture_state, | |
| ContextState* state, | |
| DecoderFramebufferState* framebuffer_state, | |
| const char* function_name, | |
| TextureRef* texture_ref, | |
| const DoTexImageArguments& args) { | |
| Buffer* buffer = state->bound_pixel_unpack_buffer.get(); | |
| glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); | |
| state->SetBoundBuffer(GL_PIXEL_UNPACK_BUFFER, nullptr); | |
| DoTexImageArguments new_args = args; | |
| new_args.pixels = nullptr; | |
| // pixels_size might be incorrect, but it's not used in this case. | |
| DoTexImage(texture_state, state, framebuffer_state, function_name, | |
| texture_ref, new_args); | |
| glBindBuffer(GL_PIXEL_UNPACK_BUFFER, buffer->service_id()); | |
| state->SetBoundBuffer(GL_PIXEL_UNPACK_BUFFER, buffer); | |
| } | |
| bool TextureManager::ValidateTexSubImage(ContextState* state, | |
| const char* function_name, | |
| const DoTexSubImageArguments& args, | |
| TextureRef** texture_ref) { | |
| ErrorState* error_state = state->GetErrorState(); | |
| const Validators* validators = feature_info_->validators(); | |
| if ((args.command_type == DoTexSubImageArguments::kTexSubImage2D && | |
| !validators->texture_target.IsValid(args.target)) || | |
| (args.command_type == DoTexSubImageArguments::kTexSubImage3D && | |
| !validators->texture_3_d_target.IsValid(args.target))) { | |
| ERRORSTATE_SET_GL_ERROR_INVALID_ENUM(error_state, function_name, | |
| args.target, "target"); | |
| return false; | |
| } | |
| DCHECK(args.width >= 0 && args.height >= 0 && args.depth >= 0); | |
| TextureRef* local_texture_ref = GetTextureInfoForTarget(state, args.target); | |
| if (!local_texture_ref) { | |
| ERRORSTATE_SET_GL_ERROR(error_state, GL_INVALID_OPERATION, function_name, | |
| "unknown texture for target"); | |
| return false; | |
| } | |
| Texture* texture = local_texture_ref->texture(); | |
| GLenum current_type = 0; | |
| GLenum internal_format = 0; | |
| if (!texture->GetLevelType(args.target, args.level, ¤t_type, | |
| &internal_format)) { | |
| std::string msg = base::StringPrintf( | |
| "level %d does not exist", args.level); | |
| ERRORSTATE_SET_GL_ERROR(error_state, GL_INVALID_OPERATION, function_name, | |
| msg.c_str()); | |
| return false; | |
| } | |
| if (!ValidateTextureParameters(error_state, function_name, false, args.format, | |
| args.type, internal_format, args.level)) { | |
| return false; | |
| } | |
| if (args.type != current_type && !feature_info_->IsWebGL2OrES3Context()) { | |
| // It isn't explicitly required in the ES2 spec, but some drivers generate | |
| // an error. It is better to be consistent across drivers. | |
| ERRORSTATE_SET_GL_ERROR(error_state, GL_INVALID_OPERATION, function_name, | |
| "type does not match type of texture."); | |
| return false; | |
| } | |
| if (!texture->ValidForTexture(args.target, args.level, | |
| args.xoffset, args.yoffset, args.zoffset, | |
| args.width, args.height, args.depth)) { | |
| ERRORSTATE_SET_GL_ERROR(error_state, GL_INVALID_VALUE, function_name, | |
| "bad dimensions."); | |
| return false; | |
| } | |
| if ((GLES2Util::GetChannelsForFormat(args.format) & | |
| (GLES2Util::kDepth | GLES2Util::kStencil)) != 0 && | |
| !feature_info_->IsWebGL2OrES3Context()) { | |
| ERRORSTATE_SET_GL_ERROR( | |
| error_state, GL_INVALID_OPERATION, function_name, | |
| "can not supply data for depth or stencil textures"); | |
| return false; | |
| } | |
| Buffer* buffer = state->bound_pixel_unpack_buffer.get(); | |
| if (buffer) { | |
| if (buffer->GetMappedRange()) { | |
| ERRORSTATE_SET_GL_ERROR( | |
| error_state, GL_INVALID_OPERATION, function_name, | |
| "pixel unpack buffer should not be mapped to client memory"); | |
| return false; | |
| } | |
| base::CheckedNumeric<uint32_t> size = args.pixels_size; | |
| GLuint offset = ToGLuint(args.pixels); | |
| size += offset; | |
| if (!size.IsValid()) { | |
| ERRORSTATE_SET_GL_ERROR( | |
| error_state, GL_INVALID_VALUE, function_name, | |
| "size + offset overflow"); | |
| return false; | |
| } | |
| uint32_t buffer_size = static_cast<uint32_t>(buffer->size()); | |
| if (buffer_size < size.ValueOrDefault(0)) { | |
| ERRORSTATE_SET_GL_ERROR( | |
| error_state, GL_INVALID_OPERATION, function_name, | |
| "pixel unpack buffer is not large enough"); | |
| return false; | |
| } | |
| size_t type_size = GLES2Util::GetGLTypeSizeForTextures(args.type); | |
| DCHECK_LT(0u, type_size); | |
| if (offset % type_size != 0) { | |
| ERRORSTATE_SET_GL_ERROR( | |
| error_state, GL_INVALID_OPERATION, function_name, | |
| "offset is not evenly divisible by elements"); | |
| return false; | |
| } | |
| } else { | |
| if (!args.pixels && args.pixels_size) { | |
| // This isn't in the spec, but the spec would define dereferencing NULL | |
| // here. Fail instead. | |
| ERRORSTATE_SET_GL_ERROR(error_state, GL_INVALID_OPERATION, function_name, | |
| "non-empty rect without valid data"); | |
| return false; | |
| } | |
| } | |
| *texture_ref = local_texture_ref; | |
| return true; | |
| } | |
| void TextureManager::ValidateAndDoTexSubImage( | |
| GLES2Decoder* decoder, | |
| DecoderTextureState* texture_state, | |
| ContextState* state, | |
| DecoderFramebufferState* framebuffer_state, | |
| const char* function_name, | |
| const DoTexSubImageArguments& args) { | |
| ErrorState* error_state = state->GetErrorState(); | |
| TextureRef* texture_ref; | |
| if (!ValidateTexSubImage(state, function_name, args, &texture_ref)) { | |
| return; | |
| } | |
| Texture* texture = texture_ref->texture(); | |
| GLsizei tex_width = 0; | |
| GLsizei tex_height = 0; | |
| GLsizei tex_depth = 0; | |
| bool ok = texture->GetLevelSize(args.target, args.level, &tex_width, | |
| &tex_height, &tex_depth); | |
| DCHECK(ok); | |
| bool full_image; | |
| if (args.xoffset != 0 || args.yoffset != 0 || args.zoffset != 0 || | |
| args.width != tex_width || args.height != tex_height || | |
| args.depth != tex_depth) { | |
| gfx::Rect cleared_rect; | |
| if (args.command_type == DoTexSubImageArguments::kTexSubImage2D && | |
| CombineAdjacentRects( | |
| texture->GetLevelClearedRect(args.target, args.level), | |
| gfx::Rect(args.xoffset, args.yoffset, args.width, args.height), | |
| &cleared_rect)) { | |
| DCHECK_GE(cleared_rect.size().GetArea(), | |
| texture->GetLevelClearedRect(args.target, args.level) | |
| .size() | |
| .GetArea()); | |
| SetLevelClearedRect(texture_ref, args.target, args.level, cleared_rect); | |
| } else { | |
| // Otherwise clear part of texture level that is not already cleared. | |
| if (!ClearTextureLevel(decoder, texture_ref, args.target, args.level)) { | |
| ERRORSTATE_SET_GL_ERROR(error_state, GL_OUT_OF_MEMORY, | |
| function_name, "dimensions too big"); | |
| return; | |
| } | |
| } | |
| full_image = false; | |
| } else { | |
| SetLevelCleared(texture_ref, args.target, args.level, true); | |
| full_image = true; | |
| } | |
| Buffer* buffer = state->bound_pixel_unpack_buffer.get(); | |
| if (texture_state->unpack_overlapping_rows_separately_unpack_buffer && | |
| buffer) { | |
| ContextState::Dimension dimension = | |
| (args.command_type == DoTexSubImageArguments::kTexSubImage3D) | |
| ? ContextState::k3D | |
| : ContextState::k2D; | |
| const PixelStoreParams unpack_params(state->GetUnpackParams(dimension)); | |
| if (unpack_params.row_length != 0 && | |
| unpack_params.row_length < args.width) { | |
| // The rows overlap in unpack memory. Upload the texture row by row to | |
| // work around driver bug. | |
| DoTexSubImageRowByRowWorkaround(texture_state, state, args, | |
| unpack_params); | |
| return; | |
| } | |
| } | |
| if (args.command_type == DoTexSubImageArguments::kTexSubImage3D && | |
| texture_state->unpack_image_height_workaround_with_unpack_buffer && | |
| buffer) { | |
| ContextState::Dimension dimension = ContextState::k3D; | |
| const PixelStoreParams unpack_params(state->GetUnpackParams(dimension)); | |
| if (unpack_params.image_height != 0 && | |
| unpack_params.image_height != args.height) { | |
| DoTexSubImageLayerByLayerWorkaround(texture_state, state, args, | |
| unpack_params); | |
| return; | |
| } | |
| } | |
| if (texture_state->unpack_alignment_workaround_with_unpack_buffer && buffer) { | |
| uint32_t buffer_size = static_cast<uint32_t>(buffer->size()); | |
| if (buffer_size - args.pixels_size - ToGLuint(args.pixels) < args.padding) { | |
| DoTexSubImageWithAlignmentWorkaround(texture_state, state, args); | |
| return; | |
| } | |
| } | |
| if (full_image && !texture_state->texsubimage_faster_than_teximage && | |
| !texture->IsImmutable() && !texture->HasImages()) { | |
| GLenum internal_format; | |
| GLenum tex_type; | |
| texture->GetLevelType(args.target, args.level, &tex_type, &internal_format); | |
| // NOTE: In OpenGL ES 2/3 border is always zero. If that changes we'll need | |
| // to look it up. | |
| if (args.command_type == DoTexSubImageArguments::kTexSubImage3D) { | |
| glTexImage3D( | |
| args.target, args.level, | |
| AdjustTexInternalFormat(feature_info_.get(), internal_format), | |
| args.width, args.height, args.depth, 0, | |
| AdjustTexFormat(feature_info_.get(), args.format), args.type, | |
| args.pixels); | |
| } else { | |
| glTexImage2D( | |
| args.target, args.level, | |
| AdjustTexInternalFormat(feature_info_.get(), internal_format), | |
| args.width, args.height, 0, | |
| AdjustTexFormat(feature_info_.get(), args.format), args.type, | |
| args.pixels); | |
| } | |
| } else { | |
| if (args.command_type == DoTexSubImageArguments::kTexSubImage3D) { | |
| glTexSubImage3D(args.target, args.level, args.xoffset, args.yoffset, | |
| args.zoffset, args.width, args.height, args.depth, | |
| AdjustTexFormat(feature_info_.get(), args.format), | |
| args.type, args.pixels); | |
| } else { | |
| glTexSubImage2D(args.target, args.level, args.xoffset, args.yoffset, | |
| args.width, args.height, | |
| AdjustTexFormat(feature_info_.get(), args.format), | |
| args.type, args.pixels); | |
| } | |
| } | |
| } | |
| void TextureManager::DoTexSubImageWithAlignmentWorkaround( | |
| DecoderTextureState* texture_state, | |
| ContextState* state, | |
| const DoTexSubImageArguments& args) { | |
| DCHECK(state->bound_pixel_unpack_buffer.get()); | |
| DCHECK(args.width > 0 && args.height > 0 && args.depth > 0); | |
| uint32_t offset = ToGLuint(args.pixels); | |
| if (args.command_type == DoTexSubImageArguments::kTexSubImage2D) { | |
| PixelStoreParams params = state->GetUnpackParams(ContextState::k2D); | |
| if (args.height > 1) { | |
| glTexSubImage2D(args.target, args.level, args.xoffset, args.yoffset, | |
| args.width, args.height - 1, | |
| AdjustTexFormat(feature_info_.get(), args.format), | |
| args.type, args.pixels); | |
| GLint actual_width = state->unpack_row_length > 0 ? | |
| state->unpack_row_length : args.width; | |
| uint32_t size; | |
| uint32_t padding; | |
| // No need to worry about integer overflow here. | |
| GLES2Util::ComputeImageDataSizesES3(actual_width, args.height - 1, 1, | |
| args.format, args.type, | |
| params, | |
| &size, | |
| nullptr, nullptr, nullptr, | |
| &padding); | |
| DCHECK_EQ(args.padding, padding); | |
| // Last row should be padded, not unpadded. | |
| offset += size + padding; | |
| } | |
| glPixelStorei(GL_UNPACK_ALIGNMENT, 1); | |
| glTexSubImage2D(args.target, args.level, args.xoffset, | |
| args.yoffset + args.height - 1, args.width, 1, | |
| AdjustTexFormat(feature_info_.get(), args.format), | |
| args.type, reinterpret_cast<const void*>(offset)); | |
| glPixelStorei(GL_UNPACK_ALIGNMENT, state->unpack_alignment); | |
| { | |
| uint32_t size; | |
| GLES2Util::ComputeImageDataSizesES3(args.width, 1, 1, | |
| args.format, args.type, | |
| params, | |
| &size, | |
| nullptr, nullptr, nullptr, nullptr); | |
| offset += size; | |
| } | |
| } else { // kTexSubImage3D | |
| PixelStoreParams params = state->GetUnpackParams(ContextState::k3D); | |
| GLint actual_width = state->unpack_row_length > 0 ? | |
| state->unpack_row_length : args.width; | |
| if (args.depth > 1) { | |
| glTexSubImage3D(args.target, args.level, args.xoffset, args.yoffset, | |
| args.zoffset, args.width, args.height, args.depth - 1, | |
| AdjustTexFormat(feature_info_.get(), args.format), | |
| args.type, args.pixels); | |
| GLint actual_height = state->unpack_image_height > 0 ? | |
| state->unpack_image_height : args.height; | |
| uint32_t size; | |
| uint32_t padding; | |
| // No need to worry about integer overflow here. | |
| GLES2Util::ComputeImageDataSizesES3(actual_width, actual_height, | |
| args.depth - 1, | |
| args.format, args.type, | |
| params, | |
| &size, | |
| nullptr, nullptr, nullptr, | |
| &padding); | |
| DCHECK_EQ(args.padding, padding); | |
| // Last row should be padded, not unpadded. | |
| offset += size + padding; | |
| } | |
| if (args.height > 1) { | |
| glTexSubImage3D(args.target, args.level, args.xoffset, args.yoffset, | |
| args.zoffset + args.depth - 1, args.width, | |
| args.height - 1, 1, | |
| AdjustTexFormat(feature_info_.get(), args.format), | |
| args.type, reinterpret_cast<const void*>(offset)); | |
| uint32_t size; | |
| uint32_t padding; | |
| // No need to worry about integer overflow here. | |
| GLES2Util::ComputeImageDataSizesES3(actual_width, args.height - 1, 1, | |
| args.format, args.type, | |
| params, | |
| &size, | |
| nullptr, nullptr, nullptr, | |
| &padding); | |
| DCHECK_EQ(args.padding, padding); | |
| // Last row should be padded, not unpadded. | |
| offset += size + padding; | |
| } | |
| glPixelStorei(GL_UNPACK_ALIGNMENT, 1); | |
| glTexSubImage3D(args.target, args.level, args.xoffset, | |
| args.yoffset + args.height - 1, | |
| args.zoffset + args.depth - 1, args.width, 1, 1, | |
| AdjustTexFormat(feature_info_.get(), args.format), | |
| args.type, reinterpret_cast<const void*>(offset)); | |
| glPixelStorei(GL_UNPACK_ALIGNMENT, state->unpack_alignment); | |
| { | |
| uint32_t size; | |
| GLES2Util::ComputeImageDataSizesES3(args.width, 1, 1, | |
| args.format, args.type, | |
| params, | |
| &size, | |
| nullptr, nullptr, nullptr, nullptr); | |
| offset += size; | |
| } | |
| } | |
| DCHECK_EQ(ToGLuint(args.pixels) + args.pixels_size, offset); | |
| } | |
| void TextureManager::DoTexSubImageRowByRowWorkaround( | |
| DecoderTextureState* texture_state, | |
| ContextState* state, | |
| const DoTexSubImageArguments& args, | |
| const PixelStoreParams& unpack_params) { | |
| glPixelStorei(GL_UNPACK_ALIGNMENT, 1); | |
| glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); | |
| DCHECK_EQ(0, state->unpack_skip_pixels); | |
| DCHECK_EQ(0, state->unpack_skip_rows); | |
| DCHECK_EQ(0, state->unpack_skip_images); | |
| GLenum format = AdjustTexFormat(feature_info_.get(), args.format); | |
| GLsizei row_bytes = unpack_params.row_length * | |
| GLES2Util::ComputeImageGroupSize(format, args.type); | |
| GLsizei alignment_diff = row_bytes % unpack_params.alignment; | |
| if (alignment_diff != 0) { | |
| row_bytes += unpack_params.alignment - alignment_diff; | |
| } | |
| DCHECK_EQ(0, row_bytes % unpack_params.alignment); | |
| if (args.command_type == DoTexSubImageArguments::kTexSubImage3D) { | |
| GLsizei image_height = args.height; | |
| if (unpack_params.image_height != 0) { | |
| image_height = unpack_params.image_height; | |
| } | |
| GLsizei image_bytes = row_bytes * image_height; | |
| for (GLsizei image = 0; image < args.depth; ++image) { | |
| GLsizei image_byte_offset = image * image_bytes; | |
| for (GLsizei row = 0; row < args.height; ++row) { | |
| GLsizei byte_offset = image_byte_offset + row * row_bytes; | |
| const GLubyte* row_pixels = | |
| reinterpret_cast<const GLubyte*>(args.pixels) + byte_offset; | |
| glTexSubImage3D(args.target, args.level, args.xoffset, | |
| row + args.yoffset, image + args.zoffset, args.width, 1, | |
| 1, format, args.type, row_pixels); | |
| } | |
| } | |
| } else { | |
| for (GLsizei row = 0; row < args.height; ++row) { | |
| GLsizei byte_offset = row * row_bytes; | |
| const GLubyte* row_pixels = | |
| reinterpret_cast<const GLubyte*>(args.pixels) + byte_offset; | |
| glTexSubImage2D(args.target, args.level, args.xoffset, row + args.yoffset, | |
| args.width, 1, format, args.type, row_pixels); | |
| } | |
| } | |
| // Restore unpack state | |
| glPixelStorei(GL_UNPACK_ALIGNMENT, unpack_params.alignment); | |
| glPixelStorei(GL_UNPACK_ROW_LENGTH, unpack_params.row_length); | |
| } | |
| void TextureManager::DoTexSubImageLayerByLayerWorkaround( | |
| DecoderTextureState* texture_state, | |
| ContextState* state, | |
| const DoTexSubImageArguments& args, | |
| const PixelStoreParams& unpack_params) { | |
| glPixelStorei(GL_UNPACK_IMAGE_HEIGHT, 0); | |
| GLenum format = AdjustTexFormat(feature_info_.get(), args.format); | |
| GLsizei row_length = | |
| unpack_params.row_length ? unpack_params.row_length : args.width; | |
| GLsizei row_bytes = | |
| row_length * GLES2Util::ComputeImageGroupSize(format, args.type); | |
| GLsizei alignment_diff = row_bytes % unpack_params.alignment; | |
| if (alignment_diff != 0) { | |
| row_bytes += unpack_params.alignment - alignment_diff; | |
| } | |
| DCHECK_EQ(0, row_bytes % unpack_params.alignment); | |
| // process the texture layer by layer | |
| GLsizei image_height = unpack_params.image_height; | |
| GLsizei image_bytes = row_bytes * image_height; | |
| const GLubyte* image_pixels = reinterpret_cast<const GLubyte*>(args.pixels); | |
| for (GLsizei image = 0; image < args.depth - 1; ++image) { | |
| glTexSubImage3D(args.target, args.level, args.xoffset, args.yoffset, | |
| image + args.zoffset, args.width, args.height, 1, format, | |
| args.type, image_pixels); | |
| image_pixels += image_bytes; | |
| } | |
| // Process the last image row by row | |
| glPixelStorei(GL_UNPACK_ALIGNMENT, 1); | |
| const GLubyte* row_pixels = image_pixels; | |
| for (GLsizei row = 0; row < args.height; ++row) { | |
| glTexSubImage3D(args.target, args.level, args.xoffset, row + args.yoffset, | |
| args.depth - 1 + args.zoffset, args.width, 1, 1, format, | |
| args.type, row_pixels); | |
| row_pixels += row_bytes; | |
| } | |
| // Restore unpack state | |
| glPixelStorei(GL_UNPACK_ALIGNMENT, unpack_params.alignment); | |
| glPixelStorei(GL_UNPACK_IMAGE_HEIGHT, unpack_params.image_height); | |
| } | |
| // static | |
| GLenum TextureManager::AdjustTexInternalFormat( | |
| const gles2::FeatureInfo* feature_info, | |
| GLenum format) { | |
| if (feature_info->gl_version_info().is_desktop_core_profile) { | |
| const Texture::CompatibilitySwizzle* swizzle = | |
| GetCompatibilitySwizzle(format); | |
| if (swizzle) | |
| return swizzle->dest_format; | |
| } | |
| return format; | |
| } | |
| // static | |
| GLenum TextureManager::AdjustTexFormat(const gles2::FeatureInfo* feature_info, | |
| GLenum format) { | |
| // TODO(bajones): GLES 3 allows for internal format and format to differ. | |
| // This logic may need to change as a result. | |
| if (!feature_info->gl_version_info().is_es) { | |
| if (format == GL_SRGB_EXT) | |
| return GL_RGB; | |
| if (format == GL_SRGB_ALPHA_EXT) | |
| return GL_RGBA; | |
| } | |
| if (feature_info->gl_version_info().is_desktop_core_profile) { | |
| const Texture::CompatibilitySwizzle* swizzle = | |
| GetCompatibilitySwizzle(format); | |
| if (swizzle) | |
| return swizzle->dest_format; | |
| } | |
| return format; | |
| } | |
| void TextureManager::DoTexImage( | |
| DecoderTextureState* texture_state, | |
| ContextState* state, | |
| DecoderFramebufferState* framebuffer_state, | |
| const char* function_name, | |
| TextureRef* texture_ref, | |
| const DoTexImageArguments& args) { | |
| ErrorState* error_state = state->GetErrorState(); | |
| Texture* texture = texture_ref->texture(); | |
| GLsizei tex_width = 0; | |
| GLsizei tex_height = 0; | |
| GLsizei tex_depth = 0; | |
| GLenum tex_type = 0; | |
| GLenum tex_internal_format = 0; | |
| bool level_is_same = | |
| texture->GetLevelSize( | |
| args.target, args.level, &tex_width, &tex_height, &tex_depth) && | |
| args.width == tex_width && args.height == tex_height && | |
| args.depth == tex_depth && | |
| texture->GetLevelType( | |
| args.target, args.level, &tex_type, &tex_internal_format) && | |
| args.type == tex_type && args.internal_format == tex_internal_format; | |
| bool unpack_buffer_bound = | |
| (state->bound_pixel_unpack_buffer.get() != nullptr); | |
| if (level_is_same && !args.pixels && !unpack_buffer_bound) { | |
| // Just set the level texture but mark the texture as uncleared. | |
| SetLevelInfo( | |
| texture_ref, args.target, args.level, args.internal_format, args.width, | |
| args.height, args.depth, args.border, args.format, args.type, | |
| gfx::Rect()); | |
| texture_state->tex_image_failed = false; | |
| return; | |
| } | |
| if (texture->IsAttachedToFramebuffer()) { | |
| framebuffer_state->clear_state_dirty = true; | |
| } | |
| if (texture_state->texsubimage_faster_than_teximage && | |
| level_is_same && args.pixels && !unpack_buffer_bound) { | |
| { | |
| if (args.command_type == DoTexImageArguments::kTexImage3D) { | |
| glTexSubImage3D(args.target, args.level, 0, 0, 0, args.width, | |
| args.height, args.depth, | |
| AdjustTexFormat(feature_info_.get(), args.format), | |
| args.type, args.pixels); | |
| } else { | |
| glTexSubImage2D(args.target, args.level, 0, 0, args.width, args.height, | |
| AdjustTexFormat(feature_info_.get(), args.format), | |
| args.type, args.pixels); | |
| } | |
| } | |
| SetLevelInfo(texture_ref, args.target, args.level, args.internal_format, | |
| args.width, args.height, args.depth, args.border, args.format, | |
| args.type, gfx::Rect(args.width, args.height)); | |
| texture_state->tex_image_failed = false; | |
| return; | |
| } | |
| ERRORSTATE_COPY_REAL_GL_ERRORS_TO_WRAPPER(error_state, function_name); | |
| { | |
| if (args.command_type == DoTexImageArguments::kTexImage3D) { | |
| glTexImage3D( | |
| args.target, args.level, | |
| AdjustTexInternalFormat(feature_info_.get(), args.internal_format), | |
| args.width, args.height, args.depth, args.border, | |
| AdjustTexFormat(feature_info_.get(), args.format), args.type, | |
| args.pixels); | |
| } else { | |
| glTexImage2D( | |
| args.target, args.level, | |
| AdjustTexInternalFormat(feature_info_.get(), args.internal_format), | |
| args.width, args.height, args.border, | |
| AdjustTexFormat(feature_info_.get(), args.format), args.type, | |
| args.pixels); | |
| } | |
| } | |
| GLenum error = ERRORSTATE_PEEK_GL_ERROR(error_state, function_name); | |
| if (args.command_type == DoTexImageArguments::kTexImage3D) { | |
| UMA_HISTOGRAM_CUSTOM_ENUMERATION("GPU.Error_TexImage3D", error, | |
| GetAllGLErrors()); | |
| } else { | |
| UMA_HISTOGRAM_CUSTOM_ENUMERATION("GPU.Error_TexImage2D", error, | |
| GetAllGLErrors()); | |
| } | |
| if (error == GL_NO_ERROR) { | |
| bool set_as_cleared = (args.pixels != nullptr || unpack_buffer_bound); | |
| SetLevelInfo( | |
| texture_ref, args.target, args.level, args.internal_format, args.width, | |
| args.height, args.depth, args.border, args.format, args.type, | |
| set_as_cleared ? gfx::Rect(args.width, args.height) : gfx::Rect()); | |
| texture->ApplyFormatWorkarounds(feature_info_.get()); | |
| texture_state->tex_image_failed = false; | |
| } | |
| } | |
| bool TextureManager::CombineAdjacentRects(const gfx::Rect& rect1, | |
| const gfx::Rect& rect2, | |
| gfx::Rect* result) { | |
| // Return |rect2| if |rect1| is empty or |rect2| contains |rect1|. | |
| if (rect1.IsEmpty() || rect2.Contains(rect1)) { | |
| *result = rect2; | |
| return true; | |
| } | |
| // Return |rect1| if |rect2| is empty or |rect1| contains |rect2|. | |
| if (rect2.IsEmpty() || rect1.Contains(rect2)) { | |
| *result = rect1; | |
| return true; | |
| } | |
| // Return the union of |rect1| and |rect2| if they share an edge. | |
| if (rect1.SharesEdgeWith(rect2)) { | |
| *result = gfx::UnionRects(rect1, rect2); | |
| return true; | |
| } | |
| // Return false if it's not possible to combine |rect1| and |rect2|. | |
| return false; | |
| } | |
| bool TextureManager::OnMemoryDump(const base::trace_event::MemoryDumpArgs& args, | |
| base::trace_event::ProcessMemoryDump* pmd) { | |
| if (args.level_of_detail == MemoryDumpLevelOfDetail::BACKGROUND) { | |
| std::string dump_name = | |
| base::StringPrintf("gpu/gl/textures/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; | |
| } | |
| for (const auto& resource : textures_) { | |
| // Only dump memory info for textures actually owned by this | |
| // TextureManager. | |
| DumpTextureRef(pmd, resource.second.get()); | |
| } | |
| // Also dump TextureManager internal textures, if allocated. | |
| for (int i = 0; i < kNumDefaultTextures; i++) { | |
| if (default_textures_[i]) { | |
| DumpTextureRef(pmd, default_textures_[i].get()); | |
| } | |
| } | |
| return true; | |
| } | |
| void TextureManager::DumpTextureRef(base::trace_event::ProcessMemoryDump* pmd, | |
| TextureRef* ref) { | |
| uint32_t size = ref->texture()->estimated_size(); | |
| // Ignore unallocated texture IDs. | |
| if (size == 0) | |
| return; | |
| std::string dump_name = base::StringPrintf( | |
| "gpu/gl/textures/share_group_0x%" PRIX64 "/texture_0x%" PRIX32, | |
| memory_tracker_->ShareGroupTracingGUID(), ref->client_id()); | |
| MemoryAllocatorDump* dump = pmd->CreateAllocatorDump(dump_name); | |
| dump->AddScalar(MemoryAllocatorDump::kNameSize, | |
| MemoryAllocatorDump::kUnitsBytes, | |
| static_cast<uint64_t>(size)); | |
| // Add the |client_guid| which expresses shared ownership with the client | |
| // process. | |
| auto client_guid = gl::GetGLTextureClientGUIDForTracing( | |
| memory_tracker_->ShareGroupTracingGUID(), ref->client_id()); | |
| pmd->CreateSharedGlobalAllocatorDump(client_guid); | |
| pmd->AddOwnershipEdge(dump->guid(), client_guid); | |
| // Add a |service_guid| which expresses shared ownership between the various | |
| // |client_guid|s. | |
| // TODO(ericrk): May need to ensure uniqueness using GLShareGroup and | |
| // potentially cross-share-group sharing via EGLImages. crbug.com/512534 | |
| auto service_guid = gl::GetGLTextureServiceGUIDForTracing( | |
| memory_tracker_->ShareGroupTracingGUID(), ref->texture()->service_id()); | |
| pmd->CreateSharedGlobalAllocatorDump(service_guid); | |
| int importance = 0; // Default importance. | |
| // The link to the memory tracking |client_id| is given a higher importance | |
| // than other refs. | |
| if (ref == ref->texture()->memory_tracking_ref_) | |
| importance = 2; | |
| pmd->AddOwnershipEdge(client_guid, service_guid, importance); | |
| // Dump all sub-levels held by the texture. They will appear below the main | |
| // gl/textures/client_X/texture_Y dump. | |
| ref->texture()->DumpLevelMemory(pmd, memory_tracker_->ClientTracingId(), | |
| dump_name); | |
| } | |
| GLenum TextureManager::ExtractFormatFromStorageFormat(GLenum internalformat) { | |
| switch (internalformat) { | |
| case GL_COMPRESSED_R11_EAC: | |
| case GL_COMPRESSED_SIGNED_R11_EAC: | |
| case GL_RED: | |
| case GL_R8: | |
| case GL_R8_SNORM: | |
| case GL_R16F: | |
| case GL_R32F: | |
| return GL_RED; | |
| case GL_R8UI: | |
| case GL_R8I: | |
| case GL_R16UI: | |
| case GL_R16I: | |
| case GL_R32UI: | |
| case GL_R32I: | |
| return GL_RED_INTEGER; | |
| case GL_COMPRESSED_RG11_EAC: | |
| case GL_COMPRESSED_SIGNED_RG11_EAC: | |
| case GL_RG: | |
| case GL_RG8: | |
| case GL_RG8_SNORM: | |
| case GL_RG16F: | |
| case GL_RG32F: | |
| return GL_RG; | |
| case GL_RG8UI: | |
| case GL_RG8I: | |
| case GL_RG16UI: | |
| case GL_RG16I: | |
| case GL_RG32UI: | |
| case GL_RG32I: | |
| return GL_RG_INTEGER; | |
| case GL_ATC_RGB_AMD: | |
| case GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG: | |
| case GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG: | |
| case GL_COMPRESSED_RGB_S3TC_DXT1_EXT: | |
| case GL_COMPRESSED_RGB8_ETC2: | |
| case GL_COMPRESSED_SRGB_S3TC_DXT1_EXT: | |
| case GL_COMPRESSED_SRGB8_ETC2: | |
| case GL_ETC1_RGB8_OES: | |
| case GL_RGB: | |
| case GL_RGB8: | |
| case GL_SRGB8: | |
| case GL_R11F_G11F_B10F: | |
| case GL_RGB565: | |
| case GL_RGB8_SNORM: | |
| case GL_RGB9_E5: | |
| case GL_RGB16F: | |
| case GL_RGB32F: | |
| return GL_RGB; | |
| case GL_RGB8UI: | |
| case GL_RGB8I: | |
| case GL_RGB16UI: | |
| case GL_RGB16I: | |
| case GL_RGB32UI: | |
| case GL_RGB32I: | |
| return GL_RGB_INTEGER; | |
| case GL_SRGB: | |
| return GL_SRGB; | |
| case GL_ATC_RGBA_EXPLICIT_ALPHA_AMD: | |
| case GL_ATC_RGBA_INTERPOLATED_ALPHA_AMD: | |
| case GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2: | |
| case GL_COMPRESSED_RGBA8_ETC2_EAC: | |
| case GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG: | |
| case GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG: | |
| case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT: | |
| case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT: | |
| case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT: | |
| case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT: | |
| case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT: | |
| case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT: | |
| case GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC: | |
| case GL_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2: | |
| case GL_RGBA: | |
| case GL_RGBA8: | |
| case GL_SRGB8_ALPHA8: | |
| case GL_RGBA8_SNORM: | |
| case GL_RGBA4: | |
| case GL_RGB5_A1: | |
| case GL_RGB10_A2: | |
| case GL_RGBA16F: | |
| case GL_RGBA32F: | |
| return GL_RGBA; | |
| case GL_SRGB_ALPHA: | |
| return GL_SRGB_ALPHA; | |
| case GL_RGBA8UI: | |
| case GL_RGBA8I: | |
| case GL_RGB10_A2UI: | |
| case GL_RGBA16UI: | |
| case GL_RGBA16I: | |
| case GL_RGBA32UI: | |
| case GL_RGBA32I: | |
| return GL_RGBA_INTEGER; | |
| case GL_BGRA_EXT: | |
| case GL_BGRA8_EXT: | |
| return GL_BGRA_EXT; | |
| case GL_DEPTH_COMPONENT16: | |
| case GL_DEPTH_COMPONENT24: | |
| case GL_DEPTH_COMPONENT32F: | |
| return GL_DEPTH_COMPONENT; | |
| case GL_DEPTH24_STENCIL8: | |
| case GL_DEPTH32F_STENCIL8: | |
| return GL_DEPTH_STENCIL; | |
| case GL_LUMINANCE_ALPHA: | |
| case GL_LUMINANCE8_ALPHA8_EXT: | |
| return GL_LUMINANCE_ALPHA; | |
| case GL_LUMINANCE: | |
| case GL_LUMINANCE8_EXT: | |
| return GL_LUMINANCE; | |
| case GL_ALPHA: | |
| case GL_ALPHA8_EXT: | |
| return GL_ALPHA; | |
| case GL_ALPHA32F_EXT: | |
| return GL_ALPHA; | |
| case GL_LUMINANCE32F_EXT: | |
| return GL_LUMINANCE; | |
| case GL_LUMINANCE_ALPHA32F_EXT: | |
| return GL_LUMINANCE_ALPHA; | |
| case GL_ALPHA16F_EXT: | |
| return GL_ALPHA; | |
| case GL_LUMINANCE16F_EXT: | |
| return GL_LUMINANCE; | |
| case GL_LUMINANCE_ALPHA16F_EXT: | |
| return GL_LUMINANCE_ALPHA; | |
| default: | |
| return GL_NONE; | |
| } | |
| } | |
| GLenum TextureManager::ExtractTypeFromStorageFormat(GLenum internalformat) { | |
| switch (internalformat) { | |
| case GL_RED: | |
| case GL_RG: | |
| case GL_RGB: | |
| case GL_SRGB: | |
| case GL_RGBA: | |
| case GL_BGRA_EXT: | |
| case GL_SRGB_ALPHA: | |
| case GL_LUMINANCE_ALPHA: | |
| case GL_LUMINANCE: | |
| case GL_ALPHA: | |
| case GL_R8: | |
| return GL_UNSIGNED_BYTE; | |
| case GL_R8_SNORM: | |
| return GL_BYTE; | |
| case GL_R16F: | |
| return GL_HALF_FLOAT; | |
| case GL_R32F: | |
| return GL_FLOAT; | |
| case GL_R8UI: | |
| return GL_UNSIGNED_BYTE; | |
| case GL_R8I: | |
| return GL_BYTE; | |
| case GL_R16UI: | |
| return GL_UNSIGNED_SHORT; | |
| case GL_R16I: | |
| return GL_SHORT; | |
| case GL_R32UI: | |
| return GL_UNSIGNED_INT; | |
| case GL_R32I: | |
| return GL_INT; | |
| case GL_RG8: | |
| return GL_UNSIGNED_BYTE; | |
| case GL_RG8_SNORM: | |
| return GL_BYTE; | |
| case GL_RG16F: | |
| return GL_HALF_FLOAT; | |
| case GL_RG32F: | |
| return GL_FLOAT; | |
| case GL_RG8UI: | |
| return GL_UNSIGNED_BYTE; | |
| case GL_RG8I: | |
| return GL_BYTE; | |
| case GL_RG16UI: | |
| return GL_UNSIGNED_SHORT; | |
| case GL_RG16I: | |
| return GL_SHORT; | |
| case GL_RG32UI: | |
| return GL_UNSIGNED_INT; | |
| case GL_RG32I: | |
| return GL_INT; | |
| case GL_RGB8: | |
| case GL_SRGB8: | |
| return GL_UNSIGNED_BYTE; | |
| case GL_R11F_G11F_B10F: | |
| return GL_UNSIGNED_INT_10F_11F_11F_REV; | |
| case GL_RGB565: | |
| return GL_UNSIGNED_SHORT_5_6_5; | |
| case GL_RGB8_SNORM: | |
| return GL_BYTE; | |
| case GL_RGB9_E5: | |
| return GL_UNSIGNED_INT_5_9_9_9_REV; | |
| case GL_RGB16F: | |
| return GL_HALF_FLOAT; | |
| case GL_RGB32F: | |
| return GL_FLOAT; | |
| case GL_RGB8UI: | |
| return GL_UNSIGNED_BYTE; | |
| case GL_RGB8I: | |
| return GL_BYTE; | |
| case GL_RGB16UI: | |
| return GL_UNSIGNED_SHORT; | |
| case GL_RGB16I: | |
| return GL_SHORT; | |
| case GL_RGB32UI: | |
| return GL_UNSIGNED_INT; | |
| case GL_RGB32I: | |
| return GL_INT; | |
| case GL_RGBA8: | |
| return GL_UNSIGNED_BYTE; | |
| case GL_SRGB8_ALPHA8: | |
| return GL_UNSIGNED_BYTE; | |
| case GL_RGBA8_SNORM: | |
| return GL_BYTE; | |
| case GL_RGBA4: | |
| return GL_UNSIGNED_SHORT_4_4_4_4; | |
| case GL_RGB10_A2: | |
| return GL_UNSIGNED_INT_2_10_10_10_REV; | |
| case GL_RGB5_A1: | |
| return GL_UNSIGNED_SHORT_5_5_5_1; | |
| case GL_RGBA16F: | |
| return GL_HALF_FLOAT; | |
| case GL_RGBA32F: | |
| return GL_FLOAT; | |
| case GL_RGBA8UI: | |
| return GL_UNSIGNED_BYTE; | |
| case GL_RGBA8I: | |
| return GL_BYTE; | |
| case GL_RGB10_A2UI: | |
| return GL_UNSIGNED_INT_2_10_10_10_REV; | |
| case GL_RGBA16UI: | |
| return GL_UNSIGNED_SHORT; | |
| case GL_RGBA16I: | |
| return GL_SHORT; | |
| case GL_RGBA32I: | |
| return GL_INT; | |
| case GL_RGBA32UI: | |
| return GL_UNSIGNED_INT; | |
| case GL_DEPTH_COMPONENT16: | |
| return GL_UNSIGNED_SHORT; | |
| case GL_DEPTH_COMPONENT24: | |
| return GL_UNSIGNED_INT; | |
| case GL_DEPTH_COMPONENT32F: | |
| return GL_FLOAT; | |
| case GL_DEPTH24_STENCIL8: | |
| return GL_UNSIGNED_INT_24_8; | |
| case GL_DEPTH32F_STENCIL8: | |
| return GL_FLOAT_32_UNSIGNED_INT_24_8_REV; | |
| case GL_LUMINANCE8_ALPHA8_EXT: | |
| return GL_UNSIGNED_BYTE; | |
| case GL_LUMINANCE8_EXT: | |
| return GL_UNSIGNED_BYTE; | |
| case GL_ALPHA8_EXT: | |
| return GL_UNSIGNED_BYTE; | |
| case GL_ALPHA32F_EXT: | |
| return GL_FLOAT; | |
| case GL_LUMINANCE32F_EXT: | |
| return GL_FLOAT; | |
| case GL_LUMINANCE_ALPHA32F_EXT: | |
| return GL_FLOAT; | |
| case GL_ALPHA16F_EXT: | |
| return GL_HALF_FLOAT_OES; | |
| case GL_LUMINANCE16F_EXT: | |
| return GL_HALF_FLOAT_OES; | |
| case GL_LUMINANCE_ALPHA16F_EXT: | |
| return GL_HALF_FLOAT_OES; | |
| case GL_BGRA8_EXT: | |
| return GL_UNSIGNED_BYTE; | |
| default: | |
| return GL_NONE; | |
| } | |
| } | |
| void Texture::IncrementManagerServiceIdGeneration() { | |
| for (auto* ref : refs_) { | |
| TextureManager* manager = ref->manager(); | |
| manager->IncrementServiceIdGeneration(); | |
| } | |
| } | |
| uint32_t TextureManager::GetServiceIdGeneration() const { | |
| return current_service_id_generation_; | |
| } | |
| void TextureManager::IncrementServiceIdGeneration() { | |
| current_service_id_generation_++; | |
| } | |
| } // namespace gles2 | |
| } // namespace gpu |