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?
Really cool ![]()
Does the game engine produce any renders that you could share?
I havent found a single one in the repo…
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.
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.
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!) ![]()
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
Nice! Yes, I can try to work on integrating Ark into the project, I will give it a try and let you know!
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.
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 ![]()
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!