noizebox / vstimgui

Vst 2.4 plugin editor using Dear ImGui and glfw
MIT License
17 stars 1 forks source link

Questions about VST ImGui implementation #1

Open AnClark opened 3 years ago

AnClark commented 3 years ago

Hi, Gustav!

I'm AnClark, trying to rewrite UI for Amsynth by Dear ImGui. Several days ago I discovered your NB-01 distortion processor in ImGui's gallery. It would be helpful for my future development, however it seems proprietary.

Here are some questions about NB-01 and this source tree:

noizebox commented 3 years ago

Hi AnClark and thanks for the interest.

NB-01 is closed source atm and I don't have any immediate plans to release it as open source, though it will remain freeware and I plan on providing updates and bugfixes in the future.

The Windows release of NB-01 is built with Visual Studio 2019, the linux release with GCC 10. I have built it with the official VST 2.4 sdk, not vestige.h (though that should be doable I think, but I have a license from Steinberg so don't need to). It shouldn't be too hard to port to VST3 either, as the editor part isn't very complex. Though the building and linking part might be more difficult.

NB01 doesn't actually use vstimgui per se, I built my own translation layer (kinda like JUCE, but very minimal) that vstimgui was sort of the prototype for, but the mechanism is essentially the same. And my intention is to update vstimgui with what I've learned since so it has essentially the same code as in NB-01. I've moved the reference counted initialization from glfw to the editor for instance.

I haven't put a license on vstimgui, but if I eventually do if it feels less like work in progress, it will be a permissive one, so feel free to use it for anything or copy code from it.

AnClark commented 3 years ago

I haven't put a license on vstimgui, but if I eventually do if it feels less like work in progress, it will be a permissive one, so feel free to use it for anything or copy code from it.

Great! Vstimgui is really an essential for learning how to write a ImGui-based editor.

I'm continue implementing codes into Amsynth on Windows. And I've encountered those issues.

Environment:

My codes:

0x01 Too small embedded window

Finally ImGui can be embedded into REAPER's UI. However its size is too small (seems less than 200x100), no matter how I set sizes via different methods (via io.DisplaySize or glViewport).

What's more, when double-clicking REAPER's title bar, ImGui UI can be fully shown. But it will overlap REAPER's own UI elements.

0x02 Host window will lose focus

Normally, no matter how to interact with embedded editor, host window should still be in focus. But here when I click my editor, REAPER's FX window will automatically lost focus as if it's a totally external application. It would be terrible on production.

I wonder why it would happen.

AnClark commented 3 years ago

0x02 Host window will lose focus

Solved via my modded GLFW.

noizebox commented 3 years ago

You seem to have done quite a bit of progress, great! Reaper is probably the most challenging host out there in terms of plugin editor support. NB01 worked fine in all other hosts I tested, but most of the bugs reported came from Reaper users.

Of the tricks for Reaper was to call sizeWindow() in the idle call (Reapers gui thread apparently) and not in the editors draw thread. I added a counter and a flag so that that is called after the editor has finished setting up.

I haven't figured out exactly why the editor sometimes overlap Reapers own status bar, but my slightly hackish solution has been to call sizeWindow() in AudioEffect twice, and first with an incorrect size (of by 1 pixel is enough), then with the correct size. That always seen to work for me. Though let me know if you figure something out as I believe most of the problems with Reaper and editor size/position is due to some race condition in how Reaper sets up the editor window.

Keyboard focus is a complicated thing I've realized. Does your trick for host window focus always work or does it only give focus back to the host when the editor is created? So that clicking in the editor again makes the editor take focus?

In NB01 I eventually changed it so that I don't even accept focus when the cursor enters the window. This required a change to the Imgui glfw frontend so that you accept mouse click even when you don't have focus though. But it means that keyboard events (start stop with space bar for instance) works while you're tweaking knobs in the editor. That gives an experience that more matches other plugins.

I tried various ways of having focus and passing keyboard events to the parent window, but couldn't make it work in a stable way.

You can have a look at the NB01_modifications branch here, it also contains some WindowClassName modifications that were necessary for multiple plugins using the same glfw/dear imgui combination. https://github.com/noizebox/glfw/tree/NB01_modifications

AnClark commented 3 years ago

Does your trick for host window focus always work or does it only give focus back to the host when the editor is created? So that clicking in the editor again makes the editor take focus?

With my trick in GLFW, window focusing always works well just like other products. I just simply specify parent window when creating window via CreateWindowExW(), see my commit for details. Setting parent window when invoking CreateWindowExW() is the official method in VST2/3 SDK. This is also what PUGL (a framework similar with GLFW, but mainly aims at embedded UI) uses.

And this trick also fixed overlapping. SetParent() may cause those issues I mentioned above.


Keyboard focus is a complicated thing I've realized.

Keyboard events also seems well on Windows and WINE, but fails on Linux. On REAPER for Linux, editor always catches focus, even on when I attempt to type on host's textbox.


What's more, my trick is not so stable:

Maybe there's something to do with std:thread. I'll consider using Win32 thread API instead.

noizebox commented 2 years ago

Hi AnClark, how's your progress been? The last week or so I've had some time to get back into the windowing issues. Thanks a lot for your glfw changes, I dunno why I wasn't able to create windows using the parent window directly and not re-parenting the plugin window after it has been created. Though with your branch it does work!

I was able to solve a few issues that showed up as well. One crash on Linux when closing the editor in Reaper (throwing a BadWindow error) was that glfw steals the x11 error handler and doesn't put it back. After not doing that, it seem to work. At least Reaper appears to handle it without crashing. Feel free to have a look at my glfw branch https://github.com/noizebox/glfw/tree/c8eb88448f7be0bfcc366096cd33d9ddcc5fa176

For windows, I found it best to actually call glfwDestroyWindow() in the same thread that calls close() on the editor, and not in the thread that created the window. Weird, but that seemed more stable. I haven't ported that bit to vstimgui yet, but planning on doing it when I have time.

AnClark commented 2 years ago

Thanks a lot for your glfw changes, I dunno why I wasn't able to create windows using the parent window directly and not re-parenting the plugin window after it has been created. Though with your branch it does work!

My pleasure!

I've also asked Mr. Justin Frankel, the founder of Cockos and REAPER, for ideas of solving those issues. He gave me a lot of great advice. With his great help, everything works well. He also told me why GLFW crash in X11 (see commit 06ed4fd).

What's more, on my Windows, I always encounter some issues on embedded editor, such as UI misplacing. Mr. Frankel told me that I should use effEditIdle to redraw editor instead of multi-thread.

noizebox commented 2 years ago

Great, I see you also figured out the error handler issue on Linux.

Do you still get a good framerate from drawing in the effEditIdle callback.

AnClark commented 2 years ago

Do you still get a good framerate from drawing in the effEditIdle callback.

It still hard for me to optimize. Mr. Frankel informed me that I can increase the "idle rate" on Windows (maybe via a Win32 API, but I cannot find what the API is). Usually the framerate can between 25fps ~ 28fps.

On Linux, the framerate could be faster than Windows. It could be up to 30fps on native format, while 25~28fps via Wine.

Currently they're enough for my project (my own fork of Amsynth).

noizebox commented 2 years ago

That still sounds pretty good. I guess it you wan't a solid 60fps gui you would need to have a dedicated drawing thread. I'm still keeping my drawing thread for now, maybe with an option to run it from the idle callback instead.

I'll keep the issue open mostly for reference, if anyone else is interested in the discussion btw.

AnClark commented 2 years ago

I found it best to actually call glfwDestroyWindow() in the same thread that calls close() on the editor, and not in the thread that created the window.

Does it mean you need to call glfwDestroyWindow() in the editor drawing thread? Maybe it would be helpful.

Now I'm using vestige.h instead of the official VST SDK. I'm not sure how those commercial products using official SDK handles UI drawing. (e.g. Memorymoon)

noizebox commented 2 years ago

To my knowledge, everything build with Juce has its own thread for drawing and does not rely on effEditIdle being called. Otherwise I don't know how other commercial plugins handle it.

In the update to NB01 that I'm working on I'm calling glfwDestroyWindow() from the thread calling close_editor() (I think that's opcode effEditClose), after joining the drawing thread. That seems more stable in Reaper on Windows, otherwise glfwDestroyWindow() could sometimes hang. For all other hosts, it didn't seem to matter. I haven't ported that part over to vstimgui yet, but planning on doing it.

AnClark commented 2 years ago

I haven't ported that part over to vstimgui yet, but planning on doing it.

Great! Hope that those issues of drawing thread could be perfectly resolved.

No wonder Juce-based GUIs are smooth (for example, Helm). Maybe effEditIdle is just a polyfill for me. I'll use effEditIdle first, and when I finish building my GUI, I'll turn to your solution.