Allow rendering the graph on a background thread (#1014)
* Render on background thread * More work * variable fix * Add comments
This commit is contained in:
		@@ -47,11 +47,13 @@ public
 | 
			
		||||
            {
 | 
			
		||||
                if (value < Min)
 | 
			
		||||
                {
 | 
			
		||||
                    value = Min;
 | 
			
		||||
                    Min = value;
 | 
			
		||||
                    RaisePropertyChanged(L"Min");
 | 
			
		||||
                }
 | 
			
		||||
                else if (value > Max)
 | 
			
		||||
                {
 | 
			
		||||
                    value = Max;
 | 
			
		||||
                    Max = value;
 | 
			
		||||
                    RaisePropertyChanged(L"Max");
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if (Value != value)
 | 
			
		||||
 
 | 
			
		||||
@@ -319,26 +319,6 @@ void EquationTextBox::OnHasErrorPropertyChanged(bool, bool)
 | 
			
		||||
    UpdateCommonVisualState();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Platform::String ^ EquationTextBox::GetEquationText()
 | 
			
		||||
{
 | 
			
		||||
    String ^ text;
 | 
			
		||||
    if (m_richEditBox != nullptr)
 | 
			
		||||
    {
 | 
			
		||||
        // Clear formatting since the graph control doesn't work with bold/underlines
 | 
			
		||||
        ITextRange ^ range = m_richEditBox->TextDocument->GetRange(0, m_richEditBox->TextDocument->Selection->EndPosition);
 | 
			
		||||
 | 
			
		||||
        if (range != nullptr)
 | 
			
		||||
        {
 | 
			
		||||
            range->CharacterFormat->Bold = FormatEffect::Off;
 | 
			
		||||
            range->CharacterFormat->Underline = UnderlineType::None;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        text = m_richEditBox->MathText;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return text;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void EquationTextBox::SetEquationText(Platform::String ^ equationText)
 | 
			
		||||
{
 | 
			
		||||
    if (m_richEditBox != nullptr)
 | 
			
		||||
 
 | 
			
		||||
@@ -33,7 +33,6 @@ namespace CalculatorApp
 | 
			
		||||
            event Windows::Foundation::EventHandler<MathRichEditBoxFormatRequest ^> ^ EquationFormatRequested;
 | 
			
		||||
            event Windows::UI::Xaml::RoutedEventHandler ^ EquationButtonClicked;
 | 
			
		||||
 | 
			
		||||
            Platform::String ^ GetEquationText();
 | 
			
		||||
            void SetEquationText(Platform::String ^ equationText);
 | 
			
		||||
            void FocusTextBox();
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -12,6 +12,7 @@ using namespace std;
 | 
			
		||||
using namespace Windows::ApplicationModel;
 | 
			
		||||
using namespace Windows::UI::Xaml;
 | 
			
		||||
using namespace Windows::UI::Xaml::Controls;
 | 
			
		||||
using namespace Windows::UI::Text;
 | 
			
		||||
using namespace Windows::Foundation::Collections;
 | 
			
		||||
using namespace Windows::System;
 | 
			
		||||
 | 
			
		||||
@@ -178,6 +179,15 @@ void MathRichEditBox::BackSpace()
 | 
			
		||||
 | 
			
		||||
void MathRichEditBox::SubmitEquation(EquationSubmissionSource source)
 | 
			
		||||
{
 | 
			
		||||
    // Clear formatting since the graph control doesn't work with bold/underlines
 | 
			
		||||
    auto range = this->TextDocument->GetRange(0, this->TextDocument->Selection->EndPosition);
 | 
			
		||||
 | 
			
		||||
    if (range != nullptr)
 | 
			
		||||
    {
 | 
			
		||||
        range->CharacterFormat->Bold = FormatEffect::Off;
 | 
			
		||||
        range->CharacterFormat->Underline = UnderlineType::None;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    auto newVal = GetMathTextProperty();
 | 
			
		||||
    if (MathText != newVal)
 | 
			
		||||
    {
 | 
			
		||||
 
 | 
			
		||||
@@ -330,9 +330,6 @@ void EquationInputArea::SubmitTextbox(TextBox ^ sender)
 | 
			
		||||
    {
 | 
			
		||||
        val = validateDouble(sender->Text, variableViewModel->Value);
 | 
			
		||||
        variableViewModel->Value = val;
 | 
			
		||||
 | 
			
		||||
        // Assign back to val in case it gets changed due to min/max
 | 
			
		||||
        val = variableViewModel->Value;
 | 
			
		||||
    }
 | 
			
		||||
    else if (sender->Name == "MinTextBox")
 | 
			
		||||
    {
 | 
			
		||||
 
 | 
			
		||||
@@ -12,11 +12,13 @@ using namespace GraphControl::DX;
 | 
			
		||||
using namespace Platform;
 | 
			
		||||
using namespace Platform::Collections;
 | 
			
		||||
using namespace std;
 | 
			
		||||
using namespace Concurrency;
 | 
			
		||||
using namespace Windows::Devices::Input;
 | 
			
		||||
using namespace Windows::Foundation;
 | 
			
		||||
using namespace Windows::Foundation::Collections;
 | 
			
		||||
using namespace Windows::Storage::Streams;
 | 
			
		||||
using namespace Windows::System;
 | 
			
		||||
using namespace Windows::System::Threading;
 | 
			
		||||
using namespace Windows::UI;
 | 
			
		||||
using namespace Windows::UI::Core;
 | 
			
		||||
using namespace Windows::UI::Input;
 | 
			
		||||
@@ -228,9 +230,9 @@ namespace GraphControl
 | 
			
		||||
        TryPlotGraph(keepCurrentView, false);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void Grapher::TryPlotGraph(bool keepCurrentView, bool shouldRetry)
 | 
			
		||||
    task<void> Grapher::TryPlotGraph(bool keepCurrentView, bool shouldRetry)
 | 
			
		||||
    {
 | 
			
		||||
        if (TryUpdateGraph(keepCurrentView))
 | 
			
		||||
        if (co_await TryUpdateGraph(keepCurrentView))
 | 
			
		||||
        {
 | 
			
		||||
            SetEquationsAsValid();
 | 
			
		||||
        }
 | 
			
		||||
@@ -241,12 +243,12 @@ namespace GraphControl
 | 
			
		||||
            // If we failed to plot the graph, try again after the bad equations are flagged.
 | 
			
		||||
            if (shouldRetry)
 | 
			
		||||
            {
 | 
			
		||||
                TryUpdateGraph(keepCurrentView);
 | 
			
		||||
                co_await TryUpdateGraph(keepCurrentView);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    bool Grapher::TryUpdateGraph(bool keepCurrentView)
 | 
			
		||||
    task<bool> Grapher::TryUpdateGraph(bool keepCurrentView)
 | 
			
		||||
    {
 | 
			
		||||
        optional<vector<shared_ptr<IEquation>>> initResult = nullopt;
 | 
			
		||||
        bool successful = false;
 | 
			
		||||
@@ -282,7 +284,7 @@ namespace GraphControl
 | 
			
		||||
                    // If the equation request failed, then fail graphing.
 | 
			
		||||
                    if (equationRequest == nullptr)
 | 
			
		||||
                    {
 | 
			
		||||
                        return false;
 | 
			
		||||
                        co_return false;
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    request += equationRequest;
 | 
			
		||||
@@ -303,7 +305,8 @@ namespace GraphControl
 | 
			
		||||
                    m_renderMain->Graph = m_graph;
 | 
			
		||||
 | 
			
		||||
                    // It is possible that the render fails, in that case fall through to explicit empty initialization
 | 
			
		||||
                    if (m_renderMain->RunRenderPass())
 | 
			
		||||
                    co_await m_renderMain->RunRenderPassAsync(false);
 | 
			
		||||
                    if (m_renderMain->IsRenderPassSuccesful())
 | 
			
		||||
                    {
 | 
			
		||||
                        UpdateVariables();
 | 
			
		||||
                        successful = true;
 | 
			
		||||
@@ -329,7 +332,7 @@ namespace GraphControl
 | 
			
		||||
                        SetGraphArgs();
 | 
			
		||||
 | 
			
		||||
                        m_renderMain->Graph = m_graph;
 | 
			
		||||
                        m_renderMain->RunRenderPass();
 | 
			
		||||
                        co_await m_renderMain->RunRenderPassAsync();
 | 
			
		||||
 | 
			
		||||
                        UpdateVariables();
 | 
			
		||||
 | 
			
		||||
@@ -341,7 +344,7 @@ namespace GraphControl
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Return true if we were able to graph and render all graphable equations
 | 
			
		||||
        return successful;
 | 
			
		||||
        co_return successful;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void Grapher::SetEquationsAsValid()
 | 
			
		||||
@@ -365,8 +368,10 @@ namespace GraphControl
 | 
			
		||||
 | 
			
		||||
    void Grapher::SetGraphArgs()
 | 
			
		||||
    {
 | 
			
		||||
        if (m_graph)
 | 
			
		||||
        if (m_graph != nullptr && m_renderMain != nullptr)
 | 
			
		||||
        {
 | 
			
		||||
            critical_section::scoped_lock lock(m_renderMain->GetCriticalSection());
 | 
			
		||||
 | 
			
		||||
            for (auto variable : Variables)
 | 
			
		||||
            {
 | 
			
		||||
                m_graph->SetArgValue(variable->Key->Data(), variable->Value);
 | 
			
		||||
@@ -436,14 +441,19 @@ namespace GraphControl
 | 
			
		||||
 | 
			
		||||
        Variables->Insert(variableName, newValue);
 | 
			
		||||
 | 
			
		||||
        if (m_graph)
 | 
			
		||||
        if (m_graph != nullptr && m_renderMain != nullptr)
 | 
			
		||||
        {
 | 
			
		||||
            m_graph->SetArgValue(variableName->Data(), newValue);
 | 
			
		||||
 | 
			
		||||
            if (m_renderMain)
 | 
			
		||||
            {
 | 
			
		||||
                m_renderMain->RunRenderPass();
 | 
			
		||||
            }
 | 
			
		||||
                auto workItemHandler = ref new WorkItemHandler([this, variableName, newValue](IAsyncAction ^ action) {
 | 
			
		||||
                m_renderMain->GetCriticalSection().lock();
 | 
			
		||||
                m_graph->SetArgValue(variableName->Data(), newValue);
 | 
			
		||||
                m_renderMain->GetCriticalSection().unlock();
 | 
			
		||||
 | 
			
		||||
                m_renderMain->RunRenderPassAsync();
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            ThreadPool::RunAsync(workItemHandler, WorkItemPriority::High, WorkItemOptions::None);
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -272,8 +272,8 @@ public
 | 
			
		||||
        void OnEquationChanged(Equation ^ equation);
 | 
			
		||||
        void OnEquationStyleChanged(Equation ^ equation);
 | 
			
		||||
        void OnEquationLineEnabledChanged(Equation ^ equation);
 | 
			
		||||
        bool TryUpdateGraph(bool keepCurrentView);
 | 
			
		||||
        void TryPlotGraph(bool keepCurrentView, bool shouldRetry);
 | 
			
		||||
        concurrency::task<bool> TryUpdateGraph(bool keepCurrentView);
 | 
			
		||||
        concurrency::task<void> TryPlotGraph(bool keepCurrentView, bool shouldRetry);
 | 
			
		||||
        void UpdateGraphOptions(Graphing::IGraphingOptions& options, const std::vector<Equation ^>& validEqs);
 | 
			
		||||
        std::vector<Equation ^> GetGraphableEquations();
 | 
			
		||||
        void SetGraphArgs();
 | 
			
		||||
 
 | 
			
		||||
@@ -63,7 +63,6 @@ namespace GraphControl::DX
 | 
			
		||||
                renderer->SetDpi(dpi, dpi);
 | 
			
		||||
 | 
			
		||||
                renderer->SetGraphSize(static_cast<unsigned int>(m_swapChainPanel->ActualWidth), static_cast<unsigned int>(m_swapChainPanel->ActualHeight));
 | 
			
		||||
              
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
@@ -130,6 +129,54 @@ namespace GraphControl::DX
 | 
			
		||||
 | 
			
		||||
    bool RenderMain::RunRenderPass()
 | 
			
		||||
    {
 | 
			
		||||
        // Non async render passes cancel if they can't obtain the lock immediatly
 | 
			
		||||
        if (!m_criticalSection.try_lock())
 | 
			
		||||
        {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        m_criticalSection.unlock();
 | 
			
		||||
 | 
			
		||||
        critical_section::scoped_lock lock(m_criticalSection);
 | 
			
		||||
 | 
			
		||||
        return RunRenderPassInternal();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    IAsyncAction ^ RenderMain::RunRenderPassAsync(bool allowCancel)
 | 
			
		||||
    {
 | 
			
		||||
        // Try to cancel the renderPass that is in progress
 | 
			
		||||
        if (m_renderPass != nullptr && m_renderPass->Status == ::AsyncStatus::Started)
 | 
			
		||||
        {
 | 
			
		||||
            m_renderPass->Cancel();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        auto device = m_deviceResources;
 | 
			
		||||
        auto workItemHandler = ref new WorkItemHandler([this, allowCancel](IAsyncAction ^ action) {
 | 
			
		||||
            critical_section::scoped_lock lock(m_criticalSection);
 | 
			
		||||
 | 
			
		||||
            // allowCancel is passed as false when the grapher relies on the render pass to validate that an equation can be succesfully rendered.
 | 
			
		||||
            // Passing false garauntees that another render pass doesn't cancel this one.
 | 
			
		||||
            if (allowCancel && action->Status == ::AsyncStatus::Canceled)
 | 
			
		||||
            {
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            RunRenderPassInternal();
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        m_renderPass = ThreadPool::RunAsync(workItemHandler, WorkItemPriority::High, WorkItemOptions::None);
 | 
			
		||||
 | 
			
		||||
        return m_renderPass;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    bool RenderMain::RunRenderPassInternal()
 | 
			
		||||
    {
 | 
			
		||||
        // We are accessing Direct3D resources directly without Direct2D's knowledge, so we
 | 
			
		||||
        // must manually acquire and apply the Direct2D factory lock.
 | 
			
		||||
        ID2D1Multithread* m_D2DMultithread;
 | 
			
		||||
        m_deviceResources.GetD2DFactory()->QueryInterface(IID_PPV_ARGS(&m_D2DMultithread));
 | 
			
		||||
        m_D2DMultithread->Enter();
 | 
			
		||||
 | 
			
		||||
        bool succesful = Render();
 | 
			
		||||
 | 
			
		||||
        if (succesful)
 | 
			
		||||
@@ -137,7 +184,12 @@ namespace GraphControl::DX
 | 
			
		||||
            m_deviceResources.Present();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return succesful;
 | 
			
		||||
        // It is absolutely critical that the factory lock be released upon
 | 
			
		||||
        // exiting this function, or else any consequent Direct2D calls will be blocked.
 | 
			
		||||
        m_D2DMultithread->Leave();
 | 
			
		||||
 | 
			
		||||
        m_isRenderPassSuccesful = succesful;
 | 
			
		||||
        return m_isRenderPassSuccesful;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Renders the current frame according to the current application state.
 | 
			
		||||
@@ -204,11 +256,11 @@ namespace GraphControl::DX
 | 
			
		||||
                            {
 | 
			
		||||
                                auto lineColors = m_graph->GetOptions().GetGraphColors();
 | 
			
		||||
 | 
			
		||||
                            if (formulaId >= 0 && static_cast<unsigned int>(formulaId) < lineColors.size())
 | 
			
		||||
                            {
 | 
			
		||||
                                auto dotColor = lineColors[formulaId];
 | 
			
		||||
                                m_nearestPointRenderer.SetColor(D2D1::ColorF(dotColor.R * 65536 + dotColor.G * 256 + dotColor.B, 1.0));
 | 
			
		||||
                            }
 | 
			
		||||
                                if (formulaId >= 0 && static_cast<unsigned int>(formulaId) < lineColors.size())
 | 
			
		||||
                                {
 | 
			
		||||
                                    auto dotColor = lineColors[formulaId];
 | 
			
		||||
                                    m_nearestPointRenderer.SetColor(D2D1::ColorF(dotColor.R * 65536 + dotColor.G * 256 + dotColor.B, 1.0));
 | 
			
		||||
                                }
 | 
			
		||||
 | 
			
		||||
                                m_TraceLocation = Point(nearestPointLocationX, nearestPointLocationY);
 | 
			
		||||
                                m_nearestPointRenderer.Render(m_TraceLocation);
 | 
			
		||||
 
 | 
			
		||||
@@ -47,6 +47,18 @@ namespace GraphControl::DX
 | 
			
		||||
 | 
			
		||||
        bool RunRenderPass();
 | 
			
		||||
 | 
			
		||||
        Windows::Foundation::IAsyncAction ^ RunRenderPassAsync(bool allowCancel = true);
 | 
			
		||||
 | 
			
		||||
        Concurrency::critical_section& GetCriticalSection()
 | 
			
		||||
        {
 | 
			
		||||
            return m_criticalSection;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        bool IsRenderPassSuccesful()
 | 
			
		||||
        {
 | 
			
		||||
            return m_isRenderPassSuccesful;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Indicates if we are in active tracing mode (the tracing box is being used and controlled through keyboard input)
 | 
			
		||||
        property bool ActiveTracing
 | 
			
		||||
        {
 | 
			
		||||
@@ -99,6 +111,8 @@ namespace GraphControl::DX
 | 
			
		||||
    private:
 | 
			
		||||
        bool Render();
 | 
			
		||||
 | 
			
		||||
        bool RunRenderPassInternal();
 | 
			
		||||
 | 
			
		||||
        // Loaded/Unloaded
 | 
			
		||||
        void OnLoaded(Platform::Object ^ sender, Windows::UI::Xaml::RoutedEventArgs ^ e);
 | 
			
		||||
 | 
			
		||||
@@ -162,5 +176,11 @@ namespace GraphControl::DX
 | 
			
		||||
 | 
			
		||||
        // Are we currently showing the tracing value
 | 
			
		||||
        bool m_Tracing;
 | 
			
		||||
 | 
			
		||||
        Concurrency::critical_section m_criticalSection;
 | 
			
		||||
 | 
			
		||||
         Windows::Foundation::IAsyncAction ^ m_renderPass = nullptr;
 | 
			
		||||
 | 
			
		||||
         bool m_isRenderPassSuccesful;
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user