New Canvas software rendering structure (WIP)

This commit is contained in:
Michaël Lemaire 2014-06-05 17:12:49 +02:00
parent 57c55970da
commit 43431aae87
31 changed files with 639 additions and 8 deletions

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,46 @@
#include "WidgetPreviewCanvas.h"
#include "Canvas.h"
WidgetPreviewCanvas::WidgetPreviewCanvas(QWidget *parent) :
QWidget(parent), canvas(NULL)
{
startTimer(1000);
}
void WidgetPreviewCanvas::setCanvas(const Canvas *canvas)
{
this->canvas = canvas;
}
void WidgetPreviewCanvas::canvasResized(int width, int height)
{
// TODO
}
void WidgetPreviewCanvas::canvasCleared(const Color &col)
{
// TODO
}
void WidgetPreviewCanvas::canvasPainted(int x, int y, const Color &col)
{
// TODO
}
void WidgetPreviewCanvas::timerEvent(QTimerEvent *)
{
// Refresh the view
if (canvas)
{
int width = canvas->getPreviewWidth();
int height = canvas->getPreviewHeight();
if (QSize(width, height) != this->size())
{
setMaximumSize(width, height);
setMinimumSize(width, height);
resize(width, height);
}
}
}

View file

@ -0,0 +1,39 @@
#ifndef WIDGETPREVIEWCANVAS_H
#define WIDGETPREVIEWCANVAS_H
#include "desktop_global.h"
#include <QWidget>
#include "CanvasLiveClient.h"
namespace paysages {
namespace desktop {
/*!
* \brief 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);
/*!
* \brief Set the canvas to watch and display, null to stop watching.
*/
void setCanvas(const Canvas *canvas);
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:
const Canvas *canvas;
};
}
}
#endif // WIDGETPREVIEWCANVAS_H

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

@ -21,6 +21,8 @@
#include "SoftwareRenderer.h" #include "SoftwareRenderer.h"
#include "Scenery.h" #include "Scenery.h"
#include "ColorProfile.h" #include "ColorProfile.h"
#include "SoftwareCanvasRenderer.h"
#include "WidgetPreviewCanvas.h"
static DialogRender* _current_dialog; static DialogRender* _current_dialog;
@ -103,6 +105,11 @@ DialogRender::DialogRender(QWidget *parent, SoftwareRenderer* renderer):
_scroll->setWidget(area); _scroll->setWidget(area);
layout()->addWidget(_scroll); layout()->addWidget(_scroll);
canvas_renderer = new SoftwareCanvasRenderer();
canvas_preview = new WidgetPreviewCanvas(this);
canvas_preview->setCanvas(canvas_renderer->getCanvas());
layout()->addWidget(canvas_preview);
// Status bar // Status bar
_info = new QWidget(this); _info = new QWidget(this);
_info->setLayout(new QHBoxLayout()); _info->setLayout(new QHBoxLayout());
@ -180,6 +187,8 @@ void DialogRender::startRender(RenderArea::RenderParams params)
{ {
_started = time(NULL); _started = time(NULL);
canvas_renderer->setSize(params.width, params.height, params.antialias);
applyRenderSize(params.width, params.height); applyRenderSize(params.width, params.height);
_renderer->setPreviewCallbacks(_renderStart, _renderDraw, _renderUpdate); _renderer->setPreviewCallbacks(_renderStart, _renderDraw, _renderUpdate);

View file

@ -45,6 +45,9 @@ signals:
void renderEnded(); void renderEnded();
private: private:
SoftwareCanvasRenderer* canvas_renderer;
WidgetPreviewCanvas* canvas_preview;
QScrollArea* _scroll; QScrollArea* _scroll;
QWidget* _info; QWidget* _info;
QWidget* _actions; QWidget* _actions;

View file

@ -0,0 +1,76 @@
#include "Canvas.h"
#include "CanvasPortion.h"
#include <cassert>
Canvas::Canvas()
{
horizontal_portion_count = 1;
vertical_portion_count = 1;
width = 1;
height = 1;
preview_width = 1;
preview_height = 1;
portions.push_back(new CanvasPortion());
}
Canvas::~Canvas()
{
for (auto portion: portions)
{
delete portion;
}
}
void Canvas::setSize(int width, int height)
{
horizontal_portion_count = 1 + width / 400;
vertical_portion_count = 1 + height / 400;
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;
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();
portion->setSize((x == horizontal_portion_count - 1) ? width - done_width : portion_width,
(y == vertical_portion_count - 1) ? height - done_height : portion_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;
this->preview_width = width;
this->preview_height = 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];
}

View file

@ -0,0 +1,46 @@
#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;
inline int getWidth() const {return width;}
inline int getHeight() const {return height;}
inline int getPreviewWidth() const {return preview_width;}
inline int getPreviewHeight() const {return preview_height;}
private:
std::vector<CanvasPortion*> portions;
int horizontal_portion_count;
int vertical_portion_count;
int width;
int height;
int preview_width;
int preview_height;
};
}
}
#endif // CANVAS_H

