vibbit! a fun 2d graphics and game framework!

that’s really cool! i love trying out new game framework, and you seem to have a very clear and sensible design for the scope of vibbit!

i’m not the biggest Rust, but i’ll gladly try to make something with it anyway when possible

best of luck :)

sidequestion: will this be open source?

thanks yeah - I’d be interested in letting folks from here work with it a bit earlier if they wanted to help with writing tiny examples / demos, and just generally giving feedback on the ease of use and such. I’d have to think about something i could do or help out with in return probably…

Yup that’s the plan, though I haven’t settled on which license yet. It really doesn’t make any sense for this kind of thing NOT to be FOSS - there are enough alternatives, love/monogame/raylib etc, that are already free why bother buying something proprietary? I really don’t have any model for monetization other than ‘maybe this seems cool and lands me a job down the road’

Screenshot from 2023-03-28 04-03-42Screenshot from 2023-03-28 07-24-13Screenshot from 2023-03-28 04-03-21

originally i wanted the icon/logo thing to be more of a face. having it be something you could draw with the tool itself was important, and the shakey lineart approach is nice because it gives off a vib ribbon look, which is where the name kind of came from. but simplifying it to a more flat lilypad kind of look ends up being a lot easier to draw in a vector programmatic way. and admittedly is a bit less creepy looking

Also, if Godot is any indicator, making your symbol a cartoony face tends to push people away

for me it was never having a face that bothered me, in fact i like mascots when they are kept pretty simple. moreso just that the robot head just screams clipart in my opinion


testing to see if webm uploads work ~~
I put together a little demo this morning that does a pseudo 3d effect. it is definitely legally distinct from any other 3d 2d hybrid games. ideally I’d like to have a lot of small one-screen little projects like this that demonstrate using the framework to do different common effects, and give an example of the breadth of what you can set up even with a simple/barebones toolkit like this


not entirely related, but i’ve been working out fully 3D rendering recently too, while studying slightly more modern opengl techniques. been doing the above all in c++ and i can say for certain i made the right choice writing vibbit in rust! so many fewer code tasks feel like ‘chores’. plus the build system makes it take way more setup to compile on anything other than the host OS.

i’ll probably go back and reimplement some of my data structures to use this part of the API later on. it’s an interesting challenge working on this deciding just how modern of opengl features to support - most resources out there don’t bother talking about anything after 3.3 - and its still a good choice if you wanna support macos - but the DSA api has a lot of cool stuff that makes it easier to deal with your GPU resources outside of the global state. at this point, what I’m considering is having a default mode, with a seperate “3.3-esque” fallback mode that is handled based on compilation platform - right now macos and webgl would share this mode. end users shouldn’t have to configure anything, and the outward API will remain the same. the trick is to do this in a way where i’m hopefully not writing two seperate rendering backends, just ‘diverting’ to fallbacks when it actually matters.

in actual Vibbit news - I’ve alluded to this briefly on twitter, but I’m setting a goal for the project to enter a private test starting sometime in January. The goal of this is to see how far people can get with the framework more-or-less on their own (of course i’ll be around, but there won’t be any docs, and very few examples). im not expecting any real games to necessarily come together, but maybe some more visual demos that could be recycled into example files (credit included of course). I really want to see how intuitive and ergonomic the library is to people going in mostly blind.

My next update is gonna go more in-depth on what the private test will look like, and what i need to get done leading up to that (spoiler: a LOT, there isn’t even an audio/sfx api). But if trying it out a little early sounds interesting, feel free to shoot me a message. this demo will require working in rust, but the plan is for the first official release to have at least 1 scripting language still.

Technical wizardry in this thread! :o I would love to try my hands at this when the time comes, though before I send the fateful message, I need to ask - in your personal estimate, how much of an uphill battle would it be to learn the coding language of this engine for somebody whose sole experience with written code is GML/Javascript?

I recall back in the late 2000s I attempted to learn Rust for work on an extremely ill-fated fangame in RMXP, and failed - naturally because I was 13, but even so.

