Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

android build instantly exits while windows build runs just fine winit backend #2

Closed
kyoobey opened this issue May 20, 2022 · 28 comments

Comments

@kyoobey
Copy link

kyoobey commented May 20, 2022

sorry to bug you again, but i'm completely stuck on this

when i added a vertex buffer to the agdk-winit-wgpu example, it ran on windows but on resizing, the window lagged, and on android it stopped responding when trying to change the device orientation

code
use log::Level;
use log::trace;
use wgpu::TextureFormat;
use wgpu::{Instance, Adapter, Device, ShaderModule, PipelineLayout, RenderPipeline, Queue};
use wgpu::util::DeviceExt;
use winit::event_loop::EventLoopWindowTarget;

use std::ops::Deref;
use std::borrow::Cow;
use std::sync::{Arc, RwLock};
use winit::{
	event::{Event, WindowEvent},
	event_loop::{ControlFlow, EventLoop},
};



#[repr(C)]
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
struct Vertex {
	position: [f32; 2],
}

impl Vertex {
	fn desc<'a>() -> wgpu::VertexBufferLayout<'a> {
		wgpu::VertexBufferLayout {
			array_stride: std::mem::size_of::<Vertex>() as wgpu::BufferAddress,
			step_mode: wgpu::VertexStepMode::Vertex,
			attributes: &[
				wgpu::VertexAttribute {
					offset: 0,
					shader_location: 0,
					format: wgpu::VertexFormat::Float32x2
				}
			]
		}
	}
}

const VERTICES: [Vertex; 4] = [
	Vertex { position: [-1.0, -1.0] },
	Vertex { position: [-1.0,  1.0] },
	Vertex { position: [ 1.0,  1.0] },
	Vertex { position: [ 1.0, -1.0] }
];

const INDICES: &[u16] = &[
	2, 1, 0,
	3, 2, 0
];





struct RenderState {
	device: Device,
	queue: Queue,
	_shader: ShaderModule,
	target_format: TextureFormat,
	_pipeline_layout: PipelineLayout,
	render_pipeline: RenderPipeline,

	vertices: [Vertex; 4],
	vertex_buffer: wgpu::Buffer,
	num_vertices: u32,
	index_buffer: wgpu::Buffer,
	num_indices: u32


}

struct SurfaceState {
	window: winit::window::Window,
	surface: wgpu::Surface
}

struct AppInner {
	instance: Instance,
	adapter: Option<Adapter>,
	surface_state: Option<SurfaceState>,
	render_state: Option<RenderState>,
}

struct App {
	inner: Arc<RwLock<AppInner>>
}

impl App {
	fn new(instance: Instance) -> Self {
		Self {
			inner: Arc::new(RwLock::new(AppInner {
				instance,
				adapter: None,
				surface_state: None,
				render_state: None,
			}))
		}
	}
}
impl Deref for App {
	type Target = Arc<RwLock<AppInner>>;
	fn deref(&self) -> &Self::Target {
		&self.inner
	}
}


async fn init_render_state(adapter: &Adapter, target_format: TextureFormat) -> RenderState {
	trace!("Initializing render state");

	trace!("WGPU: requesting device");
	// Create the logical device and command queue
	let (device, queue) = adapter
		.request_device(
			&wgpu::DeviceDescriptor {
				label: None,
				features: wgpu::Features::empty(),
				// Make sure we use the texture resolution limits from the adapter, so we can support images the size of the swapchain.
				limits: wgpu::Limits::downlevel_webgl2_defaults()
					.using_resolution(adapter.limits()),
			},
			None,
		)
		.await
		.expect("Failed to create device");

	trace!("WGPU: loading shader");
	// Load the shaders from disk
	let shader = device.create_shader_module(&wgpu::ShaderModuleDescriptor {
		label: None,
		source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("shader.wgsl"))),
	});

	let vertices = VERTICES;

	let vertex_buffer = device.create_buffer_init(
		&wgpu::util::BufferInitDescriptor {
			label: Some("Vertex Buffer"),
			contents: bytemuck::cast_slice(&vertices),
			usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST
		}
	);
	let num_vertices = vertices.len() as u32;

	let index_buffer = device.create_buffer_init(
		&wgpu::util::BufferInitDescriptor {
			label: Some("Index Buffer"),
			contents: bytemuck::cast_slice(INDICES),
			usage: wgpu::BufferUsages::INDEX
		}
	);
	let num_indices = INDICES.len() as u32;

	trace!("WGPU: creating pipeline layout");
	let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
		label: None,
		bind_group_layouts: &[],
		push_constant_ranges: &[],
	});

	trace!("WGPU: creating render pipeline");
	let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
		label: None,
		layout: Some(&pipeline_layout),
		vertex: wgpu::VertexState {
			module: &shader,
			entry_point: "vs_main",
			buffers: &[
				Vertex::desc()
			],
		},
		fragment: Some(wgpu::FragmentState {
			module: &shader,
			entry_point: "fs_main",
			targets: &[target_format.into()],
		}),
		primitive: wgpu::PrimitiveState::default(),
		depth_stencil: None,
		multisample: wgpu::MultisampleState::default(),
		multiview: None,
	});

	RenderState {
		device,
		queue,
		_shader: shader,
		target_format,
		_pipeline_layout: pipeline_layout,
		render_pipeline,
		vertices, vertex_buffer, num_vertices, index_buffer, num_indices
	}
}

