OpenGLContext on top of a VideoComponent

Hello everyone!

I have noticed something weird when trying to place an OpenGLContext on top of a VideoComponent.

On MacOS, this seems to work as planned.

Instead, on Windows, the VideoComponent is always positioned on top of the OpenGLContext, despite they are clearly stated to be positioned the other way around:

Given the two Components:

  1. VideoComponent videoComponentObj
  2. OpenGLObj openGLObj (OpenGLObj is a very simple class that inherits from OpenGLRenderer and draws a yellow background).

In the MainComponent, the code that I am using is very straightforward: the addAndMakeVisible(videoComponentObj) call is followed by the addAndMakeVisible(openGLObj)

Is there a way to put an OpenGLContext on top of a VideoComponent on Windows?

Forgot to put the image for MacOS (which shows that the same code is correctly working):

Does anyone know anything about this?

I suspect this is because both the OpenGL component and the VideoComponent are implemented using ‘heavyweight’ views provided by the OS, so they may not behave quite like ‘normal’ components. In particular, I’m not sure whether this z-ordering can be expected to work as-is. Because the heavyweight views are wrapped inside other components, attempting to modify the z-order of the components themselves may not have an effect on the inner heavyweight views.

I haven’t tried the following, so I’m not absolutely sure that it will work. That said, the first thing I’d try is to use Component::addToDesktop to add the OpenGL and Video components to the top-level peer’s native handle (getPeer()->getNativeHandle()). Then, you could use Component::toFront to bring either of the components to the front. toFront explicitly checks for the case that the component itself is backed by a heavyweight view, and should do the right thing.

You would also need to make sure the OpenGLContext is made transparent otherwise it will be on top but you still won’t see anything even if you are rendering transparent black.

Check this topic i created years ago:

But in the end i’m afraid that if you really want to start mixing OpenGL and video playback you should consider using FFMPEG or a similar framework to get access to the raw video frames so you can upload them as textures, once you have this you can do whatever you like with the video data and the fun part starts. I know it is not a trivial task but eventually this is the way to go, there is plenty enough resources/projects out there that will help you get started.

A quick search revealed this repo:

1 Like

I am trying to do so:

auto styleFlags = getPeer()->getStyleFlags();
videoComponentObj.addToDesktop(styleFlags, getPeer()->getNativeHandle());
openGLObj.addToDesktop(styleFlags, getPeer()->getNativeHandle());
openGLObj.toFront(false);

But I am hitting this assertion:

// it's not possible to have a transparent window with a title bar at the moment!
            jassert (! hasTitleBar());

I then tried to set both objects to be opaque - setOpaque(true) - which then hits this other assertion:

// if your component is marked as opaque, you must implement a paint
    // method and ensure that its entire area is completely painted.
    jassert (getBounds().isEmpty() || ! isOpaque());

Also, I have noticed that the addToDesktop is creating a whole new window of the application, while I just need to add the Components to the existing main window. Am I using addToDesktop correctly? What is its correct usage?

I don’t think you need to set any flags at all - you can just pass ‘0’ instead.

Have you checked the result of getPeer()->getNativeHandle()? Is it definitely non-null at the point where you call it? You may need to override parentHierarchyChanged and wait until getPeer()->getNativeHandle() returns a non-null pointer. You should also try passing flags of ‘0’, as some of the flags may cause the window to (attempt to) display as a new top-level window, rather than as a nested view.

I see. However, when I try this:

void MainComponent::parentHierarchyChanged()
{
    auto handle = getPeer()->getNativeHandle();
    if (handle)
    {
        videoComponentObj.addToDesktop(0, handle);
        openGLObj.addToDesktop(0, handle);
        openGLObj.toFront(false);
    }
}

I hit another assertion in juce_win32_Windowing.cpp in the createWindow() function. The newly created hwnd appears to be NULL if I do not use the same flags as the peer’s.

I think the sequence you need, for each component, is something like:

comp.setOpaque (true);
comp.setVisible (true);
comp.removeFromDesktop(); // in case it was already attached to a different window
comp.addToDesktop (0, nativeHandle);
comp.setBounds (...);

If I try that, I still end up at this assertion:

// if your component is marked as opaque, you must implement a paint
    // method and ensure that its entire area is completely painted.
    jassert (getBounds().isEmpty() || ! isOpaque());

I think this happens because the VideoComponent class does not implement a paint method.

However, if I resume the execution, the code appears to be working (until I try to resize, which triggers paint again). So, we’re definitely getting somewhere, thanks!

I tried to put comp.setOpaque(false) to solve the assertion issue, but then it would end up in this same assertion:

Is there a way that the top component can be transparent? Would I need to look into Windows API calls directly for that?

Perhaps you could try deriving from VideoComponent yourself and just overriding paint to fill the background.

I have done just that while I was replying :slight_smile:

Ok, last thing now: how could I make the OpenGL Component (the yellow box) on top of the VideoComponent transparent? I would like to use it to draw different shapes on top of a playing video.

Unfortunately I don’t think that’s possible.

I have noticed that I actually can create a transparent window with setOpaque(false) only if I am not adding it to another component, but to the desktop directly with openGLObj.addToDesktop(0).

However, if I do try to add it to my MainComponent with the openGLObj.addToDesktop(0, handle) call, this results in a jassert as the creteWindowsEx function returns a nullptr. This works if the window is opaque, and thus the WS_EX_LAYERED flag is not used in creating the child window.

Do you perhaps have any clues on to why the creteWindowsEx function would return a nullptr if the WS_EX_LAYERED flag is on for a child component?

The docs for WS_EX_LAYERED say:

The window is a layered window. This style cannot be used if the window has a class style of either CS_OWNDC or CS_CLASSDC .

The JUCE window uses a style of CS_OWNDC, and I suspect that’s why window creation fails. Perhaps it would be possible to modify JUCE to request a different class style in the case that the window is known to be a transparent child window. However, it would require some careful reading of the documentation and testing to determine which (if any) class style would be correct to use in this scenario.

Even then, I’m not sure whether OpenGL rendering will work as you expect. From a bit of searching online, people seem to struggle getting OpenGL to render into a WS_EX_LAYERED window. Apparently the window sometimes flickers, and the transparency may be applied uniformly to the entire window rather than on a pixel-by-pixel basis. I’ve seen suggestions to use the WS_POPUP style instead to work around these problems. It also looks like special flags may need to be passed when creating the OpenGL context in order to make it properly transparent.

In short, this isn’t currently supported in JUCE. It might be possible to get it working by using the WGL and win32 APIs directly, but even that seems quite difficult to get working and would require some experimentation and testing.

1 Like

I see, thanks! I’ll keep experimenting