Permalink
Browse files

demo: Rework .obj parser to not use STL

This change replaces all std::vector containers inside ObjFile with
manual memory allocation and reallocation. This change aims to minimize
STL push_back operations that happen to be *very* slow in debug STL in
MSVC with default project settings.

We still keep std::vector for extracting data out of ObjFile but don't
use push_back for this anymore, instead filling elements of the array
post-resize, which ends up being ~2x faster in debug.

Overall this change reduces the .obj parsing time (specifically,
parseObj timings) ~10x, e.g. from 1230 msec to 126 msec on 3.2 MB .obj.
  • Loading branch information...
zeux committed Sep 20, 2018
1 parent 61320e1 commit 1996f143b8b3e488e114efefbb12c1d5e174685a
Showing with 150 additions and 69 deletions.
  1. +8 −8 demo/main.cpp
  2. +95 −28 demo/objparser.cpp
  3. +23 −7 demo/objparser.h
  4. +8 −8 tools/lodviewer.cpp
  5. +7 −8 tools/meshencoder.cpp
  6. +2 −2 tools/vcachetester.cpp
  7. +7 −8 tools/vcachetuner.cpp
@@ -6,6 +6,7 @@
#include <cstdio>
#include <cstring>
#include <ctime>
#include <vector>

#include "miniz.h"
#include "objparser.h"
@@ -112,16 +113,15 @@ Mesh parseObj(const char* path, double& reindex)

objTriangulate(file);

size_t total_indices = file.f.size() / 3;
size_t total_indices = file.f_size / 3;

std::vector<Vertex> vertices;
vertices.reserve(total_indices);
std::vector<Vertex> vertices(total_indices);

for (size_t i = 0; i < file.f.size(); i += 3)
for (size_t i = 0; i < total_indices; ++i)
{
int vi = file.f[i + 0];
int vti = file.f[i + 1];
int vni = file.f[i + 2];
int vi = file.f[i * 3 + 0];
int vti = file.f[i * 3 + 1];
int vni = file.f[i * 3 + 2];

Vertex v =
{
@@ -137,7 +137,7 @@ Mesh parseObj(const char* path, double& reindex)
vti >= 0 ? file.vt[vti * 3 + 1] : 0,
};

vertices.push_back(v);
vertices[i] = v;
}

reindex = timestamp();
@@ -8,6 +8,22 @@
#include <cstdlib>
#include <cstring>

template <typename T>
static void growArray(T*& data, size_t& capacity)
{
size_t newcapacity = capacity == 0 ? 32 : capacity + capacity / 2;
T* newdata = new T[newcapacity];

if (data)
{
memcpy(newdata, data, capacity * sizeof(T));
delete[] data;
}

data = newdata;
capacity = newcapacity;
}

static int fixupIndex(int index, size_t size)
{
return (index >= 0) ? index - 1 : int(size) + index;
@@ -136,6 +152,34 @@ static const char* parseFace(const char* s, int& vi, int& vti, int& vni)
return s;
}

ObjFile::ObjFile()
: v(0)
, v_size(0)
, v_cap(0)
, vt(0)
, vt_size(0)
, vt_cap(0)
, vn(0)
, vn_size(0)
, vn_cap(0)
, fv(0)
, fv_size(0)
, fv_cap(0)
, f(0)
, f_size(0)
, f_cap(0)
{
}

ObjFile::~ObjFile()
{
delete[] v;
delete[] vt;
delete[] vn;
delete[] fv;
delete[] f;
}

void objParseLine(ObjFile& result, const char* line)
{
if (line[0] == 'v' && line[1] == ' ')
@@ -146,9 +190,12 @@ void objParseLine(ObjFile& result, const char* line)
float y = parseFloat(s, &s);
float z = parseFloat(s, &s);

result.v.push_back(x);
result.v.push_back(y);
result.v.push_back(z);
if (result.v_size + 3 > result.v_cap)
growArray(result.v, result.v_cap);

result.v[result.v_size++] = x;
result.v[result.v_size++] = y;
result.v[result.v_size++] = z;
}
else if (line[0] == 'v' && line[1] == 't' && line[2] == ' ')
{
@@ -158,9 +205,12 @@ void objParseLine(ObjFile& result, const char* line)
float v = parseFloat(s, &s);
float w = parseFloat(s, &s);

result.vt.push_back(u);
result.vt.push_back(v);
result.vt.push_back(w);
if (result.vt_size + 3 > result.vt_cap)
growArray(result.vt, result.vt_cap);

result.vt[result.vt_size++] = u;
result.vt[result.vt_size++] = v;
result.vt[result.vt_size++] = w;
}
else if (line[0] == 'v' && line[1] == 'n' && line[2] == ' ')
{
@@ -170,18 +220,21 @@ void objParseLine(ObjFile& result, const char* line)
float y = parseFloat(s, &s);
float z = parseFloat(s, &s);

result.vn.push_back(x);
result.vn.push_back(y);
result.vn.push_back(z);
if (result.vn_size + 3 > result.vn_cap)
growArray(result.vn, result.vn_cap);

result.vn[result.vn_size++] = x;
result.vn[result.vn_size++] = y;
result.vn[result.vn_size++] = z;
}
else if (line[0] == 'f' && line[1] == ' ')
{
const char* s = line + 2;
int fv = 0;

size_t v = result.v.size() / 3;
size_t vt = result.vt.size() / 3;
size_t vn = result.vn.size() / 3;
size_t v = result.v_size / 3;
size_t vt = result.vt_size / 3;
size_t vn = result.vn_size / 3;

while (*s)
{
@@ -191,14 +244,20 @@ void objParseLine(ObjFile& result, const char* line)
if (vi == 0)
break;

result.f.push_back(fixupIndex(vi, v));
result.f.push_back(fixupIndex(vti, vt));
result.f.push_back(fixupIndex(vni, vn));
if (result.f_size + 3 > result.f_cap)
growArray(result.f, result.f_cap);

result.f[result.f_size++] = fixupIndex(vi, v);
result.f[result.f_size++] = fixupIndex(vti, vt);
result.f[result.f_size++] = fixupIndex(vni, vn);

fv++;
}

result.fv.push_back(char(fv));
if (result.fv_size + 1 > result.fv_cap)
growArray(result.fv, result.fv_cap);

result.fv[result.fv_size++] = char(fv);
}
}

@@ -259,7 +318,7 @@ bool objValidate(const ObjFile& result)
{
size_t total_indices = 0;

for (size_t face = 0; face < result.fv.size(); ++face)
for (size_t face = 0; face < result.fv_size; ++face)
{
int fv = result.fv[face];

@@ -269,14 +328,14 @@ bool objValidate(const ObjFile& result)
total_indices += fv;
}

if (total_indices * 3 != result.f.size())
if (total_indices * 3 != result.f_size)
return false;

size_t v = result.v.size() / 3;
size_t vt = result.vt.size() / 3;
size_t vn = result.vn.size() / 3;
size_t v = result.v_size / 3;
size_t vt = result.vt_size / 3;
size_t vn = result.vn_size / 3;

for (size_t i = 0; i < result.f.size(); i += 3)
for (size_t i = 0; i < result.f_size; i += 3)
{
int vi = result.f[i + 0];
int vti = result.f[i + 1];
@@ -302,20 +361,20 @@ void objTriangulate(ObjFile& result)
{
size_t total_indices = 0;

for (size_t face = 0; face < result.fv.size(); ++face)
for (size_t face = 0; face < result.fv_size; ++face)
{
int fv = result.fv[face];

assert(fv >= 3);
total_indices += (fv - 2) * 3;
}

std::vector<int> f(total_indices * 3);
int* f = new int[total_indices * 3];

size_t read = 0;
size_t write = 0;

for (size_t face = 0; face < result.fv.size(); ++face)
for (size_t face = 0; face < result.fv_size; ++face)
{
int fv = result.fv[face];

@@ -339,9 +398,17 @@ void objTriangulate(ObjFile& result)
read += fv * 3;
}

assert(read == result.f.size());
assert(read == result.f_size);
assert(write == total_indices * 3);

result.f.swap(f);
std::vector<char>(total_indices / 3, 3).swap(result.fv);
delete[] result.f;
result.f = f;
result.f_size = result.f_cap = write;

char* fv = new char[total_indices / 3];
memset(fv, 3, total_indices / 3);

delete[] result.fv;
result.fv = fv;
result.fv_size = result.fv_cap = total_indices / 3;
}
@@ -1,15 +1,31 @@
#pragma once

#include <vector>
#include <stddef.h>

struct ObjFile
class ObjFile
{
std::vector<float> v; // positions; stride 3 (xyz)
std::vector<float> vt; // texture coordinates; stride 3 (uvw)
std::vector<float> vn; // vertex normals; stride 3 (xyz)
public:
float* v; // positions; stride 3 (xyz)
size_t v_size, v_cap;

std::vector<char> fv; // face vertex count
std::vector<int> f; // face elements; stride defined by fv (*3 since f contains indices into v/vt/vn)
float* vt; // texture coordinates; stride 3 (uvw)
size_t vt_size, vt_cap;

float* vn; // vertex normals; stride 3 (xyz)
size_t vn_size, vn_cap;

char* fv; // face vertex count
size_t fv_size, fv_cap;

int* f; // face elements; stride defined by fv (*3 since f contains indices into v/vt/vn)
size_t f_size, f_cap;

ObjFile();
~ObjFile();

private:
ObjFile(const ObjFile&);
ObjFile& operator=(const ObjFile&);
};

void objParseLine(ObjFile& result, const char* line);
@@ -7,6 +7,7 @@
#include <cmath>
#include <cstdio>
#include <ctime>
#include <vector>

#include <GLFW/glfw3.h>

@@ -59,16 +60,15 @@ Mesh parseObj(const char* path)

objTriangulate(file);

size_t total_indices = file.f.size() / 3;
size_t total_indices = file.f_size / 3;

std::vector<Vertex> vertices;
vertices.reserve(total_indices);
std::vector<Vertex> vertices(total_indices);

for (size_t i = 0; i < file.f.size(); i += 3)
for (size_t i = 0; i < total_indices; ++i)
{
int vi = file.f[i + 0];
int vti = file.f[i + 1];
int vni = file.f[i + 2];
int vi = file.f[i * 3 + 0];
int vti = file.f[i * 3 + 1];
int vni = file.f[i * 3 + 2];

Vertex v =
{
@@ -84,7 +84,7 @@ Mesh parseObj(const char* path)
vti >= 0 ? file.vt[vti * 3 + 1] : 0,
};

vertices.push_back(v);
vertices[i] = v;
}

Mesh result;
@@ -73,16 +73,15 @@ Mesh parseObj(const char* path)

objTriangulate(file);

size_t total_indices = file.f.size() / 3;
size_t total_indices = file.f_size / 3;

std::vector<Vertex> vertices;
vertices.reserve(total_indices);
std::vector<Vertex> vertices(total_indices);

for (size_t i = 0; i < file.f.size(); i += 3)
for (size_t i = 0; i < total_indices; ++i)
{
int vi = file.f[i + 0];
int vti = file.f[i + 1];
int vni = file.f[i + 2];
int vi = file.f[i * 3 + 0];
int vti = file.f[i * 3 + 1];
int vni = file.f[i * 3 + 2];

Vertex v =
{
@@ -98,7 +97,7 @@ Mesh parseObj(const char* path)
vti >= 0 ? file.vt[vti * 3 + 1] : 0,
};

vertices.push_back(v);
vertices[i] = v;
}

Mesh result;
@@ -414,10 +414,10 @@ void testCacheMeshes(IDXGIAdapter* adapter, int argc, char** argv)

std::vector<unsigned int> ib1;

for (size_t i = 0; i < file.f.size(); i += 3)
for (size_t i = 0; i < file.f_size; i += 3)
ib1.push_back(file.f[i]);

unsigned int vertex_count = file.v.size() / 3;
unsigned int vertex_count = file.v_size / 3;
unsigned int index_count = ib1.size();

unsigned int invocations1 = queryVSInvocations(device, context, ib1.data(), index_count);
Oops, something went wrong.

0 comments on commit 1996f14

Please sign in to comment.