Compare commits
31 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
21ce8a9b80 | ||
|
9ecbee8032 | ||
|
80519af67d | ||
|
26e30faff3 | ||
|
0992310b76 | ||
|
009c1101d2 | ||
|
ba95ee54ab | ||
|
4ce4299ca2 | ||
|
17620d18db | ||
|
9f1cf6458c | ||
|
67b4e63cff | ||
|
c05c688ee8 | ||
|
b2623dc27d | ||
|
5131b71437 | ||
|
7870423671 | ||
|
b72916fbc1 | ||
|
da073fce61 | ||
|
1fc90e57d2 | ||
|
eafcc314a9 | ||
|
6e9bd4de13 | ||
|
05a41b31bc | ||
|
eed17f963e | ||
|
c09c0c002d | ||
|
d56d335c0b | ||
|
23c844b2aa | ||
|
81691b9e37 | ||
|
2dc422bc14 | ||
|
a80fa5e33f | ||
|
954e995321 | ||
|
dad9ab6bb6 | ||
|
f0562b9c75 |
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
|
37
.github/workflows/release.yml
vendored
37
.github/workflows/release.yml
vendored
@@ -13,23 +13,22 @@ on:
|
|||||||
|
|
||||||
concurrency: release
|
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:
|
jobs:
|
||||||
release:
|
release:
|
||||||
runs-on: windows-latest
|
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:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- uses: actions/setup-dotnet@v3
|
- uses: actions/setup-dotnet@v3
|
||||||
with:
|
with:
|
||||||
dotnet-version: 7.0.x
|
global-json-file: global.json
|
||||||
- name: Get version info
|
- name: Get version info
|
||||||
id: version_info
|
id: version_info
|
||||||
run: |
|
run: |
|
||||||
@@ -112,3 +111,21 @@ jobs:
|
|||||||
owner: ${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}
|
owner: ${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_OWNER }}
|
||||||
repo: ${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}
|
repo: ${{ env.RYUJINX_TARGET_RELEASE_CHANNEL_REPO }}
|
||||||
token: ${{ secrets.RELEASE_TOKEN }}
|
token: ${{ secrets.RELEASE_TOKEN }}
|
||||||
|
|
||||||
|
- name: Create tag
|
||||||
|
uses: actions/github-script@v5
|
||||||
|
with:
|
||||||
|
script: |
|
||||||
|
github.rest.git.createRef({
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
ref: 'refs/tags/${{ steps.version_info.outputs.build_version }}',
|
||||||
|
sha: context.sha
|
||||||
|
})
|
||||||
|
|
||||||
|
flatpak_release:
|
||||||
|
uses: ./.github/workflows/flatpak.yml
|
||||||
|
needs: release
|
||||||
|
with:
|
||||||
|
ryujinx_version: "1.1.${{ github.run_number }}"
|
||||||
|
secrets: inherit
|
||||||
|
@@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\Ryujinx.Common\Ryujinx.Common.csproj" />
|
<ProjectReference Include="..\Ryujinx.Common\Ryujinx.Common.csproj" />
|
||||||
|
<ProjectReference Include="..\Ryujinx.Memory\Ryujinx.Memory.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
using ARMeilleure.CodeGen.Linking;
|
using ARMeilleure.CodeGen.Linking;
|
||||||
using ARMeilleure.CodeGen.RegisterAllocators;
|
using ARMeilleure.CodeGen.RegisterAllocators;
|
||||||
using ARMeilleure.IntermediateRepresentation;
|
using ARMeilleure.IntermediateRepresentation;
|
||||||
|
using Ryujinx.Common.Memory;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
@@ -59,7 +60,7 @@ namespace ARMeilleure.CodeGen.Arm64
|
|||||||
|
|
||||||
public CodeGenContext(AllocationResult allocResult, int maxCallArgs, int blocksCount, bool relocatable)
|
public CodeGenContext(AllocationResult allocResult, int maxCallArgs, int blocksCount, bool relocatable)
|
||||||
{
|
{
|
||||||
_stream = new MemoryStream();
|
_stream = MemoryStreamManager.Shared.GetStream();
|
||||||
|
|
||||||
AllocResult = allocResult;
|
AllocResult = allocResult;
|
||||||
|
|
||||||
@@ -265,7 +266,7 @@ namespace ARMeilleure.CodeGen.Arm64
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
relocInfo = new RelocInfo(new RelocEntry[0]);
|
relocInfo = new RelocInfo(Array.Empty<RelocEntry>());
|
||||||
}
|
}
|
||||||
|
|
||||||
return (code, relocInfo);
|
return (code, relocInfo);
|
||||||
|
@@ -9,7 +9,7 @@ using static ARMeilleure.IntermediateRepresentation.Operation.Factory;
|
|||||||
|
|
||||||
namespace ARMeilleure.CodeGen.Arm64
|
namespace ARMeilleure.CodeGen.Arm64
|
||||||
{
|
{
|
||||||
class PreAllocator
|
static class PreAllocator
|
||||||
{
|
{
|
||||||
private class ConstantDict
|
private class ConstantDict
|
||||||
{
|
{
|
||||||
@@ -54,8 +54,8 @@ namespace ARMeilleure.CodeGen.Arm64
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
HandleConstantRegCopy(constants, block.Operations, node);
|
InsertConstantRegCopies(constants, block.Operations, node);
|
||||||
HandleDestructiveRegCopy(block.Operations, node);
|
InsertDestructiveRegCopies(block.Operations, node);
|
||||||
|
|
||||||
switch (node.Instruction)
|
switch (node.Instruction)
|
||||||
{
|
{
|
||||||
@@ -78,28 +78,28 @@ namespace ARMeilleure.CodeGen.Arm64
|
|||||||
|
|
||||||
// Copy values to registers expected by the function
|
// Copy values to registers expected by the function
|
||||||
// being called, as mandated by the ABI.
|
// being called, as mandated by the ABI.
|
||||||
HandleCall(constants, block.Operations, node);
|
InsertCallCopies(constants, block.Operations, node);
|
||||||
break;
|
break;
|
||||||
case Instruction.CompareAndSwap:
|
case Instruction.CompareAndSwap:
|
||||||
case Instruction.CompareAndSwap16:
|
case Instruction.CompareAndSwap16:
|
||||||
case Instruction.CompareAndSwap8:
|
case Instruction.CompareAndSwap8:
|
||||||
nextNode = HandleCompareAndSwap(block.Operations, node);
|
nextNode = GenerateCompareAndSwap(block.Operations, node);
|
||||||
break;
|
break;
|
||||||
case Instruction.LoadArgument:
|
case Instruction.LoadArgument:
|
||||||
nextNode = HandleLoadArgument(cctx, ref buffer, block.Operations, preservedArgs, node);
|
nextNode = InsertLoadArgumentCopy(cctx, ref buffer, block.Operations, preservedArgs, node);
|
||||||
break;
|
break;
|
||||||
case Instruction.Return:
|
case Instruction.Return:
|
||||||
HandleReturn(block.Operations, node);
|
InsertReturnCopy(block.Operations, node);
|
||||||
break;
|
break;
|
||||||
case Instruction.Tailcall:
|
case Instruction.Tailcall:
|
||||||
HandleTailcall(constants, block.Operations, stackAlloc, node, node);
|
InsertTailcallCopies(constants, block.Operations, stackAlloc, node, node);
|
||||||
break;
|
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))
|
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)
|
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;
|
Operation operation = node;
|
||||||
|
|
||||||
@@ -319,7 +319,7 @@ namespace ARMeilleure.CodeGen.Arm64
|
|||||||
|
|
||||||
Operation copyOp = Operation(Instruction.Copy, argReg, source);
|
Operation copyOp = Operation(Instruction.Copy, argReg, source);
|
||||||
|
|
||||||
HandleConstantRegCopy(constants, nodes, nodes.AddBefore(node, copyOp));
|
InsertConstantRegCopies(constants, nodes, nodes.AddBefore(node, copyOp));
|
||||||
|
|
||||||
sources.Add(argReg);
|
sources.Add(argReg);
|
||||||
}
|
}
|
||||||
@@ -329,7 +329,7 @@ namespace ARMeilleure.CodeGen.Arm64
|
|||||||
|
|
||||||
Operation spillOp = Operation(Instruction.SpillArg, default, offset, source);
|
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();
|
stackOffset += source.Type.GetSizeInBytes();
|
||||||
}
|
}
|
||||||
@@ -364,7 +364,7 @@ namespace ARMeilleure.CodeGen.Arm64
|
|||||||
operation.SetSources(sources.ToArray());
|
operation.SetSources(sources.ToArray());
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void HandleTailcall(
|
private static void InsertTailcallCopies(
|
||||||
ConstantDict constants,
|
ConstantDict constants,
|
||||||
IntrusiveList<Operation> nodes,
|
IntrusiveList<Operation> nodes,
|
||||||
StackAllocator stackAlloc,
|
StackAllocator stackAlloc,
|
||||||
@@ -420,7 +420,7 @@ namespace ARMeilleure.CodeGen.Arm64
|
|||||||
|
|
||||||
Operation copyOp = Operation(Instruction.Copy, argReg, source);
|
Operation copyOp = Operation(Instruction.Copy, argReg, source);
|
||||||
|
|
||||||
HandleConstantRegCopy(constants, nodes, nodes.AddBefore(node, copyOp));
|
InsertConstantRegCopies(constants, nodes, nodes.AddBefore(node, copyOp));
|
||||||
|
|
||||||
sources.Add(argReg);
|
sources.Add(argReg);
|
||||||
}
|
}
|
||||||
@@ -444,7 +444,7 @@ namespace ARMeilleure.CodeGen.Arm64
|
|||||||
operation.SetSources(sources.ToArray());
|
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);
|
Operand expected = node.GetSource(1);
|
||||||
|
|
||||||
@@ -508,7 +508,7 @@ namespace ARMeilleure.CodeGen.Arm64
|
|||||||
return node.ListNext;
|
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)
|
if (node.SourcesCount == 0)
|
||||||
{
|
{
|
||||||
@@ -537,7 +537,7 @@ namespace ARMeilleure.CodeGen.Arm64
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Operation HandleLoadArgument(
|
private static Operation InsertLoadArgumentCopy(
|
||||||
CompilerContext cctx,
|
CompilerContext cctx,
|
||||||
ref Span<Operation> buffer,
|
ref Span<Operation> buffer,
|
||||||
IntrusiveList<Operation> nodes,
|
IntrusiveList<Operation> nodes,
|
||||||
@@ -629,7 +629,7 @@ namespace ARMeilleure.CodeGen.Arm64
|
|||||||
if (dest.AssignmentsCount == 1)
|
if (dest.AssignmentsCount == 1)
|
||||||
{
|
{
|
||||||
// Let's propagate the argument if we can to avoid copies.
|
// 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;
|
nextNode = node.ListNext;
|
||||||
}
|
}
|
||||||
else
|
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(
|
private static Operand AddFloatConstantCopy(
|
||||||
ConstantDict constants,
|
ConstantDict constants,
|
||||||
IntrusiveList<Operation> nodes,
|
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)
|
private static int GetHighestValueIndex(Span<int> span)
|
||||||
{
|
{
|
||||||
int highest = span[0];
|
int highest = int.MinValue;
|
||||||
|
|
||||||
if (highest == int.MaxValue)
|
|
||||||
{
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int selected = 0;
|
int selected = 0;
|
||||||
|
|
||||||
for (int index = 1; index < span.Length; index++)
|
for (int index = 0; index < span.Length; index++)
|
||||||
{
|
{
|
||||||
int current = span[index];
|
int current = span[index];
|
||||||
|
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
using ARMeilleure.CodeGen.Linking;
|
using ARMeilleure.CodeGen.Linking;
|
||||||
using ARMeilleure.IntermediateRepresentation;
|
using ARMeilleure.IntermediateRepresentation;
|
||||||
|
using Ryujinx.Common.Memory;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
@@ -1033,7 +1034,13 @@ namespace ARMeilleure.CodeGen.X86
|
|||||||
|
|
||||||
Debug.Assert(opCode != BadOp, "Invalid opcode value.");
|
Debug.Assert(opCode != BadOp, "Invalid opcode value.");
|
||||||
|
|
||||||
if ((flags & InstructionFlags.Vex) != 0 && HardwareCapabilities.SupportsVexEncoding)
|
if ((flags & InstructionFlags.Evex) != 0 && HardwareCapabilities.SupportsEvexEncoding)
|
||||||
|
{
|
||||||
|
WriteEvexInst(dest, src1, src2, type, flags, opCode);
|
||||||
|
|
||||||
|
opCode &= 0xff;
|
||||||
|
}
|
||||||
|
else if ((flags & InstructionFlags.Vex) != 0 && HardwareCapabilities.SupportsVexEncoding)
|
||||||
{
|
{
|
||||||
// In a vex encoding, only one prefix can be active at a time. The active prefix is encoded in the second byte using two bits.
|
// In a vex encoding, only one prefix can be active at a time. The active prefix is encoded in the second byte using two bits.
|
||||||
|
|
||||||
@@ -1152,6 +1159,103 @@ namespace ARMeilleure.CodeGen.X86
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void WriteEvexInst(
|
||||||
|
Operand dest,
|
||||||
|
Operand src1,
|
||||||
|
Operand src2,
|
||||||
|
OperandType type,
|
||||||
|
InstructionFlags flags,
|
||||||
|
int opCode,
|
||||||
|
bool broadcast = false,
|
||||||
|
int registerWidth = 128,
|
||||||
|
int maskRegisterIdx = 0,
|
||||||
|
bool zeroElements = false)
|
||||||
|
{
|
||||||
|
int op1Idx = dest.GetRegister().Index;
|
||||||
|
int op2Idx = src1.GetRegister().Index;
|
||||||
|
int op3Idx = src2.GetRegister().Index;
|
||||||
|
|
||||||
|
WriteByte(0x62);
|
||||||
|
|
||||||
|
// P0
|
||||||
|
// Extend operand 1 register
|
||||||
|
bool r = (op1Idx & 8) == 0;
|
||||||
|
// Extend operand 3 register
|
||||||
|
bool x = (op3Idx & 16) == 0;
|
||||||
|
// Extend operand 3 register
|
||||||
|
bool b = (op3Idx & 8) == 0;
|
||||||
|
// Extend operand 1 register
|
||||||
|
bool rp = (op1Idx & 16) == 0;
|
||||||
|
// Escape code index
|
||||||
|
byte mm = 0b00;
|
||||||
|
|
||||||
|
switch ((ushort)(opCode >> 8))
|
||||||
|
{
|
||||||
|
case 0xf00: mm = 0b01; break;
|
||||||
|
case 0xf38: mm = 0b10; break;
|
||||||
|
case 0xf3a: mm = 0b11; break;
|
||||||
|
|
||||||
|
default: Debug.Fail($"Failed to EVEX encode opcode 0x{opCode:X}."); break;
|
||||||
|
}
|
||||||
|
|
||||||
|
WriteByte(
|
||||||
|
(byte)(
|
||||||
|
(r ? 0x80 : 0) |
|
||||||
|
(x ? 0x40 : 0) |
|
||||||
|
(b ? 0x20 : 0) |
|
||||||
|
(rp ? 0x10 : 0) |
|
||||||
|
mm));
|
||||||
|
|
||||||
|
// P1
|
||||||
|
// Specify 64-bit lane mode
|
||||||
|
bool w = Is64Bits(type);
|
||||||
|
// Operand 2 register index
|
||||||
|
byte vvvv = (byte)(~op2Idx & 0b1111);
|
||||||
|
// Opcode prefix
|
||||||
|
byte pp = (flags & InstructionFlags.PrefixMask) switch
|
||||||
|
{
|
||||||
|
InstructionFlags.Prefix66 => 0b01,
|
||||||
|
InstructionFlags.PrefixF3 => 0b10,
|
||||||
|
InstructionFlags.PrefixF2 => 0b11,
|
||||||
|
_ => 0
|
||||||
|
};
|
||||||
|
WriteByte(
|
||||||
|
(byte)(
|
||||||
|
(w ? 0x80 : 0) |
|
||||||
|
(vvvv << 3) |
|
||||||
|
0b100 |
|
||||||
|
pp));
|
||||||
|
|
||||||
|
// P2
|
||||||
|
// Mask register determines what elements to zero, rather than what elements to merge
|
||||||
|
bool z = zeroElements;
|
||||||
|
// Specifies register-width
|
||||||
|
byte ll = 0b00;
|
||||||
|
switch (registerWidth)
|
||||||
|
{
|
||||||
|
case 128: ll = 0b00; break;
|
||||||
|
case 256: ll = 0b01; break;
|
||||||
|
case 512: ll = 0b10; break;
|
||||||
|
|
||||||
|
default: Debug.Fail($"Invalid EVEX vector register width {registerWidth}."); break;
|
||||||
|
}
|
||||||
|
// Embedded broadcast in the case of a memory operand
|
||||||
|
bool bcast = broadcast;
|
||||||
|
// Extend operand 2 register
|
||||||
|
bool vp = (op2Idx & 16) == 0;
|
||||||
|
// Mask register index
|
||||||
|
Debug.Assert(maskRegisterIdx < 8, $"Invalid mask register index {maskRegisterIdx}.");
|
||||||
|
byte aaa = (byte)(maskRegisterIdx & 0b111);
|
||||||
|
|
||||||
|
WriteByte(
|
||||||
|
(byte)(
|
||||||
|
(z ? 0x80 : 0) |
|
||||||
|
(ll << 5) |
|
||||||
|
(bcast ? 0x10 : 0) |
|
||||||
|
(vp ? 8 : 0) |
|
||||||
|
aaa));
|
||||||
|
}
|
||||||
|
|
||||||
private void WriteCompactInst(Operand operand, int opCode)
|
private void WriteCompactInst(Operand operand, int opCode)
|
||||||
{
|
{
|
||||||
int regIndex = operand.GetRegister().Index;
|
int regIndex = operand.GetRegister().Index;
|
||||||
@@ -1285,7 +1389,7 @@ namespace ARMeilleure.CodeGen.X86
|
|||||||
// Write the code, ignoring the dummy bytes after jumps, into a new stream.
|
// Write the code, ignoring the dummy bytes after jumps, into a new stream.
|
||||||
_stream.Seek(0, SeekOrigin.Begin);
|
_stream.Seek(0, SeekOrigin.Begin);
|
||||||
|
|
||||||
using var codeStream = new MemoryStream();
|
using var codeStream = MemoryStreamManager.Shared.GetStream();
|
||||||
var assembler = new Assembler(codeStream, HasRelocs);
|
var assembler = new Assembler(codeStream, HasRelocs);
|
||||||
|
|
||||||
bool hasRelocs = HasRelocs;
|
bool hasRelocs = HasRelocs;
|
||||||
|
@@ -20,6 +20,7 @@ namespace ARMeilleure.CodeGen.X86
|
|||||||
Reg8Dest = 1 << 2,
|
Reg8Dest = 1 << 2,
|
||||||
RexW = 1 << 3,
|
RexW = 1 << 3,
|
||||||
Vex = 1 << 4,
|
Vex = 1 << 4,
|
||||||
|
Evex = 1 << 5,
|
||||||
|
|
||||||
PrefixBit = 16,
|
PrefixBit = 16,
|
||||||
PrefixMask = 7 << PrefixBit,
|
PrefixMask = 7 << PrefixBit,
|
||||||
@@ -278,6 +279,7 @@ namespace ARMeilleure.CodeGen.X86
|
|||||||
Add(X86Instruction.Vfnmsub231sd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f38bf, InstructionFlags.Vex | InstructionFlags.Prefix66 | InstructionFlags.RexW));
|
Add(X86Instruction.Vfnmsub231sd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f38bf, InstructionFlags.Vex | InstructionFlags.Prefix66 | InstructionFlags.RexW));
|
||||||
Add(X86Instruction.Vfnmsub231ss, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f38bf, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
Add(X86Instruction.Vfnmsub231ss, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f38bf, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||||
Add(X86Instruction.Vpblendvb, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f3a4c, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
Add(X86Instruction.Vpblendvb, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f3a4c, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||||
|
Add(X86Instruction.Vpternlogd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x000f3a25, InstructionFlags.Evex | InstructionFlags.Prefix66));
|
||||||
Add(X86Instruction.Xor, new InstructionInfo(0x00000031, 0x06000083, 0x06000081, BadOp, 0x00000033, InstructionFlags.None));
|
Add(X86Instruction.Xor, new InstructionInfo(0x00000031, 0x06000083, 0x06000081, BadOp, 0x00000033, InstructionFlags.None));
|
||||||
Add(X86Instruction.Xorpd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f57, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
Add(X86Instruction.Xorpd, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f57, InstructionFlags.Vex | InstructionFlags.Prefix66));
|
||||||
Add(X86Instruction.Xorps, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f57, InstructionFlags.Vex));
|
Add(X86Instruction.Xorps, new InstructionInfo(BadOp, BadOp, BadOp, BadOp, 0x00000f57, InstructionFlags.Vex));
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
using ARMeilleure.CodeGen.RegisterAllocators;
|
using ARMeilleure.CodeGen.RegisterAllocators;
|
||||||
using ARMeilleure.IntermediateRepresentation;
|
using ARMeilleure.IntermediateRepresentation;
|
||||||
|
using Ryujinx.Common.Memory;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
|
|
||||||
@@ -22,7 +23,7 @@ namespace ARMeilleure.CodeGen.X86
|
|||||||
|
|
||||||
public CodeGenContext(AllocationResult allocResult, int maxCallArgs, int blocksCount, bool relocatable)
|
public CodeGenContext(AllocationResult allocResult, int maxCallArgs, int blocksCount, bool relocatable)
|
||||||
{
|
{
|
||||||
_stream = new MemoryStream();
|
_stream = MemoryStreamManager.Shared.GetStream();
|
||||||
_blockLabels = new Operand[blocksCount];
|
_blockLabels = new Operand[blocksCount];
|
||||||
|
|
||||||
AllocResult = allocResult;
|
AllocResult = allocResult;
|
||||||
|
@@ -1,10 +1,14 @@
|
|||||||
|
using Ryujinx.Memory;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
using System.Runtime.Intrinsics.X86;
|
using System.Runtime.Intrinsics.X86;
|
||||||
|
|
||||||
namespace ARMeilleure.CodeGen.X86
|
namespace ARMeilleure.CodeGen.X86
|
||||||
{
|
{
|
||||||
static class HardwareCapabilities
|
static class HardwareCapabilities
|
||||||
{
|
{
|
||||||
|
private delegate uint GetXcr0();
|
||||||
|
|
||||||
static HardwareCapabilities()
|
static HardwareCapabilities()
|
||||||
{
|
{
|
||||||
if (!X86Base.IsSupported)
|
if (!X86Base.IsSupported)
|
||||||
@@ -24,6 +28,34 @@ namespace ARMeilleure.CodeGen.X86
|
|||||||
FeatureInfo7Ebx = (FeatureFlags7Ebx)ebx7;
|
FeatureInfo7Ebx = (FeatureFlags7Ebx)ebx7;
|
||||||
FeatureInfo7Ecx = (FeatureFlags7Ecx)ecx7;
|
FeatureInfo7Ecx = (FeatureFlags7Ecx)ecx7;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Xcr0InfoEax = (Xcr0FlagsEax)GetXcr0Eax();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static uint GetXcr0Eax()
|
||||||
|
{
|
||||||
|
if (!FeatureInfo1Ecx.HasFlag(FeatureFlags1Ecx.Xsave))
|
||||||
|
{
|
||||||
|
// XSAVE feature required for xgetbv
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ReadOnlySpan<byte> asmGetXcr0 = new byte[]
|
||||||
|
{
|
||||||
|
0x31, 0xc9, // xor ecx, ecx
|
||||||
|
0xf, 0x01, 0xd0, // xgetbv
|
||||||
|
0xc3, // ret
|
||||||
|
};
|
||||||
|
|
||||||
|
using MemoryBlock memGetXcr0 = new MemoryBlock((ulong)asmGetXcr0.Length);
|
||||||
|
|
||||||
|
memGetXcr0.Write(0, asmGetXcr0);
|
||||||
|
|
||||||
|
memGetXcr0.Reprotect(0, (ulong)asmGetXcr0.Length, MemoryPermission.ReadAndExecute);
|
||||||
|
|
||||||
|
var fGetXcr0 = Marshal.GetDelegateForFunctionPointer<GetXcr0>(memGetXcr0.Pointer);
|
||||||
|
|
||||||
|
return fGetXcr0();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Flags]
|
[Flags]
|
||||||
@@ -44,6 +76,8 @@ namespace ARMeilleure.CodeGen.X86
|
|||||||
Sse42 = 1 << 20,
|
Sse42 = 1 << 20,
|
||||||
Popcnt = 1 << 23,
|
Popcnt = 1 << 23,
|
||||||
Aes = 1 << 25,
|
Aes = 1 << 25,
|
||||||
|
Xsave = 1 << 26,
|
||||||
|
Osxsave = 1 << 27,
|
||||||
Avx = 1 << 28,
|
Avx = 1 << 28,
|
||||||
F16c = 1 << 29
|
F16c = 1 << 29
|
||||||
}
|
}
|
||||||
@@ -52,7 +86,11 @@ namespace ARMeilleure.CodeGen.X86
|
|||||||
public enum FeatureFlags7Ebx
|
public enum FeatureFlags7Ebx
|
||||||
{
|
{
|
||||||
Avx2 = 1 << 5,
|
Avx2 = 1 << 5,
|
||||||
Sha = 1 << 29
|
Avx512f = 1 << 16,
|
||||||
|
Avx512dq = 1 << 17,
|
||||||
|
Sha = 1 << 29,
|
||||||
|
Avx512bw = 1 << 30,
|
||||||
|
Avx512vl = 1 << 31
|
||||||
}
|
}
|
||||||
|
|
||||||
[Flags]
|
[Flags]
|
||||||
@@ -61,10 +99,21 @@ namespace ARMeilleure.CodeGen.X86
|
|||||||
Gfni = 1 << 8,
|
Gfni = 1 << 8,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Flags]
|
||||||
|
public enum Xcr0FlagsEax
|
||||||
|
{
|
||||||
|
Sse = 1 << 1,
|
||||||
|
YmmHi128 = 1 << 2,
|
||||||
|
Opmask = 1 << 5,
|
||||||
|
ZmmHi256 = 1 << 6,
|
||||||
|
Hi16Zmm = 1 << 7
|
||||||
|
}
|
||||||
|
|
||||||
public static FeatureFlags1Edx FeatureInfo1Edx { get; }
|
public static FeatureFlags1Edx FeatureInfo1Edx { get; }
|
||||||
public static FeatureFlags1Ecx FeatureInfo1Ecx { get; }
|
public static FeatureFlags1Ecx FeatureInfo1Ecx { get; }
|
||||||
public static FeatureFlags7Ebx FeatureInfo7Ebx { get; } = 0;
|
public static FeatureFlags7Ebx FeatureInfo7Ebx { get; } = 0;
|
||||||
public static FeatureFlags7Ecx FeatureInfo7Ecx { get; } = 0;
|
public static FeatureFlags7Ecx FeatureInfo7Ecx { get; } = 0;
|
||||||
|
public static Xcr0FlagsEax Xcr0InfoEax { get; } = 0;
|
||||||
|
|
||||||
public static bool SupportsSse => FeatureInfo1Edx.HasFlag(FeatureFlags1Edx.Sse);
|
public static bool SupportsSse => FeatureInfo1Edx.HasFlag(FeatureFlags1Edx.Sse);
|
||||||
public static bool SupportsSse2 => FeatureInfo1Edx.HasFlag(FeatureFlags1Edx.Sse2);
|
public static bool SupportsSse2 => FeatureInfo1Edx.HasFlag(FeatureFlags1Edx.Sse2);
|
||||||
@@ -76,8 +125,13 @@ namespace ARMeilleure.CodeGen.X86
|
|||||||
public static bool SupportsSse42 => FeatureInfo1Ecx.HasFlag(FeatureFlags1Ecx.Sse42);
|
public static bool SupportsSse42 => FeatureInfo1Ecx.HasFlag(FeatureFlags1Ecx.Sse42);
|
||||||
public static bool SupportsPopcnt => FeatureInfo1Ecx.HasFlag(FeatureFlags1Ecx.Popcnt);
|
public static bool SupportsPopcnt => FeatureInfo1Ecx.HasFlag(FeatureFlags1Ecx.Popcnt);
|
||||||
public static bool SupportsAesni => FeatureInfo1Ecx.HasFlag(FeatureFlags1Ecx.Aes);
|
public static bool SupportsAesni => FeatureInfo1Ecx.HasFlag(FeatureFlags1Ecx.Aes);
|
||||||
public static bool SupportsAvx => FeatureInfo1Ecx.HasFlag(FeatureFlags1Ecx.Avx);
|
public static bool SupportsAvx => FeatureInfo1Ecx.HasFlag(FeatureFlags1Ecx.Avx | FeatureFlags1Ecx.Xsave | FeatureFlags1Ecx.Osxsave) && Xcr0InfoEax.HasFlag(Xcr0FlagsEax.Sse | Xcr0FlagsEax.YmmHi128);
|
||||||
public static bool SupportsAvx2 => FeatureInfo7Ebx.HasFlag(FeatureFlags7Ebx.Avx2) && SupportsAvx;
|
public static bool SupportsAvx2 => FeatureInfo7Ebx.HasFlag(FeatureFlags7Ebx.Avx2) && SupportsAvx;
|
||||||
|
public static bool SupportsAvx512F => FeatureInfo7Ebx.HasFlag(FeatureFlags7Ebx.Avx512f) && FeatureInfo1Ecx.HasFlag(FeatureFlags1Ecx.Xsave | FeatureFlags1Ecx.Osxsave)
|
||||||
|
&& Xcr0InfoEax.HasFlag(Xcr0FlagsEax.Sse | Xcr0FlagsEax.YmmHi128 | Xcr0FlagsEax.Opmask | Xcr0FlagsEax.ZmmHi256 | Xcr0FlagsEax.Hi16Zmm);
|
||||||
|
public static bool SupportsAvx512Vl => FeatureInfo7Ebx.HasFlag(FeatureFlags7Ebx.Avx512vl) && SupportsAvx512F;
|
||||||
|
public static bool SupportsAvx512Bw => FeatureInfo7Ebx.HasFlag(FeatureFlags7Ebx.Avx512bw) && SupportsAvx512F;
|
||||||
|
public static bool SupportsAvx512Dq => FeatureInfo7Ebx.HasFlag(FeatureFlags7Ebx.Avx512dq) && SupportsAvx512F;
|
||||||
public static bool SupportsF16c => FeatureInfo1Ecx.HasFlag(FeatureFlags1Ecx.F16c);
|
public static bool SupportsF16c => FeatureInfo1Ecx.HasFlag(FeatureFlags1Ecx.F16c);
|
||||||
public static bool SupportsSha => FeatureInfo7Ebx.HasFlag(FeatureFlags7Ebx.Sha);
|
public static bool SupportsSha => FeatureInfo7Ebx.HasFlag(FeatureFlags7Ebx.Sha);
|
||||||
public static bool SupportsGfni => FeatureInfo7Ecx.HasFlag(FeatureFlags7Ecx.Gfni);
|
public static bool SupportsGfni => FeatureInfo7Ecx.HasFlag(FeatureFlags7Ecx.Gfni);
|
||||||
@@ -85,5 +139,6 @@ namespace ARMeilleure.CodeGen.X86
|
|||||||
public static bool ForceLegacySse { get; set; }
|
public static bool ForceLegacySse { get; set; }
|
||||||
|
|
||||||
public static bool SupportsVexEncoding => SupportsAvx && !ForceLegacySse;
|
public static bool SupportsVexEncoding => SupportsAvx && !ForceLegacySse;
|
||||||
|
public static bool SupportsEvexEncoding => SupportsAvx512F && !ForceLegacySse;
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -180,6 +180,7 @@ namespace ARMeilleure.CodeGen.X86
|
|||||||
Add(Intrinsic.X86Vfnmadd231ss, new IntrinsicInfo(X86Instruction.Vfnmadd231ss, IntrinsicType.Fma));
|
Add(Intrinsic.X86Vfnmadd231ss, new IntrinsicInfo(X86Instruction.Vfnmadd231ss, IntrinsicType.Fma));
|
||||||
Add(Intrinsic.X86Vfnmsub231sd, new IntrinsicInfo(X86Instruction.Vfnmsub231sd, IntrinsicType.Fma));
|
Add(Intrinsic.X86Vfnmsub231sd, new IntrinsicInfo(X86Instruction.Vfnmsub231sd, IntrinsicType.Fma));
|
||||||
Add(Intrinsic.X86Vfnmsub231ss, new IntrinsicInfo(X86Instruction.Vfnmsub231ss, IntrinsicType.Fma));
|
Add(Intrinsic.X86Vfnmsub231ss, new IntrinsicInfo(X86Instruction.Vfnmsub231ss, IntrinsicType.Fma));
|
||||||
|
Add(Intrinsic.X86Vpternlogd, new IntrinsicInfo(X86Instruction.Vpternlogd, IntrinsicType.TernaryImm));
|
||||||
Add(Intrinsic.X86Xorpd, new IntrinsicInfo(X86Instruction.Xorpd, IntrinsicType.Binary));
|
Add(Intrinsic.X86Xorpd, new IntrinsicInfo(X86Instruction.Xorpd, IntrinsicType.Binary));
|
||||||
Add(Intrinsic.X86Xorps, new IntrinsicInfo(X86Instruction.Xorps, IntrinsicType.Binary));
|
Add(Intrinsic.X86Xorps, new IntrinsicInfo(X86Instruction.Xorps, IntrinsicType.Binary));
|
||||||
}
|
}
|
||||||
|
@@ -2,19 +2,20 @@ using ARMeilleure.CodeGen.RegisterAllocators;
|
|||||||
using ARMeilleure.IntermediateRepresentation;
|
using ARMeilleure.IntermediateRepresentation;
|
||||||
using ARMeilleure.Translation;
|
using ARMeilleure.Translation;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using static ARMeilleure.IntermediateRepresentation.Operand.Factory;
|
using static ARMeilleure.IntermediateRepresentation.Operand.Factory;
|
||||||
using static ARMeilleure.IntermediateRepresentation.Operation.Factory;
|
using static ARMeilleure.IntermediateRepresentation.Operation.Factory;
|
||||||
|
|
||||||
namespace ARMeilleure.CodeGen.X86
|
namespace ARMeilleure.CodeGen.X86
|
||||||
{
|
{
|
||||||
static class PreAllocator
|
class PreAllocator
|
||||||
{
|
{
|
||||||
public static void RunPass(CompilerContext cctx, StackAllocator stackAlloc, out int maxCallArgs)
|
public static void RunPass(CompilerContext cctx, StackAllocator stackAlloc, out int maxCallArgs)
|
||||||
{
|
{
|
||||||
maxCallArgs = -1;
|
maxCallArgs = -1;
|
||||||
|
|
||||||
|
Span<Operation> buffer = default;
|
||||||
|
|
||||||
CallConvName callConv = CallingConvention.GetCurrentCallConv();
|
CallConvName callConv = CallingConvention.GetCurrentCallConv();
|
||||||
|
|
||||||
Operand[] preservedArgs = new Operand[CallingConvention.GetArgumentsOnRegsCount()];
|
Operand[] preservedArgs = new Operand[CallingConvention.GetArgumentsOnRegsCount()];
|
||||||
@@ -32,9 +33,9 @@ namespace ARMeilleure.CodeGen.X86
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
HandleConstantRegCopy(block.Operations, node);
|
InsertConstantRegCopies(block.Operations, node);
|
||||||
HandleDestructiveRegCopy(block.Operations, node);
|
InsertDestructiveRegCopies(block.Operations, node);
|
||||||
HandleConstrainedRegCopy(block.Operations, node);
|
InsertConstrainedRegCopies(block.Operations, node);
|
||||||
|
|
||||||
switch (node.Instruction)
|
switch (node.Instruction)
|
||||||
{
|
{
|
||||||
@@ -59,62 +60,62 @@ namespace ARMeilleure.CodeGen.X86
|
|||||||
// being called, as mandated by the ABI.
|
// being called, as mandated by the ABI.
|
||||||
if (callConv == CallConvName.Windows)
|
if (callConv == CallConvName.Windows)
|
||||||
{
|
{
|
||||||
HandleCallWindowsAbi(block.Operations, stackAlloc, node);
|
PreAllocatorWindows.InsertCallCopies(block.Operations, stackAlloc, node);
|
||||||
}
|
}
|
||||||
else /* if (callConv == CallConvName.SystemV) */
|
else /* if (callConv == CallConvName.SystemV) */
|
||||||
{
|
{
|
||||||
HandleCallSystemVAbi(block.Operations, node);
|
PreAllocatorSystemV.InsertCallCopies(block.Operations, node);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Instruction.ConvertToFPUI:
|
case Instruction.ConvertToFPUI:
|
||||||
HandleConvertToFPUI(block.Operations, node);
|
GenerateConvertToFPUI(block.Operations, node);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Instruction.LoadArgument:
|
case Instruction.LoadArgument:
|
||||||
if (callConv == CallConvName.Windows)
|
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) */
|
else /* if (callConv == CallConvName.SystemV) */
|
||||||
{
|
{
|
||||||
nextNode = HandleLoadArgumentSystemVAbi(cctx, block.Operations, preservedArgs, node);
|
nextNode = PreAllocatorSystemV.InsertLoadArgumentCopy(cctx, ref buffer, block.Operations, preservedArgs, node);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Instruction.Negate:
|
case Instruction.Negate:
|
||||||
if (!node.GetSource(0).Type.IsInteger())
|
if (!node.GetSource(0).Type.IsInteger())
|
||||||
{
|
{
|
||||||
HandleNegate(block.Operations, node);
|
GenerateNegate(block.Operations, node);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Instruction.Return:
|
case Instruction.Return:
|
||||||
if (callConv == CallConvName.Windows)
|
if (callConv == CallConvName.Windows)
|
||||||
{
|
{
|
||||||
HandleReturnWindowsAbi(cctx, block.Operations, preservedArgs, node);
|
PreAllocatorWindows.InsertReturnCopy(cctx, block.Operations, preservedArgs, node);
|
||||||
}
|
}
|
||||||
else /* if (callConv == CallConvName.SystemV) */
|
else /* if (callConv == CallConvName.SystemV) */
|
||||||
{
|
{
|
||||||
HandleReturnSystemVAbi(block.Operations, node);
|
PreAllocatorSystemV.InsertReturnCopy(block.Operations, node);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Instruction.Tailcall:
|
case Instruction.Tailcall:
|
||||||
if (callConv == CallConvName.Windows)
|
if (callConv == CallConvName.Windows)
|
||||||
{
|
{
|
||||||
HandleTailcallWindowsAbi(block.Operations, stackAlloc, node);
|
PreAllocatorWindows.InsertTailcallCopies(block.Operations, stackAlloc, node);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
HandleTailcallSystemVAbi(block.Operations, stackAlloc, node);
|
PreAllocatorSystemV.InsertTailcallCopies(block.Operations, stackAlloc, node);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case Instruction.VectorInsert8:
|
case Instruction.VectorInsert8:
|
||||||
if (!HardwareCapabilities.SupportsSse41)
|
if (!HardwareCapabilities.SupportsSse41)
|
||||||
{
|
{
|
||||||
HandleVectorInsert8(block.Operations, node);
|
GenerateVectorInsert8(block.Operations, node);
|
||||||
}
|
}
|
||||||
break;
|
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))
|
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;
|
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)
|
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.
|
// Unsigned integer to FP conversions are not supported on X86.
|
||||||
// We need to turn them into signed integer to FP conversions, and
|
// We need to turn them into signed integer to FP conversions, and
|
||||||
@@ -501,7 +502,7 @@ namespace ARMeilleure.CodeGen.X86
|
|||||||
Delete(nodes, currentNode);
|
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
|
// 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.
|
// 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);
|
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.
|
// Handle vector insertion, when SSE 4.1 is not supported.
|
||||||
Operand dest = node.Destination;
|
Operand dest = node.Destination;
|
||||||
@@ -579,620 +580,7 @@ namespace ARMeilleure.CodeGen.X86
|
|||||||
Delete(nodes, currentNode);
|
Delete(nodes, currentNode);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void HandleCallWindowsAbi(IntrusiveList<Operation> nodes, StackAllocator stackAlloc, Operation node)
|
protected static Operand AddXmmCopy(IntrusiveList<Operation> nodes, Operation node, Operand source)
|
||||||
{
|
|
||||||
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)
|
|
||||||
{
|
{
|
||||||
Operand temp = Local(source.Type);
|
Operand temp = Local(source.Type);
|
||||||
Operand intConst = AddCopy(nodes, node, GetIntConst(source));
|
Operand intConst = AddCopy(nodes, node, GetIntConst(source));
|
||||||
@@ -1204,7 +592,7 @@ namespace ARMeilleure.CodeGen.X86
|
|||||||
return temp;
|
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);
|
Operand temp = Local(source.Type);
|
||||||
|
|
||||||
@@ -1229,7 +617,7 @@ namespace ARMeilleure.CodeGen.X86
|
|||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void Delete(IntrusiveList<Operation> nodes, Operation node)
|
protected static void Delete(IntrusiveList<Operation> nodes, Operation node)
|
||||||
{
|
{
|
||||||
node.Destination = default;
|
node.Destination = default;
|
||||||
|
|
||||||
@@ -1241,12 +629,12 @@ namespace ARMeilleure.CodeGen.X86
|
|||||||
nodes.Remove(node);
|
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);
|
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);
|
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>());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -219,6 +219,7 @@ namespace ARMeilleure.CodeGen.X86
|
|||||||
Vfnmsub231sd,
|
Vfnmsub231sd,
|
||||||
Vfnmsub231ss,
|
Vfnmsub231ss,
|
||||||
Vpblendvb,
|
Vpblendvb,
|
||||||
|
Vpternlogd,
|
||||||
Xor,
|
Xor,
|
||||||
Xorpd,
|
Xorpd,
|
||||||
Xorps,
|
Xorps,
|
||||||
|
@@ -17,7 +17,7 @@ namespace ARMeilleure.Decoders
|
|||||||
{
|
{
|
||||||
uint[] tbl = new uint[256];
|
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);
|
tbl[idx] = ExpandImm8ToFP32((uint)idx);
|
||||||
}
|
}
|
||||||
@@ -29,7 +29,7 @@ namespace ARMeilleure.Decoders
|
|||||||
{
|
{
|
||||||
ulong[] tbl = new ulong[256];
|
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);
|
tbl[idx] = ExpandImm8ToFP64((ulong)idx);
|
||||||
}
|
}
|
||||||
|
@@ -1301,7 +1301,7 @@ namespace ARMeilleure.Decoders
|
|||||||
{
|
{
|
||||||
List<InstInfo>[] temp = new List<InstInfo>[FastLookupSize];
|
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>();
|
temp[index] = new List<InstInfo>();
|
||||||
}
|
}
|
||||||
@@ -1311,7 +1311,7 @@ namespace ARMeilleure.Decoders
|
|||||||
int mask = ToFastLookupIndex(inst.Mask);
|
int mask = ToFastLookupIndex(inst.Mask);
|
||||||
int value = ToFastLookupIndex(inst.Value);
|
int value = ToFastLookupIndex(inst.Value);
|
||||||
|
|
||||||
for (int index = 0; index < FastLookupSize; index++)
|
for (int index = 0; index < temp.Length; index++)
|
||||||
{
|
{
|
||||||
if ((index & mask) == value)
|
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();
|
table[index] = temp[index].ToArray();
|
||||||
}
|
}
|
||||||
|
@@ -254,7 +254,22 @@ namespace ARMeilleure.Instructions
|
|||||||
|
|
||||||
public static void Not_V(ArmEmitterContext context)
|
public static void Not_V(ArmEmitterContext context)
|
||||||
{
|
{
|
||||||
if (Optimizations.UseSse2)
|
if (Optimizations.UseAvx512Ortho)
|
||||||
|
{
|
||||||
|
OpCodeSimd op = (OpCodeSimd)context.CurrOp;
|
||||||
|
|
||||||
|
Operand n = GetVec(op.Rn);
|
||||||
|
|
||||||
|
Operand res = context.AddIntrinsic(Intrinsic.X86Vpternlogd, n, n, Const(~0b10101010));
|
||||||
|
|
||||||
|
if (op.RegisterSize == RegisterSize.Simd64)
|
||||||
|
{
|
||||||
|
res = context.VectorZeroUpper64(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
context.Copy(GetVec(op.Rd), res);
|
||||||
|
}
|
||||||
|
else if (Optimizations.UseSse2)
|
||||||
{
|
{
|
||||||
OpCodeSimd op = (OpCodeSimd)context.CurrOp;
|
OpCodeSimd op = (OpCodeSimd)context.CurrOp;
|
||||||
|
|
||||||
@@ -283,6 +298,22 @@ namespace ARMeilleure.Instructions
|
|||||||
{
|
{
|
||||||
InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64OrnV);
|
InstEmitSimdHelperArm64.EmitVectorBinaryOp(context, Intrinsic.Arm64OrnV);
|
||||||
}
|
}
|
||||||
|
else if (Optimizations.UseAvx512Ortho)
|
||||||
|
{
|
||||||
|
OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp;
|
||||||
|
|
||||||
|
Operand n = GetVec(op.Rn);
|
||||||
|
Operand m = GetVec(op.Rm);
|
||||||
|
|
||||||
|
Operand res = context.AddIntrinsic(Intrinsic.X86Vpternlogd, n, m, Const(0b11001100 | ~0b10101010));
|
||||||
|
|
||||||
|
if (op.RegisterSize == RegisterSize.Simd64)
|
||||||
|
{
|
||||||
|
res = context.VectorZeroUpper64(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
context.Copy(GetVec(op.Rd), res);
|
||||||
|
}
|
||||||
else if (Optimizations.UseSse2)
|
else if (Optimizations.UseSse2)
|
||||||
{
|
{
|
||||||
OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp;
|
OpCodeSimdReg op = (OpCodeSimdReg)context.CurrOp;
|
||||||
|
@@ -151,6 +151,13 @@ namespace ARMeilleure.Instructions
|
|||||||
{
|
{
|
||||||
InstEmitSimdHelper32Arm64.EmitVectorBinaryOpSimd32(context, (n, m) => context.AddIntrinsic(Intrinsic.Arm64OrnV | Intrinsic.Arm64V128, n, m));
|
InstEmitSimdHelper32Arm64.EmitVectorBinaryOpSimd32(context, (n, m) => context.AddIntrinsic(Intrinsic.Arm64OrnV | Intrinsic.Arm64V128, n, m));
|
||||||
}
|
}
|
||||||
|
else if (Optimizations.UseAvx512Ortho)
|
||||||
|
{
|
||||||
|
EmitVectorBinaryOpSimd32(context, (n, m) =>
|
||||||
|
{
|
||||||
|
return context.AddIntrinsic(Intrinsic.X86Vpternlogd, n, m, Const(0b11001100 | ~0b10101010));
|
||||||
|
});
|
||||||
|
}
|
||||||
else if (Optimizations.UseSse2)
|
else if (Optimizations.UseSse2)
|
||||||
{
|
{
|
||||||
Operand mask = context.VectorOne();
|
Operand mask = context.VectorOne();
|
||||||
|
@@ -34,7 +34,14 @@ namespace ARMeilleure.Instructions
|
|||||||
|
|
||||||
public static void Vmvn_I(ArmEmitterContext context)
|
public static void Vmvn_I(ArmEmitterContext context)
|
||||||
{
|
{
|
||||||
if (Optimizations.UseSse2)
|
if (Optimizations.UseAvx512Ortho)
|
||||||
|
{
|
||||||
|
EmitVectorUnaryOpSimd32(context, (op1) =>
|
||||||
|
{
|
||||||
|
return context.AddIntrinsic(Intrinsic.X86Vpternlogd, op1, op1, Const(0b01010101));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else if (Optimizations.UseSse2)
|
||||||
{
|
{
|
||||||
EmitVectorUnaryOpSimd32(context, (op1) =>
|
EmitVectorUnaryOpSimd32(context, (op1) =>
|
||||||
{
|
{
|
||||||
|
@@ -173,6 +173,7 @@ namespace ARMeilleure.IntermediateRepresentation
|
|||||||
X86Vfnmadd231ss,
|
X86Vfnmadd231ss,
|
||||||
X86Vfnmsub231sd,
|
X86Vfnmsub231sd,
|
||||||
X86Vfnmsub231ss,
|
X86Vfnmsub231ss,
|
||||||
|
X86Vpternlogd,
|
||||||
X86Xorpd,
|
X86Xorpd,
|
||||||
X86Xorps,
|
X86Xorps,
|
||||||
|
|
||||||
|
@@ -23,6 +23,10 @@ namespace ARMeilleure
|
|||||||
public static bool UseSse42IfAvailable { get; set; } = true;
|
public static bool UseSse42IfAvailable { get; set; } = true;
|
||||||
public static bool UsePopCntIfAvailable { get; set; } = true;
|
public static bool UsePopCntIfAvailable { get; set; } = true;
|
||||||
public static bool UseAvxIfAvailable { get; set; } = true;
|
public static bool UseAvxIfAvailable { get; set; } = true;
|
||||||
|
public static bool UseAvx512FIfAvailable { get; set; } = true;
|
||||||
|
public static bool UseAvx512VlIfAvailable { get; set; } = true;
|
||||||
|
public static bool UseAvx512BwIfAvailable { get; set; } = true;
|
||||||
|
public static bool UseAvx512DqIfAvailable { get; set; } = true;
|
||||||
public static bool UseF16cIfAvailable { get; set; } = true;
|
public static bool UseF16cIfAvailable { get; set; } = true;
|
||||||
public static bool UseFmaIfAvailable { get; set; } = true;
|
public static bool UseFmaIfAvailable { get; set; } = true;
|
||||||
public static bool UseAesniIfAvailable { get; set; } = true;
|
public static bool UseAesniIfAvailable { get; set; } = true;
|
||||||
@@ -47,11 +51,18 @@ namespace ARMeilleure
|
|||||||
internal static bool UseSse42 => UseSse42IfAvailable && X86HardwareCapabilities.SupportsSse42;
|
internal static bool UseSse42 => UseSse42IfAvailable && X86HardwareCapabilities.SupportsSse42;
|
||||||
internal static bool UsePopCnt => UsePopCntIfAvailable && X86HardwareCapabilities.SupportsPopcnt;
|
internal static bool UsePopCnt => UsePopCntIfAvailable && X86HardwareCapabilities.SupportsPopcnt;
|
||||||
internal static bool UseAvx => UseAvxIfAvailable && X86HardwareCapabilities.SupportsAvx && !ForceLegacySse;
|
internal static bool UseAvx => UseAvxIfAvailable && X86HardwareCapabilities.SupportsAvx && !ForceLegacySse;
|
||||||
|
internal static bool UseAvx512F => UseAvx512FIfAvailable && X86HardwareCapabilities.SupportsAvx512F && !ForceLegacySse;
|
||||||
|
internal static bool UseAvx512Vl => UseAvx512VlIfAvailable && X86HardwareCapabilities.SupportsAvx512Vl && !ForceLegacySse;
|
||||||
|
internal static bool UseAvx512Bw => UseAvx512BwIfAvailable && X86HardwareCapabilities.SupportsAvx512Bw && !ForceLegacySse;
|
||||||
|
internal static bool UseAvx512Dq => UseAvx512DqIfAvailable && X86HardwareCapabilities.SupportsAvx512Dq && !ForceLegacySse;
|
||||||
internal static bool UseF16c => UseF16cIfAvailable && X86HardwareCapabilities.SupportsF16c;
|
internal static bool UseF16c => UseF16cIfAvailable && X86HardwareCapabilities.SupportsF16c;
|
||||||
internal static bool UseFma => UseFmaIfAvailable && X86HardwareCapabilities.SupportsFma;
|
internal static bool UseFma => UseFmaIfAvailable && X86HardwareCapabilities.SupportsFma;
|
||||||
internal static bool UseAesni => UseAesniIfAvailable && X86HardwareCapabilities.SupportsAesni;
|
internal static bool UseAesni => UseAesniIfAvailable && X86HardwareCapabilities.SupportsAesni;
|
||||||
internal static bool UsePclmulqdq => UsePclmulqdqIfAvailable && X86HardwareCapabilities.SupportsPclmulqdq;
|
internal static bool UsePclmulqdq => UsePclmulqdqIfAvailable && X86HardwareCapabilities.SupportsPclmulqdq;
|
||||||
internal static bool UseSha => UseShaIfAvailable && X86HardwareCapabilities.SupportsSha;
|
internal static bool UseSha => UseShaIfAvailable && X86HardwareCapabilities.SupportsSha;
|
||||||
internal static bool UseGfni => UseGfniIfAvailable && X86HardwareCapabilities.SupportsGfni;
|
internal static bool UseGfni => UseGfniIfAvailable && X86HardwareCapabilities.SupportsGfni;
|
||||||
|
|
||||||
|
internal static bool UseAvx512Ortho => UseAvx512F && UseAvx512Vl;
|
||||||
|
internal static bool UseAvx512OrthoFloat => UseAvx512Ortho && UseAvx512Dq;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -6,6 +6,7 @@ using ARMeilleure.Memory;
|
|||||||
using Ryujinx.Common;
|
using Ryujinx.Common;
|
||||||
using Ryujinx.Common.Configuration;
|
using Ryujinx.Common.Configuration;
|
||||||
using Ryujinx.Common.Logging;
|
using Ryujinx.Common.Logging;
|
||||||
|
using Ryujinx.Common.Memory;
|
||||||
using System;
|
using System;
|
||||||
using System.Buffers.Binary;
|
using System.Buffers.Binary;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
@@ -29,7 +30,7 @@ namespace ARMeilleure.Translation.PTC
|
|||||||
private const string OuterHeaderMagicString = "PTCohd\0\0";
|
private const string OuterHeaderMagicString = "PTCohd\0\0";
|
||||||
private const string InnerHeaderMagicString = "PTCihd\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 = 4485; //! To be incremented manually for each change to the ARMeilleure project.
|
||||||
|
|
||||||
private const string ActualDir = "0";
|
private const string ActualDir = "0";
|
||||||
private const string BackupDir = "1";
|
private const string BackupDir = "1";
|
||||||
@@ -150,10 +151,10 @@ namespace ARMeilleure.Translation.PTC
|
|||||||
|
|
||||||
private void InitializeCarriers()
|
private void InitializeCarriers()
|
||||||
{
|
{
|
||||||
_infosStream = new MemoryStream();
|
_infosStream = MemoryStreamManager.Shared.GetStream();
|
||||||
_codesList = new List<byte[]>();
|
_codesList = new List<byte[]>();
|
||||||
_relocsStream = new MemoryStream();
|
_relocsStream = MemoryStreamManager.Shared.GetStream();
|
||||||
_unwindInfosStream = new MemoryStream();
|
_unwindInfosStream = MemoryStreamManager.Shared.GetStream();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DisposeCarriers()
|
private void DisposeCarriers()
|
||||||
@@ -968,6 +969,7 @@ namespace ARMeilleure.Translation.PTC
|
|||||||
(ulong)Arm64HardwareCapabilities.LinuxFeatureInfoHwCap,
|
(ulong)Arm64HardwareCapabilities.LinuxFeatureInfoHwCap,
|
||||||
(ulong)Arm64HardwareCapabilities.LinuxFeatureInfoHwCap2,
|
(ulong)Arm64HardwareCapabilities.LinuxFeatureInfoHwCap2,
|
||||||
(ulong)Arm64HardwareCapabilities.MacOsFeatureInfo,
|
(ulong)Arm64HardwareCapabilities.MacOsFeatureInfo,
|
||||||
|
0,
|
||||||
0);
|
0);
|
||||||
}
|
}
|
||||||
else if (RuntimeInformation.ProcessArchitecture == Architecture.X64)
|
else if (RuntimeInformation.ProcessArchitecture == Architecture.X64)
|
||||||
@@ -976,11 +978,12 @@ namespace ARMeilleure.Translation.PTC
|
|||||||
(ulong)X86HardwareCapabilities.FeatureInfo1Ecx,
|
(ulong)X86HardwareCapabilities.FeatureInfo1Ecx,
|
||||||
(ulong)X86HardwareCapabilities.FeatureInfo1Edx,
|
(ulong)X86HardwareCapabilities.FeatureInfo1Edx,
|
||||||
(ulong)X86HardwareCapabilities.FeatureInfo7Ebx,
|
(ulong)X86HardwareCapabilities.FeatureInfo7Ebx,
|
||||||
(ulong)X86HardwareCapabilities.FeatureInfo7Ecx);
|
(ulong)X86HardwareCapabilities.FeatureInfo7Ecx,
|
||||||
|
(ulong)X86HardwareCapabilities.Xcr0InfoEax);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
return new FeatureInfo(0, 0, 0, 0);
|
return new FeatureInfo(0, 0, 0, 0, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1001,7 +1004,7 @@ namespace ARMeilleure.Translation.PTC
|
|||||||
return osPlatform;
|
return osPlatform;
|
||||||
}
|
}
|
||||||
|
|
||||||
[StructLayout(LayoutKind.Sequential, Pack = 1/*, Size = 78*/)]
|
[StructLayout(LayoutKind.Sequential, Pack = 1/*, Size = 86*/)]
|
||||||
private struct OuterHeader
|
private struct OuterHeader
|
||||||
{
|
{
|
||||||
public ulong Magic;
|
public ulong Magic;
|
||||||
@@ -1033,8 +1036,8 @@ namespace ARMeilleure.Translation.PTC
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[StructLayout(LayoutKind.Sequential, Pack = 1/*, Size = 32*/)]
|
[StructLayout(LayoutKind.Sequential, Pack = 1/*, Size = 40*/)]
|
||||||
private record struct FeatureInfo(ulong FeatureInfo0, ulong FeatureInfo1, ulong FeatureInfo2, ulong FeatureInfo3);
|
private record struct FeatureInfo(ulong FeatureInfo0, ulong FeatureInfo1, ulong FeatureInfo2, ulong FeatureInfo3, ulong FeatureInfo4);
|
||||||
|
|
||||||
[StructLayout(LayoutKind.Sequential, Pack = 1/*, Size = 128*/)]
|
[StructLayout(LayoutKind.Sequential, Pack = 1/*, Size = 128*/)]
|
||||||
private struct InnerHeader
|
private struct InnerHeader
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
using ARMeilleure.State;
|
using ARMeilleure.State;
|
||||||
using Ryujinx.Common;
|
using Ryujinx.Common;
|
||||||
using Ryujinx.Common.Logging;
|
using Ryujinx.Common.Logging;
|
||||||
|
using Ryujinx.Common.Memory;
|
||||||
using System;
|
using System;
|
||||||
using System.Buffers.Binary;
|
using System.Buffers.Binary;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
@@ -182,7 +183,7 @@ namespace ARMeilleure.Translation.PTC
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
using (MemoryStream stream = new MemoryStream())
|
using (MemoryStream stream = MemoryStreamManager.Shared.GetStream())
|
||||||
{
|
{
|
||||||
Debug.Assert(stream.Seek(0L, SeekOrigin.Begin) == 0L && stream.Length == 0L);
|
Debug.Assert(stream.Seek(0L, SeekOrigin.Begin) == 0L && stream.Length == 0L);
|
||||||
|
|
||||||
@@ -274,7 +275,7 @@ namespace ARMeilleure.Translation.PTC
|
|||||||
|
|
||||||
outerHeader.SetHeaderHash();
|
outerHeader.SetHeaderHash();
|
||||||
|
|
||||||
using (MemoryStream stream = new MemoryStream())
|
using (MemoryStream stream = MemoryStreamManager.Shared.GetStream())
|
||||||
{
|
{
|
||||||
Debug.Assert(stream.Seek(0L, SeekOrigin.Begin) == 0L && stream.Length == 0L);
|
Debug.Assert(stream.Seek(0L, SeekOrigin.Begin) == 0L && stream.Length == 0L);
|
||||||
|
|
||||||
|
@@ -12,7 +12,6 @@
|
|||||||
<PackageVersion Include="Avalonia.Svg.Skia" Version="0.10.18" />
|
<PackageVersion Include="Avalonia.Svg.Skia" Version="0.10.18" />
|
||||||
<PackageVersion Include="CommandLineParser" Version="2.9.1" />
|
<PackageVersion Include="CommandLineParser" Version="2.9.1" />
|
||||||
<PackageVersion Include="Concentus" Version="1.1.7" />
|
<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="DiscordRichPresence" Version="1.1.3.18" />
|
||||||
<PackageVersion Include="DynamicData" Version="7.12.11" />
|
<PackageVersion Include="DynamicData" Version="7.12.11" />
|
||||||
<PackageVersion Include="FluentAvaloniaUI" Version="1.4.5" />
|
<PackageVersion Include="FluentAvaloniaUI" Version="1.4.5" />
|
||||||
@@ -23,6 +22,7 @@
|
|||||||
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" />
|
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" />
|
||||||
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.5.0" />
|
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.5.0" />
|
||||||
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.5.0" />
|
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.5.0" />
|
||||||
|
<PackageVersion Include="Microsoft.IO.RecyclableMemoryStream" Version="2.3.2" />
|
||||||
<PackageVersion Include="MsgPack.Cli" Version="1.0.1" />
|
<PackageVersion Include="MsgPack.Cli" Version="1.0.1" />
|
||||||
<PackageVersion Include="NUnit" Version="3.13.3" />
|
<PackageVersion Include="NUnit" Version="3.13.3" />
|
||||||
<PackageVersion Include="NUnit3TestAdapter" Version="4.1.0" />
|
<PackageVersion Include="NUnit3TestAdapter" Version="4.1.0" />
|
||||||
@@ -34,7 +34,7 @@
|
|||||||
<PackageVersion Include="Ryujinx.Graphics.Nvdec.Dependencies" Version="5.0.1-build13" />
|
<PackageVersion Include="Ryujinx.Graphics.Nvdec.Dependencies" Version="5.0.1-build13" />
|
||||||
<PackageVersion Include="Ryujinx.Graphics.Vulkan.Dependencies.MoltenVK" Version="1.2.0" />
|
<PackageVersion Include="Ryujinx.Graphics.Vulkan.Dependencies.MoltenVK" Version="1.2.0" />
|
||||||
<PackageVersion Include="Ryujinx.GtkSharp" Version="3.24.24.59-ryujinx" />
|
<PackageVersion Include="Ryujinx.GtkSharp" Version="3.24.24.59-ryujinx" />
|
||||||
<PackageVersion Include="Ryujinx.SDL2-CS" Version="2.26.1-build23" />
|
<PackageVersion Include="Ryujinx.SDL2-CS" Version="2.26.3-build25" />
|
||||||
<PackageVersion Include="shaderc.net" Version="0.1.0" />
|
<PackageVersion Include="shaderc.net" Version="0.1.0" />
|
||||||
<PackageVersion Include="SharpZipLib" Version="1.4.2" />
|
<PackageVersion Include="SharpZipLib" Version="1.4.2" />
|
||||||
<PackageVersion Include="Silk.NET.Vulkan" Version="2.16.0" />
|
<PackageVersion Include="Silk.NET.Vulkan" Version="2.16.0" />
|
||||||
@@ -45,11 +45,9 @@
|
|||||||
<PackageVersion Include="SPB" Version="0.0.4-build28" />
|
<PackageVersion Include="SPB" Version="0.0.4-build28" />
|
||||||
<PackageVersion Include="System.Drawing.Common" Version="7.0.0" />
|
<PackageVersion Include="System.Drawing.Common" Version="7.0.0" />
|
||||||
<PackageVersion Include="System.IdentityModel.Tokens.Jwt" Version="6.27.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.Management" Version="7.0.0" />
|
||||||
<PackageVersion Include="System.Net.NameResolution" Version="4.3.0" />
|
<PackageVersion Include="UnicornEngine.Unicorn" Version="2.0.2-rc1-fb78016" />
|
||||||
<PackageVersion Include="System.Threading.ThreadPool" Version="4.3.0" />
|
|
||||||
<PackageVersion Include="UnicornEngine.Unicorn" Version="2.0.2-rc1-f7c841d" />
|
|
||||||
<PackageVersion Include="XamlNameReferenceGenerator" Version="1.5.1" />
|
<PackageVersion Include="XamlNameReferenceGenerator" Version="1.5.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
@@ -400,7 +400,9 @@ namespace Ryujinx.Audio.Common
|
|||||||
{
|
{
|
||||||
uint bufferIndex = (_releasedBufferIndex - _bufferReleasedCount) % Constants.AudioDeviceBufferCountMax;
|
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)
|
if (_buffers[bufferIndex].BufferTag == bufferTag)
|
||||||
{
|
{
|
||||||
|
@@ -583,10 +583,10 @@
|
|||||||
"SelectUpdateDialogTitle": "Select update files",
|
"SelectUpdateDialogTitle": "Select update files",
|
||||||
"UserProfileWindowTitle": "User Profiles Manager",
|
"UserProfileWindowTitle": "User Profiles Manager",
|
||||||
"CheatWindowTitle": "Cheats Manager",
|
"CheatWindowTitle": "Cheats Manager",
|
||||||
"DlcWindowTitle": "Downloadable Content Manager",
|
"DlcWindowTitle": "Manage Downloadable Content for {0} ({1})",
|
||||||
"UpdateWindowTitle": "Title Update Manager",
|
"UpdateWindowTitle": "Title Update Manager",
|
||||||
"CheatWindowHeading": "Cheats Available for {0} [{1}]",
|
"CheatWindowHeading": "Cheats Available for {0} [{1}]",
|
||||||
"DlcWindowHeading": "{0} Downloadable Content(s) available for {1} ({2})",
|
"DlcWindowHeading": "{0} Downloadable Content(s)",
|
||||||
"UserProfilesEditProfile": "Edit Selected",
|
"UserProfilesEditProfile": "Edit Selected",
|
||||||
"Cancel": "Cancel",
|
"Cancel": "Cancel",
|
||||||
"Save": "Save",
|
"Save": "Save",
|
||||||
|
@@ -13,7 +13,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<Target Name="PostBuild" AfterTargets="PostBuildEvent" Condition="$([MSBuild]::IsOSPlatform('OSX'))">
|
<Target Name="PostBuild" AfterTargets="PostBuildEvent" Condition="$([MSBuild]::IsOSPlatform('OSX'))">
|
||||||
<Exec Command="codesign --entitlements $(ProjectDir)..\distribution\macos\entitlements.xml -f --deep -s $(SigningCertificate) $(TargetDir)$(TargetName)" />
|
<Exec Command="codesign --entitlements '$(ProjectDir)..\distribution\macos\entitlements.xml' -f --deep -s $(SigningCertificate) '$(TargetDir)$(TargetName)'" />
|
||||||
</Target>
|
</Target>
|
||||||
|
|
||||||
<PropertyGroup Condition="'$(RuntimeIdentifier)' != ''">
|
<PropertyGroup Condition="'$(RuntimeIdentifier)' != ''">
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
using Ryujinx.Ava.UI.ViewModels;
|
using Ryujinx.Ava.UI.ViewModels;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
namespace Ryujinx.Ava.UI.Models
|
namespace Ryujinx.Ava.UI.Models
|
||||||
{
|
{
|
||||||
@@ -21,6 +22,8 @@ namespace Ryujinx.Ava.UI.Models
|
|||||||
public string ContainerPath { get; }
|
public string ContainerPath { get; }
|
||||||
public string FullPath { get; }
|
public string FullPath { get; }
|
||||||
|
|
||||||
|
public string FileName => Path.GetFileName(ContainerPath);
|
||||||
|
|
||||||
public DownloadableContentModel(string titleId, string containerPath, string fullPath, bool enabled)
|
public DownloadableContentModel(string titleId, string containerPath, string fullPath, bool enabled)
|
||||||
{
|
{
|
||||||
TitleId = titleId;
|
TitleId = titleId;
|
||||||
|
340
Ryujinx.Ava/UI/ViewModels/DownloadableContentManagerViewModel.cs
Normal file
340
Ryujinx.Ava/UI/ViewModels/DownloadableContentManagerViewModel.cs
Normal file
@@ -0,0 +1,340 @@
|
|||||||
|
using Avalonia.Collections;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Controls.ApplicationLifetimes;
|
||||||
|
using Avalonia.Threading;
|
||||||
|
using DynamicData;
|
||||||
|
using LibHac.Common;
|
||||||
|
using LibHac.Fs;
|
||||||
|
using LibHac.Fs.Fsa;
|
||||||
|
using LibHac.FsSystem;
|
||||||
|
using LibHac.Tools.Fs;
|
||||||
|
using LibHac.Tools.FsSystem;
|
||||||
|
using LibHac.Tools.FsSystem.NcaUtils;
|
||||||
|
using Ryujinx.Ava.Common.Locale;
|
||||||
|
using Ryujinx.Ava.UI.Helpers;
|
||||||
|
using Ryujinx.Ava.UI.Models;
|
||||||
|
using Ryujinx.Common.Configuration;
|
||||||
|
using Ryujinx.Common.Logging;
|
||||||
|
using Ryujinx.Common.Utilities;
|
||||||
|
using Ryujinx.HLE.FileSystem;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Path = System.IO.Path;
|
||||||
|
|
||||||
|
namespace Ryujinx.Ava.UI.ViewModels
|
||||||
|
{
|
||||||
|
public class DownloadableContentManagerViewModel : BaseModel
|
||||||
|
{
|
||||||
|
private readonly List<DownloadableContentContainer> _downloadableContentContainerList;
|
||||||
|
private readonly string _downloadableContentJsonPath;
|
||||||
|
|
||||||
|
private VirtualFileSystem _virtualFileSystem;
|
||||||
|
private AvaloniaList<DownloadableContentModel> _downloadableContents = new();
|
||||||
|
private AvaloniaList<DownloadableContentModel> _views = new();
|
||||||
|
private AvaloniaList<DownloadableContentModel> _selectedDownloadableContents = new();
|
||||||
|
|
||||||
|
private string _search;
|
||||||
|
private ulong _titleId;
|
||||||
|
private string _titleName;
|
||||||
|
|
||||||
|
public AvaloniaList<DownloadableContentModel> DownloadableContents
|
||||||
|
{
|
||||||
|
get => _downloadableContents;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_downloadableContents = value;
|
||||||
|
OnPropertyChanged();
|
||||||
|
OnPropertyChanged(nameof(UpdateCount));
|
||||||
|
Sort();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public AvaloniaList<DownloadableContentModel> Views
|
||||||
|
{
|
||||||
|
get => _views;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_views = value;
|
||||||
|
OnPropertyChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public AvaloniaList<DownloadableContentModel> SelectedDownloadableContents
|
||||||
|
{
|
||||||
|
get => _selectedDownloadableContents;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_selectedDownloadableContents = value;
|
||||||
|
OnPropertyChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Search
|
||||||
|
{
|
||||||
|
get => _search;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_search = value;
|
||||||
|
OnPropertyChanged();
|
||||||
|
Sort();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string UpdateCount
|
||||||
|
{
|
||||||
|
get => string.Format(LocaleManager.Instance[LocaleKeys.DlcWindowHeading], DownloadableContents.Count);
|
||||||
|
}
|
||||||
|
|
||||||
|
public DownloadableContentManagerViewModel(VirtualFileSystem virtualFileSystem, ulong titleId, string titleName)
|
||||||
|
{
|
||||||
|
_virtualFileSystem = virtualFileSystem;
|
||||||
|
|
||||||
|
_titleId = titleId;
|
||||||
|
_titleName = titleName;
|
||||||
|
|
||||||
|
_downloadableContentJsonPath = Path.Combine(AppDataManager.GamesDirPath, titleId.ToString("x16"), "dlc.json");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_downloadableContentContainerList = JsonHelper.DeserializeFromFile<List<DownloadableContentContainer>>(_downloadableContentJsonPath);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
Logger.Error?.Print(LogClass.Configuration, "Downloadable Content JSON failed to deserialize.");
|
||||||
|
_downloadableContentContainerList = new List<DownloadableContentContainer>();
|
||||||
|
}
|
||||||
|
|
||||||
|
LoadDownloadableContents();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LoadDownloadableContents()
|
||||||
|
{
|
||||||
|
foreach (DownloadableContentContainer downloadableContentContainer in _downloadableContentContainerList)
|
||||||
|
{
|
||||||
|
if (File.Exists(downloadableContentContainer.ContainerPath))
|
||||||
|
{
|
||||||
|
using FileStream containerFile = File.OpenRead(downloadableContentContainer.ContainerPath);
|
||||||
|
|
||||||
|
PartitionFileSystem partitionFileSystem = new(containerFile.AsStorage());
|
||||||
|
|
||||||
|
_virtualFileSystem.ImportTickets(partitionFileSystem);
|
||||||
|
|
||||||
|
foreach (DownloadableContentNca downloadableContentNca in downloadableContentContainer.DownloadableContentNcaList)
|
||||||
|
{
|
||||||
|
using UniqueRef<IFile> ncaFile = new();
|
||||||
|
|
||||||
|
partitionFileSystem.OpenFile(ref ncaFile.Ref, downloadableContentNca.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
||||||
|
|
||||||
|
Nca nca = TryOpenNca(ncaFile.Get.AsStorage(), downloadableContentContainer.ContainerPath);
|
||||||
|
if (nca != null)
|
||||||
|
{
|
||||||
|
var content = new DownloadableContentModel(nca.Header.TitleId.ToString("X16"),
|
||||||
|
downloadableContentContainer.ContainerPath,
|
||||||
|
downloadableContentNca.FullPath,
|
||||||
|
downloadableContentNca.Enabled);
|
||||||
|
|
||||||
|
DownloadableContents.Add(content);
|
||||||
|
|
||||||
|
if (content.Enabled)
|
||||||
|
{
|
||||||
|
SelectedDownloadableContents.Add(content);
|
||||||
|
}
|
||||||
|
|
||||||
|
OnPropertyChanged(nameof(UpdateCount));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: Save the list again to remove leftovers.
|
||||||
|
Save();
|
||||||
|
Sort();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Sort()
|
||||||
|
{
|
||||||
|
DownloadableContents.AsObservableChangeSet()
|
||||||
|
.Filter(Filter)
|
||||||
|
.Bind(out var view).AsObservableList();
|
||||||
|
|
||||||
|
_views.Clear();
|
||||||
|
_views.AddRange(view);
|
||||||
|
OnPropertyChanged(nameof(Views));
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool Filter(object arg)
|
||||||
|
{
|
||||||
|
if (arg is DownloadableContentModel content)
|
||||||
|
{
|
||||||
|
return string.IsNullOrWhiteSpace(_search) || content.FileName.ToLower().Contains(_search.ToLower()) || content.TitleId.ToLower().Contains(_search.ToLower());
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Nca TryOpenNca(IStorage ncaStorage, string containerPath)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return new Nca(_virtualFileSystem.KeySet, ncaStorage);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Dispatcher.UIThread.InvokeAsync(async () =>
|
||||||
|
{
|
||||||
|
await ContentDialogHelper.CreateErrorDialog(string.Format(LocaleManager.Instance[LocaleKeys.DialogLoadNcaErrorMessage], ex.Message, containerPath));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async void Add()
|
||||||
|
{
|
||||||
|
OpenFileDialog dialog = new OpenFileDialog()
|
||||||
|
{
|
||||||
|
Title = LocaleManager.Instance[LocaleKeys.SelectDlcDialogTitle],
|
||||||
|
AllowMultiple = true
|
||||||
|
};
|
||||||
|
|
||||||
|
dialog.Filters.Add(new FileDialogFilter
|
||||||
|
{
|
||||||
|
Name = "NSP",
|
||||||
|
Extensions = { "nsp" }
|
||||||
|
});
|
||||||
|
|
||||||
|
if (Avalonia.Application.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
||||||
|
{
|
||||||
|
string[] files = await dialog.ShowAsync(desktop.MainWindow);
|
||||||
|
|
||||||
|
if (files != null)
|
||||||
|
{
|
||||||
|
foreach (string file in files)
|
||||||
|
{
|
||||||
|
await AddDownloadableContent(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task AddDownloadableContent(string path)
|
||||||
|
{
|
||||||
|
if (!File.Exists(path) || DownloadableContents.FirstOrDefault(x => x.ContainerPath == path) != null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
using FileStream containerFile = File.OpenRead(path);
|
||||||
|
|
||||||
|
PartitionFileSystem partitionFileSystem = new(containerFile.AsStorage());
|
||||||
|
bool containsDownloadableContent = false;
|
||||||
|
|
||||||
|
_virtualFileSystem.ImportTickets(partitionFileSystem);
|
||||||
|
|
||||||
|
foreach (DirectoryEntryEx fileEntry in partitionFileSystem.EnumerateEntries("/", "*.nca"))
|
||||||
|
{
|
||||||
|
using var ncaFile = new UniqueRef<IFile>();
|
||||||
|
|
||||||
|
partitionFileSystem.OpenFile(ref ncaFile.Ref, fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
||||||
|
|
||||||
|
Nca nca = TryOpenNca(ncaFile.Get.AsStorage(), path);
|
||||||
|
if (nca == null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nca.Header.ContentType == NcaContentType.PublicData)
|
||||||
|
{
|
||||||
|
if ((nca.Header.TitleId & 0xFFFFFFFFFFFFE000) != _titleId)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
var content = new DownloadableContentModel(nca.Header.TitleId.ToString("X16"), path, fileEntry.FullPath, true);
|
||||||
|
DownloadableContents.Add(content);
|
||||||
|
SelectedDownloadableContents.Add(content);
|
||||||
|
|
||||||
|
OnPropertyChanged(nameof(UpdateCount));
|
||||||
|
Sort();
|
||||||
|
|
||||||
|
containsDownloadableContent = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!containsDownloadableContent)
|
||||||
|
{
|
||||||
|
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogDlcNoDlcErrorMessage]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Remove(DownloadableContentModel model)
|
||||||
|
{
|
||||||
|
DownloadableContents.Remove(model);
|
||||||
|
OnPropertyChanged(nameof(UpdateCount));
|
||||||
|
Sort();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RemoveAll()
|
||||||
|
{
|
||||||
|
DownloadableContents.Clear();
|
||||||
|
OnPropertyChanged(nameof(UpdateCount));
|
||||||
|
Sort();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void EnableAll()
|
||||||
|
{
|
||||||
|
SelectedDownloadableContents = new(DownloadableContents);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DisableAll()
|
||||||
|
{
|
||||||
|
SelectedDownloadableContents.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Save()
|
||||||
|
{
|
||||||
|
_downloadableContentContainerList.Clear();
|
||||||
|
|
||||||
|
DownloadableContentContainer container = default;
|
||||||
|
|
||||||
|
foreach (DownloadableContentModel downloadableContent in DownloadableContents)
|
||||||
|
{
|
||||||
|
if (container.ContainerPath != downloadableContent.ContainerPath)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrWhiteSpace(container.ContainerPath))
|
||||||
|
{
|
||||||
|
_downloadableContentContainerList.Add(container);
|
||||||
|
}
|
||||||
|
|
||||||
|
container = new DownloadableContentContainer
|
||||||
|
{
|
||||||
|
ContainerPath = downloadableContent.ContainerPath,
|
||||||
|
DownloadableContentNcaList = new List<DownloadableContentNca>()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
container.DownloadableContentNcaList.Add(new DownloadableContentNca
|
||||||
|
{
|
||||||
|
Enabled = downloadableContent.Enabled,
|
||||||
|
TitleId = Convert.ToUInt64(downloadableContent.TitleId, 16),
|
||||||
|
FullPath = downloadableContent.FullPath
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(container.ContainerPath))
|
||||||
|
{
|
||||||
|
_downloadableContentContainerList.Add(container);
|
||||||
|
}
|
||||||
|
|
||||||
|
using (FileStream downloadableContentJsonStream = File.Create(_downloadableContentJsonPath, 4096, FileOptions.WriteThrough))
|
||||||
|
{
|
||||||
|
downloadableContentJsonStream.Write(Encoding.UTF8.GetBytes(JsonHelper.Serialize(_downloadableContentContainerList, true)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@@ -1564,7 +1564,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
{
|
{
|
||||||
if (SelectedApplication != null)
|
if (SelectedApplication != null)
|
||||||
{
|
{
|
||||||
await new DownloadableContentManagerWindow(VirtualFileSystem, ulong.Parse(SelectedApplication.TitleId, NumberStyles.HexNumber), SelectedApplication.TitleName).ShowDialog(TopLevel as Window);
|
await DownloadableContentManagerWindow.Show(VirtualFileSystem, ulong.Parse(SelectedApplication.TitleId, NumberStyles.HexNumber), SelectedApplication.TitleName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -236,7 +236,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
|
|
||||||
public DateTimeOffset DateOffset { get; set; }
|
public DateTimeOffset DateOffset { get; set; }
|
||||||
public TimeSpan TimeOffset { 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 AvaloniaList<string> GameDirectories { get; set; }
|
||||||
public ObservableCollection<ComboBoxItem> AvailableGpus { get; set; }
|
public ObservableCollection<ComboBoxItem> AvailableGpus { get; set; }
|
||||||
|
|
||||||
|
@@ -1,172 +1,194 @@
|
|||||||
<window:StyleableWindow
|
<UserControl
|
||||||
x:Class="Ryujinx.Ava.UI.Windows.DownloadableContentManagerWindow"
|
x:Class="Ryujinx.Ava.UI.Windows.DownloadableContentManagerWindow"
|
||||||
xmlns="https://github.com/avaloniaui"
|
xmlns="https://github.com/avaloniaui"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
|
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:window="clr-namespace:Ryujinx.Ava.UI.Windows"
|
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
|
||||||
Width="800"
|
xmlns:models="clr-namespace:Ryujinx.Ava.UI.Models"
|
||||||
Height="500"
|
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||||
MinWidth="800"
|
Width="500"
|
||||||
MinHeight="500"
|
Height="380"
|
||||||
MaxWidth="800"
|
|
||||||
MaxHeight="500"
|
|
||||||
SizeToContent="Height"
|
|
||||||
WindowStartupLocation="CenterOwner"
|
|
||||||
mc:Ignorable="d"
|
mc:Ignorable="d"
|
||||||
|
x:CompileBindings="True"
|
||||||
|
x:DataType="viewModels:DownloadableContentManagerViewModel"
|
||||||
Focusable="True">
|
Focusable="True">
|
||||||
<Grid Name="DownloadableContentGrid" Margin="15">
|
<Grid>
|
||||||
<Grid.RowDefinitions>
|
<Grid.RowDefinitions>
|
||||||
<RowDefinition Height="Auto" />
|
|
||||||
<RowDefinition Height="Auto" />
|
|
||||||
<RowDefinition Height="Auto" />
|
<RowDefinition Height="Auto" />
|
||||||
<RowDefinition Height="*" />
|
<RowDefinition Height="*" />
|
||||||
<RowDefinition Height="Auto" />
|
<RowDefinition Height="Auto" />
|
||||||
</Grid.RowDefinitions>
|
</Grid.RowDefinitions>
|
||||||
<TextBlock
|
<Panel
|
||||||
Name="Heading"
|
Margin="0 0 0 10"
|
||||||
Grid.Row="1"
|
Grid.Row="0">
|
||||||
MaxWidth="500"
|
<Grid>
|
||||||
Margin="20,15,20,20"
|
<Grid.ColumnDefinitions>
|
||||||
HorizontalAlignment="Center"
|
<ColumnDefinition Width="Auto" />
|
||||||
VerticalAlignment="Center"
|
<ColumnDefinition Width="Auto" />
|
||||||
LineHeight="18"
|
<ColumnDefinition Width="*" />
|
||||||
TextAlignment="Center"
|
</Grid.ColumnDefinitions>
|
||||||
TextWrapping="Wrap" />
|
<TextBlock
|
||||||
<DockPanel
|
Grid.Column="0"
|
||||||
Grid.Row="2"
|
Text="{Binding UpdateCount}" />
|
||||||
Margin="0"
|
<StackPanel
|
||||||
HorizontalAlignment="Left">
|
Margin="10 0"
|
||||||
<Button
|
Grid.Column="1"
|
||||||
Name="EnableAllButton"
|
Orientation="Horizontal">
|
||||||
MinWidth="90"
|
<Button
|
||||||
Margin="5"
|
Name="EnableAllButton"
|
||||||
Command="{Binding EnableAll}">
|
MinWidth="90"
|
||||||
<TextBlock Text="{locale:Locale DlcManagerEnableAllButton}" />
|
Margin="5"
|
||||||
</Button>
|
Command="{ReflectionBinding EnableAll}">
|
||||||
<Button
|
<TextBlock Text="{locale:Locale DlcManagerEnableAllButton}" />
|
||||||
Name="DisableAllButton"
|
</Button>
|
||||||
MinWidth="90"
|
<Button
|
||||||
Margin="5"
|
Name="DisableAllButton"
|
||||||
Command="{Binding DisableAll}">
|
MinWidth="90"
|
||||||
<TextBlock Text="{locale:Locale DlcManagerDisableAllButton}" />
|
Margin="5"
|
||||||
</Button>
|
Command="{ReflectionBinding DisableAll}">
|
||||||
</DockPanel>
|
<TextBlock Text="{locale:Locale DlcManagerDisableAllButton}" />
|
||||||
|
</Button>
|
||||||
|
</StackPanel>
|
||||||
|
<TextBox
|
||||||
|
Grid.Column="2"
|
||||||
|
MinHeight="27"
|
||||||
|
MaxHeight="27"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
Watermark="{locale:Locale Search}"
|
||||||
|
Text="{Binding Search}" />
|
||||||
|
</Grid>
|
||||||
|
</Panel>
|
||||||
<Border
|
<Border
|
||||||
Grid.Row="3"
|
Grid.Row="1"
|
||||||
Margin="5"
|
Margin="0 0 0 24"
|
||||||
HorizontalAlignment="Stretch"
|
HorizontalAlignment="Stretch"
|
||||||
VerticalAlignment="Stretch"
|
VerticalAlignment="Stretch"
|
||||||
BorderBrush="Gray"
|
BorderBrush="{DynamicResource AppListHoverBackgroundColor}"
|
||||||
BorderThickness="1">
|
BorderThickness="1"
|
||||||
<ScrollViewer
|
CornerRadius="5"
|
||||||
VerticalAlignment="Stretch"
|
Padding="2.5">
|
||||||
HorizontalScrollBarVisibility="Auto"
|
<ListBox
|
||||||
VerticalScrollBarVisibility="Auto">
|
AutoScrollToSelectedItem="False"
|
||||||
<DataGrid
|
VirtualizationMode="None"
|
||||||
Name="DlcDataGrid"
|
SelectionMode="Multiple, Toggle"
|
||||||
MinHeight="200"
|
Background="Transparent"
|
||||||
HorizontalAlignment="Stretch"
|
SelectionChanged="OnSelectionChanged"
|
||||||
VerticalAlignment="Stretch"
|
SelectedItems="{Binding SelectedDownloadableContents, Mode=TwoWay}"
|
||||||
CanUserReorderColumns="False"
|
Items="{Binding Views}">
|
||||||
CanUserResizeColumns="True"
|
<ListBox.DataTemplates>
|
||||||
CanUserSortColumns="True"
|
<DataTemplate
|
||||||
HorizontalScrollBarVisibility="Auto"
|
DataType="models:DownloadableContentModel">
|
||||||
Items="{Binding _downloadableContents}"
|
<Panel Margin="10">
|
||||||
SelectionMode="Extended"
|
<Grid>
|
||||||
VerticalScrollBarVisibility="Auto">
|
<Grid.ColumnDefinitions>
|
||||||
<DataGrid.Styles>
|
<ColumnDefinition Width="*" />
|
||||||
<Styles>
|
<ColumnDefinition Width="Auto" />
|
||||||
<Style Selector="DataGridCell:nth-child(3), DataGridCell:nth-child(4)">
|
</Grid.ColumnDefinitions>
|
||||||
<Setter Property="HorizontalAlignment" Value="Left" />
|
<Grid
|
||||||
<Setter Property="HorizontalContentAlignment" Value="Left" />
|
Grid.Column="0">
|
||||||
</Style>
|
<Grid.ColumnDefinitions>
|
||||||
</Styles>
|
<ColumnDefinition Width="*"></ColumnDefinition>
|
||||||
<Styles>
|
<ColumnDefinition Width="Auto"></ColumnDefinition>
|
||||||
<Style Selector="DataGridCell:nth-child(1)">
|
</Grid.ColumnDefinitions>
|
||||||
<Setter Property="HorizontalAlignment" Value="Right" />
|
<TextBlock
|
||||||
<Setter Property="HorizontalContentAlignment" Value="Right" />
|
Grid.Column="0"
|
||||||
</Style>
|
HorizontalAlignment="Left"
|
||||||
</Styles>
|
VerticalAlignment="Center"
|
||||||
</DataGrid.Styles>
|
MaxLines="2"
|
||||||
<DataGrid.Columns>
|
TextWrapping="Wrap"
|
||||||
<DataGridTemplateColumn Width="90">
|
TextTrimming="CharacterEllipsis"
|
||||||
<DataGridTemplateColumn.CellTemplate>
|
Text="{Binding FileName}" />
|
||||||
<DataTemplate>
|
<TextBlock
|
||||||
<CheckBox
|
Grid.Column="1"
|
||||||
Width="50"
|
Margin="10 0"
|
||||||
MinWidth="40"
|
HorizontalAlignment="Left"
|
||||||
HorizontalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
IsChecked="{Binding Enabled}" />
|
Text="{Binding TitleId}" />
|
||||||
</DataTemplate>
|
</Grid>
|
||||||
</DataGridTemplateColumn.CellTemplate>
|
<StackPanel
|
||||||
<DataGridTemplateColumn.Header>
|
Grid.Column="1"
|
||||||
<TextBlock Text="{locale:Locale DlcManagerTableHeadingEnabledLabel}" />
|
Spacing="10"
|
||||||
</DataGridTemplateColumn.Header>
|
Orientation="Horizontal"
|
||||||
</DataGridTemplateColumn>
|
HorizontalAlignment="Right">
|
||||||
<DataGridTextColumn Width="140" Binding="{Binding TitleId}">
|
<Button
|
||||||
<DataGridTextColumn.Header>
|
VerticalAlignment="Center"
|
||||||
<TextBlock Text="{locale:Locale DlcManagerTableHeadingTitleIdLabel}" />
|
HorizontalAlignment="Right"
|
||||||
</DataGridTextColumn.Header>
|
Padding="10"
|
||||||
</DataGridTextColumn>
|
MinWidth="0"
|
||||||
<DataGridTextColumn Width="280" Binding="{Binding FullPath}">
|
MinHeight="0"
|
||||||
<DataGridTextColumn.Header>
|
Click="OpenLocation">
|
||||||
<TextBlock Text="{locale:Locale DlcManagerTableHeadingFullPathLabel}" />
|
<ui:SymbolIcon
|
||||||
</DataGridTextColumn.Header>
|
Symbol="OpenFolder"
|
||||||
</DataGridTextColumn>
|
HorizontalAlignment="Center"
|
||||||
<DataGridTextColumn Binding="{Binding ContainerPath}">
|
VerticalAlignment="Center" />
|
||||||
<DataGridTextColumn.Header>
|
</Button>
|
||||||
<TextBlock Text="{locale:Locale DlcManagerTableHeadingContainerPathLabel}" />
|
<Button
|
||||||
</DataGridTextColumn.Header>
|
VerticalAlignment="Center"
|
||||||
</DataGridTextColumn>
|
HorizontalAlignment="Right"
|
||||||
</DataGrid.Columns>
|
Padding="10"
|
||||||
</DataGrid>
|
MinWidth="0"
|
||||||
</ScrollViewer>
|
MinHeight="0"
|
||||||
|
Click="RemoveDLC">
|
||||||
|
<ui:SymbolIcon
|
||||||
|
Symbol="Cancel"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
VerticalAlignment="Center" />
|
||||||
|
</Button>
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
</Panel>
|
||||||
|
</DataTemplate>
|
||||||
|
</ListBox.DataTemplates>
|
||||||
|
<ListBox.Styles>
|
||||||
|
<Style Selector="ListBoxItem">
|
||||||
|
<Setter Property="Background" Value="Transparent" />
|
||||||
|
</Style>
|
||||||
|
</ListBox.Styles>
|
||||||
|
</ListBox>
|
||||||
</Border>
|
</Border>
|
||||||
<DockPanel
|
<Panel
|
||||||
Grid.Row="4"
|
Grid.Row="2"
|
||||||
Margin="0"
|
|
||||||
HorizontalAlignment="Stretch">
|
HorizontalAlignment="Stretch">
|
||||||
<DockPanel Margin="0" HorizontalAlignment="Left">
|
<StackPanel
|
||||||
|
Orientation="Horizontal"
|
||||||
|
Spacing="10"
|
||||||
|
HorizontalAlignment="Left">
|
||||||
<Button
|
<Button
|
||||||
Name="AddButton"
|
Name="AddButton"
|
||||||
MinWidth="90"
|
MinWidth="90"
|
||||||
Margin="5"
|
Margin="5"
|
||||||
Command="{Binding Add}">
|
Command="{ReflectionBinding Add}">
|
||||||
<TextBlock Text="{locale:Locale SettingsTabGeneralAdd}" />
|
<TextBlock Text="{locale:Locale SettingsTabGeneralAdd}" />
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
|
||||||
Name="RemoveButton"
|
|
||||||
MinWidth="90"
|
|
||||||
Margin="5"
|
|
||||||
Command="{Binding RemoveSelected}">
|
|
||||||
<TextBlock Text="{locale:Locale SettingsTabGeneralRemove}" />
|
|
||||||
</Button>
|
|
||||||
<Button
|
<Button
|
||||||
Name="RemoveAllButton"
|
Name="RemoveAllButton"
|
||||||
MinWidth="90"
|
MinWidth="90"
|
||||||
Margin="5"
|
Margin="5"
|
||||||
Command="{Binding RemoveAll}">
|
Command="{ReflectionBinding RemoveAll}">
|
||||||
<TextBlock Text="{locale:Locale DlcManagerRemoveAllButton}" />
|
<TextBlock Text="{locale:Locale DlcManagerRemoveAllButton}" />
|
||||||
</Button>
|
</Button>
|
||||||
</DockPanel>
|
</StackPanel>
|
||||||
<DockPanel Margin="0" HorizontalAlignment="Right">
|
<StackPanel
|
||||||
|
Orientation="Horizontal"
|
||||||
|
Spacing="10"
|
||||||
|
HorizontalAlignment="Right">
|
||||||
<Button
|
<Button
|
||||||
Name="SaveButton"
|
Name="SaveButton"
|
||||||
MinWidth="90"
|
MinWidth="90"
|
||||||
Margin="5"
|
Margin="5"
|
||||||
Command="{Binding SaveAndClose}">
|
Click="SaveAndClose">
|
||||||
<TextBlock Text="{locale:Locale SettingsButtonSave}" />
|
<TextBlock Text="{locale:Locale SettingsButtonSave}" />
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
Name="CancelButton"
|
Name="CancelButton"
|
||||||
MinWidth="90"
|
MinWidth="90"
|
||||||
Margin="5"
|
Margin="5"
|
||||||
Command="{Binding Close}">
|
Click="Close">
|
||||||
<TextBlock Text="{locale:Locale InputDialogCancel}" />
|
<TextBlock Text="{locale:Locale InputDialogCancel}" />
|
||||||
</Button>
|
</Button>
|
||||||
</DockPanel>
|
</StackPanel>
|
||||||
</DockPanel>
|
</Panel>
|
||||||
</Grid>
|
</Grid>
|
||||||
</window:StyleableWindow>
|
</UserControl>
|
@@ -1,314 +1,115 @@
|
|||||||
using Avalonia.Collections;
|
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Threading;
|
using Avalonia.Interactivity;
|
||||||
using LibHac.Common;
|
using Avalonia.Styling;
|
||||||
using LibHac.Fs;
|
using FluentAvalonia.UI.Controls;
|
||||||
using LibHac.Fs.Fsa;
|
|
||||||
using LibHac.FsSystem;
|
|
||||||
using LibHac.Tools.Fs;
|
|
||||||
using LibHac.Tools.FsSystem;
|
|
||||||
using LibHac.Tools.FsSystem.NcaUtils;
|
|
||||||
using Ryujinx.Ava.Common.Locale;
|
using Ryujinx.Ava.Common.Locale;
|
||||||
using Ryujinx.Ava.UI.Controls;
|
|
||||||
using Ryujinx.Ava.UI.Helpers;
|
using Ryujinx.Ava.UI.Helpers;
|
||||||
using Ryujinx.Ava.UI.Models;
|
using Ryujinx.Ava.UI.Models;
|
||||||
using Ryujinx.Common.Configuration;
|
using Ryujinx.Ava.UI.ViewModels;
|
||||||
using Ryujinx.Common.Utilities;
|
|
||||||
using Ryujinx.HLE.FileSystem;
|
using Ryujinx.HLE.FileSystem;
|
||||||
using System;
|
using Ryujinx.Ui.Common.Helper;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Reactive.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Path = System.IO.Path;
|
using Button = Avalonia.Controls.Button;
|
||||||
|
|
||||||
namespace Ryujinx.Ava.UI.Windows
|
namespace Ryujinx.Ava.UI.Windows
|
||||||
{
|
{
|
||||||
public partial class DownloadableContentManagerWindow : StyleableWindow
|
public partial class DownloadableContentManagerWindow : UserControl
|
||||||
{
|
{
|
||||||
private readonly List<DownloadableContentContainer> _downloadableContentContainerList;
|
public DownloadableContentManagerViewModel ViewModel;
|
||||||
private readonly string _downloadableContentJsonPath;
|
|
||||||
|
|
||||||
private VirtualFileSystem _virtualFileSystem { get; }
|
|
||||||
private AvaloniaList<DownloadableContentModel> _downloadableContents { get; set; }
|
|
||||||
|
|
||||||
private ulong _titleId { get; }
|
|
||||||
private string _titleName { get; }
|
|
||||||
|
|
||||||
public DownloadableContentManagerWindow()
|
public DownloadableContentManagerWindow()
|
||||||
{
|
{
|
||||||
DataContext = this;
|
DataContext = this;
|
||||||
|
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
|
||||||
Title = $"Ryujinx {Program.Version} - {LocaleManager.Instance[LocaleKeys.DlcWindowTitle]} - {_titleName} ({_titleId:X16})";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public DownloadableContentManagerWindow(VirtualFileSystem virtualFileSystem, ulong titleId, string titleName)
|
public DownloadableContentManagerWindow(VirtualFileSystem virtualFileSystem, ulong titleId, string titleName)
|
||||||
{
|
{
|
||||||
_virtualFileSystem = virtualFileSystem;
|
DataContext = ViewModel = new DownloadableContentManagerViewModel(virtualFileSystem, titleId, titleName);
|
||||||
_downloadableContents = new AvaloniaList<DownloadableContentModel>();
|
|
||||||
|
|
||||||
_titleId = titleId;
|
|
||||||
_titleName = titleName;
|
|
||||||
|
|
||||||
_downloadableContentJsonPath = Path.Combine(AppDataManager.GamesDirPath, titleId.ToString("x16"), "dlc.json");
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
_downloadableContentContainerList = JsonHelper.DeserializeFromFile<List<DownloadableContentContainer>>(_downloadableContentJsonPath);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
_downloadableContentContainerList = new List<DownloadableContentContainer>();
|
|
||||||
}
|
|
||||||
|
|
||||||
DataContext = this;
|
|
||||||
|
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
|
||||||
RemoveButton.IsEnabled = false;
|
|
||||||
|
|
||||||
DlcDataGrid.SelectionChanged += DlcDataGrid_SelectionChanged;
|
|
||||||
|
|
||||||
Title = $"Ryujinx {Program.Version} - {LocaleManager.Instance[LocaleKeys.DlcWindowTitle]} - {_titleName} ({_titleId:X16})";
|
|
||||||
|
|
||||||
LoadDownloadableContents();
|
|
||||||
PrintHeading();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DlcDataGrid_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
public static async Task Show(VirtualFileSystem virtualFileSystem, ulong titleId, string titleName)
|
||||||
{
|
{
|
||||||
RemoveButton.IsEnabled = (DlcDataGrid.SelectedItems.Count > 0);
|
ContentDialog contentDialog = new()
|
||||||
}
|
|
||||||
|
|
||||||
private void PrintHeading()
|
|
||||||
{
|
|
||||||
Heading.Text = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DlcWindowHeading, _downloadableContents.Count, _titleName, _titleId.ToString("X16"));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void LoadDownloadableContents()
|
|
||||||
{
|
|
||||||
foreach (DownloadableContentContainer downloadableContentContainer in _downloadableContentContainerList)
|
|
||||||
{
|
{
|
||||||
if (File.Exists(downloadableContentContainer.ContainerPath))
|
PrimaryButtonText = "",
|
||||||
{
|
SecondaryButtonText = "",
|
||||||
using FileStream containerFile = File.OpenRead(downloadableContentContainer.ContainerPath);
|
CloseButtonText = "",
|
||||||
|
Content = new DownloadableContentManagerWindow(virtualFileSystem, titleId, titleName),
|
||||||
PartitionFileSystem partitionFileSystem = new(containerFile.AsStorage());
|
Title = string.Format(LocaleManager.Instance[LocaleKeys.DlcWindowTitle], titleName, titleId.ToString("X16"))
|
||||||
|
|
||||||
_virtualFileSystem.ImportTickets(partitionFileSystem);
|
|
||||||
|
|
||||||
foreach (DownloadableContentNca downloadableContentNca in downloadableContentContainer.DownloadableContentNcaList)
|
|
||||||
{
|
|
||||||
using UniqueRef<IFile> ncaFile = new();
|
|
||||||
|
|
||||||
partitionFileSystem.OpenFile(ref ncaFile.Ref, downloadableContentNca.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
|
||||||
|
|
||||||
Nca nca = TryOpenNca(ncaFile.Get.AsStorage(), downloadableContentContainer.ContainerPath);
|
|
||||||
if (nca != null)
|
|
||||||
{
|
|
||||||
_downloadableContents.Add(new DownloadableContentModel(nca.Header.TitleId.ToString("X16"),
|
|
||||||
downloadableContentContainer.ContainerPath,
|
|
||||||
downloadableContentNca.FullPath,
|
|
||||||
downloadableContentNca.Enabled));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NOTE: Save the list again to remove leftovers.
|
|
||||||
Save();
|
|
||||||
}
|
|
||||||
|
|
||||||
private Nca TryOpenNca(IStorage ncaStorage, string containerPath)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
return new Nca(_virtualFileSystem.KeySet, ncaStorage);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Dispatcher.UIThread.InvokeAsync(async () =>
|
|
||||||
{
|
|
||||||
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.DialogLoadNcaErrorMessage, ex.Message, containerPath));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task AddDownloadableContent(string path)
|
|
||||||
{
|
|
||||||
if (!File.Exists(path) || _downloadableContents.FirstOrDefault(x => x.ContainerPath == path) != null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
using FileStream containerFile = File.OpenRead(path);
|
|
||||||
|
|
||||||
PartitionFileSystem partitionFileSystem = new(containerFile.AsStorage());
|
|
||||||
bool containsDownloadableContent = false;
|
|
||||||
|
|
||||||
_virtualFileSystem.ImportTickets(partitionFileSystem);
|
|
||||||
|
|
||||||
foreach (DirectoryEntryEx fileEntry in partitionFileSystem.EnumerateEntries("/", "*.nca"))
|
|
||||||
{
|
|
||||||
using var ncaFile = new UniqueRef<IFile>();
|
|
||||||
|
|
||||||
partitionFileSystem.OpenFile(ref ncaFile.Ref, fileEntry.FullPath.ToU8Span(), OpenMode.Read).ThrowIfFailure();
|
|
||||||
|
|
||||||
Nca nca = TryOpenNca(ncaFile.Get.AsStorage(), path);
|
|
||||||
if (nca == null)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nca.Header.ContentType == NcaContentType.PublicData)
|
|
||||||
{
|
|
||||||
if ((nca.Header.TitleId & 0xFFFFFFFFFFFFE000) != _titleId)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
_downloadableContents.Add(new DownloadableContentModel(nca.Header.TitleId.ToString("X16"), path, fileEntry.FullPath, true));
|
|
||||||
|
|
||||||
containsDownloadableContent = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!containsDownloadableContent)
|
|
||||||
{
|
|
||||||
await ContentDialogHelper.CreateErrorDialog(LocaleManager.Instance[LocaleKeys.DialogDlcNoDlcErrorMessage]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void RemoveDownloadableContents(bool removeSelectedOnly = false)
|
|
||||||
{
|
|
||||||
if (removeSelectedOnly)
|
|
||||||
{
|
|
||||||
AvaloniaList<DownloadableContentModel> removedItems = new();
|
|
||||||
|
|
||||||
foreach (var item in DlcDataGrid.SelectedItems)
|
|
||||||
{
|
|
||||||
removedItems.Add(item as DownloadableContentModel);
|
|
||||||
}
|
|
||||||
|
|
||||||
DlcDataGrid.SelectedItems.Clear();
|
|
||||||
|
|
||||||
foreach (var item in removedItems)
|
|
||||||
{
|
|
||||||
_downloadableContents.RemoveAll(_downloadableContents.Where(x => x.TitleId == item.TitleId).ToList());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_downloadableContents.Clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
PrintHeading();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void RemoveSelected()
|
|
||||||
{
|
|
||||||
RemoveDownloadableContents(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void RemoveAll()
|
|
||||||
{
|
|
||||||
RemoveDownloadableContents();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void EnableAll()
|
|
||||||
{
|
|
||||||
foreach(var item in _downloadableContents)
|
|
||||||
{
|
|
||||||
item.Enabled = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void DisableAll()
|
|
||||||
{
|
|
||||||
foreach (var item in _downloadableContents)
|
|
||||||
{
|
|
||||||
item.Enabled = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async void Add()
|
|
||||||
{
|
|
||||||
OpenFileDialog dialog = new OpenFileDialog()
|
|
||||||
{
|
|
||||||
Title = LocaleManager.Instance[LocaleKeys.SelectDlcDialogTitle],
|
|
||||||
AllowMultiple = true
|
|
||||||
};
|
};
|
||||||
|
|
||||||
dialog.Filters.Add(new FileDialogFilter
|
Style bottomBorder = new(x => x.OfType<Grid>().Name("DialogSpace").Child().OfType<Border>());
|
||||||
{
|
bottomBorder.Setters.Add(new Setter(IsVisibleProperty, false));
|
||||||
Name = "NSP",
|
|
||||||
Extensions = { "nsp" }
|
|
||||||
});
|
|
||||||
|
|
||||||
string[] files = await dialog.ShowAsync(this);
|
contentDialog.Styles.Add(bottomBorder);
|
||||||
|
|
||||||
if (files != null)
|
await ContentDialogHelper.ShowAsync(contentDialog);
|
||||||
{
|
|
||||||
foreach (string file in files)
|
|
||||||
{
|
|
||||||
await AddDownloadableContent(file);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
PrintHeading();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Save()
|
private void SaveAndClose(object sender, RoutedEventArgs routedEventArgs)
|
||||||
{
|
{
|
||||||
_downloadableContentContainerList.Clear();
|
ViewModel.Save();
|
||||||
|
((ContentDialog)Parent).Hide();
|
||||||
|
}
|
||||||
|
|
||||||
DownloadableContentContainer container = default;
|
private void Close(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
((ContentDialog)Parent).Hide();
|
||||||
|
}
|
||||||
|
|
||||||
foreach (DownloadableContentModel downloadableContent in _downloadableContents)
|
private void RemoveDLC(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
if (sender is Button button)
|
||||||
{
|
{
|
||||||
if (container.ContainerPath != downloadableContent.ContainerPath)
|
if (button.DataContext is DownloadableContentModel model)
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrWhiteSpace(container.ContainerPath))
|
ViewModel.Remove(model);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OpenLocation(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
if (sender is Button button)
|
||||||
|
{
|
||||||
|
if (button.DataContext is DownloadableContentModel model)
|
||||||
|
{
|
||||||
|
OpenHelper.LocateFile(model.ContainerPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnSelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||||
|
{
|
||||||
|
foreach (var content in e.AddedItems)
|
||||||
|
{
|
||||||
|
if (content is DownloadableContentModel model)
|
||||||
|
{
|
||||||
|
var index = ViewModel.DownloadableContents.IndexOf(model);
|
||||||
|
|
||||||
|
if (index != -1)
|
||||||
{
|
{
|
||||||
_downloadableContentContainerList.Add(container);
|
ViewModel.DownloadableContents[index].Enabled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
container = new DownloadableContentContainer
|
|
||||||
{
|
|
||||||
ContainerPath = downloadableContent.ContainerPath,
|
|
||||||
DownloadableContentNcaList = new List<DownloadableContentNca>()
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
container.DownloadableContentNcaList.Add(new DownloadableContentNca
|
foreach (var content in e.RemovedItems)
|
||||||
|
{
|
||||||
|
if (content is DownloadableContentModel model)
|
||||||
{
|
{
|
||||||
Enabled = downloadableContent.Enabled,
|
var index = ViewModel.DownloadableContents.IndexOf(model);
|
||||||
TitleId = Convert.ToUInt64(downloadableContent.TitleId, 16),
|
|
||||||
FullPath = downloadableContent.FullPath
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(container.ContainerPath))
|
if (index != -1)
|
||||||
{
|
{
|
||||||
_downloadableContentContainerList.Add(container);
|
ViewModel.DownloadableContents[index].Enabled = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
using (FileStream downloadableContentJsonStream = File.Create(_downloadableContentJsonPath, 4096, FileOptions.WriteThrough))
|
|
||||||
{
|
|
||||||
downloadableContentJsonStream.Write(Encoding.UTF8.GetBytes(JsonHelper.Serialize(_downloadableContentContainerList, true)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SaveAndClose()
|
|
||||||
{
|
|
||||||
Save();
|
|
||||||
Close();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -125,7 +125,7 @@ namespace Ryujinx.Ava.UI.Windows
|
|||||||
|
|
||||||
public static Bgra32[] GetBuffer(Image<Bgra32> image)
|
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)
|
private static int GetColorScore(Dictionary<int, int> dominantColorBin, int maxHitCount, PaletteColor color)
|
||||||
|
@@ -12,19 +12,5 @@ namespace Ryujinx.Common
|
|||||||
{
|
{
|
||||||
return MemoryMarshal.Cast<byte, T>(reader.ReadBytes(Unsafe.SizeOf<T>()))[0];
|
return MemoryMarshal.Cast<byte, T>(reader.ReadBytes(Unsafe.SizeOf<T>()))[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
public unsafe static void WriteStruct<T>(this BinaryWriter writer, T value)
|
|
||||||
where T : unmanaged
|
|
||||||
{
|
|
||||||
ReadOnlySpan<byte> data = MemoryMarshal.Cast<T, byte>(MemoryMarshal.CreateReadOnlySpan(ref value, 1));
|
|
||||||
|
|
||||||
writer.Write(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void Write(this BinaryWriter writer, UInt128 value)
|
|
||||||
{
|
|
||||||
writer.Write((ulong)value);
|
|
||||||
writer.Write((ulong)(value >> 64));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
28
Ryujinx.Common/Extensions/BinaryWriterExtensions.cs
Normal file
28
Ryujinx.Common/Extensions/BinaryWriterExtensions.cs
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace Ryujinx.Common
|
||||||
|
{
|
||||||
|
public static class BinaryWriterExtensions
|
||||||
|
{
|
||||||
|
public unsafe static void WriteStruct<T>(this BinaryWriter writer, T value)
|
||||||
|
where T : unmanaged
|
||||||
|
{
|
||||||
|
ReadOnlySpan<byte> data = MemoryMarshal.Cast<T, byte>(MemoryMarshal.CreateReadOnlySpan(ref value, 1));
|
||||||
|
|
||||||
|
writer.Write(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Write(this BinaryWriter writer, UInt128 value)
|
||||||
|
{
|
||||||
|
writer.Write((ulong)value);
|
||||||
|
writer.Write((ulong)(value >> 64));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Write(this BinaryWriter writer, MemoryStream stream)
|
||||||
|
{
|
||||||
|
stream.CopyTo(writer.BaseStream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
138
Ryujinx.Common/Extensions/StreamExtensions.cs
Normal file
138
Ryujinx.Common/Extensions/StreamExtensions.cs
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
using System;
|
||||||
|
using System.Buffers.Binary;
|
||||||
|
using System.IO;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace Ryujinx.Common
|
||||||
|
{
|
||||||
|
public static class StreamExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Writes a <cref="ReadOnlySpan<int>" /> to this stream.
|
||||||
|
///
|
||||||
|
/// This default implementation converts each buffer value to a stack-allocated
|
||||||
|
/// byte array, then writes it to the Stream using <cref="System.Stream.Write(byte[])" />.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="stream">The stream to be written to</param>
|
||||||
|
/// <param name="buffer">The buffer of values to be written</param>
|
||||||
|
public static void Write(this Stream stream, ReadOnlySpan<int> buffer)
|
||||||
|
{
|
||||||
|
if (buffer.Length == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (BitConverter.IsLittleEndian)
|
||||||
|
{
|
||||||
|
ReadOnlySpan<byte> byteBuffer = MemoryMarshal.Cast<int, byte>(buffer);
|
||||||
|
stream.Write(byteBuffer);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Span<byte> byteBuffer = stackalloc byte[sizeof(int)];
|
||||||
|
|
||||||
|
foreach (int value in buffer)
|
||||||
|
{
|
||||||
|
BinaryPrimitives.WriteInt32LittleEndian(byteBuffer, value);
|
||||||
|
stream.Write(byteBuffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes a four-byte signed integer to this stream. The current position
|
||||||
|
/// of the stream is advanced by four.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="stream">The stream to be written to</param>
|
||||||
|
/// <param name="value">The value to be written</param>
|
||||||
|
public static void Write(this Stream stream, int value)
|
||||||
|
{
|
||||||
|
Span<byte> buffer = stackalloc byte[sizeof(int)];
|
||||||
|
BinaryPrimitives.WriteInt32LittleEndian(buffer, value);
|
||||||
|
stream.Write(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes an eight-byte signed integer to this stream. The current position
|
||||||
|
/// of the stream is advanced by eight.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="stream">The stream to be written to</param>
|
||||||
|
/// <param name="value">The value to be written</param>
|
||||||
|
public static void Write(this Stream stream, long value)
|
||||||
|
{
|
||||||
|
Span<byte> buffer = stackalloc byte[sizeof(long)];
|
||||||
|
BinaryPrimitives.WriteInt64LittleEndian(buffer, value);
|
||||||
|
stream.Write(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
// Writes a four-byte unsigned integer to this stream. The current position
|
||||||
|
// of the stream is advanced by four.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="stream">The stream to be written to</param>
|
||||||
|
/// <param name="value">The value to be written</param>
|
||||||
|
public static void Write(this Stream stream, uint value)
|
||||||
|
{
|
||||||
|
Span<byte> buffer = stackalloc byte[sizeof(uint)];
|
||||||
|
BinaryPrimitives.WriteUInt32LittleEndian(buffer, value);
|
||||||
|
stream.Write(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes an eight-byte unsigned integer to this stream. The current
|
||||||
|
/// position of the stream is advanced by eight.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="stream">The stream to be written to</param>
|
||||||
|
/// <param name="value">The value to be written</param>
|
||||||
|
public static void Write(this Stream stream, ulong value)
|
||||||
|
{
|
||||||
|
Span<byte> buffer = stackalloc byte[sizeof(ulong)];
|
||||||
|
BinaryPrimitives.WriteUInt64LittleEndian(buffer, value);
|
||||||
|
stream.Write(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes the contents of source to stream by calling source.CopyTo(stream).
|
||||||
|
/// Provides consistency with other Stream.Write methods.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="stream">The stream to be written to</param>
|
||||||
|
/// <param name="source">The stream to be read from</param>
|
||||||
|
public static void Write(this Stream stream, Stream source)
|
||||||
|
{
|
||||||
|
source.CopyTo(stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes a sequence of bytes to the Stream.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="stream">The stream to be written to.</param>
|
||||||
|
/// <param name="value">The byte to be written</param>
|
||||||
|
/// <param name="count">The number of times the value should be written</param>
|
||||||
|
public static void WriteByte(this Stream stream, byte value, int count)
|
||||||
|
{
|
||||||
|
if (count <= 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const int BlockSize = 16;
|
||||||
|
|
||||||
|
int blockCount = count / BlockSize;
|
||||||
|
if (blockCount > 0)
|
||||||
|
{
|
||||||
|
Span<byte> span = stackalloc byte[BlockSize];
|
||||||
|
span.Fill(value);
|
||||||
|
for (int x = 0; x < blockCount; x++)
|
||||||
|
{
|
||||||
|
stream.Write(span);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int nonBlockBytes = count % BlockSize;
|
||||||
|
for (int x = 0; x < nonBlockBytes; x++)
|
||||||
|
{
|
||||||
|
stream.WriteByte(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
99
Ryujinx.Common/Memory/MemoryStreamManager.cs
Normal file
99
Ryujinx.Common/Memory/MemoryStreamManager.cs
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
using Microsoft.IO;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Ryujinx.Common.Memory
|
||||||
|
{
|
||||||
|
public static class MemoryStreamManager
|
||||||
|
{
|
||||||
|
private static readonly RecyclableMemoryStreamManager _shared = new RecyclableMemoryStreamManager();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// We don't expose the <c>RecyclableMemoryStreamManager</c> directly because version 2.x
|
||||||
|
/// returns them as <c>MemoryStream</c>. This Shared class is here to a) offer only the GetStream() versions we use
|
||||||
|
/// and b) return them as <c>RecyclableMemoryStream</c> so we don't have to cast.
|
||||||
|
/// </summary>
|
||||||
|
public static class Shared
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieve a new <c>MemoryStream</c> object with no tag and a default initial capacity.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>A <c>RecyclableMemoryStream</c></returns>
|
||||||
|
public static RecyclableMemoryStream GetStream()
|
||||||
|
=> new RecyclableMemoryStream(_shared);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieve a new <c>MemoryStream</c> object with the contents copied from the provided
|
||||||
|
/// buffer. The provided buffer is not wrapped or used after construction.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>The new stream's position is set to the beginning of the stream when returned.</remarks>
|
||||||
|
/// <param name="buffer">The byte buffer to copy data from</param>
|
||||||
|
/// <returns>A <c>RecyclableMemoryStream</c></returns>
|
||||||
|
public static RecyclableMemoryStream GetStream(byte[] buffer)
|
||||||
|
=> GetStream(Guid.NewGuid(), null, buffer, 0, buffer.Length);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieve a new <c>MemoryStream</c> object with the given tag and with contents copied from the provided
|
||||||
|
/// buffer. The provided buffer is not wrapped or used after construction.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>The new stream's position is set to the beginning of the stream when returned.</remarks>
|
||||||
|
/// <param name="buffer">The byte buffer to copy data from</param>
|
||||||
|
/// <returns>A <c>RecyclableMemoryStream</c></returns>
|
||||||
|
public static RecyclableMemoryStream GetStream(ReadOnlySpan<byte> buffer)
|
||||||
|
=> GetStream(Guid.NewGuid(), null, buffer);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieve a new <c>RecyclableMemoryStream</c> object with the given tag and with contents copied from the provided
|
||||||
|
/// buffer. The provided buffer is not wrapped or used after construction.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>The new stream's position is set to the beginning of the stream when returned.</remarks>
|
||||||
|
/// <param name="id">A unique identifier which can be used to trace usages of the stream</param>
|
||||||
|
/// <param name="tag">A tag which can be used to track the source of the stream</param>
|
||||||
|
/// <param name="buffer">The byte buffer to copy data from</param>
|
||||||
|
/// <returns>A <c>RecyclableMemoryStream</c></returns>
|
||||||
|
public static RecyclableMemoryStream GetStream(Guid id, string tag, ReadOnlySpan<byte> buffer)
|
||||||
|
{
|
||||||
|
RecyclableMemoryStream stream = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
stream = new RecyclableMemoryStream(_shared, id, tag, buffer.Length);
|
||||||
|
stream.Write(buffer);
|
||||||
|
stream.Position = 0;
|
||||||
|
return stream;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
stream?.Dispose();
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieve a new <c>RecyclableMemoryStream</c> object with the given tag and with contents copied from the provided
|
||||||
|
/// buffer. The provided buffer is not wrapped or used after construction.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>The new stream's position is set to the beginning of the stream when returned</remarks>
|
||||||
|
/// <param name="id">A unique identifier which can be used to trace usages of the stream</param>
|
||||||
|
/// <param name="tag">A tag which can be used to track the source of the stream</param>
|
||||||
|
/// <param name="buffer">The byte buffer to copy data from</param>
|
||||||
|
/// <param name="offset">The offset from the start of the buffer to copy from</param>
|
||||||
|
/// <param name="count">The number of bytes to copy from the buffer</param>
|
||||||
|
/// <returns>A <c>RecyclableMemoryStream</c></returns>
|
||||||
|
public static RecyclableMemoryStream GetStream(Guid id, string tag, byte[] buffer, int offset, int count)
|
||||||
|
{
|
||||||
|
RecyclableMemoryStream stream = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
stream = new RecyclableMemoryStream(_shared, id, tag, count);
|
||||||
|
stream.Write(buffer, offset, count);
|
||||||
|
stream.Position = 0;
|
||||||
|
return stream;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
stream?.Dispose();
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -7,6 +7,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.IO.RecyclableMemoryStream" />
|
||||||
<PackageReference Include="MsgPack.Cli" />
|
<PackageReference Include="MsgPack.Cli" />
|
||||||
<PackageReference Include="System.Management" />
|
<PackageReference Include="System.Management" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
@@ -1,3 +1,5 @@
|
|||||||
|
using Ryujinx.Common.Memory;
|
||||||
|
using Ryujinx.Common.Utilities;
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
@@ -38,12 +40,7 @@ namespace Ryujinx.Common
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
using (var mem = new MemoryStream())
|
return StreamUtils.StreamToBytes(stream);
|
||||||
{
|
|
||||||
stream.CopyTo(mem);
|
|
||||||
|
|
||||||
return mem.ToArray();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,12 +53,7 @@ namespace Ryujinx.Common
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
using (var mem = new MemoryStream())
|
return await StreamUtils.StreamToBytesAsync(stream);
|
||||||
{
|
|
||||||
await stream.CopyToAsync(mem);
|
|
||||||
|
|
||||||
return mem.ToArray();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,4 +1,8 @@
|
|||||||
using System.IO;
|
using Microsoft.IO;
|
||||||
|
using Ryujinx.Common.Memory;
|
||||||
|
using System.IO;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Ryujinx.Common.Utilities
|
namespace Ryujinx.Common.Utilities
|
||||||
{
|
{
|
||||||
@@ -6,12 +10,22 @@ namespace Ryujinx.Common.Utilities
|
|||||||
{
|
{
|
||||||
public static byte[] StreamToBytes(Stream input)
|
public static byte[] StreamToBytes(Stream input)
|
||||||
{
|
{
|
||||||
using (MemoryStream stream = new MemoryStream())
|
using (MemoryStream stream = MemoryStreamManager.Shared.GetStream())
|
||||||
{
|
{
|
||||||
input.CopyTo(stream);
|
input.CopyTo(stream);
|
||||||
|
|
||||||
return stream.ToArray();
|
return stream.ToArray();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static async Task<byte[]> StreamToBytesAsync(Stream input, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
using (MemoryStream stream = MemoryStreamManager.Shared.GetStream())
|
||||||
|
{
|
||||||
|
await input.CopyToAsync(stream, cancellationToken);
|
||||||
|
|
||||||
|
return stream.ToArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -1,4 +1,6 @@
|
|||||||
using Ryujinx.Memory;
|
using Microsoft.IO;
|
||||||
|
using Ryujinx.Common.Memory;
|
||||||
|
using Ryujinx.Memory;
|
||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
@@ -40,7 +42,7 @@ namespace Ryujinx.Cpu
|
|||||||
|
|
||||||
public static string ReadAsciiString(IVirtualMemoryManager memory, ulong position, long maxSize = -1)
|
public static string ReadAsciiString(IVirtualMemoryManager memory, ulong position, long maxSize = -1)
|
||||||
{
|
{
|
||||||
using (MemoryStream ms = new MemoryStream())
|
using (RecyclableMemoryStream ms = MemoryStreamManager.Shared.GetStream())
|
||||||
{
|
{
|
||||||
for (long offs = 0; offs < maxSize || maxSize == -1; offs++)
|
for (long offs = 0; offs < maxSize || maxSize == -1; offs++)
|
||||||
{
|
{
|
||||||
@@ -54,7 +56,7 @@ namespace Ryujinx.Cpu
|
|||||||
ms.WriteByte(value);
|
ms.WriteByte(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Encoding.ASCII.GetString(ms.ToArray());
|
return Encoding.ASCII.GetString(ms.GetReadOnlySequence());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -28,5 +28,10 @@ namespace Ryujinx.Cpu.Tracking
|
|||||||
public void Reprotect(bool asDirty = false) => _impl.Reprotect(asDirty);
|
public void Reprotect(bool asDirty = false) => _impl.Reprotect(asDirty);
|
||||||
|
|
||||||
public bool OverlapsWith(ulong address, ulong size) => _impl.OverlapsWith(address, size);
|
public bool OverlapsWith(ulong address, ulong size) => _impl.OverlapsWith(address, size);
|
||||||
|
|
||||||
|
public bool RangeEquals(CpuRegionHandle other)
|
||||||
|
{
|
||||||
|
return _impl.RealAddress == other._impl.RealAddress && _impl.RealSize == other._impl.RealSize;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -15,7 +15,12 @@ namespace Ryujinx.Graphics.GAL
|
|||||||
|
|
||||||
void BackgroundContextAction(Action action, bool alwaysBackground = false);
|
void BackgroundContextAction(Action action, bool alwaysBackground = false);
|
||||||
|
|
||||||
BufferHandle CreateBuffer(int size);
|
BufferHandle CreateBuffer(int size, BufferHandle storageHint);
|
||||||
|
|
||||||
|
BufferHandle CreateBuffer(int size)
|
||||||
|
{
|
||||||
|
return CreateBuffer(size, BufferHandle.Null);
|
||||||
|
}
|
||||||
|
|
||||||
IProgram CreateProgram(ShaderSource[] shaders, ShaderInfo info);
|
IProgram CreateProgram(ShaderSource[] shaders, ShaderInfo info);
|
||||||
|
|
||||||
@@ -26,7 +31,7 @@ namespace Ryujinx.Graphics.GAL
|
|||||||
|
|
||||||
void DeleteBuffer(BufferHandle buffer);
|
void DeleteBuffer(BufferHandle buffer);
|
||||||
|
|
||||||
ReadOnlySpan<byte> GetBufferData(BufferHandle buffer, int offset, int size);
|
PinnedSpan<byte> GetBufferData(BufferHandle buffer, int offset, int size);
|
||||||
|
|
||||||
Capabilities GetCapabilities();
|
Capabilities GetCapabilities();
|
||||||
ulong GetCurrentSync();
|
ulong GetCurrentSync();
|
||||||
|
@@ -15,8 +15,8 @@ namespace Ryujinx.Graphics.GAL
|
|||||||
|
|
||||||
ITexture CreateView(TextureCreateInfo info, int firstLayer, int firstLevel);
|
ITexture CreateView(TextureCreateInfo info, int firstLayer, int firstLevel);
|
||||||
|
|
||||||
ReadOnlySpan<byte> GetData();
|
PinnedSpan<byte> GetData();
|
||||||
ReadOnlySpan<byte> GetData(int layer, int level);
|
PinnedSpan<byte> GetData(int layer, int level);
|
||||||
|
|
||||||
void SetData(SpanOrArray<byte> data);
|
void SetData(SpanOrArray<byte> data);
|
||||||
void SetData(SpanOrArray<byte> data, int layer, int level);
|
void SetData(SpanOrArray<byte> data, int layer, int level);
|
||||||
|
@@ -21,9 +21,9 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Buffer
|
|||||||
|
|
||||||
public static void Run(ref BufferGetDataCommand command, ThreadedRenderer threaded, IRenderer renderer)
|
public static void Run(ref BufferGetDataCommand command, ThreadedRenderer threaded, IRenderer renderer)
|
||||||
{
|
{
|
||||||
ReadOnlySpan<byte> result = renderer.GetBufferData(threaded.Buffers.MapBuffer(command._buffer), command._offset, command._size);
|
PinnedSpan<byte> result = renderer.GetBufferData(threaded.Buffers.MapBuffer(command._buffer), command._offset, command._size);
|
||||||
|
|
||||||
command._result.Get(threaded).Result = new PinnedSpan<byte>(result);
|
command._result.Get(threaded).Result = result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -5,16 +5,25 @@
|
|||||||
public CommandType CommandType => CommandType.CreateBuffer;
|
public CommandType CommandType => CommandType.CreateBuffer;
|
||||||
private BufferHandle _threadedHandle;
|
private BufferHandle _threadedHandle;
|
||||||
private int _size;
|
private int _size;
|
||||||
|
private BufferHandle _storageHint;
|
||||||
|
|
||||||
public void Set(BufferHandle threadedHandle, int size)
|
public void Set(BufferHandle threadedHandle, int size, BufferHandle storageHint)
|
||||||
{
|
{
|
||||||
_threadedHandle = threadedHandle;
|
_threadedHandle = threadedHandle;
|
||||||
_size = size;
|
_size = size;
|
||||||
|
_storageHint = storageHint;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void Run(ref CreateBufferCommand command, ThreadedRenderer threaded, IRenderer renderer)
|
public static void Run(ref CreateBufferCommand command, ThreadedRenderer threaded, IRenderer renderer)
|
||||||
{
|
{
|
||||||
threaded.Buffers.AssignBuffer(command._threadedHandle, renderer.CreateBuffer(command._size));
|
BufferHandle hint = BufferHandle.Null;
|
||||||
|
|
||||||
|
if (command._storageHint != BufferHandle.Null)
|
||||||
|
{
|
||||||
|
hint = threaded.Buffers.MapBuffer(command._storageHint);
|
||||||
|
}
|
||||||
|
|
||||||
|
threaded.Buffers.AssignBuffer(command._threadedHandle, renderer.CreateBuffer(command._size, hint));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -18,9 +18,9 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Texture
|
|||||||
|
|
||||||
public static void Run(ref TextureGetDataCommand command, ThreadedRenderer threaded, IRenderer renderer)
|
public static void Run(ref TextureGetDataCommand command, ThreadedRenderer threaded, IRenderer renderer)
|
||||||
{
|
{
|
||||||
ReadOnlySpan<byte> result = command._texture.Get(threaded).Base.GetData();
|
PinnedSpan<byte> result = command._texture.Get(threaded).Base.GetData();
|
||||||
|
|
||||||
command._result.Get(threaded).Result = new PinnedSpan<byte>(result);
|
command._result.Get(threaded).Result = result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -22,9 +22,9 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Texture
|
|||||||
|
|
||||||
public static void Run(ref TextureGetDataSliceCommand command, ThreadedRenderer threaded, IRenderer renderer)
|
public static void Run(ref TextureGetDataSliceCommand command, ThreadedRenderer threaded, IRenderer renderer)
|
||||||
{
|
{
|
||||||
ReadOnlySpan<byte> result = command._texture.Get(threaded).Base.GetData(command._layer, command._level);
|
PinnedSpan<byte> result = command._texture.Get(threaded).Base.GetData(command._layer, command._level);
|
||||||
|
|
||||||
command._result.Get(threaded).Result = new PinnedSpan<byte>(result);
|
command._result.Get(threaded).Result = result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,23 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
|
|
||||||
namespace Ryujinx.Graphics.GAL.Multithreading.Model
|
|
||||||
{
|
|
||||||
unsafe struct PinnedSpan<T> where T : unmanaged
|
|
||||||
{
|
|
||||||
private void* _ptr;
|
|
||||||
private int _size;
|
|
||||||
|
|
||||||
public PinnedSpan(ReadOnlySpan<T> span)
|
|
||||||
{
|
|
||||||
_ptr = Unsafe.AsPointer(ref MemoryMarshal.GetReference(span));
|
|
||||||
_size = span.Length;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ReadOnlySpan<T> Get()
|
|
||||||
{
|
|
||||||
return new ReadOnlySpan<T>(_ptr, _size * Unsafe.SizeOf<T>());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -72,7 +72,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Resources
|
|||||||
return newTex;
|
return newTex;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ReadOnlySpan<byte> GetData()
|
public PinnedSpan<byte> GetData()
|
||||||
{
|
{
|
||||||
if (_renderer.IsGpuThread())
|
if (_renderer.IsGpuThread())
|
||||||
{
|
{
|
||||||
@@ -80,7 +80,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Resources
|
|||||||
_renderer.New<TextureGetDataCommand>().Set(Ref(this), Ref(box));
|
_renderer.New<TextureGetDataCommand>().Set(Ref(this), Ref(box));
|
||||||
_renderer.InvokeCommand();
|
_renderer.InvokeCommand();
|
||||||
|
|
||||||
return box.Result.Get();
|
return box.Result;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -90,7 +90,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Resources
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public ReadOnlySpan<byte> GetData(int layer, int level)
|
public PinnedSpan<byte> GetData(int layer, int level)
|
||||||
{
|
{
|
||||||
if (_renderer.IsGpuThread())
|
if (_renderer.IsGpuThread())
|
||||||
{
|
{
|
||||||
@@ -98,7 +98,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Resources
|
|||||||
_renderer.New<TextureGetDataSliceCommand>().Set(Ref(this), Ref(box), layer, level);
|
_renderer.New<TextureGetDataSliceCommand>().Set(Ref(this), Ref(box), layer, level);
|
||||||
_renderer.InvokeCommand();
|
_renderer.InvokeCommand();
|
||||||
|
|
||||||
return box.Result.Get();
|
return box.Result;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@@ -265,10 +265,10 @@ namespace Ryujinx.Graphics.GAL.Multithreading
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public BufferHandle CreateBuffer(int size)
|
public BufferHandle CreateBuffer(int size, BufferHandle storageHint)
|
||||||
{
|
{
|
||||||
BufferHandle handle = Buffers.CreateBufferHandle();
|
BufferHandle handle = Buffers.CreateBufferHandle();
|
||||||
New<CreateBufferCommand>().Set(handle, size);
|
New<CreateBufferCommand>().Set(handle, size, storageHint);
|
||||||
QueueCommand();
|
QueueCommand();
|
||||||
|
|
||||||
return handle;
|
return handle;
|
||||||
@@ -329,7 +329,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading
|
|||||||
QueueCommand();
|
QueueCommand();
|
||||||
}
|
}
|
||||||
|
|
||||||
public ReadOnlySpan<byte> GetBufferData(BufferHandle buffer, int offset, int size)
|
public PinnedSpan<byte> GetBufferData(BufferHandle buffer, int offset, int size)
|
||||||
{
|
{
|
||||||
if (IsGpuThread())
|
if (IsGpuThread())
|
||||||
{
|
{
|
||||||
@@ -337,7 +337,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading
|
|||||||
New<BufferGetDataCommand>().Set(buffer, offset, size, Ref(box));
|
New<BufferGetDataCommand>().Set(buffer, offset, size, Ref(box));
|
||||||
InvokeCommand();
|
InvokeCommand();
|
||||||
|
|
||||||
return box.Result.Get();
|
return box.Result;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
53
Ryujinx.Graphics.GAL/PinnedSpan.cs
Normal file
53
Ryujinx.Graphics.GAL/PinnedSpan.cs
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
using System;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace Ryujinx.Graphics.GAL
|
||||||
|
{
|
||||||
|
public unsafe struct PinnedSpan<T> : IDisposable where T : unmanaged
|
||||||
|
{
|
||||||
|
private void* _ptr;
|
||||||
|
private int _size;
|
||||||
|
private Action _disposeAction;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new PinnedSpan from an existing ReadOnlySpan. The span *must* be pinned in memory.
|
||||||
|
/// The data must be guaranteed to live until disposeAction is called.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="span">Existing span</param>
|
||||||
|
/// <param name="disposeAction">Action to call on dispose</param>
|
||||||
|
/// <remarks>
|
||||||
|
/// If a dispose action is not provided, it is safe to assume the resource will be available until the next call.
|
||||||
|
/// </remarks>
|
||||||
|
public static PinnedSpan<T> UnsafeFromSpan(ReadOnlySpan<T> span, Action disposeAction = null)
|
||||||
|
{
|
||||||
|
return new PinnedSpan<T>(Unsafe.AsPointer(ref MemoryMarshal.GetReference(span)), span.Length, disposeAction);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new PinnedSpan from an existing unsafe region. The data must be guaranteed to live until disposeAction is called.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="ptr">Pointer to the region</param>
|
||||||
|
/// <param name="size">The total items of T the region contains</param>
|
||||||
|
/// <param name="disposeAction">Action to call on dispose</param>
|
||||||
|
/// <remarks>
|
||||||
|
/// If a dispose action is not provided, it is safe to assume the resource will be available until the next call.
|
||||||
|
/// </remarks>
|
||||||
|
public PinnedSpan(void* ptr, int size, Action disposeAction = null)
|
||||||
|
{
|
||||||
|
_ptr = ptr;
|
||||||
|
_size = size;
|
||||||
|
_disposeAction = disposeAction;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReadOnlySpan<T> Get()
|
||||||
|
{
|
||||||
|
return new ReadOnlySpan<T>(_ptr, _size * Unsafe.SizeOf<T>());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_disposeAction?.Invoke();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -20,5 +20,15 @@ namespace Ryujinx.Graphics.GAL
|
|||||||
{
|
{
|
||||||
return target == Target.Texture2DMultisample || target == Target.Texture2DMultisampleArray;
|
return target == Target.Texture2DMultisample || target == Target.Texture2DMultisampleArray;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static bool HasDepthOrLayers(this Target target)
|
||||||
|
{
|
||||||
|
return target == Target.Texture3D ||
|
||||||
|
target == Target.Texture1DArray ||
|
||||||
|
target == Target.Texture2DArray ||
|
||||||
|
target == Target.Texture2DMultisampleArray ||
|
||||||
|
target == Target.Cubemap ||
|
||||||
|
target == Target.CubemapArray;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -180,7 +180,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
|||||||
|
|
||||||
int firstInstance = (int)_state.State.FirstInstance;
|
int firstInstance = (int)_state.State.FirstInstance;
|
||||||
|
|
||||||
int inlineIndexCount = _drawState.IbStreamer.GetAndResetInlineIndexCount();
|
int inlineIndexCount = _drawState.IbStreamer.GetAndResetInlineIndexCount(_context.Renderer);
|
||||||
|
|
||||||
if (inlineIndexCount != 0)
|
if (inlineIndexCount != 0)
|
||||||
{
|
{
|
||||||
@@ -670,7 +670,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
|||||||
{
|
{
|
||||||
if (indexedInline)
|
if (indexedInline)
|
||||||
{
|
{
|
||||||
int inlineIndexCount = _drawState.IbStreamer.GetAndResetInlineIndexCount();
|
int inlineIndexCount = _drawState.IbStreamer.GetAndResetInlineIndexCount(_context.Renderer);
|
||||||
BufferRange br = new BufferRange(_drawState.IbStreamer.GetInlineIndexBuffer(), 0, inlineIndexCount * 4);
|
BufferRange br = new BufferRange(_drawState.IbStreamer.GetInlineIndexBuffer(), 0, inlineIndexCount * 4);
|
||||||
|
|
||||||
_channel.BufferManager.SetIndexBuffer(br, IndexType.UInt);
|
_channel.BufferManager.SetIndexBuffer(br, IndexType.UInt);
|
||||||
|
@@ -11,9 +11,13 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
struct IbStreamer
|
struct IbStreamer
|
||||||
{
|
{
|
||||||
|
private const int BufferCapacity = 256; // Must be a power of 2.
|
||||||
|
|
||||||
private BufferHandle _inlineIndexBuffer;
|
private BufferHandle _inlineIndexBuffer;
|
||||||
private int _inlineIndexBufferSize;
|
private int _inlineIndexBufferSize;
|
||||||
private int _inlineIndexCount;
|
private int _inlineIndexCount;
|
||||||
|
private uint[] _buffer;
|
||||||
|
private int _bufferOffset;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Indicates if any index buffer data has been pushed.
|
/// Indicates if any index buffer data has been pushed.
|
||||||
@@ -38,9 +42,11 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
|||||||
/// Gets the number of elements on the current inline index buffer,
|
/// Gets the number of elements on the current inline index buffer,
|
||||||
/// while also reseting it to zero for the next draw.
|
/// while also reseting it to zero for the next draw.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="renderer">Host renderer</param>
|
||||||
/// <returns>Inline index bufffer count</returns>
|
/// <returns>Inline index bufffer count</returns>
|
||||||
public int GetAndResetInlineIndexCount()
|
public int GetAndResetInlineIndexCount(IRenderer renderer)
|
||||||
{
|
{
|
||||||
|
UpdateRemaining(renderer);
|
||||||
int temp = _inlineIndexCount;
|
int temp = _inlineIndexCount;
|
||||||
_inlineIndexCount = 0;
|
_inlineIndexCount = 0;
|
||||||
return temp;
|
return temp;
|
||||||
@@ -58,16 +64,12 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
|||||||
byte i2 = (byte)(argument >> 16);
|
byte i2 = (byte)(argument >> 16);
|
||||||
byte i3 = (byte)(argument >> 24);
|
byte i3 = (byte)(argument >> 24);
|
||||||
|
|
||||||
Span<uint> data = stackalloc uint[4];
|
int offset = _inlineIndexCount;
|
||||||
|
|
||||||
data[0] = i0;
|
PushData(renderer, offset, i0);
|
||||||
data[1] = i1;
|
PushData(renderer, offset + 1, i1);
|
||||||
data[2] = i2;
|
PushData(renderer, offset + 2, i2);
|
||||||
data[3] = i3;
|
PushData(renderer, offset + 3, i3);
|
||||||
|
|
||||||
int offset = _inlineIndexCount * 4;
|
|
||||||
|
|
||||||
renderer.SetBufferData(GetInlineIndexBuffer(renderer, offset), offset, MemoryMarshal.Cast<uint, byte>(data));
|
|
||||||
|
|
||||||
_inlineIndexCount += 4;
|
_inlineIndexCount += 4;
|
||||||
}
|
}
|
||||||
@@ -82,14 +84,10 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
|||||||
ushort i0 = (ushort)argument;
|
ushort i0 = (ushort)argument;
|
||||||
ushort i1 = (ushort)(argument >> 16);
|
ushort i1 = (ushort)(argument >> 16);
|
||||||
|
|
||||||
Span<uint> data = stackalloc uint[2];
|
int offset = _inlineIndexCount;
|
||||||
|
|
||||||
data[0] = i0;
|
PushData(renderer, offset, i0);
|
||||||
data[1] = i1;
|
PushData(renderer, offset + 1, i1);
|
||||||
|
|
||||||
int offset = _inlineIndexCount * 4;
|
|
||||||
|
|
||||||
renderer.SetBufferData(GetInlineIndexBuffer(renderer, offset), offset, MemoryMarshal.Cast<uint, byte>(data));
|
|
||||||
|
|
||||||
_inlineIndexCount += 2;
|
_inlineIndexCount += 2;
|
||||||
}
|
}
|
||||||
@@ -103,13 +101,61 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
|||||||
{
|
{
|
||||||
uint i0 = (uint)argument;
|
uint i0 = (uint)argument;
|
||||||
|
|
||||||
Span<uint> data = stackalloc uint[1];
|
int offset = _inlineIndexCount++;
|
||||||
|
|
||||||
data[0] = i0;
|
PushData(renderer, offset, i0);
|
||||||
|
}
|
||||||
|
|
||||||
int offset = _inlineIndexCount++ * 4;
|
/// <summary>
|
||||||
|
/// Pushes a 32-bit value to the index buffer.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="renderer">Host renderer</param>
|
||||||
|
/// <param name="offset">Offset where the data should be written, in 32-bit words</param>
|
||||||
|
/// <param name="value">Index value to be written</param>
|
||||||
|
private void PushData(IRenderer renderer, int offset, uint value)
|
||||||
|
{
|
||||||
|
if (_buffer == null)
|
||||||
|
{
|
||||||
|
_buffer = new uint[BufferCapacity];
|
||||||
|
}
|
||||||
|
|
||||||
renderer.SetBufferData(GetInlineIndexBuffer(renderer, offset), offset, MemoryMarshal.Cast<uint, byte>(data));
|
// We upload data in chunks.
|
||||||
|
// If we are at the start of a chunk, then the buffer might be full,
|
||||||
|
// in that case we need to submit any existing data before overwriting the buffer.
|
||||||
|
int subOffset = offset & (BufferCapacity - 1);
|
||||||
|
|
||||||
|
if (subOffset == 0 && offset != 0)
|
||||||
|
{
|
||||||
|
int baseOffset = (offset - BufferCapacity) * sizeof(uint);
|
||||||
|
BufferHandle buffer = GetInlineIndexBuffer(renderer, baseOffset, BufferCapacity * sizeof(uint));
|
||||||
|
renderer.SetBufferData(buffer, baseOffset, MemoryMarshal.Cast<uint, byte>(_buffer));
|
||||||
|
}
|
||||||
|
|
||||||
|
_buffer[subOffset] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Makes sure that any pending data is submitted to the GPU before the index buffer is used.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="renderer">Host renderer</param>
|
||||||
|
private void UpdateRemaining(IRenderer renderer)
|
||||||
|
{
|
||||||
|
int offset = _inlineIndexCount;
|
||||||
|
if (offset == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int count = offset & (BufferCapacity - 1);
|
||||||
|
if (count == 0)
|
||||||
|
{
|
||||||
|
count = BufferCapacity;
|
||||||
|
}
|
||||||
|
|
||||||
|
int baseOffset = (offset - count) * sizeof(uint);
|
||||||
|
int length = count * sizeof(uint);
|
||||||
|
BufferHandle buffer = GetInlineIndexBuffer(renderer, baseOffset, length);
|
||||||
|
renderer.SetBufferData(buffer, baseOffset, MemoryMarshal.Cast<uint, byte>(_buffer).Slice(0, length));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -117,12 +163,13 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="renderer">Host renderer</param>
|
/// <param name="renderer">Host renderer</param>
|
||||||
/// <param name="offset">Offset where the data will be written</param>
|
/// <param name="offset">Offset where the data will be written</param>
|
||||||
|
/// <param name="length">Number of bytes that will be written</param>
|
||||||
/// <returns>Buffer handle</returns>
|
/// <returns>Buffer handle</returns>
|
||||||
private BufferHandle GetInlineIndexBuffer(IRenderer renderer, int offset)
|
private BufferHandle GetInlineIndexBuffer(IRenderer renderer, int offset, int length)
|
||||||
{
|
{
|
||||||
// Calculate a reasonable size for the buffer that can fit all the data,
|
// Calculate a reasonable size for the buffer that can fit all the data,
|
||||||
// and that also won't require frequent resizes if we need to push more data.
|
// and that also won't require frequent resizes if we need to push more data.
|
||||||
int size = BitUtils.AlignUp(offset + 0x10, 0x200);
|
int size = BitUtils.AlignUp(offset + length + 0x10, 0x200);
|
||||||
|
|
||||||
if (_inlineIndexBuffer == BufferHandle.Null)
|
if (_inlineIndexBuffer == BufferHandle.Null)
|
||||||
{
|
{
|
||||||
|
@@ -152,21 +152,10 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
|||||||
|
|
||||||
ulong ticks = _context.GetTimestamp();
|
ulong ticks = _context.GetTimestamp();
|
||||||
|
|
||||||
float divisor = type switch
|
|
||||||
{
|
|
||||||
ReportCounterType.SamplesPassed => _channel.TextureManager.RenderTargetScale * _channel.TextureManager.RenderTargetScale,
|
|
||||||
_ => 1f
|
|
||||||
};
|
|
||||||
|
|
||||||
ICounterEvent counter = null;
|
ICounterEvent counter = null;
|
||||||
|
|
||||||
void resultHandler(object evt, ulong result)
|
void resultHandler(object evt, ulong result)
|
||||||
{
|
{
|
||||||
if (divisor != 1f)
|
|
||||||
{
|
|
||||||
result = (ulong)MathF.Ceiling(result / divisor);
|
|
||||||
}
|
|
||||||
|
|
||||||
CounterData counterData = new CounterData
|
CounterData counterData = new CounterData
|
||||||
{
|
{
|
||||||
Counter = result,
|
Counter = result,
|
||||||
|
@@ -36,6 +36,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
{
|
{
|
||||||
public TexturePool Pool;
|
public TexturePool Pool;
|
||||||
public int ID;
|
public int ID;
|
||||||
|
public ulong GpuAddress;
|
||||||
}
|
}
|
||||||
|
|
||||||
private GpuContext _context;
|
private GpuContext _context;
|
||||||
@@ -162,6 +163,11 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public bool IsView => _viewStorage != this;
|
public bool IsView => _viewStorage != this;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether or not this texture has views.
|
||||||
|
/// </summary>
|
||||||
|
public bool HasViews => _views.Count > 0;
|
||||||
|
|
||||||
private int _referenceCount;
|
private int _referenceCount;
|
||||||
private List<TexturePoolOwner> _poolOwners;
|
private List<TexturePoolOwner> _poolOwners;
|
||||||
|
|
||||||
@@ -354,7 +360,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
|
|
||||||
texture._viewStorage = this;
|
texture._viewStorage = this;
|
||||||
|
|
||||||
Group.UpdateViews(_views);
|
Group.UpdateViews(_views, texture);
|
||||||
|
|
||||||
if (texture.Group != null && texture.Group != Group)
|
if (texture.Group != null && texture.Group != Group)
|
||||||
{
|
{
|
||||||
@@ -378,11 +384,24 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
{
|
{
|
||||||
_views.Remove(texture);
|
_views.Remove(texture);
|
||||||
|
|
||||||
|
Group.RemoveView(texture);
|
||||||
|
|
||||||
texture._viewStorage = texture;
|
texture._viewStorage = texture;
|
||||||
|
|
||||||
DecrementReferenceCount();
|
DecrementReferenceCount();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Replaces the texture's physical memory range. This forces tracking to regenerate.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="range">New physical memory range backing the texture</param>
|
||||||
|
public void ReplaceRange(MultiRange range)
|
||||||
|
{
|
||||||
|
Range = range;
|
||||||
|
|
||||||
|
Group.RangeChanged();
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Create a copy dependency to a texture that is view compatible with this one.
|
/// Create a copy dependency to a texture that is view compatible with this one.
|
||||||
/// When either texture is modified, the texture data will be copied to the other to keep them in sync.
|
/// When either texture is modified, the texture data will be copied to the other to keep them in sync.
|
||||||
@@ -715,6 +734,8 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
height = Math.Max(height >> level, 1);
|
height = Math.Max(height >> level, 1);
|
||||||
depth = Math.Max(depth >> level, 1);
|
depth = Math.Max(depth >> level, 1);
|
||||||
|
|
||||||
|
int sliceDepth = single ? 1 : depth;
|
||||||
|
|
||||||
SpanOrArray<byte> result;
|
SpanOrArray<byte> result;
|
||||||
|
|
||||||
if (Info.IsLinear)
|
if (Info.IsLinear)
|
||||||
@@ -735,7 +756,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
depth,
|
depth,
|
||||||
single ? 1 : depth,
|
sliceDepth,
|
||||||
levels,
|
levels,
|
||||||
layers,
|
layers,
|
||||||
Info.FormatInfo.BlockWidth,
|
Info.FormatInfo.BlockWidth,
|
||||||
@@ -759,7 +780,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
Info.FormatInfo.BlockHeight,
|
Info.FormatInfo.BlockHeight,
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
depth,
|
sliceDepth,
|
||||||
levels,
|
levels,
|
||||||
layers,
|
layers,
|
||||||
out byte[] decoded))
|
out byte[] decoded))
|
||||||
@@ -771,7 +792,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
|
|
||||||
if (GraphicsConfig.EnableTextureRecompression)
|
if (GraphicsConfig.EnableTextureRecompression)
|
||||||
{
|
{
|
||||||
decoded = BCnEncoder.EncodeBC7(decoded, width, height, depth, levels, layers);
|
decoded = BCnEncoder.EncodeBC7(decoded, width, height, sliceDepth, levels, layers);
|
||||||
}
|
}
|
||||||
|
|
||||||
result = decoded;
|
result = decoded;
|
||||||
@@ -782,15 +803,15 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
{
|
{
|
||||||
case Format.Etc2RgbaSrgb:
|
case Format.Etc2RgbaSrgb:
|
||||||
case Format.Etc2RgbaUnorm:
|
case Format.Etc2RgbaUnorm:
|
||||||
result = ETC2Decoder.DecodeRgba(result, width, height, depth, levels, layers);
|
result = ETC2Decoder.DecodeRgba(result, width, height, sliceDepth, levels, layers);
|
||||||
break;
|
break;
|
||||||
case Format.Etc2RgbPtaSrgb:
|
case Format.Etc2RgbPtaSrgb:
|
||||||
case Format.Etc2RgbPtaUnorm:
|
case Format.Etc2RgbPtaUnorm:
|
||||||
result = ETC2Decoder.DecodePta(result, width, height, depth, levels, layers);
|
result = ETC2Decoder.DecodePta(result, width, height, sliceDepth, levels, layers);
|
||||||
break;
|
break;
|
||||||
case Format.Etc2RgbSrgb:
|
case Format.Etc2RgbSrgb:
|
||||||
case Format.Etc2RgbUnorm:
|
case Format.Etc2RgbUnorm:
|
||||||
result = ETC2Decoder.DecodeRgb(result, width, height, depth, levels, layers);
|
result = ETC2Decoder.DecodeRgb(result, width, height, sliceDepth, levels, layers);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -800,31 +821,31 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
{
|
{
|
||||||
case Format.Bc1RgbaSrgb:
|
case Format.Bc1RgbaSrgb:
|
||||||
case Format.Bc1RgbaUnorm:
|
case Format.Bc1RgbaUnorm:
|
||||||
result = BCnDecoder.DecodeBC1(result, width, height, depth, levels, layers);
|
result = BCnDecoder.DecodeBC1(result, width, height, sliceDepth, levels, layers);
|
||||||
break;
|
break;
|
||||||
case Format.Bc2Srgb:
|
case Format.Bc2Srgb:
|
||||||
case Format.Bc2Unorm:
|
case Format.Bc2Unorm:
|
||||||
result = BCnDecoder.DecodeBC2(result, width, height, depth, levels, layers);
|
result = BCnDecoder.DecodeBC2(result, width, height, sliceDepth, levels, layers);
|
||||||
break;
|
break;
|
||||||
case Format.Bc3Srgb:
|
case Format.Bc3Srgb:
|
||||||
case Format.Bc3Unorm:
|
case Format.Bc3Unorm:
|
||||||
result = BCnDecoder.DecodeBC3(result, width, height, depth, levels, layers);
|
result = BCnDecoder.DecodeBC3(result, width, height, sliceDepth, levels, layers);
|
||||||
break;
|
break;
|
||||||
case Format.Bc4Snorm:
|
case Format.Bc4Snorm:
|
||||||
case Format.Bc4Unorm:
|
case Format.Bc4Unorm:
|
||||||
result = BCnDecoder.DecodeBC4(result, width, height, depth, levels, layers, Format == Format.Bc4Snorm);
|
result = BCnDecoder.DecodeBC4(result, width, height, sliceDepth, levels, layers, Format == Format.Bc4Snorm);
|
||||||
break;
|
break;
|
||||||
case Format.Bc5Snorm:
|
case Format.Bc5Snorm:
|
||||||
case Format.Bc5Unorm:
|
case Format.Bc5Unorm:
|
||||||
result = BCnDecoder.DecodeBC5(result, width, height, depth, levels, layers, Format == Format.Bc5Snorm);
|
result = BCnDecoder.DecodeBC5(result, width, height, sliceDepth, levels, layers, Format == Format.Bc5Snorm);
|
||||||
break;
|
break;
|
||||||
case Format.Bc6HSfloat:
|
case Format.Bc6HSfloat:
|
||||||
case Format.Bc6HUfloat:
|
case Format.Bc6HUfloat:
|
||||||
result = BCnDecoder.DecodeBC6(result, width, height, depth, levels, layers, Format == Format.Bc6HSfloat);
|
result = BCnDecoder.DecodeBC6(result, width, height, sliceDepth, levels, layers, Format == Format.Bc6HSfloat);
|
||||||
break;
|
break;
|
||||||
case Format.Bc7Srgb:
|
case Format.Bc7Srgb:
|
||||||
case Format.Bc7Unorm:
|
case Format.Bc7Unorm:
|
||||||
result = BCnDecoder.DecodeBC7(result, width, height, depth, levels, layers);
|
result = BCnDecoder.DecodeBC7(result, width, height, sliceDepth, levels, layers);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1001,13 +1022,12 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
/// This method should be used to retrieve data that was modified by the host GPU.
|
/// This method should be used to retrieve data that was modified by the host GPU.
|
||||||
/// This is not cheap, avoid doing that unless strictly needed.
|
/// This is not cheap, avoid doing that unless strictly needed.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
/// <param name="output">An output span to place the texture data into. If empty, one is generated</param>
|
/// <param name="output">An output span to place the texture data into</param>
|
||||||
/// <param name="blacklist">True if the texture should be blacklisted, false otherwise</param>
|
/// <param name="blacklist">True if the texture should be blacklisted, false otherwise</param>
|
||||||
/// <param name="texture">The specific host texture to flush. Defaults to this texture</param>
|
/// <param name="texture">The specific host texture to flush. Defaults to this texture</param>
|
||||||
/// <returns>The span containing the texture data</returns>
|
private void GetTextureDataFromGpu(Span<byte> output, bool blacklist, ITexture texture = null)
|
||||||
private ReadOnlySpan<byte> GetTextureDataFromGpu(Span<byte> output, bool blacklist, ITexture texture = null)
|
|
||||||
{
|
{
|
||||||
ReadOnlySpan<byte> data;
|
PinnedSpan<byte> data;
|
||||||
|
|
||||||
if (texture != null)
|
if (texture != null)
|
||||||
{
|
{
|
||||||
@@ -1033,9 +1053,9 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
data = ConvertFromHostCompatibleFormat(output, data);
|
ConvertFromHostCompatibleFormat(output, data.Get());
|
||||||
|
|
||||||
return data;
|
data.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -1050,10 +1070,9 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
/// <param name="level">The level of the texture to flush</param>
|
/// <param name="level">The level of the texture to flush</param>
|
||||||
/// <param name="blacklist">True if the texture should be blacklisted, false otherwise</param>
|
/// <param name="blacklist">True if the texture should be blacklisted, false otherwise</param>
|
||||||
/// <param name="texture">The specific host texture to flush. Defaults to this texture</param>
|
/// <param name="texture">The specific host texture to flush. Defaults to this texture</param>
|
||||||
/// <returns>The span containing the texture data</returns>
|
public void GetTextureDataSliceFromGpu(Span<byte> output, int layer, int level, bool blacklist, ITexture texture = null)
|
||||||
public ReadOnlySpan<byte> GetTextureDataSliceFromGpu(Span<byte> output, int layer, int level, bool blacklist, ITexture texture = null)
|
|
||||||
{
|
{
|
||||||
ReadOnlySpan<byte> data;
|
PinnedSpan<byte> data;
|
||||||
|
|
||||||
if (texture != null)
|
if (texture != null)
|
||||||
{
|
{
|
||||||
@@ -1079,9 +1098,9 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
data = ConvertFromHostCompatibleFormat(output, data, level, true);
|
ConvertFromHostCompatibleFormat(output, data.Get(), level, true);
|
||||||
|
|
||||||
return data;
|
data.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -1454,8 +1473,8 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
|
|
||||||
MultiRange otherRange = texture.Range;
|
MultiRange otherRange = texture.Range;
|
||||||
|
|
||||||
IEnumerable<MultiRange> regions = _sizeInfo.AllRegions().Select((region) => Range.GetSlice((ulong)region.Offset, (ulong)region.Size));
|
IEnumerable<MultiRange> regions = _sizeInfo.AllRegions().Select((region) => Range.Slice((ulong)region.Offset, (ulong)region.Size));
|
||||||
IEnumerable<MultiRange> otherRegions = texture._sizeInfo.AllRegions().Select((region) => otherRange.GetSlice((ulong)region.Offset, (ulong)region.Size));
|
IEnumerable<MultiRange> otherRegions = texture._sizeInfo.AllRegions().Select((region) => otherRange.Slice((ulong)region.Offset, (ulong)region.Size));
|
||||||
|
|
||||||
foreach (MultiRange region in regions)
|
foreach (MultiRange region in regions)
|
||||||
{
|
{
|
||||||
@@ -1484,11 +1503,12 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="pool">The texture pool this texture has been added to</param>
|
/// <param name="pool">The texture pool this texture has been added to</param>
|
||||||
/// <param name="id">The ID of the reference to this texture in the pool</param>
|
/// <param name="id">The ID of the reference to this texture in the pool</param>
|
||||||
public void IncrementReferenceCount(TexturePool pool, int id)
|
/// <param name="gpuVa">GPU VA of the pool reference</param>
|
||||||
|
public void IncrementReferenceCount(TexturePool pool, int id, ulong gpuVa)
|
||||||
{
|
{
|
||||||
lock (_poolOwners)
|
lock (_poolOwners)
|
||||||
{
|
{
|
||||||
_poolOwners.Add(new TexturePoolOwner { Pool = pool, ID = id });
|
_poolOwners.Add(new TexturePoolOwner { Pool = pool, ID = id, GpuAddress = gpuVa });
|
||||||
}
|
}
|
||||||
_referenceCount++;
|
_referenceCount++;
|
||||||
|
|
||||||
@@ -1585,6 +1605,36 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
InvalidatedSequence++;
|
InvalidatedSequence++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Queue updating texture mappings on the pool. Happens from another thread.
|
||||||
|
/// </summary>
|
||||||
|
public void UpdatePoolMappings()
|
||||||
|
{
|
||||||
|
lock (_poolOwners)
|
||||||
|
{
|
||||||
|
ulong address = 0;
|
||||||
|
|
||||||
|
foreach (var owner in _poolOwners)
|
||||||
|
{
|
||||||
|
if (address == 0 || address == owner.GpuAddress)
|
||||||
|
{
|
||||||
|
address = owner.GpuAddress;
|
||||||
|
|
||||||
|
owner.Pool.QueueUpdateMapping(this, owner.ID);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// If there is a different GPU VA mapping, prefer the first and delete the others.
|
||||||
|
owner.Pool.ForceRemove(this, owner.ID, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_poolOwners.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
InvalidatedSequence++;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Delete the texture if it is not used anymore.
|
/// Delete the texture if it is not used anymore.
|
||||||
/// The texture is considered unused when the reference count is zero,
|
/// The texture is considered unused when the reference count is zero,
|
||||||
@@ -1636,7 +1686,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
Group.ClearModified(unmapRange);
|
Group.ClearModified(unmapRange);
|
||||||
}
|
}
|
||||||
|
|
||||||
RemoveFromPools(true);
|
UpdatePoolMappings();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@@ -194,6 +194,39 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
_cache.Lift(texture);
|
_cache.Lift(texture);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Attempts to update a texture's physical memory range.
|
||||||
|
/// Returns false if there is an existing texture that matches with the updated range.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="texture">Texture to update</param>
|
||||||
|
/// <param name="range">New physical memory range</param>
|
||||||
|
/// <returns>True if the mapping was updated, false otherwise</returns>
|
||||||
|
public bool UpdateMapping(Texture texture, MultiRange range)
|
||||||
|
{
|
||||||
|
// There cannot be an existing texture compatible with this mapping in the texture cache already.
|
||||||
|
int overlapCount = _textures.FindOverlaps(range, ref _textureOverlaps);
|
||||||
|
|
||||||
|
for (int i = 0; i < overlapCount; i++)
|
||||||
|
{
|
||||||
|
var other = _textureOverlaps[i];
|
||||||
|
|
||||||
|
if (texture != other &&
|
||||||
|
(texture.IsViewCompatible(other.Info, other.Range, true, other.LayerSize, _context.Capabilities, out _, out _) != TextureViewCompatibility.Incompatible ||
|
||||||
|
other.IsViewCompatible(texture.Info, texture.Range, true, texture.LayerSize, _context.Capabilities, out _, out _) != TextureViewCompatibility.Incompatible))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_textures.Remove(texture);
|
||||||
|
|
||||||
|
texture.ReplaceRange(range);
|
||||||
|
|
||||||
|
_textures.Add(texture);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Tries to find an existing texture, or create a new one if not found.
|
/// Tries to find an existing texture, or create a new one if not found.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@@ -39,6 +39,11 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
class TextureGroup : IDisposable
|
class TextureGroup : IDisposable
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Threshold of layers to force granular handles (and thus partial loading) on array/3D textures.
|
||||||
|
/// </summary>
|
||||||
|
private const int GranularLayerThreshold = 8;
|
||||||
|
|
||||||
private delegate void HandlesCallbackDelegate(int baseHandle, int regionCount, bool split = false);
|
private delegate void HandlesCallbackDelegate(int baseHandle, int regionCount, bool split = false);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -116,7 +121,29 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
_allOffsets = size.AllOffsets;
|
_allOffsets = size.AllOffsets;
|
||||||
_sliceSizes = size.SliceSizes;
|
_sliceSizes = size.SliceSizes;
|
||||||
|
|
||||||
(_hasLayerViews, _hasMipViews) = PropagateGranularity(hasLayerViews, hasMipViews);
|
if (Storage.Target.HasDepthOrLayers() && Storage.Info.GetSlices() > GranularLayerThreshold)
|
||||||
|
{
|
||||||
|
_hasLayerViews = true;
|
||||||
|
_hasMipViews = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
(_hasLayerViews, _hasMipViews) = PropagateGranularity(hasLayerViews, hasMipViews);
|
||||||
|
|
||||||
|
// If the texture is partially mapped, fully subdivide handles immediately.
|
||||||
|
|
||||||
|
MultiRange range = Storage.Range;
|
||||||
|
for (int i = 0; i < range.Count; i++)
|
||||||
|
{
|
||||||
|
if (range.GetSubRange(i).Address == MemoryManager.PteUnmapped)
|
||||||
|
{
|
||||||
|
_hasLayerViews = true;
|
||||||
|
_hasMipViews = true;
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
RecalculateHandleRegions();
|
RecalculateHandleRegions();
|
||||||
}
|
}
|
||||||
@@ -249,7 +276,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
{
|
{
|
||||||
bool dirty = false;
|
bool dirty = false;
|
||||||
bool anyModified = false;
|
bool anyModified = false;
|
||||||
bool anyUnmapped = false;
|
bool anyNotDirty = false;
|
||||||
|
|
||||||
for (int i = 0; i < regionCount; i++)
|
for (int i = 0; i < regionCount; i++)
|
||||||
{
|
{
|
||||||
@@ -294,20 +321,21 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
dirty |= handleDirty;
|
dirty |= handleDirty;
|
||||||
}
|
}
|
||||||
|
|
||||||
anyUnmapped |= handleUnmapped;
|
|
||||||
|
|
||||||
if (group.NeedsCopy)
|
if (group.NeedsCopy)
|
||||||
{
|
{
|
||||||
// The texture we copied from is still being written to. Copy from it again the next time this texture is used.
|
// The texture we copied from is still being written to. Copy from it again the next time this texture is used.
|
||||||
texture.SignalGroupDirty();
|
texture.SignalGroupDirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
_loadNeeded[baseHandle + i] = handleDirty && !handleUnmapped;
|
bool loadNeeded = handleDirty && !handleUnmapped;
|
||||||
|
|
||||||
|
anyNotDirty |= !loadNeeded;
|
||||||
|
_loadNeeded[baseHandle + i] = loadNeeded;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dirty)
|
if (dirty)
|
||||||
{
|
{
|
||||||
if (anyUnmapped || (_handles.Length > 1 && (anyModified || split)))
|
if (anyNotDirty || (_handles.Length > 1 && (anyModified || split)))
|
||||||
{
|
{
|
||||||
// Partial texture invalidation. Only update the layers/levels with dirty flags of the storage.
|
// Partial texture invalidation. Only update the layers/levels with dirty flags of the storage.
|
||||||
|
|
||||||
@@ -331,24 +359,56 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
/// <param name="regionCount">The number of handles to synchronize</param>
|
/// <param name="regionCount">The number of handles to synchronize</param>
|
||||||
private void SynchronizePartial(int baseHandle, int regionCount)
|
private void SynchronizePartial(int baseHandle, int regionCount)
|
||||||
{
|
{
|
||||||
|
int spanEndIndex = -1;
|
||||||
|
int spanBase = 0;
|
||||||
|
ReadOnlySpan<byte> dataSpan = ReadOnlySpan<byte>.Empty;
|
||||||
|
|
||||||
for (int i = 0; i < regionCount; i++)
|
for (int i = 0; i < regionCount; i++)
|
||||||
{
|
{
|
||||||
if (_loadNeeded[baseHandle + i])
|
if (_loadNeeded[baseHandle + i])
|
||||||
{
|
{
|
||||||
var info = GetHandleInformation(baseHandle + i);
|
var info = GetHandleInformation(baseHandle + i);
|
||||||
|
|
||||||
|
// Ensure the data for this handle is loaded in the span.
|
||||||
|
if (spanEndIndex <= i - 1)
|
||||||
|
{
|
||||||
|
spanEndIndex = i;
|
||||||
|
|
||||||
|
if (_is3D)
|
||||||
|
{
|
||||||
|
// Look ahead to see how many handles need to be loaded.
|
||||||
|
for (int j = i + 1; j < regionCount; j++)
|
||||||
|
{
|
||||||
|
if (_loadNeeded[baseHandle + j])
|
||||||
|
{
|
||||||
|
spanEndIndex = j;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var endInfo = spanEndIndex == i ? info : GetHandleInformation(baseHandle + spanEndIndex);
|
||||||
|
|
||||||
|
spanBase = _allOffsets[info.Index];
|
||||||
|
int spanLast = _allOffsets[endInfo.Index + endInfo.Layers * endInfo.Levels - 1];
|
||||||
|
int endOffset = Math.Min(spanLast + _sliceSizes[endInfo.BaseLevel + endInfo.Levels - 1], (int)Storage.Size);
|
||||||
|
int size = endOffset - spanBase;
|
||||||
|
|
||||||
|
dataSpan = _physicalMemory.GetSpan(Storage.Range.Slice((ulong)spanBase, (ulong)size));
|
||||||
|
}
|
||||||
|
|
||||||
// Only one of these will be greater than 1, as partial sync is only called when there are sub-image views.
|
// Only one of these will be greater than 1, as partial sync is only called when there are sub-image views.
|
||||||
for (int layer = 0; layer < info.Layers; layer++)
|
for (int layer = 0; layer < info.Layers; layer++)
|
||||||
{
|
{
|
||||||
for (int level = 0; level < info.Levels; level++)
|
for (int level = 0; level < info.Levels; level++)
|
||||||
{
|
{
|
||||||
int offsetIndex = GetOffsetIndex(info.BaseLayer + layer, info.BaseLevel + level);
|
int offsetIndex = GetOffsetIndex(info.BaseLayer + layer, info.BaseLevel + level);
|
||||||
|
|
||||||
int offset = _allOffsets[offsetIndex];
|
int offset = _allOffsets[offsetIndex];
|
||||||
int endOffset = Math.Min(offset + _sliceSizes[info.BaseLevel + level], (int)Storage.Size);
|
|
||||||
int size = endOffset - offset;
|
|
||||||
|
|
||||||
ReadOnlySpan<byte> data = _physicalMemory.GetSpan(Storage.Range.GetSlice((ulong)offset, (ulong)size));
|
ReadOnlySpan<byte> data = dataSpan.Slice(offset - spanBase);
|
||||||
|
|
||||||
SpanOrArray<byte> result = Storage.ConvertToHostCompatibleFormat(data, info.BaseLevel + level, true);
|
SpanOrArray<byte> result = Storage.ConvertToHostCompatibleFormat(data, info.BaseLevel + level, true);
|
||||||
|
|
||||||
@@ -413,7 +473,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
int endOffset = Math.Min(offset + _sliceSizes[level], (int)Storage.Size);
|
int endOffset = Math.Min(offset + _sliceSizes[level], (int)Storage.Size);
|
||||||
int size = endOffset - offset;
|
int size = endOffset - offset;
|
||||||
|
|
||||||
using WritableRegion region = _physicalMemory.GetWritableRegion(Storage.Range.GetSlice((ulong)offset, (ulong)size), tracked);
|
using WritableRegion region = _physicalMemory.GetWritableRegion(Storage.Range.Slice((ulong)offset, (ulong)size), tracked);
|
||||||
|
|
||||||
Storage.GetTextureDataSliceFromGpu(region.Memory.Span, layer, level, tracked, texture);
|
Storage.GetTextureDataSliceFromGpu(region.Memory.Span, layer, level, tracked, texture);
|
||||||
}
|
}
|
||||||
@@ -865,8 +925,11 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
/// <returns>A TextureGroupHandle covering the given views</returns>
|
/// <returns>A TextureGroupHandle covering the given views</returns>
|
||||||
private TextureGroupHandle GenerateHandles(int viewStart, int views)
|
private TextureGroupHandle GenerateHandles(int viewStart, int views)
|
||||||
{
|
{
|
||||||
|
int viewEnd = viewStart + views - 1;
|
||||||
|
(_, int lastLevel) = GetLayerLevelForView(viewEnd);
|
||||||
|
|
||||||
int offset = _allOffsets[viewStart];
|
int offset = _allOffsets[viewStart];
|
||||||
int endOffset = (viewStart + views == _allOffsets.Length) ? (int)Storage.Size : _allOffsets[viewStart + views];
|
int endOffset = _allOffsets[viewEnd] + _sliceSizes[lastLevel];
|
||||||
int size = endOffset - offset;
|
int size = endOffset - offset;
|
||||||
|
|
||||||
var result = new List<CpuRegionHandle>();
|
var result = new List<CpuRegionHandle>();
|
||||||
@@ -926,7 +989,8 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
/// Update the views in this texture group, rebuilding the memory tracking if required.
|
/// Update the views in this texture group, rebuilding the memory tracking if required.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="views">The views list of the storage texture</param>
|
/// <param name="views">The views list of the storage texture</param>
|
||||||
public void UpdateViews(List<Texture> views)
|
/// <param name="texture">The texture that has been added, if that is the only change, otherwise null</param>
|
||||||
|
public void UpdateViews(List<Texture> views, Texture texture)
|
||||||
{
|
{
|
||||||
// This is saved to calculate overlapping views for each handle.
|
// This is saved to calculate overlapping views for each handle.
|
||||||
_views = views;
|
_views = views;
|
||||||
@@ -964,17 +1028,44 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
|
|
||||||
if (!regionsRebuilt)
|
if (!regionsRebuilt)
|
||||||
{
|
{
|
||||||
// Must update the overlapping views on all handles, but only if they were not just recreated.
|
if (texture != null)
|
||||||
|
|
||||||
foreach (TextureGroupHandle handle in _handles)
|
|
||||||
{
|
{
|
||||||
handle.RecalculateOverlaps(this, views);
|
int offset = FindOffset(texture);
|
||||||
|
|
||||||
|
foreach (TextureGroupHandle handle in _handles)
|
||||||
|
{
|
||||||
|
handle.AddOverlap(offset, texture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Must update the overlapping views on all handles, but only if they were not just recreated.
|
||||||
|
|
||||||
|
foreach (TextureGroupHandle handle in _handles)
|
||||||
|
{
|
||||||
|
handle.RecalculateOverlaps(this, views);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SignalAllDirty();
|
SignalAllDirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes a view from the group, removing it from all overlap lists.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="view">View to remove from the group</param>
|
||||||
|
public void RemoveView(Texture view)
|
||||||
|
{
|
||||||
|
int offset = FindOffset(view);
|
||||||
|
|
||||||
|
foreach (TextureGroupHandle handle in _handles)
|
||||||
|
{
|
||||||
|
handle.RemoveOverlap(offset, view);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Inherit handle state from an old set of handles, such as modified and dirty flags.
|
/// Inherit handle state from an old set of handles, such as modified and dirty flags.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -1057,7 +1148,8 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
/// The dirty flags from the previous handles will be kept.
|
/// The dirty flags from the previous handles will be kept.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="handles">The handles to replace the current handles with</param>
|
/// <param name="handles">The handles to replace the current handles with</param>
|
||||||
private void ReplaceHandles(TextureGroupHandle[] handles)
|
/// <param name="rangeChanged">True if the storage memory range changed since the last region handle generation</param>
|
||||||
|
private void ReplaceHandles(TextureGroupHandle[] handles, bool rangeChanged)
|
||||||
{
|
{
|
||||||
if (_handles != null)
|
if (_handles != null)
|
||||||
{
|
{
|
||||||
@@ -1065,9 +1157,50 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
|
|
||||||
foreach (TextureGroupHandle groupHandle in handles)
|
foreach (TextureGroupHandle groupHandle in handles)
|
||||||
{
|
{
|
||||||
foreach (CpuRegionHandle handle in groupHandle.Handles)
|
if (rangeChanged)
|
||||||
{
|
{
|
||||||
handle.Reprotect();
|
// When the storage range changes, this becomes a little different.
|
||||||
|
// If a range does not match one in the original, treat it as modified.
|
||||||
|
// It has been newly mapped and its data must be synchronized.
|
||||||
|
|
||||||
|
if (groupHandle.Handles.Length == 0)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var oldGroup in _handles)
|
||||||
|
{
|
||||||
|
if (!groupHandle.OverlapsWith(oldGroup.Offset, oldGroup.Size))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (CpuRegionHandle handle in groupHandle.Handles)
|
||||||
|
{
|
||||||
|
bool hasMatch = false;
|
||||||
|
|
||||||
|
foreach (var oldHandle in oldGroup.Handles)
|
||||||
|
{
|
||||||
|
if (oldHandle.RangeEquals(handle))
|
||||||
|
{
|
||||||
|
hasMatch = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasMatch)
|
||||||
|
{
|
||||||
|
handle.Reprotect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
foreach (CpuRegionHandle handle in groupHandle.Handles)
|
||||||
|
{
|
||||||
|
handle.Reprotect();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1089,7 +1222,8 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Recalculate handle regions for this texture group, and inherit existing state into the new handles.
|
/// Recalculate handle regions for this texture group, and inherit existing state into the new handles.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void RecalculateHandleRegions()
|
/// <param name="rangeChanged">True if the storage memory range changed since the last region handle generation</param>
|
||||||
|
private void RecalculateHandleRegions(bool rangeChanged = false)
|
||||||
{
|
{
|
||||||
TextureGroupHandle[] handles;
|
TextureGroupHandle[] handles;
|
||||||
|
|
||||||
@@ -1171,7 +1305,21 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ReplaceHandles(handles);
|
ReplaceHandles(handles, rangeChanged);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Regenerates handles when the storage range has been remapped.
|
||||||
|
/// This forces the regions to be fully subdivided.
|
||||||
|
/// </summary>
|
||||||
|
public void RangeChanged()
|
||||||
|
{
|
||||||
|
_hasLayerViews = true;
|
||||||
|
_hasMipViews = true;
|
||||||
|
|
||||||
|
RecalculateHandleRegions(true);
|
||||||
|
|
||||||
|
SignalAllDirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -1271,13 +1419,13 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
for (int i = 0; i < _allOffsets.Length; i++)
|
for (int i = 0; i < _allOffsets.Length; i++)
|
||||||
{
|
{
|
||||||
(int layer, int level) = GetLayerLevelForView(i);
|
(int layer, int level) = GetLayerLevelForView(i);
|
||||||
MultiRange handleRange = Storage.Range.GetSlice((ulong)_allOffsets[i], 1);
|
MultiRange handleRange = Storage.Range.Slice((ulong)_allOffsets[i], 1);
|
||||||
ulong handleBase = handleRange.GetSubRange(0).Address;
|
ulong handleBase = handleRange.GetSubRange(0).Address;
|
||||||
|
|
||||||
for (int j = 0; j < other._handles.Length; j++)
|
for (int j = 0; j < other._handles.Length; j++)
|
||||||
{
|
{
|
||||||
(int otherLayer, int otherLevel) = other.GetLayerLevelForView(j);
|
(int otherLayer, int otherLevel) = other.GetLayerLevelForView(j);
|
||||||
MultiRange otherHandleRange = other.Storage.Range.GetSlice((ulong)other._allOffsets[j], 1);
|
MultiRange otherHandleRange = other.Storage.Range.Slice((ulong)other._allOffsets[j], 1);
|
||||||
ulong otherHandleBase = otherHandleRange.GetSubRange(0).Address;
|
ulong otherHandleBase = otherHandleRange.GetSubRange(0).Address;
|
||||||
|
|
||||||
if (handleBase == otherHandleBase)
|
if (handleBase == otherHandleBase)
|
||||||
@@ -1354,7 +1502,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
// Handles list is not modified by another thread, only replaced, so this is thread safe.
|
// Handles list is not modified by another thread, only replaced, so this is thread safe.
|
||||||
// Remove modified flags from all overlapping handles, so that the textures don't flush to unmapped/remapped GPU memory.
|
// Remove modified flags from all overlapping handles, so that the textures don't flush to unmapped/remapped GPU memory.
|
||||||
|
|
||||||
MultiRange subRange = Storage.Range.GetSlice((ulong)handle.Offset, (ulong)handle.Size);
|
MultiRange subRange = Storage.Range.Slice((ulong)handle.Offset, (ulong)handle.Size);
|
||||||
|
|
||||||
if (range.OverlapsWith(subRange))
|
if (range.OverlapsWith(subRange))
|
||||||
{
|
{
|
||||||
|
@@ -159,6 +159,42 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a single texture view as an overlap if its range overlaps.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="offset">The offset of the view in the group</param>
|
||||||
|
/// <param name="view">The texture to add as an overlap</param>
|
||||||
|
public void AddOverlap(int offset, Texture view)
|
||||||
|
{
|
||||||
|
// Overlaps can be accessed from the memory tracking signal handler, so access must be atomic.
|
||||||
|
|
||||||
|
if (OverlapsWith(offset, (int)view.Size))
|
||||||
|
{
|
||||||
|
lock (Overlaps)
|
||||||
|
{
|
||||||
|
Overlaps.Add(view);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes a single texture view as an overlap if its range overlaps.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="offset">The offset of the view in the group</param>
|
||||||
|
/// <param name="view">The texture to add as an overlap</param>
|
||||||
|
public void RemoveOverlap(int offset, Texture view)
|
||||||
|
{
|
||||||
|
// Overlaps can be accessed from the memory tracking signal handler, so access must be atomic.
|
||||||
|
|
||||||
|
if (OverlapsWith(offset, (int)view.Size))
|
||||||
|
{
|
||||||
|
lock (Overlaps)
|
||||||
|
{
|
||||||
|
Overlaps.Remove(view);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Registers a sync action to happen for this handle, and an interim flush action on the tracking handle.
|
/// Registers a sync action to happen for this handle, and an interim flush action on the tracking handle.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@@ -1,9 +1,12 @@
|
|||||||
using Ryujinx.Common.Logging;
|
using Ryujinx.Common.Logging;
|
||||||
using Ryujinx.Graphics.GAL;
|
using Ryujinx.Graphics.GAL;
|
||||||
|
using Ryujinx.Graphics.Gpu.Memory;
|
||||||
using Ryujinx.Graphics.Texture;
|
using Ryujinx.Graphics.Texture;
|
||||||
|
using Ryujinx.Memory.Range;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Threading;
|
||||||
|
|
||||||
namespace Ryujinx.Graphics.Gpu.Image
|
namespace Ryujinx.Graphics.Gpu.Image
|
||||||
{
|
{
|
||||||
@@ -12,8 +15,63 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
class TexturePool : Pool<Texture, TextureDescriptor>, IPool<TexturePool>
|
class TexturePool : Pool<Texture, TextureDescriptor>, IPool<TexturePool>
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A request to dereference a texture from a pool.
|
||||||
|
/// </summary>
|
||||||
|
private struct DereferenceRequest
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Whether the dereference is due to a mapping change or not.
|
||||||
|
/// </summary>
|
||||||
|
public readonly bool IsRemapped;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The texture being dereferenced.
|
||||||
|
/// </summary>
|
||||||
|
public readonly Texture Texture;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The ID of the pool entry this reference belonged to.
|
||||||
|
/// </summary>
|
||||||
|
public readonly int ID;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a dereference request for a texture with a specific pool ID, and remapped flag.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="isRemapped">Whether the dereference is due to a mapping change or not</param>
|
||||||
|
/// <param name="texture">The texture being dereferenced</param>
|
||||||
|
/// <param name="id">The ID of the pool entry, used to restore remapped textures</param>
|
||||||
|
private DereferenceRequest(bool isRemapped, Texture texture, int id)
|
||||||
|
{
|
||||||
|
IsRemapped = isRemapped;
|
||||||
|
Texture = texture;
|
||||||
|
ID = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a dereference request for a texture removal.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="texture">The texture being removed</param>
|
||||||
|
/// <returns>A texture removal dereference request</returns>
|
||||||
|
public static DereferenceRequest Remove(Texture texture)
|
||||||
|
{
|
||||||
|
return new DereferenceRequest(false, texture, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create a dereference request for a texture remapping with a specific pool ID.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="texture">The texture being remapped</param>
|
||||||
|
/// <param name="id">The ID of the pool entry, used to restore remapped textures</param>
|
||||||
|
/// <returns>A remap dereference request</returns>
|
||||||
|
public static DereferenceRequest Remap(Texture texture, int id)
|
||||||
|
{
|
||||||
|
return new DereferenceRequest(true, texture, id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private readonly GpuChannel _channel;
|
private readonly GpuChannel _channel;
|
||||||
private readonly ConcurrentQueue<Texture> _dereferenceQueue = new ConcurrentQueue<Texture>();
|
private readonly ConcurrentQueue<DereferenceRequest> _dereferenceQueue = new ConcurrentQueue<DereferenceRequest>();
|
||||||
private TextureDescriptor _defaultDescriptor;
|
private TextureDescriptor _defaultDescriptor;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -58,7 +116,11 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
{
|
{
|
||||||
TextureInfo info = GetInfo(descriptor, out int layerSize);
|
TextureInfo info = GetInfo(descriptor, out int layerSize);
|
||||||
|
|
||||||
ProcessDereferenceQueue();
|
// The dereference queue can put our texture back on the cache.
|
||||||
|
if ((texture = ProcessDereferenceQueue(id)) != null)
|
||||||
|
{
|
||||||
|
return ref descriptor;
|
||||||
|
}
|
||||||
|
|
||||||
texture = PhysicalMemory.TextureCache.FindOrCreateTexture(_channel.MemoryManager, TextureSearchFlags.ForSampler, info, layerSize);
|
texture = PhysicalMemory.TextureCache.FindOrCreateTexture(_channel.MemoryManager, TextureSearchFlags.ForSampler, info, layerSize);
|
||||||
|
|
||||||
@@ -68,11 +130,15 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
return ref descriptor;
|
return ref descriptor;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
texture.IncrementReferenceCount(this, id);
|
{
|
||||||
|
texture.SynchronizeMemory();
|
||||||
|
}
|
||||||
|
|
||||||
Items[id] = texture;
|
Items[id] = texture;
|
||||||
|
|
||||||
|
texture.IncrementReferenceCount(this, id, descriptor.UnpackAddress());
|
||||||
|
|
||||||
DescriptorCache[id] = descriptor;
|
DescriptorCache[id] = descriptor;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -155,11 +221,14 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
/// <param name="deferred">If true, queue the dereference to happen on the render thread, otherwise dereference immediately</param>
|
/// <param name="deferred">If true, queue the dereference to happen on the render thread, otherwise dereference immediately</param>
|
||||||
public void ForceRemove(Texture texture, int id, bool deferred)
|
public void ForceRemove(Texture texture, int id, bool deferred)
|
||||||
{
|
{
|
||||||
Items[id] = null;
|
var previous = Interlocked.Exchange(ref Items[id], null);
|
||||||
|
|
||||||
if (deferred)
|
if (deferred)
|
||||||
{
|
{
|
||||||
_dereferenceQueue.Enqueue(texture);
|
if (previous != null)
|
||||||
|
{
|
||||||
|
_dereferenceQueue.Enqueue(DereferenceRequest.Remove(texture));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -167,16 +236,91 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Queues a request to update a texture's mapping.
|
||||||
|
/// Mapping is updated later to avoid deleting the texture if it is still sparsely mapped.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="texture">Texture with potential mapping change</param>
|
||||||
|
/// <param name="id">ID in cache of texture with potential mapping change</param>
|
||||||
|
public void QueueUpdateMapping(Texture texture, int id)
|
||||||
|
{
|
||||||
|
if (Interlocked.Exchange(ref Items[id], null) == texture)
|
||||||
|
{
|
||||||
|
_dereferenceQueue.Enqueue(DereferenceRequest.Remap(texture, id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Process the dereference queue, decrementing the reference count for each texture in it.
|
/// Process the dereference queue, decrementing the reference count for each texture in it.
|
||||||
/// This is used to ensure that texture disposal happens on the render thread.
|
/// This is used to ensure that texture disposal happens on the render thread.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void ProcessDereferenceQueue()
|
/// <param name="id">The ID of the entry that triggered this method</param>
|
||||||
|
/// <returns>Texture that matches the entry ID if it has been readded to the cache.</returns>
|
||||||
|
private Texture ProcessDereferenceQueue(int id = -1)
|
||||||
{
|
{
|
||||||
while (_dereferenceQueue.TryDequeue(out Texture toRemove))
|
while (_dereferenceQueue.TryDequeue(out DereferenceRequest request))
|
||||||
{
|
{
|
||||||
toRemove.DecrementReferenceCount();
|
Texture texture = request.Texture;
|
||||||
|
|
||||||
|
// Unmapped storage textures can swap their ranges. The texture must be storage with no views or dependencies.
|
||||||
|
// TODO: Would need to update ranges on views, or guarantee that ones where the range changes can be instantly deleted.
|
||||||
|
|
||||||
|
if (request.IsRemapped && texture.Group.Storage == texture && !texture.HasViews && !texture.Group.HasCopyDependencies)
|
||||||
|
{
|
||||||
|
// Has the mapping for this texture changed?
|
||||||
|
ref readonly TextureDescriptor descriptor = ref GetDescriptorRef(request.ID);
|
||||||
|
|
||||||
|
ulong address = descriptor.UnpackAddress();
|
||||||
|
|
||||||
|
MultiRange range = _channel.MemoryManager.GetPhysicalRegions(address, texture.Size);
|
||||||
|
|
||||||
|
// If the texture is not mapped at all, delete its reference.
|
||||||
|
|
||||||
|
if (range.Count == 1 && range.GetSubRange(0).Address == MemoryManager.PteUnmapped)
|
||||||
|
{
|
||||||
|
texture.DecrementReferenceCount();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Items[request.ID] = texture;
|
||||||
|
|
||||||
|
// Create a new pool reference, as the last one was removed on unmap.
|
||||||
|
|
||||||
|
texture.IncrementReferenceCount(this, request.ID, address);
|
||||||
|
texture.DecrementReferenceCount();
|
||||||
|
|
||||||
|
// Refetch the range. Changes since the last check could have been lost
|
||||||
|
// as the cache entry was not restored (required to queue mapping change).
|
||||||
|
|
||||||
|
range = _channel.MemoryManager.GetPhysicalRegions(address, texture.Size);
|
||||||
|
|
||||||
|
if (!range.Equals(texture.Range))
|
||||||
|
{
|
||||||
|
// Part of the texture was mapped or unmapped. Replace the range and regenerate tracking handles.
|
||||||
|
if (!_channel.MemoryManager.Physical.TextureCache.UpdateMapping(texture, range))
|
||||||
|
{
|
||||||
|
// Texture could not be remapped due to a collision, just delete it.
|
||||||
|
if (Interlocked.Exchange(ref Items[request.ID], null) != null)
|
||||||
|
{
|
||||||
|
// If this is null, a request was already queued to decrement reference.
|
||||||
|
texture.DecrementReferenceCount(this, request.ID);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (request.ID == id)
|
||||||
|
{
|
||||||
|
return texture;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
texture.DecrementReferenceCount();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -213,9 +357,10 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
_channel.MemoryManager.Physical.TextureCache.AddShortCache(texture, ref cachedDescriptor);
|
_channel.MemoryManager.Physical.TextureCache.AddShortCache(texture, ref cachedDescriptor);
|
||||||
}
|
}
|
||||||
|
|
||||||
texture.DecrementReferenceCount(this, id);
|
if (Interlocked.Exchange(ref Items[id], null) != null)
|
||||||
|
{
|
||||||
Items[id] = null;
|
texture.DecrementReferenceCount(this, id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -82,7 +82,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||||||
Address = address;
|
Address = address;
|
||||||
Size = size;
|
Size = size;
|
||||||
|
|
||||||
Handle = context.Renderer.CreateBuffer((int)size);
|
Handle = context.Renderer.CreateBuffer((int)size, baseBuffers?.MaxBy(x => x.Size).Handle ?? BufferHandle.Null);
|
||||||
|
|
||||||
_useGranular = size > GranularBufferThreshold;
|
_useGranular = size > GranularBufferThreshold;
|
||||||
|
|
||||||
@@ -415,10 +415,10 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||||||
{
|
{
|
||||||
int offset = (int)(address - Address);
|
int offset = (int)(address - Address);
|
||||||
|
|
||||||
ReadOnlySpan<byte> data = _context.Renderer.GetBufferData(Handle, offset, (int)size);
|
using PinnedSpan<byte> data = _context.Renderer.GetBufferData(Handle, offset, (int)size);
|
||||||
|
|
||||||
// TODO: When write tracking shaders, they will need to be aware of changes in overlapping buffers.
|
// TODO: When write tracking shaders, they will need to be aware of changes in overlapping buffers.
|
||||||
_physicalMemory.WriteUntracked(address, data);
|
_physicalMemory.WriteUntracked(address, data.Get());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@@ -1,3 +1,5 @@
|
|||||||
|
using Ryujinx.Common;
|
||||||
|
using Ryujinx.Common.Memory;
|
||||||
using Ryujinx.Graphics.GAL;
|
using Ryujinx.Graphics.GAL;
|
||||||
using Ryujinx.Graphics.Shader;
|
using Ryujinx.Graphics.Shader;
|
||||||
using Ryujinx.Graphics.Shader.Translation;
|
using Ryujinx.Graphics.Shader.Translation;
|
||||||
@@ -11,16 +13,15 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
|||||||
{
|
{
|
||||||
public static byte[] Pack(ShaderSource[] sources)
|
public static byte[] Pack(ShaderSource[] sources)
|
||||||
{
|
{
|
||||||
using MemoryStream output = new MemoryStream();
|
using MemoryStream output = MemoryStreamManager.Shared.GetStream();
|
||||||
using BinaryWriter writer = new BinaryWriter(output);
|
|
||||||
|
|
||||||
writer.Write(sources.Length);
|
output.Write(sources.Length);
|
||||||
|
|
||||||
for (int i = 0; i < sources.Length; i++)
|
foreach (ShaderSource source in sources)
|
||||||
{
|
{
|
||||||
writer.Write((int)sources[i].Stage);
|
output.Write((int)source.Stage);
|
||||||
writer.Write(sources[i].BinaryCode.Length);
|
output.Write(source.BinaryCode.Length);
|
||||||
writer.Write(sources[i].BinaryCode);
|
output.Write(source.BinaryCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
return output.ToArray();
|
return output.ToArray();
|
||||||
|
@@ -55,11 +55,14 @@ namespace Ryujinx.Graphics.OpenGL
|
|||||||
(IntPtr)size);
|
(IntPtr)size);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static unsafe ReadOnlySpan<byte> GetData(OpenGLRenderer renderer, BufferHandle buffer, int offset, int size)
|
public static unsafe PinnedSpan<byte> GetData(OpenGLRenderer renderer, BufferHandle buffer, int offset, int size)
|
||||||
{
|
{
|
||||||
|
// Data in the persistent buffer and host array is guaranteed to be available
|
||||||
|
// until the next time the host thread requests data.
|
||||||
|
|
||||||
if (HwCapabilities.UsePersistentBufferForFlush)
|
if (HwCapabilities.UsePersistentBufferForFlush)
|
||||||
{
|
{
|
||||||
return renderer.PersistentBuffers.Default.GetBufferData(buffer, offset, size);
|
return PinnedSpan<byte>.UnsafeFromSpan(renderer.PersistentBuffers.Default.GetBufferData(buffer, offset, size));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -69,7 +72,7 @@ namespace Ryujinx.Graphics.OpenGL
|
|||||||
|
|
||||||
GL.GetBufferSubData(BufferTarget.CopyReadBuffer, (IntPtr)offset, size, target);
|
GL.GetBufferSubData(BufferTarget.CopyReadBuffer, (IntPtr)offset, size, target);
|
||||||
|
|
||||||
return new ReadOnlySpan<byte>(target.ToPointer(), size);
|
return new PinnedSpan<byte>(target.ToPointer(), size);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -39,12 +39,12 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
|||||||
throw new NotSupportedException();
|
throw new NotSupportedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
public ReadOnlySpan<byte> GetData()
|
public PinnedSpan<byte> GetData()
|
||||||
{
|
{
|
||||||
return Buffer.GetData(_renderer, _buffer, _bufferOffset, _bufferSize);
|
return Buffer.GetData(_renderer, _buffer, _bufferOffset, _bufferSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ReadOnlySpan<byte> GetData(int layer, int level)
|
public PinnedSpan<byte> GetData(int layer, int level)
|
||||||
{
|
{
|
||||||
return GetData();
|
return GetData();
|
||||||
}
|
}
|
||||||
|
@@ -166,7 +166,7 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
|||||||
_renderer.TextureCopy.Copy(this, (TextureView)destination, srcRegion, dstRegion, linearFilter);
|
_renderer.TextureCopy.Copy(this, (TextureView)destination, srcRegion, dstRegion, linearFilter);
|
||||||
}
|
}
|
||||||
|
|
||||||
public unsafe ReadOnlySpan<byte> GetData()
|
public unsafe PinnedSpan<byte> GetData()
|
||||||
{
|
{
|
||||||
int size = 0;
|
int size = 0;
|
||||||
int levels = Info.GetLevelsClamped();
|
int levels = Info.GetLevelsClamped();
|
||||||
@@ -196,16 +196,16 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
|||||||
data = FormatConverter.ConvertD24S8ToS8D24(data);
|
data = FormatConverter.ConvertD24S8ToS8D24(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
return data;
|
return PinnedSpan<byte>.UnsafeFromSpan(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
public unsafe ReadOnlySpan<byte> GetData(int layer, int level)
|
public unsafe PinnedSpan<byte> GetData(int layer, int level)
|
||||||
{
|
{
|
||||||
int size = Info.GetMipSize(level);
|
int size = Info.GetMipSize(level);
|
||||||
|
|
||||||
if (HwCapabilities.UsePersistentBufferForFlush)
|
if (HwCapabilities.UsePersistentBufferForFlush)
|
||||||
{
|
{
|
||||||
return _renderer.PersistentBuffers.Default.GetTextureData(this, size, layer, level);
|
return PinnedSpan<byte>.UnsafeFromSpan(_renderer.PersistentBuffers.Default.GetTextureData(this, size, layer, level));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -213,7 +213,7 @@ namespace Ryujinx.Graphics.OpenGL.Image
|
|||||||
|
|
||||||
int offset = WriteTo2D(target, layer, level);
|
int offset = WriteTo2D(target, layer, level);
|
||||||
|
|
||||||
return new ReadOnlySpan<byte>(target.ToPointer(), size).Slice(offset);
|
return new PinnedSpan<byte>((byte*)target.ToPointer() + offset, size);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -57,7 +57,7 @@ namespace Ryujinx.Graphics.OpenGL
|
|||||||
ResourcePool = new ResourcePool();
|
ResourcePool = new ResourcePool();
|
||||||
}
|
}
|
||||||
|
|
||||||
public BufferHandle CreateBuffer(int size)
|
public BufferHandle CreateBuffer(int size, BufferHandle storageHint)
|
||||||
{
|
{
|
||||||
BufferCount++;
|
BufferCount++;
|
||||||
|
|
||||||
@@ -96,7 +96,7 @@ namespace Ryujinx.Graphics.OpenGL
|
|||||||
return new HardwareInfo(GpuVendor, GpuRenderer);
|
return new HardwareInfo(GpuVendor, GpuRenderer);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ReadOnlySpan<byte> GetBufferData(BufferHandle buffer, int offset, int size)
|
public PinnedSpan<byte> GetBufferData(BufferHandle buffer, int offset, int size)
|
||||||
{
|
{
|
||||||
return Buffer.GetData(this, buffer, offset, size);
|
return Buffer.GetData(this, buffer, offset, size);
|
||||||
}
|
}
|
||||||
@@ -178,7 +178,7 @@ namespace Ryujinx.Graphics.OpenGL
|
|||||||
}
|
}
|
||||||
|
|
||||||
_pipeline.Initialize(this);
|
_pipeline.Initialize(this);
|
||||||
_counters.Initialize();
|
_counters.Initialize(_pipeline);
|
||||||
|
|
||||||
// This is required to disable [0, 1] clamping for SNorm outputs on compatibility profiles.
|
// This is required to disable [0, 1] clamping for SNorm outputs on compatibility profiles.
|
||||||
// This call is expected to fail if we're running with a core profile,
|
// This call is expected to fail if we're running with a core profile,
|
||||||
|
@@ -773,6 +773,16 @@ namespace Ryujinx.Graphics.OpenGL
|
|||||||
_tfEnabled = false;
|
_tfEnabled = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public double GetCounterDivisor(CounterType type)
|
||||||
|
{
|
||||||
|
if (type == CounterType.SamplesPassed)
|
||||||
|
{
|
||||||
|
return _renderScale[0].X * _renderScale[0].X;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
public void SetAlphaTest(bool enable, float reference, CompareOp op)
|
public void SetAlphaTest(bool enable, float reference, CompareOp op)
|
||||||
{
|
{
|
||||||
if (!enable)
|
if (!enable)
|
||||||
|
@@ -10,6 +10,7 @@ namespace Ryujinx.Graphics.OpenGL.Queries
|
|||||||
{
|
{
|
||||||
private const int MaxQueryRetries = 5000;
|
private const int MaxQueryRetries = 5000;
|
||||||
private const long DefaultValue = -1;
|
private const long DefaultValue = -1;
|
||||||
|
private const ulong HighMask = 0xFFFFFFFF00000000;
|
||||||
|
|
||||||
public int Query { get; }
|
public int Query { get; }
|
||||||
|
|
||||||
@@ -63,11 +64,17 @@ namespace Ryujinx.Graphics.OpenGL.Queries
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool WaitingForValue(long data)
|
||||||
|
{
|
||||||
|
return data == DefaultValue ||
|
||||||
|
((ulong)data & HighMask) == (unchecked((ulong)DefaultValue) & HighMask);
|
||||||
|
}
|
||||||
|
|
||||||
public bool TryGetResult(out long result)
|
public bool TryGetResult(out long result)
|
||||||
{
|
{
|
||||||
result = Marshal.ReadInt64(_bufferMap);
|
result = Marshal.ReadInt64(_bufferMap);
|
||||||
|
|
||||||
return result != DefaultValue;
|
return !WaitingForValue(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
public long AwaitResult(AutoResetEvent wakeSignal = null)
|
public long AwaitResult(AutoResetEvent wakeSignal = null)
|
||||||
@@ -76,7 +83,7 @@ namespace Ryujinx.Graphics.OpenGL.Queries
|
|||||||
|
|
||||||
if (wakeSignal == null)
|
if (wakeSignal == null)
|
||||||
{
|
{
|
||||||
while (data == DefaultValue)
|
while (WaitingForValue(data))
|
||||||
{
|
{
|
||||||
data = Marshal.ReadInt64(_bufferMap);
|
data = Marshal.ReadInt64(_bufferMap);
|
||||||
}
|
}
|
||||||
@@ -84,10 +91,10 @@ namespace Ryujinx.Graphics.OpenGL.Queries
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
int iterations = 0;
|
int iterations = 0;
|
||||||
while (data == DefaultValue && iterations++ < MaxQueryRetries)
|
while (WaitingForValue(data) && iterations++ < MaxQueryRetries)
|
||||||
{
|
{
|
||||||
data = Marshal.ReadInt64(_bufferMap);
|
data = Marshal.ReadInt64(_bufferMap);
|
||||||
if (data == DefaultValue)
|
if (WaitingForValue(data))
|
||||||
{
|
{
|
||||||
wakeSignal.WaitOne(1);
|
wakeSignal.WaitOne(1);
|
||||||
}
|
}
|
||||||
|
@@ -13,6 +13,8 @@ namespace Ryujinx.Graphics.OpenGL.Queries
|
|||||||
public CounterType Type { get; }
|
public CounterType Type { get; }
|
||||||
public bool Disposed { get; private set; }
|
public bool Disposed { get; private set; }
|
||||||
|
|
||||||
|
private readonly Pipeline _pipeline;
|
||||||
|
|
||||||
private Queue<CounterQueueEvent> _events = new Queue<CounterQueueEvent>();
|
private Queue<CounterQueueEvent> _events = new Queue<CounterQueueEvent>();
|
||||||
private CounterQueueEvent _current;
|
private CounterQueueEvent _current;
|
||||||
|
|
||||||
@@ -28,10 +30,12 @@ namespace Ryujinx.Graphics.OpenGL.Queries
|
|||||||
|
|
||||||
private Thread _consumerThread;
|
private Thread _consumerThread;
|
||||||
|
|
||||||
internal CounterQueue(CounterType type)
|
internal CounterQueue(Pipeline pipeline, CounterType type)
|
||||||
{
|
{
|
||||||
Type = type;
|
Type = type;
|
||||||
|
|
||||||
|
_pipeline = pipeline;
|
||||||
|
|
||||||
QueryTarget glType = GetTarget(Type);
|
QueryTarget glType = GetTarget(Type);
|
||||||
|
|
||||||
_queryPool = new Queue<BufferedQuery>(QueryPoolInitialSize);
|
_queryPool = new Queue<BufferedQuery>(QueryPoolInitialSize);
|
||||||
@@ -119,7 +123,7 @@ namespace Ryujinx.Graphics.OpenGL.Queries
|
|||||||
_current.ReserveForHostAccess();
|
_current.ReserveForHostAccess();
|
||||||
}
|
}
|
||||||
|
|
||||||
_current.Complete(draws > 0);
|
_current.Complete(draws > 0, _pipeline.GetCounterDivisor(Type));
|
||||||
_events.Enqueue(_current);
|
_events.Enqueue(_current);
|
||||||
|
|
||||||
_current.OnResult += resultHandler;
|
_current.OnResult += resultHandler;
|
||||||
|
@@ -26,6 +26,7 @@ namespace Ryujinx.Graphics.OpenGL.Queries
|
|||||||
|
|
||||||
private object _lock = new object();
|
private object _lock = new object();
|
||||||
private ulong _result = ulong.MaxValue;
|
private ulong _result = ulong.MaxValue;
|
||||||
|
private double _divisor = 1f;
|
||||||
|
|
||||||
public CounterQueueEvent(CounterQueue queue, QueryTarget type, ulong drawIndex)
|
public CounterQueueEvent(CounterQueue queue, QueryTarget type, ulong drawIndex)
|
||||||
{
|
{
|
||||||
@@ -45,9 +46,11 @@ namespace Ryujinx.Graphics.OpenGL.Queries
|
|||||||
ClearCounter = true;
|
ClearCounter = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void Complete(bool withResult)
|
internal void Complete(bool withResult, double divisor)
|
||||||
{
|
{
|
||||||
_counter.End(withResult);
|
_counter.End(withResult);
|
||||||
|
|
||||||
|
_divisor = divisor;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal bool TryConsume(ref ulong result, bool block, AutoResetEvent wakeSignal = null)
|
internal bool TryConsume(ref ulong result, bool block, AutoResetEvent wakeSignal = null)
|
||||||
@@ -78,7 +81,7 @@ namespace Ryujinx.Graphics.OpenGL.Queries
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
result += (ulong)queryResult;
|
result += _divisor == 1 ? (ulong)queryResult : (ulong)Math.Ceiling(queryResult / _divisor);
|
||||||
|
|
||||||
_result = result;
|
_result = result;
|
||||||
|
|
||||||
|
@@ -14,12 +14,12 @@ namespace Ryujinx.Graphics.OpenGL.Queries
|
|||||||
_counterQueues = new CounterQueue[count];
|
_counterQueues = new CounterQueue[count];
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Initialize()
|
public void Initialize(Pipeline pipeline)
|
||||||
{
|
{
|
||||||
for (int index = 0; index < _counterQueues.Length; index++)
|
for (int index = 0; index < _counterQueues.Length; index++)
|
||||||
{
|
{
|
||||||
CounterType type = (CounterType)index;
|
CounterType type = (CounterType)index;
|
||||||
_counterQueues[index] = new CounterQueue(type);
|
_counterQueues[index] = new CounterQueue(pipeline, type);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -112,7 +112,7 @@ namespace Ryujinx.Graphics.Texture
|
|||||||
int outSize = GetTextureSize(
|
int outSize = GetTextureSize(
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
depth,
|
sliceDepth,
|
||||||
levels,
|
levels,
|
||||||
layers,
|
layers,
|
||||||
blockWidth,
|
blockWidth,
|
||||||
|
12
Ryujinx.Graphics.Vulkan/BufferAllocationType.cs
Normal file
12
Ryujinx.Graphics.Vulkan/BufferAllocationType.cs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
namespace Ryujinx.Graphics.Vulkan
|
||||||
|
{
|
||||||
|
internal enum BufferAllocationType
|
||||||
|
{
|
||||||
|
Auto = 0,
|
||||||
|
|
||||||
|
HostMappedNoCache,
|
||||||
|
HostMapped,
|
||||||
|
DeviceLocal,
|
||||||
|
DeviceLocalMapped
|
||||||
|
}
|
||||||
|
}
|
@@ -1,7 +1,10 @@
|
|||||||
using Ryujinx.Graphics.GAL;
|
using Ryujinx.Common.Logging;
|
||||||
|
using Ryujinx.Graphics.GAL;
|
||||||
using Silk.NET.Vulkan;
|
using Silk.NET.Vulkan;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Threading;
|
||||||
using VkBuffer = Silk.NET.Vulkan.Buffer;
|
using VkBuffer = Silk.NET.Vulkan.Buffer;
|
||||||
using VkFormat = Silk.NET.Vulkan.Format;
|
using VkFormat = Silk.NET.Vulkan.Format;
|
||||||
|
|
||||||
@@ -11,6 +14,12 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
{
|
{
|
||||||
private const int MaxUpdateBufferSize = 0x10000;
|
private const int MaxUpdateBufferSize = 0x10000;
|
||||||
|
|
||||||
|
private const int SetCountThreshold = 100;
|
||||||
|
private const int WriteCountThreshold = 50;
|
||||||
|
private const int FlushCountThreshold = 5;
|
||||||
|
|
||||||
|
public const int DeviceLocalSizeThreshold = 256 * 1024; // 256kb
|
||||||
|
|
||||||
public const AccessFlags DefaultAccessFlags =
|
public const AccessFlags DefaultAccessFlags =
|
||||||
AccessFlags.IndirectCommandReadBit |
|
AccessFlags.IndirectCommandReadBit |
|
||||||
AccessFlags.ShaderReadBit |
|
AccessFlags.ShaderReadBit |
|
||||||
@@ -21,10 +30,10 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
|
|
||||||
private readonly VulkanRenderer _gd;
|
private readonly VulkanRenderer _gd;
|
||||||
private readonly Device _device;
|
private readonly Device _device;
|
||||||
private readonly MemoryAllocation _allocation;
|
private MemoryAllocation _allocation;
|
||||||
private readonly Auto<DisposableBuffer> _buffer;
|
private Auto<DisposableBuffer> _buffer;
|
||||||
private readonly Auto<MemoryAllocation> _allocationAuto;
|
private Auto<MemoryAllocation> _allocationAuto;
|
||||||
private readonly ulong _bufferHandle;
|
private ulong _bufferHandle;
|
||||||
|
|
||||||
private CacheByRange<BufferHolder> _cachedConvertedBuffers;
|
private CacheByRange<BufferHolder> _cachedConvertedBuffers;
|
||||||
|
|
||||||
@@ -32,11 +41,28 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
|
|
||||||
private IntPtr _map;
|
private IntPtr _map;
|
||||||
|
|
||||||
private readonly MultiFenceHolder _waitable;
|
private MultiFenceHolder _waitable;
|
||||||
|
|
||||||
private bool _lastAccessIsWrite;
|
private bool _lastAccessIsWrite;
|
||||||
|
|
||||||
public BufferHolder(VulkanRenderer gd, Device device, VkBuffer buffer, MemoryAllocation allocation, int size)
|
private BufferAllocationType _baseType;
|
||||||
|
private BufferAllocationType _currentType;
|
||||||
|
private bool _swapQueued;
|
||||||
|
|
||||||
|
public BufferAllocationType DesiredType { get; private set; }
|
||||||
|
|
||||||
|
private int _setCount;
|
||||||
|
private int _writeCount;
|
||||||
|
private int _flushCount;
|
||||||
|
private int _flushTemp;
|
||||||
|
|
||||||
|
private ReaderWriterLock _flushLock;
|
||||||
|
private FenceHolder _flushFence;
|
||||||
|
private int _flushWaiting;
|
||||||
|
|
||||||
|
private List<Action> _swapActions;
|
||||||
|
|
||||||
|
public BufferHolder(VulkanRenderer gd, Device device, VkBuffer buffer, MemoryAllocation allocation, int size, BufferAllocationType type, BufferAllocationType currentType)
|
||||||
{
|
{
|
||||||
_gd = gd;
|
_gd = gd;
|
||||||
_device = device;
|
_device = device;
|
||||||
@@ -47,9 +73,153 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
_bufferHandle = buffer.Handle;
|
_bufferHandle = buffer.Handle;
|
||||||
Size = size;
|
Size = size;
|
||||||
_map = allocation.HostPointer;
|
_map = allocation.HostPointer;
|
||||||
|
|
||||||
|
_baseType = type;
|
||||||
|
_currentType = currentType;
|
||||||
|
DesiredType = currentType;
|
||||||
|
|
||||||
|
_flushLock = new ReaderWriterLock();
|
||||||
}
|
}
|
||||||
|
|
||||||
public unsafe Auto<DisposableBufferView> CreateView(VkFormat format, int offset, int size)
|
public bool TryBackingSwap(ref CommandBufferScoped? cbs)
|
||||||
|
{
|
||||||
|
if (_swapQueued && DesiredType != _currentType)
|
||||||
|
{
|
||||||
|
// Only swap if the buffer is not used in any queued command buffer.
|
||||||
|
bool isRented = _buffer.HasRentedCommandBufferDependency(_gd.CommandBufferPool);
|
||||||
|
|
||||||
|
if (!isRented && _gd.CommandBufferPool.OwnedByCurrentThread && !_flushLock.IsReaderLockHeld)
|
||||||
|
{
|
||||||
|
var currentAllocation = _allocationAuto;
|
||||||
|
var currentBuffer = _buffer;
|
||||||
|
IntPtr currentMap = _map;
|
||||||
|
|
||||||
|
(VkBuffer buffer, MemoryAllocation allocation, BufferAllocationType resultType) = _gd.BufferManager.CreateBacking(_gd, Size, DesiredType, false, _currentType);
|
||||||
|
|
||||||
|
if (buffer.Handle != 0)
|
||||||
|
{
|
||||||
|
_flushLock.AcquireWriterLock(Timeout.Infinite);
|
||||||
|
|
||||||
|
ClearFlushFence();
|
||||||
|
|
||||||
|
_waitable = new MultiFenceHolder(Size);
|
||||||
|
|
||||||
|
_allocation = allocation;
|
||||||
|
_allocationAuto = new Auto<MemoryAllocation>(allocation);
|
||||||
|
_buffer = new Auto<DisposableBuffer>(new DisposableBuffer(_gd.Api, _device, buffer), _waitable, _allocationAuto);
|
||||||
|
_bufferHandle = buffer.Handle;
|
||||||
|
_map = allocation.HostPointer;
|
||||||
|
|
||||||
|
if (_map != IntPtr.Zero && currentMap != IntPtr.Zero)
|
||||||
|
{
|
||||||
|
// Copy data directly. Readbacks don't have to wait if this is done.
|
||||||
|
|
||||||
|
unsafe
|
||||||
|
{
|
||||||
|
new Span<byte>((void*)currentMap, Size).CopyTo(new Span<byte>((void*)_map, Size));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (cbs == null)
|
||||||
|
{
|
||||||
|
cbs = _gd.CommandBufferPool.Rent();
|
||||||
|
}
|
||||||
|
|
||||||
|
CommandBufferScoped cbsV = cbs.Value;
|
||||||
|
|
||||||
|
Copy(_gd, cbsV, currentBuffer, _buffer, 0, 0, Size);
|
||||||
|
|
||||||
|
// Need to wait for the data to reach the new buffer before data can be flushed.
|
||||||
|
|
||||||
|
_flushFence = _gd.CommandBufferPool.GetFence(cbsV.CommandBufferIndex);
|
||||||
|
_flushFence.Get();
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger.Debug?.PrintMsg(LogClass.Gpu, $"Converted {Size} buffer {_currentType} to {resultType}");
|
||||||
|
|
||||||
|
_currentType = resultType;
|
||||||
|
|
||||||
|
if (_swapActions != null)
|
||||||
|
{
|
||||||
|
foreach (var action in _swapActions)
|
||||||
|
{
|
||||||
|
action();
|
||||||
|
}
|
||||||
|
|
||||||
|
_swapActions.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
currentBuffer.Dispose();
|
||||||
|
currentAllocation.Dispose();
|
||||||
|
|
||||||
|
_gd.PipelineInternal.SwapBuffer(currentBuffer, _buffer);
|
||||||
|
|
||||||
|
_flushLock.ReleaseWriterLock();
|
||||||
|
}
|
||||||
|
|
||||||
|
_swapQueued = false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_swapQueued = false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ConsiderBackingSwap()
|
||||||
|
{
|
||||||
|
if (_baseType == BufferAllocationType.Auto)
|
||||||
|
{
|
||||||
|
if (_writeCount >= WriteCountThreshold || _setCount >= SetCountThreshold || _flushCount >= FlushCountThreshold)
|
||||||
|
{
|
||||||
|
if (_flushCount > 0 || _flushTemp-- > 0)
|
||||||
|
{
|
||||||
|
// Buffers that flush should ideally be mapped in host address space for easy copies.
|
||||||
|
// If the buffer is large it will do better on GPU memory, as there will be more writes than data flushes (typically individual pages).
|
||||||
|
// If it is small, then it's likely most of the buffer will be flushed so we want it on host memory, as access is cached.
|
||||||
|
DesiredType = Size > DeviceLocalSizeThreshold ? BufferAllocationType.DeviceLocalMapped : BufferAllocationType.HostMapped;
|
||||||
|
|
||||||
|
// It's harder for a buffer that is flushed to revert to another type of mapping.
|
||||||
|
if (_flushCount > 0)
|
||||||
|
{
|
||||||
|
_flushTemp = 1000;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (_writeCount >= WriteCountThreshold)
|
||||||
|
{
|
||||||
|
// Buffers that are written often should ideally be in the device local heap. (Storage buffers)
|
||||||
|
DesiredType = BufferAllocationType.DeviceLocal;
|
||||||
|
}
|
||||||
|
else if (_setCount > SetCountThreshold)
|
||||||
|
{
|
||||||
|
// Buffers that have their data set often should ideally be host mapped. (Constant buffers)
|
||||||
|
DesiredType = BufferAllocationType.HostMapped;
|
||||||
|
}
|
||||||
|
|
||||||
|
_flushCount = 0;
|
||||||
|
_writeCount = 0;
|
||||||
|
_setCount = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_swapQueued && DesiredType != _currentType)
|
||||||
|
{
|
||||||
|
_swapQueued = true;
|
||||||
|
|
||||||
|
_gd.PipelineInternal.AddBackingSwap(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public unsafe Auto<DisposableBufferView> CreateView(VkFormat format, int offset, int size, Action invalidateView)
|
||||||
{
|
{
|
||||||
var bufferViewCreateInfo = new BufferViewCreateInfo()
|
var bufferViewCreateInfo = new BufferViewCreateInfo()
|
||||||
{
|
{
|
||||||
@@ -62,9 +232,19 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
|
|
||||||
_gd.Api.CreateBufferView(_device, bufferViewCreateInfo, null, out var bufferView).ThrowOnError();
|
_gd.Api.CreateBufferView(_device, bufferViewCreateInfo, null, out var bufferView).ThrowOnError();
|
||||||
|
|
||||||
|
(_swapActions ??= new List<Action>()).Add(invalidateView);
|
||||||
|
|
||||||
return new Auto<DisposableBufferView>(new DisposableBufferView(_gd.Api, _device, bufferView), _waitable, _buffer);
|
return new Auto<DisposableBufferView>(new DisposableBufferView(_gd.Api, _device, bufferView), _waitable, _buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void InheritMetrics(BufferHolder other)
|
||||||
|
{
|
||||||
|
_setCount = other._setCount;
|
||||||
|
_writeCount = other._writeCount;
|
||||||
|
_flushCount = other._flushCount;
|
||||||
|
_flushTemp = other._flushTemp;
|
||||||
|
}
|
||||||
|
|
||||||
public unsafe void InsertBarrier(CommandBuffer commandBuffer, bool isWrite)
|
public unsafe void InsertBarrier(CommandBuffer commandBuffer, bool isWrite)
|
||||||
{
|
{
|
||||||
// If the last access is write, we always need a barrier to be sure we will read or modify
|
// If the last access is write, we always need a barrier to be sure we will read or modify
|
||||||
@@ -104,12 +284,22 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
return _buffer;
|
return _buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Auto<DisposableBuffer> GetBuffer(CommandBuffer commandBuffer, bool isWrite = false)
|
public Auto<DisposableBuffer> GetBuffer(CommandBuffer commandBuffer, bool isWrite = false, bool isSSBO = false)
|
||||||
{
|
{
|
||||||
if (isWrite)
|
if (isWrite)
|
||||||
{
|
{
|
||||||
|
_writeCount++;
|
||||||
|
|
||||||
SignalWrite(0, Size);
|
SignalWrite(0, Size);
|
||||||
}
|
}
|
||||||
|
else if (isSSBO)
|
||||||
|
{
|
||||||
|
// Always consider SSBO access for swapping to device local memory.
|
||||||
|
|
||||||
|
_writeCount++;
|
||||||
|
|
||||||
|
ConsiderBackingSwap();
|
||||||
|
}
|
||||||
|
|
||||||
return _buffer;
|
return _buffer;
|
||||||
}
|
}
|
||||||
@@ -118,6 +308,8 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
{
|
{
|
||||||
if (isWrite)
|
if (isWrite)
|
||||||
{
|
{
|
||||||
|
_writeCount++;
|
||||||
|
|
||||||
SignalWrite(offset, size);
|
SignalWrite(offset, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -126,6 +318,8 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
|
|
||||||
public void SignalWrite(int offset, int size)
|
public void SignalWrite(int offset, int size)
|
||||||
{
|
{
|
||||||
|
ConsiderBackingSwap();
|
||||||
|
|
||||||
if (offset == 0 && size == Size)
|
if (offset == 0 && size == Size)
|
||||||
{
|
{
|
||||||
_cachedConvertedBuffers.Clear();
|
_cachedConvertedBuffers.Clear();
|
||||||
@@ -147,11 +341,76 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
return _map;
|
return _map;
|
||||||
}
|
}
|
||||||
|
|
||||||
public unsafe ReadOnlySpan<byte> GetData(int offset, int size)
|
private void ClearFlushFence()
|
||||||
{
|
{
|
||||||
|
// Asusmes _flushLock is held as writer.
|
||||||
|
|
||||||
|
if (_flushFence != null)
|
||||||
|
{
|
||||||
|
if (_flushWaiting == 0)
|
||||||
|
{
|
||||||
|
_flushFence.Put();
|
||||||
|
}
|
||||||
|
|
||||||
|
_flushFence = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WaitForFlushFence()
|
||||||
|
{
|
||||||
|
// Assumes the _flushLock is held as reader, returns in same state.
|
||||||
|
|
||||||
|
if (_flushFence != null)
|
||||||
|
{
|
||||||
|
// If storage has changed, make sure the fence has been reached so that the data is in place.
|
||||||
|
|
||||||
|
var cookie = _flushLock.UpgradeToWriterLock(Timeout.Infinite);
|
||||||
|
|
||||||
|
if (_flushFence != null)
|
||||||
|
{
|
||||||
|
var fence = _flushFence;
|
||||||
|
Interlocked.Increment(ref _flushWaiting);
|
||||||
|
|
||||||
|
// Don't wait in the lock.
|
||||||
|
|
||||||
|
var restoreCookie = _flushLock.ReleaseLock();
|
||||||
|
|
||||||
|
fence.Wait();
|
||||||
|
|
||||||
|
_flushLock.RestoreLock(ref restoreCookie);
|
||||||
|
|
||||||
|
if (Interlocked.Decrement(ref _flushWaiting) == 0)
|
||||||
|
{
|
||||||
|
fence.Put();
|
||||||
|
}
|
||||||
|
|
||||||
|
_flushFence = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
_flushLock.DowngradeFromWriterLock(ref cookie);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public unsafe PinnedSpan<byte> GetData(int offset, int size)
|
||||||
|
{
|
||||||
|
_flushLock.AcquireReaderLock(Timeout.Infinite);
|
||||||
|
|
||||||
|
WaitForFlushFence();
|
||||||
|
|
||||||
|
_flushCount++;
|
||||||
|
|
||||||
|
Span<byte> result;
|
||||||
|
|
||||||
if (_map != IntPtr.Zero)
|
if (_map != IntPtr.Zero)
|
||||||
{
|
{
|
||||||
return GetDataStorage(offset, size);
|
result = GetDataStorage(offset, size);
|
||||||
|
|
||||||
|
// Need to be careful here, the buffer can't be unmapped while the data is being used.
|
||||||
|
_buffer.IncrementReferenceCount();
|
||||||
|
|
||||||
|
_flushLock.ReleaseReaderLock();
|
||||||
|
|
||||||
|
return PinnedSpan<byte>.UnsafeFromSpan(result, _buffer.DecrementReferenceCount);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -161,12 +420,17 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
{
|
{
|
||||||
_gd.FlushAllCommands();
|
_gd.FlushAllCommands();
|
||||||
|
|
||||||
return resource.GetFlushBuffer().GetBufferData(_gd.CommandBufferPool, this, offset, size);
|
result = resource.GetFlushBuffer().GetBufferData(_gd.CommandBufferPool, this, offset, size);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
return resource.GetFlushBuffer().GetBufferData(resource.GetPool(), this, offset, size);
|
result = resource.GetFlushBuffer().GetBufferData(resource.GetPool(), this, offset, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_flushLock.ReleaseReaderLock();
|
||||||
|
|
||||||
|
// Flush buffer is pinned until the next GetBufferData on the thread, which is fine for current uses.
|
||||||
|
return PinnedSpan<byte>.UnsafeFromSpan(result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -190,6 +454,8 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_setCount++;
|
||||||
|
|
||||||
if (_map != IntPtr.Zero)
|
if (_map != IntPtr.Zero)
|
||||||
{
|
{
|
||||||
// If persistently mapped, set the data directly if the buffer is not currently in use.
|
// If persistently mapped, set the data directly if the buffer is not currently in use.
|
||||||
@@ -268,6 +534,8 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
|
|
||||||
var dstBuffer = GetBuffer(cbs.CommandBuffer, dstOffset, data.Length, true).Get(cbs, dstOffset, data.Length).Value;
|
var dstBuffer = GetBuffer(cbs.CommandBuffer, dstOffset, data.Length, true).Get(cbs, dstOffset, data.Length).Value;
|
||||||
|
|
||||||
|
_writeCount--;
|
||||||
|
|
||||||
InsertBufferBarrier(
|
InsertBufferBarrier(
|
||||||
_gd,
|
_gd,
|
||||||
cbs.CommandBuffer,
|
cbs.CommandBuffer,
|
||||||
@@ -502,11 +770,19 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
|
_swapQueued = false;
|
||||||
|
|
||||||
_gd.PipelineInternal?.FlushCommandsIfWeightExceeding(_buffer, (ulong)Size);
|
_gd.PipelineInternal?.FlushCommandsIfWeightExceeding(_buffer, (ulong)Size);
|
||||||
|
|
||||||
_buffer.Dispose();
|
_buffer.Dispose();
|
||||||
_allocationAuto.Dispose();
|
_allocationAuto.Dispose();
|
||||||
_cachedConvertedBuffers.Dispose();
|
_cachedConvertedBuffers.Dispose();
|
||||||
|
|
||||||
|
_flushLock.AcquireWriterLock(Timeout.Infinite);
|
||||||
|
|
||||||
|
ClearFlushFence();
|
||||||
|
|
||||||
|
_flushLock.ReleaseWriterLock();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -4,6 +4,7 @@ using System;
|
|||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using VkFormat = Silk.NET.Vulkan.Format;
|
using VkFormat = Silk.NET.Vulkan.Format;
|
||||||
|
using VkBuffer = Silk.NET.Vulkan.Buffer;
|
||||||
|
|
||||||
namespace Ryujinx.Graphics.Vulkan
|
namespace Ryujinx.Graphics.Vulkan
|
||||||
{
|
{
|
||||||
@@ -16,17 +17,17 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
|
|
||||||
// Some drivers don't expose a "HostCached" memory type,
|
// Some drivers don't expose a "HostCached" memory type,
|
||||||
// so we need those alternative flags for the allocation to succeed there.
|
// so we need those alternative flags for the allocation to succeed there.
|
||||||
private const MemoryPropertyFlags DefaultBufferMemoryAltFlags =
|
private const MemoryPropertyFlags DefaultBufferMemoryNoCacheFlags =
|
||||||
MemoryPropertyFlags.HostVisibleBit |
|
MemoryPropertyFlags.HostVisibleBit |
|
||||||
MemoryPropertyFlags.HostCoherentBit;
|
MemoryPropertyFlags.HostCoherentBit;
|
||||||
|
|
||||||
private const MemoryPropertyFlags DeviceLocalBufferMemoryFlags =
|
private const MemoryPropertyFlags DeviceLocalBufferMemoryFlags =
|
||||||
MemoryPropertyFlags.DeviceLocalBit;
|
MemoryPropertyFlags.DeviceLocalBit;
|
||||||
|
|
||||||
private const MemoryPropertyFlags FlushableDeviceLocalBufferMemoryFlags =
|
private const MemoryPropertyFlags DeviceLocalMappedBufferMemoryFlags =
|
||||||
|
MemoryPropertyFlags.DeviceLocalBit |
|
||||||
MemoryPropertyFlags.HostVisibleBit |
|
MemoryPropertyFlags.HostVisibleBit |
|
||||||
MemoryPropertyFlags.HostCoherentBit |
|
MemoryPropertyFlags.HostCoherentBit;
|
||||||
MemoryPropertyFlags.DeviceLocalBit;
|
|
||||||
|
|
||||||
private const BufferUsageFlags DefaultBufferUsageFlags =
|
private const BufferUsageFlags DefaultBufferUsageFlags =
|
||||||
BufferUsageFlags.TransferSrcBit |
|
BufferUsageFlags.TransferSrcBit |
|
||||||
@@ -54,14 +55,14 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
StagingBuffer = new StagingBuffer(gd, this);
|
StagingBuffer = new StagingBuffer(gd, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public BufferHandle CreateWithHandle(VulkanRenderer gd, int size, bool deviceLocal)
|
public BufferHandle CreateWithHandle(VulkanRenderer gd, int size, BufferAllocationType baseType = BufferAllocationType.HostMapped, BufferHandle storageHint = default)
|
||||||
{
|
{
|
||||||
return CreateWithHandle(gd, size, deviceLocal, out _);
|
return CreateWithHandle(gd, size, out _, baseType, storageHint);
|
||||||
}
|
}
|
||||||
|
|
||||||
public BufferHandle CreateWithHandle(VulkanRenderer gd, int size, bool deviceLocal, out BufferHolder holder)
|
public BufferHandle CreateWithHandle(VulkanRenderer gd, int size, out BufferHolder holder, BufferAllocationType baseType = BufferAllocationType.HostMapped, BufferHandle storageHint = default)
|
||||||
{
|
{
|
||||||
holder = Create(gd, size, deviceLocal: deviceLocal);
|
holder = Create(gd, size, baseType: baseType, storageHint: storageHint);
|
||||||
if (holder == null)
|
if (holder == null)
|
||||||
{
|
{
|
||||||
return BufferHandle.Null;
|
return BufferHandle.Null;
|
||||||
@@ -74,7 +75,12 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
return Unsafe.As<ulong, BufferHandle>(ref handle64);
|
return Unsafe.As<ulong, BufferHandle>(ref handle64);
|
||||||
}
|
}
|
||||||
|
|
||||||
public unsafe BufferHolder Create(VulkanRenderer gd, int size, bool forConditionalRendering = false, bool deviceLocal = false)
|
public unsafe (VkBuffer buffer, MemoryAllocation allocation, BufferAllocationType resultType) CreateBacking(
|
||||||
|
VulkanRenderer gd,
|
||||||
|
int size,
|
||||||
|
BufferAllocationType type,
|
||||||
|
bool forConditionalRendering = false,
|
||||||
|
BufferAllocationType fallbackType = BufferAllocationType.Auto)
|
||||||
{
|
{
|
||||||
var usage = DefaultBufferUsageFlags;
|
var usage = DefaultBufferUsageFlags;
|
||||||
|
|
||||||
@@ -98,48 +104,106 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
gd.Api.CreateBuffer(_device, in bufferCreateInfo, null, out var buffer).ThrowOnError();
|
gd.Api.CreateBuffer(_device, in bufferCreateInfo, null, out var buffer).ThrowOnError();
|
||||||
gd.Api.GetBufferMemoryRequirements(_device, buffer, out var requirements);
|
gd.Api.GetBufferMemoryRequirements(_device, buffer, out var requirements);
|
||||||
|
|
||||||
MemoryPropertyFlags allocateFlags;
|
MemoryAllocation allocation;
|
||||||
MemoryPropertyFlags allocateFlagsAlt;
|
|
||||||
|
|
||||||
if (deviceLocal)
|
do
|
||||||
{
|
{
|
||||||
allocateFlags = DeviceLocalBufferMemoryFlags;
|
var allocateFlags = type switch
|
||||||
allocateFlagsAlt = DeviceLocalBufferMemoryFlags;
|
{
|
||||||
}
|
BufferAllocationType.HostMappedNoCache => DefaultBufferMemoryNoCacheFlags,
|
||||||
else
|
BufferAllocationType.HostMapped => DefaultBufferMemoryFlags,
|
||||||
{
|
BufferAllocationType.DeviceLocal => DeviceLocalBufferMemoryFlags,
|
||||||
allocateFlags = DefaultBufferMemoryFlags;
|
BufferAllocationType.DeviceLocalMapped => DeviceLocalMappedBufferMemoryFlags,
|
||||||
allocateFlagsAlt = DefaultBufferMemoryAltFlags;
|
_ => DefaultBufferMemoryFlags
|
||||||
}
|
};
|
||||||
|
|
||||||
var allocation = gd.MemoryAllocator.AllocateDeviceMemory(requirements, allocateFlags, allocateFlagsAlt);
|
// If an allocation with this memory type fails, fall back to the previous one.
|
||||||
|
try
|
||||||
|
{
|
||||||
|
allocation = gd.MemoryAllocator.AllocateDeviceMemory(requirements, allocateFlags, true);
|
||||||
|
}
|
||||||
|
catch (VulkanException)
|
||||||
|
{
|
||||||
|
allocation = default;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while (allocation.Memory.Handle == 0 && (--type != fallbackType));
|
||||||
|
|
||||||
if (allocation.Memory.Handle == 0UL)
|
if (allocation.Memory.Handle == 0UL)
|
||||||
{
|
{
|
||||||
gd.Api.DestroyBuffer(_device, buffer, null);
|
gd.Api.DestroyBuffer(_device, buffer, null);
|
||||||
return null;
|
return default;
|
||||||
}
|
}
|
||||||
|
|
||||||
gd.Api.BindBufferMemory(_device, buffer, allocation.Memory, allocation.Offset);
|
gd.Api.BindBufferMemory(_device, buffer, allocation.Memory, allocation.Offset);
|
||||||
|
|
||||||
return new BufferHolder(gd, _device, buffer, allocation, size);
|
return (buffer, allocation, type);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Auto<DisposableBufferView> CreateView(BufferHandle handle, VkFormat format, int offset, int size)
|
public unsafe BufferHolder Create(
|
||||||
|
VulkanRenderer gd,
|
||||||
|
int size,
|
||||||
|
bool forConditionalRendering = false,
|
||||||
|
BufferAllocationType baseType = BufferAllocationType.HostMapped,
|
||||||
|
BufferHandle storageHint = default)
|
||||||
{
|
{
|
||||||
if (TryGetBuffer(handle, out var holder))
|
BufferAllocationType type = baseType;
|
||||||
|
BufferHolder storageHintHolder = null;
|
||||||
|
|
||||||
|
if (baseType == BufferAllocationType.Auto)
|
||||||
{
|
{
|
||||||
return holder.CreateView(format, offset, size);
|
if (gd.IsSharedMemory)
|
||||||
|
{
|
||||||
|
baseType = BufferAllocationType.HostMapped;
|
||||||
|
type = baseType;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
type = size >= BufferHolder.DeviceLocalSizeThreshold ? BufferAllocationType.DeviceLocal : BufferAllocationType.HostMapped;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (storageHint != BufferHandle.Null)
|
||||||
|
{
|
||||||
|
if (TryGetBuffer(storageHint, out storageHintHolder))
|
||||||
|
{
|
||||||
|
type = storageHintHolder.DesiredType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
(VkBuffer buffer, MemoryAllocation allocation, BufferAllocationType resultType) =
|
||||||
|
CreateBacking(gd, size, type, forConditionalRendering);
|
||||||
|
|
||||||
|
if (buffer.Handle != 0)
|
||||||
|
{
|
||||||
|
var holder = new BufferHolder(gd, _device, buffer, allocation, size, baseType, resultType);
|
||||||
|
|
||||||
|
if (storageHintHolder != null)
|
||||||
|
{
|
||||||
|
holder.InheritMetrics(storageHintHolder);
|
||||||
|
}
|
||||||
|
|
||||||
|
return holder;
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Auto<DisposableBuffer> GetBuffer(CommandBuffer commandBuffer, BufferHandle handle, bool isWrite)
|
public Auto<DisposableBufferView> CreateView(BufferHandle handle, VkFormat format, int offset, int size, Action invalidateView)
|
||||||
{
|
{
|
||||||
if (TryGetBuffer(handle, out var holder))
|
if (TryGetBuffer(handle, out var holder))
|
||||||
{
|
{
|
||||||
return holder.GetBuffer(commandBuffer, isWrite);
|
return holder.CreateView(format, offset, size, invalidateView);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Auto<DisposableBuffer> GetBuffer(CommandBuffer commandBuffer, BufferHandle handle, bool isWrite, bool isSSBO = false)
|
||||||
|
{
|
||||||
|
if (TryGetBuffer(handle, out var holder))
|
||||||
|
{
|
||||||
|
return holder.GetBuffer(commandBuffer, isWrite, isSSBO);
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
@@ -332,14 +396,14 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ReadOnlySpan<byte> GetData(BufferHandle handle, int offset, int size)
|
public PinnedSpan<byte> GetData(BufferHandle handle, int offset, int size)
|
||||||
{
|
{
|
||||||
if (TryGetBuffer(handle, out var holder))
|
if (TryGetBuffer(handle, out var holder))
|
||||||
{
|
{
|
||||||
return holder.GetData(offset, size);
|
return holder.GetData(offset, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
return ReadOnlySpan<byte>.Empty;
|
return new PinnedSpan<byte>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetData<T>(BufferHandle handle, int offset, ReadOnlySpan<T> data) where T : unmanaged
|
public void SetData<T>(BufferHandle handle, int offset, ReadOnlySpan<T> data) where T : unmanaged
|
||||||
|
@@ -2,14 +2,14 @@
|
|||||||
|
|
||||||
namespace Ryujinx.Graphics.Vulkan
|
namespace Ryujinx.Graphics.Vulkan
|
||||||
{
|
{
|
||||||
readonly struct BufferState : IDisposable
|
struct BufferState : IDisposable
|
||||||
{
|
{
|
||||||
public static BufferState Null => new BufferState(null, 0, 0);
|
public static BufferState Null => new BufferState(null, 0, 0);
|
||||||
|
|
||||||
private readonly int _offset;
|
private readonly int _offset;
|
||||||
private readonly int _size;
|
private readonly int _size;
|
||||||
|
|
||||||
private readonly Auto<DisposableBuffer> _buffer;
|
private Auto<DisposableBuffer> _buffer;
|
||||||
|
|
||||||
public BufferState(Auto<DisposableBuffer> buffer, int offset, int size)
|
public BufferState(Auto<DisposableBuffer> buffer, int offset, int size)
|
||||||
{
|
{
|
||||||
@@ -29,6 +29,17 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Swap(Auto<DisposableBuffer> from, Auto<DisposableBuffer> to)
|
||||||
|
{
|
||||||
|
if (_buffer == from)
|
||||||
|
{
|
||||||
|
_buffer.DecrementReferenceCount();
|
||||||
|
to.IncrementReferenceCount();
|
||||||
|
|
||||||
|
_buffer = to;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
_buffer?.DecrementReferenceCount();
|
_buffer?.DecrementReferenceCount();
|
||||||
|
@@ -94,7 +94,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
// If null descriptors are not supported, we need to pass the handle of a dummy buffer on unused bindings.
|
// If null descriptors are not supported, we need to pass the handle of a dummy buffer on unused bindings.
|
||||||
_dummyBuffer = gd.BufferManager.Create(gd, 0x10000, forConditionalRendering: false, deviceLocal: true);
|
_dummyBuffer = gd.BufferManager.Create(gd, 0x10000, forConditionalRendering: false, baseType: BufferAllocationType.DeviceLocal);
|
||||||
}
|
}
|
||||||
|
|
||||||
_dummyTexture = gd.CreateTextureView(new TextureCreateInfo(
|
_dummyTexture = gd.CreateTextureView(new TextureCreateInfo(
|
||||||
@@ -178,7 +178,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
var buffer = assignment.Range;
|
var buffer = assignment.Range;
|
||||||
int index = assignment.Binding;
|
int index = assignment.Binding;
|
||||||
|
|
||||||
Auto<DisposableBuffer> vkBuffer = _gd.BufferManager.GetBuffer(commandBuffer, buffer.Handle, false);
|
Auto<DisposableBuffer> vkBuffer = _gd.BufferManager.GetBuffer(commandBuffer, buffer.Handle, false, isSSBO: true);
|
||||||
ref Auto<DisposableBuffer> currentVkBuffer = ref _storageBufferRefs[index];
|
ref Auto<DisposableBuffer> currentVkBuffer = ref _storageBufferRefs[index];
|
||||||
|
|
||||||
DescriptorBufferInfo info = new DescriptorBufferInfo()
|
DescriptorBufferInfo info = new DescriptorBufferInfo()
|
||||||
@@ -640,6 +640,23 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
Array.Clear(_storageSet);
|
Array.Clear(_storageSet);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void SwapBuffer(Auto<DisposableBuffer>[] list, Auto<DisposableBuffer> from, Auto<DisposableBuffer> to)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < list.Length; i++)
|
||||||
|
{
|
||||||
|
if (list[i] == from)
|
||||||
|
{
|
||||||
|
list[i] = to;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SwapBuffer(Auto<DisposableBuffer> from, Auto<DisposableBuffer> to)
|
||||||
|
{
|
||||||
|
SwapBuffer(_uniformBufferRefs, from, to);
|
||||||
|
SwapBuffer(_storageBufferRefs, from, to);
|
||||||
|
}
|
||||||
|
|
||||||
protected virtual void Dispose(bool disposing)
|
protected virtual void Dispose(bool disposing)
|
||||||
{
|
{
|
||||||
if (disposing)
|
if (disposing)
|
||||||
|
@@ -156,11 +156,11 @@ namespace Ryujinx.Graphics.Vulkan.Effects
|
|||||||
};
|
};
|
||||||
|
|
||||||
int rangeSize = dimensionsBuffer.Length * sizeof(float);
|
int rangeSize = dimensionsBuffer.Length * sizeof(float);
|
||||||
var bufferHandle = _renderer.BufferManager.CreateWithHandle(_renderer, rangeSize, false);
|
var bufferHandle = _renderer.BufferManager.CreateWithHandle(_renderer, rangeSize);
|
||||||
_renderer.BufferManager.SetData(bufferHandle, 0, dimensionsBuffer);
|
_renderer.BufferManager.SetData(bufferHandle, 0, dimensionsBuffer);
|
||||||
|
|
||||||
ReadOnlySpan<float> sharpeningBuffer = stackalloc float[] { 1.5f - (Level * 0.01f * 1.5f)};
|
ReadOnlySpan<float> sharpeningBuffer = stackalloc float[] { 1.5f - (Level * 0.01f * 1.5f)};
|
||||||
var sharpeningBufferHandle = _renderer.BufferManager.CreateWithHandle(_renderer, sizeof(float), false);
|
var sharpeningBufferHandle = _renderer.BufferManager.CreateWithHandle(_renderer, sizeof(float));
|
||||||
_renderer.BufferManager.SetData(sharpeningBufferHandle, 0, sharpeningBuffer);
|
_renderer.BufferManager.SetData(sharpeningBufferHandle, 0, sharpeningBuffer);
|
||||||
|
|
||||||
int threadGroupWorkRegionDim = 16;
|
int threadGroupWorkRegionDim = 16;
|
||||||
|
@@ -87,7 +87,7 @@ namespace Ryujinx.Graphics.Vulkan.Effects
|
|||||||
|
|
||||||
ReadOnlySpan<float> resolutionBuffer = stackalloc float[] { view.Width, view.Height };
|
ReadOnlySpan<float> resolutionBuffer = stackalloc float[] { view.Width, view.Height };
|
||||||
int rangeSize = resolutionBuffer.Length * sizeof(float);
|
int rangeSize = resolutionBuffer.Length * sizeof(float);
|
||||||
var bufferHandle = _renderer.BufferManager.CreateWithHandle(_renderer, rangeSize, false);
|
var bufferHandle = _renderer.BufferManager.CreateWithHandle(_renderer, rangeSize);
|
||||||
|
|
||||||
_renderer.BufferManager.SetData(bufferHandle, 0, resolutionBuffer);
|
_renderer.BufferManager.SetData(bufferHandle, 0, resolutionBuffer);
|
||||||
|
|
||||||
|
@@ -266,7 +266,7 @@ namespace Ryujinx.Graphics.Vulkan.Effects
|
|||||||
|
|
||||||
ReadOnlySpan<float> resolutionBuffer = stackalloc float[] { view.Width, view.Height };
|
ReadOnlySpan<float> resolutionBuffer = stackalloc float[] { view.Width, view.Height };
|
||||||
int rangeSize = resolutionBuffer.Length * sizeof(float);
|
int rangeSize = resolutionBuffer.Length * sizeof(float);
|
||||||
var bufferHandle = _renderer.BufferManager.CreateWithHandle(_renderer, rangeSize, false);
|
var bufferHandle = _renderer.BufferManager.CreateWithHandle(_renderer, rangeSize);
|
||||||
|
|
||||||
_renderer.BufferManager.SetData(bufferHandle, 0, resolutionBuffer);
|
_renderer.BufferManager.SetData(bufferHandle, 0, resolutionBuffer);
|
||||||
var bufferRanges = new BufferRange(bufferHandle, 0, rangeSize);
|
var bufferRanges = new BufferRange(bufferHandle, 0, rangeSize);
|
||||||
|
@@ -394,7 +394,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
(region[2], region[3]) = (region[3], region[2]);
|
(region[2], region[3]) = (region[3], region[2]);
|
||||||
}
|
}
|
||||||
|
|
||||||
var bufferHandle = gd.BufferManager.CreateWithHandle(gd, RegionBufferSize, false);
|
var bufferHandle = gd.BufferManager.CreateWithHandle(gd, RegionBufferSize);
|
||||||
|
|
||||||
gd.BufferManager.SetData<float>(bufferHandle, 0, region);
|
gd.BufferManager.SetData<float>(bufferHandle, 0, region);
|
||||||
|
|
||||||
@@ -495,7 +495,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
(region[2], region[3]) = (region[3], region[2]);
|
(region[2], region[3]) = (region[3], region[2]);
|
||||||
}
|
}
|
||||||
|
|
||||||
var bufferHandle = gd.BufferManager.CreateWithHandle(gd, RegionBufferSize, false);
|
var bufferHandle = gd.BufferManager.CreateWithHandle(gd, RegionBufferSize);
|
||||||
|
|
||||||
gd.BufferManager.SetData<float>(bufferHandle, 0, region);
|
gd.BufferManager.SetData<float>(bufferHandle, 0, region);
|
||||||
|
|
||||||
@@ -649,7 +649,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
|
|
||||||
_pipeline.SetCommandBuffer(cbs);
|
_pipeline.SetCommandBuffer(cbs);
|
||||||
|
|
||||||
var bufferHandle = gd.BufferManager.CreateWithHandle(gd, ClearColorBufferSize, false);
|
var bufferHandle = gd.BufferManager.CreateWithHandle(gd, ClearColorBufferSize);
|
||||||
|
|
||||||
gd.BufferManager.SetData<float>(bufferHandle, 0, clearColor);
|
gd.BufferManager.SetData<float>(bufferHandle, 0, clearColor);
|
||||||
|
|
||||||
@@ -726,7 +726,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
(region[2], region[3]) = (region[3], region[2]);
|
(region[2], region[3]) = (region[3], region[2]);
|
||||||
}
|
}
|
||||||
|
|
||||||
var bufferHandle = gd.BufferManager.CreateWithHandle(gd, RegionBufferSize, false);
|
var bufferHandle = gd.BufferManager.CreateWithHandle(gd, RegionBufferSize);
|
||||||
|
|
||||||
gd.BufferManager.SetData<float>(bufferHandle, 0, region);
|
gd.BufferManager.SetData<float>(bufferHandle, 0, region);
|
||||||
|
|
||||||
@@ -802,7 +802,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
shaderParams[2] = size;
|
shaderParams[2] = size;
|
||||||
shaderParams[3] = srcOffset;
|
shaderParams[3] = srcOffset;
|
||||||
|
|
||||||
var bufferHandle = gd.BufferManager.CreateWithHandle(gd, ParamsBufferSize, false);
|
var bufferHandle = gd.BufferManager.CreateWithHandle(gd, ParamsBufferSize);
|
||||||
|
|
||||||
gd.BufferManager.SetData<int>(bufferHandle, 0, shaderParams);
|
gd.BufferManager.SetData<int>(bufferHandle, 0, shaderParams);
|
||||||
|
|
||||||
@@ -958,7 +958,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
|
|
||||||
shaderParams[0] = BitOperations.Log2((uint)ratio);
|
shaderParams[0] = BitOperations.Log2((uint)ratio);
|
||||||
|
|
||||||
var bufferHandle = gd.BufferManager.CreateWithHandle(gd, ParamsBufferSize, false);
|
var bufferHandle = gd.BufferManager.CreateWithHandle(gd, ParamsBufferSize);
|
||||||
|
|
||||||
gd.BufferManager.SetData<int>(bufferHandle, 0, shaderParams);
|
gd.BufferManager.SetData<int>(bufferHandle, 0, shaderParams);
|
||||||
|
|
||||||
@@ -1050,7 +1050,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
(shaderParams[0], shaderParams[1]) = GetSampleCountXYLog2(samples);
|
(shaderParams[0], shaderParams[1]) = GetSampleCountXYLog2(samples);
|
||||||
(shaderParams[2], shaderParams[3]) = GetSampleCountXYLog2((int)TextureStorage.ConvertToSampleCountFlags(gd.Capabilities.SupportedSampleCounts, (uint)samples));
|
(shaderParams[2], shaderParams[3]) = GetSampleCountXYLog2((int)TextureStorage.ConvertToSampleCountFlags(gd.Capabilities.SupportedSampleCounts, (uint)samples));
|
||||||
|
|
||||||
var bufferHandle = gd.BufferManager.CreateWithHandle(gd, ParamsBufferSize, false);
|
var bufferHandle = gd.BufferManager.CreateWithHandle(gd, ParamsBufferSize);
|
||||||
|
|
||||||
gd.BufferManager.SetData<int>(bufferHandle, 0, shaderParams);
|
gd.BufferManager.SetData<int>(bufferHandle, 0, shaderParams);
|
||||||
|
|
||||||
@@ -1133,7 +1133,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
(shaderParams[0], shaderParams[1]) = GetSampleCountXYLog2(samples);
|
(shaderParams[0], shaderParams[1]) = GetSampleCountXYLog2(samples);
|
||||||
(shaderParams[2], shaderParams[3]) = GetSampleCountXYLog2((int)TextureStorage.ConvertToSampleCountFlags(gd.Capabilities.SupportedSampleCounts, (uint)samples));
|
(shaderParams[2], shaderParams[3]) = GetSampleCountXYLog2((int)TextureStorage.ConvertToSampleCountFlags(gd.Capabilities.SupportedSampleCounts, (uint)samples));
|
||||||
|
|
||||||
var bufferHandle = gd.BufferManager.CreateWithHandle(gd, ParamsBufferSize, false);
|
var bufferHandle = gd.BufferManager.CreateWithHandle(gd, ParamsBufferSize);
|
||||||
|
|
||||||
gd.BufferManager.SetData<int>(bufferHandle, 0, shaderParams);
|
gd.BufferManager.SetData<int>(bufferHandle, 0, shaderParams);
|
||||||
|
|
||||||
@@ -1407,7 +1407,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
|
|
||||||
pattern.OffsetIndex.CopyTo(shaderParams.Slice(0, pattern.OffsetIndex.Length));
|
pattern.OffsetIndex.CopyTo(shaderParams.Slice(0, pattern.OffsetIndex.Length));
|
||||||
|
|
||||||
var patternBufferHandle = gd.BufferManager.CreateWithHandle(gd, ParamsBufferSize, false, out var patternBuffer);
|
var patternBufferHandle = gd.BufferManager.CreateWithHandle(gd, ParamsBufferSize, out var patternBuffer);
|
||||||
var patternBufferAuto = patternBuffer.GetBuffer();
|
var patternBufferAuto = patternBuffer.GetBuffer();
|
||||||
|
|
||||||
gd.BufferManager.SetData<int>(patternBufferHandle, 0, shaderParams);
|
gd.BufferManager.SetData<int>(patternBufferHandle, 0, shaderParams);
|
||||||
|
@@ -82,7 +82,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Expand the repeating pattern to the number of requested primitives.
|
// Expand the repeating pattern to the number of requested primitives.
|
||||||
BufferHandle newBuffer = _gd.CreateBuffer(expectedSize * sizeof(int));
|
BufferHandle newBuffer = _gd.BufferManager.CreateWithHandle(_gd, expectedSize * sizeof(int));
|
||||||
|
|
||||||
// Copy the old data to the new one.
|
// Copy the old data to the new one.
|
||||||
if (_repeatingBuffer != BufferHandle.Null)
|
if (_repeatingBuffer != BufferHandle.Null)
|
||||||
|
@@ -146,5 +146,16 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
{
|
{
|
||||||
return _buffer == buffer;
|
return _buffer == buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Swap(Auto<DisposableBuffer> from, Auto<DisposableBuffer> to)
|
||||||
|
{
|
||||||
|
if (_buffer == from)
|
||||||
|
{
|
||||||
|
_buffer.DecrementReferenceCount();
|
||||||
|
to.IncrementReferenceCount();
|
||||||
|
|
||||||
|
_buffer = to;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -28,32 +28,25 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
|
|
||||||
public MemoryAllocation AllocateDeviceMemory(
|
public MemoryAllocation AllocateDeviceMemory(
|
||||||
MemoryRequirements requirements,
|
MemoryRequirements requirements,
|
||||||
MemoryPropertyFlags flags = 0)
|
MemoryPropertyFlags flags = 0,
|
||||||
|
bool isBuffer = false)
|
||||||
{
|
{
|
||||||
return AllocateDeviceMemory(requirements, flags, flags);
|
int memoryTypeIndex = FindSuitableMemoryTypeIndex(requirements.MemoryTypeBits, flags);
|
||||||
}
|
|
||||||
|
|
||||||
public MemoryAllocation AllocateDeviceMemory(
|
|
||||||
MemoryRequirements requirements,
|
|
||||||
MemoryPropertyFlags flags,
|
|
||||||
MemoryPropertyFlags alternativeFlags)
|
|
||||||
{
|
|
||||||
int memoryTypeIndex = FindSuitableMemoryTypeIndex(requirements.MemoryTypeBits, flags, alternativeFlags);
|
|
||||||
if (memoryTypeIndex < 0)
|
if (memoryTypeIndex < 0)
|
||||||
{
|
{
|
||||||
return default;
|
return default;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool map = flags.HasFlag(MemoryPropertyFlags.HostVisibleBit);
|
bool map = flags.HasFlag(MemoryPropertyFlags.HostVisibleBit);
|
||||||
return Allocate(memoryTypeIndex, requirements.Size, requirements.Alignment, map);
|
return Allocate(memoryTypeIndex, requirements.Size, requirements.Alignment, map, isBuffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
private MemoryAllocation Allocate(int memoryTypeIndex, ulong size, ulong alignment, bool map)
|
private MemoryAllocation Allocate(int memoryTypeIndex, ulong size, ulong alignment, bool map, bool isBuffer)
|
||||||
{
|
{
|
||||||
for (int i = 0; i < _blockLists.Count; i++)
|
for (int i = 0; i < _blockLists.Count; i++)
|
||||||
{
|
{
|
||||||
var bl = _blockLists[i];
|
var bl = _blockLists[i];
|
||||||
if (bl.MemoryTypeIndex == memoryTypeIndex)
|
if (bl.MemoryTypeIndex == memoryTypeIndex && bl.ForBuffer == isBuffer)
|
||||||
{
|
{
|
||||||
lock (bl)
|
lock (bl)
|
||||||
{
|
{
|
||||||
@@ -62,18 +55,15 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var newBl = new MemoryAllocatorBlockList(_api, _device, memoryTypeIndex, _blockAlignment);
|
var newBl = new MemoryAllocatorBlockList(_api, _device, memoryTypeIndex, _blockAlignment, isBuffer);
|
||||||
_blockLists.Add(newBl);
|
_blockLists.Add(newBl);
|
||||||
return newBl.Allocate(size, alignment, map);
|
return newBl.Allocate(size, alignment, map);
|
||||||
}
|
}
|
||||||
|
|
||||||
private int FindSuitableMemoryTypeIndex(
|
private int FindSuitableMemoryTypeIndex(
|
||||||
uint memoryTypeBits,
|
uint memoryTypeBits,
|
||||||
MemoryPropertyFlags flags,
|
MemoryPropertyFlags flags)
|
||||||
MemoryPropertyFlags alternativeFlags)
|
|
||||||
{
|
{
|
||||||
int bestCandidateIndex = -1;
|
|
||||||
|
|
||||||
for (int i = 0; i < _physicalDeviceMemoryProperties.MemoryTypeCount; i++)
|
for (int i = 0; i < _physicalDeviceMemoryProperties.MemoryTypeCount; i++)
|
||||||
{
|
{
|
||||||
var type = _physicalDeviceMemoryProperties.MemoryTypes[i];
|
var type = _physicalDeviceMemoryProperties.MemoryTypes[i];
|
||||||
@@ -84,14 +74,27 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
{
|
{
|
||||||
return i;
|
return i;
|
||||||
}
|
}
|
||||||
else if (type.PropertyFlags.HasFlag(alternativeFlags))
|
|
||||||
{
|
|
||||||
bestCandidateIndex = i;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return bestCandidateIndex;
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool IsDeviceMemoryShared(Vk api, PhysicalDevice physicalDevice)
|
||||||
|
{
|
||||||
|
// The device is regarded as having shared memory if all heaps have the device local bit.
|
||||||
|
|
||||||
|
api.GetPhysicalDeviceMemoryProperties(physicalDevice, out var properties);
|
||||||
|
|
||||||
|
for (int i = 0; i < properties.MemoryHeapCount; i++)
|
||||||
|
{
|
||||||
|
if (!properties.MemoryHeaps[i].Flags.HasFlag(MemoryHeapFlags.DeviceLocalBit))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
|
@@ -162,15 +162,17 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
private readonly Device _device;
|
private readonly Device _device;
|
||||||
|
|
||||||
public int MemoryTypeIndex { get; }
|
public int MemoryTypeIndex { get; }
|
||||||
|
public bool ForBuffer { get; }
|
||||||
|
|
||||||
private readonly int _blockAlignment;
|
private readonly int _blockAlignment;
|
||||||
|
|
||||||
public MemoryAllocatorBlockList(Vk api, Device device, int memoryTypeIndex, int blockAlignment)
|
public MemoryAllocatorBlockList(Vk api, Device device, int memoryTypeIndex, int blockAlignment, bool forBuffer)
|
||||||
{
|
{
|
||||||
_blocks = new List<Block>();
|
_blocks = new List<Block>();
|
||||||
_api = api;
|
_api = api;
|
||||||
_device = device;
|
_device = device;
|
||||||
MemoryTypeIndex = memoryTypeIndex;
|
MemoryTypeIndex = memoryTypeIndex;
|
||||||
|
ForBuffer = forBuffer;
|
||||||
_blockAlignment = blockAlignment;
|
_blockAlignment = blockAlignment;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -684,6 +684,16 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
_tfEnabled = false;
|
_tfEnabled = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public double GetCounterDivisor(CounterType type)
|
||||||
|
{
|
||||||
|
if (type == CounterType.SamplesPassed)
|
||||||
|
{
|
||||||
|
return _renderScale[0].X * _renderScale[0].X;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
public bool IsCommandBufferActive(CommandBuffer cb)
|
public bool IsCommandBufferActive(CommandBuffer cb)
|
||||||
{
|
{
|
||||||
return CommandBuffer.Handle == cb.Handle;
|
return CommandBuffer.Handle == cb.Handle;
|
||||||
@@ -1287,6 +1297,25 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
SignalStateChange();
|
SignalStateChange();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void SwapBuffer(Auto<DisposableBuffer> from, Auto<DisposableBuffer> to)
|
||||||
|
{
|
||||||
|
_indexBuffer.Swap(from, to);
|
||||||
|
|
||||||
|
for (int i = 0; i < _vertexBuffers.Length; i++)
|
||||||
|
{
|
||||||
|
_vertexBuffers[i].Swap(from, to);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < _transformFeedbackBuffers.Length; i++)
|
||||||
|
{
|
||||||
|
_transformFeedbackBuffers[i].Swap(from, to);
|
||||||
|
}
|
||||||
|
|
||||||
|
_descriptorSetUpdater.SwapBuffer(from, to);
|
||||||
|
|
||||||
|
SignalCommandBufferChange();
|
||||||
|
}
|
||||||
|
|
||||||
public unsafe void TextureBarrier()
|
public unsafe void TextureBarrier()
|
||||||
{
|
{
|
||||||
MemoryBarrier memoryBarrier = new MemoryBarrier()
|
MemoryBarrier memoryBarrier = new MemoryBarrier()
|
||||||
|
@@ -17,10 +17,13 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
|
|
||||||
private ulong _byteWeight;
|
private ulong _byteWeight;
|
||||||
|
|
||||||
|
private List<BufferHolder> _backingSwaps;
|
||||||
|
|
||||||
public PipelineFull(VulkanRenderer gd, Device device) : base(gd, device)
|
public PipelineFull(VulkanRenderer gd, Device device) : base(gd, device)
|
||||||
{
|
{
|
||||||
_activeQueries = new List<(QueryPool, bool)>();
|
_activeQueries = new List<(QueryPool, bool)>();
|
||||||
_pendingQueryCopies = new();
|
_pendingQueryCopies = new();
|
||||||
|
_backingSwaps = new();
|
||||||
|
|
||||||
CommandBuffer = (Cbs = gd.CommandBufferPool.Rent()).CommandBuffer;
|
CommandBuffer = (Cbs = gd.CommandBufferPool.Rent()).CommandBuffer;
|
||||||
}
|
}
|
||||||
@@ -185,6 +188,20 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void TryBackingSwaps()
|
||||||
|
{
|
||||||
|
CommandBufferScoped? cbs = null;
|
||||||
|
|
||||||
|
_backingSwaps.RemoveAll((holder) => holder.TryBackingSwap(ref cbs));
|
||||||
|
|
||||||
|
cbs?.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddBackingSwap(BufferHolder holder)
|
||||||
|
{
|
||||||
|
_backingSwaps.Add(holder);
|
||||||
|
}
|
||||||
|
|
||||||
public void Restore()
|
public void Restore()
|
||||||
{
|
{
|
||||||
if (Pipeline != null)
|
if (Pipeline != null)
|
||||||
@@ -230,6 +247,8 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
|
|
||||||
Gd.ResetCounterPool();
|
Gd.ResetCounterPool();
|
||||||
|
|
||||||
|
TryBackingSwaps();
|
||||||
|
|
||||||
Restore();
|
Restore();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -12,6 +12,7 @@ namespace Ryujinx.Graphics.Vulkan.Queries
|
|||||||
private const int MaxQueryRetries = 5000;
|
private const int MaxQueryRetries = 5000;
|
||||||
private const long DefaultValue = -1;
|
private const long DefaultValue = -1;
|
||||||
private const long DefaultValueInt = 0xFFFFFFFF;
|
private const long DefaultValueInt = 0xFFFFFFFF;
|
||||||
|
private const ulong HighMask = 0xFFFFFFFF00000000;
|
||||||
|
|
||||||
private readonly Vk _api;
|
private readonly Vk _api;
|
||||||
private readonly Device _device;
|
private readonly Device _device;
|
||||||
@@ -125,6 +126,12 @@ namespace Ryujinx.Graphics.Vulkan.Queries
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool WaitingForValue(long data)
|
||||||
|
{
|
||||||
|
return data == _defaultValue ||
|
||||||
|
(!_result32Bit && ((ulong)data & HighMask) == ((ulong)_defaultValue & HighMask));
|
||||||
|
}
|
||||||
|
|
||||||
public bool TryGetResult(out long result)
|
public bool TryGetResult(out long result)
|
||||||
{
|
{
|
||||||
result = Marshal.ReadInt64(_bufferMap);
|
result = Marshal.ReadInt64(_bufferMap);
|
||||||
@@ -138,7 +145,7 @@ namespace Ryujinx.Graphics.Vulkan.Queries
|
|||||||
|
|
||||||
if (wakeSignal == null)
|
if (wakeSignal == null)
|
||||||
{
|
{
|
||||||
while (data == _defaultValue)
|
while (WaitingForValue(data))
|
||||||
{
|
{
|
||||||
data = Marshal.ReadInt64(_bufferMap);
|
data = Marshal.ReadInt64(_bufferMap);
|
||||||
}
|
}
|
||||||
@@ -146,10 +153,10 @@ namespace Ryujinx.Graphics.Vulkan.Queries
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
int iterations = 0;
|
int iterations = 0;
|
||||||
while (data == _defaultValue && iterations++ < MaxQueryRetries)
|
while (WaitingForValue(data) && iterations++ < MaxQueryRetries)
|
||||||
{
|
{
|
||||||
data = Marshal.ReadInt64(_bufferMap);
|
data = Marshal.ReadInt64(_bufferMap);
|
||||||
if (data == _defaultValue)
|
if (WaitingForValue(data))
|
||||||
{
|
{
|
||||||
wakeSignal.WaitOne(1);
|
wakeSignal.WaitOne(1);
|
||||||
}
|
}
|
||||||
|
@@ -148,7 +148,7 @@ namespace Ryujinx.Graphics.Vulkan.Queries
|
|||||||
_current.ReserveForHostAccess();
|
_current.ReserveForHostAccess();
|
||||||
}
|
}
|
||||||
|
|
||||||
_current.Complete(draws > 0 && Type != CounterType.TransformFeedbackPrimitivesWritten);
|
_current.Complete(draws > 0 && Type != CounterType.TransformFeedbackPrimitivesWritten, _pipeline.GetCounterDivisor(Type));
|
||||||
_events.Enqueue(_current);
|
_events.Enqueue(_current);
|
||||||
|
|
||||||
_current.OnResult += resultHandler;
|
_current.OnResult += resultHandler;
|
||||||
|
@@ -24,6 +24,7 @@ namespace Ryujinx.Graphics.Vulkan.Queries
|
|||||||
|
|
||||||
private object _lock = new object();
|
private object _lock = new object();
|
||||||
private ulong _result = ulong.MaxValue;
|
private ulong _result = ulong.MaxValue;
|
||||||
|
private double _divisor = 1f;
|
||||||
|
|
||||||
public CounterQueueEvent(CounterQueue queue, CounterType type, ulong drawIndex)
|
public CounterQueueEvent(CounterQueue queue, CounterType type, ulong drawIndex)
|
||||||
{
|
{
|
||||||
@@ -52,9 +53,11 @@ namespace Ryujinx.Graphics.Vulkan.Queries
|
|||||||
ClearCounter = true;
|
ClearCounter = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void Complete(bool withResult)
|
internal void Complete(bool withResult, double divisor)
|
||||||
{
|
{
|
||||||
_counter.End(withResult);
|
_counter.End(withResult);
|
||||||
|
|
||||||
|
_divisor = divisor;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal bool TryConsume(ref ulong result, bool block, AutoResetEvent wakeSignal = null)
|
internal bool TryConsume(ref ulong result, bool block, AutoResetEvent wakeSignal = null)
|
||||||
@@ -85,7 +88,7 @@ namespace Ryujinx.Graphics.Vulkan.Queries
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
result += (ulong)queryResult;
|
result += _divisor == 1 ? (ulong)queryResult : (ulong)Math.Ceiling(queryResult / _divisor);
|
||||||
|
|
||||||
_result = result;
|
_result = result;
|
||||||
|
|
||||||
|
@@ -17,7 +17,7 @@ namespace Ryujinx.Graphics.Vulkan.Queries
|
|||||||
|
|
||||||
_counterQueues = new CounterQueue[count];
|
_counterQueues = new CounterQueue[count];
|
||||||
|
|
||||||
for (int index = 0; index < count; index++)
|
for (int index = 0; index < _counterQueues.Length; index++)
|
||||||
{
|
{
|
||||||
CounterType type = (CounterType)index;
|
CounterType type = (CounterType)index;
|
||||||
_counterQueues[index] = new CounterQueue(gd, device, pipeline, type);
|
_counterQueues[index] = new CounterQueue(gd, device, pipeline, type);
|
||||||
|
@@ -29,9 +29,6 @@
|
|||||||
<PackageReference Include="Silk.NET.Vulkan" />
|
<PackageReference Include="Silk.NET.Vulkan" />
|
||||||
<PackageReference Include="Silk.NET.Vulkan.Extensions.EXT" />
|
<PackageReference Include="Silk.NET.Vulkan.Extensions.EXT" />
|
||||||
<PackageReference Include="Silk.NET.Vulkan.Extensions.KHR" />
|
<PackageReference Include="Silk.NET.Vulkan.Extensions.KHR" />
|
||||||
<PackageReference Include="System.IO.FileSystem.Primitives" />
|
|
||||||
<PackageReference Include="System.Net.NameResolution" />
|
|
||||||
<PackageReference Include="System.Threading.ThreadPool" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@@ -29,7 +29,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
|
|
||||||
uint structSize = 0;
|
uint structSize = 0;
|
||||||
|
|
||||||
for (int i = 0; i < count; ++i)
|
for (int i = 0; i < Map.Length; ++i)
|
||||||
{
|
{
|
||||||
var typeSize = SizeOf(description[i].Type);
|
var typeSize = SizeOf(description[i].Type);
|
||||||
Map[i] = new SpecializationMapEntry(description[i].Id, structSize, typeSize);
|
Map[i] = new SpecializationMapEntry(description[i].Id, structSize, typeSize);
|
||||||
@@ -46,11 +46,10 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
// For advanced mapping with overlapping or staggered fields
|
// For advanced mapping with overlapping or staggered fields
|
||||||
public SpecDescription(SpecializationMapEntry[] map)
|
public SpecDescription(SpecializationMapEntry[] map)
|
||||||
{
|
{
|
||||||
int count = map.Length;
|
|
||||||
Map = map;
|
Map = map;
|
||||||
|
|
||||||
uint structSize = 0;
|
uint structSize = 0;
|
||||||
for (int i = 0; i < count; ++i)
|
for (int i = 0; i < map.Length; ++i)
|
||||||
{
|
{
|
||||||
structSize = Math.Max(structSize, map[i].Offset + (uint)map[i].Size);
|
structSize = Math.Max(structSize, map[i].Offset + (uint)map[i].Size);
|
||||||
}
|
}
|
||||||
|
@@ -57,12 +57,12 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
throw new NotSupportedException();
|
throw new NotSupportedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
public ReadOnlySpan<byte> GetData()
|
public PinnedSpan<byte> GetData()
|
||||||
{
|
{
|
||||||
return _gd.GetBufferData(_bufferHandle, _offset, _size);
|
return _gd.GetBufferData(_bufferHandle, _offset, _size);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ReadOnlySpan<byte> GetData(int layer, int level)
|
public PinnedSpan<byte> GetData(int layer, int level)
|
||||||
{
|
{
|
||||||
return GetData();
|
return GetData();
|
||||||
}
|
}
|
||||||
@@ -128,7 +128,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
{
|
{
|
||||||
if (_bufferView == null)
|
if (_bufferView == null)
|
||||||
{
|
{
|
||||||
_bufferView = _gd.BufferManager.CreateView(_bufferHandle, VkFormat, _offset, _size);
|
_bufferView = _gd.BufferManager.CreateView(_bufferHandle, VkFormat, _offset, _size, ReleaseImpl);
|
||||||
}
|
}
|
||||||
|
|
||||||
return _bufferView?.Get(cbs, _offset, _size).Value ?? default;
|
return _bufferView?.Get(cbs, _offset, _size).Value ?? default;
|
||||||
@@ -147,7 +147,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
return bufferView.Get(cbs, _offset, _size).Value;
|
return bufferView.Get(cbs, _offset, _size).Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
bufferView = _gd.BufferManager.CreateView(_bufferHandle, vkFormat, _offset, _size);
|
bufferView = _gd.BufferManager.CreateView(_bufferHandle, vkFormat, _offset, _size, ReleaseImpl);
|
||||||
|
|
||||||
if (bufferView != null)
|
if (bufferView != null)
|
||||||
{
|
{
|
||||||
|
@@ -531,7 +531,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
return bitmap;
|
return bitmap;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ReadOnlySpan<byte> GetData()
|
public PinnedSpan<byte> GetData()
|
||||||
{
|
{
|
||||||
BackgroundResource resources = _gd.BackgroundResources.Get();
|
BackgroundResource resources = _gd.BackgroundResources.Get();
|
||||||
|
|
||||||
@@ -539,15 +539,15 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
{
|
{
|
||||||
_gd.FlushAllCommands();
|
_gd.FlushAllCommands();
|
||||||
|
|
||||||
return GetData(_gd.CommandBufferPool, resources.GetFlushBuffer());
|
return PinnedSpan<byte>.UnsafeFromSpan(GetData(_gd.CommandBufferPool, resources.GetFlushBuffer()));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
return GetData(resources.GetPool(), resources.GetFlushBuffer());
|
return PinnedSpan<byte>.UnsafeFromSpan(GetData(resources.GetPool(), resources.GetFlushBuffer()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public ReadOnlySpan<byte> GetData(int layer, int level)
|
public PinnedSpan<byte> GetData(int layer, int level)
|
||||||
{
|
{
|
||||||
BackgroundResource resources = _gd.BackgroundResources.Get();
|
BackgroundResource resources = _gd.BackgroundResources.Get();
|
||||||
|
|
||||||
@@ -555,11 +555,11 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
{
|
{
|
||||||
_gd.FlushAllCommands();
|
_gd.FlushAllCommands();
|
||||||
|
|
||||||
return GetData(_gd.CommandBufferPool, resources.GetFlushBuffer(), layer, level);
|
return PinnedSpan<byte>.UnsafeFromSpan(GetData(_gd.CommandBufferPool, resources.GetFlushBuffer(), layer, level));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
return GetData(resources.GetPool(), resources.GetFlushBuffer(), layer, level);
|
return PinnedSpan<byte>.UnsafeFromSpan(GetData(resources.GetPool(), resources.GetFlushBuffer(), layer, level));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -129,6 +129,17 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
return _buffer == buffer;
|
return _buffer == buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Swap(Auto<DisposableBuffer> from, Auto<DisposableBuffer> to)
|
||||||
|
{
|
||||||
|
if (_buffer == from)
|
||||||
|
{
|
||||||
|
_buffer.DecrementReferenceCount();
|
||||||
|
to.IncrementReferenceCount();
|
||||||
|
|
||||||
|
_buffer = to;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
// Only dispose if this buffer is not refetched on each bind.
|
// Only dispose if this buffer is not refetched on each bind.
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user