support loading and closing games

This commit is contained in:
emmaus 2018-10-05 22:53:40 +00:00
commit 7cdd401322
12 changed files with 413 additions and 135 deletions

View file

@ -8,6 +8,8 @@ namespace Ryujinx.Graphics.Gal
void RunActions(); void RunActions();
void ClearActions();
IGalConstBuffer Buffer { get; } IGalConstBuffer Buffer { get; }
IGalRenderTarget RenderTarget { get; } IGalRenderTarget RenderTarget { get; }

View file

@ -50,5 +50,10 @@ namespace Ryujinx.Graphics.Gal.OpenGL
RenderAction(); RenderAction();
} }
} }
public void ClearActions()
{
ActionsQueue.Clear();
}
} }
} }

View file

@ -1,8 +1,9 @@
using Ryujinx.Graphics.Gal; using Ryujinx.Graphics.Gal;
using System;
namespace Ryujinx.Graphics namespace Ryujinx.Graphics
{ {
public class NvGpu public class NvGpu : IDisposable
{ {
public IGalRenderer Renderer { get; private set; } public IGalRenderer Renderer { get; private set; }
@ -28,5 +29,15 @@ namespace Ryujinx.Graphics
EngineM2mf = new NvGpuEngineM2mf(this); EngineM2mf = new NvGpuEngineM2mf(this);
EngineP2mf = new NvGpuEngineP2mf(this); EngineP2mf = new NvGpuEngineP2mf(this);
} }
public void Dispose()
{
Dispose(true);
}
protected virtual void Dispose(bool Disposing)
{
Fifo.Dispose();
}
} }
} }

View file

@ -1,10 +1,11 @@
using Ryujinx.Graphics.Memory; using Ryujinx.Graphics.Memory;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Threading; using System.Threading;
using System;
namespace Ryujinx.Graphics namespace Ryujinx.Graphics
{ {
public class NvGpuFifo public class NvGpuFifo : IDisposable
{ {
private const int MacrosCount = 0x80; private const int MacrosCount = 0x80;
private const int MacroIndexMask = MacrosCount - 1; private const int MacroIndexMask = MacrosCount - 1;
@ -199,5 +200,15 @@ namespace Ryujinx.Graphics
{ {
Gpu.EngineM2mf.CallMethod(Vmm, PBEntry); Gpu.EngineM2mf.CallMethod(Vmm, PBEntry);
} }
public void Dispose()
{
Dispose(true);
}
protected virtual void Dispose(bool Disposing)
{
BufferQueue.Clear();
}
} }
} }

View file

@ -120,6 +120,8 @@ namespace Ryujinx.HLE
System.Dispose(); System.Dispose();
VsyncEvent.Dispose(); VsyncEvent.Dispose();
Gpu.Dispose();
} }
} }
} }

View file

