Building standalone OpenGL application without Xcode, with bundled separate shader file
Shadoweaver
Posted on May 7, 2018
Background
I have a "Hello Rectangle" project which runs as expected when invoked in Terminal.app. But I'm having trouble creating a standalone macOS bundle out of it.
Let's just say using full Xcode is out of question. I'm very tight on disk space and I'm not doing iOS dev.
Instead I'm using brewed toolchain, including gcc-8
, glfw
and glew
.
The code base looks like this:
src
├── include
│ ├── draw.hpp
│ └── public.hpp
├── resc
│ ├── rect.frag
│ └── rect.vert
├── draw.cpp
├── init-shader.cpp
└── window.cpp
The actual compile command is:
$ g++-8 -lglew -lglfw -framework OpenGL -o <binary> <sources>
Currently, after make
, the binary can be invoked in terminal, providing both shader files are present in the same directory. The file reader in init-shader.cpp
accepts just filename without preceding path.
Problem
I don't quite want to expose shader files, and if I read this SO answer right, it suggests either you pack shader files in final release (what I'm trying), or you hard code them in other sources (even more undesired). Plus, I want to make sure when I distribute it as a standalone .app
bundle, it can run out of box (as long as lowest OpenGL version requirement is met) instead of having to get all brewed dependencies.
I read through this SO question, attempted the operations mentioned, namely:
- Build with the
-headerpad_max_install_names
flag. - Create
Info.plist
and structre the directory as macOS dictates. - Find all dylibs needed using
otool -L
on executable, and recursively useotool -L
on them to avoid DLL hell (for a want of macOS-specific name), exclude what Apple have guaranteed, and copy them toa.app/Contents/Frameworks
. Four of them are found in my case, all in Homebrew's cellar:- libglfw.3.dylib
- libGLEW.2.1.dylib
- libstdc++.6.dylib
- libgcc_s.1.dylib
- Use
install_name_tool -change
to modify the reference to dylibs in binary file to those copied. - Wrap the entry point in
Info.plist
into a shell script so that working directory is right, wherever it's invoked.
Terminal didn't complain all the way through. But it won't run, whether using open -a a.app
or directly invoke the actual binary or that tiny script.
And I found this blog post which implies I also need to invoke install_name_tool -id
between (2) and (3), and codesign
them between (3) and (4).
But it still won't run.
In the former case, the shader linker says shader programs are corrupted, but compilation will succeed, but compilation log is empty; in the latter case the app crashes and dyld
complains.
I suspect the former is mostly right, since when I blindly change that folder name from standard Contents
to content
(or any other name), it will run if I invoke the binary from terminal, and even when I brew unlink
glew
and glfw
, suggesting the binary knows where to look up dylibs and shader files.
What did I miss?
Probing
At this time my release dir looked like this:
Contents
├── Frameworks
│ ├── libGLEW.2.1.dylib
│ ├── libgcc_s.1.dylib
│ ├── libglfw.3.dylib
│ └── libstdc++.6.dylib
├── MacOS
│ ├── hello-rect <- the actual binary
│ ├── launcher.sh <- entry wrapper for working dir as the above link suggests
│ ├── rect.frag
│ └── rect.vert
└── Info.plist
Because the unbundled version obviously looks nowhere but cwd
, I thought in bundles they would do the same. Later on Google lead me to believe that I need CoreFoundation
API to correctly read files in bundle, and it murdered my day. I learned that there should be a Resources
folder in bundle, and then added CF codes, which for some reason always give segfault 11. I even brought up one new question on SO.
The real caveat
By pure chance I saw yet another SO question which finally saved my day. Turns out, if GLFW sees Resources
in bundle, it changes working dir to that dir!
Solution
So the take away is this: structure the release folder like following:
Contents
├── Frameworks
│ └── <dylibs>
├── MacOS
│ ├── hello-rect
│ └── launcher.sh
├── Resources
│ └── <shader files>
└── Info.plist
Then, walk through the previous list, without install_name_tool -id
or codesign
, and we're done.
One final question: is make
suitable for wrapping all these into a script?
Posted on May 7, 2018
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
November 30, 2024