eglCreatePbufferFromClientBuffer implementation

Added support for eglCreatePbufferFromClientBuffer(), using an
IOSurface on MacOS, or just a straight buffer pointer on other
platforms.

Added new unit tests (IOSurfaceClientBufferTest class), which
pass on both Windows and MacOS.

Change-Id: I79a6b420d85fb1f3ae505e0c0067bad2e27510d4
Reviewed-on: https://swiftshader-review.googlesource.com/17168
Tested-by: Alexis Hétu <sugoi@google.com>
Reviewed-by: Nicolas Capens <nicolascapens@google.com>
diff --git a/extensions/EGL_ANGLE_iosurface_client_buffer.txt b/extensions/EGL_ANGLE_iosurface_client_buffer.txt
new file mode 100644
index 0000000..692f721
--- /dev/null
+++ b/extensions/EGL_ANGLE_iosurface_client_buffer.txt
@@ -0,0 +1,122 @@
+Name
+
+    ANGLE_iosurface_client_buffer
+
+Name Strings
+
+    EGL_ANGLE_iosurface_client_buffer
+
+Contributors
+
+    Corentin Wallez
+    Geoff Lang
+
+Contacts
+
+    Corentin Wallez, Google Inc. (cwallez 'at' google.com)
+
+Status
+
+    Draft
+
+Version
+
+    Version 1, Dec 6, 2017
+
+Number
+
+    EGL Extension #??
+
+Dependencies
+
+    This extension is written against the wording of the EGL 1.4
+    Specification.
+
+Overview
+
+    This extension allows creating EGL surfaces from IOSurface objects.
+
+New Types
+
+    None
+
+New Procedures and Functions
+
+    None
+
+New Tokens
+
+    Accepted in the <buftype> parameter of eglCreatePbufferFromClientBuffer:
+
+        EGL_IOSURFACE_ANGLE            0x3454
+        EGL_IOSURFACE_PLANE_ANGLE      0x345A
+        EGL_TEXTURE_RECTANGLE_ANGLE    0x345B
+        EGL_TEXTURE_TYPE_ANGLE         0x345C
+        EGL_TEXTURE_INTERNAL_FORMAT_ANGLE 0x345D
+
+Additions to Chapter 3 of the EGL 1.4 Specification (EGL Functions and Errors)
+
+    Replace the last sentence of paragraph 1 of Section 3.5.3 with the
+    following text.
+    "Currently, the only client API resources which may be bound in this
+    fashion are OpenVG VGImage objects and IOSurface objects."
+
+    Replace the third paragraph of Section 3.5.3 with the following text.
+    "<buftype> specifies the type of buffer to be bound. The only allowed values
+    of <buftype> are EGL_OPENVG_IMAGE and EGL_IOSURFACE_ANGLE".
+
+    Append the following text to the fourth paragraph of Section 3.5.3.
+    "When <buftype> is EGL_IOSURFACE_ANGLE, <buffer> must be a valid IOSurface
+    object case into the type EGLClientBuffer."
+
+    Append to the end of Section 3.5.3.
+    "When <buftype> is EGL_IOSURFACE_ANGLE, <attrib_list> must contain all the
+    following attributes otherwise EGL_BAD_PARAMETER is generated. The
+    attributes must satisfy the following constraints otherwise
+    EGL_BAD_ATTRIBUTE is generated:
+      - EGL_TEXTURE_TYPE_ANGLE, and EGL_TEXTURE_INTERNAL_FORMAT_ANGLE followed
+    by OpenGL enums for texture types, and texture internal format
+    respectively.
+      - EGL_TEXTURE_FORMAT with a value of EGL_TEXTURE_RGBA
+      - EGL_WIDTH with a value between 1 and the width of <buffer>.
+      - EGL_HEIGHT with a value between 1 and the height of <buffer>.
+      - EGL_TEXTURE_TARGET with a value of EGL_TEXTURE_RECTANGLE_ANGLE
+      - EGL_IOSURFACE_PLANE_ANGLE with a value between 0 and the number of
+    planes of <buffer> (exclusive).
+
+    In addition the EGL_TEXTURE_TYPE_ANGLE and
+    EGL_TEXTURE_INTERNAL_FORMAT_ANGLE attributes must be one of the
+    combinations listed in table egl.iosurface.formats or an
+    EGL_BAD_PARAMETER is generated. The combination must also be a valid
+    combinations for glTexImage2D or EGL_BAD_PARAMETER is generated."
+
+    ---------------------------------------------------------------------------
+    Texture Type               Texture Internal Format
+    ---------------------------------------------------------------------------
+    GL_UNSIGNED_BYTE           GL_RED
+    GL_UNSIGNED_SHORT          GL_R16UI
+    GL_UNSIGNED_BYTE           GL_RG
+    GL_UNSIGNED_BYTE           GL_BGRA_EXT
+    GL_HALF_FLOAT              GL_RGBA
+    ---------------------------------------------------------------------------
+    Table egl.iosurface.formats - Valid combinations of format, type and
+    internal format for IOSurface-backed pbuffers.
+    ---------------------------------------------------------------------------
+
+    Append to the end of Section 3.5.3.
+    "When a pbuffer is created with type EGL_IOSURFACE_ANGLE, the contents
+    of the associcated IOSurface object are undefined while the pbuffer is
+    bound to a client texture."
+
+    Append to the list of errors generated by eglMakeCurrent in Section 3.7.3:
+    "  - If either draw or read are pbuffers created with
+    eglCreatePbufferFromClientBuffer with <buftype> set to EGL_IOSURFACE_ANGLE,
+    an EGL_BAD_SURFACE is generated."
+
+Issues
+
+    There are no issues, please move on.
+
+Revision History
+
+    Version 1, 2017/12/06 - first draft.
diff --git a/src/OpenGL/common/Image.cpp b/src/OpenGL/common/Image.cpp
index 8da7cd3..9c2645d 100644
--- a/src/OpenGL/common/Image.cpp
+++ b/src/OpenGL/common/Image.cpp
@@ -24,6 +24,11 @@
 #include <string.h>
 #include <algorithm>
 
+#if defined(__APPLE__)
+#include <CoreFoundation/CoreFoundation.h>
+#include <IOSurface/IOSurface.h>
+#endif
+
 namespace gl
 {
 	sw::Format ConvertReadFormatType(GLenum format, GLenum type)
@@ -1205,6 +1210,195 @@
 		return new ImageImplementation(width, height, internalformat, multiSampleDepth, lockable);
 	}
 
