/******************************************************************************* * 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 . * --------------------------------------------------------------------------- * 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) : 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 (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 (new ImageProcessing (opts)) ; animationProcessing = shared_ptr (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(GetDisplay()); if (display != NULL) { vfeDisplay *disp = dynamic_cast(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& name, const char *mode) { return (fopen (UCS2toASCIIString (name).c_str(), mode)) ; } bool vfeRemove(const UCS2String& Filename) { return (DELETE_FILE (UCS2toASCIIString (Filename).c_str()) == 0); } }