The JUCE 8 Preview branch is available now!

I’ve limited this down to something to do with HeapBlock - I swapped out juce::HeapBlock<juce::HeapBlock>; for std::vector<std::array<int16, 4096>>; and no longer had any memory leaks when debugging with MSVC, as they were consistent to this particular object. Not sure what JUCE want to do with that - as it’s actually happening in the latest commit for JUCE 7 too.

Edit: There’s also random leaks happening similar to yours, but again only on windows. I imagine linked to the D2D addition

Congratulations!

I did try it on Windows, and so far I have a few rendering bugs to report:

  • Direct2D: Direct2DGraphicsContext::getPhysicalPixelScaleFactor() only returns the display scale in standalone, and returns 1.0f in VST3 mode, regardless of the display or AffineTransform scale. For other renderers on both Mac and Windows this does return the display scale multiplied by the AffineTransform scale (which seems like the most logical behavior).

  • openGL/software: With the openGL and software renderers, JUCE_CHECK_COORDS_ARE_VALID line 831 of juce_Path.cpp is triggered when reducing the window of this example: Int vs float rectangles with scaling: a proposal for Windows/MacOS consistency for JUCE 8 - #8 by fuo

  • All renderers: the jassert line 2203 of juce_RenderingHelpers.h gets triggered in my app (I did not try to isolate the problem yet)

  • Direct2D: Direct2DGraphicsContext::drawLine and Direct2DGraphicsContext::drawLineWithThickness refuses to draw purely horizontal or vertical lines

  • All renderers: Image::clear or setting the clear flag in the constructor does not seem to work with NativeImageType

  • Direct2D: in standalone mode with TopLevelWindow::setUsingNativeTitleBar set to true, grabbing and moving the window makes the entire window repaint continuously (GPU goes nuts), even after the mouse button has been released

2 Likes

Congratulations! Can’t wait to give the new version a try, and feeling very happy at the moment to see expected improvements on font rendering and animation / UI rendering :wink:

2 Likes

Thanks for reporting, we’re aware the TextEditor has some issues we’re working on those at the moment but we didn’t want to hold up the announcement of JUCE 8.

Some interesting ideas here, we’ll discuss in the team about this. There will probably be a number of things for us to get to first before we can consider anything like this though.

One immediate downside I see is that it increases the already sizeable Component API. Maybe there’s some way this could be separated out? I guess the most obvious would be a ComponentAnimator (which unfortunately we already have and have to consider how best to deprecate).

Another thing to consider is that we decided to put all the animation stuff into its own module. The module is currently dependent on juce_gui_basics, but juce_gui_basics is not dependent on juce_animation. We particularly wanted to separate things because juce_gui_basics is already plenty big enough!

Regardlessl I think you’re right that there’s still something in there in terms of a class that could live in juce_animation that could handle some common use cases like this.

Thanks for the preview, looking through some the new features.

One minor thing I notice is there still remain some @available conditions on macOS for deployment targets no longer supported (i.e., < 10.11). Picking a completely random example:

static NSAccessibilityNotificationName layoutChangedNotification()
{
    if (@available (macOS 10.9, *))
        return NSAccessibilityLayoutChangedNotification;

    static NSString* layoutChangedString = @"AXLayoutChanged";
    return layoutChangedString;
}

NSAccessibilityLayoutChangedNotification can safely be used directly as we’re sure to be on 10.11 or later. This is similar for iOS < 12.0 availability conditions.

Looks like this could be a nice tidy up of some of that native code.

1 Like

Thanks @martinrobinson-2 indeed we plan to go through and tidy these up in the near future.

1 Like

Sounds great, thanks!

The only real downsize I see to that approach is that it doesn’t offer a reason to switch to the new JUCE approach for anyone who’s already implemented their own animations system. I’ve written a fairly extensive one for my own plugins, we have another one for our control apps, yet another for Studio, and there’s some open-source ones, e.g. Friz. The thing that all these have in common is that they’re external to the Component API.

