Merge branch 'canvas'

This commit is contained in:
Michaël Lemaire 2014-08-21 21:25:19 +02:00
commit 4087bde594
75 changed files with 2913 additions and 1916 deletions

View file

@ -43,17 +43,17 @@ else
endif endif
run_cli:build 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 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 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 perf report -g
profile_cli:build 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 perf report -g
package:build package:build

View file

@ -45,7 +45,7 @@ void NoiseGenerator::save(PackStream* stream)
{ {
NoiseLevel* level = levels + x; NoiseLevel* level = levels + x;
stream->write(&level->wavelength); stream->write(&level->frequency);
stream->write(&level->amplitude); stream->write(&level->amplitude);
stream->write(&level->minvalue); stream->write(&level->minvalue);
} }
@ -69,7 +69,7 @@ void NoiseGenerator::load(PackStream* stream)
{ {
NoiseLevel* level = levels + x; NoiseLevel* level = levels + x;
stream->read(&level->wavelength); stream->read(&level->frequency);
stream->read(&level->amplitude); stream->read(&level->amplitude);
stream->read(&level->minvalue); stream->read(&level->minvalue);
} }
@ -215,7 +215,7 @@ void NoiseGenerator::addLevelSimple(double scaling, double minvalue, double maxv
{ {
NoiseLevel level; NoiseLevel level;
level.wavelength = scaling; level.frequency = 1.0 / scaling;
level.minvalue = minvalue; level.minvalue = minvalue;
level.amplitude = maxvalue - minvalue; level.amplitude = maxvalue - minvalue;
@ -230,7 +230,7 @@ void NoiseGenerator::addLevels(int level_count, NoiseLevel start_level, double s
{ {
addLevel(start_level); addLevel(start_level);
start_level.minvalue += start_level.amplitude * (1.0 - amplitude_factor) * center_factor; 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; start_level.amplitude *= amplitude_factor;
} }
} }
@ -239,7 +239,7 @@ void NoiseGenerator::addLevelsSimple(int level_count, double scaling, double min
{ {
NoiseLevel level; NoiseLevel level;
level.wavelength = scaling; level.frequency = 1.0 / scaling;
level.minvalue = minvalue; level.minvalue = minvalue;
level.amplitude = maxvalue - minvalue; level.amplitude = maxvalue - minvalue;
addLevels(level_count, level, 0.5, 0.5, center_factor); 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; NoiseLevel level;
level.wavelength = scaling; level.frequency = 1.0 / scaling;
level.minvalue = minvalue; level.minvalue = minvalue;
level.amplitude = maxvalue - minvalue; level.amplitude = maxvalue - minvalue;
@ -313,7 +313,7 @@ void NoiseGenerator::normalizeAmplitude(double minvalue, double maxvalue, int ad
levels[level].amplitude *= factor; levels[level].amplitude *= factor;
if (adjust_scaling) if (adjust_scaling)
{ {
levels[level].wavelength *= factor; levels[level].frequency /= factor;
} }
} }
height_offset = minvalue + (height_offset - current_minvalue) * 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) 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) 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) 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) 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) 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) double NoiseGenerator::get3DLevel(int level, double x, double y, double z)

View file

@ -28,7 +28,7 @@ public:
typedef struct typedef struct
{ {
double wavelength; double frequency;
double amplitude; double amplitude;
double minvalue; double minvalue;
} NoiseLevel; } NoiseLevel;

View file

@ -90,11 +90,14 @@ void CameraDefinition::validate()
projector = mperspective.mult(Matrix4::newLookAt(location, target, up)); projector = mperspective.mult(Matrix4::newLookAt(location, target, up));
unprojector = projector.inversed(); 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 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; return unperspective.transform(v).z;
} }

View file

@ -85,6 +85,8 @@ private:
Matrix4 projector; Matrix4 projector;
Matrix4 unprojector; Matrix4 unprojector;
Matrix4 unperspective; Matrix4 unperspective;
double inv_x_factor;
double inv_y_factor;
}; };
} }

View file

@ -5,16 +5,18 @@
#include "CameraDefinition.h" #include "CameraDefinition.h"
#include "AtmosphereDefinition.h" #include "AtmosphereDefinition.h"
#include "SoftwareRenderer.h" #include "SoftwareCanvasRenderer.h"
#include "Scenery.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); printf("\rRendering %s ... \n", outputpath);
renderer->start(params); renderer->render();
printf("\rSaving %s ... \n", outputpath); printf("\rSaving %s ... \n", outputpath);
remove(outputpath); remove(outputpath);
renderer->render_area->saveToFile(outputpath); renderer->saveToDisk(outputpath);
} }
void displayHelp() void displayHelp()
@ -43,9 +45,9 @@ void _previewUpdate(double progress)
int main(int argc, char** argv) int main(int argc, char** argv)
{ {
SoftwareRenderer* renderer; SoftwareCanvasRenderer* renderer;
char* conf_file_path = NULL; 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_first_picture = 0;
int conf_nb_pictures = 1; int conf_nb_pictures = 1;
double conf_daytime_start = 0.4; 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}; Vector3 step = {conf_camera_step_x, conf_camera_step_y, conf_camera_step_z};
camera->setLocation(camera->getLocation().add(step)); camera->setLocation(camera->getLocation().add(step));
renderer = new SoftwareRenderer(scenery); renderer = new SoftwareCanvasRenderer();
renderer->setPreviewCallbacks(NULL, NULL, _previewUpdate); renderer->setConfig(conf_render_params);
renderer->setScenery(scenery);
if (outputcount >= conf_first_picture) if (outputcount >= conf_first_picture)
{ {
sprintf(outputpath, "output/pic%05d.png", outputcount); sprintf(outputpath, "output/pic%05d.png", outputcount);
startRender(renderer, outputpath, conf_render_params); startRender(renderer, outputpath);
} }
delete renderer; delete renderer;

View file

@ -0,0 +1,6 @@
#include "WidgetCanvas.h"
WidgetCanvas::WidgetCanvas(QWidget *parent) :
QWidget(parent)
{
}

View file

@ -0,0 +1,29 @@
#ifndef WIDGETCANVAS_H
#define WIDGETCANVAS_H
#include "desktop_global.h"
#include <QWidget>
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

View file

@ -0,0 +1,77 @@
#include "WidgetPreviewCanvas.h"
#include "tools.h"
#include "Canvas.h"
#include "CanvasPreview.h"
#include <QPainter>
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();
}
}

