OpenSubdiv/examples/glImaging/glImaging.cpp
Takahito Tejima 4a4322983f Osd drawing API refactoring.
Remove DrawRegistry from osd layer and put a simple shader caching
utility into examples/common. osd layer only provides patch shader
snippet and let client configure and compile the code. Clients also
maintain the lifetime of shader object, which is preferable for the
actual application integration.

update all examples to use the new scheme.
2015-05-13 17:35:46 -07:00

730 lines
24 KiB
C++
Executable File

//
// Copyright 2015 Pixar
//
// Licensed under the Apache License, Version 2.0 (the "Apache License")
// with the following modification; you may not use this file except in
// compliance with the Apache License and the following modification to it:
// Section 6. Trademarks. is deleted and replaced with:
//
// 6. Trademarks. This License does not grant permission to use the trade
// names, trademarks, service marks, or product names of the Licensor
// and its affiliates, except as required to comply with Section 4(c) of
// the License and to reproduce the content of the NOTICE file.
//
// You may obtain a copy of the Apache License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the Apache License with the above modification is
// distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the Apache License for the specific
// language governing permissions and limitations under the Apache License.
//
#if defined(__APPLE__)
#if defined(OSD_USES_GLEW)
#include <GL/glew.h>
#else
#include <OpenGL/gl3.h>
#endif
#define GLFW_INCLUDE_GL3
#define GLFW_NO_GLU
#else
#include <stdlib.h>
#include <GL/glew.h>
#if defined(_WIN32)
// XXX Must include windows.h here or GLFW pollutes the global namespace
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#endif
#endif
#include <iostream>
#include <fstream>
#include <sstream>
#include <limits>
#include <GLFW/glfw3.h>
#include <osd/cpuEvaluator.h>
#include <osd/cpuGLVertexBuffer.h>
#ifdef OPENSUBDIV_HAS_OPENMP
#include <osd/ompEvaluator.h>
#endif
#ifdef OPENSUBDIV_HAS_TBB
#include <osd/tbbEvaluator.h>
#endif
#ifdef OPENSUBDIV_HAS_OPENCL
#include <osd/clEvaluator.h>
#include <osd/clGLVertexBuffer.h>
#include "../common/clDeviceContext.h"
CLDeviceContext g_clDeviceContext;
#endif
#ifdef OPENSUBDIV_HAS_CUDA
#include <osd/cudaEvaluator.h>
#include <osd/cudaGLVertexBuffer.h>
#include "../common/cudaDeviceContext.h"
CudaDeviceContext g_cudaDeviceContext;
#endif
#ifdef OPENSUBDIV_HAS_GLSL_TRANSFORM_FEEDBACK
#include <osd/glXFBEvaluator.h>
#include <osd/glVertexBuffer.h>
#endif
#ifdef OPENSUBDIV_HAS_GLSL_COMPUTE
#include <osd/glComputeEvaluator.h>
#include <osd/glVertexBuffer.h>
#endif
#include <osd/glDrawContext.h>
#include <osd/glMesh.h>
#include <common/vtr_utils.h>
#include "../common/patchColors.h"
#include "../common/stb_image_write.h" // common.obj has an implementation.
#include "../common/glShaderCache.h"
#include "init_shapes.h"
using namespace OpenSubdiv;
#include <osd/glslPatchShaderSource.h>
static const char *shaderSource =
#include "shader.gen.h"
;
static void
setGLCoreProfile() {
#define glfwOpenWindowHint glfwWindowHint
#define GLFW_OPENGL_VERSION_MAJOR GLFW_CONTEXT_VERSION_MAJOR
#define GLFW_OPENGL_VERSION_MINOR GLFW_CONTEXT_VERSION_MINOR
glfwOpenWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
#if not defined(__APPLE__)
glfwOpenWindowHint(GLFW_OPENGL_VERSION_MAJOR, 4);
#ifdef OPENSUBDIV_HAS_GLSL_COMPUTE
glfwOpenWindowHint(GLFW_OPENGL_VERSION_MINOR, 3);
#else
glfwOpenWindowHint(GLFW_OPENGL_VERSION_MINOR, 2);
#endif
#else
glfwOpenWindowHint(GLFW_OPENGL_VERSION_MAJOR, 4);
glfwOpenWindowHint(GLFW_OPENGL_VERSION_MINOR, 1);
#endif
glfwOpenWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
}
class ShaderCache : public GLShaderCache<OpenSubdiv::Far::PatchDescriptor> {
public:
ShaderCache(std::string const &displayMode) : _displayMode(displayMode) { }
virtual GLDrawConfig *CreateDrawConfig(OpenSubdiv::Far::PatchDescriptor const &desc) {
using namespace OpenSubdiv;
// compile shader program
#if defined(GL_ARB_tessellation_shader) || defined(GL_VERSION_4_0)
const char *glslVersion = "#version 400\n";
#else
const char *glslVersion = "#version 330\n";
#endif
GLDrawConfig *config = new GLDrawConfig(glslVersion);
Far::PatchDescriptor::Type type = desc.GetType();
// common defines
std::stringstream ss;
if (type == Far::PatchDescriptor::QUADS) {
ss << "#define PRIM_QUAD\n";
} else {
ss << "#define PRIM_TRI\n";
}
if (desc.IsAdaptive()) {
ss << "#define SMOOTH_NORMALS\n";
}
ss << "#define DISPLAY_MODE_" << _displayMode << "\n";
ss << "#define OSD_ENABLE_PATCH_CULL\n";
ss << "#define GEOMETRY_OUT_LINE\n";
// include osd PatchCommon
ss << Osd::GLSLPatchShaderSource::GetCommonShaderSource();
std::string common = ss.str();
ss.str("");
// vertex shader
ss << common
<< (desc.IsAdaptive() ? "" : "#define VERTEX_SHADER\n") // for my shader source
<< shaderSource
<< Osd::GLSLPatchShaderSource::GetVertexShaderSource(type);
config->CompileAndAttachShader(GL_VERTEX_SHADER, ss.str());
ss.str("");
if (desc.IsAdaptive()) {
// tess control shader
ss << common
<< shaderSource
<< Osd::GLSLPatchShaderSource::GetTessControlShaderSource(type);
config->CompileAndAttachShader(GL_TESS_CONTROL_SHADER, ss.str());
ss.str("");
// tess eval shader
ss << common
<< shaderSource
<< Osd::GLSLPatchShaderSource::GetTessEvalShaderSource(type);
config->CompileAndAttachShader(GL_TESS_EVALUATION_SHADER, ss.str());
ss.str("");
}
// geometry shader
ss << common
<< "#define GEOMETRY_SHADER\n" // for my shader source
<< shaderSource;
config->CompileAndAttachShader(GL_GEOMETRY_SHADER, ss.str());
ss.str("");
// fragment shader
ss << common
<< "#define FRAGMENT_SHADER\n" // for my shader source
<< shaderSource;
config->CompileAndAttachShader(GL_FRAGMENT_SHADER, ss.str());
ss.str("");
if (!config->Link()) {
delete config;
return NULL;
}
// assign uniform locations
GLuint uboIndex;
GLuint program = config->GetProgram();
uboIndex = glGetUniformBlockIndex(program, "Transform");
if (uboIndex != GL_INVALID_INDEX)
glUniformBlockBinding(program, uboIndex, 0);
// assign texture locations
GLint loc;
if ((loc = glGetUniformLocation(program, "OsdPatchParamBuffer")) != -1) {
glProgramUniform1i(program, loc, 0); // GL_TEXTURE0
}
return config;
}
private:
std::string _displayMode;
};
// ---------------------------------------------------------------------------
static Osd::GLMeshInterface *
createOsdMesh(std::string const &kernel,
Far::TopologyRefiner *refiner,
int numVertexElements,
int numVaryingElements,
int level,
Osd::MeshBitset bits)
{
if (kernel == "CPU") {
return new Osd::Mesh<Osd::CpuGLVertexBuffer,
Far::StencilTables,
Osd::CpuEvaluator,
Osd::GLDrawContext>(
refiner,
numVertexElements,
numVaryingElements,
level, bits);
#ifdef OPENSUBDIV_HAS_OPENMP
} else if (kernel == "OPENMP") {
return new Osd::Mesh<Osd::CpuGLVertexBuffer,
Far::StencilTables,
Osd::OmpEvaluator,
Osd::GLDrawContext>(
refiner,
numVertexElements,
numVaryingElements,
level, bits);
#endif
#ifdef OPENSUBDIV_HAS_TBB
} else if (kernel == "TBB") {
return new Osd::Mesh<Osd::CpuGLVertexBuffer,
Far::StencilTables,
Osd::TbbEvaluator,
Osd::GLDrawContext>(
refiner,
numVertexElements,
numVaryingElements,
level, bits);
#endif
#ifdef OPENSUBDIV_HAS_OPENCL
} else if(kernel == "CL") {
return new Osd::Mesh<Osd::CLGLVertexBuffer,
Osd::CLStencilTables,
Osd::CLEvaluator,
Osd::GLDrawContext,
CLDeviceContext>(
refiner,
numVertexElements,
numVaryingElements,
level, bits,
NULL,
&g_clDeviceContext);
#endif
#ifdef OPENSUBDIV_HAS_CUDA
} else if(kernel == "CUDA") {
return new Osd::Mesh<Osd::CudaGLVertexBuffer,
Osd::CudaStencilTables,
Osd::CudaEvaluator,
Osd::GLDrawContext>(
refiner,
numVertexElements,
numVaryingElements,
level, bits);
#endif
#ifdef OPENSUBDIV_HAS_GLSL_TRANSFORM_FEEDBACK
} else if(kernel == "XFB") {
return new Osd::Mesh<Osd::GLVertexBuffer,
Osd::GLStencilTablesTBO,
Osd::GLXFBEvaluator,
Osd::GLDrawContext>(
refiner,
numVertexElements,
numVaryingElements,
level, bits);
#endif
#ifdef OPENSUBDIV_HAS_GLSL_COMPUTE
} else if(kernel == "GLSL") {
return new Osd::Mesh<Osd::GLVertexBuffer,
Osd::GLStencilTablesSSBO,
Osd::GLComputeEvaluator,
Osd::GLDrawContext>(
refiner,
numVertexElements,
numVaryingElements,
level, bits);
#endif
}
std::cout << "Specified kernel is not supported in this build.\n";
exit(1);
}
void runTest(ShapeDesc const &shapeDesc, std::string const &kernel,
int level, bool adaptive,
ShaderCache *shaderCache) {
std::cout << "Testing " << shapeDesc.name << ", kernel = " << kernel << "\n";
Shape const * shape = Shape::parseObj(shapeDesc.data.c_str(),
shapeDesc.scheme);
// create Vtr mesh (topology)
Sdc::SchemeType sdctype = GetSdcType(*shape);
Sdc::Options sdcoptions = GetSdcOptions(*shape);
// create topology refiner
Far::TopologyRefiner *refiner =
Far::TopologyRefinerFactory<Shape>::Create(
*shape,
Far::TopologyRefinerFactory<Shape>::Options(sdctype, sdcoptions));
// Adaptive refinement currently supported only for catmull-clark scheme
bool doAdaptive = adaptive && (shapeDesc.scheme == kCatmark);
bool interleaveVarying = true;
bool doSingleCreasePatch = true;
Osd::MeshBitset bits;
bits.set(Osd::MeshAdaptive, doAdaptive);
bits.set(Osd::MeshUseSingleCreasePatch, doSingleCreasePatch);
bits.set(Osd::MeshInterleaveVarying, interleaveVarying);
bits.set(Osd::MeshFVarData, false);
bits.set(Osd::MeshEndCapGregoryBasis, true);
int numVertexElements = 3 + 4; // XYZ, RGBA (interleaved)
int numVaryingElements = 0;
Osd::GLMeshInterface *mesh = createOsdMesh(
kernel, refiner, numVertexElements, numVaryingElements, level, bits);
int nverts = shape->GetNumVertices();
// centering
float pmin[3] = { std::numeric_limits<float>::max(),
std::numeric_limits<float>::max(),
std::numeric_limits<float>::max() };
float pmax[3] = { -std::numeric_limits<float>::max(),
-std::numeric_limits<float>::max(),
-std::numeric_limits<float>::max() };
for (int i = 0; i < nverts; ++i) {
for (int j = 0; j < 3; ++j) {
float v = shape->verts[i*3+j];
pmin[j] = std::min(v, pmin[j]);
pmax[j] = std::max(v, pmax[j]);
}
}
float center[3] = { (pmax[0]+pmin[0])*0.5f,
(pmax[1]+pmin[1])*0.5f,
(pmax[2]+pmin[2])*0.5f };
float radius = sqrt((pmax[0]-pmin[0])*(pmax[0]-pmin[0]) +
(pmax[1]-pmin[1])*(pmax[1]-pmin[1]) +
(pmax[2]-pmin[2])*(pmax[2]-pmin[2]));
// prepare coarse vertices
std::vector<float> vertex;
vertex.resize(nverts * numVertexElements);
for (int i = 0; i < nverts; ++i) {
// normalize xyz
for (int j = 0; j < 3; ++j) {
vertex[i*numVertexElements+j] =
(shape->verts[i*3+j] - center[j])/radius;
}
// set rgb from xyz
for (int j = 0; j < 3; ++j) {
vertex[i*numVertexElements+j+3] =
(shape->verts[i*3+j] - pmin[j])*2.0f/radius;
}
// set alpha to 1.0
vertex[i*numVertexElements+3+3] = 1.0f;
}
mesh->UpdateVertexBuffer(&vertex[0], 0, nverts);
// refine
mesh->Refine();
// draw
glClearColor(0.1f, 0.1f, 0.1f, 1.0);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
GLuint vao;
glGenVertexArrays(1, &vao);
glBindVertexArray(vao);
// bind vertex
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mesh->GetDrawContext()->GetPatchIndexBuffer());
glBindBuffer(GL_ARRAY_BUFFER, mesh->BindVertexBuffer());
glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE,
sizeof (GLfloat) * numVertexElements, 0);
glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE,
sizeof (GLfloat) * numVertexElements,
(const void*)(sizeof(GLfloat)*3));
// bind patchparam
if (mesh->GetDrawContext()->GetPatchParamTextureBuffer()) {
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_BUFFER,
mesh->GetDrawContext()->GetPatchParamTextureBuffer());
}
Osd::DrawContext::PatchArrayVector const & patches =
mesh->GetDrawContext()->GetPatchArrays();
for (int i=0; i<(int)patches.size(); ++i) {
Osd::DrawContext::PatchArray const & patch = patches[i];
Far::PatchDescriptor desc = patch.GetDescriptor();
Far::PatchDescriptor::Type patchType = desc.GetType();
GLenum primType;
switch(patchType) {
case Far::PatchDescriptor::QUADS:
primType = GL_LINES_ADJACENCY;
break;
case Far::PatchDescriptor::TRIANGLES:
primType = GL_TRIANGLES;
break;
default:
primType = GL_PATCHES;
glPatchParameteri(GL_PATCH_VERTICES, desc.GetNumControlVertices());
}
GLuint program = shaderCache->GetDrawConfig(desc)->GetProgram();
glUseProgram(program);
GLuint diffuseColor =
glGetUniformLocation(program, "diffuseColor");
GLuint uniformPrimitiveIdBase =
glGetUniformLocation(program, "PrimitiveIdBase");
if (primType == GL_PATCHES) {
float const * color = getAdaptivePatchColor( desc );
glProgramUniform4f(program, diffuseColor,
color[0], color[1], color[2], color[3]);
glProgramUniform1i(program, uniformPrimitiveIdBase,
patch.GetPatchIndex());
} else {
glProgramUniform4f(program, diffuseColor, 0.4f, 0.4f, 0.8f, 1);
}
glDrawElements(primType, patch.GetNumIndices(), GL_UNSIGNED_INT,
(void *)(patch.GetVertIndex() * sizeof(unsigned int)));
}
glDisableVertexAttribArray(0);
glDisableVertexAttribArray(1);
glBindVertexArray(0);
glDeleteVertexArrays(1, &vao);
// cleanup
delete shape;
delete mesh;
// mesh takes an ownership of topologyRefiner. no need to delete it.
}
static void usage(const char *program) {
std::cout
<< "Usage %s : " << program << "\n"
<< " -a : adaptive refinement\n"
<< " -l <isolation level> : isolation level (default = 2)\n"
<< " -t <tess level> : tessellation level (default = 1)\n"
<< " -w <prefix> : write images to PNG as\n"
<< " <prefix>_<kernel>_modelname.png\n"
<< " -s <width> <height> : image size (default = 128 128)\n"
<< " -k <kernel>,<kernel>... : kernel types (default = all)\n"
<< " kernel = [CPU, OPENMP, TBB, CUDA, CL, XFB, GLSL]\n"
<< " -d <displayMode> : display mode\n"
<< " displayMode = [PATCH_TYPE, VARYING, NORMAL]\n";
}
int main(int argc, char ** argv) {
int width = 128;
int height = 128;
int tessLevel = 1;
int isolationLevel = 2;
bool writeToFile = false;
bool adaptive = false;
std::string prefix;
std::string displayMode = "PATCH_TYPE";
std::vector<std::string> kernels;
for (int i = 1; i < argc; ++i) {
if (!strcmp(argv[i], "-a")) {
adaptive = true;
} else if (!strcmp(argv[i], "-l")) {
isolationLevel = atoi(argv[++i]);
} else if (!strcmp(argv[i], "-k")) {
std::stringstream ss(argv[++i]);
std::string kernel;
while(std::getline(ss, kernel, ',')) {
kernels.push_back(kernel);
}
} else if (!strcmp(argv[i], "-t")) {
tessLevel = atoi(argv[++i]);
} else if (!strcmp(argv[i], "-w")) {
writeToFile = true;
prefix = argv[++i];
} else if (!strcmp(argv[i], "-s")) {
width = atoi(argv[++i]);
height = atoi(argv[++i]);
} else if (!strcmp(argv[i], "-d")) {
displayMode = argv[++i];
} else {
usage(argv[0]);
return 1;
}
}
// by default, test all available kernels
if (kernels.empty()) {
kernels.push_back("CPU");
#ifdef OPENSUBDIV_HAS_OPENMP
kernels.push_back("OPENMP");
#endif
#ifdef OPENSUBDIV_HAS_TBB
kernels.push_back("TBB");
#endif
#ifdef OPENSUBDIV_HAS_CUDA
kernels.push_back("CUDA");
#endif
#ifdef OPENSUBDIV_HAS_OPENCL
kernels.push_back("CL");
#endif
#ifdef OPENSUBDIV_HAS_GLSL_TRANSFORM_FEEDBACK
kernels.push_back("XFB");
#endif
#ifdef OPENSUBDIV_HAS_GLSL_COMPUTE
kernels.push_back("GLSL");
#endif
}
if (not glfwInit()) {
std::cout << "Failed to initialize GLFW\n";
return 1;
}
static const char windowTitle[] =
"OpenSubdiv imaging test " OPENSUBDIV_VERSION_STRING;
setGLCoreProfile();
GLFWwindow *window = glfwCreateWindow(width, height, windowTitle, NULL, NULL);
if (not window) {
std::cout << "Failed to open window.\n";
glfwTerminate();
}
glfwMakeContextCurrent(window);
#if defined(OSD_USES_GLEW)
// this is the only way to initialize glew correctly under core profile context.
glewExperimental = true;
if (GLenum r = glewInit() != GLEW_OK) {
std::cout << "Failed to initialize glew. Error = "
<< glewGetErrorString(r) << "\n";
exit(1);
}
// clear GL errors generated during glewInit()
glGetError();
#endif
initShapes();
// initialize GL states
glViewport(0, 0, width, height);
glEnable(GL_DEPTH_TEST);
// some regression shapes are not visible in this camera
// with backface culling.
// glEnable(GL_CULL_FACE);
// transform uniform
float modelview[16] = {
0.945518f, -0.191364f, 0.263390f, 0.000000f,
0.325568f, 0.555762f, -0.764941f, 0.000000f,
0.000000f, 0.809017f, 0.587785f, 0.000000f,
0.000000f, 0.000000f, -1.500000f, 1.000000f };
float projection[16] = {
2.414213f, 0.000000f, 0.000000f, 0.000000f,
0.000000f, 2.414213f, 0.000000f, 0.000000f,
0.000000f, 0.000000f, -1.000000f, -1.000000f,
0.000000f, 0.000000f, -0.000200f, 0.000000f
};
struct Transform {
float ModelViewMatrix[16];
float ProjectionMatrix[16];
float Viewport[4];
float TessLevel;
} transformData;
memcpy(transformData.ModelViewMatrix, modelview, sizeof(modelview));
memcpy(transformData.ProjectionMatrix, projection, sizeof(projection));
transformData.Viewport[0] = 0;
transformData.Viewport[1] = 0;
transformData.Viewport[2] = static_cast<float>(width);
transformData.Viewport[3] = static_cast<float>(height);
transformData.TessLevel = static_cast<float>(1 << tessLevel);
GLuint transformUB = 0;
glGenBuffers(1, &transformUB);
glBindBuffer(GL_UNIFORM_BUFFER, transformUB);
glBufferData(GL_UNIFORM_BUFFER, sizeof(transformData),
&transformData, GL_STATIC_DRAW);
glBindBufferBase(GL_UNIFORM_BUFFER, /*binding=*/0, transformUB);
// create draw registry;
ShaderCache shaderCache(displayMode);
// write report html
if (writeToFile) {
std::string reportfile = prefix + ".html";
std::ofstream ofs(reportfile.c_str());
ofs << "<html>\n"
<< "<head><style>\n"
<< "table { border-collapse:collapse; } "
<< "table,th,td {border: 1px solid black} "
<< "</style></head>\n";
ofs << "<body>\n";
ofs << "<h3>OpenSubdiv imaging regression test<h3>\n";
ofs << "<pre>\n";
ofs << "OpenSubdiv : " << OPENSUBDIV_VERSION_STRING << "\n";
ofs << "GL Version : " << glGetString(GL_VERSION)
<< ", " << glGetString(GL_VENDOR)
<< ", " << glGetString(GL_RENDERER)
<< "\n";
ofs << "Isolation Level : " << isolationLevel << "\n";
ofs << "Tess Level : " << tessLevel << "\n";
ofs << "Adaptive : " << adaptive << "\n";
ofs << "Display Mode : " << displayMode << "\n";
ofs << "</pre>\n";
ofs << "<table>\n";
ofs << "<tr>\n";
ofs << "<th>Reference<br>(on github. to be updated)</th>\n";
for (size_t k = 0; k < kernels.size(); ++k) {
ofs << "<th>" << kernels[k] << "</th>\n";
}
ofs << "</tr>\n";
for (size_t i = 0; i < g_shapes.size(); ++i) {
ofs << "<tr>\n";
ofs << "<td>" << g_shapes[i].name << "</td>\n";
for (size_t k = 0; k < kernels.size(); ++k) {
ofs << "<td>";
ofs << "<img src='" << prefix << "_" << kernels[k] << "_" << g_shapes[i].name << ".png'>";
ofs << "</td>";
}
ofs << "</tr>\n";
}
ofs << "</table>\n";
ofs << "</body></html>\n";
ofs.close();
}
// run test
for (size_t k = 0; k < kernels.size(); ++k) {
std::string const &kernel = kernels[k];
// prep GPU kernel
#ifdef OPENSUBDIV_HAS_OPENCL
if (kernel == "CL") {
if (g_clDeviceContext.IsInitialized() == false) {
if (g_clDeviceContext.Initialize() == false) {
std::cout << "Error in initializing OpenCL\n";
exit(1);
}
}
}
#endif
#ifdef OPENSUBDIV_HAS_CUDA
if (kernel == "CUDA") {
if (g_cudaDeviceContext.IsInitialized() == false) {
if (g_cudaDeviceContext.Initialize() == false) {
std::cout << "Error in initializing Cuda\n";
exit(1);
}
}
}
#endif
for (size_t i = 0; i < g_shapes.size(); ++i) {
// run test
runTest(g_shapes[i], kernel, isolationLevel, adaptive, &shaderCache);
if (writeToFile) {
// read back pixels
std::vector<unsigned char> data(width*height*3);
glReadPixels(0, 0, width, height, GL_RGB, GL_UNSIGNED_BYTE, &data[0]);
// write image
std::string filename = prefix + "_" + kernel + "_" + g_shapes[i].name + ".png";
// flip vertical
stbi_write_png(filename.c_str(), width, height, 3, &data[width*3*(height-1)], -width*3);
}
glfwSwapBuffers(window);
}
}
return 0;
}