View file

@ -0,0 +1,5 @@
#include "CanvasFragment.h"
CanvasFragment::CanvasFragment()
{
}

View file

@ -0,0 +1,21 @@
#ifndef CANVASFRAGMENT_H
#define CANVASFRAGMENT_H
#include "software_global.h"
namespace paysages {
namespace software {
/**
* @brief Representation of world coordinates projected in a canvas pixel.
*/
class SOFTWARESHARED_EXPORT CanvasFragment
{
public:
CanvasFragment();
};
}
}
#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,5 @@
#include "CanvasPixel.h"
CanvasPixel::CanvasPixel()
{
}

View file

@ -0,0 +1,23 @@
#ifndef CANVASPIXEL_H
#define CANVASPIXEL_H
#include "software_global.h"
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();
};
}
}
#endif // CANVASPIXEL_H

View file

@ -0,0 +1,12 @@
#include "CanvasPortion.h"
CanvasPortion::CanvasPortion()
{
}
void CanvasPortion::setSize(int width, int height)
{
this->width = width;
this->height = height;
// TODO Resize and clear pixels
}

View file

@ -0,0 +1,43 @@
#ifndef CANVASPORTION_H
#define CANVASPORTION_H
#include "software_global.h"
namespace paysages {
namespace software {
typedef struct {
double red;
double green;
double blue;
} CanvasPreviewPixel;
/**
* @brief Rectangular portion of a Canvas.
*
* Contains the pixels of a canvas region (CanvasPixel).
*/
class SOFTWARESHARED_EXPORT CanvasPortion
{
public:
CanvasPortion();
inline int getWidth() const {return width;}
inline int getHeight() const {return height;}
void setSize(int width, int height);
private:
int width;
int height;
std::vector<CanvasPixel> *pixels;
int preview_width;
int preview_height;
std::vector<CanvasPreviewPixel> *preview_pixels;
};
}
}
#endif // CANVASPORTION_H

View file

@ -0,0 +1,5 @@
#include "Rasterizer.h"
Rasterizer::Rasterizer()
{
}

View file

@ -0,0 +1,21 @@
#ifndef RASTERIZER_H
#define RASTERIZER_H
#include "software_global.h"
namespace paysages {
namespace software {
/**
* @brief Base abstract class for scenery pieces that can be rasterized to polygons.
*/
class SOFTWARESHARED_EXPORT Rasterizer
{
public:
Rasterizer();
};
}
}
#endif // RASTERIZER_H

View file

@ -3,10 +3,12 @@
#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);

View file

@ -0,0 +1,51 @@
#include "SoftwareCanvasRenderer.h"
#include "Rasterizer.h"
#include "SoftwareRenderer.h"
#include "Canvas.h"
SoftwareCanvasRenderer::SoftwareCanvasRenderer()
{
started = false;
renderer = new SoftwareRenderer();
canvas = new Canvas();
}
SoftwareCanvasRenderer::~SoftwareCanvasRenderer()
{
delete renderer;
delete canvas;
}
void SoftwareCanvasRenderer::setSize(int width, int height, int samples)
{
if (not started)
{
canvas->setSize(width * samples, height * samples);
}
}
void SoftwareCanvasRenderer::render()
{
// TEMP
started = true;
CanvasPortion *portion = canvas->at(0, 0);
rasterize(portion, true);
postProcess(portion, true);
}
void SoftwareCanvasRenderer::rasterize(CanvasPortion *portion, bool threaded)
{
std::vector<Rasterizer> rasterizers;
renderer->getRasterizers(&rasterizers);
for (auto &rasterizer:rasterizers)
{
}
}
void SoftwareCanvasRenderer::postProcess(CanvasPortion *portion, bool threaded)
{
// TODO
}

