FR: Improve capabilities for modern vector UI (blurs, shadows, gradients, animation...)

I’m not sure JUCE’s features had much influence on the design trends adopted by Apple and Google

2 Likes

Btw DALL-E generated a nice header image for this thread (sorry if unrelated):

7 Likes

My experience is that it’s audio/plugins that are usually latest to design trend parties! Even Logic still clung to skeuomorphism while the rest of Apple transitioned to flat… guess we’re serving a community steeped in a ton of retro-nostalgia for things like big knobs, etc!

Flat design was very popular in 2012-4 after the release of iOS7 (Metro in 2010 was already flat). Pure flat often ends up with usability challenges — depth/blurs/gradients are pretty helpful when grouping elements and focusing attention, so they made a bit of a comeback and I guess they’ll stick around (like neumorphism becoming popular). Apple seems to be softening back up (check out that big juicy animated background blur in iOS control center!)

Anyway, Auto Align’s flat design does look great, nice job!

:laughing:

2 Likes

So you just meant to say that the general consensus is that we need hardware acceleration, not OpenGL specifically. Gotcha.

That’s the framerate in case you’re working with video-tracks. A feature that bitwig probably doesn’t even have. It’s meant to let musicians work on audio for movies, so you tell Cubase the framerate of the video material you’re working with. This has nothing to do with the UI refresh rate. And in Cubase Pro this setting also goes up to 60 Hz, but that’s inconsequential, because it’s not the UI refresh rate.

Any transform is possible. You can even translate it off the screen, flip it upside down, etc.

But it’s just a display transform. Logically it’s still just a rectangle of the dimensions you’ve supplied. The transform happens only during the rendering phase. It’s an abstraction you don’t need to worry about. You still draw your text, rects, gradients, etc. normally, and JUCE transforms all these for you to display correctly.

JUCE also take the mouse input and transforms it backwards, so that the click inside the component seems to happen in the logical coordinate space for you.

This is how a lot of JUCE plugins scale their entire UI (we do it for NEXUS). We just apply a transform (scale only) to our child component (in our case, the first child of the main component), and we calculate the scale factor by dividing the main-component width (which gets changed when you change the window size) by the “native” width of our child-component. Now JUCE will scale our child component up/down and we don’t have to worry about anything.

So you meant to say to have optional std::functions, and if they are not overwritten, then the default LookAndFell functions get used? Because you made it sound like a replacement, instead of an option.

2 Likes

In our experience, audio plugins’ users (not creators!) vastly prefer a hardware-like look.

When we switched from NEXUS2 to NEXUS3, we also switched from 3D rendered, photorealistic look with shadows, reflections, additive blending for light, etc., to a completely flat look. Many of our customers contacted us about it and complained about how the new design looks so much worse. Some even wanted to go back to NEXUS2.

It’s not so much that JUCE features determine what is possible and thus set the trend. The trend is already there, and we use the tools available to achieve our goals. We could easily replicate the NEXUS2 UI in JUCE, as it’s just a bunch of bitmaps for a few components. The rest is just a giant background bitmap.

But the new abilities afforded to us allow us to think differently. Not to use bitmaps, but more dynamic techniques, which lets us iterate faster, experiment more, etc., which leads to new ideas and hopefully better UIs.

4 Likes

We’re getting off topic again, but I agree with the others that the features of a framework shouldn’t be limited to the requirements of current trends - esspecially for an industry-standard framework like JUCE. If the more complex graphics were more easily achievable in JUCE, we might even see new trends immerging!

I’ve spent countless hours trying to explain to our designers that, although the things they’re asking for seem very trivial and would be very trivial in other frameworks, JUCE just doesn’t have good support for them. We can do pretty much anything they want, but anything more than some basic gradients and primitive shapes will require a lot of dev work and will be really inefficient to run.

A few months ago our designer updated the wireframe to include a block of text that had a single inline icon :star:, like this. I had to explain that to add that icon would mean writing an entirely new custom text component and would be a bigger ticket than the majority of the features currently on our backlog. They could barely believe me.

4 Likes

This is the current situation, but look a bit in the future. Smartphones approach 120Hz and 4K on newer devices. Desktops will soon regularly use 4K, or widescreen formats. TVs offer HDR10. Gaming Monitors use 144Hz and higher. Graphics Designers use and expect colour spaces for proper gamma correction (sRGB, DCI-P3). Modern image formats use 16 bits per pixel.

While currently all of this may sound like unnecessary fancy tech numbers, it will soon be the reality of most consumer products. Everything about this depends on proper GPU (or co-processor) usage. If it’s already hard to keep up with the current middleground, JUCEs look-and-feel and UI tech will be extremely out of date in the next 5 years. While design products from big companies will eventually adopt these changes. Even now some smartphone apps are more responsive than the desktop version.

