Compare commits
21 Commits
__refs_pul
...
__refs_pul
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2ac7472d3f | ||
|
|
0f54b541f4 | ||
|
|
5818959e54 | ||
|
|
913b7a6872 | ||
|
|
a9943222f2 | ||
|
|
5c1e1a148e | ||
|
|
5d31bab69a | ||
|
|
4882c058fd | ||
|
|
0ec9da2f9f | ||
|
|
b9e1db1312 | ||
|
|
bbc5b5d62d | ||
|
|
4d82158274 | ||
|
|
d4fc560c05 | ||
|
|
a1845d1dd3 | ||
|
|
697206092e | ||
|
|
ca6f08e3b1 | ||
|
|
ce64a9fab9 | ||
|
|
b901cd584e | ||
|
|
d0966b9f7c | ||
|
|
1689784c19 | ||
|
|
13a8fde3ad |
15
.ci/scripts/common/post-upload.sh
Normal file
15
.ci/scripts/common/post-upload.sh
Normal file
@@ -0,0 +1,15 @@
|
||||
#!/bin/bash -ex
|
||||
|
||||
# Copy documentation
|
||||
cp license.txt "$REV_NAME"
|
||||
cp README.md "$REV_NAME"
|
||||
|
||||
tar $COMPRESSION_FLAGS "$ARCHIVE_NAME" "$REV_NAME"
|
||||
|
||||
mv "$REV_NAME" $RELEASE_NAME
|
||||
|
||||
7z a "$REV_NAME.7z" $RELEASE_NAME
|
||||
|
||||
# move the compiled archive into the artifacts directory to be uploaded by travis releases
|
||||
mv "$ARCHIVE_NAME" artifacts/
|
||||
mv "$REV_NAME.7z" artifacts/
|
||||
6
.ci/scripts/common/pre-upload.sh
Normal file
6
.ci/scripts/common/pre-upload.sh
Normal file
@@ -0,0 +1,6 @@
|
||||
#!/bin/bash -ex
|
||||
|
||||
GITDATE="`git show -s --date=short --format='%ad' | sed 's/-//g'`"
|
||||
GITREV="`git show -s --format='%h'`"
|
||||
|
||||
mkdir -p artifacts
|
||||
6
.ci/scripts/format/docker.sh
Normal file
6
.ci/scripts/format/docker.sh
Normal file
@@ -0,0 +1,6 @@
|
||||
#!/bin/bash -ex
|
||||
|
||||
# Run clang-format
|
||||
cd /yuzu
|
||||
chmod a+x ./.ci/scripts/format/script.sh
|
||||
./.ci/scripts/format/script.sh
|
||||
4
.ci/scripts/format/exec.sh
Normal file
4
.ci/scripts/format/exec.sh
Normal file
@@ -0,0 +1,4 @@
|
||||
#!/bin/bash -ex
|
||||
|
||||
chmod a+x ./.ci/scripts/format/docker.sh
|
||||
docker run -v $(pwd):/yuzu yuzuemu/build-environments:linux-clang-format /bin/bash -ex /yuzu/.ci/scripts/format/docker.sh
|
||||
37
.ci/scripts/format/script.sh
Normal file
37
.ci/scripts/format/script.sh
Normal file
@@ -0,0 +1,37 @@
|
||||
#!/bin/bash -ex
|
||||
|
||||
if grep -nrI '\s$' src *.yml *.txt *.md Doxyfile .gitignore .gitmodules .ci* dist/*.desktop \
|
||||
dist/*.svg dist/*.xml; then
|
||||
echo Trailing whitespace found, aborting
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Default clang-format points to default 3.5 version one
|
||||
CLANG_FORMAT=clang-format-6.0
|
||||
$CLANG_FORMAT --version
|
||||
|
||||
if [ "$TRAVIS_EVENT_TYPE" = "pull_request" ]; then
|
||||
# Get list of every file modified in this pull request
|
||||
files_to_lint="$(git diff --name-only --diff-filter=ACMRTUXB $TRAVIS_COMMIT_RANGE | grep '^src/[^.]*[.]\(cpp\|h\)$' || true)"
|
||||
else
|
||||
# Check everything for branch pushes
|
||||
files_to_lint="$(find src/ -name '*.cpp' -or -name '*.h')"
|
||||
fi
|
||||
|
||||
# Turn off tracing for this because it's too verbose
|
||||
set +x
|
||||
|
||||
for f in $files_to_lint; do
|
||||
d=$(diff -u "$f" <($CLANG_FORMAT "$f") || true)
|
||||
if ! [ -z "$d" ]; then
|
||||
echo "!!! $f not compliant to coding style, here is the fix:"
|
||||
echo "$d"
|
||||
fail=1
|
||||
fi
|
||||
done
|
||||
|
||||
set -x
|
||||
|
||||
if [ "$fail" = 1 ]; then
|
||||
exit 1
|
||||
fi
|
||||
14
.ci/scripts/linux/docker.sh
Normal file
14
.ci/scripts/linux/docker.sh
Normal file
@@ -0,0 +1,14 @@
|
||||
#!/bin/bash -ex
|
||||
|
||||
cd /yuzu
|
||||
|
||||
ccache -s
|
||||
|
||||
mkdir build || true && cd build
|
||||
cmake .. -G Ninja -DYUZU_USE_BUNDLED_UNICORN=ON -DYUZU_USE_QT_WEB_ENGINE=ON -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=/usr/lib/ccache/gcc -DCMAKE_CXX_COMPILER=/usr/lib/ccache/g++ -DYUZU_ENABLE_COMPATIBILITY_REPORTING=${ENABLE_COMPATIBILITY_REPORTING:-"OFF"} -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DUSE_DISCORD_PRESENCE=ON
|
||||
|
||||
ninja
|
||||
|
||||
ccache -s
|
||||
|
||||
ctest -VV -C Release
|
||||
5
.ci/scripts/linux/exec.sh
Normal file
5
.ci/scripts/linux/exec.sh
Normal file
@@ -0,0 +1,5 @@
|
||||
#!/bin/bash -ex
|
||||
|
||||
mkdir -p "ccache" || true
|
||||
chmod a+x ./.ci/scripts/linux/docker.sh
|
||||
docker run -e ENABLE_COMPATIBILITY_REPORTING -e CCACHE_DIR=/yuzu/ccache -v $(pwd):/yuzu yuzuemu/build-environments:linux-fresh /bin/bash /yuzu/.ci/scripts/linux/docker.sh
|
||||
14
.ci/scripts/linux/upload.sh
Normal file
14
.ci/scripts/linux/upload.sh
Normal file
@@ -0,0 +1,14 @@
|
||||
#!/bin/bash -ex
|
||||
|
||||
. .ci/scripts/common/pre-upload.sh
|
||||
|
||||
REV_NAME="yuzu-linux-${GITDATE}-${GITREV}"
|
||||
ARCHIVE_NAME="${REV_NAME}.tar.xz"
|
||||
COMPRESSION_FLAGS="-cJvf"
|
||||
|
||||
mkdir "$REV_NAME"
|
||||
|
||||
cp build/bin/yuzu-cmd "$REV_NAME"
|
||||
cp build/bin/yuzu "$REV_NAME"
|
||||
|
||||
. .ci/scripts/common/post-upload.sh
|
||||
28
.ci/scripts/merge/apply-patches-by-label.py
Normal file
28
.ci/scripts/merge/apply-patches-by-label.py
Normal file
@@ -0,0 +1,28 @@
|
||||
# Download all pull requests as patches that match a specific label
|
||||
# Usage: python download-patches-by-label.py <Label to Match> <Root Path Folder to DL to>
|
||||
|
||||
import requests, sys, json, urllib3.request, shutil, subprocess
|
||||
|
||||
http = urllib3.PoolManager()
|
||||
dl_list = {}
|
||||
|
||||
def check_individual(labels):
|
||||
for label in labels:
|
||||
if (label["name"] == sys.argv[1]):
|
||||
return True
|
||||
return False
|
||||
|
||||
try:
|
||||
url = 'https://api.github.com/repos/yuzu-emu/yuzu/pulls'
|
||||
response = requests.get(url)
|
||||
if (response.ok):
|
||||
j = json.loads(response.content)
|
||||
for pr in j:
|
||||
if (check_individual(pr["labels"])):
|
||||
pn = pr["number"]
|
||||
print("Matched PR# %s" % pn)
|
||||
print(subprocess.check_output(["git", "fetch", "https://github.com/yuzu-emu/yuzu.git", "pull/%s/head:pr-%s" % (pn, pn), "-f"]))
|
||||
print(subprocess.check_output(["git", "merge", "--squash", "pr-%s" % pn]))
|
||||
print(subprocess.check_output(["git", "commit", "-m\"Merge PR %s\"" % pn]))
|
||||
except:
|
||||
sys.exit(-1)
|
||||
18
.ci/scripts/merge/check-label-presence.py
Normal file
18
.ci/scripts/merge/check-label-presence.py
Normal file
@@ -0,0 +1,18 @@
|
||||
# Checks to see if the specified pull request # has the specified tag
|
||||
# Usage: python check-label-presence.py <Pull Request ID> <Name of Label>
|
||||
|
||||
import requests, json, sys
|
||||
|
||||
try:
|
||||
url = 'https://api.github.com/repos/yuzu-emu/yuzu/issues/%s' % sys.argv[1]
|
||||
response = requests.get(url)
|
||||
if (response.ok):
|
||||
j = json.loads(response.content)
|
||||
for label in j["labels"]:
|
||||
if label["name"] == sys.argv[2]:
|
||||
print('##vso[task.setvariable variable=enabletesting;]true')
|
||||
sys.exit()
|
||||
except:
|
||||
sys.exit(-1)
|
||||
|
||||
print('##vso[task.setvariable variable=enabletesting;]false')
|
||||
2
.ci/scripts/merge/yuzubot-git-config.sh
Normal file
2
.ci/scripts/merge/yuzubot-git-config.sh
Normal file
@@ -0,0 +1,2 @@
|
||||
git config --global user.email "yuzu@yuzu-emu.org"
|
||||
git config --global user.name "yuzubot"
|
||||
50
.ci/scripts/windows/docker.sh
Normal file
50
.ci/scripts/windows/docker.sh
Normal file
@@ -0,0 +1,50 @@
|
||||
#!/bin/bash -ex
|
||||
|
||||
cd /yuzu
|
||||
|
||||
ccache -s
|
||||
|
||||
# Dirty hack to trick unicorn makefile into believing we are in a MINGW system
|
||||
mv /bin/uname /bin/uname1 && echo -e '#!/bin/sh\necho MINGW64' >> /bin/uname
|
||||
chmod +x /bin/uname
|
||||
|
||||
# Dirty hack to trick unicorn makefile into believing we have cmd
|
||||
echo '' >> /bin/cmd
|
||||
chmod +x /bin/cmd
|
||||
|
||||
mkdir build || true && cd build
|
||||
cmake .. -G Ninja -DCMAKE_TOOLCHAIN_FILE="$(pwd)/../CMakeModules/MinGWCross.cmake" -DUSE_CCACHE=ON -DYUZU_USE_BUNDLED_UNICORN=ON -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DCMAKE_BUILD_TYPE=Release
|
||||
ninja
|
||||
|
||||
# Clean up the dirty hacks
|
||||
rm /bin/uname && mv /bin/uname1 /bin/uname
|
||||
rm /bin/cmd
|
||||
|
||||
ccache -s
|
||||
|
||||
echo "Tests skipped"
|
||||
#ctest -VV -C Release
|
||||
|
||||
echo 'Prepare binaries...'
|
||||
cd ..
|
||||
mkdir package
|
||||
|
||||
QT_PLATFORM_DLL_PATH='/usr/x86_64-w64-mingw32/lib/qt5/plugins/platforms/'
|
||||
find build/ -name "yuzu*.exe" -exec cp {} 'package' \;
|
||||
|
||||
# copy Qt plugins
|
||||
mkdir package/platforms
|
||||
cp "${QT_PLATFORM_DLL_PATH}/qwindows.dll" package/platforms/
|
||||
cp -rv "${QT_PLATFORM_DLL_PATH}/../mediaservice/" package/
|
||||
cp -rv "${QT_PLATFORM_DLL_PATH}/../imageformats/" package/
|
||||
rm -f package/mediaservice/*d.dll
|
||||
|
||||
for i in package/*.exe; do
|
||||
# we need to process pdb here, however, cv2pdb
|
||||
# does not work here, so we just simply strip all the debug symbols
|
||||
x86_64-w64-mingw32-strip "${i}"
|
||||
done
|
||||
|
||||
pip3 install pefile
|
||||
python3 .ci/scripts/windows/scan_dll.py package/*.exe "package/"
|
||||
python3 .ci/scripts/windows/scan_dll.py package/imageformats/*.dll "package/"
|
||||
5
.ci/scripts/windows/exec.sh
Normal file
5
.ci/scripts/windows/exec.sh
Normal file
@@ -0,0 +1,5 @@
|
||||
#!/bin/bash -ex
|
||||
|
||||
mkdir -p "ccache" || true
|
||||
chmod a+x ./.ci/scripts/windows/docker.sh
|
||||
docker run -e CCACHE_DIR=/yuzu/ccache -v $(pwd):/yuzu yuzuemu/build-environments:linux-mingw /bin/bash -ex /yuzu/.ci/scripts/windows/docker.sh
|
||||
106
.ci/scripts/windows/scan_dll.py
Normal file
106
.ci/scripts/windows/scan_dll.py
Normal file
@@ -0,0 +1,106 @@
|
||||
import pefile
|
||||
import sys
|
||||
import re
|
||||
import os
|
||||
import queue
|
||||
import shutil
|
||||
|
||||
# constant definitions
|
||||
KNOWN_SYS_DLLS = ['WINMM.DLL', 'MSVCRT.DLL', 'VERSION.DLL', 'MPR.DLL',
|
||||
'DWMAPI.DLL', 'UXTHEME.DLL', 'DNSAPI.DLL', 'IPHLPAPI.DLL']
|
||||
# below is for Ubuntu 18.04 with specified PPA enabled, if you are using
|
||||
# other distro or different repositories, change the following accordingly
|
||||
DLL_PATH = [
|
||||
'/usr/x86_64-w64-mingw32/bin/',
|
||||
'/usr/x86_64-w64-mingw32/lib/',
|
||||
'/usr/lib/gcc/x86_64-w64-mingw32/7.3-posix/'
|
||||
]
|
||||
|
||||
missing = []
|
||||
|
||||
|
||||
def parse_imports(file_name):
|
||||
results = []
|
||||
pe = pefile.PE(file_name, fast_load=True)
|
||||
pe.parse_data_directories()
|
||||
|
||||
for entry in pe.DIRECTORY_ENTRY_IMPORT:
|
||||
current = entry.dll.decode()
|
||||
current_u = current.upper() # b/c Windows is often case insensitive
|
||||
# here we filter out system dlls
|
||||
# dll w/ names like *32.dll are likely to be system dlls
|
||||
if current_u.upper() not in KNOWN_SYS_DLLS and not re.match(string=current_u, pattern=r'.*32\.DLL'):
|
||||
results.append(current)
|
||||
|
||||
return results
|
||||
|
||||
|
||||
def parse_imports_recursive(file_name, path_list=[]):
|
||||
q = queue.Queue() # create a FIFO queue
|
||||
# file_name can be a string or a list for the convience
|
||||
if isinstance(file_name, str):
|
||||
q.put(file_name)
|
||||
elif isinstance(file_name, list):
|
||||
for i in file_name:
|
||||
q.put(i)
|
||||
full_list = []
|
||||
while q.qsize():
|
||||
current = q.get_nowait()
|
||||
print('> %s' % current)
|
||||
deps = parse_imports(current)
|
||||
# if this dll does not have any import, ignore it
|
||||
if not deps:
|
||||
continue
|
||||
for dep in deps:
|
||||
# the dependency already included in the list, skip
|
||||
if dep in full_list:
|
||||
continue
|
||||
# find the requested dll in the provided paths
|
||||
full_path = find_dll(dep)
|
||||
if not full_path:
|
||||
missing.append(dep)
|
||||
continue
|
||||
full_list.append(dep)
|
||||
q.put(full_path)
|
||||
path_list.append(full_path)
|
||||
return full_list
|
||||
|
||||
|
||||
def find_dll(name):
|
||||
for path in DLL_PATH:
|
||||
for root, _, files in os.walk(path):
|
||||
for f in files:
|
||||
if name.lower() == f.lower():
|
||||
return os.path.join(root, f)
|
||||
|
||||
|
||||
def deploy(name, dst, dry_run=False):
|
||||
dlls_path = []
|
||||
parse_imports_recursive(name, dlls_path)
|
||||
for dll_entry in dlls_path:
|
||||
if not dry_run:
|
||||
shutil.copy(dll_entry, dst)
|
||||
else:
|
||||
print('[Dry-Run] Copy %s to %s' % (dll_entry, dst))
|
||||
print('Deploy completed.')
|
||||
return dlls_path
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) < 3:
|
||||
print('Usage: %s [files to examine ...] [target deploy directory]')
|
||||
return 1
|
||||
to_deploy = sys.argv[1:-1]
|
||||
tgt_dir = sys.argv[-1]
|
||||
if not os.path.isdir(tgt_dir):
|
||||
print('%s is not a directory.' % tgt_dir)
|
||||
return 1
|
||||
print('Scanning dependencies...')
|
||||
deploy(to_deploy, tgt_dir)
|
||||
if missing:
|
||||
print('Following DLLs are not found: %s' % ('\n'.join(missing)))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
13
.ci/scripts/windows/upload.sh
Normal file
13
.ci/scripts/windows/upload.sh
Normal file
@@ -0,0 +1,13 @@
|
||||
#!/bin/bash -ex
|
||||
|
||||
. .ci/scripts/common/pre-upload.sh
|
||||
|
||||
REV_NAME="yuzu-windows-mingw-${GITDATE}-${GITREV}"
|
||||
ARCHIVE_NAME="${REV_NAME}.tar.gz"
|
||||
COMPRESSION_FLAGS="-czvf"
|
||||
|
||||
mkdir "$REV_NAME"
|
||||
# get around the permission issues
|
||||
cp -r package/* "$REV_NAME"
|
||||
|
||||
. .ci/scripts/common/post-upload.sh
|
||||
21
.ci/templates/build-single.yml
Normal file
21
.ci/templates/build-single.yml
Normal file
@@ -0,0 +1,21 @@
|
||||
parameters:
|
||||
artifactSource: 'true'
|
||||
|
||||
steps:
|
||||
- task: DockerInstaller@0
|
||||
displayName: 'Prepare Environment'
|
||||
inputs:
|
||||
dockerVersion: '17.09.0-ce'
|
||||
- task: CacheBeta@0
|
||||
displayName: 'Cache Build System'
|
||||
inputs:
|
||||
key: yuzu-v1-$(BuildName)-$(BuildSuffix)-$(CacheSuffix)
|
||||
path: $(System.DefaultWorkingDirectory)/ccache
|
||||
cacheHitVar: CACHE_RESTORED
|
||||
- script: chmod a+x ./.ci/scripts/$(ScriptFolder)/exec.sh && ./.ci/scripts/$(ScriptFolder)/exec.sh
|
||||
displayName: 'Build'
|
||||
- script: chmod a+x ./.ci/scripts/$(ScriptFolder)/upload.sh && ./.ci/scripts/$(ScriptFolder)/upload.sh
|
||||
displayName: 'Package Artifacts'
|
||||
- publish: artifacts
|
||||
artifact: 'yuzu-$(BuildName)-$(BuildSuffix)'
|
||||
displayName: 'Upload Artifacts'
|
||||
22
.ci/templates/build-standard.yml
Normal file
22
.ci/templates/build-standard.yml
Normal file
@@ -0,0 +1,22 @@
|
||||
jobs:
|
||||
- job: build
|
||||
displayName: 'standard'
|
||||
pool:
|
||||
vmImage: ubuntu-latest
|
||||
strategy:
|
||||
maxParallel: 10
|
||||
matrix:
|
||||
windows:
|
||||
BuildSuffix: 'windows-mingw'
|
||||
ScriptFolder: 'windows'
|
||||
linux:
|
||||
BuildSuffix: 'linux'
|
||||
ScriptFolder: 'linux'
|
||||
steps:
|
||||
- template: ./sync-source.yml
|
||||
parameters:
|
||||
artifactSource: $(parameters.artifactSource)
|
||||
needSubmodules: 'true'
|
||||
- template: ./build-single.yml
|
||||
parameters:
|
||||
artifactSource: 'false'
|
||||
30
.ci/templates/build-testing.yml
Normal file
30
.ci/templates/build-testing.yml
Normal file
@@ -0,0 +1,30 @@
|
||||
jobs:
|
||||
- job: build_test
|
||||
displayName: 'testing'
|
||||
pool:
|
||||
vmImage: ubuntu-latest
|
||||
strategy:
|
||||
maxParallel: 10
|
||||
matrix:
|
||||
windows:
|
||||
BuildSuffix: 'windows-testing'
|
||||
ScriptFolder: 'windows'
|
||||
steps:
|
||||
- task: PythonScript@0
|
||||
condition: eq(variables['Build.Reason'], 'PullRequest')
|
||||
displayName: 'Determine Testing Status'
|
||||
inputs:
|
||||
scriptSource: 'filePath'
|
||||
scriptPath: '../scripts/merge/check-label-presence.py'
|
||||
arguments: '$(System.PullRequest.PullRequestNumber) create-testing-build'
|
||||
- ${{ if eq(variables.enabletesting, 'true') }}:
|
||||
- template: ./sync-source.yml
|
||||
parameters:
|
||||
artifactSource: $(parameters.artifactSource)
|
||||
needSubmodules: 'true'
|
||||
- template: ./mergebot.yml
|
||||
parameters:
|
||||
matchLabel: 'testing-merge'
|
||||
- template: ./build-single.yml
|
||||
parameters:
|
||||
artifactSource: 'false'
|
||||
14
.ci/templates/format-check.yml
Normal file
14
.ci/templates/format-check.yml
Normal file
@@ -0,0 +1,14 @@
|
||||
parameters:
|
||||
artifactSource: 'true'
|
||||
|
||||
steps:
|
||||
- template: ./sync-source.yml
|
||||
parameters:
|
||||
artifactSource: $(parameters.artifactSource)
|
||||
needSubmodules: 'false'
|
||||
- task: DockerInstaller@0
|
||||
displayName: 'Prepare Environment'
|
||||
inputs:
|
||||
dockerVersion: '17.09.0-ce'
|
||||
- script: chmod a+x ./.ci/scripts/format/exec.sh && ./.ci/scripts/format/exec.sh
|
||||
displayName: 'Verify Formatting'
|
||||
46
.ci/templates/merge.yml
Normal file
46
.ci/templates/merge.yml
Normal file
@@ -0,0 +1,46 @@
|
||||
jobs:
|
||||
- job: merge
|
||||
displayName: 'pull requests'
|
||||
steps:
|
||||
- checkout: self
|
||||
submodules: recursive
|
||||
- template: ./mergebot.yml
|
||||
parameters:
|
||||
matchLabel: '$(BuildName)-merge'
|
||||
- task: ArchiveFiles@2
|
||||
displayName: 'Package Source'
|
||||
inputs:
|
||||
rootFolderOrFile: '$(System.DefaultWorkingDirectory)'
|
||||
includeRootFolder: false
|
||||
archiveType: '7z'
|
||||
archiveFile: '$(Build.ArtifactStagingDirectory)/yuzu-$(BuildName)-source.7z'
|
||||
- task: PublishPipelineArtifact@1
|
||||
displayName: 'Upload Artifacts'
|
||||
inputs:
|
||||
targetPath: '$(Build.ArtifactStagingDirectory)/yuzu-$(BuildName)-source.7z'
|
||||
artifact: 'yuzu-$(BuildName)-source'
|
||||
replaceExistingArchive: true
|
||||
- job: upload_source
|
||||
displayName: 'upload'
|
||||
dependsOn: merge
|
||||
steps:
|
||||
- template: ./sync-source.yml
|
||||
parameters:
|
||||
artifactSource: 'true'
|
||||
needSubmodules: 'true'
|
||||
- script: chmod a+x $(System.DefaultWorkingDirectory)/.ci/scripts/merge/yuzubot-git-config.sh && $(System.DefaultWorkingDirectory)/.ci/scripts/merge/yuzubot-git-config.sh
|
||||
displayName: 'Apply Git Configuration'
|
||||
- script: git tag -a $(BuildName)-$(Build.BuildId) -m "yuzu $(BuildName) $(Build.BuildNumber) $(Build.DefinitionName)"
|
||||
displayName: 'Tag Source'
|
||||
- script: git remote add other $(GitRepoPushChangesURL)
|
||||
displayName: 'Register Repository'
|
||||
- script: git push --follow-tags --force other HEAD:$(GitPushBranch)
|
||||
displayName: 'Update Code'
|
||||
- script: git rev-list -n 1 $(BuildName)-$(Build.BuildId) > $(Build.ArtifactStagingDirectory)/tag-commit.sha
|
||||
displayName: 'Calculate Release Point'
|
||||
- task: PublishPipelineArtifact@1
|
||||
displayName: 'Upload Release Point'
|
||||
inputs:
|
||||
targetPath: '$(Build.ArtifactStagingDirectory)/tag-commit.sha'
|
||||
artifact: 'yuzu-$(BuildName)-release-point'
|
||||
replaceExistingArchive: true
|
||||
15
.ci/templates/mergebot.yml
Normal file
15
.ci/templates/mergebot.yml
Normal file
@@ -0,0 +1,15 @@
|
||||
parameters:
|
||||
matchLabel: 'dummy-merge'
|
||||
|
||||
steps:
|
||||
- script: mkdir $(System.DefaultWorkingDirectory)/patches && pip install requests urllib3
|
||||
displayName: 'Prepare Environment'
|
||||
- script: chmod a+x $(System.DefaultWorkingDirectory)/.ci/scripts/merge/yuzubot-git-config.sh && $(System.DefaultWorkingDirectory)/.ci/scripts/merge/yuzubot-git-config.sh
|
||||
displayName: 'Apply Git Configuration'
|
||||
- task: PythonScript@0
|
||||
displayName: 'Discover, Download, and Apply Patches'
|
||||
inputs:
|
||||
scriptSource: 'filePath'
|
||||
scriptPath: '.ci/scripts/merge/apply-patches-by-label.py'
|
||||
arguments: '${{ parameters.matchLabel }} patches'
|
||||
workingDirectory: '$(System.DefaultWorkingDirectory)'
|
||||
29
.ci/templates/release.yml
Normal file
29
.ci/templates/release.yml
Normal file
@@ -0,0 +1,29 @@
|
||||
steps:
|
||||
- task: DownloadPipelineArtifact@2
|
||||
displayName: 'Download Windows Release'
|
||||
inputs:
|
||||
artifactName: 'yuzu-$(BuildName)-windows-mingw'
|
||||
buildType: 'current'
|
||||
targetPath: '$(Build.ArtifactStagingDirectory)'
|
||||
- task: DownloadPipelineArtifact@2
|
||||
displayName: 'Download Linux Release'
|
||||
inputs:
|
||||
artifactName: 'yuzu-$(BuildName)-linux'
|
||||
buildType: 'current'
|
||||
targetPath: '$(Build.ArtifactStagingDirectory)'
|
||||
- task: DownloadPipelineArtifact@2
|
||||
displayName: 'Download Release Point'
|
||||
inputs:
|
||||
artifactName: 'yuzu-$(BuildName)-release-point'
|
||||
buildType: 'current'
|
||||
targetPath: '$(Build.ArtifactStagingDirectory)'
|
||||
- script: echo '##vso[task.setvariable variable=tagcommit]' && cat $(Build.ArtifactStagingDirectory)/tag-commit.sha
|
||||
displayName: 'Calculate Release Point'
|
||||
- task: GitHubRelease@0
|
||||
inputs:
|
||||
gitHubConnection: $(GitHubReleaseConnectionName)
|
||||
repositoryName: '$(GitHubReleaseRepoName)'
|
||||
action: 'create'
|
||||
target: $(variables.tagcommit)
|
||||
title: 'yuzu $(BuildName) #$(Build.BuildId)'
|
||||
assets: '$(Build.ArtifactStagingDirectory)/*'
|
||||
16
.ci/templates/retrieve-artifact-source.yml
Normal file
16
.ci/templates/retrieve-artifact-source.yml
Normal file
@@ -0,0 +1,16 @@
|
||||
steps:
|
||||
- checkout: none
|
||||
- task: DownloadPipelineArtifact@2
|
||||
displayName: 'Download Source'
|
||||
inputs:
|
||||
artifactName: 'yuzu-$(BuildName)-source'
|
||||
buildType: 'current'
|
||||
targetPath: '$(Build.ArtifactStagingDirectory)'
|
||||
- script: rm -rf $(System.DefaultWorkingDirectory) && mkdir $(System.DefaultWorkingDirectory)
|
||||
displayName: 'Clean Working Directory'
|
||||
- task: ExtractFiles@1
|
||||
displayName: 'Prepare Source'
|
||||
inputs:
|
||||
archiveFilePatterns: '$(Build.ArtifactStagingDirectory)/*.7z'
|
||||
destinationFolder: '$(System.DefaultWorkingDirectory)'
|
||||
cleanDestinationFolder: false
|
||||
11
.ci/templates/retrieve-master-source.yml
Normal file
11
.ci/templates/retrieve-master-source.yml
Normal file
@@ -0,0 +1,11 @@
|
||||
parameters:
|
||||
needSubmodules: 'true'
|
||||
|
||||
steps:
|
||||
- checkout: self
|
||||
displayName: 'Checkout Recursive'
|
||||
submodules: recursive
|
||||
# condition: eq(parameters.needSubmodules, 'true')
|
||||
#- checkout: self
|
||||
# displayName: 'Checkout Fast'
|
||||
# condition: ne(parameters.needSubmodules, 'true')
|
||||
7
.ci/templates/sync-source.yml
Normal file
7
.ci/templates/sync-source.yml
Normal file
@@ -0,0 +1,7 @@
|
||||
steps:
|
||||
- ${{ if eq(parameters.artifactSource, 'true') }}:
|
||||
- template: ./retrieve-artifact-source.yml
|
||||
- ${{ if ne(parameters.artifactSource, 'true') }}:
|
||||
- template: ./retrieve-master-source.yml
|
||||
parameters:
|
||||
needSubmodules: $(parameters.needSubmodules)
|
||||
@@ -1,19 +1,23 @@
|
||||
# Starter pipeline
|
||||
# Start with a minimal pipeline that you can customize to build and deploy your code.
|
||||
# Add steps that build, run tests, deploy, and more:
|
||||
# https://aka.ms/yaml
|
||||
|
||||
trigger:
|
||||
- master
|
||||
|
||||
pool:
|
||||
vmImage: 'ubuntu-latest'
|
||||
|
||||
steps:
|
||||
- script: echo Hello, world!
|
||||
displayName: 'Run a one-line script'
|
||||
|
||||
- script: |
|
||||
echo Add other tasks to build, test, and deploy your project.
|
||||
echo See https://aka.ms/yaml
|
||||
displayName: 'Run a multi-line script'
|
||||
stages:
|
||||
- stage: merge
|
||||
displayName: 'merge'
|
||||
jobs:
|
||||
- template: ./templates/merge.yml
|
||||
- stage: format
|
||||
dependsOn: merge
|
||||
displayName: 'format'
|
||||
jobs:
|
||||
- job: format
|
||||
displayName: 'clang'
|
||||
pool:
|
||||
vmImage: ubuntu-latest
|
||||
steps:
|
||||
- template: ./templates/format-check.yml
|
||||
- stage: build
|
||||
displayName: 'build'
|
||||
dependsOn: format
|
||||
jobs:
|
||||
- template: ./templates/build-standard.yml
|
||||
|
||||
18
.ci/yuzu-verify.yml
Normal file
18
.ci/yuzu-verify.yml
Normal file
@@ -0,0 +1,18 @@
|
||||
stages:
|
||||
- stage: format
|
||||
displayName: 'format'
|
||||
jobs:
|
||||
- job: format
|
||||
displayName: 'clang'
|
||||
pool:
|
||||
vmImage: ubuntu-latest
|
||||
steps:
|
||||
- template: ./templates/format-check.yml
|
||||
parameters:
|
||||
artifactSource: 'false'
|
||||
- stage: build
|
||||
displayName: 'build'
|
||||
dependsOn: format
|
||||
jobs:
|
||||
- template: ./templates/build-standard.yml
|
||||
- template: ./templates/build-testing.yml
|
||||
19
.ci/yuzu.yml
19
.ci/yuzu.yml
@@ -1,19 +0,0 @@
|
||||
# Starter pipeline
|
||||
# Start with a minimal pipeline that you can customize to build and deploy your code.
|
||||
# Add steps that build, run tests, deploy, and more:
|
||||
# https://aka.ms/yaml
|
||||
|
||||
trigger:
|
||||
- master
|
||||
|
||||
pool:
|
||||
vmImage: 'ubuntu-latest'
|
||||
|
||||
steps:
|
||||
- script: echo Hello, world!
|
||||
displayName: 'Run a one-line script'
|
||||
|
||||
- script: |
|
||||
echo Add other tasks to build, test, and deploy your project.
|
||||
echo See https://aka.ms/yaml
|
||||
displayName: 'Run a multi-line script'
|
||||
@@ -94,6 +94,10 @@ u64 ProgramMetadata::GetFilesystemPermissions() const {
|
||||
return aci_file_access.permissions;
|
||||
}
|
||||
|
||||
u32 ProgramMetadata::GetSystemResourceSize() const {
|
||||
return npdm_header.system_resource_size;
|
||||
}
|
||||
|
||||
const ProgramMetadata::KernelCapabilityDescriptors& ProgramMetadata::GetKernelCapabilities() const {
|
||||
return aci_kernel_capabilities;
|
||||
}
|
||||
|
||||
@@ -58,6 +58,7 @@ public:
|
||||
u32 GetMainThreadStackSize() const;
|
||||
u64 GetTitleID() const;
|
||||
u64 GetFilesystemPermissions() const;
|
||||
u32 GetSystemResourceSize() const;
|
||||
const KernelCapabilityDescriptors& GetKernelCapabilities() const;
|
||||
|
||||
void Print() const;
|
||||
@@ -76,7 +77,8 @@ private:
|
||||
u8 reserved_3;
|
||||
u8 main_thread_priority;
|
||||
u8 main_thread_cpu;
|
||||
std::array<u8, 8> reserved_4;
|
||||
std::array<u8, 4> reserved_4;
|
||||
u32_le system_resource_size;
|
||||
u32_le process_category;
|
||||
u32_le main_stack_size;
|
||||
std::array<u8, 0x10> application_name;
|
||||
|
||||
@@ -129,20 +129,17 @@ u64 Process::GetTotalPhysicalMemoryAvailable() const {
|
||||
return vm_manager.GetTotalPhysicalMemoryAvailable();
|
||||
}
|
||||
|
||||
u64 Process::GetTotalPhysicalMemoryAvailableWithoutMmHeap() const {
|
||||
// TODO: Subtract the personal heap size from this when the
|
||||
// personal heap is implemented.
|
||||
return GetTotalPhysicalMemoryAvailable();
|
||||
u64 Process::GetTotalPhysicalMemoryAvailableWithoutSystemResource() const {
|
||||
return GetTotalPhysicalMemoryAvailable() - GetSystemResourceSize();
|
||||
}
|
||||
|
||||
u64 Process::GetTotalPhysicalMemoryUsed() const {
|
||||
return vm_manager.GetCurrentHeapSize() + main_thread_stack_size + code_memory_size;
|
||||
return vm_manager.GetCurrentHeapSize() + main_thread_stack_size + code_memory_size +
|
||||
GetSystemResourceUsage();
|
||||
}
|
||||
|
||||
u64 Process::GetTotalPhysicalMemoryUsedWithoutMmHeap() const {
|
||||
// TODO: Subtract the personal heap size from this when the
|
||||
// personal heap is implemented.
|
||||
return GetTotalPhysicalMemoryUsed();
|
||||
u64 Process::GetTotalPhysicalMemoryUsedWithoutSystemResource() const {
|
||||
return GetTotalPhysicalMemoryUsed() - GetSystemResourceUsage();
|
||||
}
|
||||
|
||||
void Process::RegisterThread(const Thread* thread) {
|
||||
@@ -172,6 +169,7 @@ ResultCode Process::LoadFromMetadata(const FileSys::ProgramMetadata& metadata) {
|
||||
program_id = metadata.GetTitleID();
|
||||
ideal_core = metadata.GetMainThreadCore();
|
||||
is_64bit_process = metadata.Is64BitProgram();
|
||||
system_resource_size = metadata.GetSystemResourceSize();
|
||||
|
||||
vm_manager.Reset(metadata.GetAddressSpaceType());
|
||||
|
||||
|
||||
@@ -168,8 +168,24 @@ public:
|
||||
return capabilities.GetPriorityMask();
|
||||
}
|
||||
|
||||
u32 IsVirtualMemoryEnabled() const {
|
||||
return is_virtual_address_memory_enabled;
|
||||
/// Gets the amount of secure memory to allocate for memory management.
|
||||
u32 GetSystemResourceSize() const {
|
||||
return system_resource_size;
|
||||
}
|
||||
|
||||
/// Gets the amount of secure memory currently in use for memory management.
|
||||
u32 GetSystemResourceUsage() const {
|
||||
// On hardware, this returns the amount of system resource memory that has
|
||||
// been used by the kernel. This is problematic for Yuzu to emulate, because
|
||||
// system resource memory is used for page tables -- and yuzu doesn't really
|
||||
// have a way to calculate how much memory is required for page tables for
|
||||
// the current process at any given time.
|
||||
// TODO: Is this even worth implementing? Games may retrieve this value via
|
||||
// an SDK function that gets used + available system resource size for debug
|
||||
// or diagnostic purposes. However, it seems unlikely that a game would make
|
||||
// decisions based on how much system memory is dedicated to its page tables.
|
||||
// Is returning a value other than zero wise?
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// Whether this process is an AArch64 or AArch32 process.
|
||||
@@ -196,15 +212,15 @@ public:
|
||||
u64 GetTotalPhysicalMemoryAvailable() const;
|
||||
|
||||
/// Retrieves the total physical memory available to this process in bytes,
|
||||
/// without the size of the personal heap added to it.
|
||||
u64 GetTotalPhysicalMemoryAvailableWithoutMmHeap() const;
|
||||
/// without the size of the personal system resource heap added to it.
|
||||
u64 GetTotalPhysicalMemoryAvailableWithoutSystemResource() const;
|
||||
|
||||
/// Retrieves the total physical memory used by this process in bytes.
|
||||
u64 GetTotalPhysicalMemoryUsed() const;
|
||||
|
||||
/// Retrieves the total physical memory used by this process in bytes,
|
||||
/// without the size of the personal heap added to it.
|
||||
u64 GetTotalPhysicalMemoryUsedWithoutMmHeap() const;
|
||||
/// without the size of the personal system resource heap added to it.
|
||||
u64 GetTotalPhysicalMemoryUsedWithoutSystemResource() const;
|
||||
|
||||
/// Gets the list of all threads created with this process as their owner.
|
||||
const std::list<const Thread*>& GetThreadList() const {
|
||||
@@ -298,12 +314,16 @@ private:
|
||||
/// Title ID corresponding to the process
|
||||
u64 program_id = 0;
|
||||
|
||||
/// Specifies additional memory to be reserved for the process's memory management by the
|
||||
/// system. When this is non-zero, secure memory is allocated and used for page table allocation
|
||||
/// instead of using the normal global page tables/memory block management.
|
||||
u32 system_resource_size = 0;
|
||||
|
||||
/// Resource limit descriptor for this process
|
||||
SharedPtr<ResourceLimit> resource_limit;
|
||||
|
||||
/// The ideal CPU core for this process, threads are scheduled on this core by default.
|
||||
u8 ideal_core = 0;
|
||||
u32 is_virtual_address_memory_enabled = 0;
|
||||
|
||||
/// The Thread Local Storage area is allocated as processes create threads,
|
||||
/// each TLS area is 0x200 bytes, so one page (0x1000) is split up in 8 parts, and each part
|
||||
|
||||
@@ -736,16 +736,16 @@ static ResultCode GetInfo(Core::System& system, u64* result, u64 info_id, u64 ha
|
||||
StackRegionBaseAddr = 14,
|
||||
StackRegionSize = 15,
|
||||
// 3.0.0+
|
||||
IsVirtualAddressMemoryEnabled = 16,
|
||||
PersonalMmHeapUsage = 17,
|
||||
SystemResourceSize = 16,
|
||||
SystemResourceUsage = 17,
|
||||
TitleId = 18,
|
||||
// 4.0.0+
|
||||
PrivilegedProcessId = 19,
|
||||
// 5.0.0+
|
||||
UserExceptionContextAddr = 20,
|
||||
// 6.0.0+
|
||||
TotalPhysicalMemoryAvailableWithoutMmHeap = 21,
|
||||
TotalPhysicalMemoryUsedWithoutMmHeap = 22,
|
||||
TotalPhysicalMemoryAvailableWithoutSystemResource = 21,
|
||||
TotalPhysicalMemoryUsedWithoutSystemResource = 22,
|
||||
};
|
||||
|
||||
const auto info_id_type = static_cast<GetInfoType>(info_id);
|
||||
@@ -763,12 +763,12 @@ static ResultCode GetInfo(Core::System& system, u64* result, u64 info_id, u64 ha
|
||||
case GetInfoType::StackRegionSize:
|
||||
case GetInfoType::TotalPhysicalMemoryAvailable:
|
||||
case GetInfoType::TotalPhysicalMemoryUsed:
|
||||
case GetInfoType::IsVirtualAddressMemoryEnabled:
|
||||
case GetInfoType::PersonalMmHeapUsage:
|
||||
case GetInfoType::SystemResourceSize:
|
||||
case GetInfoType::SystemResourceUsage:
|
||||
case GetInfoType::TitleId:
|
||||
case GetInfoType::UserExceptionContextAddr:
|
||||
case GetInfoType::TotalPhysicalMemoryAvailableWithoutMmHeap:
|
||||
case GetInfoType::TotalPhysicalMemoryUsedWithoutMmHeap: {
|
||||
case GetInfoType::TotalPhysicalMemoryAvailableWithoutSystemResource:
|
||||
case GetInfoType::TotalPhysicalMemoryUsedWithoutSystemResource: {
|
||||
if (info_sub_id != 0) {
|
||||
return ERR_INVALID_ENUM_VALUE;
|
||||
}
|
||||
@@ -829,8 +829,13 @@ static ResultCode GetInfo(Core::System& system, u64* result, u64 info_id, u64 ha
|
||||
*result = process->GetTotalPhysicalMemoryUsed();
|
||||
return RESULT_SUCCESS;
|
||||
|
||||
case GetInfoType::IsVirtualAddressMemoryEnabled:
|
||||
*result = process->IsVirtualMemoryEnabled();
|
||||
case GetInfoType::SystemResourceSize:
|
||||
*result = process->GetSystemResourceSize();
|
||||
return RESULT_SUCCESS;
|
||||
|
||||
case GetInfoType::SystemResourceUsage:
|
||||
LOG_WARNING(Kernel_SVC, "(STUBBED) Attempted to query system resource usage");
|
||||
*result = process->GetSystemResourceUsage();
|
||||
return RESULT_SUCCESS;
|
||||
|
||||
case GetInfoType::TitleId:
|
||||
@@ -843,12 +848,12 @@ static ResultCode GetInfo(Core::System& system, u64* result, u64 info_id, u64 ha
|
||||
*result = 0;
|
||||
return RESULT_SUCCESS;
|
||||
|
||||
case GetInfoType::TotalPhysicalMemoryAvailableWithoutMmHeap:
|
||||
*result = process->GetTotalPhysicalMemoryAvailable();
|
||||
case GetInfoType::TotalPhysicalMemoryAvailableWithoutSystemResource:
|
||||
*result = process->GetTotalPhysicalMemoryAvailableWithoutSystemResource();
|
||||
return RESULT_SUCCESS;
|
||||
|
||||
case GetInfoType::TotalPhysicalMemoryUsedWithoutMmHeap:
|
||||
*result = process->GetTotalPhysicalMemoryUsedWithoutMmHeap();
|
||||
case GetInfoType::TotalPhysicalMemoryUsedWithoutSystemResource:
|
||||
*result = process->GetTotalPhysicalMemoryUsedWithoutSystemResource();
|
||||
return RESULT_SUCCESS;
|
||||
|
||||
default:
|
||||
@@ -953,6 +958,86 @@ static ResultCode GetInfo(Core::System& system, u64* result, u64 info_id, u64 ha
|
||||
}
|
||||
}
|
||||
|
||||
/// Maps memory at a desired address
|
||||
static ResultCode MapPhysicalMemory(Core::System& system, VAddr addr, u64 size) {
|
||||
LOG_DEBUG(Kernel_SVC, "called, addr=0x{:016X}, size=0x{:X}", addr, size);
|
||||
|
||||
if (!Common::Is4KBAligned(addr)) {
|
||||
LOG_ERROR(Kernel_SVC, "Address is not aligned to 4KB, 0x{:016X}", addr);
|
||||
return ERR_INVALID_ADDRESS;
|
||||
}
|
||||
|
||||
if (!Common::Is4KBAligned(size)) {
|
||||
LOG_ERROR(Kernel_SVC, "Size is not aligned to 4KB, 0x{:X}", size);
|
||||
return ERR_INVALID_SIZE;
|
||||
}
|
||||
|
||||
if (size == 0) {
|
||||
LOG_ERROR(Kernel_SVC, "Size is zero");
|
||||
return ERR_INVALID_SIZE;
|
||||
}
|
||||
|
||||
if (!(addr < addr + size)) {
|
||||
LOG_ERROR(Kernel_SVC, "Size causes 64-bit overflow of address");
|
||||
return ERR_INVALID_MEMORY_RANGE;
|
||||
}
|
||||
|
||||
Process* const current_process = system.Kernel().CurrentProcess();
|
||||
auto& vm_manager = current_process->VMManager();
|
||||
|
||||
if (current_process->GetSystemResourceSize() == 0) {
|
||||
LOG_ERROR(Kernel_SVC, "System Resource Size is zero");
|
||||
return ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
if (!vm_manager.IsWithinMapRegion(addr, size)) {
|
||||
LOG_ERROR(Kernel_SVC, "Range not within map region");
|
||||
return ERR_INVALID_MEMORY_RANGE;
|
||||
}
|
||||
|
||||
return vm_manager.MapPhysicalMemory(addr, size);
|
||||
}
|
||||
|
||||
/// Unmaps memory previously mapped via MapPhysicalMemory
|
||||
static ResultCode UnmapPhysicalMemory(Core::System& system, VAddr addr, u64 size) {
|
||||
LOG_DEBUG(Kernel_SVC, "called, addr=0x{:016X}, size=0x{:X}", addr, size);
|
||||
|
||||
if (!Common::Is4KBAligned(addr)) {
|
||||
LOG_ERROR(Kernel_SVC, "Address is not aligned to 4KB, 0x{:016X}", addr);
|
||||
return ERR_INVALID_ADDRESS;
|
||||
}
|
||||
|
||||
if (!Common::Is4KBAligned(size)) {
|
||||
LOG_ERROR(Kernel_SVC, "Size is not aligned to 4KB, 0x{:X}", size);
|
||||
return ERR_INVALID_SIZE;
|
||||
}
|
||||
|
||||
if (size == 0) {
|
||||
LOG_ERROR(Kernel_SVC, "Size is zero");
|
||||
return ERR_INVALID_SIZE;
|
||||
}
|
||||
|
||||
if (!(addr < addr + size)) {
|
||||
LOG_ERROR(Kernel_SVC, "Size causes 64-bit overflow of address");
|
||||
return ERR_INVALID_MEMORY_RANGE;
|
||||
}
|
||||
|
||||
Process* const current_process = system.Kernel().CurrentProcess();
|
||||
auto& vm_manager = current_process->VMManager();
|
||||
|
||||
if (current_process->GetSystemResourceSize() == 0) {
|
||||
LOG_ERROR(Kernel_SVC, "System Resource Size is zero");
|
||||
return ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
if (!vm_manager.IsWithinMapRegion(addr, size)) {
|
||||
LOG_ERROR(Kernel_SVC, "Range not within map region");
|
||||
return ERR_INVALID_MEMORY_RANGE;
|
||||
}
|
||||
|
||||
return vm_manager.UnmapPhysicalMemory(addr, size);
|
||||
}
|
||||
|
||||
/// Sets the thread activity
|
||||
static ResultCode SetThreadActivity(Core::System& system, Handle handle, u32 activity) {
|
||||
LOG_DEBUG(Kernel_SVC, "called, handle=0x{:08X}, activity=0x{:08X}", handle, activity);
|
||||
@@ -2310,8 +2395,8 @@ static const FunctionDef SVC_Table[] = {
|
||||
{0x29, SvcWrap<GetInfo>, "GetInfo"},
|
||||
{0x2A, nullptr, "FlushEntireDataCache"},
|
||||
{0x2B, nullptr, "FlushDataCache"},
|
||||
{0x2C, nullptr, "MapPhysicalMemory"},
|
||||
{0x2D, nullptr, "UnmapPhysicalMemory"},
|
||||
{0x2C, SvcWrap<MapPhysicalMemory>, "MapPhysicalMemory"},
|
||||
{0x2D, SvcWrap<UnmapPhysicalMemory>, "UnmapPhysicalMemory"},
|
||||
{0x2E, nullptr, "GetFutureThreadInfo"},
|
||||
{0x2F, nullptr, "GetLastThreadInfo"},
|
||||
{0x30, SvcWrap<GetResourceLimitLimitValue>, "GetResourceLimitLimitValue"},
|
||||
|
||||
@@ -32,6 +32,11 @@ void SvcWrap(Core::System& system) {
|
||||
FuncReturn(system, func(system, Param(system, 0)).raw);
|
||||
}
|
||||
|
||||
template <ResultCode func(Core::System&, u64, u64)>
|
||||
void SvcWrap(Core::System& system) {
|
||||
FuncReturn(system, func(system, Param(system, 0), Param(system, 1)).raw);
|
||||
}
|
||||
|
||||
template <ResultCode func(Core::System&, u32)>
|
||||
void SvcWrap(Core::System& system) {
|
||||
FuncReturn(system, func(system, static_cast<u32>(Param(system, 0))).raw);
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
#include "core/core.h"
|
||||
#include "core/file_sys/program_metadata.h"
|
||||
#include "core/hle/kernel/errors.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
#include "core/hle/kernel/resource_limit.h"
|
||||
#include "core/hle/kernel/vm_manager.h"
|
||||
#include "core/memory.h"
|
||||
#include "core/memory_setup.h"
|
||||
@@ -48,10 +50,14 @@ bool VirtualMemoryArea::CanBeMergedWith(const VirtualMemoryArea& next) const {
|
||||
type != next.type) {
|
||||
return false;
|
||||
}
|
||||
if (type == VMAType::AllocatedMemoryBlock &&
|
||||
(backing_block != next.backing_block || offset + size != next.offset)) {
|
||||
if ((attribute & MemoryAttribute::DeviceMapped) == MemoryAttribute::DeviceMapped) {
|
||||
// TODO: Can device mapped memory be merged sanely?
|
||||
// Not merging it may cause inaccuracies versus hardware when memory layout is queried.
|
||||
return false;
|
||||
}
|
||||
if (type == VMAType::AllocatedMemoryBlock) {
|
||||
return true;
|
||||
}
|
||||
if (type == VMAType::BackingMemory && backing_memory + size != next.backing_memory) {
|
||||
return false;
|
||||
}
|
||||
@@ -99,7 +105,7 @@ bool VMManager::IsValidHandle(VMAHandle handle) const {
|
||||
ResultVal<VMManager::VMAHandle> VMManager::MapMemoryBlock(VAddr target,
|
||||
std::shared_ptr<std::vector<u8>> block,
|
||||
std::size_t offset, u64 size,
|
||||
MemoryState state) {
|
||||
MemoryState state, VMAPermission perm) {
|
||||
ASSERT(block != nullptr);
|
||||
ASSERT(offset + size <= block->size());
|
||||
|
||||
@@ -109,7 +115,7 @@ ResultVal<VMManager::VMAHandle> VMManager::MapMemoryBlock(VAddr target,
|
||||
ASSERT(final_vma.size == size);
|
||||
|
||||
final_vma.type = VMAType::AllocatedMemoryBlock;
|
||||
final_vma.permissions = VMAPermission::ReadWrite;
|
||||
final_vma.permissions = perm;
|
||||
final_vma.state = state;
|
||||
final_vma.backing_block = std::move(block);
|
||||
final_vma.offset = offset;
|
||||
@@ -288,6 +294,166 @@ ResultVal<VAddr> VMManager::SetHeapSize(u64 size) {
|
||||
return MakeResult<VAddr>(heap_region_base);
|
||||
}
|
||||
|
||||
ResultCode VMManager::MapPhysicalMemory(VAddr target, u64 size) {
|
||||
const auto end_addr = target + size;
|
||||
const auto last_addr = end_addr - 1;
|
||||
VAddr cur_addr = target;
|
||||
|
||||
ResultCode result = RESULT_SUCCESS;
|
||||
|
||||
// Check how much memory we've already mapped.
|
||||
const auto mapped_size_result = SizeOfAllocatedVMAsInRange(target, size);
|
||||
if (mapped_size_result.Failed()) {
|
||||
return mapped_size_result.Code();
|
||||
}
|
||||
|
||||
// If we've already mapped the desired amount, return early.
|
||||
const std::size_t mapped_size = *mapped_size_result;
|
||||
if (mapped_size == size) {
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
// Check that we can map the memory we want.
|
||||
const auto res_limit = system.CurrentProcess()->GetResourceLimit();
|
||||
const u64 physmem_remaining = res_limit->GetMaxResourceValue(ResourceType::PhysicalMemory) -
|
||||
res_limit->GetCurrentResourceValue(ResourceType::PhysicalMemory);
|
||||
if (physmem_remaining < (size - mapped_size)) {
|
||||
return ERR_RESOURCE_LIMIT_EXCEEDED;
|
||||
}
|
||||
|
||||
// Keep track of the memory regions we unmap.
|
||||
std::vector<std::pair<u64, u64>> mapped_regions;
|
||||
|
||||
// Iterate, trying to map memory.
|
||||
{
|
||||
cur_addr = target;
|
||||
|
||||
auto iter = FindVMA(target);
|
||||
ASSERT_MSG(iter != vma_map.end(), "MapPhysicalMemory iter != end");
|
||||
|
||||
while (true) {
|
||||
const auto& vma = iter->second;
|
||||
const auto vma_start = vma.base;
|
||||
const auto vma_end = vma_start + vma.size;
|
||||
const auto vma_last = vma_end - 1;
|
||||
|
||||
// Map the memory block
|
||||
const auto map_size = std::min(end_addr - cur_addr, vma_end - cur_addr);
|
||||
if (vma.state == MemoryState::Unmapped) {
|
||||
const auto map_res =
|
||||
MapMemoryBlock(cur_addr, std::make_shared<std::vector<u8>>(map_size, 0), 0,
|
||||
map_size, MemoryState::Heap, VMAPermission::ReadWrite);
|
||||
result = map_res.Code();
|
||||
if (result.IsError()) {
|
||||
break;
|
||||
}
|
||||
|
||||
mapped_regions.emplace_back(cur_addr, map_size);
|
||||
}
|
||||
|
||||
// Break once we hit the end of the range.
|
||||
if (last_addr <= vma_last) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Advance to the next block.
|
||||
cur_addr = vma_end;
|
||||
iter = FindVMA(cur_addr);
|
||||
ASSERT_MSG(iter != vma_map.end(), "MapPhysicalMemory iter != end");
|
||||
}
|
||||
}
|
||||
|
||||
// If we failed, unmap memory.
|
||||
if (result.IsError()) {
|
||||
for (const auto [unmap_address, unmap_size] : mapped_regions) {
|
||||
ASSERT_MSG(UnmapRange(unmap_address, unmap_size).IsSuccess(),
|
||||
"MapPhysicalMemory un-map on error");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Update amount of mapped physical memory.
|
||||
physical_memory_mapped += size - mapped_size;
|
||||
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
ResultCode VMManager::UnmapPhysicalMemory(VAddr target, u64 size) {
|
||||
const auto end_addr = target + size;
|
||||
const auto last_addr = end_addr - 1;
|
||||
VAddr cur_addr = target;
|
||||
|
||||
ResultCode result = RESULT_SUCCESS;
|
||||
|
||||
// Check how much memory is currently mapped.
|
||||
const auto mapped_size_result = SizeOfUnmappablePhysicalMemoryInRange(target, size);
|
||||
if (mapped_size_result.Failed()) {
|
||||
return mapped_size_result.Code();
|
||||
}
|
||||
|
||||
// If we've already unmapped all the memory, return early.
|
||||
const std::size_t mapped_size = *mapped_size_result;
|
||||
if (mapped_size == 0) {
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
// Keep track of the memory regions we unmap.
|
||||
std::vector<std::pair<u64, u64>> unmapped_regions;
|
||||
|
||||
// Try to unmap regions.
|
||||
{
|
||||
cur_addr = target;
|
||||
|
||||
auto iter = FindVMA(target);
|
||||
ASSERT_MSG(iter != vma_map.end(), "UnmapPhysicalMemory iter != end");
|
||||
|
||||
while (true) {
|
||||
const auto& vma = iter->second;
|
||||
const auto vma_start = vma.base;
|
||||
const auto vma_end = vma_start + vma.size;
|
||||
const auto vma_last = vma_end - 1;
|
||||
|
||||
// Unmap the memory block
|
||||
const auto unmap_size = std::min(end_addr - cur_addr, vma_end - cur_addr);
|
||||
if (vma.state == MemoryState::Heap) {
|
||||
result = UnmapRange(cur_addr, unmap_size);
|
||||
if (result.IsError()) {
|
||||
break;
|
||||
}
|
||||
|
||||
unmapped_regions.emplace_back(cur_addr, unmap_size);
|
||||
}
|
||||
|
||||
// Break once we hit the end of the range.
|
||||
if (last_addr <= vma_last) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Advance to the next block.
|
||||
cur_addr = vma_end;
|
||||
iter = FindVMA(cur_addr);
|
||||
ASSERT_MSG(iter != vma_map.end(), "UnmapPhysicalMemory iter != end");
|
||||
}
|
||||
}
|
||||
|
||||
// If we failed, re-map regions.
|
||||
// TODO: Preserve memory contents?
|
||||
if (result.IsError()) {
|
||||
for (const auto [map_address, map_size] : unmapped_regions) {
|
||||
const auto remap_res =
|
||||
MapMemoryBlock(map_address, std::make_shared<std::vector<u8>>(map_size, 0), 0,
|
||||
map_size, MemoryState::Heap, VMAPermission::None);
|
||||
ASSERT_MSG(remap_res.Succeeded(), "UnmapPhysicalMemory re-map on error");
|
||||
}
|
||||
}
|
||||
|
||||
// Update mapped amount
|
||||
physical_memory_mapped -= mapped_size;
|
||||
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
ResultCode VMManager::MapCodeMemory(VAddr dst_address, VAddr src_address, u64 size) {
|
||||
constexpr auto ignore_attribute = MemoryAttribute::LockedForIPC | MemoryAttribute::DeviceMapped;
|
||||
const auto src_check_result = CheckRangeState(
|
||||
@@ -435,7 +601,7 @@ ResultCode VMManager::MirrorMemory(VAddr dst_addr, VAddr src_addr, u64 size, Mem
|
||||
// Protect mirror with permissions from old region
|
||||
Reprotect(new_vma, vma->second.permissions);
|
||||
// Remove permissions from old region
|
||||
Reprotect(vma, VMAPermission::None);
|
||||
ReprotectRange(src_addr, size, VMAPermission::None);
|
||||
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
@@ -568,14 +734,14 @@ VMManager::VMAIter VMManager::SplitVMA(VMAIter vma_handle, u64 offset_in_vma) {
|
||||
VMManager::VMAIter VMManager::MergeAdjacent(VMAIter iter) {
|
||||
const VMAIter next_vma = std::next(iter);
|
||||
if (next_vma != vma_map.end() && iter->second.CanBeMergedWith(next_vma->second)) {
|
||||
iter->second.size += next_vma->second.size;
|
||||
MergeAdjacentVMA(iter->second, next_vma->second);
|
||||
vma_map.erase(next_vma);
|
||||
}
|
||||
|
||||
if (iter != vma_map.begin()) {
|
||||
VMAIter prev_vma = std::prev(iter);
|
||||
if (prev_vma->second.CanBeMergedWith(iter->second)) {
|
||||
prev_vma->second.size += iter->second.size;
|
||||
MergeAdjacentVMA(prev_vma->second, iter->second);
|
||||
vma_map.erase(iter);
|
||||
iter = prev_vma;
|
||||
}
|
||||
@@ -584,6 +750,38 @@ VMManager::VMAIter VMManager::MergeAdjacent(VMAIter iter) {
|
||||
return iter;
|
||||
}
|
||||
|
||||
void VMManager::MergeAdjacentVMA(VirtualMemoryArea& left, const VirtualMemoryArea& right) {
|
||||
ASSERT(left.CanBeMergedWith(right));
|
||||
|
||||
// Always merge allocated memory blocks, even when they don't share the same backing block.
|
||||
if (left.type == VMAType::AllocatedMemoryBlock &&
|
||||
(left.backing_block != right.backing_block || left.offset + left.size != right.offset)) {
|
||||
// Check if we can save work.
|
||||
if (left.offset == 0 && left.size == left.backing_block->size()) {
|
||||
// Fast case: left is an entire backing block.
|
||||
left.backing_block->insert(left.backing_block->end(),
|
||||
right.backing_block->begin() + right.offset,
|
||||
right.backing_block->begin() + right.offset + right.size);
|
||||
} else {
|
||||
// Slow case: make a new memory block for left and right.
|
||||
auto new_memory = std::make_shared<std::vector<u8>>();
|
||||
new_memory->insert(new_memory->end(), left.backing_block->begin() + left.offset,
|
||||
left.backing_block->begin() + left.offset + left.size);
|
||||
new_memory->insert(new_memory->end(), right.backing_block->begin() + right.offset,
|
||||
right.backing_block->begin() + right.offset + right.size);
|
||||
left.backing_block = new_memory;
|
||||
left.offset = 0;
|
||||
}
|
||||
|
||||
// Page table update is needed, because backing memory changed.
|
||||
left.size += right.size;
|
||||
UpdatePageTableForVMA(left);
|
||||
} else {
|
||||
// Just update the size.
|
||||
left.size += right.size;
|
||||
}
|
||||
}
|
||||
|
||||
void VMManager::UpdatePageTableForVMA(const VirtualMemoryArea& vma) {
|
||||
switch (vma.type) {
|
||||
case VMAType::Free:
|
||||
@@ -758,6 +956,84 @@ VMManager::CheckResults VMManager::CheckRangeState(VAddr address, u64 size, Memo
|
||||
std::make_tuple(initial_state, initial_permissions, initial_attributes & ~ignore_mask));
|
||||
}
|
||||
|
||||
ResultVal<std::size_t> VMManager::SizeOfAllocatedVMAsInRange(VAddr address,
|
||||
std::size_t size) const {
|
||||
const VAddr end_addr = address + size;
|
||||
const VAddr last_addr = end_addr - 1;
|
||||
std::size_t mapped_size = 0;
|
||||
|
||||
VAddr cur_addr = address;
|
||||
auto iter = FindVMA(cur_addr);
|
||||
ASSERT_MSG(iter != vma_map.end(), "SizeOfAllocatedVMAsInRange iter != end");
|
||||
|
||||
while (true) {
|
||||
const auto& vma = iter->second;
|
||||
const VAddr vma_start = vma.base;
|
||||
const VAddr vma_end = vma_start + vma.size;
|
||||
const VAddr vma_last = vma_end - 1;
|
||||
|
||||
// Add size if relevant.
|
||||
if (vma.state != MemoryState::Unmapped) {
|
||||
mapped_size += std::min(end_addr - cur_addr, vma_end - cur_addr);
|
||||
}
|
||||
|
||||
// Break once we hit the end of the range.
|
||||
if (last_addr <= vma_last) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Advance to the next block.
|
||||
cur_addr = vma_end;
|
||||
iter = std::next(iter);
|
||||
ASSERT_MSG(iter != vma_map.end(), "SizeOfAllocatedVMAsInRange iter != end");
|
||||
}
|
||||
|
||||
return MakeResult(mapped_size);
|
||||
}
|
||||
|
||||
ResultVal<std::size_t> VMManager::SizeOfUnmappablePhysicalMemoryInRange(VAddr address,
|
||||
std::size_t size) const {
|
||||
const VAddr end_addr = address + size;
|
||||
const VAddr last_addr = end_addr - 1;
|
||||
std::size_t mapped_size = 0;
|
||||
|
||||
VAddr cur_addr = address;
|
||||
auto iter = FindVMA(cur_addr);
|
||||
ASSERT_MSG(iter != vma_map.end(), "SizeOfUnmappablePhysicalMemoryInRange iter != end");
|
||||
|
||||
while (true) {
|
||||
const auto& vma = iter->second;
|
||||
const auto vma_start = vma.base;
|
||||
const auto vma_end = vma_start + vma.size;
|
||||
const auto vma_last = vma_end - 1;
|
||||
const auto state = vma.state;
|
||||
const auto attr = vma.attribute;
|
||||
|
||||
// Memory within region must be free or mapped heap.
|
||||
if (!((state == MemoryState::Heap && attr == MemoryAttribute::None) ||
|
||||
(state == MemoryState::Unmapped))) {
|
||||
return ERR_INVALID_ADDRESS_STATE;
|
||||
}
|
||||
|
||||
// Add size if relevant.
|
||||
if (state != MemoryState::Unmapped) {
|
||||
mapped_size += std::min(end_addr - cur_addr, vma_end - cur_addr);
|
||||
}
|
||||
|
||||
// Break once we hit the end of the range.
|
||||
if (last_addr <= vma_last) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Advance to the next block.
|
||||
cur_addr = vma_end;
|
||||
iter = std::next(iter);
|
||||
ASSERT_MSG(iter != vma_map.end(), "SizeOfUnmappablePhysicalMemoryInRange iter != end");
|
||||
}
|
||||
|
||||
return MakeResult(mapped_size);
|
||||
}
|
||||
|
||||
u64 VMManager::GetTotalPhysicalMemoryAvailable() const {
|
||||
LOG_WARNING(Kernel, "(STUBBED) called");
|
||||
return 0xF8000000;
|
||||
|
||||
@@ -349,7 +349,8 @@ public:
|
||||
* @param state MemoryState tag to attach to the VMA.
|
||||
*/
|
||||
ResultVal<VMAHandle> MapMemoryBlock(VAddr target, std::shared_ptr<std::vector<u8>> block,
|
||||
std::size_t offset, u64 size, MemoryState state);
|
||||
std::size_t offset, u64 size, MemoryState state,
|
||||
VMAPermission perm = VMAPermission::ReadWrite);
|
||||
|
||||
/**
|
||||
* Maps an unmanaged host memory pointer at a given address.
|
||||
@@ -450,6 +451,34 @@ public:
|
||||
///
|
||||
ResultVal<VAddr> SetHeapSize(u64 size);
|
||||
|
||||
/// Maps memory at a given address.
|
||||
///
|
||||
/// @param addr The virtual address to map memory at.
|
||||
/// @param size The amount of memory to map.
|
||||
///
|
||||
/// @note The destination address must lie within the Map region.
|
||||
///
|
||||
/// @note This function requires that SystemResourceSize be non-zero,
|
||||
/// however, this is just because if it were not then the
|
||||
/// resulting page tables could be exploited on hardware by
|
||||
/// a malicious program. SystemResource usage does not need
|
||||
/// to be explicitly checked or updated here.
|
||||
ResultCode MapPhysicalMemory(VAddr target, u64 size);
|
||||
|
||||
/// Unmaps memory at a given address.
|
||||
///
|
||||
/// @param addr The virtual address to unmap memory at.
|
||||
/// @param size The amount of memory to unmap.
|
||||
///
|
||||
/// @note The destination address must lie within the Map region.
|
||||
///
|
||||
/// @note This function requires that SystemResourceSize be non-zero,
|
||||
/// however, this is just because if it were not then the
|
||||
/// resulting page tables could be exploited on hardware by
|
||||
/// a malicious program. SystemResource usage does not need
|
||||
/// to be explicitly checked or updated here.
|
||||
ResultCode UnmapPhysicalMemory(VAddr target, u64 size);
|
||||
|
||||
/// Maps a region of memory as code memory.
|
||||
///
|
||||
/// @param dst_address The base address of the region to create the aliasing memory region.
|
||||
@@ -657,6 +686,11 @@ private:
|
||||
*/
|
||||
VMAIter MergeAdjacent(VMAIter vma);
|
||||
|
||||
/**
|
||||
* Merges two adjacent VMAs.
|
||||
*/
|
||||
void MergeAdjacentVMA(VirtualMemoryArea& left, const VirtualMemoryArea& right);
|
||||
|
||||
/// Updates the pages corresponding to this VMA so they match the VMA's attributes.
|
||||
void UpdatePageTableForVMA(const VirtualMemoryArea& vma);
|
||||
|
||||
@@ -701,6 +735,13 @@ private:
|
||||
MemoryAttribute attribute_mask, MemoryAttribute attribute,
|
||||
MemoryAttribute ignore_mask) const;
|
||||
|
||||
/// Gets the amount of memory currently mapped (state != Unmapped) in a range.
|
||||
ResultVal<std::size_t> SizeOfAllocatedVMAsInRange(VAddr address, std::size_t size) const;
|
||||
|
||||
/// Gets the amount of memory unmappable by UnmapPhysicalMemory in a range.
|
||||
ResultVal<std::size_t> SizeOfUnmappablePhysicalMemoryInRange(VAddr address,
|
||||
std::size_t size) const;
|
||||
|
||||
/**
|
||||
* A map covering the entirety of the managed address space, keyed by the `base` field of each
|
||||
* VMA. It must always be modified by splitting or merging VMAs, so that the invariant
|
||||
@@ -742,6 +783,11 @@ private:
|
||||
// end of the range. This is essentially 'base_address + current_size'.
|
||||
VAddr heap_end = 0;
|
||||
|
||||
// The current amount of memory mapped via MapPhysicalMemory.
|
||||
// This is used here (and in Nintendo's kernel) only for debugging, and does not impact
|
||||
// any behavior.
|
||||
u64 physical_memory_mapped = 0;
|
||||
|
||||
Core::System& system;
|
||||
};
|
||||
} // namespace Kernel
|
||||
|
||||
@@ -1278,6 +1278,7 @@ union Instruction {
|
||||
union {
|
||||
BitField<49, 1, u64> nodep_flag;
|
||||
BitField<53, 4, u64> texture_info;
|
||||
BitField<59, 1, u64> fp32_flag;
|
||||
|
||||
TextureType GetTextureType() const {
|
||||
// The TLDS instruction has a weird encoding for the texture type.
|
||||
@@ -1776,7 +1777,7 @@ private:
|
||||
INST("1101111101010---", Id::TXQ_B, Type::Texture, "TXQ_B"),
|
||||
INST("1101-00---------", Id::TEXS, Type::Texture, "TEXS"),
|
||||
INST("11011100--11----", Id::TLD, Type::Texture, "TLD"),
|
||||
INST("1101101---------", Id::TLDS, Type::Texture, "TLDS"),
|
||||
INST("1101-01---------", Id::TLDS, Type::Texture, "TLDS"),
|
||||
INST("110010----111---", Id::TLD4, Type::Texture, "TLD4"),
|
||||
INST("1101111100------", Id::TLD4S, Type::Texture, "TLD4S"),
|
||||
INST("110111110110----", Id::TMML_B, Type::Texture, "TMML_B"),
|
||||
|
||||
@@ -31,7 +31,7 @@ u32 FramebufferConfig::BytesPerPixel(PixelFormat format) {
|
||||
|
||||
GPU::GPU(Core::System& system, VideoCore::RendererBase& renderer) : renderer{renderer} {
|
||||
auto& rasterizer{renderer.Rasterizer()};
|
||||
memory_manager = std::make_unique<Tegra::MemoryManager>(rasterizer);
|
||||
memory_manager = std::make_unique<Tegra::MemoryManager>(system, rasterizer);
|
||||
dma_pusher = std::make_unique<Tegra::DmaPusher>(*this);
|
||||
maxwell_3d = std::make_unique<Engines::Maxwell3D>(system, rasterizer, *memory_manager);
|
||||
fermi_2d = std::make_unique<Engines::Fermi2D>(rasterizer, *memory_manager);
|
||||
|
||||
@@ -4,14 +4,18 @@
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/microprofile.h"
|
||||
#include "video_core/engines/maxwell_3d.h"
|
||||
#include "video_core/macro_interpreter.h"
|
||||
|
||||
MICROPROFILE_DEFINE(MacroInterp, "GPU", "Execute macro interpreter", MP_RGB(128, 128, 192));
|
||||
|
||||
namespace Tegra {
|
||||
|
||||
MacroInterpreter::MacroInterpreter(Engines::Maxwell3D& maxwell3d) : maxwell3d(maxwell3d) {}
|
||||
|
||||
void MacroInterpreter::Execute(u32 offset, std::vector<u32> parameters) {
|
||||
MICROPROFILE_SCOPE(MacroInterp);
|
||||
Reset();
|
||||
registers[1] = parameters[0];
|
||||
this->parameters = std::move(parameters);
|
||||
|
||||
@@ -5,13 +5,17 @@
|
||||
#include "common/alignment.h"
|
||||
#include "common/assert.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/core.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
#include "core/hle/kernel/vm_manager.h"
|
||||
#include "core/memory.h"
|
||||
#include "video_core/memory_manager.h"
|
||||
#include "video_core/rasterizer_interface.h"
|
||||
|
||||
namespace Tegra {
|
||||
|
||||
MemoryManager::MemoryManager(VideoCore::RasterizerInterface& rasterizer) : rasterizer{rasterizer} {
|
||||
MemoryManager::MemoryManager(Core::System& system, VideoCore::RasterizerInterface& rasterizer)
|
||||
: rasterizer{rasterizer}, system{system} {
|
||||
std::fill(page_table.pointers.begin(), page_table.pointers.end(), nullptr);
|
||||
std::fill(page_table.attributes.begin(), page_table.attributes.end(),
|
||||
Common::PageType::Unmapped);
|
||||
@@ -49,6 +53,11 @@ GPUVAddr MemoryManager::MapBufferEx(VAddr cpu_addr, u64 size) {
|
||||
const GPUVAddr gpu_addr{FindFreeRegion(address_space_base, aligned_size)};
|
||||
|
||||
MapBackingMemory(gpu_addr, Memory::GetPointer(cpu_addr), aligned_size, cpu_addr);
|
||||
ASSERT(system.CurrentProcess()
|
||||
->VMManager()
|
||||
.SetMemoryAttribute(cpu_addr, size, Kernel::MemoryAttribute::DeviceMapped,
|
||||
Kernel::MemoryAttribute::DeviceMapped)
|
||||
.IsSuccess());
|
||||
|
||||
return gpu_addr;
|
||||
}
|
||||
@@ -59,7 +68,11 @@ GPUVAddr MemoryManager::MapBufferEx(VAddr cpu_addr, GPUVAddr gpu_addr, u64 size)
|
||||
const u64 aligned_size{Common::AlignUp(size, page_size)};
|
||||
|
||||
MapBackingMemory(gpu_addr, Memory::GetPointer(cpu_addr), aligned_size, cpu_addr);
|
||||
|
||||
ASSERT(system.CurrentProcess()
|
||||
->VMManager()
|
||||
.SetMemoryAttribute(cpu_addr, size, Kernel::MemoryAttribute::DeviceMapped,
|
||||
Kernel::MemoryAttribute::DeviceMapped)
|
||||
.IsSuccess());
|
||||
return gpu_addr;
|
||||
}
|
||||
|
||||
@@ -68,9 +81,16 @@ GPUVAddr MemoryManager::UnmapBuffer(GPUVAddr gpu_addr, u64 size) {
|
||||
|
||||
const u64 aligned_size{Common::AlignUp(size, page_size)};
|
||||
const CacheAddr cache_addr{ToCacheAddr(GetPointer(gpu_addr))};
|
||||
const auto cpu_addr = GpuToCpuAddress(gpu_addr);
|
||||
ASSERT(cpu_addr);
|
||||
|
||||
rasterizer.FlushAndInvalidateRegion(cache_addr, aligned_size);
|
||||
UnmapRange(gpu_addr, aligned_size);
|
||||
ASSERT(system.CurrentProcess()
|
||||
->VMManager()
|
||||
.SetMemoryAttribute(cpu_addr.value(), size, Kernel::MemoryAttribute::DeviceMapped,
|
||||
Kernel::MemoryAttribute::None)
|
||||
.IsSuccess());
|
||||
|
||||
return gpu_addr;
|
||||
}
|
||||
|
||||
@@ -14,6 +14,10 @@ namespace VideoCore {
|
||||
class RasterizerInterface;
|
||||
}
|
||||
|
||||
namespace Core {
|
||||
class System;
|
||||
}
|
||||
|
||||
namespace Tegra {
|
||||
|
||||
/**
|
||||
@@ -47,7 +51,7 @@ struct VirtualMemoryArea {
|
||||
|
||||
class MemoryManager final {
|
||||
public:
|
||||
explicit MemoryManager(VideoCore::RasterizerInterface& rasterizer);
|
||||
explicit MemoryManager(Core::System& system, VideoCore::RasterizerInterface& rasterizer);
|
||||
~MemoryManager();
|
||||
|
||||
GPUVAddr AllocateSpace(u64 size, u64 align);
|
||||
@@ -173,6 +177,8 @@ private:
|
||||
Common::PageTable page_table{page_bits};
|
||||
VMAMap vma_map;
|
||||
VideoCore::RasterizerInterface& rasterizer;
|
||||
|
||||
Core::System& system;
|
||||
};
|
||||
|
||||
} // namespace Tegra
|
||||
|
||||
@@ -6,8 +6,11 @@
|
||||
#include <glad/glad.h>
|
||||
#include "common/assert.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/microprofile.h"
|
||||
#include "video_core/renderer_opengl/gl_state.h"
|
||||
|
||||
MICROPROFILE_DEFINE(OpenGL_State, "OpenGL", "State Change", MP_RGB(192, 128, 128));
|
||||
|
||||
namespace OpenGL {
|
||||
|
||||
using Maxwell = Tegra::Engines::Maxwell3D::Regs;
|
||||
@@ -524,6 +527,7 @@ void OpenGLState::ApplySamplers() const {
|
||||
}
|
||||
|
||||
void OpenGLState::Apply() const {
|
||||
MICROPROFILE_SCOPE(OpenGL_State);
|
||||
ApplyFramebufferState();
|
||||
ApplyVertexArrayState();
|
||||
ApplyShaderProgram();
|
||||
|
||||
@@ -31,6 +31,8 @@ using VideoCore::Surface::SurfaceType;
|
||||
|
||||
MICROPROFILE_DEFINE(OpenGL_Texture_Upload, "OpenGL", "Texture Upload", MP_RGB(128, 192, 128));
|
||||
MICROPROFILE_DEFINE(OpenGL_Texture_Download, "OpenGL", "Texture Download", MP_RGB(128, 192, 128));
|
||||
MICROPROFILE_DEFINE(OpenGL_Texture_Buffer_Copy, "OpenGL", "Texture Buffer Copy",
|
||||
MP_RGB(128, 192, 128));
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -535,6 +537,7 @@ void TextureCacheOpenGL::ImageBlit(View& src_view, View& dst_view,
|
||||
}
|
||||
|
||||
void TextureCacheOpenGL::BufferCopy(Surface& src_surface, Surface& dst_surface) {
|
||||
MICROPROFILE_SCOPE(OpenGL_Texture_Buffer_Copy);
|
||||
const auto& src_params = src_surface->GetSurfaceParams();
|
||||
const auto& dst_params = dst_surface->GetSurfaceParams();
|
||||
UNIMPLEMENTED_IF(src_params.num_levels > 1 || dst_params.num_levels > 1);
|
||||
|
||||
@@ -269,7 +269,13 @@ u32 ShaderIR::DecodeTexture(NodeBlock& bb, u32 pc) {
|
||||
LOG_WARNING(HW_GPU, "TLDS.NODEP implementation is incomplete");
|
||||
}
|
||||
|
||||
WriteTexsInstructionFloat(bb, instr, GetTldsCode(instr, texture_type, is_array));
|
||||
const Node4 components = GetTldsCode(instr, texture_type, is_array);
|
||||
|
||||
if (instr.tlds.fp32_flag) {
|
||||
WriteTexsInstructionFloat(bb, instr, components);
|
||||
} else {
|
||||
WriteTexsInstructionHalfFloat(bb, instr, components);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
|
||||
@@ -75,9 +75,12 @@ MatchStructureResult SurfaceBaseImpl::MatchesStructure(const SurfaceParams& rhs)
|
||||
|
||||
// Linear Surface check
|
||||
if (!params.is_tiled) {
|
||||
if (std::tie(params.width, params.height, params.pitch) ==
|
||||
std::tie(rhs.width, rhs.height, rhs.pitch)) {
|
||||
return MatchStructureResult::FullMatch;
|
||||
if (std::tie(params.height, params.pitch) == std::tie(rhs.height, rhs.pitch)) {
|
||||
if (params.width == rhs.width) {
|
||||
return MatchStructureResult::FullMatch;
|
||||
} else {
|
||||
return MatchStructureResult::SemiMatch;
|
||||
}
|
||||
}
|
||||
return MatchStructureResult::None;
|
||||
}
|
||||
|
||||
@@ -200,8 +200,9 @@ public:
|
||||
modification_tick = tick;
|
||||
}
|
||||
|
||||
void MarkAsRenderTarget(const bool is_target) {
|
||||
void MarkAsRenderTarget(const bool is_target, const u32 index) {
|
||||
this->is_target = is_target;
|
||||
this->index = index;
|
||||
}
|
||||
|
||||
void MarkAsPicked(const bool is_picked) {
|
||||
@@ -221,6 +222,10 @@ public:
|
||||
return is_target;
|
||||
}
|
||||
|
||||
u32 GetRenderTarget() const {
|
||||
return index;
|
||||
}
|
||||
|
||||
bool IsRegistered() const {
|
||||
return is_registered;
|
||||
}
|
||||
@@ -307,10 +312,13 @@ private:
|
||||
return view;
|
||||
}
|
||||
|
||||
static constexpr u32 NO_RT = 0xFFFFFFFF;
|
||||
|
||||
bool is_modified{};
|
||||
bool is_target{};
|
||||
bool is_registered{};
|
||||
bool is_picked{};
|
||||
u32 index{NO_RT};
|
||||
u64 modification_tick{};
|
||||
};
|
||||
|
||||
|
||||
@@ -290,12 +290,19 @@ std::size_t SurfaceParams::GetLayerSize(bool as_host_size, bool uncompressed) co
|
||||
|
||||
std::size_t SurfaceParams::GetInnerMipmapMemorySize(u32 level, bool as_host_size,
|
||||
bool uncompressed) const {
|
||||
const bool tiled{as_host_size ? false : is_tiled};
|
||||
const u32 width{GetMipmapSize(uncompressed, GetMipWidth(level), GetDefaultBlockWidth())};
|
||||
const u32 height{GetMipmapSize(uncompressed, GetMipHeight(level), GetDefaultBlockHeight())};
|
||||
const u32 depth{is_layered ? 1U : GetMipDepth(level)};
|
||||
return Tegra::Texture::CalculateSize(tiled, GetBytesPerPixel(), width, height, depth,
|
||||
GetMipBlockHeight(level), GetMipBlockDepth(level));
|
||||
if (is_tiled) {
|
||||
return Tegra::Texture::CalculateSize(!as_host_size, GetBytesPerPixel(), width, height,
|
||||
depth, GetMipBlockHeight(level),
|
||||
GetMipBlockDepth(level));
|
||||
} else if (as_host_size || IsBuffer()) {
|
||||
return GetBytesPerPixel() * width * height * depth;
|
||||
} else {
|
||||
// Linear Texture Case
|
||||
return pitch * height * depth;
|
||||
}
|
||||
}
|
||||
|
||||
bool SurfaceParams::operator==(const SurfaceParams& rhs) const {
|
||||
|
||||
@@ -133,11 +133,11 @@ public:
|
||||
regs.zeta.memory_layout.block_depth, regs.zeta.memory_layout.type)};
|
||||
auto surface_view = GetSurface(gpu_addr, depth_params, preserve_contents, true);
|
||||
if (depth_buffer.target)
|
||||
depth_buffer.target->MarkAsRenderTarget(false);
|
||||
depth_buffer.target->MarkAsRenderTarget(false, NO_RT);
|
||||
depth_buffer.target = surface_view.first;
|
||||
depth_buffer.view = surface_view.second;
|
||||
if (depth_buffer.target)
|
||||
depth_buffer.target->MarkAsRenderTarget(true);
|
||||
depth_buffer.target->MarkAsRenderTarget(true, DEPTH_RT);
|
||||
return surface_view.second;
|
||||
}
|
||||
|
||||
@@ -167,11 +167,11 @@ public:
|
||||
auto surface_view = GetSurface(gpu_addr, SurfaceParams::CreateForFramebuffer(system, index),
|
||||
preserve_contents, true);
|
||||
if (render_targets[index].target)
|
||||
render_targets[index].target->MarkAsRenderTarget(false);
|
||||
render_targets[index].target->MarkAsRenderTarget(false, NO_RT);
|
||||
render_targets[index].target = surface_view.first;
|
||||
render_targets[index].view = surface_view.second;
|
||||
if (render_targets[index].target)
|
||||
render_targets[index].target->MarkAsRenderTarget(true);
|
||||
render_targets[index].target->MarkAsRenderTarget(true, static_cast<u32>(index));
|
||||
return surface_view.second;
|
||||
}
|
||||
|
||||
@@ -191,7 +191,7 @@ public:
|
||||
if (depth_buffer.target == nullptr) {
|
||||
return;
|
||||
}
|
||||
depth_buffer.target->MarkAsRenderTarget(false);
|
||||
depth_buffer.target->MarkAsRenderTarget(false, NO_RT);
|
||||
depth_buffer.target = nullptr;
|
||||
depth_buffer.view = nullptr;
|
||||
}
|
||||
@@ -200,7 +200,7 @@ public:
|
||||
if (render_targets[index].target == nullptr) {
|
||||
return;
|
||||
}
|
||||
render_targets[index].target->MarkAsRenderTarget(false);
|
||||
render_targets[index].target->MarkAsRenderTarget(false, NO_RT);
|
||||
render_targets[index].target = nullptr;
|
||||
render_targets[index].view = nullptr;
|
||||
}
|
||||
@@ -270,6 +270,16 @@ protected:
|
||||
// and reading it from a sepparate buffer.
|
||||
virtual void BufferCopy(TSurface& src_surface, TSurface& dst_surface) = 0;
|
||||
|
||||
void ManageRenderTargetUnregister(TSurface& surface) {
|
||||
auto& maxwell3d = system.GPU().Maxwell3D();
|
||||
const u32 index = surface->GetRenderTarget();
|
||||
if (index == DEPTH_RT) {
|
||||
maxwell3d.dirty_flags.zeta_buffer = true;
|
||||
} else {
|
||||
maxwell3d.dirty_flags.color_buffer.set(index, true);
|
||||
}
|
||||
}
|
||||
|
||||
void Register(TSurface surface) {
|
||||
const GPUVAddr gpu_addr = surface->GetGpuAddr();
|
||||
const CacheAddr cache_ptr = ToCacheAddr(system.GPU().MemoryManager().GetPointer(gpu_addr));
|
||||
@@ -294,6 +304,9 @@ protected:
|
||||
if (guard_render_targets && surface->IsProtected()) {
|
||||
return;
|
||||
}
|
||||
if (!guard_render_targets && surface->IsRenderTarget()) {
|
||||
ManageRenderTargetUnregister(surface);
|
||||
}
|
||||
const GPUVAddr gpu_addr = surface->GetGpuAddr();
|
||||
const CacheAddr cache_ptr = surface->GetCacheAddr();
|
||||
const std::size_t size = surface->GetSizeInBytes();
|
||||
@@ -649,15 +662,6 @@ private:
|
||||
}
|
||||
return {current_surface, *view};
|
||||
}
|
||||
// The next case is unsafe, so if we r in accurate GPU, just skip it
|
||||
if (Settings::values.use_accurate_gpu_emulation) {
|
||||
return RecycleSurface(overlaps, params, gpu_addr, preserve_contents,
|
||||
MatchTopologyResult::FullMatch);
|
||||
}
|
||||
// This is the case the texture is a part of the parent.
|
||||
if (current_surface->MatchesSubTexture(params, gpu_addr)) {
|
||||
return RebuildSurface(current_surface, params, is_render);
|
||||
}
|
||||
} else {
|
||||
// If there are many overlaps, odds are they are subtextures of the candidate
|
||||
// surface. We try to construct a new surface based on the candidate parameters,
|
||||
@@ -793,6 +797,9 @@ private:
|
||||
static constexpr u64 registry_page_size{1 << registry_page_bits};
|
||||
std::unordered_map<CacheAddr, std::vector<TSurface>> registry;
|
||||
|
||||
static constexpr u32 DEPTH_RT = 8;
|
||||
static constexpr u32 NO_RT = 0xFFFFFFFF;
|
||||
|
||||
// The L1 Cache is used for fast texture lookup before checking the overlaps
|
||||
// This avoids calculating size and other stuffs.
|
||||
std::unordered_map<CacheAddr, TSurface> l1_cache;
|
||||
|
||||
Reference in New Issue
Block a user