support loading and closing games
This commit is contained in:
parent
f48c0e91a5
commit
7cdd401322
12 changed files with 413 additions and 135 deletions
|
@ -8,6 +8,8 @@ namespace Ryujinx.Graphics.Gal
|
|||
|
||||
void RunActions();
|
||||
|
||||
void ClearActions();
|
||||
|
||||
IGalConstBuffer Buffer { get; }
|
||||
|
||||
IGalRenderTarget RenderTarget { get; }
|
||||
|
|
|
@ -50,5 +50,10 @@ namespace Ryujinx.Graphics.Gal.OpenGL
|
|||
RenderAction();
|
||||
}
|
||||
}
|
||||
|
||||
public void ClearActions()
|
||||
{
|
||||
ActionsQueue.Clear();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -120,6 +120,8 @@ namespace Ryujinx.HLE
|
|||
System.Dispose();
|
||||
|
||||
VsyncEvent.Dispose();
|
||||
|
||||
Gpu.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
206
Ryujinx.UI/Emulation/EmulationController.cs
Normal file
206
Ryujinx.UI/Emulation/EmulationController.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
|
|
|
@ -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" />
|
||||
|
|
1
Ryujinx.UI/UI/Images/closeGame.svg
Normal file
1
Ryujinx.UI/UI/Images/closeGame.svg
Normal 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 |
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
##^##*/
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
|
|
Loading…
Add table
Reference in a new issue