Click here to Skip to main content
15,894,540 members
Articles / Multimedia / DirectX

The Console Reinvented

Rate me:
Please Sign up or sign in to vote.
4.82/5 (26 votes)
26 Mar 2009CPOL9 min read 76.6K   805   76  
A multi-view console written in C# and DirectX.
namespace Nesteruk.MdxConsole
{
  using System;
  using System.Collections.Generic;
  using System.ComponentModel;
  using System.Diagnostics;
  using System.Drawing;
  using System.Threading;
  using System.Windows.Forms;
  using Microsoft.DirectX;
  using Microsoft.DirectX.Direct3D;

  /// <summary>
  /// Represents either a form or a full-screen application that contains a
  /// DirectX-driven console.
  /// </summary>
  /// <example>
  /// The easiest way to use the <c>Console</c> is by creating it with one
  /// <c>Buffer</c> and one <c>Viewport</c> as follows:
  /// <para>
  /// using (Console c = Console.NewConsole(30, 20)) {
  ///   c.Show();
  ///   // your code here
  ///   while (c.Created) {
  ///     c.Render();
  ///     Application.DoEvents();
  ///   }
  /// }
  /// </para>
  /// </example>
  public class Console : Form
  {
    #region Fields

    private const float distanceToObject = 1.0f;
    private readonly Device device;
    private readonly PresentParameters pp;
    private readonly TextureManager texManager;
    private readonly IList<Viewport> viewports = new List<Viewport>();
    private Color backgroundColor = Color.Black;
    private Size charSize;
    private Color foregroundColor = Color.White;
    private Size gridSize;
    private VertexBuffer vb;

    #endregion

    #region Constructors

    /// <summary>
    /// Creates a windowed console with 10x14 characters.
    /// </summary>
    public Console() : this(false)
    {
    }

    /// <summary>
    /// Creates a console with 10x14 characters.
    /// </summary>
    /// <param name="fullScreen">A flag to indicate whether the console should be full-screen.</param>
    public Console(bool fullScreen) : this(fullScreen, 10, 14, 200, 280)
    {
    }

    public Console(Viewport viewport) : this(false, 10, 14, 10*viewport.Size.Width, 14*viewport.Size.Height)
    {
      viewports.Add(viewport);
    }


    /// <summary>
    /// Creates a console.
    /// </summary>
    /// <param name="fullScreen">A flag to indicate whether the console should be full-screen.</param>
    /// <param name="charWidth">Width of a character.</param>
    /// <param name="charHeight">Height of a character.</param>
    /// <param name="windowWidth">Window width. Ignored for full-screen mode.</param>
    /// <param name="windowHeight">Window height. Ignored for full-screen mode.</param>
    public Console(bool fullScreen, int charWidth, int charHeight, int windowWidth, int windowHeight)
    {
      #region some obvious window settings

      // if this is windowed and size is provided, set it
      if (!fullScreen && windowWidth >= 0 && windowHeight >= 0)
      {
        ClientSize = new Size(windowWidth, windowHeight);
      } // if this is full-screen, get the size and set it
      else if (fullScreen)
      {
        FormBorderStyle = FormBorderStyle.None;
        ClientSize = new Size(Manager.Adapters[0].CurrentDisplayMode.Width,
                              Manager.Adapters[0].CurrentDisplayMode.Height);
      }

      // allowing min/max is a bad idea
      MaximizeBox = MinimizeBox = false;
      StartPosition = FormStartPosition.CenterParent;

      #endregion

      Load += OnLoad;

      FullScreen = fullScreen;
      InitializePresentParameters(out pp, fullScreen);

      #region create device and wire events

      // here are a couple of pairs to try
      var pairsToTry = new[]
      {
        new Pair<DeviceType, CreateFlags>(DeviceType.Hardware, CreateFlags.HardwareVertexProcessing),
        new Pair<DeviceType, CreateFlags>(DeviceType.Hardware, CreateFlags.SoftwareVertexProcessing),
        new Pair<DeviceType, CreateFlags>(DeviceType.Software, CreateFlags.SoftwareVertexProcessing),
        new Pair<DeviceType, CreateFlags>(DeviceType.Reference, CreateFlags.SoftwareVertexProcessing),
      };

      for (int i = 0; i < pairsToTry.Length; i++)
      {
        Pair<DeviceType, CreateFlags> p = pairsToTry[i];
        try
        {
          device = new Device(0, p.First, this, p.Second, pp);
          break;
        }
        catch
        {
          continue;
        }
      }

      if (device == null)
        throw new ApplicationException("Could not create device.");

      device.DeviceReset += OnResetDevice;
      device.DeviceLost += OnLostDevice;
      device.DeviceResizing += OnResizeDevice;

      OnResetDevice(device, null);

      #endregion

      texManager = new TextureManager(device);
      charSize = new Size(charWidth, charHeight);
      gridSize = new Size((int) Math.Floor((double) ClientSize.Width/charWidth),
                          (int) Math.Floor((double) ClientSize.Height/charHeight));
      Debug.WriteLine(string.Format("Grid size has been set to {0} by {1}", gridSize.Width, gridSize.Height));
    }

