Retrieving a shader from a LayerShaderLayer
-
I've been puzzling over this all morning. I'm writing a shader and if this is in a Layer shader and I want to get a pointer to it (my shader, that is). This is the code I have:
BaseShader* MyPlugin::WalkLayers(LayerShader* ls, const BaseDocument *doc) const { BaseShader* ret = nullptr; LayerShaderLayer* lslayer = nullptr; GeData gd; lslayer = ls->GetFirstLayer(); while(lslayer != nullptr) { if (lslayer->GetType() == LayerType::TypeShader) { if(lslayer->GetParameter(LAYER_S_PARAM_SHADER_LINK, gd) == true) { const BaseList2D* blist = gd.GetLink(doc); if (blist != nullptr) { ret = (BaseShader*)blist; if (ret->GetType() == ID_MYSHADER) break; } else ApplicationOutput("gd.GetLink() returns a null pointer."); } ret = nullptr; lslayer = lslayer->GetNext(); } } return ret; }
The problem is that the call to GeData::GetLink() always returns a null pointer. I've checked the layer name and it returns the name of my shader, but I just can't get the link to work. I must be missing something obvious, but what?
Many thanks,
Steve -
Hello @spedler,
Thank you for reaching out to us. Your code seems to be correct. I personally would not use all that nesting and would be more defensive about the type of the
GeData
but this is all semantics, your code should work.This topic reminds me of cannot-copy-baselink-from-one-parameter-to-another-in-shaderdata which might be related. I did then some internal testing but did not really get anywhere because that scenario was specifically difficult to debug with opaque V-Ray binaries in my stack traces on one side and super low level
AliasTrans/AliasGoal
code when a document is being deserialized on the other side (the underpinning of aBaseLink
). But what I took from that then is that we touchedBaseLink
with 2024 and that I had 'the hunch' that this introduced a regression without me being able to put the finger on it.- When I run the code from traversing-a-layer-shader-with-python on a document with one standard material with a layer shader in its color channel with another layer shader nested in this layer shader, everything works fine for me, I find all the shaders. This is of course Python, so things are not 100% transferable, but this then made me look at Python's
C4DAtom.GetParameter
code to see how our Python handles the case that theGeData
is of typeDA_ALIASLINK
. - The interesting thing is that the Python code does not use
GeData::GetLink
which is a sort of high level convenance function, but does what is shown below. I.e., it manually gets theBaseLink
and then gets the link from it. It seems a bit unlikely that this makes a difference, as that is more or less what::GetLink
does. According to git blame, this code is 12 years old, so we did not change it recently in the wake of theBaseLink
changes.
// This is not the exact code to be found there, I modernized the casting and renamed variables. You should also avoid // C-style casting as it is a common culprit for severe bugs, see: http://tiny.cc/n74gzz case DA_ALIASLINK: { const BaseLink* const link = geData.GetBaseLink(); if (!link) return nullptr; BaseList2D* const node = static_cast<BaseList2D*>(link->GetLinkAtom(doc)); if (!node) return nullptr; // ... }
So, in sum, I would try to do what Python does, as this seems to work. But this likely won't be the solution. If not, I would have to ask you to create a bug report as described here, i.e., provide reproduction steps, more than a code snippet, and ideally a scene file.
And since we are at hunches, I have the hunch that there is a problem with how alias goals, i.e., links, are resolved in scenes that are not the active scene. If you have the ability to quickly check that, this could bring us closer to an answer too.
Cheers,
Ferdinand - When I run the code from traversing-a-layer-shader-with-python on a document with one standard material with a layer shader in its color channel with another layer shader nested in this layer shader, everything works fine for me, I find all the shaders. This is of course Python, so things are not 100% transferable, but this then made me look at Python's
-
Hi @ferdinand,
Many thanks for your very helpful comments and code. I've investigated this a bit further and I may have found where the problem lies. This is the slightly modified code snippet:
while(lslayer != nullptr) { if (lslayer->GetType() == LayerType::TypeShader) { if(lslayer->GetParameter(LAYER_S_PARAM_SHADER_LINK, gd) == true) { if(gd.GetType() == DA_ALIASLINK) { const BaseLink* link = gd.GetBaseLink(); if (link != nullptr)
The main difference is that I've added a check for the GeData type. This test always fails: that is, the GeData type returned is not DA_ALIASLINK but a value of 14, which appears to be DA_MISSINGPLUG - a mssing datatype plugin, whatever that means.
Given that, if that check is not carried out and it is assumed that it must be a DA_ALIASLINK (as I did in the previous code) it's not surprising that a nullptr is always returned, in fact it's surprising Cinema doesn't crash . In fact, in the debugger if you manually bypass that GetType() check, Cinema triggers a breakpoint in c4d_gedata.h but it doesn't actually crash.
Unless, again, I've done something wrong this is looking like a bug but it's odd that the Python version works correctly. Should I submit a bug report?
Cheers,
Steve -
Hey Steve,
DA_MISSINGPLUG
means that this parameter has been serialized with a data type plugin which is not present in the executing Cinema 4D instance. Imagine loading a scene which contains aFoo Light
for theFoo Renderer
and you do not have that renderer installed. The light would still load as a ?-object for the end user and you could still read its parameters in the API, as for example its name. When you then would try to read its parameterFOO_LIGHT_FALLOFF
which is of typeFooFalloffDatatype
, Cinema 4D would return you aGeData
of typeDA_MISSINGPLUG
as the data type implementation comes withFoo Renderer
.This should not be raised for
LAYER_S_PARAM_SHADER_LINK
since it is of typeDTYPE_BASELISTLINK
(orDA_ALIASLINK
in the terms ofGeData
), a built-in type. The only thing I could imagine, is that the file you are trying to load holds there a link in your layer shader to a node type realized by a plugin you do not have installed. E.g.: Your layer shader holds aBaseShader
of typeFoo
as its first layer, and you do not have theFoo Shader
plugin installed. I would still consider this a bit odd behaviour to mark such parameter as of typeDA_MISSINGPLUG
and I have personally never run in this behaviour before.Can you show us the scene file?
Cheers,
Ferdinand -
Hi Ferdinand,
There is no scene file worth sending because all I do is add my shader to a layer shader and try to retrieve a pointer to it from the layer shader. So at it's most simple all I would do is create a new standard C4D material (not Redshift or any other renderer) in a blank scene, add a layer shader to the mat's color channel and add my shader as the only layer in that layer shader.
To show what I mean, I've created an absolutely bare-bones shader to reproduce the problem. A zip with source and resource is attached to this post. It should be straightforward to build if anyone wants to do that.
It may be, of course, that what I'm trying to do is not possible. In case you're wondering what exactly I am trying to do, all I want is to discover which channel the instance of the shader is in. This is easy at render time by checking the cd->texflag in the Output function but I couldn't find a way to do it when not rendering. The reason I want to do this is because I want to disable parts of the shader's UI depending on the channel. For example, my shader has a colour gradient which is used in the Color channel but not in the Bump channel. Now, this all works fine if the shader is not in a Layer shader, but if it is I need to traverse the layer shader to find out if my shader is present. But I can't do that because I can't get a pointer to any of the shaders in the layer shader.
(There may well be a better/easier way to do this, in which case I'd really love to know what it is )
Cheers,
Steve
Attached: LayerShaderTest.zip -
Hey Steve,
I understand that it sometimes can seem a bit stupid when we insist on scene files and reproduction steps. But when you once have debugged something with no or only little directions from a user because things "were obvious", just to run one or even multiple days straight against a brick wall trying to reproduce the issue, only to later find out the user "of course" always did the uncommon thing X before doing the "obvious" thing, you start to look differently at such things.
Can you please provide a scene file with which this fails for your plugin/code? I will try to have a look in the next days. For me this all still sounds very much like a bug/issue on our side.
Cheers,
Ferdinand -
Hi Ferdinand,
No worries, attached is a zip with the compiled test plugin and a scene file. Just open the file, switch to the material in the AM and check the console for output. The plugin's source is in the previous file I uploaded.
Cheers,
Steve
Attached: LayerShaderTest_Scene.zip -
Just one small correction - the returned value of 14 when GeData::GetType() is called equates to DA_VOID, not DA_MISSINGPLUG. My mistake, sorry about that. That value makes a bit more sense; still fails though
Steve
-
Hey Steve,
I will have a look, but it will probably only be on Friday or so, as this will likely take me at least two hours to untangle, and I currently do not have the time for that. I will latest answer by the end of next week, but I'll try to make it happen this week.
Cheers,
Ferdinand -
Hi Ferdinand,
No worries, I know you must be very busy. The shader works in itself, it’s just that I’d like to tidy the interface to avoid confusing users. Whenever you have some time to look at it is fine.
Cheers,
Steve -
Hey @spedler,
you did everything correctly, layer shaders are just a bit of an oddball. Find below code which does what you want.
Cheers,
FerdinandResult
Code
INITRENDERRESULT LayerShaderTest::InitRender(BaseShader* shader, const InitRenderStruct& renderStruct) { if (!shader) return INITRENDERRESULT::UNKNOWNERROR; // Get the host of the shader. Although shaders can be owned by shaders, in this example file this shader is for // example owned by a layer shader, the branching relation will always be to a non-shader entity. Shaders are in // a hierarchial relationship to each other. E.g., this shader is a child of the layer shader in the test scene. See // https://developers.maxon.net/docs/cpp/2024_5_0/page_manual_baseshader.html#page_manual_baseshader_access // for more information. BaseList2D* const host = shader->GetMain(); if (!host || host->GetType() != Mmaterial) return INITRENDERRESULT::OK; // Attempt to get a layer shader in the color channel of a material host. GeData linkData; host->GetParameter(ConstDescID(DescLevel(MATERIAL_COLOR_SHADER)), linkData, DESCFLAGS_GET::NONE); if (linkData.GetType() != DA_ALIASLINK) return INITRENDERRESULT::OK; LayerShader* const layerShader = static_cast<LayerShader*>(linkData.GetLink(shader->GetDocument(), Xbase)); if (!layerShader) return INITRENDERRESULT::OK; // Iterate over the layers in the layer shader. LayerShaderLayer* layer = layerShader->GetFirstLayer(); while (layer) { // Youd did more or less everything correctly, layer shaders are just a bit of an oddball and pass on their links // as void pointers; who doesn't like a good void pointer? GeData data; layer->GetParameter(LAYER_S_PARAM_SHADER_LINK, data); if (data.GetType() != DA_VOID) return INITRENDERRESULT::UNKNOWNERROR; const BaseLink* const link = reinterpret_cast<const BaseLink*>(data.GetVoid()); if (!link) return INITRENDERRESULT::UNKNOWNERROR; // And finally, get the shader from the link and print its name. Please do not print in a production context from // this or any shader method (NodeData::Message and ::Init and some other NodeData methods are fine for printing). const BaseShader* const other = static_cast<const BaseShader*>(link->GetLink(shader->GetDocument(), Xbase)); ApplicationOutput("Shader in layer: @", other ? other->GetName() : "None"_s); layer = layer->GetNext(); } return INITRENDERRESULT::OK; }
-
Hi Ferdinand,
That’s great, really very helpful, thank you. I’m glad my code was right in principle even if it didn’t work! I would never have guessed the layer returned a void pointer. That should now work perfectly and do exactly what I need.
Thanks again for taking the time to look into this, very much appreciated.
Cheers,
Steve