+	int ClientBuffer::getWidth() const
+	{
+		return width;
+	}
+
+	int ClientBuffer::getHeight() const
+	{
+		return height;
+	}
+
+	sw::Format ClientBuffer::getFormat() const
+	{
+		return format;
+	}
+
+	int ClientBuffer::pitchP() const
+	{
+#if defined(__APPLE__)
+		if(buffer)
+		{
+			IOSurfaceRef ioSurface = reinterpret_cast<IOSurfaceRef>(buffer);
+			int pitchB = static_cast<int>(IOSurfaceGetBytesPerRowOfPlane(ioSurface, plane));
+			int bytesPerPixel = sw::Surface::bytes(format);
+			ASSERT((pitchB % bytesPerPixel) == 0);
+			return pitchB / bytesPerPixel;
+		}
+
+		return 0;
+#else
+		return sw::Surface::pitchP(width, 0, format, false);
+#endif
+	}
+
+	void ClientBuffer::retain()
+	{
+#if defined(__APPLE__)
+		if(buffer)
+		{
+			CFRetain(reinterpret_cast<IOSurfaceRef>(buffer));
+		}
+#endif
+	}
+
+	void ClientBuffer::release()
+	{
+#if defined(__APPLE__)
+		if(buffer)
+		{
+			CFRelease(reinterpret_cast<IOSurfaceRef>(buffer));
+			buffer = nullptr;
+		}
+#endif
+	}
+
+	void* ClientBuffer::lock(int x, int y, int z)
+	{
+#if defined(__APPLE__)
+		if(buffer)
+		{
+			IOSurfaceRef ioSurface = reinterpret_cast<IOSurfaceRef>(buffer);
+			IOSurfaceLock(ioSurface, 0, nullptr);
+			void* pixels = IOSurfaceGetBaseAddressOfPlane(ioSurface, plane);
+			int bytes = sw::Surface::bytes(format);
+			int pitchB = static_cast<int>(IOSurfaceGetBytesPerRowOfPlane(ioSurface, plane));
+			int sliceB = static_cast<int>(IOSurfaceGetHeightOfPlane(ioSurface, plane)) * pitchB;
+			return (unsigned char*)pixels + x * bytes + y * pitchB + z * sliceB;
+		}
+
+		return nullptr;
+#else
+		int bytes = sw::Surface::bytes(format);
+		int pitchB = sw::Surface::pitchB(width, 0, format, false);
+		int sliceB = height * pitchB;
+		return (unsigned char*)buffer + x * bytes + y * pitchB + z * sliceB;
+#endif
+	}
+
+	void ClientBuffer::unlock()
+	{
+#if defined(__APPLE__)
+		if(buffer)
+		{
+			IOSurfaceRef ioSurface = reinterpret_cast<IOSurfaceRef>(buffer);
+			IOSurfaceUnlock(ioSurface, 0, nullptr);
+		}
+#endif
+	}
+
+	class ClientBufferImage : public egl::Image
+	{
+	public:
+		explicit ClientBufferImage(const ClientBuffer& clientBuffer) :
+			egl::Image(clientBuffer.getWidth(),
+				clientBuffer.getHeight(),
+				getClientBufferInternalFormat(clientBuffer.getFormat()),
+				clientBuffer.pitchP()),
+			clientBuffer(clientBuffer)
+		{
+			shared = false;
+			this->clientBuffer.retain();
+		}
+
+	private:
+		ClientBuffer clientBuffer;
+
+		~ClientBufferImage() override
+		{
+			sync();   // Wait for any threads that use this image to finish.
+
+			clientBuffer.release();
+		}
+
+		static GLint getClientBufferInternalFormat(sw::Format format)
+		{
+			switch(format)
+			{
+			case sw::FORMAT_R8:            return GL_R8;
+			case sw::FORMAT_G8R8:          return GL_RG8;
+			case sw::FORMAT_A8R8G8B8:      return GL_BGRA8_EXT;
+			case sw::FORMAT_R16UI:         return GL_R16UI;
+			case sw::FORMAT_A16B16G16R16F: return GL_RGBA16F;
+			default:                       return GL_NONE;
+			}
+		}
+
+		void *lockInternal(int x, int y, int z, sw::Lock lock, sw::Accessor client) override
+		{
+			LOGLOCK("image=%p op=%s.swsurface lock=%d", this, __FUNCTION__, lock);
+
+			// Always do this for reference counting.
+			void *data = sw::Surface::lockInternal(x, y, z, lock, client);
+
+			if(x != 0 || y != 0 || z != 0)
+			{
+				LOGLOCK("badness: %s called with unsupported parms: image=%p x=%d y=%d z=%d", __FUNCTION__, this, x, y, z);
+			}
+
+			LOGLOCK("image=%p op=%s.ani lock=%d", this, __FUNCTION__, lock);
+
+			// Lock the ClientBuffer and use its address.
+			data = clientBuffer.lock(x, y, z);
+
+			if(lock == sw::LOCK_UNLOCKED)
+			{
+				// We're never going to get a corresponding unlock, so unlock
+				// immediately. This keeps the reference counts sane.
+				clientBuffer.unlock();
+			}
+
+			return data;
+		}
+
+		void unlockInternal() override
+		{
+			LOGLOCK("image=%p op=%s.ani", this, __FUNCTION__);
+			clientBuffer.unlock();
+
+			LOGLOCK("image=%p op=%s.swsurface", this, __FUNCTION__);
+			sw::Surface::unlockInternal();
+		}
+
+		void *lock(int x, int y, int z, sw::Lock lock) override
+		{
+			LOGLOCK("image=%p op=%s lock=%d", this, __FUNCTION__, lock);
+			(void)sw::Surface::lockExternal(x, y, z, lock, sw::PUBLIC);
+
+			return clientBuffer.lock(x, y, z);
+		}
+
+		void unlock() override
+		{
+			LOGLOCK("image=%p op=%s.ani", this, __FUNCTION__);
+			clientBuffer.unlock();
+
+			LOGLOCK("image=%p op=%s.swsurface", this, __FUNCTION__);
+			sw::Surface::unlockExternal();
+		}
+
+		void release() override
+		{
+			Image::release();
+		}
+	};
+
+	Image *Image::create(const egl::ClientBuffer& clientBuffer)
+	{
+		return new ClientBufferImage(clientBuffer);
+	}
+
 	Image::~Image()
 	{
 		// sync() must be called in the destructor of the most derived class to ensure their vtable isn't destroyed
diff --git a/src/OpenGL/common/Image.hpp b/src/OpenGL/common/Image.hpp
index 3b984b9..5e924c0 100644
--- a/src/OpenGL/common/Image.hpp
+++ b/src/OpenGL/common/Image.hpp
@@ -63,6 +63,30 @@
 namespace egl
 {
 
+class ClientBuffer
+{
+public:
+	ClientBuffer(int width, int height, sw::Format format, void* buffer, size_t plane)
+		: width(width), height(height), format(format), buffer(buffer), plane(plane)
+	{}
+
+	int getWidth() const;
+	int getHeight() const;
+	sw::Format getFormat() const;
+	int pitchP() const;
+	void retain();
+	void release();
+	void* lock(int x, int y, int z);
+	void unlock();
+
+private:
+	int width;
+	int height;
+	sw::Format format;
+	void* buffer;
+	size_t plane;
+};
+
 class [[clang::lto_visibility_public]] Image : public sw::Surface, public gl::Object
 {
 protected:
@@ -117,6 +141,9 @@
 	// Render target
 	static Image *create(GLsizei width, GLsizei height, GLint internalformat, int multiSampleDepth, bool lockable);
 
+	// Back buffer from client buffer
+	static Image *create(const egl::ClientBuffer& clientBuffer);
+
 	GLsizei getWidth() const
 	{
 		return width;
diff --git a/src/OpenGL/common/Surface.hpp b/src/OpenGL/common/Surface.hpp
index 493d676..c9e1973 100644
--- a/src/OpenGL/common/Surface.hpp
+++ b/src/OpenGL/common/Surface.hpp
@@ -42,6 +42,7 @@
 
 	virtual EGLint getWidth() const = 0;
 	virtual EGLint getHeight() const = 0;
+	virtual EGLenum getTextureTarget() const = 0;
 
 	virtual void setBoundTexture(egl::Texture *texture) = 0;
 };
diff --git a/src/OpenGL/libEGL/BUILD.gn b/src/OpenGL/libEGL/BUILD.gn
index 24727cf..c9ea43b 100644
--- a/src/OpenGL/libEGL/BUILD.gn
+++ b/src/OpenGL/libEGL/BUILD.gn
@@ -67,6 +67,8 @@
     libs = [
       "Quartz.framework",
       "Cocoa.framework",
+      "CoreFoundation.framework",
+      "IOSurface.framework",
     ]
     ldflags = [ "-Wl,-install_name,@rpath/libswiftshader_libEGL.dylib" ]
   } else if (is_win) {
diff --git a/src/OpenGL/libEGL/Display.cpp b/src/OpenGL/libEGL/Display.cpp
index 0e58125..a5c87b9 100644
--- a/src/OpenGL/libEGL/Display.cpp
+++ b/src/OpenGL/libEGL/Display.cpp
@@ -34,6 +34,8 @@
 #include "Main/libX11.hpp"
 #elif defined(__APPLE__)
 #include "OSXUtils.hpp"
+#include <CoreFoundation/CoreFoundation.h>
+#include <IOSurface/IOSurface.h>
 #endif
 
 #include <algorithm>
@@ -338,11 +340,13 @@
 	return success(surface);
 }
 
-EGLSurface Display::createPBufferSurface(EGLConfig config, const EGLint *attribList)
+EGLSurface Display::createPBufferSurface(EGLConfig config, const EGLint *attribList, EGLClientBuffer clientBuffer)
 {
-	EGLint width = 0, height = 0;
+	EGLint width = -1, height = -1, ioSurfacePlane = -1;
 	EGLenum textureFormat = EGL_NO_TEXTURE;
 	EGLenum textureTarget = EGL_NO_TEXTURE;
+	EGLenum clientBufferFormat = EGL_NO_TEXTURE;
+	EGLenum clientBufferType = EGL_NO_TEXTURE;
 	EGLBoolean largestPBuffer = EGL_FALSE;
 	const Config *configuration = mConfigSet.get(config);
 
@@ -373,11 +377,45 @@
 					return error(EGL_BAD_ATTRIBUTE, EGL_NO_SURFACE);
 				}
 				break;
+			case EGL_TEXTURE_INTERNAL_FORMAT_ANGLE:
+				switch(attribList[1])
+				{
+				case GL_RED:
+				case GL_R16UI:
+				case GL_RG:
+				case GL_BGRA_EXT:
+				case GL_RGBA:
+					clientBufferFormat = attribList[1];
+					break;
+				default:
+					return error(EGL_BAD_ATTRIBUTE, EGL_NO_SURFACE);
+				}
+				break;
+			case EGL_TEXTURE_TYPE_ANGLE:
+				switch(attribList[1])
+				{
+				case GL_UNSIGNED_BYTE:
+				case GL_UNSIGNED_SHORT:
+				case GL_HALF_FLOAT:
+					clientBufferType = attribList[1];
+					break;
+				default:
+					return error(EGL_BAD_ATTRIBUTE, EGL_NO_SURFACE);
+				}
+				break;
+			case EGL_IOSURFACE_PLANE_ANGLE:
+				if(attribList[1] < 0)
+				{
+					return error(EGL_BAD_ATTRIBUTE, EGL_NO_SURFACE);
+				}
+				ioSurfacePlane = attribList[1];
+				break;
 			case EGL_TEXTURE_TARGET:
 				switch(attribList[1])
 				{
 				case EGL_NO_TEXTURE:
 				case EGL_TEXTURE_2D:
+				case EGL_TEXTURE_RECTANGLE_ANGLE:
 					textureTarget = attribList[1];
 					break;
 				default:
@@ -423,13 +461,92 @@
 		return error(EGL_BAD_MATCH, EGL_NO_SURFACE);
 	}
 
-	if((textureFormat == EGL_TEXTURE_RGB && configuration->mBindToTextureRGB != EGL_TRUE) ||
-	   (textureFormat == EGL_TEXTURE_RGBA && configuration->mBindToTextureRGBA != EGL_TRUE))
+	if(clientBuffer)
 	{
-		return error(EGL_BAD_ATTRIBUTE, EGL_NO_SURFACE);
+		switch(clientBufferType)
+		{
+		case GL_UNSIGNED_BYTE:
+			switch(clientBufferFormat)
+			{
+			case GL_RED:
+			case GL_RG:
+			case GL_BGRA_EXT:
+				break;
+			case GL_R16UI:
+			case GL_RGBA:
+				return error(EGL_BAD_ATTRIBUTE, EGL_NO_SURFACE);
+			default:
+				return error(EGL_BAD_PARAMETER, EGL_NO_SURFACE);
+			}
+			break;
+		case GL_UNSIGNED_SHORT:
+			switch(clientBufferFormat)
+			{
+			case GL_R16UI:
+				break;
+			case GL_RED:
+			case GL_RG:
+			case GL_BGRA_EXT:
+			case GL_RGBA:
+				return error(EGL_BAD_ATTRIBUTE, EGL_NO_SURFACE);
+			default:
+				return error(EGL_BAD_PARAMETER, EGL_NO_SURFACE);
+			}
+			break;
+		case GL_HALF_FLOAT:
+			switch(clientBufferFormat)
+			{
+			case GL_RGBA:
+				break;
+			case GL_RED:
+			case GL_R16UI:
+			case GL_RG:
+			case GL_BGRA_EXT:
+				return error(EGL_BAD_ATTRIBUTE, EGL_NO_SURFACE);
+			default:
+				return error(EGL_BAD_PARAMETER, EGL_NO_SURFACE);
+			}
+			break;
+		default:
+			return error(EGL_BAD_PARAMETER, EGL_NO_SURFACE);
+		}
+
+		if(ioSurfacePlane < 0)
+		{
+			return error(EGL_BAD_PARAMETER, EGL_NO_SURFACE);
+		}
+
+		if(textureFormat != EGL_TEXTURE_RGBA)
+		{
+			return error(EGL_BAD_ATTRIBUTE, EGL_NO_SURFACE);
+		}
+
+		if(textureTarget != EGL_TEXTURE_RECTANGLE_ANGLE)
+		{
+			return error(EGL_BAD_ATTRIBUTE, EGL_NO_SURFACE);
+		}
+
+#if defined(__APPLE__)
+		IOSurfaceRef ioSurface = reinterpret_cast<IOSurfaceRef>(clientBuffer);
+		size_t planeCount = IOSurfaceGetPlaneCount(ioSurface);
+		if((static_cast<size_t>(width) > IOSurfaceGetWidthOfPlane(ioSurface, ioSurfacePlane)) ||
+		   (static_cast<size_t>(height) > IOSurfaceGetHeightOfPlane(ioSurface, ioSurfacePlane)) ||
+		   ((planeCount != 0) && static_cast<size_t>(ioSurfacePlane) >= planeCount))
+		{
+			return error(EGL_BAD_ATTRIBUTE, EGL_NO_SURFACE);
+		}
+#endif
+	}
+	else
+	{
+		if((textureFormat == EGL_TEXTURE_RGB && configuration->mBindToTextureRGB != EGL_TRUE) ||
+		   ((textureFormat == EGL_TEXTURE_RGBA && configuration->mBindToTextureRGBA != EGL_TRUE)))
+		{
+			return error(EGL_BAD_ATTRIBUTE, EGL_NO_SURFACE);
+		}
 	}
 
-	Surface *surface = new PBufferSurface(this, configuration, width, height, textureFormat, textureTarget, largestPBuffer);
+	Surface *surface = new PBufferSurface(this, configuration, width, height, textureFormat, textureTarget, clientBufferFormat, clientBufferType, largestPBuffer, clientBuffer, ioSurfacePlane);
 
 	if(!surface->initialize())
 	{
diff --git a/src/OpenGL/libEGL/Display.h b/src/OpenGL/libEGL/Display.h
index 5944d69..30f01b9 100644
--- a/src/OpenGL/libEGL/Display.h
+++ b/src/OpenGL/libEGL/Display.h
@@ -26,6 +26,15 @@
 
 #include <set>
 
+#ifndef EGL_ANGLE_iosurface_client_buffer
+#define EGL_ANGLE_iosurface_client_buffer 1
+#define EGL_IOSURFACE_ANGLE 0x3454
+#define EGL_IOSURFACE_PLANE_ANGLE 0x345A
+#define EGL_TEXTURE_RECTANGLE_ANGLE 0x345B
+#define EGL_TEXTURE_TYPE_ANGLE 0x345C
+#define EGL_TEXTURE_INTERNAL_FORMAT_ANGLE 0x345D
+#endif // EGL_ANGLE_iosurface_client_buffer
+
 namespace egl
 {
 	class Surface;
@@ -51,7 +60,7 @@
 		bool getConfigAttrib(EGLConfig config, EGLint attribute, EGLint *value);
 
 		EGLSurface createWindowSurface(EGLNativeWindowType window, EGLConfig config, const EGLint *attribList);
-		EGLSurface createPBufferSurface(EGLConfig config, const EGLint *attribList);
+		EGLSurface createPBufferSurface(EGLConfig config, const EGLint *attribList, EGLClientBuffer clientBuffer = nullptr);
 		EGLContext createContext(EGLConfig configHandle, const Context *shareContext, EGLint clientVersion);
 		EGLSyncKHR createSync(Context *context);
 
diff --git a/src/OpenGL/libEGL/Surface.cpp b/src/OpenGL/libEGL/Surface.cpp
index d373990..ead75a6 100644
--- a/src/OpenGL/libEGL/Surface.cpp
+++ b/src/OpenGL/libEGL/Surface.cpp
@@ -63,6 +63,10 @@
 	swapBehavior = EGL_BUFFER_PRESERVED;
 	textureFormat = EGL_NO_TEXTURE;
 	textureTarget = EGL_NO_TEXTURE;
+	clientBufferFormat = EGL_NO_TEXTURE;
+	clientBufferType = EGL_NO_TEXTURE;
+	clientBuffer = nullptr;
+	clientBufferPlane = -1;
 	swapInterval = -1;
 	setSwapInterval(1);
 }
@@ -78,7 +82,15 @@
 
 	if(libGLESv2)
 	{
-		backBuffer = libGLESv2->createBackBuffer(width, height, config->mRenderTargetFormat, config->mSamples);
+		if(clientBuffer)
+		{
+			backBuffer = libGLESv2->createBackBufferFromClientBuffer(
+				egl::ClientBuffer(width, height, getClientBufferFormat(), clientBuffer, clientBufferPlane));
+		}
+		else
+		{
+			backBuffer = libGLESv2->createBackBuffer(width, height, config->mRenderTargetFormat, config->mSamples);
+		}
 	}
 	else if(libGLES_CM)
 	{
@@ -222,6 +234,51 @@
 	return largestPBuffer;
 }
 
+sw::Format Surface::getClientBufferFormat() const
+{
+	switch(clientBufferType)
+	{
+	case GL_UNSIGNED_BYTE:
+		switch(clientBufferFormat)
+		{
+		case GL_RED:
+			return sw::FORMAT_R8;
+		case GL_RG:
+			return sw::FORMAT_G8R8;
+		case GL_BGRA_EXT:
+			return sw::FORMAT_A8R8G8B8;
+		default:
+			UNREACHABLE(clientBufferFormat);
+			break;
+		}
+		break;
+	case GL_UNSIGNED_SHORT:
+		switch(clientBufferFormat)
+		{
+		case GL_R16UI:
+			return sw::FORMAT_R16UI;
+		default:
+			UNREACHABLE(clientBufferFormat);
+			break;
+		}
+		break;
+	case GL_HALF_FLOAT:
+		switch(clientBufferFormat)
+		{
+		case GL_RGBA:
+			return sw::FORMAT_A16B16G16R16F;
+		default:
+			UNREACHABLE(clientBufferFormat);
+			break;
+		}
+	default:
+		UNREACHABLE(clientBufferType);
+		break;
+	}
+
+	return sw::FORMAT_NULL;
+}
+
 void Surface::setBoundTexture(egl::Texture *texture)
 {
 	this->texture = texture;
@@ -356,12 +413,21 @@
 	return Surface::initialize();
 }
 
-PBufferSurface::PBufferSurface(Display *display, const Config *config, EGLint width, EGLint height, EGLenum textureFormat, EGLenum textureType, EGLBoolean largestPBuffer)
+PBufferSurface::PBufferSurface(Display *display, const Config *config, EGLint width, EGLint height,
+                               EGLenum textureFormat, EGLenum textureTarget, EGLenum clientBufferFormat,
+                               EGLenum clientBufferType, EGLBoolean largestPBuffer, EGLClientBuffer clientBuffer,
+                               EGLint clientBufferPlane)
 	: Surface(display, config)
 {
 	this->width = width;
 	this->height = height;
 	this->largestPBuffer = largestPBuffer;
+	this->textureFormat = textureFormat;
+	this->textureTarget = textureTarget;
+	this->clientBufferFormat = clientBufferFormat;
+	this->clientBufferType = clientBufferType;
+	this->clientBuffer = clientBuffer;
+	this->clientBufferPlane = clientBufferPlane;
 }
 
 PBufferSurface::~PBufferSurface()
diff --git a/src/OpenGL/libEGL/Surface.hpp b/src/OpenGL/libEGL/Surface.hpp
index 2e9e26b..be43edb 100644
--- a/src/OpenGL/libEGL/Surface.hpp
+++ b/src/OpenGL/libEGL/Surface.hpp
@@ -48,11 +48,11 @@
 
 	EGLint getWidth() const override;
 	EGLint getHeight() const override;
+	EGLenum getTextureTarget() const override;
 	virtual EGLint getPixelAspectRatio() const;
 	virtual EGLenum getRenderBuffer() const;
 	virtual EGLenum getSwapBehavior() const;
 	virtual EGLenum getTextureFormat() const;
-	virtual EGLenum getTextureTarget() const;
 	virtual EGLBoolean getLargestPBuffer() const;
 	virtual EGLNativeWindowType getWindowHandle() const = 0;
 
@@ -61,6 +61,7 @@
 
 	virtual bool isWindowSurface() const { return false; }
 	virtual bool isPBufferSurface() const { return false; }
+	bool hasClientBuffer() const { return clientBuffer != nullptr; }
 
 protected:
 	Surface(const Display *display, const Config *config);
@@ -69,6 +70,8 @@
 
 	virtual void deleteResources();
 
+	sw::Format getClientBufferFormat() const;
+
 	const Display *const display;
 	Image *depthStencil;
 	Image *backBuffer;
@@ -77,8 +80,8 @@
 	bool reset(int backbufferWidth, int backbufferHeight);
 
 	const Config *const config;    // EGL config surface was created with
-	EGLint height;                 // Height of surface
 	EGLint width;                  // Width of surface
+	EGLint height;                 // Height of surface
 //  EGLint horizontalResolution;   // Horizontal dot pitch
 //  EGLint verticalResolution;     // Vertical dot pitch
 	EGLBoolean largestPBuffer;     // If true, create largest pbuffer possible
@@ -90,9 +93,13 @@
 	EGLenum swapBehavior;          // Buffer swap behavior
 	EGLenum textureFormat;         // Format of texture: RGB, RGBA, or no texture
 	EGLenum textureTarget;         // Type of texture: 2D or no texture
+	EGLenum clientBufferFormat;    // Format of the client buffer
+	EGLenum clientBufferType;      // Type of the client buffer
 //  EGLenum vgAlphaFormat;         // Alpha format for OpenVG
 //  EGLenum vgColorSpace;          // Color space for OpenVG
 	EGLint swapInterval;
+	EGLClientBuffer clientBuffer;
+	EGLint clientBufferPlane;
 };
 
 class WindowSurface : public Surface
@@ -120,7 +127,10 @@
 class PBufferSurface : public Surface
 {
 public:
-	PBufferSurface(Display *display, const egl::Config *config, EGLint width, EGLint height, EGLenum textureFormat, EGLenum textureTarget, EGLBoolean largestPBuffer);
+	PBufferSurface(Display *display, const egl::Config *config, EGLint width, EGLint height,
+	               EGLenum textureFormat, EGLenum textureTarget, EGLenum internalFormat,
+	               EGLenum textureType, EGLBoolean largestPBuffer, EGLClientBuffer clientBuffer,
+	               EGLint clientBufferPlane);
 	~PBufferSurface() override;
 
 	bool isPBufferSurface() const override { return true; }
diff --git a/src/OpenGL/libEGL/libEGL.cpp b/src/OpenGL/libEGL/libEGL.cpp
index 55ee224..74510df 100644
--- a/src/OpenGL/libEGL/libEGL.cpp
+++ b/src/OpenGL/libEGL/libEGL.cpp
@@ -204,6 +204,7 @@
 		               "EGL_KHR_fence_sync "
 		               "EGL_KHR_image_base "
 		               "EGL_KHR_surfaceless_context "
+		               "EGL_ANGLE_iosurface_client_buffer "
 		               "EGL_ANDROID_framebuffer_target "
 		               "EGL_ANDROID_recordable");
 	case EGL_VENDOR:
@@ -507,9 +508,25 @@
 	      "EGLConfig config = %p, const EGLint *attrib_list = %p)",
 	      dpy, buftype, buffer, config, attrib_list);
 
-	UNIMPLEMENTED();
+	switch(buftype)
+	{
+	case EGL_IOSURFACE_ANGLE:
+	{
+		egl::Display *display = egl::Display::get(dpy);
 
-	return error(EGL_BAD_PARAMETER, EGL_NO_SURFACE);
+		if(!validateConfig(display, config))
+		{
+			return EGL_NO_SURFACE;
+		}
+
+		return display->createPBufferSurface(config, attrib_list, buffer);
+	}
+	case EGL_OPENVG_IMAGE:
+		UNIMPLEMENTED();
+		return error(EGL_BAD_PARAMETER, EGL_NO_SURFACE);
+	default:
+		return error(EGL_BAD_PARAMETER, EGL_NO_SURFACE);
+	};
 }
 
 EGLBoolean SurfaceAttrib(EGLDisplay dpy, EGLSurface surface, EGLint attribute, EGLint value)
