BizHawk/BizHawk.MultiClient/RenderPanel.cs

341 lines
8.9 KiB
C#

using System;
using System.Runtime.InteropServices;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Threading;
using System.Text;
using System.Windows.Forms;
#if WINDOWS
using SlimDX;
using SlimDX.Direct3D9;
using Font = SlimDX.Direct3D9.Font;
#endif
using BizHawk.Core;
namespace BizHawk.MultiClient
{
#if WINDOWS
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 unsafe void SetImage(DisplaySurface surface, int width, int height)
{
//this function fails if the width and height are zero
if (width == 0 || height == 0) return;
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.
surface.Lock();
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(surface.PixelIntPtr, imageWidth * imageHeight * 4);
}
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)
{
IntPtr src = new IntPtr(((byte*)surface.PixelPtr + r*surface.Stride));
Data.WriteRange(src,imageWidth * 4);
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);
}
}
}
#endif
public interface IRenderer : IDisposable
{
void Render(DisplaySurface surface);
bool Resized { get; set; }
Size NativeSize { get; }
}
public class SysdrawingRenderPanel : IRenderer
{
public bool Resized { get; set; }
public void Dispose() { }
public void Render(DisplaySurface surface)
{
//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);
Bitmap bmp = (Bitmap)surface.PeekBitmap().Clone();
backingControl.SetBitmap(bmp);
}
public SysdrawingRenderPanel(RetainedViewportPanel control)
{
backingControl = control;
}
RetainedViewportPanel backingControl;
public Size NativeSize { get { return backingControl.ClientSize; } }
}
#if WINDOWS
public class Direct3DRenderPanel : IRenderer
{
public Color BackgroundColor { get; set; }
public bool Resized { get; set; }
public string FPS { get; set; }
public string MT { get; set; }
private Direct3D d3d;
private Device Device;
private Control backingControl;
public ImageTexture Texture;
private Sprite Sprite;
private bool Vsync;
public Size NativeSize { get { return backingControl.ClientSize; } }
public Direct3DRenderPanel(Direct3D direct3D, Control control)
{
d3d = direct3D;
backingControl = control;
control.MouseDoubleClick += (o, e) => HandleFullscreenToggle(o, e);
control.MouseClick += (o, e) => Global.MainForm.MainForm_MouseClick(o, e);
}
private void HandleFullscreenToggle(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
Global.MainForm.ToggleFullscreen();
}
private void DestroyDevice()
{
if (Texture != null)
{
Texture.Dispose();
Texture = null;
}
if (Sprite != null)
{
Sprite.Dispose();
Sprite = null;
}
if (Device != null)
{
Device.Dispose();
Device = null;
}
}
private bool VsyncRequested
{
get
{
if (Global.ForceNoVsync) return false;
return Global.Config.DisplayVSync;
}
}
public void CreateDevice()
{
DestroyDevice();
Vsync = VsyncRequested;
var pp = new PresentParameters
{
BackBufferWidth = Math.Max(1, backingControl.ClientSize.Width),
BackBufferHeight = Math.Max(1, backingControl.ClientSize.Height),
DeviceWindowHandle = backingControl.Handle,
PresentationInterval = Vsync ? PresentInterval.One : PresentInterval.Immediate
};
var flags = CreateFlags.SoftwareVertexProcessing;
if ((d3d.GetDeviceCaps(0, DeviceType.Hardware).DeviceCaps & DeviceCaps.HWTransformAndLight) != 0)
{
flags = CreateFlags.HardwareVertexProcessing;
}
Device = new Device(d3d, 0, DeviceType.Hardware, backingControl.Handle, flags, pp);
Sprite = new Sprite(Device);
Texture = new ImageTexture(Device);
}
public void Render()
{
if (Device == null || Resized || Vsync != VsyncRequested)
backingControl.Invoke(() => CreateDevice());
Resized = false;
Device.Clear(ClearFlags.Target, BackgroundColor, 1.0f, 0);
Device.Present(Present.DoNotWait);
}
public void Render(DisplaySurface surface)
{
try
{
RenderExec(surface);
}
catch (Direct3D9Exception)
{
// Wait until device is available or user gets annoyed and closes app
Result r;
do
{
r = Device.TestCooperativeLevel();
Thread.Sleep(100);
} while (r == ResultCode.DeviceLost);
// lets try recovery!
DestroyDevice();
backingControl.Invoke(() => CreateDevice());
RenderExec(surface);
}
}
private void RenderExec(DisplaySurface surface)
{
if (surface == null)
{
Render();
return;
}
if (Device == null || Resized || Vsync != VsyncRequested)
backingControl.Invoke(() => CreateDevice());
Resized = false;
//TODO
//BackgroundColor = Color.FromArgb(video.BackgroundColor);
Texture.SetImage(surface, surface.Width, surface.Height);
Device.Clear(ClearFlags.Target, BackgroundColor, 1.0f, 0);
// figure out scaling factor
float widthScale = (float)backingControl.Size.Width / surface.Width;
float heightScale = (float)backingControl.Size.Height / surface.Height;
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, surface.Width, surface.Height), new Vector3(surface.Width / 2f, surface.Height / 2f, 0), new Vector3(backingControl.Size.Width / 2f / finalScale, backingControl.Size.Height / 2f / finalScale, 0), Color.White);
Sprite.End();
Device.EndScene();
Device.Present(Present.DoNotWait);
}
private bool disposed;
public void Dispose()
{
if (disposed == false)
{
disposed = true;
DestroyDevice();
}
}
}
#endif
class UIMessage
{
public string Message;
public DateTime ExpireAt;
}
class UIDisplay
{
public string Message;
public DateTime ExpireAt;
public int X;
public int Y;
public bool Alert;
public int Anchor;
public Brush ForeColor;
public Brush BackGround;
}
}