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

Enable creating "virtual" windows without corresponding OS window #6256

Closed
wants to merge 5 commits into from

Conversation

MDeiml
Copy link
Contributor

@MDeiml MDeiml commented Oct 14, 2022

Objective

  • Enable creating windows without an associated OS window
    • This will be useful for embedding games into other applications like editors
    • It will also enable games to be run headless without much change for the specific game

Solution

  • Make the raw_window_handle attribute an Option
  • Ignore windows with a raw_window_handle of None in prepare_windows
    • Instead, the creator of a virtual window is responsible for creating a render target texture for the window and injecting it into the ExtractedWindow

Changelog

  • Added a new_virtual method to Window, which does not take a RawWindowHandleWrapper
  • Changed Window::raw_window_handle to return None if it was created using new_virtual
  • Changed prepare_windows to ignore windows that were created using new_virtual

@alice-i-cecile alice-i-cecile added A-Windowing Platform-agnostic interface layer to run your app in C-Usability A simple quality-of-life change that makes Bevy easier to use labels Oct 14, 2022
@alice-i-cecile
Copy link
Member

Related to #6114, which makes this field an Option.

#6147 seems to be tackling similar problems as well. Ping @anlumo and @targrub; I'd like to talk through how all of these PRs inter-relate and how we should move forward.

@MDeiml
Copy link
Contributor Author

MDeiml commented Oct 14, 2022

Oh, I missed #6114, which seems to make almost the same changes with mainly different intention / language around them. It also adds a test example and changes the RenderApp to only run if there is a primary window present (which I'm not sure is the right behavior).

I'd suggest rebasing @targrub's changes on my implementation. My pull request seems to be more minimal and be more open to different use cases. #6114 for example says

/// During normal use, this can be safely unwrapped; the value should only be [`None`] when synthetically constructed for tests.

which rules out the use cases I am suggesting. Of course we should probably mention the use of "virtual windows" for testing and @targrub's test example is also a good addition.

I'm not sure how #6147 fits in. The windows created there do seem to always have a RawWindowHandleWrapper, but it looks like this handle is not used. Maybe it makes sense to change the handle into an enum

enum AbstractWindowHandle {
    Raw(RawWindowHandleWrapper),
    Virtual,
    Canvas(Canvas),
}

instead.

@alice-i-cecile
Copy link
Member

Sounds good; I'm on board with that course of action.

@MDeiml
Copy link
Contributor Author

MDeiml commented Oct 14, 2022

I rewrote my code to use an enum instead of Option. I did not include a Canvas variant, but it should be easy for @anlumo to adapt their PR to use the same enum.

@anlumo
Copy link

anlumo commented Oct 14, 2022

I did not include a Canvas variant, but it should be easy for @anlumo to adapt their PR to use the same enum.

I've updated my PR to use your enum instead.

@targrub
Copy link
Contributor

targrub commented Oct 15, 2022

Oh, I missed #6114, which seems to make almost the same changes with mainly different intention / language around them. It also adds a test example and changes the RenderApp to only run if there is a primary window present (which I'm not sure is the right behavior).

I've fixed that behavior in a subsequent commit.

I'd suggest rebasing @targrub's changes on my implementation. My pull request seems to be more minimal and be more open to different use cases. #6114 for example says

I'm not familiar with that phrasing; what does that mean, "rebasing @targrub's changes on my implementation"?

/// During normal use, this can be safely unwrapped; the value should only be [None] when synthetically constructed for tests.

That comment was written by @alice-i-cecile . :)

@MDeiml
Copy link
Contributor Author

MDeiml commented Oct 15, 2022

I'm not familiar with that phrasing; what does that mean, "rebasing @targrub's changes on my implementation"?

It just means that my PR gets merged first. Your PR would then show a message say something about "conflicts" because we are editing the same lines in the same file. You would then merge the (then) current main branch from bevy into your branch solving these conflicts (or alternatively, run git rebase main on it, that's where the name comes from).

All in all the commit history should then look like

...older commits -> my commits -> your commits

You can also do that work before my PR gets merged, but that requires some git magic.

@alice-i-cecile
Copy link
Member

I'm going to merge targrub's PR first to spare them the git pain :) I also think it's less complex / controversial than this change.

@targrub
Copy link
Contributor

targrub commented Oct 16, 2022

I'm going to merge targrub's PR first to spare them the git pain :) I also think it's less complex / controversial than this change.

Gee, thanks!

@anlumo
Copy link

anlumo commented Oct 16, 2022

It's causing a lot of trouble for my side to not have the AbstractWindowHandle being part of WindowDescriptor, instead generating it by dedicated functions. I don't know why the RawWindowHandle wasn't part of the struct beforehand, and this PR just copies that design choice.

Wouldn't it be a good idea to include AbstractWindowHandle in that struct? The way I understand it is that the WindowDescriptor is supposed to provide the information to generate the Window, but this piece of information is added with the actual function call of creating the Window for some reason instead.

@MDeiml
Copy link
Contributor Author

MDeiml commented Oct 17, 2022

It's causing a lot of trouble for my side to not have the AbstractWindowHandle being part of WindowDescriptor, instead generating it by dedicated functions. I don't know why the RawWindowHandle wasn't part of the struct beforehand, and this PR just copies that design choice.

Wouldn't it be a good idea to include AbstractWindowHandle in that struct? The way I understand it is that the WindowDescriptor is supposed to provide the information to generate the Window, but this piece of information is added with the actual function call of creating the Window for some reason instead.