@@ -810,6 +827,13 @@
 		return EGL_FALSE;
 	}
 
+	if((draw != EGL_NO_SURFACE && drawSurface->hasClientBuffer()) ||
+	   (read != EGL_NO_SURFACE && readSurface->hasClientBuffer()))
+	{
+		// Make current is not supported on IOSurface pbuffers.
+		return error(EGL_BAD_SURFACE, EGL_FALSE);
+	}
+
 	if((draw != EGL_NO_SURFACE) ^ (read != EGL_NO_SURFACE))
 	{
 		return error(EGL_BAD_MATCH, EGL_FALSE);
diff --git a/src/OpenGL/libGLESv2/BUILD.gn b/src/OpenGL/libGLESv2/BUILD.gn
index b4383c9..82d3191 100644
--- a/src/OpenGL/libGLESv2/BUILD.gn
+++ b/src/OpenGL/libGLESv2/BUILD.gn
@@ -109,6 +109,10 @@
   if (is_win) {
     ldflags = [ "/DEF:" + rebase_path("libGLESv2.def", root_build_dir) ]
   } else if (is_mac) {
+    libs = [
+      "CoreFoundation.framework",
+      "IOSurface.framework",
+    ]
     ldflags = [ "-Wl,-install_name,@rpath/libswiftshader_libGLESv2.dylib" ]
   } else if (is_linux) {
     ldflags =
diff --git a/src/OpenGL/libGLESv2/Context.cpp b/src/OpenGL/libGLESv2/Context.cpp
index 6233e8f..5148e6b 100644
--- a/src/OpenGL/libGLESv2/Context.cpp
+++ b/src/OpenGL/libGLESv2/Context.cpp
@@ -4093,8 +4093,8 @@
 	{
 		GLenum readColorbufferType = readFramebuffer->getReadBufferType();
 		GLenum drawColorbufferType = drawFramebuffer->getColorbufferType(0);
-		const bool validReadType = readColorbufferType == GL_TEXTURE_2D || Framebuffer::IsRenderbuffer(readColorbufferType);
-		const bool validDrawType = drawColorbufferType == GL_TEXTURE_2D || Framebuffer::IsRenderbuffer(drawColorbufferType);
+		const bool validReadType = readColorbufferType == GL_TEXTURE_2D || readColorbufferType == GL_TEXTURE_RECTANGLE_ARB || Framebuffer::IsRenderbuffer(readColorbufferType);
+		const bool validDrawType = drawColorbufferType == GL_TEXTURE_2D || drawColorbufferType == GL_TEXTURE_RECTANGLE_ARB || Framebuffer::IsRenderbuffer(drawColorbufferType);
 		if(!validReadType || !validDrawType)
 		{
 			return error(GL_INVALID_OPERATION);
@@ -4145,8 +4145,10 @@
 			return error(GL_INVALID_OPERATION);
 		}
 
-		if((readRenderbuffer->getSamples() > 0) &&
-		   (readRenderbuffer->getFormat() != drawRenderbuffer->getFormat()))
+		// From the ANGLE_framebuffer_blit extension:
+		// "Calling BlitFramebufferANGLE will result in an INVALID_OPERATION error if <mask>
+		//  includes COLOR_BUFFER_BIT and the source and destination color formats to not match."
+		if((clientVersion < 3) && (readRenderbuffer->getSamples() > 0) && (readFormat != drawFormat))
 		{
 			return error(GL_INVALID_OPERATION);
 		}
@@ -4291,7 +4293,8 @@
 
 void Context::bindTexImage(gl::Surface *surface)
 {
-	es2::Texture2D *textureObject = getTexture2D();
+	bool isRect = (surface->getTextureTarget() == EGL_TEXTURE_RECTANGLE_ANGLE);
+	es2::Texture2D *textureObject = isRect ? getTexture2DRect() : getTexture2D();
 
 	if(textureObject)
 	{
diff --git a/src/OpenGL/libGLESv2/Texture.cpp b/src/OpenGL/libGLESv2/Texture.cpp
index dc0e807..ec96373 100644
--- a/src/OpenGL/libGLESv2/Texture.cpp
+++ b/src/OpenGL/libGLESv2/Texture.cpp
@@ -1900,6 +1900,18 @@
 	return egl::Image::create(width, height, internalformat, multiSampleDepth, false);
 }
 
+NO_SANITIZE_FUNCTION egl::Image *createBackBufferFromClientBuffer(const egl::ClientBuffer& clientBuffer)
+{
+	if(clientBuffer.getWidth() > es2::IMPLEMENTATION_MAX_RENDERBUFFER_SIZE ||
+	   clientBuffer.getHeight() > es2::IMPLEMENTATION_MAX_RENDERBUFFER_SIZE)
+	{
+		ERR("Invalid parameters: %dx%d", clientBuffer.getWidth(), clientBuffer.getHeight());
+		return nullptr;
+	}
+
+	return egl::Image::create(clientBuffer);
+}
+
 NO_SANITIZE_FUNCTION egl::Image *createDepthStencil(int width, int height, sw::Format format, int multiSampleDepth)
 {
 	if(width > es2::IMPLEMENTATION_MAX_RENDERBUFFER_SIZE || height > es2::IMPLEMENTATION_MAX_RENDERBUFFER_SIZE)
diff --git a/src/OpenGL/libGLESv2/entry_points.cpp b/src/OpenGL/libGLESv2/entry_points.cpp
index e4b35aa..b142b85 100644
--- a/src/OpenGL/libGLESv2/entry_points.cpp
+++ b/src/OpenGL/libGLESv2/entry_points.cpp
@@ -1211,6 +1211,7 @@
 egl::Context *es2CreateContext(egl::Display *display, const egl::Context *shareContext, int clientVersion, const egl::Config *config);
 extern "C" __eglMustCastToProperFunctionPointerType es2GetProcAddress(const char *procname);
 egl::Image *createBackBuffer(int width, int height, sw::Format format, int multiSampleDepth);
+egl::Image *createBackBufferFromClientBuffer(const egl::ClientBuffer& clientBuffer);
 egl::Image *createDepthStencil(int width, int height, sw::Format format, int multiSampleDepth);
 sw::FrameBuffer *createFrameBuffer(void *nativeDisplay, EGLNativeWindowType window, int width, int height);
 
@@ -1414,6 +1415,7 @@
 	this->es2CreateContext = ::es2CreateContext;
 	this->es2GetProcAddress = ::es2GetProcAddress;
 	this->createBackBuffer = ::createBackBuffer;
+	this->createBackBufferFromClientBuffer = ::createBackBufferFromClientBuffer;
 	this->createDepthStencil = ::createDepthStencil;
 	this->createFrameBuffer = ::createFrameBuffer;
 }
diff --git a/src/OpenGL/libGLESv2/libGLESv2.hpp b/src/OpenGL/libGLESv2/libGLESv2.hpp
index 0897d3b..8946f0b 100644
--- a/src/OpenGL/libGLESv2/libGLESv2.hpp
+++ b/src/OpenGL/libGLESv2/libGLESv2.hpp
@@ -33,6 +33,7 @@
 class Context;
 class Image;
 class Config;
+class ClientBuffer;
 }
 
 class LibGLESv2exports
@@ -245,6 +246,7 @@
 	egl::Context *(*es2CreateContext)(egl::Display *display, const egl::Context *shareContext, int clientVersion, const egl::Config *config);
 	__eglMustCastToProperFunctionPointerType (*es2GetProcAddress)(const char *procname);
 	egl::Image *(*createBackBuffer)(int width, int height, sw::Format format, int multiSampleDepth);
+	egl::Image *(*createBackBufferFromClientBuffer)(const egl::ClientBuffer& clientBuffer);
 	egl::Image *(*createDepthStencil)(int width, int height, sw::Format format, int multiSampleDepth);
 	sw::FrameBuffer *(*createFrameBuffer)(void *nativeDisplay, EGLNativeWindowType window, int width, int height);
 };
diff --git a/tests/unittests/BUILD.gn b/tests/unittests/BUILD.gn
index aa7dfef..10f5fa5 100644
--- a/tests/unittests/BUILD.gn
+++ b/tests/unittests/BUILD.gn
@@ -46,6 +46,10 @@
       "-rpath",
       "@executable_path/",
     ]
+    libs = [
+      "CoreFoundation.framework",
+      "IOSurface.framework",
+    ]
   } else {
     ldflags = [ "-Wl,-rpath=\$ORIGIN/swiftshader" ]
   }