    #endregion

    #region Properties

    /// <summary>
    /// A flag to inficate whether the console is full-screen or not.
    /// </summary>
    public bool FullScreen { get; protected internal set; }

    /// <summary>
    /// Background color.
    /// </summary>
    public Color BackgroundColor
    {
      get { return backgroundColor; }
      set { backgroundColor = value; }
    }

    /// <summary>
    /// Foreground (i.e., text) color.
    /// </summary>
    public Color ForegroundColor
    {
      get { return foregroundColor; }
      set { foregroundColor = value; }
    }

    /// <summary>
    /// The list of viewports in this console.
    /// </summary>
    public IList<Viewport> Viewports
    {
      get { return viewports; }
    }

    /// <summary>
    /// The texture manager for this console.
    /// </summary>
    public TextureManager TexManager
    {
      get { return texManager; }
    }

    public int CurrentViewport
    {
      get;
      set;
    }

    #endregion

    #region Methods

    #region Relayed Write

    public Console Write(string s)
    {
      return WriteFormat(s, 0);
    }

    public Console WriteLine(string s)
    {
      return Write(s + Environment.NewLine);
    }

    public Console WriteLineFormat(string s, short format)
    {
      return WriteFormat(s + Environment.NewLine, format);
    }

    public Console WriteFormat(string s, short format)
    {
      if (viewports.Count > 0)
        if (viewports[CurrentViewport].Buffer != null)
          viewports[CurrentViewport].Buffer.Write(s, format);
      return this;
    }

    #endregion

    /// <summary>
    /// Sets up the vertex buffer to hold a rectangle of a size matching the
    /// size of a glyph texture.
    /// </summary>
    /// <param name="sender">The vertex buffer.</param>
    /// <param name="e">Typically <c>null</c>.</param>
    private void OnCreateVertexBuffer(object sender, EventArgs e)
    {
      // co-ordinates are explicitly shifted by half a pixel each way
      // to compensate for pixel/texel mismatch
      var v = (CustomVertex.PositionTextured[]) vb.Lock(0, 0);
      v[0].X = 0.0f + 0.5f;
      v[0].Y = 0.0f + 0.5f;
      v[0].Z = 0.0f;
      v[0].Tu = 0.0f;
      v[0].Tv = 1.0f;

      v[1].X = charSize.Width + 0.5f;
      v[1].Y = 0.0f + 0.5f;
      v[1].Z = 0.0f;
      v[1].Tu = 1.0f;
      v[1].Tv = 1.0f;

      v[2].X = charSize.Width + 0.5f;
      v[2].Y = charSize.Height + 0.5f;
      v[2].Z = 0.0f;
      v[2].Tu = 1.0f;
      v[2].Tv = 0.0f;

      v[3].X = 0.0f + 0.5f;
      v[3].Y = charSize.Height + 0.5f;
      v[3].Z = 0.0f;
      v[3].Tu = 0.0f;
      v[3].Tv = 0.0f;

      vb.Unlock();
    }


