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

Render frame to a file instead of a window #758

Open
alexfertel opened this issue Jun 20, 2021 · 10 comments
Open

Render frame to a file instead of a window #758

alexfertel opened this issue Jun 20, 2021 · 10 comments
Labels

Comments

@alexfertel
Copy link
Contributor

My current workflow in other generative art frameworks is to generate files with the output. I would like to know if there's a way to not create a window and just specify a file to dump the first frame.

I have been searching for two days and I haven't found a solution that I'm comfortable with. The closest approach I see is something like this. If I understood the code correctly it saves the frame to a file when pressing the s key.

Thank you for this awesome framework!

@alexfertel alexfertel changed the title Render draw output to a file instead of a window Render draw output to a file instead of a window Jun 21, 2021
@alexfertel alexfertel changed the title Render draw output to a file instead of a window Render frame to a file instead of a window Jun 21, 2021
@mitchmindtree
Copy link
Member

Thanks for the issue!

I think the closest example to what you're after might be draw_capture.rs or draw_capture_hi_res.rs for rendering an arbitrary size image. However, both examples also render the output to the window.

To ensure that only one frame is produced, you should be able to call app.set_loop_mode(LoopMode::loop_once()); in your model function, though I haven't tested this in a while.

I don't think we provide a nice or simple way of rendering the output of the Draw API to a texture without also instantiating an event loop or any windows at the moment.

Currently the process of instantiating a WGPU adapter, device and queue is tied quite closely to the window creation process (see here). Nannou doesn't yet provide an API to set up these items without also creating a window - I think this is one area where we could improve!

It is technically possible today to use the Draw API, render the result to a texture and write that texture to a file without an event loop, App instance or any windows, however it currently involves a bit of setup and some direct calling into the wgpu API. Perhaps we could start by making a draw_headless.rs example that demonstrates how to achieve this, then use the example as a guide to try and simplify this kind of workflow.

I think ideally we should eventually expose a nannou_draw crate that provides the Draw API and its wgpu backend without the need for the rest of the nannou crate (event loop, etc). I imagine the nannou crate would then re-export the nannou_draw crate to appear as the nannou::draw module currently does. I think the recent work in #754 is a good step towards making this possible.

@alexfertel
Copy link
Contributor Author

That's great!

I'll check them out and let you know if I'm able to get it to work.

I am pretty new to Rust, so I am not very confident about contributing to a big project like this one, but I'll try to do it. Let's go!

Thanks for the quick response!

@alexfertel
Copy link
Contributor Author

Hello, @mitchmindtree!

I had some time to give it a try, and I was able to set up something by taking different bits from the pointers you gave me that ended up like this:

fn main() {
    nannou::app(model).simple_window(view).run();
}

fn model(app: &App) -> Model {
    app.set_loop_mode(LoopMode::loop_once());
    Model {}
}

fn view(app: &App, _model: &Model, frame: Frame) {
    let draw = app.draw();

    draw.rect()
        .x_y(0.0, 0.0)
        .w(100.0)
        .rgb(255.0, 0.0, 0.0);

    draw.to_frame(app, &frame).unwrap();

    println!("Path {:#?}", capture_directory(app));

    let file_path = captured_frame_path(app, &frame);
    app.main_window().capture_frame(file_path);
}

fn capture_directory(app: &App) -> std::path::PathBuf {
    app.project_path()
        .expect("could not locate project_path")
        .join(app.exe_name().unwrap())
}

fn captured_frame_path(app: &App, frame: &Frame) -> std::path::PathBuf {
    app.project_path()
        .expect("failed to locate `project_path`")
        .join(app.exe_name().unwrap())
        .join(format!("{:03}", frame.nth()))
        .with_extension("png")
}

As you said, a window is instantiated, and then the frame is saved to a png file. The weird thing is that it saves two frames instead of one, and the println statement gets called twice.

Could you give me some pointers so I can write the draw_headless.rs example, or do you think it would be too much for a beginner?

@fenjalien
Copy link

Hi @alexfertel! @dzil123 has forked this repo and made a branch that produces a headless API (sorry dan I did some snooping).
See here: https://github.com/dzil123/nannou/blob/headless/

In the branch theres an example called draw_headless which creates a folder called headless and produces ten(?) images similar to the polyline example without creating a window. I haven't tried with a sketch of my own yet but the example works really well!

@alexfertel
Copy link
Contributor Author

Hello @fenjalien!

Wow, that's great! The link that worked for me was https://github.com/dzil123/nannou/tree/headless for some reason, and I took a look and it certainly is very close to what I was hoping for!

I'll see if I can play with it this weekend, this got me very excited, hahaha.

Thank you for snooping 😁

@dzil123
Copy link
Contributor

dzil123 commented Aug 7, 2021

Haha, you found my branch! It's not quite ready for inclusion into nannou, and I haven't thoroughly tested it, but the example does work.

I think eventually it might be possible to integrate this into the real nannou repo, with a flag during the app builder or window creation to create a HeadlessWindow instead. However, for right now HeadlessApp is simply a different struct that duplicates or fakes some functionality of the real App.

It's worth noting that app.time is purely arbitrary; the images are rendered "offline," so there's no true time, but I added a 30fps clock to keep the draw_polyline example as unmodified as possible.

I would appreciate any suggestions for how this API should look, or what your typical use cases for headless rendering are.

@fenjalien
Copy link

Just a heads up, I'm working on my own version that keeps the same drawing logic but with an API more similar to nannou's.

I've already tried attaching a headless window type to the main App struct with hopes of later creating a window trait. However, App and Window are tied to closely together with winit to make it possible without a complete restructuring.

I'm now trying to create completely separate structs focused on headless with an API as close to the main structs as possible. We may be able to abstract from there...

@fenjalien
Copy link

A slight update, I've started a PR for a headless feature. See: #784

@alexfertel
Copy link
Contributor Author

Hello! I just wanted to say that I've been playing with https://github.com/dzil123/nannou/tree/headless a lot, and it works absolutely wonderfully.

I haven't felt the need to make too many changes to the example, but that is because it works well for me.

It would be great having this merged into nannou!

@josh-gree
Copy link

josh-gree commented Nov 15, 2023

Sorry for noise - would just love to know if this feature is still being investigated? I would really love to be able to make use of this!

Or if not is this possible, with some acrobatics, from the current version?

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

No branches or pull requests

5 participants