This document summarizes the implementation of the new open-source OpenGL renderer for Twoyi, as requested in the issue.
A complete open-source OpenGL renderer implementation in Rust that communicates with the container via QEMU pipes:
Files Created:
mod.rs- Module definition and public APIpipe.rs- QEMU pipe connection implementation (similar to Anbox's pipe_connection_creator.h)opengles.rs- OpenGL ES protocol implementationrenderer.rs- High-level renderer interface that mimics the old renderer API
Key Features:
- Connects to container's OpenGL ES endpoints (
/opengles,/opengles2,/opengles3) - Automatic fallback from OpenGL ES 3 → 2 → 1
- Thread-safe implementation using Rust's
Mutexand atomic operations - Graceful fallback to old renderer if QEMU pipe is unavailable
Updated the core module to support both renderers:
Changes Made:
- Added
RendererTypeenum to distinguish between Old and New renderers - Added
set_renderer_type()function to configure which renderer to use - Modified
init_renderer()to dispatch to appropriate renderer based on configuration - Updated
reset_window()andremove_window()to support both renderers
Preserved:
- All old renderer code remains untouched
- Old renderer is the default (backwards compatible)
- Old renderer bindings unchanged
Added new native method for renderer selection:
Changes Made:
- Added
set_renderer_type()JNI function - Registered new method in
JNI_OnLoad - Updated Java bindings
- Added
setRendererType(int useNewRenderer)native method
- Added
USE_NEW_RENDERERsetting key - Added
useNewRenderer()andsetUseNewRenderer()methods - Settings are profile-specific
- Modified
surfaceCreated()to check renderer preference - Calls
Renderer.setRendererType()before initializing
- Added checkbox preference for new renderer
- Displays reboot warning when changed
- Properly saves to profile settings
- Added
settings_key_use_new_renderer - Added
settings_use_new_renderer_summary
- Added CheckBoxPreference for renderer selection
- Placed in Advanced settings category
- Default value: false (uses old renderer)
Created comprehensive documentation:
OPENGL_RENDERER.md:
- Architecture overview
- Component descriptions
- Protocol details
- Comparison table (Old vs New)
- Usage instructions
- Troubleshooting guide
- Future improvement suggestions
As specified in the requirements, the new renderer is implemented in Rust (like libtwoyi) rather than C++:
- Memory safety without garbage collection
- Zero-cost abstractions
- Strong type system prevents bugs
- Excellent concurrency support
- Consistent with existing libtwoyi implementation
The new renderer provides the exact same function signatures as the old renderer:
start_renderer()→startOpenGLRenderer()set_native_window()→setNativeWindow()reset_window()→resetSubWindow()remove_window()→removeSubWindow()destroy_subwindow()→destroyOpenGLSubwindow()repaint_display()→repaintOpenGLDisplay()
This allows seamless switching between renderers without changing the calling code.
The renderer choice is stored per-profile, allowing users to:
- Test the new renderer on one profile while keeping another stable
- Use different renderers for different use cases
- Revert easily if issues occur
The new renderer includes intelligent fallback mechanisms:
- If QEMU pipe device doesn't exist → automatically uses old renderer
- If OpenGL ES 3 unavailable → falls back to ES 2
- If OpenGL ES 2 unavailable → falls back to ES 1
- If connection fails → logs error but doesn't crash
Critical requirement met: The old renderer is completely untouched:
renderer_bindings.rs- unchangedlibOpenglRender.so- still included- Default behavior - uses old renderer
- No breaking changes to existing functionality
┌─────────────────────────────────────────────────────────────┐
│ Android Application │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ Render2Activity (Java) │ │
│ │ ┌────────────────────────────────────────────────┐ │ │
│ │ │ ProfileSettings.useNewRenderer() ? │ │ │
│ │ │ ↓ true ↓ false │ │ │
│ │ │ New Renderer Old Renderer │ │ │
│ │ └────────────────────────────────────────────────┘ │ │
│ └──────────────────────────────────────────────────────┘ │
│ ↓ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ Renderer.java (JNI) │ │
│ │ - setRendererType(useNew ? 1 : 0) │ │
│ │ - init(), resetWindow(), removeWindow() │ │
│ └──────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
↓ JNI
┌─────────────────────────────────────────────────────────────┐
│ Native Layer (Rust) │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ lib.rs / core.rs │ │
│ │ RendererType = Old | New │ │
│ └──────────────────────────────────────────────────────┘ │
│ ↓ Old ↓ New │
│ ┌──────────────────┐ ┌──────────────────────────┐ │
│ │ renderer_bindings│ │ renderer_new/ │ │
│ │ (unchanged) │ │ ┌───────────────────┐ │ │
│ │ - Links to │ │ │ renderer.rs │ │ │
│ │ libOpenglRender │ │ │ (coordinator) │ │ │
│ └──────────────────┘ │ └───────────────────┘ │ │
│ ↓ │ ┌───────────────────┐ │ │
│ ┌──────────────────┐ │ │ opengles.rs │ │ │
│ │ libOpenglRender │ │ │ (GL protocol) │ │ │
│ │ .so (binary) │ │ └───────────────────┘ │ │
│ │ (proprietary) │ │ ┌───────────────────┐ │ │
│ └──────────────────┘ │ │ pipe.rs │ │ │
│ │ │ (QEMU pipe) │ │ │
│ │ └───────────────────┘ │ │
│ └──────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ Container Layer │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ /dev/qemu_pipe │ │
│ │ ┌────────────────────────────────────────────────┐ │ │
│ │ │ OpenGL ES Services: │ │ │
│ │ │ - /opengles │ │ │
│ │ │ - /opengles2 │ │ │
│ │ │ - /opengles3 │ │ │
│ │ └────────────────────────────────────────────────┘ │ │
│ └──────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
Due to environment limitations, the following tests should be performed:
cd app/rs
cargo test --target x86_64-unknown-linux-gnucd app/rs
./build_rs.sh --release-
Old Renderer Test:
- Launch app with "Use New Renderer" disabled
- Verify container displays correctly
- Test touch input, display rotation, etc.
-
New Renderer Test:
- Enable "Use New Renderer" in settings
- Reboot container
- Verify new renderer initializes
- Check logcat for "Using renderer: New"
-
Fallback Test:
- On device without QEMU pipe
- Enable new renderer
- Verify automatic fallback to old renderer
- Check logs for fallback message
- Build Environment: Requires Android NDK and cargo-xdk for building
- QEMU Pipe Dependency: New renderer requires
/dev/qemu_pipedevice - Protocol Assumptions: Based on Anbox implementation, may need tuning
- Testing: Requires physical Android device or emulator for full testing
✅ Fully backwards compatible:
- Old renderer is default
- Old renderer code unchanged
- No breaking changes to existing functionality
- Users can opt-in to new renderer
- Easy rollback if issues occur
Potential enhancements:
- Implement more GL commands for better compatibility
- Add performance metrics and logging
- Support for additional graphics protocols
- Hardware acceleration integration
- Benchmark against old renderer
- Add visual tests and screenshots
Implementation based on:
- Anbox Graphics
- Anbox Pipe Connection Creator
- Existing Twoyi architecture and patterns
The implementation successfully delivers:
✅ Open-source OpenGL renderer in Rust
✅ QEMU pipe communication (like Anbox)
✅ Mimics old renderer API
✅ Old renderer preserved and untouched
✅ Settings UI for renderer selection
✅ Profile-specific configuration
✅ Graceful fallback mechanisms
✅ Comprehensive documentation
The new renderer is ready for testing and can be built with the standard Twoyi build process.