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