Added opengl resources deleting at exit (textures, arrays...)

This commit is contained in:
Michaël Lemaire 2015-12-10 23:41:42 +01:00
parent bc9db69564
commit 479dcb03ac
27 changed files with 203 additions and 65 deletions

View file

@ -117,7 +117,7 @@ Vector3 Vector3::midPointTo(const Vector3 &other) const {
Vector3 Vector3::randomInSphere(double radius, bool only_surface, RandomGenerator &random) { Vector3 Vector3::randomInSphere(double radius, bool only_surface, RandomGenerator &random) {
// TODO More uniform spatial repartition // TODO More uniform spatial repartition
// The current randomization clusters result near the center and at the poles // The current randomization clusters result near the center and at the poles
VectorSpherical vec = {only_surface ? radius : random.genDouble() * radius, VectorSpherical vec = {only_surface ? radius : random.genDouble() * radius, (random.genDouble() - 0.5) * M_PI,
(random.genDouble() - 0.5) * M_PI, random.genDouble() * M_2PI}; random.genDouble() * M_2PI};
return Vector3(vec); return Vector3(vec);
} }

View file

@ -71,7 +71,8 @@ class BASICSSHARED_EXPORT Vector3 {
* *
* If *only_surface* is true, produce a vector with *radius* as length. * If *only_surface* is true, produce a vector with *radius* as length.
*/ */
static Vector3 randomInSphere(double radius = 1.0, bool only_surface = false, RandomGenerator &random = RandomGeneratorDefault); static Vector3 randomInSphere(double radius = 1.0, bool only_surface = false,
RandomGenerator &random = RandomGeneratorDefault);
public: public:
// TODO Make private // TODO Make private

View file

@ -153,8 +153,7 @@ void AtmosphereDefinition::generateStars(int count, RandomGenerator &random) {
for (int i = 0; i < count; ++i) { for (int i = 0; i < count; ++i) {
Star star; Star star;
star.location = star.location = Vector3((random.genDouble() - 0.5) * 100000.0, (random.genDouble() * 0.5) * 100000.0,
Vector3((random.genDouble() - 0.5) * 100000.0, (random.genDouble() * 0.5) * 100000.0,
(random.genDouble() - 0.5) * 100000.0); (random.genDouble() - 0.5) * 100000.0);
if (star.location.getNorm() < 30000.0) { if (star.location.getNorm() < 30000.0) {
i--; i--;

View file

@ -108,8 +108,7 @@ void Scenery::autoPreset(RandomGenerator &random) {
Logs::debug() << "[Definition] New scenery generated from seed " << random.getSeed() << std::endl; Logs::debug() << "[Definition] New scenery generated from seed " << random.getSeed() << std::endl;
} }
void Scenery::autoPreset(unsigned int seed) void Scenery::autoPreset(unsigned int seed) {
{
RandomGenerator random(seed); RandomGenerator random(seed);
autoPreset(random); autoPreset(random);
} }

View file

@ -42,23 +42,11 @@ MainModelerWindow::MainModelerWindow() {
render_process = new RenderProcess(this, render_preview_provider); render_process = new RenderProcess(this, render_preview_provider);
// Bind file buttons connectQmlSignal("tool_file_new", SIGNAL(clicked()), this, SLOT(newFile()));
QObject *button_new = findQmlObject("tool_file_new"); connectQmlSignal("tool_file_save", SIGNAL(clicked()), this, SLOT(saveFile()));
if (button_new) { connectQmlSignal("tool_file_load", SIGNAL(clicked()), this, SLOT(loadFile()));
connect(button_new, SIGNAL(clicked()), this, SLOT(newFile())); connectQmlSignal("tool_file_exit", SIGNAL(clicked()), this, SLOT(exit()));
} connectQmlSignal("root", SIGNAL(stopped()), this, SLOT(effectiveExit()));
QObject *button_save = findQmlObject("tool_file_save");
if (button_save) {
connect(button_save, SIGNAL(clicked()), this, SLOT(saveFile()));
}
QObject *button_load = findQmlObject("tool_file_load");
if (button_load) {
connect(button_load, SIGNAL(clicked()), this, SLOT(loadFile()));
}
QObject *button_exit = findQmlObject("tool_file_exit");
if (button_exit) {
connect(button_exit, SIGNAL(clicked()), this, SLOT(exit()));
}
} }
MainModelerWindow::~MainModelerWindow() { MainModelerWindow::~MainModelerWindow() {
@ -126,7 +114,7 @@ void MainModelerWindow::loadFile() {
} }
void MainModelerWindow::exit() { void MainModelerWindow::exit() {
close(); renderer->stop();
} }
void MainModelerWindow::keyReleaseEvent(QKeyEvent *event) { void MainModelerWindow::keyReleaseEvent(QKeyEvent *event) {
@ -178,3 +166,7 @@ void MainModelerWindow::keyReleaseEvent(QKeyEvent *event) {
} }
} }
} }
void MainModelerWindow::effectiveExit() {
close();
}

View file

@ -40,6 +40,9 @@ class MainModelerWindow : public QQuickView {
protected: protected:
virtual void keyReleaseEvent(QKeyEvent *event) override; virtual void keyReleaseEvent(QKeyEvent *event) override;
protected slots:
void effectiveExit();
private: private:
Scenery *scenery; Scenery *scenery;
OpenGLRenderer *renderer; OpenGLRenderer *renderer;

View file

@ -41,6 +41,11 @@ void OpenGLView::paint() {
return; return;
} }
if (renderer->isStopped()) {
emit stopped();
return;
}
if (not initialized or not renderer) { if (not initialized or not renderer) {
renderer->initialize(); renderer->initialize();
initialized = true; initialized = true;

View file

@ -19,6 +19,9 @@ class OpenGLView : public QQuickItem {
void handleResize(); void handleResize();
void handleSceneGraphReady(); void handleSceneGraphReady();
signals:
void stopped();
protected: protected:
virtual void wheelEvent(QWheelEvent *event) override; virtual void wheelEvent(QWheelEvent *event) override;
virtual void mousePressEvent(QMouseEvent *event) override; virtual void mousePressEvent(QMouseEvent *event) override;

View file

@ -1,5 +1,6 @@
#include "OpenGLPart.h" #include "OpenGLPart.h"
#include "OpenGLRenderer.h"
#include "OpenGLShaderProgram.h" #include "OpenGLShaderProgram.h"
#include "OpenGLVertexArray.h" #include "OpenGLVertexArray.h"
@ -7,14 +8,25 @@ OpenGLPart::OpenGLPart(OpenGLRenderer *renderer) : renderer(renderer) {
} }
OpenGLPart::~OpenGLPart() { OpenGLPart::~OpenGLPart() {
for (auto &pair: shaders) { for (auto pair : shaders) {
delete pair.second; delete pair.second;
} }
for (auto &array: arrays) { for (auto array : arrays) {
delete array; delete array;
} }
} }
void OpenGLPart::destroy() {
OpenGLFunctions *functions = getFunctions();
for (auto shader : shaders) {
shader.second->destroy(functions);
}
for (auto array : arrays) {
array->destroy(functions);
}
}
void OpenGLPart::interrupt() { void OpenGLPart::interrupt() {
} }
@ -29,13 +41,16 @@ OpenGLShaderProgram *OpenGLPart::createShader(const std::string &name) {
} }
} }
OpenGLVertexArray *OpenGLPart::createVertexArray(bool has_uv, bool strip) OpenGLVertexArray *OpenGLPart::createVertexArray(bool has_uv, bool strip) {
{
OpenGLVertexArray *result = new OpenGLVertexArray(has_uv, strip); OpenGLVertexArray *result = new OpenGLVertexArray(has_uv, strip);
arrays.push_back(result); arrays.push_back(result);
return result; return result;
} }
OpenGLFunctions *OpenGLPart::getFunctions() {
return renderer->getOpenGlFunctions();
}
void OpenGLPart::updateScenery(bool onlyCommon) { void OpenGLPart::updateScenery(bool onlyCommon) {
// Let subclass do its own collecting // Let subclass do its own collecting
if (not onlyCommon) { if (not onlyCommon) {

View file

@ -26,6 +26,9 @@ class OPENGLSHARED_EXPORT OpenGLPart {
// Do the rendering // Do the rendering
virtual void render() = 0; virtual void render() = 0;
// Free opengl resources generated in context (like textures...)
virtual void destroy();
// Interrupt the rendering // Interrupt the rendering
virtual void interrupt(); virtual void interrupt();
@ -46,6 +49,8 @@ class OPENGLSHARED_EXPORT OpenGLPart {
*/ */
OpenGLVertexArray *createVertexArray(bool has_uv, bool strip); OpenGLVertexArray *createVertexArray(bool has_uv, bool strip);
OpenGLFunctions *getFunctions();
// Access to the main scenery renderer // Access to the main scenery renderer
OpenGLRenderer *renderer; OpenGLRenderer *renderer;

View file

@ -18,6 +18,8 @@ OpenGLRenderer::OpenGLRenderer(Scenery *scenery) : SoftwareRenderer(scenery) {
ready = false; ready = false;
paused = false; paused = false;
displayed = false; displayed = false;
stopping = false;
stopped = false;
vp_width = 1; vp_width = 1;
vp_height = 1; vp_height = 1;
@ -65,6 +67,16 @@ void OpenGLRenderer::checkForErrors(const std::string &domain) {
} }
} }
void OpenGLRenderer::destroy() {
shared_state->destroy(functions);
skybox->destroy();
terrain->destroy();
water->destroy();
checkForErrors("stopping");
}
void OpenGLRenderer::initialize() { void OpenGLRenderer::initialize() {
ready = functions->initializeOpenGLFunctions(); ready = functions->initializeOpenGLFunctions();
@ -145,7 +157,12 @@ void OpenGLRenderer::resize(int width, int height) {
} }
void OpenGLRenderer::paint(bool clear) { void OpenGLRenderer::paint(bool clear) {
if (ready and not paused) { if (stopping) {
if (not stopped) {
destroy();
stopped = true;
}
} else if (ready and not paused) {
checkForErrors("before_paint"); checkForErrors("before_paint");
if (clear) { if (clear) {
functions->glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); functions->glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
@ -169,6 +186,11 @@ void OpenGLRenderer::paint(bool clear) {
} }
} }
bool OpenGLRenderer::stop() {
stopping = true;
return stopped;
}
void OpenGLRenderer::reset() { void OpenGLRenderer::reset() {
if (ready) { if (ready) {
skybox->updateScenery(); skybox->updateScenery();

View file

@ -10,8 +10,8 @@ class QMatrix4x4;
namespace paysages { namespace paysages {
namespace opengl { namespace opengl {
/*! /**
* \brief Scenery renderer in an OpenGL context. * Scenery renderer in an OpenGL context.
*/ */
class OPENGLSHARED_EXPORT OpenGLRenderer : public SoftwareRenderer { class OPENGLSHARED_EXPORT OpenGLRenderer : public SoftwareRenderer {
public: public:
@ -30,6 +30,12 @@ class OPENGLSHARED_EXPORT OpenGLRenderer : public SoftwareRenderer {
inline bool isDisplayed() const { inline bool isDisplayed() const {
return displayed; return displayed;
} }
inline bool isStopping() const {
return stopping;
}
inline bool isStopped() const {
return stopped;
}
/** /**
* Check for errors in OpenGL context. * Check for errors in OpenGL context.
@ -38,11 +44,27 @@ class OPENGLSHARED_EXPORT OpenGLRenderer : public SoftwareRenderer {
*/ */
void checkForErrors(const std::string &domain); void checkForErrors(const std::string &domain);
/**
* Release any allocated resource in the opengl context.
*
* Must be called in the opengl rendering thread, and before the destructor is called.
*/
void destroy();
void initialize(); void initialize();
void prepareOpenGLState(bool clear = true); void prepareOpenGLState(bool clear = true);
void resize(int width, int height); void resize(int width, int height);
void paint(bool clear = true); void paint(bool clear = true);
/**
* Ask for the rendering to stop gracefully.
*
* Returns true if the rendering is stopped and resources freed.
*
* This should be called in an idle loop, while it returns false.
*/
bool stop();
/** /**
* Reset the whole state (when the scenery has been massively updated). * Reset the whole state (when the scenery has been massively updated).
*/ */
@ -95,6 +117,8 @@ class OPENGLSHARED_EXPORT OpenGLRenderer : public SoftwareRenderer {
bool ready; bool ready;
bool paused; bool paused;
bool displayed; bool displayed;
bool stopping;
bool stopped;
int vp_width; int vp_width;
int vp_height; int vp_height;

View file

@ -41,6 +41,10 @@ void OpenGLShaderProgram::addFragmentSource(const std::string &path) {
} }
} }
void OpenGLShaderProgram::destroy(OpenGLFunctions *functions) {
program->removeAllShaders();
}
void OpenGLShaderProgram::compile() { void OpenGLShaderProgram::compile() {
std::string prefix = std::string("#version ") + OPENGL_GLSL_VERSION + "\n\n"; std::string prefix = std::string("#version ") + OPENGL_GLSL_VERSION + "\n\n";

View file

@ -16,6 +16,13 @@ class OPENGLSHARED_EXPORT OpenGLShaderProgram {
void addVertexSource(const std::string &path); void addVertexSource(const std::string &path);
void addFragmentSource(const std::string &path); void addFragmentSource(const std::string &path);
/**
* Release any allocated resource in the opengl context.
*
* Must be called in the opengl rendering thread, and before the destructor is called.
*/
void destroy(OpenGLFunctions *functions);
/** /**
* Draw a VertexArray object. * Draw a VertexArray object.
* *

View file

@ -15,6 +15,12 @@ void OpenGLSharedState::apply(OpenGLShaderProgram *program, int &texture_unit) {
} }
} }
void OpenGLSharedState::destroy(OpenGLFunctions *functions) {
for (const auto &pair : variables) {
pair.second->destroy(functions);
}
}
OpenGLVariable *OpenGLSharedState::get(const std::string &name) { OpenGLVariable *OpenGLSharedState::get(const std::string &name) {
OpenGLVariable *&var = variables[name]; OpenGLVariable *&var = variables[name];
if (var == NULL) { if (var == NULL) {

View file

@ -11,21 +11,28 @@ class QImage;
namespace paysages { namespace paysages {
namespace opengl { namespace opengl {
/*! /**
* \brief OpenGL variables that can be shared between shaders. * OpenGL variables that can be shared between shaders.
*/ */
class OPENGLSHARED_EXPORT OpenGLSharedState { class OPENGLSHARED_EXPORT OpenGLSharedState {
public: public:
OpenGLSharedState(); OpenGLSharedState();
~OpenGLSharedState(); ~OpenGLSharedState();
/*! /**
* \brief Apply the stored variables to the bound program. * Apply the stored variables to the bound program.
*/ */
void apply(OpenGLShaderProgram *program, int &texture_unit); void apply(OpenGLShaderProgram *program, int &texture_unit);
/*! /**
* \brief Get or create a variable in the state. * Release any allocated resource in the opengl context.
*
* Must be called in the opengl rendering thread, and before the destructor is called.
*/
void destroy(OpenGLFunctions *functions);
/**
* Get or create a variable in the state.
*/ */
OpenGLVariable *get(const std::string &name); OpenGLVariable *get(const std::string &name);

View file

@ -94,6 +94,13 @@ void OpenGLTerrain::interrupt() {
} }
} }
void OpenGLTerrain::destroy() {
OpenGLFunctions *functions = getFunctions();
for (auto &chunk : _chunks) {
chunk->destroy(functions);
}
}
void OpenGLTerrain::pause() { void OpenGLTerrain::pause() {
paused = true; paused = true;
interrupt(); interrupt();

View file

@ -22,6 +22,7 @@ class OPENGLSHARED_EXPORT OpenGLTerrain : public OpenGLPart, public DefinitionWa
virtual void update() override; virtual void update() override;
virtual void render() override; virtual void render() override;
virtual void interrupt() override; virtual void interrupt() override;
virtual void destroy() override;
void pause(); void pause();
void resume(); void resume();

View file

@ -146,14 +146,14 @@ void OpenGLTerrainChunk::updatePriority(CameraDefinition *camera) {
_lock_data->release(); _lock_data->release();
// Update wanted LOD // Update wanted LOD
if (distance_to_camera < 60.0) { if (distance_to_camera < 100.0) {
_texture_wanted_size = _texture_max_size; _texture_wanted_size = _texture_max_size;
} else if (distance_to_camera < 140.0) { } else if (distance_to_camera < 200.0) {
_texture_wanted_size = _texture_max_size / 4; _texture_wanted_size = _texture_max_size / 4;
} else if (distance_to_camera < 300.0) { } else if (distance_to_camera < 400.0) {
_texture_wanted_size = _texture_max_size / 8; _texture_wanted_size = _texture_max_size / 8;
} else { } else {
_texture_wanted_size = 8; _texture_wanted_size = _texture_max_size / 16;
} }
// Update priority // Update priority
@ -195,6 +195,11 @@ void OpenGLTerrainChunk::askResume() {
interrupt = false; interrupt = false;
} }
void OpenGLTerrainChunk::destroy(OpenGLFunctions *functions) {
vertices->destroy(functions);
glstate->destroy(functions);
}
void OpenGLTerrainChunk::setFirstStepVertices() { void OpenGLTerrainChunk::setFirstStepVertices() {
OpenGLVertexArray next(true); OpenGLVertexArray next(true);
next.setVertexCount(6); next.setVertexCount(6);
@ -210,6 +215,9 @@ void OpenGLTerrainChunk::augmentVertices() {
// TODO Re-use existing vertices from previous level when possible // TODO Re-use existing vertices from previous level when possible
double quad_size = _size / (double)next_vertices_level; double quad_size = _size / (double)next_vertices_level;
for (int iz = 0; iz < next_vertices_level; iz++) { for (int iz = 0; iz < next_vertices_level; iz++) {
if (interrupt or _reset_topology) {
return;
}
for (int ix = 0; ix < next_vertices_level; ix++) { for (int ix = 0; ix < next_vertices_level; ix++) {
fillVerticesFromSquare(&next, (iz * next_vertices_level + ix) * 6, _startx + quad_size * (double)ix, fillVerticesFromSquare(&next, (iz * next_vertices_level + ix) * 6, _startx + quad_size * (double)ix,
_startz + quad_size * (double)iz, quad_size); _startz + quad_size * (double)iz, quad_size);

View file

@ -28,6 +28,13 @@ class OPENGLSHARED_EXPORT OpenGLTerrainChunk {
return vertices; return vertices;
} }
/**
* Release any allocated resource in the opengl context.
*
* Must be called in the opengl rendering thread, and before the destructor is called.
*/
void destroy(OpenGLFunctions *functions);
/** /**
* Fill *vertices* with a quick initial set of vertices, that can be augmented later using *augmentVertices*. * Fill *vertices* with a quick initial set of vertices, that can be augmented later using *augmentVertices*.
*/ */

View file

@ -79,6 +79,13 @@ void OpenGLVariable::apply(OpenGLShaderProgram *program, int &texture_unit) {
} }
} }
void OpenGLVariable::destroy(OpenGLFunctions *functions) {
if (texture_id) {
functions->glDeleteTextures(1, &texture_id);
texture_id = 0;
}
}
void OpenGLVariable::set(const Texture2D *texture, bool repeat, bool color) { void OpenGLVariable::set(const Texture2D *texture, bool repeat, bool color) {
assert(type == TYPE_NONE or type == TYPE_TEXTURE_2D); assert(type == TYPE_NONE or type == TYPE_TEXTURE_2D);
@ -111,8 +118,7 @@ void OpenGLVariable::set(const Texture2D *texture, bool repeat, bool color) {
texture_color = color; texture_color = color;
} }
void OpenGLVariable::set(const QImage &texture, bool repeat, bool color) void OpenGLVariable::set(const QImage &texture, bool repeat, bool color) {
{
assert(type == TYPE_NONE or type == TYPE_TEXTURE_2D); assert(type == TYPE_NONE or type == TYPE_TEXTURE_2D);
type = TYPE_TEXTURE_2D; type = TYPE_TEXTURE_2D;
@ -272,8 +278,10 @@ void OpenGLVariable::uploadTexture(OpenGLRenderer *renderer) {
int dest_format = texture_color ? GL_RGBA : GL_RED; int dest_format = texture_color ? GL_RGBA : GL_RED;
if (type == TYPE_TEXTURE_2D) { if (type == TYPE_TEXTURE_2D) {
functions->glTexImage2D(GL_TEXTURE_2D, 0, dest_format, texture_size_x, texture_size_y, 0, GL_RGBA, GL_FLOAT, value_texture_data); functions->glTexImage2D(GL_TEXTURE_2D, 0, dest_format, texture_size_x, texture_size_y, 0, GL_RGBA, GL_FLOAT,
value_texture_data);
} else { } else {
functions->glTexImage3D(GL_TEXTURE_3D, 0, dest_format, texture_size_x, texture_size_y, texture_size_z, 0, GL_RGBA, GL_FLOAT, value_texture_data); functions->glTexImage3D(GL_TEXTURE_3D, 0, dest_format, texture_size_x, texture_size_y, texture_size_z, 0,
GL_RGBA, GL_FLOAT, value_texture_data);
} }
} }

View file

@ -33,6 +33,13 @@ class OpenGLVariable {
void apply(OpenGLShaderProgram *program, int &texture_unit); void apply(OpenGLShaderProgram *program, int &texture_unit);
/**
* Release any allocated resource in the opengl context.
*
* Must be called in the opengl rendering thread, and before the destructor is called.
*/
void destroy(OpenGLFunctions *functions);
void set(const Texture2D *texture, bool repeat = false, bool color = true); void set(const Texture2D *texture, bool repeat = false, bool color = true);
void set(const QImage &texture, bool repeat = false, bool color = true); void set(const QImage &texture, bool repeat = false, bool color = true);
void set(const Texture3D *texture, bool repeat = false, bool color = true); void set(const Texture3D *texture, bool repeat = false, bool color = true);

View file

@ -30,8 +30,19 @@ OpenGLVertexArray::~OpenGLVertexArray() {
free(array_uv); free(array_uv);
} }
void OpenGLVertexArray::destroy() { void OpenGLVertexArray::destroy(OpenGLFunctions *functions) {
// TODO if (vbo_vertex) {
functions->glDeleteBuffers(1, &vbo_vertex);
vbo_vertex = 0;
}
if (vbo_uv) {
functions->glDeleteBuffers(1, &vbo_uv);
vbo_uv = 0;
}
if (vao) {
functions->glDeleteVertexArrays(1, &vao);
vao = 0;
}
} }
void OpenGLVertexArray::render(OpenGLFunctions *functions) { void OpenGLVertexArray::render(OpenGLFunctions *functions) {
@ -72,8 +83,7 @@ void OpenGLVertexArray::set(int index, const Vector3 &location, double u, double
} }
} }
void OpenGLVertexArray::get(int index, Vector3 *location, double *u, double *v) const void OpenGLVertexArray::get(int index, Vector3 *location, double *u, double *v) const {
{
if (index >= 0 and index < vertexcount) { if (index >= 0 and index < vertexcount) {
location->x = array_vertex[index * 3]; location->x = array_vertex[index * 3];
location->y = array_vertex[index * 3 + 1]; location->y = array_vertex[index * 3 + 1];
@ -85,8 +95,7 @@ void OpenGLVertexArray::get(int index, Vector3 *location, double *u, double *v)
} }
} }
void OpenGLVertexArray::copyTo(OpenGLVertexArray *destination) const void OpenGLVertexArray::copyTo(OpenGLVertexArray *destination) const {
{
destination->setVertexCount(vertexcount); destination->setVertexCount(vertexcount);
if (vertexcount) { if (vertexcount) {
memcpy(destination->array_vertex, array_vertex, sizeof(float) * vertexcount * 3); memcpy(destination->array_vertex, array_vertex, sizeof(float) * vertexcount * 3);

View file

@ -25,7 +25,7 @@ class OpenGLVertexArray {
* *
* Must be called in the opengl rendering thread, and before the destructor is called. * Must be called in the opengl rendering thread, and before the destructor is called.
*/ */
void destroy(); void destroy(OpenGLFunctions *functions);
/** /**
* Render this array in current opengl context. * Render this array in current opengl context.

View file

@ -27,12 +27,10 @@ RandomGenerator::RandomGenerator(RandomGenerator::Seed seed) {
data = new RandomGeneratorPrivate(seed); data = new RandomGeneratorPrivate(seed);
} }
RandomGenerator::~RandomGenerator() RandomGenerator::~RandomGenerator() {
{
delete data; delete data;
} }
double RandomGenerator::genDouble() double RandomGenerator::genDouble() {
{
return data->distribution_double(data->generator); return data->distribution_double(data->generator);
} }

View file

@ -6,7 +6,8 @@
#include "OpenGLVertexArray.h" #include "OpenGLVertexArray.h"
#include "Vector3.h" #include "Vector3.h"
static void checkVertex(const OpenGLVertexArray *array, int index, const Vector3 &expected_location, double expected_u, double expected_v) { static void checkVertex(const OpenGLVertexArray *array, int index, const Vector3 &expected_location, double expected_u,
double expected_v) {
Vector3 location; Vector3 location;
double u, v; double u, v;