fn configure_surface_swapchain(render_state: &RenderState, surface_state: &SurfaceState) {
	let swapchain_format = render_state.target_format;
	let size = surface_state.window.inner_size();

	let mut config = wgpu::SurfaceConfiguration {
		usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
		format: swapchain_format,
		width: size.width,
		height: size.height,
		// present_mode: wgpu::PresentMode::Mailbox,
		present_mode: wgpu::PresentMode::Fifo,
	};

	trace!("WGPU: Configuring surface swapchain: format = {swapchain_format:?}, size = {size:?}");
	surface_state.surface.configure(&render_state.device, &config);
}

// We want to defer the initialization of our render state until
// we have a surface so we can take its format into account.
//
// After we've initialized our render state once though we
// expect all future surfaces will have the same format and we
// so this stat will remain valid.
async fn ensure_render_state_for_surface(app: &App, new_surface_state: &SurfaceState) {
	let mut app_guard = app.inner.write().unwrap();

	if app_guard.adapter.is_none() {
		trace!("WGPU: requesting a suitable adapter (compatible with our surface)");
		let adapter = app_guard.instance
			.request_adapter(&wgpu::RequestAdapterOptions {
				power_preference: wgpu::PowerPreference::default(),
				force_fallback_adapter: false,
				// Request an adapter which can render to our surface
				compatible_surface: Some(&new_surface_state.surface),
			})
			.await
			.expect("Failed to find an appropriate adapter");

		app_guard.adapter = Some(adapter);
	}
	let adapter = app_guard.adapter.as_ref().unwrap();

	if app_guard.render_state.is_none() {
		trace!("WGPU: finding preferred swapchain format");
		let swapchain_format = new_surface_state.surface.get_preferred_format(&adapter).unwrap();

		let rs = init_render_state(adapter, swapchain_format).await;
		app_guard.render_state = Some(rs);
	}
}

fn create_surface<T>(app: &App, event_loop: &EventLoopWindowTarget<T>) -> SurfaceState {
	let window = winit::window::Window::new(&event_loop).unwrap();

	trace!("WGPU: creating surface for native window");
	let guard = app.inner.read().unwrap();
	let surface = unsafe { guard.instance.create_surface(&window) };

	SurfaceState {
		window,
		surface
	}
}

fn resume<T>(app: &App, event_loop: &EventLoopWindowTarget<T>) {
	trace!("Resumed, creating render state...");

	let new_surface = create_surface(&app, event_loop);

	pollster::block_on(ensure_render_state_for_surface(&app, &new_surface));

	app.write().unwrap().surface_state = Some(new_surface);

	let guard = app.read().unwrap();
	let render_state = guard.render_state.as_ref().unwrap();
	let surface_state = guard.surface_state.as_ref().unwrap();
	configure_surface_swapchain(render_state, surface_state);

	trace!("Making Redraw Request");
	surface_state.window.request_redraw();
}


fn run(event_loop: EventLoop<()>, app: App) {
	trace!("Running mainloop...");

	event_loop.run(move |event, event_loop, control_flow| {
		trace!("Received Winit event: {event:?}");

		*control_flow = ControlFlow::Wait;
		match event {
			Event::NewEvents(winit::event::StartCause::Init) => {
				// Note: that because Winit doesn't currently support lifecycle events consistently
				// across platforms then we effectively issue a fake 'resume' on non-android
				// platforms...
				#[cfg(not(target_os="android"))]
				resume(&app, event_loop)
			}
			Event::Resumed => {
				resume(&app, event_loop);
			}
			Event::Suspended => {
				trace!("Suspended, dropping render state...");
				let mut guard = app.write().unwrap();
				//guard.running = false;
				guard.render_state = None;
			},
			Event::WindowEvent {
				event: WindowEvent::Resized(_size),
				..
			} => {
				let guard = app.read().unwrap();
				if let Some(ref surface_state) = guard.surface_state {
					if let Some(ref render_state) = guard.render_state {
						configure_surface_swapchain(render_state, surface_state);

						// Winit: doesn't currently implicitly request a redraw
						// for a resize which may be required on some platforms...
						surface_state.window.request_redraw();
					}
				}
			}
			Event::RedrawRequested(_) => {
				trace!("Handling Redraw Request");

				let guard = app.read().unwrap();
				if let Some(ref surface_state) = guard.surface_state {
					if let Some(ref rs) = guard.render_state {
						let frame = surface_state.surface
							.get_current_texture()
							.expect("Failed to acquire next swap chain texture");
						let view = frame
							.texture
							.create_view(&wgpu::TextureViewDescriptor::default());
						let mut encoder =
							rs.device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None });
						{
							let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
								label: None,
								color_attachments: &[wgpu::RenderPassColorAttachment {
									view: &view,
									resolve_target: None,
									ops: wgpu::Operations {
										load: wgpu::LoadOp::Clear(wgpu::Color::GREEN),
										store: true,
									},
								}],
								depth_stencil_attachment: None,
							});
							rpass.set_pipeline(&rs.render_pipeline);

							rpass.set_vertex_buffer(0, rs.vertex_buffer.slice(..));
							rpass.set_index_buffer(rs.index_buffer.slice(..), wgpu::IndexFormat::Uint16);
							rpass.draw_indexed(0..rs.num_indices, 0, 0..1);

						}

						rs.queue.submit(std::iter::once(encoder.finish()));
						frame.present();
						surface_state.window.request_redraw();
					}
				}
			}
			Event::WindowEvent {
				event: WindowEvent::CloseRequested,
				..
			} => *control_flow = ControlFlow::Exit,
			_ => {}
		}
	});
}


