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

Problems when rendering window one frame at a time #167

Open
Boscop opened this issue Oct 12, 2016 · 4 comments
Open

Problems when rendering window one frame at a time #167

Boscop opened this issue Oct 12, 2016 · 4 comments

Comments

@Boscop
Copy link

Boscop commented Oct 12, 2016

I'm using conrod & piston_window for the GUI in a VST plugin, but I'm having some problems with event handling:
The way the GUI is run in a VST is, one frame at a time.
So I can't use while let Some(event) = gui.window.next() { because that stalls, but when I try to do one frame by doing if let Some(event) = gui.window.next() { it doesn't register input events. So I tried while let Some(event) = gui.window.poll_event() { which seemed like a logical way to do it (only process events that are in the queue right now, don't block to wait for new events at the end of the queue), then I get input events, but no render events so the GUI doesn't render!
So what should I do to handle the GUI one frame at a time / without internal event loop?

Also, I have another question: What exactly does the event loop do behind the scenes (with the value given by window.set_ups())? Is there a way to turn the event loop off?

(This question is also relevant for writing game UIs, where the game should be in control of the loop and the GUI should be drawn over the scene.)


The host creates the window for each plugin and passes the handle to the plugin, then the plugin creates a child window with the given parent, and creates an opengl context on it.
I forked and modified conrod, piston_window, piston/window, glutin and glutin_window so I could pass the parent HWND down to where glutin creates the window. (I also modified glutin so that the child windows are created in the same thread and so that I could register timers on the window etc.).
Then I register a timer on that child window that calls my UI rendering callback repeatedly.
I already tested this before using only glutin and shaders to render my plugin GUI: https://i.imgur.com/Oi6JUzd.png

I created a standalone version of the plugin to test it, that passes NULL as the parent HWND so that glutin creates a normal window, and when I use while let Some(event) = gui.window.next() { it works correctly:

Input(Move(MouseRelative(1, 0)))
Update(UpdateArgs { dt: 0.016666666666666666 })
Idle(IdleArgs { dt: 0.000000000011982044 })
Input(Move(MouseCursor(411, 218)))
Input(Move(MouseRelative(1, 0)))
Render(RenderArgs { ext_dt: 0.000000000014581911, width: 682, height: 360, draw_
width: 682, draw_height: 360 })
AfterRender(AfterRenderArgs)
Input(Move(MouseCursor(412, 218)))
Input(Move(MouseRelative(1, 0)))
Input(Move(MouseCursor(414, 218)))
Input(Move(MouseRelative(2, 0)))
Input(Move(MouseCursor(415, 218)))
Input(Move(MouseRelative(1, 0)))
Update(UpdateArgs { dt: 0.016666666666666666 })
Render(RenderArgs { ext_dt: 0.000000000016664646, width: 682, height: 360, draw_
width: 682, draw_height: 360 })
AfterRender(AfterRenderArgs)
Input(Move(MouseCursor(416, 218)))
Input(Move(MouseRelative(1, 0)))
Update(UpdateArgs { dt: 0.016666666666666666 })

But it stalls (because of while) so I can't use this in the plugin version), but if I use if let Some(event) = gui.window.next() { on every frame, I only get Input events:

Input(Move(MouseRelative(0, -1)))
Input(Move(MouseCursor(46, 157)))
Input(Move(MouseRelative(0, -1)))
Input(Move(MouseCursor(46, 156)))
Input(Move(MouseRelative(0, -1)))
Input(Move(MouseCursor(46, 155)))
Input(Move(MouseRelative(0, -1)))
Input(Move(MouseCursor(46, 154)))
Input(Move(MouseRelative(0, -1)))
Input(Move(MouseCursor(46, 152)))
Input(Move(MouseRelative(0, -2)))
Input(Move(MouseCursor(46, 151)))
Input(Move(MouseRelative(0, -1)))
Input(Move(MouseCursor(46, 150)))
Input(Move(MouseRelative(0, -1)))
Input(Release(Mouse(Left)))

But I don't get render events, so nothing gets rendered.
My gui consists of a slider widget. I have it set up so that it prints 'value changed' when I change the slider with the mouse.
I wanted to see if the input is still being processed, even though nothing is rendered in this case.
But if I click on where the slider widget would be rendered, it doesn't print 'value changed' so the input apparently doesn't get processed correctly either.

What's weird is that I get mouse move events even if i dont move the mouse.
So I thought, maybe with 'while' I never get render events because for some reason it 1. puts input events before render events and 2. creates input events on every frame even when there is no input.
So I changed the code to this:

let mut i = 10;
while let Some(event) = gui.window.next() {
    i -= 1;
    if i == 0 {break}
    // usual event processing
}

Now it seems to work better: it renders and processes input, but it's very laggy and I'm not sure if this is the best way to do it.. And if there are no new events, it still waits until it got 10, which shouldn't happen. So my questions are:

  1. Is there a way to only process events that are in the queue right now, and not block to wait for new events at the end of the queue?
  2. How can I disable the event loop under the hood? I don't want it to interfere with my loop.
    Especially because the UI rendering is done in a callback triggered by a timer that fires every few ms.
  3. Currently ESC causes window.next() to return None. How can I ignore ESC? In the plugin, ESC shouldn't do anything, events should keep coming.

Edit: It seems the method of breaking out of the while loop after 10 events doesn't work in the plugin version (only in standalone, but laggy as hell). In the plugin version it renders but I get no input events. Why could that be?

@leroycep
Copy link

leroycep commented Oct 13, 2016

  1. Back in the other issue, @mitchmindtree mentioned something about using this method to manually create render events. Since the VST host is going to be calling your code once per frame, it would make sense to use this. Combine this with the poll_events function and that should solve question 1.
  2. See above
  3. This function might help. (WindowSettings::new("Hello World", [WIDTH,HEIGHT]).exit_on_esc(false).build().unwrap();)

EDIT: Also, if it is possible, seeing more of your code would help us help you. :P

@christolliday
Copy link
Contributor

This is my understanding from writing a non-polling event loop, that might help you out

A typical conrod event loop looks like this:

while let Some(event) = window.next() {
    if let Some(e) = conrod::backend::piston_window::convert_event(event.clone(), &window) {
        ui.handle_event(e);
    }
    event.update(|_| set_ui(ui.set_widgets(), &ids));

    window.draw_2d(&event, |c, g| {
        ...ui.draw_if_changed()..
    });
}

the window.next() method returns three event types that are used, Input, Update and Render.

  • Input events are passed to ui.handle_event(e) to update the internal input state
  • Update events trigger set_ui(ui.set_widgets(), &ids)) to update all the widgets
  • Render events trigger ui.draw_if_changed() to draw what's changed

Input events can be received at any time, but update and render are called at a fixed rate, 60fps by default. That's the rate set by set_ups

If the VST host is calling your code once per frame, does it also supply all the input events that happened in that frame? If not, I think there's no way around handling the input events as they happen. In which case you might need a separate thread to collect input events, then when the VST calls each frame, use that as the Update and Render events.

@Boscop
Copy link
Author

Boscop commented Nov 14, 2016

The VST host calls my render callback. The window runs in the same thread as the host, and creating another thread for event handling is not possible/allowed in this situation.
So I have to generate an Update and a Render event?

@mitchmindtree
Copy link
Contributor

@Boscop conrod's glium feature (as used in the glutin_glium.rs example) may be more suited to your use if you're mixing conrod GUI into a plugin like this one frame at a time. It will allow you to use conrod with glutin directly (without piston_window). This might be useful as glutin allows you to poll for all pending events at once (i.e. you could do this once per frame), rather than being restricted to a loop that polls or waits for you and returns one event at a time. There might be a way to change piston_window to poll for all pending input events at once, but I'm not aware of this.

So I have to generate an Update and a Render event?

As of PistonDevelopers/conrod#855 conrod no longer needs to know about either of these events, so if you do want to stick to your current setup (rather than switch to the glium feature), you might not need to do much else. Just call ui.set_widgets each time you get a frame or new input, draw the Ui, and it might work, though it's hard to know for sure with limited info.

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

No branches or pull requests

4 participants