View file

@ -0,0 +1,50 @@
#ifndef WIDGETPREVIEWCANVAS_H
#define WIDGETPREVIEWCANVAS_H
#include "desktop_global.h"
#include <QWidget>
#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

View file

@ -12,9 +12,10 @@
#include "mainwindow.h" #include "mainwindow.h"
#include "dialogrender.h" #include "dialogrender.h"
#include "dialogexplorer.h" #include "dialogexplorer.h"
#include "RenderConfig.h"
#include "DesktopScenery.h" #include "DesktopScenery.h"
#include "BasePreview.h" #include "BasePreview.h"
#include "SoftwareRenderer.h" #include "SoftwareCanvasRenderer.h"
#include "CameraDefinition.h" #include "CameraDefinition.h"
#include "tools.h" #include "tools.h"
@ -246,15 +247,16 @@ void FreeFormHelper::processExploreClicked()
void FreeFormHelper::processRenderClicked() 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); emit needAlterRenderer(&renderer);
DialogRender* dialog = new DialogRender(_form_widget, &renderer); DialogRender dialog(_form_widget, &renderer);
RenderArea::RenderParams params = {400, 300, 1, 3}; dialog.startRender();
dialog->startRender(params);
delete dialog;
} }
void FreeFormHelper::processDecimalChange(double value) void FreeFormHelper::processDecimalChange(double value)

View file

@ -52,7 +52,9 @@ HEADERS += \
lighting/SmallPreviewHues.h \ lighting/SmallPreviewHues.h \
textures/DialogTexturesLayer.h \ textures/DialogTexturesLayer.h \
desktop_global.h \ desktop_global.h \
DesktopScenery.h DesktopScenery.h \
WidgetCanvas.h \
WidgetPreviewCanvas.h
SOURCES += \ SOURCES += \
terrain/widgetheightmap.cpp \ terrain/widgetheightmap.cpp \
@ -96,7 +98,9 @@ SOURCES += \
lighting/SmallPreviewColor.cpp \ lighting/SmallPreviewColor.cpp \
lighting/SmallPreviewHues.cpp \ lighting/SmallPreviewHues.cpp \
textures/DialogTexturesLayer.cpp \ textures/DialogTexturesLayer.cpp \
DesktopScenery.cpp DesktopScenery.cpp \
WidgetCanvas.cpp \
WidgetPreviewCanvas.cpp
FORMS += \ FORMS += \
terrain/dialogterrainpainting.ui \ terrain/dialogterrainpainting.ui \

View file

@ -10,6 +10,9 @@ namespace paysages {
namespace desktop { namespace desktop {
class BaseInput; class BaseInput;
class BaseForm; class BaseForm;
class WidgetCanvas;
class WidgetPreviewCanvas;
} }
} }

View file

@ -282,7 +282,7 @@ void DialogNoise::addLevel()
NoiseGenerator::NoiseLevel level; NoiseGenerator::NoiseLevel level;
level.amplitude = 0.1; level.amplitude = 0.1;
level.wavelength = 0.1; level.frequency = 0.1;
_current->addLevel(level); _current->addLevel(level);
@ -330,7 +330,7 @@ void DialogNoise::levelChanged(int row)
((PreviewLevel*)previewLevel)->setLevel(row); ((PreviewLevel*)previewLevel)->setLevel(row);
slider_height->setValue(_current_level_params.amplitude * 1000.0); 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 ... // TODO else ...
} }
@ -345,7 +345,7 @@ void DialogNoise::heightChanged(int value)
void DialogNoise::scalingChanged(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); _current->setLevel(_current_level, _current_level_params);
previewLevel->redraw(); previewLevel->redraw();
previewTotal->redraw(); previewTotal->redraw();

View file

