Compare commits
23 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
eed17f963e | ||
|
c09c0c002d | ||
|
d56d335c0b | ||
|
23c844b2aa | ||
|
81691b9e37 | ||
|
2dc422bc14 | ||
|
a80fa5e33f | ||
|
954e995321 | ||
|
dad9ab6bb6 | ||
|
f0562b9c75 | ||
|
b8556530f2 | ||
|
4f3af839be | ||
|
155736c986 | ||
|
dba908dc78 | ||
|
ecee34a50c | ||
|
9b5a0c3889 | ||
|
80b4972139 | ||
|
5d85468302 | ||
|
9b1cc2cec6 | ||
|
e691622f0a | ||
|
f663a5cd38 | ||
|
f7c2e867f4 | ||
|
cedd200745 |
171
.github/workflows/flatpak.yml
vendored
Normal file
171
.github/workflows/flatpak.yml
vendored
Normal file
@@ -0,0 +1,171 @@
|
||||
name: Flatpak release job
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
ryujinx_version:
|
||||
required: true
|
||||
type: string
|
||||
|
||||
|
||||
concurrency: flatpak-release
|
||||
|
||||
jobs:
|
||||
release:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
env:
|
||||
NUGET_PACKAGES: ${{ github.workspace }}/.nuget/packages
|
||||
GIT_COMMITTER_NAME: "RyujinxBot"
|
||||
GIT_COMMITTER_EMAIL: "61127645+RyujinxBot@users.noreply.github.com"
|
||||
RYUJINX_PROJECT_FILE: "Ryujinx/Ryujinx.csproj"
|
||||
NUGET_SOURCES_DESTDIR: "nuget-sources"
|
||||
RYUJINX_VERSION: "${{ inputs.ryujinx_version }}"
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
path: Ryujinx
|
||||
|
||||
- uses: actions/setup-dotnet@v3
|
||||
with:
|
||||
global-json-file: Ryujinx/global.json
|
||||
|
||||
- name: Get version info
|
||||
id: version_info
|
||||
working-directory: Ryujinx
|
||||
run: |
|
||||
echo "git_hash=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT
|
||||
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
repository: flathub/org.ryujinx.Ryujinx
|
||||
token: ${{ secrets.RYUJINX_BOT_PAT }}
|
||||
submodules: recursive
|
||||
path: flathub
|
||||
|
||||
- name: Install dependencies
|
||||
run: python -m pip install PyYAML lxml
|
||||
|
||||
- name: Restore Nuget packages
|
||||
run: dotnet restore Ryujinx/${{ env.RYUJINX_PROJECT_FILE }}
|
||||
|
||||
- name: Generate nuget_sources.json
|
||||
shell: python
|
||||
run: |
|
||||
from pathlib import Path
|
||||
import base64
|
||||
import binascii
|
||||
import json
|
||||
import os
|
||||
|
||||
sources = []
|
||||
|
||||
for path in Path(os.environ['NUGET_PACKAGES']).glob('**/*.nupkg.sha512'):
|
||||
name = path.parent.parent.name
|
||||
version = path.parent.name
|
||||
filename = '{}.{}.nupkg'.format(name, version)
|
||||
url = 'https://api.nuget.org/v3-flatcontainer/{}/{}/{}'.format(name, version, filename)
|
||||
|
||||
with path.open() as fp:
|
||||
sha512 = binascii.hexlify(base64.b64decode(fp.read())).decode('ascii')
|
||||
|
||||
sources.append({
|
||||
'type': 'file',
|
||||
'url': url,
|
||||
'sha512': sha512,
|
||||
'dest': os.environ['NUGET_SOURCES_DESTDIR'],
|
||||
'dest-filename': filename,
|
||||
})
|
||||
|
||||
with open('flathub/nuget_sources.json', 'w') as fp:
|
||||
json.dump(sources, fp, indent=4)
|
||||
|
||||
- name: Update flatpak metadata
|
||||
id: metadata
|
||||
env:
|
||||
RYUJINX_GIT_HASH: ${{ steps.version_info.outputs.git_hash }}
|
||||
shell: python
|
||||
run: |
|
||||
import hashlib
|
||||
import hmac
|
||||
import json
|
||||
import os
|
||||
import yaml
|
||||
from datetime import datetime
|
||||
from lxml import etree
|
||||
|
||||
|
||||
# Ensure we don't destroy multiline strings
|
||||
def str_presenter(dumper, data):
|
||||
if len(data.splitlines()) > 1:
|
||||
return dumper.represent_scalar("tag:yaml.org,2002:str", data, style="|")
|
||||
return dumper.represent_scalar("tag:yaml.org,2002:str", data)
|
||||
|
||||
|
||||
yaml.representer.SafeRepresenter.add_representer(str, str_presenter)
|
||||
|
||||
yaml_file = "flathub/org.ryujinx.Ryujinx.yml"
|
||||
xml_file = "flathub/org.ryujinx.Ryujinx.appdata.xml"
|
||||
|
||||
with open(yaml_file, "r") as f:
|
||||
data = yaml.safe_load(f)
|
||||
|
||||
for source in data["modules"][0]["sources"]:
|
||||
if type(source) is str:
|
||||
continue
|
||||
if (
|
||||
source["type"] == "git"
|
||||
and source["url"] == "https://github.com/Ryujinx/Ryujinx.git"
|
||||
):
|
||||
source["commit"] = os.environ['RYUJINX_GIT_HASH']
|
||||
|
||||
is_same_version = data["modules"][0]["build-options"]["env"]["RYUJINX_VERSION"] == os.environ['RYUJINX_VERSION']
|
||||
|
||||
with open(os.environ['GITHUB_OUTPUT'], "a") as gh_out:
|
||||
if is_same_version:
|
||||
gh_out.write(f"commit_message=Retry update to {os.environ['RYUJINX_VERSION']}")
|
||||
else:
|
||||
gh_out.write(f"commit_message=Update to {os.environ['RYUJINX_VERSION']}")
|
||||
|
||||
if not is_same_version:
|
||||
data["modules"][0]["build-options"]["env"]["RYUJINX_VERSION"] = os.environ['RYUJINX_VERSION']
|
||||
|
||||
with open(yaml_file, "w") as f:
|
||||
yaml.safe_dump(data, f, sort_keys=False)
|
||||
|
||||
parser = etree.XMLParser(remove_blank_text=True)
|
||||
tree = etree.parse(xml_file, parser)
|
||||
|
||||
root = tree.getroot()
|
||||
|
||||
releases = root.find("releases")
|
||||
|
||||
element = etree.Element("release")
|
||||
element.set("version", os.environ['RYUJINX_VERSION'])
|
||||
element.set("date", datetime.now().date().isoformat())
|
||||
releases.insert(0, element)
|
||||
|
||||
# Ensure 4 spaces
|
||||
etree.indent(root, space=" ")
|
||||
|
||||
with open(xml_file, "wb") as f:
|
||||
f.write(
|
||||
etree.tostring(
|
||||
tree,
|
||||
pretty_print=True,
|
||||
encoding="UTF-8",
|
||||
doctype='<?xml version="1.0" encoding="UTF-8"?>',
|
||||
)
|
||||
)
|
||||
|
||||
- name: Push flatpak update
|
||||
working-directory: flathub
|
||||
env:
|
||||
COMMIT_MESSAGE: ${{ steps.metadata.outputs.commit_message }}
|
||||
run: |
|
||||
git config user.name "${{ env.GIT_COMMITTER_NAME }}"
|
||||
git config user.email "${{ env.GIT_COMMITTER_EMAIL }}"
|
||||
git add .
|
||||
git commit -m "$COMMIT_MESSAGE"
|
||||
git push origin master
|
26
.github/workflows/release.yml
vendored
26
.github/workflows/release.yml
vendored
@@ -13,23 +13,22 @@ on:
|
||||
|
||||
concurrency: release
|
||||
|
||||
env:
|
||||
POWERSHELL_TELEMETRY_OPTOUT: 1
|
||||
DOTNET_CLI_TELEMETRY_OPTOUT: 1
|
||||
RYUJINX_BASE_VERSION: "1.1"
|
||||
RYUJINX_TARGET_RELEASE_CHANNEL_NAME: "master"
|
||||
RYUJINX_TARGET_RELEASE_CHANNEL_OWNER: "Ryujinx"
|
||||
RYUJINX_TARGET_RELEASE_CHANNEL_REPO: "release-channel-master"
|
||||
|
||||
jobs:
|
||||
release:
|
||||
runs-on: windows-latest
|
||||
|
||||
env:
|
||||
POWERSHELL_TELEMETRY_OPTOUT: 1
|
||||
DOTNET_CLI_TELEMETRY_OPTOUT: 1
|
||||
RYUJINX_BASE_VERSION: "1.1"
|
||||
RYUJINX_TARGET_RELEASE_CHANNEL_NAME: "master"
|
||||
RYUJINX_TARGET_RELEASE_CHANNEL_OWNER: "Ryujinx"
|
||||
RYUJINX_TARGET_RELEASE_CHANNEL_REPO: "release-channel-master"
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-dotnet@v3
|
||||
with:
|
||||
dotnet-version: 7.0.x
|
||||
global-json-file: global.json
|
||||
- name: Get version info
|
||||
id: version_info
|
||||
run: |
|
||||
@@ -112,3 +111,10 @@ jobs:
|
||||
owner: ${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}
|
||||
repo: ${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}
|
||||
token: ${{ secrets.RELEASE_TOKEN }}
|
||||
|
||||
flatpak_release:
|
||||
uses: ./.github/workflows/flatpak.yml
|
||||
needs: release
|
||||
with:
|
||||
ryujinx_version: "1.1.${{ github.run_number }}"
|
||||
secrets: inherit
|
||||
|
@@ -265,7 +265,7 @@ namespace ARMeilleure.CodeGen.Arm64
|
||||
}
|
||||
else
|
||||
{
|
||||
relocInfo = new RelocInfo(new RelocEntry[0]);
|
||||
relocInfo = new RelocInfo(Array.Empty<RelocEntry>());
|
||||
}
|
||||
|
||||
return (code, relocInfo);
|
||||
|
@@ -9,7 +9,7 @@ using static ARMeilleure.IntermediateRepresentation.Operation.Factory;
|
||||
|
||||
namespace ARMeilleure.CodeGen.Arm64
|
||||
{
|
||||
class PreAllocator
|
||||
static class PreAllocator
|
||||
{
|
||||
private class ConstantDict
|
||||
{
|
||||
@@ -54,8 +54,8 @@ namespace ARMeilleure.CodeGen.Arm64
|
||||
continue;
|
||||
}
|
||||
|
||||
HandleConstantRegCopy(constants, block.Operations, node);
|
||||
HandleDestructiveRegCopy(block.Operations, node);
|
||||
InsertConstantRegCopies(constants, block.Operations, node);
|
||||
InsertDestructiveRegCopies(block.Operations, node);
|
||||
|
||||
switch (node.Instruction)
|
||||
{
|
||||
@@ -78,28 +78,28 @@ namespace ARMeilleure.CodeGen.Arm64
|
||||
|
||||
// Copy values to registers expected by the function
|
||||
// being called, as mandated by the ABI.
|
||||
HandleCall(constants, block.Operations, node);
|
||||
InsertCallCopies(constants, block.Operations, node);
|
||||
break;
|
||||
case Instruction.CompareAndSwap:
|
||||
case Instruction.CompareAndSwap16:
|
||||
case Instruction.CompareAndSwap8:
|
||||
nextNode = HandleCompareAndSwap(block.Operations, node);
|
||||
nextNode = GenerateCompareAndSwap(block.Operations, node);
|
||||
break;
|
||||
case Instruction.LoadArgument:
|
||||
nextNode = HandleLoadArgument(cctx, ref buffer, block.Operations, preservedArgs, node);
|
||||
nextNode = InsertLoadArgumentCopy(cctx, ref buffer, block.Operations, preservedArgs, node);
|
||||
break;
|
||||
case Instruction.Return:
|
||||
HandleReturn(block.Operations, node);
|
||||
InsertReturnCopy(block.Operations, node);
|
||||
break;
|
||||
case Instruction.Tailcall:
|
||||
HandleTailcall(constants, block.Operations, stackAlloc, node, node);
|
||||
InsertTailcallCopies(constants, block.Operations, stackAlloc, node, node);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void HandleConstantRegCopy(ConstantDict constants, IntrusiveList<Operation> nodes, Operation node)
|
||||
private static void InsertConstantRegCopies(ConstantDict constants, IntrusiveList<Operation> nodes, Operation node)
|
||||
{
|
||||
if (node.SourcesCount == 0 || IsIntrinsicWithConst(node))
|
||||
{
|
||||
@@ -211,7 +211,7 @@ namespace ARMeilleure.CodeGen.Arm64
|
||||
}
|
||||
}
|
||||
|
||||
private static void HandleDestructiveRegCopy(IntrusiveList<Operation> nodes, Operation node)
|
||||
private static void InsertDestructiveRegCopies(IntrusiveList<Operation> nodes, Operation node)
|
||||
{
|
||||
if (node.Destination == default || node.SourcesCount == 0)
|
||||
{
|
||||
@@ -259,7 +259,7 @@ namespace ARMeilleure.CodeGen.Arm64
|
||||
}
|
||||
}
|
||||
|
||||
private static void HandleCall(ConstantDict constants, IntrusiveList<Operation> nodes, Operation node)
|
||||
private static void InsertCallCopies(ConstantDict constants, IntrusiveList<Operation> nodes, Operation node)
|
||||
{
|
||||
Operation operation = node;
|
||||
|
||||
@@ -319,7 +319,7 @@ namespace ARMeilleure.CodeGen.Arm64
|
||||
|
||||
Operation copyOp = Operation(Instruction.Copy, argReg, source);
|
||||
|
||||
HandleConstantRegCopy(constants, nodes, nodes.AddBefore(node, copyOp));
|
||||
InsertConstantRegCopies(constants, nodes, nodes.AddBefore(node, copyOp));
|
||||
|
||||
sources.Add(argReg);
|
||||
}
|
||||
@@ -329,7 +329,7 @@ namespace ARMeilleure.CodeGen.Arm64
|
||||
|
||||
Operation spillOp = Operation(Instruction.SpillArg, default, offset, source);
|
||||
|
||||
HandleConstantRegCopy(constants, nodes, nodes.AddBefore(node, spillOp));
|
||||
InsertConstantRegCopies(constants, nodes, nodes.AddBefore(node, spillOp));
|
||||
|
||||
stackOffset += source.Type.GetSizeInBytes();
|
||||
}
|
||||
@@ -364,7 +364,7 @@ namespace ARMeilleure.CodeGen.Arm64
|
||||
operation.SetSources(sources.ToArray());
|
||||
}
|
||||
|
||||
private static void HandleTailcall(
|
||||
private static void InsertTailcallCopies(
|
||||
ConstantDict constants,
|
||||
IntrusiveList<Operation> nodes,
|
||||
StackAllocator stackAlloc,
|
||||
@@ -420,7 +420,7 @@ namespace ARMeilleure.CodeGen.Arm64
|
||||
|
||||
Operation copyOp = Operation(Instruction.Copy, argReg, source);
|
||||
|
||||
HandleConstantRegCopy(constants, nodes, nodes.AddBefore(node, copyOp));
|
||||
InsertConstantRegCopies(constants, nodes, nodes.AddBefore(node, copyOp));
|
||||
|
||||
sources.Add(argReg);
|
||||
}
|
||||
@@ -444,7 +444,7 @@ namespace ARMeilleure.CodeGen.Arm64
|
||||
operation.SetSources(sources.ToArray());
|
||||
}
|
||||
|
||||
private static Operation HandleCompareAndSwap(IntrusiveList<Operation> nodes, Operation node)
|
||||
private static Operation GenerateCompareAndSwap(IntrusiveList<Operation> nodes, Operation node)
|
||||
{
|
||||
Operand expected = node.GetSource(1);
|
||||
|
||||
@@ -508,7 +508,7 @@ namespace ARMeilleure.CodeGen.Arm64
|
||||
return node.ListNext;
|
||||
}
|
||||
|
||||
private static void HandleReturn(IntrusiveList<Operation> nodes, Operation node)
|
||||
private static void InsertReturnCopy(IntrusiveList<Operation> nodes, Operation node)
|
||||
{
|
||||
if (node.SourcesCount == 0)
|
||||
{
|
||||
@@ -537,7 +537,7 @@ namespace ARMeilleure.CodeGen.Arm64
|
||||
}
|
||||
}
|
||||
|
||||
private static Operation HandleLoadArgument(
|
||||
private static Operation InsertLoadArgumentCopy(
|
||||
CompilerContext cctx,
|
||||
ref Span<Operation> buffer,
|
||||
IntrusiveList<Operation> nodes,
|
||||
@@ -629,7 +629,7 @@ namespace ARMeilleure.CodeGen.Arm64
|
||||
if (dest.AssignmentsCount == 1)
|
||||
{
|
||||
// Let's propagate the argument if we can to avoid copies.
|
||||
Propagate(ref buffer, dest, preservedArgs[index]);
|
||||
PreAllocatorCommon.Propagate(ref buffer, dest, preservedArgs[index]);
|
||||
nextNode = node.ListNext;
|
||||
}
|
||||
else
|
||||
@@ -648,54 +648,6 @@ namespace ARMeilleure.CodeGen.Arm64
|
||||
}
|
||||
}
|
||||
|
||||
private static void Propagate(ref Span<Operation> buffer, Operand dest, Operand value)
|
||||
{
|
||||
ReadOnlySpan<Operation> uses = dest.GetUses(ref buffer);
|
||||
|
||||
foreach (Operation use in uses)
|
||||
{
|
||||
for (int srcIndex = 0; srcIndex < use.SourcesCount; srcIndex++)
|
||||
{
|
||||
Operand useSrc = use.GetSource(srcIndex);
|
||||
|
||||
if (useSrc == dest)
|
||||
{
|
||||
use.SetSource(srcIndex, value);
|
||||
}
|
||||
else if (useSrc.Kind == OperandKind.Memory)
|
||||
{
|
||||
MemoryOperand memoryOp = useSrc.GetMemory();
|
||||
|
||||
Operand baseAddr = memoryOp.BaseAddress;
|
||||
Operand index = memoryOp.Index;
|
||||
bool changed = false;
|
||||
|
||||
if (baseAddr == dest)
|
||||
{
|
||||
baseAddr = value;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (index == dest)
|
||||
{
|
||||
index = value;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (changed)
|
||||
{
|
||||
use.SetSource(srcIndex, MemoryOp(
|
||||
useSrc.Type,
|
||||
baseAddr,
|
||||
index,
|
||||
memoryOp.Scale,
|
||||
memoryOp.Displacement));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static Operand AddFloatConstantCopy(
|
||||
ConstantDict constants,
|
||||
IntrusiveList<Operation> nodes,
|
||||
|
57
ARMeilleure/CodeGen/PreAllocatorCommon.cs
Normal file
57
ARMeilleure/CodeGen/PreAllocatorCommon.cs
Normal file
@@ -0,0 +1,57 @@
|
||||
using ARMeilleure.IntermediateRepresentation;
|
||||
using System;
|
||||
using static ARMeilleure.IntermediateRepresentation.Operand.Factory;
|
||||
|
||||
namespace ARMeilleure.CodeGen
|
||||
{
|
||||
static class PreAllocatorCommon
|
||||
{
|
||||
public static void Propagate(ref Span<Operation> buffer, Operand dest, Operand value)
|
||||
{
|
||||
ReadOnlySpan<Operation> uses = dest.GetUses(ref buffer);
|
||||
|
||||
foreach (Operation use in uses)
|
||||
{
|
||||
for (int srcIndex = 0; srcIndex < use.SourcesCount; srcIndex++)
|
||||
{
|
||||
Operand useSrc = use.GetSource(srcIndex);
|
||||
|
||||
if (useSrc == dest)
|
||||
{
|
||||
use.SetSource(srcIndex, value);
|
||||
}
|
||||
else if (useSrc.Kind == OperandKind.Memory)
|
||||
{
|
||||
MemoryOperand memoryOp = useSrc.GetMemory();
|
||||
|
||||
Operand baseAddr = memoryOp.BaseAddress;
|
||||
Operand index = memoryOp.Index;
|
||||
bool changed = false;
|
||||
|
||||
if (baseAddr == dest)
|
||||
{
|
||||
baseAddr = value;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (index == dest)
|
||||
{
|
||||
index = value;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (changed)
|
||||
{
|
||||
use.SetSource(srcIndex, MemoryOp(
|
||||
useSrc.Type,
|
||||
baseAddr,
|
||||
index,
|
||||
memoryOp.Scale,
|
||||
memoryOp.Displacement));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -433,16 +433,11 @@ namespace ARMeilleure.CodeGen.RegisterAllocators
|
||||
|
||||
private static int GetHighestValueIndex(Span<int> span)
|
||||
{
|
||||
int highest = span[0];
|
||||
|
||||
if (highest == int.MaxValue)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
int highest = int.MinValue;
|
||||
|
||||
int selected = 0;
|
||||
|
||||
for (int index = 1; index < span.Length; index++)
|
||||
for (int index = 0; index < span.Length; index++)
|
||||
{
|
||||
int current = span[index];
|
||||
|
||||
|
@@ -2,19 +2,20 @@ using ARMeilleure.CodeGen.RegisterAllocators;
|
||||
using ARMeilleure.IntermediateRepresentation;
|
||||
using ARMeilleure.Translation;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using static ARMeilleure.IntermediateRepresentation.Operand.Factory;
|
||||
using static ARMeilleure.IntermediateRepresentation.Operation.Factory;
|
||||
|
||||
namespace ARMeilleure.CodeGen.X86
|
||||
{
|
||||
static class PreAllocator
|
||||
class PreAllocator
|
||||
{
|
||||
public static void RunPass(CompilerContext cctx, StackAllocator stackAlloc, out int maxCallArgs)
|
||||
{
|
||||
maxCallArgs = -1;
|
||||
|
||||
Span<Operation> buffer = default;
|
||||
|
||||
CallConvName callConv = CallingConvention.GetCurrentCallConv();
|
||||
|
||||
Operand[] preservedArgs = new Operand[CallingConvention.GetArgumentsOnRegsCount()];
|
||||
@@ -32,9 +33,9 @@ namespace ARMeilleure.CodeGen.X86
|
||||
continue;
|
||||
}
|
||||
|
||||
HandleConstantRegCopy(block.Operations, node);
|
||||
HandleDestructiveRegCopy(block.Operations, node);
|
||||
HandleConstrainedRegCopy(block.Operations, node);
|
||||
InsertConstantRegCopies(block.Operations, node);
|
||||
InsertDestructiveRegCopies(block.Operations, node);
|
||||
InsertConstrainedRegCopies(block.Operations, node);
|
||||
|
||||
switch (node.Instruction)
|
||||
{
|
||||
@@ -59,62 +60,62 @@ namespace ARMeilleure.CodeGen.X86
|
||||
// being called, as mandated by the ABI.
|
||||
if (callConv == CallConvName.Windows)
|
||||
{
|
||||
HandleCallWindowsAbi(block.Operations, stackAlloc, node);
|
||||
PreAllocatorWindows.InsertCallCopies(block.Operations, stackAlloc, node);
|
||||
}
|
||||
else /* if (callConv == CallConvName.SystemV) */
|
||||
{
|
||||
HandleCallSystemVAbi(block.Operations, node);
|
||||
PreAllocatorSystemV.InsertCallCopies(block.Operations, node);
|
||||
}
|
||||
break;
|
||||
|
||||
case Instruction.ConvertToFPUI:
|
||||
HandleConvertToFPUI(block.Operations, node);
|
||||
GenerateConvertToFPUI(block.Operations, node);
|
||||
break;
|
||||
|
||||
case Instruction.LoadArgument:
|
||||
if (callConv == CallConvName.Windows)
|
||||
{
|
||||
nextNode = HandleLoadArgumentWindowsAbi(cctx, block.Operations, preservedArgs, node);
|
||||
nextNode = PreAllocatorWindows.InsertLoadArgumentCopy(cctx, ref buffer, block.Operations, preservedArgs, node);
|
||||
}
|
||||
else /* if (callConv == CallConvName.SystemV) */
|
||||
{
|
||||
nextNode = HandleLoadArgumentSystemVAbi(cctx, block.Operations, preservedArgs, node);
|
||||
nextNode = PreAllocatorSystemV.InsertLoadArgumentCopy(cctx, ref buffer, block.Operations, preservedArgs, node);
|
||||
}
|
||||
break;
|
||||
|
||||
case Instruction.Negate:
|
||||
if (!node.GetSource(0).Type.IsInteger())
|
||||
{
|
||||
HandleNegate(block.Operations, node);
|
||||
GenerateNegate(block.Operations, node);
|
||||
}
|
||||
break;
|
||||
|
||||
case Instruction.Return:
|
||||
if (callConv == CallConvName.Windows)
|
||||
{
|
||||
HandleReturnWindowsAbi(cctx, block.Operations, preservedArgs, node);
|
||||
PreAllocatorWindows.InsertReturnCopy(cctx, block.Operations, preservedArgs, node);
|
||||
}
|
||||
else /* if (callConv == CallConvName.SystemV) */
|
||||
{
|
||||
HandleReturnSystemVAbi(block.Operations, node);
|
||||
PreAllocatorSystemV.InsertReturnCopy(block.Operations, node);
|
||||
}
|
||||
break;
|
||||
|
||||
case Instruction.Tailcall:
|
||||
if (callConv == CallConvName.Windows)
|
||||
{
|
||||
HandleTailcallWindowsAbi(block.Operations, stackAlloc, node);
|
||||
PreAllocatorWindows.InsertTailcallCopies(block.Operations, stackAlloc, node);
|
||||
}
|
||||
else
|
||||
{
|
||||
HandleTailcallSystemVAbi(block.Operations, stackAlloc, node);
|
||||
PreAllocatorSystemV.InsertTailcallCopies(block.Operations, stackAlloc, node);
|
||||
}
|
||||
break;
|
||||
|
||||
case Instruction.VectorInsert8:
|
||||
if (!HardwareCapabilities.SupportsSse41)
|
||||
{
|
||||
HandleVectorInsert8(block.Operations, node);
|
||||
GenerateVectorInsert8(block.Operations, node);
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -131,7 +132,7 @@ namespace ARMeilleure.CodeGen.X86
|
||||
}
|
||||
}
|
||||
|
||||
private static void HandleConstantRegCopy(IntrusiveList<Operation> nodes, Operation node)
|
||||
protected static void InsertConstantRegCopies(IntrusiveList<Operation> nodes, Operation node)
|
||||
{
|
||||
if (node.SourcesCount == 0 || IsXmmIntrinsic(node))
|
||||
{
|
||||
@@ -212,7 +213,7 @@ namespace ARMeilleure.CodeGen.X86
|
||||
}
|
||||
}
|
||||
|
||||
private static void HandleConstrainedRegCopy(IntrusiveList<Operation> nodes, Operation node)
|
||||
protected static void InsertConstrainedRegCopies(IntrusiveList<Operation> nodes, Operation node)
|
||||
{
|
||||
Operand dest = node.Destination;
|
||||
|
||||
@@ -369,7 +370,7 @@ namespace ARMeilleure.CodeGen.X86
|
||||
}
|
||||
}
|
||||
|
||||
private static void HandleDestructiveRegCopy(IntrusiveList<Operation> nodes, Operation node)
|
||||
protected static void InsertDestructiveRegCopies(IntrusiveList<Operation> nodes, Operation node)
|
||||
{
|
||||
if (node.Destination == default || node.SourcesCount == 0)
|
||||
{
|
||||
@@ -447,7 +448,7 @@ namespace ARMeilleure.CodeGen.X86
|
||||
}
|
||||
}
|
||||
|
||||
private static void HandleConvertToFPUI(IntrusiveList<Operation> nodes, Operation node)
|
||||
private static void GenerateConvertToFPUI(IntrusiveList<Operation> nodes, Operation node)
|
||||
{
|
||||
// Unsigned integer to FP conversions are not supported on X86.
|
||||
// We need to turn them into signed integer to FP conversions, and
|
||||
@@ -501,7 +502,7 @@ namespace ARMeilleure.CodeGen.X86
|
||||
Delete(nodes, currentNode);
|
||||
}
|
||||
|
||||
private static void HandleNegate(IntrusiveList<Operation> nodes, Operation node)
|
||||
private static void GenerateNegate(IntrusiveList<Operation> nodes, Operation node)
|
||||
{
|
||||
// There's no SSE FP negate instruction, so we need to transform that into
|
||||
// a XOR of the value to be negated with a mask with the highest bit set.
|
||||
@@ -534,7 +535,7 @@ namespace ARMeilleure.CodeGen.X86
|
||||
Delete(nodes, currentNode);
|
||||
}
|
||||
|
||||
private static void HandleVectorInsert8(IntrusiveList<Operation> nodes, Operation node)
|
||||
private static void GenerateVectorInsert8(IntrusiveList<Operation> nodes, Operation node)
|
||||
{
|
||||
// Handle vector insertion, when SSE 4.1 is not supported.
|
||||
Operand dest = node.Destination;
|
||||
@@ -579,620 +580,7 @@ namespace ARMeilleure.CodeGen.X86
|
||||
Delete(nodes, currentNode);
|
||||
}
|
||||
|
||||
private static void HandleCallWindowsAbi(IntrusiveList<Operation> nodes, StackAllocator stackAlloc, Operation node)
|
||||
{
|
||||
Operand dest = node.Destination;
|
||||
|
||||
// Handle struct arguments.
|
||||
int retArgs = 0;
|
||||
int stackAllocOffset = 0;
|
||||
|
||||
int AllocateOnStack(int size)
|
||||
{
|
||||
// We assume that the stack allocator is initially empty (TotalSize = 0).
|
||||
// Taking that into account, we can reuse the space allocated for other
|
||||
// calls by keeping track of our own allocated size (stackAllocOffset).
|
||||
// If the space allocated is not big enough, then we just expand it.
|
||||
int offset = stackAllocOffset;
|
||||
|
||||
if (stackAllocOffset + size > stackAlloc.TotalSize)
|
||||
{
|
||||
stackAlloc.Allocate((stackAllocOffset + size) - stackAlloc.TotalSize);
|
||||
}
|
||||
|
||||
stackAllocOffset += size;
|
||||
|
||||
return offset;
|
||||
}
|
||||
|
||||
Operand arg0Reg = default;
|
||||
|
||||
if (dest != default && dest.Type == OperandType.V128)
|
||||
{
|
||||
int stackOffset = AllocateOnStack(dest.Type.GetSizeInBytes());
|
||||
|
||||
arg0Reg = Gpr(CallingConvention.GetIntArgumentRegister(0), OperandType.I64);
|
||||
|
||||
Operation allocOp = Operation(Instruction.StackAlloc, arg0Reg, Const(stackOffset));
|
||||
|
||||
nodes.AddBefore(node, allocOp);
|
||||
|
||||
retArgs = 1;
|
||||
}
|
||||
|
||||
int argsCount = node.SourcesCount - 1;
|
||||
int maxArgs = CallingConvention.GetArgumentsOnRegsCount() - retArgs;
|
||||
|
||||
if (argsCount > maxArgs)
|
||||
{
|
||||
argsCount = maxArgs;
|
||||
}
|
||||
|
||||
Operand[] sources = new Operand[1 + retArgs + argsCount];
|
||||
|
||||
sources[0] = node.GetSource(0);
|
||||
|
||||
if (arg0Reg != default)
|
||||
{
|
||||
sources[1] = arg0Reg;
|
||||
}
|
||||
|
||||
for (int index = 1; index < node.SourcesCount; index++)
|
||||
{
|
||||
Operand source = node.GetSource(index);
|
||||
|
||||
if (source.Type == OperandType.V128)
|
||||
{
|
||||
Operand stackAddr = Local(OperandType.I64);
|
||||
|
||||
int stackOffset = AllocateOnStack(source.Type.GetSizeInBytes());
|
||||
|
||||
nodes.AddBefore(node, Operation(Instruction.StackAlloc, stackAddr, Const(stackOffset)));
|
||||
|
||||
Operation storeOp = Operation(Instruction.Store, default, stackAddr, source);
|
||||
|
||||
HandleConstantRegCopy(nodes, nodes.AddBefore(node, storeOp));
|
||||
|
||||
node.SetSource(index, stackAddr);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle arguments passed on registers.
|
||||
for (int index = 0; index < argsCount; index++)
|
||||
{
|
||||
Operand source = node.GetSource(index + 1);
|
||||
Operand argReg;
|
||||
|
||||
int argIndex = index + retArgs;
|
||||
|
||||
if (source.Type.IsInteger())
|
||||
{
|
||||
argReg = Gpr(CallingConvention.GetIntArgumentRegister(argIndex), source.Type);
|
||||
}
|
||||
else
|
||||
{
|
||||
argReg = Xmm(CallingConvention.GetVecArgumentRegister(argIndex), source.Type);
|
||||
}
|
||||
|
||||
Operation copyOp = Operation(Instruction.Copy, argReg, source);
|
||||
|
||||
HandleConstantRegCopy(nodes, nodes.AddBefore(node, copyOp));
|
||||
|
||||
sources[1 + retArgs + index] = argReg;
|
||||
}
|
||||
|
||||
// The remaining arguments (those that are not passed on registers)
|
||||
// should be passed on the stack, we write them to the stack with "SpillArg".
|
||||
for (int index = argsCount; index < node.SourcesCount - 1; index++)
|
||||
{
|
||||
Operand source = node.GetSource(index + 1);
|
||||
Operand offset = Const((index + retArgs) * 8);
|
||||
|
||||
Operation spillOp = Operation(Instruction.SpillArg, default, offset, source);
|
||||
|
||||
HandleConstantRegCopy(nodes, nodes.AddBefore(node, spillOp));
|
||||
}
|
||||
|
||||
if (dest != default)
|
||||
{
|
||||
if (dest.Type == OperandType.V128)
|
||||
{
|
||||
Operand retValueAddr = Local(OperandType.I64);
|
||||
|
||||
nodes.AddBefore(node, Operation(Instruction.Copy, retValueAddr, arg0Reg));
|
||||
|
||||
Operation loadOp = Operation(Instruction.Load, dest, retValueAddr);
|
||||
|
||||
nodes.AddAfter(node, loadOp);
|
||||
|
||||
node.Destination = default;
|
||||
}
|
||||
else
|
||||
{
|
||||
Operand retReg = dest.Type.IsInteger()
|
||||
? Gpr(CallingConvention.GetIntReturnRegister(), dest.Type)
|
||||
: Xmm(CallingConvention.GetVecReturnRegister(), dest.Type);
|
||||
|
||||
Operation copyOp = Operation(Instruction.Copy, dest, retReg);
|
||||
|
||||
nodes.AddAfter(node, copyOp);
|
||||
|
||||
node.Destination = retReg;
|
||||
}
|
||||
}
|
||||
|
||||
node.SetSources(sources);
|
||||
}
|
||||
|
||||
private static void HandleCallSystemVAbi(IntrusiveList<Operation> nodes, Operation node)
|
||||
{
|
||||
Operand dest = node.Destination;
|
||||
|
||||
List<Operand> sources = new List<Operand>
|
||||
{
|
||||
node.GetSource(0)
|
||||
};
|
||||
|
||||
int argsCount = node.SourcesCount - 1;
|
||||
|
||||
int intMax = CallingConvention.GetIntArgumentsOnRegsCount();
|
||||
int vecMax = CallingConvention.GetVecArgumentsOnRegsCount();
|
||||
|
||||
int intCount = 0;
|
||||
int vecCount = 0;
|
||||
|
||||
int stackOffset = 0;
|
||||
|
||||
for (int index = 0; index < argsCount; index++)
|
||||
{
|
||||
Operand source = node.GetSource(index + 1);
|
||||
|
||||
bool passOnReg;
|
||||
|
||||
if (source.Type.IsInteger())
|
||||
{
|
||||
passOnReg = intCount < intMax;
|
||||
}
|
||||
else if (source.Type == OperandType.V128)
|
||||
{
|
||||
passOnReg = intCount + 1 < intMax;
|
||||
}
|
||||
else
|
||||
{
|
||||
passOnReg = vecCount < vecMax;
|
||||
}
|
||||
|
||||
if (source.Type == OperandType.V128 && passOnReg)
|
||||
{
|
||||
// V128 is a struct, we pass each half on a GPR if possible.
|
||||
Operand argReg = Gpr(CallingConvention.GetIntArgumentRegister(intCount++), OperandType.I64);
|
||||
Operand argReg2 = Gpr(CallingConvention.GetIntArgumentRegister(intCount++), OperandType.I64);
|
||||
|
||||
nodes.AddBefore(node, Operation(Instruction.VectorExtract, argReg, source, Const(0)));
|
||||
nodes.AddBefore(node, Operation(Instruction.VectorExtract, argReg2, source, Const(1)));
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (passOnReg)
|
||||
{
|
||||
Operand argReg = source.Type.IsInteger()
|
||||
? Gpr(CallingConvention.GetIntArgumentRegister(intCount++), source.Type)
|
||||
: Xmm(CallingConvention.GetVecArgumentRegister(vecCount++), source.Type);
|
||||
|
||||
Operation copyOp = Operation(Instruction.Copy, argReg, source);
|
||||
|
||||
HandleConstantRegCopy(nodes, nodes.AddBefore(node, copyOp));
|
||||
|
||||
sources.Add(argReg);
|
||||
}
|
||||
else
|
||||
{
|
||||
Operand offset = Const(stackOffset);
|
||||
|
||||
Operation spillOp = Operation(Instruction.SpillArg, default, offset, source);
|
||||
|
||||
HandleConstantRegCopy(nodes, nodes.AddBefore(node, spillOp));
|
||||
|
||||
stackOffset += source.Type.GetSizeInBytes();
|
||||
}
|
||||
}
|
||||
|
||||
node.SetSources(sources.ToArray());
|
||||
|
||||
if (dest != default)
|
||||
{
|
||||
if (dest.Type == OperandType.V128)
|
||||
{
|
||||
Operand retLReg = Gpr(CallingConvention.GetIntReturnRegister(), OperandType.I64);
|
||||
Operand retHReg = Gpr(CallingConvention.GetIntReturnRegisterHigh(), OperandType.I64);
|
||||
|
||||
Operation operation = node;
|
||||
|
||||
node = nodes.AddAfter(node, Operation(Instruction.VectorCreateScalar, dest, retLReg));
|
||||
nodes.AddAfter(node, Operation(Instruction.VectorInsert, dest, dest, retHReg, Const(1)));
|
||||
|
||||
operation.Destination = default;
|
||||
}
|
||||
else
|
||||
{
|
||||
Operand retReg = dest.Type.IsInteger()
|
||||
? Gpr(CallingConvention.GetIntReturnRegister(), dest.Type)
|
||||
: Xmm(CallingConvention.GetVecReturnRegister(), dest.Type);
|
||||
|
||||
Operation copyOp = Operation(Instruction.Copy, dest, retReg);
|
||||
|
||||
nodes.AddAfter(node, copyOp);
|
||||
|
||||
node.Destination = retReg;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void HandleTailcallSystemVAbi(IntrusiveList<Operation> nodes, StackAllocator stackAlloc, Operation node)
|
||||
{
|
||||
List<Operand> sources = new List<Operand>
|
||||
{
|
||||
node.GetSource(0)
|
||||
};
|
||||
|
||||
int argsCount = node.SourcesCount - 1;
|
||||
|
||||
int intMax = CallingConvention.GetIntArgumentsOnRegsCount();
|
||||
int vecMax = CallingConvention.GetVecArgumentsOnRegsCount();
|
||||
|
||||
int intCount = 0;
|
||||
int vecCount = 0;
|
||||
|
||||
// Handle arguments passed on registers.
|
||||
for (int index = 0; index < argsCount; index++)
|
||||
{
|
||||
Operand source = node.GetSource(1 + index);
|
||||
|
||||
bool passOnReg;
|
||||
|
||||
if (source.Type.IsInteger())
|
||||
{
|
||||
passOnReg = intCount + 1 < intMax;
|
||||
}
|
||||
else
|
||||
{
|
||||
passOnReg = vecCount < vecMax;
|
||||
}
|
||||
|
||||
if (source.Type == OperandType.V128 && passOnReg)
|
||||
{
|
||||
// V128 is a struct, we pass each half on a GPR if possible.
|
||||
Operand argReg = Gpr(CallingConvention.GetIntArgumentRegister(intCount++), OperandType.I64);
|
||||
Operand argReg2 = Gpr(CallingConvention.GetIntArgumentRegister(intCount++), OperandType.I64);
|
||||
|
||||
nodes.AddBefore(node, Operation(Instruction.VectorExtract, argReg, source, Const(0)));
|
||||
nodes.AddBefore(node, Operation(Instruction.VectorExtract, argReg2, source, Const(1)));
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (passOnReg)
|
||||
{
|
||||
Operand argReg = source.Type.IsInteger()
|
||||
? Gpr(CallingConvention.GetIntArgumentRegister(intCount++), source.Type)
|
||||
: Xmm(CallingConvention.GetVecArgumentRegister(vecCount++), source.Type);
|
||||
|
||||
Operation copyOp = Operation(Instruction.Copy, argReg, source);
|
||||
|
||||
HandleConstantRegCopy(nodes, nodes.AddBefore(node, copyOp));
|
||||
|
||||
sources.Add(argReg);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotImplementedException("Spilling is not currently supported for tail calls. (too many arguments)");
|
||||
}
|
||||
}
|
||||
|
||||
// The target address must be on the return registers, since we
|
||||
// don't return anything and it is guaranteed to not be a
|
||||
// callee saved register (which would be trashed on the epilogue).
|
||||
Operand retReg = Gpr(CallingConvention.GetIntReturnRegister(), OperandType.I64);
|
||||
|
||||
Operation addrCopyOp = Operation(Instruction.Copy, retReg, node.GetSource(0));
|
||||
|
||||
nodes.AddBefore(node, addrCopyOp);
|
||||
|
||||
sources[0] = retReg;
|
||||
|
||||
node.SetSources(sources.ToArray());
|
||||
}
|
||||
|
||||
private static void HandleTailcallWindowsAbi(IntrusiveList<Operation> nodes, StackAllocator stackAlloc, Operation node)
|
||||
{
|
||||
int argsCount = node.SourcesCount - 1;
|
||||
int maxArgs = CallingConvention.GetArgumentsOnRegsCount();
|
||||
|
||||
if (argsCount > maxArgs)
|
||||
{
|
||||
throw new NotImplementedException("Spilling is not currently supported for tail calls. (too many arguments)");
|
||||
}
|
||||
|
||||
Operand[] sources = new Operand[1 + argsCount];
|
||||
|
||||
// Handle arguments passed on registers.
|
||||
for (int index = 0; index < argsCount; index++)
|
||||
{
|
||||
Operand source = node.GetSource(1 + index);
|
||||
Operand argReg = source.Type.IsInteger()
|
||||
? Gpr(CallingConvention.GetIntArgumentRegister(index), source.Type)
|
||||
: Xmm(CallingConvention.GetVecArgumentRegister(index), source.Type);
|
||||
|
||||
Operation copyOp = Operation(Instruction.Copy, argReg, source);
|
||||
|
||||
HandleConstantRegCopy(nodes, nodes.AddBefore(node, copyOp));
|
||||
|
||||
sources[1 + index] = argReg;
|
||||
}
|
||||
|
||||
// The target address must be on the return registers, since we
|
||||
// don't return anything and it is guaranteed to not be a
|
||||
// callee saved register (which would be trashed on the epilogue).
|
||||
Operand retReg = Gpr(CallingConvention.GetIntReturnRegister(), OperandType.I64);
|
||||
|
||||
Operation addrCopyOp = Operation(Instruction.Copy, retReg, node.GetSource(0));
|
||||
|
||||
nodes.AddBefore(node, addrCopyOp);
|
||||
|
||||
sources[0] = retReg;
|
||||
|
||||
node.SetSources(sources);
|
||||
}
|
||||
|
||||
private static Operation HandleLoadArgumentWindowsAbi(
|
||||
CompilerContext cctx,
|
||||
IntrusiveList<Operation> nodes,
|
||||
Operand[] preservedArgs,
|
||||
Operation node)
|
||||
{
|
||||
Operand source = node.GetSource(0);
|
||||
|
||||
Debug.Assert(source.Kind == OperandKind.Constant, "Non-constant LoadArgument source kind.");
|
||||
|
||||
int retArgs = cctx.FuncReturnType == OperandType.V128 ? 1 : 0;
|
||||
|
||||
int index = source.AsInt32() + retArgs;
|
||||
|
||||
if (index < CallingConvention.GetArgumentsOnRegsCount())
|
||||
{
|
||||
Operand dest = node.Destination;
|
||||
|
||||
if (preservedArgs[index] == default)
|
||||
{
|
||||
Operand argReg, pArg;
|
||||
|
||||
if (dest.Type.IsInteger())
|
||||
{
|
||||
argReg = Gpr(CallingConvention.GetIntArgumentRegister(index), dest.Type);
|
||||
pArg = Local(dest.Type);
|
||||
}
|
||||
else if (dest.Type == OperandType.V128)
|
||||
{
|
||||
argReg = Gpr(CallingConvention.GetIntArgumentRegister(index), OperandType.I64);
|
||||
pArg = Local(OperandType.I64);
|
||||
}
|
||||
else
|
||||
{
|
||||
argReg = Xmm(CallingConvention.GetVecArgumentRegister(index), dest.Type);
|
||||
pArg = Local(dest.Type);
|
||||
}
|
||||
|
||||
Operation copyOp = Operation(Instruction.Copy, pArg, argReg);
|
||||
|
||||
cctx.Cfg.Entry.Operations.AddFirst(copyOp);
|
||||
|
||||
preservedArgs[index] = pArg;
|
||||
}
|
||||
|
||||
Operation argCopyOp = Operation(dest.Type == OperandType.V128
|
||||
? Instruction.Load
|
||||
: Instruction.Copy, dest, preservedArgs[index]);
|
||||
|
||||
Operation newNode = nodes.AddBefore(node, argCopyOp);
|
||||
|
||||
Delete(nodes, node);
|
||||
|
||||
return newNode;
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: Pass on stack.
|
||||
return node;
|
||||
}
|
||||
}
|
||||
|
||||
private static Operation HandleLoadArgumentSystemVAbi(
|
||||
CompilerContext cctx,
|
||||
IntrusiveList<Operation> nodes,
|
||||
Operand[] preservedArgs,
|
||||
Operation node)
|
||||
{
|
||||
Operand source = node.GetSource(0);
|
||||
|
||||
Debug.Assert(source.Kind == OperandKind.Constant, "Non-constant LoadArgument source kind.");
|
||||
|
||||
int index = source.AsInt32();
|
||||
|
||||
int intCount = 0;
|
||||
int vecCount = 0;
|
||||
|
||||
for (int cIndex = 0; cIndex < index; cIndex++)
|
||||
{
|
||||
OperandType argType = cctx.FuncArgTypes[cIndex];
|
||||
|
||||
if (argType.IsInteger())
|
||||
{
|
||||
intCount++;
|
||||
}
|
||||
else if (argType == OperandType.V128)
|
||||
{
|
||||
intCount += 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
vecCount++;
|
||||
}
|
||||
}
|
||||
|
||||
bool passOnReg;
|
||||
|
||||
if (source.Type.IsInteger())
|
||||
{
|
||||
passOnReg = intCount < CallingConvention.GetIntArgumentsOnRegsCount();
|
||||
}
|
||||
else if (source.Type == OperandType.V128)
|
||||
{
|
||||
passOnReg = intCount + 1 < CallingConvention.GetIntArgumentsOnRegsCount();
|
||||
}
|
||||
else
|
||||
{
|
||||
passOnReg = vecCount < CallingConvention.GetVecArgumentsOnRegsCount();
|
||||
}
|
||||
|
||||
if (passOnReg)
|
||||
{
|
||||
Operand dest = node.Destination;
|
||||
|
||||
if (preservedArgs[index] == default)
|
||||
{
|
||||
if (dest.Type == OperandType.V128)
|
||||
{
|
||||
// V128 is a struct, we pass each half on a GPR if possible.
|
||||
Operand pArg = Local(OperandType.V128);
|
||||
|
||||
Operand argLReg = Gpr(CallingConvention.GetIntArgumentRegister(intCount), OperandType.I64);
|
||||
Operand argHReg = Gpr(CallingConvention.GetIntArgumentRegister(intCount + 1), OperandType.I64);
|
||||
|
||||
Operation copyL = Operation(Instruction.VectorCreateScalar, pArg, argLReg);
|
||||
Operation copyH = Operation(Instruction.VectorInsert, pArg, pArg, argHReg, Const(1));
|
||||
|
||||
cctx.Cfg.Entry.Operations.AddFirst(copyH);
|
||||
cctx.Cfg.Entry.Operations.AddFirst(copyL);
|
||||
|
||||
preservedArgs[index] = pArg;
|
||||
}
|
||||
else
|
||||
{
|
||||
Operand pArg = Local(dest.Type);
|
||||
|
||||
Operand argReg = dest.Type.IsInteger()
|
||||
? Gpr(CallingConvention.GetIntArgumentRegister(intCount), dest.Type)
|
||||
: Xmm(CallingConvention.GetVecArgumentRegister(vecCount), dest.Type);
|
||||
|
||||
Operation copyOp = Operation(Instruction.Copy, pArg, argReg);
|
||||
|
||||
cctx.Cfg.Entry.Operations.AddFirst(copyOp);
|
||||
|
||||
preservedArgs[index] = pArg;
|
||||
}
|
||||
}
|
||||
|
||||
Operation argCopyOp = Operation(Instruction.Copy, dest, preservedArgs[index]);
|
||||
|
||||
Operation newNode = nodes.AddBefore(node, argCopyOp);
|
||||
|
||||
Delete(nodes, node);
|
||||
|
||||
return newNode;
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: Pass on stack.
|
||||
return node;
|
||||
}
|
||||
}
|
||||
|
||||
private static void HandleReturnWindowsAbi(
|
||||
CompilerContext cctx,
|
||||
IntrusiveList<Operation> nodes,
|
||||
Operand[] preservedArgs,
|
||||
Operation node)
|
||||
{
|
||||
if (node.SourcesCount == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Operand source = node.GetSource(0);
|
||||
Operand retReg;
|
||||
|
||||
if (source.Type.IsInteger())
|
||||
{
|
||||
retReg = Gpr(CallingConvention.GetIntReturnRegister(), source.Type);
|
||||
}
|
||||
else if (source.Type == OperandType.V128)
|
||||
{
|
||||
if (preservedArgs[0] == default)
|
||||
{
|
||||
Operand preservedArg = Local(OperandType.I64);
|
||||
Operand arg0 = Gpr(CallingConvention.GetIntArgumentRegister(0), OperandType.I64);
|
||||
|
||||
Operation copyOp = Operation(Instruction.Copy, preservedArg, arg0);
|
||||
|
||||
cctx.Cfg.Entry.Operations.AddFirst(copyOp);
|
||||
|
||||
preservedArgs[0] = preservedArg;
|
||||
}
|
||||
|
||||
retReg = preservedArgs[0];
|
||||
}
|
||||
else
|
||||
{
|
||||
retReg = Xmm(CallingConvention.GetVecReturnRegister(), source.Type);
|
||||
}
|
||||
|
||||
if (source.Type == OperandType.V128)
|
||||
{
|
||||
Operation retStoreOp = Operation(Instruction.Store, default, retReg, source);
|
||||
|
||||
nodes.AddBefore(node, retStoreOp);
|
||||
}
|
||||
else
|
||||
{
|
||||
Operation retCopyOp = Operation(Instruction.Copy, retReg, source);
|
||||
|
||||
nodes.AddBefore(node, retCopyOp);
|
||||
}
|
||||
|
||||
node.SetSources(Array.Empty<Operand>());
|
||||
}
|
||||
|
||||
private static void HandleReturnSystemVAbi(IntrusiveList<Operation> nodes, Operation node)
|
||||
{
|
||||
if (node.SourcesCount == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Operand source = node.GetSource(0);
|
||||
|
||||
if (source.Type == OperandType.V128)
|
||||
{
|
||||
Operand retLReg = Gpr(CallingConvention.GetIntReturnRegister(), OperandType.I64);
|
||||
Operand retHReg = Gpr(CallingConvention.GetIntReturnRegisterHigh(), OperandType.I64);
|
||||
|
||||
nodes.AddBefore(node, Operation(Instruction.VectorExtract, retLReg, source, Const(0)));
|
||||
nodes.AddBefore(node, Operation(Instruction.VectorExtract, retHReg, source, Const(1)));
|
||||
}
|
||||
else
|
||||
{
|
||||
Operand retReg = source.Type.IsInteger()
|
||||
? Gpr(CallingConvention.GetIntReturnRegister(), source.Type)
|
||||
: Xmm(CallingConvention.GetVecReturnRegister(), source.Type);
|
||||
|
||||
Operation retCopyOp = Operation(Instruction.Copy, retReg, source);
|
||||
|
||||
nodes.AddBefore(node, retCopyOp);
|
||||
}
|
||||
}
|
||||
|
||||
private static Operand AddXmmCopy(IntrusiveList<Operation> nodes, Operation node, Operand source)
|
||||
protected static Operand AddXmmCopy(IntrusiveList<Operation> nodes, Operation node, Operand source)
|
||||
{
|
||||
Operand temp = Local(source.Type);
|
||||
Operand intConst = AddCopy(nodes, node, GetIntConst(source));
|
||||
@@ -1204,7 +592,7 @@ namespace ARMeilleure.CodeGen.X86
|
||||
return temp;
|
||||
}
|
||||
|
||||
private static Operand AddCopy(IntrusiveList<Operation> nodes, Operation node, Operand source)
|
||||
protected static Operand AddCopy(IntrusiveList<Operation> nodes, Operation node, Operand source)
|
||||
{
|
||||
Operand temp = Local(source.Type);
|
||||
|
||||
@@ -1229,7 +617,7 @@ namespace ARMeilleure.CodeGen.X86
|
||||
return value;
|
||||
}
|
||||
|
||||
private static void Delete(IntrusiveList<Operation> nodes, Operation node)
|
||||
protected static void Delete(IntrusiveList<Operation> nodes, Operation node)
|
||||
{
|
||||
node.Destination = default;
|
||||
|
||||
@@ -1241,12 +629,12 @@ namespace ARMeilleure.CodeGen.X86
|
||||
nodes.Remove(node);
|
||||
}
|
||||
|
||||
private static Operand Gpr(X86Register register, OperandType type)
|
||||
protected static Operand Gpr(X86Register register, OperandType type)
|
||||
{
|
||||
return Register((int)register, RegisterType.Integer, type);
|
||||
}
|
||||
|
||||
private static Operand Xmm(X86Register register, OperandType type)
|
||||
protected static Operand Xmm(X86Register register, OperandType type)
|
||||
{
|
||||
return Register((int)register, RegisterType.Vector, type);
|
||||
}
|
||||
|
334
ARMeilleure/CodeGen/X86/PreAllocatorSystemV.cs
Normal file
334
ARMeilleure/CodeGen/X86/PreAllocatorSystemV.cs
Normal file
@@ -0,0 +1,334 @@
|
||||
using ARMeilleure.CodeGen.RegisterAllocators;
|
||||
using ARMeilleure.IntermediateRepresentation;
|
||||
using ARMeilleure.Translation;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using static ARMeilleure.IntermediateRepresentation.Operand.Factory;
|
||||
using static ARMeilleure.IntermediateRepresentation.Operation.Factory;
|
||||
|
||||
namespace ARMeilleure.CodeGen.X86
|
||||
{
|
||||
class PreAllocatorSystemV : PreAllocator
|
||||
{
|
||||
public static void InsertCallCopies(IntrusiveList<Operation> nodes, Operation node)
|
||||
{
|
||||
Operand dest = node.Destination;
|
||||
|
||||
List<Operand> sources = new List<Operand>
|
||||
{
|
||||
node.GetSource(0)
|
||||
};
|
||||
|
||||
int argsCount = node.SourcesCount - 1;
|
||||
|
||||
int intMax = CallingConvention.GetIntArgumentsOnRegsCount();
|
||||
int vecMax = CallingConvention.GetVecArgumentsOnRegsCount();
|
||||
|
||||
int intCount = 0;
|
||||
int vecCount = 0;
|
||||
|
||||
int stackOffset = 0;
|
||||
|
||||
for (int index = 0; index < argsCount; index++)
|
||||
{
|
||||
Operand source = node.GetSource(index + 1);
|
||||
|
||||
bool passOnReg;
|
||||
|
||||
if (source.Type.IsInteger())
|
||||
{
|
||||
passOnReg = intCount < intMax;
|
||||
}
|
||||
else if (source.Type == OperandType.V128)
|
||||
{
|
||||
passOnReg = intCount + 1 < intMax;
|
||||
}
|
||||
else
|
||||
{
|
||||
passOnReg = vecCount < vecMax;
|
||||
}
|
||||
|
||||
if (source.Type == OperandType.V128 && passOnReg)
|
||||
{
|
||||
// V128 is a struct, we pass each half on a GPR if possible.
|
||||
Operand argReg = Gpr(CallingConvention.GetIntArgumentRegister(intCount++), OperandType.I64);
|
||||
Operand argReg2 = Gpr(CallingConvention.GetIntArgumentRegister(intCount++), OperandType.I64);
|
||||
|
||||
nodes.AddBefore(node, Operation(Instruction.VectorExtract, argReg, source, Const(0)));
|
||||
nodes.AddBefore(node, Operation(Instruction.VectorExtract, argReg2, source, Const(1)));
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (passOnReg)
|
||||
{
|
||||
Operand argReg = source.Type.IsInteger()
|
||||
? Gpr(CallingConvention.GetIntArgumentRegister(intCount++), source.Type)
|
||||
: Xmm(CallingConvention.GetVecArgumentRegister(vecCount++), source.Type);
|
||||
|
||||
Operation copyOp = Operation(Instruction.Copy, argReg, source);
|
||||
|
||||
InsertConstantRegCopies(nodes, nodes.AddBefore(node, copyOp));
|
||||
|
||||
sources.Add(argReg);
|
||||
}
|
||||
else
|
||||
{
|
||||
Operand offset = Const(stackOffset);
|
||||
|
||||
Operation spillOp = Operation(Instruction.SpillArg, default, offset, source);
|
||||
|
||||
InsertConstantRegCopies(nodes, nodes.AddBefore(node, spillOp));
|
||||
|
||||
stackOffset += source.Type.GetSizeInBytes();
|
||||
}
|
||||
}
|
||||
|
||||
node.SetSources(sources.ToArray());
|
||||
|
||||
if (dest != default)
|
||||
{
|
||||
if (dest.Type == OperandType.V128)
|
||||
{
|
||||
Operand retLReg = Gpr(CallingConvention.GetIntReturnRegister(), OperandType.I64);
|
||||
Operand retHReg = Gpr(CallingConvention.GetIntReturnRegisterHigh(), OperandType.I64);
|
||||
|
||||
Operation operation = node;
|
||||
|
||||
node = nodes.AddAfter(node, Operation(Instruction.VectorCreateScalar, dest, retLReg));
|
||||
nodes.AddAfter(node, Operation(Instruction.VectorInsert, dest, dest, retHReg, Const(1)));
|
||||
|
||||
operation.Destination = default;
|
||||
}
|
||||
else
|
||||
{
|
||||
Operand retReg = dest.Type.IsInteger()
|
||||
? Gpr(CallingConvention.GetIntReturnRegister(), dest.Type)
|
||||
: Xmm(CallingConvention.GetVecReturnRegister(), dest.Type);
|
||||
|
||||
Operation copyOp = Operation(Instruction.Copy, dest, retReg);
|
||||
|
||||
nodes.AddAfter(node, copyOp);
|
||||
|
||||
node.Destination = retReg;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void InsertTailcallCopies(IntrusiveList<Operation> nodes, StackAllocator stackAlloc, Operation node)
|
||||
{
|
||||
List<Operand> sources = new List<Operand>
|
||||
{
|
||||
node.GetSource(0)
|
||||
};
|
||||
|
||||
int argsCount = node.SourcesCount - 1;
|
||||
|
||||
int intMax = CallingConvention.GetIntArgumentsOnRegsCount();
|
||||
int vecMax = CallingConvention.GetVecArgumentsOnRegsCount();
|
||||
|
||||
int intCount = 0;
|
||||
int vecCount = 0;
|
||||
|
||||
// Handle arguments passed on registers.
|
||||
for (int index = 0; index < argsCount; index++)
|
||||
{
|
||||
Operand source = node.GetSource(1 + index);
|
||||
|
||||
bool passOnReg;
|
||||
|
||||
if (source.Type.IsInteger())
|
||||
{
|
||||
passOnReg = intCount + 1 < intMax;
|
||||
}
|
||||
else
|
||||
{
|
||||
passOnReg = vecCount < vecMax;
|
||||
}
|
||||
|
||||
if (source.Type == OperandType.V128 && passOnReg)
|
||||
{
|
||||
// V128 is a struct, we pass each half on a GPR if possible.
|
||||
Operand argReg = Gpr(CallingConvention.GetIntArgumentRegister(intCount++), OperandType.I64);
|
||||
Operand argReg2 = Gpr(CallingConvention.GetIntArgumentRegister(intCount++), OperandType.I64);
|
||||
|
||||
nodes.AddBefore(node, Operation(Instruction.VectorExtract, argReg, source, Const(0)));
|
||||
nodes.AddBefore(node, Operation(Instruction.VectorExtract, argReg2, source, Const(1)));
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (passOnReg)
|
||||
{
|
||||
Operand argReg = source.Type.IsInteger()
|
||||
? Gpr(CallingConvention.GetIntArgumentRegister(intCount++), source.Type)
|
||||
: Xmm(CallingConvention.GetVecArgumentRegister(vecCount++), source.Type);
|
||||
|
||||
Operation copyOp = Operation(Instruction.Copy, argReg, source);
|
||||
|
||||
InsertConstantRegCopies(nodes, nodes.AddBefore(node, copyOp));
|
||||
|
||||
sources.Add(argReg);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotImplementedException("Spilling is not currently supported for tail calls. (too many arguments)");
|
||||
}
|
||||
}
|
||||
|
||||
// The target address must be on the return registers, since we
|
||||
// don't return anything and it is guaranteed to not be a
|
||||
// callee saved register (which would be trashed on the epilogue).
|
||||
Operand retReg = Gpr(CallingConvention.GetIntReturnRegister(), OperandType.I64);
|
||||
|
||||
Operation addrCopyOp = Operation(Instruction.Copy, retReg, node.GetSource(0));
|
||||
|
||||
nodes.AddBefore(node, addrCopyOp);
|
||||
|
||||
sources[0] = retReg;
|
||||
|
||||
node.SetSources(sources.ToArray());
|
||||
}
|
||||
|
||||
public static Operation InsertLoadArgumentCopy(
|
||||
CompilerContext cctx,
|
||||
ref Span<Operation> buffer,
|
||||
IntrusiveList<Operation> nodes,
|
||||
Operand[] preservedArgs,
|
||||
Operation node)
|
||||
{
|
||||
Operand source = node.GetSource(0);
|
||||
|
||||
Debug.Assert(source.Kind == OperandKind.Constant, "Non-constant LoadArgument source kind.");
|
||||
|
||||
int index = source.AsInt32();
|
||||
|
||||
int intCount = 0;
|
||||
int vecCount = 0;
|
||||
|
||||
for (int cIndex = 0; cIndex < index; cIndex++)
|
||||
{
|
||||
OperandType argType = cctx.FuncArgTypes[cIndex];
|
||||
|
||||
if (argType.IsInteger())
|
||||
{
|
||||
intCount++;
|
||||
}
|
||||
else if (argType == OperandType.V128)
|
||||
{
|
||||
intCount += 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
vecCount++;
|
||||
}
|
||||
}
|
||||
|
||||
bool passOnReg;
|
||||
|
||||
if (source.Type.IsInteger())
|
||||
{
|
||||
passOnReg = intCount < CallingConvention.GetIntArgumentsOnRegsCount();
|
||||
}
|
||||
else if (source.Type == OperandType.V128)
|
||||
{
|
||||
passOnReg = intCount + 1 < CallingConvention.GetIntArgumentsOnRegsCount();
|
||||
}
|
||||
else
|
||||
{
|
||||
passOnReg = vecCount < CallingConvention.GetVecArgumentsOnRegsCount();
|
||||
}
|
||||
|
||||
if (passOnReg)
|
||||
{
|
||||
Operand dest = node.Destination;
|
||||
|
||||
if (preservedArgs[index] == default)
|
||||
{
|
||||
if (dest.Type == OperandType.V128)
|
||||
{
|
||||
// V128 is a struct, we pass each half on a GPR if possible.
|
||||
Operand pArg = Local(OperandType.V128);
|
||||
|
||||
Operand argLReg = Gpr(CallingConvention.GetIntArgumentRegister(intCount), OperandType.I64);
|
||||
Operand argHReg = Gpr(CallingConvention.GetIntArgumentRegister(intCount + 1), OperandType.I64);
|
||||
|
||||
Operation copyL = Operation(Instruction.VectorCreateScalar, pArg, argLReg);
|
||||
Operation copyH = Operation(Instruction.VectorInsert, pArg, pArg, argHReg, Const(1));
|
||||
|
||||
cctx.Cfg.Entry.Operations.AddFirst(copyH);
|
||||
cctx.Cfg.Entry.Operations.AddFirst(copyL);
|
||||
|
||||
preservedArgs[index] = pArg;
|
||||
}
|
||||
else
|
||||
{
|
||||
Operand pArg = Local(dest.Type);
|
||||
|
||||
Operand argReg = dest.Type.IsInteger()
|
||||
? Gpr(CallingConvention.GetIntArgumentRegister(intCount), dest.Type)
|
||||
: Xmm(CallingConvention.GetVecArgumentRegister(vecCount), dest.Type);
|
||||
|
||||
Operation copyOp = Operation(Instruction.Copy, pArg, argReg);
|
||||
|
||||
cctx.Cfg.Entry.Operations.AddFirst(copyOp);
|
||||
|
||||
preservedArgs[index] = pArg;
|
||||
}
|
||||
}
|
||||
|
||||
Operation nextNode;
|
||||
|
||||
if (dest.AssignmentsCount == 1)
|
||||
{
|
||||
// Let's propagate the argument if we can to avoid copies.
|
||||
PreAllocatorCommon.Propagate(ref buffer, dest, preservedArgs[index]);
|
||||
nextNode = node.ListNext;
|
||||
}
|
||||
else
|
||||
{
|
||||
Operation argCopyOp = Operation(Instruction.Copy, dest, preservedArgs[index]);
|
||||
nextNode = nodes.AddBefore(node, argCopyOp);
|
||||
}
|
||||
|
||||
Delete(nodes, node);
|
||||
return nextNode;
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: Pass on stack.
|
||||
return node;
|
||||
}
|
||||
}
|
||||
|
||||
public static void InsertReturnCopy(IntrusiveList<Operation> nodes, Operation node)
|
||||
{
|
||||
if (node.SourcesCount == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Operand source = node.GetSource(0);
|
||||
|
||||
if (source.Type == OperandType.V128)
|
||||
{
|
||||
Operand retLReg = Gpr(CallingConvention.GetIntReturnRegister(), OperandType.I64);
|
||||
Operand retHReg = Gpr(CallingConvention.GetIntReturnRegisterHigh(), OperandType.I64);
|
||||
|
||||
nodes.AddBefore(node, Operation(Instruction.VectorExtract, retLReg, source, Const(0)));
|
||||
nodes.AddBefore(node, Operation(Instruction.VectorExtract, retHReg, source, Const(1)));
|
||||
}
|
||||
else
|
||||
{
|
||||
Operand retReg = source.Type.IsInteger()
|
||||
? Gpr(CallingConvention.GetIntReturnRegister(), source.Type)
|
||||
: Xmm(CallingConvention.GetVecReturnRegister(), source.Type);
|
||||
|
||||
Operation retCopyOp = Operation(Instruction.Copy, retReg, source);
|
||||
|
||||
nodes.AddBefore(node, retCopyOp);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
327
ARMeilleure/CodeGen/X86/PreAllocatorWindows.cs
Normal file
327
ARMeilleure/CodeGen/X86/PreAllocatorWindows.cs
Normal file
@@ -0,0 +1,327 @@
|
||||
using ARMeilleure.CodeGen.RegisterAllocators;
|
||||
using ARMeilleure.IntermediateRepresentation;
|
||||
using ARMeilleure.Translation;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using static ARMeilleure.IntermediateRepresentation.Operand.Factory;
|
||||
using static ARMeilleure.IntermediateRepresentation.Operation.Factory;
|
||||
|
||||
namespace ARMeilleure.CodeGen.X86
|
||||
{
|
||||
class PreAllocatorWindows : PreAllocator
|
||||
{
|
||||
public static void InsertCallCopies(IntrusiveList<Operation> nodes, StackAllocator stackAlloc, Operation node)
|
||||
{
|
||||
Operand dest = node.Destination;
|
||||
|
||||
// Handle struct arguments.
|
||||
int retArgs = 0;
|
||||
int stackAllocOffset = 0;
|
||||
|
||||
int AllocateOnStack(int size)
|
||||
{
|
||||
// We assume that the stack allocator is initially empty (TotalSize = 0).
|
||||
// Taking that into account, we can reuse the space allocated for other
|
||||
// calls by keeping track of our own allocated size (stackAllocOffset).
|
||||
// If the space allocated is not big enough, then we just expand it.
|
||||
int offset = stackAllocOffset;
|
||||
|
||||
if (stackAllocOffset + size > stackAlloc.TotalSize)
|
||||
{
|
||||
stackAlloc.Allocate((stackAllocOffset + size) - stackAlloc.TotalSize);
|
||||
}
|
||||
|
||||
stackAllocOffset += size;
|
||||
|
||||
return offset;
|
||||
}
|
||||
|
||||
Operand arg0Reg = default;
|
||||
|
||||
if (dest != default && dest.Type == OperandType.V128)
|
||||
{
|
||||
int stackOffset = AllocateOnStack(dest.Type.GetSizeInBytes());
|
||||
|
||||
arg0Reg = Gpr(CallingConvention.GetIntArgumentRegister(0), OperandType.I64);
|
||||
|
||||
Operation allocOp = Operation(Instruction.StackAlloc, arg0Reg, Const(stackOffset));
|
||||
|
||||
nodes.AddBefore(node, allocOp);
|
||||
|
||||
retArgs = 1;
|
||||
}
|
||||
|
||||
int argsCount = node.SourcesCount - 1;
|
||||
int maxArgs = CallingConvention.GetArgumentsOnRegsCount() - retArgs;
|
||||
|
||||
if (argsCount > maxArgs)
|
||||
{
|
||||
argsCount = maxArgs;
|
||||
}
|
||||
|
||||
Operand[] sources = new Operand[1 + retArgs + argsCount];
|
||||
|
||||
sources[0] = node.GetSource(0);
|
||||
|
||||
if (arg0Reg != default)
|
||||
{
|
||||
sources[1] = arg0Reg;
|
||||
}
|
||||
|
||||
for (int index = 1; index < node.SourcesCount; index++)
|
||||
{
|
||||
Operand source = node.GetSource(index);
|
||||
|
||||
if (source.Type == OperandType.V128)
|
||||
{
|
||||
Operand stackAddr = Local(OperandType.I64);
|
||||
|
||||
int stackOffset = AllocateOnStack(source.Type.GetSizeInBytes());
|
||||
|
||||
nodes.AddBefore(node, Operation(Instruction.StackAlloc, stackAddr, Const(stackOffset)));
|
||||
|
||||
Operation storeOp = Operation(Instruction.Store, default, stackAddr, source);
|
||||
|
||||
InsertConstantRegCopies(nodes, nodes.AddBefore(node, storeOp));
|
||||
|
||||
node.SetSource(index, stackAddr);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle arguments passed on registers.
|
||||
for (int index = 0; index < argsCount; index++)
|
||||
{
|
||||
Operand source = node.GetSource(index + 1);
|
||||
Operand argReg;
|
||||
|
||||
int argIndex = index + retArgs;
|
||||
|
||||
if (source.Type.IsInteger())
|
||||
{
|
||||
argReg = Gpr(CallingConvention.GetIntArgumentRegister(argIndex), source.Type);
|
||||
}
|
||||
else
|
||||
{
|
||||
argReg = Xmm(CallingConvention.GetVecArgumentRegister(argIndex), source.Type);
|
||||
}
|
||||
|
||||
Operation copyOp = Operation(Instruction.Copy, argReg, source);
|
||||
|
||||
InsertConstantRegCopies(nodes, nodes.AddBefore(node, copyOp));
|
||||
|
||||
sources[1 + retArgs + index] = argReg;
|
||||
}
|
||||
|
||||
// The remaining arguments (those that are not passed on registers)
|
||||
// should be passed on the stack, we write them to the stack with "SpillArg".
|
||||
for (int index = argsCount; index < node.SourcesCount - 1; index++)
|
||||
{
|
||||
Operand source = node.GetSource(index + 1);
|
||||
Operand offset = Const((index + retArgs) * 8);
|
||||
|
||||
Operation spillOp = Operation(Instruction.SpillArg, default, offset, source);
|
||||
|
||||
InsertConstantRegCopies(nodes, nodes.AddBefore(node, spillOp));
|
||||
}
|
||||
|
||||
if (dest != default)
|
||||
{
|
||||
if (dest.Type == OperandType.V128)
|
||||
{
|
||||
Operand retValueAddr = Local(OperandType.I64);
|
||||
|
||||
nodes.AddBefore(node, Operation(Instruction.Copy, retValueAddr, arg0Reg));
|
||||
|
||||
Operation loadOp = Operation(Instruction.Load, dest, retValueAddr);
|
||||
|
||||
nodes.AddAfter(node, loadOp);
|
||||
|
||||
node.Destination = default;
|
||||
}
|
||||
else
|
||||
{
|
||||
Operand retReg = dest.Type.IsInteger()
|
||||
? Gpr(CallingConvention.GetIntReturnRegister(), dest.Type)
|
||||
: Xmm(CallingConvention.GetVecReturnRegister(), dest.Type);
|
||||
|
||||
Operation copyOp = Operation(Instruction.Copy, dest, retReg);
|
||||
|
||||
nodes.AddAfter(node, copyOp);
|
||||
|
||||
node.Destination = retReg;
|
||||
}
|
||||
}
|
||||
|
||||
node.SetSources(sources);
|
||||
}
|
||||
|
||||
public static void InsertTailcallCopies(IntrusiveList<Operation> nodes, StackAllocator stackAlloc, Operation node)
|
||||
{
|
||||
int argsCount = node.SourcesCount - 1;
|
||||
int maxArgs = CallingConvention.GetArgumentsOnRegsCount();
|
||||
|
||||
if (argsCount > maxArgs)
|
||||
{
|
||||
throw new NotImplementedException("Spilling is not currently supported for tail calls. (too many arguments)");
|
||||
}
|
||||
|
||||
Operand[] sources = new Operand[1 + argsCount];
|
||||
|
||||
// Handle arguments passed on registers.
|
||||
for (int index = 0; index < argsCount; index++)
|
||||
{
|
||||
Operand source = node.GetSource(1 + index);
|
||||
Operand argReg = source.Type.IsInteger()
|
||||
? Gpr(CallingConvention.GetIntArgumentRegister(index), source.Type)
|
||||
: Xmm(CallingConvention.GetVecArgumentRegister(index), source.Type);
|
||||
|
||||
Operation copyOp = Operation(Instruction.Copy, argReg, source);
|
||||
|
||||
InsertConstantRegCopies(nodes, nodes.AddBefore(node, copyOp));
|
||||
|
||||
sources[1 + index] = argReg;
|
||||
}
|
||||
|
||||
// The target address must be on the return registers, since we
|
||||
// don't return anything and it is guaranteed to not be a
|
||||
// callee saved register (which would be trashed on the epilogue).
|
||||
Operand retReg = Gpr(CallingConvention.GetIntReturnRegister(), OperandType.I64);
|
||||
|
||||
Operation addrCopyOp = Operation(Instruction.Copy, retReg, node.GetSource(0));
|
||||
|
||||
nodes.AddBefore(node, addrCopyOp);
|
||||
|
||||
sources[0] = retReg;
|
||||
|
||||
node.SetSources(sources);
|
||||
}
|
||||
|
||||
public static Operation InsertLoadArgumentCopy(
|
||||
CompilerContext cctx,
|
||||
ref Span<Operation> buffer,
|
||||
IntrusiveList<Operation> nodes,
|
||||
Operand[] preservedArgs,
|
||||
Operation node)
|
||||
{
|
||||
Operand source = node.GetSource(0);
|
||||
|
||||
Debug.Assert(source.Kind == OperandKind.Constant, "Non-constant LoadArgument source kind.");
|
||||
|
||||
int retArgs = cctx.FuncReturnType == OperandType.V128 ? 1 : 0;
|
||||
|
||||
int index = source.AsInt32() + retArgs;
|
||||
|
||||
if (index < CallingConvention.GetArgumentsOnRegsCount())
|
||||
{
|
||||
Operand dest = node.Destination;
|
||||
|
||||
if (preservedArgs[index] == default)
|
||||
{
|
||||
Operand argReg, pArg;
|
||||
|
||||
if (dest.Type.IsInteger())
|
||||
{
|
||||
argReg = Gpr(CallingConvention.GetIntArgumentRegister(index), dest.Type);
|
||||
pArg = Local(dest.Type);
|
||||
}
|
||||
else if (dest.Type == OperandType.V128)
|
||||
{
|
||||
argReg = Gpr(CallingConvention.GetIntArgumentRegister(index), OperandType.I64);
|
||||
pArg = Local(OperandType.I64);
|
||||
}
|
||||
else
|
||||
{
|
||||
argReg = Xmm(CallingConvention.GetVecArgumentRegister(index), dest.Type);
|
||||
pArg = Local(dest.Type);
|
||||
}
|
||||
|
||||
Operation copyOp = Operation(Instruction.Copy, pArg, argReg);
|
||||
|
||||
cctx.Cfg.Entry.Operations.AddFirst(copyOp);
|
||||
|
||||
preservedArgs[index] = pArg;
|
||||
}
|
||||
|
||||
Operation nextNode;
|
||||
|
||||
if (dest.Type != OperandType.V128 && dest.AssignmentsCount == 1)
|
||||
{
|
||||
// Let's propagate the argument if we can to avoid copies.
|
||||
PreAllocatorCommon.Propagate(ref buffer, dest, preservedArgs[index]);
|
||||
nextNode = node.ListNext;
|
||||
}
|
||||
else
|
||||
{
|
||||
Operation argCopyOp = Operation(dest.Type == OperandType.V128
|
||||
? Instruction.Load
|
||||
: Instruction.Copy, dest, preservedArgs[index]);
|
||||
|
||||
nextNode = nodes.AddBefore(node, argCopyOp);
|
||||
}
|
||||
|
||||
Delete(nodes, node);
|
||||
return nextNode;
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: Pass on stack.
|
||||
return node;
|
||||
}
|
||||
}
|
||||
|
||||
public static void InsertReturnCopy(
|
||||
CompilerContext cctx,
|
||||
IntrusiveList<Operation> nodes,
|
||||
Operand[] preservedArgs,
|
||||
Operation node)
|
||||
{
|
||||
if (node.SourcesCount == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Operand source = node.GetSource(0);
|
||||
Operand retReg;
|
||||
|
||||
if (source.Type.IsInteger())
|
||||
{
|
||||
retReg = Gpr(CallingConvention.GetIntReturnRegister(), source.Type);
|
||||
}
|
||||
else if (source.Type == OperandType.V128)
|
||||
{
|
||||
if (preservedArgs[0] == default)
|
||||
{
|
||||
Operand preservedArg = Local(OperandType.I64);
|
||||
Operand arg0 = Gpr(CallingConvention.GetIntArgumentRegister(0), OperandType.I64);
|
||||
|
||||
Operation copyOp = Operation(Instruction.Copy, preservedArg, arg0);
|
||||
|
||||
cctx.Cfg.Entry.Operations.AddFirst(copyOp);
|
||||
|
||||
preservedArgs[0] = preservedArg;
|
||||
}
|
||||
|
||||
retReg = preservedArgs[0];
|
||||
}
|
||||
else
|
||||
{
|
||||
retReg = Xmm(CallingConvention.GetVecReturnRegister(), source.Type);
|
||||
}
|
||||
|
||||
if (source.Type == OperandType.V128)
|
||||
{
|
||||
Operation retStoreOp = Operation(Instruction.Store, default, retReg, source);
|
||||
|
||||
nodes.AddBefore(node, retStoreOp);
|
||||
}
|
||||
else
|
||||
{
|
||||
Operation retCopyOp = Operation(Instruction.Copy, retReg, source);
|
||||
|
||||
nodes.AddBefore(node, retCopyOp);
|
||||
}
|
||||
|
||||
node.SetSources(Array.Empty<Operand>());
|
||||
}
|
||||
}
|
||||
}
|
@@ -17,7 +17,7 @@ namespace ARMeilleure.Decoders
|
||||
{
|
||||
uint[] tbl = new uint[256];
|
||||
|
||||
for (int idx = 0; idx < 256; idx++)
|
||||
for (int idx = 0; idx < tbl.Length; idx++)
|
||||
{
|
||||
tbl[idx] = ExpandImm8ToFP32((uint)idx);
|
||||
}
|
||||
@@ -29,7 +29,7 @@ namespace ARMeilleure.Decoders
|
||||
{
|
||||
ulong[] tbl = new ulong[256];
|
||||
|
||||
for (int idx = 0; idx < 256; idx++)
|
||||
for (int idx = 0; idx < tbl.Length; idx++)
|
||||
{
|
||||
tbl[idx] = ExpandImm8ToFP64((ulong)idx);
|
||||
}
|
||||
|
@@ -1301,7 +1301,7 @@ namespace ARMeilleure.Decoders
|
||||
{
|
||||
List<InstInfo>[] temp = new List<InstInfo>[FastLookupSize];
|
||||
|
||||
for (int index = 0; index < FastLookupSize; index++)
|
||||
for (int index = 0; index < temp.Length; index++)
|
||||
{
|
||||
temp[index] = new List<InstInfo>();
|
||||
}
|
||||
@@ -1311,7 +1311,7 @@ namespace ARMeilleure.Decoders
|
||||
int mask = ToFastLookupIndex(inst.Mask);
|
||||
int value = ToFastLookupIndex(inst.Value);
|
||||
|
||||
for (int index = 0; index < FastLookupSize; index++)
|
||||
for (int index = 0; index < temp.Length; index++)
|
||||
{
|
||||
if ((index & mask) == value)
|
||||
{
|
||||
@@ -1320,7 +1320,7 @@ namespace ARMeilleure.Decoders
|
||||
}
|
||||
}
|
||||
|
||||
for (int index = 0; index < FastLookupSize; index++)
|
||||
for (int index = 0; index < temp.Length; index++)
|
||||
{
|
||||
table[index] = temp[index].ToArray();
|
||||
}
|
||||
|
@@ -1,9 +1,8 @@
|
||||
using ARMeilleure.Decoders;
|
||||
using ARMeilleure.IntermediateRepresentation;
|
||||
using ARMeilleure.Translation;
|
||||
|
||||
using static ARMeilleure.Instructions.InstEmitHelper;
|
||||
using static ARMeilleure.Instructions.InstEmitHashHelper;
|
||||
using static ARMeilleure.Instructions.InstEmitHelper;
|
||||
|
||||
namespace ARMeilleure.Instructions
|
||||
{
|
||||
|
@@ -4,9 +4,8 @@ using ARMeilleure.IntermediateRepresentation;
|
||||
using ARMeilleure.Translation;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
|
||||
using static ARMeilleure.IntermediateRepresentation.Operand.Factory;
|
||||
using static ARMeilleure.Instructions.InstEmitSimdHelper;
|
||||
using static ARMeilleure.IntermediateRepresentation.Operand.Factory;
|
||||
|
||||
namespace ARMeilleure.Instructions
|
||||
{
|
||||
|
@@ -29,7 +29,7 @@ namespace ARMeilleure.Translation.PTC
|
||||
private const string OuterHeaderMagicString = "PTCohd\0\0";
|
||||
private const string InnerHeaderMagicString = "PTCihd\0\0";
|
||||
|
||||
private const uint InternalVersion = 4328; //! To be incremented manually for each change to the ARMeilleure project.
|
||||
private const uint InternalVersion = 4484; //! To be incremented manually for each change to the ARMeilleure project.
|
||||
|
||||
private const string ActualDir = "0";
|
||||
private const string BackupDir = "1";
|
||||
|
@@ -12,24 +12,23 @@
|
||||
<PackageVersion Include="Avalonia.Svg.Skia" Version="0.10.18" />
|
||||
<PackageVersion Include="CommandLineParser" Version="2.9.1" />
|
||||
<PackageVersion Include="Concentus" Version="1.1.7" />
|
||||
<PackageVersion Include="Crc32.NET" Version="1.2.0" />
|
||||
<PackageVersion Include="DiscordRichPresence" Version="1.1.3.18" />
|
||||
<PackageVersion Include="DynamicData" Version="7.12.11" />
|
||||
<PackageVersion Include="FluentAvaloniaUI" Version="1.4.5" />
|
||||
<PackageVersion Include="GtkSharp.Dependencies" Version="1.1.1" />
|
||||
<PackageVersion Include="GtkSharp.Dependencies.osx" Version="0.0.5" />
|
||||
<PackageVersion Include="jp2masa.Avalonia.Flexbox" Version="0.2.0" />
|
||||
<PackageVersion Include="LibHac" Version="0.17.0" />
|
||||
<PackageVersion Include="LibHac" Version="0.18.0" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.4.0" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.5.0" />
|
||||
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.5.0" />
|
||||
<PackageVersion Include="MsgPack.Cli" Version="1.0.1" />
|
||||
<PackageVersion Include="NUnit" Version="3.13.3" />
|
||||
<PackageVersion Include="NUnit3TestAdapter" Version="4.1.0" />
|
||||
<PackageVersion Include="OpenTK.Core" Version="4.7.5" />
|
||||
<PackageVersion Include="OpenTK.Graphics" Version="4.7.5" />
|
||||
<PackageVersion Include="OpenTK.OpenAL" Version="4.7.5" />
|
||||
<PackageVersion Include="OpenTK.Windowing.GraphicsLibraryFramework" Version="4.7.5" />
|
||||
<PackageVersion Include="OpenTK.Core" Version="4.7.7" />
|
||||
<PackageVersion Include="OpenTK.Graphics" Version="4.7.7" />
|
||||
<PackageVersion Include="OpenTK.OpenAL" Version="4.7.7" />
|
||||
<PackageVersion Include="OpenTK.Windowing.GraphicsLibraryFramework" Version="4.7.7" />
|
||||
<PackageVersion Include="Ryujinx.Audio.OpenAL.Dependencies" Version="1.21.0.1" />
|
||||
<PackageVersion Include="Ryujinx.Graphics.Nvdec.Dependencies" Version="5.0.1-build13" />
|
||||
<PackageVersion Include="Ryujinx.Graphics.Vulkan.Dependencies.MoltenVK" Version="1.2.0" />
|
||||
@@ -45,11 +44,9 @@
|
||||
<PackageVersion Include="SPB" Version="0.0.4-build28" />
|
||||
<PackageVersion Include="System.Drawing.Common" Version="7.0.0" />
|
||||
<PackageVersion Include="System.IdentityModel.Tokens.Jwt" Version="6.27.0" />
|
||||
<PackageVersion Include="System.IO.FileSystem.Primitives" Version="4.3.0" />
|
||||
<PackageVersion Include="System.IO.Hashing" Version="7.0.0" />
|
||||
<PackageVersion Include="System.Management" Version="7.0.0" />
|
||||
<PackageVersion Include="System.Net.NameResolution" Version="4.3.0" />
|
||||
<PackageVersion Include="System.Threading.ThreadPool" Version="4.3.0" />
|
||||
<PackageVersion Include="UnicornEngine.Unicorn" Version="2.0.2-rc1-a913199" />
|
||||
<PackageVersion Include="UnicornEngine.Unicorn" Version="2.0.2-rc1-f7c841d" />
|
||||
<PackageVersion Include="XamlNameReferenceGenerator" Version="1.5.1" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
</Project>
|
||||
|
@@ -96,7 +96,7 @@ Ryujinx system files are stored in the `Ryujinx` folder. This folder is located
|
||||
|
||||
- **GPU**
|
||||
|
||||
The GPU emulator emulates the Switch's Maxwell GPU using either the OpenGL (version 4.5 minimum), Vulkan, or Metal (via MoltenVK) APIs through a custom build of OpenTK or Silk.NET respectively. There are currently four graphics enhancements available to the end user in Ryujinx: Disk Shader Caching, Resolution Scaling, Aspect Ratio Adjustment, and Anisotropic Filtering. These enhancements can be adjusted or toggled as desired in the GUI.
|
||||
The GPU emulator emulates the Switch's Maxwell GPU using either the OpenGL (version 4.5 minimum), Vulkan, or Metal (via MoltenVK) APIs through a custom build of OpenTK or Silk.NET respectively. There are currently six graphics enhancements available to the end user in Ryujinx: Disk Shader Caching, Resolution Scaling, Anti-Aliasing, Scaling Filters (including FSR), Anisotropic Filtering and Aspect Ratio Adjustment. These enhancements can be adjusted or toggled as desired in the GUI.
|
||||
|
||||
- **Input**
|
||||
|
||||
|
@@ -5,9 +5,8 @@ using Ryujinx.Memory;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Threading;
|
||||
|
||||
using static Ryujinx.Audio.Integration.IHardwareDeviceDriver;
|
||||
using static Ryujinx.Audio.Backends.SoundIo.Native.SoundIo;
|
||||
using static Ryujinx.Audio.Integration.IHardwareDeviceDriver;
|
||||
|
||||
namespace Ryujinx.Audio.Backends.SoundIo
|
||||
{
|
||||
|
@@ -400,7 +400,9 @@ namespace Ryujinx.Audio.Common
|
||||
{
|
||||
uint bufferIndex = (_releasedBufferIndex - _bufferReleasedCount) % Constants.AudioDeviceBufferCountMax;
|
||||
|
||||
for (int i = 0; i < GetTotalBufferCount(); i++)
|
||||
uint totalBufferCount = GetTotalBufferCount();
|
||||
|
||||
for (int i = 0; i < totalBufferCount; i++)
|
||||
{
|
||||
if (_buffers[bufferIndex].BufferTag == bufferTag)
|
||||
{
|
||||
|
@@ -1,8 +1,8 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using Ryujinx.Audio.Renderer.Dsp.Effect;
|
||||
using Ryujinx.Audio.Renderer.Dsp.Effect;
|
||||
using Ryujinx.Audio.Renderer.Dsp.State;
|
||||
using Ryujinx.Audio.Renderer.Parameter.Effect;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Ryujinx.Audio.Renderer.Dsp.Command
|
||||
{
|
||||
|
@@ -1,7 +1,7 @@
|
||||
using Ryujinx.Audio.Renderer.Common;
|
||||
using Ryujinx.Audio.Renderer.Dsp.State;
|
||||
using Ryujinx.Audio.Renderer.Parameter.Effect;
|
||||
using Ryujinx.Audio.Renderer.Parameter;
|
||||
using Ryujinx.Audio.Renderer.Parameter.Effect;
|
||||
using Ryujinx.Audio.Renderer.Server.MemoryPool;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
|
@@ -171,6 +171,11 @@ namespace Ryujinx.Ava
|
||||
ConfigurationState.Instance.Graphics.AspectRatio.Event += UpdateAspectRatioState;
|
||||
ConfigurationState.Instance.System.EnableDockedMode.Event += UpdateDockedModeState;
|
||||
ConfigurationState.Instance.System.AudioVolume.Event += UpdateAudioVolumeState;
|
||||
ConfigurationState.Instance.System.EnableDockedMode.Event += UpdateDockedModeState;
|
||||
ConfigurationState.Instance.System.AudioVolume.Event += UpdateAudioVolumeState;
|
||||
ConfigurationState.Instance.Graphics.AntiAliasing.Event += UpdateAntiAliasing;
|
||||
ConfigurationState.Instance.Graphics.ScalingFilter.Event += UpdateScalingFilter;
|
||||
ConfigurationState.Instance.Graphics.ScalingFilterLevel.Event += UpdateScalingFilterLevel;
|
||||
|
||||
_gpuCancellationTokenSource = new CancellationTokenSource();
|
||||
}
|
||||
@@ -193,6 +198,17 @@ namespace Ryujinx.Ava
|
||||
}
|
||||
}
|
||||
}
|
||||
private void UpdateScalingFilterLevel(object sender, ReactiveEventArgs<int> e)
|
||||
{
|
||||
_renderer.Window?.SetScalingFilter((Graphics.GAL.ScalingFilter)ConfigurationState.Instance.Graphics.ScalingFilter.Value);
|
||||
_renderer.Window?.SetScalingFilterLevel(ConfigurationState.Instance.Graphics.ScalingFilterLevel.Value);
|
||||
}
|
||||
|
||||
private void UpdateScalingFilter(object sender, ReactiveEventArgs<Ryujinx.Common.Configuration.ScalingFilter> e)
|
||||
{
|
||||
_renderer.Window?.SetScalingFilter((Graphics.GAL.ScalingFilter)ConfigurationState.Instance.Graphics.ScalingFilter.Value);
|
||||
_renderer.Window?.SetScalingFilterLevel(ConfigurationState.Instance.Graphics.ScalingFilterLevel.Value);
|
||||
}
|
||||
|
||||
private void ShowCursor()
|
||||
{
|
||||
@@ -345,6 +361,11 @@ namespace Ryujinx.Ava
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateAntiAliasing(object sender, ReactiveEventArgs<Ryujinx.Common.Configuration.AntiAliasing> e)
|
||||
{
|
||||
_renderer?.Window?.SetAntiAliasing((Graphics.GAL.AntiAliasing)e.NewValue);
|
||||
}
|
||||
|
||||
private void UpdateDockedModeState(object sender, ReactiveEventArgs<bool> e)
|
||||
{
|
||||
Device?.System.ChangeDockedModeState(e.NewValue);
|
||||
@@ -411,6 +432,9 @@ namespace Ryujinx.Ava
|
||||
ConfigurationState.Instance.Graphics.AspectRatio.Event -= UpdateAspectRatioState;
|
||||
ConfigurationState.Instance.System.EnableDockedMode.Event -= UpdateDockedModeState;
|
||||
ConfigurationState.Instance.System.AudioVolume.Event -= UpdateAudioVolumeState;
|
||||
ConfigurationState.Instance.Graphics.ScalingFilter.Event -= UpdateScalingFilter;
|
||||
ConfigurationState.Instance.Graphics.ScalingFilterLevel.Event -= UpdateScalingFilterLevel;
|
||||
ConfigurationState.Instance.Graphics.AntiAliasing.Event -= UpdateAntiAliasing;
|
||||
|
||||
_topLevel.PointerMoved -= TopLevel_PointerMoved;
|
||||
|
||||
@@ -788,6 +812,10 @@ namespace Ryujinx.Ava
|
||||
|
||||
Device.Gpu.Renderer.Initialize(_glLogLevel);
|
||||
|
||||
_renderer?.Window?.SetAntiAliasing((Graphics.GAL.AntiAliasing)ConfigurationState.Instance.Graphics.AntiAliasing.Value);
|
||||
_renderer?.Window?.SetScalingFilter((Graphics.GAL.ScalingFilter)ConfigurationState.Instance.Graphics.ScalingFilter.Value);
|
||||
_renderer?.Window?.SetScalingFilterLevel(ConfigurationState.Instance.Graphics.ScalingFilterLevel.Value);
|
||||
|
||||
Width = (int)_rendererHost.Bounds.Width;
|
||||
Height = (int)_rendererHost.Bounds.Height;
|
||||
|
||||
|
@@ -626,6 +626,16 @@
|
||||
"Recover": "Recover",
|
||||
"UserProfilesRecoverHeading" : "Saves were found for the following accounts",
|
||||
"UserProfilesRecoverEmptyList": "No profiles to recover",
|
||||
"GraphicsAATooltip": "Applies anti-aliasing to the game render",
|
||||
"GraphicsAALabel": "Anti-Aliasing:",
|
||||
"GraphicsScalingFilterLabel": "Scaling Filter:",
|
||||
"GraphicsScalingFilterTooltip": "Enables Framebuffer Scaling",
|
||||
"GraphicsScalingFilterLevelLabel": "Level",
|
||||
"GraphicsScalingFilterLevelTooltip": "Set Scaling Filter Level",
|
||||
"SmaaLow": "SMAA Low",
|
||||
"SmaaMedium": "SMAA Medium",
|
||||
"SmaaHigh": "SMAA High",
|
||||
"SmaaUltra": "SMAA Ultra",
|
||||
"UserEditorTitle" : "Edit User",
|
||||
"UserEditorTitleCreate" : "Create User"
|
||||
}
|
||||
|
@@ -193,7 +193,7 @@ namespace Ryujinx.Ava.Common
|
||||
{
|
||||
using var ncaFile = new UniqueRef<IFile>();
|
||||
|
||||
pfs.OpenFile(ref ncaFile.Ref(), fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
||||
pfs.OpenFile(ref ncaFile.Ref, fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
||||
|
||||
Nca nca = new(_virtualFileSystem.KeySet, ncaFile.Get.AsStorage());
|
||||
if (nca.Header.ContentType == NcaContentType.Program)
|
||||
@@ -249,8 +249,8 @@ namespace Ryujinx.Ava.Common
|
||||
using var uniqueSourceFs = new UniqueRef<IFileSystem>(ncaFileSystem);
|
||||
using var uniqueOutputFs = new UniqueRef<IFileSystem>(new LocalFileSystem(destination));
|
||||
|
||||
fsClient.Register(source.ToU8Span(), ref uniqueSourceFs.Ref());
|
||||
fsClient.Register(output.ToU8Span(), ref uniqueOutputFs.Ref());
|
||||
fsClient.Register(source.ToU8Span(), ref uniqueSourceFs.Ref);
|
||||
fsClient.Register(output.ToU8Span(), ref uniqueOutputFs.Ref);
|
||||
|
||||
(Result? resultCode, bool canceled) = CopyDirectory(fsClient, $"{source}:/", $"{output}:/", cancellationToken.Token);
|
||||
|
||||
|
@@ -21,6 +21,7 @@ using System.Net.Http;
|
||||
using System.Net.NetworkInformation;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.Versioning;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
@@ -57,7 +58,7 @@ namespace Ryujinx.Modules
|
||||
// Detect current platform
|
||||
if (OperatingSystem.IsMacOS())
|
||||
{
|
||||
_platformExt = "osx_x64.zip";
|
||||
_platformExt = "macos_universal.app.tar.gz";
|
||||
}
|
||||
else if (OperatingSystem.IsWindows())
|
||||
{
|
||||
@@ -286,22 +287,40 @@ namespace Ryujinx.Modules
|
||||
|
||||
if (_updateSuccessful)
|
||||
{
|
||||
var shouldRestart = await ContentDialogHelper.CreateChoiceDialog(LocaleManager.Instance[LocaleKeys.RyujinxUpdater],
|
||||
LocaleManager.Instance[LocaleKeys.DialogUpdaterCompleteMessage],
|
||||
LocaleManager.Instance[LocaleKeys.DialogUpdaterRestartMessage]);
|
||||
bool shouldRestart = true;
|
||||
|
||||
if (!OperatingSystem.IsMacOS())
|
||||
{
|
||||
shouldRestart = await ContentDialogHelper.CreateChoiceDialog(LocaleManager.Instance[LocaleKeys.RyujinxUpdater],
|
||||
LocaleManager.Instance[LocaleKeys.DialogUpdaterCompleteMessage],
|
||||
LocaleManager.Instance[LocaleKeys.DialogUpdaterRestartMessage]);
|
||||
}
|
||||
|
||||
if (shouldRestart)
|
||||
{
|
||||
List<string> arguments = CommandLineState.Arguments.ToList();
|
||||
string ryuName = Path.GetFileName(Environment.ProcessPath);
|
||||
string ryuExe = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, ryuName);
|
||||
string executableDirectory = AppDomain.CurrentDomain.BaseDirectory;
|
||||
string executablePath = Path.Combine(executableDirectory, ryuName);
|
||||
|
||||
if (!Path.Exists(ryuExe))
|
||||
if (!Path.Exists(executablePath))
|
||||
{
|
||||
ryuExe = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, OperatingSystem.IsWindows() ? "Ryujinx.exe" : "Ryujinx");
|
||||
executablePath = Path.Combine(executableDirectory, OperatingSystem.IsWindows() ? "Ryujinx.exe" : "Ryujinx");
|
||||
}
|
||||
|
||||
Process.Start(ryuExe, CommandLineState.Arguments);
|
||||
// On macOS we perform the update at relaunch.
|
||||
if (OperatingSystem.IsMacOS())
|
||||
{
|
||||
string baseBundlePath = Path.GetFullPath(Path.Combine(executableDirectory, "..", ".."));
|
||||
string newBundlePath = Path.Combine(UpdateDir, "Ryujinx.app");
|
||||
string updaterScriptPath = Path.Combine(newBundlePath, "Contents", "Resources", "updater.sh");
|
||||
string currentPid = Process.GetCurrentProcess().Id.ToString();
|
||||
|
||||
executablePath = "/bin/bash";
|
||||
arguments.InsertRange(0, new List<string> { updaterScriptPath, baseBundlePath, newBundlePath, currentPid });
|
||||
}
|
||||
|
||||
Process.Start(executablePath, arguments);
|
||||
Environment.Exit(0);
|
||||
}
|
||||
}
|
||||
@@ -381,6 +400,15 @@ namespace Ryujinx.Modules
|
||||
|
||||
File.WriteAllBytes(updateFile, mergedFileBytes);
|
||||
|
||||
// On macOS, ensure that we remove the quarantine bit to prevent Gatekeeper from blocking execution.
|
||||
if (OperatingSystem.IsMacOS())
|
||||
{
|
||||
using (Process xattrProcess = Process.Start("xattr", new List<string> { "-d", "com.apple.quarantine", updateFile }))
|
||||
{
|
||||
xattrProcess.WaitForExit();
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
InstallUpdate(taskDialog, updateFile);
|
||||
@@ -470,87 +498,98 @@ namespace Ryujinx.Modules
|
||||
worker.Start();
|
||||
}
|
||||
|
||||
[SupportedOSPlatform("linux")]
|
||||
[SupportedOSPlatform("macos")]
|
||||
private static void ExtractTarGzipFile(TaskDialog taskDialog, string archivePath, string outputDirectoryPath)
|
||||
{
|
||||
using Stream inStream = File.OpenRead(archivePath);
|
||||
using GZipInputStream gzipStream = new(inStream);
|
||||
using TarInputStream tarStream = new(gzipStream, Encoding.ASCII);
|
||||
|
||||
TarEntry tarEntry;
|
||||
|
||||
while ((tarEntry = tarStream.GetNextEntry()) is not null)
|
||||
{
|
||||
if (tarEntry.IsDirectory)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
string outPath = Path.Combine(outputDirectoryPath, tarEntry.Name);
|
||||
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(outPath));
|
||||
|
||||
using (FileStream outStream = File.OpenWrite(outPath))
|
||||
{
|
||||
tarStream.CopyEntryContents(outStream);
|
||||
}
|
||||
|
||||
File.SetUnixFileMode(outPath, (UnixFileMode)tarEntry.TarHeader.Mode);
|
||||
File.SetLastWriteTime(outPath, DateTime.SpecifyKind(tarEntry.ModTime, DateTimeKind.Utc));
|
||||
|
||||
Dispatcher.UIThread.Post(() =>
|
||||
{
|
||||
if (tarEntry is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
taskDialog.SetProgressBarState(GetPercentage(tarEntry.Size, inStream.Length), TaskDialogProgressState.Normal);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private static void ExtractZipFile(TaskDialog taskDialog, string archivePath, string outputDirectoryPath)
|
||||
{
|
||||
using Stream inStream = File.OpenRead(archivePath);
|
||||
using ZipFile zipFile = new(inStream);
|
||||
|
||||
double count = 0;
|
||||
foreach (ZipEntry zipEntry in zipFile)
|
||||
{
|
||||
count++;
|
||||
if (zipEntry.IsDirectory) continue;
|
||||
|
||||
string outPath = Path.Combine(outputDirectoryPath, zipEntry.Name);
|
||||
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(outPath));
|
||||
|
||||
using (Stream zipStream = zipFile.GetInputStream(zipEntry))
|
||||
using (FileStream outStream = File.OpenWrite(outPath))
|
||||
{
|
||||
zipStream.CopyTo(outStream);
|
||||
}
|
||||
|
||||
File.SetLastWriteTime(outPath, DateTime.SpecifyKind(zipEntry.DateTime, DateTimeKind.Utc));
|
||||
|
||||
Dispatcher.UIThread.Post(() =>
|
||||
{
|
||||
taskDialog.SetProgressBarState(GetPercentage(count, zipFile.Count), TaskDialogProgressState.Normal);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private static async void InstallUpdate(TaskDialog taskDialog, string updateFile)
|
||||
{
|
||||
// Extract Update
|
||||
taskDialog.SubHeader = LocaleManager.Instance[LocaleKeys.UpdaterExtracting];
|
||||
taskDialog.SetProgressBarState(0, TaskDialogProgressState.Normal);
|
||||
|
||||
if (OperatingSystem.IsLinux())
|
||||
await Task.Run(() =>
|
||||
{
|
||||
using Stream inStream = File.OpenRead(updateFile);
|
||||
using GZipInputStream gzipStream = new(inStream);
|
||||
using TarInputStream tarStream = new(gzipStream, Encoding.ASCII);
|
||||
|
||||
await Task.Run(() =>
|
||||
if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
|
||||
{
|
||||
TarEntry tarEntry;
|
||||
|
||||
if (!OperatingSystem.IsWindows())
|
||||
{
|
||||
while ((tarEntry = tarStream.GetNextEntry()) is not null)
|
||||
{
|
||||
if (tarEntry.IsDirectory) continue;
|
||||
|
||||
string outPath = Path.Combine(UpdateDir, tarEntry.Name);
|
||||
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(outPath));
|
||||
|
||||
using (FileStream outStream = File.OpenWrite(outPath))
|
||||
{
|
||||
tarStream.CopyEntryContents(outStream);
|
||||
}
|
||||
|
||||
File.SetUnixFileMode(outPath, (UnixFileMode)tarEntry.TarHeader.Mode);
|
||||
File.SetLastWriteTime(outPath, DateTime.SpecifyKind(tarEntry.ModTime, DateTimeKind.Utc));
|
||||
|
||||
Dispatcher.UIThread.Post(() =>
|
||||
{
|
||||
if (tarEntry is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
taskDialog.SetProgressBarState(GetPercentage(tarEntry.Size, inStream.Length), TaskDialogProgressState.Normal);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
taskDialog.SetProgressBarState(100, TaskDialogProgressState.Normal);
|
||||
}
|
||||
else
|
||||
{
|
||||
using Stream inStream = File.OpenRead(updateFile);
|
||||
using ZipFile zipFile = new(inStream);
|
||||
|
||||
await Task.Run(() =>
|
||||
ExtractTarGzipFile(taskDialog, updateFile, UpdateDir);
|
||||
}
|
||||
else if (OperatingSystem.IsWindows())
|
||||
{
|
||||
double count = 0;
|
||||
foreach (ZipEntry zipEntry in zipFile)
|
||||
{
|
||||
count++;
|
||||
if (zipEntry.IsDirectory) continue;
|
||||
|
||||
string outPath = Path.Combine(UpdateDir, zipEntry.Name);
|
||||
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(outPath));
|
||||
|
||||
using (Stream zipStream = zipFile.GetInputStream(zipEntry))
|
||||
using (FileStream outStream = File.OpenWrite(outPath))
|
||||
{
|
||||
zipStream.CopyTo(outStream);
|
||||
}
|
||||
|
||||
File.SetLastWriteTime(outPath, DateTime.SpecifyKind(zipEntry.DateTime, DateTimeKind.Utc));
|
||||
|
||||
Dispatcher.UIThread.Post(() =>
|
||||
{
|
||||
taskDialog.SetProgressBarState(GetPercentage(count, zipFile.Count), TaskDialogProgressState.Normal);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
ExtractZipFile(taskDialog, updateFile, UpdateDir);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
});
|
||||
|
||||
// Delete downloaded zip
|
||||
File.Delete(updateFile);
|
||||
@@ -560,38 +599,42 @@ namespace Ryujinx.Modules
|
||||
taskDialog.SubHeader = LocaleManager.Instance[LocaleKeys.UpdaterRenaming];
|
||||
taskDialog.SetProgressBarState(0, TaskDialogProgressState.Normal);
|
||||
|
||||
// Replace old files
|
||||
await Task.Run(() =>
|
||||
// NOTE: On macOS, replacement is delayed to the restart phase.
|
||||
if (!OperatingSystem.IsMacOS())
|
||||
{
|
||||
double count = 0;
|
||||
foreach (string file in allFiles)
|
||||
// Replace old files
|
||||
await Task.Run(() =>
|
||||
{
|
||||
count++;
|
||||
try
|
||||
double count = 0;
|
||||
foreach (string file in allFiles)
|
||||
{
|
||||
File.Move(file, file + ".ryuold");
|
||||
|
||||
Dispatcher.UIThread.Post(() =>
|
||||
count++;
|
||||
try
|
||||
{
|
||||
taskDialog.SetProgressBarState(GetPercentage(count, allFiles.Count), TaskDialogProgressState.Normal);
|
||||
});
|
||||
}
|
||||
catch
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Application, LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.UpdaterRenameFailed, file));
|
||||
}
|
||||
}
|
||||
File.Move(file, file + ".ryuold");
|
||||
|
||||
Dispatcher.UIThread.Post(() =>
|
||||
{
|
||||
taskDialog.SubHeader = LocaleManager.Instance[LocaleKeys.UpdaterAddingFiles];
|
||||
taskDialog.SetProgressBarState(0, TaskDialogProgressState.Normal);
|
||||
Dispatcher.UIThread.Post(() =>
|
||||
{
|
||||
taskDialog.SetProgressBarState(GetPercentage(count, allFiles.Count), TaskDialogProgressState.Normal);
|
||||
});
|
||||
}
|
||||
catch
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Application, LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.UpdaterRenameFailed, file));
|
||||
}
|
||||
}
|
||||
|
||||
Dispatcher.UIThread.Post(() =>
|
||||
{
|
||||
taskDialog.SubHeader = LocaleManager.Instance[LocaleKeys.UpdaterAddingFiles];
|
||||
taskDialog.SetProgressBarState(0, TaskDialogProgressState.Normal);
|
||||
});
|
||||
|
||||
MoveAllFilesOver(UpdatePublishDir, HomeDir, taskDialog);
|
||||
});
|
||||
|
||||
MoveAllFilesOver(UpdatePublishDir, HomeDir, taskDialog);
|
||||
});
|
||||
|
||||
Directory.Delete(UpdateDir, true);
|
||||
Directory.Delete(UpdateDir, true);
|
||||
}
|
||||
|
||||
_updateSuccessful = true;
|
||||
|
||||
@@ -601,7 +644,7 @@ namespace Ryujinx.Modules
|
||||
public static bool CanUpdate(bool showWarnings)
|
||||
{
|
||||
#if !DISABLE_UPDATER
|
||||
if (RuntimeInformation.OSArchitecture != Architecture.X64)
|
||||
if (RuntimeInformation.OSArchitecture != Architecture.X64 && !OperatingSystem.IsMacOS())
|
||||
{
|
||||
if (showWarnings)
|
||||
{
|
||||
@@ -674,7 +717,7 @@ namespace Ryujinx.Modules
|
||||
#endif
|
||||
}
|
||||
|
||||
// NOTE: This method should always reflect the latest build layout.s
|
||||
// NOTE: This method should always reflect the latest build layout.
|
||||
private static IEnumerable<string> EnumerateFilesToDelete()
|
||||
{
|
||||
var files = Directory.EnumerateFiles(HomeDir); // All files directly in base dir.
|
||||
|
@@ -16,9 +16,9 @@ using Ryujinx.Ava.UI.Views.User;
|
||||
using Ryujinx.HLE.FileSystem;
|
||||
using Ryujinx.HLE.HOS.Services.Account.Acc;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using UserProfile = Ryujinx.Ava.UI.Models.UserProfile;
|
||||
|
||||
namespace Ryujinx.Ava.UI.Controls
|
||||
@@ -121,7 +121,7 @@ namespace Ryujinx.Ava.UI.Controls
|
||||
|
||||
using var saveDataIterator = new UniqueRef<SaveDataIterator>();
|
||||
|
||||
HorizonClient.Fs.OpenSaveDataIterator(ref saveDataIterator.Ref(), SaveDataSpaceId.User, in saveDataFilter).ThrowIfFailure();
|
||||
HorizonClient.Fs.OpenSaveDataIterator(ref saveDataIterator.Ref, SaveDataSpaceId.User, in saveDataFilter).ThrowIfFailure();
|
||||
|
||||
Span<SaveDataInfo> saveDataInfo = stackalloc SaveDataInfo[10];
|
||||
|
||||
|
@@ -6,8 +6,8 @@ namespace Ryujinx.Ava.UI.Helpers
|
||||
{
|
||||
using AvaLogger = Avalonia.Logging.Logger;
|
||||
using AvaLogLevel = Avalonia.Logging.LogEventLevel;
|
||||
using RyuLogger = Ryujinx.Common.Logging.Logger;
|
||||
using RyuLogClass = Ryujinx.Common.Logging.LogClass;
|
||||
using RyuLogger = Ryujinx.Common.Logging.Logger;
|
||||
|
||||
internal class LoggerAdapter : Avalonia.Logging.ILogSink
|
||||
{
|
||||
|
@@ -3,7 +3,6 @@ using Avalonia.Collections;
|
||||
using Avalonia.Media.Imaging;
|
||||
using Avalonia.Threading;
|
||||
using Ryujinx.Ava.Common.Locale;
|
||||
using Ryujinx.Ava.UI.Controls;
|
||||
using Ryujinx.Ava.UI.Helpers;
|
||||
using Ryujinx.Ava.UI.Models;
|
||||
using Ryujinx.Ava.UI.Windows;
|
||||
@@ -17,7 +16,6 @@ using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Ryujinx.Ava.UI.ViewModels
|
||||
@@ -31,7 +29,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
private readonly byte[] _amiiboLogoBytes;
|
||||
private readonly HttpClient _httpClient;
|
||||
private readonly StyleableWindow _owner;
|
||||
|
||||
|
||||
private Bitmap _amiiboImage;
|
||||
private List<Amiibo.AmiiboApi> _amiiboList;
|
||||
private AvaloniaList<Amiibo.AmiiboApi> _amiibos;
|
||||
|
@@ -246,7 +246,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
{
|
||||
using var file = new UniqueRef<IFile>();
|
||||
|
||||
romfs.OpenFile(ref file.Ref(), ("/" + item.FullPath).ToU8Span(), OpenMode.Read)
|
||||
romfs.OpenFile(ref file.Ref, ("/" + item.FullPath).ToU8Span(), OpenMode.Read)
|
||||
.ThrowIfFailure();
|
||||
|
||||
using (MemoryStream stream = new())
|
||||
|
@@ -3,11 +3,8 @@ using Avalonia.Controls;
|
||||
using Avalonia.Controls.ApplicationLifetimes;
|
||||
using Avalonia.Svg.Skia;
|
||||
using Avalonia.Threading;
|
||||
using LibHac.Bcat;
|
||||
using LibHac.Tools.Fs;
|
||||
using Ryujinx.Ava.Common.Locale;
|
||||
using Ryujinx.Ava.Input;
|
||||
using Ryujinx.Ava.UI.Controls;
|
||||
using Ryujinx.Ava.UI.Helpers;
|
||||
using Ryujinx.Ava.UI.Models;
|
||||
using Ryujinx.Ava.UI.Windows;
|
||||
|
@@ -7,8 +7,7 @@ using DynamicData;
|
||||
using DynamicData.Binding;
|
||||
using LibHac.Common;
|
||||
using LibHac.Fs;
|
||||
using LibHac.FsSystem;
|
||||
using LibHac.Tools.Fs;
|
||||
using LibHac.Tools.FsSystem.NcaUtils;
|
||||
using Ryujinx.Ava.Common;
|
||||
using Ryujinx.Ava.Common.Locale;
|
||||
using Ryujinx.Ava.Input;
|
||||
|
@@ -45,6 +45,8 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
private KeyboardHotkeys _keyboardHotkeys;
|
||||
private int _graphicsBackendIndex;
|
||||
private string _customThemePath;
|
||||
private int _scalingFilter;
|
||||
private int _scalingFilterLevel;
|
||||
|
||||
public event Action CloseWindow;
|
||||
public event Action SaveSettingsEvent;
|
||||
@@ -153,6 +155,8 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
public bool IsSDL2Enabled { get; set; }
|
||||
public bool EnableCustomTheme { get; set; }
|
||||
public bool IsCustomResolutionScaleActive => _resolutionScale == 4;
|
||||
public bool IsScalingFilterActive => _scalingFilter == (int)Ryujinx.Common.Configuration.ScalingFilter.Fsr;
|
||||
|
||||
public bool IsVulkanSelected => GraphicsBackendIndex == 0;
|
||||
public bool UseHypervisor { get; set; }
|
||||
|
||||
@@ -179,6 +183,18 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
public int AudioBackend { get; set; }
|
||||
public int MaxAnisotropy { get; set; }
|
||||
public int AspectRatio { get; set; }
|
||||
public int AntiAliasingEffect { get; set; }
|
||||
public string ScalingFilterLevelText => ScalingFilterLevel.ToString("0");
|
||||
public int ScalingFilterLevel
|
||||
{
|
||||
get => _scalingFilterLevel;
|
||||
set
|
||||
{
|
||||
_scalingFilterLevel = value;
|
||||
OnPropertyChanged();
|
||||
OnPropertyChanged(nameof(ScalingFilterLevelText));
|
||||
}
|
||||
}
|
||||
public int OpenglDebugLevel { get; set; }
|
||||
public int MemoryMode { get; set; }
|
||||
public int BaseStyleIndex { get; set; }
|
||||
@@ -192,6 +208,16 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
OnPropertyChanged(nameof(IsVulkanSelected));
|
||||
}
|
||||
}
|
||||
public int ScalingFilter
|
||||
{
|
||||
get => _scalingFilter;
|
||||
set
|
||||
{
|
||||
_scalingFilter = value;
|
||||
OnPropertyChanged();
|
||||
OnPropertyChanged(nameof(IsScalingFilterActive));
|
||||
}
|
||||
}
|
||||
|
||||
public int PreferredGpuIndex { get; set; }
|
||||
|
||||
@@ -210,7 +236,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
|
||||
public DateTimeOffset DateOffset { get; set; }
|
||||
public TimeSpan TimeOffset { get; set; }
|
||||
private AvaloniaList<TimeZone> TimeZones { get; set; }
|
||||
internal AvaloniaList<TimeZone> TimeZones { get; set; }
|
||||
public AvaloniaList<string> GameDirectories { get; set; }
|
||||
public ObservableCollection<ComboBoxItem> AvailableGpus { get; set; }
|
||||
|
||||
@@ -365,6 +391,9 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
AspectRatio = (int)config.Graphics.AspectRatio.Value;
|
||||
GraphicsBackendMultithreadingIndex = (int)config.Graphics.BackendThreading.Value;
|
||||
ShaderDumpPath = config.Graphics.ShadersDumpPath;
|
||||
AntiAliasingEffect = (int)config.Graphics.AntiAliasing.Value;
|
||||
ScalingFilter = (int)config.Graphics.ScalingFilter.Value;
|
||||
ScalingFilterLevel = config.Graphics.ScalingFilterLevel.Value;
|
||||
|
||||
// Audio
|
||||
AudioBackend = (int)config.System.AudioBackend.Value;
|
||||
@@ -447,6 +476,9 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
config.Graphics.ResScaleCustom.Value = CustomResolutionScale;
|
||||
config.Graphics.MaxAnisotropy.Value = MaxAnisotropy == 0 ? -1 : MathF.Pow(2, MaxAnisotropy);
|
||||
config.Graphics.AspectRatio.Value = (AspectRatio)AspectRatio;
|
||||
config.Graphics.AntiAliasing.Value = (AntiAliasing)AntiAliasingEffect;
|
||||
config.Graphics.ScalingFilter.Value = (ScalingFilter)ScalingFilter;
|
||||
config.Graphics.ScalingFilterLevel.Value = ScalingFilterLevel;
|
||||
|
||||
if (ConfigurationState.Instance.Graphics.BackendThreading != (BackendThreading)GraphicsBackendMultithreadingIndex)
|
||||
{
|
||||
|
@@ -170,7 +170,7 @@ public class TitleUpdateViewModel : BaseModel
|
||||
|
||||
using UniqueRef<IFile> nacpFile = new();
|
||||
|
||||
controlNca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.None).OpenFile(ref nacpFile.Ref(), "/control.nacp".ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
||||
controlNca.OpenFileSystem(NcaSectionType.Data, IntegrityCheckLevel.None).OpenFile(ref nacpFile.Ref, "/control.nacp".ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
||||
nacpFile.Get.Read(out _, 0, SpanHelpers.AsByteSpan(ref controlData), ReadOption.None).ThrowIfFailure();
|
||||
|
||||
TitleUpdates.Add(new TitleUpdateModel(controlData, path));
|
||||
|
@@ -126,7 +126,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
{
|
||||
using var file = new UniqueRef<IFile>();
|
||||
|
||||
romfs.OpenFile(ref file.Ref(), ("/" + item.FullPath).ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
||||
romfs.OpenFile(ref file.Ref, ("/" + item.FullPath).ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
||||
|
||||
using (MemoryStream stream = new())
|
||||
using (MemoryStream streamPng = new())
|
||||
|
@@ -1,8 +1,8 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Interactivity;
|
||||
using LibHac.FsSystem;
|
||||
using LibHac.Ncm;
|
||||
using LibHac.Tools.FsSystem.NcaUtils;
|
||||
using Ryujinx.Ava.Common.Locale;
|
||||
using Ryujinx.Ava.UI.Helpers;
|
||||
using Ryujinx.Ava.UI.ViewModels;
|
||||
|
@@ -7,6 +7,7 @@
|
||||
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
|
||||
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
|
||||
Design.Width="1000"
|
||||
mc:Ignorable="d"
|
||||
x:CompileBindings="True"
|
||||
x:DataType="viewModels:SettingsViewModel">
|
||||
@@ -111,6 +112,83 @@
|
||||
Minimum="0.1"
|
||||
Value="{Binding CustomResolutionScale}" />
|
||||
</StackPanel>
|
||||
<StackPanel
|
||||
HorizontalAlignment="Stretch"
|
||||
Orientation="Vertical"
|
||||
Spacing="10">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock VerticalAlignment="Center"
|
||||
ToolTip.Tip="{locale:Locale GraphicsAATooltip}"
|
||||
Text="{locale:Locale GraphicsAALabel}"
|
||||
Width="250" />
|
||||
<ComboBox Width="350"
|
||||
HorizontalContentAlignment="Left"
|
||||
ToolTip.Tip="{locale:Locale GraphicsAATooltip}"
|
||||
SelectedIndex="{Binding AntiAliasingEffect}">
|
||||
<ComboBoxItem>
|
||||
<TextBlock Text="{locale:Locale SettingsTabLoggingGraphicsBackendLogLevelNone}" />
|
||||
</ComboBoxItem>
|
||||
<ComboBoxItem>
|
||||
<TextBlock Text="FXAA" />
|
||||
</ComboBoxItem>
|
||||
<ComboBoxItem>
|
||||
<TextBlock Text="{locale:Locale SmaaLow}" />
|
||||
</ComboBoxItem>
|
||||
<ComboBoxItem>
|
||||
<TextBlock Text="{locale:Locale SmaaMedium}" />
|
||||
</ComboBoxItem>
|
||||
<ComboBoxItem>
|
||||
<TextBlock Text="{locale:Locale SmaaHigh}" />
|
||||
</ComboBoxItem>
|
||||
<ComboBoxItem>
|
||||
<TextBlock Text="{locale:Locale SmaaUltra}" />
|
||||
</ComboBoxItem>
|
||||
</ComboBox>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
<StackPanel
|
||||
HorizontalAlignment="Stretch"
|
||||
Orientation="Vertical"
|
||||
Spacing="10">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock VerticalAlignment="Center"
|
||||
ToolTip.Tip="{locale:Locale GraphicsScalingFilterTooltip}"
|
||||
Text="{locale:Locale GraphicsScalingFilterLabel}"
|
||||
Width="250" />
|
||||
<ComboBox Width="350"
|
||||
HorizontalContentAlignment="Left"
|
||||
ToolTip.Tip="{locale:Locale GraphicsScalingFilterTooltip}"
|
||||
SelectedIndex="{Binding ScalingFilter}">
|
||||
<ComboBoxItem>
|
||||
<TextBlock Text="Bilinear" />
|
||||
</ComboBoxItem>
|
||||
<ComboBoxItem>
|
||||
<TextBlock Text="Nearest" />
|
||||
</ComboBoxItem>
|
||||
<ComboBoxItem>
|
||||
<TextBlock Text="FSR" />
|
||||
</ComboBoxItem>
|
||||
</ComboBox>
|
||||
<Slider Value="{Binding ScalingFilterLevel}"
|
||||
ToolTip.Tip="{locale:Locale GraphicsScalingFilterLevelTooltip}"
|
||||
MinWidth="150"
|
||||
Margin="10,-3,0,0"
|
||||
Height="32"
|
||||
Padding="0,-5"
|
||||
IsVisible="{Binding IsScalingFilterActive}"
|
||||
TickFrequency="1"
|
||||
IsSnapToTickEnabled="True"
|
||||
LargeChange="10"
|
||||
SmallChange="1"
|
||||
VerticalAlignment="Center"
|
||||
Minimum="0"
|
||||
Maximum="100" />
|
||||
<TextBlock Margin="5,0"
|
||||
Width="40"
|
||||
IsVisible="{Binding IsScalingFilterActive}"
|
||||
Text="{Binding ScalingFilterLevelText}"/>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock VerticalAlignment="Center"
|
||||
ToolTip.Tip="{locale:Locale AnisotropyTooltip}"
|
||||
|
@@ -5,8 +5,8 @@ using FluentAvalonia.UI.Controls;
|
||||
using FluentAvalonia.UI.Navigation;
|
||||
using Ryujinx.Ava.Common.Locale;
|
||||
using Ryujinx.Ava.UI.Controls;
|
||||
using Ryujinx.Ava.UI.Models;
|
||||
using Ryujinx.Ava.UI.Helpers;
|
||||
using Ryujinx.Ava.UI.Models;
|
||||
using Ryujinx.HLE.HOS.Services.Account.Acc;
|
||||
using System;
|
||||
using UserProfile = Ryujinx.Ava.UI.Models.UserProfile;
|
||||
|
@@ -76,7 +76,7 @@ namespace Ryujinx.Ava.UI.Views.User
|
||||
|
||||
using var saveDataIterator = new UniqueRef<SaveDataIterator>();
|
||||
|
||||
_horizonClient.Fs.OpenSaveDataIterator(ref saveDataIterator.Ref(), SaveDataSpaceId.User, in saveDataFilter).ThrowIfFailure();
|
||||
_horizonClient.Fs.OpenSaveDataIterator(ref saveDataIterator.Ref, SaveDataSpaceId.User, in saveDataFilter).ThrowIfFailure();
|
||||
|
||||
Span<SaveDataInfo> saveDataInfo = stackalloc SaveDataInfo[10];
|
||||
|
||||
|
@@ -105,7 +105,7 @@ namespace Ryujinx.Ava.UI.Windows
|
||||
{
|
||||
using UniqueRef<IFile> ncaFile = new();
|
||||
|
||||
partitionFileSystem.OpenFile(ref ncaFile.Ref(), downloadableContentNca.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
||||
partitionFileSystem.OpenFile(ref ncaFile.Ref, downloadableContentNca.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
||||
|
||||
Nca nca = TryOpenNca(ncaFile.Get.AsStorage(), downloadableContentContainer.ContainerPath);
|
||||
if (nca != null)
|
||||
@@ -158,7 +158,7 @@ namespace Ryujinx.Ava.UI.Windows
|
||||
{
|
||||
using var ncaFile = new UniqueRef<IFile>();
|
||||
|
||||
partitionFileSystem.OpenFile(ref ncaFile.Ref(), fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
||||
partitionFileSystem.OpenFile(ref ncaFile.Ref, fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
||||
|
||||
Nca nca = TryOpenNca(ncaFile.Get.AsStorage(), path);
|
||||
if (nca == null)
|
||||
|
@@ -125,7 +125,7 @@ namespace Ryujinx.Ava.UI.Windows
|
||||
|
||||
public static Bgra32[] GetBuffer(Image<Bgra32> image)
|
||||
{
|
||||
return image.TryGetSinglePixelSpan(out var data) ? data.ToArray() : new Bgra32[0];
|
||||
return image.TryGetSinglePixelSpan(out var data) ? data.ToArray() : Array.Empty<Bgra32>();
|
||||
}
|
||||
|
||||
private static int GetColorScore(Dictionary<int, int> dominantColorBin, int maxHitCount, PaletteColor color)
|
||||
|
12
Ryujinx.Common/Configuration/AntiAliasing.cs
Normal file
12
Ryujinx.Common/Configuration/AntiAliasing.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
namespace Ryujinx.Common.Configuration
|
||||
{
|
||||
public enum AntiAliasing
|
||||
{
|
||||
None,
|
||||
Fxaa,
|
||||
SmaaLow,
|
||||
SmaaMedium,
|
||||
SmaaHigh,
|
||||
SmaaUltra
|
||||
}
|
||||
}
|
9
Ryujinx.Common/Configuration/ScalingFilter.cs
Normal file
9
Ryujinx.Common/Configuration/ScalingFilter.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace Ryujinx.Common.Configuration
|
||||
{
|
||||
public enum ScalingFilter
|
||||
{
|
||||
Bilinear,
|
||||
Nearest,
|
||||
Fsr
|
||||
}
|
||||
}
|
@@ -1,3 +1,4 @@
|
||||
using Ryujinx.Common.SystemInterop;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
@@ -14,6 +15,8 @@ namespace Ryujinx.Common.Logging
|
||||
|
||||
private static readonly List<ILogTarget> m_LogTargets;
|
||||
|
||||
private static readonly StdErrAdapter _stdErrAdapter;
|
||||
|
||||
public static event EventHandler<LogEventArgs> Updated;
|
||||
|
||||
public readonly struct Log
|
||||
@@ -77,7 +80,13 @@ namespace Ryujinx.Common.Logging
|
||||
{
|
||||
Updated?.Invoke(null, new LogEventArgs(Level, m_Time.Elapsed, Thread.CurrentThread.Name, FormatMessage(logClass, caller, "Stubbed. " + message), data));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void PrintRawMsg(string message)
|
||||
{
|
||||
Updated?.Invoke(null, new LogEventArgs(Level, m_Time.Elapsed, Thread.CurrentThread.Name, message));
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static string FormatMessage(LogClass Class, string Caller, string Message) => $"{Class} {Caller}: {Message}";
|
||||
@@ -119,6 +128,8 @@ namespace Ryujinx.Common.Logging
|
||||
Warning = new Log(LogLevel.Warning);
|
||||
Info = new Log(LogLevel.Info);
|
||||
Trace = new Log(LogLevel.Trace);
|
||||
|
||||
_stdErrAdapter = new StdErrAdapter();
|
||||
}
|
||||
|
||||
public static void RestartTime()
|
||||
@@ -164,6 +175,8 @@ namespace Ryujinx.Common.Logging
|
||||
{
|
||||
Updated = null;
|
||||
|
||||
_stdErrAdapter.Dispose();
|
||||
|
||||
foreach (var target in m_LogTargets)
|
||||
{
|
||||
target.Dispose();
|
||||
|
@@ -3,6 +3,7 @@
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<DefineConstants Condition=" '$(ExtraDefineConstants)' != '' ">$(DefineConstants);$(ExtraDefineConstants)</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@@ -1,9 +1,9 @@
|
||||
using Ryujinx.Common.Logging;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Runtime.Versioning;
|
||||
using Ryujinx.Common.Logging;
|
||||
|
||||
namespace Ryujinx.Common.SystemInfo
|
||||
{
|
||||
|
@@ -1,9 +1,9 @@
|
||||
using Ryujinx.Common.Logging;
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.Versioning;
|
||||
using System.Text;
|
||||
using Ryujinx.Common.Logging;
|
||||
|
||||
namespace Ryujinx.Common.SystemInfo
|
||||
{
|
||||
|
@@ -1,8 +1,8 @@
|
||||
using System;
|
||||
using Ryujinx.Common.Logging;
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.Intrinsics.X86;
|
||||
using System.Text;
|
||||
using Ryujinx.Common.Logging;
|
||||
|
||||
namespace Ryujinx.Common.SystemInfo
|
||||
{
|
||||
|
@@ -1,8 +1,8 @@
|
||||
using Ryujinx.Common.Logging;
|
||||
using System;
|
||||
using System.Management;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.Versioning;
|
||||
using Ryujinx.Common.Logging;
|
||||
|
||||
namespace Ryujinx.Common.SystemInfo
|
||||
{
|
||||
|
93
Ryujinx.Common/SystemInterop/StdErrAdapter.cs
Normal file
93
Ryujinx.Common/SystemInterop/StdErrAdapter.cs
Normal file
@@ -0,0 +1,93 @@
|
||||
using Ryujinx.Common.Logging;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.Versioning;
|
||||
using System.Threading;
|
||||
|
||||
namespace Ryujinx.Common.SystemInterop
|
||||
{
|
||||
public partial class StdErrAdapter : IDisposable
|
||||
{
|
||||
private bool _disposable = false;
|
||||
private UnixStream _pipeReader;
|
||||
private UnixStream _pipeWriter;
|
||||
private Thread _worker;
|
||||
|
||||
public StdErrAdapter()
|
||||
{
|
||||
if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
|
||||
{
|
||||
RegisterPosix();
|
||||
}
|
||||
}
|
||||
|
||||
[SupportedOSPlatform("linux")]
|
||||
[SupportedOSPlatform("macos")]
|
||||
private void RegisterPosix()
|
||||
{
|
||||
const int stdErrFileno = 2;
|
||||
|
||||
(int readFd, int writeFd) = MakePipe();
|
||||
dup2(writeFd, stdErrFileno);
|
||||
|
||||
_pipeReader = new UnixStream(readFd);
|
||||
_pipeWriter = new UnixStream(writeFd);
|
||||
|
||||
_worker = new Thread(EventWorker);
|
||||
_disposable = true;
|
||||
_worker.Start();
|
||||
}
|
||||
|
||||
[SupportedOSPlatform("linux")]
|
||||
[SupportedOSPlatform("macos")]
|
||||
private void EventWorker()
|
||||
{
|
||||
TextReader reader = new StreamReader(_pipeReader);
|
||||
string line;
|
||||
while ((line = reader.ReadLine()) != null)
|
||||
{
|
||||
Logger.Error?.PrintRawMsg(line);
|
||||
}
|
||||
}
|
||||
|
||||
private void Dispose(bool disposing)
|
||||
{
|
||||
if (_disposable)
|
||||
{
|
||||
if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
|
||||
{
|
||||
_pipeReader?.Close();
|
||||
_pipeWriter?.Close();
|
||||
}
|
||||
|
||||
_disposable = false;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
}
|
||||
|
||||
[LibraryImport("libc", SetLastError = true)]
|
||||
private static partial int dup2(int fd, int fd2);
|
||||
|
||||
[LibraryImport("libc", SetLastError = true)]
|
||||
private static unsafe partial int pipe(int* pipefd);
|
||||
|
||||
private static unsafe (int, int) MakePipe()
|
||||
{
|
||||
int *pipefd = stackalloc int[2];
|
||||
|
||||
if (pipe(pipefd) == 0)
|
||||
{
|
||||
return (pipefd[0], pipefd[1]);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
155
Ryujinx.Common/SystemInterop/UnixStream.cs
Normal file
155
Ryujinx.Common/SystemInterop/UnixStream.cs
Normal file
@@ -0,0 +1,155 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.Versioning;
|
||||
|
||||
namespace Ryujinx.Common.SystemInterop
|
||||
{
|
||||
[SupportedOSPlatform("linux")]
|
||||
[SupportedOSPlatform("macos")]
|
||||
public partial class UnixStream : Stream, IDisposable
|
||||
{
|
||||
private const int InvalidFd = -1;
|
||||
|
||||
private int _fd;
|
||||
|
||||
[LibraryImport("libc", SetLastError = true)]
|
||||
private static partial long read(int fd, IntPtr buf, ulong count);
|
||||
|
||||
[LibraryImport("libc", SetLastError = true)]
|
||||
private static partial long write(int fd, IntPtr buf, ulong count);
|
||||
|
||||
[LibraryImport("libc", SetLastError = true)]
|
||||
private static partial int close(int fd);
|
||||
|
||||
public UnixStream(int fd)
|
||||
{
|
||||
if (InvalidFd == fd)
|
||||
{
|
||||
throw new ArgumentException("Invalid file descriptor");
|
||||
}
|
||||
|
||||
_fd = fd;
|
||||
|
||||
CanRead = read(fd, IntPtr.Zero, 0) != -1;
|
||||
CanWrite = write(fd, IntPtr.Zero, 0) != -1;
|
||||
}
|
||||
|
||||
~UnixStream()
|
||||
{
|
||||
Close();
|
||||
}
|
||||
|
||||
public override bool CanRead { get; }
|
||||
public override bool CanWrite { get; }
|
||||
public override bool CanSeek => false;
|
||||
|
||||
public override long Length => throw new NotSupportedException();
|
||||
|
||||
public override long Position
|
||||
{
|
||||
get => throw new NotSupportedException();
|
||||
set => throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public override void Flush()
|
||||
{
|
||||
}
|
||||
|
||||
public override unsafe int Read([In, Out] byte[] buffer, int offset, int count)
|
||||
{
|
||||
if (offset < 0 || offset > (buffer.Length - count) || count < 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
|
||||
if (buffer.Length == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
long r = 0;
|
||||
fixed (byte* buf = &buffer[offset])
|
||||
{
|
||||
do
|
||||
{
|
||||
r = read(_fd, (IntPtr)buf, (ulong)count);
|
||||
} while (ShouldRetry(r));
|
||||
}
|
||||
|
||||
return (int)r;
|
||||
}
|
||||
|
||||
public override unsafe void Write(byte[] buffer, int offset, int count)
|
||||
{
|
||||
if (offset < 0 || offset > (buffer.Length - count) || count < 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
|
||||
if (buffer.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
fixed (byte* buf = &buffer[offset])
|
||||
{
|
||||
long r = 0;
|
||||
do {
|
||||
r = write(_fd, (IntPtr)buf, (ulong)count);
|
||||
} while (ShouldRetry(r));
|
||||
}
|
||||
}
|
||||
|
||||
public override long Seek(long offset, SeekOrigin origin)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public override void SetLength(long value)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public override void Close()
|
||||
{
|
||||
if (_fd == InvalidFd)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Flush();
|
||||
|
||||
int r;
|
||||
do {
|
||||
r = close(_fd);
|
||||
} while (ShouldRetry(r));
|
||||
|
||||
_fd = InvalidFd;
|
||||
}
|
||||
|
||||
void IDisposable.Dispose()
|
||||
{
|
||||
Close();
|
||||
}
|
||||
|
||||
private bool ShouldRetry(long r)
|
||||
{
|
||||
if (r == -1)
|
||||
{
|
||||
const int eintr = 4;
|
||||
|
||||
int errno = Marshal.GetLastPInvokeError();
|
||||
|
||||
if (errno == eintr)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
throw new SystemException($"Operation failed with error 0x{errno:X}");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
12
Ryujinx.Graphics.GAL/AntiAliasing.cs
Normal file
12
Ryujinx.Graphics.GAL/AntiAliasing.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
namespace Ryujinx.Graphics.GAL
|
||||
{
|
||||
public enum AntiAliasing
|
||||
{
|
||||
None,
|
||||
Fxaa,
|
||||
SmaaLow,
|
||||
SmaaMedium,
|
||||
SmaaHigh,
|
||||
SmaaUltra
|
||||
}
|
||||
}
|
@@ -26,6 +26,7 @@ namespace Ryujinx.Graphics.GAL
|
||||
public readonly bool SupportsBlendEquationAdvanced;
|
||||
public readonly bool SupportsFragmentShaderInterlock;
|
||||
public readonly bool SupportsFragmentShaderOrderingIntel;
|
||||
public readonly bool SupportsGeometryShader;
|
||||
public readonly bool SupportsGeometryShaderPassthrough;
|
||||
public readonly bool SupportsImageLoadFormatted;
|
||||
public readonly bool SupportsLayerVertexTessellation;
|
||||
@@ -68,6 +69,7 @@ namespace Ryujinx.Graphics.GAL
|
||||
bool supportsBlendEquationAdvanced,
|
||||
bool supportsFragmentShaderInterlock,
|
||||
bool supportsFragmentShaderOrderingIntel,
|
||||
bool supportsGeometryShader,
|
||||
bool supportsGeometryShaderPassthrough,
|
||||
bool supportsImageLoadFormatted,
|
||||
bool supportsLayerVertexTessellation,
|
||||
@@ -107,6 +109,7 @@ namespace Ryujinx.Graphics.GAL
|
||||
SupportsBlendEquationAdvanced = supportsBlendEquationAdvanced;
|
||||
SupportsFragmentShaderInterlock = supportsFragmentShaderInterlock;
|
||||
SupportsFragmentShaderOrderingIntel = supportsFragmentShaderOrderingIntel;
|
||||
SupportsGeometryShader = supportsGeometryShader;
|
||||
SupportsGeometryShaderPassthrough = supportsGeometryShaderPassthrough;
|
||||
SupportsImageLoadFormatted = supportsImageLoadFormatted;
|
||||
SupportsLayerVertexTessellation = supportsLayerVertexTessellation;
|
||||
|
@@ -9,5 +9,9 @@ namespace Ryujinx.Graphics.GAL
|
||||
void SetSize(int width, int height);
|
||||
|
||||
void ChangeVSyncMode(bool vsyncEnabled);
|
||||
|
||||
void SetAntiAliasing(AntiAliasing antialiasing);
|
||||
void SetScalingFilter(ScalingFilter type);
|
||||
void SetScalingFilterLevel(float level);
|
||||
}
|
||||
}
|
||||
|
@@ -32,5 +32,11 @@ namespace Ryujinx.Graphics.GAL.Multithreading
|
||||
}
|
||||
|
||||
public void ChangeVSyncMode(bool vsyncEnabled) { }
|
||||
|
||||
public void SetAntiAliasing(AntiAliasing effect) { }
|
||||
|
||||
public void SetScalingFilter(ScalingFilter type) { }
|
||||
|
||||
public void SetScalingFilterLevel(float level) { }
|
||||
}
|
||||
}
|
||||
|
9
Ryujinx.Graphics.GAL/UpscaleType.cs
Normal file
9
Ryujinx.Graphics.GAL/UpscaleType.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace Ryujinx.Graphics.GAL
|
||||
{
|
||||
public enum ScalingFilter
|
||||
{
|
||||
Bilinear,
|
||||
Nearest,
|
||||
Fsr
|
||||
}
|
||||
}
|
@@ -22,7 +22,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||
private const ushort FileFormatVersionMajor = 1;
|
||||
private const ushort FileFormatVersionMinor = 2;
|
||||
private const uint FileFormatVersionPacked = ((uint)FileFormatVersionMajor << 16) | FileFormatVersionMinor;
|
||||
private const uint CodeGenVersion = 4369;
|
||||
private const uint CodeGenVersion = 4368;
|
||||
|
||||
private const string SharedTocFileName = "shared.toc";
|
||||
private const string SharedDataFileName = "shared.data";
|
||||
@@ -774,6 +774,8 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||
sBuffers,
|
||||
textures,
|
||||
images,
|
||||
ShaderIdentification.None,
|
||||
0,
|
||||
dataInfo.Stage,
|
||||
dataInfo.UsesInstanceId,
|
||||
dataInfo.UsesDrawParameters,
|
||||
|
@@ -633,6 +633,11 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||
}
|
||||
}
|
||||
|
||||
if (!_context.Capabilities.SupportsGeometryShader)
|
||||
{
|
||||
ShaderCache.TryRemoveGeometryStage(translatorContexts);
|
||||
}
|
||||
|
||||
CachedShaderStage[] shaders = new CachedShaderStage[guestShaders.Length];
|
||||
List<ShaderProgram> translatedStages = new List<ShaderProgram>();
|
||||
|
||||
|
@@ -126,6 +126,8 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
|
||||
public bool QueryHostSupportsFragmentShaderOrderingIntel() => _context.Capabilities.SupportsFragmentShaderOrderingIntel;
|
||||
|
||||
public bool QueryHostSupportsGeometryShader() => _context.Capabilities.SupportsGeometryShader;
|
||||
|
||||
public bool QueryHostSupportsGeometryShaderPassthrough() => _context.Capabilities.SupportsGeometryShaderPassthrough;
|
||||
|
||||
public bool QueryHostSupportsImageLoadFormatted() => _context.Capabilities.SupportsImageLoadFormatted;
|
||||
|
@@ -353,6 +353,11 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
}
|
||||
}
|
||||
|
||||
if (!_context.Capabilities.SupportsGeometryShader)
|
||||
{
|
||||
TryRemoveGeometryStage(translatorContexts);
|
||||
}
|
||||
|
||||
CachedShaderStage[] shaders = new CachedShaderStage[Constants.ShaderStages + 1];
|
||||
List<ShaderSource> shaderSources = new List<ShaderSource>();
|
||||
|
||||
@@ -421,6 +426,39 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
return gpShaders;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to eliminate the geometry stage from the array of translator contexts.
|
||||
/// </summary>
|
||||
/// <param name="translatorContexts">Array of translator contexts</param>
|
||||
public static void TryRemoveGeometryStage(TranslatorContext[] translatorContexts)
|
||||
{
|
||||
if (translatorContexts[4] != null)
|
||||
{
|
||||
// We have a geometry shader, but geometry shaders are not supported.
|
||||
// Try to eliminate the geometry shader.
|
||||
|
||||
ShaderProgramInfo info = translatorContexts[4].Translate().Info;
|
||||
|
||||
if (info.Identification == ShaderIdentification.GeometryLayerPassthrough)
|
||||
{
|
||||
// We managed to identify that this geometry shader is only used to set the output Layer value,
|
||||
// we can set the Layer on the previous stage instead (usually the vertex stage) and eliminate it.
|
||||
|
||||
for (int i = 3; i >= 1; i--)
|
||||
{
|
||||
if (translatorContexts[i] != null)
|
||||
{
|
||||
translatorContexts[i].SetGeometryShaderLayerInputAttribute(info.GpLayerInputAttribute);
|
||||
translatorContexts[i].SetLastInVertexPipeline(translatorContexts[5] != null);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
translatorContexts[4] = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a shader source for use with the backend from a translated shader program.
|
||||
/// </summary>
|
||||
|
@@ -1,7 +1,7 @@
|
||||
using Ryujinx.Common.Memory;
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Graphics.Gpu.Image;
|
||||
using Ryujinx.Graphics.Gpu.Memory;
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Graphics.Gpu.Shader.DiskCache;
|
||||
using Ryujinx.Graphics.Shader;
|
||||
using System;
|
||||
|
@@ -1,7 +1,7 @@
|
||||
using System;
|
||||
using Ryujinx.Graphics.Nvdec.Vp9.Common;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Ryujinx.Graphics.Nvdec.Vp9.Common;
|
||||
using static Ryujinx.Graphics.Nvdec.Vp9.Dsp.TxfmCommon;
|
||||
|
||||
namespace Ryujinx.Graphics.Nvdec.Vp9.Dsp
|
||||
|
@@ -1,6 +1,6 @@
|
||||
using System;
|
||||
using Ryujinx.Common.Memory;
|
||||
using System;
|
||||
using System.Buffers.Binary;
|
||||
using Ryujinx.Common.Memory;
|
||||
|
||||
namespace Ryujinx.Graphics.Nvdec.Vp9.Dsp
|
||||
{
|
||||
|
177
Ryujinx.Graphics.OpenGL/Effects/FsrScalingFilter.cs
Normal file
177
Ryujinx.Graphics.OpenGL/Effects/FsrScalingFilter.cs
Normal file
@@ -0,0 +1,177 @@
|
||||
using OpenTK.Graphics.OpenGL;
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Graphics.OpenGL.Image;
|
||||
using System;
|
||||
using static Ryujinx.Graphics.OpenGL.Effects.ShaderHelper;
|
||||
|
||||
namespace Ryujinx.Graphics.OpenGL.Effects
|
||||
{
|
||||
internal class FsrScalingFilter : IScalingFilter
|
||||
{
|
||||
private readonly OpenGLRenderer _renderer;
|
||||
private int _inputUniform;
|
||||
private int _outputUniform;
|
||||
private int _sharpeningUniform;
|
||||
private int _srcX0Uniform;
|
||||
private int _srcX1Uniform;
|
||||
private int _srcY0Uniform;
|
||||
private int _scalingShaderProgram;
|
||||
private int _sharpeningShaderProgram;
|
||||
private float _scale = 1;
|
||||
private int _srcY1Uniform;
|
||||
private int _dstX0Uniform;
|
||||
private int _dstX1Uniform;
|
||||
private int _dstY0Uniform;
|
||||
private int _dstY1Uniform;
|
||||
private int _scaleXUniform;
|
||||
private int _scaleYUniform;
|
||||
private TextureStorage _intermediaryTexture;
|
||||
|
||||
public float Level
|
||||
{
|
||||
get => _scale;
|
||||
set
|
||||
{
|
||||
_scale = MathF.Max(0.01f, value);
|
||||
}
|
||||
}
|
||||
|
||||
public FsrScalingFilter(OpenGLRenderer renderer, IPostProcessingEffect filter)
|
||||
{
|
||||
Initialize();
|
||||
|
||||
_renderer = renderer;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_scalingShaderProgram != 0)
|
||||
{
|
||||
GL.DeleteProgram(_scalingShaderProgram);
|
||||
GL.DeleteProgram(_sharpeningShaderProgram);
|
||||
}
|
||||
|
||||
_intermediaryTexture?.Dispose();
|
||||
}
|
||||
|
||||
private void Initialize()
|
||||
{
|
||||
var scalingShader = EmbeddedResources.ReadAllText("Ryujinx.Graphics.OpenGL/Effects/Shaders/fsr_scaling.glsl");
|
||||
var sharpeningShader = EmbeddedResources.ReadAllText("Ryujinx.Graphics.OpenGL/Effects/Shaders/fsr_sharpening.glsl");
|
||||
var fsrA = EmbeddedResources.ReadAllText("Ryujinx.Graphics.OpenGL/Effects/Shaders/ffx_a.h");
|
||||
var fsr1 = EmbeddedResources.ReadAllText("Ryujinx.Graphics.OpenGL/Effects/Shaders/ffx_fsr1.h");
|
||||
|
||||
scalingShader = scalingShader.Replace("#include \"ffx_a.h\"", fsrA);
|
||||
scalingShader = scalingShader.Replace("#include \"ffx_fsr1.h\"", fsr1);
|
||||
sharpeningShader = sharpeningShader.Replace("#include \"ffx_a.h\"", fsrA);
|
||||
sharpeningShader = sharpeningShader.Replace("#include \"ffx_fsr1.h\"", fsr1);
|
||||
|
||||
_scalingShaderProgram = CompileProgram(scalingShader, ShaderType.ComputeShader);
|
||||
_sharpeningShaderProgram = CompileProgram(sharpeningShader, ShaderType.ComputeShader);
|
||||
|
||||
_inputUniform = GL.GetUniformLocation(_scalingShaderProgram, "Source");
|
||||
_outputUniform = GL.GetUniformLocation(_scalingShaderProgram, "imgOutput");
|
||||
_sharpeningUniform = GL.GetUniformLocation(_sharpeningShaderProgram, "sharpening");
|
||||
|
||||
_srcX0Uniform = GL.GetUniformLocation(_scalingShaderProgram, "srcX0");
|
||||
_srcX1Uniform = GL.GetUniformLocation(_scalingShaderProgram, "srcX1");
|
||||
_srcY0Uniform = GL.GetUniformLocation(_scalingShaderProgram, "srcY0");
|
||||
_srcY1Uniform = GL.GetUniformLocation(_scalingShaderProgram, "srcY1");
|
||||
_dstX0Uniform = GL.GetUniformLocation(_scalingShaderProgram, "dstX0");
|
||||
_dstX1Uniform = GL.GetUniformLocation(_scalingShaderProgram, "dstX1");
|
||||
_dstY0Uniform = GL.GetUniformLocation(_scalingShaderProgram, "dstY0");
|
||||
_dstY1Uniform = GL.GetUniformLocation(_scalingShaderProgram, "dstY1");
|
||||
_scaleXUniform = GL.GetUniformLocation(_scalingShaderProgram, "scaleX");
|
||||
_scaleYUniform = GL.GetUniformLocation(_scalingShaderProgram, "scaleY");
|
||||
}
|
||||
|
||||
public void Run(
|
||||
TextureView view,
|
||||
TextureView destinationTexture,
|
||||
int width,
|
||||
int height,
|
||||
Extents2D source,
|
||||
Extents2D destination)
|
||||
{
|
||||
if (_intermediaryTexture == null || _intermediaryTexture.Info.Width != width || _intermediaryTexture.Info.Height != height)
|
||||
{
|
||||
_intermediaryTexture?.Dispose();
|
||||
var originalInfo = view.Info;
|
||||
var info = new TextureCreateInfo(width,
|
||||
height,
|
||||
originalInfo.Depth,
|
||||
originalInfo.Levels,
|
||||
originalInfo.Samples,
|
||||
originalInfo.BlockWidth,
|
||||
originalInfo.BlockHeight,
|
||||
originalInfo.BytesPerPixel,
|
||||
originalInfo.Format,
|
||||
originalInfo.DepthStencilMode,
|
||||
originalInfo.Target,
|
||||
originalInfo.SwizzleR,
|
||||
originalInfo.SwizzleG,
|
||||
originalInfo.SwizzleB,
|
||||
originalInfo.SwizzleA);
|
||||
|
||||
_intermediaryTexture = new TextureStorage(_renderer, info, view.ScaleFactor);
|
||||
_intermediaryTexture.CreateDefaultView();
|
||||
}
|
||||
|
||||
var textureView = _intermediaryTexture.CreateView(_intermediaryTexture.Info, 0, 0) as TextureView;
|
||||
|
||||
int previousProgram = GL.GetInteger(GetPName.CurrentProgram);
|
||||
int previousUnit = GL.GetInteger(GetPName.ActiveTexture);
|
||||
GL.ActiveTexture(TextureUnit.Texture0);
|
||||
int previousTextureBinding = GL.GetInteger(GetPName.TextureBinding2D);
|
||||
|
||||
GL.BindImageTexture(0, textureView.Handle, 0, false, 0, TextureAccess.ReadWrite, SizedInternalFormat.Rgba8);
|
||||
|
||||
int threadGroupWorkRegionDim = 16;
|
||||
int dispatchX = (width + (threadGroupWorkRegionDim - 1)) / threadGroupWorkRegionDim;
|
||||
int dispatchY = (height + (threadGroupWorkRegionDim - 1)) / threadGroupWorkRegionDim;
|
||||
|
||||
// Scaling pass
|
||||
float srcWidth = Math.Abs(source.X2 - source.X1);
|
||||
float srcHeight = Math.Abs(source.Y2 - source.Y1);
|
||||
float scaleX = srcWidth / view.Width;
|
||||
float scaleY = srcHeight / view.Height;
|
||||
GL.UseProgram(_scalingShaderProgram);
|
||||
view.Bind(0);
|
||||
GL.Uniform1(_inputUniform, 0);
|
||||
GL.Uniform1(_outputUniform, 0);
|
||||
GL.Uniform1(_srcX0Uniform, (float)source.X1);
|
||||
GL.Uniform1(_srcX1Uniform, (float)source.X2);
|
||||
GL.Uniform1(_srcY0Uniform, (float)source.Y1);
|
||||
GL.Uniform1(_srcY1Uniform, (float)source.Y2);
|
||||
GL.Uniform1(_dstX0Uniform, (float)destination.X1);
|
||||
GL.Uniform1(_dstX1Uniform, (float)destination.X2);
|
||||
GL.Uniform1(_dstY0Uniform, (float)destination.Y1);
|
||||
GL.Uniform1(_dstY1Uniform, (float)destination.Y2);
|
||||
GL.Uniform1(_scaleXUniform, scaleX);
|
||||
GL.Uniform1(_scaleYUniform, scaleY);
|
||||
GL.DispatchCompute(dispatchX, dispatchY, 1);
|
||||
|
||||
GL.MemoryBarrier(MemoryBarrierFlags.ShaderImageAccessBarrierBit);
|
||||
|
||||
// Sharpening Pass
|
||||
GL.UseProgram(_sharpeningShaderProgram);
|
||||
GL.BindImageTexture(0, destinationTexture.Handle, 0, false, 0, TextureAccess.ReadWrite, SizedInternalFormat.Rgba8);
|
||||
textureView.Bind(0);
|
||||
GL.Uniform1(_inputUniform, 0);
|
||||
GL.Uniform1(_outputUniform, 0);
|
||||
GL.Uniform1(_sharpeningUniform, 1.5f - (Level * 0.01f * 1.5f));
|
||||
GL.DispatchCompute(dispatchX, dispatchY, 1);
|
||||
|
||||
GL.UseProgram(previousProgram);
|
||||
GL.MemoryBarrier(MemoryBarrierFlags.ShaderImageAccessBarrierBit);
|
||||
|
||||
(_renderer.Pipeline as Pipeline).RestoreImages1And2();
|
||||
|
||||
GL.ActiveTexture(TextureUnit.Texture0);
|
||||
GL.BindTexture(TextureTarget.Texture2D, previousTextureBinding);
|
||||
|
||||
GL.ActiveTexture((TextureUnit)previousUnit);
|
||||
}
|
||||
}
|
||||
}
|
81
Ryujinx.Graphics.OpenGL/Effects/FxaaPostProcessingEffect.cs
Normal file
81
Ryujinx.Graphics.OpenGL/Effects/FxaaPostProcessingEffect.cs
Normal file
@@ -0,0 +1,81 @@
|
||||
using OpenTK.Graphics.OpenGL;
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Graphics.OpenGL.Image;
|
||||
|
||||
namespace Ryujinx.Graphics.OpenGL.Effects
|
||||
{
|
||||
internal class FxaaPostProcessingEffect : IPostProcessingEffect
|
||||
{
|
||||
private readonly OpenGLRenderer _renderer;
|
||||
private int _resolutionUniform;
|
||||
private int _inputUniform;
|
||||
private int _outputUniform;
|
||||
private int _shaderProgram;
|
||||
private TextureStorage _textureStorage;
|
||||
|
||||
public FxaaPostProcessingEffect(OpenGLRenderer renderer)
|
||||
{
|
||||
Initialize();
|
||||
|
||||
_renderer = renderer;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_shaderProgram != 0)
|
||||
{
|
||||
GL.DeleteProgram(_shaderProgram);
|
||||
_textureStorage?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private void Initialize()
|
||||
{
|
||||
_shaderProgram = ShaderHelper.CompileProgram(EmbeddedResources.ReadAllText("Ryujinx.Graphics.OpenGL/Effects/Shaders/fxaa.glsl"), ShaderType.ComputeShader);
|
||||
|
||||
_resolutionUniform = GL.GetUniformLocation(_shaderProgram, "invResolution");
|
||||
_inputUniform = GL.GetUniformLocation(_shaderProgram, "inputTexture");
|
||||
_outputUniform = GL.GetUniformLocation(_shaderProgram, "imgOutput");
|
||||
}
|
||||
|
||||
public TextureView Run(TextureView view, int width, int height)
|
||||
{
|
||||
if (_textureStorage == null || _textureStorage.Info.Width != view.Width || _textureStorage.Info.Height != view.Height)
|
||||
{
|
||||
_textureStorage?.Dispose();
|
||||
_textureStorage = new TextureStorage(_renderer, view.Info, view.ScaleFactor);
|
||||
_textureStorage.CreateDefaultView();
|
||||
}
|
||||
|
||||
var textureView = _textureStorage.CreateView(view.Info, 0, 0) as TextureView;
|
||||
|
||||
int previousProgram = GL.GetInteger(GetPName.CurrentProgram);
|
||||
int previousUnit = GL.GetInteger(GetPName.ActiveTexture);
|
||||
GL.ActiveTexture(TextureUnit.Texture0);
|
||||
int previousTextureBinding = GL.GetInteger(GetPName.TextureBinding2D);
|
||||
|
||||
GL.BindImageTexture(0, textureView.Handle, 0, false, 0, TextureAccess.ReadWrite, SizedInternalFormat.Rgba8);
|
||||
GL.UseProgram(_shaderProgram);
|
||||
|
||||
var dispatchX = BitUtils.DivRoundUp(view.Width, IPostProcessingEffect.LocalGroupSize);
|
||||
var dispatchY = BitUtils.DivRoundUp(view.Height, IPostProcessingEffect.LocalGroupSize);
|
||||
|
||||
view.Bind(0);
|
||||
GL.Uniform1(_inputUniform, 0);
|
||||
GL.Uniform1(_outputUniform, 0);
|
||||
GL.Uniform2(_resolutionUniform, (float)view.Width, (float)view.Height);
|
||||
GL.DispatchCompute(dispatchX, dispatchY, 1);
|
||||
GL.UseProgram(previousProgram);
|
||||
GL.MemoryBarrier(MemoryBarrierFlags.ShaderImageAccessBarrierBit);
|
||||
|
||||
(_renderer.Pipeline as Pipeline).RestoreImages1And2();
|
||||
|
||||
GL.ActiveTexture(TextureUnit.Texture0);
|
||||
GL.BindTexture(TextureTarget.Texture2D, previousTextureBinding);
|
||||
|
||||
GL.ActiveTexture((TextureUnit)previousUnit);
|
||||
|
||||
return textureView;
|
||||
}
|
||||
}
|
||||
}
|
11
Ryujinx.Graphics.OpenGL/Effects/IPostProcessingEffect.cs
Normal file
11
Ryujinx.Graphics.OpenGL/Effects/IPostProcessingEffect.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
using Ryujinx.Graphics.OpenGL.Image;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Graphics.OpenGL.Effects
|
||||
{
|
||||
internal interface IPostProcessingEffect : IDisposable
|
||||
{
|
||||
const int LocalGroupSize = 64;
|
||||
TextureView Run(TextureView view, int width, int height);
|
||||
}
|
||||
}
|
18
Ryujinx.Graphics.OpenGL/Effects/IScalingFilter.cs
Normal file
18
Ryujinx.Graphics.OpenGL/Effects/IScalingFilter.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Graphics.OpenGL.Image;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Graphics.OpenGL.Effects
|
||||
{
|
||||
internal interface IScalingFilter : IDisposable
|
||||
{
|
||||
float Level { get; set; }
|
||||
void Run(
|
||||
TextureView view,
|
||||
TextureView destinationTexture,
|
||||
int width,
|
||||
int height,
|
||||
Extents2D source,
|
||||
Extents2D destination);
|
||||
}
|
||||
}
|
40
Ryujinx.Graphics.OpenGL/Effects/ShaderHelper.cs
Normal file
40
Ryujinx.Graphics.OpenGL/Effects/ShaderHelper.cs
Normal file
@@ -0,0 +1,40 @@
|
||||
using OpenTK.Graphics.OpenGL;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Graphics.OpenGL.Effects
|
||||
{
|
||||
internal static class ShaderHelper
|
||||
{
|
||||
public static int CompileProgram(string shaderCode, ShaderType shaderType)
|
||||
{
|
||||
var shader = GL.CreateShader(shaderType);
|
||||
GL.ShaderSource(shader, shaderCode);
|
||||
GL.CompileShader(shader);
|
||||
|
||||
var program = GL.CreateProgram();
|
||||
GL.AttachShader(program, shader);
|
||||
GL.LinkProgram(program);
|
||||
|
||||
GL.DetachShader(program, shader);
|
||||
GL.DeleteShader(shader);
|
||||
|
||||
return program;
|
||||
}
|
||||
|
||||
public static int CompileProgram(string[] shaders, ShaderType shaderType)
|
||||
{
|
||||
var shader = GL.CreateShader(shaderType);
|
||||
GL.ShaderSource(shader, shaders.Length, shaders, (int[])null);
|
||||
GL.CompileShader(shader);
|
||||
|
||||
var program = GL.CreateProgram();
|
||||
GL.AttachShader(program, shader);
|
||||
GL.LinkProgram(program);
|
||||
|
||||
GL.DetachShader(program, shader);
|
||||
GL.DeleteShader(shader);
|
||||
|
||||
return program;
|
||||
}
|
||||
}
|
||||
}
|
2656
Ryujinx.Graphics.OpenGL/Effects/Shaders/ffx_a.h
Normal file
2656
Ryujinx.Graphics.OpenGL/Effects/Shaders/ffx_a.h
Normal file
File diff suppressed because it is too large
Load Diff
1199
Ryujinx.Graphics.OpenGL/Effects/Shaders/ffx_fsr1.h
Normal file
1199
Ryujinx.Graphics.OpenGL/Effects/Shaders/ffx_fsr1.h
Normal file
File diff suppressed because it is too large
Load Diff
88
Ryujinx.Graphics.OpenGL/Effects/Shaders/fsr_scaling.glsl
Normal file
88
Ryujinx.Graphics.OpenGL/Effects/Shaders/fsr_scaling.glsl
Normal file
@@ -0,0 +1,88 @@
|
||||
#version 430 core
|
||||
precision mediump float;
|
||||
layout (local_size_x = 64) in;
|
||||
layout(rgba8, binding = 0, location=0) uniform image2D imgOutput;
|
||||
layout( location=1 ) uniform sampler2D Source;
|
||||
layout( location=2 ) uniform float srcX0;
|
||||
layout( location=3 ) uniform float srcX1;
|
||||
layout( location=4 ) uniform float srcY0;
|
||||
layout( location=5 ) uniform float srcY1;
|
||||
layout( location=6 ) uniform float dstX0;
|
||||
layout( location=7 ) uniform float dstX1;
|
||||
layout( location=8 ) uniform float dstY0;
|
||||
layout( location=9 ) uniform float dstY1;
|
||||
layout( location=10 ) uniform float scaleX;
|
||||
layout( location=11 ) uniform float scaleY;
|
||||
|
||||
#define A_GPU 1
|
||||
#define A_GLSL 1
|
||||
#include "ffx_a.h"
|
||||
|
||||
#define FSR_EASU_F 1
|
||||
AU4 con0, con1, con2, con3;
|
||||
float srcW, srcH, dstW, dstH;
|
||||
vec2 bLeft, tRight;
|
||||
|
||||
AF2 translate(AF2 pos) {
|
||||
return AF2(pos.x * scaleX, pos.y * scaleY);
|
||||
}
|
||||
|
||||
void setBounds(vec2 bottomLeft, vec2 topRight) {
|
||||
bLeft = bottomLeft;
|
||||
tRight = topRight;
|
||||
}
|
||||
|
||||
AF2 translateDest(AF2 pos) {
|
||||
AF2 translatedPos = AF2(pos.x, pos.y);
|
||||
translatedPos.x = dstX1 < dstX0 ? dstX1 - translatedPos.x : translatedPos.x;
|
||||
translatedPos.y = dstY0 > dstY1 ? dstY0 + dstY1 - translatedPos.y - 1: translatedPos.y;
|
||||
return translatedPos;
|
||||
}
|
||||
|
||||
AF4 FsrEasuRF(AF2 p) { AF4 res = textureGather(Source, translate(p), 0); return res; }
|
||||
AF4 FsrEasuGF(AF2 p) { AF4 res = textureGather(Source, translate(p), 1); return res; }
|
||||
AF4 FsrEasuBF(AF2 p) { AF4 res = textureGather(Source, translate(p), 2); return res; }
|
||||
|
||||
#include "ffx_fsr1.h"
|
||||
|
||||
float insideBox(vec2 v) {
|
||||
vec2 s = step(bLeft, v) - step(tRight, v);
|
||||
return s.x * s.y;
|
||||
}
|
||||
|
||||
void CurrFilter(AU2 pos)
|
||||
{
|
||||
if((insideBox(vec2(pos.x, pos.y))) == 0) {
|
||||
imageStore(imgOutput, ASU2(pos.x, pos.y), AF4(0,0,0,1));
|
||||
return;
|
||||
}
|
||||
AF3 c;
|
||||
FsrEasuF(c, AU2(pos.x - bLeft.x, pos.y - bLeft.y), con0, con1, con2, con3);
|
||||
imageStore(imgOutput, ASU2(translateDest(pos)), AF4(c, 1));
|
||||
}
|
||||
|
||||
void main() {
|
||||
srcW = abs(srcX1 - srcX0);
|
||||
srcH = abs(srcY1 - srcY0);
|
||||
dstW = abs(dstX1 - dstX0);
|
||||
dstH = abs(dstY1 - dstY0);
|
||||
|
||||
AU2 gxy = ARmp8x8(gl_LocalInvocationID.x) + AU2(gl_WorkGroupID.x << 4u, gl_WorkGroupID.y << 4u);
|
||||
|
||||
setBounds(vec2(dstX0 < dstX1 ? dstX0 : dstX1, dstY0 < dstY1 ? dstY0 : dstY1),
|
||||
vec2(dstX1 > dstX0 ? dstX1 : dstX0, dstY1 > dstY0 ? dstY1 : dstY0));
|
||||
|
||||
// Upscaling
|
||||
FsrEasuCon(con0, con1, con2, con3,
|
||||
srcW, srcH, // Viewport size (top left aligned) in the input image which is to be scaled.
|
||||
srcW, srcH, // The size of the input image.
|
||||
dstW, dstH); // The output resolution.
|
||||
|
||||
CurrFilter(gxy);
|
||||
gxy.x += 8u;
|
||||
CurrFilter(gxy);
|
||||
gxy.y += 8u;
|
||||
CurrFilter(gxy);
|
||||
gxy.x -= 8u;
|
||||
CurrFilter(gxy);
|
||||
}
|
37
Ryujinx.Graphics.OpenGL/Effects/Shaders/fsr_sharpening.glsl
Normal file
37
Ryujinx.Graphics.OpenGL/Effects/Shaders/fsr_sharpening.glsl
Normal file
@@ -0,0 +1,37 @@
|
||||
#version 430 core
|
||||
precision mediump float;
|
||||
layout (local_size_x = 64) in;
|
||||
layout(rgba8, binding = 0, location=0) uniform image2D imgOutput;
|
||||
layout( location=1 ) uniform sampler2D source;
|
||||
layout( location=2 ) uniform float sharpening;
|
||||
|
||||
#define A_GPU 1
|
||||
#define A_GLSL 1
|
||||
#include "ffx_a.h"
|
||||
|
||||
#define FSR_RCAS_F 1
|
||||
AU4 con0;
|
||||
|
||||
AF4 FsrRcasLoadF(ASU2 p) { return AF4(texelFetch(source, p, 0)); }
|
||||
void FsrRcasInputF(inout AF1 r, inout AF1 g, inout AF1 b) {}
|
||||
|
||||
#include "ffx_fsr1.h"
|
||||
|
||||
void CurrFilter(AU2 pos)
|
||||
{
|
||||
AF3 c;
|
||||
FsrRcasF(c.r, c.g, c.b, pos, con0);
|
||||
imageStore(imgOutput, ASU2(pos), AF4(c, 1));
|
||||
}
|
||||
|
||||
void main() {
|
||||
FsrRcasCon(con0, sharpening);
|
||||
AU2 gxy = ARmp8x8(gl_LocalInvocationID.x) + AU2(gl_WorkGroupID.x << 4u, gl_WorkGroupID.y << 4u);
|
||||
CurrFilter(gxy);
|
||||
gxy.x += 8u;
|
||||
CurrFilter(gxy);
|
||||
gxy.y += 8u;
|
||||
CurrFilter(gxy);
|
||||
gxy.x -= 8u;
|
||||
CurrFilter(gxy);
|
||||
}
|
1174
Ryujinx.Graphics.OpenGL/Effects/Shaders/fxaa.glsl
Normal file
1174
Ryujinx.Graphics.OpenGL/Effects/Shaders/fxaa.glsl
Normal file
File diff suppressed because it is too large
Load Diff
1361
Ryujinx.Graphics.OpenGL/Effects/Shaders/smaa.hlsl
Normal file
1361
Ryujinx.Graphics.OpenGL/Effects/Shaders/smaa.hlsl
Normal file
File diff suppressed because it is too large
Load Diff
26
Ryujinx.Graphics.OpenGL/Effects/Shaders/smaa_blend.glsl
Normal file
26
Ryujinx.Graphics.OpenGL/Effects/Shaders/smaa_blend.glsl
Normal file
@@ -0,0 +1,26 @@
|
||||
layout(rgba8, binding = 0) uniform image2D imgOutput;
|
||||
|
||||
uniform sampler2D inputTexture;
|
||||
layout( location=0 ) uniform vec2 invResolution;
|
||||
uniform sampler2D samplerArea;
|
||||
uniform sampler2D samplerSearch;
|
||||
|
||||
void main() {
|
||||
ivec2 loc = ivec2(gl_GlobalInvocationID.x * 4, gl_GlobalInvocationID.y * 4);
|
||||
for(int i = 0; i < 4; i++)
|
||||
{
|
||||
for(int j = 0; j < 4; j++)
|
||||
{
|
||||
ivec2 texelCoord = ivec2(loc.x + i, loc.y + j);
|
||||
vec2 coord = (texelCoord + vec2(0.5)) / invResolution;
|
||||
vec2 pixCoord;
|
||||
vec4 offset[3];
|
||||
|
||||
SMAABlendingWeightCalculationVS(coord, pixCoord, offset);
|
||||
|
||||
vec4 oColor = SMAABlendingWeightCalculationPS(coord, pixCoord, offset, inputTexture, samplerArea, samplerSearch, ivec4(0));
|
||||
|
||||
imageStore(imgOutput, texelCoord, oColor);
|
||||
}
|
||||
}
|
||||
}
|
24
Ryujinx.Graphics.OpenGL/Effects/Shaders/smaa_edge.glsl
Normal file
24
Ryujinx.Graphics.OpenGL/Effects/Shaders/smaa_edge.glsl
Normal file
@@ -0,0 +1,24 @@
|
||||
layout(rgba8, binding = 0) uniform image2D imgOutput;
|
||||
|
||||
uniform sampler2D inputTexture;
|
||||
layout( location=0 ) uniform vec2 invResolution;
|
||||
|
||||
void main()
|
||||
{
|
||||
vec2 loc = ivec2(gl_GlobalInvocationID.x * 4, gl_GlobalInvocationID.y * 4);
|
||||
for(int i = 0; i < 4; i++)
|
||||
{
|
||||
for(int j = 0; j < 4; j++)
|
||||
{
|
||||
ivec2 texelCoord = ivec2(loc.x + i, loc.y + j);
|
||||
vec2 coord = (texelCoord + vec2(0.5)) / invResolution;
|
||||
vec4 offset[3];
|
||||
SMAAEdgeDetectionVS(coord, offset);
|
||||
vec2 oColor = SMAAColorEdgeDetectionPS(coord, offset, inputTexture);
|
||||
if (oColor != float2(-2.0, -2.0))
|
||||
{
|
||||
imageStore(imgOutput, texelCoord, vec4(oColor, 0.0, 1.0));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
26
Ryujinx.Graphics.OpenGL/Effects/Shaders/smaa_neighbour.glsl
Normal file
26
Ryujinx.Graphics.OpenGL/Effects/Shaders/smaa_neighbour.glsl
Normal file
@@ -0,0 +1,26 @@
|
||||
layout(rgba8, binding = 0) uniform image2D imgOutput;
|
||||
|
||||
uniform sampler2D inputTexture;
|
||||
layout( location=0 ) uniform vec2 invResolution;
|
||||
uniform sampler2D samplerBlend;
|
||||
|
||||
void main() {
|
||||
vec2 loc = ivec2(gl_GlobalInvocationID.x * 4, gl_GlobalInvocationID.y * 4);
|
||||
for(int i = 0; i < 4; i++)
|
||||
{
|
||||
for(int j = 0; j < 4; j++)
|
||||
{
|
||||
ivec2 texelCoord = ivec2(loc.x + i, loc.y + j);
|
||||
vec2 coord = (texelCoord + vec2(0.5)) / invResolution;
|
||||
vec2 pixCoord;
|
||||
vec4 offset;
|
||||
|
||||
SMAANeighborhoodBlendingVS(coord, offset);
|
||||
|
||||
vec4 oColor = SMAANeighborhoodBlendingPS(coord, offset, inputTexture, samplerBlend);
|
||||
|
||||
imageStore(imgOutput, texelCoord, oColor);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
261
Ryujinx.Graphics.OpenGL/Effects/SmaaPostProcessingEffect.cs
Normal file
261
Ryujinx.Graphics.OpenGL/Effects/SmaaPostProcessingEffect.cs
Normal file
@@ -0,0 +1,261 @@
|
||||
using OpenTK.Graphics.OpenGL;
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Graphics.OpenGL.Image;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Graphics.OpenGL.Effects.Smaa
|
||||
{
|
||||
internal partial class SmaaPostProcessingEffect : IPostProcessingEffect
|
||||
{
|
||||
public const int AreaWidth = 160;
|
||||
public const int AreaHeight = 560;
|
||||
public const int SearchWidth = 64;
|
||||
public const int SearchHeight = 16;
|
||||
|
||||
private readonly OpenGLRenderer _renderer;
|
||||
private TextureStorage _outputTexture;
|
||||
private TextureStorage _searchTexture;
|
||||
private TextureStorage _areaTexture;
|
||||
private int[] _edgeShaderPrograms;
|
||||
private int[] _blendShaderPrograms;
|
||||
private int[] _neighbourShaderPrograms;
|
||||
private TextureStorage _edgeOutputTexture;
|
||||
private TextureStorage _blendOutputTexture;
|
||||
private string[] _qualities;
|
||||
private int _inputUniform;
|
||||
private int _outputUniform;
|
||||
private int _samplerAreaUniform;
|
||||
private int _samplerSearchUniform;
|
||||
private int _samplerBlendUniform;
|
||||
private int _resolutionUniform;
|
||||
private int _quality = 1;
|
||||
|
||||
public int Quality
|
||||
{
|
||||
get => _quality; set
|
||||
{
|
||||
_quality = Math.Clamp(value, 0, _qualities.Length - 1);
|
||||
}
|
||||
}
|
||||
public SmaaPostProcessingEffect(OpenGLRenderer renderer, int quality)
|
||||
{
|
||||
_renderer = renderer;
|
||||
|
||||
_edgeShaderPrograms = Array.Empty<int>();
|
||||
_blendShaderPrograms = Array.Empty<int>();
|
||||
_neighbourShaderPrograms = Array.Empty<int>();
|
||||
|
||||
_qualities = new string[] { "SMAA_PRESET_LOW", "SMAA_PRESET_MEDIUM", "SMAA_PRESET_HIGH", "SMAA_PRESET_ULTRA" };
|
||||
|
||||
Quality = quality;
|
||||
|
||||
Initialize();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_searchTexture?.Dispose();
|
||||
_areaTexture?.Dispose();
|
||||
_outputTexture?.Dispose();
|
||||
_edgeOutputTexture?.Dispose();
|
||||
_blendOutputTexture?.Dispose();
|
||||
|
||||
DeleteShaders();
|
||||
}
|
||||
|
||||
private void DeleteShaders()
|
||||
{
|
||||
for (int i = 0; i < _edgeShaderPrograms.Length; i++)
|
||||
{
|
||||
GL.DeleteProgram(_edgeShaderPrograms[i]);
|
||||
GL.DeleteProgram(_blendShaderPrograms[i]);
|
||||
GL.DeleteProgram(_neighbourShaderPrograms[i]);
|
||||
}
|
||||
}
|
||||
|
||||
private unsafe void RecreateShaders(int width, int height)
|
||||
{
|
||||
string baseShader = EmbeddedResources.ReadAllText("Ryujinx.Graphics.OpenGL/Effects/Shaders/smaa.hlsl");
|
||||
var pixelSizeDefine = $"#define SMAA_RT_METRICS float4(1.0 / {width}.0, 1.0 / {height}.0, {width}, {height}) \n";
|
||||
|
||||
_edgeShaderPrograms = new int[_qualities.Length];
|
||||
_blendShaderPrograms = new int[_qualities.Length];
|
||||
_neighbourShaderPrograms = new int[_qualities.Length];
|
||||
|
||||
for (int i = 0; i < +_edgeShaderPrograms.Length; i++)
|
||||
{
|
||||
var presets = $"#version 430 core \n#define {_qualities[i]} 1 \n{pixelSizeDefine}#define SMAA_GLSL_4 1 \nlayout (local_size_x = 16, local_size_y = 16) in;\n{baseShader}";
|
||||
|
||||
var edgeShaderData = EmbeddedResources.ReadAllText("Ryujinx.Graphics.OpenGL/Effects/Shaders/smaa_edge.glsl");
|
||||
var blendShaderData = EmbeddedResources.ReadAllText("Ryujinx.Graphics.OpenGL/Effects/Shaders/smaa_blend.glsl");
|
||||
var neighbourShaderData = EmbeddedResources.ReadAllText("Ryujinx.Graphics.OpenGL/Effects/Shaders/smaa_neighbour.glsl");
|
||||
|
||||
var shaders = new string[] { presets, edgeShaderData };
|
||||
var edgeProgram = ShaderHelper.CompileProgram(shaders, ShaderType.ComputeShader);
|
||||
|
||||
shaders[1] = blendShaderData;
|
||||
var blendProgram = ShaderHelper.CompileProgram(shaders, ShaderType.ComputeShader);
|
||||
|
||||
shaders[1] = neighbourShaderData;
|
||||
var neighbourProgram = ShaderHelper.CompileProgram(shaders, ShaderType.ComputeShader);
|
||||
|
||||
_edgeShaderPrograms[i] = edgeProgram;
|
||||
_blendShaderPrograms[i] = blendProgram;
|
||||
_neighbourShaderPrograms[i] = neighbourProgram;
|
||||
}
|
||||
|
||||
_inputUniform = GL.GetUniformLocation(_edgeShaderPrograms[0], "inputTexture");
|
||||
_outputUniform = GL.GetUniformLocation(_edgeShaderPrograms[0], "imgOutput");
|
||||
_samplerAreaUniform = GL.GetUniformLocation(_blendShaderPrograms[0], "samplerArea");
|
||||
_samplerSearchUniform = GL.GetUniformLocation(_blendShaderPrograms[0], "samplerSearch");
|
||||
_samplerBlendUniform = GL.GetUniformLocation(_neighbourShaderPrograms[0], "samplerBlend");
|
||||
_resolutionUniform = GL.GetUniformLocation(_edgeShaderPrograms[0], "invResolution");
|
||||
}
|
||||
|
||||
private void Initialize()
|
||||
{
|
||||
var areaInfo = new TextureCreateInfo(AreaWidth,
|
||||
AreaHeight,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
Format.R8G8Unorm,
|
||||
DepthStencilMode.Depth,
|
||||
Target.Texture2D,
|
||||
SwizzleComponent.Red,
|
||||
SwizzleComponent.Green,
|
||||
SwizzleComponent.Blue,
|
||||
SwizzleComponent.Alpha);
|
||||
|
||||
var searchInfo = new TextureCreateInfo(SearchWidth,
|
||||
SearchHeight,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
Format.R8Unorm,
|
||||
DepthStencilMode.Depth,
|
||||
Target.Texture2D,
|
||||
SwizzleComponent.Red,
|
||||
SwizzleComponent.Green,
|
||||
SwizzleComponent.Blue,
|
||||
SwizzleComponent.Alpha);
|
||||
|
||||
_areaTexture = new TextureStorage(_renderer, areaInfo, 1);
|
||||
_searchTexture = new TextureStorage(_renderer, searchInfo, 1);
|
||||
|
||||
var areaTexture = EmbeddedResources.Read("Ryujinx.Graphics.OpenGL/Effects/Textures/SmaaAreaTexture.bin");
|
||||
var searchTexture = EmbeddedResources.Read("Ryujinx.Graphics.OpenGL/Effects/Textures/SmaaSearchTexture.bin");
|
||||
|
||||
var areaView = _areaTexture.CreateDefaultView();
|
||||
var searchView = _searchTexture.CreateDefaultView();
|
||||
|
||||
areaView.SetData(areaTexture);
|
||||
searchView.SetData(searchTexture);
|
||||
}
|
||||
|
||||
public TextureView Run(TextureView view, int width, int height)
|
||||
{
|
||||
if (_outputTexture == null || _outputTexture.Info.Width != view.Width || _outputTexture.Info.Height != view.Height)
|
||||
{
|
||||
_outputTexture?.Dispose();
|
||||
_outputTexture = new TextureStorage(_renderer, view.Info, view.ScaleFactor);
|
||||
_outputTexture.CreateDefaultView();
|
||||
_edgeOutputTexture = new TextureStorage(_renderer, view.Info, view.ScaleFactor);
|
||||
_edgeOutputTexture.CreateDefaultView();
|
||||
_blendOutputTexture = new TextureStorage(_renderer, view.Info, view.ScaleFactor);
|
||||
_blendOutputTexture.CreateDefaultView();
|
||||
|
||||
DeleteShaders();
|
||||
|
||||
RecreateShaders(view.Width, view.Height);
|
||||
}
|
||||
|
||||
var textureView = _outputTexture.CreateView(view.Info, 0, 0) as TextureView;
|
||||
var edgeOutput = _edgeOutputTexture.DefaultView as TextureView;
|
||||
var blendOutput = _blendOutputTexture.DefaultView as TextureView;
|
||||
var areaTexture = _areaTexture.DefaultView as TextureView;
|
||||
var searchTexture = _searchTexture.DefaultView as TextureView;
|
||||
|
||||
var previousFramebuffer = GL.GetInteger(GetPName.FramebufferBinding);
|
||||
int previousUnit = GL.GetInteger(GetPName.ActiveTexture);
|
||||
GL.ActiveTexture(TextureUnit.Texture0);
|
||||
int previousTextureBinding0 = GL.GetInteger(GetPName.TextureBinding2D);
|
||||
GL.ActiveTexture(TextureUnit.Texture1);
|
||||
int previousTextureBinding1 = GL.GetInteger(GetPName.TextureBinding2D);
|
||||
GL.ActiveTexture(TextureUnit.Texture2);
|
||||
int previousTextureBinding2 = GL.GetInteger(GetPName.TextureBinding2D);
|
||||
|
||||
var framebuffer = new Framebuffer();
|
||||
framebuffer.Bind();
|
||||
framebuffer.AttachColor(0, edgeOutput);
|
||||
GL.Clear(ClearBufferMask.ColorBufferBit);
|
||||
GL.ClearColor(0, 0, 0, 0);
|
||||
framebuffer.AttachColor(0, blendOutput);
|
||||
GL.Clear(ClearBufferMask.ColorBufferBit);
|
||||
GL.ClearColor(0, 0, 0, 0);
|
||||
|
||||
GL.BindFramebuffer(FramebufferTarget.Framebuffer, previousFramebuffer);
|
||||
|
||||
framebuffer.Dispose();
|
||||
|
||||
var dispatchX = BitUtils.DivRoundUp(view.Width, IPostProcessingEffect.LocalGroupSize);
|
||||
var dispatchY = BitUtils.DivRoundUp(view.Height, IPostProcessingEffect.LocalGroupSize);
|
||||
|
||||
int previousProgram = GL.GetInteger(GetPName.CurrentProgram);
|
||||
GL.BindImageTexture(0, edgeOutput.Handle, 0, false, 0, TextureAccess.ReadWrite, SizedInternalFormat.Rgba8);
|
||||
GL.UseProgram(_edgeShaderPrograms[Quality]);
|
||||
view.Bind(0);
|
||||
GL.Uniform1(_inputUniform, 0);
|
||||
GL.Uniform1(_outputUniform, 0);
|
||||
GL.Uniform2(_resolutionUniform, (float)view.Width, (float)view.Height);
|
||||
GL.DispatchCompute(dispatchX, dispatchY, 1);
|
||||
GL.MemoryBarrier(MemoryBarrierFlags.ShaderImageAccessBarrierBit);
|
||||
|
||||
GL.BindImageTexture(0, blendOutput.Handle, 0, false, 0, TextureAccess.ReadWrite, SizedInternalFormat.Rgba8);
|
||||
GL.UseProgram(_blendShaderPrograms[Quality]);
|
||||
edgeOutput.Bind(0);
|
||||
areaTexture.Bind(1);
|
||||
searchTexture.Bind(2);
|
||||
GL.Uniform1(_inputUniform, 0);
|
||||
GL.Uniform1(_outputUniform, 0);
|
||||
GL.Uniform1(_samplerAreaUniform, 1);
|
||||
GL.Uniform1(_samplerSearchUniform, 2);
|
||||
GL.Uniform2(_resolutionUniform, (float)view.Width, (float)view.Height);
|
||||
GL.DispatchCompute(dispatchX, dispatchY, 1);
|
||||
GL.MemoryBarrier(MemoryBarrierFlags.ShaderImageAccessBarrierBit);
|
||||
|
||||
GL.BindImageTexture(0, textureView.Handle, 0, false, 0, TextureAccess.ReadWrite, SizedInternalFormat.Rgba8);
|
||||
GL.UseProgram(_neighbourShaderPrograms[Quality]);
|
||||
view.Bind(0);
|
||||
blendOutput.Bind(1);
|
||||
GL.Uniform1(_inputUniform, 0);
|
||||
GL.Uniform1(_outputUniform, 0);
|
||||
GL.Uniform1(_samplerBlendUniform, 1);
|
||||
GL.Uniform2(_resolutionUniform, (float)view.Width, (float)view.Height);
|
||||
GL.DispatchCompute(dispatchX, dispatchY, 1);
|
||||
GL.MemoryBarrier(MemoryBarrierFlags.ShaderImageAccessBarrierBit);
|
||||
|
||||
(_renderer.Pipeline as Pipeline).RestoreImages1And2();
|
||||
|
||||
GL.UseProgram(previousProgram);
|
||||
|
||||
GL.ActiveTexture(TextureUnit.Texture0);
|
||||
GL.BindTexture(TextureTarget.Texture2D, previousTextureBinding0);
|
||||
GL.ActiveTexture(TextureUnit.Texture1);
|
||||
GL.BindTexture(TextureTarget.Texture2D, previousTextureBinding1);
|
||||
GL.ActiveTexture(TextureUnit.Texture2);
|
||||
GL.BindTexture(TextureTarget.Texture2D, previousTextureBinding2);
|
||||
|
||||
GL.ActiveTexture((TextureUnit)previousUnit);
|
||||
|
||||
return textureView;
|
||||
}
|
||||
}
|
||||
}
|
BIN
Ryujinx.Graphics.OpenGL/Effects/Textures/SmaaAreaTexture.bin
Normal file
BIN
Ryujinx.Graphics.OpenGL/Effects/Textures/SmaaAreaTexture.bin
Normal file
Binary file not shown.
BIN
Ryujinx.Graphics.OpenGL/Effects/Textures/SmaaSearchTexture.bin
Normal file
BIN
Ryujinx.Graphics.OpenGL/Effects/Textures/SmaaSearchTexture.bin
Normal file
Binary file not shown.
@@ -124,6 +124,7 @@ namespace Ryujinx.Graphics.OpenGL
|
||||
supportsBlendEquationAdvanced: HwCapabilities.SupportsBlendEquationAdvanced,
|
||||
supportsFragmentShaderInterlock: HwCapabilities.SupportsFragmentShaderInterlock,
|
||||
supportsFragmentShaderOrderingIntel: HwCapabilities.SupportsFragmentShaderOrdering,
|
||||
supportsGeometryShader: true,
|
||||
supportsGeometryShaderPassthrough: HwCapabilities.SupportsGeometryShaderPassthrough,
|
||||
supportsImageLoadFormatted: HwCapabilities.SupportsImageLoadFormatted,
|
||||
supportsLayerVertexTessellation: HwCapabilities.SupportsShaderViewportLayerArray,
|
||||
|
@@ -1,10 +1,10 @@
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using OpenTK.Graphics.OpenGL;
|
||||
using OpenTK.Graphics.OpenGL;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Graphics.OpenGL.Image;
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Graphics.OpenGL
|
||||
{
|
||||
|
@@ -9,6 +9,20 @@
|
||||
<PackageReference Include="OpenTK.Graphics" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Effects\Textures\SmaaAreaTexture.bin" />
|
||||
<EmbeddedResource Include="Effects\Textures\SmaaSearchTexture.bin" />
|
||||
<EmbeddedResource Include="Effects\Shaders\fsr_sharpening.glsl" />
|
||||
<EmbeddedResource Include="Effects\Shaders\fxaa.glsl" />
|
||||
<EmbeddedResource Include="Effects\Shaders\smaa.hlsl" />
|
||||
<EmbeddedResource Include="Effects\Shaders\smaa_blend.glsl" />
|
||||
<EmbeddedResource Include="Effects\Shaders\smaa_edge.glsl" />
|
||||
<EmbeddedResource Include="Effects\Shaders\smaa_neighbour.glsl" />
|
||||
<EmbeddedResource Include="Effects\Shaders\ffx_fsr1.h" />
|
||||
<EmbeddedResource Include="Effects\Shaders\ffx_a.h" />
|
||||
<EmbeddedResource Include="Effects\Shaders\fsr_scaling.glsl" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Ryujinx.Common\Ryujinx.Common.csproj" />
|
||||
<ProjectReference Include="..\Ryujinx.Graphics.GAL\Ryujinx.Graphics.GAL.csproj" />
|
||||
|
@@ -1,5 +1,7 @@
|
||||
using OpenTK.Graphics.OpenGL;
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Graphics.OpenGL.Effects;
|
||||
using Ryujinx.Graphics.OpenGL.Effects.Smaa;
|
||||
using Ryujinx.Graphics.OpenGL.Image;
|
||||
using System;
|
||||
|
||||
@@ -7,14 +9,24 @@ namespace Ryujinx.Graphics.OpenGL
|
||||
{
|
||||
class Window : IWindow, IDisposable
|
||||
{
|
||||
private const int TextureCount = 3;
|
||||
private readonly OpenGLRenderer _renderer;
|
||||
|
||||
private bool _initialized;
|
||||
|
||||
private int _width;
|
||||
private int _height;
|
||||
private bool _updateSize;
|
||||
private int _copyFramebufferHandle;
|
||||
private IPostProcessingEffect _antiAliasing;
|
||||
private IScalingFilter _scalingFilter;
|
||||
private bool _isLinear;
|
||||
private AntiAliasing _currentAntiAliasing;
|
||||
private bool _updateEffect;
|
||||
private ScalingFilter _currentScalingFilter;
|
||||
private float _scalingFilterLevel;
|
||||
private bool _updateScalingFilter;
|
||||
private bool _isBgra;
|
||||
private TextureView _upscaledTexture;
|
||||
|
||||
internal BackgroundContextWorker BackgroundContext { get; private set; }
|
||||
|
||||
@@ -48,6 +60,8 @@ namespace Ryujinx.Graphics.OpenGL
|
||||
{
|
||||
_width = width;
|
||||
_height = height;
|
||||
|
||||
_updateSize = true;
|
||||
}
|
||||
|
||||
private void CopyTextureToFrameBufferRGB(int drawFramebuffer, int readFramebuffer, TextureView view, ImageCrop crop, Action swapBuffersCallback)
|
||||
@@ -57,6 +71,32 @@ namespace Ryujinx.Graphics.OpenGL
|
||||
|
||||
TextureView viewConverted = view.Format.IsBgr() ? _renderer.TextureCopy.BgraSwap(view) : view;
|
||||
|
||||
UpdateEffect();
|
||||
|
||||
if (_antiAliasing != null)
|
||||
{
|
||||
var oldView = viewConverted;
|
||||
|
||||
viewConverted = _antiAliasing.Run(viewConverted, _width, _height);
|
||||
|
||||
if (viewConverted.Format.IsBgr())
|
||||
{
|
||||
var swappedView = _renderer.TextureCopy.BgraSwap(viewConverted);
|
||||
|
||||
viewConverted?.Dispose();
|
||||
|
||||
viewConverted = swappedView;
|
||||
}
|
||||
|
||||
if (viewConverted != oldView && oldView != view)
|
||||
{
|
||||
oldView.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
GL.BindFramebuffer(FramebufferTarget.DrawFramebuffer, drawFramebuffer);
|
||||
GL.BindFramebuffer(FramebufferTarget.ReadFramebuffer, readFramebuffer);
|
||||
|
||||
GL.FramebufferTexture(
|
||||
FramebufferTarget.ReadFramebuffer,
|
||||
FramebufferAttachment.ColorAttachment0,
|
||||
@@ -71,12 +111,12 @@ namespace Ryujinx.Graphics.OpenGL
|
||||
GL.Clear(ClearBufferMask.ColorBufferBit);
|
||||
|
||||
int srcX0, srcX1, srcY0, srcY1;
|
||||
float scale = view.ScaleFactor;
|
||||
float scale = viewConverted.ScaleFactor;
|
||||
|
||||
if (crop.Left == 0 && crop.Right == 0)
|
||||
{
|
||||
srcX0 = 0;
|
||||
srcX1 = (int)(view.Width / scale);
|
||||
srcX1 = (int)(viewConverted.Width / scale);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -87,7 +127,7 @@ namespace Ryujinx.Graphics.OpenGL
|
||||
if (crop.Top == 0 && crop.Bottom == 0)
|
||||
{
|
||||
srcY0 = 0;
|
||||
srcY1 = (int)(view.Height / scale);
|
||||
srcY1 = (int)(viewConverted.Height / scale);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -125,6 +165,42 @@ namespace Ryujinx.Graphics.OpenGL
|
||||
ScreenCaptureRequested = false;
|
||||
}
|
||||
|
||||
if (_scalingFilter != null)
|
||||
{
|
||||
if (viewConverted.Format.IsBgr() && !_isBgra)
|
||||
{
|
||||
RecreateUpscalingTexture(true);
|
||||
}
|
||||
|
||||
_scalingFilter.Run(
|
||||
viewConverted,
|
||||
_upscaledTexture,
|
||||
_width,
|
||||
_height,
|
||||
new Extents2D(
|
||||
srcX0,
|
||||
srcY0,
|
||||
srcX1,
|
||||
srcY1),
|
||||
new Extents2D(
|
||||
dstX0,
|
||||
dstY0,
|
||||
dstX1,
|
||||
dstY1)
|
||||
);
|
||||
|
||||
srcX0 = dstX0;
|
||||
srcY0 = dstY0;
|
||||
srcX1 = dstX1;
|
||||
srcY1 = dstY1;
|
||||
|
||||
GL.FramebufferTexture(
|
||||
FramebufferTarget.ReadFramebuffer,
|
||||
FramebufferAttachment.ColorAttachment0,
|
||||
_upscaledTexture.Handle,
|
||||
0);
|
||||
}
|
||||
|
||||
GL.BlitFramebuffer(
|
||||
srcX0,
|
||||
srcY0,
|
||||
@@ -135,7 +211,7 @@ namespace Ryujinx.Graphics.OpenGL
|
||||
dstX1,
|
||||
dstY1,
|
||||
ClearBufferMask.ColorBufferBit,
|
||||
BlitFramebufferFilter.Linear);
|
||||
_isLinear ? BlitFramebufferFilter.Linear : BlitFramebufferFilter.Nearest);
|
||||
|
||||
// Remove Alpha channel
|
||||
GL.ColorMask(false, false, false, true);
|
||||
@@ -209,6 +285,135 @@ namespace Ryujinx.Graphics.OpenGL
|
||||
|
||||
_copyFramebufferHandle = 0;
|
||||
}
|
||||
|
||||
_antiAliasing?.Dispose();
|
||||
_scalingFilter?.Dispose();
|
||||
_upscaledTexture?.Dispose();
|
||||
}
|
||||
|
||||
public void SetAntiAliasing(AntiAliasing effect)
|
||||
{
|
||||
if (_currentAntiAliasing == effect && _antiAliasing != null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_currentAntiAliasing = effect;
|
||||
|
||||
_updateEffect = true;
|
||||
}
|
||||
|
||||
public void SetScalingFilter(ScalingFilter type)
|
||||
{
|
||||
if (_currentScalingFilter == type && _antiAliasing != null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_currentScalingFilter = type;
|
||||
|
||||
_updateScalingFilter = true;
|
||||
}
|
||||
|
||||
private void UpdateEffect()
|
||||
{
|
||||
if (_updateEffect)
|
||||
{
|
||||
_updateEffect = false;
|
||||
|
||||
switch (_currentAntiAliasing)
|
||||
{
|
||||
case AntiAliasing.Fxaa:
|
||||
_antiAliasing?.Dispose();
|
||||
_antiAliasing = new FxaaPostProcessingEffect(_renderer);
|
||||
break;
|
||||
case AntiAliasing.None:
|
||||
_antiAliasing?.Dispose();
|
||||
_antiAliasing = null;
|
||||
break;
|
||||
case AntiAliasing.SmaaLow:
|
||||
case AntiAliasing.SmaaMedium:
|
||||
case AntiAliasing.SmaaHigh:
|
||||
case AntiAliasing.SmaaUltra:
|
||||
var quality = _currentAntiAliasing - AntiAliasing.SmaaLow;
|
||||
if (_antiAliasing is SmaaPostProcessingEffect smaa)
|
||||
{
|
||||
smaa.Quality = quality;
|
||||
}
|
||||
else
|
||||
{
|
||||
_antiAliasing?.Dispose();
|
||||
_antiAliasing = new SmaaPostProcessingEffect(_renderer, quality);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (_updateSize && !_updateScalingFilter)
|
||||
{
|
||||
RecreateUpscalingTexture();
|
||||
}
|
||||
|
||||
_updateSize = false;
|
||||
|
||||
if (_updateScalingFilter)
|
||||
{
|
||||
_updateScalingFilter = false;
|
||||
|
||||
switch (_currentScalingFilter)
|
||||
{
|
||||
case ScalingFilter.Bilinear:
|
||||
case ScalingFilter.Nearest:
|
||||
_scalingFilter?.Dispose();
|
||||
_scalingFilter = null;
|
||||
_isLinear = _currentScalingFilter == ScalingFilter.Bilinear;
|
||||
_upscaledTexture?.Dispose();
|
||||
_upscaledTexture = null;
|
||||
break;
|
||||
case ScalingFilter.Fsr:
|
||||
if (_scalingFilter is not FsrScalingFilter)
|
||||
{
|
||||
_scalingFilter?.Dispose();
|
||||
_scalingFilter = new FsrScalingFilter(_renderer, _antiAliasing);
|
||||
}
|
||||
_isLinear = false;
|
||||
_scalingFilter.Level = _scalingFilterLevel;
|
||||
|
||||
RecreateUpscalingTexture();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void RecreateUpscalingTexture(bool forceBgra = false)
|
||||
{
|
||||
_upscaledTexture?.Dispose();
|
||||
|
||||
var info = new TextureCreateInfo(
|
||||
_width,
|
||||
_height,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
Format.R8G8B8A8Unorm,
|
||||
DepthStencilMode.Depth,
|
||||
Target.Texture2D,
|
||||
forceBgra ? SwizzleComponent.Blue : SwizzleComponent.Red,
|
||||
SwizzleComponent.Green,
|
||||
forceBgra ? SwizzleComponent.Red : SwizzleComponent.Blue,
|
||||
SwizzleComponent.Alpha);
|
||||
|
||||
_isBgra = forceBgra;
|
||||
_upscaledTexture = _renderer.CreateTexture(info, 1) as TextureView;
|
||||
}
|
||||
|
||||
public void SetScalingFilterLevel(float level)
|
||||
{
|
||||
_scalingFilterLevel = level;
|
||||
_updateScalingFilter = true;
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,7 +1,7 @@
|
||||
using FuncUnaryInstruction = System.Func<Spv.Generator.Instruction, Spv.Generator.Instruction, Spv.Generator.Instruction>;
|
||||
using FuncBinaryInstruction = System.Func<Spv.Generator.Instruction, Spv.Generator.Instruction, Spv.Generator.Instruction, Spv.Generator.Instruction>;
|
||||
using FuncTernaryInstruction = System.Func<Spv.Generator.Instruction, Spv.Generator.Instruction, Spv.Generator.Instruction, Spv.Generator.Instruction, Spv.Generator.Instruction>;
|
||||
using FuncBinaryInstruction = System.Func<Spv.Generator.Instruction, Spv.Generator.Instruction, Spv.Generator.Instruction, Spv.Generator.Instruction>;
|
||||
using FuncQuaternaryInstruction = System.Func<Spv.Generator.Instruction, Spv.Generator.Instruction, Spv.Generator.Instruction, Spv.Generator.Instruction, Spv.Generator.Instruction, Spv.Generator.Instruction>;
|
||||
using FuncTernaryInstruction = System.Func<Spv.Generator.Instruction, Spv.Generator.Instruction, Spv.Generator.Instruction, Spv.Generator.Instruction, Spv.Generator.Instruction>;
|
||||
using FuncUnaryInstruction = System.Func<Spv.Generator.Instruction, Spv.Generator.Instruction, Spv.Generator.Instruction>;
|
||||
|
||||
namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
||||
{
|
||||
|
@@ -9,9 +9,8 @@ using static Spv.Specification;
|
||||
namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
||||
{
|
||||
using SpvInstruction = Spv.Generator.Instruction;
|
||||
using SpvLiteralInteger = Spv.Generator.LiteralInteger;
|
||||
|
||||
using SpvInstructionPool = Spv.Generator.GeneratorPool<Spv.Generator.Instruction>;
|
||||
using SpvLiteralInteger = Spv.Generator.LiteralInteger;
|
||||
using SpvLiteralIntegerPool = Spv.Generator.GeneratorPool<Spv.Generator.LiteralInteger>;
|
||||
|
||||
static class SpirvGenerator
|
||||
|
@@ -259,6 +259,15 @@ namespace Ryujinx.Graphics.Shader
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queries host GPU geometry shader support.
|
||||
/// </summary>
|
||||
/// <returns>True if the GPU and driver supports geometry shaders, false otherwise</returns>
|
||||
bool QueryHostSupportsGeometryShader()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queries host GPU geometry shader passthrough support.
|
||||
/// </summary>
|
||||
|
8
Ryujinx.Graphics.Shader/ShaderIdentification.cs
Normal file
8
Ryujinx.Graphics.Shader/ShaderIdentification.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace Ryujinx.Graphics.Shader
|
||||
{
|
||||
public enum ShaderIdentification
|
||||
{
|
||||
None,
|
||||
GeometryLayerPassthrough
|
||||
}
|
||||
}
|
@@ -10,6 +10,8 @@ namespace Ryujinx.Graphics.Shader
|
||||
public ReadOnlyCollection<TextureDescriptor> Textures { get; }
|
||||
public ReadOnlyCollection<TextureDescriptor> Images { get; }
|
||||
|
||||
public ShaderIdentification Identification { get; }
|
||||
public int GpLayerInputAttribute { get; }
|
||||
public ShaderStage Stage { get; }
|
||||
public bool UsesInstanceId { get; }
|
||||
public bool UsesDrawParameters { get; }
|
||||
@@ -22,6 +24,8 @@ namespace Ryujinx.Graphics.Shader
|
||||
BufferDescriptor[] sBuffers,
|
||||
TextureDescriptor[] textures,
|
||||
TextureDescriptor[] images,
|
||||
ShaderIdentification identification,
|
||||
int gpLayerInputAttribute,
|
||||
ShaderStage stage,
|
||||
bool usesInstanceId,
|
||||
bool usesDrawParameters,
|
||||
@@ -34,6 +38,8 @@ namespace Ryujinx.Graphics.Shader
|
||||
Textures = Array.AsReadOnly(textures);
|
||||
Images = Array.AsReadOnly(images);
|
||||
|
||||
Identification = identification;
|
||||
GpLayerInputAttribute = gpLayerInputAttribute;
|
||||
Stage = stage;
|
||||
UsesInstanceId = usesInstanceId;
|
||||
UsesDrawParameters = usesDrawParameters;
|
||||
|
@@ -241,6 +241,13 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||
|
||||
this.Copy(Attribute(AttributeConsts.PositionZ), this.FPFusedMultiplyAdd(z, ConstF(0.5f), halfW));
|
||||
}
|
||||
|
||||
if (Config.Stage != ShaderStage.Geometry && Config.HasLayerInputAttribute)
|
||||
{
|
||||
Config.SetUsedFeature(FeatureFlags.RtLayer);
|
||||
|
||||
this.Copy(Attribute(AttributeConsts.Layer), Attribute(Config.GpLayerInputAttribute | AttributeConsts.LoadOutputMask));
|
||||
}
|
||||
}
|
||||
|
||||
public void PrepareForVertexReturn(out Operand oldXLocal, out Operand oldYLocal, out Operand oldZLocal)
|
||||
|
@@ -20,6 +20,8 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||
public bool LastInPipeline { get; private set; }
|
||||
public bool LastInVertexPipeline { get; private set; }
|
||||
|
||||
public bool HasLayerInputAttribute { get; private set; }
|
||||
public int GpLayerInputAttribute { get; private set; }
|
||||
public int ThreadsPerInputPrimitive { get; }
|
||||
|
||||
public OutputTopology OutputTopology { get; }
|
||||
@@ -245,6 +247,22 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||
LayerOutputAttribute = attr;
|
||||
}
|
||||
|
||||
public void SetGeometryShaderLayerInputAttribute(int attr)
|
||||
{
|
||||
HasLayerInputAttribute = true;
|
||||
GpLayerInputAttribute = attr;
|
||||
}
|
||||
|
||||
public void SetLastInVertexPipeline(bool hasFragment)
|
||||
{
|
||||
if (!hasFragment)
|
||||
{
|
||||
LastInPipeline = true;
|
||||
}
|
||||
|
||||
LastInVertexPipeline = true;
|
||||
}
|
||||
|
||||
public void SetInputUserAttributeFixedFunc(int index)
|
||||
{
|
||||
UsedInputAttributes |= 1 << index;
|
||||
@@ -706,13 +724,15 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||
return FindDescriptorIndex(GetImageDescriptors(), texOp);
|
||||
}
|
||||
|
||||
public ShaderProgramInfo CreateProgramInfo()
|
||||
public ShaderProgramInfo CreateProgramInfo(ShaderIdentification identification = ShaderIdentification.None)
|
||||
{
|
||||
return new ShaderProgramInfo(
|
||||
GetConstantBufferDescriptors(),
|
||||
GetStorageBufferDescriptors(),
|
||||
GetTextureDescriptors(),
|
||||
GetImageDescriptors(),
|
||||
identification,
|
||||
GpLayerInputAttribute,
|
||||
Stage,
|
||||
UsedFeatures.HasFlag(FeatureFlags.InstanceId),
|
||||
UsedFeatures.HasFlag(FeatureFlags.DrawParameters),
|
||||
|
145
Ryujinx.Graphics.Shader/Translation/ShaderIdentifier.cs
Normal file
145
Ryujinx.Graphics.Shader/Translation/ShaderIdentifier.cs
Normal file
@@ -0,0 +1,145 @@
|
||||
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
|
||||
using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper;
|
||||
|
||||
namespace Ryujinx.Graphics.Shader.Translation
|
||||
{
|
||||
static class ShaderIdentifier
|
||||
{
|
||||
public static ShaderIdentification Identify(Function[] functions, ShaderConfig config)
|
||||
{
|
||||
if (config.Stage == ShaderStage.Geometry &&
|
||||
config.GpuAccessor.QueryPrimitiveTopology() == InputTopology.Triangles &&
|
||||
!config.GpuAccessor.QueryHostSupportsGeometryShader() &&
|
||||
IsLayerPassthroughGeometryShader(functions, out int layerInputAttr))
|
||||
{
|
||||
config.SetGeometryShaderLayerInputAttribute(layerInputAttr);
|
||||
|
||||
return ShaderIdentification.GeometryLayerPassthrough;
|
||||
}
|
||||
|
||||
return ShaderIdentification.None;
|
||||
}
|
||||
|
||||
private static bool IsLayerPassthroughGeometryShader(Function[] functions, out int layerInputAttr)
|
||||
{
|
||||
bool writesLayer = false;
|
||||
layerInputAttr = 0;
|
||||
|
||||
if (functions.Length != 1)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
int verticesCount = 0;
|
||||
int totalVerticesCount = 0;
|
||||
|
||||
foreach (BasicBlock block in functions[0].Blocks)
|
||||
{
|
||||
// We are not expecting loops or any complex control flow here, so fail in those cases.
|
||||
if (block.Branch != null && block.Branch.Index <= block.Index)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (INode node in block.Operations)
|
||||
{
|
||||
if (!(node is Operation operation))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (IsResourceWrite(operation.Inst))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (operation.Inst == Instruction.StoreAttribute)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (operation.Inst == Instruction.Copy && operation.Dest.Type == OperandType.Attribute)
|
||||
{
|
||||
Operand src = operation.GetSource(0);
|
||||
|
||||
if (src.Type == OperandType.LocalVariable && src.AsgOp is Operation asgOp && asgOp.Inst == Instruction.LoadAttribute)
|
||||
{
|
||||
src = Attribute(asgOp.GetSource(0).Value);
|
||||
}
|
||||
|
||||
if (src.Type == OperandType.Attribute)
|
||||
{
|
||||
if (operation.Dest.Value == AttributeConsts.Layer)
|
||||
{
|
||||
if ((src.Value & AttributeConsts.LoadOutputMask) != 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
writesLayer = true;
|
||||
layerInputAttr = src.Value;
|
||||
}
|
||||
else if (src.Value != operation.Dest.Value)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (src.Type == OperandType.Constant)
|
||||
{
|
||||
int dstComponent = (operation.Dest.Value >> 2) & 3;
|
||||
float expectedValue = dstComponent == 3 ? 1f : 0f;
|
||||
|
||||
if (src.AsFloat() != expectedValue)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (operation.Inst == Instruction.EmitVertex)
|
||||
{
|
||||
verticesCount++;
|
||||
}
|
||||
else if (operation.Inst == Instruction.EndPrimitive)
|
||||
{
|
||||
totalVerticesCount += verticesCount;
|
||||
verticesCount = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return totalVerticesCount + verticesCount == 3 && writesLayer;
|
||||
}
|
||||
|
||||
private static bool IsResourceWrite(Instruction inst)
|
||||
{
|
||||
switch (inst)
|
||||
{
|
||||
case Instruction.AtomicAdd:
|
||||
case Instruction.AtomicAnd:
|
||||
case Instruction.AtomicCompareAndSwap:
|
||||
case Instruction.AtomicMaxS32:
|
||||
case Instruction.AtomicMaxU32:
|
||||
case Instruction.AtomicMinS32:
|
||||
case Instruction.AtomicMinU32:
|
||||
case Instruction.AtomicOr:
|
||||
case Instruction.AtomicSwap:
|
||||
case Instruction.AtomicXor:
|
||||
case Instruction.ImageAtomic:
|
||||
case Instruction.ImageStore:
|
||||
case Instruction.StoreGlobal:
|
||||
case Instruction.StoreGlobal16:
|
||||
case Instruction.StoreGlobal8:
|
||||
case Instruction.StoreStorage:
|
||||
case Instruction.StoreStorage16:
|
||||
case Instruction.StoreStorage8:
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
@@ -77,9 +77,11 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||
funcs[i] = new Function(cfg.Blocks, $"fun{i}", false, inArgumentsCount, outArgumentsCount);
|
||||
}
|
||||
|
||||
var identification = ShaderIdentifier.Identify(funcs, config);
|
||||
|
||||
var sInfo = StructuredProgram.MakeStructuredProgram(funcs, config);
|
||||
|
||||
var info = config.CreateProgramInfo();
|
||||
var info = config.CreateProgramInfo(identification);
|
||||
|
||||
return config.Options.TargetLanguage switch
|
||||
{
|
||||
|
@@ -138,6 +138,16 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||
_config.MergeFromtNextStage(nextStage._config);
|
||||
}
|
||||
|
||||
public void SetGeometryShaderLayerInputAttribute(int attr)
|
||||
{
|
||||
_config.SetGeometryShaderLayerInputAttribute(attr);
|
||||
}
|
||||
|
||||
public void SetLastInVertexPipeline(bool hasFragment)
|
||||
{
|
||||
_config.SetLastInVertexPipeline(hasFragment);
|
||||
}
|
||||
|
||||
public ShaderProgram Translate(TranslatorContext other = null)
|
||||
{
|
||||
FunctionCode[] code = EmitShader(_program, _config, initializeOutputs: other == null, out _);
|
||||
|
@@ -1,6 +1,6 @@
|
||||
using Ryujinx.Graphics.Texture.Utils;
|
||||
using System.Diagnostics;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Numerics;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
|
@@ -1,7 +1,7 @@
|
||||
using System.Threading;
|
||||
using System.Collections.Generic;
|
||||
using Silk.NET.Vulkan;
|
||||
using System;
|
||||
using Silk.NET.Vulkan;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
|
||||
namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
|
@@ -163,6 +163,13 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
SignalDirty(DirtyFlags.Image);
|
||||
}
|
||||
|
||||
public void SetImage(int binding, Auto<DisposableImageView> image)
|
||||
{
|
||||
_imageRefs[binding] = image;
|
||||
|
||||
SignalDirty(DirtyFlags.Image);
|
||||
}
|
||||
|
||||
public void SetStorageBuffers(CommandBuffer commandBuffer, ReadOnlySpan<BufferAssignment> buffers)
|
||||
{
|
||||
for (int i = 0; i < buffers.Length; i++)
|
||||
|
208
Ryujinx.Graphics.Vulkan/Effects/FsrScalingFilter.cs
Normal file
208
Ryujinx.Graphics.Vulkan/Effects/FsrScalingFilter.cs
Normal file
@@ -0,0 +1,208 @@
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Graphics.Shader;
|
||||
using Ryujinx.Graphics.Shader.Translation;
|
||||
using Silk.NET.Vulkan;
|
||||
using System;
|
||||
using Extent2D = Ryujinx.Graphics.GAL.Extents2D;
|
||||
|
||||
namespace Ryujinx.Graphics.Vulkan.Effects
|
||||
{
|
||||
internal partial class FsrScalingFilter : IScalingFilter
|
||||
{
|
||||
private readonly VulkanRenderer _renderer;
|
||||
private PipelineHelperShader _pipeline;
|
||||
private ISampler _sampler;
|
||||
private ShaderCollection _scalingProgram;
|
||||
private ShaderCollection _sharpeningProgram;
|
||||
private float _sharpeningLevel = 1;
|
||||
private Device _device;
|
||||
private TextureView _intermediaryTexture;
|
||||
|
||||
public float Level
|
||||
{
|
||||
get => _sharpeningLevel;
|
||||
set
|
||||
{
|
||||
_sharpeningLevel = MathF.Max(0.01f, value);
|
||||
}
|
||||
}
|
||||
|
||||
public FsrScalingFilter(VulkanRenderer renderer, Device device)
|
||||
{
|
||||
_device = device;
|
||||
_renderer = renderer;
|
||||
|
||||
Initialize();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_pipeline.Dispose();
|
||||
_scalingProgram.Dispose();
|
||||
_sharpeningProgram.Dispose();
|
||||
_sampler.Dispose();
|
||||
_intermediaryTexture?.Dispose();
|
||||
}
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
_pipeline = new PipelineHelperShader(_renderer, _device);
|
||||
|
||||
_pipeline.Initialize();
|
||||
|
||||
var scalingShader = EmbeddedResources.Read("Ryujinx.Graphics.Vulkan/Effects/Shaders/FsrScaling.spv");
|
||||
var sharpeningShader = EmbeddedResources.Read("Ryujinx.Graphics.Vulkan/Effects/Shaders/FsrSharpening.spv");
|
||||
|
||||
var computeBindings = new ShaderBindings(
|
||||
new[] { 2 },
|
||||
Array.Empty<int>(),
|
||||
new[] { 1 },
|
||||
new[] { 0 });
|
||||
|
||||
var sharpeningBindings = new ShaderBindings(
|
||||
new[] { 2, 3, 4 },
|
||||
Array.Empty<int>(),
|
||||
new[] { 1 },
|
||||
new[] { 0 });
|
||||
|
||||
_sampler = _renderer.CreateSampler(GAL.SamplerCreateInfo.Create(MinFilter.Linear, MagFilter.Linear));
|
||||
|
||||
_scalingProgram = _renderer.CreateProgramWithMinimalLayout(new[]
|
||||
{
|
||||
new ShaderSource(scalingShader, computeBindings, ShaderStage.Compute, TargetLanguage.Spirv)
|
||||
});
|
||||
|
||||
_sharpeningProgram = _renderer.CreateProgramWithMinimalLayout(new[]
|
||||
{
|
||||
new ShaderSource(sharpeningShader, sharpeningBindings, ShaderStage.Compute, TargetLanguage.Spirv)
|
||||
});
|
||||
}
|
||||
|
||||
public void Run(
|
||||
TextureView view,
|
||||
CommandBufferScoped cbs,
|
||||
Auto<DisposableImageView> destinationTexture,
|
||||
Silk.NET.Vulkan.Format format,
|
||||
int width,
|
||||
int height,
|
||||
Extent2D source,
|
||||
Extent2D destination)
|
||||
{
|
||||
if (_intermediaryTexture == null
|
||||
|| _intermediaryTexture.Info.Width != width
|
||||
|| _intermediaryTexture.Info.Height != height
|
||||
|| !_intermediaryTexture.Info.Equals(view.Info))
|
||||
{
|
||||
var originalInfo = view.Info;
|
||||
|
||||
var swapRB = originalInfo.Format.IsBgr() && originalInfo.SwizzleR == SwizzleComponent.Red;
|
||||
|
||||
var info = new TextureCreateInfo(
|
||||
width,
|
||||
height,
|
||||
originalInfo.Depth,
|
||||
originalInfo.Levels,
|
||||
originalInfo.Samples,
|
||||
originalInfo.BlockWidth,
|
||||
originalInfo.BlockHeight,
|
||||
originalInfo.BytesPerPixel,
|
||||
originalInfo.Format,
|
||||
originalInfo.DepthStencilMode,
|
||||
originalInfo.Target,
|
||||
swapRB ? originalInfo.SwizzleB : originalInfo.SwizzleR,
|
||||
originalInfo.SwizzleG,
|
||||
swapRB ? originalInfo.SwizzleR : originalInfo.SwizzleB,
|
||||
originalInfo.SwizzleA);
|
||||
_intermediaryTexture?.Dispose();
|
||||
_intermediaryTexture = _renderer.CreateTexture(info, view.ScaleFactor) as TextureView;
|
||||
}
|
||||
|
||||
Span<GAL.Viewport> viewports = stackalloc GAL.Viewport[1];
|
||||
Span<Rectangle<int>> scissors = stackalloc Rectangle<int>[1];
|
||||
|
||||
viewports[0] = new GAL.Viewport(
|
||||
new Rectangle<float>(0, 0, view.Width, view.Height),
|
||||
ViewportSwizzle.PositiveX,
|
||||
ViewportSwizzle.PositiveY,
|
||||
ViewportSwizzle.PositiveZ,
|
||||
ViewportSwizzle.PositiveW,
|
||||
0f,
|
||||
1f);
|
||||
|
||||
scissors[0] = new Rectangle<int>(0, 0, view.Width, view.Height);
|
||||
|
||||
_pipeline.SetCommandBuffer(cbs);
|
||||
_pipeline.SetProgram(_scalingProgram);
|
||||
_pipeline.SetTextureAndSampler(ShaderStage.Compute, 1, view, _sampler);
|
||||
|
||||
float srcWidth = Math.Abs(source.X2 - source.X1);
|
||||
float srcHeight = Math.Abs(source.Y2 - source.Y1);
|
||||
float scaleX = srcWidth / view.Width;
|
||||
float scaleY = srcHeight / view.Height;
|
||||
|
||||
ReadOnlySpan<float> dimensionsBuffer = stackalloc float[]
|
||||
{
|
||||
source.X1,
|
||||
source.X2,
|
||||
source.Y1,
|
||||
source.Y2,
|
||||
destination.X1,
|
||||
destination.X2,
|
||||
destination.Y1,
|
||||
destination.Y2,
|
||||
scaleX,
|
||||
scaleY
|
||||
};
|
||||
|
||||
int rangeSize = dimensionsBuffer.Length * sizeof(float);
|
||||
var bufferHandle = _renderer.BufferManager.CreateWithHandle(_renderer, rangeSize, false);
|
||||
_renderer.BufferManager.SetData(bufferHandle, 0, dimensionsBuffer);
|
||||
|
||||
ReadOnlySpan<float> sharpeningBuffer = stackalloc float[] { 1.5f - (Level * 0.01f * 1.5f)};
|
||||
var sharpeningBufferHandle = _renderer.BufferManager.CreateWithHandle(_renderer, sizeof(float), false);
|
||||
_renderer.BufferManager.SetData(sharpeningBufferHandle, 0, sharpeningBuffer);
|
||||
|
||||
int threadGroupWorkRegionDim = 16;
|
||||
int dispatchX = (width + (threadGroupWorkRegionDim - 1)) / threadGroupWorkRegionDim;
|
||||
int dispatchY = (height + (threadGroupWorkRegionDim - 1)) / threadGroupWorkRegionDim;
|
||||
|
||||
var bufferRanges = new BufferRange(bufferHandle, 0, rangeSize);
|
||||
_pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(2, bufferRanges) });
|
||||
_pipeline.SetScissors(scissors);
|
||||
_pipeline.SetViewports(viewports, false);
|
||||
_pipeline.SetImage(0, _intermediaryTexture, GAL.Format.R8G8B8A8Unorm);
|
||||
_pipeline.DispatchCompute(dispatchX, dispatchY, 1);
|
||||
_pipeline.ComputeBarrier();
|
||||
|
||||
viewports[0] = new GAL.Viewport(
|
||||
new Rectangle<float>(0, 0, width, height),
|
||||
ViewportSwizzle.PositiveX,
|
||||
ViewportSwizzle.PositiveY,
|
||||
ViewportSwizzle.PositiveZ,
|
||||
ViewportSwizzle.PositiveW,
|
||||
0f,
|
||||
1f);
|
||||
|
||||
scissors[0] = new Rectangle<int>(0, 0, width, height);
|
||||
|
||||
// Sharpening pass
|
||||
_pipeline.SetCommandBuffer(cbs);
|
||||
_pipeline.SetProgram(_sharpeningProgram);
|
||||
_pipeline.SetTextureAndSampler(ShaderStage.Compute, 1, _intermediaryTexture, _sampler);
|
||||
_pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(2, bufferRanges) });
|
||||
var sharpeningRange = new BufferRange(sharpeningBufferHandle, 0, sizeof(float));
|
||||
_pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(4, sharpeningRange) });
|
||||
_pipeline.SetScissors(scissors);
|
||||
_pipeline.SetViewports(viewports, false);
|
||||
_pipeline.SetImage(0, destinationTexture);
|
||||
_pipeline.DispatchCompute(dispatchX, dispatchY, 1);
|
||||
_pipeline.ComputeBarrier();
|
||||
|
||||
_pipeline.Finish();
|
||||
|
||||
_renderer.BufferManager.Delete(bufferHandle);
|
||||
_renderer.BufferManager.Delete(sharpeningBufferHandle);
|
||||
}
|
||||
}
|
||||
}
|
127
Ryujinx.Graphics.Vulkan/Effects/FxaaPostProcessingEffect.cs
Normal file
127
Ryujinx.Graphics.Vulkan/Effects/FxaaPostProcessingEffect.cs
Normal file
@@ -0,0 +1,127 @@
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Graphics.Shader;
|
||||
using Ryujinx.Graphics.Shader.Translation;
|
||||
using Silk.NET.Vulkan;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Graphics.Vulkan.Effects
|
||||
{
|
||||
internal partial class FxaaPostProcessingEffect : IPostProcessingEffect
|
||||
{
|
||||
private readonly VulkanRenderer _renderer;
|
||||
private ISampler _samplerLinear;
|
||||
private ShaderCollection _shaderProgram;
|
||||
|
||||
private PipelineHelperShader _pipeline;
|
||||
private TextureView _texture;
|
||||
|
||||
public FxaaPostProcessingEffect(VulkanRenderer renderer, Device device)
|
||||
{
|
||||
_renderer = renderer;
|
||||
_pipeline = new PipelineHelperShader(renderer, device);
|
||||
|
||||
Initialize();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_shaderProgram.Dispose();
|
||||
_pipeline.Dispose();
|
||||
_samplerLinear.Dispose();
|
||||
_texture?.Dispose();
|
||||
}
|
||||
|
||||
private void Initialize()
|
||||
{
|
||||
_pipeline.Initialize();
|
||||
|
||||
var shader = EmbeddedResources.Read("Ryujinx.Graphics.Vulkan/Effects/Shaders/Fxaa.spv");
|
||||
|
||||
var computeBindings = new ShaderBindings(
|
||||
new[] { 2 },
|
||||
Array.Empty<int>(),
|
||||
new[] { 1 },
|
||||
new[] { 0 });
|
||||
|
||||
_samplerLinear = _renderer.CreateSampler(GAL.SamplerCreateInfo.Create(MinFilter.Linear, MagFilter.Linear));
|
||||
|
||||
_shaderProgram = _renderer.CreateProgramWithMinimalLayout(new[]
|
||||
{
|
||||
new ShaderSource(shader, computeBindings, ShaderStage.Compute, TargetLanguage.Spirv)
|
||||
});
|
||||
}
|
||||
|
||||
public TextureView Run(TextureView view, CommandBufferScoped cbs, int width, int height)
|
||||
{
|
||||
if (_texture == null || _texture.Width != view.Width || _texture.Height != view.Height)
|
||||
{
|
||||
_texture?.Dispose();
|
||||
|
||||
var info = view.Info;
|
||||
|
||||
if (view.Info.Format.IsBgr())
|
||||
{
|
||||
info = new TextureCreateInfo(info.Width,
|
||||
info.Height,
|
||||
info.Depth,
|
||||
info.Levels,
|
||||
info.Samples,
|
||||
info.BlockWidth,
|
||||
info.BlockHeight,
|
||||
info.BytesPerPixel,
|
||||
info.Format,
|
||||
info.DepthStencilMode,
|
||||
info.Target,
|
||||
info.SwizzleB,
|
||||
info.SwizzleG,
|
||||
info.SwizzleR,
|
||||
info.SwizzleA);
|
||||
}
|
||||
_texture = _renderer.CreateTexture(info, view.ScaleFactor) as TextureView;
|
||||
}
|
||||
|
||||
_pipeline.SetCommandBuffer(cbs);
|
||||
_pipeline.SetProgram(_shaderProgram);
|
||||
_pipeline.SetTextureAndSampler(ShaderStage.Compute, 1, view, _samplerLinear);
|
||||
|
||||
ReadOnlySpan<float> resolutionBuffer = stackalloc float[] { view.Width, view.Height };
|
||||
int rangeSize = resolutionBuffer.Length * sizeof(float);
|
||||
var bufferHandle = _renderer.BufferManager.CreateWithHandle(_renderer, rangeSize, false);
|
||||
|
||||
_renderer.BufferManager.SetData(bufferHandle, 0, resolutionBuffer);
|
||||
|
||||
var bufferRanges = new BufferRange(bufferHandle, 0, rangeSize);
|
||||
_pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(2, bufferRanges) });
|
||||
|
||||
Span<GAL.Viewport> viewports = stackalloc GAL.Viewport[1];
|
||||
|
||||
viewports[0] = new GAL.Viewport(
|
||||
new Rectangle<float>(0, 0, view.Width, view.Height),
|
||||
ViewportSwizzle.PositiveX,
|
||||
ViewportSwizzle.PositiveY,
|
||||
ViewportSwizzle.PositiveZ,
|
||||
ViewportSwizzle.PositiveW,
|
||||
0f,
|
||||
1f);
|
||||
|
||||
Span<Rectangle<int>> scissors = stackalloc Rectangle<int>[1];
|
||||
|
||||
var dispatchX = BitUtils.DivRoundUp(view.Width, IPostProcessingEffect.LocalGroupSize);
|
||||
var dispatchY = BitUtils.DivRoundUp(view.Height, IPostProcessingEffect.LocalGroupSize);
|
||||
|
||||
_pipeline.SetScissors(stackalloc[] { new Rectangle<int>(0, 0, view.Width, view.Height) });
|
||||
_pipeline.SetViewports(viewports, false);
|
||||
|
||||
_pipeline.SetImage(0, _texture, GAL.Format.R8G8B8A8Unorm);
|
||||
_pipeline.DispatchCompute(dispatchX, dispatchY, 1);
|
||||
|
||||
_renderer.BufferManager.Delete(bufferHandle);
|
||||
_pipeline.ComputeBarrier();
|
||||
|
||||
_pipeline.Finish();
|
||||
|
||||
return _texture;
|
||||
}
|
||||
}
|
||||
}
|
10
Ryujinx.Graphics.Vulkan/Effects/IPostProcessingEffect.cs
Normal file
10
Ryujinx.Graphics.Vulkan/Effects/IPostProcessingEffect.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Graphics.Vulkan.Effects
|
||||
{
|
||||
internal interface IPostProcessingEffect : IDisposable
|
||||
{
|
||||
const int LocalGroupSize = 64;
|
||||
TextureView Run(TextureView view, CommandBufferScoped cbs, int width, int height);
|
||||
}
|
||||
}
|
20
Ryujinx.Graphics.Vulkan/Effects/IScalingFilter.cs
Normal file
20
Ryujinx.Graphics.Vulkan/Effects/IScalingFilter.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using Silk.NET.Vulkan;
|
||||
using System;
|
||||
using Extent2D = Ryujinx.Graphics.GAL.Extents2D;
|
||||
|
||||
namespace Ryujinx.Graphics.Vulkan.Effects
|
||||
{
|
||||
internal interface IScalingFilter : IDisposable
|
||||
{
|
||||
float Level { get; set; }
|
||||
void Run(
|
||||
TextureView view,
|
||||
CommandBufferScoped cbs,
|
||||
Auto<DisposableImageView> destinationTexture,
|
||||
Format format,
|
||||
int width,
|
||||
int height,
|
||||
Extent2D source,
|
||||
Extent2D destination);
|
||||
}
|
||||
}
|
3945
Ryujinx.Graphics.Vulkan/Effects/Shaders/FsrScaling.glsl
Normal file
3945
Ryujinx.Graphics.Vulkan/Effects/Shaders/FsrScaling.glsl
Normal file
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user