View file

@ -0,0 +1,61 @@
#ifndef SOFTWARECANVASRENDERER_H
#define SOFTWARECANVASRENDERER_H
#include "software_global.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:
SoftwareCanvasRenderer();
~SoftwareCanvasRenderer();
inline const Canvas *getCanvas() const {return canvas;}
/**
* @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();
protected:
/**
* @brief Rasterize the scenery into a canvas portion.
*
* If 'threaded' is true, the rasterization will take advantage of multiple CPU cores.
*/
void rasterize(CanvasPortion* portion, bool threaded);
/**
* @brief Apply post-processing to fragments stored in the CanvasPortion.
*
* If 'threaded' is true, the post-processing will take advantage of multiple CPU cores.
*/
void postProcess(CanvasPortion* portion, bool threaded=true);
private:
SoftwareRenderer* renderer;
Canvas* canvas;
bool started;
};
}
}
#endif // SOFTWARECANVASRENDERER_H

View file

@ -108,6 +108,13 @@ void SoftwareRenderer::prepare()
//fluid_medium->registerMedium(water_renderer); //fluid_medium->registerMedium(water_renderer);
} }
void SoftwareRenderer::getRasterizers(std::vector<Rasterizer> *array)
{
array->push_back(TerrainRasterizer(this));
array->push_back(WaterRasterizer(this));
array->push_back(SkyRasterizer(this));
}
void SoftwareRenderer::rasterize() void SoftwareRenderer::rasterize()
{ {
TerrainRasterizer terrain(this); TerrainRasterizer terrain(this);

View file

@ -59,6 +59,11 @@ public:
*/ */
virtual void prepare(); virtual void prepare();
/*!
* \brief Get the list of objects that can be rasterized to polygons on a canvas.
*/
virtual void getRasterizers(std::vector<Rasterizer> *array);
/*! /*!
* \brief Start the rasterization process. * \brief Start the rasterization process.
*/ */

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

View file

@ -3,10 +3,12 @@
#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);

View file

@ -37,7 +37,14 @@ SOURCES += SoftwareRenderer.cpp \
RenderArea.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
HEADERS += SoftwareRenderer.h\ HEADERS += SoftwareRenderer.h\
software_global.h \ software_global.h \
@ -64,7 +71,14 @@ HEADERS += SoftwareRenderer.h\
RenderArea.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
unix:!symbian { unix:!symbian {
maemo5 { maemo5 {

View file

@ -14,6 +14,7 @@
namespace paysages { namespace paysages {
namespace software { namespace software {
class SoftwareRenderer; class SoftwareRenderer;
class SoftwareCanvasRenderer;
class RenderArea; class RenderArea;
class FluidMediumManager; class FluidMediumManager;
@ -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,12 @@ namespace software {
class NightSky; class NightSky;
class TerrainRayWalker; class TerrainRayWalker;
class Canvas;
class CanvasPortion;
class CanvasPixel;
class CanvasFragment;
class CanvasLiveClient;
} }
} }

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

@ -0,0 +1,41 @@
#include "BaseTestCase.h"
#include "Canvas.h"
#include "CanvasPortion.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.getPreviewWidth());
EXPECT_EQ(100, canvas.getPreviewHeight());
ASSERT_EQ(1, canvas.getHorizontalPortionCount());
ASSERT_EQ(1, canvas.getVerticalPortionCount());
checkPortion(canvas, 0, 0, 200, 100);
canvas.setSize(600, 501);
EXPECT_EQ(600, canvas.getWidth());
EXPECT_EQ(501, canvas.getHeight());
EXPECT_EQ(600, canvas.getPreviewWidth());
EXPECT_EQ(501, canvas.getPreviewHeight());
ASSERT_EQ(2, canvas.getHorizontalPortionCount());
ASSERT_EQ(2, canvas.getVerticalPortionCount());
checkPortion(canvas, 0, 0, 300, 250);
checkPortion(canvas, 0, 1, 300, 251);
checkPortion(canvas, 1, 0, 300, 250);
checkPortion(canvas, 1, 1, 300, 251);
}

View file

@ -19,7 +19,8 @@ SOURCES += main.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
HEADERS += \ HEADERS += \
BaseTestCase.h BaseTestCase.h