using System; using System.Runtime.InteropServices; using System.Collections.Generic; using System.Drawing; using System.Drawing.Imaging; using System.IO; using System.Windows.Forms; using SlimDX; using SlimDX.Direct3D9; using Font = SlimDX.Direct3D9.Font; using BizHawk.Core; namespace BizHawk.MultiClient { public class ImageTexture : IDisposable { public Device GraphicsDevice; public Texture Texture; private int imageWidth; public int ImageWidth { get { return imageWidth; } } private int imageHeight; public int ImageHeight { get { return imageHeight; } } private int textureWidth; public int TextureWidth { get { return textureWidth; } } private int textureHeight; public int TextureHeight { get { return textureHeight; } } public ImageTexture(Device graphicsDevice) { GraphicsDevice = graphicsDevice; } public void SetImage(int[] data, int width, int height) { bool needsRecreating = false; if (Texture == null) { needsRecreating = true; } else { var currentTextureSize = Texture.GetLevelDescription(0); if (imageWidth != width || imageHeight != height) { needsRecreating = true; } } // If we need to recreate the texture, do so. if (needsRecreating) { if (Texture != null) { Texture.Dispose(); Texture = null; } // Copy the width/height to member fields. imageWidth = width; imageHeight = height; // Round up the width/height to the nearest power of two. textureWidth = 32; textureHeight = 32; while (textureWidth < imageWidth) textureWidth <<= 1; while (textureHeight < imageHeight) textureHeight <<= 1; // Create a new texture instance. Texture = new Texture(GraphicsDevice, textureWidth, textureHeight, 1, Usage.Dynamic, Format.X8R8G8B8, Pool.Default); } // Copy the image data to the texture. using (var Data = Texture.LockRectangle(0, new Rectangle(0, 0, imageWidth, imageHeight), LockFlags.None).Data) { if (imageWidth == textureWidth) { // Widths are the same, just dump the data across (easy!) Data.WriteRange(data, 0, imageWidth * imageHeight); } else { // Widths are different, need a bit of additional magic here to make them fit: long RowSeekOffset = 4 * (textureWidth - imageWidth); for (int r = 0, s = 0; r < imageHeight; ++r, s += imageWidth) { Data.WriteRange(data, s, imageWidth); Data.Seek(RowSeekOffset, SeekOrigin.Current); } } Texture.UnlockRectangle(0); } } private bool disposed; public void Dispose() { if (!disposed) { disposed = true; if (Texture != null) Texture.Dispose(); Texture = null; GC.SuppressFinalize(this); } } } public interface IRenderer : IDisposable { void Render(IVideoProvider video); bool Resized { get; set; } void AddMessage(string msg); } public class SysdrawingRenderPanel : IRenderer { public bool Resized { get; set; } public void Dispose() { } public void Render(IVideoProvider video) { Color BackgroundColor = Color.FromArgb(video.BackgroundColor); int[] data = video.GetVideoBuffer(); Bitmap bmp = new Bitmap(video.BufferWidth, video.BufferHeight, PixelFormat.Format32bppArgb); BitmapData bmpdata = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb); //TODO - this is not very intelligent. no handling of pitch, for instance Marshal.Copy(data, 0, bmpdata.Scan0, bmp.Width * bmp.Height); bmp.UnlockBits(bmpdata); backingControl.SetBitmap(bmp); } public SysdrawingRenderPanel(RetainedViewportPanel control) { backingControl = control; } RetainedViewportPanel backingControl; public void AddMessage(string msg) { } } public class Direct3DRenderPanel : IRenderer { public Color BackgroundColor { get; set; } public bool Resized { get; set; } private Direct3D d3d; private Device Device; private Control backingControl; public ImageTexture Texture; private Sprite Sprite; private Font MessageFont; public Direct3DRenderPanel(Direct3D direct3D, Control control) { d3d = direct3D; backingControl = control; control.DoubleClick += (o, e) => Global.MainForm.ToggleFullscreen(); } private void DestroyDevice() { if (Texture != null) { Texture.Dispose(); Texture = null; } if (Sprite != null) { Sprite.Dispose(); Sprite = null; } if (MessageFont != null) { MessageFont.Dispose(); MessageFont = null; } } public void CreateDevice() { DestroyDevice(); var pp = new PresentParameters { BackBufferWidth = Math.Max(1, backingControl.ClientSize.Width), BackBufferHeight = Math.Max(1, backingControl.ClientSize.Height), DeviceWindowHandle = backingControl.Handle, PresentationInterval = Global.Config.DisplayVSync?PresentInterval.One:PresentInterval.Immediate }; Device = new Device(d3d, 0, DeviceType.Hardware, backingControl.Handle, CreateFlags.HardwareVertexProcessing, pp); Sprite = new Sprite(Device); Texture = new ImageTexture(Device); MessageFont = new Font(Device, 16, 0, FontWeight.Bold, 1, false, CharacterSet.Default, Precision.Default, FontQuality.Default, PitchAndFamily.Default | PitchAndFamily.DontCare, "Arial"); } public void Render() { if (Device == null || Resized) CreateDevice(); Resized = false; Device.Clear(ClearFlags.Target, BackgroundColor, 1.0f, 0); Device.Present(Present.DoNotWait); } public void Render(IVideoProvider video) { if (video == null) { Render(); return; } if (Device == null || Resized) CreateDevice(); Resized = false; BackgroundColor = Color.FromArgb(video.BackgroundColor); int[] data = video.GetVideoBuffer(); Texture.SetImage(data, video.BufferWidth, video.BufferHeight); Device.Clear(ClearFlags.Target, BackgroundColor, 1.0f, 0); // figure out scaling factor float widthScale = (float) backingControl.Size.Width / video.BufferWidth; float heightScale = (float) backingControl.Size.Height / video.BufferHeight; float finalScale = Math.Min(widthScale, heightScale); Device.BeginScene(); Sprite.Begin(SpriteFlags.None); Device.SetSamplerState(0, SamplerState.MagFilter, TextureFilter.Point); Device.SetSamplerState(1, SamplerState.MagFilter, TextureFilter.Point); Sprite.Transform = Matrix.Scaling(finalScale, finalScale, 0f); Sprite.Draw(Texture.Texture, new Rectangle(0, 0, video.BufferWidth, video.BufferHeight), new Vector3(video.BufferWidth / 2f, video.BufferHeight / 2f, 0), new Vector3(backingControl.Size.Width / 2f / finalScale, backingControl.Size.Height / 2f / finalScale, 0), Color.White); Sprite.End(); DrawMessages(); Device.EndScene(); Device.Present(Present.DoNotWait); } /// /// Display all screen info objects like fps, frame counter, lag counter, and input display /// public void DrawScreenInfo() { //TODO: If movie loaded use that frame counter, and also display total movie frame count if read-only if (Global.Config.DisplayFrameCounter) MessageFont.DrawString(null, Global.Emulator.Frame.ToString(), 1, 1, new Color4(Color.White)); //TODO: Allow user to set screen coordinates? if (Global.Config.DisplayInput) { string input = ""; switch (Global.Emulator.SystemId) { case "SMS": input = MakeSMSInputDisplay(); break; case "PCE": input = MakePCEInputDisplay(); break; default: break; } MessageFont.DrawString(null, input, 1, 16, new Color4(Color.White)); } } private List messages = new List(5); public void AddMessage(string message) { messages.Add(new UIMessage {Message = message, ExpireAt = DateTime.Now + TimeSpan.FromSeconds(2)}); } private void DrawMessages() { messages.RemoveAll(m => DateTime.Now > m.ExpireAt); DrawScreenInfo(); int line = 1; for (int i=messages.Count - 1; i>=0; i--, line++) { int x = 3; int y = backingControl.Size.Height - (line*18); MessageFont.DrawString(null, messages[i].Message, x+2, y+2, new Color4(Color.Black)); MessageFont.DrawString(null, messages[i].Message, x, y, new Color4(Color.White)); } } private bool disposed; public void Dispose() { if (disposed == false) { disposed = true; DestroyDevice(); } } public string MakeSMSInputDisplay() { string input = ""; if (Global.Emulator.Controller.IsPressed("P1 Up")) input += "U"; else input += " "; if (Global.Emulator.Controller.IsPressed("P1 Down")) input += "D"; else input += " "; if (Global.Emulator.Controller.IsPressed("P1 Left")) input += "L"; else input += " "; if (Global.Emulator.Controller.IsPressed("P1 Right")) input += "R"; else input += " "; if (Global.Emulator.Controller.IsPressed("P1 B1")) input += "1"; else input += " "; if (Global.Emulator.Controller.IsPressed("P1 B2")) input += "2"; else input += " "; if (Global.Emulator.Controller.IsPressed("P2 Up")) input += "U"; else input += " "; if (Global.Emulator.Controller.IsPressed("P2 Down")) input += "D"; else input += " "; if (Global.Emulator.Controller.IsPressed("P2 Left")) input += "L"; else input += " "; if (Global.Emulator.Controller.IsPressed("P2 Right")) input += "R"; else input += " "; if (Global.Emulator.Controller.IsPressed("P2 B1")) input += "1"; else input += " "; if (Global.Emulator.Controller.IsPressed("P2 B2")) input += "2"; else input += " "; if (Global.Emulator.Controller.IsPressed("Pause")) input += "S"; else input += " "; if (Global.Emulator.Controller.IsPressed("Reset")) input += "R"; else input += " "; return input; } public string MakePCEInputDisplay() { string input = ""; if (Global.Emulator.Controller.IsPressed("Up")) input += "U"; else input += " "; if (Global.Emulator.Controller.IsPressed("Down")) input += "D"; else input += " "; if (Global.Emulator.Controller.IsPressed("Left")) input += "L"; else input += " "; if (Global.Emulator.Controller.IsPressed("Right")) input += "R"; else input += " "; if (Global.Emulator.Controller.IsPressed("I")) input += "1"; else input += " "; if (Global.Emulator.Controller.IsPressed("II")) input += "2"; else input += " "; if (Global.Emulator.Controller.IsPressed("Select")) input += "S"; else input += " "; if (Global.Emulator.Controller.IsPressed("Run")) input += "R"; else input += " "; return input; } } class UIMessage { public string Message; public DateTime ExpireAt; } }