@ -18,90 +18,45 @@
#include <QComboBox> #include <QComboBox>
#include "tools.h" #include "tools.h"
#include "SoftwareRenderer.h"
#include "Scenery.h" #include "Scenery.h"
#include "ColorProfile.h" #include "ColorProfile.h"
#include "SoftwareCanvasRenderer.h"
static DialogRender* _current_dialog; #include "WidgetPreviewCanvas.h"
#include "Canvas.h"
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);
}
class RenderThread:public QThread class RenderThread:public QThread
{ {
public: public:
RenderThread(DialogRender* dialog, SoftwareRenderer* renderer, RenderArea::RenderParams params):QThread() RenderThread(DialogRender* dialog, SoftwareCanvasRenderer* renderer):QThread()
{ {
_dialog = dialog; _dialog = dialog;
_renderer = renderer; _renderer = renderer;
_params = params;
} }
void run() void run()
{ {
_renderer->start(_params); _renderer->render();
_dialog->tellRenderEnded(); _dialog->tellRenderEnded();
} }
private: private:
DialogRender* _dialog; DialogRender* _dialog;
SoftwareRenderer* _renderer; SoftwareCanvasRenderer* _renderer;
RenderArea::RenderParams _params;
}; };
class _RenderArea:public QWidget DialogRender::DialogRender(QWidget *parent, SoftwareCanvasRenderer* renderer):
{
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):
QDialog(parent, Qt::WindowTitleHint | Qt::WindowMaximizeButtonHint | Qt::WindowMinimizeButtonHint | Qt::WindowCloseButtonHint) QDialog(parent, Qt::WindowTitleHint | Qt::WindowMaximizeButtonHint | Qt::WindowMinimizeButtonHint | Qt::WindowCloseButtonHint)
{ {
pixbuf_lock = new QMutex(); pixbuf_lock = new QMutex();
pixbuf = new QImage(1, 1, QImage::Format_ARGB32); pixbuf = new QImage(1, 1, QImage::Format_ARGB32);
_current_dialog = this;
_render_thread = NULL; _render_thread = NULL;
_renderer = renderer; canvas_renderer = renderer;
setModal(true); setModal(true);
setWindowTitle(tr("Paysages 3D - Render")); setWindowTitle(tr("Paysages 3D - Render"));
setLayout(new QVBoxLayout()); setLayout(new QVBoxLayout());
_scroll = new QScrollArea(this); canvas_preview = new WidgetPreviewCanvas(this);
_scroll->setAlignment(Qt::AlignCenter); canvas_preview->setCanvas(canvas_renderer->getCanvas());
area = new _RenderArea(_scroll); layout()->addWidget(canvas_preview);
_scroll->setWidget(area);
layout()->addWidget(_scroll);
// Status bar // Status bar
_info = new QWidget(this); _info = new QWidget(this);
@ -140,19 +95,19 @@ DialogRender::DialogRender(QWidget *parent, SoftwareRenderer* renderer):
_actions->layout()->addWidget(_save_button); _actions->layout()->addWidget(_save_button);
// Connections // 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(this, SIGNAL(renderEnded()), this, SLOT(applyRenderEnded()));
connect(_save_button, SIGNAL(clicked()), this, SLOT(saveRender())); connect(_save_button, SIGNAL(clicked()), this, SLOT(saveRender()));
connect(_tonemapping_control, SIGNAL(currentIndexChanged(int)), this, SLOT(toneMappingChanged())); connect(_tonemapping_control, SIGNAL(currentIndexChanged(int)), this, SLOT(toneMappingChanged()));
connect(_exposure_control, SIGNAL(valueChanged(int)), this, SLOT(toneMappingChanged())); connect(_exposure_control, SIGNAL(valueChanged(int)), this, SLOT(toneMappingChanged()));
toneMappingChanged();
} }
DialogRender::~DialogRender() DialogRender::~DialogRender()
{ {
if (_render_thread) if (_render_thread)
{ {
_renderer->interrupt(); canvas_renderer->interrupt();
_render_thread->wait(); _render_thread->wait();
delete _render_thread; delete _render_thread;
@ -161,31 +116,20 @@ DialogRender::~DialogRender()
delete pixbuf_lock; 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() void DialogRender::tellRenderEnded()
{ {
emit renderEnded(); emit renderEnded();
} }
void DialogRender::startRender(RenderArea::RenderParams params) void DialogRender::startRender()
{ {
_started = time(NULL); _started = time(NULL);
applyRenderSize(params.width, params.height); _render_thread = new RenderThread(this, canvas_renderer);
_renderer->setPreviewCallbacks(_renderStart, _renderDraw, _renderUpdate);
_render_thread = new RenderThread(this, _renderer, params);
_render_thread->start(); _render_thread->start();
startTimer(100);
exec(); exec();
} }
@ -193,8 +137,6 @@ void DialogRender::applyRenderEnded()
{ {
_info->hide(); _info->hide();
_actions->show(); _actions->show();
area->update();
} }
void DialogRender::saveRender() void DialogRender::saveRender()
@ -208,8 +150,7 @@ void DialogRender::saveRender()
{ {
filepath = filepath.append(".png"); filepath = filepath.append(".png");
} }
std::string filepathstr = filepath.toStdString(); if (canvas_renderer->saveToDisk(filepath.toStdString()))
if (_renderer->render_area->saveToFile((char*)filepathstr.c_str()))
{ {
QMessageBox::information(this, "Message", QString(tr("The picture %1 has been saved.")).arg(filepath)); QMessageBox::information(this, "Message", QString(tr("The picture %1 has been saved.")).arg(filepath));
} }
@ -223,35 +164,26 @@ void DialogRender::saveRender()
void DialogRender::toneMappingChanged() void DialogRender::toneMappingChanged()
{ {
ColorProfile profile((ColorProfile::ToneMappingOperator)_tonemapping_control->currentIndex(), ((double)_exposure_control->value()) * 0.01); 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() void DialogRender::loadLastRender()
{ {
applyRenderSize(_renderer->render_width, _renderer->render_height);
_renderer->setPreviewCallbacks(_renderStart, _renderDraw, _renderUpdate);
renderEnded(); renderEnded();
toneMappingChanged(); toneMappingChanged();
exec(); exec();
} }
void DialogRender::applyRenderSize(int width, int height) void DialogRender::timerEvent(QTimerEvent *)
{
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)
{ {
double diff = difftime(time(NULL), _started); double diff = difftime(time(NULL), _started);
int hours = (int)floor(diff / 3600.0); int hours = (int)floor(diff / 3600.0);
int minutes = (int)floor((diff - 3600.0 * hours) / 60.0); int minutes = (int)floor((diff - 3600.0 * hours) / 60.0);
int seconds = (int)floor(diff - 3600.0 * hours - 60.0 * minutes); 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'))); _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(); _progress->update();
} }

View file

@ -5,7 +5,6 @@
#include <ctime> #include <ctime>
#include <QDialog> #include <QDialog>
#include "RenderArea.h"
class QThread; class QThread;
class QProgressBar; class QProgressBar;
@ -19,33 +18,30 @@ class DialogRender : public QDialog
{ {
Q_OBJECT Q_OBJECT
public: public:
explicit DialogRender(QWidget *parent, SoftwareRenderer* renderer); explicit DialogRender(QWidget *parent, SoftwareCanvasRenderer *renderer);
~DialogRender(); ~DialogRender();
void tellRenderSize(int width, int height);
void tellProgressChange(double value);
void tellRenderEnded(); void tellRenderEnded();
void startRender(RenderArea::RenderParams params); void startRender();
void loadLastRender(); void loadLastRender();
virtual void timerEvent(QTimerEvent *event) override;
QImage* pixbuf; QImage* pixbuf;
QMutex* pixbuf_lock; QMutex* pixbuf_lock;
QWidget* area;
private slots: private slots:
void applyRenderSize(int width, int height);
void applyProgress(double value);
void saveRender(); void saveRender();
void applyRenderEnded(); void applyRenderEnded();
void toneMappingChanged(); void toneMappingChanged();
signals: signals:
void renderSizeChanged(int width, int height);
void progressChanged(double value);
void renderEnded(); void renderEnded();
private: private:
QScrollArea* _scroll; SoftwareCanvasRenderer* canvas_renderer;
WidgetPreviewCanvas* canvas_preview;
QWidget* _info; QWidget* _info;
QWidget* _actions; QWidget* _actions;
QComboBox* _tonemapping_control; QComboBox* _tonemapping_control;
@ -53,7 +49,6 @@ private:
QPushButton* _save_button; QPushButton* _save_button;
QThread* _render_thread; QThread* _render_thread;
QLabel* _timer; QLabel* _timer;
SoftwareRenderer* _renderer;
QProgressBar* _progress; QProgressBar* _progress;
time_t _started; time_t _started;
}; };

View file

@ -7,7 +7,7 @@
#include "tools.h" #include "tools.h"
#include "DesktopScenery.h" #include "DesktopScenery.h"
#include "PackStream.h" #include "PackStream.h"
#include "SoftwareRenderer.h" #include "SoftwareCanvasRenderer.h"
#include "BasePreview.h" #include "BasePreview.h"
#include "CloudsDefinition.h" #include "CloudsDefinition.h"
#include "CameraDefinition.h" #include "CameraDefinition.h"
@ -36,8 +36,8 @@ BaseForm(parent, true)
addInput(new InputCamera(this, tr("Camera"), _camera)); addInput(new InputCamera(this, tr("Camera"), _camera));
addInputInt(tr("Quality"), &_params.quality, 1, 10, 1, 1); addInputInt(tr("Quality"), &_params.quality, 1, 10, 1, 1);
addInputInt(tr("Image width"), &_params.width, 100, 2000, 10, 100); addInputInt(tr("Image width"), &_params.width, 100, 4000, 10, 100);
addInputInt(tr("Image height"), &_params.height, 100, 1200, 10, 100); addInputInt(tr("Image height"), &_params.height, 100, 3000, 10, 100);
addInputInt(tr("Anti aliasing"), &_params.antialias, 1, 4, 1, 1); addInputInt(tr("Anti aliasing"), &_params.antialias, 1, 4, 1, 1);
button = addButton(tr("Start new render")); button = addButton(tr("Start new render"));
@ -103,14 +103,16 @@ void FormRender::startQuickRender()
{ {
delete _renderer; 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; _renderer_inited = true;
DialogRender* dialog = new DialogRender(this, _renderer); DialogRender dialog(this, _renderer);
RenderArea::RenderParams params = {400, 300, 1, 3}; dialog.startRender();
dialog->startRender(params);
delete dialog;
} }
void FormRender::startRender() void FormRender::startRender()
@ -119,22 +121,20 @@ void FormRender::startRender()
{ {
delete _renderer; delete _renderer;
} }
_renderer = new SoftwareRenderer(DesktopScenery::getCurrent()); _renderer = new SoftwareCanvasRenderer();
_renderer->setScenery(DesktopScenery::getCurrent());
_renderer->setConfig(_params);
_renderer_inited = true; _renderer_inited = true;
DialogRender* dialog = new DialogRender(this, _renderer); DialogRender dialog(this, _renderer);
dialog->startRender(_params); dialog.startRender();
delete dialog;
} }
void FormRender::showRender() void FormRender::showRender()
{ {
if (_renderer_inited) if (_renderer_inited)
{ {
DialogRender* dialog = new DialogRender(this, _renderer); DialogRender dialog(this, _renderer);
dialog->loadLastRender(); dialog.loadLastRender();
delete dialog;
} }
} }

View file

@ -5,7 +5,7 @@
#include "baseform.h" #include "baseform.h"
#include "RenderArea.h" #include "RenderConfig.h"
class FormRender : public BaseForm class FormRender : public BaseForm
{ {
@ -29,9 +29,9 @@ protected slots:
virtual void configChangeEvent(); virtual void configChangeEvent();
private: private:
RenderArea::RenderParams _params; RenderConfig _params;
CameraDefinition* _camera; CameraDefinition* _camera;
SoftwareRenderer* _renderer; SoftwareCanvasRenderer* _renderer;
bool _renderer_inited; bool _renderer_inited;
BasePreview* _preview_landscape; BasePreview* _preview_landscape;
Base2dPreviewRenderer* _preview_landscape_renderer; Base2dPreviewRenderer* _preview_landscape_renderer;

View file

@ -0,0 +1,112 @@
#include "Canvas.h"
#include <cassert>
#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);
}

View file

@ -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<CanvasPortion*> portions;
int horizontal_portion_count;
int vertical_portion_count;
int width;
int height;
CanvasPreview *preview;
};
}
}
#endif // CANVAS_H

View file

@ -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;
}

View file

@ -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

View file

@ -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 &)
{
}

