From f7464f8de54c161a64001067f6a0be263ed9f19a Mon Sep 17 00:00:00 2001 From: Ricardo Quesada Date: Sun, 7 Aug 2016 14:51:02 -0700 Subject: [PATCH] fix: TriangleCommands with custom uniforms can be batched (#16329) * fix: TriangleCommands with custom uniforms can be batched TriangleCommands with custom uniforms can be batched together. This improves the performance when using custom uniforms without adding any penalties when not using them Github issue #16224 * better tests --- cocos/renderer/CCTrianglesCommand.cpp | 38 ++-- cocos/renderer/CCTrianglesCommand.h | 2 - .../NewRendererTest/NewRendererTest.cpp | 163 ++++++++++++++++++ .../Classes/NewRendererTest/NewRendererTest.h | 26 +++ 4 files changed, 210 insertions(+), 19 deletions(-) diff --git a/cocos/renderer/CCTrianglesCommand.cpp b/cocos/renderer/CCTrianglesCommand.cpp index 81883ee48c..4d5f581b31 100644 --- a/cocos/renderer/CCTrianglesCommand.cpp +++ b/cocos/renderer/CCTrianglesCommand.cpp @@ -36,7 +36,6 @@ TrianglesCommand::TrianglesCommand() :_materialID(0) ,_textureID(0) ,_glProgramState(nullptr) -,_glProgram(nullptr) ,_blendType(BlendFunc::DISABLE) ,_alphaTextureID(0) { @@ -60,14 +59,12 @@ void TrianglesCommand::init(float globalOrder, GLuint textureID, GLProgramState* _mv = mv; if( _textureID != textureID || _blendType.src != blendType.src || _blendType.dst != blendType.dst || - _glProgramState != glProgramState || - _glProgram != glProgramState->getGLProgram()) + _glProgramState != glProgramState) { _textureID = textureID; _blendType = blendType; _glProgramState = glProgramState; - _glProgram = glProgramState->getGLProgram(); - + generateMaterialID(); } } @@ -89,18 +86,25 @@ TrianglesCommand::~TrianglesCommand() void TrianglesCommand::generateMaterialID() { - // do not batch if using custom uniforms (since we cannot batch) it - if(_glProgramState->getUniformCount() > 0) - { - _materialID = Renderer::MATERIAL_ID_DO_NOT_BATCH; - setSkipBatching(true); - } - else - { - int glProgram = (int)_glProgram->getProgram(); - int intArray[4] = { glProgram, (int)_textureID, (int)_blendType.src, (int)_blendType.dst}; - _materialID = XXH32((const void*)intArray, sizeof(intArray), 0); - } + // glProgramState is hashed because it contains: + // * uniforms/values + // * glProgram + // + // we safely can when the same glProgramState is being used then they share those states + // if they don't have the same glProgramState, they might still have the same + // uniforms/values and glProgram, but it would be too expensive to check the uniforms. + struct { + GLuint textureId; + GLenum blendSrc; + GLenum blendDst; + void* glProgramState; + } hashMe; + + hashMe.textureId = _textureID; + hashMe.blendSrc = _blendType.src; + hashMe.blendDst = _blendType.dst; + hashMe.glProgramState = _glProgramState; + _materialID = XXH32((const void*)&hashMe, sizeof(hashMe), 0); } void TrianglesCommand::useMaterial() const diff --git a/cocos/renderer/CCTrianglesCommand.h b/cocos/renderer/CCTrianglesCommand.h index 133b14a7af..207cacf50f 100644 --- a/cocos/renderer/CCTrianglesCommand.h +++ b/cocos/renderer/CCTrianglesCommand.h @@ -105,8 +105,6 @@ protected: GLuint _textureID; /**GLprogramstate for the command. encapsulate shaders and uniforms.*/ GLProgramState* _glProgramState; - /**The GLProgram used by GLProgramState*/ - GLProgram* _glProgram; /**Blend function when rendering the triangles.*/ BlendFunc _blendType; /**Rendered triangles.*/ diff --git a/tests/cpp-tests/Classes/NewRendererTest/NewRendererTest.cpp b/tests/cpp-tests/Classes/NewRendererTest/NewRendererTest.cpp index 17949fdd87..1591a32fcb 100644 --- a/tests/cpp-tests/Classes/NewRendererTest/NewRendererTest.cpp +++ b/tests/cpp-tests/Classes/NewRendererTest/NewRendererTest.cpp @@ -38,6 +38,8 @@ NewRendererTests::NewRendererTests() ADD_TEST_CASE(CaptureNodeTest); ADD_TEST_CASE(BugAutoCulling); ADD_TEST_CASE(RendererBatchQuadTri); + ADD_TEST_CASE(RendererUniformBatch); + ADD_TEST_CASE(RendererUniformBatch2); }; std::string MultiSceneTest::title() const @@ -610,3 +612,164 @@ std::string RendererBatchQuadTri::subtitle() const { return "QuadCommand and TriangleCommands are batched together"; } + + +// +// +// RendererUniformBatch +// + +RendererUniformBatch::RendererUniformBatch() +{ + Size s = Director::getInstance()->getWinSize(); + + auto glBlurState = createBlurGLProgramState(); + auto glSepiaState = createSepiaGLProgramState(); + + auto x_inc = s.width / 20; + auto y_inc = s.height / 6; + + for (int y=0; y<6; ++y) + { + for (int x=0; x<20; ++x) + { + auto sprite = Sprite::create("Images/grossini.png"); + sprite->setPosition(Vec2(x * x_inc, y * y_inc)); + sprite->setScale(0.4); + addChild(sprite); + + if (y>=4) { + sprite->setGLProgramState(glSepiaState); + } else if(y>=2) { + sprite->setGLProgramState(glBlurState); + } + } + } +} + +GLProgramState* RendererUniformBatch::createBlurGLProgramState() +{ +#if (CC_TARGET_PLATFORM != CC_PLATFORM_WINRT) + const std::string shaderName("Shaders/example_Blur.fsh"); +#else + const std::string shaderName("Shaders/example_Blur_winrt.fsh"); +#endif + // outline shader + auto fileUtiles = FileUtils::getInstance(); + auto fragmentFullPath = fileUtiles->fullPathForFilename(shaderName); + auto fragSource = fileUtiles->getStringFromFile(fragmentFullPath); + auto glprogram = GLProgram::createWithByteArrays(ccPositionTextureColor_noMVP_vert, fragSource.c_str()); + auto glprogramstate = (glprogram == nullptr ? nullptr : GLProgramState::getOrCreateWithGLProgram(glprogram)); + + glprogramstate->setUniformVec2("resolution", Vec2(85,121)); +#if (CC_TARGET_PLATFORM != CC_PLATFORM_WINRT) + glprogramstate->setUniformFloat("blurRadius", 10); + glprogramstate->setUniformFloat("sampleNum", 5); +#endif + + return glprogramstate; +} + +GLProgramState* RendererUniformBatch::createSepiaGLProgramState() +{ + const std::string shaderName("Shaders/example_Sepia.fsh"); + + // outline shader + auto fileUtiles = FileUtils::getInstance(); + auto fragmentFullPath = fileUtiles->fullPathForFilename(shaderName); + auto fragSource = fileUtiles->getStringFromFile(fragmentFullPath); + auto glprogram = GLProgram::createWithByteArrays(ccPositionTextureColor_noMVP_vert, fragSource.c_str()); + auto glprogramstate = (glprogram == nullptr ? nullptr : GLProgramState::getOrCreateWithGLProgram(glprogram)); + + return glprogramstate; +} + +std::string RendererUniformBatch::title() const +{ + return "RendererUniformBatch"; +} + +std::string RendererUniformBatch::subtitle() const +{ + return "Only 9 draw calls should appear"; +} + + +// +// RendererUniformBatch2 +// + +RendererUniformBatch2::RendererUniformBatch2() +{ + Size s = Director::getInstance()->getWinSize(); + + auto glBlurState = createBlurGLProgramState(); + auto glSepiaState = createSepiaGLProgramState(); + + auto x_inc = s.width / 20; + auto y_inc = s.height / 6; + + for (int y=0; y<6; ++y) + { + for (int x=0; x<20; ++x) + { + auto sprite = Sprite::create("Images/grossini.png"); + sprite->setPosition(Vec2(x * x_inc, y * y_inc)); + sprite->setScale(0.4); + addChild(sprite); + + auto r = CCRANDOM_0_1(); + if (r < 0.33) + sprite->setGLProgramState(glSepiaState); + else if (r < 0.66) + sprite->setGLProgramState(glBlurState); + } + } +} + +GLProgramState* RendererUniformBatch2::createBlurGLProgramState() +{ +#if (CC_TARGET_PLATFORM != CC_PLATFORM_WINRT) + const std::string shaderName("Shaders/example_Blur.fsh"); +#else + const std::string shaderName("Shaders/example_Blur_winrt.fsh"); +#endif + // outline shader + auto fileUtiles = FileUtils::getInstance(); + auto fragmentFullPath = fileUtiles->fullPathForFilename(shaderName); + auto fragSource = fileUtiles->getStringFromFile(fragmentFullPath); + auto glprogram = GLProgram::createWithByteArrays(ccPositionTextureColor_noMVP_vert, fragSource.c_str()); + auto glprogramstate = (glprogram == nullptr ? nullptr : GLProgramState::getOrCreateWithGLProgram(glprogram)); + + glprogramstate->setUniformVec2("resolution", Vec2(85,121)); +#if (CC_TARGET_PLATFORM != CC_PLATFORM_WINRT) + glprogramstate->setUniformFloat("blurRadius", 10); + glprogramstate->setUniformFloat("sampleNum", 5); +#endif + + return glprogramstate; +} + +GLProgramState* RendererUniformBatch2::createSepiaGLProgramState() +{ + const std::string shaderName("Shaders/example_Sepia.fsh"); + + // outline shader + auto fileUtiles = FileUtils::getInstance(); + auto fragmentFullPath = fileUtiles->fullPathForFilename(shaderName); + auto fragSource = fileUtiles->getStringFromFile(fragmentFullPath); + auto glprogram = GLProgram::createWithByteArrays(ccPositionTextureColor_noMVP_vert, fragSource.c_str()); + auto glprogramstate = (glprogram == nullptr ? nullptr : GLProgramState::getOrCreateWithGLProgram(glprogram)); + + return glprogramstate; +} + +std::string RendererUniformBatch2::title() const +{ + return "RendererUniformBatch 2"; +} + +std::string RendererUniformBatch2::subtitle() const +{ + return "Mixing different shader states should work ok"; +} diff --git a/tests/cpp-tests/Classes/NewRendererTest/NewRendererTest.h b/tests/cpp-tests/Classes/NewRendererTest/NewRendererTest.h index 95dd625064..b63b44a621 100644 --- a/tests/cpp-tests/Classes/NewRendererTest/NewRendererTest.h +++ b/tests/cpp-tests/Classes/NewRendererTest/NewRendererTest.h @@ -166,4 +166,30 @@ protected: RendererBatchQuadTri(); }; +class RendererUniformBatch : public MultiSceneTest +{ +public: + CREATE_FUNC(RendererUniformBatch); + virtual std::string title() const override; + virtual std::string subtitle() const override; +protected: + RendererUniformBatch(); + + cocos2d::GLProgramState* createBlurGLProgramState(); + cocos2d::GLProgramState* createSepiaGLProgramState(); +}; + +class RendererUniformBatch2 : public MultiSceneTest +{ +public: + CREATE_FUNC(RendererUniformBatch2); + virtual std::string title() const override; + virtual std::string subtitle() const override; +protected: + RendererUniformBatch2(); + + cocos2d::GLProgramState* createBlurGLProgramState(); + cocos2d::GLProgramState* createSepiaGLProgramState(); +}; + #endif //__NewRendererTest_H_