Skip to content

Commit

Permalink
Merge branch 'emilk:master' into patch7
Browse files Browse the repository at this point in the history
  • Loading branch information
rustbasic authored Dec 13, 2024
2 parents 78d6bfa + ba060a2 commit 515bf9c
Show file tree
Hide file tree
Showing 29 changed files with 880 additions and 329 deletions.
38 changes: 38 additions & 0 deletions Cargo.lock
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,23 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"

[[package]]
name = "android_log-sys"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ecc8056bf6ab9892dcd53216c83d1597487d7dacac16c8df6b877d127df9937"

[[package]]
name = "android_logger"
version = "0.14.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05b07e8e73d720a1f2e4b6014766e6039fd2e96a4fa44e2a78d0e1fa2ff49826"
dependencies = [
"android_log-sys",
"env_filter",
"log",
]

[[package]]
name = "android_system_properties"
version = "0.1.5"
Expand Down Expand Up @@ -1473,6 +1490,16 @@ dependencies = [
"syn",
]

[[package]]
name = "env_filter"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4f2c92ceda6ceec50f43169f9ee8424fe2db276791afde7b2cd8bc084cb376ab"
dependencies = [
"log",
"regex",
]

[[package]]
name = "env_logger"
version = "0.10.2"
Expand Down Expand Up @@ -1935,6 +1962,17 @@ dependencies = [
"allocator-api2",
]

[[package]]
name = "hello_android"
version = "0.1.0"
dependencies = [
"android_logger",
"eframe",
"egui_extras",
"log",
"winit",
]

[[package]]
name = "hello_world"
version = "0.1.0"
Expand Down
16 changes: 16 additions & 0 deletions crates/eframe/src/epi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,16 @@ pub struct NativeOptions {
///
/// Defaults to true.
pub dithering: bool,

/// Android application for `winit`'s event loop.
///
/// This value is required on Android to correctly create the event loop. See
/// [`EventLoopBuilder::build`] and [`with_android_app`] for details.
///
/// [`EventLoopBuilder::build`]: winit::event_loop::EventLoopBuilder::build
/// [`with_android_app`]: winit::platform::android::EventLoopBuilderExtAndroid::with_android_app
#[cfg(target_os = "android")]
pub android_app: Option<winit::platform::android::activity::AndroidApp>,
}

