Zeppelin - cross-platform 2D graphics

Zeppelin 0.2.0 is out which adds clipboard support:

1 Like

This gives me a fun idea. In one of your streams I could teach/help you write a win32 GUI executable in Zig. I think alot of people would find that fun/educational.

2 Likes

I doubt Andrew has a windows machine at home, so you’d have to use wine, which doesn’t have the nice main-thread-blocking behaviour that windows has, which kinda defeats the purpose :grin:

Nonsense, Wine works great for basic win32 programming.

Sounds fun! I think I’ve done that before but it was many years ago.

I got bit by this once again today :slight_smile:

Looks like the function you call to get Video Sources (like webcam devices) MFEnumDeviceSources actually enters its own modal loop! Freaking nuts! Here’s the relevant part of the stack trace:

... ends up sending WM_PAINT to one of our windows, which, needs the list of webcams, but they are currently in the middle of being updated ...

USER32+0x000b642 UserCallWinProcCheckWow
USER32+0x000ad1b DispatchClientMessage
USER32+0x0047322 _fnDWORD
ntdll+0x0163813 KiUserCallbackDispatch
win32u+0x00018b3 ZwUserDispatchMessage
USER32+0x0009337 DispatchMessageWorker
combase+0x007ab1b CCliModalLoop::MyDispatchMessage (callctrl.cxx:2946)
combase+0x007ab1b CCliModalLoop::HandleWakeForMsg (callctrl.cxx:2276)
combase+0x007b392 CCliModalLoop::BlockFn (callctrl.cxx:2186)
combase+0x007af98 ModalLoop (chancont.cxx:169)
combase+0x0027e35 ThreadSendReceive (channelb.cxx:7272)
combase+0x0027e35 CSyncClientCall::SwitchAptAndDispatchCall (channelb.cxx:5748)
combase+0x0027e35 CSyncClientCall::SendReceive2 (channelb.cxx:5310)
combase+0x007c7a1 SyncClientCallRetryContext::SendReceiveWithRetry (callctrl.cxx:1493)
combase+0x007c7a1 CSyncClientCall::SendReceiveInRetryContext (callctrl.cxx:581)
combase+0x007c7a1 ClassicSTAThreadSendReceive (callctrl.cxx:563)
combase+0x006fcf9 CSyncClientCall::SendReceive (ctxchnl.cxx:797)
combase+0x002adb2 CClientChannel::SendReceive (ctxchnl.cxx:674)
combase+0x002adb2 NdrExtpProxySendReceive (proxy.cxx:1899)
RPCRT4+0x00d85e0 NdrpClientCall3
combase+0x010eb95 ObjectStublessClient (stblsclt.cxx:289)
combase+0x0249f91 ObjectStubless (stubless.asm:175)
combase+0x0049c02 CRpcResolver::DelegateActivationToSCM (resolver.cxx:2221)
combase+0x006a73d CRpcResolver::GetClassObject (resolver.cxx:2307)
combase+0x006a73d CClientContextActivator::GetClassObject (actvator.cxx:451)
combase+0x006bc66 WinRTGetActivationFactoryOfOutofprocClass (objact.cxx:2327)
combase+0x00a2ac6 _RoGetActivationFactory (winrtbase.cpp:1133)
combase+0x00a23be RoGetActivationFactory (winrtbase.cpp:1232)
CompPkgSup.DLL+0x000c65d GetMediaComponentPackageInfoForApplicationExtension
CompPkgSup.DLL+0x0017b0b GetMediaComponentPackageInfoInternal
CompPkgSup.DLL+0x0017922 GetMediaComponentPackageInfo
MFPlat.DLL+0x00ba2ae EnumPackagedMFTs
MFPlat.DLL+0x00a495c MFTEnumEx
MFCORE.DLL+0x028cc5e EnumLegacyDshowCustomSources
MFCORE.DLL+0x01512a0 EnumVideoCaptureSources
MFCORE.DLL+0x00fa379 MFEnumDeviceSources
tuple+0x1233444 WebcamDeviceIterator::init (WebcamDevicesWin32.cpp:77)
tuple+0x1231703 update_devices<T> (UpdateDevices.h:62)
tuple+0x12325e3 Devices<T>::update (UpdateDevices.h:140)

