DirectX Error on Workspace Switch – D2DERR_WRONG_RESOURCE_DOMAIN from OnRender, despite SharpDX best practices

Dear NinjaTrader Support,

I’m encountering a persistent DirectX error when switching between workspaces, even when following documented best practices for SharpDX resource management in NinjaScript. This happens even in a minimal indicator that draws a single line using a correctly managed SharpDX brush and implements OnRenderTargetChanged() properly.

:white_check_mark: Steps To Reproduce

  1. Add the attached SimpleDxLineTest indicator to any chart.
  2. Switch to a different workspace (leaving the current chart open but no longer visible).
  3. Observe the NinjaScript Output Log or Control Center > Log tab.

You’ll see the following DirectX error immediately upon switching away:

A direct X error has occurred while rendering the chart: HRESULT: [0x88990015] 
Module: [SharpDX.Direct2D1] 
ApiCode: [D2DERR_WRONG_RESOURCE_DOMAIN/WrongResourceDomain] 
Message: The resource was realized on the wrong render target. 

:test_tube: Observations

  • No calls to OnRender() or OnRenderTargetChanged()** are made at the moment of workspace switch (verified via print logs).
  • Error occurs only when switching away from the workspace.
  • When switching back, OnRender() and OnRenderTargetChanged() are called, and rendering resumes without any error.
  • RenderTarget.IsDisposed remains false throughout.

:white_check_mark: Implementation Notes

The indicator:

  • Uses SharpDX properly: brushes are disposed and recreated in OnRenderTargetChanged().
  • Never accesses RenderTarget outside OnRender() or OnRenderTargetChanged().
  • Avoids calling ToDxBrush() at runtime.
  • All rendering is guarded by null checks and IsDisposed checks.

Expected Behavior

If the chart is no longer visible (due to workspace switch), OnRender() should not be invoked — and if it is, SharpDX resources tied to a different render target shouldn’t cause a crash if we’re not actively drawing. Or OnRenderTargetChanged() should be invoked when switching away and back to a workspace so that it can handle the dispose and create of SharpDx resources to prevent the run time error

I’ve attached the SimpleDxLineTest.cs file that demonstrates the issue in isolation.

#region Using declarations

using System;
using System.Windows.Media;
using SharpDX;
using SharpDX.Direct2D1;
using NinjaTrader.Cbi;
using NinjaTrader.Gui.Tools;
using NinjaTrader.NinjaScript;
using NinjaTrader.Data;
using NinjaTrader.Gui.Chart;
using NinjaTrader.NinjaScript.Strategies;
using NinjaTrader.NinjaScript.Indicators;
using Media = System.Windows.Media;

#endregion

namespace NinjaTrader.NinjaScript.Indicators
{
    public class SimpleDxLineTest : Indicator
    {
        private SharpDX.Direct2D1.Brush dxBrush;
        private StrokeStyle strokeStyle;

        protected override void OnStateChange()
        {
            if (State == State.SetDefaults)
            {
                Description = "Draws a DX line at Close[0]";
                Name = "SimpleDxLineTest";
                IsOverlay = true;
                Calculate = Calculate.OnEachTick;
                IsSuspendedWhileInactive = true;
            }
            else if (State == State.Terminated)
            {
                strokeStyle?.Dispose();
                dxBrush?.Dispose();
            }
        }

        public override void OnRenderTargetChanged()
        {
            Print($">>> [OnRenderTargetChanged] called at {DateTime.Now:HH:mm:ss.fff}");

            // Only create strokeStyle once
            if (strokeStyle == null)
            {
                strokeStyle = new StrokeStyle(Core.Globals.D2DFactory,
                    new StrokeStyleProperties { DashStyle = SharpDX.Direct2D1.DashStyle.Solid });
            }

            dxBrush?.Dispose();
            dxBrush = new SharpDX.Direct2D1.SolidColorBrush(RenderTarget, SharpDX.Color.DodgerBlue);
        }

        protected override void OnRender(ChartControl chartControl, ChartScale chartScale)
        {
            Print($">>> [OnRender] called at {DateTime.Now:HH:mm:ss.fff}");

            if (ChartBars == null || chartScale == null || RenderTarget == null)
                return;

            // Draw horizontal line in the middle of chart panel
            float y = ChartPanel.H / 2f;
            RenderTarget.DrawLine(
                new Vector2(0, y),
                new Vector2(ChartPanel.W, y),
                dxBrush,
                2f,
                strokeStyle
            );
        }
    }
}

Please let me know if there is a lifecycle-safe workaround or recommended approach to handle workspace switches cleanly without triggering this error. I’m happy to assist with any additional diagnostics.

Why not just define a public Stroke, and use that in your OnRender?

So much simpler than using Brushes and/or handling SharpDX objects.
Plus, Color, Style, and Opacity All end up being user configurable.

public class HTBlank : Indicator
{
	protected override void OnStateChange()
	{
		if (State == State.SetDefaults)
		{
			Description					= @"";
			Name						= "HTBlank";
			Calculate					= Calculate.OnBarClose;
			IsOverlay					= true;
			PaintPriceMarkers			= false;
			ScaleJustification			= NinjaTrader.Gui.Chart.ScaleJustification.Right;
			IsSuspendedWhileInactive	= true;
			
			MyStroke					= new Stroke(System.Windows.Media.Brushes.Red, NinjaTrader.Gui.DashStyleHelper.Dash, 2f, 100);
		}
	}
	