This external approach means that you have to have some shared ownership between the component, and the animation(s) that are being applied to it, which means more boilerplate and in general more LoC before you have something shippable.

An internal approach (where you can pass ownership and control of the animations to the component itself) would mean significantly less boilerplate to introduce animations to an app, which would be motivation enough to refactor existing projects to remove any home-brewed animations APIs.

These are just some ideas - I hope it doesn’t come across as criticism! Either way, I’m really excited to finally have proper animations support :smiley:

2 Likes

Maybe it could be implemented as a class template that acts as wrapper, something like:

template <typename ComponentType>
class AnimatedComponent : public ComponentType
{
    // here a static assert that ComponentType is a juce::Component

    animateBoundsTo (...);
    // additional functions here
}

usage example:

AnimatedComponent <juce::TextEditor> usernameField;

usernameField.animateBoundsTo (...);

1 Like

There are some more breaking changes due to the change in the underlying JavascriptEngine implementation. Previously more literals would be parsed and returned into a juce::var without error. In JUCE 8 there are more failing cases expressed by this quick-and-dirty catch2 TEST_CASE:

TEST_CASE ("JUCE JavascriptEngine evalations all pass on JUCE 7", "[JuceJSEngine]")
{
    juce::String jsCode;
    
    SECTION ("Integer")
    {
        jsCode = "1"; // PASSES
    }
    
    SECTION ("Number")
    {
        jsCode = "1.0"; // PASSES
    }
    
    SECTION ("String")
    {
        jsCode = "'hello'"; // PASSES
    }
    
    SECTION ("Object")
    {
        jsCode = "{ message: 'hello' }"; // PASSES
    }
    
    SECTION ("Object with trailing commma")
    {
        jsCode = "{ message: 'hello', }"; // FAILS on JUCE 8, passes in JUCE 7
    }

    SECTION ("Array")
    {
        jsCode = "[ 1, 2, 3 ]"; // FAILS on JUCE 8, passes in JUCE 7
    }

    SECTION ("Array with trailing comma")
    {
        jsCode = "[ 1, 2, 3, ]"; // FAILS on JUCE 8, passes in JUCE 7
    }
    
    SECTION ("Complex object")
    {
        jsCode = "{ message: 'hello', counters: [ 1, 2, 3 ] }"; // FAILS on JUCE 8, passes in JUCE 7
    }

    SECTION ("Complex object with inner trailing comma")
    {
        jsCode = "{ message: 'hello', counters: [ 1, 2, 3, ] }"; // FAILS on JUCE 8, passes in JUCE 7
    }

    juce::JavascriptEngine jsEngine;
    
    auto errorMessage = juce::Result::ok();
    const auto result = jsEngine.evaluate (jsCode, &errorMessage);
    const auto errorString = errorMessage.getErrorMessage();
    
    REQUIRE (errorString == "");
    REQUIRE (! result.isVoid());
}

At first I thought it was just trailing commas (which should be vaild in modern JS) but the fail to parse [1, 2, 3] was a surprise.

Was this UB already or something to be more concerned about?

So far I haven’t been able to repro your 2nd and 3rd points (Windows 11, 200% scale). Are you able to provide any more info, e.g.

  • Windows version
  • Desktop scaling setting
  • GPU model
  • In the case of the 3rd point, the stack trace when the assertion is hit

Thanks!


Edit:

I’m seeing that Image::clear isn’t quite right, but the Image constructor seems to work as expected. Do you have a small example that shows it misbehaving?