thanks for reading! I have to be honest - it will be kinda rough… the symbolic hill is an acute angle, and you’re on the inside face…
on one angle, a big challenge coming from traditional engines is this is a ‘bring your own EVERYTHING’ approach. there are no maps, or tiles, or entities, or scenes. you’d have to do a lot of library hunting, or be comfortable trying to write all the small little game engine features you’ve taken for granted from scratch. i’d say the whole thing is mostly intended for tinkerer types who want to figure things out for themselves

for the programming, to put it short:
rust is known for introducing entire new categories of compiler errors that will have you scratching your head, and reading dozens of stackoverflows, for what looks like incredibly normal, reasonable code. and hours later you will find out that the solution to the problem, instead of fixing a silly typo or importing a useful function/data structure, is to completely scrap the idea for how you first planned your function to work, and rewrite it in an entirely different style you’ve never seen before. instead of just syntax errors, the compiler feels as if it has ‘Opinions’ on your code - it is the first language which will make you think it actively disagrees with the program you are making. that’s not to say it is all bad - learning it teaches you a lot about how programs actually work under hood, and has actually helped me a bit with being able to prematurely identify and work around potential bugs when working in other languages too. it Really retrains your brain on a ton of programming concepts
and in my opinion, rust is dozens of times easier to get setup with, and start messing around and trying out libraries compared to the other ‘hard’ languages like c++. the ecosystem of tools you use to work with rust are top-notch, and integrate perfectly with the language

to put it long: i’ve got an in-depth thread on my experience with Rustlang for gamedevs

as sadistic as i made it sound, im not using it to ‘challenge myself’, and i really believe its something that can push smalltime/indie game engines to solve traditional tech debt challenges (multithreading, efficient batching, data throughput, fast scripting languages) with a fraction of the effort of more traditional choices. and if rust isn’t your tea in the end, focusing on porting it directly to other languages like javascript is already in the air, where you can get the chance to learn things in a familiar environment, then swap back to rust if you feel the need to squeeze some more performance, while translating basically 100% of the skills learned from the framework between languages

ehehe - That’s Ruby actually! Of which i’ve worked with a lot in my earlier rpgmaker days, but probably would never try to build something like this with. In the grand scheme of things Rust is an incredibly new language (2015), which is largely why the game development ecosystem is in such flux. also because its a very low level technical language, mostly pure-engineers play with it, so the running joke is that there are literally more game engines in development than there are actual games.

How embarrassing ;__; Yeah you’re absolutely right, that’s what I was thinking of.

Yeaaahhhh… Got it.

I’ve sort of taken to pointedly calling myself a “scripter” rather than a “programmer”, and perhaps I should remember the distinction of the two (at least in my mind) better from henceforth.

Hope some cool people bite and try the engine out, though! >:)


Been a while but have been able to continue some work on vibbit recently! Probably worth taking another look at the todolist and sorting things into a workable order. This one is a pretty sizably useful feature though - user supplied shaders. This is a feature I already had going in my original single-threaded test version of the tool, but implementing it this time around widestepped a few bugs i didn’t know the original ever had, and it’s got a bit nicer functionality overall too.
Things are pretty barebones right now but definitely usable - you can load up a shader just by providing strings for vertex, fragment, and even a geometry shader if you optionally need one. then when you set the graphics to ‘use’ that shader, any type of draw call you make (lines, shapes, sprites) will try to use it. there’s also a convenient ‘reset’ function that puts everything back to its default shaders. Right now there’s also a utility function for working with sprites, where for a ‘sprite shader’ you only provide a fragment shader, and it will populate the other steps with the default code used by vibbit. should be useful for quickly experimenting with things.

here’s a stripped down code example for the above video:

