mirror of https://github.com/BOINC/boinc.git
api: BOINC graphics library changes to support Mac OS 10.13
This commit is contained in:
parent
8495e2f806
commit
baac6772d2
|
@ -1,6 +1,6 @@
|
|||
// This file is part of BOINC.
|
||||
// http://boinc.berkeley.edu
|
||||
// Copyright (C) 2008 University of California
|
||||
// Copyright (C) 2017 University of California
|
||||
//
|
||||
// BOINC is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License
|
||||
|
@ -31,6 +31,9 @@
|
|||
#include "shmem.h"
|
||||
#include "boinc_api.h"
|
||||
#include "graphics2.h"
|
||||
#ifdef __APPLE__
|
||||
#include "x_opengl.h"
|
||||
#endif
|
||||
|
||||
double boinc_max_fps = 30.;
|
||||
double boinc_max_gfx_cpu_frac = 0.2;
|
||||
|
@ -84,7 +87,23 @@ bool throttled_app_render(int x, int y, double t) {
|
|||
if (boinc_max_gfx_cpu_frac) {
|
||||
boinc_calling_thread_cpu_time(t0);
|
||||
}
|
||||
|
||||
#ifdef __APPLE__
|
||||
if (UseSharedOffscreenBuffer()) {
|
||||
MacPrepareOffscreenBuffer();
|
||||
}
|
||||
#endif
|
||||
app_graphics_render(x, y, t);
|
||||
|
||||
#ifdef __APPLE__
|
||||
if (UseSharedOffscreenBuffer()) {
|
||||
MacPassOffscreenBufferToScreenSaver();
|
||||
|
||||
//app_graphics_render(x, y, t); // For testing only
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
if (boinc_max_gfx_cpu_frac) {
|
||||
boinc_calling_thread_cpu_time(t1);
|
||||
total_render_time += t1 - t0;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
// This file is part of BOINC.
|
||||
// http://boinc.berkeley.edu
|
||||
// Copyright (C) 2008 University of California
|
||||
// Copyright (C) 2017 University of California
|
||||
//
|
||||
// BOINC is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License
|
||||
|
@ -118,6 +118,11 @@ static void maybe_render() {
|
|||
|
||||
|
||||
if (throttled_app_render(new_width, new_height, dtime())) {
|
||||
#ifdef __APPLE__
|
||||
if (UseSharedOffscreenBuffer()) {
|
||||
return; // Don't try to send garbage to screen
|
||||
}
|
||||
#endif
|
||||
glutSwapBuffers();
|
||||
if (! fullscreen) {
|
||||
// If user has changed window size, wait until it stops
|
||||
|
@ -244,3 +249,48 @@ void boinc_graphics_loop(int argc, char** argv, const char* title) {
|
|||
#endif
|
||||
glutMainLoop();
|
||||
}
|
||||
|
||||
#ifdef __APPLE__
|
||||
|
||||
bool UseSharedOffscreenBuffer() {
|
||||
static bool alreadyTested = false;
|
||||
static bool needSharedGfxBuffer = false;
|
||||
|
||||
//return true; // FOR TESTING ONLY
|
||||
if (alreadyTested) {
|
||||
return needSharedGfxBuffer;
|
||||
}
|
||||
alreadyTested = true;
|
||||
if (fullscreen) {
|
||||
SInt32 major = -1;
|
||||
SInt32 minor = -1;
|
||||
char vers[100], *p1 = NULL;
|
||||
FILE *f;
|
||||
vers[0] = '\0';
|
||||
f = popen("sw_vers -productVersion", "r");
|
||||
if (f) {
|
||||
fscanf(f, "%s", vers);
|
||||
pclose(f);
|
||||
}
|
||||
if (vers[0] == '\0') {
|
||||
fprintf(stderr, "popen(\"sw_vers -productVersion\" failed\n");
|
||||
fflush(stderr);
|
||||
return false;
|
||||
}
|
||||
// Extract the major system version number
|
||||
major = atoi(vers);
|
||||
if (major > 10) { // OS 11.0 or later
|
||||
needSharedGfxBuffer = true;
|
||||
return true;
|
||||
}
|
||||
// Extract the minor system version number
|
||||
p1 = strchr(vers, '.');
|
||||
minor = atoi(p1+1);
|
||||
if (minor > 12) { // OS 10.13 or later
|
||||
needSharedGfxBuffer = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
|
357
api/macglutfix.m
357
api/macglutfix.m
|
@ -1,6 +1,6 @@
|
|||
// Berkeley Open Infrastructure for Network Computing
|
||||
// http://boinc.berkeley.edu
|
||||
// Copyright (C) 2005 University of California
|
||||
// Copyright (C) 2017 University of California
|
||||
//
|
||||
// This is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU Lesser General Public
|
||||
|
@ -21,13 +21,32 @@
|
|||
// macglutfix.m
|
||||
//
|
||||
|
||||
#define CREATE_LOG 0 // Set to 1 for debugging
|
||||
|
||||
#define GL_DO_NOT_WARN_IF_MULTI_GL_VERSION_HEADERS_INCLUDED
|
||||
|
||||
#include <Cocoa/Cocoa.h>
|
||||
#include <mach/mach_time.h>
|
||||
#include <pthread.h>
|
||||
#include "x_opengl.h"
|
||||
#import <OpenGL/CGLIOSurface.h>
|
||||
#import <GLKit/GLKit.h>
|
||||
#include <servers/bootstrap.h>
|
||||
#import "MultiGPUMig.h"
|
||||
#import "MultiGPUMigServer.h"
|
||||
|
||||
#include "diagnostics.h"
|
||||
#include "boinc_gl.h"
|
||||
#include "boinc_glut.h"
|
||||
|
||||
extern bool fullscreen; // set in graphics2_unix.cpp
|
||||
|
||||
// For unknown reason, "boinc_api.h" gets a compile
|
||||
// error here so just declare boinc_is_standalone()
|
||||
//#include "boinc_api.h"
|
||||
extern int boinc_is_standalone(void);
|
||||
|
||||
// int set_realtime(int period, int computation, int constraint);
|
||||
void MacGLUTFix(bool isScreenSaver);
|
||||
void BringAppToFront(void);
|
||||
|
||||
|
@ -54,11 +73,6 @@ void MacGLUTFix(bool isScreenSaver) {
|
|||
}
|
||||
}
|
||||
|
||||
// In screensaver mode, set our window's level just above
|
||||
// our BOINC screensaver's window level so it can appear
|
||||
// over it. This doesn't interfere with the screensaver
|
||||
// password dialog because the dialog appears only after
|
||||
// our screensaver is closed.
|
||||
myContext = [ NSOpenGLContext currentContext ];
|
||||
if (myContext)
|
||||
myView = [ myContext view ];
|
||||
|
@ -66,18 +80,58 @@ void MacGLUTFix(bool isScreenSaver) {
|
|||
myWindow = [ myView window ];
|
||||
if (myWindow == nil)
|
||||
return;
|
||||
|
||||
|
||||
if (!isScreenSaver) {
|
||||
NSButton *closeButton = [myWindow standardWindowButton:NSWindowCloseButton ];
|
||||
[closeButton setEnabled:YES];
|
||||
[myWindow setDocumentEdited: NO];
|
||||
return;
|
||||
}
|
||||
|
||||
if ([ myWindow level ] == GlutFullScreenWindowLevel)
|
||||
[ myWindow setLevel:RealSaverLevel+20 ];
|
||||
|
||||
// As of OS 10.13, app windows can no longer appear on top of screensaver
|
||||
// window, but we still use this method on older versions of OS X for
|
||||
// compatibility with older project graphics apps.
|
||||
if (!UseSharedOffscreenBuffer()) {
|
||||
// In screensaver mode, set our window's level just above
|
||||
// our BOINC screensaver's window level so it can appear
|
||||
// over it. This doesn't interfere with the screensaver
|
||||
// password dialog because the dialog appears only after
|
||||
// our screensaver is closed.
|
||||
if ([ myWindow level ] == GlutFullScreenWindowLevel) {
|
||||
[ myWindow setLevel:RealSaverLevel+20 ];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if 0
|
||||
// NOT USED: See comments in animateOneFrame in Mac_Saver_ModuleView.m
|
||||
// <https://developer.apple.com/library/content/technotes/tn2169>
|
||||
int set_realtime(int period, int computation, int constraint) {
|
||||
mach_timebase_info_data_t timebase_info;
|
||||
mach_timebase_info(&timebase_info);
|
||||
|
||||
const uint64_t NANOS_PER_MSEC = 1000000ULL;
|
||||
double clock2abs = ((double)timebase_info.denom / (double)timebase_info.numer) * NANOS_PER_MSEC;
|
||||
|
||||
thread_time_constraint_policy_data_t policy;
|
||||
policy.period = period;
|
||||
policy.computation = (uint32_t)(computation * clock2abs); // computation ms of work
|
||||
policy.constraint = (uint32_t)(constraint * clock2abs);
|
||||
// policy.preemptible = FALSE;
|
||||
policy.preemptible = TRUE;
|
||||
|
||||
int kr = thread_policy_set(pthread_mach_thread_np(pthread_self()),
|
||||
THREAD_TIME_CONSTRAINT_POLICY,
|
||||
(thread_policy_t)&policy,
|
||||
THREAD_TIME_CONSTRAINT_POLICY_COUNT);
|
||||
if (kr != KERN_SUCCESS) {
|
||||
fprintf(stderr, "set_realtime() failed.\n");
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
#endif
|
||||
|
||||
void BringAppToFront() {
|
||||
[ NSApp activateIgnoringOtherApps:YES ];
|
||||
}
|
||||
|
@ -86,3 +140,286 @@ void HideThisApp() {
|
|||
[ NSApp hide:NSApp ];
|
||||
}
|
||||
|
||||
// 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 ServerController : NSObject <NSMachPortDelegate>
|
||||
{
|
||||
NSMachPort *serverPort;
|
||||
NSMachPort *localPort;
|
||||
|
||||
uint32_t serverPortName;
|
||||
uint32_t localPortName;
|
||||
|
||||
NSMachPort *clientPort[16];
|
||||
uint32_t clientPortNames[16];
|
||||
uint32_t clientPortCount;
|
||||
}
|
||||
- (ServerController *)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;
|
||||
|
||||
@end
|
||||
|
||||
static ServerController *myserverController;
|
||||
|
||||
static uint32_t nextFrameIndex;
|
||||
static uint32_t currentFrameIndex;
|
||||
|
||||
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 ServerController
|
||||
|
||||
- (ServerController *)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";
|
||||
|
||||
// NSMachBootstrapServer is deprecated in OS 10.13, so use bootstrap_look_up
|
||||
// serverPort = [(NSMachPort *)([[NSMachBootstrapServer sharedInstance] servicePortWithName:@"edu.berkeley.boincsaver"]) retain];
|
||||
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];
|
||||
|
||||
// NOT USED: See comments in animateOneFrame in Mac_Saver_ModuleView.m
|
||||
#if 0
|
||||
// This is an alternate method to get enough CPU cycles when we
|
||||
// are running in the background behind ScreensaverEngine.app
|
||||
set_realtime(0, 5, 33);
|
||||
//set_realtime(0, 5, 10);
|
||||
//set_realtime(33, 5, 33);
|
||||
//set_realtime(30, 3, 6);
|
||||
//set_realtime(30, 10, 20);
|
||||
#endif
|
||||
|
||||
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])
|
||||
{
|
||||
// print_to_log_file("BOINCSCR: about to call _MGCDisplayFrame with iosurface_port %d, IOSurfaceGetID %d and frameIndex %d", (int)iosurface_port, IOSurfaceGetID(ioSurfaceBuffers[index]), (int)index);
|
||||
_MGCDisplayFrame(clientPortNames[i], index, iosurface_port);
|
||||
}
|
||||
}
|
||||
}
|
||||
@end
|
||||
|
||||
|
||||
void MacPrepareOffscreenBuffer() {
|
||||
GLuint name, namef;
|
||||
|
||||
if (!myserverController) {
|
||||
myserverController = [[[ServerController alloc] init] retain];
|
||||
}
|
||||
|
||||
if (!ioSurfaceBuffers[0]) {
|
||||
NSOpenGLContext * myContext = [ NSOpenGLContext currentContext ];
|
||||
NSView *myView = [ myContext view ];
|
||||
GLsizei w = myView.bounds.size.width;
|
||||
GLsizei h = myView.bounds.size.height;
|
||||
|
||||
// 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]);
|
||||
}
|
||||
}
|
||||
|
||||
if(!textureNames[nextFrameIndex])
|
||||
{
|
||||
NSOpenGLContext * myContext = [ NSOpenGLContext currentContext ];
|
||||
NSView *myView = [ myContext view ];
|
||||
GLsizei w = myView.bounds.size.width;
|
||||
GLsizei h = myView.bounds.size.height;
|
||||
|
||||
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[nextFrameIndex], 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);
|
||||
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
|
||||
fboNames[nextFrameIndex] = namef;
|
||||
textureNames[nextFrameIndex] = name;
|
||||
}
|
||||
currentFrameIndex = nextFrameIndex;
|
||||
nextFrameIndex = (nextFrameIndex + 1) % NUM_IOSURFACE_BUFFERS;
|
||||
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, fboNames[currentFrameIndex]);
|
||||
}
|
||||
|
||||
void MacPassOffscreenBufferToScreenSaver() {
|
||||
glutSwapBuffers();
|
||||
[myserverController sendIOSurfaceMachPortToClients: currentFrameIndex
|
||||
withMachPort:ioSurfaceMachPorts[currentFrameIndex]];
|
||||
glFlush();
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
}
|
||||
|
||||
// Code for debugging:
|
||||
|
||||
#if CREATE_LOG
|
||||
void strip_cr(char *buf)
|
||||
{
|
||||
char *theCR;
|
||||
|
||||
theCR = strrchr(buf, '\n');
|
||||
if (theCR)
|
||||
*theCR = '\0';
|
||||
theCR = strrchr(buf, '\r');
|
||||
if (theCR)
|
||||
*theCR = '\0';
|
||||
}
|
||||
#endif
|
||||
|
||||
void print_to_log_file(const char *format, ...) {
|
||||
#if CREATE_LOG
|
||||
va_list args;
|
||||
char buf[256];
|
||||
time_t t;
|
||||
FILE *f;
|
||||
if (fullscreen) {
|
||||
// We can't write to our home directory if running as user / group boinc_project
|
||||
f = fopen("/Users/Shared/test_log.txt", "a");
|
||||
} else {
|
||||
strlcpy(buf, getenv("HOME"), sizeof(buf));
|
||||
strlcat(buf, "/Documents/test_log.txt", sizeof(buf));
|
||||
f = fopen(buf, "a");
|
||||
// freopen(buf, "a", stdout);
|
||||
//freopen(buf, "a", stderr);
|
||||
}
|
||||
if (!f) return;
|
||||
time(&t);
|
||||
strcpy(buf, asctime(localtime(&t)));
|
||||
strip_cr(buf);
|
||||
|
||||
fputs(buf, f);
|
||||
fputs(" ", f);
|
||||
|
||||
va_start(args, format);
|
||||
vfprintf(f, format, args);
|
||||
va_end(args);
|
||||
|
||||
fputs("\n", f);
|
||||
fflush(f);
|
||||
fclose(f);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
// This file is part of BOINC.
|
||||
// http://boinc.berkeley.edu
|
||||
// Copyright (C) 2008 University of California
|
||||
// Copyright (C) 2017 University of California
|
||||
//
|
||||
// BOINC is free software; you can redistribute it and/or modify it
|
||||
// under the terms of the GNU Lesser General Public License
|
||||
|
@ -25,9 +25,15 @@ extern "C" {
|
|||
extern int xwin_glut_is_initialized();
|
||||
|
||||
#ifdef __APPLE__
|
||||
extern void MacGLUTFix(bool isScreenSaver);
|
||||
extern void MacGLUTFix(bool isScreenSaver);
|
||||
extern void MacPrepareOffscreenBuffer(void);
|
||||
extern void MacPassOffscreenBufferToScreenSaver(void);
|
||||
extern void BringAppToFront(void);
|
||||
extern void HideThisApp(void);
|
||||
extern bool UseSharedOffscreenBuffer(void);
|
||||
|
||||
extern void print_to_log_file(const char *format, ...);
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
|
Loading…
Reference in New Issue