Hi! I have just compiled one testing plugin with the latest commit from branch juce8. Unfortunately, the buffered image issue (which was solved once, see Clipping and custom fonts - #22 by matt) appears again.
Here is a screenshot of the testing plugin GitHub - ZL-Audio/ZLTest at juce8 :

The first row is with setBufferedToImage=True and the second row is without the buffered images. It ALSO refuses to paint those horizontal lines (second column, second row) (which is similar to the second point of @fuo )

I am using VMWare Fusion on macOS (with 2.6 GHz 6-Core Intel Core i7 & Radeon Pro 560X) to host Windows 10 22H2 with full resolution for Retina display (not sure about the scaling since VMWare handles this, but not 100%).

Regarding #2, it does not happen with the default direct2D renderer, only the software and openGL ones.

My setup:

  • Windows 11 22H2
  • 200% display scaling
  • no dedicated GPU (14900K iGPU is a UHD Graphics 770)
1 Like

I’m looking for the WebViewPluginDemo project discussed here. I’ve built this projucer from the latest juce8 branch, am I missing something?

Also can’t find it in Finder or on Github …

I’m looking for the WebViewPluginDemo project discussed here . I’ve built this projucer from the latest juce8 branch, am I missing something?

I was wondering about this too. For some reason, the demo is only available as a plugin, not in the demo runner. You can find it in the examples/GUI folder on the juce8 branch: https://github.com/juce-framework/JUCE/tree/juce8/examples/GUI/WebViewPluginDemoGUI

The “Building the WebViewPluginDemo” section at https://juce.com/docs/juce-8-feature-overview-webview-uis/ explains how to build the demo plugin.

Ah thanks for the link. I think this is just in the wrong place? On JUCE 8 Feature Overview: WebView UIs - JUCE

it says that it should in fact not be in the demo runner (so this is correct), but it should show up in the “Open Example/Plugins” tab in Projucer instead. Which it doesn’t, because it’s in the wrong place in the examples it seems :slight_smile: (GUI/WebViewPluginDemoGUI instead of Plugins/)

WebViewPluginDemo standalone crashes (macOS)

#0	0x0000000104ed1272 in createAssetInputStream(char const*, AssertAssetExists) at /Users/oli/Desktop/WebViewPluginDemo/Source/DemoUtilities.h:127
#1	0x0000000104ecfa5d in WebViewPluginAudioProcessorEditor::getResource(juce::String const&) at /Users/oli/Desktop/WebViewPluginDemo/Source/WebViewPluginDemo.h:537
#2	0x0000000104ef4deb in auto WebViewPluginAudioProcessorEditor::webComponent::'lambda'(auto const&)::operator()<juce::String>(auto const) const at /Users/oli/Desktop/WebViewPluginDemo/Source/WebViewPluginDemo.h:454

Works via cmake build. Pip from projucer didn;t work

Looks like it is due to setBufferedToImage

.exe!juce::RenderingHelpers::SavedStateBase<juce::RenderingHelpers::SoftwareRendererSavedState>::fillRect(juce::Rectangle<int> r, bool replaceContents) Line 2203	C++
.exe!juce::RenderingHelpers::StackBasedLowLevelGraphicsContext<juce::RenderingHelpers::SoftwareRendererSavedState>::fillRect(const juce::Rectangle<int> & r, bool replace) Line 2606	C++
[Inline Frame] .exe!juce::detail::StandardCachedComponentImage::paintImage(juce::Rectangle<int>) Line 95	C++
.exe!juce::detail::StandardCachedComponentImage::paint(juce::Graphics & g) Line 69	C++
.exe!juce::Component::paintComponentAndChildren(juce::Graphics & g) Line 1684	C++
.exe!juce::Component::paintEntireComponent(juce::Graphics & g, bool ignoreAlphaLevel) Line 1756	C++
.exe!juce::Component::paintComponentAndChildren(juce::Graphics & g) Line 1684	C++
.exe!juce::Component::paintEntireComponent(juce::Graphics & g, bool ignoreAlphaLevel) Line 1756	C++
.exe!juce::Component::paintComponentAndChildren(juce::Graphics & g) Line 1684	C++
.exe!juce::Component::paintEntireComponent(juce::Graphics & g, bool ignoreAlphaLevel) Line 1756	C++
.exe!juce::Component::paintComponentAndChildren(juce::Graphics & g) Line 1684	C++
.exe!juce::Component::paintEntireComponent(juce::Graphics & g, bool ignoreAlphaLevel) Line 1756	C++
.exe!juce::Component::paintComponentAndChildren(juce::Graphics & g) Line 1684	C++
.exe!juce::Component::paintEntireComponent(juce::Graphics & g, bool ignoreAlphaLevel) Line 1756	C++
[Inline Frame] .exe!juce::Component::paintWithinParentContext(juce::Graphics &) Line 1610	C++
.exe!juce::Component::paintComponentAndChildren(juce::Graphics & g) Line 1656	C++
.exe!juce::Component::paintEntireComponent(juce::Graphics & g, bool ignoreAlphaLevel) Line 1756	C++
.exe!juce::Component::paintComponentAndChildren(juce::Graphics & g) Line 1684	C++
.exe!juce::Component::paintEntireComponent(juce::Graphics & g, bool ignoreAlphaLevel) Line 1756	C++
.exe!juce::Component::paintComponentAndChildren(juce::Graphics & g) Line 1684	C++
.exe!juce::Component::paintEntireComponent(juce::Graphics & g, bool ignoreAlphaLevel) Line 1756	C++
.exe!juce::ComponentPeer::handlePaint(juce::LowLevelGraphicsContext & contextToPaintTo) Line 170	C++
[Inline Frame] .exe!juce::D2DContext::handleDirect2DPaint() Line 4782	C++
.exe!juce::D2DContext::handleShowWindow() Line 4755	C++
.exe!juce::HWNDComponentPeer::peerWindowProc(HWND__ * h, unsigned int message, unsigned __int64 wParam, __int64 lParam) Line 3824	C++
.exe!juce::HWNDComponentPeer::windowProc(HWND__ * h, unsigned int message, unsigned __int64 wParam, __int64 lParam) Line 3540	C++
[External Code]	
.exe!juce::HWNDComponentPeer::setVisible(bool shouldBeVisible) Line 1563	C++
.exe!juce::Component::setVisible(bool shouldBeVisible) Line 275	C++
.exe!juce::JUCEApplicationBase::initialiseApp() Line 312	C++
.exe!juce::JUCEApplication::initialiseApp() Line 97	C++
.exe!juce::JUCEApplicationBase::main() Line 271	C++
[External Code]	


JavascriptEngine

Regarding my JS issue I started posting about yesterday. I’ve expanded my tests a little it seems like quite a big change to what JS is accepted by the JavascriptEngine especially if one is using it to parse JSON-like data as Javascript code instead of JSON e.g.

{ message: 'hello', counters: [ 1, 2, 3 ] }
as opposed to:
{ "message": "hello", "counters": [ 1, 2, 3 ] }

The former was parsed correctly in JUCE 7 and returned to a var, but in JUCE 8 it fails to parse. It can be rewritten as a function that returns that. object and is then immediately called, but that approach isn’t backwards compatible with JUCE 7 and fails to parse there.

Stranger is that this one: { message: 'hello' } — this simply evaulates to the string "hello".

Integer values are all parsed in JUCE 8 to doubles too, although this perhaps should be an acceptable change given JS doesn’t really support a separate integer type.

Here is my expanded test case:

TEST_CASE ("JUCE JavascriptEngine evalations all pass on JUCE 7", "[JuceJSEngine]")
{
    juce::String jsCode;
    juce::String json;
    
    SECTION ("Integer")
    {
        jsCode = "1"; // PASSES
        json   = "1";
    }

    SECTION ("Number")
    {
        jsCode = "2.0"; // PASSES
        json   = "2.0";
    }

    SECTION ("String")
    {
        jsCode = "'hello'"; // PASSES
        json   = R"("hello")";
    }

    SECTION ("Object")
    {
        jsCode = "{ message: 'hello' }"; // parses but incorrectly on JUCE 8 as just "hello"
        json   = R"({ "message": "hello" })";
    }

    SECTION ("Object with trailing commma")
    {
        jsCode = "{ message: 'hello', }"; // FAILS to parse on JUCE 8, passes in JUCE 7
        json   = R"({ "message": "hello" })";
    }

    SECTION ("Array")
    {
        jsCode = "[ 1, 2, 3 ]"; // FAILS to parse on JUCE 8, passes in JUCE 7
        json   = R"([ 1, 2, 3 ])";
    }

    SECTION ("Array with trailing comma")
    {
        jsCode = "[ 1, 2, 3, ]"; // FAILS to parse on JUCE 8, passes in JUCE 7
        json   = R"([ 1, 2, 3 ])";
    }

    SECTION ("Complex object")
    {
        jsCode = "{ message: 'hello', counters: [ 1, 2, 3 ] }"; // FAILS to parse on JUCE 8, passes in JUCE 7
        json   = R"({ "message": "hello", "counters": [ 1, 2, 3 ] })";
    }

    SECTION ("Complex object with inner trailing comma")
    {
        jsCode = "{ message: 'hello', counters: [ 1, 2, 3, ] }"; // FAILS to parse on JUCE 8, passes in JUCE 7
        json   = R"({ "message": "hello", "counters": [ 1, 2, 3 ] })";
    }

    SECTION ("Complex object returned from function")
    {
        jsCode = R"(
            function fn()
            {
                return { message: "hello", counters: [ 1, 2, 3 ] };
            }

            fn();
        )"; // PASSES on JUCE 8, fails to parse on JUCE 7

        json = R"({ "message": "hello", "counters": [ 1, 2, 3 ] })";
    }

    juce::JavascriptEngine jsEngine;
    
    auto errorMessage = juce::Result::ok();
    const auto result = jsEngine.evaluate (jsCode, &errorMessage);
    const auto errorString = errorMessage.getErrorMessage();
    
    juce::String log;
    
    constexpr auto divider = "------------------------------------------------\n";
    const auto jsonFormat = juce::JSON::FormatOptions().withSpacing (juce::JSON::Spacing::singleLine);
    
    log << divider;
    log << "jsCode: " << jsCode << "\n";
    log << divider;
    log << "json: " << json << "\n";
    log << divider;
    log << "result: " << juce::JSON::toString (result, jsonFormat) << "\n";
    log << divider;
    
    juce::Logger::outputDebugString (log);

    REQUIRE (errorString == "");
    REQUIRE (JSONUtils::deepEqual (result, juce::JSON::fromString (json)));
}

