povray/vfe/vfe.cpp
2013-11-06 13:07:19 -05:00

1403 lines
44 KiB
C++

/*******************************************************************************
* vfe.cpp
*
* This module contains the C++ implementation for the virtual frontend.
*
* Author: Christopher J. Cason
*
* ---------------------------------------------------------------------------
* Persistence of Vision Ray Tracer ('POV-Ray') version 3.7.
* Copyright 1991-2013 Persistence of Vision Raytracer Pty. Ltd.
*
* POV-Ray is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* POV-Ray is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* ---------------------------------------------------------------------------
* POV-Ray is based on the popular DKB raytracer version 2.12.
* DKBTrace was originally written by David K. Buck.
* DKBTrace Ver 2.0-2.12 were written by David K. Buck & Aaron A. Collins.
* ---------------------------------------------------------------------------
* $File: //depot/public/povray/3.x/vfe/vfe.cpp $
* $Revision: #1 $
* $Change: 6069 $
* $DateTime: 2013/11/06 11:59:40 $
* $Author: chrisc $
*******************************************************************************/
#ifdef _MSC_VER
#pragma warning(disable : 4244)
#pragma warning(disable : 4267)
#endif
#include "frame.h"
#include "povray.h"
#include "vfe.h"
// this must be the last file included
#include "base/povdebug.h"
namespace vfe
{
using namespace pov_base;
using boost::format;
////////////////////////////////////////////////////////////////////////////////////////
//
// class POVMSMessageDetails
//
////////////////////////////////////////////////////////////////////////////////////////
class POVMSMessageDetails
{
public:
POVMSMessageDetails (POVMS_Object &Obj);
virtual ~POVMSMessageDetails () {} ;
string GetContext (int NumLines) ;
protected:
string File ;
UCS2String UCS2File;
string URL ;
string Message ;
POVMSInt Line ;
POVMSInt Col ;
POVMSLong Offset ;
} ;
POVMSMessageDetails::POVMSMessageDetails (POVMS_Object& Obj)
{
char buffer [2048] = "";
UCS2 ubuffer [2048];
POVMSInt l = sizeof (ubuffer);
POVMSLong ll ;
POVMSObject msgobj (Obj());
POVMSObjectPtr msg = &msgobj;
Line = Col = 0 ;
Offset = -1 ;
ubuffer[0] = 0 ;
if (POVMSUtil_GetUCS2String (msg, kPOVAttrib_FileName, ubuffer, &l) == kNoErr)
{
UCS2File = ubuffer ;
File = UCS2toASCIIString (UCS2File);
}
if (POVMSUtil_GetLong (msg, kPOVAttrib_Line, &ll) == kNoErr)
Line = POVMSInt(ll) ;
if (POVMSUtil_GetLong (msg, kPOVAttrib_Column, &ll) == kNoErr)
Col = POVMSInt(ll + 1) ;
if(POVMSUtil_GetLong(msg, kPOVAttrib_FilePosition, &ll) == kNoErr)
Offset = ll ;
l = sizeof (buffer) ;
if (POVMSUtil_GetString (msg, kPOVAttrib_EnglishText, buffer, &l) == kNoErr)
Message = buffer ;
POVMSObject_Delete(msg);
}
string POVMSMessageDetails::GetContext (int NumLines)
{
return ("") ;
}
////////////////////////////////////////////////////////////////////////////////////////
//
// class ParseErrorDetails, ParseWarningDetails
//
////////////////////////////////////////////////////////////////////////////////////////
class ParseWarningDetails : public POVMSMessageDetails
{
public:
ParseWarningDetails (POVMS_Object &Obj) : POVMSMessageDetails (Obj) {} ;
virtual ~ParseWarningDetails () {} ;
public:
using POVMSMessageDetails::File ;
using POVMSMessageDetails::UCS2File ;
using POVMSMessageDetails::Message ;
using POVMSMessageDetails::Line ;
using POVMSMessageDetails::Col ;
using POVMSMessageDetails::Offset ;
} ;
class ParseErrorDetails : public POVMSMessageDetails
{
public:
ParseErrorDetails (POVMS_Object &Obj) : POVMSMessageDetails (Obj) {} ;
virtual ~ParseErrorDetails () {} ;
public:
using POVMSMessageDetails::File ;
using POVMSMessageDetails::UCS2File ;
using POVMSMessageDetails::Message ;
using POVMSMessageDetails::Line ;
using POVMSMessageDetails::Col ;
using POVMSMessageDetails::Offset ;
} ;
////////////////////////////////////////////////////////////////////////////////////////
//
// class vfeConsole
//
////////////////////////////////////////////////////////////////////////////////////////
vfeConsole::vfeConsole(vfeSession *session, int width) : Console(width == -1 ? session->GetConsoleWidth() : width)
{
m_Session = session;
Initialise();
}
vfeConsole::~vfeConsole()
{
}
void vfeConsole::Initialise()
{
rawBuffer [0] = '\0' ;
}
void vfeConsole::BufferOutput(const char *str, unsigned int chars, vfeSession::MessageType mType)
{
char *s ;
// HACK FIXME - this is to prevent duplicate messages
if (m_Session->HadErrorMessage() && strncmp (str, "Fatal error in parser: ", 23) == 0)
return ;
if (str [0] == '\n' && str [1] == '\0')
{
m_Session->AppendStreamMessage (mType, rawBuffer) ;
rawBuffer [0] = '\0' ;
return ;
}
size_t sLen = chars ;
if (sLen == 0)
sLen = strlen (str) ;
size_t bLen = strlen (rawBuffer) ;
if (sLen > sizeof (rawBuffer) - bLen - 1)
sLen = sizeof (rawBuffer) - bLen - 1 ;
strncat (rawBuffer, str, sLen) ;
if ((s = strrchr (rawBuffer, '\n')) != NULL)
{
*s++ = '\0' ;
m_Session->AppendStreamMessage (mType, rawBuffer) ;
strcpy (rawBuffer, s) ;
}
}
void vfeConsole::Output(const char *str, vfeSession::MessageType mType)
{
BufferOutput (str, (unsigned int) strlen (str), mType) ;
BufferOutput ("\n", 1, mType) ;
}
void vfeConsole::Output(const string& str, vfeSession::MessageType mType)
{
Output (str.c_str(), mType) ;
}
void vfeConsole::Output(const string& str)
{
Output (str.c_str()) ;
}
////////////////////////////////////////////////////////////////////////////////////////
//
// class vfePlatformBase
//
////////////////////////////////////////////////////////////////////////////////////////
vfePlatformBase::vfePlatformBase(vfeSession& session) : m_Session(&session), PlatformBase()
{
}
vfePlatformBase::~vfePlatformBase()
{
}
IStream *vfePlatformBase::CreateIStream(const unsigned int stype)
{
return (new IStream (stype)) ;
}
OStream *vfePlatformBase::CreateOStream(const unsigned int stype)
{
return (new OStream (stype)) ;
}
UCS2String vfePlatformBase::GetTemporaryPath(void)
{
return m_Session->GetTemporaryPath();
}
UCS2String vfePlatformBase::CreateTemporaryFile(void)
{
return m_Session->CreateTemporaryFile();
}
void vfePlatformBase::DeleteTemporaryFile(const UCS2String& filename)
{
m_Session->DeleteTemporaryFile(filename);
}
bool vfePlatformBase::ReadFileFromURL(OStream *file, const UCS2String& url, const UCS2String& referrer)
{
return false;
}
////////////////////////////////////////////////////////////////////////////////////////
//
// class vfeParserMessageHandler
//
////////////////////////////////////////////////////////////////////////////////////////
vfeParserMessageHandler::vfeParserMessageHandler() : ParserMessageHandler()
{
m_Session = vfeSession::GetSessionFromThreadID();
}
vfeParserMessageHandler::~vfeParserMessageHandler()
{
}
void vfeParserMessageHandler::Options(Console *Con, POVMS_Object& Obj, bool conout)
{
if (Obj.TryGetBool (kPOVAttrib_OutputAlpha, false))
m_Session->SetUsingAlpha();
if (Obj.TryGetBool (kPOVAttrib_ClocklessAnimation, false))
m_Session->SetClocklessAnimation();
if (Obj.TryGetBool (kPOVAttrib_RealTimeRaytracing, false))
m_Session->SetRealTimeRaytracing();
ParserMessageHandler::Options (Con, Obj, conout) ;
}
void vfeParserMessageHandler::Statistics(Console *Con, POVMS_Object& Obj, bool conout)
{
ParserMessageHandler::Statistics (Con, Obj, conout) ;
}
void vfeParserMessageHandler::Progress(Console *Con, POVMS_Object& Obj, bool verbose)
{
switch(Obj.GetType(kPOVMSObjectClassID))
{
case kPOVObjectClass_ParserProgress:
{
m_Session->AppendStatusMessage (format ("Parsing %uK tokens") % (Obj.GetLong (kPOVAttrib_CurrentTokenCount) / 1000));
break;
}
case kPOVObjectClass_BoundingProgress:
{
m_Session->AppendStatusMessage (format ("Constructed %uK BSP nodes") % (Obj.GetLong (kPOVAttrib_CurrentNodeCount) / 1000));
break;
}
}
}
void vfeParserMessageHandler::Warning(Console *Con, POVMS_Object& Obj, bool conout)
{
ParseWarningDetails d (Obj) ;
if (d.Message == "")
return ;
// as we provide special treatment for warning messages here if we're not
// optimized for console output, we don't duplicate them to the console
// regardless of whether or not conout is set.
if (m_Session->m_OptimizeForConsoleOutput == false)
m_Session->AppendWarningMessage (d.Message, d.UCS2File, d.Line, d.Col) ;
if (!d.File.empty() && (d.Line > 0))
{
format f = format ("File '%s' line %d: %s") % d.File % d.Line % d.Message ;
if (m_Session->m_OptimizeForConsoleOutput == false)
m_Session->AppendStatusMessage (f) ;
else if (conout)
Con->puts (f.str().c_str()) ;
}
else
{
if (m_Session->m_OptimizeForConsoleOutput == false)
m_Session->AppendStatusMessage (d.Message) ;
else if (conout)
Con->puts (d.Message.c_str()) ;
}
}
void vfeParserMessageHandler::Error(Console *Con, POVMS_Object& Obj, bool conout)
{
ParseErrorDetails d (Obj) ;
if (d.Message == "" && (d.File == "" || d.Line <= 0))
return ;
// as we provide special treatment for parser errors here if we're not
// optimized for console output, we don't duplicate them to the console
// regardless of whether or not conout is set.
if (m_Session->m_OptimizeForConsoleOutput == false)
m_Session->AppendErrorMessage (d.Message, d.UCS2File, d.Line, d.Col) ;
if (!d.Message.empty())
{
if (!d.File.empty() && (d.Line > 0))
{
format f = format ("File '%s' line %d: %s") % d.File % d.Line % d.Message ;
if (m_Session->m_OptimizeForConsoleOutput == false)
m_Session->AppendStatusMessage (f) ;
else if (conout)
Con->puts (f.str().c_str()) ;
}
else
{
if (m_Session->m_OptimizeForConsoleOutput == false)
m_Session->AppendStatusMessage (d.Message) ;
if (conout)
if (m_Session->m_OptimizeForConsoleOutput == true)
Con->puts (d.Message.c_str()) ;
}
}
else
{
format f = format ("Parse error in file '%s' at line %d") % d.File % d.Line ;
if (m_Session->m_OptimizeForConsoleOutput == false)
m_Session->AppendStatusMessage (f) ;
if (conout)
if (m_Session->m_OptimizeForConsoleOutput == true)
Con->puts (f.str().c_str()) ;
}
}
void vfeParserMessageHandler::FatalError(Console *Con, POVMS_Object& Obj, bool conout)
{
m_Session->SetFailed();
Error (Con, Obj, conout) ;
}
void vfeParserMessageHandler::DebugInfo(Console *Con, POVMS_Object& Obj, bool conout)
{
string str(Obj.GetString(kPOVAttrib_EnglishText));
if (m_Session->m_OptimizeForConsoleOutput == true)
{
if (conout)
Con->puts (str.c_str()) ;
}
else
m_Session->AppendStreamMessage (vfeSession::mDebug, str.c_str()) ;
}
////////////////////////////////////////////////////////////////////////////////////////
//
// class vfeRenderMessageHandler
//
////////////////////////////////////////////////////////////////////////////////////////
vfeRenderMessageHandler::vfeRenderMessageHandler() : RenderMessageHandler()
{
m_Session = vfeSession::GetSessionFromThreadID();
}
vfeRenderMessageHandler::~vfeRenderMessageHandler()
{
}
void vfeRenderMessageHandler::Options(Console *Con, POVMS_Object& Obj, bool conout)
{
RenderMessageHandler::Options (Con, Obj, conout) ;
}
void vfeRenderMessageHandler::Statistics(Console *Con, POVMS_Object& Obj, bool conout)
{
RenderMessageHandler::Statistics (Con, Obj, conout) ;
}
void vfeRenderMessageHandler::Progress(Console *Con, POVMS_Object& Obj, bool verbose)
{
switch (Obj.GetType(kPOVMSObjectClassID))
{
case kPOVObjectClass_PhotonProgress:
{
int cpc (Obj.GetInt (kPOVAttrib_CurrentPhotonCount)) ;
m_Session->AppendStatusMessage (format ("Photon count %u") % cpc, 250) ;
break;
}
case kPOVObjectClass_RadiosityProgress:
{
int pc (Obj.GetInt (kPOVAttrib_Pixels)) ;
int cc (Obj.GetInt (kPOVAttrib_PixelsCompleted)) ;
m_Session->SetPixelsRendered(cc, pc);
int percent = pc > 0 ? int ((cc * 100.0) / pc) : 0 ;
m_Session->SetPercentComplete (percent);
m_Session->AppendStatusMessage (format ("Performing radiosity pretrace: %d of %d pixels (%d%%)") % cc % pc % percent, 250) ;
break;
}
case kPOVObjectClass_RenderProgress:
{
int pc (Obj.GetInt (kPOVAttrib_Pixels)) ;
int cc (Obj.GetInt (kPOVAttrib_PixelsCompleted)) ;
if (m_Session->GetRealTimeRaytracing() == false)
{
m_Session->SetPixelsRendered(cc, pc);
int percent = pc > 0 ? (int) ((cc * 100.0) / pc) : 0 ;
m_Session->SetPercentComplete (percent);
if (verbose == true || m_Session->m_OptimizeForConsoleOutput == false)
m_Session->AppendStatusMessage (format ("Rendered %u of %u pixels (%d%%)") % cc % pc % percent, 250) ;
}
else
{
m_Session->SetPixelsRendered(cc % pc, pc);
float elapsed = m_Session->GetElapsedTime() / 1000.0f;
float frames = (float) cc / pc;
float fps = frames / elapsed;
if (verbose == true || m_Session->m_OptimizeForConsoleOutput == false)
m_Session->AppendStatusMessage (format ("Rendered %g frames over %g seconds (%g FPS)") % frames % elapsed % fps, 250) ;
}
break;
}
}
}
void vfeRenderMessageHandler::Warning(Console *Con, POVMS_Object& Obj, bool conout)
{
RenderMessageHandler::Warning (Con, Obj, conout) ;
}
void vfeRenderMessageHandler::Error(Console *Con, POVMS_Object& Obj, bool conout)
{
m_Session->SetFailed();
RenderMessageHandler::Error (Con, Obj, conout) ;
}
void vfeRenderMessageHandler::FatalError(Console *Con, POVMS_Object& Obj, bool conout)
{
m_Session->SetFailed();
RenderMessageHandler::FatalError (Con, Obj, conout) ;
}
////////////////////////////////////////////////////////////////////////////////////////
//
// class vfeProcessRenderOptions
//
////////////////////////////////////////////////////////////////////////////////////////
vfeProcessRenderOptions::vfeProcessRenderOptions(vfeSession *Session) : ProcessRenderOptions(), m_Session(Session)
{
}
vfeProcessRenderOptions::~vfeProcessRenderOptions()
{
}
int vfeProcessRenderOptions::ReadSpecialOptionHandler(INI_Parser_Table *Table, char *Param, POVMSObjectPtr Obj)
{
return ProcessRenderOptions::ReadSpecialOptionHandler (Table, Param, Obj);
}
int vfeProcessRenderOptions::ReadSpecialSwitchHandler(Cmd_Parser_Table *Table, char *Param, POVMSObjectPtr Obj, bool On)
{
return ProcessRenderOptions::ReadSpecialSwitchHandler (Table, Param, Obj, On);
}
int vfeProcessRenderOptions::WriteSpecialOptionHandler(INI_Parser_Table *Table, POVMSObjectPtr Obj, OTextStream *S)
{
return ProcessRenderOptions::WriteSpecialOptionHandler (Table, Obj, S);
}
bool vfeProcessRenderOptions::WriteOptionFilter(INI_Parser_Table *Table)
{
return ProcessRenderOptions::WriteOptionFilter (Table);
}
int vfeProcessRenderOptions::ProcessUnknownString(char *String, POVMSObjectPtr Obj)
{
return ProcessRenderOptions::ProcessUnknownString (String, Obj);
}
ITextStream *vfeProcessRenderOptions::OpenFileForRead(const char *Name, POVMSObjectPtr Obj)
{
return (ProcessRenderOptions::OpenFileForRead (Name, Obj)) ;
}
OTextStream *vfeProcessRenderOptions::OpenFileForWrite(const char *Name, POVMSObjectPtr Obj)
{
return (ProcessRenderOptions::OpenFileForWrite (Name, Obj)) ;
}
void vfeProcessRenderOptions::ParseError(const char *format, ...)
{
char str[1024];
va_list marker;
va_start(marker, format);
vsnprintf(str, sizeof(str)-2, format, marker);
va_end(marker);
m_Session->AppendStatusMessage (str);
m_Session->AppendErrorMessage (str) ;
m_Session->SetFailed();
}
void vfeProcessRenderOptions::ParseErrorAt(ITextStream *file, const char *format, ...)
{
char str[1024];
va_list marker;
va_start(marker, format);
vsnprintf(str, sizeof(str)-2, format, marker);
va_end(marker);
m_Session->AppendStatusMessage (str);
m_Session->AppendErrorMessage (str, file->name(), file->line(), 0) ;
m_Session->SetFailed();
}
void vfeProcessRenderOptions::WriteError(const char *format, ...)
{
char str[1024];
va_list marker;
va_start(marker, format);
vsnprintf(str, sizeof(str)-2, format, marker);
va_end(marker);
m_Session->AppendStatusMessage (str);
m_Session->AppendErrorMessage (str) ;
m_Session->SetFailed();
}
////////////////////////////////////////////////////////////////////////////////////////
//
// class VirtualFrontEnd
//
////////////////////////////////////////////////////////////////////////////////////////
VirtualFrontEnd::VirtualFrontEnd(vfeSession& session, POVMSContext ctx, POVMSAddress addr, POVMS_Object& msg, POVMS_Object *result, shared_ptr<Console>& console) :
m_Session(&session), m_PlatformBase(session), renderFrontend (ctx)
{
backendAddress = addr ;
state = kReady ;
m_PostPauseState = kReady;
consoleResult = NULL ;
displayResult = NULL ;
m_PauseRequested = m_PausedAfterFrame = false;
renderFrontend.ConnectToBackend(backendAddress, msg, result, console);
}
VirtualFrontEnd::~VirtualFrontEnd()
{
// file-backed images may require a reference to PlatformBase to delete temporary files
// we need to explicitly delete it here since otherwise PlatformBase will have been destroyed
// before the shared_ptr does its cleanup
imageProcessing.reset();
if (backendAddress != POVMSInvalidAddress)
renderFrontend.DisconnectFromBackend(backendAddress);
state = kUnknown;
}
bool VirtualFrontEnd::Start(POVMS_Object& opts)
{
if (state != kReady)
return false;
m_Session->Clear();
animationProcessing.reset() ;
m_PauseRequested = m_PausedAfterFrame = false;
m_PostPauseState = kReady;
Path ip (m_Session->GetInputFilename());
shelloutProcessing.reset(m_Session->CreateShelloutProcessing(opts, UCS2toASCIIString(ip.GetFile()), m_Session->GetRenderWidth(), m_Session->GetRenderHeight())) ;
shelloutProcessing->SetCancelMessage("Render halted because the %1% shell-out ('%6%') requested POV-Ray to %5%.");
shelloutProcessing->SetSkipMessage("The %1% shell-out ('%3%') requested POV-Ray to %2%.");
POVMS_List declares;
if(opts.Exist(kPOVAttrib_Declare) == true)
opts.Get(kPOVAttrib_Declare, declares);
POVMS_Object image_width(kPOVMSType_WildCard);
image_width.SetString(kPOVAttrib_Identifier, "image_width");
image_width.SetFloat(kPOVAttrib_Value, opts.TryGetInt(kPOVAttrib_Width, 160));
declares.Append(image_width);
POVMS_Object image_height(kPOVMSType_WildCard);
image_height.SetString(kPOVAttrib_Identifier, "image_height");
image_height.SetFloat(kPOVAttrib_Value, opts.TryGetInt(kPOVAttrib_Height, 120));
declares.Append(image_height);
POVMS_Object input_file_name(kPOVMSType_WildCard);
input_file_name.SetString(kPOVAttrib_Identifier, "input_file_name");
input_file_name.SetString(kPOVAttrib_Value, UCS2toASCIIString(ip.GetFile()).c_str());
declares.Append(input_file_name);
int initialFrame = opts.TryGetInt (kPOVAttrib_InitialFrame, 0) ;
int finalFrame = opts.TryGetInt (kPOVAttrib_FinalFrame, 0) ;
if ((initialFrame == 0 && finalFrame == 0) || (initialFrame == 1 && finalFrame == 1))
{
POVMS_Object clock_delta(kPOVMSType_WildCard);
clock_delta.SetString(kPOVAttrib_Identifier, "clock_delta");
clock_delta.SetFloat(kPOVAttrib_Value, 0.0f);
declares.Append(clock_delta);
POVMS_Object final_clock(kPOVMSType_WildCard);
final_clock.SetString(kPOVAttrib_Identifier, "final_clock");
final_clock.SetFloat(kPOVAttrib_Value, 0.0f);
declares.Append(final_clock);
POVMS_Object final_frame(kPOVMSType_WildCard);
final_frame.SetString(kPOVAttrib_Identifier, "final_frame");
final_frame.SetFloat(kPOVAttrib_Value, 0.0f);
declares.Append(final_frame);
POVMS_Object frame_number(kPOVMSType_WildCard);
frame_number.SetString(kPOVAttrib_Identifier, "frame_number");
frame_number.SetFloat(kPOVAttrib_Value, 0.0f);
declares.Append(frame_number);
POVMS_Object initial_clock(kPOVMSType_WildCard);
initial_clock.SetString(kPOVAttrib_Identifier, "initial_clock");
initial_clock.SetFloat(kPOVAttrib_Value, 0.0f);
declares.Append(initial_clock);
POVMS_Object initial_frame(kPOVMSType_WildCard);
initial_frame.SetString(kPOVAttrib_Identifier, "initial_frame");
initial_frame.SetFloat(kPOVAttrib_Value, 0.0f);
declares.Append(initial_frame);
opts.Set(kPOVAttrib_Declare, declares);
// optimization: reset imageProcessing now even though the following assign
// will free the old pointer (if it exists). this can potentially free a
// significant amount of memory and may in some circumstances prevent the
// image allocation from failing. TODO: it may be useful to check whether
// we can re-use an old imageProcessing instance (e.g. if image options are
// the same).
imageProcessing.reset();
// TODO: update ImageProcessing with the means of accepting and caching
// blocks of pixels as opposed to individual ones, with a back-end that
// can serialize completed rows to the final image output file.
options = opts;
if (m_Session->OutputToFileSet())
{
imageProcessing = shared_ptr<ImageProcessing> (new ImageProcessing (opts));
UCS2String filename = imageProcessing->GetOutputFilename (opts, 0, 0);
options.SetUCS2String (kPOVAttrib_OutputFile, filename.c_str());
if ((imageProcessing->OutputIsStdout() || imageProcessing->OutputIsStderr()) && m_Session->ImageOutputToStdoutSupported() == false)
throw POV_EXCEPTION(kCannotOpenFileErr, "Image output to STDOUT/STDERR not supported on this platform");
// test access permission now to avoid surprise later after waiting for
// the render to complete.
if (imageProcessing->OutputIsStdout() == false && imageProcessing->OutputIsStderr() == false && m_Session->TestAccessAllowed(filename, true) == false)
{
string str ("IO Restrictions prohibit write access to '") ;
str += UCS2toASCIIString(filename);
str += "'";
throw POV_EXCEPTION(kCannotOpenFileErr, str);
}
shelloutProcessing->SetOutputFile(UCS2toASCIIString(filename));
m_Session->AdviseOutputFilename (filename);
}
}
else
{
// the output filename is set in Process()
m_Session->SetRenderingAnimation();
opts.Set(kPOVAttrib_Declare, declares);
imageProcessing.reset();
if (m_Session->OutputToFileSet())
imageProcessing = shared_ptr<ImageProcessing> (new ImageProcessing (opts)) ;
animationProcessing = shared_ptr<AnimationProcessing> (new AnimationProcessing (opts)) ;
options = animationProcessing->GetFrameRenderOptions () ;
}
state = kStarting;
return true;
}
bool VirtualFrontEnd::Stop()
{
bool result = false;
try
{
switch(state)
{
case kStarting:
state = kStopped;
m_Session->SetFailed();
result = true;
break;
case kPreSceneShellout:
case kPreFrameShellout:
case kPostFrameShellout:
case kPostSceneShellout:
if (shelloutProcessing->ShelloutRunning())
if (!shelloutProcessing->KillShellouts(2, false) && !shelloutProcessing->KillShellouts(1, true))
m_Session->AppendErrorAndStatusMessage("Failed to terminate currently-running shellout process") ;
if (state == kPostSceneShellout)
{
state = kDone;
return true;
}
m_Session->SetFailed();
state = kStopped;
result = true;
break;
case kPostShelloutPause:
m_Session->SetFailed();
state = kStopping;
result = true;
break;
case kParsing:
case kPausedParsing:
// the parser could be already in a finished state, even if it accepted a pause earlier
try { renderFrontend.StopParser(sceneId); } catch (pov_base::Exception&) { }
m_Session->SetFailed();
state = kStopping;
result = true;
break;
case kRendering:
case kPausedRendering:
m_Session->SetFailed();
if (m_PausedAfterFrame == true)
{
m_PausedAfterFrame = false;
state = kStopped;
}
else
{
// the renderer could be already in a finished state, even if it accepted a pause earlier
try { renderFrontend.StopRender(viewId); } catch (pov_base::Exception&) { }
state = kStopping;
}
result = true;
break;
}
}
catch (pov_base::Exception& e)
{
m_Session->SetFailed();
m_Session->AppendErrorAndStatusMessage (e.what()) ;
}
try
{
shelloutProcessing->ProcessEvent(ShelloutProcessing::userAbort);
}
catch (pov_base::Exception& e)
{
// if it's a kCannotOpenFileErr, it means permission to run the process was denied
// we don't set failed in that case as we allow shelloutprocessing to handle it
if (!e.codevalid() || (e.code() != kCannotOpenFileErr))
m_Session->SetFailed();
m_Session->AppendErrorAndStatusMessage (e.what()) ;
}
return result;
}
bool VirtualFrontEnd::Pause()
{
try
{
switch(state)
{
case kParsing:
renderFrontend.PauseParser(sceneId);
state = kPausedParsing;
return true;
case kPreSceneShellout:
case kPreFrameShellout:
case kPostFrameShellout:
m_PauseRequested = true;
return true;
case kRendering:
renderFrontend.PauseRender(viewId);
state = kPausedRendering;
return true;
default:
break;
}
}
catch (pov_base::Exception&)
{
return false;
}
return false;
}
bool VirtualFrontEnd::Resume()
{
try
{
switch(state)
{
case kPostShelloutPause:
state = m_PostPauseState;
return true;
case kPausedParsing:
if (renderFrontend.GetSceneState(sceneId) == SceneData::Scene_Paused)
renderFrontend.ResumeParser(sceneId);
state = kParsing;
return true;
case kPausedRendering:
if (m_PausedAfterFrame)
{
state = kStarting;
m_PausedAfterFrame = false;
return true;
}
if (renderFrontend.GetViewState(viewId) == ViewData::View_Paused)
renderFrontend.ResumeRender(viewId);
state = kRendering;
return true;
default:
break;
}
}
catch (pov_base::Exception&)
{
return (false) ;
}
return false;
}
bool VirtualFrontEnd::HandleShelloutCancel()
{
if (!shelloutProcessing->RenderCancelled())
return false;
int code(shelloutProcessing->ExitCode());
string str(shelloutProcessing->GetCancelMessage());
if (code)
{
state = kFailed;
m_Session->SetFailed();
}
else
{
state = kStopped;
m_Session->SetSucceeded(true);
}
m_Session->AppendErrorMessage(str) ;
m_Session->AppendStatusMessage(str) ;
return true;
}
State VirtualFrontEnd::Process()
{
if (state == kReady)
return kReady;
switch(state)
{
case kStarting:
try
{
m_Session->SetSucceeded (false);
if (animationProcessing != NULL)
{
shelloutProcessing->SetFrameClock(animationProcessing->GetFrameNumber(), animationProcessing->GetClockValue());
if (shelloutProcessing->SkipNextFrame() == false)
{
UCS2String filename;
int frame = animationProcessing->GetFrameNumber() - animationProcessing->GetStartFrame() ;
options = animationProcessing->GetFrameRenderOptions ();
if (m_Session->OutputToFileSet())
{
filename = imageProcessing->GetOutputFilename (options, animationProcessing->GetFrameNumber(), animationProcessing->GetFrameNumberDigits());
options.SetUCS2String (kPOVAttrib_OutputFile, filename.c_str());
// test access permission now to avoid surprise later after waiting for
// the render to complete.
if (m_Session->TestAccessAllowed(filename, true) == false)
{
string str ("IO Restrictions prohibit write access to '") ;
str += UCS2toASCIIString(filename);
str += "'";
throw POV_EXCEPTION(kCannotOpenFileErr, str);
}
shelloutProcessing->SetOutputFile(UCS2toASCIIString(filename));
m_Session->AdviseOutputFilename (filename);
}
m_Session->AppendAnimationStatus (frame + 1, animationProcessing->GetTotalFrames(), filename) ;
}
}
bool hadPreScene = shelloutProcessing->HadPreScene();
// will do pre-scene instead if it hasn't yet been done
try
{
shelloutProcessing->ProcessEvent(ShelloutProcessing::preFrame);
}
catch (pov_base::Exception& e)
{
// if it's a kCannotOpenFileErr, it means permission to run the process was denied
// we don't set failed in that case as we allow shelloutprocessing to handle it
m_Session->AppendErrorAndStatusMessage (e.what()) ;
if (!e.codevalid() || (e.code() != kCannotOpenFileErr))
{
m_Session->SetFailed();
return state = kFailed;
}
}
// returns true if cancel has been requested
if (HandleShelloutCancel())
return state;
state = hadPreScene ? kPreFrameShellout : kPreSceneShellout;
return state;
}
catch(pov_base::Exception& e)
{
m_Session->SetFailed();
m_Session->AppendErrorAndStatusMessage (e.what()) ;
return state = kFailed;
}
case kPreSceneShellout:
if (shelloutProcessing->ShelloutRunning() || HandleShelloutCancel())
return state;
// if a pause was requested by the user whilst the shellout was still running, do it now
if (m_PauseRequested)
{
m_PostPauseState = kStarting;
m_PauseRequested = false;
return state = kPostShelloutPause;
}
// go back to kStarting; it won't run pre-scene again
return state = kStarting;
case kPreFrameShellout:
if (shelloutProcessing->ShelloutRunning() || HandleShelloutCancel())
return state;
if (shelloutProcessing->SkipNextFrame())
{
string str(shelloutProcessing->GetSkipMessage());
m_Session->AppendStatusMessage (str) ;
m_Session->AppendStreamMessage (vfeSession::mInformation, str.c_str()) ;
if ((animationProcessing != NULL) && (animationProcessing->MoreFrames() == true))
{
animationProcessing->ComputeNextFrame();
m_Session->SetPixelsRendered(0, m_Session->GetTotalPixels());
m_Session->SetPercentComplete(0);
if (m_PauseRequested)
{
m_PostPauseState = kStarting;
m_PauseRequested = false;
return state = kPostShelloutPause;
}
return state = kStarting;
}
else
{
m_Session->SetSucceeded (true);
if (m_PauseRequested)
{
m_PostPauseState = kStopped;
m_PauseRequested = false;
return state = kPostShelloutPause;
}
return state = kStopped;
}
}
// now set up the scene in preparation for parsing, then start the parser
try { sceneId = renderFrontend.CreateScene(backendAddress, options, boost::bind(&vfe::VirtualFrontEnd::CreateConsole, this)); }
catch(pov_base::Exception& e)
{
m_Session->SetFailed();
m_Session->AppendErrorMessage (e.what()) ;
m_Session->AppendStatusMessage (e.what()) ;
return state = kFailed;
}
try { renderFrontend.StartParser(sceneId, options); }
catch(pov_base::Exception& e)
{
m_Session->SetFailed();
m_Session->AppendErrorMessage (e.what()) ;
m_Session->AppendStatusMessage (e.what()) ;
return state = kFailed;
}
if (m_PauseRequested)
{
m_PostPauseState = kParsing;
m_PauseRequested = false;
return state = kPostShelloutPause;
}
return state = kParsing;
case kParsing:
case kPausedParsing:
switch(renderFrontend.GetSceneState(sceneId))
{
case SceneData::Scene_Paused:
return state = kPausedParsing;
case SceneData::Scene_Failed:
m_Session->SetFailed();
return state = kStopped;
case SceneData::Scene_Stopping:
return state = kStopping;
case SceneData::Scene_Ready:
if (state == kPausedParsing)
{
// it's possible for the parser to transition to Scene_Ready after a successful pause request.
// this typically happens if the request comes in very close to the end of a parse, since the
// task thread only checks the pause state intermittently. we don't start the renderer in this
// case.
return state;
}
try { viewId = renderFrontend.CreateView(sceneId, options, imageProcessing, boost::bind(&vfe::VirtualFrontEnd::CreateDisplay, this, _1, _2, _3)); }
catch(pov_base::Exception& e)
{
m_Session->SetFailed();
m_Session->AppendErrorMessage (e.what()) ;
m_Session->AppendStatusMessage (e.what()) ;
return state = kFailed;
}
try { renderFrontend.StartRender(viewId, options); }
catch(pov_base::Exception& e)
{
m_Session->ClearStatusMessages();
if (e.codevalid() && e.code() == kImageAlreadyRenderedErr)
{
// this is not a failure; continue has been requested and
// the file has already been rendered, so we skip it.
m_Session->AppendStatusMessage ("File already rendered and continue requested; skipping.") ;
m_Session->AppendStreamMessage (vfeSession::mInformation, "File already rendered and continue requested; skipping.") ;
/* [JG] the block here is a duplicate of actions done after
* the post frame shellout (that won't be reached because
* the image was already there).
*/
try { renderFrontend.CloseView(viewId); }
catch (pov_base::Exception&) { /* Ignore any error here! */ }
try { renderFrontend.CloseScene(sceneId); }
catch (pov_base::Exception&) { /* Ignore any error here! */ }
if ((animationProcessing != NULL) && (animationProcessing->MoreFrames() == true))
{
animationProcessing->ComputeNextFrame();
m_Session->SetPixelsRendered(0, m_Session->GetTotalPixels());
m_Session->SetPercentComplete(0);
return state = kStarting;
}
else
{
m_Session->SetSucceeded (true);
return state = kStopped;
}
}
m_Session->SetFailed();
m_Session->AppendErrorMessage (e.what()) ;
m_Session->AppendStatusMessage (e.what()) ;
return state = kFailed;
}
// now we display the render window, if enabled
shared_ptr<Display> display(GetDisplay());
if (display != NULL)
{
vfeDisplay *disp = dynamic_cast<vfeDisplay *>(display.get());
if (disp != NULL)
disp->Show () ;
}
return state = kRendering;
}
return kParsing;
case kRendering:
case kPausedRendering:
switch(renderFrontend.GetViewState(viewId))
{
case ViewData::View_Paused:
return state = kPausedRendering;
case ViewData::View_Failed:
m_Session->SetFailed();
return state = kStopped;
case ViewData::View_Stopping:
return state = kStopping;
case ViewData::View_Rendered:
if (state == kPausedRendering)
{
// it's possible for the renderer to transition to View_Rendered after a successful pause request.
return kPausedRendering;
}
try
{
if (animationProcessing != NULL)
{
if (m_Session->OutputToFileSet())
m_Session->AdviseOutputFilename (imageProcessing->WriteImage(options, animationProcessing->GetFrameNumber(), animationProcessing->GetFrameNumberDigits()));
m_Session->AdviseFrameCompleted();
}
else
if (m_Session->OutputToFileSet())
m_Session->AdviseOutputFilename (imageProcessing->WriteImage(options));
}
catch (pov_base::Exception& e)
{
m_Session->SetFailed();
m_Session->AppendErrorMessage (e.what()) ;
m_Session->AppendStatusMessage (e.what()) ;
// TODO: perhaps we should allow them to pause the queue/insert render
// here if need be.
return state = kFailed;
}
try
{
shelloutProcessing->ProcessEvent(ShelloutProcessing::postFrame);
}
catch (pov_base::Exception& e)
{
// if it's a kCannotOpenFileErr, it means permission to run the process was denied
// we don't set failed in that case as we allow shelloutprocessing to handle it
m_Session->AppendErrorAndStatusMessage (e.what());
if (!e.codevalid() || (e.code() != kCannotOpenFileErr))
{
m_Session->SetFailed();
return state = kFailed;
}
}
// check for cancel here: if the return value is true, state has already been changed
if (HandleShelloutCancel())
return state;
return state = kPostFrameShellout;
default:
break;
}
return kRendering;
case kPostFrameShellout:
if (shelloutProcessing->ShelloutRunning() || HandleShelloutCancel())
return state;
if ((animationProcessing == NULL) || animationProcessing->MoreFrames() == false)
{
m_Session->SetSucceeded (true);
if (m_PauseRequested)
{
m_PostPauseState = kStopped;
m_PauseRequested = false;
return state = kPostShelloutPause;
}
return state = kStopped;
}
if (shelloutProcessing->SkipAllFrames())
{
string str(shelloutProcessing->GetSkipMessage());
m_Session->SetSucceeded (true);
m_Session->AppendStatusMessage (str) ;
m_Session->AppendStreamMessage (vfeSession::mInformation, str.c_str()) ;
if (m_PauseRequested)
{
m_PostPauseState = kStopped;
m_PauseRequested = false;
return state = kPostShelloutPause;
}
return state = kStopped;
}
/* [JG] the actions hereafter should be also done
* when the image already existed: tidy up the data before next frame or stop
*/
try { renderFrontend.CloseView(viewId); }
catch (pov_base::Exception&) { /* Ignore any error here! */ }
try { renderFrontend.CloseScene(sceneId); }
catch (pov_base::Exception&) { /* Ignore any error here! */ }
animationProcessing->ComputeNextFrame();
if (m_Session->GetPauseWhenDone())
{
// wait for a manual continue
m_PausedAfterFrame = true;
m_PauseRequested = false;
return state = kPausedRendering;
}
if (m_PauseRequested)
{
m_PostPauseState = kStarting;
m_PauseRequested = false;
return state = kPostShelloutPause;
}
return state = kStarting;
case kPostSceneShellout:
if (shelloutProcessing->ShelloutRunning())
return state;
return state = kDone;
case kPostShelloutPause:
break;
case kStopping:
if (renderFrontend.GetSceneState(sceneId) == SceneData::Scene_Ready || renderFrontend.GetSceneState(sceneId) == SceneData::Scene_Failed)
return state = kStopped;
if (renderFrontend.GetViewState(viewId) == ViewData::View_Rendered || renderFrontend.GetViewState(viewId) == ViewData::View_Failed)
return state = kStopped;
return kStopping;
case kFailed:
m_Session->SetFailed();
// ShelloutProcessing ignores the fatal error event if it requested a cancel
try { shelloutProcessing->ProcessEvent(ShelloutProcessing::fatalError); }
catch (pov_base::Exception&) { /* Ignore any error here */ }
return state = kStopped;
case kStopped:
try { renderFrontend.CloseView(viewId); }
catch (pov_base::Exception&) { /* Ignore any error here! */ }
try { renderFrontend.CloseScene(sceneId); }
catch (pov_base::Exception&) { /* Ignore any error here! */ }
animationProcessing.reset();
imageProcessing.reset();
// we only run the post-scene or failed action if we have passed the pre-scene point
// i.e. if we stop before pre-scene, we don't run post-scene
if (shelloutProcessing->HadPreScene())
{
if (m_Session->Failed())
{
// it is possible for us to get to kStopped without going through kFailed
// so we call the failed shellout event here just in case: ShelloutProcessing
// can handle being called twice for the same event.
try { shelloutProcessing->ProcessEvent(ShelloutProcessing::fatalError); }
catch (pov_base::Exception&) { /* Ignore any error here */ }
}
if (!shelloutProcessing->HadPostScene())
{
try { shelloutProcessing->ProcessEvent(ShelloutProcessing::postScene); }
catch (pov_base::Exception&) { /* Ignore any error here! */ }
return state = kPostSceneShellout;
}
}
return state = kDone;
case kDone:
return state = kReady;
}
return state;
}
void VirtualFrontEnd::SetResultPointers(Console **cr, Image **ir, Display **dr)
{
consoleResult = cr;
displayResult = dr;
}
bool VirtualFrontEnd::IsPausable (void)
{
switch (GetState ())
{
case kParsing :
case kPausedParsing :
case kRendering :
case kPausedRendering :
case kPreSceneShellout:
case kPreFrameShellout:
case kPostFrameShellout:
case kPostShelloutPause:
return (true) ;
default :
return (false) ;
}
}
bool VirtualFrontEnd::Paused (void)
{
switch (GetState ())
{
case kPausedParsing :
case kPausedRendering :
case kPostShelloutPause:
return (true) ;
case kPreSceneShellout:
case kPreFrameShellout:
case kPostFrameShellout:
return m_PauseRequested;
default :
return (false) ;
}
}
////////////////////////////////////////////////////////////////////////////////////////
//
// helper funtions
//
////////////////////////////////////////////////////////////////////////////////////////
int Allow_File_Write (const char *Filename, const unsigned int FileType)
{
if (strcmp(Filename, "stdout") == 0 || strcmp(Filename, "stderr") == 0)
return true;
return (vfeSession::GetSessionFromThreadID()->TestAccessAllowed(Filename, true));
}
int Allow_File_Write (const unsigned short *Filename, const unsigned int FileType)
{
if (strcmp(UCS2toASCIIString(Filename).c_str(), "stdout") == 0 || strcmp(UCS2toASCIIString(Filename).c_str(), "stderr") == 0)
return true;
return (vfeSession::GetSessionFromThreadID()->TestAccessAllowed(Filename, true));
}
int Allow_File_Read (const char *Filename, const unsigned int FileType)
{
return (vfeSession::GetSessionFromThreadID()->TestAccessAllowed(Filename, false));
}
int Allow_File_Read (const unsigned short *Filename, const unsigned int FileType)
{
return (vfeSession::GetSessionFromThreadID()->TestAccessAllowed(Filename, false));
}
FILE *vfeFOpen (const std::basic_string<unsigned short>& name, const char *mode)
{
return (fopen (UCS2toASCIIString (name).c_str(), mode)) ;
}
bool vfeRemove(const UCS2String& Filename)
{
return (DELETE_FILE (UCS2toASCIIString (Filename).c_str()) == 0);
}
}