mirror of
https://bitbucket.org/mfeemster/fractorium.git
synced 2025-01-21 21:20:07 -05:00
018ba26b5f
-Add support for multiple GPU devices. --These options are present in the command line and in Fractorium. -Change scheme of specifying devices from platform,device to just total device index. --Single number on the command line. --Change from combo boxes for device selection to a table of all devices in Fractorium. -Temporal samples defaults to 100 instead of 1000 which was needless overkill. --Bug fixes -EmberAnimate, EmberRender, FractoriumSettings, FinalRenderDialog: Fix wrong order of arguments to Clamp() when assigning thread priority. -VariationsDC.h: Fix NVidia OpenCL compilation error in DCTriangleVariation. -FractoriumXformsColor.cpp: Checking for null pixmap pointer is not enough, must also check if the underlying buffer is null via call to QPixmap::isNull(). --Code changes -Ember.h: Add case for FLAME_MOTION_NONE and default in ApplyFlameMotion(). -EmberMotion.h: Call base constructor. -EmberPch.h: #pragma once only on Windows. -EmberToXml.h: --Handle different types of exceptions. --Add default cases to ToString(). -Isaac.h: Remove unused variable in constructor. -Point.h: Call base constructor in Color(). -Renderer.h/cpp: --Add bool to Alloc() to only allocate memory for the histogram. Needed for multi-GPU. --Make CoordMap() return a const ref, not a pointer. -SheepTools.h: --Use 64-bit types like the rest of the code already does. --Fix some comment misspellings. -Timing.h: Make BeginTime(), EndTime(), ElapsedTime() and Format() be const functions. -Utils.h: --Add new functions Equal() and Split(). --Handle more exception types in ReadFile(). --Get rid of most legacy blending of C and C++ argument parsing. -XmlToEmber.h: --Get rid of most legacy blending of C and C++ code from flam3. --Remove some unused variables. -EmberAnimate: --Support multi-GPU processing that alternates full frames between devices. --Use OpenCLInfo instead of OpenCLWrapper for --openclinfo option. --Remove bucketT template parameter, and hard code float in its place. --If a render fails, exit since there is no point in continuing an animation with a missing frame. --Pass variables to threaded save better, which most likely fixes a very subtle bug that existed before. --Remove some unused variables. -EmberGenome, EmberRender: --Support multi-GPU processing that alternates full frames between devices. --Use OpenCLInfo instead of OpenCLWrapper for --openclinfo option. --Remove bucketT template parameter, and hard code float in its place. -EmberRender: --Support multi-GPU processing that alternates full frames between devices. --Use OpenCLInfo instead of OpenCLWrapper for --openclinfo option. --Remove bucketT template parameter, and hard code float in its place. --Only print values when not rendering with OpenCL, since they're always 0 in that case. -EmberCLPch.h: --#pragma once only on Windows. --#include <atomic>. -IterOpenCLKernelCreator.h: Add new kernel for summing two histograms. This is needed for multi-GPU. -OpenCLWrapper.h: --Move all OpenCL info related code into its own class OpenCLInfo. --Add members to cache the values of global memory size and max allocation size. -RendererCL.h/cpp: --Redesign to accomodate multi-GPU. --Constructor now takes a vector of devices. --Remove DumpErrorReport() function, it's handled in the base. --ClearBuffer(), ReadPoints(), WritePoints(), ReadHist() and WriteHist() now optionally take a device index as a parameter. --MakeDmap() override and m_DmapCL member removed because it no longer applies since the histogram is always float since the last commit. --Add new function SumDeviceHist() to sum histograms from two devices by first copying to a temporary on the host, then a temporary on the device, then summing. --m_Calls member removed, as it's now per-device. --OpenCLWrapper removed. --m_Seeds member is now a vector of vector of seeds, to accomodate a separate and different array of seeds for each device. --Added member m_Devices, a vector of unique_ptr of RendererCLDevice. -EmberCommon.h --Added Devices() function to convert from a vector of device indices to a vector of platform,device indices. --Changed CreateRenderer() to accept a vector of devices to create a single RendererCL which will split work across multiple devices. --Added CreateRenderers() function to accept a vector of devices to create multiple RendererCL, each which will render on a single device. --Add more comments to some existing functions. -EmberCommonPch.h: #pragma once only on Windows. -EmberOptions.h: --Remove --platform option, it's just sequential device number now with the --device option. --Make --out be OPT_USE_RENDER instead of OPT_RENDER_ANIM since it's an error condition when animating. It makes no sense to write all frames to a single image. --Add Devices() function to parse comma separated --device option string and return a vector of device indices. --Make int and uint types be 64-bit, so intmax_t and size_t. --Make better use of macros. -JpegUtils.h: Make string parameters to WriteJpeg() and WritePng() be const ref. -All project files: Turn off buffer security check option in Visual Studio (/Gs-) -deployment.pri: Remove the line OTHER_FILES +=, it's pointless and was causing problems. -Ember.pro, EmberCL.pro: Add CONFIG += plugin, otherwise it wouldn't link. -EmberCL.pro: Add new files for multi-GPU support. -build_all.sh: use -j4 and QMAKE=${QMAKE:/usr/bin/qmake} -shared_settings.pri: -Add version string. -Remove old DESTDIR definitions. -Add the following lines or else nothing would build: CONFIG(release, debug|release) { CONFIG += warn_off DESTDIR = ../../../Bin/release } CONFIG(debug, debug|release) { DESTDIR = ../../../Bin/debug } QMAKE_POST_LINK += $$quote(cp --update ../../../Data/flam3-palettes.xml $${DESTDIR}$$escape_expand(\n\t)) LIBS += -L/usr/lib -lpthread -AboutDialog.ui: Another futile attempt to make it look correct on Linux. -FinalRenderDialog.h/cpp: --Add support for multi-GPU. --Change from combo boxes for device selection to a table of all devices. --Ensure device selection makes sense. --Remove "FinalRender" prefix of various function names, it's implied given the context. -FinalRenderEmberController.h/cpp: --Add support for multi-GPU. --Change m_FinishedImageCount to be atomic. --Move CancelRender() from the base to FinalRenderEmberController<T>. --Refactor RenderComplete() to omit any progress related functionality or image saving since it can be potentially ran in a thread. --Consolidate setting various renderer fields into SyncGuiToRenderer(). -Fractorium.cpp: Allow for resizing of the options dialog to show the entire device table. -FractoriumCommon.h: Add various functions to handle a table showing the available OpenCL devices on the system. -FractoriumEmberController.h/cpp: Remove m_FinalImageIndex, it's no longer needed. -FractoriumRender.cpp: Scale the interactive sub batch count and quality by the number of devices used. -FractoriumSettings.h/cpp: --Temporal samples defaults to 100 instead of 1000 which was needless overkill. --Add multi-GPU support, remove old device,platform pair. -FractoriumToolbar.cpp: Disable OpenCL toolbar button if there are no devices present on the system. -FractoriumOptionsDialog.h/cpp: --Add support for multi-GPU. --Consolidate more assignments in DataToGui(). --Enable/disable CPU/OpenCL items in response to OpenCL checkbox event. -Misc: Convert almost everything to size_t for unsigned, intmax_t for signed.
353 lines
14 KiB
C++
353 lines
14 KiB
C++
#include "FractoriumPch.h"
|
|
#include "FractoriumEmberController.h"
|
|
#include "Fractorium.h"
|
|
#include "GLEmberController.h"
|
|
|
|
#define SAVE_EACH 1
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
/// <param name="fractorium">Pointer to the main window.</param>
|
|
FractoriumEmberControllerBase::FractoriumEmberControllerBase(Fractorium* fractorium)
|
|
: m_Info(OpenCLInfo::Instance())
|
|
{
|
|
Timing t;
|
|
|
|
m_Rendering = false;
|
|
m_Shared = true;
|
|
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_Fractorium = fractorium;
|
|
m_RenderTimer = nullptr;
|
|
m_RenderRestartTimer = nullptr;
|
|
m_Rand = QTIsaac<ISAAC_SIZE, ISAAC_INT>(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()));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Destructor which stops rendering and deletes the timers.
|
|
/// All other memory is cleared automatically through the use of STL.
|
|
/// </summary>
|
|
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;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
/// <param name="fractorium">Pointer to the main window.</param>
|
|
template <typename T>
|
|
FractoriumEmberController<T>::FractoriumEmberController(Fractorium* fractorium)
|
|
: FractoriumEmberControllerBase(fractorium)
|
|
{
|
|
m_PreviewRun = false;
|
|
m_PreviewRunning = false;
|
|
m_SheepTools = unique_ptr<SheepTools<T, float>>(new SheepTools<T, float>(
|
|
QString(QApplication::applicationDirPath() + "flam3-palettes.xml").toLocal8Bit().data(),
|
|
new EmberNs::Renderer<T, float>()));
|
|
m_GLController = unique_ptr<GLEmberController<T>>(new GLEmberController<T>(fractorium, fractorium->ui.GLDisplay, this));
|
|
m_PreviewRenderer = unique_ptr<EmberNs::Renderer<T, float>>(new EmberNs::Renderer<T, float>());
|
|
|
|
//Initial combo change event to fill the palette table will be called automatically later.
|
|
if (!InitPaletteList(QCoreApplication::applicationDirPath().toLocal8Bit().data()))
|
|
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<T> 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<T>* treeItem = dynamic_cast<EmberTreeWidgetItem<T>*>(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<EmberTreeWidgetItemBase*>(treeItem)),
|
|
Q_ARG(vector<byte>&, 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;
|
|
};
|
|
}
|
|
|
|
/// <summary>
|
|
/// Empty destructor that does nothing.
|
|
/// </summary>
|
|
template <typename T>
|
|
FractoriumEmberController<T>::~FractoriumEmberController() { }
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
template <typename T> void FractoriumEmberController<T>::SetEmber(const Ember<float>& ember, bool verbatim) { SetEmberPrivate<float>(ember, verbatim); }
|
|
template <typename T> void FractoriumEmberController<T>::CopyEmber(Ember<float>& ember, std::function<void(Ember<float>& ember)> perEmberOperation) { ember = m_Ember; perEmberOperation(ember); }
|
|
template <typename T> void FractoriumEmberController<T>::SetEmberFile(const EmberFile<float>& emberFile) { m_EmberFile = emberFile; }
|
|
template <typename T> void FractoriumEmberController<T>::CopyEmberFile(EmberFile<float>& emberFile, std::function<void(Ember<float>& ember)> perEmberOperation)
|
|
{
|
|
emberFile.m_Filename = m_EmberFile.m_Filename;
|
|
CopyVec(emberFile.m_Embers, m_EmberFile.m_Embers, perEmberOperation);
|
|
}
|
|
|
|
template <typename T> void FractoriumEmberController<T>::SetTempPalette(const Palette<float>& palette) { m_TempPalette = palette; }
|
|
template <typename T> void FractoriumEmberController<T>::CopyTempPalette(Palette<float>& palette) { palette = m_TempPalette; }
|
|
#ifdef DO_DOUBLE
|
|
template <typename T> void FractoriumEmberController<T>::SetEmber(const Ember<double>& ember, bool verbatim) { SetEmberPrivate<double>(ember, verbatim); }
|
|
template <typename T> void FractoriumEmberController<T>::CopyEmber(Ember<double>& ember, std::function<void(Ember<double>& ember)> perEmberOperation) { ember = m_Ember; perEmberOperation(ember); }
|
|
template <typename T> void FractoriumEmberController<T>::SetEmberFile(const EmberFile<double>& emberFile) { m_EmberFile = emberFile; }
|
|
template <typename T> void FractoriumEmberController<T>::CopyEmberFile(EmberFile<double>& emberFile, std::function<void(Ember<double>& ember)> perEmberOperation)
|
|
{
|
|
emberFile.m_Filename = m_EmberFile.m_Filename;
|
|
CopyVec(emberFile.m_Embers, m_EmberFile.m_Embers, perEmberOperation);
|
|
}
|
|
|
|
template <typename T> void FractoriumEmberController<T>::SetTempPalette(const Palette<double>& palette) { m_TempPalette = palette; }
|
|
template <typename T> void FractoriumEmberController<T>::CopyTempPalette(Palette<double>& palette) { palette = m_TempPalette; }
|
|
#endif
|
|
template <typename T> Ember<T>* FractoriumEmberController<T>::CurrentEmber() { return &m_Ember; }
|
|
|
|
template <typename T>
|
|
void FractoriumEmberController<T>::ConstrainDimensions(Ember<T>& ember)
|
|
{
|
|
ember.m_FinalRasW = std::min<int>(m_Fractorium->ui.GLDisplay->MaxTexSize(), ember.m_FinalRasW);
|
|
ember.m_FinalRasH = std::min<int>(m_Fractorium->ui.GLDisplay->MaxTexSize(), ember.m_FinalRasH);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Set the ember at the specified index from the currently opened file as the current Ember.
|
|
/// Clears the undo state.
|
|
/// Resets the rendering process.
|
|
/// </summary>
|
|
/// <param name="index">The index in the file from which to retrieve the ember</param>
|
|
template <typename T>
|
|
void FractoriumEmberController<T>::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<T>* emberItem = dynamic_cast<EmberTreeWidgetItem<T>*>(top->child(i)))
|
|
emberItem->setSelected(i == index);
|
|
}
|
|
}
|
|
|
|
ClearUndo();
|
|
SetEmber(m_EmberFile.m_Embers[index]);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Wrapper to call a function, then optionally add the requested action to the rendering queue.
|
|
/// </summary>
|
|
/// <param name="func">The function to call</param>
|
|
/// <param name="updateRender">True to update renderer, else false. Default: true.</param>
|
|
/// <param name="action">The action to add to the rendering queue. Default: FULL_RENDER.</param>
|
|
template <typename T>
|
|
void FractoriumEmberController<T>::Update(std::function<void (void)> func, bool updateRender, eProcessAction action)
|
|
{
|
|
func();
|
|
|
|
if (updateRender)
|
|
UpdateRender(action);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
/// <param name="func">The function to call</param>
|
|
/// <param name="updateType">Whether to apply this update operation on the current, all or selected xforms. Default: UPDATE_CURRENT.</param>
|
|
/// <param name="updateRender">True to update renderer, else false. Default: true.</param>
|
|
/// <param name="action">The action to add to the rendering queue. Default: FULL_RENDER.</param>
|
|
template <typename T>
|
|
void FractoriumEmberController<T>::UpdateXform(std::function<void(Xform<T>*)> 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<T>* xform = CurrentXform())
|
|
func(xform);
|
|
}
|
|
break;
|
|
|
|
case eXformUpdate::UPDATE_SELECTED:
|
|
case eXformUpdate::UPDATE_SELECTED_EXCEPT_FINAL:
|
|
{
|
|
bool anyUpdated = false;
|
|
|
|
while (Xform<T>* xform = (doFinal ? m_Ember.GetTotalXform(i) : m_Ember.GetXform(i)))
|
|
{
|
|
if (QLayoutItem* child = m_Fractorium->m_XformsSelectionLayout->itemAt(i))
|
|
{
|
|
if (auto* w = qobject_cast<QCheckBox*>(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<T>* xform = CurrentXform())
|
|
func(xform);
|
|
}
|
|
|
|
break;
|
|
|
|
case eXformUpdate::UPDATE_ALL:
|
|
{
|
|
while (Xform<T>* xform = m_Ember.GetTotalXform(i++))
|
|
func(xform);
|
|
}
|
|
|
|
break;
|
|
|
|
case eXformUpdate::UPDATE_ALL_EXCEPT_FINAL:
|
|
default:
|
|
{
|
|
while (Xform<T>* xform = m_Ember.GetXform(i++))
|
|
func(xform);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
if (updateRender)
|
|
UpdateRender(action);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
/// <param name="ember">The ember to set as the current</param>
|
|
/// <param name="verbatim">If true, do not overwrite temporal samples, quality or supersample value, else overwrite.</param>
|
|
template <typename T>
|
|
template <typename U>
|
|
void FractoriumEmberController<T>::SetEmberPrivate(const Ember<U>& 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<T> 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();
|
|
FillSummary();
|
|
|
|
//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<float>;
|
|
|
|
#ifdef DO_DOUBLE
|
|
template class FractoriumEmberController<double>;
|
|
#endif
|