xlib, 64bit, pascal

Programming applications for making music on Linux.

Moderators: MattKingUSA, khz

Post Reply
User avatar
skei
Established Member
Posts: 337
Joined: Sun May 18, 2014 4:24 pm
Has thanked: 8 times
Been thanked: 57 times
Contact:

xlib, 64bit, pascal

Post by skei »

argh... i have been tearing my hair out over this..

i am (always) working on my vst plugin library/framework, and currently i'm trying to get the linux version up to the same 'level' as the windows version.. but i have some serious problems with xlib (i think)..

if i compile a standalone (64bit) binary, the gui and everything works fine, no problems at all..
but when i compile a (64bit) vst plugin (.so), things doesn't look so good.. the behaviour is different in various hosts, so it's hard to find out what's going wrong.. for example, in jost, everything seems to work, haven't been able to crash the plugin(s), and the editor pops up, and works as intended.. but in other hosts - carla, qtractor, bitwig studio, ardour, etc.. it randomly crashes either during the first time the editor/window is opened, or the second time, or some time later.. always during opening.. or the window comes up 'blank'/transparent/unpainted..

i've been suspecting something related to the async nature of xlib, and have added tons of checks and asserts and debugging stuff to try to narrow down the problem.. but no luck so far.. or.. could it be that xlib has some issues in 64bit shared libraries that i'm not aware of?

the code is almost 100% identical to the code i have used in earlier incarnations of the library (axonlib, holos, etc), except that it's now 64bit.. and that it doesn't work reliably.. :-/

so, what do i do now?

i am getting really frustrated by this, and i'm tempted to just forget about it, and concentrate on the windows version(s) instead.. but i don't want to give up just yet..

is there any xlib gurus/experts here that could help me? maybe there's some arcane xlib knowledge that i haven't grasped yet, perhaps it's a xlib vs 64-bit issue.. perhaps something completely different.. just some pointers to things to check, and common pitfalls, or problems, or similar, would help a lot..

here's my library:
https://github.com/skei/kode.pas/
and specifically, here's the window code:
https://github.com/skei/kode.pas/blob/m ... _linux.pas
(where i suspect the problem could be)

it's in pascal, but i don't think it should be too problematic to understand for c/c++ coders, as pascal is quite verbose, and i have tried to keep the code as understandable as i can (well.. tried..)..

anybody?

- tor-helge
User avatar
skei
Established Member
Posts: 337
Joined: Sun May 18, 2014 4:24 pm
Has thanked: 8 times
Been thanked: 57 times
Contact:

Re: xlib, 64bit, pascal

Post by skei »

falktx:

i'm using the 2.4 sdk (and only the low-level structs (aeffect) and defines, not the class layer on top).. and, no, i haven't considered other frameworks.. one thing is that i code in pascal (i don't like c++ very much, even if i have used it both professionally and personally for 20 years or so).. another thing is that i don't like bloat.. in my eyes, a 1mb plugin is seriously bloated :-) .. a third thing is that i don't want any locks or mutexes or whatever.. my library/framework is 100% lock/wait-free (i have spent sooo long making sure (as sure as i can be) this is thread safe and stable).. and i want the plugins to be compileable both for linux and windows, for standalone binaries and vst plugins.. and 32 and 64 bit, without any source code changes.. hmm..

but thanks for the dpf link/info.. i will take a look!

- tor-helge
User avatar
skei
Established Member
Posts: 337
Joined: Sun May 18, 2014 4:24 pm
Has thanked: 8 times
Been thanked: 57 times
Contact:

Re: xlib, 64bit, pascal

Post by skei »

hmmm.. that sounds very interesting..
i will definitively take a closer look at dpf! :-)

currently, i'm just responding to effEditIdle events/opcodes in the (vst) dispatcher..
i have a separate thread for the gui that handles the events (calling XNextEvent and then (after some checks and stuff) sending the events to the 'real' event handling method in my window/editor class)..
i also keep a back buffer updated all the time, and only blit (parts of) this buffer to the screen (gui window) in response to Expose events (using XCopyArea)
when i tweak a knob or slider or something in the gui, i redraw the widget in the back buffer, and fire of an Expose event, so that the event handler can blit the updated part to the screen when it wants/needs to/have time (when we receive the Expose event)..