View file

@ -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

View file

@ -0,0 +1,116 @@
#include "CanvasPictureWriter.h"
#include <cassert>
#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;
}

View file

@ -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

View file

@ -0,0 +1,106 @@
#include "CanvasPixel.h"
#include <cstring>
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;
}

View file

@ -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

View file

@ -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);
}
}
}
}
}
}

View file

@ -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

View file

@ -0,0 +1,168 @@
#include "CanvasPortion.h"
#include <cassert>
#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());
}
}

View file

@ -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

View file

@ -0,0 +1,182 @@
#include "CanvasPreview.h"
#include "Color.h"
#include "CanvasLiveClient.h"
#include "Mutex.h"
#include "ColorProfile.h"
#include <cassert>
#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();
}

View file

@ -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

View file

@ -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, &current);
}
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);
}
}
}
}

View file

@ -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

View file

@ -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, &current);
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);
}

View file

@ -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

View file

@ -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;
}
}

View file

@ -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

View file

@ -6,30 +6,17 @@
#include "AtmosphereRenderer.h" #include "AtmosphereRenderer.h"
#include "AtmosphereResult.h" #include "AtmosphereResult.h"
#include "CloudsRenderer.h" #include "CloudsRenderer.h"
#include "Rasterizer.h"
#include "CanvasFragment.h"
#define SPHERE_SIZE 20000.0 #define SPHERE_SIZE 20000.0
SkyRasterizer::SkyRasterizer(SoftwareRenderer* renderer): SkyRasterizer::SkyRasterizer(SoftwareRenderer* renderer, int client_id):
renderer(renderer) Rasterizer(renderer, client_id, Color(0.9, 0.9, 1.0))
{ {
} }
static Color _postProcessFragment(SoftwareRenderer* renderer, const Vector3 &location, void*) void SkyRasterizer::rasterizeToCanvas(CanvasPortion* canvas)
{
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()
{ {
int res_i, res_j; int res_i, res_j;
int i, j; int i, j;
@ -47,7 +34,7 @@ void SkyRasterizer::rasterize()
for (j = 0; j < res_j; j++) for (j = 0; j < res_j; j++)
{ {
if (!renderer->addRenderProgress(0.0)) if (interrupted)
{ {
return; return;
} }
@ -79,7 +66,23 @@ void SkyRasterizer::rasterize()
vertex4 = camera_location.add(direction); vertex4 = camera_location.add(direction);
/* TODO Triangles at poles */ /* 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;
}

View file

@ -3,17 +3,18 @@
#include "software_global.h" #include "software_global.h"
#include "Rasterizer.h"
namespace paysages { namespace paysages {
namespace software { namespace software {
class SOFTWARESHARED_EXPORT SkyRasterizer class SOFTWARESHARED_EXPORT SkyRasterizer: public Rasterizer
{ {
public: public:
SkyRasterizer(SoftwareRenderer* renderer); SkyRasterizer(SoftwareRenderer* renderer, int client_id);
void rasterize();
private: virtual void rasterizeToCanvas(CanvasPortion* canvas) override;
SoftwareRenderer* renderer; virtual Color shadeFragment(const CanvasFragment &fragment) const override;
}; };
} }

View file

@ -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;
}
}

View file

@ -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<Rasterizer*> rasterizers;
bool started;
bool interrupted;
ParallelWork *current_work;
};
}
}
#endif // SOFTWARECANVASRENDERER_H

View file

@ -22,17 +22,8 @@
SoftwareRenderer::SoftwareRenderer(Scenery* scenery) SoftwareRenderer::SoftwareRenderer(Scenery* scenery)
{ {
RenderArea::RenderParams params = {1, 1, 1, 5};
render_quality = 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_camera = new CameraDefinition;
render_area = new RenderArea(this);
render_area->setParams(params);
atmosphere_renderer = new BaseAtmosphereRenderer(this); atmosphere_renderer = new BaseAtmosphereRenderer(this);
clouds_renderer = new CloudsRenderer(this); clouds_renderer = new CloudsRenderer(this);
@ -59,7 +50,6 @@ SoftwareRenderer::SoftwareRenderer(Scenery* scenery)
SoftwareRenderer::~SoftwareRenderer() SoftwareRenderer::~SoftwareRenderer()
{ {
delete render_camera; delete render_camera;
delete render_area;
delete fluid_medium; delete fluid_medium;
delete lighting; delete lighting;
@ -108,18 +98,6 @@ void SoftwareRenderer::prepare()
//fluid_medium->registerMedium(water_renderer); //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() void SoftwareRenderer::disableClouds()
{ {
scenery->getClouds()->clear(); scenery->getClouds()->clear();
@ -164,68 +142,6 @@ void SoftwareRenderer::disableAtmosphere(const std::vector<LightComponent> &ligh
atmosphere_renderer->setStaticLights(lights); 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) Color SoftwareRenderer::applyLightingToSurface(const Vector3 &location, const Vector3 &normal, const SurfaceMaterial &material)
{ {
LightStatus status(lighting, location, getCameraLocation(location)); LightStatus status(lighting, location, getCameraLocation(location));
@ -261,11 +177,6 @@ RayCastingResult SoftwareRenderer::rayWalking(const Vector3 &location, const Vec
return result; return result;
} }
int SoftwareRenderer::addRenderProgress(double)
{
return not render_interrupt;
}
Vector3 SoftwareRenderer::getCameraLocation(const Vector3 &) Vector3 SoftwareRenderer::getCameraLocation(const Vector3 &)
{ {
return render_camera->getLocation(); return render_camera->getLocation();
@ -296,37 +207,3 @@ Vector3 SoftwareRenderer::unprojectPoint(const Vector3 &point)
{ {
return render_camera->unproject(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);
}

View file

@ -3,7 +3,6 @@
#include "software_global.h" #include "software_global.h"
#include "RenderArea.h"
#include "RayCastingManager.h" #include "RayCastingManager.h"
namespace paysages { namespace paysages {
@ -21,16 +20,8 @@ public:
/* Render base configuration */ /* Render base configuration */
int render_quality; int render_quality;
int render_width;
int render_height;
CameraDefinition* render_camera; CameraDefinition* render_camera;
/* Render related */
RenderArea* render_area;
double render_progress;
int render_interrupt;
int is_rendering;
void* customData[10]; void* customData[10];
virtual Vector3 getCameraLocation(const Vector3 &target); virtual Vector3 getCameraLocation(const Vector3 &target);
@ -38,11 +29,6 @@ public:
virtual double getPrecision(const Vector3 &location); virtual double getPrecision(const Vector3 &location);
virtual Vector3 projectPoint(const Vector3 &point); virtual Vector3 projectPoint(const Vector3 &point);
virtual Vector3 unprojectPoint(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. * \brief Set the scenery to render.
@ -59,11 +45,6 @@ public:
*/ */
virtual void prepare(); virtual void prepare();
/*!
* \brief Start the rasterization process.
*/
virtual void rasterize();
/*! /*!
* \brief Disable the clouds feature. * \brief Disable the clouds feature.
* *
@ -78,10 +59,6 @@ public:
void disableAtmosphere(); void disableAtmosphere();
void disableAtmosphere(const std::vector<LightComponent> &lights); void disableAtmosphere(const std::vector<LightComponent> &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 Scenery* getScenery() const {return scenery;}
inline BaseAtmosphereRenderer* getAtmosphereRenderer() const {return atmosphere_renderer;} inline BaseAtmosphereRenderer* getAtmosphereRenderer() const {return atmosphere_renderer;}

View file

@ -7,10 +7,11 @@
#include "WaterRenderer.h" #include "WaterRenderer.h"
#include "TexturesRenderer.h" #include "TexturesRenderer.h"
#include "Scenery.h" #include "Scenery.h"
#include "ParallelQueue.h" #include "CanvasPortion.h"
#include "CanvasFragment.h"
TerrainRasterizer::TerrainRasterizer(SoftwareRenderer* renderer): TerrainRasterizer::TerrainRasterizer(SoftwareRenderer* renderer, int client_id):
renderer(renderer) 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); 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)); if (detail < 1)
return renderer->getTerrainRenderer()->getFinalColor(point, precision); {
return;
} }
static void _renderQuad(SoftwareRenderer* renderer, double x, double z, double size, double water_height) 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);
}
}
}
void TerrainRasterizer::renderQuad(CanvasPortion *canvas, double x, double z, double size, double water_height)
{ {
Vector3 ov1, ov2, ov3, ov4; Vector3 ov1, ov2, ov3, ov4;
Vector3 dv1, dv2, dv3, dv4; 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) 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); pushDisplacedQuad(canvas, dv1, dv2, dv3, dv4, ov1, ov2, ov3, ov4);
}
}
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);
}
} }
} }
@ -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; TerrainChunkInfo chunk;
int chunk_factor, chunk_count, i; int chunk_factor, chunk_count, i;
@ -157,25 +152,29 @@ void TerrainRasterizer::getTessellationInfo(int displaced)
for (i = 0; i < chunk_count - 1; i++) for (i = 0; i < chunk_count - 1; i++)
{ {
_getChunk(renderer, &chunk, cx - radius_ext + chunk_size * i, cz - radius_ext, chunk_size, displaced); _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; return;
} }
_getChunk(renderer, &chunk, cx + radius_int, cz - radius_ext + chunk_size * i, chunk_size, displaced); _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; return;
} }
_getChunk(renderer, &chunk, cx + radius_int - chunk_size * i, cz + radius_int, chunk_size, displaced); _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; return;
} }
_getChunk(renderer, &chunk, cx - radius_ext, cz + radius_int - chunk_size * i, chunk_size, displaced); _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; return;
} }
@ -193,48 +192,19 @@ void TerrainRasterizer::getTessellationInfo(int displaced)
} }
} }
typedef struct void TerrainRasterizer::processChunk(CanvasPortion* canvas, TerrainChunkInfo* chunk, double progress)
{ {
TerrainRasterizer* rasterizer; tessellateChunk(canvas, chunk, chunk->detail_hint);
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; void TerrainRasterizer::rasterizeToCanvas(CanvasPortion *canvas)
return 0;
}
int TerrainRasterizer::processChunk(TerrainChunkInfo* chunk, double progress)
{ {
ParallelRasterInfo* info = new ParallelRasterInfo; getTessellationInfo(canvas, 0);
}
info->rasterizer = this; Color TerrainRasterizer::shadeFragment(const CanvasFragment &fragment) const
info->chunk = *chunk;
if (!queue->addJob(_parallelJobCallback, info))
{ {
delete info; Vector3 point = fragment.getLocation();
} double precision = renderer->getPrecision(_getPoint(renderer, point.x, point.z));
return renderer->getTerrainRenderer()->getFinalColor(point, precision);
renderer->render_progress = 0.05 * progress;
return !renderer->render_interrupt;
}
void TerrainRasterizer::renderSurface()
{
queue = new ParallelQueue();
renderer->render_progress = 0.0;
getTessellationInfo(0);
renderer->render_progress = 0.05;
queue->wait();
} }

View file

@ -3,12 +3,13 @@
#include "software_global.h" #include "software_global.h"
#include "Rasterizer.h"
#include "Vector3.h" #include "Vector3.h"
namespace paysages { namespace paysages {
namespace software { namespace software {
class SOFTWARESHARED_EXPORT TerrainRasterizer class SOFTWARESHARED_EXPORT TerrainRasterizer: public Rasterizer
{ {
public: public:
typedef struct typedef struct
@ -21,35 +22,29 @@ public:
} TerrainChunkInfo; } TerrainChunkInfo;
public: public:
TerrainRasterizer(SoftwareRenderer* renderer); TerrainRasterizer(SoftwareRenderer* renderer, int client_id);
/** /**
* Method called for each chunk tessellated by getTessellationInfo. * 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. * Tessellate the terrain, calling processChunk for each chunk.
* *
* The terrain will be broken in chunks, most detailed near the camera. * 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. * 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);
/** void renderQuad(CanvasPortion* canvas, double x, double z, double size, double water_height);
* Start the final rasterization of terrain.
*
* This will push the rasterized quads in the render area, waiting for post process.
*/
void renderSurface();
private: virtual void rasterizeToCanvas(CanvasPortion* canvas) override;
SoftwareRenderer* renderer; virtual Color shadeFragment(const CanvasFragment &fragment) const override;
ParallelQueue* queue;
}; };
} }

View file

@ -40,10 +40,10 @@ double TexturesRenderer::getTriplanarNoise(NoiseGenerator *noise, const Vector3
double mXY = fabs(normal.z); double mXY = fabs(normal.z);
double mXZ = fabs(normal.y); double mXZ = fabs(normal.y);
double mYZ = fabs(normal.x); double mYZ = fabs(normal.x);
double total = mXY + mXZ + mYZ; double total = 1.0 / (mXY + mXZ + mYZ);
mXY /= total; mXY *= total;
mXZ /= total; mXZ *= total;
mYZ /= total; mYZ *= total;
return noiseXY * mXY + noiseXZ * mXZ + noiseYZ * mYZ; return noiseXY * mXY + noiseXZ * mXZ + noiseYZ * mYZ;
} }

View file

@ -2,19 +2,14 @@
#include "SoftwareRenderer.h" #include "SoftwareRenderer.h"
#include "WaterRenderer.h" #include "WaterRenderer.h"
#include "ParallelQueue.h" #include "CanvasFragment.h"
WaterRasterizer::WaterRasterizer(SoftwareRenderer* renderer): WaterRasterizer::WaterRasterizer(SoftwareRenderer* renderer, int client_id):
renderer(renderer) Rasterizer(renderer, client_id, Color(0.9, 0.95, 1.0))
{ {
} }
static Color _postProcessFragment(SoftwareRenderer* renderer, const Vector3 &location, void*) static inline Vector3 _getFirstPassVertex(SoftwareRenderer* renderer, double x, double z)
{
return renderer->getWaterRenderer()->getResult(location.x, location.z).final;
}
static Vector3 _getFirstPassVertex(SoftwareRenderer* renderer, double x, double z)
{ {
Vector3 result; Vector3 result;
@ -25,7 +20,7 @@ static Vector3 _getFirstPassVertex(SoftwareRenderer* renderer, double x, double
return result; 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; 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); v3 = _getFirstPassVertex(renderer, x + size, z + size);
v4 = _getFirstPassVertex(renderer, x + size, z); 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; int chunk_factor, chunk_count, i;
Vector3 cam = renderer->getCameraLocation(VECTOR_ZERO); Vector3 cam = renderer->getCameraLocation(VECTOR_ZERO);
double radius_int, radius_ext, base_chunk_size, chunk_size; double radius_int, radius_ext, base_chunk_size, chunk_size;
@ -91,27 +55,17 @@ void WaterRasterizer::renderSurface()
while (radius_int < 20000.0) while (radius_int < 20000.0)
{ {
if (!renderer->addRenderProgress(0.0)) if (interrupted)
{ {
return; return;
} }
for (i = 0; i < chunk_count - 1; i++) for (i = 0; i < chunk_count - 1; i++)
{ {
info = new ParallelRasterInfo; 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);
info->renderer = renderer; rasterizeQuad(canvas, cx + radius_int - chunk_size * i, cz + radius_int, chunk_size);
info->cx = cx; rasterizeQuad(canvas, cx - radius_ext, cz + radius_int - chunk_size * i, chunk_size);
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;
}
} }
if (radius_int > 20.0 && chunk_count % 64 == 0 && (double)chunk_factor < radius_int / 20.0) 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_int = radius_ext;
radius_ext += chunk_size; 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;
} }

View file

@ -3,18 +3,20 @@
#include "software_global.h" #include "software_global.h"
#include "Rasterizer.h"
namespace paysages { namespace paysages {
namespace software { namespace software {
class WaterRasterizer class WaterRasterizer: public Rasterizer
{ {
public: public:
WaterRasterizer(SoftwareRenderer* renderer); WaterRasterizer(SoftwareRenderer* renderer, int client_id);
void renderSurface(); void rasterizeQuad(CanvasPortion* canvas, double x, double z, double size);
private: virtual void rasterizeToCanvas(CanvasPortion* canvas) override;
SoftwareRenderer* renderer; virtual Color shadeFragment(const CanvasFragment &fragment) const override;
}; };
} }

View file

@ -34,10 +34,20 @@ SOURCES += SoftwareRenderer.cpp \
TerrainRenderer.cpp \ TerrainRenderer.cpp \
TexturesRenderer.cpp \ TexturesRenderer.cpp \
WaterRenderer.cpp \ WaterRenderer.cpp \
RenderArea.cpp \
RayCastingManager.cpp \ RayCastingManager.cpp \
NightSky.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\ HEADERS += SoftwareRenderer.h\
software_global.h \ software_global.h \
@ -61,10 +71,20 @@ HEADERS += SoftwareRenderer.h\
TerrainRenderer.h \ TerrainRenderer.h \
TexturesRenderer.h \ TexturesRenderer.h \
WaterRenderer.h \ WaterRenderer.h \
RenderArea.h \
RayCastingManager.h \ RayCastingManager.h \
NightSky.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 { unix:!symbian {
maemo5 { maemo5 {

View file

@ -14,7 +14,8 @@
namespace paysages { namespace paysages {
namespace software { namespace software {
class SoftwareRenderer; class SoftwareRenderer;
class RenderArea; class SoftwareCanvasRenderer;
class RenderConfig;
class FluidMediumManager; class FluidMediumManager;
class FluidMediumInterface; class FluidMediumInterface;
@ -33,6 +34,7 @@ namespace software {
class TexturesRenderer; class TexturesRenderer;
class WaterRenderer; class WaterRenderer;
class Rasterizer;
class SkyRasterizer; class SkyRasterizer;
class TerrainRasterizer; class TerrainRasterizer;
@ -44,6 +46,15 @@ namespace software {
class NightSky; class NightSky;
class TerrainRayWalker; class TerrainRayWalker;
class Canvas;
class CanvasPortion;
class CanvasPixel;
class CanvasFragment;
class CanvasLiveClient;
class CanvasPreview;
class CanvasPixelShader;
class CanvasPictureWriter;
} }
} }

14
src/system/FileSystem.cpp Normal file
View file

@ -0,0 +1,14 @@
#include "FileSystem.h"
#include <QDir>
#include <QFileInfo>
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();
}

28
src/system/FileSystem.h Normal file
View file

@ -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

View file

@ -114,3 +114,13 @@ std::string PackStream::readString()
return ""; 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);
}

View file

@ -32,6 +32,9 @@ public:
void read(char* value, int max_length); void read(char* value, int max_length);
std::string readString(); std::string readString();
void skip(const int &value, int count=1);
void skip(const double &value, int count=1);
private: private:
QFile* file; QFile* file;
QDataStream* stream; QDataStream* stream;

View file

@ -1,181 +0,0 @@
#include "ParallelQueue.h"
#include "Mutex.h"
#include "Thread.h"
#include "System.h"
#include <cassert>
#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;
}

View file

@ -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

View file

@ -2,113 +2,186 @@
#include "Thread.h" #include "Thread.h"
#include "System.h" #include "System.h"
#include "Semaphore.h"
#include "Mutex.h"
#include "ParallelWorker.h"
#include <cassert> #include <cassert>
/**
* 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) ParallelWork::ParallelWork(ParallelUnitFunction func, int units, void* data)
{ {
this->units = units; this->units = units;
this->running = 0; this->running = 0;
this->unit_function = func; this->worker = new ParallelWorkerCompat(this, func, data);
this->data = data; this->worker_compat = true;
} }
ParallelWork::~ParallelWork() ParallelWork::~ParallelWork()
{ {
assert(not running); assert(not running);
} if (worker_compat)
static void* _workerThreadCallback(ParallelWork::ParallelWorker* worker)
{ {
worker->result = worker->work->unit_function(worker->work, worker->unit, worker->work->data); delete worker;
worker->status = ParallelWork::PARALLEL_WORKER_STATUS_DONE;
return NULL;
}
static int _runNextWorker(ParallelWork::ParallelWorker workers[], int worker_count, int unit)
{
int i;
while (1)
{
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);
} }
} }
int ParallelWork::perform(int nbworkers) int ParallelWork::perform(int thread_count)
{ {
int i, done, result; int i, done;
assert(not running); assert(not running);
result = 0; // Get thread count
if (thread_count <= 0)
if (nbworkers <= 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; running = 1;
/* Init workers */ // Init threads
for (i = 0; i < nbworkers; i++) 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; threads[i] = available[i] = new ParallelThread(this);
workers[i].work = this; threads[i]->start();
} }
/* Perform run */ // Perform all units
for (done = 0; done < units; done++) 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 */ // Wait for all threads to end, then cleanup
for (i = 0; i < nbworkers; i++) for (i = 0; i < thread_count; i++)
{ {
if (workers[i].status != PARALLEL_WORKER_STATUS_VOID) threads[i]->interrupt();
}
for (i = 0; i < thread_count; i++)
{ {
workers[i].thread->join(); threads[i]->join();
delete workers[i].thread; delete threads[i];
if (workers[i].result)
{
result++;
}
}
} }
delete[] threads;
delete[] available;
delete semaphore;
delete mutex;
running = 0; 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();
} }

View file

@ -13,32 +13,24 @@ class SYSTEMSHARED_EXPORT ParallelWork
public: public:
typedef int (*ParallelUnitFunction)(ParallelWork* work, int unit, void* data); typedef int (*ParallelUnitFunction)(ParallelWork* work, int unit, void* data);
typedef enum /**
{ * Obscure thread class.
PARALLEL_WORKER_STATUS_VOID, */
PARALLEL_WORKER_STATUS_RUNNING, class ParallelThread;
PARALLEL_WORKER_STATUS_DONE friend class ParallelThread;
} ParallelWorkerStatus;
typedef struct
{
Thread* thread;
ParallelWork* work;
ParallelWorkerStatus status;
int unit;
int result;
} ParallelWorker;
public: public:
/** /**
* Create a parallel work handler. * 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. * This is a compatibility constructor for older code, use the constructor with ParallelWorker instead.
* @param units Number of units to handle.
* @param data Custom data that will be passed to the callback.
* @return The newly allocated handler.
*/ */
ParallelWork(ParallelUnitFunction func, int units, void* data); ParallelWork(ParallelUnitFunction func, int units, void* data);
@ -52,15 +44,33 @@ public:
/** /**
* Start working on the units. * 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 units;
int running; int running;
ParallelUnitFunction unit_function; ParallelWorker *worker;
ParallelWorker workers[PARALLEL_MAX_THREADS]; bool worker_compat;
void* data;
int thread_count;
Mutex* mutex;
Semaphore* semaphore;
ParallelThread** threads;
ParallelThread** available;
int available_offset;
int available_length;
}; };
} }

View file

@ -0,0 +1,15 @@
#include "ParallelWorker.h"
ParallelWorker::ParallelWorker()
{
interrupted = false;
}
ParallelWorker::~ParallelWorker()
{
}
void ParallelWorker::interrupt()
{
interrupted = true;
}

View file

@ -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

6
src/system/Semaphore.cpp Normal file
View file

@ -0,0 +1,6 @@
#include "Semaphore.h"
Semaphore::Semaphore(int resources):
QSemaphore(resources)
{
}

25
src/system/Semaphore.h Normal file
View file

@ -0,0 +1,25 @@
#ifndef SEMAPHORE_H
#define SEMAPHORE_H
#include "system_global.h"
#include <QSemaphore>
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

View file

@ -29,7 +29,7 @@ public:
* \brief Start the thread * \brief Start the thread
* \param data User data to pass to the threaded function * \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. * \brief Wait for the thread to end, and collect its result.

View file

@ -21,11 +21,13 @@ SOURCES += \
RandomGenerator.cpp \ RandomGenerator.cpp \
Memory.cpp \ Memory.cpp \
ParallelWork.cpp \ ParallelWork.cpp \
ParallelQueue.cpp \
CacheFile.cpp \ CacheFile.cpp \
PictureWriter.cpp \ PictureWriter.cpp \
Logs.cpp \ Logs.cpp \
ParallelPool.cpp ParallelPool.cpp \
ParallelWorker.cpp \
Semaphore.cpp \
FileSystem.cpp
HEADERS += \ HEADERS += \
system_global.h \ system_global.h \
@ -36,11 +38,13 @@ HEADERS += \
RandomGenerator.h \ RandomGenerator.h \
Memory.h \ Memory.h \
ParallelWork.h \ ParallelWork.h \
ParallelQueue.h \
CacheFile.h \ CacheFile.h \
PictureWriter.h \ PictureWriter.h \
Logs.h \ Logs.h \
ParallelPool.h ParallelPool.h \
ParallelWorker.h \
Semaphore.h \
FileSystem.h
unix:!symbian { unix:!symbian {
maemo5 { maemo5 {

View file

@ -15,11 +15,13 @@
namespace paysages { namespace paysages {
namespace system { namespace system {
class PackStream; class PackStream;
class ParallelQueue;
class ParallelWork; class ParallelWork;
class ParallelPool; class ParallelPool;
class ParallelWorker;
class Thread; class Thread;
class Mutex; class Mutex;
class Semaphore;
class PictureWriter;
} }
} }
using namespace paysages::system; using namespace paysages::system;

View file

@ -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");
}

View file

@ -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));
}

View file

@ -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);
}

42
src/tests/Canvas_Test.cpp Normal file
View file

@ -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);
}

View file

@ -49,3 +49,46 @@ TEST(PackStream, All)
} }
delete stream; 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;
}

View file

@ -1,90 +0,0 @@
#include "BaseTestCase.h"
#include <cmath>
#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");
}

View file

@ -10,16 +10,17 @@ SOURCES += main.cpp \
Layers_Test.cpp \ Layers_Test.cpp \
PackStream_Test.cpp \ PackStream_Test.cpp \
NoiseGenerator_Test.cpp \ NoiseGenerator_Test.cpp \
Render_Test.cpp \
TerrainPainting_Test.cpp \ TerrainPainting_Test.cpp \
Zone_Test.cpp \ Zone_Test.cpp \
Euclid_Test.cpp \ Euclid_Test.cpp \
Bruneton_Test.cpp \
Camera_Test.cpp \ Camera_Test.cpp \
Clouds_Test.cpp \ Clouds_Test.cpp \
FluidMediumManager_Test.cpp \ FluidMediumManager_Test.cpp \
VertexArray_Test.cpp \ VertexArray_Test.cpp \
FractalNoise_Test.cpp FractalNoise_Test.cpp \
Canvas_Test.cpp \
CanvasPortion_Test.cpp \
CanvasPreview_Test.cpp
HEADERS += \ HEADERS += \
BaseTestCase.h BaseTestCase.h