Direct2D preview branch

Hello all.

We are delighted to announce that a preview of the new Direct2D (Windows) renderer is now available on the direct2d branch of the JUCE GitHub repository:

We would encourage anyone interested in GUI rendering performance on Windows (which is likely a large percentage of our users) to give the new renderer a try so that you are prepared for its introduction in JUCE 8.

Using Direct2D

All you need to do is to pull the JUCE repository and switch to the direct2d branch. Rebuild and run. The Direct2D renderer will be used by default on Windows

If you are interested in better optimizing your painting to work with Direct2D, please refer to this repository:

Here you’ll find PIPs that test the renderer as well as notes on how best to take advantage of Direct2D.

New features since the last preview

  • Direct2D windows can now contain child windows that use GDI or OpenGL to render. That means that legacy plugins that use the software renderer can be hosted inside windows that use Direct2D.

  • Image bitmaps are now cached in the GPU for significantly increased performance; there’s now a full implementation of NativeImageType and ImagePixelData for Direct2D.

  • Path objects are now cached in the GPU using geometry realizations, again for significantly increased performance.

Reporting issues

Please either reply to this thread, create a new topic using the Direct2D category, or submit an issue on @matt’s example repo on GitHub.

The implementation of the Direct2D renderer may change significantly between now and the official integration into JUCE 8. There are some non-standard additions used for profiling that likely will not last through the official merge, and we expect that broader testing will uncover issues that we will need to address.

34 Likes

I don’t know if you’re soliciting reports at this juncture, but in general it works fine. I do note, however, that the path stroking is quite a bit thinner than on macOS, Linux, and with GDI+. Thinner lines show aliasing and stepping and look quite “un-pro,” to the point where I’d have to do an #if WINDOWS situation, which is obviously counter to the entire concept of JUCE.

This is difficult to screenshot, as the Windows screenshot mechanism is different than the macOS one, looks fuzzy. But to recreate:

float myPixel = (float)getWidth() * 0.001f;
g.setColour(Colours::black);
g.fillall();
g.setColour(Colours::red);
[ make a path with some curves in it]
g.strokePath(p, PathStrokeType(myPixel, etc.), etc)

On Windows with the Direct2D branch this will be quite thin and aliased, but on Mac and Linux it looks fine.

Reports are welcome. Thanks for trying i tout.

How do you have your desktop DPI scaling set?

What is the value of myPixel?

Could you please provide the rest of the code in your example?

Alternatively, you could modify this example:

Try disabling caching for the path by calling Path::setCacheEnabled and see how it looks.

Matt

Hello, I’m trying to give this a shot but I’m getting this when building the project with the D2D branch. I’m not sure if it’s related but when I try clicking on the only binary image (png) in the project from within Projucer, it crashes instead of displaying the image. It seems like there’s an issue with zlib.h. Sorry if it’s something silly on my end. Thanks for taking a look.



Thanks. Switching to this, my debug-mode CPU usage dropped from 40% in (very bitmap+alpha-heavy) graphics code down to 5%, substantial improvement. Keen to see where it goes, cheers.
(edit: although it seems like a bunch of bitmaps are rendering in the wrong place, so i have to switch back to develop branch again unfortunately. known issue, or should i submit a bug report? thanks)

Yes, please provide more details on this thread.

I’ve got another update in the works with some nice optimizations. Stay tuned.

Matt

1 Like

Oops, missed this reply - I added info here: JUCE d2d - one drawImage API (partial image draw) results in bad behaviour · Issue #3 · mattgonzalez/JUCEDirect2DTest · GitHub

TLDR this API behaves incorrectly on my machine when sx != 0
Occurs with multiple desktop res/scaling settings. Nvidia 4070, if that’s useful.
Thanks.

void Graphics::drawImage (const Image& imageToDraw,
int dx, int dy, int dw, int dh,
int sx, int sy, int sw, int sh,
const bool fillAlphaChannelWithCurrentBrush) const

Try this patch:

drawImageOrigin.patch (1.4 KB)

1 Like

it’s late here - but i copied those two lines over manually and it seems to have fixed the issue. thanks for the quick turnaround! will give a proper test in the next day or two. cheers.

1 Like

I’ve got another one, I think. Haven’t confirmed yet though.

I have a failed assert in Image::BitmapData::BitmapData (const Image& im, BitmapData::ReadWriteMode mode) when loading data from a 17280 x 192 ARGB PNG (oops) using juce::ImageFileFormat::loadFrom(f);

