369 lines
13 KiB
C#
369 lines
13 KiB
C#
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Display all screen info objects like fps, frame counter, lag counter, and input display
|
|
/// </summary>
|
|
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<UIMessage> messages = new List<UIMessage>(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;
|
|
}
|
|
} |