Output from JUCE 7:

------------------------------------------------
jsCode: 1
------------------------------------------------
json: 1
------------------------------------------------
result: 1
------------------------------------------------

------------------------------------------------
jsCode: 2.0
------------------------------------------------
json: 2.0
------------------------------------------------
result: 2.0
------------------------------------------------

------------------------------------------------
jsCode: 'hello'
------------------------------------------------
json: "hello"
------------------------------------------------
result: "hello"
------------------------------------------------

------------------------------------------------
jsCode: { message: 'hello' }
------------------------------------------------
json: { "message": "hello" }
------------------------------------------------
result: {"message": "hello"}
------------------------------------------------

------------------------------------------------
jsCode: { message: 'hello', }
------------------------------------------------
json: { "message": "hello" }
------------------------------------------------
result: {"message": "hello"}
------------------------------------------------

------------------------------------------------
jsCode: [ 1, 2, 3 ]
------------------------------------------------
json: [ 1, 2, 3 ]
------------------------------------------------
result: [1, 2, 3]
------------------------------------------------

------------------------------------------------
jsCode: [ 1, 2, 3, ]
------------------------------------------------
json: [ 1, 2, 3 ]
------------------------------------------------
result: [1, 2, 3]
------------------------------------------------

------------------------------------------------
jsCode: { message: 'hello', counters: [ 1, 2, 3 ] }
------------------------------------------------
json: { "message": "hello", "counters": [ 1, 2, 3 ] }
------------------------------------------------
result: {"message": "hello", "counters": [1, 2, 3]}
------------------------------------------------

