diff options
Diffstat (limited to 'modules')
70 files changed, 1222 insertions, 567 deletions
diff --git a/modules/etcpak/SCsub b/modules/etcpak/SCsub index 821c6801b4..2d3b69be75 100644 --- a/modules/etcpak/SCsub +++ b/modules/etcpak/SCsub @@ -11,31 +11,15 @@ thirdparty_obj = [] thirdparty_dir = "#thirdparty/etcpak/" thirdparty_sources = [ - "Bitmap.cpp", - "BitmapDownsampled.cpp", - "BlockData.cpp", - "ColorSpace.cpp", - "DataProvider.cpp", - "Debug.cpp", "Dither.cpp", - "Error.cpp", - "mmap.cpp", "ProcessDxtc.cpp", "ProcessRGB.cpp", - "System.cpp", "Tables.cpp", - "TaskDispatch.cpp", - "Timing.cpp", - "lz4/lz4.c", ] thirdparty_sources = [thirdparty_dir + file for file in thirdparty_sources] env_etcpak.Prepend(CPPPATH=[thirdparty_dir]) -# Also requires libpng headers -if env["builtin_libpng"]: - env_etcpak.Prepend(CPPPATH=["#thirdparty/libpng"]) - env_thirdparty = env_etcpak.Clone() env_thirdparty.disable_warnings() env_thirdparty.add_source_files(thirdparty_obj, thirdparty_sources) diff --git a/modules/etcpak/image_etcpak.cpp b/modules/etcpak/image_compress_etcpak.cpp index 251d2cd7b0..abc3c26188 100644 --- a/modules/etcpak/image_etcpak.cpp +++ b/modules/etcpak/image_compress_etcpak.cpp @@ -1,5 +1,5 @@ /*************************************************************************/ -/* image_etcpak.cpp */ +/* image_compress_etcpak.cpp */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,21 +28,16 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#include "image_etcpak.h" +#include "image_compress_etcpak.h" -#include "core/os/copymem.h" #include "core/os/os.h" #include "core/string/print_string.h" #include "thirdparty/etcpak/ProcessDxtc.hpp" #include "thirdparty/etcpak/ProcessRGB.hpp" -// thresholds for the early compression-mode decision scheme in QuickETC2 -// which can be changed by the option -e -float ecmd_threshold[3] = { 0.03f, 0.09f, 0.38f }; - -EtcpakType _determine_etc_type(Image::UsedChannels p_source) { - switch (p_source) { +EtcpakType _determine_etc_type(Image::UsedChannels p_channels) { + switch (p_channels) { case Image::USED_CHANNELS_L: return EtcpakType::ETCPAK_TYPE_ETC1; case Image::USED_CHANNELS_LA: @@ -60,8 +55,8 @@ EtcpakType _determine_etc_type(Image::UsedChannels p_source) { } } -EtcpakType _determine_dxt_type(Image::UsedChannels p_source) { - switch (p_source) { +EtcpakType _determine_dxt_type(Image::UsedChannels p_channels) { + switch (p_channels) { case Image::USED_CHANNELS_L: return EtcpakType::ETCPAK_TYPE_DXT1; case Image::USED_CHANNELS_LA: @@ -78,93 +73,112 @@ EtcpakType _determine_dxt_type(Image::UsedChannels p_source) { return EtcpakType::ETCPAK_TYPE_DXT5; } } -void _compress_etc2(Image *p_img, float p_lossy_quality, Image::UsedChannels p_source) { - EtcpakType type = _determine_etc_type(p_source); - _compress_etcpak(type, p_img, p_lossy_quality, false, p_source); + +void _compress_etc1(Image *r_img, float p_lossy_quality) { + _compress_etcpak(EtcpakType::ETCPAK_TYPE_ETC1, r_img, p_lossy_quality); } -void _compress_bc(Image *p_img, float p_lossy_quality, Image::UsedChannels p_source) { - EtcpakType type = _determine_dxt_type(p_source); - _compress_etcpak(type, p_img, p_lossy_quality, false, p_source); + +void _compress_etc2(Image *r_img, float p_lossy_quality, Image::UsedChannels p_channels) { + EtcpakType type = _determine_etc_type(p_channels); + _compress_etcpak(type, r_img, p_lossy_quality); } -void _compress_etc1(Image *p_img, float p_lossy_quality) { - _compress_etcpak(EtcpakType::ETCPAK_TYPE_ETC1, p_img, p_lossy_quality, true, Image::USED_CHANNELS_RGB); + +void _compress_bc(Image *r_img, float p_lossy_quality, Image::UsedChannels p_channels) { + EtcpakType type = _determine_dxt_type(p_channels); + _compress_etcpak(type, r_img, p_lossy_quality); } -void _compress_etcpak(EtcpakType p_compresstype, Image *p_img, float p_lossy_quality, bool force_etc1_format, Image::UsedChannels p_channels) { - uint64_t t = OS::get_singleton()->get_ticks_msec(); - Image::Format img_format = p_img->get_format(); +void _compress_etcpak(EtcpakType p_compresstype, Image *r_img, float p_lossy_quality) { + uint64_t start_time = OS::get_singleton()->get_ticks_msec(); + // TODO: See how to handle lossy quality. + + Image::Format img_format = r_img->get_format(); if (img_format >= Image::FORMAT_DXT1) { - return; //do not compress, already compressed + return; // Do not compress, already compressed. } - if (img_format > Image::FORMAT_RGBA8) { // TODO: we should be able to handle FORMAT_RGBA4444 and FORMAT_RGBA5551 eventually return; } - Image::Format format = Image::FORMAT_RGBA8; - if (p_img->get_format() != Image::FORMAT_RGBA8) { - p_img->convert(Image::FORMAT_RGBA8); + // Use RGBA8 to convert. + if (img_format != Image::FORMAT_RGBA8) { + r_img->convert(Image::FORMAT_RGBA8); } - if (p_compresstype == EtcpakType::ETCPAK_TYPE_ETC1 || force_etc1_format) { - format = Image::FORMAT_ETC; + + // Determine output format based on Etcpak type. + Image::Format target_format = Image::FORMAT_RGBA8; + if (p_compresstype == EtcpakType::ETCPAK_TYPE_ETC1) { + target_format = Image::FORMAT_ETC; } else if (p_compresstype == EtcpakType::ETCPAK_TYPE_ETC2) { - format = Image::FORMAT_ETC2_RGB8; + target_format = Image::FORMAT_ETC2_RGB8; } else if (p_compresstype == EtcpakType::ETCPAK_TYPE_ETC2_RA_AS_RG) { - format = Image::FORMAT_ETC2_RA_AS_RG; - p_img->convert_rg_to_ra_rgba8(); + target_format = Image::FORMAT_ETC2_RA_AS_RG; + r_img->convert_rg_to_ra_rgba8(); } else if (p_compresstype == EtcpakType::ETCPAK_TYPE_ETC2_ALPHA) { - format = Image::FORMAT_ETC2_RGBA8; + target_format = Image::FORMAT_ETC2_RGBA8; } else if (p_compresstype == EtcpakType::ETCPAK_TYPE_DXT1) { - format = Image::FORMAT_DXT1; + target_format = Image::FORMAT_DXT1; } else if (p_compresstype == EtcpakType::ETCPAK_TYPE_DXT5_RA_AS_RG) { - format = Image::FORMAT_DXT5_RA_AS_RG; - p_img->convert_rg_to_ra_rgba8(); + target_format = Image::FORMAT_DXT5_RA_AS_RG; + r_img->convert_rg_to_ra_rgba8(); } else if (p_compresstype == EtcpakType::ETCPAK_TYPE_DXT5) { - format = Image::FORMAT_DXT5; + target_format = Image::FORMAT_DXT5; } else { - ERR_FAIL(); + ERR_FAIL_MSG("Invalid or unsupported Etcpak compression format."); } - const bool mipmap = p_img->has_mipmaps(); - print_verbose("Encoding format: " + Image::get_format_name(format)); + // Compress image data and (if required) mipmaps. + + const bool mipmaps = r_img->has_mipmaps(); + const int width = r_img->get_width(); + const int height = r_img->get_height(); + const uint8_t *src_read = r_img->get_data().ptr(); - Ref<Image> new_img; - new_img.instance(); - new_img->create(p_img->get_width(), p_img->get_height(), mipmap, format); - Vector<uint8_t> data = new_img->get_data(); - uint8_t *wr = data.ptrw(); + print_verbose(vformat("ETCPAK: Encoding image size %dx%d to format %s.", width, height, Image::get_format_name(target_format))); - Ref<Image> image = p_img->duplicate(); - int mmc = 1 + (mipmap ? Image::get_image_required_mipmaps(new_img->get_width(), new_img->get_height(), format) : 0); - for (int i = 0; i < mmc; i++) { - int ofs, size, mip_w, mip_h; - new_img->get_mipmap_offset_size_and_dimensions(i, ofs, size, mip_w, mip_h); + int dest_size = Image::get_image_data_size(width, height, target_format, mipmaps); + Vector<uint8_t> dest_data; + dest_data.resize(dest_size); + uint8_t *dest_write = dest_data.ptrw(); + + int mip_count = mipmaps ? Image::get_image_required_mipmaps(width, height, target_format) : 0; + + for (int i = 0; i < mip_count + 1; i++) { + // Get write mip metrics for target image. + int mip_w, mip_h; + int mip_ofs = Image::get_image_mipmap_offset_and_dimensions(width, height, target_format, i, mip_w, mip_h); + // Ensure that mip offset is a multiple of 8 (etcpak expects uint64_t pointer). + ERR_FAIL_COND(mip_ofs % 8 != 0); + uint64_t *dest_mip_write = (uint64_t *)&dest_write[mip_ofs]; + + // Block size. Align stride to multiple of 4 (RGBA8). mip_w = (mip_w + 3) & ~3; mip_h = (mip_h + 3) & ~3; - Vector<uint8_t> dst_data; - dst_data.resize(size); - int mipmap_ofs = image->get_mipmap_offset(i); - - const uint32_t *image_read = (const uint32_t *)&image->get_data().ptr()[mipmap_ofs]; - uint64_t *dst_write = (uint64_t *)dst_data.ptrw(); - if (p_compresstype == EtcpakType::ETCPAK_TYPE_ETC1 || force_etc1_format) { - CompressEtc1RgbDither(image_read, dst_write, mip_w * mip_h / 16, mip_w); + const uint32_t blocks = mip_w * mip_h / 16; + + // Get mip data from source image for reading. + int src_mip_ofs = r_img->get_mipmap_offset(i); + const uint32_t *src_mip_read = (const uint32_t *)&src_read[src_mip_ofs]; + + if (p_compresstype == EtcpakType::ETCPAK_TYPE_ETC1) { + CompressEtc1RgbDither(src_mip_read, dest_mip_write, blocks, mip_w); } else if (p_compresstype == EtcpakType::ETCPAK_TYPE_ETC2 || p_compresstype == EtcpakType::ETCPAK_TYPE_ETC2_RA_AS_RG) { - CompressEtc2Rgb(image_read, dst_write, mip_w * mip_h / 16, mip_w); + CompressEtc2Rgb(src_mip_read, dest_mip_write, blocks, mip_w); } else if (p_compresstype == EtcpakType::ETCPAK_TYPE_ETC2_ALPHA) { - CompressEtc2Rgba(image_read, dst_write, mip_w * mip_h / 16, mip_w); - } else if (p_compresstype == EtcpakType::ETCPAK_TYPE_DXT5 || p_compresstype == EtcpakType::ETCPAK_TYPE_DXT5_RA_AS_RG) { - CompressDxt5(image_read, dst_write, mip_w * mip_h / 16, mip_w); + CompressEtc2Rgba(src_mip_read, dest_mip_write, blocks, mip_w); } else if (p_compresstype == EtcpakType::ETCPAK_TYPE_DXT1) { - CompressDxt1Dither(image_read, dst_write, mip_w * mip_h / 16, mip_w); + CompressDxt1Dither(src_mip_read, dest_mip_write, blocks, mip_w); + } else if (p_compresstype == EtcpakType::ETCPAK_TYPE_DXT5 || p_compresstype == EtcpakType::ETCPAK_TYPE_DXT5_RA_AS_RG) { + CompressDxt5(src_mip_read, dest_mip_write, blocks, mip_w); } else { - ERR_FAIL(); + ERR_FAIL_MSG("Invalid or unsupported Etcpak compression format."); } - copymem(&wr[ofs], dst_data.ptr(), size); } - p_img->create(new_img->get_width(), new_img->get_height(), mipmap, format, data); - print_verbose(vformat("ETCPAK encode took %s ms.", rtos(OS::get_singleton()->get_ticks_msec() - t))); + // Replace original image with compressed one. + r_img->create(width, height, mipmaps, target_format, dest_data); + + print_verbose(vformat("ETCPAK encode took %s ms.", rtos(OS::get_singleton()->get_ticks_msec() - start_time))); } diff --git a/modules/etcpak/image_etcpak.h b/modules/etcpak/image_compress_etcpak.h index 0137bab7cc..ccf157fada 100644 --- a/modules/etcpak/image_etcpak.h +++ b/modules/etcpak/image_compress_etcpak.h @@ -1,5 +1,5 @@ /*************************************************************************/ -/* image_etcpak.h */ +/* image_compress_etcpak.h */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,8 +28,8 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef IMAGE_ETCPAK_H -#define IMAGE_ETCPAK_H +#ifndef IMAGE_COMPRESS_ETCPAK_H +#define IMAGE_COMPRESS_ETCPAK_H #include "core/io/image.h" @@ -43,9 +43,10 @@ enum class EtcpakType { ETCPAK_TYPE_DXT5_RA_AS_RG, }; -void _compress_etcpak(EtcpakType p_compresstype, Image *p_img, float p_lossy_quality, bool force_etc1_format, Image::UsedChannels p_channels); -void _compress_etc1(Image *p_img, float p_lossy_quality); -void _compress_etc2(Image *p_img, float p_lossy_quality, Image::UsedChannels p_source); -void _compress_bc(Image *p_img, float p_lossy_quality, Image::UsedChannels p_source); +void _compress_etc1(Image *r_img, float p_lossy_quality); +void _compress_etc2(Image *r_img, float p_lossy_quality, Image::UsedChannels p_channels); +void _compress_bc(Image *r_img, float p_lossy_quality, Image::UsedChannels p_channels); -#endif // IMAGE_ETCPAK_H +void _compress_etcpak(EtcpakType p_compresstype, Image *r_img, float p_lossy_quality); + +#endif // IMAGE_COMPRESS_ETCPAK_H diff --git a/modules/etcpak/register_types.cpp b/modules/etcpak/register_types.cpp index fcc0bc8b6f..d57d2f747a 100644 --- a/modules/etcpak/register_types.cpp +++ b/modules/etcpak/register_types.cpp @@ -30,7 +30,7 @@ #include "register_types.h" -#include "image_etcpak.h" +#include "image_compress_etcpak.h" void register_etcpak_types() { Image::_image_compress_etc1_func = _compress_etc1; diff --git a/modules/etcpak/register_types.h b/modules/etcpak/register_types.h index 9b300a3275..a9e10a4aae 100644 --- a/modules/etcpak/register_types.h +++ b/modules/etcpak/register_types.h @@ -28,5 +28,10 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ +#ifndef ETCPAK_REGISTER_TYPES_H +#define ETCPAK_REGISTER_TYPES_H + void register_etcpak_types(); void unregister_etcpak_types(); + +#endif // ETCPAK_REGISTER_TYPES_H diff --git a/modules/fbx/data/fbx_material.cpp b/modules/fbx/data/fbx_material.cpp index 5995097b2f..d54ac86e9f 100644 --- a/modules/fbx/data/fbx_material.cpp +++ b/modules/fbx/data/fbx_material.cpp @@ -277,7 +277,7 @@ Ref<StandardMaterial3D> FBXMaterial::import_material(ImportState &state) { } /// ALL below is related to properties - for (FBXDocParser::LazyPropertyMap::value_type iter : material->Props()->GetLazyProperties()) { + for (FBXDocParser::LazyPropertyMap::value_type iter : material->GetLazyProperties()) { const std::string name = iter.first; if (name.empty()) { @@ -317,7 +317,7 @@ Ref<StandardMaterial3D> FBXMaterial::import_material(ImportState &state) { ERR_CONTINUE_MSG(desc == PROPERTY_DESC_NOT_FOUND, "The FBX material parameter: `" + String(name.c_str()) + "` was not recognized. Please open an issue so we can add the support to it."); - const FBXDocParser::PropertyTable *tbl = material->Props(); + const FBXDocParser::PropertyTable *tbl = material; FBXDocParser::PropertyPtr prop = tbl->Get(name); ERR_CONTINUE_MSG(prop == nullptr, "This file may be corrupted because is not possible to extract the material parameter: " + String(name.c_str())); diff --git a/modules/fbx/data/fbx_mesh_data.cpp b/modules/fbx/data/fbx_mesh_data.cpp index b088dd8640..304d1598f6 100644 --- a/modules/fbx/data/fbx_mesh_data.cpp +++ b/modules/fbx/data/fbx_mesh_data.cpp @@ -101,20 +101,6 @@ HashMap<int, Vector2> collect_uv(const Vector<VertexData<Vector2>> *p_data, Hash return collection; } -typedef int Vertex; -typedef int SurfaceId; -typedef int PolygonId; -typedef int DataIndex; - -struct SurfaceData { - Ref<SurfaceTool> surface_tool; - OrderedHashMap<Vertex, int> lookup_table; // proposed fix is to replace lookup_table[vertex_id] to give the position of the vertices_map[int] index. - LocalVector<Vertex> vertices_map; // this must be ordered the same as insertion <-- slow to do find() operation. - Ref<Material> material; - HashMap<PolygonId, Vector<DataIndex>> surface_polygon_vertex; - Array morphs; -}; - EditorSceneImporterMeshNode3D *FBXMeshData::create_fbx_mesh(const ImportState &state, const FBXDocParser::MeshGeometry *p_mesh_geometry, const FBXDocParser::Model *model, bool use_compression) { mesh_geometry = p_mesh_geometry; // todo: make this just use a uint64_t FBX ID this is a copy of our original materials unfortunately. @@ -307,11 +293,9 @@ EditorSceneImporterMeshNode3D *FBXMeshData::create_fbx_mesh(const ImportState &s // Triangulate the various polygons and add the indices. for (const PolygonId *polygon_id = surface->surface_polygon_vertex.next(nullptr); polygon_id != nullptr; polygon_id = surface->surface_polygon_vertex.next(polygon_id)) { const Vector<DataIndex> *indices = surface->surface_polygon_vertex.getptr(*polygon_id); - triangulate_polygon( - surface->surface_tool, + surface, *indices, - surface->vertices_map, vertices); } } @@ -336,7 +320,7 @@ EditorSceneImporterMeshNode3D *FBXMeshData::create_fbx_mesh(const ImportState &s morph_st->begin(Mesh::PRIMITIVE_TRIANGLES); for (unsigned int vi = 0; vi < surface->vertices_map.size(); vi += 1) { - const Vertex vertex = surface->vertices_map[vi]; + const Vertex &vertex = surface->vertices_map[vi]; add_vertex( state, morph_st, @@ -398,6 +382,9 @@ EditorSceneImporterMeshNode3D *FBXMeshData::create_fbx_mesh(const ImportState &s EditorSceneImporterMeshNode3D *godot_mesh = memnew(EditorSceneImporterMeshNode3D); godot_mesh->set_mesh(mesh); + const String name = ImportUtils::FBXNodeToName(model->Name()); + godot_mesh->set_name(name); // hurry up compiling >.< + mesh->set_name("mesh3d-" + name); return godot_mesh; } @@ -816,8 +803,10 @@ void FBXMeshData::add_vertex( p_surface_tool->add_vertex((p_vertices_position[p_vertex] + p_morph_value) * p_scale); } -void FBXMeshData::triangulate_polygon(Ref<SurfaceTool> st, Vector<int> p_polygon_vertex, const Vector<Vertex> p_surface_vertex_map, const std::vector<Vector3> &p_vertices) const { +void FBXMeshData::triangulate_polygon(SurfaceData *surface, const Vector<int> &p_polygon_vertex, const std::vector<Vector3> &p_vertices) const { + Ref<SurfaceTool> st(surface->surface_tool); const int polygon_vertex_count = p_polygon_vertex.size(); + //const Vector<Vertex>& p_surface_vertex_map if (polygon_vertex_count == 1) { // point to triangle st->add_index(p_polygon_vertex[0]); @@ -856,9 +845,9 @@ void FBXMeshData::triangulate_polygon(Ref<SurfaceTool> st, Vector<int> p_polygon is_simple_convex = true; Vector3 first_vec; for (int i = 0; i < polygon_vertex_count; i += 1) { - const Vector3 p1 = p_vertices[p_surface_vertex_map[p_polygon_vertex[i]]]; - const Vector3 p2 = p_vertices[p_surface_vertex_map[p_polygon_vertex[(i + 1) % polygon_vertex_count]]]; - const Vector3 p3 = p_vertices[p_surface_vertex_map[p_polygon_vertex[(i + 2) % polygon_vertex_count]]]; + const Vector3 p1 = p_vertices[surface->vertices_map[p_polygon_vertex[i]]]; + const Vector3 p2 = p_vertices[surface->vertices_map[p_polygon_vertex[(i + 1) % polygon_vertex_count]]]; + const Vector3 p3 = p_vertices[surface->vertices_map[p_polygon_vertex[(i + 2) % polygon_vertex_count]]]; const Vector3 edge1 = p1 - p2; const Vector3 edge2 = p3 - p2; @@ -893,7 +882,7 @@ void FBXMeshData::triangulate_polygon(Ref<SurfaceTool> st, Vector<int> p_polygon std::vector<Vector3> poly_vertices(polygon_vertex_count); for (int i = 0; i < polygon_vertex_count; i += 1) { - poly_vertices[i] = p_vertices[p_surface_vertex_map[p_polygon_vertex[i]]]; + poly_vertices[i] = p_vertices[surface->vertices_map[p_polygon_vertex[i]]]; } const Vector3 poly_norm = get_poly_normal(poly_vertices); diff --git a/modules/fbx/data/fbx_mesh_data.h b/modules/fbx/data/fbx_mesh_data.h index 77510ff2ec..575f833584 100644 --- a/modules/fbx/data/fbx_mesh_data.h +++ b/modules/fbx/data/fbx_mesh_data.h @@ -32,6 +32,8 @@ #define FBX_MESH_DATA_H #include "core/templates/hash_map.h" +#include "core/templates/local_vector.h" +#include "core/templates/ordered_hash_map.h" #include "editor/import/resource_importer_scene.h" #include "editor/import/scene_importer_mesh_node_3d.h" #include "scene/3d/mesh_instance_3d.h" @@ -47,6 +49,20 @@ struct FBXMeshData; struct FBXBone; struct ImportState; +typedef int Vertex; +typedef int SurfaceId; +typedef int PolygonId; +typedef int DataIndex; + +struct SurfaceData { + Ref<SurfaceTool> surface_tool; + OrderedHashMap<Vertex, int> lookup_table; // proposed fix is to replace lookup_table[vertex_id] to give the position of the vertices_map[int] index. + LocalVector<Vertex> vertices_map; // this must be ordered the same as insertion <-- slow to do find() operation. + Ref<Material> material; + HashMap<PolygonId, Vector<DataIndex>> surface_polygon_vertex; + Array morphs; +}; + struct VertexWeightMapping { Vector<real_t> weights; Vector<int> bones; @@ -127,7 +143,7 @@ private: const Vector3 &p_morph_value = Vector3(), const Vector3 &p_morph_normal = Vector3()); - void triangulate_polygon(Ref<SurfaceTool> st, Vector<int> p_polygon_vertex, Vector<int> p_surface_vertex_map, const std::vector<Vector3> &p_vertices) const; + void triangulate_polygon(SurfaceData *surface, const Vector<int> &p_polygon_vertex, const std::vector<Vector3> &p_vertices) const; /// This function is responsible to convert the FBX polygon vertex to /// vertex index. diff --git a/modules/fbx/data/pivot_transform.cpp b/modules/fbx/data/pivot_transform.cpp index 1895af6f9f..f4055c830f 100644 --- a/modules/fbx/data/pivot_transform.cpp +++ b/modules/fbx/data/pivot_transform.cpp @@ -33,7 +33,7 @@ #include "tools/import_utils.h" void PivotTransform::ReadTransformChain() { - const FBXDocParser::PropertyTable *props = fbx_model->Props(); + const FBXDocParser::PropertyTable *props = fbx_model; const FBXDocParser::Model::RotOrder &rot = fbx_model->RotationOrder(); const FBXDocParser::TransformInheritance &inheritType = fbx_model->InheritType(); inherit_type = inheritType; // copy the inherit type we need it in the second step. diff --git a/modules/fbx/editor_scene_importer_fbx.cpp b/modules/fbx/editor_scene_importer_fbx.cpp index 55d524883f..b23a58a414 100644 --- a/modules/fbx/editor_scene_importer_fbx.cpp +++ b/modules/fbx/editor_scene_importer_fbx.cpp @@ -44,7 +44,6 @@ #include "scene/3d/bone_attachment_3d.h" #include "scene/3d/camera_3d.h" #include "scene/3d/light_3d.h" -#include "scene/3d/mesh_instance_3d.h" #include "scene/main/node.h" #include "scene/resources/material.h" @@ -121,15 +120,27 @@ Node3D *EditorSceneImporterFBX::import_scene(const String &p_path, uint32_t p_fl print_verbose("[doc] opening fbx file: " + p_path); print_verbose("[doc] fbx header: " + fbx_header_string); + bool corrupt = false; // safer to check this way as there can be different formatted headers if (fbx_header_string.find("Kaydara FBX Binary", 0) != -1) { is_binary = true; print_verbose("[doc] is binary"); - FBXDocParser::TokenizeBinary(tokens, (const char *)data.ptrw(), (size_t)data.size()); + + FBXDocParser::TokenizeBinary(tokens, (const char *)data.ptrw(), (size_t)data.size(), corrupt); + } else { print_verbose("[doc] is ascii"); - FBXDocParser::Tokenize(tokens, (const char *)data.ptrw(), (size_t)data.size()); + FBXDocParser::Tokenize(tokens, (const char *)data.ptrw(), (size_t)data.size(), corrupt); + } + + if (corrupt) { + for (FBXDocParser::TokenPtr token : tokens) { + delete token; + } + tokens.clear(); + ERR_PRINT(vformat("Cannot import FBX file: %s the file is corrupt so we safely exited parsing the file.", p_path)); + return memnew(Node3D); } // The import process explained: @@ -141,6 +152,16 @@ Node3D *EditorSceneImporterFBX::import_scene(const String &p_path, uint32_t p_fl // use this information to construct a very rudimentary // parse-tree representing the FBX scope structure FBXDocParser::Parser parser(tokens, is_binary); + + if (parser.IsCorrupt()) { + for (FBXDocParser::TokenPtr token : tokens) { + delete token; + } + tokens.clear(); + ERR_PRINT(vformat("Cannot import FBX file: %s the file is corrupt so we safely exited parsing the file.", p_path)); + return memnew(Node3D); + } + FBXDocParser::ImportSettings settings; settings.strictMode = false; @@ -153,12 +174,10 @@ Node3D *EditorSceneImporterFBX::import_scene(const String &p_path, uint32_t p_fl // safety for version handling if (doc.IsSafeToImport()) { bool is_blender_fbx = false; - //const FBXDocParser::PropertyPtr app_vendor = p_document->GlobalSettingsPtr()->Props() - // p_document->Creator() - const FBXDocParser::PropertyTable *import_props = doc.GetMetadataProperties(); - const FBXDocParser::PropertyPtr app_name = import_props->Get("Original|ApplicationName"); - const FBXDocParser::PropertyPtr app_vendor = import_props->Get("Original|ApplicationVendor"); - const FBXDocParser::PropertyPtr app_version = import_props->Get("Original|ApplicationVersion"); + const FBXDocParser::PropertyTable &import_props = doc.GetMetadataProperties(); + const FBXDocParser::PropertyPtr app_name = import_props.Get("Original|ApplicationName"); + const FBXDocParser::PropertyPtr app_vendor = import_props.Get("Original|ApplicationVendor"); + const FBXDocParser::PropertyPtr app_version = import_props.Get("Original|ApplicationVersion"); // if (app_name) { const FBXDocParser::TypedProperty<std::string> *app_name_string = dynamic_cast<const FBXDocParser::TypedProperty<std::string> *>(app_name); @@ -200,6 +219,11 @@ Node3D *EditorSceneImporterFBX::import_scene(const String &p_path, uint32_t p_fl return spatial; } else { + for (FBXDocParser::TokenPtr token : tokens) { + delete token; + } + tokens.clear(); + ERR_PRINT(vformat("Cannot import FBX file: %s. It uses file format %d which is unsupported by Godot. Please re-export it or convert it to a newer format.", p_path, doc.FBXVersion())); } } @@ -892,7 +916,7 @@ Node3D *EditorSceneImporterFBX::_generate_scene( uint64_t target_id = target->ID(); String target_name = ImportUtils::FBXNodeToName(target->Name()); - const FBXDocParser::PropertyTable *properties = curve_node->Props(); + const FBXDocParser::PropertyTable *properties = curve_node; bool got_x = false, got_y = false, got_z = false; float offset_x = FBXDocParser::PropertyGet<float>(properties, "d|X", got_x); float offset_y = FBXDocParser::PropertyGet<float>(properties, "d|Y", got_y); @@ -1047,7 +1071,7 @@ Node3D *EditorSceneImporterFBX::_generate_scene( Ref<FBXNode> target_node = state.fbx_target_map[target_id]; const FBXDocParser::Model *model = target_node->fbx_model; - const FBXDocParser::PropertyTable *props = model->Props(); + const FBXDocParser::PropertyTable *props = dynamic_cast<const FBXDocParser::PropertyTable *>(model); Map<StringName, FBXTrack> &track_data = track->value(); FBXTrack &translation_keys = track_data[StringName("T")]; diff --git a/modules/fbx/fbx_parser/FBXAnimation.cpp b/modules/fbx/fbx_parser/FBXAnimation.cpp index 4ab5edebb1..1690df6943 100644 --- a/modules/fbx/fbx_parser/FBXAnimation.cpp +++ b/modules/fbx/fbx_parser/FBXAnimation.cpp @@ -130,9 +130,7 @@ AnimationCurve::~AnimationCurve() { AnimationCurveNode::AnimationCurveNode(uint64_t id, const ElementPtr element, const std::string &name, const Document &doc, const char *const *target_prop_whitelist /*= NULL*/, size_t whitelist_size /*= 0*/) : - Object(id, element, name), doc(doc) { - const ScopePtr sc = GetRequiredScope(element); - + Object(id, element, name), target(), doc(doc) { // find target node const char *whitelist[] = { "Model", "NodeAttribute", "Deformer" }; const std::vector<const Connection *> &conns = doc.GetConnectionsBySourceSequenced(ID(), whitelist, 3); @@ -154,8 +152,6 @@ AnimationCurveNode::AnimationCurveNode(uint64_t id, const ElementPtr element, co prop = con->PropertyName(); break; } - - props = GetPropertyTable(doc, "AnimationCurveNode.FbxAnimCurveNode", element, sc, false); } // ------------------------------------------------------------------------------------------------ @@ -187,10 +183,6 @@ const AnimationMap &AnimationCurveNode::Curves() const { // ------------------------------------------------------------------------------------------------ AnimationLayer::AnimationLayer(uint64_t id, const ElementPtr element, const std::string &name, const Document &doc) : Object(id, element, name), doc(doc) { - const ScopePtr sc = GetRequiredScope(element); - - // note: the props table here bears little importance and is usually absent - props = GetPropertyTable(doc, "AnimationLayer.FbxAnimLayer", element, sc, true); } // ------------------------------------------------------------------------------------------------ @@ -248,11 +240,6 @@ const AnimationCurveNodeList AnimationLayer::Nodes(const char *const *target_pro // ------------------------------------------------------------------------------------------------ AnimationStack::AnimationStack(uint64_t id, const ElementPtr element, const std::string &name, const Document &doc) : Object(id, element, name) { - const ScopePtr sc = GetRequiredScope(element); - - // note: we don't currently use any of these properties so we shouldn't bother if it is missing - props = GetPropertyTable(doc, "AnimationStack.FbxAnimStack", element, sc, true); - // resolve attached animation layers const std::vector<const Connection *> &conns = doc.GetConnectionsByDestinationSequenced(ID(), "AnimationLayer"); layers.reserve(conns.size()); @@ -282,9 +269,5 @@ AnimationStack::AnimationStack(uint64_t id, const ElementPtr element, const std: // ------------------------------------------------------------------------------------------------ AnimationStack::~AnimationStack() { - if (props != nullptr) { - delete props; - props = nullptr; - } } } // namespace FBXDocParser diff --git a/modules/fbx/fbx_parser/FBXBinaryTokenizer.cpp b/modules/fbx/fbx_parser/FBXBinaryTokenizer.cpp index 1d2b7765c5..1eee10b251 100644 --- a/modules/fbx/fbx_parser/FBXBinaryTokenizer.cpp +++ b/modules/fbx/fbx_parser/FBXBinaryTokenizer.cpp @@ -130,6 +130,7 @@ Token::Token(const char *sbegin, const char *send, TokenType type, size_t offset line(offset), column(BINARY_MARKER) { #ifdef DEBUG_ENABLED + // contents is bad.. :/ contents = std::string(sbegin, static_cast<size_t>(send - sbegin)); #endif // calc length @@ -232,9 +233,11 @@ unsigned int ReadString(const char *&sbegin_out, const char *&send_out, const ch } // ------------------------------------------------------------------------------------------------ -void ReadData(const char *&sbegin_out, const char *&send_out, const char *input, const char *&cursor, const char *end) { +void ReadData(const char *&sbegin_out, const char *&send_out, const char *input, const char *&cursor, const char *end, bool &corrupt) { if (Offset(cursor, end) < 1) { TokenizeError("cannot ReadData, out of bounds reading length", input, cursor); + corrupt = true; + return; } const char type = *cursor; @@ -328,9 +331,7 @@ void ReadData(const char *&sbegin_out, const char *&send_out, const char *input, } cursor += comp_len; break; - } - - // string + } // string case 'S': { const char *sb, *se; // 0 characters can legally happen in such strings @@ -338,11 +339,15 @@ void ReadData(const char *&sbegin_out, const char *&send_out, const char *input, break; } default: + corrupt = true; // must exit TokenizeError("cannot ReadData, unexpected type code: " + std::string(&type, 1), input, cursor); + return; } if (cursor > end) { + corrupt = true; // must exit TokenizeError("cannot ReadData, the remaining size is too small for the data type: " + std::string(&type, 1), input, cursor); + return; } // the type code is contained in the returned range @@ -350,7 +355,7 @@ void ReadData(const char *&sbegin_out, const char *&send_out, const char *input, } // ------------------------------------------------------------------------------------------------ -bool ReadScope(TokenList &output_tokens, const char *input, const char *&cursor, const char *end, bool const is64bits) { +bool ReadScope(TokenList &output_tokens, const char *input, const char *&cursor, const char *end, bool const is64bits, bool &corrupt) { // the first word contains the offset at which this block ends const uint64_t end_offset = is64bits ? ReadDoubleWord(input, cursor, end) : ReadWord(input, cursor, end); @@ -364,8 +369,12 @@ bool ReadScope(TokenList &output_tokens, const char *input, const char *&cursor, if (end_offset > Offset(input, end)) { TokenizeError("block offset is out of range", input, cursor); + corrupt = true; + return false; } else if (end_offset < Offset(input, cursor)) { TokenizeError("block offset is negative out of range", input, cursor); + corrupt = true; + return false; } // the second data word contains the number of properties in the scope @@ -375,7 +384,7 @@ bool ReadScope(TokenList &output_tokens, const char *input, const char *&cursor, const uint64_t prop_length = is64bits ? ReadDoubleWord(input, cursor, end) : ReadWord(input, cursor, end); // now comes the name of the scope/key - const char *sbeg, *send; + const char *sbeg = nullptr, *send = nullptr; ReadString(sbeg, send, input, cursor, end); output_tokens.push_back(new_Token(sbeg, send, TokenType_KEY, Offset(input, cursor))); @@ -383,7 +392,10 @@ bool ReadScope(TokenList &output_tokens, const char *input, const char *&cursor, // now come the individual properties const char *begin_cursor = cursor; for (unsigned int i = 0; i < prop_count; ++i) { - ReadData(sbeg, send, input, cursor, begin_cursor + prop_length); + ReadData(sbeg, send, input, cursor, begin_cursor + prop_length, corrupt); + if (corrupt) { + return false; + } output_tokens.push_back(new_Token(sbeg, send, TokenType_DATA, Offset(input, cursor))); @@ -394,6 +406,8 @@ bool ReadScope(TokenList &output_tokens, const char *input, const char *&cursor, if (Offset(begin_cursor, cursor) != prop_length) { TokenizeError("property length not reached, something is wrong", input, cursor); + corrupt = true; + return false; } // at the end of each nested block, there is a NUL record to indicate @@ -410,13 +424,18 @@ bool ReadScope(TokenList &output_tokens, const char *input, const char *&cursor, // XXX this is vulnerable to stack overflowing .. while (Offset(input, cursor) < end_offset - sentinel_block_length) { - ReadScope(output_tokens, input, cursor, input + end_offset - sentinel_block_length, is64bits); + ReadScope(output_tokens, input, cursor, input + end_offset - sentinel_block_length, is64bits, corrupt); + if (corrupt) { + return false; + } } output_tokens.push_back(new_Token(cursor, cursor + 1, TokenType_CLOSE_BRACKET, Offset(input, cursor))); for (unsigned int i = 0; i < sentinel_block_length; ++i) { if (cursor[i] != '\0') { TokenizeError("failed to read nested block sentinel, expected all bytes to be 0", input, cursor); + corrupt = true; + return false; } } cursor += sentinel_block_length; @@ -424,6 +443,8 @@ bool ReadScope(TokenList &output_tokens, const char *input, const char *&cursor, if (Offset(input, cursor) != end_offset) { TokenizeError("scope length not reached, something is wrong", input, cursor); + corrupt = true; + return false; } return true; @@ -432,7 +453,7 @@ bool ReadScope(TokenList &output_tokens, const char *input, const char *&cursor, // ------------------------------------------------------------------------------------------------ // TODO: Test FBX Binary files newer than the 7500 version to check if the 64 bits address behaviour is consistent -void TokenizeBinary(TokenList &output_tokens, const char *input, size_t length) { +void TokenizeBinary(TokenList &output_tokens, const char *input, size_t length, bool &corrupt) { if (length < 0x1b) { //TokenizeError("file is too short",0); } @@ -459,7 +480,7 @@ void TokenizeBinary(TokenList &output_tokens, const char *input, size_t length) const bool is64bits = version >= 7500; const char *end = input + length; while (cursor < end) { - if (!ReadScope(output_tokens, input, cursor, input + length, is64bits)) { + if (!ReadScope(output_tokens, input, cursor, input + length, is64bits, corrupt)) { break; } } diff --git a/modules/fbx/fbx_parser/FBXDeformer.cpp b/modules/fbx/fbx_parser/FBXDeformer.cpp index 4b774e6b2a..039718ae15 100644 --- a/modules/fbx/fbx_parser/FBXDeformer.cpp +++ b/modules/fbx/fbx_parser/FBXDeformer.cpp @@ -89,10 +89,6 @@ using namespace Util; // ------------------------------------------------------------------------------------------------ Deformer::Deformer(uint64_t id, const ElementPtr element, const Document &doc, const std::string &name) : Object(id, element, name) { - const ScopePtr sc = GetRequiredScope(element); - - const std::string &classname = ParseTokenAsString(GetRequiredToken(element, 2)); - props = GetPropertyTable(doc, "Deformer.Fbx" + classname, element, sc, true); } // ------------------------------------------------------------------------------------------------ @@ -101,10 +97,6 @@ Deformer::~Deformer() { Constraint::Constraint(uint64_t id, const ElementPtr element, const Document &doc, const std::string &name) : Object(id, element, name) { - const ScopePtr sc = GetRequiredScope(element); - const std::string &classname = ParseTokenAsString(GetRequiredToken(element, 2)); - // used something.fbx as this is a cache name. - props = GetPropertyTable(doc, "Something.Fbx" + classname, element, sc, true); } Constraint::~Constraint() { diff --git a/modules/fbx/fbx_parser/FBXDocument.cpp b/modules/fbx/fbx_parser/FBXDocument.cpp index d156db201b..bb85d6ff7c 100644 --- a/modules/fbx/fbx_parser/FBXDocument.cpp +++ b/modules/fbx/fbx_parser/FBXDocument.cpp @@ -228,7 +228,7 @@ ObjectPtr LazyObject::LoadObject() { // ------------------------------------------------------------------------------------------------ Object::Object(uint64_t id, const ElementPtr element, const std::string &name) : - element(element), name(name), id(id) { + PropertyTable(element), element(element), name(name), id(id) { } // ------------------------------------------------------------------------------------------------ @@ -237,17 +237,13 @@ Object::~Object() { } // ------------------------------------------------------------------------------------------------ -FileGlobalSettings::FileGlobalSettings(const Document &doc, const PropertyTable *props) : - props(props), doc(doc) { +FileGlobalSettings::FileGlobalSettings(const Document &doc) : + PropertyTable(), doc(doc) { // empty } // ------------------------------------------------------------------------------------------------ FileGlobalSettings::~FileGlobalSettings() { - if (props != nullptr) { - delete props; - props = nullptr; - } } // ------------------------------------------------------------------------------------------------ @@ -287,15 +283,12 @@ Document::~Document() { delete v.second; } - if (metadata_properties != nullptr) { - delete metadata_properties; - } // clear globals import pointer globals.reset(); } // ------------------------------------------------------------------------------------------------ -static const unsigned int LowerSupportedVersion = 7300; +static const unsigned int LowerSupportedVersion = 7100; static const unsigned int UpperSupportedVersion = 7700; bool Document::ReadHeader() { @@ -306,6 +299,11 @@ bool Document::ReadHeader() { DOMError("no FBXHeaderExtension dictionary found"); } + if (parser.IsCorrupt()) { + DOMError("File is corrupt"); + return false; + } + const ScopePtr shead = ehead->Compound(); fbxVersion = ParseTokenAsInt(GetRequiredToken(GetRequiredElement(shead, "FBXVersion", ehead), 0)); @@ -325,18 +323,11 @@ bool Document::ReadHeader() { creator = ParseTokenAsString(GetRequiredToken(ecreator, 0)); } - // // Scene Info - // - const ElementPtr scene_info = shead->GetElement("SceneInfo"); if (scene_info) { - PropertyTable *fileExportProps = const_cast<PropertyTable *>(GetPropertyTable(*this, "", scene_info, scene_info->Compound(), true)); - - if (fileExportProps) { - metadata_properties = fileExportProps; - } + metadata_properties.Setup(scene_info); } const ElementPtr etimestamp = shead->GetElement("CreationTimeStamp"); @@ -358,23 +349,7 @@ bool Document::ReadHeader() { void Document::ReadGlobalSettings() { ERR_FAIL_COND_MSG(globals != nullptr, "Global settings is already setup this is a serious error and should be reported"); - const ScopePtr sc = parser.GetRootScope(); - const ElementPtr ehead = sc->GetElement("GlobalSettings"); - if (nullptr == ehead || !ehead->Compound()) { - DOMWarning("no GlobalSettings dictionary found"); - globals = std::make_shared<FileGlobalSettings>(*this, new PropertyTable()); - return; - } - - const PropertyTable *props = GetPropertyTable(*this, "", ehead, ehead->Compound(), true); - - //double v = PropertyGet<float>( *props, std::string("UnitScaleFactor"), 1.0 ); - - if (!props) { - DOMError("GlobalSettings dictionary contains no property table"); - } - - globals = std::make_shared<FileGlobalSettings>(*this, props); + globals = std::make_shared<FileGlobalSettings>(*this); } // ------------------------------------------------------------------------------------------------ @@ -445,58 +420,6 @@ void Document::ReadObjects() { // ------------------------------------------------------------------------------------------------ void Document::ReadPropertyTemplates() { - const ScopePtr sc = parser.GetRootScope(); - // read property templates from "Definitions" section - const ElementPtr edefs = sc->GetElement("Definitions"); - if (!edefs || !edefs->Compound()) { - DOMWarning("no Definitions dictionary found"); - return; - } - - const ScopePtr sdefs = edefs->Compound(); - const ElementCollection otypes = sdefs->GetCollection("ObjectType"); - for (ElementMap::const_iterator it = otypes.first; it != otypes.second; ++it) { - const ElementPtr el = (*it).second; - const ScopePtr sc_2 = el->Compound(); - if (!sc_2) { - DOMWarning("expected nested scope in ObjectType, ignoring", el); - continue; - } - - const TokenList &tok = el->Tokens(); - if (tok.empty()) { - DOMWarning("expected name for ObjectType element, ignoring", el); - continue; - } - - const std::string &oname = ParseTokenAsString(tok[0]); - - const ElementCollection templs = sc_2->GetCollection("PropertyTemplate"); - for (ElementMap::const_iterator iter = templs.first; iter != templs.second; ++iter) { - const ElementPtr el_2 = (*iter).second; - const ScopePtr sc_3 = el_2->Compound(); - if (!sc_3) { - DOMWarning("expected nested scope in PropertyTemplate, ignoring", el); - continue; - } - - const TokenList &tok_2 = el_2->Tokens(); - if (tok_2.empty()) { - DOMWarning("expected name for PropertyTemplate element, ignoring", el); - continue; - } - - const std::string &pname = ParseTokenAsString(tok_2[0]); - - const ElementPtr Properties70 = sc_3->GetElement("Properties70"); - if (Properties70) { - // PropertyTable(const ElementPtr element, const PropertyTable* templateProps); - const PropertyTable *props = new PropertyTable(Properties70, nullptr); - - templates[oname + "." + pname] = props; - } - } - } } // ------------------------------------------------------------------------------------------------ diff --git a/modules/fbx/fbx_parser/FBXDocument.h b/modules/fbx/fbx_parser/FBXDocument.h index 20e635a6a4..49b7c11c31 100644 --- a/modules/fbx/fbx_parser/FBXDocument.h +++ b/modules/fbx/fbx_parser/FBXDocument.h @@ -130,7 +130,7 @@ private: }; /** Base class for in-memory (DOM) representations of FBX objects */ -class Object { +class Object : public PropertyTable { public: Object(uint64_t id, const ElementPtr element, const std::string &name); @@ -149,9 +149,9 @@ public: } protected: - const ElementPtr element; + const ElementPtr element = nullptr; const std::string name; - const uint64_t id = 0; + const uint64_t id; }; /** DOM class for generic FBX NoteAttribute blocks. NoteAttribute's just hold a property table, @@ -159,22 +159,13 @@ protected: class NodeAttribute : public Object { public: NodeAttribute(uint64_t id, const ElementPtr element, const Document &doc, const std::string &name); - virtual ~NodeAttribute(); - - const PropertyTable *Props() const { - return props; - } - -private: - const PropertyTable *props; }; /** DOM base class for FBX camera settings attached to a node */ class CameraSwitcher : public NodeAttribute { public: CameraSwitcher(uint64_t id, const ElementPtr element, const Document &doc, const std::string &name); - virtual ~CameraSwitcher(); int CameraID() const { @@ -190,26 +181,26 @@ public: } private: - int cameraId; + int cameraId = 0; std::string cameraName; std::string cameraIndexName; }; #define fbx_stringize(a) #a -#define fbx_simple_property(name, type, default_value) \ - type name() const { \ - return PropertyGet<type>(Props(), fbx_stringize(name), (default_value)); \ +#define fbx_simple_property(name, type, default_value) \ + type name() const { \ + return PropertyGet<type>(this, fbx_stringize(name), (default_value)); \ } // XXX improve logging -#define fbx_simple_enum_property(name, type, default_value) \ - type name() const { \ - const int ival = PropertyGet<int>(Props(), fbx_stringize(name), static_cast<int>(default_value)); \ - if (ival < 0 || ival >= AI_CONCAT(type, _MAX)) { \ - return static_cast<type>(default_value); \ - } \ - return static_cast<type>(ival); \ +#define fbx_simple_enum_property(name, type, default_value) \ + type name() const { \ + const int ival = PropertyGet<int>(this, fbx_stringize(name), static_cast<int>(default_value)); \ + if (ival < 0 || ival >= AI_CONCAT(type, _MAX)) { \ + return static_cast<type>(default_value); \ + } \ + return static_cast<type>(ival); \ } class FbxPoseNode; @@ -256,7 +247,7 @@ public: } private: - uint64_t target_id; + uint64_t target_id = 0; Transform transform; }; @@ -264,7 +255,6 @@ private: class Camera : public NodeAttribute { public: Camera(uint64_t id, const ElementPtr element, const Document &doc, const std::string &name); - virtual ~Camera(); fbx_simple_property(Position, Vector3, Vector3(0, 0, 0)); @@ -380,7 +370,6 @@ public: }; Model(uint64_t id, const ElementPtr element, const Document &doc, const std::string &name); - virtual ~Model(); fbx_simple_property(QuaternionInterpolate, int, 0); @@ -466,10 +455,6 @@ public: return culling; } - const PropertyTable *Props() const { - return props; - } - /** Get material links */ const std::vector<const Material *> &GetMaterials() const { return materials; @@ -498,13 +483,11 @@ private: std::string shading; std::string culling; - const PropertyTable *props = nullptr; }; class ModelLimbNode : public Model { public: ModelLimbNode(uint64_t id, const ElementPtr element, const Document &doc, const std::string &name); - virtual ~ModelLimbNode(); }; @@ -512,7 +495,6 @@ public: class Texture : public Object { public: Texture(uint64_t id, const ElementPtr element, const Document &doc, const std::string &name); - virtual ~Texture(); const std::string &Type() const { @@ -539,10 +521,6 @@ public: return uvScaling; } - const PropertyTable *Props() const { - return props; - } - // return a 4-tuple const unsigned int *Crop() const { return crop; @@ -560,10 +538,8 @@ private: std::string relativeFileName; std::string fileName; std::string alphaSource; - const PropertyTable *props = nullptr; unsigned int crop[4] = { 0 }; - const Video *media = nullptr; }; @@ -626,8 +602,8 @@ public: private: std::vector<const Texture *> textures; - BlendMode blendMode; - float alpha; + BlendMode blendMode = BlendMode::BlendMode_Additive; + float alpha = 0; }; typedef std::map<std::string, const Texture *> TextureMap; @@ -656,10 +632,6 @@ public: return relativeFileName; } - const PropertyTable *Props() const { - return props; - } - const uint8_t *Content() const { return content; } @@ -687,7 +659,6 @@ private: std::string type; std::string relativeFileName; std::string fileName; - const PropertyTable *props = nullptr; uint64_t contentLength = 0; uint8_t *content = nullptr; @@ -708,10 +679,6 @@ public: return multilayer; } - const PropertyTable *Props() const { - return props; - } - const TextureMap &Textures() const { return textures; } @@ -722,8 +689,7 @@ public: private: std::string shading; - bool multilayer; - const PropertyTable *props; + bool multilayer = false; TextureMap textures; LayeredTextureMap layeredTextures; @@ -791,10 +757,6 @@ public: virtual ~AnimationCurveNode(); - const PropertyTable *Props() const { - return props; - } - const AnimationMap &Curves() const; /** Object the curve is assigned to, this can be NULL if the @@ -819,7 +781,6 @@ public: private: Object *target = nullptr; - const PropertyTable *props; mutable AnimationMap curves; std::string prop; const Document &doc; @@ -837,18 +798,12 @@ public: AnimationLayer(uint64_t id, const ElementPtr element, const std::string &name, const Document &doc); virtual ~AnimationLayer(); - const PropertyTable *Props() const { - //ai_assert(props.get()); - return props; - } - /* the optional white list specifies a list of property names for which the caller wants animations for. Curves not matching this list will not be added to the animation layer. */ const AnimationCurveNodeList Nodes(const char *const *target_prop_whitelist = nullptr, size_t whitelist_size = 0) const; private: - const PropertyTable *props; const Document &doc; }; @@ -863,16 +818,11 @@ public: fbx_simple_property(ReferenceStart, int64_t, 0L); fbx_simple_property(ReferenceStop, int64_t, 0L); - const PropertyTable *Props() const { - return props; - } - const AnimationLayerList &Layers() const { return layers; } private: - const PropertyTable *props = nullptr; AnimationLayerList layers; }; @@ -881,14 +831,6 @@ class Deformer : public Object { public: Deformer(uint64_t id, const ElementPtr element, const Document &doc, const std::string &name); virtual ~Deformer(); - - const PropertyTable *Props() const { - //ai_assert(props.get()); - return props; - } - -private: - const PropertyTable *props; }; /** Constraints are from Maya they can help us with BoneAttachments :) **/ @@ -896,9 +838,6 @@ class Constraint : public Object { public: Constraint(uint64_t id, const ElementPtr element, const Document &doc, const std::string &name); virtual ~Constraint(); - -private: - const PropertyTable *props; }; typedef std::vector<float> WeightArray; @@ -924,7 +863,7 @@ public: } private: - float percent; + float percent = 0; WeightArray fullWeights; std::vector<const ShapeGeometry *> shapeGeometries; }; @@ -1006,7 +945,7 @@ private: Transform transformLink; Transform transformAssociateModel; SkinLinkMode link_mode; - bool valid_transformAssociateModel; + bool valid_transformAssociateModel = false; const Model *node = nullptr; }; @@ -1037,8 +976,8 @@ public: } private: - float accuracy; - SkinType skinType; + float accuracy = 0; + SkinType skinType = SkinType::Skin_Linear; std::vector<const Cluster *> clusters; }; @@ -1087,10 +1026,10 @@ public: } public: - uint64_t insertionOrder; + uint64_t insertionOrder = 0; const std::string prop; - uint64_t src, dest; + uint64_t src = 0, dest = 0; const Document &doc; }; @@ -1105,15 +1044,10 @@ typedef std::multimap<uint64_t, const Connection *> ConnectionMap; /** DOM class for global document settings, a single instance per document can * be accessed via Document.Globals(). */ -class FileGlobalSettings { +class FileGlobalSettings : public PropertyTable { public: - FileGlobalSettings(const Document &doc, const PropertyTable *props); - - ~FileGlobalSettings(); - - const PropertyTable *Props() const { - return props; - } + FileGlobalSettings(const Document &doc); + virtual ~FileGlobalSettings(); const Document &GetDocument() const { return doc; @@ -1158,7 +1092,6 @@ public: fbx_simple_property(CustomFrameRate, float, -1.0f); private: - const PropertyTable *props = nullptr; const Document &doc; }; @@ -1196,7 +1129,7 @@ public: return globals.get(); } - const PropertyTable *GetMetadataProperties() const { + const PropertyTable &GetMetadataProperties() const { return metadata_properties; } @@ -1293,7 +1226,7 @@ private: std::vector<uint64_t> materials; std::vector<uint64_t> skins; mutable std::vector<const AnimationStack *> animationStacksResolved; - PropertyTable *metadata_properties = nullptr; + PropertyTable metadata_properties; std::shared_ptr<FileGlobalSettings> globals = nullptr; }; } // namespace FBXDocParser diff --git a/modules/fbx/fbx_parser/FBXDocumentUtil.cpp b/modules/fbx/fbx_parser/FBXDocumentUtil.cpp index df50a32c39..3930e005c3 100644 --- a/modules/fbx/fbx_parser/FBXDocumentUtil.cpp +++ b/modules/fbx/fbx_parser/FBXDocumentUtil.cpp @@ -137,36 +137,5 @@ void DOMWarning(const std::string &message, const std::shared_ptr<Element> eleme print_verbose("[FBX-DOM] warning:" + String(message.c_str())); } -// ------------------------------------------------------------------------------------------------ -// fetch a property table and the corresponding property template -const PropertyTable *GetPropertyTable(const Document &doc, - const std::string &templateName, - const ElementPtr element, - const ScopePtr sc, - bool no_warn /*= false*/) { - // todo: make this an abstraction - const ElementPtr Properties70 = sc->GetElement("Properties70"); - const PropertyTable *templateProps = static_cast<const PropertyTable *>(nullptr); - - if (templateName.length()) { - PropertyTemplateMap::const_iterator it = doc.Templates().find(templateName); - if (it != doc.Templates().end()) { - templateProps = (*it).second; - } - } - - if (!Properties70 || !Properties70->Compound()) { - if (!no_warn) { - DOMWarning("property table (Properties70) not found", element); - } - if (templateProps) { - return new const PropertyTable(templateProps); - } else { - return new const PropertyTable(); - } - } - - return new PropertyTable(Properties70, templateProps); -} } // namespace Util } // namespace FBXDocParser diff --git a/modules/fbx/fbx_parser/FBXDocumentUtil.h b/modules/fbx/fbx_parser/FBXDocumentUtil.h index daa9de4a33..ba86191c4a 100644 --- a/modules/fbx/fbx_parser/FBXDocumentUtil.h +++ b/modules/fbx/fbx_parser/FBXDocumentUtil.h @@ -98,13 +98,6 @@ void DOMWarning(const std::string &message, const Element *element); void DOMWarning(const std::string &message, const std::shared_ptr<Token> token); void DOMWarning(const std::string &message, const std::shared_ptr<Element> element); -// fetch a property table and the corresponding property template -const PropertyTable *GetPropertyTable(const Document &doc, - const std::string &templateName, - const ElementPtr element, - const ScopePtr sc, - bool no_warn = false); - // ------------------------------------------------------------------------------------------------ template <typename T> const T *ProcessSimpleConnection(const Connection &con, diff --git a/modules/fbx/fbx_parser/FBXMaterial.cpp b/modules/fbx/fbx_parser/FBXMaterial.cpp index 219da1b2f4..08fff5714a 100644 --- a/modules/fbx/fbx_parser/FBXMaterial.cpp +++ b/modules/fbx/fbx_parser/FBXMaterial.cpp @@ -118,8 +118,6 @@ Material::Material(uint64_t id, const ElementPtr element, const Document &doc, c DOMWarning("shading mode not recognized: " + shading, element); } - props = GetPropertyTable(doc, templateName, element, sc); - // resolve texture links const std::vector<const Connection *> &conns = doc.GetConnectionsByDestinationSequenced(ID()); for (const Connection *con : conns) { @@ -163,10 +161,6 @@ Material::Material(uint64_t id, const ElementPtr element, const Document &doc, c // ------------------------------------------------------------------------------------------------ Material::~Material() { - if (props != nullptr) { - delete props; - props = nullptr; - } } // ------------------------------------------------------------------------------------------------ @@ -219,17 +213,15 @@ Texture::Texture(uint64_t id, const ElementPtr element, const Document &doc, con alphaSource = ParseTokenAsString(GetRequiredToken(Texture_Alpha_Source, 0)); } - props = GetPropertyTable(doc, "Texture.FbxFileTexture", element, sc); - // 3DS Max and FBX SDK use "Scaling" and "Translation" instead of "ModelUVScaling" and "ModelUVTranslation". Use these properties if available. - bool ok; - const Vector3 &scaling = PropertyGet<Vector3>(props, "Scaling", ok); + bool ok = true; + const Vector3 &scaling = PropertyGet<Vector3>(this, "Scaling", ok); if (ok) { uvScaling.x = scaling.x; uvScaling.y = scaling.y; } - const Vector3 &trans = PropertyGet<Vector3>(props, "Translation", ok); + const Vector3 &trans = PropertyGet<Vector3>(this, "Translation", ok); if (ok) { uvTrans.x = trans.x; uvTrans.y = trans.y; @@ -254,10 +246,6 @@ Texture::Texture(uint64_t id, const ElementPtr element, const Document &doc, con } Texture::~Texture() { - if (props != nullptr) { - delete props; - props = nullptr; - } } LayeredTexture::LayeredTexture(uint64_t id, const ElementPtr element, const Document & /*doc*/, const std::string &name) : @@ -390,18 +378,11 @@ Video::Video(uint64_t id, const ElementPtr element, const Document &doc, const s // runtimeError.what()); } } - - props = GetPropertyTable(doc, "Video.FbxVideo", element, sc); } Video::~Video() { if (content) { delete[] content; } - - if (props != nullptr) { - delete props; - props = nullptr; - } } } // namespace FBXDocParser diff --git a/modules/fbx/fbx_parser/FBXModel.cpp b/modules/fbx/fbx_parser/FBXModel.cpp index 767994441f..03c9de0c35 100644 --- a/modules/fbx/fbx_parser/FBXModel.cpp +++ b/modules/fbx/fbx_parser/FBXModel.cpp @@ -98,16 +98,11 @@ Model::Model(uint64_t id, const ElementPtr element, const Document &doc, const s culling = ParseTokenAsString(GetRequiredToken(Culling, 0)); } - props = GetPropertyTable(doc, "Model.FbxNode", element, sc); ResolveLinks(element, doc); } // ------------------------------------------------------------------------------------------------ Model::~Model() { - if (props != nullptr) { - delete props; - props = nullptr; - } } ModelLimbNode::ModelLimbNode(uint64_t id, const ElementPtr element, const Document &doc, const std::string &name) : diff --git a/modules/fbx/fbx_parser/FBXNodeAttribute.cpp b/modules/fbx/fbx_parser/FBXNodeAttribute.cpp index 2749fc9f4d..15184a0f5d 100644 --- a/modules/fbx/fbx_parser/FBXNodeAttribute.cpp +++ b/modules/fbx/fbx_parser/FBXNodeAttribute.cpp @@ -84,16 +84,7 @@ using namespace Util; // ------------------------------------------------------------------------------------------------ NodeAttribute::NodeAttribute(uint64_t id, const ElementPtr element, const Document &doc, const std::string &name) : - Object(id, element, name), props() { - const ScopePtr sc = GetRequiredScope(element); - - const std::string &classname = ParseTokenAsString(GetRequiredToken(element, 2)); - - // hack on the deriving type but Null/LimbNode attributes are the only case in which - // the property table is by design absent and no warning should be generated - // for it. - const bool is_null_or_limb = !strcmp(classname.c_str(), "Null") || !strcmp(classname.c_str(), "LimbNode"); - props = GetPropertyTable(doc, "NodeAttribute.Fbx" + classname, element, sc, is_null_or_limb); + Object(id, element, name) { } // ------------------------------------------------------------------------------------------------ diff --git a/modules/fbx/fbx_parser/FBXParser.cpp b/modules/fbx/fbx_parser/FBXParser.cpp index 166d98bb8c..82d532e0b8 100644 --- a/modules/fbx/fbx_parser/FBXParser.cpp +++ b/modules/fbx/fbx_parser/FBXParser.cpp @@ -131,6 +131,8 @@ Element::Element(const TokenPtr key_token, Parser &parser) : if (!n) { print_error("unexpected end of file, expected bracket, comma or key" + String(parser.LastToken()->StringContents().c_str())); + parser.corrupt = true; + return; } const TokenType ty = n->Type(); @@ -143,6 +145,8 @@ Element::Element(const TokenPtr key_token, Parser &parser) : if (ty != TokenType_OPEN_BRACKET && ty != TokenType_CLOSE_BRACKET && ty != TokenType_COMMA && ty != TokenType_KEY) { print_error("unexpected token; expected bracket, comma or key" + String(n->StringContents().c_str())); + parser.corrupt = true; + return; } } @@ -150,11 +154,17 @@ Element::Element(const TokenPtr key_token, Parser &parser) : compound = new_Scope(parser); parser.scopes.push_back(compound); + if (parser.corrupt) { + return; + } + // current token should be a TOK_CLOSE_BRACKET n = parser.CurrentToken(); if (n && n->Type() != TokenType_CLOSE_BRACKET) { print_error("expected closing bracket" + String(n->StringContents().c_str())); + parser.corrupt = true; + return; } parser.AdvanceToNextToken(); @@ -173,22 +183,31 @@ Scope::Scope(Parser &parser, bool topLevel) { TokenPtr t = parser.CurrentToken(); if (t->Type() != TokenType_OPEN_BRACKET) { print_error("expected open bracket" + String(t->StringContents().c_str())); + parser.corrupt = true; + return; } } TokenPtr n = parser.AdvanceToNextToken(); if (n == nullptr) { print_error("unexpected end of file"); + parser.corrupt = true; + return; } // note: empty scopes are allowed while (n && n->Type() != TokenType_CLOSE_BRACKET) { if (n->Type() != TokenType_KEY) { print_error("unexpected token, expected TOK_KEY" + String(n->StringContents().c_str())); + parser.corrupt = true; + return; } const std::string str = n->StringContents(); + if (parser.corrupt) { + return; + } // std::multimap<std::string, ElementPtr> (key and value) elements.insert(ElementMap::value_type(str, new_Element(n, parser))); @@ -216,7 +235,7 @@ Scope::~Scope() { // ------------------------------------------------------------------------------------------------ Parser::Parser(const TokenList &tokens, bool is_binary) : - tokens(tokens), cursor(tokens.begin()), is_binary(is_binary) { + corrupt(false), tokens(tokens), cursor(tokens.begin()), is_binary(is_binary) { root = new_Scope(*this, true); scopes.push_back(root); } @@ -1231,6 +1250,21 @@ ScopePtr GetRequiredScope(const ElementPtr el) { } // ------------------------------------------------------------------------------------------------ +// extract optional compound scope +ScopePtr GetOptionalScope(const ElementPtr el) { + if (el) { + ScopePtr s = el->Compound(); + TokenPtr token = el->KeyToken(); + + if (token && s) { + return s; + } + } + + return nullptr; +} + +// ------------------------------------------------------------------------------------------------ // get token at a particular index TokenPtr GetRequiredToken(const ElementPtr el, unsigned int index) { if (el) { diff --git a/modules/fbx/fbx_parser/FBXParser.h b/modules/fbx/fbx_parser/FBXParser.h index 37d27d3dca..bfbcb22ffa 100644 --- a/modules/fbx/fbx_parser/FBXParser.h +++ b/modules/fbx/fbx_parser/FBXParser.h @@ -199,6 +199,10 @@ public: return is_binary; } + bool IsCorrupt() const { + return corrupt; + } + private: friend class Scope; friend class Element; @@ -208,6 +212,7 @@ private: TokenPtr CurrentToken() const; private: + bool corrupt = false; ScopeList scopes; const TokenList &tokens; @@ -249,6 +254,8 @@ bool HasElement(const ScopePtr sc, const std::string &index); // extract a required element from a scope, abort if the element cannot be found ElementPtr GetRequiredElement(const ScopePtr sc, const std::string &index, const ElementPtr element = nullptr); ScopePtr GetRequiredScope(const ElementPtr el); // New in 2020. (less likely to destroy application) +ScopePtr GetOptionalScope(const ElementPtr el); // New in 2021. (even LESS likely to destroy application now) + ElementPtr GetOptionalElement(const ScopePtr sc, const std::string &index, const ElementPtr element = nullptr); // extract required compound scope ScopePtr GetRequiredScope(const ElementPtr el); diff --git a/modules/fbx/fbx_parser/FBXProperties.cpp b/modules/fbx/fbx_parser/FBXProperties.cpp index 84e71512d6..1b3f29ec04 100644 --- a/modules/fbx/fbx_parser/FBXProperties.cpp +++ b/modules/fbx/fbx_parser/FBXProperties.cpp @@ -145,19 +145,33 @@ std::string PeekPropertyName(const Element &element) { } // namespace // ------------------------------------------------------------------------------------------------ -PropertyTable::PropertyTable() { +PropertyTable::PropertyTable() : + element(nullptr) { } -// ------------------------------------------------------------------------------------------------ -PropertyTable::PropertyTable(const PropertyTable *templateProps) : - templateProps(templateProps), element() { +// Is used when dealing with FBX Objects not metadata. +PropertyTable::PropertyTable(const ElementPtr element) : + element(element) { + Setup(element); } // ------------------------------------------------------------------------------------------------ -PropertyTable::PropertyTable(const ElementPtr element, const PropertyTable *templateProps) : - templateProps(templateProps), element(element) { - const ScopePtr scope = GetRequiredScope(element); - ERR_FAIL_COND(!scope); +PropertyTable::~PropertyTable() { + for (PropertyMap::value_type &v : props) { + delete v.second; + } +} + +void PropertyTable::Setup(ElementPtr ptr) { + const ScopePtr sc = GetRequiredScope(ptr); + const ElementPtr Properties70 = sc->GetElement("Properties70"); + const ScopePtr scope = GetOptionalScope(Properties70); + + // no scope, no care. + if (!scope) { + return; // NOTE: this is not an error this is actually a Object, without properties, here we will nullptr it. + } + for (const ElementMap::value_type &v : scope->Elements()) { if (v.first != "P") { DOMWarning("expected only P elements in property table", v.second); @@ -182,13 +196,6 @@ PropertyTable::PropertyTable(const ElementPtr element, const PropertyTable *temp } // ------------------------------------------------------------------------------------------------ -PropertyTable::~PropertyTable() { - for (PropertyMap::value_type &v : props) { - delete v.second; - } -} - -// ------------------------------------------------------------------------------------------------ PropertyPtr PropertyTable::Get(const std::string &name) const { PropertyMap::const_iterator it = props.find(name); if (it == props.end()) { @@ -203,10 +210,6 @@ PropertyPtr PropertyTable::Get(const std::string &name) const { if (it == props.end()) { // check property template - if (templateProps) { - return templateProps->Get(name); - } - return nullptr; } } diff --git a/modules/fbx/fbx_parser/FBXProperties.h b/modules/fbx/fbx_parser/FBXProperties.h index 0595b25fa7..bfd27ac94e 100644 --- a/modules/fbx/fbx_parser/FBXProperties.h +++ b/modules/fbx/fbx_parser/FBXProperties.h @@ -137,36 +137,31 @@ class PropertyTable { public: // in-memory property table with no source element PropertyTable(); - PropertyTable(const PropertyTable *templateProps); - PropertyTable(const ElementPtr element, const PropertyTable *templateProps); - ~PropertyTable(); + PropertyTable(const ElementPtr element); + virtual ~PropertyTable(); PropertyPtr Get(const std::string &name) const; + void Setup(ElementPtr ptr); // PropertyTable's need not be coupled with FBX elements so this can be NULL - ElementPtr GetElement() const { + ElementPtr GetElement() { return element; } - PropertyMap &GetProperties() const { + PropertyMap &GetProperties() { return props; } - const LazyPropertyMap &GetLazyProperties() const { + const LazyPropertyMap &GetLazyProperties() { return lazyProps; } - const PropertyTable *TemplateProps() const { - return templateProps; - } - DirectPropertyMap GetUnparsedProperties() const; private: LazyPropertyMap lazyProps; mutable PropertyMap props; - const PropertyTable *templateProps = nullptr; - const ElementPtr element = nullptr; + ElementPtr element = nullptr; }; // ------------------------------------------------------------------------------------------------ @@ -191,16 +186,11 @@ template <typename T> inline T PropertyGet(const PropertyTable *in, const std::string &name, bool &result, bool useTemplate = false) { PropertyPtr prop = in->Get(name); if (nullptr == prop) { - if (!useTemplate) { - result = false; - return T(); - } - const PropertyTable *templ = in->TemplateProps(); - if (nullptr == templ) { + if (nullptr == in) { result = false; return T(); } - prop = templ->Get(name); + prop = in->Get(name); if (nullptr == prop) { result = false; return T(); diff --git a/modules/fbx/fbx_parser/FBXTokenizer.cpp b/modules/fbx/fbx_parser/FBXTokenizer.cpp index ea4568fe32..81c5b128e8 100644 --- a/modules/fbx/fbx_parser/FBXTokenizer.cpp +++ b/modules/fbx/fbx_parser/FBXTokenizer.cpp @@ -141,7 +141,7 @@ void ProcessDataToken(TokenList &output_tokens, const char *&start, const char * } // namespace // ------------------------------------------------------------------------------------------------ -void Tokenize(TokenList &output_tokens, const char *input, size_t length) { +void Tokenize(TokenList &output_tokens, const char *input, size_t length, bool &corrupt) { // line and column numbers numbers are one-based unsigned int line = 1; unsigned int column = 1; @@ -185,6 +185,8 @@ void Tokenize(TokenList &output_tokens, const char *input, size_t length) { case '\"': if (token_begin) { TokenizeError("unexpected double-quote", line, column); + corrupt = true; + return; } token_begin = cur; in_double_quotes = true; diff --git a/modules/fbx/fbx_parser/FBXTokenizer.h b/modules/fbx/fbx_parser/FBXTokenizer.h index 1e7e5e6535..184d0fd894 100644 --- a/modules/fbx/fbx_parser/FBXTokenizer.h +++ b/modules/fbx/fbx_parser/FBXTokenizer.h @@ -187,7 +187,7 @@ typedef std::vector<TokenPtr> TokenList; * @param output_tokens Receives a list of all tokens in the input data. * @param input_buffer Textual input buffer to be processed, 0-terminated. * @print_error if something goes wrong */ -void Tokenize(TokenList &output_tokens, const char *input, size_t length); +void Tokenize(TokenList &output_tokens, const char *input, size_t length, bool &corrupt); /** Tokenizer function for binary FBX files. * @@ -197,7 +197,7 @@ void Tokenize(TokenList &output_tokens, const char *input, size_t length); * @param input_buffer Binary input buffer to be processed. * @param length Length of input buffer, in bytes. There is no 0-terminal. * @print_error if something goes wrong */ -void TokenizeBinary(TokenList &output_tokens, const char *input, size_t length); +void TokenizeBinary(TokenList &output_tokens, const char *input, size_t length, bool &corrupt); } // namespace FBXDocParser #endif // FBX_TOKENIZER_H diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp index c9c5d00aa5..5f590383d0 100644 --- a/modules/gdscript/gdscript.cpp +++ b/modules/gdscript/gdscript.cpp @@ -45,6 +45,10 @@ #include "gdscript_parser.h" #include "gdscript_warning.h" +#ifdef TESTS_ENABLED +#include "tests/gdscript_test_runner.h" +#endif + /////////////////////////// GDScriptNativeClass::GDScriptNativeClass(const StringName &p_name) { @@ -1766,6 +1770,10 @@ void GDScriptLanguage::init() { for (List<Engine::Singleton>::Element *E = singletons.front(); E; E = E->next()) { _add_global(E->get().name, E->get().ptr); } + +#ifdef TESTS_ENABLED + GDScriptTests::GDScriptTestRunner::handle_cmdline(); +#endif } String GDScriptLanguage::get_type() const { diff --git a/modules/gdscript/gdscript_byte_codegen.cpp b/modules/gdscript/gdscript_byte_codegen.cpp index 21b221b6ba..89c5f5482b 100644 --- a/modules/gdscript/gdscript_byte_codegen.cpp +++ b/modules/gdscript/gdscript_byte_codegen.cpp @@ -571,7 +571,7 @@ void GDScriptByteCodeGenerator::write_type_test(const Address &p_target, const A } void GDScriptByteCodeGenerator::write_type_test_builtin(const Address &p_target, const Address &p_source, Variant::Type p_type) { - append(GDScriptFunction::OPCODE_IS_BUILTIN, 3); + append(GDScriptFunction::OPCODE_IS_BUILTIN, 2); append(p_source); append(p_target); append(p_type); diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp index 695154e9a9..ca8bb8fcae 100644 --- a/modules/gdscript/gdscript_parser.cpp +++ b/modules/gdscript/gdscript_parser.cpp @@ -811,6 +811,7 @@ GDScriptParser::VariableNode *GDScriptParser::parse_variable(bool p_allow_proper VariableNode *variable = alloc_node<VariableNode>(); variable->identifier = parse_identifier(); + variable->export_info.name = variable->identifier->name; if (match(GDScriptTokenizer::Token::COLON)) { if (check(GDScriptTokenizer::Token::NEWLINE)) { @@ -860,8 +861,6 @@ GDScriptParser::VariableNode *GDScriptParser::parse_variable(bool p_allow_proper end_statement("variable declaration"); - variable->export_info.name = variable->identifier->name; - return variable; } diff --git a/modules/gdscript/register_types.cpp b/modules/gdscript/register_types.cpp index 19fd3daf20..2d2f94f5e0 100644 --- a/modules/gdscript/register_types.cpp +++ b/modules/gdscript/register_types.cpp @@ -163,19 +163,19 @@ void unregister_gdscript_types() { #ifdef TESTS_ENABLED void test_tokenizer() { - TestGDScript::test(TestGDScript::TestType::TEST_TOKENIZER); + GDScriptTests::test(GDScriptTests::TestType::TEST_TOKENIZER); } void test_parser() { - TestGDScript::test(TestGDScript::TestType::TEST_PARSER); + GDScriptTests::test(GDScriptTests::TestType::TEST_PARSER); } void test_compiler() { - TestGDScript::test(TestGDScript::TestType::TEST_COMPILER); + GDScriptTests::test(GDScriptTests::TestType::TEST_COMPILER); } void test_bytecode() { - TestGDScript::test(TestGDScript::TestType::TEST_BYTECODE); + GDScriptTests::test(GDScriptTests::TestType::TEST_BYTECODE); } REGISTER_TEST_COMMAND("gdscript-tokenizer", &test_tokenizer); diff --git a/modules/gdscript/tests/gdscript_test_runner.cpp b/modules/gdscript/tests/gdscript_test_runner.cpp new file mode 100644 index 0000000000..f53c3046e6 --- /dev/null +++ b/modules/gdscript/tests/gdscript_test_runner.cpp @@ -0,0 +1,584 @@ +/*************************************************************************/ +/* gdscript_test_runner.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 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 "gdscript_test_runner.h" + +#include "../gdscript.h" +#include "../gdscript_analyzer.h" +#include "../gdscript_compiler.h" +#include "../gdscript_parser.h" + +#include "core/config/project_settings.h" +#include "core/core_string_names.h" +#include "core/io/file_access_pack.h" +#include "core/os/dir_access.h" +#include "core/os/os.h" +#include "core/string/string_builder.h" +#include "scene/resources/packed_scene.h" + +#include "tests/test_macros.h" + +namespace GDScriptTests { + +void init_autoloads() { + Map<StringName, ProjectSettings::AutoloadInfo> autoloads = ProjectSettings::get_singleton()->get_autoload_list(); + + // First pass, add the constants so they exist before any script is loaded. + for (Map<StringName, ProjectSettings::AutoloadInfo>::Element *E = autoloads.front(); E; E = E->next()) { + const ProjectSettings::AutoloadInfo &info = E->get(); + + if (info.is_singleton) { + for (int i = 0; i < ScriptServer::get_language_count(); i++) { + ScriptServer::get_language(i)->add_global_constant(info.name, Variant()); + } + } + } + + // Second pass, load into global constants. + for (Map<StringName, ProjectSettings::AutoloadInfo>::Element *E = autoloads.front(); E; E = E->next()) { + const ProjectSettings::AutoloadInfo &info = E->get(); + + if (!info.is_singleton) { + // Skip non-singletons since we don't have a scene tree here anyway. + continue; + } + + RES res = ResourceLoader::load(info.path); + ERR_CONTINUE_MSG(res.is_null(), "Can't autoload: " + info.path); + Node *n = nullptr; + if (res->is_class("PackedScene")) { + Ref<PackedScene> ps = res; + n = ps->instance(); + } else if (res->is_class("Script")) { + Ref<Script> script_res = res; + StringName ibt = script_res->get_instance_base_type(); + bool valid_type = ClassDB::is_parent_class(ibt, "Node"); + ERR_CONTINUE_MSG(!valid_type, "Script does not inherit a Node: " + info.path); + + Object *obj = ClassDB::instance(ibt); + + ERR_CONTINUE_MSG(obj == nullptr, + "Cannot instance script for autoload, expected 'Node' inheritance, got: " + + String(ibt)); + + n = Object::cast_to<Node>(obj); + n->set_script(script_res); + } + + ERR_CONTINUE_MSG(!n, "Path in autoload not a node or script: " + info.path); + n->set_name(info.name); + + for (int i = 0; i < ScriptServer::get_language_count(); i++) { + ScriptServer::get_language(i)->add_global_constant(info.name, n); + } + } +} + +void init_language(const String &p_base_path) { + // Setup project settings since it's needed by the languages to get the global scripts. + // This also sets up the base resource path. + Error err = ProjectSettings::get_singleton()->setup(p_base_path, String(), true); + if (err) { + print_line("Could not load project settings."); + // Keep going since some scripts still work without this. + } + + // Initialize the language for the test routine. + GDScriptLanguage::get_singleton()->init(); + init_autoloads(); +} + +void finish_language() { + GDScriptLanguage::get_singleton()->finish(); + ScriptServer::global_classes_clear(); +} + +StringName GDScriptTestRunner::test_function_name; + +GDScriptTestRunner::GDScriptTestRunner(const String &p_source_dir, bool p_init_language) { + test_function_name = StaticCString::create("test"); + do_init_languages = p_init_language; + + source_dir = p_source_dir; + if (!source_dir.ends_with("/")) { + source_dir += "/"; + } + + if (do_init_languages) { + init_language(p_source_dir); + + // Enable all warnings for GDScript, so we can test them. + ProjectSettings::get_singleton()->set_setting("debug/gdscript/warnings/enable", true); + for (int i = 0; i < (int)GDScriptWarning::WARNING_MAX; i++) { + String warning = GDScriptWarning::get_name_from_code((GDScriptWarning::Code)i).to_lower(); + ProjectSettings::get_singleton()->set_setting("debug/gdscript/warnings/" + warning, true); + } + } + + // Enable printing to show results + _print_line_enabled = true; + _print_error_enabled = true; +} + +GDScriptTestRunner::~GDScriptTestRunner() { + test_function_name = StringName(); + if (do_init_languages) { + finish_language(); + } +} + +int GDScriptTestRunner::run_tests() { + if (!make_tests()) { + FAIL("An error occurred while making the tests."); + return -1; + } + + if (!generate_class_index()) { + FAIL("An error occurred while generating class index."); + return -1; + } + + int failed = 0; + for (int i = 0; i < tests.size(); i++) { + GDScriptTest test = tests[i]; + GDScriptTest::TestResult result = test.run_test(); + + String expected = FileAccess::get_file_as_string(test.get_output_file()); + INFO(test.get_source_file()); + if (!result.passed) { + INFO(expected); + failed++; + } + + CHECK_MESSAGE(result.passed, (result.passed ? String() : result.output)); + } + + return failed; +} + +bool GDScriptTestRunner::generate_outputs() { + is_generating = true; + + if (!make_tests()) { + print_line("Failed to generate a test output."); + return false; + } + + if (!generate_class_index()) { + return false; + } + + for (int i = 0; i < tests.size(); i++) { + OS::get_singleton()->print("."); + GDScriptTest test = tests[i]; + bool result = test.generate_output(); + + if (!result) { + print_line("\nCould not generate output for " + test.get_source_file()); + return false; + } + } + print_line("\nGenerated output files for " + itos(tests.size()) + " tests successfully."); + + return true; +} + +bool GDScriptTestRunner::make_tests_for_dir(const String &p_dir) { + Error err = OK; + DirAccessRef dir(DirAccess::open(p_dir, &err)); + + if (err != OK) { + return false; + } + + String current_dir = dir->get_current_dir(); + + dir->list_dir_begin(); + String next = dir->get_next(); + + while (!next.is_empty()) { + if (dir->current_is_dir()) { + if (next == "." || next == "..") { + next = dir->get_next(); + continue; + } + if (!make_tests_for_dir(current_dir.plus_file(next))) { + return false; + } + } else { + if (next.get_extension().to_lower() == "gd") { + String out_file = next.get_basename() + ".out"; + if (!is_generating && !dir->file_exists(out_file)) { + ERR_FAIL_V_MSG(false, "Could not find output file for " + next); + } + GDScriptTest test(current_dir.plus_file(next), current_dir.plus_file(out_file), source_dir); + tests.push_back(test); + } + } + + next = dir->get_next(); + } + + dir->list_dir_end(); + + return true; +} + +bool GDScriptTestRunner::make_tests() { + Error err = OK; + DirAccessRef dir(DirAccess::open(source_dir, &err)); + + ERR_FAIL_COND_V_MSG(err != OK, false, "Could not open specified test directory."); + + return make_tests_for_dir(dir->get_current_dir()); +} + +bool GDScriptTestRunner::generate_class_index() { + StringName gdscript_name = GDScriptLanguage::get_singleton()->get_name(); + for (int i = 0; i < tests.size(); i++) { + GDScriptTest test = tests[i]; + String base_type; + + String class_name = GDScriptLanguage::get_singleton()->get_global_class_name(test.get_source_file(), &base_type); + if (class_name == String()) { + continue; + } + ERR_FAIL_COND_V_MSG(ScriptServer::is_global_class(class_name), false, + "Class name '" + class_name + "' from " + test.get_source_file() + " is already used in " + ScriptServer::get_global_class_path(class_name)); + + ScriptServer::add_global_class(class_name, base_type, gdscript_name, test.get_source_file()); + } + return true; +} + +GDScriptTest::GDScriptTest(const String &p_source_path, const String &p_output_path, const String &p_base_dir) { + source_file = p_source_path; + output_file = p_output_path; + base_dir = p_base_dir; + _print_handler.printfunc = print_handler; + _error_handler.errfunc = error_handler; +} + +void GDScriptTestRunner::handle_cmdline() { + List<String> cmdline_args = OS::get_singleton()->get_cmdline_args(); + // TODO: this could likely be ported to use test commands: + // https://github.com/godotengine/godot/pull/41355 + // Currently requires to startup the whole engine, which is slow. + String test_cmd = "--gdscript-test"; + String gen_cmd = "--gdscript-generate-tests"; + + for (List<String>::Element *E = cmdline_args.front(); E != nullptr; E = E->next()) { + String &cmd = E->get(); + if (cmd == test_cmd || cmd == gen_cmd) { + if (E->next() == nullptr) { + ERR_PRINT("Needed a path for the test files."); + exit(-1); + } + + const String &path = E->next()->get(); + + GDScriptTestRunner runner(path, false); + int failed = 0; + if (cmd == test_cmd) { + failed = runner.run_tests(); + } else { + bool completed = runner.generate_outputs(); + failed = completed ? 0 : -1; + } + exit(failed); + } + } +} + +void GDScriptTest::enable_stdout() { + // TODO: this could likely be handled by doctest or `tests/test_macros.h`. + OS::get_singleton()->set_stdout_enabled(true); + OS::get_singleton()->set_stderr_enabled(true); +} + +void GDScriptTest::disable_stdout() { + // TODO: this could likely be handled by doctest or `tests/test_macros.h`. + OS::get_singleton()->set_stdout_enabled(false); + OS::get_singleton()->set_stderr_enabled(false); +} + +void GDScriptTest::print_handler(void *p_this, const String &p_message, bool p_error) { + TestResult *result = (TestResult *)p_this; + result->output += p_message + "\n"; +} + +void GDScriptTest::error_handler(void *p_this, const char *p_function, const char *p_file, int p_line, const char *p_error, const char *p_explanation, ErrorHandlerType p_type) { + ErrorHandlerData *data = (ErrorHandlerData *)p_this; + GDScriptTest *self = data->self; + TestResult *result = data->result; + + result->status = GDTEST_RUNTIME_ERROR; + + StringBuilder builder; + builder.append(">> "); + switch (p_type) { + case ERR_HANDLER_ERROR: + builder.append("ERROR"); + break; + case ERR_HANDLER_WARNING: + builder.append("WARNING"); + break; + case ERR_HANDLER_SCRIPT: + builder.append("SCRIPT ERROR"); + break; + case ERR_HANDLER_SHADER: + builder.append("SHADER ERROR"); + break; + default: + builder.append("Unknown error type"); + break; + } + + builder.append("\n>> "); + builder.append(p_function); + builder.append("\n>> "); + builder.append(p_function); + builder.append("\n>> "); + builder.append(String(p_file).trim_prefix(self->base_dir)); + builder.append("\n>> "); + builder.append(itos(p_line)); + builder.append("\n>> "); + builder.append(p_error); + if (strlen(p_explanation) > 0) { + builder.append("\n>> "); + builder.append(p_explanation); + } + builder.append("\n"); + + result->output = builder.as_string(); +} + +bool GDScriptTest::check_output(const String &p_output) const { + Error err = OK; + String expected = FileAccess::get_file_as_string(output_file, &err); + + ERR_FAIL_COND_V_MSG(err != OK, false, "Error when opening the output file."); + + String got = p_output.strip_edges(); // TODO: may be hacky. + got += "\n"; // Make sure to insert newline for CI static checks. + + return got == expected; +} + +String GDScriptTest::get_text_for_status(GDScriptTest::TestStatus p_status) const { + switch (p_status) { + case GDTEST_OK: + return "GDTEST_OK"; + case GDTEST_LOAD_ERROR: + return "GDTEST_LOAD_ERROR"; + case GDTEST_PARSER_ERROR: + return "GDTEST_PARSER_ERROR"; + case GDTEST_ANALYZER_ERROR: + return "GDTEST_ANALYZER_ERROR"; + case GDTEST_COMPILER_ERROR: + return "GDTEST_COMPILER_ERROR"; + case GDTEST_RUNTIME_ERROR: + return "GDTEST_RUNTIME_ERROR"; + } + return ""; +} + +GDScriptTest::TestResult GDScriptTest::execute_test_code(bool p_is_generating) { + disable_stdout(); + + TestResult result; + result.status = GDTEST_OK; + result.output = String(); + + Error err = OK; + + // Create script. + Ref<GDScript> script; + script.instance(); + script->set_path(source_file); + script->set_script_path(source_file); + err = script->load_source_code(source_file); + if (err != OK) { + enable_stdout(); + result.status = GDTEST_LOAD_ERROR; + result.passed = false; + ERR_FAIL_V_MSG(result, "\nCould not load source code for: '" + source_file + "'"); + } + + // Test parsing. + GDScriptParser parser; + err = parser.parse(script->get_source_code(), source_file, false); + if (err != OK) { + enable_stdout(); + result.status = GDTEST_PARSER_ERROR; + result.output = get_text_for_status(result.status) + "\n"; + + const List<GDScriptParser::ParserError> &errors = parser.get_errors(); + for (auto *E = errors.front(); E; E = E->next()) { + result.output += E->get().message + "\n"; // TODO: line, column? + break; // Only the first error since the following might be cascading. + } + if (!p_is_generating) { + result.passed = check_output(result.output); + } + return result; + } + + // Test type-checking. + GDScriptAnalyzer analyzer(&parser); + err = analyzer.analyze(); + if (err != OK) { + enable_stdout(); + result.status = GDTEST_ANALYZER_ERROR; + result.output = get_text_for_status(result.status) + "\n"; + + const List<GDScriptParser::ParserError> &errors = parser.get_errors(); + for (auto *E = errors.front(); E; E = E->next()) { + result.output += E->get().message + "\n"; // TODO: line, column? + break; // Only the first error since the following might be cascading. + } + if (!p_is_generating) { + result.passed = check_output(result.output); + } + return result; + } + + StringBuilder warning_string; + for (const List<GDScriptWarning>::Element *E = parser.get_warnings().front(); E != nullptr; E = E->next()) { + const GDScriptWarning warning = E->get(); + warning_string.append(">> WARNING"); + warning_string.append("\n>> Line: "); + warning_string.append(itos(warning.start_line)); + warning_string.append("\n>> "); + warning_string.append(warning.get_name()); + warning_string.append("\n>> "); + warning_string.append(warning.get_message()); + warning_string.append("\n"); + } + result.output += warning_string.as_string(); + + // Test compiling. + GDScriptCompiler compiler; + err = compiler.compile(&parser, script.ptr(), false); + if (err != OK) { + enable_stdout(); + result.status = GDTEST_COMPILER_ERROR; + result.output = get_text_for_status(result.status) + "\n"; + result.output = compiler.get_error(); + if (!p_is_generating) { + result.passed = check_output(result.output); + } + return result; + } + + // Test running. + const Map<StringName, GDScriptFunction *>::Element *test_function_element = script->get_member_functions().find(GDScriptTestRunner::test_function_name); + if (test_function_element == nullptr) { + enable_stdout(); + result.status = GDTEST_LOAD_ERROR; + result.output = ""; + result.passed = false; + ERR_FAIL_V_MSG(result, "\nCould not find test function on: '" + source_file + "'"); + } + + script->reload(); + + // Create object instance for test. + Object *obj = ClassDB::instance(script->get_native()->get_name()); + Ref<Reference> obj_ref; + if (obj->is_reference()) { + obj_ref = Ref<Reference>(Object::cast_to<Reference>(obj)); + } + obj->set_script(script); + GDScriptInstance *instance = static_cast<GDScriptInstance *>(obj->get_script_instance()); + + // Setup output handlers. + ErrorHandlerData error_data(&result, this); + + _print_handler.userdata = &result; + _error_handler.userdata = &error_data; + add_print_handler(&_print_handler); + add_error_handler(&_error_handler); + + // Call test function. + Callable::CallError call_err; + instance->call(GDScriptTestRunner::test_function_name, nullptr, 0, call_err); + + // Tear down output handlers. + remove_print_handler(&_print_handler); + remove_error_handler(&_error_handler); + + // Check results. + if (call_err.error != Callable::CallError::CALL_OK) { + enable_stdout(); + result.status = GDTEST_LOAD_ERROR; + result.passed = false; + ERR_FAIL_V_MSG(result, "\nCould not call test function on: '" + source_file + "'"); + } + + result.output = get_text_for_status(result.status) + "\n" + result.output; + if (!p_is_generating) { + result.passed = check_output(result.output); + } + + if (obj_ref.is_null()) { + memdelete(obj); + } + + enable_stdout(); + return result; +} + +GDScriptTest::TestResult GDScriptTest::run_test() { + return execute_test_code(false); +} + +bool GDScriptTest::generate_output() { + TestResult result = execute_test_code(true); + if (result.status == GDTEST_LOAD_ERROR) { + return false; + } + + Error err = OK; + FileAccessRef out_file = FileAccess::open(output_file, FileAccess::WRITE, &err); + if (err != OK) { + return false; + } + + String output = result.output.strip_edges(); // TODO: may be hacky. + output += "\n"; // Make sure to insert newline for CI static checks. + + out_file->store_string(output); + out_file->close(); + + return true; +} + +} // namespace GDScriptTests diff --git a/modules/gdscript/tests/gdscript_test_runner.h b/modules/gdscript/tests/gdscript_test_runner.h new file mode 100644 index 0000000000..9b2d14a371 --- /dev/null +++ b/modules/gdscript/tests/gdscript_test_runner.h @@ -0,0 +1,126 @@ +/*************************************************************************/ +/* gdscript_test_runner.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 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 GDSCRIPT_TEST_H +#define GDSCRIPT_TEST_H + +#include "../gdscript.h" +#include "core/error/error_macros.h" +#include "core/string/print_string.h" +#include "core/string/ustring.h" +#include "core/templates/vector.h" + +namespace GDScriptTests { + +void init_autoloads(); +void init_language(const String &p_base_path); +void finish_language(); + +// Single test instance in a suite. +class GDScriptTest { +public: + enum TestStatus { + GDTEST_OK, + GDTEST_LOAD_ERROR, + GDTEST_PARSER_ERROR, + GDTEST_ANALYZER_ERROR, + GDTEST_COMPILER_ERROR, + GDTEST_RUNTIME_ERROR, + }; + + struct TestResult { + TestStatus status; + String output; + bool passed; + }; + +private: + struct ErrorHandlerData { + TestResult *result; + GDScriptTest *self; + ErrorHandlerData(TestResult *p_result, GDScriptTest *p_this) { + result = p_result; + self = p_this; + } + }; + + String source_file; + String output_file; + String base_dir; + + PrintHandlerList _print_handler; + ErrorHandlerList _error_handler; + + void enable_stdout(); + void disable_stdout(); + bool check_output(const String &p_output) const; + String get_text_for_status(TestStatus p_status) const; + + TestResult execute_test_code(bool p_is_generating); + +public: + static void print_handler(void *p_this, const String &p_message, bool p_error); + static void error_handler(void *p_this, const char *p_function, const char *p_file, int p_line, const char *p_error, const char *p_explanation, ErrorHandlerType p_type); + TestResult run_test(); + bool generate_output(); + + const String &get_source_file() const { return source_file; } + const String &get_output_file() const { return output_file; } + + GDScriptTest(const String &p_source_path, const String &p_output_path, const String &p_base_dir); + GDScriptTest() : + GDScriptTest(String(), String(), String()) {} // Needed to use in Vector. +}; + +class GDScriptTestRunner { + String source_dir; + Vector<GDScriptTest> tests; + + bool is_generating = false; + bool do_init_languages = false; + + bool make_tests(); + bool make_tests_for_dir(const String &p_dir); + bool generate_class_index(); + +public: + static StringName test_function_name; + + static void handle_cmdline(); + int run_tests(); + bool generate_outputs(); + + GDScriptTestRunner(const String &p_source_dir, bool p_init_language); + ~GDScriptTestRunner(); +}; + +} // namespace GDScriptTests + +#endif // GDSCRIPT_TEST_H diff --git a/modules/gdscript/tests/gdscript_test_runner_suite.h b/modules/gdscript/tests/gdscript_test_runner_suite.h new file mode 100644 index 0000000000..136907b316 --- /dev/null +++ b/modules/gdscript/tests/gdscript_test_runner_suite.h @@ -0,0 +1,53 @@ +/*************************************************************************/ +/* gdscript_test_runner_suite.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 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 GDSCRIPT_TEST_RUNNER_SUITE_H +#define GDSCRIPT_TEST_RUNNER_SUITE_H + +#include "gdscript_test_runner.h" +#include "tests/test_macros.h" + +namespace GDScriptTests { + +TEST_SUITE("[Modules][GDScript]") { + // GDScript 2.0 is still under heavy construction. + // Allow the tests to fail, but do not ignore errors during development. + // Update the scripts and expected output as needed. + TEST_CASE("Script compilation and runtime") { + GDScriptTestRunner runner("modules/gdscript/tests/scripts", true); + int fail_count = runner.run_tests(); + INFO("Make sure `*.out` files have expected results."); + REQUIRE_MESSAGE(fail_count == 0, "All GDScript tests should pass."); + } +} + +} // namespace GDScriptTests + +#endif // GDSCRIPT_TEST_RUNNER_SUITE_H diff --git a/modules/gdscript/tests/scripts/.gitignore b/modules/gdscript/tests/scripts/.gitignore new file mode 100644 index 0000000000..94c5b1bf6b --- /dev/null +++ b/modules/gdscript/tests/scripts/.gitignore @@ -0,0 +1,2 @@ +# Ignore metadata if someone open this on Godot. +/.godot diff --git a/modules/gdscript/tests/scripts/parser-errors/missing-argument.gd b/modules/gdscript/tests/scripts/parser-errors/missing-argument.gd new file mode 100644 index 0000000000..c56ad94095 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser-errors/missing-argument.gd @@ -0,0 +1,6 @@ +func args(a, b): + print(a) + print(b) + +func test(): + args(1,) diff --git a/modules/gdscript/tests/scripts/parser-errors/missing-argument.out b/modules/gdscript/tests/scripts/parser-errors/missing-argument.out new file mode 100644 index 0000000000..fc2a891109 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser-errors/missing-argument.out @@ -0,0 +1,2 @@ +GDTEST_ANALYZER_ERROR +Too few arguments for "args()" call. Expected at least 2 but received 1. diff --git a/modules/gdscript/tests/scripts/parser-errors/missing-closing-expr-paren.gd b/modules/gdscript/tests/scripts/parser-errors/missing-closing-expr-paren.gd new file mode 100644 index 0000000000..a1077e1985 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser-errors/missing-closing-expr-paren.gd @@ -0,0 +1,2 @@ +func test(): + var a = ("missing paren ->" diff --git a/modules/gdscript/tests/scripts/parser-errors/missing-closing-expr-paren.out b/modules/gdscript/tests/scripts/parser-errors/missing-closing-expr-paren.out new file mode 100644 index 0000000000..7326afa33d --- /dev/null +++ b/modules/gdscript/tests/scripts/parser-errors/missing-closing-expr-paren.out @@ -0,0 +1,2 @@ +GDTEST_PARSER_ERROR +Expected closing ")" after grouping expression. diff --git a/modules/gdscript/tests/scripts/parser-errors/missing-colon.gd b/modules/gdscript/tests/scripts/parser-errors/missing-colon.gd new file mode 100644 index 0000000000..62cb633e9e --- /dev/null +++ b/modules/gdscript/tests/scripts/parser-errors/missing-colon.gd @@ -0,0 +1,3 @@ +func test(): + if true # Missing colon here. + print("true") diff --git a/modules/gdscript/tests/scripts/parser-errors/missing-colon.out b/modules/gdscript/tests/scripts/parser-errors/missing-colon.out new file mode 100644 index 0000000000..687b963bc8 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser-errors/missing-colon.out @@ -0,0 +1,2 @@ +GDTEST_PARSER_ERROR +Expected ":" after "if" condition. diff --git a/modules/gdscript/tests/scripts/parser-errors/missing-paren-after-args.gd b/modules/gdscript/tests/scripts/parser-errors/missing-paren-after-args.gd new file mode 100644 index 0000000000..116b0151da --- /dev/null +++ b/modules/gdscript/tests/scripts/parser-errors/missing-paren-after-args.gd @@ -0,0 +1,6 @@ +func args(a, b): + print(a) + print(b) + +func test(): + args(1,2 diff --git a/modules/gdscript/tests/scripts/parser-errors/missing-paren-after-args.out b/modules/gdscript/tests/scripts/parser-errors/missing-paren-after-args.out new file mode 100644 index 0000000000..34ea7ac323 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser-errors/missing-paren-after-args.out @@ -0,0 +1,2 @@ +GDTEST_PARSER_ERROR +Expected closing ")" after call arguments. diff --git a/modules/gdscript/tests/scripts/parser-errors/mixing-tabs-spaces.gd b/modules/gdscript/tests/scripts/parser-errors/mixing-tabs-spaces.gd new file mode 100644 index 0000000000..9ad77f1432 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser-errors/mixing-tabs-spaces.gd @@ -0,0 +1,3 @@ +func test(): + print("Using spaces") + print("Using tabs") diff --git a/modules/gdscript/tests/scripts/parser-errors/mixing-tabs-spaces.out b/modules/gdscript/tests/scripts/parser-errors/mixing-tabs-spaces.out new file mode 100644 index 0000000000..6390de9788 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser-errors/mixing-tabs-spaces.out @@ -0,0 +1,2 @@ +GDTEST_PARSER_ERROR +Used "\t" for indentation instead " " as used before in the file. diff --git a/modules/gdscript/tests/scripts/parser-errors/nothing-after-dollar.gd b/modules/gdscript/tests/scripts/parser-errors/nothing-after-dollar.gd new file mode 100644 index 0000000000..3875ce3936 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser-errors/nothing-after-dollar.gd @@ -0,0 +1,3 @@ +extends Node +func test(): + var a = $ # Expected some node path. diff --git a/modules/gdscript/tests/scripts/parser-errors/nothing-after-dollar.out b/modules/gdscript/tests/scripts/parser-errors/nothing-after-dollar.out new file mode 100644 index 0000000000..b3dc181a22 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser-errors/nothing-after-dollar.out @@ -0,0 +1,2 @@ +GDTEST_PARSER_ERROR +Expect node path as string or identifier after "$". diff --git a/modules/gdscript/tests/scripts/parser-errors/wrong-value-after-dollar-slash.gd b/modules/gdscript/tests/scripts/parser-errors/wrong-value-after-dollar-slash.gd new file mode 100644 index 0000000000..1836d42226 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser-errors/wrong-value-after-dollar-slash.gd @@ -0,0 +1,3 @@ +extends Node +func test(): + $MyNode/23 # Can't use number here. diff --git a/modules/gdscript/tests/scripts/parser-errors/wrong-value-after-dollar-slash.out b/modules/gdscript/tests/scripts/parser-errors/wrong-value-after-dollar-slash.out new file mode 100644 index 0000000000..dcb4ccecb0 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser-errors/wrong-value-after-dollar-slash.out @@ -0,0 +1,2 @@ +GDTEST_PARSER_ERROR +Expect node path after "/". diff --git a/modules/gdscript/tests/scripts/parser-errors/wrong-value-after-dollar.gd b/modules/gdscript/tests/scripts/parser-errors/wrong-value-after-dollar.gd new file mode 100644 index 0000000000..6fd2692d47 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser-errors/wrong-value-after-dollar.gd @@ -0,0 +1,3 @@ +extends Node +func test(): + $23 # Can't use number here. diff --git a/modules/gdscript/tests/scripts/parser-errors/wrong-value-after-dollar.out b/modules/gdscript/tests/scripts/parser-errors/wrong-value-after-dollar.out new file mode 100644 index 0000000000..b3dc181a22 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser-errors/wrong-value-after-dollar.out @@ -0,0 +1,2 @@ +GDTEST_PARSER_ERROR +Expect node path as string or identifier after "$". diff --git a/modules/gdscript/tests/scripts/parser-features/semicolon-as-end-statement.gd b/modules/gdscript/tests/scripts/parser-features/semicolon-as-end-statement.gd new file mode 100644 index 0000000000..08f2eedb2d --- /dev/null +++ b/modules/gdscript/tests/scripts/parser-features/semicolon-as-end-statement.gd @@ -0,0 +1,2 @@ +func test(): + print("A"); print("B") diff --git a/modules/gdscript/tests/scripts/parser-features/semicolon-as-end-statement.out b/modules/gdscript/tests/scripts/parser-features/semicolon-as-end-statement.out new file mode 100644 index 0000000000..fc03f3efe8 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser-features/semicolon-as-end-statement.out @@ -0,0 +1,3 @@ +GDTEST_OK +A +B diff --git a/modules/gdscript/tests/scripts/parser-features/trailing-comma-in-function-args.gd b/modules/gdscript/tests/scripts/parser-features/trailing-comma-in-function-args.gd new file mode 100644 index 0000000000..6097b11b10 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser-features/trailing-comma-in-function-args.gd @@ -0,0 +1,7 @@ +# See https://github.com/godotengine/godot/issues/41066. + +func f(p, ): ## <-- no errors + print(p) + +func test(): + f(0, ) ## <-- no error diff --git a/modules/gdscript/tests/scripts/parser-features/trailing-comma-in-function-args.out b/modules/gdscript/tests/scripts/parser-features/trailing-comma-in-function-args.out new file mode 100644 index 0000000000..94e2ec2af8 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser-features/trailing-comma-in-function-args.out @@ -0,0 +1,2 @@ +GDTEST_OK +0 diff --git a/modules/gdscript/tests/scripts/parser-features/variable-declaration.gd b/modules/gdscript/tests/scripts/parser-features/variable-declaration.gd new file mode 100644 index 0000000000..3b48f10ca7 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser-features/variable-declaration.gd @@ -0,0 +1,12 @@ +var a # No init. +var b = 42 # Init. + +func test(): + var c # No init, local. + var d = 23 # Init, local. + + a = 1 + c = 2 + + prints(a, b, c, d) + print("OK") diff --git a/modules/gdscript/tests/scripts/parser-features/variable-declaration.out b/modules/gdscript/tests/scripts/parser-features/variable-declaration.out new file mode 100644 index 0000000000..2e0a63c024 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser-features/variable-declaration.out @@ -0,0 +1,7 @@ +GDTEST_OK +>> WARNING +>> Line: 5 +>> UNASSIGNED_VARIABLE +>> The variable 'c' was used but never assigned a value. +1 42 2 23 +OK diff --git a/modules/gdscript/tests/scripts/parser-warnings/unused-variable.gd b/modules/gdscript/tests/scripts/parser-warnings/unused-variable.gd new file mode 100644 index 0000000000..68e3bd424f --- /dev/null +++ b/modules/gdscript/tests/scripts/parser-warnings/unused-variable.gd @@ -0,0 +1,2 @@ +func test(): + var unused = "not used" diff --git a/modules/gdscript/tests/scripts/parser-warnings/unused-variable.out b/modules/gdscript/tests/scripts/parser-warnings/unused-variable.out new file mode 100644 index 0000000000..270e0e69c0 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser-warnings/unused-variable.out @@ -0,0 +1,5 @@ +GDTEST_OK +>> WARNING +>> Line: 2 +>> UNUSED_VARIABLE +>> The local variable 'unused' is declared but never used in the block. If this is intended, prefix it with an underscore: '_unused' diff --git a/modules/gdscript/tests/scripts/project.godot b/modules/gdscript/tests/scripts/project.godot new file mode 100644 index 0000000000..25b49c0abd --- /dev/null +++ b/modules/gdscript/tests/scripts/project.godot @@ -0,0 +1,10 @@ +; This is not an actual project. +; This config only exists to properly set up the test environment. +; It also helps for opening Godot to edit the scripts, but please don't +; let the editor changes be saved. + +config_version=4 + +[application] + +config/name="GDScript Integration Test Suite" diff --git a/modules/gdscript/tests/test_gdscript.cpp b/modules/gdscript/tests/test_gdscript.cpp index 3cc0eee672..e70f221c0a 100644 --- a/modules/gdscript/tests/test_gdscript.cpp +++ b/modules/gdscript/tests/test_gdscript.cpp @@ -47,7 +47,7 @@ #include "editor/editor_settings.h" #endif -namespace TestGDScript { +namespace GDScriptTests { static void test_tokenizer(const String &p_code, const Vector<String> &p_lines) { GDScriptTokenizer tokenizer; @@ -183,60 +183,6 @@ static void test_compiler(const String &p_code, const String &p_script_path, con } } -void init_autoloads() { - Map<StringName, ProjectSettings::AutoloadInfo> autoloads = ProjectSettings::get_singleton()->get_autoload_list(); - - // First pass, add the constants so they exist before any script is loaded. - for (Map<StringName, ProjectSettings::AutoloadInfo>::Element *E = autoloads.front(); E; E = E->next()) { - const ProjectSettings::AutoloadInfo &info = E->get(); - - if (info.is_singleton) { - for (int i = 0; i < ScriptServer::get_language_count(); i++) { - ScriptServer::get_language(i)->add_global_constant(info.name, Variant()); - } - } - } - - // Second pass, load into global constants. - for (Map<StringName, ProjectSettings::AutoloadInfo>::Element *E = autoloads.front(); E; E = E->next()) { - const ProjectSettings::AutoloadInfo &info = E->get(); - - if (!info.is_singleton) { - // Skip non-singletons since we don't have a scene tree here anyway. - continue; - } - - RES res = ResourceLoader::load(info.path); - ERR_CONTINUE_MSG(res.is_null(), "Can't autoload: " + info.path); - Node *n = nullptr; - if (res->is_class("PackedScene")) { - Ref<PackedScene> ps = res; - n = ps->instance(); - } else if (res->is_class("Script")) { - Ref<Script> script_res = res; - StringName ibt = script_res->get_instance_base_type(); - bool valid_type = ClassDB::is_parent_class(ibt, "Node"); - ERR_CONTINUE_MSG(!valid_type, "Script does not inherit a Node: " + info.path); - - Object *obj = ClassDB::instance(ibt); - - ERR_CONTINUE_MSG(obj == nullptr, - "Cannot instance script for autoload, expected 'Node' inheritance, got: " + - String(ibt)); - - n = Object::cast_to<Node>(obj); - n->set_script(script_res); - } - - ERR_CONTINUE_MSG(!n, "Path in autoload not a node or script: " + info.path); - n->set_name(info.name); - - for (int i = 0; i < ScriptServer::get_language_count(); i++) { - ScriptServer::get_language(i)->add_global_constant(info.name, n); - } - } -} - void test(TestType p_type) { List<String> cmdlargs = OS::get_singleton()->get_cmdline_args(); @@ -253,20 +199,8 @@ void test(TestType p_type) { FileAccessRef fa = FileAccess::open(test, FileAccess::READ); ERR_FAIL_COND_MSG(!fa, "Could not open file: " + test); - // Init PackedData since it's used by ProjectSettings. - PackedData *packed_data = memnew(PackedData); - - // Setup project settings since it's needed by the languages to get the global scripts. - // This also sets up the base resource path. - Error err = ProjectSettings::get_singleton()->setup(fa->get_path_absolute().get_base_dir(), String(), true); - if (err) { - print_line("Could not load project settings."); - // Keep going since some scripts still work without this. - } - // Initialize the language for the test routine. - ScriptServer::init_languages(); - init_autoloads(); + init_language(fa->get_path_absolute().get_base_dir()); Vector<uint8_t> buf; int flen = fa->get_len(); @@ -300,8 +234,6 @@ void test(TestType p_type) { print_line("Not implemented."); } - // Destroy stuff we set up earlier. - ScriptServer::finish_languages(); - memdelete(packed_data); + finish_language(); } -} // namespace TestGDScript +} // namespace GDScriptTests diff --git a/modules/gdscript/tests/test_gdscript.h b/modules/gdscript/tests/test_gdscript.h index bbda46cdad..c7ee5a2208 100644 --- a/modules/gdscript/tests/test_gdscript.h +++ b/modules/gdscript/tests/test_gdscript.h @@ -31,7 +31,10 @@ #ifndef TEST_GDSCRIPT_H #define TEST_GDSCRIPT_H -namespace TestGDScript { +#include "gdscript_test_runner.h" +#include "tests/test_macros.h" + +namespace GDScriptTests { enum TestType { TEST_TOKENIZER, @@ -41,6 +44,7 @@ enum TestType { }; void test(TestType p_type); -} // namespace TestGDScript + +} // namespace GDScriptTests #endif // TEST_GDSCRIPT_H diff --git a/modules/gltf/gltf_document.cpp b/modules/gltf/gltf_document.cpp index 0b70175a24..027a054b70 100644 --- a/modules/gltf/gltf_document.cpp +++ b/modules/gltf/gltf_document.cpp @@ -3293,6 +3293,7 @@ Error GLTFDocument::_serialize_materials(Ref<GLTFState> state) { } img->decompress(); img->convert(Image::FORMAT_RGBA8); + img->convert_ra_rgba8_to_rg(); for (int32_t y = 0; y < img->get_height(); y++) { for (int32_t x = 0; x < img->get_width(); x++) { Color c = img->get_pixel(x, y); @@ -4958,8 +4959,8 @@ GLTFMeshIndex GLTFDocument::_convert_mesh_instance(Ref<GLTFState> state, MeshIns if (godot_array_mesh.is_valid()) { surface_name = godot_array_mesh->surface_get_name(surface_i); } - if (p_mesh_instance->get_surface_material(surface_i).is_valid()) { - mat = p_mesh_instance->get_surface_material(surface_i); + if (p_mesh_instance->get_surface_override_material(surface_i).is_valid()) { + mat = p_mesh_instance->get_surface_override_material(surface_i); } if (p_mesh_instance->get_material_override().is_valid()) { mat = p_mesh_instance->get_material_override(); diff --git a/modules/lightmapper_rd/lm_blendseams.glsl b/modules/lightmapper_rd/lm_blendseams.glsl index e47e5fcc51..374c48082e 100644 --- a/modules/lightmapper_rd/lm_blendseams.glsl +++ b/modules/lightmapper_rd/lm_blendseams.glsl @@ -7,7 +7,7 @@ triangles = "#define MODE_TRIANGLES"; #version 450 -VERSION_DEFINES +#VERSION_DEFINES #include "lm_common_inc.glsl" @@ -74,7 +74,7 @@ void main() { #version 450 -VERSION_DEFINES +#VERSION_DEFINES #include "lm_common_inc.glsl" diff --git a/modules/lightmapper_rd/lm_compute.glsl b/modules/lightmapper_rd/lm_compute.glsl index eb9d817f99..3dd96893fb 100644 --- a/modules/lightmapper_rd/lm_compute.glsl +++ b/modules/lightmapper_rd/lm_compute.glsl @@ -10,7 +10,7 @@ light_probes = "#define MODE_LIGHT_PROBES"; #version 450 -VERSION_DEFINES +#VERSION_DEFINES // One 2D local group focusing in one layer at a time, though all // in parallel (no barriers) makes more sense than a 3D local group diff --git a/modules/lightmapper_rd/lm_raster.glsl b/modules/lightmapper_rd/lm_raster.glsl index 6c2904192b..55ca193cc1 100644 --- a/modules/lightmapper_rd/lm_raster.glsl +++ b/modules/lightmapper_rd/lm_raster.glsl @@ -2,7 +2,7 @@ #version 450 -VERSION_DEFINES +#VERSION_DEFINES #include "lm_common_inc.glsl" @@ -56,7 +56,7 @@ void main() { #version 450 -VERSION_DEFINES +#VERSION_DEFINES #include "lm_common_inc.glsl" diff --git a/modules/mono/mono_gd/gd_mono_wasm_m2n.h b/modules/mono/mono_gd/gd_mono_wasm_m2n.h index 159a2ed7b6..366662ff81 100644 --- a/modules/mono/mono_gd/gd_mono_wasm_m2n.h +++ b/modules/mono/mono_gd/gd_mono_wasm_m2n.h @@ -176,7 +176,7 @@ T m2n_arg_cast(Mono_InterpMethodArguments *p_margs, size_t p_idx) { } else if constexpr (cookie == 'F') { return *reinterpret_cast<float *>(&p_margs->fargs[fidx(p_idx)]); } else if constexpr (cookie == 'D') { - return (T)(size_t)p_margs->fargs[p_idx]; + return (T)p_margs->fargs[p_idx]; } } diff --git a/modules/squish/image_compress_squish.cpp b/modules/squish/image_decompress_squish.cpp index fb0c7aba1d..1450b0fe88 100644 --- a/modules/squish/image_compress_squish.cpp +++ b/modules/squish/image_decompress_squish.cpp @@ -1,5 +1,5 @@ /*************************************************************************/ -/* image_compress_squish.cpp */ +/* image_decompress_squish.cpp */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,7 +28,7 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#include "image_compress_squish.h" +#include "image_decompress_squish.h" #include <squish.h> diff --git a/modules/squish/image_compress_squish.h b/modules/squish/image_decompress_squish.h index ebc5a41887..fff5839ac4 100644 --- a/modules/squish/image_compress_squish.h +++ b/modules/squish/image_decompress_squish.h @@ -1,5 +1,5 @@ /*************************************************************************/ -/* image_compress_squish.h */ +/* image_decompress_squish.h */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ @@ -28,11 +28,11 @@ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifndef IMAGE_COMPRESS_SQUISH_H -#define IMAGE_COMPRESS_SQUISH_H +#ifndef IMAGE_DECOMPRESS_SQUISH_H +#define IMAGE_DECOMPRESS_SQUISH_H #include "core/io/image.h" void image_decompress_squish(Image *p_image); -#endif // IMAGE_COMPRESS_SQUISH_H +#endif // IMAGE_DECOMPRESS_SQUISH_H diff --git a/modules/squish/register_types.cpp b/modules/squish/register_types.cpp index c51cdc9521..51aab040e7 100644 --- a/modules/squish/register_types.cpp +++ b/modules/squish/register_types.cpp @@ -29,7 +29,8 @@ /*************************************************************************/ #include "register_types.h" -#include "image_compress_squish.h" + +#include "image_decompress_squish.h" void register_squish_types() { Image::_image_decompress_bc = image_decompress_squish; diff --git a/modules/visual_script/visual_script_editor.cpp b/modules/visual_script/visual_script_editor.cpp index 3cdf60708b..02ec9ccd06 100644 --- a/modules/visual_script/visual_script_editor.cpp +++ b/modules/visual_script/visual_script_editor.cpp @@ -710,7 +710,7 @@ void VisualScriptEditor::_update_graph(int p_only_id) { has_gnode_text = true; LineEdit *line_edit = memnew(LineEdit); line_edit->set_text(node->get_text()); - line_edit->set_expand_to_text_length(true); + line_edit->set_expand_to_text_length_enabled(true); line_edit->add_theme_font_override("font", get_theme_font("source", "EditorFonts")); gnode->add_child(line_edit); line_edit->connect("text_changed", callable_mp(this, &VisualScriptEditor::_expression_text_changed), varray(E->get())); @@ -843,7 +843,7 @@ void VisualScriptEditor::_update_graph(int p_only_id) { hbc->add_child(name_box); name_box->set_custom_minimum_size(Size2(60 * EDSCALE, 0)); name_box->set_text(left_name); - name_box->set_expand_to_text_length(true); + name_box->set_expand_to_text_length_enabled(true); name_box->connect("resized", callable_mp(this, &VisualScriptEditor::_update_node_size), varray(E->get())); name_box->connect("focus_exited", callable_mp(this, &VisualScriptEditor::_port_name_focus_out), varray(name_box, E->get(), i, true)); } else { @@ -938,7 +938,7 @@ void VisualScriptEditor::_update_graph(int p_only_id) { hbc->add_child(name_box); name_box->set_custom_minimum_size(Size2(60 * EDSCALE, 0)); name_box->set_text(right_name); - name_box->set_expand_to_text_length(true); + name_box->set_expand_to_text_length_enabled(true); name_box->connect("resized", callable_mp(this, &VisualScriptEditor::_update_node_size), varray(E->get())); name_box->connect("focus_exited", callable_mp(this, &VisualScriptEditor::_port_name_focus_out), varray(name_box, E->get(), i, false)); } else { @@ -4322,7 +4322,7 @@ VisualScriptEditor::VisualScriptEditor() { function_name_box = memnew(LineEdit); function_name_edit->add_child(function_name_box); function_name_box->connect("gui_input", callable_mp(this, &VisualScriptEditor::_fn_name_box_input)); - function_name_box->set_expand_to_text_length(true); + function_name_box->set_expand_to_text_length_enabled(true); add_child(function_name_edit); /// Actual Graph /// |