i also have a timer thread coded and ready, that i used in axnlib/holos.. but i wanted to get the simplest version using effEditIdle working first.. additionally i have a cairo graphics backend (almost) ready, but it still needs a window and some surfaces to draw to, so i would need some of the basic xlib stuff anyway, wouldn't i?

there's one thing i have been thinking about the last few hours..

i have read several places that i should wait until the first Expose event (or MapNotify) before drawing anything to the screen (to be sure the window actually exist in the xlib server), but since i only draw to the screen in response to Expose events, i don't think that should be a problem?

but.. during the window constructor, i create a Pixmap, using the Display pointer, and the Window as the Drawable (xlib docs says: "The server uses the specified drawable to determine on which screen to create the pixmap. The pixmap can be used only on this screen and only with other drawables of the same depth."), and use that as the back buffer, blitting from this to the screen with XCopyArea.. does the window need to be ready and fully created before XCreatePixmap works? does it need to be mapped/visible? i don't get any error messages, and i also tried to insert tons of XFlush and XSync things everywhere, without much difference..

- tor-helge
User avatar
skei
Established Member
Posts: 337
Joined: Sun May 18, 2014 4:24 pm
Has thanked: 8 times
Been thanked: 57 times
Contact:

Re: xlib, 64bit, pascal

Post by skei »

hmmm... i'm getting closer... i think..
i am able to make it pretty stable in bitwig studio now, by calling XInitThreads..
it hasn't crashed yet, after countless rapid open/close editor cycles..
promising!
but calling XInitThreads makes the plugin unstable in other hosts (especially carla)..

without xinitthreads:
* bitwig: crash
* qtractor: sometimes hangs
* carla: crash if i open/close editor too quickly..

with xinitthreads:
* bitwig: seems to work quite well!
* qtractor: works, but when i close, i get a segmentation fault
* carla: crashes almost immediately "/usr/bin/python3: munmap_chunk(): invalid pointer"

the crashing/hanging is always during opening/closing of the editor..

so...

should i call XInitThreads?
and maybe this indicate that the problem is related to some threading/concurrency issues?

---

let's see...

