PudimBasicsGl - Pudim Basics OpenGL
A minimal 2D graphics library for Lua using OpenGL. PudimBasicsGl focuses on the essentials: window management, 2D rendering, textures, input, audio, text, time, camera, shaders, math, file utilities, and immediate-mode UI.
Warning
This project is experimental. APIs and features may change without notice — use with caution.
Features
- Window: Create and manage OpenGL windows with VSync, fullscreen, and resize support
- Renderer: Draw 2D primitives (pixels, lines, rectangles, circles, triangles, gradients) with automatic batch management
- Textures: Load images (PNG, JPG, BMP, etc.) and draw them with rotation, tinting, chroma key, and sprite sheet support
- Input: Keyboard and mouse input (key state, mouse position, cursor control)
- Audio: Load and play audio files (WAV, MP3, FLAC) with volume, pitch, and looping via miniaudio
- Text: Load TrueType fonts (.ttf) and render text with customizable size, color, and measurement via stb_truetype
- Time: Delta time, FPS, and timing utilities
- Camera: 2D camera controls (position, zoom, rotation, look_at, screen/world conversion)
- Shader: Custom GLSL shaders — compile from strings or load from files, set uniforms (int, float, vec2-4, mat4)
- Math: Vector math (vec2/vec3/vec4), arithmetic, lerp, clamp, angle conversion, and constants (PI, TAU)
- Studio: File system utilities (list directory, file timestamps, file copy)
- UI: Immediate-mode GUI widgets (panels, buttons, sliders, labels) for tools and debug overlays
Building
The recommended way to install is via LuaRocks (see below). For local development:
makeThis creates PudimBasicsGl.so, a Lua module that you can load with require("PudimBasicsGl").
LSP Support
Add library/ to your lua-language-server library path for full autocomplete and type checking:
// .vscode/settings.json
{
"Lua.workspace.library": ["./library"]
}Or copy library/PudimBasicsGl.lua to your global lua-language-server addons folder.
Editor tip: Annotate variables with the qualified types to get method suggestions for instances. For example:
---@type PudimBasicsGl
local pb = require("PudimBasicsGl")
---@type PudimBasicsGl.Window
local win = pb.window.create(800, 600, "Title")
-- Now `win:` will show methods in VSCode's completion list
win:swap_buffers()
---@type PudimBasicsGl.Texture
local tex = pb.texture.load("sprite.png")
-- `tex:` will show texture methods such as `draw`, `get_size`, etc.
---@type Sound
local snd = pb.audio.load("music.mp3")
-- `snd:` will show audio methods such as `play`, `stop`, `set_volume`, etc.
---@type Font
local font = pb.text.load("my_font.ttf", 32)
-- `font:` will show text methods such as `draw`, `measure`, `set_size`, etc.
---@type Shader
local shd = pb.shader.create(vertex_src, fragment_src)
-- `shd:` will show shader methods such as `use`, `set_float`, `set_vec3`, etc.Installation via LuaRocks
luarocks install pudimbasicsglOr build from the local rockspec:
luarocks make pudimbasicsgl-1.0.0-19.rockspecThe rockspec uses the builtin build type — LuaRocks compiles all .c sources directly into .so (Linux) or .dll (Windows), no makefile needed. The make command is only required for local development.
Usage
local pb = require("PudimBasicsGl")
-- Create a window
local window = pb.window.create(800, 600, "My App")
pb.renderer.init()
-- Load a texture (optional)
local texture = pb.texture.load("sprite.png")
-- Main loop (you control it!)
while not pb.window.should_close(window) do
pb.time.update()
local dt = pb.time.delta()
-- Your game logic here
-- Get current window size (handles resize)
local w, h = pb.window.get_size(window)
-- Render
pb.renderer.clear(0.1, 0.1, 0.15, 1.0)
pb.renderer.begin(w, h)
-- Draw primitives
pb.renderer.rect_filled(100, 100, 50, 50, pb.renderer.colors.RED)
pb.renderer.circle_filled(400, 300, 30, {r=0, g=1, b=0.5, a=1})
-- Draw textures (auto-flushes primitives!)
if texture then
texture:draw(200, 200) -- Simple draw
texture:draw_rotated(400, 400, 64, 64, 45) -- Rotated 45 degrees
end
pb.renderer.finish()
pb.window.swap_buffers(window)
pb.window.poll_events()
end
if texture then texture:destroy() end
pb.window.destroy(window)Input Example
-- Keyboard
if pb.input.is_key_pressed(pb.input.KEY_W) then
player.y = player.y - speed * dt
end
if pb.input.is_key_pressed(pb.input.KEY_ESCAPE) then
break
end
-- Mouse
local mx, my = pb.input.get_mouse_position()
if pb.input.is_mouse_button_pressed(pb.input.MOUSE_LEFT) then
-- handle click
endAudio Example
local music = pb.audio.load("music.mp3")
music:set_looping(true)
music:set_volume(0.8)
music:play()
-- Later...
music:pause()
music:resume()
music:stop()
music:destroy()
pb.audio.set_master_volume(0.5)
pb.audio.shutdown()Text Example
-- Load a TrueType font at 32px
local font = pb.text.load("my_font.ttf", 32)
-- Draw text (auto-flushing handles switching from primitives)
font:draw("Hello, World!", 100, 100, 1, 1, 1) -- white text
font:draw("Colored!", 100, 150, {r=1, g=0.5, b=0, a=1}) -- orange text
-- Measure text dimensions
local w, h = font:measure("Hello, World!")
-- Change font size dynamically
font:set_size(48)
-- Cleanup
font:destroy()API Reference
pb.window
| Function | Description |
|---|---|
create(width, height, title) |
Create a window, returns window handle |
destroy(window) |
Destroy window and free resources |
should_close(window) |
Check if window should close |
close(window) |
Signal window to close |
swap_buffers(window) |
Present the rendered frame |
poll_events() |
Process window events |
get_size(window) |
Get window dimensions |
set_size(window, w, h) |
Set window dimensions |
set_title(window, title) |
Change window title |
get_handle(window) |
Get native GLFW handle |
set_vsync(window, enabled) |
Enable/disable VSync |
get_vsync(window) |
Check if VSync is enabled |
set_fullscreen(window, bool) |
Set fullscreen mode |
is_fullscreen(window) |
Check if fullscreen |
toggle_fullscreen(window) |
Toggle fullscreen mode |
get_position(window) |
Get window position |
set_position(window, x, y) |
Set window position |
focus(window) |
Focus the window |
is_focused(window) |
Check if window has focus |
set_resizable(window, bool) |
Enable/disable resize |
pb.renderer
| Function | Description |
|---|---|
init() |
Initialize the renderer |
clear(r, g, b, a) |
Clear screen with color |
begin(width, height) |
Begin 2D rendering batch |
finish() |
End rendering batch |
flush() |
Flush without ending batch |
pixel(x, y, color) |
Draw a point |
line(x1, y1, x2, y2, color) |
Draw a line |
rect(x, y, w, h, color) |
Draw rectangle outline |
rect_filled(x, y, w, h, color) |
Draw filled rectangle |
circle(x, y, radius, color) |
Draw circle outline |
circle_filled(x, y, radius, color) |
Draw filled circle |
triangle(...) |
Draw triangle outline |
triangle_filled(...) |
Draw filled triangle |
set_point_size(size) |
Set point rendering size |
set_line_width(width) |
Set line rendering width |
color(r, g, b, a) |
Create a color table (0.0-1.0 floats or hex) |
color255(r, g, b, a) |
Create a color table from 0-255 integers |
color_unpack(color) |
Unpack Color table → r, g, b, a (0.0-1.0) |
set_clear_color(r, g, b, a?) |
Set OpenGL clear color state |
enable_depth_test(enabled) |
Enable/disable OpenGL depth testing |
enable_blend(enabled) |
Enable/disable alpha blending |
set_viewport(x, y, width, height) |
Set the OpenGL viewport |
get_info() |
Get OpenGL info table (version, renderer, vendor, glsl_version) |
begin_ui(screen_width, screen_height) |
Begin screen-space rendering that ignores the camera |
end_ui() |
End UI mode rendering |
rect_gradient(x, y, w, h, top_color, bottom_color) |
Draw a vertical color gradient rectangle |
read_pixel(x, y, screen_height) |
Read a pixel from the framebuffer (returns r, g, b, a) |
colors.WHITE, colors.RED, etc. |
Predefined colors |
pb.texture
| Function | Description |
|---|---|
load(filepath) |
Load texture from file (PNG, JPG, BMP, TGA) |
load_with_colorkey(filepath, hex_color) |
Load texture and make a specific color transparent |
create(w, h, data?) |
Create texture with optional RGBA data |
flush() |
Flush pending texture draws |
Texture Methods
| Method | Description |
|---|---|
texture:draw(x, y, w?, h?) |
Draw at position |
texture:draw_tinted(x, y, w, h, r, g, b, a?) |
Draw with color tint |
texture:draw_rotated(x, y, w, h, angle) |
Draw rotated (degrees) |
texture:draw_ex(x, y, w, h, angle, ox, oy, r, g, b, a?) |
Full control draw |
texture:draw_region(x, y, w, h, sx, sy, sw, sh) |
Draw sprite sheet region |
texture:draw_region_ex(...) |
Region with rotation/tint |
texture:get_size() |
Get width, height |
texture:get_width() |
Get width |
texture:get_height() |
Get height |
texture:destroy() |
Free texture resources |
pb.input
| Function | Description |
|---|---|
is_key_pressed(key) |
Check if a key is currently held down |
is_key_released(key) |
Check if a key is not held down |
is_mouse_button_pressed(button) |
Check if a mouse button is held down |
get_mouse_position() |
Get cursor position (x, y) |
set_mouse_position(x, y) |
Set cursor position |
set_cursor_visible(visible) |
Show/hide the cursor |
set_cursor_locked(locked) |
Lock/unlock cursor (FPS-style) |
Key Constants
KEY_A to KEY_Z, KEY_0 to KEY_9, KEY_SPACE, KEY_ESCAPE, KEY_ENTER, KEY_TAB, KEY_BACKSPACE, KEY_UP, KEY_DOWN, KEY_LEFT, KEY_RIGHT, KEY_F1 to KEY_F3, KEY_F11, KEY_F12, KEY_LEFT_SHIFT, KEY_RIGHT_SHIFT, KEY_LEFT_CTRL, KEY_RIGHT_CTRL, KEY_LEFT_ALT, KEY_RIGHT_ALT
Mouse Constants
MOUSE_LEFT, MOUSE_RIGHT, MOUSE_MIDDLE
pb.audio
| Function | Description |
|---|---|
load(filepath) |
Load audio file (WAV, MP3, FLAC), returns Sound |
set_master_volume(volume) |
Set master volume (0.0–2.0) |
get_master_volume() |
Get current master volume |
shutdown() |
Shutdown audio engine |
Sound Methods
| Method | Description |
|---|---|
sound:play() |
Play from the beginning |
sound:stop() |
Stop and rewind |
sound:pause() |
Pause at current position |
sound:resume() |
Resume from paused position |
sound:is_playing() |
Check if playing |
sound:set_looping(bool) |
Enable/disable looping |
sound:is_looping() |
Check if looping |
sound:set_volume(vol) |
Set volume (0.0 = silent, 1.0 = normal) |
sound:get_volume() |
Get current volume |
sound:set_pitch(pitch) |
Set pitch (1.0 = normal, 0.5 = slow, 2.0 = fast) |
sound:get_pitch() |
Get current pitch |
sound:destroy() |
Free sound resources |
pb.text
| Function | Description |
|---|---|
load(filepath, size?) |
Load a TrueType font (.ttf) at pixel size (default 24), returns Font |
flush() |
Flush pending text draws |
Font Methods
| Method | Description |
|---|---|
font:draw(text, x, y, color) |
Draw text at position with color |
font:measure(text) |
Get text width and height without drawing |
font:set_size(size) |
Change font size (re-rasterizes atlas) |
font:get_size() |
Get current font size in pixels |
font:get_line_height() |
Get line height in pixels |
font:destroy() |
Free font resources |
pb.math
| Function | Description |
|---|---|
lerp(a, b, t) |
Linear interpolation |
clamp(val, min, max) |
Clamp value between min and max |
radians(deg) |
Convert degrees to radians |
degrees(rad) |
Convert radians to degrees |
vec2(x, y) |
Create a 2D vector {x, y} |
vec3(x, y, z) |
Create a 3D vector {x, y, z} |
vec4(x, y, z, w) |
Create a 4D vector {x, y, z, w} |
vec_add(a, b) |
Add vectors |
vec_sub(a, b) |
Subtract vectors |
vec_scale(v, s) |
Scale vector by scalar |
vec_length(v) |
Calculate vector length |
vec_normalize(v) |
Normalize vector |
vec_dot(a, b) |
Dot product of vectors |
Constants: pb.math.PI, pb.math.TAU, pb.math.HALF_PI
pb.studio
| Function | Description |
|---|---|
list_dir(path) |
Returns a table of file names in the directory |
get_file_modified_time(path) |
Get file modification timestamp (useful for hot-reload) |
copy_file(src, dst) |
Copy a file from src to dst |
pb.ui
Immediate-mode GUI widgets. First call set_font before the first frame.
| Function | Description |
|---|---|
set_font(font) |
Set the font used by UI widgets |
begin_frame() |
Begin a new UI frame (reads mouse state automatically) |
end_frame() |
End current UI frame and draw everything |
label(text, x, y, color) |
Draw a text label |
panel(title, x, y, w, h) |
Draw a background panel with title |
button(id, label, x, y, w, h, color) |
Draw an interactive button (returns true if clicked) |
slider(id, label, x, y, w, h, val, min, max) |
Draw an interactive slider (returns updated value) |
pb.shader
| Function | Description |
|---|---|
create(vertex_src, fragment_src) |
Compile a shader from GLSL source strings, returns Shader |
load(vertex_path, fragment_path) |
Load and compile shader from files, returns Shader |
unuse() |
Unbind the current shader (restore default) |
Shader Methods
| Method | Description |
|---|---|
shader:use() |
Bind this shader for rendering |
shader:unuse() |
Unbind this shader |
shader:set_int(name, value) |
Set an integer uniform |
shader:set_float(name, value) |
Set a float uniform |
shader:set_vec2(name, x, y) |
Set a vec2 uniform |
shader:set_vec3(name, x, y, z) |
Set a vec3 uniform |
shader:set_vec4(name, x, y, z, w) |
Set a vec4 uniform |
shader:set_mat4(name, {m1..m16}) |
Set a mat4 uniform (16 floats, column-major) |
shader:get_id() |
Get the OpenGL program ID |
shader:is_valid() |
Check if the shader compiled successfully |
shader:destroy() |
Free shader GPU resources |
pb.time
| Function | Description |
|---|---|
update() |
Update time system (call once per frame) |
delta() |
Get time since last frame |
get() |
Get total time since init |
fps() |
Get current FPS |
sleep(seconds) |
Busy-wait sleep |
pb.camera
| Function | Description |
|---|---|
set_position(x, y) |
Set camera world offset (0,0 = no offset) |
get_position() |
Get camera position → x, y |
move(dx, dy) |
Move camera by delta |
set_zoom(zoom) |
Set zoom level (1.0 = normal, >1 = zoom in) |
get_zoom() |
Get current zoom level |
set_rotation(angle) |
Set rotation in degrees |
get_rotation() |
Get rotation in degrees |
look_at(x, y, sw, sh) |
Center camera on world point |
reset() |
Reset to defaults (pos=0,0 zoom=1 rot=0) |
screen_to_world(sx, sy) |
Convert screen → world coordinates |
world_to_screen(wx, wy) |
Convert world → screen coordinates |
Color Format
Colors can be passed as:
- Table:
{r=1.0, g=0.5, b=0.0, a=1.0} - Individual values:
r, g, b, a(alpha optional, defaults to 1.0)
Shader Example
-- Create a custom fragment shader
local vs = [[
#version 330 core
layout(location = 0) in vec2 aPos;
void main() { gl_Position = vec4(aPos, 0.0, 1.0); }
]]
local fs = [[
#version 330 core
out vec4 FragColor;
uniform float uTime;
void main() {
FragColor = vec4(sin(uTime)*0.5+0.5, 0.3, 0.8, 1.0);
}
]]
local shader = pb.shader.create(vs, fs)
shader:use()
shader:set_float("uTime", pb.time.get())
-- draw geometry ...
shader:unuse()
shader:destroy()Examples
We provide several example scripts in the examples/ directory. A new OOP-style demo demonstrates the object-style API (method calls via :) — it shows window:should_close(), texture:draw(), pb.time:update() and a simple render loop.
Run the demo after building/installing the module locally:
# Build/install locally (optional)
luarocks make --local
# Run example (ensures local .so is in package.cpath)
lua -e "package.cpath='./?.so;'..package.cpath" examples/oop_demo.luaYou can also inspect scripts/test_oop.lua for a minimal non-visual smoke test of the object-style API.
Dependencies
- GLFW3
- OpenGL 3.3+
- Lua 5.4 or 5.5
Building
Linux
makeThe makefile auto-detects your Lua version. To build for a specific version:
make LUA_VERSION=5.4 # Force Lua 5.4
make LUA_VERSION=5.5 # Force Lua 5.5Windows (MSYS2/MinGW)
- Install MSYS2 and open the MinGW64 terminal
- Install dependencies:
pacman -S mingw-w64-x86_64-gcc mingw-w64-x86_64-glfw mingw-w64-x86_64-lua- Build:
makeThis creates PudimBasicsGl.dll. Use with:
-- Windows
package.cpath = ".\\?.dll;" .. package.cpath
local pb = require("PudimBasicsGl")Example Scripts
examples/main.lua- Basic rendering demoexamples/minimal.lua- Minimal exampleexamples/texture_demo.lua- Texture loading and drawingexamples/window_demo.lua- VSync and window featuresexamples/input_demo.lua- Keyboard and mouse input demoexamples/audio_demo.lua- Audio loading and playback demoexamples/oop_demo.lua- Object-oriented style API demoexamples/text_demo.lua- Text rendering and font loading demoexamples/camera_demo.lua- 2D camera pan, zoom, rotation and coordinate conversion demoexamples/shader_demo.lua- Custom GLSL shader with animated color wave effectexamples/api_reference.lua- Complete API reference example
Command-line tool (pbgl)
A small CLI named pbgl is installed with the rock. It provides two commands:
pbgl show-examples— Copy bundled example scripts to./demos/in the current directorypbgl help— Show usage information
After running pbgl show-examples you'll have a ./demos/ folder; run an example with:
lua demos/main.luaMake sure ~/.luarocks/bin is in your PATH so you can call pbgl directly:
export PATH="$HOME/.luarocks/bin:$PATH"Credits
- Sample audio file (
examples/example.mp3) from file-examples.com — used for testing/demo purposes only. - miniaudio — single-header audio library by David Reid (public domain / MIT-0).
- stb_image — single-header image loader by Sean Barrett (public domain / MIT).
- stb_truetype — single-header TrueType font rasterizer by Sean Barrett (public domain / MIT).
- GLAD — OpenGL loader generator.
License
MIT