Path: blob/master/drivers/gles3/storage/material_storage.h
21680 views
/**************************************************************************/1/* material_storage.h */2/**************************************************************************/3/* This file is part of: */4/* GODOT ENGINE */5/* https://godotengine.org */6/**************************************************************************/7/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */8/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */9/* */10/* Permission is hereby granted, free of charge, to any person obtaining */11/* a copy of this software and associated documentation files (the */12/* "Software"), to deal in the Software without restriction, including */13/* without limitation the rights to use, copy, modify, merge, publish, */14/* distribute, sublicense, and/or sell copies of the Software, and to */15/* permit persons to whom the Software is furnished to do so, subject to */16/* the following conditions: */17/* */18/* The above copyright notice and this permission notice shall be */19/* included in all copies or substantial portions of the Software. */20/* */21/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */22/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */23/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */24/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */25/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */26/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */27/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */28/**************************************************************************/2930#pragma once3132#ifdef GLES3_ENABLED3334#include "core/templates/rid_owner.h"35#include "core/templates/self_list.h"36#include "servers/rendering/shader_compiler.h"37#include "servers/rendering/shader_language.h"38#include "servers/rendering/storage/material_storage.h"39#include "servers/rendering/storage/utilities.h"4041#include "drivers/gles3/shaders/canvas.glsl.gen.h"42#include "drivers/gles3/shaders/particles.glsl.gen.h"43#include "drivers/gles3/shaders/scene.glsl.gen.h"44#include "drivers/gles3/shaders/sky.glsl.gen.h"45#include "drivers/gles3/shaders/tex_blit.glsl.gen.h"4647namespace GLES3 {4849/* Shader Structs */5051struct ShaderData {52String path;53HashMap<StringName, ShaderLanguage::ShaderNode::Uniform> uniforms;54HashMap<StringName, HashMap<int, RID>> default_texture_params;5556virtual void set_path_hint(const String &p_hint);57virtual void set_default_texture_parameter(const StringName &p_name, RID p_texture, int p_index);58virtual Variant get_default_parameter(const StringName &p_parameter) const;59virtual void get_shader_uniform_list(List<PropertyInfo> *p_param_list) const;60virtual void get_instance_param_list(List<RendererMaterialStorage::InstanceShaderParam> *p_param_list) const;61virtual bool is_parameter_texture(const StringName &p_param) const;6263virtual void set_code(const String &p_Code) = 0;64virtual bool is_animated() const = 0;65virtual bool casts_shadows() const = 0;66virtual RS::ShaderNativeSourceCode get_native_source_code() const { return RS::ShaderNativeSourceCode(); }6768virtual ~ShaderData() {}69};7071typedef ShaderData *(*ShaderDataRequestFunction)();7273struct Material;7475struct Shader {76ShaderData *data = nullptr;77String code;78String path_hint;79RS::ShaderMode mode;80HashMap<StringName, HashMap<int, RID>> default_texture_parameter;81HashSet<Material *> owners;82};8384/* Material structs */8586struct MaterialData {87void update_uniform_buffer(const HashMap<StringName, ShaderLanguage::ShaderNode::Uniform> &p_uniforms, const uint32_t *p_uniform_offsets, const HashMap<StringName, Variant> &p_parameters, uint8_t *p_buffer, uint32_t p_buffer_size);88void update_textures(const HashMap<StringName, Variant> &p_parameters, const HashMap<StringName, HashMap<int, RID>> &p_default_textures, const Vector<ShaderCompiler::GeneratedCode::Texture> &p_texture_uniforms, RID *p_textures, bool p_use_linear_color);8990virtual void set_render_priority(int p_priority) = 0;91virtual void set_next_pass(RID p_pass) = 0;92virtual void update_parameters(const HashMap<StringName, Variant> &p_parameters, bool p_uniform_dirty, bool p_textures_dirty) = 0;93virtual void bind_uniforms() = 0;94virtual ~MaterialData();9596// Used internally by all Materials97void update_parameters_internal(const HashMap<StringName, Variant> &p_parameters, bool p_uniform_dirty, bool p_textures_dirty, const HashMap<StringName, ShaderLanguage::ShaderNode::Uniform> &p_uniforms, const uint32_t *p_uniform_offsets, const Vector<ShaderCompiler::GeneratedCode::Texture> &p_texture_uniforms, const HashMap<StringName, HashMap<int, RID>> &p_default_texture_params, uint32_t p_ubo_size, bool p_is_3d_shader_type);9899protected:100Vector<uint8_t> ubo_data;101GLuint uniform_buffer = GLuint(0);102Vector<RID> texture_cache;103104private:105friend class MaterialStorage;106RID self;107List<RID>::Element *global_buffer_E = nullptr;108List<RID>::Element *global_texture_E = nullptr;109uint64_t global_textures_pass = 0;110HashMap<StringName, uint64_t> used_global_textures;111};112113typedef MaterialData *(*MaterialDataRequestFunction)(ShaderData *);114115struct Material {116RID self;117MaterialData *data = nullptr;118Shader *shader = nullptr;119//shortcut to shader data and type120RS::ShaderMode shader_mode = RS::SHADER_MAX;121uint32_t shader_id = 0;122bool uniform_dirty = false;123bool texture_dirty = false;124HashMap<StringName, Variant> params;125int32_t priority = 0;126RID next_pass;127SelfList<Material> update_element;128129Dependency dependency;130131Material() :132update_element(this) {}133};134135/* CanvasItem Materials */136137struct CanvasShaderData : public ShaderData {138enum BlendMode { // Used internally.139BLEND_MODE_MIX,140BLEND_MODE_ADD,141BLEND_MODE_SUB,142BLEND_MODE_MUL,143BLEND_MODE_PMALPHA,144BLEND_MODE_DISABLED,145BLEND_MODE_LCD,146};147148// All these members are (re)initialized in `set_code`.149// Make sure to add the init to `set_code` whenever adding new members.150151bool valid;152RID version;153154Vector<ShaderCompiler::GeneratedCode::Texture> texture_uniforms;155156Vector<uint32_t> ubo_offsets;157uint32_t ubo_size;158159String code;160161BlendMode blend_mode;162163bool uses_screen_texture;164bool uses_screen_texture_mipmaps;165bool uses_sdf;166bool uses_time;167bool uses_custom0;168bool uses_custom1;169170uint64_t vertex_input_mask;171172virtual void set_code(const String &p_Code);173virtual bool is_animated() const;174virtual bool casts_shadows() const;175virtual RS::ShaderNativeSourceCode get_native_source_code() const;176177CanvasShaderData();178virtual ~CanvasShaderData();179};180181ShaderData *_create_canvas_shader_func();182183struct CanvasMaterialData : public MaterialData {184CanvasShaderData *shader_data = nullptr;185186virtual void set_render_priority(int p_priority) {}187virtual void set_next_pass(RID p_pass) {}188virtual void update_parameters(const HashMap<StringName, Variant> &p_parameters, bool p_uniform_dirty, bool p_textures_dirty);189virtual void bind_uniforms();190virtual ~CanvasMaterialData();191};192193MaterialData *_create_canvas_material_func(ShaderData *p_shader);194195/* Sky Materials */196197struct SkyShaderData : public ShaderData {198// All these members are (re)initialized in `set_code`.199// Make sure to add the init to `set_code` whenever adding new members.200201bool valid;202RID version;203204Vector<ShaderCompiler::GeneratedCode::Texture> texture_uniforms;205206Vector<uint32_t> ubo_offsets;207uint32_t ubo_size;208209String code;210211bool uses_time;212bool uses_position;213bool uses_half_res;214bool uses_quarter_res;215bool uses_light;216217virtual void set_code(const String &p_Code);218virtual bool is_animated() const;219virtual bool casts_shadows() const;220virtual RS::ShaderNativeSourceCode get_native_source_code() const;221SkyShaderData();222virtual ~SkyShaderData();223};224225ShaderData *_create_sky_shader_func();226227struct SkyMaterialData : public MaterialData {228SkyShaderData *shader_data = nullptr;229bool uniform_set_updated = false;230231virtual void set_render_priority(int p_priority) {}232virtual void set_next_pass(RID p_pass) {}233virtual void update_parameters(const HashMap<StringName, Variant> &p_parameters, bool p_uniform_dirty, bool p_textures_dirty);234virtual void bind_uniforms();235virtual ~SkyMaterialData();236};237238MaterialData *_create_sky_material_func(ShaderData *p_shader);239240/* Scene Materials */241242struct SceneShaderData : public ShaderData {243enum BlendMode { // Used internally.244BLEND_MODE_MIX,245BLEND_MODE_ADD,246BLEND_MODE_SUB,247BLEND_MODE_MUL,248BLEND_MODE_PREMULT_ALPHA,249BLEND_MODE_ALPHA_TO_COVERAGE250};251252enum DepthDraw {253DEPTH_DRAW_DISABLED,254DEPTH_DRAW_OPAQUE,255DEPTH_DRAW_ALWAYS256};257258enum DepthTest {259DEPTH_TEST_DISABLED,260DEPTH_TEST_ENABLED,261DEPTH_TEST_ENABLED_INVERTED,262};263264enum StencilCompare {265STENCIL_COMPARE_LESS,266STENCIL_COMPARE_EQUAL,267STENCIL_COMPARE_LESS_OR_EQUAL,268STENCIL_COMPARE_GREATER,269STENCIL_COMPARE_NOT_EQUAL,270STENCIL_COMPARE_GREATER_OR_EQUAL,271STENCIL_COMPARE_ALWAYS,272STENCIL_COMPARE_MAX // not an actual operator, just the amount of operators273};274275enum StencilFlags {276STENCIL_FLAG_READ = 1,277STENCIL_FLAG_WRITE = 2,278STENCIL_FLAG_WRITE_DEPTH_FAIL = 4,279};280281enum AlphaAntiAliasing {282ALPHA_ANTIALIASING_OFF,283ALPHA_ANTIALIASING_ALPHA_TO_COVERAGE,284ALPHA_ANTIALIASING_ALPHA_TO_COVERAGE_AND_TO_ONE285};286287// All these members are (re)initialized in `set_code`.288// Make sure to add the init to `set_code` whenever adding new members.289290bool valid;291RID version;292293Vector<ShaderCompiler::GeneratedCode::Texture> texture_uniforms;294295Vector<uint32_t> ubo_offsets;296uint32_t ubo_size;297298String code;299300BlendMode blend_mode;301AlphaAntiAliasing alpha_antialiasing_mode;302DepthDraw depth_draw;303DepthTest depth_test;304RS::CullMode cull_mode;305306StencilCompare stencil_compare;307uint32_t stencil_flags;308int32_t stencil_reference;309bool stencil_enabled;310311bool uses_point_size;312bool uses_alpha;313bool uses_alpha_clip;314bool uses_blend_alpha;315bool uses_depth_prepass_alpha;316bool uses_discard;317bool uses_roughness;318bool uses_normal;319bool uses_particle_trails;320bool wireframe;321322bool unshaded;323bool uses_vertex;324bool uses_position;325bool uses_sss;326bool uses_transmittance;327bool uses_screen_texture;328bool uses_screen_texture_mipmaps;329bool uses_depth_texture;330bool uses_normal_texture;331bool uses_bent_normal_texture;332bool uses_time;333bool uses_vertex_time;334bool uses_fragment_time;335bool writes_modelview_or_projection;336bool uses_world_coordinates;337bool uses_tangent;338bool uses_color;339bool uses_uv;340bool uses_uv2;341bool uses_custom0;342bool uses_custom1;343bool uses_custom2;344bool uses_custom3;345bool uses_bones;346bool uses_weights;347348uint64_t vertex_input_mask;349350virtual void set_code(const String &p_Code);351virtual bool is_animated() const;352virtual bool casts_shadows() const;353virtual RS::ShaderNativeSourceCode get_native_source_code() const;354355SceneShaderData();356virtual ~SceneShaderData();357};358359ShaderData *_create_scene_shader_func();360361struct SceneMaterialData : public MaterialData {362SceneShaderData *shader_data = nullptr;363uint64_t last_pass = 0;364uint32_t index = 0;365RID next_pass;366uint8_t priority = 0;367virtual void set_render_priority(int p_priority);368virtual void set_next_pass(RID p_pass);369virtual void update_parameters(const HashMap<StringName, Variant> &p_parameters, bool p_uniform_dirty, bool p_textures_dirty);370virtual void bind_uniforms();371virtual ~SceneMaterialData();372};373374MaterialData *_create_scene_material_func(ShaderData *p_shader);375376/* Particle Shader */377378enum {379PARTICLES_MAX_USERDATAS = 6380};381382struct ParticlesShaderData : public ShaderData {383// All these members are (re)initialized in `set_code`.384// Make sure to add the init to `set_code` whenever adding new members.385386bool valid;387RID version;388389Vector<ShaderCompiler::GeneratedCode::Texture> texture_uniforms;390391Vector<uint32_t> ubo_offsets;392uint32_t ubo_size;393394String code;395396bool uses_collision;397bool uses_time;398399bool userdatas_used[PARTICLES_MAX_USERDATAS] = {};400uint32_t userdata_count;401402virtual void set_code(const String &p_Code);403virtual bool is_animated() const;404virtual bool casts_shadows() const;405virtual RS::ShaderNativeSourceCode get_native_source_code() const;406407ParticlesShaderData() {}408virtual ~ParticlesShaderData();409};410411ShaderData *_create_particles_shader_func();412413struct ParticleProcessMaterialData : public MaterialData {414ParticlesShaderData *shader_data = nullptr;415RID uniform_set;416417virtual void set_render_priority(int p_priority) {}418virtual void set_next_pass(RID p_pass) {}419virtual void update_parameters(const HashMap<StringName, Variant> &p_parameters, bool p_uniform_dirty, bool p_textures_dirty);420virtual void bind_uniforms();421virtual ~ParticleProcessMaterialData();422};423424MaterialData *_create_particles_material_func(ShaderData *p_shader);425426/* Texture Blit Shader */427428struct TexBlitShaderData : public ShaderData {429enum BlendMode { // Used internally.430BLEND_MODE_MIX,431BLEND_MODE_ADD,432BLEND_MODE_SUB,433BLEND_MODE_MUL,434BLEND_MODE_DISABLED,435};436437bool valid;438RID version;439440Vector<ShaderCompiler::GeneratedCode::Texture> texture_uniforms;441442Vector<uint32_t> ubo_offsets;443uint32_t ubo_size;444445String code;446447BlendMode blend_mode;448449virtual void set_code(const String &p_code);450virtual bool is_animated() const;451virtual bool casts_shadows() const;452virtual RS::ShaderNativeSourceCode get_native_source_code() const;453454TexBlitShaderData();455virtual ~TexBlitShaderData();456};457458ShaderData *_create_tex_blit_shader_func();459460struct TexBlitMaterialData : public MaterialData {461TexBlitShaderData *shader_data = nullptr;462463virtual void set_render_priority(int p_priority) {}464virtual void set_next_pass(RID p_pass) {}465virtual void update_parameters(const HashMap<StringName, Variant> &p_parameters, bool p_uniform_dirty, bool p_textures_dirty);466virtual void bind_uniforms();467virtual ~TexBlitMaterialData();468};469470MaterialData *_create_tex_blit_material_func(ShaderData *p_shader);471472/* Global shader uniform structs */473struct GlobalShaderUniforms {474enum {475BUFFER_DIRTY_REGION_SIZE = 1024476};477struct Variable {478HashSet<RID> texture_materials; // materials using this479480RS::GlobalShaderParameterType type;481Variant value;482Variant override;483int32_t buffer_index; //for vectors484int32_t buffer_elements; //for vectors485};486487HashMap<StringName, Variable> variables;488489struct Value {490float x;491float y;492float z;493float w;494};495496struct ValueInt {497int32_t x;498int32_t y;499int32_t z;500int32_t w;501};502503struct ValueUInt {504uint32_t x;505uint32_t y;506uint32_t z;507uint32_t w;508};509510struct ValueUsage {511uint32_t elements = 0;512};513514List<RID> materials_using_buffer;515List<RID> materials_using_texture;516517GLuint buffer = GLuint(0);518Value *buffer_values = nullptr;519ValueUsage *buffer_usage = nullptr;520bool *buffer_dirty_regions = nullptr;521uint32_t buffer_dirty_region_count = 0;522523uint32_t buffer_size;524525bool must_update_texture_materials = false;526bool must_update_buffer_materials = false;527528HashMap<RID, int32_t> instance_buffer_pos;529};530531class MaterialStorage : public RendererMaterialStorage {532private:533friend struct MaterialData;534static MaterialStorage *singleton;535536/* GLOBAL SHADER UNIFORM API */537538GlobalShaderUniforms global_shader_uniforms;539540int32_t _global_shader_uniform_allocate(uint32_t p_elements);541void _global_shader_uniform_store_in_buffer(int32_t p_index, RS::GlobalShaderParameterType p_type, const Variant &p_value);542void _global_shader_uniform_mark_buffer_dirty(int32_t p_index, int32_t p_elements);543544/* SHADER API */545546ShaderDataRequestFunction shader_data_request_func[RS::SHADER_MAX];547mutable RID_Owner<Shader, true> shader_owner;548549/* MATERIAL API */550MaterialDataRequestFunction material_data_request_func[RS::SHADER_MAX];551mutable RID_Owner<Material, true> material_owner;552553SelfList<Material>::List material_update_list;554HashSet<RID> dummy_embedded_set;555556public:557static MaterialStorage *get_singleton();558559MaterialStorage();560virtual ~MaterialStorage();561562static _FORCE_INLINE_ void store_transform(const Transform3D &p_mtx, float *p_array) {563p_array[0] = p_mtx.basis.rows[0][0];564p_array[1] = p_mtx.basis.rows[1][0];565p_array[2] = p_mtx.basis.rows[2][0];566p_array[3] = 0;567p_array[4] = p_mtx.basis.rows[0][1];568p_array[5] = p_mtx.basis.rows[1][1];569p_array[6] = p_mtx.basis.rows[2][1];570p_array[7] = 0;571p_array[8] = p_mtx.basis.rows[0][2];572p_array[9] = p_mtx.basis.rows[1][2];573p_array[10] = p_mtx.basis.rows[2][2];574p_array[11] = 0;575p_array[12] = p_mtx.origin.x;576p_array[13] = p_mtx.origin.y;577p_array[14] = p_mtx.origin.z;578p_array[15] = 1;579}580581static _FORCE_INLINE_ void store_transform_3x3(const Basis &p_mtx, float *p_array) {582p_array[0] = p_mtx.rows[0][0];583p_array[1] = p_mtx.rows[1][0];584p_array[2] = p_mtx.rows[2][0];585p_array[3] = 0;586p_array[4] = p_mtx.rows[0][1];587p_array[5] = p_mtx.rows[1][1];588p_array[6] = p_mtx.rows[2][1];589p_array[7] = 0;590p_array[8] = p_mtx.rows[0][2];591p_array[9] = p_mtx.rows[1][2];592p_array[10] = p_mtx.rows[2][2];593p_array[11] = 0;594}595596static _FORCE_INLINE_ void store_camera(const Projection &p_mtx, float *p_array) {597for (int i = 0; i < 4; i++) {598for (int j = 0; j < 4; j++) {599p_array[i * 4 + j] = p_mtx.columns[i][j];600}601}602}603604struct Shaders {605CanvasShaderGLES3 canvas_shader;606SkyShaderGLES3 sky_shader;607SceneShaderGLES3 scene_shader;608ParticlesShaderGLES3 particles_process_shader;609TexBlitShaderGLES3 tex_blit_shader;610611ShaderCompiler compiler_canvas;612ShaderCompiler compiler_scene;613ShaderCompiler compiler_particles;614ShaderCompiler compiler_sky;615ShaderCompiler compiler_tex_blit;616} shaders;617618/* GLOBAL SHADER UNIFORM API */619620void _update_global_shader_uniforms();621622virtual void global_shader_parameter_add(const StringName &p_name, RS::GlobalShaderParameterType p_type, const Variant &p_value) override;623virtual void global_shader_parameter_remove(const StringName &p_name) override;624virtual Vector<StringName> global_shader_parameter_get_list() const override;625626virtual void global_shader_parameter_set(const StringName &p_name, const Variant &p_value) override;627virtual void global_shader_parameter_set_override(const StringName &p_name, const Variant &p_value) override;628virtual Variant global_shader_parameter_get(const StringName &p_name) const override;629virtual RS::GlobalShaderParameterType global_shader_parameter_get_type(const StringName &p_name) const override;630RS::GlobalShaderParameterType global_shader_parameter_get_type_internal(const StringName &p_name) const;631632virtual void global_shader_parameters_load_settings(bool p_load_textures = true) override;633virtual void global_shader_parameters_clear() override;634635virtual int32_t global_shader_parameters_instance_allocate(RID p_instance) override;636virtual void global_shader_parameters_instance_free(RID p_instance) override;637virtual void global_shader_parameters_instance_update(RID p_instance, int p_index, const Variant &p_value, int p_flags_count = 0) override;638639GLuint global_shader_parameters_get_uniform_buffer() const;640641/* SHADER API */642643Shader *get_shader(RID p_rid) { return shader_owner.get_or_null(p_rid); }644bool owns_shader(RID p_rid) { return shader_owner.owns(p_rid); }645646void _shader_make_dirty(Shader *p_shader);647648virtual RID shader_allocate() override;649virtual void shader_initialize(RID p_rid, bool p_embedded = true) override;650virtual void shader_free(RID p_rid) override;651652virtual void shader_set_code(RID p_shader, const String &p_code) override;653virtual void shader_set_path_hint(RID p_shader, const String &p_path) override;654virtual String shader_get_code(RID p_shader) const override;655virtual void get_shader_parameter_list(RID p_shader, List<PropertyInfo> *p_param_list) const override;656657virtual void shader_set_default_texture_parameter(RID p_shader, const StringName &p_name, RID p_texture, int p_index) override;658virtual RID shader_get_default_texture_parameter(RID p_shader, const StringName &p_name, int p_index) const override;659virtual Variant shader_get_parameter_default(RID p_shader, const StringName &p_name) const override;660661virtual RS::ShaderNativeSourceCode shader_get_native_source_code(RID p_shader) const override;662virtual void shader_embedded_set_lock() override {}663virtual const HashSet<RID> &shader_embedded_set_get() const override { return dummy_embedded_set; }664virtual void shader_embedded_set_unlock() override {}665666/* MATERIAL API */667668Material *get_material(RID p_rid) { return material_owner.get_or_null(p_rid); }669bool owns_material(RID p_rid) { return material_owner.owns(p_rid); }670671void _material_queue_update(Material *material, bool p_uniform, bool p_texture);672void _update_queued_materials();673674virtual RID material_allocate() override;675virtual void material_initialize(RID p_rid) override;676virtual void material_free(RID p_rid) override;677678virtual void material_set_shader(RID p_material, RID p_shader) override;679680virtual void material_set_param(RID p_material, const StringName &p_param, const Variant &p_value) override;681virtual Variant material_get_param(RID p_material, const StringName &p_param) const override;682683virtual void material_set_next_pass(RID p_material, RID p_next_material) override;684virtual void material_set_render_priority(RID p_material, int priority) override;685686virtual bool material_is_animated(RID p_material) override;687virtual bool material_casts_shadows(RID p_material) override;688virtual RS::CullMode material_get_cull_mode(RID p_material) const override;689690virtual void material_get_instance_shader_parameters(RID p_material, List<InstanceShaderParam> *r_parameters) override;691692virtual void material_update_dependency(RID p_material, DependencyTracker *p_instance) override;693694_FORCE_INLINE_ uint32_t material_get_shader_id(RID p_material) {695Material *material = material_owner.get_or_null(p_material);696return material->shader_id;697}698699_FORCE_INLINE_ MaterialData *material_get_data(RID p_material, RS::ShaderMode p_shader_mode) {700Material *material = material_owner.get_or_null(p_material);701if (!material || material->shader_mode != p_shader_mode) {702return nullptr;703} else {704return material->data;705}706}707};708709} // namespace GLES3710711#endif // GLES3_ENABLED712713714