------------------------------------------------
jsCode: { message: 'hello', counters: [ 1, 2, 3, ] }
------------------------------------------------
json: { "message": "hello", "counters": [ 1, 2, 3 ] }
------------------------------------------------
result: {"message": "hello", "counters": [1, 2, 3]}
------------------------------------------------

Output from JUCE 8:

------------------------------------------------
jsCode: 1
------------------------------------------------
json: 1
------------------------------------------------
result: 1.0
------------------------------------------------

------------------------------------------------
jsCode: 2.0
------------------------------------------------
json: 2.0
------------------------------------------------
result: 2.0
------------------------------------------------

------------------------------------------------
jsCode: 'hello'
------------------------------------------------
json: "hello"
------------------------------------------------
result: "hello"
------------------------------------------------

------------------------------------------------
jsCode: { message: 'hello' }
------------------------------------------------
json: { "message": "hello" }
------------------------------------------------
result: "hello"
------------------------------------------------

------------------------------------------------
jsCode: { message: 'hello', }
------------------------------------------------
json: { "message": "hello" }
------------------------------------------------
result: undefined
------------------------------------------------

------------------------------------------------
jsCode: [ 1, 2, 3 ]
------------------------------------------------
json: [ 1, 2, 3 ]
------------------------------------------------
result: [1.0, 2.0, 3.0]
------------------------------------------------

