diff --git a/Makefile b/Makefile index 56d7343..f3cb8b7 100644 --- a/Makefile +++ b/Makefile @@ -43,17 +43,17 @@ else endif run_cli:build - LD_LIBRARY_PATH=$(LIBRARY_PATH) ${RUNNER} ${BUILDPATH}/interface/commandline/paysages-cli + LD_LIBRARY_PATH=$(LIBRARY_PATH) ${RUNNER} ${BUILDPATH}/interface/commandline/paysages-cli $(ARGS) run:build - LD_LIBRARY_PATH=$(LIBRARY_PATH) ${RUNNER} ${BUILDPATH}/interface/desktop/paysages-gui + LD_LIBRARY_PATH=$(LIBRARY_PATH) ${RUNNER} ${BUILDPATH}/interface/desktop/paysages-gui $(ARGS) profile:build - LD_LIBRARY_PATH=${LIBRARY_PATH} perf record -g fp ${BUILDPATH}/interface/desktop/paysages-gui $(ARGS) + LD_LIBRARY_PATH=${LIBRARY_PATH} perf record -g ${BUILDPATH}/interface/desktop/paysages-gui $(ARGS) perf report -g profile_cli:build - LD_LIBRARY_PATH=${LIBRARY_PATH} perf record -g fp ${BUILDPATH}/interface/commandline/paysages-cli $(ARGS) + LD_LIBRARY_PATH=${LIBRARY_PATH} perf record -g ${BUILDPATH}/interface/commandline/paysages-cli $(ARGS) perf report -g package:build diff --git a/src/basics/NoiseGenerator.cpp b/src/basics/NoiseGenerator.cpp index 06b3f85..907095b 100644 --- a/src/basics/NoiseGenerator.cpp +++ b/src/basics/NoiseGenerator.cpp @@ -45,7 +45,7 @@ void NoiseGenerator::save(PackStream* stream) { NoiseLevel* level = levels + x; - stream->write(&level->wavelength); + stream->write(&level->frequency); stream->write(&level->amplitude); stream->write(&level->minvalue); } @@ -69,7 +69,7 @@ void NoiseGenerator::load(PackStream* stream) { NoiseLevel* level = levels + x; - stream->read(&level->wavelength); + stream->read(&level->frequency); stream->read(&level->amplitude); stream->read(&level->minvalue); } @@ -215,7 +215,7 @@ void NoiseGenerator::addLevelSimple(double scaling, double minvalue, double maxv { NoiseLevel level; - level.wavelength = scaling; + level.frequency = 1.0 / scaling; level.minvalue = minvalue; level.amplitude = maxvalue - minvalue; @@ -230,7 +230,7 @@ void NoiseGenerator::addLevels(int level_count, NoiseLevel start_level, double s { addLevel(start_level); start_level.minvalue += start_level.amplitude * (1.0 - amplitude_factor) * center_factor; - start_level.wavelength *= scaling_factor; + start_level.frequency /= scaling_factor; start_level.amplitude *= amplitude_factor; } } @@ -239,7 +239,7 @@ void NoiseGenerator::addLevelsSimple(int level_count, double scaling, double min { NoiseLevel level; - level.wavelength = scaling; + level.frequency = 1.0 / scaling; level.minvalue = minvalue; level.amplitude = maxvalue - minvalue; addLevels(level_count, level, 0.5, 0.5, center_factor); @@ -284,7 +284,7 @@ void NoiseGenerator::setLevelSimple(int index, double scaling, double minvalue, { NoiseLevel level; - level.wavelength = scaling; + level.frequency = 1.0 / scaling; level.minvalue = minvalue; level.amplitude = maxvalue - minvalue; @@ -313,7 +313,7 @@ void NoiseGenerator::normalizeAmplitude(double minvalue, double maxvalue, int ad levels[level].amplitude *= factor; if (adjust_scaling) { - levels[level].wavelength *= factor; + levels[level].frequency /= factor; } } height_offset = minvalue + (height_offset - current_minvalue) * factor; @@ -371,7 +371,7 @@ static inline double _fixValue(double value, double ridge, double curve) inline double NoiseGenerator::_get1DLevelValue(NoiseLevel* level, const NoiseState::NoiseOffset &offset, double x) { - return level->minvalue + _fixValue(_func_noise_1d(x / level->wavelength + offset.x), function.ridge_factor, function.curve_factor) * level->amplitude; + return level->minvalue + _fixValue(_func_noise_1d(x * level->frequency + offset.x), function.ridge_factor, function.curve_factor) * level->amplitude; } double NoiseGenerator::get1DLevel(int level, double x) @@ -428,7 +428,7 @@ double NoiseGenerator::get1DDetail(double x, double detail) inline double NoiseGenerator::_get2DLevelValue(NoiseLevel* level, const NoiseState::NoiseOffset &offset, double x, double y) { - return level->minvalue + _fixValue(_func_noise_2d(x / level->wavelength + offset.x, y / level->wavelength + offset.y), function.ridge_factor, function.curve_factor) * level->amplitude; + return level->minvalue + _fixValue(_func_noise_2d(x * level->frequency + offset.x, y * level->frequency + offset.y), function.ridge_factor, function.curve_factor) * level->amplitude; } double NoiseGenerator::get2DLevel(int level, double x, double y) @@ -485,7 +485,7 @@ double NoiseGenerator::get2DDetail(double x, double y, double detail) inline double NoiseGenerator::_get3DLevelValue(NoiseLevel* level, const NoiseState::NoiseOffset &offset, double x, double y, double z) { - return level->minvalue + _fixValue(_func_noise_3d(x / level->wavelength + offset.x, y / level->wavelength + offset.y, z / level->wavelength + offset.z), function.ridge_factor, function.curve_factor) * level->amplitude; + return level->minvalue + _fixValue(_func_noise_3d(x * level->frequency + offset.x, y * level->frequency + offset.y, z * level->frequency + offset.z), function.ridge_factor, function.curve_factor) * level->amplitude; } double NoiseGenerator::get3DLevel(int level, double x, double y, double z) diff --git a/src/basics/NoiseGenerator.h b/src/basics/NoiseGenerator.h index 89eb688..ca94f6f 100644 --- a/src/basics/NoiseGenerator.h +++ b/src/basics/NoiseGenerator.h @@ -28,7 +28,7 @@ public: typedef struct { - double wavelength; + double frequency; double amplitude; double minvalue; } NoiseLevel; diff --git a/src/definition/CameraDefinition.cpp b/src/definition/CameraDefinition.cpp index 5fcfce6..1f43199 100644 --- a/src/definition/CameraDefinition.cpp +++ b/src/definition/CameraDefinition.cpp @@ -90,11 +90,14 @@ void CameraDefinition::validate() projector = mperspective.mult(Matrix4::newLookAt(location, target, up)); unprojector = projector.inversed(); + + inv_x_factor = 1.0 / (0.5 * width); + inv_y_factor = 1.0 / (0.5 * height); } double CameraDefinition::getRealDepth(const Vector3 &projected) const { - Vector3 v(projected.x / (0.5 * width) - 1.0, -(projected.y / (0.5 * height) - 1.0), projected.z); + Vector3 v(projected.x * inv_x_factor - 1.0, -(projected.y * inv_x_factor - 1.0), projected.z); return unperspective.transform(v).z; } diff --git a/src/definition/CameraDefinition.h b/src/definition/CameraDefinition.h index faab350..3268381 100644 --- a/src/definition/CameraDefinition.h +++ b/src/definition/CameraDefinition.h @@ -85,6 +85,8 @@ private: Matrix4 projector; Matrix4 unprojector; Matrix4 unperspective; + double inv_x_factor; + double inv_y_factor; }; } diff --git a/src/interface/commandline/main.cpp b/src/interface/commandline/main.cpp index dcd2571..5de8824 100644 --- a/src/interface/commandline/main.cpp +++ b/src/interface/commandline/main.cpp @@ -5,16 +5,18 @@ #include "CameraDefinition.h" #include "AtmosphereDefinition.h" -#include "SoftwareRenderer.h" +#include "SoftwareCanvasRenderer.h" #include "Scenery.h" +#include "RenderConfig.h" +#include "ColorProfile.h" -void startRender(SoftwareRenderer* renderer, char* outputpath, RenderArea::RenderParams params) +void startRender(SoftwareCanvasRenderer *renderer, char *outputpath) { printf("\rRendering %s ... \n", outputpath); - renderer->start(params); + renderer->render(); printf("\rSaving %s ... \n", outputpath); remove(outputpath); - renderer->render_area->saveToFile(outputpath); + renderer->saveToDisk(outputpath); } void displayHelp() @@ -43,9 +45,9 @@ void _previewUpdate(double progress) int main(int argc, char** argv) { - SoftwareRenderer* renderer; + SoftwareCanvasRenderer* renderer; char* conf_file_path = NULL; - RenderArea::RenderParams conf_render_params = {800, 600, 1, 5}; + RenderConfig conf_render_params(800, 600, 1, 5); int conf_first_picture = 0; int conf_nb_pictures = 1; double conf_daytime_start = 0.4; @@ -177,13 +179,14 @@ int main(int argc, char** argv) Vector3 step = {conf_camera_step_x, conf_camera_step_y, conf_camera_step_z}; camera->setLocation(camera->getLocation().add(step)); - renderer = new SoftwareRenderer(scenery); - renderer->setPreviewCallbacks(NULL, NULL, _previewUpdate); + renderer = new SoftwareCanvasRenderer(); + renderer->setConfig(conf_render_params); + renderer->setScenery(scenery); if (outputcount >= conf_first_picture) { sprintf(outputpath, "output/pic%05d.png", outputcount); - startRender(renderer, outputpath, conf_render_params); + startRender(renderer, outputpath); } delete renderer; diff --git a/src/interface/desktop/WidgetCanvas.cpp b/src/interface/desktop/WidgetCanvas.cpp new file mode 100644 index 0000000..a1a84b5 --- /dev/null +++ b/src/interface/desktop/WidgetCanvas.cpp @@ -0,0 +1,6 @@ +#include "WidgetCanvas.h" + +WidgetCanvas::WidgetCanvas(QWidget *parent) : + QWidget(parent) +{ +} diff --git a/src/interface/desktop/WidgetCanvas.h b/src/interface/desktop/WidgetCanvas.h new file mode 100644 index 0000000..ad74ae3 --- /dev/null +++ b/src/interface/desktop/WidgetCanvas.h @@ -0,0 +1,29 @@ +#ifndef WIDGETCANVAS_H +#define WIDGETCANVAS_H + +#include "desktop_global.h" + +#include + +namespace paysages { +namespace desktop { + +/*! + * \brief Widget to display the full content of a Canvas. + */ +class WidgetCanvas : public QWidget +{ + Q_OBJECT +public: + explicit WidgetCanvas(QWidget *parent = 0); + +signals: + +public slots: + +}; + +} +} + +#endif // WIDGETCANVAS_H diff --git a/src/interface/desktop/WidgetPreviewCanvas.cpp b/src/interface/desktop/WidgetPreviewCanvas.cpp new file mode 100644 index 0000000..f52e72c --- /dev/null +++ b/src/interface/desktop/WidgetPreviewCanvas.cpp @@ -0,0 +1,77 @@ +#include "WidgetPreviewCanvas.h" + +#include "tools.h" +#include "Canvas.h" +#include "CanvasPreview.h" + +#include + +WidgetPreviewCanvas::WidgetPreviewCanvas(QWidget *parent) : + QWidget(parent), canvas(NULL) +{ + pixbuf = new QImage(); + + startTimer(500); +} + +WidgetPreviewCanvas::~WidgetPreviewCanvas() +{ + delete pixbuf; +} + +void WidgetPreviewCanvas::setCanvas(const Canvas *canvas) +{ + if (not this->canvas) + { + this->canvas = canvas; + canvas->getPreview()->initLive(this); + } +} + +void WidgetPreviewCanvas::setToneMapping(const ColorProfile &profile) +{ + if (canvas) + { + canvas->getPreview()->setToneMapping(profile); + canvas->getPreview()->updateLive(this); + update(); + } +} + +void WidgetPreviewCanvas::paintEvent(QPaintEvent *) +{ + QPainter painter(this); + painter.drawImage(0, 0, *pixbuf); +} + +void WidgetPreviewCanvas::canvasResized(int width, int height) +{ + if (QSize(width, height) != this->size()) + { + setMaximumSize(width, height); + setMinimumSize(width, height); + resize(width, height); + + delete pixbuf; + pixbuf = new QImage(width, height, QImage::Format_ARGB32); + } +} + +void WidgetPreviewCanvas::canvasCleared(const Color &col) +{ + pixbuf->fill(colorToQColor(col)); +} + +void WidgetPreviewCanvas::canvasPainted(int x, int y, const Color &col) +{ + pixbuf->setPixel(x, pixbuf->height() - 1 - y, colorToQColor(col).rgb()); +} + +void WidgetPreviewCanvas::timerEvent(QTimerEvent *) +{ + if (canvas) + { + canvas->getPreview()->updateLive(this); + update(); + } +} diff --git a/src/interface/desktop/WidgetPreviewCanvas.h b/src/interface/desktop/WidgetPreviewCanvas.h new file mode 100644 index 0000000..3933d55 --- /dev/null +++ b/src/interface/desktop/WidgetPreviewCanvas.h @@ -0,0 +1,50 @@ +#ifndef WIDGETPREVIEWCANVAS_H +#define WIDGETPREVIEWCANVAS_H + +#include "desktop_global.h" + +#include +#include "CanvasLiveClient.h" + +namespace paysages { +namespace desktop { + +/** + * Widget to display a live-updated preview of a Canvas software rendering. + */ +class WidgetPreviewCanvas : public QWidget, public CanvasLiveClient +{ + Q_OBJECT +public: + explicit WidgetPreviewCanvas(QWidget *parent = 0); + virtual ~WidgetPreviewCanvas(); + + /** + * Set the canvas to watch and display, null to stop watching. + * + * This function must be called from the graphics thread. + */ + void setCanvas(const Canvas *canvas); + + /** + * Set the tone mapping to apply to pixel colors. + */ + void setToneMapping(const ColorProfile &profile); + + virtual void paintEvent(QPaintEvent* event); + +protected: + virtual void canvasResized(int width, int height); + virtual void canvasCleared(const Color &col); + virtual void canvasPainted(int x, int y, const Color &col); + virtual void timerEvent(QTimerEvent *event); + +private: + QImage* pixbuf; + const Canvas *canvas; +}; + +} +} + +#endif // WIDGETPREVIEWCANVAS_H diff --git a/src/interface/desktop/common/freeformhelper.cpp b/src/interface/desktop/common/freeformhelper.cpp index 7a927f8..aee47fa 100644 --- a/src/interface/desktop/common/freeformhelper.cpp +++ b/src/interface/desktop/common/freeformhelper.cpp @@ -12,9 +12,10 @@ #include "mainwindow.h" #include "dialogrender.h" #include "dialogexplorer.h" +#include "RenderConfig.h" #include "DesktopScenery.h" #include "BasePreview.h" -#include "SoftwareRenderer.h" +#include "SoftwareCanvasRenderer.h" #include "CameraDefinition.h" #include "tools.h" @@ -246,15 +247,16 @@ void FreeFormHelper::processExploreClicked() void FreeFormHelper::processRenderClicked() { - SoftwareRenderer renderer(DesktopScenery::getCurrent()); + RenderConfig params(400, 300, 1, 3); + + SoftwareCanvasRenderer renderer; + renderer.setConfig(params); + renderer.setScenery(DesktopScenery::getCurrent()); emit needAlterRenderer(&renderer); - DialogRender* dialog = new DialogRender(_form_widget, &renderer); - RenderArea::RenderParams params = {400, 300, 1, 3}; - dialog->startRender(params); - - delete dialog; + DialogRender dialog(_form_widget, &renderer); + dialog.startRender(); } void FreeFormHelper::processDecimalChange(double value) diff --git a/src/interface/desktop/desktop.pro b/src/interface/desktop/desktop.pro index cb703cd..e6a47d2 100644 --- a/src/interface/desktop/desktop.pro +++ b/src/interface/desktop/desktop.pro @@ -52,7 +52,9 @@ HEADERS += \ lighting/SmallPreviewHues.h \ textures/DialogTexturesLayer.h \ desktop_global.h \ - DesktopScenery.h + DesktopScenery.h \ + WidgetCanvas.h \ + WidgetPreviewCanvas.h SOURCES += \ terrain/widgetheightmap.cpp \ @@ -96,7 +98,9 @@ SOURCES += \ lighting/SmallPreviewColor.cpp \ lighting/SmallPreviewHues.cpp \ textures/DialogTexturesLayer.cpp \ - DesktopScenery.cpp + DesktopScenery.cpp \ + WidgetCanvas.cpp \ + WidgetPreviewCanvas.cpp FORMS += \ terrain/dialogterrainpainting.ui \ diff --git a/src/interface/desktop/desktop_global.h b/src/interface/desktop/desktop_global.h index da63022..79c0d46 100644 --- a/src/interface/desktop/desktop_global.h +++ b/src/interface/desktop/desktop_global.h @@ -10,6 +10,9 @@ namespace paysages { namespace desktop { class BaseInput; class BaseForm; + + class WidgetCanvas; + class WidgetPreviewCanvas; } } diff --git a/src/interface/desktop/dialognoise.cpp b/src/interface/desktop/dialognoise.cpp index b63f52e..57a826e 100644 --- a/src/interface/desktop/dialognoise.cpp +++ b/src/interface/desktop/dialognoise.cpp @@ -282,7 +282,7 @@ void DialogNoise::addLevel() NoiseGenerator::NoiseLevel level; level.amplitude = 0.1; - level.wavelength = 0.1; + level.frequency = 0.1; _current->addLevel(level); @@ -330,7 +330,7 @@ void DialogNoise::levelChanged(int row) ((PreviewLevel*)previewLevel)->setLevel(row); slider_height->setValue(_current_level_params.amplitude * 1000.0); - slider_scaling->setValue(_current_level_params.wavelength * 1000.0); + slider_scaling->setValue(_current_level_params.frequency * 1000.0); } // TODO else ... } @@ -345,7 +345,7 @@ void DialogNoise::heightChanged(int value) void DialogNoise::scalingChanged(int value) { - _current_level_params.wavelength = ((double)value) / 1000.0; + _current_level_params.frequency = ((double)value) / 1000.0; _current->setLevel(_current_level, _current_level_params); previewLevel->redraw(); previewTotal->redraw(); diff --git a/src/interface/desktop/dialogrender.cpp b/src/interface/desktop/dialogrender.cpp index 1d979f0..58293b7 100644 --- a/src/interface/desktop/dialogrender.cpp +++ b/src/interface/desktop/dialogrender.cpp @@ -18,90 +18,45 @@ #include #include "tools.h" -#include "SoftwareRenderer.h" #include "Scenery.h" #include "ColorProfile.h" - -static DialogRender* _current_dialog; - -static void _renderStart(int width, int height, const Color &background) -{ - _current_dialog->pixbuf_lock->lock(); - delete _current_dialog->pixbuf; - _current_dialog->pixbuf = new QImage(width, height, QImage::Format_ARGB32); - _current_dialog->pixbuf->fill(colorToQColor(background).rgb()); - _current_dialog->pixbuf_lock->unlock(); - - _current_dialog->tellRenderSize(width, height); -} - -static void _renderDraw(int x, int y, const Color &col) -{ - _current_dialog->pixbuf->setPixel(x, _current_dialog->pixbuf->height() - 1 - y, colorToQColor(col).rgb()); -} - -static void _renderUpdate(double progress) -{ - _current_dialog->area->update(); - _current_dialog->tellProgressChange(progress); -} +#include "SoftwareCanvasRenderer.h" +#include "WidgetPreviewCanvas.h" +#include "Canvas.h" class RenderThread:public QThread { public: - RenderThread(DialogRender* dialog, SoftwareRenderer* renderer, RenderArea::RenderParams params):QThread() + RenderThread(DialogRender* dialog, SoftwareCanvasRenderer* renderer):QThread() { _dialog = dialog; _renderer = renderer; - _params = params; } void run() { - _renderer->start(_params); + _renderer->render(); _dialog->tellRenderEnded(); } private: DialogRender* _dialog; - SoftwareRenderer* _renderer; - RenderArea::RenderParams _params; + SoftwareCanvasRenderer* _renderer; }; -class _RenderArea:public QWidget -{ -public: - _RenderArea(QWidget* parent): - QWidget(parent) - { - setMinimumSize(800, 600); - } - - void paintEvent(QPaintEvent*) - { - QPainter painter(this); - _current_dialog->pixbuf_lock->lock(); - painter.drawImage(0, 0, *_current_dialog->pixbuf); - _current_dialog->pixbuf_lock->unlock(); - } -}; - -DialogRender::DialogRender(QWidget *parent, SoftwareRenderer* renderer): +DialogRender::DialogRender(QWidget *parent, SoftwareCanvasRenderer* renderer): QDialog(parent, Qt::WindowTitleHint | Qt::WindowMaximizeButtonHint | Qt::WindowMinimizeButtonHint | Qt::WindowCloseButtonHint) { pixbuf_lock = new QMutex(); pixbuf = new QImage(1, 1, QImage::Format_ARGB32); - _current_dialog = this; _render_thread = NULL; - _renderer = renderer; + canvas_renderer = renderer; setModal(true); setWindowTitle(tr("Paysages 3D - Render")); setLayout(new QVBoxLayout()); - _scroll = new QScrollArea(this); - _scroll->setAlignment(Qt::AlignCenter); - area = new _RenderArea(_scroll); - _scroll->setWidget(area); - layout()->addWidget(_scroll); + canvas_preview = new WidgetPreviewCanvas(this); + canvas_preview->setCanvas(canvas_renderer->getCanvas()); + layout()->addWidget(canvas_preview); // Status bar _info = new QWidget(this); @@ -140,19 +95,19 @@ DialogRender::DialogRender(QWidget *parent, SoftwareRenderer* renderer): _actions->layout()->addWidget(_save_button); // Connections - //connect(this, SIGNAL(renderSizeChanged(int, int)), this, SLOT(applyRenderSize(int, int))); - connect(this, SIGNAL(progressChanged(double)), this, SLOT(applyProgress(double))); connect(this, SIGNAL(renderEnded()), this, SLOT(applyRenderEnded())); connect(_save_button, SIGNAL(clicked()), this, SLOT(saveRender())); connect(_tonemapping_control, SIGNAL(currentIndexChanged(int)), this, SLOT(toneMappingChanged())); connect(_exposure_control, SIGNAL(valueChanged(int)), this, SLOT(toneMappingChanged())); + + toneMappingChanged(); } DialogRender::~DialogRender() { if (_render_thread) { - _renderer->interrupt(); + canvas_renderer->interrupt(); _render_thread->wait(); delete _render_thread; @@ -161,31 +116,20 @@ DialogRender::~DialogRender() delete pixbuf_lock; } -void DialogRender::tellRenderSize(int width, int height) -{ - emit renderSizeChanged(width, height); -} - -void DialogRender::tellProgressChange(double value) -{ - emit progressChanged(value); -} - void DialogRender::tellRenderEnded() { emit renderEnded(); } -void DialogRender::startRender(RenderArea::RenderParams params) +void DialogRender::startRender() { _started = time(NULL); - applyRenderSize(params.width, params.height); - _renderer->setPreviewCallbacks(_renderStart, _renderDraw, _renderUpdate); - - _render_thread = new RenderThread(this, _renderer, params); + _render_thread = new RenderThread(this, canvas_renderer); _render_thread->start(); + startTimer(100); + exec(); } @@ -193,8 +137,6 @@ void DialogRender::applyRenderEnded() { _info->hide(); _actions->show(); - - area->update(); } void DialogRender::saveRender() @@ -208,8 +150,7 @@ void DialogRender::saveRender() { filepath = filepath.append(".png"); } - std::string filepathstr = filepath.toStdString(); - if (_renderer->render_area->saveToFile((char*)filepathstr.c_str())) + if (canvas_renderer->saveToDisk(filepath.toStdString())) { QMessageBox::information(this, "Message", QString(tr("The picture %1 has been saved.")).arg(filepath)); } @@ -223,35 +164,26 @@ void DialogRender::saveRender() void DialogRender::toneMappingChanged() { ColorProfile profile((ColorProfile::ToneMappingOperator)_tonemapping_control->currentIndex(), ((double)_exposure_control->value()) * 0.01); - _renderer->render_area->setToneMapping(profile); + canvas_preview->setToneMapping(profile); } void DialogRender::loadLastRender() { - applyRenderSize(_renderer->render_width, _renderer->render_height); - _renderer->setPreviewCallbacks(_renderStart, _renderDraw, _renderUpdate); renderEnded(); toneMappingChanged(); exec(); } -void DialogRender::applyRenderSize(int width, int height) -{ - area->setMinimumSize(width, height); - area->setMaximumSize(width, height); - area->resize(width, height); - _scroll->setMinimumSize(width > 800 ? 820 : width + 20, height > 600 ? 620 : height + 20); -} - -void DialogRender::applyProgress(double value) +void DialogRender::timerEvent(QTimerEvent *) { double diff = difftime(time(NULL), _started); int hours = (int)floor(diff / 3600.0); int minutes = (int)floor((diff - 3600.0 * hours) / 60.0); int seconds = (int)floor(diff - 3600.0 * hours - 60.0 * minutes); _timer->setText(tr("%1:%2.%3").arg(hours).arg(minutes, 2, 10, QLatin1Char('0')).arg(seconds, 2, 10, QLatin1Char('0'))); - _progress->setValue((int)(value * 1000.0)); + + _progress->setValue((int)(canvas_renderer->getProgress() * 1000.0)); _progress->update(); } diff --git a/src/interface/desktop/dialogrender.h b/src/interface/desktop/dialogrender.h index 80dbb05..57b33e3 100644 --- a/src/interface/desktop/dialogrender.h +++ b/src/interface/desktop/dialogrender.h @@ -5,7 +5,6 @@ #include #include -#include "RenderArea.h" class QThread; class QProgressBar; @@ -19,33 +18,30 @@ class DialogRender : public QDialog { Q_OBJECT public: - explicit DialogRender(QWidget *parent, SoftwareRenderer* renderer); + explicit DialogRender(QWidget *parent, SoftwareCanvasRenderer *renderer); ~DialogRender(); - void tellRenderSize(int width, int height); - void tellProgressChange(double value); void tellRenderEnded(); - void startRender(RenderArea::RenderParams params); + void startRender(); void loadLastRender(); + virtual void timerEvent(QTimerEvent *event) override; + QImage* pixbuf; QMutex* pixbuf_lock; - QWidget* area; private slots: - void applyRenderSize(int width, int height); - void applyProgress(double value); void saveRender(); void applyRenderEnded(); void toneMappingChanged(); signals: - void renderSizeChanged(int width, int height); - void progressChanged(double value); void renderEnded(); private: - QScrollArea* _scroll; + SoftwareCanvasRenderer* canvas_renderer; + WidgetPreviewCanvas* canvas_preview; + QWidget* _info; QWidget* _actions; QComboBox* _tonemapping_control; @@ -53,7 +49,6 @@ private: QPushButton* _save_button; QThread* _render_thread; QLabel* _timer; - SoftwareRenderer* _renderer; QProgressBar* _progress; time_t _started; }; diff --git a/src/interface/desktop/formrender.cpp b/src/interface/desktop/formrender.cpp index 9a24dab..2b9150f 100644 --- a/src/interface/desktop/formrender.cpp +++ b/src/interface/desktop/formrender.cpp @@ -7,7 +7,7 @@ #include "tools.h" #include "DesktopScenery.h" #include "PackStream.h" -#include "SoftwareRenderer.h" +#include "SoftwareCanvasRenderer.h" #include "BasePreview.h" #include "CloudsDefinition.h" #include "CameraDefinition.h" @@ -36,8 +36,8 @@ BaseForm(parent, true) addInput(new InputCamera(this, tr("Camera"), _camera)); addInputInt(tr("Quality"), &_params.quality, 1, 10, 1, 1); - addInputInt(tr("Image width"), &_params.width, 100, 2000, 10, 100); - addInputInt(tr("Image height"), &_params.height, 100, 1200, 10, 100); + addInputInt(tr("Image width"), &_params.width, 100, 4000, 10, 100); + addInputInt(tr("Image height"), &_params.height, 100, 3000, 10, 100); addInputInt(tr("Anti aliasing"), &_params.antialias, 1, 4, 1, 1); button = addButton(tr("Start new render")); @@ -103,14 +103,16 @@ void FormRender::startQuickRender() { delete _renderer; } - _renderer = new SoftwareRenderer(DesktopScenery::getCurrent()); + + RenderConfig config(400, 300, 1, 3); + + _renderer = new SoftwareCanvasRenderer(); + _renderer->setScenery(DesktopScenery::getCurrent()); + _renderer->setConfig(config); _renderer_inited = true; - DialogRender* dialog = new DialogRender(this, _renderer); - RenderArea::RenderParams params = {400, 300, 1, 3}; - dialog->startRender(params); - - delete dialog; + DialogRender dialog(this, _renderer); + dialog.startRender(); } void FormRender::startRender() @@ -119,22 +121,20 @@ void FormRender::startRender() { delete _renderer; } - _renderer = new SoftwareRenderer(DesktopScenery::getCurrent()); + _renderer = new SoftwareCanvasRenderer(); + _renderer->setScenery(DesktopScenery::getCurrent()); + _renderer->setConfig(_params); _renderer_inited = true; - DialogRender* dialog = new DialogRender(this, _renderer); - dialog->startRender(_params); - - delete dialog; + DialogRender dialog(this, _renderer); + dialog.startRender(); } void FormRender::showRender() { if (_renderer_inited) { - DialogRender* dialog = new DialogRender(this, _renderer); - dialog->loadLastRender(); - - delete dialog; + DialogRender dialog(this, _renderer); + dialog.loadLastRender(); } } diff --git a/src/interface/desktop/formrender.h b/src/interface/desktop/formrender.h index b555575..8404f62 100644 --- a/src/interface/desktop/formrender.h +++ b/src/interface/desktop/formrender.h @@ -5,7 +5,7 @@ #include "baseform.h" -#include "RenderArea.h" +#include "RenderConfig.h" class FormRender : public BaseForm { @@ -29,9 +29,9 @@ protected slots: virtual void configChangeEvent(); private: - RenderArea::RenderParams _params; + RenderConfig _params; CameraDefinition* _camera; - SoftwareRenderer* _renderer; + SoftwareCanvasRenderer* _renderer; bool _renderer_inited; BasePreview* _preview_landscape; Base2dPreviewRenderer* _preview_landscape_renderer; diff --git a/src/render/software/Canvas.cpp b/src/render/software/Canvas.cpp new file mode 100644 index 0000000..8c9d0df --- /dev/null +++ b/src/render/software/Canvas.cpp @@ -0,0 +1,112 @@ +#include "Canvas.h" + +#include + +#include "CanvasPortion.h" +#include "CanvasPreview.h" +#include "CanvasPictureWriter.h" + +#define CUTTER_SIZE 800 + +Canvas::Canvas() +{ + horizontal_portion_count = 1; + vertical_portion_count = 1; + width = 1; + height = 1; + portions.push_back(new CanvasPortion); + + preview = new CanvasPreview; +} + +Canvas::~Canvas() +{ + for (auto portion: portions) + { + delete portion; + } + delete preview; +} + +void Canvas::setSize(int width, int height) +{ + horizontal_portion_count = 1 + (width - 1) / CUTTER_SIZE; + vertical_portion_count = 1 + (height - 1) / CUTTER_SIZE; + + int portion_width = width / horizontal_portion_count; + int portion_height = height / vertical_portion_count; + + for (auto portion: portions) + { + delete portion; + } + portions.clear(); + + int done_width = 0; + int done_height = 0; + int index = 0; + + for (int y = 0; y < vertical_portion_count; y++) + { + done_width = 0; + for (int x = 0; x < horizontal_portion_count; x++) + { + CanvasPortion *portion = new CanvasPortion(index++, preview); + + portion->setSize((x == horizontal_portion_count - 1) ? width - done_width : portion_width, + (y == vertical_portion_count - 1) ? height - done_height : portion_height, + done_width, + done_height); + + done_width += portion->getWidth(); + if (x == horizontal_portion_count - 1) + { + done_height += portion->getHeight(); + } + + portions.push_back(portion); + } + assert(done_width == width); + } + assert(done_height == height); + + this->width = width; + this->height = height; + + // Smaller preview + while (width > 1000 or height > 700) + { + width = width / 2; + height = height / 2; + } + preview->setSize(this->width, this->height, width, height); +} + +CanvasPortion *Canvas::at(int x, int y) const +{ + assert(x >= 0 && x < horizontal_portion_count); + assert(y >= 0 && y < vertical_portion_count); + + return portions[y * horizontal_portion_count + x]; +} + +CanvasPortion *Canvas::atPixel(int x, int y) const +{ + assert(x >= 0 && x < width); + assert(y >= 0 && y < height); + + int pwidth = portions[0]->getWidth(); + int pheight = portions[0]->getHeight(); + + return at(x / pwidth, y / pheight); +} + +bool Canvas::saveToDisk(const std::string &filepath, const ColorProfile &profile, int antialias) const +{ + assert(antialias >= 1); + + CanvasPictureWriter writer(this); + writer.setColorProfile(profile); + writer.setAntialias(antialias); + return writer.saveCanvas(filepath); +} diff --git a/src/render/software/Canvas.h b/src/render/software/Canvas.h new file mode 100644 index 0000000..c326bca --- /dev/null +++ b/src/render/software/Canvas.h @@ -0,0 +1,53 @@ +#ifndef CANVAS_H +#define CANVAS_H + +#include "software_global.h" + +namespace paysages { +namespace software { + +/** + * @brief Graphics area to draw and do compositing. + * + * Software rendering is done in portions of Canvas (in CanvasPortion class). + * This splitting in portions allows to keep memory consumption low. + */ +class SOFTWARESHARED_EXPORT Canvas +{ +public: + Canvas(); + ~Canvas(); + + void setSize(int width, int height); + + inline int getHorizontalPortionCount() const {return horizontal_portion_count;} + inline int getVerticalPortionCount() const {return vertical_portion_count;} + + CanvasPortion *at(int x, int y) const; + CanvasPortion *atPixel(int x, int y) const; + + inline int getWidth() const {return width;} + inline int getHeight() const {return height;} + inline CanvasPreview *getPreview() const {return preview;} + + /** + * Save the canvas to a picture file on disk. + * + * Returns true if the save was successful. + */ + bool saveToDisk(const std::string &filepath, const ColorProfile &profile, int antialias) const; + +private: + std::vector portions; + int horizontal_portion_count; + int vertical_portion_count; + int width; + int height; + + CanvasPreview *preview; +}; + +} +} + +#endif // CANVAS_H diff --git a/src/render/software/CanvasFragment.cpp b/src/render/software/CanvasFragment.cpp new file mode 100644 index 0000000..20ce8a0 --- /dev/null +++ b/src/render/software/CanvasFragment.cpp @@ -0,0 +1,16 @@ +#include "CanvasFragment.h" + +CanvasFragment::CanvasFragment() +{ +} + +CanvasFragment::CanvasFragment(double z, const Vector3 &location, int client, bool opaque): + opaque(opaque), z(z), location(location), client(client) +{ + color = COLOR_WHITE; +} + +void CanvasFragment::setColor(const Color &col) +{ + color = col; +} diff --git a/src/render/software/CanvasFragment.h b/src/render/software/CanvasFragment.h new file mode 100644 index 0000000..520415c --- /dev/null +++ b/src/render/software/CanvasFragment.h @@ -0,0 +1,40 @@ +#ifndef CANVASFRAGMENT_H +#define CANVASFRAGMENT_H + +#include "software_global.h" + +#include "Color.h" +#include "Vector3.h" + +namespace paysages { +namespace software { + +/** + * @brief Representation of world coordinates projected in a canvas pixel. + */ +class SOFTWARESHARED_EXPORT CanvasFragment +{ +public: + CanvasFragment(); + CanvasFragment(double z, const Vector3 &location, int client=0, bool opaque=true); + + void setColor(const Color &col); + + inline bool getOpaque() const {return opaque;} + inline double getZ() const {return z;} + inline const Vector3 &getLocation() const {return location;} + inline int getClient() const {return client;} + inline const Color &getColor() const {return color;} + +private: + bool opaque; + double z; + Vector3 location; + int client; + Color color; +}; + +} +} + +#endif // CANVASFRAGMENT_H diff --git a/src/render/software/CanvasLiveClient.cpp b/src/render/software/CanvasLiveClient.cpp new file mode 100644 index 0000000..3586fdd --- /dev/null +++ b/src/render/software/CanvasLiveClient.cpp @@ -0,0 +1,17 @@ +#include "CanvasLiveClient.h" + +CanvasLiveClient::CanvasLiveClient() +{ +} + +void CanvasLiveClient::canvasResized(int, int) +{ +} + +void CanvasLiveClient::canvasCleared(const Color &) +{ +} + +void CanvasLiveClient::canvasPainted(int, int, const Color &) +{ +} diff --git a/src/render/software/CanvasLiveClient.h b/src/render/software/CanvasLiveClient.h new file mode 100644 index 0000000..133e7cb --- /dev/null +++ b/src/render/software/CanvasLiveClient.h @@ -0,0 +1,25 @@ +#ifndef CANVASLIVECLIENT_H +#define CANVASLIVECLIENT_H + +#include "software_global.h" + +namespace paysages { +namespace software { + +/** + * @brief Abstract class to receive live modifications from canvas preview. + */ +class SOFTWARESHARED_EXPORT CanvasLiveClient +{ +public: + CanvasLiveClient(); + + virtual void canvasResized(int width, int height); + virtual void canvasCleared(const Color &col); + virtual void canvasPainted(int x, int y, const Color &col); +}; + +} +} + +#endif // CANVASLIVECLIENT_H diff --git a/src/render/software/CanvasPictureWriter.cpp b/src/render/software/CanvasPictureWriter.cpp new file mode 100644 index 0000000..22d1775 --- /dev/null +++ b/src/render/software/CanvasPictureWriter.cpp @@ -0,0 +1,116 @@ +#include "CanvasPictureWriter.h" + +#include + +#include "Canvas.h" +#include "CanvasPortion.h" +#include "ColorProfile.h" +#include "PackStream.h" + +CanvasPictureWriter::CanvasPictureWriter(const Canvas *canvas): + canvas(canvas) +{ + profile = new ColorProfile(); + antialias = 1; + width = canvas->getWidth(); + height = canvas->getHeight(); + + last_portion = NULL; + last_stream = NULL; + last_y = 0; +} + +CanvasPictureWriter::~CanvasPictureWriter() +{ + delete profile; + if (last_stream) + { + delete last_stream; + } +} + +void CanvasPictureWriter::setAntialias(int antialias) +{ + assert(antialias >= 1); + assert(canvas->getWidth() % antialias == 0); + assert(canvas->getHeight() % antialias == 0); + + this->antialias = antialias; + this->width = canvas->getWidth() / antialias; + this->height = canvas->getHeight() / antialias; +} + +void CanvasPictureWriter::setColorProfile(const ColorProfile &profile) +{ + profile.copy(this->profile); +} + +bool CanvasPictureWriter::saveCanvas(const std::string &filepath) +{ + return save(filepath, width, height); +} + +unsigned int CanvasPictureWriter::getPixel(int x, int y) +{ + Color comp; + + if (antialias > 1) + { + int basex = x * antialias; + int basey = y * antialias; + double factor = 1.0 / (antialias * antialias); + + comp = COLOR_BLACK; + + for (int iy = 0; iy < antialias; iy++) + { + for (int ix = 0; ix < antialias; ix++) + { + Color col = getRawPixel(basex + ix, basey + iy); + comp.r += col.r * factor; + comp.g += col.g * factor; + comp.b += col.b * factor; + } + } + } + else + { + comp = getRawPixel(x, y); + } + + comp = profile->apply(comp); + comp.normalize(); + return comp.to32BitBGRA(); +} + +Color CanvasPictureWriter::getRawPixel(int x, int y) +{ + // Get the portion this pixel is in + CanvasPortion *portion = canvas->atPixel(x, y); + + // While we stay in the same portion line, read is sequential in the stream + if (portion != last_portion or last_y != y) + { + // Get the pack stream positioned at the pixel + if (last_stream) + { + delete last_stream; + } + last_stream = new PackStream; + if (portion->getReadStream(*last_stream, x - portion->getXOffset(), y - portion->getYOffset())) + { + last_portion = portion; + last_y = y; + } + else + { + // Portion has no stream + return COLOR_BLACK; + } + } + + // Load the pixel + Color col; + col.load(last_stream); + return col; +} diff --git a/src/render/software/CanvasPictureWriter.h b/src/render/software/CanvasPictureWriter.h new file mode 100644 index 0000000..a07d3df --- /dev/null +++ b/src/render/software/CanvasPictureWriter.h @@ -0,0 +1,58 @@ +#ifndef CANVASPICTUREWRITER_H +#define CANVASPICTUREWRITER_H + +#include "software_global.h" + +#include "PictureWriter.h" + +namespace paysages { +namespace software { + +/** + * Picture writer to create the final image from canvas portions. + */ +class SOFTWARESHARED_EXPORT CanvasPictureWriter: public PictureWriter +{ +public: + CanvasPictureWriter(const Canvas *canvas); + virtual ~CanvasPictureWriter(); + + /** + * Set the antialias factor, 1 for no antialiasing. + */ + void setAntialias(int antialias); + + /** + * Set the color profile to apply to final pixels. + */ + void setColorProfile(const ColorProfile &profile); + + /** + * Start the saving process. + * + * Returns true if saving was successful. + */ + bool saveCanvas(const std::string &filepath); + +protected: + virtual unsigned int getPixel(int x, int y) override; + +private: + Color getRawPixel(int x, int y); + +private: + const Canvas *canvas; + int antialias; + int width; + int height; + ColorProfile *profile; + + CanvasPortion *last_portion; + int last_y; + PackStream *last_stream; +}; + +} +} + +#endif // CANVASPICTUREWRITER_H diff --git a/src/render/software/CanvasPixel.cpp b/src/render/software/CanvasPixel.cpp new file mode 100644 index 0000000..aa11a38 --- /dev/null +++ b/src/render/software/CanvasPixel.cpp @@ -0,0 +1,106 @@ +#include "CanvasPixel.h" + +#include + +CanvasPixel::CanvasPixel() +{ + count = 0; + composite = COLOR_BLACK; +} + +const CanvasFragment *CanvasPixel::getFrontFragment() const +{ + if (count == 0) + { + return NULL; + } + else + { + return fragments + (count - 1); + } +} + +void CanvasPixel::reset() +{ + count = 0; +} + +void CanvasPixel::pushFragment(const CanvasFragment &fragment) +{ + if (count == 0) + { + fragments[0] = fragment; + count = 1; + } + else + { + if (fragments[0].getOpaque() and fragment.getZ() < fragments[0].getZ()) + { + // behind opaque fragment, don't bother + return; + } + + // find expected position + int i = 0; + while (i < count and fragment.getZ() > fragments[i].getZ()) + { + i++; + } + + if (fragment.getOpaque()) + { + // Discard fragments masked by the incoming opaque one + if (i < count) + { + memmove(fragments + 1, fragments + i, sizeof(CanvasFragment) * (count - i)); + count -= i; + } + else + { + count = 1; + } + fragments[0] = fragment; + } + else if (i < count) + { + // Need to make room for the incoming fragment + if (count < MAX_FRAGMENT_COUNT) + { + memmove(fragments + i + 1, fragments + i, sizeof(CanvasFragment) * (count - i)); + fragments[i] = fragment; + count++; + } + } + else + { + if (count == MAX_FRAGMENT_COUNT) + { + // Replace nearest fragment + fragments[count - 1] = fragment; + } + else + { + // Append + fragments[count] = fragment; + count++; + } + } + } + + updateComposite(); +} + +void CanvasPixel::updateComposite() +{ + Color result(0.0, 0.0, 0.0, 1.0); + for (int i = 0; i < count; i++) + { + result.mask(fragments[i].getColor()); + } + composite = result; +} + +void CanvasPixel::setComposite(const Color &color) +{ + composite = color; +} diff --git a/src/render/software/CanvasPixel.h b/src/render/software/CanvasPixel.h new file mode 100644 index 0000000..314e5b0 --- /dev/null +++ b/src/render/software/CanvasPixel.h @@ -0,0 +1,42 @@ +#ifndef CANVASPIXEL_H +#define CANVASPIXEL_H + +#include "software_global.h" + +#include "CanvasFragment.h" + +const int MAX_FRAGMENT_COUNT = 7; + +namespace paysages { +namespace software { + +/** + * @brief One pixel of a Canvas. + * + * A pixel stores superimposed fragments (CanvasFragment), sorted by their distance to camera. + */ +class SOFTWARESHARED_EXPORT CanvasPixel +{ +public: + CanvasPixel(); + + inline int getFragmentCount() const {return count;} + inline const Color &getComposite() const {return composite;} + inline const CanvasFragment &getFragment(int position) const {return fragments[position];} + const CanvasFragment *getFrontFragment() const; + + void reset(); + void pushFragment(const CanvasFragment &fragment); + void updateComposite(); + void setComposite(const Color &color); + +private: + int count; + CanvasFragment fragments[MAX_FRAGMENT_COUNT]; + Color composite; +}; + +} +} + +#endif // CANVASPIXEL_H diff --git a/src/render/software/CanvasPixelShader.cpp b/src/render/software/CanvasPixelShader.cpp new file mode 100644 index 0000000..ee8b9a9 --- /dev/null +++ b/src/render/software/CanvasPixelShader.cpp @@ -0,0 +1,63 @@ +#include "CanvasPixelShader.h" + +#include "Color.h" +#include "SoftwareCanvasRenderer.h" +#include "CanvasPortion.h" +#include "CanvasPixel.h" +#include "CanvasFragment.h" +#include "Rasterizer.h" + +CanvasPixelShader::CanvasPixelShader(const SoftwareCanvasRenderer &renderer, CanvasPortion *portion, int chunk_size, int sub_chunk_size, int chunks_x, int chunks_y): + renderer(renderer), portion(portion), chunk_size(chunk_size), sub_chunk_size(sub_chunk_size), chunks_x(chunks_x), chunks_y(chunks_y) +{ +} + +void CanvasPixelShader::processParallelUnit(int unit) +{ + // Locate the chunk we work on + int prev_sub_chunk_size = chunk_size * 2; + int chunk_x = unit / chunks_y; + int chunk_y = unit % chunks_y; + int base_x = chunk_x * chunk_size; + int base_y = chunk_y * chunk_size; + int limit_x = portion->getWidth() - base_x; + int limit_y = portion->getHeight() - base_y; + + limit_x = (limit_x > chunk_size) ? chunk_size : limit_x; + limit_y = (limit_y > chunk_size) ? chunk_size : limit_y; + + // Iterate on sub-chunks + for (int x = 0; x < limit_x; x += sub_chunk_size) + { + for (int y = 0; y < limit_y; y += sub_chunk_size) + { + if (interrupted) + { + return; + } + + if (sub_chunk_size == chunk_size or x % prev_sub_chunk_size != 0 or y % prev_sub_chunk_size != 0) + { + // Resolve the pixel color + const CanvasPixel &pixel = portion->at(base_x + x, base_y + y); + int n = pixel.getFragmentCount(); + Color composite = COLOR_BLACK; + for (int i = 0; i < n; i++) + { + const CanvasFragment &fragment = pixel.getFragment(i); + const Rasterizer &rasterizer = renderer.getRasterizer(fragment.getClient()); + composite.mask(rasterizer.shadeFragment(fragment)); + } + + // Fill the square area + for (int fx = 0; fx + x < limit_x and fx < sub_chunk_size; fx++) + { + for (int fy = 0; fy + y < limit_y and fy < sub_chunk_size; fy++) + { + portion->setColor(base_x + x + fx, base_y + y + fy, composite); + } + } + } + } + } +} diff --git a/src/render/software/CanvasPixelShader.h b/src/render/software/CanvasPixelShader.h new file mode 100644 index 0000000..76a6c75 --- /dev/null +++ b/src/render/software/CanvasPixelShader.h @@ -0,0 +1,37 @@ +#ifndef CANVASPIXELSHADER_H +#define CANVASPIXELSHADER_H + +#include "software_global.h" + +#include "ParallelWorker.h" + +namespace paysages { +namespace software { + +/** + * @brief Parallel worker that can work on canvas portion to resolve pixel colors. + * + * This is used after the rasterization phase to compute pixel colors from the fragments stored in them. + * + * This worker will be set to work on a given chunk of a canvas portion. + */ +class CanvasPixelShader: public ParallelWorker +{ +public: + CanvasPixelShader(const SoftwareCanvasRenderer &renderer, CanvasPortion *portion, int chunk_size, int sub_chunk_size, int chunks_x, int chunks_y); + + virtual void processParallelUnit(int unit) override; + +private: + const SoftwareCanvasRenderer &renderer; + CanvasPortion *portion; + int chunk_size; + int sub_chunk_size; + int chunks_x; + int chunks_y; +}; + +} +} + +#endif // CANVASPIXELSHADER_H diff --git a/src/render/software/CanvasPortion.cpp b/src/render/software/CanvasPortion.cpp new file mode 100644 index 0000000..805cb02 --- /dev/null +++ b/src/render/software/CanvasPortion.cpp @@ -0,0 +1,168 @@ +#include "CanvasPortion.h" + +#include + +#include "CanvasPixel.h" +#include "CanvasPreview.h" +#include "PackStream.h" +#include "FileSystem.h" + +#define CHECK_COORDINATES() assert(x >= 0); \ + assert(x < width); \ + assert(y >= 0); \ + assert(y < height); \ + assert(pixels != NULL) + +CanvasPortion::CanvasPortion(int index, CanvasPreview* preview): + index(index), preview(preview) +{ + width = 1; + height = 1; + xoffset = 0; + yoffset = 0; + pixels = NULL; +} + +CanvasPortion::~CanvasPortion() +{ + if (pixels) + { + delete[] pixels; + } +} + +int CanvasPortion::getFragmentCount(int x, int y) const +{ + CHECK_COORDINATES(); + + return pixels[y * width + x].getFragmentCount(); +} + +const CanvasFragment *CanvasPortion::getFrontFragment(int x, int y) const +{ + CHECK_COORDINATES(); + + return pixels[y * width + x].getFrontFragment(); +} + +void CanvasPortion::clear() +{ + int n = width * height; + for (int i = 0; i < n; i++) + { + pixels[i].reset(); + } +} + +void CanvasPortion::setSize(int width, int height, int xoffset, int yoffset) +{ + this->width = width; + this->height = height; + this->xoffset = xoffset; + this->yoffset = yoffset; +} + +void CanvasPortion::preparePixels() +{ + if (pixels) + { + delete[] pixels; + } + pixels = new CanvasPixel[width * height]; + clear(); +} + +void CanvasPortion::discardPixels(bool save) +{ + if (pixels) + { + if (save) + { + saveToDisk(); + } + delete[] pixels; + pixels = NULL; + } +} + +void CanvasPortion::saveToDisk() +{ + if (pixels) + { + filepath = FileSystem::getTempFile("paysages_portion_" + std::to_string(index) + ".dat"); + PackStream stream; + stream.bindToFile(filepath, true); + stream.write(&width); + stream.write(&height); + for (int y = 0; y < height; y++) + { + for (int x = 0; x < width; x++) + { + pixels[y * width + x].getComposite().save(&stream); + } + } + } +} + +bool CanvasPortion::getReadStream(PackStream &stream, int x, int y) +{ + if (FileSystem::isFile(filepath)) + { + if (not stream.bindToFile(filepath)) + { + return false; + } + + int unused_i; + stream.skip(unused_i, 2); + + if (x > 0 or y > 0) + { + double unused_d; + stream.skip(unused_d, (y * width + x) * 4); + } + + return true; + } + else + { + return false; + } +} + +void CanvasPortion::pushFragment(int x, int y, const CanvasFragment &fragment) +{ + CHECK_COORDINATES(); + + CanvasPixel &pixel = pixels[y * width + x]; + Color old_color = pixel.getComposite(); + + pixel.pushFragment(fragment); + + if (preview) + { + preview->pushPixel(xoffset + x, yoffset + y, old_color, pixel.getComposite()); + } +} + +const CanvasPixel &CanvasPortion::at(int x, int y) +{ + CHECK_COORDINATES(); + + return pixels[y * width + x]; +} + +void CanvasPortion::setColor(int x, int y, const Color &color) +{ + CHECK_COORDINATES(); + + CanvasPixel &pixel = pixels[y * width + x]; + Color old_color = pixel.getComposite(); + + pixel.setComposite(color); + + if (preview) + { + preview->pushPixel(xoffset + x, yoffset + y, old_color, pixel.getComposite()); + } +} diff --git a/src/render/software/CanvasPortion.h b/src/render/software/CanvasPortion.h new file mode 100644 index 0000000..1131e16 --- /dev/null +++ b/src/render/software/CanvasPortion.h @@ -0,0 +1,91 @@ +#ifndef CANVASPORTION_H +#define CANVASPORTION_H + +#include "software_global.h" + +namespace paysages { +namespace software { + +/** + * Rectangular portion of a Canvas. + * + * Contains the pixels of a canvas region (CanvasPixel). + * + * Pixels are not allocated until preparePixels is called. + */ +class SOFTWARESHARED_EXPORT CanvasPortion +{ +public: + CanvasPortion(int index=0, CanvasPreview *preview=NULL); + virtual ~CanvasPortion(); + + inline int getWidth() const {return width;} + inline int getHeight() const {return height;} + inline int getXOffset() const {return xoffset;} + inline int getYOffset() const {return yoffset;} + int getFragmentCount(int x, int y) const; + const CanvasFragment *getFrontFragment(int x, int y) const; + + void clear(); + void setSize(int width, int height, int xoffset=0, int yoffset=0); + + /** + * Prepare (allocate in memory) the pixels area. + */ + void preparePixels(); + + /** + * Discard the memory used by pixels. + * + * If save is true, the portion will be saved to disk before. + */ + void discardPixels(bool save=true); + + /** + * Save the portion to a picture file on disk. + */ + void saveToDisk(); + + /** + * Bind a stream to pixel data, and position it on a given pixel. + * + * Returns true if the stream was successfully located, false if it was not possible. + */ + bool getReadStream(PackStream &stream, int x=0, int y=0); + + /** + * Add a fragment to the pixel located at (x, y). + * + * Checking x and y coordinates to be in the canvas portion should be done before this call. + */ + void pushFragment(int x, int y, const CanvasFragment &fragment); + + /** + * Get the CanvasPixel at a given coordinates. + * + * Checking x and y coordinates to be in the canvas portion should be done before this call. + */ + const CanvasPixel &at(int x, int y); + + /** + * Change the final color of the pixel. + * + * Checking x and y coordinates to be in the canvas portion should be done before this call. + */ + void setColor(int x, int y, const Color &color); + +private: + int index; + int width; + int height; + int xoffset; + int yoffset; + CanvasPixel *pixels; + CanvasPreview *preview; + std::string filepath; +}; + +} +} + +#endif // CANVASPORTION_H diff --git a/src/render/software/CanvasPreview.cpp b/src/render/software/CanvasPreview.cpp new file mode 100644 index 0000000..6e49eec --- /dev/null +++ b/src/render/software/CanvasPreview.cpp @@ -0,0 +1,182 @@ +#include "CanvasPreview.h" + +#include "Color.h" +#include "CanvasLiveClient.h" +#include "Mutex.h" +#include "ColorProfile.h" + +#include + +#define CHECK_COORDINATES(_x_, _y_) \ + assert(_x_ >= 0); \ + assert(_y_ >= 0); \ + assert(_x_ < this->width); \ + assert(_y_ < this->height) \ + +CanvasPreview::CanvasPreview() +{ + width = 1; + height = 1; + pixels = new Color[1]; + + dirty_left = 1; + dirty_right = -1; + dirty_down = 1; + dirty_up = -1; + + scaled = false; + factor = 1.0; + factor_x = 1.0; + factor_y = 1.0; + + lock = new Mutex(); + profile = new ColorProfile(); +} + +CanvasPreview::~CanvasPreview() +{ + delete [] pixels; + delete lock; + delete profile; +} + +const Color &CanvasPreview::getFinalPixel(int x, int y) const +{ + CHECK_COORDINATES(x, y); + + return pixels[y * width + x]; +} + +void CanvasPreview::setSize(int real_width, int real_height, int preview_width, int preview_height) +{ + lock->acquire(); + + delete [] pixels; + pixels = new Color[preview_width * preview_height]; + + width = preview_width; + height = preview_height; + + dirty_left = width; + dirty_right = -1; + dirty_down = height; + dirty_up = -1; + + scaled = (real_width != preview_height or real_height != preview_height); + factor_x = (double)preview_width / (double)real_width; + factor_y = (double)preview_height / (double)real_height; + factor = factor_x * factor_y; + + lock->release(); + + reset(); +} + +void CanvasPreview::setToneMapping(const ColorProfile &profile) +{ + profile.copy(this->profile); + setAllDirty(); +} + +void CanvasPreview::reset() +{ + lock->acquire(); + + int n = width * height; + for (int i = 0; i < n; i++) + { + pixels[i] = COLOR_BLACK; + } + + lock->release(); +} + +void CanvasPreview::initLive(CanvasLiveClient *client) +{ + client->canvasResized(width, height); + client->canvasCleared(COLOR_BLACK); + + setAllDirty(); +} + +void CanvasPreview::updateLive(CanvasLiveClient *client) +{ + int x, y; + + lock->acquire(); + + for (y = dirty_down; y <= dirty_up; y++) + { + for (x = dirty_left; x <= dirty_right; x++) + { + client->canvasPainted(x, y, profile->apply(pixels[y * width + x])); + } + } + + dirty_left = width; + dirty_right = -1; + dirty_down = height; + dirty_up = -1; + + lock->release(); +} + +void CanvasPreview::pushPixel(int real_x, int real_y, const Color &old_color, const Color &new_color) +{ + int x, y; + + if (scaled) + { + x = int(real_x * factor_x); + y = int(real_y * factor_y); + + x = (x >= width) ? width - 1 : x; + y = (y >= height) ? height - 1 : y; + } + else + { + x = real_x; + y = real_y; + } + + CHECK_COORDINATES(x, y); + + lock->acquire(); + + Color* pixel = pixels + (y * width + x); + pixel->r = pixel->r - old_color.r * factor + new_color.r * factor; + pixel->g = pixel->g - old_color.g * factor + new_color.g * factor; + pixel->b = pixel->b - old_color.b * factor + new_color.b * factor; + + // Set pixel dirty + if (x < dirty_left) + { + dirty_left = x; + } + if (x > dirty_right) + { + dirty_right = x; + } + if (y < dirty_down) + { + dirty_down = y; + } + if (y > dirty_up) + { + dirty_up = y; + } + + lock->release(); +} + +void CanvasPreview::setAllDirty() +{ + lock->acquire(); + + dirty_left = 0; + dirty_right = width - 1; + dirty_down = 0; + dirty_up = height - 1; + + lock->release(); +} diff --git a/src/render/software/CanvasPreview.h b/src/render/software/CanvasPreview.h new file mode 100644 index 0000000..20f4a35 --- /dev/null +++ b/src/render/software/CanvasPreview.h @@ -0,0 +1,59 @@ +#ifndef CANVASPREVIEW_H +#define CANVASPREVIEW_H + +#include "software_global.h" + +namespace paysages { +namespace software { + +/** + * @brief Smaller preview of a Canvas rendering, that can be watched live. + */ +class SOFTWARESHARED_EXPORT CanvasPreview +{ +public: + CanvasPreview(); + ~CanvasPreview(); + + inline int getWidth() const {return width;} + inline int getHeight() const {return height;} + inline const ColorProfile *getToneMapping() const {return profile;} + + const Color &getFinalPixel(int x, int y) const; + + void setSize(int real_width, int real_height, int preview_width, int preview_height); + void setToneMapping(const ColorProfile &profile); + void reset(); + + void initLive(CanvasLiveClient *client); + void updateLive(CanvasLiveClient *client); + + void pushPixel(int real_x, int real_y, const Color &old_color, const Color &new_color); + +protected: + void setAllDirty(); + +private: + Mutex *lock; + + Color *pixels; + int width; + int height; + + ColorProfile *profile; + + int dirty_left; + int dirty_right; + int dirty_down; + int dirty_up; + + bool scaled; + double factor; + double factor_x; + double factor_y; +}; + +} +} + +#endif // CANVASPREVIEW_H diff --git a/src/render/software/Rasterizer.cpp b/src/render/software/Rasterizer.cpp new file mode 100644 index 0000000..ed8ff8f --- /dev/null +++ b/src/render/software/Rasterizer.cpp @@ -0,0 +1,373 @@ +#include "Rasterizer.h" + +#include "SoftwareRenderer.h" +#include "CameraDefinition.h" +#include "CanvasPortion.h" +#include "CanvasFragment.h" +#include "Vector3.h" + +struct paysages::software::ScanPoint +{ + int x; + int y; + struct { + double x; + double y; + double z; + } pixel; + struct { + double x; + double y; + double z; + } location; + int client; +}; + +struct paysages::software::RenderScanlines +{ + ScanPoint* up; + ScanPoint* down; + int left; + int right; +}; + +Rasterizer::Rasterizer(SoftwareRenderer* renderer, int client_id, const Color &color): + renderer(renderer), client_id(client_id) +{ + this->color = new Color(color); + + interrupted = false; + predicted_poly_count = 0; + done_poly_count = 0; +} + +Rasterizer::~Rasterizer() +{ + delete color; +} + +void Rasterizer::interrupt() +{ + interrupted = true; +} + +void Rasterizer::addPredictedPolys(int count) +{ + predicted_poly_count += count; +} + +void Rasterizer::addDonePolys(int count) +{ + done_poly_count += count; +} + +void Rasterizer::pushProjectedTriangle(CanvasPortion *canvas, const Vector3 &pixel1, const Vector3 &pixel2, const Vector3 &pixel3, const Vector3 &location1, const Vector3 &location2, const Vector3 &location3) +{ + ScanPoint point1, point2, point3; + double limit_width = (double)(canvas->getWidth() - 1); + double limit_height = (double)(canvas->getHeight() - 1); + + Vector3 canvas_offset(canvas->getXOffset(), canvas->getYOffset(), 0.0); + Vector3 dpixel1 = pixel1.sub(canvas_offset); + Vector3 dpixel2 = pixel2.sub(canvas_offset); + Vector3 dpixel3 = pixel3.sub(canvas_offset); + + /* Filter if outside screen */ + if (dpixel1.z < 1.0 || dpixel2.z < 1.0 || dpixel3.z < 1.0 || (dpixel1.x < 0.0 && dpixel2.x < 0.0 && dpixel3.x < 0.0) || (dpixel1.y < 0.0 && dpixel2.y < 0.0 && dpixel3.y < 0.0) || (dpixel1.x > limit_width && dpixel2.x > limit_width && dpixel3.x > limit_width) || (dpixel1.y > limit_height && dpixel2.y > limit_height && dpixel3.y > limit_height)) + { + return; + } + + /* Prepare vertices */ + point1.pixel.x = dpixel1.x; + point1.pixel.y = dpixel1.y; + point1.pixel.z = dpixel1.z; + point1.location.x = location1.x; + point1.location.y = location1.y; + point1.location.z = location1.z; + point1.client = client_id; + + point2.pixel.x = dpixel2.x; + point2.pixel.y = dpixel2.y; + point2.pixel.z = dpixel2.z; + point2.location.x = location2.x; + point2.location.y = location2.y; + point2.location.z = location2.z; + point2.client = client_id; + + point3.pixel.x = dpixel3.x; + point3.pixel.y = dpixel3.y; + point3.pixel.z = dpixel3.z; + point3.location.x = location3.x; + point3.location.y = location3.y; + point3.location.z = location3.z; + point3.client = client_id; + + /* Prepare scanlines */ + // TODO Don't create scanlines for each triangles (one by thread is more appropriate) + RenderScanlines scanlines; + int width = canvas->getWidth(); + scanlines.left = width; + scanlines.right = -1; + scanlines.up = new ScanPoint[width]; + scanlines.down = new ScanPoint[width]; + + /* Render edges in scanlines */ + pushScanLineEdge(canvas, &scanlines, &point1, &point2); + pushScanLineEdge(canvas, &scanlines, &point2, &point3); + pushScanLineEdge(canvas, &scanlines, &point3, &point1); + + /* Commit scanlines to area */ + renderScanLines(canvas, &scanlines); + + /* Free scalines */ + delete[] scanlines.up; + delete[] scanlines.down; +} + +void Rasterizer::pushTriangle(CanvasPortion *canvas, const Vector3 &v1, const Vector3 &v2, const Vector3 &v3) +{ + Vector3 p1, p2, p3; + + p1 = getRenderer()->projectPoint(v1); + p2 = getRenderer()->projectPoint(v2); + p3 = getRenderer()->projectPoint(v3); + + pushProjectedTriangle(canvas, p1, p2, p3, v1, v2, v3); +} + +void Rasterizer::pushQuad(CanvasPortion *canvas, const Vector3 &v1, const Vector3 &v2, const Vector3 &v3, const Vector3 &v4) +{ + pushTriangle(canvas, v2, v3, v1); + pushTriangle(canvas, v4, v1, v3); +} + +void Rasterizer::pushDisplacedTriangle(CanvasPortion *canvas, const Vector3 &v1, const Vector3 &v2, const Vector3 &v3, const Vector3 &ov1, const Vector3 &ov2, const Vector3 &ov3) +{ + Vector3 p1, p2, p3; + + p1 = getRenderer()->projectPoint(v1); + p2 = getRenderer()->projectPoint(v2); + p3 = getRenderer()->projectPoint(v3); + + pushProjectedTriangle(canvas, p1, p2, p3, ov1, ov2, ov3); +} + +void Rasterizer::pushDisplacedQuad(CanvasPortion *canvas, const Vector3 &v1, const Vector3 &v2, const Vector3 &v3, const Vector3 &v4, const Vector3 &ov1, const Vector3 &ov2, const Vector3 &ov3, const Vector3 &ov4) +{ + pushDisplacedTriangle(canvas, v2, v3, v1, ov2, ov3, ov1); + pushDisplacedTriangle(canvas, v4, v1, v3, ov4, ov1, ov3); +} + +void Rasterizer::scanGetDiff(ScanPoint* v1, ScanPoint* v2, ScanPoint* result) +{ + result->pixel.x = v2->pixel.x - v1->pixel.x; + result->pixel.y = v2->pixel.y - v1->pixel.y; + result->pixel.z = v2->pixel.z - v1->pixel.z; + result->location.x = v2->location.x - v1->location.x; + result->location.y = v2->location.y - v1->location.y; + result->location.z = v2->location.z - v1->location.z; + result->client = v1->client; +} + +void Rasterizer::scanInterpolate(CameraDefinition* camera, ScanPoint* v1, ScanPoint* diff, double value, ScanPoint* result) +{ + Vector3 vec1(v1->pixel.x, v1->pixel.y, v1->pixel.z); + Vector3 vecdiff(diff->pixel.x, diff->pixel.y, diff->pixel.z); + double v1depth = 1.0 / camera->getRealDepth(vec1); + double v2depth = 1.0 / camera->getRealDepth(vec1.add(vecdiff)); + double factor = 1.0 / ((1.0 - value) * v1depth + value * v2depth); + + result->pixel.x = v1->pixel.x + diff->pixel.x * value; + result->pixel.y = v1->pixel.y + diff->pixel.y * value; + result->pixel.z = v1->pixel.z + diff->pixel.z * value; + result->location.x = ((1.0 - value) * (v1->location.x * v1depth) + value * (v1->location.x + diff->location.x) * v2depth) * factor; + result->location.y = ((1.0 - value) * (v1->location.y * v1depth) + value * (v1->location.y + diff->location.y) * v2depth) * factor; + result->location.z = ((1.0 - value) * (v1->location.z * v1depth) + value * (v1->location.z + diff->location.z) * v2depth) * factor; + result->client = v1->client; +} + +void Rasterizer::pushScanPoint(CanvasPortion* canvas, RenderScanlines* scanlines, ScanPoint* point) +{ + point->x = (int)floor(point->pixel.x); + point->y = (int)floor(point->pixel.y); + + if (point->x < 0 || point->x >= canvas->getWidth()) + { + // Point outside scanline range + return; + } + else if (scanlines->right < 0) + { + // First point pushed + scanlines->left = point->x; + scanlines->right = point->x; + scanlines->up[point->x] = *point; + scanlines->down[point->x] = *point; + } + else if (point->x > scanlines->right) + { + // Grow scanlines to right + for (int x = scanlines->right + 1; x < point->x; x++) + { + scanlines->up[x].y = -1; + scanlines->down[x].y = canvas->getHeight(); + } + scanlines->right = point->x; + scanlines->up[point->x] = *point; + scanlines->down[point->x] = *point; + } + else if (point->x < scanlines->left) + { + // Grow scanlines to left + for (int x = point->x + 1; x < scanlines->left; x++) + { + scanlines->up[x].y = -1; + scanlines->down[x].y = canvas->getHeight(); + } + scanlines->left = point->x; + scanlines->up[point->x] = *point; + scanlines->down[point->x] = *point; + } + else + { + // Expand existing scanline + if (point->y > scanlines->up[point->x].y) + { + scanlines->up[point->x] = *point; + } + if (point->y < scanlines->down[point->x].y) + { + scanlines->down[point->x] = *point; + } + } +} + +void Rasterizer::pushScanLineEdge(CanvasPortion *canvas, RenderScanlines *scanlines, ScanPoint *point1, ScanPoint *point2) +{ + double dx, fx; + ScanPoint diff, point; + int startx = lround(point1->pixel.x); + int endx = lround(point2->pixel.x); + int curx; + + if (endx < startx) + { + pushScanLineEdge(canvas, scanlines, point2, point1); + } + else if (endx < 0 || startx >= canvas->getWidth()) + { + return; + } + else if (startx == endx) + { + pushScanPoint(canvas, scanlines, point1); + pushScanPoint(canvas, scanlines, point2); + } + else + { + if (startx < 0) + { + startx = 0; + } + if (endx >= canvas->getWidth()) + { + endx = canvas->getWidth() - 1; + } + + dx = point2->pixel.x - point1->pixel.x; + scanGetDiff(point1, point2, &diff); + for (curx = startx; curx <= endx; curx++) + { + fx = (double)curx + 0.5; + if (fx < point1->pixel.x) + { + fx = point1->pixel.x; + } + else if (fx > point2->pixel.x) + { + fx = point2->pixel.x; + } + fx = fx - point1->pixel.x; + scanInterpolate(renderer->render_camera, point1, &diff, fx / dx, &point); + + /*point.pixel.x = (double)curx;*/ + + pushScanPoint(canvas, scanlines, &point); + } + } +} + +void Rasterizer::renderScanLines(CanvasPortion *canvas, RenderScanlines* scanlines) +{ + int x, starty, endy, cury; + ScanPoint diff; + double dy, fy; + ScanPoint up, down, current; + + if (scanlines->right > 0) + { + for (x = scanlines->left; x <= scanlines->right; x++) + { + up = scanlines->up[x]; + down = scanlines->down[x]; + + starty = down.y; + endy = up.y; + + if (endy < 0 || starty >= canvas->getHeight()) + { + continue; + } + + if (starty < 0) + { + starty = 0; + } + if (endy >= canvas->getHeight()) + { + endy = canvas->getHeight() - 1; + } + + dy = up.pixel.y - down.pixel.y; + scanGetDiff(&down, &up, &diff); + + current.x = x; + for (cury = starty; cury <= endy; cury++) + { + if (dy == 0) + { + // Down and up are the same + current = down; + } + else + { + fy = (double)cury + 0.5; + if (fy < down.pixel.y) + { + fy = down.pixel.y; + } + else if (fy > up.pixel.y) + { + fy = up.pixel.y; + } + fy = fy - down.pixel.y; + + current.y = cury; + scanInterpolate(renderer->render_camera, &down, &diff, fy / dy, ¤t); + } + + CanvasFragment fragment(current.pixel.z, Vector3(current.location.x, current.location.y, current.location.z), current.client); + + Color frag_color = *color; + if (cury == starty || cury == endy) + { + frag_color.mask(Color(0.0, 0.0, 0.0, 0.3)); + } + fragment.setColor(frag_color); + + canvas->pushFragment(current.x, current.y, fragment); + } + } + } +} diff --git a/src/render/software/Rasterizer.h b/src/render/software/Rasterizer.h new file mode 100644 index 0000000..b633bc2 --- /dev/null +++ b/src/render/software/Rasterizer.h @@ -0,0 +1,57 @@ +#ifndef RASTERIZER_H +#define RASTERIZER_H + +#include "software_global.h" + +namespace paysages { +namespace software { + +typedef struct ScanPoint ScanPoint; +typedef struct RenderScanlines RenderScanlines; + +/** + * @brief Base abstract class for scenery pieces that can be rasterized to polygons. + */ +class SOFTWARESHARED_EXPORT Rasterizer +{ +public: + Rasterizer(SoftwareRenderer *renderer, int client_id, const Color &color); + virtual ~Rasterizer(); + + inline SoftwareRenderer *getRenderer() const {return renderer;} + + virtual void rasterizeToCanvas(CanvasPortion* canvas) = 0; + virtual Color shadeFragment(const CanvasFragment &fragment) const = 0; + virtual void interrupt(); + +protected: + void addPredictedPolys(int count=1); + void addDonePolys(int count=1); + + void pushProjectedTriangle(CanvasPortion *canvas, const Vector3 &pixel1, const Vector3 &pixel2, const Vector3 &pixel3, const Vector3 &location1, const Vector3 &location2, const Vector3 &location3); + + void pushTriangle(CanvasPortion *canvas, const Vector3 &v1, const Vector3 &v2, const Vector3 &v3); + void pushQuad(CanvasPortion *canvas, const Vector3 &v1, const Vector3 &v2, const Vector3 &v3, const Vector3 &v4); + void pushDisplacedTriangle(CanvasPortion *canvas, const Vector3 &v1, const Vector3 &v2, const Vector3 &v3, const Vector3 &ov1, const Vector3 &ov2, const Vector3 &ov3); + void pushDisplacedQuad(CanvasPortion *canvas, const Vector3 &v1, const Vector3 &v2, const Vector3 &v3, const Vector3 &v4, const Vector3 &ov1, const Vector3 &ov2, const Vector3 &ov3, const Vector3 &ov4); + + Color* color; + SoftwareRenderer *renderer; + int client_id; + bool interrupted; + +private: + void scanGetDiff(ScanPoint *v1, ScanPoint *v2, ScanPoint *result); + void scanInterpolate(CameraDefinition *camera, ScanPoint *v1, ScanPoint *diff, double value, ScanPoint *result); + void pushScanPoint(CanvasPortion *canvas, RenderScanlines *scanlines, ScanPoint *point); + void pushScanLineEdge(CanvasPortion *canvas, RenderScanlines *scanlines, ScanPoint *point1, ScanPoint *point2); + void renderScanLines(CanvasPortion *canvas, RenderScanlines *scanlines); + + int predicted_poly_count; + int done_poly_count; +}; + +} +} + +#endif // RASTERIZER_H diff --git a/src/render/software/RenderArea.cpp b/src/render/software/RenderArea.cpp deleted file mode 100644 index 26064ed..0000000 --- a/src/render/software/RenderArea.cpp +++ /dev/null @@ -1,792 +0,0 @@ -#include "RenderArea.h" - -#include "Vector3.h" -#include "ColorProfile.h" -#include "Mutex.h" -#include "CameraDefinition.h" -#include "SoftwareRenderer.h" -#include "Thread.h" -#include "PictureWriter.h" - -struct RenderFragment -{ - struct - { - unsigned char dirty : 1; - unsigned char edge : 1; - unsigned char callback : 6; - } flags; - union - { - struct { - double x; - double y; - double z; - } location; - struct { - double r; - double g; - double b; - } color; - } data; - double z; -}; - -typedef struct -{ - int x; - int y; - struct { - double x; - double y; - double z; - } pixel; - struct { - double x; - double y; - double z; - } location; - int callback; -} ScanPoint; - -struct FragmentCallback -{ - RenderArea::f_RenderFragmentCallback function; - void* data; -}; - -typedef struct -{ - ScanPoint* up; - ScanPoint* down; - int left; - int right; -} RenderScanlines; - -typedef struct -{ - int startx; - int endx; - int starty; - int endy; - int finished; - int interrupt; - int pixel_done; - Thread* thread; - RenderArea* area; -} RenderChunk; - -static void _callbackStart(int, int, const Color&) {} -static void _callbackDraw(int, int, const Color&) {} -static void _callbackUpdate(double) {} - -RenderArea::RenderArea(SoftwareRenderer* renderer) -{ - this->renderer = renderer; - this->hdr_mapping = new ColorProfile; - this->params.width = 1; - this->params.height = 1; - this->params.antialias = 1; - this->params.quality = 5; - this->pixel_count = 1; - this->pixels = new RenderFragment[1]; - this->fragment_callbacks_count = 0; - this->fragment_callbacks = new FragmentCallback[64]; - this->background_color = COLOR_TRANSPARENT; - this->dirty_left = 1; - this->dirty_right = -1; - this->dirty_down = 1; - this->dirty_up = -1; - this->dirty_count = 0; - this->lock = new Mutex(); - this->callback_start = _callbackStart; - this->callback_draw = _callbackDraw; - this->callback_update = _callbackUpdate; -} - -RenderArea::~RenderArea() -{ - delete hdr_mapping; - delete lock; - delete[] fragment_callbacks; - delete[] pixels; -} - -void RenderArea::setAllDirty() -{ - dirty_left = 0; - dirty_right = params.width * params.antialias - 1; - dirty_down = 0; - dirty_up = params.height * params.antialias - 1; -} - -void RenderArea::setParams(RenderParams params) -{ - int width, height; - - width = params.width * params.antialias; - height = params.height * params.antialias; - - this->params = params; - delete[] pixels; - pixels = new RenderFragment[width * height]; - pixel_count = width * height; - - dirty_left = width; - dirty_right = -1; - dirty_down = height; - dirty_up = -1; - dirty_count = 0; - - clear(); -} - -void RenderArea::setToneMapping(const ColorProfile &profile) -{ - profile.copy(hdr_mapping); - setAllDirty(); - update(); -} - -void RenderArea::setBackgroundColor(const Color &col) -{ - background_color = col; -} - -void RenderArea::clear() -{ - RenderFragment* pixel; - int x; - int y; - - fragment_callbacks_count = 1; - fragment_callbacks[0].function = NULL; - fragment_callbacks[0].data = NULL; - - for (x = 0; x < params.width * params.antialias; x++) - { - for (y = 0; y < params.height * params.antialias; y++) - { - pixel = pixels + (y * params.width * params.antialias + x); - pixel->z = -100000000.0; - pixel->flags.dirty = 0; - pixel->flags.callback = 0; - pixel->data.color.r = background_color.r; - pixel->data.color.g = background_color.g; - pixel->data.color.b = background_color.b; - } - } - - callback_start(params.width, params.height, background_color); - - dirty_left = params.width * params.antialias; - dirty_right = -1; - dirty_down = params.height * params.antialias; - dirty_up = -1; - dirty_count = 0; -} - -static inline void _setDirtyPixel(RenderArea* area, int x, int y) -{ - if (x < area->dirty_left) - { - area->dirty_left = x; - } - if (x > area->dirty_right) - { - area->dirty_right = x; - } - if (y < area->dirty_down) - { - area->dirty_down = y; - } - if (y > area->dirty_up) - { - area->dirty_up = y; - } - - area->dirty_count++; -} - -static inline Color _getFinalPixel(RenderArea* area, int x, int y) -{ - Color result, col; - int sx, sy; - double factor = 1.0 / (double)(area->params.antialias * area->params.antialias); - RenderFragment* pixel_data; - - result.r = result.g = result.b = 0.0; - result.a = 1.0; - for (sx = 0; sx < area->params.antialias; sx++) - { - for (sy = 0; sy < area->params.antialias; sy++) - { - pixel_data = area->pixels + (y * area->params.antialias + sy) * area->params.width * area->params.antialias + (x * area->params.antialias + sx); - if (pixel_data->flags.dirty) - { - if (pixel_data->flags.edge) - { - col = COLOR_GREY; - } - else - { - col = COLOR_WHITE; - } - } - else - { - col.r = pixel_data->data.color.r; - col.g = pixel_data->data.color.g; - col.b = pixel_data->data.color.b; - } - result.r += col.r * factor; - result.g += col.g * factor; - result.b += col.b * factor; - - } - } - - return area->hdr_mapping->apply(result); -} - -void RenderArea::processDirtyPixels() -{ - int x, y; - int down, up, left, right; - - down = dirty_down / params.antialias; - up = dirty_up / params.antialias; - left = dirty_left / params.antialias; - right = dirty_right / params.antialias; - - for (y = down; y <= up; y++) - { - for (x = left; x <= right; x++) - { - callback_draw(x, y, _getFinalPixel(this, x, y)); - } - } - - callback_update(renderer->render_progress); - - dirty_left = params.width * params.antialias; - dirty_right = -1; - dirty_down = params.height * params.antialias; - dirty_up = -1; - dirty_count = 0; -} - -void RenderArea::update() -{ - lock->acquire(); - processDirtyPixels(); - lock->release(); -} - -static inline unsigned int _pushCallback(RenderArea* area, FragmentCallback callback) -{ - int i; - for (i = 0; i < area->fragment_callbacks_count; i++) - { - if (area->fragment_callbacks[i].function == callback.function && area->fragment_callbacks[i].data == callback.data) - { - return i; - } - } - - if (area->fragment_callbacks_count >= 64) - { - return 0; - } - else - { - area->fragment_callbacks[area->fragment_callbacks_count].function = callback.function; - area->fragment_callbacks[area->fragment_callbacks_count].data = callback.data; - return area->fragment_callbacks_count++; - } -} - -void RenderArea::pushFragment(int x, int y, double z, int edge, const Vector3 &location, int callback) -{ - RenderFragment* pixel_data; - - if (x >= 0 && x < params.width * params.antialias && y >= 0 && y < params.height * params.antialias && z > 1.0) - { - pixel_data = pixels + (y * params.width * params.antialias + x); - - if (z > pixel_data->z) - { - pixel_data->flags.dirty = (unsigned char)1; - pixel_data->flags.edge = (unsigned char)edge; - pixel_data->flags.callback = (unsigned char)callback; - pixel_data->data.location.x = location.x; - pixel_data->data.location.y = location.y; - pixel_data->data.location.z = location.z; - pixel_data->z = z; - _setDirtyPixel(this, x, y); - } - } -} - -static void _scanGetDiff(ScanPoint* v1, ScanPoint* v2, ScanPoint* result) -{ - result->pixel.x = v2->pixel.x - v1->pixel.x; - result->pixel.y = v2->pixel.y - v1->pixel.y; - result->pixel.z = v2->pixel.z - v1->pixel.z; - result->location.x = v2->location.x - v1->location.x; - result->location.y = v2->location.y - v1->location.y; - result->location.z = v2->location.z - v1->location.z; - result->callback = v1->callback; -} - -static void _scanInterpolate(CameraDefinition* camera, ScanPoint* v1, ScanPoint* diff, double value, ScanPoint* result) -{ - Vector3 vec1(v1->pixel.x, v1->pixel.y, v1->pixel.z); - Vector3 vecdiff(diff->pixel.x, diff->pixel.y, diff->pixel.z); - double v1depth = camera->getRealDepth(vec1); - double v2depth = camera->getRealDepth(vec1.add(vecdiff)); - double factor = ((1.0 - value) / v1depth + value / v2depth); - - result->pixel.x = v1->pixel.x + diff->pixel.x * value; - result->pixel.y = v1->pixel.y + diff->pixel.y * value; - result->pixel.z = v1->pixel.z + diff->pixel.z * value; - result->location.x = ((1.0 - value) * (v1->location.x / v1depth) + value * (v1->location.x + diff->location.x) / v2depth) / factor; - result->location.y = ((1.0 - value) * (v1->location.y / v1depth) + value * (v1->location.y + diff->location.y) / v2depth) / factor; - result->location.z = ((1.0 - value) * (v1->location.z / v1depth) + value * (v1->location.z + diff->location.z) / v2depth) / factor; - result->callback = v1->callback; -} - -static void _pushScanPoint(RenderArea* area, RenderScanlines* scanlines, ScanPoint* point) -{ - point->x = (int)floor(point->pixel.x); - point->y = (int)floor(point->pixel.y); - - if (point->x < 0 || point->x >= area->params.width * area->params.antialias) - { - // Point outside scanline range - return; - } - else if (scanlines->right < 0) - { - // First point pushed - scanlines->left = point->x; - scanlines->right = point->x; - scanlines->up[point->x] = *point; - scanlines->down[point->x] = *point; - } - else if (point->x > scanlines->right) - { - // Grow scanlines to right - for (int x = scanlines->right + 1; x < point->x; x++) - { - scanlines->up[x].y = -1; - scanlines->down[x].y = area->params.height * area->params.antialias; - } - scanlines->right = point->x; - scanlines->up[point->x] = *point; - scanlines->down[point->x] = *point; - } - else if (point->x < scanlines->left) - { - // Grow scanlines to left - for (int x = point->x + 1; x < scanlines->left; x++) - { - scanlines->up[x].y = -1; - scanlines->down[x].y = area->params.height * area->params.antialias; - } - scanlines->left = point->x; - scanlines->up[point->x] = *point; - scanlines->down[point->x] = *point; - } - else - { - // Expand existing scanline - if (point->y > scanlines->up[point->x].y) - { - scanlines->up[point->x] = *point; - } - if (point->y < scanlines->down[point->x].y) - { - scanlines->down[point->x] = *point; - } - } -} - -static void _pushScanLineEdge(RenderArea* area, RenderScanlines* scanlines, ScanPoint* point1, ScanPoint* point2) -{ - double dx, fx; - ScanPoint diff, point; - int startx = lround(point1->pixel.x); - int endx = lround(point2->pixel.x); - int curx; - - if (endx < startx) - { - _pushScanLineEdge(area, scanlines, point2, point1); - } - else if (endx < 0 || startx >= area->params.width * area->params.antialias) - { - return; - } - else if (startx == endx) - { - _pushScanPoint(area, scanlines, point1); - _pushScanPoint(area, scanlines, point2); - } - else - { - if (startx < 0) - { - startx = 0; - } - if (endx >= area->params.width * area->params.antialias) - { - endx = area->params.width * area->params.antialias - 1; - } - - dx = point2->pixel.x - point1->pixel.x; - _scanGetDiff(point1, point2, &diff); - for (curx = startx; curx <= endx; curx++) - { - fx = (double)curx + 0.5; - if (fx < point1->pixel.x) - { - fx = point1->pixel.x; - } - else if (fx > point2->pixel.x) - { - fx = point2->pixel.x; - } - fx = fx - point1->pixel.x; - _scanInterpolate(area->renderer->render_camera, point1, &diff, fx / dx, &point); - - /*point.pixel.x = (double)curx;*/ - - _pushScanPoint(area, scanlines, &point); - } - } -} - -static void _renderScanLines(RenderArea* area, RenderScanlines* scanlines) -{ - int x, starty, endy, cury; - ScanPoint diff; - double dy, fy; - ScanPoint up, down, current; - - if (scanlines->right > 0) - { - for (x = scanlines->left; x <= scanlines->right; x++) - { - up = scanlines->up[x]; - down = scanlines->down[x]; - - starty = down.y; - endy = up.y; - - if (endy < 0 || starty >= area->params.height * area->params.antialias) - { - continue; - } - - if (starty < 0) - { - starty = 0; - } - if (endy >= area->params.height * area->params.antialias) - { - endy = area->params.height * area->params.antialias - 1; - } - - dy = up.pixel.y - down.pixel.y; - _scanGetDiff(&down, &up, &diff); - - current.x = x; - for (cury = starty; cury <= endy; cury++) - { - fy = (double)cury + 0.5; - if (fy < down.pixel.y) - { - fy = down.pixel.y; - } - else if (fy > up.pixel.y) - { - fy = up.pixel.y; - } - fy = fy - down.pixel.y; - - current.y = cury; - _scanInterpolate(area->renderer->render_camera, &down, &diff, fy / dy, ¤t); - - Vector3 veclocation = Vector3(current.location.x, current.location.y, current.location.z); - area->pushFragment(current.x, current.y, current.pixel.z, (cury == starty || cury == endy), veclocation, current.callback); - } - } - } -} - -void RenderArea::pushTriangle(const Vector3 &pixel1, const Vector3 &pixel2, const Vector3 &pixel3, const Vector3 &location1, const Vector3 &location2, const Vector3 &location3, f_RenderFragmentCallback callback, void* callback_data) -{ - FragmentCallback fragment_callback = {callback, callback_data}; - ScanPoint point1, point2, point3; - double limit_width = (double)(params.width * params.antialias - 1); - double limit_height = (double)(params.height * params.antialias - 1); - - /* Filter if outside screen */ - if (pixel1.z < 1.0 || pixel2.z < 1.0 || pixel3.z < 1.0 || (pixel1.x < 0.0 && pixel2.x < 0.0 && pixel3.x < 0.0) || (pixel1.y < 0.0 && pixel2.y < 0.0 && pixel3.y < 0.0) || (pixel1.x > limit_width && pixel2.x > limit_width && pixel3.x > limit_width) || (pixel1.y > limit_height && pixel2.y > limit_height && pixel3.y > limit_height)) - { - return; - } - - /* Prepare fragment callback */ - lock->acquire(); - point1.callback = _pushCallback(this, fragment_callback); - lock->release(); - - /* Prepare vertices */ - point1.pixel.x = pixel1.x; - point1.pixel.y = pixel1.y; - point1.pixel.z = pixel1.z; - point1.location.x = location1.x; - point1.location.y = location1.y; - point1.location.z = location1.z; - - point2.pixel.x = pixel2.x; - point2.pixel.y = pixel2.y; - point2.pixel.z = pixel2.z; - point2.location.x = location2.x; - point2.location.y = location2.y; - point2.location.z = location2.z; - point2.callback = point1.callback; - - point3.pixel.x = pixel3.x; - point3.pixel.y = pixel3.y; - point3.pixel.z = pixel3.z; - point3.location.x = location3.x; - point3.location.y = location3.y; - point3.location.z = location3.z; - point3.callback = point1.callback; - - /* Prepare scanlines */ - // TODO Don't create scanlines for each triangles (one by thread is more appropriate) - RenderScanlines scanlines; - int width = params.width * params.antialias; - scanlines.left = width; - scanlines.right = -1; - scanlines.up = new ScanPoint[width]; - scanlines.down = new ScanPoint[width]; - - /* Render edges in scanlines */ - _pushScanLineEdge(this, &scanlines, &point1, &point2); - _pushScanLineEdge(this, &scanlines, &point2, &point3); - _pushScanLineEdge(this, &scanlines, &point3, &point1); - - /* Commit scanlines to area */ - lock->acquire(); - _renderScanLines(this, &scanlines); - lock->release(); - - /* Free scalines */ - delete[] scanlines.up; - delete[] scanlines.down; -} - -Color RenderArea::getPixel(int x, int y) -{ - Color result; - - lock->acquire(); - result = _getFinalPixel(this, x, y); - lock->release(); - - return result; -} - -void* _renderPostProcessChunk(void* data) -{ - int x, y; - RenderFragment* fragment; - RenderChunk* chunk = (RenderChunk*)data; - - for (x = chunk->startx; x <= chunk->endx; x++) -{ - for (y = chunk->starty; y <= chunk->endy; y++) - { - fragment = chunk->area->pixels + (y * chunk->area->params.width * chunk->area->params.antialias + x); - if (fragment->flags.dirty) - { - FragmentCallback callback; - Color col; - - callback = chunk->area->fragment_callbacks[fragment->flags.callback]; - if (callback.function) - { - Vector3 location(fragment->data.location.x, fragment->data.location.y, fragment->data.location.z); - col = callback.function(chunk->area->renderer, location, callback.data); - /*colorNormalize(&col);*/ - } - else - { - col = COLOR_BLACK; - } - - fragment->data.color.r = col.r; - fragment->data.color.g = col.g; - fragment->data.color.b = col.b; - - chunk->area->lock->acquire(); - fragment->flags.dirty = 0; - _setDirtyPixel(chunk->area, x, y); - chunk->area->lock->release(); - } - chunk->area->pixel_done++; - } - if (chunk->interrupt) - { - break; - } - } - - chunk->finished = 1; - return NULL; -} - -#define MAX_CHUNKS 8 -void RenderArea::postProcess(int nbchunks) -{ - volatile RenderChunk chunks[MAX_CHUNKS]; - int i; - int x, y, dx, dy, nx, ny; - int loops, running; - - if (nbchunks > MAX_CHUNKS) - { - nbchunks = MAX_CHUNKS; - } - if (nbchunks < 1) - { - nbchunks = 1; - } - - nx = 10; - ny = 10; - dx = params.width * params.antialias / nx; - dy = params.height * params.antialias / ny; - x = 0; - y = 0; - pixel_done = 0; - - for (i = 0; i < nbchunks; i++) - { - chunks[i].thread = NULL; - chunks[i].area = this; - } - - running = 0; - loops = 0; - while ((x < nx && !renderer->render_interrupt) || running > 0) - { - Thread::timeSleepMs(50); - - for (i = 0; i < nbchunks; i++) - { - if (chunks[i].thread) - { - if (chunks[i].finished) - { - chunks[i].thread->join(); - delete chunks[i].thread; - chunks[i].thread = NULL; - running--; - } - else if (renderer->render_interrupt) - { - chunks[i].interrupt = 1; - } - } - - renderer->render_progress = 0.1 + ((double)pixel_done / (double)pixel_count) * 0.9; - - if (x < nx && !chunks[i].thread && !renderer->render_interrupt) - { - chunks[i].finished = 0; - chunks[i].interrupt = 0; - chunks[i].startx = x * dx; - if (x == nx - 1) - { - chunks[i].endx = params.width * params.antialias - 1; - } - else - { - chunks[i].endx = (x + 1) * dx - 1; - } - chunks[i].starty = y * dy; - if (y == ny - 1) - { - chunks[i].endy = params.height * params.antialias - 1; - } - else - { - chunks[i].endy = (y + 1) * dy - 1; - } - - chunks[i].thread = new Thread(_renderPostProcessChunk); - chunks[i].thread->start((void*)(chunks + i)); - running++; - - if (++y >= ny) - { - x++; - y = 0; - } - } - } - - if (++loops >= 10) - { - lock->acquire(); - processDirtyPixels(); - lock->release(); - - loops = 0; - } - } - - processDirtyPixels(); - callback_update(1.0); -} - -class RenderWriter:public PictureWriter -{ -public: - RenderWriter(RenderArea *area): area(area) {} - - virtual unsigned int getPixel(int x, int y) override - { - Color result = _getFinalPixel(area, x, y); - result.normalize(); - return result.to32BitBGRA(); - } -private: - RenderArea *area; -}; - -int RenderArea::saveToFile(const std::string &path) -{ - RenderWriter writer(this); - return writer.save(path, params.width, params.height); -} - -void RenderArea::setPreviewCallbacks(RenderCallbackStart start, RenderCallbackDraw draw, RenderCallbackUpdate update) -{ - callback_start = start ? start : _callbackStart; - callback_draw = draw ? draw : _callbackDraw; - callback_update = update ? update : _callbackUpdate; - - callback_start(params.width, params.height, background_color); - - setAllDirty(); - processDirtyPixels(); - - callback_update(0.0); -} diff --git a/src/render/software/RenderArea.h b/src/render/software/RenderArea.h deleted file mode 100644 index 8bcb826..0000000 --- a/src/render/software/RenderArea.h +++ /dev/null @@ -1,76 +0,0 @@ -#ifndef RENDERAREA_H -#define RENDERAREA_H - -#include "software_global.h" - -#include "Color.h" - -typedef struct RenderFragment RenderFragment; -typedef struct FragmentCallback FragmentCallback; - -namespace paysages { -namespace software { - -class SOFTWARESHARED_EXPORT RenderArea -{ -public: - typedef struct - { - int width; - int height; - int antialias; - int quality; - } RenderParams; - - typedef Color (*f_RenderFragmentCallback)(SoftwareRenderer* renderer, const Vector3 &location, void* data); - typedef void (*RenderCallbackStart)(int width, int height, const Color &background); - typedef void (*RenderCallbackDraw)(int x, int y, const Color &col); - typedef void (*RenderCallbackUpdate)(double progress); - -public: - RenderArea(SoftwareRenderer* parent); - ~RenderArea(); - - void setParams(RenderParams params); - void setToneMapping(const ColorProfile &profile); - void setBackgroundColor(const Color& col); - void clear(); - void update(); - - void pushTriangle(const Vector3 &pixel1, const Vector3 &pixel2, const Vector3 &pixel3, const Vector3 &location1, const Vector3 &location2, const Vector3 &location3, f_RenderFragmentCallback callback, void* callback_data); - - Color getPixel(int x, int y); - - void postProcess(int nbchunks); - int saveToFile(const std::string &path); - void setPreviewCallbacks(RenderCallbackStart start, RenderCallbackDraw draw, RenderCallbackUpdate update); - - void setAllDirty(); - void processDirtyPixels(); - void pushFragment(int x, int y, double z, int edge, const Vector3 &location, int callback); - -public: - ColorProfile* hdr_mapping; - SoftwareRenderer* renderer; - RenderParams params; - int pixel_count; - int pixel_done; - RenderFragment* pixels; - int fragment_callbacks_count; - FragmentCallback* fragment_callbacks; - Color background_color; - volatile int dirty_left; - volatile int dirty_right; - volatile int dirty_up; - volatile int dirty_down; - volatile int dirty_count; - Mutex* lock; - RenderCallbackStart callback_start; - RenderCallbackDraw callback_draw; - RenderCallbackUpdate callback_update; -}; - -} -} - -#endif // RENDERAREA_H diff --git a/src/render/software/RenderConfig.cpp b/src/render/software/RenderConfig.cpp new file mode 100644 index 0000000..c4f67ca --- /dev/null +++ b/src/render/software/RenderConfig.cpp @@ -0,0 +1,30 @@ +#include "RenderConfig.h" + +RenderConfig::RenderConfig(int width, int height, int antialias, int quality): + width(width), height(height), antialias(antialias), quality(quality) +{ + if (this->width <= 0) + { + this->width = 400; + } + if (this->height <= 0) + { + this->height = this->width * 4 / 3; + } + if (this->antialias < 1) + { + this->antialias = 1; + } + if (this->antialias > 4) + { + this->antialias = 4; + } + if (this->quality < 1) + { + this->quality = 1; + } + if (this->quality > 10) + { + this->quality = 10; + } +} diff --git a/src/render/software/RenderConfig.h b/src/render/software/RenderConfig.h new file mode 100644 index 0000000..558b5b3 --- /dev/null +++ b/src/render/software/RenderConfig.h @@ -0,0 +1,23 @@ +#ifndef RENDERCONFIG_H +#define RENDERCONFIG_H + +#include "software_global.h" + +namespace paysages { +namespace software { + +class SOFTWARESHARED_EXPORT RenderConfig +{ +public: + RenderConfig(int width=0, int height=0, int antialias=1, int quality=5); + + int width; + int height; + int antialias; + int quality; +}; + +} +} + +#endif // RENDERCONFIG_H diff --git a/src/render/software/SkyRasterizer.cpp b/src/render/software/SkyRasterizer.cpp index 226e070..57866ae 100644 --- a/src/render/software/SkyRasterizer.cpp +++ b/src/render/software/SkyRasterizer.cpp @@ -6,30 +6,17 @@ #include "AtmosphereRenderer.h" #include "AtmosphereResult.h" #include "CloudsRenderer.h" +#include "Rasterizer.h" +#include "CanvasFragment.h" #define SPHERE_SIZE 20000.0 -SkyRasterizer::SkyRasterizer(SoftwareRenderer* renderer): - renderer(renderer) +SkyRasterizer::SkyRasterizer(SoftwareRenderer* renderer, int client_id): + Rasterizer(renderer, client_id, Color(0.9, 0.9, 1.0)) { } -static Color _postProcessFragment(SoftwareRenderer* renderer, const Vector3 &location, void*) -{ - Vector3 camera_location, direction; - Color result; - - camera_location = renderer->getCameraLocation(location); - direction = location.sub(camera_location); - - /* TODO Don't compute result->color if it's fully covered by clouds */ - result = renderer->getAtmosphereRenderer()->getSkyColor(direction.normalize()).final; - result = renderer->getCloudsRenderer()->getColor(camera_location, camera_location.add(direction.scale(10.0)), result); - - return result; -} - -void SkyRasterizer::rasterize() +void SkyRasterizer::rasterizeToCanvas(CanvasPortion* canvas) { int res_i, res_j; int i, j; @@ -47,7 +34,7 @@ void SkyRasterizer::rasterize() for (j = 0; j < res_j; j++) { - if (!renderer->addRenderProgress(0.0)) + if (interrupted) { return; } @@ -79,7 +66,23 @@ void SkyRasterizer::rasterize() vertex4 = camera_location.add(direction); /* TODO Triangles at poles */ - renderer->pushQuad(vertex1, vertex4, vertex3, vertex2, _postProcessFragment, NULL); + pushQuad(canvas, vertex1, vertex4, vertex3, vertex2); } } } + +Color SkyRasterizer::shadeFragment(const CanvasFragment &fragment) const +{ + Vector3 location = fragment.getLocation(); + Vector3 camera_location, direction; + Color result; + + camera_location = renderer->getCameraLocation(location); + direction = location.sub(camera_location); + + /* TODO Don't compute result->color if it's fully covered by clouds */ + result = renderer->getAtmosphereRenderer()->getSkyColor(direction.normalize()).final; + result = renderer->getCloudsRenderer()->getColor(camera_location, camera_location.add(direction.scale(10.0)), result); + + return result; +} diff --git a/src/render/software/SkyRasterizer.h b/src/render/software/SkyRasterizer.h index 1d03902..39b09ab 100644 --- a/src/render/software/SkyRasterizer.h +++ b/src/render/software/SkyRasterizer.h @@ -3,17 +3,18 @@ #include "software_global.h" +#include "Rasterizer.h" + namespace paysages { namespace software { -class SOFTWARESHARED_EXPORT SkyRasterizer +class SOFTWARESHARED_EXPORT SkyRasterizer: public Rasterizer { public: - SkyRasterizer(SoftwareRenderer* renderer); - void rasterize(); + SkyRasterizer(SoftwareRenderer* renderer, int client_id); -private: - SoftwareRenderer* renderer; + virtual void rasterizeToCanvas(CanvasPortion* canvas) override; + virtual Color shadeFragment(const CanvasFragment &fragment) const override; }; } diff --git a/src/render/software/SoftwareCanvasRenderer.cpp b/src/render/software/SoftwareCanvasRenderer.cpp new file mode 100644 index 0000000..922b12d --- /dev/null +++ b/src/render/software/SoftwareCanvasRenderer.cpp @@ -0,0 +1,164 @@ +#include "SoftwareCanvasRenderer.h" + +#include "Rasterizer.h" +#include "SoftwareRenderer.h" +#include "Canvas.h" +#include "TerrainRasterizer.h" +#include "WaterRasterizer.h" +#include "SkyRasterizer.h" +#include "CameraDefinition.h" +#include "ParallelWork.h" +#include "CanvasPortion.h" +#include "CanvasPixelShader.h" +#include "RenderConfig.h" +#include "ColorProfile.h" +#include "CanvasPreview.h" + +SoftwareCanvasRenderer::SoftwareCanvasRenderer() +{ + started = false; + interrupted = false; + canvas = new Canvas(); + progress = 0.0; + samples = 1; + + rasterizers.push_back(new SkyRasterizer(this, 0)); + rasterizers.push_back(new WaterRasterizer(this, 1)); + rasterizers.push_back(new TerrainRasterizer(this, 2)); + + current_work = NULL; +} + +SoftwareCanvasRenderer::~SoftwareCanvasRenderer() +{ + delete canvas; + + for (auto &rasterizer: rasterizers) + { + delete rasterizer; + } +} + +void SoftwareCanvasRenderer::setConfig(const RenderConfig &config) +{ + if (not started) + { + setSize(config.width, config.height, config.antialias); + render_quality = config.quality; + } +} + +void SoftwareCanvasRenderer::setSize(int width, int height, int samples) +{ + if (not started) + { + canvas->setSize(width * samples, height * samples); + this->samples = samples; + } +} + +void SoftwareCanvasRenderer::render() +{ + started = true; + progress = 0.0; + + render_camera->setRenderSize(canvas->getWidth(), canvas->getHeight()); + + prepare(); + + // Iterate portions + int nx = canvas->getHorizontalPortionCount(); + int ny = canvas->getVerticalPortionCount(); + int i = 0; + int n = nx * ny; + for (int y = 0; y < ny; y++) + { + for (int x = 0; x < nx; x++) + { + CanvasPortion *portion = canvas->at(x, y); + + if (not interrupted) + { + progress_segment = 0.2 / (double)n; + portion->preparePixels(); + rasterize(portion); + } + + if (not interrupted) + { + progress_segment = 0.8 / (double)n; + applyPixelShader(portion); + } + + portion->discardPixels(); + i++; + progress = (double)i / (double)n; + } + } +} + +void SoftwareCanvasRenderer::interrupt() +{ + interrupted = true; + + if (current_work) + { + current_work->interrupt(); + } + for (auto &rasterizer:rasterizers) + { + rasterizer->interrupt(); + } +} + +const Rasterizer &SoftwareCanvasRenderer::getRasterizer(int client_id) const +{ + return *(rasterizers[client_id]); +} + +bool SoftwareCanvasRenderer::saveToDisk(const std::string &filepath) const +{ + return getCanvas()->saveToDisk(filepath, *getCanvas()->getPreview()->getToneMapping(), samples); +} + +void SoftwareCanvasRenderer::rasterize(CanvasPortion *portion) +{ + for (auto &rasterizer:rasterizers) + { + rasterizer->rasterizeToCanvas(portion); + progress += progress_segment / (double)rasterizers.size(); + } +} + +void SoftwareCanvasRenderer::applyPixelShader(CanvasPortion *portion) +{ + // Subdivide in chunks + int chunk_size = 64; + int chunks_x = (portion->getWidth() - 1) / chunk_size + 1; + int chunks_y = (portion->getHeight() - 1) / chunk_size + 1; + int units = chunks_x * chunks_y; + + // Estimate chunks + int n = 0; + for (int sub_chunk_size = chunk_size; sub_chunk_size >= 1; sub_chunk_size /= 2) + { + n += chunk_size / sub_chunk_size; + } + + // Render chunks in parallel + for (int sub_chunk_size = chunk_size; sub_chunk_size >= 1; sub_chunk_size /= 2) + { + if (interrupted) + { + break; + } + + CanvasPixelShader shader(*this, portion, chunk_size, sub_chunk_size, chunks_x, chunks_y); + ParallelWork work(&shader, units); + + current_work = &work; + work.perform(); + current_work = NULL; + progress += progress_segment * (double)(chunk_size / sub_chunk_size) / (double)n; + } +} diff --git a/src/render/software/SoftwareCanvasRenderer.h b/src/render/software/SoftwareCanvasRenderer.h new file mode 100644 index 0000000..e157248 --- /dev/null +++ b/src/render/software/SoftwareCanvasRenderer.h @@ -0,0 +1,89 @@ +#ifndef SOFTWARECANVASRENDERER_H +#define SOFTWARECANVASRENDERER_H + +#include "software_global.h" + +#include "SoftwareRenderer.h" + +namespace paysages { +namespace software { + +/** + * @brief Software rendering inside a Canvas surface. + * + * This class launches the rasterization process into canvas portions and + * redirects post processing to the software renderer. + * + * It tries to keep a canvas portion rasterized ahead of the post processing. + */ +class SOFTWARESHARED_EXPORT SoftwareCanvasRenderer: public SoftwareRenderer +{ +public: + SoftwareCanvasRenderer(); + virtual ~SoftwareCanvasRenderer(); + + inline const Canvas *getCanvas() const {return canvas;} + inline double getProgress() const {return progress;} + + /** + * Set the renderer configuration. + */ + void setConfig(const RenderConfig &config); + + /** + * @brief Set the rendering size in pixels. + * + * Set 'samples' to something bigger than 1 to allow for the multi-sampling of pixels. + */ + void setSize(int width, int height, int samples=1); + + /** + * @brief Start the two-pass render process. + */ + void render(); + + /** + * @brief Interrupt the render process. + */ + void interrupt(); + + /** + * Get a rasterizer by its client id. + */ + const Rasterizer &getRasterizer(int client_id) const; + + /** + * Save the rendered canvas to a picture file on disk. + * + * Returns true if the save was successful. + */ + bool saveToDisk(const std::string &filepath) const; + +protected: + /** + * @brief Rasterize the scenery into a canvas portion. + */ + void rasterize(CanvasPortion *portion); + + /** + * @brief Apply pixel shader to fragments stored in the CanvasPortion. + */ + void applyPixelShader(CanvasPortion *portion); + +private: + double progress; + double progress_segment; + + Canvas *canvas; + int samples; + std::vector rasterizers; + bool started; + bool interrupted; + + ParallelWork *current_work; +}; + +} +} + +#endif // SOFTWARECANVASRENDERER_H diff --git a/src/render/software/SoftwareRenderer.cpp b/src/render/software/SoftwareRenderer.cpp index 1f88f17..548858c 100644 --- a/src/render/software/SoftwareRenderer.cpp +++ b/src/render/software/SoftwareRenderer.cpp @@ -22,17 +22,8 @@ SoftwareRenderer::SoftwareRenderer(Scenery* scenery) { - RenderArea::RenderParams params = {1, 1, 1, 5}; - render_quality = 5; - render_width = 1; - render_height = 1; - render_interrupt = 0; - render_progress = 0.0; - is_rendering = 0; render_camera = new CameraDefinition; - render_area = new RenderArea(this); - render_area->setParams(params); atmosphere_renderer = new BaseAtmosphereRenderer(this); clouds_renderer = new CloudsRenderer(this); @@ -59,7 +50,6 @@ SoftwareRenderer::SoftwareRenderer(Scenery* scenery) SoftwareRenderer::~SoftwareRenderer() { delete render_camera; - delete render_area; delete fluid_medium; delete lighting; @@ -108,18 +98,6 @@ void SoftwareRenderer::prepare() //fluid_medium->registerMedium(water_renderer); } -void SoftwareRenderer::rasterize() -{ - TerrainRasterizer terrain(this); - terrain.renderSurface(); - - WaterRasterizer water(this); - water.renderSurface(); - - SkyRasterizer sky(this); - sky.rasterize(); -} - void SoftwareRenderer::disableClouds() { scenery->getClouds()->clear(); @@ -164,68 +142,6 @@ void SoftwareRenderer::disableAtmosphere(const std::vector &ligh atmosphere_renderer->setStaticLights(lights); } -void SoftwareRenderer::setPreviewCallbacks(RenderArea::RenderCallbackStart start, RenderArea::RenderCallbackDraw draw, RenderArea::RenderCallbackUpdate update) -{ - render_area->setPreviewCallbacks(start, draw, update); -} - -static void* _renderFirstPass(void* data) -{ - SoftwareRenderer* renderer = (SoftwareRenderer*)data; - renderer->rasterize(); - renderer->is_rendering = 0; - return NULL; -} - -void SoftwareRenderer::start(RenderArea::RenderParams params) -{ - Thread thread(_renderFirstPass); - int loops; - int core_count = System::getCoreCount(); - - params.antialias = (params.antialias < 1) ? 1 : params.antialias; - params.antialias = (params.antialias > 4) ? 4 : params.antialias; - - render_quality = params.quality; - render_width = params.width * params.antialias; - render_height = params.height * params.antialias; - render_interrupt = 0; - render_progress = 0.0; - - prepare(); - - render_camera->setRenderSize(render_width, render_height); - - render_area->setBackgroundColor(COLOR_BLACK); - render_area->setParams(params); - render_area->clear(); - - is_rendering = 1; - thread.start(this); - loops = 0; - - while (is_rendering) - { - Thread::timeSleepMs(100); - - if (++loops >= 10) - { - render_area->update(); - loops = 0; - } - } - thread.join(); - - is_rendering = 1; - render_area->postProcess(core_count); - is_rendering = 0; -} - -void SoftwareRenderer::interrupt() -{ - render_interrupt = 1; -} - Color SoftwareRenderer::applyLightingToSurface(const Vector3 &location, const Vector3 &normal, const SurfaceMaterial &material) { LightStatus status(lighting, location, getCameraLocation(location)); @@ -261,11 +177,6 @@ RayCastingResult SoftwareRenderer::rayWalking(const Vector3 &location, const Vec return result; } -int SoftwareRenderer::addRenderProgress(double) -{ - return not render_interrupt; -} - Vector3 SoftwareRenderer::getCameraLocation(const Vector3 &) { return render_camera->getLocation(); @@ -296,37 +207,3 @@ Vector3 SoftwareRenderer::unprojectPoint(const Vector3 &point) { return render_camera->unproject(point); } - -void SoftwareRenderer::pushTriangle(const Vector3 &v1, const Vector3 &v2, const Vector3 &v3, RenderArea::f_RenderFragmentCallback callback, void* callback_data) -{ - Vector3 p1, p2, p3; - - p1 = projectPoint(v1); - p2 = projectPoint(v2); - p3 = projectPoint(v3); - - render_area->pushTriangle(p1, p2, p3, v1, v2, v3, callback, callback_data); -} - -void SoftwareRenderer::pushQuad(const Vector3 &v1, const Vector3 &v2, const Vector3 &v3, const Vector3 &v4, RenderArea::f_RenderFragmentCallback callback, void* callback_data) -{ - pushTriangle(v2, v3, v1, callback, callback_data); - pushTriangle(v4, v1, v3, callback, callback_data); -} - -void SoftwareRenderer::pushDisplacedTriangle(const Vector3 &v1, const Vector3 &v2, const Vector3 &v3, const Vector3 &ov1, const Vector3 &ov2, const Vector3 &ov3, RenderArea::f_RenderFragmentCallback callback, void* callback_data) -{ - Vector3 p1, p2, p3; - - p1 = projectPoint(v1); - p2 = projectPoint(v2); - p3 = projectPoint(v3); - - render_area->pushTriangle(p1, p2, p3, ov1, ov2, ov3, callback, callback_data); -} - -void SoftwareRenderer::pushDisplacedQuad(const Vector3 &v1, const Vector3 &v2, const Vector3 &v3, const Vector3 &v4, const Vector3 &ov1, const Vector3 &ov2, const Vector3 &ov3, const Vector3 &ov4, RenderArea::f_RenderFragmentCallback callback, void* callback_data) -{ - pushDisplacedTriangle(v2, v3, v1, ov2, ov3, ov1, callback, callback_data); - pushDisplacedTriangle(v4, v1, v3, ov4, ov1, ov3, callback, callback_data); -} diff --git a/src/render/software/SoftwareRenderer.h b/src/render/software/SoftwareRenderer.h index 9f269f3..b1d8fa9 100644 --- a/src/render/software/SoftwareRenderer.h +++ b/src/render/software/SoftwareRenderer.h @@ -3,7 +3,6 @@ #include "software_global.h" -#include "RenderArea.h" #include "RayCastingManager.h" namespace paysages { @@ -21,16 +20,8 @@ public: /* Render base configuration */ int render_quality; - int render_width; - int render_height; CameraDefinition* render_camera; - /* Render related */ - RenderArea* render_area; - double render_progress; - int render_interrupt; - int is_rendering; - void* customData[10]; virtual Vector3 getCameraLocation(const Vector3 &target); @@ -38,11 +29,6 @@ public: virtual double getPrecision(const Vector3 &location); virtual Vector3 projectPoint(const Vector3 &point); virtual Vector3 unprojectPoint(const Vector3 &point); - virtual int addRenderProgress(double progress); - virtual void pushTriangle(const Vector3 &v1, const Vector3 &v2, const Vector3 &v3, RenderArea::f_RenderFragmentCallback callback, void* callback_data); - virtual void pushQuad(const Vector3 &v1, const Vector3 &v2, const Vector3 &v3, const Vector3 &v4, RenderArea::f_RenderFragmentCallback callback, void* callback_data); - virtual void pushDisplacedTriangle(const Vector3 &v1, const Vector3 &v2, const Vector3 &v3, const Vector3 &ov1, const Vector3 &ov2, const Vector3 &ov3, RenderArea::f_RenderFragmentCallback callback, void* callback_data); - virtual void pushDisplacedQuad(const Vector3 &v1, const Vector3 &v2, const Vector3 &v3, const Vector3 &v4, const Vector3 &ov1, const Vector3 &ov2, const Vector3 &ov3, const Vector3 &ov4, RenderArea::f_RenderFragmentCallback callback, void* callback_data); /*! * \brief Set the scenery to render. @@ -59,11 +45,6 @@ public: */ virtual void prepare(); - /*! - * \brief Start the rasterization process. - */ - virtual void rasterize(); - /*! * \brief Disable the clouds feature. * @@ -78,10 +59,6 @@ public: void disableAtmosphere(); void disableAtmosphere(const std::vector &lights); - void setPreviewCallbacks(RenderArea::RenderCallbackStart start, RenderArea::RenderCallbackDraw draw, RenderArea::RenderCallbackUpdate update); - void start(RenderArea::RenderParams params); - void interrupt(); - inline Scenery* getScenery() const {return scenery;} inline BaseAtmosphereRenderer* getAtmosphereRenderer() const {return atmosphere_renderer;} diff --git a/src/render/software/TerrainRasterizer.cpp b/src/render/software/TerrainRasterizer.cpp index cf53310..14e7b55 100644 --- a/src/render/software/TerrainRasterizer.cpp +++ b/src/render/software/TerrainRasterizer.cpp @@ -7,10 +7,11 @@ #include "WaterRenderer.h" #include "TexturesRenderer.h" #include "Scenery.h" -#include "ParallelQueue.h" +#include "CanvasPortion.h" +#include "CanvasFragment.h" -TerrainRasterizer::TerrainRasterizer(SoftwareRenderer* renderer): - renderer(renderer) +TerrainRasterizer::TerrainRasterizer(SoftwareRenderer* renderer, int client_id): + Rasterizer(renderer, client_id, Color(1.0, 0.9, 0.9)) { } @@ -19,13 +20,30 @@ static inline Vector3 _getPoint(SoftwareRenderer* renderer, double x, double z) return Vector3(x, renderer->getTerrainRenderer()->getHeight(x, z, 1), z); } -static Color _postProcessFragment(SoftwareRenderer* renderer, const Vector3 &point, void*) +void TerrainRasterizer::tessellateChunk(CanvasPortion* canvas, TerrainChunkInfo* chunk, int detail) { - double precision = renderer->getPrecision(_getPoint(renderer, point.x, point.z)); - return renderer->getTerrainRenderer()->getFinalColor(point, precision); + if (detail < 1) + { + return; + } + + double water_height = renderer->getWaterRenderer()->getHeightInfo().min_height; + + double startx = chunk->point_nw.x; + double startz = chunk->point_nw.z; + double size = (chunk->point_ne.x - chunk->point_nw.x) / (double)detail; + int i, j; + + for (i = 0; i < detail; i++) + { + for (j = 0; j < detail; j++) + { + renderQuad(canvas, startx + (double)i * size, startz + (double)j * size, size, water_height); + } + } } -static void _renderQuad(SoftwareRenderer* renderer, double x, double z, double size, double water_height) +void TerrainRasterizer::renderQuad(CanvasPortion *canvas, double x, double z, double size, double water_height) { Vector3 ov1, ov2, ov3, ov4; Vector3 dv1, dv2, dv3, dv4; @@ -50,30 +68,7 @@ static void _renderQuad(SoftwareRenderer* renderer, double x, double z, double s if (dv1.y > water_height || dv2.y > water_height || dv3.y > water_height || dv4.y > water_height) { - renderer->pushDisplacedQuad(dv1, dv2, dv3, dv4, ov1, ov2, ov3, ov4, _postProcessFragment, NULL); - } -} - -void TerrainRasterizer::tessellateChunk(TerrainChunkInfo* chunk, int detail) -{ - if (detail < 1) - { - return; - } - - double water_height = renderer->getWaterRenderer()->getHeightInfo().min_height; - - double startx = chunk->point_nw.x; - double startz = chunk->point_nw.z; - double size = (chunk->point_ne.x - chunk->point_nw.x) / (double)detail; - int i, j; - - for (i = 0; i < detail; i++) - { - for (j = 0; j < detail; j++) - { - _renderQuad(renderer, startx + (double)i * size, startz + (double)j * size, size, water_height); - } + pushDisplacedQuad(canvas, dv1, dv2, dv3, dv4, ov1, ov2, ov3, ov4); } } @@ -130,7 +125,7 @@ static void _getChunk(SoftwareRenderer* renderer, TerrainRasterizer::TerrainChun } } -void TerrainRasterizer::getTessellationInfo(int displaced) +void TerrainRasterizer::getTessellationInfo(CanvasPortion* canvas, int displaced) { TerrainChunkInfo chunk; int chunk_factor, chunk_count, i; @@ -157,25 +152,29 @@ void TerrainRasterizer::getTessellationInfo(int displaced) for (i = 0; i < chunk_count - 1; i++) { _getChunk(renderer, &chunk, cx - radius_ext + chunk_size * i, cz - radius_ext, chunk_size, displaced); - if (!processChunk(&chunk, progress)) + processChunk(canvas, &chunk, progress); + if (interrupted) { return; } _getChunk(renderer, &chunk, cx + radius_int, cz - radius_ext + chunk_size * i, chunk_size, displaced); - if (!processChunk(&chunk, progress)) + processChunk(canvas, &chunk, progress); + if (interrupted) { return; } _getChunk(renderer, &chunk, cx + radius_int - chunk_size * i, cz + radius_int, chunk_size, displaced); - if (!processChunk(&chunk, progress)) + processChunk(canvas, &chunk, progress); + if (interrupted) { return; } _getChunk(renderer, &chunk, cx - radius_ext, cz + radius_int - chunk_size * i, chunk_size, displaced); - if (!processChunk(&chunk, progress)) + processChunk(canvas, &chunk, progress); + if (interrupted) { return; } @@ -193,48 +192,19 @@ void TerrainRasterizer::getTessellationInfo(int displaced) } } -typedef struct +void TerrainRasterizer::processChunk(CanvasPortion* canvas, TerrainChunkInfo* chunk, double progress) { - TerrainRasterizer* rasterizer; - TerrainRasterizer::TerrainChunkInfo chunk; -} ParallelRasterInfo; - -static int _parallelJobCallback(ParallelQueue*, int, void* data, int stopping) -{ - ParallelRasterInfo* info = (ParallelRasterInfo*)data; - - if (!stopping) - { - info->rasterizer->tessellateChunk(&info->chunk, info->chunk.detail_hint); - } - - delete info; - return 0; + tessellateChunk(canvas, chunk, chunk->detail_hint); } -int TerrainRasterizer::processChunk(TerrainChunkInfo* chunk, double progress) +void TerrainRasterizer::rasterizeToCanvas(CanvasPortion *canvas) { - ParallelRasterInfo* info = new ParallelRasterInfo; - - info->rasterizer = this; - info->chunk = *chunk; - - if (!queue->addJob(_parallelJobCallback, info)) - { - delete info; - } - - renderer->render_progress = 0.05 * progress; - return !renderer->render_interrupt; + getTessellationInfo(canvas, 0); } -void TerrainRasterizer::renderSurface() +Color TerrainRasterizer::shadeFragment(const CanvasFragment &fragment) const { - queue = new ParallelQueue(); - - renderer->render_progress = 0.0; - getTessellationInfo(0); - renderer->render_progress = 0.05; - - queue->wait(); + Vector3 point = fragment.getLocation(); + double precision = renderer->getPrecision(_getPoint(renderer, point.x, point.z)); + return renderer->getTerrainRenderer()->getFinalColor(point, precision); } diff --git a/src/render/software/TerrainRasterizer.h b/src/render/software/TerrainRasterizer.h index 47b559e..379c046 100644 --- a/src/render/software/TerrainRasterizer.h +++ b/src/render/software/TerrainRasterizer.h @@ -3,12 +3,13 @@ #include "software_global.h" +#include "Rasterizer.h" #include "Vector3.h" namespace paysages { namespace software { -class SOFTWARESHARED_EXPORT TerrainRasterizer +class SOFTWARESHARED_EXPORT TerrainRasterizer: public Rasterizer { public: typedef struct @@ -21,35 +22,29 @@ public: } TerrainChunkInfo; public: - TerrainRasterizer(SoftwareRenderer* renderer); + TerrainRasterizer(SoftwareRenderer* renderer, int client_id); /** * Method called for each chunk tessellated by getTessellationInfo. */ - int processChunk(TerrainChunkInfo* chunk, double progress); + void processChunk(CanvasPortion* canvas, TerrainChunkInfo* chunk, double progress); /** * Tessellate the terrain, calling processChunk for each chunk. * * The terrain will be broken in chunks, most detailed near the camera. */ - void getTessellationInfo(int displaced); + void getTessellationInfo(CanvasPortion* canvas, int displaced); /** * Tessellate a terrain chunk, pushing the quads in the render area. */ - void tessellateChunk(TerrainChunkInfo* chunk, int detail); + void tessellateChunk(CanvasPortion* canvas, TerrainChunkInfo* chunk, int detail); - /** - * Start the final rasterization of terrain. - * - * This will push the rasterized quads in the render area, waiting for post process. - */ - void renderSurface(); + void renderQuad(CanvasPortion* canvas, double x, double z, double size, double water_height); -private: - SoftwareRenderer* renderer; - ParallelQueue* queue; + virtual void rasterizeToCanvas(CanvasPortion* canvas) override; + virtual Color shadeFragment(const CanvasFragment &fragment) const override; }; } diff --git a/src/render/software/TexturesRenderer.cpp b/src/render/software/TexturesRenderer.cpp index 056881a..6dce8e0 100644 --- a/src/render/software/TexturesRenderer.cpp +++ b/src/render/software/TexturesRenderer.cpp @@ -40,10 +40,10 @@ double TexturesRenderer::getTriplanarNoise(NoiseGenerator *noise, const Vector3 double mXY = fabs(normal.z); double mXZ = fabs(normal.y); double mYZ = fabs(normal.x); - double total = mXY + mXZ + mYZ; - mXY /= total; - mXZ /= total; - mYZ /= total; + double total = 1.0 / (mXY + mXZ + mYZ); + mXY *= total; + mXZ *= total; + mYZ *= total; return noiseXY * mXY + noiseXZ * mXZ + noiseYZ * mYZ; } diff --git a/src/render/software/WaterRasterizer.cpp b/src/render/software/WaterRasterizer.cpp index 2ec32d1..16c82b1 100644 --- a/src/render/software/WaterRasterizer.cpp +++ b/src/render/software/WaterRasterizer.cpp @@ -2,19 +2,14 @@ #include "SoftwareRenderer.h" #include "WaterRenderer.h" -#include "ParallelQueue.h" +#include "CanvasFragment.h" -WaterRasterizer::WaterRasterizer(SoftwareRenderer* renderer): - renderer(renderer) +WaterRasterizer::WaterRasterizer(SoftwareRenderer* renderer, int client_id): + Rasterizer(renderer, client_id, Color(0.9, 0.95, 1.0)) { } -static Color _postProcessFragment(SoftwareRenderer* renderer, const Vector3 &location, void*) -{ - return renderer->getWaterRenderer()->getResult(location.x, location.z).final; -} - -static Vector3 _getFirstPassVertex(SoftwareRenderer* renderer, double x, double z) +static inline Vector3 _getFirstPassVertex(SoftwareRenderer* renderer, double x, double z) { Vector3 result; @@ -25,7 +20,7 @@ static Vector3 _getFirstPassVertex(SoftwareRenderer* renderer, double x, double return result; } -static void _renderQuad(SoftwareRenderer* renderer, double x, double z, double size) +void WaterRasterizer::rasterizeQuad(CanvasPortion* canvas, double x, double z, double size) { Vector3 v1, v2, v3, v4; @@ -34,42 +29,11 @@ static void _renderQuad(SoftwareRenderer* renderer, double x, double z, double s v3 = _getFirstPassVertex(renderer, x + size, z + size); v4 = _getFirstPassVertex(renderer, x + size, z); - renderer->pushQuad(v1, v2, v3, v4, _postProcessFragment, NULL); + pushQuad(canvas, v1, v2, v3, v4); } -typedef struct +void WaterRasterizer::rasterizeToCanvas(CanvasPortion *canvas) { - SoftwareRenderer* renderer; - int i; - double cx; - double cz; - double radius_int; - double chunk_size; - double radius_ext; -} ParallelRasterInfo; - -static int _parallelJobCallback(ParallelQueue*, int, void* data, int stopping) -{ - ParallelRasterInfo* info = (ParallelRasterInfo*)data; - - if (!stopping) - { - _renderQuad(info->renderer, info->cx - info->radius_ext + info->chunk_size * info->i, info->cz - info->radius_ext, info->chunk_size); - _renderQuad(info->renderer, info->cx + info->radius_int, info->cz - info->radius_ext + info->chunk_size * info->i, info->chunk_size); - _renderQuad(info->renderer, info->cx + info->radius_int - info->chunk_size * info->i, info->cz + info->radius_int, info->chunk_size); - _renderQuad(info->renderer, info->cx - info->radius_ext, info->cz + info->radius_int - info->chunk_size * info->i, info->chunk_size); - } - - delete info; - return 0; -} - -void WaterRasterizer::renderSurface() -{ - ParallelRasterInfo* info; - ParallelQueue* queue; - queue = new ParallelQueue(); - int chunk_factor, chunk_count, i; Vector3 cam = renderer->getCameraLocation(VECTOR_ZERO); double radius_int, radius_ext, base_chunk_size, chunk_size; @@ -91,27 +55,17 @@ void WaterRasterizer::renderSurface() while (radius_int < 20000.0) { - if (!renderer->addRenderProgress(0.0)) + if (interrupted) { return; } for (i = 0; i < chunk_count - 1; i++) { - info = new ParallelRasterInfo; - - info->renderer = renderer; - info->cx = cx; - info->cz = cz; - info->i = i; - info->radius_int = radius_int; - info->radius_ext = radius_ext; - info->chunk_size = chunk_size; - - if (!queue->addJob(_parallelJobCallback, info)) - { - delete info; - } + rasterizeQuad(canvas, cx - radius_ext + chunk_size * i, cz - radius_ext, chunk_size); + rasterizeQuad(canvas, cx + radius_int, cz - radius_ext + chunk_size * i, chunk_size); + rasterizeQuad(canvas, cx + radius_int - chunk_size * i, cz + radius_int, chunk_size); + rasterizeQuad(canvas, cx - radius_ext, cz + radius_int - chunk_size * i, chunk_size); } if (radius_int > 20.0 && chunk_count % 64 == 0 && (double)chunk_factor < radius_int / 20.0) @@ -124,7 +78,10 @@ void WaterRasterizer::renderSurface() radius_int = radius_ext; radius_ext += chunk_size; } - - queue->wait(); - delete queue; +} + +Color WaterRasterizer::shadeFragment(const CanvasFragment &fragment) const +{ + Vector3 location = fragment.getLocation(); + return renderer->getWaterRenderer()->getResult(location.x, location.z).final; } diff --git a/src/render/software/WaterRasterizer.h b/src/render/software/WaterRasterizer.h index 7d08798..4d587ae 100644 --- a/src/render/software/WaterRasterizer.h +++ b/src/render/software/WaterRasterizer.h @@ -3,18 +3,20 @@ #include "software_global.h" +#include "Rasterizer.h" + namespace paysages { namespace software { -class WaterRasterizer +class WaterRasterizer: public Rasterizer { public: - WaterRasterizer(SoftwareRenderer* renderer); + WaterRasterizer(SoftwareRenderer* renderer, int client_id); - void renderSurface(); + void rasterizeQuad(CanvasPortion* canvas, double x, double z, double size); -private: - SoftwareRenderer* renderer; + virtual void rasterizeToCanvas(CanvasPortion* canvas) override; + virtual Color shadeFragment(const CanvasFragment &fragment) const override; }; } diff --git a/src/render/software/software.pro b/src/render/software/software.pro index ec32efa..15472e7 100644 --- a/src/render/software/software.pro +++ b/src/render/software/software.pro @@ -34,10 +34,20 @@ SOURCES += SoftwareRenderer.cpp \ TerrainRenderer.cpp \ TexturesRenderer.cpp \ WaterRenderer.cpp \ - RenderArea.cpp \ RayCastingManager.cpp \ NightSky.cpp \ - TerrainRayWalker.cpp + TerrainRayWalker.cpp \ + Canvas.cpp \ + CanvasPortion.cpp \ + CanvasPixel.cpp \ + CanvasFragment.cpp \ + SoftwareCanvasRenderer.cpp \ + Rasterizer.cpp \ + CanvasLiveClient.cpp \ + CanvasPreview.cpp \ + RenderConfig.cpp \ + CanvasPixelShader.cpp \ + CanvasPictureWriter.cpp HEADERS += SoftwareRenderer.h\ software_global.h \ @@ -61,10 +71,20 @@ HEADERS += SoftwareRenderer.h\ TerrainRenderer.h \ TexturesRenderer.h \ WaterRenderer.h \ - RenderArea.h \ RayCastingManager.h \ NightSky.h \ - TerrainRayWalker.h + TerrainRayWalker.h \ + Canvas.h \ + CanvasPortion.h \ + CanvasPixel.h \ + CanvasFragment.h \ + SoftwareCanvasRenderer.h \ + Rasterizer.h \ + CanvasLiveClient.h \ + CanvasPreview.h \ + RenderConfig.h \ + CanvasPixelShader.h \ + CanvasPictureWriter.h unix:!symbian { maemo5 { diff --git a/src/render/software/software_global.h b/src/render/software/software_global.h index 180bcca..637b85d 100644 --- a/src/render/software/software_global.h +++ b/src/render/software/software_global.h @@ -14,7 +14,8 @@ namespace paysages { namespace software { class SoftwareRenderer; - class RenderArea; + class SoftwareCanvasRenderer; + class RenderConfig; class FluidMediumManager; class FluidMediumInterface; @@ -33,6 +34,7 @@ namespace software { class TexturesRenderer; class WaterRenderer; + class Rasterizer; class SkyRasterizer; class TerrainRasterizer; @@ -44,6 +46,15 @@ namespace software { class NightSky; class TerrainRayWalker; + + class Canvas; + class CanvasPortion; + class CanvasPixel; + class CanvasFragment; + class CanvasLiveClient; + class CanvasPreview; + class CanvasPixelShader; + class CanvasPictureWriter; } } diff --git a/src/system/FileSystem.cpp b/src/system/FileSystem.cpp new file mode 100644 index 0000000..ae43dc2 --- /dev/null +++ b/src/system/FileSystem.cpp @@ -0,0 +1,14 @@ +#include "FileSystem.h" + +#include +#include + +std::string FileSystem::getTempFile(const std::string &filename) +{ + return QDir::temp().filePath(QString::fromStdString(filename)).toStdString(); +} + +bool FileSystem::isFile(const std::string &filepath) +{ + return QFileInfo(QString::fromStdString(filepath)).exists(); +} diff --git a/src/system/FileSystem.h b/src/system/FileSystem.h new file mode 100644 index 0000000..f59e596 --- /dev/null +++ b/src/system/FileSystem.h @@ -0,0 +1,28 @@ +#ifndef FILESYSTEM_H +#define FILESYSTEM_H + +#include "system_global.h" + +namespace paysages { +namespace system { + +class SYSTEMSHARED_EXPORT FileSystem +{ +public: + /** + * Get an absolute path to a temporary file. + * + * filename must not contain directory separators. + */ + static std::string getTempFile(const std::string &filename); + + /** + * Returns true if the given path points to a file. + */ + static bool isFile(const std::string &filepath); +}; + +} +} + +#endif // FILESYSTEM_H diff --git a/src/system/PackStream.cpp b/src/system/PackStream.cpp index d7f9149..49010b1 100644 --- a/src/system/PackStream.cpp +++ b/src/system/PackStream.cpp @@ -114,3 +114,13 @@ std::string PackStream::readString() return ""; } } + +void PackStream::skip(const int &value, int count) +{ + stream->skipRawData(sizeof(value) * count); +} + +void PackStream::skip(const double &value, int count) +{ + stream->skipRawData(sizeof(value) * count); +} diff --git a/src/system/PackStream.h b/src/system/PackStream.h index 127adf6..2de2d03 100644 --- a/src/system/PackStream.h +++ b/src/system/PackStream.h @@ -32,6 +32,9 @@ public: void read(char* value, int max_length); std::string readString(); + void skip(const int &value, int count=1); + void skip(const double &value, int count=1); + private: QFile* file; QDataStream* stream; diff --git a/src/system/ParallelQueue.cpp b/src/system/ParallelQueue.cpp deleted file mode 100644 index 496b341..0000000 --- a/src/system/ParallelQueue.cpp +++ /dev/null @@ -1,181 +0,0 @@ -#include "ParallelQueue.h" - -#include "Mutex.h" -#include "Thread.h" -#include "System.h" -#include - -#define QUEUE_SIZE 1000 - -static void* _queueThreadCallback(ParallelQueue* queue) -{ - ParallelQueue::ParallelJob* job; - - while (!queue->stopping) - { - /* Try to take a job */ - queue->lock->acquire(); - job = queue->jobs + queue->jobs_index_pending; - if (job->state == ParallelQueue::JOB_STATE_PENDING) - { - if (queue->jobs_index_pending >= QUEUE_SIZE - 1) - { - queue->jobs_index_pending = 0; - } - else - { - queue->jobs_index_pending++; - } - job->state = ParallelQueue::JOB_STATE_PROCESSING; - } - else - { - job = NULL; - } - queue->lock->release(); - - if (job) - { - /* Process the job */ - job->process(queue, job->id, job->data, 0); - - queue->lock->acquire(); - if (queue->collect) - { - job->state = ParallelQueue::JOB_STATE_TOCOLLECT; - /* TODO jobs_index_collect ? */ - } - else - { - job->state = ParallelQueue::JOB_STATE_FREE; - queue->jobs_count--; - } - queue->lock->release(); - } - else - { - Thread::timeSleepMs(50); - } - } - return NULL; -} - -ParallelQueue::ParallelQueue(int collect) -{ - int i; - - assert(!collect); /* Not fully implemented yet ! */ - - this->collect = collect; - this->stopping = 0; - this->lock = new Mutex(); - - this->jobs = new ParallelJob[QUEUE_SIZE]; - for (i = 0; i < QUEUE_SIZE; i++) - { - this->jobs[i].state = JOB_STATE_FREE; - } - this->jobs_count = 0; - this->jobs_index_free = 0; - this->jobs_index_collect = 0; - this->jobs_index_pending = 0; - this->jobs_next_id = 1; - - /* Start workers */ - this->workers_count = System::getCoreCount(); - this->workers = new Thread*[this->workers_count]; - for (i = 0; i < this->workers_count; i++) - { - this->workers[i] = new Thread((ThreadFunction)_queueThreadCallback); - this->workers[i]->start(this); - } -} - -ParallelQueue::~ParallelQueue() -{ - interrupt(); - - assert(not collect or jobs[jobs_index_collect].state != JOB_STATE_TOCOLLECT); - assert(jobs_count == 0); - - delete lock; - delete[] jobs; - delete[] workers; -} - -void ParallelQueue::interrupt() -{ - int i; - - if (not stopping) - { - stopping = 1; - - for (i = 0; i < workers_count; i++) - { - workers[i]->join(); - delete workers[i]; - } - } -} - -void ParallelQueue::wait() -{ - while (jobs_count > 0) - { - Thread::timeSleepMs(100); - } -} - -int ParallelQueue::addJob(FuncParallelJob func_process, void* data) -{ - if (stopping) - { - return 0; - } - - /* Wait for a free slot */ - while (jobs[jobs_index_free].state != JOB_STATE_FREE) - { - Thread::timeSleepMs(50); - if (stopping) - { - return 0; - } - } - - /* Prepare the job */ - ParallelJob job; - job.state = JOB_STATE_PENDING; - job.id = jobs_next_id++; - job.process = func_process; - job.data = data; - - /* Add the job to the queue */ - lock->acquire(); - if (stopping) - { - lock->release(); - return 0; - } - jobs[jobs_index_free] = job; - if (jobs_index_free >= QUEUE_SIZE - 1) - { - jobs_index_free = 0; - } - else - { - jobs_index_free++; - } - jobs_count++; - assert(jobs_count <= QUEUE_SIZE); - lock->release(); - - return job.id; -} - -int ParallelQueue::collectJobs(FuncParallelJob) -{ - /* TODO */ - return 0; -} diff --git a/src/system/ParallelQueue.h b/src/system/ParallelQueue.h deleted file mode 100644 index ae40a0d..0000000 --- a/src/system/ParallelQueue.h +++ /dev/null @@ -1,94 +0,0 @@ -#ifndef PARALLELQUEUE_H -#define PARALLELQUEUE_H - -#include "system_global.h" - -namespace paysages { -namespace system { - -class SYSTEMSHARED_EXPORT ParallelQueue -{ -public: - typedef int (*FuncParallelJob)(ParallelQueue* queue, int job_id, void* data, int stopping); - - typedef enum - { - JOB_STATE_FREE, - JOB_STATE_PENDING, - JOB_STATE_PROCESSING, - JOB_STATE_TOCOLLECT - } EnumJobState; - - typedef struct - { - EnumJobState state; - int id; - FuncParallelJob process; - void* data; - } ParallelJob; - -public: - /** - * Create a parallel processing queue. - * - * This queue will use parallel workers to process jobs added to it. - * @param collect True to collect finished jobs and wait for a call to collectJobs, False to discard finished jobs. - * @return The newly allocated queue. - */ - ParallelQueue(int collect=0); - - /** - * Delete a parallel queue. - * - * This will interrupt the queue. - * If the queue is in collect mode, you should call interrupt, then collectJobs, before calling this. - */ - ~ParallelQueue(); - - /** - * Interrupt the queue processing. - * - * This will wait for running jobs to end, cancel pending jobs (still calling their callbacks with stopping=1) and - * refuse future jobs. - */ - void interrupt(); - - /** - * Wait for all jobs to finish. - * - * This function will return as soon as there is no pending jobs. It is recommended to stop feeding the queue, or this - * function may never return. - */ - void wait(); - - /** - * Add a job to the queue. - * - * Don't call this method concurrently from several threads. - * @param func_process The function that will be called for the job processing. - * @param data The data that will be passed to the callback. - * @return The job ID, 0 if the queue doesn't accept jobs. - */ - int addJob(FuncParallelJob func_process, void* data); - - int collectJobs(FuncParallelJob func_collect); - - int collect; - volatile int stopping; - Mutex* lock; - - int workers_count; - Thread** workers; - - ParallelJob* jobs; - int jobs_count; /** Number of jobs in queue (all status except JOB_STATE_FREE) */ - int jobs_index_free; /** Index of next free position */ - int jobs_index_collect; /** Index of first job to collect */ - int jobs_index_pending; /** Index of first pending job to process */ - int jobs_next_id; -}; - -} -} - -#endif // PARALLELQUEUE_H diff --git a/src/system/ParallelWork.cpp b/src/system/ParallelWork.cpp index acd94f7..fd6338a 100644 --- a/src/system/ParallelWork.cpp +++ b/src/system/ParallelWork.cpp @@ -2,113 +2,186 @@ #include "Thread.h" #include "System.h" +#include "Semaphore.h" +#include "Mutex.h" +#include "ParallelWorker.h" #include +/** + * Compatibility class for code that uses ParallelUnitFunction. + */ +class ParallelWorkerCompat:public ParallelWorker +{ +public: + ParallelWorkerCompat(ParallelWork *work, ParallelWork::ParallelUnitFunction func, void* data): + ParallelWorker(), work(work), func(func), data(data) + { + } + + virtual void processParallelUnit(int unit) + { + func(work, unit, data); + } + +private: + ParallelWork* work; + ParallelWork::ParallelUnitFunction func; + void* data; +}; + +/** + * Thread sub-class to perform units of work + */ +class ParallelWork::ParallelThread:public Thread +{ +public: + ParallelThread(ParallelWork *work): + Thread(), work(work), sem(0) + { + interrupted = false; + unit = -1; + } + + void feedUnit(int unit) + { + this->unit = unit; + sem.release(); + } + + void interrupt() + { + interrupted = true; + sem.release(); + } + +protected: + virtual void run() override + { + while (unit >= 0 or not interrupted) + { + // Wait for a unit (or interrupt) + sem.acquire(); + + // Process the unit + if (unit >= 0) + { + work->worker->processParallelUnit(unit); + unit = -1; + work->returnThread(this); + } + } + } + +private: + ParallelWork* work; + Semaphore sem; + int unit; + bool interrupted; +}; + +ParallelWork::ParallelWork(ParallelWorker *worker, int units) +{ + this->units = units; + this->running = 0; + this->worker = worker; + this->worker_compat = false; +} + ParallelWork::ParallelWork(ParallelUnitFunction func, int units, void* data) { this->units = units; this->running = 0; - this->unit_function = func; - this->data = data; + this->worker = new ParallelWorkerCompat(this, func, data); + this->worker_compat = true; } ParallelWork::~ParallelWork() { assert(not running); -} - -static void* _workerThreadCallback(ParallelWork::ParallelWorker* worker) -{ - worker->result = worker->work->unit_function(worker->work, worker->unit, worker->work->data); - worker->status = ParallelWork::PARALLEL_WORKER_STATUS_DONE; - return NULL; -} - -static int _runNextWorker(ParallelWork::ParallelWorker workers[], int worker_count, int unit) -{ - int i; - - while (1) + if (worker_compat) { - for (i = 0; i < worker_count; i++) - { - ParallelWork::ParallelWorker* worker = workers + i; - if (worker->status == ParallelWork::PARALLEL_WORKER_STATUS_VOID) - { - worker->status = ParallelWork::PARALLEL_WORKER_STATUS_RUNNING; - worker->result = 0; - worker->unit = unit; - worker->thread = new Thread((ThreadFunction)_workerThreadCallback); - worker->thread->start(worker); - - return 0; - } - else if (worker->status == ParallelWork::PARALLEL_WORKER_STATUS_DONE) - { - int result = worker->result; - - worker->status = ParallelWork::PARALLEL_WORKER_STATUS_RUNNING; - worker->result = 0; - worker->unit = unit; - worker->thread->join(); - delete worker->thread; - worker->thread = new Thread((ThreadFunction)_workerThreadCallback); - worker->thread->start(worker); - - return result; - } - } - Thread::timeSleepMs(50); + delete worker; } } -int ParallelWork::perform(int nbworkers) +int ParallelWork::perform(int thread_count) { - int i, done, result; + int i, done; assert(not running); - result = 0; - - if (nbworkers <= 0) + // Get thread count + if (thread_count <= 0) { - nbworkers = System::getCoreCount(); + thread_count = System::getCoreCount(); } - if (nbworkers > PARALLEL_MAX_THREADS) + if (thread_count > PARALLEL_MAX_THREADS) { - nbworkers = PARALLEL_MAX_THREADS; + thread_count = PARALLEL_MAX_THREADS; } + this->thread_count = thread_count; running = 1; - /* Init workers */ - for (i = 0; i < nbworkers; i++) + // Init threads + semaphore = new Semaphore(thread_count); + mutex = new Mutex(); + threads = new ParallelThread*[thread_count]; + available = new ParallelThread*[thread_count]; + available_offset = 0; + available_length = thread_count; + for (i = 0; i < thread_count; i++) { - workers[i].status = PARALLEL_WORKER_STATUS_VOID; - workers[i].work = this; + threads[i] = available[i] = new ParallelThread(this); + threads[i]->start(); } - /* Perform run */ + // Perform all units for (done = 0; done < units; done++) { - if (_runNextWorker(workers, nbworkers, done)) + // Take first available thread + semaphore->acquire(); + mutex->acquire(); + ParallelThread *thread = available[available_offset]; + available_offset++; + if (available_offset >= thread_count) { - result++; + available_offset = 0; } + available_length--; + mutex->release(); + + // Feed the unit to it + thread->feedUnit(done); } - /* Wait and clean up workers */ - for (i = 0; i < nbworkers; i++) + // Wait for all threads to end, then cleanup + for (i = 0; i < thread_count; i++) { - if (workers[i].status != PARALLEL_WORKER_STATUS_VOID) - { - workers[i].thread->join(); - delete workers[i].thread; - if (workers[i].result) - { - result++; - } - } + threads[i]->interrupt(); } + for (i = 0; i < thread_count; i++) + { + threads[i]->join(); + delete threads[i]; + } + delete[] threads; + delete[] available; + delete semaphore; + delete mutex; running = 0; - return result; + return done; +} + +void ParallelWork::interrupt() +{ + worker->interrupt(); +} + +void ParallelWork::returnThread(ParallelWork::ParallelThread *thread) +{ + mutex->acquire(); + available[(available_offset + available_length) % thread_count] = thread; + available_length++; + semaphore->release(); + mutex->release(); } diff --git a/src/system/ParallelWork.h b/src/system/ParallelWork.h index 528ce31..f1b6133 100644 --- a/src/system/ParallelWork.h +++ b/src/system/ParallelWork.h @@ -13,32 +13,24 @@ class SYSTEMSHARED_EXPORT ParallelWork public: typedef int (*ParallelUnitFunction)(ParallelWork* work, int unit, void* data); - typedef enum - { - PARALLEL_WORKER_STATUS_VOID, - PARALLEL_WORKER_STATUS_RUNNING, - PARALLEL_WORKER_STATUS_DONE - } ParallelWorkerStatus; - - typedef struct - { - Thread* thread; - ParallelWork* work; - ParallelWorkerStatus status; - int unit; - int result; - } ParallelWorker; + /** + * Obscure thread class. + */ + class ParallelThread; + friend class ParallelThread; public: /** * Create a parallel work handler. * - * This will spawn an optimal number of threads to process a given number of work units. + * This will spawn a number of threads. + */ + ParallelWork(ParallelWorker *worker, int units); + + /** + * Create a parallel work handler. * - * @param func The callback that will be called from threads to process one unit. - * @param units Number of units to handle. - * @param data Custom data that will be passed to the callback. - * @return The newly allocated handler. + * This is a compatibility constructor for older code, use the constructor with ParallelWorker instead. */ ParallelWork(ParallelUnitFunction func, int units, void* data); @@ -52,15 +44,33 @@ public: /** * Start working on the units. * - * @param workers Number of threads to spaws, -1 for an optimal number. + * @param threads Number of threads to spaws, -1 for an optimal number. */ - int perform(int workers=-1); + int perform(int thread_count=-1); + /** + * Tell the threads to interrupt what they are doing. + * + * This will also call interrupt() on the worker. + */ + void interrupt(); + +private: + void returnThread(ParallelThread *thread); + +private: int units; int running; - ParallelUnitFunction unit_function; - ParallelWorker workers[PARALLEL_MAX_THREADS]; - void* data; + ParallelWorker *worker; + bool worker_compat; + + int thread_count; + Mutex* mutex; + Semaphore* semaphore; + ParallelThread** threads; + ParallelThread** available; + int available_offset; + int available_length; }; } diff --git a/src/system/ParallelWorker.cpp b/src/system/ParallelWorker.cpp new file mode 100644 index 0000000..d08dc7b --- /dev/null +++ b/src/system/ParallelWorker.cpp @@ -0,0 +1,15 @@ +#include "ParallelWorker.h" + +ParallelWorker::ParallelWorker() +{ + interrupted = false; +} + +ParallelWorker::~ParallelWorker() +{ +} + +void ParallelWorker::interrupt() +{ + interrupted = true; +} diff --git a/src/system/ParallelWorker.h b/src/system/ParallelWorker.h new file mode 100644 index 0000000..1d6d8b4 --- /dev/null +++ b/src/system/ParallelWorker.h @@ -0,0 +1,37 @@ +#ifndef PARALLELWORKER_H +#define PARALLELWORKER_H + +#include "system_global.h" + +namespace paysages { +namespace system { + +/** + * @brief Worker that can be used by the ParallelWork object to perform tasks in several threads. + */ +class SYSTEMSHARED_EXPORT ParallelWorker +{ +public: + ParallelWorker(); + virtual ~ParallelWorker(); + + /** + * Abstract method to reimplement to process a work unit. + * + * This method will be called from any thread in the thread pool used by the ParallelWork. + */ + virtual void processParallelUnit(int unit) = 0; + + /** + * Method to reimplement to know when to interrupt the processing of units. + */ + virtual void interrupt(); + +protected: + bool interrupted; +}; + +} +} + +#endif // PARALLELWORKER_H diff --git a/src/system/Semaphore.cpp b/src/system/Semaphore.cpp new file mode 100644 index 0000000..65ca0c7 --- /dev/null +++ b/src/system/Semaphore.cpp @@ -0,0 +1,6 @@ +#include "Semaphore.h" + +Semaphore::Semaphore(int resources): + QSemaphore(resources) +{ +} diff --git a/src/system/Semaphore.h b/src/system/Semaphore.h new file mode 100644 index 0000000..659cee2 --- /dev/null +++ b/src/system/Semaphore.h @@ -0,0 +1,25 @@ +#ifndef SEMAPHORE_H +#define SEMAPHORE_H + +#include "system_global.h" + +#include + +namespace paysages +{ +namespace system +{ + +class Semaphore: private QSemaphore +{ +public: + Semaphore(int resources); + + inline void acquire() {QSemaphore::acquire();} + inline void release() {QSemaphore::release();} +}; + +} +} + +#endif // SEMAPHORE_H diff --git a/src/system/Thread.h b/src/system/Thread.h index 699e381..5c49233 100644 --- a/src/system/Thread.h +++ b/src/system/Thread.h @@ -29,7 +29,7 @@ public: * \brief Start the thread * \param data User data to pass to the threaded function */ - void start(void* data); + void start(void* data=0); /*! * \brief Wait for the thread to end, and collect its result. diff --git a/src/system/system.pro b/src/system/system.pro index 5de52fa..0c1de8c 100644 --- a/src/system/system.pro +++ b/src/system/system.pro @@ -21,11 +21,13 @@ SOURCES += \ RandomGenerator.cpp \ Memory.cpp \ ParallelWork.cpp \ - ParallelQueue.cpp \ CacheFile.cpp \ PictureWriter.cpp \ Logs.cpp \ - ParallelPool.cpp + ParallelPool.cpp \ + ParallelWorker.cpp \ + Semaphore.cpp \ + FileSystem.cpp HEADERS += \ system_global.h \ @@ -36,11 +38,13 @@ HEADERS += \ RandomGenerator.h \ Memory.h \ ParallelWork.h \ - ParallelQueue.h \ CacheFile.h \ PictureWriter.h \ Logs.h \ - ParallelPool.h + ParallelPool.h \ + ParallelWorker.h \ + Semaphore.h \ + FileSystem.h unix:!symbian { maemo5 { diff --git a/src/system/system_global.h b/src/system/system_global.h index 52e9755..769b128 100644 --- a/src/system/system_global.h +++ b/src/system/system_global.h @@ -15,11 +15,13 @@ namespace paysages { namespace system { class PackStream; - class ParallelQueue; class ParallelWork; class ParallelPool; + class ParallelWorker; class Thread; class Mutex; + class Semaphore; + class PictureWriter; } } using namespace paysages::system; diff --git a/src/tests/Bruneton_Test.cpp b/src/tests/Bruneton_Test.cpp deleted file mode 100644 index 70d49d9..0000000 --- a/src/tests/Bruneton_Test.cpp +++ /dev/null @@ -1,83 +0,0 @@ -#include "BaseTestCase.h" - -#include "CameraDefinition.h" -#include "SoftwareRenderer.h" -#include "AtmosphereDefinition.h" -#include "AtmosphereRenderer.h" -#include "AtmosphereResult.h" -#include "Scenery.h" -#include "System.h" - -#define OUTPUT_WIDTH 400 -#define OUTPUT_HEIGHT 300 - -static Color _postProcessFragment(SoftwareRenderer* renderer, const Vector3 &location, void*) -{ - return renderer->getAtmosphereRenderer()->applyAerialPerspective(location, COLOR_BLACK).final; -} - -TEST(Bruneton, AerialPerspective1) -{ -#ifndef TESTS_FULL - return; -#endif - Scenery scenery; - SoftwareRenderer renderer(&scenery); - renderer.render_width = 800; - renderer.render_height = 600; - renderer.render_quality = 1; - - renderer.render_camera->setLocation(VECTOR_ZERO); - renderer.render_camera->setTarget(VECTOR_EAST); - renderer.render_camera->setRenderSize(renderer.render_width, renderer.render_height); - - RenderArea::RenderParams params = {renderer.render_width, renderer.render_height, 1, 1}; - renderer.render_area->setParams(params); - renderer.render_area->setBackgroundColor(COLOR_BLACK); - renderer.render_area->clear(); - - renderer.pushQuad(Vector3(50.0, -10.0, -50.0), Vector3(1.0, -10.0, -50.0), Vector3(1.0, -10.0, 50.0), Vector3(50.0, -10.0, 50.0), _postProcessFragment, NULL); - renderer.pushQuad(Vector3(10.0, -10.0, -10.0), Vector3(10.0, -10.0, -5.0), Vector3(10.0, 50.0, -5.0), Vector3(10.0, 50.0, -10.0), _postProcessFragment, NULL); - renderer.pushQuad(Vector3(15.0, -10.0, -5.0), Vector3(15.0, -10.0, 0.0), Vector3(15.0, 50.0, 0.0), Vector3(15.0, 50.0, -5.0), _postProcessFragment, NULL); - renderer.pushQuad(Vector3(20.0, -10.0, 5.0), Vector3(20.0, -10.0, 10.0), Vector3(20.0, 50.0, 10.0), Vector3(20.0, 50.0, 5.0), _postProcessFragment, NULL); - renderer.pushQuad(Vector3(30.0, -10.0, 25.0), Vector3(30.0, -10.0, 30.0), Vector3(30.0, 50.0, 30.0), Vector3(30.0, 50.0, 25.0), _postProcessFragment, NULL); - renderer.render_area->postProcess(System::getCoreCount()); - - renderer.render_area->saveToFile("./output/test_bruneton_perspective.png"); -} - -TEST(Bruneton, AerialPerspective2) -{ -#ifndef TESTS_FULL - return; -#endif - Scenery scenery; - - AtmosphereDefinition* atmo = scenery.getAtmosphere(); - atmo->hour = 6; - atmo->minute = 30; - atmo->validate(); - - SoftwareRenderer renderer(&scenery); - renderer.render_width = 800; - renderer.render_height = 600; - renderer.render_quality = 1; - - renderer.render_camera->setLocation(VECTOR_ZERO); - renderer.render_camera->setTarget(VECTOR_EAST); - renderer.render_camera->setRenderSize(renderer.render_width, renderer.render_height); - - RenderArea::RenderParams params = {renderer.render_width, renderer.render_height, 1, 1}; - renderer.render_area->setParams(params); - renderer.render_area->setBackgroundColor(COLOR_BLACK); - renderer.render_area->clear(); - - renderer.pushQuad(Vector3(50.0, -10.0, -50.0), Vector3(1.0, -10.0, -50.0), Vector3(1.0, -10.0, 50.0), Vector3(50.0, -10.0, 50.0), _postProcessFragment, NULL); - renderer.pushQuad(Vector3(10.0, -10.0, -10.0), Vector3(10.0, -10.0, -5.0), Vector3(10.0, 50.0, -5.0), Vector3(10.0, 50.0, -10.0), _postProcessFragment, NULL); - renderer.pushQuad(Vector3(15.0, -10.0, -5.0), Vector3(15.0, -10.0, 0.0), Vector3(15.0, 50.0, 0.0), Vector3(15.0, 50.0, -5.0), _postProcessFragment, NULL); - renderer.pushQuad(Vector3(20.0, -10.0, 5.0), Vector3(20.0, -10.0, 10.0), Vector3(20.0, 50.0, 10.0), Vector3(20.0, 50.0, 5.0), _postProcessFragment, NULL); - renderer.pushQuad(Vector3(30.0, -10.0, 25.0), Vector3(30.0, -10.0, 30.0), Vector3(30.0, 50.0, 30.0), Vector3(30.0, 50.0, 25.0), _postProcessFragment, NULL); - renderer.render_area->postProcess(System::getCoreCount()); - - renderer.render_area->saveToFile("./output/test_bruneton_perspective1.png"); -} diff --git a/src/tests/CanvasPortion_Test.cpp b/src/tests/CanvasPortion_Test.cpp new file mode 100644 index 0000000..c3e8cd2 --- /dev/null +++ b/src/tests/CanvasPortion_Test.cpp @@ -0,0 +1,110 @@ +#include "BaseTestCase.h" + +#include "CanvasPortion.h" +#include "CanvasFragment.h" + +TEST(CanvasPortion, setSize) +{ + CanvasPortion portion; + + portion.setSize(150, 30); + + EXPECT_EQ(150, portion.getWidth()); + EXPECT_EQ(30, portion.getHeight()); +} + +TEST(CanvasPortion, pushFragment) +{ + CanvasPortion portion; + CanvasFragment pushed; + const CanvasFragment *got; + int count; + + portion.setSize(50, 50); + portion.preparePixels(); + + portion.pushFragment(10, 15, pushed); + + count = portion.getFragmentCount(10, 14); + ASSERT_EQ(0, count); + got = portion.getFrontFragment(10, 14); + ASSERT_FALSE(got); + + count = portion.getFragmentCount(10, 15); + ASSERT_EQ(1, count); + got = portion.getFrontFragment(10, 15); + ASSERT_TRUE(got); +} + +TEST(CanvasPortion, pushFragment_opaque) +{ + CanvasPortion portion; + CanvasFragment pushed; + + portion.setSize(10, 10); + portion.preparePixels(); + + pushed = CanvasFragment(2.0, VECTOR_ZERO, 0); + portion.pushFragment(2, 2, pushed); + + ASSERT_EQ(1, portion.getFragmentCount(2, 2)); + EXPECT_DOUBLE_EQ(2.0, portion.getFrontFragment(2, 2)->getZ()); + + pushed = CanvasFragment(1.0, VECTOR_ZERO, 0); + portion.pushFragment(2, 2, pushed); + + ASSERT_EQ(1, portion.getFragmentCount(2, 2)); + EXPECT_DOUBLE_EQ(2.0, portion.getFrontFragment(2, 2)->getZ()); + + pushed = CanvasFragment(4.0, VECTOR_ZERO, 0); + portion.pushFragment(2, 2, pushed); + + ASSERT_EQ(1, portion.getFragmentCount(2, 2)); + EXPECT_DOUBLE_EQ(4.0, portion.getFrontFragment(2, 2)->getZ()); +} + +TEST(CanvasPortion, pushFragment_transparent) +{ + CanvasPortion portion; + CanvasFragment pushed; + + portion.setSize(10, 10); + portion.preparePixels(); + + pushed = CanvasFragment(2.0, VECTOR_ZERO, 0, false); + portion.pushFragment(2, 2, pushed); + + ASSERT_EQ(1, portion.getFragmentCount(2, 2)); + EXPECT_DOUBLE_EQ(2.0, portion.getFrontFragment(2, 2)->getZ()); + + pushed = CanvasFragment(3.0, VECTOR_ZERO, 0, true); + portion.pushFragment(2, 2, pushed); + + ASSERT_EQ(1, portion.getFragmentCount(2, 2)); + EXPECT_DOUBLE_EQ(3.0, portion.getFrontFragment(2, 2)->getZ()); + + pushed = CanvasFragment(4.0, VECTOR_ZERO, 0, false); + portion.pushFragment(2, 2, pushed); + + ASSERT_EQ(2, portion.getFragmentCount(2, 2)); + EXPECT_DOUBLE_EQ(4.0, portion.getFrontFragment(2, 2)->getZ()); +} + +TEST(CanvasPortion, clear) +{ + CanvasPortion portion; + CanvasFragment fragment; + + portion.setSize(10, 10); + portion.preparePixels(); + + portion.pushFragment(5, 5, fragment); + + EXPECT_EQ(0, portion.getFragmentCount(4, 5)); + EXPECT_EQ(1, portion.getFragmentCount(5, 5)); + + portion.clear(); + + EXPECT_EQ(0, portion.getFragmentCount(4, 5)); + EXPECT_EQ(0, portion.getFragmentCount(5, 5)); +} diff --git a/src/tests/CanvasPreview_Test.cpp b/src/tests/CanvasPreview_Test.cpp new file mode 100644 index 0000000..e754267 --- /dev/null +++ b/src/tests/CanvasPreview_Test.cpp @@ -0,0 +1,50 @@ +#include "BaseTestCase.h" + +#include "CanvasPreview.h" +#include "Color.h" + +TEST(CanvasPreview, setSize) +{ + CanvasPreview preview; + + preview.setSize(800, 600, 400, 300); + + EXPECT_EQ(400, preview.getWidth()); + EXPECT_EQ(300, preview.getHeight()); +} + +TEST(CanvasPreview, pushPixel_accumulate) +{ + CanvasPreview preview; + Color col; + preview.setSize(800, 600, 400, 300); + + col = preview.getFinalPixel(0, 0); + EXPECT_COLOR_RGBA(col, 0.0, 0.0, 0.0, 1.0); + + preview.pushPixel(0, 0, COLOR_BLACK, COLOR_RED); + col = preview.getFinalPixel(0, 0); + EXPECT_COLOR_RGBA(col, 0.25, 0.0, 0.0, 1.0); + + preview.pushPixel(0, 1, COLOR_BLACK, COLOR_BLUE); + col = preview.getFinalPixel(0, 0); + EXPECT_COLOR_RGBA(col, 0.25, 0.0, 0.25, 1.0); + + preview.pushPixel(0, 2, COLOR_BLACK, COLOR_BLUE); + col = preview.getFinalPixel(0, 0); + EXPECT_COLOR_RGBA(col, 0.25, 0.0, 0.25, 1.0); + col = preview.getFinalPixel(0, 1); + EXPECT_COLOR_RGBA(col, 0.0, 0.0, 0.25, 1.0); + + preview.pushPixel(0, 1, COLOR_BLUE, COLOR_GREEN); + col = preview.getFinalPixel(0, 0); + EXPECT_COLOR_RGBA(col, 0.25, 0.25, 0.0, 1.0); +} + +TEST(CanvasPreview, pushPixel_border) +{ + CanvasPreview preview; + preview.setSize(759, 237, 9, 14); + + preview.pushPixel(759, 237, COLOR_BLACK, COLOR_RED); +} diff --git a/src/tests/Canvas_Test.cpp b/src/tests/Canvas_Test.cpp new file mode 100644 index 0000000..4070c91 --- /dev/null +++ b/src/tests/Canvas_Test.cpp @@ -0,0 +1,42 @@ +#include "BaseTestCase.h" + +#include "Canvas.h" +#include "CanvasPortion.h" +#include "CanvasPreview.h" + +static void checkPortion(Canvas &canvas, int x, int y, int width, int height) +{ + ASSERT_LT(x, canvas.getHorizontalPortionCount()); + ASSERT_LT(y, canvas.getVerticalPortionCount()); + + CanvasPortion* portion = canvas.at(x, y); + + EXPECT_EQ(width, portion->getWidth()); + EXPECT_EQ(height, portion->getHeight()); +} + +TEST(Canvas, SizingAndCutting) +{ + Canvas canvas; + + canvas.setSize(200, 100); + EXPECT_EQ(200, canvas.getWidth()); + EXPECT_EQ(100, canvas.getHeight()); + EXPECT_EQ(200, canvas.getPreview()->getWidth()); + EXPECT_EQ(100, canvas.getPreview()->getHeight()); + ASSERT_EQ(1, canvas.getHorizontalPortionCount()); + ASSERT_EQ(1, canvas.getVerticalPortionCount()); + checkPortion(canvas, 0, 0, 200, 100); + + canvas.setSize(1100, 901); + EXPECT_EQ(1100, canvas.getWidth()); + EXPECT_EQ(901, canvas.getHeight()); + EXPECT_EQ(550, canvas.getPreview()->getWidth()); + EXPECT_EQ(450, canvas.getPreview()->getHeight()); + ASSERT_EQ(2, canvas.getHorizontalPortionCount()); + ASSERT_EQ(2, canvas.getVerticalPortionCount()); + checkPortion(canvas, 0, 0, 550, 450); + checkPortion(canvas, 0, 1, 550, 451); + checkPortion(canvas, 1, 0, 550, 450); + checkPortion(canvas, 1, 1, 550, 451); +} diff --git a/src/tests/PackStream_Test.cpp b/src/tests/PackStream_Test.cpp index d2e7407..92b5c4e 100644 --- a/src/tests/PackStream_Test.cpp +++ b/src/tests/PackStream_Test.cpp @@ -49,3 +49,46 @@ TEST(PackStream, All) } delete stream; } + +TEST(PackStream, Skip) +{ + PackStream* stream; + int i1=1, i2=2, i3=3; + double d1=1.1, d2=2.2; + + stream = new PackStream(); + stream->bindToFile("/tmp/test_paysages_pack", true); + + stream->write(&i1); + stream->write(&i2); + stream->write(&d1); + stream->write(&d2); + stream->write(&i3); + + delete stream; + + int resi; + double resd; + + stream = new PackStream(); + stream->bindToFile("/tmp/test_paysages_pack"); + stream->skip(i1, 1); + stream->read(&resi); + EXPECT_EQ(2, resi); + delete stream; + + stream = new PackStream(); + stream->bindToFile("/tmp/test_paysages_pack"); + stream->skip(i1, 2); + stream->read(&resd); + EXPECT_DOUBLE_EQ(1.1, resd); + delete stream; + + stream = new PackStream(); + stream->bindToFile("/tmp/test_paysages_pack"); + stream->skip(i1, 2); + stream->skip(d1, 2); + stream->read(&resi); + EXPECT_EQ(3, resi); + delete stream; +} diff --git a/src/tests/Render_Test.cpp b/src/tests/Render_Test.cpp deleted file mode 100644 index e1914fb..0000000 --- a/src/tests/Render_Test.cpp +++ /dev/null @@ -1,90 +0,0 @@ -#include "BaseTestCase.h" - -#include -#include "SoftwareRenderer.h" -#include "CameraDefinition.h" -#include "ColorProfile.h" -#include "System.h" - -static Color _postProcessFragment(SoftwareRenderer*, const Vector3 &location, void*) -{ - /* Checker-board */ - double x = fmod(location.x, 0.2); - double z = fmod(location.z, 0.2); - if (x < 0.0) - { - x = 0.2 + x; - } - if (z < 0.0) - { - z = 0.2 + z; - } - if ((x > 0.1) ^ (z > 0.1)) - { - return COLOR_WHITE; - } - else - { - return COLOR_BLACK; - } -} - -static void _render_quad_checker(SoftwareRenderer &renderer) -{ - renderer.render_width = 800; - renderer.render_height = 600; - renderer.render_quality = 1; - renderer.render_area->setToneMapping(ColorProfile(ColorProfile::TONE_MAPPING_CLAMP, 0.0)); - - renderer.render_camera->setRenderSize(renderer.render_width, renderer.render_height); - renderer.render_camera->setFov(1.57); - - RenderArea::RenderParams params = {renderer.render_width, renderer.render_height, 1, 1}; - renderer.render_area->setParams(params); - - renderer.render_area->setBackgroundColor(COLOR_BLUE); - renderer.render_area->clear(); - - renderer.pushQuad(Vector3(-1.0, 0.0, 1.0), Vector3(-1.0, 0.0, -1.0), Vector3(1.0, 0.0, -1.0), Vector3(1.0, 0.0, 1.0), _postProcessFragment, NULL); - renderer.render_area->postProcess(System::getCoreCount()); -} - -TEST(Render, quad) -{ -#ifndef TESTS_FULL - return; -#endif - SoftwareRenderer renderer; - - renderer.render_camera->setLocationCoords(0.0, 0.5, 2.0); - renderer.render_camera->setTargetCoords(0.0, 0.5, 0.0); - - _render_quad_checker(renderer); - - Color col; - col = renderer.render_area->getPixel(399, 599 - 435); - ASSERT_COLOR_RGBA(col, 1.0, 1.0, 1.0, 1.0); - col = renderer.render_area->getPixel(399, 599 - 436); - ASSERT_COLOR_RGBA(col, 0.0, 0.0, 0.0, 1.0); - col = renderer.render_area->getPixel(400, 599 - 435); - ASSERT_COLOR_RGBA(col, 0.0, 0.0, 0.0, 1.0); - col = renderer.render_area->getPixel(400, 599 - 436); - ASSERT_COLOR_RGBA(col, 1.0, 1.0, 1.0, 1.0); - - renderer.render_area->saveToFile("./output/test_render_quad.png"); -} - -TEST(Render, quad_cut) -{ -#ifndef TESTS_FULL - return; -#endif - SoftwareRenderer renderer; - - renderer.render_camera->setLocationCoords(0.8, 0.7, 1.0); - renderer.render_camera->setTargetCoords(0.0, 0.0, -0.5); - - _render_quad_checker(renderer); - - renderer.render_area->saveToFile("./output/test_render_quad_cut.png"); -} diff --git a/src/tests/tests.pro b/src/tests/tests.pro index 378272f..f01b4e3 100644 --- a/src/tests/tests.pro +++ b/src/tests/tests.pro @@ -10,16 +10,17 @@ SOURCES += main.cpp \ Layers_Test.cpp \ PackStream_Test.cpp \ NoiseGenerator_Test.cpp \ - Render_Test.cpp \ TerrainPainting_Test.cpp \ Zone_Test.cpp \ Euclid_Test.cpp \ - Bruneton_Test.cpp \ Camera_Test.cpp \ Clouds_Test.cpp \ FluidMediumManager_Test.cpp \ VertexArray_Test.cpp \ - FractalNoise_Test.cpp + FractalNoise_Test.cpp \ + Canvas_Test.cpp \ + CanvasPortion_Test.cpp \ + CanvasPreview_Test.cpp HEADERS += \ BaseTestCase.h