fn _main() {
	let event_loop = EventLoop::new();

	// We can decide on our graphics API / backend up-front and that
	// doesn't need to be re-considered later
	let instance = wgpu::Instance::new(wgpu::Backends::all());
	//let instance = wgpu::Instance::new(wgpu::Backends::VULKAN);
	//let instance = wgpu::Instance::new(wgpu::Backends::GL);

	let app = App::new(instance);

	run(event_loop, app);
}


#[cfg(target_os="android")]
#[no_mangle]
extern "C" fn android_main() {
	android_logger::init_once(
		android_logger::Config::default().with_min_level(Level::Trace)
	);

	_main();
}
// Stop rust-analyzer from complaining that this file doesn't have a main() function...
#[cfg(target_os="android")]
fn main() {}

#[cfg(not(target_os="android"))]
fn main() {
	simple_logger::SimpleLogger::new().init().unwrap();

	_main();
}
shader
struct VertexInput {
	[[location(0)]] position: vec2<f32>;
};

struct VertexOutput {
	[[builtin(position)]] clip_position: vec4<f32>;
	[[location(1)]] uv: vec2<f32>;
};



[[stage(vertex)]]
fn vs_main(model: VertexInput) -> VertexOutput {
	var out: VertexOutput;

	out.clip_position = vec4<f32>(model.position.x, model.position.y, 0.0, 1.0);
	out.uv = model.position;

	return out;
}



[[stage(fragment)]]
fn fs_main(in: VertexOutput) -> [[location(0)]] vec4<f32> {
	return vec4<f32>(in.uv.x, in.uv.y, 0.0, 1.0);
}

so i tried to make a simplified version which runs on windows but instantly exits on android

code
use pollster;
use winit::{
	event::*,
	event_loop::{ ControlFlow, EventLoop },
	window::{ WindowBuilder, Window }
};
use wgpu;

#[macro_use]
extern crate log;



struct State {
	surface: wgpu::Surface,
	device: wgpu::Device,
	queue: wgpu::Queue,
	config: wgpu::SurfaceConfiguration,
	window_size: winit::dpi::PhysicalSize<u32>,

	clear_color: wgpu::Color
}

impl State {

	async fn new(window: &Window) -> Self {
		let window_size = window.inner_size();

		let instance = wgpu::Instance::new(wgpu::Backends::all());
		let surface = unsafe { instance.create_surface(&window) };
		let adapter = instance.request_adapter(
			&wgpu::RequestAdapterOptions {
				power_preference: wgpu::PowerPreference::HighPerformance,
				compatible_surface: Some(&surface),
				force_fallback_adapter: false
			}
		).await.unwrap();

		let (device, queue) = adapter.request_device(
			&wgpu::DeviceDescriptor {
				features: wgpu::Features::empty(),
				limits: wgpu::Limits::downlevel_webgl2_defaults().using_resolution(adapter.limits()),
				label: None
			},
			None
		).await.unwrap();

		let config = wgpu::SurfaceConfiguration {
			usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
			format: surface.get_preferred_format(&adapter).unwrap(),
			width: window_size.width,
			height: window_size.height,
			present_mode: wgpu::PresentMode::Fifo
		};
		surface.configure(&device, &config);


		let clear_color = wgpu::Color::GREEN;


		Self {
			surface, device, queue, config, window_size,
			clear_color
		}
	}



	fn render(&mut self) -> Result<(), wgpu::SurfaceError> {
		let output = self.surface.get_current_texture()?;
		let view = output.texture.create_view(&wgpu::TextureViewDescriptor::default());
		let mut encoder = self.device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
			label: Some("Render Encoder")
		});

		{
			let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
				label: Some("Render Pass"),
				color_attachments: &[
					wgpu::RenderPassColorAttachment {
						view: &view,
						resolve_target: None,
						ops: wgpu::Operations {
							load: wgpu::LoadOp::Clear(self.clear_color),
							store: true
						}
					}
				],
				depth_stencil_attachment: None
			});

		}

		self.queue.submit(std::iter::once(encoder.finish()));
		output.present();

		Ok(())
	}



	fn resize(&mut self, new_size: winit::dpi::PhysicalSize<u32>) {
		if new_size.width > 0 && new_size.height > 0 {
			self.window_size = new_size;
			self.config.width = new_size.width;
			self.config.height = new_size.height;
			self.surface.configure(&self.device, &self.config);
		}
	}

}



