#include "FractoriumPch.h"
#include "FractoriumEmberController.h"
#include "Fractorium.h"
#include "GLEmberController.h"
#define SAVE_EACH 1
///
/// Constructor which initializes the non-templated members contained in this class.
/// The renderer, other templated members and GUI setup will be done in the templated derived controller class.
///
/// Pointer to the main window.
FractoriumEmberControllerBase::FractoriumEmberControllerBase(Fractorium* fractorium)
{
Timing t;
m_Rendering = false;
m_Shared = true;
m_Platform = 0;
m_Device = 0;
m_FailedRenders = 0;
m_UndoIndex = 0;
m_RenderType = CPU_RENDERER;
m_OutputTexID = 0;
m_SubBatchCount = 1;//Will be ovewritten by the options on first render.
m_FinalImageIndex = 0;
m_Fractorium = fractorium;
m_RenderTimer = nullptr;
m_RenderRestartTimer = nullptr;
m_Rand = QTIsaac(ISAAC_INT(t.Tic()), ISAAC_INT(t.Tic() * 2), ISAAC_INT(t.Tic() * 3));//Ensure a different rand seed on each instance.
m_RenderTimer = new QTimer(m_Fractorium);
m_RenderTimer->setInterval(0);
m_Fractorium->connect(m_RenderTimer, SIGNAL(timeout()), SLOT(IdleTimer()));
m_RenderRestartTimer = new QTimer(m_Fractorium);
m_Fractorium->connect(m_RenderRestartTimer, SIGNAL(timeout()), SLOT(StartRenderTimer()));
}
///
/// Destructor which stops rendering and deletes the timers.
/// All other memory is cleared automatically through the use of STL.
///
FractoriumEmberControllerBase::~FractoriumEmberControllerBase()
{
StopRenderTimer(true);
if (m_RenderTimer)
{
m_RenderTimer->stop();
delete m_RenderTimer;
m_RenderTimer = nullptr;
}
if (m_RenderRestartTimer)
{
m_RenderRestartTimer->stop();
delete m_RenderRestartTimer;
m_RenderRestartTimer = nullptr;
}
}
///
/// Constructor which passes the main window parameter to the base, initializes the templated members contained in this class.
/// Then sets up the parts of the GUI that require templated Widgets, such as the variations tree and the palette table.
/// Note the renderer is not setup here automatically. Instead, it must be manually created by the caller later.
///
/// Pointer to the main window.
template
FractoriumEmberController::FractoriumEmberController(Fractorium* fractorium)
: FractoriumEmberControllerBase(fractorium)
{
m_PreviewRun = false;
m_PreviewRunning = false;
m_SheepTools = unique_ptr>(new SheepTools("flam3-palettes.xml", new EmberNs::Renderer()));
m_GLController = unique_ptr>(new GLEmberController(fractorium, fractorium->ui.GLDisplay, this));
m_PreviewRenderer = unique_ptr>(new EmberNs::Renderer());
//Initial combo change event to fill the palette table will be called automatically later.
if (!InitPaletteList("./"))
throw "No palettes found, exiting.";
BackgroundChanged(QColor(0, 0, 0));//Default to black.
ClearUndo();
m_PreviewRenderer->Callback(nullptr);
m_PreviewRenderer->NumChannels(4);
m_PreviewRenderer->EarlyClip(m_Fractorium->m_Settings->EarlyClip());
m_PreviewRenderer->YAxisUp(m_Fractorium->m_Settings->YAxisUp());
m_PreviewRenderer->SetEmber(m_Ember);//Give it an initial ember, will be updated many times later.
//m_PreviewRenderer->ThreadCount(1);//For debugging.
m_PreviewRenderFunc = [&](uint start, uint end)
{
while(m_PreviewRun || m_PreviewRunning)
{
}
m_PreviewRun = true;
m_PreviewRunning = true;
m_PreviewRenderer->ThreadCount(std::max(1u, Timing::ProcessorCount() - 1));//Leave one processor free so the GUI can breathe.
QTreeWidget* tree = m_Fractorium->ui.LibraryTree;
if (QTreeWidgetItem* top = tree->topLevelItem(0))
{
for (size_t i = start; m_PreviewRun && i < end && i < m_EmberFile.Size(); i++)
{
Ember ember = m_EmberFile.m_Embers[i];
ember.SyncSize();
ember.SetSizeAndAdjustScale(PREVIEW_SIZE, PREVIEW_SIZE, false, SCALE_WIDTH);
ember.m_TemporalSamples = 1;
ember.m_Quality = 25;
ember.m_Supersample = 1;
m_PreviewRenderer->SetEmber(ember);
if (m_PreviewRenderer->Run(m_PreviewFinalImage) == RENDER_OK)
{
if (EmberTreeWidgetItem* treeItem = dynamic_cast*>(top->child(i)))
{
//It is critical that Qt::BlockingQueuedConnection is passed because this is running on a different thread than the UI.
//This ensures the events are processed in order as each preview is updated, and that control does not return here
//until the update is complete.
QMetaObject::invokeMethod(m_Fractorium, "SetLibraryTreeItemData", Qt::BlockingQueuedConnection,
Q_ARG(EmberTreeWidgetItemBase*, dynamic_cast(treeItem)),
Q_ARG(vector&, m_PreviewFinalImage),
Q_ARG(uint, PREVIEW_SIZE),
Q_ARG(uint, PREVIEW_SIZE));
//treeItem->SetImage(m_PreviewFinalImage, PREVIEW_SIZE, PREVIEW_SIZE);
}
}
}
}
m_PreviewRun = false;
m_PreviewRunning = false;
};
}
///
/// Empty destructor that does nothing.
///
template
FractoriumEmberController::~FractoriumEmberController() { }
///
/// Setters for embers, ember files and palettes which convert between float and double types.
/// These are used to preserve the current ember/file when switching between renderers.
/// Note that some precision will be lost when going from double to float.
///
template void FractoriumEmberController::SetEmber(const Ember& ember, bool verbatim) { SetEmberPrivate(ember, verbatim); }
template void FractoriumEmberController::CopyEmber(Ember& ember, std::function& ember)> perEmberOperation) { ember = m_Ember; perEmberOperation(ember); }
template void FractoriumEmberController::SetEmberFile(const EmberFile& emberFile) { m_EmberFile = emberFile; }
template void FractoriumEmberController::CopyEmberFile(EmberFile& emberFile, std::function& ember)> perEmberOperation)
{
emberFile.m_Filename = m_EmberFile.m_Filename;
CopyVec(emberFile.m_Embers, m_EmberFile.m_Embers, perEmberOperation);
}
template void FractoriumEmberController::SetTempPalette(const Palette& palette) { m_TempPalette = palette; }
template void FractoriumEmberController::CopyTempPalette(Palette& palette) { palette = m_TempPalette; }
#ifdef DO_DOUBLE
template void FractoriumEmberController::SetEmber(const Ember& ember, bool verbatim) { SetEmberPrivate(ember, verbatim); }
template void FractoriumEmberController::CopyEmber(Ember& ember, std::function& ember)> perEmberOperation) { ember = m_Ember; perEmberOperation(ember); }
template void FractoriumEmberController::SetEmberFile(const EmberFile& emberFile) { m_EmberFile = emberFile; }
template void FractoriumEmberController::CopyEmberFile(EmberFile& emberFile, std::function& ember)> perEmberOperation)
{
emberFile.m_Filename = m_EmberFile.m_Filename;
CopyVec(emberFile.m_Embers, m_EmberFile.m_Embers, perEmberOperation);
}
template void FractoriumEmberController::SetTempPalette(const Palette& palette) { m_TempPalette = palette; }
template void FractoriumEmberController::CopyTempPalette(Palette& palette) { palette = m_TempPalette; }
#endif
template Ember* FractoriumEmberController::CurrentEmber() { return &m_Ember; }
template
void FractoriumEmberController::ConstrainDimensions(Ember& ember)
{
ember.m_FinalRasW = std::min(m_Fractorium->ui.GLDisplay->MaxTexSize(), ember.m_FinalRasW);
ember.m_FinalRasH = std::min(m_Fractorium->ui.GLDisplay->MaxTexSize(), ember.m_FinalRasH);
}
///
/// Set the ember at the specified index from the currently opened file as the current Ember.
/// Clears the undo state.
/// Resets the rendering process.
///
/// The index in the file from which to retrieve the ember
template
void FractoriumEmberController::SetEmber(size_t index)
{
if (index < m_EmberFile.Size())
{
if (QTreeWidgetItem* top = m_Fractorium->ui.LibraryTree->topLevelItem(0))
{
for (uint i = 0; i < top->childCount(); i++)
{
if (EmberTreeWidgetItem* emberItem = dynamic_cast*>(top->child(i)))
emberItem->setSelected(i == index);
}
}
ClearUndo();
SetEmber(m_EmberFile.m_Embers[index]);
}
}
///
/// Wrapper to call a function, then optionally add the requested action to the rendering queue.
///
/// The function to call
/// True to update renderer, else false. Default: true.
/// The action to add to the rendering queue. Default: FULL_RENDER.
template
void FractoriumEmberController::Update(std::function func, bool updateRender, eProcessAction action)
{
func();
if (updateRender)
UpdateRender(action);
}
///
/// Wrapper to call a function on the specified xforms, then optionally add the requested action to the rendering queue.
/// If no xforms are selected via the checkboxes, and the update type is UPDATE_SELECTED, then the function will be called only on the currently selected xform.
///
/// The function to call
/// Whether to apply this update operation on the current, all or selected xforms. Default: UPDATE_CURRENT.
/// True to update renderer, else false. Default: true.
/// The action to add to the rendering queue. Default: FULL_RENDER.
template
void FractoriumEmberController::UpdateXform(std::function*)> func, eXformUpdate updateType, bool updateRender, eProcessAction action)
{
size_t i = 0;
bool isCurrentFinal = m_Ember.IsFinalXform(CurrentXform());
bool doFinal = updateType != eXformUpdate::UPDATE_SELECTED_EXCEPT_FINAL && updateType != eXformUpdate::UPDATE_ALL_EXCEPT_FINAL;
switch (updateType)
{
case eXformUpdate::UPDATE_CURRENT:
{
if (Xform* xform = CurrentXform())
func(xform);
}
break;
case eXformUpdate::UPDATE_SELECTED:
case eXformUpdate::UPDATE_SELECTED_EXCEPT_FINAL:
{
bool anyUpdated = false;
while (Xform* xform = (doFinal ? m_Ember.GetTotalXform(i) : m_Ember.GetXform(i)))
{
if (QLayoutItem* child = m_Fractorium->m_XformsSelectionLayout->itemAt(i))
{
if (auto* w = dynamic_cast(child->widget()))
{
if (w->isChecked())
{
func(xform);
anyUpdated = true;
}
}
}
i++;
}
if (!anyUpdated)//None were selected, so just apply to the current.
if (doFinal || !isCurrentFinal)//If do final, call func regardless. If not, only call if current is not final.
if (Xform* xform = CurrentXform())
func(xform);
}
break;
case eXformUpdate::UPDATE_ALL:
{
while (Xform* xform = m_Ember.GetTotalXform(i++))
func(xform);
}
break;
case eXformUpdate::UPDATE_ALL_EXCEPT_FINAL:
default:
{
while (Xform* xform = m_Ember.GetXform(i++))
func(xform);
}
break;
}
if (updateRender)
UpdateRender(action);
}
///
/// Set the current ember, but use GUI values for the fields which make sense to
/// keep the same between ember selection changes.
/// Note the extra template parameter U allows for assigning ember of different types.
/// Resets the rendering process.
///
/// The ember to set as the current
template
template
void FractoriumEmberController::SetEmberPrivate(const Ember& ember, bool verbatim)
{
if (ember.m_Name != m_Ember.m_Name)
m_LastSaveCurrent = "";
size_t w = m_Ember.m_FinalRasW;//Cache values for use below.
size_t h = m_Ember.m_FinalRasH;
m_Ember = ember;
if (!verbatim)
{
//m_Ember.SetSizeAndAdjustScale(m_Fractorium->ui.GLDisplay->width(), m_Fractorium->ui.GLDisplay->height(), true, SCALE_WIDTH);
m_Ember.m_TemporalSamples = 1;//Change once animation is supported.
m_Ember.m_Quality = m_Fractorium->m_QualitySpin->value();
m_Ember.m_Supersample = m_Fractorium->m_SupersampleSpin->value();
}
#ifdef SAVE_EACH
static EmberToXml writer;
string filename = "last.flame";
writer.Save(filename.c_str(), m_Ember, 0, true, false, true);
#endif
m_GLController->ResetMouseState();
FillXforms();//Must do this first because the palette setup in FillParamTablesAndPalette() uses the xforms combo.
FillParamTablesAndPalette();
//If a resize happened, this won't do anything because the new size is not reflected in the scroll area yet.
//However, it will have been taken care of in SyncSizes() in that case, so it's ok.
//This is for when a new ember with the same size was loaded. If it was larger than the scroll area, and was scrolled, re-center it.
if (m_Ember.m_FinalRasW == w && m_Ember.m_FinalRasH == h)
m_Fractorium->CenterScrollbars();
}
template class FractoriumEmberController;
#ifdef DO_DOUBLE
template class FractoriumEmberController;
#endif