diff --git a/tests/unittests/unittests.cpp b/tests/unittests/unittests.cpp
index 7ae84da..99cbb61 100644
--- a/tests/unittests/unittests.cpp
+++ b/tests/unittests/unittests.cpp
@@ -97,6 +97,7 @@
 			EGL_ALPHA_SIZE,			8,
 			EGL_NONE
 		};
+
 		EGLint num_config = -1;
 		EGLBoolean success = eglChooseConfig(display, configAttributes, &config, 1, &num_config);
 		EXPECT_EQ(EGL_SUCCESS, eglGetError());
@@ -203,29 +204,43 @@
 		EXPECT_EQ((EGLBoolean)EGL_TRUE, success);
 	}
 
-	GLuint createProgram(const std::string& vs, const std::string& fs)
+	struct ProgramHandles {
+		GLuint program;
+		GLuint vsShader;
+		GLuint fsShader;
+	};
+
+	ProgramHandles createProgram(const std::string& vs, const std::string& fs)
 	{
-		GLuint program = glCreateProgram();
+		ProgramHandles ph;
+		ph.program = glCreateProgram();
 		EXPECT_GLENUM_EQ(GL_NONE, glGetError());
 
-		GLuint vsShader = glCreateShader(GL_VERTEX_SHADER);
+		ph.vsShader = glCreateShader(GL_VERTEX_SHADER);
 		const char* vsSource[1] = { vs.c_str() };
-		glShaderSource(vsShader, 1, vsSource, nullptr);
-		glCompileShader(vsShader);
+		glShaderSource(ph.vsShader, 1, vsSource, nullptr);
+		glCompileShader(ph.vsShader);
 		EXPECT_GLENUM_EQ(GL_NONE, glGetError());
 
-		GLuint fsShader = glCreateShader(GL_FRAGMENT_SHADER);
+		ph.fsShader = glCreateShader(GL_FRAGMENT_SHADER);
 		const char* fsSource[1] = { fs.c_str() };
-		glShaderSource(fsShader, 1, fsSource, nullptr);
-		glCompileShader(fsShader);
+		glShaderSource(ph.fsShader, 1, fsSource, nullptr);
+		glCompileShader(ph.fsShader);
 		EXPECT_GLENUM_EQ(GL_NONE, glGetError());
 
-		glAttachShader(program, vsShader);
-		glAttachShader(program, fsShader);
-		glLinkProgram(program);
+		glAttachShader(ph.program, ph.vsShader);
+		glAttachShader(ph.program, ph.fsShader);
+		glLinkProgram(ph.program);
 		EXPECT_GLENUM_EQ(GL_NONE, glGetError());
 
-		return program;
+		return ph;
+	}
+
+	void deleteProgram(const ProgramHandles& ph)
+	{
+		glDeleteShader(ph.fsShader);
+		glDeleteShader(ph.vsShader);
+		glDeleteProgram(ph.program);
 	}
 
 	void drawQuad(GLuint program)