async fn run(event_loop: EventLoop<()>, window: Window) {

	let mut state = pollster::block_on(State::new(&window));


	event_loop.run(move |event, _, control_flow| match event {
		Event::RedrawRequested(window_id) if window_id == window.id() => {
			match state.render() {
				Ok(_) => {},
				Err(wgpu::SurfaceError::Lost) => state.resize(state.window_size),
				Err(wgpu::SurfaceError::OutOfMemory) => *control_flow = ControlFlow::Exit,
				Err(e) => error!("{:?}", e)
			}
		},
		Event::RedrawEventsCleared => {
			window.request_redraw();
		},
		Event::WindowEvent {
			event: WindowEvent::CloseRequested,
			..
		} => *control_flow = ControlFlow::Exit,
		_ => {}
	});

}



fn _main() {

	let event_loop = EventLoop::new();
	let window = WindowBuilder::new().build(&event_loop).unwrap();

	pollster::block_on(run(event_loop, window));

}



#[cfg(target_os="android")]
#[no_mangle]
extern "C" fn android_main() {
	android_logger::init_once(
		android_logger::Config::default().with_min_level(log::Level::Trace)
	);

	_main();
}

#[cfg(target_os="android")]
fn main() {}

#[cfg(not(target_os="android"))]
fn main() {
	simple_logger::SimpleLogger::new().init().unwrap();

	_main();
}

can you please take a look at the (second) code and tell me what am i missing ?

@rib
Copy link
Collaborator

rib commented May 21, 2022

Probably the first thing to check is whether you have pulled the latest repo changes.

In particular, hopefully you have this patch:

commit 3f1e6ced43879f8294beae95290875c37b85291c
Author: Robert Bragg <[email protected]>
Date:   Fri May 20 01:46:54 2022 +0100

    GameActivity PATH: fix for checking history pointer samples

(without that, there was a bug that would cause lots of crashes)

I pushed a bunch of other changes yesterday related to being able to support NativeActivity and GameActivity in the winit backend which also changed the game_activity input API, but hopefully that shouldn't affect you while you're using Winit here.

@kyoobey
Copy link
Author

kyoobey commented May 21, 2022

i've pulled the latest changes, and the issue still exists

@rib
Copy link
Collaborator

rib commented May 21, 2022

Okey, curious. Can you maybe push your changes to branch on a forked repo perhaps, just to make it easier for me to fetch and try running?

Just in case it's an issue with how vulkan is used within wgpu, I would also recommend downloading the Vulkan validation layer for Android here: https://github.com/KhronosGroup/Vulkan-ValidationLayers/releases (unpack and place under app/src/main/jniLibs/ since that might also give some clues.

Have you seen anything in your adb log that gives a clue about what caused the crash?

@kyoobey
Copy link
Author

kyoobey commented May 21, 2022

https://github.com/tshrpl/agdk-rust-by-rib/tree/issue_2/examples

i have added two apps in the examples folder, these also have a NOTES.md file with the adb logcat output

adding the "Vulkan validation layer" files didn't result in any new errors

@rib
Copy link
Collaborator

rib commented May 21, 2022

Aah, I hadn't taken it in properly when you originally said the issue showed up when you changed the device orientation.

okey, yeah there's probably going to be some fiddly things to consider here.

On Android when you change orientation then by default it will actually completely destroy your current Activity which is going to cause havoc here.

I think there are some config options that can be put in the AndroidManifest that can be used to tell Android not to destroy the activity for an orientation change. I'll have a quick look to see if I can dig up the info for that.

@kyoobey
Copy link
Author

kyoobey commented May 21, 2022

also, the second example only has an empty winit window (without any vertex buffer) but it exits instantly

@rib
Copy link
Collaborator

rib commented May 21, 2022

One thing we can try is adding something like this to the AndroidManifest.xml:

<activity android:name=".MyActivity"
          android:configChanges="orientation|screenSize|screenLayout|keyboardHidden"
          android:label="@string/app_name">

(That should tell Android that the application will handle the config change via onConfigurationChanged instead of by restarting the Activity)

@kyoobey
Copy link
Author

kyoobey commented May 21, 2022

how can i set it to always be in landscape mode, this way i dont have to handle the orientation changes

@rib
Copy link
Collaborator

rib commented May 21, 2022

Ah, okey for your try-2 program this problem is that the app tries to initialize its render state before the Android application has been Resumed.

On Android we have to wait until the application is Resumed before we are given a SurfaceView which is required to be able to create a wgpu surface - and you ideally need a wgpu surface to find a compatible adapter.

This is the main challenge currently with writing Winit applications that are portable between Android and desktop because Winit's API design doesn't naturally lead to portable code and it's very common to find examples that do exactly the same as what you have done here, which is to try and initialize all your graphics state at the start of the program at the same time as creating the event loop.

Somehow you need to defer the creation of your render state and surface until you get a Resumed event and also each time you get a Suspend event you need to remember to drop your wgpu surface and make a new wgpu surface the next time that you get a Resumed event. The structure of agkd-winit-wgpu tries to show an example of this but sorry that I haven't provided much explanation with the example itself.

@rib
Copy link
Collaborator

rib commented May 21, 2022

For some reason the try-1 test doesn't seem to hang for me so I'm not able to confirm whether adding android:configChanges="orientation|screenSize|screenLayout|keyboardHidden" helps.

This is the change i tried in case you want to see if it helps for you though:

