882 lines
21 KiB
C#
882 lines
21 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.Drawing;
|
|
using System.Drawing.Imaging;
|
|
using System.IO;
|
|
using System.Runtime.InteropServices;
|
|
using System.Threading;
|
|
using MyNes.Core;
|
|
using SDL2;
|
|
|
|
namespace MyNes;
|
|
|
|
internal class SDL2VideoRenderer : IVideoProvider
|
|
{
|
|
private Timer timer;
|
|
|
|
private bool initialized;
|
|
|
|
private bool keep_aspect_ratio;
|
|
|
|
private int current_window_width;
|
|
|
|
private int current_window_height;
|
|
|
|
private int RENDER_WIDTH;
|
|
|
|
private bool isRendering;
|
|
|
|
private bool LockPixels;
|
|
|
|
private bool hide_lines;
|
|
|
|
private int lines_to_hide;
|
|
|
|
private bool show_fps;
|
|
|
|
private bool signal;
|
|
|
|
private IntPtr window;
|
|
|
|
private IntPtr renderer;
|
|
|
|
private SDL.SDL_Rect targetPixelsRect;
|
|
|
|
private SDL.SDL_Rect renderPixelsRect;
|
|
|
|
private IntPtr text_normal_Font;
|
|
|
|
private string fontFile = "";
|
|
|
|
private IntPtr pixels_texture;
|
|
|
|
private IntPtr buffer_pixels;
|
|
|
|
private int[] buffer_pixels_tmp;
|
|
|
|
private int buffer_size;
|
|
|
|
private int int_size;
|
|
|
|
private TextObject fps_text_object;
|
|
|
|
private string fps_text;
|
|
|
|
private double fps_avg;
|
|
|
|
private int fps_count;
|
|
|
|
private double fps_emu_imm_av;
|
|
|
|
private double fps_emu_fr_av;
|
|
|
|
private double fps_emu_clocks;
|
|
|
|
private TextObject not_text_object;
|
|
|
|
private List<string> not_texts;
|
|
|
|
private List<Color> not_colors;
|
|
|
|
private bool not_show;
|
|
|
|
private int not_counter;
|
|
|
|
private TextObject not_up_text_object;
|
|
|
|
private TextObject not_turbo_text_object;
|
|
|
|
private double fps_time_last;
|
|
|
|
private double fps_time_start;
|
|
|
|
private double fps_time_token;
|
|
|
|
private double fps_time_dead;
|
|
|
|
private double fps_time_period;
|
|
|
|
private double fps_time_frame_time;
|
|
|
|
internal bool threadON;
|
|
|
|
internal bool threadPAUSED;
|
|
|
|
private Thread threadMain;
|
|
|
|
private Random r = new Random();
|
|
|
|
private byte c;
|
|
|
|
private bool take_snap_request;
|
|
|
|
private bool take_snap_as_request;
|
|
|
|
private string take_snap_as_file;
|
|
|
|
private string take_snap_as_format;
|
|
|
|
private SDL2Settings sdl_settings;
|
|
|
|
public string Name => "SDL2 Video Renderer";
|
|
|
|
public string ID => "sdl2.video";
|
|
|
|
public SDL2VideoRenderer()
|
|
{
|
|
timer = new Timer(FPSTimerCallback, null, 0, 1000);
|
|
}
|
|
|
|
private void LoadSettings()
|
|
{
|
|
hide_lines = MyNesMain.RendererSettings.Vid_HideLines;
|
|
keep_aspect_ratio = MyNesMain.RendererSettings.Vid_KeepAspectRatio;
|
|
show_fps = MyNesMain.RendererSettings.Vid_ShowFPS;
|
|
fps_text = " ";
|
|
ApplyRegionChanges();
|
|
}
|
|
|
|
public void Execute(string command)
|
|
{
|
|
switch (command)
|
|
{
|
|
case "resume":
|
|
Tracer.WriteLine("SlimDX Direct3D: Excuting command '" + command + "'");
|
|
threadPAUSED = false;
|
|
break;
|
|
case "pause":
|
|
Tracer.WriteLine("SlimDX Direct3D: Excuting command '" + command + "'");
|
|
threadPAUSED = true;
|
|
break;
|
|
case "shutdown":
|
|
Tracer.WriteLine("SlimDX Direct3D: Excuting command '" + command + "'");
|
|
ShutDown();
|
|
break;
|
|
}
|
|
}
|
|
|
|
public void Initialize()
|
|
{
|
|
sdl_settings = new SDL2Settings(Path.Combine(Program.WorkingFolder, "sdlsettings.ini"));
|
|
sdl_settings.LoadSettings();
|
|
LoadSettings();
|
|
not_texts = new List<string>();
|
|
not_colors = new List<Color>();
|
|
SDL.SDL_SetHint("SDL_RENDER_DRIVER", sdl_settings.Video_Driver);
|
|
Tracer.WriteLine("SDL: video driver set to '" + sdl_settings.Video_Driver + "'");
|
|
SDL.SDL_SetHint("SDL_RENDER_OPENGL_SHADERS", sdl_settings.Video_EnableOpenglShaders.ToString());
|
|
Tracer.WriteLine("SDL: video enable opengl shaders set to '" + sdl_settings.Video_EnableOpenglShaders + "'");
|
|
Tracer.WriteLine("SDL: Initializing video ...");
|
|
SDL.SDL_Init(32u);
|
|
SDL.SDL_SetHint("SDL_RENDER_SCALE_QUALITY", MyNesMain.RendererSettings.Vid_Filter.ToString());
|
|
Tracer.WriteLine("SDL: video render scale quality set to '" + MyNesMain.RendererSettings.Vid_Filter + "'");
|
|
Tracer.WriteLine("SDL: creating window...");
|
|
window = SDL.SDL_CreateWindowFrom(Program.FormMain.panel_surface.Handle);
|
|
Tracer.WriteLine("SDL: window created successfully.");
|
|
Tracer.WriteLine("SDL: creating renderer for video ...");
|
|
SDL.SDL_RendererFlags sDL_RendererFlags = SDL.SDL_RendererFlags.SDL_RENDERER_TARGETTEXTURE;
|
|
if (MyNesMain.RendererSettings.Vid_VSync && !MyNesMain.RendererSettings.FrameSkipEnabled && NesEmu.Region == EmuRegion.NTSC)
|
|
{
|
|
sDL_RendererFlags |= SDL.SDL_RendererFlags.SDL_RENDERER_PRESENTVSYNC;
|
|
Tracer.WriteLine("SDL: VSYNC ENABLED");
|
|
}
|
|
if (sdl_settings.Video_Accelerated)
|
|
{
|
|
sDL_RendererFlags |= SDL.SDL_RendererFlags.SDL_RENDERER_ACCELERATED;
|
|
Tracer.WriteLine("SDL: ACCELERATION ENABLED");
|
|
}
|
|
if (sdl_settings.Video_Software)
|
|
{
|
|
sDL_RendererFlags |= SDL.SDL_RendererFlags.SDL_RENDERER_SOFTWARE;
|
|
Tracer.WriteLine("SDL: SOFTWARE RENDER ENABLED");
|
|
}
|
|
renderer = SDL.SDL_CreateRenderer(window, -1, sDL_RendererFlags);
|
|
Console.WriteLine("SDL: renderer is ready !");
|
|
Console.WriteLine("SDL: creating textures and buffers...");
|
|
pixels_texture = SDL.SDL_CreateTexture(renderer, SDL.SDL_PIXELFORMAT_ARGB8888, 0, 256, 240);
|
|
buffer_pixels_tmp = new int[buffer_size = 61440];
|
|
int_size = Marshal.SizeOf((object)buffer_pixels_tmp[0]) * buffer_pixels_tmp.Length;
|
|
buffer_pixels = Marshal.AllocHGlobal(int_size);
|
|
LockPixels = false;
|
|
CalculateTargetRect();
|
|
Console.WriteLine("SDL: done !!");
|
|
if (!File.Exists(fontFile))
|
|
{
|
|
fontFile = Path.Combine(MyNesMain.AppPath, "FreeSans.ttf");
|
|
}
|
|
Tracer.WriteLine("SDL: ->Initializing fonts ...");
|
|
SDL_ttf.TTF_Init();
|
|
Tracer.WriteLine("SDL: ->FONT FILE = " + fontFile);
|
|
text_normal_Font = SDL_ttf.TTF_OpenFont(fontFile, 16);
|
|
if (text_normal_Font == IntPtr.Zero)
|
|
{
|
|
Tracer.WriteLine("SDL: ->ERORR !! NORMAL FONT DOESN'T LOADED !");
|
|
}
|
|
fps_text_object = new TextObject(window, renderer, " ", text_normal_Font, Color.Lime, new Point(10, 10), blended: false);
|
|
not_text_object = new TextObject(window, renderer, " ", text_normal_Font, Color.Lime, new Point(10, targetPixelsRect.h - 50), blended: false);
|
|
not_up_text_object = new TextObject(window, renderer, " ", text_normal_Font, Color.Red, new Point(targetPixelsRect.w - 220, 10), blended: false);
|
|
not_turbo_text_object = new TextObject(window, renderer, " ", text_normal_Font, Color.Yellow, new Point(targetPixelsRect.w - 100, targetPixelsRect.h - 50), blended: false);
|
|
double num = 0.0;
|
|
switch (NesEmu.Region)
|
|
{
|
|
case EmuRegion.NTSC:
|
|
num = 60.0988;
|
|
break;
|
|
case EmuRegion.PALB:
|
|
case EmuRegion.DENDY:
|
|
num = 50.0;
|
|
break;
|
|
}
|
|
if (!MyNesMain.RendererSettings.FrameSkipEnabled)
|
|
{
|
|
fps_time_period = 1.0 / num;
|
|
}
|
|
else
|
|
{
|
|
fps_time_period = 1.0 / (num / (double)MyNesMain.RendererSettings.FrameSkipInterval);
|
|
}
|
|
threadON = true;
|
|
threadPAUSED = false;
|
|
initialized = true;
|
|
Console.WriteLine("SDL: video initialized successfully.");
|
|
threadMain = new Thread(ClockThread);
|
|
threadMain.Start();
|
|
}
|
|
|
|
internal void ClockThread()
|
|
{
|
|
while (threadON)
|
|
{
|
|
if (!threadPAUSED)
|
|
{
|
|
RunThreaded();
|
|
}
|
|
}
|
|
}
|
|
|
|
internal void CloseThread()
|
|
{
|
|
threadON = false;
|
|
}
|
|
|
|
private void RunThreaded()
|
|
{
|
|
if (!initialized)
|
|
{
|
|
return;
|
|
}
|
|
fps_time_start = GetTime();
|
|
isRendering = true;
|
|
if (take_snap_request)
|
|
{
|
|
take_snap_request = false;
|
|
TakeSnapThreaded();
|
|
}
|
|
if (take_snap_as_request)
|
|
{
|
|
take_snap_as_request = false;
|
|
TakeSnapshotAsThreaded();
|
|
}
|
|
RenderBackground();
|
|
if (not_show)
|
|
{
|
|
if (not_counter > 0)
|
|
{
|
|
not_counter--;
|
|
if (not_texts.Count > 0)
|
|
{
|
|
not_text_object.Color = not_colors[0];
|
|
not_text_object.SetText(not_texts[0], ref window, ref renderer);
|
|
not_text_object.OnRender(ref window, ref renderer);
|
|
}
|
|
}
|
|
else if (not_texts.Count > 0)
|
|
{
|
|
not_texts.RemoveAt(0);
|
|
not_colors.RemoveAt(0);
|
|
if (not_texts.Count > 0)
|
|
{
|
|
not_counter = 120;
|
|
not_show = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
not_show = false;
|
|
}
|
|
}
|
|
if (show_fps)
|
|
{
|
|
fps_text_object.OnRender(ref window, ref renderer);
|
|
}
|
|
if (MyNesMain.WaveRecorder.IsRecording)
|
|
{
|
|
not_up_text_object.SetText("Recording Audio " + TimeSpan.FromSeconds(MyNesMain.WaveRecorder.Time).ToString(), ref window, ref renderer);
|
|
not_up_text_object.OnRender(ref window, ref renderer);
|
|
}
|
|
if (NesEmu.ON)
|
|
{
|
|
if (NesEmu.PAUSED)
|
|
{
|
|
not_turbo_text_object.SetText("PAUSED !", ref window, ref renderer);
|
|
not_turbo_text_object.OnRender(ref window, ref renderer);
|
|
}
|
|
else if (!NesEmu.FrameLimiterEnabled)
|
|
{
|
|
not_turbo_text_object.SetText("TURBO !", ref window, ref renderer);
|
|
not_turbo_text_object.OnRender(ref window, ref renderer);
|
|
}
|
|
}
|
|
SDL.SDL_RenderPresent(renderer);
|
|
isRendering = false;
|
|
CheckEvents();
|
|
fps_time_token = GetTime() - fps_time_start;
|
|
if (fps_time_token > 0.0)
|
|
{
|
|
fps_time_dead = fps_time_period - fps_time_token;
|
|
if (fps_time_dead > 0.0)
|
|
{
|
|
Thread.Sleep((int)Math.Floor(fps_time_dead * 1000.0));
|
|
fps_time_dead = GetTime() - fps_time_start;
|
|
while (fps_time_period - fps_time_dead > 0.0)
|
|
{
|
|
fps_time_dead = GetTime() - fps_time_start;
|
|
}
|
|
}
|
|
}
|
|
fps_time_last = GetTime();
|
|
fps_time_frame_time = fps_time_last - fps_time_start;
|
|
fps_avg += 1.0 / fps_time_frame_time;
|
|
fps_count++;
|
|
}
|
|
|
|
private void RenderBackground()
|
|
{
|
|
if (!signal)
|
|
{
|
|
for (int i = 0; i < buffer_size; i++)
|
|
{
|
|
c = (byte)r.Next(0, 255);
|
|
buffer_pixels_tmp[i] = c | (c << 8) | (c << 16) | -16777216;
|
|
}
|
|
Marshal.Copy(buffer_pixels_tmp, 0, buffer_pixels, buffer_size);
|
|
}
|
|
SDL.SDL_RenderClear(renderer);
|
|
if (!LockPixels)
|
|
{
|
|
SDL.SDL_UpdateTexture(pixels_texture, ref renderPixelsRect, buffer_pixels, RENDER_WIDTH * 4);
|
|
SDL.SDL_RenderCopy(renderer, pixels_texture, ref renderPixelsRect, ref targetPixelsRect);
|
|
}
|
|
}
|
|
|
|
private void CheckEvents()
|
|
{
|
|
SDL.SDL_PumpEvents();
|
|
if (SDL.SDL_HasEvent(SDL.SDL_EventType.SDL_QUIT) == SDL.SDL_bool.SDL_TRUE)
|
|
{
|
|
threadON = false;
|
|
}
|
|
}
|
|
|
|
private void HandleWindowResize()
|
|
{
|
|
int w = 0;
|
|
int h = 0;
|
|
SDL.SDL_GetWindowSize(window, out w, out h);
|
|
if (current_window_width != w || current_window_height != h)
|
|
{
|
|
CalculateTargetRect();
|
|
}
|
|
}
|
|
|
|
internal void CalculateTargetRect()
|
|
{
|
|
int w = 0;
|
|
int h = 0;
|
|
SDL.SDL_GetWindowSize(window, out w, out h);
|
|
targetPixelsRect = default(SDL.SDL_Rect);
|
|
if (keep_aspect_ratio)
|
|
{
|
|
GetRatioStretchRectangle(renderPixelsRect.w, renderPixelsRect.h, w, h, ref targetPixelsRect.x, ref targetPixelsRect.y, ref targetPixelsRect.w, ref targetPixelsRect.h);
|
|
}
|
|
else
|
|
{
|
|
targetPixelsRect.x = 0;
|
|
targetPixelsRect.y = 0;
|
|
targetPixelsRect.h = h;
|
|
targetPixelsRect.w = w;
|
|
}
|
|
current_window_width = w;
|
|
current_window_height = h;
|
|
if (not_text_object != null)
|
|
{
|
|
not_text_object.Position = new Point(10, h - 50);
|
|
}
|
|
if (not_up_text_object != null)
|
|
{
|
|
not_up_text_object.Position = new Point(w - 220, 10);
|
|
}
|
|
if (not_turbo_text_object != null)
|
|
{
|
|
not_turbo_text_object.Position = new Point(w - 100, h - 50);
|
|
}
|
|
Tracer.WriteLine("Resize, w=" + w + ", h=" + h);
|
|
}
|
|
|
|
private void GetRatioStretchRectangle(int orgWidth, int orgHeight, int maxWidth, int maxHeight, ref int out_x, ref int out_y, ref int out_w, ref int out_h)
|
|
{
|
|
float num = orgHeight / maxHeight;
|
|
float num2 = orgWidth / maxWidth;
|
|
bool flag = false;
|
|
if ((num2 > num) ^ flag)
|
|
{
|
|
out_w = maxWidth;
|
|
out_h = orgHeight * maxWidth / orgWidth;
|
|
}
|
|
else
|
|
{
|
|
out_h = maxHeight;
|
|
out_w = orgWidth * maxHeight / orgHeight;
|
|
}
|
|
out_x = maxWidth / 2 - out_w / 2;
|
|
out_y = 0;
|
|
}
|
|
|
|
private void FPSTimerCallback(object state)
|
|
{
|
|
if (!initialized)
|
|
{
|
|
return;
|
|
}
|
|
double num = 0.0;
|
|
if (fps_count > 0)
|
|
{
|
|
num = fps_avg / (double)fps_count;
|
|
}
|
|
fps_avg = (fps_count = 0);
|
|
if (NesEmu.ON)
|
|
{
|
|
double num2 = 0.0;
|
|
double num3 = 0.0;
|
|
if (fps_emu_clocks > 0.0)
|
|
{
|
|
num2 = 1.0 / (fps_emu_imm_av / fps_emu_clocks);
|
|
num3 = 1.0 / (fps_emu_fr_av / fps_emu_clocks);
|
|
}
|
|
fps_text = $"Video FPS: {num:F2} / Emu Clocked: {fps_emu_clocks} | Emu FPS: {num2:F2} | Emu Can Make FPS: {num3:F2}";
|
|
fps_emu_imm_av = (fps_emu_fr_av = (fps_emu_clocks = 0.0));
|
|
}
|
|
else
|
|
{
|
|
fps_text = $"Video FPS: {num:F2}";
|
|
}
|
|
fps_text_object.SetText(fps_text, ref window, ref renderer);
|
|
}
|
|
|
|
public void ResizeBegin()
|
|
{
|
|
threadPAUSED = true;
|
|
}
|
|
|
|
public void ResizeEnd()
|
|
{
|
|
sdl_settings.LoadSettings();
|
|
Thread.Sleep(50);
|
|
initialized = false;
|
|
fps_text_object.Destroy();
|
|
not_text_object.Destroy();
|
|
not_turbo_text_object.Destroy();
|
|
not_up_text_object.Destroy();
|
|
SDL.SDL_DestroyRenderer(renderer);
|
|
SDL.SDL_DestroyTexture(pixels_texture);
|
|
Tracer.WriteLine("SDL: creating renderer for video ...");
|
|
SDL.SDL_RendererFlags sDL_RendererFlags = SDL.SDL_RendererFlags.SDL_RENDERER_TARGETTEXTURE;
|
|
if (MyNesMain.RendererSettings.Vid_VSync && !MyNesMain.RendererSettings.FrameSkipEnabled && NesEmu.Region == EmuRegion.NTSC)
|
|
{
|
|
sDL_RendererFlags |= SDL.SDL_RendererFlags.SDL_RENDERER_PRESENTVSYNC;
|
|
Tracer.WriteLine("SDL: VSYNC ENABLED");
|
|
}
|
|
if (sdl_settings.Video_Accelerated)
|
|
{
|
|
sDL_RendererFlags |= SDL.SDL_RendererFlags.SDL_RENDERER_ACCELERATED;
|
|
Tracer.WriteLine("SDL: ACCELERATION ENABLED");
|
|
}
|
|
if (sdl_settings.Video_Software)
|
|
{
|
|
sDL_RendererFlags |= SDL.SDL_RendererFlags.SDL_RENDERER_SOFTWARE;
|
|
Tracer.WriteLine("SDL: SOFTWARE RENDER ENABLED");
|
|
}
|
|
double num = 0.0;
|
|
switch (NesEmu.Region)
|
|
{
|
|
case EmuRegion.NTSC:
|
|
num = 60.0;
|
|
break;
|
|
case EmuRegion.PALB:
|
|
case EmuRegion.DENDY:
|
|
num = 50.0;
|
|
break;
|
|
}
|
|
if (!MyNesMain.RendererSettings.FrameSkipEnabled)
|
|
{
|
|
fps_time_period = 1.0 / num;
|
|
}
|
|
else
|
|
{
|
|
fps_time_period = 1.0 / (num / (double)MyNesMain.RendererSettings.FrameSkipInterval);
|
|
}
|
|
renderer = SDL.SDL_CreateRenderer(window, -1, sDL_RendererFlags);
|
|
Console.WriteLine("SDL: creating texture..");
|
|
pixels_texture = SDL.SDL_CreateTexture(renderer, SDL.SDL_PIXELFORMAT_ARGB8888, 0, 256, 240);
|
|
CalculateTargetRect();
|
|
initialized = true;
|
|
threadPAUSED = false;
|
|
}
|
|
|
|
public void ShutDown()
|
|
{
|
|
threadON = false;
|
|
Thread.Sleep(100);
|
|
threadMain.Abort();
|
|
SDL.SDL_DestroyWindow(window);
|
|
SDL.SDL_DestroyRenderer(renderer);
|
|
SDL.SDL_DestroyTexture(pixels_texture);
|
|
sdl_settings.SaveSettings();
|
|
}
|
|
|
|
public void SignalToggle(bool started)
|
|
{
|
|
signal = started;
|
|
double num = 0.0;
|
|
switch (NesEmu.Region)
|
|
{
|
|
case EmuRegion.NTSC:
|
|
num = 60.0988;
|
|
break;
|
|
case EmuRegion.PALB:
|
|
case EmuRegion.DENDY:
|
|
num = 50.0;
|
|
break;
|
|
}
|
|
if (!MyNesMain.RendererSettings.FrameSkipEnabled)
|
|
{
|
|
fps_time_period = 1.0 / num;
|
|
}
|
|
else
|
|
{
|
|
fps_time_period = 1.0 / (num / (double)MyNesMain.RendererSettings.FrameSkipInterval);
|
|
}
|
|
}
|
|
|
|
public void SubmitFrame(ref int[] buffer)
|
|
{
|
|
if (initialized)
|
|
{
|
|
if (hide_lines)
|
|
{
|
|
Marshal.Copy(buffer, lines_to_hide * RENDER_WIDTH, buffer_pixels, buffer_size - lines_to_hide * RENDER_WIDTH);
|
|
}
|
|
else
|
|
{
|
|
Marshal.Copy(buffer, 0, buffer_pixels, buffer_size);
|
|
}
|
|
double immediate_frame_time = 0.0;
|
|
NesEmu.GetSpeedValues(out var frame_time, out immediate_frame_time);
|
|
fps_emu_imm_av += immediate_frame_time;
|
|
fps_emu_fr_av += frame_time;
|
|
fps_emu_clocks += 1.0;
|
|
}
|
|
}
|
|
|
|
public void TakeSnapshot()
|
|
{
|
|
take_snap_request = true;
|
|
}
|
|
|
|
private void TakeSnapThreaded()
|
|
{
|
|
string text = "";
|
|
string text2 = "";
|
|
if (NesEmu.CurrentFilePath != null && NesEmu.CurrentFilePath != "")
|
|
{
|
|
text2 = Path.GetFileNameWithoutExtension(NesEmu.CurrentFilePath);
|
|
}
|
|
if (text2 == "")
|
|
{
|
|
text2 = "snap_shot";
|
|
}
|
|
if (!MyNesMain.EmuSettings.SnapsReplace)
|
|
{
|
|
int num = 0;
|
|
text = Path.Combine(MyNesMain.EmuSettings.SnapsFolder, text2 + "_" + num + MyNesMain.EmuSettings.SnapsFormat);
|
|
while (File.Exists(text))
|
|
{
|
|
num++;
|
|
text = Path.Combine(MyNesMain.EmuSettings.SnapsFolder, text2 + "_" + num + MyNesMain.EmuSettings.SnapsFormat);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
text = Path.Combine(MyNesMain.EmuSettings.SnapsFolder, text2 + MyNesMain.EmuSettings.SnapsFormat);
|
|
}
|
|
Tracer.WriteLine("Saving snap at " + text);
|
|
if (hide_lines)
|
|
{
|
|
Bitmap bitmap = new Bitmap(256, 240 - lines_to_hide * 2);
|
|
int[] array = new int[buffer_size];
|
|
Marshal.Copy(buffer_pixels, array, 0, buffer_size);
|
|
for (int i = 256 * lines_to_hide; i < buffer_size - 256 * lines_to_hide * 2; i++)
|
|
{
|
|
int y = i / 256;
|
|
int x = i % 256;
|
|
bitmap.SetPixel(x, y, Color.FromArgb(array[i]));
|
|
}
|
|
switch (MyNesMain.EmuSettings.SnapsFormat)
|
|
{
|
|
case ".png":
|
|
bitmap.Save(text, ImageFormat.Png);
|
|
break;
|
|
case ".jpg":
|
|
bitmap.Save(text, ImageFormat.Jpeg);
|
|
break;
|
|
case ".bmp":
|
|
bitmap.Save(text, ImageFormat.Bmp);
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Bitmap bitmap2 = new Bitmap(256, 240);
|
|
int[] array2 = new int[buffer_size];
|
|
Marshal.Copy(buffer_pixels, array2, 0, buffer_size);
|
|
for (int j = 0; j < buffer_size; j++)
|
|
{
|
|
int y2 = j / 256;
|
|
int x2 = j % 256;
|
|
bitmap2.SetPixel(x2, y2, Color.FromArgb(array2[j]));
|
|
}
|
|
switch (MyNesMain.EmuSettings.SnapsFormat)
|
|
{
|
|
case ".png":
|
|
bitmap2.Save(text, ImageFormat.Png);
|
|
break;
|
|
case ".jpg":
|
|
bitmap2.Save(text, ImageFormat.Jpeg);
|
|
break;
|
|
case ".bmp":
|
|
bitmap2.Save(text, ImageFormat.Bmp);
|
|
break;
|
|
}
|
|
}
|
|
threadPAUSED = false;
|
|
if (NesEmu.ON)
|
|
{
|
|
NesEmu.PAUSED = false;
|
|
}
|
|
Tracer.WriteInformation("Snapshot saved: " + Path.GetFileName(text));
|
|
WriteInfoNotification("Snapshot file saved: " + Path.GetFileName(text), instant: false);
|
|
}
|
|
|
|
public void TakeSnapshotAs(string fileName, string format)
|
|
{
|
|
take_snap_as_request = true;
|
|
take_snap_as_file = fileName;
|
|
take_snap_as_format = format;
|
|
}
|
|
|
|
private void TakeSnapshotAsThreaded()
|
|
{
|
|
Tracer.WriteLine("Saving snap at " + take_snap_as_file);
|
|
if (hide_lines)
|
|
{
|
|
Bitmap bitmap = new Bitmap(256, 240 - lines_to_hide * 2);
|
|
int[] array = new int[buffer_size];
|
|
Marshal.Copy(buffer_pixels, array, 0, buffer_size);
|
|
for (int i = 256 * lines_to_hide; i < buffer_size - 256 * lines_to_hide * 2; i++)
|
|
{
|
|
int y = i / 256;
|
|
int x = i % 256;
|
|
bitmap.SetPixel(x, y, Color.FromArgb(array[i]));
|
|
}
|
|
switch (take_snap_as_format)
|
|
{
|
|
case ".png":
|
|
bitmap.Save(take_snap_as_file, ImageFormat.Png);
|
|
break;
|
|
case ".jpg":
|
|
bitmap.Save(take_snap_as_file, ImageFormat.Jpeg);
|
|
break;
|
|
case ".bmp":
|
|
bitmap.Save(take_snap_as_file, ImageFormat.Bmp);
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Bitmap bitmap2 = new Bitmap(256, 240);
|
|
int[] array2 = new int[buffer_size];
|
|
Marshal.Copy(buffer_pixels, array2, 0, buffer_size);
|
|
for (int j = 0; j < buffer_size; j++)
|
|
{
|
|
int y2 = j / 256;
|
|
int x2 = j % 256;
|
|
bitmap2.SetPixel(x2, y2, Color.FromArgb(array2[j]));
|
|
}
|
|
switch (take_snap_as_format)
|
|
{
|
|
case ".png":
|
|
bitmap2.Save(take_snap_as_file, ImageFormat.Png);
|
|
break;
|
|
case ".jpg":
|
|
bitmap2.Save(take_snap_as_file, ImageFormat.Jpeg);
|
|
break;
|
|
case ".bmp":
|
|
bitmap2.Save(take_snap_as_file, ImageFormat.Bmp);
|
|
break;
|
|
}
|
|
}
|
|
threadPAUSED = false;
|
|
if (NesEmu.ON)
|
|
{
|
|
NesEmu.PAUSED = false;
|
|
}
|
|
Tracer.WriteInformation("Snapshot saved: " + Path.GetFileName(take_snap_as_file));
|
|
}
|
|
|
|
public void WriteErrorNotification(string message, bool instant)
|
|
{
|
|
if (MyNesMain.RendererSettings.Vid_ShowNotifications)
|
|
{
|
|
if (instant)
|
|
{
|
|
not_texts.Clear();
|
|
not_colors.Clear();
|
|
not_texts.Add(message);
|
|
not_colors.Add(Color.Red);
|
|
not_show = true;
|
|
not_counter = 120;
|
|
}
|
|
else if (!not_texts.Contains(message))
|
|
{
|
|
not_texts.Add(message);
|
|
not_counter = 120;
|
|
not_colors.Add(Color.Red);
|
|
not_show = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
public void WriteInfoNotification(string message, bool instant)
|
|
{
|
|
if (MyNesMain.RendererSettings.Vid_ShowNotifications)
|
|
{
|
|
if (instant)
|
|
{
|
|
not_texts.Clear();
|
|
not_colors.Clear();
|
|
not_texts.Add(message);
|
|
not_colors.Add(Color.Lime);
|
|
not_show = true;
|
|
not_counter = 120;
|
|
}
|
|
else if (!not_texts.Contains(message))
|
|
{
|
|
not_texts.Add(message);
|
|
not_counter = 120;
|
|
not_colors.Add(Color.Lime);
|
|
not_show = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
public void WriteWarningNotification(string message, bool instant)
|
|
{
|
|
if (MyNesMain.RendererSettings.Vid_ShowNotifications)
|
|
{
|
|
if (instant)
|
|
{
|
|
not_texts.Clear();
|
|
not_colors.Clear();
|
|
not_texts.Add(message);
|
|
not_colors.Add(Color.Yellow);
|
|
not_show = true;
|
|
not_counter = 120;
|
|
}
|
|
else if (!not_texts.Contains(message))
|
|
{
|
|
not_texts.Add(message);
|
|
not_counter = 120;
|
|
not_colors.Add(Color.Yellow);
|
|
not_show = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
private double GetTime()
|
|
{
|
|
return (double)Stopwatch.GetTimestamp() / (double)Stopwatch.Frequency;
|
|
}
|
|
|
|
public void ApplyRegionChanges()
|
|
{
|
|
RENDER_WIDTH = 256;
|
|
if (MyNesMain.RendererSettings.Vid_HideLines)
|
|
{
|
|
switch (NesEmu.Region)
|
|
{
|
|
case EmuRegion.NTSC:
|
|
lines_to_hide = 8;
|
|
break;
|
|
case EmuRegion.PALB:
|
|
case EmuRegion.DENDY:
|
|
lines_to_hide = 1;
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
lines_to_hide = 0;
|
|
}
|
|
renderPixelsRect = default(SDL.SDL_Rect);
|
|
if (!hide_lines)
|
|
{
|
|
renderPixelsRect.x = 0;
|
|
renderPixelsRect.y = 0;
|
|
renderPixelsRect.w = RENDER_WIDTH;
|
|
renderPixelsRect.h = 240;
|
|
}
|
|
else
|
|
{
|
|
renderPixelsRect.x = 0;
|
|
renderPixelsRect.y = lines_to_hide - 1;
|
|
renderPixelsRect.w = RENDER_WIDTH;
|
|
renderPixelsRect.h = 240 - lines_to_hide * 2;
|
|
}
|
|
}
|
|
|
|
public void Resume()
|
|
{
|
|
threadPAUSED = false;
|
|
}
|
|
|
|
public void ToggleAspectRatio(bool keep_aspect)
|
|
{
|
|
keep_aspect_ratio = keep_aspect;
|
|
CalculateTargetRect();
|
|
}
|
|
|
|
public void ToggleFPS(bool show_fps)
|
|
{
|
|
this.show_fps = show_fps;
|
|
}
|
|
|
|
public void ApplyFilter()
|
|
{
|
|
threadPAUSED = true;
|
|
SDL.SDL_SetHint("SDL_RENDER_SCALE_QUALITY", MyNesMain.RendererSettings.Vid_Filter.ToString());
|
|
Tracer.WriteLine("SDL: video render scale quality set to '" + MyNesMain.RendererSettings.Vid_Filter + "'");
|
|
ResizeEnd();
|
|
}
|
|
}
|