@@ -262,7 +277,7 @@
 	}
 
 	EGLDisplay getDisplay() const { return display; }
-	EGLDisplay getConfig() const { return config; }
+	EGLConfig getConfig() const { return config; }
 	EGLSurface getSurface() const { return surface; }
 	EGLContext getContext() const { return context; }
 private:
@@ -532,10 +547,10 @@
 		"    gl_FragColor = texture2DRect(tex, vec2(0, 0));\n"
 		"}\n";
 
-	GLuint program = createProgram(vs, fs);
+	const ProgramHandles ph = createProgram(vs, fs);
 
-	glUseProgram(program);
-	GLint location = glGetUniformLocation(program, "tex");
+	glUseProgram(ph.program);
+	GLint location = glGetUniformLocation(ph.program, "tex");
 	ASSERT_NE(-1, location);
 	glUniform1i(location, 0);
 
@@ -543,7 +558,9 @@
 	glClear(GL_COLOR_BUFFER_BIT);
 	EXPECT_GLENUM_EQ(GL_NONE, glGetError());
 
-	drawQuad(program);
+	drawQuad(ph.program);
+
+	deleteProgram(ph);
 
 	compareColor(green);
 
@@ -584,9 +601,10 @@
 		"    fragColor = texture(tex, vec2(0, 0));\n"
 		"}\n";
 
-	GLuint program = createProgram(vs, fs);
-	glUseProgram(program);
-	GLint location = glGetUniformLocation(program, "tex");
+	const ProgramHandles ph = createProgram(vs, fs);
+
+	glUseProgram(ph.program);
+	GLint location = glGetUniformLocation(ph.program, "tex");
 	ASSERT_NE(-1, location);
 	glUniform1i(location, 0);
 
@@ -594,7 +612,9 @@
 	glClear(GL_COLOR_BUFFER_BIT);
 	EXPECT_GLENUM_EQ(GL_NONE, glGetError());
 
-	drawQuad(program);
+	drawQuad(ph.program);
+
+	deleteProgram(ph);
 
 	compareColor(green);
 
@@ -717,3 +737,743 @@
 
 	Uninitialize();
 }
