| // Copyright 2014 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 "ui/gl/gl_image_memory.h" |
| |
| #include <stdint.h> |
| |
| #include "base/logging.h" |
| #include "base/numerics/safe_conversions.h" |
| #include "base/trace_event/trace_event.h" |
| #include "ui/gfx/buffer_format_util.h" |
| #include "ui/gl/gl_bindings.h" |
| #include "ui/gl/gl_context.h" |
| #include "ui/gl/gl_version_info.h" |
| |
| using gfx::BufferFormat; |
| |
| namespace gl { |
| namespace { |
| |
| bool ValidInternalFormat(unsigned internalformat) { |
| switch (internalformat) { |
| case GL_ATC_RGB_AMD: |
| case GL_ATC_RGBA_INTERPOLATED_ALPHA_AMD: |
| case GL_COMPRESSED_RGB_S3TC_DXT1_EXT: |
| case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT: |
| case GL_ETC1_RGB8_OES: |
| case GL_RED: |
| case GL_RG: |
| case GL_RGB: |
| case GL_RGBA: |
| case GL_BGRA_EXT: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| bool ValidFormat(gfx::BufferFormat format) { |
| switch (format) { |
| case gfx::BufferFormat::ATC: |
| case gfx::BufferFormat::ATCIA: |
| case gfx::BufferFormat::DXT1: |
| case gfx::BufferFormat::DXT5: |
| case gfx::BufferFormat::ETC1: |
| case gfx::BufferFormat::R_8: |
| case gfx::BufferFormat::RG_88: |
| case gfx::BufferFormat::BGR_565: |
| case gfx::BufferFormat::RGBA_4444: |
| case gfx::BufferFormat::RGBX_8888: |
| case gfx::BufferFormat::RGBA_8888: |
| case gfx::BufferFormat::BGRX_8888: |
| case gfx::BufferFormat::BGRA_8888: |
| case gfx::BufferFormat::RGBA_F16: |
| return true; |
| case gfx::BufferFormat::YVU_420: |
| case gfx::BufferFormat::YUV_420_BIPLANAR: |
| case gfx::BufferFormat::UYVY_422: |
| return false; |
| } |
| |
| NOTREACHED(); |
| return false; |
| } |
| |
| bool IsCompressedFormat(gfx::BufferFormat format) { |
| switch (format) { |
| case gfx::BufferFormat::ATC: |
| case gfx::BufferFormat::ATCIA: |
| case gfx::BufferFormat::DXT1: |
| case gfx::BufferFormat::DXT5: |
| case gfx::BufferFormat::ETC1: |
| return true; |
| case gfx::BufferFormat::R_8: |
| case gfx::BufferFormat::RG_88: |
| case gfx::BufferFormat::BGR_565: |
| case gfx::BufferFormat::RGBA_4444: |
| case gfx::BufferFormat::RGBX_8888: |
| case gfx::BufferFormat::RGBA_8888: |
| case gfx::BufferFormat::BGRX_8888: |
| case gfx::BufferFormat::BGRA_8888: |
| case gfx::BufferFormat::RGBA_F16: |
| return false; |
| case gfx::BufferFormat::YVU_420: |
| case gfx::BufferFormat::YUV_420_BIPLANAR: |
| case gfx::BufferFormat::UYVY_422: |
| NOTREACHED(); |
| return false; |
| } |
| |
| NOTREACHED(); |
| return false; |
| } |
| |
| GLenum TextureFormat(gfx::BufferFormat format) { |
| switch (format) { |
| case gfx::BufferFormat::ATC: |
| return GL_ATC_RGB_AMD; |
| case gfx::BufferFormat::ATCIA: |
| return GL_ATC_RGBA_INTERPOLATED_ALPHA_AMD; |
| case gfx::BufferFormat::DXT1: |
| return GL_COMPRESSED_RGB_S3TC_DXT1_EXT; |
| case gfx::BufferFormat::DXT5: |
| return GL_COMPRESSED_RGBA_S3TC_DXT5_EXT; |
| case gfx::BufferFormat::ETC1: |
| return GL_ETC1_RGB8_OES; |
| case gfx::BufferFormat::R_8: |
| return GL_RED; |
| case gfx::BufferFormat::RG_88: |
| return GL_RG; |
| case gfx::BufferFormat::RGBA_4444: |
| case gfx::BufferFormat::RGBA_8888: |
| case gfx::BufferFormat::RGBA_F16: |
| return GL_RGBA; |
| case gfx::BufferFormat::BGRA_8888: |
| return GL_BGRA_EXT; |
| case gfx::BufferFormat::BGR_565: |
| case gfx::BufferFormat::RGBX_8888: |
| case gfx::BufferFormat::BGRX_8888: |
| return GL_RGB; |
| case gfx::BufferFormat::YVU_420: |
| case gfx::BufferFormat::YUV_420_BIPLANAR: |
| case gfx::BufferFormat::UYVY_422: |
| NOTREACHED(); |
| return 0; |
| } |
| |
| NOTREACHED(); |
| return 0; |
| } |
| |
| GLenum DataFormat(gfx::BufferFormat format) { |
| switch (format) { |
| case gfx::BufferFormat::RGBX_8888: |
| return GL_RGBA; |
| case gfx::BufferFormat::BGRX_8888: |
| return GL_BGRA_EXT; |
| case gfx::BufferFormat::BGR_565: |
| case gfx::BufferFormat::RGBA_4444: |
| case gfx::BufferFormat::RGBA_8888: |
| case gfx::BufferFormat::BGRA_8888: |
| case gfx::BufferFormat::RGBA_F16: |
| case gfx::BufferFormat::R_8: |
| case gfx::BufferFormat::RG_88: |
| case gfx::BufferFormat::ATC: |
| case gfx::BufferFormat::ATCIA: |
| case gfx::BufferFormat::DXT1: |
| case gfx::BufferFormat::DXT5: |
| case gfx::BufferFormat::ETC1: |
| return TextureFormat(format); |
| case gfx::BufferFormat::YVU_420: |
| case gfx::BufferFormat::YUV_420_BIPLANAR: |
| case gfx::BufferFormat::UYVY_422: |
| NOTREACHED(); |
| return 0; |
| } |
| |
| NOTREACHED(); |
| return 0; |
| } |
| |
| GLenum DataType(gfx::BufferFormat format) { |
| switch (format) { |
| case gfx::BufferFormat::BGR_565: |
| return GL_UNSIGNED_SHORT_5_6_5_REV; |
| case gfx::BufferFormat::RGBA_4444: |
| return GL_UNSIGNED_SHORT_4_4_4_4; |
| case gfx::BufferFormat::RGBX_8888: |
| case gfx::BufferFormat::RGBA_8888: |
| case gfx::BufferFormat::BGRX_8888: |
| case gfx::BufferFormat::BGRA_8888: |
| case gfx::BufferFormat::R_8: |
| case gfx::BufferFormat::RG_88: |
| return GL_UNSIGNED_BYTE; |
| case gfx::BufferFormat::RGBA_F16: |
| return GL_HALF_FLOAT_OES; |
| case gfx::BufferFormat::ATC: |
| case gfx::BufferFormat::ATCIA: |
| case gfx::BufferFormat::DXT1: |
| case gfx::BufferFormat::DXT5: |
| case gfx::BufferFormat::ETC1: |
| case gfx::BufferFormat::YVU_420: |
| case gfx::BufferFormat::YUV_420_BIPLANAR: |
| case gfx::BufferFormat::UYVY_422: |
| NOTREACHED(); |
| return 0; |
| } |
| |
| NOTREACHED(); |
| return 0; |
| } |
| |
| GLint DataRowLength(size_t stride, gfx::BufferFormat format) { |
| switch (format) { |
| case gfx::BufferFormat::RG_88: |
| case gfx::BufferFormat::BGR_565: |
| case gfx::BufferFormat::RGBA_4444: |
| return base::checked_cast<GLint>(stride) / 2; |
| case gfx::BufferFormat::RGBX_8888: |
| case gfx::BufferFormat::RGBA_8888: |
| case gfx::BufferFormat::BGRX_8888: |
| case gfx::BufferFormat::BGRA_8888: |
| return base::checked_cast<GLint>(stride) / 4; |
| case gfx::BufferFormat::RGBA_F16: |
| return base::checked_cast<GLint>(stride) / 8; |
| case gfx::BufferFormat::R_8: |
| return base::checked_cast<GLint>(stride); |
| case gfx::BufferFormat::ATC: |
| case gfx::BufferFormat::ATCIA: |
| case gfx::BufferFormat::DXT1: |
| case gfx::BufferFormat::DXT5: |
| case gfx::BufferFormat::ETC1: |
| case gfx::BufferFormat::YVU_420: |
| case gfx::BufferFormat::YUV_420_BIPLANAR: |
| case gfx::BufferFormat::UYVY_422: |
| NOTREACHED(); |
| return 0; |
| } |
| |
| NOTREACHED(); |
| return 0; |
| } |
| |
| template <typename F> |
| std::unique_ptr<uint8_t[]> GLES2RGBData(const gfx::Size& size, |
| size_t stride, |
| const uint8_t* data, |
| F const& data_to_rgb, |
| GLenum* data_format, |
| GLenum* data_type, |
| GLint* data_row_length) { |
| TRACE_EVENT2("gpu", "GLES2RGBData", "width", size.width(), "height", |
| size.height()); |
| |
| // Four-byte row alignment as specified by glPixelStorei with argument |
| // GL_UNPACK_ALIGNMENT set to 4. |
| size_t gles2_rgb_data_stride = (size.width() * 3 + 3) & ~3; |
| std::unique_ptr<uint8_t[]> gles2_rgb_data( |
| new uint8_t[gles2_rgb_data_stride * size.height()]); |
| |
| for (int y = 0; y < size.height(); ++y) { |
| for (int x = 0; x < size.width(); ++x) { |
| data_to_rgb(&data[y * stride + x * 4], |
| &gles2_rgb_data[y * gles2_rgb_data_stride + x * 3]); |
| } |
| } |
| |
| *data_format = GL_RGB; |
| *data_type = GL_UNSIGNED_BYTE; |
| *data_row_length = size.width(); |
| return gles2_rgb_data; |
| } |
| |
| std::unique_ptr<uint8_t[]> GLES2RGB565Data(const gfx::Size& size, |
| size_t stride, |
| const uint8_t* data, |
| GLenum* data_format, |
| GLenum* data_type, |
| GLint* data_row_length) { |
| TRACE_EVENT2("gpu", "GLES2RGB565Data", "width", size.width(), "height", |
| size.height()); |
| |
| // Four-byte row alignment as specified by glPixelStorei with argument |
| // GL_UNPACK_ALIGNMENT set to 4. |
| size_t gles2_rgb_data_stride = (size.width() * 2 + 3) & ~3; |
| std::unique_ptr<uint8_t[]> gles2_rgb_data( |
| new uint8_t[gles2_rgb_data_stride * size.height()]); |
| |
| for (int y = 0; y < size.height(); ++y) { |
| for (int x = 0; x < size.width(); ++x) { |
| const uint16_t* src = |
| reinterpret_cast<const uint16_t*>(&data[y * stride + x * 2]); |
| uint16_t* dst = reinterpret_cast<uint16_t*>( |
| &gles2_rgb_data[y * gles2_rgb_data_stride + x * 2]); |
| *dst = (((*src & 0x1f) >> 0) << 11) | (((*src & 0x7e0) >> 5) << 5) | |
| (((*src & 0xf800) >> 11) << 5); |
| } |
| } |
| |
| *data_format = GL_RGB; |
| *data_type = GL_UNSIGNED_SHORT_5_6_5; |
| *data_row_length = size.width(); |
| return gles2_rgb_data; |
| } |
| |
| std::unique_ptr<uint8_t[]> GLES2Data(const gfx::Size& size, |
| gfx::BufferFormat format, |
| size_t stride, |
| const uint8_t* data, |
| GLenum* data_format, |
| GLenum* data_type, |
| GLint* data_row_length) { |
| TRACE_EVENT2("gpu", "GLES2Data", "width", size.width(), "height", |
| size.height()); |
| |
| switch (format) { |
| case gfx::BufferFormat::RGBX_8888: |
| return GLES2RGBData(size, stride, data, |
| [](const uint8_t* src, uint8_t* dst) { |
| dst[0] = src[0]; |
| dst[1] = src[1]; |
| dst[2] = src[2]; |
| }, |
| data_format, data_type, data_row_length); |
| case gfx::BufferFormat::BGR_565: |
| return GLES2RGB565Data(size, stride, data, data_format, data_type, |
| data_row_length); |
| case gfx::BufferFormat::BGRX_8888: |
| return GLES2RGBData(size, stride, data, |
| [](const uint8_t* src, uint8_t* dst) { |
| dst[0] = src[2]; |
| dst[1] = src[1]; |
| dst[2] = src[0]; |
| }, |
| data_format, data_type, data_row_length); |
| case gfx::BufferFormat::RGBA_4444: |
| case gfx::BufferFormat::RGBA_8888: |
| case gfx::BufferFormat::BGRA_8888: |
| case gfx::BufferFormat::RGBA_F16: |
| case gfx::BufferFormat::R_8: |
| case gfx::BufferFormat::RG_88: { |
| size_t gles2_data_stride = |
| RowSizeForBufferFormat(size.width(), format, 0); |
| if (stride == gles2_data_stride || |
| g_current_gl_driver->ext.b_GL_EXT_unpack_subimage) |
| return nullptr; // No data conversion needed |
| |
| std::unique_ptr<uint8_t[]> gles2_data( |
| new uint8_t[gles2_data_stride * size.height()]); |
| for (int y = 0; y < size.height(); ++y) { |
| memcpy(&gles2_data[y * gles2_data_stride], &data[y * stride], |
| gles2_data_stride); |
| } |
| *data_row_length = size.width(); |
| return gles2_data; |
| } |
| case gfx::BufferFormat::ATC: |
| case gfx::BufferFormat::ATCIA: |
| case gfx::BufferFormat::DXT1: |
| case gfx::BufferFormat::DXT5: |
| case gfx::BufferFormat::ETC1: |
| return nullptr; // No data conversion needed |
| case gfx::BufferFormat::YVU_420: |
| case gfx::BufferFormat::YUV_420_BIPLANAR: |
| case gfx::BufferFormat::UYVY_422: |
| NOTREACHED(); |
| return nullptr; |
| } |
| |
| NOTREACHED(); |
| return nullptr; |
| } |
| |
| } // namespace |
| |
| GLImageMemory::GLImageMemory(const gfx::Size& size, unsigned internalformat) |
| : size_(size), |
| internalformat_(internalformat), |
| memory_(nullptr), |
| format_(gfx::BufferFormat::RGBA_8888), |
| stride_(0) {} |
| |
| GLImageMemory::~GLImageMemory() {} |
| |
| // static |
| GLImageMemory* GLImageMemory::FromGLImage(GLImage* image) { |
| if (!image || image->GetType() != Type::MEMORY) |
| return nullptr; |
| return static_cast<GLImageMemory*>(image); |
| } |
| |
| bool GLImageMemory::Initialize(const unsigned char* memory, |
| gfx::BufferFormat format, |
| size_t stride) { |
| if (!ValidInternalFormat(internalformat_)) { |
| LOG(ERROR) << "Invalid internalformat: " << internalformat_; |
| return false; |
| } |
| |
| if (!ValidFormat(format)) { |
| LOG(ERROR) << "Invalid format: " << static_cast<int>(format); |
| return false; |
| } |
| |
| if (stride < RowSizeForBufferFormat(size_.width(), format, 0) || stride & 3) { |
| LOG(ERROR) << "Invalid stride: " << stride; |
| return false; |
| } |
| |
| DCHECK(memory); |
| DCHECK(!memory_); |
| DCHECK(!IsCompressedFormat(format) || size_.width() % 4 == 0); |
| DCHECK(!IsCompressedFormat(format) || size_.height() % 4 == 0); |
| memory_ = memory; |
| format_ = format; |
| stride_ = stride; |
| return true; |
| } |
| |
| gfx::Size GLImageMemory::GetSize() { |
| return size_; |
| } |
| |
| unsigned GLImageMemory::GetInternalFormat() { |
| return internalformat_; |
| } |
| |
| bool GLImageMemory::BindTexImage(unsigned target) { |
| return false; |
| } |
| |
| bool GLImageMemory::CopyTexImage(unsigned target) { |
| TRACE_EVENT2("gpu", "GLImageMemory::CopyTexImage", "width", size_.width(), |
| "height", size_.height()); |
| |
| // GL_TEXTURE_EXTERNAL_OES is not a supported target. |
| if (target == GL_TEXTURE_EXTERNAL_OES) |
| return false; |
| |
| if (IsCompressedFormat(format_)) { |
| glCompressedTexImage2D( |
| target, 0, TextureFormat(format_), size_.width(), size_.height(), 0, |
| static_cast<GLsizei>(BufferSizeForBufferFormat(size_, format_)), |
| memory_); |
| } else { |
| GLenum data_format = DataFormat(format_); |
| GLenum data_type = DataType(format_); |
| GLint data_row_length = DataRowLength(stride_, format_); |
| std::unique_ptr<uint8_t[]> gles2_data; |
| |
| if (GLContext::GetCurrent()->GetVersionInfo()->is_es) { |
| gles2_data = GLES2Data(size_, format_, stride_, memory_, &data_format, |
| &data_type, &data_row_length); |
| } |
| |
| if (data_row_length != size_.width()) |
| glPixelStorei(GL_UNPACK_ROW_LENGTH, data_row_length); |
| |
| glTexImage2D(target, 0, TextureFormat(format_), size_.width(), |
| size_.height(), 0, data_format, data_type, |
| gles2_data ? gles2_data.get() : memory_); |
| |
| if (data_row_length != size_.width()) |
| glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); |
| } |
| |
| return true; |
| } |
| |
| bool GLImageMemory::CopyTexSubImage(unsigned target, |
| const gfx::Point& offset, |
| const gfx::Rect& rect) { |
| TRACE_EVENT2("gpu", "GLImageMemory::CopyTexSubImage", "width", rect.width(), |
| "height", rect.height()); |
| |
| // GL_TEXTURE_EXTERNAL_OES is not a supported target. |
| if (target == GL_TEXTURE_EXTERNAL_OES) |
| return false; |
| |
| // Sub width is not supported. |
| if (rect.width() != size_.width()) |
| return false; |
| |
| const uint8_t* data = memory_ + rect.y() * stride_; |
| if (IsCompressedFormat(format_)) { |
| // Height must be a multiple of 4. |
| if (rect.height() % 4) |
| return false; |
| |
| glCompressedTexSubImage2D( |
| target, 0, offset.x(), offset.y(), rect.width(), rect.height(), |
| DataFormat(format_), |
| static_cast<GLsizei>(BufferSizeForBufferFormat(rect.size(), format_)), |
| data); |
| } else { |
| GLenum data_format = DataFormat(format_); |
| GLenum data_type = DataType(format_); |
| GLint data_row_length = DataRowLength(stride_, format_); |
| std::unique_ptr<uint8_t[]> gles2_data; |
| |
| if (GLContext::GetCurrent()->GetVersionInfo()->is_es) { |
| gles2_data = GLES2Data(rect.size(), format_, stride_, data, &data_format, |
| &data_type, &data_row_length); |
| } |
| |
| if (data_row_length != rect.width()) |
| glPixelStorei(GL_UNPACK_ROW_LENGTH, data_row_length); |
| |
| glTexSubImage2D(target, 0, offset.x(), offset.y(), rect.width(), |
| rect.height(), data_format, data_type, |
| gles2_data ? gles2_data.get() : data); |
| |
| if (data_row_length != rect.width()) |
| glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); |
| } |
| |
| return true; |
| } |
| |
| bool GLImageMemory::ScheduleOverlayPlane(gfx::AcceleratedWidget widget, |
| int z_order, |
| gfx::OverlayTransform transform, |
| const gfx::Rect& bounds_rect, |
| const gfx::RectF& crop_rect) { |
| return false; |
| } |
| |
| GLImageMemory::Type GLImageMemory::GetType() const { |
| return Type::MEMORY; |
| } |
| |
| // static |
| unsigned GLImageMemory::GetInternalFormatForTesting(gfx::BufferFormat format) { |
| DCHECK(ValidFormat(format)); |
| return TextureFormat(format); |
| } |
| |
| } // namespace gl |