├── .gitignore ├── build.sh ├── gen ├── .gitignore ├── cdef.py ├── glext.h └── headers.h ├── glfw_base.lua ├── readme.md └── test.lua /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | glfw.lua 3 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | gcc -E -dD -I ./gen/ ./gen/headers.h | python3 ./gen/cdef.py | cat - glfw_base.lua > glfw.lua 4 | -------------------------------------------------------------------------------- /gen/.gitignore: -------------------------------------------------------------------------------- 1 | gl.lua 2 | -------------------------------------------------------------------------------- /gen/cdef.py: -------------------------------------------------------------------------------- 1 | 2 | import re 3 | import sys 4 | 5 | defines = {} 6 | cdefs = [] 7 | 8 | location_re = re.compile(r'^# \d+ "([^"]*)"') 9 | glpath_re = re.compile(r'^(?:.*[\\/]|\A)(gl|glu|glfw3|glext)\.h$') 10 | define_re = re.compile(r"^#define\s+([^\s]+)\s+([^\s]+)$") 11 | 12 | number_re = re.compile(r"^-?[0-9]+$") 13 | hex_re = re.compile(r"0x[0-9a-fA-F]+$") 14 | 15 | # Set of known #defines that we don't need to include 16 | INVALID_DEFINES = set(["GLAPI", "APIENTRY", "GLU_TESS_MAX_COORD", "gluErrorStringWIN", "WINGDIAPI", "CALLBACK"]) 17 | 18 | if __name__ == "__main__": 19 | in_gl = False 20 | for line in sys.stdin: 21 | # Ignore blank lines 22 | line = line.strip() 23 | if not line: 24 | continue 25 | 26 | # Is this a preprocessor statement? 27 | if line.startswith("#"): 28 | 29 | # Is this a location pragma? 30 | location_match = location_re.match(line) 31 | if location_match: 32 | # If we are transitioning to a header we need to parse, set the flag 33 | glpath_match = glpath_re.match(location_match.group(1)) 34 | in_gl = bool(glpath_match) 35 | continue 36 | 37 | if in_gl: 38 | # Is it a define? 39 | define_match = define_re.match(line) 40 | if define_match: 41 | name, val = define_match.groups() 42 | 43 | if val in defines: 44 | # Is this an alias of something we have already defined? 45 | val = defines[val] 46 | elif number_re.match(val) or hex_re.match(val): 47 | # Is this a number? 48 | # Store the define 49 | defines[name] = val 50 | elif val == "0xFFFFFFFFFFFFFFFFull": 51 | # Fix for GL_TIMEOUT_IGNORED 52 | defines[name] = val 53 | elif val == "0xFFFFFFFFu": 54 | # Fix for GL_INVALID_INDEX 55 | defines[name] = "0xFFFFFFFF" 56 | elif name not in INVALID_DEFINES: 57 | # Incompatible define 58 | print("Invalid define:", name, file=sys.stderr) 59 | 60 | continue 61 | 62 | # Otherwise just include it in the cdef 63 | elif in_gl: 64 | # Windows likes to add __stdcall__ to everything, but it isn't needed and is actually harmful when using under linux. 65 | # However, it is needed for callbacks in windows. 66 | if line.find("typedef") >= 0 and line.find(" PFNGL") < 0: 67 | line = line.replace('GL_APIENTRY ', 'WINDOWS_STDCALL ') 68 | else: 69 | line = line.replace('GL_APIENTRY ', '') 70 | # While linux likes to add __attribute__((visibility("default"))) 71 | line = line.replace('__attribute__((visibility("default"))) ', '') 72 | cdefs.append(line) 73 | 74 | # Output the file 75 | print("--[[ BEGIN AUTOGENERATED SEGMENT ]]") 76 | print("local glc; do local cdecl = [[") 77 | for line in cdefs: 78 | print("\t", line, sep="") 79 | print("\t]]; if require('ffi').os == 'Windows' then cdecl = cdecl:gsub('WINDOWS_STDCALL', '__attribute__((__stdcall__))') else cdecl = cdecl:gsub('WINDOWS_STDCALL', '') end; require('ffi').cdef(cdecl); glc = {") 80 | 81 | for k in sorted(defines.keys()): 82 | print("\t%s = %s," % ("['"+k+"']", defines[k])) 83 | 84 | print("} end") 85 | print("--[[ END AUTOGENERATED SEGMENT ]]") 86 | -------------------------------------------------------------------------------- /gen/headers.h: -------------------------------------------------------------------------------- 1 | 2 | // APIENTRY is defined as __stdcall in windows, and empty on others. 3 | // Callback function pointer typedefs need to be marked as stdcall under Windows, 4 | // even if glfw.lua was built on another system, so override this macro 5 | // and scan for it in cdef.py 6 | #define APIENTRY GL_APIENTRY 7 | 8 | #include 9 | #include 10 | #include 11 | #include "glext.h" 12 | -------------------------------------------------------------------------------- /glfw_base.lua: -------------------------------------------------------------------------------- 1 | 2 | assert(glc, "luajit-glfw was not built properly.") 3 | local ffi = require "ffi" 4 | local jit = require "jit" 5 | 6 | local Lib = {} 7 | 8 | setmetatable(glc, {__index=function(self, k) error("Unknown GL constant: "..k) end}) 9 | 10 | -- Load and export libraries 11 | local gl, glu, glfw 12 | if ffi.os == "Windows" then 13 | gl = ffi.load("opengl32") 14 | glu = ffi.load("glu32") 15 | glfw = ffi.load("glfw3") 16 | else 17 | gl = ffi.load("GL") 18 | glu = ffi.load("GLU") 19 | glfw = ffi.load("glfw.so.3") 20 | end 21 | 22 | Lib.gl = gl 23 | Lib.glc = glc 24 | Lib.glu = glu 25 | Lib.glfw = glfw 26 | 27 | -- Export a metatable for automatically loading extension functions 28 | Lib.glext = setmetatable({}, { 29 | __index = function(self, k) 30 | local ok, typ = pcall(ffi.typeof, string.format("PFN%sPROC", string.upper(k))) 31 | if not ok then error("Couldn't find pointer type for "..k.." (are you accessing the right function?)",2) end 32 | 33 | local ptr = ffi.cast(typ, glfw.glfwGetProcAddress(k)) 34 | if ptr == nil then error("Unable to load function: "..k, 2) end 35 | 36 | rawset(Lib.glext, k, ptr) 37 | 38 | return ptr 39 | end, 40 | }) 41 | 42 | -- TODO: The docs say that `lib.foo()` is faster than `local foo = lib.foo; foo()`, but is the overhead of a closure 43 | -- worth it? 44 | local function wrap(lib, fname) 45 | return function(...) 46 | return lib[fname](...) 47 | end 48 | end 49 | 50 | -- Similar to wrap, but errors if the returned object is null 51 | local function wrapErrorOnNull(lib, fname) 52 | return function(...) 53 | local obj = lib[fname](...) 54 | if obj == nil then 55 | error(fname.." failed",2) 56 | end 57 | return obj 58 | end 59 | end 60 | 61 | -- Some buffers for out parameters 62 | local int_buffer = ffi.new("int[2]") 63 | local double_buffer = ffi.new("double[2]") 64 | 65 | --------------------------------------------------------------------------------------------------------------------- 66 | -- GLFW Global Functions 67 | 68 | -- C functions that don't need special handling 69 | Lib.defaultWindowHints = wrap(glfw, "glfwDefaultWindowHints") 70 | Lib.getCurrentContext = wrap(glfw, "glfwGetCurrentContext") 71 | Lib.getPrimaryMonitor = wrapErrorOnNull(glfw, "glfwGetPrimaryMonitor") 72 | Lib.getProcAddress = wrap(glfw, "glfwGetProcAddress") 73 | Lib.getTime = wrap(glfw, "glfwGetTime") 74 | Lib.hint = wrap(glfw, "glfwWindowHint") 75 | Lib.joystickPresent = wrap(glfw, "glfwJoystickPresent") 76 | Lib.setTime = wrap(glfw, "glfwSetTime") 77 | Lib.swapInterval = wrap(glfw, "glfwSwapInterval") 78 | Lib.terminate = wrap(glfw, "glfwTerminate") 79 | 80 | -- Functions with special Lua code 81 | 82 | -- Shortcut for localizing libraries 83 | function Lib.libraries() 84 | return Lib.gl, Lib.glc, Lib.glu, Lib.glfw, Lib.glext 85 | end 86 | 87 | -- Throws an error on failure 88 | function Lib.init() 89 | if glfw.glfwInit() == 0 then 90 | error("glfwInit failed",0) 91 | end 92 | end 93 | 94 | -- Returns true/false 95 | function Lib.extensionSupported(name) return glfw.glfwExtensionSupported(name) ~= 0 end 96 | 97 | -- Returns three integers instead of out parameters 98 | function Lib.glfwVersion() 99 | local buffer = ffi.new("int[3]") 100 | glfw.glfwGetVersion(buffer, buffer+1, buffer+2) 101 | return buffer[0], buffer[1], buffer[2] 102 | end 103 | 104 | -- Converts the returned strung to a Lua string 105 | function Lib.glfwVersionString() 106 | return ffi.string(glfw.glfwGetVersionString()) 107 | end 108 | 109 | -- Allocates or fills a table and fills it with the results. 110 | function Lib.getJoystickAxes(joy, arr) 111 | arr = arr or {} 112 | local values = glfw.glfwGetJoystickAxes(joy, int_buffer) 113 | if values == nil then error("Invalid joystick: "..joy, 2) end 114 | for i=0,int_buffer[0]-1 do 115 | arr[i+1] = values[i] 116 | end 117 | return arr 118 | end 119 | 120 | -- Allocates or fills a table and fills it with the results. 121 | function Lib.getJoystickButtons(joy, arr) 122 | arr = arr or {} 123 | local values = glfw.glfwGetJoystickButtons(joy, int_buffer) 124 | if values == nil then error("Invalid joystick: "..joy, 2) end 125 | for i=0,int_buffer[0]-1 do 126 | arr[i+1] = values[i] 127 | end 128 | return arr 129 | end 130 | 131 | -- Returns Lua string 132 | function Lib.getJoystickName(joy) 133 | local name = glfw.glfwGetJoystickName(joy) 134 | if name == nil then error("Invalid joystick: "..joy, 2) end 135 | return ffi.string(name) 136 | end 137 | 138 | -- Returns Lua table 139 | function Lib.getMonitors() 140 | local cmonitors = glfw.glfwGetMonitors(int_buffer) 141 | if cmonitors == nil then error("glfwGetMonitors failed",2) end 142 | 143 | local monitors = {} 144 | for i=0,int_buffer[0]-1 do 145 | monitors[i+1] = cmonitors[i] 146 | end 147 | return monitors 148 | end 149 | 150 | -- These functions can't be jit compiled, because they may call callbacks. 151 | -- Don't use wrap because jit.off affects the prototype, which will affect other functions too. 152 | Lib.pollEvents = function() return glfw.glfwPollEvents() end 153 | jit.off(Lib.pollEvents) 154 | 155 | Lib.waitEvents = function() return glfw.glfwWaitEvents() end 156 | jit.off(Lib.waitEvents) 157 | 158 | --------------------------------------------------------------------------------------------------------------------- 159 | -- Window 160 | 161 | local Window = {} 162 | Window.__index = Window 163 | local Window_t = ffi.typeof("GLFWwindow") 164 | Lib.Window = Window_t 165 | 166 | function Window:__new(w, h, title, monitor, share) 167 | local window = glfw.glfwCreateWindow(w,h,title,monitor,share) 168 | if window == nil then error("glfwCreateWindow failed", 2) end 169 | return window 170 | end 171 | 172 | -- C functions that don't need special handling 173 | Window.__gc = wrap(glfw, "glfwDestroyWindow") 174 | Window.getInputMode = wrap(glfw, "glfwGetInputMode") 175 | Window.getKey = wrap(glfw, "glfwGetKey") 176 | Window.getMonitor = wrap(glfw, "glfwGetWindowMonitor") 177 | Window.getMouseButton = wrap(glfw, "glfwGetMouseButton") 178 | Window.hide = wrap(glfw, "glfwHideWindow") 179 | Window.iconify = wrap(glfw, "glfwIconifyWindow") 180 | Window.makeContextCurrent = wrap(glfw, "glfwMakeContextCurrent") 181 | Window.restore = wrap(glfw, "glfwRestoreWindow") 182 | Window.setCharCallback = wrap(glfw, "glfwSetCharCallback") 183 | Window.setClipboardString = wrap(glfw, "glfwSetClipboardString") 184 | Window.setCloseCallback = wrap(glfw, "glfwSetWindowCloseCallback") 185 | Window.setCursorEnterCallback = wrap(glfw, "glfwSetCursorEnterCallback") 186 | Window.setCursorPos = wrap(glfw, "glfwSetCursorPos") 187 | Window.setCursorPosCallback = wrap(glfw, "glfwSetCursorPosCallback") 188 | Window.setFocusCallback = wrap(glfw, "glfwSetWindowFocusCallback") 189 | Window.setFramebufferSizeCallback = wrap(glfw, "glfwSetFramebufferSizeCallback") 190 | Window.setIconifyCallback = wrap(glfw, "glfwSetWindowIconifyCallback") 191 | Window.setInputMode = wrap(glfw, "glfwSetInputMode") 192 | Window.setKeyCallback = wrap(glfw, "glfwSetKeyCallback") 193 | Window.setMouseButtonCallback = wrap(glfw, "glfwSetMouseButtonCallback") 194 | Window.setPos = wrap(glfw, "glfwSetWindowPos") 195 | Window.setPosCallback = wrap(glfw, "glfwSetWindowPosCallback") 196 | Window.setRefreshCallback = wrap(glfw, "glfwSetWindowRefreshCallback") 197 | Window.setScrollCallback = wrap(glfw, "glfwSetScrollCallback") 198 | Window.setShouldClose = wrap(glfw, "glfwSetWindowShouldClose") 199 | Window.setSize = wrap(glfw, "glfwSetWindowSize") 200 | Window.setSizeCallback = wrap(glfw, "glfwSetWindowSizeCallback") 201 | Window.setTitle = wrap(glfw, "glfwSetWindowTitle") 202 | Window.show = wrap(glfw, "glfwShowWindow") 203 | Window.swapBuffers = wrap(glfw, "glfwSwapBuffers") 204 | 205 | -- Functions with special Lua code 206 | 207 | -- Errors on zero 208 | function Window:getAttrib(attrib) 209 | local v = glfw.glfwGetWindowAttrib(self, attrib) 210 | if v == 0 then error("glfwGetWindowAttrib failed", 2) end 211 | return v 212 | end 213 | 214 | -- Returns width, height instead of needing out parameters 215 | function Window:getFramebufferSize() 216 | glfw.glfwGetFramebufferSize(self, int_buffer, int_buffer+1) 217 | return int_buffer[0], int_buffer[1] 218 | end 219 | 220 | -- Returns width, height instead of needing out parameters 221 | function Window:getSize() 222 | glfw.glfwGetWindowSize(self, int_buffer, int_buffer+1) 223 | return int_buffer[0], int_buffer[1] 224 | end 225 | 226 | -- Returns x, y instead of needing out parameters 227 | function Window:getPos() 228 | glfw.glfwGetWindowPos(self, int_buffer, int_buffer+1) 229 | return int_buffer[0], int_buffer[1] 230 | end 231 | 232 | -- Converts the returned strung to a Lua string 233 | function Window:getClipboardString() 234 | return ffi.string(glfw.glfwGetClipboardString(self)) 235 | end 236 | 237 | -- Returns x, y instead of needing out parameters 238 | function Window:getCursorPos() 239 | glfw.glfwGetCursorPos(self, double_buffer, double_buffer+1) 240 | return double_buffer[0], double_buffer[1] 241 | end 242 | 243 | -- Returns a boolean 244 | function Window:shouldClose() 245 | return glfw.glfwWindowShouldClose(self) ~= 0 246 | end 247 | 248 | -- Prevents the GC from double free'ing 249 | function Window:destroy() 250 | glfw.glfwDestroyWindow(ffi.gc(self, nil)) 251 | end 252 | 253 | ffi.metatype(Window_t, Window) 254 | 255 | --------------------------------------------------------------------------------------------------------------------- 256 | -- Monitor 257 | 258 | local Monitor = {} 259 | Monitor.__index = Monitor 260 | local Monitor_t = ffi.typeof("GLFWmonitor") 261 | Lib.Monitor = Monitor_t 262 | 263 | Monitor.setCallback = wrap(glfw, "glfwSetMonitorCallback") 264 | Monitor.getVideoMode = wrapErrorOnNull(glfw, "glfwGetVideoMode") 265 | 266 | -- Returns x,y instead of needing out params 267 | function Monitor:getPos() 268 | glfw.glfwGetMonitorPos(self, int_buffer, int_buffer+1) 269 | return int_buffer[0], int_buffer[1] 270 | end 271 | 272 | -- Retruns w,h instead of needing out params 273 | function Monitor:getPhysicalSize() 274 | glfw.glfwGetMonitorPhysicalSize(self, int_buffer, int_buffer+1) 275 | return int_buffer[0], int_buffer[1] 276 | end 277 | 278 | -- Returns Lua table 279 | function Monitor:getVideoModes() 280 | local carr = glfw.glfwGetVideoModes(self, int_buffer) 281 | local arr = {} 282 | for i=0,int_buffer[0]-1 do 283 | arr[i+1] = carr[i] 284 | end 285 | return arr 286 | end 287 | 288 | ffi.metatype(Monitor_t, Monitor) 289 | 290 | --------------------------------------------------------------------------------------------------------------------- 291 | return Lib 292 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | 2 | LuaJIT-GLFW 3 | =========== 4 | 5 | LuaJIT FFI bindings for GLFW 3 and OpenGL. 6 | 7 | This library contains everything needed to start a basic OpenGL app in Lua. 8 | 9 | Building 10 | -------- 11 | 12 | LuaJIT-GLFW builds bindings from the systems OpenGL and GLFW headers, as well as an included `glext.h` file. 13 | To build the bindings, you need to have a C preprocessor (only GCC is supported at the moment), headers for OpenGL and GLFW 3, and Python 3 installed, though the resulting 14 | file should be cross-platform compatible. 15 | 16 | To build with GCC, just run `build.sh` in the repository directory. This will create a `glfw.lua` file, which is the only file 17 | you need to install. 18 | 19 | Usage 20 | ----- 21 | 22 | To load the library, use the `require` function: 23 | 24 | ```lua 25 | local luajit_glfw = require "glfw" 26 | ``` 27 | 28 | LuaJIT-GLFW loads the following libraries: 29 | 30 | * `luajit_glfw.gl`: OpenGL 31 | * `luajit_glfw.glc`: `#define`d values for OpenGL and GLFW (this must be a Lua table instead of `static const` values, because OpenGL uses `longs` in a couple of places) 32 | * `luajit_glfw.glu`: GLU 33 | * `luajit_glfw.glfw`: GLFW 34 | * `luajit_glfw.glext`: A table that, when indexed, loads and returns the specified extension function. Ex. `glext.glMyExtFuncARB(p1, p2)` 35 | 36 | You can also use the following snippet to concisely localize the libraries. 37 | 38 | ```lua 39 | local gl, glc, glu, glfw, glext = luajit_glfw.libraries() 40 | -- Or if you just need the libraries: 41 | local gl, glc, glu, glfw, glext = require('glfw').libraries() 42 | ``` 43 | 44 | Additionally, LuaJIT-GLFW wraps GLFW functions and sets metatypes for GLFW structs for convenience. See `glfw_base.lua` 45 | -------------------------------------------------------------------------------- /test.lua: -------------------------------------------------------------------------------- 1 | 2 | -- Load libraries 3 | local lj_glfw = require "glfw" 4 | local ffi = require "ffi" 5 | local bit = require "bit" 6 | -- Localize the FFI libraries 7 | local gl, glc, glu, glfw, glext = lj_glfw.libraries() 8 | 9 | assert(pcall(function() local _ = glc.this_doesnt_exist end) == false) 10 | 11 | -- Define vertex arrays for the model we will draw 12 | local CubeVerticies = {} 13 | CubeVerticies.v = ffi.new("const float[8][3]", { 14 | {0,0,1}, {0,0,0}, {0,1,0}, {0,1,1}, 15 | {1,0,1}, {1,0,0}, {1,1,0}, {1,1,1} 16 | }) 17 | 18 | CubeVerticies.n = ffi.new("const float[6][3]", { 19 | {-1.0, 0.0, 0.0}, {0.0, 1.0, 0.0}, {1.0, 0.0, 0.0}, 20 | {0.0, -1.0, 0.0}, {0.0, 0.0, -1.0}, {0.0, 0.0, 1.0} 21 | }) 22 | 23 | CubeVerticies.f = ffi.new("const float[6][4]", { 24 | {0, 1, 2, 3}, {3, 2, 6, 7}, {7, 6, 5, 4}, 25 | {4, 5, 1, 0}, {5, 6, 2, 1}, {7, 4, 0, 3} 26 | }) 27 | 28 | -- Initialize GLFW. Unline glfwInit, this throws an error on failure 29 | lj_glfw.init() 30 | 31 | -- Creates a new window. lj_glfw.Window is a ctype object with a __new metamethod that 32 | -- runs glfwCreateWindow. 33 | -- The window ctype has most of the windows functions as methods 34 | local window = lj_glfw.Window(1024, 768, "LuaJIT-GLFW Test") 35 | 36 | -- Initialize the context. This needs to be called before any OpenGL calls. 37 | window:makeContextCurrent() 38 | 39 | local w, h = window:getFramebufferSize() 40 | 41 | -- Set up OpenGL 42 | gl.glEnable(glc.GL_DEPTH_TEST); 43 | 44 | gl.glMatrixMode(glc.GL_PROJECTION) 45 | glu.gluPerspective(60, w/h, 0.01, 1000) 46 | gl.glMatrixMode(glc.GL_MODELVIEW) 47 | glu.gluLookAt(0,0,5, 48 | 0,0,0, 49 | 0,1,0) 50 | 51 | -- Set up some values 52 | local rotx, roty, rotz = 1/math.sqrt(2), 1/math.sqrt(2), 0 53 | local boxx, boxy, boxz = -0.5,-0.5,2 54 | 55 | -- Main loop 56 | while not window:shouldClose() do 57 | gl.glClear(bit.bor(glc.GL_COLOR_BUFFER_BIT, glc.GL_DEPTH_BUFFER_BIT)) 58 | 59 | gl.glPushMatrix() 60 | gl.glColor3d(1,1,1) 61 | gl.glTranslated(boxx, boxy, boxz) 62 | gl.glRotated(lj_glfw.getTime()*10, rotx, roty, rotz) 63 | for i=0,5 do 64 | gl.glBegin(glc.GL_QUADS) 65 | gl.glNormal3fv(CubeVerticies.n[i]) 66 | for j=0,3 do 67 | gl.glVertex3fv(CubeVerticies.v[CubeVerticies.f[i][j]]) 68 | end 69 | gl.glEnd() 70 | end 71 | gl.glPopMatrix() 72 | 73 | window:swapBuffers() 74 | lj_glfw.pollEvents() 75 | end 76 | 77 | -- Destroy the window and deinitialize GLFW. 78 | window:destroy() 79 | lj_glfw.terminate() 80 | --------------------------------------------------------------------------------