| // Copyright 2017 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "gpu/command_buffer/client/raster_implementation_gles.h" |
| |
| #include <algorithm> |
| #include <cstddef> |
| #include <limits> |
| #include <set> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/compiler_specific.h" |
| #include "base/feature_list.h" |
| #include "base/logging.h" |
| #include "cc/paint/decode_stashing_image_provider.h" |
| #include "cc/paint/display_item_list.h" // nogncheck |
| #include "cc/paint/paint_op_buffer_serializer.h" |
| #include "cc/paint/transfer_cache_entry.h" |
| #include "cc/paint/transfer_cache_serialize_helper.h" |
| #include "gpu/GLES2/gl2extchromium.h" |
| #include "gpu/command_buffer/client/client_shared_image.h" |
| #include "gpu/command_buffer/client/gl_helper.h" |
| #include "gpu/command_buffer/client/gles2_implementation.h" |
| #include "gpu/command_buffer/client/gles2_interface.h" |
| #include "gpu/command_buffer/common/capabilities.h" |
| #include "gpu/command_buffer/common/mailbox.h" |
| #include "ui/gfx/geometry/rect_conversions.h" |
| |
| namespace gpu { |
| namespace raster { |
| |
| namespace { |
| |
| // This is kill-switch for fixing error handling of ReadbackImagePixels |
| // function. |
| // TODO(crbug.com/40058879): Disable this work-around, once call-sites are |
| // handling failures correctly. |
| BASE_FEATURE(kDisableErrorHandlingForReadbackGLES, |
| "kDisableErrorHandlingForReadbackGLES", |
| base::FEATURE_ENABLED_BY_DEFAULT); |
| |
| GLenum SkColorTypeToGLDataFormat(SkColorType color_type, bool supports_rg) { |
| switch (color_type) { |
| case kRGBA_8888_SkColorType: |
| return GL_RGBA; |
| case kBGRA_8888_SkColorType: |
| return GL_BGRA_EXT; |
| case kR8G8_unorm_SkColorType: |
| case kR16G16_unorm_SkColorType: |
| return GL_RG_EXT; |
| case kGray_8_SkColorType: |
| return supports_rg ? GL_RED : GL_LUMINANCE; |
| case kAlpha_8_SkColorType: |
| case kA16_unorm_SkColorType: |
| return supports_rg ? GL_RED : GL_ALPHA; |
| // kA16_float_SkColorType is only used by LUMINANCE_F16 format and hence |
| // should only support GL_LUMINANCE. |
| case kA16_float_SkColorType: |
| return GL_LUMINANCE; |
| case kRGBA_F16_SkColorType: |
| return GL_RGBA16F; |
| default: |
| NOTREACHED() << "Unknown SkColorType " << color_type; |
| } |
| } |
| |
| GLenum SkColorTypeToGLDataType(SkColorType color_type) { |
| switch (color_type) { |
| case kRGBA_8888_SkColorType: |
| case kBGRA_8888_SkColorType: |
| case kR8G8_unorm_SkColorType: |
| case kGray_8_SkColorType: |
| case kAlpha_8_SkColorType: |
| return GL_UNSIGNED_BYTE; |
| case kA16_unorm_SkColorType: |
| case kR16G16_unorm_SkColorType: |
| return GL_UNSIGNED_SHORT; |
| case kA16_float_SkColorType: |
| case kRGBA_F16_SkColorType: |
| return GL_HALF_FLOAT_OES; |
| default: |
| NOTREACHED() << "Unknown SkColorType " << color_type; |
| } |
| } |
| |
| } // namespace |
| |
| RasterImplementationGLES::RasterImplementationGLES( |
| gles2::GLES2Interface* gl, |
| ContextSupport* context_support, |
| const gpu::Capabilities& caps) |
| : gl_(gl), context_support_(context_support), capabilities_(caps) {} |
| |
| RasterImplementationGLES::~RasterImplementationGLES() = default; |
| |
| void RasterImplementationGLES::Finish() { |
| gl_->Finish(); |
| } |
| |
| void RasterImplementationGLES::Flush() { |
| gl_->Flush(); |
| } |
| |
| void RasterImplementationGLES::ShallowFlushCHROMIUM() { |
| gl_->ShallowFlushCHROMIUM(); |
| } |
| |
| void RasterImplementationGLES::OrderingBarrierCHROMIUM() { |
| gl_->OrderingBarrierCHROMIUM(); |
| } |
| |
| GLenum RasterImplementationGLES::GetError() { |
| return gl_->GetError(); |
| } |
| |
| GLenum RasterImplementationGLES::GetGraphicsResetStatusKHR() { |
| return gl_->GetGraphicsResetStatusKHR(); |
| } |
| |
| void RasterImplementationGLES::LoseContextCHROMIUM(GLenum current, |
| GLenum other) { |
| gl_->LoseContextCHROMIUM(current, other); |
| } |
| |
| void RasterImplementationGLES::GenQueriesEXT(GLsizei n, GLuint* queries) { |
| gl_->GenQueriesEXT(n, queries); |
| } |
| |
| void RasterImplementationGLES::DeleteQueriesEXT(GLsizei n, |
| const GLuint* queries) { |
| gl_->DeleteQueriesEXT(n, queries); |
| } |
| |
| void RasterImplementationGLES::BeginQueryEXT(GLenum target, GLuint id) { |
| gl_->BeginQueryEXT(target, id); |
| } |
| |
| void RasterImplementationGLES::EndQueryEXT(GLenum target) { |
| gl_->EndQueryEXT(target); |
| } |
| |
| void RasterImplementationGLES::QueryCounterEXT(GLuint id, GLenum target) { |
| gl_->QueryCounterEXT(id, target); |
| } |
| |
| void RasterImplementationGLES::GetQueryObjectuivEXT(GLuint id, |
| GLenum pname, |
| GLuint* params) { |
| gl_->GetQueryObjectuivEXT(id, pname, params); |
| } |
| |
| void RasterImplementationGLES::GetQueryObjectui64vEXT(GLuint id, |
| GLenum pname, |
| GLuint64* params) { |
| gl_->GetQueryObjectui64vEXT(id, pname, params); |
| } |
| |
| void RasterImplementationGLES::CopySharedImage( |
| const gpu::Mailbox& source_mailbox, |
| const gpu::Mailbox& dest_mailbox, |
| GLint xoffset, |
| GLint yoffset, |
| GLint x, |
| GLint y, |
| GLsizei width, |
| GLsizei height) { |
| if (width < 0) { |
| LOG(ERROR) << "GL_INVALID_VALUE, glCopySharedImage, width < 0"; |
| return; |
| } |
| if (height < 0) { |
| LOG(ERROR) << "GL_INVALID_VALUE, glCopySharedImage, height < 0"; |
| return; |
| } |
| GLbyte mailboxes[sizeof(source_mailbox.name) * 2]; |
| UNSAFE_TODO( |
| memcpy(mailboxes, source_mailbox.name, sizeof(source_mailbox.name))); |
| UNSAFE_TODO(memcpy(mailboxes + sizeof(source_mailbox.name), dest_mailbox.name, |
| sizeof(dest_mailbox.name))); |
| gl_->CopySharedImageINTERNAL(xoffset, yoffset, x, y, width, height, |
| mailboxes); |
| } |
| |
| void RasterImplementationGLES::WritePixels(const gpu::Mailbox& dest_mailbox, |
| int dst_x_offset, |
| int dst_y_offset, |
| GLenum texture_target, |
| const SkPixmap& src_sk_pixmap) { |
| const auto& src_info = src_sk_pixmap.info(); |
| const auto& src_row_bytes = src_sk_pixmap.rowBytes(); |
| DCHECK_GE(src_row_bytes, src_info.minRowBytes()); |
| GLuint texture_id = CreateAndConsumeForGpuRaster(dest_mailbox); |
| BeginSharedImageAccessDirectCHROMIUM( |
| texture_id, GL_SHARED_IMAGE_ACCESS_MODE_READWRITE_CHROMIUM); |
| |
| GLint old_align = 0; |
| gl_->GetIntegerv(GL_UNPACK_ALIGNMENT, &old_align); |
| gl_->PixelStorei(GL_UNPACK_ALIGNMENT, 1); |
| gl_->PixelStorei(GL_UNPACK_ROW_LENGTH, |
| src_row_bytes / src_info.bytesPerPixel()); |
| gl_->BindTexture(texture_target, texture_id); |
| gl_->TexSubImage2D( |
| texture_target, 0, dst_x_offset, dst_y_offset, src_info.width(), |
| src_info.height(), |
| SkColorTypeToGLDataFormat(src_info.colorType(), capabilities_.texture_rg), |
| SkColorTypeToGLDataType(src_info.colorType()), src_sk_pixmap.addr()); |
| gl_->BindTexture(texture_target, 0); |
| gl_->PixelStorei(GL_UNPACK_ROW_LENGTH, 0); |
| gl_->PixelStorei(GL_UNPACK_ALIGNMENT, old_align); |
| |
| EndSharedImageAccessDirectCHROMIUM(texture_id); |
| DeleteGpuRasterTexture(texture_id); |
| } |
| |
| void RasterImplementationGLES::WritePixelsYUV( |
| const gpu::Mailbox& dest_mailbox, |
| const SkYUVAPixmaps& src_yuv_pixmap) { |
| const auto& src_yuv_info = src_yuv_pixmap.yuvaInfo(); |
| const auto& src_yuv_pixmap_info = src_yuv_pixmap.pixmapsInfo(); |
| const std::array<SkPixmap, SkYUVAInfo::kMaxPlanes>& src_sk_pixmaps = |
| src_yuv_pixmap.planes(); |
| |
| gl_->WritePixelsYUVINTERNAL( |
| dest_mailbox.name, src_sk_pixmaps[0].computeByteSize(), |
| src_sk_pixmaps[1].computeByteSize(), src_sk_pixmaps[2].computeByteSize(), |
| src_sk_pixmaps[3].computeByteSize(), src_yuv_info.width(), |
| src_yuv_info.height(), static_cast<int>(src_yuv_info.planeConfig()), |
| static_cast<int>(src_yuv_info.subsampling()), |
| static_cast<int>(src_yuv_pixmap_info.dataType()), |
| src_sk_pixmaps[0].rowBytes(), src_sk_pixmaps[1].rowBytes(), |
| src_sk_pixmaps[2].rowBytes(), src_sk_pixmaps[3].rowBytes(), |
| src_sk_pixmaps[0].addr(), src_sk_pixmaps[1].addr(), |
| src_sk_pixmaps[2].addr(), src_sk_pixmaps[3].addr()); |
| } |
| |
| void RasterImplementationGLES::BeginRasterCHROMIUM( |
| SkColor4f sk_color_4f, |
| GLboolean needs_clear, |
| GLuint msaa_sample_count, |
| MsaaMode msaa_mode, |
| GLboolean can_use_lcd_text, |
| GLboolean visible, |
| const gfx::ColorSpace& color_space, |
| float hdr_headroom, |
| const GLbyte* mailbox) { |
| NOTREACHED(); |
| } |
| |
| void RasterImplementationGLES::RasterCHROMIUM( |
| const cc::DisplayItemList* list, |
| cc::ImageProvider* provider, |
| const gfx::Size& content_size, |
| const gfx::Rect& full_raster_rect, |
| const gfx::Rect& playback_rect, |
| const gfx::Vector2dF& post_translate, |
| const gfx::Vector2dF& post_scale, |
| bool requires_clear, |
| const ScrollOffsetMap* raster_inducing_scroll_offsets, |
| size_t* max_op_size_hint) { |
| NOTREACHED(); |
| } |
| |
| void RasterImplementationGLES::SetActiveURLCHROMIUM(const char* url) { |
| gl_->SetActiveURLCHROMIUM(url); |
| } |
| |
| void RasterImplementationGLES::EndRasterCHROMIUM() { |
| NOTREACHED(); |
| } |
| |
| SyncToken RasterImplementationGLES::ScheduleImageDecode( |
| base::span<const uint8_t> encoded_data, |
| const gfx::Size& output_size, |
| uint32_t transfer_cache_entry_id, |
| const gfx::ColorSpace& target_color_space, |
| bool needs_mips) { |
| NOTREACHED(); |
| } |
| |
| void RasterImplementationGLES::ReadbackARGBPixelsAsync( |
| const gpu::Mailbox& source_mailbox, |
| GLenum source_target, |
| GrSurfaceOrigin src_origin, |
| const gfx::Size& source_size, |
| const gfx::Point& source_starting_point, |
| const SkImageInfo& dst_info, |
| GLuint dst_row_bytes, |
| base::span<uint8_t> out, |
| base::OnceCallback<void(bool)> readback_done) { |
| DCHECK(!readback_done.is_null()); |
| DCHECK(dst_info.colorType() == kRGBA_8888_SkColorType || |
| dst_info.colorType() == kBGRA_8888_SkColorType); |
| GLenum format = |
| dst_info.colorType() == kRGBA_8888_SkColorType ? GL_RGBA : GL_BGRA_EXT; |
| gfx::Size dst_gfx_size(dst_info.width(), dst_info.height()); |
| GLuint texture_id = CreateAndConsumeForGpuRaster(source_mailbox); |
| BeginSharedImageAccessDirectCHROMIUM( |
| texture_id, GL_SHARED_IMAGE_ACCESS_MODE_READ_CHROMIUM); |
| |
| // Convert bottom-left GL coordinates to top-left coordinates expected |
| // by RI clients. |
| bool flip_y; |
| gfx::Point starting_point(source_starting_point); |
| switch (src_origin) { |
| case kTopLeft_GrSurfaceOrigin: |
| flip_y = false; |
| break; |
| case kBottomLeft_GrSurfaceOrigin: |
| // Since RI clients always expect top-left origin, two things need to be |
| // done when texture's origin is bottom-left. |
| |
| // 1. Rows in the output buffer need to be switched vertically. |
| flip_y = true; |
| |
| // 2. Starting of a target rectangle needs to be adjusted from top-left |
| // to bottom-left. That's how glReadPixels expects it. |
| // It's okay if we accidentally go negative here, glReadPixels checks |
| // its input. |
| starting_point.set_y(source_size.height() - starting_point.y() - |
| dst_gfx_size.height()); |
| break; |
| } |
| |
| GetGLHelper()->ReadbackTextureAsync( |
| texture_id, source_target, starting_point, dst_gfx_size, out, |
| dst_row_bytes, flip_y, format, |
| base::BindOnce(&RasterImplementationGLES::OnReadARGBPixelsAsync, |
| weak_ptr_factory_.GetWeakPtr(), texture_id, |
| std::move(readback_done))); |
| } |
| |
| void RasterImplementationGLES::OnReadARGBPixelsAsync( |
| GLuint texture_id, |
| base::OnceCallback<void(bool)> readback_done, |
| bool success) { |
| DCHECK(texture_id); |
| EndSharedImageAccessDirectCHROMIUM(texture_id); |
| DeleteGpuRasterTexture(texture_id); |
| |
| std::move(readback_done).Run(success); |
| } |
| |
| void RasterImplementationGLES::ReadbackYUVPixelsAsync( |
| const gpu::Mailbox& source_mailbox, |
| GLenum source_target, |
| const gfx::Size& source_size, |
| const gfx::Rect& output_rect, |
| bool vertically_flip_texture, |
| int y_plane_row_stride_bytes, |
| base::span<uint8_t> y_plane_data, |
| int u_plane_row_stride_bytes, |
| base::span<uint8_t> u_plane_data, |
| int v_plane_row_stride_bytes, |
| base::span<uint8_t> v_plane_data, |
| const gfx::Point& paste_location, |
| base::OnceCallback<void()> release_mailbox, |
| base::OnceCallback<void(bool)> readback_done) { |
| GLuint shared_texture_id = CreateAndConsumeForGpuRaster(source_mailbox); |
| BeginSharedImageAccessDirectCHROMIUM( |
| shared_texture_id, GL_SHARED_IMAGE_ACCESS_MODE_READ_CHROMIUM); |
| base::OnceCallback<void()> on_release_mailbox = |
| base::BindOnce(&RasterImplementationGLES::OnReleaseMailbox, |
| weak_ptr_factory_.GetWeakPtr(), shared_texture_id, |
| std::move(release_mailbox)); |
| |
| // The YUV readback path only works for 2D textures. |
| GLuint texture_for_readback = shared_texture_id; |
| GLuint copy_texture_id = 0; |
| if (source_target != GL_TEXTURE_2D) { |
| int width = source_size.width(); |
| int height = source_size.height(); |
| |
| gl_->GenTextures(1, ©_texture_id); |
| gl_->BindTexture(GL_TEXTURE_2D, copy_texture_id); |
| gl_->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); |
| gl_->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); |
| 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_->TexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, |
| GL_UNSIGNED_BYTE, nullptr); |
| gl_->CopyTextureCHROMIUM(shared_texture_id, 0, GL_TEXTURE_2D, |
| copy_texture_id, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0, |
| 0, 0); |
| texture_for_readback = copy_texture_id; |
| |
| // |copy_texture_id| now contains the texture we want to copy, release the |
| // pinned mailbox. |
| std::move(on_release_mailbox).Run(); |
| } |
| |
| DCHECK(GetGLHelper()); |
| gpu::ReadbackYUVInterface* const yuv_reader = |
| GetGLHelper()->GetReadbackPipelineYUV(vertically_flip_texture); |
| yuv_reader->ReadbackYUV( |
| texture_for_readback, source_size, gfx::Rect(source_size), |
| y_plane_row_stride_bytes, y_plane_data, u_plane_row_stride_bytes, |
| u_plane_data, v_plane_row_stride_bytes, v_plane_data, paste_location, |
| base::BindOnce(&RasterImplementationGLES::OnReadYUVPixelsAsync, |
| weak_ptr_factory_.GetWeakPtr(), copy_texture_id, |
| std::move(on_release_mailbox), std::move(readback_done))); |
| } |
| |
| void RasterImplementationGLES::OnReadYUVPixelsAsync( |
| GLuint copy_texture_id, |
| base::OnceCallback<void()> on_release_mailbox, |
| base::OnceCallback<void(bool)> readback_done, |
| bool success) { |
| if (copy_texture_id) { |
| DCHECK(on_release_mailbox.is_null()); |
| gl_->DeleteTextures(1, ©_texture_id); |
| } else { |
| DCHECK(!on_release_mailbox.is_null()); |
| std::move(on_release_mailbox).Run(); |
| } |
| |
| std::move(readback_done).Run(success); |
| } |
| |
| void RasterImplementationGLES::OnReleaseMailbox( |
| GLuint shared_texture_id, |
| base::OnceCallback<void()> release_mailbox) { |
| DCHECK(shared_texture_id); |
| DCHECK(!release_mailbox.is_null()); |
| |
| EndSharedImageAccessDirectCHROMIUM(shared_texture_id); |
| DeleteGpuRasterTexture(shared_texture_id); |
| std::move(release_mailbox).Run(); |
| } |
| |
| bool RasterImplementationGLES::ReadbackImagePixels( |
| const gpu::Mailbox& source_mailbox, |
| const SkImageInfo& dst_info, |
| GLuint dst_row_bytes, |
| int src_x, |
| int src_y, |
| int plane_index, |
| void* dst_pixels) { |
| DCHECK_GE(dst_row_bytes, dst_info.minRowBytes()); |
| |
| sk_sp<SkData> dst_color_space_data; |
| if (dst_info.colorSpace()) { |
| dst_color_space_data = dst_info.colorSpace()->serialize(); |
| } |
| |
| GLuint dst_size = dst_info.computeByteSize(dst_row_bytes); |
| return gl_->ReadbackARGBImagePixelsINTERNAL( |
| source_mailbox.name, |
| dst_color_space_data ? dst_color_space_data->data() : nullptr, |
| dst_color_space_data ? dst_color_space_data->size() : 0, dst_size, |
| dst_info.width(), dst_info.height(), dst_info.colorType(), |
| dst_info.alphaType(), dst_row_bytes, src_x, src_y, plane_index, |
| dst_pixels) || |
| base::FeatureList::IsEnabled(kDisableErrorHandlingForReadbackGLES); |
| } |
| |
| GLuint RasterImplementationGLES::CreateAndConsumeForGpuRaster( |
| const gpu::Mailbox& mailbox) { |
| return gl_->CreateAndTexStorage2DSharedImageCHROMIUM(mailbox.name); |
| } |
| |
| GLuint RasterImplementationGLES::CreateAndConsumeForGpuRaster( |
| const scoped_refptr<gpu::ClientSharedImage>& shared_image) { |
| CHECK(shared_image); |
| return CreateAndConsumeForGpuRaster(shared_image->mailbox()); |
| } |
| |
| void RasterImplementationGLES::DeleteGpuRasterTexture(GLuint texture) { |
| gl_->DeleteTextures(1u, &texture); |
| } |
| |
| void RasterImplementationGLES::BeginGpuRaster() { |
| // Using push/pop functions directly incurs cost to evaluate function |
| // arguments even when tracing is disabled. |
| gl_->TraceBeginCHROMIUM("BeginGpuRaster", "GpuRasterization"); |
| } |
| |
| void RasterImplementationGLES::EndGpuRaster() { |
| // Restore default GL unpack alignment. TextureUploader expects this. |
| gl_->PixelStorei(GL_UNPACK_ALIGNMENT, 4); |
| |
| // Using push/pop functions directly incurs cost to evaluate function |
| // arguments even when tracing is disabled. |
| gl_->TraceEndCHROMIUM(); |
| |
| // Reset cached raster state. |
| gl_->ActiveTexture(GL_TEXTURE0); |
| } |
| |
| void RasterImplementationGLES::BeginSharedImageAccessDirectCHROMIUM( |
| GLuint texture, |
| GLenum mode) { |
| gl_->BeginSharedImageAccessDirectCHROMIUM(texture, mode); |
| } |
| |
| void RasterImplementationGLES::EndSharedImageAccessDirectCHROMIUM( |
| GLuint texture) { |
| gl_->EndSharedImageAccessDirectCHROMIUM(texture); |
| } |
| |
| void RasterImplementationGLES::InitializeDiscardableTextureCHROMIUM( |
| GLuint texture) { |
| gl_->InitializeDiscardableTextureCHROMIUM(texture); |
| } |
| |
| void RasterImplementationGLES::UnlockDiscardableTextureCHROMIUM( |
| GLuint texture) { |
| gl_->UnlockDiscardableTextureCHROMIUM(texture); |
| } |
| |
| bool RasterImplementationGLES::LockDiscardableTextureCHROMIUM(GLuint texture) { |
| return gl_->LockDiscardableTextureCHROMIUM(texture); |
| } |
| |
| void RasterImplementationGLES::TraceBeginCHROMIUM(const char* category_name, |
| const char* trace_name) { |
| gl_->TraceBeginCHROMIUM(category_name, trace_name); |
| } |
| |
| void RasterImplementationGLES::TraceEndCHROMIUM() { |
| gl_->TraceEndCHROMIUM(); |
| } |
| |
| // InterfaceBase implementation. |
| void RasterImplementationGLES::GenSyncTokenCHROMIUM(GLbyte* sync_token) { |
| gl_->GenSyncTokenCHROMIUM(sync_token); |
| } |
| void RasterImplementationGLES::GenUnverifiedSyncTokenCHROMIUM( |
| GLbyte* sync_token) { |
| gl_->GenUnverifiedSyncTokenCHROMIUM(sync_token); |
| } |
| void RasterImplementationGLES::VerifySyncTokensCHROMIUM(GLbyte** sync_tokens, |
| GLsizei count) { |
| gl_->VerifySyncTokensCHROMIUM(sync_tokens, count); |
| } |
| void RasterImplementationGLES::WaitSyncTokenCHROMIUM(const GLbyte* sync_token) { |
| gl_->WaitSyncTokenCHROMIUM(sync_token); |
| } |
| |
| GLHelper* RasterImplementationGLES::GetGLHelper() { |
| if (!gl_helper_) { |
| DCHECK(gl_); |
| DCHECK(context_support_); |
| gl_helper_ = std::make_unique<GLHelper>(gl_, context_support_); |
| } |
| |
| return gl_helper_.get(); |
| } |
| |
| } // namespace raster |
| } // namespace gpu |