Initial refactoring on shader usage and generation

`IShaderSource` was designed with the idea that if you want a shader,
you must want it for a node. So it depends heavily on being given a tile
material and the node drawtype. But this doesn't make sense neither in theory
nor in practice.
This commit takes a small step towards removing the incorrect abstraction.
This commit is contained in:
sfan5 2024-12-13 16:11:21 +01:00
parent eb8beb335e
commit a6293b9861
14 changed files with 81 additions and 53 deletions

@ -1,6 +0,0 @@
varying lowp vec4 varColor;
void main(void)
{
gl_FragData[0] = varColor;
}

@ -1,7 +0,0 @@
varying lowp vec4 varColor;
void main(void)
{
gl_Position = mWorldViewProj * inVertexPosition;
varColor = inVertexColor;
}

@ -1,7 +1,3 @@
#if (MATERIAL_TYPE == TILE_MATERIAL_WAVING_LIQUID_TRANSPARENT || MATERIAL_TYPE == TILE_MATERIAL_WAVING_LIQUID_OPAQUE || MATERIAL_TYPE == TILE_MATERIAL_WAVING_LIQUID_BASIC || MATERIAL_TYPE == TILE_MATERIAL_LIQUID_TRANSPARENT)
#define MATERIAL_WAVING_LIQUID 1
#endif
uniform sampler2D baseTexture; uniform sampler2D baseTexture;
uniform vec3 dayLight; uniform vec3 dayLight;
@ -53,7 +49,7 @@ varying highp vec3 eyeVec;
varying float nightRatio; varying float nightRatio;
#ifdef ENABLE_DYNAMIC_SHADOWS #ifdef ENABLE_DYNAMIC_SHADOWS
#if (defined(MATERIAL_WAVING_LIQUID) && defined(ENABLE_WATER_REFLECTIONS) && ENABLE_WAVING_WATER) #if (defined(ENABLE_WATER_REFLECTIONS) && MATERIAL_WAVING_LIQUID && ENABLE_WAVING_WATER)
vec4 perm(vec4 x) vec4 perm(vec4 x)
{ {
return mod(((x * 34.0) + 1.0) * x, 289.0); return mod(((x * 34.0) + 1.0) * x, 289.0);
@ -504,7 +500,7 @@ void main(void)
vec3 viewVec = normalize(worldPosition + cameraOffset - cameraPosition); vec3 viewVec = normalize(worldPosition + cameraOffset - cameraPosition);
// Water reflections // Water reflections
#if (defined(MATERIAL_WAVING_LIQUID) && defined(ENABLE_WATER_REFLECTIONS) && ENABLE_WAVING_WATER) #if (defined(ENABLE_WATER_REFLECTIONS) && MATERIAL_WAVING_LIQUID && ENABLE_WAVING_WATER)
vec3 wavePos = worldPosition * vec3(2.0, 0.0, 2.0); vec3 wavePos = worldPosition * vec3(2.0, 0.0, 2.0);
float off = animationTimer * WATER_WAVE_SPEED * 10.0; float off = animationTimer * WATER_WAVE_SPEED * 10.0;
wavePos.x /= WATER_WAVE_LENGTH * 3.0; wavePos.x /= WATER_WAVE_LENGTH * 3.0;
@ -532,7 +528,7 @@ void main(void)
col.rgb += water_reflect_color * f_adj_shadow_strength * brightness_factor; col.rgb += water_reflect_color * f_adj_shadow_strength * brightness_factor;
#endif #endif
#if (defined(ENABLE_NODE_SPECULAR) && !defined(MATERIAL_WAVING_LIQUID)) #if (defined(ENABLE_NODE_SPECULAR) && !MATERIAL_WAVING_LIQUID)
// Apply specular to blocks. // Apply specular to blocks.
if (dot(v_LightDirection, vNormal) < 0.0) { if (dot(v_LightDirection, vNormal) < 0.0) {
float intensity = 2.0 * (1.0 - (base.r * varColor.r)); float intensity = 2.0 * (1.0 - (base.r * varColor.r));

@ -108,8 +108,7 @@ float smoothTriangleWave(float x)
return smoothCurve(triangleWave(x)) * 2.0 - 1.0; return smoothCurve(triangleWave(x)) * 2.0 - 1.0;
} }
// OpenGL < 4.3 does not support continued preprocessor lines #if MATERIAL_WAVING_LIQUID && ENABLE_WAVING_WATER
#if (MATERIAL_TYPE == TILE_MATERIAL_WAVING_LIQUID_TRANSPARENT || MATERIAL_TYPE == TILE_MATERIAL_WAVING_LIQUID_OPAQUE || MATERIAL_TYPE == TILE_MATERIAL_WAVING_LIQUID_BASIC) && ENABLE_WAVING_WATER
// //
// Simple, fast noise function. // Simple, fast noise function.
@ -166,8 +165,7 @@ void main(void)
#endif #endif
vec4 pos = inVertexPosition; vec4 pos = inVertexPosition;
// OpenGL < 4.3 does not support continued preprocessor lines #if MATERIAL_WAVING_LIQUID && ENABLE_WAVING_WATER
#if (MATERIAL_TYPE == TILE_MATERIAL_WAVING_LIQUID_TRANSPARENT || MATERIAL_TYPE == TILE_MATERIAL_WAVING_LIQUID_OPAQUE || MATERIAL_TYPE == TILE_MATERIAL_WAVING_LIQUID_BASIC) && ENABLE_WAVING_WATER
// Generate waves with Perlin-type noise. // Generate waves with Perlin-type noise.
// The constants are calibrated such that they roughly // The constants are calibrated such that they roughly
// correspond to the old sine waves. // correspond to the old sine waves.

@ -38,7 +38,7 @@ Clouds::Clouds(scene::ISceneManager* mgr, IShaderSource *ssrc,
m_material.FogEnable = true; m_material.FogEnable = true;
m_material.AntiAliasing = video::EAAM_SIMPLE; m_material.AntiAliasing = video::EAAM_SIMPLE;
{ {
auto sid = ssrc->getShader("cloud_shader", TILE_MATERIAL_ALPHA); auto sid = ssrc->getShaderRaw("cloud_shader", true);
m_material.MaterialType = ssrc->getShaderInfo(sid).material; m_material.MaterialType = ssrc->getShaderInfo(sid).material;
} }

@ -86,10 +86,11 @@ Hud::Hud(Client *client, LocalPlayer *player,
// Initialize m_selection_material // Initialize m_selection_material
IShaderSource *shdrsrc = client->getShaderSource(); IShaderSource *shdrsrc = client->getShaderSource();
{ if (m_mode == HIGHLIGHT_HALO) {
auto shader_id = shdrsrc->getShader( auto shader_id = shdrsrc->getShaderRaw("selection_shader", true);
m_mode == HIGHLIGHT_HALO ? "selection_shader" : "default_shader", TILE_MATERIAL_ALPHA);
m_selection_material.MaterialType = shdrsrc->getShaderInfo(shader_id).material; m_selection_material.MaterialType = shdrsrc->getShaderInfo(shader_id).material;
} else {
m_selection_material.MaterialType = video::EMT_SOLID;
} }
if (m_mode == HIGHLIGHT_BOX) { if (m_mode == HIGHLIGHT_BOX) {
@ -103,10 +104,7 @@ Hud::Hud(Client *client, LocalPlayer *player,
} }
// Initialize m_block_bounds_material // Initialize m_block_bounds_material
{ m_block_bounds_material.MaterialType = video::EMT_SOLID;
auto shader_id = shdrsrc->getShader("default_shader", TILE_MATERIAL_ALPHA);
m_block_bounds_material.MaterialType = shdrsrc->getShaderInfo(shader_id).material;
}
m_block_bounds_material.Thickness = m_block_bounds_material.Thickness =
rangelim(g_settings->getS16("selectionbox_width"), 1, 5); rangelim(g_settings->getS16("selectionbox_width"), 1, 5);

@ -599,7 +599,7 @@ void Minimap::drawMinimap(core::rect<s32> rect)
material.TextureLayers[1].Texture = data->heightmap_texture; material.TextureLayers[1].Texture = data->heightmap_texture;
if (data->mode.type == MINIMAP_TYPE_SURFACE) { if (data->mode.type == MINIMAP_TYPE_SURFACE) {
auto sid = m_shdrsrc->getShader("minimap_shader", TILE_MATERIAL_ALPHA); auto sid = m_shdrsrc->getShaderRaw("minimap_shader", true);
material.MaterialType = m_shdrsrc->getShaderInfo(sid).material; material.MaterialType = m_shdrsrc->getShaderInfo(sid).material;
} else { } else {
material.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL; material.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL;

@ -66,10 +66,12 @@ void populateInterlacedPipeline(RenderPipeline *pipeline, Client *client)
} }
pipeline->addStep<OffsetCameraStep>(0.0f); pipeline->addStep<OffsetCameraStep>(0.0f);
IShaderSource *s = client->getShaderSource(); IShaderSource *s = client->getShaderSource();
u32 shader = s->getShader("3d_interlaced_merge", TILE_MATERIAL_BASIC); auto shader = s->getShaderRaw("3d_interlaced_merge");
video::E_MATERIAL_TYPE material = s->getShaderInfo(shader).material; video::E_MATERIAL_TYPE material = s->getShaderInfo(shader).material;
auto texture_map = { TEXTURE_LEFT, TEXTURE_RIGHT, TEXTURE_MASK }; auto texture_map = { TEXTURE_LEFT, TEXTURE_RIGHT, TEXTURE_MASK };
auto merge = pipeline->addStep<PostProcessingStep>(material, texture_map); auto merge = pipeline->addStep<PostProcessingStep>(material, texture_map);
merge->setRenderSource(buffer); merge->setRenderSource(buffer);
merge->setRenderTarget(pipeline->createOwned<ScreenTarget>()); merge->setRenderTarget(pipeline->createOwned<ScreenTarget>());

@ -188,7 +188,7 @@ RenderStep *addPostProcessing(RenderPipeline *pipeline, RenderStep *previousStep
buffer->setTexture(TEXTURE_BLOOM, scale, "bloom", bloom_format); buffer->setTexture(TEXTURE_BLOOM, scale, "bloom", bloom_format);
// get bright spots // get bright spots
u32 shader_id = client->getShaderSource()->getShader("extract_bloom", TILE_MATERIAL_PLAIN, NDT_MESH); u32 shader_id = client->getShaderSource()->getShaderRaw("extract_bloom");
RenderStep *extract_bloom = pipeline->addStep<PostProcessingStep>(shader_id, std::vector<u8> { source, TEXTURE_EXPOSURE_1 }); RenderStep *extract_bloom = pipeline->addStep<PostProcessingStep>(shader_id, std::vector<u8> { source, TEXTURE_EXPOSURE_1 });
extract_bloom->setRenderSource(buffer); extract_bloom->setRenderSource(buffer);
extract_bloom->setRenderTarget(pipeline->createOwned<TextureBufferOutput>(buffer, TEXTURE_BLOOM)); extract_bloom->setRenderTarget(pipeline->createOwned<TextureBufferOutput>(buffer, TEXTURE_BLOOM));
@ -198,7 +198,7 @@ RenderStep *addPostProcessing(RenderPipeline *pipeline, RenderStep *previousStep
if (enable_volumetric_light) { if (enable_volumetric_light) {
buffer->setTexture(TEXTURE_VOLUME, scale, "volume", color_format); buffer->setTexture(TEXTURE_VOLUME, scale, "volume", color_format);
shader_id = client->getShaderSource()->getShader("volumetric_light", TILE_MATERIAL_PLAIN, NDT_MESH); shader_id = client->getShaderSource()->getShaderRaw("volumetric_light");
auto volume = pipeline->addStep<PostProcessingStep>(shader_id, std::vector<u8> { source, TEXTURE_DEPTH }); auto volume = pipeline->addStep<PostProcessingStep>(shader_id, std::vector<u8> { source, TEXTURE_DEPTH });
volume->setRenderSource(buffer); volume->setRenderSource(buffer);
volume->setRenderTarget(pipeline->createOwned<TextureBufferOutput>(buffer, TEXTURE_VOLUME)); volume->setRenderTarget(pipeline->createOwned<TextureBufferOutput>(buffer, TEXTURE_VOLUME));
@ -206,7 +206,7 @@ RenderStep *addPostProcessing(RenderPipeline *pipeline, RenderStep *previousStep
} }
// downsample // downsample
shader_id = client->getShaderSource()->getShader("bloom_downsample", TILE_MATERIAL_PLAIN, NDT_MESH); shader_id = client->getShaderSource()->getShaderRaw("bloom_downsample");
for (u8 i = 0; i < MIPMAP_LEVELS; i++) { for (u8 i = 0; i < MIPMAP_LEVELS; i++) {
auto step = pipeline->addStep<PostProcessingStep>(shader_id, std::vector<u8> { source }); auto step = pipeline->addStep<PostProcessingStep>(shader_id, std::vector<u8> { source });
step->setRenderSource(buffer); step->setRenderSource(buffer);
@ -219,7 +219,7 @@ RenderStep *addPostProcessing(RenderPipeline *pipeline, RenderStep *previousStep
// Bloom pt 2 // Bloom pt 2
if (enable_bloom) { if (enable_bloom) {
// upsample // upsample
shader_id = client->getShaderSource()->getShader("bloom_upsample", TILE_MATERIAL_PLAIN, NDT_MESH); shader_id = client->getShaderSource()->getShaderRaw("bloom_upsample");
for (u8 i = MIPMAP_LEVELS - 1; i > 0; i--) { for (u8 i = MIPMAP_LEVELS - 1; i > 0; i--) {
auto step = pipeline->addStep<PostProcessingStep>(shader_id, std::vector<u8> { u8(TEXTURE_SCALE_DOWN + i - 1), source }); auto step = pipeline->addStep<PostProcessingStep>(shader_id, std::vector<u8> { u8(TEXTURE_SCALE_DOWN + i - 1), source });
step->setRenderSource(buffer); step->setRenderSource(buffer);
@ -232,7 +232,7 @@ RenderStep *addPostProcessing(RenderPipeline *pipeline, RenderStep *previousStep
// Dynamic Exposure pt2 // Dynamic Exposure pt2
if (enable_auto_exposure) { if (enable_auto_exposure) {
shader_id = client->getShaderSource()->getShader("update_exposure", TILE_MATERIAL_PLAIN, NDT_MESH); shader_id = client->getShaderSource()->getShaderRaw("update_exposure");
auto update_exposure = pipeline->addStep<PostProcessingStep>(shader_id, std::vector<u8> { TEXTURE_EXPOSURE_1, u8(TEXTURE_SCALE_DOWN + MIPMAP_LEVELS - 1) }); auto update_exposure = pipeline->addStep<PostProcessingStep>(shader_id, std::vector<u8> { TEXTURE_EXPOSURE_1, u8(TEXTURE_SCALE_DOWN + MIPMAP_LEVELS - 1) });
update_exposure->setBilinearFilter(1, true); update_exposure->setBilinearFilter(1, true);
update_exposure->setRenderSource(buffer); update_exposure->setRenderSource(buffer);
@ -246,7 +246,7 @@ RenderStep *addPostProcessing(RenderPipeline *pipeline, RenderStep *previousStep
final_stage_source = TEXTURE_FXAA; final_stage_source = TEXTURE_FXAA;
buffer->setTexture(TEXTURE_FXAA, scale, "fxaa", color_format); buffer->setTexture(TEXTURE_FXAA, scale, "fxaa", color_format);
shader_id = client->getShaderSource()->getShader("fxaa", TILE_MATERIAL_PLAIN); shader_id = client->getShaderSource()->getShaderRaw("fxaa");
PostProcessingStep *effect = pipeline->createOwned<PostProcessingStep>(shader_id, std::vector<u8> { TEXTURE_COLOR }); PostProcessingStep *effect = pipeline->createOwned<PostProcessingStep>(shader_id, std::vector<u8> { TEXTURE_COLOR });
pipeline->addStep(effect); pipeline->addStep(effect);
effect->setBilinearFilter(0, true); effect->setBilinearFilter(0, true);
@ -255,7 +255,7 @@ RenderStep *addPostProcessing(RenderPipeline *pipeline, RenderStep *previousStep
} }
// final merge // final merge
shader_id = client->getShaderSource()->getShader("second_stage", TILE_MATERIAL_PLAIN, NDT_MESH); shader_id = client->getShaderSource()->getShaderRaw("second_stage");
PostProcessingStep *effect = pipeline->createOwned<PostProcessingStep>(shader_id, std::vector<u8> { final_stage_source, TEXTURE_SCALE_UP, TEXTURE_EXPOSURE_2 }); PostProcessingStep *effect = pipeline->createOwned<PostProcessingStep>(shader_id, std::vector<u8> { final_stage_source, TEXTURE_SCALE_UP, TEXTURE_EXPOSURE_2 });
pipeline->addStep(effect); pipeline->addStep(effect);
if (enable_ssaa) if (enable_ssaa)

@ -271,7 +271,7 @@ public:
The id 0 points to a null shader. Its material is EMT_SOLID. The id 0 points to a null shader. Its material is EMT_SOLID.
*/ */
u32 getShaderIdDirect(const std::string &name, u32 getShaderIdDirect(const std::string &name,
MaterialType material_type, NodeDrawType drawtype) override; MaterialType material_type, NodeDrawType drawtype);
/* /*
If shader specified by the name pointed by the id doesn't If shader specified by the name pointed by the id doesn't
@ -281,10 +281,18 @@ public:
and not found in cache, the call is queued to the main thread and not found in cache, the call is queued to the main thread
for processing. for processing.
*/ */
u32 getShader(const std::string &name, u32 getShader(const std::string &name,
MaterialType material_type, NodeDrawType drawtype) override; MaterialType material_type, NodeDrawType drawtype) override;
u32 getShaderRaw(const std::string &name, bool blendAlpha) override
{
// TODO: the shader system should be refactored to be much more generic.
// Just let callers pass arbitrary constants, this would also deal with
// runtime changes cleanly.
return getShader(name, blendAlpha ? TILE_MATERIAL_ALPHA : TILE_MATERIAL_BASIC,
NodeDrawType_END);
}
ShaderInfo getShaderInfo(u32 id) override; ShaderInfo getShaderInfo(u32 id) override;
// Processes queued shader requests from other threads. // Processes queued shader requests from other threads.
@ -607,11 +615,17 @@ ShaderInfo ShaderSource::generateShader(const std::string &name,
#define textureFlags texture2 #define textureFlags texture2
)"; )";
/* Define constants for node and object shaders */
const bool node_shader = drawtype != NodeDrawType_END;
if (node_shader) {
bool use_discard = fully_programmable; bool use_discard = fully_programmable;
// For renderers that should use discard instead of GL_ALPHA_TEST if (!use_discard) {
const char *renderer = reinterpret_cast<const char*>(GL.GetString(GL.RENDERER)); // workaround for a certain OpenGL implementation lacking GL_ALPHA_TEST
if (strstr(renderer, "GC7000")) const char *renderer = reinterpret_cast<const char*>(GL.GetString(GL.RENDERER));
use_discard = true; if (strstr(renderer, "GC7000"))
use_discard = true;
}
if (use_discard) { if (use_discard) {
if (shaderinfo.base_material == video::EMT_TRANSPARENT_ALPHA_CHANNEL) if (shaderinfo.base_material == video::EMT_TRANSPARENT_ALPHA_CHANNEL)
shaders_header << "#define USE_DISCARD 1\n"; shaders_header << "#define USE_DISCARD 1\n";
@ -664,9 +678,25 @@ ShaderInfo ShaderSource::generateShader(const std::string &name,
shaders_header << "#define WATER_WAVE_LENGTH " << g_settings->getFloat("water_wave_length") << "\n"; shaders_header << "#define WATER_WAVE_LENGTH " << g_settings->getFloat("water_wave_length") << "\n";
shaders_header << "#define WATER_WAVE_SPEED " << g_settings->getFloat("water_wave_speed") << "\n"; shaders_header << "#define WATER_WAVE_SPEED " << g_settings->getFloat("water_wave_speed") << "\n";
} }
switch (material_type) {
case TILE_MATERIAL_WAVING_LIQUID_TRANSPARENT:
case TILE_MATERIAL_WAVING_LIQUID_OPAQUE:
case TILE_MATERIAL_WAVING_LIQUID_BASIC:
case TILE_MATERIAL_LIQUID_TRANSPARENT:
shaders_header << "#define MATERIAL_WAVING_LIQUID 1\n";
break;
default:
shaders_header << "#define MATERIAL_WAVING_LIQUID 0\n";
break;
}
shaders_header << "#define ENABLE_WAVING_LEAVES " << g_settings->getBool("enable_waving_leaves") << "\n"; shaders_header << "#define ENABLE_WAVING_LEAVES " << g_settings->getBool("enable_waving_leaves") << "\n";
shaders_header << "#define ENABLE_WAVING_PLANTS " << g_settings->getBool("enable_waving_plants") << "\n"; shaders_header << "#define ENABLE_WAVING_PLANTS " << g_settings->getBool("enable_waving_plants") << "\n";
}
/* Other constants */
shaders_header << "#define ENABLE_TONE_MAPPING " << g_settings->getBool("tone_mapping") << "\n"; shaders_header << "#define ENABLE_TONE_MAPPING " << g_settings->getBool("tone_mapping") << "\n";
if (g_settings->getBool("enable_dynamic_shadows")) { if (g_settings->getBool("enable_dynamic_shadows")) {

@ -196,18 +196,33 @@ using CachedStructPixelShaderSetting = CachedStructShaderSetting<T, count, cache
/* /*
ShaderSource creates and caches shaders. ShaderSource creates and caches shaders.
*/
A "shader" could more precisely be called a "shader material" and comprises
a vertex, fragment and optional geometry shader.
*/
class IShaderSource { class IShaderSource {
public: public:
IShaderSource() = default; IShaderSource() = default;
virtual ~IShaderSource() = default; virtual ~IShaderSource() = default;
virtual u32 getShaderIdDirect(const std::string &name, /**
MaterialType material_type, NodeDrawType drawtype = NDT_NORMAL){return 0;} * @brief returns information about an existing shader
virtual ShaderInfo getShaderInfo(u32 id){return ShaderInfo();} *
* Use this to get the material ID to plug into `video::SMaterial`.
*/
virtual ShaderInfo getShaderInfo(u32 id) = 0;
/// @brief Generates or gets a shader suitable for nodes and entities
virtual u32 getShader(const std::string &name, virtual u32 getShader(const std::string &name,
MaterialType material_type, NodeDrawType drawtype = NDT_NORMAL){return 0;} MaterialType material_type, NodeDrawType drawtype = NDT_NORMAL) = 0;
/**
* Generates or gets a shader for general use.
* @param name name of the shader (directory on disk)
* @param blendAlpha enable alpha blending for this material?
* @return shader ID
*/
virtual u32 getShaderRaw(const std::string &name, bool blendAlpha = false) = 0;
}; };
class IWritableShaderSource : public IShaderSource { class IWritableShaderSource : public IShaderSource {

@ -62,7 +62,7 @@ Sky::Sky(s32 id, RenderingEngine *rendering_engine, ITextureSource *tsrc, IShade
m_materials[0] = baseMaterial(); m_materials[0] = baseMaterial();
m_materials[0].MaterialType = m_materials[0].MaterialType =
ssrc->getShaderInfo(ssrc->getShader("stars_shader", TILE_MATERIAL_ALPHA)).material; ssrc->getShaderInfo(ssrc->getShaderRaw("stars_shader", true)).material;
m_materials[1] = baseMaterial(); m_materials[1] = baseMaterial();
m_materials[1].MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL; m_materials[1].MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL;

@ -20,6 +20,7 @@ enum MaterialType{
TILE_MATERIAL_WAVING_LIQUID_BASIC, TILE_MATERIAL_WAVING_LIQUID_BASIC,
TILE_MATERIAL_WAVING_LIQUID_TRANSPARENT, TILE_MATERIAL_WAVING_LIQUID_TRANSPARENT,
TILE_MATERIAL_WAVING_LIQUID_OPAQUE, TILE_MATERIAL_WAVING_LIQUID_OPAQUE,
// Note: PLAIN isn't a material actually used by tiles, rather just entities.
TILE_MATERIAL_PLAIN, TILE_MATERIAL_PLAIN,
TILE_MATERIAL_PLAIN_ALPHA TILE_MATERIAL_PLAIN_ALPHA
}; };

@ -222,6 +222,7 @@ enum NodeDrawType : u8
NDT_MESH, NDT_MESH,
// Combined plantlike-on-solid // Combined plantlike-on-solid
NDT_PLANTLIKE_ROOTED, NDT_PLANTLIKE_ROOTED,
// Dummy for validity check // Dummy for validity check
NodeDrawType_END NodeDrawType_END
}; };