diff --git a/examples/agdk-winit-wgpu-try-1/app/src/main/AndroidManifest.xml b/examples/agdk-winit-wgpu-try-1/app/src/main/AndroidManifest.xml
index 42ef1ea..d0f559a 100644
--- a/examples/agdk-winit-wgpu-try-1/app/src/main/AndroidManifest.xml
+++ b/examples/agdk-winit-wgpu-try-1/app/src/main/AndroidManifest.xml
@@ -11,6 +11,7 @@
         android:theme="@style/Theme.RustTemplate">
         <activity
             android:name=".MainActivity"
+            android:configChanges="orientation|screenSize|screenLayout|keyboardHidden"
             android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />

I think there is a way to disable orientation changes entirely and force landscape, but not sure off the top of my head.

@kyoobey
Copy link
Author

kyoobey commented May 21, 2022

android:configChanges="orientation|screenSize|screenLayout|keyboardHidden"
yes, this solved the issue

@rib
Copy link
Collaborator

rib commented May 21, 2022

android:configChanges="orientation|screenSize|screenLayout|keyboardHidden" yes, this solved the issue

Ah, that's good to know - yeah I should update the AndroidManifest.xml for all the examples to include this, since it's not really practical for native applications to deal with their Activity getting destroyed.

@kyoobey
Copy link
Author

kyoobey commented May 22, 2022

hey, i made some changes to try-2 according to what you suggested, it more or less looks the same as your example, but still doesn't work on android, can you take a look, ofcourse if you have some spare time

@rib
Copy link
Collaborator

rib commented May 22, 2022

One thing I just saw from skimming the code is that here:

			Event::Suspended => {
				let mut guard = app.write().unwrap();
				guard.render_state = None;
			},

you should be able to get away with just dropping your surface state, you don't have to drop all your render state on suspend.

I guess that's not the problem you're seeing though.

@rib
Copy link
Collaborator

rib commented May 22, 2022

ah, interesting, it's panicking here:

			Event::RedrawEventsCleared => {
				let mut guard = app.read().unwrap();
				guard.surface_state.as_ref().unwrap().window.request_redraw(); // <--- this line
			},

but I wonder if maybe the winit backend shouldn't be sending RedrawEventsCleared events while you're paused, I'll have a little look

@rib
Copy link
Collaborator

rib commented May 22, 2022

The docs for that event currently say:

    /// Emitted after all `RedrawRequested` events have been processed and control flow is about to
    /// be taken away from the program. If there are no `RedrawRequested` events, it is emitted
    /// immediately after `MainEventsCleared`.
    ///
    /// This event is useful for doing any cleanup or bookkeeping work after all the rendering
    /// tasks have been completed.
    RedrawEventsCleared,

so it seems to suggest that it should be sent even if your app didn't get any redraw requests, but I do wonder if it would maybe better for Winit to not send that if there were no redraw events.