	protected override void OnBarUpdate() { }

	
	public override void OnRenderTargetChanged()
	{
	   if (RenderTarget != null) MyStroke.RenderTarget = RenderTarget;
	}

	
   	protected override void OnRender(ChartControl chartControl, ChartScale chartScale)
    {
		if(IsInHitTest || RenderTarget == null || ChartPanel == null || MyStroke == null) return; 
        base.OnRender(chartControl, chartScale);
		
		float y = (ChartPanel.H - ChartPanel.Y) / 2f;
		RenderTarget.DrawLine(new SharpDX.Vector2(ChartPanel.X, y), new SharpDX.Vector2(ChartPanel.W, y), MyStroke.BrushDX, MyStroke.Width, MyStroke.StrokeStyle);
	}		

	
	[Display(Name="MyStoke", Description=@"MyStoke", Order=0, GroupName="Settings")]
	public Stroke MyStroke { get; set; }

}

Be Safe in this Crazy World!
:smiling_face_with_sunglasses:

1 Like

Thank you for reviewing this and writing this coding suggestion. It’s very useful to know this and I will use this for indicators that require simpler SharpDX rendering. My actual indicator is more complicated with the use of multiple strokes, gradient fills, different brushes for up/down candles, filled candles. So for that indicator I may need to manage the SharpDX resources.

However I did compile this code and found that this never caused the run-time error in my System Log when switching workspaces. Puzzled by this, I then found the root cause of my problem. I had another indicator in the workspace which had not implemented OnRenderTargetChanged() and was not disposing and recreating SharpDX resources and it was this indicator giving the run-time errors.

This thread can be closed as issue identified due to unrelated indicator using SharpDX but not implementing OnRenderTargetChanged() to dispose and recreate SharpDX resources as per guidelines.

Thanks for this gem. I suppose same could be done with text styling?

Edit: I see you did that there: Drawing tool for swing highs and swing lows - #8 by Edge

Create SimpleFont public property to be used for TextFormat.
TextFormat can be declared and assigned once, then re-used.
TextLayout not so much. I would recommend using() statement.

While you do not need a TextLayout to just render a DrawText.
Easier to grab the height and length of string from Metrics.

	public class HTBlank : Indicator
	{
		private bool 	isConfigSet	= false;
		private string 	myString 	= "Test String";
		private SharpDX.DirectWrite.TextFormat myTextFormat;
		
		protected override void OnStateChange()
		{
			if (State == State.SetDefaults)
			{
				Description					= @"";
				Name						= "HTBlank";
				Calculate					= Calculate.OnBarClose;
				IsOverlay					= true;
				PaintPriceMarkers			= false;
				ScaleJustification			= NinjaTrader.Gui.Chart.ScaleJustification.Right;
				IsSuspendedWhileInactive	= true;
				
				MyStroke					= new Stroke(System.Windows.Media.Brushes.Red, NinjaTrader.Gui.DashStyleHelper.Dash, 2f, 100);
				MySimpleFont		 		= new NinjaTrader.Gui.Tools.SimpleFont("Arial", 16) { Size = 16, Bold = true };
			}
			else if (State == State.Configure)
			{
				isConfigSet = true;
				myTextFormat = MySimpleFont.ToDirectWriteTextFormat();
				myTextFormat.TextAlignment = SharpDX.DirectWrite.TextAlignment.Leading;
			}
			else if (State == State.Terminated)
			{
				if(!isConfigSet) return;
				
				if(myTextFormat != null)
				{
					myTextFormat.Dispose();
					myTextFormat = null;
				}
			}
		}
		
		protected override void OnBarUpdate() { }
		
		
		public override void OnRenderTargetChanged()
		{
		   if (RenderTarget != null) MyStroke.RenderTarget = RenderTarget;
		}
		
		
	   	protected override void OnRender(ChartControl chartControl, ChartScale chartScale)
        {
			if(IsInHitTest || RenderTarget == null || ChartPanel == null || myTextFormat == null || MyStroke == null) return; 
	        base.OnRender(chartControl, chartScale);
			
			//DrawLine
			float y = (ChartPanel.H - ChartPanel.Y) / 2f;
			RenderTarget.DrawLine(new SharpDX.Vector2(ChartPanel.X, y), new SharpDX.Vector2(ChartPanel.W, y), MyStroke.BrushDX, MyStroke.Width, MyStroke.StrokeStyle);
			
			//DrawText
			float x = (ChartPanel.W - ChartPanel.X) / 2f;
			RenderTarget.DrawText(myString, myTextFormat, new SharpDX.RectangleF(x, y, 250f, 50f ), MyStroke.BrushDX);
			
			//DrawTextLayout
			using(SharpDX.DirectWrite.TextLayout myTextLayout = new SharpDX.DirectWrite.TextLayout(Core.Globals.DirectWriteFactory, myString, myTextFormat, 250f, 50f))
			{
				RenderTarget.DrawTextLayout(new SharpDX.Vector2(x - myTextLayout.Metrics.Width/2, y - myTextLayout.Metrics.Height), myTextLayout, MyStroke.BrushDX);
			}
		}		
		
		
		[Display(Name="MyStoke", Description=@"MyStoke", Order=0, GroupName="Settings")]
		public Stroke MyStroke { get; set; }
	
		[Display(Name="MySimpleFont", Description=@"MySimpleFont", Order=1, GroupName="Settings")]
		public SimpleFont MySimpleFont { get; set; }
		
		
	}

Be Safe in this Crazy World!
:smiling_face_with_sunglasses: