Direct2D debug messages

@refx and @fuo - I recommend you enable the Direct2D debug layer on your machines; that might help track down some of the issues you’ve both reported.

Some relevant links:
Direct2D debug layer overview

Install Direct2D debug layer

Once you’ve installed the debug layer, go to juce_DirectX_windows.h in modules/juce_graphics/native. Look for the initializer for the Direct2D factory (around line 258):

    ComSmartPtr<ID2D1Factory2> d2dSharedFactory = [&]
    {
        D2D1_FACTORY_OPTIONS options;
        options.debugLevel = D2D1_DEBUG_LEVEL_NONE;
        JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wlanguage-extension-token")
        ComSmartPtr<ID2D1Factory2> result;
        auto hr = D2D1CreateFactory (D2D1_FACTORY_TYPE_MULTI_THREADED,
                                     __uuidof (ID2D1Factory2),
                                     &options,
                                     (void**) result.resetAndGetPointerAddress());
        jassertquiet (SUCCEEDED (hr));
        JUCE_END_IGNORE_WARNINGS_GCC_LIKE

        return result;
    }();

Change D2D1_DEBUG_LEVEL_NONE to D2D1_DEBUG_LEVEL_INFORMATION.

Matt

1 Like

Done!

I got a bunch of these:

D2D DEBUG INFO - PERF - A layer is being used with a NULL opacity mask, 1.0 opacity, and an axis aligned rectangular geometric mask. The Push/Pop Clip API should achieve the same results with higher performance.

I get these with all three renders.

I don’t see any other errors beside the “Microsoft C++ exception: IntegerOverflowException” ones, that are few and far between compared to the D2D DEBUG INFO ones.

That’s very helpful.

Could you please share some example code?

Matt

The “D2D DEBUG INFO” warning does trigger with drop shadows:

juce::Path p;
p.addRectangle(getLocalBounds().reduced(20));
juce::DropShadow(juce::Colours::black, 10, { 0, 0 }).drawForPath(g, p);

The IntegerOverflow exception does trigger with some fonts, for example Figtree:

auto font = juce::Font(juce::Typeface::createSystemTypefaceFor(BinaryData::FigtreeRegular_ttf, BinaryData::FigtreeRegular_ttfSize));
g.setFont(font);
g.drawText("test", getLocalBounds(), juce::Justification::centred);

Now I am seeing a very strange behavior that only happens with the JUCE 8 Direct2D renderer (JUCE 8 software and openGL renderers are OK, as well as the Direct2D JUCE 7 one).

In short, past a given window size (eg 2500px wide), when dragging a slider on one side of the interface with an animated path occurring on the order side, the dragging gets really choppy. Dragging other sliders closer to the side with the animation does not trigger that choppy behavior. The further apart the choppier it gets.

I see no error or warning in Debug mode.

This will be difficult to reproduce/isolate, so does that ring a bell right off the bat?..

What happens if you disable the D2D debug layer?

Same Behavior.

I recommend you run a release build with the profiler and see what shows up as a hot spot.

Matt

Looks like the integer overflows are due to missing glyphs. Here’s the stack trace.

Matt

KernelBase.dll!RaiseException() Unknown
msvcrt.dll!_CxxThrowException() Unknown
DWrite.dll!SafeIntExceptionHandler::SafeIntOnOverflow(void) Unknown
DWrite.dll!ClientSideConnection::AllocateMessage<struct AlpcDatagram<5> >(unsigned int) Unknown
DWrite.dll!ClientSideConnection::SendMissReport(unsigned int,unsigned int,unsigned int,void const *,unsigned int,unsigned short const *,unsigned int) Unknown
DWrite.dll!DWriteFactory::SendMissReport() Unknown
DWrite.dll!DWriteFactory::ReportMissingGlyphs(unsigned int,unsigned int,class CheckedPtr<unsigned char const ,class FailAsExceptionPolicy,unsigned int>,unsigned short const *,unsigned int) Unknown
DWrite.dll!GlyphLookupCache::Rasterizer::SendMissReport(class ClientSideCacheContext &,class LookupCacheBitmapEntry &) Unknown
DWrite.dll!GlyphLookupCache::CleanupElementsGlyphLookupCache::BitmapTraits() Unknown
DWrite.dll!DWriteGlyphLookupCache::Cleanup(void) Unknown
d2d1.dll!CHwSurfaceRenderTarget::EndDrawCleanup() Unknown
d2d1.dll!DrawingContext::EndDrawCleanup(void) Unknown
d2d1.dll!DrawingContext::EndDraw() Unknown
1 Like

Hi @matt,

Here is an example demonstrating the behavior.
On my system (14900k iGPU, 4k 60Hz monitor) with the window maximized, looking at the GPU usage in task manager I go from around 35% when both animations are on the same side to around 45% when they are on opposite sides.

#pragma once
#include <JuceHeader.h>

class TestAnimation : public juce::Component, juce::Timer {
public:
	TestAnimation() {
		startTimerHz(60);
	}

	void timerCallback() override {
		++mI;
		setPath();
	}
	void resized() override {
		setPath();
	}

	void setPath() {
		auto yM = .5f * getHeight();
		mPath.clear();
		
		for (int x = 0; x < getWidth(); ++x) {
			juce::Point<float> point{ (float)x, yM * (1 + sin(.1f * (x + mI))) };
			if (x == 0) {
				mPath.startNewSubPath(point);
			}
			else {
				mPath.lineTo(point);
			}
		}
		repaint();
	}

	void paint(juce::Graphics& g) override {
		g.drawRect(getLocalBounds().toFloat());
		g.strokePath(mPath, juce::PathStrokeType(1.f, juce::PathStrokeType::curved, juce::PathStrokeType::EndCapStyle::rounded));
	}
private:
	float mI = 0;
	juce::Path mPath;

	JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(TestAnimation)
};

class TestComponent : public juce::Component {
public:
	TestComponent() {
		addAndMakeVisible(mAnimation1);
		addAndMakeVisible(mAnimation2);

		addAndMakeVisible(mLeft);
		addAndMakeVisible(mRight);
		addAndMakeVisible(mOpposite);

		mLeft.onClick = [this] { mAnimatorPos = 0; resized(); };
		mRight.onClick = [this] { mAnimatorPos = 1; resized(); };
		mOpposite.onClick = [this] { mAnimatorPos = 2; resized(); };
	}

	void resized() override {

		auto bounds = getLocalBounds();
	
		{
			auto buttonBar = bounds.removeFromTop(50);
			mLeft.setBounds(buttonBar.removeFromLeft(150).reduced(10));
			mRight.setBounds(buttonBar.removeFromLeft(150).reduced(10));
			mOpposite.setBounds(buttonBar.removeFromLeft(150).reduced(10));
		}

		{
			bounds = bounds.removeFromTop(400);
			switch (mAnimatorPos) {
				case 0:
					mAnimation1.setBounds(bounds.removeFromLeft(200));
					mAnimation2.setBounds(bounds.removeFromLeft(200));
					break;
				case 1:
					mAnimation1.setBounds(bounds.removeFromRight(200));
					mAnimation2.setBounds(bounds.removeFromRight(200));
					break;
				case 2:
					mAnimation1.setBounds(bounds.removeFromLeft(200));
					mAnimation2.setBounds(bounds.removeFromRight(200));
					break;
				default:
					break;
			}
		}
	}

	void paint(juce::Graphics& g) override {
		g.fillAll(juce::Colours::white);
	}

private:
	TestAnimation mAnimation1, mAnimation2;

	int mAnimatorPos = 0;

	bool mRepaintAll = false;

	juce::TextButton mLeft{ "Both Left" }, mRight{ "Both Right" }, mOpposite{ "One Left, One Right" };

	JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(TestComponent)
};

class MainComponent  : public juce::Component {
public:
	MainComponent() {

		//juce::Timer::callAfterDelay(0, [this] {
		//	auto* peer = getPeer();
		//	jassert(peer);
		//	if (peer) peer->setCurrentRenderingEngine(0);
		//});

		setSize(referenceWidth, referenceWidth / 2);

		addAndMakeVisible(component);
		addAndMakeVisible(zoomLabel);
		refreshZoomLabel();
	}

	void paint(juce::Graphics& g) override {
		g.fillAll(juce::Colours::black);
	}

	void resized() override {
		refreshZoomLabel();
		zoomLabel.setBounds(getLocalBounds().removeFromTop(30));
		component.setBounds(0, 0, referenceWidth, referenceWidth);
		component.setTransform(juce::AffineTransform::scale(getScale()).followedBy(juce::AffineTransform::translation(0,30)));
	}

private:
	void refreshZoomLabel() {
		zoomLabel.setText("zoom: " + juce::String(juce::roundToInt(100 * getScale())) + "%", juce::dontSendNotification);
	}

	float getScale() {
		return (float)getWidth() / referenceWidth;
	}

	juce::Label zoomLabel;
	TestComponent component;

	int referenceWidth = 1000;

	JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainComponent)
};

I can confirm that I see higher GPU usage in Task Manager with your example with the animations on opposite sides.

However, in my case (with JUCE 8) the GPU usage is around 0.8% with both animations to one side, and 1.4% with the animations on opposite sides. I’m using an Nvidia 2080Ti, so there’s clearly a difference between the GPUs.

I see similar results with JUCE 7.

I’ll see if I can dig up a machine with an integrated Intel GPU.

You might learn more if you open Task Manager, go to the Performance page, and select your GPU. You’ll see more detail about your GPU resources. Looks like your particular GPU doesn’t have dedicated VRAM, so that may cause performance bottlenecks.

Matt

1 Like

Hi @matt,

Thank you for your response.

Running the same code on a Ryzen 4800U laptop (iGPU again) on a 4k display results in a GPU usage of around 17% (same side) and 22% (opposite).
I cannot expect my users to have a better GPU than that on average.
In fact most non-gaming Windows laptops will have this kind of configuration.

I see the same behavior with the JUCE 7 Direct2D branch indeed, but the plugin I am working on was running smoothly with the JUCE 7 Direct2D branch on all my test machines (including one with an Intel m5-6Y54, which is a 9 years old CPU that barely manages 2GHz), and now it struggles with all of them in JUCE 8 (none of them have dedicated GPUs though).
I tried removing the shadows and using default fonts to silence the warnings mentioned in this thread, but that does not seem to solve the speed issue.
It also affects the software renderer.

By the way, in the above example repainting the whole window at the same rate as the animations does seem to get the GPU usage down.

Understood; thanks for the extra information. I’m glad to hear it was working smoothly with the previous branch. I’ll keep at it.

Matt

1 Like

I’ve coincidentally also used the Figtree font, which explains the Integer overflow exceptions, but I’m confused about why. First, I use only ASCII characters (no Unicode in sight); second, shouldn’t the fallback mechanism handle all missing characters?

Performance: I have the opposite experience of @fuo. With ultraSID, the D2D branch of JUCE7 is very stuttery and laggy and seems to struggle a great deal (bad frame-timing). With the JUCE8 branch, everything is butter smooth. This is with a GTX 1060 6GB, an eight-year-old GPU.

From my experience, we can expect users to have much better GPUs than the iGPUs @fuo mentioned. Whenever we get a support file (so from non-power users who need help with simple stuff), they have something like a 2080 or better.

It’s all certainly much faster than with the software renderer, especially at higher resolutions.

Yeah, I’m confused about the Figtree/integer overflow issue as well.

Glad to hear the JUCE8 branch is working well for you! I have to give the JUCE team credit here; they made some nice improvements.

Matt

I just found something strange by accident: it looks like setting opaque to true does increase GPU usage.
Removing all setOpaque calls in different components of the plugin I am working on, GPU usage is now similar to that of JUCE 7 D2D (software renderer is still much slower though).

This can be verified with the example code I posted above. calling setOpaque(true) for TestAnimation, and of course calling fillAll in paint(), does increase GPU usage by a substantial factor:

  • setOpaque(false), animation on the same size: 35%
  • setOpaque(false), animation on opposite side: 45%
  • setOpaque(true), animation on the same size: 55%
  • setOpaque(true), animation on opposite side: 75%

(14900k iGPU, 4k 60Hz display, maximized window, release build)

Edit: strangely enough, I see the same phenomenon in JUCE 7 D2D, and GPU usage is even greater than JUCE 8 in the above example, but for some reason this is a different story in my plugin. Tracking down the exact thing that makes JUCE 8 slower in my app might be difficult. In any case, dropping setOpaque does close the gap for me, at least for the d2d renderer.

Looks like the integer overflow message are related to issues with the custom font loader. Should have a fix soon.

Matt

The font loader issues took a while to sort out. I’ll dig into the other issues soon.

Matt

1 Like

Here’s a patch that will hopefully resolve the integer overflow exceptions with Figtree and other custom fonts.

Matt

custom-font-loader.patch (10.2 KB)

1 Like

Works perfectly. No more IntergerOverlow Exceptions. Everything looks exactly as before, so no visual regressions or anything.