support loading and closing games

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

View file

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

View file

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

View file

@ -1,8 +1,9 @@
using Ryujinx.Graphics.Gal;
using System;
namespace Ryujinx.Graphics
{
public class NvGpu
public class NvGpu : IDisposable
{
public IGalRenderer Renderer { get; private set; }
@ -28,5 +29,15 @@ namespace Ryujinx.Graphics
EngineM2mf = new NvGpuEngineM2mf(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 System.Collections.Concurrent;
using System.Threading;
using System;
namespace Ryujinx.Graphics
{
public class NvGpuFifo
public class NvGpuFifo : IDisposable
{
private const int MacrosCount = 0x80;
private const int MacroIndexMask = MacrosCount - 1;
@ -199,5 +200,15 @@ namespace Ryujinx.Graphics
{
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();
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 System;
using Ryujinx.UI.Emulation;
using static Qml.Net.Qml;
namespace Ryujinx.UI
{
@ -7,12 +9,14 @@ namespace Ryujinx.UI
{
static int Main(string[] args)
{
QQuickStyle.SetStyle("Material");
QQuickStyle.SetStyle("Fusion");
using (var Application = new QGuiApplication(args))
{
using (var QmlEngine = new QQmlApplicationEngine())
{
RegisterType<EmulationController>("Ryujinx");
QmlEngine.Load("UI/MainWindow.qml");
return Application.Exec();

View file

@ -6,6 +6,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="OpenTK.NetStandard" Version="1.0.4" />
<PackageReference Include="Qml.Net" Version="0.6.2" />
<PackageReference Include="Qml.Net.LinuxBinaries" 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.Controls 2.3
import QtQuick.Controls.Material 2.1
import QtQuick.Dialogs 1.0
import QtQuick.Controls 2.4
import QtQuick.Controls.Material 2.4
import QtQuick.Dialogs 1.3
import Ryujinx 1.0
ApplicationWindow {
id: window
@ -11,9 +13,39 @@ ApplicationWindow {
visible: true
title: "Ryujinx"
Material.theme: Material.Light
Material.accent: '#41cd52'
Material.primary: '#41cd52'
menuBar: MenuBar {
Menu{
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 {
id: toolBar
@ -23,23 +55,6 @@ ApplicationWindow {
anchors.fill: parent
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 {
id: mainControlPanel
spacing: 20
@ -48,7 +63,8 @@ ApplicationWindow {
ToolButton {
id: openGameFileButton
display: AbstractButton.IconOnly
text: qsTr("Load Game File")
display: AbstractButton.TextUnderIcon
icon.source: "./Images/loadGame.svg"
ToolTip {
text: qsTr("Load Game File")
@ -61,7 +77,8 @@ ApplicationWindow {
ToolButton {
id: openGameFolderButton
display: AbstractButton.IconOnly
text: qsTr("Load Game Folder")
display: AbstractButton.TextUnderIcon
icon.source: "./Images/loadFolder.svg"
ToolTip {
text: qsTr("Load Game Folder")
@ -71,6 +88,24 @@ ApplicationWindow {
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
}
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 {
id: loadDialog
selectMultiple: false
@ -160,6 +124,17 @@ ApplicationWindow {
"All Supported Formats (*.xci *.nca *.nsp *.nso *.nro)"]
folder: shortcuts.home
onAccepted: {
if(selectFolder )
{
controller.loadGameFolder(fileUrl)
}
else
{
controller.loadGameFile(fileUrl)
}
}
function loadGame() {
selectFolder = false
title = qsTr("Load Game File")
@ -174,9 +149,47 @@ ApplicationWindow {
open()
}
}
}
/*##^## Designer {
D{i:272;anchors_height:120;anchors_width:100}
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
}
onSuccess: {
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
{
static class ConsoleLog
public static class ConsoleLog
{
private static Thread MessageThread;
@ -26,31 +26,7 @@ namespace Ryujinx
{ LogLevel.Error, ConsoleColor.Red }
};
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();
Start();
}
private static void PrintLog(LogEventArgs e)
@ -84,5 +60,44 @@ namespace Ryujinx
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;
while (Exists && !IsExiting)
while (!IsDisposed && Exists && !IsExiting)
{
if (Device.WaitFifo())
{
@ -109,7 +109,7 @@ namespace Ryujinx
{
ProcessEvents();
if (!IsExiting)
if (!IsDisposed && !IsExiting)
{
UpdateFrame();
@ -266,11 +266,18 @@ namespace Ryujinx
TitleEvent = true;
SwapBuffers();
if (!IsDisposed && Exists)
{
SwapBuffers();
}
else
{
}
Device.System.SignalVsync();
Device.VsyncEvent.Set();
Device.VsyncEvent?.Set();
}
protected override void OnUnload(EventArgs e)