Direct2D

Turns out the clipping needed a lot of attention, especially using transformed components (e.g. ComponentTransformsDemo). Direct2D does support rectangular clipping, but that clips differently when transformed (see PushAxisAlignedClip), so that won’t work.

I rewrote all the clipping to use geometry layers, which cleaned the code up nicely. I’ll need to test the performance more thoroughly, but so far it works fine.

I found a bug in the path rendering; every call to ID2D1GeometrySink::BeginFigure needs to be matched with a call to EndFigure, but sometimes JUCE paths can have a startNewSubPath marker without a matching closePath (see the toolbar tab in the WidgetsDemo). That just needed a little extra logic.

restoreState wasn’t restoring the last brush color; that’s fixed. There are probably other issues with popping the Graphics stack and restoring the previous state properly.

All pushed to Github.

Matt

3 Likes

I turned on the Direct2D debug messages which identified some object leaks and a performance improvement with clip layers. All sorted.

Matt

2 Likes

I’ve made a few more fixes to clean up the custom font collection loaders, and fixed a minor issue where painting an AttributedString would clip the bottom of the font descenders.

I think this is now at the point that I can start using it for my apps. If anyone else would like to try it, bug reports and performance reports would be most welcome. Note that this will only work on Windows 8 or later.

The next phase is to switch to ID2D1DeviceContext to take advantage of D2D1_DEVICE_CONTEXT_OPTIONS_ENABLE_MULTITHREADED_OPTIMIZATIONS and better path rendering.

The main performance issue is that the Direct2D renderer has to create a lot of temporary geometry and brush objects and then discard them. We could get better performance with Direct2D if JUCE graphics objects had a platform-specific peer, similar to a JUCE ComponentPeer. For example, if a JUCE Path also had a PathPeer, then each component class could retain the PathPeer as a member and not have to recreate it for every paint call. But I don’t know how that might affect other renderers, such as Vulkan or OpenGL.

Anyhow, I hope this proves useful to someone. Enjoy!

Matt

2 Likes

I’m no expert in Direct2D whatsoever, but for the Path → PathPeer matter, perhaps you can

  1. inherit from Path a derived class named PathWithPeer that holds a platform-agnostic pointer to such peer
  2. Inside your Direct2D code, when you deal with a Path, if you can dynamic_cast it to a PathPeer, you can store the peer there and reuse it in later renders
  3. if this proves to really improve rendering performance, that will represent a strong argument in favour of moving the “peer” member up in the Path base class in JUCE code

Sure, that could work. I was also considering using a dynamic cast with Graphics::getInternalContext to get direct (no pun intended) access to the Direct2D renderer.

To your point #3, I think for now I’ll focus on benchmarking to see how much a difference all this really makes.

Matt

I’ve updated the renderer to support Direct2D 1.1 and JUCE 6.1.

Direct2D 1.1 requires Windows 8 or later. This change should open up access to some of the newer Direct2D features like faster path rendering and changing the image resampling quality.

Matt

3 Likes

I’ve been lurking this thread long time ago, and I like to test it in my real-world app with hundreds of components and paths. However since I have Juce hacked (to allow things like capturing right key modifiers), I’m not sure how to test it. Should it be enough with downloading your Direct2DLowLevelGraphicsContext? or did you modify another files?

1 Like

Hi @juan1979 - thanks for offering to check this out.

Since you have your own modified version of JUCE, I recommend you update your fork to JUCE 6.1.2 and then merge my direct2d branch into your fork.

https://github.com/mattgonzalez/JUCE/tree/direct2d

I had to change several files:

modules/juce_graphics/juce_graphics.cpp
modules/juce_graphics/native/juce_win32_Direct2DGraphicsContext.cpp
modules/juce_graphics/native/juce_win32_Direct2DGraphicsContext.h
modules/juce_graphics/native/juce_win32_DirectWriteTypeface.cpp
modules/juce_graphics/native/juce_win32_DirectWriteTypeLayout.cpp
modules/juce_gui_basics/native/juce_win32_Windowing.cpp

and I added one new file:

modules/juce_graphics/native/juce_win32_DirectWriteCustomFontCollection.cpp

Once you’re finished merging, you’ll need to set JUCE_DIRECT2D=1 in your preprocessor definitions.

Direct2D is not enabled by default, so add this to your main window constructor:

getPeer()->setCurrentRenderingEngine(1);

Alternatively, you could add a renderer select control, similar to the JUCE DemoRunner.

Enjoy!

Matt

Great, I will test this weekend and I will report you back, thanks!

There are still some bugs lurking:

  • The clipping region reported by Graphics::getClipBounds doesn’t always match between the software renderer and Direct2D mode.

  • Sometimes if the app window is occluded and then brought to the front, the child components don’t paint.

There are probably other unresolved issues; please consider this to be a beta, at best. There’s still lots of benchmarking to be done.

Matt

Be sure to grab the latest commit; turns out the Direct2D 1.1 swap chain I’m implementing always needs to render the entire window. I think I can improve on that using a different swap mode, but this should at least keep entire components from vanishing.

Matt