Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAras Pranckevicius <aras_p>2022-01-30 23:03:31 +0300
committerHoward Trickey <howard.trickey@gmail.com>2022-01-30 23:03:31 +0300
commit1f7013fb90b30d1bfcbc832f91bf18d707eaae8c (patch)
tree2fdabcdaca33f434e935300931f38840f408ab33 /source/blender/io/wavefront_obj
parentb315678feafa0372365e38394fc1a37812debfa2 (diff)
Speed up the new OBJ exporter via bigger write buffer and parallelization.
This is a patch from Aras Pranckevicius, D13927. See that patch for full details. On Windows, the many small fprintfs were taking up a large amount of time and significant speedup comes from using snprintf into chained buffers, and writing them all out later. On both Windows and Linux, parallelizing the processing by Object can also lead to a significant increase in speed. The 3.0 splash screen scene exports 8 times faster than the current C++ exporter on a Windows machine with 32 threads, and 5.8 times faster on a Linux machine with 48 threads. There is admittedly more memory usage for this, but it is still using 25 times less memory than the old python exporter on the 3.0 splash screen scene, so this seems an acceptable tradeoff. If use cases come up for exporting obj files that exceed the memory size of users, a flag could be added to not parallelize and write the buffers out every so often.
Diffstat (limited to 'source/blender/io/wavefront_obj')
-rw-r--r--source/blender/io/wavefront_obj/CMakeLists.txt6
-rw-r--r--source/blender/io/wavefront_obj/exporter/obj_export_file_writer.cc240
-rw-r--r--source/blender/io/wavefront_obj/exporter/obj_export_file_writer.hh84
-rw-r--r--source/blender/io/wavefront_obj/exporter/obj_export_io.hh94
-rw-r--r--source/blender/io/wavefront_obj/exporter/obj_export_mesh.cc35
-rw-r--r--source/blender/io/wavefront_obj/exporter/obj_export_mesh.hh32
-rw-r--r--source/blender/io/wavefront_obj/exporter/obj_exporter.cc110
-rw-r--r--source/blender/io/wavefront_obj/tests/obj_exporter_tests.cc34
8 files changed, 433 insertions, 202 deletions
diff --git a/source/blender/io/wavefront_obj/CMakeLists.txt b/source/blender/io/wavefront_obj/CMakeLists.txt
index 296dd70b5a2..0b1be7946cf 100644
--- a/source/blender/io/wavefront_obj/CMakeLists.txt
+++ b/source/blender/io/wavefront_obj/CMakeLists.txt
@@ -56,6 +56,12 @@ set(LIB
bf_blenkernel
)
+if(WITH_TBB)
+ add_definitions(-DWITH_TBB)
+ list(APPEND INC_SYS ${TBB_INCLUDE_DIRS})
+ list(APPEND LIB ${TBB_LIBRARIES})
+endif()
+
blender_add_lib(bf_wavefront_obj "${SRC}" "${INC}" "${INC_SYS}" "${LIB}")
if(WITH_GTESTS)
diff --git a/source/blender/io/wavefront_obj/exporter/obj_export_file_writer.cc b/source/blender/io/wavefront_obj/exporter/obj_export_file_writer.cc
index d31353c4a76..4f5321019d5 100644
--- a/source/blender/io/wavefront_obj/exporter/obj_export_file_writer.cc
+++ b/source/blender/io/wavefront_obj/exporter/obj_export_file_writer.cc
@@ -52,68 +52,75 @@ const char *DEFORM_GROUP_DISABLED = "off";
* So an empty material name is written. */
const char *MATERIAL_GROUP_DISABLED = "";
-void OBJWriter::write_vert_uv_normal_indices(Span<int> vert_indices,
+void OBJWriter::write_vert_uv_normal_indices(FormatHandler<eFileType::OBJ> &fh,
+ const IndexOffsets &offsets,
+ Span<int> vert_indices,
Span<int> uv_indices,
Span<int> normal_indices) const
{
BLI_assert(vert_indices.size() == uv_indices.size() &&
vert_indices.size() == normal_indices.size());
- file_handler_->write<eOBJSyntaxElement::poly_element_begin>();
+ fh.write<eOBJSyntaxElement::poly_element_begin>();
for (int j = 0; j < vert_indices.size(); j++) {
- file_handler_->write<eOBJSyntaxElement::vertex_uv_normal_indices>(
- vert_indices[j] + index_offsets_.vertex_offset + 1,
- uv_indices[j] + index_offsets_.uv_vertex_offset + 1,
- normal_indices[j] + index_offsets_.normal_offset + 1);
+ fh.write<eOBJSyntaxElement::vertex_uv_normal_indices>(
+ vert_indices[j] + offsets.vertex_offset + 1,
+ uv_indices[j] + offsets.uv_vertex_offset + 1,
+ normal_indices[j] + offsets.normal_offset + 1);
}
- file_handler_->write<eOBJSyntaxElement::poly_element_end>();
+ fh.write<eOBJSyntaxElement::poly_element_end>();
}
-void OBJWriter::write_vert_normal_indices(Span<int> vert_indices,
+void OBJWriter::write_vert_normal_indices(FormatHandler<eFileType::OBJ> &fh,
+ const IndexOffsets &offsets,
+ Span<int> vert_indices,
Span<int> /*uv_indices*/,
Span<int> normal_indices) const
{
BLI_assert(vert_indices.size() == normal_indices.size());
- file_handler_->write<eOBJSyntaxElement::poly_element_begin>();
+ fh.write<eOBJSyntaxElement::poly_element_begin>();
for (int j = 0; j < vert_indices.size(); j++) {
- file_handler_->write<eOBJSyntaxElement::vertex_normal_indices>(
- vert_indices[j] + index_offsets_.vertex_offset + 1,
- normal_indices[j] + index_offsets_.normal_offset + 1);
+ fh.write<eOBJSyntaxElement::vertex_normal_indices>(vert_indices[j] + offsets.vertex_offset + 1,
+ normal_indices[j] + offsets.normal_offset +
+ 1);
}
- file_handler_->write<eOBJSyntaxElement::poly_element_end>();
+ fh.write<eOBJSyntaxElement::poly_element_end>();
}
-void OBJWriter::write_vert_uv_indices(Span<int> vert_indices,
+void OBJWriter::write_vert_uv_indices(FormatHandler<eFileType::OBJ> &fh,
+ const IndexOffsets &offsets,
+ Span<int> vert_indices,
Span<int> uv_indices,
Span<int> /*normal_indices*/) const
{
BLI_assert(vert_indices.size() == uv_indices.size());
- file_handler_->write<eOBJSyntaxElement::poly_element_begin>();
+ fh.write<eOBJSyntaxElement::poly_element_begin>();
for (int j = 0; j < vert_indices.size(); j++) {
- file_handler_->write<eOBJSyntaxElement::vertex_uv_indices>(
- vert_indices[j] + index_offsets_.vertex_offset + 1,
- uv_indices[j] + index_offsets_.uv_vertex_offset + 1);
+ fh.write<eOBJSyntaxElement::vertex_uv_indices>(vert_indices[j] + offsets.vertex_offset + 1,
+ uv_indices[j] + offsets.uv_vertex_offset + 1);
}
- file_handler_->write<eOBJSyntaxElement::poly_element_end>();
+ fh.write<eOBJSyntaxElement::poly_element_end>();
}
-void OBJWriter::write_vert_indices(Span<int> vert_indices,
+void OBJWriter::write_vert_indices(FormatHandler<eFileType::OBJ> &fh,
+ const IndexOffsets &offsets,
+ Span<int> vert_indices,
Span<int> /*uv_indices*/,
Span<int> /*normal_indices*/) const
{
- file_handler_->write<eOBJSyntaxElement::poly_element_begin>();
+ fh.write<eOBJSyntaxElement::poly_element_begin>();
for (const int vert_index : vert_indices) {
- file_handler_->write<eOBJSyntaxElement::vertex_indices>(vert_index +
- index_offsets_.vertex_offset + 1);
+ fh.write<eOBJSyntaxElement::vertex_indices>(vert_index + offsets.vertex_offset + 1);
}
- file_handler_->write<eOBJSyntaxElement::poly_element_end>();
+ fh.write<eOBJSyntaxElement::poly_element_end>();
}
void OBJWriter::write_header() const
{
using namespace std::string_literals;
- file_handler_->write<eOBJSyntaxElement::string>("# Blender "s + BKE_blender_version_string() +
- "\n");
- file_handler_->write<eOBJSyntaxElement::string>("# www.blender.org\n");
+ FormatHandler<eFileType::OBJ> fh;
+ fh.write<eOBJSyntaxElement::string>("# Blender "s + BKE_blender_version_string() + "\n");
+ fh.write<eOBJSyntaxElement::string>("# www.blender.org\n");
+ fh.write_to_file(outfile_);
}
void OBJWriter::write_mtllib_name(const StringRefNull mtl_filepath) const
@@ -122,10 +129,13 @@ void OBJWriter::write_mtllib_name(const StringRefNull mtl_filepath) const
char mtl_file_name[FILE_MAXFILE];
char mtl_dir_name[FILE_MAXDIR];
BLI_split_dirfile(mtl_filepath.data(), mtl_dir_name, mtl_file_name, FILE_MAXDIR, FILE_MAXFILE);
- file_handler_->write<eOBJSyntaxElement::mtllib>(mtl_file_name);
+ FormatHandler<eFileType::OBJ> fh;
+ fh.write<eOBJSyntaxElement::mtllib>(mtl_file_name);
+ fh.write_to_file(outfile_);
}
-void OBJWriter::write_object_group(const OBJMesh &obj_mesh_data) const
+void OBJWriter::write_object_group(FormatHandler<eFileType::OBJ> &fh,
+ const OBJMesh &obj_mesh_data) const
{
/* "o object_name" is not mandatory. A valid .OBJ file may contain neither
* "o name" nor "g group_name". */
@@ -138,54 +148,52 @@ void OBJWriter::write_object_group(const OBJMesh &obj_mesh_data) const
const char *object_material_name = obj_mesh_data.get_object_material_name(0);
if (export_params_.export_materials && export_params_.export_material_groups &&
object_material_name) {
- file_handler_->write<eOBJSyntaxElement::object_group>(object_name + "_" + object_mesh_name +
- "_" + object_material_name);
- return;
+ fh.write<eOBJSyntaxElement::object_group>(object_name + "_" + object_mesh_name + "_" +
+ object_material_name);
+ }
+ else {
+ fh.write<eOBJSyntaxElement::object_group>(object_name + "_" + object_mesh_name);
}
- file_handler_->write<eOBJSyntaxElement::object_group>(object_name + "_" + object_mesh_name);
}
-void OBJWriter::write_object_name(const OBJMesh &obj_mesh_data) const
+void OBJWriter::write_object_name(FormatHandler<eFileType::OBJ> &fh,
+ const OBJMesh &obj_mesh_data) const
{
const char *object_name = obj_mesh_data.get_object_name();
if (export_params_.export_object_groups) {
- write_object_group(obj_mesh_data);
+ write_object_group(fh, obj_mesh_data);
return;
}
- file_handler_->write<eOBJSyntaxElement::object_name>(object_name);
+ fh.write<eOBJSyntaxElement::object_name>(object_name);
}
-void OBJWriter::write_vertex_coords(const OBJMesh &obj_mesh_data) const
+void OBJWriter::write_vertex_coords(FormatHandler<eFileType::OBJ> &fh,
+ const OBJMesh &obj_mesh_data) const
{
const int tot_vertices = obj_mesh_data.tot_vertices();
for (int i = 0; i < tot_vertices; i++) {
float3 vertex = obj_mesh_data.calc_vertex_coords(i, export_params_.scaling_factor);
- file_handler_->write<eOBJSyntaxElement::vertex_coords>(vertex[0], vertex[1], vertex[2]);
+ fh.write<eOBJSyntaxElement::vertex_coords>(vertex[0], vertex[1], vertex[2]);
}
}
-void OBJWriter::write_uv_coords(OBJMesh &r_obj_mesh_data) const
+void OBJWriter::write_uv_coords(FormatHandler<eFileType::OBJ> &fh, OBJMesh &r_obj_mesh_data) const
{
- Vector<std::array<float, 2>> uv_coords;
- /* UV indices are calculated and stored in an OBJMesh member here. */
- r_obj_mesh_data.store_uv_coords_and_indices(uv_coords);
-
- for (const std::array<float, 2> &uv_vertex : uv_coords) {
- file_handler_->write<eOBJSyntaxElement::uv_vertex_coords>(uv_vertex[0], uv_vertex[1]);
+ for (const float2 &uv_vertex : r_obj_mesh_data.get_uv_coords()) {
+ fh.write<eOBJSyntaxElement::uv_vertex_coords>(uv_vertex[0], uv_vertex[1]);
}
}
-void OBJWriter::write_poly_normals(OBJMesh &obj_mesh_data)
+void OBJWriter::write_poly_normals(FormatHandler<eFileType::OBJ> &fh, OBJMesh &obj_mesh_data)
{
- obj_mesh_data.ensure_mesh_normals();
- Vector<float3> normals;
- obj_mesh_data.store_normal_coords_and_indices(normals);
- for (const float3 &normal : normals) {
- file_handler_->write<eOBJSyntaxElement::normal>(normal[0], normal[1], normal[2]);
+ /* Poly normals should be calculated earlier via store_normal_coords_and_indices. */
+ for (const float3 &normal : obj_mesh_data.get_normal_coords()) {
+ fh.write<eOBJSyntaxElement::normal>(normal[0], normal[1], normal[2]);
}
}
-int OBJWriter::write_smooth_group(const OBJMesh &obj_mesh_data,
+int OBJWriter::write_smooth_group(FormatHandler<eFileType::OBJ> &fh,
+ const OBJMesh &obj_mesh_data,
const int poly_index,
const int last_poly_smooth_group) const
{
@@ -203,11 +211,12 @@ int OBJWriter::write_smooth_group(const OBJMesh &obj_mesh_data,
/* Group has already been written, even if it is "s 0". */
return current_group;
}
- file_handler_->write<eOBJSyntaxElement::smooth_group>(current_group);
+ fh.write<eOBJSyntaxElement::smooth_group>(current_group);
return current_group;
}
-int16_t OBJWriter::write_poly_material(const OBJMesh &obj_mesh_data,
+int16_t OBJWriter::write_poly_material(FormatHandler<eFileType::OBJ> &fh,
+ const OBJMesh &obj_mesh_data,
const int poly_index,
const int16_t last_poly_mat_nr,
std::function<const char *(int)> matname_fn) const
@@ -221,23 +230,25 @@ int16_t OBJWriter::write_poly_material(const OBJMesh &obj_mesh_data,
if (last_poly_mat_nr == current_mat_nr) {
return current_mat_nr;
}
+
if (current_mat_nr == NOT_FOUND) {
- file_handler_->write<eOBJSyntaxElement::poly_usemtl>(MATERIAL_GROUP_DISABLED);
+ fh.write<eOBJSyntaxElement::poly_usemtl>(MATERIAL_GROUP_DISABLED);
return current_mat_nr;
}
if (export_params_.export_object_groups) {
- write_object_group(obj_mesh_data);
+ write_object_group(fh, obj_mesh_data);
}
const char *mat_name = matname_fn(current_mat_nr);
if (!mat_name) {
mat_name = MATERIAL_GROUP_DISABLED;
}
- file_handler_->write<eOBJSyntaxElement::poly_usemtl>(mat_name);
+ fh.write<eOBJSyntaxElement::poly_usemtl>(mat_name);
return current_mat_nr;
}
-int16_t OBJWriter::write_vertex_group(const OBJMesh &obj_mesh_data,
+int16_t OBJWriter::write_vertex_group(FormatHandler<eFileType::OBJ> &fh,
+ const OBJMesh &obj_mesh_data,
const int poly_index,
const int16_t last_poly_vertex_group) const
{
@@ -251,11 +262,12 @@ int16_t OBJWriter::write_vertex_group(const OBJMesh &obj_mesh_data,
return current_group;
}
if (current_group == NOT_FOUND) {
- file_handler_->write<eOBJSyntaxElement::object_group>(DEFORM_GROUP_DISABLED);
- return current_group;
+ fh.write<eOBJSyntaxElement::object_group>(DEFORM_GROUP_DISABLED);
+ }
+ else {
+ fh.write<eOBJSyntaxElement::object_group>(
+ obj_mesh_data.get_poly_deform_group_name(current_group));
}
- file_handler_->write<eOBJSyntaxElement::object_group>(
- obj_mesh_data.get_poly_deform_group_name(current_group));
return current_group;
}
@@ -278,7 +290,9 @@ OBJWriter::func_vert_uv_normal_indices OBJWriter::get_poly_element_writer(
return &OBJWriter::write_vert_indices;
}
-void OBJWriter::write_poly_elements(const OBJMesh &obj_mesh_data,
+void OBJWriter::write_poly_elements(FormatHandler<eFileType::OBJ> &fh,
+ const IndexOffsets &offsets,
+ const OBJMesh &obj_mesh_data,
std::function<const char *(int)> matname_fn)
{
int last_poly_smooth_group = NEGATIVE_INIT;
@@ -294,16 +308,19 @@ void OBJWriter::write_poly_elements(const OBJMesh &obj_mesh_data,
Span<int> poly_uv_indices = obj_mesh_data.calc_poly_uv_indices(i);
Vector<int> poly_normal_indices = obj_mesh_data.calc_poly_normal_indices(i);
- last_poly_smooth_group = write_smooth_group(obj_mesh_data, i, last_poly_smooth_group);
- last_poly_vertex_group = write_vertex_group(obj_mesh_data, i, last_poly_vertex_group);
- last_poly_mat_nr = write_poly_material(obj_mesh_data, i, last_poly_mat_nr, matname_fn);
- (this->*poly_element_writer)(poly_vertex_indices, poly_uv_indices, poly_normal_indices);
+ last_poly_smooth_group = write_smooth_group(fh, obj_mesh_data, i, last_poly_smooth_group);
+ last_poly_vertex_group = write_vertex_group(fh, obj_mesh_data, i, last_poly_vertex_group);
+ last_poly_mat_nr = write_poly_material(fh, obj_mesh_data, i, last_poly_mat_nr, matname_fn);
+ (this->*poly_element_writer)(
+ fh, offsets, poly_vertex_indices, poly_uv_indices, poly_normal_indices);
}
}
-void OBJWriter::write_edges_indices(const OBJMesh &obj_mesh_data) const
+void OBJWriter::write_edges_indices(FormatHandler<eFileType::OBJ> &fh,
+ const IndexOffsets &offsets,
+ const OBJMesh &obj_mesh_data) const
{
- obj_mesh_data.ensure_mesh_edges();
+ /* Note: ensure_mesh_edges should be called before. */
const int tot_edges = obj_mesh_data.tot_edges();
for (int edge_index = 0; edge_index < tot_edges; edge_index++) {
const std::optional<std::array<int, 2>> vertex_indices =
@@ -311,13 +328,13 @@ void OBJWriter::write_edges_indices(const OBJMesh &obj_mesh_data) const
if (!vertex_indices) {
continue;
}
- file_handler_->write<eOBJSyntaxElement::edge>(
- (*vertex_indices)[0] + index_offsets_.vertex_offset + 1,
- (*vertex_indices)[1] + index_offsets_.vertex_offset + 1);
+ fh.write<eOBJSyntaxElement::edge>((*vertex_indices)[0] + offsets.vertex_offset + 1,
+ (*vertex_indices)[1] + offsets.vertex_offset + 1);
}
}
-void OBJWriter::write_nurbs_curve(const OBJCurve &obj_nurbs_data) const
+void OBJWriter::write_nurbs_curve(FormatHandler<eFileType::OBJ> &fh,
+ const OBJCurve &obj_nurbs_data) const
{
const int total_splines = obj_nurbs_data.total_splines();
for (int spline_idx = 0; spline_idx < total_splines; spline_idx++) {
@@ -325,15 +342,15 @@ void OBJWriter::write_nurbs_curve(const OBJCurve &obj_nurbs_data) const
for (int vertex_idx = 0; vertex_idx < total_vertices; vertex_idx++) {
const float3 vertex_coords = obj_nurbs_data.vertex_coordinates(
spline_idx, vertex_idx, export_params_.scaling_factor);
- file_handler_->write<eOBJSyntaxElement::vertex_coords>(
+ fh.write<eOBJSyntaxElement::vertex_coords>(
vertex_coords[0], vertex_coords[1], vertex_coords[2]);
}
const char *nurbs_name = obj_nurbs_data.get_curve_name();
const int nurbs_degree = obj_nurbs_data.get_nurbs_degree(spline_idx);
- file_handler_->write<eOBJSyntaxElement::object_group>(nurbs_name);
- file_handler_->write<eOBJSyntaxElement::cstype>();
- file_handler_->write<eOBJSyntaxElement::nurbs_degree>(nurbs_degree);
+ fh.write<eOBJSyntaxElement::object_group>(nurbs_name);
+ fh.write<eOBJSyntaxElement::cstype>();
+ fh.write<eOBJSyntaxElement::nurbs_degree>(nurbs_degree);
/**
* The numbers written here are indices into the vertex coordinates written
* earlier, relative to the line that is going to be written.
@@ -342,36 +359,28 @@ void OBJWriter::write_nurbs_curve(const OBJCurve &obj_nurbs_data) const
* 0.0 1.0 -1 -2 -3 -4 -1 -2 -3 for a cyclic curve with 4 vertices.
*/
const int total_control_points = obj_nurbs_data.total_spline_control_points(spline_idx);
- file_handler_->write<eOBJSyntaxElement::curve_element_begin>();
+ fh.write<eOBJSyntaxElement::curve_element_begin>();
for (int i = 0; i < total_control_points; i++) {
/* "+1" to keep indices one-based, even if they're negative: i.e., -1 refers to the
* last vertex coordinate, -2 second last. */
- file_handler_->write<eOBJSyntaxElement::vertex_indices>(-((i % total_vertices) + 1));
+ fh.write<eOBJSyntaxElement::vertex_indices>(-((i % total_vertices) + 1));
}
- file_handler_->write<eOBJSyntaxElement::curve_element_end>();
+ fh.write<eOBJSyntaxElement::curve_element_end>();
/**
* In `parm u 0 0.1 ..` line:, (total control points + 2) equidistant numbers in the
* parameter range are inserted.
*/
- file_handler_->write<eOBJSyntaxElement::nurbs_parameter_begin>();
+ fh.write<eOBJSyntaxElement::nurbs_parameter_begin>();
for (int i = 1; i <= total_control_points + 2; i++) {
- file_handler_->write<eOBJSyntaxElement::nurbs_parameters>(1.0f * i /
- (total_control_points + 2 + 1));
+ fh.write<eOBJSyntaxElement::nurbs_parameters>(1.0f * i / (total_control_points + 2 + 1));
}
- file_handler_->write<eOBJSyntaxElement::nurbs_parameter_end>();
+ fh.write<eOBJSyntaxElement::nurbs_parameter_end>();
- file_handler_->write<eOBJSyntaxElement::nurbs_group_end>();
+ fh.write<eOBJSyntaxElement::nurbs_group_end>();
}
}
-void OBJWriter::update_index_offsets(const OBJMesh &obj_mesh_data)
-{
- index_offsets_.vertex_offset += obj_mesh_data.tot_vertices();
- index_offsets_.uv_vertex_offset += obj_mesh_data.tot_uv_vertices();
- index_offsets_.normal_offset += obj_mesh_data.tot_normal_indices();
-}
-
/* -------------------------------------------------------------------- */
/** \name .MTL writers.
* \{ */
@@ -394,18 +403,31 @@ MTLWriter::MTLWriter(const char *obj_filepath) noexcept(false)
if (!ok) {
throw std::system_error(ENAMETOOLONG, std::system_category(), "");
}
- file_handler_ = std::make_unique<FormattedFileHandler<eFileType::MTL>>(mtl_filepath_);
+ outfile_ = BLI_fopen(mtl_filepath_.c_str(), "wb");
+ if (!outfile_) {
+ throw std::system_error(errno, std::system_category(), "Cannot open file " + mtl_filepath_);
+ }
+}
+MTLWriter::~MTLWriter()
+{
+ if (outfile_) {
+ fmt_handler_.write_to_file(outfile_);
+ if (std::fclose(outfile_)) {
+ std::cerr << "Error: could not close the file '" << mtl_filepath_
+ << "' properly, it may be corrupted." << std::endl;
+ }
+ }
}
-void MTLWriter::write_header(const char *blen_filepath) const
+void MTLWriter::write_header(const char *blen_filepath)
{
using namespace std::string_literals;
const char *blen_basename = (blen_filepath && blen_filepath[0] != '\0') ?
BLI_path_basename(blen_filepath) :
"None";
- file_handler_->write<eMTLSyntaxElement::string>("# Blender "s + BKE_blender_version_string() +
- " MTL File: '" + blen_basename + "'\n");
- file_handler_->write<eMTLSyntaxElement::string>("# www.blender.org\n");
+ fmt_handler_.write<eMTLSyntaxElement::string>("# Blender "s + BKE_blender_version_string() +
+ " MTL File: '" + blen_basename + "'\n");
+ fmt_handler_.write<eMTLSyntaxElement::string>("# www.blender.org\n");
}
StringRefNull MTLWriter::mtl_file_path() const
@@ -415,18 +437,18 @@ StringRefNull MTLWriter::mtl_file_path() const
void MTLWriter::write_bsdf_properties(const MTLMaterial &mtl_material)
{
- file_handler_->write<eMTLSyntaxElement::Ns>(mtl_material.Ns);
- file_handler_->write<eMTLSyntaxElement::Ka>(
+ fmt_handler_.write<eMTLSyntaxElement::Ns>(mtl_material.Ns);
+ fmt_handler_.write<eMTLSyntaxElement::Ka>(
mtl_material.Ka.x, mtl_material.Ka.y, mtl_material.Ka.z);
- file_handler_->write<eMTLSyntaxElement::Kd>(
+ fmt_handler_.write<eMTLSyntaxElement::Kd>(
mtl_material.Kd.x, mtl_material.Kd.y, mtl_material.Kd.z);
- file_handler_->write<eMTLSyntaxElement::Ks>(
+ fmt_handler_.write<eMTLSyntaxElement::Ks>(
mtl_material.Ks.x, mtl_material.Ks.y, mtl_material.Ks.z);
- file_handler_->write<eMTLSyntaxElement::Ke>(
+ fmt_handler_.write<eMTLSyntaxElement::Ke>(
mtl_material.Ke.x, mtl_material.Ke.y, mtl_material.Ke.z);
- file_handler_->write<eMTLSyntaxElement::Ni>(mtl_material.Ni);
- file_handler_->write<eMTLSyntaxElement::d>(mtl_material.d);
- file_handler_->write<eMTLSyntaxElement::illum>(mtl_material.illum);
+ fmt_handler_.write<eMTLSyntaxElement::Ni>(mtl_material.Ni);
+ fmt_handler_.write<eMTLSyntaxElement::d>(mtl_material.d);
+ fmt_handler_.write<eMTLSyntaxElement::illum>(mtl_material.illum);
}
void MTLWriter::write_texture_map(
@@ -449,8 +471,8 @@ void MTLWriter::write_texture_map(
#define SYNTAX_DISPATCH(eMTLSyntaxElement) \
if (texture_map.key == eMTLSyntaxElement) { \
- file_handler_->write<eMTLSyntaxElement>(translation + scale + map_bump_strength, \
- texture_map.value.image_path); \
+ fmt_handler_.write<eMTLSyntaxElement>(translation + scale + map_bump_strength, \
+ texture_map.value.image_path); \
return; \
}
@@ -474,8 +496,8 @@ void MTLWriter::write_materials()
mtlmaterials_.end(),
[](const MTLMaterial &a, const MTLMaterial &b) { return a.name < b.name; });
for (const MTLMaterial &mtlmat : mtlmaterials_) {
- file_handler_->write<eMTLSyntaxElement::string>("\n");
- file_handler_->write<eMTLSyntaxElement::newmtl>(mtlmat.name);
+ fmt_handler_.write<eMTLSyntaxElement::string>("\n");
+ fmt_handler_.write<eMTLSyntaxElement::newmtl>(mtlmat.name);
write_bsdf_properties(mtlmat);
for (const Map<const eMTLSyntaxElement, tex_map_XX>::Item &texture_map :
mtlmat.texture_maps.items()) {
diff --git a/source/blender/io/wavefront_obj/exporter/obj_export_file_writer.hh b/source/blender/io/wavefront_obj/exporter/obj_export_file_writer.hh
index 7385d9fabe2..2620d65f28c 100644
--- a/source/blender/io/wavefront_obj/exporter/obj_export_file_writer.hh
+++ b/source/blender/io/wavefront_obj/exporter/obj_export_file_writer.hh
@@ -49,14 +49,29 @@ struct IndexOffsets {
class OBJWriter : NonMovable, NonCopyable {
private:
const OBJExportParams &export_params_;
- std::unique_ptr<FormattedFileHandler<eFileType::OBJ>> file_handler_ = nullptr;
- IndexOffsets index_offsets_{0, 0, 0};
+ std::string outfile_path_;
+ FILE *outfile_;
public:
OBJWriter(const char *filepath, const OBJExportParams &export_params) noexcept(false)
- : export_params_(export_params)
+ : export_params_(export_params), outfile_path_(filepath), outfile_(nullptr)
{
- file_handler_ = std::make_unique<FormattedFileHandler<eFileType::OBJ>>(filepath);
+ outfile_ = BLI_fopen(filepath, "wb");
+ if (!outfile_) {
+ throw std::system_error(errno, std::system_category(), "Cannot open file " + outfile_path_);
+ }
+ }
+ ~OBJWriter()
+ {
+ if (outfile_ && std::fclose(outfile_)) {
+ std::cerr << "Error: could not close the file '" << outfile_path_
+ << "' properly, it may be corrupted." << std::endl;
+ }
+ }
+
+ FILE *get_outfile() const
+ {
+ return outfile_;
}
void write_header() const;
@@ -64,11 +79,11 @@ class OBJWriter : NonMovable, NonCopyable {
/**
* Write object's name or group.
*/
- void write_object_name(const OBJMesh &obj_mesh_data) const;
+ void write_object_name(FormatHandler<eFileType::OBJ> &fh, const OBJMesh &obj_mesh_data) const;
/**
* Write an object's group with mesh and/or material name appended conditionally.
*/
- void write_object_group(const OBJMesh &obj_mesh_data) const;
+ void write_object_group(FormatHandler<eFileType::OBJ> &fh, const OBJMesh &obj_mesh_data) const;
/**
* Write file name of Material Library in .OBJ file.
*/
@@ -76,21 +91,22 @@ class OBJWriter : NonMovable, NonCopyable {
/**
* Write vertex coordinates for all vertices as "v x y z".
*/
- void write_vertex_coords(const OBJMesh &obj_mesh_data) const;
+ void write_vertex_coords(FormatHandler<eFileType::OBJ> &fh, const OBJMesh &obj_mesh_data) const;
/**
* Write UV vertex coordinates for all vertices as `vt u v`.
* \note UV indices are stored here, but written with polygons later.
*/
- void write_uv_coords(OBJMesh &obj_mesh_data) const;
+ void write_uv_coords(FormatHandler<eFileType::OBJ> &fh, OBJMesh &obj_mesh_data) const;
/**
* Write loop normals for smooth-shaded polygons, and polygon normals otherwise, as "vn x y z".
* \note Normal indices ares stored here, but written with polygons later.
*/
- void write_poly_normals(OBJMesh &obj_mesh_data);
+ void write_poly_normals(FormatHandler<eFileType::OBJ> &fh, OBJMesh &obj_mesh_data);
/**
* Write smooth group if polygon at the given index is shaded smooth else "s 0"
*/
- int write_smooth_group(const OBJMesh &obj_mesh_data,
+ int write_smooth_group(FormatHandler<eFileType::OBJ> &fh,
+ const OBJMesh &obj_mesh_data,
int poly_index,
int last_poly_smooth_group) const;
/**
@@ -98,14 +114,16 @@ class OBJWriter : NonMovable, NonCopyable {
* \return #mat_nr of the polygon at the given index.
* \note It doesn't write to the material library.
*/
- int16_t write_poly_material(const OBJMesh &obj_mesh_data,
+ int16_t write_poly_material(FormatHandler<eFileType::OBJ> &fh,
+ const OBJMesh &obj_mesh_data,
int poly_index,
int16_t last_poly_mat_nr,
std::function<const char *(int)> matname_fn) const;
/**
* Write the name of the deform group of a polygon.
*/
- int16_t write_vertex_group(const OBJMesh &obj_mesh_data,
+ int16_t write_vertex_group(FormatHandler<eFileType::OBJ> &fh,
+ const OBJMesh &obj_mesh_data,
int poly_index,
int16_t last_poly_vertex_group) const;
/**
@@ -115,25 +133,25 @@ class OBJWriter : NonMovable, NonCopyable {
* name used in the .obj file.
* \note UV indices were stored while writing UV vertices.
*/
- void write_poly_elements(const OBJMesh &obj_mesh_data,
+ void write_poly_elements(FormatHandler<eFileType::OBJ> &fh,
+ const IndexOffsets &offsets,
+ const OBJMesh &obj_mesh_data,
std::function<const char *(int)> matname_fn);
/**
* Write loose edges of a mesh as "l v1 v2".
*/
- void write_edges_indices(const OBJMesh &obj_mesh_data) const;
+ void write_edges_indices(FormatHandler<eFileType::OBJ> &fh,
+ const IndexOffsets &offsets,
+ const OBJMesh &obj_mesh_data) const;
/**
* Write a NURBS curve to the .OBJ file in parameter form.
*/
- void write_nurbs_curve(const OBJCurve &obj_nurbs_data) const;
-
- /**
- * When there are multiple objects in a frame, the indices of previous objects' coordinates or
- * normals add up.
- */
- void update_index_offsets(const OBJMesh &obj_mesh_data);
+ void write_nurbs_curve(FormatHandler<eFileType::OBJ> &fh, const OBJCurve &obj_nurbs_data) const;
private:
- using func_vert_uv_normal_indices = void (OBJWriter::*)(Span<int> vert_indices,
+ using func_vert_uv_normal_indices = void (OBJWriter::*)(FormatHandler<eFileType::OBJ> &fh,
+ const IndexOffsets &offsets,
+ Span<int> vert_indices,
Span<int> uv_indices,
Span<int> normal_indices) const;
/**
@@ -144,25 +162,33 @@ class OBJWriter : NonMovable, NonCopyable {
/**
* Write one line of polygon indices as "f v1/vt1/vn1 v2/vt2/vn2 ...".
*/
- void write_vert_uv_normal_indices(Span<int> vert_indices,
+ void write_vert_uv_normal_indices(FormatHandler<eFileType::OBJ> &fh,
+ const IndexOffsets &offsets,
+ Span<int> vert_indices,
Span<int> uv_indices,
Span<int> normal_indices) const;
/**
* Write one line of polygon indices as "f v1//vn1 v2//vn2 ...".
*/
- void write_vert_normal_indices(Span<int> vert_indices,
+ void write_vert_normal_indices(FormatHandler<eFileType::OBJ> &fh,
+ const IndexOffsets &offsets,
+ Span<int> vert_indices,
Span<int> /*uv_indices*/,
Span<int> normal_indices) const;
/**
* Write one line of polygon indices as "f v1/vt1 v2/vt2 ...".
*/
- void write_vert_uv_indices(Span<int> vert_indices,
+ void write_vert_uv_indices(FormatHandler<eFileType::OBJ> &fh,
+ const IndexOffsets &offsets,
+ Span<int> vert_indices,
Span<int> uv_indices,
Span<int> /*normal_indices*/) const;
/**
* Write one line of polygon indices as "f v1 v2 ...".
*/
- void write_vert_indices(Span<int> vert_indices,
+ void write_vert_indices(FormatHandler<eFileType::OBJ> &fh,
+ const IndexOffsets &offsets,
+ Span<int> vert_indices,
Span<int> /*uv_indices*/,
Span<int> /*normal_indices*/) const;
};
@@ -172,7 +198,8 @@ class OBJWriter : NonMovable, NonCopyable {
*/
class MTLWriter : NonMovable, NonCopyable {
private:
- std::unique_ptr<FormattedFileHandler<eFileType::MTL>> file_handler_ = nullptr;
+ FormatHandler<eFileType::MTL> fmt_handler_;
+ FILE *outfile_;
std::string mtl_filepath_;
Vector<MTLMaterial> mtlmaterials_;
/* Map from a Material* to an index into mtlmaterials_. */
@@ -183,8 +210,9 @@ class MTLWriter : NonMovable, NonCopyable {
* Create the .MTL file.
*/
MTLWriter(const char *obj_filepath) noexcept(false);
+ ~MTLWriter();
- void write_header(const char *blen_filepath) const;
+ void write_header(const char *blen_filepath);
/**
* Write all of the material specifications to the MTL file.
* For consistency of output from run to run (useful for testing),
diff --git a/source/blender/io/wavefront_obj/exporter/obj_export_io.hh b/source/blender/io/wavefront_obj/exporter/obj_export_io.hh
index 1bbefaee75f..f1a8178bf0a 100644
--- a/source/blender/io/wavefront_obj/exporter/obj_export_io.hh
+++ b/source/blender/io/wavefront_obj/exporter/obj_export_io.hh
@@ -24,6 +24,7 @@
#include <string>
#include <system_error>
#include <type_traits>
+#include <vector>
#include "BLI_compiler_attrs.h"
#include "BLI_fileops.h"
@@ -124,6 +125,14 @@ constexpr bool is_type_integral = (... && std::is_integral_v<std::decay_t<T>>);
template<typename... T>
constexpr bool is_type_string_related = (... && std::is_constructible_v<std::string, T>);
+// gcc (at least 9.3) while compiling the obj_exporter_tests.cc with optimizations on,
+// results in "obj_export_io.hh:205:18: warning: ā€˜%sā€™ directive output truncated writing 34 bytes
+// into a region of size 6" and similar warnings. Yes the output is truncated, and that is covered
+// as an edge case by tests on purpose.
+#if defined __GNUC__
+# pragma GCC diagnostic push
+# pragma GCC diagnostic ignored "-Wformat-truncation"
+#endif
template<typename... T>
constexpr FormattingSyntax syntax_elem_to_formatting(const eOBJSyntaxElement key)
{
@@ -264,31 +273,44 @@ constexpr FormattingSyntax syntax_elem_to_formatting(const eMTLSyntaxElement key
}
}
}
+#if defined __GNUC__
+# pragma GCC diagnostic pop
+#endif
/**
- * File format and syntax agnostic file writer.
+ * File format and syntax agnostic file buffer writer.
+ * All writes are done into an internal chunked memory buffer
+ * (list of default 64 kilobyte blocks).
+ * Call write_fo_file once in a while to write the memory buffer(s)
+ * into the given file.
*/
-template<eFileType filetype> class FormattedFileHandler : NonCopyable, NonMovable {
+template<eFileType filetype,
+ size_t buffer_chunk_size = 64 * 1024,
+ size_t write_local_buffer_size = 1024>
+class FormatHandler : NonCopyable, NonMovable {
private:
- std::FILE *outfile_ = nullptr;
- std::string outfile_path_;
+ typedef std::vector<char> VectorChar;
+ std::vector<VectorChar> blocks_;
public:
- FormattedFileHandler(std::string outfile_path) noexcept(false)
- : outfile_path_(std::move(outfile_path))
+ /* Write contents to the buffer(s) into a file, and clear the buffers. */
+ void write_to_file(FILE *f)
{
- outfile_ = BLI_fopen(outfile_path_.c_str(), "w");
- if (!outfile_) {
- throw std::system_error(errno, std::system_category(), "Cannot open file " + outfile_path_);
- }
+ for (const auto &b : blocks_)
+ fwrite(b.data(), 1, b.size(), f);
+ blocks_.clear();
}
- ~FormattedFileHandler()
+ std::string get_as_string() const
{
- if (outfile_ && std::fclose(outfile_)) {
- std::cerr << "Error: could not close the file '" << outfile_path_
- << "' properly, it may be corrupted." << std::endl;
- }
+ std::string s;
+ for (const auto &b : blocks_)
+ s.append(b.data(), b.size());
+ return s;
+ }
+ size_t get_block_count() const
+ {
+ return blocks_.size();
}
/**
@@ -298,7 +320,7 @@ template<eFileType filetype> class FormattedFileHandler : NonCopyable, NonMovabl
* `eFileType::MTL`.
*/
template<typename FileTypeTraits<filetype>::SyntaxType key, typename... T>
- constexpr void write(T &&...args) const
+ constexpr void write(T &&...args)
{
/* Get format syntax, number of arguments expected and whether types of given arguments are
* valid.
@@ -339,13 +361,47 @@ template<eFileType filetype> class FormattedFileHandler : NonCopyable, NonMovabl
}
}
- template<typename... T> constexpr void write_impl(const char *fmt, T &&...args) const
+ /* Ensure the last block contains at least this amount of free space.
+ * If not, add a new block with max of block size & the amount of space needed. */
+ void ensure_space(size_t at_least)
+ {
+ if (blocks_.empty() || (blocks_.back().capacity() - blocks_.back().size() < at_least)) {
+ VectorChar &b = blocks_.emplace_back(VectorChar());
+ b.reserve(std::max(at_least, buffer_chunk_size));
+ }
+ }
+
+ template<typename... T> constexpr void write_impl(const char *fmt, T &&...args)
{
if constexpr (sizeof...(T) == 0) {
- std::fputs(fmt, outfile_);
+ /* No arguments: just emit the format string. */
+ size_t len = strlen(fmt);
+ ensure_space(len);
+ VectorChar &bb = blocks_.back();
+ bb.insert(bb.end(), fmt, fmt + len);
}
else {
- std::fprintf(outfile_, fmt, convert_to_primitive(std::forward<T>(args))...);
+ /* Format into a local buffer. */
+ char buf[write_local_buffer_size];
+ int needed = std::snprintf(
+ buf, write_local_buffer_size, fmt, convert_to_primitive(std::forward<T>(args))...);
+ if (needed < 0)
+ throw std::system_error(
+ errno, std::system_category(), "Failed to format obj export string into a buffer");
+ ensure_space(needed + 1); /* Ensure space for zero terminator. */
+ VectorChar &bb = blocks_.back();
+ if (needed < write_local_buffer_size) {
+ /* String formatted successfully into the local buffer, copy it. */
+ bb.insert(bb.end(), buf, buf + needed);
+ }
+ else {
+ /* Would need more space than the local buffer: insert said space and format again into
+ * that. */
+ size_t bbEnd = bb.size();
+ bb.insert(bb.end(), needed, ' ');
+ std::snprintf(
+ bb.data() + bbEnd, needed + 1, fmt, convert_to_primitive(std::forward<T>(args))...);
+ }
}
}
};
diff --git a/source/blender/io/wavefront_obj/exporter/obj_export_mesh.cc b/source/blender/io/wavefront_obj/exporter/obj_export_mesh.cc
index 14e50d726c0..468631cdd82 100644
--- a/source/blender/io/wavefront_obj/exporter/obj_export_mesh.cc
+++ b/source/blender/io/wavefront_obj/exporter/obj_export_mesh.cc
@@ -69,16 +69,28 @@ OBJMesh::OBJMesh(Depsgraph *depsgraph, const OBJExportParams &export_params, Obj
*/
OBJMesh::~OBJMesh()
{
- free_mesh_if_needed();
- if (poly_smooth_groups_) {
- MEM_freeN(poly_smooth_groups_);
- }
+ clear();
}
void OBJMesh::free_mesh_if_needed()
{
if (mesh_eval_needs_free_ && export_mesh_eval_) {
BKE_id_free(nullptr, export_mesh_eval_);
+ export_mesh_eval_ = nullptr;
+ mesh_eval_needs_free_ = false;
+ }
+}
+
+void OBJMesh::clear()
+{
+ free_mesh_if_needed();
+ uv_indices_.clear_and_make_inline();
+ uv_coords_.clear_and_make_inline();
+ loop_to_normal_index_.clear_and_make_inline();
+ normal_coords_.clear_and_make_inline();
+ if (poly_smooth_groups_) {
+ MEM_freeN(poly_smooth_groups_);
+ poly_smooth_groups_ = nullptr;
}
}
@@ -256,7 +268,7 @@ Vector<int> OBJMesh::calc_poly_vertex_indices(const int poly_index) const
return r_poly_vertex_indices;
}
-void OBJMesh::store_uv_coords_and_indices(Vector<std::array<float, 2>> &r_uv_coords)
+void OBJMesh::store_uv_coords_and_indices()
{
const MPoly *mpoly = export_mesh_eval_->mpoly;
const MLoop *mloop = export_mesh_eval_->mloop;
@@ -276,7 +288,7 @@ void OBJMesh::store_uv_coords_and_indices(Vector<std::array<float, 2>> &r_uv_coo
uv_indices_.resize(totpoly);
/* At least total vertices of a mesh will be present in its texture map. So
* reserve minimum space early. */
- r_uv_coords.reserve(totvert);
+ uv_coords_.reserve(totvert);
tot_uv_vertices_ = 0;
for (int vertex_index = 0; vertex_index < totvert; vertex_index++) {
@@ -288,11 +300,10 @@ void OBJMesh::store_uv_coords_and_indices(Vector<std::array<float, 2>> &r_uv_coo
const int vertices_in_poly = mpoly[uv_vert->poly_index].totloop;
/* Store UV vertex coordinates. */
- r_uv_coords.resize(tot_uv_vertices_);
+ uv_coords_.resize(tot_uv_vertices_);
const int loopstart = mpoly[uv_vert->poly_index].loopstart;
Span<float> vert_uv_coords(mloopuv[loopstart + uv_vert->loop_of_poly_index].uv, 2);
- r_uv_coords[tot_uv_vertices_ - 1][0] = vert_uv_coords[0];
- r_uv_coords[tot_uv_vertices_ - 1][1] = vert_uv_coords[1];
+ uv_coords_[tot_uv_vertices_ - 1] = float2(vert_uv_coords[0], vert_uv_coords[1]);
/* Store UV vertex indices. */
uv_indices_[uv_vert->poly_index].resize(vertices_in_poly);
@@ -340,7 +351,7 @@ static float3 round_float3_to_n_digits(const float3 &v, int round_digits)
return ans;
}
-void OBJMesh::store_normal_coords_and_indices(Vector<float3> &r_normal_coords)
+void OBJMesh::store_normal_coords_and_indices()
{
/* We'll round normal components to 4 digits.
* This will cover up some minor differences
@@ -371,7 +382,7 @@ void OBJMesh::store_normal_coords_and_indices(Vector<float3> &r_normal_coords)
if (loop_norm_index == -1) {
loop_norm_index = cur_normal_index++;
normal_to_index.add(rounded_loop_normal, loop_norm_index);
- r_normal_coords.append(rounded_loop_normal);
+ normal_coords_.append(rounded_loop_normal);
}
loop_to_normal_index_[loop_index] = loop_norm_index;
}
@@ -383,7 +394,7 @@ void OBJMesh::store_normal_coords_and_indices(Vector<float3> &r_normal_coords)
if (poly_norm_index == -1) {
poly_norm_index = cur_normal_index++;
normal_to_index.add(rounded_poly_normal, poly_norm_index);
- r_normal_coords.append(rounded_poly_normal);
+ normal_coords_.append(rounded_poly_normal);
}
for (int i = 0; i < mpoly.totloop; ++i) {
int loop_index = mpoly.loopstart + i;
diff --git a/source/blender/io/wavefront_obj/exporter/obj_export_mesh.hh b/source/blender/io/wavefront_obj/exporter/obj_export_mesh.hh
index f3ace140006..4cfbffdcebc 100644
--- a/source/blender/io/wavefront_obj/exporter/obj_export_mesh.hh
+++ b/source/blender/io/wavefront_obj/exporter/obj_export_mesh.hh
@@ -82,11 +82,19 @@ class OBJMesh : NonCopyable {
* Per-polygon-per-vertex UV vertex indices.
*/
Vector<Vector<int>> uv_indices_;
+ /*
+ * UV vertices.
+ */
+ Vector<float2> uv_coords_;
/**
* Per-loop normal index.
*/
Vector<int> loop_to_normal_index_;
/*
+ * Normal coords.
+ */
+ Vector<float3> normal_coords_;
+ /*
* Total number of normal indices (maximum entry, plus 1, in
* the loop_to_norm_index_ vector).
*/
@@ -108,6 +116,9 @@ class OBJMesh : NonCopyable {
OBJMesh(Depsgraph *depsgraph, const OBJExportParams &export_params, Object *mesh_object);
~OBJMesh();
+ /* Clear various arrays to release potentially large memory allocations. */
+ void clear();
+
int tot_vertices() const;
int tot_polygons() const;
int tot_uv_vertices() const;
@@ -165,10 +176,14 @@ class OBJMesh : NonCopyable {
Vector<int> calc_poly_vertex_indices(int poly_index) const;
/**
* Calculate UV vertex coordinates of an Object.
- *
- * \note Also store the UV vertex indices in the member variable.
+ * Stores the coordinates and UV vertex indices in the member variables.
*/
- void store_uv_coords_and_indices(Vector<std::array<float, 2>> &r_uv_coords);
+ void store_uv_coords_and_indices();
+ /* Get UV coordinates computed by store_uv_coords_and_indices. */
+ const Vector<float2> &get_uv_coords() const
+ {
+ return uv_coords_;
+ }
Span<int> calc_poly_uv_indices(int poly_index) const;
/**
* Calculate polygon normal of a polygon at given index.
@@ -177,10 +192,15 @@ class OBJMesh : NonCopyable {
*/
float3 calc_poly_normal(int poly_index) const;
/**
- * Find the unique normals of the mesh and return them in \a r_normal_coords.
- * Store the indices into that vector with for each loop in this #OBJMesh.
+ * Find the unique normals of the mesh and stores them in a member variable.
+ * Also stores the indices into that vector with for each loop.
*/
- void store_normal_coords_and_indices(Vector<float3> &r_normal_coords);
+ void store_normal_coords_and_indices();
+ /* Get normals calculate by store_normal_coords_and_indices. */
+ const Vector<float3> &get_normal_coords() const
+ {
+ return normal_coords_;
+ }
/**
* Calculate a polygon's polygon/loop normal indices.
* \param poly_index Index of the polygon to calculate indices for.
diff --git a/source/blender/io/wavefront_obj/exporter/obj_exporter.cc b/source/blender/io/wavefront_obj/exporter/obj_exporter.cc
index 0c753ccdcac..187f50277f1 100644
--- a/source/blender/io/wavefront_obj/exporter/obj_exporter.cc
+++ b/source/blender/io/wavefront_obj/exporter/obj_exporter.cc
@@ -25,6 +25,7 @@
#include "BKE_scene.h"
#include "BLI_path_util.h"
+#include "BLI_task.hh"
#include "BLI_vector.hh"
#include "DEG_depsgraph_query.h"
@@ -155,44 +156,97 @@ static void write_mesh_objects(Vector<std::unique_ptr<OBJMesh>> exportable_as_me
MTLWriter *mtl_writer,
const OBJExportParams &export_params)
{
+ /* Parallelization is over meshes/objects, which means
+ * we have to have the output text buffer for each object,
+ * and write them all into the file at the end. */
+ size_t count = exportable_as_mesh.size();
+ std::vector<FormatHandler<eFileType::OBJ>> buffers(count);
+
+ /* Serial: gather material indices, ensure normals & edges. */
+ Vector<Vector<int>> mtlindices;
if (mtl_writer) {
obj_writer.write_mtllib_name(mtl_writer->mtl_file_path());
+ mtlindices.reserve(count);
}
-
- /* Smooth groups and UV vertex indices may make huge memory allocations, so they should be freed
- * right after they're written, instead of waiting for #blender::Vector to clean them up after
- * all the objects are exported. */
for (auto &obj_mesh : exportable_as_mesh) {
- obj_writer.write_object_name(*obj_mesh);
- obj_writer.write_vertex_coords(*obj_mesh);
- Vector<int> obj_mtlindices;
+ OBJMesh &obj = *obj_mesh;
+ if (mtl_writer) {
+ mtlindices.append(mtl_writer->add_materials(obj));
+ }
+ if (export_params.export_normals) {
+ obj.ensure_mesh_normals();
+ }
+ obj.ensure_mesh_edges();
+ }
- if (obj_mesh->tot_polygons() > 0) {
- if (export_params.export_smooth_groups) {
- obj_mesh->calc_smooth_groups(export_params.smooth_groups_bitflags);
- }
+ /* Parallel over meshes: store normal coords & indices, uv coords and indices. */
+ blender::threading::parallel_for(IndexRange(count), 1, [&](IndexRange range) {
+ for (const int i : range) {
+ OBJMesh &obj = *exportable_as_mesh[i];
if (export_params.export_normals) {
- obj_writer.write_poly_normals(*obj_mesh);
+ obj.store_normal_coords_and_indices();
}
if (export_params.export_uv) {
- obj_writer.write_uv_coords(*obj_mesh);
- }
- if (mtl_writer) {
- obj_mtlindices = mtl_writer->add_materials(*obj_mesh);
+ obj.store_uv_coords_and_indices();
}
- /* This function takes a 0-indexed slot index for the obj_mesh object and
- * returns the material name that we are using in the .obj file for it. */
- std::function<const char *(int)> matname_fn = [&](int s) -> const char * {
- if (!mtl_writer || s < 0 || s >= obj_mtlindices.size()) {
- return nullptr;
+ }
+ });
+
+ /* Serial: calculate index offsets; these are sequentially added
+ * over all meshes, and requite normal/uv indices to be calculated. */
+ Vector<IndexOffsets> index_offsets;
+ index_offsets.reserve(count);
+ IndexOffsets offsets{0, 0, 0};
+ for (auto &obj_mesh : exportable_as_mesh) {
+ OBJMesh &obj = *obj_mesh;
+ index_offsets.append(offsets);
+ offsets.vertex_offset += obj.tot_vertices();
+ offsets.uv_vertex_offset += obj.tot_uv_vertices();
+ offsets.normal_offset += obj.tot_normal_indices();
+ }
+
+ /* Parallel over meshes: main result writing. */
+ blender::threading::parallel_for(IndexRange(count), 1, [&](IndexRange range) {
+ for (const int i : range) {
+ OBJMesh &obj = *exportable_as_mesh[i];
+ auto &fh = buffers[i];
+
+ obj_writer.write_object_name(fh, obj);
+ obj_writer.write_vertex_coords(fh, obj);
+
+ if (obj.tot_polygons() > 0) {
+ if (export_params.export_smooth_groups) {
+ obj.calc_smooth_groups(export_params.smooth_groups_bitflags);
+ }
+ if (export_params.export_normals) {
+ obj_writer.write_poly_normals(fh, obj);
}
- return mtl_writer->mtlmaterial_name(obj_mtlindices[s]);
- };
- obj_writer.write_poly_elements(*obj_mesh, matname_fn);
+ if (export_params.export_uv) {
+ obj_writer.write_uv_coords(fh, obj);
+ }
+ /* This function takes a 0-indexed slot index for the obj_mesh object and
+ * returns the material name that we are using in the .obj file for it. */
+ const auto *obj_mtlindices = mtlindices.is_empty() ? nullptr : &mtlindices[i];
+ std::function<const char *(int)> matname_fn = [&](int s) -> const char * {
+ if (!obj_mtlindices || s < 0 || s >= obj_mtlindices->size()) {
+ return nullptr;
+ }
+ return mtl_writer->mtlmaterial_name((*obj_mtlindices)[s]);
+ };
+ obj_writer.write_poly_elements(fh, index_offsets[i], obj, matname_fn);
+ }
+ obj_writer.write_edges_indices(fh, index_offsets[i], obj);
+
+ /* Nothing will need this object's data after this point, release
+ * various arrays here. */
+ obj.clear();
}
- obj_writer.write_edges_indices(*obj_mesh);
+ });
- obj_writer.update_index_offsets(*obj_mesh);
+ /* Write all the object text buffers into the output file. */
+ FILE *f = obj_writer.get_outfile();
+ for (auto &b : buffers) {
+ b.write_to_file(f);
}
}
@@ -202,11 +256,13 @@ static void write_mesh_objects(Vector<std::unique_ptr<OBJMesh>> exportable_as_me
static void write_nurbs_curve_objects(const Vector<std::unique_ptr<OBJCurve>> &exportable_as_nurbs,
const OBJWriter &obj_writer)
{
+ FormatHandler<eFileType::OBJ> fh;
/* #OBJCurve doesn't have any dynamically allocated memory, so it's fine
* to wait for #blender::Vector to clean the objects up. */
for (const std::unique_ptr<OBJCurve> &obj_curve : exportable_as_nurbs) {
- obj_writer.write_nurbs_curve(*obj_curve);
+ obj_writer.write_nurbs_curve(fh, *obj_curve);
}
+ fh.write_to_file(obj_writer.get_outfile());
}
void export_frame(Depsgraph *depsgraph, const OBJExportParams &export_params, const char *filepath)
diff --git a/source/blender/io/wavefront_obj/tests/obj_exporter_tests.cc b/source/blender/io/wavefront_obj/tests/obj_exporter_tests.cc
index 1e2d509ef29..ac2ee8d566b 100644
--- a/source/blender/io/wavefront_obj/tests/obj_exporter_tests.cc
+++ b/source/blender/io/wavefront_obj/tests/obj_exporter_tests.cc
@@ -193,7 +193,7 @@ static std::string read_temp_file_in_string(const std::string &file_path)
std::string res;
size_t buffer_len;
void *buffer = BLI_file_read_text_as_mem(file_path.c_str(), 0, &buffer_len);
- if (buffer != NULL) {
+ if (buffer != nullptr) {
res.assign((const char *)buffer, buffer_len);
MEM_freeN(buffer);
}
@@ -238,6 +238,38 @@ TEST(obj_exporter_writer, mtllib)
BLI_delete(out_file_path.c_str(), false, false);
}
+TEST(obj_exporter_writer, format_handler_buffer_chunking)
+{
+ /* Use a tiny buffer chunk size, so that the test below ends up creating several blocks. */
+ FormatHandler<eFileType::OBJ, 16, 8> h;
+ h.write<eOBJSyntaxElement::object_name>("abc");
+ h.write<eOBJSyntaxElement::object_name>("abcd");
+ h.write<eOBJSyntaxElement::object_name>("abcde");
+ h.write<eOBJSyntaxElement::object_name>("abcdef");
+ h.write<eOBJSyntaxElement::object_name>("012345678901234567890123456789abcd");
+ h.write<eOBJSyntaxElement::object_name>("123");
+ h.write<eOBJSyntaxElement::curve_element_begin>();
+ h.write<eOBJSyntaxElement::new_line>();
+ h.write<eOBJSyntaxElement::nurbs_parameter_begin>();
+ h.write<eOBJSyntaxElement::new_line>();
+
+ size_t got_blocks = h.get_block_count();
+ ASSERT_EQ(got_blocks, 7);
+
+ std::string got_string = h.get_as_string();
+ using namespace std::string_literals;
+ const char *expected = R"(o abc
+o abcd
+o abcde
+o abcdef
+o 012345678901234567890123456789abcd
+o 123
+curv 0.0 1.0
+parm 0.0
+)";
+ ASSERT_EQ(got_string, expected);
+}
+
/* Return true if string #a and string #b are equal after their first newline. */
static bool strings_equal_after_first_lines(const std::string &a, const std::string &b)
{