Occasionally, users want to render certain shapes differently than the methods available in the standard release of Scalefrom. This can be a way to achieve altered vertex transformations, or special fragment shader fill effects.
Generally, only small subsets of shapes within a movie need special rendering effects. These objects are identified in ActionScript by using the AS2 extension properties “rendererString” and “rendererFloat”, or the AS3 extension methods “setRendererString” and “setRendererFloat”. Refer to the AS2 Extensions Reference and AS3 Extensions Reference for details on their usage. Setting a string and/or float on a movie clip causes an instance of UserDataState::Data to be pushed on the Render::HAL’s UserDataStack member via the HAL::PushUserData function, before the movie clip is rendered. The entire stack is accessible at any time, allowing for the usage of nested data; however, each movie clip can only contain a single String and/or Float. When the movie clip is finished rendering, the UserDataState::Data is popped off the stack, from the HAL::PopUserData function.
Currently, to perform custom rendering taking into account the user data set on a movie clip, the HAL must be modified. While there are several ways to modify the HAL to perform custom rendering, this example will focus on a simple extension to the internal shader system, by adding a 2D position offset to all shapes that are rendered that have corresponding user data.
For this example create an AS3 SWF, which has a movie clip with instance name “m”, and the following AS3 code:
import scaleform.gfx.*; DisplayObjectEx.setRendererString(m, "Abc"); DisplayObjectEx.setRendererFloat(m, 17.1717);
Using the new shader scripting system in 4.1, we can easily add a new shader feature. In Src/Render/ShaderData.xml, add the highlighted line:
<ShaderFeature id="Position"> <ShaderFeatureFlavor id="Position2d" hide="true"/> <ShaderFeatureFlavor id="Position3d"/> <ShaderFeatureFlavor id="Position2dOffset"/> </ShaderFeature>
This line causes the shader system to use the “Position2dOffset” snippet as a possibility for processing the position data in the vertex shader. Later in the file, add the “Position2dOffset” snippet:
<ShaderSource id="Position2dOffset" pipeline="Vertex"> Position2d( attribute float4 pos: POSITION, varying float4 vpos : POSITION, uniform float4 mvp[2], uniform float poffset) { vpos = float4(0,0,0,1); vpos.x = dot(pos, mvp[0]) + poffset; vpos.y = dot(pos, mvp[1]) + poffset; } </ShaderSource>
This snippet is identical to the “Position2d” snippet, except that it adds the uniform value ‘poffset’ to the post-transformed x and y of the vertex.
Now, in D3D9_Shader.cpp, add the following block at the top of ShaderInterface::SetStaticShader:
// See if we have user data on the stack. if (pHal->UserDataStack.GetSize() > 0 ) { const UserDataState::Data* data = pHal->UserDataStack.Back(); if (data->Flags & (UserDataState::Data::Data_Float|UserDataState::Data::Data_String) && data->RendererString.CompareNoCase("abc") == 0) { // Modify the shader we use, if we find the matching user data we were expecting. shader = (VertexShaderDesc::ShaderType)((int)shader + ShaderDesc::ST_base_Position2dOffset); } } // existing implementation, uses the updated 'shader' parameter -> VertexShaderDesc::ShaderIndex vsIndex = VertexShaderDesc::GetShaderIndex(shader, pHal->SManager.ShaderModel); FragShaderDesc::ShaderIndex psIndex = FragShaderDesc::GetShaderIndex(shader, pHal->SManager.ShaderModel);
This block checks to see whether the HAL’s UserDataStack has the data we are trying to intercept at the top (has a string which is “abc”, and has a float value as well). If it matches, then we modify the incoming shader types to add in the Position2dOffset path. Note that these symbols will not be available until ShaderData.xml has been run through its custom build step, which generates a new D3D9_ShaderDescs.h.
In addition to altering the shader definition used, we will also need to update the ‘poffset’ uniform before actually rendering. This can be accomplished by inserting the following block at the top of ShaderInterface::Finish:
// See if we have user data on the stack. if (pHal->UserDataStack.GetSize() > 0 ) { const UserDataState::Data* data = pHal->UserDataStack.Back(); if (data->Flags & (UserDataState::Data::Data_Float|UserDataState::Data::Data_String) && data->RendererString.CompareNoCase("abc") == 0) { // Add the poffset uniform into the uniform data. for ( unsigned m = 0; m < Alg::Max<unsigned>(1, meshCount); ++m) { float poffset = data->RendererFloat / 256.0f; SetUniform(CurShaders, Uniform::SU_poffset, &poffset, 1, 0, m); } } }
Note that this example sets the uniform once per ‘meshCount’. meshCount will be greater than one for batched or instanced draws (which the shader modifications made above will also support). Running the modified version of the HAL, the movie clip ‘m’ should now be moved slightly up and to the right.
The HALs can also be modified such that rendering is performed completely outside of the Scaleform shader system. The exact method for performing these modifications is not covered here, and can vary from case to case depending on the desired results.
When performing rendering externally, it can be desirable to disable batched and instanced mesh generation. This is because shaders authored externally from Scaleform do not generally support batching and/or instancing in the same way as the shaders internal to Scaleform. To disable batched drawing, simply use the AS2 “disableBatching” extension member, or the AS3 “disableBatching” extension function. Refer to the AS2 Extensions Reference and AS3 Extensions Reference for exact syntax.