I was checking commits/issues for Spine this morning and I see that 3.6 merge is underway. One note on an issue that I saw was:
Ports don't include TwoColorBatch yet. Will have a separate issue to investigate how to implement that in runtimes other than libGDX.
Which I assume is a reference to this item from the change log.
Added Tint black for slots. Requires special runtime support.
The problems being:
passing the a_Black attribute to the shader
-
render systems that don't support or utilize shaders
The following are my thoughts.
I'm going to reference the Unity shader and the libGDX shader. I'll address the issues in reverse order in hopes to maybe point towards a more generic solution.
Render Systems that don't support (or use) shaders FIXED_FUNCTION
Support for the effect utilizing a 'fixed function' pipeline is as simple as using two passes. From the Unity shader, this is the line that does the magic:
return (texColor * i.vertexColor) + float4(((1-texColor.rgb) * texColor.a * _Black.rgb), 0);
Which can be broken into two passes on the '+' operator by changing the DEST_BLEND_OP to additive on the second pass. 1-texColor.rgb
can be done using GL_ONE_MINUS_SRC_COLOR. Be sure to look up glBlendFuncSeparate to control the color and alpha blending ops and pass in _black with glBlendColor.
Because this is done using two passes, it eliminates the need to pass a_Black as a part of the geometry stream. glBlendColor takes care of this. (This is a 'material' style approach)
Passing a_Black to shader SHADER_SUPPORT
This item has a small discrepancy, nothing major but the Unity TwoColor Shader passes _Black as a uniform and libGDX passes as a vertex attribute. There are trade offs in both situations, for Unity the u_Black this shifts the tinting concept into a 'Material' state and prevents geometry with different _tint values from being batched together. Where as the libGDX approach causes the vert data format to change (because v_dark is interleaved with the mesh data).
I'd like to propose to split the difference between the approaches by using a separate stream source for the a_Black data. It addresses the batch-ability of the u_Uniform approach while avoiding a fluid vert data format. Yes, there are trade offs in terms of memory access times with interleaved vs separate, but gpus are pretty smart when it comes to caching.
A General Solution
Today it's a tinting parameter, tomorrow it's a glow parameter. In the future it's a custom user parameter. Someday the Spine editor will support user shaders (with runtime change updates) to facilitate the animation of the custom user parameters. (Probably after a material system is added with custom shaders and the current shader sets are broken out as a 'Standard/Default' material, 😉 )
So let's step back and pretend that the 'Tint' parameter is actually a custom user value. Whether the end-user passes as uniform or attribute is platform specific. Treat 'Tint' as an experiment and use 'reference' implementations that address { FIXED_FUNCTION
, SHADER
, NO_SUPPORT
}. No support being a fallback implementation where the tint parameter is presented to the render system, but completely ignored. This effectively marks the feature as OPTIONAL
which means that the runtime core will provide support, but the runtime render system doesn't necessarily support it.
Keep Unity's implementation as a uniform and libGDX as attribute stream (but perhaps switch to a separate stream). Use these examples as SHADER_SUPPORT
.
For other runtimes that support direct OpenGL calls, implement a FIXED_FUNCTION
approach. (For reference) Unless they already use shaders.
For runtimes without OpenGL calls AND without ONE_MINUS_SRC_COLOR equivalent blending mode, mark these runtimes as NO_SUPPORT
.
I take back my suggestion for a separate stream. Once the attribute is bound it really doesn't matter where it's coming from. Only how easy it is to do on the runtime render system. libGDX is easy, others not so much.