1*8975f5c5SAndroid Build Coastguard Worker// 2*8975f5c5SAndroid Build Coastguard Worker// Copyright 2019 The ANGLE Project Authors. All rights reserved. 3*8975f5c5SAndroid Build Coastguard Worker// Use of this source code is governed by a BSD-style license that can be 4*8975f5c5SAndroid Build Coastguard Worker// found in the LICENSE file. 5*8975f5c5SAndroid Build Coastguard Worker// 6*8975f5c5SAndroid Build Coastguard Worker// IOSurfaceSurfaceMtl.mm: 7*8975f5c5SAndroid Build Coastguard Worker// Implements the class methods for IOSurfaceSurfaceMtl. 8*8975f5c5SAndroid Build Coastguard Worker// 9*8975f5c5SAndroid Build Coastguard Worker 10*8975f5c5SAndroid Build Coastguard Worker#include "libANGLE/renderer/metal/IOSurfaceSurfaceMtl.h" 11*8975f5c5SAndroid Build Coastguard Worker 12*8975f5c5SAndroid Build Coastguard Worker#include <TargetConditionals.h> 13*8975f5c5SAndroid Build Coastguard Worker 14*8975f5c5SAndroid Build Coastguard Worker#include "libANGLE/Display.h" 15*8975f5c5SAndroid Build Coastguard Worker#include "libANGLE/Surface.h" 16*8975f5c5SAndroid Build Coastguard Worker#include "libANGLE/renderer/metal/ContextMtl.h" 17*8975f5c5SAndroid Build Coastguard Worker#include "libANGLE/renderer/metal/DisplayMtl.h" 18*8975f5c5SAndroid Build Coastguard Worker#include "libANGLE/renderer/metal/FrameBufferMtl.h" 19*8975f5c5SAndroid Build Coastguard Worker#include "libANGLE/renderer/metal/mtl_format_utils.h" 20*8975f5c5SAndroid Build Coastguard Worker#include "libANGLE/renderer/metal/mtl_utils.h" 21*8975f5c5SAndroid Build Coastguard Worker 22*8975f5c5SAndroid Build Coastguard Worker// Compiler can turn on programmatical frame capture in release build by defining 23*8975f5c5SAndroid Build Coastguard Worker// ANGLE_METAL_FRAME_CAPTURE flag. 24*8975f5c5SAndroid Build Coastguard Worker#if defined(NDEBUG) && !defined(ANGLE_METAL_FRAME_CAPTURE) 25*8975f5c5SAndroid Build Coastguard Worker# define ANGLE_METAL_FRAME_CAPTURE_ENABLED 0 26*8975f5c5SAndroid Build Coastguard Worker#else 27*8975f5c5SAndroid Build Coastguard Worker# define ANGLE_METAL_FRAME_CAPTURE_ENABLED 1 28*8975f5c5SAndroid Build Coastguard Worker#endif 29*8975f5c5SAndroid Build Coastguard Workernamespace rx 30*8975f5c5SAndroid Build Coastguard Worker{ 31*8975f5c5SAndroid Build Coastguard Worker 32*8975f5c5SAndroid Build Coastguard Workernamespace 33*8975f5c5SAndroid Build Coastguard Worker{ 34*8975f5c5SAndroid Build Coastguard Worker 35*8975f5c5SAndroid Build Coastguard Workerstruct IOSurfaceFormatInfo 36*8975f5c5SAndroid Build Coastguard Worker{ 37*8975f5c5SAndroid Build Coastguard Worker GLenum internalFormat; 38*8975f5c5SAndroid Build Coastguard Worker GLenum type; 39*8975f5c5SAndroid Build Coastguard Worker size_t componentBytes; 40*8975f5c5SAndroid Build Coastguard Worker 41*8975f5c5SAndroid Build Coastguard Worker angle::FormatID nativeAngleFormatId; 42*8975f5c5SAndroid Build Coastguard Worker}; 43*8975f5c5SAndroid Build Coastguard Worker 44*8975f5c5SAndroid Build Coastguard Worker// clang-format off 45*8975f5c5SAndroid Build Coastguard Worker// GL_RGB is a special case. The native angle::FormatID would be either R8G8B8X8_UNORM 46*8975f5c5SAndroid Build Coastguard Worker// or B8G8R8X8_UNORM based on the IOSurface's pixel format. 47*8975f5c5SAndroid Build Coastguard Workerconstexpr std::array<IOSurfaceFormatInfo, 9> kIOSurfaceFormats = {{ 48*8975f5c5SAndroid Build Coastguard Worker {GL_RED, GL_UNSIGNED_BYTE, 1, angle::FormatID::R8_UNORM}, 49*8975f5c5SAndroid Build Coastguard Worker {GL_RED, GL_UNSIGNED_SHORT, 2, angle::FormatID::R16_UNORM}, 50*8975f5c5SAndroid Build Coastguard Worker {GL_RG, GL_UNSIGNED_BYTE, 2, angle::FormatID::R8G8_UNORM}, 51*8975f5c5SAndroid Build Coastguard Worker {GL_RG, GL_UNSIGNED_SHORT, 4, angle::FormatID::R16G16_UNORM}, 52*8975f5c5SAndroid Build Coastguard Worker {GL_RGB, GL_UNSIGNED_BYTE, 4, angle::FormatID::NONE}, 53*8975f5c5SAndroid Build Coastguard Worker {GL_RGBA, GL_UNSIGNED_BYTE, 4, angle::FormatID::R8G8B8A8_UNORM}, 54*8975f5c5SAndroid Build Coastguard Worker {GL_BGRA_EXT, GL_UNSIGNED_BYTE, 4, angle::FormatID::B8G8R8A8_UNORM}, 55*8975f5c5SAndroid Build Coastguard Worker {GL_RGBA, GL_HALF_FLOAT, 8, angle::FormatID::R16G16B16A16_FLOAT}, 56*8975f5c5SAndroid Build Coastguard Worker {GL_RGB10_A2, GL_UNSIGNED_INT_2_10_10_10_REV, 4, angle::FormatID::B10G10R10A2_UNORM}, 57*8975f5c5SAndroid Build Coastguard Worker}}; 58*8975f5c5SAndroid Build Coastguard Worker// clang-format on 59*8975f5c5SAndroid Build Coastguard Worker 60*8975f5c5SAndroid Build Coastguard Workerint FindIOSurfaceFormatIndex(GLenum internalFormat, GLenum type) 61*8975f5c5SAndroid Build Coastguard Worker{ 62*8975f5c5SAndroid Build Coastguard Worker for (int i = 0; i < static_cast<int>(kIOSurfaceFormats.size()); ++i) 63*8975f5c5SAndroid Build Coastguard Worker { 64*8975f5c5SAndroid Build Coastguard Worker const auto &formatInfo = kIOSurfaceFormats[i]; 65*8975f5c5SAndroid Build Coastguard Worker if (formatInfo.internalFormat == internalFormat && formatInfo.type == type) 66*8975f5c5SAndroid Build Coastguard Worker { 67*8975f5c5SAndroid Build Coastguard Worker return i; 68*8975f5c5SAndroid Build Coastguard Worker } 69*8975f5c5SAndroid Build Coastguard Worker } 70*8975f5c5SAndroid Build Coastguard Worker return -1; 71*8975f5c5SAndroid Build Coastguard Worker} 72*8975f5c5SAndroid Build Coastguard Worker 73*8975f5c5SAndroid Build Coastguard Worker} // anonymous namespace 74*8975f5c5SAndroid Build Coastguard Worker 75*8975f5c5SAndroid Build Coastguard Worker// IOSurfaceSurfaceMtl implementation. 76*8975f5c5SAndroid Build Coastguard WorkerIOSurfaceSurfaceMtl::IOSurfaceSurfaceMtl(DisplayMtl *display, 77*8975f5c5SAndroid Build Coastguard Worker const egl::SurfaceState &state, 78*8975f5c5SAndroid Build Coastguard Worker EGLClientBuffer buffer, 79*8975f5c5SAndroid Build Coastguard Worker const egl::AttributeMap &attribs) 80*8975f5c5SAndroid Build Coastguard Worker : OffscreenSurfaceMtl(display, state, attribs), mIOSurface((__bridge IOSurfaceRef)(buffer)) 81*8975f5c5SAndroid Build Coastguard Worker{ 82*8975f5c5SAndroid Build Coastguard Worker CFRetain(mIOSurface); 83*8975f5c5SAndroid Build Coastguard Worker 84*8975f5c5SAndroid Build Coastguard Worker mIOSurfacePlane = static_cast<int>(attribs.get(EGL_IOSURFACE_PLANE_ANGLE)); 85*8975f5c5SAndroid Build Coastguard Worker 86*8975f5c5SAndroid Build Coastguard Worker EGLAttrib internalFormat = attribs.get(EGL_TEXTURE_INTERNAL_FORMAT_ANGLE); 87*8975f5c5SAndroid Build Coastguard Worker EGLAttrib type = attribs.get(EGL_TEXTURE_TYPE_ANGLE); 88*8975f5c5SAndroid Build Coastguard Worker mIOSurfaceFormatIdx = 89*8975f5c5SAndroid Build Coastguard Worker FindIOSurfaceFormatIndex(static_cast<GLenum>(internalFormat), static_cast<GLenum>(type)); 90*8975f5c5SAndroid Build Coastguard Worker ASSERT(mIOSurfaceFormatIdx >= 0); 91*8975f5c5SAndroid Build Coastguard Worker 92*8975f5c5SAndroid Build Coastguard Worker angle::FormatID actualAngleFormatId = 93*8975f5c5SAndroid Build Coastguard Worker kIOSurfaceFormats[mIOSurfaceFormatIdx].nativeAngleFormatId; 94*8975f5c5SAndroid Build Coastguard Worker if (actualAngleFormatId == angle::FormatID::NONE) 95*8975f5c5SAndroid Build Coastguard Worker { 96*8975f5c5SAndroid Build Coastguard Worker // The actual angle::Format depends on the IOSurface's format. 97*8975f5c5SAndroid Build Coastguard Worker ASSERT(internalFormat == GL_RGB); 98*8975f5c5SAndroid Build Coastguard Worker switch (IOSurfaceGetPixelFormat(mIOSurface)) 99*8975f5c5SAndroid Build Coastguard Worker { 100*8975f5c5SAndroid Build Coastguard Worker case 'BGRA': 101*8975f5c5SAndroid Build Coastguard Worker actualAngleFormatId = angle::FormatID::B8G8R8X8_UNORM; 102*8975f5c5SAndroid Build Coastguard Worker break; 103*8975f5c5SAndroid Build Coastguard Worker case 'RGBA': 104*8975f5c5SAndroid Build Coastguard Worker actualAngleFormatId = angle::FormatID::R8G8B8X8_UNORM; 105*8975f5c5SAndroid Build Coastguard Worker break; 106*8975f5c5SAndroid Build Coastguard Worker default: 107*8975f5c5SAndroid Build Coastguard Worker UNREACHABLE(); 108*8975f5c5SAndroid Build Coastguard Worker } 109*8975f5c5SAndroid Build Coastguard Worker } 110*8975f5c5SAndroid Build Coastguard Worker 111*8975f5c5SAndroid Build Coastguard Worker mColorFormat = display->getPixelFormat(actualAngleFormatId); 112*8975f5c5SAndroid Build Coastguard Worker} 113*8975f5c5SAndroid Build Coastguard WorkerIOSurfaceSurfaceMtl::~IOSurfaceSurfaceMtl() 114*8975f5c5SAndroid Build Coastguard Worker{ 115*8975f5c5SAndroid Build Coastguard Worker if (mIOSurface != nullptr) 116*8975f5c5SAndroid Build Coastguard Worker { 117*8975f5c5SAndroid Build Coastguard Worker CFRelease(mIOSurface); 118*8975f5c5SAndroid Build Coastguard Worker mIOSurface = nullptr; 119*8975f5c5SAndroid Build Coastguard Worker } 120*8975f5c5SAndroid Build Coastguard Worker} 121*8975f5c5SAndroid Build Coastguard Worker 122*8975f5c5SAndroid Build Coastguard Workeregl::Error IOSurfaceSurfaceMtl::bindTexImage(const gl::Context *context, 123*8975f5c5SAndroid Build Coastguard Worker gl::Texture *texture, 124*8975f5c5SAndroid Build Coastguard Worker EGLint buffer) 125*8975f5c5SAndroid Build Coastguard Worker{ 126*8975f5c5SAndroid Build Coastguard Worker ContextMtl *contextMtl = mtl::GetImpl(context); 127*8975f5c5SAndroid Build Coastguard Worker StartFrameCapture(contextMtl); 128*8975f5c5SAndroid Build Coastguard Worker 129*8975f5c5SAndroid Build Coastguard Worker // Initialize offscreen texture if needed: 130*8975f5c5SAndroid Build Coastguard Worker ANGLE_TO_EGL_TRY(ensureColorTextureCreated(context)); 131*8975f5c5SAndroid Build Coastguard Worker 132*8975f5c5SAndroid Build Coastguard Worker return OffscreenSurfaceMtl::bindTexImage(context, texture, buffer); 133*8975f5c5SAndroid Build Coastguard Worker} 134*8975f5c5SAndroid Build Coastguard Worker 135*8975f5c5SAndroid Build Coastguard Workeregl::Error IOSurfaceSurfaceMtl::releaseTexImage(const gl::Context *context, EGLint buffer) 136*8975f5c5SAndroid Build Coastguard Worker{ 137*8975f5c5SAndroid Build Coastguard Worker egl::Error re = OffscreenSurfaceMtl::releaseTexImage(context, buffer); 138*8975f5c5SAndroid Build Coastguard Worker StopFrameCapture(); 139*8975f5c5SAndroid Build Coastguard Worker return re; 140*8975f5c5SAndroid Build Coastguard Worker} 141*8975f5c5SAndroid Build Coastguard Worker 142*8975f5c5SAndroid Build Coastguard Workerangle::Result IOSurfaceSurfaceMtl::getAttachmentRenderTarget( 143*8975f5c5SAndroid Build Coastguard Worker const gl::Context *context, 144*8975f5c5SAndroid Build Coastguard Worker GLenum binding, 145*8975f5c5SAndroid Build Coastguard Worker const gl::ImageIndex &imageIndex, 146*8975f5c5SAndroid Build Coastguard Worker GLsizei samples, 147*8975f5c5SAndroid Build Coastguard Worker FramebufferAttachmentRenderTarget **rtOut) 148*8975f5c5SAndroid Build Coastguard Worker{ 149*8975f5c5SAndroid Build Coastguard Worker // Initialize offscreen texture if needed: 150*8975f5c5SAndroid Build Coastguard Worker ANGLE_TRY(ensureColorTextureCreated(context)); 151*8975f5c5SAndroid Build Coastguard Worker 152*8975f5c5SAndroid Build Coastguard Worker return OffscreenSurfaceMtl::getAttachmentRenderTarget(context, binding, imageIndex, samples, 153*8975f5c5SAndroid Build Coastguard Worker rtOut); 154*8975f5c5SAndroid Build Coastguard Worker} 155*8975f5c5SAndroid Build Coastguard Worker 156*8975f5c5SAndroid Build Coastguard Workerangle::Result IOSurfaceSurfaceMtl::ensureColorTextureCreated(const gl::Context *context) 157*8975f5c5SAndroid Build Coastguard Worker{ 158*8975f5c5SAndroid Build Coastguard Worker if (mColorTexture) 159*8975f5c5SAndroid Build Coastguard Worker { 160*8975f5c5SAndroid Build Coastguard Worker return angle::Result::Continue; 161*8975f5c5SAndroid Build Coastguard Worker } 162*8975f5c5SAndroid Build Coastguard Worker ContextMtl *contextMtl = mtl::GetImpl(context); 163*8975f5c5SAndroid Build Coastguard Worker ANGLE_MTL_OBJC_SCOPE 164*8975f5c5SAndroid Build Coastguard Worker { 165*8975f5c5SAndroid Build Coastguard Worker auto texDesc = 166*8975f5c5SAndroid Build Coastguard Worker [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:mColorFormat.metalFormat 167*8975f5c5SAndroid Build Coastguard Worker width:mSize.width 168*8975f5c5SAndroid Build Coastguard Worker height:mSize.height 169*8975f5c5SAndroid Build Coastguard Worker mipmapped:NO]; 170*8975f5c5SAndroid Build Coastguard Worker 171*8975f5c5SAndroid Build Coastguard Worker texDesc.usage = MTLTextureUsageShaderRead | MTLTextureUsageRenderTarget; 172*8975f5c5SAndroid Build Coastguard Worker 173*8975f5c5SAndroid Build Coastguard Worker mColorTexture = 174*8975f5c5SAndroid Build Coastguard Worker mtl::Texture::MakeFromMetal(contextMtl->getMetalDevice().newTextureWithDescriptor( 175*8975f5c5SAndroid Build Coastguard Worker texDesc, mIOSurface, mIOSurfacePlane)); 176*8975f5c5SAndroid Build Coastguard Worker 177*8975f5c5SAndroid Build Coastguard Worker if (mColorTexture) 178*8975f5c5SAndroid Build Coastguard Worker { 179*8975f5c5SAndroid Build Coastguard Worker size_t resourceSize = EstimateTextureSizeInBytes( 180*8975f5c5SAndroid Build Coastguard Worker mColorFormat, mColorTexture->widthAt0(), mColorTexture->heightAt0(), 181*8975f5c5SAndroid Build Coastguard Worker mColorTexture->depthAt0(), mColorTexture->samples(), mColorTexture->mipmapLevels()); 182*8975f5c5SAndroid Build Coastguard Worker 183*8975f5c5SAndroid Build Coastguard Worker mColorTexture->setEstimatedByteSize(resourceSize); 184*8975f5c5SAndroid Build Coastguard Worker } 185*8975f5c5SAndroid Build Coastguard Worker } 186*8975f5c5SAndroid Build Coastguard Worker 187*8975f5c5SAndroid Build Coastguard Worker mColorRenderTarget.set(mColorTexture, mtl::kZeroNativeMipLevel, 0, mColorFormat); 188*8975f5c5SAndroid Build Coastguard Worker 189*8975f5c5SAndroid Build Coastguard Worker if (kIOSurfaceFormats[mIOSurfaceFormatIdx].internalFormat == GL_RGB) 190*8975f5c5SAndroid Build Coastguard Worker { 191*8975f5c5SAndroid Build Coastguard Worker // This format has emulated alpha channel. Initialize texture's alpha channel to 1.0. 192*8975f5c5SAndroid Build Coastguard Worker const mtl::Format &rgbClearFormat = 193*8975f5c5SAndroid Build Coastguard Worker contextMtl->getPixelFormat(angle::FormatID::R8G8B8_UNORM); 194*8975f5c5SAndroid Build Coastguard Worker ANGLE_TRY(mtl::InitializeTextureContentsGPU( 195*8975f5c5SAndroid Build Coastguard Worker context, mColorTexture, rgbClearFormat, 196*8975f5c5SAndroid Build Coastguard Worker mtl::ImageNativeIndex::FromBaseZeroGLIndex(gl::ImageIndex::Make2D(0)), 197*8975f5c5SAndroid Build Coastguard Worker MTLColorWriteMaskAlpha)); 198*8975f5c5SAndroid Build Coastguard Worker 199*8975f5c5SAndroid Build Coastguard Worker // Disable subsequent rendering to alpha channel. 200*8975f5c5SAndroid Build Coastguard Worker mColorTexture->setColorWritableMask(MTLColorWriteMaskAll & (~MTLColorWriteMaskAlpha)); 201*8975f5c5SAndroid Build Coastguard Worker } 202*8975f5c5SAndroid Build Coastguard Worker // Robust resource init: currently we do not allow passing contents with IOSurfaces. 203*8975f5c5SAndroid Build Coastguard Worker mColorTextureInitialized = false; 204*8975f5c5SAndroid Build Coastguard Worker 205*8975f5c5SAndroid Build Coastguard Worker return angle::Result::Continue; 206*8975f5c5SAndroid Build Coastguard Worker} 207*8975f5c5SAndroid Build Coastguard Worker 208*8975f5c5SAndroid Build Coastguard Worker// static 209*8975f5c5SAndroid Build Coastguard Workerbool IOSurfaceSurfaceMtl::ValidateAttributes(EGLClientBuffer buffer, 210*8975f5c5SAndroid Build Coastguard Worker const egl::AttributeMap &attribs) 211*8975f5c5SAndroid Build Coastguard Worker{ 212*8975f5c5SAndroid Build Coastguard Worker IOSurfaceRef ioSurface = (__bridge IOSurfaceRef)(buffer); 213*8975f5c5SAndroid Build Coastguard Worker 214*8975f5c5SAndroid Build Coastguard Worker // The plane must exist for this IOSurface. IOSurfaceGetPlaneCount can return 0 for non-planar 215*8975f5c5SAndroid Build Coastguard Worker // ioSurfaces but we will treat non-planar like it is a single plane. 216*8975f5c5SAndroid Build Coastguard Worker size_t surfacePlaneCount = std::max(size_t(1), IOSurfaceGetPlaneCount(ioSurface)); 217*8975f5c5SAndroid Build Coastguard Worker EGLAttrib plane = attribs.get(EGL_IOSURFACE_PLANE_ANGLE); 218*8975f5c5SAndroid Build Coastguard Worker if (plane < 0 || static_cast<size_t>(plane) >= surfacePlaneCount) 219*8975f5c5SAndroid Build Coastguard Worker { 220*8975f5c5SAndroid Build Coastguard Worker return false; 221*8975f5c5SAndroid Build Coastguard Worker } 222*8975f5c5SAndroid Build Coastguard Worker 223*8975f5c5SAndroid Build Coastguard Worker // The width height specified must be at least (1, 1) and at most the plane size 224*8975f5c5SAndroid Build Coastguard Worker EGLAttrib width = attribs.get(EGL_WIDTH); 225*8975f5c5SAndroid Build Coastguard Worker EGLAttrib height = attribs.get(EGL_HEIGHT); 226*8975f5c5SAndroid Build Coastguard Worker if (width <= 0 || static_cast<size_t>(width) > IOSurfaceGetWidthOfPlane(ioSurface, plane) || 227*8975f5c5SAndroid Build Coastguard Worker height <= 0 || static_cast<size_t>(height) > IOSurfaceGetHeightOfPlane(ioSurface, plane)) 228*8975f5c5SAndroid Build Coastguard Worker { 229*8975f5c5SAndroid Build Coastguard Worker return false; 230*8975f5c5SAndroid Build Coastguard Worker } 231*8975f5c5SAndroid Build Coastguard Worker 232*8975f5c5SAndroid Build Coastguard Worker // Find this IOSurface format 233*8975f5c5SAndroid Build Coastguard Worker EGLAttrib internalFormat = attribs.get(EGL_TEXTURE_INTERNAL_FORMAT_ANGLE); 234*8975f5c5SAndroid Build Coastguard Worker EGLAttrib type = attribs.get(EGL_TEXTURE_TYPE_ANGLE); 235*8975f5c5SAndroid Build Coastguard Worker 236*8975f5c5SAndroid Build Coastguard Worker int formatIndex = 237*8975f5c5SAndroid Build Coastguard Worker FindIOSurfaceFormatIndex(static_cast<GLenum>(internalFormat), static_cast<GLenum>(type)); 238*8975f5c5SAndroid Build Coastguard Worker 239*8975f5c5SAndroid Build Coastguard Worker if (formatIndex < 0) 240*8975f5c5SAndroid Build Coastguard Worker { 241*8975f5c5SAndroid Build Coastguard Worker return false; 242*8975f5c5SAndroid Build Coastguard Worker } 243*8975f5c5SAndroid Build Coastguard Worker 244*8975f5c5SAndroid Build Coastguard Worker // FIXME: Check that the format matches this IOSurface plane for pixel formats that we know of. 245*8975f5c5SAndroid Build Coastguard Worker // We could map IOSurfaceGetPixelFormat to expected type plane and format type. 246*8975f5c5SAndroid Build Coastguard Worker // However, the caller might supply us non-public pixel format, which makes exhaustive checks 247*8975f5c5SAndroid Build Coastguard Worker // problematic. 248*8975f5c5SAndroid Build Coastguard Worker if (IOSurfaceGetBytesPerElementOfPlane(ioSurface, plane) != 249*8975f5c5SAndroid Build Coastguard Worker kIOSurfaceFormats[formatIndex].componentBytes) 250*8975f5c5SAndroid Build Coastguard Worker { 251*8975f5c5SAndroid Build Coastguard Worker WARN() << "IOSurface bytes per elements does not match the pbuffer internal format."; 252*8975f5c5SAndroid Build Coastguard Worker } 253*8975f5c5SAndroid Build Coastguard Worker 254*8975f5c5SAndroid Build Coastguard Worker return true; 255*8975f5c5SAndroid Build Coastguard Worker} 256*8975f5c5SAndroid Build Coastguard Worker} // namespace rx 257