@ -0,0 +1,206 @@
using Qml.Net;
using Ryujinx.Audio;
using Ryujinx.Audio.OpenAL;
using Ryujinx.Graphics.Gal;
using Ryujinx.Graphics.Gal.OpenGL;
using Ryujinx.HLE;
using System;
using System.IO;
using System.Threading;
namespace Ryujinx.UI.Emulation
{
[Signal("failed", NetVariantType.String)]
[Signal("success")]
[Signal("loaded")]
[Signal("unloaded")]
class EmulationController
{
private static GLScreen RenderScreen;
private static Switch Device;
private static IAalOutput AudioOut;
private static IGalRenderer Renderer;
private bool IsClosing;
private bool isLoaded;
public bool IsLoaded
{
get
{
return isLoaded;
}
set
{
isLoaded = value;
if (value)
{
this.ActivateSignal("loaded");
}
else
{
this.ActivateSignal("unloaded");
}
}
}
public EmulationController()
{
RenderScreen = null;
Device = null;
AudioOut = null;
IsLoaded = false;
}
public void LoadGameFile(string GameFile)
{
GameFile = GameFile.Replace("file:///", string.Empty);
if (!File.Exists(GameFile))
{
this.ActivateSignal("failed", $"File {GameFile} does not exist.");
return;
}
ShutdownEmulation();
InitializeEmulator();
switch (Path.GetExtension(GameFile).ToLowerInvariant())
{
case ".xci":
Console.WriteLine("Loading as XCI.");
Device.LoadXci(GameFile);
break;
case ".nca":
Console.WriteLine("Loading as NCA.");
Device.LoadNca(GameFile);
break;
case ".nsp":
Console.WriteLine("Loading as NSP.");
Device.LoadNsp(GameFile);
break;
default:
Console.WriteLine("Loading as homebrew.");
Device.LoadProgram(GameFile);
break;
}
StartRenderer();
IsLoaded = true;
this.ActivateSignal("success");
}
public void LoadGameFolder(string ExeFsPath)
{
if (!Directory.Exists(ExeFsPath))
{
this.ActivateSignal("failed", $"Directory {ExeFsPath} does not exist.");
return;
}
ShutdownEmulation();
InitializeEmulator();
string[] RomFsFiles = Directory.GetFiles(ExeFsPath, "*.istorage");
if (RomFsFiles.Length == 0)
{
RomFsFiles = Directory.GetFiles(ExeFsPath, "*.romfs");
}
if (RomFsFiles.Length > 0)
{
Console.WriteLine("Loading as cart with RomFS.");
Device.LoadCart(ExeFsPath, RomFsFiles[0]);
}
else
{
Console.WriteLine("Loading as cart WITHOUT RomFS.");
Device.LoadCart(ExeFsPath);
}
StartRenderer();
IsLoaded = true;
this.ActivateSignal("success");
}
public void ShutdownEmulation()
{
if(IsClosing || !IsLoaded)
{
return;
}
IsClosing = true;
Renderer?.ClearActions();
RenderScreen?.Close();
RenderScreen?.Dispose();
Device?.Dispose();
AudioOut?.Dispose();
ConsoleLog.Stop();
while (RenderScreen!=null && RenderScreen.Exists)
{
Thread.Sleep(5000);
}
RenderScreen = null;
Device = null;
AudioOut = null;
Renderer = null;
IsLoaded = false;
IsClosing = false;
}
public void InitializeEmulator()
{
Renderer = new OGLRenderer();
AudioOut = new OpenALAudioOut();
Device = new Switch(Renderer, AudioOut);
Config.Read(Device);
ConsoleLog.Start();
Device.Log.Updated += ConsoleLog.Log;
}
public void StartRenderer()
{
Thread RendererThread = new Thread(() =>
{
using (RenderScreen = new GLScreen(Device, Renderer))
{
RenderScreen.Closed += (o, e) => { ShutdownEmulation(); };
RenderScreen.MainLoop();
}
});
RendererThread.Start();
}
}
}

View file

@ -1,5 +1,7 @@
using Qml.Net; using Qml.Net;
using System; using Ryujinx.UI.Emulation;
using static Qml.Net.Qml;
namespace Ryujinx.UI namespace Ryujinx.UI
{ {
@ -7,12 +9,14 @@ namespace Ryujinx.UI
{ {
static int Main(string[] args) static int Main(string[] args)
{ {
QQuickStyle.SetStyle("Material"); QQuickStyle.SetStyle("Fusion");
using (var Application = new QGuiApplication(args)) using (var Application = new QGuiApplication(args))
{ {
using (var QmlEngine = new QQmlApplicationEngine()) using (var QmlEngine = new QQmlApplicationEngine())
{ {
RegisterType<EmulationController>("Ryujinx");
QmlEngine.Load("UI/MainWindow.qml"); QmlEngine.Load("UI/MainWindow.qml");
return Application.Exec(); return Application.Exec();

View file

@ -6,6 +6,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="OpenTK.NetStandard" Version="1.0.4" />
<PackageReference Include="Qml.Net" Version="0.6.2" /> <PackageReference Include="Qml.Net" Version="0.6.2" />
<PackageReference Include="Qml.Net.LinuxBinaries" Version="0.6.2" /> <PackageReference Include="Qml.Net.LinuxBinaries" Version="0.6.2" />
<PackageReference Include="Qml.Net.OSXBinaries" Version="0.6.2" /> <PackageReference Include="Qml.Net.OSXBinaries" Version="0.6.2" />

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48"><path d="M38 12.83L35.17 10 24 21.17 12.83 10 10 12.83 21.17 24 10 35.17 12.83 38 24 26.83 35.17 38 38 35.17 26.83 24z"/></svg>

After

Width:  |  Height:  |  Size: 210 B

View file

@ -1,8 +1,10 @@
import QtQuick 2.9 import QtQuick 2.11
import QtQuick.Layouts 1.3 import QtQuick.Layouts 1.3
import QtQuick.Controls 2.3 import QtQuick.Controls 2.4
import QtQuick.Controls.Material 2.1 import QtQuick.Controls.Material 2.4
import QtQuick.Dialogs 1.0 import QtQuick.Dialogs 1.3
import Ryujinx 1.0
ApplicationWindow { ApplicationWindow {
id: window id: window
@ -11,9 +13,39 @@ ApplicationWindow {
visible: true visible: true
title: "Ryujinx" title: "Ryujinx"
Material.theme: Material.Light menuBar: MenuBar {
Material.accent: '#41cd52' Menu{
Material.primary: '#41cd52' leftPadding: 5
leftMargin: 0
title: "&File"
MenuItem {
id: loadGameMenuItem
text: "Load Game File"
onClicked: {
loadDialog.loadGame()
}
}
MenuItem {
id: loadGameFolderMenuItem
text: "Load Game Folder"
onClicked: {
loadDialog.loadGame()
}
}
MenuSeparator{}
MenuItem {
text: "Exit"
onClicked: {
Qt.quit()
}
}
}
}
header: ToolBar { header: ToolBar {
id: toolBar id: toolBar
@ -23,23 +55,6 @@ ApplicationWindow {
anchors.fill: parent anchors.fill: parent
spacing: 20 spacing: 20
ToolButton {
id: drawerButton
text: qsTr("")
spacing: 3
display: AbstractButton.IconOnly
icon.source: !drawer.visible ? "./Images/drawer.png"
: "./Images/arrowBack.svg"
onClicked: {
if (drawer.visible) {
drawer.close()
} else {
drawer.open()
}
}
}
RowLayout { RowLayout {
id: mainControlPanel id: mainControlPanel
spacing: 20 spacing: 20
@ -48,7 +63,8 @@ ApplicationWindow {
ToolButton { ToolButton {
id: openGameFileButton id: openGameFileButton
display: AbstractButton.IconOnly text: qsTr("Load Game File")
display: AbstractButton.TextUnderIcon
icon.source: "./Images/loadGame.svg" icon.source: "./Images/loadGame.svg"
ToolTip { ToolTip {
text: qsTr("Load Game File") text: qsTr("Load Game File")
@ -61,7 +77,8 @@ ApplicationWindow {
ToolButton { ToolButton {
id: openGameFolderButton id: openGameFolderButton
display: AbstractButton.IconOnly text: qsTr("Load Game Folder")
display: AbstractButton.TextUnderIcon
icon.source: "./Images/loadFolder.svg" icon.source: "./Images/loadFolder.svg"
ToolTip { ToolTip {
text: qsTr("Load Game Folder") text: qsTr("Load Game Folder")
@ -71,6 +88,24 @@ ApplicationWindow {
loadDialog.loadGameFolder() loadDialog.loadGameFolder()
} }
} }
ToolSeparator{}
ToolButton {
id: closeGameButton
text: qsTr("Stop")
display: AbstractButton.TextUnderIcon
icon.source: "./Images/closeGame.svg"
enabled: false
ToolTip {
text: qsTr("Close Current Game")
}
onClicked: {
controller.shutdownEmulation()
}
}
} }
} }
} }
@ -80,77 +115,6 @@ ApplicationWindow {
anchors.fill: parent anchors.fill: parent
} }
Drawer {
id: drawer
width: window.width / 3
height: window.height
topMargin: toolBar.height
spacing: 10
Rectangle{
Column{
id: column
x: 40
y: 20
anchors.left: parent.left
anchors.leftMargin: 40
anchors.top: parent.top
anchors.topMargin: 20
spacing: 20
Image {
id: logo
width: 100
height: 100
fillMode: Image.PreserveAspectFit
source: "./Images/ryujinxLogo.png"
}
Label {
id: appLabel
text: qsTr("Ryujinx")
font.bold: true
font.pointSize: 16
font.weight: Font.Bold
lineHeight: 1.2
}
Rectangle{
id: rectangle
anchors.top: appLabel.bottom
anchors.topMargin: 20
ListView {
id: drawerMenuList
width: 100
height: 120
anchors.top: parent.top
anchors.topMargin: 0
currentIndex: -1
delegate: ItemDelegate {
width: parent.width
text: model.title
highlighted: ListView.isCurrentItem
}
model: ListModel {
ListElement { title: "Games"}
ListElement { title: "Settings"}
ListElement { title: "Exit"}
}
ScrollIndicator.vertical: ScrollIndicator { }
}
}
}
}
}
FileDialog { FileDialog {
id: loadDialog id: loadDialog
selectMultiple: false selectMultiple: false
@ -160,6 +124,17 @@ ApplicationWindow {
"All Supported Formats (*.xci *.nca *.nsp *.nso *.nro)"] "All Supported Formats (*.xci *.nca *.nsp *.nso *.nro)"]
folder: shortcuts.home folder: shortcuts.home
onAccepted: {
if(selectFolder )
{
controller.loadGameFolder(fileUrl)
}
else
{
controller.loadGameFile(fileUrl)
}
}
function loadGame() { function loadGame() {
selectFolder = false selectFolder = false
title = qsTr("Load Game File") title = qsTr("Load Game File")
@ -174,9 +149,47 @@ ApplicationWindow {
open() open()
} }
} }
EmulationController {
id: controller
onFailed: function(result) {
// alertBox.title = "Failed to load game"
// alertBox.text = result
// alertBox.open()
loadGameMenuItem.enabled = true
loadGameFolderMenuItem.enabled = true
openGameFileButton.enabled = true
openGameFolderButton.enabled = true
closeGameButton.enabled = false
} }
/*##^## Designer { onSuccess: {
D{i:272;anchors_height:120;anchors_width:100} loadGameMenuItem.enabled = false
loadGameFolderMenuItem.enabled = false
openGameFileButton.enabled = false
openGameFolderButton.enabled = false
closeGameButton.enabled = true
}
onLoaded: {
loadGameMenuItem.enabled = false
loadGameFolderMenuItem.enabled = false
openGameFileButton.enabled = false
openGameFolderButton.enabled = false
closeGameButton.enabled = true
}
onUnloaded: {
loadGameMenuItem.enabled = true
loadGameFolderMenuItem.enabled = true
openGameFileButton.enabled = true
openGameFolderButton.enabled = true
closeGameButton.enabled = false
}
}
} }
##^##*/

View file

@ -6,7 +6,7 @@ using System.Threading;
namespace Ryujinx namespace Ryujinx
{ {
static class ConsoleLog public static class ConsoleLog
{ {
private static Thread MessageThread; private static Thread MessageThread;
@ -26,31 +26,7 @@ namespace Ryujinx
{ LogLevel.Error, ConsoleColor.Red } { LogLevel.Error, ConsoleColor.Red }
}; };
MessageQueue = new BlockingCollection<LogEventArgs>(); Start();
ConsoleLock = new object();
MessageThread = new Thread(() =>
{
while (!MessageQueue.IsCompleted)
{
try
{
PrintLog(MessageQueue.Take());
}
catch (InvalidOperationException)
{
// IOE means that Take() was called on a completed collection.
// Some other thread can call CompleteAdding after we pass the
// IsCompleted check but before we call Take.
// We can simply catch the exception since the loop will break
// on the next iteration.
}
}
});
MessageThread.IsBackground = true;
MessageThread.Start();
} }
private static void PrintLog(LogEventArgs e) private static void PrintLog(LogEventArgs e)
@ -84,5 +60,44 @@ namespace Ryujinx
MessageQueue.Add(e); MessageQueue.Add(e);
} }
} }
public static void Stop()
{
MessageQueue?.CompleteAdding();
}
public static void Start()
{
if (MessageQueue != null && !MessageQueue.IsCompleted)
{
return;
}
MessageQueue = new BlockingCollection<LogEventArgs>();
ConsoleLock = new object();
MessageThread = new Thread(() =>
{
while (!MessageQueue.IsCompleted)
{
try
{
PrintLog(MessageQueue.Take());
}
catch (InvalidOperationException)
{
// IOE means that Take() was called on a completed collection.
// Some other thread can call CompleteAdding after we pass the
// IsCompleted check but before we call Take.
// We can simply catch the exception since the loop will break
// on the next iteration.
}
}
});
MessageThread.IsBackground = true;
MessageThread.Start();
}
} }
} }

View file

@ -60,7 +60,7 @@ namespace Ryujinx
long Ticks = 0; long Ticks = 0;
while (Exists && !IsExiting) while (!IsDisposed && Exists && !IsExiting)
{ {
if (Device.WaitFifo()) if (Device.WaitFifo())
{ {
@ -109,7 +109,7 @@ namespace Ryujinx
{ {
ProcessEvents(); ProcessEvents();
if (!IsExiting) if (!IsDisposed && !IsExiting)
{ {
UpdateFrame(); UpdateFrame();
@ -266,11 +266,18 @@ namespace Ryujinx
TitleEvent = true; TitleEvent = true;
if (!IsDisposed && Exists)
{
SwapBuffers(); SwapBuffers();
}
else
{
}
Device.System.SignalVsync(); Device.System.SignalVsync();
Device.VsyncEvent.Set(); Device.VsyncEvent?.Set();
} }
protected override void OnUnload(EventArgs e) protected override void OnUnload(EventArgs e)