Blur at pause works very slow

I’ve tried this code examples

  1. [Cocos3.8 Tutorial] RenderTexture + Blur
  2. https://github.com/cocos2d/cocos2d-x/blob/v3/tests/cpp-tests/Classes/ShaderTest/ShaderTest.cpp#L341

First works really fast, but about 0.9-1.2 sec. And second example give me about 3 seconds of processing. Thats all anyway too slow.

So, what I’m doing - render all scene at pause using RenderTexture and then applying blur using that methods.

I want a speed like this(tested on iPod Touch 5G, it’s really old and slow device, but blur works really fast):

Example from badland 2. Also badland 1 has a bit different blur as I remember, but works flawless too on old device and by the way its made with cocos2d-x :wink: .

How I can do this? This is possible, but how? Anyone? @zhangxm @Victor_K

Are you creating another FBO to apply shader on whole scene?

Ahh yea, forgot to say, I’m complete noob in all that, so I just used a code. Ideally I just want some fast native component in cocos2d-x which works fast… so I just need to use a simple line like blur bg or smt.

There is a possibility to make this thing fast. if we can avoid to create another frameBuffer object and can get directt texture from renderQueue we can manage to apply shader to it. Thank we can push multiple layers over it to use multiple shaders at a time. tell me if you find it. I am also stucked :wink: OR what we can do is tell the renderer to not use default fbo and use our own. but how thats a little trick.

Programmers who created badland know answer for sure. So sad, that they used this engine but never posting any suggestions on the forum. I just want to ask them - hot to make same kind of blur as in their game.

seriously many people are still developing cool games using this engine still now a days. but hardly very few of them helps to others in community.

Hi,
Are you doing the render to texture inside the Draw function?

No. Okay. SO, is anyone created successfully fast working blur of scene? Anyone? Even creators of cocos2d-x can’t do that :slight_smile: ?

Well, for my lighting system I use a blur shader to have soft shadows and it gets executed every frame for every light so it’s fast at least on my PC and on my android phone.
I cannot help if you don’t describe exactly what you’re doing. If you’re following exactly the first tutorial and you’re saving the blurred image and then reloading it to show it then it is going to be really slow.
Just by reading the first example, that code should execute quite fast if you’re doing it right.
Maybe try to avoid calculating the weighs and use a blur shader with predefined weights like this one: https://github.com/HaxeFlixel/flixel-demos/blob/master/Effects/PostProcess/assets/shaders/blur.frag
It is not going to look as good but it is a good start.
Another question? exactly in what moment are you loading the shader?, if you do it when you press the “pause button”, then it is also going to be slow because the gpu has to compile the shader.

As I said: [quote=“KAMIKAZE, post:3, topic:38061”]
I’m complete noob in all that, so I just used a code.
[/quote]
So I’m using:

TextureBlur.h

#pragma once

#include "cocos2d.h"

class TextureBlur
{
public:
    static cocos2d::Sprite* create(cocos2d::Texture2D* target, const int radius, const int step = 1);

private:
    static void calculateGaussianWeights(const int points, float* weights);    
    static cocos2d::GLProgram* getBlurShader(cocos2d::Vec2 pixelSize, cocos2d::Vec2 direction, const int radius, float* weights);
};

TextureBlur.cpp

#include "TextureBlur.h"

USING_NS_CC;

static const int maxRadius = 64;

void TextureBlur::calculateGaussianWeights(const int points, float* weights)
{
    float dx = 1.0f/float(points-1);
    float sigma = 1.0f/3.0f;
    float norm = 1.0f/(sqrtf(2.0f*M_PI)*sigma*points);
    float divsigma2 = 0.5f/(sigma*sigma);
    weights[0] = 1.0f;
    for (int i = 1; i < points; i++)
    {
        float x = float(i)*dx;
        weights[i] = norm*expf(-x*x*divsigma2);
        weights[0] -= 2.0f*weights[i];
    }
}

GLProgram* TextureBlur::getBlurShader(Vec2 pixelSize, Vec2 direction, const int radius, float* weights)
{
	std::string blurShaderPath = FileUtils::getInstance()->fullPathForFilename("TextureBlur.fsh");
	const GLchar *blur_frag = __String::createWithContentsOfFile(blurShaderPath.c_str())->getCString();

	GLProgram *blur = GLProgram::createWithByteArrays(ccPositionTextureColor_vert, blur_frag);
	
	GLProgramState* state = GLProgramState::getOrCreateWithGLProgram(blur);
	state->setUniformVec2("pixelSize", pixelSize);
	state->setUniformVec2("direction", direction);
	state->setUniformInt("radius", radius);
	state->setUniformFloatv("weights", radius, weights);

    return blur;
}

Sprite* TextureBlur::create(Texture2D* target, const int radius, const int step)
{
    CCASSERT(target != nullptr, "Null pointer passed as a texture to blur");
    CCASSERT(radius <= maxRadius, "Blur radius is too big");
    CCASSERT(radius > 0, "Blur radius is too small");
    CCASSERT(step <= radius/2 + 1 , "Step is too big");
    CCASSERT(step > 0 , "Step is too small");
    
	Size textureSize = target->getContentSize();
    Vec2 pixelSize = Vec2(float(step)/textureSize.width, float(step)/textureSize.height);
    int radiusWithStep = radius/step;
    
    float* weights = new float[maxRadius];
    calculateGaussianWeights(radiusWithStep, weights);
    
    Sprite* stepX = Sprite::createWithTexture(target);
    stepX->retain();
    stepX->setPosition(Point(0.5f*textureSize.width, 0.5f*textureSize.height));
    stepX->setFlippedY(true);
    
    GLProgram* blurX = getBlurShader(pixelSize, Vec2(1.0f, 0.0f), radiusWithStep, weights);
    stepX->setGLProgram(blurX);
    
    RenderTexture* rtX = RenderTexture::create(textureSize.width, textureSize.height);
    rtX->retain();
    rtX->begin();
    stepX->visit();
    rtX->end();
    
    Sprite* stepY = Sprite::createWithTexture(rtX->getSprite()->getTexture());
    stepY->retain();
    stepY->setPosition(Point(0.5f*textureSize.width, 0.5f*textureSize.height));
    stepY->setFlippedY(true);
    
    GLProgram* blurY = getBlurShader(pixelSize, Vec2(0.0f, 1.0f), radiusWithStep, weights);
    stepY->setGLProgram(blurY);
    
    RenderTexture* rtY = RenderTexture::create(textureSize.width, textureSize.height);
    rtY->retain();
    rtY->begin();
    stepY->visit();
    rtY->end();
    
    return rtY->getSprite();
}

Pause button pressed:

RenderTexture* render = RenderTexture::create((int)WS.width,(int)WS.height, Texture2D::PixelFormat::RGB565);
render->beginWithClear(0, 0, 0, 0);
scene->visit();
render->end();
TextureBlur *textureBlur = TextureBlur::create(render->getSprite()->getTexture(), 10);
textureBlur->setPosition(posByScreen(0.5, 0.5));
addChild(textureBlur,10);

With radius 10, it’s works slow, but ideally I need maximum radius - 64, with it, it’s result I needed, but currently crazy slow.

p.s. honestly I just need badland like crazy fast blur effect, same. Is anyone did such? How Is it possible, that badland developers did this easily, but no cocos2d-x solution ever can be found?

Sadly I can not help out with any shader experience but maybe I can still help you out.

Try different faking techniques until you found your performant one.

For example, slice the screen in a 10x10 grid. if you have a way to find out the average or dominant color in each grid, you could try to use a brush texture (like a brush in Photoshop) to batch draw that into a texture sprite and than just fade that in.

What do you think about that?
Could that work?

Do a dry run in photoshop before you write a ton of code :smile:

1 Like

Well, no. I just want to have blur same as in badland. Is anyone smart here who can do this? Who has enough knowledge in shaders? I don’t understand why blur is so secret thing?.. or something…
Nobody done it’s really fast for real life like it done, again that badland game. Only who develop that game knows shaders and nobody else? @zhangxm @Victor_K @ricardo @s1ddok any one more maybe?

Hi,
As I tought, you’re reloading the shader everytime you press the pause which is slow, you’re also creating the RenderTextures everytime (I’m not sure if this is very slow but it would be faster to “cache” the textures).
Some ideas:

  • Instead of creating lots of render textures everytime you use the shader store them as member variables
  • When you’re capturing the screen save it to a texture with half or a quarter of the size of the screen and apply the blur to this little texture, then scale it to fit the screen. With this, the shader should be at least as twice as fast and you have to apply a lot less passes (change the radius from 64 to 10 at you would get really similar results).
  • Like I said before, change the shader code to use a simpler shader like the one I showed you

I took the code of the first sample and modified it to use a simpler shader and to scale down the textures it uses. on my computer it runs twice as fast, the code is a little messy and has some harcoded number for the scales so you have to change that. The simpler shader doesn’t look as good but I think is good enough.

RenderTexture.zip (45.8 KB)

see this one blur shader may be it works well.

uniform sampler2D bgl_RenderedTexture;
uniform float distance;
uniform float percentage;

void main(void)
{
	float value = 0.0025 * distance;

	vec4 color = texture2D(bgl_RenderedTexture, vec2(gl_TexCoord[0].st.x + value * 0.25, gl_TexCoord[0].st.y + value * 0.25));
	color += texture2D(bgl_RenderedTexture, vec2(gl_TexCoord[0].st.x - value * 0.25, gl_TexCoord[0].st.y - value * 0.25));
	color += texture2D(bgl_RenderedTexture, vec2(gl_TexCoord[0].st.x + value * 0.25, gl_TexCoord[0].st.y - value * 0.25));
	color += texture2D(bgl_RenderedTexture, vec2(gl_TexCoord[0].st.x - value * 0.25, gl_TexCoord[0].st.y + value * 0.25));

	color += texture2D(bgl_RenderedTexture, vec2(gl_TexCoord[0].st.x + value * 0.5, gl_TexCoord[0].st.y + value * 0.5));
	color += texture2D(bgl_RenderedTexture, vec2(gl_TexCoord[0].st.x - value * 0.5, gl_TexCoord[0].st.y - value * 0.5));
	color += texture2D(bgl_RenderedTexture, vec2(gl_TexCoord[0].st.x + value * 0.5, gl_TexCoord[0].st.y - value * 0.5));
	color += texture2D(bgl_RenderedTexture, vec2(gl_TexCoord[0].st.x - value * 0.5, gl_TexCoord[0].st.y + value * 0.5));

	color += texture2D(bgl_RenderedTexture, vec2(gl_TexCoord[0].st.x + value * 0.75, gl_TexCoord[0].st.y + value * 0.75));
	color += texture2D(bgl_RenderedTexture, vec2(gl_TexCoord[0].st.x - value * 0.75, gl_TexCoord[0].st.y - value * 0.75));
	color += texture2D(bgl_RenderedTexture, vec2(gl_TexCoord[0].st.x + value * 0.75, gl_TexCoord[0].st.y - value * 0.75));
	color += texture2D(bgl_RenderedTexture, vec2(gl_TexCoord[0].st.x - value * 0.75, gl_TexCoord[0].st.y + value * 0.75));

	color += texture2D(bgl_RenderedTexture, vec2(gl_TexCoord[0].st.x + value, gl_TexCoord[0].st.y + value));
	color += texture2D(bgl_RenderedTexture, vec2(gl_TexCoord[0].st.x - value, gl_TexCoord[0].st.y - value));
	color += texture2D(bgl_RenderedTexture, vec2(gl_TexCoord[0].st.x + value, gl_TexCoord[0].st.y - value));
	color += texture2D(bgl_RenderedTexture, vec2(gl_TexCoord[0].st.x - value, gl_TexCoord[0].st.y + value));
	color /= 16.0;
	vec4 origcolor = texture2D(bgl_RenderedTexture, gl_TexCoord[0].st);

	gl_FragColor = origcolor + percentage * (color - origcolor);
}

Thank you for your suggestions it’s ok, but I think just improve speed a bit… I will try it laters. And again - it’s like badland style blur? Sure not, but anyways, yes if compare some working fast blur and nothing - I will take 1st option. But ideally I need badland like blur.
However, why you storing file in file system? After your suggestions this looks wrong.

Also, what this first line doing?:

TextureBlur::init();
TextureBlur::create(rt->getSprite()->getTexture(), 5, fileName, std::bind(&HelloWorld::completionCallback, this, fullFileName));

Initing in nothing? I don’t understand C++ or what?

First time I take and modified TextureBlur class to removed completionCallback and saveToFile.
Also, on PC it works fast, but no reason to test on it. I’m testing only on device, iPod Touch 5G, it’s really slow, but badland game pause blur works crazy fast on it.
I’ve posted modified class in my previous post.

@gplayers I have no idea how to use it, if I can just replace shader file and it will work, well yes I can use it :smiley:

Found some info.
https://github.com/mattdesl/lwjgl-basics/wiki/ShaderLesson5
Conclusion is: “This solution is impractical on low end hardware (such as Android or iOS) due to the fill-rate and multiple passes involved.”
So, should be used: https://github.com/mattdesl/lwjgl-basics/wiki/OpenGL-ES-Blurs

Any ideas how to do same with cocos2d-x?

i guess its possible for good i will try :sunglasses:

Hi,
I just took the code from the github sample you showed at the begining of the post, thats why it also saves the image to disk. If you read the code, you can easily modify it to work with your code just as you modified the original example. I took the github code to be able to test it more easily.
That init is just a function I created, maybe it’s a very bad name considering how cocos functions are named.

Hey i just found a file named postprocess.fsh in the shaders folder in the Badland app package. There is some mention of blur in there, maybe you can get some inspiration from that as well.
Good luck

Hi,
implementing that is not as easy for your task and is going to be extremely slow.
That code precomputes diferent strengths of blur for an image and then interpolates between them to obtain other strengths. What you want is to blur what is currently on the screen so is impossible to have precomputed data unless the screen doesn’t change.

As I said, try with the code you already have but draw the scene on a tiny texture (maybe a quarter of the size or even smaller) and then blur that little texture. to show it simple scale it to fit the screen. this is going to speed up the shader a lot.

1 Like