2023-04-25 19:59:54 -04:00
# include "FractoriumPch.h"
# include "Fractorium.h"
/// <summary>
/// Return whether the render timer is running.
/// </summary>
/// <returns>True if running, else false.</returns>
bool FractoriumEmberControllerBase : : RenderTimerRunning ( )
{
return m_RenderTimer - > isActive ( ) ;
}
/// <summary>
/// Start the render timer.
/// If a renderer has not been created yet, it will be created from the options.
/// </summary>
void FractoriumEmberControllerBase : : StartRenderTimer ( )
{
UpdateRender ( ) ;
m_RenderTimer - > start ( ) ;
m_RenderElapsedTimer . Tic ( ) ;
}
/// <summary>
/// Start the render timer after a short delay.
/// If the timer is already running, stop it first.
/// This is useful for stopping and restarting the render
/// process in response to things like a window resize.
/// </summary>
void FractoriumEmberControllerBase : : DelayedStartRenderTimer ( )
{
DeleteRenderer ( ) ;
m_RenderRestartTimer - > setSingleShot ( true ) ;
m_RenderRestartTimer - > start ( 300 ) ; //Will stop the timer if it's already running, and start again.
}
/// <summary>
/// Stop the render timer and abort the rendering process.
/// Optionally block until stopping is complete.
/// </summary>
/// <param name="wait">True to block, else false.</param>
void FractoriumEmberControllerBase : : StopRenderTimer ( bool wait )
{
m_RenderTimer - > stop ( ) ;
if ( m_Renderer . get ( ) )
m_Renderer - > Abort ( ) ;
if ( wait )
{
while ( m_Rendering | | RenderTimerRunning ( ) | | ( Renderer ( ) & & ( ! m_Renderer - > Aborted ( ) | | m_Renderer - > InRender ( ) ) ) )
QApplication : : processEvents ( ) ;
}
}
/// <summary>
/// Stop all timers, rendering and drawing and block until they are done.
/// </summary>
void FractoriumEmberControllerBase : : Shutdown ( )
{
StopRenderTimer ( true ) ;
ClearFinalImages ( ) ;
while ( m_Fractorium - > ui . GLDisplay - > Drawing ( ) )
QApplication : : processEvents ( ) ;
}
/// <summary>
/// Clear the output image buffers.
/// </summary>
void FractoriumEmberControllerBase : : ClearFinalImages ( )
{
Memset ( m_FinalImage ) ;
//Unsure if we should also call RendererCL::ClearFinal() as well. At the moment it seems unnecessary.
}
/// <summary>
/// Update the state of the renderer.
/// Upon changing values, some intelligence is used to avoid blindly restarting the
/// entire iteration proceess every time a value changes. This is because some values don't affect the
/// iteration, and only affect filtering and final accumulation. They are broken into three categories:
/// 1) Restart the entire process.
/// 2) Log/density filter, then final accum.
/// 3) Final accum only.
/// 4) Continue iterating.
/// </summary>
/// <param name="action">The action to take</param>
void FractoriumEmberControllerBase : : UpdateRender ( eProcessAction action )
{
AddProcessAction ( action ) ;
m_RenderElapsedTimer . Tic ( ) ;
}
/// <summary>
/// Call Shutdown() then delete the renderer and clear the textures in the output window if there is one.
/// Note the name is somewhat misleading because a new empty renderer is actually created as a placeholder.
/// This is that the program won't crash if the user adjusts any of the controls while the renderer is shut down.
/// </summary>
template < typename T >
void FractoriumEmberController < T > : : DeleteRenderer ( )
{
Shutdown ( ) ;
m_Renderer = make_unique < EmberNs : : Renderer < T , float > > ( ) ;
if ( GLController ( ) )
GLController ( ) - > ClearWindow ( ) ;
}
/// <summary>
/// Save the current render results to a file.
/// This could benefit from QImageWriter, however its compression capabilities are
/// severely lacking. A Png file comes out larger than a bitmap, so instead use the
/// Png and Jpg wrapper functions from the command line programs.
/// This will embed the id, url and nick fields from the options in the image comments.
/// </summary>
/// <param name="filename">The full path and filename</param>
/// <param name="comments">The comments to save in the png, jpg or exr</param>
/// <param name="pixels">The buffer containing the pixels</param>
/// <param name="width">The width in pixels of the image</param>
/// <param name="height">The height in pixels of the image</param>
/// <param name="png16Bit">Whether to use 16 bits per channel per pixel when saving as Png/32-bits per channel when saving as Exr.</param>
/// <param name="transparency">Whether to use alpha when saving as Png or Exr.</param>
void FractoriumEmberControllerBase : : SaveCurrentRender ( const QString & filename , const EmberImageComments & comments , vector < v4F > & pixels , size_t width , size_t height , bool png16Bit , bool transparency )
{
if ( filename ! = " " )
{
bool ret = false ;
const auto size = width * height ;
auto settings = m_Fractorium - > m_Settings ;
QFileInfo fileInfo ( filename ) ;
QString suffix = fileInfo . suffix ( ) ;
string s = filename . toStdString ( ) ;
string id = settings - > Id ( ) . toStdString ( ) ;
string url = settings - > Url ( ) . toStdString ( ) ;
string nick = settings - > Nick ( ) . toStdString ( ) ;
//Ensure dimensions are valid.
if ( pixels . size ( ) < size )
{
m_Fractorium - > ShowCritical ( " Save Failed " , " Dimensions didn't match, not saving. " , true ) ;
return ;
}
auto data = pixels . data ( ) ;
if ( suffix . endsWith ( " bmp " , Qt : : CaseInsensitive ) | | suffix . endsWith ( " jpg " , Qt : : CaseInsensitive ) )
{
vector < unsigned char > rgb8Image ( size * 3 ) ;
Rgba32ToRgb8 ( data , rgb8Image . data ( ) , width , height ) ;
if ( suffix . endsWith ( " bmp " , Qt : : CaseInsensitive ) )
ret = WriteBmp ( s . c_str ( ) , rgb8Image . data ( ) , width , height ) ;
else if ( suffix . endsWith ( " jpg " , Qt : : CaseInsensitive ) )
ret = WriteJpeg ( s . c_str ( ) , rgb8Image . data ( ) , width , height , 100 , true , comments , id , url , nick ) ;
}
else if ( suffix . endsWith ( " png " , Qt : : CaseInsensitive ) )
{
if ( ! png16Bit )
{
vector < unsigned char > rgba8Image ( size * 4 ) ;
Rgba32ToRgba8 ( data , rgba8Image . data ( ) , width , height , transparency ) ;
ret = WritePng ( s . c_str ( ) , rgba8Image . data ( ) , width , height , 1 , true , comments , id , url , nick ) ;
}
else
{
vector < glm : : uint16 > rgba16Image ( size * 4 ) ;
Rgba32ToRgba16 ( data , rgba16Image . data ( ) , width , height , transparency ) ;
ret = WritePng ( s . c_str ( ) , ( unsigned char * ) rgba16Image . data ( ) , width , height , 2 , true , comments , id , url , nick ) ;
}
}
else if ( suffix . endsWith ( " exr " , Qt : : CaseInsensitive ) )
{
if ( ! png16Bit ) //Repurpose this for EXR 32-bit.
{
vector < Rgba > rgba32Image ( size ) ;
Rgba32ToRgbaExr ( data , rgba32Image . data ( ) , width , height , transparency ) ;
ret = WriteExr16 ( s . c_str ( ) , rgba32Image . data ( ) , width , height , true , comments , id , url , nick ) ;
}
else
{
vector < float > r ( size ) ;
vector < float > g ( size ) ;
vector < float > b ( size ) ;
vector < float > a ( size ) ;
Rgba32ToRgba32Exr ( data , r . data ( ) , g . data ( ) , b . data ( ) , a . data ( ) , width , height , transparency ) ;
ret = WriteExr32 ( s . c_str ( ) , r . data ( ) , g . data ( ) , b . data ( ) , a . data ( ) , width , height , true , comments , id , url , nick ) ;
}
}
else
{
m_Fractorium - > ShowCritical ( " Save Failed " , " Unrecognized format " + suffix + " , not saving. " , true ) ;
return ;
}
if ( ret )
settings - > SaveFolder ( fileInfo . canonicalPath ( ) ) ;
else
m_Fractorium - > ShowCritical ( " Save Failed " , " Could not save file, try saving to a different folder. " , true ) ;
}
}
/// <summary>
/// Add a process action to the list of actions to take.
/// Called in response to the user changing something on the GUI.
/// </summary>
/// <param name="action">The action for the renderer to take</param>
void FractoriumEmberControllerBase : : AddProcessAction ( eProcessAction action )
{
rlg l ( m_Cs ) ;
m_ProcessActions . push_back ( action ) ;
if ( m_Renderer . get ( ) )
m_Renderer - > Abort ( ) ;
}
/// <summary>
/// Condense and clear the process actions into a single action and return.
/// Many actions may be specified, but only the one requiring the greatest amount
/// of processing matters. Extract and return the greatest and clear the vector.
/// </summary>
/// <returns>The most significant processing action desired</returns>
eProcessAction FractoriumEmberControllerBase : : CondenseAndClearProcessActions ( )
{
rlg l ( m_Cs ) ;
auto action = eProcessAction : : NOTHING ;
for ( auto a : m_ProcessActions )
if ( a > action )
action = a ;
m_ProcessActions . clear ( ) ;
return action ;
}
/// <summary>
/// Render progress callback function to update progress bar.
/// </summary>
/// <param name="ember">The ember currently being rendered</param>
/// <param name="foo">An extra dummy parameter</param>
/// <param name="fraction">The progress fraction from 0-100</param>
/// <param name="stage">The stage of iteration. 1 is iterating, 2 is density filtering, 2 is final accumulation.</param>
/// <param name="etaMs">The estimated milliseconds to completion of the current stage</param>
/// <returns>0 if the user has changed anything on the GUI, else 1 to continue rendering.</returns>
template < typename T >
int FractoriumEmberController < T > : : ProgressFunc ( Ember < T > & ember , void * foo , double fraction , int stage , double etaMs )
{
QString status ;
QMetaObject : : invokeMethod ( m_Fractorium - > m_ProgressBar , " setValue " , Qt : : QueuedConnection , Q_ARG ( const int , int ( fraction ) ) ) ; //Only really applies to iter and filter, because final accum only gives progress 0 and 100.
if ( stage = = 0 )
status = " Iterating " ;
else if ( stage = = 1 )
status = " Density Filtering " ;
else if ( stage = = 2 )
status = " Spatial Filtering + Final Accumulation " ;
QMetaObject : : invokeMethod ( m_Fractorium - > m_RenderStatusLabel , " setText " , Qt : : QueuedConnection , Q_ARG ( const QString & , status ) ) ;
return m_ProcessActions . empty ( ) ? 1 : 0 ; //If they've done anything, abort.
}
/// <summary>
/// Clear the undo list as well as the undo/redo index and state.
/// </summary>
template < typename T >
void FractoriumEmberController < T > : : ClearUndo ( )
{
m_UndoIndex = 0 ;
m_UndoList . clear ( ) ;
m_EditState = eEditUndoState : : REGULAR_EDIT ;
m_LastEditWasUndoRedo = false ;
m_Fractorium - > ui . ActionUndo - > setEnabled ( false ) ;
m_Fractorium - > ui . ActionRedo - > setEnabled ( false ) ;
}
/// <summary>
/// The hierarchy/order of sizes is like so:
/// Ember
/// GL Widget
/// Texture (passed to RendererCL)
/// Viewport
/// Since this uses m_GL->SizesMatch(), which uses the renderer's dimensions, this
/// must be called after the renderer has set the current ember.
/// </summary>
/// <returns>True if dimensions had to be resized due to a mismatch, else false.</returns>
template < typename T >
bool FractoriumEmberController < T > : : SyncSizes ( )
{
bool changed = false ;
auto gl = m_Fractorium - > ui . GLDisplay ;
RendererCL < T , float > * rendererCL = nullptr ;
if ( gl & & ! m_GLController - > SizesMatch ( ) )
{
m_GLController - > ClearWindow ( ) ;
gl - > SetDimensions ( static_cast < int > ( m_Ember . m_FinalRasW ) , static_cast < int > ( m_Ember . m_FinalRasH ) ) ;
gl - > Allocate ( ) ;
gl - > SetViewport ( ) ;
if ( m_Renderer - > RendererType ( ) = = eRendererType : : OPENCL_RENDERER & & ( rendererCL = dynamic_cast < RendererCL < T , float > * > ( m_Renderer . get ( ) ) ) )
rendererCL - > SetOutputTexture ( gl - > OutputTexID ( ) ) ;
m_Fractorium - > CenterScrollbars ( ) ;
changed = true ;
}
return changed ;
}
/// <summary>
/// The main rendering function.
/// Called whenever the event loop is idle.
/// </summary>
/// <returns>True if nothing went wrong, else false.</returns>
template < typename T >
bool FractoriumEmberController < T > : : Render ( )
{
if ( ! m_Renderer . get ( ) )
return false ;
m_Rendering = true ;
bool success = true ;
auto gl = m_Fractorium - > ui . GLDisplay ;
RendererCL < T , float > * rendererCL = nullptr ;
eProcessAction qualityAction = eProcessAction : : NOTHING , action = eProcessAction : : NOTHING ;
//Quality is the only parameter we update inside the timer.
//This is to allow the user to rapidly increase the quality spinner
//without fully resetting the render. Instead, it will just keep iterating
//where it last left off in response to an increase.
T d = static_cast < T > ( m_Fractorium - > m_QualitySpin - > value ( ) ) ;
if ( d < m_Ember . m_Quality ) //Full restart if quality decreased.
{
m_Ember . m_Quality = d ;
qualityAction = eProcessAction : : FULL_RENDER ;
}
else if ( d > m_Ember . m_Quality & & ProcessState ( ) = = eProcessState : : ACCUM_DONE ) //If quality increased, keep iterating after current render finishes.
{
m_Ember . m_Quality = d ;
qualityAction = eProcessAction : : KEEP_ITERATING ;
}
else if ( IsClose ( d , m_Ember . m_Quality ) )
qualityAction = eProcessAction : : NOTHING ;
if ( qualityAction = = eProcessAction : : FULL_RENDER )
Update ( [ & ] { } ) ; //Stop the current render, a full restart is needed.
else if ( qualityAction = = eProcessAction : : KEEP_ITERATING )
m_ProcessActions . push_back ( qualityAction ) ; //Special, direct call to avoid resetting the render inside Update() because only KEEP_ITERATING is needed.
action = CondenseAndClearProcessActions ( ) ; //Combine with all other previously requested actions.
if ( m_Renderer - > RendererType ( ) = = eRendererType : : OPENCL_RENDERER )
rendererCL = dynamic_cast < RendererCL < T , float > * > ( m_Renderer . get ( ) ) ;
//Force temporal samples to always be 1. Perhaps change later when animation is implemented.
m_Ember . m_TemporalSamples = 1 ;
//Take care of solo xforms and set the current ember and action.
if ( action ! = eProcessAction : : NOTHING )
{
size_t i = 0 ;
const auto solo = m_Ember . m_Solo ;
const bool forceFinal = m_Fractorium - > HaveFinal ( ) ;
if ( solo ! = - 1 )
{
m_TempOpacities . resize ( m_Ember . TotalXformCount ( forceFinal ) ) ;
while ( auto xform = m_Ember . GetTotalXform ( i ) )
{
m_TempOpacities [ i ] = xform - > m_Opacity ;
xform - > m_Opacity = i + + = = solo ? 1 : 0 ;
}
}
i = 0 ;
m_Renderer - > SetEmber ( m_Ember , action , true ) ;
if ( solo ! = - 1 )
while ( auto xform = m_Ember . GetTotalXform ( i , forceFinal ) )
xform - > m_Opacity = m_TempOpacities [ i + + ] ;
}
//Ensure sizes are equal and if not, update dimensions.
if ( SyncSizes ( ) )
{
action = eProcessAction : : FULL_RENDER ;
return true ;
}
//Determining if a completely new rendering process is being started.
const auto iterBegin = ProcessState ( ) = = eProcessState : : NONE ;
if ( iterBegin )
{
if ( m_Renderer - > RendererType ( ) = = eRendererType : : CPU_RENDERER )
m_SubBatchCount = m_Fractorium - > m_Settings - > CpuSubBatch ( ) ;
else if ( m_Renderer - > RendererType ( ) = = eRendererType : : OPENCL_RENDERER )
m_SubBatchCount = m_Fractorium - > m_Settings - > OpenCLSubBatch ( ) ;
m_Fractorium - > m_ProgressBar - > setValue ( 0 ) ;
const QString status = " Starting " ;
QMetaObject : : invokeMethod ( m_Fractorium - > m_RenderStatusLabel , " setText " , Qt : : QueuedConnection , Q_ARG ( const QString & , status ) ) ;
}
//If the rendering process hasn't finished, render with the current specified action.
if ( ProcessState ( ) ! = eProcessState : : ACCUM_DONE )
{
//if (m_Renderer->Run(m_FinalImage, 0) == RENDER_OK)//Full, non-incremental render for debugging.
const bool update = iterBegin | | m_Fractorium - > m_Settings - > ContinuousUpdate ( ) ;
eRenderStatus result ;
if ( ( result = m_Renderer - > Run ( m_FinalImage , 0 , m_SubBatchCount , update ) ) = = eRenderStatus : : RENDER_OK ) //Force output on iterBegin or if the settings specify to always do it.
{
//The amount to increment sub batch while rendering proceeds is purely empirical.
//Change later if better values can be derived/observed.
if ( m_Renderer - > RendererType ( ) = = eRendererType : : OPENCL_RENDERER )
{
if ( m_SubBatchCount < ( 4 * m_Devices . size ( ) ) ) //More than 4 with OpenCL gives a sluggish UI.
m_SubBatchCount + = m_Devices . size ( ) ;
}
else
{
if ( m_SubBatchCount < 5 )
m_SubBatchCount + + ;
else if ( m_SubBatchCount < 105 ) //More than 105 with CPU gives a sluggish UI.
m_SubBatchCount + = 25 ;
}
//Rendering has finished, update final stats.
if ( ProcessState ( ) = = eProcessState : : ACCUM_DONE )
{
const auto stats = m_Renderer - > Stats ( ) ;
auto iters = ToString < qulonglong > ( stats . m_Iters ) ;
auto scaledQuality = ToString ( static_cast < qulonglong > ( m_Renderer - > ScaledQuality ( ) ) ) ;
//auto renderTime = m_RenderElapsedTimer.Format(m_RenderElapsedTimer.Toc());
auto renderTime = m_RenderElapsedTimer . Format ( stats . m_RenderMs ) ;
m_Fractorium - > m_ProgressBar - > setValue ( 100 ) ;
//Only certain stats can be reported with OpenCL.
if ( m_Renderer - > RendererType ( ) = = eRendererType : : OPENCL_RENDERER )
{
const QString status = " Iters: " + iters + " . Scaled quality: " + scaledQuality + " . Total time: " + QString : : fromStdString ( renderTime ) + " . " ;
QMetaObject : : invokeMethod ( m_Fractorium - > m_RenderStatusLabel , " setText " , Qt : : QueuedConnection , Q_ARG ( const QString & , status ) ) ;
}
else
{
const auto percent = static_cast < double > ( stats . m_Badvals ) / static_cast < double > ( stats . m_Iters ) ;
auto badVals = ToString < qulonglong > ( stats . m_Badvals ) ;
auto badPercent = QLocale : : system ( ) . toString ( percent * 100 , ' f ' , 2 ) ;
const QString status = " Iters: " + iters + " . Scaled quality: " + scaledQuality + " . Bad values: " + badVals + " ( " + badPercent + " %). Total time: " + QString : : fromStdString ( renderTime ) + " . " ;
QMetaObject : : invokeMethod ( m_Fractorium - > m_RenderStatusLabel , " setText " , Qt : : QueuedConnection , Q_ARG ( const QString & , status ) ) ;
}
if ( m_LastEditWasUndoRedo & & ( m_UndoIndex = = m_UndoList . size ( ) - 1 ) ) //Traversing through undo list, reached the end, so put back in regular edit mode.
{
m_EditState = eEditUndoState : : REGULAR_EDIT ;
}
else if ( m_EditState = = eEditUndoState : : REGULAR_EDIT ) //Regular edit, just add to the end of the undo list.
{
const auto btn = QApplication : : mouseButtons ( ) ;
if ( ( action = = eProcessAction : : ACCUM_ONLY | | action = = eProcessAction : : FILTER_AND_ACCUM ) | |
( ! btn . testFlag ( Qt : : LeftButton ) & & ! btn . testFlag ( Qt : : RightButton ) & & ! btn . testFlag ( Qt : : MiddleButton ) ) )
{
m_UndoList . push_back ( m_Ember ) ;
m_UndoIndex = m_UndoList . size ( ) - 1 ;
m_Fractorium - > ui . ActionUndo - > setEnabled ( m_UndoList . size ( ) > 1 ) ;
m_Fractorium - > ui . ActionRedo - > setEnabled ( false ) ;
if ( m_UndoList . size ( ) > = UNDO_SIZE )
m_UndoList . pop_front ( ) ;
}
//else
// qDebug() << "Mouse was down, not adding to undo list.";
}
else if ( ! m_LastEditWasUndoRedo & &
m_UndoList . size ( ) & &
m_UndoIndex < m_UndoList . size ( ) - 1 ) //They were anywhere but the end of the undo list, then did a manual edit, so clear the undo list.
{
Ember < T > ember ( m_UndoList [ m_UndoIndex ] ) ;
ClearUndo ( ) ;
m_UndoList . push_back ( ember ) ;
m_UndoList . push_back ( m_Ember ) ;
m_UndoIndex = m_UndoList . size ( ) - 1 ;
m_Fractorium - > ui . ActionUndo - > setEnabled ( true ) ;
m_Fractorium - > ui . ActionRedo - > setEnabled ( false ) ;
}
m_LastEditWasUndoRedo = false ;
m_Fractorium - > UpdateHistogramBounds ( ) ; //Mostly of engineering interest.
FillSummary ( ) ; //Only update summary on render completion since it's not the type of thing the user needs real-time updates on.
}
//Update the GL window on start or continuous rendering because the output will be forced.
//Update it on finish because the rendering process is completely done.
if ( update | | ProcessState ( ) = = eProcessState : : ACCUM_DONE )
{
gl - > update ( ) ; //Queue update.
if ( ProcessState ( ) = = eProcessState : : ACCUM_DONE )
SaveCurrentToOpenedFile ( ) ; //Will not save if the previews are still rendering.
//Uncomment for debugging kernel build and execution errors.
//m_Fractorium->ui.InfoRenderingTextEdit->setText(QString::fromStdString(m_Fractorium->m_Wrapper.DumpInfo()));
//if (rendererCL)
//{
// string s = "OpenCL Kernels: \r\n" + rendererCL->IterKernel() + "\r\n" + rendererCL->DEKernel() + "\r\n" + rendererCL->FinalAccumKernel();
//
// QMetaObject::invokeMethod(m_Fractorium->ui.InfoRenderingTextEdit, "setText", Qt::QueuedConnection, Q_ARG(const QString&, QString::fromStdString(s)));
//}
}
}
else if ( result ! = eRenderStatus : : RENDER_ABORT ) //If error was returned, something went very wrong, show error report.
{
auto errors = m_Renderer - > ErrorReport ( ) ;
success = false ;
m_FailedRenders + + ;
const QString status = " Rendering failed, see info tab. Try changing parameters. " ;
QMetaObject : : invokeMethod ( m_Fractorium - > m_RenderStatusLabel , " setText " , Qt : : QueuedConnection , Q_ARG ( const QString & , status ) ) ;
m_Fractorium - > ErrorReportToQTextEdit ( errors , m_Fractorium - > ui . InfoRenderingTextEdit ) ;
m_Renderer - > ClearErrorReport ( ) ;
if ( m_FailedRenders > = 3 )
{
m_Rendering = false ;
StopRenderTimer ( true ) ;
const QString status2 = " Rendering failed 3 or more times, stopping all rendering, see info tab. Try changing renderer types. " ;
QMetaObject : : invokeMethod ( m_Fractorium - > m_RenderStatusLabel , " setText " , Qt : : QueuedConnection , Q_ARG ( const QString & , status2 ) ) ;
ClearFinalImages ( ) ;
m_GLController - > ClearWindow ( ) ;
if ( rendererCL )
{
//string s = "OpenCL Kernels: \r\n" + rendererCL->IterKernel() + "\r\n" + rendererCL->DEKernel() + "\r\n" + rendererCL->FinalAccumKernel();
rendererCL - > ClearFinal ( ) ;
//QMetaObject::invokeMethod(m_Fractorium->ui.InfoRenderingTextEdit, "setText", Qt::QueuedConnection, Q_ARG(const QString&, QString::fromStdString(s)));
}
}
}
}
//Upon finishing, or having nothing to do, rest.
if ( ProcessState ( ) = = eProcessState : : ACCUM_DONE )
QThread : : msleep ( 1 ) ;
m_Rendering = false ;
return success ;
}
/// <summary>
/// Stop rendering and initialize a new renderer, using the specified type.
/// Rendering will be left in a stopped state. The caller is responsible for restarting the render loop again.
/// </summary>
/// <param name="renderType">The type of render to create</param>
/// <param name="devices">The platform,device index pairs of the devices to use</param>
/// <param name="updatePreviews">True to re-render the library previews, else false.</param>
/// <param name="shared">True if shared with OpenGL, else false. Default: true.</param>
/// <returns>True if nothing went wrong, else false.</returns>
template < typename T >
bool FractoriumEmberController < T > : : CreateRenderer ( eRendererType renderType , const vector < pair < size_t , size_t > > & devices , bool updatePreviews , bool shared )
{
bool ok = true ;
auto s = m_Fractorium - > m_Settings ;
auto gl = m_Fractorium - > ui . GLDisplay ;
if ( ! m_Renderer . get ( ) | | ( m_Renderer - > RendererType ( ) ! = renderType ) | | ! Equal ( m_Devices , devices ) | | m_Renderer - > Shared ( ) ! = shared )
{
EmberReport emberReport ;
vector < string > errorReport ;
DeleteRenderer ( ) ; //Delete the renderer and refresh the textures.
//Before starting, must take care of allocations.
if ( gl - > Allocate ( true ) ) //Forcing a realloc of the texture is necessary on AMD, but not on nVidia.
{
m_Renderer = unique_ptr < EmberNs : : RendererBase > ( : : CreateRenderer < T > ( renderType , devices , shared , gl - > OutputTexID ( ) , emberReport ) ) ; //Always make bucket type float.
errorReport = emberReport . ErrorReport ( ) ;
if ( errorReport . empty ( ) )
{
m_Devices = devices ;
m_OutputTexID = gl - > OutputTexID ( ) ;
}
else
{
ok = false ;
m_Fractorium - > ShowCritical ( " Renderer Creation Error " , " Could not create requested renderer, fallback CPU renderer created. See info tab for details. " ) ;
m_Fractorium - > ErrorReportToQTextEdit ( errorReport , m_Fractorium - > ui . InfoRenderingTextEdit ) ;
}
}
else
{
ok = false ;
m_Fractorium - > ShowCritical ( " Renderer Creation Error " , " Could not create OpenGL texture, interactive rendering will be disabled. " ) ;
m_Fractorium - > ErrorReportToQTextEdit ( errorReport , m_Fractorium - > ui . InfoRenderingTextEdit ) ;
}
}
if ( m_Renderer . get ( ) )
{
m_RenderType = m_Renderer - > RendererType ( ) ;
if ( m_RenderType = = eRendererType : : OPENCL_RENDERER )
{
auto val = m_Fractorium - > m_Settings - > OpenClQuality ( ) * m_Fractorium - > m_Settings - > Devices ( ) . size ( ) ;
m_Fractorium - > m_QualitySpin - > DoubleClickZero ( val ) ;
m_Fractorium - > m_QualitySpin - > DoubleClickNonZero ( val ) ;
if ( m_Fractorium - > m_QualitySpin - > value ( ) < val )
m_Fractorium - > m_QualitySpin - > setValue ( val ) ;
if ( auto rendererCL = dynamic_cast < RendererCL < T , float > * > ( m_Renderer . get ( ) ) )
rendererCL - > SubBatchPercentPerThread ( static_cast < float > ( s - > OpenCLSubBatchPct ( ) ) ) ;
}
else
{
const auto quality = m_Fractorium - > m_Settings - > CpuQuality ( ) ;
m_Fractorium - > m_QualitySpin - > DoubleClickZero ( quality ) ;
m_Fractorium - > m_QualitySpin - > DoubleClickNonZero ( quality ) ;
if ( m_Fractorium - > m_QualitySpin - > value ( ) > quality )
m_Fractorium - > m_QualitySpin - > setValue ( quality ) ;
}
m_Renderer - > Callback ( this ) ;
m_Renderer - > ReclaimOnResize ( true ) ;
//Give it an initial ember, will be updated many times later.
//Even though the bounds are computed when starting the next render. The OpenGL draw calls use these values, which might get called before the render starts.
m_Renderer - > SetEmber ( m_Ember , eProcessAction : : FULL_RENDER , true ) ;
m_Renderer - > EarlyClip ( s - > EarlyClip ( ) ) ;
m_Renderer - > YAxisUp ( s - > YAxisUp ( ) ) ;
m_Renderer - > ThreadCount ( s - > ThreadCount ( ) ) ;
if ( m_Renderer - > RendererType ( ) = = eRendererType : : CPU_RENDERER )
m_Renderer - > InteractiveFilter ( s - > CpuDEFilter ( ) ? eInteractiveFilter : : FILTER_DE : eInteractiveFilter : : FILTER_LOG ) ;
else
m_Renderer - > InteractiveFilter ( s - > OpenCLDEFilter ( ) ? eInteractiveFilter : : FILTER_DE : eInteractiveFilter : : FILTER_LOG ) ;
if ( updatePreviews )
{
m_LibraryPreviewRenderer = make_unique < TreePreviewRenderer < T > > ( this , m_Fractorium - > ui . LibraryTree , m_EmberFile ) ; //Will take the same settings as the main renderer.
RenderLibraryPreviews ( ) ;
}
m_FailedRenders = 0 ;
m_RenderElapsedTimer . Tic ( ) ;
//Leave rendering in a stopped state. The caller is responsible for restarting the render loop again.
}
else
{
ok = false ;
m_Fractorium - > ShowCritical ( " Renderer Creation Error " , " Creating a basic CPU renderer failed, something is catastrophically wrong. Exiting program. " ) ;
QApplication : : quit ( ) ;
}
return ok ;
}
/// <summary>
/// Enable or disable the controls related to changing the renderer.
/// Used when pausing and restarting the renderer.
/// </summary>
/// <param name="enable">True to enable, else false.</param>
void Fractorium : : EnableRenderControls ( bool enable )
{
ui . ActionCpu - > setEnabled ( enable ) ;
ui . ActionCL - > setEnabled ( enable ) ;
ui . ActionSP - > setEnabled ( enable ) ;
ui . ActionDP - > setEnabled ( enable ) ;
ui . ActionOptions - > setEnabled ( enable ) ;
}
/// <summary>
/// Wrapper to stop the timer, shutdown the controller and recreate, then restart the controller and renderer from the options.
/// </summary>
/// <param name="updatePreviews">True to re-render the library previews, else false.</param>
void Fractorium : : ShutdownAndRecreateFromOptions ( bool updatePreviews )
{
//First completely stop what the current rendering process is doing.
m_Controller - > Shutdown ( ) ;
StartRenderTimer ( updatePreviews ) ; //This will recreate the controller and/or the renderer from the options if necessary, then start the render timer.
m_Settings - > sync ( ) ;
}
/// <summary>
/// Create a new renderer from the options.
/// </summary>
/// <param name="updatePreviews">True to re-render the library previews, else false.</param>
/// <returns>True if nothing went wrong, else false.</returns>
bool Fractorium : : CreateRendererFromOptions ( bool updatePreviews )
{
bool ok = true ;
const auto useOpenCL = m_Info - > Ok ( ) & & m_Settings - > OpenCL ( ) ;
auto v = Devices ( m_Settings - > Devices ( ) ) ;
const auto doOpenCL = useOpenCL & & ! v . empty ( ) ;
ui . ActionCopyKernel - > setEnabled ( doOpenCL ) ;
//The most important option to process is what kind of renderer is desired, so do it first.
if ( ! m_Controller - > CreateRenderer ( doOpenCL ? eRendererType : : OPENCL_RENDERER : eRendererType : : CPU_RENDERER , v , updatePreviews , doOpenCL & & m_Settings - > SharedTexture ( ) ) )
{
//If using OpenCL, will only get here if creating RendererCL failed, but creating a backup CPU Renderer succeeded.
ShowCritical ( " Renderer Creation Error " , " Error creating renderer, most likely a GPU problem. Using CPU instead. " ) ;
m_Settings - > OpenCL ( false ) ;
m_Settings - > SharedTexture ( false ) ;
ui . ActionCpu - > setChecked ( true ) ;
ui . ActionCL - > setChecked ( false ) ;
ui . ActionCopyKernel - > setEnabled ( false ) ;
m_OptionsDialog - > ui . OpenCLCheckBox - > setChecked ( false ) ;
m_OptionsDialog - > ui . SharedTextureCheckBox - > setChecked ( false ) ;
if ( m_FinalRenderDialog )
m_FinalRenderDialog - > ui . FinalRenderOpenCLCheckBox - > setChecked ( false ) ;
ok = false ;
}
if ( auto rendererCL = dynamic_cast < RendererCLBase * > ( m_Controller - > Renderer ( ) ) )
{
rendererCL - > m_CompileBegun = [ & ] ( )
{
const QString status = " Compiling OpenCL kernel... " ;
QMetaObject : : invokeMethod ( m_RenderStatusLabel , " setText " , Qt : : QueuedConnection , Q_ARG ( const QString & , status ) ) ;
//m_RenderStatusLabel->repaint();
QApplication : : processEvents ( ) ;
} ;
}
return ok ;
}
/// <summary>
/// Create a new controller from the options.
/// This does not create the internal renderer or start the timers.
/// </summary>
/// <returns>True if successful, else false.</returns>
bool Fractorium : : CreateControllerFromOptions ( )
{
const size_t elementSize =
# ifdef DO_DOUBLE
m_Settings - > Double ( ) ? sizeof ( double ) :
# endif
sizeof ( float ) ;
if ( ! m_Controller . get ( ) | | ( m_Controller - > SizeOfT ( ) ! = elementSize ) )
{
const auto hue = m_PaletteHueSpin - > value ( ) ;
const auto sat = m_PaletteSaturationSpin - > value ( ) ;
const auto bright = m_PaletteBrightnessSpin - > value ( ) ;
const auto con = m_PaletteContrastSpin - > value ( ) ;
const auto blur = m_PaletteBlurSpin - > value ( ) ;
const auto freq = m_PaletteFrequencySpin - > value ( ) ;
const auto rot = m_PreviewPaletteRotation ;
double scale = 0 ;
uint current = 0 ;
# ifdef DO_DOUBLE
EmberFile < double > efd ;
Palette < double > tempPalette ;
# else
EmberFile < float > efd ;
Palette < float > tempPalette ;
# endif
const QModelIndex index = ui . LibraryTree - > currentIndex ( ) ;
ui . LibraryTree - > clear ( ) ; //This must be here before FillLibraryTree() is called below, else a spurious EmberTreeItemChanged event will be called on a deleted object.
//First check if a controller has already been created, and if so, save its embers and gracefully shut it down.
if ( m_Controller . get ( ) )
{
scale = m_Controller - > LockedScale ( ) ;
m_Controller - > StopAllPreviewRenderers ( ) ; //Must stop any previews first, else changing controllers will crash the program and SaveCurrentToOpenedFile() will return 0.
m_Controller - > CopyTempPalette ( tempPalette ) ; //Convert float to double or save double verbatim;
current = m_Controller - > SaveCurrentToOpenedFile ( false ) ;
//Replace below with this once LLVM fixes a crash in their compiler with default lambda parameters.//TODO
//m_Controller->CopyEmberFile(efd);
# ifdef DO_DOUBLE
m_Controller - > CopyEmberFile ( efd , false , [ & ] ( Ember < double > & ember ) { } ) ;
# else
m_Controller - > CopyEmberFile ( efd , false , [ & ] ( Ember < float > & ember ) { } ) ;
# endif
m_Controller - > Shutdown ( ) ;
}
# ifdef DO_DOUBLE
if ( m_Settings - > Double ( ) )
m_Controller = unique_ptr < FractoriumEmberControllerBase > ( new FractoriumEmberController < double > ( this ) ) ;
else
# endif
m_Controller = unique_ptr < FractoriumEmberControllerBase > ( new FractoriumEmberController < float > ( this ) ) ;
//Restore the ember and ember file.
if ( m_Controller . get ( ) )
{
if ( auto prev = efd . Get ( current ) ) //Restore base temp palette. Adjustments will be then be applied and stored back in in m_Ember.m_Palette below.
prev - > m_Palette = tempPalette ;
m_Controller - > SetEmberFile ( efd , true ) ;
//Template specific palette table and variations tree setup in controller constructor, but
//must manually setup the library tree here because it's after the embers were assigned.
//Passing row re-selects the item that was previously selected.
//This will eventually call FillParamTablesAndPalette(), which in addition to filling in various fields,
//will apply the palette adjustments.
m_Controller - > FillLibraryTree ( index . row ( ) ) ;
m_Controller - > SetEmber ( current , true ) ;
m_Controller - > LockedScale ( scale ) ;
//Setting these and updating the GUI overwrites the work of clearing them done in SetEmber() above.
//It's a corner case, but doesn't seem to matter.
m_PaletteHueSpin - > SetValueStealth ( hue ) ;
m_PaletteSaturationSpin - > SetValueStealth ( sat ) ;
m_PaletteBrightnessSpin - > SetValueStealth ( bright ) ;
m_PaletteContrastSpin - > SetValueStealth ( con ) ;
m_PaletteBlurSpin - > SetValueStealth ( blur ) ;
m_PaletteFrequencySpin - > SetValueStealth ( freq ) ;
m_PreviewPaletteRotation = rot ;
m_Controller - > PaletteAdjust ( ) ; //Applies the adjustments to temp and saves in m_Ember.m_Palette, then fills in the palette preview widget.
}
return m_Controller . get ( ) ;
}
return false ;
}
/// <summary>
/// Start the render timer.
/// If a renderer has not been created yet, or differs form the options, it will first be created from the options.
/// </summary>
/// <param name="updatePreviews">True to re-render the library previews, else false.</param>
void Fractorium : : StartRenderTimer ( bool updatePreviews )
{
//Starting the render timer, either for the first time
//or from a paused state, such as resizing or applying new options.
const auto newController = CreateControllerFromOptions ( ) ;
if ( m_Controller . get ( ) )
{
//On program startup, the renderer does not get initialized until now.
//If a new controller was created, then previews will have started, so only start the previews if a new controller
//was *not* created and updatePreviews is true.
CreateRendererFromOptions ( updatePreviews & & ! newController ) ;
if ( m_Controller - > Renderer ( ) )
m_Controller - > StartRenderTimer ( ) ;
}
}
/// <summary>
/// Idle timer event which calls the controller's Render() function.
/// </summary>
void Fractorium : : IdleTimer ( ) { m_Controller - > Render ( ) ; }
/// <summary>
/// Thin wrapper to determine if the controllers have been properly initialized.
/// </summary>
/// <returns>True if the ember controller and GL controllers are both not nullptr, else false.</returns>
bool Fractorium : : ControllersOk ( ) { return m_Controller . get ( ) & & m_Controller - > GLController ( ) ; }
template class FractoriumEmberController < float > ;
# ifdef DO_DOUBLE
template class FractoriumEmberController < double > ;
# endif