+
+#ifndef EGL_ANGLE_iosurface_client_buffer
+#define EGL_ANGLE_iosurface_client_buffer 1
+#define EGL_IOSURFACE_ANGLE 0x3454
+#define EGL_IOSURFACE_PLANE_ANGLE 0x345A
+#define EGL_TEXTURE_RECTANGLE_ANGLE 0x345B
+#define EGL_TEXTURE_TYPE_ANGLE 0x345C
+#define EGL_TEXTURE_INTERNAL_FORMAT_ANGLE 0x345D
+#endif /* EGL_ANGLE_iosurface_client_buffer */
+
+#if defined(__APPLE__)
+#include <CoreFoundation/CoreFoundation.h>
+#include <IOSurface/IOSurface.h>
+
+namespace
+{
+	void AddIntegerValue(CFMutableDictionaryRef dictionary, const CFStringRef key, int32_t value)
+	{
+		CFNumberRef number = CFNumberCreate(nullptr, kCFNumberSInt32Type, &value);
+		CFDictionaryAddValue(dictionary, key, number);
+		CFRelease(number);
+	}
+}  // anonymous namespace
+
+class EGLClientBufferWrapper
+{
+public:
+	EGLClientBufferWrapper(int width = 1, int height = 1)
+	{
+		// Create a 1 by 1 BGRA8888 IOSurface
+		ioSurface = nullptr;
+
+		CFMutableDictionaryRef dict = CFDictionaryCreateMutable(
+			kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
+		AddIntegerValue(dict, kIOSurfaceWidth, width);
+		AddIntegerValue(dict, kIOSurfaceHeight, height);
+		AddIntegerValue(dict, kIOSurfacePixelFormat, 'BGRA');
+		AddIntegerValue(dict, kIOSurfaceBytesPerElement, 4);
+
+		ioSurface = IOSurfaceCreate(dict);
+		CFRelease(dict);
+
+		EXPECT_NE(nullptr, ioSurface);
+	}
+
+	~EGLClientBufferWrapper()
+	{
+		IOSurfaceUnlock(ioSurface, kIOSurfaceLockReadOnly, nullptr);
+
+		CFRelease(ioSurface);
+	}
+
+	EGLClientBuffer getClientBuffer() const
+	{
+		return ioSurface;
+	}
+
+	const unsigned char* lockColor()
+	{
+		IOSurfaceLock(ioSurface, kIOSurfaceLockReadOnly, nullptr);
+		return reinterpret_cast<const unsigned char*>(IOSurfaceGetBaseAddress(ioSurface));
+	}
+
+	void unlockColor()
+	{
+		IOSurfaceUnlock(ioSurface, kIOSurfaceLockReadOnly, nullptr);
+	}
+
+	void writeColor(void* data, size_t dataSize)
+	{
+		// Write the data to the IOSurface
+		IOSurfaceLock(ioSurface, 0, nullptr);
+		memcpy(IOSurfaceGetBaseAddress(ioSurface), data, dataSize);
+		IOSurfaceUnlock(ioSurface, 0, nullptr);
+	}
+private:
+	IOSurfaceRef ioSurface;
+};
+
+#else // __APPLE__
+
+class EGLClientBufferWrapper
+{
+public:
+	EGLClientBufferWrapper(int width = 1, int height = 1)
+	{
+		clientBuffer = new unsigned char[4 * width * height];
+	}
+
+	~EGLClientBufferWrapper()
+	{
+		delete[] clientBuffer;
+	}
+
+	EGLClientBuffer getClientBuffer() const
+	{
+		return clientBuffer;
+	}
+
+	const unsigned char* lockColor()
+	{
+		return clientBuffer;
+	}
+
+	void unlockColor()
+	{
+	}
+
+	void writeColor(void* data, size_t dataSize)
+	{
+		memcpy(clientBuffer, data, dataSize);
+	}
+private:
+	unsigned char* clientBuffer;
+};
+
+#endif
+
+class IOSurfaceClientBufferTest : public SwiftShaderTest
+{
+protected:
+	EGLSurface createIOSurfacePbuffer(EGLClientBuffer buffer, EGLint width, EGLint height, EGLint plane, GLenum internalFormat, GLenum type) const
+	{
+		// Make a PBuffer from it using the EGL_ANGLE_iosurface_client_buffer extension
+		const EGLint attribs[] = {
+			EGL_WIDTH,                         width,
+			EGL_HEIGHT,                        height,
+			EGL_IOSURFACE_PLANE_ANGLE,         plane,
+			EGL_TEXTURE_TARGET,                EGL_TEXTURE_RECTANGLE_ANGLE,
+			EGL_TEXTURE_INTERNAL_FORMAT_ANGLE, (EGLint)internalFormat,
+			EGL_TEXTURE_FORMAT,                EGL_TEXTURE_RGBA,
+			EGL_TEXTURE_TYPE_ANGLE,            (EGLint)type,
+			EGL_NONE,                          EGL_NONE,
+		};
+
+		EGLSurface pbuffer = eglCreatePbufferFromClientBuffer(getDisplay(), EGL_IOSURFACE_ANGLE, buffer, getConfig(), attribs);
+		EXPECT_NE(EGL_NO_SURFACE, pbuffer);
+		return pbuffer;
+	}
+
+	void bindIOSurfaceToTexture(EGLClientBuffer buffer, EGLint width, EGLint height, EGLint plane, GLenum internalFormat, GLenum type, EGLSurface *pbuffer, GLuint *texture) const
+	{
+		*pbuffer = createIOSurfacePbuffer(buffer, width, height, plane, internalFormat, type);
+
+		// Bind the pbuffer
+		glBindTexture(GL_TEXTURE_RECTANGLE_ARB, *texture);
+		EGLBoolean result = eglBindTexImage(getDisplay(), *pbuffer, EGL_BACK_BUFFER);
+		EXPECT_EQ((EGLBoolean)EGL_TRUE, result);
+		EXPECT_EQ(EGL_SUCCESS, eglGetError());
+	}
+
+	void doClear(GLenum internalFormat, bool clearToZero)
+	{
+		if(internalFormat == GL_R16UI)
+		{
+			GLuint color = clearToZero ? 0 : 257;
+			glClearBufferuiv(GL_COLOR, 0, &color);
+			EXPECT_GLENUM_EQ(GL_NO_ERROR, glGetError());
+		}
+		else
+		{
+			glClearColor(clearToZero ? 0.0f : 1.0f / 255.0f,
+				clearToZero ? 0.0f : 2.0f / 255.0f,
+				clearToZero ? 0.0f : 3.0f / 255.0f,
+				clearToZero ? 0.0f : 4.0f / 255.0f);
+			EXPECT_GLENUM_EQ(GL_NO_ERROR, glGetError());
+			glClear(GL_COLOR_BUFFER_BIT);
+			EXPECT_GLENUM_EQ(GL_NO_ERROR, glGetError());
+		}
+	}
+
+	void doClearTest(EGLClientBufferWrapper& clientBufferWrapper, GLenum internalFormat, GLenum type, void *data, size_t dataSize)
+	{
+		ASSERT_TRUE(dataSize <= 4);
+
+		// Bind the IOSurface to a texture and clear it.
+		GLuint texture = 1;
+		EGLSurface pbuffer;
+		bindIOSurfaceToTexture(clientBufferWrapper.getClientBuffer(), 1, 1, 0, internalFormat, type, &pbuffer, &texture);
+
+		// glClear the pbuffer
+		GLuint fbo = 2;
+		glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+		EXPECT_GLENUM_EQ(GL_NO_ERROR, glGetError());
+		glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_RECTANGLE_ARB, texture, 0);
+		EXPECT_GLENUM_EQ(GL_NO_ERROR, glGetError());
+		EXPECT_GLENUM_EQ(glCheckFramebufferStatus(GL_FRAMEBUFFER), GL_FRAMEBUFFER_COMPLETE);
+		EXPECT_GLENUM_EQ(GL_NO_ERROR, glGetError());
+
+		doClear(internalFormat, false);
+
+		// Unbind pbuffer and check content.
+		EGLBoolean result = eglReleaseTexImage(getDisplay(), pbuffer, EGL_BACK_BUFFER);
+		EXPECT_EQ((EGLBoolean)EGL_TRUE, result);
+		EXPECT_EQ(EGL_SUCCESS, eglGetError());
+
+		const unsigned char* color = clientBufferWrapper.lockColor();
+		for(size_t i = 0; i < dataSize; ++i)
+		{
+			EXPECT_EQ(color[i], reinterpret_cast<unsigned char*>(data)[i]);
+		}
+
+		result = eglDestroySurface(getDisplay(), pbuffer);
+		EXPECT_EQ((EGLBoolean)EGL_TRUE, result);
+		EXPECT_EQ(EGL_SUCCESS, eglGetError());
+	}
+
+	void doSampleTest(EGLClientBufferWrapper& clientBufferWrapper, GLenum internalFormat, GLenum type, void *data, size_t dataSize)
+	{
+		ASSERT_TRUE(dataSize <= 4);
+
+		clientBufferWrapper.writeColor(data, dataSize);
+
+		// Bind the IOSurface to a texture and clear it.
+		GLuint texture = 1;
+		EGLSurface pbuffer;
+		bindIOSurfaceToTexture(clientBufferWrapper.getClientBuffer(), 1, 1, 0, internalFormat, type, &pbuffer, &texture);
+
+		doClear(internalFormat, true);
+
+		// Create program and draw quad using it
+		const std::string vs =
+			"attribute vec4 position;\n"
+			"void main()\n"
+			"{\n"
+			"    gl_Position = vec4(position.xy, 0.0, 1.0);\n"
+			"}\n";
+
+		const std::string fs =
+			"#extension GL_ARB_texture_rectangle : require\n"
+			"precision mediump float;\n"
+			"uniform sampler2DRect tex;\n"
+			"void main()\n"
+			"{\n"
+			"    gl_FragColor = texture2DRect(tex, vec2(0, 0));\n"
+			"}\n";
+
+		const ProgramHandles ph = createProgram(vs, fs);
+
+		drawQuad(ph.program);
+
+		deleteProgram(ph);
+
+		EXPECT_GLENUM_EQ(GL_NO_ERROR, glGetError());
+
+		// Unbind pbuffer and check content.
+		EGLBoolean result = eglReleaseTexImage(getDisplay(), pbuffer, EGL_BACK_BUFFER);
+		EXPECT_EQ((EGLBoolean)EGL_TRUE, result);
+		EXPECT_EQ(EGL_SUCCESS, eglGetError());
+
+		const unsigned char* color = clientBufferWrapper.lockColor();
+		for(size_t i = 0; i < dataSize; ++i)
+		{
+			EXPECT_EQ(color[i], reinterpret_cast<unsigned char*>(data)[i]);
+		}
+		clientBufferWrapper.unlockColor();
+	}
+};
+
+// Tests for the EGL_ANGLE_iosurface_client_buffer extension
+TEST_F(IOSurfaceClientBufferTest, RenderToBGRA8888IOSurface)
+{
+	Initialize(3, false);
+
+	{ // EGLClientBufferWrapper scope
+		EGLClientBufferWrapper clientBufferWrapper;
+		unsigned char data[4] = { 3, 2, 1, 4 };
+		doClearTest(clientBufferWrapper, GL_BGRA_EXT, GL_UNSIGNED_BYTE, data, 4);
+	} // end of EGLClientBufferWrapper scope
+
+	Uninitialize();
+}
+
+// Test reading from BGRA8888 IOSurfaces
+TEST_F(IOSurfaceClientBufferTest, ReadFromBGRA8888IOSurface)
+{
+	Initialize(3, false);
+
+	{ // EGLClientBufferWrapper scope
+		EGLClientBufferWrapper clientBufferWrapper;
+		unsigned char data[4] = { 3, 2, 1, 4 };
+		doSampleTest(clientBufferWrapper, GL_BGRA_EXT, GL_UNSIGNED_BYTE, data, 4);
+	} // end of EGLClientBufferWrapper scope
+
+	Uninitialize();
+}
+
+// Test using RG88 IOSurfaces for rendering
+TEST_F(IOSurfaceClientBufferTest, RenderToRG88IOSurface)
+{
+	Initialize(3, false);
+
+	{ // EGLClientBufferWrapper scope
+		EGLClientBufferWrapper clientBufferWrapper;
+		unsigned char data[2] = { 1, 2 };
+		doClearTest(clientBufferWrapper, GL_RG, GL_UNSIGNED_BYTE, data, 2);
+	} // end of EGLClientBufferWrapper scope
+
+	Uninitialize();
+}
+
+// Test reading from RG88 IOSurfaces
+TEST_F(IOSurfaceClientBufferTest, ReadFromRG88IOSurface)
+{
+	Initialize(3, false);
+
+	{ // EGLClientBufferWrapper scope
+		EGLClientBufferWrapper clientBufferWrapper;
+		unsigned char data[2] = { 1, 2 };
+		doSampleTest(clientBufferWrapper, GL_RG, GL_UNSIGNED_BYTE, data, 2);
+	} // end of EGLClientBufferWrapper scope
+
+	Uninitialize();
+}
+
+// Test using R8 IOSurfaces for rendering
+TEST_F(IOSurfaceClientBufferTest, RenderToR8IOSurface)
+{
+	Initialize(3, false);
+
+	{ // EGLClientBufferWrapper scope
+		EGLClientBufferWrapper clientBufferWrapper;
+		unsigned char data[1] = { 1 };
+		doClearTest(clientBufferWrapper, GL_RED, GL_UNSIGNED_BYTE, data, 1);
+	} // end of EGLClientBufferWrapper scope
+
+	Uninitialize();
+}
+
+// Test reading from R8 IOSurfaces
+TEST_F(IOSurfaceClientBufferTest, ReadFromR8IOSurface)
+{
+	Initialize(3, false);
+
+	{ // EGLClientBufferWrapper scope
+		EGLClientBufferWrapper clientBufferWrapper;
+		unsigned char data[1] = { 1 };
+		doSampleTest(clientBufferWrapper, GL_RED, GL_UNSIGNED_BYTE, data, 1);
+	} // end of EGLClientBufferWrapper scope
+
+	Uninitialize();
+}
+
+// Test using R16 IOSurfaces for rendering
+TEST_F(IOSurfaceClientBufferTest, RenderToR16IOSurface)
+{
+	Initialize(3, false);
+
+	{ // EGLClientBufferWrapper scope
+		EGLClientBufferWrapper clientBufferWrapper;
+		uint16_t data[1] = { 257 };
+		doClearTest(clientBufferWrapper, GL_R16UI, GL_UNSIGNED_SHORT, data, 2);
+	} // end of EGLClientBufferWrapper scope
+
+	Uninitialize();
+}
+
+// Test reading from R8 IOSurfaces
+TEST_F(IOSurfaceClientBufferTest, ReadFromR16IOSurface)
+{
+	Initialize(3, false);
+
+	{ // EGLClientBufferWrapper scope
+		EGLClientBufferWrapper clientBufferWrapper;
+		uint16_t data[1] = { 257 };
+		doSampleTest(clientBufferWrapper, GL_R16UI, GL_UNSIGNED_SHORT, data, 1);
+	} // end of EGLClientBufferWrapper scope
+
+	Uninitialize();
+}
+
+// Test the validation errors for missing attributes for eglCreatePbufferFromClientBuffer with
+// IOSurface
+TEST_F(IOSurfaceClientBufferTest, NegativeValidationMissingAttributes)
+{
+	Initialize(3, false);
+
+	{
+		EGLClientBufferWrapper clientBufferWrapper(10, 10);
+
+		// Success case
+		{
+			const EGLint attribs[] = {
+				EGL_WIDTH,                         10,
+				EGL_HEIGHT,                        10,
+				EGL_IOSURFACE_PLANE_ANGLE,         0,
+				EGL_TEXTURE_TARGET,                EGL_TEXTURE_RECTANGLE_ANGLE,
+				EGL_TEXTURE_INTERNAL_FORMAT_ANGLE, GL_BGRA_EXT,
+				EGL_TEXTURE_FORMAT,                EGL_TEXTURE_RGBA,
+				EGL_TEXTURE_TYPE_ANGLE,            GL_UNSIGNED_BYTE,
+				EGL_NONE,                          EGL_NONE,
+			};
+
+			EGLSurface pbuffer = eglCreatePbufferFromClientBuffer(getDisplay(), EGL_IOSURFACE_ANGLE, clientBufferWrapper.getClientBuffer(), getConfig(), attribs);
+			EXPECT_NE(EGL_NO_SURFACE, pbuffer);
+
+			EGLBoolean result = eglDestroySurface(getDisplay(), pbuffer);
+			EXPECT_EQ((EGLBoolean)EGL_TRUE, result);
+			EXPECT_EQ(EGL_SUCCESS, eglGetError());
+		}
+
+		// Missing EGL_WIDTH
+		{
+			const EGLint attribs[] = {
+				EGL_HEIGHT,                        10,
+				EGL_IOSURFACE_PLANE_ANGLE,         0,
+				EGL_TEXTURE_TARGET,                EGL_TEXTURE_RECTANGLE_ANGLE,
+				EGL_TEXTURE_INTERNAL_FORMAT_ANGLE, GL_BGRA_EXT,
+				EGL_TEXTURE_FORMAT,                EGL_TEXTURE_RGBA,
+				EGL_TEXTURE_TYPE_ANGLE,            GL_UNSIGNED_BYTE,
+				EGL_NONE,                          EGL_NONE,
+			};
+
+			EGLSurface pbuffer = eglCreatePbufferFromClientBuffer(getDisplay(), EGL_IOSURFACE_ANGLE, clientBufferWrapper.getClientBuffer(), getConfig(), attribs);
+			EXPECT_EQ(EGL_NO_SURFACE, pbuffer);
+			EXPECT_EQ(EGL_BAD_PARAMETER, eglGetError());
+		}
+
+		// Missing EGL_HEIGHT
+		{
+			const EGLint attribs[] = {
+				EGL_WIDTH,                         10,
+				EGL_IOSURFACE_PLANE_ANGLE,         0,
+				EGL_TEXTURE_TARGET,                EGL_TEXTURE_RECTANGLE_ANGLE,
+				EGL_TEXTURE_INTERNAL_FORMAT_ANGLE, GL_BGRA_EXT,
+				EGL_TEXTURE_FORMAT,                EGL_TEXTURE_RGBA,
+				EGL_TEXTURE_TYPE_ANGLE,            GL_UNSIGNED_BYTE,
+				EGL_NONE,                          EGL_NONE,
+			};
+
+			EGLSurface pbuffer = eglCreatePbufferFromClientBuffer(getDisplay(), EGL_IOSURFACE_ANGLE, clientBufferWrapper.getClientBuffer(), getConfig(), attribs);
+			EXPECT_EQ(EGL_NO_SURFACE, pbuffer);
+			EXPECT_EQ(EGL_BAD_PARAMETER, eglGetError());
+		}
+
+		// Missing EGL_IOSURFACE_PLANE_ANGLE
+		{
+			const EGLint attribs[] = {
+				EGL_WIDTH,                         10,
+				EGL_HEIGHT,                        10,
+				EGL_TEXTURE_TARGET,                EGL_TEXTURE_RECTANGLE_ANGLE,
+				EGL_TEXTURE_INTERNAL_FORMAT_ANGLE, GL_BGRA_EXT,
+				EGL_TEXTURE_FORMAT,                EGL_TEXTURE_RGBA,
+				EGL_TEXTURE_TYPE_ANGLE,            GL_UNSIGNED_BYTE,
+				EGL_NONE,                          EGL_NONE,
+			};
+
+			EGLSurface pbuffer = eglCreatePbufferFromClientBuffer(getDisplay(), EGL_IOSURFACE_ANGLE, clientBufferWrapper.getClientBuffer(), getConfig(), attribs);
+			EXPECT_EQ(EGL_NO_SURFACE, pbuffer);
+			EXPECT_EQ(EGL_BAD_PARAMETER, eglGetError());
+		}
+
+		// Missing EGL_TEXTURE_TARGET - EGL_BAD_MATCH from the base spec of
+		// eglCreatePbufferFromClientBuffer
+		{
+			const EGLint attribs[] = {
+				EGL_WIDTH,                         10,
+				EGL_HEIGHT,                        10,
+				EGL_IOSURFACE_PLANE_ANGLE,         0,
+				EGL_TEXTURE_INTERNAL_FORMAT_ANGLE, GL_BGRA_EXT,
+				EGL_TEXTURE_FORMAT,                EGL_TEXTURE_RGBA,
+				EGL_TEXTURE_TYPE_ANGLE,            GL_UNSIGNED_BYTE,
+				EGL_NONE,                          EGL_NONE,
+			};
+
+			EGLSurface pbuffer = eglCreatePbufferFromClientBuffer(getDisplay(), EGL_IOSURFACE_ANGLE, clientBufferWrapper.getClientBuffer(), getConfig(), attribs);
+			EXPECT_EQ(EGL_NO_SURFACE, pbuffer);
+			EXPECT_EQ(EGL_BAD_MATCH, eglGetError());
+		}
+
+		// Missing EGL_TEXTURE_INTERNAL_FORMAT_ANGLE
+		{
+			const EGLint attribs[] = {
+				EGL_WIDTH,                         10,
+				EGL_HEIGHT,                        10,
+				EGL_IOSURFACE_PLANE_ANGLE,         0,
+				EGL_TEXTURE_TARGET,                EGL_TEXTURE_RECTANGLE_ANGLE,
+				EGL_TEXTURE_FORMAT,                EGL_TEXTURE_RGBA,
+				EGL_TEXTURE_TYPE_ANGLE,            GL_UNSIGNED_BYTE,
+				EGL_NONE,                          EGL_NONE,
+			};
+
+			EGLSurface pbuffer = eglCreatePbufferFromClientBuffer(getDisplay(), EGL_IOSURFACE_ANGLE, clientBufferWrapper.getClientBuffer(), getConfig(), attribs);
+			EXPECT_EQ(EGL_NO_SURFACE, pbuffer);
+			EXPECT_EQ(EGL_BAD_PARAMETER, eglGetError());
+		}
+
+		// Missing EGL_TEXTURE_FORMAT - EGL_BAD_MATCH from the base spec of
+		// eglCreatePbufferFromClientBuffer
+		{
+			const EGLint attribs[] = {
+				EGL_WIDTH,                         10,
+				EGL_HEIGHT,                        10,
+				EGL_IOSURFACE_PLANE_ANGLE,         0,
+				EGL_TEXTURE_TARGET,                EGL_TEXTURE_RECTANGLE_ANGLE,
+				EGL_TEXTURE_INTERNAL_FORMAT_ANGLE, GL_BGRA_EXT,
+				EGL_TEXTURE_TYPE_ANGLE,            GL_UNSIGNED_BYTE,
+				EGL_NONE,                          EGL_NONE,
+			};
+
+			EGLSurface pbuffer = eglCreatePbufferFromClientBuffer(getDisplay(), EGL_IOSURFACE_ANGLE, clientBufferWrapper.getClientBuffer(), getConfig(), attribs);
+			EXPECT_EQ(EGL_NO_SURFACE, pbuffer);
+			EXPECT_EQ(EGL_BAD_MATCH, eglGetError());
+		}
+
+		// Missing EGL_TEXTURE_TYPE_ANGLE
+		{
+			const EGLint attribs[] = {
+				EGL_WIDTH,                         10,
+				EGL_HEIGHT,                        10,
+				EGL_IOSURFACE_PLANE_ANGLE,         0,
+				EGL_TEXTURE_TARGET,                EGL_TEXTURE_RECTANGLE_ANGLE,
+				EGL_TEXTURE_INTERNAL_FORMAT_ANGLE, GL_BGRA_EXT,
+				EGL_TEXTURE_FORMAT,                EGL_TEXTURE_RGBA,
+				EGL_NONE,                          EGL_NONE,
+			};
+
+			EGLSurface pbuffer = eglCreatePbufferFromClientBuffer(getDisplay(), EGL_IOSURFACE_ANGLE, clientBufferWrapper.getClientBuffer(), getConfig(), attribs);
+			EXPECT_EQ(EGL_NO_SURFACE, pbuffer);
+			EXPECT_EQ(EGL_BAD_PARAMETER, eglGetError());
+		}
+	}
+
+	Uninitialize();
+}
+
+// Test the validation errors for bad parameters for eglCreatePbufferFromClientBuffer with IOSurface
+TEST_F(IOSurfaceClientBufferTest, NegativeValidationBadAttributes)
+{
+	Initialize(3, false);
+
+	{
+		EGLClientBufferWrapper clientBufferWrapper(10, 10);
+
+		// Success case
+		{
+			const EGLint attribs[] = {
+				EGL_WIDTH,                         10,
+				EGL_HEIGHT,                        10,
+				EGL_IOSURFACE_PLANE_ANGLE,         0,
+				EGL_TEXTURE_TARGET,                EGL_TEXTURE_RECTANGLE_ANGLE,
+				EGL_TEXTURE_INTERNAL_FORMAT_ANGLE, GL_BGRA_EXT,
+				EGL_TEXTURE_FORMAT,                EGL_TEXTURE_RGBA,
+				EGL_TEXTURE_TYPE_ANGLE,            GL_UNSIGNED_BYTE,
+				EGL_NONE,                          EGL_NONE,
+			};
+
+			EGLSurface pbuffer = eglCreatePbufferFromClientBuffer(getDisplay(), EGL_IOSURFACE_ANGLE, clientBufferWrapper.getClientBuffer(), getConfig(), attribs);
+			EXPECT_NE(EGL_NO_SURFACE, pbuffer);
+
+			EGLBoolean result = eglDestroySurface(getDisplay(), pbuffer);
+			EXPECT_EQ((EGLBoolean)EGL_TRUE, result);
+			EXPECT_EQ(EGL_SUCCESS, eglGetError());
+		}
+
+		// EGL_TEXTURE_FORMAT must be EGL_TEXTURE_RGBA
+		{
+			const EGLint attribs[] = {
+				EGL_WIDTH,                         10,
+				EGL_HEIGHT,                        10,
+				EGL_IOSURFACE_PLANE_ANGLE,         0,
+				EGL_TEXTURE_TARGET,                EGL_TEXTURE_RECTANGLE_ANGLE,
+				EGL_TEXTURE_INTERNAL_FORMAT_ANGLE, GL_BGRA_EXT,
+				EGL_TEXTURE_FORMAT,                EGL_TEXTURE_RGB,
+				EGL_TEXTURE_TYPE_ANGLE,            GL_UNSIGNED_BYTE,
+				EGL_NONE,                          EGL_NONE,
+			};
+
+			EGLSurface pbuffer = eglCreatePbufferFromClientBuffer(getDisplay(), EGL_IOSURFACE_ANGLE, clientBufferWrapper.getClientBuffer(), getConfig(), attribs);
+			EXPECT_EQ(EGL_NO_SURFACE, pbuffer);
+			EXPECT_EQ(EGL_BAD_ATTRIBUTE, eglGetError());
+		}
+
+		// EGL_WIDTH must be at least 1
+		{
+			const EGLint attribs[] = {
+				EGL_WIDTH,                         0,
+				EGL_HEIGHT,                        10,
+				EGL_IOSURFACE_PLANE_ANGLE,         0,
+				EGL_TEXTURE_TARGET,                EGL_TEXTURE_RECTANGLE_ANGLE,
+				EGL_TEXTURE_INTERNAL_FORMAT_ANGLE, GL_BGRA_EXT,
+				EGL_TEXTURE_FORMAT,                EGL_TEXTURE_RGBA,
+				EGL_TEXTURE_TYPE_ANGLE,            GL_UNSIGNED_BYTE,
+				EGL_NONE,                          EGL_NONE,
+			};
+
+			EGLSurface pbuffer = eglCreatePbufferFromClientBuffer(getDisplay(), EGL_IOSURFACE_ANGLE, clientBufferWrapper.getClientBuffer(), getConfig(), attribs);
+			EXPECT_EQ(EGL_NO_SURFACE, pbuffer);
+			EXPECT_EQ(EGL_BAD_ATTRIBUTE, eglGetError());
+		}
+
+		// EGL_HEIGHT must be at least 1
+		{
+			const EGLint attribs[] = {
+				EGL_WIDTH,                         10,
+				EGL_HEIGHT,                        0,
+				EGL_IOSURFACE_PLANE_ANGLE,         0,
+				EGL_TEXTURE_TARGET,                EGL_TEXTURE_RECTANGLE_ANGLE,
+				EGL_TEXTURE_INTERNAL_FORMAT_ANGLE, GL_BGRA_EXT,
+				EGL_TEXTURE_FORMAT,                EGL_TEXTURE_RGBA,
+				EGL_TEXTURE_TYPE_ANGLE,            GL_UNSIGNED_BYTE,
+				EGL_NONE,                          EGL_NONE,
+			};
+
+			EGLSurface pbuffer = eglCreatePbufferFromClientBuffer(getDisplay(), EGL_IOSURFACE_ANGLE, clientBufferWrapper.getClientBuffer(), getConfig(), attribs);
+			EXPECT_EQ(EGL_NO_SURFACE, pbuffer);
+			EXPECT_EQ(EGL_BAD_ATTRIBUTE, eglGetError());
+		}
+
+#if defined(__APPLE__)
+		// EGL_WIDTH must be at most the width of the IOSurface
+		{
+			const EGLint attribs[] = {
+				EGL_WIDTH,                         11,
+				EGL_HEIGHT,                        10,
+				EGL_IOSURFACE_PLANE_ANGLE,         0,
+				EGL_TEXTURE_TARGET,                EGL_TEXTURE_RECTANGLE_ANGLE,
+				EGL_TEXTURE_INTERNAL_FORMAT_ANGLE, GL_BGRA_EXT,
+				EGL_TEXTURE_FORMAT,                EGL_TEXTURE_RGBA,
+				EGL_TEXTURE_TYPE_ANGLE,            GL_UNSIGNED_BYTE,
+				EGL_NONE,                          EGL_NONE,
+			};
+
+			EGLSurface pbuffer = eglCreatePbufferFromClientBuffer(getDisplay(), EGL_IOSURFACE_ANGLE, clientBufferWrapper.getClientBuffer(), getConfig(), attribs);
+			EXPECT_EQ(EGL_NO_SURFACE, pbuffer);
+			EXPECT_EQ(EGL_BAD_ATTRIBUTE, eglGetError());
+		}
+
+		// EGL_HEIGHT must be at most the height of the IOSurface
+		{
+			const EGLint attribs[] = {
+				EGL_WIDTH,                         10,
+				EGL_HEIGHT,                        11,
+				EGL_IOSURFACE_PLANE_ANGLE,         0,
+				EGL_TEXTURE_TARGET,                EGL_TEXTURE_RECTANGLE_ANGLE,
+				EGL_TEXTURE_INTERNAL_FORMAT_ANGLE, GL_BGRA_EXT,
+				EGL_TEXTURE_FORMAT,                EGL_TEXTURE_RGBA,
+				EGL_TEXTURE_TYPE_ANGLE,            GL_UNSIGNED_BYTE,
+				EGL_NONE,                          EGL_NONE,
+			};
+
+			EGLSurface pbuffer = eglCreatePbufferFromClientBuffer(getDisplay(), EGL_IOSURFACE_ANGLE, clientBufferWrapper.getClientBuffer(), getConfig(), attribs);
+			EXPECT_EQ(EGL_NO_SURFACE, pbuffer);
+			EXPECT_EQ(EGL_BAD_ATTRIBUTE, eglGetError());
+		}
+
+		// EGL_IOSURFACE_PLANE_ANGLE must less than the number of planes of the IOSurface
+		{
+			const EGLint attribs[] = {
+				EGL_WIDTH,                         10,
+				EGL_HEIGHT,                        10,
+				EGL_IOSURFACE_PLANE_ANGLE,         1,
+				EGL_TEXTURE_TARGET,                EGL_TEXTURE_RECTANGLE_ANGLE,
+				EGL_TEXTURE_INTERNAL_FORMAT_ANGLE, GL_BGRA_EXT,
+				EGL_TEXTURE_FORMAT,                EGL_TEXTURE_RGBA,
+				EGL_TEXTURE_TYPE_ANGLE,            GL_UNSIGNED_BYTE,
+				EGL_NONE,                          EGL_NONE,
+			};
+
+			EGLSurface pbuffer = eglCreatePbufferFromClientBuffer(getDisplay(), EGL_IOSURFACE_ANGLE, clientBufferWrapper.getClientBuffer(), getConfig(), attribs);
+			EXPECT_EQ(EGL_NO_SURFACE, pbuffer);
+			EXPECT_EQ(EGL_BAD_ATTRIBUTE, eglGetError());
+		}
+#endif
+
+		// EGL_TEXTURE_FORMAT must be at EGL_TEXTURE_RECTANGLE_ANGLE
+		{
+			const EGLint attribs[] = {
+				EGL_WIDTH,                         10,
+				EGL_HEIGHT,                        10,
+				EGL_IOSURFACE_PLANE_ANGLE,         0,
+				EGL_TEXTURE_TARGET,                EGL_TEXTURE_2D,
+				EGL_TEXTURE_INTERNAL_FORMAT_ANGLE, GL_BGRA_EXT,
+				EGL_TEXTURE_FORMAT,                EGL_TEXTURE_RGBA,
+				EGL_TEXTURE_TYPE_ANGLE,            GL_UNSIGNED_BYTE,
+				EGL_NONE,                          EGL_NONE,
+			};
+
+			EGLSurface pbuffer = eglCreatePbufferFromClientBuffer(getDisplay(), EGL_IOSURFACE_ANGLE, clientBufferWrapper.getClientBuffer(), getConfig(), attribs);
+			EXPECT_EQ(EGL_NO_SURFACE, pbuffer);
+			EXPECT_EQ(EGL_BAD_ATTRIBUTE, eglGetError());
+		}
+
+		// EGL_IOSURFACE_PLANE_ANGLE must be at least 0
+		{
+			const EGLint attribs[] = {
+				EGL_WIDTH,                         10,
+				EGL_HEIGHT,                        10,
+				EGL_IOSURFACE_PLANE_ANGLE,         -1,
+				EGL_TEXTURE_TARGET,                EGL_TEXTURE_RECTANGLE_ANGLE,
+				EGL_TEXTURE_INTERNAL_FORMAT_ANGLE, GL_BGRA_EXT,
+				EGL_TEXTURE_FORMAT,                EGL_TEXTURE_RGBA,
+				EGL_TEXTURE_TYPE_ANGLE,            GL_UNSIGNED_BYTE,
+				EGL_NONE,                          EGL_NONE,
+			};
+
+			EGLSurface pbuffer = eglCreatePbufferFromClientBuffer(getDisplay(), EGL_IOSURFACE_ANGLE, clientBufferWrapper.getClientBuffer(), getConfig(), attribs);
+			EXPECT_EQ(EGL_NO_SURFACE, pbuffer);
+			EXPECT_EQ(EGL_BAD_ATTRIBUTE, eglGetError());
+		}
+
+		// The internal format / type most be listed in the table
+		{
+			const EGLint attribs[] = {
+				EGL_WIDTH,                         10,
+				EGL_HEIGHT,                        10,
+				EGL_IOSURFACE_PLANE_ANGLE,         0,
+				EGL_TEXTURE_TARGET,                EGL_TEXTURE_RECTANGLE_ANGLE,
+				EGL_TEXTURE_INTERNAL_FORMAT_ANGLE, GL_RGBA,
+				EGL_TEXTURE_FORMAT,                EGL_TEXTURE_RGBA,
+				EGL_TEXTURE_TYPE_ANGLE,            GL_UNSIGNED_BYTE,
+				EGL_NONE,                          EGL_NONE,
+			};
+
+			EGLSurface pbuffer = eglCreatePbufferFromClientBuffer(getDisplay(), EGL_IOSURFACE_ANGLE, clientBufferWrapper.getClientBuffer(), getConfig(), attribs);
+			EXPECT_EQ(EGL_NO_SURFACE, pbuffer);
+			EXPECT_EQ(EGL_BAD_ATTRIBUTE, eglGetError());
+		}
+	}
+
+	Uninitialize();
+}
+
+// Test IOSurface pbuffers cannot be made current
+TEST_F(IOSurfaceClientBufferTest, MakeCurrentDisallowed)
+{
+	Initialize(3, false);
+
+	{
+		EGLClientBufferWrapper clientBufferWrapper(10, 10);
+
+		EGLSurface pbuffer = createIOSurfacePbuffer(clientBufferWrapper.getClientBuffer(), 10, 10, 0, GL_BGRA_EXT, GL_UNSIGNED_BYTE);
+
+		EGLBoolean result = eglMakeCurrent(getDisplay(), pbuffer, pbuffer, getContext());
+		EXPECT_EQ((EGLBoolean)EGL_FALSE, result);
+		EXPECT_EQ(EGL_BAD_SURFACE, eglGetError());
+	}
+
+	Uninitialize();
+}