A 12960x144 sized PNG is fine. I am pretty sure this works OK with the software renderer. I guess I’m hitting a texture size limit somewhere. That’s fine, it’d be great to get the assert closer to the failure point though if fixing it is intractable.

The problematic file is about 4MB, I can share it if required.

I’m going to shrink that UI element for now so I can proceed; I could also split the filmstrips over multiple files if required but that’ll add a bunch of complexity, and my current approach conceptually pretty elegant if a bit resource heavy.

Cheers, and thanks again for your work, the performance of the new renderer is crazy good. Really impressive improvement.

Thanks for the kind words!

The maximum bitmap size for Direct2D depends on the graphics adapter; looks like your adapter has a maximum texture size of 16384x16384.

I don’t have an immediate fix for this. I suppose the renderer could somehow virtualize a larger bitmap into multiple smaller bitmaps. That’ll take some thought.

You could use a software image by specifying SoftwareImageType, but I don’t think you can do that if the PNGImageFormat decoder.

Matt

I figured it’d be a hardware limit.

I have a few options - right now, a goofy option shrinking my control width by 20 pixels solves my issue, but I assume different video cards will have different behaviour?

I suspect the simplest option will be to generate the filmstrip as a grid instead of a strip, which will shrink my largest texture down to 1920x1728 without too much extra work. Are you able to provide guidance on safe texture limits? Thanks.

(update: fwiw, i have already moved to square tilemap grids instead of very wide/short filmstrips, so i’m already fixed here, thanks)

That sounds like the best approach for now.

I’ll add a check for the maximum bitmap size supported by the adapter.

Matt

1 Like

in case anyone else ends up here: i dug around a bit, it seems opengl 4.1 requires cards support textures 16384x16384, and back in 2013 apparently “4096^2 is a reasonable number if you want to account for really old graphic cards”.

Best to query the cards capabilities, that way you know for sure what the maximum is.

I added an assert to the bitmap creation code:

#if JUCE_DEBUG
                    //
                    // Verify that the GPU can handle a bitmap of this size
                    //
                    // If you need a bitmap larger than this, you'll need to either split it up into multiple bitmaps
                    // or use a software image (see SoftwareImageType).
                    //
                    auto maxBitmapSize = deviceContext->GetMaximumBitmapSize();
                    jassert(size.width <= maxBitmapSize && size.height <= maxBitmapSize);
#endif

FWIW my Nvidia card reports a maximum texture size of 16384x16384.

Matt

I just tried it today and I must say I am really impressed with the performance!
Congrats @matt!

Here are a few oddities that I encountered:

  • setBufferedToImage(true) seems to cause strange behaviors, like component popping in and out of view. I do have component buffered to image that are child of other that are also buffered to image, so that might be the cause. I did not go to the bottom of it.

  • g.getInternalContext().getPhysicalPixelScaleFactor() does report the display scale and ignores any affinetransform scale.

  • Image getPixelAt and setPixelAt methods are super slow (like 1ms per call), while Image::BitmapData getPixelColour and setPixelColour remain fast. This seems to be due to initialiseBitmapData, which is called when a new BitmaData is created for each and every get/setPixelAt call.

  • fillRect with a width or height smaller than 1.0f do tend to disappear at times with AffineTransform scale smaller than 1 (popping in and out of view when scaling the UI). This is also independent of the display scale ratio, as even with a 200% display scale any rectangle with a dimensions smaller than 1.0f will face this problem, even if they are effectively almost 2 pixels thick.
    This can be solved by using drawLine, but it is less efficient, and the doc explicitly recommends against it:

If you’re trying to draw horizontal or vertical lines, don’t use this - it’s better to use fillRect() instead unless you really need an angled line.

Oddly enough, the problem seem to disappear with setBufferedToImage(true) !

  • I have a few components with complex paths that do not render properly when scaled up
1 Like

I have a few components with complex paths that do not render properly when scaled up

I think I found what the problem was:

The thickness of a strokePath stays constant regardless of the AffineTransfrom::scale of the component.
The problem seems to be in Direct2DGraphicsContext::drawPath()

After further investigation it appears it only correctly reports display scale in standalone mode: as a VST3 it seems to always report 1 regardless of the display scaling (and pathstroke thickiness is half of what it is in standalone mode, so this is most probably the crux of it).
AffineTransform-induced scale is ignored in both cases.