[cocos2dx] Two-color tinted skeletons do not render on the first frame
badlogic opened this issue · comments
Some more info about this bug:
While two-color tinting is enabled, it causes visual artifacts on the skeleton on the frames just before/after going to a new animation. I never set animation mixing for any of these animations however, and Cocos2d-x defaults the mixing to 0.0f, at least it did in v3.17.2 (so I don't think these visual artifacts are related to any animation mixing, especially since turning off two-color tinting fixes these artifacts).
It also seems to make any skeleton atlases that were exported with premultiplied alphas enabled to render with artifacts on the edges of the symbols: as if the premultiplied alpha was being applied twice. But neither changing the GL mode to BlendFunc::ALPHA_NON_PREMULTIPLIED
and/or calling SkeletonRenderer::setOpacityModifyRGB(false)
on the Skeleton with the premultiplied alphas in it's Atlas changed this behavior. It's worth noting however that disabling Image::PNG_PREMULTIPLIED_ALPHA_ENABLED does cause the Skeleton to render properly and without artifacts, but it also breaks every other PNG image used.
Two-color tinting is implemented with a custom batcher (SkeletonTwoColorBatch.cpp
) which submits custom commands (TwoColorTrianglesCommand
) to the Cocos2d-x renderer.
Following the repro steps for the RaptorExample
, I've confirmed that on the first frame, the SkeletonRenderer
actually submits render commands to the batcher, which submits them to the Cocos2d-x Renderer
, which you can see here:
![Screenshot 2024-04-30 at 11 27 57](https://private-user-images.githubusercontent.com/514052/326746807-a5570ac5-dbbe-4d94-b2de-91b6bbda026f.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MjQxNjYyMTcsIm5iZiI6MTcyNDE2NTkxNywicGF0aCI6Ii81MTQwNTIvMzI2NzQ2ODA3LWE1NTcwYWM1LWRiYmUtNGQ5NC1iMmRlLTkxYjZiYmRhMDI2Zi5wbmc_WC1BbXotQWxnb3JpdGhtPUFXUzQtSE1BQy1TSEEyNTYmWC1BbXotQ3JlZGVudGlhbD1BS0lBVkNPRFlMU0E1M1BRSzRaQSUyRjIwMjQwODIwJTJGdXMtZWFzdC0xJTJGczMlMkZhd3M0X3JlcXVlc3QmWC1BbXotRGF0ZT0yMDI0MDgyMFQxNDU4MzdaJlgtQW16LUV4cGlyZXM9MzAwJlgtQW16LVNpZ25hdHVyZT05ZDUwMTRmZmEzZGY1ODhiYjJiZWRiOTA2NjBhZjRiY2MyZTdjODkxMDM1NWRmODQ1YWIwN2U2YWY3Y2QwN2QwJlgtQW16LVNpZ25lZEhlYWRlcnM9aG9zdCZhY3Rvcl9pZD0wJmtleV9pZD0wJnJlcG9faWQ9MCJ9.tMCCC4nl_MFt6TlBcW06tH7xlb_fWek5U0ATwxiRILc)
This happens in CCRenderer.cpp:371
. These submitted commands are ultimately processed in Renderer::processRenderCommand()
![Screenshot 2024-04-30 at 11 34 57](https://private-user-images.githubusercontent.com/514052/326747977-00297f9c-4d53-4ba0-abb2-2c4dffc2fb60.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MjQxNjYyMTcsIm5iZiI6MTcyNDE2NTkxNywicGF0aCI6Ii81MTQwNTIvMzI2NzQ3OTc3LTAwMjk3ZjljLTRkNTMtNGJhMC1hYmIyLTJjNGRmZmMyZmI2MC5wbmc_WC1BbXotQWxnb3JpdGhtPUFXUzQtSE1BQy1TSEEyNTYmWC1BbXotQ3JlZGVudGlhbD1BS0lBVkNPRFlMU0E1M1BRSzRaQSUyRjIwMjQwODIwJTJGdXMtZWFzdC0xJTJGczMlMkZhd3M0X3JlcXVlc3QmWC1BbXotRGF0ZT0yMDI0MDgyMFQxNDU4MzdaJlgtQW16LUV4cGlyZXM9MzAwJlgtQW16LVNpZ25hdHVyZT1lZDU2MjE3MWFhNTEwYWFhNjAzMTVlZTg2MzdkYmY0OTlkZTQxNmU0YzdlYzgyMTM1NTMyZGI5ZWM4MDBlYzQyJlgtQW16LVNpZ25lZEhlYWRlcnM9aG9zdCZhY3Rvcl9pZD0wJmtleV9pZD0wJnJlcG9faWQ9MCJ9.ogeVLmSqYTI6VERhegYkaALeFX050HM2d3Sj1Kz2Lgw)
I can also confirm that the commands are submitted to the GPU:
![Screenshot 2024-04-30 at 11 38 35](https://private-user-images.githubusercontent.com/514052/326749127-b2233aca-6597-458b-9ace-b05334034f1e.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MjQxNjYyMTcsIm5iZiI6MTcyNDE2NTkxNywicGF0aCI6Ii81MTQwNTIvMzI2NzQ5MTI3LWIyMjMzYWNhLTY1OTctNDU4Yi05YWNlLWIwNTMzNDAzNGYxZS5wbmc_WC1BbXotQWxnb3JpdGhtPUFXUzQtSE1BQy1TSEEyNTYmWC1BbXotQ3JlZGVudGlhbD1BS0lBVkNPRFlMU0E1M1BRSzRaQSUyRjIwMjQwODIwJTJGdXMtZWFzdC0xJTJGczMlMkZhd3M0X3JlcXVlc3QmWC1BbXotRGF0ZT0yMDI0MDgyMFQxNDU4MzdaJlgtQW16LUV4cGlyZXM9MzAwJlgtQW16LVNpZ25hdHVyZT1lNDZlYWQzOWFhZjY5MTgxOTVmMDUyNWRiZTAwNDZkYmIxYmJhMTZjNmZiYzMyM2M1ZTJlMmQzZWI0ZTg5MGY5JlgtQW16LVNpZ25lZEhlYWRlcnM9aG9zdCZhY3Rvcl9pZD0wJmtleV9pZD0wJnJlcG9faWQ9MCJ9.kqXWQhrtRshdDaqNTOfF-A5JXDzcw9Tt_aBJStczKJc)
In subsequent frames, the exact same code paths are executed, but result in the commands actually being rendered.
Another indicator that we do everything correctly is that in our example, the Cocos2d-x backend reports the number of vertices and draw calls correctly for the first frame, despite nothing being rendered.
![Screenshot 2024-04-30 at 11 56 39](https://private-user-images.githubusercontent.com/514052/326754978-709679c0-8b83-4109-9fc4-31a017f6c632.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3MjQxNjYyMTcsIm5iZiI6MTcyNDE2NTkxNywicGF0aCI6Ii81MTQwNTIvMzI2NzU0OTc4LTcwOTY3OWMwLThiODMtNDEwOS05ZmM0LTMxYTAxN2Y2YzYzMi5wbmc_WC1BbXotQWxnb3JpdGhtPUFXUzQtSE1BQy1TSEEyNTYmWC1BbXotQ3JlZGVudGlhbD1BS0lBVkNPRFlMU0E1M1BRSzRaQSUyRjIwMjQwODIwJTJGdXMtZWFzdC0xJTJGczMlMkZhd3M0X3JlcXVlc3QmWC1BbXotRGF0ZT0yMDI0MDgyMFQxNDU4MzdaJlgtQW16LUV4cGlyZXM9MzAwJlgtQW16LVNpZ25hdHVyZT0xNTQ1YmUwYWFlYjY2YzNhZjY3MDM2ZGM1ZTRjZjJmNzk5NGE1ZTNiYmI4MmE3YWVmMGRmNGFjYWIxMTdiMTc2JlgtQW16LVNpZ25lZEhlYWRlcnM9aG9zdCZhY3Rvcl9pZD0wJmtleV9pZD0wJnJlcG9faWQ9MCJ9.LHdX1UACqZosD-A6m03vPph6fkDBeNdIrqjHwg3wZiE)
The second issue you described might well be a "follow-on" error from the original issue that causes the first frame not to be drawn.
I'm afraid I have no idea how this can be. On our end, we are not doing anything different between the first and subsequent frames. My guess is that this is a bug in Cocos2d-x with regards to custom rendering commands. As such, we might have to loop in the Cocos2d-X folks. (or fork maintainers) to fix this. I'm afraid I'm not familiar enough with Cocos2d-X internals to fix this myself.
So, ultimately, this needs to be passed to whoever is now maintaining Cocos2d-X (or a fork). I'm afraid on our end there is not much we can do, other than recommend to not use the two-color tinting renderer.
So, ultimately, this needs to be passed to whoever is now maintaining Cocos2d-X (or a fork). I'm afraid on our end there is not much we can do, other than recommend to not use the two-color tinting renderer.
Unfortunately Cocos2d-x has quietly dropped all maintenance and support for that engine back in 2019, so there is 0 chance of a fix ever making it to the main branch there. However I did let the folks at Axmol know: hopefully they will be able to fix the issue since they are much more knowledgeable about the inner workings of Axmol than I am.
We were using Cocos2d-x v3.17.2 until recently, so I'm not yet familiar with the changes made to the renderer logic in Cocos2d-x v4.0 (and thus also Axmol). Hopefully it ends up being a relatively simple fix, though I wasn't able to fix it after trying and experimenting for a day and a half. That said I was mostly playing around with the Spine runtimes, and not the rendering/triangle commands like you suggest.
So for anyone experiencing this issue, rh101 over on the Axmol fork was able to fix this issue with the following changes:
In
SkeletonRenderer.cpp
, around line 461, find this section of code for the two color tint:} else { TwoColorTrianglesCommand *batchedTriangles = lastTwoColorTrianglesCommand = twoColorBatch->addCommand(renderer, _globalZOrder, texture, _programState, blendFunc, trianglesTwoColor, transform, transformFlags); V3F_C4B_C4B_T2F *vertex = batchedTriangles->getTriangles().verts; for (int v = 0, vn = batchedTriangles->getTriangles().vertCount; v < vn; ++v, ++vertex) { vertex->color = color4B; vertex->color2 = darkColor4B; } }
Change it to this:
} else { V3F_C4B_C4B_T2F* vertex = trianglesTwoColor.verts; for (int v = 0, vn = trianglesTwoColor.vertCount; v < vn; ++v, ++vertex) { vertex->color = color4B; vertex->color2 = darkColor4B; } lastTwoColorTrianglesCommand = twoColorBatch->addCommand(renderer, _globalZOrder, texture, _programState, blendFunc, trianglesTwoColor, transform, transformFlags); }
Hopefully this might be useful for folks who are still using Cocos2d-x v4.0, even if they need to manually implement this change; this change will most likely be pulled into the main branch for Axmol soon.
Thanks for taking the time to look into this issue badlogic, cheers!
This has been fixed in the 4.2 branch. Note that this fix also handles clipping, which the change suggested by rh101 does not.
Is there a reason Axmol ships their own spine-cocos2d-x runtimes? We'd prefer if people used our official runtime, which lowers the burden on Axmol maintainers and prevents issues in the runtime fork being reported as an issue of the official runtime (should they diverge).
This has been fixed in the 4.2 branch. Note that this fix also handles clipping, which the change suggested by rh101 does not.
The Axmol versions of the runtimes (here) do have the clipping section handled too, along with the non-two tint sections, although they probably aren't required since the vertex data isn't set the same way as the two tint render command. The Axmol Spine runtimes for 3.6, 3.7, 3.8, 4.0 and 4.1 have all been updated with the fix for this issue.
Is there a reason Axmol ships their own spine-cocos2d-x runtimes?
Axmol and Cocos2d-x have diverged, and while they are mostly compatible, to use the same runtime for both would probably require a lot of preprocessor conditionals, but that's really up to you.