diff options
author | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2018-05-15 10:20:33 +0200 |
---|---|---|
committer | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2018-05-15 10:28:57 +0000 |
commit | d17ea114e5ef69ad5d5d7413280a13e6428098aa (patch) | |
tree | 2c01a75df69f30d27b1432467cfe7c1467a498da /chromium/third_party/blink/renderer/platform/graphics/gpu | |
parent | 8c5c43c7b138c9b4b0bf56d946e61d3bbc111bec (diff) | |
download | qtwebengine-chromium-d17ea114e5ef69ad5d5d7413280a13e6428098aa.tar.gz |
BASELINE: Update Chromium to 67.0.3396.47
Change-Id: Idcb1341782e417561a2473eeecc82642dafda5b7
Reviewed-by: Michal Klocek <michal.klocek@qt.io>
Diffstat (limited to 'chromium/third_party/blink/renderer/platform/graphics/gpu')
25 files changed, 9640 insertions, 0 deletions
diff --git a/chromium/third_party/blink/renderer/platform/graphics/gpu/DEPS b/chromium/third_party/blink/renderer/platform/graphics/gpu/DEPS new file mode 100644 index 00000000000..adce483a8d2 --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/graphics/gpu/DEPS @@ -0,0 +1,13 @@ +include_rules = [ + "+mojo/public/cpp/bindings/binding.h", + "+mojo/public/cpp/system/platform_handle.h", + "+device/vr/public/mojom/vr_service.mojom-blink.h", + "+gpu/command_buffer/client/gles2_interface.h", + "+gpu/command_buffer/client/gles2_interface_stub.h", + "+gpu/command_buffer/common/capabilities.h", + "+gpu/command_buffer/common/gpu_memory_buffer_support.h", + "+gpu/command_buffer/common/mailbox_holder.h", + "+gpu/config/gpu_driver_bug_workaround_type.h", + "+gpu/config/gpu_feature_info.h", + "+ui/gfx/gpu_fence.h", +] diff --git a/chromium/third_party/blink/renderer/platform/graphics/gpu/OWNERS b/chromium/third_party/blink/renderer/platform/graphics/gpu/OWNERS new file mode 100644 index 00000000000..45ab8c73e8c --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/graphics/gpu/OWNERS @@ -0,0 +1,4 @@ +# The bulk of the files in this directory are related to WebGL, though +# there are a couple used by accelerated 2D canvas as well. + +# COMPONENT: Blink>WebGL diff --git a/chromium/third_party/blink/renderer/platform/graphics/gpu/drawing_buffer.cc b/chromium/third_party/blink/renderer/platform/graphics/gpu/drawing_buffer.cc new file mode 100644 index 00000000000..dded64ab9ff --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/graphics/gpu/drawing_buffer.cc @@ -0,0 +1,1603 @@ +/* + * Copyright (c) 2010, Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "third_party/blink/renderer/platform/graphics/gpu/drawing_buffer.h" + +#include <algorithm> +#include <memory> +#include <utility> + +#include "build/build_config.h" +#include "cc/layers/texture_layer.h" +#include "components/viz/common/quads/shared_bitmap.h" +#include "components/viz/common/resources/bitmap_allocation.h" +#include "components/viz/common/resources/transferable_resource.h" +#include "gpu/GLES2/gl2extchromium.h" +#include "gpu/command_buffer/client/gles2_interface.h" +#include "gpu/command_buffer/client/gpu_memory_buffer_manager.h" +#include "gpu/command_buffer/common/capabilities.h" +#include "gpu/command_buffer/common/gpu_memory_buffer_support.h" +#include "gpu/config/gpu_driver_bug_workaround_type.h" +#include "gpu/config/gpu_feature_info.h" +#include "skia/ext/texture_handle.h" +#include "third_party/blink/public/platform/platform.h" +#include "third_party/blink/public/platform/web_compositor_support.h" +#include "third_party/blink/public/platform/web_external_texture_layer.h" +#include "third_party/blink/renderer/platform/graphics/accelerated_static_bitmap_image.h" +#include "third_party/blink/renderer/platform/graphics/gpu/extensions_3d_util.h" +#include "third_party/blink/renderer/platform/graphics/graphics_layer.h" +#include "third_party/blink/renderer/platform/graphics/unaccelerated_static_bitmap_image.h" +#include "third_party/blink/renderer/platform/graphics/web_graphics_context_3d_provider_wrapper.h" +#include "third_party/blink/renderer/platform/instrumentation/tracing/trace_event.h" +#include "third_party/blink/renderer/platform/runtime_enabled_features.h" +#include "third_party/blink/renderer/platform/wtf/checked_numeric.h" +#include "third_party/blink/renderer/platform/wtf/typed_arrays/array_buffer_contents.h" +#include "third_party/skia/include/core/SkColorSpaceXform.h" +#include "third_party/skia/include/core/SkSurface.h" +#include "third_party/skia/include/gpu/GrContext.h" +#include "third_party/skia/include/gpu/gl/GrGLTypes.h" +#include "v8/include/v8.h" + +namespace blink { + +namespace { + +const float kResourceAdjustedRatio = 0.5; + +static bool g_should_fail_drawing_buffer_creation_for_testing = false; + +} // namespace + +scoped_refptr<DrawingBuffer> DrawingBuffer::Create( + std::unique_ptr<WebGraphicsContext3DProvider> context_provider, + bool using_gpu_compositing, + Client* client, + const IntSize& size, + bool premultiplied_alpha, + bool want_alpha_channel, + bool want_depth_buffer, + bool want_stencil_buffer, + bool want_antialiasing, + PreserveDrawingBuffer preserve, + WebGLVersion webgl_version, + ChromiumImageUsage chromium_image_usage, + const CanvasColorParams& color_params) { + if (g_should_fail_drawing_buffer_creation_for_testing) { + g_should_fail_drawing_buffer_creation_for_testing = false; + return nullptr; + } + + CheckedNumeric<int> data_size = color_params.BytesPerPixel(); + data_size *= size.Width(); + data_size *= size.Height(); + if (!data_size.IsValid() || + data_size.ValueOrDie() > v8::TypedArray::kMaxLength) + return nullptr; + + DCHECK(context_provider); + std::unique_ptr<Extensions3DUtil> extensions_util = + Extensions3DUtil::Create(context_provider->ContextGL()); + if (!extensions_util->IsValid()) { + // This might be the first time we notice that the GL context is lost. + return nullptr; + } + DCHECK(extensions_util->SupportsExtension("GL_OES_packed_depth_stencil")); + extensions_util->EnsureExtensionEnabled("GL_OES_packed_depth_stencil"); + bool multisample_supported = + want_antialiasing && + (extensions_util->SupportsExtension( + "GL_CHROMIUM_framebuffer_multisample") || + extensions_util->SupportsExtension( + "GL_EXT_multisampled_render_to_texture")) && + extensions_util->SupportsExtension("GL_OES_rgb8_rgba8"); + if (multisample_supported) { + extensions_util->EnsureExtensionEnabled("GL_OES_rgb8_rgba8"); + if (extensions_util->SupportsExtension( + "GL_CHROMIUM_framebuffer_multisample")) { + extensions_util->EnsureExtensionEnabled( + "GL_CHROMIUM_framebuffer_multisample"); + } else { + extensions_util->EnsureExtensionEnabled( + "GL_EXT_multisampled_render_to_texture"); + } + } + bool discard_framebuffer_supported = + extensions_util->SupportsExtension("GL_EXT_discard_framebuffer"); + if (discard_framebuffer_supported) + extensions_util->EnsureExtensionEnabled("GL_EXT_discard_framebuffer"); + + scoped_refptr<DrawingBuffer> drawing_buffer = + base::AdoptRef(new DrawingBuffer( + std::move(context_provider), using_gpu_compositing, + std::move(extensions_util), client, discard_framebuffer_supported, + want_alpha_channel, premultiplied_alpha, preserve, webgl_version, + want_depth_buffer, want_stencil_buffer, chromium_image_usage, + color_params)); + if (!drawing_buffer->Initialize(size, multisample_supported)) { + drawing_buffer->BeginDestruction(); + return scoped_refptr<DrawingBuffer>(); + } + return drawing_buffer; +} + +void DrawingBuffer::ForceNextDrawingBufferCreationToFail() { + g_should_fail_drawing_buffer_creation_for_testing = true; +} + +DrawingBuffer::DrawingBuffer( + std::unique_ptr<WebGraphicsContext3DProvider> context_provider, + bool using_gpu_compositing, + std::unique_ptr<Extensions3DUtil> extensions_util, + Client* client, + bool discard_framebuffer_supported, + bool want_alpha_channel, + bool premultiplied_alpha, + PreserveDrawingBuffer preserve, + WebGLVersion webgl_version, + bool want_depth, + bool want_stencil, + ChromiumImageUsage chromium_image_usage, + const CanvasColorParams& color_params) + : client_(client), + preserve_drawing_buffer_(preserve), + webgl_version_(webgl_version), + context_provider_(std::make_unique<WebGraphicsContext3DProviderWrapper>( + std::move(context_provider))), + gl_(this->ContextProvider()->ContextGL()), + extensions_util_(std::move(extensions_util)), + discard_framebuffer_supported_(discard_framebuffer_supported), + want_alpha_channel_(want_alpha_channel), + premultiplied_alpha_(premultiplied_alpha), + using_gpu_compositing_(using_gpu_compositing), + want_depth_(want_depth), + want_stencil_(want_stencil), + storage_color_space_(color_params.GetStorageGfxColorSpace()), + sampler_color_space_(color_params.GetSamplerGfxColorSpace()), + use_half_float_storage_(color_params.PixelFormat() == + kF16CanvasPixelFormat), + chromium_image_usage_(chromium_image_usage) { + // Used by browser tests to detect the use of a DrawingBuffer. + TRACE_EVENT_INSTANT0("test_gpu", "DrawingBufferCreation", + TRACE_EVENT_SCOPE_GLOBAL); +} + +DrawingBuffer::~DrawingBuffer() { + DCHECK(destruction_in_progress_); + layer_.reset(); + context_provider_.reset(); +} + +bool DrawingBuffer::MarkContentsChanged() { + if (contents_change_resolved_ || !contents_changed_) { + contents_change_resolved_ = false; + contents_changed_ = true; + return true; + } + return false; +} + +void DrawingBuffer::ResetBuffersToAutoClear() { + GLuint buffers = GL_COLOR_BUFFER_BIT; + if (want_depth_) + buffers |= GL_DEPTH_BUFFER_BIT; + if (want_stencil_ || has_implicit_stencil_buffer_) + buffers |= GL_STENCIL_BUFFER_BIT; + SetBuffersToAutoClear(buffers); +} + +void DrawingBuffer::SetBuffersToAutoClear(GLbitfield buffers) { + if (preserve_drawing_buffer_ == kDiscard) { + buffers_to_auto_clear_ = buffers; + } else { + DCHECK_EQ(0u, buffers_to_auto_clear_); + } +} + +GLbitfield DrawingBuffer::GetBuffersToAutoClear() const { + return buffers_to_auto_clear_; +} + +gpu::gles2::GLES2Interface* DrawingBuffer::ContextGL() { + return gl_; +} + +WebGraphicsContext3DProvider* DrawingBuffer::ContextProvider() { + return context_provider_->ContextProvider(); +} + +base::WeakPtr<WebGraphicsContext3DProviderWrapper> +DrawingBuffer::ContextProviderWeakPtr() { + return context_provider_->GetWeakPtr(); +} + +void DrawingBuffer::SetIsHidden(bool hidden) { + if (is_hidden_ == hidden) + return; + is_hidden_ = hidden; + if (is_hidden_) + recycled_color_buffer_queue_.clear(); +} + +void DrawingBuffer::SetFilterQuality(SkFilterQuality filter_quality) { + if (filter_quality_ != filter_quality) { + filter_quality_ = filter_quality; + if (layer_) + layer_->SetNearestNeighbor(filter_quality == kNone_SkFilterQuality); + } +} + +bool DrawingBuffer::RequiresAlphaChannelToBePreserved() { + return client_->DrawingBufferClientIsBoundForDraw() && + DefaultBufferRequiresAlphaChannelToBePreserved(); +} + +bool DrawingBuffer::DefaultBufferRequiresAlphaChannelToBePreserved() { + return !want_alpha_channel_ && have_alpha_channel_; +} + +DrawingBuffer::RegisteredBitmap DrawingBuffer::CreateOrRecycleBitmap( + cc::SharedBitmapIdRegistrar* bitmap_registrar) { + auto it = std::remove_if(recycled_bitmaps_.begin(), recycled_bitmaps_.end(), + [this](const RegisteredBitmap& registered) { + return registered.bitmap->size() != size_; + }); + recycled_bitmaps_.Shrink(it - recycled_bitmaps_.begin()); + + if (!recycled_bitmaps_.IsEmpty()) { + RegisteredBitmap recycled = std::move(recycled_bitmaps_.back()); + recycled_bitmaps_.pop_back(); + DCHECK(recycled.bitmap->size() == size_); + return recycled; + } + + viz::SharedBitmapId id = viz::SharedBitmap::GenerateId(); + std::unique_ptr<base::SharedMemory> shm = + viz::bitmap_allocation::AllocateMappedBitmap(size_, viz::RGBA_8888); + auto bitmap = base::MakeRefCounted<cc::CrossThreadSharedBitmap>( + id, std::move(shm), size_, viz::RGBA_8888); + RegisteredBitmap registered = { + bitmap, bitmap_registrar->RegisterSharedBitmapId(id, bitmap)}; + return registered; +} + +bool DrawingBuffer::PrepareTransferableResource( + cc::SharedBitmapIdRegistrar* bitmap_registrar, + viz::TransferableResource* out_resource, + std::unique_ptr<viz::SingleReleaseCallback>* out_release_callback) { + ScopedStateRestorer scoped_state_restorer(this); + bool force_gpu_result = false; + return PrepareTransferableResourceInternal( + bitmap_registrar, out_resource, out_release_callback, force_gpu_result); +} + +bool DrawingBuffer::PrepareTransferableResourceInternal( + cc::SharedBitmapIdRegistrar* bitmap_registrar, + viz::TransferableResource* out_resource, + std::unique_ptr<viz::SingleReleaseCallback>* out_release_callback, + bool force_gpu_result) { + DCHECK(state_restorer_); + if (destruction_in_progress_) { + // It can be hit in the following sequence. + // 1. WebGL draws something. + // 2. The compositor begins the frame. + // 3. Javascript makes a context lost using WEBGL_lose_context extension. + // 4. Here. + return false; + } + DCHECK(!is_hidden_); + if (!contents_changed_) + return false; + + // If the context is lost, we don't know if we should be producing GPU or + // software frames, until we get a new context, since the compositor will + // be trying to get a new context and may change modes. + if (gl_->GetGraphicsResetStatusKHR() != GL_NO_ERROR) + return false; + + TRACE_EVENT0("blink,rail", "DrawingBuffer::prepareMailbox"); + + // Resolve the multisampled buffer into the texture attached to fbo_. + ResolveIfNeeded(); + + if (!using_gpu_compositing_ && !force_gpu_result) { + FinishPrepareTransferableResourceSoftware(bitmap_registrar, out_resource, + out_release_callback); + } else { + FinishPrepareTransferableResourceGpu(out_resource, out_release_callback); + } + return true; +} + +void DrawingBuffer::FinishPrepareTransferableResourceSoftware( + cc::SharedBitmapIdRegistrar* bitmap_registrar, + viz::TransferableResource* out_resource, + std::unique_ptr<viz::SingleReleaseCallback>* out_release_callback) { + DCHECK(state_restorer_); + RegisteredBitmap registered = CreateOrRecycleBitmap(bitmap_registrar); + + // Read the framebuffer into |bitmap|. + { + unsigned char* pixels = static_cast<unsigned char*>( + registered.bitmap->shared_memory()->memory()); + DCHECK(pixels); + bool need_premultiply = want_alpha_channel_ && !premultiplied_alpha_; + WebGLImageConversion::AlphaOp op = + need_premultiply ? WebGLImageConversion::kAlphaDoPremultiply + : WebGLImageConversion::kAlphaDoNothing; + state_restorer_->SetFramebufferBindingDirty(); + gl_->BindFramebuffer(GL_FRAMEBUFFER, fbo_); + ReadBackFramebuffer(pixels, Size().Width(), Size().Height(), kReadbackSkia, + op); + } + + *out_resource = viz::TransferableResource::MakeSoftware( + registered.bitmap->id(), /*sequence_number=*/0, size_, viz::RGBA_8888); + out_resource->color_space = storage_color_space_; + + // This holds a ref on the DrawingBuffer that will keep it alive until the + // mailbox is released (and while the release callback is running). It also + // owns the SharedBitmap. + auto func = WTF::Bind(&DrawingBuffer::MailboxReleasedSoftware, + scoped_refptr<DrawingBuffer>(this), + WTF::Passed(std::move(registered))); + *out_release_callback = viz::SingleReleaseCallback::Create(std::move(func)); + + ResetBuffersToAutoClear(); +} + +void DrawingBuffer::FinishPrepareTransferableResourceGpu( + viz::TransferableResource* out_resource, + std::unique_ptr<viz::SingleReleaseCallback>* out_release_callback) { + DCHECK(state_restorer_); + if (webgl_version_ > kWebGL1) { + state_restorer_->SetPixelUnpackBufferBindingDirty(); + gl_->BindBuffer(GL_PIXEL_UNPACK_BUFFER, 0); + } + + if (premultiplied_alpha_false_texture_) { + // The rendering results are in this texture rather than the + // back_color_buffer_'s texture. Copy them in, multiplying the alpha channel + // into the color channels. + gl_->CopySubTextureCHROMIUM(premultiplied_alpha_false_texture_, 0, + texture_target_, back_color_buffer_->texture_id, + 0, 0, 0, 0, 0, size_.Width(), size_.Height(), + GL_FALSE, GL_TRUE, GL_FALSE); + } + + // Specify the buffer that we will put in the mailbox. + scoped_refptr<ColorBuffer> color_buffer_for_mailbox; + if (preserve_drawing_buffer_ == kDiscard) { + // If we can discard the backbuffer, send the old backbuffer directly + // into the mailbox, and allocate (or recycle) a new backbuffer. + color_buffer_for_mailbox = back_color_buffer_; + back_color_buffer_ = CreateOrRecycleColorBuffer(); + AttachColorBufferToReadFramebuffer(); + + // Explicitly specify that m_fbo (which is now bound to the just-allocated + // m_backColorBuffer) is not initialized, to save GPU memory bandwidth for + // tile-based GPU architectures. + if (discard_framebuffer_supported_) { + const GLenum kAttachments[3] = {GL_COLOR_ATTACHMENT0, GL_DEPTH_ATTACHMENT, + GL_STENCIL_ATTACHMENT}; + state_restorer_->SetFramebufferBindingDirty(); + gl_->BindFramebuffer(GL_FRAMEBUFFER, fbo_); + gl_->DiscardFramebufferEXT(GL_FRAMEBUFFER, 3, kAttachments); + } + } else { + // If we can't discard the backbuffer, create (or recycle) a buffer to put + // in the mailbox, and copy backbuffer's contents there. + color_buffer_for_mailbox = CreateOrRecycleColorBuffer(); + gl_->CopySubTextureCHROMIUM( + back_color_buffer_->texture_id, 0, texture_target_, + color_buffer_for_mailbox->texture_id, 0, 0, 0, 0, 0, size_.Width(), + size_.Height(), GL_FALSE, GL_FALSE, GL_FALSE); + } + + // Put colorBufferForMailbox into its mailbox, and populate its + // produceSyncToken with that point. + { + gl_->ProduceTextureDirectCHROMIUM(color_buffer_for_mailbox->texture_id, + color_buffer_for_mailbox->mailbox.name); + // It's critical to order the execution of this context's work relative + // to other contexts, in particular the compositor. Previously this + // used to be a Flush, and there was a bug that we didn't flush before + // synchronizing with the composition, and on some platforms this caused + // incorrect rendering with complex WebGL content that wasn't always + // properly flushed to the driver. There is now a basic assumption that + // there are implicit flushes between contexts at the lowest level. + gl_->GenUnverifiedSyncTokenCHROMIUM( + color_buffer_for_mailbox->produce_sync_token.GetData()); +#if defined(OS_MACOSX) + // Needed for GPU back-pressure on macOS. Used to be in the middle + // of the commands above; try to move it to the bottom to allow + // them to be treated atomically. + gl_->DescheduleUntilFinishedCHROMIUM(); +#endif + } + + // Populate the output mailbox and callback. + { + bool is_overlay_candidate = color_buffer_for_mailbox->image_id != 0; + *out_resource = viz::TransferableResource::MakeGLOverlay( + color_buffer_for_mailbox->mailbox, GL_LINEAR, texture_target_, + color_buffer_for_mailbox->produce_sync_token, gfx::Size(size_), + is_overlay_candidate); + out_resource->color_space = sampler_color_space_; + out_resource->format = viz::RGBA_8888; + + // This holds a ref on the DrawingBuffer that will keep it alive until the + // mailbox is released (and while the release callback is running). + auto func = + WTF::Bind(&DrawingBuffer::MailboxReleasedGpu, + scoped_refptr<DrawingBuffer>(this), color_buffer_for_mailbox); + *out_release_callback = viz::SingleReleaseCallback::Create(std::move(func)); + } + + // Point |m_frontColorBuffer| to the buffer that we are now presenting. + front_color_buffer_ = color_buffer_for_mailbox; + + contents_changed_ = false; + ResetBuffersToAutoClear(); +} + +void DrawingBuffer::MailboxReleasedGpu(scoped_refptr<ColorBuffer> color_buffer, + const gpu::SyncToken& sync_token, + bool lost_resource) { + // If the mailbox has been returned by the compositor then it is no + // longer being presented, and so is no longer the front buffer. + if (color_buffer == front_color_buffer_) + front_color_buffer_ = nullptr; + + // Update the SyncToken to ensure that we will wait for it even if we + // immediately destroy this buffer. + color_buffer->receive_sync_token = sync_token; + + if (destruction_in_progress_ || color_buffer->size != size_ || + gl_->GetGraphicsResetStatusKHR() != GL_NO_ERROR || lost_resource || + is_hidden_) { + return; + } + + // Creation of image backed mailboxes is very expensive, so be less + // aggressive about pruning them. Pruning is done in FIFO order. + size_t cache_limit = 1; + if (ShouldUseChromiumImage()) + cache_limit = 4; + while (recycled_color_buffer_queue_.size() >= cache_limit) + recycled_color_buffer_queue_.TakeLast(); + + recycled_color_buffer_queue_.push_front(color_buffer); +} + +void DrawingBuffer::MailboxReleasedSoftware(RegisteredBitmap registered, + const gpu::SyncToken& sync_token, + bool lost_resource) { + DCHECK(!sync_token.HasData()); // No sync tokens for software resources. + if (destruction_in_progress_ || lost_resource || is_hidden_ || + registered.bitmap->size() != size_) { + // Just delete the RegisteredBitmap, which will free the memory and + // unregister it with the compositor. + return; + } + + recycled_bitmaps_.push_back(std::move(registered)); +} + +scoped_refptr<StaticBitmapImage> DrawingBuffer::TransferToStaticBitmapImage( + std::unique_ptr<viz::SingleReleaseCallback>* out_release_callback) { + ScopedStateRestorer scoped_state_restorer(this); + + viz::TransferableResource transferable_resource; + std::unique_ptr<viz::SingleReleaseCallback> release_callback; + constexpr bool force_gpu_result = true; + if (!PrepareTransferableResourceInternal(nullptr, &transferable_resource, + &release_callback, + force_gpu_result)) { + // If we can't get a mailbox, return an transparent black ImageBitmap. + // The only situation in which this could happen is when two or more calls + // to transferToImageBitmap are made back-to-back, or when the context gets + // lost. We intentionally leave the transparent black image in legacy color + // space. + sk_sp<SkSurface> surface = + SkSurface::MakeRasterN32Premul(size_.Width(), size_.Height()); + return StaticBitmapImage::Create(surface->makeImageSnapshot()); + } + + DCHECK_EQ(size_.Width(), transferable_resource.size.width()); + DCHECK_EQ(size_.Height(), transferable_resource.size.height()); + + // Make our own textureId that is a reference on the same texture backing + // being used as the front buffer (which was returned from + // PrepareTransferableResourceInternal()). We do not need to wait on the sync + // token in |transferable_resource| since the mailbox was produced on the same + // |m_gl| context that we are using here. Similarly, the |release_callback| + // will run on the same context so we don't need to send a sync token for this + // consume action back to it. + // TODO(danakj): Instead of using PrepareTransferableResourceInternal(), we + // could just use the actual texture id and avoid needing to produce/consume a + // mailbox. + GLuint texture_id = gl_->CreateAndConsumeTextureCHROMIUM( + transferable_resource.mailbox_holder.mailbox.name); + + if (out_release_callback) { + // Allow the consumer to release the resource when done using it, so it can + // be recycled. + *out_release_callback = std::move(release_callback); + } else { + // Return the mailbox but report that the resource is lost to prevent trying + // to use the backing for future frames. We keep it alive with our own + // reference to the backing via our |textureId|. + release_callback->Run(gpu::SyncToken(), true /* lost_resource */); + } + + // We reuse the same mailbox name from above since our texture id was consumed + // from it. + const auto& sk_image_mailbox = transferable_resource.mailbox_holder.mailbox; + // Use the sync token generated after producing the mailbox. Waiting for this + // before trying to use the mailbox with some other context will ensure it is + // valid. We wouldn't need to wait for the consume done in this function + // because the texture id it generated would only be valid for the + // DrawingBuffer's context anyways. + const auto& sk_image_sync_token = + transferable_resource.mailbox_holder.sync_token; + + // TODO(xidachen): Create a small pool of recycled textures from + // ImageBitmapRenderingContext's transferFromImageBitmap, and try to use them + // in DrawingBuffer. + return AcceleratedStaticBitmapImage::CreateFromWebGLContextImage( + sk_image_mailbox, sk_image_sync_token, texture_id, + context_provider_->GetWeakPtr(), size_); +} + +scoped_refptr<DrawingBuffer::ColorBuffer> DrawingBuffer::CreateOrRecycleColorBuffer() { + DCHECK(state_restorer_); + if (!recycled_color_buffer_queue_.IsEmpty()) { + scoped_refptr<ColorBuffer> recycled = + recycled_color_buffer_queue_.TakeLast(); + if (recycled->receive_sync_token.HasData()) + gl_->WaitSyncTokenCHROMIUM(recycled->receive_sync_token.GetData()); + DCHECK(recycled->size == size_); + return recycled; + } + return CreateColorBuffer(size_); +} + +DrawingBuffer::ScopedRGBEmulationForBlitFramebuffer:: + ScopedRGBEmulationForBlitFramebuffer(DrawingBuffer* drawing_buffer, + bool is_user_draw_framebuffer_bound) + : drawing_buffer_(drawing_buffer) { + doing_work_ = drawing_buffer->SetupRGBEmulationForBlitFramebuffer( + is_user_draw_framebuffer_bound); +} + +DrawingBuffer::ScopedRGBEmulationForBlitFramebuffer:: + ~ScopedRGBEmulationForBlitFramebuffer() { + if (doing_work_) { + drawing_buffer_->CleanupRGBEmulationForBlitFramebuffer(); + } +} + +DrawingBuffer::ColorBuffer::ColorBuffer( + DrawingBuffer* drawing_buffer, + const IntSize& size, + GLuint texture_id, + GLuint image_id, + std::unique_ptr<gfx::GpuMemoryBuffer> gpu_memory_buffer) + : drawing_buffer(drawing_buffer), + size(size), + texture_id(texture_id), + image_id(image_id), + gpu_memory_buffer(std::move(gpu_memory_buffer)) { + drawing_buffer->ContextGL()->GenMailboxCHROMIUM(mailbox.name); +} + +DrawingBuffer::ColorBuffer::~ColorBuffer() { + gpu::gles2::GLES2Interface* gl = drawing_buffer->gl_; + GLenum texture_target = drawing_buffer->texture_target_; + if (receive_sync_token.HasData()) + gl->WaitSyncTokenCHROMIUM(receive_sync_token.GetConstData()); + if (image_id) { + gl->BindTexture(texture_target, texture_id); + gl->ReleaseTexImage2DCHROMIUM(texture_target, image_id); + if (rgb_workaround_texture_id) { + gl->BindTexture(texture_target, rgb_workaround_texture_id); + gl->ReleaseTexImage2DCHROMIUM(texture_target, image_id); + } + gl->DestroyImageCHROMIUM(image_id); + switch (texture_target) { + case GL_TEXTURE_2D: + // Restore the texture binding for GL_TEXTURE_2D, since the client will + // expect the previous state. + if (drawing_buffer->client_) + drawing_buffer->client_->DrawingBufferClientRestoreTexture2DBinding(); + break; + case GC3D_TEXTURE_RECTANGLE_ARB: + // Rectangle textures aren't exposed to WebGL, so don't bother + // restoring this state (there is no meaningful way to restore it). + break; + default: + NOTREACHED(); + break; + } + gpu_memory_buffer.reset(); + } + gl->DeleteTextures(1, &texture_id); + if (rgb_workaround_texture_id) { + // Avoid deleting this texture if it was unused. + gl->DeleteTextures(1, &rgb_workaround_texture_id); + } +} + +bool DrawingBuffer::Initialize(const IntSize& size, bool use_multisampling) { + ScopedStateRestorer scoped_state_restorer(this); + + if (gl_->GetGraphicsResetStatusKHR() != GL_NO_ERROR) { + // Need to try to restore the context again later. + DLOG(ERROR) << "Cannot initialize with lost context."; + return false; + } + + // Specifying a half-float backbuffer requires and implicitly enables + // half-float backbuffer extensions. + if (use_half_float_storage_) { + const char* color_buffer_extension = webgl_version_ > kWebGL1 + ? "GL_EXT_color_buffer_float" + : "GL_EXT_color_buffer_half_float"; + if (!extensions_util_->EnsureExtensionEnabled(color_buffer_extension)) { + DLOG(ERROR) << "Half-float color buffer support is absent."; + return false; + } + // Support for RGB half-float renderbuffers is absent from ES3. Do not + // attempt to expose them. + if (!want_alpha_channel_) { + DLOG(ERROR) << "RGB half-float renderbuffers are not supported."; + return false; + } + } + + gl_->GetIntegerv(GL_MAX_TEXTURE_SIZE, &max_texture_size_); + + int max_sample_count = 0; + anti_aliasing_mode_ = kNone; + if (use_multisampling) { + gl_->GetIntegerv(GL_MAX_SAMPLES_ANGLE, &max_sample_count); + anti_aliasing_mode_ = kMSAAExplicitResolve; + if (extensions_util_->SupportsExtension( + "GL_EXT_multisampled_render_to_texture")) { + anti_aliasing_mode_ = kMSAAImplicitResolve; + } else if (extensions_util_->SupportsExtension( + "GL_CHROMIUM_screen_space_antialiasing")) { + anti_aliasing_mode_ = kScreenSpaceAntialiasing; + } + } + // TODO(dshwang): Enable storage textures on all platforms. crbug.com/557848 + // The Linux ATI bot fails + // WebglConformance.conformance_textures_misc_tex_image_webgl, so use storage + // textures only if ScreenSpaceAntialiasing is enabled, because + // ScreenSpaceAntialiasing is much faster with storage textures. + storage_texture_supported_ = + (webgl_version_ > kWebGL1 || + extensions_util_->SupportsExtension("GL_EXT_texture_storage")) && + anti_aliasing_mode_ == kScreenSpaceAntialiasing; + // Performance regreses by 30% in WebGL apps for AMD Stoney + // if sample count is 8x + if (ContextProvider()->GetGpuFeatureInfo().IsWorkaroundEnabled( + gpu::MAX_MSAA_SAMPLE_COUNT_4)) + sample_count_ = std::min(4, max_sample_count); + else + sample_count_ = std::min(8, max_sample_count); + + texture_target_ = GL_TEXTURE_2D; +#if defined(OS_MACOSX) + if (ShouldUseChromiumImage()) { + // A CHROMIUM_image backed texture requires a specialized set of parameters + // on OSX. + texture_target_ = GC3D_TEXTURE_RECTANGLE_ARB; + } +#endif + + // Initialize the alpha allocation settings based on the features and + // workarounds in use. + if (want_alpha_channel_) { + allocate_alpha_channel_ = true; + have_alpha_channel_ = true; + } else { + allocate_alpha_channel_ = false; + have_alpha_channel_ = false; + if (ContextProvider()->GetGpuFeatureInfo().IsWorkaroundEnabled( + gpu::DISABLE_GL_RGB_FORMAT)) { + // This configuration will + // - allow invalid CopyTexImage to RGBA targets + // - fail valid FramebufferBlit from RGB targets + // https://crbug.com/776269 + allocate_alpha_channel_ = true; + have_alpha_channel_ = true; + } + if (WantExplicitResolve() && + ContextProvider()->GetGpuFeatureInfo().IsWorkaroundEnabled( + gpu::DISABLE_WEBGL_RGB_MULTISAMPLING_USAGE)) { + // This configuration avoids the above issues because + // - CopyTexImage is invalid from multisample renderbuffers + // - FramebufferBlit is invalid to multisample renderbuffers + allocate_alpha_channel_ = true; + have_alpha_channel_ = true; + } + if (ShouldUseChromiumImage() && + ContextProvider()->GetCapabilities().chromium_image_rgb_emulation) { + // This configuration avoids the above issues by + // - extra command buffer validation for CopyTexImage + // - explicity re-binding as RGB for FramebufferBlit + allocate_alpha_channel_ = false; + have_alpha_channel_ = true; + } + } + + state_restorer_->SetFramebufferBindingDirty(); + gl_->GenFramebuffers(1, &fbo_); + gl_->BindFramebuffer(GL_FRAMEBUFFER, fbo_); + if (WantExplicitResolve()) { + gl_->GenFramebuffers(1, &multisample_fbo_); + gl_->BindFramebuffer(GL_FRAMEBUFFER, multisample_fbo_); + gl_->GenRenderbuffers(1, &multisample_renderbuffer_); + } + if (!ResizeFramebufferInternal(size)) { + DLOG(ERROR) << "Initialization failed to allocate backbuffer."; + return false; + } + + if (depth_stencil_buffer_) { + DCHECK(WantDepthOrStencil()); + has_implicit_stencil_buffer_ = !want_stencil_; + } + + if (gl_->GetGraphicsResetStatusKHR() != GL_NO_ERROR) { + // It's possible that the drawing buffer allocation provokes a context loss, + // so check again just in case. http://crbug.com/512302 + DLOG(ERROR) << "Context lost during initialization."; + return false; + } + + return true; +} + +bool DrawingBuffer::CopyToPlatformTexture(gpu::gles2::GLES2Interface* dst_gl, + GLenum dst_texture_target, + GLuint dst_texture, + bool premultiply_alpha, + bool flip_y, + const IntPoint& dst_texture_offset, + const IntRect& src_sub_rectangle, + SourceDrawingBuffer src_buffer) { + ScopedStateRestorer scoped_state_restorer(this); + + gpu::gles2::GLES2Interface* src_gl = gl_; + + if (contents_changed_) { + ResolveIfNeeded(); + src_gl->Flush(); + } + + if (!Extensions3DUtil::CanUseCopyTextureCHROMIUM(dst_texture_target)) + return false; + + // Contexts may be in a different share group. We must transfer the texture + // through a mailbox first. + gpu::Mailbox mailbox; + gpu::SyncToken produce_sync_token; + if (src_buffer == kFrontBuffer && front_color_buffer_) { + mailbox = front_color_buffer_->mailbox; + produce_sync_token = front_color_buffer_->produce_sync_token; + } else { + src_gl->GenMailboxCHROMIUM(mailbox.name); + if (premultiplied_alpha_false_texture_) { + // If this texture exists, then it holds the rendering results at this + // point, rather than back_color_buffer_. back_color_buffer_ receives the + // contents of this texture later, premultiplying alpha into the color + // channels. + src_gl->ProduceTextureDirectCHROMIUM(premultiplied_alpha_false_texture_, + mailbox.name); + } else { + src_gl->ProduceTextureDirectCHROMIUM(back_color_buffer_->texture_id, + mailbox.name); + } + src_gl->GenUnverifiedSyncTokenCHROMIUM(produce_sync_token.GetData()); + } + + if (!produce_sync_token.HasData()) { + // This should only happen if the context has been lost. + return false; + } + + dst_gl->WaitSyncTokenCHROMIUM(produce_sync_token.GetConstData()); + GLuint src_texture = dst_gl->CreateAndConsumeTextureCHROMIUM(mailbox.name); + + GLboolean unpack_premultiply_alpha_needed = GL_FALSE; + GLboolean unpack_unpremultiply_alpha_needed = GL_FALSE; + if (want_alpha_channel_ && premultiplied_alpha_ && !premultiply_alpha) + unpack_unpremultiply_alpha_needed = GL_TRUE; + else if (want_alpha_channel_ && !premultiplied_alpha_ && premultiply_alpha) + unpack_premultiply_alpha_needed = GL_TRUE; + + dst_gl->CopySubTextureCHROMIUM( + src_texture, 0, dst_texture_target, dst_texture, 0, + dst_texture_offset.X(), dst_texture_offset.Y(), src_sub_rectangle.X(), + src_sub_rectangle.Y(), src_sub_rectangle.Width(), + src_sub_rectangle.Height(), flip_y, unpack_premultiply_alpha_needed, + unpack_unpremultiply_alpha_needed); + + dst_gl->DeleteTextures(1, &src_texture); + + gpu::SyncToken sync_token; + dst_gl->GenUnverifiedSyncTokenCHROMIUM(sync_token.GetData()); + src_gl->WaitSyncTokenCHROMIUM(sync_token.GetData()); + + return true; +} + +WebLayer* DrawingBuffer::PlatformLayer() { + if (!layer_) { + layer_ = + Platform::Current()->CompositorSupport()->CreateExternalTextureLayer( + this); + + layer_->SetOpaque(!want_alpha_channel_); + layer_->SetBlendBackgroundColor(want_alpha_channel_); + // If premultiplied_alpha_false_texture_ exists, then premultiplied_alpha_ + // has already been handled via CopySubTextureCHROMIUM -- the alpha channel + // has been multiplied into the color channels. In this case, or if + // premultiplied_alpha_ is true, then the layer should consider its contents + // to be premultiplied. + // + // The only situation where the layer should consider its contents + // un-premultiplied is when premultiplied_alpha_ is false, and + // premultiplied_alpha_false_texture_ does not exist. + DCHECK(!(premultiplied_alpha_ && premultiplied_alpha_false_texture_)); + layer_->SetPremultipliedAlpha(premultiplied_alpha_ || + premultiplied_alpha_false_texture_); + layer_->SetNearestNeighbor(filter_quality_ == kNone_SkFilterQuality); + GraphicsLayer::RegisterContentsLayer(layer_->Layer()); + } + + return layer_->Layer(); +} + +void DrawingBuffer::ClearPlatformLayer() { + if (layer_) + layer_->ClearTexture(); + + gl_->Flush(); +} + +void DrawingBuffer::BeginDestruction() { + DCHECK(!destruction_in_progress_); + destruction_in_progress_ = true; + + ClearPlatformLayer(); + recycled_color_buffer_queue_.clear(); + + // If the drawing buffer is being destroyed due to a real context loss these + // calls will be ineffective, but won't be harmful. + if (multisample_fbo_) + gl_->DeleteFramebuffers(1, &multisample_fbo_); + + if (fbo_) + gl_->DeleteFramebuffers(1, &fbo_); + + if (multisample_renderbuffer_) + gl_->DeleteRenderbuffers(1, &multisample_renderbuffer_); + + if (depth_stencil_buffer_) + gl_->DeleteRenderbuffers(1, &depth_stencil_buffer_); + + if (premultiplied_alpha_false_texture_) + gl_->DeleteTextures(1, &premultiplied_alpha_false_texture_); + + size_ = IntSize(); + + back_color_buffer_ = nullptr; + front_color_buffer_ = nullptr; + multisample_renderbuffer_ = 0; + depth_stencil_buffer_ = 0; + premultiplied_alpha_false_texture_ = 0; + multisample_fbo_ = 0; + fbo_ = 0; + + if (layer_) + GraphicsLayer::UnregisterContentsLayer(layer_->Layer()); + + client_ = nullptr; +} + +bool DrawingBuffer::ResizeDefaultFramebuffer(const IntSize& size) { + DCHECK(state_restorer_); + // Recreate m_backColorBuffer. + back_color_buffer_ = CreateColorBuffer(size); + + // Most OS compositors assume GpuMemoryBuffers contain premultiplied-alpha + // content. If the user created the context with premultipliedAlpha:false and + // GpuMemoryBuffers are being used, allocate a non-GMB texture which will hold + // the non-premultiplied rendering results. These will be copied into the GMB + // via CopySubTextureCHROMIUM, performing the premultiplication step then. + if (ShouldUseChromiumImage() && allocate_alpha_channel_ && + !premultiplied_alpha_) { + state_restorer_->SetTextureBindingDirty(); + // TODO(kbr): unify with code in CreateColorBuffer. + if (premultiplied_alpha_false_texture_) { + gl_->DeleteTextures(1, &premultiplied_alpha_false_texture_); + premultiplied_alpha_false_texture_ = 0; + } + gl_->GenTextures(1, &premultiplied_alpha_false_texture_); + // The command decoder forbids allocating "real" OpenGL textures with the + // GL_TEXTURE_RECTANGLE_ARB target. Allocate this temporary texture with + // type GL_TEXTURE_2D all the time. CopySubTextureCHROMIUM can handle + // copying between 2D and rectangular textures. + gl_->BindTexture(GL_TEXTURE_2D, premultiplied_alpha_false_texture_); + if (storage_texture_supported_) { + GLenum internal_storage_format = GL_RGBA8; + if (use_half_float_storage_) { + internal_storage_format = GL_RGBA16F_EXT; + } + gl_->TexStorage2DEXT(GL_TEXTURE_2D, 1, internal_storage_format, + size.Width(), size.Height()); + } else { + GLenum internal_format = GL_RGBA; + GLenum format = internal_format; + GLenum data_type = GL_UNSIGNED_BYTE; + if (use_half_float_storage_) { + if (webgl_version_ > kWebGL1) { + internal_format = GL_RGBA16F; + data_type = GL_HALF_FLOAT; + } else { + internal_format = GL_RGBA; + data_type = GL_HALF_FLOAT_OES; + } + } + gl_->TexImage2D(GL_TEXTURE_2D, 0, internal_format, size.Width(), + size.Height(), 0, format, data_type, nullptr); + } + } + + AttachColorBufferToReadFramebuffer(); + + if (WantExplicitResolve()) { + state_restorer_->SetFramebufferBindingDirty(); + state_restorer_->SetRenderbufferBindingDirty(); + gl_->BindFramebuffer(GL_FRAMEBUFFER, multisample_fbo_); + gl_->BindRenderbuffer(GL_RENDERBUFFER, multisample_renderbuffer_); + // Note that the multisample rendertarget will allocate an alpha channel + // based on |have_alpha_channel_|, not |allocate_alpha_channel_|, since it + // will resolve into the ColorBuffer. + GLenum internal_format = have_alpha_channel_ ? GL_RGBA8_OES : GL_RGB8_OES; + if (use_half_float_storage_) { + DCHECK(want_alpha_channel_); + internal_format = GL_RGBA16F_EXT; + } + gl_->RenderbufferStorageMultisampleCHROMIUM(GL_RENDERBUFFER, sample_count_, + internal_format, size.Width(), + size.Height()); + + if (gl_->GetError() == GL_OUT_OF_MEMORY) + return false; + + gl_->FramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, + GL_RENDERBUFFER, multisample_renderbuffer_); + } + + if (WantDepthOrStencil()) { + state_restorer_->SetFramebufferBindingDirty(); + state_restorer_->SetRenderbufferBindingDirty(); + gl_->BindFramebuffer(GL_FRAMEBUFFER, + multisample_fbo_ ? multisample_fbo_ : fbo_); + if (!depth_stencil_buffer_) + gl_->GenRenderbuffers(1, &depth_stencil_buffer_); + gl_->BindRenderbuffer(GL_RENDERBUFFER, depth_stencil_buffer_); + if (anti_aliasing_mode_ == kMSAAImplicitResolve) { + gl_->RenderbufferStorageMultisampleEXT(GL_RENDERBUFFER, sample_count_, + GL_DEPTH24_STENCIL8_OES, + size.Width(), size.Height()); + } else if (anti_aliasing_mode_ == kMSAAExplicitResolve) { + gl_->RenderbufferStorageMultisampleCHROMIUM( + GL_RENDERBUFFER, sample_count_, GL_DEPTH24_STENCIL8_OES, size.Width(), + size.Height()); + } else { + gl_->RenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8_OES, + size.Width(), size.Height()); + } + // For ES 2.0 contexts DEPTH_STENCIL is not available natively, so we + // emulate + // it at the command buffer level for WebGL contexts. + gl_->FramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, + GL_RENDERBUFFER, depth_stencil_buffer_); + gl_->BindRenderbuffer(GL_RENDERBUFFER, 0); + } + + if (WantExplicitResolve()) { + state_restorer_->SetFramebufferBindingDirty(); + gl_->BindFramebuffer(GL_FRAMEBUFFER, multisample_fbo_); + if (gl_->CheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) + return false; + } + + state_restorer_->SetFramebufferBindingDirty(); + gl_->BindFramebuffer(GL_FRAMEBUFFER, fbo_); + return gl_->CheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE; +} + +void DrawingBuffer::ClearFramebuffers(GLbitfield clear_mask) { + ScopedStateRestorer scoped_state_restorer(this); + ClearFramebuffersInternal(clear_mask); +} + +void DrawingBuffer::ClearFramebuffersInternal(GLbitfield clear_mask) { + DCHECK(state_restorer_); + state_restorer_->SetFramebufferBindingDirty(); + // We will clear the multisample FBO, but we also need to clear the + // non-multisampled buffer. + if (multisample_fbo_) { + gl_->BindFramebuffer(GL_FRAMEBUFFER, fbo_); + gl_->Clear(GL_COLOR_BUFFER_BIT); + } + + gl_->BindFramebuffer(GL_FRAMEBUFFER, + multisample_fbo_ ? multisample_fbo_ : fbo_); + gl_->Clear(clear_mask); +} + +IntSize DrawingBuffer::AdjustSize(const IntSize& desired_size, + const IntSize& cur_size, + int max_texture_size) { + IntSize adjusted_size = desired_size; + + // Clamp if the desired size is greater than the maximum texture size for the + // device. + if (adjusted_size.Height() > max_texture_size) + adjusted_size.SetHeight(max_texture_size); + + if (adjusted_size.Width() > max_texture_size) + adjusted_size.SetWidth(max_texture_size); + + return adjusted_size; +} + +bool DrawingBuffer::Resize(const IntSize& new_size) { + ScopedStateRestorer scoped_state_restorer(this); + return ResizeFramebufferInternal(new_size); +} + +bool DrawingBuffer::ResizeFramebufferInternal(const IntSize& new_size) { + DCHECK(state_restorer_); + DCHECK(!new_size.IsEmpty()); + IntSize adjusted_size = AdjustSize(new_size, size_, max_texture_size_); + if (adjusted_size.IsEmpty()) + return false; + + if (adjusted_size != size_) { + do { + if (!ResizeDefaultFramebuffer(adjusted_size)) { + adjusted_size.Scale(kResourceAdjustedRatio); + continue; + } + break; + } while (!adjusted_size.IsEmpty()); + + size_ = adjusted_size; + // Free all mailboxes, because they are now of the wrong size. Only the + // first call in this loop has any effect. + recycled_color_buffer_queue_.clear(); + recycled_bitmaps_.clear(); + + if (adjusted_size.IsEmpty()) + return false; + } + + state_restorer_->SetClearStateDirty(); + gl_->Disable(GL_SCISSOR_TEST); + gl_->ClearColor(0, 0, 0, + DefaultBufferRequiresAlphaChannelToBePreserved() ? 1 : 0); + gl_->ColorMask(true, true, true, true); + + GLbitfield clear_mask = GL_COLOR_BUFFER_BIT; + if (!!depth_stencil_buffer_) { + gl_->ClearDepthf(1.0f); + clear_mask |= GL_DEPTH_BUFFER_BIT; + gl_->DepthMask(true); + } + if (!!depth_stencil_buffer_) { + gl_->ClearStencil(0); + clear_mask |= GL_STENCIL_BUFFER_BIT; + gl_->StencilMaskSeparate(GL_FRONT, 0xFFFFFFFF); + } + + ClearFramebuffersInternal(clear_mask); + return true; +} + +void DrawingBuffer::ResolveAndBindForReadAndDraw() { + { + ScopedStateRestorer scoped_state_restorer(this); + ResolveIfNeeded(); + } + gl_->BindFramebuffer(GL_FRAMEBUFFER, fbo_); +} + +void DrawingBuffer::ResolveMultisampleFramebufferInternal() { + DCHECK(state_restorer_); + state_restorer_->SetFramebufferBindingDirty(); + if (WantExplicitResolve()) { + state_restorer_->SetClearStateDirty(); + gl_->BindFramebuffer(GL_READ_FRAMEBUFFER_ANGLE, multisample_fbo_); + gl_->BindFramebuffer(GL_DRAW_FRAMEBUFFER_ANGLE, fbo_); + gl_->Disable(GL_SCISSOR_TEST); + + int width = size_.Width(); + int height = size_.Height(); + // Use NEAREST, because there is no scale performed during the blit. + GLuint filter = GL_NEAREST; + + gl_->BlitFramebufferCHROMIUM(0, 0, width, height, 0, 0, width, height, + GL_COLOR_BUFFER_BIT, filter); + + // On old AMD GPUs on OS X, glColorMask doesn't work correctly for + // multisampled renderbuffers and the alpha channel can be overwritten. + // Clear the alpha channel of |m_fbo|. + if (DefaultBufferRequiresAlphaChannelToBePreserved() && + ContextProvider()->GetGpuFeatureInfo().IsWorkaroundEnabled( + gpu::DISABLE_MULTISAMPLING_COLOR_MASK_USAGE)) { + gl_->ClearColor(0, 0, 0, 1); + gl_->ColorMask(false, false, false, true); + gl_->Clear(GL_COLOR_BUFFER_BIT); + } + } + + gl_->BindFramebuffer(GL_FRAMEBUFFER, fbo_); + if (anti_aliasing_mode_ == kScreenSpaceAntialiasing) + gl_->ApplyScreenSpaceAntialiasingCHROMIUM(); +} + +void DrawingBuffer::ResolveIfNeeded() { + if (anti_aliasing_mode_ != kNone && !contents_change_resolved_) + ResolveMultisampleFramebufferInternal(); + contents_change_resolved_ = true; +} + +void DrawingBuffer::RestoreFramebufferBindings() { + client_->DrawingBufferClientRestoreFramebufferBinding(); +} + +void DrawingBuffer::RestoreAllState() { + client_->DrawingBufferClientRestoreScissorTest(); + client_->DrawingBufferClientRestoreMaskAndClearValues(); + client_->DrawingBufferClientRestorePixelPackParameters(); + client_->DrawingBufferClientRestoreTexture2DBinding(); + client_->DrawingBufferClientRestoreRenderbufferBinding(); + client_->DrawingBufferClientRestoreFramebufferBinding(); + client_->DrawingBufferClientRestorePixelUnpackBufferBinding(); + client_->DrawingBufferClientRestorePixelPackBufferBinding(); +} + +bool DrawingBuffer::Multisample() const { + return anti_aliasing_mode_ != kNone; +} + +void DrawingBuffer::Bind(GLenum target) { + gl_->BindFramebuffer(target, WantExplicitResolve() ? multisample_fbo_ : fbo_); +} + +scoped_refptr<Uint8Array> DrawingBuffer::PaintRenderingResultsToDataArray( + SourceDrawingBuffer source_buffer) { + ScopedStateRestorer scoped_state_restorer(this); + + int width = Size().Width(); + int height = Size().Height(); + + CheckedNumeric<int> data_size = 4; + data_size *= width; + data_size *= height; + if (RuntimeEnabledFeatures::CanvasColorManagementEnabled() && + use_half_float_storage_) { + data_size *= 2; + } + if (!data_size.IsValid()) + return nullptr; + + unsigned byte_length = width * height * 4; + if (RuntimeEnabledFeatures::CanvasColorManagementEnabled() && + use_half_float_storage_) { + byte_length *= 2; + } + scoped_refptr<ArrayBuffer> dst_buffer = + ArrayBuffer::CreateOrNull(byte_length, 1); + if (!dst_buffer) + return nullptr; + scoped_refptr<Uint8Array> data_array = + Uint8Array::Create(std::move(dst_buffer), 0, byte_length); + if (!data_array) + return nullptr; + + GLuint fbo = 0; + state_restorer_->SetFramebufferBindingDirty(); + if (source_buffer == kFrontBuffer && front_color_buffer_) { + gl_->GenFramebuffers(1, &fbo); + gl_->BindFramebuffer(GL_FRAMEBUFFER, fbo); + gl_->FramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, + texture_target_, front_color_buffer_->texture_id, + 0); + } else { + gl_->BindFramebuffer(GL_FRAMEBUFFER, fbo_); + } + + ReadBackFramebuffer(static_cast<unsigned char*>(data_array->Data()), width, + height, kReadbackRGBA, + WebGLImageConversion::kAlphaDoNothing); + FlipVertically(static_cast<uint8_t*>(data_array->Data()), width, height); + + if (fbo) { + gl_->FramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, + texture_target_, 0, 0); + gl_->DeleteFramebuffers(1, &fbo); + } + + return data_array; +} + +void DrawingBuffer::ReadBackFramebuffer(unsigned char* pixels, + int width, + int height, + ReadbackOrder readback_order, + WebGLImageConversion::AlphaOp op) { + DCHECK(state_restorer_); + state_restorer_->SetPixelPackParametersDirty(); + gl_->PixelStorei(GL_PACK_ALIGNMENT, 1); + if (webgl_version_ > kWebGL1) { + gl_->PixelStorei(GL_PACK_SKIP_ROWS, 0); + gl_->PixelStorei(GL_PACK_SKIP_PIXELS, 0); + gl_->PixelStorei(GL_PACK_ROW_LENGTH, 0); + + state_restorer_->SetPixelPackBufferBindingDirty(); + gl_->BindBuffer(GL_PIXEL_PACK_BUFFER, 0); + } + + GLenum data_type = GL_UNSIGNED_BYTE; + if (RuntimeEnabledFeatures::CanvasColorManagementEnabled() && + use_half_float_storage_) { + if (webgl_version_ > kWebGL1) + data_type = GL_HALF_FLOAT; + else + data_type = GL_HALF_FLOAT_OES; + } + gl_->ReadPixels(0, 0, width, height, GL_RGBA, data_type, pixels); + + size_t buffer_size = 4 * width * height; + if (data_type != GL_UNSIGNED_BYTE) + buffer_size *= 2; + // For half float storage Skia order is RGBA, hence no swizzling is needed. + if (readback_order == kReadbackSkia && data_type == GL_UNSIGNED_BYTE) { +#if (SK_R32_SHIFT == 16) && !SK_B32_SHIFT + // Swizzle red and blue channels to match SkBitmap's byte ordering. + // TODO(kbr): expose GL_BGRA as extension. + for (size_t i = 0; i < buffer_size; i += 4) { + std::swap(pixels[i], pixels[i + 2]); + } +#endif + } + + if (op == WebGLImageConversion::kAlphaDoPremultiply) { + std::unique_ptr<SkColorSpaceXform> xform = + SkColorSpaceXform::New(SkColorSpace::MakeSRGBLinear().get(), + SkColorSpace::MakeSRGBLinear().get()); + SkColorSpaceXform::ColorFormat color_format = + SkColorSpaceXform::ColorFormat::kRGBA_8888_ColorFormat; + if (data_type != GL_UNSIGNED_BYTE) + color_format = SkColorSpaceXform::ColorFormat::kRGBA_F16_ColorFormat; + xform->apply(color_format, pixels, color_format, pixels, width * height, + kPremul_SkAlphaType); + } else if (op != WebGLImageConversion::kAlphaDoNothing) { + NOTREACHED(); + } +} + +void DrawingBuffer::FlipVertically(uint8_t* framebuffer, + int width, + int height) { + unsigned row_bytes = width * 4; + if (RuntimeEnabledFeatures::CanvasColorManagementEnabled() && + use_half_float_storage_) { + row_bytes *= 2; + } + std::vector<uint8_t> scanline(row_bytes); + unsigned count = height / 2; + for (unsigned i = 0; i < count; i++) { + uint8_t* row_a = framebuffer + i * row_bytes; + uint8_t* row_b = framebuffer + (height - i - 1) * row_bytes; + memcpy(scanline.data(), row_b, row_bytes); + memcpy(row_b, row_a, row_bytes); + memcpy(row_a, scanline.data(), row_bytes); + } +} + +scoped_refptr<DrawingBuffer::ColorBuffer> DrawingBuffer::CreateColorBuffer( + const IntSize& size) { + DCHECK(state_restorer_); + state_restorer_->SetFramebufferBindingDirty(); + state_restorer_->SetTextureBindingDirty(); + + // Select the parameters for the texture object. Allocate the backing + // GpuMemoryBuffer and GLImage, if one is going to be used. + GLuint image_id = 0; + std::unique_ptr<gfx::GpuMemoryBuffer> gpu_memory_buffer; + gpu::GpuMemoryBufferManager* gpu_memory_buffer_manager = + Platform::Current()->GetGpuMemoryBufferManager(); + if (ShouldUseChromiumImage()) { + gfx::BufferFormat buffer_format; + GLenum gl_format = GL_NONE; + if (allocate_alpha_channel_) { + buffer_format = use_half_float_storage_ ? gfx::BufferFormat::RGBA_F16 + : gfx::BufferFormat::RGBA_8888; + gl_format = GL_RGBA; + } else { + DCHECK(!use_half_float_storage_); + buffer_format = gfx::BufferFormat::RGBX_8888; + if (gpu::IsImageFromGpuMemoryBufferFormatSupported( + gfx::BufferFormat::BGRX_8888, + ContextProvider()->GetCapabilities())) + buffer_format = gfx::BufferFormat::BGRX_8888; + gl_format = GL_RGB; + } + gpu_memory_buffer = gpu_memory_buffer_manager->CreateGpuMemoryBuffer( + gfx::Size(size), buffer_format, gfx::BufferUsage::SCANOUT, + gpu::kNullSurfaceHandle); + if (gpu_memory_buffer) { + gpu_memory_buffer->SetColorSpace(storage_color_space_); + image_id = + gl_->CreateImageCHROMIUM(gpu_memory_buffer->AsClientBuffer(), + size.Width(), size.Height(), gl_format); + if (!image_id) + gpu_memory_buffer.reset(); + } + } + + // Allocate the texture for this object. + GLuint texture_id = 0; + { + gl_->GenTextures(1, &texture_id); + gl_->BindTexture(texture_target_, texture_id); + gl_->TexParameteri(texture_target_, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + gl_->TexParameteri(texture_target_, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + gl_->TexParameteri(texture_target_, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + gl_->TexParameteri(texture_target_, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + } + + // If this is GpuMemoryBuffer-backed, then bind the texture to the + // GpuMemoryBuffer's GLImage. Otherwise, allocate ordinary texture storage. + if (image_id) { + gl_->BindTexImage2DCHROMIUM(texture_target_, image_id); + } else { + if (storage_texture_supported_) { + GLenum internal_storage_format = + allocate_alpha_channel_ ? GL_RGBA8 : GL_RGB8; + if (use_half_float_storage_) { + DCHECK(want_alpha_channel_); + internal_storage_format = GL_RGBA16F_EXT; + } + gl_->TexStorage2DEXT(GL_TEXTURE_2D, 1, internal_storage_format, + size.Width(), size.Height()); + } else { + GLenum internal_format = allocate_alpha_channel_ ? GL_RGBA : GL_RGB; + GLenum format = internal_format; + GLenum data_type = GL_UNSIGNED_BYTE; + if (use_half_float_storage_) { + DCHECK(want_alpha_channel_); + if (webgl_version_ > kWebGL1) { + internal_format = GL_RGBA16F; + data_type = GL_HALF_FLOAT; + } else { + internal_format = GL_RGBA; + data_type = GL_HALF_FLOAT_OES; + } + } + gl_->TexImage2D(texture_target_, 0, internal_format, size.Width(), + size.Height(), 0, format, data_type, nullptr); + } + } + + // Clear the alpha channel if this is RGB emulated. + if (image_id && !want_alpha_channel_ && have_alpha_channel_) { + GLuint fbo = 0; + + state_restorer_->SetClearStateDirty(); + gl_->GenFramebuffers(1, &fbo); + gl_->BindFramebuffer(GL_FRAMEBUFFER, fbo); + gl_->FramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, + texture_target_, texture_id, 0); + gl_->ClearColor(0, 0, 0, 1); + gl_->ColorMask(false, false, false, true); + gl_->Disable(GL_SCISSOR_TEST); + gl_->Clear(GL_COLOR_BUFFER_BIT); + gl_->FramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, + texture_target_, 0, 0); + gl_->DeleteFramebuffers(1, &fbo); + } + + return base::AdoptRef(new ColorBuffer(this, size, texture_id, image_id, + std::move(gpu_memory_buffer))); +} + +void DrawingBuffer::AttachColorBufferToReadFramebuffer() { + DCHECK(state_restorer_); + state_restorer_->SetFramebufferBindingDirty(); + state_restorer_->SetTextureBindingDirty(); + + gl_->BindFramebuffer(GL_FRAMEBUFFER, fbo_); + + GLenum id = 0; + GLenum texture_target = 0; + + if (premultiplied_alpha_false_texture_) { + id = premultiplied_alpha_false_texture_; + texture_target = GL_TEXTURE_2D; + } else { + id = back_color_buffer_->texture_id; + texture_target = texture_target_; + } + + gl_->BindTexture(texture_target, id); + + if (anti_aliasing_mode_ == kMSAAImplicitResolve) { + gl_->FramebufferTexture2DMultisampleEXT( + GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, texture_target, id, 0, + sample_count_); + } else { + gl_->FramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, + texture_target, id, 0); + } +} + +bool DrawingBuffer::WantExplicitResolve() { + return anti_aliasing_mode_ == kMSAAExplicitResolve; +} + +bool DrawingBuffer::WantDepthOrStencil() { + return want_depth_ || want_stencil_; +} + +bool DrawingBuffer::SetupRGBEmulationForBlitFramebuffer( + bool is_user_draw_framebuffer_bound) { + // We only need to do this work if: + // - We are blitting to the default framebuffer + // - The user has selected alpha:false and antialias:false + // - We are using CHROMIUM_image with RGB emulation + // macOS is the only platform on which this is necessary. + + if (is_user_draw_framebuffer_bound) { + return false; + } + + if (anti_aliasing_mode_ != kNone) + return false; + + bool has_emulated_rgb = !allocate_alpha_channel_ && have_alpha_channel_; + if (!has_emulated_rgb) + return false; + + // If for some reason the back buffer doesn't exist or doesn't have a + // CHROMIUM_image, don't proceed with this workaround. + if (!back_color_buffer_ || !back_color_buffer_->image_id) + return false; + + // Before allowing the BlitFramebuffer call to go through, it's necessary + // to swap out the RGBA texture that's bound to the CHROMIUM_image + // instance with an RGB texture. BlitFramebuffer requires the internal + // formats of the source and destination to match when doing a + // multisample resolve, and the best way to achieve this without adding + // more full-screen blits is to hook up a true RGB texture to the + // underlying IOSurface. Unfortunately, on macOS, this rendering path + // destroys the alpha channel and requires a fixup afterward, which is + // why it isn't used all the time. + + GLuint rgb_texture = back_color_buffer_->rgb_workaround_texture_id; + DCHECK_EQ(texture_target_, GC3D_TEXTURE_RECTANGLE_ARB); + if (!rgb_texture) { + gl_->GenTextures(1, &rgb_texture); + gl_->BindTexture(texture_target_, rgb_texture); + gl_->TexParameteri(texture_target_, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + gl_->TexParameteri(texture_target_, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + gl_->TexParameteri(texture_target_, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + gl_->TexParameteri(texture_target_, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + + // Bind this texture to the CHROMIUM_image instance that the color + // buffer owns. This is an expensive operation, so it's important that + // the result be cached. + gl_->BindTexImage2DWithInternalformatCHROMIUM(texture_target_, GL_RGB, + back_color_buffer_->image_id); + back_color_buffer_->rgb_workaround_texture_id = rgb_texture; + } + + gl_->FramebufferTexture2D(GL_DRAW_FRAMEBUFFER_ANGLE, GL_COLOR_ATTACHMENT0, + texture_target_, rgb_texture, 0); + return true; +} + +void DrawingBuffer::CleanupRGBEmulationForBlitFramebuffer() { + // This will only be called if SetupRGBEmulationForBlitFramebuffer was. + // Put the framebuffer back the way it was, and clear the alpha channel. + DCHECK(back_color_buffer_); + DCHECK(back_color_buffer_->image_id); + gl_->FramebufferTexture2D(GL_DRAW_FRAMEBUFFER_ANGLE, GL_COLOR_ATTACHMENT0, + texture_target_, back_color_buffer_->texture_id, 0); + // Clear the alpha channel. + gl_->ColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_TRUE); + gl_->Disable(GL_SCISSOR_TEST); + gl_->ClearColor(0, 0, 0, 1); + gl_->Clear(GL_COLOR_BUFFER_BIT); + DCHECK(client_); + client_->DrawingBufferClientRestoreScissorTest(); + client_->DrawingBufferClientRestoreMaskAndClearValues(); +} + +DrawingBuffer::ScopedStateRestorer::ScopedStateRestorer( + DrawingBuffer* drawing_buffer) + : drawing_buffer_(drawing_buffer) { + // If this is a nested restorer, save the previous restorer. + previous_state_restorer_ = drawing_buffer->state_restorer_; + drawing_buffer_->state_restorer_ = this; +} + +DrawingBuffer::ScopedStateRestorer::~ScopedStateRestorer() { + DCHECK_EQ(drawing_buffer_->state_restorer_, this); + drawing_buffer_->state_restorer_ = previous_state_restorer_; + Client* client = drawing_buffer_->client_; + if (!client) + return; + + if (clear_state_dirty_) { + client->DrawingBufferClientRestoreScissorTest(); + client->DrawingBufferClientRestoreMaskAndClearValues(); + } + if (pixel_pack_parameters_dirty_) + client->DrawingBufferClientRestorePixelPackParameters(); + if (texture_binding_dirty_) + client->DrawingBufferClientRestoreTexture2DBinding(); + if (renderbuffer_binding_dirty_) + client->DrawingBufferClientRestoreRenderbufferBinding(); + if (framebuffer_binding_dirty_) + client->DrawingBufferClientRestoreFramebufferBinding(); + if (pixel_unpack_buffer_binding_dirty_) + client->DrawingBufferClientRestorePixelUnpackBufferBinding(); + if (pixel_pack_buffer_binding_dirty_) + client->DrawingBufferClientRestorePixelPackBufferBinding(); +} + +bool DrawingBuffer::ShouldUseChromiumImage() { + return RuntimeEnabledFeatures::WebGLImageChromiumEnabled() && + chromium_image_usage_ == kAllowChromiumImage && + Platform::Current()->GetGpuMemoryBufferManager(); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/platform/graphics/gpu/drawing_buffer.h b/chromium/third_party/blink/renderer/platform/graphics/gpu/drawing_buffer.h new file mode 100644 index 00000000000..e19db7bcc26 --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/graphics/gpu/drawing_buffer.h @@ -0,0 +1,585 @@ +/* + * Copyright (c) 2010, Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_GRAPHICS_GPU_DRAWING_BUFFER_H_ +#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_GRAPHICS_GPU_DRAWING_BUFFER_H_ + +#include <memory> + +#include "cc/layers/texture_layer_client.h" +#include "cc/resources/cross_thread_shared_bitmap.h" +#include "cc/resources/shared_bitmap_id_registrar.h" +#include "gpu/command_buffer/common/mailbox.h" +#include "gpu/command_buffer/common/sync_token.h" +#include "third_party/blink/renderer/platform/geometry/int_size.h" +#include "third_party/blink/renderer/platform/graphics/gpu/webgl_image_conversion.h" +#include "third_party/blink/renderer/platform/graphics/graphics_types_3d.h" +#include "third_party/blink/renderer/platform/platform_export.h" +#include "third_party/blink/renderer/platform/wtf/deque.h" +#include "third_party/blink/renderer/platform/wtf/noncopyable.h" +#include "third_party/blink/renderer/platform/wtf/ref_counted.h" +#include "third_party/blink/renderer/platform/wtf/vector.h" +#include "third_party/khronos/GLES2/gl2.h" +#include "third_party/skia/include/core/SkBitmap.h" +#include "ui/gfx/color_space.h" + +namespace gfx { +class GpuMemoryBuffer; +} + +namespace gpu { +namespace gles2 { +class GLES2Interface; +} +} + +namespace blink { +class CanvasColorParams; +class Extensions3DUtil; +class StaticBitmapImage; +class WebExternalTextureLayer; +class WebGraphicsContext3DProvider; +class WebGraphicsContext3DProviderWrapper; +class WebLayer; + +// Manages a rendering target (framebuffer + attachment) for a canvas. Can +// publish its rendering results to a WebLayer for compositing. +class PLATFORM_EXPORT DrawingBuffer : public cc::TextureLayerClient, + public RefCounted<DrawingBuffer> { + WTF_MAKE_NONCOPYABLE(DrawingBuffer); + + public: + class Client { + public: + // Returns true if the DrawingBuffer is currently bound for draw. + virtual bool DrawingBufferClientIsBoundForDraw() = 0; + virtual void DrawingBufferClientRestoreScissorTest() = 0; + // Restores the mask and clear value for color, depth, and stencil buffers. + virtual void DrawingBufferClientRestoreMaskAndClearValues() = 0; + // Assume client knows the GL/WebGL version and restore necessary params + // accordingly. + virtual void DrawingBufferClientRestorePixelPackParameters() = 0; + // Restores the GL_TEXTURE_2D binding for the active texture unit only. + virtual void DrawingBufferClientRestoreTexture2DBinding() = 0; + virtual void DrawingBufferClientRestoreRenderbufferBinding() = 0; + virtual void DrawingBufferClientRestoreFramebufferBinding() = 0; + virtual void DrawingBufferClientRestorePixelUnpackBufferBinding() = 0; + virtual void DrawingBufferClientRestorePixelPackBufferBinding() = 0; + }; + + enum PreserveDrawingBuffer { + kPreserve, + kDiscard, + }; + enum WebGLVersion { + kWebGL1, + kWebGL2, + }; + + enum ChromiumImageUsage { + kAllowChromiumImage, + kDisallowChromiumImage, + }; + + static scoped_refptr<DrawingBuffer> Create( + std::unique_ptr<WebGraphicsContext3DProvider>, + bool using_gpu_compositing, + Client*, + const IntSize&, + bool premultiplied_alpha, + bool want_alpha_channel, + bool want_depth_buffer, + bool want_stencil_buffer, + bool want_antialiasing, + PreserveDrawingBuffer, + WebGLVersion, + ChromiumImageUsage, + const CanvasColorParams&); + static void ForceNextDrawingBufferCreationToFail(); + + ~DrawingBuffer() override; + + // Destruction will be completed after all mailboxes are released. + void BeginDestruction(); + + // Issues a glClear() on all framebuffers associated with this DrawingBuffer. + void ClearFramebuffers(GLbitfield clear_mask); + + // Indicates whether the DrawingBuffer internally allocated a packed + // depth-stencil renderbuffer in the situation where the end user only asked + // for a depth buffer. In this case, we need to upgrade clears of the depth + // buffer to clears of the depth and stencil buffers in order to avoid + // performance problems on some GPUs. + bool HasImplicitStencilBuffer() const { return has_implicit_stencil_buffer_; } + bool HasDepthBuffer() const { return !!depth_stencil_buffer_; } + bool HasStencilBuffer() const { return !!depth_stencil_buffer_; } + + // Given the desired buffer size, provides the largest dimensions that will + // fit in the pixel budget. + static IntSize AdjustSize(const IntSize& desired_size, + const IntSize& cur_size, + int max_texture_size); + + // Resizes (or allocates if necessary) all buffers attached to the default + // framebuffer. Returns whether the operation was successful. + bool Resize(const IntSize&); + + // Bind the default framebuffer to |target|. |target| must be + // GL_FRAMEBUFFER, GL_READ_FRAMEBUFFER, or GL_DRAW_FRAMEBUFFER. + void Bind(GLenum target); + IntSize Size() const { return size_; } + + // Resolves the multisample color buffer to the normal color buffer and leaves + // the resolved color buffer bound to GL_READ_FRAMEBUFFER and + // GL_DRAW_FRAMEBUFFER. + void ResolveAndBindForReadAndDraw(); + + bool Multisample() const; + + bool DiscardFramebufferSupported() const { + return discard_framebuffer_supported_; + } + + // Returns false if the contents had previously been marked as changed and + // have not yet been resolved. + bool MarkContentsChanged(); + + // Maintenance of auto-clearing of color/depth/stencil buffers. The + // Reset method is present to keep calling code simpler, so it + // doesn't have to know which buffers were allocated. + void ResetBuffersToAutoClear(); + void SetBuffersToAutoClear(GLbitfield bitmask); + GLbitfield GetBuffersToAutoClear() const; + + void SetIsHidden(bool); + void SetFilterQuality(SkFilterQuality); + + // Whether the target for draw operations has format GL_RGBA, but is + // emulating format GL_RGB. When the target's storage is first + // allocated, its alpha channel must be cleared to 1. All future drawing + // operations must use a color mask with alpha=GL_FALSE. + bool RequiresAlphaChannelToBePreserved(); + + // Similar to requiresAlphaChannelToBePreserved(), but always targets the + // default framebuffer. + bool DefaultBufferRequiresAlphaChannelToBePreserved(); + + WebLayer* PlatformLayer(); + + gpu::gles2::GLES2Interface* ContextGL(); + WebGraphicsContext3DProvider* ContextProvider(); + base::WeakPtr<WebGraphicsContext3DProviderWrapper> ContextProviderWeakPtr(); + Client* client() { return client_; } + WebGLVersion webgl_version() const { return webgl_version_; } + bool destroyed() const { return destruction_in_progress_; } + + // cc::TextureLayerClient implementation. + bool PrepareTransferableResource( + cc::SharedBitmapIdRegistrar* bitmap_registrar, + viz::TransferableResource* out_resource, + std::unique_ptr<viz::SingleReleaseCallback>* out_release_callback) + override; + + // Returns a StaticBitmapImage backed by a texture containing the current + // contents of the front buffer. This is done without any pixel copies. The + // texture in the ImageBitmap is from the active ContextProvider on the + // DrawingBuffer. + // If out_release_callback is null, the image is discarded. If it is non-null + // the image must be recycled or discarded by calling *out_release_callback. + scoped_refptr<StaticBitmapImage> TransferToStaticBitmapImage( + std::unique_ptr<viz::SingleReleaseCallback>* out_release_callback); + + bool CopyToPlatformTexture(gpu::gles2::GLES2Interface*, + GLenum dst_target, + GLuint dst_texture, + bool premultiply_alpha, + bool flip_y, + const IntPoint& dst_texture_offset, + const IntRect& src_sub_rectangle, + SourceDrawingBuffer); + + scoped_refptr<Uint8Array> PaintRenderingResultsToDataArray( + SourceDrawingBuffer); + + int SampleCount() const { return sample_count_; } + bool ExplicitResolveOfMultisampleData() const { + return anti_aliasing_mode_ == kMSAAExplicitResolve; + } + + // Rebind the read and draw framebuffers that WebGL is expecting. + void RestoreFramebufferBindings(); + + // Restore all state that may have been dirtied by any call. + void RestoreAllState(); + + // This class helps implement correct semantics for BlitFramebuffer + // when the DrawingBuffer is using a CHROMIUM image for its backing + // store and RGB emulation is in use (basically, macOS only). + class PLATFORM_EXPORT ScopedRGBEmulationForBlitFramebuffer { + public: + ScopedRGBEmulationForBlitFramebuffer(DrawingBuffer*, + bool is_user_draw_framebuffer_bound); + ~ScopedRGBEmulationForBlitFramebuffer(); + + private: + scoped_refptr<DrawingBuffer> drawing_buffer_; + bool doing_work_ = false; + }; + + protected: // For unittests + DrawingBuffer(std::unique_ptr<WebGraphicsContext3DProvider>, + bool using_gpu_compositing, + std::unique_ptr<Extensions3DUtil>, + Client*, + bool discard_framebuffer_supported, + bool want_alpha_channel, + bool premultiplied_alpha, + PreserveDrawingBuffer, + WebGLVersion, + bool wants_depth, + bool wants_stencil, + ChromiumImageUsage, + const CanvasColorParams&); + + bool Initialize(const IntSize&, bool use_multisampling); + + struct RegisteredBitmap { + scoped_refptr<cc::CrossThreadSharedBitmap> bitmap; + cc::SharedBitmapIdRegistration registration; + + // Explicitly move-only. + RegisteredBitmap(RegisteredBitmap&&) = default; + RegisteredBitmap& operator=(RegisteredBitmap&&) = default; + }; + // Shared memory bitmaps that were released by the compositor and can be used + // again by this DrawingBuffer. + Vector<RegisteredBitmap> recycled_bitmaps_; + + private: + friend class ScopedRGBEmulationForBlitFramebuffer; + friend class ScopedStateRestorer; + friend class ColorBuffer; + + // This structure should wrap all public entrypoints that may modify GL state. + // It will restore all state when it drops out of scope. + class ScopedStateRestorer { + public: + ScopedStateRestorer(DrawingBuffer*); + ~ScopedStateRestorer(); + + // Mark parts of the state that are dirty and need to be restored. + void SetClearStateDirty() { clear_state_dirty_ = true; } + void SetPixelPackParametersDirty() { pixel_pack_parameters_dirty_ = true; } + void SetTextureBindingDirty() { texture_binding_dirty_ = true; } + void SetRenderbufferBindingDirty() { renderbuffer_binding_dirty_ = true; } + void SetFramebufferBindingDirty() { framebuffer_binding_dirty_ = true; } + void SetPixelUnpackBufferBindingDirty() { + pixel_unpack_buffer_binding_dirty_ = true; + } + void SetPixelPackBufferBindingDirty() { + pixel_pack_buffer_binding_dirty_ = true; + } + + private: + scoped_refptr<DrawingBuffer> drawing_buffer_; + // The previous state restorer, in case restorers are nested. + ScopedStateRestorer* previous_state_restorer_ = nullptr; + bool clear_state_dirty_ = false; + bool pixel_pack_parameters_dirty_ = false; + bool texture_binding_dirty_ = false; + bool renderbuffer_binding_dirty_ = false; + bool framebuffer_binding_dirty_ = false; + bool pixel_unpack_buffer_binding_dirty_ = false; + bool pixel_pack_buffer_binding_dirty_ = false; + }; + + struct ColorBuffer : public RefCounted<ColorBuffer> { + ColorBuffer(DrawingBuffer*, + const IntSize&, + GLuint texture_id, + GLuint image_id, + std::unique_ptr<gfx::GpuMemoryBuffer>); + ~ColorBuffer(); + + // The owning DrawingBuffer. Note that DrawingBuffer is explicitly destroyed + // by the beginDestruction method, which will eventually drain all of its + // ColorBuffers. + scoped_refptr<DrawingBuffer> drawing_buffer; + const IntSize size; + const GLuint texture_id = 0; + const GLuint image_id = 0; + std::unique_ptr<gfx::GpuMemoryBuffer> gpu_memory_buffer; + + // If we're emulating an RGB back buffer using an RGBA Chromium + // image (essentially macOS only), then when performing + // BlitFramebuffer calls, we have to swap in an RGB texture in + // place of the RGBA texture bound to the image. The reason is + // that BlitFramebuffer requires the internal formats of the + // source and destination to match (e.g. RGB8 on both sides). + // There are bugs in the semantics of RGB8 textures in this + // situation (the alpha channel is zeroed), requiring more fixups. + GLuint rgb_workaround_texture_id = 0; + + // The mailbox used to send this buffer to the compositor. + gpu::Mailbox mailbox; + + // The sync token for when this buffer was sent to the compositor. + gpu::SyncToken produce_sync_token; + + // The sync token for when this buffer was received back from the + // compositor. + gpu::SyncToken receive_sync_token; + + private: + WTF_MAKE_NONCOPYABLE(ColorBuffer); + }; + + // The same as clearFramebuffers(), but leaves GL state dirty. + void ClearFramebuffersInternal(GLbitfield clear_mask); + + // The same as reset(), but leaves GL state dirty. + bool ResizeFramebufferInternal(const IntSize&); + + // The same as resolveAndBindForReadAndDraw(), but leaves GL state dirty. + void ResolveMultisampleFramebufferInternal(); + + // Resolves m_multisampleFBO into m_fbo, if multisampling. + void ResolveIfNeeded(); + + bool PrepareTransferableResourceInternal( + cc::SharedBitmapIdRegistrar* bitmap_registrar, + viz::TransferableResource* out_resource, + std::unique_ptr<viz::SingleReleaseCallback>* out_release_callback, + bool force_gpu_result); + + // Helper functions to be called only by PrepareTransferableResourceInternal. + void FinishPrepareTransferableResourceGpu( + viz::TransferableResource* out_resource, + std::unique_ptr<viz::SingleReleaseCallback>* out_release_callback); + void FinishPrepareTransferableResourceSoftware( + cc::SharedBitmapIdRegistrar* bitmap_registrar, + viz::TransferableResource* out_resource, + std::unique_ptr<viz::SingleReleaseCallback>* out_release_callback); + + // Callbacks for mailboxes given to the compositor from + // FinishPrepareTransferableResource{Gpu,Software}. + void MailboxReleasedGpu(scoped_refptr<ColorBuffer>, + const gpu::SyncToken&, + bool lost_resource); + void MailboxReleasedSoftware(RegisteredBitmap, + const gpu::SyncToken&, + bool lost_resource); + + // Attempts to allocator storage for, or resize all buffers. Returns whether + // the operation was successful. + bool ResizeDefaultFramebuffer(const IntSize&); + + void ClearPlatformLayer(); + + RegisteredBitmap CreateOrRecycleBitmap( + cc::SharedBitmapIdRegistrar* bitmap_registrar); + + // Updates the current size of the buffer, ensuring that + // s_currentResourceUsePixels is updated. + void SetSize(const IntSize&); + + // This is the order of bytes to use when doing a readback. + enum ReadbackOrder { kReadbackRGBA, kReadbackSkia }; + + // Helper function which does a readback from the currently-bound + // framebuffer into a buffer of a certain size with 4-byte pixels. + void ReadBackFramebuffer(unsigned char* pixels, + int width, + int height, + ReadbackOrder, + WebGLImageConversion::AlphaOp); + + // Helper function to flip a bitmap vertically. + void FlipVertically(uint8_t* data, int width, int height); + + // If RGB emulation is required, then the CHROMIUM image's alpha channel + // must be immediately cleared after it is bound to a texture. Nothing + // should be allowed to change the alpha channel after this. + void ClearChromiumImageAlpha(const ColorBuffer&); + + // Tries to create a CHROMIUM_image backed texture if + // RuntimeEnabledFeatures::WebGLImageChromiumEnabled() is true. On failure, + // or if the flag is false, creates a default texture. Always returns a valid + // ColorBuffer. + scoped_refptr<ColorBuffer> CreateColorBuffer(const IntSize&); + + // Creates or recycles a ColorBuffer of size |m_size|. + scoped_refptr<ColorBuffer> CreateOrRecycleColorBuffer(); + + // Attaches |m_backColorBuffer| to |m_fbo|, which is always the source for + // read operations. + void AttachColorBufferToReadFramebuffer(); + + // Whether the WebGL client desires an explicit resolve. This is + // implemented by forwarding all draw operations to a multisample + // renderbuffer, which is resolved before any read operations or swaps. + bool WantExplicitResolve(); + + // Whether the WebGL client wants a depth or stencil buffer. + bool WantDepthOrStencil(); + + // Helpers to ensure correct behavior of BlitFramebuffer when using + // an emulated RGB CHROMIUM_image back buffer. + bool SetupRGBEmulationForBlitFramebuffer(bool is_user_draw_framebuffer_bound); + void CleanupRGBEmulationForBlitFramebuffer(); + + // Weak, reset by beginDestruction. + Client* client_ = nullptr; + + const PreserveDrawingBuffer preserve_drawing_buffer_; + const WebGLVersion webgl_version_; + + std::unique_ptr<WebGraphicsContext3DProviderWrapper> context_provider_; + // Lifetime is tied to the m_contextProvider. + gpu::gles2::GLES2Interface* gl_; + std::unique_ptr<Extensions3DUtil> extensions_util_; + IntSize size_ = {-1, -1}; + const bool discard_framebuffer_supported_; + // Did the user request an alpha channel be allocated. + const bool want_alpha_channel_; + // Do we explicitly allocate an alpha channel in our ColorBuffer allocations. + // Note that this does not apply to |multisample_renderbuffer_|. + bool allocate_alpha_channel_ = false; + // Does our allocation have an alpha channel (potentially implicitly created). + // Note that this determines if |multisample_renderbuffer_| allocates an alpha + // channel. + bool have_alpha_channel_ = false; + const bool premultiplied_alpha_; + const bool using_gpu_compositing_; + bool has_implicit_stencil_buffer_ = false; + bool storage_texture_supported_ = false; + + // The texture target (2D or RECTANGLE) for our allocations. + GLenum texture_target_ = 0; + + // The current state restorer, which is used to track state dirtying. It is an + // error to dirty state shared with WebGL while there is no existing state + // restorer. + ScopedStateRestorer* state_restorer_ = nullptr; + + // This is used when the user requests either a depth or stencil buffer. + GLuint depth_stencil_buffer_ = 0; + + // When wantExplicitResolve() returns true, the target of all draw + // operations. + GLuint multisample_fbo_ = 0; + + // The id of the renderbuffer storage for |m_multisampleFBO|. + GLuint multisample_renderbuffer_ = 0; + + // If premultipliedAlpha:false is set during context creation, and a + // GpuMemoryBuffer is used for the DrawingBuffer's storage, then a separate, + // regular, OpenGL texture is allocated to hold either the rendering results + // (if antialias:false) or resolve results (if antialias:true). Then + // CopyTextureCHROMIUM is used to multiply the alpha channel into the color + // channels when copying into the GMB. + GLuint premultiplied_alpha_false_texture_ = 0; + + // When wantExplicitResolve() returns false, the target of all draw and + // read operations. When wantExplicitResolve() returns true, the target of + // all read operations. + GLuint fbo_ = 0; + + // The ColorBuffer that backs |m_fbo|. + scoped_refptr<ColorBuffer> back_color_buffer_; + + // The ColorBuffer that was most recently presented to the compositor by + // PrepareTransferableResourceInternal. + scoped_refptr<ColorBuffer> front_color_buffer_; + + // True if our contents have been modified since the last presentation of this + // buffer. + bool contents_changed_ = true; + + // True if resolveIfNeeded() has been called since the last time + // markContentsChanged() had been called. + bool contents_change_resolved_ = false; + + // A bitmask of GL buffer bits (GL_COLOR_BUFFER_BIT, + // GL_DEPTH_BUFFER_BIT, GL_STENCIL_BUFFER_BIT) which need to be + // auto-cleared. + GLbitfield buffers_to_auto_clear_ = 0; + + // Whether the client wants a depth or stencil buffer. + const bool want_depth_; + const bool want_stencil_; + + // The color space of this buffer's storage, and the color space in which + // shader samplers will read this buffer. + const gfx::ColorSpace storage_color_space_; + const gfx::ColorSpace sampler_color_space_; + + enum AntialiasingMode { + kNone, + kMSAAImplicitResolve, + kMSAAExplicitResolve, + kScreenSpaceAntialiasing, + }; + + AntialiasingMode anti_aliasing_mode_ = kNone; + + bool use_half_float_storage_ = false; + + int max_texture_size_ = 0; + int sample_count_ = 0; + bool destruction_in_progress_ = false; + bool is_hidden_ = false; + SkFilterQuality filter_quality_ = kLow_SkFilterQuality; + + std::unique_ptr<WebExternalTextureLayer> layer_; + + // Mailboxes that were released by the compositor can be used again by this + // DrawingBuffer. + Deque<scoped_refptr<ColorBuffer>> recycled_color_buffer_queue_; + + // If the width and height of the Canvas's backing store don't + // match those that we were given in the most recent call to + // reshape(), then we need an intermediate bitmap to read back the + // frame buffer into. This seems to happen when CSS styles are + // used to resize the Canvas. + SkBitmap resizing_bitmap_; + + // In the case of OffscreenCanvas, we do not want to enable the + // WebGLImageChromium flag, so we replace all the + // RuntimeEnabledFeatures::WebGLImageChromiumEnabled() call with + // shouldUseChromiumImage() calls, and set m_chromiumImageUsage to + // DisallowChromiumImage in the case of OffscreenCanvas. + ChromiumImageUsage chromium_image_usage_; + bool ShouldUseChromiumImage(); +}; + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_PLATFORM_GRAPHICS_GPU_DRAWING_BUFFER_H_ diff --git a/chromium/third_party/blink/renderer/platform/graphics/gpu/drawing_buffer_software_rendering_test.cc b/chromium/third_party/blink/renderer/platform/graphics/gpu/drawing_buffer_software_rendering_test.cc new file mode 100644 index 00000000000..c8e07ba0c7a --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/graphics/gpu/drawing_buffer_software_rendering_test.cc @@ -0,0 +1,117 @@ +// Copyright 2016 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 "third_party/blink/renderer/platform/graphics/gpu/drawing_buffer.h" + +#include "cc/resources/shared_bitmap_id_registrar.h" +#include "components/viz/common/resources/single_release_callback.h" +#include "components/viz/common/resources/transferable_resource.h" +#include "gpu/command_buffer/client/gles2_interface_stub.h" +#include "gpu/config/gpu_feature_info.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/blink/renderer/platform/graphics/gpu/drawing_buffer_test_helpers.h" + +// These unit tests are separate from DrawingBufferTests.cpp because they are +// built as a part of webkit_unittests instead blink_platform_unittests. This is +// because the software rendering mode has a dependency on the blink::Platform +// interface for buffer allocations. + +namespace blink { +namespace { + +class TestSharedBitmapIdRegistar : public cc::SharedBitmapIdRegistrar { + virtual cc::SharedBitmapIdRegistration RegisterSharedBitmapId( + const viz::SharedBitmapId& id, + scoped_refptr<cc::CrossThreadSharedBitmap> bitmap) { + return {}; + } +}; + +class DrawingBufferSoftwareCompositingTest : public testing::Test { + protected: + void SetUp() override { + IntSize initial_size(kInitialWidth, kInitialHeight); + auto gl = std::make_unique<GLES2InterfaceForTests>(); + auto provider = + std::make_unique<WebGraphicsContext3DProviderForTests>(std::move(gl)); + GLES2InterfaceForTests* gl_ = + static_cast<GLES2InterfaceForTests*>(provider->ContextGL()); + bool gpu_compositing = false; + drawing_buffer_ = DrawingBufferForTests::Create( + std::move(provider), gpu_compositing, gl_, initial_size, + DrawingBuffer::kPreserve, kDisableMultisampling); + CHECK(drawing_buffer_); + } + + scoped_refptr<DrawingBufferForTests> drawing_buffer_; + TestSharedBitmapIdRegistar test_shared_bitmap_id_registrar_; +}; + +TEST_F(DrawingBufferSoftwareCompositingTest, BitmapRecycling) { + viz::TransferableResource resource; + std::unique_ptr<viz::SingleReleaseCallback> release_callback1; + std::unique_ptr<viz::SingleReleaseCallback> release_callback2; + std::unique_ptr<viz::SingleReleaseCallback> release_callback3; + IntSize initial_size(kInitialWidth, kInitialHeight); + IntSize alternate_size(kInitialWidth, kAlternateHeight); + + drawing_buffer_->Resize(initial_size); + drawing_buffer_->MarkContentsChanged(); + drawing_buffer_->PrepareTransferableResource( + &test_shared_bitmap_id_registrar_, &resource, + &release_callback1); // create a bitmap. + EXPECT_EQ(0, drawing_buffer_->RecycledBitmapCount()); + release_callback1->Run( + gpu::SyncToken(), + false /* lostResource */); // release bitmap to the recycling queue + EXPECT_EQ(1, drawing_buffer_->RecycledBitmapCount()); + drawing_buffer_->MarkContentsChanged(); + drawing_buffer_->PrepareTransferableResource( + &test_shared_bitmap_id_registrar_, &resource, + &release_callback2); // recycle a bitmap. + EXPECT_EQ(0, drawing_buffer_->RecycledBitmapCount()); + release_callback2->Run( + gpu::SyncToken(), + false /* lostResource */); // release bitmap to the recycling queue + EXPECT_EQ(1, drawing_buffer_->RecycledBitmapCount()); + drawing_buffer_->Resize(alternate_size); + drawing_buffer_->MarkContentsChanged(); + // Regression test for crbug.com/647896 - Next line must not crash + drawing_buffer_->PrepareTransferableResource( + &test_shared_bitmap_id_registrar_, &resource, + &release_callback3); // cause recycling queue to be purged due to resize + EXPECT_EQ(0, drawing_buffer_->RecycledBitmapCount()); + release_callback3->Run(gpu::SyncToken(), false /* lostResource */); + EXPECT_EQ(1, drawing_buffer_->RecycledBitmapCount()); + + drawing_buffer_->BeginDestruction(); +} + +TEST_F(DrawingBufferSoftwareCompositingTest, FramebufferBinding) { + GLES2InterfaceForTests* gl_ = drawing_buffer_->ContextGLForTests(); + viz::TransferableResource resource; + std::unique_ptr<viz::SingleReleaseCallback> release_callback; + IntSize initial_size(kInitialWidth, kInitialHeight); + GLint drawBinding = 0, readBinding = 0; + + GLuint draw_framebuffer_binding = 0xbeef3; + GLuint read_framebuffer_binding = 0xbeef4; + gl_->BindFramebuffer(GL_DRAW_FRAMEBUFFER, draw_framebuffer_binding); + gl_->BindFramebuffer(GL_READ_FRAMEBUFFER, read_framebuffer_binding); + gl_->SaveState(); + drawing_buffer_->Resize(initial_size); + drawing_buffer_->MarkContentsChanged(); + drawing_buffer_->PrepareTransferableResource( + &test_shared_bitmap_id_registrar_, &resource, &release_callback); + gl_->GetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &drawBinding); + gl_->GetIntegerv(GL_READ_FRAMEBUFFER_BINDING, &readBinding); + EXPECT_EQ(static_cast<GLint>(draw_framebuffer_binding), drawBinding); + EXPECT_EQ(static_cast<GLint>(read_framebuffer_binding), readBinding); + release_callback->Run(gpu::SyncToken(), false /* lostResource */); + + drawing_buffer_->BeginDestruction(); +} + +} // unnamed namespace +} // blink diff --git a/chromium/third_party/blink/renderer/platform/graphics/gpu/drawing_buffer_test.cc b/chromium/third_party/blink/renderer/platform/graphics/gpu/drawing_buffer_test.cc new file mode 100644 index 00000000000..e4171a1f293 --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/graphics/gpu/drawing_buffer_test.cc @@ -0,0 +1,744 @@ +/* + * Copyright (C) 2013 Google Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "third_party/blink/renderer/platform/graphics/gpu/drawing_buffer.h" + +#include <memory> +#include "base/memory/scoped_refptr.h" +#include "components/viz/common/resources/single_release_callback.h" +#include "components/viz/common/resources/transferable_resource.h" +#include "components/viz/test/test_gpu_memory_buffer_manager.h" +#include "gpu/command_buffer/client/gles2_interface_stub.h" +#include "gpu/command_buffer/common/mailbox.h" +#include "gpu/command_buffer/common/sync_token.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/blink/public/platform/platform.h" +#include "third_party/blink/renderer/platform/graphics/canvas_color_params.h" +#include "third_party/blink/renderer/platform/graphics/gpu/drawing_buffer_test_helpers.h" +#include "third_party/blink/renderer/platform/testing/runtime_enabled_features_test_helpers.h" +#include "third_party/blink/renderer/platform/testing/testing_platform_support.h" +#include "v8/include/v8.h" + +using testing::Test; +using testing::_; + +namespace blink { + +namespace { + +class FakePlatformSupport : public TestingPlatformSupport { + gpu::GpuMemoryBufferManager* GetGpuMemoryBufferManager() override { + return &test_gpu_memory_buffer_manager_; + } + + private: + viz::TestGpuMemoryBufferManager test_gpu_memory_buffer_manager_; +}; + +} // anonymous namespace + +class DrawingBufferTest : public Test { + protected: + void SetUp() override { Init(kDisableMultisampling); } + + void Init(UseMultisampling use_multisampling) { + IntSize initial_size(kInitialWidth, kInitialHeight); + auto gl = std::make_unique<GLES2InterfaceForTests>(); + auto provider = + std::make_unique<WebGraphicsContext3DProviderForTests>(std::move(gl)); + GLES2InterfaceForTests* gl_ = + static_cast<GLES2InterfaceForTests*>(provider->ContextGL()); + bool gpu_compositing = true; + drawing_buffer_ = DrawingBufferForTests::Create( + std::move(provider), gpu_compositing, gl_, initial_size, + DrawingBuffer::kPreserve, use_multisampling); + CHECK(drawing_buffer_); + SetAndSaveRestoreState(false); + } + + // Initialize GL state with unusual values, to verify that they are restored. + // The |invert| parameter will reverse all boolean parameters, so that all + // values are tested. + void SetAndSaveRestoreState(bool invert) { + GLES2InterfaceForTests* gl_ = drawing_buffer_->ContextGLForTests(); + GLboolean scissor_enabled = !invert; + GLfloat clear_color[4] = {0.1, 0.2, 0.3, 0.4}; + GLfloat clear_depth = 0.8; + GLint clear_stencil = 37; + GLboolean color_mask[4] = {invert, !invert, !invert, invert}; + GLboolean depth_mask = invert; + GLboolean stencil_mask = invert; + GLint pack_alignment = 7; + GLuint active_texture2d_binding = 0xbeef1; + GLuint renderbuffer_binding = 0xbeef2; + GLuint draw_framebuffer_binding = 0xbeef3; + GLuint read_framebuffer_binding = 0xbeef4; + GLuint pixel_unpack_buffer_binding = 0xbeef5; + + if (scissor_enabled) + gl_->Enable(GL_SCISSOR_TEST); + else + gl_->Disable(GL_SCISSOR_TEST); + + gl_->ClearColor(clear_color[0], clear_color[1], clear_color[2], + clear_color[3]); + gl_->ClearDepthf(clear_depth); + gl_->ClearStencil(clear_stencil); + gl_->ColorMask(color_mask[0], color_mask[1], color_mask[2], color_mask[3]); + gl_->DepthMask(depth_mask); + gl_->StencilMask(stencil_mask); + gl_->PixelStorei(GL_PACK_ALIGNMENT, pack_alignment); + gl_->BindTexture(GL_TEXTURE_2D, active_texture2d_binding); + gl_->BindRenderbuffer(GL_RENDERBUFFER, renderbuffer_binding); + gl_->BindFramebuffer(GL_DRAW_FRAMEBUFFER, draw_framebuffer_binding); + gl_->BindFramebuffer(GL_READ_FRAMEBUFFER, read_framebuffer_binding); + gl_->BindBuffer(GL_PIXEL_UNPACK_BUFFER, pixel_unpack_buffer_binding); + + gl_->SaveState(); + } + + void VerifyStateWasRestored() { + GLES2InterfaceForTests* gl_ = drawing_buffer_->ContextGLForTests(); + gl_->VerifyStateHasNotChangedSinceSave(); + } + + scoped_refptr<DrawingBufferForTests> drawing_buffer_; +}; + +class DrawingBufferTestMultisample : public DrawingBufferTest { + protected: + void SetUp() override { Init(kEnableMultisampling); } +}; + +TEST_F(DrawingBufferTestMultisample, verifyMultisampleResolve) { + // Initial state: already marked changed, multisampled + EXPECT_FALSE(drawing_buffer_->MarkContentsChanged()); + EXPECT_TRUE(drawing_buffer_->ExplicitResolveOfMultisampleData()); + + // Resolve the multisample buffer + drawing_buffer_->ResolveAndBindForReadAndDraw(); + + // After resolve, acknowledge new content + EXPECT_TRUE(drawing_buffer_->MarkContentsChanged()); + // No new content + EXPECT_FALSE(drawing_buffer_->MarkContentsChanged()); + + drawing_buffer_->BeginDestruction(); +} + +TEST_F(DrawingBufferTest, VerifyResizingProperlyAffectsResources) { + GLES2InterfaceForTests* gl_ = drawing_buffer_->ContextGLForTests(); + VerifyStateWasRestored(); + viz::TransferableResource resource; + std::unique_ptr<viz::SingleReleaseCallback> release_callback; + + IntSize initial_size(kInitialWidth, kInitialHeight); + IntSize alternate_size(kInitialWidth, kAlternateHeight); + + // Produce one resource at size 100x100. + EXPECT_FALSE(drawing_buffer_->MarkContentsChanged()); + EXPECT_TRUE(drawing_buffer_->PrepareTransferableResource(nullptr, &resource, + &release_callback)); + VerifyStateWasRestored(); + EXPECT_EQ(initial_size, gl_->MostRecentlyProducedSize()); + + // Resize to 100x50. + drawing_buffer_->Resize(alternate_size); + VerifyStateWasRestored(); + release_callback->Run(gpu::SyncToken(), false /* lostResource */); + VerifyStateWasRestored(); + + // Produce a resource at this size. + EXPECT_TRUE(drawing_buffer_->MarkContentsChanged()); + EXPECT_TRUE(drawing_buffer_->PrepareTransferableResource(nullptr, &resource, + &release_callback)); + EXPECT_EQ(alternate_size, gl_->MostRecentlyProducedSize()); + VerifyStateWasRestored(); + + // Reset to initial size. + drawing_buffer_->Resize(initial_size); + VerifyStateWasRestored(); + SetAndSaveRestoreState(true); + release_callback->Run(gpu::SyncToken(), false /* lostResource */); + VerifyStateWasRestored(); + + // Prepare another resource and verify that it's the correct size. + EXPECT_TRUE(drawing_buffer_->MarkContentsChanged()); + EXPECT_TRUE(drawing_buffer_->PrepareTransferableResource(nullptr, &resource, + &release_callback)); + EXPECT_EQ(initial_size, gl_->MostRecentlyProducedSize()); + VerifyStateWasRestored(); + + // Prepare one final resource and verify that it's the correct size. + release_callback->Run(gpu::SyncToken(), false /* lostResource */); + EXPECT_TRUE(drawing_buffer_->MarkContentsChanged()); + EXPECT_TRUE(drawing_buffer_->PrepareTransferableResource(nullptr, &resource, + &release_callback)); + VerifyStateWasRestored(); + EXPECT_EQ(initial_size, gl_->MostRecentlyProducedSize()); + release_callback->Run(gpu::SyncToken(), false /* lostResource */); + drawing_buffer_->BeginDestruction(); +} + +TEST_F(DrawingBufferTest, VerifyDestructionCompleteAfterAllResourceReleased) { + bool live = true; + drawing_buffer_->live_ = &live; + + viz::TransferableResource resource1; + std::unique_ptr<viz::SingleReleaseCallback> release_callback1; + viz::TransferableResource resource2; + std::unique_ptr<viz::SingleReleaseCallback> release_callback2; + viz::TransferableResource resource3; + std::unique_ptr<viz::SingleReleaseCallback> release_callback3; + + IntSize initial_size(kInitialWidth, kInitialHeight); + + // Produce resources. + EXPECT_FALSE(drawing_buffer_->MarkContentsChanged()); + drawing_buffer_->ClearFramebuffers(GL_STENCIL_BUFFER_BIT); + VerifyStateWasRestored(); + EXPECT_TRUE(drawing_buffer_->PrepareTransferableResource(nullptr, &resource1, + &release_callback1)); + EXPECT_TRUE(drawing_buffer_->MarkContentsChanged()); + drawing_buffer_->ClearFramebuffers(GL_DEPTH_BUFFER_BIT); + VerifyStateWasRestored(); + EXPECT_TRUE(drawing_buffer_->PrepareTransferableResource(nullptr, &resource2, + &release_callback2)); + EXPECT_TRUE(drawing_buffer_->MarkContentsChanged()); + drawing_buffer_->ClearFramebuffers(GL_COLOR_BUFFER_BIT); + VerifyStateWasRestored(); + EXPECT_TRUE(drawing_buffer_->PrepareTransferableResource(nullptr, &resource3, + &release_callback3)); + + EXPECT_TRUE(drawing_buffer_->MarkContentsChanged()); + release_callback1->Run(gpu::SyncToken(), false /* lostResource */); + + drawing_buffer_->BeginDestruction(); + ASSERT_EQ(live, true); + + DrawingBufferForTests* raw_pointer = drawing_buffer_.get(); + drawing_buffer_ = nullptr; + ASSERT_EQ(live, true); + + EXPECT_FALSE(raw_pointer->MarkContentsChanged()); + release_callback2->Run(gpu::SyncToken(), false /* lostResource */); + ASSERT_EQ(live, true); + + EXPECT_FALSE(raw_pointer->MarkContentsChanged()); + release_callback3->Run(gpu::SyncToken(), false /* lostResource */); + ASSERT_EQ(live, false); +} + +TEST_F(DrawingBufferTest, verifyDrawingBufferStaysAliveIfResourcesAreLost) { + bool live = true; + drawing_buffer_->live_ = &live; + + viz::TransferableResource resource1; + std::unique_ptr<viz::SingleReleaseCallback> release_callback1; + viz::TransferableResource resource2; + std::unique_ptr<viz::SingleReleaseCallback> release_callback2; + viz::TransferableResource resource3; + std::unique_ptr<viz::SingleReleaseCallback> release_callback3; + + EXPECT_FALSE(drawing_buffer_->MarkContentsChanged()); + EXPECT_TRUE(drawing_buffer_->PrepareTransferableResource(nullptr, &resource1, + &release_callback1)); + VerifyStateWasRestored(); + EXPECT_TRUE(drawing_buffer_->MarkContentsChanged()); + EXPECT_TRUE(drawing_buffer_->PrepareTransferableResource(nullptr, &resource2, + &release_callback2)); + VerifyStateWasRestored(); + EXPECT_TRUE(drawing_buffer_->MarkContentsChanged()); + EXPECT_TRUE(drawing_buffer_->PrepareTransferableResource(nullptr, &resource3, + &release_callback3)); + VerifyStateWasRestored(); + + EXPECT_TRUE(drawing_buffer_->MarkContentsChanged()); + release_callback1->Run(gpu::SyncToken(), true /* lostResource */); + EXPECT_EQ(live, true); + + drawing_buffer_->BeginDestruction(); + EXPECT_EQ(live, true); + + EXPECT_FALSE(drawing_buffer_->MarkContentsChanged()); + release_callback2->Run(gpu::SyncToken(), false /* lostResource */); + EXPECT_EQ(live, true); + + DrawingBufferForTests* raw_ptr = drawing_buffer_.get(); + drawing_buffer_ = nullptr; + EXPECT_EQ(live, true); + + EXPECT_FALSE(raw_ptr->MarkContentsChanged()); + release_callback3->Run(gpu::SyncToken(), true /* lostResource */); + EXPECT_EQ(live, false); +} + +TEST_F(DrawingBufferTest, VerifyOnlyOneRecycledResourceMustBeKept) { + viz::TransferableResource resource1; + std::unique_ptr<viz::SingleReleaseCallback> release_callback1; + viz::TransferableResource resource2; + std::unique_ptr<viz::SingleReleaseCallback> release_callback2; + viz::TransferableResource resource3; + std::unique_ptr<viz::SingleReleaseCallback> release_callback3; + + // Produce resources. + EXPECT_FALSE(drawing_buffer_->MarkContentsChanged()); + EXPECT_TRUE(drawing_buffer_->PrepareTransferableResource(nullptr, &resource1, + &release_callback1)); + EXPECT_TRUE(drawing_buffer_->MarkContentsChanged()); + EXPECT_TRUE(drawing_buffer_->PrepareTransferableResource(nullptr, &resource2, + &release_callback2)); + EXPECT_TRUE(drawing_buffer_->MarkContentsChanged()); + EXPECT_TRUE(drawing_buffer_->PrepareTransferableResource(nullptr, &resource3, + &release_callback3)); + + // Release resources by specific order; 1, 3, 2. + EXPECT_TRUE(drawing_buffer_->MarkContentsChanged()); + release_callback1->Run(gpu::SyncToken(), false /* lostResource */); + EXPECT_FALSE(drawing_buffer_->MarkContentsChanged()); + release_callback3->Run(gpu::SyncToken(), false /* lostResource */); + EXPECT_FALSE(drawing_buffer_->MarkContentsChanged()); + release_callback2->Run(gpu::SyncToken(), false /* lostResource */); + + // The first recycled resource must be 2. 1 and 3 were deleted by FIFO order + // because DrawingBuffer never keeps more than one resource. + viz::TransferableResource recycled_resource1; + std::unique_ptr<viz::SingleReleaseCallback> recycled_release_callback1; + EXPECT_FALSE(drawing_buffer_->MarkContentsChanged()); + EXPECT_TRUE(drawing_buffer_->PrepareTransferableResource( + nullptr, &recycled_resource1, &recycled_release_callback1)); + EXPECT_EQ(resource2.mailbox_holder.mailbox, + recycled_resource1.mailbox_holder.mailbox); + + // The second recycled resource must be a new resource. + viz::TransferableResource recycled_resource2; + std::unique_ptr<viz::SingleReleaseCallback> recycled_release_callback2; + EXPECT_TRUE(drawing_buffer_->MarkContentsChanged()); + EXPECT_TRUE(drawing_buffer_->PrepareTransferableResource( + nullptr, &recycled_resource2, &recycled_release_callback2)); + EXPECT_NE(resource1.mailbox_holder.mailbox, + recycled_resource2.mailbox_holder.mailbox); + EXPECT_NE(resource2.mailbox_holder.mailbox, + recycled_resource2.mailbox_holder.mailbox); + EXPECT_NE(resource3.mailbox_holder.mailbox, + recycled_resource2.mailbox_holder.mailbox); + + recycled_release_callback1->Run(gpu::SyncToken(), false /* lostResource */); + recycled_release_callback2->Run(gpu::SyncToken(), false /* lostResource */); + drawing_buffer_->BeginDestruction(); +} + +TEST_F(DrawingBufferTest, verifyInsertAndWaitSyncTokenCorrectly) { + GLES2InterfaceForTests* gl_ = drawing_buffer_->ContextGLForTests(); + viz::TransferableResource resource; + std::unique_ptr<viz::SingleReleaseCallback> release_callback; + + // Produce resources. + EXPECT_FALSE(drawing_buffer_->MarkContentsChanged()); + EXPECT_EQ(gpu::SyncToken(), gl_->MostRecentlyWaitedSyncToken()); + EXPECT_TRUE(drawing_buffer_->PrepareTransferableResource(nullptr, &resource, + &release_callback)); + // PrepareTransferableResource() does not wait for any sync point. + EXPECT_EQ(gpu::SyncToken(), gl_->MostRecentlyWaitedSyncToken()); + + gpu::SyncToken wait_sync_token; + gl_->GenSyncTokenCHROMIUM(wait_sync_token.GetData()); + release_callback->Run(wait_sync_token, false /* lostResource */); + // m_drawingBuffer will wait for the sync point when recycling. + EXPECT_EQ(gpu::SyncToken(), gl_->MostRecentlyWaitedSyncToken()); + + EXPECT_TRUE(drawing_buffer_->MarkContentsChanged()); + EXPECT_TRUE(drawing_buffer_->PrepareTransferableResource(nullptr, &resource, + &release_callback)); + // m_drawingBuffer waits for the sync point when recycling in + // PrepareTransferableResource(). + EXPECT_EQ(wait_sync_token, gl_->MostRecentlyWaitedSyncToken()); + + drawing_buffer_->BeginDestruction(); + gl_->GenSyncTokenCHROMIUM(wait_sync_token.GetData()); + release_callback->Run(wait_sync_token, false /* lostResource */); + // m_drawingBuffer waits for the sync point because the destruction is in + // progress. + EXPECT_EQ(wait_sync_token, gl_->MostRecentlyWaitedSyncToken()); +} + +class DrawingBufferImageChromiumTest : public DrawingBufferTest, + private ScopedWebGLImageChromiumForTest { + public: + DrawingBufferImageChromiumTest() : ScopedWebGLImageChromiumForTest(true) {} + + protected: + void SetUp() override { + platform_.reset(new ScopedTestingPlatformSupport<FakePlatformSupport>); + + IntSize initial_size(kInitialWidth, kInitialHeight); + auto gl = std::make_unique<GLES2InterfaceForTests>(); + auto provider = + std::make_unique<WebGraphicsContext3DProviderForTests>(std::move(gl)); + GLES2InterfaceForTests* gl_ = + static_cast<GLES2InterfaceForTests*>(provider->ContextGL()); + image_id0_ = gl_->NextImageIdToBeCreated(); + EXPECT_CALL(*gl_, BindTexImage2DMock(image_id0_)).Times(1); + bool gpu_compositing = true; + drawing_buffer_ = DrawingBufferForTests::Create( + std::move(provider), gpu_compositing, gl_, initial_size, + DrawingBuffer::kPreserve, kDisableMultisampling); + CHECK(drawing_buffer_); + SetAndSaveRestoreState(true); + testing::Mock::VerifyAndClearExpectations(gl_); + } + + void TearDown() override { + platform_.reset(); + } + + GLuint image_id0_; + std::unique_ptr<ScopedTestingPlatformSupport<FakePlatformSupport>> platform_; +}; + +TEST_F(DrawingBufferImageChromiumTest, VerifyResizingReallocatesImages) { + GLES2InterfaceForTests* gl_ = drawing_buffer_->ContextGLForTests(); + viz::TransferableResource resource; + std::unique_ptr<viz::SingleReleaseCallback> release_callback; + + IntSize initial_size(kInitialWidth, kInitialHeight); + IntSize alternate_size(kInitialWidth, kAlternateHeight); + + GLuint image_id1 = gl_->NextImageIdToBeCreated(); + EXPECT_CALL(*gl_, BindTexImage2DMock(image_id1)).Times(1); + // Produce one resource at size 100x100. + EXPECT_FALSE(drawing_buffer_->MarkContentsChanged()); + EXPECT_TRUE(drawing_buffer_->PrepareTransferableResource(nullptr, &resource, + &release_callback)); + EXPECT_EQ(initial_size, gl_->MostRecentlyProducedSize()); + EXPECT_TRUE(resource.is_overlay_candidate); + EXPECT_EQ(initial_size, resource.size); + testing::Mock::VerifyAndClearExpectations(gl_); + VerifyStateWasRestored(); + + GLuint image_id2 = gl_->NextImageIdToBeCreated(); + EXPECT_CALL(*gl_, BindTexImage2DMock(image_id2)).Times(1); + EXPECT_CALL(*gl_, DestroyImageMock(image_id0_)).Times(1); + EXPECT_CALL(*gl_, ReleaseTexImage2DMock(image_id0_)).Times(1); + EXPECT_CALL(*gl_, DestroyImageMock(image_id1)).Times(1); + EXPECT_CALL(*gl_, ReleaseTexImage2DMock(image_id1)).Times(1); + // Resize to 100x50. + drawing_buffer_->Resize(alternate_size); + VerifyStateWasRestored(); + release_callback->Run(gpu::SyncToken(), false /* lostResource */); + VerifyStateWasRestored(); + testing::Mock::VerifyAndClearExpectations(gl_); + + GLuint image_id3 = gl_->NextImageIdToBeCreated(); + EXPECT_CALL(*gl_, BindTexImage2DMock(image_id3)).Times(1); + // Produce a resource at this size. + EXPECT_TRUE(drawing_buffer_->MarkContentsChanged()); + EXPECT_TRUE(drawing_buffer_->PrepareTransferableResource(nullptr, &resource, + &release_callback)); + EXPECT_EQ(alternate_size, gl_->MostRecentlyProducedSize()); + EXPECT_TRUE(resource.is_overlay_candidate); + EXPECT_EQ(alternate_size, resource.size); + testing::Mock::VerifyAndClearExpectations(gl_); + + GLuint image_id4 = gl_->NextImageIdToBeCreated(); + EXPECT_CALL(*gl_, BindTexImage2DMock(image_id4)).Times(1); + EXPECT_CALL(*gl_, DestroyImageMock(image_id2)).Times(1); + EXPECT_CALL(*gl_, ReleaseTexImage2DMock(image_id2)).Times(1); + EXPECT_CALL(*gl_, DestroyImageMock(image_id3)).Times(1); + EXPECT_CALL(*gl_, ReleaseTexImage2DMock(image_id3)).Times(1); + // Reset to initial size. + drawing_buffer_->Resize(initial_size); + VerifyStateWasRestored(); + release_callback->Run(gpu::SyncToken(), false /* lostResource */); + VerifyStateWasRestored(); + testing::Mock::VerifyAndClearExpectations(gl_); + + GLuint image_id5 = gl_->NextImageIdToBeCreated(); + EXPECT_CALL(*gl_, BindTexImage2DMock(image_id5)).Times(1); + // Prepare another resource and verify that it's the correct size. + EXPECT_TRUE(drawing_buffer_->MarkContentsChanged()); + EXPECT_TRUE(drawing_buffer_->PrepareTransferableResource(nullptr, &resource, + &release_callback)); + EXPECT_EQ(initial_size, gl_->MostRecentlyProducedSize()); + EXPECT_TRUE(resource.is_overlay_candidate); + EXPECT_EQ(initial_size, resource.size); + testing::Mock::VerifyAndClearExpectations(gl_); + + // Prepare one final resource and verify that it's the correct size. + release_callback->Run(gpu::SyncToken(), false /* lostResource */); + EXPECT_TRUE(drawing_buffer_->MarkContentsChanged()); + EXPECT_TRUE(drawing_buffer_->PrepareTransferableResource(nullptr, &resource, + &release_callback)); + EXPECT_EQ(initial_size, gl_->MostRecentlyProducedSize()); + EXPECT_TRUE(resource.is_overlay_candidate); + EXPECT_EQ(initial_size, resource.size); + release_callback->Run(gpu::SyncToken(), false /* lostResource */); + + EXPECT_CALL(*gl_, DestroyImageMock(image_id5)).Times(1); + EXPECT_CALL(*gl_, ReleaseTexImage2DMock(image_id5)).Times(1); + EXPECT_CALL(*gl_, DestroyImageMock(image_id4)).Times(1); + EXPECT_CALL(*gl_, ReleaseTexImage2DMock(image_id4)).Times(1); + drawing_buffer_->BeginDestruction(); + testing::Mock::VerifyAndClearExpectations(gl_); +} + +TEST_F(DrawingBufferImageChromiumTest, AllocationFailure) { + GLES2InterfaceForTests* gl_ = drawing_buffer_->ContextGLForTests(); + viz::TransferableResource resource1; + std::unique_ptr<viz::SingleReleaseCallback> release_callback1; + viz::TransferableResource resource2; + std::unique_ptr<viz::SingleReleaseCallback> release_callback2; + viz::TransferableResource resource3; + std::unique_ptr<viz::SingleReleaseCallback> release_callback3; + + // Request a resource. An image should already be created. Everything works + // as expected. + EXPECT_CALL(*gl_, BindTexImage2DMock(_)).Times(1); + IntSize initial_size(kInitialWidth, kInitialHeight); + EXPECT_FALSE(drawing_buffer_->MarkContentsChanged()); + EXPECT_TRUE(drawing_buffer_->PrepareTransferableResource(nullptr, &resource1, + &release_callback1)); + EXPECT_TRUE(resource1.is_overlay_candidate); + testing::Mock::VerifyAndClearExpectations(gl_); + VerifyStateWasRestored(); + + // Force image CHROMIUM creation failure. Request another resource. It should + // still be provided, but this time with allowOverlay = false. + gl_->SetCreateImageChromiumFail(true); + EXPECT_TRUE(drawing_buffer_->MarkContentsChanged()); + EXPECT_TRUE(drawing_buffer_->PrepareTransferableResource(nullptr, &resource2, + &release_callback2)); + EXPECT_FALSE(resource2.is_overlay_candidate); + VerifyStateWasRestored(); + + // Check that if image CHROMIUM starts working again, resources are + // correctly created with allowOverlay = true. + EXPECT_CALL(*gl_, BindTexImage2DMock(_)).Times(1); + gl_->SetCreateImageChromiumFail(false); + EXPECT_TRUE(drawing_buffer_->MarkContentsChanged()); + EXPECT_TRUE(drawing_buffer_->PrepareTransferableResource(nullptr, &resource3, + &release_callback3)); + EXPECT_TRUE(resource3.is_overlay_candidate); + testing::Mock::VerifyAndClearExpectations(gl_); + VerifyStateWasRestored(); + + release_callback1->Run(gpu::SyncToken(), false /* lostResource */); + release_callback2->Run(gpu::SyncToken(), false /* lostResource */); + release_callback3->Run(gpu::SyncToken(), false /* lostResource */); + + EXPECT_CALL(*gl_, DestroyImageMock(_)).Times(3); + EXPECT_CALL(*gl_, ReleaseTexImage2DMock(_)).Times(3); + drawing_buffer_->BeginDestruction(); + testing::Mock::VerifyAndClearExpectations(gl_); +} + +class DepthStencilTrackingGLES2Interface + : public gpu::gles2::GLES2InterfaceStub { + public: + void FramebufferRenderbuffer(GLenum target, + GLenum attachment, + GLenum renderbuffertarget, + GLuint renderbuffer) override { + switch (attachment) { + case GL_DEPTH_ATTACHMENT: + depth_attachment_ = renderbuffer; + break; + case GL_STENCIL_ATTACHMENT: + stencil_attachment_ = renderbuffer; + break; + case GL_DEPTH_STENCIL_ATTACHMENT: + depth_stencil_attachment_ = renderbuffer; + break; + default: + NOTREACHED(); + break; + } + } + + GLenum CheckFramebufferStatus(GLenum target) override { + return GL_FRAMEBUFFER_COMPLETE; + } + + void GetIntegerv(GLenum ptype, GLint* value) override { + switch (ptype) { + case GL_MAX_TEXTURE_SIZE: + *value = 1024; + return; + } + } + + const GLubyte* GetString(GLenum type) override { + if (type == GL_EXTENSIONS) + return reinterpret_cast<const GLubyte*>("GL_OES_packed_depth_stencil"); + return reinterpret_cast<const GLubyte*>(""); + } + + void GenRenderbuffers(GLsizei n, GLuint* renderbuffers) override { + for (GLsizei i = 0; i < n; ++i) + renderbuffers[i] = next_gen_renderbuffer_id_++; + } + + GLuint StencilAttachment() const { return stencil_attachment_; } + GLuint DepthAttachment() const { return depth_attachment_; } + GLuint DepthStencilAttachment() const { return depth_stencil_attachment_; } + size_t NumAllocatedRenderBuffer() const { + return next_gen_renderbuffer_id_ - 1; + } + + private: + GLuint next_gen_renderbuffer_id_ = 1; + GLuint depth_attachment_ = 0; + GLuint stencil_attachment_ = 0; + GLuint depth_stencil_attachment_ = 0; +}; + +struct DepthStencilTestCase { + DepthStencilTestCase(bool request_stencil, + bool request_depth, + int expected_render_buffers, + const char* const test_case_name) + : request_stencil(request_stencil), + request_depth(request_depth), + expected_render_buffers(expected_render_buffers), + test_case_name(test_case_name) {} + + bool request_stencil; + bool request_depth; + size_t expected_render_buffers; + const char* const test_case_name; +}; + +// This tests that, when the packed depth+stencil extension is supported, and +// either depth or stencil is requested, DrawingBuffer always allocates a single +// packed renderbuffer and properly computes the actual context attributes as +// defined by WebGL. We always allocate a packed buffer in this case since many +// desktop OpenGL drivers that support this extension do not consider a +// framebuffer with only a depth or a stencil buffer attached to be complete. +TEST(DrawingBufferDepthStencilTest, packedDepthStencilSupported) { + DepthStencilTestCase cases[] = { + DepthStencilTestCase(false, false, 0, "neither"), + DepthStencilTestCase(true, false, 1, "stencil only"), + DepthStencilTestCase(false, true, 1, "depth only"), + DepthStencilTestCase(true, true, 1, "both"), + }; + + for (size_t i = 0; i < WTF_ARRAY_LENGTH(cases); i++) { + SCOPED_TRACE(cases[i].test_case_name); + auto gl = std::make_unique<DepthStencilTrackingGLES2Interface>(); + DepthStencilTrackingGLES2Interface* tracking_gl = gl.get(); + auto provider = + std::make_unique<WebGraphicsContext3DProviderForTests>(std::move(gl)); + DrawingBuffer::PreserveDrawingBuffer preserve = DrawingBuffer::kPreserve; + + bool premultiplied_alpha = false; + bool want_alpha_channel = true; + bool want_depth_buffer = cases[i].request_depth; + bool want_stencil_buffer = cases[i].request_stencil; + bool want_antialiasing = false; + bool gpu_compositing = true; + scoped_refptr<DrawingBuffer> drawing_buffer = DrawingBuffer::Create( + std::move(provider), gpu_compositing, nullptr, IntSize(10, 10), + premultiplied_alpha, want_alpha_channel, want_depth_buffer, + want_stencil_buffer, want_antialiasing, preserve, + DrawingBuffer::kWebGL1, DrawingBuffer::kAllowChromiumImage, + CanvasColorParams()); + + // When we request a depth or a stencil buffer, we will get both. + EXPECT_EQ(cases[i].request_depth || cases[i].request_stencil, + drawing_buffer->HasDepthBuffer()); + EXPECT_EQ(cases[i].request_depth || cases[i].request_stencil, + drawing_buffer->HasStencilBuffer()); + EXPECT_EQ(cases[i].expected_render_buffers, + tracking_gl->NumAllocatedRenderBuffer()); + if (cases[i].request_depth || cases[i].request_stencil) { + EXPECT_NE(0u, tracking_gl->DepthStencilAttachment()); + EXPECT_EQ(0u, tracking_gl->DepthAttachment()); + EXPECT_EQ(0u, tracking_gl->StencilAttachment()); + } else { + EXPECT_EQ(0u, tracking_gl->DepthStencilAttachment()); + EXPECT_EQ(0u, tracking_gl->DepthAttachment()); + EXPECT_EQ(0u, tracking_gl->StencilAttachment()); + } + + drawing_buffer->Resize(IntSize(10, 20)); + EXPECT_EQ(cases[i].request_depth || cases[i].request_stencil, + drawing_buffer->HasDepthBuffer()); + EXPECT_EQ(cases[i].request_depth || cases[i].request_stencil, + drawing_buffer->HasStencilBuffer()); + EXPECT_EQ(cases[i].expected_render_buffers, + tracking_gl->NumAllocatedRenderBuffer()); + if (cases[i].request_depth || cases[i].request_stencil) { + EXPECT_NE(0u, tracking_gl->DepthStencilAttachment()); + EXPECT_EQ(0u, tracking_gl->DepthAttachment()); + EXPECT_EQ(0u, tracking_gl->StencilAttachment()); + } else { + EXPECT_EQ(0u, tracking_gl->DepthStencilAttachment()); + EXPECT_EQ(0u, tracking_gl->DepthAttachment()); + EXPECT_EQ(0u, tracking_gl->StencilAttachment()); + } + + drawing_buffer->BeginDestruction(); + } +} + +TEST_F(DrawingBufferTest, VerifySetIsHiddenProperlyAffectsMailboxes) { + GLES2InterfaceForTests* gl_ = drawing_buffer_->ContextGLForTests(); + viz::TransferableResource resource; + std::unique_ptr<viz::SingleReleaseCallback> release_callback; + + // Produce resources. + EXPECT_FALSE(drawing_buffer_->MarkContentsChanged()); + EXPECT_TRUE(drawing_buffer_->PrepareTransferableResource(nullptr, &resource, + &release_callback)); + + gpu::SyncToken wait_sync_token; + gl_->GenSyncTokenCHROMIUM(wait_sync_token.GetData()); + drawing_buffer_->SetIsHidden(true); + release_callback->Run(wait_sync_token, false /* lostResource */); + // m_drawingBuffer deletes resource immediately when hidden. + + EXPECT_EQ(wait_sync_token, gl_->MostRecentlyWaitedSyncToken()); + + drawing_buffer_->BeginDestruction(); +} + +TEST_F(DrawingBufferTest, + VerifyTooBigDrawingBufferExceedingV8MaxSizeFailsToCreate) { + IntSize too_big_size(1, (v8::TypedArray::kMaxLength / 4) + 1); + bool gpu_compositing = true; + scoped_refptr<DrawingBuffer> too_big_drawing_buffer = DrawingBuffer::Create( + nullptr, gpu_compositing, nullptr, too_big_size, false, false, false, + false, false, DrawingBuffer::kDiscard, DrawingBuffer::kWebGL1, + DrawingBuffer::kAllowChromiumImage, CanvasColorParams()); + EXPECT_EQ(too_big_drawing_buffer, nullptr); + drawing_buffer_->BeginDestruction(); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/platform/graphics/gpu/drawing_buffer_test_helpers.h b/chromium/third_party/blink/renderer/platform/graphics/gpu/drawing_buffer_test_helpers.h new file mode 100644 index 00000000000..308e30ab5a2 --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/graphics/gpu/drawing_buffer_test_helpers.h @@ -0,0 +1,452 @@ +// Copyright 2016 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. + +#ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_GRAPHICS_GPU_DRAWING_BUFFER_TEST_HELPERS_H_ +#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_GRAPHICS_GPU_DRAWING_BUFFER_TEST_HELPERS_H_ + +#include "build/build_config.h" +#include "gpu/command_buffer/common/capabilities.h" +#include "gpu/config/gpu_feature_info.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/blink/public/platform/web_graphics_context_3d_provider.h" +#include "third_party/blink/renderer/platform/graphics/canvas_color_params.h" +#include "third_party/blink/renderer/platform/graphics/gpu/drawing_buffer.h" +#include "third_party/blink/renderer/platform/graphics/gpu/extensions_3d_util.h" +#include "third_party/blink/renderer/platform/runtime_enabled_features.h" + +namespace blink { + +enum { + kInitialWidth = 100, + kInitialHeight = 100, + kAlternateHeight = 50, +}; + +enum UseMultisampling { + kDisableMultisampling, + kEnableMultisampling, +}; + +class WebGraphicsContext3DProviderForTests + : public WebGraphicsContext3DProvider { + public: + WebGraphicsContext3DProviderForTests( + std::unique_ptr<gpu::gles2::GLES2Interface> gl) + : gl_(std::move(gl)) {} + + gpu::gles2::GLES2Interface* ContextGL() override { return gl_.get(); } + bool IsSoftwareRendering() const override { return false; } + + // Not used by WebGL code. + GrContext* GetGrContext() override { return nullptr; } + bool BindToCurrentThread() override { return false; } + const gpu::Capabilities& GetCapabilities() const override { + return capabilities_; + } + const gpu::GpuFeatureInfo& GetGpuFeatureInfo() const override { + return gpu_feature_info_; + } + viz::GLHelper* GetGLHelper() override { return nullptr; } + void SetLostContextCallback(base::Closure) override {} + void SetErrorMessageCallback( + base::RepeatingCallback<void(const char*, int32_t id)>) {} + void SignalQuery(uint32_t, base::OnceClosure) override {} + cc::ImageDecodeCache* ImageDecodeCache() override { return nullptr; } + + private: + std::unique_ptr<gpu::gles2::GLES2Interface> gl_; + gpu::Capabilities capabilities_; + gpu::GpuFeatureInfo gpu_feature_info_; +}; + +// The target to use when binding a texture to a Chromium image. +GLenum ImageCHROMIUMTextureTarget() { +#if defined(OS_MACOSX) + return GC3D_TEXTURE_RECTANGLE_ARB; +#else + return GL_TEXTURE_2D; +#endif +} + +// The target to use when preparing a mailbox texture. +GLenum DrawingBufferTextureTarget() { + if (RuntimeEnabledFeatures::WebGLImageChromiumEnabled()) + return ImageCHROMIUMTextureTarget(); + return GL_TEXTURE_2D; +} + +class GLES2InterfaceForTests : public gpu::gles2::GLES2InterfaceStub, + public DrawingBuffer::Client { + public: + // GLES2InterfaceStub implementation: + void BindTexture(GLenum target, GLuint texture) override { + if (target == GL_TEXTURE_2D) + state_.active_texture2d_binding = texture; + bound_textures_[target] = texture; + } + + void BindFramebuffer(GLenum target, GLuint framebuffer) override { + switch (target) { + case GL_FRAMEBUFFER: + state_.draw_framebuffer_binding = framebuffer; + state_.read_framebuffer_binding = framebuffer; + break; + case GL_DRAW_FRAMEBUFFER: + state_.draw_framebuffer_binding = framebuffer; + break; + case GL_READ_FRAMEBUFFER: + state_.read_framebuffer_binding = framebuffer; + break; + default: + break; + } + } + + void BindRenderbuffer(GLenum target, GLuint renderbuffer) override { + state_.renderbuffer_binding = renderbuffer; + } + + void Enable(GLenum cap) { + if (cap == GL_SCISSOR_TEST) + state_.scissor_enabled = true; + } + + void Disable(GLenum cap) { + if (cap == GL_SCISSOR_TEST) + state_.scissor_enabled = false; + } + + void ClearColor(GLfloat red, + GLfloat green, + GLfloat blue, + GLfloat alpha) override { + state_.clear_color[0] = red; + state_.clear_color[1] = green; + state_.clear_color[2] = blue; + state_.clear_color[3] = alpha; + } + + void ClearDepthf(GLfloat depth) override { state_.clear_depth = depth; } + + void ClearStencil(GLint s) override { state_.clear_stencil = s; } + + void ColorMask(GLboolean red, + GLboolean green, + GLboolean blue, + GLboolean alpha) override { + state_.color_mask[0] = red; + state_.color_mask[1] = green; + state_.color_mask[2] = blue; + state_.color_mask[3] = alpha; + } + + void DepthMask(GLboolean flag) override { state_.depth_mask = flag; } + + void StencilMask(GLuint mask) override { state_.stencil_mask = mask; } + + void StencilMaskSeparate(GLenum face, GLuint mask) override { + if (face == GL_FRONT) + state_.stencil_mask = mask; + } + + void PixelStorei(GLenum pname, GLint param) override { + if (pname == GL_PACK_ALIGNMENT) + state_.pack_alignment = param; + } + + void BindBuffer(GLenum target, GLuint buffer) override { + switch (target) { + case GL_PIXEL_UNPACK_BUFFER: + state_.pixel_unpack_buffer_binding = buffer; + break; + case GL_PIXEL_PACK_BUFFER: + state_.pixel_pack_buffer_binding = buffer; + break; + default: + break; + } + } + + void WaitSyncTokenCHROMIUM(const GLbyte* sync_token) override { + memcpy(&most_recently_waited_sync_token_, sync_token, + sizeof(most_recently_waited_sync_token_)); + } + + GLenum CheckFramebufferStatus(GLenum target) override { + return GL_FRAMEBUFFER_COMPLETE; + } + + void GetIntegerv(GLenum pname, GLint* value) override { + switch (pname) { + case GL_DRAW_FRAMEBUFFER_BINDING: + *value = state_.draw_framebuffer_binding; + break; + case GL_READ_FRAMEBUFFER_BINDING: + *value = state_.read_framebuffer_binding; + break; + case GL_MAX_TEXTURE_SIZE: + *value = 1024; + break; + default: + break; + } + } + + void GenMailboxCHROMIUM(GLbyte* mailbox) override { + ++current_mailbox_byte_; + memset(mailbox, current_mailbox_byte_, GL_MAILBOX_SIZE_CHROMIUM); + } + + void ProduceTextureDirectCHROMIUM(GLuint texture, + const GLbyte* mailbox) override { + if (!create_image_chromium_fail_) { + ASSERT_TRUE(texture_sizes_.Contains(texture)); + most_recently_produced_size_ = texture_sizes_.at(texture); + } + } + + void TexImage2D(GLenum target, + GLint level, + GLint internalformat, + GLsizei width, + GLsizei height, + GLint border, + GLenum format, + GLenum type, + const void* pixels) override { + if (target == GL_TEXTURE_2D && !level) { + texture_sizes_.Set(bound_textures_[target], IntSize(width, height)); + } + } + + GLuint CreateImageCHROMIUM(ClientBuffer buffer, + GLsizei width, + GLsizei height, + GLenum internalformat) override { + if (create_image_chromium_fail_) + return 0; + image_sizes_.Set(current_image_id_, IntSize(width, height)); + return current_image_id_++; + } + + MOCK_METHOD1(DestroyImageMock, void(GLuint imageId)); + void DestroyImageCHROMIUM(GLuint image_id) { + image_sizes_.erase(image_id); + // No textures should be bound to this. + CHECK(image_to_texture_map_.find(image_id) == image_to_texture_map_.end()); + image_sizes_.erase(image_id); + DestroyImageMock(image_id); + } + + MOCK_METHOD1(BindTexImage2DMock, void(GLint imageId)); + void BindTexImage2DCHROMIUM(GLenum target, GLint image_id) { + if (target == ImageCHROMIUMTextureTarget()) { + texture_sizes_.Set(bound_textures_[target], + image_sizes_.find(image_id)->value); + image_to_texture_map_.Set(image_id, bound_textures_[target]); + BindTexImage2DMock(image_id); + } + } + + MOCK_METHOD1(ReleaseTexImage2DMock, void(GLint imageId)); + void ReleaseTexImage2DCHROMIUM(GLenum target, GLint image_id) { + if (target == ImageCHROMIUMTextureTarget()) { + image_sizes_.Set(current_image_id_, IntSize()); + image_to_texture_map_.erase(image_id); + ReleaseTexImage2DMock(image_id); + } + } + + void GenSyncTokenCHROMIUM(GLbyte* sync_token) override { + static uint64_t unique_id = 1; + gpu::SyncToken source( + gpu::GPU_IO, gpu::CommandBufferId::FromUnsafeValue(unique_id++), 2); + memcpy(sync_token, &source, sizeof(source)); + } + + void GenTextures(GLsizei n, GLuint* textures) override { + static GLuint id = 1; + for (GLsizei i = 0; i < n; ++i) + textures[i] = id++; + } + + // DrawingBuffer::Client implementation. + bool DrawingBufferClientIsBoundForDraw() override { + return !state_.draw_framebuffer_binding; + } + void DrawingBufferClientRestoreScissorTest() override { + state_.scissor_enabled = saved_state_.scissor_enabled; + } + void DrawingBufferClientRestoreMaskAndClearValues() override { + memcpy(state_.color_mask, saved_state_.color_mask, + sizeof(state_.color_mask)); + state_.clear_depth = saved_state_.clear_depth; + state_.clear_stencil = saved_state_.clear_stencil; + + memcpy(state_.clear_color, saved_state_.clear_color, + sizeof(state_.clear_color)); + state_.depth_mask = saved_state_.depth_mask; + state_.stencil_mask = saved_state_.stencil_mask; + } + void DrawingBufferClientRestorePixelPackParameters() override { + // TODO(zmo): restore ES3 pack parameters? + state_.pack_alignment = saved_state_.pack_alignment; + } + void DrawingBufferClientRestoreTexture2DBinding() override { + state_.active_texture2d_binding = saved_state_.active_texture2d_binding; + } + void DrawingBufferClientRestoreRenderbufferBinding() override { + state_.renderbuffer_binding = saved_state_.renderbuffer_binding; + } + void DrawingBufferClientRestoreFramebufferBinding() override { + state_.draw_framebuffer_binding = saved_state_.draw_framebuffer_binding; + state_.read_framebuffer_binding = saved_state_.read_framebuffer_binding; + } + void DrawingBufferClientRestorePixelUnpackBufferBinding() override { + state_.pixel_unpack_buffer_binding = + saved_state_.pixel_unpack_buffer_binding; + } + void DrawingBufferClientRestorePixelPackBufferBinding() override { + state_.pixel_pack_buffer_binding = saved_state_.pixel_pack_buffer_binding; + } + + // Testing methods. + gpu::SyncToken MostRecentlyWaitedSyncToken() const { + return most_recently_waited_sync_token_; + } + GLuint NextImageIdToBeCreated() const { return current_image_id_; } + IntSize MostRecentlyProducedSize() const { + return most_recently_produced_size_; + } + + void SetCreateImageChromiumFail(bool fail) { + create_image_chromium_fail_ = fail; + } + + // Saves current GL state for later verification. + void SaveState() { saved_state_ = state_; } + void VerifyStateHasNotChangedSinceSave() const { + for (size_t i = 0; i < 4; ++i) { + EXPECT_EQ(state_.clear_color[0], saved_state_.clear_color[0]); + EXPECT_EQ(state_.color_mask[0], saved_state_.color_mask[0]); + } + EXPECT_EQ(state_.clear_depth, saved_state_.clear_depth); + EXPECT_EQ(state_.clear_stencil, saved_state_.clear_stencil); + EXPECT_EQ(state_.depth_mask, saved_state_.depth_mask); + EXPECT_EQ(state_.stencil_mask, saved_state_.stencil_mask); + EXPECT_EQ(state_.pack_alignment, saved_state_.pack_alignment); + EXPECT_EQ(state_.active_texture2d_binding, + saved_state_.active_texture2d_binding); + EXPECT_EQ(state_.renderbuffer_binding, saved_state_.renderbuffer_binding); + EXPECT_EQ(state_.draw_framebuffer_binding, + saved_state_.draw_framebuffer_binding); + EXPECT_EQ(state_.read_framebuffer_binding, + saved_state_.read_framebuffer_binding); + EXPECT_EQ(state_.pixel_unpack_buffer_binding, + saved_state_.pixel_unpack_buffer_binding); + EXPECT_EQ(state_.pixel_pack_buffer_binding, + saved_state_.pixel_pack_buffer_binding); + } + + private: + std::map<GLenum, GLuint> bound_textures_; + + // State tracked to verify that it is restored correctly. + struct State { + bool scissor_enabled = false; + + GLfloat clear_color[4] = {0, 0, 0, 0}; + GLfloat clear_depth = 0; + GLint clear_stencil = 0; + + GLboolean color_mask[4] = {0, 0, 0, 0}; + GLboolean depth_mask = 0; + GLuint stencil_mask = 0; + + GLint pack_alignment = 4; + + // The bound 2D texture for the active texture unit. + GLuint active_texture2d_binding = 0; + GLuint renderbuffer_binding = 0; + GLuint draw_framebuffer_binding = 0; + GLuint read_framebuffer_binding = 0; + GLuint pixel_unpack_buffer_binding = 0; + GLuint pixel_pack_buffer_binding = 0; + }; + State state_; + State saved_state_; + + gpu::SyncToken most_recently_waited_sync_token_; + GLbyte current_mailbox_byte_ = 0; + IntSize most_recently_produced_size_; + bool create_image_chromium_fail_ = false; + GLuint current_image_id_ = 1; + HashMap<GLuint, IntSize> texture_sizes_; + HashMap<GLuint, IntSize> image_sizes_; + HashMap<GLuint, GLuint> image_to_texture_map_; +}; + +class DrawingBufferForTests : public DrawingBuffer { + public: + static scoped_refptr<DrawingBufferForTests> Create( + std::unique_ptr<WebGraphicsContext3DProvider> context_provider, + bool using_gpu_compositing, + DrawingBuffer::Client* client, + const IntSize& size, + PreserveDrawingBuffer preserve, + UseMultisampling use_multisampling) { + std::unique_ptr<Extensions3DUtil> extensions_util = + Extensions3DUtil::Create(context_provider->ContextGL()); + scoped_refptr<DrawingBufferForTests> drawing_buffer = + base::AdoptRef(new DrawingBufferForTests( + std::move(context_provider), using_gpu_compositing, + std::move(extensions_util), client, preserve)); + if (!drawing_buffer->Initialize( + size, use_multisampling != kDisableMultisampling)) { + drawing_buffer->BeginDestruction(); + return nullptr; + } + return drawing_buffer; + } + + DrawingBufferForTests( + std::unique_ptr<WebGraphicsContext3DProvider> context_provider, + bool using_gpu_compositing, + std::unique_ptr<Extensions3DUtil> extensions_util, + DrawingBuffer::Client* client, + PreserveDrawingBuffer preserve) + : DrawingBuffer( + std::move(context_provider), + using_gpu_compositing, + std::move(extensions_util), + client, + false /* discardFramebufferSupported */, + true /* wantAlphaChannel */, + true /* premultipliedAlpha */, + preserve, + kWebGL1, + false /* wantDepth */, + false /* wantStencil */, + DrawingBuffer::kAllowChromiumImage /* ChromiumImageUsage */, + CanvasColorParams()), + live_(nullptr) {} + + ~DrawingBufferForTests() override { + if (live_) + *live_ = false; + } + + GLES2InterfaceForTests* ContextGLForTests() { + return static_cast<GLES2InterfaceForTests*>(ContextGL()); + } + + bool* live_; + + int RecycledBitmapCount() { return recycled_bitmaps_.size(); } +}; + +} // blink + +#endif // THIRD_PARTY_BLINK_RENDERER_PLATFORM_GRAPHICS_GPU_DRAWING_BUFFER_TEST_HELPERS_H_ diff --git a/chromium/third_party/blink/renderer/platform/graphics/gpu/extensions_3d_util.cc b/chromium/third_party/blink/renderer/platform/graphics/gpu/extensions_3d_util.cc new file mode 100644 index 00000000000..760ffa1f4a7 --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/graphics/gpu/extensions_3d_util.cc @@ -0,0 +1,93 @@ +// 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 "third_party/blink/renderer/platform/graphics/gpu/extensions_3d_util.h" + +#include <memory> + +#include "base/memory/ptr_util.h" +#include "gpu/command_buffer/client/gles2_interface.h" +#include "third_party/blink/renderer/platform/wtf/text/cstring.h" +#include "third_party/blink/renderer/platform/wtf/text/string_hash.h" + +namespace blink { + +namespace { + +void SplitStringHelper(const String& str, HashSet<String>& set) { + Vector<String> substrings; + str.Split(' ', substrings); + for (size_t i = 0; i < substrings.size(); ++i) + set.insert(substrings[i]); +} + +} // anonymous namespace + +std::unique_ptr<Extensions3DUtil> Extensions3DUtil::Create( + gpu::gles2::GLES2Interface* gl) { + std::unique_ptr<Extensions3DUtil> out = + base::WrapUnique(new Extensions3DUtil(gl)); + out->InitializeExtensions(); + return out; +} + +Extensions3DUtil::Extensions3DUtil(gpu::gles2::GLES2Interface* gl) + : gl_(gl), is_valid_(true) {} + +Extensions3DUtil::~Extensions3DUtil() = default; + +void Extensions3DUtil::InitializeExtensions() { + if (gl_->GetGraphicsResetStatusKHR() != GL_NO_ERROR) { + // If the context is lost don't initialize the extension strings. + // This will cause supportsExtension, ensureExtensionEnabled, and + // isExtensionEnabled to always return false. + is_valid_ = false; + return; + } + + String extensions_string(gl_->GetString(GL_EXTENSIONS)); + SplitStringHelper(extensions_string, enabled_extensions_); + + String requestable_extensions_string(gl_->GetRequestableExtensionsCHROMIUM()); + SplitStringHelper(requestable_extensions_string, requestable_extensions_); +} + +bool Extensions3DUtil::SupportsExtension(const String& name) { + return enabled_extensions_.Contains(name) || + requestable_extensions_.Contains(name); +} + +bool Extensions3DUtil::EnsureExtensionEnabled(const String& name) { + if (enabled_extensions_.Contains(name)) + return true; + + if (requestable_extensions_.Contains(name)) { + gl_->RequestExtensionCHROMIUM(name.Ascii().data()); + enabled_extensions_.clear(); + requestable_extensions_.clear(); + InitializeExtensions(); + } + return enabled_extensions_.Contains(name); +} + +bool Extensions3DUtil::IsExtensionEnabled(const String& name) { + return enabled_extensions_.Contains(name); +} + +bool Extensions3DUtil::CanUseCopyTextureCHROMIUM(GLenum dest_target) { + switch (dest_target) { + case GL_TEXTURE_2D: + 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: + return true; + default: + return false; + } +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/platform/graphics/gpu/extensions_3d_util.h b/chromium/third_party/blink/renderer/platform/graphics/gpu/extensions_3d_util.h new file mode 100644 index 00000000000..f220a21eb4e --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/graphics/gpu/extensions_3d_util.h @@ -0,0 +1,55 @@ +// 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. + +#ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_GRAPHICS_GPU_EXTENSIONS_3D_UTIL_H_ +#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_GRAPHICS_GPU_EXTENSIONS_3D_UTIL_H_ + +#include <memory> +#include "third_party/blink/renderer/platform/platform_export.h" +#include "third_party/blink/renderer/platform/wtf/allocator.h" +#include "third_party/blink/renderer/platform/wtf/hash_set.h" +#include "third_party/blink/renderer/platform/wtf/noncopyable.h" +#include "third_party/blink/renderer/platform/wtf/text/string_hash.h" +#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h" +#include "third_party/khronos/GLES2/gl2.h" + +namespace gpu { +namespace gles2 { +class GLES2Interface; +} +} + +namespace blink { + +class PLATFORM_EXPORT Extensions3DUtil final { + USING_FAST_MALLOC(Extensions3DUtil); + WTF_MAKE_NONCOPYABLE(Extensions3DUtil); + + public: + // Creates a new Extensions3DUtil. If the passed GLES2Interface has been + // spontaneously lost, returns null. + static std::unique_ptr<Extensions3DUtil> Create(gpu::gles2::GLES2Interface*); + ~Extensions3DUtil(); + + bool IsValid() { return is_valid_; } + + bool SupportsExtension(const String& name); + bool EnsureExtensionEnabled(const String& name); + bool IsExtensionEnabled(const String& name); + + static bool CanUseCopyTextureCHROMIUM(GLenum dest_target); + + private: + Extensions3DUtil(gpu::gles2::GLES2Interface*); + void InitializeExtensions(); + + gpu::gles2::GLES2Interface* gl_; + HashSet<String> enabled_extensions_; + HashSet<String> requestable_extensions_; + bool is_valid_; +}; + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_PLATFORM_GRAPHICS_GPU_EXTENSIONS_3D_UTIL_H_ diff --git a/chromium/third_party/blink/renderer/platform/graphics/gpu/graphics_context_3d_utils.cc b/chromium/third_party/blink/renderer/platform/graphics/gpu/graphics_context_3d_utils.cc new file mode 100644 index 00000000000..0adc80a4a48 --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/graphics/gpu/graphics_context_3d_utils.cc @@ -0,0 +1,83 @@ +// Copyright 2017 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 "third_party/blink/renderer/platform/graphics/gpu/graphics_context_3d_utils.h" + +#include "gpu/command_buffer/client/gles2_interface.h" +#include "skia/ext/texture_handle.h" +#include "third_party/blink/renderer/platform/graphics/web_graphics_context_3d_provider_wrapper.h" +#include "third_party/skia/include/core/SkImage.h" +#include "third_party/skia/include/gpu/GrContext.h" + +namespace { + +struct GrTextureMailboxReleaseProcData { + GrTexture* gr_texture_; + base::WeakPtr<blink::WebGraphicsContext3DProviderWrapper> + context_provider_wrapper_; +}; + +void GrTextureMailboxReleaseProc(void* data) { + GrTextureMailboxReleaseProcData* release_proc_data = + static_cast<GrTextureMailboxReleaseProcData*>(data); + + if (release_proc_data->context_provider_wrapper_) { + release_proc_data->context_provider_wrapper_->Utils()->RemoveCachedMailbox( + release_proc_data->gr_texture_); + } + + delete release_proc_data; +} + +} // unnamed namespace + +namespace blink { + +void GraphicsContext3DUtils::GetMailboxForSkImage(gpu::Mailbox& out_mailbox, + const sk_sp<SkImage>& image, + GLenum filter) { + // This object is owned by context_provider_wrapper_, so that weak ref + // should never be null. + DCHECK(context_provider_wrapper_); + DCHECK(image->isTextureBacked()); + GrContext* gr = context_provider_wrapper_->ContextProvider()->GetGrContext(); + gpu::gles2::GLES2Interface* gl = + context_provider_wrapper_->ContextProvider()->ContextGL(); + + DCHECK(gr); + DCHECK(gl); + GrTexture* gr_texture = image->getTexture(); + DCHECK(gr == gr_texture->getContext()); + auto it = cached_mailboxes_.find(gr_texture); + if (it != cached_mailboxes_.end()) { + out_mailbox = it->value; + } else { + gl->GenMailboxCHROMIUM(out_mailbox.name); + + GrTextureMailboxReleaseProcData* release_proc_data = + new GrTextureMailboxReleaseProcData(); + release_proc_data->gr_texture_ = gr_texture; + release_proc_data->context_provider_wrapper_ = context_provider_wrapper_; + gr_texture->setRelease(GrTextureMailboxReleaseProc, release_proc_data); + cached_mailboxes_.insert(gr_texture, out_mailbox); + } + + GLuint texture_id = + skia::GrBackendObjectToGrGLTextureInfo(image->getTextureHandle(true)) + ->fID; + gl->BindTexture(GL_TEXTURE_2D, texture_id); + gl->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filter); + gl->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filter); + gl->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + gl->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + gl->BindTexture(GL_TEXTURE_2D, 0); + gl->ProduceTextureDirectCHROMIUM(texture_id, out_mailbox.name); + image->getTexture()->textureParamsModified(); +} + +void GraphicsContext3DUtils::RemoveCachedMailbox(GrTexture* gr_texture) { + cached_mailboxes_.erase(gr_texture); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/platform/graphics/gpu/graphics_context_3d_utils.h b/chromium/third_party/blink/renderer/platform/graphics/gpu/graphics_context_3d_utils.h new file mode 100644 index 00000000000..d88bef383ab --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/graphics/gpu/graphics_context_3d_utils.h @@ -0,0 +1,46 @@ +// Copyright 2017 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. + +#ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_GRAPHICS_GPU_GRAPHICS_CONTEXT_3D_UTILS_H_ +#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_GRAPHICS_GPU_GRAPHICS_CONTEXT_3D_UTILS_H_ + +#include "base/memory/weak_ptr.h" +#include "gpu/command_buffer/common/mailbox.h" +#include "gpu/command_buffer/common/sync_token.h" +#include "third_party/blink/renderer/platform/platform_export.h" +#include "third_party/blink/renderer/platform/wtf/hash_map.h" +#include "third_party/skia/include/core/SkImage.h" +#include "third_party/skia/include/gpu/GrTexture.h" + +typedef unsigned int GLenum; + +namespace blink { + +class WebGraphicsContext3DProviderWrapper; + +class PLATFORM_EXPORT GraphicsContext3DUtils { + public: + // The constructor takes a weak ref to the wrapper because it internally + // it generates callbacks that may outlive the wrapper. + GraphicsContext3DUtils(base::WeakPtr<WebGraphicsContext3DProviderWrapper>&& + context_provider_wrapper) + : context_provider_wrapper_(std::move(context_provider_wrapper)) {} + + // Use this service to create a new mailbox or possibly obtain a pre-existing + // mailbox for a given texture. The caching of pre-existing mailboxes survives + // when the texture gets recycled by skia for creating a new SkSurface or + // SkImage with a pre-existing GrTexture backing. + void GetMailboxForSkImage(gpu::Mailbox&, + const sk_sp<SkImage>&, + GLenum filter); + void RemoveCachedMailbox(GrTexture*); + + private: + base::WeakPtr<WebGraphicsContext3DProviderWrapper> context_provider_wrapper_; + WTF::HashMap<GrTexture*, gpu::Mailbox> cached_mailboxes_; +}; + +} // namespace blink + +#endif diff --git a/chromium/third_party/blink/renderer/platform/graphics/gpu/image_layer_bridge.cc b/chromium/third_party/blink/renderer/platform/graphics/gpu/image_layer_bridge.cc new file mode 100644 index 00000000000..10f2d7faeda --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/graphics/gpu/image_layer_bridge.cc @@ -0,0 +1,217 @@ +// Copyright 2017 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 "third_party/blink/renderer/platform/graphics/gpu/image_layer_bridge.h" + +#include "components/viz/common/quads/shared_bitmap.h" +#include "components/viz/common/resources/transferable_resource.h" +#include "gpu/command_buffer/client/gles2_interface.h" +#include "third_party/blink/public/platform/platform.h" +#include "third_party/blink/public/platform/web_compositor_support.h" +#include "third_party/blink/public/platform/web_external_texture_layer.h" +#include "third_party/blink/public/platform/web_graphics_context_3d_provider.h" +#include "third_party/blink/renderer/platform/graphics/accelerated_static_bitmap_image.h" +#include "third_party/blink/renderer/platform/graphics/color_behavior.h" +#include "third_party/blink/renderer/platform/graphics/gpu/shared_gpu_context.h" +#include "third_party/blink/renderer/platform/graphics/graphics_layer.h" + +namespace blink { + +ImageLayerBridge::ImageLayerBridge(OpacityMode opacity_mode) + : opacity_mode_(opacity_mode) { + layer_ = Platform::Current()->CompositorSupport()->CreateExternalTextureLayer( + this); + GraphicsLayer::RegisterContentsLayer(layer_->Layer()); + layer_->SetNearestNeighbor(filter_quality_ == kNone_SkFilterQuality); + if (opacity_mode_ == kOpaque) { + layer_->SetOpaque(true); + layer_->SetBlendBackgroundColor(false); + } +} + +ImageLayerBridge::~ImageLayerBridge() { + if (!disposed_) + Dispose(); +} + +void ImageLayerBridge::SetImage(scoped_refptr<StaticBitmapImage> image) { + if (disposed_) + return; + + image_ = std::move(image); + if (image_) { + if (opacity_mode_ == kNonOpaque) { + layer_->SetOpaque(image_->CurrentFrameKnownToBeOpaque()); + layer_->SetBlendBackgroundColor(!image_->CurrentFrameKnownToBeOpaque()); + } + } + if (!has_presented_since_last_set_image_ && image_ && + image_->IsTextureBacked()) { + // If the layer bridge is not presenting, the GrContext may not be getting + // flushed regularly. The flush is normally triggered inside the + // m_image->EnsureMailbox() call of + // ImageLayerBridge::PrepareTransferableResource. To prevent a potential + // memory leak we must flush the GrContext here. + image_->PaintImageForCurrentFrame().GetSkImage()->getTextureHandle( + true); // GrContext flush. + } + has_presented_since_last_set_image_ = false; +} + +void ImageLayerBridge::SetUV(const FloatPoint left_top, + const FloatPoint right_bottom) { + if (disposed_) + return; + + layer_->SetUV(WebFloatPoint(left_top.X(), left_top.Y()), + WebFloatPoint(right_bottom.X(), right_bottom.Y())); +} + +void ImageLayerBridge::Dispose() { + if (layer_) { + GraphicsLayer::UnregisterContentsLayer(layer_->Layer()); + layer_->ClearTexture(); + layer_.reset(); + } + image_ = nullptr; + disposed_ = true; +} + +bool ImageLayerBridge::PrepareTransferableResource( + cc::SharedBitmapIdRegistrar* bitmap_registrar, + viz::TransferableResource* out_resource, + std::unique_ptr<viz::SingleReleaseCallback>* out_release_callback) { + if (disposed_) + return false; + + if (!image_) + return false; + + if (has_presented_since_last_set_image_) + return false; + + has_presented_since_last_set_image_ = true; + + bool gpu_compositing = SharedGpuContext::IsGpuCompositingEnabled(); + bool gpu_image = image_->IsTextureBacked(); + + // Expect software images for software compositing. + if (!gpu_compositing && gpu_image) + return false; + + // If the texture comes from a software image then it does not need to be + // flipped. + layer_->SetFlipped(gpu_image); + + scoped_refptr<StaticBitmapImage> image_for_compositor; + + // Upload to a texture if the compositor is expecting one. + if (gpu_compositing && !image_->IsTextureBacked()) { + image_for_compositor = + image_->MakeAccelerated(SharedGpuContext::ContextProviderWrapper()); + } else if (!gpu_compositing && image_->IsTextureBacked()) { + image_for_compositor = image_->MakeUnaccelerated(); + } else { + image_for_compositor = image_; + } + DCHECK_EQ(image_for_compositor->IsTextureBacked(), gpu_compositing); + + if (gpu_compositing) { + uint32_t filter = + filter_quality_ == kNone_SkFilterQuality ? GL_NEAREST : GL_LINEAR; + image_for_compositor->EnsureMailbox(kUnverifiedSyncToken, filter); + *out_resource = viz::TransferableResource::MakeGL( + image_for_compositor->GetMailbox(), filter, GL_TEXTURE_2D, + image_for_compositor->GetSyncToken()); + auto func = + WTF::Bind(&ImageLayerBridge::ResourceReleasedGpu, + WrapWeakPersistent(this), std::move(image_for_compositor)); + *out_release_callback = viz::SingleReleaseCallback::Create(std::move(func)); + } else { + std::unique_ptr<viz::SharedBitmap> bitmap = + CreateOrRecycleBitmap(image_for_compositor->Size()); + if (!bitmap) + return false; + + sk_sp<SkImage> sk_image = + image_for_compositor->PaintImageForCurrentFrame().GetSkImage(); + if (!sk_image) + return false; + + SkImageInfo dst_info = + SkImageInfo::MakeN32Premul(image_for_compositor->width(), 1); + dst_info = dst_info.makeColorSpace(sk_image->refColorSpace()); + size_t row_bytes = image_for_compositor->width() * 4; + + // Copy from SkImage into |bitmap|, while flipping the Y axis. + for (int row = 0; row < image_for_compositor->height(); row++) { + if (!sk_image->readPixels(dst_info, bitmap->pixels(), row_bytes, 0, 0)) + return false; + } + + *out_resource = viz::TransferableResource::MakeSoftware( + bitmap->id(), bitmap->sequence_number(), + gfx::Size(image_for_compositor->width(), + image_for_compositor->height()), + viz::RGBA_8888); + auto func = WTF::Bind(&ImageLayerBridge::ResourceReleasedSoftware, + WrapWeakPersistent(this), std::move(bitmap), + image_for_compositor->Size()); + *out_release_callback = viz::SingleReleaseCallback::Create(std::move(func)); + } + + // TODO(junov): Figure out how to get the color space info. + // out_resource->color_space = ...; + + return true; +} + +std::unique_ptr<viz::SharedBitmap> ImageLayerBridge::CreateOrRecycleBitmap( + const IntSize& size) { + auto it = std::remove_if( + recycled_bitmaps_.begin(), recycled_bitmaps_.end(), + [&size](const RecycledBitmap& bitmap) { return bitmap.size != size; }); + recycled_bitmaps_.Shrink(it - recycled_bitmaps_.begin()); + + if (!recycled_bitmaps_.IsEmpty()) { + RecycledBitmap recycled = std::move(recycled_bitmaps_.back()); + recycled_bitmaps_.pop_back(); + DCHECK(recycled.size == size); + return std::move(recycled.bitmap); + } + return Platform::Current()->AllocateSharedBitmap(size, viz::RGBA_8888); +} + +void ImageLayerBridge::ResourceReleasedGpu( + scoped_refptr<StaticBitmapImage> image, + const gpu::SyncToken& token, + bool lost_resource) { + if (image && image->IsValid()) { + DCHECK(image->IsTextureBacked()); + if (token.HasData() && image->ContextProvider() && + image->ContextProvider()->ContextGL()) { + image->ContextProvider()->ContextGL()->WaitSyncTokenCHROMIUM( + token.GetConstData()); + } + } + // let 'image' go out of scope to release gpu resources. +} + +void ImageLayerBridge::ResourceReleasedSoftware( + std::unique_ptr<viz::SharedBitmap> bitmap, + const IntSize& size, + const gpu::SyncToken& sync_token, + bool lost_resource) { + DCHECK(!sync_token.HasData()); // No sync tokens for software resources. + if (!disposed_ && !lost_resource) { + RecycledBitmap recycled = {std::move(bitmap), size}; + recycled_bitmaps_.push_back(std::move(recycled)); + } +} + +WebLayer* ImageLayerBridge::PlatformLayer() const { + return layer_->Layer(); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/platform/graphics/gpu/image_layer_bridge.h b/chromium/third_party/blink/renderer/platform/graphics/gpu/image_layer_bridge.h new file mode 100644 index 00000000000..9dfaae76c1c --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/graphics/gpu/image_layer_bridge.h @@ -0,0 +1,87 @@ +// Copyright 2017 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. + +#ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_GRAPHICS_GPU_IMAGE_LAYER_BRIDGE_H_ +#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_GRAPHICS_GPU_IMAGE_LAYER_BRIDGE_H_ + +#include "cc/layers/texture_layer_client.h" +#include "third_party/blink/renderer/platform/geometry/float_point.h" +#include "third_party/blink/renderer/platform/graphics/graphics_types.h" +#include "third_party/blink/renderer/platform/graphics/static_bitmap_image.h" +#include "third_party/blink/renderer/platform/heap/heap.h" +#include "third_party/blink/renderer/platform/platform_export.h" + +namespace viz { +class SharedBitmap; +} + +namespace blink { + +class WebLayer; +class WebExternalTextureLayer; + +class PLATFORM_EXPORT ImageLayerBridge + : public GarbageCollectedFinalized<ImageLayerBridge>, + public cc::TextureLayerClient { + WTF_MAKE_NONCOPYABLE(ImageLayerBridge); + + public: + ImageLayerBridge(OpacityMode); + ~ImageLayerBridge(); + + void SetImage(scoped_refptr<StaticBitmapImage>); + void Dispose(); + + // cc::TextureLayerClient implementation. + bool PrepareTransferableResource( + cc::SharedBitmapIdRegistrar* bitmap_registrar, + viz::TransferableResource* out_resource, + std::unique_ptr<viz::SingleReleaseCallback>* out_release_callback) + override; + + void ResourceReleasedGpu(scoped_refptr<StaticBitmapImage>, + const gpu::SyncToken&, + bool lost_resource); + + void ResourceReleasedSoftware(std::unique_ptr<viz::SharedBitmap>, + const IntSize&, + const gpu::SyncToken&, + bool lost_resource); + + scoped_refptr<StaticBitmapImage> GetImage() { return image_; } + + WebLayer* PlatformLayer() const; + + void SetFilterQuality(SkFilterQuality filter_quality) { + filter_quality_ = filter_quality; + } + void SetUV(const FloatPoint left_top, const FloatPoint right_bottom); + + bool IsAccelerated() { return image_->IsTextureBacked(); } + + void Trace(blink::Visitor* visitor) {} + + private: + std::unique_ptr<viz::SharedBitmap> CreateOrRecycleBitmap(const IntSize& size); + + scoped_refptr<StaticBitmapImage> image_; + std::unique_ptr<WebExternalTextureLayer> layer_; + SkFilterQuality filter_quality_ = kLow_SkFilterQuality; + + // Shared memory bitmaps that were released by the compositor and can be used + // again by this ImageLayerBridge. + struct RecycledBitmap { + std::unique_ptr<viz::SharedBitmap> bitmap; + IntSize size; + }; + Vector<RecycledBitmap> recycled_bitmaps_; + + bool disposed_ = false; + bool has_presented_since_last_set_image_ = false; + OpacityMode opacity_mode_ = kNonOpaque; +}; + +} // namespace blink + +#endif diff --git a/chromium/third_party/blink/renderer/platform/graphics/gpu/shared_context_rate_limiter.cc b/chromium/third_party/blink/renderer/platform/graphics/gpu/shared_context_rate_limiter.cc new file mode 100644 index 00000000000..c31f06e8e9e --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/graphics/gpu/shared_context_rate_limiter.cc @@ -0,0 +1,84 @@ +// Copyright 2015 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 "third_party/blink/renderer/platform/graphics/gpu/shared_context_rate_limiter.h" + +#include <memory> + +#include "base/memory/ptr_util.h" +#include "gpu/GLES2/gl2extchromium.h" +#include "third_party/blink/public/platform/platform.h" +#include "third_party/blink/public/platform/web_graphics_context_3d_provider.h" +#include "third_party/blink/renderer/platform/graphics/gpu/extensions_3d_util.h" +#include "third_party/khronos/GLES2/gl2.h" + +namespace blink { + +std::unique_ptr<SharedContextRateLimiter> SharedContextRateLimiter::Create( + unsigned max_pending_ticks) { + return base::WrapUnique(new SharedContextRateLimiter(max_pending_ticks)); +} + +SharedContextRateLimiter::SharedContextRateLimiter(unsigned max_pending_ticks) + : max_pending_ticks_(max_pending_ticks), can_use_sync_queries_(false) { + context_provider_ = + Platform::Current()->CreateSharedOffscreenGraphicsContext3DProvider(); + if (!context_provider_) + return; + + gpu::gles2::GLES2Interface* gl = context_provider_->ContextGL(); + if (gl && gl->GetGraphicsResetStatusKHR() == GL_NO_ERROR) { + std::unique_ptr<Extensions3DUtil> extensions_util = + Extensions3DUtil::Create(gl); + // TODO(junov): when the GLES 3.0 command buffer is ready, we could use + // fenceSync instead. + can_use_sync_queries_ = + extensions_util->SupportsExtension("GL_CHROMIUM_sync_query"); + } +} + +void SharedContextRateLimiter::Tick() { + if (!context_provider_) + return; + + gpu::gles2::GLES2Interface* gl = context_provider_->ContextGL(); + if (!gl || gl->GetGraphicsResetStatusKHR() != GL_NO_ERROR) + return; + + queries_.push_back(0); + if (can_use_sync_queries_) { + gl->GenQueriesEXT(1, &queries_.back()); + gl->BeginQueryEXT(GL_COMMANDS_COMPLETED_CHROMIUM, queries_.back()); + gl->EndQueryEXT(GL_COMMANDS_COMPLETED_CHROMIUM); + } + if (queries_.size() > max_pending_ticks_) { + if (can_use_sync_queries_) { + GLuint result; + gl->GetQueryObjectuivEXT(queries_.front(), GL_QUERY_RESULT_EXT, &result); + gl->DeleteQueriesEXT(1, &queries_.front()); + queries_.pop_front(); + } else { + gl->Finish(); + Reset(); + } + } +} + +void SharedContextRateLimiter::Reset() { + if (!context_provider_) + return; + + gpu::gles2::GLES2Interface* gl = context_provider_->ContextGL(); + if (can_use_sync_queries_ && gl && + gl->GetGraphicsResetStatusKHR() == GL_NO_ERROR) { + while (queries_.size() > 0) { + gl->DeleteQueriesEXT(1, &queries_.front()); + queries_.pop_front(); + } + } else { + queries_.clear(); + } +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/platform/graphics/gpu/shared_context_rate_limiter.h b/chromium/third_party/blink/renderer/platform/graphics/gpu/shared_context_rate_limiter.h new file mode 100644 index 00000000000..86b0a6c0209 --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/graphics/gpu/shared_context_rate_limiter.h @@ -0,0 +1,59 @@ +// Copyright 2015 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. + +#ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_GRAPHICS_GPU_SHARED_CONTEXT_RATE_LIMITER_H_ +#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_GRAPHICS_GPU_SHARED_CONTEXT_RATE_LIMITER_H_ + +#include <memory> +#include "gpu/command_buffer/client/gles2_interface.h" +#include "third_party/blink/renderer/platform/wtf/allocator.h" +#include "third_party/blink/renderer/platform/wtf/deque.h" +#include "third_party/blink/renderer/platform/wtf/noncopyable.h" + +namespace blink { + +class WebGraphicsContext3DProvider; + +// Purpose: to limit the amount of worked queued for execution +// (backlog) on the GPU by blocking the main thread to allow the GPU +// to catch up. The Prevents unsynchronized tight animation loops +// from cause a GPU denial of service. +// +// How it works: The rate limiter uses GPU fences to mark each tick +// and makes sure there are never more that 'maxPendingTicks' fences +// that are awaiting completion. On platforms that do not support +// fences, we use glFinish instead. glFinish will only be called in +// unsynchronized cases that submit more than maxPendingTicks animation +// tick per compositor frame, which should be quite rare. +// +// How to use it: Each unit of work that constitutes a complete animation +// frame must call tick(). reset() must be called when the animation +// is consumed by committing to the compositor. Several rate limiters can +// be used concurrently: they will each use their own sequences of +// fences which may be interleaved. When the graphics context is lost +// and later restored, the existing rate limiter must be destroyed and +// a new one created. + +class SharedContextRateLimiter final { + USING_FAST_MALLOC(SharedContextRateLimiter); + WTF_MAKE_NONCOPYABLE(SharedContextRateLimiter); + + public: + static std::unique_ptr<SharedContextRateLimiter> Create( + unsigned max_pending_ticks); + void Tick(); + void Reset(); + + private: + SharedContextRateLimiter(unsigned max_pending_ticks); + + std::unique_ptr<WebGraphicsContext3DProvider> context_provider_; + Deque<GLuint> queries_; + unsigned max_pending_ticks_; + bool can_use_sync_queries_; +}; + +} // namespace blink + +#endif diff --git a/chromium/third_party/blink/renderer/platform/graphics/gpu/shared_gpu_context.cc b/chromium/third_party/blink/renderer/platform/graphics/gpu/shared_gpu_context.cc new file mode 100644 index 00000000000..cfddf674cfc --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/graphics/gpu/shared_gpu_context.cc @@ -0,0 +1,178 @@ +// Copyright 2016 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 "third_party/blink/renderer/platform/graphics/gpu/shared_gpu_context.h" + +#include "base/single_thread_task_runner.h" +#include "gpu/command_buffer/client/gles2_interface.h" +#include "gpu/config/gpu_driver_bug_workaround_type.h" +#include "gpu/config/gpu_feature_info.h" +#include "third_party/blink/public/platform/platform.h" +#include "third_party/blink/public/platform/web_graphics_context_3d_provider.h" +#include "third_party/blink/renderer/platform/cross_thread_functional.h" +#include "third_party/blink/renderer/platform/waitable_event.h" +#include "third_party/blink/renderer/platform/web_task_runner.h" + +namespace blink { + +SharedGpuContext* SharedGpuContext::GetInstanceForCurrentThread() { + DEFINE_THREAD_SAFE_STATIC_LOCAL(ThreadSpecific<SharedGpuContext>, + thread_specific_instance, ()); + return thread_specific_instance; +} + +SharedGpuContext::SharedGpuContext() = default; + +// static +bool SharedGpuContext::IsGpuCompositingEnabled() { + SharedGpuContext* this_ptr = GetInstanceForCurrentThread(); + // The check for gpu compositing enabled implies a context will + // desired, so we combine them into a single trip to the main thread. + // This also ensures that the compositing mode does not change before + // the context is created, so if it does change the context will be lost + // and this class will know to check the compositing mode again. + bool only_if_gpu_compositing = true; + this_ptr->CreateContextProviderIfNeeded(only_if_gpu_compositing); + return !this_ptr->is_gpu_compositing_disabled_; +} + +base::WeakPtr<WebGraphicsContext3DProviderWrapper> +SharedGpuContext::ContextProviderWrapper() { + SharedGpuContext* this_ptr = GetInstanceForCurrentThread(); + bool only_if_gpu_compositing = false; + this_ptr->CreateContextProviderIfNeeded(only_if_gpu_compositing); + if (!this_ptr->context_provider_wrapper_) + return nullptr; + return this_ptr->context_provider_wrapper_->GetWeakPtr(); +} + +static void CreateContextProviderOnMainThread( + bool only_if_gpu_compositing, + bool* gpu_compositing_disabled, + std::unique_ptr<WebGraphicsContext3DProviderWrapper>* wrapper, + WaitableEvent* waitable_event) { + DCHECK(IsMainThread()); + + Platform::ContextAttributes context_attributes; + context_attributes.enable_raster_interface = true; + context_attributes.support_grcontext = true; + + *gpu_compositing_disabled = Platform::Current()->IsGpuCompositingDisabled(); + if (*gpu_compositing_disabled && only_if_gpu_compositing) { + waitable_event->Signal(); + return; + } + + Platform::GraphicsInfo graphics_info; + auto context_provider = + Platform::Current()->CreateOffscreenGraphicsContext3DProvider( + context_attributes, WebURL(), nullptr, &graphics_info); + if (context_provider) { + *wrapper = std::make_unique<WebGraphicsContext3DProviderWrapper>( + std::move(context_provider)); + } + waitable_event->Signal(); +} + +void SharedGpuContext::CreateContextProviderIfNeeded( + bool only_if_gpu_compositing) { + // Once true, |is_gpu_compositing_disabled_| will always stay true. + if (is_gpu_compositing_disabled_ && only_if_gpu_compositing) + return; + + // TODO(danakj): This needs to check that the context is being used on the + // thread it was made on, or else lock it. + if (context_provider_wrapper_ && + context_provider_wrapper_->ContextProvider() + ->ContextGL() + ->GetGraphicsResetStatusKHR() == GL_NO_ERROR) { + // If the context isn't lost then |is_gpu_compositing_disabled_| state + // hasn't changed yet. RenderThreadImpl::CompositingModeFallbackToSoftware() + // will lose the context to let us know if it changes. + return; + } + + is_gpu_compositing_disabled_ = false; + context_provider_wrapper_ = nullptr; + + if (context_provider_factory_) { + // This path should only be used in unit tests. + auto context_provider = + context_provider_factory_.Run(&is_gpu_compositing_disabled_); + if (context_provider) { + context_provider_wrapper_ = + std::make_unique<WebGraphicsContext3DProviderWrapper>( + std::move(context_provider)); + } + } else if (IsMainThread()) { + is_gpu_compositing_disabled_ = + Platform::Current()->IsGpuCompositingDisabled(); + if (is_gpu_compositing_disabled_ && only_if_gpu_compositing) + return; + auto context_provider = + Platform::Current()->CreateSharedOffscreenGraphicsContext3DProvider(); + if (context_provider) { + context_provider_wrapper_ = + std::make_unique<WebGraphicsContext3DProviderWrapper>( + std::move(context_provider)); + } + } else { + // This synchronous round-trip to the main thread is the reason why + // SharedGpuContext encasulates the context provider: so we only have to do + // this once per thread. + WaitableEvent waitable_event; + scoped_refptr<base::SingleThreadTaskRunner> task_runner = + Platform::Current()->MainThread()->GetTaskRunner(); + PostCrossThreadTask( + *task_runner, FROM_HERE, + CrossThreadBind(&CreateContextProviderOnMainThread, + only_if_gpu_compositing, + CrossThreadUnretained(&is_gpu_compositing_disabled_), + CrossThreadUnretained(&context_provider_wrapper_), + CrossThreadUnretained(&waitable_event))); + waitable_event.Wait(); + if (context_provider_wrapper_ && + !context_provider_wrapper_->ContextProvider()->BindToCurrentThread()) + context_provider_wrapper_ = nullptr; + } +} + +// static +void SharedGpuContext::SetContextProviderFactoryForTesting( + ContextProviderFactory factory) { + SharedGpuContext* this_ptr = GetInstanceForCurrentThread(); + DCHECK(!this_ptr->context_provider_wrapper_); + this_ptr->context_provider_factory_ = std::move(factory); +} + +// static +void SharedGpuContext::ResetForTesting() { + SharedGpuContext* this_ptr = GetInstanceForCurrentThread(); + this_ptr->is_gpu_compositing_disabled_ = false; + this_ptr->context_provider_wrapper_.reset(); + this_ptr->context_provider_factory_.Reset(); +} + +bool SharedGpuContext::IsValidWithoutRestoring() { + SharedGpuContext* this_ptr = GetInstanceForCurrentThread(); + if (!this_ptr->context_provider_wrapper_) + return false; + return this_ptr->context_provider_wrapper_->ContextProvider() + ->ContextGL() + ->GetGraphicsResetStatusKHR() == GL_NO_ERROR; +} + +bool SharedGpuContext::AllowSoftwareToAcceleratedCanvasUpgrade() { + SharedGpuContext* this_ptr = GetInstanceForCurrentThread(); + bool only_if_gpu_compositing = false; + this_ptr->CreateContextProviderIfNeeded(only_if_gpu_compositing); + if (!this_ptr->context_provider_wrapper_) + return false; + return !this_ptr->context_provider_wrapper_->ContextProvider() + ->GetGpuFeatureInfo() + .IsWorkaroundEnabled( + gpu::DISABLE_SOFTWARE_TO_ACCELERATED_CANVAS_UPGRADE); +} + +} // blink diff --git a/chromium/third_party/blink/renderer/platform/graphics/gpu/shared_gpu_context.h b/chromium/third_party/blink/renderer/platform/graphics/gpu/shared_gpu_context.h new file mode 100644 index 00000000000..57535d41863 --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/graphics/gpu/shared_gpu_context.h @@ -0,0 +1,66 @@ +// Copyright 2016 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. + +#ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_GRAPHICS_GPU_SHARED_GPU_CONTEXT_H_ +#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_GRAPHICS_GPU_SHARED_GPU_CONTEXT_H_ + +#include <memory> +#include "base/callback.h" +#include "base/memory/weak_ptr.h" +#include "third_party/blink/renderer/platform/graphics/web_graphics_context_3d_provider_wrapper.h" +#include "third_party/blink/renderer/platform/platform_export.h" +#include "third_party/blink/renderer/platform/wtf/thread_specific.h" + +namespace blink { + +class WebGraphicsContext3DProvider; + +// SharedGpuContext provides access to a thread-specific GPU context +// that is shared by many callsites throughout the thread. +// When on the main thread, provides access to the same context as +// Platform::CreateSharedOffscreenGraphicsContext3DProvider, and the +// same query as Platform::IsGPUCompositingEnabled(). +class PLATFORM_EXPORT SharedGpuContext { + public: + // Thread-safe query if gpu compositing is enabled. This should be done before + // calling ContextProviderWrapper() if the context will be used to make + // resources meant for the compositor. When it is false, no context will be + // needed and software-based resources should be given to the compositor + // instead. + static bool IsGpuCompositingEnabled(); + // May re-create context if context was lost + static base::WeakPtr<WebGraphicsContext3DProviderWrapper> + ContextProviderWrapper(); + static bool AllowSoftwareToAcceleratedCanvasUpgrade(); + static bool IsValidWithoutRestoring(); + + using ContextProviderFactory = + base::RepeatingCallback<std::unique_ptr<WebGraphicsContext3DProvider>( + bool* is_gpu_compositing_disabled)>; + static void SetContextProviderFactoryForTesting(ContextProviderFactory); + // Resets the global instance including the |context_provider_factory_| and + // dropping the context. Should be called at the end of a test that uses this + // to not interfere with the next test. + static void ResetForTesting(); + + private: + friend class WTF::ThreadSpecific<SharedGpuContext>; + + static SharedGpuContext* GetInstanceForCurrentThread(); + + SharedGpuContext(); + void CreateContextProviderIfNeeded(bool only_if_gpu_compositing); + + // Can be overridden for tests. + ContextProviderFactory context_provider_factory_; + + // This is sticky once true, we never need to ask again. + bool is_gpu_compositing_disabled_ = false; + std::unique_ptr<WebGraphicsContext3DProviderWrapper> + context_provider_wrapper_; +}; + +} // blink + +#endif // THIRD_PARTY_BLINK_RENDERER_PLATFORM_GRAPHICS_GPU_SHARED_GPU_CONTEXT_H_ diff --git a/chromium/third_party/blink/renderer/platform/graphics/gpu/shared_gpu_context_test.cc b/chromium/third_party/blink/renderer/platform/graphics/gpu/shared_gpu_context_test.cc new file mode 100644 index 00000000000..368d8cb03db --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/graphics/gpu/shared_gpu_context_test.cc @@ -0,0 +1,272 @@ +// Copyright 2016 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 "third_party/blink/renderer/platform/graphics/gpu/shared_gpu_context.h" + +#include <memory> + +#include "gpu/command_buffer/client/gles2_interface.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/blink/renderer/platform/graphics/canvas_2d_layer_bridge.h" +#include "third_party/blink/renderer/platform/graphics/canvas_resource_provider.h" +#include "third_party/blink/renderer/platform/graphics/static_bitmap_image.h" +#include "third_party/blink/renderer/platform/graphics/test/fake_gles2_interface.h" +#include "third_party/blink/renderer/platform/graphics/test/fake_web_graphics_context_3d_provider.h" +#include "third_party/khronos/GLES2/gl2ext.h" + +using testing::Test; + +namespace blink { + +namespace { + +template <class GLES2InterfaceType> +class SharedGpuContextTestBase : public Test { + public: + void SetUp() override { + auto factory = [](GLES2InterfaceType* gl, bool* gpu_compositing_disabled) + -> std::unique_ptr<WebGraphicsContext3DProvider> { + *gpu_compositing_disabled = false; + gl->SetIsContextLost(false); + return std::make_unique<FakeWebGraphicsContext3DProvider>(gl); + }; + SharedGpuContext::SetContextProviderFactoryForTesting( + WTF::BindRepeating(factory, WTF::Unretained(&gl_))); + } + + void TearDown() override { SharedGpuContext::ResetForTesting(); } + + GLES2InterfaceType gl_; +}; + +class SharedGpuContextTest + : public SharedGpuContextTestBase<FakeGLES2Interface> {}; + +class MailboxMockGLES2Interface : public FakeGLES2Interface { + public: + MOCK_METHOD1(GenMailboxCHROMIUM, void(GLbyte*)); + MOCK_METHOD1(GenSyncTokenCHROMIUM, void(GLbyte*)); + MOCK_METHOD1(GenUnverifiedSyncTokenCHROMIUM, void(GLbyte*)); +}; + +class MailboxSharedGpuContextTest + : public SharedGpuContextTestBase<MailboxMockGLES2Interface> {}; + +// Test fixure that simulate a graphics context creation failure, when using gpu +// compositing. +class BadSharedGpuContextTest : public Test { + public: + void SetUp() override { + auto factory = [](bool* gpu_compositing_disabled) + -> std::unique_ptr<WebGraphicsContext3DProvider> { + *gpu_compositing_disabled = false; + return nullptr; + }; + SharedGpuContext::SetContextProviderFactoryForTesting( + WTF::BindRepeating(factory)); + } + + void TearDown() override { SharedGpuContext::ResetForTesting(); } +}; + +// Test fixure that simulate not using gpu compositing. +class SoftwareCompositingTest : public Test { + public: + void SetUp() override { + auto factory = [](FakeGLES2Interface* gl, bool* gpu_compositing_disabled) + -> std::unique_ptr<WebGraphicsContext3DProvider> { + *gpu_compositing_disabled = true; + // Return a context anyway, to ensure that's not what the class checks + // to determine compositing mode. + gl->SetIsContextLost(false); + return std::make_unique<FakeWebGraphicsContext3DProvider>(gl); + }; + SharedGpuContext::SetContextProviderFactoryForTesting( + WTF::BindRepeating(factory, WTF::Unretained(&gl_))); + } + + void TearDown() override { SharedGpuContext::ResetForTesting(); } + + FakeGLES2Interface gl_; +}; + +TEST_F(SharedGpuContextTest, contextLossAutoRecovery) { + EXPECT_NE(SharedGpuContext::ContextProviderWrapper(), nullptr); + base::WeakPtr<WebGraphicsContext3DProviderWrapper> context = + SharedGpuContext::ContextProviderWrapper(); + gl_.SetIsContextLost(true); + EXPECT_FALSE(SharedGpuContext::IsValidWithoutRestoring()); + EXPECT_TRUE(!!context); + + // Context recreation results in old provider being discarded. + EXPECT_TRUE(!!SharedGpuContext::ContextProviderWrapper()); + EXPECT_FALSE(!!context); +} + +TEST_F(SharedGpuContextTest, AccelerateImageBufferSurfaceAutoRecovery) { + // Verifies that after a context loss, attempting to allocate an + // AcceleratedImageBufferSurface will restore the context and succeed + gl_.SetIsContextLost(true); + EXPECT_FALSE(SharedGpuContext::IsValidWithoutRestoring()); + IntSize size(10, 10); + std::unique_ptr<CanvasResourceProvider> resource_provider = + CanvasResourceProvider::Create( + size, CanvasResourceProvider::kAcceleratedResourceUsage, + SharedGpuContext::ContextProviderWrapper()); + EXPECT_TRUE(resource_provider && resource_provider->IsValid()); + EXPECT_TRUE(SharedGpuContext::IsValidWithoutRestoring()); +} + +TEST_F(SharedGpuContextTest, Canvas2DLayerBridgeAutoRecovery) { + // Verifies that after a context loss, attempting to allocate a + // Canvas2DLayerBridge will restore the context and succeed. + gl_.SetIsContextLost(true); + EXPECT_FALSE(SharedGpuContext::IsValidWithoutRestoring()); + IntSize size(10, 10); + CanvasColorParams color_params; + std::unique_ptr<Canvas2DLayerBridge> bridge = + std::make_unique<Canvas2DLayerBridge>( + size, 0, + /*msaa sample count*/ Canvas2DLayerBridge::kEnableAcceleration, + color_params); + EXPECT_TRUE(bridge->IsAccelerated()); + EXPECT_TRUE(SharedGpuContext::IsValidWithoutRestoring()); + bridge->BeginDestruction(); +} + +TEST_F(SharedGpuContextTest, IsValidWithoutRestoring) { + EXPECT_NE(SharedGpuContext::ContextProviderWrapper(), nullptr); + EXPECT_TRUE(SharedGpuContext::IsValidWithoutRestoring()); +} + +TEST_F(BadSharedGpuContextTest, IsValidWithoutRestoring) { + EXPECT_FALSE(SharedGpuContext::IsValidWithoutRestoring()); +} + +TEST_F(BadSharedGpuContextTest, AllowSoftwareToAcceleratedCanvasUpgrade) { + EXPECT_FALSE(SharedGpuContext::AllowSoftwareToAcceleratedCanvasUpgrade()); +} + +TEST_F(BadSharedGpuContextTest, AccelerateImageBufferSurfaceCreationFails) { + // With a bad shared context, AccelerateImageBufferSurface creation should + // fail gracefully + IntSize size(10, 10); + std::unique_ptr<CanvasResourceProvider> resource_provider = + CanvasResourceProvider::Create( + size, CanvasResourceProvider::kAcceleratedResourceUsage, + SharedGpuContext::ContextProviderWrapper()); + EXPECT_FALSE(!resource_provider); +} + +TEST_F(SharedGpuContextTest, CompositingMode) { + EXPECT_TRUE(SharedGpuContext::IsGpuCompositingEnabled()); +} + +TEST_F(BadSharedGpuContextTest, CompositingMode) { + EXPECT_TRUE(SharedGpuContext::IsGpuCompositingEnabled()); +} + +TEST_F(SoftwareCompositingTest, CompositingMode) { + EXPECT_FALSE(SharedGpuContext::IsGpuCompositingEnabled()); +} + +class FakeMailboxGenerator { + public: + void GenMailbox(GLbyte* name) { *name = counter_++; } + + GLbyte counter_ = 1; +}; + +TEST_F(MailboxSharedGpuContextTest, MailboxCaching) { + IntSize size(10, 10); + std::unique_ptr<CanvasResourceProvider> resource_provider = + CanvasResourceProvider::Create( + size, CanvasResourceProvider::kAcceleratedResourceUsage, + SharedGpuContext::ContextProviderWrapper()); + EXPECT_TRUE(resource_provider && resource_provider->IsValid()); + scoped_refptr<StaticBitmapImage> image = resource_provider->Snapshot(); + testing::Mock::VerifyAndClearExpectations(&gl_); + + FakeMailboxGenerator mailboxGenerator; + gpu::Mailbox mailbox; + mailbox.name[0] = 0; + + EXPECT_CALL(gl_, GenMailboxCHROMIUM(mailbox.name)) + .Times(1) + .WillOnce(testing::Invoke(&mailboxGenerator, + &FakeMailboxGenerator::GenMailbox)); + + SharedGpuContext::ContextProviderWrapper()->Utils()->GetMailboxForSkImage( + mailbox, image->PaintImageForCurrentFrame().GetSkImage(), GL_NEAREST); + + EXPECT_EQ(mailbox.name[0], 1); + + testing::Mock::VerifyAndClearExpectations(&gl_); + + EXPECT_CALL(gl_, GenMailboxCHROMIUM(mailbox.name)) + .Times(0); // GenMailboxCHROMIUM must not be called! + + mailbox.name[0] = 0; + SharedGpuContext::ContextProviderWrapper()->Utils()->GetMailboxForSkImage( + mailbox, image->PaintImageForCurrentFrame().GetSkImage(), GL_NEAREST); + EXPECT_EQ(mailbox.name[0], 1); + + testing::Mock::VerifyAndClearExpectations(&gl_); +} + +TEST_F(MailboxSharedGpuContextTest, MailboxCacheSurvivesSkiaRecycling) { + IntSize size(10, 10); + std::unique_ptr<CanvasResourceProvider> resource_provider = + CanvasResourceProvider::Create( + size, CanvasResourceProvider::kAcceleratedResourceUsage, + SharedGpuContext::ContextProviderWrapper()); + EXPECT_TRUE(resource_provider && resource_provider->IsValid()); + scoped_refptr<StaticBitmapImage> image = resource_provider->Snapshot(); + testing::Mock::VerifyAndClearExpectations(&gl_); + + FakeMailboxGenerator mailboxGenerator; + gpu::Mailbox mailbox; + mailbox.name[0] = 0; + + EXPECT_CALL(gl_, GenMailboxCHROMIUM(mailbox.name)) + .Times(1) + .WillOnce(testing::Invoke(&mailboxGenerator, + &FakeMailboxGenerator::GenMailbox)); + + SharedGpuContext::ContextProviderWrapper()->Utils()->GetMailboxForSkImage( + mailbox, image->PaintImageForCurrentFrame().GetSkImage(), GL_NEAREST); + + EXPECT_EQ(mailbox.name[0], 1); + testing::Mock::VerifyAndClearExpectations(&gl_); + + // Destroy image and surface to return texture to recleable resource pool + image = nullptr; + resource_provider = nullptr; + + testing::Mock::VerifyAndClearExpectations(&gl_); + + // Re-creating surface should recycle the old GrTexture inside skia + resource_provider = CanvasResourceProvider::Create( + size, CanvasResourceProvider::kAcceleratedResourceUsage, + SharedGpuContext::ContextProviderWrapper()); + EXPECT_TRUE(resource_provider && resource_provider->IsValid()); + image = resource_provider->Snapshot(); + + testing::Mock::VerifyAndClearExpectations(&gl_); + + EXPECT_CALL(gl_, GenMailboxCHROMIUM(mailbox.name)) + .Times(0); // GenMailboxCHROMIUM must not be called! + + mailbox.name[0] = 0; + SharedGpuContext::ContextProviderWrapper()->Utils()->GetMailboxForSkImage( + mailbox, image->PaintImageForCurrentFrame().GetSkImage(), GL_NEAREST); + EXPECT_EQ(mailbox.name[0], 1); + + testing::Mock::VerifyAndClearExpectations(&gl_); +} + +} // unnamed namespace + +} // blink diff --git a/chromium/third_party/blink/renderer/platform/graphics/gpu/webgl_image_conversion.cc b/chromium/third_party/blink/renderer/platform/graphics/gpu/webgl_image_conversion.cc new file mode 100644 index 00000000000..bdaa943e701 --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/graphics/gpu/webgl_image_conversion.cc @@ -0,0 +1,3260 @@ +// 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 "third_party/blink/renderer/platform/graphics/gpu/webgl_image_conversion.h" + +#include <memory> +#include "build/build_config.h" +#include "third_party/blink/renderer/platform/graphics/cpu/arm/webgl_image_conversion_neon.h" +#include "third_party/blink/renderer/platform/graphics/cpu/mips/webgl_image_conversion_msa.h" +#include "third_party/blink/renderer/platform/graphics/cpu/x86/webgl_image_conversion_sse.h" +#include "third_party/blink/renderer/platform/graphics/image_observer.h" +#include "third_party/blink/renderer/platform/graphics/skia/skia_utils.h" +#include "third_party/blink/renderer/platform/image-decoders/image_decoder.h" +#include "third_party/blink/renderer/platform/wtf/checked_numeric.h" +#include "third_party/skia/include/core/SkImage.h" + +namespace blink { + +namespace { + +const float kMaxInt8Value = INT8_MAX; +const float kMaxUInt8Value = UINT8_MAX; +const float kMaxInt16Value = INT16_MAX; +const float kMaxUInt16Value = UINT16_MAX; +const double kMaxInt32Value = INT32_MAX; +const double kMaxUInt32Value = UINT32_MAX; + +int8_t ClampMin(int8_t value) { + const static int8_t kMinInt8Value = INT8_MIN + 1; + return value < kMinInt8Value ? kMinInt8Value : value; +} + +int16_t ClampMin(int16_t value) { + const static int16_t kMinInt16Value = INT16_MIN + 1; + return value < kMinInt16Value ? kMinInt16Value : value; +} + +int32_t ClampMin(int32_t value) { + const static int32_t kMinInt32Value = INT32_MIN + 1; + return value < kMinInt32Value ? kMinInt32Value : value; +} + +// Return kDataFormatNumFormats if format/type combination is invalid. +WebGLImageConversion::DataFormat GetDataFormat(GLenum destination_format, + GLenum destination_type) { + WebGLImageConversion::DataFormat dst_format = + WebGLImageConversion::kDataFormatRGBA8; + switch (destination_type) { + case GL_BYTE: + switch (destination_format) { + case GL_RED: + case GL_RED_INTEGER: + dst_format = WebGLImageConversion::kDataFormatR8_S; + break; + case GL_RG: + case GL_RG_INTEGER: + dst_format = WebGLImageConversion::kDataFormatRG8_S; + break; + case GL_RGB: + case GL_RGB_INTEGER: + dst_format = WebGLImageConversion::kDataFormatRGB8_S; + break; + case GL_RGBA: + case GL_RGBA_INTEGER: + dst_format = WebGLImageConversion::kDataFormatRGBA8_S; + break; + default: + return WebGLImageConversion::kDataFormatNumFormats; + } + break; + case GL_UNSIGNED_BYTE: + switch (destination_format) { + case GL_RGB: + case GL_RGB_INTEGER: + case GL_SRGB_EXT: + dst_format = WebGLImageConversion::kDataFormatRGB8; + break; + case GL_RGBA: + case GL_RGBA_INTEGER: + case GL_SRGB_ALPHA_EXT: + dst_format = WebGLImageConversion::kDataFormatRGBA8; + break; + case GL_ALPHA: + dst_format = WebGLImageConversion::kDataFormatA8; + break; + case GL_LUMINANCE: + case GL_RED: + case GL_RED_INTEGER: + dst_format = WebGLImageConversion::kDataFormatR8; + break; + case GL_RG: + case GL_RG_INTEGER: + dst_format = WebGLImageConversion::kDataFormatRG8; + break; + case GL_LUMINANCE_ALPHA: + dst_format = WebGLImageConversion::kDataFormatRA8; + break; + default: + return WebGLImageConversion::kDataFormatNumFormats; + } + break; + case GL_SHORT: + switch (destination_format) { + case GL_RED_INTEGER: + dst_format = WebGLImageConversion::kDataFormatR16_S; + break; + case GL_RG_INTEGER: + dst_format = WebGLImageConversion::kDataFormatRG16_S; + break; + case GL_RGB_INTEGER: + dst_format = WebGLImageConversion::kDataFormatRGB16_S; + break; + case GL_RGBA_INTEGER: + dst_format = WebGLImageConversion::kDataFormatRGBA16_S; + break; + default: + return WebGLImageConversion::kDataFormatNumFormats; + } + break; + case GL_UNSIGNED_SHORT: + switch (destination_format) { + case GL_RED_INTEGER: + dst_format = WebGLImageConversion::kDataFormatR16; + break; + case GL_DEPTH_COMPONENT: + dst_format = WebGLImageConversion::kDataFormatD16; + break; + case GL_RG_INTEGER: + dst_format = WebGLImageConversion::kDataFormatRG16; + break; + case GL_RGB_INTEGER: + dst_format = WebGLImageConversion::kDataFormatRGB16; + break; + case GL_RGBA_INTEGER: + dst_format = WebGLImageConversion::kDataFormatRGBA16; + break; + default: + return WebGLImageConversion::kDataFormatNumFormats; + } + break; + case GL_INT: + switch (destination_format) { + case GL_RED_INTEGER: + dst_format = WebGLImageConversion::kDataFormatR32_S; + break; + case GL_RG_INTEGER: + dst_format = WebGLImageConversion::kDataFormatRG32_S; + break; + case GL_RGB_INTEGER: + dst_format = WebGLImageConversion::kDataFormatRGB32_S; + break; + case GL_RGBA_INTEGER: + dst_format = WebGLImageConversion::kDataFormatRGBA32_S; + break; + default: + return WebGLImageConversion::kDataFormatNumFormats; + } + break; + case GL_UNSIGNED_INT: + switch (destination_format) { + case GL_RED_INTEGER: + dst_format = WebGLImageConversion::kDataFormatR32; + break; + case GL_DEPTH_COMPONENT: + dst_format = WebGLImageConversion::kDataFormatD32; + break; + case GL_RG_INTEGER: + dst_format = WebGLImageConversion::kDataFormatRG32; + break; + case GL_RGB_INTEGER: + dst_format = WebGLImageConversion::kDataFormatRGB32; + break; + case GL_RGBA_INTEGER: + dst_format = WebGLImageConversion::kDataFormatRGBA32; + break; + default: + return WebGLImageConversion::kDataFormatNumFormats; + } + break; + case GL_HALF_FLOAT_OES: // OES_texture_half_float + case GL_HALF_FLOAT: + switch (destination_format) { + case GL_RGBA: + dst_format = WebGLImageConversion::kDataFormatRGBA16F; + break; + case GL_RGB: + dst_format = WebGLImageConversion::kDataFormatRGB16F; + break; + case GL_RG: + dst_format = WebGLImageConversion::kDataFormatRG16F; + break; + case GL_ALPHA: + dst_format = WebGLImageConversion::kDataFormatA16F; + break; + case GL_LUMINANCE: + case GL_RED: + dst_format = WebGLImageConversion::kDataFormatR16F; + break; + case GL_LUMINANCE_ALPHA: + dst_format = WebGLImageConversion::kDataFormatRA16F; + break; + default: + return WebGLImageConversion::kDataFormatNumFormats; + } + break; + case GL_FLOAT: // OES_texture_float + switch (destination_format) { + case GL_RGBA: + dst_format = WebGLImageConversion::kDataFormatRGBA32F; + break; + case GL_RGB: + dst_format = WebGLImageConversion::kDataFormatRGB32F; + break; + case GL_RG: + dst_format = WebGLImageConversion::kDataFormatRG32F; + break; + case GL_ALPHA: + dst_format = WebGLImageConversion::kDataFormatA32F; + break; + case GL_LUMINANCE: + case GL_RED: + dst_format = WebGLImageConversion::kDataFormatR32F; + break; + case GL_DEPTH_COMPONENT: + dst_format = WebGLImageConversion::kDataFormatD32F; + break; + case GL_LUMINANCE_ALPHA: + dst_format = WebGLImageConversion::kDataFormatRA32F; + break; + default: + return WebGLImageConversion::kDataFormatNumFormats; + } + break; + case GL_UNSIGNED_SHORT_4_4_4_4: + dst_format = WebGLImageConversion::kDataFormatRGBA4444; + break; + case GL_UNSIGNED_SHORT_5_5_5_1: + dst_format = WebGLImageConversion::kDataFormatRGBA5551; + break; + case GL_UNSIGNED_SHORT_5_6_5: + dst_format = WebGLImageConversion::kDataFormatRGB565; + break; + case GL_UNSIGNED_INT_5_9_9_9_REV: + dst_format = WebGLImageConversion::kDataFormatRGB5999; + break; + case GL_UNSIGNED_INT_24_8: + dst_format = WebGLImageConversion::kDataFormatDS24_8; + break; + case GL_UNSIGNED_INT_10F_11F_11F_REV: + dst_format = WebGLImageConversion::kDataFormatRGB10F11F11F; + break; + case GL_UNSIGNED_INT_2_10_10_10_REV: + dst_format = WebGLImageConversion::kDataFormatRGBA2_10_10_10; + break; + default: + return WebGLImageConversion::kDataFormatNumFormats; + } + return dst_format; +} + +// The following Float to Half-Float conversion code is from the implementation +// of ftp://www.fox-toolkit.org/pub/fasthalffloatconversion.pdf, "Fast Half +// Float Conversions" by Jeroen van der Zijp, November 2008 (Revised September +// 2010). Specially, the basetable[512] and shifttable[512] are generated as +// follows: +/* +unsigned short basetable[512]; +unsigned char shifttable[512]; + +void generatetables(){ + unsigned int i; + int e; + for (i = 0; i < 256; ++i){ + e = i - 127; + if (e < -24){ // Very small numbers map to zero + basetable[i | 0x000] = 0x0000; + basetable[i | 0x100] = 0x8000; + shifttable[i | 0x000] = 24; + shifttable[i | 0x100] = 24; + } + else if (e < -14) { // Small numbers map to denorms + basetable[i | 0x000] = (0x0400>>(-e-14)); + basetable[i | 0x100] = (0x0400>>(-e-14)) | 0x8000; + shifttable[i | 0x000] = -e-1; + shifttable[i | 0x100] = -e-1; + } + else if (e <= 15){ // Normal numbers just lose precision + basetable[i | 0x000] = ((e+15)<<10); + basetable[i| 0x100] = ((e+15)<<10) | 0x8000; + shifttable[i|0x000] = 13; + shifttable[i|0x100] = 13; + } + else if (e<128){ // Large numbers map to Infinity + basetable[i|0x000] = 0x7C00; + basetable[i|0x100] = 0xFC00; + shifttable[i|0x000] = 24; + shifttable[i|0x100] = 24; + } + else { // Infinity and NaN's stay Infinity and NaN's + basetable[i|0x000] = 0x7C00; + basetable[i|0x100] = 0xFC00; + shifttable[i|0x000] = 13; + shifttable[i|0x100] = 13; + } + } +} +*/ + +unsigned short g_base_table[512] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 1, 2, 4, 8, 16, 32, 64, + 128, 256, 512, 1024, 2048, 3072, 4096, 5120, 6144, 7168, 8192, + 9216, 10240, 11264, 12288, 13312, 14336, 15360, 16384, 17408, 18432, 19456, + 20480, 21504, 22528, 23552, 24576, 25600, 26624, 27648, 28672, 29696, 30720, + 31744, 31744, 31744, 31744, 31744, 31744, 31744, 31744, 31744, 31744, 31744, + 31744, 31744, 31744, 31744, 31744, 31744, 31744, 31744, 31744, 31744, 31744, + 31744, 31744, 31744, 31744, 31744, 31744, 31744, 31744, 31744, 31744, 31744, + 31744, 31744, 31744, 31744, 31744, 31744, 31744, 31744, 31744, 31744, 31744, + 31744, 31744, 31744, 31744, 31744, 31744, 31744, 31744, 31744, 31744, 31744, + 31744, 31744, 31744, 31744, 31744, 31744, 31744, 31744, 31744, 31744, 31744, + 31744, 31744, 31744, 31744, 31744, 31744, 31744, 31744, 31744, 31744, 31744, + 31744, 31744, 31744, 31744, 31744, 31744, 31744, 31744, 31744, 31744, 31744, + 31744, 31744, 31744, 31744, 31744, 31744, 31744, 31744, 31744, 31744, 31744, + 31744, 31744, 31744, 31744, 31744, 31744, 31744, 31744, 31744, 31744, 31744, + 31744, 31744, 31744, 32768, 32768, 32768, 32768, 32768, 32768, 32768, 32768, + 32768, 32768, 32768, 32768, 32768, 32768, 32768, 32768, 32768, 32768, 32768, + 32768, 32768, 32768, 32768, 32768, 32768, 32768, 32768, 32768, 32768, 32768, + 32768, 32768, 32768, 32768, 32768, 32768, 32768, 32768, 32768, 32768, 32768, + 32768, 32768, 32768, 32768, 32768, 32768, 32768, 32768, 32768, 32768, 32768, + 32768, 32768, 32768, 32768, 32768, 32768, 32768, 32768, 32768, 32768, 32768, + 32768, 32768, 32768, 32768, 32768, 32768, 32768, 32768, 32768, 32768, 32768, + 32768, 32768, 32768, 32768, 32768, 32768, 32768, 32768, 32768, 32768, 32768, + 32768, 32768, 32768, 32768, 32768, 32768, 32768, 32768, 32768, 32768, 32768, + 32768, 32768, 32768, 32768, 32768, 32768, 32768, 32769, 32770, 32772, 32776, + 32784, 32800, 32832, 32896, 33024, 33280, 33792, 34816, 35840, 36864, 37888, + 38912, 39936, 40960, 41984, 43008, 44032, 45056, 46080, 47104, 48128, 49152, + 50176, 51200, 52224, 53248, 54272, 55296, 56320, 57344, 58368, 59392, 60416, + 61440, 62464, 63488, 64512, 64512, 64512, 64512, 64512, 64512, 64512, 64512, + 64512, 64512, 64512, 64512, 64512, 64512, 64512, 64512, 64512, 64512, 64512, + 64512, 64512, 64512, 64512, 64512, 64512, 64512, 64512, 64512, 64512, 64512, + 64512, 64512, 64512, 64512, 64512, 64512, 64512, 64512, 64512, 64512, 64512, + 64512, 64512, 64512, 64512, 64512, 64512, 64512, 64512, 64512, 64512, 64512, + 64512, 64512, 64512, 64512, 64512, 64512, 64512, 64512, 64512, 64512, 64512, + 64512, 64512, 64512, 64512, 64512, 64512, 64512, 64512, 64512, 64512, 64512, + 64512, 64512, 64512, 64512, 64512, 64512, 64512, 64512, 64512, 64512, 64512, + 64512, 64512, 64512, 64512, 64512, 64512, 64512, 64512, 64512, 64512, 64512, + 64512, 64512, 64512, 64512, 64512, 64512, 64512, 64512, 64512, 64512, 64512, + 64512, 64512, 64512, 64512, 64512, 64512}; + +unsigned char g_shift_table[512] = { + 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, + 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, + 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, + 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, + 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, + 24, 24, 24, 24, 24, 24, 24, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, + 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, + 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 24, 24, 24, 24, 24, 24, 24, 24, 24, + 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, + 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, + 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, + 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, + 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, + 24, 24, 24, 24, 24, 24, 24, 24, 13, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, + 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, + 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, + 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, + 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, + 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 23, 22, + 21, 20, 19, 18, 17, 16, 15, 14, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, + 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, + 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, + 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, + 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, + 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, + 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, + 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 13}; + +unsigned short ConvertFloatToHalfFloat(float f) { + unsigned temp = *(reinterpret_cast<unsigned*>(&f)); + unsigned signexp = (temp >> 23) & 0x1ff; + return g_base_table[signexp] + + ((temp & 0x007fffff) >> g_shift_table[signexp]); +} + +/* BEGIN CODE SHARED WITH MOZILLA FIREFOX */ + +// The following packing and unpacking routines are expressed in terms of +// function templates and inline functions to achieve generality and speedup. +// Explicit template specializations correspond to the cases that would occur. +// Some code are merged back from Mozilla code in +// http://mxr.mozilla.org/mozilla-central/source/content/canvas/src/WebGLTexelConversions.h + +//---------------------------------------------------------------------- +// Pixel unpacking routines. +template <int format, typename SourceType, typename DstType> +void Unpack(const SourceType*, DstType*, unsigned) { + NOTREACHED(); +} + +template <> +void Unpack<WebGLImageConversion::kDataFormatARGB8, uint8_t, uint8_t>( + const uint8_t* source, + uint8_t* destination, + unsigned pixels_per_row) { + for (unsigned i = 0; i < pixels_per_row; ++i) { + destination[0] = source[1]; + destination[1] = source[2]; + destination[2] = source[3]; + destination[3] = source[0]; + source += 4; + destination += 4; + } +} + +template <> +void Unpack<WebGLImageConversion::kDataFormatABGR8, uint8_t, uint8_t>( + const uint8_t* source, + uint8_t* destination, + unsigned pixels_per_row) { + for (unsigned i = 0; i < pixels_per_row; ++i) { + destination[0] = source[3]; + destination[1] = source[2]; + destination[2] = source[1]; + destination[3] = source[0]; + source += 4; + destination += 4; + } +} + +template <> +void Unpack<WebGLImageConversion::kDataFormatBGRA8, uint8_t, uint8_t>( + const uint8_t* source, + uint8_t* destination, + unsigned pixels_per_row) { + const uint32_t* source32 = reinterpret_cast_ptr<const uint32_t*>(source); + uint32_t* destination32 = reinterpret_cast_ptr<uint32_t*>(destination); + +#if defined(ARCH_CPU_X86_FAMILY) + SIMD::UnpackOneRowOfBGRA8LittleToRGBA8(source32, destination32, + pixels_per_row); +#endif +#if HAVE_MIPS_MSA_INTRINSICS + SIMD::unpackOneRowOfBGRA8LittleToRGBA8MSA(source32, destination32, + pixels_per_row); +#endif + for (unsigned i = 0; i < pixels_per_row; ++i) { + uint32_t bgra = source32[i]; +#if defined(ARCH_CPU_BIG_ENDIAN) + uint32_t brMask = 0xff00ff00; + uint32_t gaMask = 0x00ff00ff; +#else + uint32_t br_mask = 0x00ff00ff; + uint32_t ga_mask = 0xff00ff00; +#endif + uint32_t rgba = + (((bgra >> 16) | (bgra << 16)) & br_mask) | (bgra & ga_mask); + destination32[i] = rgba; + } +} + +template <> +void Unpack<WebGLImageConversion::kDataFormatRGBA5551, uint16_t, uint8_t>( + const uint16_t* source, + uint8_t* destination, + unsigned pixels_per_row) { +#if defined(ARCH_CPU_X86_FAMILY) + SIMD::UnpackOneRowOfRGBA5551LittleToRGBA8(source, destination, + pixels_per_row); +#endif +#if WTF_CPU_ARM_NEON + SIMD::UnpackOneRowOfRGBA5551ToRGBA8(source, destination, pixels_per_row); +#endif +#if HAVE_MIPS_MSA_INTRINSICS + SIMD::unpackOneRowOfRGBA5551ToRGBA8MSA(source, destination, pixels_per_row); +#endif + + for (unsigned i = 0; i < pixels_per_row; ++i) { + uint16_t packed_value = source[0]; + uint8_t r = packed_value >> 11; + uint8_t g = (packed_value >> 6) & 0x1F; + uint8_t b = (packed_value >> 1) & 0x1F; + destination[0] = (r << 3) | (r & 0x7); + destination[1] = (g << 3) | (g & 0x7); + destination[2] = (b << 3) | (b & 0x7); + destination[3] = (packed_value & 0x1) ? 0xFF : 0x0; + source += 1; + destination += 4; + } +} + +template <> +void Unpack<WebGLImageConversion::kDataFormatRGBA4444, uint16_t, uint8_t>( + const uint16_t* source, + uint8_t* destination, + unsigned pixels_per_row) { +#if defined(ARCH_CPU_X86_FAMILY) + SIMD::UnpackOneRowOfRGBA4444LittleToRGBA8(source, destination, + pixels_per_row); +#endif +#if WTF_CPU_ARM_NEON + SIMD::UnpackOneRowOfRGBA4444ToRGBA8(source, destination, pixels_per_row); +#endif +#if HAVE_MIPS_MSA_INTRINSICS + SIMD::unpackOneRowOfRGBA4444ToRGBA8MSA(source, destination, pixels_per_row); +#endif + for (unsigned i = 0; i < pixels_per_row; ++i) { + uint16_t packed_value = source[0]; + uint8_t r = packed_value >> 12; + uint8_t g = (packed_value >> 8) & 0x0F; + uint8_t b = (packed_value >> 4) & 0x0F; + uint8_t a = packed_value & 0x0F; + destination[0] = r << 4 | r; + destination[1] = g << 4 | g; + destination[2] = b << 4 | b; + destination[3] = a << 4 | a; + source += 1; + destination += 4; + } +} + +template <> +void Unpack<WebGLImageConversion::kDataFormatRA8, uint8_t, uint8_t>( + const uint8_t* source, + uint8_t* destination, + unsigned pixels_per_row) { + for (unsigned i = 0; i < pixels_per_row; ++i) { + destination[0] = source[0]; + destination[1] = source[0]; + destination[2] = source[0]; + destination[3] = source[1]; + source += 2; + destination += 4; + } +} + +template <> +void Unpack<WebGLImageConversion::kDataFormatAR8, uint8_t, uint8_t>( + const uint8_t* source, + uint8_t* destination, + unsigned pixels_per_row) { + for (unsigned i = 0; i < pixels_per_row; ++i) { + destination[0] = source[1]; + destination[1] = source[1]; + destination[2] = source[1]; + destination[3] = source[0]; + source += 2; + destination += 4; + } +} + +template <> +void Unpack<WebGLImageConversion::kDataFormatRGBA8, uint8_t, float>( + const uint8_t* source, + float* destination, + unsigned pixels_per_row) { + const float kScaleFactor = 1.0f / 255.0f; + for (unsigned i = 0; i < pixels_per_row; ++i) { + destination[0] = source[0] * kScaleFactor; + destination[1] = source[1] * kScaleFactor; + destination[2] = source[2] * kScaleFactor; + destination[3] = source[3] * kScaleFactor; + source += 4; + destination += 4; + } +} + +template <> +void Unpack<WebGLImageConversion::kDataFormatBGRA8, uint8_t, float>( + const uint8_t* source, + float* destination, + unsigned pixels_per_row) { + const float kScaleFactor = 1.0f / 255.0f; + for (unsigned i = 0; i < pixels_per_row; ++i) { + destination[0] = source[2] * kScaleFactor; + destination[1] = source[1] * kScaleFactor; + destination[2] = source[0] * kScaleFactor; + destination[3] = source[3] * kScaleFactor; + source += 4; + destination += 4; + } +} + +template <> +void Unpack<WebGLImageConversion::kDataFormatABGR8, uint8_t, float>( + const uint8_t* source, + float* destination, + unsigned pixels_per_row) { + const float kScaleFactor = 1.0f / 255.0f; + for (unsigned i = 0; i < pixels_per_row; ++i) { + destination[0] = source[3] * kScaleFactor; + destination[1] = source[2] * kScaleFactor; + destination[2] = source[1] * kScaleFactor; + destination[3] = source[0] * kScaleFactor; + source += 4; + destination += 4; + } +} + +template <> +void Unpack<WebGLImageConversion::kDataFormatARGB8, uint8_t, float>( + const uint8_t* source, + float* destination, + unsigned pixels_per_row) { + const float kScaleFactor = 1.0f / 255.0f; + for (unsigned i = 0; i < pixels_per_row; ++i) { + destination[0] = source[1] * kScaleFactor; + destination[1] = source[2] * kScaleFactor; + destination[2] = source[3] * kScaleFactor; + destination[3] = source[0] * kScaleFactor; + source += 4; + destination += 4; + } +} + +template <> +void Unpack<WebGLImageConversion::kDataFormatRA32F, float, float>( + const float* source, + float* destination, + unsigned pixels_per_row) { + for (unsigned i = 0; i < pixels_per_row; ++i) { + destination[0] = source[0]; + destination[1] = source[0]; + destination[2] = source[0]; + destination[3] = source[1]; + source += 2; + destination += 4; + } +} + +template <> +void Unpack<WebGLImageConversion::kDataFormatRGBA2_10_10_10, uint32_t, float>( + const uint32_t* source, + float* destination, + unsigned pixels_per_row) { + static const float kRgbScaleFactor = 1.0f / 1023.0f; + static const float kAlphaScaleFactor = 1.0f / 3.0f; + for (unsigned i = 0; i < pixels_per_row; ++i) { + uint32_t packed_value = source[0]; + destination[0] = static_cast<float>(packed_value & 0x3FF) * kRgbScaleFactor; + destination[1] = + static_cast<float>((packed_value >> 10) & 0x3FF) * kRgbScaleFactor; + destination[2] = + static_cast<float>((packed_value >> 20) & 0x3FF) * kRgbScaleFactor; + destination[3] = static_cast<float>(packed_value >> 30) * kAlphaScaleFactor; + source += 1; + destination += 4; + } +} + +//---------------------------------------------------------------------- +// Pixel packing routines. +// + +template <int format, int alphaOp, typename SourceType, typename DstType> +void Pack(const SourceType*, DstType*, unsigned) { + NOTREACHED(); +} + +template <> +void Pack<WebGLImageConversion::kDataFormatA8, + WebGLImageConversion::kAlphaDoNothing, + uint8_t, + uint8_t>(const uint8_t* source, + uint8_t* destination, + unsigned pixels_per_row) { + for (unsigned i = 0; i < pixels_per_row; ++i) { + destination[0] = source[3]; + source += 4; + destination += 1; + } +} + +template <> +void Pack<WebGLImageConversion::kDataFormatR8, + WebGLImageConversion::kAlphaDoNothing, + uint8_t, + uint8_t>(const uint8_t* source, + uint8_t* destination, + unsigned pixels_per_row) { + for (unsigned i = 0; i < pixels_per_row; ++i) { + destination[0] = source[0]; + source += 4; + destination += 1; + } +} + +template <> +void Pack<WebGLImageConversion::kDataFormatR8, + WebGLImageConversion::kAlphaDoPremultiply, + uint8_t, + uint8_t>(const uint8_t* source, + uint8_t* destination, + unsigned pixels_per_row) { + for (unsigned i = 0; i < pixels_per_row; ++i) { + float scale_factor = source[3] / 255.0f; + uint8_t source_r = + static_cast<uint8_t>(static_cast<float>(source[0]) * scale_factor); + destination[0] = source_r; + source += 4; + destination += 1; + } +} + +// FIXME: this routine is lossy and must be removed. +template <> +void Pack<WebGLImageConversion::kDataFormatR8, + WebGLImageConversion::kAlphaDoUnmultiply, + uint8_t, + uint8_t>(const uint8_t* source, + uint8_t* destination, + unsigned pixels_per_row) { +#if defined(ARCH_CPU_X86_FAMILY) + SIMD::PackOneRowOfRGBA8LittleToR8(source, destination, pixels_per_row); +#endif +#if HAVE_MIPS_MSA_INTRINSICS + SIMD::packOneRowOfRGBA8LittleToR8MSA(source, destination, pixels_per_row); +#endif + for (unsigned i = 0; i < pixels_per_row; ++i) { + float scale_factor = source[3] ? 255.0f / source[3] : 1.0f; + uint8_t source_r = + static_cast<uint8_t>(static_cast<float>(source[0]) * scale_factor); + destination[0] = source_r; + source += 4; + destination += 1; + } +} + +template <> +void Pack<WebGLImageConversion::kDataFormatRA8, + WebGLImageConversion::kAlphaDoNothing, + uint8_t, + uint8_t>(const uint8_t* source, + uint8_t* destination, + unsigned pixels_per_row) { + for (unsigned i = 0; i < pixels_per_row; ++i) { + destination[0] = source[0]; + destination[1] = source[3]; + source += 4; + destination += 2; + } +} + +template <> +void Pack<WebGLImageConversion::kDataFormatRA8, + WebGLImageConversion::kAlphaDoPremultiply, + uint8_t, + uint8_t>(const uint8_t* source, + uint8_t* destination, + unsigned pixels_per_row) { + for (unsigned i = 0; i < pixels_per_row; ++i) { + float scale_factor = source[3] / 255.0f; + uint8_t source_r = + static_cast<uint8_t>(static_cast<float>(source[0]) * scale_factor); + destination[0] = source_r; + destination[1] = source[3]; + source += 4; + destination += 2; + } +} + +// FIXME: this routine is lossy and must be removed. +template <> +void Pack<WebGLImageConversion::kDataFormatRA8, + WebGLImageConversion::kAlphaDoUnmultiply, + uint8_t, + uint8_t>(const uint8_t* source, + uint8_t* destination, + unsigned pixels_per_row) { +#if defined(ARCH_CPU_X86_FAMILY) + SIMD::PackOneRowOfRGBA8LittleToRA8(source, destination, pixels_per_row); +#endif +#if HAVE_MIPS_MSA_INTRINSICS + SIMD::packOneRowOfRGBA8LittleToRA8MSA(source, destination, pixels_per_row); +#endif + for (unsigned i = 0; i < pixels_per_row; ++i) { + float scale_factor = source[3] ? 255.0f / source[3] : 1.0f; + uint8_t source_r = + static_cast<uint8_t>(static_cast<float>(source[0]) * scale_factor); + destination[0] = source_r; + destination[1] = source[3]; + source += 4; + destination += 2; + } +} + +template <> +void Pack<WebGLImageConversion::kDataFormatRGB8, + WebGLImageConversion::kAlphaDoNothing, + uint8_t, + uint8_t>(const uint8_t* source, + uint8_t* destination, + unsigned pixels_per_row) { + for (unsigned i = 0; i < pixels_per_row; ++i) { + destination[0] = source[0]; + destination[1] = source[1]; + destination[2] = source[2]; + source += 4; + destination += 3; + } +} + +template <> +void Pack<WebGLImageConversion::kDataFormatRGB8, + WebGLImageConversion::kAlphaDoPremultiply, + uint8_t, + uint8_t>(const uint8_t* source, + uint8_t* destination, + unsigned pixels_per_row) { + for (unsigned i = 0; i < pixels_per_row; ++i) { + float scale_factor = source[3] / 255.0f; + uint8_t source_r = + static_cast<uint8_t>(static_cast<float>(source[0]) * scale_factor); + uint8_t source_g = + static_cast<uint8_t>(static_cast<float>(source[1]) * scale_factor); + uint8_t source_b = + static_cast<uint8_t>(static_cast<float>(source[2]) * scale_factor); + destination[0] = source_r; + destination[1] = source_g; + destination[2] = source_b; + source += 4; + destination += 3; + } +} + +// FIXME: this routine is lossy and must be removed. +template <> +void Pack<WebGLImageConversion::kDataFormatRGB8, + WebGLImageConversion::kAlphaDoUnmultiply, + uint8_t, + uint8_t>(const uint8_t* source, + uint8_t* destination, + unsigned pixels_per_row) { + for (unsigned i = 0; i < pixels_per_row; ++i) { + float scale_factor = source[3] ? 255.0f / source[3] : 1.0f; + uint8_t source_r = + static_cast<uint8_t>(static_cast<float>(source[0]) * scale_factor); + uint8_t source_g = + static_cast<uint8_t>(static_cast<float>(source[1]) * scale_factor); + uint8_t source_b = + static_cast<uint8_t>(static_cast<float>(source[2]) * scale_factor); + destination[0] = source_r; + destination[1] = source_g; + destination[2] = source_b; + source += 4; + destination += 3; + } +} + +template <> +void Pack<WebGLImageConversion::kDataFormatRGBA8, + WebGLImageConversion::kAlphaDoPremultiply, + uint8_t, + uint8_t>(const uint8_t* source, + uint8_t* destination, + unsigned pixels_per_row) { + for (unsigned i = 0; i < pixels_per_row; ++i) { + float scale_factor = source[3] / 255.0f; + uint8_t source_r = + static_cast<uint8_t>(static_cast<float>(source[0]) * scale_factor); + uint8_t source_g = + static_cast<uint8_t>(static_cast<float>(source[1]) * scale_factor); + uint8_t source_b = + static_cast<uint8_t>(static_cast<float>(source[2]) * scale_factor); + destination[0] = source_r; + destination[1] = source_g; + destination[2] = source_b; + destination[3] = source[3]; + source += 4; + destination += 4; + } +} + +// FIXME: this routine is lossy and must be removed. +template <> +void Pack<WebGLImageConversion::kDataFormatRGBA8, + WebGLImageConversion::kAlphaDoUnmultiply, + uint8_t, + uint8_t>(const uint8_t* source, + uint8_t* destination, + unsigned pixels_per_row) { +#if defined(ARCH_CPU_X86_FAMILY) + SIMD::PackOneRowOfRGBA8LittleToRGBA8(source, destination, pixels_per_row); +#endif +#if HAVE_MIPS_MSA_INTRINSICS + SIMD::packOneRowOfRGBA8LittleToRGBA8MSA(source, destination, pixels_per_row); +#endif + for (unsigned i = 0; i < pixels_per_row; ++i) { + float scale_factor = source[3] ? 255.0f / source[3] : 1.0f; + uint8_t source_r = + static_cast<uint8_t>(static_cast<float>(source[0]) * scale_factor); + uint8_t source_g = + static_cast<uint8_t>(static_cast<float>(source[1]) * scale_factor); + uint8_t source_b = + static_cast<uint8_t>(static_cast<float>(source[2]) * scale_factor); + destination[0] = source_r; + destination[1] = source_g; + destination[2] = source_b; + destination[3] = source[3]; + source += 4; + destination += 4; + } +} + +template <> +void Pack<WebGLImageConversion::kDataFormatRGBA4444, + WebGLImageConversion::kAlphaDoNothing, + uint8_t, + uint16_t>(const uint8_t* source, + uint16_t* destination, + unsigned pixels_per_row) { +#if WTF_CPU_ARM_NEON + SIMD::PackOneRowOfRGBA8ToUnsignedShort4444(source, destination, + pixels_per_row); +#endif +#if HAVE_MIPS_MSA_INTRINSICS + SIMD::packOneRowOfRGBA8ToUnsignedShort4444MSA(source, destination, + pixels_per_row); +#endif + for (unsigned i = 0; i < pixels_per_row; ++i) { + *destination = (((source[0] & 0xF0) << 8) | ((source[1] & 0xF0) << 4) | + (source[2] & 0xF0) | (source[3] >> 4)); + source += 4; + destination += 1; + } +} + +template <> +void Pack<WebGLImageConversion::kDataFormatRGBA4444, + WebGLImageConversion::kAlphaDoPremultiply, + uint8_t, + uint16_t>(const uint8_t* source, + uint16_t* destination, + unsigned pixels_per_row) { + for (unsigned i = 0; i < pixels_per_row; ++i) { + float scale_factor = source[3] / 255.0f; + uint8_t source_r = + static_cast<uint8_t>(static_cast<float>(source[0]) * scale_factor); + uint8_t source_g = + static_cast<uint8_t>(static_cast<float>(source[1]) * scale_factor); + uint8_t source_b = + static_cast<uint8_t>(static_cast<float>(source[2]) * scale_factor); + *destination = (((source_r & 0xF0) << 8) | ((source_g & 0xF0) << 4) | + (source_b & 0xF0) | (source[3] >> 4)); + source += 4; + destination += 1; + } +} + +// FIXME: this routine is lossy and must be removed. +template <> +void Pack<WebGLImageConversion::kDataFormatRGBA4444, + WebGLImageConversion::kAlphaDoUnmultiply, + uint8_t, + uint16_t>(const uint8_t* source, + uint16_t* destination, + unsigned pixels_per_row) { + for (unsigned i = 0; i < pixels_per_row; ++i) { + float scale_factor = source[3] ? 255.0f / source[3] : 1.0f; + uint8_t source_r = + static_cast<uint8_t>(static_cast<float>(source[0]) * scale_factor); + uint8_t source_g = + static_cast<uint8_t>(static_cast<float>(source[1]) * scale_factor); + uint8_t source_b = + static_cast<uint8_t>(static_cast<float>(source[2]) * scale_factor); + *destination = (((source_r & 0xF0) << 8) | ((source_g & 0xF0) << 4) | + (source_b & 0xF0) | (source[3] >> 4)); + source += 4; + destination += 1; + } +} + +template <> +void Pack<WebGLImageConversion::kDataFormatRGBA5551, + WebGLImageConversion::kAlphaDoNothing, + uint8_t, + uint16_t>(const uint8_t* source, + uint16_t* destination, + unsigned pixels_per_row) { +#if WTF_CPU_ARM_NEON + SIMD::PackOneRowOfRGBA8ToUnsignedShort5551(source, destination, + pixels_per_row); +#endif +#if HAVE_MIPS_MSA_INTRINSICS + SIMD::packOneRowOfRGBA8ToUnsignedShort5551MSA(source, destination, + pixels_per_row); +#endif + for (unsigned i = 0; i < pixels_per_row; ++i) { + *destination = (((source[0] & 0xF8) << 8) | ((source[1] & 0xF8) << 3) | + ((source[2] & 0xF8) >> 2) | (source[3] >> 7)); + source += 4; + destination += 1; + } +} + +template <> +void Pack<WebGLImageConversion::kDataFormatRGBA5551, + WebGLImageConversion::kAlphaDoPremultiply, + uint8_t, + uint16_t>(const uint8_t* source, + uint16_t* destination, + unsigned pixels_per_row) { + for (unsigned i = 0; i < pixels_per_row; ++i) { + float scale_factor = source[3] / 255.0f; + uint8_t source_r = + static_cast<uint8_t>(static_cast<float>(source[0]) * scale_factor); + uint8_t source_g = + static_cast<uint8_t>(static_cast<float>(source[1]) * scale_factor); + uint8_t source_b = + static_cast<uint8_t>(static_cast<float>(source[2]) * scale_factor); + *destination = (((source_r & 0xF8) << 8) | ((source_g & 0xF8) << 3) | + ((source_b & 0xF8) >> 2) | (source[3] >> 7)); + source += 4; + destination += 1; + } +} + +// FIXME: this routine is lossy and must be removed. +template <> +void Pack<WebGLImageConversion::kDataFormatRGBA5551, + WebGLImageConversion::kAlphaDoUnmultiply, + uint8_t, + uint16_t>(const uint8_t* source, + uint16_t* destination, + unsigned pixels_per_row) { + for (unsigned i = 0; i < pixels_per_row; ++i) { + float scale_factor = source[3] ? 255.0f / source[3] : 1.0f; + uint8_t source_r = + static_cast<uint8_t>(static_cast<float>(source[0]) * scale_factor); + uint8_t source_g = + static_cast<uint8_t>(static_cast<float>(source[1]) * scale_factor); + uint8_t source_b = + static_cast<uint8_t>(static_cast<float>(source[2]) * scale_factor); + *destination = (((source_r & 0xF8) << 8) | ((source_g & 0xF8) << 3) | + ((source_b & 0xF8) >> 2) | (source[3] >> 7)); + source += 4; + destination += 1; + } +} + +template <> +void Pack<WebGLImageConversion::kDataFormatRGB565, + WebGLImageConversion::kAlphaDoNothing, + uint8_t, + uint16_t>(const uint8_t* source, + uint16_t* destination, + unsigned pixels_per_row) { +#if WTF_CPU_ARM_NEON + SIMD::PackOneRowOfRGBA8ToUnsignedShort565(source, destination, + pixels_per_row); +#endif +#if HAVE_MIPS_MSA_INTRINSICS + SIMD::packOneRowOfRGBA8ToUnsignedShort565MSA(source, destination, + pixels_per_row); +#endif + for (unsigned i = 0; i < pixels_per_row; ++i) { + *destination = (((source[0] & 0xF8) << 8) | ((source[1] & 0xFC) << 3) | + ((source[2] & 0xF8) >> 3)); + source += 4; + destination += 1; + } +} + +template <> +void Pack<WebGLImageConversion::kDataFormatRGB565, + WebGLImageConversion::kAlphaDoPremultiply, + uint8_t, + uint16_t>(const uint8_t* source, + uint16_t* destination, + unsigned pixels_per_row) { + for (unsigned i = 0; i < pixels_per_row; ++i) { + float scale_factor = source[3] / 255.0f; + uint8_t source_r = + static_cast<uint8_t>(static_cast<float>(source[0]) * scale_factor); + uint8_t source_g = + static_cast<uint8_t>(static_cast<float>(source[1]) * scale_factor); + uint8_t source_b = + static_cast<uint8_t>(static_cast<float>(source[2]) * scale_factor); + *destination = (((source_r & 0xF8) << 8) | ((source_g & 0xFC) << 3) | + ((source_b & 0xF8) >> 3)); + source += 4; + destination += 1; + } +} + +// FIXME: this routine is lossy and must be removed. +template <> +void Pack<WebGLImageConversion::kDataFormatRGB565, + WebGLImageConversion::kAlphaDoUnmultiply, + uint8_t, + uint16_t>(const uint8_t* source, + uint16_t* destination, + unsigned pixels_per_row) { + for (unsigned i = 0; i < pixels_per_row; ++i) { + float scale_factor = source[3] ? 255.0f / source[3] : 1.0f; + uint8_t source_r = + static_cast<uint8_t>(static_cast<float>(source[0]) * scale_factor); + uint8_t source_g = + static_cast<uint8_t>(static_cast<float>(source[1]) * scale_factor); + uint8_t source_b = + static_cast<uint8_t>(static_cast<float>(source[2]) * scale_factor); + *destination = (((source_r & 0xF8) << 8) | ((source_g & 0xFC) << 3) | + ((source_b & 0xF8) >> 3)); + source += 4; + destination += 1; + } +} + +template <> +void Pack<WebGLImageConversion::kDataFormatRGB32F, + WebGLImageConversion::kAlphaDoNothing, + float, + float>(const float* source, + float* destination, + unsigned pixels_per_row) { + for (unsigned i = 0; i < pixels_per_row; ++i) { + destination[0] = source[0]; + destination[1] = source[1]; + destination[2] = source[2]; + source += 4; + destination += 3; + } +} + +template <> +void Pack<WebGLImageConversion::kDataFormatRGB32F, + WebGLImageConversion::kAlphaDoPremultiply, + float, + float>(const float* source, + float* destination, + unsigned pixels_per_row) { + for (unsigned i = 0; i < pixels_per_row; ++i) { + float scale_factor = source[3]; + destination[0] = source[0] * scale_factor; + destination[1] = source[1] * scale_factor; + destination[2] = source[2] * scale_factor; + source += 4; + destination += 3; + } +} + +template <> +void Pack<WebGLImageConversion::kDataFormatRGB32F, + WebGLImageConversion::kAlphaDoUnmultiply, + float, + float>(const float* source, + float* destination, + unsigned pixels_per_row) { + for (unsigned i = 0; i < pixels_per_row; ++i) { + float scale_factor = source[3] ? 1.0f / source[3] : 1.0f; + destination[0] = source[0] * scale_factor; + destination[1] = source[1] * scale_factor; + destination[2] = source[2] * scale_factor; + source += 4; + destination += 3; + } +} + +template <> +void Pack<WebGLImageConversion::kDataFormatRGBA32F, + WebGLImageConversion::kAlphaDoPremultiply, + float, + float>(const float* source, + float* destination, + unsigned pixels_per_row) { + for (unsigned i = 0; i < pixels_per_row; ++i) { + float scale_factor = source[3]; + destination[0] = source[0] * scale_factor; + destination[1] = source[1] * scale_factor; + destination[2] = source[2] * scale_factor; + destination[3] = source[3]; + source += 4; + destination += 4; + } +} + +template <> +void Pack<WebGLImageConversion::kDataFormatRGBA32F, + WebGLImageConversion::kAlphaDoUnmultiply, + float, + float>(const float* source, + float* destination, + unsigned pixels_per_row) { + for (unsigned i = 0; i < pixels_per_row; ++i) { + float scale_factor = source[3] ? 1.0f / source[3] : 1.0f; + destination[0] = source[0] * scale_factor; + destination[1] = source[1] * scale_factor; + destination[2] = source[2] * scale_factor; + destination[3] = source[3]; + source += 4; + destination += 4; + } +} + +template <> +void Pack<WebGLImageConversion::kDataFormatA32F, + WebGLImageConversion::kAlphaDoNothing, + float, + float>(const float* source, + float* destination, + unsigned pixels_per_row) { + for (unsigned i = 0; i < pixels_per_row; ++i) { + destination[0] = source[3]; + source += 4; + destination += 1; + } +} + +template <> +void Pack<WebGLImageConversion::kDataFormatR32F, + WebGLImageConversion::kAlphaDoNothing, + float, + float>(const float* source, + float* destination, + unsigned pixels_per_row) { + for (unsigned i = 0; i < pixels_per_row; ++i) { + destination[0] = source[0]; + source += 4; + destination += 1; + } +} + +template <> +void Pack<WebGLImageConversion::kDataFormatR32F, + WebGLImageConversion::kAlphaDoPremultiply, + float, + float>(const float* source, + float* destination, + unsigned pixels_per_row) { + for (unsigned i = 0; i < pixels_per_row; ++i) { + float scale_factor = source[3]; + destination[0] = source[0] * scale_factor; + source += 4; + destination += 1; + } +} + +template <> +void Pack<WebGLImageConversion::kDataFormatR32F, + WebGLImageConversion::kAlphaDoUnmultiply, + float, + float>(const float* source, + float* destination, + unsigned pixels_per_row) { + for (unsigned i = 0; i < pixels_per_row; ++i) { + float scale_factor = source[3] ? 1.0f / source[3] : 1.0f; + destination[0] = source[0] * scale_factor; + source += 4; + destination += 1; + } +} + +template <> +void Pack<WebGLImageConversion::kDataFormatRA32F, + WebGLImageConversion::kAlphaDoNothing, + float, + float>(const float* source, + float* destination, + unsigned pixels_per_row) { + for (unsigned i = 0; i < pixels_per_row; ++i) { + destination[0] = source[0]; + destination[1] = source[3]; + source += 4; + destination += 2; + } +} + +template <> +void Pack<WebGLImageConversion::kDataFormatRA32F, + WebGLImageConversion::kAlphaDoPremultiply, + float, + float>(const float* source, + float* destination, + unsigned pixels_per_row) { + for (unsigned i = 0; i < pixels_per_row; ++i) { + float scale_factor = source[3]; + destination[0] = source[0] * scale_factor; + destination[1] = source[3]; + source += 4; + destination += 2; + } +} + +template <> +void Pack<WebGLImageConversion::kDataFormatRA32F, + WebGLImageConversion::kAlphaDoUnmultiply, + float, + float>(const float* source, + float* destination, + unsigned pixels_per_row) { + for (unsigned i = 0; i < pixels_per_row; ++i) { + float scale_factor = source[3] ? 1.0f / source[3] : 1.0f; + destination[0] = source[0] * scale_factor; + destination[1] = source[3]; + source += 4; + destination += 2; + } +} + +template <> +void Pack<WebGLImageConversion::kDataFormatRGBA16F, + WebGLImageConversion::kAlphaDoNothing, + float, + uint16_t>(const float* source, + uint16_t* destination, + unsigned pixels_per_row) { + for (unsigned i = 0; i < pixels_per_row; ++i) { + destination[0] = ConvertFloatToHalfFloat(source[0]); + destination[1] = ConvertFloatToHalfFloat(source[1]); + destination[2] = ConvertFloatToHalfFloat(source[2]); + destination[3] = ConvertFloatToHalfFloat(source[3]); + source += 4; + destination += 4; + } +} + +template <> +void Pack<WebGLImageConversion::kDataFormatRGBA16F, + WebGLImageConversion::kAlphaDoPremultiply, + float, + uint16_t>(const float* source, + uint16_t* destination, + unsigned pixels_per_row) { + for (unsigned i = 0; i < pixels_per_row; ++i) { + float scale_factor = source[3]; + destination[0] = ConvertFloatToHalfFloat(source[0] * scale_factor); + destination[1] = ConvertFloatToHalfFloat(source[1] * scale_factor); + destination[2] = ConvertFloatToHalfFloat(source[2] * scale_factor); + destination[3] = ConvertFloatToHalfFloat(source[3]); + source += 4; + destination += 4; + } +} + +template <> +void Pack<WebGLImageConversion::kDataFormatRGBA16F, + WebGLImageConversion::kAlphaDoUnmultiply, + float, + uint16_t>(const float* source, + uint16_t* destination, + unsigned pixels_per_row) { + for (unsigned i = 0; i < pixels_per_row; ++i) { + float scale_factor = source[3] ? 1.0f / source[3] : 1.0f; + destination[0] = ConvertFloatToHalfFloat(source[0] * scale_factor); + destination[1] = ConvertFloatToHalfFloat(source[1] * scale_factor); + destination[2] = ConvertFloatToHalfFloat(source[2] * scale_factor); + destination[3] = ConvertFloatToHalfFloat(source[3]); + source += 4; + destination += 4; + } +} + +template <> +void Pack<WebGLImageConversion::kDataFormatRGB16F, + WebGLImageConversion::kAlphaDoNothing, + float, + uint16_t>(const float* source, + uint16_t* destination, + unsigned pixels_per_row) { + for (unsigned i = 0; i < pixels_per_row; ++i) { + destination[0] = ConvertFloatToHalfFloat(source[0]); + destination[1] = ConvertFloatToHalfFloat(source[1]); + destination[2] = ConvertFloatToHalfFloat(source[2]); + source += 4; + destination += 3; + } +} + +template <> +void Pack<WebGLImageConversion::kDataFormatRGB16F, + WebGLImageConversion::kAlphaDoPremultiply, + float, + uint16_t>(const float* source, + uint16_t* destination, + unsigned pixels_per_row) { + for (unsigned i = 0; i < pixels_per_row; ++i) { + float scale_factor = source[3]; + destination[0] = ConvertFloatToHalfFloat(source[0] * scale_factor); + destination[1] = ConvertFloatToHalfFloat(source[1] * scale_factor); + destination[2] = ConvertFloatToHalfFloat(source[2] * scale_factor); + source += 4; + destination += 3; + } +} + +template <> +void Pack<WebGLImageConversion::kDataFormatRGB16F, + WebGLImageConversion::kAlphaDoUnmultiply, + float, + uint16_t>(const float* source, + uint16_t* destination, + unsigned pixels_per_row) { + for (unsigned i = 0; i < pixels_per_row; ++i) { + float scale_factor = source[3] ? 1.0f / source[3] : 1.0f; + destination[0] = ConvertFloatToHalfFloat(source[0] * scale_factor); + destination[1] = ConvertFloatToHalfFloat(source[1] * scale_factor); + destination[2] = ConvertFloatToHalfFloat(source[2] * scale_factor); + source += 4; + destination += 3; + } +} + +template <> +void Pack<WebGLImageConversion::kDataFormatRA16F, + WebGLImageConversion::kAlphaDoNothing, + float, + uint16_t>(const float* source, + uint16_t* destination, + unsigned pixels_per_row) { + for (unsigned i = 0; i < pixels_per_row; ++i) { + destination[0] = ConvertFloatToHalfFloat(source[0]); + destination[1] = ConvertFloatToHalfFloat(source[3]); + source += 4; + destination += 2; + } +} + +template <> +void Pack<WebGLImageConversion::kDataFormatRA16F, + WebGLImageConversion::kAlphaDoPremultiply, + float, + uint16_t>(const float* source, + uint16_t* destination, + unsigned pixels_per_row) { + for (unsigned i = 0; i < pixels_per_row; ++i) { + float scale_factor = source[3]; + destination[0] = ConvertFloatToHalfFloat(source[0] * scale_factor); + destination[1] = ConvertFloatToHalfFloat(source[3]); + source += 4; + destination += 2; + } +} + +template <> +void Pack<WebGLImageConversion::kDataFormatRA16F, + WebGLImageConversion::kAlphaDoUnmultiply, + float, + uint16_t>(const float* source, + uint16_t* destination, + unsigned pixels_per_row) { + for (unsigned i = 0; i < pixels_per_row; ++i) { + float scale_factor = source[3] ? 1.0f / source[3] : 1.0f; + destination[0] = ConvertFloatToHalfFloat(source[0] * scale_factor); + destination[1] = ConvertFloatToHalfFloat(source[3]); + source += 4; + destination += 2; + } +} + +template <> +void Pack<WebGLImageConversion::kDataFormatR16F, + WebGLImageConversion::kAlphaDoNothing, + float, + uint16_t>(const float* source, + uint16_t* destination, + unsigned pixels_per_row) { + for (unsigned i = 0; i < pixels_per_row; ++i) { + destination[0] = ConvertFloatToHalfFloat(source[0]); + source += 4; + destination += 1; + } +} + +template <> +void Pack<WebGLImageConversion::kDataFormatR16F, + WebGLImageConversion::kAlphaDoPremultiply, + float, + uint16_t>(const float* source, + uint16_t* destination, + unsigned pixels_per_row) { + for (unsigned i = 0; i < pixels_per_row; ++i) { + float scale_factor = source[3]; + destination[0] = ConvertFloatToHalfFloat(source[0] * scale_factor); + source += 4; + destination += 1; + } +} + +template <> +void Pack<WebGLImageConversion::kDataFormatR16F, + WebGLImageConversion::kAlphaDoUnmultiply, + float, + uint16_t>(const float* source, + uint16_t* destination, + unsigned pixels_per_row) { + for (unsigned i = 0; i < pixels_per_row; ++i) { + float scale_factor = source[3] ? 1.0f / source[3] : 1.0f; + destination[0] = ConvertFloatToHalfFloat(source[0] * scale_factor); + source += 4; + destination += 1; + } +} + +template <> +void Pack<WebGLImageConversion::kDataFormatA16F, + WebGLImageConversion::kAlphaDoNothing, + float, + uint16_t>(const float* source, + uint16_t* destination, + unsigned pixels_per_row) { + for (unsigned i = 0; i < pixels_per_row; ++i) { + destination[0] = ConvertFloatToHalfFloat(source[3]); + source += 4; + destination += 1; + } +} + +template <> +void Pack<WebGLImageConversion::kDataFormatRGBA8_S, + WebGLImageConversion::kAlphaDoPremultiply, + int8_t, + int8_t>(const int8_t* source, + int8_t* destination, + unsigned pixels_per_row) { + for (unsigned i = 0; i < pixels_per_row; ++i) { + destination[3] = ClampMin(source[3]); + float scale_factor = static_cast<float>(destination[3]) / kMaxInt8Value; + destination[0] = static_cast<int8_t>( + static_cast<float>(ClampMin(source[0])) * scale_factor); + destination[1] = static_cast<int8_t>( + static_cast<float>(ClampMin(source[1])) * scale_factor); + destination[2] = static_cast<int8_t>( + static_cast<float>(ClampMin(source[2])) * scale_factor); + source += 4; + destination += 4; + } +} + +template <> +void Pack<WebGLImageConversion::kDataFormatRGBA16, + WebGLImageConversion::kAlphaDoPremultiply, + uint16_t, + uint16_t>(const uint16_t* source, + uint16_t* destination, + unsigned pixels_per_row) { + for (unsigned i = 0; i < pixels_per_row; ++i) { + float scale_factor = static_cast<float>(source[3]) / kMaxUInt16Value; + destination[0] = + static_cast<uint16_t>(static_cast<float>(source[0]) * scale_factor); + destination[1] = + static_cast<uint16_t>(static_cast<float>(source[1]) * scale_factor); + destination[2] = + static_cast<uint16_t>(static_cast<float>(source[2]) * scale_factor); + destination[3] = source[3]; + source += 4; + destination += 4; + } +} + +template <> +void Pack<WebGLImageConversion::kDataFormatRGBA16_S, + WebGLImageConversion::kAlphaDoPremultiply, + int16_t, + int16_t>(const int16_t* source, + int16_t* destination, + unsigned pixels_per_row) { + for (unsigned i = 0; i < pixels_per_row; ++i) { + destination[3] = ClampMin(source[3]); + float scale_factor = static_cast<float>(destination[3]) / kMaxInt16Value; + destination[0] = static_cast<int16_t>( + static_cast<float>(ClampMin(source[0])) * scale_factor); + destination[1] = static_cast<int16_t>( + static_cast<float>(ClampMin(source[1])) * scale_factor); + destination[2] = static_cast<int16_t>( + static_cast<float>(ClampMin(source[2])) * scale_factor); + source += 4; + destination += 4; + } +} + +template <> +void Pack<WebGLImageConversion::kDataFormatRGBA32, + WebGLImageConversion::kAlphaDoPremultiply, + uint32_t, + uint32_t>(const uint32_t* source, + uint32_t* destination, + unsigned pixels_per_row) { + for (unsigned i = 0; i < pixels_per_row; ++i) { + double scale_factor = static_cast<double>(source[3]) / kMaxUInt32Value; + destination[0] = + static_cast<uint32_t>(static_cast<double>(source[0]) * scale_factor); + destination[1] = + static_cast<uint32_t>(static_cast<double>(source[1]) * scale_factor); + destination[2] = + static_cast<uint32_t>(static_cast<double>(source[2]) * scale_factor); + destination[3] = source[3]; + source += 4; + destination += 4; + } +} + +template <> +void Pack<WebGLImageConversion::kDataFormatRGBA32_S, + WebGLImageConversion::kAlphaDoPremultiply, + int32_t, + int32_t>(const int32_t* source, + int32_t* destination, + unsigned pixels_per_row) { + for (unsigned i = 0; i < pixels_per_row; ++i) { + destination[3] = ClampMin(source[3]); + double scale_factor = static_cast<double>(destination[3]) / kMaxInt32Value; + destination[0] = static_cast<int32_t>( + static_cast<double>(ClampMin(source[0])) * scale_factor); + destination[1] = static_cast<int32_t>( + static_cast<double>(ClampMin(source[1])) * scale_factor); + destination[2] = static_cast<int32_t>( + static_cast<double>(ClampMin(source[2])) * scale_factor); + source += 4; + destination += 4; + } +} + +template <> +void Pack<WebGLImageConversion::kDataFormatRGBA2_10_10_10, + WebGLImageConversion::kAlphaDoNothing, + float, + uint32_t>(const float* source, + uint32_t* destination, + unsigned pixels_per_row) { + for (unsigned i = 0; i < pixels_per_row; ++i) { + uint32_t r = static_cast<uint32_t>(source[0] * 1023.0f); + uint32_t g = static_cast<uint32_t>(source[1] * 1023.0f); + uint32_t b = static_cast<uint32_t>(source[2] * 1023.0f); + uint32_t a = static_cast<uint32_t>(source[3] * 3.0f); + destination[0] = (a << 30) | (b << 20) | (g << 10) | r; + source += 4; + destination += 1; + } +} + +template <> +void Pack<WebGLImageConversion::kDataFormatRGBA2_10_10_10, + WebGLImageConversion::kAlphaDoPremultiply, + float, + uint32_t>(const float* source, + uint32_t* destination, + unsigned pixels_per_row) { + for (unsigned i = 0; i < pixels_per_row; ++i) { + uint32_t r = static_cast<uint32_t>(source[0] * source[3] * 1023.0f); + uint32_t g = static_cast<uint32_t>(source[1] * source[3] * 1023.0f); + uint32_t b = static_cast<uint32_t>(source[2] * source[3] * 1023.0f); + uint32_t a = static_cast<uint32_t>(source[3] * 3.0f); + destination[0] = (a << 30) | (b << 20) | (g << 10) | r; + source += 4; + destination += 1; + } +} + +template <> +void Pack<WebGLImageConversion::kDataFormatRGBA2_10_10_10, + WebGLImageConversion::kAlphaDoUnmultiply, + float, + uint32_t>(const float* source, + uint32_t* destination, + unsigned pixels_per_row) { + for (unsigned i = 0; i < pixels_per_row; ++i) { + float scale_factor = source[3] ? 1023.0f / source[3] : 1023.0f; + uint32_t r = static_cast<uint32_t>(source[0] * scale_factor); + uint32_t g = static_cast<uint32_t>(source[1] * scale_factor); + uint32_t b = static_cast<uint32_t>(source[2] * scale_factor); + uint32_t a = static_cast<uint32_t>(source[3] * 3.0f); + destination[0] = (a << 30) | (b << 20) | (g << 10) | r; + source += 4; + destination += 1; + } +} + +template <> +void Pack<WebGLImageConversion::kDataFormatRG8, + WebGLImageConversion::kAlphaDoNothing, + uint8_t, + uint8_t>(const uint8_t* source, + uint8_t* destination, + unsigned pixels_per_row) { + for (unsigned i = 0; i < pixels_per_row; ++i) { + destination[0] = source[0]; + destination[1] = source[1]; + source += 4; + destination += 2; + } +} + +template <> +void Pack<WebGLImageConversion::kDataFormatRG8, + WebGLImageConversion::kAlphaDoPremultiply, + uint8_t, + uint8_t>(const uint8_t* source, + uint8_t* destination, + unsigned pixels_per_row) { + for (unsigned i = 0; i < pixels_per_row; ++i) { + float scale_factor = static_cast<float>(source[3]) / kMaxUInt8Value; + destination[0] = + static_cast<uint8_t>(static_cast<float>(source[0]) * scale_factor); + destination[1] = + static_cast<uint8_t>(static_cast<float>(source[1]) * scale_factor); + source += 4; + destination += 2; + } +} + +// FIXME: this routine is lossy and must be removed. +template <> +void Pack<WebGLImageConversion::kDataFormatRG8, + WebGLImageConversion::kAlphaDoUnmultiply, + uint8_t, + uint8_t>(const uint8_t* source, + uint8_t* destination, + unsigned pixels_per_row) { + for (unsigned i = 0; i < pixels_per_row; ++i) { + float scale_factor = + source[3] ? kMaxUInt8Value / static_cast<float>(source[3]) : 1.0f; + destination[0] = + static_cast<uint8_t>(static_cast<float>(source[0]) * scale_factor); + destination[1] = + static_cast<uint8_t>(static_cast<float>(source[1]) * scale_factor); + source += 4; + destination += 2; + } +} + +template <> +void Pack<WebGLImageConversion::kDataFormatRG16F, + WebGLImageConversion::kAlphaDoNothing, + float, + uint16_t>(const float* source, + uint16_t* destination, + unsigned pixels_per_row) { + for (unsigned i = 0; i < pixels_per_row; ++i) { + destination[0] = ConvertFloatToHalfFloat(source[0]); + destination[1] = ConvertFloatToHalfFloat(source[1]); + source += 4; + destination += 2; + } +} + +template <> +void Pack<WebGLImageConversion::kDataFormatRG16F, + WebGLImageConversion::kAlphaDoPremultiply, + float, + uint16_t>(const float* source, + uint16_t* destination, + unsigned pixels_per_row) { + for (unsigned i = 0; i < pixels_per_row; ++i) { + float scale_factor = source[3]; + destination[0] = ConvertFloatToHalfFloat(source[0] * scale_factor); + destination[1] = ConvertFloatToHalfFloat(source[1] * scale_factor); + source += 4; + destination += 2; + } +} + +// FIXME: this routine is lossy and must be removed. +template <> +void Pack<WebGLImageConversion::kDataFormatRG16F, + WebGLImageConversion::kAlphaDoUnmultiply, + float, + uint16_t>(const float* source, + uint16_t* destination, + unsigned pixels_per_row) { + for (unsigned i = 0; i < pixels_per_row; ++i) { + float scale_factor = source[3] ? 1.0f / source[3] : 1.0f; + destination[0] = ConvertFloatToHalfFloat(source[0] * scale_factor); + destination[1] = ConvertFloatToHalfFloat(source[1] * scale_factor); + source += 4; + destination += 2; + } +} + +template <> +void Pack<WebGLImageConversion::kDataFormatRG32F, + WebGLImageConversion::kAlphaDoNothing, + float, + float>(const float* source, + float* destination, + unsigned pixels_per_row) { + for (unsigned i = 0; i < pixels_per_row; ++i) { + destination[0] = source[0]; + destination[1] = source[1]; + source += 4; + destination += 2; + } +} + +template <> +void Pack<WebGLImageConversion::kDataFormatRG32F, + WebGLImageConversion::kAlphaDoPremultiply, + float, + float>(const float* source, + float* destination, + unsigned pixels_per_row) { + for (unsigned i = 0; i < pixels_per_row; ++i) { + float scale_factor = source[3]; + destination[0] = source[0] * scale_factor; + destination[1] = source[1] * scale_factor; + source += 4; + destination += 2; + } +} + +// FIXME: this routine is lossy and must be removed. +template <> +void Pack<WebGLImageConversion::kDataFormatRG32F, + WebGLImageConversion::kAlphaDoUnmultiply, + float, + float>(const float* source, + float* destination, + unsigned pixels_per_row) { + for (unsigned i = 0; i < pixels_per_row; ++i) { + float scale_factor = source[3] ? 1.0f / source[3] : 1.0f; + destination[0] = source[0] * scale_factor; + destination[1] = source[1] * scale_factor; + source += 4; + destination += 2; + } +} + +bool HasAlpha(int format) { + return format == WebGLImageConversion::kDataFormatA8 || + format == WebGLImageConversion::kDataFormatA16F || + format == WebGLImageConversion::kDataFormatA32F || + format == WebGLImageConversion::kDataFormatRA8 || + format == WebGLImageConversion::kDataFormatAR8 || + format == WebGLImageConversion::kDataFormatRA16F || + format == WebGLImageConversion::kDataFormatRA32F || + format == WebGLImageConversion::kDataFormatRGBA8 || + format == WebGLImageConversion::kDataFormatBGRA8 || + format == WebGLImageConversion::kDataFormatARGB8 || + format == WebGLImageConversion::kDataFormatABGR8 || + format == WebGLImageConversion::kDataFormatRGBA16F || + format == WebGLImageConversion::kDataFormatRGBA32F || + format == WebGLImageConversion::kDataFormatRGBA4444 || + format == WebGLImageConversion::kDataFormatRGBA5551 || + format == WebGLImageConversion::kDataFormatRGBA8_S || + format == WebGLImageConversion::kDataFormatRGBA16 || + format == WebGLImageConversion::kDataFormatRGBA16_S || + format == WebGLImageConversion::kDataFormatRGBA32 || + format == WebGLImageConversion::kDataFormatRGBA32_S || + format == WebGLImageConversion::kDataFormatRGBA2_10_10_10; +} + +bool HasColor(int format) { + return format == WebGLImageConversion::kDataFormatRGBA8 || + format == WebGLImageConversion::kDataFormatRGBA16F || + format == WebGLImageConversion::kDataFormatRGBA32F || + format == WebGLImageConversion::kDataFormatRGB8 || + format == WebGLImageConversion::kDataFormatRGB16F || + format == WebGLImageConversion::kDataFormatRGB32F || + format == WebGLImageConversion::kDataFormatBGR8 || + format == WebGLImageConversion::kDataFormatBGRA8 || + format == WebGLImageConversion::kDataFormatARGB8 || + format == WebGLImageConversion::kDataFormatABGR8 || + format == WebGLImageConversion::kDataFormatRGBA5551 || + format == WebGLImageConversion::kDataFormatRGBA4444 || + format == WebGLImageConversion::kDataFormatRGB565 || + format == WebGLImageConversion::kDataFormatR8 || + format == WebGLImageConversion::kDataFormatR16F || + format == WebGLImageConversion::kDataFormatR32F || + format == WebGLImageConversion::kDataFormatRA8 || + format == WebGLImageConversion::kDataFormatRA16F || + format == WebGLImageConversion::kDataFormatRA32F || + format == WebGLImageConversion::kDataFormatAR8 || + format == WebGLImageConversion::kDataFormatRGBA8_S || + format == WebGLImageConversion::kDataFormatRGBA16 || + format == WebGLImageConversion::kDataFormatRGBA16_S || + format == WebGLImageConversion::kDataFormatRGBA32 || + format == WebGLImageConversion::kDataFormatRGBA32_S || + format == WebGLImageConversion::kDataFormatRGBA2_10_10_10 || + format == WebGLImageConversion::kDataFormatRGB8_S || + format == WebGLImageConversion::kDataFormatRGB16 || + format == WebGLImageConversion::kDataFormatRGB16_S || + format == WebGLImageConversion::kDataFormatRGB32 || + format == WebGLImageConversion::kDataFormatRGB32_S || + format == WebGLImageConversion::kDataFormatRGB10F11F11F || + format == WebGLImageConversion::kDataFormatRGB5999 || + format == WebGLImageConversion::kDataFormatRG8 || + format == WebGLImageConversion::kDataFormatRG8_S || + format == WebGLImageConversion::kDataFormatRG16 || + format == WebGLImageConversion::kDataFormatRG16_S || + format == WebGLImageConversion::kDataFormatRG32 || + format == WebGLImageConversion::kDataFormatRG32_S || + format == WebGLImageConversion::kDataFormatRG16F || + format == WebGLImageConversion::kDataFormatRG32F || + format == WebGLImageConversion::kDataFormatR8_S || + format == WebGLImageConversion::kDataFormatR16 || + format == WebGLImageConversion::kDataFormatR16_S || + format == WebGLImageConversion::kDataFormatR32 || + format == WebGLImageConversion::kDataFormatR32_S; +} + +template <int Format> +struct IsInt8Format { + STATIC_ONLY(IsInt8Format); + static const bool value = + Format == WebGLImageConversion::kDataFormatRGBA8_S || + Format == WebGLImageConversion::kDataFormatRGB8_S || + Format == WebGLImageConversion::kDataFormatRG8_S || + Format == WebGLImageConversion::kDataFormatR8_S; +}; + +template <int Format> +struct IsInt16Format { + STATIC_ONLY(IsInt16Format); + static const bool value = + Format == WebGLImageConversion::kDataFormatRGBA16_S || + Format == WebGLImageConversion::kDataFormatRGB16_S || + Format == WebGLImageConversion::kDataFormatRG16_S || + Format == WebGLImageConversion::kDataFormatR16_S; +}; + +template <int Format> +struct IsInt32Format { + STATIC_ONLY(IsInt32Format); + static const bool value = + Format == WebGLImageConversion::kDataFormatRGBA32_S || + Format == WebGLImageConversion::kDataFormatRGB32_S || + Format == WebGLImageConversion::kDataFormatRG32_S || + Format == WebGLImageConversion::kDataFormatR32_S; +}; + +template <int Format> +struct IsUInt8Format { + STATIC_ONLY(IsUInt8Format); + static const bool value = Format == WebGLImageConversion::kDataFormatRGBA8 || + Format == WebGLImageConversion::kDataFormatRGB8 || + Format == WebGLImageConversion::kDataFormatRG8 || + Format == WebGLImageConversion::kDataFormatR8 || + Format == WebGLImageConversion::kDataFormatBGRA8 || + Format == WebGLImageConversion::kDataFormatBGR8 || + Format == WebGLImageConversion::kDataFormatARGB8 || + Format == WebGLImageConversion::kDataFormatABGR8 || + Format == WebGLImageConversion::kDataFormatRA8 || + Format == WebGLImageConversion::kDataFormatAR8 || + Format == WebGLImageConversion::kDataFormatA8; +}; + +template <int Format> +struct IsUInt16Format { + STATIC_ONLY(IsUInt16Format); + static const bool value = Format == WebGLImageConversion::kDataFormatRGBA16 || + Format == WebGLImageConversion::kDataFormatRGB16 || + Format == WebGLImageConversion::kDataFormatRG16 || + Format == WebGLImageConversion::kDataFormatR16; +}; + +template <int Format> +struct IsUInt32Format { + STATIC_ONLY(IsUInt32Format); + static const bool value = Format == WebGLImageConversion::kDataFormatRGBA32 || + Format == WebGLImageConversion::kDataFormatRGB32 || + Format == WebGLImageConversion::kDataFormatRG32 || + Format == WebGLImageConversion::kDataFormatR32; +}; + +template <int Format> +struct IsFloatFormat { + STATIC_ONLY(IsFloatFormat); + static const bool value = + Format == WebGLImageConversion::kDataFormatRGBA32F || + Format == WebGLImageConversion::kDataFormatRGB32F || + Format == WebGLImageConversion::kDataFormatRA32F || + Format == WebGLImageConversion::kDataFormatR32F || + Format == WebGLImageConversion::kDataFormatA32F || + Format == WebGLImageConversion::kDataFormatRG32F; +}; + +template <int Format> +struct IsHalfFloatFormat { + STATIC_ONLY(IsHalfFloatFormat); + static const bool value = + Format == WebGLImageConversion::kDataFormatRGBA16F || + Format == WebGLImageConversion::kDataFormatRGB16F || + Format == WebGLImageConversion::kDataFormatRA16F || + Format == WebGLImageConversion::kDataFormatR16F || + Format == WebGLImageConversion::kDataFormatA16F || + Format == WebGLImageConversion::kDataFormatRG16F; +}; + +template <int Format> +struct Is32bppFormat { + STATIC_ONLY(Is32bppFormat); + static const bool value = + Format == WebGLImageConversion::kDataFormatRGBA2_10_10_10 || + Format == WebGLImageConversion::kDataFormatRGB5999 || + Format == WebGLImageConversion::kDataFormatRGB10F11F11F; +}; + +template <int Format> +struct Is16bppFormat { + STATIC_ONLY(Is16bppFormat); + static const bool value = + Format == WebGLImageConversion::kDataFormatRGBA5551 || + Format == WebGLImageConversion::kDataFormatRGBA4444 || + Format == WebGLImageConversion::kDataFormatRGB565; +}; + +template <int Format, + bool IsInt8Format = IsInt8Format<Format>::value, + bool IsUInt8Format = IsUInt8Format<Format>::value, + bool IsInt16Format = IsInt16Format<Format>::value, + bool IsUInt16Format = IsUInt16Format<Format>::value, + bool IsInt32Format = IsInt32Format<Format>::value, + bool IsUInt32Format = IsUInt32Format<Format>::value, + bool IsFloat = IsFloatFormat<Format>::value, + bool IsHalfFloat = IsHalfFloatFormat<Format>::value, + bool Is16bpp = Is16bppFormat<Format>::value, + bool Is32bpp = Is32bppFormat<Format>::value> +struct DataTypeForFormat { + STATIC_ONLY(DataTypeForFormat); + typedef double Type; // Use a type that's not used in unpack/pack. +}; + +template <int Format> +struct DataTypeForFormat<Format, + true, + false, + false, + false, + false, + false, + false, + false, + false, + false> { + STATIC_ONLY(DataTypeForFormat); + typedef int8_t Type; +}; + +template <int Format> +struct DataTypeForFormat<Format, + false, + true, + false, + false, + false, + false, + false, + false, + false, + false> { + STATIC_ONLY(DataTypeForFormat); + typedef uint8_t Type; +}; + +template <int Format> +struct DataTypeForFormat<Format, + false, + false, + true, + false, + false, + false, + false, + false, + false, + false> { + STATIC_ONLY(DataTypeForFormat); + typedef int16_t Type; +}; + +template <int Format> +struct DataTypeForFormat<Format, + false, + false, + false, + true, + false, + false, + false, + false, + false, + false> { + STATIC_ONLY(DataTypeForFormat); + typedef uint16_t Type; +}; + +template <int Format> +struct DataTypeForFormat<Format, + false, + false, + false, + false, + true, + false, + false, + false, + false, + false> { + STATIC_ONLY(DataTypeForFormat); + typedef int32_t Type; +}; + +template <int Format> +struct DataTypeForFormat<Format, + false, + false, + false, + false, + false, + true, + false, + false, + false, + false> { + STATIC_ONLY(DataTypeForFormat); + typedef uint32_t Type; +}; + +template <int Format> +struct DataTypeForFormat<Format, + false, + false, + false, + false, + false, + false, + true, + false, + false, + false> { + STATIC_ONLY(DataTypeForFormat); + typedef float Type; +}; + +template <int Format> +struct DataTypeForFormat<Format, + false, + false, + false, + false, + false, + false, + false, + true, + false, + false> { + STATIC_ONLY(DataTypeForFormat); + typedef uint16_t Type; +}; + +template <int Format> +struct DataTypeForFormat<Format, + false, + false, + false, + false, + false, + false, + false, + false, + true, + false> { + STATIC_ONLY(DataTypeForFormat); + typedef uint16_t Type; +}; + +template <int Format> +struct DataTypeForFormat<Format, + false, + false, + false, + false, + false, + false, + false, + false, + false, + true> { + STATIC_ONLY(DataTypeForFormat); + typedef uint32_t Type; +}; + +template <int Format> +struct UsesFloatIntermediateFormat { + STATIC_ONLY(UsesFloatIntermediateFormat); + static const bool value = + IsFloatFormat<Format>::value || IsHalfFloatFormat<Format>::value || + Format == WebGLImageConversion::kDataFormatRGBA2_10_10_10 || + Format == WebGLImageConversion::kDataFormatRGB10F11F11F || + Format == WebGLImageConversion::kDataFormatRGB5999; +}; + +template <int Format> +struct IntermediateFormat { + STATIC_ONLY(IntermediateFormat); + static const int value = + UsesFloatIntermediateFormat<Format>::value + ? WebGLImageConversion::kDataFormatRGBA32F + : IsInt32Format<Format>::value + ? WebGLImageConversion::kDataFormatRGBA32_S + : IsUInt32Format<Format>::value + ? WebGLImageConversion::kDataFormatRGBA32 + : IsInt16Format<Format>::value + ? WebGLImageConversion::kDataFormatRGBA16_S + : (IsUInt16Format<Format>::value || + Is32bppFormat<Format>::value) + ? WebGLImageConversion::kDataFormatRGBA16 + : IsInt8Format<Format>::value + ? WebGLImageConversion:: + kDataFormatRGBA8_S + : WebGLImageConversion:: + kDataFormatRGBA8; +}; + +unsigned TexelBytesForFormat(WebGLImageConversion::DataFormat format) { + switch (format) { + case WebGLImageConversion::kDataFormatR8: + case WebGLImageConversion::kDataFormatR8_S: + case WebGLImageConversion::kDataFormatA8: + return 1; + case WebGLImageConversion::kDataFormatRG8: + case WebGLImageConversion::kDataFormatRG8_S: + case WebGLImageConversion::kDataFormatRA8: + case WebGLImageConversion::kDataFormatAR8: + case WebGLImageConversion::kDataFormatRGBA5551: + case WebGLImageConversion::kDataFormatRGBA4444: + case WebGLImageConversion::kDataFormatRGB565: + case WebGLImageConversion::kDataFormatA16F: + case WebGLImageConversion::kDataFormatR16: + case WebGLImageConversion::kDataFormatR16_S: + case WebGLImageConversion::kDataFormatR16F: + case WebGLImageConversion::kDataFormatD16: + return 2; + case WebGLImageConversion::kDataFormatRGB8: + case WebGLImageConversion::kDataFormatRGB8_S: + case WebGLImageConversion::kDataFormatBGR8: + return 3; + case WebGLImageConversion::kDataFormatRGBA8: + case WebGLImageConversion::kDataFormatRGBA8_S: + case WebGLImageConversion::kDataFormatARGB8: + case WebGLImageConversion::kDataFormatABGR8: + case WebGLImageConversion::kDataFormatBGRA8: + case WebGLImageConversion::kDataFormatR32: + case WebGLImageConversion::kDataFormatR32_S: + case WebGLImageConversion::kDataFormatR32F: + case WebGLImageConversion::kDataFormatA32F: + case WebGLImageConversion::kDataFormatRA16F: + case WebGLImageConversion::kDataFormatRGBA2_10_10_10: + case WebGLImageConversion::kDataFormatRGB10F11F11F: + case WebGLImageConversion::kDataFormatRGB5999: + case WebGLImageConversion::kDataFormatRG16: + case WebGLImageConversion::kDataFormatRG16_S: + case WebGLImageConversion::kDataFormatRG16F: + case WebGLImageConversion::kDataFormatD32: + case WebGLImageConversion::kDataFormatD32F: + case WebGLImageConversion::kDataFormatDS24_8: + return 4; + case WebGLImageConversion::kDataFormatRGB16: + case WebGLImageConversion::kDataFormatRGB16_S: + case WebGLImageConversion::kDataFormatRGB16F: + return 6; + case WebGLImageConversion::kDataFormatRGBA16: + case WebGLImageConversion::kDataFormatRGBA16_S: + case WebGLImageConversion::kDataFormatRA32F: + case WebGLImageConversion::kDataFormatRGBA16F: + case WebGLImageConversion::kDataFormatRG32: + case WebGLImageConversion::kDataFormatRG32_S: + case WebGLImageConversion::kDataFormatRG32F: + return 8; + case WebGLImageConversion::kDataFormatRGB32: + case WebGLImageConversion::kDataFormatRGB32_S: + case WebGLImageConversion::kDataFormatRGB32F: + return 12; + case WebGLImageConversion::kDataFormatRGBA32: + case WebGLImageConversion::kDataFormatRGBA32_S: + case WebGLImageConversion::kDataFormatRGBA32F: + return 16; + default: + return 0; + } +} + +/* END CODE SHARED WITH MOZILLA FIREFOX */ + +class FormatConverter { + STACK_ALLOCATED(); + + public: + FormatConverter(const IntRect& source_data_sub_rectangle, + int depth, + int unpack_image_height, + const void* src_start, + void* dst_start, + int src_stride, + int src_row_offset, + int dst_stride) + : src_sub_rectangle_(source_data_sub_rectangle), + depth_(depth), + unpack_image_height_(unpack_image_height), + src_start_(src_start), + dst_start_(dst_start), + src_stride_(src_stride), + src_row_offset_(src_row_offset), + dst_stride_(dst_stride), + success_(false) { + const unsigned kMaxNumberOfComponents = 4; + const unsigned kMaxBytesPerComponent = 4; + unpacked_intermediate_src_data_ = std::make_unique<uint8_t[]>( + src_sub_rectangle_.Width() * kMaxNumberOfComponents * + kMaxBytesPerComponent); + DCHECK(unpacked_intermediate_src_data_.get()); + } + + void Convert(WebGLImageConversion::DataFormat src_format, + WebGLImageConversion::DataFormat dst_format, + WebGLImageConversion::AlphaOp); + bool Success() const { return success_; } + + private: + template <WebGLImageConversion::DataFormat SrcFormat> + void Convert(WebGLImageConversion::DataFormat dst_format, + WebGLImageConversion::AlphaOp); + + template <WebGLImageConversion::DataFormat SrcFormat, + WebGLImageConversion::DataFormat DstFormat> + void Convert(WebGLImageConversion::AlphaOp); + + template <WebGLImageConversion::DataFormat SrcFormat, + WebGLImageConversion::DataFormat DstFormat, + WebGLImageConversion::AlphaOp alphaOp> + void Convert(); + + const IntRect& src_sub_rectangle_; + const int depth_; + const int unpack_image_height_; + const void* const src_start_; + void* const dst_start_; + const int src_stride_, src_row_offset_, dst_stride_; + bool success_; + std::unique_ptr<uint8_t[]> unpacked_intermediate_src_data_; +}; + +void FormatConverter::Convert(WebGLImageConversion::DataFormat src_format, + WebGLImageConversion::DataFormat dst_format, + WebGLImageConversion::AlphaOp alpha_op) { +#define FORMATCONVERTER_CASE_SRCFORMAT(SrcFormat) \ + case SrcFormat: \ + return Convert<SrcFormat>(dst_format, alpha_op); + + switch (src_format) { + FORMATCONVERTER_CASE_SRCFORMAT(WebGLImageConversion::kDataFormatRA8) + FORMATCONVERTER_CASE_SRCFORMAT(WebGLImageConversion::kDataFormatRA32F) + FORMATCONVERTER_CASE_SRCFORMAT(WebGLImageConversion::kDataFormatRGBA8) + FORMATCONVERTER_CASE_SRCFORMAT(WebGLImageConversion::kDataFormatARGB8) + FORMATCONVERTER_CASE_SRCFORMAT(WebGLImageConversion::kDataFormatABGR8) + FORMATCONVERTER_CASE_SRCFORMAT(WebGLImageConversion::kDataFormatAR8) + FORMATCONVERTER_CASE_SRCFORMAT(WebGLImageConversion::kDataFormatBGRA8) + FORMATCONVERTER_CASE_SRCFORMAT(WebGLImageConversion::kDataFormatRGBA5551) + FORMATCONVERTER_CASE_SRCFORMAT(WebGLImageConversion::kDataFormatRGBA4444) + FORMATCONVERTER_CASE_SRCFORMAT(WebGLImageConversion::kDataFormatRGBA32F) + FORMATCONVERTER_CASE_SRCFORMAT( + WebGLImageConversion::kDataFormatRGBA2_10_10_10) + default: + NOTREACHED(); + } +#undef FORMATCONVERTER_CASE_SRCFORMAT +} + +template <WebGLImageConversion::DataFormat SrcFormat> +void FormatConverter::Convert(WebGLImageConversion::DataFormat dst_format, + WebGLImageConversion::AlphaOp alpha_op) { +#define FORMATCONVERTER_CASE_DSTFORMAT(DstFormat) \ + case DstFormat: \ + return Convert<SrcFormat, DstFormat>(alpha_op); + + switch (dst_format) { + FORMATCONVERTER_CASE_DSTFORMAT(WebGLImageConversion::kDataFormatR8) + FORMATCONVERTER_CASE_DSTFORMAT(WebGLImageConversion::kDataFormatR16F) + FORMATCONVERTER_CASE_DSTFORMAT(WebGLImageConversion::kDataFormatR32F) + FORMATCONVERTER_CASE_DSTFORMAT(WebGLImageConversion::kDataFormatA8) + FORMATCONVERTER_CASE_DSTFORMAT(WebGLImageConversion::kDataFormatA16F) + FORMATCONVERTER_CASE_DSTFORMAT(WebGLImageConversion::kDataFormatA32F) + FORMATCONVERTER_CASE_DSTFORMAT(WebGLImageConversion::kDataFormatRA8) + FORMATCONVERTER_CASE_DSTFORMAT(WebGLImageConversion::kDataFormatRA16F) + FORMATCONVERTER_CASE_DSTFORMAT(WebGLImageConversion::kDataFormatRA32F) + FORMATCONVERTER_CASE_DSTFORMAT(WebGLImageConversion::kDataFormatRGB8) + FORMATCONVERTER_CASE_DSTFORMAT(WebGLImageConversion::kDataFormatRGB565) + FORMATCONVERTER_CASE_DSTFORMAT(WebGLImageConversion::kDataFormatRGB16F) + FORMATCONVERTER_CASE_DSTFORMAT(WebGLImageConversion::kDataFormatRGB32F) + FORMATCONVERTER_CASE_DSTFORMAT(WebGLImageConversion::kDataFormatRGBA8) + FORMATCONVERTER_CASE_DSTFORMAT(WebGLImageConversion::kDataFormatRGBA5551) + FORMATCONVERTER_CASE_DSTFORMAT(WebGLImageConversion::kDataFormatRGBA4444) + FORMATCONVERTER_CASE_DSTFORMAT(WebGLImageConversion::kDataFormatRGBA16F) + FORMATCONVERTER_CASE_DSTFORMAT(WebGLImageConversion::kDataFormatRGBA32F) + FORMATCONVERTER_CASE_DSTFORMAT(WebGLImageConversion::kDataFormatRGBA8_S) + FORMATCONVERTER_CASE_DSTFORMAT(WebGLImageConversion::kDataFormatRGBA16) + FORMATCONVERTER_CASE_DSTFORMAT(WebGLImageConversion::kDataFormatRGBA16_S) + FORMATCONVERTER_CASE_DSTFORMAT(WebGLImageConversion::kDataFormatRGBA32) + FORMATCONVERTER_CASE_DSTFORMAT(WebGLImageConversion::kDataFormatRGBA32_S) + FORMATCONVERTER_CASE_DSTFORMAT( + WebGLImageConversion::kDataFormatRGBA2_10_10_10) + FORMATCONVERTER_CASE_DSTFORMAT(WebGLImageConversion::kDataFormatRG8) + FORMATCONVERTER_CASE_DSTFORMAT(WebGLImageConversion::kDataFormatRG16F) + FORMATCONVERTER_CASE_DSTFORMAT(WebGLImageConversion::kDataFormatRG32F) + default: + NOTREACHED(); + } + +#undef FORMATCONVERTER_CASE_DSTFORMAT +} + +template <WebGLImageConversion::DataFormat SrcFormat, + WebGLImageConversion::DataFormat DstFormat> +void FormatConverter::Convert(WebGLImageConversion::AlphaOp alpha_op) { +#define FORMATCONVERTER_CASE_ALPHAOP(alphaOp) \ + case alphaOp: \ + return Convert<SrcFormat, DstFormat, alphaOp>(); + + switch (alpha_op) { + FORMATCONVERTER_CASE_ALPHAOP(WebGLImageConversion::kAlphaDoNothing) + FORMATCONVERTER_CASE_ALPHAOP(WebGLImageConversion::kAlphaDoPremultiply) + FORMATCONVERTER_CASE_ALPHAOP(WebGLImageConversion::kAlphaDoUnmultiply) + default: + NOTREACHED(); + } +#undef FORMATCONVERTER_CASE_ALPHAOP +} + +template <int Format> +struct SupportsConversionFromDomElements { + STATIC_ONLY(SupportsConversionFromDomElements); + static const bool value = + Format == WebGLImageConversion::kDataFormatRGBA8 || + Format == WebGLImageConversion::kDataFormatRGB8 || + Format == WebGLImageConversion::kDataFormatRG8 || + Format == WebGLImageConversion::kDataFormatRA8 || + Format == WebGLImageConversion::kDataFormatR8 || + Format == WebGLImageConversion::kDataFormatRGBA32F || + Format == WebGLImageConversion::kDataFormatRGB32F || + Format == WebGLImageConversion::kDataFormatRG32F || + Format == WebGLImageConversion::kDataFormatRA32F || + Format == WebGLImageConversion::kDataFormatR32F || + Format == WebGLImageConversion::kDataFormatRGBA16F || + Format == WebGLImageConversion::kDataFormatRGB16F || + Format == WebGLImageConversion::kDataFormatRG16F || + Format == WebGLImageConversion::kDataFormatRA16F || + Format == WebGLImageConversion::kDataFormatR16F || + Format == WebGLImageConversion::kDataFormatRGBA5551 || + Format == WebGLImageConversion::kDataFormatRGBA4444 || + Format == WebGLImageConversion::kDataFormatRGB565 || + Format == WebGLImageConversion::kDataFormatRGBA2_10_10_10; +}; + +template <WebGLImageConversion::DataFormat SrcFormat, + WebGLImageConversion::DataFormat DstFormat, + WebGLImageConversion::AlphaOp alphaOp> +void FormatConverter::Convert() { + // Many instantiations of this template function will never be entered, so we + // try to return immediately in these cases to avoid generating useless code. + if (SrcFormat == DstFormat && + alphaOp == WebGLImageConversion::kAlphaDoNothing) { + NOTREACHED(); + return; + } + if (!IsFloatFormat<DstFormat>::value && IsFloatFormat<SrcFormat>::value) { + NOTREACHED(); + return; + } + + // Only textures uploaded from DOM elements or ImageData can allow DstFormat + // != SrcFormat. + const bool src_format_comes_from_dom_element_or_image_data = + WebGLImageConversion::SrcFormatComeFromDOMElementOrImageData(SrcFormat); + if (!src_format_comes_from_dom_element_or_image_data && + SrcFormat != DstFormat) { + NOTREACHED(); + return; + } + // Likewise, only textures uploaded from DOM elements or ImageData can + // possibly need to be unpremultiplied. + if (!src_format_comes_from_dom_element_or_image_data && + alphaOp == WebGLImageConversion::kAlphaDoUnmultiply) { + NOTREACHED(); + return; + } + if (src_format_comes_from_dom_element_or_image_data && + alphaOp == WebGLImageConversion::kAlphaDoUnmultiply && + !SupportsConversionFromDomElements<DstFormat>::value) { + NOTREACHED(); + return; + } + if ((!HasAlpha(SrcFormat) || !HasColor(SrcFormat) || !HasColor(DstFormat)) && + alphaOp != WebGLImageConversion::kAlphaDoNothing) { + NOTREACHED(); + return; + } + // If converting DOM element data to UNSIGNED_INT_5_9_9_9_REV or + // UNSIGNED_INT_10F_11F_11F_REV, we should always switch to FLOAT instead to + // avoid unpacking/packing these two types. + if (src_format_comes_from_dom_element_or_image_data && + SrcFormat != DstFormat && + (DstFormat == WebGLImageConversion::kDataFormatRGB5999 || + DstFormat == WebGLImageConversion::kDataFormatRGB10F11F11F)) { + NOTREACHED(); + return; + } + + typedef typename DataTypeForFormat<SrcFormat>::Type SrcType; + typedef typename DataTypeForFormat<DstFormat>::Type DstType; + const int kIntermFormat = IntermediateFormat<DstFormat>::value; + typedef typename DataTypeForFormat<kIntermFormat>::Type IntermType; + const ptrdiff_t src_stride_in_elements = src_stride_ / sizeof(SrcType); + const ptrdiff_t dst_stride_in_elements = dst_stride_ / sizeof(DstType); + const bool kTrivialUnpack = SrcFormat == kIntermFormat; + const bool kTrivialPack = DstFormat == kIntermFormat && + alphaOp == WebGLImageConversion::kAlphaDoNothing; + DCHECK(!kTrivialUnpack || !kTrivialPack); + + const SrcType* src_row_start = + static_cast<const SrcType*>(static_cast<const void*>( + static_cast<const uint8_t*>(src_start_) + + ((src_stride_ * src_sub_rectangle_.Y()) + src_row_offset_))); + + // If packing multiple images into a 3D texture, and flipY is true, + // then the sub-rectangle is pointing at the start of the + // "bottommost" of those images. Since the source pointer strides in + // the positive direction, we need to back it up to point at the + // last, or "topmost", of these images. + if (dst_stride_ < 0 && depth_ > 1) { + src_row_start -= + (depth_ - 1) * src_stride_in_elements * unpack_image_height_; + } + + DstType* dst_row_start = static_cast<DstType*>(dst_start_); + if (kTrivialUnpack) { + for (int d = 0; d < depth_; ++d) { + for (int i = 0; i < src_sub_rectangle_.Height(); ++i) { + Pack<DstFormat, alphaOp>(src_row_start, dst_row_start, + src_sub_rectangle_.Width()); + src_row_start += src_stride_in_elements; + dst_row_start += dst_stride_in_elements; + } + src_row_start += src_stride_in_elements * + (unpack_image_height_ - src_sub_rectangle_.Height()); + } + } else if (kTrivialPack) { + for (int d = 0; d < depth_; ++d) { + for (int i = 0; i < src_sub_rectangle_.Height(); ++i) { + Unpack<SrcFormat>(src_row_start, dst_row_start, + src_sub_rectangle_.Width()); + src_row_start += src_stride_in_elements; + dst_row_start += dst_stride_in_elements; + } + src_row_start += src_stride_in_elements * + (unpack_image_height_ - src_sub_rectangle_.Height()); + } + } else { + for (int d = 0; d < depth_; ++d) { + for (int i = 0; i < src_sub_rectangle_.Height(); ++i) { + Unpack<SrcFormat>(src_row_start, + reinterpret_cast<IntermType*>( + unpacked_intermediate_src_data_.get()), + src_sub_rectangle_.Width()); + Pack<DstFormat, alphaOp>(reinterpret_cast<IntermType*>( + unpacked_intermediate_src_data_.get()), + dst_row_start, src_sub_rectangle_.Width()); + src_row_start += src_stride_in_elements; + dst_row_start += dst_stride_in_elements; + } + src_row_start += src_stride_in_elements * + (unpack_image_height_ - src_sub_rectangle_.Height()); + } + } + success_ = true; + return; +} + +bool FrameIsValid(const SkBitmap& frame_bitmap) { + return !frame_bitmap.isNull() && !frame_bitmap.empty() && + frame_bitmap.colorType() == kN32_SkColorType; +} + +} // anonymous namespace + +WebGLImageConversion::PixelStoreParams::PixelStoreParams() + : alignment(4), + row_length(0), + image_height(0), + skip_pixels(0), + skip_rows(0), + skip_images(0) {} + +bool WebGLImageConversion::ComputeFormatAndTypeParameters( + GLenum format, + GLenum type, + unsigned* components_per_pixel, + unsigned* bytes_per_component) { + switch (format) { + case GL_ALPHA: + case GL_LUMINANCE: + case GL_RED: + case GL_RED_INTEGER: + case GL_DEPTH_COMPONENT: + case GL_DEPTH_STENCIL: // Treat it as one component. + *components_per_pixel = 1; + break; + case GL_LUMINANCE_ALPHA: + case GL_RG: + case GL_RG_INTEGER: + *components_per_pixel = 2; + break; + case GL_RGB: + case GL_RGB_INTEGER: + case GL_SRGB_EXT: // GL_EXT_sRGB + *components_per_pixel = 3; + break; + case GL_RGBA: + case GL_RGBA_INTEGER: + case GL_BGRA_EXT: // GL_EXT_texture_format_BGRA8888 + case GL_SRGB_ALPHA_EXT: // GL_EXT_sRGB + *components_per_pixel = 4; + break; + default: + return false; + } + switch (type) { + case GL_BYTE: + *bytes_per_component = sizeof(GLbyte); + break; + case GL_UNSIGNED_BYTE: + *bytes_per_component = sizeof(GLubyte); + break; + case GL_SHORT: + *bytes_per_component = sizeof(GLshort); + break; + case GL_UNSIGNED_SHORT: + *bytes_per_component = sizeof(GLushort); + break; + case GL_UNSIGNED_SHORT_5_6_5: + case GL_UNSIGNED_SHORT_4_4_4_4: + case GL_UNSIGNED_SHORT_5_5_5_1: + *components_per_pixel = 1; + *bytes_per_component = sizeof(GLushort); + break; + case GL_INT: + *bytes_per_component = sizeof(GLint); + break; + case GL_UNSIGNED_INT: + *bytes_per_component = sizeof(GLuint); + break; + case GL_UNSIGNED_INT_24_8_OES: + case GL_UNSIGNED_INT_10F_11F_11F_REV: + case GL_UNSIGNED_INT_5_9_9_9_REV: + case GL_UNSIGNED_INT_2_10_10_10_REV: + *components_per_pixel = 1; + *bytes_per_component = sizeof(GLuint); + break; + case GL_FLOAT: // OES_texture_float + *bytes_per_component = sizeof(GLfloat); + break; + case GL_HALF_FLOAT: + case GL_HALF_FLOAT_OES: // OES_texture_half_float + *bytes_per_component = sizeof(GLushort); + break; + default: + return false; + } + return true; +} + +GLenum WebGLImageConversion::ComputeImageSizeInBytes( + GLenum format, + GLenum type, + GLsizei width, + GLsizei height, + GLsizei depth, + const PixelStoreParams& params, + unsigned* image_size_in_bytes, + unsigned* padding_in_bytes, + unsigned* skip_size_in_bytes) { + DCHECK(image_size_in_bytes); + DCHECK(params.alignment == 1 || params.alignment == 2 || + params.alignment == 4 || params.alignment == 8); + DCHECK_GE(params.row_length, 0); + DCHECK_GE(params.image_height, 0); + DCHECK_GE(params.skip_pixels, 0); + DCHECK_GE(params.skip_rows, 0); + DCHECK_GE(params.skip_images, 0); + if (width < 0 || height < 0 || depth < 0) + return GL_INVALID_VALUE; + if (!width || !height || !depth) { + *image_size_in_bytes = 0; + if (padding_in_bytes) + *padding_in_bytes = 0; + if (skip_size_in_bytes) + *skip_size_in_bytes = 0; + return GL_NO_ERROR; + } + + int row_length = params.row_length > 0 ? params.row_length : width; + int image_height = params.image_height > 0 ? params.image_height : height; + + unsigned bytes_per_component, components_per_pixel; + if (!ComputeFormatAndTypeParameters(format, type, &bytes_per_component, + &components_per_pixel)) + return GL_INVALID_ENUM; + unsigned bytes_per_group = bytes_per_component * components_per_pixel; + CheckedNumeric<uint32_t> checked_value = static_cast<uint32_t>(row_length); + checked_value *= bytes_per_group; + if (!checked_value.IsValid()) + return GL_INVALID_VALUE; + + unsigned last_row_size; + if (params.row_length > 0 && params.row_length != width) { + CheckedNumeric<uint32_t> tmp = width; + tmp *= bytes_per_group; + if (!tmp.IsValid()) + return GL_INVALID_VALUE; + last_row_size = tmp.ValueOrDie(); + } else { + last_row_size = checked_value.ValueOrDie(); + } + + unsigned padding = 0; + CheckedNumeric<uint32_t> checked_residual = checked_value % params.alignment; + if (!checked_residual.IsValid()) { + return GL_INVALID_VALUE; + } + unsigned residual = checked_residual.ValueOrDie(); + if (residual) { + padding = params.alignment - residual; + checked_value += padding; + } + if (!checked_value.IsValid()) + return GL_INVALID_VALUE; + unsigned padded_row_size = checked_value.ValueOrDie(); + + CheckedNumeric<uint32_t> rows = image_height; + rows *= (depth - 1); + // Last image is not affected by IMAGE_HEIGHT parameter. + rows += height; + if (!rows.IsValid()) + return GL_INVALID_VALUE; + checked_value *= (rows - 1); + // Last row is not affected by ROW_LENGTH parameter. + checked_value += last_row_size; + if (!checked_value.IsValid()) + return GL_INVALID_VALUE; + *image_size_in_bytes = checked_value.ValueOrDie(); + if (padding_in_bytes) + *padding_in_bytes = padding; + + CheckedNumeric<uint32_t> skip_size = 0; + if (params.skip_images > 0) { + CheckedNumeric<uint32_t> tmp = padded_row_size; + tmp *= image_height; + tmp *= params.skip_images; + if (!tmp.IsValid()) + return GL_INVALID_VALUE; + skip_size += tmp.ValueOrDie(); + } + if (params.skip_rows > 0) { + CheckedNumeric<uint32_t> tmp = padded_row_size; + tmp *= params.skip_rows; + if (!tmp.IsValid()) + return GL_INVALID_VALUE; + skip_size += tmp.ValueOrDie(); + } + if (params.skip_pixels > 0) { + CheckedNumeric<uint32_t> tmp = bytes_per_group; + tmp *= params.skip_pixels; + if (!tmp.IsValid()) + return GL_INVALID_VALUE; + skip_size += tmp.ValueOrDie(); + } + if (!skip_size.IsValid()) + return GL_INVALID_VALUE; + if (skip_size_in_bytes) + *skip_size_in_bytes = skip_size.ValueOrDie(); + + checked_value += skip_size.ValueOrDie(); + if (!checked_value.IsValid()) + return GL_INVALID_VALUE; + return GL_NO_ERROR; +} + +WebGLImageConversion::ImageExtractor::ImageExtractor( + Image* image, + ImageHtmlDomSource image_html_dom_source, + bool premultiply_alpha, + bool ignore_color_space) { + image_ = image; + image_html_dom_source_ = image_html_dom_source; + ExtractImage(premultiply_alpha, ignore_color_space); +} + +void WebGLImageConversion::ImageExtractor::ExtractImage( + bool premultiply_alpha, + bool ignore_color_space) { + DCHECK(!image_pixel_locker_); + + if (!image_) + return; + + sk_sp<SkImage> skia_image = image_->PaintImageForCurrentFrame().GetSkImage(); + SkImageInfo info = + skia_image ? SkImageInfo::MakeN32Premul(image_->width(), image_->height()) + : SkImageInfo::MakeUnknown(); + alpha_op_ = kAlphaDoNothing; + bool has_alpha = skia_image ? !skia_image->isOpaque() : true; + + if ((!skia_image || ignore_color_space || + (has_alpha && !premultiply_alpha)) && + image_->Data()) { + // Attempt to get raw unpremultiplied image data. + std::unique_ptr<ImageDecoder> decoder(ImageDecoder::Create( + image_->Data(), true, ImageDecoder::kAlphaNotPremultiplied, + ignore_color_space ? ColorBehavior::Ignore() + : ColorBehavior::TransformToSRGB())); + if (!decoder || !decoder->FrameCount()) + return; + ImageFrame* frame = decoder->DecodeFrameBufferAtIndex(0); + if (!frame || frame->GetStatus() != ImageFrame::kFrameComplete) + return; + has_alpha = frame->HasAlpha(); + SkBitmap bitmap = frame->Bitmap(); + if (!FrameIsValid(bitmap)) + return; + + // TODO(fmalita): Partial frames are not supported currently: only fully + // decoded frames make it through. We could potentially relax this and + // use SkImage::MakeFromBitmap(bitmap) to make a copy. + skia_image = frame->FinalizePixelsAndGetImage(); + info = bitmap.info(); + + if (has_alpha && premultiply_alpha) + alpha_op_ = kAlphaDoPremultiply; + } else if (!premultiply_alpha && has_alpha) { + // 1. For texImage2D with HTMLVideoElment input, assume no PremultiplyAlpha + // had been applied and the alpha value for each pixel is 0xFF. This is + // true at present; if it is changed in the future it will need + // adjustment accordingly. + // 2. For texImage2D with HTMLCanvasElement input in which alpha is already + // premultiplied in this port, do AlphaDoUnmultiply if + // UNPACK_PREMULTIPLY_ALPHA_WEBGL is set to false. + if (image_html_dom_source_ != kHtmlDomVideo) + alpha_op_ = kAlphaDoUnmultiply; + } + + if (!skia_image) + return; + + image_source_format_ = SK_B32_SHIFT ? kDataFormatRGBA8 : kDataFormatBGRA8; + image_source_unpack_alignment_ = + 0; // FIXME: this seems to always be zero - why use at all? + + DCHECK(skia_image->width()); + DCHECK(skia_image->height()); + image_width_ = skia_image->width(); + image_height_ = skia_image->height(); + + // Fail if the image was downsampled because of memory limits. + if (image_width_ != (unsigned)image_->width() || + image_height_ != (unsigned)image_->height()) + return; + + image_pixel_locker_.emplace(std::move(skia_image), info.alphaType(), + kN32_SkColorType); +} + +unsigned WebGLImageConversion::GetChannelBitsByFormat(GLenum format) { + switch (format) { + case GL_ALPHA: + return kChannelAlpha; + case GL_RED: + case GL_RED_INTEGER: + case GL_R8: + case GL_R8_SNORM: + case GL_R8UI: + case GL_R8I: + case GL_R16UI: + case GL_R16I: + case GL_R32UI: + case GL_R32I: + case GL_R16F: + case GL_R32F: + return kChannelRed; + case GL_RG: + case GL_RG_INTEGER: + case GL_RG8: + case GL_RG8_SNORM: + case GL_RG8UI: + case GL_RG8I: + case GL_RG16UI: + case GL_RG16I: + case GL_RG32UI: + case GL_RG32I: + case GL_RG16F: + case GL_RG32F: + return kChannelRG; + case GL_LUMINANCE: + return kChannelRGB; + case GL_LUMINANCE_ALPHA: + return kChannelRGBA; + case GL_RGB: + case GL_RGB_INTEGER: + case GL_RGB8: + case GL_RGB8_SNORM: + case GL_RGB8UI: + case GL_RGB8I: + case GL_RGB16UI: + case GL_RGB16I: + case GL_RGB32UI: + case GL_RGB32I: + case GL_RGB16F: + case GL_RGB32F: + case GL_RGB565: + case GL_R11F_G11F_B10F: + case GL_RGB9_E5: + case GL_SRGB_EXT: + case GL_SRGB8: + return kChannelRGB; + case GL_RGBA: + case GL_RGBA_INTEGER: + case GL_RGBA8: + case GL_RGBA8_SNORM: + case GL_RGBA8UI: + case GL_RGBA8I: + case GL_RGBA16UI: + case GL_RGBA16I: + case GL_RGBA32UI: + case GL_RGBA32I: + case GL_RGBA16F: + case GL_RGBA32F: + case GL_RGBA4: + case GL_RGB5_A1: + case GL_RGB10_A2: + case GL_RGB10_A2UI: + case GL_SRGB_ALPHA_EXT: + case GL_SRGB8_ALPHA8: + return kChannelRGBA; + case GL_DEPTH_COMPONENT: + case GL_DEPTH_COMPONENT16: + case GL_DEPTH_COMPONENT24: + case GL_DEPTH_COMPONENT32F: + return kChannelDepth; + case GL_STENCIL: + case GL_STENCIL_INDEX8: + return kChannelStencil; + case GL_DEPTH_STENCIL: + case GL_DEPTH24_STENCIL8: + case GL_DEPTH32F_STENCIL8: + return kChannelDepthStencil; + default: + return 0; + } +} + +bool WebGLImageConversion::PackImageData( + Image* image, + const void* pixels, + GLenum format, + GLenum type, + bool flip_y, + AlphaOp alpha_op, + DataFormat source_format, + unsigned source_image_width, + unsigned source_image_height, + const IntRect& source_image_sub_rectangle, + int depth, + unsigned source_unpack_alignment, + int unpack_image_height, + Vector<uint8_t>& data) { + if (!pixels) + return false; + + unsigned packed_size; + // Output data is tightly packed (alignment == 1). + PixelStoreParams params; + params.alignment = 1; + if (ComputeImageSizeInBytes(format, type, source_image_sub_rectangle.Width(), + source_image_sub_rectangle.Height(), depth, + params, &packed_size, nullptr, + nullptr) != GL_NO_ERROR) + return false; + data.resize(packed_size); + + return PackPixels(reinterpret_cast<const uint8_t*>(pixels), source_format, + source_image_width, source_image_height, + source_image_sub_rectangle, depth, source_unpack_alignment, + unpack_image_height, format, type, alpha_op, data.data(), + flip_y); +} + +bool WebGLImageConversion::ExtractImageData( + const uint8_t* image_data, + DataFormat source_data_format, + const IntSize& image_data_size, + const IntRect& source_image_sub_rectangle, + int depth, + int unpack_image_height, + GLenum format, + GLenum type, + bool flip_y, + bool premultiply_alpha, + Vector<uint8_t>& data) { + if (!image_data) + return false; + int width = image_data_size.Width(); + int height = image_data_size.Height(); + + unsigned packed_size; + // Output data is tightly packed (alignment == 1). + PixelStoreParams params; + params.alignment = 1; + if (ComputeImageSizeInBytes(format, type, source_image_sub_rectangle.Width(), + source_image_sub_rectangle.Height(), depth, + params, &packed_size, nullptr, + nullptr) != GL_NO_ERROR) + return false; + data.resize(packed_size); + + if (!PackPixels(image_data, source_data_format, width, height, + source_image_sub_rectangle, depth, 0, unpack_image_height, + format, type, + premultiply_alpha ? kAlphaDoPremultiply : kAlphaDoNothing, + data.data(), flip_y)) + return false; + + return true; +} + +bool WebGLImageConversion::ExtractTextureData( + unsigned width, + unsigned height, + GLenum format, + GLenum type, + const PixelStoreParams& unpack_params, + bool flip_y, + bool premultiply_alpha, + const void* pixels, + Vector<uint8_t>& data) { + // Assumes format, type, etc. have already been validated. + DataFormat source_data_format = GetDataFormat(format, type); + if (source_data_format == kDataFormatNumFormats) + return false; + + // Resize the output buffer. + unsigned int components_per_pixel, bytes_per_component; + if (!ComputeFormatAndTypeParameters(format, type, &components_per_pixel, + &bytes_per_component)) + return false; + unsigned bytes_per_pixel = components_per_pixel * bytes_per_component; + data.resize(width * height * bytes_per_pixel); + + unsigned image_size_in_bytes, skip_size_in_bytes; + ComputeImageSizeInBytes(format, type, width, height, 1, unpack_params, + &image_size_in_bytes, nullptr, &skip_size_in_bytes); + const uint8_t* src_data = static_cast<const uint8_t*>(pixels); + if (skip_size_in_bytes) { + src_data += skip_size_in_bytes; + } + + if (!PackPixels(src_data, source_data_format, + unpack_params.row_length ? unpack_params.row_length : width, + height, IntRect(0, 0, width, height), 1, + unpack_params.alignment, 0, format, type, + (premultiply_alpha ? kAlphaDoPremultiply : kAlphaDoNothing), + data.data(), flip_y)) + return false; + + return true; +} + +bool WebGLImageConversion::PackPixels(const uint8_t* source_data, + DataFormat source_data_format, + unsigned source_data_width, + unsigned source_data_height, + const IntRect& source_data_sub_rectangle, + int depth, + unsigned source_unpack_alignment, + int unpack_image_height, + unsigned destination_format, + unsigned destination_type, + AlphaOp alpha_op, + void* destination_data, + bool flip_y) { + DCHECK_GE(depth, 1); + if (unpack_image_height == 0) { + unpack_image_height = source_data_sub_rectangle.Height(); + } + int valid_src = source_data_width * TexelBytesForFormat(source_data_format); + int remainder = + source_unpack_alignment ? (valid_src % source_unpack_alignment) : 0; + int src_stride = + remainder ? (valid_src + source_unpack_alignment - remainder) : valid_src; + int src_row_offset = + source_data_sub_rectangle.X() * TexelBytesForFormat(source_data_format); + + DataFormat dst_data_format = + GetDataFormat(destination_format, destination_type); + if (dst_data_format == kDataFormatNumFormats) + return false; + int dst_stride = + source_data_sub_rectangle.Width() * TexelBytesForFormat(dst_data_format); + if (flip_y) { + destination_data = + static_cast<uint8_t*>(destination_data) + + dst_stride * ((depth * source_data_sub_rectangle.Height()) - 1); + dst_stride = -dst_stride; + } + if (!HasAlpha(source_data_format) || !HasColor(source_data_format) || + !HasColor(dst_data_format)) + alpha_op = kAlphaDoNothing; + + if (source_data_format == dst_data_format && alpha_op == kAlphaDoNothing) { + const uint8_t* base_ptr = + source_data + src_stride * source_data_sub_rectangle.Y(); + const uint8_t* base_end = + source_data + src_stride * source_data_sub_rectangle.MaxY(); + + // If packing multiple images into a 3D texture, and flipY is true, + // then the sub-rectangle is pointing at the start of the + // "bottommost" of those images. Since the source pointer strides in + // the positive direction, we need to back it up to point at the + // last, or "topmost", of these images. + if (flip_y && depth > 1) { + const ptrdiff_t distance_to_top_image = + (depth - 1) * src_stride * unpack_image_height; + base_ptr -= distance_to_top_image; + base_end -= distance_to_top_image; + } + + unsigned row_size = (dst_stride > 0) ? dst_stride : -dst_stride; + uint8_t* dst = static_cast<uint8_t*>(destination_data); + + for (int i = 0; i < depth; ++i) { + const uint8_t* ptr = base_ptr; + const uint8_t* ptr_end = base_end; + while (ptr < ptr_end) { + memcpy(dst, ptr + src_row_offset, row_size); + ptr += src_stride; + dst += dst_stride; + } + base_ptr += unpack_image_height * src_stride; + base_end += unpack_image_height * src_stride; + } + return true; + } + + FormatConverter converter(source_data_sub_rectangle, depth, + unpack_image_height, source_data, destination_data, + src_stride, src_row_offset, dst_stride); + converter.Convert(source_data_format, dst_data_format, alpha_op); + if (!converter.Success()) + return false; + return true; +} + +void WebGLImageConversion::UnpackPixels(const uint16_t* source_data, + DataFormat source_data_format, + unsigned pixels_per_row, + uint8_t* destination_data) { + switch (source_data_format) { + case kDataFormatRGBA4444: { + typedef typename DataTypeForFormat< + WebGLImageConversion::kDataFormatRGBA4444>::Type SrcType; + const SrcType* src_row_start = static_cast<const SrcType*>(source_data); + Unpack<WebGLImageConversion::kDataFormatRGBA4444>( + src_row_start, destination_data, pixels_per_row); + } break; + case kDataFormatRGBA5551: { + typedef typename DataTypeForFormat< + WebGLImageConversion::kDataFormatRGBA5551>::Type SrcType; + const SrcType* src_row_start = static_cast<const SrcType*>(source_data); + Unpack<WebGLImageConversion::kDataFormatRGBA5551>( + src_row_start, destination_data, pixels_per_row); + } break; + case kDataFormatBGRA8: { + const uint8_t* psrc = (const uint8_t*)source_data; + typedef typename DataTypeForFormat< + WebGLImageConversion::kDataFormatBGRA8>::Type SrcType; + const SrcType* src_row_start = static_cast<const SrcType*>(psrc); + Unpack<WebGLImageConversion::kDataFormatBGRA8>( + src_row_start, destination_data, pixels_per_row); + } break; + default: + break; + } +} + +void WebGLImageConversion::PackPixels(const uint8_t* source_data, + DataFormat source_data_format, + unsigned pixels_per_row, + uint8_t* destination_data) { + switch (source_data_format) { + case kDataFormatRA8: { + typedef typename DataTypeForFormat< + WebGLImageConversion::kDataFormatRGBA8>::Type SrcType; + const SrcType* src_row_start = static_cast<const SrcType*>(source_data); + Pack<WebGLImageConversion::kDataFormatRA8, + WebGLImageConversion::kAlphaDoUnmultiply>( + src_row_start, destination_data, pixels_per_row); + } break; + case kDataFormatR8: { + typedef typename DataTypeForFormat< + WebGLImageConversion::kDataFormatRGBA8>::Type SrcType; + const SrcType* src_row_start = static_cast<const SrcType*>(source_data); + Pack<WebGLImageConversion::kDataFormatR8, + WebGLImageConversion::kAlphaDoUnmultiply>( + src_row_start, destination_data, pixels_per_row); + } break; + case kDataFormatRGBA8: { + typedef typename DataTypeForFormat< + WebGLImageConversion::kDataFormatRGBA8>::Type SrcType; + const SrcType* src_row_start = static_cast<const SrcType*>(source_data); + Pack<WebGLImageConversion::kDataFormatRGBA8, + WebGLImageConversion::kAlphaDoUnmultiply>( + src_row_start, destination_data, pixels_per_row); + } break; + case kDataFormatRGBA4444: { + uint16_t* pdst = (uint16_t*)destination_data; + typedef typename DataTypeForFormat< + WebGLImageConversion::kDataFormatRGBA8>::Type SrcType; + const SrcType* src_row_start = static_cast<const SrcType*>(source_data); + typedef typename DataTypeForFormat< + WebGLImageConversion::kDataFormatRGBA4444>::Type DstType; + DstType* dst_row_start = static_cast<DstType*>(pdst); + Pack<WebGLImageConversion::kDataFormatRGBA4444, + WebGLImageConversion::kAlphaDoNothing>(src_row_start, dst_row_start, + pixels_per_row); + } break; + case kDataFormatRGBA5551: { + uint16_t* pdst = (uint16_t*)destination_data; + typedef typename DataTypeForFormat< + WebGLImageConversion::kDataFormatRGBA8>::Type SrcType; + const SrcType* src_row_start = static_cast<const SrcType*>(source_data); + typedef typename DataTypeForFormat< + WebGLImageConversion::kDataFormatRGBA5551>::Type DstType; + DstType* dst_row_start = static_cast<DstType*>(pdst); + Pack<WebGLImageConversion::kDataFormatRGBA5551, + WebGLImageConversion::kAlphaDoNothing>(src_row_start, dst_row_start, + pixels_per_row); + } break; + case kDataFormatRGB565: { + uint16_t* pdst = (uint16_t*)destination_data; + typedef typename DataTypeForFormat< + WebGLImageConversion::kDataFormatRGBA8>::Type SrcType; + const SrcType* src_row_start = static_cast<const SrcType*>(source_data); + typedef typename DataTypeForFormat< + WebGLImageConversion::kDataFormatRGB565>::Type DstType; + DstType* dst_row_start = static_cast<DstType*>(pdst); + Pack<WebGLImageConversion::kDataFormatRGB565, + WebGLImageConversion::kAlphaDoNothing>(src_row_start, dst_row_start, + pixels_per_row); + } break; + default: + break; + } +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/platform/graphics/gpu/webgl_image_conversion.h b/chromium/third_party/blink/renderer/platform/graphics/gpu/webgl_image_conversion.h new file mode 100644 index 00000000000..ad66c2f7ca1 --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/graphics/gpu/webgl_image_conversion.h @@ -0,0 +1,293 @@ +// 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. + +#ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_GRAPHICS_GPU_WEBGL_IMAGE_CONVERSION_H_ +#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_GRAPHICS_GPU_WEBGL_IMAGE_CONVERSION_H_ + +#include "base/memory/scoped_refptr.h" +#include "third_party/blink/renderer/platform/graphics/image.h" +#include "third_party/blink/renderer/platform/graphics/skia/image_pixel_locker.h" +#include "third_party/blink/renderer/platform/heap/heap.h" +#include "third_party/blink/renderer/platform/platform_export.h" +#include "third_party/blink/renderer/platform/wtf/allocator.h" +#include "third_party/blink/renderer/platform/wtf/optional.h" +#include "third_party/khronos/GLES2/gl2.h" +#include "third_party/khronos/GLES2/gl2ext.h" +#include "third_party/khronos/GLES3/gl3.h" + +namespace blink { +class Image; +class IntSize; + +// Helper functions for texture uploading and pixel readback. +class PLATFORM_EXPORT WebGLImageConversion final { + STATIC_ONLY(WebGLImageConversion); + + public: + // Attempt to enumerate all possible native image formats to + // reduce the amount of temporary allocations during texture + // uploading. This enum must be public because it is accessed + // by non-member functions. + // "_S" postfix indicates signed type. + enum DataFormat { + kDataFormatRGBA8 = 0, + kDataFormatRGBA8_S, + kDataFormatRGBA16, + kDataFormatRGBA16_S, + kDataFormatRGBA32, + kDataFormatRGBA32_S, + kDataFormatRGBA16F, + kDataFormatRGBA32F, + kDataFormatRGBA2_10_10_10, + kDataFormatRGB8, + kDataFormatRGB8_S, + kDataFormatRGB16, + kDataFormatRGB16_S, + kDataFormatRGB32, + kDataFormatRGB32_S, + kDataFormatRGB16F, + kDataFormatRGB32F, + kDataFormatBGR8, + kDataFormatBGRA8, + kDataFormatARGB8, + kDataFormatABGR8, + kDataFormatRGBA5551, + kDataFormatRGBA4444, + kDataFormatRGB565, + kDataFormatRGB10F11F11F, + kDataFormatRGB5999, + kDataFormatRG8, + kDataFormatRG8_S, + kDataFormatRG16, + kDataFormatRG16_S, + kDataFormatRG32, + kDataFormatRG32_S, + kDataFormatRG16F, + kDataFormatRG32F, + kDataFormatR8, + kDataFormatR8_S, + kDataFormatR16, + kDataFormatR16_S, + kDataFormatR32, + kDataFormatR32_S, + kDataFormatR16F, + kDataFormatR32F, + kDataFormatRA8, + kDataFormatRA16F, + kDataFormatRA32F, + kDataFormatAR8, + kDataFormatA8, + kDataFormatA16F, + kDataFormatA32F, + kDataFormatD16, + kDataFormatD32, + kDataFormatD32F, + kDataFormatDS24_8, + kDataFormatNumFormats + }; + + enum ChannelBits { + kChannelRed = 1, + kChannelGreen = 2, + kChannelBlue = 4, + kChannelAlpha = 8, + kChannelDepth = 16, + kChannelStencil = 32, + kChannelRG = kChannelRed | kChannelGreen, + kChannelRGB = kChannelRed | kChannelGreen | kChannelBlue, + kChannelRGBA = kChannelRGB | kChannelAlpha, + kChannelDepthStencil = kChannelDepth | kChannelStencil, + }; + + // Possible alpha operations that may need to occur during + // pixel packing. FIXME: kAlphaDoUnmultiply is lossy and must + // be removed. + enum AlphaOp { + kAlphaDoNothing = 0, + kAlphaDoPremultiply = 1, + kAlphaDoUnmultiply = 2 + }; + + enum ImageHtmlDomSource { + kHtmlDomImage = 0, + kHtmlDomCanvas = 1, + kHtmlDomVideo = 2, + kHtmlDomNone = 3 + }; + + struct PLATFORM_EXPORT PixelStoreParams final { + PixelStoreParams(); + + GLint alignment; + GLint row_length; + GLint image_height; + GLint skip_pixels; + GLint skip_rows; + GLint skip_images; + }; + + class PLATFORM_EXPORT ImageExtractor final { + STACK_ALLOCATED(); + WTF_MAKE_NONCOPYABLE(ImageExtractor); + + public: + ImageExtractor(Image*, + ImageHtmlDomSource, + bool premultiply_alpha, + bool ignore_color_space); + + const void* ImagePixelData() { + return image_pixel_locker_ ? image_pixel_locker_->Pixels() : nullptr; + } + unsigned ImageWidth() { return image_width_; } + unsigned ImageHeight() { return image_height_; } + DataFormat ImageSourceFormat() { return image_source_format_; } + AlphaOp ImageAlphaOp() { return alpha_op_; } + unsigned ImageSourceUnpackAlignment() { + return image_source_unpack_alignment_; + } + + private: + // Extracts the image and keeps track of its status, such as width, height, + // Source Alignment, format, AlphaOp, etc. This needs to lock the resources + // or relevant data if needed. + void ExtractImage(bool premultiply_alpha, bool ignore_color_space); + + Image* image_; + Optional<ImagePixelLocker> image_pixel_locker_; + ImageHtmlDomSource image_html_dom_source_; + unsigned image_width_; + unsigned image_height_; + DataFormat image_source_format_; + AlphaOp alpha_op_; + unsigned image_source_unpack_alignment_; + }; + + // Computes the components per pixel and bytes per component + // for the given format and type combination. Returns false if + // either was an invalid enum. + static bool ComputeFormatAndTypeParameters(GLenum format, + GLenum type, + unsigned* components_per_pixel, + unsigned* bytes_per_component); + + // Computes the image size in bytes. If paddingInBytes is not null, padding + // is also calculated in return. Returns NO_ERROR if succeed, otherwise + // return the suggested GL error indicating the cause of the failure: + // INVALID_VALUE if width/height/depth is negative or overflow happens. + // INVALID_ENUM if format/type is illegal. + // Note that imageSizeBytes does not include skipSizeInBytes, but it is + // guaranteed if NO_ERROR is returned, adding the two sizes won't cause + // overflow. + // |paddingInBytes| and |skipSizeInBytes| are optional and can be null, but + // the overflow validation is still performed. + static GLenum ComputeImageSizeInBytes(GLenum format, + GLenum type, + GLsizei width, + GLsizei height, + GLsizei depth, + const PixelStoreParams&, + unsigned* image_size_in_bytes, + unsigned* padding_in_bytes, + unsigned* skip_size_in_bytes); + + // Check if the format is one of the formats from the ImageData or DOM + // elements. The format from ImageData is always RGBA8. The formats from DOM + // elements vary with Graphics ports, but can only be RGBA8 or BGRA8. + static ALWAYS_INLINE bool SrcFormatComeFromDOMElementOrImageData( + DataFormat src_format) { + return src_format == kDataFormatBGRA8 || src_format == kDataFormatRGBA8; + } + + // The input can be either format or internalformat. + static unsigned GetChannelBitsByFormat(GLenum); + + // The Following functions are implemented in + // GraphicsContext3DImagePacking.cpp. + + // Packs the contents of the given Image, which is passed in |pixels|, into + // the passed Vector according to the given format and type, and obeying the + // flipY and AlphaOp flags. Returns true upon success. + static bool PackImageData(Image*, + const void* pixels, + GLenum format, + GLenum type, + bool flip_y, + AlphaOp, + DataFormat source_format, + unsigned source_image_width, + unsigned source_image_height, + const IntRect& source_image_sub_rectangle, + int depth, + unsigned source_unpack_alignment, + int unpack_image_height, + Vector<uint8_t>& data); + + // Extracts the contents of the given ImageData into the passed Vector, + // packing the pixel data according to the given format and type, + // and obeying the flipY and premultiplyAlpha flags. Returns true + // upon success. + static bool ExtractImageData(const uint8_t* image_data, + DataFormat source_data_format, + const IntSize& image_data_size, + const IntRect& source_image_sub_rectangle, + int depth, + int unpack_image_height, + GLenum format, + GLenum type, + bool flip_y, + bool premultiply_alpha, + Vector<uint8_t>& data); + + // Helper function which extracts the user-supplied texture + // data, applying the flipY and premultiplyAlpha parameters. + // If the data is not tightly packed according to the passed + // unpackAlignment, the output data will be tightly packed. + // Returns true if successful, false if any error occurred. + static bool ExtractTextureData(unsigned width, + unsigned height, + GLenum format, + GLenum type, + const PixelStoreParams& unpack_params, + bool flip_y, + bool premultiply_alpha, + const void* pixels, + Vector<uint8_t>& data); + + // End GraphicsContext3DImagePacking.cpp functions + + private: + friend class WebGLImageConversionTest; + // Helper for packImageData/extractImageData/extractTextureData, which + // implement packing of pixel data into the specified OpenGL destination + // format and type. A sourceUnpackAlignment of zero indicates that the source + // data is tightly packed. Non-zero values may take a slow path. Destination + // data will have no gaps between rows. Implemented in + // GraphicsContext3DImagePacking.cpp. + static bool PackPixels(const uint8_t* source_data, + DataFormat source_data_format, + unsigned source_data_width, + unsigned source_data_height, + const IntRect& source_data_sub_rectangle, + int depth, + unsigned source_unpack_alignment, + int unpack_image_height, + unsigned destination_format, + unsigned destination_type, + AlphaOp, + void* destination_data, + bool flip_y); + static void UnpackPixels(const uint16_t* source_data, + DataFormat source_data_format, + unsigned pixels_per_row, + uint8_t* destination_data); + static void PackPixels(const uint8_t* source_data, + DataFormat source_data_format, + unsigned pixels_per_row, + uint8_t* destination_data); +}; + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_PLATFORM_GRAPHICS_GPU_WEBGL_IMAGE_CONVERSION_H_ diff --git a/chromium/third_party/blink/renderer/platform/graphics/gpu/webgl_image_conversion_test.cc b/chromium/third_party/blink/renderer/platform/graphics/gpu/webgl_image_conversion_test.cc new file mode 100644 index 00000000000..e3156158588 --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/graphics/gpu/webgl_image_conversion_test.cc @@ -0,0 +1,173 @@ +// Copyright 2016 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 "third_party/blink/renderer/platform/graphics/gpu/webgl_image_conversion.h" + +#include "build/build_config.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace blink { + +class WebGLImageConversionTest : public testing::Test { + protected: + void UnpackPixels(const uint16_t* source_data, + WebGLImageConversion::DataFormat source_data_format, + unsigned pixels_per_row, + uint8_t* destination_data) { + WebGLImageConversion::UnpackPixels(source_data, source_data_format, + pixels_per_row, destination_data); + } + void PackPixels(const uint8_t* source_data, + WebGLImageConversion::DataFormat source_data_format, + unsigned pixels_per_row, + uint8_t* destination_data) { + WebGLImageConversion::PackPixels(source_data, source_data_format, + pixels_per_row, destination_data); + } +}; + +TEST_F(WebGLImageConversionTest, ConvertRGBA4444toRGBA8) { + uint16_t source_data[9] = {0x1234, 0x3456, 0x1234, 0x3456, 0x1234, + 0x3456, 0x1234, 0x3456, 0x1234}; + uint8_t expected_data[36] = { + 0x11, 0x22, 0x33, 0x44, 0x33, 0x44, 0x55, 0x66, 0x11, 0x22, 0x33, 0x44, + 0x33, 0x44, 0x55, 0x66, 0x11, 0x22, 0x33, 0x44, 0x33, 0x44, 0x55, 0x66, + 0x11, 0x22, 0x33, 0x44, 0x33, 0x44, 0x55, 0x66, 0x11, 0x22, 0x33, 0x44}; + uint8_t destination_data[36]; + UnpackPixels(source_data, WebGLImageConversion::kDataFormatRGBA4444, 9, + destination_data); + EXPECT_EQ(0, + memcmp(expected_data, destination_data, sizeof(destination_data))); +} + +TEST_F(WebGLImageConversionTest, ConvertRGBA5551toRGBA8) { + uint16_t source_data[9] = {0x1234, 0x3456, 0x1234, 0x3456, 0x1234, + 0x3456, 0x1234, 0x3456, 0x1234}; + uint8_t expected_data[36] = { + 0x12, 0x40, 0xd2, 0x0, 0x36, 0x89, 0x5b, 0x0, 0x12, 0x40, 0xd2, 0x0, + 0x36, 0x89, 0x5b, 0x0, 0x12, 0x40, 0xd2, 0x0, 0x36, 0x89, 0x5b, 0x0, + 0x12, 0x40, 0xd2, 0x0, 0x36, 0x89, 0x5b, 0x0, 0x12, 0x40, 0xd2, 0x0}; + uint8_t destination_data[36]; + UnpackPixels(source_data, WebGLImageConversion::kDataFormatRGBA5551, 9, + destination_data); + EXPECT_EQ(0, + memcmp(expected_data, destination_data, sizeof(destination_data))); +} + +TEST_F(WebGLImageConversionTest, ConvertRGBA8toRA8) { + uint8_t source_data[40] = {0x34, 0x56, 0x34, 0x56, 0x34, 0x56, 0x34, 0x56, + 0x34, 0x56, 0x34, 0x56, 0x34, 0x56, 0x34, 0x56, + 0x34, 0x56, 0x34, 0x56, 0x34, 0x56, 0x34, 0x56, + 0x34, 0x56, 0x34, 0x56, 0x34, 0x56, 0x34, 0x56, + 0x34, 0x56, 0x34, 0x56, 0x34, 0x56, 0x34, 0x56}; + uint8_t expected_data[20] = {0x9a, 0x56, 0x9a, 0x56, 0x9a, 0x56, 0x9a, + 0x56, 0x9a, 0x56, 0x9a, 0x56, 0x9a, 0x56, + 0x9a, 0x56, 0x9a, 0x56, 0x9a, 0x56}; + uint8_t destination_data[20]; + PackPixels(source_data, WebGLImageConversion::kDataFormatRA8, 10, + destination_data); + EXPECT_EQ(0, + memcmp(expected_data, destination_data, sizeof(destination_data))); +} + +TEST_F(WebGLImageConversionTest, convertBGRA8toRGBA8) { + uint32_t source_data[9] = {0x12345678, 0x34567888, 0x12345678, + 0x34567888, 0x12345678, 0x34567888, + 0x12345678, 0x34567888, 0x12345678}; +#if defined(ARCH_CPU_BIG_ENDIAN) + uint32_t expectedData[9] = {0x56341278, 0x78563488, 0x56341278, + 0x78563488, 0x56341278, 0x78563488, + 0x56341278, 0x78563488, 0x56341278}; +#else + uint32_t expected_data[9] = {0x12785634, 0x34887856, 0x12785634, + 0x34887856, 0x12785634, 0x34887856, + 0x12785634, 0x34887856, 0x12785634}; +#endif + uint32_t destination_data[9]; + UnpackPixels(reinterpret_cast<uint16_t*>(&source_data[0]), + WebGLImageConversion::kDataFormatBGRA8, 9, + reinterpret_cast<uint8_t*>(&destination_data[0])); + EXPECT_EQ(0, + memcmp(expected_data, destination_data, sizeof(destination_data))); +} + +TEST_F(WebGLImageConversionTest, ConvertRGBA8toR8) { + uint8_t source_data[40] = {0x34, 0x56, 0x34, 0x56, 0x34, 0x56, 0x34, 0x56, + 0x34, 0x56, 0x34, 0x56, 0x34, 0x56, 0x34, 0x56, + 0x34, 0x56, 0x34, 0x56, 0x34, 0x56, 0x34, 0x56, + 0x34, 0x56, 0x34, 0x56, 0x34, 0x56, 0x34, 0x56, + 0x34, 0x56, 0x34, 0x56, 0x34, 0x56, 0x34, 0x56}; + uint8_t expected_data[10] = {0x9a, 0x9a, 0x9a, 0x9a, 0x9a, + 0x9a, 0x9a, 0x9a, 0x9a, 0x9a}; + uint8_t destination_data[10]; + PackPixels(source_data, WebGLImageConversion::kDataFormatR8, 10, + destination_data); + EXPECT_EQ(0, + memcmp(expected_data, destination_data, sizeof(destination_data))); +} + +TEST_F(WebGLImageConversionTest, ConvertRGBA8toRGBA8) { + uint8_t source_data[40] = {0x34, 0x56, 0x34, 0x56, 0x34, 0x56, 0x34, 0x56, + 0x34, 0x56, 0x34, 0x56, 0x34, 0x56, 0x34, 0x56, + 0x34, 0x56, 0x34, 0x56, 0x34, 0x56, 0x34, 0x56, + 0x34, 0x56, 0x34, 0x56, 0x34, 0x56, 0x34, 0x56, + 0x34, 0x56, 0x34, 0x56, 0x34, 0x56, 0x34, 0x56}; + uint8_t expected_data[40] = {0x9a, 0xff, 0x9a, 0x56, 0x9a, 0xff, 0x9a, 0x56, + 0x9a, 0xff, 0x9a, 0x56, 0x9a, 0xff, 0x9a, 0x56, + 0x9a, 0xff, 0x9a, 0x56, 0x9a, 0xff, 0x9a, 0x56, + 0x9a, 0xff, 0x9a, 0x56, 0x9a, 0xff, 0x9a, 0x56, + 0x9a, 0xff, 0x9a, 0x56, 0x9a, 0xff, 0x9a, 0x56}; + uint8_t destination_data[40]; + PackPixels(source_data, WebGLImageConversion::kDataFormatRGBA8, 10, + destination_data); + EXPECT_EQ(0, + memcmp(expected_data, destination_data, sizeof(destination_data))); +} + +TEST_F(WebGLImageConversionTest, ConvertRGBA8ToUnsignedShort4444) { + uint8_t source_data[40] = {0x34, 0x56, 0x34, 0x56, 0x34, 0x56, 0x34, 0x56, + 0x34, 0x56, 0x34, 0x56, 0x34, 0x56, 0x34, 0x56, + 0x34, 0x56, 0x34, 0x56, 0x34, 0x56, 0x34, 0x56, + 0x34, 0x56, 0x34, 0x56, 0x34, 0x56, 0x34, 0x56, + 0x34, 0x56, 0x34, 0x56, 0x34, 0x56, 0x34, 0x56}; + uint16_t expected_data[10] = {0x3535, 0x3535, 0x3535, 0x3535, 0x3535, + 0x3535, 0x3535, 0x3535, 0x3535, 0x3535}; + uint16_t destination_data[10]; + PackPixels(source_data, WebGLImageConversion::kDataFormatRGBA4444, 10, + reinterpret_cast<uint8_t*>(&destination_data[0])); + EXPECT_EQ(0, + memcmp(expected_data, destination_data, sizeof(destination_data))); +} + +TEST_F(WebGLImageConversionTest, ConvertRGBA8ToRGBA5551) { + uint8_t source_data[40] = {0x34, 0x56, 0x34, 0x56, 0x34, 0x56, 0x34, 0x56, + 0x34, 0x56, 0x34, 0x56, 0x34, 0x56, 0x34, 0x56, + 0x34, 0x56, 0x34, 0x56, 0x34, 0x56, 0x34, 0x56, + 0x34, 0x56, 0x34, 0x56, 0x34, 0x56, 0x34, 0x56, + 0x34, 0x56, 0x34, 0x56, 0x34, 0x56, 0x34, 0x56}; + uint16_t expected_data[10] = {0x328c, 0x328c, 0x328c, 0x328c, 0x328c, + 0x328c, 0x328c, 0x328c, 0x328c, 0x328c}; + uint16_t destination_data[10]; + PackPixels(source_data, WebGLImageConversion::kDataFormatRGBA5551, 10, + reinterpret_cast<uint8_t*>(&destination_data[0])); + EXPECT_EQ(0, + memcmp(expected_data, destination_data, sizeof(destination_data))); +} + +TEST_F(WebGLImageConversionTest, ConvertRGBA8ToRGB565) { + uint8_t source_data[40] = {0x34, 0x56, 0x34, 0x56, 0x34, 0x56, 0x34, 0x56, + 0x34, 0x56, 0x34, 0x56, 0x34, 0x56, 0x34, 0x56, + 0x34, 0x56, 0x34, 0x56, 0x34, 0x56, 0x34, 0x56, + 0x34, 0x56, 0x34, 0x56, 0x34, 0x56, 0x34, 0x56, + 0x34, 0x56, 0x34, 0x56, 0x34, 0x56, 0x34, 0x56}; + uint16_t expected_data[10] = {0x32a6, 0x32a6, 0x32a6, 0x32a6, 0x32a6, + 0x32a6, 0x32a6, 0x32a6, 0x32a6, 0x32a6}; + uint16_t destination_data[10]; + PackPixels(source_data, WebGLImageConversion::kDataFormatRGB565, 10, + reinterpret_cast<uint8_t*>(&destination_data[0])); + EXPECT_EQ(0, + memcmp(expected_data, destination_data, sizeof(destination_data))); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/platform/graphics/gpu/xr_frame_transport.cc b/chromium/third_party/blink/renderer/platform/graphics/gpu/xr_frame_transport.cc new file mode 100644 index 00000000000..8956cfa76cf --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/graphics/gpu/xr_frame_transport.cc @@ -0,0 +1,266 @@ +// Copyright 2017 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 "third_party/blink/renderer/platform/graphics/gpu/xr_frame_transport.h" + +#include "build/build_config.h" +#include "device/vr/public/mojom/vr_service.mojom-blink.h" +#include "gpu/command_buffer/client/gles2_interface.h" +#include "gpu/command_buffer/common/mailbox_holder.h" +#include "mojo/public/cpp/system/platform_handle.h" +#include "third_party/blink/renderer/platform/graphics/gpu_memory_buffer_image_copy.h" +#include "third_party/blink/renderer/platform/graphics/static_bitmap_image.h" +#include "ui/gfx/gpu_fence.h" + +namespace blink { + +XRFrameTransport::XRFrameTransport() : submit_frame_client_binding_(this) {} + +XRFrameTransport::~XRFrameTransport() { + CallPreviousFrameCallback(); +} + +void XRFrameTransport::PresentChange() { + frame_copier_ = nullptr; +} + +void XRFrameTransport::SetTransportOptions( + device::mojom::blink::VRDisplayFrameTransportOptionsPtr transport_options) { + transport_options_ = std::move(transport_options); +} + +device::mojom::blink::VRSubmitFrameClientPtr +XRFrameTransport::GetSubmitFrameClient() { + device::mojom::blink::VRSubmitFrameClientPtr submit_frame_client; + submit_frame_client_binding_.Close(); + submit_frame_client_binding_.Bind(mojo::MakeRequest(&submit_frame_client)); + return submit_frame_client; +} + +bool XRFrameTransport::DrawingIntoSharedBuffer() { + switch (transport_options_->transport_method) { + case device::mojom::blink::VRDisplayFrameTransportMethod:: + SUBMIT_AS_TEXTURE_HANDLE: + case device::mojom::blink::VRDisplayFrameTransportMethod:: + SUBMIT_AS_MAILBOX_HOLDER: + return false; + case device::mojom::blink::VRDisplayFrameTransportMethod:: + DRAW_INTO_TEXTURE_MAILBOX: + return true; + default: + NOTREACHED(); + return false; + } +} + +void XRFrameTransport::FramePreImage(gpu::gles2::GLES2Interface* gl) { + frame_wait_time_ = WTF::TimeDelta(); + + // If we're expecting a fence for the previous frame and it hasn't arrived + // yet, wait for it to be received. + if (waiting_for_previous_frame_fence_) { + frame_wait_time_ += WaitForGpuFenceReceived(); + } + // If we have a GpuFence (it may be missing if WaitForIncomingMethodCall + // failed), send it to the GPU service process and ask it to do an + // asynchronous server wait. + if (previous_frame_fence_) { + DVLOG(3) << "CreateClientGpuFenceCHROMIUM"; + GLuint id = gl->CreateClientGpuFenceCHROMIUM( + previous_frame_fence_->AsClientGpuFence()); + gl->WaitGpuFenceCHROMIUM(id); + gl->DestroyGpuFenceCHROMIUM(id); + previous_frame_fence_.reset(); + } +} + +void XRFrameTransport::CallPreviousFrameCallback() { + if (previous_image_release_callback_) { + previous_image_release_callback_->Run(gpu::SyncToken(), false); + previous_image_release_callback_ = nullptr; + } +} + +void XRFrameTransport::FrameSubmitMissing( + device::mojom::blink::VRPresentationProvider* vr_presentation_provider, + gpu::gles2::GLES2Interface* gl, + int16_t vr_frame_id) { + TRACE_EVENT0("gpu", __FUNCTION__); + gpu::SyncToken sync_token; + gl->GenSyncTokenCHROMIUM(sync_token.GetData()); + vr_presentation_provider->SubmitFrameMissing(vr_frame_id, sync_token); +} + +void XRFrameTransport::FrameSubmit( + device::mojom::blink::VRPresentationProvider* vr_presentation_provider, + gpu::gles2::GLES2Interface* gl, + DrawingBuffer::Client* drawing_buffer_client, + scoped_refptr<Image> image_ref, + std::unique_ptr<viz::SingleReleaseCallback> release_callback, + int16_t vr_frame_id, + bool needs_copy) { + DCHECK(transport_options_); + + if (transport_options_->transport_method == + device::mojom::blink::VRDisplayFrameTransportMethod:: + SUBMIT_AS_TEXTURE_HANDLE) { +#if defined(OS_WIN) + // Currently, we assume that this transport needs a copy. + DCHECK(needs_copy); + TRACE_EVENT0("gpu", "XRFrameTransport::CopyImage"); + // Update last_transfer_succeeded_ value. This should usually complete + // without waiting. + if (transport_options_->wait_for_transfer_notification) + WaitForPreviousTransfer(); + CallPreviousFrameCallback(); + previous_image_release_callback_ = std::move(release_callback); + if (!frame_copier_ || !last_transfer_succeeded_) { + frame_copier_ = std::make_unique<GpuMemoryBufferImageCopy>(gl); + } + auto gpu_memory_buffer = frame_copier_->CopyImage(image_ref.get()); + drawing_buffer_client->DrawingBufferClientRestoreTexture2DBinding(); + drawing_buffer_client->DrawingBufferClientRestoreFramebufferBinding(); + drawing_buffer_client->DrawingBufferClientRestoreRenderbufferBinding(); + + // We can fail to obtain a GpuMemoryBuffer if we don't have GPU support, or + // for some out-of-memory situations. + // TODO(billorr): Consider whether we should just drop the frame or exit + // presentation. + if (gpu_memory_buffer) { + // We decompose the cloned handle, and use it to create a + // mojo::ScopedHandle which will own cleanup of the handle, and will be + // passed over IPC. + gfx::GpuMemoryBufferHandle gpu_handle = + CloneHandleForIPC(gpu_memory_buffer->GetHandle()); + vr_presentation_provider->SubmitFrameWithTextureHandle( + vr_frame_id, mojo::WrapPlatformFile(gpu_handle.handle.GetHandle())); + } +#else + NOTIMPLEMENTED(); +#endif + } else if (transport_options_->transport_method == + device::mojom::blink::VRDisplayFrameTransportMethod:: + SUBMIT_AS_MAILBOX_HOLDER) { + // Currently, this transport assumes we don't need to make a separate copy + // of the canvas content. + DCHECK(!needs_copy); + + // The AcceleratedStaticBitmapImage must be kept alive until the + // mailbox is used via createAndConsumeTextureCHROMIUM, the mailbox + // itself does not keep it alive. We must keep a reference to the + // image until the mailbox was consumed. + StaticBitmapImage* static_image = + static_cast<StaticBitmapImage*>(image_ref.get()); + TRACE_EVENT_BEGIN0("gpu", "XRFrameTransport::EnsureMailbox"); + static_image->EnsureMailbox(kVerifiedSyncToken, GL_NEAREST); + TRACE_EVENT_END0("gpu", "XRFrameTransport::EnsureMailbox"); + + // Conditionally wait for the previous render to finish. A late wait here + // attempts to overlap work in parallel with the previous frame's + // rendering. This is used if submitting fully rendered frames to GVR, but + // is susceptible to bad GPU scheduling if the new frame competes with the + // previous frame's incomplete rendering. + if (waiting_for_previous_frame_render_) + frame_wait_time_ += WaitForPreviousRenderToFinish(); + + // Save a reference to the image to keep it alive until next frame, + // but first wait for the transfer to finish before overwriting it. + // Usually this check is satisfied without waiting. + if (transport_options_->wait_for_transfer_notification) + WaitForPreviousTransfer(); + previous_image_ = std::move(image_ref); + CallPreviousFrameCallback(); + previous_image_release_callback_ = std::move(release_callback); + + // Create mailbox and sync token for transfer. + TRACE_EVENT_BEGIN0("gpu", "XRFrameTransport::GetMailbox"); + auto mailbox = static_image->GetMailbox(); + TRACE_EVENT_END0("gpu", "XRFrameTransport::GetMailbox"); + auto sync_token = static_image->GetSyncToken(); + + TRACE_EVENT_BEGIN0("gpu", "XRFrameTransport::SubmitFrame"); + vr_presentation_provider->SubmitFrame( + vr_frame_id, gpu::MailboxHolder(mailbox, sync_token, GL_TEXTURE_2D), + frame_wait_time_); + TRACE_EVENT_END0("gpu", "XRFrameTransport::SubmitFrame"); + } else if (transport_options_->transport_method == + device::mojom::blink::VRDisplayFrameTransportMethod:: + DRAW_INTO_TEXTURE_MAILBOX) { + TRACE_EVENT0("gpu", "XRFrameTransport::SubmitFrameDrawnIntoTexture"); + gpu::SyncToken sync_token; + { + TRACE_EVENT0("gpu", "GenSyncTokenCHROMIUM"); + gl->GenSyncTokenCHROMIUM(sync_token.GetData()); + } + if (waiting_for_previous_frame_render_) + frame_wait_time_ += WaitForPreviousRenderToFinish(); + vr_presentation_provider->SubmitFrameDrawnIntoTexture( + vr_frame_id, sync_token, frame_wait_time_); + } else { + NOTREACHED() << "Unimplemented frame transport method"; + } + + // Set the expected notifications the next frame should wait for. + waiting_for_previous_frame_transfer_ = + transport_options_->wait_for_transfer_notification; + waiting_for_previous_frame_render_ = + transport_options_->wait_for_render_notification; + waiting_for_previous_frame_fence_ = transport_options_->wait_for_gpu_fence; +} + +void XRFrameTransport::OnSubmitFrameTransferred(bool success) { + DVLOG(3) << __FUNCTION__; + waiting_for_previous_frame_transfer_ = false; + last_transfer_succeeded_ = success; +} + +void XRFrameTransport::WaitForPreviousTransfer() { + TRACE_EVENT0("gpu", "waitForPreviousTransferToFinish"); + while (waiting_for_previous_frame_transfer_) { + if (!submit_frame_client_binding_.WaitForIncomingMethodCall()) { + DLOG(ERROR) << __FUNCTION__ << ": Failed to receive response"; + break; + } + } +} + +void XRFrameTransport::OnSubmitFrameRendered() { + DVLOG(3) << __FUNCTION__; + waiting_for_previous_frame_render_ = false; +} + +WTF::TimeDelta XRFrameTransport::WaitForPreviousRenderToFinish() { + TRACE_EVENT0("gpu", "waitForPreviousRenderToFinish"); + WTF::TimeTicks start = WTF::CurrentTimeTicks(); + while (waiting_for_previous_frame_render_) { + if (!submit_frame_client_binding_.WaitForIncomingMethodCall()) { + DLOG(ERROR) << __FUNCTION__ << ": Failed to receive response"; + break; + } + } + return WTF::CurrentTimeTicks() - start; +} + +void XRFrameTransport::OnSubmitFrameGpuFence( + const gfx::GpuFenceHandle& handle) { + // We just received a GpuFence, unblock WaitForGpuFenceReceived. + waiting_for_previous_frame_fence_ = false; + previous_frame_fence_ = std::make_unique<gfx::GpuFence>(handle); +} + +WTF::TimeDelta XRFrameTransport::WaitForGpuFenceReceived() { + TRACE_EVENT0("gpu", "WaitForGpuFenceReceived"); + WTF::TimeTicks start = WTF::CurrentTimeTicks(); + while (waiting_for_previous_frame_fence_) { + if (!submit_frame_client_binding_.WaitForIncomingMethodCall()) { + DLOG(ERROR) << __FUNCTION__ << ": Failed to receive response"; + break; + } + } + return WTF::CurrentTimeTicks() - start; +} + +void XRFrameTransport::Trace(blink::Visitor* visitor) {} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/platform/graphics/gpu/xr_frame_transport.h b/chromium/third_party/blink/renderer/platform/graphics/gpu/xr_frame_transport.h new file mode 100644 index 00000000000..f416c8821d4 --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/graphics/gpu/xr_frame_transport.h @@ -0,0 +1,100 @@ +// Copyright 2017 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. + +#ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_GRAPHICS_GPU_XR_FRAME_TRANSPORT_H_ +#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_GRAPHICS_GPU_XR_FRAME_TRANSPORT_H_ + +#include "device/vr/public/mojom/vr_service.mojom-blink.h" +#include "mojo/public/cpp/bindings/binding.h" +#include "third_party/blink/public/platform/web_graphics_context_3d_provider.h" +#include "third_party/blink/renderer/platform/graphics/gpu/drawing_buffer.h" +#include "third_party/blink/renderer/platform/heap/handle.h" +#include "third_party/blink/renderer/platform/platform_export.h" +#include "third_party/blink/renderer/platform/wtf/time.h" + +namespace gfx { +class GpuFence; +} + +namespace gpu { +namespace gles2 { +class GLES2Interface; +} +} // namespace gpu + +namespace blink { + +class GpuMemoryBufferImageCopy; +class Image; + +class PLATFORM_EXPORT XRFrameTransport final + : public GarbageCollectedFinalized<XRFrameTransport>, + public device::mojom::blink::VRSubmitFrameClient { + public: + explicit XRFrameTransport(); + ~XRFrameTransport(); + + device::mojom::blink::VRSubmitFrameClientPtr GetSubmitFrameClient(); + + void PresentChange(); + + void SetTransportOptions( + device::mojom::blink::VRDisplayFrameTransportOptionsPtr); + + bool DrawingIntoSharedBuffer(); + + // Call before finalizing the frame's image snapshot. + void FramePreImage(gpu::gles2::GLES2Interface*); + + void FrameSubmit(device::mojom::blink::VRPresentationProvider*, + gpu::gles2::GLES2Interface*, + DrawingBuffer::Client*, + scoped_refptr<Image> image_ref, + std::unique_ptr<viz::SingleReleaseCallback>, + int16_t vr_frame_id, + bool needs_copy); + + void FrameSubmitMissing(device::mojom::blink::VRPresentationProvider*, + gpu::gles2::GLES2Interface*, + int16_t vr_frame_id); + + virtual void Trace(blink::Visitor*); + + private: + void WaitForPreviousTransfer(); + WTF::TimeDelta WaitForPreviousRenderToFinish(); + WTF::TimeDelta WaitForGpuFenceReceived(); + void CallPreviousFrameCallback(); + + // VRSubmitFrameClient + void OnSubmitFrameTransferred(bool success) override; + void OnSubmitFrameRendered() override; + void OnSubmitFrameGpuFence(const gfx::GpuFenceHandle&) override; + + mojo::Binding<device::mojom::blink::VRSubmitFrameClient> + submit_frame_client_binding_; + + // Used to keep the image alive until the next frame if using + // waitForPreviousTransferToFinish. + scoped_refptr<Image> previous_image_; + std::unique_ptr<viz::SingleReleaseCallback> previous_image_release_callback_; + + bool waiting_for_previous_frame_transfer_ = false; + bool last_transfer_succeeded_ = false; + WTF::TimeDelta frame_wait_time_; + bool waiting_for_previous_frame_render_ = false; + // If using GpuFence to separate frames, need to wait for the previous + // frame's fence, but not if this is the first frame. Separately track + // if we're expecting a fence and the received fence itself. + bool waiting_for_previous_frame_fence_ = false; + std::unique_ptr<gfx::GpuFence> previous_frame_fence_; + + device::mojom::blink::VRDisplayFrameTransportOptionsPtr transport_options_; + + std::unique_ptr<GpuMemoryBufferImageCopy> frame_copier_; +}; + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_PLATFORM_GRAPHICS_GPU_XR_FRAME_TRANSPORT_H_ diff --git a/chromium/third_party/blink/renderer/platform/graphics/gpu/xr_webgl_drawing_buffer.cc b/chromium/third_party/blink/renderer/platform/graphics/gpu/xr_webgl_drawing_buffer.cc new file mode 100644 index 00000000000..8eca5b8fa6e --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/graphics/gpu/xr_webgl_drawing_buffer.cc @@ -0,0 +1,626 @@ +// Copyright 2017 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 "third_party/blink/renderer/platform/graphics/gpu/xr_webgl_drawing_buffer.h" + +#include "build/build_config.h" +#include "third_party/blink/renderer/platform/graphics/accelerated_static_bitmap_image.h" +#include "third_party/blink/renderer/platform/graphics/gpu/drawing_buffer.h" +#include "third_party/blink/renderer/platform/graphics/gpu/extensions_3d_util.h" +#include "third_party/skia/include/core/SkSurface.h" + +namespace blink { + +// Large parts of this file have been shamelessly borrowed from +// platform/graphics/gpu/DrawingBuffer.cpp and simplified where applicable due +// to the more narrow use case. It may make sense in the future to abstract out +// some of the common bits into a base class? + +XRWebGLDrawingBuffer::ColorBuffer::ColorBuffer( + XRWebGLDrawingBuffer* drawing_buffer, + const IntSize& size, + GLuint texture_id) + : drawing_buffer(drawing_buffer), size(size), texture_id(texture_id) { + drawing_buffer->ContextGL()->GenMailboxCHROMIUM(mailbox.name); +} + +XRWebGLDrawingBuffer::ColorBuffer::~ColorBuffer() { + gpu::gles2::GLES2Interface* gl = drawing_buffer->ContextGL(); + if (receive_sync_token.HasData()) + gl->WaitSyncTokenCHROMIUM(receive_sync_token.GetConstData()); + gl->DeleteTextures(1, &texture_id); +} + +scoped_refptr<XRWebGLDrawingBuffer> XRWebGLDrawingBuffer::Create( + DrawingBuffer* drawing_buffer, + GLuint framebuffer, + const IntSize& size, + bool want_alpha_channel, + bool want_depth_buffer, + bool want_stencil_buffer, + bool want_antialiasing, + bool want_multiview) { + DCHECK(drawing_buffer); + + // Don't proceeed if the context is already lost. + if (drawing_buffer->destroyed()) + return nullptr; + + gpu::gles2::GLES2Interface* gl = drawing_buffer->ContextGL(); + + std::unique_ptr<Extensions3DUtil> extensions_util = + Extensions3DUtil::Create(gl); + if (!extensions_util->IsValid()) { + return nullptr; + } + + DCHECK(extensions_util->SupportsExtension("GL_OES_packed_depth_stencil")); + extensions_util->EnsureExtensionEnabled("GL_OES_packed_depth_stencil"); + bool multisample_supported = + want_antialiasing && + (extensions_util->SupportsExtension( + "GL_CHROMIUM_framebuffer_multisample") || + extensions_util->SupportsExtension( + "GL_EXT_multisampled_render_to_texture")) && + extensions_util->SupportsExtension("GL_OES_rgb8_rgba8"); + if (multisample_supported) { + extensions_util->EnsureExtensionEnabled("GL_OES_rgb8_rgba8"); + if (extensions_util->SupportsExtension( + "GL_CHROMIUM_framebuffer_multisample")) { + extensions_util->EnsureExtensionEnabled( + "GL_CHROMIUM_framebuffer_multisample"); + } else { + extensions_util->EnsureExtensionEnabled( + "GL_EXT_multisampled_render_to_texture"); + } + } + bool discard_framebuffer_supported = + extensions_util->SupportsExtension("GL_EXT_discard_framebuffer"); + if (discard_framebuffer_supported) + extensions_util->EnsureExtensionEnabled("GL_EXT_discard_framebuffer"); + + // TODO(bajones): Support multiview. + bool multiview_supported = false; + + scoped_refptr<XRWebGLDrawingBuffer> xr_drawing_buffer = + base::AdoptRef(new XRWebGLDrawingBuffer( + drawing_buffer, framebuffer, discard_framebuffer_supported, + want_alpha_channel, want_depth_buffer, want_stencil_buffer, + multiview_supported)); + if (!xr_drawing_buffer->Initialize(size, multisample_supported, + multiview_supported)) { + DLOG(ERROR) << "XRWebGLDrawingBuffer Initialization Failed"; + return nullptr; + } + + return xr_drawing_buffer; +} + +XRWebGLDrawingBuffer::XRWebGLDrawingBuffer(DrawingBuffer* drawing_buffer, + GLuint framebuffer, + bool discard_framebuffer_supported, + bool want_alpha_channel, + bool want_depth_buffer, + bool want_stencil_buffer, + bool multiview_supported) + : drawing_buffer_(drawing_buffer), + framebuffer_(framebuffer), + discard_framebuffer_supported_(discard_framebuffer_supported), + depth_(want_depth_buffer), + stencil_(want_stencil_buffer), + alpha_(want_alpha_channel), + multiview_(false) {} + +// TODO(bajones): The GL resources allocated in this function are leaking. Add +// a way to clean up the buffers when the layer is GCed or the session ends. +bool XRWebGLDrawingBuffer::Initialize(const IntSize& size, + bool use_multisampling, + bool use_multiview) { + gpu::gles2::GLES2Interface* gl = drawing_buffer_->ContextGL(); + + std::unique_ptr<Extensions3DUtil> extensions_util = + Extensions3DUtil::Create(gl); + + gl->GetIntegerv(GL_MAX_TEXTURE_SIZE, &max_texture_size_); + DVLOG(2) << __FUNCTION__ << ": max_texture_size_=" << max_texture_size_; + + // Check context capabilities + int max_sample_count = 0; + anti_aliasing_mode_ = kNone; + if (use_multisampling) { + gl->GetIntegerv(GL_MAX_SAMPLES_ANGLE, &max_sample_count); + anti_aliasing_mode_ = kMSAAExplicitResolve; + if (extensions_util->SupportsExtension( + "GL_EXT_multisampled_render_to_texture")) { + anti_aliasing_mode_ = kMSAAImplicitResolve; + } else if (extensions_util->SupportsExtension( + "GL_CHROMIUM_screen_space_antialiasing")) { + anti_aliasing_mode_ = kScreenSpaceAntialiasing; + } + } + DVLOG(2) << __FUNCTION__ + << ": anti_aliasing_mode_=" << static_cast<int>(anti_aliasing_mode_); + + storage_texture_supported_ = + (drawing_buffer_->webgl_version() > DrawingBuffer::kWebGL1 || + extensions_util->SupportsExtension("GL_EXT_texture_storage")) && + anti_aliasing_mode_ == kScreenSpaceAntialiasing; + +#if defined(OS_ANDROID) + // On Android devices use a smaller numer of samples to provide more breathing + // room for fill-rate-bound applications. + sample_count_ = std::min(2, max_sample_count); +#else + sample_count_ = std::min(4, max_sample_count); +#endif + + Resize(size); + + // It's possible that the drawing buffer allocation provokes a context loss, + // so check again just in case. + if (gl->GetGraphicsResetStatusKHR() != GL_NO_ERROR) { + return false; + } + + return true; +} + +gpu::gles2::GLES2Interface* XRWebGLDrawingBuffer::ContextGL() { + return drawing_buffer_->ContextGL(); +} + +void XRWebGLDrawingBuffer::SetMirrorClient(MirrorClient* client) { + mirror_client_ = client; + if (mirror_client_) { + // Immediately send a black 1x1 image to the mirror client to ensure that + // it has content to show. + sk_sp<SkSurface> surface = SkSurface::MakeRasterN32Premul(1, 1); + mirror_client_->OnMirrorImageAvailable( + StaticBitmapImage::Create(surface->makeImageSnapshot()), nullptr); + } +} + +bool XRWebGLDrawingBuffer::ContextLost() { + return drawing_buffer_->destroyed(); +} + +IntSize XRWebGLDrawingBuffer::AdjustSize(const IntSize& new_size) { + // Ensure we always have at least a 1x1 buffer + float width = std::max(1, new_size.Width()); + float height = std::max(1, new_size.Height()); + + float adjusted_scale = + std::min(static_cast<float>(max_texture_size_) / width, + static_cast<float>(max_texture_size_) / height); + + // Clamp if the desired size is greater than the maximum texture size for the + // device. Scale both dimensions proportionally so that we avoid stretching. + if (adjusted_scale < 1.0f) { + width *= adjusted_scale; + height *= adjusted_scale; + } + + return IntSize(width, height); +} + +void XRWebGLDrawingBuffer::UseSharedBuffer( + const gpu::MailboxHolder& buffer_mailbox_holder) { + DVLOG(3) << __FUNCTION__; + + gpu::gles2::GLES2Interface* gl = drawing_buffer_->ContextGL(); + + // Create a texture backed by the shared buffer image. + DCHECK(!shared_buffer_texture_id_); + shared_buffer_texture_id_ = + gl->CreateAndConsumeTextureCHROMIUM(buffer_mailbox_holder.mailbox.name); + + if (WantExplicitResolve()) { + // Bind the shared texture to the destination framebuffer of + // the explicit resolve step. + if (!resolved_framebuffer_) { + gl->GenFramebuffers(1, &resolved_framebuffer_); + } + gl->BindFramebuffer(GL_FRAMEBUFFER, resolved_framebuffer_); + } else { + // Bind the shared texture directly to the drawing framebuffer. + gl->BindFramebuffer(GL_FRAMEBUFFER, framebuffer_); + } + + if (anti_aliasing_mode_ == kMSAAImplicitResolve) { + gl->FramebufferTexture2DMultisampleEXT( + GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, + shared_buffer_texture_id_, 0, sample_count_); + } else { + // Explicit resolve, screen space antialiasing, or no antialiasing. + gl->FramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, + GL_TEXTURE_2D, shared_buffer_texture_id_, 0); + } + + if (!framebuffer_complete_checked_for_sharedbuffer_) { + DCHECK(gl->CheckFramebufferStatus(GL_FRAMEBUFFER) == + GL_FRAMEBUFFER_COMPLETE); + framebuffer_complete_checked_for_sharedbuffer_ = true; + } + + if (discard_framebuffer_supported_) { + const GLenum kAttachments[3] = {GL_COLOR_ATTACHMENT0, GL_DEPTH_ATTACHMENT, + GL_STENCIL_ATTACHMENT}; + gl->DiscardFramebufferEXT(GL_FRAMEBUFFER, 3, kAttachments); + } + + DrawingBuffer::Client* client = drawing_buffer_->client(); + client->DrawingBufferClientRestoreFramebufferBinding(); +} + +void XRWebGLDrawingBuffer::DoneWithSharedBuffer() { + DVLOG(3) << __FUNCTION__; + + BindAndResolveDestinationFramebuffer(); + + gpu::gles2::GLES2Interface* gl = drawing_buffer_->ContextGL(); + if (discard_framebuffer_supported_) { + // Discard the depth and stencil attachments since we're done with them. + // Don't discard the color buffer, we do need this rendered into the + // shared buffer. + if (WantExplicitResolve()) { + gl->BindFramebuffer(GL_FRAMEBUFFER, resolved_framebuffer_); + } else { + gl->BindFramebuffer(GL_FRAMEBUFFER, framebuffer_); + } + const GLenum kAttachments[] = {GL_DEPTH_ATTACHMENT, GL_STENCIL_ATTACHMENT}; + gl->DiscardFramebufferEXT(GL_FRAMEBUFFER, + sizeof(kAttachments) / sizeof(kAttachments[0]), + kAttachments); + } + + // Always bind to the default framebuffer as a hint to the GPU to start + // rendering now. + gl->BindFramebuffer(GL_FRAMEBUFFER, 0); + + // Done with the texture created by CreateAndConsumeTexture, delete it. + DCHECK(shared_buffer_texture_id_); + gl->DeleteTextures(1, &shared_buffer_texture_id_); + shared_buffer_texture_id_ = 0; + + DrawingBuffer::Client* client = drawing_buffer_->client(); + client->DrawingBufferClientRestoreFramebufferBinding(); +} + +void XRWebGLDrawingBuffer::Resize(const IntSize& new_size) { + IntSize adjusted_size = AdjustSize(new_size); + + if (adjusted_size == size_) + return; + + // Don't attempt to resize if the context is lost. + if (ContextLost()) + return; + + gpu::gles2::GLES2Interface* gl = drawing_buffer_->ContextGL(); + + size_ = adjusted_size; + + // Free all mailboxes, because they are now of the wrong size. Only the + // first call in this loop has any effect. + recycled_color_buffer_queue_.clear(); + + gl->BindFramebuffer(GL_FRAMEBUFFER, framebuffer_); + + // Provide a depth and/or stencil buffer if requested. + if (depth_ || stencil_) { + if (depth_stencil_buffer_) { + gl->DeleteRenderbuffers(1, &depth_stencil_buffer_); + depth_stencil_buffer_ = 0; + } + gl->GenRenderbuffers(1, &depth_stencil_buffer_); + gl->BindRenderbuffer(GL_RENDERBUFFER, depth_stencil_buffer_); + + if (anti_aliasing_mode_ == kMSAAImplicitResolve) { + gl->RenderbufferStorageMultisampleEXT(GL_RENDERBUFFER, sample_count_, + GL_DEPTH24_STENCIL8_OES, + size_.Width(), size_.Height()); + } else if (anti_aliasing_mode_ == kMSAAExplicitResolve) { + gl->RenderbufferStorageMultisampleCHROMIUM(GL_RENDERBUFFER, sample_count_, + GL_DEPTH24_STENCIL8_OES, + size_.Width(), size_.Height()); + } else { + gl->RenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8_OES, + size_.Width(), size_.Height()); + } + + gl->FramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, + GL_RENDERBUFFER, depth_stencil_buffer_); + } + + if (WantExplicitResolve()) { + // If we're doing an explicit multisample resolve use the main framebuffer + // as the multisample target and resolve into resolved_fbo_ when needed. + GLenum multisample_format = alpha_ ? GL_RGBA8_OES : GL_RGB8_OES; + + if (multisample_renderbuffer_) { + gl->DeleteRenderbuffers(1, &multisample_renderbuffer_); + multisample_renderbuffer_ = 0; + } + + gl->GenRenderbuffers(1, &multisample_renderbuffer_); + gl->BindRenderbuffer(GL_RENDERBUFFER, multisample_renderbuffer_); + gl->RenderbufferStorageMultisampleCHROMIUM(GL_RENDERBUFFER, sample_count_, + multisample_format, + size_.Width(), size_.Height()); + + gl->FramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, + GL_RENDERBUFFER, multisample_renderbuffer_); + + // Now bind the resolve target framebuffer to attach the color textures to. + if (!resolved_framebuffer_) { + gl->GenFramebuffers(1, &resolved_framebuffer_); + } + gl->BindFramebuffer(GL_FRAMEBUFFER, resolved_framebuffer_); + } + + back_color_buffer_ = CreateColorBuffer(); + front_color_buffer_ = nullptr; + + if (anti_aliasing_mode_ == kMSAAImplicitResolve) { + gl->FramebufferTexture2DMultisampleEXT( + GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, + back_color_buffer_->texture_id, 0, sample_count_); + } else { + gl->FramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, + GL_TEXTURE_2D, back_color_buffer_->texture_id, 0); + } + + if (!framebuffer_complete_checked_for_resize_) { + DCHECK(gl->CheckFramebufferStatus(GL_FRAMEBUFFER) == + GL_FRAMEBUFFER_COMPLETE); + framebuffer_complete_checked_for_resize_ = true; + } + + DrawingBuffer::Client* client = drawing_buffer_->client(); + client->DrawingBufferClientRestoreRenderbufferBinding(); + client->DrawingBufferClientRestoreFramebufferBinding(); +} + +scoped_refptr<XRWebGLDrawingBuffer::ColorBuffer> +XRWebGLDrawingBuffer::CreateColorBuffer() { + gpu::gles2::GLES2Interface* gl = drawing_buffer_->ContextGL(); + + GLuint texture_id = 0; + gl->GenTextures(1, &texture_id); + gl->BindTexture(GL_TEXTURE_2D, texture_id); + gl->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + gl->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + gl->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + gl->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + + if (storage_texture_supported_) { + GLenum internal_storage_format = alpha_ ? GL_RGBA8 : GL_RGB8; + gl->TexStorage2DEXT(GL_TEXTURE_2D, 1, internal_storage_format, + size_.Width(), size_.Height()); + } else { + GLenum gl_format = alpha_ ? GL_RGBA : GL_RGB; + gl->TexImage2D(GL_TEXTURE_2D, 0, gl_format, size_.Width(), size_.Height(), + 0, gl_format, GL_UNSIGNED_BYTE, nullptr); + } + + DrawingBuffer::Client* client = drawing_buffer_->client(); + client->DrawingBufferClientRestoreTexture2DBinding(); + + return base::AdoptRef(new ColorBuffer(this, size_, texture_id)); +} + +scoped_refptr<XRWebGLDrawingBuffer::ColorBuffer> +XRWebGLDrawingBuffer::CreateOrRecycleColorBuffer() { + if (!recycled_color_buffer_queue_.IsEmpty()) { + scoped_refptr<ColorBuffer> recycled = + recycled_color_buffer_queue_.TakeLast(); + if (recycled->receive_sync_token.HasData()) { + gpu::gles2::GLES2Interface* gl = drawing_buffer_->ContextGL(); + gl->WaitSyncTokenCHROMIUM(recycled->receive_sync_token.GetData()); + } + DCHECK(recycled->size == size_); + return recycled; + } + return CreateColorBuffer(); +} + +bool XRWebGLDrawingBuffer::WantExplicitResolve() const { + return anti_aliasing_mode_ == kMSAAExplicitResolve; +} + +void XRWebGLDrawingBuffer::BindAndResolveDestinationFramebuffer() { + // Ensure that the mode-appropriate destination framebuffer's color + // attachment contains the drawn content after any antialiasing steps needed. + + gpu::gles2::GLES2Interface* gl = drawing_buffer_->ContextGL(); + + DrawingBuffer::Client* client = drawing_buffer_->client(); + + // Resolve multisample buffers if needed + if (WantExplicitResolve()) { + DVLOG(3) << __FUNCTION__ << ": explicit resolve"; + gl->BindFramebuffer(GL_READ_FRAMEBUFFER_ANGLE, framebuffer_); + gl->BindFramebuffer(GL_DRAW_FRAMEBUFFER_ANGLE, resolved_framebuffer_); + gl->Disable(GL_SCISSOR_TEST); + + int width = size_.Width(); + int height = size_.Height(); + // Use NEAREST, because there is no scale performed during the blit. + gl->BlitFramebufferCHROMIUM(0, 0, width, height, 0, 0, width, height, + GL_COLOR_BUFFER_BIT, GL_NEAREST); + + gl->BindFramebuffer(GL_FRAMEBUFFER, resolved_framebuffer_); + + client->DrawingBufferClientRestoreScissorTest(); + } else { + gl->BindFramebuffer(GL_FRAMEBUFFER, framebuffer_); + if (anti_aliasing_mode_ == kScreenSpaceAntialiasing) { + DVLOG(3) << __FUNCTION__ << ": screen space antialiasing"; + gl->ApplyScreenSpaceAntialiasingCHROMIUM(); + } else { + DVLOG(3) << __FUNCTION__ << ": nothing to do"; + } + } + + // On exit, leaves the destination framebuffer active. Caller is responsible + // for restoring client bindings. +} + +// Swap the front and back buffers. After this call the front buffer should +// contain the previously rendered content, resolved from the multisample +// renderbuffer if needed. +void XRWebGLDrawingBuffer::SwapColorBuffers() { + gpu::gles2::GLES2Interface* gl = drawing_buffer_->ContextGL(); + + DrawingBuffer::Client* client = drawing_buffer_->client(); + + BindAndResolveDestinationFramebuffer(); + + // Swap buffers + front_color_buffer_ = back_color_buffer_; + back_color_buffer_ = CreateOrRecycleColorBuffer(); + + if (anti_aliasing_mode_ == kMSAAImplicitResolve) { + gl->FramebufferTexture2DMultisampleEXT( + GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, + back_color_buffer_->texture_id, 0, sample_count_); + } else { + gl->FramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, + GL_TEXTURE_2D, back_color_buffer_->texture_id, 0); + } + + if (!framebuffer_complete_checked_for_swap_) { + DCHECK(gl->CheckFramebufferStatus(GL_FRAMEBUFFER) == + GL_FRAMEBUFFER_COMPLETE); + framebuffer_complete_checked_for_swap_ = true; + } + + if (discard_framebuffer_supported_) { + const GLenum kAttachments[3] = {GL_COLOR_ATTACHMENT0, GL_DEPTH_ATTACHMENT, + GL_STENCIL_ATTACHMENT}; + gl->DiscardFramebufferEXT(GL_FRAMEBUFFER, 3, kAttachments); + } + + client->DrawingBufferClientRestoreFramebufferBinding(); +} + +scoped_refptr<StaticBitmapImage> +XRWebGLDrawingBuffer::TransferToStaticBitmapImage( + std::unique_ptr<viz::SingleReleaseCallback>* out_release_callback) { + gpu::gles2::GLES2Interface* gl = drawing_buffer_->ContextGL(); + scoped_refptr<ColorBuffer> buffer; + bool success = false; + + // Ensure the context isn't lost and the framebuffer is complete before + // continuing. + if (!ContextLost()) { + SwapColorBuffers(); + + buffer = front_color_buffer_; + + gl->ProduceTextureDirectCHROMIUM(buffer->texture_id, buffer->mailbox.name); + gl->GenUnverifiedSyncTokenCHROMIUM(buffer->produce_sync_token.GetData()); + + // This should only fail if the context is lost during the buffer swap. + if (buffer->produce_sync_token.HasData()) { + success = true; + } + } + + if (!success) { + // If we can't get a mailbox, return an transparent black ImageBitmap. + // The only situation in which this could happen is when two or more calls + // to transferToImageBitmap are made back-to-back, if the framebuffer is + // incomplete (likely due to a failed buffer allocation), or when the + // context gets lost. + sk_sp<SkSurface> surface = + SkSurface::MakeRasterN32Premul(size_.Width(), size_.Height()); + return StaticBitmapImage::Create(surface->makeImageSnapshot()); + } + + // This holds a ref on the XRWebGLDrawingBuffer that will keep it alive + // until the mailbox is released (and while the callback is running). + auto func = + WTF::Bind(mirror_client_ ? &XRWebGLDrawingBuffer::MailboxReleasedToMirror + : &XRWebGLDrawingBuffer::MailboxReleased, + scoped_refptr<XRWebGLDrawingBuffer>(this), buffer); + + std::unique_ptr<viz::SingleReleaseCallback> release_callback = + viz::SingleReleaseCallback::Create(std::move(func)); + + // Make our own textureId that is a reference on the same texture backing + // being used as the front buffer. We do not need to wait on the sync + // token since the mailbox was produced on the same GL context that we are + // using here. Similarly, the release callback will run on the same context so + // we don't need to send a sync token for this consume action back to it. + GLuint texture_id = gl->CreateAndConsumeTextureCHROMIUM(buffer->mailbox.name); + + if (out_release_callback) { + *out_release_callback = std::move(release_callback); + } else { + release_callback->Run(gpu::SyncToken(), true /* lost_resource */); + } + + return AcceleratedStaticBitmapImage::CreateFromWebGLContextImage( + buffer->mailbox, buffer->produce_sync_token, texture_id, + drawing_buffer_->ContextProviderWeakPtr(), size_); +} + +void XRWebGLDrawingBuffer::MailboxReleased( + scoped_refptr<ColorBuffer> color_buffer, + const gpu::SyncToken& sync_token, + bool lost_resource) { + // If the mailbox has been returned by the compositor then it is no + // longer being presented, and so is no longer the front buffer. + if (color_buffer == front_color_buffer_) + front_color_buffer_ = nullptr; + + // Update the SyncToken to ensure that we will wait for it even if we + // immediately destroy this buffer. + color_buffer->receive_sync_token = sync_token; + + if (drawing_buffer_->destroyed() || color_buffer->size != size_ || + lost_resource) { + return; + } + + const size_t cache_limit = 2; + while (recycled_color_buffer_queue_.size() >= cache_limit) + recycled_color_buffer_queue_.TakeLast(); + + recycled_color_buffer_queue_.push_front(color_buffer); +} + +void XRWebGLDrawingBuffer::MailboxReleasedToMirror( + scoped_refptr<ColorBuffer> color_buffer, + const gpu::SyncToken& sync_token, + bool lost_resource) { + if (!mirror_client_ || lost_resource) { + MailboxReleased(std::move(color_buffer), sync_token, lost_resource); + return; + } + + gpu::gles2::GLES2Interface* gl = drawing_buffer_->ContextGL(); + color_buffer->receive_sync_token = sync_token; + + auto func = + WTF::Bind(&XRWebGLDrawingBuffer::MailboxReleased, + scoped_refptr<XRWebGLDrawingBuffer>(this), color_buffer); + + std::unique_ptr<viz::SingleReleaseCallback> release_callback = + viz::SingleReleaseCallback::Create(std::move(func)); + + GLuint texture_id = + gl->CreateAndConsumeTextureCHROMIUM(color_buffer->mailbox.name); + + scoped_refptr<StaticBitmapImage> image = + AcceleratedStaticBitmapImage::CreateFromWebGLContextImage( + color_buffer->mailbox, color_buffer->produce_sync_token, texture_id, + drawing_buffer_->ContextProviderWeakPtr(), color_buffer->size); + + mirror_client_->OnMirrorImageAvailable(std::move(image), + std::move(release_callback)); +} + +} // namespace blink diff --git a/chromium/third_party/blink/renderer/platform/graphics/gpu/xr_webgl_drawing_buffer.h b/chromium/third_party/blink/renderer/platform/graphics/gpu/xr_webgl_drawing_buffer.h new file mode 100644 index 00000000000..99514de6d6d --- /dev/null +++ b/chromium/third_party/blink/renderer/platform/graphics/gpu/xr_webgl_drawing_buffer.h @@ -0,0 +1,164 @@ +// Copyright 2017 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. + +#ifndef THIRD_PARTY_BLINK_RENDERER_PLATFORM_GRAPHICS_GPU_XR_WEBGL_DRAWING_BUFFER_H_ +#define THIRD_PARTY_BLINK_RENDERER_PLATFORM_GRAPHICS_GPU_XR_WEBGL_DRAWING_BUFFER_H_ + +#include "cc/layers/texture_layer_client.h" +#include "gpu/command_buffer/client/gles2_interface.h" +#include "gpu/command_buffer/common/mailbox_holder.h" +#include "third_party/blink/renderer/platform/geometry/int_size.h" +#include "third_party/blink/renderer/platform/heap/handle.h" +#include "third_party/blink/renderer/platform/platform_export.h" +#include "third_party/blink/renderer/platform/wtf/deque.h" +#include "third_party/blink/renderer/platform/wtf/noncopyable.h" +#include "third_party/blink/renderer/platform/wtf/ref_counted.h" + +namespace blink { + +class DrawingBuffer; +class StaticBitmapImage; + +class PLATFORM_EXPORT XRWebGLDrawingBuffer + : public RefCounted<XRWebGLDrawingBuffer> { + public: + static scoped_refptr<XRWebGLDrawingBuffer> Create(DrawingBuffer*, + GLuint framebuffer, + const IntSize&, + bool want_alpha_channel, + bool want_depth_buffer, + bool want_stencil_buffer, + bool want_antialiasing, + bool want_multiview); + + gpu::gles2::GLES2Interface* ContextGL(); + bool ContextLost(); + + const IntSize& size() const { return size_; } + + bool antialias() const { return anti_aliasing_mode_ != kNone; } + bool depth() const { return depth_; } + bool stencil() const { return stencil_; } + bool alpha() const { return alpha_; } + bool multiview() const { return multiview_; } + + void Resize(const IntSize&); + + scoped_refptr<StaticBitmapImage> TransferToStaticBitmapImage( + std::unique_ptr<viz::SingleReleaseCallback>* out_release_callback); + + class MirrorClient { + public: + virtual void OnMirrorImageAvailable( + scoped_refptr<StaticBitmapImage>, + std::unique_ptr<viz::SingleReleaseCallback>) = 0; + }; + + void SetMirrorClient(MirrorClient*); + + void UseSharedBuffer(const gpu::MailboxHolder&); + void DoneWithSharedBuffer(); + + private: + struct ColorBuffer : public RefCounted<ColorBuffer> { + ColorBuffer(XRWebGLDrawingBuffer*, const IntSize&, GLuint texture_id); + ~ColorBuffer(); + + // The owning XRWebGLDrawingBuffer. Note that DrawingBuffer is explicitly + // destroyed by the beginDestruction method, which will eventually drain all + // of its ColorBuffers. + scoped_refptr<XRWebGLDrawingBuffer> drawing_buffer; + const IntSize size; + const GLuint texture_id = 0; + + // The mailbox used to send this buffer to the compositor. + gpu::Mailbox mailbox; + + // The sync token for when this buffer was sent to the compositor. + gpu::SyncToken produce_sync_token; + + // The sync token for when this buffer was received back from the + // compositor. + gpu::SyncToken receive_sync_token; + + private: + WTF_MAKE_NONCOPYABLE(ColorBuffer); + }; + + XRWebGLDrawingBuffer(DrawingBuffer*, + GLuint framebuffer, + bool discard_framebuffer_supported, + bool want_alpha_channel, + bool want_depth_buffer, + bool want_stencil_buffer, + bool multiview_supported); + + bool Initialize(const IntSize&, bool use_multisampling, bool use_multiview); + + IntSize AdjustSize(const IntSize&); + + scoped_refptr<ColorBuffer> CreateColorBuffer(); + scoped_refptr<ColorBuffer> CreateOrRecycleColorBuffer(); + + bool WantExplicitResolve() const; + void BindAndResolveDestinationFramebuffer(); + void SwapColorBuffers(); + + void MailboxReleased(scoped_refptr<ColorBuffer>, + const gpu::SyncToken&, + bool lost_resource); + void MailboxReleasedToMirror(scoped_refptr<ColorBuffer>, + const gpu::SyncToken&, + bool lost_resource); + + // Reference to the DrawingBuffer that owns the GL context for this object. + scoped_refptr<DrawingBuffer> drawing_buffer_; + + const GLuint framebuffer_ = 0; + GLuint resolved_framebuffer_ = 0; + GLuint multisample_renderbuffer_ = 0; + scoped_refptr<ColorBuffer> back_color_buffer_ = 0; + scoped_refptr<ColorBuffer> front_color_buffer_ = 0; + GLuint depth_stencil_buffer_ = 0; + IntSize size_; + + // Nonzero for shared buffer mode from UseSharedBuffer until + // DoneWithSharedBuffer. + GLuint shared_buffer_texture_id_ = 0; + + // Checking framebuffer completeness is extremely expensive, it's basically a + // glFinish followed by a synchronous wait for a reply. Do so only once per + // code path, and only in DCHECK mode. + bool framebuffer_complete_checked_for_resize_ = false; + bool framebuffer_complete_checked_for_swap_ = false; + bool framebuffer_complete_checked_for_sharedbuffer_ = false; + + // Color buffers that were released by the XR compositor can be used again. + Deque<scoped_refptr<ColorBuffer>> recycled_color_buffer_queue_; + + bool discard_framebuffer_supported_; + bool depth_; + bool stencil_; + bool alpha_; + bool multiview_; + + enum AntialiasingMode { + kNone, + kMSAAImplicitResolve, + kMSAAExplicitResolve, + kScreenSpaceAntialiasing, + }; + + AntialiasingMode anti_aliasing_mode_ = kNone; + + bool storage_texture_supported_ = false; + int max_texture_size_ = 0; + int sample_count_ = 0; + + MirrorClient* mirror_client_ = nullptr; +}; + +} // namespace blink + +#endif // THIRD_PARTY_BLINK_RENDERER_PLATFORM_GRAPHICS_GPU_XR_WEBGL_DRAWING_BUFFER_H_ |