What style of UI isn’t really important. But let’s assume a flat one. Without the necessary performance, the designers and creators will always limit the possibilities and aim for a “static flat” design. The newest FL Studio (Image Line) version is a good example of what modern vector design could be. Some of their UI elements dynamically resize their knobs and sliders on mouse over, glow, or otherwise signal with colours. Or tween and smoothly interpolate the motion. Something that is not possible with static vector designs. A proper 60 fps GPU supported UI will always feel more responsive than a 30 fps cached software rendered UI. No matter what layout or graphic style is used.

So if I request any change, it should at least lay the groundwork in the right direction. Focussing on small improvements may fix some things, right now. But it will soon be obsolete anyway. In this regard it’s at least questionable if the time on it is not completely wasted. In some cases the “fix” is also vastly more complicated. The multithreaded stack blur for example. The shader is almost trivial and outperforms it by lightyears, while the CPU version is much more complicated from a maintenance/architectural viewpoint and more resource hungry.

Before the JUCE devs fight against windmills, they should probably clarify their overall aim and adjust their team’s capacity.

We must remember that, at least for plugins, we can not compare ourselves to stand-alone or mobile apps. We must share the resources with other plugins, the host, etc.

The situation right now, everything being purely software rendered on Windows, is less than ideal, but it’s 100% compatible. For a while, during the NEXUS3 release, we tried to use OpenGL as the rendering context, and not only was it overall slower (which was a big surprise to us), but we had tons of compatibility issues. Some customers only had empty or black windows. Even our stand-alone cloud app didn’t work for everyone. So we ultimately stopped using it and reverted everything to the software renderer.

Everything 60fps, animated, and using only 1% CPU would be really really nice, but at the end of the day, compatibility and useability are more important.

5 Likes

Yes, this is an important issue. But there is a big difference between “black screen” and “not working efficiently”.

To my knowledge the classic “OpenGL black window” is mostly related to versions prior to 3.0. Either the framebuffer or the shaders are not usable (or not without using the ARB function calls). In most cases a driver update will fix it, or the default setting is using outdated onboard graphics (e.g Intel HD), instead of the dedicated GPU.
Anyway, the issue here is more due to the non existing, not widely tested, fallback of juce::OpenGLContext. It can definitely be avoided or communicated to the user. If there is a clear warning “OpenGL 3.0 is required”, then at least the responsibility is shifted to the user. For PC Games (a mass market) this is a normal thing. No GPU driver? Well, then you can’t play that game. No modern GPU? Well, then you can’t play it at 4K 60fps.

Ideally, a multi API backend should pick the OS prefered version, query the features, then let the developer decide which one to pick. If it’s clear that a basic feature is not supported, then a fallback to alternatives and if not possible a fallback to software can be used. Every browser (Chromium within Skia for example) does exactly this. But it’s done in a smart way that guarantees basic functionality. Albeit with “slower” performance.

This is mostly a problem of OpenGLs single threaded context model. Vulkan, DX12 and Metal address this by using instance based access to the “PhysicalDevice” objects. And while experimenting with Vulkan, multiple open plugin instances (20 or more) accessing the GPU were not a problem. Similar to multiple open browser windows that access the GPU for rendering. It was definitely an issue in the past, but it shouldn’t stay that way.

2 Likes

It was slower because JUCE doesn’t use it properly. If you want better performance out of the current offering, you need to tell it to not render components, and send your own geometry. Juce as it stands just renders a picture on the cpu, and then sends that picture to the gpu. It’s actually adding extra steps. If you turn off component rendering, then do your own gl_buffer_data type calls, you can get very good performance. Juce never really integrated with OpenGL properly imo, it as it stands it’s more of an ‘on paper’ item.

4 Likes

I think it’s because the OpenGL integration was made to work with existing components that were already doing a lot of work in their paint() calls.

Drawing components in JUCE requires very imperative code - you can encapsulate things but the final calls to juce::Graphics are very imperative. Compare that to how you style things on the web using CSS - it’s purely declarative.

So, whatever JUCE settles on for the future of hardware-accelerated graphics, it’s never going to have optimal performance if we all carry on using paint() methods - we need a more declarative approach where we can simply describe how our components should look and whatever renderer happens to be being used can interpret that however it sees best.

IMO the best way to do that is with SVG since it’s a well established format and supported by every major UI design app.

1 Like

The declarative part of the web is just front end for the “real” rendering code that runs on the browser and is also imperative. SVG (both in JUCE and elsewhere) is also just front end to a bunch of imperative paint calls.

There’s no problem to switch paint calls to be very efficient, but you need to be able to pass more detailed information, like reusable objects and transform, for Graphics to be able to generate efficient shaders. You can look at www.Shadertoy.com to see examples of what the GPU can do when it’s passed the data in a friendly way.

