diff --git a/build.sh b/build.sh
index 61d4faee47e39799f66bc0a8421c381860c6bdae..3e907a7d1ad303f079afd6deffb2791149ccf49f 100755
--- a/build.sh
+++ b/build.sh
@@ -442,7 +442,10 @@ build_boinc()
         cd $ROOT/3rdparty/boinc/mac_build || failure
         xcodebuild -project boinc.xcodeproj -target gfx2libboinc -configuration Deployment build >> $LOGFILE 2>&1 || failure
         cp $ROOT/3rdparty/boinc/mac_build/build/boinc.build/Deployment/gfx2libboinc.build/DerivedSources/x86_64/*.h $ROOT/build/boinc >> $LOGFILE 2>&1 || failure
-	cd $ROOT/build/boinc || failure
+        echo "Patching BOINC..." | tee -a $LOGFILE
+        cd $ROOT/3rdparty/boinc/api || failure
+        patch Makefile.am < $ROOT/patches/boinc_mac_iosurface.patch >> $LOGFILE 2>&1 || failure
+        cd $ROOT/build/boinc || failure
         export CPPFLAGS="-I/sw/include -I/opt/local/include $CPPFLAGS"
         $ROOT/3rdparty/boinc/configure --prefix=$ROOT/install --enable-shared=no --enable-static=yes --disable-server --disable-client --with-apple-opengl-framework --enable-install-headers --enable-libraries --disable-manager --disable-fcgi >> $LOGFILE 2>&1 || failure
     elif [ -d "/usr/local/ssl" ]; then
@@ -454,6 +457,7 @@ build_boinc()
     echo "Fixing up BOINC's incomplete out-of-tree build..." | tee -a $LOGFILE
     mkdir -p $ROOT/install/include/boinc >> $LOGFILE 2>&1 || failure
     cp $ROOT/build/boinc/config.h $ROOT/install/include/boinc >> $LOGFILE 2>&1 || failure
+    cp $ROOT/build/boinc/MultiGPUMig*.h $ROOT/install/include/boinc >> $LOGFILE 2>&1 || failure
     cp $ROOT/3rdparty/boinc/project_specific_defines.h $ROOT/install/include/boinc >> $LOGFILE 2>&1 || failure
     echo "Building BOINC (this may take a while)..." | tee -a $LOGFILE
     make >> $LOGFILE 2>&1 || failure
diff --git a/patches/boinc_mac_iosurface.patch b/patches/boinc_mac_iosurface.patch
new file mode 100644
index 0000000000000000000000000000000000000000..22f8a85619b203b00546de66eb97135319d3e5dd
--- /dev/null
+++ b/patches/boinc_mac_iosurface.patch
@@ -0,0 +1,13 @@
+diff --git a/api/Makefile.am b/api/Makefile.am
+index 383c2afbdf..49b9a03b64 100644
+--- a/api/Makefile.am
++++ b/api/Makefile.am
+@@ -29,6 +29,8 @@ endif
+ if OS_DARWIN
+     graphics2_files += mac_icon.cpp
+     graphics2_files += macglutfix.m
++    graphics2_files += MultiGPUMigServer.c
++    graphics2_files += MultiGPUMigUser.c
+ endif
+ 
+ # library for OpenCL apps
diff --git a/src/framework/MacIOSurfaceManager.h b/src/framework/MacIOSurfaceManager.h
new file mode 100644
index 0000000000000000000000000000000000000000..c70a3df2216273cf5b18eac0869852153233b65e
--- /dev/null
+++ b/src/framework/MacIOSurfaceManager.h
@@ -0,0 +1,13 @@
+#ifndef MAC_IOSURFACE_MANAGER
+#define MAC_IOSURFACE_MANAGER
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+    void initMacIOSurface();
+    void renderMacIOSurfacePre();
+    bool renderMacIOSurfacePost();
+#ifdef __cplusplus
+}
+#endif
diff --git a/src/framework/MacIOSurfaceManager.m b/src/framework/MacIOSurfaceManager.m
new file mode 100644
index 0000000000000000000000000000000000000000..e0466e0fee63cd3ffcb7ea6fd16f48ac9d3484b5
--- /dev/null
+++ b/src/framework/MacIOSurfaceManager.m
@@ -0,0 +1,238 @@
+#define GL_DO_NOT_WARN_IF_MULTI_GL_VERSION_HEADERS_INCLUDED
+
+#include <Cocoa/Cocoa.h>
+#import <GLKit/GLKit.h>
+#include <servers/bootstrap.h>
+#import "MultiGPUMig.h"
+#import "MultiGPUMigServer.h"
+
+#include "MacIOSurfaceManager.h"
+
+
+// On OS 10.13 or later, use MachO comunication and IOSurfaceBuffer to
+// display the graphics output of our child graphics apps in our window.
+
+// Code adapted from Apple Developer Tech Support Sample Code MutiGPUIOSurface:
+// <https://developer.apple.com/library/content/samplecode/MultiGPUIOSurface>
+
+#define NUM_IOSURFACE_BUFFERS 2
+
+@interface MachServerController : NSObject <NSMachPortDelegate>
+{
+	NSMachPort *serverPort;
+	NSMachPort *localPort;
+
+	uint32_t serverPortName;
+	uint32_t localPortName;
+
+	NSMachPort *clientPort[16];
+	uint32_t clientPortNames[16];
+	uint32_t clientPortCount;
+}
+- (MachServerController *)init;
+- (kern_return_t)checkInClient:(mach_port_t)client_port index:(int32_t *)client_index;
+- (void)portDied:(NSNotification *)notification;
+- (void)sendIOSurfaceMachPortToClients: (uint32_t)index withMachPort:(mach_port_t) iosurface_port;
+- (bool)activeClients;
+@end
+
+static MachServerController *myserverController;
+
+static uint32_t currentFrameIndex;
+static GLsizei w, h;
+
+static IOSurfaceRef ioSurfaceBuffers[NUM_IOSURFACE_BUFFERS];
+static mach_port_t ioSurfaceMachPorts[NUM_IOSURFACE_BUFFERS];
+static GLuint textureNames[NUM_IOSURFACE_BUFFERS];
+static GLuint fboNames[NUM_IOSURFACE_BUFFERS];
+static GLuint depthBufferName;
+
+@implementation MachServerController
+- (MachServerController *)init
+{
+	[[NSNotificationCenter defaultCenter] addObserver:self
+	 selector:@selector(portDied:) name:NSPortDidBecomeInvalidNotification object:nil];
+
+	mach_port_t servicePortNum = MACH_PORT_NULL;
+	kern_return_t machErr;
+	char *portName = "edu.berkeley.boincsaver";
+
+	machErr = bootstrap_check_in(bootstrap_port, portName, &servicePortNum);
+	if (machErr != KERN_SUCCESS) {
+		[NSApp terminate:self];
+	}
+	serverPort = (NSMachPort*)[NSMachPort portWithMachPort:servicePortNum];
+
+	// Create a local dummy reply port to use with the mig reply stuff
+	localPort = [[NSMachPort alloc] init];
+
+	// Retrieve raw mach port names.
+	serverPortName = [serverPort machPort];
+	localPortName  = [localPort machPort];
+
+	[serverPort setDelegate:self];
+	[serverPort scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
+
+	return self;
+}
+
+- (void)portDied:(NSNotification *)notification
+{
+	NSPort *port = [notification object];
+	if(port == serverPort)
+	{
+		[NSApp terminate:self];
+	}
+	else
+	{
+		int i;
+		for(i = 0; i < clientPortCount+1; i++)
+		{
+			if([clientPort[i] isEqual:port])
+			{
+				[clientPort[i] release];
+				clientPort[i] = nil;
+				clientPortNames[i] = 0;
+			}
+		}
+	}
+}
+
+- (void)handleMachMessage:(void *)msg
+{
+	union __ReplyUnion___MGCMGSServer_subsystem reply;
+
+	mach_msg_header_t *reply_header = (void *)&reply;
+	kern_return_t kr;
+
+	if(MGSServer_server(msg, reply_header) && reply_header->msgh_remote_port != MACH_PORT_NULL)
+	{
+		kr = mach_msg(reply_header, MACH_SEND_MSG, reply_header->msgh_size, 0, MACH_PORT_NULL,
+		              0, MACH_PORT_NULL);
+		if(kr != 0)
+			[NSApp terminate:nil];
+	}
+}
+
+- (kern_return_t)checkInClient:(mach_port_t)client_port index:(int32_t *)client_index
+{
+	clientPortCount++;                          // clients always start at index 1
+	clientPortNames[clientPortCount] = client_port;
+	clientPort[clientPortCount] = [[NSMachPort alloc] initWithMachPort:client_port];
+
+	*client_index = clientPortCount;
+	return 0;
+}
+
+kern_return_t _MGSCheckinClient(mach_port_t server_port, mach_port_t client_port,
+                                int32_t *client_index)
+{
+	return [myserverController checkInClient:client_port index:client_index];
+}
+
+// For the MachO server, this is a no-op
+kern_return_t _MGSDisplayFrame(mach_port_t server_port, int32_t frame_index, uint32_t iosurface_port)
+{
+	return 0;
+}
+
+- (void)sendIOSurfaceMachPortToClients:(uint32_t)index withMachPort:(mach_port_t)iosurface_port
+{
+	int i;
+	for(i = 0; i < clientPortCount+1; i++)
+	{
+		if(clientPortNames[i])
+		{
+			_MGCDisplayFrame(clientPortNames[i], index, iosurface_port);
+		}
+	}
+}
+
+- (bool)activeClients
+{
+	return clientPortCount > 0 ? true : false;
+}
+@end
+
+void initMacIOSurface() {
+	int viewportRect[4];
+	glGetIntegerv(GL_VIEWPORT, (GLint*)viewportRect);
+	w = viewportRect[2];
+	h = viewportRect[3];
+
+	if (!myserverController) {
+		myserverController = [[[MachServerController alloc] init] retain];
+	}
+
+	if (!ioSurfaceBuffers[0]) {
+		// Set up all of our iosurface buffers
+		for(int i = 0; i < NUM_IOSURFACE_BUFFERS; i++) {
+			ioSurfaceBuffers[i] = IOSurfaceCreate((CFDictionaryRef) @{
+				(id)kIOSurfaceWidth: [NSNumber numberWithInt: w],
+				(id)kIOSurfaceHeight: [NSNumber numberWithInt: h],
+				(id)kIOSurfaceBytesPerElement: @4
+				});
+			ioSurfaceMachPorts[i] = IOSurfaceCreateMachPort(ioSurfaceBuffers[i]);
+		}
+	}
+}
+
+void renderMacIOSurfacePre() {
+	GLuint name, namef;
+
+	if(!myserverController || ![myserverController activeClients]) {
+		return;
+	}
+
+	if(!textureNames[currentFrameIndex]) {
+		NSOpenGLContext * myContext = [ NSOpenGLContext currentContext ];
+		CGLContextObj cgl_ctx = (CGLContextObj)[myContext CGLContextObj];
+
+		glGenTextures(1, &name);
+
+		glBindTexture(GL_TEXTURE_RECTANGLE, name);
+		// At the moment, CGLTexImageIOSurface2D requires the GL_TEXTURE_RECTANGLE target
+		CGLTexImageIOSurface2D(cgl_ctx, GL_TEXTURE_RECTANGLE, GL_RGBA, w, h, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV,
+		                       ioSurfaceBuffers[currentFrameIndex], 0);
+		glTexParameteri(GL_TEXTURE_RECTANGLE, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+		glTexParameteri(GL_TEXTURE_RECTANGLE, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+		glTexParameteri(GL_TEXTURE_RECTANGLE, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+		glTexParameteri(GL_TEXTURE_RECTANGLE, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+
+		// Generate an FBO and bind the texture to it as a render target.
+		glBindTexture(GL_TEXTURE_RECTANGLE, 0);
+
+		glGenFramebuffers(1, &namef);
+		glBindFramebuffer(GL_FRAMEBUFFER, namef);
+		glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_RECTANGLE, name, 0);
+
+		if(!depthBufferName) {
+			glGenRenderbuffers(1, &depthBufferName);
+			glRenderbufferStorage(GL_TEXTURE_RECTANGLE, GL_DEPTH, w, h);
+		}
+
+		glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_RECTANGLE, depthBufferName);
+
+		fboNames[currentFrameIndex] = namef;
+		textureNames[currentFrameIndex] = name;
+	}
+
+	glBindFramebuffer(GL_FRAMEBUFFER, fboNames[currentFrameIndex]);
+}
+
+bool renderMacIOSurfacePost() {
+
+	if(!myserverController || ![myserverController activeClients]) {
+		return false;
+	}
+
+	glBindFramebuffer(GL_FRAMEBUFFER, 0);
+	glFlush();
+
+	[myserverController sendIOSurfaceMachPortToClients: currentFrameIndex
+	 withMachPort:ioSurfaceMachPorts[currentFrameIndex]];
+
+	currentFrameIndex = (currentFrameIndex + 1) % NUM_IOSURFACE_BUFFERS;
+
+	return true;
+}
diff --git a/src/framework/Makefile b/src/framework/Makefile
index 97f02c8cac5b87c06e2ce5102f2ef1cc6f962765..8959472d9d71b8f9f49413fa09048e30ab60a05c 100644
--- a/src/framework/Makefile
+++ b/src/framework/Makefile
@@ -23,6 +23,7 @@ FRAMEWORK_SRC?=$(PWD)
 FRAMEWORK_INSTALL?=$(PWD)
 
 # config values
+OS = $(shell uname -s)
 CXX?=g++
 
 # variables
@@ -34,6 +35,9 @@ CPPFLAGS += -I$(FRAMEWORK_INSTALL)/include/boinc -I/usr/include
 
 DEPS = Makefile
 OBJS = AbstractGraphicsEngine.o GraphicsEngineFactory.o WindowManager.o Resource.o ResourceFactory.o BOINCClientAdapter.o Libxml2Adapter.o
+ifeq ($(OS), Darwin)
+OBJS += MacIOSurfaceManager.o
+endif
 
 # TODO: GraphicsEngineFactory obviously depends on the actual implementations (here starsphere)! need to change the structure! what about plugins?
 CPPFLAGS += -I$(FRAMEWORK_SRC) -I$(FRAMEWORK_SRC)/../starsphere
@@ -62,6 +66,11 @@ GraphicsEngineFactory.o: $(DEPS) $(FRAMEWORK_SRC)/GraphicsEngineFactory.cpp $(FR
 WindowManager.o: $(DEPS) $(FRAMEWORK_SRC)/WindowManager.cpp $(FRAMEWORK_SRC)/WindowManager.h
 	$(CXX) -g ${CPPFLAGS} -c $(FRAMEWORK_SRC)/WindowManager.cpp
 
+ifeq ($(OS), Darwin)
+MacIOSurfaceManager.o: $(DEPS) $(FRAMEWORK_SRC)/MacIOSurfaceManager.m $(FRAMEWORK_SRC)/MacIOSurfaceManager.h
+	$(CXX) -g ${CPPFLAGS} -c $(FRAMEWORK_SRC)/MacIOSurfaceManager.m
+endif
+
 BOINCClientAdapter.o: $(DEPS) $(FRAMEWORK_SRC)/BOINCClientAdapter.cpp $(FRAMEWORK_SRC)/BOINCClientAdapter.h
 	$(CXX) -g ${CPPFLAGS} -c $(FRAMEWORK_SRC)/BOINCClientAdapter.cpp
 
diff --git a/src/framework/WindowManager.cpp b/src/framework/WindowManager.cpp
index a30be54589195d25227ce07758d4cfc450fc57bb..dba0d9f8d05f187c40d9c7f0bb92bd3bf492b06e 100644
--- a/src/framework/WindowManager.cpp
+++ b/src/framework/WindowManager.cpp
@@ -20,6 +20,10 @@
 
 #include "WindowManager.h"
 
+#ifdef __APPLE__
+#include "MacIOSurfaceManager.h"
+#endif
+
 WindowManager::WindowManager()
 {
 	m_ScreensaverMode = false;
@@ -232,8 +236,24 @@ void WindowManager::eventLoop()
 				i++;
 #endif
 				// notify our observers (currently exactly one, hence front())
+#ifdef __APPLE__
+				if(!m_ScreensaverMode) {
+					eventObservers.front()->render(dtime());
+					SDL_GL_SwapWindow(m_Window);
+				}
+				else {
+					// render to IOSurface and pass it to BOINC via Mach RPC
+					renderMacIOSurfacePre();
+					eventObservers.front()->render(dtime());
+					if(renderMacIOSurfacePost() == false) {
+						// there isn't a Mach client attached, render normally
+						SDL_GL_SwapWindow(m_Window);
+					}
+				}
+#else
 				eventObservers.front()->render(dtime());
 				SDL_GL_SwapWindow(m_Window);
+#endif
 
 #ifdef DEBUG_VALGRIND
 			}
@@ -256,10 +276,13 @@ void WindowManager::eventLoop()
 				(event.type == SDL_MOUSEMOTION || event.type == SDL_MOUSEBUTTONDOWN ||
 				 event.type == SDL_KEYDOWN)) {
 
-			// we're in screensaver mode so exit on user input
-			SDL_GL_DeleteContext(m_GLContext);
-			SDL_DestroyWindow(m_Window);
-			SDL_Quit();
+			// don't handle events prematurely
+			if((SDL_GetWindowFlags(m_Window) & SDL_WINDOW_SHOWN)) {
+				// we're in screensaver mode so exit on user input
+				SDL_GL_DeleteContext(m_GLContext);
+				SDL_DestroyWindow(m_Window);
+				SDL_Quit();
+			}
 		}
 		else if (event.motion.state & (SDL_BUTTON(1) | SDL_BUTTON(3)) &&
 				 event.type == SDL_MOUSEMOTION) {
@@ -287,6 +310,13 @@ void WindowManager::eventLoop()
 			// (windoze needs to be reinitialized instead of just resized, oh well)
 			/// \todo Can we determine the host OS? On X11 a resize() is sufficient!
 			eventObservers.front()->initialize(m_CurrentWidth, m_CurrentHeight, getDisplayScalingFactor(), 0, true);
+
+#ifdef __APPLE__
+			// initialize IOSurface and Mach RPC server
+			if(m_ScreensaverMode) {
+				initMacIOSurface();
+			}
+#endif
 		}
 		else if (event.window.event == SDL_WINDOWEVENT_SIZE_CHANGED) {
 			m_WindowedWidth = event.window.data1;
@@ -471,6 +501,21 @@ void WindowManager::toggleFullscreen()
 		m_VideoModeFlags |= SDL_WINDOW_FULLSCREEN;
 	}
 
+#ifdef __APPLE__
+	// force window resize or BOINC's macOS screensaver host won't get the memo
+	if(m_ScreensaverMode) {
+		SDL_Rect rect;
+		int displayIndex = SDL_GetWindowDisplayIndex(m_Window);
+		if(displayIndex >= 0 && SDL_GetDisplayBounds(displayIndex, &rect) == 0) {
+			SDL_SetWindowSize(m_Window, rect.w, rect.h);
+			SDL_GL_GetDrawableSize(m_Window, &m_CurrentWidth, &m_CurrentHeight);
+		}
+		else {
+			cerr << "Could not force window resize: " << SDL_GetError() << endl;
+		}
+	}
+#endif
+
 	// notify our observers (currently exactly one, hence front())
 	// (windoze needs to be reinitialized instead of just resized, oh well)
 	/// \todo Can we determine the host OS? On X11 a resize() is sufficient!
diff --git a/src/starsphere/Makefile.macos b/src/starsphere/Makefile.macos
index 9bfb2a32f8ff3b2ea38c31591ef7304c13f42f92..9654342287d8bd3f6bff62a2dbc2b8dbc9307a07 100644
--- a/src/starsphere/Makefile.macos
+++ b/src/starsphere/Makefile.macos
@@ -31,7 +31,7 @@ LIBS += $(shell $(STARSPHERE_INSTALL)/bin/freetype-config --libs)
 LIBS += $(shell $(STARSPHERE_INSTALL)/bin/xml2-config --libs)
 LIBS += -lboinc_graphics2 -lboinc_api -lboinc -L$(STARSPHERE_INSTALL)/lib
 LIBS += $(shell $(STARSPHERE_INSTALL)/bin/sdl2-config --static-libs)
-LIBS += -Wl,-framework,OpenGL
+LIBS += -Wl,-framework,IOSurface -Wl,-framework,OpenGL
 LIBS += -lpthread -lm -lc
 
 CPPFLAGS += -I$(STARSPHERE_INSTALL)/include