mirror of
https://github.com/patchzyy/wiicompiled
synced 2026-10-05 17:03:41 -04:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0d04be19d6 | |||
| c16f1533e5 |
@@ -3,4 +3,3 @@
|
|||||||
|
|
||||||
# Patch files must stay LF: git apply matches context bytes against LF upstream sources
|
# Patch files must stay LF: git apply matches context bytes against LF upstream sources
|
||||||
*.patch -text
|
*.patch -text
|
||||||
translator/tests/Translator.Tests/TestAssets/**/*.bin binary
|
|
||||||
|
|||||||
@@ -30,14 +30,6 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
dotnet-version: '8.0.x'
|
dotnet-version: '8.0.x'
|
||||||
|
|
||||||
- name: Cache NuGet packages
|
|
||||||
uses: actions/cache@v5
|
|
||||||
with:
|
|
||||||
path: ~/.nuget/packages
|
|
||||||
key: ${{ runner.os }}-nuget-${{ hashFiles('translator/Translator.sln', '**/*.csproj', '**/*.props', '**/*.targets', '**/packages.lock.json', 'global.json', 'NuGet.config', 'nuget.config') }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-nuget-
|
|
||||||
|
|
||||||
- name: Restore
|
- name: Restore
|
||||||
run: dotnet restore translator/Translator.sln
|
run: dotnet restore translator/Translator.sln
|
||||||
|
|
||||||
@@ -55,10 +47,6 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|
||||||
- name: Test macOS app dependency packaging
|
|
||||||
shell: bash
|
|
||||||
run: bash Launcher/macos/test-publish-app.command
|
|
||||||
|
|
||||||
- name: Configure native runtime
|
- name: Configure native runtime
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
@@ -73,7 +61,6 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
cmake --build build-macos --target \
|
cmake --build build-macos --target \
|
||||||
mkw_platform_paths_tests \
|
mkw_platform_paths_tests \
|
||||||
mkw_runtime_config_tests \
|
|
||||||
mkw_nand_save_tests \
|
mkw_nand_save_tests \
|
||||||
mkw_nand_settings_tests \
|
mkw_nand_settings_tests \
|
||||||
mkw_sc_serial_tests \
|
mkw_sc_serial_tests \
|
||||||
@@ -81,8 +68,7 @@ jobs:
|
|||||||
mkw_macos_native_compile \
|
mkw_macos_native_compile \
|
||||||
mkw_macos_context_abi_tests \
|
mkw_macos_context_abi_tests \
|
||||||
mkw_macos_host_context_tests \
|
mkw_macos_host_context_tests \
|
||||||
mkw_macos_guest_flat_memory_tests \
|
mkw_macos_guest_flat_memory_tests
|
||||||
mkw_macos_external_audio_tests
|
|
||||||
|
|
||||||
- name: Test execution substrate
|
- name: Test execution substrate
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|||||||
@@ -11,6 +11,11 @@ on:
|
|||||||
tags:
|
tags:
|
||||||
- '*'
|
- '*'
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
version:
|
||||||
|
description: Package version
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
@@ -163,15 +168,12 @@ jobs:
|
|||||||
|
|
||||||
- name: Build Setup.pkg
|
- name: Build Setup.pkg
|
||||||
env:
|
env:
|
||||||
|
PACKAGE_VERSION: ${{ inputs.version }}
|
||||||
TAG_VERSION: ${{ github.ref_name }}
|
TAG_VERSION: ${{ github.ref_name }}
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
package_version=""
|
package_version="$PACKAGE_VERSION"
|
||||||
if [[ "${TAG_VERSION:-}" =~ ^v?[0-9] ]]; then
|
if [[ -z "$package_version" ]]; then package_version="${TAG_VERSION#v}"; fi
|
||||||
package_version="${TAG_VERSION#v}"
|
|
||||||
elif [[ -f "Launcher/Directory.Build.props" ]]; then
|
|
||||||
package_version=$(grep -m1 '<Version>' Launcher/Directory.Build.props | sed -E 's/.*<Version>([^<]+)<\/Version>.*/\1/')
|
|
||||||
fi
|
|
||||||
mkdir -p Launcher/dist
|
mkdir -p Launcher/dist
|
||||||
Launcher/macos/build-setup-pkg.command \
|
Launcher/macos/build-setup-pkg.command \
|
||||||
--nodtool-arm64 Launcher/artifacts/macos/nodtool-arm64 \
|
--nodtool-arm64 Launcher/artifacts/macos/nodtool-arm64 \
|
||||||
@@ -182,17 +184,14 @@ jobs:
|
|||||||
--ninja-arm64 Launcher/artifacts/macos/ninja/ninja \
|
--ninja-arm64 Launcher/artifacts/macos/ninja/ninja \
|
||||||
--ninja-x86_64 Launcher/artifacts/macos/ninja/ninja \
|
--ninja-x86_64 Launcher/artifacts/macos/ninja/ninja \
|
||||||
--output Launcher/dist/WiiCompiled-Setup.pkg \
|
--output Launcher/dist/WiiCompiled-Setup.pkg \
|
||||||
${package_version:+--version "$package_version"}
|
--version "$package_version"
|
||||||
|
|
||||||
- name: Verify package layout and architecture-specific tools
|
- name: Verify package layout and architecture-specific tools
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
pkgutil --check-signature Launcher/dist/WiiCompiled-Setup.pkg || true
|
pkgutil --check-signature Launcher/dist/WiiCompiled-Setup.pkg
|
||||||
if pkgutil --payload-files Launcher/dist/WiiCompiled-Setup.pkg | \
|
! pkgutil --payload-files Launcher/dist/WiiCompiled-Setup.pkg | \
|
||||||
grep -E '/(Assets|generated|PulsarPacks|WiiCompiled.app|RetroRewind.app)(/|$)'; then
|
grep -E '/(Assets|generated|PulsarPacks|WiiCompiled.app|RetroRewind.app)(/|$)'
|
||||||
echo "::error::Setup.pkg contains a forbidden payload"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
expanded="$RUNNER_TEMP/wiicompiled-setup-expanded"
|
expanded="$RUNNER_TEMP/wiicompiled-setup-expanded"
|
||||||
pkgutil --expand-full Launcher/dist/WiiCompiled-Setup.pkg "$expanded"
|
pkgutil --expand-full Launcher/dist/WiiCompiled-Setup.pkg "$expanded"
|
||||||
resources="$expanded/Payload/Applications/WiiCompiled Setup.app/Contents/Resources"
|
resources="$expanded/Payload/Applications/WiiCompiled Setup.app/Contents/Resources"
|
||||||
@@ -229,7 +228,7 @@ jobs:
|
|||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|
||||||
# Wheel Wizard runs the downloaded setup with --version and refuses it when the reported
|
# Wheel Wizard runs the downloaded setup with --version and refuses it when the reported
|
||||||
# version differs from the release tag, so a tag that was pushed without bumping the pinned
|
# version differs from the release tag, so a tag that was pushed without bumping every pinned
|
||||||
# version would ship an update nobody can install. Catch that before anything is published.
|
# version would ship an update nobody can install. Catch that before anything is published.
|
||||||
- name: Verify the tag matches the pinned setup version
|
- name: Verify the tag matches the pinned setup version
|
||||||
env:
|
env:
|
||||||
@@ -244,7 +243,9 @@ jobs:
|
|||||||
status=1
|
status=1
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
check Launcher/Directory.Build.props "<Version>$version</Version>"
|
check Launcher/WiiCompiled.Setup.Windows/Program.cs "public const string Version = \"$version\";"
|
||||||
|
check Launcher/WiiCompiled.Setup.Windows/WiiCompiled.Setup.Windows.csproj "<Version>$version</Version>"
|
||||||
|
check Launcher/Build-Installer.ps1 "ProductVersion = '$version'"
|
||||||
exit $status
|
exit $status
|
||||||
|
|
||||||
# The build jobs upload with `archive: false`, which stores each installer as a raw file
|
# The build jobs upload with `archive: false`, which stores each installer as a raw file
|
||||||
|
|||||||
@@ -28,19 +28,6 @@ jobs:
|
|||||||
path: Launcher/artifacts/downloads
|
path: Launcher/artifacts/downloads
|
||||||
key: windows-recomp-downloads-${{ hashFiles('Launcher/Prepare-PortableTools.ps1', 'Launcher/Prepare-Dependencies.ps1') }}
|
key: windows-recomp-downloads-${{ hashFiles('Launcher/Prepare-PortableTools.ps1', 'Launcher/Prepare-Dependencies.ps1') }}
|
||||||
|
|
||||||
# Install sccache.
|
|
||||||
- name: Run sccache-action
|
|
||||||
uses: mozilla/sccache-action@v0.0.11
|
|
||||||
|
|
||||||
# Tell CMake to use sccache and use GitHub's API.
|
|
||||||
- name: Configure sccache environment
|
|
||||||
shell: pwsh
|
|
||||||
run: |
|
|
||||||
"SCCACHE_GHA_ENABLED=true" | Add-Content -Path $env:GITHUB_ENV
|
|
||||||
"ACTIONS_CACHE_SERVICE_V2=on" | Add-Content -Path $env:GITHUB_ENV
|
|
||||||
"CMAKE_C_COMPILER_LAUNCHER=$env:SCCACHE_PATH" | Add-Content -Path $env:GITHUB_ENV
|
|
||||||
"CMAKE_CXX_COMPILER_LAUNCHER=$env:SCCACHE_PATH" | Add-Content -Path $env:GITHUB_ENV
|
|
||||||
|
|
||||||
- name: Prepare the shipped Windows toolchain
|
- name: Prepare the shipped Windows toolchain
|
||||||
shell: pwsh
|
shell: pwsh
|
||||||
run: ./Launcher/Prepare-PortableTools.ps1
|
run: ./Launcher/Prepare-PortableTools.ps1
|
||||||
@@ -49,13 +36,6 @@ jobs:
|
|||||||
shell: pwsh
|
shell: pwsh
|
||||||
run: ./Launcher/Prepare-Dependencies.ps1
|
run: ./Launcher/Prepare-Dependencies.ps1
|
||||||
|
|
||||||
# Expose cache token context to the build script.
|
|
||||||
- name: Translate, compile the full runtime, and link
|
- name: Translate, compile the full runtime, and link
|
||||||
shell: pwsh
|
shell: pwsh
|
||||||
run: ./Launcher/Test-Recompilation.ps1 -Parallel 4
|
run: ./Launcher/Test-Recompilation.ps1 -Parallel 3
|
||||||
|
|
||||||
# Print cache results (even if the build fails)
|
|
||||||
- name: Show sccache stats
|
|
||||||
if: always()
|
|
||||||
shell: pwsh
|
|
||||||
run: sccache --show-stats
|
|
||||||
|
|||||||
@@ -72,5 +72,3 @@ project.lock.json
|
|||||||
*.log
|
*.log
|
||||||
output.txt
|
output.txt
|
||||||
|
|
||||||
# Operating System
|
|
||||||
.DS_Store
|
|
||||||
|
|||||||
@@ -279,12 +279,9 @@ foreach ($required in @('ToolkitFingerprint','TranslationFingerprint','NativeToo
|
|||||||
if ([string]::IsNullOrWhiteSpace($identities.$required)) { throw "Payload identity output is missing $required." }
|
if ([string]::IsNullOrWhiteSpace($identities.$required)) { throw "Payload identity output is missing $required." }
|
||||||
}
|
}
|
||||||
|
|
||||||
$productVersion = ((& $setupHost --version) -join '').Trim()
|
|
||||||
if ($LASTEXITCODE -ne 0 -or [string]::IsNullOrWhiteSpace($productVersion)) { throw 'The setup host did not report its version.' }
|
|
||||||
|
|
||||||
$manifest = [ordered]@{
|
$manifest = [ordered]@{
|
||||||
SchemaVersion = 2
|
SchemaVersion = 2
|
||||||
ProductVersion = $productVersion
|
ProductVersion = '0.2.31'
|
||||||
ExpectedGameId = $pins.GameId
|
ExpectedGameId = $pins.GameId
|
||||||
ExpectedDolSha256 = $pins.DolSha256
|
ExpectedDolSha256 = $pins.DolSha256
|
||||||
ExpectedRelSha256 = $pins.RelSha256
|
ExpectedRelSha256 = $pins.RelSha256
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
<Project>
|
|
||||||
<PropertyGroup>
|
|
||||||
<!-- The one product version. Release tags must match it. -->
|
|
||||||
<Version>0.2.33</Version>
|
|
||||||
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
|
|
||||||
</PropertyGroup>
|
|
||||||
</Project>
|
|
||||||
+2
-10
@@ -103,7 +103,7 @@ function Write-MkwBuildStep([string]$StepId, [string]$Message) {
|
|||||||
function Reset-LocalDirectory([string]$Path) {
|
function Reset-LocalDirectory([string]$Path) {
|
||||||
$full = [IO.Path]::GetFullPath($Path)
|
$full = [IO.Path]::GetFullPath($Path)
|
||||||
$root = [IO.Path]::GetFullPath($Workspace).TrimEnd('\') + '\'
|
$root = [IO.Path]::GetFullPath($Workspace).TrimEnd('\') + '\'
|
||||||
$installRoot = [IO.Path]::GetFullPath((Split-Path -Parent $realWorkspace)).TrimEnd('\') + '\'
|
$installRoot = [IO.Path]::GetFullPath((Split-Path -Parent $Workspace)).TrimEnd('\') + '\'
|
||||||
# The caller-supplied output destinations are legitimate reset targets by
|
# The caller-supplied output destinations are legitimate reset targets by
|
||||||
# definition, wherever the caller placed them: a fresh install's operation
|
# definition, wherever the caller placed them: a fresh install's operation
|
||||||
# scratch lives beside the installation directory rather than inside it.
|
# scratch lives beside the installation directory rather than inside it.
|
||||||
@@ -150,16 +150,8 @@ if ($Profile -eq 'both' -and [string]::IsNullOrWhiteSpace($BaseOutputDirectory))
|
|||||||
if ($Profile -ne 'both' -and -not [string]::IsNullOrWhiteSpace($BaseOutputDirectory)) {
|
if ($Profile -ne 'both' -and -not [string]::IsNullOrWhiteSpace($BaseOutputDirectory)) {
|
||||||
throw '-BaseOutputDirectory is valid only with -Profile both.'
|
throw '-BaseOutputDirectory is valid only with -Profile both.'
|
||||||
}
|
}
|
||||||
$realWorkspace = $Workspace.TrimEnd('\')
|
|
||||||
$Workspace = Get-MkwBuildSafePath $realWorkspace 'workspace' 'runtime\CMakeLists.txt'
|
|
||||||
# A selected package inside the install may also name the same Code.pul through the real path.
|
|
||||||
# Give it the workspace spelling before comparing it with the staged copy.
|
|
||||||
if (-not [string]::IsNullOrWhiteSpace($RetroRewindPackageDirectory) -and
|
|
||||||
$RetroRewindPackageDirectory.StartsWith($realWorkspace + '\', [StringComparison]::OrdinalIgnoreCase)) {
|
|
||||||
$RetroRewindPackageDirectory = $Workspace + $RetroRewindPackageDirectory.Substring($realWorkspace.Length)
|
|
||||||
}
|
|
||||||
$translator = Join-Path $Toolkit 'Translator\Translator.Cli.exe'
|
$translator = Join-Path $Toolkit 'Translator\Translator.Cli.exe'
|
||||||
$toolchain = Get-MkwBuildSafePath $Toolkit 'toolchain' 'CMake\bin\cmake.exe'
|
$toolchain = Get-MkwShellSafeToolchainRoot $Toolkit
|
||||||
$cmake = Join-Path $toolchain 'CMake\bin\cmake.exe'
|
$cmake = Join-Path $toolchain 'CMake\bin\cmake.exe'
|
||||||
$ninja = Join-Path $toolchain 'Ninja\ninja.exe'
|
$ninja = Join-Path $toolchain 'Ninja\ninja.exe'
|
||||||
$toolchainBin = Join-Path $toolchain 'llvm-mingw\bin'
|
$toolchainBin = Join-Path $toolchain 'llvm-mingw\bin'
|
||||||
|
|||||||
@@ -40,55 +40,41 @@ function Get-MkwToolchainPath([string]$ToolchainRoot) {
|
|||||||
) -join ';')
|
) -join ';')
|
||||||
}
|
}
|
||||||
|
|
||||||
function Get-MkwBuildSafePath([string]$Path, [string]$Kind, [string]$MarkerFile) {
|
function Get-MkwShellSafeToolchainRoot([string]$ToolchainRoot) {
|
||||||
<#
|
if ([string]::IsNullOrWhiteSpace($ToolchainRoot)) { throw 'A toolchain root is required.' }
|
||||||
The Windows native build passes workspace paths through CMake, Ninja response files and
|
$full = [IO.Path]::GetFullPath($ToolchainRoot)
|
||||||
clang, which do not all interpret quotes the same way. Keep those paths plain even when the
|
|
||||||
user's install directory contains an apostrophe, ampersand or other punctuation.
|
|
||||||
#>
|
|
||||||
if ([string]::IsNullOrWhiteSpace($Path)) { throw "A $Kind path is required." }
|
|
||||||
$full = [IO.Path]::GetFullPath($Path)
|
|
||||||
# A drive root keeps its separator: "C:" is relative to the current directory on that drive.
|
# A drive root keeps its separator: "C:" is relative to the current directory on that drive.
|
||||||
if ($full -ne [IO.Path]::GetPathRoot($full)) { $full = $full.TrimEnd('\') }
|
if ($full -ne [IO.Path]::GetPathRoot($full)) { $full = $full.TrimEnd('\') }
|
||||||
if ($full -cmatch '^[A-Za-z0-9 ._\\:-]+$') { return $full }
|
if ($full -notmatch '[()&^%!]') { return $full }
|
||||||
|
|
||||||
Assert-File (Join-Path $full $MarkerFile) "$Kind marker"
|
|
||||||
|
|
||||||
$sha = [Security.Cryptography.SHA256]::Create()
|
$sha = [Security.Cryptography.SHA256]::Create()
|
||||||
try {
|
try {
|
||||||
$bytes = $sha.ComputeHash([Text.Encoding]::UTF8.GetBytes($full.ToLowerInvariant()))
|
$bytes = $sha.ComputeHash([Text.Encoding]::UTF8.GetBytes($full.ToLowerInvariant()))
|
||||||
} finally { $sha.Dispose() }
|
} finally { $sha.Dispose() }
|
||||||
$linkName = "$Kind-" + ((($bytes[0..7]) | ForEach-Object { $_.ToString('x2') }) -join '')
|
$linkName = 'toolchain-' + ((($bytes[0..7]) | ForEach-Object { $_.ToString('x2') }) -join '')
|
||||||
|
|
||||||
$failures = @()
|
$failures = @()
|
||||||
foreach ($base in @($env:ProgramData, $env:PUBLIC)) {
|
foreach ($base in @($env:ProgramData, $env:PUBLIC)) {
|
||||||
if ([string]::IsNullOrWhiteSpace($base) -or $base -cnotmatch '^[A-Za-z0-9._\\:-]+$') { continue }
|
if ([string]::IsNullOrWhiteSpace($base) -or $base -match '[()&^%! ]') { continue }
|
||||||
$link = Join-Path (Join-Path $base 'WiiCompiled') $linkName
|
$link = Join-Path (Join-Path $base 'WiiCompiled') $linkName
|
||||||
try {
|
try {
|
||||||
[IO.Directory]::CreateDirectory((Split-Path -Parent $link)) | Out-Null
|
[IO.Directory]::CreateDirectory((Split-Path -Parent $link)) | Out-Null
|
||||||
$existing = Get-Item -LiteralPath $link -Force -ErrorAction SilentlyContinue
|
# The name already identifies the target, so an existing junction that still resolves is
|
||||||
if ($null -ne $existing) {
|
# this one; only a broken leftover is replaced. Directory.Delete removes the reparse
|
||||||
# Never trust a directory just because it has the marker: it could point at a
|
# point itself, where Remove-Item -Recurse would delete the toolchain it points at.
|
||||||
# different installation. Nor may we remove a directory we did not create.
|
if (-not (Test-Path -LiteralPath (Join-Path $link 'CMake\bin\cmake.exe') -PathType Leaf)) {
|
||||||
if ($existing.LinkType -ne 'Junction' -or
|
if (Test-Path -LiteralPath $link) { [IO.Directory]::Delete($link) }
|
||||||
@($existing.Target).Count -ne 1 -or
|
|
||||||
-not [string]::Equals([IO.Path]::GetFullPath(@($existing.Target)[0]),
|
|
||||||
$full, [StringComparison]::OrdinalIgnoreCase)) {
|
|
||||||
throw "An existing path is not the expected junction: $link"
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
New-Item -ItemType Junction -Path $link -Target $full -ErrorAction Stop | Out-Null
|
New-Item -ItemType Junction -Path $link -Target $full -ErrorAction Stop | Out-Null
|
||||||
}
|
}
|
||||||
Assert-File (Join-Path $link $MarkerFile) "$Kind junction marker"
|
Write-Host "MKWCBUILD: Building through $link, because $full contains characters cmd.exe cannot parse"
|
||||||
Write-Host "MKWCBUILD: Building the $Kind through $link, because $full contains characters the native build cannot quote reliably"
|
|
||||||
return $link
|
return $link
|
||||||
} catch {
|
} catch {
|
||||||
$failures += "$link ($($_.Exception.Message))"
|
$failures += "$link ($($_.Exception.Message))"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
throw ("The $Kind path $full cannot be passed safely to the native build, and no junction " +
|
throw ("The toolchain path $full contains a character (one of ( ) & ^ % !) that the compiler " +
|
||||||
'to it could be created: ' + ($failures -join '; ') + '. Install to a path of plain ' +
|
'cannot be invoked through, and no junction to it could be created: ' + ($failures -join '; ') +
|
||||||
'letters, digits and spaces, or make a safe junction location available.')
|
'. Install to a path without those characters.')
|
||||||
}
|
}
|
||||||
|
|
||||||
function Get-MkwProjectPins([string]$ProjectFile) {
|
function Get-MkwProjectPins([string]$ProjectFile) {
|
||||||
@@ -131,13 +117,12 @@ function Get-MkwProjectPins([string]$ProjectFile) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function Invoke-Checked([string]$FilePath, [string[]]$Arguments, [string]$Description,
|
function Invoke-Checked([string]$FilePath, [string[]]$Arguments, [string]$Description,
|
||||||
[string]$LogPrefix = 'MKWCBUILD', [string]$StepId = '', [bool]$WaitForProcessTree = $true) {
|
[string]$LogPrefix = 'MKWCBUILD', [string]$StepId = '') {
|
||||||
<#
|
<#
|
||||||
Runs a build tool and turns a non-zero exit code into a described failure. By default,
|
Runs a build tool and turns a non-zero exit code into a described failure. Start-Process -Wait
|
||||||
Start-Process -Wait waits for the whole process tree, since a .NET single-file bundle host may
|
is deliberate: it waits for the whole process tree, since a .NET single-file bundle host may
|
||||||
hand off to an extracted child that PowerShell's call operator would not wait for. Callers that
|
hand off to an extracted child that PowerShell's call operator would not wait for. Start-Process
|
||||||
need to avoid waiting on unrelated descendants can opt into the call-operator path.
|
doesn't publish $LASTEXITCODE, so this sets it manually for callers that check it.
|
||||||
Start-Process doesn't publish $LASTEXITCODE, so this sets it manually for callers that check it.
|
|
||||||
-StepId emits the machine-readable form the installer's progress bar consumes (BuildStepIds in
|
-StepId emits the machine-readable form the installer's progress bar consumes (BuildStepIds in
|
||||||
WiiCompiled.Setup/InstallProgress.cs); the human sentence stays on the same log line.
|
WiiCompiled.Setup/InstallProgress.cs); the human sentence stays on the same log line.
|
||||||
#>
|
#>
|
||||||
@@ -147,14 +132,9 @@ function Invoke-Checked([string]$FilePath, [string[]]$Arguments, [string]$Descri
|
|||||||
if ($_.Contains('"')) { throw "A native build argument contains an unsupported quote: $_" }
|
if ($_.Contains('"')) { throw "A native build argument contains an unsupported quote: $_" }
|
||||||
'"' + $_ + '"'
|
'"' + $_ + '"'
|
||||||
})
|
})
|
||||||
if ($WaitForProcessTree) {
|
$process = Start-Process -FilePath $FilePath -ArgumentList $quotedArguments `
|
||||||
$process = Start-Process -FilePath $FilePath -ArgumentList $quotedArguments `
|
-NoNewWindow -Wait -PassThru
|
||||||
-NoNewWindow -Wait -PassThru
|
$exitCode = $process.ExitCode
|
||||||
$exitCode = $process.ExitCode
|
|
||||||
} else {
|
|
||||||
& $FilePath @Arguments
|
|
||||||
$exitCode = $LASTEXITCODE
|
|
||||||
}
|
|
||||||
$global:LASTEXITCODE = $exitCode
|
$global:LASTEXITCODE = $exitCode
|
||||||
if ($exitCode -ne 0) { throw "$Description failed with exit code $exitCode." }
|
if ($exitCode -ne 0) { throw "$Description failed with exit code $exitCode." }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,9 +39,9 @@ $packages = @(
|
|||||||
},
|
},
|
||||||
[pscustomobject]@{
|
[pscustomobject]@{
|
||||||
Name = 'dawn_prebuilt'; File = 'dawn-v20260603.191052-windows-amd64.tar.gz'
|
Name = 'dawn_prebuilt'; File = 'dawn-v20260603.191052-windows-amd64.tar.gz'
|
||||||
Uris = @('https://github.com/theofficialgman/dawn-build/releases/download/v20260603.191052/dawn-windows-amd64.tar.gz')
|
Uris = @('https://github.com/encounter/dawn-build/releases/download/v20260603.191052/dawn-windows-amd64.tar.gz')
|
||||||
Pins = @(@{ File = $auroraCMake; Text = 'set(AURORA_DAWN_VERSION "v20260603.191052"' },
|
Pins = @(@{ File = $auroraCMake; Text = 'set(AURORA_DAWN_VERSION "v20260603.191052"' },
|
||||||
@{ File = $auroraDawn; Text = 'SHA256=13be9cff8b9b179c42dcd16aeabb6effcc8f0dfdcc14463eda2a5caeda225142' })
|
@{ File = $auroraDawn; Text = 'SHA256=7785373d569b3b0237918ec9c523239f7d0667857c5ea8242e3cdfde95e6aeab' })
|
||||||
},
|
},
|
||||||
[pscustomobject]@{
|
[pscustomobject]@{
|
||||||
Name = 'fmt'; File = 'fmt-11.1.4.tar.gz'
|
Name = 'fmt'; File = 'fmt-11.1.4.tar.gz'
|
||||||
@@ -82,9 +82,9 @@ $packages = @(
|
|||||||
Pins = @(@{ File = $auroraExtern; Text = 'https://github.com/wolfpld/tracy/archive/a64b9a20294d59421a2f57aeca3c6383d8c48169.tar.gz' })
|
Pins = @(@{ File = $auroraExtern; Text = 'https://github.com/wolfpld/tracy/archive/a64b9a20294d59421a2f57aeca3c6383d8c48169.tar.gz' })
|
||||||
},
|
},
|
||||||
[pscustomobject]@{
|
[pscustomobject]@{
|
||||||
Name = 'xxhash'; File = 'xxHash-0.8.4.tar.gz'
|
Name = 'xxhash'; File = 'xxHash-0.8.3.tar.gz'
|
||||||
Uris = @('https://github.com/Cyan4973/xxHash/archive/refs/tags/v0.8.4.tar.gz')
|
Uris = @('https://github.com/Cyan4973/xxHash/archive/refs/tags/v0.8.3.tar.gz')
|
||||||
Pins = @(@{ File = $auroraExtern; Text = 'https://github.com/Cyan4973/xxHash/archive/refs/tags/v0.8.4.tar.gz' })
|
Pins = @(@{ File = $auroraExtern; Text = 'https://github.com/Cyan4973/xxHash/archive/refs/tags/v0.8.3.tar.gz' })
|
||||||
},
|
},
|
||||||
[pscustomobject]@{
|
[pscustomobject]@{
|
||||||
Name = 'zlib'; File = 'zlib-1.3.2.tar.gz'
|
Name = 'zlib'; File = 'zlib-1.3.2.tar.gz'
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# Builds the redistributable precompiled aurora + third-party package for native Linux: aurora
|
# Builds the redistributable precompiled aurora + third-party package for native Linux: aurora
|
||||||
# (~43% of local build CPU time per Prepare-NativePrebuilt.ps1), Crypto++ and mbed TLS are identical
|
# (~43% of local build CPU time per Prepare-NativePrebuilt.ps1) and vendored Crypto++ are identical
|
||||||
# for every user under the pinned toolchain prepare-portable-tools.sh bundles, so this configures
|
# for every user under the pinned toolchain prepare-portable-tools.sh bundles, so this configures
|
||||||
# runtime/ against that toolchain, builds just that closure, and harvests the archives plus a
|
# runtime/ against that toolchain, builds just that closure, and harvests the archives plus a
|
||||||
# generated CMake description into an output package - the Linux counterpart to
|
# generated CMake description into an output package - the Linux counterpart to
|
||||||
@@ -22,17 +22,11 @@ workspace=$(cd "$script_dir/.." && pwd)
|
|||||||
|
|
||||||
arch=""
|
arch=""
|
||||||
output_dir=""
|
output_dir=""
|
||||||
toolchain_dir=""
|
|
||||||
ninja_bin=""
|
|
||||||
cmake_bin=""
|
|
||||||
llvm_dir=""
|
|
||||||
sysroot=""
|
|
||||||
stage_dir="$workspace/build/native-prebuilt-stage"
|
stage_dir="$workspace/build/native-prebuilt-stage"
|
||||||
keep_stage=0
|
keep_stage=0
|
||||||
reuse_stage=0
|
reuse_stage=0
|
||||||
parallel=0
|
parallel=0
|
||||||
print_fingerprint_only=0
|
print_fingerprint_only=0
|
||||||
disconnected=0
|
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
cat <<'EOF'
|
cat <<'EOF'
|
||||||
@@ -45,7 +39,8 @@ Usage: Prepare-NativePrebuilt.sh --arch {x86_64|aarch64} [options]
|
|||||||
--reuse-stage Reuse an existing staging build directory (maintainer iteration aid: a
|
--reuse-stage Reuse an existing staging build directory (maintainer iteration aid: a
|
||||||
re-harvest does not recompile aurora from scratch)
|
re-harvest does not recompile aurora from scratch)
|
||||||
--parallel N Ninja build parallelism (default: nproc)
|
--parallel N Ninja build parallelism (default: nproc)
|
||||||
--print-fingerprint-only Print the provenance inputs as "key=value" lines and
|
--print-fingerprint-only Print the four provenance inputs (compiler_sha256, flag_fingerprint,
|
||||||
|
aurora_fingerprint, third_party_fingerprint) as "key=value" lines and
|
||||||
exit, without configuring/building/harvesting anything - lets a caller
|
exit, without configuring/building/harvesting anything - lets a caller
|
||||||
(build-appimage.sh) decide whether an existing package is still current
|
(build-appimage.sh) decide whether an existing package is still current
|
||||||
without paying for a full aurora rebuild just to find out.
|
without paying for a full aurora rebuild just to find out.
|
||||||
@@ -61,12 +56,6 @@ while [[ $# -gt 0 ]]; do
|
|||||||
--reuse-stage) reuse_stage=1; shift ;;
|
--reuse-stage) reuse_stage=1; shift ;;
|
||||||
--parallel) parallel=$2; shift 2 ;;
|
--parallel) parallel=$2; shift 2 ;;
|
||||||
--print-fingerprint-only) print_fingerprint_only=1; shift ;;
|
--print-fingerprint-only) print_fingerprint_only=1; shift ;;
|
||||||
--toolchain-dir) toolchain_dir=$2; shift 2 ;;
|
|
||||||
--ninja-bin) ninja_bin=$2; shift 2 ;;
|
|
||||||
--cmake-bin) cmake_bin=$2; shift 2 ;;
|
|
||||||
--llvm-dir) llvm_dir=$2; shift 2 ;;
|
|
||||||
--sysroot) sysroot=$2; shift 2 ;;
|
|
||||||
--disconnected) disconnected=1; shift ;;
|
|
||||||
-h|--help) usage; exit 0 ;;
|
-h|--help) usage; exit 0 ;;
|
||||||
*) echo "Prepare-NativePrebuilt.sh: unknown argument: $1" >&2; exit 1 ;;
|
*) echo "Prepare-NativePrebuilt.sh: unknown argument: $1" >&2; exit 1 ;;
|
||||||
esac
|
esac
|
||||||
@@ -83,22 +72,14 @@ assert_dir() { [[ -d "$1" ]] || fail "$2 is missing: $1"; }
|
|||||||
sha256_of() { sha256sum "$1" | awk '{print $1}'; }
|
sha256_of() { sha256sum "$1" | awk '{print $1}'; }
|
||||||
normalize() { readlink -f "$1"; }
|
normalize() { readlink -f "$1"; }
|
||||||
|
|
||||||
# Environment check: Ensure the `SOURCE_DATE_EPOCH` value is not invalid
|
|
||||||
# before trying to build anything (fail-fast)
|
|
||||||
if [[ -n "${SOURCE_DATE_EPOCH:-}" ]] && ! python3 -c \
|
|
||||||
'import datetime, os; datetime.datetime.fromtimestamp(int(os.environ.get("SOURCE_DATE_EPOCH")), datetime.timezone.utc)'
|
|
||||||
then
|
|
||||||
fail "SOURCE_DATE_EPOCH must be a valid UNIX timestamp"
|
|
||||||
fi
|
|
||||||
|
|
||||||
[[ -n "$output_dir" ]] || output_dir="$script_dir/artifacts/native-prebuilt-$arch"
|
[[ -n "$output_dir" ]] || output_dir="$script_dir/artifacts/native-prebuilt-$arch"
|
||||||
[[ "$stage_dir" = /* ]] || stage_dir="$workspace/$stage_dir"
|
[[ "$stage_dir" = /* ]] || stage_dir="$workspace/$stage_dir"
|
||||||
|
|
||||||
toolchain_dir="${toolchain_dir:-$script_dir/artifacts/portable-tools/toolchain-$arch}"
|
toolchain_dir="$script_dir/artifacts/portable-tools/toolchain-$arch"
|
||||||
cc="${llvm_dir:-$toolchain_dir}/bin/clang"
|
cc="$toolchain_dir/bin/clang"
|
||||||
cxx="${llvm_dir:-$toolchain_dir}/bin/clang++"
|
cxx="$toolchain_dir/bin/clang++"
|
||||||
cmake_bin="${cmake_bin:-$toolchain_dir/bin/cmake}"
|
cmake_bin="$toolchain_dir/bin/cmake"
|
||||||
ninja_bin="${ninja_bin:-$toolchain_dir/bin/ninja}"
|
ninja_bin="$toolchain_dir/bin/ninja"
|
||||||
runtime_source="$workspace/runtime"
|
runtime_source="$workspace/runtime"
|
||||||
aurora_source="$workspace/aurora-main"
|
aurora_source="$workspace/aurora-main"
|
||||||
|
|
||||||
@@ -107,7 +88,7 @@ assert_file "$ninja_bin" "Portable Ninja"
|
|||||||
assert_file "$cc" "Portable C compiler"
|
assert_file "$cc" "Portable C compiler"
|
||||||
assert_file "$cxx" "Portable C++ compiler"
|
assert_file "$cxx" "Portable C++ compiler"
|
||||||
assert_dir "$aurora_source" "aurora-main source tree"
|
assert_dir "$aurora_source" "aurora-main source tree"
|
||||||
clang_binary=$(normalize "${llvm_dir:-$toolchain_dir}/bin/clang-22")
|
clang_binary=$(normalize "$toolchain_dir/bin/clang-22")
|
||||||
assert_file "$clang_binary" "Portable clang driver binary"
|
assert_file "$clang_binary" "Portable clang driver binary"
|
||||||
|
|
||||||
(( parallel > 0 )) || parallel=$(nproc)
|
(( parallel > 0 )) || parallel=$(nproc)
|
||||||
@@ -150,8 +131,6 @@ fixed_configure_flags=(
|
|||||||
-DCMAKE_DISABLE_FIND_PACKAGE_absl=ON
|
-DCMAKE_DISABLE_FIND_PACKAGE_absl=ON
|
||||||
-DCMAKE_DISABLE_FIND_PACKAGE_PNG=ON
|
-DCMAKE_DISABLE_FIND_PACKAGE_PNG=ON
|
||||||
-DCMAKE_DISABLE_FIND_PACKAGE_Freetype=ON
|
-DCMAKE_DISABLE_FIND_PACKAGE_Freetype=ON
|
||||||
-DUSE_STATIC_MBEDTLS_LIBRARY=ON
|
|
||||||
-DUSE_SHARED_MBEDTLS_LIBRARY=OFF
|
|
||||||
# Freetype's own vendored CMakeLists.txt separately probes for system BZip2 (optional
|
# Freetype's own vendored CMakeLists.txt separately probes for system BZip2 (optional
|
||||||
# bzip2-compressed-font support aurora-main never asked for) regardless of the Freetype
|
# bzip2-compressed-font support aurora-main never asked for) regardless of the Freetype
|
||||||
# find_package disable above, since that only stops aurora's own outer find_package(Freetype)
|
# find_package disable above, since that only stops aurora's own outer find_package(Freetype)
|
||||||
@@ -162,16 +141,6 @@ fixed_configure_flags=(
|
|||||||
-DFT_DISABLE_BZIP2=ON
|
-DFT_DISABLE_BZIP2=ON
|
||||||
-DCMAKE_POLICY_DEFAULT_CMP0168=NEW
|
-DCMAKE_POLICY_DEFAULT_CMP0168=NEW
|
||||||
)
|
)
|
||||||
if [[ "$disconnected" -eq 1 ]]; then
|
|
||||||
fixed_configure_flags+=(
|
|
||||||
-DFETCHCONTENT_FULLY_DISCONNECTED=ON
|
|
||||||
)
|
|
||||||
fi
|
|
||||||
if [[ -n "$sysroot" ]]; then
|
|
||||||
fixed_configure_flags+=(
|
|
||||||
-DCMAKE_SYSROOT="$sysroot"
|
|
||||||
)
|
|
||||||
fi
|
|
||||||
flag_fingerprint=$(printf '%s\n' "${fixed_configure_flags[@]}" | sha256sum | awk '{print $1}')
|
flag_fingerprint=$(printf '%s\n' "${fixed_configure_flags[@]}" | sha256sum | awk '{print $1}')
|
||||||
|
|
||||||
# extern/ is excluded because the payload ships that tree separately (aurora-main/extern is bundled
|
# extern/ is excluded because the payload ships that tree separately (aurora-main/extern is bundled
|
||||||
@@ -179,24 +148,19 @@ flag_fingerprint=$(printf '%s\n' "${fixed_configure_flags[@]}" | sha256sum | awk
|
|||||||
aurora_fingerprint=$(fingerprint_tree "$aurora_source" extern build)
|
aurora_fingerprint=$(fingerprint_tree "$aurora_source" extern build)
|
||||||
[[ -n "$aurora_fingerprint" ]] || fail "The aurora source tree could not be fingerprinted: $aurora_source"
|
[[ -n "$aurora_fingerprint" ]] || fail "The aurora source tree could not be fingerprinted: $aurora_source"
|
||||||
|
|
||||||
# The harvested Crypto++ archive is consumed against this tree's headers, so it is fingerprinted.
|
# The harvested Crypto++ archive is consumed against this tree's headers, so it is fingerprinted
|
||||||
|
# for the same reason as aurora above. No exclusions: unlike aurora's extern/, nothing under
|
||||||
|
# runtime/third_party is shipped separately.
|
||||||
third_party_fingerprint=$(fingerprint_tree "$runtime_source/third_party")
|
third_party_fingerprint=$(fingerprint_tree "$runtime_source/third_party")
|
||||||
[[ -n "$third_party_fingerprint" ]] || fail "The vendored third-party tree could not be fingerprinted: $runtime_source/third_party"
|
[[ -n "$third_party_fingerprint" ]] || fail "The vendored third-party tree could not be fingerprinted: $runtime_source/third_party"
|
||||||
mbedtls_fingerprint=$(sha256_of "$runtime_source/cmake/MbedTLSPin.cmake")
|
|
||||||
|
|
||||||
compiler_sha256=$(sha256_of "$cc")
|
compiler_sha256=$(sha256_of "$clang_binary")
|
||||||
cxx_sha256=$(sha256_of "$cxx")
|
|
||||||
# Append the CXX SHA256 if it doesn't match the hash of CC
|
|
||||||
if [[ "${cxx_sha256}" != "${compiler_sha256}" ]]; then
|
|
||||||
compiler_sha256="${compiler_sha256}:${cxx_sha256}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ "$print_fingerprint_only" -eq 1 ]]; then
|
if [[ "$print_fingerprint_only" -eq 1 ]]; then
|
||||||
printf 'compiler_sha256=%s\n' "$compiler_sha256"
|
printf 'compiler_sha256=%s\n' "$compiler_sha256"
|
||||||
printf 'flag_fingerprint=%s\n' "$flag_fingerprint"
|
printf 'flag_fingerprint=%s\n' "$flag_fingerprint"
|
||||||
printf 'aurora_fingerprint=%s\n' "$aurora_fingerprint"
|
printf 'aurora_fingerprint=%s\n' "$aurora_fingerprint"
|
||||||
printf 'third_party_fingerprint=%s\n' "$third_party_fingerprint"
|
printf 'third_party_fingerprint=%s\n' "$third_party_fingerprint"
|
||||||
printf 'mbedtls_fingerprint=%s\n' "$mbedtls_fingerprint"
|
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -397,21 +361,6 @@ while IFS='|' read -r name type file linkerfile; do
|
|||||||
linker_file_to_reference["$linkerfile"]="@PKG@/$relative_linker"
|
linker_file_to_reference["$linkerfile"]="@PKG@/$relative_linker"
|
||||||
fi
|
fi
|
||||||
done < "$targets_txt"
|
done < "$targets_txt"
|
||||||
mbedtls_refs=()
|
|
||||||
mbedtls_txt="$export_dir/mbedtls.txt"
|
|
||||||
assert_file "$mbedtls_txt" "Mbed TLS export targets list"
|
|
||||||
while IFS='|' read -r name linkerfile; do
|
|
||||||
[[ -n "$name" ]] || continue
|
|
||||||
linkerfile=$(normalize "$linkerfile")
|
|
||||||
ref=${linker_file_to_reference["$linkerfile"]:-}
|
|
||||||
[[ -n "$ref" ]] || fail "Mbed TLS archive was not harvested: $name ($linkerfile)"
|
|
||||||
mbedtls_refs+=("$ref")
|
|
||||||
done < "$mbedtls_txt"
|
|
||||||
[[ ${#mbedtls_refs[@]} -ge 3 ]] || fail "Expected at least three Mbed TLS archives, got ${#mbedtls_refs[@]}"
|
|
||||||
mbedtls_source_dir=$(get_meta mbedtls_source_dir)
|
|
||||||
assert_dir "$mbedtls_source_dir/include/mbedtls" "Mbed TLS headers"
|
|
||||||
mkdir -p "$output_dir/include/mbedtls"
|
|
||||||
cp -a "$mbedtls_source_dir/include/." "$output_dir/include/mbedtls/"
|
|
||||||
# Unlike Windows (SDL/zlib/libpng ship as DLLs by default), everything here was forced static above
|
# Unlike Windows (SDL/zlib/libpng ship as DLLs by default), everything here was forced static above
|
||||||
# and Dawn's own Linux package (verified directly) ships libwebgpu_dawn.a, also static - so zero
|
# and Dawn's own Linux package (verified directly) ships libwebgpu_dawn.a, also static - so zero
|
||||||
# shared imports is the expected, normal outcome, not a failure.
|
# shared imports is the expected, normal outcome, not a failure.
|
||||||
@@ -477,9 +426,6 @@ for item in "${link_items[@]}"; do
|
|||||||
if [[ "$item" = /* ]]; then item_abs=$(normalize "$item"); else item_abs=$(normalize "$stage_dir/$item"); fi
|
if [[ "$item" = /* ]]; then item_abs=$(normalize "$item"); else item_abs=$(normalize "$stage_dir/$item"); fi
|
||||||
ref=${linker_file_to_reference["$item_abs"]:-}
|
ref=${linker_file_to_reference["$item_abs"]:-}
|
||||||
if [[ -n "$ref" ]]; then
|
if [[ -n "$ref" ]]; then
|
||||||
for mbedtls_ref in "${mbedtls_refs[@]}"; do
|
|
||||||
[[ "$ref" == "$mbedtls_ref" ]] && continue 2
|
|
||||||
done
|
|
||||||
package_link_items+=("$ref")
|
package_link_items+=("$ref")
|
||||||
continue
|
continue
|
||||||
fi
|
fi
|
||||||
@@ -559,9 +505,6 @@ generated_cmake="$output_dir/native_prebuilt.cmake"
|
|||||||
format_cmake_block MKW_NP_COMPILE_DEFINITIONS "${package_definitions[@]}"
|
format_cmake_block MKW_NP_COMPILE_DEFINITIONS "${package_definitions[@]}"
|
||||||
format_cmake_block MKW_NP_COMPILE_OPTIONS "${package_compile_options[@]}"
|
format_cmake_block MKW_NP_COMPILE_OPTIONS "${package_compile_options[@]}"
|
||||||
format_cmake_block MKW_NP_LINK_LIBRARIES "${package_link_items[@]}"
|
format_cmake_block MKW_NP_LINK_LIBRARIES "${package_link_items[@]}"
|
||||||
format_cmake_block MKW_NP_MBEDTLS_LIBRARIES "${mbedtls_refs[@]}"
|
|
||||||
echo 'set(MKW_NP_MBEDTLS_INCLUDE_DIR "@PKG@/include/mbedtls")'
|
|
||||||
printf 'set(MKW_NP_MBEDTLS_FINGERPRINT "%s")\n' "$mbedtls_fingerprint"
|
|
||||||
format_cmake_block MKW_NP_AURORA_TARGETS "aurora::gx" "aurora::pad" "aurora::si" "aurora::vi" "aurora::mtx"
|
format_cmake_block MKW_NP_AURORA_TARGETS "aurora::gx" "aurora::pad" "aurora::si" "aurora::vi" "aurora::mtx"
|
||||||
echo ""
|
echo ""
|
||||||
printf 'set(MKW_NP_DAWN_CONFIG_DIR "%s")\n' "$dawn_config_token"
|
printf 'set(MKW_NP_DAWN_CONFIG_DIR "%s")\n' "$dawn_config_token"
|
||||||
@@ -597,12 +540,12 @@ harvested_count=${#linker_file_to_reference[@]}
|
|||||||
|
|
||||||
python3 - "$output_dir" "$compiler_sha256" "$compiler_version" "$flag_fingerprint" \
|
python3 - "$output_dir" "$compiler_sha256" "$compiler_version" "$flag_fingerprint" \
|
||||||
"$dawn_version" "$dawn_runtime_sha256" "$aurora_fingerprint" "$third_party_fingerprint" \
|
"$dawn_version" "$dawn_runtime_sha256" "$aurora_fingerprint" "$third_party_fingerprint" \
|
||||||
"$sdl3_target" "$harvested_count" "$mbedtls_fingerprint" <<'PY'
|
"$sdl3_target" "$harvested_count" <<'PY'
|
||||||
import hashlib, json, os, sys, datetime
|
import hashlib, json, os, sys, datetime
|
||||||
|
|
||||||
(output_dir, compiler_sha256, compiler_version, flag_fingerprint, dawn_version,
|
(output_dir, compiler_sha256, compiler_version, flag_fingerprint, dawn_version,
|
||||||
dawn_runtime_sha256, aurora_fingerprint, third_party_fingerprint, sdl3_target,
|
dawn_runtime_sha256, aurora_fingerprint, third_party_fingerprint, sdl3_target,
|
||||||
harvested_count, mbedtls_fingerprint) = sys.argv[1:]
|
harvested_count) = sys.argv[1:]
|
||||||
|
|
||||||
contents = []
|
contents = []
|
||||||
for root, dirs, files in os.walk(output_dir):
|
for root, dirs, files in os.walk(output_dir):
|
||||||
@@ -617,21 +560,9 @@ for root, dirs, files in os.walk(output_dir):
|
|||||||
contents.append({"Path": rel, "Bytes": os.path.getsize(path), "Sha256": digest})
|
contents.append({"Path": rel, "Bytes": os.path.getsize(path), "Sha256": digest})
|
||||||
contents.sort(key=lambda c: c["Path"])
|
contents.sort(key=lambda c: c["Path"])
|
||||||
|
|
||||||
source_date_epoch = os.environ.get("SOURCE_DATE_EPOCH") or None
|
|
||||||
utc = datetime.timezone.utc
|
|
||||||
if source_date_epoch is not None:
|
|
||||||
built_utc = datetime.datetime.fromtimestamp(
|
|
||||||
int(source_date_epoch),
|
|
||||||
utc
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
built_utc = datetime.datetime.now(utc)
|
|
||||||
|
|
||||||
built_utc = built_utc.strftime("%Y-%m-%dT%H:%M:%S.%f")[:-3] + "Z"
|
|
||||||
|
|
||||||
provenance = {
|
provenance = {
|
||||||
"SchemaVersion": 1,
|
"SchemaVersion": 1,
|
||||||
"BuiltUtc": built_utc,
|
"BuiltUtc": datetime.datetime.now(datetime.timezone.utc).strftime("%Y-%m-%dT%H:%M:%S.%f")[:-3] + "Z",
|
||||||
"CompilerSha256": compiler_sha256,
|
"CompilerSha256": compiler_sha256,
|
||||||
"CompilerVersion": compiler_version,
|
"CompilerVersion": compiler_version,
|
||||||
"FlagFingerprint": flag_fingerprint,
|
"FlagFingerprint": flag_fingerprint,
|
||||||
@@ -639,7 +570,6 @@ provenance = {
|
|||||||
"DawnRuntimeSha256": dawn_runtime_sha256,
|
"DawnRuntimeSha256": dawn_runtime_sha256,
|
||||||
"AuroraSourceFingerprint": aurora_fingerprint,
|
"AuroraSourceFingerprint": aurora_fingerprint,
|
||||||
"ThirdPartySourceFingerprint": third_party_fingerprint,
|
"ThirdPartySourceFingerprint": third_party_fingerprint,
|
||||||
"MbedTlsFingerprint": mbedtls_fingerprint,
|
|
||||||
"Sdl3Target": sdl3_target,
|
"Sdl3Target": sdl3_target,
|
||||||
"HarvestedLibraryCount": int(harvested_count),
|
"HarvestedLibraryCount": int(harvested_count),
|
||||||
"Contents": contents,
|
"Contents": contents,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Fails the release build when a fact duplicated across the repo stops agreeing with the copy
|
# Fails the release build when a fact duplicated across the repo stops agreeing with the copy
|
||||||
# that owns it (recomp.yml). Scripts read pinned facts through Get-MkwProjectPins, but three
|
# that owns it (recomp.yml). Scripts read pinned facts through Get-MkwProjectPins, but three
|
||||||
# consumers can't read YAML (the C++ runtime header, the C# constants, shell scripts, and hand-written lists on
|
# consumers can't read YAML (the C++ runtime header, the C# constants, hand-written lists on
|
||||||
# both sides of the C#/PowerShell boundary), so those are checked here instead.
|
# both sides of the C#/PowerShell boundary), so those are checked here instead.
|
||||||
[CmdletBinding()]
|
[CmdletBinding()]
|
||||||
param([string]$RepositoryRoot)
|
param([string]$RepositoryRoot)
|
||||||
@@ -58,12 +58,6 @@ $hostUri = Get-CapturedValue $retroWfcPayload 'CurrentRetroWfcPayloadUri\s*=\s*"
|
|||||||
if ($hostUri -cne $pins.RetroWfcPayloadUri) {
|
if ($hostUri -cne $pins.RetroWfcPayloadUri) {
|
||||||
Add-Failure "InputValidation.CurrentRetroWfcPayloadUri is '$hostUri' but recomp.yml pins '$($pins.RetroWfcPayloadUri)'."
|
Add-Failure "InputValidation.CurrentRetroWfcPayloadUri is '$hostUri' but recomp.yml pins '$($pins.RetroWfcPayloadUri)'."
|
||||||
}
|
}
|
||||||
$macosSetup = Read-SourceFile (Join-Path $launcher 'macos\setup.command') 'macOS setup.command'
|
|
||||||
$macosUri = Get-CapturedValue $macosSetup "'([^']*/api/wfc/payload\?g=RMCPD00)'" `
|
|
||||||
'The macOS Retro-WFC endpoint'
|
|
||||||
if ($macosUri -cne $pins.RetroWfcPayloadUri) {
|
|
||||||
Add-Failure "macOS setup.command downloads '$macosUri' but recomp.yml pins '$($pins.RetroWfcPayloadUri)'."
|
|
||||||
}
|
|
||||||
|
|
||||||
# --- The game identity: the manifest carries it, but the host also compiles a fallback for a
|
# --- The game identity: the manifest carries it, but the host also compiles a fallback for a
|
||||||
# --- manifest that predates the field, and that fallback decides which disc is accepted.
|
# --- manifest that predates the field, and that fallback decides which disc is accepted.
|
||||||
|
|||||||
@@ -136,11 +136,9 @@ try {
|
|||||||
-CxxCompiler (Join-Path $compilerBin 'x86_64-w64-mingw32-clang++.exe') `
|
-CxxCompiler (Join-Path $compilerBin 'x86_64-w64-mingw32-clang++.exe') `
|
||||||
-ResourceCompiler (Join-Path $compilerBin 'x86_64-w64-mingw32-windres.exe') `
|
-ResourceCompiler (Join-Path $compilerBin 'x86_64-w64-mingw32-windres.exe') `
|
||||||
-DependenciesDirectory $dependencies -AdditionalArguments @('-DMKW_BUILD_PRODUCTS=ON')
|
-DependenciesDirectory $dependencies -AdditionalArguments @('-DMKW_BUILD_PRODUCTS=ON')
|
||||||
Invoke-Checked $cmake $configure 'Configuring the production Windows runtime' `
|
Invoke-Checked $cmake $configure 'Configuring the production Windows runtime'
|
||||||
-WaitForProcessTree $false
|
|
||||||
Invoke-Checked $cmake @('--build', $nativeBuild, '--target', 'WiiCompiled', '--parallel', "$Parallel") `
|
Invoke-Checked $cmake @('--build', $nativeBuild, '--target', 'WiiCompiled', '--parallel', "$Parallel") `
|
||||||
'Compiling and linking the synthetic product with the full runtime' `
|
'Compiling and linking the synthetic product with the full runtime'
|
||||||
-WaitForProcessTree $false
|
|
||||||
Assert-File (Join-Path $nativeBuild 'WiiCompiled.exe') 'Linked synthetic product'
|
Assert-File (Join-Path $nativeBuild 'WiiCompiled.exe') 'Linked synthetic product'
|
||||||
} finally {
|
} finally {
|
||||||
$env:PATH = $oldPath
|
$env:PATH = $oldPath
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<RootNamespace>WiiCompiled.Setup.Common.Cli</RootNamespace>
|
<RootNamespace>WiiCompiled.Setup.Common.Cli</RootNamespace>
|
||||||
<AssemblyName>WiiCompiled.Setup.Common.Cli</AssemblyName>
|
<AssemblyName>WiiCompiled.Setup.Common.Cli</AssemblyName>
|
||||||
|
<Version>0.2.31</Version>
|
||||||
<Authors>patchzy</Authors>
|
<Authors>patchzy</Authors>
|
||||||
<Product>WiiCompiled</Product>
|
<Product>WiiCompiled</Product>
|
||||||
<Description>Packaging-time helper: resolves (downloading if needed) the nodtool binary bundled by build-appimage.sh and Build-Installer.ps1</Description>
|
<Description>Packaging-time helper: resolves (downloading if needed) the nodtool binary bundled by build-appimage.sh and Build-Installer.ps1</Description>
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ public static class RetroWfcPayload
|
|||||||
private static readonly TimeSpan RetroWfcDownloadTimeout = TimeSpan.FromSeconds(30);
|
private static readonly TimeSpan RetroWfcDownloadTimeout = TimeSpan.FromSeconds(30);
|
||||||
private static readonly TimeSpan RetroWfcRetryDelay = TimeSpan.FromSeconds(1);
|
private static readonly TimeSpan RetroWfcRetryDelay = TimeSpan.FromSeconds(1);
|
||||||
|
|
||||||
public const string CurrentRetroWfcPayloadUri = "https://rwfc.net/api/wfc/payload?g=RMCPD00";
|
public const string CurrentRetroWfcPayloadUri = "http://nas.play.rwfc.net/payload?g=RMCPD00";
|
||||||
private static readonly string RetroWfcOfflinePayloadFile =
|
private static readonly string RetroWfcOfflinePayloadFile =
|
||||||
Path.Combine("binary", "payload.RMCPD00.bin");
|
Path.Combine("binary", "payload.RMCPD00.bin");
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<RootNamespace>WiiCompiled.Setup.Common</RootNamespace>
|
<RootNamespace>WiiCompiled.Setup.Common</RootNamespace>
|
||||||
<AssemblyName>WiiCompiled.Setup.Common</AssemblyName>
|
<AssemblyName>WiiCompiled.Setup.Common</AssemblyName>
|
||||||
|
<Version>0.2.31</Version>
|
||||||
<Authors>patchzy</Authors>
|
<Authors>patchzy</Authors>
|
||||||
<Product>WiiCompiled</Product>
|
<Product>WiiCompiled</Product>
|
||||||
<Description>Shared nodtool/Retro-WFC-payload logic used by both the Windows and Linux installers</Description>
|
<Description>Shared nodtool/Retro-WFC-payload logic used by both the Windows and Linux installers</Description>
|
||||||
|
|||||||
@@ -13,8 +13,7 @@ internal static class BuildRunner
|
|||||||
string workspace, string profile, string outputDir, string? baseOutputDir,
|
string workspace, string profile, string outputDir, string? baseOutputDir,
|
||||||
string? retroDir, string? retroWfcOfflineDir, bool skipRetroWfcPayload,
|
string? retroDir, string? retroWfcOfflineDir, bool skipRetroWfcPayload,
|
||||||
bool forceCleanBuild, string? translatorBin, string? ccBin, string? cxxBin, string? fuseLd,
|
bool forceCleanBuild, string? translatorBin, string? ccBin, string? cxxBin, string? fuseLd,
|
||||||
string? cmakeBin, string? ninjaBin, string? nativePrebuiltDir, string? sysroot,
|
string? cmakeBin, string? ninjaBin, string? nativePrebuiltDir, IInstallReporter reporter,
|
||||||
IInstallReporter reporter,
|
|
||||||
CancellationToken cancellationToken)
|
CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var script = Path.Combine(workspace, "Launcher", "local-build.sh");
|
var script = Path.Combine(workspace, "Launcher", "local-build.sh");
|
||||||
@@ -78,10 +77,6 @@ internal static class BuildRunner
|
|||||||
{
|
{
|
||||||
startInfo.ArgumentList.Add("--native-prebuilt-dir"); startInfo.ArgumentList.Add(nativePrebuiltDir);
|
startInfo.ArgumentList.Add("--native-prebuilt-dir"); startInfo.ArgumentList.Add(nativePrebuiltDir);
|
||||||
}
|
}
|
||||||
if (!string.IsNullOrEmpty(sysroot))
|
|
||||||
{
|
|
||||||
startInfo.ArgumentList.Add("--sysroot"); startInfo.ArgumentList.Add(sysroot);
|
|
||||||
}
|
|
||||||
|
|
||||||
using var process = new Process { StartInfo = startInfo };
|
using var process = new Process { StartInfo = startInfo };
|
||||||
var window = new BuildProgressWindow(reporter, InstallStages.Build, start: 6, end: 96);
|
var window = new BuildProgressWindow(reporter, InstallStages.Build, start: 6, end: 96);
|
||||||
|
|||||||
@@ -1,13 +1,9 @@
|
|||||||
using System.Reflection;
|
|
||||||
|
|
||||||
namespace WiiCompiled.Setup.Linux;
|
namespace WiiCompiled.Setup.Linux;
|
||||||
|
|
||||||
internal static class ProductInfo
|
internal static class ProductInfo
|
||||||
{
|
{
|
||||||
public const string Name = "WiiCompiled";
|
public const string Name = "WiiCompiled";
|
||||||
public static readonly string Version =
|
public const string Version = "0.2.31";
|
||||||
typeof(ProductInfo).Assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>()!
|
|
||||||
.InformationalVersion;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>One installed product's record inside install-state.json.</summary>
|
/// <summary>One installed product's record inside install-state.json.</summary>
|
||||||
|
|||||||
@@ -127,52 +127,22 @@ internal static class Program
|
|||||||
string? retroWfcOfflineDir = null;
|
string? retroWfcOfflineDir = null;
|
||||||
if (downloadPayload)
|
if (downloadPayload)
|
||||||
{
|
{
|
||||||
|
// Reused if a previous install already downloaded and it's still valid - matches
|
||||||
|
// Windows's own reuse-if-valid behavior instead of re-downloading on every install.
|
||||||
var cacheDir = Path.Combine(workspace, "generated", "retro-wfc-payload");
|
var cacheDir = Path.Combine(workspace, "generated", "retro-wfc-payload");
|
||||||
reporter.Progress(InstallStages.Validate,
|
reporter.Progress(InstallStages.Validate, "Preparing the Retro-WFC payload", 1);
|
||||||
"Downloading the current Retro-WFC payload", 1);
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// A valid signature authenticates a payload, but does not prove it is the latest
|
RetroWfcPayload.ValidateStagedRetroWfcPayloadDirectory(cacheDir);
|
||||||
// signed revision. Always ask the fixed endpoint for the current snapshot; the
|
}
|
||||||
// downloader verifies it before atomically replacing the cache.
|
catch (InvalidDataException)
|
||||||
|
{
|
||||||
await RetroWfcPayload.DownloadRetroWfcPayloadAsync(
|
await RetroWfcPayload.DownloadRetroWfcPayloadAsync(
|
||||||
RetroWfcPayload.CurrentRetroWfcPayloadUri, cacheDir, token);
|
RetroWfcPayload.CurrentRetroWfcPayloadUri, cacheDir, token);
|
||||||
}
|
}
|
||||||
catch (Exception downloadFailure) when (!token.IsCancellationRequested &&
|
|
||||||
downloadFailure is HttpRequestException or TimeoutException
|
|
||||||
or IOException)
|
|
||||||
{
|
|
||||||
// Offline installs may continue with a previously authenticated snapshot. Do not
|
|
||||||
// use this path for a newly downloaded payload that failed signature validation:
|
|
||||||
// that must remain a hard failure instead of hiding possible endpoint tampering.
|
|
||||||
try
|
|
||||||
{
|
|
||||||
RetroWfcPayload.ValidateStagedRetroWfcPayloadDirectory(cacheDir);
|
|
||||||
}
|
|
||||||
catch (Exception cacheFailure) when (cacheFailure is IOException or
|
|
||||||
UnauthorizedAccessException or InvalidDataException)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException(
|
|
||||||
"The current Retro-WFC payload could not be downloaded and no valid cached " +
|
|
||||||
$"payload is available ({cacheFailure.Message.TrimEnd('.')}).", downloadFailure);
|
|
||||||
}
|
|
||||||
|
|
||||||
reporter.Diagnostic(
|
|
||||||
"The current Retro-WFC payload could not be downloaded; using the previously " +
|
|
||||||
$"verified cached payload instead ({downloadFailure.Message.TrimEnd('.')}).");
|
|
||||||
}
|
|
||||||
retroWfcOfflineDir = cacheDir;
|
retroWfcOfflineDir = cacheDir;
|
||||||
}
|
}
|
||||||
|
|
||||||
var sysroot = flags.GetValueOrDefault("sysroot");
|
|
||||||
// --sysroot explicitly provided (even as bare flag at end of argv, which ParseArgs
|
|
||||||
// stores as null) must carry a path; omitting --sysroot entirely is fine (local-build.sh
|
|
||||||
// adds -UCMAKE_SYSROOT to clear any stale cached value from a prior configure).
|
|
||||||
if (flags.ContainsKey("sysroot") && string.IsNullOrWhiteSpace(sysroot))
|
|
||||||
{
|
|
||||||
throw new ArgumentException("--sysroot requires a non-empty directory path.");
|
|
||||||
}
|
|
||||||
|
|
||||||
await BuildRunner.RunAsync(
|
await BuildRunner.RunAsync(
|
||||||
workspace, profile, installDir, baseInstallDir,
|
workspace, profile, installDir, baseInstallDir,
|
||||||
retroDir,
|
retroDir,
|
||||||
@@ -186,7 +156,6 @@ internal static class Program
|
|||||||
flags.GetValueOrDefault("cmake"),
|
flags.GetValueOrDefault("cmake"),
|
||||||
flags.GetValueOrDefault("ninja"),
|
flags.GetValueOrDefault("ninja"),
|
||||||
flags.GetValueOrDefault("native-prebuilt-dir"),
|
flags.GetValueOrDefault("native-prebuilt-dir"),
|
||||||
sysroot,
|
|
||||||
reporter, token);
|
reporter, token);
|
||||||
|
|
||||||
reporter.Progress(InstallStages.Shortcuts, "Creating shortcuts", 98);
|
reporter.Progress(InstallStages.Shortcuts, "Creating shortcuts", 98);
|
||||||
@@ -355,7 +324,7 @@ internal static class Program
|
|||||||
{--download-retro-wfc-payload | --skip-retro-wfc-payload}]
|
{--download-retro-wfc-payload | --skip-retro-wfc-payload}]
|
||||||
[--force-clean-build] [--translator-bin PATH] [--disc-tool-bin PATH]
|
[--force-clean-build] [--translator-bin PATH] [--disc-tool-bin PATH]
|
||||||
[--cc PATH] [--cxx PATH] [--fuse-ld NAME_OR_PATH] [--cmake PATH] [--ninja PATH]
|
[--cc PATH] [--cxx PATH] [--fuse-ld NAME_OR_PATH] [--cmake PATH] [--ninja PATH]
|
||||||
[--native-prebuilt-dir DIR] [--sysroot PATH] [--progress-json] [--workspace DIR]
|
[--native-prebuilt-dir DIR] [--progress-json] [--workspace DIR]
|
||||||
uninstall
|
uninstall
|
||||||
launch-base
|
launch-base
|
||||||
launch-retro
|
launch-retro
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<AssemblyName>WiiCompiled.Setup.Linux</AssemblyName>
|
<AssemblyName>WiiCompiled.Setup.Linux</AssemblyName>
|
||||||
<RootNamespace>WiiCompiled.Setup.Linux</RootNamespace>
|
<RootNamespace>WiiCompiled.Setup.Linux</RootNamespace>
|
||||||
|
<Version>0.2.31</Version>
|
||||||
<Authors>patchzy</Authors>
|
<Authors>patchzy</Authors>
|
||||||
<Product>WiiCompiled</Product>
|
<Product>WiiCompiled</Product>
|
||||||
<Description>Command-line installer and launcher for WiiCompiled on Linux</Description>
|
<Description>Command-line installer and launcher for WiiCompiled on Linux</Description>
|
||||||
|
|||||||
@@ -110,7 +110,8 @@ internal sealed class ProductRepairService
|
|||||||
InputValidation.CurrentRetroWfcPayloadUri, payloadScratch, cancellationToken);
|
InputValidation.CurrentRetroWfcPayloadUri, payloadScratch, cancellationToken);
|
||||||
}
|
}
|
||||||
catch (Exception ex) when (!cancellationToken.IsCancellationRequested &&
|
catch (Exception ex) when (!cancellationToken.IsCancellationRequested &&
|
||||||
ex is HttpRequestException or TimeoutException or IOException)
|
ex is HttpRequestException or IOException or InvalidDataException
|
||||||
|
or InvalidOperationException or OperationCanceledException)
|
||||||
{
|
{
|
||||||
payloadSnapshot = RecoverInstalledRetroWfcPayload(toolkitFingerprint,
|
payloadSnapshot = RecoverInstalledRetroWfcPayload(toolkitFingerprint,
|
||||||
Path.Combine(scratchRoot, "retro-wfc-payload-recovered"), ex, cancellationToken);
|
Path.Combine(scratchRoot, "retro-wfc-payload-recovered"), ex, cancellationToken);
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
namespace WiiCompiled.Setup.Windows;
|
namespace WiiCompiled.Setup.Windows;
|
||||||
|
|
||||||
using System.Reflection;
|
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
internal static class Program
|
internal static class Program
|
||||||
@@ -122,9 +121,7 @@ internal static class PlatformChecks
|
|||||||
internal static class ProductInfo
|
internal static class ProductInfo
|
||||||
{
|
{
|
||||||
public const string Name = "WiiCompiled";
|
public const string Name = "WiiCompiled";
|
||||||
public static readonly string Version =
|
public const string Version = "0.2.31";
|
||||||
typeof(ProductInfo).Assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>()!
|
|
||||||
.InformationalVersion;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The setup executable is copied into the installation under this name. It is the launcher and
|
/// The setup executable is copied into the installation under this name. It is the launcher and
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
<AssemblyName>WiiCompiled.Setup</AssemblyName>
|
<AssemblyName>WiiCompiled.Setup</AssemblyName>
|
||||||
<RootNamespace>WiiCompiled.Setup.Windows</RootNamespace>
|
<RootNamespace>WiiCompiled.Setup.Windows</RootNamespace>
|
||||||
<ApplicationManifest>app.manifest</ApplicationManifest>
|
<ApplicationManifest>app.manifest</ApplicationManifest>
|
||||||
|
<Version>0.2.31</Version>
|
||||||
<Authors>patchzy</Authors>
|
<Authors>patchzy</Authors>
|
||||||
<Product>WiiCompiled</Product>
|
<Product>WiiCompiled</Product>
|
||||||
<Description>Command-line installer and launcher for WiiCompiled</Description>
|
<Description>Command-line installer and launcher for WiiCompiled</Description>
|
||||||
|
|||||||
@@ -128,9 +128,9 @@ cp -a "$workspace/Launcher/artifacts/portable-tools/toolchain-$appimagetool_arch
|
|||||||
|
|
||||||
# Precompiled aurora + third-party package (see Prepare-NativePrebuilt.sh) so a user's own
|
# Precompiled aurora + third-party package (see Prepare-NativePrebuilt.sh) so a user's own
|
||||||
# local-build.sh never has to compile aurora itself (~43% of local build CPU time). Re-harvesting
|
# local-build.sh never has to compile aurora itself (~43% of local build CPU time). Re-harvesting
|
||||||
# recompiles the aurora/Crypto++/mbed TLS closure with the toolchain above, so this is skipped unless
|
# recompiles the whole aurora/Crypto++ closure with the toolchain above, so this is skipped unless
|
||||||
# --print-fingerprint-only (a fast, build-free check) says the existing package no longer matches
|
# --print-fingerprint-only (a fast, build-free check) says the existing package no longer matches
|
||||||
# the current compiler, flags, source trees or mbed TLS pin.
|
# the current compiler/flags/aurora/third_party sources.
|
||||||
native_prebuilt_dir="$workspace/Launcher/artifacts/native-prebuilt-$appimagetool_arch"
|
native_prebuilt_dir="$workspace/Launcher/artifacts/native-prebuilt-$appimagetool_arch"
|
||||||
echo "Checking whether the precompiled aurora + third-party package ($appimagetool_arch) is current..."
|
echo "Checking whether the precompiled aurora + third-party package ($appimagetool_arch) is current..."
|
||||||
current_fingerprint=$(bash "$script_dir/Prepare-NativePrebuilt.sh" --arch "$appimagetool_arch" --print-fingerprint-only)
|
current_fingerprint=$(bash "$script_dir/Prepare-NativePrebuilt.sh" --arch "$appimagetool_arch" --print-fingerprint-only)
|
||||||
@@ -148,7 +148,6 @@ fields = {
|
|||||||
"flag_fingerprint": "FlagFingerprint",
|
"flag_fingerprint": "FlagFingerprint",
|
||||||
"aurora_fingerprint": "AuroraSourceFingerprint",
|
"aurora_fingerprint": "AuroraSourceFingerprint",
|
||||||
"third_party_fingerprint": "ThirdPartySourceFingerprint",
|
"third_party_fingerprint": "ThirdPartySourceFingerprint",
|
||||||
"mbedtls_fingerprint": "MbedTlsFingerprint",
|
|
||||||
}
|
}
|
||||||
print(1 if all(provenance.get(v) == current.get(k) for k, v in fields.items()) else 0)
|
print(1 if all(provenance.get(v) == current.get(k) for k, v in fields.items()) else 0)
|
||||||
PY
|
PY
|
||||||
|
|||||||
@@ -134,9 +134,7 @@ if (( builds_retro )); then args+=(--resolved-profile "$mod_out/resolved_dispatc
|
|||||||
step emit-build-shards 'Preparing native build shards'; translator "${args[@]}"
|
step emit-build-shards 'Preparing native build shards'; translator "${args[@]}"
|
||||||
|
|
||||||
step configure-native 'Configuring the native toolchain'
|
step configure-native 'Configuring the native toolchain'
|
||||||
# Use Aurora's pinned SDL3 source on macOS. A system SDL3 can be older than
|
"$cmake_bin" -S "$workspace/runtime" -B "$native_build" -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_MAKE_PROGRAM="$ninja_bin" -DCMAKE_OSX_ARCHITECTURES="$macos_arch" -DCMAKE_OSX_DEPLOYMENT_TARGET="$macos_deployment_target" -DAURORA_SDL3_PROVIDER=vendor -DMKW_TRANSLATED_COMPILE_JOBS="$translated_jobs"
|
||||||
# Aurora's required API even when find_package() succeeds.
|
|
||||||
"$cmake_bin" -S "$workspace/runtime" -B "$native_build" -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_MAKE_PROGRAM="$ninja_bin" -DCMAKE_OSX_ARCHITECTURES="$macos_arch" -DCMAKE_OSX_DEPLOYMENT_TARGET="$macos_deployment_target" -DMKW_TRANSLATED_COMPILE_JOBS="$translated_jobs" -DAURORA_SDL3_PROVIDER=vendor
|
|
||||||
targets=(); [[ "$profile" != retro-rewind ]] && targets+=(WiiCompiled); [[ "$profile" != base ]] && targets+=(RetroRewind)
|
targets=(); [[ "$profile" != retro-rewind ]] && targets+=(WiiCompiled); [[ "$profile" != base ]] && targets+=(RetroRewind)
|
||||||
step compile "Compiling ${targets[*]} locally"; "$cmake_bin" --build "$native_build" --target "${targets[@]}" --parallel "$global_jobs"
|
step compile "Compiling ${targets[*]} locally"; "$cmake_bin" --build "$native_build" --target "${targets[@]}" --parallel "$global_jobs"
|
||||||
if [[ "$profile" != retro-rewind ]]; then "$script_dir/macos/publish-app.command" --build-dir "$native_build" --product WiiCompiled --output-dir "${base_output_dir:-$output_dir}" --architecture "$macos_arch" --minimum-system-version "$macos_deployment_target"; fi
|
if [[ "$profile" != retro-rewind ]]; then "$script_dir/macos/publish-app.command" --build-dir "$native_build" --product WiiCompiled --output-dir "${base_output_dir:-$output_dir}" --architecture "$macos_arch" --minimum-system-version "$macos_deployment_target"; fi
|
||||||
|
|||||||
+1
-39
@@ -65,7 +65,6 @@ translator_dll_override=""
|
|||||||
translator_bin_override=""
|
translator_bin_override=""
|
||||||
fuse_ld_override=""
|
fuse_ld_override=""
|
||||||
native_prebuilt_dir=""
|
native_prebuilt_dir=""
|
||||||
sysroot=""
|
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
cat <<'EOF'
|
cat <<'EOF'
|
||||||
@@ -88,8 +87,6 @@ Usage: local-build.sh --output-dir DIR [options]
|
|||||||
--translator-bin PATH Self-contained Translator.Cli executable (skips building AND needs no dotnet at all)
|
--translator-bin PATH Self-contained Translator.Cli executable (skips building AND needs no dotnet at all)
|
||||||
--native-prebuilt-dir DIR Precompiled aurora/third-party package (see Prepare-NativePrebuilt.sh);
|
--native-prebuilt-dir DIR Precompiled aurora/third-party package (see Prepare-NativePrebuilt.sh);
|
||||||
skips compiling aurora-main from source entirely
|
skips compiling aurora-main from source entirely
|
||||||
--sysroot PATH Passed to CMake as -DCMAKE_SYSROOT: where the compiler resolves
|
|
||||||
standard headers/startup files
|
|
||||||
EOF
|
EOF
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,7 +110,6 @@ while [[ $# -gt 0 ]]; do
|
|||||||
--translator-dll) translator_dll_override=$2; shift 2 ;;
|
--translator-dll) translator_dll_override=$2; shift 2 ;;
|
||||||
--translator-bin) translator_bin_override=$2; shift 2 ;;
|
--translator-bin) translator_bin_override=$2; shift 2 ;;
|
||||||
--native-prebuilt-dir) native_prebuilt_dir=$2; shift 2 ;;
|
--native-prebuilt-dir) native_prebuilt_dir=$2; shift 2 ;;
|
||||||
--sysroot) sysroot=$2; shift 2 ;;
|
|
||||||
-h|--help) usage; exit 0 ;;
|
-h|--help) usage; exit 0 ;;
|
||||||
*) fail "unknown argument: $1" ;;
|
*) fail "unknown argument: $1" ;;
|
||||||
esac
|
esac
|
||||||
@@ -190,30 +186,6 @@ assert_file "$project" "Translation project"
|
|||||||
assert_file "$assets/main.dol" "Extracted main.dol (see translator/README.md - owning the game is required)"
|
assert_file "$assets/main.dol" "Extracted main.dol (see translator/README.md - owning the game is required)"
|
||||||
assert_file "$assets/StaticR.rel" "Extracted StaticR.rel (see translator/README.md - owning the game is required)"
|
assert_file "$assets/StaticR.rel" "Extracted StaticR.rel (see translator/README.md - owning the game is required)"
|
||||||
|
|
||||||
# The AppImage bundles Clang, but Linux startup objects and the C/C++ link runtimes
|
|
||||||
# still come from the host. Check them before the expensive translation so a missing
|
|
||||||
# development package produces a useful error instead of CMake's generic exit 1.
|
|
||||||
link_probe_dir=$(mktemp -d)
|
|
||||||
link_probe_flags=()
|
|
||||||
[[ -z "$sysroot" ]] || link_probe_flags+=(--sysroot="$sysroot")
|
|
||||||
[[ -z "$fuse_ld_override" ]] || link_probe_flags+=(-fuse-ld="$fuse_ld_override")
|
|
||||||
printf 'int main(void) { return 0; }\n' > "$link_probe_dir/probe.c"
|
|
||||||
cat > "$link_probe_dir/probe.cpp" <<'EOF'
|
|
||||||
#include <vector>
|
|
||||||
int main() { std::vector<int> values{1}; return values.front() - 1; }
|
|
||||||
EOF
|
|
||||||
if ! "$cc_bin" "${link_probe_flags[@]}" "$link_probe_dir/probe.c" -o "$link_probe_dir/probe-c" > "$link_probe_dir/error" 2>&1; then
|
|
||||||
cat "$link_probe_dir/error" >&2
|
|
||||||
rm -rf "$link_probe_dir"
|
|
||||||
fail "The C compiler cannot link a test program. Linux needs C development files (glibc startup objects and a compiler runtime) in addition to bundled Clang. Install your distribution's development packages, or on SteamOS run WiiCompiled through the Wheel Wizard Flatpak."
|
|
||||||
fi
|
|
||||||
if ! "$cxx_bin" "${link_probe_flags[@]}" "$link_probe_dir/probe.cpp" -o "$link_probe_dir/probe-cxx" > "$link_probe_dir/error" 2>&1; then
|
|
||||||
cat "$link_probe_dir/error" >&2
|
|
||||||
rm -rf "$link_probe_dir"
|
|
||||||
fail "The C++ compiler cannot link a test program. Install your distribution's C++ development packages, or on SteamOS run WiiCompiled through the Wheel Wizard Flatpak."
|
|
||||||
fi
|
|
||||||
rm -rf "$link_probe_dir"
|
|
||||||
|
|
||||||
# Literal line matching against the manifest's fixed shape, not a YAML dependency - the same
|
# Literal line matching against the manifest's fixed shape, not a YAML dependency - the same
|
||||||
# approach NativeBuildFlags.ps1's Get-MkwProjectPins uses on Windows, kept here only for the one
|
# approach NativeBuildFlags.ps1's Get-MkwProjectPins uses on Windows, kept here only for the one
|
||||||
# field this script actually needs from the manifest.
|
# field this script actually needs from the manifest.
|
||||||
@@ -442,14 +414,6 @@ fi
|
|||||||
if [[ -n "$native_prebuilt_dir" ]]; then
|
if [[ -n "$native_prebuilt_dir" ]]; then
|
||||||
configure_args+=(-DMKW_NATIVE_PREBUILT_DIR="$native_prebuilt_dir")
|
configure_args+=(-DMKW_NATIVE_PREBUILT_DIR="$native_prebuilt_dir")
|
||||||
fi
|
fi
|
||||||
if [[ -n "$sysroot" ]]; then
|
|
||||||
configure_args+=(-DCMAKE_SYSROOT="$sysroot")
|
|
||||||
else
|
|
||||||
# Explicitly clear any cached CMAKE_SYSROOT from a prior configure so an
|
|
||||||
# incremental build that transitions from one sysroot to none does not
|
|
||||||
# silently keep the stale cached path.
|
|
||||||
configure_args+=(-UCMAKE_SYSROOT)
|
|
||||||
fi
|
|
||||||
|
|
||||||
log_step configure-native "Configuring the native toolchain"
|
log_step configure-native "Configuring the native toolchain"
|
||||||
"$cmake_bin" "${configure_args[@]}"
|
"$cmake_bin" "${configure_args[@]}"
|
||||||
@@ -481,9 +445,7 @@ publish_built_product() {
|
|||||||
local exe=$build/$target
|
local exe=$build/$target
|
||||||
assert_file "$exe" "Locally compiled game executable"
|
assert_file "$exe" "Locally compiled game executable"
|
||||||
cp -f "$exe" "$destination/$target"
|
cp -f "$exe" "$destination/$target"
|
||||||
# cacert.pem is the TLS root bundle the mbed TLS path looks up beside the executable
|
for name in dsp_coef.bin initial_pipeline_cache.db; do
|
||||||
# (runtime/src/hle/net/network_ssl.cpp); without it HTTPS fails at runtime.
|
|
||||||
for name in dsp_coef.bin initial_pipeline_cache.db cacert.pem; do
|
|
||||||
[[ -f "$build/$name" ]] && cp -f "$build/$name" "$destination/"
|
[[ -f "$build/$name" ]] && cp -f "$build/$name" "$destination/"
|
||||||
done
|
done
|
||||||
[[ -d "$build/wii_bootstrap" ]] && cp -rf "$build/wii_bootstrap" "$destination/"
|
[[ -d "$build/wii_bootstrap" ]] && cp -rf "$build/wii_bootstrap" "$destination/"
|
||||||
|
|||||||
@@ -45,15 +45,6 @@ while (($#)); do
|
|||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
version=${version#v}
|
version=${version#v}
|
||||||
if [[ -z "$version" || "$version" == "0.1.0" ]]; then
|
|
||||||
local_csproj="$workspace/Launcher/Directory.Build.props"
|
|
||||||
if [[ -f "$local_csproj" ]]; then
|
|
||||||
detected=$(grep -m1 '<Version>' "$local_csproj" | sed -E 's/.*<Version>([^<]+)<\/Version>.*/\1/' || true)
|
|
||||||
if [[ -n "$detected" ]]; then
|
|
||||||
version="$detected"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
[[ "$version" =~ ^[0-9]+(\.[0-9]+){0,2}$ ]] || fail '--version must contain one to three period-separated integers'
|
[[ "$version" =~ ^[0-9]+(\.[0-9]+){0,2}$ ]] || fail '--version must contain one to three period-separated integers'
|
||||||
IFS=. read -r version_major version_minor version_patch <<< "$version"
|
IFS=. read -r version_major version_minor version_patch <<< "$version"
|
||||||
short_version="$version_major.${version_minor:-0}.${version_patch:-0}"
|
short_version="$version_major.${version_minor:-0}.${version_patch:-0}"
|
||||||
@@ -154,7 +145,7 @@ copy_clean "$workspace/Launcher/local-build-macos.command" "$resources/workspace
|
|||||||
copy_clean "$workspace/Launcher/macos/extract-disc.command" "$resources/workspace/Launcher/macos/extract-disc.command"
|
copy_clean "$workspace/Launcher/macos/extract-disc.command" "$resources/workspace/Launcher/macos/extract-disc.command"
|
||||||
copy_clean "$workspace/Launcher/macos/publish-app.command" "$resources/workspace/Launcher/macos/publish-app.command"
|
copy_clean "$workspace/Launcher/macos/publish-app.command" "$resources/workspace/Launcher/macos/publish-app.command"
|
||||||
chmod +x "$resources/workspace/Launcher/local-build-macos.command" "$resources/workspace/Launcher/macos/"*.command
|
chmod +x "$resources/workspace/Launcher/local-build-macos.command" "$resources/workspace/Launcher/macos/"*.command
|
||||||
# setup.command uses this marker to update source inputs in an existing user
|
# setup.command uses this marker to refresh source inputs in an existing user
|
||||||
# workspace without replacing extracted game assets or Retro Rewind files.
|
# workspace without replacing extracted game assets or Retro Rewind files.
|
||||||
printf '%s\n' "$version" > "$resources/workspace/.bundle-version"
|
printf '%s\n' "$version" > "$resources/workspace/.bundle-version"
|
||||||
mkdir -p "$resources/tools/cmake" "$resources/tools/arm64" "$resources/tools/x86_64"
|
mkdir -p "$resources/tools/cmake" "$resources/tools/arm64" "$resources/tools/x86_64"
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ done
|
|||||||
for tool in codesign ditto install_name_tool lipo otool; do command -v "$tool" >/dev/null || fail "required macOS tool is unavailable: $tool"; done
|
for tool in codesign ditto install_name_tool lipo otool; do command -v "$tool" >/dev/null || fail "required macOS tool is unavailable: $tool"; done
|
||||||
[[ -x "$build_dir/$product" ]] || fail "missing compiled product: $build_dir/$product"
|
[[ -x "$build_dir/$product" ]] || fail "missing compiled product: $build_dir/$product"
|
||||||
lipo "$build_dir/$product" -verify_arch "$architecture" || fail "compiled product is not $architecture: $build_dir/$product"
|
lipo "$build_dir/$product" -verify_arch "$architecture" || fail "compiled product is not $architecture: $build_dir/$product"
|
||||||
for asset in dsp_coef.bin initial_pipeline_cache.db cacert.pem wii_bootstrap; do [[ -e "$build_dir/$asset" ]] || fail "missing runtime asset: $build_dir/$asset"; done
|
for asset in dsp_coef.bin initial_pipeline_cache.db wii_bootstrap; do [[ -e "$build_dir/$asset" ]] || fail "missing runtime asset: $build_dir/$asset"; done
|
||||||
|
|
||||||
app="$output_dir/$product.app"
|
app="$output_dir/$product.app"
|
||||||
macos="$app/Contents/MacOS"
|
macos="$app/Contents/MacOS"
|
||||||
@@ -60,98 +60,64 @@ cat > "$app/Contents/Info.plist" <<EOF
|
|||||||
</dict></plist>
|
</dict></plist>
|
||||||
EOF
|
EOF
|
||||||
ditto "$build_dir/$product" "$macos/$product"
|
ditto "$build_dir/$product" "$macos/$product"
|
||||||
for asset in dsp_coef.bin initial_pipeline_cache.db cacert.pem wii_bootstrap; do
|
for asset in dsp_coef.bin initial_pipeline_cache.db wii_bootstrap; do
|
||||||
ditto "$build_dir/$asset" "$resources/$asset"
|
ditto "$build_dir/$asset" "$resources/$asset"
|
||||||
ln -s "../Resources/$asset" "$macos/$asset"
|
ln -s "../Resources/$asset" "$macos/$asset"
|
||||||
done
|
done
|
||||||
|
|
||||||
# Expand each image's rpaths before inheriting them, so @loader_path stays
|
# Resolve a non-system dependency from the build product's rpaths. This covers
|
||||||
# relative to the image that declared it rather than a descendant library.
|
# both traditional Homebrew dylibs and the vendored dylibs CMake emits under
|
||||||
expanded_rpaths() {
|
# the local build directory for a cross-architecture build.
|
||||||
local target=$1 rpath
|
|
||||||
while IFS= read -r rpath; do
|
|
||||||
case "$rpath" in
|
|
||||||
@loader_path/*) rpath="$(dirname "$target")/${rpath#@loader_path/}" ;;
|
|
||||||
@loader_path) rpath="$(dirname "$target")" ;;
|
|
||||||
@executable_path/*) rpath="$build_dir/${rpath#@executable_path/}" ;;
|
|
||||||
@executable_path) rpath="$build_dir" ;;
|
|
||||||
esac
|
|
||||||
printf '%s\n' "$rpath"
|
|
||||||
done < <(otool -l "$target" | awk '
|
|
||||||
/LC_RPATH/ { rpath = 1; next }
|
|
||||||
rpath && /^[[:space:]]*path / {
|
|
||||||
sub(/^[[:space:]]*path[[:space:]]+/, "");
|
|
||||||
sub(/[[:space:]]+\(offset[[:space:]]+[0-9]+\)$/, "");
|
|
||||||
print;
|
|
||||||
rpath = 0;
|
|
||||||
}')
|
|
||||||
}
|
|
||||||
|
|
||||||
# Resolve a non-system dependency using the current image's rpaths followed
|
|
||||||
# by the inherited loader stack, matching dyld's dependency-chain search.
|
|
||||||
dependency_path() {
|
dependency_path() {
|
||||||
local current=$1 dependency=$2 search_rpaths=$3 rpath candidate
|
local current=$1 dependency=$2 name rpath candidate
|
||||||
case "$dependency" in
|
case "$dependency" in
|
||||||
/System/Library/*|/usr/lib/*)
|
/opt/homebrew/*|/usr/local/*)
|
||||||
return 1
|
|
||||||
;;
|
|
||||||
/*)
|
|
||||||
[[ -f "$dependency" ]] && { printf '%s\n' "$dependency"; return 0; }
|
[[ -f "$dependency" ]] && { printf '%s\n' "$dependency"; return 0; }
|
||||||
;;
|
;;
|
||||||
|
@rpath/*)
|
||||||
|
name=${dependency##*/}
|
||||||
|
while IFS= read -r rpath; do
|
||||||
|
case "$rpath" in
|
||||||
|
@loader_path/*) rpath="$(dirname "$current")/${rpath#@loader_path/}" ;;
|
||||||
|
@executable_path/*) rpath="$macos/${rpath#@executable_path/}" ;;
|
||||||
|
esac
|
||||||
|
candidate="$rpath/$name"
|
||||||
|
[[ -f "$candidate" ]] && { printf '%s\n' "$candidate"; return 0; }
|
||||||
|
done < <(otool -l "$current" | awk '
|
||||||
|
/LC_RPATH/ { rpath = 1; next }
|
||||||
|
rpath && /^[[:space:]]*path / {
|
||||||
|
sub(/^[[:space:]]*path /, "")
|
||||||
|
sub(/ \(offset [0-9]+\)$/, "")
|
||||||
|
print
|
||||||
|
rpath = 0
|
||||||
|
}
|
||||||
|
')
|
||||||
|
;;
|
||||||
@loader_path/*)
|
@loader_path/*)
|
||||||
candidate="$(dirname "$current")/${dependency#@loader_path/}"
|
candidate="$(dirname "$current")/${dependency#@loader_path/}"
|
||||||
[[ -f "$candidate" ]] && { printf '%s\n' "$candidate"; return 0; }
|
[[ -f "$candidate" ]] && { printf '%s\n' "$candidate"; return 0; }
|
||||||
;;
|
;;
|
||||||
@executable_path/*)
|
|
||||||
candidate="$build_dir/${dependency#@executable_path/}"
|
|
||||||
[[ -f "$candidate" ]] && { printf '%s\n' "$candidate"; return 0; }
|
|
||||||
;;
|
|
||||||
@rpath/*|*.dylib)
|
|
||||||
local subpath
|
|
||||||
if [[ "$dependency" == @rpath/* ]]; then
|
|
||||||
subpath="${dependency#@rpath/}"
|
|
||||||
else
|
|
||||||
subpath="$dependency"
|
|
||||||
fi
|
|
||||||
while IFS= read -r rpath; do
|
|
||||||
[[ -n "$rpath" ]] || continue
|
|
||||||
candidate="$rpath/$subpath"
|
|
||||||
[[ -f "$candidate" ]] && { printf '%s\n' "$candidate"; return 0; }
|
|
||||||
done <<< "$search_rpaths"
|
|
||||||
;;
|
|
||||||
esac
|
esac
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
# Build a closure of non-system dylibs. System libraries remain system
|
# Build a closure of non-system dylibs. System libraries remain system
|
||||||
# references, while every resolved dependency is copied beside the executable.
|
# references, while every resolved dependency is copied beside the executable.
|
||||||
queue=("$build_dir/$product")
|
queue=("$macos/$product")
|
||||||
queue_rpaths=("")
|
|
||||||
while ((${#queue[@]})); do
|
while ((${#queue[@]})); do
|
||||||
current=${queue[0]}
|
current=${queue[0]}
|
||||||
inherited_rpaths=${queue_rpaths[0]}
|
|
||||||
queue=("${queue[@]:1}")
|
queue=("${queue[@]:1}")
|
||||||
queue_rpaths=("${queue_rpaths[@]:1}")
|
|
||||||
current_rpaths=$(expanded_rpaths "$current")
|
|
||||||
if [[ -n "$inherited_rpaths" ]]; then
|
|
||||||
current_rpaths="${current_rpaths}${current_rpaths:+$'\n'}$inherited_rpaths"
|
|
||||||
fi
|
|
||||||
self_id=$(otool -D "$current" 2>/dev/null | tail -n +2 | sed -nE 's/^[[:space:]]*(.*)[[:space:]]*$/\1/p' || true)
|
|
||||||
while IFS= read -r dependency; do
|
while IFS= read -r dependency; do
|
||||||
[[ -z "$dependency" ]] && continue
|
dependency_path=$(dependency_path "$current" "$dependency") || continue
|
||||||
[[ -n "$self_id" && "$dependency" == "$self_id" ]] && continue
|
name=$(basename "$dependency")
|
||||||
case "$dependency" in
|
|
||||||
/System/Library/*|/usr/lib/*) continue ;;
|
|
||||||
esac
|
|
||||||
dep_path=$(dependency_path "$current" "$dependency" "$current_rpaths") || fail "unresolved non-system dependency: '$dependency' needed by '$current'"
|
|
||||||
name=$(basename "$dep_path")
|
|
||||||
if [[ ! -f "$frameworks/$name" ]]; then
|
if [[ ! -f "$frameworks/$name" ]]; then
|
||||||
ditto "$dep_path" "$frameworks/$name"
|
ditto "$dependency_path" "$frameworks/$name"
|
||||||
install_name_tool -id "@rpath/$name" "$frameworks/$name"
|
install_name_tool -id "@rpath/$name" "$frameworks/$name"
|
||||||
queue+=("$dep_path")
|
# Resolve transitive @loader_path dependencies relative to the
|
||||||
queue_rpaths+=("$current_rpaths")
|
# original dylib, not its copied Frameworks location.
|
||||||
|
queue+=("$dependency_path")
|
||||||
fi
|
fi
|
||||||
done < <(otool -L "$current" | tail -n +2 | sed -nE 's/^[[:space:]]*(.*)[[:space:]]+\(compatibility version .*/\1/p')
|
done < <(otool -L "$current" | tail -n +2 | awk '{print $1}')
|
||||||
done
|
done
|
||||||
while IFS= read -r binary; do
|
while IFS= read -r binary; do
|
||||||
while IFS= read -r old; do
|
while IFS= read -r old; do
|
||||||
@@ -162,7 +128,7 @@ while IFS= read -r binary; do
|
|||||||
else
|
else
|
||||||
install_name_tool -change "$old" "@loader_path/$name" "$binary"
|
install_name_tool -change "$old" "@loader_path/$name" "$binary"
|
||||||
fi
|
fi
|
||||||
done < <(otool -L "$binary" | tail -n +2 | sed -nE 's/^[[:space:]]*(.*)[[:space:]]+\(compatibility version .*/\1/p')
|
done < <(otool -L "$binary" | tail -n +2 | awk '{print $1}')
|
||||||
done < <(find "$frameworks" -type f -print; printf '%s\n' "$macos/$product")
|
done < <(find "$frameworks" -type f -print; printf '%s\n' "$macos/$product")
|
||||||
|
|
||||||
find "$frameworks" -type f -exec codesign --force --sign - {} +
|
find "$frameworks" -type f -exec codesign --force --sign - {} +
|
||||||
|
|||||||
@@ -87,7 +87,6 @@ if (( needs_workspace_refresh )); then
|
|||||||
# Refresh only packaged source inputs. Assets and the staged Retro
|
# Refresh only packaged source inputs. Assets and the staged Retro
|
||||||
# Rewind package belong to the user and stay in place.
|
# Rewind package belong to the user and stay in place.
|
||||||
for source in aurora-main projects runtime translator Launcher; do
|
for source in aurora-main projects runtime translator Launcher; do
|
||||||
rm -rf "$workspace/$source"
|
|
||||||
/usr/bin/ditto "$workspace_source/$source" "$workspace/$source"
|
/usr/bin/ditto "$workspace_source/$source" "$workspace/$source"
|
||||||
done
|
done
|
||||||
/usr/bin/ditto "$source_bundle_version" "$workspace_bundle_version"
|
/usr/bin/ditto "$source_bundle_version" "$workspace_bundle_version"
|
||||||
@@ -106,40 +105,26 @@ if [[ -n "$retro_dir" ]]; then
|
|||||||
# verify its pinned signature before publishing it into the local cache.
|
# verify its pinned signature before publishing it into the local cache.
|
||||||
retro_wfc_dir="$support_root/RetroWfcPayload"
|
retro_wfc_dir="$support_root/RetroWfcPayload"
|
||||||
retro_wfc_payload="$retro_wfc_dir/binary/payload.RMCPD00.bin"
|
retro_wfc_payload="$retro_wfc_dir/binary/payload.RMCPD00.bin"
|
||||||
cached_payload_valid=0
|
if [[ -f "$retro_wfc_payload" ]] && ! "$translator" validate-retro-wfc-payload --directory "$retro_wfc_dir"; then
|
||||||
if [[ -f "$retro_wfc_payload" ]]; then
|
printf 'Discarding an invalid cached Retro-WFC payload...\n' >&2
|
||||||
if "$translator" validate-retro-wfc-payload --directory "$retro_wfc_dir"; then
|
rm -f "$retro_wfc_payload"
|
||||||
cached_payload_valid=1
|
|
||||||
else
|
|
||||||
printf 'Discarding an invalid cached Retro-WFC payload...\n' >&2
|
|
||||||
rm -f "$retro_wfc_payload"
|
|
||||||
fi
|
|
||||||
fi
|
fi
|
||||||
|
if [[ ! -f "$retro_wfc_payload" ]]; then
|
||||||
# A signed cache may still be an older vulnerable revision, so always attempt to replace it
|
printf 'Downloading the Retro-WFC payload needed for online play...\n'
|
||||||
# with the current signed snapshot. A transport failure may fall back to the verified cache;
|
mkdir -p "$retro_wfc_dir"
|
||||||
# a downloaded snapshot with an invalid signature remains a hard failure.
|
payload_stage=$(mktemp -d "$retro_wfc_dir/.payload-download.XXXXXX")
|
||||||
printf 'Downloading the current Retro-WFC payload needed for online play...\n'
|
temporary_payload="$payload_stage/binary/payload.RMCPD00.bin"
|
||||||
mkdir -p "$retro_wfc_dir"
|
mkdir -p "$(dirname "$temporary_payload")"
|
||||||
payload_stage=$(mktemp -d "$retro_wfc_dir/.payload-download.XXXXXX")
|
trap 'rm -rf "$payload_stage"' EXIT
|
||||||
temporary_payload="$payload_stage/binary/payload.RMCPD00.bin"
|
/usr/bin/curl --fail --silent --show-error --connect-timeout 10 --max-time 30 \
|
||||||
mkdir -p "$(dirname "$temporary_payload")"
|
--retry 1 --output "$temporary_payload" \
|
||||||
trap 'rm -rf "$payload_stage"' EXIT
|
'http://nas.play.rwfc.net/payload?g=RMCPD00' || fail 'could not download the Retro-WFC payload needed for online play'
|
||||||
if /usr/bin/curl --fail --silent --show-error --connect-timeout 10 --max-time 30 \
|
|
||||||
--retry 1 --output "$temporary_payload" \
|
|
||||||
'https://rwfc.net/api/wfc/payload?g=RMCPD00'; then
|
|
||||||
"$translator" validate-retro-wfc-payload --directory "$payload_stage" || \
|
"$translator" validate-retro-wfc-payload --directory "$payload_stage" || \
|
||||||
fail 'downloaded Retro-WFC payload failed signature validation'
|
fail 'downloaded Retro-WFC payload failed signature validation'
|
||||||
mkdir -p "$retro_wfc_dir/binary"
|
mkdir -p "$retro_wfc_dir/binary"
|
||||||
mv "$temporary_payload" "$retro_wfc_payload"
|
mv "$temporary_payload" "$retro_wfc_payload"
|
||||||
rmdir "$payload_stage/binary" "$payload_stage"
|
rmdir "$payload_stage/binary" "$payload_stage"
|
||||||
trap - EXIT
|
trap - EXIT
|
||||||
elif (( cached_payload_valid )); then
|
|
||||||
printf 'Could not download the current Retro-WFC payload; using the previously verified cached payload.\n' >&2
|
|
||||||
rm -rf "$payload_stage"
|
|
||||||
trap - EXIT
|
|
||||||
else
|
|
||||||
fail 'could not download the current Retro-WFC payload and no valid cached payload is available'
|
|
||||||
fi
|
fi
|
||||||
build_args+=(--profile both --base-output-dir "$products" --retro-rewind-package-dir "$retro_dir" --retro-wfc-offline-dir "$retro_wfc_dir")
|
build_args+=(--profile both --base-output-dir "$products" --retro-rewind-package-dir "$retro_dir" --retro-wfc-offline-dir "$retro_wfc_dir")
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -1,61 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
# Exercise a real dyld dependency chain before and after app packaging.
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
[[ $(uname -s) == Darwin ]] || { printf 'This test requires macOS.\n' >&2; exit 1; }
|
|
||||||
script_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
|
||||||
temp_root=$(cd "${TMPDIR:-/tmp}" && pwd)
|
|
||||||
test_root=$(mktemp -d "$temp_root/wiicompiled-publish-test.XXXXXX")
|
|
||||||
[[ "$test_root" == "$temp_root"/wiicompiled-publish-test.* ]] || exit 1
|
|
||||||
trap 'rm -rf "$test_root"' EXIT
|
|
||||||
|
|
||||||
build_dir="$test_root/build with spaces"
|
|
||||||
output_dir="$test_root/output with spaces"
|
|
||||||
mkdir -p "$build_dir/A/b" "$build_dir/A/c" "$build_dir/wii_bootstrap" "$output_dir"
|
|
||||||
cat > "$test_root/c.c" <<'EOF'
|
|
||||||
int value_c(void) { return 7; }
|
|
||||||
EOF
|
|
||||||
cat > "$test_root/b.c" <<'EOF'
|
|
||||||
extern int value_c(void);
|
|
||||||
int value_b(void) { return 2 * value_c(); }
|
|
||||||
EOF
|
|
||||||
cat > "$test_root/a.c" <<'EOF'
|
|
||||||
extern int value_b(void);
|
|
||||||
int value_a(void) { return 1 + value_b(); }
|
|
||||||
EOF
|
|
||||||
cat > "$test_root/main.c" <<'EOF'
|
|
||||||
#include <stdio.h>
|
|
||||||
extern int value_a(void);
|
|
||||||
int main(void) { printf("%d\n", value_a()); return 0; }
|
|
||||||
EOF
|
|
||||||
|
|
||||||
clang -dynamiclib "$test_root/c.c" -o "$build_dir/A/c/libC.dylib" \
|
|
||||||
-Wl,-headerpad_max_install_names -Wl,-install_name,@rpath/libC.dylib
|
|
||||||
clang -dynamiclib "$test_root/b.c" -o "$build_dir/A/b/libB.dylib" \
|
|
||||||
-L "$build_dir/A/c" -lC \
|
|
||||||
-Wl,-headerpad_max_install_names -Wl,-install_name,@rpath/libB.dylib
|
|
||||||
clang -dynamiclib "$test_root/a.c" -o "$build_dir/A/libA.dylib" \
|
|
||||||
-L "$build_dir/A/b" -lB \
|
|
||||||
-Wl,-headerpad_max_install_names -Wl,-install_name,@rpath/libA.dylib \
|
|
||||||
-Wl,-rpath,@loader_path/b -Wl,-rpath,@loader_path/c
|
|
||||||
clang "$test_root/main.c" -o "$build_dir/WiiCompiled" \
|
|
||||||
-L "$build_dir/A" -lA \
|
|
||||||
-Wl,-headerpad_max_install_names -Wl,-rpath,@executable_path/A
|
|
||||||
|
|
||||||
# B has no rpaths: its C dependency must inherit A's loader-relative path.
|
|
||||||
[[ $("$build_dir/WiiCompiled") == 15 ]]
|
|
||||||
for asset in dsp_coef.bin initial_pipeline_cache.db cacert.pem; do
|
|
||||||
: > "$build_dir/$asset"
|
|
||||||
done
|
|
||||||
bash "$script_dir/publish-app.command" --build-dir "$build_dir" \
|
|
||||||
--product WiiCompiled --output-dir "$output_dir" --architecture "$(uname -m)"
|
|
||||||
for name in libA.dylib libB.dylib libC.dylib; do
|
|
||||||
[[ -f "$output_dir/WiiCompiled.app/Contents/Frameworks/$name" ]]
|
|
||||||
done
|
|
||||||
|
|
||||||
# Remove access to the original paths and relocate the app before executing.
|
|
||||||
mv "$build_dir" "$test_root/hidden build"
|
|
||||||
mkdir "$test_root/relocated app"
|
|
||||||
mv "$output_dir/WiiCompiled.app" "$test_root/relocated app/WiiCompiled.app"
|
|
||||||
[[ $("$test_root/relocated app/WiiCompiled.app/Contents/MacOS/WiiCompiled") == 15 ]]
|
|
||||||
printf 'publish-app dependency-chain test passed\n'
|
|
||||||
@@ -52,13 +52,13 @@ done
|
|||||||
|
|
||||||
case "$arch" in
|
case "$arch" in
|
||||||
x86_64) llvm_release_arch=X64; target_triple=x86_64-unknown-linux-gnu
|
x86_64) llvm_release_arch=X64; target_triple=x86_64-unknown-linux-gnu
|
||||||
llvm_release_sha256=fccecb1906e7ddf5ec040aec5b646b650e2daaafa4423b41341c4717db5bdec0
|
llvm_release_sha256=df0e1ecf16caf3489a272a5eea4eec9b0d82878f6477fa309504f918a0006384
|
||||||
cmake_release_arch=x86_64
|
cmake_release_arch=x86_64
|
||||||
cmake_sha256=927b2368a946c37269c3a66225ab00544e756459cdd0b5d0da438694fb9ff802
|
cmake_sha256=927b2368a946c37269c3a66225ab00544e756459cdd0b5d0da438694fb9ff802
|
||||||
ninja_asset=ninja-linux.zip
|
ninja_asset=ninja-linux.zip
|
||||||
ninja_sha256=5749cbc4e668273514150a80e387a957f933c6ed3f5f11e03fb30955e2bbead6 ;;
|
ninja_sha256=5749cbc4e668273514150a80e387a957f933c6ed3f5f11e03fb30955e2bbead6 ;;
|
||||||
aarch64) llvm_release_arch=ARM64; target_triple=aarch64-unknown-linux-gnu
|
aarch64) llvm_release_arch=ARM64; target_triple=aarch64-unknown-linux-gnu
|
||||||
llvm_release_sha256=d431eff9f064c86ee7c4c94af570a8f74fcccd1f74c6f0da3af32ce34a1e1b05
|
llvm_release_sha256=805efad2bb91cb4967fa569e0881d10c0f69c04461cf671cccbae19f547acc34
|
||||||
cmake_release_arch=aarch64
|
cmake_release_arch=aarch64
|
||||||
cmake_sha256=9ea38356dbd3e32e51029a3e09a0f2f8e117ef4fbcaad7a21ffb36409bbd5cb4
|
cmake_sha256=9ea38356dbd3e32e51029a3e09a0f2f8e117ef4fbcaad7a21ffb36409bbd5cb4
|
||||||
ninja_asset=ninja-linux-aarch64.zip
|
ninja_asset=ninja-linux-aarch64.zip
|
||||||
@@ -101,14 +101,11 @@ rm -rf "$work"
|
|||||||
mkdir -p "$work/bin" "$work/lib/$target_triple" "$work/include/$target_triple/c++/v1"
|
mkdir -p "$work/bin" "$work/lib/$target_triple" "$work/include/$target_triple/c++/v1"
|
||||||
|
|
||||||
# --- clang/lld/llvm-ar, pruned from the official LLVM release ---
|
# --- clang/lld/llvm-ar, pruned from the official LLVM release ---
|
||||||
# built from PR https://github.com/llvm/llvm-project/pull/222821 on official LLVM Github Actions Runner
|
|
||||||
# only switch to an official stable LLVM release again once:
|
llvm_archive_name="LLVM-$llvm_version-Linux-$llvm_release_arch.tar.xz"
|
||||||
# - this PR has merged https://github.com/llvm/llvm-project/pull/221365 and been backported to LLVM stable branch
|
|
||||||
# - this bug has been fixed with a workaround in the Wiicompiled translator https://github.com/patchzyy/Wiicompiled/issues/208 or in LLVM and been backported to LLVM stable branch
|
|
||||||
llvm_archive_name="LLVM-PR222821-5ae1c7c43a11b4cdc5ce4dd483c28357bab7dae2-Linux-$llvm_release_arch.tar.xz"
|
|
||||||
llvm_archive="$downloads/$llvm_archive_name"
|
llvm_archive="$downloads/$llvm_archive_name"
|
||||||
download_verified "$llvm_archive" \
|
download_verified "$llvm_archive" \
|
||||||
"https://github.com/theofficialgman/llvm-project/releases/download/llvmorg-22.1.8-patched/$llvm_archive_name" \
|
"https://github.com/llvm/llvm-project/releases/download/llvmorg-$llvm_version/$llvm_archive_name" \
|
||||||
"$llvm_release_sha256"
|
"$llvm_release_sha256"
|
||||||
|
|
||||||
extract_root="$script_dir/artifacts/.extract-clang-$arch"
|
extract_root="$script_dir/artifacts/.extract-clang-$arch"
|
||||||
@@ -116,7 +113,7 @@ rm -rf "$extract_root"
|
|||||||
mkdir -p "$extract_root"
|
mkdir -p "$extract_root"
|
||||||
echo "prepare-portable-tools.sh: extracting $llvm_archive_name (this is the full ~1.9 GiB release; only a fraction is kept)..."
|
echo "prepare-portable-tools.sh: extracting $llvm_archive_name (this is the full ~1.9 GiB release; only a fraction is kept)..."
|
||||||
tar -xf "$llvm_archive" -C "$extract_root"
|
tar -xf "$llvm_archive" -C "$extract_root"
|
||||||
src="$extract_root/${llvm_archive_name%.tar.xz}"
|
src="$extract_root/LLVM-$llvm_version-Linux-$llvm_release_arch"
|
||||||
[[ -d "$src" ]] || { echo "prepare-portable-tools.sh: unexpected archive layout, expected $src" >&2; exit 1; }
|
[[ -d "$src" ]] || { echo "prepare-portable-tools.sh: unexpected archive layout, expected $src" >&2; exit 1; }
|
||||||
|
|
||||||
echo "prepare-portable-tools.sh: pruning to the minimal compile+link toolchain..."
|
echo "prepare-portable-tools.sh: pruning to the minimal compile+link toolchain..."
|
||||||
@@ -168,8 +165,7 @@ rm -rf "$cmake_extract_root"
|
|||||||
mkdir -p "$cmake_extract_root"
|
mkdir -p "$cmake_extract_root"
|
||||||
echo "prepare-portable-tools.sh: extracting $cmake_archive_name..."
|
echo "prepare-portable-tools.sh: extracting $cmake_archive_name..."
|
||||||
tar -xzf "$cmake_archive" -C "$cmake_extract_root"
|
tar -xzf "$cmake_archive" -C "$cmake_extract_root"
|
||||||
|
cmake_src="$cmake_extract_root/cmake-$cmake_version-linux-$cmake_release_arch"
|
||||||
cmake_src="$cmake_extract_root/${cmake_archive_name%.tar.gz}"
|
|
||||||
[[ -d "$cmake_src" ]] || { echo "prepare-portable-tools.sh: unexpected archive layout, expected $cmake_src" >&2; exit 1; }
|
[[ -d "$cmake_src" ]] || { echo "prepare-portable-tools.sh: unexpected archive layout, expected $cmake_src" >&2; exit 1; }
|
||||||
|
|
||||||
mkdir -p "$work/share/cmake-$cmake_share_version"
|
mkdir -p "$work/share/cmake-$cmake_share_version"
|
||||||
@@ -225,7 +221,7 @@ EOF
|
|||||||
# clang++ invocation above would not catch a broken CMAKE_ROOT (Modules/Templates) or a Ninja that
|
# clang++ invocation above would not catch a broken CMAKE_ROOT (Modules/Templates) or a Ninja that
|
||||||
# can't find the compiler.
|
# can't find the compiler.
|
||||||
cat > "$test_dir/CMakeLists.txt" <<'EOF'
|
cat > "$test_dir/CMakeLists.txt" <<'EOF'
|
||||||
cmake_minimum_required(VERSION 3.25)
|
cmake_minimum_required(VERSION 3.16)
|
||||||
project(test CXX)
|
project(test CXX)
|
||||||
add_executable(test t.cpp)
|
add_executable(test t.cpp)
|
||||||
EOF
|
EOF
|
||||||
@@ -237,6 +233,5 @@ EOF
|
|||||||
rm -rf "$test_dir"
|
rm -rf "$test_dir"
|
||||||
trap - EXIT
|
trap - EXIT
|
||||||
|
|
||||||
rm -rf "$toolchain_dir"
|
|
||||||
mv "$work" "$toolchain_dir"
|
mv "$work" "$toolchain_dir"
|
||||||
echo "prepare-portable-tools.sh: toolchain ready at $toolchain_dir ($(du -sh "$toolchain_dir" | cut -f1))"
|
echo "prepare-portable-tools.sh: toolchain ready at $toolchain_dir ($(du -sh "$toolchain_dir" | cut -f1))"
|
||||||
|
|||||||
@@ -1,19 +1,6 @@
|
|||||||
<img width="4190" height="1232" alt="wiicomplogofinalfinalfinalev2MADEBY_INKWRECK_plzcredit" src="https://github.com/user-attachments/assets/df7a3f2e-5336-479a-b4c0-968dd578726d" />
|
|
||||||
|
|
||||||
# WiiCompiled
|
# WiiCompiled
|
||||||
|
|
||||||
<p align="center">
|
|
||||||
<a href="https://github.com/patchzyy/Wiicompiled/releases"><img alt="Windows 10 / 11, x64" src="https://img.shields.io/badge/Windows-10%20%2F%2011%20%C2%B7%20x64-0078D4"></a>
|
|
||||||
<a href="https://github.com/patchzyy/Wiicompiled/releases"><img alt="Linux, x64 / ARM64" src="https://img.shields.io/badge/Linux-x64%20%2F%20ARM64-FCC624?logo=linux&logoColor=white"></a>
|
|
||||||
<a href="https://github.com/patchzyy/Wiicompiled/releases"><img alt="macOS 14+, Apple Silicon" src="https://img.shields.io/badge/macOS-14%2B%20%C2%B7%20Apple%20Silicon-0A84FF?logo=apple&logoColor=white"></a>
|
|
||||||
</p>
|
|
||||||
<p align="center">
|
|
||||||
<a href="#building-from-source"><img alt="PowerPC static recompilation" src="https://img.shields.io/badge/PowerPC-static%20recompilation-FF9F0A"></a>
|
|
||||||
<a href="#retro-rewind"><img alt="Retro Rewind supported" src="https://img.shields.io/badge/Retro%20Rewind-supported-FF375F"></a>
|
|
||||||
<a href="https://github.com/TeamWheelWizard/WheelWizard/releases"><img alt="Install with Wheel Wizard" src="https://img.shields.io/badge/install%20with-Wheel%20Wizard-8B5CF6"></a>
|
|
||||||
<a href="LICENSE"><img alt="License: GPLv3" src="https://img.shields.io/badge/license-GPLv3-2EA44F?logo=gnu&logoColor=white"></a>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
A native PC port of Mario Kart Wii, made with static recompilation.
|
A native PC port of Mario Kart Wii, made with static recompilation.
|
||||||
|
|
||||||
There's no emulator in the loop, no interpreter, no JIT, no PowerPC
|
There's no emulator in the loop, no interpreter, no JIT, no PowerPC
|
||||||
@@ -52,16 +39,12 @@ Play at several times the console's resolution.
|
|||||||
**Music ducking.**
|
**Music ducking.**
|
||||||
Start playing something else, Spotify, a YouTube video, and
|
Start playing something else, Spotify, a YouTube video, and
|
||||||
the game automatically mutes its own music until the other audio stops. Optional, if you'd
|
the game automatically mutes its own music until the other audio stops. Optional, if you'd
|
||||||
rather it didn't. Windows uses system media controls and Linux uses MPRIS players.
|
rather it didn't. All audio that shows in your display media controls on your windows pc fall under this.
|
||||||
On macOS 14.2 or later, this detects other apps with active audio output and excludes
|
|
||||||
the game's own audio. Apps that keep an output stream running silently can keep
|
|
||||||
game music muted even when nothing is audible.
|
|
||||||
|
|
||||||
**An in-game settings bar.**
|
**An in-game settings bar.**
|
||||||
Press **F10** while the game window has focus:
|
Press **F10** while the game window has focus:
|
||||||
- Internal resolution
|
- Internal resolution
|
||||||
- FPS counter
|
- FPS counter
|
||||||
- MetalFX spatial upscaling on supported macOS GPUs
|
|
||||||
- Controller assignment for all four ports
|
- Controller assignment for all four ports
|
||||||
- Full per-controller button mapping, including the bumpers
|
- Full per-controller button mapping, including the bumpers
|
||||||
- Dolphin-syntax input expressions and GCPadNew.ini import
|
- Dolphin-syntax input expressions and GCPadNew.ini import
|
||||||
@@ -70,6 +53,12 @@ Press **F10** while the game window has focus:
|
|||||||
|
|
||||||
Everything you change is saved to `Config.toml` on the spot and restored next launch.
|
Everything you change is saved to `Config.toml` on the spot and restored next launch.
|
||||||
|
|
||||||
|
**Real controller support.**
|
||||||
|
Controllers are fed to the game as a GameCube controller.
|
||||||
|
Mappings are positional (`south`, `east`, `west`, `north`) rather than Xbox-labelled, so the
|
||||||
|
same config makes sense on Xbox, PlayStation, Nintendo and generic SDL pads alike, and extra
|
||||||
|
inputs like paddles, touchpads and share buttons show up when the hardware reports them.
|
||||||
|
|
||||||
**Dolphin-compatible input expressions.**
|
**Dolphin-compatible input expressions.**
|
||||||
Each GameCube control can carry an expression in Dolphin's input syntax, with the same operators
|
Each GameCube control can carry an expression in Dolphin's input syntax, with the same operators
|
||||||
and the same functions.
|
and the same functions.
|
||||||
@@ -124,10 +113,10 @@ launching. The backend itself is deliberately command-line only, Wheel Wizard is
|
|||||||
|
|
||||||
### macOS
|
### macOS
|
||||||
|
|
||||||
Download `WiiCompiled-Setup.pkg` from this repository's Releases page and open it. The universal
|
Download `WiiCompiled-Setup.pkg` from this repository's Releases page and open it. It requires an
|
||||||
package selects the appropriate bundled tools for the host architecture, supporting both Apple Silicon (`arm64`)
|
Apple Silicon Mac because its bundled nodtool and Translator.Cli executables are arm64. It installs
|
||||||
and Intel (`x86_64`) Macs. It installs **WiiCompiled Setup** in Applications; open that app, choose
|
**WiiCompiled Setup** in Applications; open that app, choose your clean PAL `RMCP01` disc image,
|
||||||
your clean PAL `RMCP01` disc image, and select either the base game or Retro Rewind. For Retro Rewind, choose the `RetroRewind6` folder
|
and select either the base game or Retro Rewind. For Retro Rewind, choose the `RetroRewind6` folder
|
||||||
or its parent folder.
|
or its parent folder.
|
||||||
|
|
||||||
Setup verifies and extracts the image locally, then translates and compiles the native app on your
|
Setup verifies and extracts the image locally, then translates and compiles the native app on your
|
||||||
@@ -176,9 +165,7 @@ The default test suite needs no binaries and no host C++ compiler, so you can ha
|
|||||||
translator without any game data around.
|
translator without any game data around.
|
||||||
|
|
||||||
For everything beyond that, feeding in your own `main.dol`/`StaticR.rel`, running the
|
For everything beyond that, feeding in your own `main.dol`/`StaticR.rel`, running the
|
||||||
translation, generating the manifest and build graph, and compiling, see [`translator/README.md`](translator/README.md).
|
translation, generating the manifest and build graph, and compiling. see [`translator/README.md`](translator/README.md).
|
||||||
|
|
||||||
For a step-by-step guide on compiling both WiiCompiled and Retro Rewind from source on macOS (Apple Silicon), see the [macOS Build Guide](docs/building-macos.md).
|
|
||||||
|
|
||||||
## FAQ
|
## FAQ
|
||||||
|
|
||||||
@@ -227,7 +214,7 @@ AI coding tools were used during development of this project.
|
|||||||
All translated output is verified against real hardware behavior and most importantly, physics accuracy is proven synced across Wii, Dolphin, and WiiCompiled (see FAQ).
|
All translated output is verified against real hardware behavior and most importantly, physics accuracy is proven synced across Wii, Dolphin, and WiiCompiled (see FAQ).
|
||||||
|
|
||||||
## Credits
|
## Credits
|
||||||
- **inkwreck** - making the logo
|
|
||||||
- **[aurora](https://github.com/encounter/aurora)** - the GX rendering/windowing backend this
|
- **[aurora](https://github.com/encounter/aurora)** - the GX rendering/windowing backend this
|
||||||
project's whole graphics layer sits on. MIT licensed.
|
project's whole graphics layer sits on. MIT licensed.
|
||||||
- **[Dawn](https://dawn.googlesource.com/dawn)** - Google's WebGPU implementation, powering
|
- **[Dawn](https://dawn.googlesource.com/dawn)** - Google's WebGPU implementation, powering
|
||||||
|
|||||||
@@ -114,16 +114,14 @@ Source: <https://github.com/higan-emu/libco>. Full license text:
|
|||||||
|
|
||||||
## Fetched at build time and redistributed in release builds
|
## Fetched at build time and redistributed in release builds
|
||||||
|
|
||||||
These are pinned in `aurora-main/extern/CMakeLists.txt`, `aurora-main/CMakeLists.txt`,
|
These are pinned in `aurora-main/extern/CMakeLists.txt`, `aurora-main/CMakeLists.txt` and
|
||||||
`aurora-main/cmake/AuroraDawnProvider.cmake`, and (for Mbed TLS) `runtime/CMakeLists.txt`. They are
|
`aurora-main/cmake/AuroraDawnProvider.cmake`. They are not stored in this repository; the build
|
||||||
not stored in this repository; the build downloads them - each fetch is pinned to an exact version
|
downloads them, and release installers carry the resulting binaries. Their license texts are
|
||||||
with a checked SHA-256 - and links or redistributes the resulting binaries. Their license texts are
|
|
||||||
included in the installer's `licenses/` folder. The Windows installer bundles the pinned source
|
included in the installer's `licenses/` folder. The Windows installer bundles the pinned source
|
||||||
trees themselves (fetched by `Launcher/Prepare-Dependencies.ps1`) so end-user builds run offline.
|
trees themselves (fetched by `Launcher/Prepare-Dependencies.ps1`) so end-user builds run offline.
|
||||||
|
|
||||||
| Component | Version | License | Upstream |
|
| Component | Version | License | Upstream |
|
||||||
| --- | --- | --- | --- |
|
| --- | --- | --- | --- |
|
||||||
| Mbed TLS | 3.6.7 | Apache-2.0 / GPL-2.0-or-later | <https://github.com/Mbed-TLS/mbedtls> |
|
|
||||||
| Dawn (WebGPU) | `v20260603.191052` prebuilt | BSD-3-Clause | <https://dawn.googlesource.com/dawn> |
|
| Dawn (WebGPU) | `v20260603.191052` prebuilt | BSD-3-Clause | <https://dawn.googlesource.com/dawn> |
|
||||||
| Tint (part of Dawn) | with Dawn | BSD-3-Clause | <https://dawn.googlesource.com/dawn> |
|
| Tint (part of Dawn) | with Dawn | BSD-3-Clause | <https://dawn.googlesource.com/dawn> |
|
||||||
| DirectXShaderCompiler (`dxcompiler.dll`) | with Dawn | NCSA / University of Illinois Open Source | <https://github.com/microsoft/DirectXShaderCompiler> |
|
| DirectXShaderCompiler (`dxcompiler.dll`) | with Dawn | NCSA / University of Illinois Open Source | <https://github.com/microsoft/DirectXShaderCompiler> |
|
||||||
@@ -132,7 +130,7 @@ trees themselves (fetched by `Launcher/Prepare-Dependencies.ps1`) so end-user bu
|
|||||||
| Abseil | LTS 20240722.0 | Apache-2.0 | <https://github.com/abseil/abseil-cpp> |
|
| Abseil | LTS 20240722.0 | Apache-2.0 | <https://github.com/abseil/abseil-cpp> |
|
||||||
| Dear ImGui | 1.91.9b-docking | MIT | <https://github.com/ocornut/imgui> |
|
| Dear ImGui | 1.91.9b-docking | MIT | <https://github.com/ocornut/imgui> |
|
||||||
| {fmt} | 11.1.4 | MIT | <https://github.com/fmtlib/fmt> |
|
| {fmt} | 11.1.4 | MIT | <https://github.com/fmtlib/fmt> |
|
||||||
| xxHash | 0.8.4 | BSD-2-Clause | <https://github.com/Cyan4973/xxHash> |
|
| xxHash | 0.8.3 | BSD-2-Clause | <https://github.com/Cyan4973/xxHash> |
|
||||||
| zlib | 1.3.2 | zlib | <https://github.com/madler/zlib> |
|
| zlib | 1.3.2 | zlib | <https://github.com/madler/zlib> |
|
||||||
| libpng | 1.6.58 | PNG Reference Library License v2 | <https://github.com/pnggroup/libpng> |
|
| libpng | 1.6.58 | PNG Reference Library License v2 | <https://github.com/pnggroup/libpng> |
|
||||||
| FreeType | 2.14.3 | **FreeType License (FTL)** - see below | <https://freetype.org/> |
|
| FreeType | 2.14.3 | **FreeType License (FTL)** - see below | <https://freetype.org/> |
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ cmake_minimum_required(VERSION 3.25)
|
|||||||
project(aurora LANGUAGES C CXX)
|
project(aurora LANGUAGES C CXX)
|
||||||
if (APPLE)
|
if (APPLE)
|
||||||
enable_language(OBJC)
|
enable_language(OBJC)
|
||||||
enable_language(OBJCXX)
|
|
||||||
endif()
|
endif()
|
||||||
set(CMAKE_C_STANDARD 11)
|
set(CMAKE_C_STANDARD 11)
|
||||||
set(CMAKE_CXX_STANDARD 20)
|
set(CMAKE_CXX_STANDARD 20)
|
||||||
@@ -84,10 +83,3 @@ if (CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR AND NOT CMAKE_CROSSCOMPIL
|
|||||||
enable_testing()
|
enable_testing()
|
||||||
add_subdirectory(tests)
|
add_subdirectory(tests)
|
||||||
endif ()
|
endif ()
|
||||||
|
|
||||||
option(AURORA_BUILD_METALFX_PRESENTATION_TEST "Build the macOS MetalFX presentation test" OFF)
|
|
||||||
if (AURORA_BUILD_METALFX_PRESENTATION_TEST AND APPLE AND AURORA_ENABLE_GX AND DAWN_ENABLE_METAL AND AURORA_METALFX_FRAMEWORK)
|
|
||||||
add_executable(metalfx_presentation_test tests/metalfx_interop/presentation_test.cpp)
|
|
||||||
target_include_directories(metalfx_presentation_test PRIVATE lib)
|
|
||||||
target_link_libraries(metalfx_presentation_test PRIVATE aurora::core aurora::gx aurora::vi dawn::webgpu_dawn)
|
|
||||||
endif ()
|
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ if (_aurora_dawn_provider STREQUAL "auto")
|
|||||||
set(_has_package TRUE)
|
set(_has_package TRUE)
|
||||||
elseif (CMAKE_SYSTEM_NAME STREQUAL "Linux" AND CMAKE_SYSTEM_PROCESSOR MATCHES "^(x86_64|aarch64)$")
|
elseif (CMAKE_SYSTEM_NAME STREQUAL "Linux" AND CMAKE_SYSTEM_PROCESSOR MATCHES "^(x86_64|aarch64)$")
|
||||||
set(_has_package TRUE)
|
set(_has_package TRUE)
|
||||||
elseif (APPLE AND (CMAKE_SYSTEM_PROCESSOR MATCHES "^(arm64|x86_64)$" OR CMAKE_OSX_ARCHITECTURES MATCHES "^(arm64|x86_64)$"))
|
elseif (APPLE AND CMAKE_SYSTEM_PROCESSOR MATCHES "^(arm64|x86_64)$")
|
||||||
set(_has_package TRUE)
|
set(_has_package TRUE)
|
||||||
endif ()
|
endif ()
|
||||||
|
|
||||||
@@ -101,7 +101,7 @@ if (_aurora_dawn_provider STREQUAL "vendor")
|
|||||||
include(FetchContent)
|
include(FetchContent)
|
||||||
FetchContent_Declare(dawn
|
FetchContent_Declare(dawn
|
||||||
URL "https://github.com/google/dawn/archive/refs/tags/${AURORA_DAWN_VERSION}.tar.gz"
|
URL "https://github.com/google/dawn/archive/refs/tags/${AURORA_DAWN_VERSION}.tar.gz"
|
||||||
DOWNLOAD_EXTRACT_TIMESTAMP FALSE
|
DOWNLOAD_EXTRACT_TIMESTAMP TRUE
|
||||||
EXCLUDE_FROM_ALL
|
EXCLUDE_FROM_ALL
|
||||||
)
|
)
|
||||||
FetchContent_MakeAvailable(dawn)
|
FetchContent_MakeAvailable(dawn)
|
||||||
@@ -143,10 +143,6 @@ elseif (_aurora_dawn_provider STREQUAL "package")
|
|||||||
if (NOT AURORA_DAWN_PACKAGE_URL)
|
if (NOT AURORA_DAWN_PACKAGE_URL)
|
||||||
string(TOLOWER "${CMAKE_SYSTEM_NAME}" _dawn_system)
|
string(TOLOWER "${CMAKE_SYSTEM_NAME}" _dawn_system)
|
||||||
string(TOLOWER "${CMAKE_SYSTEM_PROCESSOR}" _dawn_arch)
|
string(TOLOWER "${CMAKE_SYSTEM_PROCESSOR}" _dawn_arch)
|
||||||
if (APPLE AND CMAKE_OSX_ARCHITECTURES)
|
|
||||||
list(GET CMAKE_OSX_ARCHITECTURES 0 _dawn_osx_arch)
|
|
||||||
string(TOLOWER "${_dawn_osx_arch}" _dawn_arch)
|
|
||||||
endif ()
|
|
||||||
if (_dawn_system STREQUAL "windows")
|
if (_dawn_system STREQUAL "windows")
|
||||||
if (_dawn_arch STREQUAL "x86_64")
|
if (_dawn_arch STREQUAL "x86_64")
|
||||||
set(_dawn_arch "amd64")
|
set(_dawn_arch "amd64")
|
||||||
@@ -155,36 +151,15 @@ elseif (_aurora_dawn_provider STREQUAL "package")
|
|||||||
endif ()
|
endif ()
|
||||||
endif ()
|
endif ()
|
||||||
set(AURORA_DAWN_PACKAGE_URL
|
set(AURORA_DAWN_PACKAGE_URL
|
||||||
"https://github.com/theofficialgman/dawn-build/releases/download/${AURORA_DAWN_VERSION}/dawn-${_dawn_system}-${_dawn_arch}.tar.gz")
|
"https://github.com/encounter/dawn-build/releases/download/${AURORA_DAWN_VERSION}/dawn-${_dawn_system}-${_dawn_arch}.tar.gz")
|
||||||
|
|
||||||
# A release asset is mutable: the same tag has already served two different windows-amd64 archives,
|
# A release asset is mutable: the same tag has already served two different windows-amd64 archives,
|
||||||
# and a cached extraction is never re-verified. Pin the digest for the combinations we ship.
|
# and a cached extraction is never re-verified. Pin the digest for the combinations we ship.
|
||||||
if (NOT AURORA_DAWN_PACKAGE_URL_HASH AND AURORA_DAWN_VERSION STREQUAL "v20260603.191052")
|
if (NOT AURORA_DAWN_PACKAGE_URL_HASH
|
||||||
if (_dawn_system STREQUAL "windows" AND _dawn_arch STREQUAL "amd64")
|
AND AURORA_DAWN_VERSION STREQUAL "v20260603.191052"
|
||||||
set(AURORA_DAWN_PACKAGE_URL_HASH
|
AND _dawn_system STREQUAL "windows" AND _dawn_arch STREQUAL "amd64")
|
||||||
"SHA256=13be9cff8b9b179c42dcd16aeabb6effcc8f0dfdcc14463eda2a5caeda225142")
|
set(AURORA_DAWN_PACKAGE_URL_HASH
|
||||||
elseif (_dawn_system STREQUAL "windows" AND _dawn_arch STREQUAL "arm64")
|
"SHA256=7785373d569b3b0237918ec9c523239f7d0667857c5ea8242e3cdfde95e6aeab")
|
||||||
set(AURORA_DAWN_PACKAGE_URL_HASH
|
|
||||||
"SHA256=bf2d921110f14a1d6553f673c5597988e66c02af5587e4a1fee167937d247734")
|
|
||||||
elseif (_dawn_system STREQUAL "linux" AND _dawn_arch STREQUAL "x86_64")
|
|
||||||
set(AURORA_DAWN_PACKAGE_URL_HASH
|
|
||||||
"SHA256=7adcf241bb2a24ec0c576609f2d67203e0e65db9c5a286ca2bbb6281fa644b35")
|
|
||||||
elseif (_dawn_system STREQUAL "linux" AND _dawn_arch STREQUAL "aarch64")
|
|
||||||
set(AURORA_DAWN_PACKAGE_URL_HASH
|
|
||||||
"SHA256=2415e253d46f91b2d72fc73bf6055fb31b98b67c773dc546e1991b1cf019732f")
|
|
||||||
elseif (_dawn_system STREQUAL "darwin" AND _dawn_arch STREQUAL "arm64")
|
|
||||||
set(AURORA_DAWN_PACKAGE_URL_HASH
|
|
||||||
"SHA256=0a8ea8eb0159fc0ba1083c52155d9376fb173cffe690b400464a6ad8881bb461")
|
|
||||||
elseif (_dawn_system STREQUAL "darwin" AND _dawn_arch STREQUAL "x86_64")
|
|
||||||
set(AURORA_DAWN_PACKAGE_URL_HASH
|
|
||||||
"SHA256=5fe2c7a2a8b4cb82acee4af16779a83ae333c7657b9dc1a5008f5fd1f5ad5f80")
|
|
||||||
elseif (_dawn_system STREQUAL "ios" AND _dawn_arch STREQUAL "arm64")
|
|
||||||
set(AURORA_DAWN_PACKAGE_URL_HASH
|
|
||||||
"SHA256=f97701d26fd1f25bbcc260b4c31736ede134c730c12556029e2470fde967f424")
|
|
||||||
elseif (_dawn_system STREQUAL "android" AND _dawn_arch STREQUAL "aarch64")
|
|
||||||
set(AURORA_DAWN_PACKAGE_URL_HASH
|
|
||||||
"SHA256=0e63e8cbf53551f703f582d1306f4257c0380353f66b53369d96952ce6d9f934")
|
|
||||||
endif ()
|
|
||||||
endif ()
|
endif ()
|
||||||
endif ()
|
endif ()
|
||||||
message(STATUS "aurora: Fetching prebuilt Dawn package from ${AURORA_DAWN_PACKAGE_URL}")
|
message(STATUS "aurora: Fetching prebuilt Dawn package from ${AURORA_DAWN_PACKAGE_URL}")
|
||||||
@@ -198,7 +173,7 @@ elseif (_aurora_dawn_provider STREQUAL "package")
|
|||||||
FetchContent_Declare(dawn_prebuilt
|
FetchContent_Declare(dawn_prebuilt
|
||||||
URL "${AURORA_DAWN_PACKAGE_URL}"
|
URL "${AURORA_DAWN_PACKAGE_URL}"
|
||||||
${_dawn_prebuilt_hash_argument}
|
${_dawn_prebuilt_hash_argument}
|
||||||
DOWNLOAD_EXTRACT_TIMESTAMP FALSE
|
DOWNLOAD_EXTRACT_TIMESTAMP TRUE
|
||||||
)
|
)
|
||||||
FetchContent_MakeAvailable(dawn_prebuilt)
|
FetchContent_MakeAvailable(dawn_prebuilt)
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
include(FetchContent)
|
include(FetchContent)
|
||||||
FetchContent_Declare(libusb
|
FetchContent_Declare(libusb
|
||||||
URL "https://github.com/libusb/libusb/releases/download/v${AURORA_LIBUSB_VERSION}/libusb-${AURORA_LIBUSB_VERSION}.tar.bz2"
|
URL "https://github.com/libusb/libusb/releases/download/v${AURORA_LIBUSB_VERSION}/libusb-${AURORA_LIBUSB_VERSION}.tar.bz2"
|
||||||
DOWNLOAD_EXTRACT_TIMESTAMP FALSE
|
DOWNLOAD_EXTRACT_TIMESTAMP TRUE
|
||||||
)
|
)
|
||||||
# Upstream ships no CMakeLists.txt, so this only populates the source tree.
|
# Upstream ships no CMakeLists.txt, so this only populates the source tree.
|
||||||
FetchContent_MakeAvailable(libusb)
|
FetchContent_MakeAvailable(libusb)
|
||||||
|
|||||||
@@ -120,7 +120,7 @@ elseif (_aurora_nod_provider STREQUAL "package")
|
|||||||
include(FetchContent)
|
include(FetchContent)
|
||||||
FetchContent_Declare(nod_prebuilt
|
FetchContent_Declare(nod_prebuilt
|
||||||
URL "${AURORA_NOD_PACKAGE_URL}"
|
URL "${AURORA_NOD_PACKAGE_URL}"
|
||||||
DOWNLOAD_EXTRACT_TIMESTAMP FALSE
|
DOWNLOAD_EXTRACT_TIMESTAMP TRUE
|
||||||
EXCLUDE_FROM_ALL
|
EXCLUDE_FROM_ALL
|
||||||
)
|
)
|
||||||
FetchContent_MakeAvailable(nod_prebuilt)
|
FetchContent_MakeAvailable(nod_prebuilt)
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ elseif (_aurora_sdl3_provider STREQUAL "package")
|
|||||||
include(FetchContent)
|
include(FetchContent)
|
||||||
FetchContent_Declare(sdl3_prebuilt
|
FetchContent_Declare(sdl3_prebuilt
|
||||||
URL "${AURORA_SDL3_PACKAGE_URL}"
|
URL "${AURORA_SDL3_PACKAGE_URL}"
|
||||||
DOWNLOAD_EXTRACT_TIMESTAMP FALSE
|
DOWNLOAD_EXTRACT_TIMESTAMP TRUE
|
||||||
)
|
)
|
||||||
FetchContent_MakeAvailable(sdl3_prebuilt)
|
FetchContent_MakeAvailable(sdl3_prebuilt)
|
||||||
|
|
||||||
@@ -148,7 +148,7 @@ elseif (_aurora_sdl3_provider STREQUAL "vendor")
|
|||||||
endif ()
|
endif ()
|
||||||
FetchContent_Declare(SDL
|
FetchContent_Declare(SDL
|
||||||
URL "https://github.com/libsdl-org/SDL/releases/download/release-${AURORA_SDL3_VERSION}/SDL3-${AURORA_SDL3_VERSION}.tar.gz"
|
URL "https://github.com/libsdl-org/SDL/releases/download/release-${AURORA_SDL3_VERSION}/SDL3-${AURORA_SDL3_VERSION}.tar.gz"
|
||||||
DOWNLOAD_EXTRACT_TIMESTAMP FALSE
|
DOWNLOAD_EXTRACT_TIMESTAMP TRUE
|
||||||
PATCH_COMMAND "${CMAKE_COMMAND}" -DSDL_SOURCE_DIR=<SOURCE_DIR> -P "${_aurora_sdl3_patches}"
|
PATCH_COMMAND "${CMAKE_COMMAND}" -DSDL_SOURCE_DIR=<SOURCE_DIR> -P "${_aurora_sdl3_patches}"
|
||||||
EXCLUDE_FROM_ALL
|
EXCLUDE_FROM_ALL
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -34,17 +34,6 @@ if (AURORA_ENABLE_GX)
|
|||||||
target_compile_definitions(aurora_core PUBLIC AURORA_ENABLE_GX WEBGPU_DAWN)
|
target_compile_definitions(aurora_core PUBLIC AURORA_ENABLE_GX WEBGPU_DAWN)
|
||||||
target_sources(aurora_core PRIVATE lib/webgpu/gpu.cpp lib/webgpu/gpu_cache.cpp lib/dawn/BackendBinding.cpp)
|
target_sources(aurora_core PRIVATE lib/webgpu/gpu.cpp lib/webgpu/gpu_cache.cpp lib/dawn/BackendBinding.cpp)
|
||||||
target_link_libraries(aurora_core PRIVATE dawn::webgpu_dawn)
|
target_link_libraries(aurora_core PRIVATE dawn::webgpu_dawn)
|
||||||
if (APPLE AND DAWN_ENABLE_METAL)
|
|
||||||
find_library(AURORA_METALFX_FRAMEWORK MetalFX)
|
|
||||||
endif ()
|
|
||||||
if (APPLE AND DAWN_ENABLE_METAL AND AURORA_METALFX_FRAMEWORK)
|
|
||||||
target_sources(aurora_core PRIVATE lib/webgpu/metalfx.mm)
|
|
||||||
set_source_files_properties(lib/webgpu/metalfx.mm PROPERTIES COMPILE_FLAGS -fobjc-arc)
|
|
||||||
target_link_options(aurora_core PUBLIC "LINKER:-weak_framework,MetalFX")
|
|
||||||
target_link_libraries(aurora_core PRIVATE "-framework IOSurface")
|
|
||||||
else ()
|
|
||||||
target_sources(aurora_core PRIVATE lib/webgpu/metalfx_stub.cpp)
|
|
||||||
endif ()
|
|
||||||
if (DAWN_ENABLE_VULKAN)
|
if (DAWN_ENABLE_VULKAN)
|
||||||
target_compile_definitions(aurora_core PRIVATE DAWN_ENABLE_BACKEND_VULKAN)
|
target_compile_definitions(aurora_core PRIVATE DAWN_ENABLE_BACKEND_VULKAN)
|
||||||
endif ()
|
endif ()
|
||||||
|
|||||||
Vendored
+13
-13
@@ -88,7 +88,7 @@ else ()
|
|||||||
set(ABSL_PROPAGATE_CXX_STD ON)
|
set(ABSL_PROPAGATE_CXX_STD ON)
|
||||||
FetchContent_Declare(abseil-cpp
|
FetchContent_Declare(abseil-cpp
|
||||||
URL https://github.com/abseil/abseil-cpp/archive/refs/tags/20240722.0.tar.gz
|
URL https://github.com/abseil/abseil-cpp/archive/refs/tags/20240722.0.tar.gz
|
||||||
DOWNLOAD_EXTRACT_TIMESTAMP FALSE
|
DOWNLOAD_EXTRACT_TIMESTAMP TRUE
|
||||||
EXCLUDE_FROM_ALL
|
EXCLUDE_FROM_ALL
|
||||||
)
|
)
|
||||||
FetchContent_MakeAvailable(abseil-cpp)
|
FetchContent_MakeAvailable(abseil-cpp)
|
||||||
@@ -102,10 +102,10 @@ include(${CMAKE_CURRENT_SOURCE_DIR}/../cmake/AuroraSDL3Provider.cmake)
|
|||||||
if (NOT TARGET xxhash)
|
if (NOT TARGET xxhash)
|
||||||
message(STATUS "aurora: Fetching xxhash")
|
message(STATUS "aurora: Fetching xxhash")
|
||||||
FetchContent_Declare(xxhash
|
FetchContent_Declare(xxhash
|
||||||
URL https://github.com/Cyan4973/xxHash/archive/refs/tags/v0.8.4.tar.gz
|
URL https://github.com/Cyan4973/xxHash/archive/refs/tags/v0.8.3.tar.gz
|
||||||
URL_HASH SHA256=5738270935e7c3d38a79b3adf7c9692566ce7895a25f67de43ad52ab504acd32
|
URL_HASH SHA256=aae608dfe8213dfd05d909a57718ef82f30722c392344583d3f39050c7f29a80
|
||||||
SOURCE_SUBDIR build/cmake
|
SOURCE_SUBDIR cmake_unofficial
|
||||||
DOWNLOAD_EXTRACT_TIMESTAMP FALSE
|
DOWNLOAD_EXTRACT_TIMESTAMP TRUE
|
||||||
EXCLUDE_FROM_ALL
|
EXCLUDE_FROM_ALL
|
||||||
)
|
)
|
||||||
set(XXHASH_BUILD_XXHSUM OFF CACHE INTERNAL "Build the xxhsum binary")
|
set(XXHASH_BUILD_XXHSUM OFF CACHE INTERNAL "Build the xxhsum binary")
|
||||||
@@ -128,7 +128,7 @@ if (NOT TARGET fmt)
|
|||||||
FetchContent_Declare(fmt
|
FetchContent_Declare(fmt
|
||||||
URL https://github.com/fmtlib/fmt/archive/refs/tags/11.1.4.tar.gz
|
URL https://github.com/fmtlib/fmt/archive/refs/tags/11.1.4.tar.gz
|
||||||
URL_HASH SHA256=ac366b7b4c2e9f0dde63a59b3feb5ee59b67974b14ee5dc9ea8ad78aa2c1ee1e
|
URL_HASH SHA256=ac366b7b4c2e9f0dde63a59b3feb5ee59b67974b14ee5dc9ea8ad78aa2c1ee1e
|
||||||
DOWNLOAD_EXTRACT_TIMESTAMP FALSE
|
DOWNLOAD_EXTRACT_TIMESTAMP TRUE
|
||||||
EXCLUDE_FROM_ALL
|
EXCLUDE_FROM_ALL
|
||||||
)
|
)
|
||||||
FetchContent_MakeAvailable(fmt)
|
FetchContent_MakeAvailable(fmt)
|
||||||
@@ -166,7 +166,7 @@ if (AURORA_ENABLE_GX)
|
|||||||
ZLIB
|
ZLIB
|
||||||
URL https://github.com/madler/zlib/releases/download/v1.3.2/zlib-1.3.2.tar.gz
|
URL https://github.com/madler/zlib/releases/download/v1.3.2/zlib-1.3.2.tar.gz
|
||||||
URL_HASH SHA256=bb329a0a2cd0274d05519d61c667c062e06990d72e125ee2dfa8de64f0119d16
|
URL_HASH SHA256=bb329a0a2cd0274d05519d61c667c062e06990d72e125ee2dfa8de64f0119d16
|
||||||
DOWNLOAD_EXTRACT_TIMESTAMP FALSE
|
DOWNLOAD_EXTRACT_TIMESTAMP TRUE
|
||||||
EXCLUDE_FROM_ALL
|
EXCLUDE_FROM_ALL
|
||||||
)
|
)
|
||||||
FetchContent_MakeAvailable(ZLIB)
|
FetchContent_MakeAvailable(ZLIB)
|
||||||
@@ -202,7 +202,7 @@ if (AURORA_ENABLE_GX)
|
|||||||
PNG
|
PNG
|
||||||
URL https://github.com/pnggroup/libpng/archive/refs/tags/v1.6.58.tar.gz
|
URL https://github.com/pnggroup/libpng/archive/refs/tags/v1.6.58.tar.gz
|
||||||
URL_HASH SHA256=A9D4DF463D36A6E5F9C29BD6F4967312D17E996C1854F3511F833924EB1993CF
|
URL_HASH SHA256=A9D4DF463D36A6E5F9C29BD6F4967312D17E996C1854F3511F833924EB1993CF
|
||||||
DOWNLOAD_EXTRACT_TIMESTAMP FALSE
|
DOWNLOAD_EXTRACT_TIMESTAMP TRUE
|
||||||
EXCLUDE_FROM_ALL
|
EXCLUDE_FROM_ALL
|
||||||
)
|
)
|
||||||
FetchContent_MakeAvailable(PNG)
|
FetchContent_MakeAvailable(PNG)
|
||||||
@@ -226,7 +226,7 @@ if (AURORA_ENABLE_GX)
|
|||||||
FetchContent_Declare(Freetype
|
FetchContent_Declare(Freetype
|
||||||
URL https://files.twilitrealm.dev/freetype-2.14.3.tar.gz
|
URL https://files.twilitrealm.dev/freetype-2.14.3.tar.gz
|
||||||
URL_HASH SHA256=e61b31ab26358b946e767ed7eb7f4bb2e507da1cfefeb7a8861ace7fd5c899a1
|
URL_HASH SHA256=e61b31ab26358b946e767ed7eb7f4bb2e507da1cfefeb7a8861ace7fd5c899a1
|
||||||
DOWNLOAD_EXTRACT_TIMESTAMP FALSE
|
DOWNLOAD_EXTRACT_TIMESTAMP TRUE
|
||||||
EXCLUDE_FROM_ALL
|
EXCLUDE_FROM_ALL
|
||||||
)
|
)
|
||||||
FetchContent_MakeAvailable(Freetype)
|
FetchContent_MakeAvailable(Freetype)
|
||||||
@@ -240,7 +240,7 @@ if (AURORA_ENABLE_GX)
|
|||||||
FetchContent_Declare(imgui
|
FetchContent_Declare(imgui
|
||||||
URL https://github.com/ocornut/imgui/archive/refs/tags/v1.91.9b-docking.tar.gz
|
URL https://github.com/ocornut/imgui/archive/refs/tags/v1.91.9b-docking.tar.gz
|
||||||
URL_HASH SHA256=466fdef9b18de15f0bb6e288e3d00ffa3d82200ec458ce5e4f724a161d9528a5
|
URL_HASH SHA256=466fdef9b18de15f0bb6e288e3d00ffa3d82200ec458ce5e4f724a161d9528a5
|
||||||
DOWNLOAD_EXTRACT_TIMESTAMP FALSE
|
DOWNLOAD_EXTRACT_TIMESTAMP TRUE
|
||||||
EXCLUDE_FROM_ALL
|
EXCLUDE_FROM_ALL
|
||||||
)
|
)
|
||||||
FetchContent_MakeAvailable(imgui)
|
FetchContent_MakeAvailable(imgui)
|
||||||
@@ -285,7 +285,7 @@ if (AURORA_ENABLE_GX)
|
|||||||
FetchContent_Declare(sqlite3
|
FetchContent_Declare(sqlite3
|
||||||
URL https://sqlite.org/2026/sqlite-amalgamation-3510300.zip
|
URL https://sqlite.org/2026/sqlite-amalgamation-3510300.zip
|
||||||
URL_HASH SHA256=acb1e6f5d832484bf6d32b681e858c38add8b2acdfd42ac5df24b8afb46552b4
|
URL_HASH SHA256=acb1e6f5d832484bf6d32b681e858c38add8b2acdfd42ac5df24b8afb46552b4
|
||||||
DOWNLOAD_EXTRACT_TIMESTAMP FALSE
|
DOWNLOAD_EXTRACT_TIMESTAMP TRUE
|
||||||
EXCLUDE_FROM_ALL
|
EXCLUDE_FROM_ALL
|
||||||
)
|
)
|
||||||
FetchContent_MakeAvailable(sqlite3)
|
FetchContent_MakeAvailable(sqlite3)
|
||||||
@@ -328,7 +328,7 @@ if (AURORA_ENABLE_GX)
|
|||||||
FetchContent_Declare(zstd
|
FetchContent_Declare(zstd
|
||||||
URL https://github.com/facebook/zstd/releases/download/v1.5.7/zstd-1.5.7.tar.gz
|
URL https://github.com/facebook/zstd/releases/download/v1.5.7/zstd-1.5.7.tar.gz
|
||||||
URL_HASH SHA256=eb33e51f49a15e023950cd7825ca74a4a2b43db8354825ac24fc1b7ee09e6fa3
|
URL_HASH SHA256=eb33e51f49a15e023950cd7825ca74a4a2b43db8354825ac24fc1b7ee09e6fa3
|
||||||
DOWNLOAD_EXTRACT_TIMESTAMP FALSE
|
DOWNLOAD_EXTRACT_TIMESTAMP TRUE
|
||||||
SOURCE_SUBDIR build/cmake
|
SOURCE_SUBDIR build/cmake
|
||||||
EXCLUDE_FROM_ALL
|
EXCLUDE_FROM_ALL
|
||||||
)
|
)
|
||||||
@@ -349,7 +349,7 @@ if (NOT TARGET TracyClient)
|
|||||||
tracy
|
tracy
|
||||||
URL https://github.com/wolfpld/tracy/archive/a64b9a20294d59421a2f57aeca3c6383d8c48169.tar.gz
|
URL https://github.com/wolfpld/tracy/archive/a64b9a20294d59421a2f57aeca3c6383d8c48169.tar.gz
|
||||||
URL_HASH SHA256=24d342b5127d7f659dc3cf94f24347b348cc736f25b17a691fd7f69541937658
|
URL_HASH SHA256=24d342b5127d7f659dc3cf94f24347b348cc736f25b17a691fd7f69541937658
|
||||||
DOWNLOAD_EXTRACT_TIMESTAMP FALSE
|
DOWNLOAD_EXTRACT_TIMESTAMP TRUE
|
||||||
EXCLUDE_FROM_ALL
|
EXCLUDE_FROM_ALL
|
||||||
)
|
)
|
||||||
FetchContent_MakeAvailable(tracy)
|
FetchContent_MakeAvailable(tracy)
|
||||||
|
|||||||
@@ -127,20 +127,12 @@ typedef struct {
|
|||||||
const char* pipelineCachePath;
|
const char* pipelineCachePath;
|
||||||
} AuroraConfig;
|
} AuroraConfig;
|
||||||
|
|
||||||
typedef enum {
|
|
||||||
AURORA_INITIALIZATION_SUCCESS = 0,
|
|
||||||
AURORA_INITIALIZATION_GRAPHICS_UNAVAILABLE = 1,
|
|
||||||
} AuroraInitializationStatus;
|
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
AuroraBackend backend;
|
AuroraBackend backend;
|
||||||
const char* userPath;
|
const char* userPath;
|
||||||
const char* cachePath;
|
const char* cachePath;
|
||||||
SDL_Window* window;
|
SDL_Window* window;
|
||||||
AuroraWindowSize windowSize;
|
AuroraWindowSize windowSize;
|
||||||
AuroraInitializationStatus initializationStatus;
|
|
||||||
// On failure, owned by SDL on the calling thread. Copy before another SDL call.
|
|
||||||
const char* initializationError;
|
|
||||||
} AuroraInfo;
|
} AuroraInfo;
|
||||||
|
|
||||||
AuroraInfo aurora_initialize(int argc, char* argv[], const AuroraConfig* config);
|
AuroraInfo aurora_initialize(int argc, char* argv[], const AuroraConfig* config);
|
||||||
@@ -170,21 +162,6 @@ void aurora_set_background_input(bool value);
|
|||||||
void aurora_set_display_mode(AuroraDisplayMode mode);
|
void aurora_set_display_mode(AuroraDisplayMode mode);
|
||||||
AuroraDisplayMode aurora_get_display_mode();
|
AuroraDisplayMode aurora_get_display_mode();
|
||||||
|
|
||||||
typedef enum {
|
|
||||||
AURORA_METALFX_DISABLED,
|
|
||||||
AURORA_METALFX_UNSUPPORTED,
|
|
||||||
AURORA_METALFX_NOT_UPSCALING,
|
|
||||||
AURORA_METALFX_ACTIVE,
|
|
||||||
AURORA_METALFX_ERROR,
|
|
||||||
} AuroraMetalFXStatus;
|
|
||||||
|
|
||||||
// Changes are consumed at the next sealed frame boundary. MetalFX only applies
|
|
||||||
// when both source dimensions are smaller than the aspect-fitted output.
|
|
||||||
void aurora_set_metalfx_spatial(bool enabled);
|
|
||||||
bool aurora_get_metalfx_spatial();
|
|
||||||
bool aurora_is_metalfx_spatial_supported();
|
|
||||||
AuroraMetalFXStatus aurora_get_metalfx_status();
|
|
||||||
|
|
||||||
AuroraBackend aurora_get_backend();
|
AuroraBackend aurora_get_backend();
|
||||||
const AuroraBackend* aurora_get_available_backends(size_t* count);
|
const AuroraBackend* aurora_get_available_backends(size_t* count);
|
||||||
|
|
||||||
|
|||||||
@@ -88,7 +88,6 @@ typedef enum _AuroraViewportPolicy {
|
|||||||
AURORA_VIEWPORT_FIT = 0, // Preserve logical aspect in the content framebuffer
|
AURORA_VIEWPORT_FIT = 0, // Preserve logical aspect in the content framebuffer
|
||||||
AURORA_VIEWPORT_STRETCH = 1, // Match content framebuffer aspect to the native surface
|
AURORA_VIEWPORT_STRETCH = 1, // Match content framebuffer aspect to the native surface
|
||||||
AURORA_VIEWPORT_NATIVE = 2, // Use active framebuffer pixels directly
|
AURORA_VIEWPORT_NATIVE = 2, // Use active framebuffer pixels directly
|
||||||
AURORA_VIEWPORT_16_9 = 3, // Fixed 16:9 content with bars on other surfaces
|
|
||||||
} AuroraViewportPolicy;
|
} AuroraViewportPolicy;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -171,22 +171,6 @@ typedef struct PADButtonMapping {
|
|||||||
PADButton padButton;
|
PADButton padButton;
|
||||||
} PADButtonMapping;
|
} PADButtonMapping;
|
||||||
|
|
||||||
// Explicitly disabled, unlike INVALID which permits default L/R trigger input.
|
|
||||||
#define PAD_NATIVE_BUTTON_DISABLED 0xfffffffeu
|
|
||||||
|
|
||||||
// Axis-to-button bindings share the persisted nativeButton field without
|
|
||||||
// changing the binary layout of existing controller mapping files.
|
|
||||||
constexpr u32 PADEncodeAxisButton(u32 axis, bool negative, u32 threshold = 50) {
|
|
||||||
return 0x10000u | axis | (negative ? 0x80u : 0u) | (threshold << 8);
|
|
||||||
}
|
|
||||||
constexpr bool PADIsAxisButton(u32 binding) { return (binding & 0xffff0000u) == 0x10000u; }
|
|
||||||
constexpr u32 PADAxisButtonThreshold(u32 binding) { return (binding >> 8) & 0xffu; }
|
|
||||||
constexpr u32 PADAxisButtonAxis(u32 binding) { return binding & 0x7fu; }
|
|
||||||
constexpr bool PADAxisButtonNegative(u32 binding) { return (binding & 0x80u) != 0; }
|
|
||||||
constexpr u32 PADAxisButtonIdentity(u32 binding) {
|
|
||||||
return PADIsAxisButton(binding) ? (binding & ~0xff00u) : binding;
|
|
||||||
}
|
|
||||||
|
|
||||||
typedef struct PADAxisMapping {
|
typedef struct PADAxisMapping {
|
||||||
PADSignedNativeAxis nativeAxis;
|
PADSignedNativeAxis nativeAxis;
|
||||||
s32 nativeButton;
|
s32 nativeButton;
|
||||||
|
|||||||
+26
-181
@@ -7,7 +7,6 @@
|
|||||||
#include "gx/shader_info.hpp"
|
#include "gx/shader_info.hpp"
|
||||||
#include "imgui.hpp"
|
#include "imgui.hpp"
|
||||||
#include "webgpu/gpu.hpp"
|
#include "webgpu/gpu.hpp"
|
||||||
#include "webgpu/metalfx.hpp"
|
|
||||||
#include <webgpu/webgpu_cpp.h>
|
#include <webgpu/webgpu_cpp.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -61,9 +60,6 @@ std::atomic<AuroraFrameWorkerWaitCallback> g_frameWorkerWaitCallback{nullptr};
|
|||||||
// deadlines derived from it, so the presenter cannot drift. Zero means present when ready.
|
// deadlines derived from it, so the presenter cannot drift. Zero means present when ready.
|
||||||
std::atomic<uint64_t> g_presentScheduleBaseNanos{0};
|
std::atomic<uint64_t> g_presentScheduleBaseNanos{0};
|
||||||
std::atomic<uint64_t> g_presentScheduleIntervalNanos{0};
|
std::atomic<uint64_t> g_presentScheduleIntervalNanos{0};
|
||||||
std::atomic<bool> g_metalfxRequested{false};
|
|
||||||
std::atomic<bool> g_metalfxSupported{false};
|
|
||||||
std::atomic<AuroraMetalFXStatus> g_metalfxStatus{AURORA_METALFX_DISABLED};
|
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
Module Log("aurora");
|
Module Log("aurora");
|
||||||
@@ -229,7 +225,7 @@ enum class ImGuiFramePolicy {
|
|||||||
bool begin_frame_impl(bool pumpEvents, ImGuiFramePolicy imguiPolicy = ImGuiFramePolicy::Immediate,
|
bool begin_frame_impl(bool pumpEvents, ImGuiFramePolicy imguiPolicy = ImGuiFramePolicy::Immediate,
|
||||||
bool* imguiNewFrameOwed = nullptr) noexcept;
|
bool* imguiNewFrameOwed = nullptr) noexcept;
|
||||||
bool begin_frame_render_state_impl(ImGuiFramePolicy imguiPolicy, bool* imguiNewFrameOwed) noexcept;
|
bool begin_frame_render_state_impl(ImGuiFramePolicy imguiPolicy, bool* imguiNewFrameOwed) noexcept;
|
||||||
void end_frame_impl(bool pumpEvents, bool drainFifo);
|
void end_frame_impl(bool pumpEvents, bool drainFifo) noexcept;
|
||||||
|
|
||||||
// The two publication points of a frame-worker cycle, cleared together under `mutex`. Sealed:
|
// The two publication points of a frame-worker cycle, cleared together under `mutex`. Sealed:
|
||||||
// producer-shared renderer state is free again. Done: slots encoded, presented, ImGui restarted.
|
// producer-shared renderer state is free again. Done: slots encoded, presented, ImGui restarted.
|
||||||
@@ -693,23 +689,15 @@ AuroraInfo initialize(int argc, char* argv[], const AuroraConfig& config) noexce
|
|||||||
const AuroraBackend requestedBackend = config.desiredBackend;
|
const AuroraBackend requestedBackend = config.desiredBackend;
|
||||||
AuroraBackend selectedBackend = requestedBackend;
|
AuroraBackend selectedBackend = requestedBackend;
|
||||||
bool windowCreated = false;
|
bool windowCreated = false;
|
||||||
std::string firstGraphicsError;
|
|
||||||
const auto rememberGraphicsError = [&] {
|
|
||||||
if (firstGraphicsError.empty() && SDL_GetError()[0] != '\0') {
|
|
||||||
firstGraphicsError = SDL_GetError();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
if (selectedBackend != BACKEND_AUTO) {
|
if (selectedBackend != BACKEND_AUTO) {
|
||||||
Log.info("Requested graphics backend: {}", backend_name(selectedBackend));
|
Log.info("Requested graphics backend: {}", backend_name(selectedBackend));
|
||||||
if (window::create_window(selectedBackend)) {
|
if (window::create_window(selectedBackend)) {
|
||||||
if (webgpu::initialize(selectedBackend)) {
|
if (webgpu::initialize(selectedBackend)) {
|
||||||
windowCreated = true;
|
windowCreated = true;
|
||||||
} else {
|
} else {
|
||||||
rememberGraphicsError();
|
|
||||||
window::destroy_window();
|
window::destroy_window();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
rememberGraphicsError();
|
|
||||||
Log.error("Failed to create a window for backend {}: {}", backend_name(selectedBackend),
|
Log.error("Failed to create a window for backend {}: {}", backend_name(selectedBackend),
|
||||||
SDL_GetError());
|
SDL_GetError());
|
||||||
}
|
}
|
||||||
@@ -726,28 +714,18 @@ AuroraInfo initialize(int argc, char* argv[], const AuroraConfig& config) noexce
|
|||||||
for (const auto backendType : PreferredBackendOrder) {
|
for (const auto backendType : PreferredBackendOrder) {
|
||||||
selectedBackend = backendType;
|
selectedBackend = backendType;
|
||||||
if (!window::create_window(selectedBackend)) {
|
if (!window::create_window(selectedBackend)) {
|
||||||
rememberGraphicsError();
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (webgpu::initialize(selectedBackend)) {
|
if (webgpu::initialize(selectedBackend)) {
|
||||||
windowCreated = true;
|
windowCreated = true;
|
||||||
break;
|
break;
|
||||||
} else {
|
} else {
|
||||||
rememberGraphicsError();
|
|
||||||
window::destroy_window();
|
window::destroy_window();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!windowCreated) {
|
ASSERT(windowCreated, "Error creating window: {}", SDL_GetError());
|
||||||
if (firstGraphicsError.empty()) firstGraphicsError = "No supported graphics backend is available";
|
|
||||||
SDL_SetError("%s", firstGraphicsError.c_str());
|
|
||||||
Log.error("Graphics initialization failed: {}", firstGraphicsError);
|
|
||||||
return {
|
|
||||||
.initializationStatus = AURORA_INITIALIZATION_GRAPHICS_UNAVAILABLE,
|
|
||||||
.initializationError = SDL_GetError(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
if (requestedBackend != BACKEND_AUTO && selectedBackend != requestedBackend) {
|
if (requestedBackend != BACKEND_AUTO && selectedBackend != requestedBackend) {
|
||||||
Log.error("Graphics backend fallback in effect: video.graphics_api requested {}, "
|
Log.error("Graphics backend fallback in effect: video.graphics_api requested {}, "
|
||||||
"running on {}",
|
"running on {}",
|
||||||
@@ -769,9 +747,6 @@ AuroraInfo initialize(int argc, char* argv[], const AuroraConfig& config) noexce
|
|||||||
#ifdef AURORA_ENABLE_GX
|
#ifdef AURORA_ENABLE_GX
|
||||||
gfx::initialize();
|
gfx::initialize();
|
||||||
|
|
||||||
g_metalfxSupported.store(webgpu::metalfx::supported(g_device, webgpu::g_backendType));
|
|
||||||
g_metalfxStatus.store(AURORA_METALFX_DISABLED);
|
|
||||||
|
|
||||||
imgui::create_context();
|
imgui::create_context();
|
||||||
#endif
|
#endif
|
||||||
const auto size = window::get_window_size();
|
const auto size = window::get_window_size();
|
||||||
@@ -1194,114 +1169,12 @@ void stop_presenter() noexcept {
|
|||||||
g_presenterStarted.store(false, std::memory_order_release);
|
g_presenterStarted.store(false, std::memory_order_release);
|
||||||
}
|
}
|
||||||
|
|
||||||
struct MetalFXSlot {
|
|
||||||
webgpu::metalfx::Size size{};
|
|
||||||
std::unique_ptr<webgpu::metalfx::SpatialScaler> scaler;
|
|
||||||
wgpu::BindGroup bindGroup;
|
|
||||||
};
|
|
||||||
std::array<MetalFXSlot, gx::MaxInterpolatedFrames + 1> g_metalfxSlots;
|
|
||||||
size_t g_metalfxNextSlot = 0;
|
|
||||||
webgpu::metalfx::SpatialScaler* g_metalfxPendingOutput = nullptr;
|
|
||||||
bool g_metalfxFailed = false;
|
|
||||||
|
|
||||||
void metalfx_failed(const std::string& reason) {
|
|
||||||
Log.warn("MetalFX spatial upscaling disabled: {}; using normal presentation", reason);
|
|
||||||
g_metalfxFailed = true;
|
|
||||||
g_metalfxStatus.store(AURORA_METALFX_ERROR);
|
|
||||||
g_metalfxPendingOutput = nullptr;
|
|
||||||
g_metalfxSlots = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
wgpu::BindGroup upscale_presentation(wgpu::CommandEncoder& encoder,
|
|
||||||
const webgpu::PresentSource& source,
|
|
||||||
const webgpu::Viewport& viewport, bool enabled) {
|
|
||||||
if (!enabled) {
|
|
||||||
g_metalfxSlots = {};
|
|
||||||
g_metalfxFailed = false;
|
|
||||||
g_metalfxStatus.store(AURORA_METALFX_DISABLED);
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
if (!g_metalfxSupported.load()) {
|
|
||||||
g_metalfxStatus.store(AURORA_METALFX_UNSUPPORTED);
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
if (g_metalfxFailed) return {};
|
|
||||||
const webgpu::metalfx::Size size{
|
|
||||||
source.size.width, source.size.height,
|
|
||||||
static_cast<uint32_t>(viewport.width), static_cast<uint32_t>(viewport.height),
|
|
||||||
webgpu::g_graphicsConfig.surfaceConfiguration.format,
|
|
||||||
};
|
|
||||||
// The existing copy path samples perceptual values from unorm game images.
|
|
||||||
// Do not introduce implicit sRGB decoding or downscaling into MetalFX.
|
|
||||||
if (!size.inputWidth || !size.inputHeight || size.inputWidth >= size.outputWidth ||
|
|
||||||
size.inputHeight >= size.outputHeight ||
|
|
||||||
(source.format != wgpu::TextureFormat::RGBA8Unorm && source.format != wgpu::TextureFormat::BGRA8Unorm)) {
|
|
||||||
g_metalfxStatus.store(AURORA_METALFX_NOT_UPSCALING);
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
auto& slot = g_metalfxSlots[g_metalfxNextSlot++ % g_metalfxSlots.size()];
|
|
||||||
if (!slot.scaler || !(slot.size == size)) {
|
|
||||||
slot = {};
|
|
||||||
std::string error;
|
|
||||||
slot.scaler = webgpu::metalfx::create(g_instance, g_device, size, error);
|
|
||||||
if (!slot.scaler) {
|
|
||||||
if (error.empty()) g_metalfxStatus.store(AURORA_METALFX_NOT_UPSCALING);
|
|
||||||
else metalfx_failed(error);
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
slot.size = size;
|
|
||||||
wgpu::SamplerDescriptor samplerDescriptor{};
|
|
||||||
samplerDescriptor.magFilter = wgpu::FilterMode::Linear;
|
|
||||||
samplerDescriptor.minFilter = wgpu::FilterMode::Linear;
|
|
||||||
slot.bindGroup = webgpu::create_copy_bind_group(slot.scaler->output_view(),
|
|
||||||
g_device.CreateSampler(&samplerDescriptor));
|
|
||||||
Log.info("MetalFX spatial slot: {}x{} -> {}x{}", size.inputWidth, size.inputHeight,
|
|
||||||
size.outputWidth, size.outputHeight);
|
|
||||||
}
|
|
||||||
if (!slot.scaler->begin_input()) {
|
|
||||||
metalfx_failed(slot.scaler->error());
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
const wgpu::RenderPassColorAttachment attachment{
|
|
||||||
.view = slot.scaler->input_view(),
|
|
||||||
.loadOp = wgpu::LoadOp::Clear,
|
|
||||||
.storeOp = wgpu::StoreOp::Store,
|
|
||||||
};
|
|
||||||
const wgpu::RenderPassDescriptor descriptor{
|
|
||||||
.label = "MetalFX input copy",
|
|
||||||
.colorAttachmentCount = 1,
|
|
||||||
.colorAttachments = &attachment,
|
|
||||||
};
|
|
||||||
auto pass = encoder.BeginRenderPass(&descriptor);
|
|
||||||
pass.SetPipeline(webgpu::g_CopyPipeline);
|
|
||||||
pass.SetBindGroup(0, source.bindGroup);
|
|
||||||
pass.SetViewport(0, 0, static_cast<float>(size.inputWidth), static_cast<float>(size.inputHeight), 0, 1);
|
|
||||||
pass.Draw(3);
|
|
||||||
pass.End();
|
|
||||||
// Submit the sealed scene and input copy before crossing to the native queue.
|
|
||||||
// The replacement encoder composites the upscaled image and ImGui normally.
|
|
||||||
auto buffer = encoder.Finish();
|
|
||||||
{
|
|
||||||
std::lock_guard submitLock(g_queueSubmitMutex);
|
|
||||||
g_queue.Submit(1, &buffer);
|
|
||||||
}
|
|
||||||
encoder = g_device.CreateCommandEncoder();
|
|
||||||
if (!slot.scaler->upscale()) {
|
|
||||||
metalfx_failed(slot.scaler->error());
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
g_metalfxPendingOutput = slot.scaler.get();
|
|
||||||
g_metalfxStatus.store(AURORA_METALFX_ACTIVE);
|
|
||||||
return slot.bindGroup;
|
|
||||||
}
|
|
||||||
|
|
||||||
// `presentSource` is latched in the seal prologue: by the time this encodes, the producer's next
|
// `presentSource` is latched in the seal prologue: by the time this encodes, the producer's next
|
||||||
// gfx::begin_frame() may already have cleared the display-copy override.
|
// gfx::begin_frame() may already have cleared the display-copy override.
|
||||||
wgpu::BindGroup encode_presentation_snapshot(wgpu::CommandEncoder& encoder,
|
void encode_presentation_snapshot(const wgpu::CommandEncoder& encoder,
|
||||||
const webgpu::PresentSource& presentSource,
|
const webgpu::PresentSource& presentSource,
|
||||||
const PresentationImage& image,
|
const PresentationImage& image,
|
||||||
bool includeImGui, bool metalfxEnabled,
|
bool includeImGui) {
|
||||||
const wgpu::BindGroup* cachedMetalFXOutput = nullptr) {
|
|
||||||
ZoneScoped;
|
ZoneScoped;
|
||||||
auto viewport = webgpu::calculate_present_viewport(
|
auto viewport = webgpu::calculate_present_viewport(
|
||||||
image.texture.size.width, image.texture.size.height, presentSource.size.width,
|
image.texture.size.width, image.texture.size.height, presentSource.size.width,
|
||||||
@@ -1312,13 +1185,6 @@ wgpu::BindGroup encode_presentation_snapshot(wgpu::CommandEncoder& encoder,
|
|||||||
image.texture.size.width, image.texture.size.height, presentAspect);
|
image.texture.size.width, image.texture.size.height, presentAspect);
|
||||||
}
|
}
|
||||||
wgpu::BindGroup presentBindGroup = presentSource.bindGroup;
|
wgpu::BindGroup presentBindGroup = presentSource.bindGroup;
|
||||||
wgpu::BindGroup newMetalFXOutput;
|
|
||||||
if (cachedMetalFXOutput && *cachedMetalFXOutput) {
|
|
||||||
presentBindGroup = *cachedMetalFXOutput;
|
|
||||||
} else if (auto upscaled = upscale_presentation(encoder, presentSource, viewport, metalfxEnabled)) {
|
|
||||||
presentBindGroup = std::move(upscaled);
|
|
||||||
newMetalFXOutput = presentBindGroup;
|
|
||||||
}
|
|
||||||
{
|
{
|
||||||
const std::array attachments{
|
const std::array attachments{
|
||||||
wgpu::RenderPassColorAttachment{
|
wgpu::RenderPassColorAttachment{
|
||||||
@@ -1359,7 +1225,6 @@ wgpu::BindGroup encode_presentation_snapshot(wgpu::CommandEncoder& encoder,
|
|||||||
imgui::render(pass);
|
imgui::render(pass);
|
||||||
pass.End();
|
pass.End();
|
||||||
}
|
}
|
||||||
return newMetalFXOutput;
|
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -1368,13 +1233,6 @@ void shutdown() noexcept {
|
|||||||
#ifdef AURORA_ENABLE_GX
|
#ifdef AURORA_ENABLE_GX
|
||||||
stop_presenter();
|
stop_presenter();
|
||||||
g_presentationImagePools = {};
|
g_presentationImagePools = {};
|
||||||
g_metalfxSlots = {};
|
|
||||||
g_metalfxPendingOutput = nullptr;
|
|
||||||
g_metalfxNextSlot = 0;
|
|
||||||
g_metalfxFailed = false;
|
|
||||||
g_metalfxRequested.store(false);
|
|
||||||
g_metalfxSupported.store(false);
|
|
||||||
g_metalfxStatus.store(AURORA_METALFX_DISABLED);
|
|
||||||
imgui::shutdown();
|
imgui::shutdown();
|
||||||
gfx::shutdown();
|
gfx::shutdown();
|
||||||
webgpu::shutdown();
|
webgpu::shutdown();
|
||||||
@@ -1487,7 +1345,6 @@ struct SealedFrameContext {
|
|||||||
uint32_t logicalFrame = 0;
|
uint32_t logicalFrame = 0;
|
||||||
bool interpolationActive = false;
|
bool interpolationActive = false;
|
||||||
bool replayInterpolatedFrames = false;
|
bool replayInterpolatedFrames = false;
|
||||||
bool metalfxEnabled = false;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Phase 1: everything that touches producer-shared renderer state. Needs g_rendererGpuMutex and
|
// Phase 1: everything that touches producer-shared renderer state. Needs g_rendererGpuMutex and
|
||||||
@@ -1515,7 +1372,6 @@ void seal_frame_locked(gfx::SealedFrame& sealedFrame, SealedFrameContext& ctx) {
|
|||||||
ctx.snapshotWidth = (std::max)(windowSize.native_fb_width, 1u);
|
ctx.snapshotWidth = (std::max)(windowSize.native_fb_width, 1u);
|
||||||
ctx.snapshotHeight = (std::max)(windowSize.native_fb_height, 1u);
|
ctx.snapshotHeight = (std::max)(windowSize.native_fb_height, 1u);
|
||||||
ctx.logicalFrame = gfx::current_frame();
|
ctx.logicalFrame = gfx::current_frame();
|
||||||
ctx.metalfxEnabled = g_metalfxRequested.load();
|
|
||||||
// Latched before webgpu::clear_present_source_override() in the producer's
|
// Latched before webgpu::clear_present_source_override() in the producer's
|
||||||
// next gfx::begin_frame().
|
// next gfx::begin_frame().
|
||||||
ctx.presentSource = webgpu::current_present_source();
|
ctx.presentSource = webgpu::current_present_source();
|
||||||
@@ -1563,14 +1419,10 @@ std::vector<PresentationJob> encode_sealed_frame(gfx::SealedFrame& sealedFrame,
|
|||||||
const wgpu::CommandBufferDescriptor cmdBufDescriptor{
|
const wgpu::CommandBufferDescriptor cmdBufDescriptor{
|
||||||
.label = "Presentation slot command buffer",
|
.label = "Presentation slot command buffer",
|
||||||
};
|
};
|
||||||
const auto submitEncodedSlot = [&](wgpu::CommandEncoder& target, bool releaseMetalFXOutput = true) {
|
const auto submitEncodedSlot = [&](wgpu::CommandEncoder& target) {
|
||||||
const auto buffer = target.Finish(&cmdBufDescriptor);
|
const auto buffer = target.Finish(&cmdBufDescriptor);
|
||||||
std::lock_guard submitLock(g_queueSubmitMutex);
|
std::lock_guard submitLock(g_queueSubmitMutex);
|
||||||
g_queue.Submit(1, &buffer);
|
g_queue.Submit(1, &buffer);
|
||||||
if (releaseMetalFXOutput && g_metalfxPendingOutput) {
|
|
||||||
if (!g_metalfxPendingOutput->end_output()) metalfx_failed(g_metalfxPendingOutput->error());
|
|
||||||
g_metalfxPendingOutput = nullptr;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if (ctx.replayInterpolatedFrames) {
|
if (ctx.replayInterpolatedFrames) {
|
||||||
@@ -1579,7 +1431,7 @@ std::vector<PresentationJob> encode_sealed_frame(gfx::SealedFrame& sealedFrame,
|
|||||||
gfx::render(sealedFrame, encoder, static_cast<int32_t>(interpolatedFrame), false);
|
gfx::render(sealedFrame, encoder, static_cast<int32_t>(interpolatedFrame), false);
|
||||||
auto image =
|
auto image =
|
||||||
acquire_presentation_image(interpolatedFrame, ctx.snapshotWidth, ctx.snapshotHeight);
|
acquire_presentation_image(interpolatedFrame, ctx.snapshotWidth, ctx.snapshotHeight);
|
||||||
encode_presentation_snapshot(encoder, ctx.presentSource, *image, true, ctx.metalfxEnabled);
|
encode_presentation_snapshot(encoder, ctx.presentSource, *image, true);
|
||||||
presentationJobs.push_back({
|
presentationJobs.push_back({
|
||||||
.image = std::move(image),
|
.image = std::move(image),
|
||||||
.logicalFrame = ctx.logicalFrame,
|
.logicalFrame = ctx.logicalFrame,
|
||||||
@@ -1597,18 +1449,12 @@ std::vector<PresentationJob> encode_sealed_frame(gfx::SealedFrame& sealedFrame,
|
|||||||
// The copy targets now hold this frame's resolves, so queue their readbacks on the same encoder;
|
// The copy targets now hold this frame's resolves, so queue their readbacks on the same encoder;
|
||||||
// completion is harvested in gfx::after_submit, never waited on here.
|
// completion is harvested in gfx::after_submit, never waited on here.
|
||||||
gfx::efb_ram::encode_async_downloads(encoder);
|
gfx::efb_ram::encode_async_downloads(encoder);
|
||||||
wgpu::BindGroup duplicatedMetalFXOutput;
|
|
||||||
if (!ctx.replayInterpolatedFrames) {
|
if (!ctx.replayInterpolatedFrames) {
|
||||||
for (uint32_t interpolatedFrame = 0; interpolatedFrame < ctx.interpolatedFrameCount;
|
for (uint32_t interpolatedFrame = 0; interpolatedFrame < ctx.interpolatedFrameCount;
|
||||||
++interpolatedFrame) {
|
++interpolatedFrame) {
|
||||||
auto image =
|
auto image =
|
||||||
acquire_presentation_image(interpolatedFrame, ctx.snapshotWidth, ctx.snapshotHeight);
|
acquire_presentation_image(interpolatedFrame, ctx.snapshotWidth, ctx.snapshotHeight);
|
||||||
const auto newMetalFXOutput = encode_presentation_snapshot(
|
encode_presentation_snapshot(encoder, ctx.presentSource, *image, true);
|
||||||
encoder, ctx.presentSource, *image, true, ctx.metalfxEnabled,
|
|
||||||
duplicatedMetalFXOutput ? &duplicatedMetalFXOutput : nullptr);
|
|
||||||
if (!duplicatedMetalFXOutput && newMetalFXOutput) {
|
|
||||||
duplicatedMetalFXOutput = newMetalFXOutput;
|
|
||||||
}
|
|
||||||
presentationJobs.push_back({
|
presentationJobs.push_back({
|
||||||
.image = std::move(image),
|
.image = std::move(image),
|
||||||
.logicalFrame = ctx.logicalFrame,
|
.logicalFrame = ctx.logicalFrame,
|
||||||
@@ -1616,14 +1462,13 @@ std::vector<PresentationJob> encode_sealed_frame(gfx::SealedFrame& sealedFrame,
|
|||||||
.interpolated = true,
|
.interpolated = true,
|
||||||
.duplicated = true,
|
.duplicated = true,
|
||||||
});
|
});
|
||||||
submitEncodedSlot(encoder, false);
|
submitEncodedSlot(encoder);
|
||||||
encoder = g_device.CreateCommandEncoder(&encoderDescriptor);
|
encoder = g_device.CreateCommandEncoder(&encoderDescriptor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
auto finalImage =
|
auto finalImage =
|
||||||
acquire_presentation_image(ctx.interpolatedFrameCount, ctx.snapshotWidth, ctx.snapshotHeight);
|
acquire_presentation_image(ctx.interpolatedFrameCount, ctx.snapshotWidth, ctx.snapshotHeight);
|
||||||
encode_presentation_snapshot(encoder, ctx.presentSource, *finalImage, true, ctx.metalfxEnabled,
|
encode_presentation_snapshot(encoder, ctx.presentSource, *finalImage, true);
|
||||||
duplicatedMetalFXOutput ? &duplicatedMetalFXOutput : nullptr);
|
|
||||||
auto pendingFrameCapture = encode_frame_capture(encoder, ctx.presentSource);
|
auto pendingFrameCapture = encode_frame_capture(encoder, ctx.presentSource);
|
||||||
presentationJobs.push_back({
|
presentationJobs.push_back({
|
||||||
.image = std::move(finalImage),
|
.image = std::move(finalImage),
|
||||||
@@ -1816,7 +1661,7 @@ bool run_frame_worker_cycle(gfx::SealedFrame& sealedFrame) noexcept {
|
|||||||
|
|
||||||
// Synchronous frame submission: seal, encode and present inline on the calling thread. Used when
|
// Synchronous frame submission: seal, encode and present inline on the calling thread. Used when
|
||||||
// the frame worker is disabled (RenderDoc captures) and on the boot path.
|
// the frame worker is disabled (RenderDoc captures) and on the boot path.
|
||||||
void end_frame_impl(bool pumpEvents, bool drainFifo) {
|
void end_frame_impl(bool pumpEvents, bool drainFifo) noexcept {
|
||||||
ZoneScoped;
|
ZoneScoped;
|
||||||
#ifdef AURORA_ENABLE_GX
|
#ifdef AURORA_ENABLE_GX
|
||||||
webgpu::fail_if_device_lost();
|
webgpu::fail_if_device_lost();
|
||||||
@@ -1826,9 +1671,11 @@ void end_frame_impl(bool pumpEvents, bool drainFifo) {
|
|||||||
gfx::SealedFrame sealedFrame;
|
gfx::SealedFrame sealedFrame;
|
||||||
SealedFrameContext ctx;
|
SealedFrameContext ctx;
|
||||||
std::vector<PresentationJob> presentationJobs;
|
std::vector<PresentationJob> presentationJobs;
|
||||||
if (drainFifo) gx::fifo::drain();
|
|
||||||
{
|
{
|
||||||
std::lock_guard gpuLock(g_rendererGpuMutex);
|
std::lock_guard gpuLock(g_rendererGpuMutex);
|
||||||
|
if (drainFifo) {
|
||||||
|
gx::fifo::drain();
|
||||||
|
}
|
||||||
seal_frame_locked(sealedFrame, ctx);
|
seal_frame_locked(sealedFrame, ctx);
|
||||||
presentationJobs = encode_sealed_frame(sealedFrame, ctx);
|
presentationJobs = encode_sealed_frame(sealedFrame, ctx);
|
||||||
}
|
}
|
||||||
@@ -1905,7 +1752,7 @@ bool begin_frame() noexcept {
|
|||||||
return prepared;
|
return prepared;
|
||||||
}
|
}
|
||||||
|
|
||||||
void end_frame() {
|
void end_frame() noexcept {
|
||||||
#ifdef AURORA_ENABLE_GX
|
#ifdef AURORA_ENABLE_GX
|
||||||
webgpu::fail_if_device_lost();
|
webgpu::fail_if_device_lost();
|
||||||
#endif
|
#endif
|
||||||
@@ -1921,7 +1768,10 @@ void end_frame() {
|
|||||||
|
|
||||||
// Seal all current GX work on the CPU while the renderer is known ready.
|
// Seal all current GX work on the CPU while the renderer is known ready.
|
||||||
// Later FIFO writes belong exclusively to the next frame.
|
// Later FIFO writes belong exclusively to the next frame.
|
||||||
gx::fifo::drain();
|
{
|
||||||
|
std::lock_guard gpuLock(g_rendererGpuMutex);
|
||||||
|
gx::fifo::drain();
|
||||||
|
}
|
||||||
{
|
{
|
||||||
std::lock_guard lock(g_frameWorker.mutex);
|
std::lock_guard lock(g_frameWorker.mutex);
|
||||||
g_frameWorker.framePrepared = false;
|
g_frameWorker.framePrepared = false;
|
||||||
@@ -1947,10 +1797,6 @@ bool wait_for_frame_worker_for(std::chrono::microseconds timeout) noexcept {
|
|||||||
return wait_for_frame_worker_private_for(FrameWorkerPhase::Done, timeout);
|
return wait_for_frame_worker_private_for(FrameWorkerPhase::Done, timeout);
|
||||||
}
|
}
|
||||||
std::recursive_mutex& renderer_gpu_mutex() noexcept { return g_rendererGpuMutex; }
|
std::recursive_mutex& renderer_gpu_mutex() noexcept { return g_rendererGpuMutex; }
|
||||||
void submit_staging_commands(const wgpu::CommandBuffer& commands) {
|
|
||||||
std::lock_guard submitLock(g_queueSubmitMutex);
|
|
||||||
webgpu::g_queue.Submit(1, &commands);
|
|
||||||
}
|
|
||||||
} // namespace aurora
|
} // namespace aurora
|
||||||
|
|
||||||
// C API bindings
|
// C API bindings
|
||||||
@@ -2013,6 +1859,10 @@ bool aurora_flush_efb_copies_to_ram() {
|
|||||||
if (!aurora::gfx::efb_ram::has_pending()) {
|
if (!aurora::gfx::efb_ram::has_pending()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
if (!aurora::gfx::efb_ram::prepare_downloads()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// This finalizes the frame still being recorded, on the producer thread, so join the whole cycle
|
// This finalizes the frame still being recorded, on the producer thread, so join the whole cycle
|
||||||
// first: the encode phase owns the previous passes, EFB targets and image pool.
|
// first: the encode phase owns the previous passes, EFB targets and image pool.
|
||||||
aurora::wait_for_frame_worker();
|
aurora::wait_for_frame_worker();
|
||||||
@@ -2020,7 +1870,6 @@ bool aurora_flush_efb_copies_to_ram() {
|
|||||||
// suffix cannot safely be replayed against the same mutable EFB resources.
|
// suffix cannot safely be replayed against the same mutable EFB resources.
|
||||||
aurora::gx::mark_frame_interpolation_replay_unsafe();
|
aurora::gx::mark_frame_interpolation_replay_unsafe();
|
||||||
aurora::gx::fifo::drain();
|
aurora::gx::fifo::drain();
|
||||||
if (!aurora::gfx::efb_ram::prepare_downloads()) return false;
|
|
||||||
const wgpu::CommandEncoderDescriptor encoderDescriptor{
|
const wgpu::CommandEncoderDescriptor encoderDescriptor{
|
||||||
.label = "GX CPU-visible EFB copy encoder",
|
.label = "GX CPU-visible EFB copy encoder",
|
||||||
};
|
};
|
||||||
@@ -2046,7 +1895,8 @@ bool aurora_flush_efb_copies_to_ram() {
|
|||||||
}
|
}
|
||||||
bool aurora_flush_efb_copy_to_ram(void* dest) {
|
bool aurora_flush_efb_copy_to_ram(void* dest) {
|
||||||
#ifdef AURORA_ENABLE_GX
|
#ifdef AURORA_ENABLE_GX
|
||||||
if (dest == nullptr || !aurora::gfx::efb_ram::has_pending(dest)) {
|
if (dest == nullptr || !aurora::gfx::efb_ram::has_pending(dest) ||
|
||||||
|
!aurora::gfx::efb_ram::prepare_downloads(dest)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2057,7 +1907,6 @@ bool aurora_flush_efb_copy_to_ram(void* dest) {
|
|||||||
// image instead of replaying this split frame.
|
// image instead of replaying this split frame.
|
||||||
aurora::gx::mark_frame_interpolation_replay_unsafe();
|
aurora::gx::mark_frame_interpolation_replay_unsafe();
|
||||||
aurora::gx::fifo::drain();
|
aurora::gx::fifo::drain();
|
||||||
if (!aurora::gfx::efb_ram::prepare_downloads(dest)) return false;
|
|
||||||
const wgpu::CommandEncoderDescriptor encoderDescriptor{
|
const wgpu::CommandEncoderDescriptor encoderDescriptor{
|
||||||
.label = "GX demanded EFB copy encoder",
|
.label = "GX demanded EFB copy encoder",
|
||||||
};
|
};
|
||||||
@@ -2099,7 +1948,3 @@ void aurora_set_background_input(bool value) {
|
|||||||
}
|
}
|
||||||
void aurora_set_display_mode(AuroraDisplayMode mode) { aurora::window::set_display_mode(mode); }
|
void aurora_set_display_mode(AuroraDisplayMode mode) { aurora::window::set_display_mode(mode); }
|
||||||
AuroraDisplayMode aurora_get_display_mode() { return aurora::window::get_display_mode(); }
|
AuroraDisplayMode aurora_get_display_mode() { return aurora::window::get_display_mode(); }
|
||||||
void aurora_set_metalfx_spatial(bool enabled) { aurora::g_metalfxRequested.store(enabled); }
|
|
||||||
bool aurora_get_metalfx_spatial() { return aurora::g_metalfxRequested.load(); }
|
|
||||||
bool aurora_is_metalfx_spatial_supported() { return aurora::g_metalfxSupported.load(); }
|
|
||||||
AuroraMetalFXStatus aurora_get_metalfx_status() { return aurora::g_metalfxStatus.load(); }
|
|
||||||
|
|||||||
@@ -2,43 +2,12 @@
|
|||||||
|
|
||||||
#import <Foundation/Foundation.h>
|
#import <Foundation/Foundation.h>
|
||||||
#include <SDL3/SDL_metal.h>
|
#include <SDL3/SDL_metal.h>
|
||||||
#include <SDL3/SDL_properties.h>
|
|
||||||
#include <SDL3/SDL_video.h>
|
|
||||||
|
|
||||||
namespace aurora::webgpu::utils {
|
namespace aurora::webgpu::utils {
|
||||||
namespace {
|
|
||||||
constexpr const char* MetalViewProperty = "aurora.window.metal_view";
|
|
||||||
|
|
||||||
void SDLCALL DestroyMetalView(void*, void* value) {
|
|
||||||
SDL_Metal_DestroyView(value);
|
|
||||||
}
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
std::shared_ptr<wgpu::ChainedStruct> SetupWindowAndGetSurfaceDescriptorCocoa(SDL_Window* window) {
|
std::shared_ptr<wgpu::ChainedStruct> SetupWindowAndGetSurfaceDescriptorCocoa(SDL_Window* window) {
|
||||||
const auto properties = SDL_GetWindowProperties(window);
|
SDL_MetalView view = SDL_Metal_CreateView(window);
|
||||||
if (!properties) {
|
std::shared_ptr<wgpu::SurfaceSourceMetalLayer> desc = std::make_shared<wgpu::SurfaceSourceMetalLayer>();
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
auto view = SDL_GetPointerProperty(properties, MetalViewProperty, nullptr);
|
|
||||||
if (!view) {
|
|
||||||
view = SDL_Metal_CreateView(window);
|
|
||||||
if (!view) {
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
// Own one view per window, not per WebGPU surface. Surface recovery must
|
|
||||||
// preserve the UIKit root and its controls (and the Cocoa Metal subview).
|
|
||||||
// SDL cleans window properties before destroying its native window.
|
|
||||||
// The cleanup callback also runs if setting the property fails.
|
|
||||||
if (!SDL_SetPointerPropertyWithCleanup(properties, MetalViewProperty, view, DestroyMetalView, nullptr)) {
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
auto desc = std::make_shared<wgpu::SurfaceSourceMetalLayer>();
|
|
||||||
desc->layer = SDL_Metal_GetLayer(view);
|
desc->layer = SDL_Metal_GetLayer(view);
|
||||||
if (!desc->layer) {
|
return std::move(desc);
|
||||||
SDL_ClearProperty(properties, MetalViewProperty);
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
return desc;
|
|
||||||
}
|
}
|
||||||
} // namespace aurora::webgpu::utils
|
} // namespace aurora::webgpu::utils
|
||||||
|
|||||||
@@ -46,7 +46,6 @@ void AuroraSetViewportPolicy(AuroraViewportPolicy policy) {
|
|||||||
}
|
}
|
||||||
g_gxState.viewportPolicy = policy;
|
g_gxState.viewportPolicy = policy;
|
||||||
aurora::window::set_frame_buffer_aspect_fit(policy == AURORA_VIEWPORT_FIT);
|
aurora::window::set_frame_buffer_aspect_fit(policy == AURORA_VIEWPORT_FIT);
|
||||||
aurora::window::set_force_aspect_16_9(policy == AURORA_VIEWPORT_16_9);
|
|
||||||
aurora::window::set_present_surface_fill(policy == AURORA_VIEWPORT_STRETCH);
|
aurora::window::set_present_surface_fill(policy == AURORA_VIEWPORT_STRETCH);
|
||||||
if (changed) {
|
if (changed) {
|
||||||
// Reapply the guest viewport and scissor after a resize.
|
// Reapply the guest viewport and scissor after a resize.
|
||||||
|
|||||||
@@ -319,18 +319,6 @@ std::array<bool, PAD_CHANMAX> g_suppressLeftTrigger{};
|
|||||||
std::array<bool, PAD_CHANMAX> g_suppressRightTrigger{};
|
std::array<bool, PAD_CHANMAX> g_suppressRightTrigger{};
|
||||||
|
|
||||||
bool is_mouse_scancode(const s32 scancode) { return scancode < PAD_KEY_INVALID; }
|
bool is_mouse_scancode(const s32 scancode) { return scancode < PAD_KEY_INVALID; }
|
||||||
bool is_native_binding_pressed(SDL_Gamepad* gamepad, u32 binding) {
|
|
||||||
if (PADIsAxisButton(binding)) {
|
|
||||||
const u32 axis = PADAxisButtonAxis(binding);
|
|
||||||
const u32 threshold = PADAxisButtonThreshold(binding);
|
|
||||||
if (axis >= SDL_GAMEPAD_AXIS_COUNT || threshold < 1 || threshold > 100) return false;
|
|
||||||
int value = SDL_GetGamepadAxis(gamepad, static_cast<SDL_GamepadAxis>(axis));
|
|
||||||
if (PADAxisButtonNegative(binding)) value = -value;
|
|
||||||
return value > 0 && value * 100 >= static_cast<int>(threshold) * 32767;
|
|
||||||
}
|
|
||||||
return binding < SDL_GAMEPAD_BUTTON_COUNT &&
|
|
||||||
SDL_GetGamepadButton(gamepad, static_cast<SDL_GamepadButton>(binding));
|
|
||||||
}
|
|
||||||
bool is_mouse_button_pressed(const s32 scancode) {
|
bool is_mouse_button_pressed(const s32 scancode) {
|
||||||
const int32_t buttonNum = -(scancode + 1);
|
const int32_t buttonNum = -(scancode + 1);
|
||||||
if (buttonNum < 1 || buttonNum > 5) {
|
if (buttonNum < 1 || buttonNum > 5) {
|
||||||
@@ -399,20 +387,19 @@ const char* PADGetNameForControllerIndex(const u32 idx) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void PADSetPortForIndex(const u32 idx, const u32 port) {
|
void PADSetPortForIndex(const u32 idx, const u32 port) {
|
||||||
if (port >= PAD_MAX_CONTROLLERS) return;
|
|
||||||
const auto* ctrl = __PADGetControllerForIndex(idx);
|
const auto* ctrl = __PADGetControllerForIndex(idx);
|
||||||
if (ctrl == nullptr) {
|
if (ctrl == nullptr) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const int32_t oldPort = aurora::input::player_index(ctrl->m_index);
|
const int32_t oldPort = SDL_GetGamepadPlayerIndex(ctrl->m_controller);
|
||||||
if (const auto* dest = aurora::input::get_controller_for_player(port); dest != nullptr && dest != ctrl) {
|
if (const auto* dest = aurora::input::get_controller_for_player(port); dest != nullptr && dest != ctrl) {
|
||||||
aurora::input::set_player_index(dest->m_index, -1);
|
SDL_SetGamepadPlayerIndex(dest->m_controller, -1);
|
||||||
}
|
}
|
||||||
if (oldPort >= 0 && oldPort != port) {
|
if (oldPort >= 0 && oldPort != port) {
|
||||||
aurora::input::persist_controller_for_player(oldPort, nullptr);
|
aurora::input::persist_controller_for_player(oldPort, nullptr);
|
||||||
}
|
}
|
||||||
aurora::input::set_player_index(ctrl->m_index, static_cast<Sint32>(port));
|
SDL_SetGamepadPlayerIndex(ctrl->m_controller, static_cast<Sint32>(port));
|
||||||
aurora::input::persist_controller_for_player(port, ctrl);
|
aurora::input::persist_controller_for_player(port, ctrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -438,7 +425,7 @@ void PADClearPort(const u32 port) {
|
|||||||
if (ctrl == nullptr) {
|
if (ctrl == nullptr) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
aurora::input::set_player_index(ctrl->m_index, -1);
|
SDL_SetGamepadPlayerIndex(ctrl->m_controller, -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Secondary bindings live only in memory; the runtime re-applies them from its
|
// Secondary bindings live only in memory; the runtime re-applies them from its
|
||||||
@@ -737,10 +724,10 @@ u32 PADRead(PADStatus* status) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
status[i].err = PAD_ERR_NONE;
|
status[i].err = PAD_ERR_NONE;
|
||||||
if (g_keyboardBindings[i].m_mappingsSet && SDL_GetKeyboardFocus() != nullptr) {
|
if (g_keyboardBindings[i].m_mappingsSet) {
|
||||||
std::for_each(g_keyboardBindings[i].m_buttonMapping.begin(), g_keyboardBindings[i].m_buttonMapping.end(),
|
std::for_each(g_keyboardBindings[i].m_buttonMapping.begin(), g_keyboardBindings[i].m_buttonMapping.end(),
|
||||||
[&kbState, &numKeys, &i, &status](const PADKeyButtonBinding& mapping) {
|
[&kbState, &i, &status](const PADKeyButtonBinding& mapping) {
|
||||||
if (mapping.scancode > PAD_KEY_INVALID && mapping.scancode < numKeys && kbState[mapping.scancode]) {
|
if (mapping.scancode > PAD_KEY_INVALID && kbState[mapping.scancode]) {
|
||||||
status[i].button |= mapping.padButton;
|
status[i].button |= mapping.padButton;
|
||||||
} else if (is_mouse_scancode(mapping.scancode) && is_mouse_button_pressed(mapping.scancode)) {
|
} else if (is_mouse_scancode(mapping.scancode) && is_mouse_button_pressed(mapping.scancode)) {
|
||||||
status[i].button |= mapping.padButton;
|
status[i].button |= mapping.padButton;
|
||||||
@@ -801,7 +788,7 @@ u32 PADRead(PADStatus* status) {
|
|||||||
status[i].triggerRight = static_cast<u8>(std::min(static_cast<int>(status[i].triggerRight) + tr, 255));
|
status[i].triggerRight = static_cast<u8>(std::min(static_cast<int>(status[i].triggerRight) + tr, 255));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (controller && !g_keyboardBindings[i].m_mappingsSet) {
|
if (controller) {
|
||||||
EnsureMappingLoaded(controller);
|
EnsureMappingLoaded(controller);
|
||||||
|
|
||||||
// Wii U Pro Controller raw D-pad fallback. SDL's HIDAPI Wii driver posts
|
// Wii U Pro Controller raw D-pad fallback. SDL's HIDAPI Wii driver posts
|
||||||
@@ -848,7 +835,7 @@ u32 PADRead(PADStatus* status) {
|
|||||||
bool rightTriggerSet = false;
|
bool rightTriggerSet = false;
|
||||||
std::for_each(controller->m_buttonMapping.begin(), controller->m_buttonMapping.end(),
|
std::for_each(controller->m_buttonMapping.begin(), controller->m_buttonMapping.end(),
|
||||||
[&controller, &i, &status, &leftTriggerSet, &rightTriggerSet](const auto& mapping) {
|
[&controller, &i, &status, &leftTriggerSet, &rightTriggerSet](const auto& mapping) {
|
||||||
if (is_native_binding_pressed(controller->m_controller, mapping.nativeButton)) {
|
if (SDL_GetGamepadButton(controller->m_controller, static_cast<SDL_GamepadButton>(mapping.nativeButton))) {
|
||||||
status[i].button |= mapping.padButton;
|
status[i].button |= mapping.padButton;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -865,7 +852,7 @@ u32 PADRead(PADStatus* status) {
|
|||||||
if (mapping.nativeButton == PAD_NATIVE_BUTTON_INVALID) {
|
if (mapping.nativeButton == PAD_NATIVE_BUTTON_INVALID) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (is_native_binding_pressed(controller->m_controller, mapping.nativeButton)) {
|
if (SDL_GetGamepadButton(controller->m_controller, static_cast<SDL_GamepadButton>(mapping.nativeButton))) {
|
||||||
status[i].button |= mapping.padButton;
|
status[i].button |= mapping.padButton;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -959,17 +946,6 @@ u32 PADRead(PADStatus* status) {
|
|||||||
Sint16 tl = std::max(static_cast<Sint16>(0), _get_axis_value(controller, PAD_AXIS_TRIGGER_L));
|
Sint16 tl = std::max(static_cast<Sint16>(0), _get_axis_value(controller, PAD_AXIS_TRIGGER_L));
|
||||||
Sint16 tr = std::max(static_cast<Sint16>(0), _get_axis_value(controller, PAD_AXIS_TRIGGER_R));
|
Sint16 tr = std::max(static_cast<Sint16>(0), _get_axis_value(controller, PAD_AXIS_TRIGGER_R));
|
||||||
|
|
||||||
// Games can read either the digital L/R bits or their analog pressure.
|
|
||||||
// An explicit button binding must drive both, otherwise the original
|
|
||||||
// L2/R2 axis still activates L/R even when it was rebound to L1/R1.
|
|
||||||
// Real GC pads retain independent analog travel and end-stop clicks.
|
|
||||||
if (!(controller->m_isGameCube ||
|
|
||||||
(SDL_GetGamepadType(controller->m_controller) == SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_PRO &&
|
|
||||||
controller->m_pid == 0x2073))) {
|
|
||||||
if (leftTriggerSet) tl = (status[i].button & PAD_TRIGGER_L) != 0 ? 32767 : 0;
|
|
||||||
if (rightTriggerSet) tr = (status[i].button & PAD_TRIGGER_R) != 0 ? 32767 : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (controller->m_deadZones.emulateTriggers) {
|
if (controller->m_deadZones.emulateTriggers) {
|
||||||
if (!leftTriggerSet && tl > controller->m_deadZones.leftTriggerActivationZone) {
|
if (!leftTriggerSet && tl > controller->m_deadZones.leftTriggerActivationZone) {
|
||||||
status[i].button |= PAD_TRIGGER_L;
|
status[i].button |= PAD_TRIGGER_L;
|
||||||
@@ -1014,13 +990,12 @@ void PADControlMotor(const u32 chan, const u32 cmd) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (controller->m_isGameCube) {
|
if (controller->m_isGameCube) {
|
||||||
if (cmd == PAD_MOTOR_STOP || cmd == PAD_MOTOR_STOP_HARD) {
|
if (cmd == PAD_MOTOR_STOP) {
|
||||||
// Use an unambiguous motor-off request. The (0, 1) coast encoding
|
aurora::input::controller_rumble(instance, 0, 1, 0);
|
||||||
// requires SDL's GameCube brake mode; other backends or an overridden
|
|
||||||
// hint interpret it as rumble and can leave the controller vibrating.
|
|
||||||
aurora::input::controller_rumble(instance, 0, 0, 0);
|
|
||||||
} else if (cmd == PAD_MOTOR_RUMBLE) {
|
} else if (cmd == PAD_MOTOR_RUMBLE) {
|
||||||
aurora::input::controller_rumble(instance, 1, 1, 0);
|
aurora::input::controller_rumble(instance, 1, 1, 0);
|
||||||
|
} else if (cmd == PAD_MOTOR_STOP_HARD) {
|
||||||
|
aurora::input::controller_rumble(instance, 0, 0, 0);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (cmd == PAD_MOTOR_STOP) {
|
if (cmd == PAD_MOTOR_STOP) {
|
||||||
@@ -1303,11 +1278,6 @@ BOOL PADSetKeyButtonBindings(const u32 port, PADKeyButtonBinding bindings[PAD_BU
|
|||||||
}
|
}
|
||||||
|
|
||||||
PADKeyButtonBinding* PADGetKeyButtonBindings(const u32 port, u32* buttonCount) {
|
PADKeyButtonBinding* PADGetKeyButtonBindings(const u32 port, u32* buttonCount) {
|
||||||
PADInit();
|
|
||||||
if (!g_keyboardBindingsLoaded) {
|
|
||||||
g_keyboardBindingsLoaded = true;
|
|
||||||
load_keyboard_bindings();
|
|
||||||
}
|
|
||||||
if (port >= PAD_MAX_CONTROLLERS || !g_keyboardBindings[port].m_mappingsSet) {
|
if (port >= PAD_MAX_CONTROLLERS || !g_keyboardBindings[port].m_mappingsSet) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,13 +7,11 @@
|
|||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <mutex>
|
|
||||||
|
|
||||||
namespace aurora::vi {
|
namespace aurora::vi {
|
||||||
std::optional<GXRenderModeObj> g_renderMode;
|
std::optional<GXRenderModeObj> g_renderMode;
|
||||||
namespace {
|
namespace {
|
||||||
std::atomic<float> g_presentAspectCorrection{1.f};
|
std::atomic<float> g_presentAspectCorrection{1.f};
|
||||||
std::mutex g_renderModeMutex;
|
|
||||||
|
|
||||||
float calculate_present_aspect_correction(const GXRenderModeObj& rm) noexcept {
|
float calculate_present_aspect_correction(const GXRenderModeObj& rm) noexcept {
|
||||||
if (rm.viWidth == 0 || rm.viHeight == 0) {
|
if (rm.viWidth == 0 || rm.viHeight == 0) {
|
||||||
@@ -31,8 +29,9 @@ float calculate_present_aspect_correction(const GXRenderModeObj& rm) noexcept {
|
|||||||
const float verticalFill = static_cast<float>(rm.viHeight) / nominalActiveHeight;
|
const float verticalFill = static_cast<float>(rm.viHeight) / nominalActiveHeight;
|
||||||
return horizontalFill / verticalFill;
|
return horizontalFill / verticalFill;
|
||||||
}
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
Vec2<uint32_t> render_mode_size_locked() noexcept {
|
Vec2<uint32_t> render_mode_size() noexcept {
|
||||||
if (!g_renderMode) {
|
if (!g_renderMode) {
|
||||||
return {640, 528};
|
return {640, 528};
|
||||||
}
|
}
|
||||||
@@ -41,31 +40,18 @@ Vec2<uint32_t> render_mode_size_locked() noexcept {
|
|||||||
return {std::max<uint32_t>(g_renderMode->fbWidth, 640), std::max<uint32_t>(g_renderMode->efbHeight, 528)};
|
return {std::max<uint32_t>(g_renderMode->fbWidth, 640), std::max<uint32_t>(g_renderMode->efbHeight, 528)};
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
Vec2<uint32_t> render_mode_size() noexcept {
|
|
||||||
std::lock_guard lock(g_renderModeMutex);
|
|
||||||
return render_mode_size_locked();
|
|
||||||
}
|
|
||||||
|
|
||||||
void configure(const GXRenderModeObj* rm) noexcept {
|
void configure(const GXRenderModeObj* rm) noexcept {
|
||||||
bool sizeChanged = false;
|
const auto oldSize = render_mode_size();
|
||||||
{
|
if (rm == nullptr) {
|
||||||
std::lock_guard lock(g_renderModeMutex);
|
g_renderMode.reset();
|
||||||
const auto oldSize = render_mode_size_locked();
|
} else {
|
||||||
if (rm == nullptr) {
|
g_renderMode = *rm;
|
||||||
g_renderMode.reset();
|
g_presentAspectCorrection.store(calculate_present_aspect_correction(*rm), std::memory_order_release);
|
||||||
} else {
|
|
||||||
g_renderMode = *rm;
|
|
||||||
g_presentAspectCorrection.store(calculate_present_aspect_correction(*rm), std::memory_order_release);
|
|
||||||
}
|
|
||||||
if (rm == nullptr) {
|
|
||||||
g_presentAspectCorrection.store(1.f, std::memory_order_release);
|
|
||||||
}
|
|
||||||
sizeChanged = render_mode_size_locked() != oldSize;
|
|
||||||
}
|
}
|
||||||
// Never hold the mode lock across a resize request or a renderer callback.
|
if (rm == nullptr) {
|
||||||
if (sizeChanged) {
|
g_presentAspectCorrection.store(1.f, std::memory_order_release);
|
||||||
|
}
|
||||||
|
if (render_mode_size() != oldSize) {
|
||||||
window::request_frame_buffer_resize();
|
window::request_frame_buffer_resize();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -75,7 +61,6 @@ Vec2<uint32_t> configured_fb_size() noexcept {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Vec2<uint32_t> visible_fb_size() noexcept {
|
Vec2<uint32_t> visible_fb_size() noexcept {
|
||||||
std::lock_guard lock(g_renderModeMutex);
|
|
||||||
if (!g_renderMode) {
|
if (!g_renderMode) {
|
||||||
return {640, 528};
|
return {640, 528};
|
||||||
}
|
}
|
||||||
|
|||||||
+51
-190
@@ -1,5 +1,4 @@
|
|||||||
#include "common.hpp"
|
#include "common.hpp"
|
||||||
#include "staging_map.hpp"
|
|
||||||
#include "../gx/shader_info.hpp"
|
#include "../gx/shader_info.hpp"
|
||||||
|
|
||||||
#include "clear.hpp"
|
#include "clear.hpp"
|
||||||
@@ -36,13 +35,10 @@ using webgpu::g_device;
|
|||||||
using webgpu::g_instance;
|
using webgpu::g_instance;
|
||||||
using webgpu::g_queue;
|
using webgpu::g_queue;
|
||||||
|
|
||||||
struct DebugFrameData {
|
|
||||||
#ifdef AURORA_GFX_DEBUG_GROUPS
|
#ifdef AURORA_GFX_DEBUG_GROUPS
|
||||||
std::vector<std::string> groups;
|
std::vector<std::string> g_debugGroupStack;
|
||||||
std::vector<std::string> markers;
|
std::vector<std::string> g_debugMarkers;
|
||||||
#endif
|
#endif
|
||||||
};
|
|
||||||
DebugFrameData g_debugFrame;
|
|
||||||
|
|
||||||
constexpr uint64_t StagingBufferSize = UniformBufferSize + VertexBufferSize + IndexBufferSize + StorageBufferSize +
|
constexpr uint64_t StagingBufferSize = UniformBufferSize + VertexBufferSize + IndexBufferSize + StorageBufferSize +
|
||||||
(UseTextureBuffer ? TextureUploadSize : 0);
|
(UseTextureBuffer ? TextureUploadSize : 0);
|
||||||
@@ -131,7 +127,12 @@ wgpu::Buffer g_storageBuffer;
|
|||||||
constexpr size_t FrameSlotCount = 3;
|
constexpr size_t FrameSlotCount = 3;
|
||||||
static std::array<wgpu::Buffer, FrameSlotCount> g_stagingBuffers;
|
static std::array<wgpu::Buffer, FrameSlotCount> g_stagingBuffers;
|
||||||
static size_t currentStagingBuffer = 0;
|
static size_t currentStagingBuffer = 0;
|
||||||
static StagingMapState s_mappingState;
|
enum class BufferMapState {
|
||||||
|
Unmapped,
|
||||||
|
Mapping,
|
||||||
|
Mapped,
|
||||||
|
};
|
||||||
|
static std::atomic s_mappingState{BufferMapState::Unmapped};
|
||||||
static wgpu::Limits g_cachedLimits;
|
static wgpu::Limits g_cachedLimits;
|
||||||
// Advanced once per logical frame in the seal prologue, under the renderer GPU mutex and with the
|
// Advanced once per logical frame in the seal prologue, under the renderer GPU mutex and with the
|
||||||
// producer blocked, so every later reader sees a value that no longer moves.
|
// producer blocked, so every later reader sees a value that no longer moves.
|
||||||
@@ -166,12 +167,7 @@ struct RenderPass {
|
|||||||
Range resolveUniformRange;
|
Range resolveUniformRange;
|
||||||
std::array<u32, 3> resolveCopyFilterCoefficients{0, 64, 0};
|
std::array<u32, 3> resolveCopyFilterCoefficients{0, 64, 0};
|
||||||
Vec4<float> clearColorValue{0.f, 0.f, 0.f, 0.f};
|
Vec4<float> clearColorValue{0.f, 0.f, 0.f, 0.f};
|
||||||
// 1.f is the forward-Z "farthest" clear value; under UseReversedZ farthest is 0.f instead (see
|
float clearDepthValue = 1.f;
|
||||||
// gx::clear_depth_value(), which the main render pass explicitly overrides this default with -
|
|
||||||
// any OTHER pass that keeps this default, e.g. an offscreen render-to-texture pass composited
|
|
||||||
// later, needs the same reversed-Z-aware value or its depth buffer starts "already nearest",
|
|
||||||
// failing every subsequent depth test and making whatever's drawn into it vanish).
|
|
||||||
float clearDepthValue = gx::UseReversedZ ? 0.f : 1.f;
|
|
||||||
CommandList commands;
|
CommandList commands;
|
||||||
bool clearColor = true;
|
bool clearColor = true;
|
||||||
bool clearDepth = true;
|
bool clearDepth = true;
|
||||||
@@ -232,8 +228,6 @@ static void recycle_render_passes(std::vector<RenderPass>& passes) noexcept {
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct SealedFrameData {
|
struct SealedFrameData {
|
||||||
depth_peek::FrameMapping depthMapping;
|
|
||||||
DebugFrameData debug;
|
|
||||||
std::vector<RenderPass> passes;
|
std::vector<RenderPass> passes;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -255,51 +249,6 @@ static std::atomic_bool g_inOffscreen{false};
|
|||||||
static std::optional<RenderPass> g_suspendedEfbPass;
|
static std::optional<RenderPass> g_suspendedEfbPass;
|
||||||
static Viewport g_suspendedEfbViewport;
|
static Viewport g_suspendedEfbViewport;
|
||||||
static ClipRect g_suspendedEfbScissor;
|
static ClipRect g_suspendedEfbScissor;
|
||||||
// Prefix referenced by a suspended EFB pass. Preserve its offsets across an
|
|
||||||
// offscreen split, without rendering it before the bake it may sample finishes.
|
|
||||||
static StagingSizes g_suspendedEfbBytes{};
|
|
||||||
static constexpr StagingSizes PhysicalStagingCapacity{
|
|
||||||
VertexBufferSize, UniformBufferSize, IndexBufferSize, StorageBufferSize};
|
|
||||||
static StagingSizes g_stagingCapacity = PhysicalStagingCapacity;
|
|
||||||
static uint64_t g_stagingEpoch = 0;
|
|
||||||
static uint64_t g_stagingSplitCount = 0;
|
|
||||||
static StagingSizes g_stagingHighWater{};
|
|
||||||
|
|
||||||
StagingSizes staging_usage() noexcept {
|
|
||||||
return {g_verts.size(), g_uniforms.size(), g_indices.size(), g_storage.size()};
|
|
||||||
}
|
|
||||||
StagingSizes staging_high_water() noexcept { return g_stagingHighWater; }
|
|
||||||
uint64_t staging_epoch() noexcept { return g_stagingEpoch; }
|
|
||||||
uint64_t staging_split_count() noexcept { return g_stagingSplitCount; }
|
|
||||||
uint64_t staging_uniform_bytes(uint64_t bytes) {
|
|
||||||
return staging_padded(bytes, g_cachedLimits.minUniformBufferOffsetAlignment);
|
|
||||||
}
|
|
||||||
uint64_t staging_storage_bytes(uint64_t bytes) {
|
|
||||||
return staging_padded(bytes, g_cachedLimits.minStorageBufferOffsetAlignment);
|
|
||||||
}
|
|
||||||
void set_staging_capacity_limits_for_testing(const StagingSizes& limits) {
|
|
||||||
for (unsigned i = 0; i < limits.size(); ++i) {
|
|
||||||
if (limits[i] > PhysicalStagingCapacity[i])
|
|
||||||
throw StagingCapacityError("Test staging capacity exceeds physical buffer");
|
|
||||||
}
|
|
||||||
g_stagingCapacity = limits;
|
|
||||||
g_stagingHighWater = {};
|
|
||||||
}
|
|
||||||
bool staging_has_space(const StagingSizes& demand) {
|
|
||||||
// Async readback preparation runs in the worker's noexcept seal prologue.
|
|
||||||
// Reserve all 32 slots plus the uniform binding's 3840-byte trailing window.
|
|
||||||
const StagingSizes tail{0, gx::MaxUniformSize + efb_ram::MaxAsyncReadbackSlots * staging_uniform_bytes(48), 0, 0};
|
|
||||||
const StagingSizes retained = g_suspendedEfbPass ? g_suspendedEfbBytes : StagingSizes{};
|
|
||||||
if (!staging_fits(retained, demand, tail, g_stagingCapacity))
|
|
||||||
throw StagingCapacityError("GPU operation exceeds staging capacity including retained EFB data");
|
|
||||||
return staging_fits(staging_usage(), demand, tail, g_stagingCapacity);
|
|
||||||
}
|
|
||||||
void ensure_staging_space(const StagingSizes& demand) {
|
|
||||||
if (staging_has_space(demand)) return;
|
|
||||||
split_staging_batch();
|
|
||||||
if (!staging_has_space(demand))
|
|
||||||
throw StagingCapacityError("GPU operation still exceeds staging capacity after submission");
|
|
||||||
}
|
|
||||||
|
|
||||||
static void discard_suspended_efb_pass() noexcept {
|
static void discard_suspended_efb_pass() noexcept {
|
||||||
if (g_suspendedEfbPass) {
|
if (g_suspendedEfbPass) {
|
||||||
@@ -324,8 +273,7 @@ static size_t g_recordingSnapshotSlot = 0;
|
|||||||
static TextureHandle new_resolve_source_snapshot(wgpu::Extent3D size, wgpu::TextureFormat format) noexcept {
|
static TextureHandle new_resolve_source_snapshot(wgpu::Extent3D size, wgpu::TextureFormat format) noexcept {
|
||||||
const wgpu::TextureDescriptor textureDescriptor{
|
const wgpu::TextureDescriptor textureDescriptor{
|
||||||
.label = "GX Copy Source Snapshot",
|
.label = "GX Copy Source Snapshot",
|
||||||
.usage = wgpu::TextureUsage::TextureBinding | wgpu::TextureUsage::CopySrc |
|
.usage = wgpu::TextureUsage::TextureBinding | wgpu::TextureUsage::CopyDst,
|
||||||
wgpu::TextureUsage::CopyDst,
|
|
||||||
.dimension = wgpu::TextureDimension::e2D,
|
.dimension = wgpu::TextureDimension::e2D,
|
||||||
.size = size,
|
.size = size,
|
||||||
.format = format,
|
.format = format,
|
||||||
@@ -471,7 +419,7 @@ static inline void push_command(CommandType type, const Command::Data& data) {
|
|||||||
g_renderPasses[g_currentRenderPass].commands.push_back({
|
g_renderPasses[g_currentRenderPass].commands.push_back({
|
||||||
.type = type,
|
.type = type,
|
||||||
#ifdef AURORA_GFX_DEBUG_GROUPS
|
#ifdef AURORA_GFX_DEBUG_GROUPS
|
||||||
.debugGroupStack = g_debugFrame.groups,
|
.debugGroupStack = g_debugGroupStack,
|
||||||
#endif
|
#endif
|
||||||
.data = data,
|
.data = data,
|
||||||
});
|
});
|
||||||
@@ -531,7 +479,6 @@ void set_scissor(const ClipRect& cmd) noexcept {
|
|||||||
template <>
|
template <>
|
||||||
void push_draw_command(clear::DrawData data) {
|
void push_draw_command(clear::DrawData data) {
|
||||||
if (data.uniformRange.size == 0) {
|
if (data.uniformRange.size == 0) {
|
||||||
ensure_staging_space({0, staging_uniform_bytes(16), 0, 0});
|
|
||||||
const std::array clearUniform{
|
const std::array clearUniform{
|
||||||
std::clamp(data.depth, 0.f, 1.f),
|
std::clamp(data.depth, 0.f, 1.f),
|
||||||
0.f,
|
0.f,
|
||||||
@@ -558,7 +505,6 @@ void resolve_pass(TextureHandle texture, ClipRect rect, bool clearColor, bool cl
|
|||||||
Log.warn("Dropping resolve pass without an active render pass");
|
Log.warn("Dropping resolve pass without an active render pass");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ensure_staging_space({0, 2 * staging_uniform_bytes(48), 0, 0});
|
|
||||||
auto& prevPass = g_renderPasses[g_currentRenderPass];
|
auto& prevPass = g_renderPasses[g_currentRenderPass];
|
||||||
const auto targetWidth = static_cast<int32_t>(prevPass.targetSize.width);
|
const auto targetWidth = static_cast<int32_t>(prevPass.targetSize.width);
|
||||||
const auto targetHeight = static_cast<int32_t>(prevPass.targetSize.height);
|
const auto targetHeight = static_cast<int32_t>(prevPass.targetSize.height);
|
||||||
@@ -591,7 +537,7 @@ void resolve_pass(TextureHandle texture, ClipRect rect, bool clearColor, bool cl
|
|||||||
sourceRect = {srcLeft, srcTop, std::max(srcRight - srcLeft, 1.0f), std::max(srcBottom - srcTop, 1.0f)};
|
sourceRect = {srcLeft, srcTop, std::max(srcRight - srcLeft, 1.0f), std::max(srcBottom - srcTop, 1.0f)};
|
||||||
}
|
}
|
||||||
prevPass.resolveTarget = std::move(texture);
|
prevPass.resolveTarget = std::move(texture);
|
||||||
prevPass.requireReadyPipelines |= persistentCopy;
|
prevPass.requireReadyPipelines = persistentCopy;
|
||||||
prevPass.resolveRect = rect;
|
prevPass.resolveRect = rect;
|
||||||
prevPass.resolveSourceRect = sourceRect;
|
prevPass.resolveSourceRect = sourceRect;
|
||||||
prevPass.resolveFormat = resolveFormat;
|
prevPass.resolveFormat = resolveFormat;
|
||||||
@@ -787,7 +733,6 @@ void begin_offscreen(uint32_t width, uint32_t height) {
|
|||||||
if (!g_inOffscreen) {
|
if (!g_inOffscreen) {
|
||||||
auto& currentPass = g_renderPasses[g_currentRenderPass];
|
auto& currentPass = g_renderPasses[g_currentRenderPass];
|
||||||
if (!currentPass.resolveTarget) {
|
if (!currentPass.resolveTarget) {
|
||||||
g_suspendedEfbBytes = staging_usage();
|
|
||||||
g_suspendedEfbPass = std::move(currentPass);
|
g_suspendedEfbPass = std::move(currentPass);
|
||||||
g_renderPasses.pop_back();
|
g_renderPasses.pop_back();
|
||||||
--g_currentRenderPass;
|
--g_currentRenderPass;
|
||||||
@@ -811,9 +756,7 @@ void begin_offscreen(uint32_t width, uint32_t height) {
|
|||||||
.targetSize = {width, height, 1},
|
.targetSize = {width, height, 1},
|
||||||
.msaaSamples = 1,
|
.msaaSamples = 1,
|
||||||
.clearColorValue = {0.f, 0.f, 0.f, 0.f},
|
.clearColorValue = {0.f, 0.f, 0.f, 0.f},
|
||||||
// See the RenderPass::clearDepthValue default's comment: this offscreen pass gets its own
|
.clearDepthValue = 1.f,
|
||||||
// depth buffer, and the farthest clear value is 0.f, not 1.f, under UseReversedZ.
|
|
||||||
.clearDepthValue = gx::UseReversedZ ? 0.f : 1.f,
|
|
||||||
.clearColor = true,
|
.clearColor = true,
|
||||||
.clearDepth = true,
|
.clearDepth = true,
|
||||||
};
|
};
|
||||||
@@ -900,7 +843,7 @@ void initialize() {
|
|||||||
label.c_str());
|
label.c_str());
|
||||||
}
|
}
|
||||||
currentStagingBuffer = 0;
|
currentStagingBuffer = 0;
|
||||||
s_mappingState.reset();
|
s_mappingState.store(BufferMapState::Unmapped, std::memory_order_release);
|
||||||
map_staging_buffer();
|
map_staging_buffer();
|
||||||
|
|
||||||
{
|
{
|
||||||
@@ -1006,8 +949,6 @@ void shutdown() {
|
|||||||
g_uniformBuffer = {};
|
g_uniformBuffer = {};
|
||||||
g_indexBuffer = {};
|
g_indexBuffer = {};
|
||||||
g_storageBuffer = {};
|
g_storageBuffer = {};
|
||||||
// Invalidate outstanding callbacks before releasing their buffers.
|
|
||||||
s_mappingState.reset();
|
|
||||||
g_stagingBuffers.fill({});
|
g_stagingBuffers.fill({});
|
||||||
for (auto& pool : g_resolveSourceSnapshotPools) {
|
for (auto& pool : g_resolveSourceSnapshotPools) {
|
||||||
pool.entry.reset();
|
pool.entry.reset();
|
||||||
@@ -1026,36 +967,37 @@ void shutdown() {
|
|||||||
g_inOffscreen = false;
|
g_inOffscreen = false;
|
||||||
g_frameIndex = UINT32_MAX;
|
g_frameIndex = UINT32_MAX;
|
||||||
currentStagingBuffer = 0;
|
currentStagingBuffer = 0;
|
||||||
|
s_mappingState.store(BufferMapState::Unmapped, std::memory_order_release);
|
||||||
}
|
}
|
||||||
|
|
||||||
void map_staging_buffer() {
|
void map_staging_buffer() {
|
||||||
const auto generation = s_mappingState.request();
|
auto expected = BufferMapState::Unmapped;
|
||||||
if (generation == 0) {
|
if (!s_mappingState.compare_exchange_strong(expected, BufferMapState::Mapping, std::memory_order_acq_rel,
|
||||||
|
std::memory_order_acquire)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
g_stagingBuffers[currentStagingBuffer].MapAsync(
|
g_stagingBuffers[currentStagingBuffer].MapAsync(
|
||||||
wgpu::MapMode::Write, 0, StagingBufferSize, wgpu::CallbackMode::AllowSpontaneous,
|
wgpu::MapMode::Write, 0, StagingBufferSize, wgpu::CallbackMode::AllowSpontaneous,
|
||||||
[generation](wgpu::MapAsyncStatus status, wgpu::StringView message) {
|
[](wgpu::MapAsyncStatus status, wgpu::StringView message) {
|
||||||
const auto result = status == wgpu::MapAsyncStatus::Success
|
|
||||||
? BufferMapState::Mapped : BufferMapState::Unmapped;
|
|
||||||
if (!s_mappingState.complete(generation, result)) return;
|
|
||||||
if (status == wgpu::MapAsyncStatus::CallbackCancelled || status == wgpu::MapAsyncStatus::Aborted) {
|
if (status == wgpu::MapAsyncStatus::CallbackCancelled || status == wgpu::MapAsyncStatus::Aborted) {
|
||||||
Log.warn("Buffer mapping {}: {}", magic_enum::enum_name(status), message);
|
Log.warn("Buffer mapping {}: {}", magic_enum::enum_name(status), message);
|
||||||
|
s_mappingState.store(BufferMapState::Unmapped, std::memory_order_release);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ASSERT(status == wgpu::MapAsyncStatus::Success, "Buffer mapping failed: {} {}", magic_enum::enum_name(status),
|
ASSERT(status == wgpu::MapAsyncStatus::Success, "Buffer mapping failed: {} {}", magic_enum::enum_name(status),
|
||||||
message);
|
message);
|
||||||
|
s_mappingState.store(BufferMapState::Mapped, std::memory_order_release);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool begin_frame_impl(bool clearEfb, bool capacityResume = false) {
|
static bool begin_frame_impl(bool clearEfb) {
|
||||||
ZoneScoped;
|
ZoneScoped;
|
||||||
{
|
{
|
||||||
ZoneScopedN("Wait for buffer map");
|
ZoneScopedN("Wait for buffer map");
|
||||||
map_staging_buffer();
|
map_staging_buffer();
|
||||||
while (true) {
|
while (true) {
|
||||||
const auto mappingState = s_mappingState.state();
|
const auto mappingState = s_mappingState.load(std::memory_order_acquire);
|
||||||
if (mappingState == BufferMapState::Mapped) {
|
if (mappingState == BufferMapState::Mapped) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -1071,11 +1013,8 @@ static bool begin_frame_impl(bool clearEfb, bool capacityResume = false) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
g_instance.ProcessEvents();
|
g_instance.ProcessEvents();
|
||||||
webgpu::fail_if_device_lost();
|
|
||||||
s_mappingState.wait_for_progress();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
++g_stagingEpoch;
|
|
||||||
g_recordingSnapshotSlot = currentStagingBuffer;
|
g_recordingSnapshotSlot = currentStagingBuffer;
|
||||||
size_t bufferOffset = 0;
|
size_t bufferOffset = 0;
|
||||||
const auto& stagingBuf = g_stagingBuffers[currentStagingBuffer];
|
const auto& stagingBuf = g_stagingBuffers[currentStagingBuffer];
|
||||||
@@ -1100,7 +1039,7 @@ static bool begin_frame_impl(bool clearEfb, bool capacityResume = false) {
|
|||||||
gx::begin_frame_interpolation();
|
gx::begin_frame_interpolation();
|
||||||
}
|
}
|
||||||
discard_suspended_efb_pass();
|
discard_suspended_efb_pass();
|
||||||
if (!capacityResume) webgpu::clear_present_source_override();
|
webgpu::clear_present_source_override();
|
||||||
|
|
||||||
push_render_pass(RenderPass{});
|
push_render_pass(RenderPass{});
|
||||||
set_efb_targets(g_renderPasses[0]);
|
set_efb_targets(g_renderPasses[0]);
|
||||||
@@ -1139,12 +1078,12 @@ void abort_frame() noexcept {
|
|||||||
g_textureUploads.clear();
|
g_textureUploads.clear();
|
||||||
g_textureUpload.release();
|
g_textureUpload.release();
|
||||||
}
|
}
|
||||||
if (s_mappingState.state() == BufferMapState::Mapped) {
|
if (s_mappingState.load(std::memory_order_acquire) == BufferMapState::Mapped) {
|
||||||
// Pending interpolation tasks hold raw pointers into the mapped staging
|
// Pending interpolation tasks hold raw pointers into the mapped staging
|
||||||
// range; they must be dropped before the buffer is unmapped and rotated.
|
// range; they must be dropped before the buffer is unmapped and rotated.
|
||||||
gx::drop_pending_frame_interpolation_uniforms();
|
gx::drop_pending_frame_interpolation_uniforms();
|
||||||
g_stagingBuffers[currentStagingBuffer].Unmap();
|
g_stagingBuffers[currentStagingBuffer].Unmap();
|
||||||
s_mappingState.reset();
|
s_mappingState.store(BufferMapState::Unmapped, std::memory_order_release);
|
||||||
currentStagingBuffer = (currentStagingBuffer + 1) % g_stagingBuffers.size();
|
currentStagingBuffer = (currentStagingBuffer + 1) % g_stagingBuffers.size();
|
||||||
map_staging_buffer();
|
map_staging_buffer();
|
||||||
}
|
}
|
||||||
@@ -1161,7 +1100,7 @@ void abort_frame() noexcept {
|
|||||||
|
|
||||||
static void end_batch_impl(const wgpu::CommandEncoder& cmd, bool advanceFrame) {
|
static void end_batch_impl(const wgpu::CommandEncoder& cmd, bool advanceFrame) {
|
||||||
ZoneScoped;
|
ZoneScoped;
|
||||||
ASSERT(!advanceFrame || !g_inOffscreen, "end_frame called while offscreen rendering is active");
|
ASSERT(!g_inOffscreen, "end_frame called while offscreen rendering is active");
|
||||||
if (advanceFrame) {
|
if (advanceFrame) {
|
||||||
gx::finalize_frame_interpolation();
|
gx::finalize_frame_interpolation();
|
||||||
} else {
|
} else {
|
||||||
@@ -1170,8 +1109,6 @@ static void end_batch_impl(const wgpu::CommandEncoder& cmd, bool advanceFrame) {
|
|||||||
gx::drop_pending_frame_interpolation_uniforms();
|
gx::drop_pending_frame_interpolation_uniforms();
|
||||||
}
|
}
|
||||||
g_uniforms.append_zeroes(gx::MaxUniformSize); // Pad the end of the buffer
|
g_uniforms.append_zeroes(gx::MaxUniformSize); // Pad the end of the buffer
|
||||||
const auto used = staging_usage();
|
|
||||||
for (unsigned i = 0; i < used.size(); ++i) g_stagingHighWater[i] = std::max(g_stagingHighWater[i], used[i]);
|
|
||||||
uint64_t bufferOffset = 0;
|
uint64_t bufferOffset = 0;
|
||||||
const auto writeBuffer = [&](ByteBuffer& buf, wgpu::Buffer& out, uint64_t size, std::string_view label) {
|
const auto writeBuffer = [&](ByteBuffer& buf, wgpu::Buffer& out, uint64_t size, std::string_view label) {
|
||||||
const auto writeSize = buf.size(); // Only need to copy this many bytes
|
const auto writeSize = buf.size(); // Only need to copy this many bytes
|
||||||
@@ -1183,7 +1120,7 @@ static void end_batch_impl(const wgpu::CommandEncoder& cmd, bool advanceFrame) {
|
|||||||
return writeSize;
|
return writeSize;
|
||||||
};
|
};
|
||||||
g_stagingBuffers[currentStagingBuffer].Unmap();
|
g_stagingBuffers[currentStagingBuffer].Unmap();
|
||||||
s_mappingState.reset();
|
s_mappingState.store(BufferMapState::Unmapped, std::memory_order_release);
|
||||||
g_stats.drawCallCount = g_drawCallCount;
|
g_stats.drawCallCount = g_drawCallCount;
|
||||||
g_stats.mergedDrawCallCount = g_mergedDrawCallCount;
|
g_stats.mergedDrawCallCount = g_mergedDrawCallCount;
|
||||||
g_stats.lastVertSize = writeBuffer(g_verts, g_vertexBuffer, VertexBufferSize, "Vertex");
|
g_stats.lastVertSize = writeBuffer(g_verts, g_vertexBuffer, VertexBufferSize, "Vertex");
|
||||||
@@ -1226,63 +1163,6 @@ void end_frame(const wgpu::CommandEncoder& cmd) { end_batch_impl(cmd, true); }
|
|||||||
|
|
||||||
void end_batch(const wgpu::CommandEncoder& cmd) { end_batch_impl(cmd, false); }
|
void end_batch(const wgpu::CommandEncoder& cmd) { end_batch_impl(cmd, false); }
|
||||||
|
|
||||||
void split_staging_batch() {
|
|
||||||
// Never called under the decoder's renderer lock: the worker needs that lock
|
|
||||||
// to reach DONE. FIFO admission yields its unconsumed command first.
|
|
||||||
aurora::wait_for_frame_worker();
|
|
||||||
std::lock_guard gpuLock(aurora::renderer_gpu_mutex());
|
|
||||||
if (!has_current_render_pass())
|
|
||||||
throw StagingCapacityError("Cannot split staging outside an active render pass");
|
|
||||||
gx::mark_frame_interpolation_replay_unsafe();
|
|
||||||
const bool offscreen = g_inOffscreen;
|
|
||||||
const auto viewport = g_cachedViewport;
|
|
||||||
const auto scissor = g_cachedScissor;
|
|
||||||
const auto renderViewport = gx::g_gxState.renderViewport;
|
|
||||||
const auto renderScissor = gx::g_gxState.renderScissor;
|
|
||||||
const auto& active = g_renderPasses[g_currentRenderPass];
|
|
||||||
RenderPass continuation{
|
|
||||||
.colorView = active.colorView, .resolveView = active.resolveView,
|
|
||||||
.depthView = active.depthView, .copySourceTexture = active.copySourceTexture,
|
|
||||||
.copySourceView = active.copySourceView, .copySourceDepthView = active.copySourceDepthView,
|
|
||||||
.targetSize = active.targetSize, .msaaSamples = active.msaaSamples,
|
|
||||||
.clearColor = false, .clearDepth = false,
|
|
||||||
.requireReadyPipelines = active.requireReadyPipelines || offscreen,
|
|
||||||
};
|
|
||||||
auto suspended = std::move(g_suspendedEfbPass);
|
|
||||||
g_suspendedEfbPass.reset();
|
|
||||||
std::array<std::vector<uint8_t>, 4> retained;
|
|
||||||
std::array<ByteBuffer*, 4> buffers{&g_verts, &g_uniforms, &g_indices, &g_storage};
|
|
||||||
if (suspended) {
|
|
||||||
for (unsigned i = 0; i < buffers.size(); ++i) {
|
|
||||||
if (g_suspendedEfbBytes[i])
|
|
||||||
retained[i].assign(buffers[i]->data(), buffers[i]->data() + g_suspendedEfbBytes[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
auto encoder = g_device.CreateCommandEncoder();
|
|
||||||
end_batch(encoder);
|
|
||||||
render(encoder);
|
|
||||||
aurora::submit_staging_commands(encoder.Finish());
|
|
||||||
after_submit();
|
|
||||||
if (!begin_frame_impl(false, true))
|
|
||||||
throw StagingCapacityError("Staging remap failed after capacity submission");
|
|
||||||
recycle_render_passes(g_renderPasses);
|
|
||||||
push_render_pass(std::move(continuation));
|
|
||||||
g_currentRenderPass = 0;
|
|
||||||
g_suspendedEfbPass = std::move(suspended);
|
|
||||||
for (unsigned i = 0; i < buffers.size(); ++i) {
|
|
||||||
if (!retained[i].empty()) buffers[i]->append(retained[i].data(), retained[i].size());
|
|
||||||
}
|
|
||||||
g_inOffscreen = offscreen;
|
|
||||||
g_cachedViewport = viewport;
|
|
||||||
g_cachedScissor = scissor;
|
|
||||||
gx::g_gxState.renderViewport = renderViewport;
|
|
||||||
gx::g_gxState.renderScissor = renderScissor;
|
|
||||||
gx::g_gxState.stateDirty = true;
|
|
||||||
push_command(CommandType::SetViewport, Command::Data{.setViewport = viewport});
|
|
||||||
push_command(CommandType::SetScissor, Command::Data{.setScissor = scissor});
|
|
||||||
++g_stagingSplitCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t current_frame() noexcept { return g_frameIndex; }
|
uint32_t current_frame() noexcept { return g_frameIndex; }
|
||||||
|
|
||||||
// The only place that erases from g_cachedBindGroups, whose handles the frame being encoded still
|
// The only place that erases from g_cachedBindGroups, whose handles the frame being encoded still
|
||||||
@@ -1315,10 +1195,10 @@ static const char* render_pass_label(u32 index) noexcept {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void render_pass_impl(const wgpu::RenderPassEncoder& pass, const std::vector<RenderPass>& passes, u32 idx,
|
static void render_pass_impl(const wgpu::RenderPassEncoder& pass, const std::vector<RenderPass>& passes, u32 idx,
|
||||||
int32_t interpolatedFrame, DebugFrameData& debugFrame);
|
int32_t interpolatedFrame);
|
||||||
|
|
||||||
static void render_impl(std::vector<RenderPass>& renderPasses, wgpu::CommandEncoder& cmd, int32_t interpolatedFrame,
|
static void render_impl(std::vector<RenderPass>& renderPasses, wgpu::CommandEncoder& cmd, int32_t interpolatedFrame,
|
||||||
bool finalize, DebugFrameData& debugFrame, const depth_peek::FrameMapping& depthMapping) {
|
bool finalize) {
|
||||||
ZoneScoped;
|
ZoneScoped;
|
||||||
// Palette conversions, MSAA resolves and EFB copies depend on sealed frame state, not on the
|
// Palette conversions, MSAA resolves and EFB copies depend on sealed frame state, not on the
|
||||||
// interpolation weight, so encode them on the native render and let replay slots sample them.
|
// interpolation weight, so encode them on the native render and let replay slots sample them.
|
||||||
@@ -1368,11 +1248,11 @@ static void render_impl(std::vector<RenderPass>& renderPasses, wgpu::CommandEnco
|
|||||||
};
|
};
|
||||||
|
|
||||||
auto pass = cmd.BeginRenderPass(&renderPassDescriptor);
|
auto pass = cmd.BeginRenderPass(&renderPassDescriptor);
|
||||||
render_pass_impl(pass, renderPasses, i, interpolatedFrame, debugFrame);
|
render_pass_impl(pass, renderPasses, i, interpolatedFrame);
|
||||||
pass.End();
|
pass.End();
|
||||||
|
|
||||||
if (finalize && i == renderPasses.size() - 1) {
|
if (finalize && i == renderPasses.size() - 1) {
|
||||||
depth_peek::encode_frame_snapshot(cmd, passInfo.copySourceDepthView, passInfo.targetSize, passInfo.msaaSamples, depthMapping);
|
depth_peek::encode_frame_snapshot(cmd, passInfo.copySourceDepthView, passInfo.targetSize, passInfo.msaaSamples);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (passInfo.resolveTarget) {
|
if (passInfo.resolveTarget) {
|
||||||
@@ -1446,21 +1326,20 @@ static void render_impl(std::vector<RenderPass>& renderPasses, wgpu::CommandEnco
|
|||||||
}
|
}
|
||||||
|
|
||||||
#if defined(AURORA_GFX_DEBUG_GROUPS)
|
#if defined(AURORA_GFX_DEBUG_GROUPS)
|
||||||
if (finalize && !debugFrame.groups.empty()) {
|
if (finalize && !g_debugGroupStack.empty()) {
|
||||||
for (auto it = debugFrame.groups.rbegin(); it != debugFrame.groups.rend(); ++it) {
|
for (auto it = g_debugGroupStack.rbegin(); it != g_debugGroupStack.rend(); ++it) {
|
||||||
Log.warn("Debug group was not popped at end of frame: {}", *it);
|
Log.warn("Debug group was not popped at end of frame: {}", *it);
|
||||||
}
|
}
|
||||||
debugFrame.groups.clear();
|
g_debugGroupStack.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (finalize && debugFrame.markers.size() > 0) {
|
if (finalize && g_debugMarkers.size() > 0) {
|
||||||
debugFrame.markers.clear();
|
g_debugMarkers.clear();
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void seal_frame(SealedFrame& out) noexcept {
|
void seal_frame(SealedFrame& out) noexcept {
|
||||||
out.data().depthMapping = depth_peek::capture_frame_mapping();
|
|
||||||
ZoneScoped;
|
ZoneScoped;
|
||||||
// The encode that could still have been holding these has completed: the
|
// The encode that could still have been holding these has completed: the
|
||||||
// producer joins the worker's DONE phase before it seals another frame.
|
// producer joins the worker's DONE phase before it seals another frame.
|
||||||
@@ -1470,24 +1349,15 @@ void seal_frame(SealedFrame& out) noexcept {
|
|||||||
// capacity included, back to the producer.
|
// capacity included, back to the producer.
|
||||||
recycle_render_passes(passes);
|
recycle_render_passes(passes);
|
||||||
passes.swap(g_renderPasses);
|
passes.swap(g_renderPasses);
|
||||||
#ifdef AURORA_GFX_DEBUG_GROUPS
|
|
||||||
// Marker indices and unmatched-group warnings belong to these detached passes.
|
|
||||||
// The next producer frame must not modify strings still read by this encoder.
|
|
||||||
auto& debug = out.data().debug;
|
|
||||||
debug.groups.clear();
|
|
||||||
debug.markers.clear();
|
|
||||||
debug.groups.swap(g_debugFrame.groups);
|
|
||||||
debug.markers.swap(g_debugFrame.markers);
|
|
||||||
#endif
|
|
||||||
g_currentRenderPass = UINT32_MAX;
|
g_currentRenderPass = UINT32_MAX;
|
||||||
}
|
}
|
||||||
|
|
||||||
void render(SealedFrame& frame, wgpu::CommandEncoder& cmd, int32_t interpolatedFrame, bool finalize) {
|
void render(SealedFrame& frame, wgpu::CommandEncoder& cmd, int32_t interpolatedFrame, bool finalize) {
|
||||||
render_impl(frame.data().passes, cmd, interpolatedFrame, finalize, frame.data().debug, frame.data().depthMapping);
|
render_impl(frame.data().passes, cmd, interpolatedFrame, finalize);
|
||||||
}
|
}
|
||||||
|
|
||||||
void render(wgpu::CommandEncoder& cmd, int32_t interpolatedFrame, bool finalize) {
|
void render(wgpu::CommandEncoder& cmd, int32_t interpolatedFrame, bool finalize) {
|
||||||
render_impl(g_renderPasses, cmd, interpolatedFrame, finalize, g_debugFrame, depth_peek::capture_frame_mapping());
|
render_impl(g_renderPasses, cmd, interpolatedFrame, finalize);
|
||||||
if (finalize) {
|
if (finalize) {
|
||||||
g_currentRenderPass = UINT32_MAX;
|
g_currentRenderPass = UINT32_MAX;
|
||||||
expire_bind_group_cache();
|
expire_bind_group_cache();
|
||||||
@@ -1505,7 +1375,7 @@ void after_submit() noexcept {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void render_pass_impl(const wgpu::RenderPassEncoder& pass, const std::vector<RenderPass>& renderPasses, u32 idx,
|
static void render_pass_impl(const wgpu::RenderPassEncoder& pass, const std::vector<RenderPass>& renderPasses, u32 idx,
|
||||||
int32_t interpolatedFrame, DebugFrameData& debugFrame) {
|
int32_t interpolatedFrame) {
|
||||||
// Per-invocation, not per-process: two encoders can be recording at once.
|
// Per-invocation, not per-process: two encoders can be recording at once.
|
||||||
gx::DrawEncodeState encodeState{};
|
gx::DrawEncodeState encodeState{};
|
||||||
encodeState.boundTextureBindGroup = gx::g_emptyTextureBindGroup.Get();
|
encodeState.boundTextureBindGroup = gx::g_emptyTextureBindGroup.Get();
|
||||||
@@ -1539,19 +1409,10 @@ static void render_pass_impl(const wgpu::RenderPassEncoder& pass, const std::vec
|
|||||||
switch (cmd.type) {
|
switch (cmd.type) {
|
||||||
case CommandType::SetViewport: {
|
case CommandType::SetViewport: {
|
||||||
const auto& vp = cmd.data.setViewport;
|
const auto& vp = cmd.data.setViewport;
|
||||||
// WebGPU requires 0 <= minDepth <= maxDepth <= 1. vp.znear/vp.zfar are in GX's own distance
|
// WebGPU requires 0 <= minDepth <= maxDepth <= 1, and the guest's (near, far) order is already
|
||||||
// terms (0 = near); under UseReversedZ the host depth-buffer storage direction is flipped
|
// reproduced in clip space. Passing the raw swapped pair diverged per backend in release builds.
|
||||||
// (near = 1, far = 0), so this range has to be remapped through 1-x the same way the
|
const float minDepth = std::clamp(std::min(vp.znear, vp.zfar), 0.0f, 1.0f);
|
||||||
// projection matrix, depth compare function, and clear value all are - a plain min/max clamp
|
const float maxDepth = std::clamp(std::max(vp.znear, vp.zfar), 0.0f, 1.0f);
|
||||||
// (the previous code here) maps a *restricted* range (e.g. a viewport deliberately narrowed
|
|
||||||
// to force something to draw "in front of everything") to the wrong end of the buffer: what
|
|
||||||
// should land near the near-storage-extreme (1.0) instead lands near the far-storage-extreme
|
|
||||||
// (0.0), so anything else drawn afterward at its true depth wins the compare test and the
|
|
||||||
// "in front" geometry silently vanishes. A full [0,1] viewport is unaffected either way,
|
|
||||||
// which is why this only broke specific elements, not the whole scene. Matches upstream
|
|
||||||
// aurora's apply_viewport (lib/gfx/encoding.cpp) exactly.
|
|
||||||
const float minDepth = gx::UseReversedZ ? 1.0f - vp.zfar : vp.znear;
|
|
||||||
const float maxDepth = gx::UseReversedZ ? 1.0f - vp.znear : vp.zfar;
|
|
||||||
pass.SetViewport(vp.left, vp.top, vp.width, vp.height, minDepth, maxDepth);
|
pass.SetViewport(vp.left, vp.top, vp.width, vp.height, minDepth, maxDepth);
|
||||||
} break;
|
} break;
|
||||||
case CommandType::SetScissor: {
|
case CommandType::SetScissor: {
|
||||||
@@ -1585,7 +1446,7 @@ static void render_pass_impl(const wgpu::RenderPassEncoder& pass, const std::vec
|
|||||||
} break;
|
} break;
|
||||||
case CommandType::DebugMarker: {
|
case CommandType::DebugMarker: {
|
||||||
#if defined(AURORA_GFX_DEBUG_GROUPS)
|
#if defined(AURORA_GFX_DEBUG_GROUPS)
|
||||||
pass.InsertDebugMarker(wgpu::StringView(debugFrame.markers[cmd.data.debugMarkerIndex]));
|
pass.InsertDebugMarker(wgpu::StringView(g_debugMarkers[cmd.data.debugMarkerIndex]));
|
||||||
#endif
|
#endif
|
||||||
} break;
|
} break;
|
||||||
}
|
}
|
||||||
@@ -1738,8 +1599,8 @@ uint32_t align_uniform(uint32_t value) { return AURORA_ALIGN(value, g_cachedLimi
|
|||||||
|
|
||||||
void insert_debug_marker(std::string label) {
|
void insert_debug_marker(std::string label) {
|
||||||
#if defined(AURORA_GFX_DEBUG_GROUPS)
|
#if defined(AURORA_GFX_DEBUG_GROUPS)
|
||||||
auto idx = g_debugFrame.markers.size();
|
auto idx = g_debugMarkers.size();
|
||||||
g_debugFrame.markers.emplace_back(std::move(label));
|
g_debugMarkers.emplace_back(std::move(label));
|
||||||
push_command(CommandType::DebugMarker, {.debugMarkerIndex = idx});
|
push_command(CommandType::DebugMarker, {.debugMarkerIndex = idx});
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
@@ -1748,22 +1609,22 @@ void insert_debug_marker(std::string label) {
|
|||||||
|
|
||||||
void aurora::gfx::push_debug_group(std::string label) {
|
void aurora::gfx::push_debug_group(std::string label) {
|
||||||
#if defined(AURORA_GFX_DEBUG_GROUPS)
|
#if defined(AURORA_GFX_DEBUG_GROUPS)
|
||||||
g_debugFrame.groups.push_back(std::move(label));
|
g_debugGroupStack.push_back(std::move(label));
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
void aurora_push_debug_group(const char* label) {
|
void aurora_push_debug_group(const char* label) {
|
||||||
#ifdef AURORA_GFX_DEBUG_GROUPS
|
#ifdef AURORA_GFX_DEBUG_GROUPS
|
||||||
aurora::gfx::g_debugFrame.groups.emplace_back(label);
|
aurora::gfx::g_debugGroupStack.emplace_back(label);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
void aurora_pop_debug_group() {
|
void aurora_pop_debug_group() {
|
||||||
#ifdef AURORA_GFX_DEBUG_GROUPS
|
#ifdef AURORA_GFX_DEBUG_GROUPS
|
||||||
if (aurora::gfx::g_debugFrame.groups.empty()) {
|
if (aurora::gfx::g_debugGroupStack.empty()) {
|
||||||
aurora::gfx::Log.error("Debug group stack underflowed!");
|
aurora::gfx::Log.error("Debug group stack underflowed!");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
aurora::gfx::g_debugFrame.groups.pop_back();
|
aurora::gfx::g_debugGroupStack.pop_back();
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include "staging_capacity.hpp"
|
|
||||||
|
|
||||||
#include "../internal.hpp"
|
#include "../internal.hpp"
|
||||||
#include "../webgpu/gpu.hpp"
|
#include "../webgpu/gpu.hpp"
|
||||||
@@ -395,20 +394,6 @@ wgpu::BindGroup& find_bind_group(BindGroupRef id);
|
|||||||
wgpu::Sampler& sampler_ref(const wgpu::SamplerDescriptor& descriptor);
|
wgpu::Sampler& sampler_ref(const wgpu::SamplerDescriptor& descriptor);
|
||||||
|
|
||||||
uint32_t align_uniform(uint32_t value);
|
uint32_t align_uniform(uint32_t value);
|
||||||
uint64_t staging_uniform_bytes(uint64_t bytes);
|
|
||||||
uint64_t staging_storage_bytes(uint64_t bytes);
|
|
||||||
// Admission does not allocate. A false result requires a producer-side split.
|
|
||||||
// Oversized operations fail before mutating the current draw/pass.
|
|
||||||
bool staging_has_space(const StagingSizes& demand);
|
|
||||||
void ensure_staging_space(const StagingSizes& demand);
|
|
||||||
void split_staging_batch();
|
|
||||||
uint64_t staging_epoch() noexcept;
|
|
||||||
StagingSizes staging_usage() noexcept;
|
|
||||||
StagingSizes staging_high_water() noexcept;
|
|
||||||
uint64_t staging_split_count() noexcept;
|
|
||||||
// Internal integration-test seam: never increases the physical allocation.
|
|
||||||
void set_staging_capacity_limits_for_testing(const StagingSizes& limits);
|
|
||||||
|
|
||||||
|
|
||||||
Vec2<uint32_t> get_render_target_size() noexcept;
|
Vec2<uint32_t> get_render_target_size() noexcept;
|
||||||
// Same value as get_render_target_size() outside a render pass, but never
|
// Same value as get_render_target_size() outside a render pass, but never
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ struct Params {
|
|||||||
|
|
||||||
constexpr std::string_view ReversedZBody = R"(
|
constexpr std::string_view ReversedZBody = R"(
|
||||||
fn gx_z24(depth: f32) -> u32 {
|
fn gx_z24(depth: f32) -> u32 {
|
||||||
return min(u32(clamp(1.0 - depth, 0.0, 1.0) * 16777215.0 + 0.5), 0x00ffffffu);
|
return min(u32(clamp(depth, 0.0, 1.0) * 16777216.0), 0x00ffffffu);
|
||||||
}
|
}
|
||||||
)"sv;
|
)"sv;
|
||||||
|
|
||||||
@@ -196,8 +196,7 @@ wgpu::BindGroupLayout create_bind_group_layout(const char* label) {
|
|||||||
return g_device.CreateBindGroupLayout(&descriptor);
|
return g_device.CreateBindGroupLayout(&descriptor);
|
||||||
}
|
}
|
||||||
|
|
||||||
Params make_params(wgpu::Extent3D sourceSize, const FrameMapping& mapping) noexcept {
|
Params make_params(wgpu::Extent3D sourceSize, Vec2<uint32_t> dstSize) noexcept {
|
||||||
const auto dstSize = mapping.logicalSize;
|
|
||||||
Params params{
|
Params params{
|
||||||
.dstWidth = dstSize.x,
|
.dstWidth = dstSize.x,
|
||||||
.dstHeight = dstSize.y,
|
.dstHeight = dstSize.y,
|
||||||
@@ -205,17 +204,16 @@ Params make_params(wgpu::Extent3D sourceSize, const FrameMapping& mapping) noexc
|
|||||||
.srcHeight = sourceSize.height,
|
.srcHeight = sourceSize.height,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (mapping.viewportPolicy == AURORA_VIEWPORT_NATIVE) {
|
if (gx::g_gxState.viewportPolicy == AURORA_VIEWPORT_NATIVE) {
|
||||||
return params;
|
return params;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto logicalSize = mapping.logicalSize;
|
const auto logicalSize = vi::configured_fb_size();
|
||||||
if (logicalSize.x == 0 || logicalSize.y == 0 || sourceSize.width == 0 || sourceSize.height == 0) {
|
if (logicalSize.x == 0 || logicalSize.y == 0 || sourceSize.width == 0 || sourceSize.height == 0) {
|
||||||
return params;
|
return params;
|
||||||
}
|
}
|
||||||
|
|
||||||
const bool stretch = mapping.viewportPolicy == AURORA_VIEWPORT_STRETCH ||
|
const bool stretch = gx::g_gxState.viewportPolicy == AURORA_VIEWPORT_STRETCH;
|
||||||
mapping.viewportPolicy == AURORA_VIEWPORT_16_9;
|
|
||||||
const float scaleX = static_cast<float>(sourceSize.width) / static_cast<float>(logicalSize.x);
|
const float scaleX = static_cast<float>(sourceSize.width) / static_cast<float>(logicalSize.x);
|
||||||
const float scaleY = static_cast<float>(sourceSize.height) / static_cast<float>(logicalSize.y);
|
const float scaleY = static_cast<float>(sourceSize.height) / static_cast<float>(logicalSize.y);
|
||||||
const float scale = std::min(scaleX, scaleY);
|
const float scale = std::min(scaleX, scaleY);
|
||||||
@@ -338,12 +336,8 @@ void poll() noexcept {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
FrameMapping capture_frame_mapping() noexcept {
|
|
||||||
return {vi::configured_fb_size(), gx::g_gxState.viewportPolicy};
|
|
||||||
}
|
|
||||||
|
|
||||||
void encode_frame_snapshot(const wgpu::CommandEncoder& cmd, const wgpu::TextureView& depthView,
|
void encode_frame_snapshot(const wgpu::CommandEncoder& cmd, const wgpu::TextureView& depthView,
|
||||||
wgpu::Extent3D sourceSize, uint32_t msaaSamples, const FrameMapping& mapping) noexcept {
|
wgpu::Extent3D sourceSize, uint32_t msaaSamples) noexcept {
|
||||||
ZoneScoped;
|
ZoneScoped;
|
||||||
const auto now = Clock::now();
|
const auto now = Clock::now();
|
||||||
{
|
{
|
||||||
@@ -355,7 +349,7 @@ void encode_frame_snapshot(const wgpu::CommandEncoder& cmd, const wgpu::TextureV
|
|||||||
g_nextSnapshotTime = now + SnapshotInterval;
|
g_nextSnapshotTime = now + SnapshotInterval;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto dstSize = mapping.logicalSize;
|
const auto dstSize = vi::configured_fb_size();
|
||||||
if (!depthView || dstSize.x == 0 || dstSize.y == 0 || sourceSize.width == 0 || sourceSize.height == 0) {
|
if (!depthView || dstSize.x == 0 || dstSize.y == 0 || sourceSize.width == 0 || sourceSize.height == 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -363,7 +357,7 @@ void encode_frame_snapshot(const wgpu::CommandEncoder& cmd, const wgpu::TextureV
|
|||||||
Log.fatal("Depth Peek from multisampled EFB targets is not supported");
|
Log.fatal("Depth Peek from multisampled EFB targets is not supported");
|
||||||
}
|
}
|
||||||
|
|
||||||
const Params params = make_params(sourceSize, mapping);
|
const Params params = make_params(sourceSize, dstSize);
|
||||||
wgpu::Buffer storageBuffer;
|
wgpu::Buffer storageBuffer;
|
||||||
wgpu::Buffer readbackBuffer;
|
wgpu::Buffer readbackBuffer;
|
||||||
wgpu::Buffer paramsBuffer;
|
wgpu::Buffer paramsBuffer;
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "common.hpp"
|
#include "common.hpp"
|
||||||
#include <dolphin/gx/GXAurora.h>
|
|
||||||
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
@@ -14,15 +13,8 @@ void request_snapshot() noexcept;
|
|||||||
bool read_latest(uint16_t x, uint16_t y, uint32_t& z) noexcept;
|
bool read_latest(uint16_t x, uint16_t y, uint32_t& z) noexcept;
|
||||||
void poll() noexcept;
|
void poll() noexcept;
|
||||||
|
|
||||||
// Captured before SEALED; the producer may configure the next frame during encode.
|
|
||||||
struct FrameMapping {
|
|
||||||
Vec2<uint32_t> logicalSize{};
|
|
||||||
AuroraViewportPolicy viewportPolicy = AURORA_VIEWPORT_FIT;
|
|
||||||
};
|
|
||||||
FrameMapping capture_frame_mapping() noexcept;
|
|
||||||
|
|
||||||
void encode_frame_snapshot(const wgpu::CommandEncoder& cmd, const wgpu::TextureView& depthView,
|
void encode_frame_snapshot(const wgpu::CommandEncoder& cmd, const wgpu::TextureView& depthView,
|
||||||
wgpu::Extent3D sourceSize, uint32_t msaaSamples, const FrameMapping& mapping) noexcept;
|
wgpu::Extent3D sourceSize, uint32_t msaaSamples) noexcept;
|
||||||
void after_submit() noexcept;
|
void after_submit() noexcept;
|
||||||
|
|
||||||
namespace testing {
|
namespace testing {
|
||||||
|
|||||||
@@ -8,9 +8,7 @@
|
|||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <memory>
|
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <string>
|
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
@@ -29,7 +27,7 @@ using webgpu::g_instance;
|
|||||||
constexpr size_t kAsyncReadbackMaxBytes = 256;
|
constexpr size_t kAsyncReadbackMaxBytes = 256;
|
||||||
// Each destination keeps its readback buffer forever. Only a handful are expected, and the cap
|
// Each destination keeps its readback buffer forever. Only a handful are expected, and the cap
|
||||||
// stops an unexpected pattern of one-shot destinations from leaking GPU buffers.
|
// stops an unexpected pattern of one-shot destinations from leaking GPU buffers.
|
||||||
constexpr size_t kMaxAsyncSlots = MaxAsyncReadbackSlots;
|
constexpr size_t kMaxAsyncSlots = 32;
|
||||||
|
|
||||||
struct PendingCopy {
|
struct PendingCopy {
|
||||||
void* dest = nullptr;
|
void* dest = nullptr;
|
||||||
@@ -39,7 +37,6 @@ struct PendingCopy {
|
|||||||
TextureHandle texture;
|
TextureHandle texture;
|
||||||
TextureHandle nativeTexture;
|
TextureHandle nativeTexture;
|
||||||
Range nativeBlitUniform;
|
Range nativeBlitUniform;
|
||||||
uint64_t nativeUniformEpoch = 0;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Download {
|
struct Download {
|
||||||
@@ -84,7 +81,6 @@ std::vector<PendingCopy> g_asyncSealed;
|
|||||||
std::mutex g_asyncMutex;
|
std::mutex g_asyncMutex;
|
||||||
std::unordered_map<void*, AsyncSlot> g_asyncSlots;
|
std::unordered_map<void*, AsyncSlot> g_asyncSlots;
|
||||||
uint32_t g_asyncMapsInFlight = 0;
|
uint32_t g_asyncMapsInFlight = 0;
|
||||||
uint64_t g_asyncGeneration = 1;
|
|
||||||
|
|
||||||
uint32_t align_to(uint32_t value, uint32_t alignment) noexcept { return (value + alignment - 1) & ~(alignment - 1); }
|
uint32_t align_to(uint32_t value, uint32_t alignment) noexcept { return (value + alignment - 1) & ~(alignment - 1); }
|
||||||
|
|
||||||
@@ -94,10 +90,10 @@ void ensure_native_texture(PendingCopy& pending, TextureHandle* cache = nullptr)
|
|||||||
if (pending.texture->size.width == pending.width && pending.texture->size.height == pending.height) {
|
if (pending.texture->size.width == pending.width && pending.texture->size.height == pending.height) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (pending.nativeTexture && pending.nativeUniformEpoch == staging_epoch()) return;
|
|
||||||
if (pending.nativeTexture) {
|
if (pending.nativeTexture) {
|
||||||
// Keep the texture; its old staging range belongs to a submitted batch.
|
return;
|
||||||
} else if (cache != nullptr && *cache && (*cache)->size.width == pending.width &&
|
}
|
||||||
|
if (cache != nullptr && *cache && (*cache)->size.width == pending.width &&
|
||||||
(*cache)->size.height == pending.height) {
|
(*cache)->size.height == pending.height) {
|
||||||
pending.nativeTexture = *cache;
|
pending.nativeTexture = *cache;
|
||||||
} else {
|
} else {
|
||||||
@@ -106,12 +102,10 @@ void ensure_native_texture(PendingCopy& pending, TextureHandle* cache = nullptr)
|
|||||||
*cache = pending.nativeTexture;
|
*cache = pending.nativeTexture;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// The shared blit shader clamps Y to flags.z/w; preserve the full source.
|
|
||||||
const std::array nativeBlitUniform{
|
const std::array nativeBlitUniform{
|
||||||
0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 64.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f,
|
0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 64.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f,
|
||||||
};
|
};
|
||||||
pending.nativeBlitUniform = push_uniform(nativeBlitUniform);
|
pending.nativeBlitUniform = push_uniform(nativeBlitUniform);
|
||||||
pending.nativeUniformEpoch = staging_epoch();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void encode_native_blit(const wgpu::CommandEncoder& encoder, const PendingCopy& pending) noexcept {
|
void encode_native_blit(const wgpu::CommandEncoder& encoder, const PendingCopy& pending) noexcept {
|
||||||
@@ -131,17 +125,16 @@ HostPixelOrder texture_pixel_order(const TextureHandle& texture) noexcept {
|
|||||||
return texture->format == wgpu::TextureFormat::BGRA8Unorm ? HostPixelOrder::BGRA : HostPixelOrder::RGBA;
|
return texture->format == wgpu::TextureFormat::BGRA8Unorm ? HostPixelOrder::BGRA : HostPixelOrder::RGBA;
|
||||||
}
|
}
|
||||||
|
|
||||||
void complete_async_slot(void* dest, uint64_t generation, wgpu::MapAsyncStatus status,
|
void complete_async_slot(void* dest, wgpu::MapAsyncStatus status, wgpu::StringView message) noexcept {
|
||||||
wgpu::StringView message) noexcept {
|
|
||||||
std::lock_guard lock{g_asyncMutex};
|
std::lock_guard lock{g_asyncMutex};
|
||||||
if (generation != g_asyncGeneration) return;
|
if (g_asyncMapsInFlight > 0) {
|
||||||
|
--g_asyncMapsInFlight;
|
||||||
|
}
|
||||||
const auto it = g_asyncSlots.find(dest);
|
const auto it = g_asyncSlots.find(dest);
|
||||||
if (it == g_asyncSlots.end()) {
|
if (it == g_asyncSlots.end()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
auto& slot = it->second;
|
auto& slot = it->second;
|
||||||
if (slot.state != AsyncState::MapPending) return;
|
|
||||||
if (g_asyncMapsInFlight > 0) --g_asyncMapsInFlight;
|
|
||||||
if (status == wgpu::MapAsyncStatus::Success) {
|
if (status == wgpu::MapAsyncStatus::Success) {
|
||||||
const auto* pixels = static_cast<const uint8_t*>(slot.buffer.GetConstMappedRange(0, slot.bufferSize));
|
const auto* pixels = static_cast<const uint8_t*>(slot.buffer.GetConstMappedRange(0, slot.bufferSize));
|
||||||
if (pixels != nullptr) {
|
if (pixels != nullptr) {
|
||||||
@@ -234,14 +227,7 @@ bool has_pending(void* dest) noexcept {
|
|||||||
[dest](const Download& download) { return download.copy.dest == dest; });
|
[dest](const Download& download) { return download.copy.dest == dest; });
|
||||||
}
|
}
|
||||||
|
|
||||||
bool prepare_downloads(void* dest) {
|
bool prepare_downloads(void* dest) noexcept {
|
||||||
uint64_t copies = 0;
|
|
||||||
for (const auto& pending : g_pending) {
|
|
||||||
if (dest != nullptr && pending.dest != dest) continue;
|
|
||||||
if (pending.texture->size.width != pending.width || pending.texture->size.height != pending.height) ++copies;
|
|
||||||
}
|
|
||||||
// Reserve all copies, even already-prepared ones: a split retires their ranges.
|
|
||||||
ensure_staging_space({0, copies * staging_uniform_bytes(48), 0, 0});
|
|
||||||
bool found = false;
|
bool found = false;
|
||||||
for (auto& pending : g_pending) {
|
for (auto& pending : g_pending) {
|
||||||
if (dest != nullptr && pending.dest != dest) continue;
|
if (dest != nullptr && pending.dest != dest) continue;
|
||||||
@@ -304,35 +290,15 @@ void encode_downloads(const wgpu::CommandEncoder& encoder, void* dest) noexcept
|
|||||||
bool complete_downloads() noexcept {
|
bool complete_downloads() noexcept {
|
||||||
bool success = true;
|
bool success = true;
|
||||||
for (auto& download : g_downloads) {
|
for (auto& download : g_downloads) {
|
||||||
// WaitAny may time out before Dawn delivers cancellation. The callback must
|
wgpu::MapAsyncStatus mapStatus = wgpu::MapAsyncStatus::CallbackCancelled;
|
||||||
// own its result rather than retaining references to this stack frame.
|
wgpu::StringView mapMessage{};
|
||||||
struct MapResult {
|
|
||||||
std::mutex mutex;
|
|
||||||
wgpu::MapAsyncStatus status = wgpu::MapAsyncStatus::CallbackCancelled;
|
|
||||||
std::string message;
|
|
||||||
};
|
|
||||||
const auto result = std::make_shared<MapResult>();
|
|
||||||
const auto future =
|
const auto future =
|
||||||
download.buffer.MapAsync(wgpu::MapMode::Read, 0, download.bufferSize, wgpu::CallbackMode::WaitAnyOnly,
|
download.buffer.MapAsync(wgpu::MapMode::Read, 0, download.bufferSize, wgpu::CallbackMode::WaitAnyOnly,
|
||||||
[result](wgpu::MapAsyncStatus status, wgpu::StringView message) {
|
[&mapStatus, &mapMessage](wgpu::MapAsyncStatus status, wgpu::StringView message) {
|
||||||
std::lock_guard lock{result->mutex};
|
mapStatus = status;
|
||||||
result->status = status;
|
mapMessage = message;
|
||||||
if (message.data != nullptr) {
|
|
||||||
size_t length = 0;
|
|
||||||
while (length < 512 && length < message.length && message.data[length] != '\0') {
|
|
||||||
++length;
|
|
||||||
}
|
|
||||||
result->message.assign(message.data, length);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
const auto waitStatus = g_instance.WaitAny(future, 5000000000);
|
const auto waitStatus = g_instance.WaitAny(future, 5000000000);
|
||||||
wgpu::MapAsyncStatus mapStatus;
|
|
||||||
std::string mapMessage;
|
|
||||||
{
|
|
||||||
std::lock_guard lock{result->mutex};
|
|
||||||
mapStatus = result->status;
|
|
||||||
mapMessage = result->message;
|
|
||||||
}
|
|
||||||
if (waitStatus != wgpu::WaitStatus::Success || mapStatus != wgpu::MapAsyncStatus::Success) {
|
if (waitStatus != wgpu::WaitStatus::Success || mapStatus != wgpu::MapAsyncStatus::Success) {
|
||||||
Log.error("EFB RAM readback failed wait={} map={} message={}", magic_enum::enum_name(waitStatus),
|
Log.error("EFB RAM readback failed wait={} map={} message={}", magic_enum::enum_name(waitStatus),
|
||||||
magic_enum::enum_name(mapStatus), mapMessage);
|
magic_enum::enum_name(mapStatus), mapMessage);
|
||||||
@@ -446,7 +412,6 @@ void after_submit() noexcept {
|
|||||||
void* dest;
|
void* dest;
|
||||||
wgpu::Buffer buffer;
|
wgpu::Buffer buffer;
|
||||||
uint64_t bufferSize;
|
uint64_t bufferSize;
|
||||||
uint64_t generation;
|
|
||||||
};
|
};
|
||||||
std::vector<PendingMap> pendingMaps;
|
std::vector<PendingMap> pendingMaps;
|
||||||
{
|
{
|
||||||
@@ -457,15 +422,14 @@ void after_submit() noexcept {
|
|||||||
}
|
}
|
||||||
slot.state = AsyncState::MapPending;
|
slot.state = AsyncState::MapPending;
|
||||||
++g_asyncMapsInFlight;
|
++g_asyncMapsInFlight;
|
||||||
pendingMaps.push_back({dest, slot.buffer, slot.bufferSize, g_asyncGeneration});
|
pendingMaps.push_back({dest, slot.buffer, slot.bufferSize});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const auto& pending : pendingMaps) {
|
for (const auto& pending : pendingMaps) {
|
||||||
pending.buffer.MapAsync(wgpu::MapMode::Read, 0, pending.bufferSize, wgpu::CallbackMode::AllowSpontaneous,
|
pending.buffer.MapAsync(wgpu::MapMode::Read, 0, pending.bufferSize, wgpu::CallbackMode::AllowSpontaneous,
|
||||||
[dest = pending.dest, generation = pending.generation](wgpu::MapAsyncStatus status,
|
[dest = pending.dest](wgpu::MapAsyncStatus status, wgpu::StringView message) {
|
||||||
wgpu::StringView message) {
|
complete_async_slot(dest, status, message);
|
||||||
complete_async_slot(dest, generation, status, message);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -479,15 +443,9 @@ void abort_async() noexcept { g_asyncSealed.clear(); }
|
|||||||
void shutdown() noexcept {
|
void shutdown() noexcept {
|
||||||
cancel();
|
cancel();
|
||||||
g_asyncSealed.clear();
|
g_asyncSealed.clear();
|
||||||
// Retire callbacks before releasing buffers, and release outside their mutex:
|
std::lock_guard lock{g_asyncMutex};
|
||||||
// destruction may itself deliver an AllowSpontaneous cancellation callback.
|
g_asyncSlots.clear();
|
||||||
decltype(g_asyncSlots) retiredSlots;
|
g_asyncMapsInFlight = 0;
|
||||||
{
|
|
||||||
std::lock_guard lock{g_asyncMutex};
|
|
||||||
++g_asyncGeneration;
|
|
||||||
retiredSlots.swap(g_asyncSlots);
|
|
||||||
g_asyncMapsInFlight = 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace aurora::gfx::efb_ram
|
} // namespace aurora::gfx::efb_ram
|
||||||
|
|||||||
@@ -7,11 +7,9 @@
|
|||||||
|
|
||||||
namespace aurora::gfx::efb_ram {
|
namespace aurora::gfx::efb_ram {
|
||||||
|
|
||||||
inline constexpr size_t MaxAsyncReadbackSlots = 32;
|
|
||||||
|
|
||||||
void schedule(void* dest, uint32_t width, uint32_t height, GXTexFmt format, TextureHandle texture) noexcept;
|
void schedule(void* dest, uint32_t width, uint32_t height, GXTexFmt format, TextureHandle texture) noexcept;
|
||||||
bool has_pending(void* dest = nullptr) noexcept;
|
bool has_pending(void* dest = nullptr) noexcept;
|
||||||
bool prepare_downloads(void* dest = nullptr);
|
bool prepare_downloads(void* dest = nullptr) noexcept;
|
||||||
void encode_downloads(const wgpu::CommandEncoder& encoder, void* dest = nullptr) noexcept;
|
void encode_downloads(const wgpu::CommandEncoder& encoder, void* dest = nullptr) noexcept;
|
||||||
bool complete_downloads() noexcept;
|
bool complete_downloads() noexcept;
|
||||||
void cancel() noexcept;
|
void cancel() noexcept;
|
||||||
|
|||||||
@@ -396,7 +396,6 @@ static PendingPipeline* touch_pending_pipeline(PipelineRef hash, bool prioritize
|
|||||||
|
|
||||||
g_priorityPipelines.emplace_back(std::move(*backgroundIt));
|
g_priorityPipelines.emplace_back(std::move(*backgroundIt));
|
||||||
g_backgroundPipelines.erase(backgroundIt);
|
g_backgroundPipelines.erase(backgroundIt);
|
||||||
g_pipelineCv.notify_all();
|
|
||||||
return &g_priorityPipelines.back();
|
return &g_priorityPipelines.back();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -531,8 +530,7 @@ static PipelineRef find_pipeline_impl(ShaderType type, const PipelineConfig& con
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (notifyWorker) {
|
if (notifyWorker) {
|
||||||
// Compiler workers and renderer waiters share this condition variable.
|
g_pipelineCv.notify_one();
|
||||||
g_pipelineCv.notify_all();
|
|
||||||
}
|
}
|
||||||
if (notifyWaiters) {
|
if (notifyWaiters) {
|
||||||
g_pipelineCv.notify_all();
|
g_pipelineCv.notify_all();
|
||||||
|
|||||||
@@ -1,33 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
#include <array>
|
|
||||||
#include <cstdint>
|
|
||||||
#include <limits>
|
|
||||||
#include <stdexcept>
|
|
||||||
|
|
||||||
namespace aurora::gfx {
|
|
||||||
// Byte counts after each allocation's own trailing alignment, in V/U/I/S order.
|
|
||||||
using StagingSizes = std::array<uint64_t, 4>;
|
|
||||||
class StagingCapacityError : public std::runtime_error {
|
|
||||||
public:
|
|
||||||
using std::runtime_error::runtime_error;
|
|
||||||
};
|
|
||||||
struct StagingBatchFull {};
|
|
||||||
inline uint64_t staging_padded(uint64_t bytes, uint64_t alignment) {
|
|
||||||
if (!bytes) return alignment;
|
|
||||||
const auto remainder = alignment ? bytes % alignment : 0;
|
|
||||||
const auto padding = remainder ? alignment - remainder : 0;
|
|
||||||
if (bytes > UINT64_MAX - padding) throw StagingCapacityError("Staging allocation size overflow");
|
|
||||||
return bytes + padding;
|
|
||||||
}
|
|
||||||
inline bool staging_fits(const StagingSizes& used, const StagingSizes& demand,
|
|
||||||
const StagingSizes& tail, const StagingSizes& capacity) noexcept {
|
|
||||||
for (unsigned i = 0; i < used.size(); ++i) {
|
|
||||||
const auto limit = capacity[i] < UINT32_MAX ? capacity[i] : UINT32_MAX;
|
|
||||||
// The final GPU copy rounds to four bytes. Subtractions avoid wraparound.
|
|
||||||
const auto alignedLimit = limit & ~uint64_t(3);
|
|
||||||
if (tail[i] > alignedLimit || used[i] > alignedLimit - tail[i] ||
|
|
||||||
demand[i] > alignedLimit - tail[i] - used[i]) return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
} // namespace aurora::gfx
|
|
||||||
@@ -1,61 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <chrono>
|
|
||||||
#include <condition_variable>
|
|
||||||
#include <cstdint>
|
|
||||||
#include <mutex>
|
|
||||||
|
|
||||||
namespace aurora::gfx {
|
|
||||||
|
|
||||||
enum class BufferMapState { Unmapped, Mapping, Mapped };
|
|
||||||
|
|
||||||
// The renderer owns request/reset; Dawn may complete a request on another thread.
|
|
||||||
// An old callback must never publish readiness for a different staging slot.
|
|
||||||
class StagingMapState {
|
|
||||||
mutable std::mutex mutex_;
|
|
||||||
std::condition_variable changed_;
|
|
||||||
uint64_t generation_ = 0;
|
|
||||||
BufferMapState state_ = BufferMapState::Unmapped;
|
|
||||||
|
|
||||||
public:
|
|
||||||
uint64_t request() {
|
|
||||||
std::lock_guard lock(mutex_);
|
|
||||||
if (state_ != BufferMapState::Unmapped) return 0;
|
|
||||||
state_ = BufferMapState::Mapping;
|
|
||||||
return ++generation_;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool complete(uint64_t generation, BufferMapState state) {
|
|
||||||
{
|
|
||||||
std::lock_guard lock(mutex_);
|
|
||||||
if (generation != generation_ || state_ != BufferMapState::Mapping) return false;
|
|
||||||
state_ = state;
|
|
||||||
}
|
|
||||||
changed_.notify_all();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void reset() {
|
|
||||||
{
|
|
||||||
std::lock_guard lock(mutex_);
|
|
||||||
++generation_;
|
|
||||||
state_ = BufferMapState::Unmapped;
|
|
||||||
}
|
|
||||||
changed_.notify_all();
|
|
||||||
}
|
|
||||||
|
|
||||||
BufferMapState state() const {
|
|
||||||
std::lock_guard lock(mutex_);
|
|
||||||
return state_;
|
|
||||||
}
|
|
||||||
|
|
||||||
void wait_for_progress() {
|
|
||||||
std::unique_lock lock(mutex_);
|
|
||||||
// ProcessEvents is still serviced between waits for implementations that
|
|
||||||
// need it. A spontaneous completion wakes immediately, without polling.
|
|
||||||
changed_.wait_for(lock, std::chrono::milliseconds(1),
|
|
||||||
[&] { return state_ != BufferMapState::Mapping; });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace aurora::gfx
|
|
||||||
@@ -137,7 +137,7 @@ fn gx_z24_at_coord(unclamped_coord: vec2i) -> u32 {
|
|||||||
let tex_size = vec2i(textureDimensions(src));
|
let tex_size = vec2i(textureDimensions(src));
|
||||||
let coord = clamp(unclamped_coord, vec2i(0), tex_size - vec2i(1));
|
let coord = clamp(unclamped_coord, vec2i(0), tex_size - vec2i(1));
|
||||||
let depth = textureLoad(src, coord, 0);
|
let depth = textureLoad(src, coord, 0);
|
||||||
return min(u32(clamp(1.0 - depth, 0.0, 1.0) * 16777215.0 + 0.5), 0x00ffffffu);
|
return min(u32(clamp(depth, 0.0, 1.0) * 16777216.0), 0x00ffffffu);
|
||||||
}
|
}
|
||||||
)"s
|
)"s
|
||||||
: R"(
|
: R"(
|
||||||
@@ -373,7 +373,7 @@ static wgpu::BindGroupLayout g_depthBindGroupLayout;
|
|||||||
static wgpu::Sampler g_nearestSampler;
|
static wgpu::Sampler g_nearestSampler;
|
||||||
static wgpu::Sampler g_linearSampler;
|
static wgpu::Sampler g_linearSampler;
|
||||||
static absl::flat_hash_map<GXTexFmt, wgpu::RenderPipeline> g_pipelines;
|
static absl::flat_hash_map<GXTexFmt, wgpu::RenderPipeline> g_pipelines;
|
||||||
static absl::flat_hash_map<wgpu::TextureFormat, wgpu::RenderPipeline> g_blitPipelines;
|
static wgpu::RenderPipeline g_blitPipeline;
|
||||||
|
|
||||||
static wgpu::RenderPipeline create_pipeline(const ConvPipeline& conv, const std::string_view shaderPreamble,
|
static wgpu::RenderPipeline create_pipeline(const ConvPipeline& conv, const std::string_view shaderPreamble,
|
||||||
const wgpu::BindGroupLayout& bindGroupLayout) {
|
const wgpu::BindGroupLayout& bindGroupLayout) {
|
||||||
@@ -487,12 +487,9 @@ void initialize() {
|
|||||||
};
|
};
|
||||||
g_depthBindGroupLayout = g_device.CreateBindGroupLayout(&depthBindGroupLayoutDescriptor);
|
g_depthBindGroupLayout = g_device.CreateBindGroupLayout(&depthBindGroupLayoutDescriptor);
|
||||||
|
|
||||||
// Native RAM readback uses RGBA even when the EFB/surface uses BGRA.
|
g_blitPipeline = create_pipeline(
|
||||||
// Build both variants here; frame workers only read the completed map.
|
{GX_TF_RGBA8, FragPassthrough, webgpu::g_graphicsConfig.surfaceConfiguration.format, "TexCopyConv Blit"},
|
||||||
for (const auto format : {wgpu::TextureFormat::RGBA8Unorm, wgpu::TextureFormat::BGRA8Unorm}) {
|
ShaderPreamble, g_bindGroupLayout);
|
||||||
g_blitPipelines[format] = create_pipeline(
|
|
||||||
{GX_TF_RGBA8, FragPassthrough, format, "TexCopyConv Blit"}, ShaderPreamble, g_bindGroupLayout);
|
|
||||||
}
|
|
||||||
for (const auto& conv : ConvPipelines) {
|
for (const auto& conv : ConvPipelines) {
|
||||||
g_pipelines[conv.fmt] = create_pipeline(conv, ShaderPreamble, g_bindGroupLayout);
|
g_pipelines[conv.fmt] = create_pipeline(conv, ShaderPreamble, g_bindGroupLayout);
|
||||||
if (conv.outputFormat != to_wgpu(conv.fmt)) {
|
if (conv.outputFormat != to_wgpu(conv.fmt)) {
|
||||||
@@ -523,7 +520,7 @@ void initialize() {
|
|||||||
|
|
||||||
void shutdown() {
|
void shutdown() {
|
||||||
g_pipelines.clear();
|
g_pipelines.clear();
|
||||||
g_blitPipelines.clear();
|
g_blitPipeline = {};
|
||||||
g_bindGroupLayout = {};
|
g_bindGroupLayout = {};
|
||||||
g_depthBindGroupLayout = {};
|
g_depthBindGroupLayout = {};
|
||||||
g_nearestSampler = {};
|
g_nearestSampler = {};
|
||||||
@@ -605,12 +602,6 @@ void run(const wgpu::CommandEncoder& cmd, const ConvRequest& req) {
|
|||||||
execute(cmd, req, it->second);
|
execute(cmd, req, it->second);
|
||||||
}
|
}
|
||||||
|
|
||||||
void blit(const wgpu::CommandEncoder& cmd, const ConvRequest& req) {
|
void blit(const wgpu::CommandEncoder& cmd, const ConvRequest& req) { execute(cmd, req, g_blitPipeline); }
|
||||||
const auto it = g_blitPipelines.find(req.dst->format);
|
|
||||||
if (it == g_blitPipelines.end()) {
|
|
||||||
Log.fatal("Unsupported blit destination format {}", static_cast<int>(req.dst->format));
|
|
||||||
}
|
|
||||||
execute(cmd, req, it->second);
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace aurora::gfx::tex_copy_conv
|
} // namespace aurora::gfx::tex_copy_conv
|
||||||
|
|||||||
@@ -553,7 +553,7 @@ void build_index() noexcept {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
s_replacementIndex.try_emplace(*parsed, path);
|
s_replacementIndex.try_emplace(*parsed, ReplacementIndexEntry{path});
|
||||||
}
|
}
|
||||||
|
|
||||||
Log.info("Indexed {} texture replacements", s_replacementIndex.size());
|
Log.info("Indexed {} texture replacements", s_replacementIndex.size());
|
||||||
|
|||||||
@@ -30,11 +30,10 @@ using IndexBuffer = std::vector<u16>;
|
|||||||
static u32 prepare_idx_template(IndexBuffer& buf, GXPrimitive prim, u16 vtxCount) {
|
static u32 prepare_idx_template(IndexBuffer& buf, GXPrimitive prim, u16 vtxCount) {
|
||||||
size_t writePos = 0;
|
size_t writePos = 0;
|
||||||
if (prim == GX_QUADS) {
|
if (prim == GX_QUADS) {
|
||||||
// GX renders a three-vertex remainder as a triangle. One/two are ignored.
|
// Retain the existing incomplete-quad behavior: every started group emits a complete six-index quad.
|
||||||
const u32 completeVertices = static_cast<u32>(vtxCount) & ~3u;
|
buf.resize(((static_cast<u32>(vtxCount) + 3u) / 4u) * 6u);
|
||||||
buf.resize((completeVertices / 4u) * 6u + (vtxCount % 4u == 3u ? 3u : 0u));
|
|
||||||
|
|
||||||
for (u32 v = 0; v < completeVertices; v += 4) {
|
for (u16 v = 0; v < vtxCount; v += 4) {
|
||||||
const u16 idx0 = v;
|
const u16 idx0 = v;
|
||||||
const u16 idx1 = static_cast<u16>(v + 1);
|
const u16 idx1 = static_cast<u16>(v + 1);
|
||||||
const u16 idx2 = static_cast<u16>(v + 2);
|
const u16 idx2 = static_cast<u16>(v + 2);
|
||||||
@@ -46,21 +45,15 @@ static u32 prepare_idx_template(IndexBuffer& buf, GXPrimitive prim, u16 vtxCount
|
|||||||
buf[writePos++] = idx3;
|
buf[writePos++] = idx3;
|
||||||
buf[writePos++] = idx0;
|
buf[writePos++] = idx0;
|
||||||
}
|
}
|
||||||
if (vtxCount % 4u == 3u) {
|
|
||||||
buf[writePos++] = static_cast<u16>(completeVertices);
|
|
||||||
buf[writePos++] = static_cast<u16>(completeVertices + 1u);
|
|
||||||
buf[writePos++] = static_cast<u16>(completeVertices + 2u);
|
|
||||||
}
|
|
||||||
} else if (prim == GX_TRIANGLES) {
|
} else if (prim == GX_TRIANGLES) {
|
||||||
const u32 completeVertices = (static_cast<u32>(vtxCount) / 3u) * 3u;
|
buf.resize(vtxCount);
|
||||||
buf.resize(completeVertices);
|
for (u16 v = 0; v < vtxCount; ++v) {
|
||||||
for (u32 v = 0; v < completeVertices; ++v) {
|
|
||||||
buf[writePos++] = v;
|
buf[writePos++] = v;
|
||||||
}
|
}
|
||||||
} else if (prim == GX_TRIANGLEFAN) {
|
} else if (prim == GX_TRIANGLEFAN) {
|
||||||
const u32 indexCount = vtxCount < 3 ? 0u : (static_cast<u32>(vtxCount) - 2u) * 3u;
|
const u32 indexCount = vtxCount <= 3 ? vtxCount : 3u + (static_cast<u32>(vtxCount) - 3u) * 3u;
|
||||||
buf.resize(indexCount);
|
buf.resize(indexCount);
|
||||||
for (u32 v = 0; indexCount != 0 && v < vtxCount; ++v) {
|
for (u16 v = 0; v < vtxCount; ++v) {
|
||||||
if (v < 3) {
|
if (v < 3) {
|
||||||
buf[writePos++] = v;
|
buf[writePos++] = v;
|
||||||
continue;
|
continue;
|
||||||
@@ -70,9 +63,9 @@ static u32 prepare_idx_template(IndexBuffer& buf, GXPrimitive prim, u16 vtxCount
|
|||||||
buf[writePos++] = v;
|
buf[writePos++] = v;
|
||||||
}
|
}
|
||||||
} else if (prim == GX_TRIANGLESTRIP) {
|
} else if (prim == GX_TRIANGLESTRIP) {
|
||||||
const u32 indexCount = vtxCount < 3 ? 0u : (static_cast<u32>(vtxCount) - 2u) * 3u;
|
const u32 indexCount = vtxCount <= 3 ? vtxCount : 3u + (static_cast<u32>(vtxCount) - 3u) * 3u;
|
||||||
buf.resize(indexCount);
|
buf.resize(indexCount);
|
||||||
for (u32 v = 0; indexCount != 0 && v < vtxCount; ++v) {
|
for (u16 v = 0; v < vtxCount; ++v) {
|
||||||
if (v < 3) {
|
if (v < 3) {
|
||||||
buf[writePos++] = v;
|
buf[writePos++] = v;
|
||||||
continue;
|
continue;
|
||||||
@@ -95,13 +88,6 @@ static u32 prepare_idx_template(IndexBuffer& buf, GXPrimitive prim, u16 vtxCount
|
|||||||
return static_cast<u32>(writePos);
|
return static_cast<u32>(writePos);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Empty/incomplete draws consume FIFO bytes but cannot produce a primitive.
|
|
||||||
static bool has_complete_primitive(GXPrimitive prim, u16 count) {
|
|
||||||
if (prim == GX_POINTS) return count >= 1;
|
|
||||||
if (prim == GX_LINES || prim == GX_LINESTRIP) return count >= 2;
|
|
||||||
return count >= 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
// GX FIFO opcodes - use CP_ prefix to avoid clashing with GXCommandList.h macros
|
// GX FIFO opcodes - use CP_ prefix to avoid clashing with GXCommandList.h macros
|
||||||
static constexpr u8 CP_CMD_NOP = GX_NOP;
|
static constexpr u8 CP_CMD_NOP = GX_NOP;
|
||||||
static constexpr u8 CP_CMD_LOAD_CP_REG = GX_LOAD_CP_REG;
|
static constexpr u8 CP_CMD_LOAD_CP_REG = GX_LOAD_CP_REG;
|
||||||
@@ -480,14 +466,13 @@ static void handle_xf(const u8* data, u32& pos, u32 size, bool bigEndian);
|
|||||||
static bool handle_draw(u8 cmd, const u8* data, u32& pos, u32 size, bool bigEndian);
|
static bool handle_draw(u8 cmd, const u8* data, u32& pos, u32 size, bool bigEndian);
|
||||||
static bool handle_aurora(const u8* data, u32& pos, u32 size, bool bigEndian);
|
static bool handle_aurora(const u8* data, u32& pos, u32 size, bool bigEndian);
|
||||||
|
|
||||||
uint32_t process(const u8* data, u32 size, bool bigEndian) {
|
void process(const u8* data, u32 size, bool bigEndian) {
|
||||||
ZoneScoped;
|
ZoneScoped;
|
||||||
// Everything decoded here mutates renderer state (GX state, the recorded command lists and the mapped staging buffers), so take the renderer GPU mutex once for the whole drain rather than once per draw command.
|
// Everything decoded here mutates renderer state (GX state, the recorded command lists and the mapped staging buffers), so take the renderer GPU mutex once for the whole drain rather than once per draw command.
|
||||||
std::lock_guard gpuLock(aurora::renderer_gpu_mutex());
|
std::lock_guard gpuLock(aurora::renderer_gpu_mutex());
|
||||||
u32 pos = 0;
|
u32 pos = 0;
|
||||||
|
|
||||||
while (pos < size) {
|
while (pos < size) {
|
||||||
const u32 commandStart = pos;
|
|
||||||
u8 cmd = data[pos++];
|
u8 cmd = data[pos++];
|
||||||
u8 opcode = cmd & CP_OPCODE_MASK;
|
u8 opcode = cmd & CP_OPCODE_MASK;
|
||||||
// Log.warn("Processing opcode {:02x} at pos {} (size {})", opcode, pos - 1, size);
|
// Log.warn("Processing opcode {:02x} at pos {} (size {})", opcode, pos - 1, size);
|
||||||
@@ -566,16 +551,12 @@ uint32_t process(const u8* data, u32 size, bool bigEndian) {
|
|||||||
for (int i = GX_VA_POS; i <= GX_VA_TEX7; ++i) {
|
for (int i = GX_VA_POS; i <= GX_VA_TEX7; ++i) {
|
||||||
g_gxState.arrays[i].cachedRange = {};
|
g_gxState.arrays[i].cachedRange = {};
|
||||||
}
|
}
|
||||||
// A merged draw retains its previous array uploads. Force a new draw so
|
|
||||||
// handle_draw_unmerged observes the invalidation and uploads fresh data.
|
|
||||||
// Pipeline configuration itself did not change.
|
|
||||||
g_gxState.stateDirty = true;
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case GX_LOAD_AURORA: {
|
case GX_LOAD_AURORA: {
|
||||||
if (!handle_aurora(data, pos, size, bigEndian)) {
|
if (!handle_aurora(data, pos, size, bigEndian)) {
|
||||||
return size;
|
return;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -583,10 +564,8 @@ uint32_t process(const u8* data, u32 size, bool bigEndian) {
|
|||||||
default:
|
default:
|
||||||
// Draw commands occupy the full 0x80-0xBF range.
|
// Draw commands occupy the full 0x80-0xBF range.
|
||||||
if (is_draw_cmd(cmd)) {
|
if (is_draw_cmd(cmd)) {
|
||||||
try {
|
if (!handle_draw(cmd, data, pos, size, bigEndian)) {
|
||||||
if (!handle_draw(cmd, data, pos, size, bigEndian)) return size;
|
return;
|
||||||
} catch (const gfx::StagingBatchFull&) {
|
|
||||||
return commandStart;
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
static u32 unknownLogCount = 0;
|
static u32 unknownLogCount = 0;
|
||||||
@@ -609,7 +588,6 @@ uint32_t process(const u8* data, u32 size, bool bigEndian) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return size;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper to extract bit fields from a 32-bit register
|
// Helper to extract bit fields from a 32-bit register
|
||||||
@@ -1870,10 +1848,6 @@ static u32 calculate_last_vtx_size(GXVtxFmt fmt) {
|
|||||||
|
|
||||||
g_gxState.lastVtxFmt = fmt;
|
g_gxState.lastVtxFmt = fmt;
|
||||||
g_gxState.lastVtxSize = vtxSize;
|
g_gxState.lastVtxSize = vtxSize;
|
||||||
// The format is selected by the draw opcode, without a register write.
|
|
||||||
// Even equal-stride formats may decode bytes differently, so do not merge
|
|
||||||
// into a draw using the previous format's shader and uniform layout.
|
|
||||||
g_gxState.stateDirty = true;
|
|
||||||
|
|
||||||
return vtxSize;
|
return vtxSize;
|
||||||
}
|
}
|
||||||
@@ -2106,22 +2080,6 @@ static const CachedPipelineState& resolve_pipeline_state(GXPrimitive prim, GXVtx
|
|||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool admit_draw(GXPrimitive prim, GXVtxFmt fmt, u16 count, uint32_t vertexBytes, bool merged = false) {
|
|
||||||
const auto& indexTemplate = cached_index_template(prim, count);
|
|
||||||
gfx::StagingSizes demand{vertexBytes, 0, indexTemplate.indices.size() * sizeof(u16), 0};
|
|
||||||
if (merged) return gfx::staging_has_space(demand);
|
|
||||||
const auto& info = resolve_pipeline_state(prim, fmt).shaderInfo;
|
|
||||||
demand[1] = gfx::staging_uniform_bytes(info.uniformSize);
|
|
||||||
if (frame_interpolation_identity_needed() && frame_interpolation_replay_safe())
|
|
||||||
demand[1] *= 1 + MaxInterpolatedFrames;
|
|
||||||
for (int i = GX_VA_POS; i <= GX_VA_TEX7; ++i) {
|
|
||||||
if ((g_gxState.vtxDesc[i] == GX_INDEX8 || g_gxState.vtxDesc[i] == GX_INDEX16) &&
|
|
||||||
g_gxState.arrays[i].cachedRange.size == 0)
|
|
||||||
demand[3] += gfx::staging_storage_bytes(g_gxState.arrays[i].size);
|
|
||||||
}
|
|
||||||
return gfx::staging_has_space(demand);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool submit_raw_draw(GXPrimitive prim, GXVtxFmt fmt, const uint8_t* vertices, uint16_t vtxCount,
|
bool submit_raw_draw(GXPrimitive prim, GXVtxFmt fmt, const uint8_t* vertices, uint16_t vtxCount,
|
||||||
uint32_t vertexBytes) {
|
uint32_t vertexBytes) {
|
||||||
ZoneScoped;
|
ZoneScoped;
|
||||||
@@ -2154,17 +2112,8 @@ bool submit_raw_draw(GXPrimitive prim, GXVtxFmt fmt, const uint8_t* vertices, ui
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!has_complete_primitive(prim, vtxCount)) return true;
|
|
||||||
|
|
||||||
// This entry point bypasses process(), so it owns the renderer lock itself.
|
// This entry point bypasses process(), so it owns the renderer lock itself.
|
||||||
std::unique_lock gpuLock(aurora::renderer_gpu_mutex());
|
std::lock_guard gpuLock(aurora::renderer_gpu_mutex());
|
||||||
if (!admit_draw(prim, fmt, vtxCount, vertexBytes)) {
|
|
||||||
gpuLock.unlock();
|
|
||||||
gfx::split_staging_batch();
|
|
||||||
gpuLock.lock();
|
|
||||||
if (!admit_draw(prim, fmt, vtxCount, vertexBytes))
|
|
||||||
throw gfx::StagingCapacityError("Raw draw does not fit after capacity submission");
|
|
||||||
}
|
|
||||||
const gfx::Range vertRange = gfx::push_verts(vertices, vertexBytes);
|
const gfx::Range vertRange = gfx::push_verts(vertices, vertexBytes);
|
||||||
const bool interpolationIdentityActive = frame_interpolation_identity_needed();
|
const bool interpolationIdentityActive = frame_interpolation_identity_needed();
|
||||||
const PnMtxUsage matrixUsage = interpolationIdentityActive
|
const PnMtxUsage matrixUsage = interpolationIdentityActive
|
||||||
@@ -2202,32 +2151,17 @@ static bool handle_draw(u8 cmd, const u8* data, u32& pos, u32 size, bool bigEndi
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (!has_complete_primitive(prim, vtxCount)) {
|
// Push raw vertex data to buffer
|
||||||
pos += totalVtxBytes;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
DrawData* mergeTarget = nullptr;
|
|
||||||
// Decide admission before allocating anything. The merged path needs only
|
|
||||||
// vertices and indices; it must not resolve pipelines or upload arrays.
|
|
||||||
// Try to merge with previous draw call
|
|
||||||
if (!g_gxState.stateDirty) LIKELY {
|
|
||||||
auto* lastDraw = gfx::get_last_draw_command<DrawData>();
|
|
||||||
// Expanded lines/points have different vertex interpretation even with one instance.
|
|
||||||
// Triangle-list output has no restart index; index 65535 is usable.
|
|
||||||
// Overflow would address earlier vertices instead of the appended geometry.
|
|
||||||
if (lastDraw != nullptr && prim != GX_LINES && prim != GX_LINESTRIP && prim != GX_POINTS &&
|
|
||||||
!lastDraw->expandedPrimitive && lastDraw->instanceCount == 1 &&
|
|
||||||
uint64_t(lastDraw->vtxCount) +
|
|
||||||
vtxCount <= 65536u) LIKELY {
|
|
||||||
mergeTarget = lastDraw;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!admit_draw(prim, fmt, vtxCount, totalVtxBytes, mergeTarget != nullptr)) throw gfx::StagingBatchFull{};
|
|
||||||
const uint8_t* vertices = data + pos;
|
const uint8_t* vertices = data + pos;
|
||||||
gfx::Range vertRange = gfx::push_verts(vertices, totalVtxBytes);
|
gfx::Range vertRange = gfx::push_verts(vertices, totalVtxBytes);
|
||||||
pos += totalVtxBytes;
|
pos += totalVtxBytes;
|
||||||
if (auto* lastDraw = mergeTarget) {
|
|
||||||
|
// Try to merge with previous draw call
|
||||||
|
if (!g_gxState.stateDirty) LIKELY {
|
||||||
|
auto* lastDraw = gfx::get_last_draw_command<DrawData>();
|
||||||
|
// Only if the previous draw call was a single instance draw (no lines/points handling)
|
||||||
|
if (lastDraw != nullptr && prim != GX_LINES && prim != GX_LINESTRIP && prim != GX_POINTS &&
|
||||||
|
lastDraw->instanceCount == 1) LIKELY {
|
||||||
const auto& indexTemplate = cached_index_template(prim, vtxCount);
|
const auto& indexTemplate = cached_index_template(prim, vtxCount);
|
||||||
const auto indices = offset_index_template(indexTemplate, lastDraw->vtxCount);
|
const auto indices = offset_index_template(indexTemplate, lastDraw->vtxCount);
|
||||||
const u32 numIndices = indexTemplate.indexCount;
|
const u32 numIndices = indexTemplate.indexCount;
|
||||||
@@ -2248,6 +2182,7 @@ static bool handle_draw(u8 cmd, const u8* data, u32& pos, u32 size, bool bigEndi
|
|||||||
extend_interpolation_draw(pn_mtx_mask(vertices, vtxCount, vtxSize));
|
extend_interpolation_draw(pn_mtx_mask(vertices, vtxCount, vtxSize));
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const bool interpolationIdentityActive = frame_interpolation_identity_needed();
|
const bool interpolationIdentityActive = frame_interpolation_identity_needed();
|
||||||
@@ -2343,7 +2278,6 @@ static void handle_draw_unmerged(GXPrimitive prim, GXVtxFmt fmt, u16 vtxCount,
|
|||||||
.vtxCount = vtxCount,
|
.vtxCount = vtxCount,
|
||||||
.indexCount = numIndices,
|
.indexCount = numIndices,
|
||||||
.instanceCount = instanceCount,
|
.instanceCount = instanceCount,
|
||||||
.expandedPrimitive = prim == GX_LINES || prim == GX_LINESTRIP || prim == GX_POINTS,
|
|
||||||
.bindGroups = bindGroups,
|
.bindGroups = bindGroups,
|
||||||
.dstAlpha = pipelineState.dstAlpha,
|
.dstAlpha = pipelineState.dstAlpha,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ namespace aurora::gx::fifo {
|
|||||||
void reset_cp_register_cache();
|
void reset_cp_register_cache();
|
||||||
|
|
||||||
// Process a buffer of GX FIFO commands
|
// Process a buffer of GX FIFO commands
|
||||||
uint32_t process(const uint8_t* data, uint32_t size, bool bigEndian);
|
void process(const uint8_t* data, uint32_t size, bool bigEndian);
|
||||||
|
|
||||||
// Submit already-packed direct vertex bytes against the current GX state.
|
// Submit already-packed direct vertex bytes against the current GX state.
|
||||||
bool submit_raw_draw(GXPrimitive prim, GXVtxFmt fmt, const uint8_t* vertices, uint16_t vtxCount,
|
bool submit_raw_draw(GXPrimitive prim, GXVtxFmt fmt, const uint8_t* vertices, uint16_t vtxCount,
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
#include "fifo.hpp"
|
#include "fifo.hpp"
|
||||||
#include "command_processor.hpp"
|
#include "command_processor.hpp"
|
||||||
#include "../gfx/common.hpp"
|
|
||||||
#include "../internal.hpp"
|
#include "../internal.hpp"
|
||||||
|
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
@@ -82,18 +81,7 @@ void drain() {
|
|||||||
if (detail::sBufferSize == 0) {
|
if (detail::sBufferSize == 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
uint32_t consumed = 0;
|
process(detail::sBufferData, detail::sBufferSize, true);
|
||||||
bool retried = false;
|
|
||||||
while (consumed < detail::sBufferSize) {
|
|
||||||
const auto count = process(detail::sBufferData + consumed, detail::sBufferSize - consumed, true);
|
|
||||||
if (count == 0 && retried)
|
|
||||||
throw gfx::StagingCapacityError("FIFO draw does not fit after capacity submission");
|
|
||||||
consumed += count;
|
|
||||||
if (consumed == detail::sBufferSize) break;
|
|
||||||
// process returned with its renderer lock released. No recursive drain.
|
|
||||||
gfx::split_staging_batch();
|
|
||||||
retried = true;
|
|
||||||
}
|
|
||||||
detail::sBufferSize = 0;
|
detail::sBufferSize = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -143,7 +143,6 @@ private:
|
|||||||
};
|
};
|
||||||
struct FrameTransformSnapshot {
|
struct FrameTransformSnapshot {
|
||||||
Mat4x4<float> projection{};
|
Mat4x4<float> projection{};
|
||||||
HashType viewportIdentity = 0;
|
|
||||||
Mat3x4<float> position{};
|
Mat3x4<float> position{};
|
||||||
Mat3x4<float> normal{};
|
Mat3x4<float> normal{};
|
||||||
uint16_t usedMatrixMask = 1;
|
uint16_t usedMatrixMask = 1;
|
||||||
@@ -1186,8 +1185,7 @@ void finalize_frame_interpolation() noexcept {
|
|||||||
if ((transform.usedMatrixMask & (1u << slot)) == 0) {
|
if ((transform.usedMatrixMask & (1u << slot)) == 0) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
paletteSlotKeys.push_back({combine_identity(transform.indexedMatrices->slotHash[slot],
|
paletteSlotKeys.push_back({transform.indexedMatrices->slotHash[slot], palette, slot});
|
||||||
transform.viewportIdentity), palette, slot});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
std::sort(paletteSlotKeys.begin(), paletteSlotKeys.end(),
|
std::sort(paletteSlotKeys.begin(), paletteSlotKeys.end(),
|
||||||
@@ -1448,21 +1446,10 @@ void extend_interpolation_draw(uint16_t usedPnMtxMask) noexcept {
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::array<gfx::Range, MaxInterpolatedFrames> record_interpolation_draw(
|
std::array<gfx::Range, MaxInterpolatedFrames> record_interpolation_draw(
|
||||||
const FrameInterpolationDrawIdentity& drawIdentity, const Mat4x4<float>& projection,
|
const FrameInterpolationDrawIdentity& identity, const Mat4x4<float>& projection,
|
||||||
uint16_t usedPnMtxMask, const InterpolatedUniformLayout& uniformLayout) noexcept {
|
uint16_t usedPnMtxMask, const InterpolatedUniformLayout& uniformLayout) noexcept {
|
||||||
// Split-screen cameras can draw identical meshes in unrelated view spaces.
|
|
||||||
// Scope exact, material-only and sibling-palette history to the guest viewport.
|
|
||||||
// Logical coordinates keep render-scale changes out of the camera identity.
|
|
||||||
const auto& viewport = g_gxState.logicalViewport;
|
|
||||||
const std::array viewportValues{viewport.left, viewport.top, viewport.width,
|
|
||||||
viewport.height, viewport.znear, viewport.zfar};
|
|
||||||
const HashType viewportIdentity = xxh3_hash_s(viewportValues.data(), sizeof(viewportValues));
|
|
||||||
auto identity = drawIdentity;
|
|
||||||
identity.combined = combine_identity(identity.combined, viewportIdentity);
|
|
||||||
identity.pipeline = combine_identity(identity.pipeline, viewportIdentity);
|
|
||||||
FrameTransformSnapshot snapshot{
|
FrameTransformSnapshot snapshot{
|
||||||
.projection = projection,
|
.projection = projection,
|
||||||
.viewportIdentity = viewportIdentity,
|
|
||||||
.usedMatrixMask = usedPnMtxMask,
|
.usedMatrixMask = usedPnMtxMask,
|
||||||
};
|
};
|
||||||
if (uniformLayout.indexedMatrices) {
|
if (uniformLayout.indexedMatrices) {
|
||||||
|
|||||||
@@ -1416,32 +1416,23 @@ static inline GXBlendFactor remove_dst_alpha_usage(GXBlendFactor fac) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// GX_LEQUAL etc. describe "pass if this pixel is closer than/equal to what's stored" in GX's own
|
|
||||||
// distance terms, independent of how that distance is encoded as a host depth value. Under
|
|
||||||
// UseReversedZ the encoding is flipped (near=1, far=0), so "closer" now corresponds to a *larger*
|
|
||||||
// stored value, not a smaller one - the ordered compare functions (LESS/LEQUAL/GREATER/GEQUAL)
|
|
||||||
// must invert to match, or the depth test silently runs backwards (verified directly: this was
|
|
||||||
// the actual cause of a bug report after the projection/shader half of the reverse-Z fix
|
|
||||||
// eliminated the double-negation that used to accidentally keep the unreversed comparisons
|
|
||||||
// correct - LEQUAL now needs GreaterEqual, not LessEqual, once the encoding it's testing against
|
|
||||||
// is genuinely reversed). Matches upstream aurora's to_compare_function exactly.
|
|
||||||
static inline wgpu::CompareFunction to_compare_function(GXCompare func) {
|
static inline wgpu::CompareFunction to_compare_function(GXCompare func) {
|
||||||
switch (func) {
|
switch (func) {
|
||||||
DEFAULT_FATAL("invalid depth fn {}", underlying(func));
|
DEFAULT_FATAL("invalid depth fn {}", underlying(func));
|
||||||
case GX_NEVER:
|
case GX_NEVER:
|
||||||
return wgpu::CompareFunction::Never;
|
return wgpu::CompareFunction::Never;
|
||||||
case GX_LESS:
|
case GX_LESS:
|
||||||
return UseReversedZ ? wgpu::CompareFunction::Greater : wgpu::CompareFunction::Less;
|
return wgpu::CompareFunction::Less;
|
||||||
case GX_EQUAL:
|
case GX_EQUAL:
|
||||||
return wgpu::CompareFunction::Equal;
|
return wgpu::CompareFunction::Equal;
|
||||||
case GX_LEQUAL:
|
case GX_LEQUAL:
|
||||||
return UseReversedZ ? wgpu::CompareFunction::GreaterEqual : wgpu::CompareFunction::LessEqual;
|
return wgpu::CompareFunction::LessEqual;
|
||||||
case GX_GREATER:
|
case GX_GREATER:
|
||||||
return UseReversedZ ? wgpu::CompareFunction::Less : wgpu::CompareFunction::Greater;
|
return wgpu::CompareFunction::Greater;
|
||||||
case GX_NEQUAL:
|
case GX_NEQUAL:
|
||||||
return wgpu::CompareFunction::NotEqual;
|
return wgpu::CompareFunction::NotEqual;
|
||||||
case GX_GEQUAL:
|
case GX_GEQUAL:
|
||||||
return UseReversedZ ? wgpu::CompareFunction::LessEqual : wgpu::CompareFunction::GreaterEqual;
|
return wgpu::CompareFunction::GreaterEqual;
|
||||||
case GX_ALWAYS:
|
case GX_ALWAYS:
|
||||||
return wgpu::CompareFunction::Always;
|
return wgpu::CompareFunction::Always;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -436,8 +436,6 @@ struct GXState {
|
|||||||
u32 pipelineStateGeneration = next_gx_state_epoch();
|
u32 pipelineStateGeneration = next_gx_state_epoch();
|
||||||
std::array<u32, 0x100> bpRegCache = [] {
|
std::array<u32, 0x100> bpRegCache = [] {
|
||||||
std::array<u32, 0x100> regs{};
|
std::array<u32, 0x100> regs{};
|
||||||
// Force the first GEN_MODE decode without changing its masked reset value.
|
|
||||||
regs[0x00] = 0xFF000000;
|
|
||||||
regs[0xFE] = 0x00FFFFFF;
|
regs[0xFE] = 0x00FFFFFF;
|
||||||
return regs;
|
return regs;
|
||||||
}();
|
}();
|
||||||
@@ -487,14 +485,7 @@ const gfx::TextureBind& get_texture(GXTexMapID id) noexcept;
|
|||||||
void resolve_sampled_textures(const ShaderInfo& info) noexcept;
|
void resolve_sampled_textures(const ShaderInfo& info) noexcept;
|
||||||
|
|
||||||
inline float clear_depth_value() {
|
inline float clear_depth_value() {
|
||||||
// g_gxState.clearDepth is in GX's own distance terms (0 = near, larger = farther), independent of
|
return std::min(static_cast<float>(g_gxState.clearDepth) / 16777216.f, 16777215.f / 16777216.f);
|
||||||
// how UseReversedZ encodes that as a host depth value - it must be re-mapped the same way the
|
|
||||||
// projection matrix and depth compare function are, or the buffer clears to the wrong extreme
|
|
||||||
// (verified directly: matches upstream aurora's clear_depth_value, which does this same inversion
|
|
||||||
// and was the second missing piece alongside to_compare_function's compare-op inversion).
|
|
||||||
const float normalizedDepth =
|
|
||||||
std::min(static_cast<float>(g_gxState.clearDepth) / 16777216.f, 16777215.f / 16777216.f);
|
|
||||||
return UseReversedZ ? (1.f - normalizedDepth) : normalizedDepth;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
inline bool render_target_has_alpha(GXPixelFmt pixelFmt) noexcept { return pixelFmt == GX_PF_RGBA6_Z24; }
|
inline bool render_target_has_alpha(GXPixelFmt pixelFmt) noexcept { return pixelFmt == GX_PF_RGBA6_Z24; }
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ struct DrawData {
|
|||||||
uint32_t vtxCount;
|
uint32_t vtxCount;
|
||||||
uint32_t indexCount;
|
uint32_t indexCount;
|
||||||
uint32_t instanceCount;
|
uint32_t instanceCount;
|
||||||
bool expandedPrimitive;
|
|
||||||
GXBindGroups bindGroups;
|
GXBindGroups bindGroups;
|
||||||
uint32_t dstAlpha;
|
uint32_t dstAlpha;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -993,13 +993,11 @@ wgpu::ShaderModule build_shader(const ShaderConfig& config) noexcept {
|
|||||||
"\n let clip_base = select(clip_a, clip_b, use_b);"
|
"\n let clip_base = select(clip_a, clip_b, use_b);"
|
||||||
"\n out.pos = vec4f(clip_base.xy + offset_ndc * clip_base.w, clip_base.zw);";
|
"\n out.pos = vec4f(clip_base.xy + offset_ndc * clip_base.w, clip_base.zw);";
|
||||||
}
|
}
|
||||||
// The near/far depth correction used to be applied here per-vertex (out.pos.z = -out.pos.z for
|
if constexpr (UseReversedZ) {
|
||||||
// reversed, or += out.pos.w for forward), redundantly on top of the same correction already
|
vtxXfrAttrsPre += "\n out.pos.z = -out.pos.z;";
|
||||||
// folded into ubuf.proj by effective_projection() (shader_info.cpp) - applying it twice canceled
|
} else {
|
||||||
// out for the common case (any draw where effective_projection() decides to flip), silently
|
vtxXfrAttrsPre += "\n out.pos.z += out.pos.w;";
|
||||||
// making "reversed" Z behave identically to forward Z. It is now applied exactly once, in the
|
}
|
||||||
// projection matrix alone (matching upstream aurora commit 1dde08fa: "Move depth correction to
|
|
||||||
// projection matrix"), so nothing needs to happen to out.pos.z here.
|
|
||||||
// GX rasterizes at a 7/12 pixel center when antialiasing is disabled, while WebGPU rasterizes at 1/2.
|
// GX rasterizes at a 7/12 pixel center when antialiasing is disabled, while WebGPU rasterizes at 1/2.
|
||||||
vtxXfrAttrsPre +=
|
vtxXfrAttrsPre +=
|
||||||
"\n let gx_pixel_center_correction = "
|
"\n let gx_pixel_center_correction = "
|
||||||
@@ -1467,14 +1465,7 @@ wgpu::ShaderModule build_shader(const ShaderConfig& config) noexcept {
|
|||||||
textureDependency.texMapId, uvIn);
|
textureDependency.texMapId, uvIn);
|
||||||
}
|
}
|
||||||
|
|
||||||
// in.pos.z is the host NDC z (forward: 0=near/1=far; reversed: 1=near/0=far post-fix), but this
|
std::string fogDepthExpr = UseReversedZ ? "in.pos.z" : "(1.0 - in.pos.z)";
|
||||||
// expression needs to produce GX's own native distance term (always 0=near/1=far, matching how
|
|
||||||
// g_gxState.clearDepth/clear_depth_value() are interpreted before their own UseReversedZ
|
|
||||||
// inversion) - forward already matches directly; reversed needs the same 1-x flip everything
|
|
||||||
// else reversed-Z-aware uses. This was backwards (verified directly against upstream aurora's
|
|
||||||
// identical expression in build_shader_source), which fed both fog density and the GX_ZT_ADD
|
|
||||||
// z-texture path the wrong distance value.
|
|
||||||
std::string fogDepthExpr = UseReversedZ ? "(1.0 - in.pos.z)" : "in.pos.z";
|
|
||||||
std::string fogZCoordExpr =
|
std::string fogZCoordExpr =
|
||||||
fmt::format("u32(round(clamp({}, 0.0, 1.0) * 16777216.0))", fogDepthExpr);
|
fmt::format("u32(round(clamp({}, 0.0, 1.0) * 16777216.0))", fogDepthExpr);
|
||||||
if (usesZTextureDepth) {
|
if (usesZTextureDepth) {
|
||||||
@@ -1507,7 +1498,7 @@ wgpu::ShaderModule build_shader(const ShaderConfig& config) noexcept {
|
|||||||
fragmentFn += fmt::format(
|
fragmentFn += fmt::format(
|
||||||
"\n let oldZ = u32(round(clamp({0}, 0.0, 1.0) * 16777216.0));"
|
"\n let oldZ = u32(round(clamp({0}, 0.0, 1.0) * 16777216.0));"
|
||||||
"\n ztexCoord = (ztexCoord + oldZ) & 0x00ffffffu;",
|
"\n ztexCoord = (ztexCoord + oldZ) & 0x00ffffffu;",
|
||||||
UseReversedZ ? "(1.0 - in.pos.z)" : "in.pos.z");
|
UseReversedZ ? "in.pos.z" : "(1.0 - in.pos.z)");
|
||||||
}
|
}
|
||||||
fragmentFn += "\n let ztexDepth = f32(ztexCoord) / 16777216.0;";
|
fragmentFn += "\n let ztexDepth = f32(ztexCoord) / 16777216.0;";
|
||||||
fogZCoordExpr = "ztexCoord";
|
fogZCoordExpr = "ztexCoord";
|
||||||
@@ -1648,13 +1639,7 @@ wgpu::ShaderModule build_shader(const ShaderConfig& config) noexcept {
|
|||||||
" @builtin(frag_depth) depth: f32,\n"
|
" @builtin(frag_depth) depth: f32,\n"
|
||||||
"};";
|
"};";
|
||||||
|
|
||||||
// ztexDepth is in GX's native distance terms (0=near/1=far, see fogDepthExpr's comment above),
|
fragmentFn += fmt::format("\n let fragDepth = {}ztexDepth;", UseReversedZ ? "" : "1.0 - ");
|
||||||
// but frag_depth must be written in the same host NDC-z convention in.pos.z itself uses -
|
|
||||||
// forward matches directly (no change), reversed needs the same 1-x flip. This was backwards
|
|
||||||
// the same way fogDepthExpr was (verified by the same derivation, since aurora upstream has no
|
|
||||||
// directly equivalent line here to cross-check against - this z-texture-depth-output path
|
|
||||||
// appears to be specific to this fork).
|
|
||||||
fragmentFn += fmt::format("\n let fragDepth = {}ztexDepth;", UseReversedZ ? "1.0 - " : "");
|
|
||||||
fragmentReturnType = "FragmentOutput";
|
fragmentReturnType = "FragmentOutput";
|
||||||
fragmentReturn =
|
fragmentReturn =
|
||||||
" var out: FragmentOutput;\n"
|
" var out: FragmentOutput;\n"
|
||||||
@@ -1708,22 +1693,8 @@ fn load_u16(p: ptr<storage, array<u32>>, byte_off: u32, le: bool) -> u32 {{
|
|||||||
return bswap16(raw, le);
|
return bswap16(raw, le);
|
||||||
}}
|
}}
|
||||||
|
|
||||||
fn load_u24_raw(p: ptr<storage, array<u32>>, byte_off: u32) -> u32 {{
|
|
||||||
let word_idx = byte_off >> 2u;
|
|
||||||
let sub = byte_off & 3u;
|
|
||||||
let word = p[word_idx];
|
|
||||||
// Three bytes at offsets zero or one fit entirely in this word. Do not
|
|
||||||
// access the next word: this attribute may end at the binding boundary.
|
|
||||||
if (sub <= 1u) {{
|
|
||||||
return (word >> (sub * 8u)) & 0x00FFFFFFu;
|
|
||||||
}}
|
|
||||||
let next = p[word_idx + 1u];
|
|
||||||
let shift = sub * 8u;
|
|
||||||
return ((word >> shift) | (next << (32u - shift))) & 0x00FFFFFFu;
|
|
||||||
}}
|
|
||||||
|
|
||||||
fn load_u24(p: ptr<storage, array<u32>>, byte_off: u32, le: bool) -> u32 {{
|
fn load_u24(p: ptr<storage, array<u32>>, byte_off: u32, le: bool) -> u32 {{
|
||||||
let raw = load_u24_raw(p, byte_off);
|
let raw = load_u32_raw(p, byte_off) & 0x00FFFFFFu;
|
||||||
if (le) {{
|
if (le) {{
|
||||||
return raw;
|
return raw;
|
||||||
}}
|
}}
|
||||||
@@ -1763,7 +1734,7 @@ fn raw_fetch_u8_2(p: ptr<storage, array<u32>>, byte_off: u32) -> vec2u {{
|
|||||||
}}
|
}}
|
||||||
|
|
||||||
fn raw_fetch_u8_3(p: ptr<storage, array<u32>>, byte_off: u32) -> vec3u {{
|
fn raw_fetch_u8_3(p: ptr<storage, array<u32>>, byte_off: u32) -> vec3u {{
|
||||||
let raw = load_u24_raw(p, byte_off);
|
let raw = load_u32_raw(p, byte_off);
|
||||||
return vec3u(
|
return vec3u(
|
||||||
extractBits(raw, 0u, 8u),
|
extractBits(raw, 0u, 8u),
|
||||||
extractBits(raw, 8u, 8u),
|
extractBits(raw, 8u, 8u),
|
||||||
|
|||||||
@@ -548,22 +548,14 @@ constexpr size_t kStagedUniformBytes =
|
|||||||
96 + sizeof(Mat4x4<float>) + sizeof(Mat3x4<float>) * (MaxPostexMtx + MaxPnMtx);
|
96 + sizeof(Mat4x4<float>) + sizeof(Mat3x4<float>) * (MaxPostexMtx + MaxPnMtx);
|
||||||
|
|
||||||
// The host viewport always receives the normalized GX depth window (render_pass_impl clamps to minDepth <= maxDepth).
|
// The host viewport always receives the normalized GX depth window (render_pass_impl clamps to minDepth <= maxDepth).
|
||||||
//
|
|
||||||
// Folds the near/far depth correction the vertex shader used to apply per-vertex directly into the
|
|
||||||
// projection matrix instead (matching upstream aurora commit 1dde08fa, "Move depth correction to
|
|
||||||
// projection matrix") - valid because the correction is a linear combination of the z/w rows, so
|
|
||||||
// applying it once here to the row is equivalent to applying it once per-vertex to the dot product,
|
|
||||||
// and it must be applied exactly once: doing it here AND in the shader (the previous bug) canceled
|
|
||||||
// the negation out for `flip`, silently making "reversed" Z behave identically to forward Z.
|
|
||||||
// `flip` decides which of the two single-application forms this draw needs: true bakes in the
|
|
||||||
// reversed-Z inversion (z' = -z), false bakes in the forward-Z near/far combination (z' = z + w) -
|
|
||||||
// exactly one always applies, never both, and never neither.
|
|
||||||
static Mat4x4<float> effective_projection() noexcept {
|
static Mat4x4<float> effective_projection() noexcept {
|
||||||
const auto& vp = g_gxState.renderViewport;
|
const auto& vp = g_gxState.renderViewport;
|
||||||
const bool flip = (vp.znear <= vp.zfar) == UseReversedZ;
|
const bool flip = (vp.znear <= vp.zfar) == UseReversedZ;
|
||||||
Mat4x4<float> proj = g_gxState.proj;
|
Mat4x4<float> proj = g_gxState.proj;
|
||||||
for (size_t i = 0; i < 4; ++i) {
|
if (flip) {
|
||||||
proj.m2.m[i] = flip ? -proj.m2.m[i] : (proj.m2.m[i] + proj.m3.m[i]);
|
for (size_t i = 0; i < 4; ++i) {
|
||||||
|
proj.m2.m[i] = -(proj.m2.m[i] + proj.m3.m[i]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return proj;
|
return proj;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -75,30 +75,18 @@ void initialize() noexcept {
|
|||||||
|
|
||||||
void shutdown() noexcept {
|
void shutdown() noexcept {
|
||||||
ZoneScoped;
|
ZoneScoped;
|
||||||
// Startup can fail before either backend initializes. A context alone does
|
if (g_useSdlRenderer) {
|
||||||
// not mean its renderer/platform backend owns resources to release.
|
ImGui_ImplSDLRenderer3_Shutdown();
|
||||||
if (ImGui::GetCurrentContext() != nullptr) {
|
} else {
|
||||||
ImGuiIO& io = ImGui::GetIO();
|
ImGui_ImplWGPU_Shutdown();
|
||||||
if (io.BackendRendererUserData != nullptr) {
|
|
||||||
if (g_useSdlRenderer) {
|
|
||||||
ImGui_ImplSDLRenderer3_Shutdown();
|
|
||||||
} else {
|
|
||||||
ImGui_ImplWGPU_Shutdown();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (io.BackendPlatformUserData != nullptr) {
|
|
||||||
ImGui_ImplSDL3_Shutdown();
|
|
||||||
}
|
|
||||||
ImGui::DestroyContext();
|
|
||||||
}
|
}
|
||||||
|
ImGui_ImplSDL3_Shutdown();
|
||||||
|
ImGui::DestroyContext();
|
||||||
for (const auto& texture : g_sdlTextures) {
|
for (const auto& texture : g_sdlTextures) {
|
||||||
SDL_DestroyTexture(texture);
|
SDL_DestroyTexture(texture);
|
||||||
}
|
}
|
||||||
g_sdlTextures.clear();
|
g_sdlTextures.clear();
|
||||||
g_wgpuTextures.clear();
|
g_wgpuTextures.clear();
|
||||||
g_useSdlRenderer = false;
|
|
||||||
g_scale = 0.f;
|
|
||||||
g_frameDataBuilt = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void process_event(const SDL_Event& event) noexcept {
|
void process_event(const SDL_Event& event) noexcept {
|
||||||
|
|||||||
@@ -414,29 +414,6 @@ SDL_JoystickID add_controller(SDL_JoystickID which) noexcept {
|
|||||||
g_GameControllers[instance] = controller;
|
g_GameControllers[instance] = controller;
|
||||||
ensure_player_index(g_GameControllers[instance]);
|
ensure_player_index(g_GameControllers[instance]);
|
||||||
apply_port_preferences();
|
apply_port_preferences();
|
||||||
#if defined(SDL_PLATFORM_MACOS)
|
|
||||||
// First-use convenience only: never override a saved assignment or None.
|
|
||||||
if (g_portPreferences[0].state == PortPreferenceState::Unset) {
|
|
||||||
bool hasOtherPortPreference = false;
|
|
||||||
for (size_t port = 1; port < g_portPreferences.size(); ++port) {
|
|
||||||
if (g_portPreferences[port].state == PortPreferenceState::Controller &&
|
|
||||||
identity_match(g_portPreferences[port].identity, controller_identity(g_GameControllers[instance])) !=
|
|
||||||
IdentityMatch::None) {
|
|
||||||
hasOtherPortPreference = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!hasOtherPortPreference) {
|
|
||||||
const auto* p0 = get_controller_for_player(0);
|
|
||||||
if (p0 == nullptr) {
|
|
||||||
assign_player_index(g_GameControllers[instance], 0);
|
|
||||||
persist_controller_for_player(0, &g_GameControllers[instance]);
|
|
||||||
} else if (p0 == &g_GameControllers[instance]) {
|
|
||||||
persist_controller_for_player(0, &g_GameControllers[instance]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -122,10 +122,7 @@ auto underlying(T value) -> std::underlying_type_t<T> {
|
|||||||
|
|
||||||
#define UNIMPLEMENTED() FATAL("UNIMPLEMENTED: {}", __FUNCTION__)
|
#define UNIMPLEMENTED() FATAL("UNIMPLEMENTED: {}", __FUNCTION__)
|
||||||
|
|
||||||
namespace wgpu { class CommandBuffer; }
|
|
||||||
|
|
||||||
namespace aurora {
|
namespace aurora {
|
||||||
void submit_staging_commands(const wgpu::CommandBuffer& commands);
|
|
||||||
extern AuroraConfig g_config;
|
extern AuroraConfig g_config;
|
||||||
extern uint32_t g_sdlCustomEventsStart;
|
extern uint32_t g_sdlCustomEventsStart;
|
||||||
extern char g_gameName[4];
|
extern char g_gameName[4];
|
||||||
|
|||||||
@@ -570,16 +570,12 @@ bool initialize(AuroraBackend auroraBackend) {
|
|||||||
g_adapter = std::move(adapter);
|
g_adapter = std::move(adapter);
|
||||||
} else {
|
} else {
|
||||||
Log.warn("Adapter request failed: {}", message);
|
Log.warn("Adapter request failed: {}", message);
|
||||||
const std::string_view reason{message};
|
|
||||||
SDL_SetError("Graphics adapter unavailable: %.*s",
|
|
||||||
static_cast<int>(std::min<size_t>(reason.size(), 512)), reason.data());
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
const auto status = g_instance.WaitAny(future, 5000000000);
|
const auto status = g_instance.WaitAny(future, 5000000000);
|
||||||
if (status != wgpu::WaitStatus::Success) {
|
if (status != wgpu::WaitStatus::Success) {
|
||||||
Log.error("Failed to create {} adapter: {}", magic_enum::enum_name(backend),
|
Log.error("Failed to create {} adapter: {}", magic_enum::enum_name(backend),
|
||||||
magic_enum::enum_name(status));
|
magic_enum::enum_name(status));
|
||||||
SDL_SetError("Graphics adapter request did not complete within its startup deadline");
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!g_adapter) {
|
if (!g_adapter) {
|
||||||
@@ -642,14 +638,6 @@ bool initialize(AuroraBackend auroraBackend) {
|
|||||||
requiredLimits.maxDynamicStorageBuffersPerPipelineLayout, requiredLimits.maxStorageBuffersPerShaderStage,
|
requiredLimits.maxDynamicStorageBuffersPerPipelineLayout, requiredLimits.maxStorageBuffersPerShaderStage,
|
||||||
requiredLimits.minUniformBufferOffsetAlignment, requiredLimits.minStorageBufferOffsetAlignment);
|
requiredLimits.minUniformBufferOffsetAlignment, requiredLimits.minStorageBufferOffsetAlignment);
|
||||||
std::vector<wgpu::FeatureName> requiredFeatures;
|
std::vector<wgpu::FeatureName> requiredFeatures;
|
||||||
// Optional native sharing for MetalFX. Devices without either feature keep
|
|
||||||
// the normal renderer; the upscaler checks the enabled pair at runtime.
|
|
||||||
if (backend == wgpu::BackendType::Metal &&
|
|
||||||
g_adapter.HasFeature(wgpu::FeatureName::SharedTextureMemoryIOSurface) &&
|
|
||||||
g_adapter.HasFeature(wgpu::FeatureName::SharedFenceMTLSharedEvent)) {
|
|
||||||
requiredFeatures.push_back(wgpu::FeatureName::SharedTextureMemoryIOSurface);
|
|
||||||
requiredFeatures.push_back(wgpu::FeatureName::SharedFenceMTLSharedEvent);
|
|
||||||
}
|
|
||||||
bool implicitDeviceSynchronizationSupported = false;
|
bool implicitDeviceSynchronizationSupported = false;
|
||||||
wgpu::SupportedFeatures supportedFeatures;
|
wgpu::SupportedFeatures supportedFeatures;
|
||||||
g_adapter.GetFeatures(&supportedFeatures);
|
g_adapter.GetFeatures(&supportedFeatures);
|
||||||
@@ -750,15 +738,11 @@ bool initialize(AuroraBackend auroraBackend) {
|
|||||||
g_device = std::move(device);
|
g_device = std::move(device);
|
||||||
} else {
|
} else {
|
||||||
Log.warn("Device request failed: {}", message);
|
Log.warn("Device request failed: {}", message);
|
||||||
const std::string_view reason{message};
|
|
||||||
SDL_SetError("Graphics device unavailable: %.*s",
|
|
||||||
static_cast<int>(std::min<size_t>(reason.size(), 512)), reason.data());
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
const auto status = g_instance.WaitAny(future, 5000000000);
|
const auto status = g_instance.WaitAny(future, 5000000000);
|
||||||
if (status != wgpu::WaitStatus::Success) {
|
if (status != wgpu::WaitStatus::Success) {
|
||||||
Log.error("Failed to create device: {}", magic_enum::enum_name(status));
|
Log.error("Failed to create device: {}", magic_enum::enum_name(status));
|
||||||
SDL_SetError("Graphics device request did not complete within its startup deadline");
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!g_device) {
|
if (!g_device) {
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <ctime>
|
#include <ctime>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <limits>
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
@@ -287,33 +286,8 @@ size_t load_from_cache(void const* key, size_t keySize, void* value, size_t valu
|
|||||||
if (ret == SQLITE_ROW) {
|
if (ret == SQLITE_ROW) {
|
||||||
// Hit
|
// Hit
|
||||||
const auto foundPtr = sqlite3_column_blob(load_stmt, 0);
|
const auto foundPtr = sqlite3_column_blob(load_stmt, 0);
|
||||||
const auto declaredSize = sqlite3_column_int64(load_stmt, 1);
|
foundSize = sqlite3_column_int64(load_stmt, 1);
|
||||||
const auto storedSize = sqlite3_column_bytes(load_stmt, 0);
|
const bool compressed = sqlite3_column_int(load_stmt, 2) != 0;
|
||||||
const auto compression = sqlite3_column_int(load_stmt, 2);
|
|
||||||
const bool compressed = compression == 1;
|
|
||||||
// Dawn asks for the size before allocating its destination. Validate here,
|
|
||||||
// not only during the copy: corrupt metadata must become a cache miss.
|
|
||||||
bool valid = declaredSize > 0 &&
|
|
||||||
static_cast<uint64_t>(declaredSize) <= std::numeric_limits<size_t>::max() &&
|
|
||||||
foundPtr != nullptr && storedSize > 0 && (compression == 0 || compression == 1);
|
|
||||||
if (valid && compressed) {
|
|
||||||
#if defined(AURORA_CACHE_USE_ZSTD)
|
|
||||||
// Our writer uses ZSTD_compress, which records the original content size.
|
|
||||||
const auto frameSize = ZSTD_getFrameContentSize(foundPtr, static_cast<size_t>(storedSize));
|
|
||||||
valid = frameSize != ZSTD_CONTENTSIZE_ERROR && frameSize != ZSTD_CONTENTSIZE_UNKNOWN &&
|
|
||||||
frameSize == static_cast<uint64_t>(declaredSize);
|
|
||||||
#else
|
|
||||||
valid = false;
|
|
||||||
#endif
|
|
||||||
} else if (valid) {
|
|
||||||
valid = declaredSize == storedSize;
|
|
||||||
}
|
|
||||||
if (!valid) {
|
|
||||||
Log.error("Ignoring cache entry with inconsistent size or compression metadata");
|
|
||||||
check(sqlite3_reset(load_stmt));
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
foundSize = static_cast<size_t>(declaredSize);
|
|
||||||
if (value == nullptr) {
|
if (value == nullptr) {
|
||||||
g_hits.fetch_add(1, std::memory_order_relaxed);
|
g_hits.fetch_add(1, std::memory_order_relaxed);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -1,41 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <webgpu/webgpu_cpp.h>
|
|
||||||
|
|
||||||
#include <memory>
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
namespace aurora::webgpu::metalfx {
|
|
||||||
struct Size {
|
|
||||||
uint32_t inputWidth;
|
|
||||||
uint32_t inputHeight;
|
|
||||||
uint32_t outputWidth;
|
|
||||||
uint32_t outputHeight;
|
|
||||||
wgpu::TextureFormat format;
|
|
||||||
|
|
||||||
bool operator==(const Size&) const = default;
|
|
||||||
};
|
|
||||||
|
|
||||||
// All methods except supported() belong to the serialized frame encoder.
|
|
||||||
// GPU ownership is explicit: begin_input -> submit input -> upscale ->
|
|
||||||
// submit output consumption -> end_output. Neither texture may be used by
|
|
||||||
// Dawn outside its access interval. Destruction retires in-flight resources.
|
|
||||||
class SpatialScaler {
|
|
||||||
public:
|
|
||||||
virtual ~SpatialScaler() = default;
|
|
||||||
virtual const wgpu::TextureView& input_view() const = 0;
|
|
||||||
virtual const wgpu::TextureView& output_view() const = 0;
|
|
||||||
virtual const wgpu::Texture& output_texture() const = 0;
|
|
||||||
virtual bool begin_input() = 0;
|
|
||||||
virtual bool upscale() = 0;
|
|
||||||
virtual bool end_output() = 0;
|
|
||||||
virtual const std::string& error() const = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
bool supported(const wgpu::Device& device, wgpu::BackendType backend);
|
|
||||||
// A null result with no error means the bounded retirement pool is busy;
|
|
||||||
// skip upscaling for this frame and retry at a later frame boundary.
|
|
||||||
std::unique_ptr<SpatialScaler> create(const wgpu::Instance& instance,
|
|
||||||
const wgpu::Device& device, const Size& size,
|
|
||||||
std::string& error);
|
|
||||||
} // namespace aurora::webgpu::metalfx
|
|
||||||
@@ -1,326 +0,0 @@
|
|||||||
#include "metalfx.hpp"
|
|
||||||
|
|
||||||
#import <Foundation/Foundation.h>
|
|
||||||
#import <IOSurface/IOSurface.h>
|
|
||||||
#import <Metal/Metal.h>
|
|
||||||
#import <MetalFX/MetalFX.h>
|
|
||||||
|
|
||||||
#include <dawn/native/MetalBackend.h>
|
|
||||||
|
|
||||||
#include <atomic>
|
|
||||||
#include <string_view>
|
|
||||||
|
|
||||||
namespace aurora::webgpu::metalfx {
|
|
||||||
namespace {
|
|
||||||
constexpr uint64_t kScheduleTimeoutNs = 1'000'000'000;
|
|
||||||
// Four current slots plus at most four retiring slots during resize. A busy
|
|
||||||
// GPU must not allow resize events to allocate unbounded full-resolution images.
|
|
||||||
constexpr unsigned kMaxLiveResources = 8;
|
|
||||||
std::atomic<unsigned> g_liveResources{0};
|
|
||||||
|
|
||||||
struct SharedImage {
|
|
||||||
IOSurfaceRef surface = nullptr;
|
|
||||||
id<MTLTexture> metal;
|
|
||||||
wgpu::SharedTextureMemory memory;
|
|
||||||
wgpu::Texture texture;
|
|
||||||
wgpu::TextureView view;
|
|
||||||
|
|
||||||
~SharedImage() { if (surface) CFRelease(surface); }
|
|
||||||
|
|
||||||
bool create(const wgpu::Device& device, id<MTLDevice> native, uint32_t width,
|
|
||||||
uint32_t height, wgpu::TextureFormat format, MTLTextureUsage nativeUsage,
|
|
||||||
wgpu::TextureUsage usage) {
|
|
||||||
const bool bgra = format == wgpu::TextureFormat::BGRA8Unorm;
|
|
||||||
const size_t rowBytes = IOSurfaceAlignProperty(kIOSurfaceBytesPerRow, size_t(width) * 4);
|
|
||||||
NSDictionary* properties = @{
|
|
||||||
(id)kIOSurfaceWidth: @(width), (id)kIOSurfaceHeight: @(height),
|
|
||||||
(id)kIOSurfaceBytesPerElement: @4, (id)kIOSurfaceBytesPerRow: @(rowBytes),
|
|
||||||
(id)kIOSurfaceAllocSize: @(rowBytes * height),
|
|
||||||
(id)kIOSurfacePixelFormat: @(bgra ? 0x42475241u : 0x52474241u)
|
|
||||||
};
|
|
||||||
surface = IOSurfaceCreate((__bridge CFDictionaryRef)properties);
|
|
||||||
if (!surface) return false;
|
|
||||||
auto descriptor = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:
|
|
||||||
bgra ? MTLPixelFormatBGRA8Unorm : MTLPixelFormatRGBA8Unorm
|
|
||||||
width:width height:height mipmapped:NO];
|
|
||||||
descriptor.storageMode = MTLStorageModeShared;
|
|
||||||
descriptor.usage = nativeUsage;
|
|
||||||
metal = [native newTextureWithDescriptor:descriptor iosurface:surface plane:0];
|
|
||||||
if (!metal) return false;
|
|
||||||
|
|
||||||
wgpu::SharedTextureMemoryIOSurfaceDescriptor io{};
|
|
||||||
io.ioSurface = surface;
|
|
||||||
io.allowStorageBinding = false;
|
|
||||||
wgpu::SharedTextureMemoryDescriptor importDescriptor{};
|
|
||||||
importDescriptor.nextInChain = &io;
|
|
||||||
memory = device.ImportSharedTextureMemory(&importDescriptor);
|
|
||||||
wgpu::SharedTextureMemoryProperties actual{};
|
|
||||||
if (!memory || memory.GetProperties(&actual) != wgpu::Status::Success ||
|
|
||||||
actual.format != format || actual.size.width != width || actual.size.height != height ||
|
|
||||||
(actual.usage & usage) != usage) return false;
|
|
||||||
wgpu::TextureDescriptor textureDescriptor{};
|
|
||||||
textureDescriptor.label = "MetalFX shared texture";
|
|
||||||
textureDescriptor.size = {width, height, 1};
|
|
||||||
textureDescriptor.format = format;
|
|
||||||
textureDescriptor.usage = usage;
|
|
||||||
texture = memory.CreateTexture(&textureDescriptor);
|
|
||||||
if (!texture) return false;
|
|
||||||
view = texture.CreateView();
|
|
||||||
return view != nullptr;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
struct API_AVAILABLE(macos(13.0)) Resources {
|
|
||||||
SharedImage input, output;
|
|
||||||
id<MTLFXSpatialScaler> scaler;
|
|
||||||
id<MTLTexture> privateOutput;
|
|
||||||
id<MTLCommandQueue> nativeQueue;
|
|
||||||
id<MTLSharedEvent> event;
|
|
||||||
wgpu::SharedFence fence;
|
|
||||||
std::atomic<bool> failed{false};
|
|
||||||
|
|
||||||
Resources() { ++g_liveResources; }
|
|
||||||
~Resources() { --g_liveResources; }
|
|
||||||
};
|
|
||||||
|
|
||||||
class API_AVAILABLE(macos(13.0)) MetalSpatialScaler final : public SpatialScaler {
|
|
||||||
wgpu::Instance m_instance;
|
|
||||||
wgpu::Queue m_queue;
|
|
||||||
std::shared_ptr<Resources> m_resources;
|
|
||||||
wgpu::SharedTextureMemoryEndAccessState m_outputReleased{};
|
|
||||||
wgpu::Future m_outputScheduled{};
|
|
||||||
uint64_t m_value = 0;
|
|
||||||
std::string m_error;
|
|
||||||
|
|
||||||
bool fail(const char* reason) {
|
|
||||||
m_error = reason;
|
|
||||||
m_resources->failed = true;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool wait_scheduled(wgpu::Future future) {
|
|
||||||
return m_instance.WaitAny(future, kScheduleTimeoutNs) == wgpu::WaitStatus::Success ||
|
|
||||||
fail("Timed out scheduling MetalFX GPU work");
|
|
||||||
}
|
|
||||||
|
|
||||||
bool end_access(SharedImage& image, wgpu::SharedTextureMemoryEndAccessState& state,
|
|
||||||
wgpu::Future& scheduled) {
|
|
||||||
wgpu::SharedTextureMemoryMetalEndAccessState metal{};
|
|
||||||
state.nextInChain = &metal;
|
|
||||||
const auto status = image.memory.EndAccess(image.texture, &state);
|
|
||||||
state.nextInChain = nullptr;
|
|
||||||
scheduled = metal.commandsScheduledFuture;
|
|
||||||
return status == wgpu::Status::Success || fail("MetalFX Dawn EndAccess failed");
|
|
||||||
}
|
|
||||||
|
|
||||||
bool begin_access(SharedImage& image, bool initialized, uint64_t value) {
|
|
||||||
wgpu::SharedTextureMemoryBeginAccessDescriptor access{};
|
|
||||||
access.initialized = initialized;
|
|
||||||
if (value) {
|
|
||||||
access.fenceCount = 1;
|
|
||||||
access.fences = &m_resources->fence;
|
|
||||||
access.signaledValueCount = 1;
|
|
||||||
access.signaledValues = &value;
|
|
||||||
}
|
|
||||||
return image.memory.BeginAccess(image.texture, &access) == wgpu::Status::Success ||
|
|
||||||
fail("MetalFX Dawn BeginAccess failed");
|
|
||||||
}
|
|
||||||
|
|
||||||
bool encode_waits(id<MTLCommandBuffer> commands,
|
|
||||||
const wgpu::SharedTextureMemoryEndAccessState& state) {
|
|
||||||
for (size_t i = 0; i < state.fenceCount; ++i) {
|
|
||||||
wgpu::SharedFenceMTLSharedEventExportInfo metal{};
|
|
||||||
wgpu::SharedFenceExportInfo info{};
|
|
||||||
info.nextInChain = &metal;
|
|
||||||
state.fences[i].ExportInfo(&info);
|
|
||||||
if (info.type != wgpu::SharedFenceType::MTLSharedEvent || !metal.sharedEvent)
|
|
||||||
return fail("Dawn did not export a MetalFX shared-event dependency");
|
|
||||||
[commands encodeWaitForEvent:(__bridge id<MTLSharedEvent>)metal.sharedEvent
|
|
||||||
value:state.signaledValues[i]];
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void retain_until_dawn_done() {
|
|
||||||
// A resize/toggle can destroy this wrapper immediately. The last submitted
|
|
||||||
// Dawn consumer keeps the IOSurfaces/scaler alive independently of the cache.
|
|
||||||
m_queue.OnSubmittedWorkDone(wgpu::CallbackMode::AllowSpontaneous,
|
|
||||||
[resources = m_resources](wgpu::QueueWorkDoneStatus status, wgpu::StringView) {
|
|
||||||
if (status != wgpu::QueueWorkDoneStatus::Success) resources->failed = true;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public:
|
|
||||||
MetalSpatialScaler(const wgpu::Instance& instance, const wgpu::Device& device,
|
|
||||||
std::shared_ptr<Resources> resources)
|
|
||||||
: m_instance(instance), m_queue(device.GetQueue()), m_resources(std::move(resources)) {}
|
|
||||||
|
|
||||||
const wgpu::TextureView& input_view() const override { return m_resources->input.view; }
|
|
||||||
const wgpu::TextureView& output_view() const override { return m_resources->output.view; }
|
|
||||||
const wgpu::Texture& output_texture() const override { return m_resources->output.texture; }
|
|
||||||
const std::string& error() const override { return m_error; }
|
|
||||||
|
|
||||||
bool begin_input() override {
|
|
||||||
if (m_resources->failed) return fail("Previous MetalFX GPU work failed");
|
|
||||||
return begin_access(m_resources->input, m_value != 0, m_value);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool upscale() override {
|
|
||||||
@autoreleasepool {
|
|
||||||
// Input has already been submitted. Retain it even if an export or native
|
|
||||||
// allocation fails and the caller immediately falls back to normal copy.
|
|
||||||
retain_until_dawn_done();
|
|
||||||
wgpu::SharedTextureMemoryEndAccessState inputReleased{};
|
|
||||||
wgpu::Future inputScheduled{};
|
|
||||||
if (!end_access(m_resources->input, inputReleased, inputScheduled) ||
|
|
||||||
!wait_scheduled(inputScheduled)) return false;
|
|
||||||
if (m_value && !wait_scheduled(m_outputScheduled)) return false;
|
|
||||||
id<MTLCommandBuffer> commands = [m_resources->nativeQueue commandBuffer];
|
|
||||||
if (!commands) return fail("Could not allocate a MetalFX command buffer");
|
|
||||||
commands.label = @"MetalFX spatial upscale and return to Dawn";
|
|
||||||
if (!encode_waits(commands, inputReleased) || !encode_waits(commands, m_outputReleased))
|
|
||||||
return false;
|
|
||||||
[m_resources->scaler encodeToCommandBuffer:commands];
|
|
||||||
id<MTLBlitCommandEncoder> blit = [commands blitCommandEncoder];
|
|
||||||
if (!blit) return fail("Could not allocate the MetalFX output blit");
|
|
||||||
[blit copyFromTexture:m_resources->privateOutput sourceSlice:0 sourceLevel:0
|
|
||||||
sourceOrigin:MTLOriginMake(0, 0, 0)
|
|
||||||
sourceSize:MTLSizeMake(m_resources->privateOutput.width, m_resources->privateOutput.height, 1)
|
|
||||||
toTexture:m_resources->output.metal destinationSlice:0 destinationLevel:0
|
|
||||||
destinationOrigin:MTLOriginMake(0, 0, 0)];
|
|
||||||
[blit endEncoding];
|
|
||||||
++m_value;
|
|
||||||
[commands encodeSignalEvent:m_resources->event value:m_value];
|
|
||||||
const auto resources = m_resources;
|
|
||||||
[commands addCompletedHandler:^(id<MTLCommandBuffer> completed) {
|
|
||||||
if (completed.status == MTLCommandBufferStatusError) resources->failed = true;
|
|
||||||
}];
|
|
||||||
[commands commit];
|
|
||||||
// Scheduling is required to order independent Metal queues. Completion
|
|
||||||
// remains asynchronous; resource reuse is guarded by shared GPU events.
|
|
||||||
[commands waitUntilScheduled];
|
|
||||||
if (commands.status == MTLCommandBufferStatusError)
|
|
||||||
return fail("MetalFX command buffer failed");
|
|
||||||
return begin_access(m_resources->output, true, m_value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool end_output() override {
|
|
||||||
retain_until_dawn_done();
|
|
||||||
m_outputReleased = {};
|
|
||||||
return end_access(m_resources->output, m_outputReleased, m_outputScheduled);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Allocation failures must be caught here rather than reaching Aurora's fatal
|
|
||||||
// uncaptured-error callback. Scope callbacks own their strings even on timeout.
|
|
||||||
bool pop_scope(const wgpu::Instance& instance, const wgpu::Device& device, std::string& error) {
|
|
||||||
auto message = std::make_shared<std::string>();
|
|
||||||
auto future = device.PopErrorScope(wgpu::CallbackMode::WaitAnyOnly,
|
|
||||||
[message](wgpu::PopErrorScopeStatus status, wgpu::ErrorType type, wgpu::StringView text) {
|
|
||||||
if (status != wgpu::PopErrorScopeStatus::Success || type != wgpu::ErrorType::NoError) {
|
|
||||||
const std::string_view detail{text};
|
|
||||||
*message = detail.empty() ? "MetalFX texture allocation failed" : std::string(detail);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (instance.WaitAny(future, kScheduleTimeoutNs) != wgpu::WaitStatus::Success) {
|
|
||||||
error = "Timed out checking MetalFX texture allocation";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!message->empty()) { error = *message; return false; }
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
bool supported(const wgpu::Device& device, wgpu::BackendType backend) {
|
|
||||||
if (@available(macOS 13.0, *)) {
|
|
||||||
if (!device || backend != wgpu::BackendType::Metal ||
|
|
||||||
!device.HasFeature(wgpu::FeatureName::SharedTextureMemoryIOSurface) ||
|
|
||||||
!device.HasFeature(wgpu::FeatureName::SharedFenceMTLSharedEvent)) return false;
|
|
||||||
auto native = dawn::native::metal::GetMTLDevice(device.Get());
|
|
||||||
return native && [MTLFXSpatialScalerDescriptor supportsDevice:native];
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::unique_ptr<SpatialScaler> create(const wgpu::Instance& instance,
|
|
||||||
const wgpu::Device& device, const Size& size,
|
|
||||||
std::string& error) {
|
|
||||||
error.clear();
|
|
||||||
if (@available(macOS 13.0, *)) {
|
|
||||||
@autoreleasepool {
|
|
||||||
if (!supported(device, wgpu::BackendType::Metal)) {
|
|
||||||
error = "MetalFX spatial scaling is unsupported";
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
wgpu::Limits limits{};
|
|
||||||
device.GetLimits(&limits);
|
|
||||||
if (!size.inputWidth || !size.inputHeight || size.inputWidth >= size.outputWidth ||
|
|
||||||
size.inputHeight >= size.outputHeight || size.outputWidth > limits.maxTextureDimension2D ||
|
|
||||||
size.outputHeight > limits.maxTextureDimension2D ||
|
|
||||||
(size.format != wgpu::TextureFormat::RGBA8Unorm && size.format != wgpu::TextureFormat::BGRA8Unorm)) {
|
|
||||||
error = "MetalFX requires smaller input dimensions and an RGBA8/BGRA8 unorm target";
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
if (g_liveResources.load() >= kMaxLiveResources) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
auto native = dawn::native::metal::GetMTLDevice(device.Get());
|
|
||||||
auto resources = std::make_shared<Resources>();
|
|
||||||
auto descriptor = [MTLFXSpatialScalerDescriptor new];
|
|
||||||
descriptor.inputWidth = size.inputWidth;
|
|
||||||
descriptor.inputHeight = size.inputHeight;
|
|
||||||
descriptor.outputWidth = size.outputWidth;
|
|
||||||
descriptor.outputHeight = size.outputHeight;
|
|
||||||
descriptor.colorTextureFormat = size.format == wgpu::TextureFormat::BGRA8Unorm
|
|
||||||
? MTLPixelFormatBGRA8Unorm : MTLPixelFormatRGBA8Unorm;
|
|
||||||
descriptor.outputTextureFormat = descriptor.colorTextureFormat;
|
|
||||||
descriptor.colorProcessingMode = MTLFXSpatialScalerColorProcessingModePerceptual;
|
|
||||||
resources->scaler = [descriptor newSpatialScalerWithDevice:native];
|
|
||||||
resources->nativeQueue = [native newCommandQueue];
|
|
||||||
resources->event = [native newSharedEvent];
|
|
||||||
if (!resources->scaler || !resources->nativeQueue || !resources->event) {
|
|
||||||
error = "Could not create MetalFX spatial resources";
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
auto outputDescriptor = [MTLTextureDescriptor
|
|
||||||
texture2DDescriptorWithPixelFormat:descriptor.outputTextureFormat
|
|
||||||
width:size.outputWidth height:size.outputHeight mipmapped:NO];
|
|
||||||
outputDescriptor.storageMode = MTLStorageModePrivate;
|
|
||||||
outputDescriptor.usage = resources->scaler.outputTextureUsage;
|
|
||||||
resources->privateOutput = [native newTextureWithDescriptor:outputDescriptor];
|
|
||||||
if (!resources->privateOutput) { error = "Could not allocate MetalFX private output"; return {}; }
|
|
||||||
|
|
||||||
device.PushErrorScope(wgpu::ErrorFilter::Validation);
|
|
||||||
device.PushErrorScope(wgpu::ErrorFilter::OutOfMemory);
|
|
||||||
device.PushErrorScope(wgpu::ErrorFilter::Internal);
|
|
||||||
bool allocated = resources->input.create(device, native, size.inputWidth, size.inputHeight,
|
|
||||||
size.format, resources->scaler.colorTextureUsage, wgpu::TextureUsage::RenderAttachment);
|
|
||||||
allocated = allocated && resources->output.create(device, native, size.outputWidth, size.outputHeight,
|
|
||||||
size.format, MTLTextureUsageShaderRead, wgpu::TextureUsage::TextureBinding | wgpu::TextureUsage::CopySrc);
|
|
||||||
if (allocated) {
|
|
||||||
wgpu::SharedFenceMTLSharedEventDescriptor event{};
|
|
||||||
event.sharedEvent = (__bridge void*)resources->event;
|
|
||||||
wgpu::SharedFenceDescriptor fence{};
|
|
||||||
fence.nextInChain = &event;
|
|
||||||
resources->fence = device.ImportSharedFence(&fence);
|
|
||||||
allocated = resources->fence != nullptr;
|
|
||||||
}
|
|
||||||
for (int i = 0; i < 3; ++i) {
|
|
||||||
if (!pop_scope(instance, device, error)) allocated = false;
|
|
||||||
}
|
|
||||||
if (!allocated) {
|
|
||||||
if (error.empty()) error = "Could not import MetalFX IOSurface textures into Dawn";
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
resources->scaler.colorTexture = resources->input.metal;
|
|
||||||
resources->scaler.outputTexture = resources->privateOutput;
|
|
||||||
resources->scaler.inputContentWidth = size.inputWidth;
|
|
||||||
resources->scaler.inputContentHeight = size.inputHeight;
|
|
||||||
return std::make_unique<MetalSpatialScaler>(instance, device, std::move(resources));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
error = "MetalFX requires macOS 13 or newer";
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
} // namespace aurora::webgpu::metalfx
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
#include "metalfx.hpp"
|
|
||||||
|
|
||||||
namespace aurora::webgpu::metalfx {
|
|
||||||
bool supported(const wgpu::Device&, wgpu::BackendType) { return false; }
|
|
||||||
|
|
||||||
std::unique_ptr<SpatialScaler> create(const wgpu::Instance&, const wgpu::Device&,
|
|
||||||
const Size&, std::string& error) {
|
|
||||||
error = "MetalFX is not available in this build";
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
} // namespace aurora::webgpu::metalfx
|
|
||||||
@@ -46,7 +46,6 @@ SDL_Window* g_window;
|
|||||||
SDL_Renderer* g_renderer;
|
SDL_Renderer* g_renderer;
|
||||||
float g_frameBufferScale = 0.f;
|
float g_frameBufferScale = 0.f;
|
||||||
bool g_frameBufferAspectFit = true;
|
bool g_frameBufferAspectFit = true;
|
||||||
std::atomic_bool g_forceAspect169{false};
|
|
||||||
bool g_presentSurfaceFill = false;
|
bool g_presentSurfaceFill = false;
|
||||||
int g_presentAspectWidth = 0;
|
int g_presentAspectWidth = 0;
|
||||||
int g_presentAspectHeight = 0;
|
int g_presentAspectHeight = 0;
|
||||||
@@ -526,20 +525,7 @@ AuroraWindowSize get_window_size() {
|
|||||||
int fb_w = native_fb_w;
|
int fb_w = native_fb_w;
|
||||||
int fb_h = native_fb_h;
|
int fb_h = native_fb_h;
|
||||||
const auto [baseW, baseH] = vi::configured_fb_size();
|
const auto [baseW, baseH] = vi::configured_fb_size();
|
||||||
if (g_forceAspect169.load(std::memory_order_acquire) && native_fb_w > 0 && native_fb_h > 0) {
|
if (g_frameBufferAspectFit && baseW > 0 && baseH > 0) {
|
||||||
if (g_frameBufferScale > 0.f && baseW > 0 && baseH > 0) {
|
|
||||||
const auto [scaledW, scaledH] =
|
|
||||||
scale_frame_buffer_to_aspect(static_cast<int>(baseW), static_cast<int>(baseH),
|
|
||||||
g_frameBufferScale, 16.f / 9.f);
|
|
||||||
fb_w = scaledW;
|
|
||||||
fb_h = scaledH;
|
|
||||||
} else {
|
|
||||||
fb_w = std::min(native_fb_w,
|
|
||||||
std::max(1, static_cast<int>(std::lround(native_fb_h * (16.f / 9.f)))));
|
|
||||||
fb_h = std::min(native_fb_h,
|
|
||||||
std::max(1, static_cast<int>(std::lround(native_fb_w * (9.f / 16.f)))));
|
|
||||||
}
|
|
||||||
} else if (g_frameBufferAspectFit && baseW > 0 && baseH > 0) {
|
|
||||||
float renderScale = g_frameBufferScale > 0.f ? g_frameBufferScale : 1.f;
|
float renderScale = g_frameBufferScale > 0.f ? g_frameBufferScale : 1.f;
|
||||||
if (g_frameBufferScale <= 0.f) {
|
if (g_frameBufferScale <= 0.f) {
|
||||||
renderScale = std::min(static_cast<float>(native_fb_w) / static_cast<float>(baseW),
|
renderScale = std::min(static_cast<float>(native_fb_w) / static_cast<float>(baseW),
|
||||||
@@ -773,14 +759,6 @@ void set_frame_buffer_aspect_fit(bool fit) {
|
|||||||
request_frame_buffer_resize();
|
request_frame_buffer_resize();
|
||||||
}
|
}
|
||||||
|
|
||||||
void set_force_aspect_16_9(bool force) {
|
|
||||||
if (g_forceAspect169.load(std::memory_order_relaxed) == force) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
g_forceAspect169.store(force, std::memory_order_release);
|
|
||||||
request_frame_buffer_resize();
|
|
||||||
}
|
|
||||||
|
|
||||||
void set_present_surface_fill(bool fill) {
|
void set_present_surface_fill(bool fill) {
|
||||||
g_presentSurfaceFill = fill;
|
g_presentSurfaceFill = fill;
|
||||||
}
|
}
|
||||||
@@ -803,10 +781,6 @@ void unlock_present_aspect_ratio() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool get_present_aspect_ratio(float& aspect) noexcept {
|
bool get_present_aspect_ratio(float& aspect) noexcept {
|
||||||
if (g_forceAspect169.load(std::memory_order_acquire)) {
|
|
||||||
aspect = 16.f / 9.f;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (g_presentSurfaceFill && g_window != nullptr) {
|
if (g_presentSurfaceFill && g_window != nullptr) {
|
||||||
// Queried once per presentation snapshot; use the cached native client size
|
// Queried once per presentation snapshot; use the cached native client size
|
||||||
// instead of re-entering SDL for a value the window procedure already knows.
|
// instead of re-entering SDL for a value the window procedure already knows.
|
||||||
|
|||||||
@@ -56,7 +56,6 @@ void sync_frame_buffer_size() noexcept;
|
|||||||
void request_frame_buffer_resize();
|
void request_frame_buffer_resize();
|
||||||
void set_frame_buffer_scale(float scale);
|
void set_frame_buffer_scale(float scale);
|
||||||
void set_frame_buffer_aspect_fit(bool fit);
|
void set_frame_buffer_aspect_fit(bool fit);
|
||||||
void set_force_aspect_16_9(bool force);
|
|
||||||
void set_present_surface_fill(bool fill);
|
void set_present_surface_fill(bool fill);
|
||||||
void lock_present_aspect_ratio(int width, int height);
|
void lock_present_aspect_ratio(int width, int height);
|
||||||
void unlock_present_aspect_ratio();
|
void unlock_present_aspect_ratio();
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ if (NOT TARGET gtest)
|
|||||||
FetchContent_Declare(googletest
|
FetchContent_Declare(googletest
|
||||||
URL https://github.com/google/googletest/archive/refs/tags/v1.17.0.tar.gz
|
URL https://github.com/google/googletest/archive/refs/tags/v1.17.0.tar.gz
|
||||||
URL_HASH SHA256=65fab701d9829d38cb77c14acdc431d2108bfdbf8979e40eb8ae567edf10b27c
|
URL_HASH SHA256=65fab701d9829d38cb77c14acdc431d2108bfdbf8979e40eb8ae567edf10b27c
|
||||||
DOWNLOAD_EXTRACT_TIMESTAMP FALSE
|
DOWNLOAD_EXTRACT_TIMESTAMP TRUE
|
||||||
EXCLUDE_FROM_ALL
|
EXCLUDE_FROM_ALL
|
||||||
)
|
)
|
||||||
FetchContent_MakeAvailable(googletest)
|
FetchContent_MakeAvailable(googletest)
|
||||||
@@ -18,7 +18,6 @@ if (AURORA_ENABLE_GX)
|
|||||||
gx_fifo_test.cpp
|
gx_fifo_test.cpp
|
||||||
gx_test_stubs.cpp
|
gx_test_stubs.cpp
|
||||||
texture_bind_group_cache_key_test.cpp
|
texture_bind_group_cache_key_test.cpp
|
||||||
renderer_regression_test.cpp
|
|
||||||
../lib/gfx/efb_ram_encoder.cpp
|
../lib/gfx/efb_ram_encoder.cpp
|
||||||
# GX API implementations (encoders)
|
# GX API implementations (encoders)
|
||||||
../lib/dolphin/gx/GXBump.cpp
|
../lib/dolphin/gx/GXBump.cpp
|
||||||
@@ -67,18 +66,6 @@ if (AURORA_ENABLE_GX)
|
|||||||
)
|
)
|
||||||
|
|
||||||
gtest_discover_tests(gx_fifo_tests)
|
gtest_discover_tests(gx_fifo_tests)
|
||||||
|
|
||||||
option(AURORA_BUILD_GPU_TESTS "Build renderer pixel tests requiring a graphics device" OFF)
|
|
||||||
if (AURORA_BUILD_GPU_TESTS)
|
|
||||||
add_executable(gx_readback_tests gpu_readback_test.cpp)
|
|
||||||
target_compile_features(gx_readback_tests PRIVATE cxx_std_20)
|
|
||||||
target_include_directories(gx_readback_tests PRIVATE ../lib)
|
|
||||||
target_link_libraries(gx_readback_tests PRIVATE
|
|
||||||
aurora::core aurora::gx aurora::pad aurora::vi aurora::mtx aurora::si
|
|
||||||
dawn::dawncpp_headers absl::flat_hash_map absl::btree TracyClient)
|
|
||||||
add_test(NAME gx_readback_tests COMMAND gx_readback_tests "${CMAKE_CURRENT_BINARY_DIR}/readback-cache")
|
|
||||||
set_tests_properties(gx_readback_tests PROPERTIES TIMEOUT 90)
|
|
||||||
endif ()
|
|
||||||
endif () # AURORA_ENABLE_GX
|
endif () # AURORA_ENABLE_GX
|
||||||
|
|
||||||
# DVD API tests
|
# DVD API tests
|
||||||
|
|||||||
@@ -1,363 +0,0 @@
|
|||||||
// ROM-free integration probe. Links the actual maintained Aurora renderer.
|
|
||||||
#include "gfx/common.hpp"
|
|
||||||
#include "gfx/clear.hpp"
|
|
||||||
#include "gfx/efb_ram_copy.hpp"
|
|
||||||
#include "gfx/pipeline_cache.hpp"
|
|
||||||
#include "gfx/texture.hpp"
|
|
||||||
#include "gx/gx.hpp"
|
|
||||||
#include "gx/fifo.hpp"
|
|
||||||
#include "gx/command_processor.hpp"
|
|
||||||
#include "gx/frame_interpolation.hpp"
|
|
||||||
#include <dolphin/gx.h>
|
|
||||||
#include <aurora/aurora.h>
|
|
||||||
#include <array>
|
|
||||||
#include <bit>
|
|
||||||
#include <atomic>
|
|
||||||
#include <chrono>
|
|
||||||
#include <cstdio>
|
|
||||||
#include <filesystem>
|
|
||||||
#include <stdexcept>
|
|
||||||
#include <thread>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
using namespace aurora;
|
|
||||||
std::atomic<unsigned> errors{};
|
|
||||||
std::atomic<unsigned> guestWrites{};
|
|
||||||
// Keep destinations alive through shutdown, including any failing wait.
|
|
||||||
std::array<uint8_t, 16 * 16 * 4 + 32> guarded;
|
|
||||||
std::array<uint8_t, 16 * 16 * 4 + 32> guardedBake;
|
|
||||||
void require(bool value, const char* message) {
|
|
||||||
if (!value) throw std::runtime_error(message);
|
|
||||||
}
|
|
||||||
void submit(bool final, bool download = false, bool async = false) {
|
|
||||||
auto encoder = webgpu::g_device.CreateCommandEncoder();
|
|
||||||
if (final) gfx::end_frame(encoder); else gfx::end_batch(encoder);
|
|
||||||
gfx::render(encoder);
|
|
||||||
if (download) gfx::efb_ram::encode_downloads(encoder);
|
|
||||||
if (async) gfx::efb_ram::encode_async_downloads(encoder);
|
|
||||||
auto commands = encoder.Finish();
|
|
||||||
webgpu::g_queue.Submit(1, &commands);
|
|
||||||
if (download) require(gfx::efb_ram::complete_downloads(), "EFB readback failed");
|
|
||||||
gfx::after_submit();
|
|
||||||
if (!final) require(gfx::resume_frame(), "Batch resume failed");
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr std::array<std::array<uint8_t, 4>, 4> colors{{
|
|
||||||
{255, 0, 0, 255}, {0, 255, 0, 255}, {0, 0, 255, 255}, {255, 255, 0, 255}}};
|
|
||||||
using Pixels = std::vector<uint8_t>;
|
|
||||||
Pixels expected(unsigned extent) {
|
|
||||||
Pixels bytes(extent * extent * 4);
|
|
||||||
// GX RGBA8: 4x4 tiles, sixteen A/R pairs followed by sixteen G/B pairs.
|
|
||||||
for (unsigned y = 0; y < extent; ++y) for (unsigned x = 0; x < extent; ++x) {
|
|
||||||
const auto color = colors[y * 4 / extent];
|
|
||||||
const auto tile = ((y / 4) * (extent / 4) + x / 4) * 64;
|
|
||||||
const auto pair = ((y % 4) * 4 + x % 4) * 2;
|
|
||||||
bytes[tile + pair] = color[3]; bytes[tile + pair + 1] = color[0];
|
|
||||||
bytes[tile + 32 + pair] = color[1]; bytes[tile + 33 + pair] = color[2];
|
|
||||||
}
|
|
||||||
return bytes;
|
|
||||||
}
|
|
||||||
|
|
||||||
Pixels run(unsigned splitEvery, bool async = false, bool offscreen = false, unsigned geometry = 0, bool capacityStress = false, bool interpolate = false, bool frameWorker = false, unsigned copyCase = 0) {
|
|
||||||
require(!async || !offscreen, "Combined probe mode is not supported");
|
|
||||||
guardedBake.fill(0xa5);
|
|
||||||
gx::g_gxState.clearColor = {0.f, 0.f, 0.f, 1.f};
|
|
||||||
require(frameWorker ? aurora_begin_frame() : gfx::begin_frame(), "Frame begin failed");
|
|
||||||
std::array<std::array<float, 3>, 4> positions{};
|
|
||||||
if (geometry) {
|
|
||||||
alignas(32) static std::array<uint8_t, 32768> fifo;
|
|
||||||
GXInit(fifo.data(), fifo.size());
|
|
||||||
gx::g_gxState.viewportPolicy = AURORA_VIEWPORT_NATIVE;
|
|
||||||
GXSetViewport(0.f, 0.f, 64.f, 64.f, 0.f, 1.f);
|
|
||||||
GXSetScissor(0, 0, 64, 64);
|
|
||||||
GXSetCullMode(GX_CULL_NONE);
|
|
||||||
GXSetZMode(false, GX_ALWAYS, false);
|
|
||||||
GXSetBlendMode(GX_BM_NONE, GX_BL_ONE, GX_BL_ZERO, GX_LO_COPY);
|
|
||||||
GXSetColorUpdate(true); GXSetAlphaUpdate(true);
|
|
||||||
GXSetNumTexGens(0); GXSetNumChans(1); GXSetNumTevStages(copyCase ? copyCase : 1);
|
|
||||||
for (unsigned stage = 1; stage < copyCase; ++stage) {
|
|
||||||
GXSetTevOrder(static_cast<GXTevStageID>(stage), GX_TEXCOORD_NULL, GX_TEXMAP_NULL, GX_COLOR0A0);
|
|
||||||
GXSetTevOp(static_cast<GXTevStageID>(stage), GX_PASSCLR);
|
|
||||||
}
|
|
||||||
GXSetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD_NULL, GX_TEXMAP_NULL, GX_COLOR0A0);
|
|
||||||
GXSetTevOp(GX_TEVSTAGE0, GX_PASSCLR);
|
|
||||||
GXSetChanCtrl(GX_COLOR0A0, false, GX_SRC_REG, GX_SRC_VTX, GX_LIGHT_NULL, GX_DF_NONE, GX_AF_NONE);
|
|
||||||
const float projection[]{interpolate ? 0.f : 1.f, 1.f, 0.f, 1.f, 0.f, 0.f, -0.5f};
|
|
||||||
GXSetProjectionv(projection);
|
|
||||||
GXClearVtxDesc();
|
|
||||||
GXSetVtxDesc(GX_VA_POS, geometry == 2 ? GX_INDEX8 : GX_DIRECT);
|
|
||||||
GXSetVtxDesc(GX_VA_CLR0, GX_DIRECT);
|
|
||||||
if (geometry == 2) GXSetArray(GX_VA_POS, positions.data(), sizeof(positions), sizeof(positions[0]), true);
|
|
||||||
GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_POS, GX_POS_XYZ, GX_F32, 0);
|
|
||||||
GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_CLR0, GX_CLR_RGBA, GX_RGBA8, 0);
|
|
||||||
gx::fifo::drain();
|
|
||||||
}
|
|
||||||
const auto pipeline = gfx::pipeline_ref(gfx::clear::PipelineConfig{});
|
|
||||||
for (unsigned band = 0; band < 4; ++band) {
|
|
||||||
const auto c = colors[band];
|
|
||||||
if (geometry) {
|
|
||||||
const float top = 1.f - band * 0.5f;
|
|
||||||
const float bottom = top - 0.5f;
|
|
||||||
const float z = interpolate ? -1.f : 0.f;
|
|
||||||
positions = {{{-1.f, top, z}, {1.f, top, z}, {1.f, bottom, z}, {-1.f, bottom, z}}};
|
|
||||||
// Keep the same array address/format and change only its bytes between draws.
|
|
||||||
if (geometry == 2) GXInvalidateVtxCache();
|
|
||||||
if (geometry == 3) {
|
|
||||||
std::array<uint8_t, 64> raw{};
|
|
||||||
for (unsigned index = 0; index < positions.size(); ++index) {
|
|
||||||
for (unsigned axis = 0; axis < 3; ++axis) {
|
|
||||||
const auto bits = std::bit_cast<uint32_t>(positions[index][axis]);
|
|
||||||
for (unsigned byte = 0; byte < 4; ++byte)
|
|
||||||
raw[index * 16 + axis * 4 + byte] = bits >> (24 - byte * 8);
|
|
||||||
}
|
|
||||||
std::copy(c.begin(), c.end(), raw.begin() + index * 16 + 12);
|
|
||||||
}
|
|
||||||
require(gx::fifo::submit_raw_draw(GX_QUADS, GX_VTXFMT0, raw.data(), 4, raw.size()),
|
|
||||||
"Raw bridge rejected valid quad");
|
|
||||||
} else {
|
|
||||||
GXBegin(GX_QUADS, GX_VTXFMT0, 4);
|
|
||||||
for (unsigned index = 0; index < positions.size(); ++index) {
|
|
||||||
if (geometry == 2) GXPosition1x8(index);
|
|
||||||
else GXPosition3f32(positions[index][0], positions[index][1], positions[index][2]);
|
|
||||||
GXColor4u8(c[0], c[1], c[2], 255);
|
|
||||||
}
|
|
||||||
GXEnd();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
gfx::push_draw_command(gfx::clear::DrawData{
|
|
||||||
.pipeline = pipeline,
|
|
||||||
.color = {c[0] / 255., c[1] / 255., c[2] / 255., 1.},
|
|
||||||
.depth = 0.5f,
|
|
||||||
.useScissor = true,
|
|
||||||
.scissor = {0, static_cast<int32_t>(band * 16), 64, 16}});
|
|
||||||
}
|
|
||||||
if (offscreen && band == 0) {
|
|
||||||
// Suspend a partially recorded EFB, bake an independently observable copy,
|
|
||||||
// then resume it before a possible capacity-boundary submission.
|
|
||||||
gfx::begin_offscreen(64, 64);
|
|
||||||
gfx::push_draw_command(gfx::clear::DrawData{
|
|
||||||
.pipeline = pipeline, .color = {1., 0., 1., 1.}, .depth = 0.25f});
|
|
||||||
if (capacityStress) for (unsigned draw = 0; draw < 24; ++draw) {
|
|
||||||
gfx::push_draw_command(gfx::clear::DrawData{
|
|
||||||
.pipeline = pipeline, .color = {1., 0., 1., 1.}, .depth = 0.25f,
|
|
||||||
.useScissor = true, .scissor = {0, 0, 4, 4}});
|
|
||||||
}
|
|
||||||
auto baked = gfx::new_render_texture(64, 64, GX_TF_RGBA8, "Aurora probe offscreen bake");
|
|
||||||
gfx::resolve_pass(baked, {0, 0, 64, 64}, false, false, false,
|
|
||||||
{0.f, 0.f, 0.f, 1.f}, 1.f, GX_TF_RGBA8, nullptr, false,
|
|
||||||
nullptr, false, 1.f, false, false, true);
|
|
||||||
gfx::efb_ram::schedule(guardedBake.data() + 16, 16, 16, GX_TF_RGBA8, baked);
|
|
||||||
gfx::end_offscreen();
|
|
||||||
if (capacityStress) {
|
|
||||||
require(gfx::efb_ram::prepare_downloads(), "Early bake readback preparation failed");
|
|
||||||
for (unsigned draw = 0; draw < 24; ++draw) {
|
|
||||||
gfx::push_draw_command(gfx::clear::DrawData{
|
|
||||||
.pipeline = pipeline, .color = {1., 0., 0., 1.}, .depth = 0.5f,
|
|
||||||
.useScissor = true, .scissor = {0, 0, 4, 4}});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (splitEvery && band < 3 && (band + 1) % splitEvery == 0) submit(false);
|
|
||||||
}
|
|
||||||
auto texture = gfx::new_render_texture(64, 64, GX_TF_RGBA8, "Aurora probe persistent copy");
|
|
||||||
static std::array<uint8_t, 64 * 64 * 4> copyDestination;
|
|
||||||
if (copyCase) {
|
|
||||||
gx::fifo::drain();
|
|
||||||
GXSetTexCopySrc(0, 0, 64, 64);
|
|
||||||
GXSetTexCopyDst(64, 64, GX_TF_RGBA8, GX_FALSE);
|
|
||||||
GXCopyTex(copyDestination.data(), GX_FALSE);
|
|
||||||
texture = gx::g_gxState.copyTextures.at(copyDestination.data()).handle;
|
|
||||||
} else {
|
|
||||||
// A partial clear forces the real snapshot and clear-uniform paths after the copy.
|
|
||||||
gfx::resolve_pass(texture, {0, 0, 64, 64}, true, true, true, {0.f, 0.f, 0.f, 1.f},
|
|
||||||
1.f, GX_TF_RGBA8, nullptr, false, nullptr, false, 1.f, false, false, true);
|
|
||||||
}
|
|
||||||
guarded.fill(0xa5);
|
|
||||||
const unsigned extent = async ? 4 : 16;
|
|
||||||
const unsigned bytes = extent * extent * 4;
|
|
||||||
gfx::efb_ram::schedule(guarded.data() + 16, extent, extent, GX_TF_RGBA8, texture);
|
|
||||||
const auto before = guestWrites.load(std::memory_order_acquire);
|
|
||||||
if (async) gfx::efb_ram::seal_async_downloads();
|
|
||||||
else require(gfx::efb_ram::prepare_downloads(), "Readback preparation failed");
|
|
||||||
if (frameWorker) {
|
|
||||||
require(!async && aurora_flush_efb_copies_to_ram(), "Worker-mode EFB readback failed");
|
|
||||||
aurora_end_frame();
|
|
||||||
} else submit(true, !async, async);
|
|
||||||
if (async) {
|
|
||||||
const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(5);
|
|
||||||
while (guestWrites.load(std::memory_order_acquire) == before) {
|
|
||||||
webgpu::g_instance.ProcessEvents();
|
|
||||||
require(std::chrono::steady_clock::now() < deadline, "Async readback did not complete");
|
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
require(std::all_of(guarded.begin(), guarded.begin() + 16, [](auto b) { return b == 0xa5; }) &&
|
|
||||||
std::all_of(guarded.begin() + 16 + bytes, guarded.end(), [](auto b) { return b == 0xa5; }),
|
|
||||||
"Readback wrote outside its destination");
|
|
||||||
if (offscreen) {
|
|
||||||
require(std::all_of(guardedBake.begin(), guardedBake.begin() + 16, [](auto b) { return b == 0xa5; }) &&
|
|
||||||
std::all_of(guardedBake.end() - 16, guardedBake.end(), [](auto b) { return b == 0xa5; }),
|
|
||||||
"Offscreen readback wrote outside its destination");
|
|
||||||
for (unsigned tile = 0; tile < 16; ++tile) for (unsigned pair = 0; pair < 16; ++pair) {
|
|
||||||
const auto offset = 16 + tile * 64 + pair * 2;
|
|
||||||
require(guardedBake[offset] == 255 && guardedBake[offset + 1] == 255 &&
|
|
||||||
guardedBake[offset + 32] == 0 && guardedBake[offset + 33] == 255,
|
|
||||||
"Offscreen bake did not preserve expected magenta pixels");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Pixels pixels(guarded.begin() + 16, guarded.begin() + 16 + bytes);
|
|
||||||
return pixels;
|
|
||||||
}
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
int main(int argc, char** argv) {
|
|
||||||
if (argc != 2) return 2;
|
|
||||||
std::filesystem::create_directories(argv[1]);
|
|
||||||
AuroraConfig config{};
|
|
||||||
config.appName = "Aurora readback regression tests";
|
|
||||||
config.userPath = argv[1];
|
|
||||||
config.cachePath = argv[1];
|
|
||||||
config.resourcesPath = argv[1];
|
|
||||||
config.desiredBackend = BACKEND_AUTO;
|
|
||||||
config.windowWidth = 64;
|
|
||||||
config.windowHeight = 64;
|
|
||||||
config.msaa = 1;
|
|
||||||
config.maxTextureAnisotropy = 1;
|
|
||||||
config.logLevel = LOG_INFO;
|
|
||||||
config.logCallback = [](AuroraLogLevel level, const char* module, const char* message, unsigned size) {
|
|
||||||
if (level >= LOG_ERROR) ++errors;
|
|
||||||
std::fprintf(stderr, "[%s] %.*s\n", module, static_cast<int>(size), message);
|
|
||||||
};
|
|
||||||
const auto initialized = aurora_initialize(1, argv, &config);
|
|
||||||
if (initialized.initializationStatus != AURORA_INITIALIZATION_SUCCESS) return 3;
|
|
||||||
aurora_set_skip_unready_pipelines(true);
|
|
||||||
aurora_set_guest_write_hooks(nullptr, [](const void*, size_t) {
|
|
||||||
guestWrites.fetch_add(1, std::memory_order_release);
|
|
||||||
});
|
|
||||||
try {
|
|
||||||
const auto prewarmQueued = gfx::queued_pipeline_count();
|
|
||||||
const auto prewarmDeadline = std::chrono::steady_clock::now() + std::chrono::seconds(60);
|
|
||||||
while (gfx::queued_pipeline_count() != 0) {
|
|
||||||
require(std::chrono::steady_clock::now() < prewarmDeadline, "Seeded pipeline prewarm did not finish");
|
|
||||||
webgpu::g_instance.ProcessEvents();
|
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
|
||||||
}
|
|
||||||
std::printf("Actual Aurora completed seeded startup queue (%u observed pending)\n", prewarmQueued);
|
|
||||||
const auto control = run(0);
|
|
||||||
for (unsigned band = 0; band < 4; ++band) {
|
|
||||||
const auto tile = band * 4 * 64;
|
|
||||||
std::fprintf(stderr, "band=%u ARGB=%u,%u,%u,%u\n", band, control[tile],
|
|
||||||
control[tile + 1], control[tile + 32], control[tile + 33]);
|
|
||||||
}
|
|
||||||
require(control == expected(16), "Unsplit pixels differ from independently expected GX data");
|
|
||||||
for (unsigned iteration = 0; iteration < 9; ++iteration) {
|
|
||||||
const auto splitEvery = iteration % 3 + 1;
|
|
||||||
require(run(splitEvery) == control, "Split pixels differ from unsplit control");
|
|
||||||
std::printf("Actual Aurora split=%u iteration=%u matched native tiled readback\n", splitEvery, iteration);
|
|
||||||
}
|
|
||||||
for (unsigned iteration = 0; iteration < 9; ++iteration) {
|
|
||||||
require(run(iteration % 3 + 1, true) == expected(4), "Async pixels differ from expected GX data");
|
|
||||||
std::printf("Actual Aurora async iteration=%u matched native tiled readback\n", iteration);
|
|
||||||
}
|
|
||||||
for (unsigned splitEvery = 0; splitEvery < 4; ++splitEvery) {
|
|
||||||
require(run(splitEvery, false, true) == expected(16), "Offscreen interlude changed the suspended EFB");
|
|
||||||
std::printf("Actual Aurora offscreen split=%u preserved bake and suspended EFB\n", splitEvery);
|
|
||||||
}
|
|
||||||
for (unsigned splitEvery = 0; splitEvery < 4; ++splitEvery) {
|
|
||||||
require(run(splitEvery, false, false, true) == expected(16), "GX FIFO quad pixels differ from expected output");
|
|
||||||
std::printf("Actual Aurora GX FIFO split=%u preserved direct vertices, indices and uniforms\n", splitEvery);
|
|
||||||
}
|
|
||||||
for (unsigned splitEvery = 0; splitEvery < 4; ++splitEvery) {
|
|
||||||
require(run(splitEvery, false, false, 2) == expected(16), "Invalidated GX array pixels differ from expected output");
|
|
||||||
std::printf("Actual Aurora GX invalidation split=%u refreshed the same array address\n", splitEvery);
|
|
||||||
}
|
|
||||||
const gfx::StagingSizes physical{gfx::VertexBufferSize, gfx::UniformBufferSize,
|
|
||||||
gfx::IndexBufferSize, gfx::StorageBufferSize};
|
|
||||||
const auto uniformTail = gx::MaxUniformSize + 32 * gfx::staging_uniform_bytes(48);
|
|
||||||
for (unsigned buffer = 0; buffer < 4; ++buffer) {
|
|
||||||
auto limits = physical;
|
|
||||||
limits[buffer] = buffer == 0 ? 128 : buffer == 1 ? uniformTail + 512 :
|
|
||||||
buffer == 2 ? 24 : 2 * gfx::staging_storage_bytes(48);
|
|
||||||
gfx::set_staging_capacity_limits_for_testing(limits);
|
|
||||||
const auto before = gfx::staging_split_count();
|
|
||||||
require(run(0, false, false, buffer == 1 ? 0 : buffer == 3 ? 2 : 1) == expected(16),
|
|
||||||
"Automatic capacity split changed pixels");
|
|
||||||
require(gfx::staging_split_count() > before, "Forced capacity did not split");
|
|
||||||
const auto highWater = gfx::staging_high_water();
|
|
||||||
for (unsigned i = 0; i < limits.size(); ++i)
|
|
||||||
require(highWater[i] <= limits[i], "Actual staging usage exceeded admission budget");
|
|
||||||
std::printf("Actual staging high-water V/U/I/S=%llu/%llu/%llu/%llu bytes\n",
|
|
||||||
static_cast<unsigned long long>(highWater[0]), static_cast<unsigned long long>(highWater[1]),
|
|
||||||
static_cast<unsigned long long>(highWater[2]), static_cast<unsigned long long>(highWater[3]));
|
|
||||||
std::printf("Actual Aurora automatic capacity buffer=%u splits=%llu matched pixels\n", buffer,
|
|
||||||
static_cast<unsigned long long>(gfx::staging_split_count() - before));
|
|
||||||
}
|
|
||||||
auto limits = physical;
|
|
||||||
limits[0] = 128;
|
|
||||||
gfx::set_staging_capacity_limits_for_testing(limits);
|
|
||||||
require(run(0, false, false, 3) == expected(16), "Raw bridge capacity split changed pixels");
|
|
||||||
std::puts("Actual Aurora raw bridge capacity split preserved direct quad pixels");
|
|
||||||
limits = physical;
|
|
||||||
limits[1] = uniformTail + 768;
|
|
||||||
gfx::set_staging_capacity_limits_for_testing(limits);
|
|
||||||
const auto beforeBake = gfx::staging_split_count();
|
|
||||||
require(run(0, false, true, 0, true) == expected(16),
|
|
||||||
"Automatic offscreen split changed bake or suspended EFB");
|
|
||||||
require(gfx::staging_split_count() - beforeBake >= 9, "Offscreen test did not reuse all staging slots");
|
|
||||||
std::printf("Actual Aurora automatic offscreen/readback splits=%llu preserved all pixels\n",
|
|
||||||
static_cast<unsigned long long>(gfx::staging_split_count() - beforeBake));
|
|
||||||
gfx::set_staging_capacity_limits_for_testing(physical);
|
|
||||||
aurora_set_frame_interpolation_fps(120);
|
|
||||||
for (unsigned frame = 0; frame < 3; ++frame)
|
|
||||||
require(run(0, false, false, 2, false, true) == expected(16), "Perspective warmup changed pixels");
|
|
||||||
AuroraFrameInterpolationDiagnostics interpolation{};
|
|
||||||
gx::get_frame_interpolation_diagnostics(interpolation);
|
|
||||||
require(interpolation.matchable > 0 && interpolation.activeSamples > 0,
|
|
||||||
"Interpolation probe did not establish matching perspective draws");
|
|
||||||
limits = physical;
|
|
||||||
limits[3] = 2 * gfx::staging_storage_bytes(48);
|
|
||||||
gfx::set_staging_capacity_limits_for_testing(limits);
|
|
||||||
require(run(0, false, false, 2, false, true) == expected(16), "Interpolated split changed native pixels");
|
|
||||||
gx::get_frame_interpolation_diagnostics(interpolation);
|
|
||||||
require(!interpolation.replaySafe, "Split frame incorrectly retained interpolation replay");
|
|
||||||
aurora_set_frame_interpolation_fps(0);
|
|
||||||
std::puts("Actual Aurora matched perspective interpolation survived capacity split and disabled replay");
|
|
||||||
limits = physical;
|
|
||||||
limits[0] = 32; // One quad needs 64 bytes: typed rejection before any draw allocation.
|
|
||||||
gfx::set_staging_capacity_limits_for_testing(limits);
|
|
||||||
bool oversized = false;
|
|
||||||
try { run(0, false, false, 1); }
|
|
||||||
catch (const gfx::StagingCapacityError&) { oversized = true; }
|
|
||||||
require(oversized, "Oversized primitive was not rejected");
|
|
||||||
require(gfx::staging_usage() == gfx::StagingSizes{}, "Oversized primitive partially allocated");
|
|
||||||
gx::fifo::clear_buffer();
|
|
||||||
gfx::abort_frame();
|
|
||||||
gfx::set_staging_capacity_limits_for_testing(physical);
|
|
||||||
std::puts("Actual Aurora oversized primitive rejected before staging mutation");
|
|
||||||
require(run(0, false, false, 1) == expected(16), "Renderer failed after rejected primitive cleanup");
|
|
||||||
limits = physical;
|
|
||||||
limits[3] = 2 * gfx::staging_storage_bytes(48);
|
|
||||||
gfx::set_staging_capacity_limits_for_testing(limits);
|
|
||||||
aurora_set_frame_interpolation_fps(120);
|
|
||||||
for (unsigned frame = 0; frame < 16; ++frame)
|
|
||||||
require(run(0, false, false, 2, false, true, true) == expected(16),
|
|
||||||
"Frame-worker capacity split changed pixels");
|
|
||||||
// Grant preparation of the next frame before joining DONE, exactly as the
|
|
||||||
// real producer does; leave no worker waiting for a future begin_frame.
|
|
||||||
require(aurora_begin_frame(), "Final worker frame preparation failed");
|
|
||||||
aurora::wait_for_frame_worker();
|
|
||||||
gfx::abort_frame();
|
|
||||||
aurora_set_frame_interpolation_fps(0);
|
|
||||||
gfx::set_staging_capacity_limits_for_testing(physical);
|
|
||||||
std::puts("Actual Aurora frame worker completed 16 capacity-split perspective frames");
|
|
||||||
require(errors == 0, "Renderer reported an error");
|
|
||||||
} catch (const std::exception& error) {
|
|
||||||
std::fprintf(stderr, "FAIL: %s\n", error.what());
|
|
||||||
aurora_shutdown();
|
|
||||||
return 4;
|
|
||||||
}
|
|
||||||
aurora_shutdown();
|
|
||||||
if (errors != 0) return 4;
|
|
||||||
std::puts("Actual Aurora clear/resolve/snapshot/downsample/readback batches passed");
|
|
||||||
}
|
|
||||||
@@ -462,18 +462,8 @@ TEST(FrameInterpolationContract, IndexedPaletteHistoryKeepsAbsoluteVertexSlots)
|
|||||||
std::array<uint8_t, uniformSize> changedSource{};
|
std::array<uint8_t, uniformSize> changedSource{};
|
||||||
aurora::gx::begin_frame_interpolation();
|
aurora::gx::begin_frame_interpolation();
|
||||||
const auto changedRanges = recordFrame(changedTopology, 91.0f, 9.0f, changedSource);
|
const auto changedRanges = recordFrame(changedTopology, 91.0f, 9.0f, changedSource);
|
||||||
// Staging may reserve a copy for sibling matching; the correctness contract
|
EXPECT_EQ(changedRanges[0].size, 0u);
|
||||||
// is that an unmatched topology receives the current pose unchanged.
|
|
||||||
const auto expectedCurrent = changedSource;
|
|
||||||
aurora::gx::finalize_frame_interpolation();
|
aurora::gx::finalize_frame_interpolation();
|
||||||
EXPECT_EQ(changedSource, expectedCurrent);
|
|
||||||
if (changedRanges[0].size != 0) {
|
|
||||||
// No replacement range also correctly selects the original current uniform.
|
|
||||||
ASSERT_EQ(changedRanges[0].size, uniformSize);
|
|
||||||
const auto& duplicated = aurora::gfx::testing::uniform_allocation(changedRanges[0].offset);
|
|
||||||
ASSERT_EQ(duplicated.size(), expectedCurrent.size());
|
|
||||||
EXPECT_EQ(std::memcmp(duplicated.data(), expectedCurrent.data(), expectedCurrent.size()), 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
aurora::gx::set_frame_interpolation_fps(0);
|
aurora::gx::set_frame_interpolation_fps(0);
|
||||||
aurora::gx::begin_frame_interpolation();
|
aurora::gx::begin_frame_interpolation();
|
||||||
@@ -656,34 +646,12 @@ TEST(TevRegisterLivenessContract, PacksOneUniformWhenBothHalvesNeedInitialValue)
|
|||||||
auto config = baseline;
|
auto config = baseline;
|
||||||
config.tevStages[0].colorPass.a = GX_CC_C0;
|
config.tevStages[0].colorPass.a = GX_CC_C0;
|
||||||
config.tevStages[0].alphaPass.a = GX_CA_A0;
|
config.tevStages[0].alphaPass.a = GX_CA_A0;
|
||||||
config.tevStages[0].colorPass.b = GX_CC_KONST;
|
|
||||||
config.tevStages[0].kcSel = GX_TEV_KCSEL_K0;
|
|
||||||
|
|
||||||
|
const auto baselineInfo = aurora::gx::build_shader_info(baseline);
|
||||||
const auto info = aurora::gx::build_shader_info(config);
|
const auto info = aurora::gx::build_shader_info(config);
|
||||||
EXPECT_TRUE(info.loadsTevRegRgb.test(GX_TEVREG0));
|
EXPECT_TRUE(info.loadsTevRegRgb.test(GX_TEVREG0));
|
||||||
EXPECT_TRUE(info.loadsTevRegAlpha.test(GX_TEVREG0));
|
EXPECT_TRUE(info.loadsTevRegAlpha.test(GX_TEVREG0));
|
||||||
// The final allocation is alignment-rounded, so adding one register need
|
EXPECT_EQ(info.uniformSize, baselineInfo.uniformSize + sizeof(aurora::Vec4<float>));
|
||||||
// not increase it. Verify actual packing with a distinct following K color.
|
|
||||||
const auto savedReg = g_gxState.colorRegs[GX_TEVREG0];
|
|
||||||
const auto savedKColor = g_gxState.kcolors[GX_KCOLOR0];
|
|
||||||
g_gxState.colorRegs[GX_TEVREG0] = {11.f, 22.f, 33.f, 44.f};
|
|
||||||
g_gxState.kcolors[GX_KCOLOR0] = {55.f, 66.f, 77.f, 88.f};
|
|
||||||
EXPECT_TRUE(info.sampledKColors.test(GX_KCOLOR0));
|
|
||||||
aurora::gfx::testing::reset_uniform_allocations();
|
|
||||||
aurora::gx::build_uniform(info, 0, {}, {}, false);
|
|
||||||
const auto expectedReg = g_gxState.colorRegs[GX_TEVREG0];
|
|
||||||
const auto expectedKColor = g_gxState.kcolors[GX_KCOLOR0];
|
|
||||||
g_gxState.colorRegs[GX_TEVREG0] = savedReg;
|
|
||||||
g_gxState.kcolors[GX_KCOLOR0] = savedKColor;
|
|
||||||
const auto& bytes = aurora::gfx::testing::uniform_allocation(0);
|
|
||||||
const auto* reg = reinterpret_cast<const uint8_t*>(&expectedReg);
|
|
||||||
const auto found = std::search(bytes.begin(), bytes.end(), reg, reg + sizeof(aurora::Vec4<float>));
|
|
||||||
ASSERT_NE(found, bytes.end());
|
|
||||||
const size_t offset = static_cast<size_t>(found - bytes.begin());
|
|
||||||
ASSERT_LE(offset + 2 * sizeof(aurora::Vec4<float>), bytes.size());
|
|
||||||
EXPECT_EQ(std::memcmp(bytes.data() + offset + sizeof(aurora::Vec4<float>),
|
|
||||||
&expectedKColor, sizeof(aurora::Vec4<float>)), 0);
|
|
||||||
aurora::gfx::testing::reset_uniform_allocations();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// BP registers (direct FIFO writes, no dirty state flush needed)
|
// BP registers (direct FIFO writes, no dirty state flush needed)
|
||||||
@@ -740,52 +708,6 @@ TEST_F(GXFifoTest, BlendMode_Logic) {
|
|||||||
EXPECT_EQ(g_gxState.blendOp, GX_LO_XOR);
|
EXPECT_EQ(g_gxState.blendOp, GX_LO_XOR);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
TEST_F(GXFifoTest, GenMode_FirstZeroWriteDecodesAndRepeatDeduplicates) {
|
|
||||||
reset_gx_state();
|
|
||||||
const auto before = g_gxState.pipelineStateGeneration;
|
|
||||||
decode_fifo(bp_cmd(0, 0));
|
|
||||||
EXPECT_EQ(g_gxState.numTevStages, 1u);
|
|
||||||
EXPECT_EQ(g_gxState.cullMode, GX_CULL_NONE);
|
|
||||||
EXPECT_EQ(g_gxState.numChans, 0u);
|
|
||||||
EXPECT_EQ(g_gxState.numTexGens, 0u);
|
|
||||||
EXPECT_EQ(g_gxState.numIndStages, 0u);
|
|
||||||
EXPECT_EQ(g_gxState.bpRegCache[0], 0u);
|
|
||||||
EXPECT_NE(g_gxState.pipelineStateGeneration, before);
|
|
||||||
const auto decoded = g_gxState.pipelineStateGeneration;
|
|
||||||
decode_fifo(bp_cmd(0, 0));
|
|
||||||
EXPECT_EQ(g_gxState.pipelineStateGeneration, decoded);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_F(GXFifoTest, GenMode_FirstMaskedWritePreservesZeroResetBits) {
|
|
||||||
for (const u32 mask : {0u, 1u << 10}) {
|
|
||||||
reset_gx_state();
|
|
||||||
const auto before = g_gxState.pipelineStateGeneration;
|
|
||||||
decode_fifo(bp_cmd(0xFE, mask));
|
|
||||||
decode_fifo(bp_cmd(0, 0xFFFFFF));
|
|
||||||
EXPECT_EQ(g_gxState.bpRegCache[0], mask);
|
|
||||||
EXPECT_EQ(g_gxState.bpRegCache[0xFE], 0xFFFFFFu);
|
|
||||||
EXPECT_EQ(g_gxState.numTevStages, mask ? 2u : 1u);
|
|
||||||
EXPECT_EQ(g_gxState.cullMode, GX_CULL_NONE);
|
|
||||||
EXPECT_NE(g_gxState.pipelineStateGeneration, before);
|
|
||||||
decode_fifo(bp_cmd(0, 0));
|
|
||||||
EXPECT_EQ(g_gxState.numTevStages, 1u);
|
|
||||||
EXPECT_EQ(g_gxState.bpRegCache[0], 0u);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_F(GXFifoTest, GenMode_ColdSingleStageApiSetupDecodes) {
|
|
||||||
reset_gx_state();
|
|
||||||
GXSetNumTevStages(1);
|
|
||||||
GXSetNumTexGens(0);
|
|
||||||
GXSetNumChans(0);
|
|
||||||
GXSetCullMode(GX_CULL_NONE);
|
|
||||||
const auto bytes = flush_and_capture();
|
|
||||||
decode_fifo(bytes);
|
|
||||||
EXPECT_EQ(g_gxState.numTevStages, 1u);
|
|
||||||
EXPECT_EQ(g_gxState.cullMode, GX_CULL_NONE);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_F(GXFifoTest, BpMask_AppliesOnlyToNextWrite) {
|
TEST_F(GXFifoTest, BpMask_AppliesOnlyToNextWrite) {
|
||||||
std::vector<u8> bytes;
|
std::vector<u8> bytes;
|
||||||
auto mask = bp_cmd(0xFE, 1u << 19);
|
auto mask = bp_cmd(0xFE, 1u << 19);
|
||||||
@@ -2257,7 +2179,6 @@ TEST_F(GXFifoTest, DrawTopologyTemplatesPreserveExactGxIndexOrder) {
|
|||||||
const auto decodeAndReadIndices = [&](GXPrimitive primitive, u16 count) {
|
const auto decodeAndReadIndices = [&](GXPrimitive primitive, u16 count) {
|
||||||
std::vector<u8> fifo;
|
std::vector<u8> fifo;
|
||||||
append_test_draw(fifo, primitive, count);
|
append_test_draw(fifo, primitive, count);
|
||||||
aurora::gfx::testing::reset_vertex_push_record();
|
|
||||||
decode_fifo(fifo);
|
decode_fifo(fifo);
|
||||||
return aurora::gfx::testing::last_pushed_indices();
|
return aurora::gfx::testing::last_pushed_indices();
|
||||||
};
|
};
|
||||||
@@ -2272,7 +2193,7 @@ TEST_F(GXFifoTest, DrawTopologyTemplatesPreserveExactGxIndexOrder) {
|
|||||||
(std::vector<u16>{0, 1, 2, 0, 2, 3, 0, 3, 4}));
|
(std::vector<u16>{0, 1, 2, 0, 2, 3, 0, 3, 4}));
|
||||||
g_gxState.stateDirty = true;
|
g_gxState.stateDirty = true;
|
||||||
EXPECT_EQ(decodeAndReadIndices(GX_TRIANGLEFAN, 2),
|
EXPECT_EQ(decodeAndReadIndices(GX_TRIANGLEFAN, 2),
|
||||||
(std::vector<u16>{}));
|
(std::vector<u16>{0, 1}));
|
||||||
g_gxState.stateDirty = true;
|
g_gxState.stateDirty = true;
|
||||||
EXPECT_EQ(decodeAndReadIndices(GX_TRIANGLESTRIP, 6),
|
EXPECT_EQ(decodeAndReadIndices(GX_TRIANGLESTRIP, 6),
|
||||||
(std::vector<u16>{0, 1, 2, 2, 1, 3, 2, 3, 4, 4, 3, 5}));
|
(std::vector<u16>{0, 1, 2, 2, 1, 3, 2, 3, 4, 4, 3, 5}));
|
||||||
@@ -4245,9 +4166,7 @@ TEST_F(GXFifoTest, CopyTexClearTruePassesScratchRectAndUpdateMasksToResolve) {
|
|||||||
EXPECT_NEAR(resolve.clearColorValue.y(), 128.f / 255.f, 1.f / 255.f);
|
EXPECT_NEAR(resolve.clearColorValue.y(), 128.f / 255.f, 1.f / 255.f);
|
||||||
EXPECT_NEAR(resolve.clearColorValue.z(), 192.f / 255.f, 1.f / 255.f);
|
EXPECT_NEAR(resolve.clearColorValue.z(), 192.f / 255.f, 1.f / 255.f);
|
||||||
EXPECT_NEAR(resolve.clearColorValue.w(), 32.f / 255.f, 1.f / 255.f);
|
EXPECT_NEAR(resolve.clearColorValue.w(), 32.f / 255.f, 1.f / 255.f);
|
||||||
const float gxDepth = 0x123456 / 16777216.f;
|
EXPECT_NEAR(resolve.clearDepthValue, 0x123456 / 16777216.f, 1.f / 16777216.f);
|
||||||
EXPECT_NEAR(resolve.clearDepthValue, aurora::gx::UseReversedZ ? 1.f - gxDepth : gxDepth,
|
|
||||||
1.f / 16777216.f);
|
|
||||||
EXPECT_EQ(resolve.resolveFormat, GX_TF_RGBA8);
|
EXPECT_EQ(resolve.resolveFormat, GX_TF_RGBA8);
|
||||||
EXPECT_FALSE(resolve.halfScale);
|
EXPECT_FALSE(resolve.halfScale);
|
||||||
EXPECT_FALSE(resolve.forceOpaqueAlpha);
|
EXPECT_FALSE(resolve.forceOpaqueAlpha);
|
||||||
|
|||||||
@@ -299,10 +299,6 @@ std::pair<ByteBuffer, Range> copy_uniform(Range source) {
|
|||||||
return map_uniform(source.size);
|
return map_uniform(source.size);
|
||||||
}
|
}
|
||||||
uint32_t align_uniform(uint32_t value) { return (value + 255u) & ~255u; }
|
uint32_t align_uniform(uint32_t value) { return (value + 255u) & ~255u; }
|
||||||
uint64_t staging_uniform_bytes(uint64_t value) { return staging_padded(value, 256); }
|
|
||||||
uint64_t staging_storage_bytes(uint64_t value) { return staging_padded(value, 256); }
|
|
||||||
bool staging_has_space(const StagingSizes&) { return true; }
|
|
||||||
void split_staging_batch() { throw StagingCapacityError("Unexpected split in FIFO unit test"); }
|
|
||||||
|
|
||||||
Vec2<uint32_t> get_render_target_size() noexcept { return s_renderTargetSize; }
|
Vec2<uint32_t> get_render_target_size() noexcept { return s_renderTargetSize; }
|
||||||
Vec2<uint32_t> get_frame_buffer_size() noexcept { return s_renderTargetSize; }
|
Vec2<uint32_t> get_frame_buffer_size() noexcept { return s_renderTargetSize; }
|
||||||
|
|||||||
@@ -1,28 +0,0 @@
|
|||||||
cmake_minimum_required(VERSION 3.25)
|
|
||||||
project(metalfx_interop LANGUAGES CXX)
|
|
||||||
|
|
||||||
if(NOT APPLE)
|
|
||||||
message(FATAL_ERROR "The MetalFX interoperability probe requires macOS")
|
|
||||||
endif()
|
|
||||||
enable_language(OBJCXX)
|
|
||||||
set(CMAKE_OBJCXX_STANDARD 20)
|
|
||||||
set(CMAKE_OBJCXX_STANDARD_REQUIRED ON)
|
|
||||||
set(CMAKE_OSX_DEPLOYMENT_TARGET 13.0)
|
|
||||||
|
|
||||||
# Use the same Dawn package as Aurora; no separate download or renderer build.
|
|
||||||
find_package(Threads REQUIRED)
|
|
||||||
find_package(Dawn CONFIG REQUIRED)
|
|
||||||
add_executable(metalfx_interop main.mm ../../lib/webgpu/metalfx.mm)
|
|
||||||
target_include_directories(metalfx_interop PRIVATE ../../lib)
|
|
||||||
target_compile_options(metalfx_interop PRIVATE -fobjc-arc -Wall -Wextra)
|
|
||||||
target_link_libraries(metalfx_interop PRIVATE dawn::webgpu_dawn
|
|
||||||
"-framework MetalFX" "-framework Metal" "-framework IOSurface" "-framework Foundation")
|
|
||||||
enable_testing()
|
|
||||||
add_test(NAME metalfx_interop COMMAND metalfx_interop)
|
|
||||||
set_tests_properties(metalfx_interop PROPERTIES SKIP_RETURN_CODE 77 TIMEOUT 60)
|
|
||||||
|
|
||||||
add_executable(metalfx_stub_test stub_test.cpp ../../lib/webgpu/metalfx_stub.cpp)
|
|
||||||
target_include_directories(metalfx_stub_test PRIVATE ../../lib)
|
|
||||||
target_compile_features(metalfx_stub_test PRIVATE cxx_std_20)
|
|
||||||
target_link_libraries(metalfx_stub_test PRIVATE dawn::webgpu_dawn)
|
|
||||||
add_test(NAME metalfx_stub COMMAND metalfx_stub_test)
|
|
||||||
@@ -1,124 +0,0 @@
|
|||||||
# MetalFX spatial upscaling: renderer integration and tests
|
|
||||||
|
|
||||||
Aurora can now upscale the completed game image with MetalFX before aspect-fit
|
|
||||||
presentation and ImGui composition. It is opt-in and requires macOS 13+, a
|
|
||||||
supported Metal device, and Dawn IOSurface/shared-event support. Other backends
|
|
||||||
and builds without the MetalFX SDK use a stub and the existing presentation path.
|
|
||||||
The MetalFX framework is weak-linked; the game's deployment target is unchanged.
|
|
||||||
|
|
||||||
## Trying the renderer integration
|
|
||||||
|
|
||||||
Use F10 → Graphics → MetalFX spatial upscaling. Then use the existing
|
|
||||||
Resolution control to render below the output viewport's size.
|
|
||||||
Both source dimensions must be smaller than the output dimensions. Equal-size
|
|
||||||
rendering, supersampling, and unsupported source formats bypass MetalFX. In
|
|
||||||
particular, Auto (window size) generally offers no upscaling opportunity.
|
|
||||||
|
|
||||||
The F10 toggle is saved in `Config.toml` as
|
|
||||||
`video.metalfx_spatial_upscaling`. It uses these thread-safe Aurora entry points:
|
|
||||||
|
|
||||||
- `aurora_set_metalfx_spatial(bool)` requests a change at the next sealed frame.
|
|
||||||
- `aurora_get_metalfx_spatial()` returns the requested setting.
|
|
||||||
- `aurora_is_metalfx_spatial_supported()` reports device/build support.
|
|
||||||
- `aurora_get_metalfx_status()` distinguishes Disabled, Unsupported,
|
|
||||||
Not Upscaling, Active, and Error. A busy resize-retirement pool temporarily
|
|
||||||
bypasses upscaling and retries on a later frame. Other upscaler errors log a
|
|
||||||
reason and use normal presentation until a disabled frame resets the error.
|
|
||||||
|
|
||||||
The game's HUD is part of the source image and is upscaled. ImGui/F10/FPS overlays
|
|
||||||
are composed afterward at output resolution. Existing source-frame captures
|
|
||||||
still capture the original source image. The interpolation snapshot call sites
|
|
||||||
all use the same upscaling hook; game-specific interpolation remains untested.
|
|
||||||
|
|
||||||
## GPU path and ownership
|
|
||||||
|
|
||||||
1. Request Dawn's `SharedTextureMemoryIOSurface` and `SharedFenceMTLSharedEvent`
|
|
||||||
features when the Metal adapter supports both. Use that Dawn device's native
|
|
||||||
`MTLDevice`, not a separately selected default device.
|
|
||||||
2. Cache MaxInterpolatedFrames + 1 upscaling slots with IOSurface-backed input and output textures,
|
|
||||||
a spatial scaler, a private MetalFX output, and shared-event dependencies.
|
|
||||||
Check texture formats, dimensions, usages, and device size limits on creation.
|
|
||||||
3. Begin Dawn input access, copy the completed game image at its source size,
|
|
||||||
and submit the scene plus copy. End input access and wait for Dawn's
|
|
||||||
`commandsScheduledFuture` before submitting dependent native Metal work.
|
|
||||||
4. On the native queue, wait for input rendering and any prior Dawn consumption
|
|
||||||
of the shared output. Encode MetalFX into its required **private** output
|
|
||||||
texture, then GPU-blit the result into the output IOSurface and signal an event.
|
|
||||||
5. After native scheduling, begin Dawn output access with that event/value.
|
|
||||||
Composite the upscaled image into the existing content viewport, retaining
|
|
||||||
letterboxing, then draw ImGui. Submit and end output access. Reuse observes
|
|
||||||
both Dawn-to-Metal and Metal-to-Dawn event dependencies.
|
|
||||||
6. GPU completion callbacks retain resources after a cache entry is replaced,
|
|
||||||
disabled, or shut down. At most eight resource sets may exist (four current
|
|
||||||
plus four retiring); rapid resizing cannot allocate an unbounded queue.
|
|
||||||
|
|
||||||
CPU scheduling waits remain, but there are no CPU image transfers or per-frame
|
|
||||||
GPU-completion waits in the upscaling path. There is one source-size GPU copy
|
|
||||||
and one full-output GPU blit. Their cost must be measured before promising a
|
|
||||||
performance gain. The input copy follows the existing perceptual/unorm sampling
|
|
||||||
path. sRGB texture formats bypass MetalFX to avoid implicit color conversion.
|
|
||||||
|
|
||||||
## Standalone GPU regression test
|
|
||||||
|
|
||||||
The test builds the actual `lib/webgpu/metalfx.mm` implementation. Point
|
|
||||||
`Dawn_DIR` at the package used by an existing Aurora build:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
cmake -S aurora-main/tests/metalfx_interop -B build-metalfx-interop \
|
|
||||||
-DDawn_DIR="/absolute/path/to/dawn_prebuilt-src/lib/cmake/Dawn" \
|
|
||||||
-DCMAKE_BUILD_TYPE=Release
|
|
||||||
cmake --build build-metalfx-interop
|
|
||||||
MTL_DEBUG_LAYER=1 MTL_SHADER_VALIDATION=1 \
|
|
||||||
ctest --test-dir build-metalfx-interop --output-on-failure -V
|
|
||||||
```
|
|
||||||
|
|
||||||
The GPU test returns 77 (CTest **Skipped**) when no Metal adapter, required
|
|
||||||
sharing features, or spatial scaler is available. A skip is not evidence of
|
|
||||||
interoperability. Sandboxed processes may need GPU access. CTest imposes a
|
|
||||||
60-second timeout. The separate stub test needs no GPU.
|
|
||||||
|
|
||||||
Tested on Apple M3, macOS 26.5.1, using Aurora's existing Dawn package
|
|
||||||
(`v20260603.191052`). Metal API and GPU validation were enabled:
|
|
||||||
|
|
||||||
| Formats | Input | Output | Frames per format |
|
|
||||||
| --- | --- | --- | --- |
|
|
||||||
| RGBA8Unorm, BGRA8Unorm | 64 × 48 | 128 × 96 | 24 |
|
|
||||||
| RGBA8Unorm, BGRA8Unorm | 320 × 180 | 480 × 270 | 24 |
|
|
||||||
| RGBA8Unorm, BGRA8Unorm | 960 × 540 | 1920 × 1080 | 24 |
|
|
||||||
|
|
||||||
All 144 frames and 2,304 interior pixel samples passed. Red changes per frame;
|
|
||||||
green and blue distinguish left/right and top/bottom. All channels are checked
|
|
||||||
within five 8-bit levels, catching stale images, orientation/channel mistakes,
|
|
||||||
and missing output. Tests cover 1.5× and 2× scaling, padded readback rows, slot
|
|
||||||
reuse, dropping wrappers before readback completion, invalid dimensions/sRGB
|
|
||||||
formats, the eight-set allocation bound, and the unavailable-backend stub.
|
|
||||||
|
|
||||||
Readback is only the test oracle and is absent from the game upscaling path.
|
|
||||||
These samples do not measure reconstruction quality at edges or race performance.
|
|
||||||
|
|
||||||
## Windowed presentation test
|
|
||||||
|
|
||||||
This optional target exercises Aurora's actual frame submission and presentation
|
|
||||||
with a synthetic source and an ImGui overlay. It requires no Wii game data and
|
|
||||||
creates an automatically closing test window. Add the option to an existing
|
|
||||||
from-source runtime build (the normal dependency/provider options still apply):
|
|
||||||
|
|
||||||
```sh
|
|
||||||
cmake -S runtime -B build-macos -DCMAKE_BUILD_TYPE=Release \
|
|
||||||
-DAURORA_BUILD_METALFX_PRESENTATION_TEST=ON
|
|
||||||
cmake --build build-macos --target metalfx_presentation_test
|
|
||||||
MTL_DEBUG_LAYER=1 MTL_SHADER_VALIDATION=1 \
|
|
||||||
./build-macos/aurora-build/metalfx_presentation_test
|
|
||||||
```
|
|
||||||
|
|
||||||
On the same M3, all 84 frames passed with Metal API/GPU validation: disabled,
|
|
||||||
enabled, window resize, 4:3/16:9 aspect changes, native-size bypass, disable, and
|
|
||||||
re-enable. Assertions check renderer status and errors; this is not a pixel-level
|
|
||||||
verification of the window image. The core build and macOS 12 deployment-target
|
|
||||||
availability compilation also passed. Full Mario Kart gameplay, race performance,
|
|
||||||
visual quality, Intel Macs, other Apple GPUs, older macOS runtime versions, and
|
|
||||||
non-macOS full builds remain untested.
|
|
||||||
|
|
||||||
References: [Apple MetalFX](https://developer.apple.com/documentation/metalfx),
|
|
||||||
[spatial scaler requirements](https://developer.apple.com/documentation/metalfx/mtlfxspatialscaler),
|
|
||||||
and the installed Dawn `MetalBackend.h` / `webgpu_cpp.h` APIs.
|
|
||||||
@@ -1,269 +0,0 @@
|
|||||||
#import <Foundation/Foundation.h>
|
|
||||||
#import <IOSurface/IOSurface.h>
|
|
||||||
#import <Metal/Metal.h>
|
|
||||||
#include "webgpu/metalfx.hpp"
|
|
||||||
|
|
||||||
#include <dawn/native/MetalBackend.h>
|
|
||||||
#include <webgpu/webgpu_cpp.h>
|
|
||||||
|
|
||||||
#include <array>
|
|
||||||
#include <atomic>
|
|
||||||
#include <cmath>
|
|
||||||
#include <iostream>
|
|
||||||
#include <memory>
|
|
||||||
#include <stdexcept>
|
|
||||||
#include <string_view>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
constexpr uint64_t kTimeoutNs = 10'000'000'000;
|
|
||||||
constexpr unsigned kFrames = 24;
|
|
||||||
std::atomic<unsigned> g_errors{0};
|
|
||||||
|
|
||||||
void require(bool condition, const char* message) {
|
|
||||||
if (!condition) throw std::runtime_error(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
void wait(const wgpu::Instance& instance, wgpu::Future future) {
|
|
||||||
require(instance.WaitAny(future, kTimeoutNs) == wgpu::WaitStatus::Success,
|
|
||||||
"Dawn operation timed out or failed");
|
|
||||||
}
|
|
||||||
|
|
||||||
void runCase(const wgpu::Instance& instance, const wgpu::Device& device,
|
|
||||||
bool bgra, uint32_t width, uint32_t height,
|
|
||||||
uint32_t outWidth, uint32_t outHeight) {
|
|
||||||
const auto format = bgra ? wgpu::TextureFormat::BGRA8Unorm : wgpu::TextureFormat::RGBA8Unorm;
|
|
||||||
using namespace aurora::webgpu::metalfx;
|
|
||||||
std::array<std::unique_ptr<SpatialScaler>, 3> slots;
|
|
||||||
for (auto& slot : slots) {
|
|
||||||
std::string error;
|
|
||||||
slot = create(instance, device, {width, height, outWidth, outHeight, format}, error);
|
|
||||||
if (!slot) {
|
|
||||||
throw std::runtime_error(error.empty()
|
|
||||||
? "MetalFX resource pool was still busy retiring earlier slots"
|
|
||||||
: error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Asymmetric quadrants expose channel swaps, vertical flips, and stale frames.
|
|
||||||
wgpu::ShaderSourceWGSL source{};
|
|
||||||
source.code = R"(
|
|
||||||
@group(0) @binding(0) var<uniform> params: vec4f;
|
|
||||||
@vertex fn vs(@builtin(vertex_index) i: u32) -> @builtin(position) vec4f {
|
|
||||||
let p = array(vec2f(-1, -1), vec2f(3, -1), vec2f(-1, 3));
|
|
||||||
return vec4f(p[i], 0, 1);
|
|
||||||
}
|
|
||||||
@fragment fn fs(@builtin(position) p: vec4f) -> @location(0) vec4f {
|
|
||||||
return vec4f(params.x, select(0.2, 0.8, p.x >= params.y / 2),
|
|
||||||
select(0.3, 0.7, p.y >= params.z / 2), 1);
|
|
||||||
}
|
|
||||||
)";
|
|
||||||
wgpu::ShaderModuleDescriptor shaderDescriptor{};
|
|
||||||
shaderDescriptor.nextInChain = &source;
|
|
||||||
auto shader = device.CreateShaderModule(&shaderDescriptor);
|
|
||||||
wgpu::ColorTargetState target{};
|
|
||||||
target.format = format;
|
|
||||||
wgpu::FragmentState fragment{};
|
|
||||||
fragment.module = shader;
|
|
||||||
fragment.entryPoint = "fs";
|
|
||||||
fragment.targetCount = 1;
|
|
||||||
fragment.targets = ⌖
|
|
||||||
wgpu::RenderPipelineDescriptor pipelineDescriptor{};
|
|
||||||
pipelineDescriptor.vertex.module = shader;
|
|
||||||
pipelineDescriptor.vertex.entryPoint = "vs";
|
|
||||||
pipelineDescriptor.fragment = &fragment;
|
|
||||||
auto pipeline = device.CreateRenderPipeline(&pipelineDescriptor);
|
|
||||||
auto dawnQueue = device.GetQueue();
|
|
||||||
const uint32_t bytesPerRow = (outWidth * 4 + 255) & ~255u;
|
|
||||||
const uint64_t readbackSize = uint64_t(bytesPerRow) * outHeight;
|
|
||||||
std::vector<wgpu::Buffer> readbacks;
|
|
||||||
|
|
||||||
for (unsigned frame = 0; frame < kFrames; ++frame) {
|
|
||||||
auto& slot = slots[frame % slots.size()];
|
|
||||||
require(slot->begin_input(), "Production MetalFX begin_input failed");
|
|
||||||
const std::array<float, 4> params{0.2f + float(frame % 5) * 0.1f,
|
|
||||||
float(width), float(height), 0};
|
|
||||||
wgpu::BufferDescriptor uniformDescriptor{};
|
|
||||||
uniformDescriptor.size = sizeof(params);
|
|
||||||
uniformDescriptor.usage = wgpu::BufferUsage::Uniform | wgpu::BufferUsage::CopyDst;
|
|
||||||
auto uniform = device.CreateBuffer(&uniformDescriptor);
|
|
||||||
dawnQueue.WriteBuffer(uniform, 0, params.data(), sizeof(params));
|
|
||||||
wgpu::BindGroupEntry entry{};
|
|
||||||
entry.binding = 0;
|
|
||||||
entry.buffer = uniform;
|
|
||||||
entry.size = sizeof(params);
|
|
||||||
wgpu::BindGroupDescriptor bindDescriptor{};
|
|
||||||
bindDescriptor.layout = pipeline.GetBindGroupLayout(0);
|
|
||||||
bindDescriptor.entryCount = 1;
|
|
||||||
bindDescriptor.entries = &entry;
|
|
||||||
auto bindGroup = device.CreateBindGroup(&bindDescriptor);
|
|
||||||
auto encoder = device.CreateCommandEncoder();
|
|
||||||
wgpu::RenderPassColorAttachment attachment{};
|
|
||||||
attachment.view = slot->input_view();
|
|
||||||
attachment.loadOp = wgpu::LoadOp::Clear;
|
|
||||||
attachment.storeOp = wgpu::StoreOp::Store;
|
|
||||||
wgpu::RenderPassDescriptor passDescriptor{};
|
|
||||||
passDescriptor.colorAttachmentCount = 1;
|
|
||||||
passDescriptor.colorAttachments = &attachment;
|
|
||||||
auto pass = encoder.BeginRenderPass(&passDescriptor);
|
|
||||||
pass.SetPipeline(pipeline);
|
|
||||||
pass.SetBindGroup(0, bindGroup);
|
|
||||||
pass.Draw(3);
|
|
||||||
pass.End();
|
|
||||||
auto render = encoder.Finish();
|
|
||||||
dawnQueue.Submit(1, &render);
|
|
||||||
if (!slot->upscale()) throw std::runtime_error(slot->error());
|
|
||||||
|
|
||||||
wgpu::BufferDescriptor readbackDescriptor{};
|
|
||||||
readbackDescriptor.size = readbackSize;
|
|
||||||
readbackDescriptor.usage = wgpu::BufferUsage::CopyDst | wgpu::BufferUsage::MapRead;
|
|
||||||
auto readback = device.CreateBuffer(&readbackDescriptor);
|
|
||||||
encoder = device.CreateCommandEncoder();
|
|
||||||
wgpu::TexelCopyTextureInfo copySource{};
|
|
||||||
copySource.texture = slot->output_texture();
|
|
||||||
wgpu::TexelCopyBufferInfo destination{};
|
|
||||||
destination.buffer = readback;
|
|
||||||
destination.layout.bytesPerRow = bytesPerRow;
|
|
||||||
destination.layout.rowsPerImage = outHeight;
|
|
||||||
const wgpu::Extent3D extent{outWidth, outHeight, 1};
|
|
||||||
encoder.CopyTextureToBuffer(©Source, &destination, &extent);
|
|
||||||
auto copy = encoder.Finish();
|
|
||||||
dawnQueue.Submit(1, ©);
|
|
||||||
if (!slot->end_output()) throw std::runtime_error(slot->error());
|
|
||||||
readbacks.push_back(std::move(readback));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Model toggle/resize immediately after submission, while either queue may
|
|
||||||
// still be consuming these textures. Production completion callbacks must
|
|
||||||
// keep the resources alive after the cache drops its wrappers.
|
|
||||||
slots = {};
|
|
||||||
|
|
||||||
// Readback is only the test oracle. No CPU image transfer or GPU completion
|
|
||||||
// wait occurs between Dawn rendering, MetalFX, and Dawn consumption above.
|
|
||||||
for (unsigned frame = 0; frame < kFrames; ++frame) {
|
|
||||||
bool mapped = false;
|
|
||||||
auto& readback = readbacks[frame];
|
|
||||||
wait(instance, readback.MapAsync(wgpu::MapMode::Read, 0, readbackSize,
|
|
||||||
wgpu::CallbackMode::WaitAnyOnly, [&mapped](wgpu::MapAsyncStatus status, wgpu::StringView) {
|
|
||||||
mapped = status == wgpu::MapAsyncStatus::Success;
|
|
||||||
}));
|
|
||||||
require(mapped, "Output readback mapping failed");
|
|
||||||
const auto* bytes = static_cast<const uint8_t*>(readback.GetConstMappedRange());
|
|
||||||
require(bytes != nullptr, "Output readback pointer is null");
|
|
||||||
for (unsigned y = 0; y < 4; ++y) {
|
|
||||||
for (unsigned x = 0; x < 4; ++x) {
|
|
||||||
const unsigned px = (2 * x + 1) * outWidth / 8;
|
|
||||||
const unsigned py = (2 * y + 1) * outHeight / 8;
|
|
||||||
const auto* pixel = bytes + py * bytesPerRow + px * 4;
|
|
||||||
const std::array<float, 4> expected{
|
|
||||||
0.2f + float(frame % 5) * 0.1f, x >= 2 ? 0.8f : 0.2f,
|
|
||||||
y >= 2 ? 0.7f : 0.3f, 1};
|
|
||||||
for (unsigned c = 0; c < 4; ++c) {
|
|
||||||
const unsigned channel = bgra && c != 1 && c != 3 ? 2 - c : c;
|
|
||||||
if (std::abs(int(pixel[channel]) - int(std::lround(expected[c] * 255))) > 5) {
|
|
||||||
std::cerr << "Pixel mismatch: frame=" << frame << " x=" << px << " y=" << py
|
|
||||||
<< " channel=" << c << " actual=" << int(pixel[channel])
|
|
||||||
<< " expected=" << std::lround(expected[c] * 255) << '\n';
|
|
||||||
throw std::runtime_error("MetalFX output failed image validation");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
readback.Unmap();
|
|
||||||
}
|
|
||||||
require(g_errors.load() == 0, "Dawn reported validation errors or device loss");
|
|
||||||
std::cout << "PASS " << (bgra ? "BGRA8" : "RGBA8") << ' ' << width << 'x' << height
|
|
||||||
<< " -> " << outWidth << 'x' << outHeight << ": " << kFrames
|
|
||||||
<< " frames, 3 reused slots, 16 pixel samples/frame\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
int run() {
|
|
||||||
const wgpu::InstanceFeatureName timedWait = wgpu::InstanceFeatureName::TimedWaitAny;
|
|
||||||
wgpu::InstanceDescriptor instanceDescriptor{};
|
|
||||||
instanceDescriptor.requiredFeatureCount = 1;
|
|
||||||
instanceDescriptor.requiredFeatures = &timedWait;
|
|
||||||
auto instance = wgpu::CreateInstance(&instanceDescriptor);
|
|
||||||
require(instance != nullptr, "Dawn instance creation failed");
|
|
||||||
wgpu::Adapter adapter;
|
|
||||||
wgpu::RequestAdapterOptions options{};
|
|
||||||
options.backendType = wgpu::BackendType::Metal;
|
|
||||||
wait(instance, instance.RequestAdapter(&options, wgpu::CallbackMode::WaitAnyOnly,
|
|
||||||
[&adapter](wgpu::RequestAdapterStatus status, wgpu::Adapter result, wgpu::StringView message) {
|
|
||||||
if (status == wgpu::RequestAdapterStatus::Success) adapter = std::move(result);
|
|
||||||
else std::cerr << "Adapter: " << std::string_view(message) << '\n';
|
|
||||||
}));
|
|
||||||
if (!adapter) { std::cout << "SKIP: no Dawn Metal adapter\n"; return 77; }
|
|
||||||
const std::array features{wgpu::FeatureName::SharedTextureMemoryIOSurface,
|
|
||||||
wgpu::FeatureName::SharedFenceMTLSharedEvent};
|
|
||||||
for (auto feature : features) {
|
|
||||||
if (!adapter.HasFeature(feature)) {
|
|
||||||
std::cout << "SKIP: Dawn adapter lacks IOSurface/shared-event interoperability\n";
|
|
||||||
return 77;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
wgpu::DeviceDescriptor descriptor{};
|
|
||||||
descriptor.requiredFeatureCount = features.size();
|
|
||||||
descriptor.requiredFeatures = features.data();
|
|
||||||
descriptor.SetUncapturedErrorCallback(
|
|
||||||
[](const wgpu::Device&, wgpu::ErrorType, wgpu::StringView message) {
|
|
||||||
++g_errors;
|
|
||||||
std::cerr << "Dawn error: " << std::string_view(message) << '\n';
|
|
||||||
});
|
|
||||||
descriptor.SetDeviceLostCallback(wgpu::CallbackMode::AllowSpontaneous,
|
|
||||||
[](const wgpu::Device&, wgpu::DeviceLostReason reason, wgpu::StringView message) {
|
|
||||||
if (reason != wgpu::DeviceLostReason::Destroyed) {
|
|
||||||
++g_errors;
|
|
||||||
std::cerr << "Device lost: " << std::string_view(message) << '\n';
|
|
||||||
}
|
|
||||||
});
|
|
||||||
wgpu::Device device;
|
|
||||||
wait(instance, adapter.RequestDevice(&descriptor, wgpu::CallbackMode::WaitAnyOnly,
|
|
||||||
[&device](wgpu::RequestDeviceStatus status, wgpu::Device result, wgpu::StringView message) {
|
|
||||||
if (status == wgpu::RequestDeviceStatus::Success) device = std::move(result);
|
|
||||||
else std::cerr << "Device: " << std::string_view(message) << '\n';
|
|
||||||
}));
|
|
||||||
require(device != nullptr, "Dawn device creation failed");
|
|
||||||
id<MTLDevice> native = dawn::native::metal::GetMTLDevice(device.Get());
|
|
||||||
require(native != nil, "Dawn native Metal device is unavailable");
|
|
||||||
std::cout << "GPU: " << native.name.UTF8String << '\n';
|
|
||||||
if (!aurora::webgpu::metalfx::supported(device, wgpu::BackendType::Metal)) {
|
|
||||||
std::cout << "SKIP: GPU does not support MetalFX spatial scaling\n";
|
|
||||||
return 77;
|
|
||||||
}
|
|
||||||
require(!aurora::webgpu::metalfx::supported(device, wgpu::BackendType::Vulkan),
|
|
||||||
"MetalFX must reject non-Metal backends");
|
|
||||||
std::string error;
|
|
||||||
require(!aurora::webgpu::metalfx::create(instance, device,
|
|
||||||
{128, 96, 128, 96, wgpu::TextureFormat::RGBA8Unorm}, error) && !error.empty(),
|
|
||||||
"MetalFX must reject equal-size input/output");
|
|
||||||
require(!aurora::webgpu::metalfx::create(instance, device,
|
|
||||||
{128, 96, 256, 192, wgpu::TextureFormat::RGBA8UnormSrgb}, error),
|
|
||||||
"MetalFX must reject implicit sRGB conversion");
|
|
||||||
{
|
|
||||||
using namespace aurora::webgpu::metalfx;
|
|
||||||
std::array<std::unique_ptr<SpatialScaler>, 8> resources;
|
|
||||||
for (auto& scaler : resources) {
|
|
||||||
scaler = create(instance, device, {64, 48, 128, 96, wgpu::TextureFormat::RGBA8Unorm}, error);
|
|
||||||
require(scaler != nullptr, "Could not fill the MetalFX resource pool");
|
|
||||||
}
|
|
||||||
require(!create(instance, device, {64, 48, 128, 96, wgpu::TextureFormat::RGBA8Unorm}, error)
|
|
||||||
&& error.empty(), "A full retirement pool must defer allocation without a fatal error");
|
|
||||||
}
|
|
||||||
for (bool bgra : {false, true}) {
|
|
||||||
runCase(instance, device, bgra, 64, 48, 128, 96);
|
|
||||||
runCase(instance, device, bgra, 320, 180, 480, 270);
|
|
||||||
runCase(instance, device, bgra, 960, 540, 1920, 1080);
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
int main() {
|
|
||||||
@autoreleasepool {
|
|
||||||
try { return run(); }
|
|
||||||
catch (const std::exception& error) {
|
|
||||||
std::cerr << "FAIL: " << error.what() << '\n';
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,124 +0,0 @@
|
|||||||
// Explicitly opted-in windowed test of Aurora's real frame/presentation path.
|
|
||||||
// No Wii game data is needed; the source override supplies a synthetic image.
|
|
||||||
#include <aurora/aurora.h>
|
|
||||||
#include <imgui.h>
|
|
||||||
#include <SDL3/SDL_timer.h>
|
|
||||||
|
|
||||||
#include "webgpu/gpu.hpp"
|
|
||||||
#include "window.hpp"
|
|
||||||
|
|
||||||
#include <atomic>
|
|
||||||
#include <chrono>
|
|
||||||
#include <cstdio>
|
|
||||||
#include <filesystem>
|
|
||||||
#include <stdexcept>
|
|
||||||
#include <string_view>
|
|
||||||
#include <system_error>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
std::atomic<unsigned> g_errors{0};
|
|
||||||
|
|
||||||
void log_message(AuroraLogLevel level, const char* module, const char* message, unsigned len) {
|
|
||||||
if (level >= LOG_ERROR) ++g_errors;
|
|
||||||
if (level >= LOG_WARNING || std::string_view(message, len).find("MetalFX") != std::string_view::npos)
|
|
||||||
std::fprintf(stderr, "[%s] %.*s\n", module, static_cast<int>(len), message);
|
|
||||||
if (level == LOG_FATAL) std::abort();
|
|
||||||
}
|
|
||||||
|
|
||||||
void require(bool value, const char* message) {
|
|
||||||
if (!value) throw std::runtime_error(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
void draw_frames(uint32_t width, uint32_t height, AuroraMetalFXStatus expected) {
|
|
||||||
using namespace aurora::webgpu;
|
|
||||||
auto source = create_render_texture(width, height, false);
|
|
||||||
auto bindGroup = create_copy_bind_group(source);
|
|
||||||
std::vector<uint32_t> pixels(size_t(width) * height);
|
|
||||||
for (uint32_t y = 0; y < height; ++y) {
|
|
||||||
for (uint32_t x = 0; x < width; ++x) {
|
|
||||||
pixels[size_t(y) * width + x] = 0xff000000u | ((x / 16 % 2) ? 0x00bb55u : 0xbb5500u);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
wgpu::TexelCopyTextureInfo target{};
|
|
||||||
target.texture = source.texture;
|
|
||||||
wgpu::TexelCopyBufferLayout layout{};
|
|
||||||
layout.bytesPerRow = width * 4;
|
|
||||||
layout.rowsPerImage = height;
|
|
||||||
g_queue.WriteTexture(&target, pixels.data(), pixels.size() * sizeof(uint32_t), &layout, &source.size);
|
|
||||||
|
|
||||||
unsigned rendered = 0;
|
|
||||||
unsigned matchingStatus = 0;
|
|
||||||
for (unsigned attempt = 0; attempt < 300 && rendered < 12; ++attempt) {
|
|
||||||
aurora_update();
|
|
||||||
if (!aurora_begin_frame()) { SDL_Delay(5); continue; }
|
|
||||||
set_present_source_override(bindGroup, source.texture, source.size, source.format);
|
|
||||||
ImGui::SetNextWindowPos(ImVec2(12, 12), ImGuiCond_Always);
|
|
||||||
ImGui::Begin("MetalFX presentation test", nullptr, ImGuiWindowFlags_AlwaysAutoResize);
|
|
||||||
ImGui::TextUnformatted("Output-resolution overlay after game upscaling");
|
|
||||||
ImGui::Text("Source: %u x %u", width, height);
|
|
||||||
ImGui::End();
|
|
||||||
aurora_end_frame();
|
|
||||||
aurora_wait_for_frame_worker();
|
|
||||||
const auto status = aurora_get_metalfx_status();
|
|
||||||
require(status != AURORA_METALFX_ERROR, "MetalFX reported a presentation error");
|
|
||||||
if (status == expected) ++matchingStatus;
|
|
||||||
++rendered;
|
|
||||||
}
|
|
||||||
require(rendered == 12 && matchingStatus >= 9, "Presentation did not reach the expected MetalFX state");
|
|
||||||
require(g_errors.load() == 0, "Aurora reported an error");
|
|
||||||
std::printf("PASS presentation source=%ux%u status=%d frames=%u\n", width, height, expected, rendered);
|
|
||||||
}
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
int main(int argc, char** argv) {
|
|
||||||
const auto cache = std::filesystem::temp_directory_path() /
|
|
||||||
("aurora-metalfx-presentation-test-" + std::to_string(std::chrono::steady_clock::now().time_since_epoch().count()));
|
|
||||||
std::filesystem::create_directories(cache);
|
|
||||||
const auto path = cache.string();
|
|
||||||
AuroraConfig config{};
|
|
||||||
config.appName = "MetalFX presentation test";
|
|
||||||
config.userPath = path.c_str();
|
|
||||||
config.cachePath = path.c_str();
|
|
||||||
config.resourcesPath = path.c_str();
|
|
||||||
config.desiredBackend = BACKEND_METAL;
|
|
||||||
config.windowWidth = 640;
|
|
||||||
config.windowHeight = 480;
|
|
||||||
config.msaa = 1;
|
|
||||||
config.maxTextureAnisotropy = 1;
|
|
||||||
config.logCallback = log_message;
|
|
||||||
config.logLevel = LOG_INFO;
|
|
||||||
aurora_initialize(argc, argv, &config);
|
|
||||||
int result = 0;
|
|
||||||
try {
|
|
||||||
if (!aurora_is_metalfx_spatial_supported()) {
|
|
||||||
std::puts("SKIP: MetalFX spatial scaling is unavailable");
|
|
||||||
result = 77;
|
|
||||||
} else {
|
|
||||||
aurora::window::lock_present_aspect_ratio(4, 3);
|
|
||||||
aurora_set_metalfx_spatial(false);
|
|
||||||
require(!aurora_get_metalfx_spatial(), "Disable request was not retained");
|
|
||||||
draw_frames(320, 240, AURORA_METALFX_DISABLED);
|
|
||||||
aurora_set_metalfx_spatial(true);
|
|
||||||
require(aurora_get_metalfx_spatial(), "Enable request was not retained");
|
|
||||||
draw_frames(320, 240, AURORA_METALFX_ACTIVE);
|
|
||||||
aurora::window::set_window_size(800, 500);
|
|
||||||
draw_frames(320, 240, AURORA_METALFX_ACTIVE);
|
|
||||||
aurora::window::lock_present_aspect_ratio(16, 9);
|
|
||||||
draw_frames(320, 240, AURORA_METALFX_ACTIVE);
|
|
||||||
const auto output = aurora::window::get_window_size();
|
|
||||||
draw_frames(output.native_fb_width, output.native_fb_height, AURORA_METALFX_NOT_UPSCALING);
|
|
||||||
aurora_set_metalfx_spatial(false);
|
|
||||||
draw_frames(320, 240, AURORA_METALFX_DISABLED);
|
|
||||||
aurora_set_metalfx_spatial(true);
|
|
||||||
draw_frames(320, 240, AURORA_METALFX_ACTIVE);
|
|
||||||
}
|
|
||||||
} catch (const std::exception& error) {
|
|
||||||
std::fprintf(stderr, "FAIL: %s\n", error.what());
|
|
||||||
result = 1;
|
|
||||||
}
|
|
||||||
aurora_shutdown();
|
|
||||||
std::error_code cleanupError;
|
|
||||||
std::filesystem::remove_all(cache, cleanupError);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
#include "webgpu/metalfx.hpp"
|
|
||||||
|
|
||||||
int main() {
|
|
||||||
using namespace aurora::webgpu::metalfx;
|
|
||||||
if (supported({}, wgpu::BackendType::Vulkan) || supported({}, wgpu::BackendType::Metal)) return 1;
|
|
||||||
std::string error;
|
|
||||||
if (create({}, {}, {640, 480, 1280, 960, wgpu::TextureFormat::RGBA8Unorm}, error)) return 1;
|
|
||||||
return error.empty() ? 1 : 0;
|
|
||||||
}
|
|
||||||
@@ -1,174 +0,0 @@
|
|||||||
#include "gx_test_common.hpp"
|
|
||||||
#include "gfx/staging_map.hpp"
|
|
||||||
#include "gx/pipeline.hpp"
|
|
||||||
|
|
||||||
#include <thread>
|
|
||||||
|
|
||||||
using aurora::gx::g_gxState;
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
std::vector<u8> draw(GXPrimitive primitive, u16 count, GXVtxFmt format = GX_VTXFMT0) {
|
|
||||||
std::vector<u8> bytes{static_cast<u8>(primitive | format), static_cast<u8>(count >> 8),
|
|
||||||
static_cast<u8>(count)};
|
|
||||||
bytes.resize(3 + count);
|
|
||||||
return bytes;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_F(GXFifoTest, MaximumQuadCountTerminatesWithoutOutOfRangeIndices) {
|
|
||||||
g_gxState.lastVtxFmt = GX_VTXFMT0;
|
|
||||||
g_gxState.lastVtxSize = 1;
|
|
||||||
for (const u16 count : {65532, 65533, 65534, 65535}) {
|
|
||||||
g_gxState.stateDirty = true;
|
|
||||||
decode_fifo(draw(GX_QUADS, count));
|
|
||||||
const auto& indices = aurora::gfx::testing::last_pushed_indices();
|
|
||||||
ASSERT_EQ(indices.size(), (count / 4) * 6 + (count % 4 == 3 ? 3 : 0));
|
|
||||||
for (const auto index : indices) ASSERT_LT(index, count);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_F(GXFifoTest, IncompletePrimitivesNeverJoinAcrossDraws) {
|
|
||||||
g_gxState.lastVtxFmt = GX_VTXFMT0;
|
|
||||||
g_gxState.lastVtxSize = 1;
|
|
||||||
aurora::gfx::testing::use_draw_command_tracking(true);
|
|
||||||
decode_fifo(draw(GX_TRIANGLES, 4));
|
|
||||||
EXPECT_EQ(aurora::gfx::testing::last_pushed_indices(), (std::vector<u16>{0, 1, 2}));
|
|
||||||
decode_fifo(draw(GX_TRIANGLES, 5));
|
|
||||||
EXPECT_EQ(aurora::gfx::testing::last_pushed_indices(), (std::vector<u16>{4, 5, 6}));
|
|
||||||
const auto before = aurora::gfx::testing::last_pushed_indices();
|
|
||||||
decode_fifo(draw(GX_TRIANGLEFAN, 2));
|
|
||||||
EXPECT_EQ(aurora::gfx::testing::last_pushed_indices(), before);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_F(GXFifoTest, MergeStopsBeforeSixteenBitIndexOverflow) {
|
|
||||||
g_gxState.lastVtxFmt = GX_VTXFMT0;
|
|
||||||
g_gxState.lastVtxSize = 1;
|
|
||||||
aurora::gfx::testing::use_draw_command_tracking(true);
|
|
||||||
decode_fifo(draw(GX_TRIANGLES, 65535));
|
|
||||||
decode_fifo(draw(GX_TRIANGLES, 3));
|
|
||||||
EXPECT_EQ(aurora::gfx::g_mergedDrawCallCount, 0u);
|
|
||||||
EXPECT_EQ(aurora::gfx::testing::last_pushed_indices(), (std::vector<u16>{0, 1, 2}));
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_F(GXFifoTest, VertexCacheInvalidationBreaksDrawMerging) {
|
|
||||||
g_gxState.lastVtxFmt = GX_VTXFMT0;
|
|
||||||
g_gxState.lastVtxSize = 1;
|
|
||||||
aurora::gfx::testing::use_draw_command_tracking(true);
|
|
||||||
decode_fifo(draw(GX_TRIANGLES, 3));
|
|
||||||
decode_fifo({GX_CMD_INVL_VC});
|
|
||||||
EXPECT_TRUE(g_gxState.stateDirty);
|
|
||||||
decode_fifo(draw(GX_TRIANGLES, 3));
|
|
||||||
EXPECT_EQ(aurora::gfx::g_mergedDrawCallCount, 0u);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_F(GXFifoTest, EqualStrideVertexFormatChangeBreaksDrawMerging) {
|
|
||||||
aurora::gfx::testing::use_real_vertex_format_helpers(true);
|
|
||||||
g_gxState.vtxDesc[GX_VA_POS] = GX_DIRECT;
|
|
||||||
for (const auto format : {GX_VTXFMT0, GX_VTXFMT1}) {
|
|
||||||
g_gxState.vtxFmts[format].attrs[GX_VA_POS].cnt = GX_POS_XY;
|
|
||||||
g_gxState.vtxFmts[format].attrs[GX_VA_POS].type = GX_U8;
|
|
||||||
}
|
|
||||||
g_gxState.vtxFmts[GX_VTXFMT1].attrs[GX_VA_POS].frac = 1;
|
|
||||||
aurora::gfx::testing::use_draw_command_tracking(true);
|
|
||||||
for (const auto format : {GX_VTXFMT0, GX_VTXFMT1}) {
|
|
||||||
auto bytes = draw(GX_TRIANGLES, 3, format);
|
|
||||||
bytes.resize(9);
|
|
||||||
decode_fifo(bytes);
|
|
||||||
}
|
|
||||||
EXPECT_EQ(aurora::gfx::g_mergedDrawCallCount, 0u);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_F(GXFifoTest, SingleExpandedPrimitiveCannotMergeWithTriangles) {
|
|
||||||
g_gxState.lastVtxFmt = GX_VTXFMT0;
|
|
||||||
g_gxState.lastVtxSize = 1;
|
|
||||||
aurora::gfx::testing::use_draw_command_tracking(true);
|
|
||||||
decode_fifo(draw(GX_POINTS, 1));
|
|
||||||
decode_fifo(draw(GX_TRIANGLES, 3));
|
|
||||||
EXPECT_EQ(aurora::gfx::g_mergedDrawCallCount, 0u);
|
|
||||||
EXPECT_EQ(aurora::gfx::testing::last_pushed_indices(), (std::vector<u16>{0, 1, 2}));
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(StagingMapping, RetiredCallbacksCannotPublishAnotherBuffersReadiness) {
|
|
||||||
using namespace aurora::gfx;
|
|
||||||
StagingMapState state;
|
|
||||||
const auto old = state.request();
|
|
||||||
EXPECT_EQ(state.request(), 0u);
|
|
||||||
state.reset();
|
|
||||||
const auto current = state.request();
|
|
||||||
EXPECT_FALSE(state.complete(old, BufferMapState::Mapped));
|
|
||||||
EXPECT_FALSE(state.complete(old, BufferMapState::Unmapped));
|
|
||||||
EXPECT_EQ(state.state(), BufferMapState::Mapping);
|
|
||||||
EXPECT_TRUE(state.complete(current, BufferMapState::Mapped));
|
|
||||||
EXPECT_FALSE(state.complete(current, BufferMapState::Unmapped));
|
|
||||||
EXPECT_EQ(state.state(), BufferMapState::Mapped);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(StagingMapping, AsyncCompletionWakesWaiters) {
|
|
||||||
using namespace aurora::gfx;
|
|
||||||
StagingMapState state;
|
|
||||||
const auto generation = state.request();
|
|
||||||
std::thread callback([&] {
|
|
||||||
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
|
||||||
state.complete(generation, BufferMapState::Mapped);
|
|
||||||
});
|
|
||||||
const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(2);
|
|
||||||
while (state.state() == BufferMapState::Mapping && std::chrono::steady_clock::now() < deadline)
|
|
||||||
state.wait_for_progress();
|
|
||||||
callback.join();
|
|
||||||
EXPECT_EQ(state.state(), BufferMapState::Mapped);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(StagingCapacity, ReservesPaddingAndRejectsOverflow) {
|
|
||||||
using namespace aurora::gfx;
|
|
||||||
EXPECT_EQ(staging_padded(257, 256), 512u);
|
|
||||||
EXPECT_THROW(staging_padded(UINT64_MAX, 256), StagingCapacityError);
|
|
||||||
const StagingSizes used{0, 256, 0, 0}, demand{0, 256, 0, 0}, tail{0, 3840, 0, 0};
|
|
||||||
EXPECT_TRUE(staging_fits(used, demand, tail, {4, 4352, 4, 4}));
|
|
||||||
EXPECT_FALSE(staging_fits(used, demand, tail, {4, 4351, 4, 4}));
|
|
||||||
EXPECT_FALSE(staging_fits({UINT64_MAX, 0, 0, 0}, {1, 0, 0, 0}, {},
|
|
||||||
{UINT64_MAX, UINT64_MAX, UINT64_MAX, UINT64_MAX}));
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(FrameInterpolationContract, IdenticalMeshesInDifferentViewportsDoNotShareHistory) {
|
|
||||||
using namespace aurora;
|
|
||||||
const auto savedViewport = gx::g_gxState.logicalViewport;
|
|
||||||
constexpr size_t positionOffset = sizeof(Mat4x4<float>);
|
|
||||||
constexpr size_t normalOffset = positionOffset + gx::MaxPnMtx * sizeof(Mat3x4<float>);
|
|
||||||
constexpr size_t uniformSize = normalOffset + gx::MaxPnMtx * sizeof(Mat3x4<float>);
|
|
||||||
const gx::FrameInterpolationDrawIdentity identity{0x1234, 0x5678, 0x9abc, 0xdef0};
|
|
||||||
const Mat4x4<float> projection{};
|
|
||||||
const auto record = [&](float x, std::array<uint8_t, uniformSize>& source) {
|
|
||||||
gx::g_gxState.pnMtx[0].pos = {{1.f, 0.f, 0.f, x}, {0.f, 1.f, 0.f, 0.f}, {0.f, 0.f, 1.f, 0.f}};
|
|
||||||
gx::g_gxState.pnMtx[0].nrm = {{1.f, 0.f, 0.f, 0.f}, {0.f, 1.f, 0.f, 0.f}, {0.f, 0.f, 1.f, 0.f}};
|
|
||||||
std::memcpy(source.data() + positionOffset, &gx::g_gxState.pnMtx[0].pos, sizeof(Mat3x4<float>));
|
|
||||||
std::memcpy(source.data() + normalOffset, &gx::g_gxState.pnMtx[0].nrm, sizeof(Mat3x4<float>));
|
|
||||||
return gx::record_interpolation_draw(identity, projection, 1, {
|
|
||||||
.sourceUniformData = source.data(), .uniformSize = source.size(), .projectionOffset = 0,
|
|
||||||
.positionOffset = positionOffset, .normalOffset = normalOffset, .currentMatrix = 0,
|
|
||||||
.indexedMatrices = true});
|
|
||||||
};
|
|
||||||
gx::set_frame_interpolation_fps(0);
|
|
||||||
gx::begin_frame_interpolation();
|
|
||||||
gx::set_frame_interpolation_fps(120);
|
|
||||||
gx::g_gxState.logicalViewport = {0.f, 0.f, 640.f, 240.f, 0.f, 1.f};
|
|
||||||
std::array<uint8_t, uniformSize> previous{};
|
|
||||||
gx::begin_frame_interpolation();
|
|
||||||
record(0.f, previous);
|
|
||||||
gx::finalize_frame_interpolation();
|
|
||||||
gfx::testing::reset_uniform_allocations();
|
|
||||||
gx::g_gxState.logicalViewport.top = 240.f;
|
|
||||||
std::array<uint8_t, uniformSize> current{};
|
|
||||||
gx::begin_frame_interpolation();
|
|
||||||
const auto ranges = record(20.f, current);
|
|
||||||
const auto expected = current;
|
|
||||||
gx::finalize_frame_interpolation();
|
|
||||||
EXPECT_EQ(current, expected);
|
|
||||||
if (ranges[0].size) {
|
|
||||||
const auto& duplicate = gfx::testing::uniform_allocation(ranges[0].offset);
|
|
||||||
ASSERT_EQ(duplicate.size(), expected.size());
|
|
||||||
EXPECT_EQ(std::memcmp(duplicate.data(), expected.data(), expected.size()), 0);
|
|
||||||
}
|
|
||||||
gx::g_gxState.logicalViewport = savedViewport;
|
|
||||||
gx::set_frame_interpolation_fps(0);
|
|
||||||
gx::begin_frame_interpolation();
|
|
||||||
}
|
|
||||||
@@ -1,349 +0,0 @@
|
|||||||
# Building WiiCompiled and Retro Rewind on macOS
|
|
||||||
|
|
||||||
This guide covers building **WiiCompiled** (base game) and **Retro Rewind** from source on macOS for Apple Silicon (`arm64`). Follow these instructions to compile the native executables directly.
|
|
||||||
|
|
||||||
> [!NOTE]
|
|
||||||
> If you only want to build the base game (**WiiCompiled**), look for sections marked **`(Skip if only building WiiCompiled)`** to bypass Retro Rewind and online payload steps.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 1. Prerequisites
|
|
||||||
|
|
||||||
### System Requirements
|
|
||||||
- **Hardware**: Apple Silicon Mac (M1/M2/M3/M4)
|
|
||||||
- **Operating System**: macOS 14 (Sonoma) or later
|
|
||||||
- **Xcode Command Line Tools**:
|
|
||||||
```bash
|
|
||||||
xcode-select --install
|
|
||||||
```
|
|
||||||
|
|
||||||
### Toolchain Dependencies
|
|
||||||
Install the required tools using [Homebrew](https://brew.sh):
|
|
||||||
```bash
|
|
||||||
brew install cmake ninja
|
|
||||||
brew install --cask dotnet-sdk@8
|
|
||||||
```
|
|
||||||
|
|
||||||
Verify that Clang, CMake, Ninja, and the .NET 8 runtime are available:
|
|
||||||
```bash
|
|
||||||
clang --version
|
|
||||||
cmake --version
|
|
||||||
ninja --version
|
|
||||||
dotnet --list-runtimes # Must list Microsoft.NETCore.App 8.x
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2. Required Game and Mod Assets
|
|
||||||
|
|
||||||
Due to legal requirements, no proprietary Nintendo assets or code are included in this repository. You must provide your own legally dumped game files.
|
|
||||||
|
|
||||||
1. **Mario Kart Wii PAL (`RMCP01`) Disc Image** *(Required)*:
|
|
||||||
- Supported formats: `.iso`, `.wbfs`, `.ciso`, `.rvz`, `.gcm`, `.gcz`.
|
|
||||||
2. **nodtool** *(Required for disc extraction)*:
|
|
||||||
- Download the macOS Apple Silicon binary of [nodtool](https://github.com/encounter/nod/releases):
|
|
||||||
```bash
|
|
||||||
curl -fsSL "https://github.com/encounter/nod/releases/download/v2.0.0-alpha.10/nodtool-macos-arm64" -o nodtool
|
|
||||||
chmod +x nodtool
|
|
||||||
```
|
|
||||||
3. **Retro Rewind Distribution** *(Skip if only building WiiCompiled)*:
|
|
||||||
- Download the [Retro Rewind](https://wiki.tockdom.com/wiki/Retro_Rewind) release package. You will need the `RetroRewind6` folder (which contains `Binaries/Code.pul`).
|
|
||||||
4. **Retro-WFC Payload** *(Skip if only building WiiCompiled or building offline)*:
|
|
||||||
- Required for online multiplayer on Retro Rewind. Downloaded during setup from `https://rwfc.net/api/wfc/payload?g=RMCPD00`.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3. Step 1: Extract Disc Assets
|
|
||||||
|
|
||||||
Extract your clean PAL `RMCP01` disc into the `Assets/` directory of the repository:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Using nodtool directly into a temporary scratch directory
|
|
||||||
mkdir -p /tmp/mkw-extract
|
|
||||||
./nodtool extract /path/to/RMCP01.iso /tmp/mkw-extract
|
|
||||||
|
|
||||||
# Copy extracted assets into the repository Assets directory
|
|
||||||
rm -rf Assets/DATA/files Assets/DATA/sys
|
|
||||||
mkdir -p Assets/DATA
|
|
||||||
cp /tmp/mkw-extract/*/sys/main.dol Assets/main.dol
|
|
||||||
cp /tmp/mkw-extract/*/files/rel/StaticR.rel Assets/StaticR.rel
|
|
||||||
cp -R /tmp/mkw-extract/*/files Assets/DATA/files
|
|
||||||
cp -R /tmp/mkw-extract/*/sys Assets/DATA/sys
|
|
||||||
|
|
||||||
# Clean up temporary files
|
|
||||||
rm -rf /tmp/mkw-extract
|
|
||||||
```
|
|
||||||
|
|
||||||
> [!TIP]
|
|
||||||
> Alternatively, you can use the repository's helper script:
|
|
||||||
> ```bash
|
|
||||||
> Launcher/macos/extract-disc.command --game /path/to/RMCP01.iso --assets-dir Assets --nodtool ./nodtool
|
|
||||||
> ```
|
|
||||||
|
|
||||||
### Verify Extracted Asset Hashes
|
|
||||||
Confirm that the extracted files match the expected clean PAL revision:
|
|
||||||
```bash
|
|
||||||
shasum -a 256 Assets/main.dol Assets/StaticR.rel
|
|
||||||
```
|
|
||||||
- `Assets/main.dol`: `80d18895b39c63bd80f457398bfcbb91b7d16ac116a41a88967e954080155b05`
|
|
||||||
- `Assets/StaticR.rel`: `16d9d146112541fefea701ecb5bc1a496f9d50e4a752fbb5b6778e7c6399f67d`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 4. Step 2: Build the Translator CLI
|
|
||||||
|
|
||||||
Compile the static recompiler CLI:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
dotnet build translator/src/Translator.Cli/Translator.Cli.csproj -c Release
|
|
||||||
```
|
|
||||||
|
|
||||||
Define a shell function to invoke the translator (ensuring paths with spaces are handled safely):
|
|
||||||
```bash
|
|
||||||
translator() {
|
|
||||||
dotnet "$(pwd)/translator/src/Translator.Cli/bin/Release/net8.0/Translator.Cli.dll" "$@"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 5. Step 3: Translation
|
|
||||||
|
|
||||||
### A. Translate Base Game Functions
|
|
||||||
```bash
|
|
||||||
mkdir -p generated/functions build/base
|
|
||||||
|
|
||||||
translator translate-recursive 0x800060A4 \
|
|
||||||
--project projects/mkwii/recomp.yml \
|
|
||||||
--outdir generated/functions \
|
|
||||||
--output-metadata generated/base_translation_output.json \
|
|
||||||
--production-source-bundle generated/base_translation_sources.bin \
|
|
||||||
--no-function-files \
|
|
||||||
--prune-stale \
|
|
||||||
--threads $(sysctl -n hw.ncpu)
|
|
||||||
```
|
|
||||||
|
|
||||||
### B. Emit Base Manifest
|
|
||||||
```bash
|
|
||||||
translator emit-base-manifest \
|
|
||||||
--project projects/mkwii/recomp.yml \
|
|
||||||
--out build/base \
|
|
||||||
--functions-dir generated/functions \
|
|
||||||
--translation-output-metadata generated/base_translation_output.json \
|
|
||||||
--region P
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### C. Stage and Translate Retro Rewind *(Skip this step if you only want to build WiiCompiled)*
|
|
||||||
|
|
||||||
1. Stage `Code.pul`:
|
|
||||||
```bash
|
|
||||||
RETRO_DIR="/path/to/RetroRewind6"
|
|
||||||
mkdir -p PulsarPacks/completed/RetroRewind/RetroRewind6/Binaries
|
|
||||||
cp "$RETRO_DIR/Binaries/Code.pul" PulsarPacks/completed/RetroRewind/RetroRewind6/Binaries/Code.pul
|
|
||||||
```
|
|
||||||
|
|
||||||
2. **Retro-WFC Payload Setup (for Online Multiplayer)**:
|
|
||||||
Online play in Retro Rewind requires the shared Retro-WFC payload. Download and validate it:
|
|
||||||
```bash
|
|
||||||
mkdir -p build/retro-wfc/binary
|
|
||||||
curl -fsSL --retry 3 "https://rwfc.net/api/wfc/payload?g=RMCPD00" \
|
|
||||||
-o build/retro-wfc/binary/payload.RMCPD00.bin
|
|
||||||
|
|
||||||
# Validate payload signature and integrity
|
|
||||||
translator validate-retro-wfc-payload --directory build/retro-wfc
|
|
||||||
```
|
|
||||||
|
|
||||||
3. Run Retro Rewind translation:
|
|
||||||
```bash
|
|
||||||
mkdir -p build/mods/retro_rewind_full_cpp
|
|
||||||
|
|
||||||
translator translate-mod \
|
|
||||||
--project projects/mkwii/recomp.yml \
|
|
||||||
--profile retro-rewind \
|
|
||||||
--base-manifest build/base/mkwii_base_manifest.json \
|
|
||||||
--base-translation-output-metadata generated/base_translation_output.json \
|
|
||||||
--code-pul "$RETRO_DIR/Binaries/Code.pul" \
|
|
||||||
--mod-root "$RETRO_DIR" \
|
|
||||||
--mod-name "Retro Rewind" \
|
|
||||||
--region P \
|
|
||||||
--out build/mods/retro_rewind_full_cpp \
|
|
||||||
--prefer-cached-inputs \
|
|
||||||
--emit-cpp \
|
|
||||||
--threads $(sysctl -n hw.ncpu) \
|
|
||||||
--retro-wfc-payload build/retro-wfc/binary/payload.RMCPD00.bin
|
|
||||||
```
|
|
||||||
> [!TIP]
|
|
||||||
> If you do not want online play or do not have an internet connection, replace `--retro-wfc-payload ...` with `--skip-retro-wfc`.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### D. Generate Data Initialization and Build Shards
|
|
||||||
|
|
||||||
First, generate the embedded game data initializer:
|
|
||||||
```bash
|
|
||||||
translator generate-data-init --project projects/mkwii/recomp.yml
|
|
||||||
```
|
|
||||||
|
|
||||||
Next, generate the CMake build shards using **one** of the following options:
|
|
||||||
|
|
||||||
#### Option 1: Base Game Only (WiiCompiled)
|
|
||||||
```bash
|
|
||||||
mkdir -p generated/build_shards
|
|
||||||
translator emit-build-shards \
|
|
||||||
--project projects/mkwii/recomp.yml \
|
|
||||||
--base-metadata generated/base_translation_output.json \
|
|
||||||
--base-functions-dir generated/functions \
|
|
||||||
--native-source-dir runtime/src \
|
|
||||||
--out generated/build_shards
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Option 2: Base Game + Retro Rewind
|
|
||||||
```bash
|
|
||||||
mkdir -p generated/build_shards
|
|
||||||
translator emit-build-shards \
|
|
||||||
--project projects/mkwii/recomp.yml \
|
|
||||||
--base-metadata generated/base_translation_output.json \
|
|
||||||
--base-functions-dir generated/functions \
|
|
||||||
--native-source-dir runtime/src \
|
|
||||||
--out generated/build_shards \
|
|
||||||
--resolved-profile build/mods/retro_rewind_full_cpp/resolved_dispatch_profile.json \
|
|
||||||
--retro-cpp-dir build/mods/retro_rewind_full_cpp/cpp
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 6. Step 4: Configure and Compile with CMake & Ninja
|
|
||||||
|
|
||||||
Configure the native C++ build targeting Apple Silicon:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cmake -S runtime -B build-macos -G Ninja \
|
|
||||||
-DCMAKE_BUILD_TYPE=Release \
|
|
||||||
-DCMAKE_C_COMPILER=clang \
|
|
||||||
-DCMAKE_CXX_COMPILER=clang++ \
|
|
||||||
-DAURORA_SDL3_PROVIDER=vendor
|
|
||||||
```
|
|
||||||
|
|
||||||
Compile the desired target:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# To build WiiCompiled only:
|
|
||||||
cmake --build build-macos --target WiiCompiled --parallel $(sysctl -n hw.ncpu)
|
|
||||||
|
|
||||||
# OR to build both WiiCompiled and Retro Rewind:
|
|
||||||
cmake --build build-macos --target WiiCompiled RetroRewind --parallel $(sysctl -n hw.ncpu)
|
|
||||||
```
|
|
||||||
|
|
||||||
Once compilation completes, the executables are ready in your build directory:
|
|
||||||
- `build-macos/WiiCompiled`
|
|
||||||
- `build-macos/RetroRewind` (if built)
|
|
||||||
|
|
||||||
During the build, CMake automatically copies the required runtime assets into `build-macos/`:
|
|
||||||
- `build-macos/dsp_coef.bin`
|
|
||||||
- `build-macos/initial_pipeline_cache.db`
|
|
||||||
- `build-macos/wii_bootstrap/`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 7. Step 5: Running Executables from the Build Folder
|
|
||||||
|
|
||||||
### Configure `Config.toml`
|
|
||||||
The runtime reads configuration from `~/Library/Application Support/WiiCompiled/Config.toml`.
|
|
||||||
|
|
||||||
Create the directory and configuration file:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
mkdir -p "$HOME/Library/Application Support/WiiCompiled"
|
|
||||||
```
|
|
||||||
|
|
||||||
#### For Base Game Only (WiiCompiled):
|
|
||||||
```toml
|
|
||||||
# ~/Library/Application Support/WiiCompiled/Config.toml
|
|
||||||
[video]
|
|
||||||
widescreen = true
|
|
||||||
resolution_multiplier = 1.0
|
|
||||||
graphics_api = "metal"
|
|
||||||
|
|
||||||
[paths]
|
|
||||||
dvd_root = "/absolute/path/to/Wiicompiled/Assets/DATA"
|
|
||||||
```
|
|
||||||
|
|
||||||
#### For Base Game and Retro Rewind:
|
|
||||||
```toml
|
|
||||||
# ~/Library/Application Support/WiiCompiled/Config.toml
|
|
||||||
[video]
|
|
||||||
widescreen = true
|
|
||||||
resolution_multiplier = 1.0
|
|
||||||
graphics_api = "metal"
|
|
||||||
|
|
||||||
[paths]
|
|
||||||
dvd_root = "/absolute/path/to/Wiicompiled/Assets/DATA"
|
|
||||||
retro_rewind_root = "/path/to/RetroRewind6"
|
|
||||||
```
|
|
||||||
|
|
||||||
> [!NOTE]
|
|
||||||
> Ensure `dvd_root` points to the directory containing `files` and `sys/fst.bin`.
|
|
||||||
|
|
||||||
### Launching the Game
|
|
||||||
Run the compiled binaries directly from your terminal or by double clicking:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Run base WiiCompiled
|
|
||||||
./build-macos/WiiCompiled
|
|
||||||
|
|
||||||
# Run Retro Rewind
|
|
||||||
./build-macos/RetroRewind
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Press **F10** in-game at any time to open the configuration bar (controls, resolution, display settings, audio).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Quick Reference: Automated Helper Script
|
|
||||||
|
|
||||||
The repository provides a script (`Launcher/local-build-macos.command`) that handles extraction, translation, and compilation in a single command.
|
|
||||||
|
|
||||||
### Building Base Game Only:
|
|
||||||
```bash
|
|
||||||
Launcher/local-build-macos.command \
|
|
||||||
--profile base \
|
|
||||||
--output-dir build-macos/Products \
|
|
||||||
--game /path/to/RMCP01.iso \
|
|
||||||
--nodtool ./nodtool
|
|
||||||
```
|
|
||||||
|
|
||||||
### Building Both (with Online Retro-WFC Payload):
|
|
||||||
```bash
|
|
||||||
# 1. Download Retro-WFC payload into a staging directory:
|
|
||||||
mkdir -p build/retro-wfc/binary
|
|
||||||
curl -fsSL --retry 3 "https://rwfc.net/api/wfc/payload?g=RMCPD00" \
|
|
||||||
-o build/retro-wfc/binary/payload.RMCPD00.bin
|
|
||||||
|
|
||||||
# 2. Run the automated build with the payload directory:
|
|
||||||
Launcher/local-build-macos.command \
|
|
||||||
--profile both \
|
|
||||||
--output-dir build-macos/Products \
|
|
||||||
--base-output-dir build-macos/Products \
|
|
||||||
--game /path/to/RMCP01.iso \
|
|
||||||
--nodtool ./nodtool \
|
|
||||||
--retro-rewind-package-dir /path/to/RetroRewind6 \
|
|
||||||
--retro-wfc-offline-dir build/retro-wfc
|
|
||||||
```
|
|
||||||
|
|
||||||
### Building Both (Offline, Skipping Payload):
|
|
||||||
```bash
|
|
||||||
Launcher/local-build-macos.command \
|
|
||||||
--profile both \
|
|
||||||
--output-dir build-macos/Products \
|
|
||||||
--base-output-dir build-macos/Products \
|
|
||||||
--game /path/to/RMCP01.iso \
|
|
||||||
--nodtool ./nodtool \
|
|
||||||
--retro-rewind-package-dir /path/to/RetroRewind6 \
|
|
||||||
--skip-retro-wfc-payload
|
|
||||||
```
|
|
||||||
|
|
||||||
When finished, the compiled executables reside in `native-build-macos/` and the bundled `.app` packages are placed in `build-macos/Products/`.
|
|
||||||
@@ -58,7 +58,7 @@ profiles:
|
|||||||
module_link_base: 0x803992E0
|
module_link_base: 0x803992E0
|
||||||
output: build/mods/retro_rewind_full_cpp
|
output: build/mods/retro_rewind_full_cpp
|
||||||
enable_retro_wfc: true
|
enable_retro_wfc: true
|
||||||
retro_wfc_payload: https://rwfc.net/api/wfc/payload?g=RMCPD00
|
retro_wfc_payload: http://nas.play.rwfc.net/payload?g=RMCPD00
|
||||||
retro_wfc_legacy_bootstrap_hook: 0x800ED6E8
|
retro_wfc_legacy_bootstrap_hook: 0x800ED6E8
|
||||||
riivolution:
|
riivolution:
|
||||||
xml: xml/RetroRewind6.xml
|
xml: xml/RetroRewind6.xml
|
||||||
|
|||||||
+2
-91
@@ -1,4 +1,4 @@
|
|||||||
cmake_minimum_required(VERSION 3.25)
|
cmake_minimum_required(VERSION 3.16)
|
||||||
|
|
||||||
# Dawn's pinned macOS artifacts target 12.0. Set the same floor before project()
|
# Dawn's pinned macOS artifacts target 12.0. Set the same floor before project()
|
||||||
# initializes the Apple toolchain so direct developer CMake invocations cannot
|
# initializes the Apple toolchain so direct developer CMake invocations cannot
|
||||||
@@ -21,9 +21,6 @@ if(APPLE AND CMAKE_OSX_ARCHITECTURES)
|
|||||||
"WiiCompiled supports one macOS architecture per build directory; "
|
"WiiCompiled supports one macOS architecture per build directory; "
|
||||||
"configure separate arm64 and x86_64 build directories")
|
"configure separate arm64 and x86_64 build directories")
|
||||||
endif()
|
endif()
|
||||||
list(GET CMAKE_OSX_ARCHITECTURES 0 _mkw_osx_arch)
|
|
||||||
set(CMAKE_SYSTEM_PROCESSOR "${_mkw_osx_arch}" CACHE STRING "Target processor architecture" FORCE)
|
|
||||||
set(CMAKE_SYSTEM_PROCESSOR "${_mkw_osx_arch}")
|
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(WIN32 AND MINGW AND CMAKE_SYSTEM_PROCESSOR MATCHES "^(AMD64|amd64|x86_64|X86_64)$")
|
if(WIN32 AND MINGW AND CMAKE_SYSTEM_PROCESSOR MATCHES "^(AMD64|amd64|x86_64|X86_64)$")
|
||||||
@@ -47,7 +44,6 @@ if(NOT CMAKE_BUILD_TYPE STREQUAL "Release")
|
|||||||
endif()
|
endif()
|
||||||
|
|
||||||
option(MKW_BUILD_PRODUCTS "Build translated WiiCompiled product targets" ON)
|
option(MKW_BUILD_PRODUCTS "Build translated WiiCompiled product targets" ON)
|
||||||
option(MKW_BUILD_PSQ_TESTS "Build focused PSQ ISA tests" OFF)
|
|
||||||
|
|
||||||
# Preprocessor definitions that belong to this project's own code (the runtime,
|
# Preprocessor definitions that belong to this project's own code (the runtime,
|
||||||
# the translated shards and the product glue) and to nothing else. They are
|
# the translated shards and the product glue) and to nothing else. They are
|
||||||
@@ -134,44 +130,6 @@ if(NOT MKW_NATIVE_PREBUILT_DIR)
|
|||||||
set_target_properties(mkw_cryptopp PROPERTIES UNITY_BUILD OFF)
|
set_target_properties(mkw_cryptopp PROPERTIES UNITY_BUILD OFF)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# TLS for non-Windows guest network HLE (runtime/src/hle/net/network_ssl.cpp) - the Windows path
|
|
||||||
# uses Schannel (a Windows-only OS API), which has no equivalent on Linux/Android, so this project
|
|
||||||
# needs its own TLS library there. mbed TLS was chosen over OpenSSL specifically because it cross-
|
|
||||||
# compiles cleanly for Android with nothing beyond a plain C toolchain (no perl/asm build-script
|
|
||||||
# dependency the way OpenSSL's build has), matching how this project already prefers toolchain-
|
|
||||||
# simple libraries (see Crypto++ above, similarly stripped of ASM/SIMD for portability).
|
|
||||||
# The from-source build fetches a pinned upstream tarball with a checked SHA-256.
|
|
||||||
# The Linux native prebuilt package instead ships its compiled archives and headers.
|
|
||||||
# Bump both values in cmake/MbedTLSPin.cmake when updating; the hash comes from
|
|
||||||
# upstream's signed `mbedtls-<version>-sha256sum.txt` release asset.
|
|
||||||
#
|
|
||||||
# The alias exists on every platform so the link lines in cmake/PublicProducts.cmake stay
|
|
||||||
# platform-independent, but it is only populated where network_ssl.cpp actually compiles the mbed
|
|
||||||
# TLS path (`#ifndef _WIN32`). Windows keeps Schannel and must not fetch anything: its builds run
|
|
||||||
# with FETCHCONTENT_FULLY_DISCONNECTED=ON against the offline dependency set prepared by
|
|
||||||
# Launcher/Prepare-Dependencies.ps1, so an unconditional fetch would fail a clean configure there
|
|
||||||
# and would also add a dependency Windows never links.
|
|
||||||
add_library(mkw_mbedtls INTERFACE)
|
|
||||||
add_library(mkw::mbedtls ALIAS mkw_mbedtls)
|
|
||||||
if(NOT MKW_PLATFORM_WINDOWS)
|
|
||||||
include("${CMAKE_CURRENT_LIST_DIR}/cmake/MbedTLSPin.cmake")
|
|
||||||
if(NOT MKW_NATIVE_PREBUILT_DIR)
|
|
||||||
include(FetchContent)
|
|
||||||
FetchContent_Declare(mkw_mbedtls_upstream
|
|
||||||
URL "https://github.com/Mbed-TLS/mbedtls/releases/download/mbedtls-${MKW_MBEDTLS_VERSION}/mbedtls-${MKW_MBEDTLS_VERSION}.tar.bz2"
|
|
||||||
URL_HASH SHA256=${MKW_MBEDTLS_SHA256}
|
|
||||||
DOWNLOAD_EXTRACT_TIMESTAMP FALSE
|
|
||||||
)
|
|
||||||
# Subproject mode already disables testing and codegen; it still enables programs.
|
|
||||||
set(ENABLE_PROGRAMS OFF CACHE BOOL "" FORCE)
|
|
||||||
set(ENABLE_TESTING OFF CACHE BOOL "" FORCE)
|
|
||||||
set(MBEDTLS_FATAL_WARNINGS OFF CACHE BOOL "" FORCE)
|
|
||||||
set(DISABLE_PACKAGE_CONFIG_AND_INSTALL ON CACHE BOOL "" FORCE)
|
|
||||||
FetchContent_MakeAvailable(mkw_mbedtls_upstream)
|
|
||||||
target_link_libraries(mkw_mbedtls INTERFACE MbedTLS::mbedtls MbedTLS::mbedx509 MbedTLS::mbedcrypto)
|
|
||||||
endif()
|
|
||||||
endif()
|
|
||||||
|
|
||||||
set(MKW_TRANSLATED_COMPILE_JOBS 0 CACHE STRING
|
set(MKW_TRANSLATED_COMPILE_JOBS 0 CACHE STRING
|
||||||
"Cap on concurrently compiling translated shard TUs via a Ninja job pool (0 = uncapped). \
|
"Cap on concurrently compiling translated shard TUs via a Ninja job pool (0 = uncapped). \
|
||||||
Scheduling only - never affects output bytes, so it is deliberately outside the canonical flag fingerprint.")
|
Scheduling only - never affects output bytes, so it is deliberately outside the canonical flag fingerprint.")
|
||||||
@@ -189,18 +147,6 @@ set(MKW_AURORA_DIR "${CMAKE_CURRENT_LIST_DIR}/../aurora-main")
|
|||||||
# Fast-math may erase them and change guest-visible integer conversions.
|
# Fast-math may erase them and change guest-visible integer conversions.
|
||||||
set(MKW_TRANSLATED_PPC_FP_OPTIONS -fno-fast-math -ffp-contract=off)
|
set(MKW_TRANSLATED_PPC_FP_OPTIONS -fno-fast-math -ffp-contract=off)
|
||||||
|
|
||||||
set(MKW_PPC_SEMANTIC_RUNTIME_SOURCES
|
|
||||||
"${CMAKE_CURRENT_LIST_DIR}/src/ppc_helpers.cpp"
|
|
||||||
"${CMAKE_CURRENT_LIST_DIR}/src/fpu_helpers.cpp"
|
|
||||||
"${CMAKE_CURRENT_LIST_DIR}/src/ppc_quantized.cpp")
|
|
||||||
set_source_files_properties(${MKW_PPC_SEMANTIC_RUNTIME_SOURCES} PROPERTIES
|
|
||||||
SKIP_UNITY_BUILD_INCLUSION ON
|
|
||||||
SKIP_PRECOMPILE_HEADERS ON
|
|
||||||
COMPILE_OPTIONS "${MKW_TRANSLATED_PPC_FP_OPTIONS}")
|
|
||||||
# Match the optimization policy of the translated callers as well as their FP policy.
|
|
||||||
set_property(SOURCE "${CMAKE_CURRENT_LIST_DIR}/src/ppc_quantized.cpp" APPEND PROPERTY
|
|
||||||
COMPILE_OPTIONS -O2 -fno-slp-vectorize)
|
|
||||||
|
|
||||||
# ----------------------------------------------------------------------
|
# ----------------------------------------------------------------------
|
||||||
# Third-party: aurora-main (provides SDL3 + GPU backends)
|
# Third-party: aurora-main (provides SDL3 + GPU backends)
|
||||||
# ----------------------------------------------------------------------
|
# ----------------------------------------------------------------------
|
||||||
@@ -351,39 +297,11 @@ set_target_properties(mkw_platform PROPERTIES UNITY_BUILD OFF)
|
|||||||
# Keep these independent from Aurora's BUILD_TESTING option: they validate the
|
# Keep these independent from Aurora's BUILD_TESTING option: they validate the
|
||||||
# project's host-platform contracts, not Aurora's third-party test suite.
|
# project's host-platform contracts, not Aurora's third-party test suite.
|
||||||
enable_testing()
|
enable_testing()
|
||||||
if(MKW_BUILD_PSQ_TESTS)
|
|
||||||
add_executable(mkw_psq_helpers_tests
|
|
||||||
"${CMAKE_CURRENT_LIST_DIR}/tests/psq_helpers_tests.cpp"
|
|
||||||
"${CMAKE_CURRENT_LIST_DIR}/src/ppc_quantized.cpp")
|
|
||||||
target_include_directories(mkw_psq_helpers_tests PRIVATE
|
|
||||||
"${CMAKE_CURRENT_LIST_DIR}/tests/psq_memory"
|
|
||||||
"${CMAKE_CURRENT_LIST_DIR}/include/isa"
|
|
||||||
"${CMAKE_CURRENT_LIST_DIR}/include")
|
|
||||||
target_compile_features(mkw_psq_helpers_tests PRIVATE cxx_std_17)
|
|
||||||
target_compile_options(mkw_psq_helpers_tests PRIVATE
|
|
||||||
-O2 ${MKW_TRANSLATED_PPC_FP_OPTIONS} -fno-slp-vectorize)
|
|
||||||
if(CMAKE_SYSTEM_PROCESSOR MATCHES "^(AMD64|amd64|x86_64|X86_64)$")
|
|
||||||
target_compile_options(mkw_psq_helpers_tests PRIVATE -march=x86-64-v3)
|
|
||||||
endif()
|
|
||||||
set_target_properties(mkw_psq_helpers_tests PROPERTIES UNITY_BUILD OFF)
|
|
||||||
add_test(NAME mkw_psq_helpers_tests COMMAND mkw_psq_helpers_tests)
|
|
||||||
add_test(NAME mkw_psq_reserved_tests COMMAND "${CMAKE_COMMAND}"
|
|
||||||
"-DPSQ_TEST_EXECUTABLE=$<TARGET_FILE:mkw_psq_helpers_tests>"
|
|
||||||
-P "${CMAKE_CURRENT_LIST_DIR}/tests/psq_reserved_tests.cmake")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
add_executable(mkw_platform_paths_tests "${CMAKE_CURRENT_LIST_DIR}/tests/platform_paths_tests.cpp")
|
add_executable(mkw_platform_paths_tests "${CMAKE_CURRENT_LIST_DIR}/tests/platform_paths_tests.cpp")
|
||||||
target_link_libraries(mkw_platform_paths_tests PRIVATE mkw_platform)
|
target_link_libraries(mkw_platform_paths_tests PRIVATE mkw_platform)
|
||||||
target_compile_features(mkw_platform_paths_tests PRIVATE cxx_std_17)
|
target_compile_features(mkw_platform_paths_tests PRIVATE cxx_std_17)
|
||||||
add_test(NAME mkw_platform_paths_tests COMMAND mkw_platform_paths_tests)
|
add_test(NAME mkw_platform_paths_tests COMMAND mkw_platform_paths_tests)
|
||||||
|
|
||||||
add_executable(mkw_runtime_config_tests "${CMAKE_CURRENT_LIST_DIR}/tests/runtime_config_tests.cpp")
|
|
||||||
target_include_directories(mkw_runtime_config_tests PRIVATE
|
|
||||||
"${CMAKE_CURRENT_LIST_DIR}/include"
|
|
||||||
"${CMAKE_CURRENT_LIST_DIR}/third_party/toml11")
|
|
||||||
target_compile_features(mkw_runtime_config_tests PRIVATE cxx_std_20)
|
|
||||||
add_test(NAME mkw_runtime_config_tests COMMAND mkw_runtime_config_tests)
|
|
||||||
|
|
||||||
add_executable(mkw_nand_save_tests "${CMAKE_CURRENT_LIST_DIR}/tests/nand_save_tests.cpp")
|
add_executable(mkw_nand_save_tests "${CMAKE_CURRENT_LIST_DIR}/tests/nand_save_tests.cpp")
|
||||||
target_include_directories(mkw_nand_save_tests PRIVATE "${CMAKE_CURRENT_LIST_DIR}/include")
|
target_include_directories(mkw_nand_save_tests PRIVATE "${CMAKE_CURRENT_LIST_DIR}/include")
|
||||||
target_compile_features(mkw_nand_save_tests PRIVATE cxx_std_17)
|
target_compile_features(mkw_nand_save_tests PRIVATE cxx_std_17)
|
||||||
@@ -460,13 +378,6 @@ if(MKW_PLATFORM_MACOS)
|
|||||||
target_include_directories(mkw_macos_guest_flat_memory_tests PRIVATE "${CMAKE_CURRENT_LIST_DIR}/include")
|
target_include_directories(mkw_macos_guest_flat_memory_tests PRIVATE "${CMAKE_CURRENT_LIST_DIR}/include")
|
||||||
target_compile_features(mkw_macos_guest_flat_memory_tests PRIVATE cxx_std_17)
|
target_compile_features(mkw_macos_guest_flat_memory_tests PRIVATE cxx_std_17)
|
||||||
add_test(NAME mkw_macos_guest_flat_memory_tests COMMAND mkw_macos_guest_flat_memory_tests)
|
add_test(NAME mkw_macos_guest_flat_memory_tests COMMAND mkw_macos_guest_flat_memory_tests)
|
||||||
|
|
||||||
add_executable(mkw_macos_external_audio_tests
|
|
||||||
"${CMAKE_CURRENT_LIST_DIR}/tests/macos_external_audio_tests.cpp"
|
|
||||||
"${CMAKE_CURRENT_LIST_DIR}/src/external_audio_macos.cpp")
|
|
||||||
target_include_directories(mkw_macos_external_audio_tests PRIVATE "${CMAKE_CURRENT_LIST_DIR}/include")
|
|
||||||
target_compile_features(mkw_macos_external_audio_tests PRIVATE cxx_std_17)
|
|
||||||
add_test(NAME mkw_macos_external_audio_tests COMMAND mkw_macos_external_audio_tests)
|
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# The translator emits the complete, content-addressed source graph. Consuming
|
# The translator emits the complete, content-addressed source graph. Consuming
|
||||||
@@ -515,7 +426,7 @@ else()
|
|||||||
target_compile_definitions(mkw_macos_native_compile PRIVATE SDL_MAIN_HANDLED TARGET_PC)
|
target_compile_definitions(mkw_macos_native_compile PRIVATE SDL_MAIN_HANDLED TARGET_PC)
|
||||||
target_link_libraries(mkw_macos_native_compile PRIVATE
|
target_link_libraries(mkw_macos_native_compile PRIVATE
|
||||||
aurora::gx aurora::pad aurora::si aurora::vi aurora::mtx
|
aurora::gx aurora::pad aurora::si aurora::vi aurora::mtx
|
||||||
mkw::pugixml mkw::toml11 mkw::cryptopp mkw::mbedtls)
|
mkw::pugixml mkw::toml11 mkw::cryptopp)
|
||||||
if(MKW_PLATFORM_MACOS_X86_64)
|
if(MKW_PLATFORM_MACOS_X86_64)
|
||||||
target_link_libraries(mkw_macos_native_compile PRIVATE mkw::libco)
|
target_link_libraries(mkw_macos_native_compile PRIVATE mkw::libco)
|
||||||
# Keep this compile-only audit on the same Haswell-era x86-64-v3
|
# Keep this compile-only audit on the same Haswell-era x86-64-v3
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,4 +0,0 @@
|
|||||||
# Keep the release and verified archive hash together. The Linux native prebuilt
|
|
||||||
# fingerprint includes this file, so changing either value forces a new harvest.
|
|
||||||
set(MKW_MBEDTLS_VERSION "3.6.7")
|
|
||||||
set(MKW_MBEDTLS_SHA256 "a7e8bcbec0e6f761b4af24f25677626b35f762f68eef79c08677a363212d11f6")
|
|
||||||
@@ -42,28 +42,6 @@ endfunction()
|
|||||||
mkw_np_resolve(_mkw_np_includes ${MKW_NP_INCLUDE_DIRECTORIES})
|
mkw_np_resolve(_mkw_np_includes ${MKW_NP_INCLUDE_DIRECTORIES})
|
||||||
mkw_np_resolve(_mkw_np_links ${MKW_NP_LINK_LIBRARIES})
|
mkw_np_resolve(_mkw_np_links ${MKW_NP_LINK_LIBRARIES})
|
||||||
mkw_np_resolve(_mkw_np_dawn_config ${MKW_NP_DAWN_CONFIG_DIR})
|
mkw_np_resolve(_mkw_np_dawn_config ${MKW_NP_DAWN_CONFIG_DIR})
|
||||||
if(NOT MKW_PLATFORM_WINDOWS)
|
|
||||||
if(NOT MKW_NP_MBEDTLS_LIBRARIES OR NOT MKW_NP_MBEDTLS_INCLUDE_DIR)
|
|
||||||
message(FATAL_ERROR
|
|
||||||
"The Linux native prebuilt package has no Mbed TLS archives; regenerate it with Prepare-NativePrebuilt.sh")
|
|
||||||
endif()
|
|
||||||
file(SHA256 "${CMAKE_CURRENT_LIST_DIR}/MbedTLSPin.cmake" _mkw_np_current_mbedtls_fingerprint)
|
|
||||||
if(NOT MKW_NP_MBEDTLS_FINGERPRINT STREQUAL _mkw_np_current_mbedtls_fingerprint)
|
|
||||||
message(FATAL_ERROR "The native prebuilt Mbed TLS version differs from this workspace; regenerate the package")
|
|
||||||
endif()
|
|
||||||
mkw_np_resolve(_mkw_np_mbedtls_links ${MKW_NP_MBEDTLS_LIBRARIES})
|
|
||||||
mkw_np_resolve(_mkw_np_mbedtls_include ${MKW_NP_MBEDTLS_INCLUDE_DIR})
|
|
||||||
foreach(_archive IN LISTS _mkw_np_mbedtls_links)
|
|
||||||
if(NOT EXISTS "${_archive}")
|
|
||||||
message(FATAL_ERROR "The native prebuilt package is missing Mbed TLS archive: ${_archive}")
|
|
||||||
endif()
|
|
||||||
endforeach()
|
|
||||||
if(NOT IS_DIRECTORY "${_mkw_np_mbedtls_include}")
|
|
||||||
message(FATAL_ERROR "The native prebuilt package is missing Mbed TLS headers: ${_mkw_np_mbedtls_include}")
|
|
||||||
endif()
|
|
||||||
target_include_directories(mkw_mbedtls SYSTEM INTERFACE "${_mkw_np_mbedtls_include}")
|
|
||||||
target_link_libraries(mkw_mbedtls INTERFACE ${_mkw_np_mbedtls_links})
|
|
||||||
endif()
|
|
||||||
|
|
||||||
foreach(_dir IN LISTS _mkw_np_includes)
|
foreach(_dir IN LISTS _mkw_np_includes)
|
||||||
if(NOT IS_DIRECTORY "${_dir}")
|
if(NOT IS_DIRECTORY "${_dir}")
|
||||||
|
|||||||
@@ -77,9 +77,6 @@ target_compile_features(mkw_np_probe PRIVATE cxx_std_20)
|
|||||||
target_link_libraries(mkw_np_probe PRIVATE
|
target_link_libraries(mkw_np_probe PRIVATE
|
||||||
aurora::gx aurora::pad aurora::si aurora::vi aurora::mtx
|
aurora::gx aurora::pad aurora::si aurora::vi aurora::mtx
|
||||||
mkw::cryptopp)
|
mkw::cryptopp)
|
||||||
if(NOT MKW_PLATFORM_WINDOWS)
|
|
||||||
target_link_libraries(mkw_np_probe PRIVATE mkw::mbedtls)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
get_filename_component(_mkw_np_aurora_dir "${MKW_AURORA_DIR}" ABSOLUTE)
|
get_filename_component(_mkw_np_aurora_dir "${MKW_AURORA_DIR}" ABSOLUTE)
|
||||||
mkw_collect_buildsystem_targets("${_mkw_np_aurora_dir}" _mkw_np_all_targets)
|
mkw_collect_buildsystem_targets("${_mkw_np_aurora_dir}" _mkw_np_all_targets)
|
||||||
@@ -87,25 +84,6 @@ mkw_collect_buildsystem_targets("${_mkw_np_aurora_dir}" _mkw_np_all_targets)
|
|||||||
# the scan above cannot see it; it is appended explicitly. The type and closure
|
# the scan above cannot see it; it is appended explicitly. The type and closure
|
||||||
# checks below still apply to it.
|
# checks below still apply to it.
|
||||||
list(APPEND _mkw_np_all_targets "mkw_cryptopp")
|
list(APPEND _mkw_np_all_targets "mkw_cryptopp")
|
||||||
if(NOT MKW_PLATFORM_WINDOWS)
|
|
||||||
set(_mkw_np_mbedtls_targets mbedtls mbedx509 mbedcrypto)
|
|
||||||
# mbedcrypto links these third-party archives when upstream defines them.
|
|
||||||
# Keep dependencies after mbedcrypto for static linking on the consumer side.
|
|
||||||
foreach(_t everest p256m)
|
|
||||||
if(TARGET ${_t})
|
|
||||||
list(APPEND _mkw_np_mbedtls_targets ${_t})
|
|
||||||
endif()
|
|
||||||
endforeach()
|
|
||||||
list(APPEND _mkw_np_all_targets ${_mkw_np_mbedtls_targets})
|
|
||||||
# Keep the complete TLS archive set separate from aurora's aggregate link interface.
|
|
||||||
set(_mkw_np_mbedtls_lines "")
|
|
||||||
foreach(_t IN LISTS _mkw_np_mbedtls_targets)
|
|
||||||
string(APPEND _mkw_np_mbedtls_lines
|
|
||||||
"${_t}|$<TARGET_LINKER_FILE:${_t}>\n")
|
|
||||||
endforeach()
|
|
||||||
file(GENERATE OUTPUT "${MKW_NATIVE_PREBUILT_EXPORT_DIR}/mbedtls.txt"
|
|
||||||
CONTENT "${_mkw_np_mbedtls_lines}")
|
|
||||||
endif()
|
|
||||||
if(NOT _mkw_np_all_targets)
|
if(NOT _mkw_np_all_targets)
|
||||||
message(FATAL_ERROR "No buildsystem targets were found under ${_mkw_np_aurora_dir}")
|
message(FATAL_ERROR "No buildsystem targets were found under ${_mkw_np_aurora_dir}")
|
||||||
endif()
|
endif()
|
||||||
@@ -158,7 +136,6 @@ string(REPLACE ";" "," _mkw_np_lib_targets_csv "${_mkw_np_lib_targets}")
|
|||||||
file(WRITE "${MKW_NATIVE_PREBUILT_EXPORT_DIR}/meta.txt"
|
file(WRITE "${MKW_NATIVE_PREBUILT_EXPORT_DIR}/meta.txt"
|
||||||
"aurora_dir=${_mkw_np_aurora_dir}\n"
|
"aurora_dir=${_mkw_np_aurora_dir}\n"
|
||||||
"runtime_dir=${CMAKE_CURRENT_SOURCE_DIR}\n"
|
"runtime_dir=${CMAKE_CURRENT_SOURCE_DIR}\n"
|
||||||
"mbedtls_source_dir=${mkw_mbedtls_upstream_SOURCE_DIR}\n"
|
|
||||||
"binary_dir=${CMAKE_BINARY_DIR}\n"
|
"binary_dir=${CMAKE_BINARY_DIR}\n"
|
||||||
"dawn_config_dir=${_mkw_np_dawn_config_dir}\n"
|
"dawn_config_dir=${_mkw_np_dawn_config_dir}\n"
|
||||||
"dawn_prebuilt_source_dir=${dawn_prebuilt_SOURCE_DIR}\n"
|
"dawn_prebuilt_source_dir=${dawn_prebuilt_SOURCE_DIR}\n"
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ list(REMOVE_DUPLICATES SOURCES)
|
|||||||
if(MKW_PLATFORM_MACOS)
|
if(MKW_PLATFORM_MACOS)
|
||||||
find_library(MKW_IOKIT_FRAMEWORK IOKit REQUIRED)
|
find_library(MKW_IOKIT_FRAMEWORK IOKit REQUIRED)
|
||||||
find_library(MKW_COREFOUNDATION_FRAMEWORK CoreFoundation REQUIRED)
|
find_library(MKW_COREFOUNDATION_FRAMEWORK CoreFoundation REQUIRED)
|
||||||
find_library(MKW_COREAUDIO_FRAMEWORK CoreAudio REQUIRED)
|
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
function(mkw_apply_common_compile_options target)
|
function(mkw_apply_common_compile_options target)
|
||||||
@@ -82,25 +81,14 @@ target_compile_definitions(mkw_runtime_common PRIVATE
|
|||||||
_DISABLE_STRING_ANNOTATION _DISABLE_VECTOR_ANNOTATION)
|
_DISABLE_STRING_ANNOTATION _DISABLE_VECTOR_ANNOTATION)
|
||||||
target_link_libraries(mkw_runtime_common PRIVATE
|
target_link_libraries(mkw_runtime_common PRIVATE
|
||||||
aurora::gx aurora::pad aurora::si aurora::vi aurora::mtx)
|
aurora::gx aurora::pad aurora::si aurora::vi aurora::mtx)
|
||||||
target_link_libraries(mkw_runtime_common PRIVATE mkw_platform mkw::pugixml mkw::toml11 mkw::cryptopp mkw::mbedtls)
|
target_link_libraries(mkw_runtime_common PRIVATE mkw_platform mkw::pugixml mkw::toml11 mkw::cryptopp)
|
||||||
if(MKW_PLATFORM_WINDOWS)
|
if(MKW_PLATFORM_WINDOWS)
|
||||||
target_link_libraries(mkw_runtime_common PRIVATE shell32 windowsapp)
|
target_link_libraries(mkw_runtime_common PRIVATE shell32 windowsapp)
|
||||||
elseif(MKW_PLATFORM_LINUX)
|
elseif(MKW_PLATFORM_LINUX OR MKW_PLATFORM_MACOS_X86_64)
|
||||||
# ${CMAKE_DL_LIBS} for music_attenuation.cpp's dlopen of libdbus-1 (MPRIS
|
# ${CMAKE_DL_LIBS} for music_attenuation.cpp's dlopen of libdbus-1 (MPRIS
|
||||||
# media monitoring).
|
# media monitoring). Empty on platforms where dl* is already in libc/libSystem.
|
||||||
target_link_libraries(mkw_runtime_common PRIVATE mkw::libco ${CMAKE_DL_LIBS})
|
target_link_libraries(mkw_runtime_common PRIVATE mkw::libco ${CMAKE_DL_LIBS})
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(MKW_PLATFORM_MACOS)
|
|
||||||
# CoreAudio framework is required for automatic music muting on macOS.
|
|
||||||
target_link_libraries(mkw_runtime_common PRIVATE "${MKW_COREAUDIO_FRAMEWORK}")
|
|
||||||
if(MKW_PLATFORM_MACOS_X86_64)
|
|
||||||
# libco is used by Intel macOS. Apple Silicon uses the local
|
|
||||||
# x18-safe assembly backend and therefore does not define mkw::libco.
|
|
||||||
target_link_libraries(mkw_runtime_common PRIVATE mkw::libco)
|
|
||||||
endif()
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if(MKW_CPPWINRT_INCLUDE_DIR)
|
if(MKW_CPPWINRT_INCLUDE_DIR)
|
||||||
if(NOT EXISTS "${MKW_CPPWINRT_INCLUDE_DIR}/winrt/base.h")
|
if(NOT EXISTS "${MKW_CPPWINRT_INCLUDE_DIR}/winrt/base.h")
|
||||||
message(FATAL_ERROR
|
message(FATAL_ERROR
|
||||||
@@ -130,7 +118,16 @@ foreach(source IN LISTS SOURCES)
|
|||||||
endif()
|
endif()
|
||||||
set_source_files_properties("${source}" PROPERTIES UNITY_GROUP "${runtime_group}")
|
set_source_files_properties("${source}" PROPERTIES UNITY_GROUP "${runtime_group}")
|
||||||
endforeach()
|
endforeach()
|
||||||
# PPC semantic sources are excluded from unity/PCH and configured in CMakeLists.txt.
|
# These translation units implement guest-visible floating-point bit
|
||||||
|
# semantics. Keep them out of the fast-math runtime unity groups and apply
|
||||||
|
# the same contraction/rounding policy as translated PPC shards.
|
||||||
|
set(MKW_PPC_SEMANTIC_RUNTIME_SOURCES
|
||||||
|
"${MKW_RUNTIME_SOURCE_DIR}/src/ppc_helpers.cpp"
|
||||||
|
"${MKW_RUNTIME_SOURCE_DIR}/src/fpu_helpers.cpp")
|
||||||
|
set_source_files_properties(${MKW_PPC_SEMANTIC_RUNTIME_SOURCES} PROPERTIES
|
||||||
|
SKIP_UNITY_BUILD_INCLUSION ON
|
||||||
|
SKIP_PRECOMPILE_HEADERS ON
|
||||||
|
COMPILE_OPTIONS "${MKW_TRANSLATED_PPC_FP_OPTIONS}")
|
||||||
set_target_properties(mkw_runtime_common PROPERTIES UNITY_BUILD ON UNITY_BUILD_MODE GROUP)
|
set_target_properties(mkw_runtime_common PROPERTIES UNITY_BUILD ON UNITY_BUILD_MODE GROUP)
|
||||||
target_precompile_headers(mkw_runtime_common PRIVATE "${MKW_RUNTIME_SOURCE_DIR}/include/mkw_pch.h")
|
target_precompile_headers(mkw_runtime_common PRIVATE "${MKW_RUNTIME_SOURCE_DIR}/include/mkw_pch.h")
|
||||||
mkw_apply_common_compile_options(mkw_runtime_common)
|
mkw_apply_common_compile_options(mkw_runtime_common)
|
||||||
@@ -202,13 +199,13 @@ function(mkw_configure_product target)
|
|||||||
# include the same fat translated headers; bound them by the same pool.
|
# include the same fat translated headers; bound them by the same pool.
|
||||||
mkw_bound_translated_compiles(${target})
|
mkw_bound_translated_compiles(${target})
|
||||||
target_link_libraries(${target} PRIVATE
|
target_link_libraries(${target} PRIVATE
|
||||||
mkw_platform mkw_base_shared mkw::pugixml mkw::toml11 mkw::cryptopp mkw::mbedtls)
|
mkw_platform mkw_base_shared mkw::pugixml mkw::toml11 mkw::cryptopp)
|
||||||
|
|
||||||
target_link_libraries(${target} PRIVATE
|
target_link_libraries(${target} PRIVATE
|
||||||
aurora::gx aurora::pad aurora::si aurora::vi aurora::mtx)
|
aurora::gx aurora::pad aurora::si aurora::vi aurora::mtx)
|
||||||
if(MKW_PLATFORM_MACOS)
|
if(MKW_PLATFORM_MACOS)
|
||||||
target_link_libraries(${target} PRIVATE
|
target_link_libraries(${target} PRIVATE
|
||||||
"${MKW_IOKIT_FRAMEWORK}" "${MKW_COREFOUNDATION_FRAMEWORK}" "${MKW_COREAUDIO_FRAMEWORK}")
|
"${MKW_IOKIT_FRAMEWORK}" "${MKW_COREFOUNDATION_FRAMEWORK}")
|
||||||
target_link_options(${target} PRIVATE
|
target_link_options(${target} PRIVATE
|
||||||
"LINKER:-U,_OBJC_CLASS_$_MTLLogStateDescriptor")
|
"LINKER:-U,_OBJC_CLASS_$_MTLLogStateDescriptor")
|
||||||
endif()
|
endif()
|
||||||
@@ -291,21 +288,6 @@ function(mkw_configure_product target)
|
|||||||
add_custom_command(TARGET ${target} POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
add_custom_command(TARGET ${target} POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||||
"${MKW_INITIAL_PIPELINE_CACHE}"
|
"${MKW_INITIAL_PIPELINE_CACHE}"
|
||||||
"$<TARGET_FILE_DIR:${target}>/initial_pipeline_cache.db")
|
"$<TARGET_FILE_DIR:${target}>/initial_pipeline_cache.db")
|
||||||
|
|
||||||
# Non-Windows TLS (runtime/src/hle/net/network_ssl.cpp's mbed TLS path) needs a trusted root
|
|
||||||
# CA bundle to verify server certificates against - Windows gets this for free from the OS via
|
|
||||||
# Schannel, mbed TLS does not ship one itself. Not SHA256-pinned like the DSP ROM above: unlike
|
|
||||||
# a fixed hardware ROM, this bundle is expected to be refreshed periodically as CAs rotate.
|
|
||||||
# Windows gets its trust store from Schannel, so only the platforms that actually build the
|
|
||||||
# mbed TLS path need the bundle beside the executable.
|
|
||||||
if(NOT MKW_PLATFORM_WINDOWS)
|
|
||||||
set(MKW_CA_CERTIFICATE_BUNDLE "${MKW_RUNTIME_SOURCE_DIR}/assets/certs/cacert.pem")
|
|
||||||
if(NOT EXISTS "${MKW_CA_CERTIFICATE_BUNDLE}")
|
|
||||||
message(FATAL_ERROR "Missing TLS root CA bundle: ${MKW_CA_CERTIFICATE_BUNDLE}")
|
|
||||||
endif()
|
|
||||||
add_custom_command(TARGET ${target} POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
|
||||||
"${MKW_CA_CERTIFICATE_BUNDLE}" "$<TARGET_FILE_DIR:${target}>/cacert.pem")
|
|
||||||
endif()
|
|
||||||
endfunction()
|
endfunction()
|
||||||
|
|
||||||
add_executable(WiiCompiled "${MKW_BASE_PRODUCT_SOURCE}" ${MKW_BASE_REGISTRATION_SOURCES})
|
add_executable(WiiCompiled "${MKW_BASE_PRODUCT_SOURCE}" ${MKW_BASE_REGISTRATION_SOURCES})
|
||||||
|
|||||||
@@ -16,9 +16,7 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
extern "C" bool g_dynamicAspectRatioEnabled;
|
extern "C" bool g_dynamicAspectRatioEnabled;
|
||||||
void ConfigureMkwDynamicAspect(bool widescreen, bool forceAspect169, uint32_t surfaceWidth, uint32_t surfaceHeight);
|
void ConfigureMkwDynamicAspect(bool widescreen, uint32_t surfaceWidth, uint32_t surfaceHeight);
|
||||||
void SetMkwForceAspect169(bool enabled);
|
|
||||||
bool MkwForceAspect169Requested();
|
|
||||||
void UpdateMkwDynamicAspectSurface(uint32_t surfaceWidth, uint32_t surfaceHeight);
|
void UpdateMkwDynamicAspectSurface(uint32_t surfaceWidth, uint32_t surfaceHeight);
|
||||||
// Arms the "keep EGG::Frustum's projection scale" flag on every screen that
|
// Arms the "keep EGG::Frustum's projection scale" flag on every screen that
|
||||||
// renders to a fixed-size offscreen target. Cheap and idempotent; called from
|
// renders to a fixed-size offscreen target. Cheap and idempotent; called from
|
||||||
@@ -60,7 +58,6 @@ inline void Flush(bool force = false) {
|
|||||||
// still active. A window close is an intentional successful exit, so end the
|
// still active. A window close is an intentional successful exit, so end the
|
||||||
// process directly and do not run the crash/atexit paths.
|
// process directly and do not run the crash/atexit paths.
|
||||||
[[noreturn]] inline void ExitForAuroraWindowClose() noexcept {
|
[[noreturn]] inline void ExitForAuroraWindowClose() noexcept {
|
||||||
settings_overlay::ReleaseControllers();
|
|
||||||
WindowPlacementPersistence::Flush(true);
|
WindowPlacementPersistence::Flush(true);
|
||||||
#if defined(_WIN32)
|
#if defined(_WIN32)
|
||||||
::ExitProcess(0);
|
::ExitProcess(0);
|
||||||
|
|||||||
@@ -48,19 +48,8 @@ struct NativeButtonItem {
|
|||||||
uint32_t nativeButton;
|
uint32_t nativeButton;
|
||||||
};
|
};
|
||||||
|
|
||||||
inline constexpr auto kNativeButtons = std::to_array<NativeButtonItem>({
|
inline constexpr std::array<NativeButtonItem, SDL_GAMEPAD_BUTTON_COUNT + 1> kNativeButtons = {{
|
||||||
{"disabled", "Unmapped", PAD_NATIVE_BUTTON_DISABLED},
|
{"unmapped", "Unmapped / analog trigger", PAD_NATIVE_BUTTON_INVALID},
|
||||||
{"left_trigger", "Left trigger (LT / L2)", PADEncodeAxisButton(SDL_GAMEPAD_AXIS_LEFT_TRIGGER, false)},
|
|
||||||
{"right_trigger", "Right trigger (RT / R2)", PADEncodeAxisButton(SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, false)},
|
|
||||||
{"left_stick_left", "Left stick left", PADEncodeAxisButton(SDL_GAMEPAD_AXIS_LEFTX, true)},
|
|
||||||
{"left_stick_right", "Left stick right", PADEncodeAxisButton(SDL_GAMEPAD_AXIS_LEFTX, false)},
|
|
||||||
{"left_stick_up", "Left stick up", PADEncodeAxisButton(SDL_GAMEPAD_AXIS_LEFTY, true)},
|
|
||||||
{"left_stick_down", "Left stick down", PADEncodeAxisButton(SDL_GAMEPAD_AXIS_LEFTY, false)},
|
|
||||||
{"right_stick_left", "Right stick left", PADEncodeAxisButton(SDL_GAMEPAD_AXIS_RIGHTX, true)},
|
|
||||||
{"right_stick_right", "Right stick right", PADEncodeAxisButton(SDL_GAMEPAD_AXIS_RIGHTX, false)},
|
|
||||||
{"right_stick_up", "Right stick up", PADEncodeAxisButton(SDL_GAMEPAD_AXIS_RIGHTY, true)},
|
|
||||||
{"right_stick_down", "Right stick down", PADEncodeAxisButton(SDL_GAMEPAD_AXIS_RIGHTY, false)},
|
|
||||||
{"unmapped", "Default", PAD_NATIVE_BUTTON_INVALID},
|
|
||||||
{"south", "South (A / Cross)", SDL_GAMEPAD_BUTTON_SOUTH},
|
{"south", "South (A / Cross)", SDL_GAMEPAD_BUTTON_SOUTH},
|
||||||
{"east", "East (B / Circle)", SDL_GAMEPAD_BUTTON_EAST},
|
{"east", "East (B / Circle)", SDL_GAMEPAD_BUTTON_EAST},
|
||||||
{"west", "West (X / Square)", SDL_GAMEPAD_BUTTON_WEST},
|
{"west", "West (X / Square)", SDL_GAMEPAD_BUTTON_WEST},
|
||||||
@@ -87,7 +76,7 @@ inline constexpr auto kNativeButtons = std::to_array<NativeButtonItem>({
|
|||||||
{"misc4", "Misc 4 / GC R click", SDL_GAMEPAD_BUTTON_MISC4},
|
{"misc4", "Misc 4 / GC R click", SDL_GAMEPAD_BUTTON_MISC4},
|
||||||
{"misc5", "Misc 5", SDL_GAMEPAD_BUTTON_MISC5},
|
{"misc5", "Misc 5", SDL_GAMEPAD_BUTTON_MISC5},
|
||||||
{"misc6", "Misc 6", SDL_GAMEPAD_BUTTON_MISC6},
|
{"misc6", "Misc 6", SDL_GAMEPAD_BUTTON_MISC6},
|
||||||
});
|
}};
|
||||||
|
|
||||||
inline std::string TrimToken(std::string_view token) {
|
inline std::string TrimToken(std::string_view token) {
|
||||||
const size_t begin = token.find_first_not_of(" \t");
|
const size_t begin = token.find_first_not_of(" \t");
|
||||||
@@ -99,7 +88,7 @@ inline std::string TrimToken(std::string_view token) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
inline const NativeButtonItem* FindNativeButton(std::string_view configName) {
|
inline const NativeButtonItem* FindNativeButton(std::string_view configName) {
|
||||||
const std::string name = TrimToken(configName.substr(0, configName.find('@')));
|
const std::string name = TrimToken(configName);
|
||||||
const auto it = std::find_if(kNativeButtons.begin(), kNativeButtons.end(),
|
const auto it = std::find_if(kNativeButtons.begin(), kNativeButtons.end(),
|
||||||
[&](const NativeButtonItem& item) { return name == item.configName; });
|
[&](const NativeButtonItem& item) { return name == item.configName; });
|
||||||
return it == kNativeButtons.end() ? nullptr : &*it;
|
return it == kNativeButtons.end() ? nullptr : &*it;
|
||||||
@@ -108,8 +97,8 @@ inline const NativeButtonItem* FindNativeButton(std::string_view configName) {
|
|||||||
// Falls back to the "unmapped" entry so callers always have a label to draw.
|
// Falls back to the "unmapped" entry so callers always have a label to draw.
|
||||||
inline const NativeButtonItem& NativeButtonForValue(uint32_t nativeButton) {
|
inline const NativeButtonItem& NativeButtonForValue(uint32_t nativeButton) {
|
||||||
const auto it = std::find_if(kNativeButtons.begin(), kNativeButtons.end(),
|
const auto it = std::find_if(kNativeButtons.begin(), kNativeButtons.end(),
|
||||||
[&](const NativeButtonItem& item) { return PADAxisButtonIdentity(nativeButton) == PADAxisButtonIdentity(item.nativeButton); });
|
[&](const NativeButtonItem& item) { return nativeButton == item.nativeButton; });
|
||||||
return it == kNativeButtons.end() ? *FindNativeButton("unmapped") : *it;
|
return it == kNativeButtons.end() ? kNativeButtons.front() : *it;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline const GameCubeButtonItem* FindGameCubeButton(std::string_view configKey) {
|
inline const GameCubeButtonItem* FindGameCubeButton(std::string_view configKey) {
|
||||||
|
|||||||
@@ -1,14 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
namespace MusicAttenuation {
|
|
||||||
|
|
||||||
struct MacOSAudioStatus {
|
|
||||||
bool available = false;
|
|
||||||
bool playing = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Queries output activity without capturing audio. Requires Core Audio process
|
|
||||||
// objects (macOS 14.2+); older systems return an unavailable sample.
|
|
||||||
MacOSAudioStatus QueryMacOSExternalAudio() noexcept;
|
|
||||||
|
|
||||||
} // namespace MusicAttenuation
|
|
||||||
@@ -294,7 +294,7 @@ inline double PpcLoadPairPsqFloatFastInline(uint32_t addr)
|
|||||||
return PpcLoadPairPsqFloatFromHostInline(host);
|
return PpcLoadPairPsqFloatFromHostInline(host);
|
||||||
}
|
}
|
||||||
return PpcBitCastToDoubleInline(
|
return PpcBitCastToDoubleInline(
|
||||||
PpcLoadPairPsqFloatBitsPackedInline(Memory::Read64(addr)));
|
PpcLoadPairPsqFloatBitsPackedInline(MemoryInline::Read64Slow(addr)));
|
||||||
}
|
}
|
||||||
|
|
||||||
inline double PpcLoadSinglePsqFloatFastInline(uint32_t addr)
|
inline double PpcLoadSinglePsqFloatFastInline(uint32_t addr)
|
||||||
@@ -349,7 +349,7 @@ inline void PpcStorePairPsqFloatFastInline(uint32_t addr, double value)
|
|||||||
PpcStorePairPsqFloatToHostInline(host, value);
|
PpcStorePairPsqFloatToHostInline(host, value);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Memory::Write64(
|
MemoryInline::Write64Slow(
|
||||||
addr, PpcStorePairPsqFloatBitsPackedInline(PpcBitCastToU64Inline(value)));
|
addr, PpcStorePairPsqFloatBitsPackedInline(PpcBitCastToU64Inline(value)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1385,13 +1385,37 @@ MKW_PPC_FORCE_INLINE void PPC_PsqStStackInline(uint32_t addr, double value)
|
|||||||
// Context-free PSQ entries for translated regions which own GQR state as an
|
// Context-free PSQ entries for translated regions which own GQR state as an
|
||||||
// ordinary native value. All architecturally valid quantization encodings are
|
// ordinary native value. All architecturally valid quantization encodings are
|
||||||
// handled directly; reserved encodings retain the generic helper's abort.
|
// handled directly; reserved encodings retain the generic helper's abort.
|
||||||
template <uint32_t W, bool Stack>
|
template <uint32_t W, uint32_t I, bool Stack>
|
||||||
MKW_PPC_NO_INLINE MKW_PPC_COLD double PPC_PsqLStateFallback(uint32_t gqr, uint32_t addr);
|
MKW_PPC_NO_INLINE MKW_PPC_COLD inline double PPC_PsqLStateFallback(uint32_t gqr, uint32_t addr)
|
||||||
|
{
|
||||||
extern template double PPC_PsqLStateFallback<0u, false>(uint32_t, uint32_t);
|
static_assert(W <= 1u && I < 8u);
|
||||||
extern template double PPC_PsqLStateFallback<0u, true>(uint32_t, uint32_t);
|
const uint32_t type = (gqr >> 16) & 0x7u;
|
||||||
extern template double PPC_PsqLStateFallback<1u, false>(uint32_t, uint32_t);
|
const uint32_t scale = (gqr >> 24) & 0x3Fu;
|
||||||
extern template double PPC_PsqLStateFallback<1u, true>(uint32_t, uint32_t);
|
if constexpr (W == 0u)
|
||||||
|
{
|
||||||
|
switch (type)
|
||||||
|
{
|
||||||
|
case 0u: return Stack ? PpcLoadPairPsqFloatStackInline(addr) : PpcLoadPairPsqFloatFastInline(addr);
|
||||||
|
case 4u: return Stack ? PpcLoadPairPsqIntegerStackInline<uint8_t>(addr, scale) : PpcLoadPairPsqIntegerFastInline<uint8_t>(addr, scale);
|
||||||
|
case 5u: return Stack ? PpcLoadPairPsqIntegerStackInline<uint16_t>(addr, scale) : PpcLoadPairPsqIntegerFastInline<uint16_t>(addr, scale);
|
||||||
|
case 6u: return Stack ? PpcLoadPairPsqIntegerStackInline<int8_t>(addr, scale) : PpcLoadPairPsqIntegerFastInline<int8_t>(addr, scale);
|
||||||
|
case 7u: return Stack ? PpcLoadPairPsqIntegerStackInline<int16_t>(addr, scale) : PpcLoadPairPsqIntegerFastInline<int16_t>(addr, scale);
|
||||||
|
default: std::abort();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
switch (type)
|
||||||
|
{
|
||||||
|
case 0u: return Stack ? PpcLoadSinglePsqFloatStackInline(addr) : PpcLoadSinglePsqFloatFastInline(addr);
|
||||||
|
case 4u: return Stack ? PpcLoadSinglePsqQuantizedStackInline<uint8_t>(addr, scale) : PpcLoadSinglePsqQuantizedFastInline<uint8_t>(addr, scale);
|
||||||
|
case 5u: return Stack ? PpcLoadSinglePsqQuantizedStackInline<uint16_t>(addr, scale) : PpcLoadSinglePsqQuantizedFastInline<uint16_t>(addr, scale);
|
||||||
|
case 6u: return Stack ? PpcLoadSinglePsqQuantizedStackInline<int8_t>(addr, scale) : PpcLoadSinglePsqQuantizedFastInline<int8_t>(addr, scale);
|
||||||
|
case 7u: return Stack ? PpcLoadSinglePsqQuantizedStackInline<int16_t>(addr, scale) : PpcLoadSinglePsqQuantizedFastInline<int16_t>(addr, scale);
|
||||||
|
default: std::abort();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Keep the normal explicit-state path small and directly optimizable. Exact
|
// Keep the normal explicit-state path small and directly optimizable. Exact
|
||||||
// unscaled encodings cover the SDK's common GQR setup; scaled and reserved
|
// unscaled encodings cover the SDK's common GQR setup; scaled and reserved
|
||||||
@@ -1418,17 +1442,41 @@ MKW_PPC_FORCE_INLINE double PPC_PsqLStateInline(uint32_t gqr, uint32_t addr)
|
|||||||
if constexpr (W == 0u) return Stack ? PpcLoadPairPsqIntegerStackInline<int16_t>(addr, 0u) : PpcLoadPairPsqIntegerFastInline<int16_t>(addr, 0u);
|
if constexpr (W == 0u) return Stack ? PpcLoadPairPsqIntegerStackInline<int16_t>(addr, 0u) : PpcLoadPairPsqIntegerFastInline<int16_t>(addr, 0u);
|
||||||
else return Stack ? PpcLoadSinglePsqQuantizedStackInline<int16_t>(addr, 0u) : PpcLoadSinglePsqQuantizedFastInline<int16_t>(addr, 0u);
|
else return Stack ? PpcLoadSinglePsqQuantizedStackInline<int16_t>(addr, 0u) : PpcLoadSinglePsqQuantizedFastInline<int16_t>(addr, 0u);
|
||||||
default:
|
default:
|
||||||
return PPC_PsqLStateFallback<W, Stack>(gqr, addr);
|
return PPC_PsqLStateFallback<W, I, Stack>(gqr, addr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
template <uint32_t W, bool Stack>
|
template <uint32_t W, uint32_t I, bool Stack>
|
||||||
MKW_PPC_NO_INLINE MKW_PPC_COLD void PPC_PsqStStateFallback(uint32_t gqr, uint32_t addr, double value);
|
MKW_PPC_NO_INLINE MKW_PPC_COLD inline void PPC_PsqStStateFallback(uint32_t gqr, uint32_t addr, double value)
|
||||||
|
{
|
||||||
extern template void PPC_PsqStStateFallback<0u, false>(uint32_t, uint32_t, double);
|
static_assert(W <= 1u && I < 8u);
|
||||||
extern template void PPC_PsqStStateFallback<0u, true>(uint32_t, uint32_t, double);
|
const uint32_t type = gqr & 0x7u;
|
||||||
extern template void PPC_PsqStStateFallback<1u, false>(uint32_t, uint32_t, double);
|
const uint32_t scale = (gqr >> 8) & 0x3Fu;
|
||||||
extern template void PPC_PsqStStateFallback<1u, true>(uint32_t, uint32_t, double);
|
if constexpr (W == 0u)
|
||||||
|
{
|
||||||
|
switch (type)
|
||||||
|
{
|
||||||
|
case 0u: Stack ? PpcStorePairPsqFloatStackInline(addr, value) : PpcStorePairPsqFloatFastInline(addr, value); return;
|
||||||
|
case 4u: if constexpr (Stack) PpcStorePairPsqQuantizedStackInline<uint8_t>(addr, value, scale); else PpcStorePairPsqQuantizedFastInline<uint8_t>(addr, value, scale); return;
|
||||||
|
case 5u: if constexpr (Stack) PpcStorePairPsqQuantizedStackInline<uint16_t>(addr, value, scale); else PpcStorePairPsqQuantizedFastInline<uint16_t>(addr, value, scale); return;
|
||||||
|
case 6u: if constexpr (Stack) PpcStorePairPsqQuantizedStackInline<int8_t>(addr, value, scale); else PpcStorePairPsqQuantizedFastInline<int8_t>(addr, value, scale); return;
|
||||||
|
case 7u: if constexpr (Stack) PpcStorePairPsqQuantizedStackInline<int16_t>(addr, value, scale); else PpcStorePairPsqQuantizedFastInline<int16_t>(addr, value, scale); return;
|
||||||
|
default: std::abort();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
switch (type)
|
||||||
|
{
|
||||||
|
case 0u: if constexpr (Stack) PpcStoreSinglePsqFloatStackInline(addr, value); else PpcStoreSinglePsqFloatFastInline(addr, value); return;
|
||||||
|
case 4u: if constexpr (Stack) PpcStoreSinglePsqQuantizedStackInline<uint8_t>(addr, value, scale); else PpcStoreSinglePsqQuantizedFastInline<uint8_t>(addr, value, scale); return;
|
||||||
|
case 5u: if constexpr (Stack) PpcStoreSinglePsqQuantizedStackInline<uint16_t>(addr, value, scale); else PpcStoreSinglePsqQuantizedFastInline<uint16_t>(addr, value, scale); return;
|
||||||
|
case 6u: if constexpr (Stack) PpcStoreSinglePsqQuantizedStackInline<int8_t>(addr, value, scale); else PpcStoreSinglePsqQuantizedFastInline<int8_t>(addr, value, scale); return;
|
||||||
|
case 7u: if constexpr (Stack) PpcStoreSinglePsqQuantizedStackInline<int16_t>(addr, value, scale); else PpcStoreSinglePsqQuantizedFastInline<int16_t>(addr, value, scale); return;
|
||||||
|
default: std::abort();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
template <uint32_t W, uint32_t I, bool Stack>
|
template <uint32_t W, uint32_t I, bool Stack>
|
||||||
MKW_PPC_FORCE_INLINE void PPC_PsqStStateInline(uint32_t gqr, uint32_t addr, double value)
|
MKW_PPC_FORCE_INLINE void PPC_PsqStStateInline(uint32_t gqr, uint32_t addr, double value)
|
||||||
@@ -1457,18 +1505,11 @@ MKW_PPC_FORCE_INLINE void PPC_PsqStStateInline(uint32_t gqr, uint32_t addr, doub
|
|||||||
else { if constexpr (Stack) PpcStoreSinglePsqQuantizedStackInline<int16_t>(addr, value, 0u); else PpcStoreSinglePsqQuantizedFastInline<int16_t>(addr, value, 0u); }
|
else { if constexpr (Stack) PpcStoreSinglePsqQuantizedStackInline<int16_t>(addr, value, 0u); else PpcStoreSinglePsqQuantizedFastInline<int16_t>(addr, value, 0u); }
|
||||||
return;
|
return;
|
||||||
default:
|
default:
|
||||||
PPC_PsqStStateFallback<W, Stack>(gqr, addr, value);
|
PPC_PsqStStateFallback<W, I, Stack>(gqr, addr, value);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
template <uint32_t W>
|
|
||||||
MKW_PPC_NO_INLINE MKW_PPC_COLD double PPC_PsqLResolvedStateFallback(
|
|
||||||
uint32_t gqr, uint8_t* resolvedHost, uint32_t offset, uint32_t addr);
|
|
||||||
|
|
||||||
extern template double PPC_PsqLResolvedStateFallback<0u>(uint32_t, uint8_t*, uint32_t, uint32_t);
|
|
||||||
extern template double PPC_PsqLResolvedStateFallback<1u>(uint32_t, uint8_t*, uint32_t, uint32_t);
|
|
||||||
|
|
||||||
template <uint32_t W, uint32_t I>
|
template <uint32_t W, uint32_t I>
|
||||||
MKW_PPC_FORCE_INLINE double PPC_PsqLResolvedStateInline(
|
MKW_PPC_FORCE_INLINE double PPC_PsqLResolvedStateInline(
|
||||||
uint32_t gqr, uint8_t* resolvedHost, uint32_t offset, uint32_t addr)
|
uint32_t gqr, uint8_t* resolvedHost, uint32_t offset, uint32_t addr)
|
||||||
@@ -1476,41 +1517,33 @@ MKW_PPC_FORCE_INLINE double PPC_PsqLResolvedStateInline(
|
|||||||
static_assert(W <= 1u && I < 8u);
|
static_assert(W <= 1u && I < 8u);
|
||||||
if (!resolvedHost) [[unlikely]] return PPC_PsqLStateInline<W, I, false>(gqr, addr);
|
if (!resolvedHost) [[unlikely]] return PPC_PsqLStateInline<W, I, false>(gqr, addr);
|
||||||
const uint32_t type = (gqr >> 16) & 0x7u;
|
const uint32_t type = (gqr >> 16) & 0x7u;
|
||||||
if (type == 0u) {
|
const uint32_t scale = (gqr >> 24) & 0x3Fu;
|
||||||
if constexpr (W == 0u) return PpcLoadPairPsqFloatResolvedInline(resolvedHost, offset, addr);
|
|
||||||
else return PpcLoadSinglePsqFloatResolvedInline(resolvedHost, offset, addr);
|
|
||||||
}
|
|
||||||
if constexpr (W == 0u)
|
if constexpr (W == 0u)
|
||||||
{
|
{
|
||||||
switch (gqr & 0x3F070000u)
|
switch (type)
|
||||||
{
|
{
|
||||||
case 0x00040000u: return PpcLoadPairPsqIntegerResolvedInline<uint8_t>(resolvedHost, offset, addr, 0u);
|
case 0u: return PpcLoadPairPsqFloatResolvedInline(resolvedHost, offset, addr);
|
||||||
case 0x00050000u: return PpcLoadPairPsqIntegerResolvedInline<uint16_t>(resolvedHost, offset, addr, 0u);
|
case 4u: return PpcLoadPairPsqIntegerResolvedInline<uint8_t>(resolvedHost, offset, addr, scale);
|
||||||
case 0x00060000u: return PpcLoadPairPsqIntegerResolvedInline<int8_t>(resolvedHost, offset, addr, 0u);
|
case 5u: return PpcLoadPairPsqIntegerResolvedInline<uint16_t>(resolvedHost, offset, addr, scale);
|
||||||
case 0x00070000u: return PpcLoadPairPsqIntegerResolvedInline<int16_t>(resolvedHost, offset, addr, 0u);
|
case 6u: return PpcLoadPairPsqIntegerResolvedInline<int8_t>(resolvedHost, offset, addr, scale);
|
||||||
default: return PPC_PsqLResolvedStateFallback<W>(gqr, resolvedHost, offset, addr);
|
case 7u: return PpcLoadPairPsqIntegerResolvedInline<int16_t>(resolvedHost, offset, addr, scale);
|
||||||
|
default: std::abort();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
switch (gqr & 0x3F070000u)
|
switch (type)
|
||||||
{
|
{
|
||||||
case 0x00040000u: return PpcLoadSinglePsqQuantizedResolvedInline<uint8_t>(resolvedHost, offset, addr, 0u);
|
case 0u: return PpcLoadSinglePsqFloatResolvedInline(resolvedHost, offset, addr);
|
||||||
case 0x00050000u: return PpcLoadSinglePsqQuantizedResolvedInline<uint16_t>(resolvedHost, offset, addr, 0u);
|
case 4u: return PpcLoadSinglePsqQuantizedResolvedInline<uint8_t>(resolvedHost, offset, addr, scale);
|
||||||
case 0x00060000u: return PpcLoadSinglePsqQuantizedResolvedInline<int8_t>(resolvedHost, offset, addr, 0u);
|
case 5u: return PpcLoadSinglePsqQuantizedResolvedInline<uint16_t>(resolvedHost, offset, addr, scale);
|
||||||
case 0x00070000u: return PpcLoadSinglePsqQuantizedResolvedInline<int16_t>(resolvedHost, offset, addr, 0u);
|
case 6u: return PpcLoadSinglePsqQuantizedResolvedInline<int8_t>(resolvedHost, offset, addr, scale);
|
||||||
default: return PPC_PsqLResolvedStateFallback<W>(gqr, resolvedHost, offset, addr);
|
case 7u: return PpcLoadSinglePsqQuantizedResolvedInline<int16_t>(resolvedHost, offset, addr, scale);
|
||||||
|
default: std::abort();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
template <uint32_t W>
|
|
||||||
MKW_PPC_NO_INLINE MKW_PPC_COLD void PPC_PsqStResolvedStateFallback(
|
|
||||||
uint32_t gqr, uint8_t* resolvedHost, uint32_t offset, uint32_t addr, double value);
|
|
||||||
|
|
||||||
extern template void PPC_PsqStResolvedStateFallback<0u>(uint32_t, uint8_t*, uint32_t, uint32_t, double);
|
|
||||||
extern template void PPC_PsqStResolvedStateFallback<1u>(uint32_t, uint8_t*, uint32_t, uint32_t, double);
|
|
||||||
|
|
||||||
template <uint32_t W, uint32_t I>
|
template <uint32_t W, uint32_t I>
|
||||||
MKW_PPC_FORCE_INLINE void PPC_PsqStResolvedStateInline(
|
MKW_PPC_FORCE_INLINE void PPC_PsqStResolvedStateInline(
|
||||||
uint32_t gqr, uint8_t* resolvedHost, uint32_t offset, uint32_t addr, double value)
|
uint32_t gqr, uint8_t* resolvedHost, uint32_t offset, uint32_t addr, double value)
|
||||||
@@ -1522,32 +1555,29 @@ MKW_PPC_FORCE_INLINE void PPC_PsqStResolvedStateInline(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const uint32_t type = gqr & 0x7u;
|
const uint32_t type = gqr & 0x7u;
|
||||||
if (type == 0u) {
|
const uint32_t scale = (gqr >> 8) & 0x3Fu;
|
||||||
if constexpr (W == 0u) PpcStorePairPsqFloatResolvedInline(resolvedHost, offset, addr, value);
|
|
||||||
else PpcStoreSinglePsqFloatResolvedInline(resolvedHost, offset, addr, value);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if constexpr (W == 0u)
|
if constexpr (W == 0u)
|
||||||
{
|
{
|
||||||
switch (gqr & 0x3F07u)
|
switch (type)
|
||||||
{
|
{
|
||||||
case 0x3D04u: PpcStorePairPsqU8Scale61ResolvedInline(resolvedHost, offset, addr, value); return;
|
case 0u: PpcStorePairPsqFloatResolvedInline(resolvedHost, offset, addr, value); return;
|
||||||
case 4u: PpcStorePairPsqQuantizedResolvedInline<uint8_t>(resolvedHost, offset, addr, value, 0u); return;
|
case 4u: PpcStorePairPsqQuantizedResolvedInline<uint8_t>(resolvedHost, offset, addr, value, scale); return;
|
||||||
case 5u: PpcStorePairPsqQuantizedResolvedInline<uint16_t>(resolvedHost, offset, addr, value, 0u); return;
|
case 5u: PpcStorePairPsqQuantizedResolvedInline<uint16_t>(resolvedHost, offset, addr, value, scale); return;
|
||||||
case 6u: PpcStorePairPsqQuantizedResolvedInline<int8_t>(resolvedHost, offset, addr, value, 0u); return;
|
case 6u: PpcStorePairPsqQuantizedResolvedInline<int8_t>(resolvedHost, offset, addr, value, scale); return;
|
||||||
case 7u: PpcStorePairPsqQuantizedResolvedInline<int16_t>(resolvedHost, offset, addr, value, 0u); return;
|
case 7u: PpcStorePairPsqQuantizedResolvedInline<int16_t>(resolvedHost, offset, addr, value, scale); return;
|
||||||
default: PPC_PsqStResolvedStateFallback<W>(gqr, resolvedHost, offset, addr, value); return;
|
default: std::abort();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
switch (gqr & 0x3F07u)
|
switch (type)
|
||||||
{
|
{
|
||||||
case 4u: PpcStoreSinglePsqQuantizedResolvedInline<uint8_t>(resolvedHost, offset, addr, value, 0u); return;
|
case 0u: PpcStoreSinglePsqFloatResolvedInline(resolvedHost, offset, addr, value); return;
|
||||||
case 5u: PpcStoreSinglePsqQuantizedResolvedInline<uint16_t>(resolvedHost, offset, addr, value, 0u); return;
|
case 4u: PpcStoreSinglePsqQuantizedResolvedInline<uint8_t>(resolvedHost, offset, addr, value, scale); return;
|
||||||
case 6u: PpcStoreSinglePsqQuantizedResolvedInline<int8_t>(resolvedHost, offset, addr, value, 0u); return;
|
case 5u: PpcStoreSinglePsqQuantizedResolvedInline<uint16_t>(resolvedHost, offset, addr, value, scale); return;
|
||||||
case 7u: PpcStoreSinglePsqQuantizedResolvedInline<int16_t>(resolvedHost, offset, addr, value, 0u); return;
|
case 6u: PpcStoreSinglePsqQuantizedResolvedInline<int8_t>(resolvedHost, offset, addr, value, scale); return;
|
||||||
default: PPC_PsqStResolvedStateFallback<W>(gqr, resolvedHost, offset, addr, value); return;
|
case 7u: PpcStoreSinglePsqQuantizedResolvedInline<int16_t>(resolvedHost, offset, addr, value, scale); return;
|
||||||
|
default: std::abort();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user