winit_surface_window
A helper library for using existing Android Surfaces (like Presentations) as windows within the Rust ecosystem, compatible with raw-window-handle.
This library allows you to create a window from an existing Android Surface object, which is particularly useful when working with Android Presentation APIs or other scenarios where you need to render to a secondary display or a specific surface.
Features
- Create a SurfaceWindow from an Android Surface object
- Compatible with raw-window-handle for integration with various graphics libraries
- Optional Skia renderer support for easy 2D graphics rendering
- Thread-safe implementation
Demo
package com.mui;
import android.app.Presentation;
import android.content.Context;
import android.os.Bundle;
import android.view.Display;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
public class PresentationBridge extends Presentation {
private SurfaceView mSurfaceView;
private long rustRendererHandle = 0;
static {
System.loadLibrary("presentation_demo");
}
private native long nativeInit();
private native void nativeShutdown(long handle);
private native void nativeSetSurface(long handle, Surface surface);
private native void nativeClearSurface(long handle);
private native void nativeSetSize(long handle, int width, int height);
public PresentationBridge(Context context, Display display) {
super(context, display);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mSurfaceView = new SurfaceView(getContext());
setContentView(mSurfaceView);
rustRendererHandle = nativeInit();
mSurfaceView.getHolder().addCallback(new SurfaceHolder.Callback() {
@Override
public void surfaceCreated(SurfaceHolder holder) {
if (rustRendererHandle != 0) {
nativeSetSurface(rustRendererHandle, holder.getSurface());
}
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
if (rustRendererHandle != 0) {
nativeSetSize(rustRendererHandle, width, height);
}
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
if (rustRendererHandle != 0) {
nativeClearSurface(rustRendererHandle);
}
}
});
}
@Override
public void onDetachedFromWindow() {
super.onDetachedFromWindow();
nativeShutdown(rustRendererHandle);
rustRendererHandle = 0;
}
@Override
protected void onStop() {
super.onStop();
}
}
use std::ptr::NonNull;
use std::sync::mpsc::{Receiver, Sender, TryRecvError};
use std::thread::{self, JoinHandle};
use std::time::Instant;
use jni::JNIEnv;
use jni::objects::{JClass, JObject};
use jni::sys::{jint, jlong};
use log::{error, info};
use once_cell::sync::Lazy;
use parking_lot::Mutex;
use skia_safe::{Color, Paint, Point, Rect};
use winit_surface_window::{error::Result, renderer::SkiaRenderer, window::SurfaceWindow};
enum RenderThreadMessage {
SetSurface(SurfaceWindow),
ClearSurface,
SetSize(i32, i32),
Shutdown,
}
struct RendererHandle {
sender: Sender<RenderThreadMessage>,
thread_handle: Option<JoinHandle<()>>,
}
impl Drop for RendererHandle {
fn drop(&mut self) {
info!("Dropping RendererHandle, sending shutdown message.");
self.sender
.send(RenderThreadMessage::Shutdown)
.unwrap_or_else(|e| error!("Failed to send shutdown msg: {}", e));
if let Some(handle) = self.thread_handle.take() {
handle.join().unwrap();
}
info!("Render thread joined.");
}
}
fn render_thread_main(receiver: Receiver<RenderThreadMessage>) {
let mut renderer: Option<SkiaRenderer> = None;
let mut window: Option<SurfaceWindow> = None;
let mut start_time = Instant::now();
let mut last_draw_time = Instant::now();
info!("Render thread started.");
'main_loop: loop {
match receiver.try_recv() {
Ok(message) => match message {
RenderThreadMessage::SetSurface(secondary_window) => {
info!("Render thread received SetSurface");
match SkiaRenderer::new(&secondary_window) {
Ok(r) => {
renderer = Some(r);
window = Some(secondary_window);
info!("Renderer created successfully on render thread.");
}
Err(e) => {
error!("Failed to create renderer: {}", e);
window = None;
renderer = None;
}
}
}
RenderThreadMessage::ClearSurface => {
info!("Render thread received ClearSurface. Destroying renderer.");
renderer = None;
window = None;
}
RenderThreadMessage::SetSize(width, height) => {
info!(
"Resize event received ({}x{}). Handling not yet implemented.",
width, height
);
}
RenderThreadMessage::Shutdown => {
info!("Shutdown message received. Exiting render thread.");
break 'main_loop;
}
},
Err(err) => match err {
TryRecvError::Disconnected => {
info!("Channel disconnected. Shutting down render thread.");
break 'main_loop;
}
TryRecvError::Empty => {}
},
}
if last_draw_time.elapsed().as_millis() >= 16 {
if let Some(r) = renderer.as_mut() {
if let Err(e) = r.draw(|canvas| {
canvas.clear(skia_safe::Color::MAGENTA);
let mut paint = skia_safe::Paint::default();
paint.set_color(skia_safe::Color::BLACK);
paint.set_anti_alias(true);
canvas.draw_str(
"Rendered with winit_secondary_display!",
skia_safe::Point::new(100.0, 100.0),
&Default::default(),
&paint,
);
canvas.draw_rect(
Rect::from_xywh(
start_time.elapsed().as_millis() as f32 % 500.0,
start_time.elapsed().as_millis() as f32 % 500.0,
100.0,
100.0,
),
&paint,
);
}) {
error!("Failed to draw frame: {}", e);
}
}
last_draw_time = Instant::now();
}
}
info!("Render thread finished.");
}
#[unsafe(no_mangle)]
pub extern "system" fn Java_com_mui_PresentationBridge_nativeInit(
_env: JNIEnv,
_class: JClass,
) -> jlong {
android_logger::init_once(android_logger::Config::default());
let (sender, receiver) = std::sync::mpsc::channel::<RenderThreadMessage>();
let thread_handle = thread::spawn(move || {
render_thread_main(receiver);
});
let handle = RendererHandle {
sender,
thread_handle: Some(thread_handle),
};
Box::into_raw(Box::new(handle)) as jlong
}
#[unsafe(no_mangle)]
pub extern "system" fn Java_com_mui_PresentationBridge_nativeShutdown(
_env: JNIEnv,
_class: JClass,
handle_ptr: jlong,
) {
if handle_ptr != 0 {
let _handle = unsafe { Box::from_raw(handle_ptr as *mut RendererHandle) };
}
}
#[unsafe(no_mangle)]
pub extern "system" fn Java_com_mui_PresentationBridge_nativeSetSurface(
mut env: JNIEnv,
_class: JClass,
handle_ptr: jlong,
surface: JObject,
) {
let handle = unsafe { &*(handle_ptr as *const RendererHandle) };
match unsafe { SurfaceWindow::from_surface(&mut env, surface) } {
Ok(native_window) => {
handle
.sender
.send(RenderThreadMessage::SetSurface(native_window))
.unwrap();
}
Err(e) => error!("Failed to create SurfaceWindow: {}", e),
}
}
#[unsafe(no_mangle)]
pub extern "system" fn Java_com_mui_PresentationBridge_nativeSetSize(
mut env: JNIEnv,
_class: JClass,
handle_ptr: jlong,
width: jint,
height: jint,
) {
}
#[unsafe(no_mangle)]
pub extern "system" fn Java_com_mui_PresentationBridge_nativeClearSurface(
_env: JNIEnv,
_class: JClass,
handle_ptr: jlong,
) {
let handle = unsafe { &*(handle_ptr as *const RendererHandle) };
handle
.sender
.send(RenderThreadMessage::ClearSurface)
.unwrap();
}
Usage
Add this to your Cargo.toml:
[dependencies]
winit_surface_window = { path = "path/to/winit_surface_window" }
[features]
skia-renderer = ["winit_surface_window/skia-renderer"]
API
SurfaceWindow
The main struct representing an Android Surface as a window. Implements HasRawWindowHandle and HasRawDisplayHandle for compatibility with various graphics libraries.
SkiaRenderer
Optional Skia-based renderer that can draw to a SurfaceWindow. Provides a simple interface for drawing 2D graphics.
License
This project is licensed under either of
at your option.