Allow rendering the graph on a background thread (#1014)

* Render on background thread

* More work

* variable fix

* Add comments
This commit is contained in:
Pepe Rivera 2020-02-04 15:02:42 -08:00 committed by GitHub
parent f4ab94ce1c
commit f1482252ef
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 120 additions and 50 deletions

View File

@ -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)

View File

@ -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)

View File

@ -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();

View File

@ -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)
{

View File

@ -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")
{

View File

@ -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);
}
}

View File

@ -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();

View File

@ -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);

View File

@ -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;
};
}