------------------------------------------------
jsCode: [ 1, 2, 3, ]
------------------------------------------------
json: [ 1, 2, 3 ]
------------------------------------------------
result: [1.0, 2.0, 3.0]
------------------------------------------------

------------------------------------------------
jsCode: { message: 'hello', counters: [ 1, 2, 3 ] }
------------------------------------------------
json: { "message": "hello", "counters": [ 1, 2, 3 ] }
------------------------------------------------
result: undefined
------------------------------------------------

------------------------------------------------
jsCode: { message: 'hello', counters: [ 1, 2, 3, ] }
------------------------------------------------
json: { "message": "hello", "counters": [ 1, 2, 3 ] }
------------------------------------------------
result: undefined
------------------------------------------------

------------------------------------------------
jsCode: 
function fn()
{
    return { message: "hello", counters: [ 1, 2, 3 ] };
}

fn();
        
------------------------------------------------
json: { "message": "hello", "counters": [ 1, 2, 3 ] }
------------------------------------------------
result: {"message": "hello", "counters": [1.0, 2.0, 3.0]}
------------------------------------------------
1 Like

I also see a JSON related problem, not sure if that’s related to what @martinrobinson-2 reports, but sounds like it to me. Or maybe I’m just doing something wrong:

I have a native function, that i pass into the WebBrowserComponent, that looks like this

WebBrowserComponent::NativeFunction { 
    "getMyJson", 
    [&] (auto& varArr, auto complete) {
         String myJsonString = loadJsonStringFromFile ();
         complete (myJsonString);
    }
}

For the js portion I call:

    const nativeFunction = getNativeFunction ("getMyJson");
    const result = await nativeFunction ();
    console.log(result);

This throws an assertion in juce_WebBrowserComponent.cpp, because my json is interpreted as code and I also only get an error on the browser console about the json failing to interpret, because of a missing quote (which i double checked with https://jsonlint.com/ , which tells me my json is fine).

image
Obviously I don’t want it to be interpreted, i just want to pass through some data. If I just pass in a simple string that doesn’t contain json everything works as expected, but if I try to pass a json string back it’s interpreted and that fails. I also tried adding escaped quotes around it, tried escaping all quotes inside the json, and all that jazz, but to no avail.

Am i misusing that function? Can I only pass back simple strings or js that can be evaluated? Or is this related to the freshly introduced JSON problems as reported above?

//edit: looking at the test results above more closely, i think it’s most likely related since that would be Complex object with inner trailing comma for me :slight_smile:

1 Like