code
fn main() {
	let mut vib = vibbit::init(640, 360, "custom shader");
	let tex = vib.load_texture("./assets/toskr.png");
	let mut bg = Color::new(255, 254, 254, 255);
	let white = Color::new(255, 255, 255, 255);
    let black = Color::new(0, 0, 0, 255);
	let font = vib.load_font("./assets/duster.otf", 13.0);
	// FRAG is just a const string with the shader code
    // load_sprite_shader provides the vertex shader for you
	let shader = vib.load_sprite_shader(FRAG);

	let mut time: f32 = 0.0;

	// main app loop
	while !vib.should_close() {
		time += 0.01;
		vib.clear_screen(bg);

		// [..] update sprite positions

		vib.gfx_set_shader(shader);
        // shader uniform setters are strongly typed
		vib.gfx_set_shader_uniform_float(shader, "time", time);
		vib.draw_texture_scale(tex, pos_a, white, Vec2::new(2.0, 2.0));
		vib.gfx_clear_shader();

		vib.draw_text(&font, -74.0, 0.0, black, "they are vibing", 2.0);

		// when loading shaders consecutively, uniforms are remembered from last use (even across frames)
		// so setting 'time' is not needed more than once per frame
		vib.gfx_set_shader(shader);
		vib.draw_texture_scale(tex, pos_b, white, Vec2::new(2.0, 2.0));
		vib.gfx_clear_shader();

		// end frame
		vib.end_frame();
	}
}

shader-related function names are a little longer than i’d like right now :blush: :grimacing:

When it comes to providing uniforms - the uniforms you provide to a shader are cached and linked to that specific shasder. So you can load in a shader, draw some things, switch to another, and when you come back it remembers every uniform. It caches them across frames as well. This should also be useful for cases where two different shaders have a uniform with the same name but you know the values shouldn’t be linked. This seems like the most ergonomic way to handle it to me (there’s no downside to manually specifying each uniform each frame if you really want to) - but if there are any holes in this idea I’m not seeing now i’d be interested to hear.

I’m also leaving compute shaders off the table for the time being, though I do want to include them eventually. The issue with them is that vibbit defers any GPU work to the very end of a frame, and often times with compute shaders you want to grab the data off the GPU and do something with it after, or use it as a buffer for rendering. Right now vibbit is largely ‘one-way’ in that your main code will send instructions to the render thread, but if that thread needs to send something back its not clear when that will be done by. leaves some questions on whether there should be special cases to have blocking, non-async rendering tasks and how to handle that.

Interested in hearing how easy shaders seems to work with, from the code example and description of how vibbit handles uniforms. Everything mostly make sense from first impressions? I wanna hear how you usually work with shaders (esp. outside of a larger engine like unity). The next major systems to really fold in are audio/music, then probably gamepads. At that point it’d be pretty acheivable to get a functional game out vibbit (if you think small, of course :gms_explosion:)

Nice work! If/when you make the source available I’d definitely be interested to try this out. One application that jumps out at me especially with shader support is 2D video art, which often has similar technical requirements to game frameworks.

I’m curious, what’s the error handling situation like here, e.g. is there a way to catch and detect a mistake in the GLSL? I would normally expect a shader loading method to return a Result since so many things can go wrong.

very cool! glad to see vibbit is going strong. to share how i tend to use shaders, for 2d pixel art games i mostly use vertex shaders for distortion/camera effects and fragment shaders for cool shit such as palette swapping and background effects. having a layer system to apply a fragment shader to an entire layer is a super cool feature (that could be achieved if we can render to textures in vibbit)

and building up on jamie’s reply, how does vibbit handle errors?

I think there could be a good match there - this is definitely very ergonomic for ‘demoscene’ style visual work. For becoming a more solid multimedia framework it’d probably need to open up support for way more filetypes (sound, image formats), or for example I don’t really have plans to integrate video playback. Being able to feed video signal into a shader does sound fun - but that’s usually the type of thing where unless vibbit bundled an in-house decoder (slow?) it would be a lot of platform-dependant work. So i’d probably only tackle it if absolutely necessary for some project

