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.
This commit is contained in:
Aras Pranckevicius 2022-01-30 15:03:31 -05:00 committed by Howard Trickey
parent b315678fea
commit 1f7013fb90
8 changed files with 435 additions and 204 deletions

View File

@ -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)

View File

@ -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()) {

View File

@ -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),

View File

@ -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))...);
}
}
}
};

View File

@ -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;

View File

@ -82,10 +82,18 @@ 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.

View File

@ -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);
}
for (auto &obj_mesh : exportable_as_mesh) {
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();
}
/* 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;
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);
obj.store_uv_coords_and_indices();
}
if (mtl_writer) {
obj_mtlindices = mtl_writer->add_materials(*obj_mesh);
}
/* 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;
}
return mtl_writer->mtlmaterial_name(obj_mtlindices[s]);
};
obj_writer.write_poly_elements(*obj_mesh, matname_fn);
}
obj_writer.write_edges_indices(*obj_mesh);
});
obj_writer.update_index_offsets(*obj_mesh);
/* 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);
}
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();
}
});
/* 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)

View File

@ -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)
{