    private void OnLoad(object sender, EventArgs e)
    {
      // create an ordinary vertex buffer
      vb = new VertexBuffer(
        typeof (CustomVertex.PositionTextured), 4, device,
        Usage.WriteOnly, CustomVertex.PositionTextured.Format, Pool.Default);
      vb.Created += OnCreateVertexBuffer;
      OnCreateVertexBuffer(vb, null);
    }

    /// <summary>
    /// Initializes a <c>PresentParameters</c> structure.
    /// </summary>
    /// <param name="presentParams">A <c>PresentParameters</c> structure.</param>
    /// <param name="fullscreen">If true, the parameters will be initialized for a
    /// full-screen application; otherwise, they will be initialized for a windowed
    /// application.</param>
    /// <seealso cref="PresentParameters"/>
    private void InitializePresentParameters(out PresentParameters presentParams, bool fullscreen)
    {
      presentParams = new PresentParameters();

      if (fullscreen)
      {
        #region Fullscreen

        const Format adapterFormat = Format.X8R8G8B8;
        DisplayMode dm = Manager.Adapters[0].CurrentDisplayMode;
        presentParams.BackBufferWidth = dm.Width;
        presentParams.BackBufferHeight = dm.Height;
        presentParams.BackBufferFormat = adapterFormat;
        presentParams.BackBufferCount = 1;
        presentParams.MultiSample = MultiSampleType.None;
        presentParams.MultiSampleQuality = 0;
        presentParams.SwapEffect = SwapEffect.Discard;
        presentParams.DeviceWindowHandle = Handle;
        presentParams.Windowed = false;
        presentParams.EnableAutoDepthStencil = true;
        presentParams.AutoDepthStencilFormat = DepthFormat.D16;
        presentParams.PresentFlag = PresentFlag.DiscardDepthStencil;
        presentParams.FullScreenRefreshRateInHz = dm.RefreshRate;
        presentParams.PresentationInterval = PresentInterval.Immediate;

        #endregion
      }
      else
      {
        #region Windowed

        presentParams.SwapEffect = SwapEffect.Discard;
        presentParams.Windowed = true;
        presentParams.AutoDepthStencilFormat = DepthFormat.D16;
        presentParams.EnableAutoDepthStencil = true;

        #endregion
      }
    }

    private void ResetDeviceStates()
    {
      device.RenderState.CullMode = Cull.None;
      device.RenderState.Lighting = false;
      device.RenderState.ZBufferEnable = false;
      device.SetSamplerState(0, SamplerStageStates.MinFilter, 0);
      device.SetSamplerState(0, SamplerStageStates.MagFilter, 0);
    }

    #region Rendering

    /// <summary>
    /// Renders the window area.
    /// </summary>
    public void Render()
    {
      if (!device.Disposed)
      {
        try
        {
          device.TestCooperativeLevel();
          device.Clear(ClearFlags.Target | ClearFlags.ZBuffer, backgroundColor, 1.0f, 0);
          device.BeginScene();
          {
            SetupMatrices();
            RenderConsole();
          }
          device.EndScene();
          device.Present();
        }
        catch (DeviceLostException)
        {
          Thread.Sleep(500);
        }
        catch (DeviceNotResetException)
        {
          device.Reset();
        }
        catch (Exception ex)
        {
          MessageBox.Show(ex.Message);
        }
      }
    }