the design focus of vibbit so far is to keep things as non-rusty as possible from the API side. So it currently panics on a compilation error (and the driver handles glsl debug output).
Another issue with returning a Result here is the multithreading means your actual load function doesn’t have a failstate - all it does is reserve a unique ID that a shader should occupy when it’s loaded. You can still use your shader in draw calls that same frame because when the draw queue is consumed it’ll do all the loads first, and that shader uid is shared on the frontend and the render thread.

let my_shader = vib.load_shader("vs", "fs"); // returns 'handle' for shader
//[...] game logic, drawing
vib.end_frame(); // panic! shader compilation error

Internally - every drawing pipeline (like sprites, triangles, so on) have a default shader associated with them. So I could if desired set it up not to panic, and just always defer to the default if some issue exists with a shader you try to use. This is already how it will work if you try to use a shader you’ve deleted on some previous frame. This does make me think, maybe i could have end_frame() return a Result containing any issues it runs into while trying to do it’s work. This would include rendering issues, but also things like if the player tried to close the window.

Should be very possible - though i’ll note here that the default vertex shaders you’d be overriding are set up kind of differently for sprites, which is part of how I squeeze so much rendering optimization for them. You’d need to copy the source from the vibbit files to make use of it. this isn’t actually an issue or anything, just a matter of needing to document it. its also why i made a util function for sprite shaders that only accepts the frag shader.

framebuffers are already implemented and surprisingly they worked alongside shaders without any issues, first-try. It’s how the split-screen gif in the OP works. I’d like to include a utility function for framebuffers where you pass it one and it tries to draw it upscaled to the window size as smoothly as possible, either letterboxed or using a shader to help stretch it while keeping pixels uniform

image

bit of a non-update:
have been away from home this week (catsitting) and… the above shaders update isn’t even pushed to remote git. :gms_explosion:

so rather than work on anything internal i just put together a quick example to test integrating lua, with https://crates.io/crates/mlua
i set up the bare minimum to just get it running, and show how simple it is, in a style pretty similar to love2d:

function init()
    t = 0.0
end

function update()
    t = t + 1.0
    clear_screen()
    draw_text("hello from lua!", -32.0, math.sin(t / 10.0) * 10.0)
    print("test")
end

there’s no reason you couldn’t just hand over the end_frame() and should_close() functions from vibbit to lua, and have the whole update loop controlled via scripting. I may idly add other functions to this sample as I go on with the project just to try out the performance, and make sure my ideas about sharing resources like fonts/textures work properly

something i like about this too, compared to love2d, is if you used some sort of bundler (im sure ones out there for lua) - you could just embed your game code into the binary itself with rust’s include_str! macro - and in theory do your own code signing and such, too?

I’m not 100% sure if my setup is best practice (using thread_local!, every call from lua into rust invokes a borrow) - but here’s the code sample:

embedding lua
use std::cell::{RefCell};
use vibbit::{Vibbit, Color};
use mlua::prelude::*;

thread_local! {
    static VIB: RefCell<Vibbit> = RefCell::new(Vibbit::new(320, 180, ""))
}

struct App {
    lua: Lua
}

impl App {
    pub fn new() -> App {
        let lua = Lua::new();
        let mut app = App { 
            lua
        };
        app.init_module().unwrap();
        app
    }

    fn init_module(&mut self) -> Result<(), LuaError> {
        let globals = self.lua.globals();

        let font = VIB.with_borrow_mut(|vib| {
            vib.load_font("./assets/duster.otf", 13.0)
        });

        let clear_screen = self.lua.create_function(|_lua, ()| {
            VIB.with_borrow_mut(|vib| {
                let c = Color::new(0, 0, 0, 255);
                vib.clear_screen(c);
            });
            Ok(())
        })?;

        let draw_text = self.lua.create_function(move |_, (txt, x, y): (String, f32, f32)| {
            VIB.with_borrow_mut(|vib| {
                let c = Color::new(255, 255, 255, 255);
                vib.draw_text(&font, x, y, c, &txt, 1.0);
            });
            Ok(())
        })?;

        globals.set("clear_screen", clear_screen)?;
        globals.set("draw_text", draw_text)?;
        Ok(())
    }

