diff options
author | Aras Pranckevicius <aras@nesnausk.org> | 2022-05-12 13:48:55 +0300 |
---|---|---|
committer | Aras Pranckevicius <aras@nesnausk.org> | 2022-05-12 13:49:05 +0300 |
commit | 9757b4efb12e0fba21735fa7960148e6a7b67bb2 (patch) | |
tree | f10d73e9d57b840fe20131a069b75171d6d2fe17 /source/blender/io/wavefront_obj/importer/obj_import_file_reader.cc | |
parent | 207b0c2a0f792375ea7a3087823af41621ececd2 (diff) |
OBJ: improve new importer file parsing performance on windows
The OBJ parser was primarily using StringRef for convenience, with
functions like "skip whitespace" or "parse a number" taking an input
stringref, representing an input line, and returning a new stringref,
representing the remainder of the line. This is convenient, but does
more work than strictly needed -- while parsing, only the "beginning"
of the line ever changes by moving forward; the end of the line
always stays the same. We can change the code to take a pair of
pointers (begin of line, end of line) as input, and make the
functions return the new begin of line pointer. This makes the return
value neatly fit into a processor register, which StringRef did not.
On Windows, this does result in non-trivial speedups in the actual
OBJ file parsing part, due to Windows calling convention where return
values larger than 64 bits are returned via memory. Does not
measurably affect performance on Mac/Linux, because the calling
convention there uses a pair of 64-bit registers to return a
StringRef.
End-to-end times of importing several test files, on Windows
(VS2022 build, Ryzen 5950X):
- Monkey subdivided to level 6, no normals (220MB file): 1.25s -> 0.85s
- Rungholt minecraft level (270MB file): 7.0s -> 5.8s
- Blender 3 splash scene (2.4GB file): 49.1s -> 45.5s
The full import process has a lot of other overhead besides actual
OBJ file parsing (mostly creating actual blender objects out of
parsed data). In pure parsing, in the monkey test scene above, the
parsing part goes 1.0s -> 0.6s.
Reviewed By: Howard Trickey
Differential Revision: https://developer.blender.org/D14936
Diffstat (limited to 'source/blender/io/wavefront_obj/importer/obj_import_file_reader.cc')
-rw-r--r-- | source/blender/io/wavefront_obj/importer/obj_import_file_reader.cc | 294 |
1 files changed, 158 insertions, 136 deletions
diff --git a/source/blender/io/wavefront_obj/importer/obj_import_file_reader.cc b/source/blender/io/wavefront_obj/importer/obj_import_file_reader.cc index fa89b49b605..c7990028312 100644 --- a/source/blender/io/wavefront_obj/importer/obj_import_file_reader.cc +++ b/source/blender/io/wavefront_obj/importer/obj_import_file_reader.cc @@ -66,40 +66,43 @@ static Geometry *create_geometry(Geometry *const prev_geometry, } static void geom_add_vertex(Geometry *geom, - const StringRef line, + const char *p, + const char *end, GlobalVertices &r_global_vertices) { float3 vert; - parse_floats(line, 0.0f, vert, 3); + parse_floats(p, end, 0.0f, vert, 3); r_global_vertices.vertices.append(vert); geom->vertex_count_++; } static void geom_add_vertex_normal(Geometry *geom, - const StringRef line, + const char *p, + const char *end, GlobalVertices &r_global_vertices) { float3 normal; - parse_floats(line, 0.0f, normal, 3); + parse_floats(p, end, 0.0f, normal, 3); r_global_vertices.vertex_normals.append(normal); geom->has_vertex_normals_ = true; } -static void geom_add_uv_vertex(const StringRef line, GlobalVertices &r_global_vertices) +static void geom_add_uv_vertex(const char *p, const char *end, GlobalVertices &r_global_vertices) { float2 uv; - parse_floats(line, 0.0f, uv, 2); + parse_floats(p, end, 0.0f, uv, 2); r_global_vertices.uv_vertices.append(uv); } static void geom_add_edge(Geometry *geom, - StringRef line, + const char *p, + const char *end, const VertexIndexOffset &offsets, GlobalVertices &r_global_vertices) { int edge_v1, edge_v2; - line = parse_int(line, -1, edge_v1); - line = parse_int(line, -1, edge_v2); + p = parse_int(p, end, -1, edge_v1); + p = parse_int(p, end, -1, edge_v2); /* Always keep stored indices non-negative and zero-based. */ edge_v1 += edge_v1 < 0 ? r_global_vertices.vertices.size() : -offsets.get_index_offset() - 1; edge_v2 += edge_v2 < 0 ? r_global_vertices.vertices.size() : -offsets.get_index_offset() - 1; @@ -108,7 +111,8 @@ static void geom_add_edge(Geometry *geom, } static void geom_add_polygon(Geometry *geom, - StringRef line, + const char *p, + const char *end, const GlobalVertices &global_vertices, const VertexIndexOffset &offsets, const int material_index, @@ -127,24 +131,24 @@ static void geom_add_polygon(Geometry *geom, curr_face.start_index_ = orig_corners_size; bool face_valid = true; - line = drop_whitespace(line); - while (!line.is_empty() && face_valid) { + p = drop_whitespace(p, end); + while (p < end && face_valid) { PolyCorner corner; bool got_uv = false, got_normal = false; /* Parse vertex index. */ - line = parse_int(line, INT32_MAX, corner.vert_index, false); + p = parse_int(p, end, INT32_MAX, corner.vert_index, false); face_valid &= corner.vert_index != INT32_MAX; - if (!line.is_empty() && line[0] == '/') { + if (p < end && *p == '/') { /* Parse UV index. */ - line = line.drop_prefix(1); - if (!line.is_empty() && line[0] != '/') { - line = parse_int(line, INT32_MAX, corner.uv_vert_index, false); + ++p; + if (p < end && *p != '/') { + p = parse_int(p, end, INT32_MAX, corner.uv_vert_index, false); got_uv = corner.uv_vert_index != INT32_MAX; } /* Parse normal index. */ - if (!line.is_empty() && line[0] == '/') { - line = line.drop_prefix(1); - line = parse_int(line, INT32_MAX, corner.vertex_normal_index, false); + if (p < end && *p == '/') { + ++p; + p = parse_int(p, end, INT32_MAX, corner.vertex_normal_index, false); got_normal = corner.uv_vert_index != INT32_MAX; } } @@ -185,7 +189,7 @@ static void geom_add_polygon(Geometry *geom, curr_face.corner_count_++; /* Skip whitespace to get to the next face corner. */ - line = drop_whitespace(line); + p = drop_whitespace(p, end); } if (face_valid) { @@ -200,14 +204,16 @@ static void geom_add_polygon(Geometry *geom, } static Geometry *geom_set_curve_type(Geometry *geom, - const StringRef rest_line, + const char *p, + const char *end, const GlobalVertices &global_vertices, const StringRef group_name, VertexIndexOffset &r_offsets, Vector<std::unique_ptr<Geometry>> &r_all_geometries) { - if (rest_line.find("bspline") == StringRef::not_found) { - std::cerr << "Curve type not supported:'" << rest_line << "'" << std::endl; + p = drop_whitespace(p, end); + if (!StringRef(p, end).startswith("bspline")) { + std::cerr << "Curve type not supported: '" << std::string(p, end) << "'" << std::endl; return geom; } geom = create_geometry( @@ -216,22 +222,23 @@ static Geometry *geom_set_curve_type(Geometry *geom, return geom; } -static void geom_set_curve_degree(Geometry *geom, const StringRef line) +static void geom_set_curve_degree(Geometry *geom, const char *p, const char *end) { - parse_int(line, 3, geom->nurbs_element_.degree); + parse_int(p, end, 3, geom->nurbs_element_.degree); } static void geom_add_curve_vertex_indices(Geometry *geom, - StringRef line, + const char *p, + const char *end, const GlobalVertices &global_vertices) { /* Curve lines always have "0.0" and "1.0", skip over them. */ float dummy[2]; - line = parse_floats(line, 0, dummy, 2); + p = parse_floats(p, end, 0, dummy, 2); /* Parse indices. */ - while (!line.is_empty()) { + while (p < end) { int index; - line = parse_int(line, INT32_MAX, index); + p = parse_int(p, end, INT32_MAX, index); if (index == INT32_MAX) { return; } @@ -241,22 +248,22 @@ static void geom_add_curve_vertex_indices(Geometry *geom, } } -static void geom_add_curve_parameters(Geometry *geom, StringRef line) +static void geom_add_curve_parameters(Geometry *geom, const char *p, const char *end) { - line = drop_whitespace(line); - if (line.is_empty()) { - std::cerr << "Invalid OBJ curve parm line: '" << line << "'" << std::endl; + p = drop_whitespace(p, end); + if (p == end) { + std::cerr << "Invalid OBJ curve parm line" << std::endl; return; } - if (line[0] != 'u') { - std::cerr << "OBJ curve surfaces are not supported: '" << line[0] << "'" << std::endl; + if (*p != 'u') { + std::cerr << "OBJ curve surfaces are not supported: '" << *p << "'" << std::endl; return; } - line = line.drop_prefix(1); + ++p; - while (!line.is_empty()) { + while (p < end) { float val; - line = parse_float(line, FLT_MAX, val); + p = parse_float(p, end, FLT_MAX, val); if (val != FLT_MAX) { geom->nurbs_element_.parm.append(val); } @@ -269,7 +276,6 @@ static void geom_add_curve_parameters(Geometry *geom, StringRef line) static void geom_update_group(const StringRef rest_line, std::string &r_group_name) { - if (rest_line.find("off") != string::npos || rest_line.find("null") != string::npos || rest_line.find("default") != string::npos) { /* Set group for future elements like faces or curves to empty. */ @@ -279,17 +285,18 @@ static void geom_update_group(const StringRef rest_line, std::string &r_group_na r_group_name = rest_line; } -static void geom_update_smooth_group(StringRef line, bool &r_state_shaded_smooth) +static void geom_update_smooth_group(const char *p, const char *end, bool &r_state_shaded_smooth) { - line = drop_whitespace(line); + p = drop_whitespace(p, end); /* Some implementations use "0" and "null" too, in addition to "off". */ + const StringRef line = StringRef(p, end); if (line == "0" || line.startswith("off") || line.startswith("null")) { r_state_shaded_smooth = false; return; } int smooth = 0; - parse_int(line, 0, smooth); + parse_int(p, end, 0, smooth); r_state_shaded_smooth = smooth != 0; } @@ -311,21 +318,21 @@ OBJParser::~OBJParser() } /* If line starts with keyword followed by whitespace, returns true and drops it from the line. */ -static bool parse_keyword(StringRef &line, StringRef keyword) +static bool parse_keyword(const char *&p, const char *end, StringRef keyword) { const size_t keyword_len = keyword.size(); - if (line.size() < keyword_len + 1) { + if (end - p < keyword_len + 1) { return false; } - if (!line.startswith(keyword)) { + if (memcmp(p, keyword.data(), keyword_len) != 0) { return false; } /* Treat any ASCII control character as white-space; * don't use `isspace()` for performance reasons. */ - if (line[keyword_len] > ' ') { + if (p[keyword_len] > ' ') { return false; } - line = line.drop_prefix(keyword_len + 1); + p += keyword_len + 1; return true; } @@ -399,27 +406,29 @@ void OBJParser::parse(Vector<std::unique_ptr<Geometry>> &r_all_geometries, StringRef buffer_str{buffer.data(), (int64_t)last_nl}; while (!buffer_str.is_empty()) { StringRef line = read_next_line(buffer_str); - line = drop_whitespace(line); + const char *p = line.begin(), *end = line.end(); + p = drop_whitespace(p, end); ++line_number; - if (line.is_empty()) { + if (p == end) { continue; } /* Most common things that start with 'v': vertices, normals, UVs. */ - if (line[0] == 'v') { - if (parse_keyword(line, "v")) { - geom_add_vertex(curr_geom, line, r_global_vertices); + if (*p == 'v') { + if (parse_keyword(p, end, "v")) { + geom_add_vertex(curr_geom, p, end, r_global_vertices); } - else if (parse_keyword(line, "vn")) { - geom_add_vertex_normal(curr_geom, line, r_global_vertices); + else if (parse_keyword(p, end, "vn")) { + geom_add_vertex_normal(curr_geom, p, end, r_global_vertices); } - else if (parse_keyword(line, "vt")) { - geom_add_uv_vertex(line, r_global_vertices); + else if (parse_keyword(p, end, "vt")) { + geom_add_uv_vertex(p, end, r_global_vertices); } } /* Faces. */ - else if (parse_keyword(line, "f")) { + else if (parse_keyword(p, end, "f")) { geom_add_polygon(curr_geom, - line, + p, + end, r_global_vertices, offsets, state_material_index, @@ -427,20 +436,24 @@ void OBJParser::parse(Vector<std::unique_ptr<Geometry>> &r_all_geometries, state_shaded_smooth); } /* Faces. */ - else if (parse_keyword(line, "l")) { - geom_add_edge(curr_geom, line, offsets, r_global_vertices); + else if (parse_keyword(p, end, "l")) { + geom_add_edge(curr_geom, p, end, offsets, r_global_vertices); } /* Objects. */ - else if (parse_keyword(line, "o")) { + else if (parse_keyword(p, end, "o")) { state_shaded_smooth = false; state_group_name = ""; state_material_name = ""; - curr_geom = create_geometry( - curr_geom, GEOM_MESH, line.trim(), r_global_vertices, r_all_geometries, offsets); + curr_geom = create_geometry(curr_geom, + GEOM_MESH, + StringRef(p, end).trim(), + r_global_vertices, + r_all_geometries, + offsets); } /* Groups. */ - else if (parse_keyword(line, "g")) { - geom_update_group(line.trim(), state_group_name); + else if (parse_keyword(p, end, "g")) { + geom_update_group(StringRef(p, end).trim(), state_group_name); int new_index = curr_geom->group_indices_.size(); state_group_index = curr_geom->group_indices_.lookup_or_add(state_group_name, new_index); if (new_index == state_group_index) { @@ -448,12 +461,12 @@ void OBJParser::parse(Vector<std::unique_ptr<Geometry>> &r_all_geometries, } } /* Smoothing groups. */ - else if (parse_keyword(line, "s")) { - geom_update_smooth_group(line, state_shaded_smooth); + else if (parse_keyword(p, end, "s")) { + geom_update_smooth_group(p, end, state_shaded_smooth); } /* Materials and their libraries. */ - else if (parse_keyword(line, "usemtl")) { - state_material_name = line.trim(); + else if (parse_keyword(p, end, "usemtl")) { + state_material_name = StringRef(p, end).trim(); int new_mat_index = curr_geom->material_indices_.size(); state_material_index = curr_geom->material_indices_.lookup_or_add(state_material_name, new_mat_index); @@ -461,32 +474,32 @@ void OBJParser::parse(Vector<std::unique_ptr<Geometry>> &r_all_geometries, curr_geom->material_order_.append(state_material_name); } } - else if (parse_keyword(line, "mtllib")) { - add_mtl_library(line.trim()); + else if (parse_keyword(p, end, "mtllib")) { + add_mtl_library(StringRef(p, end).trim()); } /* Comments. */ - else if (line.startswith("#")) { + else if (*p == '#') { /* Nothing to do. */ } /* Curve related things. */ - else if (parse_keyword(line, "cstype")) { + else if (parse_keyword(p, end, "cstype")) { curr_geom = geom_set_curve_type( - curr_geom, line, r_global_vertices, state_group_name, offsets, r_all_geometries); + curr_geom, p, end, r_global_vertices, state_group_name, offsets, r_all_geometries); } - else if (parse_keyword(line, "deg")) { - geom_set_curve_degree(curr_geom, line); + else if (parse_keyword(p, end, "deg")) { + geom_set_curve_degree(curr_geom, p, end); } - else if (parse_keyword(line, "curv")) { - geom_add_curve_vertex_indices(curr_geom, line, r_global_vertices); + else if (parse_keyword(p, end, "curv")) { + geom_add_curve_vertex_indices(curr_geom, p, end, r_global_vertices); } - else if (parse_keyword(line, "parm")) { - geom_add_curve_parameters(curr_geom, line); + else if (parse_keyword(p, end, "parm")) { + geom_add_curve_parameters(curr_geom, p, end); } - else if (line.startswith("end")) { + else if (StringRef(p, end).startswith("end")) { /* End of curve definition, nothing else to do. */ } else { - std::cout << "OBJ element not recognized: '" << line << "'" << std::endl; + std::cout << "OBJ element not recognized: '" << std::string(p, end) << "'" << std::endl; } } @@ -500,33 +513,33 @@ void OBJParser::parse(Vector<std::unique_ptr<Geometry>> &r_all_geometries, add_default_mtl_library(); } -static eMTLSyntaxElement mtl_line_start_to_enum(StringRef &line) +static eMTLSyntaxElement mtl_line_start_to_enum(const char *&p, const char *end) { - if (parse_keyword(line, "map_Kd")) { + if (parse_keyword(p, end, "map_Kd")) { return eMTLSyntaxElement::map_Kd; } - if (parse_keyword(line, "map_Ks")) { + if (parse_keyword(p, end, "map_Ks")) { return eMTLSyntaxElement::map_Ks; } - if (parse_keyword(line, "map_Ns")) { + if (parse_keyword(p, end, "map_Ns")) { return eMTLSyntaxElement::map_Ns; } - if (parse_keyword(line, "map_d")) { + if (parse_keyword(p, end, "map_d")) { return eMTLSyntaxElement::map_d; } - if (parse_keyword(line, "refl")) { + if (parse_keyword(p, end, "refl")) { return eMTLSyntaxElement::map_refl; } - if (parse_keyword(line, "map_refl")) { + if (parse_keyword(p, end, "map_refl")) { return eMTLSyntaxElement::map_refl; } - if (parse_keyword(line, "map_Ke")) { + if (parse_keyword(p, end, "map_Ke")) { return eMTLSyntaxElement::map_Ke; } - if (parse_keyword(line, "bump")) { + if (parse_keyword(p, end, "bump")) { return eMTLSyntaxElement::map_Bump; } - if (parse_keyword(line, "map_Bump") || parse_keyword(line, "map_bump")) { + if (parse_keyword(p, end, "map_Bump") || parse_keyword(p, end, "map_bump")) { return eMTLSyntaxElement::map_Bump; } return eMTLSyntaxElement::string; @@ -544,39 +557,43 @@ static const std::pair<StringRef, int> unsupported_texture_options[] = { {"-texres", 1}, }; -static bool parse_texture_option(StringRef &line, MTLMaterial *material, tex_map_XX &tex_map) +static bool parse_texture_option(const char *&p, + const char *end, + MTLMaterial *material, + tex_map_XX &tex_map) { - line = drop_whitespace(line); - if (parse_keyword(line, "-o")) { - line = parse_floats(line, 0.0f, tex_map.translation, 3); + p = drop_whitespace(p, end); + if (parse_keyword(p, end, "-o")) { + p = parse_floats(p, end, 0.0f, tex_map.translation, 3); return true; } - if (parse_keyword(line, "-s")) { - line = parse_floats(line, 1.0f, tex_map.scale, 3); + if (parse_keyword(p, end, "-s")) { + p = parse_floats(p, end, 1.0f, tex_map.scale, 3); return true; } - if (parse_keyword(line, "-bm")) { - line = parse_float(line, 1.0f, material->map_Bump_strength); + if (parse_keyword(p, end, "-bm")) { + p = parse_float(p, end, 1.0f, material->map_Bump_strength); return true; } - if (parse_keyword(line, "-type")) { - line = drop_whitespace(line); + if (parse_keyword(p, end, "-type")) { + p = drop_whitespace(p, end); /* Only sphere is supported. */ tex_map.projection_type = SHD_PROJ_SPHERE; + const StringRef line = StringRef(p, end); if (!line.startswith("sphere")) { std::cerr << "OBJ import: only sphere MTL projection type is supported: '" << line << "'" << std::endl; } - line = drop_non_whitespace(line); + p = drop_non_whitespace(p, end); return true; } /* Check for unsupported options and skip them. */ for (const auto &opt : unsupported_texture_options) { - if (parse_keyword(line, opt.first)) { + if (parse_keyword(p, end, opt.first)) { /* Drop the arguments. */ for (int i = 0; i < opt.second; ++i) { - line = drop_whitespace(line); - line = drop_non_whitespace(line); + p = drop_whitespace(p, end); + p = drop_non_whitespace(p, end); } return true; } @@ -585,15 +602,19 @@ static bool parse_texture_option(StringRef &line, MTLMaterial *material, tex_map return false; } -static void parse_texture_map(StringRef line, MTLMaterial *material, const char *mtl_dir_path) +static void parse_texture_map(const char *p, + const char *end, + MTLMaterial *material, + const char *mtl_dir_path) { + const StringRef line = StringRef(p, end); bool is_map = line.startswith("map_"); bool is_refl = line.startswith("refl"); bool is_bump = line.startswith("bump"); if (!is_map && !is_refl && !is_bump) { return; } - eMTLSyntaxElement key = mtl_line_start_to_enum(line); + eMTLSyntaxElement key = mtl_line_start_to_enum(p, end); if (key == eMTLSyntaxElement::string || !material->texture_maps.contains(key)) { /* No supported texture map found. */ std::cerr << "OBJ import: MTL texture map type not supported: '" << line << "'" << std::endl; @@ -603,12 +624,11 @@ static void parse_texture_map(StringRef line, MTLMaterial *material, const char tex_map.mtl_dir_path = mtl_dir_path; /* Parse texture map options. */ - while (parse_texture_option(line, material, tex_map)) { + while (parse_texture_option(p, end, material, tex_map)) { } /* What remains is the image path. */ - line = line.trim(); - tex_map.image_path = line; + tex_map.image_path = StringRef(p, end).trim(); } Span<std::string> OBJParser::mtl_libraries() const @@ -666,51 +686,53 @@ void MTLParser::parse_and_store(Map<string, std::unique_ptr<MTLMaterial>> &r_mat StringRef buffer_str{(const char *)buffer, (int64_t)buffer_len}; while (!buffer_str.is_empty()) { - StringRef line = read_next_line(buffer_str); - line = drop_whitespace(line); - if (line.is_empty()) { + const StringRef line = read_next_line(buffer_str); + const char *p = line.begin(), *end = line.end(); + p = drop_whitespace(p, end); + if (p == end) { continue; } - if (parse_keyword(line, "newmtl")) { - line = line.trim(); - if (r_materials.contains(line)) { + if (parse_keyword(p, end, "newmtl")) { + StringRef mat_name = StringRef(p, end).trim(); + if (r_materials.contains(mat_name)) { material = nullptr; } else { - material = r_materials.lookup_or_add(string(line), std::make_unique<MTLMaterial>()).get(); + material = + r_materials.lookup_or_add(string(mat_name), std::make_unique<MTLMaterial>()).get(); } } else if (material != nullptr) { - if (parse_keyword(line, "Ns")) { - parse_float(line, 324.0f, material->Ns); + if (parse_keyword(p, end, "Ns")) { + parse_float(p, end, 324.0f, material->Ns); } - else if (parse_keyword(line, "Ka")) { - parse_floats(line, 0.0f, material->Ka, 3); + else if (parse_keyword(p, end, "Ka")) { + parse_floats(p, end, 0.0f, material->Ka, 3); } - else if (parse_keyword(line, "Kd")) { - parse_floats(line, 0.8f, material->Kd, 3); + else if (parse_keyword(p, end, "Kd")) { + parse_floats(p, end, 0.8f, material->Kd, 3); } - else if (parse_keyword(line, "Ks")) { - parse_floats(line, 0.5f, material->Ks, 3); + else if (parse_keyword(p, end, "Ks")) { + parse_floats(p, end, 0.5f, material->Ks, 3); } - else if (parse_keyword(line, "Ke")) { - parse_floats(line, 0.0f, material->Ke, 3); + else if (parse_keyword(p, end, "Ke")) { + parse_floats(p, end, 0.0f, material->Ke, 3); } - else if (parse_keyword(line, "Ni")) { - parse_float(line, 1.45f, material->Ni); + else if (parse_keyword(p, end, "Ni")) { + parse_float(p, end, 1.45f, material->Ni); } - else if (parse_keyword(line, "d")) { - parse_float(line, 1.0f, material->d); + else if (parse_keyword(p, end, "d")) { + parse_float(p, end, 1.0f, material->d); } - else if (parse_keyword(line, "illum")) { + else if (parse_keyword(p, end, "illum")) { /* Some files incorrectly use a float (T60135). */ float val; - parse_float(line, 1.0f, val); + parse_float(p, end, 1.0f, val); material->illum = val; } else { - parse_texture_map(line, material, mtl_dir_path_); + parse_texture_map(p, end, material, mtl_dir_path_); } } } |