    private void RenderConsole()
    {
      device.Transform.View = Matrix.Translation(- device.Viewport.Width/2,
                                                 device.Viewport.Height/2 - charSize.Height, 0);

      device.VertexFormat = CustomVertex.PositionTextured.Format;
      device.SetStreamSource(0, vb, 0);

      for (int y = 0; y < gridSize.Height; ++y)
      {
        for (int x = 0; x < gridSize.Width; ++x)
        {
          bool set = false;
          for (int i = 0; i < Viewports.Count; i++)
          {
            Viewport v = Viewports[i];
            if (v.CoversPoint(x, y)) // check that viewport corresponds to gridsas
            {
              TexManager.CurrentPreset = v.Buffer.FormatBuffer[x - v.ScreenLocation.X, y - v.ScreenLocation.Y];
              // if the buffer is in editing mode and we are over the insertion point, draw _
              if (v.Buffer.Editing &&
                  v.Buffer.InsertionPoint.X == (x - v.ScreenLocation.X) &&
                  v.Buffer.InsertionPoint.Y == (y - v.ScreenLocation.Y))
              {
                device.SetTexture(0, TexManager['_']);
              }
              else
              {
                device.SetTexture(0, TexManager[v[x - v.ScreenLocation.X, y - v.ScreenLocation.Y]]);
              }
              set = true;
            }
          }
          if (!set)
            device.SetTexture(0, TexManager[' ']);
          device.DrawPrimitives(PrimitiveType.TriangleFan, 0, 4);
          device.Transform.View *= Matrix.Translation(charSize.Width, 0.0f, 0.0f);
        }
        device.Transform.View *= Matrix.Translation(-charSize.Width*gridSize.Width,
                                                    -charSize.Height, 0.0f);
      }
    }

    private void SetupMatrices()
    {
      device.Transform.World = Matrix.Identity;
      device.Transform.View = Matrix.LookAtLH(
        new Vector3(0.0f, 3.0f*distanceToObject, -5.0f*distanceToObject),
        new Vector3(0.0f, 0.0f, 0.0f),
        new Vector3(0.0f, 1.0f, 0.0f));
      device.Transform.Projection = Matrix.OrthoRH(ClientSize.Width, ClientSize.Height, -100.0f, 100.0f);
    }

    #endregion

    #region Device Events

    private void OnLostDevice(object sender, EventArgs e)
    {
      // if the form is not visible, i'm ignoring this
      if (!Visible)
        return;

      try
      {
        device.Reset(pp);
      }
      catch (Exception ex)
      {
        MessageBox.Show(ex.Message);
      }
    }

    private void OnResetDevice(object sender, EventArgs e)
    {
      ResetDeviceStates();
    }

    private static void OnResizeDevice(object sender, CancelEventArgs e)
    {
      e.Cancel = true;
    }

    #endregion Device Events

    #endregion

    #region Static methods

    public static Console NewFullscreenConsole()
    {
      var c = new Console(true);
      var b = Buffer.NewBuffer(c, 10, 14);
      var v = new Viewport(b);
      c.viewports.Add(v);
      return c;
    }

    public static Console NewConsole(int width, int height)
    {
      return new Console(new Viewport(new Buffer(width, height)));
    }

    /// <summary>
    /// Returns true so long as any console in the array is created.
    /// </summary>
    /// <param name="consoles">An array of <c>Console</c> objects.</param>
    /// <returns>true if any console is created (i.e., is running), false otherwise.</returns>
    public static bool AnyConsoleCreated(Console[] consoles)
    {
      for (int i = 0; i < consoles.Length; i++)
      {
        if (consoles[i].Created)
          return true;
      }
      return false;
    }

    /// <summary>
    /// Renders each of the consoles in an array.
    /// </summary>
    /// <param name="consoles">An array of <c>Console</c> objects.</param>
    public static void RenderAll(Console[] consoles)
    {
      for (int i = 0; i < consoles.Length; i++)
      {
        consoles[i].Render();
      }
    }

    #endregion
  }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Founder ActiveMesa
United Kingdom United Kingdom
I work primarily with the .NET technology stack, and specialize in accelerated code production via code generation (static or dynamic), aspect-oriented programming, MDA, domain-specific languages and anything else that gets products out the door faster. My languages of choice are C# and C++, though I'm open to suggestions.

Comments and Discussions