Open Reality - a code first julia based game engine

Hi! I built OpenReality, a code-first game engine in Julia. I was inspired by RealityKit’s declarative API – you can build Vision Pro apps in ~20 lines of Swift. I wanted that elegance for games, but cross-platform and open source. Key features: - ECS architecture with immutable scene graph - 4 rendering backends (OpenGL, Metal, Vulkan, WebGPU) - Full PBR pipeline (deferred shading, CSM shadows, IBL) - Physics engine with GJK+EPA collision detection - Skeletal animation with glTF 2.0 support - 3D positional audio (OpenAL) - Exports to 400 KB WASM (vs Unity’s 200+ MB) Why Julia? Zero marshaling overhead. Unlike Unity (C#→C++) or Godot (GDScript→C++), there’s no FFI layer between your game code and the engine since they are both written in julia. Everything runs at native speed.
GitHub: GitHub - sinisterMage/Open-Reality: A Julia framework for building interactive 3D experiances
I’d love feedback on the architecture and API design. What features would make this useful for your projects?

16 Likes

Really cool :slight_smile:
Does the game engine produce any renders that you could share? :smiley: I havent found a single one in the repo…

4 Likes

Hey! Thank you so much!
Great point about the screenshots - I’m away
from my main PC right now, but I’ll add renders to the README this week.

In the meantime, feel free to check out the
examples/ directory if you want to see it running!
(note, metal examples will only work on macOS)

Thanks for taking a look!

Just added screenshots showing the
PBR materials system and rendering
pipeline! Check out the screenshots
folder - would love to hear your
thoughts on the rendering quality.

4 Likes

Hi @sinisterMage nice work! I see that you use an ECS architecture, looking inside your code it doesn’t seem too well optimized though, maybe you could use Ark.jl where we are actually trying to squeeze any performance drop we can. It would be awesome to see Ark.jl used in a project like yours!

Thanks for checking it out! Curious what optimizations you noticed - always looking to learn.

I’ll definitely check out Ark.jl! Would be interesting to compare approaches. If there are specific techniques that could benefit OpenReality, I’m all ears.

In general, I truly believe you can’t create an high-performance ECS without using generated functions, in Ark, we use them almost everywhere so that the code is well-inferred and non-allocating. Apart from that which is the major Julia specific trick, we use many other tricks to optimize performance in general. I woud be interested too to compare the approaches, we have a big amount of benchmarks here: Ark.jl/benchmark/benches at main · ark-ecs/Ark.jl · GitHub if you’d like to implement some of them we could compare Ark to your approach.

1 Like

Hey @sinisterMage, I took the time to do some basic benchmarking:

Benchmarks OpenReality
using OpenReality, BenchmarkTools

struct Position <: Component
    x::Float64
    y::Float64
end

struct Velocity <: Component
    vx::Float64
    vy::Float64
end

function NewWorld(types...)
    for T in types
        register_component_type(T)
    end
    return World()
end

function new_entity!(world::World, components::Tuple)
    e = create_entity!(world)
    for c in components
        add_component!(e, c)
    end
    return e
end

function setup_world(n_entities::Int)
    world = NewWorld(Position, Velocity)
    entities = Vector{EntityID}()
    for i in 1:n_entities
        e = new_entity!(world, (Position(Float64(i), Float64(i * 2)),))
        push!(entities, e)
    end
    return (entities, world)
end

function benchmark_world_get_1(entities, world)
    sum = 0.0
    for e in entities
        pos = get_component(e, Position)
        sum += pos.x
    end
    return sum
end

function benchmark_world_set_1(entities, world)
    for e in entities
        add_component!(e, Position(1.0, 2.0))
    end
end

function benchmark_world_add_remove_1(entities, world)
    for e in entities
        add_component!(e, Velocity(0.0, 0.0))
    end
    for e in entities
        remove_component!(e, Velocity)
    end
end

e, w = setup_world(10^5);

@benchmark benchmark_world_get_1($e, $w)

@benchmark benchmark_world_set_1($e, $w)

@benchmark benchmark_world_add_remove_1($e, $w)
Benchmarks Ark
using Ark, BenchmarkTools

struct Position
    x::Float64
    y::Float64
end

struct Velocity
    dx::Float64
    dy::Float64
end

function setup_world(n_entities::Int)
    world = World(Position, Velocity)
    entities = Vector{Entity}()
    for i in 1:n_entities
        e = new_entity!(world, (Position(i, i * 2),))
        push!(entities, e)
    end
    return (entities, world)
end

function benchmark_world_get_1(entities, world)
    sum = 0.0
    for e in entities
        pos, = get_components(world, e, (Position,))
        sum += pos.x
    end
    return sum
end

function benchmark_world_set_1(entities, world)
    for e in entities
        set_components!(world, e, (Position(1, 2),))
    end
end

function benchmark_world_add_remove_1(entities, world)
    for e in entities
        add_components!(world, e, (Velocity(0, 0),))
    end
    for e in entities
        remove_components!(world, e, (Velocity,))
    end
end

e, w = setup_world(10^5);

@benchmark benchmark_world_get_1($e, $w)

@benchmark benchmark_world_set_1($e, $w)

@benchmark benchmark_world_add_remove_1($e, $w)

I just implemented getting, setting and adding+removing a single component:

Results OpenReality
julia> @benchmark benchmark_world_get_1($e, $w)
BenchmarkTools.Trial: 2324 samples with 1 evaluation per sample.
 Range (min … max):  1.978 ms …   4.029 ms  ┊ GC (min … max): 0.00% … 0.00%
 Time  (median):     2.089 ms               ┊ GC (median):    0.00%
 Time  (mean ± σ):   2.144 ms ± 203.739 μs  ┊ GC (mean ± σ):  0.00% ± 0.00%

     ▆█▇▆▄▄▃▃▂▁                                               ▁
  ▅▆█████████████▇▇▃▅▅▃▅▅▄▄▅▃▅▅▄▅▄▄▃▆▅▃▄▄▁▄▄▄▅▅▅▄▅▃▃▄▅▄▃▁▃▁▄▅ █
  1.98 ms      Histogram: log(frequency) by time      3.23 ms <

 Memory estimate: 0 bytes, allocs estimate: 0.

julia> @benchmark benchmark_world_set_1($e, $w)
BenchmarkTools.Trial: 691 samples with 1 evaluation per sample.
 Range (min … max):  5.810 ms … 23.962 ms  ┊ GC (min … max): 0.00% … 54.76%
 Time  (median):     6.303 ms              ┊ GC (median):    0.00%
 Time  (mean ± σ):   7.231 ms ±  2.078 ms  ┊ GC (mean ± σ):  6.47% ± 10.17%

  ▄█▇▇▅▄▁▁  ▂▁    ▁ ▁▂                ▁                       
  █████████▇███████████▄▁▇▆▁▆▅▇▇▄▁▇▅▇▇█████▆▄▇▁▄▁▇▄▅▁▇▁▁▁▁▄▅ █
  5.81 ms      Histogram: log(frequency) by time     13.6 ms <

 Memory estimate: 4.57 MiB, allocs estimate: 199489.

julia> @benchmark benchmark_world_add_remove_1($e, $w)
BenchmarkTools.Trial: 200 samples with 1 evaluation per sample.
 Range (min … max):  20.435 ms … 42.006 ms  ┊ GC (min … max): 0.00% … 30.75%
 Time  (median):     24.446 ms              ┊ GC (median):    0.00%
 Time  (mean ± σ):   25.062 ms ±  2.674 ms  ┊ GC (mean ± σ):  1.04% ±  3.93%

           ▆▃▃▅▅█▅▆  ▁▃ ▂                                      
  ▃▁▁▃▃▁▃▃▄█████████▆████▅▇▅▄▄▄▃▁▃▃▃▄▃▁▁▁▁▁▁▁▄▁▁▃▁▁▁▁▁▁▁▁▁▁▁▄ ▄
  20.4 ms         Histogram: frequency by time        35.2 ms <

 Memory estimate: 3.05 MiB, allocs estimate: 100000.
Results Ark
julia> @benchmark benchmark_world_get_1($e, $w)
BenchmarkTools.Trial: 10000 samples with 1 evaluation per sample.
 Range (min … max):  122.597 μs … 213.813 μs  ┊ GC (min … max): 0.00% … 0.00%
 Time  (median):     128.403 μs               ┊ GC (median):    0.00%
 Time  (mean ± σ):   129.955 μs ±   6.004 μs  ┊ GC (mean ± σ):  0.00% ± 0.00%

  ▁▃ ▂▁▁▇█▄▄ ▂▄▆▆▅▄▂▁▁▁▂▂▂▂▂▁ ▁▁▁▁▁▁                            ▂
  ████████████████████████████████████▇▇█▇█▇▇▆▆▇▆▆▆▆▅▅▆▅▄▄▅▄▄▄▅ █
  123 μs        Histogram: log(frequency) by time        155 μs <

 Memory estimate: 0 bytes, allocs estimate: 0.

julia> @benchmark benchmark_world_set_1($e, $w)
BenchmarkTools.Trial: 10000 samples with 1 evaluation per sample.
 Range (min … max):  220.766 μs … 298.245 μs  ┊ GC (min … max): 0.00% … 0.00%
 Time  (median):     230.044 μs               ┊ GC (median):    0.00%
 Time  (mean ± σ):   231.946 μs ±   5.353 μs  ┊ GC (mean ± σ):  0.00% ± 0.00%

                ▅█▃▆▂                                            
  ▁▁▁▁▁▁▁▁▁▁▁▂▃▅█████▃▂▂▃▄▄▄▅▄▃▃▃▂▂▂▂▂▂▂▂▂▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ ▂
  221 μs           Histogram: frequency by time          253 μs <

 Memory estimate: 0 bytes, allocs estimate: 0.

julia> @benchmark benchmark_world_add_remove_1($e, $w)
BenchmarkTools.Trial: 1081 samples with 1 evaluation per sample.
 Range (min … max):  4.464 ms …  4.914 ms  ┊ GC (min … max): 0.00% … 0.00%
 Time  (median):     4.609 ms              ┊ GC (median):    0.00%
 Time  (mean ± σ):   4.615 ms ± 35.980 μs  ┊ GC (mean ± σ):  0.00% ± 0.00%

                              ▁▄▃█▆▅▅▅▃                       
  ▂▁▁▁▁▁▁▁▁▂▂▂▂▂▃▂▁▃▁▃▃▃▄▄▄▅▆██████████▇██▅▆▅▆▅▅▅▄▄▄▄▃▃▁▃▃▃▂ ▄
  4.46 ms        Histogram: frequency by time        4.71 ms <

 Memory estimate: 0 bytes, allocs estimate: 0.

so Ark.jl is more than an order of magnitude faster. Though I guess with operations on more components this difference will be higher.

I did this as a sort of encouragement to try Ark in your project (and helping out if you’d like!) :slight_smile:

4 Likes

Hey! This is fantastic - thank you so much for taking
the time to run these benchmarks!
An order of magnitude speedup would help OpenReality a lot, and your zero-allocation design is way better than my current implementation. I’d absolutely love to integrate Ark.jl into the engine
Would you be interested in collaborating on this?

  • Use Ark.jl as the core ECS (it’s clearly superior)
  • Maintain OpenReality’s API as a wrapper layer
  • List you as a core contributor
  • Use OpenReality as a production showcase for Ark.jl
    Really excited about this - and yes, definitely using
    Ark in the project! The encouragement means a lot
3 Likes

Nice! Yes, I can try to work on integrating Ark into the project, I will give it a try and let you know!

3 Likes

This is awesome! I’m actually very interested in being able to export to WASM for my projects and was curious if you had any documentation on that or insights? I would love to be able to do that with my engine, but I suspect it’s probably a major architectural difference just peeking at your code a bit.

4 Likes

Thank you! my WASM pipeline for open reality works in 2 parts
the Julia side exports the scene to a binary format (.orsb) and there is also a rust based WASM runtime required for running the orsb binary in the browser, i am not sure this is the most Julia centric approach there is since both parts of the exports are depended on rust. hopefully that gave you some insight to the WASM pipeline, you are welcome to ask questions about my pipeline if you want :smiley:

2 Likes

Very cool! Thanks for the insight on that. Definitely want to play with it myself here soon. I’ll let you know if I have any more questions. Thanks again!

1 Like