That’s exactly what I’m proposing - JUCE should act like the browser and do all the imperative back-end stuff. I just provide it with some abstract info about what I want my component to look like and it can decide how best to render that, whether it’s OpenGL, Metal, DirectX, etc.

i like imperative code. i’d rather wanna say “stuff should have width /2 and x of (x + w) / 2” (or so) to center smth than having to remember which one of the mil of css calls that have something with position, center or align in their name, was the one for that particular type of alignment. or you could wrap that into a free function that is performed on a rectangle and it becomes declarative, but you can still use it in paint calls and define what exactly is going on. but the overall logic, a function call that just does its thing rather than a list of style properties, is still there then, which is good

For the record I’m not suggesting JUCE adds CSS support, that would be a nightmere!

2 Likes

That would be extremely hard to do, as it would mean making a feature-complete high level language. It would be like writing a full-on React native front and backend in JUCE, which would be a massive undertaking even for a large company.

I don’t think there’s a need for it, all JUCE needs to do is expose a few extra calls in Graphics that will allow people who need performance write better things on top of it, which would work with whatever front ends you want, like SVG parsers or React-JUCE.

1 Like

As a personal note, I think we need to separate between how we personally would like to work, and the role of JUCE.

To me, the role of JUCE is very clear: I want a unified C++ API that wraps low level elements I don’t want to touch directly, like OS calls and plugin API calls.

I don’t think JUCE should start taking more responsibilities and start inventing languages and software design patterns for UI frameworks.

This is not why I pay the JUCE subscription costs, and surely there are people and companies more suitable to write nicer code on top of JUCE, like @daniel’s PluginGUIMagic, etc, which only require the JUCE team to expose abilities from whatever the underlying implementation is (Metal, Vulkan, etc).

5 Likes

Tbf, the solution I’m describing is some perfect, idealistic solution that I’d love to work with - I don’t necessarily think it’s the one that should be implemented given the existing solution, team size, etc. It’s what I’d aim for if I was to write a UI framework for JUCE from scratch.

Totally agree, although I’d say it should also wrap some of the mid-level stuff too to cover the most typical use cases for things. The built-in widgets are a good example of that.

Not sure if this was directed at my proposal, but I wasn’t advocating for any new languages or patterns… in fact I explicitly said JUCE should fully implement an existing language! The way we use juce::Graphics feels more other-language-y to me than if you were to use SVG. Doing graphics stuff always seems to trip up non-JUCE devs more than any other part of the framework. But as I said, my solution is very much an idealistic one so I don’t realisticly seeing juce::Graphics going away anytime soon.

Totally agree - I just think it’s a shame there isn’t a go-to solution just yet. Everytime I start a new JUCE project and find myself making a CustomLookAndFeel.h and writing dozens of lines of code just to say what a button should look like, a little voice in the back of my head always says “why do I have to do this… again!?”.

From a high level view, I agree, and the main design goal we’ve done in our codebase is: no direct paint calls in the plugin code, and instead they’re all wrapped in Components that hide that and get passed some parameters.

That also allowed my company partner, who doesn’t have any programming experience, to write quite a lot of layout code in our product while someone with more experience like me can maintain the more low level part.

But - I still think this isn’t the role of JUCE to do. People can (and do) offer 3rd party libraries of really nice to use Components, and hide whatever low level detail is involved with it.
I personally would like that better than expecting a small team of expert C++ devs like the JUCE team to act like they’re in the Flutter or React.js teams, where the team is expected to be some leader in front end programming API.

That’s very true.
But - you can always write your own JUCE modules that will encapsulate repeatable patterns.

For example, I have quite a lot of custom components in our library that have all the repeating boring parts of LnF/Graphics implemented, and you just pass into their constructor something like a color, or a dimension, or an SVG, many of those are even read from a config file and not from code.

I definitely encourage everyone to share their patterns to simplify design code, and we can create a really nice community of 3rd party solutions that save boilerplate and are more expressive.

Having that said, things like improving the speed of shadows like @sudara is asking for, can only be done by JUCE maintainers in a way that scales, due the need for library caching, efficient shader translation and all that.

6 Likes

I agree.

We have a ridiculous amount of combined experience implementing UI with the current tooling. If the goaI is to reduce the burden of implementation and raise the quality of implementations, sharing the techniques and approaches to problems we are solving today (and iterating on those together) is the most helpful thing we can do.

I’ll try to do some code extraction about how I’m solving some of the “padded component” and content bounds layout stuff. A first basic version of my stack blur drop shadow was here and I have a version that I’m working as a component member which caches n drop shadows as single color images. It still needs some work but it’s extremely fast and animatable. My next step is to bake that into a generic “modal” component…

Maybe it would be nice to have a “JUCE UI recipes” section on a wiki to collect small chunks of code that solve particular problems like this. I’ll give @theaudioprogrammer a nudge…

11 Likes