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

desktop: Add screenshot functionality to the desktop app #13719

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions desktop/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ futures-lite = "1.13.0"
async-io = "2.0.0"
async-net = "1.8.0"
async-channel = "1.9.0"
image = { version = "0.24.7", default-features = false, features = ["png"] }

# Deliberately held back to match tracy client used by profiling crate
tracing-tracy = { version = "=0.10.2", optional = true }
Expand Down
1 change: 1 addition & 0 deletions desktop/assets/texts/en-US/main_menu.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,5 @@ debug-menu-open-stage = View Stage Info
debug-menu-open-movie = View Movie
debug-menu-open-movie-list = Show Known Movies
debug-menu-search-display-objects = Search Display Objects...
debug-menu-take-screenshot = Take Screenshot

13 changes: 13 additions & 0 deletions desktop/src/gui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ pub struct RuffleGui {
default_player_options: PlayerOptions,
currently_opened: Option<(Url, PlayerOptions)>,
was_suspended_before_debug: bool,
taking_screenshot: bool,
}

impl RuffleGui {
Expand All @@ -96,6 +97,7 @@ impl RuffleGui {
volume_controls: VolumeControls::new(false, default_player_options.volume * 100.0),
is_open_dialog_visible: false,
was_suspended_before_debug: false,
taking_screenshot: false,

context_menu: vec![],
open_dialog: OpenDialog::new(
Expand Down Expand Up @@ -193,6 +195,13 @@ impl RuffleGui {
player.set_volume(self.volume_controls.get_volume());
}

fn is_taking_screenshot(&mut self) -> bool {
let taking_screenshot = self.taking_screenshot;
self.taking_screenshot = false;

taking_screenshot
}

/// Renders the main menu bar at the top of the window.
fn main_menu_bar(&mut self, egui_ctx: &egui::Context, mut player: Option<&mut Player>) {
egui::TopBottomPanel::top("menu_bar").show(egui_ctx, |ui| {
Expand Down Expand Up @@ -301,6 +310,10 @@ impl RuffleGui {
player.debug_ui().queue_message(DebugMessage::SearchForDisplayObject);
}
}
if Button::new(text(&self.locale, "debug-menu-take-screenshot")).ui(ui).clicked() {
ui.close_menu();
self.taking_screenshot = true;
}
});
});
menu::menu_button(ui, text(&self.locale, "help-menu"), |ui| {
Expand Down
81 changes: 79 additions & 2 deletions desktop/src/gui/controller.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,11 @@ use fontdb::{Database, Family, Query, Source};
use ruffle_core::Player;
use ruffle_render_wgpu::backend::{request_adapter_and_device, WgpuRenderBackend};
use ruffle_render_wgpu::descriptors::Descriptors;
use ruffle_render_wgpu::utils::{format_list, get_backend_names};
use ruffle_render_wgpu::target::RenderTarget;
use ruffle_render_wgpu::utils::{format_list, get_backend_names, BufferDimensions};
use std::rc::Rc;
use std::sync::{Arc, MutexGuard};
use std::time::{Duration, Instant};
use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
use unic_langid::LanguageIdentifier;
use url::Url;
use winit::dpi::PhysicalSize;
Expand Down Expand Up @@ -192,6 +193,7 @@ impl GuiController {
}

pub fn render(&mut self, mut player: Option<MutexGuard<Player>>) {
let taking_screenshot = self.gui.is_taking_screenshot();
let surface_texture = self
.surface
.get_current_texture()
Expand Down Expand Up @@ -271,6 +273,53 @@ impl GuiController {
None
};

let screenshot = if let Some(movie_view) = movie_view {
if taking_screenshot {
let size = wgpu::Extent3d {
width: movie_view.width(),
height: movie_view.height(),
depth_or_array_layers: 1,
};

// Calculate the right buffer size for a given texture, accounting for texture alignment / stride
let buffer_dimensions = BufferDimensions::new(
movie_view.width() as usize,
movie_view.height() as usize,
);

// The final buffer that will hold our screenshot. It needs to be copy_dst ("allowed to be copied into") and map_read ("the cpu can read the contents")
let buffer = self
.descriptors
.device
.create_buffer(&wgpu::BufferDescriptor {
label: Some("Screenshot buffer"),
size: buffer_dimensions.size(),
usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ,
mapped_at_creation: false,
});

// Tell the gpu to copy our movie texture into this new buffer
encoder.copy_texture_to_buffer(
movie_view.texture().as_image_copy(),
wgpu::ImageCopyBuffer {
buffer: &buffer,
layout: wgpu::ImageDataLayout {
offset: 0,
bytes_per_row: Some(buffer_dimensions.padded_bytes_per_row),
rows_per_image: None,
},
},
size,
);

Some((buffer_dimensions, buffer, size))
} else {
None
}
} else {
None
};

{
let surface_view = surface_texture.texture.create_view(&Default::default());

Expand Down Expand Up @@ -301,6 +350,34 @@ impl GuiController {

command_buffers.push(encoder.finish());
self.descriptors.queue.submit(command_buffers);

if let Some((buffer_dimensions, buffer, size)) = screenshot {
// Read back the buffer from the gpu onto the cpu, and copies it to a png
let image = ruffle_render_wgpu::utils::buffer_to_image(
&self.descriptors.device,
&buffer,
&buffer_dimensions,
None,
size,
);

let start = SystemTime::now();
let unix_timestamp = start
.duration_since(UNIX_EPOCH)
.expect("Time went backwards");

match image.save(
"ruffle_screenshots/".to_owned() + &unix_timestamp.as_millis().to_string() + ".png",
) {
Ok(()) => {
tracing::info!("Screenshot saved to {:?}.png", unix_timestamp);
}
Err(err) => {
tracing::error!("Couldn't capture screenshot: {err}");
}
}
}

surface_texture.present();
}

Expand Down
8 changes: 7 additions & 1 deletion desktop/src/gui/movie.rs
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,9 @@ impl MovieView {
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::Rgba8Unorm,
usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
usage: wgpu::TextureUsages::RENDER_ATTACHMENT
| wgpu::TextureUsages::TEXTURE_BINDING
| wgpu::TextureUsages::COPY_SRC,
view_formats: &[],
});
let view = texture.create_view(&Default::default());
Expand Down Expand Up @@ -208,6 +210,10 @@ impl MovieView {
render_pass.set_vertex_buffer(0, renderer.vertices.slice(..));
render_pass.draw(0..6, 0..1);
}

pub fn texture(&self) -> &wgpu::Texture {
&self.texture
}
}

impl RenderTarget for MovieView {
Expand Down