    fn run(&mut self, script: &str) {
        let globals = self.lua.globals();

        self.lua.load(script).eval::<()>().unwrap();

        let init: LuaFunction = globals.get("init").unwrap();
        let update: LuaFunction = globals.get("update").unwrap();

        init.call::<_, ()>(()).unwrap();

        loop {
            update.call::<_, ()>(()).unwrap();

            let mut should_close = false;

            VIB.with_borrow_mut(|v| {
                v.end_frame();
                should_close = v.should_close()
            });

            if should_close {break}
        }
    }

}

const SCRIPT: &str = r#"
function init()
    t = 0.0
end

function update()
    t = t + 1.0
    clear_screen()
    draw_text("hello from lua!", -32.0, math.sin(t / 10.0) * 10.0)
    print("test")
end
"#;

fn main() {
    let mut app = App::new();
    app.run(SCRIPT);
}

image
image
curiosity got the better of me and I gave the lua demo the ability to draw textures as well. when set to the same limit drawing 20k a frame, on my laptop, vibbit seems to have a nice boost! ~20% or so, maybe. It’s a pretty CPU-bound situation esp. on a laptop that doesn’t top 2.8ghz. nowhere near a real-world comparison given how little functionality is exposed on vibbit’s end - but i think it means the ideas going into it are solidly achieving the goals i’m working towards.

vibbit in 2026! wow! I’m very hopeful that this is the year. My first commits to the rust project, which originally went under a different name, were around december 2022 - Summer of 2023 began a total rewrite of that framework, which is where vibbit officially begins development.

The first major update for the year is the inclusion of SDL3. This is done as a feature flag for the crate, meaning it’s actually still possible to choose to build on SDL2 if you need it. There are some issues with the version of SDL2 rust has, that make it incompatible once you’ve got CMake 4.0 installed, so it was a necessary change for linux especially to compile.

SDL3 does not really give us too many new goodies, since my renderer is custom OpenGL stuff, but one day (when the rust bindings improve compatibility) it would be really great to use another feature flag to toggle between using SDL_GPU vs my OpenGL layer. It would basically be another full rewrite of that layer of the framework, but because I’ve set up rust traits for what the renderer needs to be able to do, it is designed to be pretty swappable.

As before, the next two stops are still:

  • gamepads
  • audio

I more or less know how I’ll be integrating controller support from gilrs - Rust - but the rust audio ecosystem is still pretty unfamiliar and i’ll likely need to test a lot of options.

Onto other stuff! This is not a part of vibbit, but it’s being rendered by it! After banging my head against ECS systems a bit, I have finally found a pattern and method for organizing game logic i personally like. Really the trick ended up being when I decided that your scene/world system needs to have the collision stuff baked into the lowest level.

I’ve built out a simple collision testing library for circles/AABBs - then structured an “entity system” around that. For game objects, you can define any struct you like, and then you implement an Actor trait on it - this trait provides the lifecycle hooks like update(), draw(), and on_collide(). What’s nice is that it doesn’t tie in whatsoever to vibbit, by default. My tests and examples actually jump between a few rendering frameworks - but of course the two libraries should stack on top of each other nicely, and i’ll certainly be using them together.

To get around “references” and the rust borrow checker, there is an ID system that uses generic types - so the enemies, for example could track the player movement by holding onto an ID<Player>. During your enemy update logic, you can quickly world.get(player_id) for a readonly view of it, that also ensures the object is still live in the scene. If i need to mutate the player, I can “queue” up an action on it

world.with(player_id, |player| {
  player.health -= 1;
});

The framework delays the execution of that action until after the enemy is done updating.

