diff options
Diffstat (limited to 'modules')
31 files changed, 646 insertions, 279 deletions
diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp index 617db883f8..e6aeef2fd1 100644 --- a/modules/gdscript/gdscript.cpp +++ b/modules/gdscript/gdscript.cpp @@ -1254,6 +1254,14 @@ GDScript::~GDScript() { memdelete(E.value); } + if (implicit_initializer) { + memdelete(implicit_initializer); + } + + if (implicit_ready) { + memdelete(implicit_ready); + } + if (GDScriptCache::singleton) { // Cache may have been already destroyed at engine shutdown. GDScriptCache::remove_script(get_path()); } @@ -1541,6 +1549,18 @@ bool GDScriptInstance::has_method(const StringName &p_method) const { Variant GDScriptInstance::callp(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) { GDScript *sptr = script.ptr(); + if (unlikely(p_method == SNAME("_ready"))) { + // Call implicit ready first, including for the super classes. + while (sptr) { + if (sptr->implicit_ready) { + sptr->implicit_ready->call(this, nullptr, 0, r_error); + } + sptr = sptr->_base; + } + + // Reset this back for the regular call. + sptr = script.ptr(); + } while (sptr) { HashMap<StringName, GDScriptFunction *>::Iterator E = sptr->member_functions.find(p_method); if (E) { diff --git a/modules/gdscript/gdscript.h b/modules/gdscript/gdscript.h index 0057962d5e..feb0a237df 100644 --- a/modules/gdscript/gdscript.h +++ b/modules/gdscript/gdscript.h @@ -120,6 +120,7 @@ class GDScript : public Script { GDScriptFunction *implicit_initializer = nullptr; GDScriptFunction *initializer = nullptr; //direct pointer to new , faster to locate + GDScriptFunction *implicit_ready = nullptr; int subclass_count = 0; RBSet<Object *> instances; diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp index 25454030b1..16a0b17d61 100644 --- a/modules/gdscript/gdscript_compiler.cpp +++ b/modules/gdscript/gdscript_compiler.cpp @@ -1990,18 +1990,18 @@ GDScriptFunction *GDScriptCompiler::_parse_function(Error &r_error, GDScript *p_ // Parse initializer if applies. bool is_implicit_initializer = !p_for_ready && !p_func && !p_for_lambda; - bool is_initializer = p_func && !p_for_lambda && String(p_func->identifier->name) == GDScriptLanguage::get_singleton()->strings._init; - bool is_for_ready = p_for_ready || (p_func && !p_for_lambda && String(p_func->identifier->name) == "_ready"); + bool is_initializer = p_func && !p_for_lambda && p_func->identifier->name == GDScriptLanguage::get_singleton()->strings._init; + bool is_implicit_ready = !p_func && p_for_ready; - if (!p_for_lambda && (is_implicit_initializer || is_for_ready)) { + if (!p_for_lambda && (is_implicit_initializer || is_implicit_ready)) { // Initialize class fields. for (int i = 0; i < p_class->members.size(); i++) { if (p_class->members[i].type != GDScriptParser::ClassNode::Member::VARIABLE) { continue; } const GDScriptParser::VariableNode *field = p_class->members[i].variable; - if (field->onready != is_for_ready) { - // Only initialize in _ready. + if (field->onready != is_implicit_ready) { + // Only initialize in @implicit_ready. continue; } @@ -2123,6 +2123,8 @@ GDScriptFunction *GDScriptCompiler::_parse_function(Error &r_error, GDScript *p_ p_script->initializer = gd_function; } else if (is_implicit_initializer) { p_script->implicit_initializer = gd_function; + } else if (is_implicit_ready) { + p_script->implicit_ready = gd_function; } if (p_func) { @@ -2140,7 +2142,7 @@ GDScriptFunction *GDScriptCompiler::_parse_function(Error &r_error, GDScript *p_ #endif } - if (!p_for_lambda) { + if (!is_implicit_initializer && !is_implicit_ready && !p_for_lambda) { p_script->member_functions[func_name] = gd_function; } @@ -2208,11 +2210,19 @@ Error GDScriptCompiler::_parse_class_level(GDScript *p_script, const GDScriptPar for (const KeyValue<StringName, GDScriptFunction *> &E : p_script->member_functions) { memdelete(E.value); } + if (p_script->implicit_initializer) { + memdelete(p_script->implicit_initializer); + } + if (p_script->implicit_ready) { + memdelete(p_script->implicit_ready); + } p_script->member_functions.clear(); p_script->member_indices.clear(); p_script->member_info.clear(); p_script->_signals.clear(); p_script->initializer = nullptr; + p_script->implicit_initializer = nullptr; + p_script->implicit_ready = nullptr; p_script->tool = parser->is_tool(); p_script->name = p_class->identifier ? p_class->identifier->name : ""; @@ -2456,15 +2466,10 @@ Error GDScriptCompiler::_parse_class_level(GDScript *p_script, const GDScriptPar Error GDScriptCompiler::_parse_class_blocks(GDScript *p_script, const GDScriptParser::ClassNode *p_class, bool p_keep_state) { //parse methods - bool has_ready = false; - for (int i = 0; i < p_class->members.size(); i++) { const GDScriptParser::ClassNode::Member &member = p_class->members[i]; if (member.type == member.FUNCTION) { const GDScriptParser::FunctionNode *function = member.function; - if (!has_ready && function->identifier->name == "_ready") { - has_ready = true; - } Error err = OK; _parse_function(err, p_script, p_class, function); if (err) { @@ -2498,8 +2503,8 @@ Error GDScriptCompiler::_parse_class_blocks(GDScript *p_script, const GDScriptPa } } - if (!has_ready && p_class->onready_used) { - //create a _ready constructor + if (p_class->onready_used) { + // Create an implicit_ready constructor. Error err = OK; _parse_function(err, p_script, p_class, nullptr, true); if (err) { diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp index 5345143271..474c8094f2 100644 --- a/modules/gdscript/gdscript_editor.cpp +++ b/modules/gdscript/gdscript_editor.cpp @@ -746,7 +746,7 @@ static void _find_annotation_arguments(const GDScriptParser::AnnotationNode *p_a ScriptLanguage::CodeCompletionOption slider2("or_lesser", ScriptLanguage::CODE_COMPLETION_KIND_PLAIN_TEXT); slider2.insert_text = slider2.display.quote(p_quote_style); r_result.insert(slider2.display, slider2); - ScriptLanguage::CodeCompletionOption slider3("noslider", ScriptLanguage::CODE_COMPLETION_KIND_PLAIN_TEXT); + ScriptLanguage::CodeCompletionOption slider3("no_slider", ScriptLanguage::CODE_COMPLETION_KIND_PLAIN_TEXT); slider3.insert_text = slider3.display.quote(p_quote_style); r_result.insert(slider3.display, slider3); } diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp index 5abbf907c7..9e347eed5a 100644 --- a/modules/gdscript/gdscript_parser.cpp +++ b/modules/gdscript/gdscript_parser.cpp @@ -109,31 +109,31 @@ GDScriptParser::GDScriptParser() { // Register valid annotations. // TODO: Should this be static? register_annotation(MethodInfo("@tool"), AnnotationInfo::SCRIPT, &GDScriptParser::tool_annotation); - register_annotation(MethodInfo("@icon", { Variant::STRING, "icon_path" }), AnnotationInfo::SCRIPT, &GDScriptParser::icon_annotation); + register_annotation(MethodInfo("@icon", PropertyInfo(Variant::STRING, "icon_path")), AnnotationInfo::SCRIPT, &GDScriptParser::icon_annotation); register_annotation(MethodInfo("@onready"), AnnotationInfo::VARIABLE, &GDScriptParser::onready_annotation); // Export annotations. register_annotation(MethodInfo("@export"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_NONE, Variant::NIL>); - register_annotation(MethodInfo("@export_enum", { Variant::STRING, "names" }), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_ENUM, Variant::INT>, 0, true); - register_annotation(MethodInfo("@export_file", { Variant::STRING, "filter" }), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_FILE, Variant::STRING>, 1, true); + register_annotation(MethodInfo("@export_enum", PropertyInfo(Variant::STRING, "names")), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_ENUM, Variant::INT>, 0, true); + register_annotation(MethodInfo("@export_file", PropertyInfo(Variant::STRING, "filter")), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_FILE, Variant::STRING>, 1, true); register_annotation(MethodInfo("@export_dir"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_DIR, Variant::STRING>); - register_annotation(MethodInfo("@export_global_file", { Variant::STRING, "filter" }), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_GLOBAL_FILE, Variant::STRING>, 1, true); + register_annotation(MethodInfo("@export_global_file", PropertyInfo(Variant::STRING, "filter")), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_GLOBAL_FILE, Variant::STRING>, 1, true); register_annotation(MethodInfo("@export_global_dir"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_GLOBAL_DIR, Variant::STRING>); register_annotation(MethodInfo("@export_multiline"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_MULTILINE_TEXT, Variant::STRING>); register_annotation(MethodInfo("@export_placeholder"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_PLACEHOLDER_TEXT, Variant::STRING>); - register_annotation(MethodInfo("@export_range", { Variant::FLOAT, "min" }, { Variant::FLOAT, "max" }, { Variant::FLOAT, "step" }, { Variant::STRING, "slider1" }, { Variant::STRING, "slider2" }, { Variant::STRING, "slider3" }), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_RANGE, Variant::FLOAT>, 4); - register_annotation(MethodInfo("@export_exp_easing", { Variant::STRING, "hint1" }, { Variant::STRING, "hint2" }), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_EXP_EASING, Variant::FLOAT>, 2); + register_annotation(MethodInfo("@export_range", PropertyInfo(Variant::FLOAT, "min"), PropertyInfo(Variant::FLOAT, "max"), PropertyInfo(Variant::FLOAT, "step"), PropertyInfo(Variant::STRING, "slider1"), PropertyInfo(Variant::STRING, "slider2"), PropertyInfo(Variant::STRING, "slider3")), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_RANGE, Variant::FLOAT>, 4); + register_annotation(MethodInfo("@export_exp_easing", PropertyInfo(Variant::STRING, "hint1"), PropertyInfo(Variant::STRING, "hint2")), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_EXP_EASING, Variant::FLOAT>, 2); register_annotation(MethodInfo("@export_color_no_alpha"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_COLOR_NO_ALPHA, Variant::COLOR>); - register_annotation(MethodInfo("@export_node_path", { Variant::STRING, "type" }), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_NODE_PATH_VALID_TYPES, Variant::NODE_PATH>, 1, true); - register_annotation(MethodInfo("@export_flags", { Variant::STRING, "names" }), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_FLAGS, Variant::INT>, 0, true); + register_annotation(MethodInfo("@export_node_path", PropertyInfo(Variant::STRING, "type")), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_NODE_PATH_VALID_TYPES, Variant::NODE_PATH>, 1, true); + register_annotation(MethodInfo("@export_flags", PropertyInfo(Variant::STRING, "names")), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_FLAGS, Variant::INT>, 0, true); register_annotation(MethodInfo("@export_flags_2d_render"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_2D_RENDER, Variant::INT>); register_annotation(MethodInfo("@export_flags_2d_physics"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_2D_PHYSICS, Variant::INT>); register_annotation(MethodInfo("@export_flags_2d_navigation"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_2D_NAVIGATION, Variant::INT>); register_annotation(MethodInfo("@export_flags_3d_render"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_3D_RENDER, Variant::INT>); register_annotation(MethodInfo("@export_flags_3d_physics"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_3D_PHYSICS, Variant::INT>); register_annotation(MethodInfo("@export_flags_3d_navigation"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_LAYERS_3D_NAVIGATION, Variant::INT>); - register_annotation(MethodInfo("@warning_ignore", { Variant::STRING, "warning" }), AnnotationInfo::CLASS | AnnotationInfo::VARIABLE | AnnotationInfo::SIGNAL | AnnotationInfo::CONSTANT | AnnotationInfo::FUNCTION | AnnotationInfo::STATEMENT, &GDScriptParser::warning_annotations, 0, true); + register_annotation(MethodInfo("@warning_ignore", PropertyInfo(Variant::STRING, "warning")), AnnotationInfo::CLASS | AnnotationInfo::VARIABLE | AnnotationInfo::SIGNAL | AnnotationInfo::CONSTANT | AnnotationInfo::FUNCTION | AnnotationInfo::STATEMENT, &GDScriptParser::warning_annotations, 0, true); // Networking. - register_annotation(MethodInfo("@rpc", { Variant::STRING, "mode" }, { Variant::STRING, "sync" }, { Variant::STRING, "transfer_mode" }, { Variant::INT, "transfer_channel" }), AnnotationInfo::FUNCTION, &GDScriptParser::network_annotations<Multiplayer::RPC_MODE_AUTHORITY>, 4, true); + register_annotation(MethodInfo("@rpc", PropertyInfo(Variant::STRING, "mode"), PropertyInfo(Variant::STRING, "sync"), PropertyInfo(Variant::STRING, "transfer_mode"), PropertyInfo(Variant::INT, "transfer_channel")), AnnotationInfo::FUNCTION, &GDScriptParser::network_annotations<Multiplayer::RPC_MODE_AUTHORITY>, 4, true); } GDScriptParser::~GDScriptParser() { diff --git a/modules/gdscript/gdscript_vm.cpp b/modules/gdscript/gdscript_vm.cpp index 55f4ebb1c5..20b8d29ec3 100644 --- a/modules/gdscript/gdscript_vm.cpp +++ b/modules/gdscript/gdscript_vm.cpp @@ -3432,9 +3432,9 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a _err_print_error(err_func.utf8().get_data(), err_file.utf8().get_data(), err_line, err_text.utf8().get_data(), false, ERR_HANDLER_SCRIPT); } -#endif // Get a default return type in case of failure retvalue = _get_default_variant_for_data_type(return_type); +#endif OPCODE_OUT; } diff --git a/modules/lightmapper_rd/lightmapper_rd.cpp b/modules/lightmapper_rd/lightmapper_rd.cpp index 214c60091c..2ce3715bea 100644 --- a/modules/lightmapper_rd/lightmapper_rd.cpp +++ b/modules/lightmapper_rd/lightmapper_rd.cpp @@ -51,7 +51,7 @@ void LightmapperRD::add_mesh(const MeshData &p_mesh) { mesh_instances.push_back(mi); } -void LightmapperRD::add_directional_light(bool p_static, const Vector3 &p_direction, const Color &p_color, float p_energy, float p_angular_distance) { +void LightmapperRD::add_directional_light(bool p_static, const Vector3 &p_direction, const Color &p_color, float p_energy, float p_angular_distance, float p_shadow_blur) { Light l; l.type = LIGHT_TYPE_DIRECTIONAL; l.direction[0] = p_direction.x; @@ -62,11 +62,12 @@ void LightmapperRD::add_directional_light(bool p_static, const Vector3 &p_direct l.color[2] = p_color.b; l.energy = p_energy; l.static_bake = p_static; - l.size = p_angular_distance; + l.size = Math::tan(Math::deg2rad(p_angular_distance)); + l.shadow_blur = p_shadow_blur; lights.push_back(l); } -void LightmapperRD::add_omni_light(bool p_static, const Vector3 &p_position, const Color &p_color, float p_energy, float p_range, float p_attenuation, float p_size) { +void LightmapperRD::add_omni_light(bool p_static, const Vector3 &p_position, const Color &p_color, float p_energy, float p_range, float p_attenuation, float p_size, float p_shadow_blur) { Light l; l.type = LIGHT_TYPE_OMNI; l.position[0] = p_position.x; @@ -80,10 +81,11 @@ void LightmapperRD::add_omni_light(bool p_static, const Vector3 &p_position, con l.energy = p_energy; l.static_bake = p_static; l.size = p_size; + l.shadow_blur = p_shadow_blur; lights.push_back(l); } -void LightmapperRD::add_spot_light(bool p_static, const Vector3 &p_position, const Vector3 p_direction, const Color &p_color, float p_energy, float p_range, float p_attenuation, float p_spot_angle, float p_spot_attenuation, float p_size) { +void LightmapperRD::add_spot_light(bool p_static, const Vector3 &p_position, const Vector3 p_direction, const Color &p_color, float p_energy, float p_range, float p_attenuation, float p_spot_angle, float p_spot_attenuation, float p_size, float p_shadow_blur) { Light l; l.type = LIGHT_TYPE_SPOT; l.position[0] = p_position.x; @@ -102,6 +104,7 @@ void LightmapperRD::add_spot_light(bool p_static, const Vector3 &p_position, con l.energy = p_energy; l.static_bake = p_static; l.size = p_size; + l.shadow_blur = p_shadow_blur; lights.push_back(l); } @@ -1140,6 +1143,23 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d RID light_uniform_set = rd->uniform_set_create(uniforms, compute_shader_primary, 1); + switch (p_quality) { + case BAKE_QUALITY_LOW: { + push_constant.ray_count = GLOBAL_GET("rendering/lightmapping/bake_quality/low_quality_ray_count"); + } break; + case BAKE_QUALITY_MEDIUM: { + push_constant.ray_count = GLOBAL_GET("rendering/lightmapping/bake_quality/medium_quality_ray_count"); + } break; + case BAKE_QUALITY_HIGH: { + push_constant.ray_count = GLOBAL_GET("rendering/lightmapping/bake_quality/high_quality_ray_count"); + } break; + case BAKE_QUALITY_ULTRA: { + push_constant.ray_count = GLOBAL_GET("rendering/lightmapping/bake_quality/ultra_quality_ray_count"); + } break; + } + + push_constant.ray_count = CLAMP(push_constant.ray_count, 16u, 8192u); + RD::ComputeListID compute_list = rd->compute_list_begin(); rd->compute_list_bind_compute_pipeline(compute_list, compute_shader_primary_pipeline); rd->compute_list_bind_uniform_set(compute_list, compute_base_uniform_set, 0); @@ -1230,23 +1250,6 @@ LightmapperRD::BakeError LightmapperRD::bake(BakeQuality p_quality, bool p_use_d uniforms.write[1].set_id(0, light_dest_tex); secondary_uniform_set[1] = rd->uniform_set_create(uniforms, compute_shader_secondary, 1); - switch (p_quality) { - case BAKE_QUALITY_LOW: { - push_constant.ray_count = GLOBAL_GET("rendering/lightmapping/bake_quality/low_quality_ray_count"); - } break; - case BAKE_QUALITY_MEDIUM: { - push_constant.ray_count = GLOBAL_GET("rendering/lightmapping/bake_quality/medium_quality_ray_count"); - } break; - case BAKE_QUALITY_HIGH: { - push_constant.ray_count = GLOBAL_GET("rendering/lightmapping/bake_quality/high_quality_ray_count"); - } break; - case BAKE_QUALITY_ULTRA: { - push_constant.ray_count = GLOBAL_GET("rendering/lightmapping/bake_quality/ultra_quality_ray_count"); - } break; - } - - push_constant.ray_count = CLAMP(push_constant.ray_count, 16u, 8192u); - int max_region_size = nearest_power_of_2_templated(int(GLOBAL_GET("rendering/lightmapping/bake_performance/region_size"))); int max_rays = GLOBAL_GET("rendering/lightmapping/bake_performance/max_rays_per_pass"); diff --git a/modules/lightmapper_rd/lightmapper_rd.h b/modules/lightmapper_rd/lightmapper_rd.h index bf9f9b5954..8cb4b58a18 100644 --- a/modules/lightmapper_rd/lightmapper_rd.h +++ b/modules/lightmapper_rd/lightmapper_rd.h @@ -57,8 +57,9 @@ class LightmapperRD : public Lightmapper { float attenuation = 0.0; float cos_spot_angle = 0.0; float inv_spot_attenuation = 0.0; + float shadow_blur = 0.0; uint32_t static_bake = 0; - uint32_t pad[3] = {}; + uint32_t pad[2] = {}; bool operator<(const Light &p_light) const { return type < p_light.type; @@ -236,9 +237,9 @@ class LightmapperRD : public Lightmapper { public: virtual void add_mesh(const MeshData &p_mesh) override; - virtual void add_directional_light(bool p_static, const Vector3 &p_direction, const Color &p_color, float p_energy, float p_angular_distance) override; - virtual void add_omni_light(bool p_static, const Vector3 &p_position, const Color &p_color, float p_energy, float p_range, float p_attenuation, float p_size) override; - virtual void add_spot_light(bool p_static, const Vector3 &p_position, const Vector3 p_direction, const Color &p_color, float p_energy, float p_range, float p_attenuation, float p_spot_angle, float p_spot_attenuation, float p_size) override; + virtual void add_directional_light(bool p_static, const Vector3 &p_direction, const Color &p_color, float p_energy, float p_angular_distance, float p_shadow_blur) override; + virtual void add_omni_light(bool p_static, const Vector3 &p_position, const Color &p_color, float p_energy, float p_range, float p_attenuation, float p_size, float p_shadow_blur) override; + virtual void add_spot_light(bool p_static, const Vector3 &p_position, const Vector3 p_direction, const Color &p_color, float p_energy, float p_range, float p_attenuation, float p_spot_angle, float p_spot_attenuation, float p_size, float p_shadow_blur) override; virtual void add_probe(const Vector3 &p_position) override; virtual BakeError bake(BakeQuality p_quality, bool p_use_denoiser, int p_bounces, float p_bias, int p_max_texture_size, bool p_bake_sh, GenerateProbes p_generate_probes, const Ref<Image> &p_environment_panorama, const Basis &p_environment_transform, BakeStepFunc p_step_function = nullptr, void *p_bake_userdata = nullptr) override; diff --git a/modules/lightmapper_rd/lm_common_inc.glsl b/modules/lightmapper_rd/lm_common_inc.glsl index 58523dc1f8..e0e8000f56 100644 --- a/modules/lightmapper_rd/lm_common_inc.glsl +++ b/modules/lightmapper_rd/lm_common_inc.glsl @@ -51,8 +51,9 @@ struct Light { float cos_spot_angle; float inv_spot_attenuation; + float shadow_blur; bool static_bake; - uint pad[3]; + uint pad[2]; }; layout(set = 0, binding = 4, std430) restrict readonly buffer Lights { diff --git a/modules/lightmapper_rd/lm_compute.glsl b/modules/lightmapper_rd/lm_compute.glsl index 0b6b72a310..efa6cd50b4 100644 --- a/modules/lightmapper_rd/lm_compute.glsl +++ b/modules/lightmapper_rd/lm_compute.glsl @@ -316,19 +316,24 @@ void main() { for (uint i = 0; i < params.light_count; i++) { vec3 light_pos; + float dist; float attenuation; + float soft_shadowing_disk_size; if (lights.data[i].type == LIGHT_TYPE_DIRECTIONAL) { vec3 light_vec = lights.data[i].direction; light_pos = position - light_vec * length(params.world_size); + dist = length(params.world_size); attenuation = 1.0; + soft_shadowing_disk_size = lights.data[i].size; } else { light_pos = lights.data[i].position; - float d = distance(position, light_pos); - if (d > lights.data[i].range) { + dist = distance(position, light_pos); + if (dist > lights.data[i].range) { continue; } + soft_shadowing_disk_size = lights.data[i].size / dist; - attenuation = get_omni_attenuation(d, 1.0 / lights.data[i].range, lights.data[i].attenuation); + attenuation = get_omni_attenuation(dist, 1.0 / lights.data[i].range, lights.data[i].attenuation); if (lights.data[i].type == LIGHT_TYPE_SPOT) { vec3 rel = normalize(position - light_pos); @@ -352,27 +357,70 @@ void main() { continue; //no need to do anything } - if (trace_ray(position + light_dir * params.bias, light_pos) == RAY_MISS) { - vec3 light = lights.data[i].color * lights.data[i].energy * attenuation; - if (lights.data[i].static_bake) { - static_light += light; -#ifdef USE_SH_LIGHTMAPS + float penumbra = 0.0; + if (lights.data[i].size > 0.0) { + vec3 light_to_point = -light_dir; + vec3 aux = light_to_point.y < 0.777 ? vec3(0.0, 1.0, 0.0) : vec3(1.0, 0.0, 0.0); + vec3 light_to_point_tan = normalize(cross(light_to_point, aux)); + vec3 light_to_point_bitan = normalize(cross(light_to_point, light_to_point_tan)); + + const uint shadowing_rays_check_penumbra_denom = 2; + uint shadowing_ray_count = params.ray_count; + + uint hits = 0; + uint noise = random_seed(ivec3(atlas_pos, 43573547 /* some prime */)); + vec3 light_disk_to_point = light_to_point; + for (uint j = 0; j < shadowing_ray_count; j++) { + // Optimization: + // Once already traced an important proportion of rays, if all are hits or misses, + // assume we're not in the penumbra so we can infer the rest would have the same result + if (j == shadowing_ray_count / shadowing_rays_check_penumbra_denom) { + if (hits == j) { + // Assume totally lit + hits = shadowing_ray_count; + break; + } else if (hits == 0) { + // Assume totally dark + hits = 0; + break; + } + } - float c[4] = float[]( - 0.282095, //l0 - 0.488603 * light_dir.y, //l1n1 - 0.488603 * light_dir.z, //l1n0 - 0.488603 * light_dir.x //l1p1 - ); + float r = randomize(noise); + float a = randomize(noise) * 2.0 * PI; + vec2 disk_sample = (r * vec2(cos(a), sin(a))) * soft_shadowing_disk_size * lights.data[i].shadow_blur; + light_disk_to_point = normalize(light_to_point + disk_sample.x * light_to_point_tan + disk_sample.y * light_to_point_bitan); - for (uint j = 0; j < 4; j++) { - sh_accum[j].rgb += light * c[j] * (1.0 / 3.0); + if (trace_ray(position - light_disk_to_point * params.bias, position - light_disk_to_point * dist) == RAY_MISS) { + hits++; } -#endif + } + penumbra = float(hits) / float(shadowing_ray_count); + } else { + if (trace_ray(position + light_dir * params.bias, light_pos) == RAY_MISS) { + penumbra = 1.0; + } + } - } else { - dynamic_light += light; + vec3 light = lights.data[i].color * lights.data[i].energy * attenuation * penumbra; + if (lights.data[i].static_bake) { + static_light += light; +#ifdef USE_SH_LIGHTMAPS + + float c[4] = float[]( + 0.282095, //l0 + 0.488603 * light_dir.y, //l1n1 + 0.488603 * light_dir.z, //l1n0 + 0.488603 * light_dir.x //l1p1 + ); + + for (uint j = 0; j < 4; j++) { + sh_accum[j].rgb += light * c[j] * (1.0 / 3.0); } +#endif + + } else { + dynamic_light += light; } } diff --git a/modules/minimp3/audio_stream_mp3.cpp b/modules/minimp3/audio_stream_mp3.cpp index b5b51403f7..c37bea519f 100644 --- a/modules/minimp3/audio_stream_mp3.cpp +++ b/modules/minimp3/audio_stream_mp3.cpp @@ -38,7 +38,9 @@ #include "core/io/file_access.h" int AudioStreamPlaybackMP3::_mix_internal(AudioFrame *p_buffer, int p_frames) { - ERR_FAIL_COND_V(!active, 0); + if (!active) { + return 0; + } int todo = p_frames; diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeMessaging.CLI/GodotTools.IdeMessaging.CLI.csproj b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging.CLI/GodotTools.IdeMessaging.CLI.csproj index ae78da27bc..303ca3a293 100644 --- a/modules/mono/editor/GodotTools/GodotTools.IdeMessaging.CLI/GodotTools.IdeMessaging.CLI.csproj +++ b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging.CLI/GodotTools.IdeMessaging.CLI.csproj @@ -12,6 +12,6 @@ <ProjectReference Include="..\GodotTools.IdeMessaging\GodotTools.IdeMessaging.csproj" /> </ItemGroup> <ItemGroup> - <PackageReference Include="Newtonsoft.Json" Version="12.0.3" /> + <PackageReference Include="Newtonsoft.Json" Version="13.0.1" /> </ItemGroup> </Project> diff --git a/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/GodotTools.IdeMessaging.csproj b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/GodotTools.IdeMessaging.csproj index dad6b9ae7a..02f1764f32 100644 --- a/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/GodotTools.IdeMessaging.csproj +++ b/modules/mono/editor/GodotTools/GodotTools.IdeMessaging/GodotTools.IdeMessaging.csproj @@ -19,6 +19,6 @@ A client using this library is only compatible with servers of the same major ve </Description> </PropertyGroup> <ItemGroup> - <PackageReference Include="Newtonsoft.Json" Version="12.0.3" /> + <PackageReference Include="Newtonsoft.Json" Version="13.0.1" /> </ItemGroup> </Project> diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs b/modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs index 9e8f7ef1b1..3c020a2589 100644 --- a/modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs +++ b/modules/mono/editor/GodotTools/GodotTools/Build/MSBuildPanel.cs @@ -126,7 +126,7 @@ namespace GodotTools.Build { base._Ready(); - MinimumSize = new Vector2(0, 228) * EditorScale; + CustomMinimumSize = new Vector2(0, 228) * EditorScale; SizeFlagsVertical = (int)SizeFlags.ExpandFill; var toolBarHBox = new HBoxContainer { SizeFlagsHorizontal = (int)SizeFlags.ExpandFill }; diff --git a/modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj b/modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj index b9aa760f4d..f1d45463c5 100644 --- a/modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj +++ b/modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj @@ -19,7 +19,7 @@ <ItemGroup> <PackageReference Include="JetBrains.Annotations" Version="2019.1.3.0" ExcludeAssets="runtime" PrivateAssets="all" /> <PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.0" PrivateAssets="All" /> - <PackageReference Include="Newtonsoft.Json" Version="12.0.3" /> + <PackageReference Include="Newtonsoft.Json" Version="13.0.1" /> <Reference Include="GodotSharp"> <HintPath>$(GodotApiAssembliesDir)/GodotSharp.dll</HintPath> <Private>False</Private> diff --git a/modules/navigation/godot_navigation_server.cpp b/modules/navigation/godot_navigation_server.cpp index 2f8cb6c230..bcbc721dbb 100644 --- a/modules/navigation/godot_navigation_server.cpp +++ b/modules/navigation/godot_navigation_server.cpp @@ -123,6 +123,18 @@ void GodotNavigationServer::add_command(SetCommand *command) const { } } +Array GodotNavigationServer::get_maps() const { + Array all_map_rids; + List<RID> maps_owned; + map_owner.get_owned_list(&maps_owned); + if (maps_owned.size()) { + for (const RID &E : maps_owned) { + all_map_rids.push_back(E); + } + } + return all_map_rids; +} + RID GodotNavigationServer::map_create() const { GodotNavigationServer *mut_this = const_cast<GodotNavigationServer *>(this); MutexLock lock(mut_this->operations_mutex); @@ -339,6 +351,16 @@ real_t GodotNavigationServer::region_get_travel_cost(RID p_region) const { return region->get_travel_cost(); } +bool GodotNavigationServer::region_owns_point(RID p_region, const Vector3 &p_point) const { + const NavRegion *region = region_owner.get_or_null(p_region); + ERR_FAIL_COND_V(region == nullptr, false); + if (region->get_map()) { + RID closest_point_owner = map_get_closest_point_owner(region->get_map()->get_self(), p_point); + return closest_point_owner == region->get_self(); + } + return false; +} + COMMAND_2(region_set_navigation_layers, RID, p_region, uint32_t, p_navigation_layers) { NavRegion *region = region_owner.get_or_null(p_region); ERR_FAIL_COND(region == nullptr); @@ -580,6 +602,15 @@ void GodotNavigationServer::flush_queries() { commands.clear(); } +void GodotNavigationServer::map_force_update(RID p_map) { + NavMap *map = map_owner.get_or_null(p_map); + ERR_FAIL_COND(map == nullptr); + + flush_queries(); + + map->sync(); +} + void GodotNavigationServer::process(real_t p_delta_time) { flush_queries(); diff --git a/modules/navigation/godot_navigation_server.h b/modules/navigation/godot_navigation_server.h index d931dbaee0..8e7e99888c 100644 --- a/modules/navigation/godot_navigation_server.h +++ b/modules/navigation/godot_navigation_server.h @@ -85,6 +85,8 @@ public: void add_command(SetCommand *command) const; + virtual Array get_maps() const override; + virtual RID map_create() const override; COMMAND_2(map_set_active, RID, p_map, bool, p_active); virtual bool map_is_active(RID p_map) const override; @@ -108,6 +110,8 @@ public: virtual Array map_get_regions(RID p_map) const override; virtual Array map_get_agents(RID p_map) const override; + virtual void map_force_update(RID p_map) override; + virtual RID region_create() const override; COMMAND_2(region_set_enter_cost, RID, p_region, real_t, p_enter_cost); @@ -115,6 +119,8 @@ public: COMMAND_2(region_set_travel_cost, RID, p_region, real_t, p_travel_cost); virtual real_t region_get_travel_cost(RID p_region) const override; + virtual bool region_owns_point(RID p_region, const Vector3 &p_point) const override; + COMMAND_2(region_set_map, RID, p_region, RID, p_map); virtual RID region_get_map(RID p_region) const override; COMMAND_2(region_set_navigation_layers, RID, p_region, uint32_t, p_navigation_layers); diff --git a/modules/navigation/navigation_mesh_generator.cpp b/modules/navigation/navigation_mesh_generator.cpp index e430f5fd59..6e8ac77f79 100644 --- a/modules/navigation/navigation_mesh_generator.cpp +++ b/modules/navigation/navigation_mesh_generator.cpp @@ -482,6 +482,21 @@ void NavigationMeshGenerator::_build_recast_navigation_mesh( cfg.bmax[1] = bmax[1]; cfg.bmax[2] = bmax[2]; + AABB baking_aabb = p_nav_mesh->get_filter_baking_aabb(); + + bool aabb_has_no_volume = baking_aabb.has_no_volume(); + + if (!aabb_has_no_volume) { + Vector3 baking_aabb_offset = p_nav_mesh->get_filter_baking_aabb_offset(); + + cfg.bmin[0] = baking_aabb.position[0] + baking_aabb_offset.x; + cfg.bmin[1] = baking_aabb.position[1] + baking_aabb_offset.y; + cfg.bmin[2] = baking_aabb.position[2] + baking_aabb_offset.z; + cfg.bmax[0] = cfg.bmin[0] + baking_aabb.size[0]; + cfg.bmax[1] = cfg.bmin[1] + baking_aabb.size[1]; + cfg.bmax[2] = cfg.bmin[2] + baking_aabb.size[2]; + } + #ifdef TOOLS_ENABLED if (ep) { ep->step(TTR("Calculating grid size..."), 2); @@ -631,6 +646,13 @@ void NavigationMeshGenerator::bake(Ref<NavigationMesh> p_nav_mesh, Node *p_node) #ifdef TOOLS_ENABLED EditorProgress *ep(nullptr); + // FIXME +#endif +#if 0 + // After discussion on devchat disabled EditorProgress for now as it is not thread-safe and uses hacks and Main::iteration() for steps. + // EditorProgress randomly crashes the Engine when the bake function is used with a thread e.g. inside Editor with a tool script and procedural navigation + // This was not a problem in older versions as previously Godot was unable to (re)bake NavigationMesh at runtime. + // If EditorProgress is fixed and made thread-safe this should be enabled again. if (Engine::get_singleton()->is_editor_hint()) { ep = memnew(EditorProgress("bake", TTR("Navigation Mesh Generator Setup:"), 11)); } diff --git a/modules/tinyexr/image_saver_tinyexr.cpp b/modules/tinyexr/image_saver_tinyexr.cpp index 5fa6ace827..661fe343af 100644 --- a/modules/tinyexr/image_saver_tinyexr.cpp +++ b/modules/tinyexr/image_saver_tinyexr.cpp @@ -141,13 +141,14 @@ static int get_channel_count(Image::Format p_format) { } } -Error save_exr(const String &p_path, const Ref<Image> &p_img, bool p_grayscale) { +Vector<uint8_t> save_exr_buffer(const Ref<Image> &p_img, bool p_grayscale) { Image::Format format = p_img->get_format(); if (!is_supported_format(format)) { // Format not supported print_error("Image format not supported for saving as EXR. Consider saving as PNG."); - return ERR_UNAVAILABLE; + + return Vector<uint8_t>(); } EXRHeader header; @@ -175,15 +176,15 @@ Error save_exr(const String &p_path, const Ref<Image> &p_img, bool p_grayscale) }; int channel_count = get_channel_count(format); - ERR_FAIL_COND_V(channel_count < 0, ERR_UNAVAILABLE); - ERR_FAIL_COND_V(p_grayscale && channel_count != 1, ERR_INVALID_PARAMETER); + ERR_FAIL_COND_V(channel_count < 0, Vector<uint8_t>()); + ERR_FAIL_COND_V(p_grayscale && channel_count != 1, Vector<uint8_t>()); int target_pixel_type = get_target_pixel_type(format); - ERR_FAIL_COND_V(target_pixel_type < 0, ERR_UNAVAILABLE); + ERR_FAIL_COND_V(target_pixel_type < 0, Vector<uint8_t>()); int target_pixel_type_size = get_pixel_type_size(target_pixel_type); - ERR_FAIL_COND_V(target_pixel_type_size < 0, ERR_UNAVAILABLE); + ERR_FAIL_COND_V(target_pixel_type_size < 0, Vector<uint8_t>()); SrcPixelType src_pixel_type = get_source_pixel_type(format); - ERR_FAIL_COND_V(src_pixel_type == SRC_UNSUPPORTED, ERR_UNAVAILABLE); + ERR_FAIL_COND_V(src_pixel_type == SRC_UNSUPPORTED, Vector<uint8_t>()); const int pixel_count = p_img->get_width() * p_img->get_height(); const int *channel_mapping = channel_mappings[channel_count - 1]; @@ -270,15 +271,25 @@ Error save_exr(const String &p_path, const Ref<Image> &p_img, bool p_grayscale) const char *err = nullptr; size_t bytes = SaveEXRImageToMemory(&image, &header, &mem, &err); + if (err && *err != OK) { + return Vector<uint8_t>(); + } + Vector<uint8_t> buffer; + buffer.resize(bytes); + memcpy(buffer.ptrw(), mem, bytes); + free(mem); + return buffer; +} - if (bytes == 0) { - print_error(String("Saving EXR failed. Error: {0}").format(varray(err))); +Error save_exr(const String &p_path, const Ref<Image> &p_img, bool p_grayscale) { + const Vector<uint8_t> buffer = save_exr_buffer(p_img, p_grayscale); + if (buffer.size() == 0) { + print_error(String("Saving EXR failed.")); return ERR_FILE_CANT_WRITE; } else { Ref<FileAccess> ref = FileAccess::open(p_path, FileAccess::WRITE); ERR_FAIL_COND_V(ref.is_null(), ERR_FILE_CANT_WRITE); - ref->store_buffer(mem, bytes); - free(mem); + ref->store_buffer(buffer.ptr(), buffer.size()); } return OK; diff --git a/modules/tinyexr/image_saver_tinyexr.h b/modules/tinyexr/image_saver_tinyexr.h index 5bf95b265e..8f97f55408 100644 --- a/modules/tinyexr/image_saver_tinyexr.h +++ b/modules/tinyexr/image_saver_tinyexr.h @@ -34,5 +34,6 @@ #include "core/os/os.h" Error save_exr(const String &p_path, const Ref<Image> &p_img, bool p_grayscale); +Vector<uint8_t> save_exr_buffer(const Ref<Image> &p_img, bool p_grayscale); #endif // IMAGE_SAVER_TINYEXR_H diff --git a/modules/tinyexr/register_types.cpp b/modules/tinyexr/register_types.cpp index d49ac23fea..c5897f37c3 100644 --- a/modules/tinyexr/register_types.cpp +++ b/modules/tinyexr/register_types.cpp @@ -44,6 +44,7 @@ void initialize_tinyexr_module(ModuleInitializationLevel p_level) { ImageLoader::add_image_format_loader(image_loader_tinyexr); Image::save_exr_func = save_exr; + Image::save_exr_buffer_func = save_exr_buffer; } void uninitialize_tinyexr_module(ModuleInitializationLevel p_level) { diff --git a/modules/visual_script/visual_script_flow_control.cpp b/modules/visual_script/visual_script_flow_control.cpp index bbbb995635..19bbd834cc 100644 --- a/modules/visual_script/visual_script_flow_control.cpp +++ b/modules/visual_script/visual_script_flow_control.cpp @@ -803,7 +803,7 @@ public: //if the script is not in use by anyone, we can safely assume whatever we got is not casting to it. return 1; } - Ref<Script> cast_script = Ref<Resource>(ResourceCache::get(script)); + Ref<Script> cast_script = ResourceCache::get_ref(script); if (!cast_script.is_valid()) { r_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD; r_error_str = "Script path is not a script: " + script; diff --git a/modules/visual_script/visual_script_func_nodes.cpp b/modules/visual_script/visual_script_func_nodes.cpp index 483fc8b6c3..b16358ae38 100644 --- a/modules/visual_script/visual_script_func_nodes.cpp +++ b/modules/visual_script/visual_script_func_nodes.cpp @@ -377,7 +377,7 @@ void VisualScriptFunctionCall::_update_method_cache() { } if (ResourceCache::has(base_script)) { - script = Ref<Resource>(ResourceCache::get(base_script)); + script = ResourceCache::get_ref(base_script); } else { return; } @@ -587,7 +587,7 @@ void VisualScriptFunctionCall::_validate_property(PropertyInfo &property) const } if (ResourceCache::has(base_script)) { - Ref<Script> script = Ref<Resource>(ResourceCache::get(base_script)); + Ref<Script> script = ResourceCache::get_ref(base_script); if (script.is_valid()) { property.hint = PROPERTY_HINT_METHOD_OF_SCRIPT; property.hint_string = itos(script->get_instance_id()); @@ -1178,7 +1178,7 @@ void VisualScriptPropertySet::_update_cache() { } if (ResourceCache::has(base_script)) { - script = Ref<Resource>(ResourceCache::get(base_script)); + script = ResourceCache::get_ref(base_script); } else { return; } @@ -1338,7 +1338,7 @@ void VisualScriptPropertySet::_validate_property(PropertyInfo &property) const { } if (ResourceCache::has(base_script)) { - Ref<Script> script = Ref<Resource>(ResourceCache::get(base_script)); + Ref<Script> script = ResourceCache::get_ref(base_script); if (script.is_valid()) { property.hint = PROPERTY_HINT_PROPERTY_OF_SCRIPT; property.hint_string = itos(script->get_instance_id()); @@ -1864,7 +1864,7 @@ void VisualScriptPropertyGet::_update_cache() { } if (ResourceCache::has(base_script)) { - script = Ref<Resource>(ResourceCache::get(base_script)); + script = ResourceCache::get_ref(base_script); } else { return; } @@ -2044,7 +2044,7 @@ void VisualScriptPropertyGet::_validate_property(PropertyInfo &property) const { } if (ResourceCache::has(base_script)) { - Ref<Script> script = Ref<Resource>(ResourceCache::get(base_script)); + Ref<Script> script = ResourceCache::get_ref(base_script); if (script.is_valid()) { property.hint = PROPERTY_HINT_PROPERTY_OF_SCRIPT; property.hint_string = itos(script->get_instance_id()); diff --git a/modules/vorbis/audio_stream_ogg_vorbis.cpp b/modules/vorbis/audio_stream_ogg_vorbis.cpp index b645a48c88..89a6b03ff8 100644 --- a/modules/vorbis/audio_stream_ogg_vorbis.cpp +++ b/modules/vorbis/audio_stream_ogg_vorbis.cpp @@ -36,23 +36,22 @@ int AudioStreamPlaybackOGGVorbis::_mix_internal(AudioFrame *p_buffer, int p_frames) { ERR_FAIL_COND_V(!ready, 0); - ERR_FAIL_COND_V(!active, 0); + + if (!active) { + return 0; + } int todo = p_frames; int start_buffer = 0; - int frames_mixed_this_step = p_frames; - - while (todo && active) { + while (todo > 0 && active) { AudioFrame *buffer = p_buffer; if (start_buffer > 0) { buffer = buffer + start_buffer; } int mixed = _mix_frames_vorbis(buffer, todo); - if (mixed < 0) { - return 0; - } + ERR_FAIL_COND_V(mixed < 0, 0); todo -= mixed; frames_mixed += mixed; start_buffer += mixed; @@ -67,16 +66,14 @@ int AudioStreamPlaybackOGGVorbis::_mix_internal(AudioFrame *p_buffer, int p_fram // we still have buffer to fill, start from this element in the next iteration. start_buffer = p_frames - todo; } else { - frames_mixed_this_step = p_frames - todo; for (int i = p_frames - todo; i < p_frames; i++) { p_buffer[i] = AudioFrame(0, 0); } active = false; - todo = 0; } } } - return frames_mixed_this_step; + return p_frames - todo; } int AudioStreamPlaybackOGGVorbis::_mix_frames_vorbis(AudioFrame *p_buffer, int p_frames) { diff --git a/modules/webp/image_loader_webp.cpp b/modules/webp/image_loader_webp.cpp index 0e41f6c973..778d562278 100644 --- a/modules/webp/image_loader_webp.cpp +++ b/modules/webp/image_loader_webp.cpp @@ -34,184 +34,21 @@ #include "core/io/marshalls.h" #include "core/os/os.h" #include "core/string/print_string.h" +#include "webp_common.h" #include <stdlib.h> #include <webp/decode.h> #include <webp/encode.h> -static Vector<uint8_t> _webp_lossy_pack(const Ref<Image> &p_image, float p_quality) { - ERR_FAIL_COND_V(p_image.is_null() || p_image->is_empty(), Vector<uint8_t>()); - - Ref<Image> img = p_image->duplicate(); - if (img->detect_alpha()) { - img->convert(Image::FORMAT_RGBA8); - } else { - img->convert(Image::FORMAT_RGB8); - } - - Size2 s(img->get_width(), img->get_height()); - Vector<uint8_t> data = img->get_data(); - const uint8_t *r = data.ptr(); - - uint8_t *dst_buff = nullptr; - size_t dst_size = 0; - if (img->get_format() == Image::FORMAT_RGB8) { - dst_size = WebPEncodeRGB(r, s.width, s.height, 3 * s.width, CLAMP(p_quality * 100.0, 0, 100.0), &dst_buff); - } else { - dst_size = WebPEncodeRGBA(r, s.width, s.height, 4 * s.width, CLAMP(p_quality * 100.0, 0, 100.0), &dst_buff); - } - - ERR_FAIL_COND_V(dst_size == 0, Vector<uint8_t>()); - Vector<uint8_t> dst; - dst.resize(4 + dst_size); - uint8_t *w = dst.ptrw(); - w[0] = 'W'; - w[1] = 'E'; - w[2] = 'B'; - w[3] = 'P'; - memcpy(&w[4], dst_buff, dst_size); - WebPFree(dst_buff); - - return dst; -} - -static Vector<uint8_t> _webp_lossless_pack(const Ref<Image> &p_image) { - ERR_FAIL_COND_V(p_image.is_null() || p_image->is_empty(), Vector<uint8_t>()); - - int compression_level = ProjectSettings::get_singleton()->get("rendering/textures/lossless_compression/webp_compression_level"); - compression_level = CLAMP(compression_level, 0, 9); - - Ref<Image> img = p_image->duplicate(); - if (img->detect_alpha()) { - img->convert(Image::FORMAT_RGBA8); - } else { - img->convert(Image::FORMAT_RGB8); - } - - Size2 s(img->get_width(), img->get_height()); - Vector<uint8_t> data = img->get_data(); - const uint8_t *r = data.ptr(); - - // we need to use the more complex API in order to access the 'exact' flag... - - WebPConfig config; - WebPPicture pic; - if (!WebPConfigInit(&config) || !WebPConfigLosslessPreset(&config, compression_level) || !WebPPictureInit(&pic)) { - ERR_FAIL_V(Vector<uint8_t>()); - } - - WebPMemoryWriter wrt; - config.exact = 1; - pic.use_argb = 1; - pic.width = s.width; - pic.height = s.height; - pic.writer = WebPMemoryWrite; - pic.custom_ptr = &wrt; - WebPMemoryWriterInit(&wrt); - - bool success_import = false; - if (img->get_format() == Image::FORMAT_RGB8) { - success_import = WebPPictureImportRGB(&pic, r, 3 * s.width); - } else { - success_import = WebPPictureImportRGBA(&pic, r, 4 * s.width); - } - bool success_encode = false; - if (success_import) { - success_encode = WebPEncode(&config, &pic); - } - WebPPictureFree(&pic); - - if (!success_encode) { - WebPMemoryWriterClear(&wrt); - ERR_FAIL_V_MSG(Vector<uint8_t>(), "WebP packing failed."); - } - - // copy from wrt - Vector<uint8_t> dst; - dst.resize(4 + wrt.size); - uint8_t *w = dst.ptrw(); - w[0] = 'W'; - w[1] = 'E'; - w[2] = 'B'; - w[3] = 'P'; - memcpy(&w[4], wrt.mem, wrt.size); - WebPMemoryWriterClear(&wrt); - - return dst; -} - -static Ref<Image> _webp_unpack(const Vector<uint8_t> &p_buffer) { - int size = p_buffer.size() - 4; - ERR_FAIL_COND_V(size <= 0, Ref<Image>()); - const uint8_t *r = p_buffer.ptr(); - - ERR_FAIL_COND_V(r[0] != 'W' || r[1] != 'E' || r[2] != 'B' || r[3] != 'P', Ref<Image>()); - WebPBitstreamFeatures features; - if (WebPGetFeatures(&r[4], size, &features) != VP8_STATUS_OK) { - ERR_FAIL_V_MSG(Ref<Image>(), "Error unpacking WEBP image."); - } - - /* - print_line("width: "+itos(features.width)); - print_line("height: "+itos(features.height)); - print_line("alpha: "+itos(features.has_alpha)); - */ - - Vector<uint8_t> dst_image; - int datasize = features.width * features.height * (features.has_alpha ? 4 : 3); - dst_image.resize(datasize); - - uint8_t *dst_w = dst_image.ptrw(); - - bool errdec = false; - if (features.has_alpha) { - errdec = WebPDecodeRGBAInto(&r[4], size, dst_w, datasize, 4 * features.width) == nullptr; - } else { - errdec = WebPDecodeRGBInto(&r[4], size, dst_w, datasize, 3 * features.width) == nullptr; - } - - ERR_FAIL_COND_V_MSG(errdec, Ref<Image>(), "Failed decoding WebP image."); - - Ref<Image> img = memnew(Image(features.width, features.height, 0, features.has_alpha ? Image::FORMAT_RGBA8 : Image::FORMAT_RGB8, dst_image)); - return img; -} - -Error webp_load_image_from_buffer(Image *p_image, const uint8_t *p_buffer, int p_buffer_len) { - ERR_FAIL_NULL_V(p_image, ERR_INVALID_PARAMETER); - - WebPBitstreamFeatures features; - if (WebPGetFeatures(p_buffer, p_buffer_len, &features) != VP8_STATUS_OK) { - ERR_FAIL_V(ERR_FILE_CORRUPT); - } - - Vector<uint8_t> dst_image; - int datasize = features.width * features.height * (features.has_alpha ? 4 : 3); - dst_image.resize(datasize); - uint8_t *dst_w = dst_image.ptrw(); - - bool errdec = false; - if (features.has_alpha) { - errdec = WebPDecodeRGBAInto(p_buffer, p_buffer_len, dst_w, datasize, 4 * features.width) == nullptr; - } else { - errdec = WebPDecodeRGBInto(p_buffer, p_buffer_len, dst_w, datasize, 3 * features.width) == nullptr; - } - - ERR_FAIL_COND_V_MSG(errdec, ERR_FILE_CORRUPT, "Failed decoding WebP image."); - - p_image->create(features.width, features.height, false, features.has_alpha ? Image::FORMAT_RGBA8 : Image::FORMAT_RGB8, dst_image); - - return OK; -} - static Ref<Image> _webp_mem_loader_func(const uint8_t *p_png, int p_size) { Ref<Image> img; img.instantiate(); - Error err = webp_load_image_from_buffer(img.ptr(), p_png, p_size); + Error err = WebPCommon::webp_load_image_from_buffer(img.ptr(), p_png, p_size); ERR_FAIL_COND_V(err, Ref<Image>()); return img; } -Error ImageLoaderWEBP::load_image(Ref<Image> p_image, Ref<FileAccess> f, bool p_force_linear, float p_scale) { +Error ImageLoaderWebP::load_image(Ref<Image> p_image, Ref<FileAccess> f, bool p_force_linear, float p_scale) { Vector<uint8_t> src_image; uint64_t src_image_len = f->get_length(); ERR_FAIL_COND_V(src_image_len == 0, ERR_FILE_CORRUPT); @@ -221,18 +58,18 @@ Error ImageLoaderWEBP::load_image(Ref<Image> p_image, Ref<FileAccess> f, bool p_ f->get_buffer(&w[0], src_image_len); - Error err = webp_load_image_from_buffer(p_image.ptr(), w, src_image_len); + Error err = WebPCommon::webp_load_image_from_buffer(p_image.ptr(), w, src_image_len); return err; } -void ImageLoaderWEBP::get_recognized_extensions(List<String> *p_extensions) const { +void ImageLoaderWebP::get_recognized_extensions(List<String> *p_extensions) const { p_extensions->push_back("webp"); } -ImageLoaderWEBP::ImageLoaderWEBP() { +ImageLoaderWebP::ImageLoaderWebP() { Image::_webp_mem_loader_func = _webp_mem_loader_func; - Image::webp_lossy_packer = _webp_lossy_pack; - Image::webp_lossless_packer = _webp_lossless_pack; - Image::webp_unpacker = _webp_unpack; + Image::webp_lossy_packer = WebPCommon::_webp_lossy_pack; + Image::webp_lossless_packer = WebPCommon::_webp_lossless_pack; + Image::webp_unpacker = WebPCommon::_webp_unpack; } diff --git a/modules/webp/image_loader_webp.h b/modules/webp/image_loader_webp.h index 1acd1459a0..448f683eb3 100644 --- a/modules/webp/image_loader_webp.h +++ b/modules/webp/image_loader_webp.h @@ -33,11 +33,11 @@ #include "core/io/image_loader.h" -class ImageLoaderWEBP : public ImageFormatLoader { +class ImageLoaderWebP : public ImageFormatLoader { public: virtual Error load_image(Ref<Image> p_image, Ref<FileAccess> f, bool p_force_linear, float p_scale); virtual void get_recognized_extensions(List<String> *p_extensions) const; - ImageLoaderWEBP(); + ImageLoaderWebP(); }; #endif diff --git a/modules/webp/register_types.cpp b/modules/webp/register_types.cpp index 148e325498..29f633743e 100644 --- a/modules/webp/register_types.cpp +++ b/modules/webp/register_types.cpp @@ -31,16 +31,20 @@ #include "register_types.h" #include "image_loader_webp.h" +#include "resource_saver_webp.h" -static ImageLoaderWEBP *image_loader_webp = nullptr; +static ImageLoaderWebP *image_loader_webp = nullptr; +static Ref<ResourceSaverWebP> resource_saver_webp; void initialize_webp_module(ModuleInitializationLevel p_level) { if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) { return; } - image_loader_webp = memnew(ImageLoaderWEBP); + image_loader_webp = memnew(ImageLoaderWebP); + resource_saver_webp.instantiate(); ImageLoader::add_image_format_loader(image_loader_webp); + ResourceSaver::add_resource_format_saver(resource_saver_webp); } void uninitialize_webp_module(ModuleInitializationLevel p_level) { @@ -49,4 +53,6 @@ void uninitialize_webp_module(ModuleInitializationLevel p_level) { } memdelete(image_loader_webp); + ResourceSaver::remove_resource_format_saver(resource_saver_webp); + resource_saver_webp.unref(); } diff --git a/modules/webp/resource_saver_webp.cpp b/modules/webp/resource_saver_webp.cpp new file mode 100644 index 0000000000..d270d39163 --- /dev/null +++ b/modules/webp/resource_saver_webp.cpp @@ -0,0 +1,90 @@ +/*************************************************************************/ +/* resource_saver_webp.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "resource_saver_webp.h" + +#include "core/io/file_access.h" +#include "core/io/image.h" +#include "scene/resources/texture.h" +#include "webp_common.h" + +Error ResourceSaverWebP::save(const String &p_path, const Ref<Resource> &p_resource, uint32_t p_flags) { + Ref<ImageTexture> texture = p_resource; + + ERR_FAIL_COND_V_MSG(!texture.is_valid(), ERR_INVALID_PARAMETER, "Can't save invalid texture as WEBP."); + ERR_FAIL_COND_V_MSG(!texture->get_width(), ERR_INVALID_PARAMETER, "Can't save empty texture as WEBP."); + + Ref<Image> img = texture->get_image(); + + Error err = save_image(p_path, img); + + return err; +} + +Error ResourceSaverWebP::save_image(const String &p_path, const Ref<Image> &p_img, const bool p_lossy, const float p_quality) { + Vector<uint8_t> buffer = save_image_to_buffer(p_img, p_lossy, p_quality); + Error err; + Ref<FileAccess> file = FileAccess::open(p_path, FileAccess::WRITE, &err); + ERR_FAIL_COND_V_MSG(err, err, vformat("Can't save WEBP at path: '%s'.", p_path)); + + const uint8_t *reader = buffer.ptr(); + + file->store_buffer(reader, buffer.size()); + if (file->get_error() != OK && file->get_error() != ERR_FILE_EOF) { + return ERR_CANT_CREATE; + } + + return OK; +} + +Vector<uint8_t> ResourceSaverWebP::save_image_to_buffer(const Ref<Image> &p_img, const bool p_lossy, const float p_quality) { + Vector<uint8_t> buffer; + if (p_lossy) { + buffer = WebPCommon::_webp_lossy_pack(p_img, p_quality); + } else { + buffer = WebPCommon::_webp_lossless_pack(p_img); + } + return buffer; +} + +bool ResourceSaverWebP::recognize(const Ref<Resource> &p_resource) const { + return (p_resource.is_valid() && p_resource->is_class("ImageTexture")); +} + +void ResourceSaverWebP::get_recognized_extensions(const Ref<Resource> &p_resource, List<String> *p_extensions) const { + if (Object::cast_to<ImageTexture>(*p_resource)) { + p_extensions->push_back("webp"); + } +} + +ResourceSaverWebP::ResourceSaverWebP() { + Image::save_webp_func = &save_image; + Image::save_webp_buffer_func = &save_image_to_buffer; +} diff --git a/modules/webp/resource_saver_webp.h b/modules/webp/resource_saver_webp.h new file mode 100644 index 0000000000..59e944efa0 --- /dev/null +++ b/modules/webp/resource_saver_webp.h @@ -0,0 +1,49 @@ +/*************************************************************************/ +/* resource_saver_webp.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef RESOURCE_SAVER_WEBP_H +#define RESOURCE_SAVER_WEBP_H + +#include "core/io/image.h" +#include "core/io/resource_saver.h" + +class ResourceSaverWebP : public ResourceFormatSaver { +public: + static Error save_image(const String &p_path, const Ref<Image> &p_img, const bool p_lossy = false, const float p_quality = 0.75f); + static Vector<uint8_t> save_image_to_buffer(const Ref<Image> &p_img, const bool p_lossy = false, const float p_quality = 0.75f); + + virtual Error save(const String &p_path, const Ref<Resource> &p_resource, uint32_t p_flags = 0); + virtual bool recognize(const Ref<Resource> &p_resource) const; + virtual void get_recognized_extensions(const Ref<Resource> &p_resource, List<String> *p_extensions) const; + + ResourceSaverWebP(); +}; + +#endif // RESOURCE_SAVER_WEBP_H diff --git a/modules/webp/webp_common.cpp b/modules/webp/webp_common.cpp new file mode 100644 index 0000000000..8657a98853 --- /dev/null +++ b/modules/webp/webp_common.cpp @@ -0,0 +1,190 @@ +/*************************************************************************/ +/* webp_common.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "webp_common.h" + +#include "core/config/project_settings.h" +#include "core/os/os.h" + +#include <string.h> +#include <webp/decode.h> +#include <webp/encode.h> + +namespace WebPCommon { +Vector<uint8_t> _webp_lossy_pack(const Ref<Image> &p_image, float p_quality) { + ERR_FAIL_COND_V(p_image.is_null() || p_image->is_empty(), Vector<uint8_t>()); + + Ref<Image> img = p_image->duplicate(); + if (img->detect_alpha()) { + img->convert(Image::FORMAT_RGBA8); + } else { + img->convert(Image::FORMAT_RGB8); + } + + Size2 s(img->get_width(), img->get_height()); + Vector<uint8_t> data = img->get_data(); + const uint8_t *r = data.ptr(); + + uint8_t *dst_buff = nullptr; + size_t dst_size = 0; + if (img->get_format() == Image::FORMAT_RGB8) { + dst_size = WebPEncodeRGB(r, s.width, s.height, 3 * s.width, CLAMP(p_quality * 100.0f, 0.0f, 100.0f), &dst_buff); + } else { + dst_size = WebPEncodeRGBA(r, s.width, s.height, 4 * s.width, CLAMP(p_quality * 100.0f, 0.0f, 100.0f), &dst_buff); + } + + ERR_FAIL_COND_V(dst_size == 0, Vector<uint8_t>()); + Vector<uint8_t> dst; + dst.resize(dst_size); + uint8_t *w = dst.ptrw(); + memcpy(w, dst_buff, dst_size); + WebPFree(dst_buff); + + return dst; +} + +Vector<uint8_t> _webp_lossless_pack(const Ref<Image> &p_image) { + ERR_FAIL_COND_V(p_image.is_null() || p_image->is_empty(), Vector<uint8_t>()); + + int compression_level = ProjectSettings::get_singleton()->get("rendering/textures/lossless_compression/webp_compression_level"); + compression_level = CLAMP(compression_level, 0, 9); + + Ref<Image> img = p_image->duplicate(); + if (img->detect_alpha()) { + img->convert(Image::FORMAT_RGBA8); + } else { + img->convert(Image::FORMAT_RGB8); + } + + Size2 s(img->get_width(), img->get_height()); + Vector<uint8_t> data = img->get_data(); + const uint8_t *r = data.ptr(); + + // we need to use the more complex API in order to access the 'exact' flag... + + WebPConfig config; + WebPPicture pic; + if (!WebPConfigInit(&config) || !WebPConfigLosslessPreset(&config, compression_level) || !WebPPictureInit(&pic)) { + ERR_FAIL_V(Vector<uint8_t>()); + } + + WebPMemoryWriter wrt; + config.exact = 1; + pic.use_argb = 1; + pic.width = s.width; + pic.height = s.height; + pic.writer = WebPMemoryWrite; + pic.custom_ptr = &wrt; + WebPMemoryWriterInit(&wrt); + + bool success_import = false; + if (img->get_format() == Image::FORMAT_RGB8) { + success_import = WebPPictureImportRGB(&pic, r, 3 * s.width); + } else { + success_import = WebPPictureImportRGBA(&pic, r, 4 * s.width); + } + bool success_encode = false; + if (success_import) { + success_encode = WebPEncode(&config, &pic); + } + WebPPictureFree(&pic); + + if (!success_encode) { + WebPMemoryWriterClear(&wrt); + ERR_FAIL_V_MSG(Vector<uint8_t>(), "WebP packing failed."); + } + + // copy from wrt + Vector<uint8_t> dst; + dst.resize(wrt.size); + uint8_t *w = dst.ptrw(); + memcpy(w, wrt.mem, wrt.size); + WebPMemoryWriterClear(&wrt); + return dst; +} + +Ref<Image> _webp_unpack(const Vector<uint8_t> &p_buffer) { + int size = p_buffer.size(); + ERR_FAIL_COND_V(size <= 0, Ref<Image>()); + const uint8_t *r = p_buffer.ptr(); + + // A WebP file uses a RIFF header, which starts with "RIFF____WEBP". + ERR_FAIL_COND_V(r[0] != 'R' || r[1] != 'I' || r[2] != 'F' || r[3] != 'F' || r[8] != 'W' || r[9] != 'E' || r[10] != 'B' || r[11] != 'P', Ref<Image>()); + WebPBitstreamFeatures features; + if (WebPGetFeatures(r, size, &features) != VP8_STATUS_OK) { + ERR_FAIL_V_MSG(Ref<Image>(), "Error unpacking WEBP image."); + } + + Vector<uint8_t> dst_image; + int datasize = features.width * features.height * (features.has_alpha ? 4 : 3); + dst_image.resize(datasize); + + uint8_t *dst_w = dst_image.ptrw(); + + bool errdec = false; + if (features.has_alpha) { + errdec = WebPDecodeRGBAInto(r, size, dst_w, datasize, 4 * features.width) == nullptr; + } else { + errdec = WebPDecodeRGBInto(r, size, dst_w, datasize, 3 * features.width) == nullptr; + } + + ERR_FAIL_COND_V_MSG(errdec, Ref<Image>(), "Failed decoding WebP image."); + + Ref<Image> img = memnew(Image(features.width, features.height, 0, features.has_alpha ? Image::FORMAT_RGBA8 : Image::FORMAT_RGB8, dst_image)); + return img; +} + +Error webp_load_image_from_buffer(Image *p_image, const uint8_t *p_buffer, int p_buffer_len) { + ERR_FAIL_NULL_V(p_image, ERR_INVALID_PARAMETER); + + WebPBitstreamFeatures features; + if (WebPGetFeatures(p_buffer, p_buffer_len, &features) != VP8_STATUS_OK) { + ERR_FAIL_V(ERR_FILE_CORRUPT); + } + + Vector<uint8_t> dst_image; + int datasize = features.width * features.height * (features.has_alpha ? 4 : 3); + dst_image.resize(datasize); + uint8_t *dst_w = dst_image.ptrw(); + + bool errdec = false; + if (features.has_alpha) { + errdec = WebPDecodeRGBAInto(p_buffer, p_buffer_len, dst_w, datasize, 4 * features.width) == nullptr; + } else { + errdec = WebPDecodeRGBInto(p_buffer, p_buffer_len, dst_w, datasize, 3 * features.width) == nullptr; + } + + ERR_FAIL_COND_V_MSG(errdec, ERR_FILE_CORRUPT, "Failed decoding WebP image."); + + p_image->create(features.width, features.height, false, features.has_alpha ? Image::FORMAT_RGBA8 : Image::FORMAT_RGB8, dst_image); + + return OK; +} +} // namespace WebPCommon diff --git a/modules/webp/webp_common.h b/modules/webp/webp_common.h new file mode 100644 index 0000000000..11bef40256 --- /dev/null +++ b/modules/webp/webp_common.h @@ -0,0 +1,45 @@ +/*************************************************************************/ +/* webp_common.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef WEBP_COMMON_H +#define WEBP_COMMON_H + +#include "core/io/image.h" + +namespace WebPCommon { +// Given an image, pack this data into a WebP file. +Vector<uint8_t> _webp_lossy_pack(const Ref<Image> &p_image, float p_quality); +Vector<uint8_t> _webp_lossless_pack(const Ref<Image> &p_image); +// Given a WebP file, unpack it into an image. +Ref<Image> _webp_unpack(const Vector<uint8_t> &p_buffer); +Error webp_load_image_from_buffer(Image *p_image, const uint8_t *p_buffer, int p_buffer_len); +} //namespace WebPCommon + +#endif // WEBP_COMMON_H |