Skip to content

Commit

Permalink
First pass on video output.
Browse files Browse the repository at this point in the history
- Added SDL video output.
- Send VI interrupts when the currently scanned line hits the interrupt register's.
- Added some program options related to video.
  • Loading branch information
Nabile-Rahmani committed Jun 27, 2018
1 parent 34dfe21 commit 58a454f
Show file tree
Hide file tree
Showing 10 changed files with 379 additions and 15 deletions.
5 changes: 5 additions & 0 deletions DotN64.Desktop/DotN64.Desktop.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,11 @@
<Compile Include="Program.cs" />
<Compile Include="AssemblyReleaseStreamAttribute.cs" />
<Compile Include="Updater.cs" />
<Compile Include="Point.cs" />
<Compile Include="SDL\Window.cs" />
</ItemGroup>
<ItemGroup>
<Folder Include="SDL\" />
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
</Project>
17 changes: 17 additions & 0 deletions DotN64.Desktop/Point.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
namespace DotN64.Desktop
{
internal struct Point
{
#region Fields
public int X, Y;
#endregion

#region Constructors
public Point(int x, int y)
{
X = x;
Y = y;
}
#endregion
}
}
59 changes: 49 additions & 10 deletions DotN64.Desktop/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.IO;
using System.Reflection;
using DotN64.Desktop;
using System.Linq;

[assembly: AssemblyTitle(nameof(DotN64))]
[assembly: AssemblyDescription("Nintendo 64 emulator.")]
Expand All @@ -10,6 +11,7 @@
namespace DotN64.Desktop
{
using Diagnostics;
using SDL;

internal static class Program
{
Expand Down Expand Up @@ -81,6 +83,29 @@ private static void Main(string[] args)
case "-h":
ShowHelp();
return;
case "--no-video":
options.NoVideo = true;
break;
case "--video":
case "-v":
var resolution = args[++i].Split('x').Select(v => int.Parse(v));
options.VideoResolution = new Point(resolution.First(), resolution.Last());

switch (args[++i])
{
case "fullscreen":
case "f":
options.FullScreenVideo = true;
break;
case "borderless":
case "b":
options.BorderlessWindow = true;
break;
case "windowed":
case "w":
break;
}
break;
default:
options.Cartridge = arg;
break;
Expand Down Expand Up @@ -126,23 +151,34 @@ private static void Update(string releaseStream = null, bool force = false)
private static void Run(Options options)
{
var nintendo64 = new Nintendo64();
Debugger debugger = null;
Window window = null;

if (options.UseDebugger)
nintendo64.Debugger = new Debugger(nintendo64);

if (!options.NoVideo)
{
window = new Window(nintendo64, size: options.VideoResolution)
{
IsFullScreen = options.FullScreenVideo,
IsBorderless = options.BorderlessWindow
};
nintendo64.VideoOutput = window;
}

if (options.BootROM != null)
nintendo64.PIF.BootROM = File.ReadAllBytes(options.BootROM);

if (options.UseDebugger)
debugger = new Debugger(nintendo64);

if (options.Cartridge != null)
{
nintendo64.Cartridge = Cartridge.FromFile(new FileInfo(options.Cartridge));

nintendo64.PowerOn();
if (window != null)
window.Title += $" - {nintendo64.Cartridge.ImageName.Trim()}";
}

if (debugger == null)
nintendo64.Run();
else
debugger.Run(true);
nintendo64.PowerOn();
nintendo64.Run();
}

private static void ShowInfo()
Expand Down Expand Up @@ -172,15 +208,18 @@ private static void ShowHelp()
Console.WriteLine("\t\t[action = 'stream', 's'] <stream>: Downloads an update from the specified release stream.");
Console.WriteLine("\t-r, --repair: Repairs the installation by redownloading the full program.");
Console.WriteLine("\t-h, --help: Shows this help.");
Console.WriteLine("\t-v, --video <width>x<height> <mode = 'fullscreen', 'f', 'borderless', 'b', 'windowed', 'w'>: Sets the window mode.");
Console.WriteLine("\t--no-video: Disables the video output.");
}
#endregion

#region Structures
private struct Options
{
#region Fields
public bool UseDebugger;
public bool UseDebugger, NoVideo, FullScreenVideo, BorderlessWindow;
public string BootROM, Cartridge;
public Point? VideoResolution;
#endregion
}
#endregion
Expand Down
205 changes: 205 additions & 0 deletions DotN64.Desktop/SDL/Window.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
using System;
using System.Runtime.InteropServices;
using static SDL2.SDL;

namespace DotN64.Desktop.SDL
{
using RCP;
using static RCP.RealityCoprocessor.VideoInterface;

internal class Window : IVideoOutput, IDisposable
{
#region Fields
private readonly IntPtr window;
private readonly Nintendo64 nintendo64;
private IntPtr renderer, texture;
private VideoFrame lastFrame;
private bool isDisposed;
#endregion

#region Properties
public string Title
{
get => SDL_GetWindowTitle(window);
set => SDL_SetWindowTitle(window, value);
}

public Point Position
{
get
{
var position = new Point();
SDL_GetWindowPosition(window, out position.X, out position.Y);

return position;
}
set => SDL_SetWindowPosition(window, value.X, value.Y);
}

public Point Size
{
get
{
var size = new Point();
SDL_GetWindowSize(window, out size.X, out size.Y);

return size;
}
set => SDL_SetWindowSize(window, value.X, value.Y);
}

public bool IsFullScreen
{
get => (SDL_GetWindowFlags(window) & (uint)SDL_WindowFlags.SDL_WINDOW_FULLSCREEN) != 0;
set => SDL_SetWindowFullscreen(window, value ? (uint)SDL_WindowFlags.SDL_WINDOW_FULLSCREEN_DESKTOP : 0); // Could also go exclusive fullscreen with desktop bounds.
}

public bool IsBorderless
{
get => (SDL_GetWindowFlags(window) & (uint)SDL_WindowFlags.SDL_WINDOW_BORDERLESS) != 0;
set => SDL_SetWindowBordered(window, !value ? SDL_bool.SDL_TRUE : SDL_bool.SDL_FALSE);
}
#endregion

#region Constructors
public Window(Nintendo64 nintendo64, string title = null, Point? position = null, Point? size = null)
{
this.nintendo64 = nintendo64;
position = position ?? new Point(SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED);
size = size ?? new Point(640, 480);
window = SDL_CreateWindow(title ?? nameof(DotN64), position.Value.X, position.Value.Y, size.Value.X, size.Value.Y, SDL_WindowFlags.SDL_WINDOW_OPENGL | SDL_WindowFlags.SDL_WINDOW_RESIZABLE);
}

~Window()
{
Dispose(false);
}

static Window()
{
SDL_Init(SDL_INIT_VIDEO);

AppDomain.CurrentDomain.ProcessExit += (s, e) => SDL_Quit();
}
#endregion

#region Methods
private void PollEvents()
{
while (SDL_PollEvent(out var sdlEvent) != 0)
{
switch (sdlEvent.type)
{
case SDL_EventType.SDL_QUIT:
nintendo64.PowerOff();
break;
case SDL_EventType.SDL_KEYDOWN:
switch (sdlEvent.key.keysym.sym)
{
case SDL_Keycode.SDLK_ESCAPE:
nintendo64.PowerOff();
break;
case SDL_Keycode.SDLK_PAUSE:
nintendo64.Debugger = nintendo64.Debugger ?? new Diagnostics.Debugger(nintendo64);
break;
case SDL_Keycode.SDLK_r: // TODO: Reset.
break;
case SDL_Keycode.SDLK_f:
IsFullScreen = !IsFullScreen;
break;
}
break;
}
}
}

private IntPtr CreateTexture(VideoFrame frame)
{
uint pixelFormat = 0;

switch (frame.Size)
{
case ControlRegister.PixelSize.RGBA5553:
pixelFormat = SDL_PIXELFORMAT_RGBA5551;
break;
case ControlRegister.PixelSize.RGBA8888:
pixelFormat = SDL_PIXELFORMAT_RGBA8888;
break;
}

return SDL_CreateTexture(renderer, pixelFormat, (int)SDL_TextureAccess.SDL_TEXTUREACCESS_STREAMING, frame.Width, frame.Height);
}

public void Draw(VideoFrame frame, RealityCoprocessor.VideoInterface vi, RDRAM ram)
{
PollEvents();

if (renderer == IntPtr.Zero)
renderer = SDL_CreateRenderer(window, SDL_GetWindowDisplayIndex(window), SDL_RendererFlags.SDL_RENDERER_PRESENTVSYNC);

if (frame.Size <= ControlRegister.PixelSize.Reserved) // Do nothing on Blank or Reserved frame.
{
// Might want to clear the screen.
SDL_RenderPresent(renderer);
return;
}

if (frame != lastFrame)
{
SDL_DestroyTexture(texture);
texture = CreateTexture(frame);
lastFrame = frame;
}

var textureRect = new SDL_Rect
{
w = frame.Width,
h = frame.Height
};
var rendererRect = new SDL_Rect
{
w = Size.X,
h = Size.Y
};

SDL_LockTexture(texture, ref textureRect, out var pixels, out var pitch);

// TODO: This should be moved to the VI itself, which would call VideoOutput methods instead.
for (vi.CurrentVerticalLine = 0; vi.CurrentVerticalLine < vi.VerticalSync; vi.CurrentVerticalLine++) // Sweep all the way down the screen.
{
if (vi.CurrentVerticalLine >= vi.VerticalVideo.ActiveVideoStart && vi.CurrentVerticalLine < vi.VerticalVideo.ActiveVideoEnd) // Only scan active lines.
{
var offset = pitch * (vi.CurrentVerticalLine - vi.VerticalVideo.ActiveVideoStart);

Marshal.Copy(ram.Memory, (int)vi.DRAMAddress + offset, pixels + offset, pitch);
}
}

SDL_UnlockTexture(texture);

SDL_RenderClear(renderer);
SDL_RenderCopy(renderer, texture, ref textureRect, ref rendererRect);

SDL_RenderPresent(renderer);
}

protected virtual void Dispose(bool disposing)
{
if (isDisposed)
return;

SDL_DestroyWindow(window);
SDL_DestroyRenderer(renderer);
SDL_DestroyTexture(texture);

isDisposed = true;
}

public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
#endregion
}
}
3 changes: 3 additions & 0 deletions DotN64/DotN64.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,9 @@
<Compile Include="RCP\VI\RealityCoprocessor.VideoInterface.VerticalVideoRegister.cs" />
<Compile Include="RCP\VI\RealityCoprocessor.VideoInterface.VerticalBurstRegister.cs" />
<Compile Include="RCP\VI\RealityCoprocessor.VideoInterface.ScaleRegister.cs" />
<Compile Include="IVideoOutput.cs" />
<Compile Include="VideoFrame.cs" />
<Compile Include="Switch.cs" />
</ItemGroup>
<ItemGroup>
<Folder Include="CPU\" />
Expand Down
11 changes: 11 additions & 0 deletions DotN64/IVideoOutput.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
namespace DotN64
{
using static RCP.RealityCoprocessor;

public interface IVideoOutput
{
#region Methods
void Draw(VideoFrame frame, VideoInterface vi, RDRAM ram);
#endregion
}
}
Loading

0 comments on commit 58a454f

Please sign in to comment.