#[cfg(not(target_arch = "wasm32"))]
Expand All @@ -383,6 +393,9 @@ impl Clone for NativeOptions {

persistence_path: self.persistence_path.clone(),

#[cfg(target_os = "android")]
android_app: self.android_app.clone(),

..*self
}
}
Expand Down Expand Up @@ -424,6 +437,9 @@ impl Default for NativeOptions {
persistence_path: None,

dithering: true,

#[cfg(target_os = "android")]
android_app: None,
}
}
}
Expand Down
11 changes: 11 additions & 0 deletions crates/eframe/src/native/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,20 @@ use crate::{

// ----------------------------------------------------------------------------
fn create_event_loop(native_options: &mut epi::NativeOptions) -> Result<EventLoop<UserEvent>> {
#[cfg(target_os = "android")]
use winit::platform::android::EventLoopBuilderExtAndroid as _;

crate::profile_function!();
let mut builder = winit::event_loop::EventLoop::with_user_event();

#[cfg(target_os = "android")]
let mut builder =
builder.with_android_app(native_options.android_app.take().ok_or_else(|| {
crate::Error::AppCreation(Box::from(
"`NativeOptions` is missing required `android_app`",
))
})?);

if let Some(hook) = std::mem::take(&mut native_options.event_loop_builder) {
hook(&mut builder);
}
Expand Down
32 changes: 6 additions & 26 deletions crates/eframe/src/native/wgpu_integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,7 @@ impl<'app> WgpuWinitApp<'app> {

#[allow(unsafe_code, unused_mut, unused_unsafe)]
let mut painter = egui_wgpu::winit::Painter::new(
egui_ctx.clone(),
self.native_options.wgpu_options.clone(),
self.native_options.multisampling.max(1) as _,
egui_wgpu::depth_format_from_bits(
Expand Down Expand Up @@ -593,6 +594,8 @@ impl<'app> WgpuWinitRunning<'app> {
.map(|(id, viewport)| (*id, viewport.info.clone()))
.collect();

painter.handle_screenshots(&mut raw_input.events);

(viewport_ui_cb, raw_input)
};

Expand Down Expand Up @@ -652,37 +655,14 @@ impl<'app> WgpuWinitRunning<'app> {
true
}
});
let screenshot_requested = !screenshot_commands.is_empty();
let (vsync_secs, screenshot) = painter.paint_and_update_textures(
let vsync_secs = painter.paint_and_update_textures(
viewport_id,
pixels_per_point,
app.clear_color(&egui_ctx.style().visuals),
&clipped_primitives,
&textures_delta,
screenshot_requested,
screenshot_commands,
);
match (screenshot_requested, screenshot) {
(false, None) => {}
(true, Some(screenshot)) => {
let screenshot = Arc::new(screenshot);
for user_data in screenshot_commands {
egui_winit
.egui_input_mut()
.events
.push(egui::Event::Screenshot {
viewport_id,
user_data,
image: screenshot.clone(),
});
}
}
(true, None) => {
log::error!("Bug in egui_wgpu: screenshot requested, but no screenshot was taken");
}
(false, Some(_)) => {
log::warn!("Bug in egui_wgpu: Got screenshot without requesting it");
}
}

for action in viewport.actions_requested.drain() {
match action {
Expand Down Expand Up @@ -1024,7 +1004,7 @@ fn render_immediate_viewport(
[0.0, 0.0, 0.0, 0.0],
&clipped_primitives,
&textures_delta,
false,
vec![],
);

egui_winit.handle_platform_output(window, platform_output);
Expand Down
33 changes: 24 additions & 9 deletions crates/eframe/src/web/app_runner.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use egui::TexturesDelta;
use egui::{TexturesDelta, UserData, ViewportCommand};
use std::mem;

use crate::{epi, App};

Expand All @@ -16,6 +17,9 @@ pub struct AppRunner {
last_save_time: f64,
pub(crate) text_agent: TextAgent,

// If not empty, the painter should capture the next frame
screenshot_commands: Vec<UserData>,

// Output for the last run:
textures_delta: TexturesDelta,
clipped_primitives: Option<Vec<egui::ClippedPrimitive>>,
Expand All @@ -36,7 +40,8 @@ impl AppRunner {
app_creator: epi::AppCreator<'static>,
text_agent: TextAgent,
) -> Result<Self, String> {
let painter = super::ActiveWebPainter::new(canvas, &web_options).await?;
let egui_ctx = egui::Context::default();
let painter = super::ActiveWebPainter::new(egui_ctx.clone(), canvas, &web_options).await?;

let info = epi::IntegrationInfo {
web_info: epi::WebInfo {
Expand All @@ -47,7 +52,6 @@ impl AppRunner {
};
let storage = LocalStorage::default();

let egui_ctx = egui::Context::default();
egui_ctx.set_os(egui::os::OperatingSystem::from_user_agent(
&super::user_agent().unwrap_or_default(),
));
Expand Down Expand Up @@ -110,6 +114,7 @@ impl AppRunner {
needs_repaint,
last_save_time: now_sec(),
text_agent,
screenshot_commands: vec![],
textures_delta: Default::default(),
clipped_primitives: None,
};
Expand Down Expand Up @@ -205,6 +210,8 @@ impl AppRunner {
pub fn logic(&mut self) {
// We sometimes miss blur/focus events due to the text agent, so let's just poll each frame:
self.update_focus();
// We might have received a screenshot
self.painter.handle_screenshots(&mut self.input.raw.events);

let canvas_size = super::canvas_size_in_points(self.canvas(), self.egui_ctx());
let mut raw_input = self.input.new_frame(canvas_size);
Expand All @@ -225,12 +232,19 @@ impl AppRunner {
if viewport_output.len() > 1 {
log::warn!("Multiple viewports not yet supported on the web");
}
for viewport_output in viewport_output.values() {
for command in &viewport_output.commands {
// TODO(emilk): handle some of the commands
log::warn!(
"Unhandled egui viewport command: {command:?} - not implemented in web backend"
);
for (_viewport_id, viewport_output) in viewport_output {
for command in viewport_output.commands {
match command {
ViewportCommand::Screenshot(user_data) => {
self.screenshot_commands.push(user_data);
}
_ => {
// TODO(emilk): handle some of the commands
log::warn!(
"Unhandled egui viewport command: {command:?} - not implemented in web backend"
);
}
}
}
}

Expand All @@ -250,6 +264,7 @@ impl AppRunner {
&clipped_primitives,
self.egui_ctx.pixels_per_point(),
&textures_delta,
mem::take(&mut self.screenshot_commands),
) {
log::error!("Failed to paint: {}", super::string_from_js_value(&err));
}
Expand Down
6 changes: 6 additions & 0 deletions crates/eframe/src/web/web_painter.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use egui::{Event, UserData};
use wasm_bindgen::JsValue;

/// Renderer for a browser canvas.
Expand All @@ -16,14 +17,19 @@ pub(crate) trait WebPainter {
fn max_texture_side(&self) -> usize;

/// Update all internal textures and paint gui.
/// When `capture` isn't empty, the rendered screen should be captured.
/// Once the screenshot is ready, the screenshot should be returned via [`Self::handle_screenshots`].
fn paint_and_update_textures(
&mut self,
clear_color: [f32; 4],
clipped_primitives: &[egui::ClippedPrimitive],
pixels_per_point: f32,
textures_delta: &egui::TexturesDelta,
capture: Vec<UserData>,
) -> Result<(), JsValue>;

fn handle_screenshots(&mut self, events: &mut Vec<Event>);

/// Destroy all resources.
fn destroy(&mut self);
}
37 changes: 33 additions & 4 deletions crates/eframe/src/web/web_painter_glow.rs
Original file line number Diff line number Diff line change
@@ -1,24 +1,30 @@
use egui::{Event, UserData, ViewportId};
use egui_glow::glow;
use std::sync::Arc;
use wasm_bindgen::JsCast;
use wasm_bindgen::JsValue;
use web_sys::HtmlCanvasElement;

use egui_glow::glow;

use crate::{WebGlContextOption, WebOptions};

use super::web_painter::WebPainter;

pub(crate) struct WebPainterGlow {
canvas: HtmlCanvasElement,
painter: egui_glow::Painter,
screenshots: Vec<(egui::ColorImage, Vec<UserData>)>,
}

impl WebPainterGlow {
pub fn gl(&self) -> &std::sync::Arc<glow::Context> {
self.painter.gl()
}

pub async fn new(canvas: HtmlCanvasElement, options: &WebOptions) -> Result<Self, String> {
pub async fn new(
_ctx: egui::Context,
canvas: HtmlCanvasElement,
options: &WebOptions,
) -> Result<Self, String> {
let (gl, shader_prefix) =
init_glow_context_from_canvas(&canvas, options.webgl_context_option)?;
#[allow(clippy::arc_with_non_send_sync)]
Expand All @@ -27,7 +33,11 @@ impl WebPainterGlow {
let painter = egui_glow::Painter::new(gl, shader_prefix, None, options.dithering)
.map_err(|err| format!("Error starting glow painter: {err}"))?;

Ok(Self { canvas, painter })
Ok(Self {
canvas,
painter,
screenshots: Vec::new(),
})
}
}

Expand All @@ -46,6 +56,7 @@ impl WebPainter for WebPainterGlow {
clipped_primitives: &[egui::ClippedPrimitive],
pixels_per_point: f32,
textures_delta: &egui::TexturesDelta,
capture: Vec<UserData>,
) -> Result<(), JsValue> {
let canvas_dimension = [self.canvas.width(), self.canvas.height()];

Expand All @@ -57,6 +68,11 @@ impl WebPainter for WebPainterGlow {
self.painter
.paint_primitives(canvas_dimension, pixels_per_point, clipped_primitives);

if !capture.is_empty() {
let image = self.painter.read_screen_rgba(canvas_dimension);
self.screenshots.push((image, capture));
}

for &id in &textures_delta.free {
self.painter.free_texture(id);
}
Expand All @@ -67,6 +83,19 @@ impl WebPainter for WebPainterGlow {
fn destroy(&mut self) {
self.painter.destroy();
}

fn handle_screenshots(&mut self, events: &mut Vec<Event>) {
for (image, data) in self.screenshots.drain(..) {
let image = Arc::new(image);
for data in data {
events.push(Event::Screenshot {
viewport_id: ViewportId::default(),
image: image.clone(),
user_data: data,
});
}
}
}
}

/// Returns glow context and shader prefix.
Expand Down
Loading

0 comments on commit 515bf9c

Please sign in to comment.