If I understand correctly, the Direct2D renderer makes use of the GPU when drawing images, so they can be drawn very efficiently, as opposed to the software renderer or the Coregraphics renderer on macOS.
@ZioGuido A further optimization could be to create a single “smoke particle” image and repaint it as often as needed. You can even create the image using the visually better gaussian blur, as you will only be applying the blur once. (This method can of course be applied when painting regular drop shadows).
I have made a few tests based on your code and can get away with more than a thousand particles at maximum framerate. MacOS, on the other hand, struggles with just a few!
@matt Does setBackupEnabled(false) have any effect on MacOS? And have I implemented it, and the swapping of the images, correctly?
#pragma once
#include <JuceHeader.h>
class GSiAnimatedAboutScreen : public Component
{
public:
static constexpr int max_num_particles = 1500;
GSiAnimatedAboutScreen()
{
setOpaque(true);
}
void Show()
{
Update();
toFront(true);
setVisible(true);
}
void Hide()
{
Desktop::getInstance().getAnimator().fadeOut(this, 250);
}
void Update()
{
for (auto& particle : particles)
{
particle.x += particle.dx;
particle.y += particle.dy;
particle.size += particle.dsize;
if (particle.y < -particle.size)
{
particle.reset(getWidth(), getHeight());
}
}
repaint();
}
private:
Image frameA;
Image frameB;
bool swap = false;
Image smokeParticleImage;
void resized() override
{
frameA = Image(Image::PixelFormat::RGB, getWidth(), getHeight(), true);
frameB = Image(Image::PixelFormat::RGB, getWidth(), getHeight(), true);
frameA.setBackupEnabled(false);
frameB.setBackupEnabled(false);
particles.clear();
for (int i = 0; i < max_num_particles; ++i)
{
SmokeParticle newParticle;
newParticle.reset(getWidth(), getHeight());
particles.add(newParticle);
}
smokeParticleImage = Image(Image::PixelFormat::ARGB, 400, 400, true);
{
Graphics g(smokeParticleImage);
g.fillAll(Colours::transparentBlack);
g.setColour(Colours::white.withAlpha(0.4f));
g.fillEllipse(smokeParticleImage.getBounds().reduced(180).toFloat());
}
smokeParticleImage.getPixelData()->applyGaussianBlurEffect (30.0f);
}
struct SmokeParticle
{
float x, y;
float dx, dy;
float size, dsize;
void reset(int width, int height)
{
juce::Random rnd;
x = rnd.nextFloat() * width - 100.0f;
y = height;
dx = (rnd.nextFloat() - 0.5f) * 2.0f;
dy = -1.0f - rnd.nextFloat() * 3.0f;
size = 30.f + rnd.nextFloat() * 500.f;
dsize = 0.1f + rnd.nextFloat() * 0.1f;
}
};
juce::Array<SmokeParticle> particles;
void paint(Graphics& g2) override
{
Graphics g (swap ? frameB : frameA);
g.fillAll (Colours::black);
g.setOpacity (0.92f);
g.drawImageAt (swap ? frameA : frameB, 0, 0);
for (const auto& particle : particles)
g.drawImage (smokeParticleImage, { particle.x, particle.y, particle.size, particle.size });
g2.drawImageAt (swap ? frameB : frameA, 0, 0);
swap = ! swap;
}
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(GSiAnimatedAboutScreen)
};