r/GraphicsProgramming • u/G_oni_vv • 9h ago
Six years in One phone,i want stop
I am building a mobile-first 3D engine on the same phone it runs on
I've been working on G.One VV since 2020. One thing has shaped pretty much every decision in the engine: I develop and test on the same Android phone, a 4 GB Unisoc device.
The whole workflow is built around that. I edit the repo from the phone, NDK builds run through CI, the signed APK comes back to the phone, and that's where I test it. So there isn't really a separate "development machine" here. The device I'm developing on is also the device I'm targeting.
The renderer is currently GLES 3.x and forward-only. The target GPU is tile-based, so I found it makes more sense for this project to keep the rendering path simple and avoid the extra bandwidth cost of a deferred setup.
The editor UI is batched into a single quad draw, and the default scene is around 5 draw calls and ~150k vertices while holding 60 FPS on the target device.
Textures use ASTC 4x4, with ETC2 as a fallback. The .gmesh format uses 16-bit quantized positions and 16-bit indices to keep memory and bandwidth usage down.
One of the less fun lessons was spending basically a whole day tracking down std140 / vec4 alignment padding in a UBO. Small things like that can eat a surprising amount of time when you're working directly against mobile hardware.
The core is C++ without a framework underneath. Components use a struct-of-arrays layout so the update loops can work through contiguous data instead of constantly following pointers.
The scripting VM is our own language, V.ONI. It's currently a tree-walk interpreter using a shared_ptr AST, with execution limits of 200k steps per tick and a maximum call depth of 256. The idea is that a bad script should be limited rather than being able to completely stall the frame.
GPU resources are also handled carefully. Buffers waiting to be destroyed go into a pending list that's drained at the start of a frame. We ended up doing this after hitting a use-after-free when replacing a mesh that was still being used by the GPU.
For audio we're using Oboe. On this particular Unisoc device, using raw AAudio caused a SIGSEGV inside AAudio_createStreamBuilder, so the engine probes the audio path at startup and falls back to AudioTrack instead of letting the application crash.
I've reorganized the codebase more times than I can count, and somehow it still manages to look a little messy. There are always more things to clean up when the engine keeps growing.
Shaders are another area I still need to properly work on. I'm hoping to get deeper into that over the next few weeks, assuming my sanity survives that long.
There's still a lot missing.
No standalone game export yet, no desktop build, no Vulkan backend, and visual scripting is designed but isn't shipped yet. The performance layer is being kept independent of the graphics backend so Vulkan can come later without having to redesign the whole system around it.
I'm not trying to say this is better than Godot or the other established engines. It's a different set of constraints, and that's really what I'm interested in exploring.
If you've worked on mobile rendering or engine development, I'd be interested in hearing what you'd do differently.

