-
-
Notifications
You must be signed in to change notification settings - Fork 3.8k
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
Add screenshot api #7163
Add screenshot api #7163
Conversation
fddfbcf
to
7311b14
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks pretty good over all. Can we add an example showcasing this? Right now it's a little hard to test and verify the work.
@@ -128,6 +138,11 @@ fn extract_windows( | |||
for closed_window in closed.iter() { | |||
extracted_windows.remove(&closed_window.id); | |||
} | |||
for (window, screenshot_func) in screenshot_manager.callbacks.lock().drain() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably should use try_lock
here to avoid a deadlock if a screenshot is being taken at the same time? Although I guess the bevy scheduler prevents that from happening here..
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, this can't really happen because callbacks
is pub(crate)
and this is the singular callsite where it's called. Even if somehow a user ended up with two of these systems, one would simply take the lock and they'd run in series. Only one lock isn't enough to cause a deadlock.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nile, your comment here would make a great comment for this line of code.
@@ -196,6 +197,18 @@ impl Image { | |||
self.data, | |||
) | |||
.map(DynamicImage::ImageRgba8), | |||
TextureFormat::Bgra8UnormSrgb => ImageBuffer::from_raw( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we introduce the TextureFormat::Bgra8UnormSrgb
here? Is there a reason bgra
might be preferred over rgba
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seconding the confusion about why this is part of this PR. I don't mind it, just a bit confused.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
wgpu apparently quite likes this format for swapchain textures - this was necessary to get the save to disk functionality working on my machine.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Interesting. Can you please leave a comment here to that effect then?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Useful functionality, generally looks solid. I'd like to see a top-level example before this gets merged.
Code quality and docs otherwise LGTM. I'll defer to rendering experts on the implementation strategy.
crashes for me on macOS:
|
Changed the code to ensure a panic like the one @mockersf encountered is no longer possible, but it's a lot more code now. |
@@ -33,6 +33,9 @@ pub fn render_system(world: &mut World) { | |||
render_device.clone(), // TODO: is this clone really necessary? | |||
&render_queue.0, | |||
world, | |||
|encoder| { | |||
crate::view::screenshot::submit_screenshot_commands(world, encoder); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a reason why you didn't just import the screenshot module?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not really, other than not wanting to pollute the namespace i guess.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fair, I don't mind either way. I'd prefer having the screenshot
module in scope just to not have crate::view
, but really it's up to you, really not a big deal.
Works for me now! Thanks |
Crashes in WASM too:
|
This seems to be a result of the brightness values being stored in the alpha channel - the two images are once again identical when alpha is disabled. |
This needs review mostly, especially from folks with rendering expertise. This ended up trickier than I would have expected. Community review counts, so please feel free to go over this carefully and approve it if you think it's ready :) |
the PR also needs to be updated for #7681 I think |
What's the current status of this PR? |
Does this snippet work without the WinitPlugin ? |
@TheRawMeatball I think there has been an issue with your merge |
2b5edfa
to
1da0a8a
Compare
What's the purpose of the render pass? Is it to avoid putting |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I feel like there should probably be a bit more docs in screenshot.rs, but in general the code works and I haven't seen anything obviously wrong.
I don't fully like the idea of passing a finalizer callback to the graph runner, but I'm not sure how to avoid that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is a reasonable solution to the specific problem of taking screenshots of a window. But I don't love how "hacked in" it is. I think this impl points to a fundamental "render output feedback" problem that should (ultimately) be solved generically. The screenshot api is pretty opinionated and isn't a full solve to the problem. People should be able to build similar abstractions on their own.
I see a number of paths forward:
- Continue down the
finalizer
path, but just modularize it (ex let people add arbitrary functions to the process via some new abstraction) - Build this into our graph abstraction ... this would mean screenshotting would not be a global feature, but rather one that exists in specific graphs ... this seems ok to me. We could build common "feedback" nodes (including a screenshot node). It seems to me like this PR could have already been expressed that way, rather than adding the
finalizer
. Functionallysubmit_screenshot_commands
is really equivalent to a "render graph node that exists in every graph and always runs last". This doesn't solve the "window / swap chain / image management stuff" though, which is definitely the bigger challenge. It feels like a chunk of this could/should be expressed as some generic graph node that says "at this point in the graph, write this COPY_SRC texture view to a buffer of appropriate size and (maybe optionally) create an image from it". That leaves the problem of "writing the swap chain to a COPY_SRC texture" to be solved on its own in whatever way we deem "best" / "most modular"/ whatever we choose to optimize for. - Give up on reading swap chain outputs and instead read
ViewTarget::maint_target_textures
outputs in render graph nodes. Seems like we could make those COPY_SRC (maybe in an opt-in fashion when screenshots are enabled). This does ignore the upscaling step though (which does seem like a problem).
That being said, creating a generic abstraction is not a blocker (and I understand there is a fair amount of implementation nuance here when it comes to swapchain access). This is totally fine for now.
# Objective - Enable taking a screenshot in wasm - Followup on #7163 ## Solution - Create a blob from the image data, generate a url to that blob, add an `a` element to the document linking to that url, click on that element, then revoke the url - This will automatically trigger a download of the screenshot file in the browser
Still could this work without a window? It seems some questions in the comments are unanswered. |
Fixes #1207
Objective
Right now, it's impossible to capture a screenshot of the entire window without forking bevy. This is because
Solution
Changelog
ScreenshotManager
resource with two functions,take_screenshot
andsave_screenshot_to_disk