* i open a display conection in the window constructor (separate connection per window)
* then i create the window and the backbuffer
* and then i create and start a event handler thread (passing a ptr to the window class as argument, so it can access the window's display pointer, and event handler, etc)
* the event threads calls XNextEvent in a loop (using the window's display connection), and calls the 'real' event handler in the window class when there's incoming events..

then, in this event handler, depending on the event type:

ConfigureNotify:
- if the window size has changed, i resize the back buffer, and repaint it, but i don't blit it to the screen (yet)..
(doesn't happen much in a vst plugin..)

Expose:
- just blit (parts) from back buffer to screen

and..
when i tweak a knob or other widgets on the gui, i repaint the widget directly to the backbuffer (mouse events are in the gui thread, right?), and sends a Expose event to the window.. which will be caught in the event thread loop (Expose), when the blitting is done..

does this sound ok?
any obvious threading errors?

- tor-helge
User avatar
skei
Established Member
Posts: 337
Joined: Sun May 18, 2014 4:24 pm
Has thanked: 8 times
Been thanked: 57 times
Contact:

Re: xlib, 64bit, pascal

Post by skei »

thanks for replying and having patience with me..

yeah, i agree it must be some threading issues...
i didn't have the xinitthreads call in earlier lib versions either, i just tried it now again so that i could cross it off my list of things i hadn't tried yet.. and i was surprised that, with the call, the plugin worked in bitwig, while without it, it crashed almost immediately.. well, well.. i will remove it, since the problem is probably elsewhere.. i also just sent bitwig support an email and asked about this, so we'll see..

i'm a bit confused..
what do you mean with get rid of the custom event thread and use effEditIdle instead?
you don't mean i should call XNextEvent in effEditIdle?

is linux (posix?) threading very different from windows? i use an almost exactly identical setup there, same kind of backbuffer (dib-section), paint to this buffer when needed (via effEditIdle or mouse events), and blit to screen in response to WM_PAINT events.. and it works flawlessly.. but i don't use a separate thread for events there, since win32 uses a message callback system.. you give it a pointer to your calback when you register your windows class, and then you start receiving events there.. but apart from that, the system is similar..

well, well, back to more testing and debugging...

i WILL get this working in the end! :-)

- tor-helge
User avatar
skei
Established Member
Posts: 337
Joined: Sun May 18, 2014 4:24 pm
Has thanked: 8 times
Been thanked: 57 times
Contact:

Re: xlib, 64bit, pascal

Post by skei »

falkTX wrote:is there a reason why *everything* can't be ran without the use of extra threads?
i want mouse interaction to be smooth, and feel instant.. but it doesn't matter that much if automation from the host (setParameter) is a little slower (effEditIdle rate).. so, i generally use a thread to collect xlib events, and pass them to the window.. where else can i receive these events? there's no main- or render-loop or anything in a vst plugin.. and effEditIdle rate varies a lot from host to host.. for example, it seems like qtractor only calls effEditIdle around 4-5 times per second (!).. if mouse interaction was that choppy, a gui would be useless..

the setup is based on a discussion i had with kraken and linuxdsp some years ago, when the anticore/jucetice forums was online/active.. it's gone now, but i copied it into a txt file:
Here's how I've implemented linux VST plugins - I've had working
prototypes in various hosts (energyXT, renoise, qtractor - apart from a window
sizing bug which is now fixed in my build - and JOST).

I don't use any toolkits e,g, Qt or GTK - I've written my own AudioFX GUI engine
to use the smallest subset of XLib necessary. (Beware XLib programming can drive
you insane!) sounds like this might be a similar principle to your axonlib.

I don't call XInitThreads and I don't assume that the host does (in fact
qtractor explicitly doesn't and my plugins work fine in that too) I am sure that
the main purpose of XInitThreads is to stop different threads trying to talk
down the same display connection to X which is where the real problems lie.
XLib seems to be thread safe as long as you make sure you have a separate
display connection for each thread. The problems occur if two threads talk on
the 'wire' at the same time, then because the X protocol is not atomic you get
garbled messages. This is the main reason to use a separate display* to
reference each thread connection to X.

If you try and use XInitThreads you also need XLockDisplay and its counterpart,
you would also need to be sure that the host played nicely in this respect which
again is not guaranteed.

I start a GUI thread for each plugin instance (this may seem extravagant, there
is a limit to the number of threads that can run concurrently but in practice
you are more likely to run out of other CPU resources before this happens).

All access to the GUI elements in that plugin instance must then go through that
thread. This takes some care to engineer, for example you must not update the
GUI directly in response to a setParameter, but instead mark the control as
'dirty' in some fashion and rely on an Idle callback in the GUI thread to do
the house-keeping. This can be the same Idle routine that handles meter updates
etc.

All the controls are rendered to a background pixel buffer parts of which are
then copied to the visible window in response to normal expose events (or forced
expose events if / when a control needs to be updated e.g. if we know its value
has changed).

There were some concerns raised in another post about re-parenting windows - you
can access any window through any display connection, they are not tied together
- the XWindow ID is allocated by the Xserver, it doesn't matter how you get to
it, so you can still reparent your plugin GUI into the host even though the host
is on a different display, just so long as you don't mix up the display
connections when you do the re-parenting otherwise the plugin GUI thread will
attempt to talk down the hosts connection to X and cause problems.

The other thing that made me try running the plugin GUI in its own thread rather
than the method in jorgens plugins was that (I think) the pointer to the Xevent
handler is passed to the host as an atom used as a window property on the
plugins window. In the implementation I looked at, this was a 32Bit value which
limited the mechanism to only working on 32Bit hosts. I wanted to try and come
up with a method that would work on either 32 or 64Bit architectures.
- tor-helge
User avatar
skei
Established Member
Posts: 337
Joined: Sun May 18, 2014 4:24 pm
Has thanked: 8 times
Been thanked: 57 times
Contact:

Re: xlib, 64bit, pascal

Post by skei »

aaahhh...
i think i solved it!
as falkTX hinted, effEditIdle was running 'in parallell' with my event thread, both calling into xlib..
i changed that so i'm not relying on effEditIdle, and so far it seems very stable in bitwig, qtractor and carla..
:-)
User avatar
skei
Established Member
Posts: 337
Joined: Sun May 18, 2014 4:24 pm
Has thanked: 8 times
Been thanked: 57 times
Contact:

Re: xlib, 64bit, pascal

Post by skei »

now i just need to get cross-compilation (to 32bit) correctly set up, and then i can finally post the full set of test-plugins..
:-)
syn_s2.png
syn_s2.png (93.05 KiB) Viewed 1113 times
User avatar
skei
Established Member
Posts: 337
Joined: Sun May 18, 2014 4:24 pm
Has thanked: 8 times
Been thanked: 57 times
Contact:

Re: xlib, 64bit, pascal

Post by skei »

the gui stuff seems pretty stable now!

i just did an intense session in bitwig, where i rapidly clicked the open/close editor button as fast as i could, for 10 minutes or so, and also, changed the intervals and added some delays and whatnot.. and it still didn't crash.. hooray! :-)

i also did some tests with qtractor, carla and jost.. but not as thoroughly as with bitwig.. i will also do some more testing with ardour, tracktion, renoise, etc..

but i always have a little voice in the back of my head telling me that i have forgot about something, or completely misunderstood something, and i'm just lucky that it works here..

i tried to go through the xlib things that happens during window open/close/idle..
does anybody see any obvious flaws, or can think of situations where this could fall apart?
there are some more stuff happening (like flags and variable checking, etc), but i tried to simplify it as much as i could, to focus on the xlib stuff..

when i open the plugin editor:

Code: Select all

* i open a separate display conection for every plugin/editor instance
* i paint to the backbuffer pixmap before i open the editor and start the event thread
* i start the event thread in window.show()

window constructor:
  XOpenDisplay
  XCreateWindow
  XCreatePixmap  
  XCreateGC  
our editor: 
  paint (pixmap/gc)
  XSendEvent (expose) // will this expose event be lost?
window.show:
  XMapRaised
  BeginThread


the idle/loop event thread:

Code: Select all

* the event thread checks if there are pending events, and if so, pass them over the the window class event handler
* i delete, and recreate the backbuffer pixmap (and gc) when/if the window resizes
* i blit/copy from the backbuffer to the window/screen only in response to expose events
* after checking pending events, i post a 'idle' message, and goes to sleep for 30ms or so.. then wake up, and repeat the process..
* i don't use effEditIdle.. 

event thread:
  XPending
  XNextEvent
window.eventHandler:
  - ClientMessage (idle):
      paint (pixmap/gc)
      XSendEvent (expose)
  - ClientMessage (kill):
      EndThread
  - ConfigureNotify (resize):
      XFreePixmap
      XFreeGC
      XCreatePixmap  
      XCreateGC  
      paint (pixmap/gc)
      XSendEvent (expose)
  - Expose (paint):
      XCopyArea (pixmap -> window)
timer/idle:      
  XSendEvent (idle)
  sleep
when i close the editor:

Code: Select all

* not sure if i should stop the event thread before or after i unmap the window..

window.hide:
  XUnmapWindow
  XSendEvent (kill)
  (wait for thread to finish)
window.destructor:  
  XFreePixmap
  XFreeGC
  XDestroyWindow
  XCloseDisplay
additionally,i have some boolean flags that i set/clear:

Code: Select all

* FMapped and FExposed is set to false in the window constructor
* FMapped is set to true when i receive a MapNotify event
* FExposed is set to true when i receive the first Expose event
* situations where i check these, and only do things if both are true:
  > XCopyArea to window/screen
  > editor.idle (redraw changed parameters, update scopes, etc)
  > XSendEvent(expose)
if you want to test it, here's a 64bit linux version of fx_cutter.. try to make it crash or misbehave :-)

[edit: wrong link.. here's the correct one]
https://www.dropbox.com/s/jflwzb6ispav5 ... 64.so?dl=0

unless i receive some bug/crash/unstability reports, i'll soon post a pack of around 30 plugins.

- tor-helge
Post Reply