I had to head home early from work today due to what feels like heat exhaustion. As I was resting, I had mad fever dreams about how best to fix the flickering from UltraMon.
After some heavy research, It seems to be a symptom of DirectX - after all, I'm trying to capture hardware overlays. I decided that I'm using a lot of system resource, and an imperfect solution to resolve this issue. In response, I've developed my own little solution.
I've posted the Source Code below. Though the interval on the timer should be 50, not 25. It seems quite simple, but it's the result of a few hours of research and coding. I began with BitBlt calls from GDI, but ultimately settled on Graphics.CopyFromScreen() (basically the same thing, but a lot easier to code).
I added in some command-line parameters to override the hard-coded screen coordinates if needed.
Finally, the application presented the same problems that Ultramon did, in that the display flickered every few frames. As a workaround, I recorded the pixel values of random locations for every frame. This allowed me to identify which frames were blank. As I'm now using my own code, it was simple to ignore these blank frames.
The end result is a constant output with far less flickering, and far less impact on system performance. The frame rate is sporadic, only updating approx 2/3rds of the output desired, and there is no hardware overlay. However, the dimensions of the output are now almost perfect, and it's a lot better than a constantly flickering device. As a nice bonus, I can now "activate" the display by launching a single application - currently it's in the Windows Startup folder!
I've been looking into Direct X Hooks to capture the true 3d output. This would be the best result, and tests with SlimDX on my desktop were very promising, however there seems to be some comparability issues on the gaming rig, so for now, this will have to do.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Drawing; | |
using System.Drawing.Imaging; | |
using System.Linq; | |
using System.Windows.Forms; | |
namespace TargetComputer | |
{ | |
public partial class FrmMain : Form | |
{ | |
//Prep Some Settings | |
private static int _captureX = 363; //X-Position (Start) of area to grab | |
private static int _captureY = 611; //Y-Position (start) of area to grab | |
private static int _captureWidth = 300; //Widht of area to grab | |
private static int _captureHeight = 156; //Height of area to grab | |
//Misc | |
private readonly Bitmap _capturedImage; | |
private readonly Graphics _g; | |
public FrmMain() | |
{ | |
//Prep | |
InitializeComponent(); | |
_capturedImage = new Bitmap(_captureWidth, _captureHeight, PixelFormat.Format32bppRgb); | |
_g = Graphics.FromImage(_capturedImage); | |
var myScreen = Screen.FromControl(this); //Identify the Start Up (Main) monitor | |
var otherScreen = Screen.AllScreens.FirstOrDefault(s => !Equals(s, myScreen)) ?? myScreen; //Locate the second monitor, if present (This should be the target output display) | |
//Position the form on the other (second) monitor, and maximize. The picture box should handle scaling | |
Left = otherScreen.WorkingArea.Left; | |
Top = otherScreen.WorkingArea.Top; | |
FormBorderStyle = FormBorderStyle.None; | |
WindowState = FormWindowState.Maximized; | |
Output.AutoSize = false; | |
} | |
/// <summary> | |
/// Load Settings from command line args, this allows customization of screen grab area | |
/// </summary> | |
public FrmMain(string[] args) :this() | |
{ | |
if (args.Any()) _captureX = Convert.ToInt16(args[0]); | |
if (args.Count() > 1) _captureY = Convert.ToInt16(args[1]); | |
if (args.Count() > 2) _captureWidth = Convert.ToInt16(args[2]); | |
if (args.Count() > 3) _captureHeight = Convert.ToInt16(args[3]); | |
} | |
private void FrameDraw_Tick(object sender, EventArgs e) | |
{ | |
//Grab the Outut from the main screen, and paste it to the picture box | |
_g.CopyFromScreen(_captureX, _captureY, 0, 0, new Size(_captureWidth, _captureHeight), CopyPixelOperation.SourceCopy); | |
//Check if the Frame was captured or not. Testing so far suggests that a failed frame capture returns [A=255, R=0, G=0, B=8] on every pixel captured. | |
//This may be DX overlay? Not sure, but anyway, lets ignore those frames. It will slow the updates down, but should look a lot better. | |
var testColor = _capturedImage.GetPixel(50, 50); | |
if (testColor != Color.FromArgb(255, 0, 0, 8)) Output.Image = _capturedImage; | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
namespace TargetComputer | |
{ | |
partial class FrmMain | |
{ | |
/// <summary> | |
/// Required designer variable. | |
/// </summary> | |
private System.ComponentModel.IContainer components = null; | |
/// <summary> | |
/// Clean up any resources being used. | |
/// </summary> | |
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param> | |
protected override void Dispose(bool disposing) | |
{ | |
if (disposing && (components != null)) | |
{ | |
components.Dispose(); | |
} | |
base.Dispose(disposing); | |
} | |
#region Windows Form Designer generated code | |
/// <summary> | |
/// Required method for Designer support - do not modify | |
/// the contents of this method with the code editor. | |
/// </summary> | |
private void InitializeComponent() | |
{ | |
this.components = new System.ComponentModel.Container(); | |
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(FrmMain)); | |
this.Output = new System.Windows.Forms.PictureBox(); | |
this.FrameDraw = new System.Windows.Forms.Timer(this.components); | |
((System.ComponentModel.ISupportInitialize)(this.Output)).BeginInit(); | |
this.SuspendLayout(); | |
// | |
// Output | |
// | |
this.Output.Dock = System.Windows.Forms.DockStyle.Fill; | |
this.Output.Location = new System.Drawing.Point(0, 0); | |
this.Output.Name = "Output"; | |
this.Output.Size = new System.Drawing.Size(284, 261); | |
this.Output.SizeMode = System.Windows.Forms.PictureBoxSizeMode.StretchImage; | |
this.Output.TabIndex = 0; | |
this.Output.TabStop = false; | |
// | |
// FrameDraw | |
// | |
this.FrameDraw.Enabled = true; | |
this.FrameDraw.Interval = 25; | |
this.FrameDraw.Tick += new System.EventHandler(this.FrameDraw_Tick); | |
// | |
// FrmMain | |
// | |
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); | |
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; | |
this.ClientSize = new System.Drawing.Size(284, 261); | |
this.Controls.Add(this.Output); | |
this.DoubleBuffered = true; | |
this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); | |
this.Name = "FrmMain"; | |
this.Text = "TargetComputer"; | |
((System.ComponentModel.ISupportInitialize)(this.Output)).EndInit(); | |
this.ResumeLayout(false); | |
} | |
#endregion | |
private System.Windows.Forms.PictureBox Output; | |
private System.Windows.Forms.Timer FrameDraw; | |
} | |
} | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Windows.Forms; | |
namespace TargetComputer | |
{ | |
static class Program | |
{ | |
/// <summary> | |
/// The main entry point for the application. | |
/// </summary> | |
[STAThread] | |
static void Main(string[] args) | |
{ | |
Application.EnableVisualStyles(); | |
Application.SetCompatibleTextRenderingDefault(false); | |
Application.Run(new FrmMain(args)); | |
} | |
} | |
} |