MyNes_src/MyNes/MyNes/SDL2VideoRenderer.cs
2024-07-03 18:15:19 +08:00

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();
}
}