I suppose for now you need to be cautious to consider that RedrawEventsCleared can be called while you Android app is still paused (while you don't have any surface state to unwrap)

@kyoobey
Copy link
Author

kyoobey commented May 22, 2022

okay, i didn't find any other way so i just removed it
one question tho, how did you debug this ? when i run adb logcat -v color | findstr "co.realfit.agdkwinitwgpu" i cant see the actual error, on some stackoverflow thread i read that to filter the logcat output we have to grep the lines with our specific package name

@rib
Copy link
Collaborator

rib commented May 22, 2022

Yeah, it's a pretty big pain getting backtraces for Rust panics on Android atm unfortunately.

I need to write this up somewhere for more people I think but this is what works for me atm:

Firstly if you scrape your adb log after a panic you should be able to find a (useless looking) backtrace like this:

2022-05-22 22:24:41.405 29938-29938/? A/DEBUG:     #00 pc 0000000000021d94  /system/lib64/libc.so (abort+124)
2022-05-22 22:24:41.405 29938-29938/? A/DEBUG:     #01 pc 00000000007353d8  /data/app/co.realfit.agdkwinitwgpu-Y208QFMCuLfnhwH6IsY4EA==/base.apk (offset 0xd02000)
2022-05-22 22:24:41.405 29938-29938/? A/DEBUG:     #02 pc 0000000000732840  /data/app/co.realfit.agdkwinitwgpu-Y208QFMCuLfnhwH6IsY4EA==/base.apk (offset 0xd02000)
2022-05-22 22:24:41.405 29938-29938/? A/DEBUG:     #03 pc 00000000007326e4  /data/app/co.realfit.agdkwinitwgpu-Y208QFMCuLfnhwH6IsY4EA==/base.apk (offset 0xd02000)
2022-05-22 22:24:41.405 29938-29938/? A/DEBUG:     #04 pc 00000000007323d0  /data/app/co.realfit.agdkwinitwgpu-Y208QFMCuLfnhwH6IsY4EA==/base.apk (offset 0xd02000)
2022-05-22 22:24:41.405 29938-29938/? A/DEBUG:     #05 pc 00000000007307ac  /data/app/co.realfit.agdkwinitwgpu-Y208QFMCuLfnhwH6IsY4EA==/base.apk (offset 0xd02000)
2022-05-22 22:24:41.405 29938-29938/? A/DEBUG:     #06 pc 0000000000732144  /data/app/co.realfit.agdkwinitwgpu-Y208QFMCuLfnhwH6IsY4EA==/base.apk (offset 0xd02000)
2022-05-22 22:24:41.405 29938-29938/? A/DEBUG:     #07 pc 000000000004bba8  /data/app/co.realfit.agdkwinitwgpu-Y208QFMCuLfnhwH6IsY4EA==/base.apk (offset 0xd02000)
2022-05-22 22:24:41.405 29938-29938/? A/DEBUG:     #08 pc 000000000004bac4  /data/app/co.realfit.agdkwinitwgpu-Y208QFMCuLfnhwH6IsY4EA==/base.apk (offset 0xd02000)
2022-05-22 22:24:41.405 29938-29938/? A/DEBUG:     #09 pc 000000000005a18c  /data/app/co.realfit.agdkwinitwgpu-Y208QFMCuLfnhwH6IsY4EA==/base.apk (offset 0xd02000)
2022-05-22 22:24:41.405 29938-29938/? A/DEBUG:     #10 pc 0000000000053f14  /data/app/co.realfit.agdkwinitwgpu-Y208QFMCuLfnhwH6IsY4EA==/base.apk (offset 0xd02000)
2022-05-22 22:24:41.405 29938-29938/? A/DEBUG:     #11 pc 0000000000055004  /data/app/co.realfit.agdkwinitwgpu-Y208QFMCuLfnhwH6IsY4EA==/base.apk (offset 0xd02000)
2022-05-22 22:24:41.405 29938-29938/? A/DEBUG:     #12 pc 0000000000056980  /data/app/co.realfit.agdkwinitwgpu-Y208QFMCuLfnhwH6IsY4EA==/base.apk (offset 0xd02000)
2022-05-22 22:24:41.405 29938-29938/? A/DEBUG:     #13 pc 000000000005767c  /data/app/co.realfit.agdkwinitwgpu-Y208QFMCuLfnhwH6IsY4EA==/base.apk (offset 0xd02000)
2022-05-22 22:24:41.405 29938-29938/? A/DEBUG:     #14 pc 00000000000575cc  /data/app/co.realfit.agdkwinitwgpu-Y208QFMCuLfnhwH6IsY4EA==/base.apk (offset 0xd02000)
2022-05-22 22:24:41.405 29938-29938/? A/DEBUG:     #15 pc 000000000005ba84  /data/app/co.realfit.agdkwinitwgpu-Y208QFMCuLfnhwH6IsY4EA==/base.apk (offset 0xd02000)
2022-05-22 22:24:41.405 29938-29938/? A/DEBUG:     #16 pc 0000000000053d28  /data/app/co.realfit.agdkwinitwgpu-Y208QFMCuLfnhwH6IsY4EA==/base.apk (offset 0xd02000)
2022-05-22 22:24:41.405 29938-29938/? A/DEBUG:     #17 pc 0000000000065be4  /data/app/co.realfit.agdkwinitwgpu-Y208QFMCuLfnhwH6IsY4EA==/base.apk (offset 0xd02000)
2022-05-22 22:24:41.405 29938-29938/? A/DEBUG:     #18 pc 000000000005ed3c  /data/app/co.realfit.agdkwinitwgpu-Y208QFMCuLfnhwH6IsY4EA==/base.apk (offset 0xd02000)
2022-05-22 22:24:41.405 29938-29938/? A/DEBUG:     #19 pc 00000000000655e8  /data/app/co.realfit.agdkwinitwgpu-Y208QFMCuLfnhwH6IsY4EA==/base.apk (offset 0xd02000)
2022-05-22 22:24:41.405 29938-29938/? A/DEBUG:     #20 pc 0000000000065654  /data/app/co.realfit.agdkwinitwgpu-Y208QFMCuLfnhwH6IsY4EA==/base.apk (offset 0xd02000) (android_main+60)
2022-05-22 22:24:41.405 29938-29938/? A/DEBUG:     #21 pc 00000000006fc9e0  /data/app/co.realfit.agdkwinitwgpu-Y208QFMCuLfnhwH6IsY4EA==/base.apk (offset 0xd02000) (_rust_glue_entry+348)
2022-05-22 22:24:41.405 29938-29938/? A/DEBUG:     #22 pc 0000000000713010  /data/app/co.realfit.agdkwinitwgpu-Y208QFMCuLfnhwH6IsY4EA==/base.apk (offset 0xd02000)

In my case I grab the backtrace via Android Studio just because that's what I happen to use to run stuff on Android

Note: you won't see the backtrace like this if you filter by the application because it's actually a system process that recognises the process has crashed and automatically outputs a stack trace

I copy that and save it into a file name backtrace.txt

Then I have a python script like this:

#!/usr/bin/python3
import sys
import re

for line in sys.stdin:
    search = re.search('#[0-9]+ +pc +([0-9A-Fa-f]+) +', line)
    if search != None:
        print(search.group(1))

(I actually added this to the git repo by mistake so you can find it here: https://github.com/rib/agdk-rust/blob/main/examples/na-mainloop/android-addr2line.py)

and if run like this:
$ cat backtrace.txt | py ./android-addr2line.py it outputs just the offsets from the backtrace like this:

0000000000021d94
00000000007353d8
0000000000732840
00000000007326e4
00000000007323d0
00000000007307ac
0000000000732144
000000000004bba8
000000000004bac4
000000000005a18c
0000000000053f14
0000000000055004
0000000000056980
000000000005767c
00000000000575cc
000000000005ba84
0000000000053d28
0000000000065be4
000000000005ed3c
00000000000655e8
0000000000065654
00000000006fc9e0
0000000000713010

Those number can then be passed to a tool that comes with the NDK called addr2line

Assuming you have $ANDROID_NDK_ROOT (or _HOME) setup in your environment to point to your NDK then you can find this tool under

$ANDROID_NDK_ROOT/toolchains/llvm/prebuilt/windows-x86_64/bin/aarch64-linux-android-addr2line.exe (assuming you're building for an arm64-v8a / aarch64-linux-android device)

The last thing you need is the libmain.so library that still has your debug symbols (normally the library that's packaged in your apk will have symbols stripped otherwise it would be huge to upload). If you're building libmain.so with cargo ndk then you should still have your unstripped library under target/aarch64-linux-android/debug/libmain.so

So in the end I run a command like:
cat backtrace.txt | py ./android-addr2line.py | $ANDROID_NDK_ROOT/toolchains/aarch64-linux-android-4.9/prebuilt/windows-x86_64/bin/aarch64-linux-android-addr2line.exe' -f -C -e target/aarch64-linux-android/debug/libmain.so

(swap windows-x86_64 for linux-x86_64 if on Linux)

and I get a (not very nicely formatted) backtrace with filenames, function names and symbols like this:

??
??:0
std::sys::unix::abort_internal::hf2e00ab422f03a90
/rustc/7737e0b5c4103216d6fd8cf941b7ab9bdbaace7c//library/std/src/sys/unix/mod.rs:259
rust_panic
/rustc/7737e0b5c4103216d6fd8cf941b7ab9bdbaace7c//library/std/src/panicking.rs:748
std::panicking::rust_panic_with_hook::hcdc16bc1d6a90d9f
/rustc/7737e0b5c4103216d6fd8cf941b7ab9bdbaace7c//library/std/src/panicking.rs:716
std::panicking::begin_panic_handler::_$u7b$$u7b$closure$u7d$$u7d$::h80c5bec89bedcc17
/rustc/7737e0b5c4103216d6fd8cf941b7ab9bdbaace7c//library/std/src/panicking.rs:586
std::sys_common::backtrace::__rust_end_short_backtrace::hbdfac0e0f8306365
/rustc/7737e0b5c4103216d6fd8cf941b7ab9bdbaace7c//library/std/src/sys_common/backtrace.rs:138
rust_begin_unwind
/rustc/7737e0b5c4103216d6fd8cf941b7ab9bdbaace7c//library/std/src/panicking.rs:584
core::panicking::panic_fmt::h28a757e2227ef488
/rustc/7737e0b5c4103216d6fd8cf941b7ab9bdbaace7c//library/core/src/panicking.rs:143
core::panicking::panic::h57be532174f5074a
/rustc/7737e0b5c4103216d6fd8cf941b7ab9bdbaace7c//library/core/src/panicking.rs:48
core::option::Option$LT$T$GT$::unwrap::h1ec4460eb4fea2cd
/rustc/7737e0b5c4103216d6fd8cf941b7ab9bdbaace7c/library/core/src/option.rs:752
main::run::_$u7b$$u7b$closure$u7d$$u7d$::_$u7b$$u7b$closure$u7d$$u7d$::hba192c7db6fafddb
C:\Users\Robert\src\agdk-rust\examples\agdk-winit-wgpu-try-2/src/lib.rs:354
winit::platform_impl::platform::sticky_exit_callback::h7f66ebcb780071dc
C:\Users\Robert\.cargo\git\checkouts\winit-0cced2578ff7cf1c\8adc3fe\src\platform_impl\android/mod.rs:315
winit::platform_impl::platform::EventLoop$LT$T$GT$::single_iteration::h73bac33729e66e46
C:\Users\Robert\.cargo\git\checkouts\winit-0cced2578ff7cf1c\8adc3fe\src\platform_impl\android/mod.rs:639
winit::platform_impl::platform::EventLoop$LT$T$GT$::run_return::hc812f7da42ada3e6
C:\Users\Robert\.cargo\git\checkouts\winit-0cced2578ff7cf1c\8adc3fe\src\platform_impl\android/mod.rs:707
winit::platform_impl::platform::EventLoop$LT$T$GT$::run::h3541b881858ac159
C:\Users\Robert\.cargo\git\checkouts\winit-0cced2578ff7cf1c\8adc3fe\src\platform_impl\android/mod.rs:694
winit::event_loop::EventLoop$LT$T$GT$::run::h3cd4361c9d4055ad
C:\Users\Robert\.cargo\git\checkouts\winit-0cced2578ff7cf1c\8adc3fe\src/event_loop.rs:250
main::run::_$u7b$$u7b$closure$u7d$$u7d$::he5af3d211448ad76
C:\Users\Robert\src\agdk-rust\examples\agdk-winit-wgpu-try-2/src/lib.rs:323
_$LT$core..future..from_generator..GenFuture$LT$T$GT$$u20$as$u20$core..future..future..Future$GT$::poll::h4a12725a0031b5ea
/rustc/7737e0b5c4103216d6fd8cf941b7ab9bdbaace7c/library/core/src/future/mod.rs:84
pollster::block_on::he5bc6fb80e91a02f
C:\Users\Robert\.cargo\registry\src\github.com-1ecc6299db9ec823\pollster-0.2.5\src/lib.rs:125
main::_main::h071149abf58acad5
C:\Users\Robert\src\agdk-rust\examples\agdk-winit-wgpu-try-2/src/lib.rs:375
android_main
C:\Users\Robert\src\agdk-rust\examples\agdk-winit-wgpu-try-2/src/lib.rs:388
_rust_glue_entry
C:\Users\Robert\.cargo\git\checkouts\agdk-rust-8b5a94340738d9ee\b2a675c\game-activity\src/lib.rs:750
android_app_entry
C:\Users\Robert\.cargo\git\checkouts\agdk-rust-8b5a94340738d9ee\b2a675c\game-activity/csrc/game-activity/native_app_glue/android_native_app_glue.c:208

So mainly in summary:

  1. save a copy of the backtrace to backtrace.txt
  2. run: cat backtrace.txt | py ./android-addr2line.py | $ANDROID_NDK_ROOT/toolchains/aarch64-linux-android-4.9/prebuilt/windows-x86_64/bin/aarch64-linux-android-addr2line.exe' -f -C -e target/aarch64-linux-android/debug/libmain.so

Hopefully that procedure can work for you too.

At some point it could be good to write a smarter python script that would probably deal with running addr2line and also format the output a bit better too.

@rib
Copy link
Collaborator

rib commented May 22, 2022

Just to add: in terms of filtering the adb log, I guess you could filter by the error level to be able to get the backtrace from the system but avoid most other spam. (the stack trace is logged by Android as an error)

@kyoobey
Copy link
Author

kyoobey commented May 23, 2022

i have made a small utility to make debugging a bit easier for myself
one issue i have is that sometimes the addr2line command doesn't show the line which is caussing the error, i have to open the app multiple time to get a sort of good stacktrace
anyway, here is the code: https://github.com/tshrpl/agdk-rust-by-rib/tree/issue_2/debugger
run it like this: debugger -v 3 -r to only get the rust errors, or debugger -v 3 to get all the output that has the package name
you need to be at the project root i.e. the directory where target lives

@rib
Copy link
Collaborator

rib commented May 24, 2022

ah, cool I'll have to give your tool a try next time I'm dealing with a panic - thanks for sharing!

have you ruled out some kind of initial bug with the input scraping that maybe messes with the offsets sometimes so you then wouldn't always get good results from addr2line? do you maybe also see the same issue when you capture the backtrace with the more manual steps?

I might have just been lucky but so far I haven't seen addr2line be unreliable - mainly just cumbersome and hard to read.

@kyoobey
Copy link
Author

kyoobey commented May 24, 2022

i get the bad traces even when doing everything manually so it's not the tool

i just noticed now that your stacktrace has different formatting than mine so you may have to change REGEX_WHOLE to match yours

@kyoobey
Copy link
Author

kyoobey commented May 25, 2022

strange, i observed that only 1 frame is drawn in my example try-2 look here

@rib
Copy link
Collaborator

rib commented May 25, 2022

Curious.

It looks like you have also commented out the code that requests a redraw?: https://github.com/tshrpl/agdk-rust-by-rib/blob/61536b5f677d74d6c936a7fa936285069a98d3d8/examples/agdk-winit-wgpu-try-2/src/lib.rs#L528

@rib
Copy link
Collaborator

rib commented May 25, 2022

Ah, right you said you just commented out that code to avoid the panic. I think maybe you either need to make it first check if you have a surface and still request a redraw, or else just request the redraw at the end of each redraw (i.e end of RedrawRequested code)

@kyoobey
Copy link
Author

kyoobey commented May 25, 2022

Event::RedrawRequested(_) => {
	(..)
	guard.surface_state.as_ref().unwrap().window.request_redraw();
}

adding the above did nothing, but adding an if check inside RedrawEventsCleared did the trick

Event::RedrawEventsCleared => {
	let mut guard = app.read().unwrap();
	if let Some(surface_state) = &guard.surface_state {
		guard.surface_state.as_ref().unwrap().window.request_redraw();
	}
}

@rib
Copy link
Collaborator

rib commented May 25, 2022

ah, that's interesting. I should have a look at why it didn't work to request the redraw as part of the RedrawRequested event handling.

@rib rib changed the title android build instantly exits while windows build runs just fine winit backend: window.request_redraw() within RedrawRequested not working Jul 4, 2022
@rib rib changed the title winit backend: window.request_redraw() within RedrawRequested not working android build instantly exits while windows build runs just fine winit backend Jul 4, 2022
@rib
Copy link
Collaborator

rib commented Jul 4, 2022

Sorry for the noise here. I haven't had a chance to look at this specifically yet, though I did do a bunch of work to create a common android-activity crate / API that hopefully you've seen (including an updated winit backend here: https://github.com/rib/winit/tree/android-activity)

I'm going to close this issue since I think we resolved the original problem, and I'll open a new issue for the last observation about request_redraw() not working as expected which I'll also link back to this issue for context.

For now the updated winit backend almost certainly still has this same issue, since it hasn't really changed much, but hopefully I can investigate this soonish

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants