From 9d7a7a0ff7289199978d35a98e2880c0ce86965f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20Lemaire?= Date: Wed, 25 Nov 2015 23:15:58 +0100 Subject: [PATCH] Added vegetation impostors to OpenGL renderer This is currently an unoptimized and broken version, to be improved --- src/basics/Color.h | 1 + src/basics/Color.inline.cpp | 6 + .../VegetationPresenceDefinition.cpp | 13 +- src/interface/commandline/commandline.pro | 6 + src/interface/commandline/tests.cpp | 25 ++- src/render/opengl/OpenGLPart.cpp | 10 ++ src/render/opengl/OpenGLPart.h | 10 ++ src/render/opengl/OpenGLRenderer.cpp | 20 ++- src/render/opengl/OpenGLRenderer.h | 4 + src/render/opengl/OpenGLShaderProgram.cpp | 3 + src/render/opengl/OpenGLShaderProgram.h | 5 + src/render/opengl/OpenGLSharedState.cpp | 2 +- src/render/opengl/OpenGLSharedState.h | 9 +- src/render/opengl/OpenGLVariable.cpp | 11 ++ src/render/opengl/OpenGLVariable.h | 3 + src/render/opengl/OpenGLVegetation.cpp | 155 ++++++++++++++++++ src/render/opengl/OpenGLVegetation.h | 89 ++++++++++ .../opengl/OpenGLVegetationImpostor.cpp | 106 ++++++++++++ src/render/opengl/OpenGLVegetationImpostor.h | 43 +++++ .../opengl/OpenGLVegetationInstance.cpp | 11 ++ src/render/opengl/OpenGLVegetationInstance.h | 43 +++++ src/render/opengl/OpenGLVegetationLayer.cpp | 141 ++++++++++++++++ src/render/opengl/OpenGLVegetationLayer.h | 82 +++++++++ src/render/opengl/opengl.pro | 4 + src/render/opengl/opengl_global.h | 4 + src/render/opengl/shaders/resources.qrc | 2 + src/render/opengl/shaders/vegetation.frag | 15 ++ src/render/opengl/shaders/vegetation.vert | 16 ++ src/render/software/SoftwareRenderer.cpp | 1 + .../software/VegetationModelRenderer.cpp | 17 ++ src/render/software/VegetationRenderer.cpp | 3 + src/render/software/VegetationRenderer.h | 1 + src/tests/OpenGLVegetationLayer_Test.cpp | 67 ++++++++ src/tests/OpenGLVegetation_Test.cpp | 19 +++ src/tests/OverlayRasterizer_Test.cpp | 3 +- 35 files changed, 935 insertions(+), 15 deletions(-) create mode 100644 src/render/opengl/OpenGLVegetation.cpp create mode 100644 src/render/opengl/OpenGLVegetation.h create mode 100644 src/render/opengl/OpenGLVegetationImpostor.cpp create mode 100644 src/render/opengl/OpenGLVegetationImpostor.h create mode 100644 src/render/opengl/OpenGLVegetationInstance.cpp create mode 100644 src/render/opengl/OpenGLVegetationInstance.h create mode 100644 src/render/opengl/OpenGLVegetationLayer.cpp create mode 100644 src/render/opengl/OpenGLVegetationLayer.h create mode 100644 src/render/opengl/shaders/vegetation.frag create mode 100644 src/render/opengl/shaders/vegetation.vert create mode 100644 src/tests/OpenGLVegetationLayer_Test.cpp create mode 100644 src/tests/OpenGLVegetation_Test.cpp diff --git a/src/basics/Color.h b/src/basics/Color.h index a305fdb..9af2225 100644 --- a/src/basics/Color.h +++ b/src/basics/Color.h @@ -27,6 +27,7 @@ class BASICSSHARED_EXPORT Color { void mask(const Color &mask); double normalize(); + Color normalized(); double getValue() const; double getPower() const; void limitPower(double max_power); diff --git a/src/basics/Color.inline.cpp b/src/basics/Color.inline.cpp index 5e1cfcd..1a4330e 100644 --- a/src/basics/Color.inline.cpp +++ b/src/basics/Color.inline.cpp @@ -118,6 +118,12 @@ METHSPEC double Color::normalize() { return max;*/ } +METHSPEC Color Color::normalized() { + Color col = *this; + col.normalize(); + return col; +} + METHSPEC double Color::getValue() const { double max; diff --git a/src/definition/VegetationPresenceDefinition.cpp b/src/definition/VegetationPresenceDefinition.cpp index 1dea116..ccb212e 100644 --- a/src/definition/VegetationPresenceDefinition.cpp +++ b/src/definition/VegetationPresenceDefinition.cpp @@ -42,11 +42,14 @@ bool VegetationPresenceDefinition::collectInstances(std::vectorget2DTotal(z * 10.0, x * 10.0)) * (noise_presence * 0.5 + 0.5); double angle = 3.0 * generator->get2DTotal(-x * 20.0, z * 20.0); // TODO balanced distribution - double xoffset = fabs(generator->get2DTotal(x * 12.0, -z * 12.0)); - double zoffset = fabs(generator->get2DTotal(-x * 27.0, -z * 27.0)); - double y = getScenery()->getTerrain()->getInterpolatedHeight(x + xoffset, z + zoffset, true, true); - result->push_back(VegetationInstance(model, Vector3(x + xoffset, y, z + zoffset), size, angle)); - added++; + double xo = x + fabs(generator->get2DTotal(x * 12.0, -z * 12.0)); + double zo = z + fabs(generator->get2DTotal(-x * 27.0, -z * 27.0)); + if (xo >= xmin and xo < xmax and zo >= zmin and zo < zmax) + { + double y = getScenery()->getTerrain()->getInterpolatedHeight(xo, zo, true, true); + result->push_back(VegetationInstance(model, Vector3(xo, y, zo), size, angle)); + added++; + } } } } diff --git a/src/interface/commandline/commandline.pro b/src/interface/commandline/commandline.pro index d34d556..3b8b1f9 100644 --- a/src/interface/commandline/commandline.pro +++ b/src/interface/commandline/commandline.pro @@ -32,3 +32,9 @@ else:win32:CONFIG(debug, debug|release): LIBS += -L$$OUT_PWD/../../render/softwa else:unix: LIBS += -L$$OUT_PWD/../../render/software/ -lpaysages_render_software INCLUDEPATH += $$PWD/../../render/software DEPENDPATH += $$PWD/../../render/software + +win32:CONFIG(release, debug|release): LIBS += -L$$OUT_PWD/../../render/opengl/release/ -lpaysages_render_opengl +else:win32:CONFIG(debug, debug|release): LIBS += -L$$OUT_PWD/../../render/opengl/debug/ -lpaysages_render_opengl +else:unix: LIBS += -L$$OUT_PWD/../../render/opengl/ -lpaysages_render_opengl +INCLUDEPATH += $$PWD/../../render/opengl +DEPENDPATH += $$PWD/../../render/opengl diff --git a/src/interface/commandline/tests.cpp b/src/interface/commandline/tests.cpp index 06b671a..9d624f3 100644 --- a/src/interface/commandline/tests.cpp +++ b/src/interface/commandline/tests.cpp @@ -23,12 +23,15 @@ #include "VegetationInstance.h" #include "VegetationRenderer.h" #include "RayCastingResult.h" +#include "OpenGLVegetationImpostor.h" +#include "Texture2D.h" #include +#include void startRender(SoftwareCanvasRenderer *renderer, const char *outputpath); -static void startTestRender(SoftwareCanvasRenderer *renderer, const std::string &name, int iteration = -1) { +static std::string getFileName(const std::string &name, int iteration = -1) { std::ostringstream stream; stream << "pic_test_" << name; @@ -40,7 +43,11 @@ static void startTestRender(SoftwareCanvasRenderer *renderer, const std::string } stream << ".png"; - startRender(renderer, stream.str().data()); + return stream.str(); +} + +static void startTestRender(SoftwareCanvasRenderer *renderer, const std::string &name, int iteration = -1) { + startRender(renderer, getFileName(name, iteration).data()); } static void testGroundShadowQuality() { @@ -288,6 +295,19 @@ static void testVegetationModels() { } } +static void testOpenGLVegetationImpostor() { + std::string filename = getFileName("opengl_vegetation_impostor"); + std::cout << "Rendering " << filename << "..." << std::endl; + + Scenery scenery; + scenery.autoPreset(1); + OpenGLVegetationImpostor impostor(200); + VegetationModelDefinition model(NULL); + bool interrupted = false; + impostor.prepareTexture(model, scenery, &interrupted); + impostor.getTexture()->saveToFile(filename); +} + void runTestSuite() { testGroundShadowQuality(); testRasterizationQuality(); @@ -297,4 +317,5 @@ void runTestSuite() { testCloudsNearGround(); testSunNearHorizon(); testVegetationModels(); + testOpenGLVegetationImpostor(); } diff --git a/src/render/opengl/OpenGLPart.cpp b/src/render/opengl/OpenGLPart.cpp index cfefc62..026ad19 100644 --- a/src/render/opengl/OpenGLPart.cpp +++ b/src/render/opengl/OpenGLPart.cpp @@ -40,3 +40,13 @@ void OpenGLPart::updateScenery(bool onlyCommon) { update(); } } + +Scenery *OpenGLPart::getScenery() const +{ + return renderer->getScenery(); +} + +OpenGLFunctions *OpenGLPart::getOpenGlFunctions() const +{ + return renderer->getOpenGlFunctions(); +} diff --git a/src/render/opengl/OpenGLPart.h b/src/render/opengl/OpenGLPart.h index fedef49..6a97893 100644 --- a/src/render/opengl/OpenGLPart.h +++ b/src/render/opengl/OpenGLPart.h @@ -30,6 +30,16 @@ class OPENGLSHARED_EXPORT OpenGLPart { void updateScenery(bool onlyCommon = false); + /** + * Get access to rendered scenery. + */ + Scenery *getScenery() const; + + /** + * Get access to OpenGL functions. + */ + OpenGLFunctions *getOpenGlFunctions() const; + protected: // Create a shader program OpenGLShaderProgram *createShader(QString name); diff --git a/src/render/opengl/OpenGLRenderer.cpp b/src/render/opengl/OpenGLRenderer.cpp index 8ba3b19..085afab 100644 --- a/src/render/opengl/OpenGLRenderer.cpp +++ b/src/render/opengl/OpenGLRenderer.cpp @@ -6,6 +6,7 @@ #include "OpenGLSkybox.h" #include "OpenGLWater.h" #include "OpenGLTerrain.h" +#include "OpenGLVegetation.h" #include "CloudsRenderer.h" #include "VegetationRenderer.h" #include "Color.h" @@ -40,9 +41,11 @@ OpenGLRenderer::OpenGLRenderer(Scenery *scenery) : SoftwareRenderer(scenery) { skybox = new OpenGLSkybox(this); water = new OpenGLWater(this); terrain = new OpenGLTerrain(this); + vegetation = new OpenGLVegetation(this); } OpenGLRenderer::~OpenGLRenderer() { + vegetation->interrupt(); terrain->interrupt(); water->interrupt(); skybox->interrupt(); @@ -54,6 +57,7 @@ OpenGLRenderer::~OpenGLRenderer() { delete skybox; delete water; delete terrain; + delete vegetation; delete functions; delete shared_state; @@ -69,9 +73,9 @@ void OpenGLRenderer::prepare() { } void OpenGLRenderer::initialize() { - ready = functions->initializeOpenGLFunctions(); + bool init = functions->initializeOpenGLFunctions(); - if (ready) { + if (init) { prepareOpenGLState(); prepare(); @@ -85,7 +89,12 @@ void OpenGLRenderer::initialize() { terrain->initialize(); terrain->updateScenery(); + vegetation->initialize(); + vegetation->updateScenery(); + cameraChangeEvent(render_camera); + + ready = true; } else { Logs::error() << "Failed to initialize OpenGL bindings" << std::endl; } @@ -100,7 +109,7 @@ void OpenGLRenderer::prepareOpenGLState() { functions->glEnable(GL_CULL_FACE); functions->glDepthFunc(GL_LESS); - functions->glDepthMask(1); + functions->glDepthMask(GL_TRUE); functions->glEnable(GL_DEPTH_TEST); functions->glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); @@ -145,6 +154,7 @@ void OpenGLRenderer::paint() { skybox->render(); terrain->render(); water->render(); + vegetation->render(); if (mouse_tracking) { updateMouseProjection(); @@ -164,6 +174,7 @@ void OpenGLRenderer::reset() { skybox->updateScenery(); water->updateScenery(); terrain->updateScenery(); + vegetation->updateScenery(); cameraChangeEvent(render_camera); } @@ -210,6 +221,9 @@ void OpenGLRenderer::cameraChangeEvent(CameraDefinition *camera) { // Set in shaders shared_state->set("cameraLocation", location); shared_state->set("viewMatrix", *view_matrix); + + // Broadcast to parts + vegetation->cameraChanged(camera); } double OpenGLRenderer::getPrecision(const Vector3 &) { diff --git a/src/render/opengl/OpenGLRenderer.h b/src/render/opengl/OpenGLRenderer.h index b8c4074..e367cf1 100644 --- a/src/render/opengl/OpenGLRenderer.h +++ b/src/render/opengl/OpenGLRenderer.h @@ -27,6 +27,9 @@ class OPENGLSHARED_EXPORT OpenGLRenderer : public SoftwareRenderer { inline OpenGLTerrain *getTerrain() const { return terrain; } + inline OpenGLVegetation *getVegetation() const { + return vegetation; + } inline bool isDisplayed() const { return displayed; } @@ -106,6 +109,7 @@ class OPENGLSHARED_EXPORT OpenGLRenderer : public SoftwareRenderer { OpenGLSkybox *skybox; OpenGLWater *water; OpenGLTerrain *terrain; + OpenGLVegetation *vegetation; }; } } diff --git a/src/render/opengl/OpenGLShaderProgram.cpp b/src/render/opengl/OpenGLShaderProgram.cpp index 817b97f..8e0387b 100644 --- a/src/render/opengl/OpenGLShaderProgram.cpp +++ b/src/render/opengl/OpenGLShaderProgram.cpp @@ -15,11 +15,13 @@ OpenGLShaderProgram::OpenGLShaderProgram(const std::string &name, OpenGLRenderer : renderer(renderer), name(name) { program = new QOpenGLShaderProgram(); functions = renderer->getOpenGlFunctions(); + state = new OpenGLSharedState(); compiled = false; } OpenGLShaderProgram::~OpenGLShaderProgram() { delete program; + delete state; } void OpenGLShaderProgram::addVertexSource(QString path) { @@ -63,6 +65,7 @@ void OpenGLShaderProgram::bind() { int texture_unit = 0; renderer->getSharedState()->apply(this, texture_unit); + state->apply(this, texture_unit); } void OpenGLShaderProgram::release() { diff --git a/src/render/opengl/OpenGLShaderProgram.h b/src/render/opengl/OpenGLShaderProgram.h index c833e5d..74482fc 100644 --- a/src/render/opengl/OpenGLShaderProgram.h +++ b/src/render/opengl/OpenGLShaderProgram.h @@ -30,6 +30,9 @@ class OPENGLSHARED_EXPORT OpenGLShaderProgram { inline OpenGLRenderer *getRenderer() const { return renderer; } + inline OpenGLSharedState *getState() const { + return state; + } protected: friend class OpenGLVariable; @@ -45,6 +48,8 @@ class OPENGLSHARED_EXPORT OpenGLShaderProgram { QOpenGLShaderProgram *program; OpenGLFunctions *functions; + OpenGLSharedState *state; + std::string source_vertex; std::string source_fragment; }; diff --git a/src/render/opengl/OpenGLSharedState.cpp b/src/render/opengl/OpenGLSharedState.cpp index f5a9d8c..5af618e 100644 --- a/src/render/opengl/OpenGLSharedState.cpp +++ b/src/render/opengl/OpenGLSharedState.cpp @@ -3,7 +3,7 @@ OpenGLSharedState::OpenGLSharedState() { } -paysages::opengl::OpenGLSharedState::~OpenGLSharedState() +OpenGLSharedState::~OpenGLSharedState() { for (const auto &pair : variables) { delete pair.second; diff --git a/src/render/opengl/OpenGLSharedState.h b/src/render/opengl/OpenGLSharedState.h index 095448c..742ea01 100644 --- a/src/render/opengl/OpenGLSharedState.h +++ b/src/render/opengl/OpenGLSharedState.h @@ -28,6 +28,12 @@ class OPENGLSHARED_EXPORT OpenGLSharedState { OpenGLVariable *get(const std::string &name); // Shortcuts + inline void setInt(const std::string &name, int value) { + get(name)->set(value); + } + inline void set(const std::string &name, float value) { + get(name)->set(value); + } inline void set(const std::string &name, const Texture2D *texture, bool repeat = false, bool color = true) { get(name)->set(texture, repeat, color); } @@ -37,9 +43,6 @@ class OPENGLSHARED_EXPORT OpenGLSharedState { inline void set(const std::string &name, const Texture4D *texture, bool repeat = false, bool color = true) { get(name)->set(texture, repeat, color); } - inline void set(const std::string &name, float value) { - get(name)->set(value); - } inline void set(const std::string &name, const Vector3 &vector) { get(name)->set(vector); } diff --git a/src/render/opengl/OpenGLVariable.cpp b/src/render/opengl/OpenGLVariable.cpp index e1df6d6..1176cca 100644 --- a/src/render/opengl/OpenGLVariable.cpp +++ b/src/render/opengl/OpenGLVariable.cpp @@ -28,6 +28,9 @@ void OpenGLVariable::apply(OpenGLShaderProgram *program, int &texture_unit) { } switch (type) { + case TYPE_INTEGER: + pr->setUniformValue(name.c_str(), value_int); + break; case TYPE_FLOAT: pr->setUniformValue(name.c_str(), value_float); break; @@ -88,6 +91,14 @@ void OpenGLVariable::set(const Texture4D *texture, bool repeat, bool color) { texture_color = color; } +void OpenGLVariable::set(int value) +{ + assert(type == TYPE_NONE or type == TYPE_INTEGER); + + type = TYPE_INTEGER; + value_int = value; +} + void OpenGLVariable::set(float value) { assert(type == TYPE_NONE or type == TYPE_FLOAT); diff --git a/src/render/opengl/OpenGLVariable.h b/src/render/opengl/OpenGLVariable.h index 5da2869..c805b38 100644 --- a/src/render/opengl/OpenGLVariable.h +++ b/src/render/opengl/OpenGLVariable.h @@ -20,6 +20,7 @@ class OpenGLVariable { TYPE_TEXTURE_2D, TYPE_TEXTURE_3D, TYPE_TEXTURE_4D, + TYPE_INTEGER, TYPE_FLOAT, TYPE_VECTOR3, TYPE_MATRIX4, @@ -34,6 +35,7 @@ class OpenGLVariable { void set(const Texture2D *texture, bool repeat = false, bool color = true); void set(const Texture3D *texture, bool repeat = false, bool color = true); void set(const Texture4D *texture, bool repeat = false, bool color = true); + void set(int value); void set(float value); void set(const Vector3 &vector); void set(const QVector3D &vector); @@ -48,6 +50,7 @@ class OpenGLVariable { std::string name; OpenGLVariableType type; + int value_int; float value_float; QColor value_color; QVector3D value_vector3; diff --git a/src/render/opengl/OpenGLVegetation.cpp b/src/render/opengl/OpenGLVegetation.cpp new file mode 100644 index 0000000..c64ce14 --- /dev/null +++ b/src/render/opengl/OpenGLVegetation.cpp @@ -0,0 +1,155 @@ +#include "OpenGLVegetation.h" + +#include "OpenGLRenderer.h" +#include "OpenGLShaderProgram.h" +#include "OpenGLVegetationLayer.h" +#include "Thread.h" +#include "Mutex.h" +#include "Scenery.h" +#include "VegetationDefinition.h" +#include "VegetationLayerDefinition.h" + +class paysages::opengl::VegetationUpdater : public Thread { + public: + VegetationUpdater(OpenGLVegetation *vegetation) : vegetation(vegetation) { + interrupted = false; + } + + void interrupt(bool wait = true) { + interrupted = true; + if (wait) { + join(); + } + } + + protected: + virtual void run() override { + while (not interrupted) { + std::vector layers; + vegetation->acquireLayers(layers); + for (auto layer: layers) { + layer->threadedUpdate(); + } + vegetation->releaseLayers(layers); + timeSleepMs(100); + } + } + + private: + bool interrupted; + OpenGLVegetation *vegetation; +}; + +OpenGLVegetation::OpenGLVegetation(OpenGLRenderer *renderer) : OpenGLPart(renderer) { + layers_lock = new Mutex(); + updater = new VegetationUpdater(this); + enabled = true; + + // Watch scenery changes + renderer->getScenery()->getVegetation()->addWatcher(this, true); +} + +OpenGLVegetation::~OpenGLVegetation() { + for (auto layer: layers) { + delete layer; + } + layers.clear(); + + delete layers_lock; + + updater->interrupt(); + delete updater; +} + +void OpenGLVegetation::initialize() { + // Start the threaded updater + updater->start(); + + // Prepare shader programs + program = createShader("vegetation"); + program->addVertexSource("vegetation"); + program->addFragmentSource("atmosphere"); + program->addFragmentSource("tonemapping"); + program->addFragmentSource("ui"); + program->addFragmentSource("vegetation"); +} + +void OpenGLVegetation::update() { +} + +void OpenGLVegetation::render() { + if (enabled) { + std::vector layers; + acquireLayers(layers); + for (auto layer: layers) { + layer->render(); + } + releaseLayers(layers); + } +} + +void OpenGLVegetation::nodeChanged(const DefinitionNode *node, const DefinitionDiff *) { + if (node->getPath() == "/vegetation") { + updateLayers(); + } +} + +Scenery *OpenGLVegetation::getScenery() const +{ + return renderer->getScenery(); +} + +void OpenGLVegetation::cameraChanged(const CameraDefinition *camera) +{ + std::vector layers; + acquireLayers(layers); + for (auto layer: layers) { + layer->setCamera(camera); + } + releaseLayers(layers); +} + +void OpenGLVegetation::acquireLayers(std::vector &layers) { + layers_lock->acquire(); + + for (auto layer : this->layers) { + // TODO Reference count + layers.push_back(layer); + } + + layers_lock->release(); +} + +void OpenGLVegetation::releaseLayers(const std::vector &layers) { + // TODO Reference count +} + +OpenGLVegetationLayer *OpenGLVegetation::findLayer(VegetationLayerDefinition *layer) { + for (auto &l : layers) { + if (l->getDefinition() == layer) { + return l; + } + } + return NULL; +} + +void OpenGLVegetation::setEnabled(bool enabled) { + this->enabled = enabled; +} + +void OpenGLVegetation::updateLayers() { + layers_lock->acquire(); + + // Add missing layers + int n = renderer->getScenery()->getVegetation()->getLayerCount(); + for (int i = 0; i < n; i++) { + VegetationLayerDefinition *layer = renderer->getScenery()->getVegetation()->getVegetationLayer(i); + if (!findLayer(layer)) { + layers.push_back(new OpenGLVegetationLayer(this, layer)); + } + } + + // TODO Mark extraneous layers for deletion + + layers_lock->release(); +} diff --git a/src/render/opengl/OpenGLVegetation.h b/src/render/opengl/OpenGLVegetation.h new file mode 100644 index 0000000..c91424e --- /dev/null +++ b/src/render/opengl/OpenGLVegetation.h @@ -0,0 +1,89 @@ +#ifndef OPENGLVEGETATION_H +#define OPENGLVEGETATION_H + +#include "opengl_global.h" + +#include "OpenGLPart.h" +#include "DefinitionWatcher.h" + +#include + +namespace paysages { +namespace opengl { + +class VegetationUpdater; + +class OPENGLSHARED_EXPORT OpenGLVegetation : public OpenGLPart, public DefinitionWatcher { + public: + OpenGLVegetation(OpenGLRenderer *renderer); + virtual ~OpenGLVegetation(); + + inline int getLayerCount() { + return layers.size(); + } + inline OpenGLVegetationLayer *getLayer(int i) { + return layers[i]; + } + inline OpenGLShaderProgram *getProgram() { + return program; + } + + virtual void initialize() override; + virtual void update() override; + virtual void render() override; + + virtual void nodeChanged(const DefinitionNode *node, const DefinitionDiff *diff) override; + + /** + * Get the currently rendered scenery. + */ + Scenery *getScenery() const; + + /** + * Call this when the dynamic camera (not the scenery one) changed. + */ + void cameraChanged(const CameraDefinition *camera); + + /** + * Acquire the current layers for processing. + * + * Don't forget to call releaseLayers once done with them. + * + * This will not hold a lock on them, but increment a reference counter to not delete them while in use. + */ + void acquireLayers(std::vector &layers); + + /** + * Release the layers acquired by acquireLayers. + */ + void releaseLayers(const std::vector &layers); + + /** + * Find a rendering layer, by the matching definition layer. + */ + OpenGLVegetationLayer *findLayer(VegetationLayerDefinition *layer); + + /** + * Enable or disable the whole vegetation rendering. + */ + void setEnabled(bool enabled); + + /** + * Update the *layers* member from the scenery. + * + * This will create missing layers, and mark extraneous ones for deletion. + * This will not update existing layers (they should update themselves by watching their definition). + */ + void updateLayers(); + + private: + OpenGLShaderProgram *program; + bool enabled; + std::vector layers; + Mutex *layers_lock; + VegetationUpdater *updater; +}; +} +} + +#endif // OPENGLVEGETATION_H diff --git a/src/render/opengl/OpenGLVegetationImpostor.cpp b/src/render/opengl/OpenGLVegetationImpostor.cpp new file mode 100644 index 0000000..5cd2331 --- /dev/null +++ b/src/render/opengl/OpenGLVegetationImpostor.cpp @@ -0,0 +1,106 @@ +#include "OpenGLVegetationImpostor.h" + +#include "OpenGLShaderProgram.h" +#include "OpenGLSharedState.h" +#include "OpenGLVegetationInstance.h" +#include "Texture2D.h" +#include "SoftwareRenderer.h" +#include "Scenery.h" +#include "AtmosphereDefinition.h" +#include "GodRaysSampler.h" +#include "VegetationRenderer.h" +#include "VegetationInstance.h" +#include "RayCastingResult.h" +#include "SpaceSegment.h" +#include "Matrix4.h" +#include "LightingManager.h" +#include "CameraDefinition.h" + +OpenGLVegetationImpostor::OpenGLVegetationImpostor(int partsize) { + vertices = new float[4 * 3]; + texture_size = partsize * 4; + texture = new Texture2D(texture_size, texture_size); + texture_changed = true; + + setVertex(0, 0.0f, 0.0f, 0.0f); + setVertex(1, 0.0f, 0.0f, 1.0f); + setVertex(2, 0.0f, 1.0f, 0.0f); + setVertex(3, 0.0f, 1.0f, 1.0f); +} + +OpenGLVegetationImpostor::~OpenGLVegetationImpostor() { + delete[] vertices; + delete texture; +} + +void OpenGLVegetationImpostor::render(OpenGLShaderProgram *program, const OpenGLVegetationInstance *instance, + int index) { + if (index == 0 or texture_changed) { + texture_changed = false; + program->getState()->set("impostorTexture", texture); + } + program->getState()->setInt("index", 15); // TODO + program->getState()->set("offset", instance->getBase()); + program->getState()->set("size", instance->getSize()); + program->drawTriangleStrip(vertices, 4); +} + +void OpenGLVegetationImpostor::prepareTexture(const VegetationModelDefinition &model, const Scenery &environment, + bool *interrupt) { + Scenery scenery; + environment.getAtmosphere()->copy(scenery.getAtmosphere()); + SoftwareRenderer renderer(&scenery); + // FIXME Self light filtering + renderer.getLightingManager()->clearFilters(); + renderer.getGodRaysSampler()->setEnabled(false); + VegetationRenderer *vegetation = renderer.getVegetationRenderer(); + VegetationInstance instance(model, VECTOR_ZERO); + + int parts = 4; + int partsize = texture_size / parts; + Matrix4 rotation; + for (int py = 0; py < parts; py++) { + for (int px = 0; px < parts; px++) { + int index = py * parts + px; + if (index == 0) { + rotation = Matrix4::newRotateX(-M_PI_2); + } else if (index < 6) { + rotation = Matrix4::newRotateY(M_2PI * (double)(index - 1) * 0.2).mult(Matrix4::newRotateX(-M_PI_4)); + } else { + rotation = Matrix4::newRotateY(M_2PI * (double)(index - 6) * 0.1); + } + + Vector3 cam(0.0, 0.0, 5.0); + scenery.getCamera()->setLocation(rotation.multPoint(cam)); + scenery.getCamera()->setTarget(VECTOR_ZERO); + renderer.prepare(); + + int startx = px * partsize; + int starty = py * partsize; + + for (int x = 0; x < partsize; x++) { + double dx = (double)x / (double)partsize; + for (int y = 0; y < partsize; y++) { + double dy = (double)y / (double)partsize; + + Vector3 near(dx - 0.5, dy - 0.5, 5.0); + Vector3 far(dx - 0.5, dy - 0.5, -5.0); + SpaceSegment segment(rotation.multPoint(near.scale(1.3)).add(VECTOR_UP.scale(0.5)), + rotation.multPoint(far.scale(1.3)).add(VECTOR_UP.scale(0.5))); + + RayCastingResult result = vegetation->renderInstance(segment, instance, false, true); + texture->setPixel(startx + x, starty + y, + result.hit ? result.hit_color.normalized() : COLOR_TRANSPARENT); + } + } + } + } + + texture_changed = true; +} + +void OpenGLVegetationImpostor::setVertex(int i, float x, float y, float z) { + vertices[i * 3] = x; + vertices[i * 3 + 1] = y; + vertices[i * 3 + 2] = z; +} diff --git a/src/render/opengl/OpenGLVegetationImpostor.h b/src/render/opengl/OpenGLVegetationImpostor.h new file mode 100644 index 0000000..f37136d --- /dev/null +++ b/src/render/opengl/OpenGLVegetationImpostor.h @@ -0,0 +1,43 @@ +#ifndef OPENGLVEGETATIONIMPOSTOR_H +#define OPENGLVEGETATIONIMPOSTOR_H + +#include "opengl_global.h" + +namespace paysages { +namespace opengl { + +/** + * A tool to render an "impostor" of a vegetation layer. + */ +class OPENGLSHARED_EXPORT OpenGLVegetationImpostor { + public: + OpenGLVegetationImpostor(int partsize = 64); + ~OpenGLVegetationImpostor(); + + inline const Texture2D *getTexture() const { + return texture; + } + + /** + * Render a single instance using this impostor. + */ + void render(OpenGLShaderProgram *program, const OpenGLVegetationInstance *instance, int index); + + /** + * Prepare the texture grid for a given model. + */ + void prepareTexture(const VegetationModelDefinition &model, const Scenery &environment, bool *interrupt); + + private: + void setVertex(int i, float x, float y, float z); + + private: + float *vertices; + int texture_size; + bool texture_changed; + Texture2D *texture; +}; +} +} + +#endif // OPENGLVEGETATIONIMPOSTOR_H diff --git a/src/render/opengl/OpenGLVegetationInstance.cpp b/src/render/opengl/OpenGLVegetationInstance.cpp new file mode 100644 index 0000000..4c72296 --- /dev/null +++ b/src/render/opengl/OpenGLVegetationInstance.cpp @@ -0,0 +1,11 @@ +#include "OpenGLVegetationInstance.h" + +#include "VegetationInstance.h" + +OpenGLVegetationInstance::OpenGLVegetationInstance(const VegetationInstance &wrapped) : wrapped(wrapped) { +} + +void OpenGLVegetationInstance::setDistance(double distance) +{ + this->distance = distance; +} diff --git a/src/render/opengl/OpenGLVegetationInstance.h b/src/render/opengl/OpenGLVegetationInstance.h new file mode 100644 index 0000000..a4262ac --- /dev/null +++ b/src/render/opengl/OpenGLVegetationInstance.h @@ -0,0 +1,43 @@ +#ifndef OPENGLVEGETATIONINSTANCE_H +#define OPENGLVEGETATIONINSTANCE_H + +#include "opengl_global.h" + +#include "VegetationInstance.h" + +namespace paysages { +namespace opengl { + +/** + * A single instance of vegetation. + */ +class OPENGLSHARED_EXPORT OpenGLVegetationInstance { + public: + OpenGLVegetationInstance(const VegetationInstance &wrapped); + + inline const VegetationModelDefinition &getModel() const { + return wrapped.getModel(); + } + inline const Vector3 &getBase() const { + return wrapped.getBase(); + } + inline double getSize() const { + return wrapped.getSize(); + } + inline double getDistance() const { + return distance; + } + + /** + * Set the distance to camera, mainly for sorting. + */ + void setDistance(double distance); + +private: + VegetationInstance wrapped; + double distance; +}; +} +} + +#endif // OPENGLVEGETATIONINSTANCE_H diff --git a/src/render/opengl/OpenGLVegetationLayer.cpp b/src/render/opengl/OpenGLVegetationLayer.cpp new file mode 100644 index 0000000..a0d9c43 --- /dev/null +++ b/src/render/opengl/OpenGLVegetationLayer.cpp @@ -0,0 +1,141 @@ +#include "OpenGLVegetationLayer.h" + +#include OPENGL_FUNCTIONS_INCLUDE +#include +#include "Vector3.h" +#include "CameraDefinition.h" +#include "Mutex.h" +#include "OpenGLVegetation.h" +#include "OpenGLVegetationInstance.h" +#include "OpenGLVegetationImpostor.h" +#include "VegetationLayerDefinition.h" +#include "VegetationPresenceDefinition.h" + +OpenGLVegetationLayer::OpenGLVegetationLayer(OpenGLVegetation *parent, VegetationLayerDefinition *definition, + bool own_instances) + : parent(parent), definition(definition), own_instances(own_instances) { + lock_instances = new Mutex; + camera_location = new Vector3(0.0, 0.0, 0.0); + impostor = new OpenGLVegetationImpostor(); + range = 10.0; + + reset(); +} + +OpenGLVegetationLayer::~OpenGLVegetationLayer() { + delete camera_location; + delete lock_instances; + delete impostor; +} + +void OpenGLVegetationLayer::produceInstancesInArea(double xmin, double xmax, double zmin, double zmax, + std::vector *instances) const { + std::vector result; + definition->getPresence()->collectInstances(&result, *definition->getModel(), xmin, zmin, xmax, zmax, false); + for (auto raw_instance : result) { + instances->push_back(new OpenGLVegetationInstance(raw_instance)); + } +} + +static bool isNull(void *ptr) { + return ptr == NULL; +} + +static bool compareInstances(OpenGLVegetationInstance *instance1, OpenGLVegetationInstance *instance2) { + return instance1->getDistance() > instance2->getDistance(); +} + +void OpenGLVegetationLayer::removeInstancesOutsideArea(double xmin, double xmax, double zmin, double zmax, + std::vector *instances) const { + for (auto &instance : *instances) { + Vector3 base = instance->getBase(); + if (base.x < xmin or base.x >= xmax or base.z < zmin or base.z >= zmax) { + if (own_instances) { + delete instance; + } + instance = NULL; + } + } + instances->erase(std::remove_if(instances->begin(), instances->end(), isNull), instances->end()); +} + +void OpenGLVegetationLayer::threadedUpdate() { + if (camera_changed) { + camera_changed = false; + + // Compute new area around camera + double newxmin, newxmax, newzmin, newzmax; + newxmin = camera_location->x - range; + newxmax = camera_location->x + range; + newzmin = camera_location->z - range; + newzmax = camera_location->z + range; + + // Prepare instances where area grew + std::vector new_instances; + if (newxmin < xmin) { + produceInstancesInArea(newxmin, xmin, newzmin, newzmax, &new_instances); + } + if (newxmax > xmax) { + produceInstancesInArea(xmax, newxmax, newzmin, newzmax, &new_instances); + } + if (newzmin < zmin) { + produceInstancesInArea(xmin, xmax, newzmin, zmin, &new_instances); + } + if (newzmax > zmax) { + produceInstancesInArea(xmin, xmax, zmax, newzmax, &new_instances); + } + + // Apply the changes + lock_instances->acquire(); + xmin = newxmin; + xmax = newxmax; + zmin = newzmin; + zmax = newzmax; + removeInstancesOutsideArea(xmin, xmax, zmin, zmax, &instances); + instances.insert(instances.end(), new_instances.begin(), new_instances.end()); + for (auto instance: instances) { + instance->setDistance(instance->getBase().sub(*camera_location).getNorm()); + } + std::sort(instances.begin(), instances.end(), compareInstances); + lock_instances->release(); + + // Update impostor texture + bool interrupted = false; + impostor->prepareTexture(*definition->getModel(), *parent->getScenery(), &interrupted); + } +} + +void OpenGLVegetationLayer::render() { + lock_instances->acquire(); + + // TODO Instanced rendering + int index = 0; + for (auto instance : instances) { + impostor->render(parent->getProgram(), instance, index++); + } + + lock_instances->release(); +} + +void OpenGLVegetationLayer::reset() { + lock_instances->acquire(); + camera_changed = true; + xmin = 0.0; + xmax = 0.0; + zmin = 0.0; + zmax = 0.0; + if (own_instances) { + for (auto instance : instances) { + delete instance; + } + } + instances.clear(); + lock_instances->release(); +} + +void OpenGLVegetationLayer::setCamera(const CameraDefinition *camera) { + if (camera_location->sub(camera->getLocation()).getNorm() > 1.0) { + *camera_location = camera->getLocation(); + camera_changed = true; + } +} diff --git a/src/render/opengl/OpenGLVegetationLayer.h b/src/render/opengl/OpenGLVegetationLayer.h new file mode 100644 index 0000000..a3d0139 --- /dev/null +++ b/src/render/opengl/OpenGLVegetationLayer.h @@ -0,0 +1,82 @@ +#ifndef OPENGLVEGETATIONLAYER_H +#define OPENGLVEGETATIONLAYER_H + +#include "opengl_global.h" + +#include + +namespace paysages { +namespace opengl { + +class OPENGLSHARED_EXPORT OpenGLVegetationLayer { + public: + OpenGLVegetationLayer(OpenGLVegetation *parent, VegetationLayerDefinition *definition, bool own_instances = true); + virtual ~OpenGLVegetationLayer(); + + inline auto getDefinition() const { + return definition; + } + inline int getInstanceCount() const { + return (int)instances.size(); + } + + /** + * Collect instances in a given area, and add them to the array *instances*. + * + * The array is not checked for already present instances. + */ + virtual void produceInstancesInArea(double xmin, double xmax, double zmin, double zmax, + std::vector *instances) const; + + /** + * Remove instances outside of a given area. + */ + virtual void removeInstancesOutsideArea(double xmin, double xmax, double zmin, double zmax, + std::vector *instances) const; + + /** + * Perform maintenance tasks that can be perform in a thread. + * + * This will be called from a thread separate from the main GUI thread, + * so it should not call OpenGL functions. + */ + void threadedUpdate(); + + /** + * Render the vegetation layer. + * + * This is called from the GUI thread. + */ + void render(); + + /** + * Reset to an initial state. + * + * It is only useful (and safe) from unit testing. + */ + void reset(); + + /** + * Set the current camera in use. + */ + void setCamera(const CameraDefinition *camera); + + private: + OpenGLVegetation *parent; + VegetationLayerDefinition *definition; + double xmin; + double xmax; + double zmin; + double zmax; + double range; + bool own_instances; + std::vector instances; + Mutex *lock_instances; + OpenGLVegetationImpostor *impostor; + Vector3 *camera_location; + bool camera_changed; +}; +} +} + +#endif // OPENGLVEGETATIONLAYER_H diff --git a/src/render/opengl/opengl.pro b/src/render/opengl/opengl.pro index c1034e4..26740cf 100644 --- a/src/render/opengl/opengl.pro +++ b/src/render/opengl/opengl.pro @@ -55,3 +55,7 @@ RESOURCES += \ OTHER_FILES += \ shaders/*.frag \ shaders/*.vert + +DISTFILES += \ + shaders/vegetation.frag \ + shaders/vegetation.vert diff --git a/src/render/opengl/opengl_global.h b/src/render/opengl/opengl_global.h index 1d1c56c..b0a7223 100644 --- a/src/render/opengl/opengl_global.h +++ b/src/render/opengl/opengl_global.h @@ -19,6 +19,10 @@ class OpenGLVariable; class OpenGLSkybox; class OpenGLWater; class OpenGLTerrain; +class OpenGLVegetation; +class OpenGLVegetationLayer; +class OpenGLVegetationInstance; +class OpenGLVegetationImpostor; class ExplorerChunkTerrain; template class VertexArray; } diff --git a/src/render/opengl/shaders/resources.qrc b/src/render/opengl/shaders/resources.qrc index 68f3f23..7c4e792 100644 --- a/src/render/opengl/shaders/resources.qrc +++ b/src/render/opengl/shaders/resources.qrc @@ -11,5 +11,7 @@ fadeout.frag noise.frag ui.frag + vegetation.vert + vegetation.frag diff --git a/src/render/opengl/shaders/vegetation.frag b/src/render/opengl/shaders/vegetation.frag new file mode 100644 index 0000000..e0ee288 --- /dev/null +++ b/src/render/opengl/shaders/vegetation.frag @@ -0,0 +1,15 @@ +varying vec2 texcoord; +uniform sampler2D impostorTexture; + +void main(void) +{ + gl_FragColor = texture2D(impostorTexture, texcoord); + float alpha = gl_FragColor.a; + + gl_FragColor = applyAerialPerspective(gl_FragColor); + + gl_FragColor = applyToneMapping(gl_FragColor); + + gl_FragColor = applyMouseTracking(unprojected, gl_FragColor); + gl_FragColor.a = alpha; +} diff --git a/src/render/opengl/shaders/vegetation.vert b/src/render/opengl/shaders/vegetation.vert new file mode 100644 index 0000000..a7915d3 --- /dev/null +++ b/src/render/opengl/shaders/vegetation.vert @@ -0,0 +1,16 @@ +attribute highp vec4 vertex; +uniform highp mat4 viewMatrix; +uniform highp vec3 offset; +uniform float size; +uniform int index; +varying vec3 unprojected; +varying vec2 texcoord; +uniform float waterOffset; + +void main(void) +{ + vec3 final = offset + size * (vertex.xyz - vec3(0.0, 0.0, 0.5)); // + vec3(0, waterOffset, 0) + unprojected = final.xyz; + texcoord = vec2(0.25 * (vertex.z + float(mod(index, 4))), 0.25 * (vertex.y + float(index / 4))); + gl_Position = viewMatrix * vec4(final.xyz, 1.0); +} diff --git a/src/render/software/SoftwareRenderer.cpp b/src/render/software/SoftwareRenderer.cpp index 22a04e6..86b3048 100644 --- a/src/render/software/SoftwareRenderer.cpp +++ b/src/render/software/SoftwareRenderer.cpp @@ -64,6 +64,7 @@ SoftwareRenderer::~SoftwareRenderer() { delete clouds_renderer; delete terrain_renderer; delete textures_renderer; + delete vegetation_renderer; delete water_renderer; } diff --git a/src/render/software/VegetationModelRenderer.cpp b/src/render/software/VegetationModelRenderer.cpp index 987a8e1..841c948 100644 --- a/src/render/software/VegetationModelRenderer.cpp +++ b/src/render/software/VegetationModelRenderer.cpp @@ -10,6 +10,13 @@ #include "VegetationModelDefinition.h" #include "VegetationResult.h" +#ifndef NDEBUG +//#define DEBUG_VEGETATION_CONTAINERS 1 +#endif +#ifdef DEBUG_VEGETATION_CONTAINERS +SurfaceMaterial DEBUG_MATERIAL1(Color(1.0, 0.0, 0.0)); +#endif + VegetationModelRenderer::VegetationModelRenderer(SoftwareRenderer *parent, const VegetationModelDefinition *model) : parent(parent), model(model) { } @@ -98,6 +105,16 @@ VegetationResult VegetationModelRenderer::getResult(const SpaceSegment &segment, } } } +#ifdef DEBUG_VEGETATION_CONTAINERS + if (!hit) { + Vector3 near, far; + intersections = foliage.findRayIntersection(ray, &near, &far); + location = near; + normal = VECTOR_ZERO; + material = &DEBUG_MATERIAL1; + hit = true; + } +#endif } } diff --git a/src/render/software/VegetationRenderer.cpp b/src/render/software/VegetationRenderer.cpp index e15d696..b859c70 100644 --- a/src/render/software/VegetationRenderer.cpp +++ b/src/render/software/VegetationRenderer.cpp @@ -44,6 +44,9 @@ VegetationRenderer::VegetationRenderer(SoftwareRenderer *parent) : parent(parent enabled = true; } +VegetationRenderer::~VegetationRenderer() { +} + void VegetationRenderer::setEnabled(bool enabled) { this->enabled = enabled; } diff --git a/src/render/software/VegetationRenderer.h b/src/render/software/VegetationRenderer.h index eaaf2f7..41efad2 100644 --- a/src/render/software/VegetationRenderer.h +++ b/src/render/software/VegetationRenderer.h @@ -11,6 +11,7 @@ namespace software { class SOFTWARESHARED_EXPORT VegetationRenderer : public LightFilter { public: VegetationRenderer(SoftwareRenderer *parent); + virtual ~VegetationRenderer(); /** * Totally enable or disable the vegetation layers rendering. diff --git a/src/tests/OpenGLVegetationLayer_Test.cpp b/src/tests/OpenGLVegetationLayer_Test.cpp new file mode 100644 index 0000000..52736c9 --- /dev/null +++ b/src/tests/OpenGLVegetationLayer_Test.cpp @@ -0,0 +1,67 @@ +#include "BaseTestCase.h" +#include "OpenGLVegetationLayer.h" + +#include "VegetationLayerDefinition.h" +#include "VegetationModelDefinition.h" +#include "VegetationInstance.h" +#include "OpenGLVegetationInstance.h" +#include "CameraDefinition.h" + +class FakeLayerRenderer : public OpenGLVegetationLayer { + public: + FakeLayerRenderer(VegetationLayerDefinition *definition) : OpenGLVegetationLayer(NULL, definition, false) { + } + virtual ~FakeLayerRenderer() { + for (auto instance : static_instances) { + delete instance; + } + } + virtual void produceInstancesInArea(double xmin, double xmax, double zmin, double zmax, + std::vector *instances) const override { + for (auto instance : static_instances) { + Vector3 location = instance->getBase(); + if (location.x >= xmin and location.z >= zmin and location.x < xmax and location.z < zmax) { + instances->push_back(instance); + } + } + } + std::vector static_instances; +}; + +TEST(OpenGLVegetationLayer, threadedUpdate) { + CameraDefinition camera; + VegetationLayerDefinition definition(NULL, "test"); + FakeLayerRenderer rendering(&definition); + VegetationModelDefinition model(NULL); + + EXPECT_EQ(0, rendering.getInstanceCount()); + + rendering.threadedUpdate(); + EXPECT_EQ(0, rendering.getInstanceCount()); + + rendering.static_instances.push_back( + new OpenGLVegetationInstance(VegetationInstance(model, Vector3(0.0, 0.0, 0.0)))); + rendering.reset(); + rendering.threadedUpdate(); + EXPECT_EQ(1, rendering.getInstanceCount()); + + camera.setLocation(Vector3(-5.0, 0.0, 0.0)); + rendering.setCamera(&camera); + rendering.threadedUpdate(); + EXPECT_EQ(1, rendering.getInstanceCount()); + + camera.setLocation(Vector3(-11.0, 0.0, 0.0)); + rendering.setCamera(&camera); + rendering.threadedUpdate(); + EXPECT_EQ(0, rendering.getInstanceCount()); + + camera.setLocation(Vector3(0.0, 0.0, 5.0)); + rendering.setCamera(&camera); + rendering.threadedUpdate(); + EXPECT_EQ(1, rendering.getInstanceCount()); + + camera.setLocation(Vector3(0.0, 0.0, 15.0)); + rendering.setCamera(&camera); + rendering.threadedUpdate(); + EXPECT_EQ(0, rendering.getInstanceCount()); +} diff --git a/src/tests/OpenGLVegetation_Test.cpp b/src/tests/OpenGLVegetation_Test.cpp new file mode 100644 index 0000000..a662b13 --- /dev/null +++ b/src/tests/OpenGLVegetation_Test.cpp @@ -0,0 +1,19 @@ +#include "BaseTestCase.h" +#include "OpenGLVegetation.h" + +#include "Scenery.h" +#include "VegetationDefinition.h" +#include "VegetationLayerDefinition.h" +#include "OpenGLRenderer.h" + +TEST(OpenGLVegetation, updateLayers) { + Scenery scenery; + OpenGLRenderer renderer(&scenery); + OpenGLVegetation glvegetation(&renderer); + + EXPECT_EQ(0, glvegetation.getLayerCount()); + + scenery.getVegetation()->addLayer("test"); + + EXPECT_EQ(1, glvegetation.getLayerCount()); +} diff --git a/src/tests/OverlayRasterizer_Test.cpp b/src/tests/OverlayRasterizer_Test.cpp index a598cb4..931d733 100644 --- a/src/tests/OverlayRasterizer_Test.cpp +++ b/src/tests/OverlayRasterizer_Test.cpp @@ -40,8 +40,9 @@ TEST(OverlayRasterizer, pixelProcessing) { Scenery scenery; SoftwareCanvasRenderer renderer(&scenery); + MockOverlayRasterizer rasterizer(&renderer); renderer.setSize(4, 3); - renderer.setSoloRasterizer(new MockOverlayRasterizer(&renderer)); + renderer.setSoloRasterizer(&rasterizer); renderer.render(); ASSERT_EQ(12, (int)calls.size());