To me, this solves so so so many of the ergonomics issues the ECS libraries within rust have. It’s very little boilerplate - you get to create your own object types, give them a simple update() function, and add them to the world at runtime. It does not require you to create a big “manifest” file that you update every time you create a new type of actor. I feel like this is the closest ive seen rust get to being usable like a “scripting” language, which i was pretty worried about even being possible. To me this will be most familiar to folks who have worked with Unity or Monogame. The whole thing is very WIP (you cant even delete objects from a scene yet) and I don’t have a name but expect more to come.

i’m excited to see where this goes! :)

heck yeah!! wonderful to see youn making progress on this again >:) keep it up, i can’t wait to see where it goes!

i’ve begun to make a few of the supplementary projects that have broader use-cases public:

vibarena

vibarena is actually published as a crate- https://crates.io/crates/vibarena. I’ve tipped about generational arenas in the thread OP but these are a really great data structure in the realm of game development. The idea behind them is rather than keeping all objects in standard list/vecs and referencing them by index, you return a ‘Key’ that contains both the index in the array but also a “generation” that determines when that element was added to that index. If I delete the element at that index, the arena increases the generation so we know the key that refers to it is out of date. So if you have a key you can always check if the object it points to is valid instead of something that was deleted and reinserted with a replacement. In a game this can be useful if you don’t want all your game objects holding direct references or pointers to each other. vibbit doesn’t have any concepts for game objects but I still use these internally for resources the user loads like textures, these all come with a key for the arena - which will be useful for working with scripting languages since you can just pass those keys (just two numbers) back and forth from one language to another rather than a full size struct
This arena stores all the actual objects totally compact in memory. Some arenas will leave deleted spots “empty” in memory, but this one will stay compact during any insertion/deletions so it can remain super fast if you just want to iterate over everything in the arena. Since its for a game 90% of what you do is go over each element and call update(). I’ve also set up some secondary data structures that allow you to associate more data in a map from key->value, but without having to actually hash anything to access it.

Rust can do some neat things automatically with your types - for the key I’ve specified the generation is a “NonZeroU32” which lets the compiler know this number will always be >= 1. This tells it that there is actually one “free” bit not being used by the struct - so I can wrap a Key with something like Option and it won’t take up any more space in memory than a Key on it’s own. An option in rust is a value that may or may not actually exist - think something like an enemy that is following a target. It may have a state where it forgets that target, or the target is deleted so you might want an Option, and in your code you must check if that option is Some or None before you try to actually access the target inside.

ogmold

I don’t have too much to say about this one other than it’s a loader for tilemap files from the ogmo editor: https://ogmo-editor-3.github.io/

I’ve worked with ldtk before but something tells me i should be sticking to the classics for this, so i wanted to create a simple lib to load files for ogmo. at some point it would probably be my plan to build some custom editor that is more in line with a greater engine, but for getting off the ground ogmo is a good balance.

since this is just an interpretation of a file format its interesting to try and figure out how much you want to be able to abstract over it. i come up with thoughts on useful things i could add to the loader, but the same time knowing anything that uses a project like this will still end up building another abstraction over it anyways, so why inject a middle abstraction here versus just presenting the data as its stored.

rusts more conventional lib for loading files is called serde, but its a pretty massive project and ive opted here to use a miniaturized crate called nanoserde instead, which is the only dependency for this ogmo loader. ogmo files have a few “existential” types, meaning JSON objects where the exact type of the object is defined in a property inside that object, which meant having to write manual parsing for a few of the structs. Otherwise serialization is quite simple in rust, where most basic types have serialization implemented as traits by the library, and if your structs only use those basic types you can derive that behavior automatically.

vibbit now has a few very basic sound functions thanks to https://crates.io/crates/kira - in my heart it was definitely way less satisfying to wrap this library, in comparison to the main work on rendering, so ive adopted a pretty small set of load sound / play sound / play music functions, enough at least to make jam-ish games on while i consider what it might look like to learn the audio stack myself. its getting to the point where i really just want to remove barriers towards whatever it will take to reach ‘playable game’ with, even if that means having to swap out a component later on. i wanna see this stuff running out there in the wild. gamepads and mouse input (which i forgot existed when i was adding keyboard stuff) will hopefully be next