Note the order is earlier parts of the trace appear lower. What happens is we want to update our list of webcam devices (see update_devices in the trace). While doing so, we set a variable as a sanity check to prevent recursive updates (updating the list while in the middle of an update). So we call MFEnumDeviceSources to get the list of devices, but, to my surprise it looks like this function actually enters its own modal loop! Inside this modal loop we got a WM_PAINT message to repaint our window showing the webcam devices. This code was asserting that we aren’t in the middle of updating the devices because how could possibly happen? Well, a recursive modal loop, that’s how!

Anyway this is another data point for me that there are a lot of unexpected behaviors with the Win32 API, and, you really can’t avoid these modal loops, it just seems to be how the OS is designed. Even though they make things complicated, it’s best to know they exist and figure out the best interfaces to work with them.

3 Likes

Been working on window moving and got something working but both SetWindowPos and MoveWindow don’t move the window in the same frame as the mouse moves. So even with a 0.3ms framerate the window lags behind the mouse (even though barely noticable). Which is not a huge problem, but on low framerate it doesn’t feel great.

I will put some more effort into it but I’m leaning more and more towards a multi-threaded approach which would also solve your video source problem.

1 Like

Nice. If you have what you’re working on in a sharable commit/branch I can take a look and see if I have any suggestions to improve the framerate etc.

Using multiple threads is something I have not explored, maybe this can provide a solution, if so please let us know! (if it does work maybe I’ll use the same technique in the win32 raylib backend I’ve been working on)

You can find the code here:

It’s not testable out of the box though:

  • SC_MOVE code is commented out
  • the WIP software backend is chosen in zeppelin.zig, as I mainly test in a vm; it only draws point vertices at the moment
  • you might want to comment out sleep() in demo.zig, as it’s precision is very bad on windows
  • FPS for software+windows is capped to 60 FPS in windows.zig

I can fix it tomorrow for you to explore.

However, what’s happening looks a bit like this:

  • wm_syscommand / sc_move
  • handled either continuously with SetWindowPos() OR DefWindowProc()
  • return from getEvents()
  • render()
  • screen composed with new mouse position but old window position
  • display sync
  • (if custom handling, window position is updated from SetWindowPos() call only now and therefore lags behind)

If I could set the window position immediately like DefWindowProc(wm_syscommand) does, there would be no issue. However, the implementation could vary from windows version to version and I wasn’t able to find the implementation of neither wm_syscommand nor SetWindowPos() in user32.dll.

The issue is definitely not coming from the event loop taking too long, as it was still happening within a 0.3ms (3000FPS) loop. Suggestions welcome.

Ah ok, I downloaded your branch and uncommented the SC_MOVE implementation and it actually worked well for me. I didn’t see anything right off the bat as to what might be causing an FPS slow down.

I’m really interested to see if you can figure out what the default WM_SYSCOMMAND loop is doing and if you can figure out how to get window snapping to work with this!

The FPS are fine, it’s just that the window position isn’t synchronized to the mouse pointer.

Custom move handler:

Default move handler:

If you call SetWindowPos() or MoveWindow(), it doesn’t immediately apply the repositioning but rather waits until after the next compositor frame or so, which is really annoying.

Looks like WM_SYSCOMMAND is implemented somewhere in kernel land…

I’m not going the chase it any further. Most of the win32 window API being syscalls also means there are no shortcuts to SetWindowPos() / MoveWindow() to get around the latency and no way to manually start window snapping, meaning, the only solution left is the multi-threaded approach. :melting_face:

1 Like

Ah that explains some things. Thanks for the update and I look forward to seeing how the multi-threaded approach works.