diff --git a/libobs-opengl/gl-cocoa.m b/libobs-opengl/gl-cocoa.m new file mode 100644 index 000000000..bd3d7acc9 --- /dev/null +++ b/libobs-opengl/gl-cocoa.m @@ -0,0 +1,253 @@ +/****************************************************************************** + Copyright (C) 2013 by Ruwen Hahn + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +******************************************************************************/ + +#include +#include "gl-subsystem.h" +#include + +#import +#import + + +//#include "util/darray.h" + + +struct gl_windowinfo { + NSView *view; +}; + +struct gl_platform { + NSOpenGLContext *context; + GLuint vao; + struct gs_swap_chain swap; +}; + +static NSOpenGLContext *gl_context_create(struct gs_init_data *info) +{ + unsigned attrib_count = 0; + +#define ADD_ATTR(x) { attributes[attrib_count++] = (NSOpenGLPixelFormatAttribute)x; } +#define ADD_ATTR2(x, y) { ADD_ATTR(x); ADD_ATTR(y); } + + NSOpenGLPixelFormatAttribute attributes[40]; + + switch(info->num_backbuffers) { + case 0: + break; + case 1: + ADD_ATTR(NSOpenGLPFADoubleBuffer); + break; + case 2: + ADD_ATTR(NSOpenGLPFATripleBuffer); + break; + default: + blog(LOG_ERROR, "Requested backbuffers (%d) not supported", info->num_backbuffers); + } + + ADD_ATTR(NSOpenGLPFAClosestPolicy); + ADD_ATTR2(NSOpenGLPFAOpenGLProfile, NSOpenGLProfileVersion3_2Core); + + int color_bits = 0;//get_color_format_bits(info->format); + if(color_bits == 0) color_bits = 24; + else if(color_bits < 15) color_bits = 15; + + ADD_ATTR2(NSOpenGLPFAColorSize, color_bits); + + ADD_ATTR2(NSOpenGLPFAAlphaSize, 8); + + ADD_ATTR2(NSOpenGLPFADepthSize, 16); + + ADD_ATTR(0); + +#undef ADD_ATTR2 +#undef ADD_ATTR + + NSOpenGLPixelFormat *pf = [[NSOpenGLPixelFormat alloc] initWithAttributes:attributes];; + if(!pf) { + blog(LOG_ERROR, "Failed to create pixel format"); + return NULL; + } + + NSOpenGLContext *context = [[NSOpenGLContext alloc] initWithFormat:pf shareContext:nil]; + [pf release]; + if(!context) { + blog(LOG_ERROR, "Failed to create context"); + return NULL; + } + + [context setView:info->view]; + + return context; +} + +static inline void required_extension_error(const char *extension) +{ + blog(LOG_ERROR, "OpenGL extension %s is required", extension); +} + +static bool gl_init_extensions(device_t device) +{ + glewExperimental=true; + GLenum error = glewInit(); + if(error != GLEW_OK) { + blog(LOG_ERROR, "glewInit failed, %u\n%s\n", error, glewGetErrorString(error)); + return false; + } + + if(!GLEW_VERSION_2_1) { + blog(LOG_ERROR, "OpenGL 2.1 minimum required by the graphics " + "adapter"); + return false; + } + + if(!GLEW_ARB_framebuffer_object) { + required_extension_error("GL_ARB_framebuffer_object"); + return false; + } + + if(!GLEW_ARB_separate_shader_objects) { + required_extension_error("GL_ARB_separate_shader_objects"); + return false; + } + + glGetError(); //something inside glew produces error code 1280 (invalid enum) + + device->copy_type = COPY_TYPE_FBO_BLIT; + + return true; +} + +static bool gl_init_default_swap(struct gl_platform *plat, device_t dev, + struct gs_init_data *info) +{ + if(!(plat->context = gl_context_create(info))) + return false; + + plat->swap.device = dev; + plat->swap.info = *info; + plat->swap.wi = gl_windowinfo_create(info); + + return plat->swap.wi != NULL; +} + +struct gl_platform *gl_platform_create(device_t device, + struct gs_init_data *info) +{ + struct gl_platform *plat = bmalloc(sizeof(struct gl_platform)); + memset(plat, 0, sizeof(struct gl_platform)); + + if(!gl_init_default_swap(plat, device, info)) + goto fail; + + [plat->context makeCurrentContext]; + + if(!gl_init_extensions(device)) + goto fail; + + gl_gen_vertex_arrays(1, &plat->vao); + gl_bind_vertex_array(plat->vao); + + if (GLEW_ARB_seamless_cube_map) { + glEnable(GL_TEXTURE_CUBE_MAP_SEAMLESS); + gl_success("GL_TEXTURE_CUBE_MAP_SEAMLESS"); + } + + return plat; + +fail: + blog(LOG_ERROR, "gl_platform_create failed"); + gl_platform_destroy(plat); + return NULL; +} + +struct gs_swap_chain *gl_platform_getswap(struct gl_platform *platform) +{ + if(platform) + return &platform->swap; + return NULL; +} + +void gl_platform_destroy(struct gl_platform *platform) +{ + if(!platform) + return; + + gl_delete_vertex_arrays(1, &platform->vao); + [platform->context release]; + platform->context = nil; + gl_windowinfo_destroy(platform->swap.wi); + + bfree(platform); +} + +struct gl_windowinfo *gl_windowinfo_create(struct gs_init_data *info) +{ + if(!info) + return NULL; + + if(!info->view) + return NULL; + + struct gl_windowinfo *wi = bmalloc(sizeof(struct gl_windowinfo)); + memset(wi, 0, sizeof(struct gl_windowinfo)); + + wi->view = info->view; + + return wi; +} + +void gl_windowinfo_destroy(struct gl_windowinfo *wi) +{ + if(!wi) + return; + + wi->view = nil; + bfree(wi); +} + +void device_entercontext(device_t device) +{ + [device->plat->context makeCurrentContext]; + //gl_bind_vertex_array(device->plat->vao); +} + +void device_leavecontext(device_t device) +{ + [NSOpenGLContext clearCurrentContext]; +} + +void device_load_swapchain(device_t device, swapchain_t swap) +{ + if(!swap) + swap = &device->plat->swap; + + if(device->cur_swap == swap) + return; + + device->cur_swap = swap; +} + +void device_present(device_t device) +{ + [device->plat->context flushBuffer]; +} + +void gl_getclientsize(struct gs_swap_chain *swap, uint32_t *width, uint32_t *height) +{ + if(width) *width = swap->info.cx; + if(height) *height = swap->info.cy; +} diff --git a/libobs/graphics/graphics.h b/libobs/graphics/graphics.h index 48c4139df..a754c8304 100644 --- a/libobs/graphics/graphics.h +++ b/libobs/graphics/graphics.h @@ -19,6 +19,9 @@ #include "../util/bmem.h" #include "input.h" +#ifdef __APPLE__ +#include +#endif /* * This is an API-independent graphics subsystem wrapper. @@ -410,7 +413,7 @@ struct gs_init_data { #if defined(_WIN32) void *hwnd; #elif defined(__APPLE__) - /* TODO */ + __unsafe_unretained id view; #elif defined(__posix__) /* TODO */ #endif diff --git a/libobs/obs-cocoa.c b/libobs/obs-cocoa.c new file mode 100644 index 000000000..96dad6ecb --- /dev/null +++ b/libobs/obs-cocoa.c @@ -0,0 +1,61 @@ +/****************************************************************************** + Copyright (C) 2013 by Ruwen Hahn + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +******************************************************************************/ + +#include "util/platform.h" +#include "util/dstr.h" +#include "obs.h" +#include "obs-data.h" + +#include + +// support both foo.so and libfoo.so for now +static const char *plugin_patterns[] = { + "../plugins/%s.so", + "../plugins/lib%s.so" +}; +static const int plugin_patterns_size = sizeof(plugin_patterns)/sizeof(plugin_patterns[0]); + +char *find_plugin(const char *plugin) +{ + struct dstr path; + dstr_init(&path); + for(int i = 0; i < plugin_patterns_size; i++) { + dstr_printf(&path, plugin_patterns[i], plugin); + if(!access(path.array, F_OK)) + break; + } + + return path.array; +} + +/* on windows, points to [base directory]/libobs */ +char *find_libobs_data_file(const char *file) +{ + struct dstr path; + dstr_init_copy(&path, "../libobs/"); + dstr_cat(&path, file); + return path.array; +} + +/* on windows, data files should always be in [base directory]/data */ +char *obs_find_plugin_file(const char *file) +{ + struct dstr path; + dstr_init_copy(&path, "../data/"); + dstr_cat(&path, file); + return path.array; +} diff --git a/libobs/util/platform-cocoa.c b/libobs/util/platform-cocoa.c new file mode 100644 index 000000000..f4bd80fe5 --- /dev/null +++ b/libobs/util/platform-cocoa.c @@ -0,0 +1,100 @@ +/****************************************************************************** + Copyright (c) 2013 by Ruwen Hahn + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source + distribution. +******************************************************************************/ + +#include "base.h" +#include "platform.h" +#include "dstr.h" + +#include +#include +#include + +#include +#include +#include + +void *os_dlopen(const char *path) +{ + struct dstr dylib_name; + dstr_init_copy(&dylib_name, path); + if(!dstr_find(&dylib_name, ".so")) + dstr_cat(&dylib_name, ".so"); + + void *res = dlopen(dylib_name.array, RTLD_LAZY); + if(!res) + blog(LOG_ERROR, "os_dlopen(%s->%s): %s\n", + path, dylib_name.array, dlerror()); + + dstr_free(&dylib_name); + return res; +} + +void *os_dlsym(void *module, const char *func) +{ + return dlsym(module, func); +} + +void os_dlclose(void *module) +{ + dlclose(module); +} + +void os_sleepto_ns(uint64_t time_target) +{ + uint64_t current = os_gettime_ns(); + if(time_target < current) + return; + time_target -= current; + struct timespec req, + remain; + memset(&req, 0, sizeof(req)); + memset(&remain, 0, sizeof(remain)); + req.tv_sec = time_target/1000000000; + req.tv_nsec = time_target%1000000000; + while(nanosleep(&req, &remain)) + { + req = remain; + memset(&remain, 0, sizeof(remain)); + } +} + +void os_sleep_ms(uint32_t duration) +{ + usleep(duration*1000); +} + +uint64_t os_gettime_ns(void) +{ + uint64_t t = mach_absolute_time(); +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + Nanoseconds nano = AbsoluteToNanoseconds(*(AbsoluteTime*) &t); +#pragma clang diagnostic pop + return *(uint64_t*) &nano; +} + +uint64_t os_gettime_ms(void) +{ + return os_gettime_ns()/1000000; +} + diff --git a/test/osx/test.mm b/test/osx/test.mm new file mode 100644 index 000000000..fb6b9791f --- /dev/null +++ b/test/osx/test.mm @@ -0,0 +1,193 @@ +#include +#include + +#include +#include + +#import +#import +#import + +#include "util/base.h" +#include "obs.h" + +static const int cx = 800; +static const int cy = 600; + + + +/* --------------------------------------------------- */ + +using SourceContext = std::unique_ptr>; +static SourceContext autorelease(obs_source_t s) +{ + return SourceContext(s, obs_source_destroy); +} + +using SceneContext = std::unique_ptr>; +static SceneContext autorelease(obs_scene_t s) +{ + return SceneContext(s, obs_scene_destroy); +} + +/* --------------------------------------------------- */ + +static void CreateOBS(NSWindow *win) +{ + struct video_info vi; + memset(&vi, 0, sizeof(struct video_info)); + vi.fps_num = 30000; + vi.fps_den = 1001; + vi.width = cx; + vi.height = cy; + vi.name = "video"; + + struct gs_init_data gsid; + memset(&gsid, 0, sizeof(gsid)); + gsid.view = [win contentView]; + gsid.cx = cx; + gsid.cy = cy; + gsid.num_backbuffers = 2; + gsid.format = GS_RGBA; + + if (!obs_startup("libobs-opengl", &gsid, &vi, NULL)) + throw "Couldn't create OBS"; +} + +static void AddTestItems(obs_scene_t scene, obs_source_t source) +{ + obs_sceneitem_t item = NULL; + struct vec2 v2; + + item = obs_scene_add(scene, source); + vec2_set(&v2, 100.0f, 200.0f); + obs_sceneitem_setpos(item, &v2); + obs_sceneitem_setrot(item, 10.0f); + vec2_set(&v2, 20.0f, 2.0f); + obs_sceneitem_setscale(item, &v2); + + item = obs_scene_add(scene, source); + vec2_set(&v2, 200.0f, 100.0f); + obs_sceneitem_setpos(item, &v2); + obs_sceneitem_setrot(item, -45.0f); + vec2_set(&v2, 5.0f, 7.0f); + obs_sceneitem_setscale(item, &v2); +} + +@interface window_closer : NSObject {} +@end + +@implementation window_closer + ++(id)window_closer +{ + return [[window_closer alloc] init]; +} + +-(void)windowWillClose:(NSNotification *)notification +{ + [NSApp stop:self]; +} +@end + +static NSWindow *CreateTestWindow() +{ + [NSApplication sharedApplication]; + + ProcessSerialNumber psn = {0, kCurrentProcess}; + TransformProcessType(&psn, kProcessTransformToForegroundApplication); +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + SetFrontProcess(&psn); +#pragma clang diagnostic pop + + NSRect content_rect = NSMakeRect(0, 0, cx, cy); + NSWindow *win = [[NSWindow alloc] + initWithContentRect:content_rect + styleMask:NSTitledWindowMask | NSClosableWindowMask + backing:NSBackingStoreBuffered + defer:NO]; + if(!win) + return nil; + + [win orderFrontRegardless]; + + NSView *view = [[NSView alloc] initWithFrame:content_rect]; + if(!view) + return nil; + + + [win setContentView:view]; + [win setTitle:@"foo"]; + [win center]; + [win makeMainWindow]; + [win setDelegate:[window_closer window_closer]]; + return win; +} + +static void test() +{ + try { + NSWindow *win = CreateTestWindow(); + if (!win) + throw "Couldn't create main window"; + + CreateOBS(win); + + /* ------------------------------------------------------ */ + /* load module */ + if (obs_load_module("test-input") != 0) + throw "Couldn't load module"; + + /* ------------------------------------------------------ */ + /* create source */ + SourceContext source = autorelease(obs_source_create(SOURCE_INPUT, + "random", NULL)); + if (!source) + throw "Couldn't create random test source"; + + /* ------------------------------------------------------ */ + /* create filter */ + SourceContext filter = autorelease(obs_source_create(SOURCE_FILTER, + "test", NULL)); + if (!filter) + throw "Couldn't create test filter"; + obs_source_filter_add(source.get(), filter.get()); + + /* ------------------------------------------------------ */ + /* create scene and add source to scene (twice) */ + SceneContext scene = autorelease(obs_scene_create()); + if (!scene) + throw "Couldn't create scene"; + + AddTestItems(scene.get(), source.get()); + + /* ------------------------------------------------------ */ + /* set the scene as the primary draw source and go */ + obs_set_primary_source(obs_scene_getsource(scene.get())); + + [NSApp run]; + + obs_set_primary_source(NULL); + + } catch (char const *error) { + printf("%s\n", error); + } + + obs_shutdown(); + + blog(LOG_INFO, "Number of memory leaks: %zu", bnum_allocs()); +} + +/* --------------------------------------------------- */ + +int main() +{ + @autoreleasepool { + test(); + } + + return 0; +}