| /* |
| * 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 "base/logging.h" |
| #include "base/memory/read_only_shared_memory_region.h" |
| #include "base/numerics/checked_math.h" |
| #include "base/numerics/ostream_operators.h" |
| #include "build/build_config.h" |
| #include "cc/layers/texture_layer.h" |
| #include "components/viz/common/resources/bitmap_allocation.h" |
| #include "components/viz/common/resources/resource_sizes.h" |
| #include "components/viz/common/resources/shared_bitmap.h" |
| #include "components/viz/common/resources/transferable_resource.h" |
| #include "gpu/command_buffer/client/gpu_memory_buffer_manager.h" |
| #include "gpu/command_buffer/client/shared_image_interface.h" |
| #include "gpu/command_buffer/common/capabilities.h" |
| #include "gpu/command_buffer/common/gpu_memory_buffer_support.h" |
| #include "gpu/command_buffer/common/shared_image_usage.h" |
| #include "gpu/config/gpu_driver_bug_workaround_type.h" |
| #include "gpu/config/gpu_feature_info.h" |
| #include "media/base/video_frame.h" |
| #include "third_party/blink/public/platform/platform.h" |
| #include "third_party/blink/public/web/blink.h" |
| #include "third_party/blink/renderer/platform/graphics/accelerated_static_bitmap_image.h" |
| #include "third_party/blink/renderer/platform/graphics/canvas_resource.h" |
| #include "third_party/blink/renderer/platform/graphics/gpu/extensions_3d_util.h" |
| #include "third_party/blink/renderer/platform/graphics/gpu/shared_gpu_context.h" |
| #include "third_party/blink/renderer/platform/graphics/skia/skia_utils.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/scheduler/public/thread_scheduler.h" |
| #include "third_party/blink/renderer/platform/wtf/functional.h" |
| #include "third_party/skia/include/core/SkPixmap.h" |
| #include "third_party/skia/include/core/SkSurface.h" |
| #include "third_party/skia/include/gpu/gl/GrGLTypes.h" |
| #include "v8/include/v8.h" |
| |
| namespace blink { |
| |
| namespace { |
| |
| const float kResourceAdjustedRatio = 0.5; |
| |
| bool g_should_fail_drawing_buffer_creation_for_testing = false; |
| |
| void FlipVertically(base::span<uint8_t> framebuffer, |
| size_t num_rows, |
| size_t row_bytes) { |
| DCHECK_EQ(framebuffer.size(), num_rows * row_bytes); |
| std::vector<uint8_t> scanline(row_bytes); |
| for (size_t i = 0; i < num_rows / 2; i++) { |
| uint8_t* row_a = framebuffer.data() + i * row_bytes; |
| uint8_t* row_b = framebuffer.data() + (num_rows - 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); |
| } |
| } |
| |
| class ScopedDrawBuffer { |
| STACK_ALLOCATED(); |
| |
| public: |
| explicit ScopedDrawBuffer(gpu::gles2::GLES2Interface* gl, |
| GLenum prev_draw_buffer, |
| GLenum new_draw_buffer) |
| : gl_(gl), |
| prev_draw_buffer_(prev_draw_buffer), |
| new_draw_buffer_(new_draw_buffer) { |
| if (prev_draw_buffer_ != new_draw_buffer_) { |
| gl_->DrawBuffersEXT(1, &new_draw_buffer_); |
| } |
| } |
| |
| ~ScopedDrawBuffer() { |
| if (prev_draw_buffer_ != new_draw_buffer_) { |
| gl_->DrawBuffersEXT(1, &prev_draw_buffer_); |
| } |
| } |
| |
| private: |
| gpu::gles2::GLES2Interface* gl_; |
| GLenum prev_draw_buffer_; |
| GLenum new_draw_buffer_; |
| }; |
| |
| } // namespace |
| |
| // Increase cache to avoid reallocation on fuchsia, see |
| // https://crbug.com/1087941. |
| #if BUILDFLAG(IS_FUCHSIA) |
| const size_t DrawingBuffer::kDefaultColorBufferCacheLimit = 2; |
| #else |
| const size_t DrawingBuffer::kDefaultColorBufferCacheLimit = 1; |
| #endif |
| |
| // Function defined in third_party/blink/public/web/blink.h. |
| void ForceNextDrawingBufferCreationToFailForTest() { |
| g_should_fail_drawing_buffer_creation_for_testing = true; |
| } |
| |
| scoped_refptr<DrawingBuffer> DrawingBuffer::Create( |
| std::unique_ptr<WebGraphicsContext3DProvider> context_provider, |
| const Platform::GraphicsInfo& graphics_info, |
| bool using_swap_chain, |
| Client* client, |
| const gfx::Size& size, |
| bool premultiplied_alpha, |
| bool want_alpha_channel, |
| bool want_depth_buffer, |
| bool want_stencil_buffer, |
| bool want_antialiasing, |
| bool desynchronized, |
| PreserveDrawingBuffer preserve, |
| WebGLVersion webgl_version, |
| ChromiumImageUsage chromium_image_usage, |
| cc::PaintFlags::FilterQuality filter_quality, |
| PredefinedColorSpace color_space, |
| CanvasPixelFormat pixel_format, |
| gl::GpuPreference gpu_preference) { |
| if (g_should_fail_drawing_buffer_creation_for_testing) { |
| g_should_fail_drawing_buffer_creation_for_testing = false; |
| return nullptr; |
| } |
| |
| base::CheckedNumeric<int> data_size = |
| SkColorTypeBytesPerPixel(CanvasPixelFormatToSkColorType(pixel_format)); |
| 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), graphics_info, using_swap_chain, |
| desynchronized, 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, filter_quality, |
| color_space, pixel_format, gpu_preference)); |
| if (!drawing_buffer->Initialize(size, multisample_supported)) { |
| drawing_buffer->BeginDestruction(); |
| return scoped_refptr<DrawingBuffer>(); |
| } |
| return drawing_buffer; |
| } |
| |
| DrawingBuffer::DrawingBuffer( |
| std::unique_ptr<WebGraphicsContext3DProvider> context_provider, |
| const Platform::GraphicsInfo& graphics_info, |
| bool using_swap_chain, |
| bool desynchronized, |
| 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, |
| cc::PaintFlags::FilterQuality filter_quality, |
| PredefinedColorSpace color_space, |
| CanvasPixelFormat pixel_format, |
| gl::GpuPreference gpu_preference) |
| : client_(client), |
| preserve_drawing_buffer_(preserve), |
| webgl_version_(webgl_version), |
| context_provider_(std::make_unique<WebGraphicsContext3DProviderWrapper>( |
| std::move(context_provider))), |
| gl_(ContextProvider()->ContextGL()), |
| extensions_util_(std::move(extensions_util)), |
| discard_framebuffer_supported_(discard_framebuffer_supported), |
| want_alpha_channel_(want_alpha_channel), |
| premultiplied_alpha_(premultiplied_alpha), |
| graphics_info_(graphics_info), |
| using_swap_chain_(using_swap_chain), |
| low_latency_enabled_(desynchronized), |
| want_depth_(want_depth), |
| want_stencil_(want_stencil), |
| color_space_(PredefinedColorSpaceToGfxColorSpace(color_space)), |
| use_half_float_storage_(pixel_format == CanvasPixelFormat::kF16), |
| filter_quality_(filter_quality), |
| chromium_image_usage_(chromium_image_usage), |
| opengl_flip_y_extension_( |
| ContextProvider()->GetCapabilities().mesa_framebuffer_flip_y), |
| initial_gpu_(gpu_preference), |
| current_active_gpu_(gpu_preference), |
| weak_factory_(this) { |
| // Used by browser tests to detect the use of a DrawingBuffer. |
| TRACE_EVENT_INSTANT0("test_gpu", "DrawingBufferCreation", |
| TRACE_EVENT_SCOPE_GLOBAL); |
| // PowerPreferenceToGpuPreference should have resolved the meaning |
| // of the "default" GPU already. |
| DCHECK(gpu_preference != gl::GpuPreference::kDefault); |
| } |
| |
| DrawingBuffer::~DrawingBuffer() { |
| DCHECK(destruction_in_progress_); |
| if (layer_) { |
| layer_->ClearClient(); |
| layer_ = nullptr; |
| } |
| context_provider_ = nullptr; |
| } |
| |
| bool DrawingBuffer::MarkContentsChanged() { |
| if (contents_change_resolved_ || !contents_changed_) { |
| contents_change_resolved_ = false; |
| contents_changed_ = true; |
| return true; |
| } |
| return false; |
| } |
| |
| bool DrawingBuffer::BufferClearNeeded() const { |
| return buffer_clear_needed_; |
| } |
| |
| void DrawingBuffer::SetBufferClearNeeded(bool flag) { |
| if (preserve_drawing_buffer_ == kDiscard) { |
| buffer_clear_needed_ = flag; |
| } else { |
| DCHECK(!buffer_clear_needed_); |
| } |
| } |
| |
| 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::SetIsInHiddenPage(bool hidden) { |
| if (is_hidden_ == hidden) |
| return; |
| is_hidden_ = hidden; |
| if (is_hidden_) |
| recycled_color_buffer_queue_.clear(); |
| gl_->ContextVisibilityHintCHROMIUM(is_hidden_ ? GL_FALSE : GL_TRUE); |
| gl_->Flush(); |
| } |
| |
| void DrawingBuffer::SetFilterQuality( |
| cc::PaintFlags::FilterQuality filter_quality) { |
| if (filter_quality_ != filter_quality) { |
| filter_quality_ = filter_quality; |
| if (layer_) { |
| layer_->SetNearestNeighbor(filter_quality == |
| cc::PaintFlags::FilterQuality::kNone); |
| } |
| } |
| } |
| |
| bool DrawingBuffer::RequiresAlphaChannelToBePreserved() { |
| return client_->DrawingBufferClientIsBoundForDraw() && |
| DefaultBufferRequiresAlphaChannelToBePreserved(); |
| } |
| |
| bool DrawingBuffer::DefaultBufferRequiresAlphaChannelToBePreserved() { |
| return !want_alpha_channel_ && have_alpha_channel_; |
| } |
| |
| void DrawingBuffer::SetDrawBuffer(GLenum draw_buffer) { |
| draw_buffer_ = draw_buffer; |
| } |
| |
| DrawingBuffer::RegisteredBitmap DrawingBuffer::CreateOrRecycleBitmap( |
| cc::SharedBitmapIdRegistrar* bitmap_registrar) { |
| // When searching for a hit in SharedBitmap, we don't consider the bitmap |
| // format (RGBA 8888 vs F16) since the allocated bitmap is always RGBA_8888. |
| auto* it = std::remove_if(recycled_bitmaps_.begin(), recycled_bitmaps_.end(), |
| [this](const RegisteredBitmap& registered) { |
| return registered.bitmap->size() != size_; |
| }); |
| recycled_bitmaps_.Shrink( |
| static_cast<wtf_size_t>(it - recycled_bitmaps_.begin())); |
| |
| if (!recycled_bitmaps_.empty()) { |
| RegisteredBitmap recycled = std::move(recycled_bitmaps_.back()); |
| recycled_bitmaps_.pop_back(); |
| DCHECK(recycled.bitmap->size() == size_); |
| return recycled; |
| } |
| |
| const viz::SharedBitmapId id = viz::SharedBitmap::GenerateId(); |
| const viz::ResourceFormat format = viz::RGBA_8888; |
| base::MappedReadOnlyRegion shm = |
| viz::bitmap_allocation::AllocateSharedBitmap(size_, format); |
| auto bitmap = base::MakeRefCounted<cc::CrossThreadSharedBitmap>( |
| id, std::move(shm), size_, format); |
| RegisteredBitmap registered = { |
| bitmap, bitmap_registrar->RegisterSharedBitmapId(id, bitmap)}; |
| return registered; |
| } |
| |
| bool DrawingBuffer::PrepareTransferableResource( |
| cc::SharedBitmapIdRegistrar* bitmap_registrar, |
| viz::TransferableResource* out_resource, |
| viz::ReleaseCallback* 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); |
| } |
| |
| DrawingBuffer::CheckForDestructionResult |
| DrawingBuffer::CheckForDestructionAndChangeAndResolveIfNeeded() { |
| 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 kDestroyedOrLost; |
| } |
| |
| // There used to be a DCHECK(!is_hidden_) here, but in some tab |
| // switching scenarios, it seems that this can racily be called for |
| // backgrounded tabs. |
| |
| if (!contents_changed_) |
| return kContentsUnchanged; |
| |
| // 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 kDestroyedOrLost; |
| |
| TRACE_EVENT0("blink,rail", "DrawingBuffer::prepareMailbox"); |
| |
| // Resolve the multisampled buffer into the texture attached to fbo_. |
| ResolveIfNeeded(); |
| |
| return kContentsResolvedIfNeeded; |
| } |
| |
| bool DrawingBuffer::PrepareTransferableResourceInternal( |
| cc::SharedBitmapIdRegistrar* bitmap_registrar, |
| viz::TransferableResource* out_resource, |
| viz::ReleaseCallback* out_release_callback, |
| bool force_gpu_result) { |
| if (CheckForDestructionAndChangeAndResolveIfNeeded() != |
| kContentsResolvedIfNeeded) |
| return false; |
| |
| if (!IsUsingGpuCompositing() && !force_gpu_result) { |
| return FinishPrepareTransferableResourceSoftware( |
| bitmap_registrar, out_resource, out_release_callback); |
| } |
| |
| return FinishPrepareTransferableResourceGpu(out_resource, |
| out_release_callback); |
| } |
| |
| scoped_refptr<StaticBitmapImage> |
| DrawingBuffer::GetUnacceleratedStaticBitmapImage(bool flip_y) { |
| ScopedStateRestorer scoped_state_restorer(this); |
| |
| if (CheckForDestructionAndChangeAndResolveIfNeeded() == kDestroyedOrLost) |
| return nullptr; |
| |
| SkBitmap bitmap; |
| if (!bitmap.tryAllocN32Pixels(size_.width(), size_.height())) |
| return nullptr; |
| ReadFramebufferIntoBitmapPixels(static_cast<uint8_t*>(bitmap.getPixels())); |
| auto sk_image = SkImage::MakeFromBitmap(bitmap); |
| |
| bool origin_top_left = |
| flip_y ? opengl_flip_y_extension_ : !opengl_flip_y_extension_; |
| |
| return sk_image ? UnacceleratedStaticBitmapImage::Create( |
| sk_image, origin_top_left |
| ? ImageOrientationEnum::kOriginTopLeft |
| : ImageOrientationEnum::kOriginBottomLeft) |
| : nullptr; |
| } |
| |
| void DrawingBuffer::ReadFramebufferIntoBitmapPixels(uint8_t* pixels) { |
| DCHECK(pixels); |
| DCHECK(state_restorer_); |
| 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_); |
| |
| // Readback in Skia native byte order (RGBA or BGRA) with kN32_SkColorType. |
| const size_t buffer_size = |
| viz::ResourceSizes::CheckedSizeInBytes<size_t>(size_, viz::RGBA_8888); |
| ReadBackFramebuffer(base::span<uint8_t>(pixels, buffer_size), |
| kN32_SkColorType, op); |
| } |
| |
| bool DrawingBuffer::FinishPrepareTransferableResourceSoftware( |
| cc::SharedBitmapIdRegistrar* bitmap_registrar, |
| viz::TransferableResource* out_resource, |
| viz::ReleaseCallback* out_release_callback) { |
| DCHECK(state_restorer_); |
| RegisteredBitmap registered = CreateOrRecycleBitmap(bitmap_registrar); |
| |
| ReadFramebufferIntoBitmapPixels( |
| static_cast<uint8_t*>(registered.bitmap->memory())); |
| |
| *out_resource = viz::TransferableResource::MakeSoftware( |
| registered.bitmap->id(), size_, viz::RGBA_8888); |
| out_resource->color_space = back_color_buffer_->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. |
| *out_release_callback = |
| base::BindOnce(&DrawingBuffer::MailboxReleasedSoftware, |
| weak_factory_.GetWeakPtr(), std::move(registered)); |
| |
| contents_changed_ = false; |
| if (preserve_drawing_buffer_ == kDiscard) { |
| SetBufferClearNeeded(true); |
| } |
| return true; |
| } |
| |
| bool DrawingBuffer::FinishPrepareTransferableResourceGpu( |
| viz::TransferableResource* out_resource, |
| viz::ReleaseCallback* 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) { |
| // 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(); |
| if (!back_color_buffer_) { |
| // Context is likely lost. |
| return false; |
| } |
| AttachColorBufferToReadFramebuffer(); |
| |
| // Explicitly specify that m_fbo (which is now bound to the just-allocated |
| // m_backColorBuffer) is not initialized, to save GPU memory bandwidth on |
| // tile-based GPU architectures. Note that the depth and stencil attachments |
| // are also discarded before multisample resolves, implicit or explicit. |
| 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. |
| // TODO(sunnyps): We can skip this test if explicit resolve is used since |
| // we'll render to the multisample fbo which will be preserved. |
| color_buffer_for_mailbox = CreateOrRecycleColorBuffer(); |
| if (!color_buffer_for_mailbox) { |
| // Context is likely lost. |
| return false; |
| } |
| 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); |
| } |
| |
| // Signal we will no longer access |color_buffer_for_mailbox| before exporting |
| // it. |
| gl_->EndSharedImageAccessDirectCHROMIUM(color_buffer_for_mailbox->texture_id); |
| |
| // Put colorBufferForMailbox into its mailbox, and populate its |
| // produceSyncToken with that point. |
| { |
| // 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 BUILDFLAG(IS_MAC) || BUILDFLAG(IS_ANDROID) |
| // Needed for GPU back-pressure on macOS and Android. 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->gpu_memory_buffer; |
| *out_resource = viz::TransferableResource::MakeGpu( |
| color_buffer_for_mailbox->mailbox, GL_LINEAR, texture_target_, |
| color_buffer_for_mailbox->produce_sync_token, size_, |
| color_buffer_for_mailbox->format, is_overlay_candidate); |
| out_resource->color_space = color_buffer_for_mailbox->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). |
| auto func = base::BindOnce(&DrawingBuffer::NotifyMailboxReleasedGpu, |
| color_buffer_for_mailbox); |
| *out_release_callback = std::move(func); |
| } |
| |
| // Point |m_frontColorBuffer| to the buffer that we are now presenting. |
| front_color_buffer_ = color_buffer_for_mailbox; |
| |
| contents_changed_ = false; |
| if (preserve_drawing_buffer_ == kDiscard) { |
| SetBufferClearNeeded(true); |
| } |
| return true; |
| } |
| |
| // static |
| void DrawingBuffer::NotifyMailboxReleasedGpu( |
| scoped_refptr<ColorBuffer> color_buffer, |
| const gpu::SyncToken& sync_token, |
| bool lost_resource) { |
| DCHECK(color_buffer->owning_thread_ref == base::PlatformThread::CurrentRef()); |
| |
| // 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 (color_buffer->drawing_buffer) { |
| color_buffer->drawing_buffer->MailboxReleasedGpu(color_buffer, |
| lost_resource); |
| } |
| } |
| |
| void DrawingBuffer::MailboxReleasedGpu(scoped_refptr<ColorBuffer> color_buffer, |
| 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; |
| |
| if (destruction_in_progress_ || color_buffer->size != size_ || |
| color_buffer->color_space != color_space_ || |
| 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 = kDefaultColorBufferCacheLimit; |
| 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() { |
| ScopedStateRestorer scoped_state_restorer(this); |
| |
| viz::TransferableResource transferable_resource; |
| viz::ReleaseCallback 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. |
| SkBitmap black_bitmap; |
| if (!black_bitmap.tryAllocN32Pixels(size_.width(), size_.height())) |
| return nullptr; |
| black_bitmap.eraseARGB(0, 0, 0, 0); |
| sk_sp<SkImage> black_image = SkImage::MakeFromBitmap(black_bitmap); |
| if (!black_image) |
| return nullptr; |
| return UnacceleratedStaticBitmapImage::Create(black_image); |
| } |
| |
| DCHECK(release_callback); |
| DCHECK_EQ(size_.width(), transferable_resource.size.width()); |
| DCHECK_EQ(size_.height(), transferable_resource.size.height()); |
| |
| // 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; |
| |
| auto sk_color_type = viz::ResourceFormatToClosestSkColorType( |
| /*gpu_compositing=*/true, transferable_resource.format); |
| |
| const SkImageInfo sk_image_info = SkImageInfo::Make( |
| size_.width(), size_.height(), sk_color_type, kPremul_SkAlphaType); |
| |
| // TODO(xidachen): Create a small pool of recycled textures from |
| // ImageBitmapRenderingContext's transferFromImageBitmap, and try to use them |
| // in DrawingBuffer. |
| return AcceleratedStaticBitmapImage::CreateFromCanvasMailbox( |
| sk_image_mailbox, sk_image_sync_token, /* shared_image_texture_id = */ 0, |
| sk_image_info, transferable_resource.mailbox_holder.texture_target, |
| /* is_origin_top_left = */ opengl_flip_y_extension_, |
| context_provider_->GetWeakPtr(), base::PlatformThread::CurrentRef(), |
| ThreadScheduler::Current()->CleanupTaskRunner(), |
| std::move(release_callback), |
| /*supports_display_compositing=*/true, |
| transferable_resource.is_overlay_candidate); |
| } |
| |
| scoped_refptr<DrawingBuffer::ColorBuffer> |
| DrawingBuffer::CreateOrRecycleColorBuffer() { |
| DCHECK(state_restorer_); |
| if (!recycled_color_buffer_queue_.empty()) { |
| 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_); |
| DCHECK(recycled->color_space == color_space_); |
| gl_->BeginSharedImageAccessDirectCHROMIUM( |
| recycled->texture_id, GL_SHARED_IMAGE_ACCESS_MODE_READWRITE_CHROMIUM); |
| return recycled; |
| } |
| return CreateColorBuffer(size_); |
| } |
| |
| scoped_refptr<CanvasResource> DrawingBuffer::ExportLowLatencyCanvasResource( |
| base::WeakPtr<CanvasResourceProvider> resource_provider) { |
| // Swap chain must be presented before resource is exported. |
| ResolveAndPresentSwapChainIfNeeded(); |
| |
| scoped_refptr<ColorBuffer> canvas_resource_buffer = |
| UsingSwapChain() ? front_color_buffer_ : back_color_buffer_; |
| |
| SkImageInfo resource_info = |
| SkImageInfo::MakeN32Premul(canvas_resource_buffer->size.width(), |
| canvas_resource_buffer->size.height()); |
| switch (canvas_resource_buffer->format) { |
| case viz::RGBA_8888: |
| case viz::RGBX_8888: |
| resource_info = resource_info.makeColorType(kRGBA_8888_SkColorType); |
| break; |
| case viz::RGBA_F16: |
| resource_info = resource_info.makeColorType(kRGBA_F16_SkColorType); |
| break; |
| default: |
| NOTREACHED(); |
| break; |
| } |
| |
| return ExternalCanvasResource::Create( |
| canvas_resource_buffer->mailbox, viz::ReleaseCallback(), gpu::SyncToken(), |
| resource_info, texture_target_, context_provider_->GetWeakPtr(), |
| resource_provider, cc::PaintFlags::FilterQuality::kLow, |
| /*is_origin_top_left=*/opengl_flip_y_extension_, |
| /*is_overlay_candidate=*/true); |
| } |
| |
| scoped_refptr<CanvasResource> DrawingBuffer::ExportCanvasResource() { |
| ScopedStateRestorer scoped_state_restorer(this); |
| TRACE_EVENT0("blink", "DrawingBuffer::ExportCanvasResource"); |
| |
| // Using PrepareTransferableResourceInternal, with force_gpu_result as we |
| // will use this ExportCanvasResource only for gpu_composited content. |
| viz::TransferableResource out_resource; |
| viz::ReleaseCallback out_release_callback; |
| const bool force_gpu_result = true; |
| if (!PrepareTransferableResourceInternal( |
| nullptr, &out_resource, &out_release_callback, force_gpu_result)) |
| return nullptr; |
| |
| SkImageInfo resource_info = SkImageInfo::MakeN32Premul( |
| out_resource.size.width(), out_resource.size.height()); |
| switch (out_resource.format.resource_format()) { |
| case viz::RGBA_8888: |
| resource_info = resource_info.makeColorType(kRGBA_8888_SkColorType); |
| break; |
| case viz::RGBX_8888: |
| resource_info = resource_info.makeColorType(kRGB_888x_SkColorType); |
| break; |
| case viz::RGBA_F16: |
| resource_info = resource_info.makeColorType(kRGBA_F16_SkColorType); |
| break; |
| default: |
| NOTREACHED(); |
| break; |
| } |
| |
| return ExternalCanvasResource::Create( |
| out_resource.mailbox_holder.mailbox, std::move(out_release_callback), |
| out_resource.mailbox_holder.sync_token, resource_info, |
| out_resource.mailbox_holder.texture_target, |
| context_provider_->GetWeakPtr(), /*resource_provider=*/nullptr, |
| cc::PaintFlags::FilterQuality::kLow, |
| /*is_origin_top_left=*/opengl_flip_y_extension_, |
| out_resource.is_overlay_candidate); |
| } |
| |
| DrawingBuffer::ColorBuffer::ColorBuffer( |
| base::WeakPtr<DrawingBuffer> drawing_buffer, |
| const gfx::Size& size, |
| const gfx::ColorSpace& color_space, |
| viz::ResourceFormat format, |
| GLuint texture_id, |
| std::unique_ptr<gfx::GpuMemoryBuffer> gpu_memory_buffer, |
| gpu::Mailbox mailbox) |
| : owning_thread_ref(base::PlatformThread::CurrentRef()), |
| drawing_buffer(std::move(drawing_buffer)), |
| size(size), |
| color_space(color_space), |
| format(format), |
| texture_id(texture_id), |
| gpu_memory_buffer(std::move(gpu_memory_buffer)), |
| mailbox(mailbox) {} |
| |
| DrawingBuffer::ColorBuffer::~ColorBuffer() { |
| if (base::PlatformThread::CurrentRef() != owning_thread_ref || |
| !drawing_buffer) { |
| // If the context has been destroyed no cleanup is necessary since all |
| // resources below are automatically destroyed. Note that if a ColorBuffer |
| // is being destroyed on a different thread, it implies that the owning |
| // thread was destroyed which means the associated context was also |
| // destroyed. |
| return; |
| } |
| |
| gpu::gles2::GLES2Interface* gl = drawing_buffer->gl_; |
| gpu::SharedImageInterface* sii = |
| drawing_buffer->ContextProvider()->SharedImageInterface(); |
| |
| sii->DestroySharedImage(receive_sync_token, mailbox); |
| gpu_memory_buffer.reset(); |
| gl->DeleteTextures(1u, &texture_id); |
| |
| // Avoid deleting this texture if it was unused. |
| if (rgb_workaround_texture_id) |
| gl->DeleteTextures(1u, &rgb_workaround_texture_id); |
| } |
| |
| bool DrawingBuffer::Initialize(const gfx::Size& 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; |
| if (use_multisampling) { |
| gl_->GetIntegerv(GL_MAX_SAMPLES_ANGLE, &max_sample_count); |
| } |
| |
| auto webgl_preferences = ContextProvider()->GetWebglPreferences(); |
| // We can't use anything other than explicit resolve for swap chain. |
| bool supports_implicit_resolve = |
| !UsingSwapChain() && extensions_util_->SupportsExtension( |
| "GL_EXT_multisampled_render_to_texture"); |
| if (webgl_preferences.anti_aliasing_mode == kAntialiasingModeUnspecified) { |
| if (use_multisampling) { |
| anti_aliasing_mode_ = kAntialiasingModeMSAAExplicitResolve; |
| if (supports_implicit_resolve) { |
| anti_aliasing_mode_ = kAntialiasingModeMSAAImplicitResolve; |
| } |
| } else { |
| anti_aliasing_mode_ = kAntialiasingModeNone; |
| } |
| } else { |
| bool prefer_implicit_resolve = (webgl_preferences.anti_aliasing_mode == |
| kAntialiasingModeMSAAImplicitResolve); |
| if (prefer_implicit_resolve && !supports_implicit_resolve) { |
| DLOG(ERROR) << "Invalid anti-aliasing mode specified."; |
| return false; |
| } |
| anti_aliasing_mode_ = webgl_preferences.anti_aliasing_mode; |
| } |
| |
| sample_count_ = std::min( |
| static_cast<int>(webgl_preferences.msaa_sample_count), max_sample_count); |
| eqaa_storage_sample_count_ = webgl_preferences.eqaa_storage_sample_count; |
| if (ContextProvider()->GetGpuFeatureInfo().IsWorkaroundEnabled( |
| gpu::USE_EQAA_STORAGE_SAMPLES_2)) |
| eqaa_storage_sample_count_ = 2; |
| if (extensions_util_->SupportsExtension( |
| "GL_AMD_framebuffer_multisample_advanced")) |
| has_eqaa_support = true; |
| |
| texture_target_ = GL_TEXTURE_2D; |
| |
| #if BUILDFLAG(IS_MAC) |
| if (ShouldUseChromiumImage()) { |
| // A CHROMIUM_image backed texture requires a specialized set of parameters |
| // on OSX. |
| texture_target_ = gpu::GetPlatformSpecificTextureTarget(); |
| } |
| #endif |
| |
| // Initialize the alpha allocation settings based on the features and |
| // workarounds in use. |
| if (want_alpha_channel_) { |
| have_alpha_channel_ = true; |
| } else { |
| have_alpha_channel_ = false; |
| // The following workarounds are used in order of importance; the |
| // first is a correctness issue, the second a major performance |
| // issue, and the third a minor performance issue. |
| 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 |
| have_alpha_channel_ = true; |
| } else 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 |
| have_alpha_channel_ = true; |
| } |
| } |
| |
| state_restorer_->SetFramebufferBindingDirty(); |
| gl_->GenFramebuffers(1, &fbo_); |
| gl_->BindFramebuffer(GL_FRAMEBUFFER, fbo_); |
| if (opengl_flip_y_extension_) |
| gl_->FramebufferParameteri(GL_FRAMEBUFFER, GL_FRAMEBUFFER_FLIP_Y_MESA, 1); |
| |
| if (WantExplicitResolve()) { |
| gl_->GenFramebuffers(1, &multisample_fbo_); |
| gl_->BindFramebuffer(GL_FRAMEBUFFER, multisample_fbo_); |
| gl_->GenRenderbuffers(1, &multisample_renderbuffer_); |
| if (opengl_flip_y_extension_) |
| gl_->FramebufferParameteri(GL_FRAMEBUFFER, GL_FRAMEBUFFER_FLIP_Y_MESA, 1); |
| } |
| if (!ResizeFramebufferInternal(size)) { |
| DLOG(ERROR) << "Initialization failed to allocate backbuffer of size " |
| << size.width() << " x " << size.height() << "."; |
| 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::CopyToPlatformInternal(gpu::InterfaceBase* dst_interface, |
| SourceDrawingBuffer src_buffer, |
| CopyFunctionRef copy_function) { |
| ScopedStateRestorer scoped_state_restorer(this); |
| |
| gpu::gles2::GLES2Interface* src_gl = gl_; |
| |
| if (contents_changed_) { |
| ResolveIfNeeded(); |
| src_gl->Flush(); |
| } |
| |
| // 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; |
| GLuint texture_id_to_restore_access = 0; |
| viz::ResourceFormat format; |
| gfx::Size size; |
| gfx::ColorSpace color_space; |
| if (src_buffer == kFrontBuffer && front_color_buffer_) { |
| mailbox = front_color_buffer_->mailbox; |
| produce_sync_token = front_color_buffer_->produce_sync_token; |
| format = front_color_buffer_->format; |
| size = front_color_buffer_->size; |
| color_space = front_color_buffer_->color_space; |
| } else { |
| GLuint texture_id = 0; |
| if (premultiplied_alpha_false_texture_) { |
| DCHECK(!premultiplied_alpha_false_mailbox_.IsZero()); |
| mailbox = premultiplied_alpha_false_mailbox_; |
| texture_id = premultiplied_alpha_false_texture_; |
| } else { |
| mailbox = back_color_buffer_->mailbox; |
| texture_id = back_color_buffer_->texture_id; |
| } |
| format = back_color_buffer_->format; |
| size = back_color_buffer_->size; |
| color_space = back_color_buffer_->color_space; |
| src_gl->EndSharedImageAccessDirectCHROMIUM(texture_id); |
| src_gl->GenUnverifiedSyncTokenCHROMIUM(produce_sync_token.GetData()); |
| texture_id_to_restore_access = texture_id; |
| } |
| |
| if (!produce_sync_token.HasData()) { |
| // This should only happen if the context has been lost. |
| return false; |
| } |
| |
| dst_interface->WaitSyncTokenCHROMIUM(produce_sync_token.GetConstData()); |
| |
| // Use an empty sync token for `mailbox_holder` because we have already waited |
| // on the required sync tokens above. |
| gpu::MailboxHolder mailbox_holder(mailbox, gpu::SyncToken(), texture_target_); |
| bool succeeded = copy_function(mailbox_holder, format, size, color_space); |
| |
| gpu::SyncToken sync_token; |
| dst_interface->GenUnverifiedSyncTokenCHROMIUM(sync_token.GetData()); |
| src_gl->WaitSyncTokenCHROMIUM(sync_token.GetData()); |
| if (texture_id_to_restore_access) { |
| src_gl->BeginSharedImageAccessDirectCHROMIUM( |
| texture_id_to_restore_access, |
| GL_SHARED_IMAGE_ACCESS_MODE_READWRITE_CHROMIUM); |
| } |
| return succeeded; |
| } |
| |
| bool DrawingBuffer::CopyToPlatformTexture(gpu::gles2::GLES2Interface* dst_gl, |
| GLenum dst_texture_target, |
| GLuint dst_texture, |
| GLint dst_level, |
| bool premultiply_alpha, |
| bool flip_y, |
| const gfx::Point& dst_texture_offset, |
| const gfx::Rect& src_sub_rectangle, |
| SourceDrawingBuffer src_buffer) { |
| if (!Extensions3DUtil::CanUseCopyTextureCHROMIUM(dst_texture_target)) |
| return false; |
| |
| 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; |
| |
| auto copy_function = [&](const gpu::MailboxHolder& src_mailbox, |
| viz::ResourceFormat, const gfx::Size&, |
| const gfx::ColorSpace&) -> bool { |
| GLuint src_texture = dst_gl->CreateAndTexStorage2DSharedImageCHROMIUM( |
| src_mailbox.mailbox.name); |
| dst_gl->BeginSharedImageAccessDirectCHROMIUM( |
| src_texture, GL_SHARED_IMAGE_ACCESS_MODE_READ_CHROMIUM); |
| dst_gl->CopySubTextureCHROMIUM( |
| src_texture, 0, dst_texture_target, dst_texture, dst_level, |
| 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->EndSharedImageAccessDirectCHROMIUM(src_texture); |
| dst_gl->DeleteTextures(1, &src_texture); |
| return true; |
| }; |
| return CopyToPlatformInternal(dst_gl, src_buffer, copy_function); |
| } |
| |
| bool DrawingBuffer::CopyToPlatformMailbox( |
| gpu::raster::RasterInterface* dst_raster_interface, |
| gpu::Mailbox dst_mailbox, |
| GLenum dst_texture_target, |
| bool flip_y, |
| const gfx::Point& dst_texture_offset, |
| const gfx::Rect& src_sub_rectangle, |
| SourceDrawingBuffer src_buffer) { |
| GLboolean unpack_premultiply_alpha_needed = GL_FALSE; |
| if (want_alpha_channel_ && !premultiplied_alpha_) |
| unpack_premultiply_alpha_needed = GL_TRUE; |
| |
| auto copy_function = [&](const gpu::MailboxHolder& src_mailbox, |
| viz::ResourceFormat, const gfx::Size&, |
| const gfx::ColorSpace&) -> bool { |
| dst_raster_interface->CopySubTexture( |
| src_mailbox.mailbox, dst_mailbox, dst_texture_target, |
| 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); |
| return true; |
| }; |
| |
| return CopyToPlatformInternal(dst_raster_interface, src_buffer, |
| copy_function); |
| } |
| |
| bool DrawingBuffer::CopyToVideoFrame( |
| WebGraphicsContext3DVideoFramePool* frame_pool, |
| SourceDrawingBuffer src_buffer, |
| bool src_origin_is_top_left, |
| const gfx::ColorSpace& dst_color_space, |
| WebGraphicsContext3DVideoFramePool::FrameReadyCallback callback) { |
| // Ensure that `frame_pool` has not experienced a context loss. |
| // https://crbug.com/1269230 |
| auto* raster_interface = frame_pool->GetRasterInterface(); |
| if (!raster_interface) |
| return false; |
| const GrSurfaceOrigin src_surface_origin = src_origin_is_top_left |
| ? kTopLeft_GrSurfaceOrigin |
| : kBottomLeft_GrSurfaceOrigin; |
| auto copy_function = |
| [&](const gpu::MailboxHolder& src_mailbox, viz::ResourceFormat src_format, |
| const gfx::Size& src_size, const gfx::ColorSpace& src_color_space) { |
| return frame_pool->CopyRGBATextureToVideoFrame( |
| src_format, src_size, src_color_space, src_surface_origin, |
| src_mailbox, dst_color_space, std::move(callback)); |
| }; |
| return CopyToPlatformInternal(raster_interface, src_buffer, copy_function); |
| } |
| |
| cc::Layer* DrawingBuffer::CcLayer() { |
| if (!layer_) { |
| layer_ = cc::TextureLayer::CreateForMailbox(this); |
| |
| layer_->SetIsDrawable(true); |
| layer_->SetHitTestable(true); |
| layer_->SetContentsOpaque(!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_ == |
| cc::PaintFlags::FilterQuality::kNone); |
| |
| if (opengl_flip_y_extension_ && IsUsingGpuCompositing()) |
| layer_->SetFlipped(false); |
| } |
| |
| return layer_.get(); |
| } |
| |
| void DrawingBuffer::ClearCcLayer() { |
| if (layer_) |
| layer_->ClearTexture(); |
| |
| gl_->Flush(); |
| } |
| |
| void DrawingBuffer::BeginDestruction() { |
| DCHECK(!destruction_in_progress_); |
| destruction_in_progress_ = true; |
| |
| ClearCcLayer(); |
| 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_->EndSharedImageAccessDirectCHROMIUM(premultiplied_alpha_false_texture_); |
| gl_->DeleteTextures(1, &premultiplied_alpha_false_texture_); |
| gpu::SharedImageInterface* sii = ContextProvider()->SharedImageInterface(); |
| gpu::SyncToken sync_token; |
| gl_->GenUnverifiedSyncTokenCHROMIUM(sync_token.GetData()); |
| sii->DestroySharedImage(sync_token, premultiplied_alpha_false_mailbox_); |
| premultiplied_alpha_false_mailbox_.SetZero(); |
| } |
| |
| size_ = gfx::Size(); |
| |
| 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; |
| |
| client_ = nullptr; |
| } |
| |
| bool DrawingBuffer::ReallocateDefaultFramebuffer(const gfx::Size& size, |
| bool only_reallocate_color) { |
| DCHECK(state_restorer_); |
| // Recreate back_color_buffer_. |
| 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. |
| // This also applies to swap chains which are exported via AsCanvasResource(). |
| if ((ShouldUseChromiumImage() || UsingSwapChain()) && have_alpha_channel_ && |
| !premultiplied_alpha_) { |
| gpu::SharedImageInterface* sii = ContextProvider()->SharedImageInterface(); |
| state_restorer_->SetTextureBindingDirty(); |
| // TODO(kbr): unify with code in CreateColorBuffer. |
| if (premultiplied_alpha_false_texture_) { |
| gl_->EndSharedImageAccessDirectCHROMIUM( |
| premultiplied_alpha_false_texture_); |
| gl_->DeleteTextures(1, &premultiplied_alpha_false_texture_); |
| gpu::SyncToken sync_token; |
| gl_->GenUnverifiedSyncTokenCHROMIUM(sync_token.GetData()); |
| sii->DestroySharedImage(sync_token, premultiplied_alpha_false_mailbox_); |
| premultiplied_alpha_false_mailbox_.SetZero(); |
| premultiplied_alpha_false_texture_ = 0; |
| } |
| GrSurfaceOrigin origin = opengl_flip_y_extension_ |
| ? kTopLeft_GrSurfaceOrigin |
| : kBottomLeft_GrSurfaceOrigin; |
| premultiplied_alpha_false_mailbox_ = sii->CreateSharedImage( |
| back_color_buffer_->format, size, back_color_buffer_->color_space, |
| origin, kUnpremul_SkAlphaType, |
| gpu::SHARED_IMAGE_USAGE_GLES2 | |
| gpu::SHARED_IMAGE_USAGE_GLES2_FRAMEBUFFER_HINT | |
| gpu::SHARED_IMAGE_USAGE_RASTER, |
| gpu::kNullSurfaceHandle); |
| gpu::SyncToken sync_token = sii->GenUnverifiedSyncToken(); |
| gl_->WaitSyncTokenCHROMIUM(sync_token.GetConstData()); |
| premultiplied_alpha_false_texture_ = |
| gl_->CreateAndTexStorage2DSharedImageCHROMIUM( |
| premultiplied_alpha_false_mailbox_.name); |
| gl_->BeginSharedImageAccessDirectCHROMIUM( |
| premultiplied_alpha_false_texture_, |
| GL_SHARED_IMAGE_ACCESS_MODE_READWRITE_CHROMIUM); |
| } |
| |
| AttachColorBufferToReadFramebuffer(); |
| |
| if (WantExplicitResolve()) { |
| if (!ReallocateMultisampleRenderbuffer(size)) { |
| return false; |
| } |
| } |
| |
| if (WantDepthOrStencil() && !only_reallocate_color) { |
| 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_ == kAntialiasingModeMSAAImplicitResolve) { |
| gl_->RenderbufferStorageMultisampleEXT(GL_RENDERBUFFER, sample_count_, |
| GL_DEPTH24_STENCIL8_OES, |
| size.width(), size.height()); |
| } else if (anti_aliasing_mode_ == kAntialiasingModeMSAAExplicitResolve) { |
| 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, kClearAllFBOs); |
| } |
| |
| void DrawingBuffer::ClearFramebuffersInternal(GLbitfield clear_mask, |
| ClearOption clear_option) { |
| DCHECK(state_restorer_); |
| state_restorer_->SetFramebufferBindingDirty(); |
| |
| GLenum prev_draw_buffer = |
| draw_buffer_ == GL_BACK ? GL_COLOR_ATTACHMENT0 : draw_buffer_; |
| |
| // Clear the multisample FBO, but also clear the non-multisampled buffer if |
| // requested. |
| if (multisample_fbo_ && clear_option == kClearAllFBOs) { |
| gl_->BindFramebuffer(GL_FRAMEBUFFER, fbo_); |
| ScopedDrawBuffer scoped_draw_buffer(gl_, prev_draw_buffer, |
| GL_COLOR_ATTACHMENT0); |
| gl_->Clear(GL_COLOR_BUFFER_BIT); |
| } |
| |
| if (multisample_fbo_ || clear_option == kClearAllFBOs) { |
| gl_->BindFramebuffer(GL_FRAMEBUFFER, |
| multisample_fbo_ ? multisample_fbo_ : fbo_); |
| ScopedDrawBuffer scoped_draw_buffer(gl_, prev_draw_buffer, |
| GL_COLOR_ATTACHMENT0); |
| gl_->Clear(clear_mask); |
| } |
| } |
| |
| void DrawingBuffer::ClearNewlyAllocatedFramebuffers(ClearOption clear_option) { |
| DCHECK(state_restorer_); |
| |
| 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, clear_option); |
| } |
| |
| gfx::Size DrawingBuffer::AdjustSize(const gfx::Size& desired_size, |
| const gfx::Size& cur_size, |
| int max_texture_size) { |
| gfx::Size 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.set_height(max_texture_size); |
| |
| if (adjusted_size.width() > max_texture_size) |
| adjusted_size.set_width(max_texture_size); |
| |
| return adjusted_size; |
| } |
| |
| bool DrawingBuffer::Resize(const gfx::Size& new_size) { |
| ScopedStateRestorer scoped_state_restorer(this); |
| return ResizeFramebufferInternal(new_size); |
| } |
| |
| bool DrawingBuffer::ResizeFramebufferInternal(const gfx::Size& new_size) { |
| DCHECK(state_restorer_); |
| DCHECK(!new_size.IsEmpty()); |
| gfx::Size adjusted_size = AdjustSize(new_size, size_, max_texture_size_); |
| if (adjusted_size.IsEmpty()) |
| return false; |
| |
| if (adjusted_size != size_) { |
| do { |
| if (!ReallocateDefaultFramebuffer(adjusted_size, |
| /*only_reallocate_color=*/false)) { |
| adjusted_size = |
| gfx::ScaleToFlooredSize(adjusted_size, 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; |
| } |
| |
| ClearNewlyAllocatedFramebuffers(kClearAllFBOs); |
| return true; |
| } |
| |
| void DrawingBuffer::SetColorSpace(PredefinedColorSpace predefined_color_space) { |
| // Color space changes that are no-ops should not reach this point. |
| const gfx::ColorSpace color_space = |
| PredefinedColorSpaceToGfxColorSpace(predefined_color_space); |
| DCHECK_NE(color_space, color_space_); |
| color_space_ = color_space; |
| |
| ScopedStateRestorer scoped_state_restorer(this); |
| |
| // Free all mailboxes, because they are now of the wrong color space. |
| recycled_color_buffer_queue_.clear(); |
| recycled_bitmaps_.clear(); |
| |
| if (!ReallocateDefaultFramebuffer(size_, /*only_reallocate_color=*/true)) { |
| // TODO(https://crbug.com/1208480): What is the correct behavior is we fail |
| // to re-allocate the buffer. |
| DLOG(ERROR) << "Failed to allocate color buffer with new color space."; |
| } |
| |
| ClearNewlyAllocatedFramebuffers(kClearAllFBOs); |
| } |
| |
| bool DrawingBuffer::ResolveAndBindForReadAndDraw() { |
| { |
| ScopedStateRestorer scoped_state_restorer(this); |
| ResolveIfNeeded(); |
| // Note that in rare situations on macOS the drawing buffer can be |
| // destroyed during the resolve process, specifically during |
| // automatic graphics switching. Guard against this. |
| if (destruction_in_progress_) |
| return false; |
| } |
| gl_->BindFramebuffer(GL_FRAMEBUFFER, fbo_); |
| return true; |
| } |
| |
| 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_); |
| } |
| |
| void DrawingBuffer::ResolveIfNeeded() { |
| DCHECK(state_restorer_); |
| if (anti_aliasing_mode_ != kAntialiasingModeNone && |
| !contents_change_resolved_) { |
| if (preserve_drawing_buffer_ == kDiscard && |
| discard_framebuffer_supported_) { |
| // Discard the depth and stencil buffers as early as possible, before |
| // making any potentially-unneeded calls to BindFramebuffer (even no-ops), |
| // in order to maximize the chances that their storage can be kept in tile |
| // memory. |
| const GLenum kAttachments[2] = {GL_DEPTH_ATTACHMENT, |
| GL_STENCIL_ATTACHMENT}; |
| state_restorer_->SetFramebufferBindingDirty(); |
| gl_->BindFramebuffer(GL_FRAMEBUFFER, fbo_); |
| gl_->DiscardFramebufferEXT(GL_FRAMEBUFFER, 2, kAttachments); |
| } |
| ResolveMultisampleFramebufferInternal(); |
| } |
| contents_change_resolved_ = true; |
| |
| auto* gl = ContextProvider()->ContextGL(); |
| gl::GpuPreference active_gpu = gl::GpuPreference::kDefault; |
| if (gl->DidGpuSwitch(&active_gpu) == GL_TRUE) { |
| // This code path is mainly taken on macOS (the only platform which, as of |
| // this writing, dispatches the GPU-switched notifications), and the |
| // comments below focus only on macOS. |
| // |
| // The code below attempts to deduce whether, if a GPU switch occurred, |
| // it's really necessary to lose the context because certain GPU resources |
| // are no longer accessible. Resources only become inaccessible if |
| // CGLSetVirtualScreen is explicitly called against a GL context to change |
| // its renderer ID. GPU switching notifications are highly asynchronous. |
| // |
| // The tests below, of the initial and currently active GPU, replicate |
| // some logic in GLContextCGL::ForceGpuSwitchIfNeeded. Basically, if a |
| // context requests the high-performance GPU, then CGLSetVirtualScreen |
| // will never be called to migrate that context to the low-power |
| // GPU. However, contexts that were allocated on the integrated GPU will |
| // be migrated to the discrete GPU, and back, when the discrete GPU is |
| // activated and deactivated. Also, if the high-performance GPU was |
| // requested, then that request took effect during context bringup, even |
| // though the GPU switching notification is generally dispatched a couple |
| // of seconds after that, so it's not necessary to either lose the context |
| // or reallocate the multisampled renderbuffers when that initial |
| // notification is received. |
| if (initial_gpu_ == gl::GpuPreference::kLowPower && |
| current_active_gpu_ != active_gpu) { |
| if ((WantExplicitResolve() && preserve_drawing_buffer_ == kPreserve) || |
| client_ |
| ->DrawingBufferClientUserAllocatedMultisampledRenderbuffers()) { |
| // In these situations there are multisampled renderbuffers whose |
| // content the application expects to be preserved, but which can not |
| // be. Forcing a lost context is the only option to keep applications |
| // rendering correctly. |
| client_->DrawingBufferClientForceLostContextWithAutoRecovery(); |
| } else if (WantExplicitResolve()) { |
| ReallocateMultisampleRenderbuffer(size_); |
| |
| // This does a bit more work than desired - clearing any depth and |
| // stencil renderbuffers is unnecessary, since they weren't reallocated |
| // - but reusing this code reduces complexity. Note that we do not clear |
| // the non-multisampled framebuffer, as doing so can cause users' |
| // content to disappear unexpectedly. |
| // |
| // TODO(crbug.com/1046146): perform this clear at the beginning rather |
| // than at the end of a frame in order to eliminate rendering glitches. |
| // This should also simplify the code, allowing removal of the |
| // ClearOption. |
| ClearNewlyAllocatedFramebuffers(kClearOnlyMultisampledFBO); |
| } |
| } |
| current_active_gpu_ = active_gpu; |
| } |
| } |
| |
| bool DrawingBuffer::ReallocateMultisampleRenderbuffer(const gfx::Size& size) { |
| 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 |want_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; |
| } |
| if (has_eqaa_support) { |
| gl_->RenderbufferStorageMultisampleAdvancedAMD( |
| GL_RENDERBUFFER, sample_count_, eqaa_storage_sample_count_, |
| internal_format, size.width(), size.height()); |
| } else { |
| 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_); |
| return true; |
| } |
| |
| void DrawingBuffer::RestoreFramebufferBindings() { |
| client_->DrawingBufferClientRestoreFramebufferBinding(); |
| } |
| |
| void DrawingBuffer::RestoreAllState() { |
| client_->DrawingBufferClientRestoreScissorTest(); |
| client_->DrawingBufferClientRestoreMaskAndClearValues(); |
| client_->DrawingBufferClientRestorePixelPackParameters(); |
| client_->DrawingBufferClientRestoreTexture2DBinding(); |
| client_->DrawingBufferClientRestoreTextureCubeMapBinding(); |
| client_->DrawingBufferClientRestoreRenderbufferBinding(); |
| client_->DrawingBufferClientRestoreFramebufferBinding(); |
| client_->DrawingBufferClientRestorePixelUnpackBufferBinding(); |
| client_->DrawingBufferClientRestorePixelPackBufferBinding(); |
| } |
| |
| bool DrawingBuffer::Multisample() const { |
| return anti_aliasing_mode_ != kAntialiasingModeNone; |
| } |
| |
| void DrawingBuffer::Bind(GLenum target) { |
| gl_->BindFramebuffer(target, WantExplicitResolve() ? multisample_fbo_ : fbo_); |
| } |
| |
| GLenum DrawingBuffer::StorageFormat() const { |
| return want_alpha_channel_ ? GL_RGBA8 : GL_RGB8; |
| } |
| |
| sk_sp<SkData> DrawingBuffer::PaintRenderingResultsToDataArray( |
| SourceDrawingBuffer source_buffer) { |
| ScopedStateRestorer scoped_state_restorer(this); |
| |
| // Readback in native GL byte order (RGBA). |
| SkColorType color_type = kRGBA_8888_SkColorType; |
| base::CheckedNumeric<size_t> row_bytes = 4; |
| if (RuntimeEnabledFeatures::WebGLDrawingBufferStorageEnabled() && |
| back_color_buffer_->format == viz::RGBA_F16) { |
| color_type = kRGBA_F16_SkColorType; |
| row_bytes *= 2; |
| } |
| row_bytes *= Size().width(); |
| |
| base::CheckedNumeric<size_t> num_rows = Size().height(); |
| base::CheckedNumeric<size_t> data_size = num_rows * row_bytes; |
| if (!data_size.IsValid()) |
| return nullptr; |
| |
| sk_sp<SkData> dst_buffer = TryAllocateSkData(data_size.ValueOrDie()); |
| if (!dst_buffer) |
| 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_->BeginSharedImageAccessDirectCHROMIUM( |
| front_color_buffer_->texture_id, |
| GL_SHARED_IMAGE_ACCESS_MODE_READ_CHROMIUM); |
| gl_->FramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, |
| texture_target_, front_color_buffer_->texture_id, |
| 0); |
| } else { |
| gl_->BindFramebuffer(GL_FRAMEBUFFER, fbo_); |
| } |
| |
| auto pixels = base::span<uint8_t>( |
| static_cast<uint8_t*>(dst_buffer->writable_data()), dst_buffer->size()); |
| ReadBackFramebuffer(pixels, color_type, |
| WebGLImageConversion::kAlphaDoNothing); |
| FlipVertically(pixels, num_rows.ValueOrDie(), row_bytes.ValueOrDie()); |
| |
| if (fbo) { |
| // The front buffer was used as the source of the pixels via |fbo|; clean up |
| // |fbo| and release access to the front buffer's SharedImage now that the |
| // readback is finished. |
| gl_->FramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, |
| texture_target_, 0, 0); |
| gl_->DeleteFramebuffers(1, &fbo); |
| gl_->EndSharedImageAccessDirectCHROMIUM(front_color_buffer_->texture_id); |
| } |
| |
| return dst_buffer; |
| } |
| |
| void DrawingBuffer::ReadBackFramebuffer(base::span<uint8_t> pixels, |
| SkColorType color_type, |
| 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; |
| |
| base::CheckedNumeric<size_t> expected_data_size = 4; |
| expected_data_size *= Size().width(); |
| expected_data_size *= Size().height(); |
| |
| if (RuntimeEnabledFeatures::WebGLDrawingBufferStorageEnabled() && |
| color_type == kRGBA_F16_SkColorType) { |
| data_type = (webgl_version_ > kWebGL1) ? GL_HALF_FLOAT : GL_HALF_FLOAT_OES; |
| expected_data_size *= 2; |
| } |
| |
| DCHECK_EQ(expected_data_size.ValueOrDie(), pixels.size()); |
| |
| gl_->ReadPixels(0, 0, Size().width(), Size().height(), GL_RGBA, data_type, |
| pixels.data()); |
| |
| // For half float storage Skia order is RGBA, hence no swizzling is needed. |
| if (color_type == kBGRA_8888_SkColorType) { |
| // Swizzle red and blue channels to match SkBitmap's byte ordering. |
| // TODO(kbr): expose GL_BGRA as extension. |
| for (size_t i = 0; i < pixels.size(); i += 4) { |
| std::swap(pixels[i], pixels[i + 2]); |
| } |
| } |
| |
| if (op == WebGLImageConversion::kAlphaDoPremultiply) { |
| for (size_t i = 0; i < pixels.size(); i += 4) { |
| uint8_t alpha = pixels[i + 3]; |
| for (size_t j = 0; j < 3; j++) |
| pixels[i + j] = (pixels[i + j] * alpha + 127) / 255; |
| } |
| } else if (op != WebGLImageConversion::kAlphaDoNothing) { |
| NOTREACHED(); |
| } |
| } |
| |
| void DrawingBuffer::ResolveAndPresentSwapChainIfNeeded() { |
| if (!contents_changed_) |
| return; |
| |
| ScopedStateRestorer scoped_state_restorer(this); |
| ResolveIfNeeded(); |
| |
| if (!UsingSwapChain()) |
| return; |
| |
| DCHECK_EQ(texture_target_, static_cast<unsigned>(GL_TEXTURE_2D)); |
| |
| if (premultiplied_alpha_false_texture_) { |
| // The rendering results are in |premultiplied_alpha_false_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); |
| } |
| |
| gpu::SyncToken sync_token; |
| gl_->GenUnverifiedSyncTokenCHROMIUM(sync_token.GetData()); |
| |
| auto* sii = ContextProvider()->SharedImageInterface(); |
| sii->PresentSwapChain(sync_token, back_color_buffer_->mailbox); |
| |
| sync_token = sii->GenUnverifiedSyncToken(); |
| gl_->WaitSyncTokenCHROMIUM(sync_token.GetConstData()); |
| |
| // If a multisample fbo is used it already preserves the previous contents. |
| if (preserve_drawing_buffer_ == kPreserve && !WantExplicitResolve()) { |
| // If premultiply alpha is false rendering results are in |
| // |premultiplied_alpha_false_texture_|. |
| GLuint dest_texture_id = premultiplied_alpha_false_texture_ |
| ? premultiplied_alpha_false_texture_ |
| : back_color_buffer_->texture_id; |
| gl_->CopySubTextureCHROMIUM(front_color_buffer_->texture_id, 0, |
| texture_target_, dest_texture_id, 0, 0, 0, 0, 0, |
| size_.width(), size_.height(), GL_FALSE, |
| GL_FALSE, GL_FALSE); |
| } |
| contents_changed_ = false; |
| if (preserve_drawing_buffer_ == kDiscard) { |
| SetBufferClearNeeded(true); |
| } |
| } |
| |
| scoped_refptr<DrawingBuffer::ColorBuffer> DrawingBuffer::CreateColorBuffer( |
| const gfx::Size& size) { |
| if (size.IsEmpty()) { |
| // Context is likely lost. |
| return nullptr; |
| } |
| |
| DCHECK(state_restorer_); |
| state_restorer_->SetFramebufferBindingDirty(); |
| state_restorer_->SetTextureBindingDirty(); |
| |
| gpu::SharedImageInterface* sii = ContextProvider()->SharedImageInterface(); |
| gpu::GpuMemoryBufferManager* gpu_memory_buffer_manager = |
| Platform::Current()->GetGpuMemoryBufferManager(); |
| |
| gpu::Mailbox back_buffer_mailbox; |
| // Set only when using swap chains. |
| gpu::Mailbox front_buffer_mailbox; |
| GLuint texture_id = 0; |
| std::unique_ptr<gfx::GpuMemoryBuffer> gpu_memory_buffer; |
| uint32_t usage = gpu::SHARED_IMAGE_USAGE_GLES2 | |
| gpu::SHARED_IMAGE_USAGE_GLES2_FRAMEBUFFER_HINT | |
| gpu::SHARED_IMAGE_USAGE_DISPLAY_READ; |
| if (initial_gpu_ == gl::GpuPreference::kHighPerformance) |
| usage |= gpu::SHARED_IMAGE_USAGE_HIGH_PERFORMANCE_GPU; |
| GrSurfaceOrigin origin = opengl_flip_y_extension_ |
| ? kTopLeft_GrSurfaceOrigin |
| : kBottomLeft_GrSurfaceOrigin; |
| |
| viz::ResourceFormat format; |
| if (have_alpha_channel_) { |
| format = use_half_float_storage_ ? viz::RGBA_F16 : viz::RGBA_8888; |
| } else { |
| DCHECK(!use_half_float_storage_); |
| format = viz::RGBX_8888; |
| } |
| if (UsingSwapChain()) { |
| gpu::SharedImageInterface::SwapChainMailboxes mailboxes = |
| sii->CreateSwapChain(format, size, color_space_, origin, |
| kPremul_SkAlphaType, |
| usage | gpu::SHARED_IMAGE_USAGE_SCANOUT); |
| back_buffer_mailbox = mailboxes.back_buffer; |
| front_buffer_mailbox = mailboxes.front_buffer; |
| } else { |
| if (ShouldUseChromiumImage()) { |
| gfx::BufferFormat buffer_format; |
| if (have_alpha_channel_) { |
| buffer_format = use_half_float_storage_ ? gfx::BufferFormat::RGBA_F16 |
| : gfx::BufferFormat::RGBA_8888; |
| } 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; |
| } |
| } |
| // TODO(crbug.com/911176): When RGB emulation is not needed, we should use |
| // the non-GMB CreateSharedImage with gpu::SHARED_IMAGE_USAGE_SCANOUT in |
| // order to allocate the GMB service-side and avoid a synchronous |
| // round-trip to the browser process here. |
| gfx::BufferUsage buffer_usage = gfx::BufferUsage::SCANOUT; |
| uint32_t additional_usage_flags = gpu::SHARED_IMAGE_USAGE_SCANOUT; |
| if (low_latency_enabled()) { |
| buffer_usage = gfx::BufferUsage::SCANOUT_FRONT_RENDERING; |
| additional_usage_flags = gpu::SHARED_IMAGE_USAGE_CONCURRENT_READ_WRITE; |
| } |
| |
| if (gpu::IsImageFromGpuMemoryBufferFormatSupported( |
| buffer_format, ContextProvider()->GetCapabilities())) { |
| gpu_memory_buffer = gpu_memory_buffer_manager->CreateGpuMemoryBuffer( |
| size, buffer_format, buffer_usage, gpu::kNullSurfaceHandle, |
| nullptr); |
| if (gpu_memory_buffer) { |
| gpu_memory_buffer->SetColorSpace(color_space_); |
| back_buffer_mailbox = sii->CreateSharedImage( |
| gpu_memory_buffer.get(), gpu_memory_buffer_manager, color_space_, |
| origin, kPremul_SkAlphaType, usage | additional_usage_flags); |
| } |
| } |
| } |
| |
| // Create a normal SharedImage if GpuMemoryBuffer is not needed or the |
| // allocation above failed. |
| if (!gpu_memory_buffer) { |
| // We want to set the correct SkAlphaType on the new shared image but in |
| // the case of ShouldUseChromiumImage() we instead keep this buffer |
| // premultiplied, draw to |premultiplied_alpha_false_mailbox_|, and |
| // convert during copy. |
| SkAlphaType alpha_type = kPremul_SkAlphaType; |
| if (!ShouldUseChromiumImage() && !premultiplied_alpha_) |
| alpha_type = kUnpremul_SkAlphaType; |
| |
| back_buffer_mailbox = |
| sii->CreateSharedImage(format, size, color_space_, origin, alpha_type, |
| usage, gpu::kNullSurfaceHandle); |
| } |
| } |
| |
| gpu::SyncToken sync_token = sii->GenUnverifiedSyncToken(); |
| gl_->WaitSyncTokenCHROMIUM(sync_token.GetConstData()); |
| if (!front_buffer_mailbox.IsZero()) { |
| DCHECK(UsingSwapChain()); |
| // Import frontbuffer of swap chain into GL. |
| texture_id = gl_->CreateAndTexStorage2DSharedImageCHROMIUM( |
| front_buffer_mailbox.name); |
| front_color_buffer_ = base::MakeRefCounted<ColorBuffer>( |
| weak_factory_.GetWeakPtr(), size, color_space_, format, texture_id, |
| nullptr, front_buffer_mailbox); |
| } |
| // Import the backbuffer of swap chain or allocated SharedImage into GL. |
| texture_id = |
| gl_->CreateAndTexStorage2DSharedImageCHROMIUM(back_buffer_mailbox.name); |
| gl_->BeginSharedImageAccessDirectCHROMIUM( |
| texture_id, GL_SHARED_IMAGE_ACCESS_MODE_READWRITE_CHROMIUM); |
| gl_->BindTexture(texture_target_, texture_id); |
| |
| // Clear the alpha channel if RGB emulation is required. |
| if (!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::MakeRefCounted<ColorBuffer>( |
| weak_factory_.GetWeakPtr(), size, color_space_, format, texture_id, |
| std::move(gpu_memory_buffer), back_buffer_mailbox); |
| } |
| |
| 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_ == kAntialiasingModeMSAAImplicitResolve) { |
| 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_ == kAntialiasingModeMSAAExplicitResolve; |
| } |
| |
| bool DrawingBuffer::WantDepthOrStencil() { |
| return want_depth_ || want_stencil_; |
| } |
| |
| 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 |