It depends what you're drawing. I'm working on a multi-channel oscilloscope so I'm offloading as much drawing and DSP to the GPU as I can to free up the CPU for more important tasks. None of this would be possible with the fixed function pipeline and I'm not concerned with isolating the tiny fraction of pre-2000 GPUs that cannot handle such duties. That's not to say that it couldn't be implemented with OpenGL 1.1, rather the CPU and RAM overhead would be far greater than using OpenGL 1.1 features. For garden variety GUI rendering, I agree, OpenGL 1.1 is certainly up to the task.mystran wrote:Actually turns out some people are still using cards (eg G550) that don't even support texture_env_combine. I'm sure OGL2.0 gives you a fairly wide user base, but I like to keep as much backwards compatibility as reasonably possible. It's not like it matters much for GUI drawing anyway.rw wrote:You can safely support at least OGL 2.0 in this day and age, doing away with fixed function pipeline and the awful texture combiners. All that stuff is emulated by the driver anyway.
Guidance needed for GUI coding
-
- KVRer
- 5 posts since 21 Mar, 2012
- KVRAF
- 8476 posts since 12 Feb, 2006 from Helsinki, Finland
Yeah ok, I admit shaders can be useful if you can take a raw buffer of audio data and transform it into a nice complex visual representation completely on GPU. If I wanted to do something like that then I'd probably drop fixed function support as well. It's not like I ever touch any state manually anyway (eg. API wrapper implements simple "shaders" that map desired functionality to texture combines and render state). In fact I don't touch the API at all directly; in my GUI lib it took me a day to write OpenGL backend to replace the original D3D backend and I can't really remember needing to change anything outside the actual backend (the two backends can even happily co-exist in the same binary so you could select one or the other at runtime; maybe some day I'll write a software-only fall-back to support the guy that's still using CL5434).
Anyway, for "basic user interfaces" I guess the only thing that could reasonably benefit from shaders would be text rendering; since it pretty much comes down to a quad per glyph (unless you cache static text directly), it's a bit wasteful on bus-bandwidth to push 4 vertices per quad when you could instance on hardware. For on-the-fly (ie not cached RGB like GDI) sub-pixels using fixed function is even more data since the whole sub-pixel crap doesn't map very nicely to texture combiners (or I'm just stupid; that's also possible) where as all of it would be trivial with simple shaders. That's something I might actually do that at some point (as an optional optimization where supported, ofcourse
).
In my "other project" (ie the reason I haven't really released much audio stuff lately, if we ignore the fact that I've been playing too much FPS games) that does a bit more on the GPU than "cheap alpha-blending", I actually don't touch the fixed function at all. Fortunately that doesn't need to run in a plugin so it can happily use D3D (and has performance requirements high enough that you can just ignore any Intel graphics even exist).
Anyway, for "basic user interfaces" I guess the only thing that could reasonably benefit from shaders would be text rendering; since it pretty much comes down to a quad per glyph (unless you cache static text directly), it's a bit wasteful on bus-bandwidth to push 4 vertices per quad when you could instance on hardware. For on-the-fly (ie not cached RGB like GDI) sub-pixels using fixed function is even more data since the whole sub-pixel crap doesn't map very nicely to texture combiners (or I'm just stupid; that's also possible) where as all of it would be trivial with simple shaders. That's something I might actually do that at some point (as an optional optimization where supported, ofcourse
In my "other project" (ie the reason I haven't really released much audio stuff lately, if we ignore the fact that I've been playing too much FPS games) that does a bit more on the GPU than "cheap alpha-blending", I actually don't touch the fixed function at all. Fortunately that doesn't need to run in a plugin so it can happily use D3D (and has performance requirements high enough that you can just ignore any Intel graphics even exist).
-
- KVRAF
- Topic Starter
- 7578 posts since 17 Feb, 2005
I am only okay with C and not very good with C++ yet. I have got a framework up and running already but my lack of knowledge is holding me up. How do I give variables to the editor class from the plugin class? I tried using global scope but I did it wrong lol. Does it involve more inheritance than just one class from AudioEffectX and one from AEffEditor? I am using the VSTGL sdk.
-
- KVRAF
- Topic Starter
- 7578 posts since 17 Feb, 2005
Testing further, I found that having multiple instances on the screen caused frames to drop between them. The more instances, the more frames dropped. Vertical sync is not the issue. I am guessing everything would have to be done in the same context, and for a VST host that might require IPC? Well I am disappoint because OGL looks amazing for plugins.
- KVRAF
- 8476 posts since 12 Feb, 2006 from Helsinki, Finland
If you are not creating a separate context and properly setting/restoring it every time you touch OpenGL then you're asking for trouble. All the state and resources (eg textures etc) are tied to particular OpenGL context, so you must always make sure you have the right one active or you'll be stepping on other plugins/hosts/whatever feet.camsr wrote:Testing further, I found that having multiple instances on the screen caused frames to drop between them. The more instances, the more frames dropped. Vertical sync is not the issue. I am guessing everything would have to be done in the same context, and for a VST host that might require IPC? Well I am disappoint because OGL looks amazing for plugins.
As far as dropping frames, if you try to wait (eg glFinish() or probably on SwapBuffers if you try to use VSync) then no other instance/plugin will run while your stalling the GUI thread (never EVER wait in GUI thread). This obviously also means no other plugin will update while your waiting. If you're not already using it, enable double buffering too (to make sure SwapBuffers is as low overhead as possible).
Other than that though, I don't see why there should be any problems. Obviously if your drawing is very CPU/GPU heavy and/or you do it too often, then you can generate more work than your system can handle which will obviously lead to dropped frames.
-
- KVRAF
- Topic Starter
- 7578 posts since 17 Feb, 2005
Just fixed itmystran wrote:If you are not creating a separate context and properly setting/restoring it every time you touch OpenGL then you're asking for trouble. All the state and resources (eg textures etc) are tied to particular OpenGL context, so you must always make sure you have the right one active or you'll be stepping on other plugins/hosts/whatever feet.camsr wrote:Testing further, I found that having multiple instances on the screen caused frames to drop between them. The more instances, the more frames dropped. Vertical sync is not the issue. I am guessing everything would have to be done in the same context, and for a VST host that might require IPC? Well I am disappoint because OGL looks amazing for plugins.
As far as dropping frames, if you try to wait (eg glFinish() or probably on SwapBuffers if you try to use VSync) then no other instance/plugin will run while your stalling the GUI thread (never EVER wait in GUI thread). This obviously also means no other plugin will update while your waiting. If you're not already using it, enable double buffering too (to make sure SwapBuffers is as low overhead as possible).
Other than that though, I don't see why there should be any problems. Obviously if your drawing is very CPU/GPU heavy and/or you do it too often, then you can generate more work than your system can handle which will obviously lead to dropped frames.
One thing that is troublesome is the windows in my host (FL) when dragged get choppy. Do you think it would help to use some kind of framerate limiting?
-
- KVRAF
- Topic Starter
- 7578 posts since 17 Feb, 2005
I haven't been able to solve the problem of framerate dividing between additional instances yet. I don't know if the problem is the Timer class in VSTGL or if I am not using the context correctly. Do I need to destroy the rendering context after every update, or just release the device context?
This has the Timer interval (refresh) dividing by the number of instances (functionally). The Timer is a Singleton and as far as I can tell, uses static types to share it. The VSYNC and Antialiasing settings made no difference.
Code: Select all
void VSTGLEditor::refreshGraphics()
{
dc = GetDC(tempHWnd);
wglMakeCurrent(dc, glRenderingContext);
draw();
swapBuffers();
//wglDeleteContext(glRenderingContext);
ReleaseDC(tempHWnd, dc);
}- KVRAF
- 8476 posts since 12 Feb, 2006 from Helsinki, Finland
You obviously need one timer per plugin instance, so singleton won't work.
As far as setting/restoring contexts (remember you also need to do this when you want to do things like load textures or whatever; I'd really recommend a RAII helper unless you never touch OpenGL outside your drawing logic)..
Note that if you use OWN_DC (which I'd really NOT recommend) then the above might not be correct. Remember to ValidateRect() the window in response to WM_PAINT or you'll immediately get a new WM_PAINT request afterwards.
As far as setting/restoring contexts (remember you also need to do this when you want to do things like load textures or whatever; I'd really recommend a RAII helper unless you never touch OpenGL outside your drawing logic)..
Code: Select all
void VSTGLEditor::refreshGraphics()
{
HDC hdc, hOldDC;
HGLRC hOldRC;
// get previous DC and render context
hOldDC = wglGetCurrentDC();
hOldRC = wglGetCurrentContext();
// get DC for the hwnd of the window to draw in
hdc = GetDC(hwnd);
// set rendering context and DC
wglMakeCurrent(hdc, glRC);
// draw, swap, whatever else you want in the context
draw();
SwapBuffers();
// clear errors to avoid confusing other code
// it's fine to call this and ignore errors if you don't
// care about them; it's just so it reports success on next call
glGetError();
// restore previous context and DC
wglMakeCurrent(hOldDC, hOldRC);
// release the DC we allocated
ReleaseDC(hwnd, hdc);
}
Last edited by mystran on Wed Apr 11, 2012 9:05 pm, edited 1 time in total.
-
- KVRAF
- Topic Starter
- 7578 posts since 17 Feb, 2005
wglMakeCurrentContext(), is this a depreceated function? I can't even find it in my headers and google doesn't return much, not even from opengl.org. I tried MakeCurrent instead and everything looks correct. Thanks!mystran wrote:You obviously need one timer per plugin instance, so singleton won't work.
As far as setting/restoring contexts (remember you also need to do this when you want to do things like load textures or whatever; I'd really recommend a RAII helper unless you never touch OpenGL outside your drawing logic)..
Note that if you use OWN_DC (which I'd really NOT recommend) then the above might not be correct. Remember to ValidateRect() the window in response to WM_PAINT or you'll immediately get a new WM_PAINT request afterwards.Code: Select all
void VSTGLEditor::refreshGraphics() { HDC hdc, hOldDC; HGLRC hOldRC; // get previous DC and render context hOldDC = wglGetCurrentDC(); hOldRC = wglGetCurrentContext(); // get DC for the hwnd of the window to draw in hdc = GetDC(hwnd); // set rendering context and DC wglMakeCurrent(hdc, glRC); // draw, swap, whatever else you want in the context draw(); SwapBuffers(); // clear errors to avoid confusing other code // it's fine to call this and ignore errors if you don't // care about them; it's just so it reports success on next call glGetError(); // restore previous context and DC wglMakeCurrentContext(hOldDC, hOldRC); // release the DC we allocated ReleaseDC(hwnd, hdc); }
ValidateRect(tempHWnd, NULL) I added this after ReleaseDC, but I don't know if it's needed?
In GLWndProc(), the case of WM_PAINT calls to idle(), and GLWndProc returns DefWindowProc(). I am confused as to why this is needed, but it looks for compatibility.
Is it the Timer's fault for the dividing framerate?! It only divides when another instance's editor is open, so I guess it is something to do with Timer. I thought the whole point of the static singleton was to prevent contexts from stepping on each others toes? Or is this unnecessary (are the OpenGL calls buffered?)
I've seen quite a few examples of multithreaded programs with multiple GL contexts in different threads (including JUCE), and everything looks in sync and full framerate! I would use JUCE for this, but I cannot afford the license and I want to sell a couple plugins I make.
Actually, I am not sure I can close source my VST with VSTGL? The License says this:
"Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software."
This is only the VSTGL portion or the VST itself?
- KVRAF
- 8476 posts since 12 Feb, 2006 from Helsinki, Finland
No, wglMakeCurrent() ofcourse.. sorry I had a brain malfunction while writing that.camsr wrote:wglMakeCurrentContext(), is this a depreceated function? I can't even find it in my headers and google doesn't return much, not even from opengl.org. I tried MakeCurrent instead and everything looks correct. Thanks!mystran wrote:You obviously need one timer per plugin instance, so singleton won't work.
As far as setting/restoring contexts (remember you also need to do this when you want to do things like load textures or whatever; I'd really recommend a RAII helper unless you never touch OpenGL outside your drawing logic)..
[...]
Note that if you use OWN_DC (which I'd really NOT recommend) then the above might not be correct. Remember to ValidateRect() the window in response to WM_PAINT or you'll immediately get a new WM_PAINT request afterwards.
When a window (or a region of the same) is "invalid" or "dirty" the system will keep generating WM_PAINT messages until you validate it. BeginPaint() that GDI drawing normally calls does this automatically, but otherwise you'll need to ValidateRect() or something similar.ValidateRect(tempHWnd, NULL) I added this after ReleaseDC, but I don't know if it's needed?
Urgh. I guess DefWindowProc() will validate the window for you, but where do you draw then?In GLWndProc(), the case of WM_PAINT calls to idle(), and GLWndProc returns DefWindowProc(). I am confused as to why this is needed, but it looks for compatibility.
You should have one timer per instance, no? Normal way to do this is to setup a WM_TIMER (or similar) for the editor window. OpenGL contexts have nothing to do with timers. The OpenGL context is like a virtual device; you can have (within reason) as many as you like, and they are pretty much completely independent. The point is, any OpenGL state or resources you set/alloc/whatever belongs to a single context, so you can safely play in your own sandbox (context) without confusing other code in other sandboxes.Is it the Timer's fault for the dividing framerate?! It only divides when another instance's editor is open, so I guess it is something to do with Timer. I thought the whole point of the static singleton was to prevent contexts from stepping on each others toes? Or is this unnecessary (are the OpenGL calls buffered?)
-
- KVRAF
- Topic Starter
- 7578 posts since 17 Feb, 2005
So I finally figured out how to utilize the editor and effect pointers. This stuff is pretty new to me as most of my programming hasn't seen OOP and more inline.
I am trying to decouple my editor's redraw rate from the host's rate. So far, the graphics refresh is timer based, so there is no coupling there. EXCEPT, in the case of utilizing a variable that is updated inside the processReplacing() loop. Is there a way to push variables out of, or pull variables into the editor from processReplacing asynchronously? Or will processReplacing guarantee completion before other functions are called? In case you are wondering, I am attempting to make an awesome peak meter.
I am trying to decouple my editor's redraw rate from the host's rate. So far, the graphics refresh is timer based, so there is no coupling there. EXCEPT, in the case of utilizing a variable that is updated inside the processReplacing() loop. Is there a way to push variables out of, or pull variables into the editor from processReplacing asynchronously? Or will processReplacing guarantee completion before other functions are called? In case you are wondering, I am attempting to make an awesome peak meter.
- KVRAF
- 8476 posts since 12 Feb, 2006 from Helsinki, Finland
For most purposes you can expect there to be two threads, one for audio, one for GUI. Which functions run in which threads isn't exactly very well documented, but generally you can assume that resume/suspend/processReplacing/Events run in audio thread (so only one is active at any given time) and anything to do with the editor runs in GUI thread.
Then there's stuff like set/getParameter/Label/Display which can pretty much be called in any thread at any time (and should probably be re-entrant too). There's also a lot of gray matter that one would expect (by common sense) to run in one thread or another but you probably shouldn't assume too much (eg hosts might change presets from GUI thread while processReplacing() is running or other similar stuff that you probably should be prepared for).
Anyway, basically this means that audio thread does audio processing and GUI thread does GUI interaction and drawing and these two go on completely asynchronously. The audio thread runs at high (usually RT) priority and obviously shouldn't ever have to wait for GUI thread (which usually runs at default priority; high priority threads so never wait for lower priority threads). Waiting for the audio (in GUI thread) is generally fine as long as the critical sections (ie the duration of wait) is sensible (holding a lock for the whole processReplacing() is not a good idea).
Now, you can really use either push or pull or a combination of both (which is what I do).
The way I do it is basically to keep a local copy of data in audio thread, then at the end of every processReplacing() I attempt to take a lock (but without waiting; eg POSIX calls it trylock() and in Windows you specify zero timeout so it fails if it can't succeed immediately). If the lock succeeds, the data is copied into a shared buffer (if not, we'll just try again next processReplacing() call). In GUI timer then, I take the same lock (except this time it's fine to wait since audio is higher priority) and copy the shared buffer to a GUI local buffer.
So there's the "audio local" buffer that's always up-to-date. Then there's the "shared" buffer that's updated regularly, but can sometimes miss an update. Then there's the "GUI local" copy of whatever was in the shared buffer during the last timer interrupt; the last copy is just so the "GUI side" critical section (ie duration the lock is held) is the minimum possible (eg a single memcpy of the buffer) which minimizes the chance of audio thread not being able to get the lock when it wants (one would miss a whole lot more updates if the lock was held for a complete redraw; while missing updates might sound bad it doesn't make much difference and in practice it almost never happens anyway if the critical sections are short).
If you don't mind adding a bit of latency to the pipeline you can further optimize it by adding a flag (protected by the above mentioned lock) that the GUI sets to notify that more data is needed, and the audio clears to notify GUI that new data is available. Doesn't matter for a peak meter but could be useful if you're copying more data around.
The above isn't the only way (nor do I claim it's the best way) to handle the issue, but it has the nice properties that it (1) is safe and (2) can handle arbitrary blobs of data just fine (well, as long as the amount of data isn't huge; if you needed something like last 10 seconds of audio for a scope/spectrum you might want to do some additional logic to only copy the changes, but the general idea still works).
Then there's stuff like set/getParameter/Label/Display which can pretty much be called in any thread at any time (and should probably be re-entrant too). There's also a lot of gray matter that one would expect (by common sense) to run in one thread or another but you probably shouldn't assume too much (eg hosts might change presets from GUI thread while processReplacing() is running or other similar stuff that you probably should be prepared for).
Anyway, basically this means that audio thread does audio processing and GUI thread does GUI interaction and drawing and these two go on completely asynchronously. The audio thread runs at high (usually RT) priority and obviously shouldn't ever have to wait for GUI thread (which usually runs at default priority; high priority threads so never wait for lower priority threads). Waiting for the audio (in GUI thread) is generally fine as long as the critical sections (ie the duration of wait) is sensible (holding a lock for the whole processReplacing() is not a good idea).
Now, you can really use either push or pull or a combination of both (which is what I do).
The way I do it is basically to keep a local copy of data in audio thread, then at the end of every processReplacing() I attempt to take a lock (but without waiting; eg POSIX calls it trylock() and in Windows you specify zero timeout so it fails if it can't succeed immediately). If the lock succeeds, the data is copied into a shared buffer (if not, we'll just try again next processReplacing() call). In GUI timer then, I take the same lock (except this time it's fine to wait since audio is higher priority) and copy the shared buffer to a GUI local buffer.
So there's the "audio local" buffer that's always up-to-date. Then there's the "shared" buffer that's updated regularly, but can sometimes miss an update. Then there's the "GUI local" copy of whatever was in the shared buffer during the last timer interrupt; the last copy is just so the "GUI side" critical section (ie duration the lock is held) is the minimum possible (eg a single memcpy of the buffer) which minimizes the chance of audio thread not being able to get the lock when it wants (one would miss a whole lot more updates if the lock was held for a complete redraw; while missing updates might sound bad it doesn't make much difference and in practice it almost never happens anyway if the critical sections are short).
If you don't mind adding a bit of latency to the pipeline you can further optimize it by adding a flag (protected by the above mentioned lock) that the GUI sets to notify that more data is needed, and the audio clears to notify GUI that new data is available. Doesn't matter for a peak meter but could be useful if you're copying more data around.
The above isn't the only way (nor do I claim it's the best way) to handle the issue, but it has the nice properties that it (1) is safe and (2) can handle arbitrary blobs of data just fine (well, as long as the amount of data isn't huge; if you needed something like last 10 seconds of audio for a scope/spectrum you might want to do some additional logic to only copy the changes, but the general idea still works).
-
- KVRAF
- Topic Starter
- 7578 posts since 17 Feb, 2005
Thanks for the help mystran. I didn't know VST is already multi threaded. Is it the same on Mac OS X?
My original idea was to use a circular buffer and iterator that loops around the end of an array, with the index being incremented per sample. Is this thread safe? Because the way it works now, the values it needs from processReplacing are only received at the end of the block. I want the editor to receive those values, on its interrupt. I don't understand multi threading, but thanks for any advice.
My original idea was to use a circular buffer and iterator that loops around the end of an array, with the index being incremented per sample. Is this thread safe? Because the way it works now, the values it needs from processReplacing are only received at the end of the block. I want the editor to receive those values, on its interrupt. I don't understand multi threading, but thanks for any advice.