Unfortunately I don't think this is possible. As I understand the main purpose of WindowDescriptor is the CreateWindow event. Obviously the window handle cannot exist yet when this event is created. Also this is "user facing" API and should probably not contain implementation details.

For your PR it would make sense though to add the canvas as a field of WindowDescriptor, but maybe someone more qualified can answer that. 😅

@anlumo
Copy link

anlumo commented Oct 17, 2022

There are many types called “Window” in bevy. The Window created by CreateWindow is the one used by winit to link the actual OS window to the engine. It's possible that it already exists when this event is called, I think the OS window is just created lazily when it doesn't exist yet.

However, I didn't write that code and it's entirely undocumented, so I'm mostly guessing here based on the snippets I've read through in the course of creating my PR.

Anyways, the problem is that there's no way get the canvas to the winit initializer at the moment without having a web-only API, which I don't want to force.

@MDeiml
Copy link
Contributor Author

MDeiml commented Oct 17, 2022

Couldn't there just be a canvas attribute in WindowDescriptor that's hidden behind a feature flag or that is only active for the web platform?

@anlumo
Copy link

anlumo commented Oct 17, 2022

That was my previous solution, but that complicates things in combination with this PR, making a few things redundant.

I'll have to take a look if I can get that working again in the new architecture.

Copy link
Member

@alice-i-cecile alice-i-cecile left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very clean, and the use cases are good. Can you add a full example to examples/window demonstrating how to use this in practice? The "render a texture to it" makes a lot of sense, but will be hard for users to piece together.

@MDeiml
Copy link
Contributor Author

MDeiml commented Oct 17, 2022

Sure, good idea. I'm gonna try to come up with something simple.

@alice-i-cecile
Copy link
Member

@anlumo, can I get a review from you on this since you've been touching related code and working off this PR?

@anlumo
Copy link

anlumo commented Nov 5, 2022

@anlumo, can I get a review from you on this since you've been touching related code and working off this PR?

So, sorry for taking so long. I already knew the code itself while adapting my PR to it, and now I've also checked out the documentation and the example. All of that seems fine to me.

@alice-i-cecile alice-i-cecile added the S-Ready-For-Final-Review This PR has been approved by the community. It's ready for a maintainer to consider merging it label Nov 5, 2022
@alice-i-cecile
Copy link
Member

@MDeiml can you rebase this?

Virtual windows will by default not have a surface texture associated to
them, but implementors can set the texture in `ExtractedWindow`
manually.

This is intended to be used when embedding games into other appications
like editors or for running games headless.
@alice-i-cecile alice-i-cecile removed the S-Ready-For-Final-Review This PR has been approved by the community. It's ready for a maintainer to consider merging it label Nov 7, 2022
Cargo.toml Outdated Show resolved Hide resolved
instance.create_surface(&handle)
let surface = {
let windows = app.world.resource_mut::<bevy_window::Windows>();
let raw_handle = windows.get_primary().and_then(|window| unsafe {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needs a SAFETY comment :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, that's true. The file was missing the SAFETY comment before the change, but I'll add one.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually looking at it now it seems to me that this was never safe. In particular this comment

// SAFETY: [`RawHandleWrapper`] is just a normal "raw pointer", which doesn't impl Send/Sync. However the pointer is only
// exposed via an unsafe method that forces the user to make a call for a given platform. (ex: some platforms don't
// support doing window operations off of the main thread).
// A recommendation for this pattern (and more context) is available here:
// https://github.com/rust-windowing/raw-window-handle/issues/59

is wrong. The raw window and display handle are directly exposed as the attributes are pub and there are pub methods to access them.

Even without this problem, for my particular case to ensure safety, all windows in Windows have to be created through the main thread, but new windows can be created and added from any thread using Windows::add.

Same goes for

.or_insert_with(|| unsafe {
// NOTE: On some OSes this MUST be called from the main thread.
which is also missing a SAFETY comment. It says it has to be run on the main thread, but as far as I see there is nothing in place to ensure that.

If I understand this situation correctly this could be made safe by:

  1. Making the attributes of RawHandleWrapper private and removing the methods to access them
  2. Making Window::add unsafe and specifying that it has to be called from the main thread. Maybe add a comment that it is better to use CreateWindow anyways.
  3. Making prepare_windows an exclusive system which if I remember correctly would make it run on the main thread

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Making prepare_windows an exclusive system which if I remember correctly would make it run on the main thread

Ideally we would add a NonSend system parameter here, which forces main thread access without blocking parallelism fully.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, it seems like changing the methods of Windows to ensure safety is unpractical, as there are also methods which expose mutable references, which also would need to be unsafe. So I'll make creating RawHandleWrapper unsafe instead.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Making prepare_windows an exclusive system which if I remember correctly would make it run on the main thread

Ideally we would add a NonSend system parameter here, which forces main thread access without blocking parallelism fully.

The marker was already there, I just missed it.

});

let window_id = WindowId::new();
windows.add(Window::new_virtual(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This bit needs more comments; it's the point of the example.

Copy link
Member

@alice-i-cecile alice-i-cecile left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some small feedback, but I'm liking the direction. I'd also be interested in seeing a headless rendering + virtual window example, but won't require it to be part of this PR. I suspect that it will be a very useful method for testing UI code without a GPU.

@alice-i-cecile
Copy link
Member

Closed per rationale in #8278.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-Windowing Platform-agnostic interface layer to run your app in C-Usability A simple quality-of-life change that makes Bevy easier to use
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants