cocos2d / cocos2d-x

Cocos2d-x is a suite of open-source, cross-platform, game-development tools utilized by millions of developers across the globe. Its core has evolved to serve as the foundation for Cocos Creator 1.x & 2.x.
https://www.cocos.com/en/cocos2d-x
18.16k stars 7.05k forks source link

TrianglesCommands can't do batching when the glprogram using custom shader with custom uniforms #16224

Closed ShuraLiu closed 8 years ago

ShuraLiu commented 8 years ago

In file CCRenderer.cpp, line 876, 2dx checks the MaterialID of the command, and if "newMaterialID == MATERIAL_ID_DO_NOT_BATCH" the command will not do batching. In file CCTrianglesCommand.cpp, line 81, if the glprogram use custom uniforms, the meterialID will be set to MATERIAL_ID_DO_NOT_BATCH, which means the command will never do batching. In the latest version of Spine ,it uses CCTrianglesCommands for rendering, so when I use custom shaders for Spine, it will not do any batching because of the custom uniforms, and it will cost more than 30 drawcalls for a single Spine node in my program, which is really a big problem. I think both the 2dx and Spine need to do something to fix this problem. @minggo

minggo commented 8 years ago

Thanks @ShuraLiu . @ricardoquesada could you please take a look? As i remember you said material id is not implemented as expected, but i may bring performance issue.

ricardoquesada commented 8 years ago

Probably similar to #14826 (but not the same)

UPDATE @ShuraLiu Yes, TriangleCommands cannot be batched. I'll take a look at it to see whether or not it is feasible to do it without affecting the performance.

ShuraLiu commented 8 years ago

@ricardoquesada the API getMaterialID() is only used when doing batching in CCRenderer.cpp now, and if we generate material ID at that time, not in init(), we can confirm the uniform values are the same or not in differernt TriangleCommands.

ricardoquesada commented 8 years ago

@ShuraLiu There are a few issues with uniforms a material id, and basically is that we should compare them. and the question is how to compare them in an efficient and reliable way? currently they are stored in a unordered_map<int,UniformValue>. comparing them in each iteration could be very time consuming... but perhaps it is worth it. perhaps hashing them and update the hash each time a uniform is updated could be another approach.

ideas?

ShuraLiu commented 8 years ago

@ricardoquesada Maybe the key is not TriangleCommand but GLProgram. In 2dx if we render different nodes using the same glprogram with different uniforms, we need to add a custom command before the render command of each node, such as : CustomCommand cmd1; renderer->addCommand(&cmd1); TrianglesCommand cmd2; renderer->addCommand(&cmd2); For example, we need to write a subclass of cocos2d::Sprite and in it's draw funciton add two commands into the render queue, one for setting uniforms and one for rendering. But if the GLProgram can be cloned, we do not need to write the new class, but just clone two GLPrograms and set them to different cocos2d::Sprite classes and set their uniforms. for differents Sprites with the same GLProgram, them share the same uniforms, and the same Material ID, and their commands can be batched. What do you think?

ricardoquesada commented 8 years ago

Sorry, why do you need to write a custom command to have uniforms? Are you adding attributes as well?

If you are adding custom attributes, then this is a duplicate of #14826, if you just want to add custom uniforms, then you don't need to add a custom command. The idea behind GLProgramState (and not GLProgram) is that you can add uniforms easily. Am I missing something?

ShuraLiu commented 8 years ago

@ricardoquesada Because I use the same GLProgram for more than one node, and for the same uniform, the values are different for different nodes. If I set the uniforms in draw function(or visit() func), the values will be covered, only setting unforms in a command can ensure that different nodes take different effects. If I want to set values in draw function, I need to create GLPrograms for each node, although the GLPrograms have the same vert and frag shaders. But if I do it, it's not necessary to check the uniforms when generating material ID. Maybe it's a solution for this issue. Do not check the uniforms, and if the values are different, users need to create a new GLProgram.

ricardoquesada commented 8 years ago

Cocos2d-x has two classes:

So, what you need is:

So, if we want to support custom uniforms we find a way to hash the unordered_map that is being used in GLProgramState

ShuraLiu commented 8 years ago

You are right. I used the GLProgram in wrong way, I should use GLProgramState. The same GLProgramState means the same uniforms, so what about using GLProgramState instead of GLProgram as part of the material ID , and we do not need to hash and compare the uniforms.

ricardoquesada commented 8 years ago

@ShuraLiu good point. I think that should work. thanks.

minggo commented 8 years ago

Hi @ricardoquesada , if i understand correctly

right?

congling commented 8 years ago

More and more devices support opengles 3.0 now, do you consider to add opengles 3.0 support recently? Opengl 3.0 has the instancing feature, which will use gl_instanceID/buffer and glDrawElementsInstanced/glDrawArraysInstanced to batch the drawcall.

Unity 5.4 also support instancing too. http://unitytaiwan.blogspot.tw/2016/05/unite-2016-unity-54-gpu-instancing.html

ricardoquesada commented 8 years ago

@minggo correct. I think we can do that following @ShuraLiu 's suggestion of just comparing the pointer to the GLProgramState.

@congling yes, we have considered supporting it. Of course it is a great feature. Most probably it will be added in v4.0

minggo commented 8 years ago

@ricardoquesada so should use the same GLProgramState if want to be batched?

congling commented 8 years ago

@ricardoquesada this might work, but it'll let the programmer to merge the same state.

Consider this scenario: In a game scene with snow, there're hundreds of snowflake around, a shader to let the snowflake(sprite) rotate 6 degree every frame (for 30 fps). The most common way is to create a GLProgram globally, and then create GLProgramState for each snowflake.

But if I want to batch the drawcalls, I need to build up a 60-length array to record all GLProgramStates in the scene and then set it to each snowflake at the frame when the snowflake is generated. At each frame, array_index++; after 60 frames, the array_index will reset to zero.

Am I correct? Thanks

ricardoquesada commented 8 years ago

@minggo correct. so when we the GLProgramState() has custom uniforms we should check if it is using the same GLProgramState() as the previous batched command triangle. If so, then they can be batched together. I'll work on this tomorrow.

@congling Sorry, I didn't get the example. If you have hundreds of snowflakes (same GLProgram, same Texture ID, and same uniforms) then they should all reuse the GLProgramState. We have a GLProgramStateCache for this reason... for example, each time that you create an sprite, we call setGLProgramState(GLProgramState::getOrCreateWithGLProgramName(GLProgram::SHADER_NAME_POSITION_TEXTURE_COLOR_NO_MVP, texture));, so that we can reuse the GLProgramState.

If you want to use custom uniforms, you should create your own GLProgramState and replace it.

Am I missing something? thanks.

minggo commented 8 years ago

@ricardoquesada sounds good to me.

ricardoquesada commented 8 years ago

fixed in v3 (planned for v3.14) @minggo let me know if you want to have it for v3.13.

minggo commented 8 years ago

@ricardoquesada i think it is ok to just fix it in v3.14.

Fraggle commented 7 years ago

Hi Ricardo,

Do you plan to go one step further at some point and allow batching even if the glProgramState are different but their glProgram and uniforms are the same? The improvement you did is nice but we have several use case where it's tricky for us to share the glProgramState even if most of the time (but not all the time) consecutive node will have exactly the same uniforms.

Thanks,

Sebastien

ricardoquesada commented 7 years ago

@Fraggle I'm no longer working on cocos2d. You should ask @minggo instead. It could be more expensive (resource-wise) to check all possible uniforms, but not that expensive... it could be added, but it would be better to understand the use case first.