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 | |
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
4 files changed, 217 insertions, 171 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_); } } } diff --git a/source/blender/io/wavefront_obj/importer/obj_import_string_utils.cc b/source/blender/io/wavefront_obj/importer/obj_import_string_utils.cc index c60306c8375..ffafbe41d0f 100644 --- a/source/blender/io/wavefront_obj/importer/obj_import_string_utils.cc +++ b/source/blender/io/wavefront_obj/importer/obj_import_string_utils.cc @@ -38,62 +38,63 @@ static bool is_whitespace(char c) return c <= ' ' || c == '\\'; } -StringRef drop_whitespace(StringRef str) +const char *drop_whitespace(const char *p, const char *end) { - while (!str.is_empty() && is_whitespace(str[0])) { - str = str.drop_prefix(1); + while (p < end && is_whitespace(*p)) { + ++p; } - return str; + return p; } -StringRef drop_non_whitespace(StringRef str) +const char *drop_non_whitespace(const char *p, const char *end) { - while (!str.is_empty() && !is_whitespace(str[0])) { - str = str.drop_prefix(1); + while (p < end && !is_whitespace(*p)) { + ++p; } - return str; + return p; } -static StringRef drop_plus(StringRef str) +static const char *drop_plus(const char *p, const char *end) { - if (!str.is_empty() && str[0] == '+') { - str = str.drop_prefix(1); + if (p < end && *p == '+') { + ++p; } - return str; + return p; } -StringRef parse_float(StringRef str, float fallback, float &dst, bool skip_space) +const char *parse_float( + const char *p, const char *end, float fallback, float &dst, bool skip_space) { if (skip_space) { - str = drop_whitespace(str); + p = drop_whitespace(p, end); } - str = drop_plus(str); - fast_float::from_chars_result res = fast_float::from_chars(str.begin(), str.end(), dst); + p = drop_plus(p, end); + fast_float::from_chars_result res = fast_float::from_chars(p, end, dst); if (res.ec == std::errc::invalid_argument || res.ec == std::errc::result_out_of_range) { dst = fallback; } - return StringRef(res.ptr, str.end()); + return res.ptr; } -StringRef parse_floats(StringRef str, float fallback, float *dst, int count) +const char *parse_floats(const char *p, const char *end, float fallback, float *dst, int count) { for (int i = 0; i < count; ++i) { - str = parse_float(str, fallback, dst[i]); + p = parse_float(p, end, fallback, dst[i]); } - return str; + return p; } -StringRef parse_int(StringRef str, int fallback, int &dst, bool skip_space) +const char *parse_int(const char *p, const char *end, int fallback, int &dst, bool skip_space) { if (skip_space) { - str = drop_whitespace(str); + p = drop_whitespace(p, end); } - str = drop_plus(str); - std::from_chars_result res = std::from_chars(str.begin(), str.end(), dst); + p = drop_plus(p, end); + std::from_chars_result res = std::from_chars(p, end, dst); if (res.ec == std::errc::invalid_argument || res.ec == std::errc::result_out_of_range) { dst = fallback; } - return StringRef(res.ptr, str.end()); + return res.ptr; } } // namespace blender::io::obj diff --git a/source/blender/io/wavefront_obj/importer/obj_import_string_utils.hh b/source/blender/io/wavefront_obj/importer/obj_import_string_utils.hh index 532224569ac..3f428b1ab5c 100644 --- a/source/blender/io/wavefront_obj/importer/obj_import_string_utils.hh +++ b/source/blender/io/wavefront_obj/importer/obj_import_string_utils.hh @@ -9,6 +9,14 @@ * The utilities are not directly usable by other formats, since * they treat backslash (\) as a whitespace character (OBJ format * allows backslashes to function as a line-continuation character). + * + * Many of these functions take two pointers (p, end) indicating + * which part of a string to operate on, and return a possibly + * changed new start of the string. They could be taking a StringRef + * as input and returning a new StringRef, but this is a hot path + * in OBJ parsing, and the StringRef approach does lose performance + * (mostly due to return of StringRef being two register-size values + * instead of just one pointer). */ namespace blender::io::obj { @@ -26,16 +34,16 @@ namespace blender::io::obj { StringRef read_next_line(StringRef &buffer); /** - * Drop leading white-space from a StringRef. + * Drop leading white-space from a string part. * Note that backslash character is considered white-space. */ -StringRef drop_whitespace(StringRef str); +const char *drop_whitespace(const char *p, const char *end); /** - * Drop leading non-white-space from a StringRef. + * Drop leading non-white-space from a string part. * Note that backslash character is considered white-space. */ -StringRef drop_non_whitespace(StringRef str); +const char *drop_non_whitespace(const char *p, const char *end); /** * Parse an integer from an input string. @@ -44,9 +52,10 @@ StringRef drop_non_whitespace(StringRef str); * number can't be parsed (invalid syntax, out of range), * `fallback` value is stored instead. * - * Returns the remainder of the input string after parsing. + * Returns the start of remainder of the input string after parsing. */ -StringRef parse_int(StringRef str, int fallback, int &dst, bool skip_space = true); +const char *parse_int( + const char *p, const char *end, int fallback, int &dst, bool skip_space = true); /** * Parse a float from an input string. @@ -55,9 +64,10 @@ StringRef parse_int(StringRef str, int fallback, int &dst, bool skip_space = tru * number can't be parsed (invalid syntax, out of range), * `fallback` value is stored instead. * - * Returns the remainder of the input string after parsing. + * Returns the start of remainder of the input string after parsing. */ -StringRef parse_float(StringRef str, float fallback, float &dst, bool skip_space = true); +const char *parse_float( + const char *p, const char *end, float fallback, float &dst, bool skip_space = true); /** * Parse a number of white-space separated floats from an input string. @@ -65,8 +75,8 @@ StringRef parse_float(StringRef str, float fallback, float &dst, bool skip_space * number can't be parsed (invalid syntax, out of range), * `fallback` value is stored instead. * - * Returns the remainder of the input string after parsing. + * Returns the start of remainder of the input string after parsing. */ -StringRef parse_floats(StringRef str, float fallback, float *dst, int count); +const char *parse_floats(const char *p, const char *end, float fallback, float *dst, int count); } // namespace blender::io::obj diff --git a/source/blender/io/wavefront_obj/tests/obj_import_string_utils_tests.cc b/source/blender/io/wavefront_obj/tests/obj_import_string_utils_tests.cc index addb1fa473e..46e093bb8a7 100644 --- a/source/blender/io/wavefront_obj/tests/obj_import_string_utils_tests.cc +++ b/source/blender/io/wavefront_obj/tests/obj_import_string_utils_tests.cc @@ -21,6 +21,19 @@ TEST(obj_import_string_utils, read_next_line) EXPECT_TRUE(s.is_empty()); } +static StringRef drop_whitespace(StringRef s) +{ + return StringRef(drop_whitespace(s.begin(), s.end()), s.end()); +} +static StringRef parse_int(StringRef s, int fallback, int &dst, bool skip_space = true) +{ + return StringRef(parse_int(s.begin(), s.end(), fallback, dst, skip_space), s.end()); +} +static StringRef parse_float(StringRef s, float fallback, float &dst, bool skip_space = true) +{ + return StringRef(parse_float(s.begin(), s.end(), fallback, dst, skip_space), s.end()); +} + TEST(obj_import_string_utils, drop_whitespace) { /* Empty */ |