Compare commits
1 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
bac61d012c |
42
.github/workflows/build.yml
vendored
42
.github/workflows/build.yml
vendored
@@ -12,13 +12,13 @@ jobs:
|
||||
if: ${{ !github.head_ref }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Pack
|
||||
run: ./.ci/source.sh
|
||||
- name: Upload
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: source
|
||||
path: artifacts/
|
||||
@@ -37,11 +37,11 @@ jobs:
|
||||
OS: linux
|
||||
TARGET: ${{ matrix.target }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Set up cache
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ${{ env.CCACHE_DIR }}
|
||||
key: ${{ runner.os }}-${{ matrix.target }}-${{ github.sha }}
|
||||
@@ -53,7 +53,7 @@ jobs:
|
||||
run: ./.ci/pack.sh
|
||||
if: ${{ matrix.target == 'appimage' }}
|
||||
- name: Upload
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v3
|
||||
if: ${{ matrix.target == 'appimage' }}
|
||||
with:
|
||||
name: ${{ env.OS }}-${{ env.TARGET }}
|
||||
@@ -70,11 +70,11 @@ jobs:
|
||||
OS: macos
|
||||
TARGET: ${{ matrix.target }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Set up cache
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ${{ env.CCACHE_DIR }}
|
||||
key: ${{ runner.os }}-${{ matrix.target }}-${{ github.sha }}
|
||||
@@ -87,7 +87,7 @@ jobs:
|
||||
- name: Prepare outputs for caching
|
||||
run: mv build/bundle $OS-$TARGET
|
||||
- name: Cache outputs for universal build
|
||||
uses: actions/cache/save@v4
|
||||
uses: actions/cache/save@v3
|
||||
with:
|
||||
path: ${{ env.OS }}-${{ env.TARGET }}
|
||||
key: ${{ runner.os }}-${{ matrix.target }}-${{ github.sha }}-${{ github.run_id }}-${{ github.run_attempt }}
|
||||
@@ -98,15 +98,15 @@ jobs:
|
||||
OS: macos
|
||||
TARGET: universal
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v3
|
||||
- name: Download x86_64 build from cache
|
||||
uses: actions/cache/restore@v4
|
||||
uses: actions/cache/restore@v3
|
||||
with:
|
||||
path: ${{ env.OS }}-x86_64
|
||||
key: ${{ runner.os }}-x86_64-${{ github.sha }}-${{ github.run_id }}-${{ github.run_attempt }}
|
||||
fail-on-cache-miss: true
|
||||
- name: Download ARM64 build from cache
|
||||
uses: actions/cache/restore@v4
|
||||
uses: actions/cache/restore@v3
|
||||
with:
|
||||
path: ${{ env.OS }}-arm64
|
||||
key: ${{ runner.os }}-arm64-${{ github.sha }}-${{ github.run_id }}-${{ github.run_attempt }}
|
||||
@@ -118,7 +118,7 @@ jobs:
|
||||
- name: Pack
|
||||
run: ./.ci/pack.sh
|
||||
- name: Upload
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: ${{ env.OS }}-${{ env.TARGET }}
|
||||
path: artifacts/
|
||||
@@ -137,11 +137,11 @@ jobs:
|
||||
OS: windows
|
||||
TARGET: ${{ matrix.target }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Set up cache
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ${{ env.CCACHE_DIR }}
|
||||
key: ${{ runner.os }}-${{ matrix.target }}-${{ github.sha }}
|
||||
@@ -179,7 +179,7 @@ jobs:
|
||||
- name: Pack
|
||||
run: ./.ci/pack.sh
|
||||
- name: Upload
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: ${{ env.OS }}-${{ env.TARGET }}
|
||||
path: artifacts/
|
||||
@@ -192,11 +192,11 @@ jobs:
|
||||
OS: android
|
||||
TARGET: universal
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Set up cache
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
~/.gradle/caches
|
||||
@@ -228,7 +228,7 @@ jobs:
|
||||
env:
|
||||
UNPACKED: 1
|
||||
- name: Upload
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: ${{ env.OS }}-${{ env.TARGET }}
|
||||
path: src/android/app/artifacts/
|
||||
@@ -242,11 +242,11 @@ jobs:
|
||||
OS: ios
|
||||
TARGET: arm64
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Set up cache
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ${{ env.CCACHE_DIR }}
|
||||
key: ${{ runner.os }}-ios-${{ github.sha }}
|
||||
@@ -261,7 +261,7 @@ jobs:
|
||||
needs: [windows, linux, macos-universal, android, source]
|
||||
if: ${{ startsWith(github.ref, 'refs/tags/') }}
|
||||
steps:
|
||||
- uses: actions/download-artifact@v4
|
||||
- uses: actions/download-artifact@v3
|
||||
- name: Create release
|
||||
uses: actions/create-release@v1
|
||||
env:
|
||||
|
2
.github/workflows/format.yml
vendored
2
.github/workflows/format.yml
vendored
@@ -13,7 +13,7 @@ jobs:
|
||||
image: citraemu/build-environments:linux-fresh
|
||||
options: -u 1001
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Build
|
||||
|
16
.github/workflows/publish.yml
vendored
16
.github/workflows/publish.yml
vendored
@@ -20,11 +20,11 @@ jobs:
|
||||
if: ${{ github.event.inputs.nightly != 'false' && github.repository == 'citra-emu/citra' }}
|
||||
steps:
|
||||
# this checkout is required to make sure the GitHub Actions scripts are available
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v3
|
||||
name: Pre-checkout
|
||||
with:
|
||||
submodules: false
|
||||
- uses: actions/github-script@v7
|
||||
- uses: actions/github-script@v6
|
||||
id: check-changes
|
||||
name: 'Check for new changes'
|
||||
env:
|
||||
@@ -38,7 +38,7 @@ jobs:
|
||||
return checkBaseChanges(github, context);
|
||||
- run: npm install execa@5
|
||||
if: ${{ steps.check-changes.outputs.result == 'true' }}
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v3
|
||||
name: Checkout
|
||||
if: ${{ steps.check-changes.outputs.result == 'true' }}
|
||||
with:
|
||||
@@ -46,7 +46,7 @@ jobs:
|
||||
fetch-depth: 0
|
||||
submodules: true
|
||||
token: ${{ secrets.ALT_GITHUB_TOKEN }}
|
||||
- uses: actions/github-script@v7
|
||||
- uses: actions/github-script@v6
|
||||
name: 'Update and tag new commits'
|
||||
if: ${{ steps.check-changes.outputs.result == 'true' }}
|
||||
env:
|
||||
@@ -62,11 +62,11 @@ jobs:
|
||||
if: ${{ github.event.inputs.canary != 'false' && github.repository == 'citra-emu/citra' }}
|
||||
steps:
|
||||
# this checkout is required to make sure the GitHub Actions scripts are available
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v3
|
||||
name: Pre-checkout
|
||||
with:
|
||||
submodules: false
|
||||
- uses: actions/github-script@v7
|
||||
- uses: actions/github-script@v6
|
||||
id: check-changes
|
||||
name: 'Check for new changes'
|
||||
env:
|
||||
@@ -79,7 +79,7 @@ jobs:
|
||||
return checkCanaryChanges(github, context);
|
||||
- run: npm install execa@5
|
||||
if: ${{ steps.check-changes.outputs.result == 'true' }}
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v3
|
||||
name: Checkout
|
||||
if: ${{ steps.check-changes.outputs.result == 'true' }}
|
||||
with:
|
||||
@@ -87,7 +87,7 @@ jobs:
|
||||
fetch-depth: 0
|
||||
submodules: true
|
||||
token: ${{ secrets.ALT_GITHUB_TOKEN }}
|
||||
- uses: actions/github-script@v7
|
||||
- uses: actions/github-script@v6
|
||||
name: 'Check and merge canary changes'
|
||||
if: ${{ steps.check-changes.outputs.result == 'true' }}
|
||||
env:
|
||||
|
2
.github/workflows/transifex.yml
vendored
2
.github/workflows/transifex.yml
vendored
@@ -10,7 +10,7 @@ jobs:
|
||||
container: citraemu/build-environments:linux-fresh
|
||||
if: ${{ github.repository == 'citra-emu/citra' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: recursive
|
||||
fetch-depth: 0
|
||||
|
17
externals/CMakeLists.txt
vendored
17
externals/CMakeLists.txt
vendored
@@ -294,20 +294,11 @@ endif()
|
||||
add_library(httplib INTERFACE)
|
||||
if(USE_SYSTEM_CPP_HTTPLIB)
|
||||
find_package(CppHttp 0.14.1)
|
||||
# Detect if system cpphttplib is a shared library
|
||||
# this breaks building as Citra relies on functions that are moved
|
||||
# into the shared object.
|
||||
get_target_property(HTTP_LIBS httplib::httplib INTERFACE_LINK_LIBRARIES)
|
||||
if(HTTP_LIBS)
|
||||
message(WARNING "Shared cpp-http (${HTTP_LIBS}) not supported. Falling back to bundled...")
|
||||
target_include_directories(httplib SYSTEM INTERFACE ./httplib)
|
||||
if(CppHttp_FOUND)
|
||||
target_link_libraries(httplib INTERFACE httplib::httplib)
|
||||
else()
|
||||
if(CppHttp_FOUND)
|
||||
target_link_libraries(httplib INTERFACE httplib::httplib)
|
||||
else()
|
||||
message(STATUS "Cpp-httplib not found or not suitable version! Falling back to bundled...")
|
||||
target_include_directories(httplib SYSTEM INTERFACE ./httplib)
|
||||
endif()
|
||||
message(STATUS "Cpp-httplib not found or not suitable version! Falling back to bundled...")
|
||||
target_include_directories(httplib SYSTEM INTERFACE ./httplib)
|
||||
endif()
|
||||
else()
|
||||
target_include_directories(httplib SYSTEM INTERFACE ./httplib)
|
||||
|
2
externals/vulkan-headers
vendored
2
externals/vulkan-headers
vendored
Submodule externals/vulkan-headers updated: 5a5c9a6434...217e93c664
@@ -10,7 +10,7 @@ plugins {
|
||||
id("org.jetbrains.kotlin.android")
|
||||
id("de.undercouch.download") version "5.5.0"
|
||||
id("kotlin-parcelize")
|
||||
kotlin("plugin.serialization") version "1.9.22"
|
||||
kotlin("plugin.serialization") version "1.8.21"
|
||||
id("androidx.navigation.safeargs.kotlin")
|
||||
}
|
||||
|
||||
@@ -173,23 +173,23 @@ android {
|
||||
|
||||
dependencies {
|
||||
implementation("androidx.recyclerview:recyclerview:1.3.2")
|
||||
implementation("androidx.activity:activity-ktx:1.8.2")
|
||||
implementation("androidx.activity:activity-ktx:1.8.0")
|
||||
implementation("androidx.fragment:fragment-ktx:1.6.2")
|
||||
implementation("androidx.appcompat:appcompat:1.6.1")
|
||||
implementation("androidx.documentfile:documentfile:1.0.1")
|
||||
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.7.0")
|
||||
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1")
|
||||
implementation("androidx.slidingpanelayout:slidingpanelayout:1.2.0")
|
||||
implementation("com.google.android.material:material:1.9.0")
|
||||
implementation("androidx.core:core-splashscreen:1.0.1")
|
||||
implementation("androidx.work:work-runtime:2.9.0")
|
||||
implementation("androidx.work:work-runtime:2.8.1")
|
||||
implementation("org.ini4j:ini4j:0.5.4")
|
||||
implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0")
|
||||
implementation("androidx.navigation:navigation-fragment-ktx:2.7.6")
|
||||
implementation("androidx.navigation:navigation-ui-ktx:2.7.6")
|
||||
implementation("androidx.navigation:navigation-fragment-ktx:2.7.5")
|
||||
implementation("androidx.navigation:navigation-ui-ktx:2.7.5")
|
||||
implementation("info.debatty:java-string-similarity:2.0.0")
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.2")
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0")
|
||||
implementation("androidx.preference:preference-ktx:1.2.1")
|
||||
implementation("io.coil-kt:coil:2.5.0")
|
||||
implementation("io.coil-kt:coil:2.2.2")
|
||||
}
|
||||
|
||||
// Download Vulkan Validation Layers from the KhronosGroup GitHub.
|
||||
|
@@ -42,9 +42,6 @@
|
||||
android:banner="@mipmap/ic_launcher"
|
||||
android:requestLegacyExternalStorage="true">
|
||||
|
||||
<meta-data android:name="android.game_mode_config"
|
||||
android:resource="@xml/game_mode_config" />
|
||||
|
||||
<activity
|
||||
android:name="org.citra.citra_emu.ui.main.MainActivity"
|
||||
android:theme="@style/Theme.Citra.Splash.Main"
|
||||
|
@@ -9,13 +9,10 @@ import android.app.Application
|
||||
import android.app.NotificationChannel
|
||||
import android.app.NotificationManager
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import org.citra.citra_emu.utils.DirectoryInitialization
|
||||
import org.citra.citra_emu.utils.DocumentsTree
|
||||
import org.citra.citra_emu.utils.GpuDriverHelper
|
||||
import org.citra.citra_emu.utils.PermissionsHandler
|
||||
import org.citra.citra_emu.utils.Log
|
||||
import org.citra.citra_emu.utils.MemoryUtil
|
||||
|
||||
class CitraApplication : Application() {
|
||||
private fun createNotificationChannel() {
|
||||
@@ -56,20 +53,9 @@ class CitraApplication : Application() {
|
||||
}
|
||||
|
||||
NativeLibrary.logDeviceInfo()
|
||||
logDeviceInfo()
|
||||
createNotificationChannel()
|
||||
}
|
||||
|
||||
fun logDeviceInfo() {
|
||||
Log.info("Device Manufacturer - ${Build.MANUFACTURER}")
|
||||
Log.info("Device Model - ${Build.MODEL}")
|
||||
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.R) {
|
||||
Log.info("SoC Manufacturer - ${Build.SOC_MANUFACTURER}")
|
||||
Log.info("SoC Model - ${Build.SOC_MODEL}")
|
||||
}
|
||||
Log.info("Total System Memory - ${MemoryUtil.getDeviceRAM()}")
|
||||
}
|
||||
|
||||
companion object {
|
||||
private var application: CitraApplication? = null
|
||||
|
||||
|
@@ -413,12 +413,12 @@ object NativeLibrary {
|
||||
}
|
||||
|
||||
fun setEmulationActivity(emulationActivity: EmulationActivity?) {
|
||||
Log.debug("[NativeLibrary] Registering EmulationActivity.")
|
||||
Log.verbose("[NativeLibrary] Registering EmulationActivity.")
|
||||
sEmulationActivity = WeakReference(emulationActivity)
|
||||
}
|
||||
|
||||
fun clearEmulationActivity() {
|
||||
Log.debug("[NativeLibrary] Unregistering EmulationActivity.")
|
||||
Log.verbose("[NativeLibrary] Unregistering EmulationActivity.")
|
||||
sEmulationActivity.clear()
|
||||
}
|
||||
|
||||
|
@@ -94,14 +94,14 @@ object DirectoryInitialization {
|
||||
val dataPath = PermissionsHandler.citraDirectory
|
||||
if (dataPath.toString().isNotEmpty()) {
|
||||
userPath = dataPath.toString()
|
||||
android.util.Log.d("[Citra Frontend]", "[DirectoryInitialization] User Dir: $userPath")
|
||||
Log.debug("[DirectoryInitialization] User Dir: $userPath")
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private fun copyAsset(asset: String, output: File, overwrite: Boolean, context: Context) {
|
||||
Log.debug("[DirectoryInitialization] Copying File $asset to $output")
|
||||
Log.verbose("[DirectoryInitialization] Copying File $asset to $output")
|
||||
try {
|
||||
if (!output.exists() || overwrite) {
|
||||
val inputStream = context.assets.open(asset)
|
||||
@@ -121,7 +121,7 @@ object DirectoryInitialization {
|
||||
overwrite: Boolean,
|
||||
context: Context
|
||||
) {
|
||||
Log.debug("[DirectoryInitialization] Copying Folder $assetFolder to $outputFolder")
|
||||
Log.verbose("[DirectoryInitialization] Copying Folder $assetFolder to $outputFolder")
|
||||
try {
|
||||
var createdFolder = false
|
||||
for (file in context.assets.list(assetFolder)!!) {
|
||||
|
@@ -4,17 +4,34 @@
|
||||
|
||||
package org.citra.citra_emu.utils
|
||||
|
||||
import android.util.Log
|
||||
import org.citra.citra_emu.BuildConfig
|
||||
|
||||
/**
|
||||
* Contains methods that call through to [android.util.Log], but
|
||||
* with the same TAG automatically provided. Also no-ops VERBOSE and DEBUG log
|
||||
* levels in release builds.
|
||||
*/
|
||||
object Log {
|
||||
// Tracks whether we should share the old log or the current log
|
||||
var gameLaunched = false
|
||||
private const val TAG = "Citra Frontend"
|
||||
|
||||
external fun debug(message: String)
|
||||
fun verbose(message: String?) {
|
||||
if (BuildConfig.DEBUG) {
|
||||
Log.v(TAG, message!!)
|
||||
}
|
||||
}
|
||||
|
||||
external fun warning(message: String)
|
||||
fun debug(message: String?) {
|
||||
if (BuildConfig.DEBUG) {
|
||||
Log.d(TAG, message!!)
|
||||
}
|
||||
}
|
||||
|
||||
external fun info(message: String)
|
||||
fun info(message: String?) = Log.i(TAG, message!!)
|
||||
|
||||
external fun error(message: String)
|
||||
fun warning(message: String?) = Log.w(TAG, message!!)
|
||||
|
||||
external fun critical(message: String)
|
||||
fun error(message: String?) = Log.e(TAG, message!!)
|
||||
}
|
||||
|
@@ -1,108 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
package org.citra.citra_emu.utils
|
||||
|
||||
import android.app.ActivityManager
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import org.citra.citra_emu.CitraApplication
|
||||
import org.citra.citra_emu.R
|
||||
import java.util.Locale
|
||||
import kotlin.math.ceil
|
||||
|
||||
object MemoryUtil {
|
||||
private val context get() = CitraApplication.appContext
|
||||
|
||||
private val Float.hundredths: String
|
||||
get() = String.format(Locale.ROOT, "%.2f", this)
|
||||
|
||||
const val Kb: Float = 1024F
|
||||
const val Mb = Kb * 1024
|
||||
const val Gb = Mb * 1024
|
||||
const val Tb = Gb * 1024
|
||||
const val Pb = Tb * 1024
|
||||
const val Eb = Pb * 1024
|
||||
|
||||
fun bytesToSizeUnit(size: Float, roundUp: Boolean = false): String =
|
||||
when {
|
||||
size < Kb -> {
|
||||
context.getString(
|
||||
R.string.memory_formatted,
|
||||
size.hundredths,
|
||||
context.getString(R.string.memory_byte_shorthand)
|
||||
)
|
||||
}
|
||||
size < Mb -> {
|
||||
context.getString(
|
||||
R.string.memory_formatted,
|
||||
if (roundUp) ceil(size / Kb) else (size / Kb).hundredths,
|
||||
context.getString(R.string.memory_kilobyte)
|
||||
)
|
||||
}
|
||||
size < Gb -> {
|
||||
context.getString(
|
||||
R.string.memory_formatted,
|
||||
if (roundUp) ceil(size / Mb) else (size / Mb).hundredths,
|
||||
context.getString(R.string.memory_megabyte)
|
||||
)
|
||||
}
|
||||
size < Tb -> {
|
||||
context.getString(
|
||||
R.string.memory_formatted,
|
||||
if (roundUp) ceil(size / Gb) else (size / Gb).hundredths,
|
||||
context.getString(R.string.memory_gigabyte)
|
||||
)
|
||||
}
|
||||
size < Pb -> {
|
||||
context.getString(
|
||||
R.string.memory_formatted,
|
||||
if (roundUp) ceil(size / Tb) else (size / Tb).hundredths,
|
||||
context.getString(R.string.memory_terabyte)
|
||||
)
|
||||
}
|
||||
size < Eb -> {
|
||||
context.getString(
|
||||
R.string.memory_formatted,
|
||||
if (roundUp) ceil(size / Pb) else (size / Pb).hundredths,
|
||||
context.getString(R.string.memory_petabyte)
|
||||
)
|
||||
}
|
||||
else -> {
|
||||
context.getString(
|
||||
R.string.memory_formatted,
|
||||
if (roundUp) ceil(size / Eb) else (size / Eb).hundredths,
|
||||
context.getString(R.string.memory_exabyte)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
val totalMemory: Float
|
||||
get() {
|
||||
val memInfo = ActivityManager.MemoryInfo()
|
||||
with(context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager) {
|
||||
getMemoryInfo(memInfo)
|
||||
}
|
||||
|
||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
||||
memInfo.advertisedMem.toFloat()
|
||||
} else {
|
||||
memInfo.totalMem.toFloat()
|
||||
}
|
||||
}
|
||||
|
||||
fun isLessThan(minimum: Int, size: Float): Boolean =
|
||||
when (size) {
|
||||
Kb -> totalMemory < Mb && totalMemory < minimum
|
||||
Mb -> totalMemory < Gb && (totalMemory / Mb) < minimum
|
||||
Gb -> totalMemory < Tb && (totalMemory / Gb) < minimum
|
||||
Tb -> totalMemory < Pb && (totalMemory / Tb) < minimum
|
||||
Pb -> totalMemory < Eb && (totalMemory / Pb) < minimum
|
||||
Eb -> totalMemory / Eb < minimum
|
||||
else -> totalMemory < Kb && totalMemory < minimum
|
||||
}
|
||||
|
||||
// Devices are unlikely to have 0.5GB increments of memory so we'll just round up to account for
|
||||
// the potential error created by memInfo.totalMem
|
||||
fun getDeviceRAM(): String = bytesToSizeUnit(totalMemory, true)
|
||||
}
|
@@ -28,7 +28,6 @@ add_library(citra-android SHARED
|
||||
ndk_motion.cpp
|
||||
ndk_motion.h
|
||||
system_save_game.cpp
|
||||
native_log.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(citra-android PRIVATE audio_core citra_common citra_core input_common network)
|
||||
|
@@ -1,30 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <common/logging/log.h>
|
||||
#include <jni.h>
|
||||
#include "android_common/android_common.h"
|
||||
|
||||
extern "C" {
|
||||
|
||||
void Java_org_citra_citra_1emu_utils_Log_debug(JNIEnv* env, jobject obj, jstring jmessage) {
|
||||
LOG_DEBUG(Frontend, "{}", GetJString(env, jmessage));
|
||||
}
|
||||
|
||||
void Java_org_citra_citra_1emu_utils_Log_warning(JNIEnv* env, jobject obj, jstring jmessage) {
|
||||
LOG_WARNING(Frontend, "{}", GetJString(env, jmessage));
|
||||
}
|
||||
|
||||
void Java_org_citra_citra_1emu_utils_Log_info(JNIEnv* env, jobject obj, jstring jmessage) {
|
||||
LOG_INFO(Frontend, "{}", GetJString(env, jmessage));
|
||||
}
|
||||
|
||||
void Java_org_citra_citra_1emu_utils_Log_error(JNIEnv* env, jobject obj, jstring jmessage) {
|
||||
LOG_ERROR(Frontend, "{}", GetJString(env, jmessage));
|
||||
}
|
||||
|
||||
void Java_org_citra_citra_1emu_utils_Log_critical(JNIEnv* env, jobject obj, jstring jmessage) {
|
||||
LOG_CRITICAL(Frontend, "{}", GetJString(env, jmessage));
|
||||
}
|
||||
|
||||
} // extern "C"
|
@@ -442,17 +442,6 @@
|
||||
<string name="cia_install_error_encrypted">\"%s\" must be decrypted before being used with Citra.\n A real 3DS is required</string>
|
||||
<string name="cia_install_error_unknown">An unknown error occurred while installing \"%s\".\n Please see the log for more details</string>
|
||||
|
||||
<!-- Memory Sizes -->
|
||||
<string name="memory_formatted">%1$s %2$s</string>
|
||||
<string name="memory_byte">Byte</string>
|
||||
<string name="memory_byte_shorthand">B</string>
|
||||
<string name="memory_kilobyte">KB</string>
|
||||
<string name="memory_megabyte">MB</string>
|
||||
<string name="memory_gigabyte">GB</string>
|
||||
<string name="memory_terabyte">TB</string>
|
||||
<string name="memory_petabyte">PB</string>
|
||||
<string name="memory_exabyte">EB</string>
|
||||
|
||||
<!-- Theme Modes -->
|
||||
<string name="change_theme_mode">Change Theme Mode</string>
|
||||
<string name="theme_mode_follow_system">Follow System</string>
|
||||
|
@@ -1,7 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<game-mode-config
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:supportsBatteryGameMode="true"
|
||||
android:supportsPerformanceGameMode="true"
|
||||
android:allowGameDownscaling="false"
|
||||
android:allowGameFpsOverride="false"/>
|
@@ -4,10 +4,10 @@
|
||||
|
||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
plugins {
|
||||
id("com.android.application") version "8.2.1" apply false
|
||||
id("com.android.library") version "8.2.1" apply false
|
||||
id("org.jetbrains.kotlin.android") version "1.9.22" apply false
|
||||
id("org.jetbrains.kotlin.plugin.serialization") version "1.9.22"
|
||||
id("com.android.application") version "8.1.2" apply false
|
||||
id("com.android.library") version "8.1.2" apply false
|
||||
id("org.jetbrains.kotlin.android") version "1.8.21" apply false
|
||||
id("org.jetbrains.kotlin.plugin.serialization") version "1.8.21"
|
||||
}
|
||||
|
||||
tasks.register("clean").configure {
|
||||
@@ -19,6 +19,6 @@ buildscript {
|
||||
google()
|
||||
}
|
||||
dependencies {
|
||||
classpath("androidx.navigation:navigation-safe-args-gradle-plugin:2.7.6")
|
||||
classpath("androidx.navigation:navigation-safe-args-gradle-plugin:2.7.5")
|
||||
}
|
||||
}
|
||||
|
@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip
|
||||
|
@@ -298,9 +298,9 @@ void Source::ParseConfig(SourceConfiguration::Configuration& config,
|
||||
b.buffer_id,
|
||||
state.mono_or_stereo,
|
||||
state.format,
|
||||
true, // from_queue
|
||||
0, // play_position
|
||||
false, // has_played
|
||||
true,
|
||||
{}, // 0 in u32_dsp
|
||||
false,
|
||||
});
|
||||
}
|
||||
LOG_TRACE(Audio_DSP, "enqueuing queued {} addr={:#010x} len={} id={}", i,
|
||||
@@ -321,11 +321,7 @@ void Source::ParseConfig(SourceConfiguration::Configuration& config,
|
||||
void Source::GenerateFrame() {
|
||||
current_frame.fill({});
|
||||
|
||||
if (state.current_buffer.empty()) {
|
||||
// TODO(SachinV): Should dequeue happen at the end of the frame generation?
|
||||
if (DequeueBuffer()) {
|
||||
return;
|
||||
}
|
||||
if (state.current_buffer.empty() && !DequeueBuffer()) {
|
||||
state.enabled = false;
|
||||
state.buffer_update = true;
|
||||
state.last_buffer_id = state.current_buffer_id;
|
||||
@@ -334,6 +330,8 @@ void Source::GenerateFrame() {
|
||||
}
|
||||
|
||||
std::size_t frame_position = 0;
|
||||
|
||||
state.current_sample_number = state.next_sample_number;
|
||||
while (frame_position < current_frame.size()) {
|
||||
if (state.current_buffer.empty() && !DequeueBuffer()) {
|
||||
break;
|
||||
@@ -360,7 +358,7 @@ void Source::GenerateFrame() {
|
||||
}
|
||||
// TODO(jroweboy): Keep track of frame_position independently so that it doesn't lose precision
|
||||
// over time
|
||||
state.current_sample_number += static_cast<u32>(frame_position * state.rate_multiplier);
|
||||
state.next_sample_number += static_cast<u32>(frame_position * state.rate_multiplier);
|
||||
|
||||
state.filters.ProcessFrame(current_frame);
|
||||
}
|
||||
@@ -411,6 +409,7 @@ bool Source::DequeueBuffer() {
|
||||
|
||||
// the first playthrough starts at play_position, loops start at the beginning of the buffer
|
||||
state.current_sample_number = (!buf.has_played) ? buf.play_position : 0;
|
||||
state.next_sample_number = state.current_sample_number;
|
||||
state.current_buffer_physical_address = buf.physical_address;
|
||||
state.current_buffer_id = buf.buffer_id;
|
||||
state.last_buffer_id = 0;
|
||||
@@ -421,17 +420,8 @@ bool Source::DequeueBuffer() {
|
||||
state.input_queue.push(buf);
|
||||
}
|
||||
|
||||
// Because our interpolation consumes samples instead of using an index,
|
||||
// let's just consume the samples up to the current sample number.
|
||||
state.current_buffer.erase(
|
||||
state.current_buffer.begin(),
|
||||
std::next(state.current_buffer.begin(), state.current_sample_number));
|
||||
|
||||
LOG_TRACE(Audio_DSP,
|
||||
"source_id={} buffer_id={} from_queue={} current_buffer.size()={}, "
|
||||
"buf.has_played={}, buf.play_position={}",
|
||||
source_id, buf.buffer_id, buf.from_queue, state.current_buffer.size(), buf.has_played,
|
||||
buf.play_position);
|
||||
LOG_TRACE(Audio_DSP, "source_id={} buffer_id={} from_queue={} current_buffer.size()={}",
|
||||
source_id, buf.buffer_id, buf.from_queue, state.current_buffer.size());
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@@ -87,8 +87,8 @@ private:
|
||||
Format format;
|
||||
|
||||
bool from_queue;
|
||||
u32 play_position; // = 0;
|
||||
bool has_played; // = false;
|
||||
u32_dsp play_position; // = 0;
|
||||
bool has_played; // = false;
|
||||
|
||||
private:
|
||||
template <class Archive>
|
||||
@@ -136,6 +136,7 @@ private:
|
||||
// Current buffer
|
||||
|
||||
u32 current_sample_number = 0;
|
||||
u32 next_sample_number = 0;
|
||||
PAddr current_buffer_physical_address = 0;
|
||||
AudioInterp::StereoBuffer16 current_buffer = {};
|
||||
|
||||
@@ -170,6 +171,7 @@ private:
|
||||
ar& mono_or_stereo;
|
||||
ar& format;
|
||||
ar& current_sample_number;
|
||||
ar& next_sample_number;
|
||||
ar& current_buffer_physical_address;
|
||||
ar& current_buffer;
|
||||
ar& buffer_update;
|
||||
|
@@ -54,7 +54,7 @@ const std::array<std::array<int, 5>, Settings::NativeAnalog::NumAnalogs> Config:
|
||||
// This must be in alphabetical order according to action name as it must have the same order as
|
||||
// UISetting::values.shortcuts, which is alphabetically ordered.
|
||||
// clang-format off
|
||||
const std::array<UISettings::Shortcut, 35> Config::default_hotkeys {{
|
||||
const std::array<UISettings::Shortcut, 30> Config::default_hotkeys {{
|
||||
{QStringLiteral("Advance Frame"), QStringLiteral("Main Window"), {QStringLiteral(""), Qt::ApplicationShortcut}},
|
||||
{QStringLiteral("Audio Mute/Unmute"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+M"), Qt::WindowShortcut}},
|
||||
{QStringLiteral("Audio Volume Down"), QStringLiteral("Main Window"), {QStringLiteral(""), Qt::WindowShortcut}},
|
||||
@@ -71,11 +71,6 @@ const std::array<UISettings::Shortcut, 35> Config::default_hotkeys {{
|
||||
{QStringLiteral("Load Amiibo"), QStringLiteral("Main Window"), {QStringLiteral("F2"), Qt::WidgetWithChildrenShortcut}},
|
||||
{QStringLiteral("Load File"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+O"), Qt::WidgetWithChildrenShortcut}},
|
||||
{QStringLiteral("Load from Newest Slot"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+V"), Qt::WindowShortcut}},
|
||||
{QStringLiteral("Multiplayer Browse Public Game Lobby"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+B"), Qt::ApplicationShortcut}},
|
||||
{QStringLiteral("Multiplayer Create Room"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+N"), Qt::ApplicationShortcut}},
|
||||
{QStringLiteral("Multiplayer Direct Connect to Room"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+Shift"), Qt::ApplicationShortcut}},
|
||||
{QStringLiteral("Multiplayer Leave Room"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+L"), Qt::ApplicationShortcut}},
|
||||
{QStringLiteral("Multiplayer Show Current Room"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+R"), Qt::ApplicationShortcut}},
|
||||
{QStringLiteral("Remove Amiibo"), QStringLiteral("Main Window"), {QStringLiteral("F3"), Qt::ApplicationShortcut}},
|
||||
{QStringLiteral("Restart Emulation"), QStringLiteral("Main Window"), {QStringLiteral("F6"), Qt::WindowShortcut}},
|
||||
{QStringLiteral("Rotate Screens Upright"), QStringLiteral("Main Window"), {QStringLiteral("F8"), Qt::WindowShortcut}},
|
||||
@@ -562,15 +557,6 @@ void Config::ReadMultiplayerValues() {
|
||||
UISettings::values.game_id = ReadSetting(QStringLiteral("game_id"), 0).toULongLong();
|
||||
UISettings::values.room_description =
|
||||
ReadSetting(QStringLiteral("room_description"), QString{}).toString();
|
||||
UISettings::values.multiplayer_filter_text =
|
||||
ReadSetting(QStringLiteral("multiplayer_filter_text"), QString{}).toString();
|
||||
UISettings::values.multiplayer_filter_games_owned =
|
||||
ReadSetting(QStringLiteral("multiplayer_filter_games_owned"), false).toBool();
|
||||
UISettings::values.multiplayer_filter_hide_empty =
|
||||
ReadSetting(QStringLiteral("multiplayer_filter_hide_empty"), false).toBool();
|
||||
UISettings::values.multiplayer_filter_hide_full =
|
||||
ReadSetting(QStringLiteral("multiplayer_filter_hide_full"), false).toBool();
|
||||
|
||||
// Read ban list back
|
||||
int size = qt_config->beginReadArray(QStringLiteral("username_ban_list"));
|
||||
UISettings::values.ban_list.first.resize(size);
|
||||
@@ -1088,15 +1074,6 @@ void Config::SaveMultiplayerValues() {
|
||||
WriteSetting(QStringLiteral("game_id"), UISettings::values.game_id, 0);
|
||||
WriteSetting(QStringLiteral("room_description"), UISettings::values.room_description,
|
||||
QString{});
|
||||
WriteSetting(QStringLiteral("multiplayer_filter_text"),
|
||||
UISettings::values.multiplayer_filter_text, QString{});
|
||||
WriteSetting(QStringLiteral("multiplayer_filter_games_owned"),
|
||||
UISettings::values.multiplayer_filter_games_owned, false);
|
||||
WriteSetting(QStringLiteral("multiplayer_filter_hide_empty"),
|
||||
UISettings::values.multiplayer_filter_hide_empty, false);
|
||||
WriteSetting(QStringLiteral("multiplayer_filter_hide_full"),
|
||||
UISettings::values.multiplayer_filter_hide_full, false);
|
||||
|
||||
// Write ban list
|
||||
qt_config->beginWriteArray(QStringLiteral("username_ban_list"));
|
||||
for (std::size_t i = 0; i < UISettings::values.ban_list.first.size(); ++i) {
|
||||
|
@@ -26,7 +26,7 @@ public:
|
||||
|
||||
static const std::array<int, Settings::NativeButton::NumButtons> default_buttons;
|
||||
static const std::array<std::array<int, 5>, Settings::NativeAnalog::NumAnalogs> default_analogs;
|
||||
static const std::array<UISettings::Shortcut, 35> default_hotkeys;
|
||||
static const std::array<UISettings::Shortcut, 30> default_hotkeys;
|
||||
|
||||
private:
|
||||
void Initialize(const std::string& config_name);
|
||||
|
@@ -647,13 +647,6 @@ void GMainWindow::InitializeHotkeys() {
|
||||
link_action_shortcut(ui->action_Advance_Frame, QStringLiteral("Advance Frame"));
|
||||
link_action_shortcut(ui->action_Load_from_Newest_Slot, QStringLiteral("Load from Newest Slot"));
|
||||
link_action_shortcut(ui->action_Save_to_Oldest_Slot, QStringLiteral("Save to Oldest Slot"));
|
||||
link_action_shortcut(ui->action_View_Lobby,
|
||||
QStringLiteral("Multiplayer Browse Public Game Lobby"));
|
||||
link_action_shortcut(ui->action_Start_Room, QStringLiteral("Multiplayer Create Room"));
|
||||
link_action_shortcut(ui->action_Connect_To_Room,
|
||||
QStringLiteral("Multiplayer Direct Connect to Room"));
|
||||
link_action_shortcut(ui->action_Show_Room, QStringLiteral("Multiplayer Show Current Room"));
|
||||
link_action_shortcut(ui->action_Leave_Room, QStringLiteral("Multiplayer Leave Room"));
|
||||
|
||||
const auto add_secondary_window_hotkey = [this](QKeySequence hotkey, const char* slot) {
|
||||
// This action will fire specifically when secondary_window is in focus
|
||||
@@ -3197,10 +3190,8 @@ int main(int argc, char* argv[]) {
|
||||
QApplication::setHighDpiScaleFactorRoundingPolicy(rounding_policy);
|
||||
|
||||
#ifdef __APPLE__
|
||||
auto bundle_dir = FileUtil::GetBundleDirectory();
|
||||
if (bundle_dir) {
|
||||
FileUtil::SetCurrentDir(bundle_dir.value() + "..");
|
||||
}
|
||||
std::string bin_path = FileUtil::GetBundleDirectory() + DIR_SEP + "..";
|
||||
chdir(bin_path.c_str());
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_OPENGL
|
||||
|
@@ -80,8 +80,9 @@ void DirectConnectWindow::Connect() {
|
||||
// Store settings
|
||||
UISettings::values.nickname = ui->nickname->text();
|
||||
UISettings::values.ip = ui->ip->text();
|
||||
UISettings::values.port =
|
||||
!ui->port->text().isEmpty() ? ui->port->text() : UISettings::values.port;
|
||||
UISettings::values.port = (ui->port->isModified() && !ui->port->text().isEmpty())
|
||||
? ui->port->text()
|
||||
: UISettings::values.port;
|
||||
|
||||
// attempt to connect in a different thread
|
||||
QFuture<void> f = QtConcurrent::run([&] {
|
||||
|
@@ -63,10 +63,10 @@ Lobby::Lobby(Core::System& system_, QWidget* parent, QStandardItemModel* list,
|
||||
|
||||
// UI Buttons
|
||||
connect(ui->refresh_list, &QPushButton::clicked, this, &Lobby::RefreshLobby);
|
||||
connect(ui->search, &QLineEdit::textChanged, proxy, &LobbyFilterProxyModel::SetFilterSearch);
|
||||
connect(ui->games_owned, &QCheckBox::toggled, proxy, &LobbyFilterProxyModel::SetFilterOwned);
|
||||
connect(ui->hide_empty, &QCheckBox::toggled, proxy, &LobbyFilterProxyModel::SetFilterEmpty);
|
||||
connect(ui->hide_full, &QCheckBox::toggled, proxy, &LobbyFilterProxyModel::SetFilterFull);
|
||||
connect(ui->search, &QLineEdit::textChanged, proxy, &LobbyFilterProxyModel::SetFilterSearch);
|
||||
connect(ui->room_list, &QTreeView::doubleClicked, this, &Lobby::OnJoinRoom);
|
||||
connect(ui->room_list, &QTreeView::clicked, this, &Lobby::OnExpandRoom);
|
||||
|
||||
@@ -74,12 +74,6 @@ Lobby::Lobby(Core::System& system_, QWidget* parent, QStandardItemModel* list,
|
||||
connect(&room_list_watcher, &QFutureWatcher<AnnounceMultiplayerRoom::RoomList>::finished, this,
|
||||
&Lobby::OnRefreshLobby);
|
||||
|
||||
// Load persistent filters after events are connected to make sure they apply
|
||||
ui->search->setText(UISettings::values.multiplayer_filter_text);
|
||||
ui->games_owned->setChecked(UISettings::values.multiplayer_filter_games_owned);
|
||||
ui->hide_empty->setChecked(UISettings::values.multiplayer_filter_hide_empty);
|
||||
ui->hide_full->setChecked(UISettings::values.multiplayer_filter_hide_full);
|
||||
|
||||
// manually start a refresh when the window is opening
|
||||
// TODO(jroweboy): if this refresh is slow for people with bad internet, then don't do it as
|
||||
// part of the constructor, but offload the refresh until after the window shown. perhaps emit a
|
||||
@@ -186,10 +180,6 @@ void Lobby::OnJoinRoom(const QModelIndex& source) {
|
||||
UISettings::values.nickname = ui->nickname->text();
|
||||
UISettings::values.ip = proxy->data(connection_index, LobbyItemHost::HostIPRole).toString();
|
||||
UISettings::values.port = proxy->data(connection_index, LobbyItemHost::HostPortRole).toString();
|
||||
UISettings::values.multiplayer_filter_text = ui->search->text();
|
||||
UISettings::values.multiplayer_filter_games_owned = ui->games_owned->isChecked();
|
||||
UISettings::values.multiplayer_filter_hide_empty = ui->hide_empty->isChecked();
|
||||
UISettings::values.multiplayer_filter_hide_full = ui->hide_full->isChecked();
|
||||
}
|
||||
|
||||
void Lobby::ResetModel() {
|
||||
|
@@ -188,37 +188,12 @@ public:
|
||||
}
|
||||
|
||||
QVariant data(int role) const override {
|
||||
switch (role) {
|
||||
case Qt::DisplayRole: {
|
||||
auto members = data(MemberListRole).toList();
|
||||
return QStringLiteral("%1 / %2").arg(QString::number(members.size()),
|
||||
data(MaxPlayerRole).toString());
|
||||
}
|
||||
case Qt::ForegroundRole: {
|
||||
auto members = data(MemberListRole).toList();
|
||||
auto max_players = data(MaxPlayerRole).toInt();
|
||||
const QColor room_full_color(255, 48, 32);
|
||||
const QColor room_almost_full_color(255, 140, 32);
|
||||
const QColor room_has_players_color(32, 160, 32);
|
||||
const QColor room_empty_color(128, 128, 128);
|
||||
|
||||
if (members.size() >= max_players) {
|
||||
return QBrush(room_full_color);
|
||||
} else if (members.size() == (max_players - 1)) {
|
||||
return QBrush(room_almost_full_color);
|
||||
} else if (members.size() == 0) {
|
||||
return QBrush(room_empty_color);
|
||||
} else if (members.size() > 0 && members.size() < (max_players - 1)) {
|
||||
return QBrush(room_has_players_color);
|
||||
}
|
||||
|
||||
// FIXME: How to return a value that tells Qt not to modify the
|
||||
// text color from the default (as if Qt::ForegroundRole wasn't overridden)?
|
||||
return QBrush(nullptr);
|
||||
}
|
||||
default:
|
||||
if (role != Qt::DisplayRole) {
|
||||
return LobbyItem::data(role);
|
||||
}
|
||||
auto members = data(MemberListRole).toList();
|
||||
return QStringLiteral("%1 / %2").arg(QString::number(members.size()),
|
||||
data(MaxPlayerRole).toString());
|
||||
}
|
||||
|
||||
bool operator<(const QStandardItem& other) const override {
|
||||
|
@@ -138,11 +138,6 @@ struct Values {
|
||||
QString room_description;
|
||||
std::pair<std::vector<std::string>, std::vector<std::string>> ban_list;
|
||||
|
||||
QString multiplayer_filter_text;
|
||||
bool multiplayer_filter_games_owned;
|
||||
bool multiplayer_filter_hide_empty;
|
||||
bool multiplayer_filter_hide_full;
|
||||
|
||||
// logging
|
||||
Settings::Setting<bool> show_console{false, "showConsole"};
|
||||
};
|
||||
|
@@ -13,6 +13,7 @@
|
||||
#endif
|
||||
|
||||
// The user data dir
|
||||
#define ROOT_DIR "."
|
||||
#define USERDATA_DIR "user"
|
||||
#ifdef USER_DIR
|
||||
#define EMU_DATA_DIR USER_DIR
|
||||
|
@@ -634,10 +634,6 @@ std::optional<std::string> GetCurrentDir() {
|
||||
std::string strDir = dir;
|
||||
#endif
|
||||
free(dir);
|
||||
|
||||
if (!strDir.ends_with(DIR_SEP)) {
|
||||
strDir += DIR_SEP;
|
||||
}
|
||||
return strDir;
|
||||
} // namespace FileUtil
|
||||
|
||||
@@ -650,36 +646,17 @@ bool SetCurrentDir(const std::string& directory) {
|
||||
}
|
||||
|
||||
#if defined(__APPLE__)
|
||||
std::optional<std::string> GetBundleDirectory() {
|
||||
std::string GetBundleDirectory() {
|
||||
CFURLRef BundleRef;
|
||||
char AppBundlePath[MAXPATHLEN];
|
||||
// Get the main bundle for the app
|
||||
CFBundleRef bundle_ref = CFBundleGetMainBundle();
|
||||
if (!bundle_ref) {
|
||||
return {};
|
||||
}
|
||||
BundleRef = CFBundleCopyBundleURL(CFBundleGetMainBundle());
|
||||
CFStringRef BundlePath = CFURLCopyFileSystemPath(BundleRef, kCFURLPOSIXPathStyle);
|
||||
CFStringGetFileSystemRepresentation(BundlePath, AppBundlePath, sizeof(AppBundlePath));
|
||||
CFRelease(BundleRef);
|
||||
CFRelease(BundlePath);
|
||||
|
||||
CFURLRef bundle_url_ref = CFBundleCopyBundleURL(bundle_ref);
|
||||
if (!bundle_url_ref) {
|
||||
return {};
|
||||
}
|
||||
SCOPE_EXIT({ CFRelease(bundle_url_ref); });
|
||||
|
||||
CFStringRef bundle_path_ref = CFURLCopyFileSystemPath(bundle_url_ref, kCFURLPOSIXPathStyle);
|
||||
if (!bundle_path_ref) {
|
||||
return {};
|
||||
}
|
||||
SCOPE_EXIT({ CFRelease(bundle_path_ref); });
|
||||
|
||||
char app_bundle_path[MAXPATHLEN];
|
||||
if (!CFStringGetFileSystemRepresentation(bundle_path_ref, app_bundle_path,
|
||||
sizeof(app_bundle_path))) {
|
||||
return {};
|
||||
}
|
||||
|
||||
std::string path_str(app_bundle_path);
|
||||
if (!path_str.ends_with(DIR_SEP)) {
|
||||
path_str += DIR_SEP;
|
||||
}
|
||||
return path_str;
|
||||
return AppBundlePath;
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -755,6 +732,22 @@ static const std::string& GetHomeDirectory() {
|
||||
}
|
||||
#endif
|
||||
|
||||
std::string GetSysDirectory() {
|
||||
std::string sysDir;
|
||||
|
||||
#if defined(__APPLE__)
|
||||
sysDir = GetBundleDirectory();
|
||||
sysDir += DIR_SEP;
|
||||
sysDir += SYSDATA_DIR;
|
||||
#else
|
||||
sysDir = SYSDATA_DIR;
|
||||
#endif
|
||||
sysDir += DIR_SEP;
|
||||
|
||||
LOG_DEBUG(Common_Filesystem, "Setting to {}:", sysDir);
|
||||
return sysDir;
|
||||
}
|
||||
|
||||
namespace {
|
||||
std::unordered_map<UserPath, std::string> g_paths;
|
||||
std::unordered_map<UserPath, std::string> g_default_paths;
|
||||
@@ -784,10 +777,8 @@ void SetUserPath(const std::string& path) {
|
||||
g_paths.emplace(UserPath::ConfigDir, user_path + CONFIG_DIR DIR_SEP);
|
||||
g_paths.emplace(UserPath::CacheDir, user_path + CACHE_DIR DIR_SEP);
|
||||
#else
|
||||
auto current_dir = FileUtil::GetCurrentDir();
|
||||
if (current_dir.has_value() &&
|
||||
FileUtil::Exists(current_dir.value() + USERDATA_DIR DIR_SEP)) {
|
||||
user_path = current_dir.value() + USERDATA_DIR DIR_SEP;
|
||||
if (FileUtil::Exists(ROOT_DIR DIR_SEP USERDATA_DIR)) {
|
||||
user_path = ROOT_DIR DIR_SEP USERDATA_DIR DIR_SEP;
|
||||
g_paths.emplace(UserPath::ConfigDir, user_path + CONFIG_DIR DIR_SEP);
|
||||
g_paths.emplace(UserPath::CacheDir, user_path + CACHE_DIR DIR_SEP);
|
||||
} else {
|
||||
|
@@ -193,8 +193,11 @@ void SetCurrentRomPath(const std::string& path);
|
||||
// Update the Global Path with the new value
|
||||
void UpdateUserPath(UserPath path, const std::string& filename);
|
||||
|
||||
// Returns the path to where the sys file are
|
||||
[[nodiscard]] std::string GetSysDirectory();
|
||||
|
||||
#ifdef __APPLE__
|
||||
[[nodiscard]] std::optional<std::string> GetBundleDirectory();
|
||||
[[nodiscard]] std::string GetBundleDirectory();
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
|
@@ -14,18 +14,18 @@ namespace ConfigMem {
|
||||
Handler::Handler() {
|
||||
std::memset(&config_mem, 0, sizeof(config_mem));
|
||||
|
||||
// Values extracted from firmware 11.17.0-50E
|
||||
config_mem.kernel_version_min = 0x3a;
|
||||
// Values extracted from firmware 11.2.0-35E
|
||||
config_mem.kernel_version_min = 0x34;
|
||||
config_mem.kernel_version_maj = 0x2;
|
||||
config_mem.ns_tid = 0x0004013000008002;
|
||||
config_mem.sys_core_ver = 0x2;
|
||||
config_mem.unit_info = 0x1; // Bit 0 set for Retail
|
||||
config_mem.prev_firm = 0x1;
|
||||
config_mem.ctr_sdk_ver = 0x0000F450;
|
||||
config_mem.firm_version_min = 0x3a;
|
||||
config_mem.ctr_sdk_ver = 0x0000F297;
|
||||
config_mem.firm_version_min = 0x34;
|
||||
config_mem.firm_version_maj = 0x2;
|
||||
config_mem.firm_sys_core_ver = 0x2;
|
||||
config_mem.firm_ctr_sdk_ver = 0x0000F450;
|
||||
config_mem.firm_ctr_sdk_ver = 0x0000F297;
|
||||
}
|
||||
|
||||
ConfigMemDef& Handler::GetConfigMem() {
|
||||
|
@@ -210,10 +210,10 @@ void Process::Set3dsxKernelCaps() {
|
||||
};
|
||||
|
||||
// Similar to Rosalina, we set kernel version to a recent one.
|
||||
// This is 11.17.0, to be consistent with core/hle/kernel/config_mem.cpp
|
||||
// This is 11.2.0, to be consistent with core/hle/kernel/config_mem.cpp
|
||||
// TODO: refactor kernel version out so it is configurable and consistent
|
||||
// among all relevant places.
|
||||
kernel_version = 0x23a;
|
||||
kernel_version = 0x234;
|
||||
}
|
||||
|
||||
void Process::Run(s32 main_thread_priority, u32 stack_size) {
|
||||
|
@@ -373,10 +373,7 @@ ResultVal<AppletManager::InitializeResult> AppletManager::Initialize(AppletId ap
|
||||
if (active_slot == AppletSlot::Error) {
|
||||
active_slot = slot;
|
||||
|
||||
// APT automatically calls enable on the first registered applet.
|
||||
Enable(attributes);
|
||||
|
||||
// Wake up the applet.
|
||||
// Wake up the application.
|
||||
SendParameter({
|
||||
.sender_id = AppletId::None,
|
||||
.destination_id = app_id,
|
||||
@@ -401,8 +398,7 @@ Result AppletManager::Enable(AppletAttributes attributes) {
|
||||
auto slot_data = GetAppletSlot(slot);
|
||||
slot_data->registered = true;
|
||||
|
||||
if (slot_data->applet_id != AppletId::None &&
|
||||
slot_data->attributes.applet_pos == AppletPos::System &&
|
||||
if (slot_data->attributes.applet_pos == AppletPos::System &&
|
||||
slot_data->attributes.is_home_menu) {
|
||||
slot_data->attributes.raw |= attributes.raw;
|
||||
LOG_DEBUG(Service_APT, "Updated home menu attributes to {:08X}.",
|
||||
@@ -790,23 +786,16 @@ Result AppletManager::PrepareToStartSystemApplet(AppletId applet_id) {
|
||||
|
||||
Result AppletManager::StartSystemApplet(AppletId applet_id, std::shared_ptr<Kernel::Object> object,
|
||||
const std::vector<u8>& buffer) {
|
||||
auto source_applet_id = AppletId::Application;
|
||||
auto source_applet_id = AppletId::None;
|
||||
if (last_system_launcher_slot != AppletSlot::Error) {
|
||||
const auto launcher_slot_data = GetAppletSlot(last_system_launcher_slot);
|
||||
source_applet_id = launcher_slot_data->applet_id;
|
||||
const auto slot_data = GetAppletSlot(last_system_launcher_slot);
|
||||
source_applet_id = slot_data->applet_id;
|
||||
|
||||
// APT generally clears and terminates the caller of StartSystemApplet. This helps in
|
||||
// situations such as a system applet launching another system applet, which would
|
||||
// otherwise deadlock.
|
||||
// TODO: In real APT, the check for AppletSlot::Application does not exist; there is
|
||||
// TODO: something wrong with our implementation somewhere that makes this necessary.
|
||||
// TODO: Otherwise, games that attempt to launch system applets will be cleared and
|
||||
// TODO: emulation will crash.
|
||||
if (!launcher_slot_data->registered ||
|
||||
(last_system_launcher_slot != AppletSlot::Application &&
|
||||
!launcher_slot_data->attributes.no_exit_on_system_applet)) {
|
||||
launcher_slot_data->Reset();
|
||||
// TODO: Implement launcher process termination.
|
||||
// If a system applet is launching another system applet, reset the slot to avoid conflicts.
|
||||
// This is needed because system applets won't necessarily call CloseSystemApplet before
|
||||
// exiting.
|
||||
if (last_system_launcher_slot == AppletSlot::SystemApplet) {
|
||||
slot_data->Reset();
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -152,7 +152,6 @@ union AppletAttributes {
|
||||
u32 raw;
|
||||
|
||||
BitField<0, 3, AppletPos> applet_pos;
|
||||
BitField<28, 1, u32> no_exit_on_system_applet;
|
||||
BitField<29, 1, u32> is_home_menu;
|
||||
|
||||
AppletAttributes() : raw(0) {}
|
||||
|
@@ -429,20 +429,18 @@ Common::ParamPackage SDLState::GetSDLControllerButtonBindByGUID(
|
||||
|
||||
#if SDL_VERSION_ATLEAST(2, 0, 6)
|
||||
{
|
||||
if (mapped_button != SDL_CONTROLLER_BUTTON_INVALID) {
|
||||
const SDL_ExtendedGameControllerBind extended_bind =
|
||||
controller->bindings[mapped_button];
|
||||
if (extended_bind.input.axis.axis_max < extended_bind.input.axis.axis_min) {
|
||||
params.Set("direction", "-");
|
||||
} else {
|
||||
params.Set("direction", "+");
|
||||
}
|
||||
params.Set("threshold", (extended_bind.input.axis.axis_min +
|
||||
(extended_bind.input.axis.axis_max -
|
||||
extended_bind.input.axis.axis_min) /
|
||||
2.0f) /
|
||||
SDL_JOYSTICK_AXIS_MAX);
|
||||
const SDL_ExtendedGameControllerBind extended_bind =
|
||||
controller->bindings[mapped_button];
|
||||
if (extended_bind.input.axis.axis_max < extended_bind.input.axis.axis_min) {
|
||||
params.Set("direction", "-");
|
||||
} else {
|
||||
params.Set("direction", "+");
|
||||
}
|
||||
params.Set(
|
||||
"threshold",
|
||||
(extended_bind.input.axis.axis_min +
|
||||
(extended_bind.input.axis.axis_max - extended_bind.input.axis.axis_min) / 2.0f) /
|
||||
SDL_JOYSTICK_AXIS_MAX);
|
||||
}
|
||||
#else
|
||||
params.Set("direction", "+"); // lacks extended_bind, so just a guess
|
||||
|
@@ -160,8 +160,8 @@ if (ENABLE_VULKAN)
|
||||
renderer_vulkan/vk_blit_helper.h
|
||||
renderer_vulkan/vk_common.cpp
|
||||
renderer_vulkan/vk_common.h
|
||||
renderer_vulkan/vk_descriptor_update_queue.cpp
|
||||
renderer_vulkan/vk_descriptor_update_queue.h
|
||||
renderer_vulkan/vk_descriptor_pool.cpp
|
||||
renderer_vulkan/vk_descriptor_pool.h
|
||||
renderer_vulkan/vk_graphics_pipeline.cpp
|
||||
renderer_vulkan/vk_graphics_pipeline.h
|
||||
renderer_vulkan/vk_master_semaphore.cpp
|
||||
@@ -183,8 +183,8 @@ if (ENABLE_VULKAN)
|
||||
renderer_vulkan/vk_platform.h
|
||||
renderer_vulkan/vk_present_window.cpp
|
||||
renderer_vulkan/vk_present_window.h
|
||||
renderer_vulkan/vk_render_manager.cpp
|
||||
renderer_vulkan/vk_render_manager.h
|
||||
renderer_vulkan/vk_renderpass_cache.cpp
|
||||
renderer_vulkan/vk_renderpass_cache.h
|
||||
renderer_vulkan/vk_shader_util.cpp
|
||||
renderer_vulkan/vk_shader_util.h
|
||||
renderer_vulkan/vk_stream_buffer.cpp
|
||||
|
@@ -385,7 +385,7 @@ std::vector<FileUtil::FSTEntry> CustomTexManager::GetTextures(u64 title_id) {
|
||||
}
|
||||
|
||||
void CustomTexManager::CreateWorkers() {
|
||||
const std::size_t num_workers = std::max(std::thread::hardware_concurrency(), 2U) >> 1;
|
||||
const std::size_t num_workers = std::max(std::thread::hardware_concurrency(), 2U) - 1;
|
||||
workers = std::make_unique<Common::ThreadWorker>(num_workers, "Custom textures");
|
||||
}
|
||||
|
||||
|
@@ -176,15 +176,15 @@ struct TexturingRegs {
|
||||
INSERT_PADDING_WORDS(0x9);
|
||||
|
||||
struct FullTextureConfig {
|
||||
const u32 enabled;
|
||||
const bool enabled;
|
||||
const TextureConfig config;
|
||||
const TextureFormat format;
|
||||
};
|
||||
const std::array<FullTextureConfig, 3> GetTextures() const {
|
||||
return {{
|
||||
{main_config.texture0_enable, texture0, texture0_format},
|
||||
{main_config.texture1_enable, texture1, texture1_format},
|
||||
{main_config.texture2_enable, texture2, texture2_format},
|
||||
{static_cast<bool>(main_config.texture0_enable), texture0, texture0_format},
|
||||
{static_cast<bool>(main_config.texture1_enable), texture1, texture1_format},
|
||||
{static_cast<bool>(main_config.texture2_enable), texture2, texture2_format},
|
||||
}};
|
||||
}
|
||||
|
||||
|
@@ -600,43 +600,14 @@ typename T::Surface& RasterizerCache<T>::GetTextureCube(const TextureCubeConfig&
|
||||
auto [it, new_surface] = texture_cube_cache.try_emplace(config);
|
||||
TextureCube& cube = it->second;
|
||||
|
||||
const std::array addresses = {config.px, config.nx, config.py, config.ny, config.pz, config.nz};
|
||||
|
||||
if (new_surface) {
|
||||
Pica::Texture::TextureInfo info = {
|
||||
.width = config.width,
|
||||
.height = config.width,
|
||||
.format = config.format,
|
||||
};
|
||||
info.SetDefaultStride();
|
||||
|
||||
u32 res_scale = 1;
|
||||
for (u32 i = 0; i < addresses.size(); i++) {
|
||||
if (!addresses[i]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
SurfaceId& face_id = cube.face_ids[i];
|
||||
if (!face_id) {
|
||||
info.physical_address = addresses[i];
|
||||
face_id = GetTextureSurface(info, config.levels - 1);
|
||||
Surface& surface = slot_surfaces[face_id];
|
||||
ASSERT_MSG(
|
||||
surface.levels >= config.levels,
|
||||
"Texture cube face levels are not enough to validate the levels requested");
|
||||
surface.flags |= SurfaceFlagBits::Tracked;
|
||||
}
|
||||
Surface& surface = slot_surfaces[face_id];
|
||||
res_scale = std::max(surface.res_scale, res_scale);
|
||||
}
|
||||
|
||||
SurfaceParams cube_params = {
|
||||
.addr = config.px,
|
||||
.width = config.width,
|
||||
.height = config.width,
|
||||
.stride = config.width,
|
||||
.levels = config.levels,
|
||||
.res_scale = res_scale,
|
||||
.res_scale = filter != Settings::TextureFilter::None ? resolution_scale_factor : 1,
|
||||
.texture_type = TextureType::CubeMap,
|
||||
.pixel_format = PixelFormatFromTextureFormat(config.format),
|
||||
.type = SurfaceType::Texture,
|
||||
@@ -645,20 +616,38 @@ typename T::Surface& RasterizerCache<T>::GetTextureCube(const TextureCubeConfig&
|
||||
cube.surface_id = CreateSurface(cube_params);
|
||||
}
|
||||
|
||||
Surface& cube_surface = slot_surfaces[cube.surface_id];
|
||||
const u32 scaled_size = slot_surfaces[cube.surface_id].GetScaledWidth();
|
||||
const std::array addresses = {config.px, config.nx, config.py, config.ny, config.pz, config.nz};
|
||||
|
||||
Pica::Texture::TextureInfo info = {
|
||||
.width = config.width,
|
||||
.height = config.width,
|
||||
.format = config.format,
|
||||
};
|
||||
info.SetDefaultStride();
|
||||
|
||||
for (u32 i = 0; i < addresses.size(); i++) {
|
||||
if (!addresses[i]) {
|
||||
continue;
|
||||
}
|
||||
Surface& surface = slot_surfaces[cube.face_ids[i]];
|
||||
|
||||
SurfaceId& face_id = cube.face_ids[i];
|
||||
if (!face_id) {
|
||||
info.physical_address = addresses[i];
|
||||
face_id = GetTextureSurface(info, config.levels - 1);
|
||||
ASSERT_MSG(slot_surfaces[face_id].levels >= config.levels,
|
||||
"Texture cube face levels are not enough to validate the levels requested");
|
||||
}
|
||||
Surface& surface = slot_surfaces[face_id];
|
||||
surface.flags |= SurfaceFlagBits::Tracked;
|
||||
if (cube.ticks[i] == surface.modification_tick) {
|
||||
continue;
|
||||
}
|
||||
cube.ticks[i] = surface.modification_tick;
|
||||
boost::container::small_vector<TextureCopy, 8> upload_copies;
|
||||
Surface& cube_surface = slot_surfaces[cube.surface_id];
|
||||
for (u32 level = 0; level < config.levels; level++) {
|
||||
const u32 width_lod = surface.GetScaledWidth() >> level;
|
||||
upload_copies.push_back({
|
||||
const u32 width_lod = scaled_size >> level;
|
||||
const TextureCopy texture_copy = {
|
||||
.src_level = level,
|
||||
.dst_level = level,
|
||||
.src_layer = 0,
|
||||
@@ -666,9 +655,9 @@ typename T::Surface& RasterizerCache<T>::GetTextureCube(const TextureCubeConfig&
|
||||
.src_offset = {0, 0},
|
||||
.dst_offset = {0, 0},
|
||||
.extent = {width_lod, width_lod},
|
||||
});
|
||||
};
|
||||
runtime.CopyTextures(surface, cube_surface, texture_copy);
|
||||
}
|
||||
runtime.CopyTextures(surface, cube_surface, upload_copies);
|
||||
}
|
||||
|
||||
return slot_surfaces[cube.surface_id];
|
||||
|
@@ -260,19 +260,16 @@ void TextureRuntime::ClearTexture(Surface& surface, const VideoCore::TextureClea
|
||||
}
|
||||
|
||||
bool TextureRuntime::CopyTextures(Surface& source, Surface& dest,
|
||||
std::span<const VideoCore::TextureCopy> copies) {
|
||||
const VideoCore::TextureCopy& copy) {
|
||||
const GLenum src_textarget = source.texture_type == VideoCore::TextureType::CubeMap
|
||||
? GL_TEXTURE_CUBE_MAP
|
||||
: GL_TEXTURE_2D;
|
||||
const GLenum dest_textarget =
|
||||
dest.texture_type == VideoCore::TextureType::CubeMap ? GL_TEXTURE_CUBE_MAP : GL_TEXTURE_2D;
|
||||
|
||||
for (const auto& copy : copies) {
|
||||
glCopyImageSubData(source.Handle(), src_textarget, copy.src_level, copy.src_offset.x,
|
||||
copy.src_offset.y, copy.src_layer, dest.Handle(), dest_textarget,
|
||||
copy.dst_level, copy.dst_offset.x, copy.dst_offset.y, copy.dst_layer,
|
||||
copy.extent.width, copy.extent.height, 1);
|
||||
}
|
||||
glCopyImageSubData(source.Handle(), src_textarget, copy.src_level, copy.src_offset.x,
|
||||
copy.src_offset.y, copy.src_layer, dest.Handle(), dest_textarget,
|
||||
copy.dst_level, copy.dst_offset.x, copy.dst_offset.y, copy.dst_layer,
|
||||
copy.extent.width, copy.extent.height, 1);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@@ -65,12 +65,7 @@ public:
|
||||
void ClearTexture(Surface& surface, const VideoCore::TextureClear& clear);
|
||||
|
||||
/// Copies a rectangle of source to another rectange of dest
|
||||
bool CopyTextures(Surface& source, Surface& dest,
|
||||
std::span<const VideoCore::TextureCopy> copies);
|
||||
|
||||
bool CopyTextures(Surface& source, Surface& dest, const VideoCore::TextureCopy& copy) {
|
||||
return CopyTextures(source, dest, std::array{copy});
|
||||
}
|
||||
bool CopyTextures(Surface& source, Surface& dest, const VideoCore::TextureCopy& copy);
|
||||
|
||||
/// Blits a rectangle of source to another rectange of dest
|
||||
bool BlitTextures(Surface& source, Surface& dest, const VideoCore::TextureBlit& blit);
|
||||
|
@@ -148,6 +148,8 @@ inline GLenum BlendFunc(Pica::FramebufferRegs::BlendFactor factor) {
|
||||
// Range check table for input
|
||||
if (index >= blend_func_table.size()) {
|
||||
LOG_CRITICAL(Render_OpenGL, "Unknown blend factor {}", index);
|
||||
UNREACHABLE();
|
||||
|
||||
return GL_ONE;
|
||||
}
|
||||
|
||||
|
@@ -96,10 +96,7 @@ inline vk::BlendFactor BlendFunc(Pica::FramebufferRegs::BlendFactor factor) {
|
||||
}};
|
||||
|
||||
const auto index = static_cast<std::size_t>(factor);
|
||||
if (index >= blend_func_table.size()) {
|
||||
LOG_CRITICAL(Render_Vulkan, "Unknown blend factor {}", index);
|
||||
return vk::BlendFactor::eOne;
|
||||
}
|
||||
ASSERT_MSG(index < blend_func_table.size(), "Unknown blend factor {}", index);
|
||||
return blend_func_table[index];
|
||||
}
|
||||
|
||||
|
@@ -54,21 +54,21 @@ RendererVulkan::RendererVulkan(Core::System& system, Pica::PicaCore& pica_,
|
||||
Frontend::EmuWindow& window, Frontend::EmuWindow* secondary_window)
|
||||
: RendererBase{system, window, secondary_window}, memory{system.Memory()}, pica{pica_},
|
||||
instance{system.TelemetrySession(), window, Settings::values.physical_device.GetValue()},
|
||||
scheduler{instance}, render_manager{instance, scheduler}, main_window{window, instance,
|
||||
scheduler},
|
||||
scheduler{instance}, renderpass_cache{instance, scheduler}, pool{instance},
|
||||
main_window{window, instance, scheduler},
|
||||
vertex_buffer{instance, scheduler, vk::BufferUsageFlagBits::eVertexBuffer,
|
||||
VERTEX_BUFFER_SIZE},
|
||||
update_queue{instance}, rasterizer{memory,
|
||||
pica,
|
||||
system.CustomTexManager(),
|
||||
*this,
|
||||
render_window,
|
||||
instance,
|
||||
scheduler,
|
||||
render_manager,
|
||||
update_queue,
|
||||
main_window.ImageCount()},
|
||||
present_heap{instance, scheduler.GetMasterSemaphore(), PRESENT_BINDINGS, 32} {
|
||||
rasterizer{memory,
|
||||
pica,
|
||||
system.CustomTexManager(),
|
||||
*this,
|
||||
render_window,
|
||||
instance,
|
||||
scheduler,
|
||||
pool,
|
||||
renderpass_cache,
|
||||
main_window.ImageCount()},
|
||||
present_set_provider{instance, pool, PRESENT_BINDINGS} {
|
||||
CompileShaders();
|
||||
BuildLayouts();
|
||||
BuildPipelines();
|
||||
@@ -127,14 +127,16 @@ void RendererVulkan::PrepareRendertarget() {
|
||||
|
||||
void RendererVulkan::PrepareDraw(Frame* frame, const Layout::FramebufferLayout& layout) {
|
||||
const auto sampler = present_samplers[!Settings::values.filter_mode.GetValue()];
|
||||
const auto present_set = present_heap.Commit();
|
||||
for (u32 index = 0; index < screen_infos.size(); index++) {
|
||||
update_queue.AddImageSampler(present_set, 0, index, screen_infos[index].image_view,
|
||||
sampler);
|
||||
}
|
||||
std::transform(screen_infos.begin(), screen_infos.end(), present_textures.begin(),
|
||||
[&](auto& info) {
|
||||
return DescriptorData{vk::DescriptorImageInfo{sampler, info.image_view,
|
||||
vk::ImageLayout::eGeneral}};
|
||||
});
|
||||
|
||||
render_manager.EndRendering();
|
||||
scheduler.Record([this, layout, frame, present_set, renderpass = main_window.Renderpass(),
|
||||
const auto descriptor_set = present_set_provider.Acquire(present_textures);
|
||||
|
||||
renderpass_cache.EndRendering();
|
||||
scheduler.Record([this, layout, frame, descriptor_set, renderpass = main_window.Renderpass(),
|
||||
index = current_pipeline](vk::CommandBuffer cmdbuf) {
|
||||
const vk::Viewport viewport = {
|
||||
.x = 0.0f,
|
||||
@@ -169,7 +171,7 @@ void RendererVulkan::PrepareDraw(Frame* frame, const Layout::FramebufferLayout&
|
||||
|
||||
cmdbuf.beginRenderPass(renderpass_begin_info, vk::SubpassContents::eInline);
|
||||
cmdbuf.bindPipeline(vk::PipelineBindPoint::eGraphics, present_pipelines[index]);
|
||||
cmdbuf.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, layout, 0, present_set, {});
|
||||
cmdbuf.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, layout, 0, descriptor_set, {});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -256,7 +258,7 @@ void RendererVulkan::BuildLayouts() {
|
||||
.size = sizeof(PresentUniformData),
|
||||
};
|
||||
|
||||
const auto descriptor_set_layout = present_heap.Layout();
|
||||
const auto descriptor_set_layout = present_set_provider.Layout();
|
||||
const vk::PipelineLayoutCreateInfo layout_info = {
|
||||
.setLayoutCount = 1,
|
||||
.pSetLayouts = &descriptor_set_layout,
|
||||
@@ -464,7 +466,7 @@ void RendererVulkan::FillScreen(Common::Vec3<u8> color, const TextureInfo& textu
|
||||
},
|
||||
};
|
||||
|
||||
render_manager.EndRendering();
|
||||
renderpass_cache.EndRendering();
|
||||
scheduler.Record([image = texture.image, clear_color](vk::CommandBuffer cmdbuf) {
|
||||
const vk::ImageSubresourceRange range = {
|
||||
.aspectMask = vk::ImageAspectFlagBits::eColor,
|
||||
|
@@ -7,10 +7,11 @@
|
||||
#include "common/common_types.h"
|
||||
#include "common/math_util.h"
|
||||
#include "video_core/renderer_base.h"
|
||||
#include "video_core/renderer_vulkan/vk_descriptor_pool.h"
|
||||
#include "video_core/renderer_vulkan/vk_instance.h"
|
||||
#include "video_core/renderer_vulkan/vk_present_window.h"
|
||||
#include "video_core/renderer_vulkan/vk_rasterizer.h"
|
||||
#include "video_core/renderer_vulkan/vk_render_manager.h"
|
||||
#include "video_core/renderer_vulkan/vk_renderpass_cache.h"
|
||||
#include "video_core/renderer_vulkan/vk_scheduler.h"
|
||||
|
||||
namespace Core {
|
||||
@@ -117,15 +118,15 @@ private:
|
||||
|
||||
Instance instance;
|
||||
Scheduler scheduler;
|
||||
RenderManager render_manager;
|
||||
RenderpassCache renderpass_cache;
|
||||
DescriptorPool pool;
|
||||
PresentWindow main_window;
|
||||
StreamBuffer vertex_buffer;
|
||||
DescriptorUpdateQueue update_queue;
|
||||
RasterizerVulkan rasterizer;
|
||||
std::unique_ptr<PresentWindow> second_window;
|
||||
|
||||
DescriptorHeap present_heap;
|
||||
vk::UniquePipelineLayout present_pipeline_layout;
|
||||
DescriptorSetProvider present_set_provider;
|
||||
std::array<vk::Pipeline, PRESENT_PIPELINES> present_pipelines;
|
||||
std::array<vk::ShaderModule, PRESENT_PIPELINES> present_shaders;
|
||||
std::array<vk::Sampler, 2> present_samplers;
|
||||
@@ -133,6 +134,7 @@ private:
|
||||
u32 current_pipeline = 0;
|
||||
|
||||
std::array<ScreenInfo, 3> screen_infos{};
|
||||
std::array<DescriptorData, 3> present_textures{};
|
||||
PresentUniformData draw_info{};
|
||||
vk::ClearColorValue clear_color{};
|
||||
};
|
||||
|
@@ -4,9 +4,8 @@
|
||||
|
||||
#include "common/vector_math.h"
|
||||
#include "video_core/renderer_vulkan/vk_blit_helper.h"
|
||||
#include "video_core/renderer_vulkan/vk_descriptor_update_queue.h"
|
||||
#include "video_core/renderer_vulkan/vk_instance.h"
|
||||
#include "video_core/renderer_vulkan/vk_render_manager.h"
|
||||
#include "video_core/renderer_vulkan/vk_renderpass_cache.h"
|
||||
#include "video_core/renderer_vulkan/vk_scheduler.h"
|
||||
#include "video_core/renderer_vulkan/vk_shader_util.h"
|
||||
#include "video_core/renderer_vulkan/vk_texture_runtime.h"
|
||||
@@ -178,13 +177,12 @@ constexpr vk::PipelineShaderStageCreateInfo MakeStages(vk::ShaderModule compute_
|
||||
|
||||
} // Anonymous namespace
|
||||
|
||||
BlitHelper::BlitHelper(const Instance& instance_, Scheduler& scheduler_,
|
||||
RenderManager& render_manager_, DescriptorUpdateQueue& update_queue_)
|
||||
: instance{instance_}, scheduler{scheduler_}, render_manager{render_manager_},
|
||||
update_queue{update_queue_}, device{instance.GetDevice()},
|
||||
compute_provider{instance, scheduler.GetMasterSemaphore(), COMPUTE_BINDINGS},
|
||||
compute_buffer_provider{instance, scheduler.GetMasterSemaphore(), COMPUTE_BUFFER_BINDINGS},
|
||||
two_textures_provider{instance, scheduler.GetMasterSemaphore(), TWO_TEXTURES_BINDINGS, 16},
|
||||
BlitHelper::BlitHelper(const Instance& instance_, Scheduler& scheduler_, DescriptorPool& pool,
|
||||
RenderpassCache& renderpass_cache_)
|
||||
: instance{instance_}, scheduler{scheduler_}, renderpass_cache{renderpass_cache_},
|
||||
device{instance.GetDevice()}, compute_provider{instance, pool, COMPUTE_BINDINGS},
|
||||
compute_buffer_provider{instance, pool, COMPUTE_BUFFER_BINDINGS},
|
||||
two_textures_provider{instance, pool, TWO_TEXTURES_BINDINGS},
|
||||
compute_pipeline_layout{
|
||||
device.createPipelineLayout(PipelineLayoutCreateInfo(&compute_provider.Layout(), true))},
|
||||
compute_buffer_pipeline_layout{device.createPipelineLayout(
|
||||
@@ -284,16 +282,27 @@ bool BlitHelper::BlitDepthStencil(Surface& source, Surface& dest,
|
||||
.extent = {dest.GetScaledWidth(), dest.GetScaledHeight()},
|
||||
};
|
||||
|
||||
const auto descriptor_set = two_textures_provider.Commit();
|
||||
update_queue.AddImageSampler(descriptor_set, 0, 0, source.DepthView(), nearest_sampler);
|
||||
update_queue.AddImageSampler(descriptor_set, 1, 0, source.StencilView(), nearest_sampler);
|
||||
std::array<DescriptorData, 2> textures{};
|
||||
textures[0].image_info = vk::DescriptorImageInfo{
|
||||
.sampler = nearest_sampler,
|
||||
.imageView = source.DepthView(),
|
||||
.imageLayout = vk::ImageLayout::eGeneral,
|
||||
};
|
||||
textures[1].image_info = vk::DescriptorImageInfo{
|
||||
.sampler = nearest_sampler,
|
||||
.imageView = source.StencilView(),
|
||||
.imageLayout = vk::ImageLayout::eGeneral,
|
||||
};
|
||||
|
||||
const auto descriptor_set = two_textures_provider.Acquire(textures);
|
||||
|
||||
const RenderPass depth_pass = {
|
||||
.framebuffer = dest.Framebuffer(),
|
||||
.render_pass = render_manager.GetRenderpass(PixelFormat::Invalid, dest.pixel_format, false),
|
||||
.render_pass =
|
||||
renderpass_cache.GetRenderpass(PixelFormat::Invalid, dest.pixel_format, false),
|
||||
.render_area = dst_render_area,
|
||||
};
|
||||
render_manager.BeginRendering(depth_pass);
|
||||
renderpass_cache.BeginRendering(depth_pass);
|
||||
|
||||
scheduler.Record([blit, descriptor_set, this](vk::CommandBuffer cmdbuf) {
|
||||
const vk::PipelineLayout layout = two_textures_pipeline_layout;
|
||||
@@ -309,14 +318,23 @@ bool BlitHelper::BlitDepthStencil(Surface& source, Surface& dest,
|
||||
|
||||
bool BlitHelper::ConvertDS24S8ToRGBA8(Surface& source, Surface& dest,
|
||||
const VideoCore::TextureCopy& copy) {
|
||||
const auto descriptor_set = compute_provider.Commit();
|
||||
update_queue.AddImageSampler(descriptor_set, 0, 0, source.DepthView(), VK_NULL_HANDLE,
|
||||
vk::ImageLayout::eDepthStencilReadOnlyOptimal);
|
||||
update_queue.AddImageSampler(descriptor_set, 1, 0, source.StencilView(), VK_NULL_HANDLE,
|
||||
vk::ImageLayout::eDepthStencilReadOnlyOptimal);
|
||||
update_queue.AddStorageImage(descriptor_set, 2, dest.ImageView());
|
||||
std::array<DescriptorData, 3> textures{};
|
||||
textures[0].image_info = vk::DescriptorImageInfo{
|
||||
.imageView = source.DepthView(),
|
||||
.imageLayout = vk::ImageLayout::eDepthStencilReadOnlyOptimal,
|
||||
};
|
||||
textures[1].image_info = vk::DescriptorImageInfo{
|
||||
.imageView = source.StencilView(),
|
||||
.imageLayout = vk::ImageLayout::eDepthStencilReadOnlyOptimal,
|
||||
};
|
||||
textures[2].image_info = vk::DescriptorImageInfo{
|
||||
.imageView = dest.ImageView(),
|
||||
.imageLayout = vk::ImageLayout::eGeneral,
|
||||
};
|
||||
|
||||
render_manager.EndRendering();
|
||||
const auto descriptor_set = compute_provider.Acquire(textures);
|
||||
|
||||
renderpass_cache.EndRendering();
|
||||
scheduler.Record([this, descriptor_set, copy, src_image = source.Image(),
|
||||
dst_image = dest.Image()](vk::CommandBuffer cmdbuf) {
|
||||
const std::array pre_barriers = {
|
||||
@@ -420,15 +438,26 @@ bool BlitHelper::ConvertDS24S8ToRGBA8(Surface& source, Surface& dest,
|
||||
|
||||
bool BlitHelper::DepthToBuffer(Surface& source, vk::Buffer buffer,
|
||||
const VideoCore::BufferTextureCopy& copy) {
|
||||
const auto descriptor_set = compute_buffer_provider.Commit();
|
||||
update_queue.AddImageSampler(descriptor_set, 0, 0, source.DepthView(), nearest_sampler,
|
||||
vk::ImageLayout::eDepthStencilReadOnlyOptimal);
|
||||
update_queue.AddImageSampler(descriptor_set, 1, 0, source.StencilView(), nearest_sampler,
|
||||
vk::ImageLayout::eDepthStencilReadOnlyOptimal);
|
||||
update_queue.AddBuffer(descriptor_set, 2, buffer, copy.buffer_offset, copy.buffer_size,
|
||||
vk::DescriptorType::eStorageBuffer);
|
||||
std::array<DescriptorData, 3> textures{};
|
||||
textures[0].image_info = vk::DescriptorImageInfo{
|
||||
.sampler = nearest_sampler,
|
||||
.imageView = source.DepthView(),
|
||||
.imageLayout = vk::ImageLayout::eDepthStencilReadOnlyOptimal,
|
||||
};
|
||||
textures[1].image_info = vk::DescriptorImageInfo{
|
||||
.sampler = nearest_sampler,
|
||||
.imageView = source.StencilView(),
|
||||
.imageLayout = vk::ImageLayout::eDepthStencilReadOnlyOptimal,
|
||||
};
|
||||
textures[2].buffer_info = vk::DescriptorBufferInfo{
|
||||
.buffer = buffer,
|
||||
.offset = copy.buffer_offset,
|
||||
.range = copy.buffer_size,
|
||||
};
|
||||
|
||||
render_manager.EndRendering();
|
||||
const auto descriptor_set = compute_buffer_provider.Acquire(textures);
|
||||
|
||||
renderpass_cache.EndRendering();
|
||||
scheduler.Record([this, descriptor_set, copy, src_image = source.Image(),
|
||||
extent = source.RealExtent(false)](vk::CommandBuffer cmdbuf) {
|
||||
const vk::ImageMemoryBarrier pre_barrier = {
|
||||
@@ -514,8 +543,8 @@ vk::Pipeline BlitHelper::MakeDepthStencilBlitPipeline() {
|
||||
}
|
||||
|
||||
const std::array stages = MakeStages(full_screen_vert, blit_depth_stencil_frag);
|
||||
const auto renderpass = render_manager.GetRenderpass(VideoCore::PixelFormat::Invalid,
|
||||
VideoCore::PixelFormat::D24S8, false);
|
||||
const auto renderpass = renderpass_cache.GetRenderpass(VideoCore::PixelFormat::Invalid,
|
||||
VideoCore::PixelFormat::D24S8, false);
|
||||
vk::GraphicsPipelineCreateInfo depth_stencil_info = {
|
||||
.stageCount = static_cast<u32>(stages.size()),
|
||||
.pStages = stages.data(),
|
||||
|
@@ -4,7 +4,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "video_core/renderer_vulkan/vk_resource_pool.h"
|
||||
#include "video_core/renderer_vulkan/vk_descriptor_pool.h"
|
||||
|
||||
namespace VideoCore {
|
||||
struct TextureBlit;
|
||||
@@ -15,17 +15,16 @@ struct BufferTextureCopy;
|
||||
namespace Vulkan {
|
||||
|
||||
class Instance;
|
||||
class RenderManager;
|
||||
class RenderpassCache;
|
||||
class Scheduler;
|
||||
class Surface;
|
||||
class DescriptorUpdateQueue;
|
||||
|
||||
class BlitHelper {
|
||||
friend class TextureRuntime;
|
||||
|
||||
public:
|
||||
explicit BlitHelper(const Instance& instance, Scheduler& scheduler,
|
||||
RenderManager& render_manager, DescriptorUpdateQueue& update_queue);
|
||||
BlitHelper(const Instance& instance, Scheduler& scheduler, DescriptorPool& pool,
|
||||
RenderpassCache& renderpass_cache);
|
||||
~BlitHelper();
|
||||
|
||||
bool BlitDepthStencil(Surface& source, Surface& dest, const VideoCore::TextureBlit& blit);
|
||||
@@ -42,15 +41,14 @@ private:
|
||||
private:
|
||||
const Instance& instance;
|
||||
Scheduler& scheduler;
|
||||
RenderManager& render_manager;
|
||||
DescriptorUpdateQueue& update_queue;
|
||||
RenderpassCache& renderpass_cache;
|
||||
|
||||
vk::Device device;
|
||||
vk::RenderPass r32_renderpass;
|
||||
|
||||
DescriptorHeap compute_provider;
|
||||
DescriptorHeap compute_buffer_provider;
|
||||
DescriptorHeap two_textures_provider;
|
||||
DescriptorSetProvider compute_provider;
|
||||
DescriptorSetProvider compute_buffer_provider;
|
||||
DescriptorSetProvider two_textures_provider;
|
||||
vk::PipelineLayout compute_pipeline_layout;
|
||||
vk::PipelineLayout compute_buffer_pipeline_layout;
|
||||
vk::PipelineLayout two_textures_pipeline_layout;
|
||||
|
@@ -9,6 +9,7 @@
|
||||
#define VK_NO_PROTOTYPES
|
||||
#define VULKAN_HPP_DISPATCH_LOADER_DYNAMIC 1
|
||||
#define VULKAN_HPP_NO_CONSTRUCTORS
|
||||
#define VULKAN_HPP_NO_UNION_CONSTRUCTORS
|
||||
#define VULKAN_HPP_NO_STRUCT_SETTERS
|
||||
#include <vulkan/vulkan.hpp>
|
||||
|
||||
|
141
src/video_core/renderer_vulkan/vk_descriptor_pool.cpp
Normal file
141
src/video_core/renderer_vulkan/vk_descriptor_pool.cpp
Normal file
@@ -0,0 +1,141 @@
|
||||
// Copyright 2023 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "common/microprofile.h"
|
||||
#include "video_core/renderer_vulkan/vk_descriptor_pool.h"
|
||||
#include "video_core/renderer_vulkan/vk_instance.h"
|
||||
|
||||
namespace Vulkan {
|
||||
|
||||
MICROPROFILE_DEFINE(Vulkan_DescriptorSetAcquire, "Vulkan", "Descriptor Set Acquire",
|
||||
MP_RGB(64, 128, 256));
|
||||
|
||||
constexpr u32 MAX_BATCH_SIZE = 8;
|
||||
|
||||
DescriptorPool::DescriptorPool(const Instance& instance_) : instance{instance_} {
|
||||
auto& pool = pools.emplace_back();
|
||||
pool = CreatePool();
|
||||
}
|
||||
|
||||
DescriptorPool::~DescriptorPool() = default;
|
||||
|
||||
std::vector<vk::DescriptorSet> DescriptorPool::Allocate(vk::DescriptorSetLayout layout,
|
||||
u32 num_sets) {
|
||||
std::array<vk::DescriptorSetLayout, MAX_BATCH_SIZE> layouts;
|
||||
layouts.fill(layout);
|
||||
|
||||
u32 current_pool = 0;
|
||||
vk::DescriptorSetAllocateInfo alloc_info = {
|
||||
.descriptorPool = *pools[current_pool],
|
||||
.descriptorSetCount = num_sets,
|
||||
.pSetLayouts = layouts.data(),
|
||||
};
|
||||
|
||||
while (true) {
|
||||
try {
|
||||
return instance.GetDevice().allocateDescriptorSets(alloc_info);
|
||||
} catch (const vk::OutOfPoolMemoryError&) {
|
||||
current_pool++;
|
||||
if (current_pool == pools.size()) {
|
||||
LOG_INFO(Render_Vulkan, "Run out of pools, creating new one!");
|
||||
auto& pool = pools.emplace_back();
|
||||
pool = CreatePool();
|
||||
}
|
||||
alloc_info.descriptorPool = *pools[current_pool];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
vk::DescriptorSet DescriptorPool::Allocate(vk::DescriptorSetLayout layout) {
|
||||
const auto sets = Allocate(layout, 1);
|
||||
return sets[0];
|
||||
}
|
||||
|
||||
vk::UniqueDescriptorPool DescriptorPool::CreatePool() {
|
||||
// Choose a sane pool size good for most games
|
||||
static constexpr std::array<vk::DescriptorPoolSize, 6> pool_sizes = {{
|
||||
{vk::DescriptorType::eUniformBufferDynamic, 64},
|
||||
{vk::DescriptorType::eUniformTexelBuffer, 64},
|
||||
{vk::DescriptorType::eCombinedImageSampler, 4096},
|
||||
{vk::DescriptorType::eSampledImage, 256},
|
||||
{vk::DescriptorType::eStorageImage, 256},
|
||||
{vk::DescriptorType::eStorageBuffer, 32},
|
||||
}};
|
||||
|
||||
const vk::DescriptorPoolCreateInfo descriptor_pool_info = {
|
||||
.maxSets = 4098,
|
||||
.poolSizeCount = static_cast<u32>(pool_sizes.size()),
|
||||
.pPoolSizes = pool_sizes.data(),
|
||||
};
|
||||
|
||||
return instance.GetDevice().createDescriptorPoolUnique(descriptor_pool_info);
|
||||
}
|
||||
|
||||
DescriptorSetProvider::DescriptorSetProvider(
|
||||
const Instance& instance, DescriptorPool& pool_,
|
||||
std::span<const vk::DescriptorSetLayoutBinding> bindings)
|
||||
: pool{pool_}, device{instance.GetDevice()} {
|
||||
std::array<vk::DescriptorUpdateTemplateEntry, MAX_DESCRIPTORS> update_entries;
|
||||
|
||||
for (u32 i = 0; i < bindings.size(); i++) {
|
||||
update_entries[i] = vk::DescriptorUpdateTemplateEntry{
|
||||
.dstBinding = bindings[i].binding,
|
||||
.dstArrayElement = 0,
|
||||
.descriptorCount = bindings[i].descriptorCount,
|
||||
.descriptorType = bindings[i].descriptorType,
|
||||
.offset = i * sizeof(DescriptorData),
|
||||
.stride = sizeof(DescriptorData),
|
||||
};
|
||||
}
|
||||
|
||||
const vk::DescriptorSetLayoutCreateInfo layout_info = {
|
||||
.bindingCount = static_cast<u32>(bindings.size()),
|
||||
.pBindings = bindings.data(),
|
||||
};
|
||||
layout = device.createDescriptorSetLayoutUnique(layout_info);
|
||||
|
||||
const vk::DescriptorUpdateTemplateCreateInfo template_info = {
|
||||
.descriptorUpdateEntryCount = static_cast<u32>(bindings.size()),
|
||||
.pDescriptorUpdateEntries = update_entries.data(),
|
||||
.templateType = vk::DescriptorUpdateTemplateType::eDescriptorSet,
|
||||
.descriptorSetLayout = *layout,
|
||||
};
|
||||
update_template = device.createDescriptorUpdateTemplateUnique(template_info);
|
||||
}
|
||||
|
||||
DescriptorSetProvider::~DescriptorSetProvider() = default;
|
||||
|
||||
vk::DescriptorSet DescriptorSetProvider::Acquire(std::span<const DescriptorData> data) {
|
||||
MICROPROFILE_SCOPE(Vulkan_DescriptorSetAcquire);
|
||||
DescriptorSetData key{};
|
||||
std::memcpy(key.data(), data.data(), data.size_bytes());
|
||||
const auto [it, new_set] = descriptor_set_map.try_emplace(key);
|
||||
if (!new_set) {
|
||||
return it->second;
|
||||
}
|
||||
if (free_sets.empty()) {
|
||||
free_sets = pool.Allocate(*layout, MAX_BATCH_SIZE);
|
||||
}
|
||||
it.value() = free_sets.back();
|
||||
free_sets.pop_back();
|
||||
device.updateDescriptorSetWithTemplate(it->second, *update_template, data[0]);
|
||||
return it->second;
|
||||
}
|
||||
|
||||
void DescriptorSetProvider::FreeWithImage(vk::ImageView image_view) {
|
||||
for (auto it = descriptor_set_map.begin(); it != descriptor_set_map.end();) {
|
||||
const auto& [data, set] = *it;
|
||||
const bool has_image = std::any_of(data.begin(), data.end(), [image_view](auto& info) {
|
||||
return info.image_info.imageView == image_view;
|
||||
});
|
||||
if (has_image) {
|
||||
free_sets.push_back(set);
|
||||
it = descriptor_set_map.erase(it);
|
||||
} else {
|
||||
it++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Vulkan
|
92
src/video_core/renderer_vulkan/vk_descriptor_pool.h
Normal file
92
src/video_core/renderer_vulkan/vk_descriptor_pool.h
Normal file
@@ -0,0 +1,92 @@
|
||||
// Copyright 2023 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <span>
|
||||
#include <vector>
|
||||
#include <tsl/robin_map.h>
|
||||
|
||||
#include "common/hash.h"
|
||||
#include "video_core/renderer_vulkan/vk_common.h"
|
||||
|
||||
namespace Vulkan {
|
||||
|
||||
class Instance;
|
||||
|
||||
constexpr u32 MAX_DESCRIPTORS = 7;
|
||||
|
||||
union DescriptorData {
|
||||
vk::DescriptorImageInfo image_info;
|
||||
vk::DescriptorBufferInfo buffer_info;
|
||||
vk::BufferView buffer_view;
|
||||
|
||||
bool operator==(const DescriptorData& other) const noexcept {
|
||||
return std::memcmp(this, &other, sizeof(DescriptorData)) == 0;
|
||||
}
|
||||
};
|
||||
|
||||
using DescriptorSetData = std::array<DescriptorData, MAX_DESCRIPTORS>;
|
||||
|
||||
struct DataHasher {
|
||||
u64 operator()(const DescriptorSetData& data) const noexcept {
|
||||
return Common::ComputeHash64(data.data(), sizeof(data));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* An interface for allocating descriptor sets that manages a collection of descriptor pools.
|
||||
*/
|
||||
class DescriptorPool {
|
||||
public:
|
||||
explicit DescriptorPool(const Instance& instance);
|
||||
~DescriptorPool();
|
||||
|
||||
std::vector<vk::DescriptorSet> Allocate(vk::DescriptorSetLayout layout, u32 num_sets);
|
||||
|
||||
vk::DescriptorSet Allocate(vk::DescriptorSetLayout layout);
|
||||
|
||||
private:
|
||||
vk::UniqueDescriptorPool CreatePool();
|
||||
|
||||
private:
|
||||
const Instance& instance;
|
||||
std::vector<vk::UniqueDescriptorPool> pools;
|
||||
};
|
||||
|
||||
/**
|
||||
* Allocates and caches descriptor sets of a specific layout.
|
||||
*/
|
||||
class DescriptorSetProvider {
|
||||
public:
|
||||
explicit DescriptorSetProvider(const Instance& instance, DescriptorPool& pool,
|
||||
std::span<const vk::DescriptorSetLayoutBinding> bindings);
|
||||
~DescriptorSetProvider();
|
||||
|
||||
vk::DescriptorSet Acquire(std::span<const DescriptorData> data);
|
||||
|
||||
void FreeWithImage(vk::ImageView image_view);
|
||||
|
||||
[[nodiscard]] vk::DescriptorSetLayout Layout() const noexcept {
|
||||
return *layout;
|
||||
}
|
||||
|
||||
[[nodiscard]] vk::DescriptorSetLayout& Layout() noexcept {
|
||||
return layout.get();
|
||||
}
|
||||
|
||||
[[nodiscard]] vk::DescriptorUpdateTemplate UpdateTemplate() const noexcept {
|
||||
return *update_template;
|
||||
}
|
||||
|
||||
private:
|
||||
DescriptorPool& pool;
|
||||
vk::Device device;
|
||||
vk::UniqueDescriptorSetLayout layout;
|
||||
vk::UniqueDescriptorUpdateTemplate update_template;
|
||||
std::vector<vk::DescriptorSet> free_sets;
|
||||
tsl::robin_map<DescriptorSetData, vk::DescriptorSet, DataHasher> descriptor_set_map;
|
||||
};
|
||||
|
||||
} // namespace Vulkan
|
@@ -1,109 +0,0 @@
|
||||
// Copyright 2024 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "video_core/renderer_vulkan/vk_descriptor_update_queue.h"
|
||||
#include "video_core/renderer_vulkan/vk_instance.h"
|
||||
|
||||
namespace Vulkan {
|
||||
|
||||
DescriptorUpdateQueue::DescriptorUpdateQueue(const Instance& instance, u32 descriptor_write_max_)
|
||||
: device{instance.GetDevice()}, descriptor_write_max{descriptor_write_max_} {
|
||||
descriptor_infos = std::make_unique<DescriptorInfoUnion[]>(descriptor_write_max);
|
||||
descriptor_writes = std::make_unique<vk::WriteDescriptorSet[]>(descriptor_write_max);
|
||||
}
|
||||
|
||||
void DescriptorUpdateQueue::Flush() {
|
||||
if (descriptor_write_end == 0) {
|
||||
return;
|
||||
}
|
||||
device.updateDescriptorSets({std::span(descriptor_writes.get(), descriptor_write_end)}, {});
|
||||
descriptor_write_end = 0;
|
||||
}
|
||||
|
||||
void DescriptorUpdateQueue::AddStorageImage(vk::DescriptorSet target, u8 binding,
|
||||
vk::ImageView image_view,
|
||||
vk::ImageLayout image_layout) {
|
||||
if (descriptor_write_end >= descriptor_write_max) [[unlikely]] {
|
||||
Flush();
|
||||
}
|
||||
|
||||
auto& image_info = descriptor_infos[descriptor_write_end].image_info;
|
||||
image_info.sampler = VK_NULL_HANDLE;
|
||||
image_info.imageView = image_view;
|
||||
image_info.imageLayout = image_layout;
|
||||
|
||||
descriptor_writes[descriptor_write_end++] = vk::WriteDescriptorSet{
|
||||
.dstSet = target,
|
||||
.dstBinding = binding,
|
||||
.dstArrayElement = 0,
|
||||
.descriptorCount = 1,
|
||||
.descriptorType = vk::DescriptorType::eStorageImage,
|
||||
.pImageInfo = &image_info,
|
||||
};
|
||||
}
|
||||
|
||||
void DescriptorUpdateQueue::AddImageSampler(vk::DescriptorSet target, u8 binding, u8 array_index,
|
||||
vk::ImageView image_view, vk::Sampler sampler,
|
||||
vk::ImageLayout image_layout) {
|
||||
if (descriptor_write_end >= descriptor_write_max) [[unlikely]] {
|
||||
Flush();
|
||||
}
|
||||
|
||||
auto& image_info = descriptor_infos[descriptor_write_end].image_info;
|
||||
image_info.sampler = sampler;
|
||||
image_info.imageView = image_view;
|
||||
image_info.imageLayout = image_layout;
|
||||
|
||||
descriptor_writes[descriptor_write_end++] = vk::WriteDescriptorSet{
|
||||
.dstSet = target,
|
||||
.dstBinding = binding,
|
||||
.dstArrayElement = array_index,
|
||||
.descriptorCount = 1,
|
||||
.descriptorType =
|
||||
sampler ? vk::DescriptorType::eCombinedImageSampler : vk::DescriptorType::eSampledImage,
|
||||
.pImageInfo = &image_info,
|
||||
};
|
||||
}
|
||||
|
||||
void DescriptorUpdateQueue::AddBuffer(vk::DescriptorSet target, u8 binding, vk::Buffer buffer,
|
||||
vk::DeviceSize offset, vk::DeviceSize size,
|
||||
vk::DescriptorType type) {
|
||||
if (descriptor_write_end >= descriptor_write_max) [[unlikely]] {
|
||||
Flush();
|
||||
}
|
||||
|
||||
auto& buffer_info = descriptor_infos[descriptor_write_end].buffer_info;
|
||||
buffer_info.buffer = buffer;
|
||||
buffer_info.offset = offset;
|
||||
buffer_info.range = size;
|
||||
|
||||
descriptor_writes[descriptor_write_end++] = vk::WriteDescriptorSet{
|
||||
.dstSet = target,
|
||||
.dstBinding = binding,
|
||||
.dstArrayElement = 0,
|
||||
.descriptorCount = 1,
|
||||
.descriptorType = type,
|
||||
.pBufferInfo = &buffer_info,
|
||||
};
|
||||
}
|
||||
|
||||
void DescriptorUpdateQueue::AddTexelBuffer(vk::DescriptorSet target, u8 binding,
|
||||
vk::BufferView buffer_view) {
|
||||
if (descriptor_write_end >= descriptor_write_max) [[unlikely]] {
|
||||
Flush();
|
||||
}
|
||||
|
||||
auto& buffer_info = descriptor_infos[descriptor_write_end].buffer_view;
|
||||
buffer_info = buffer_view;
|
||||
descriptor_writes[descriptor_write_end++] = vk::WriteDescriptorSet{
|
||||
.dstSet = target,
|
||||
.dstBinding = binding,
|
||||
.dstArrayElement = 0,
|
||||
.descriptorCount = 1,
|
||||
.descriptorType = vk::DescriptorType::eUniformTexelBuffer,
|
||||
.pTexelBufferView = &buffer_info,
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace Vulkan
|
@@ -1,53 +0,0 @@
|
||||
// Copyright 2024 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <memory>
|
||||
#include <variant>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "video_core/renderer_vulkan/vk_common.h"
|
||||
|
||||
namespace Vulkan {
|
||||
|
||||
class Instance;
|
||||
|
||||
struct DescriptorInfoUnion {
|
||||
DescriptorInfoUnion() {}
|
||||
|
||||
union {
|
||||
vk::DescriptorImageInfo image_info;
|
||||
vk::DescriptorBufferInfo buffer_info;
|
||||
vk::BufferView buffer_view;
|
||||
};
|
||||
};
|
||||
|
||||
class DescriptorUpdateQueue {
|
||||
public:
|
||||
explicit DescriptorUpdateQueue(const Instance& instance, u32 descriptor_write_max = 2048);
|
||||
~DescriptorUpdateQueue() = default;
|
||||
|
||||
void Flush();
|
||||
|
||||
void AddStorageImage(vk::DescriptorSet target, u8 binding, vk::ImageView image_view,
|
||||
vk::ImageLayout image_layout = vk::ImageLayout::eGeneral);
|
||||
|
||||
void AddImageSampler(vk::DescriptorSet target, u8 binding, u8 array_index,
|
||||
vk::ImageView image_view, vk::Sampler sampler,
|
||||
vk::ImageLayout imageLayout = vk::ImageLayout::eGeneral);
|
||||
|
||||
void AddBuffer(vk::DescriptorSet target, u8 binding, vk::Buffer buffer, vk::DeviceSize offset,
|
||||
vk::DeviceSize size = VK_WHOLE_SIZE,
|
||||
vk::DescriptorType type = vk::DescriptorType::eUniformBufferDynamic);
|
||||
|
||||
void AddTexelBuffer(vk::DescriptorSet target, u8 binding, vk::BufferView buffer_view);
|
||||
|
||||
private:
|
||||
const vk::Device device;
|
||||
const u32 descriptor_write_max;
|
||||
std::unique_ptr<DescriptorInfoUnion[]> descriptor_infos;
|
||||
std::unique_ptr<vk::WriteDescriptorSet[]> descriptor_writes;
|
||||
u32 descriptor_write_end = 0;
|
||||
};
|
||||
|
||||
} // namespace Vulkan
|
@@ -9,7 +9,7 @@
|
||||
#include "video_core/renderer_vulkan/pica_to_vk.h"
|
||||
#include "video_core/renderer_vulkan/vk_graphics_pipeline.h"
|
||||
#include "video_core/renderer_vulkan/vk_instance.h"
|
||||
#include "video_core/renderer_vulkan/vk_render_manager.h"
|
||||
#include "video_core/renderer_vulkan/vk_renderpass_cache.h"
|
||||
#include "video_core/renderer_vulkan/vk_shader_util.h"
|
||||
|
||||
namespace Vulkan {
|
||||
@@ -64,11 +64,11 @@ Shader::~Shader() {
|
||||
}
|
||||
}
|
||||
|
||||
GraphicsPipeline::GraphicsPipeline(const Instance& instance_, RenderManager& render_manager_,
|
||||
GraphicsPipeline::GraphicsPipeline(const Instance& instance_, RenderpassCache& renderpass_cache_,
|
||||
const PipelineInfo& info_, vk::PipelineCache pipeline_cache_,
|
||||
vk::PipelineLayout layout_, std::array<Shader*, 3> stages_,
|
||||
Common::ThreadWorker* worker_)
|
||||
: instance{instance_}, render_manager{render_manager_}, worker{worker_},
|
||||
: instance{instance_}, renderpass_cache{renderpass_cache_}, worker{worker_},
|
||||
pipeline_layout{layout_}, pipeline_cache{pipeline_cache_}, info{info_}, stages{stages_} {}
|
||||
|
||||
GraphicsPipeline::~GraphicsPipeline() = default;
|
||||
@@ -265,7 +265,7 @@ bool GraphicsPipeline::Build(bool fail_on_compile_required) {
|
||||
.pDynamicState = &dynamic_info,
|
||||
.layout = pipeline_layout,
|
||||
.renderPass =
|
||||
render_manager.GetRenderpass(info.attachments.color, info.attachments.depth, false),
|
||||
renderpass_cache.GetRenderpass(info.attachments.color, info.attachments.depth, false),
|
||||
};
|
||||
|
||||
if (fail_on_compile_required) {
|
||||
|
@@ -40,7 +40,7 @@ private:
|
||||
namespace Vulkan {
|
||||
|
||||
class Instance;
|
||||
class RenderManager;
|
||||
class RenderpassCache;
|
||||
|
||||
constexpr u32 MAX_SHADER_STAGES = 3;
|
||||
constexpr u32 MAX_VERTEX_ATTRIBUTES = 16;
|
||||
@@ -126,7 +126,7 @@ struct AttachmentInfo {
|
||||
};
|
||||
|
||||
/**
|
||||
* Information about a graphics pipeline
|
||||
* Information about a graphics/compute pipeline
|
||||
*/
|
||||
struct PipelineInfo {
|
||||
BlendingState blending;
|
||||
@@ -165,7 +165,7 @@ struct Shader : public Common::AsyncHandle {
|
||||
|
||||
class GraphicsPipeline : public Common::AsyncHandle {
|
||||
public:
|
||||
explicit GraphicsPipeline(const Instance& instance, RenderManager& render_manager,
|
||||
explicit GraphicsPipeline(const Instance& instance, RenderpassCache& renderpass_cache,
|
||||
const PipelineInfo& info, vk::PipelineCache pipeline_cache,
|
||||
vk::PipelineLayout layout, std::array<Shader*, 3> stages,
|
||||
Common::ThreadWorker* worker);
|
||||
@@ -181,7 +181,7 @@ public:
|
||||
|
||||
private:
|
||||
const Instance& instance;
|
||||
RenderManager& render_manager;
|
||||
RenderpassCache& renderpass_cache;
|
||||
Common::ThreadWorker* worker;
|
||||
|
||||
vk::UniquePipeline pipeline;
|
||||
|
@@ -4,7 +4,6 @@
|
||||
|
||||
#include <span>
|
||||
#include <boost/container/static_vector.hpp>
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/settings.h"
|
||||
@@ -154,12 +153,6 @@ Instance::Instance(Core::TelemetrySession& telemetry, Frontend::EmuWindow& windo
|
||||
physical_device = physical_devices[physical_device_index];
|
||||
available_extensions = GetSupportedExtensions(physical_device);
|
||||
properties = physical_device.getProperties();
|
||||
if (properties.apiVersion < TargetVulkanApiVersion) {
|
||||
throw std::runtime_error(fmt::format(
|
||||
"Vulkan {}.{} is required, but only {}.{} is supported by device!",
|
||||
VK_VERSION_MAJOR(TargetVulkanApiVersion), VK_VERSION_MINOR(TargetVulkanApiVersion),
|
||||
VK_VERSION_MAJOR(properties.apiVersion), VK_VERSION_MINOR(properties.apiVersion)));
|
||||
}
|
||||
|
||||
CollectTelemetryParameters(telemetry);
|
||||
CreateDevice();
|
||||
@@ -636,7 +629,7 @@ void Instance::CreateAllocator() {
|
||||
.device = *device,
|
||||
.pVulkanFunctions = &functions,
|
||||
.instance = *instance,
|
||||
.vulkanApiVersion = TargetVulkanApiVersion,
|
||||
.vulkanApiVersion = properties.apiVersion,
|
||||
};
|
||||
|
||||
const VkResult result = vmaCreateAllocator(&allocator_info, &allocator);
|
||||
@@ -677,7 +670,7 @@ void Instance::CollectToolingInfo() {
|
||||
if (!tooling_info) {
|
||||
return;
|
||||
}
|
||||
const auto tools = physical_device.getToolPropertiesEXT();
|
||||
const auto tools = physical_device.getToolProperties();
|
||||
for (const vk::PhysicalDeviceToolProperties& tool : tools) {
|
||||
const std::string_view name = tool.name;
|
||||
LOG_INFO(Render_Vulkan, "Attached debugging tool: {}", name);
|
||||
|
@@ -5,6 +5,7 @@
|
||||
#include <mutex>
|
||||
#include "video_core/renderer_vulkan/vk_instance.h"
|
||||
#include "video_core/renderer_vulkan/vk_master_semaphore.h"
|
||||
#include "video_core/renderer_vulkan/vk_scheduler.h"
|
||||
|
||||
namespace Vulkan {
|
||||
|
||||
@@ -98,7 +99,8 @@ void MasterSemaphoreTimeline::SubmitWork(vk::CommandBuffer cmdbuf, vk::Semaphore
|
||||
try {
|
||||
instance.GetGraphicsQueue().submit(submit_info);
|
||||
} catch (vk::DeviceLostError& err) {
|
||||
UNREACHABLE_MSG("Device lost during submit: {}", err.what());
|
||||
LOG_CRITICAL(Render_Vulkan, "Device lost during submit: {}", err.what());
|
||||
UNREACHABLE();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,21 +109,23 @@ constexpr u64 FENCE_RESERVE = 8;
|
||||
MasterSemaphoreFence::MasterSemaphoreFence(const Instance& instance_) : instance{instance_} {
|
||||
const vk::Device device{instance.GetDevice()};
|
||||
for (u64 i = 0; i < FENCE_RESERVE; i++) {
|
||||
free_queue.push_back(device.createFence({}));
|
||||
free_queue.push(device.createFenceUnique({}));
|
||||
}
|
||||
wait_thread = std::jthread([this](std::stop_token token) { WaitThread(token); });
|
||||
}
|
||||
|
||||
MasterSemaphoreFence::~MasterSemaphoreFence() {
|
||||
std::ranges::for_each(free_queue,
|
||||
[this](auto fence) { instance.GetDevice().destroyFence(fence); });
|
||||
}
|
||||
MasterSemaphoreFence::~MasterSemaphoreFence() = default;
|
||||
|
||||
void MasterSemaphoreFence::Refresh() {}
|
||||
|
||||
void MasterSemaphoreFence::Wait(u64 tick) {
|
||||
std::unique_lock lk{free_mutex};
|
||||
free_cv.wait(lk, [&] { return gpu_tick.load(std::memory_order_relaxed) >= tick; });
|
||||
while (true) {
|
||||
u64 current_value = gpu_tick.load(std::memory_order_relaxed);
|
||||
if (current_value >= tick) {
|
||||
return;
|
||||
}
|
||||
gpu_tick.wait(current_value);
|
||||
}
|
||||
}
|
||||
|
||||
void MasterSemaphoreFence::SubmitWork(vk::CommandBuffer cmdbuf, vk::Semaphore wait,
|
||||
@@ -145,56 +149,59 @@ void MasterSemaphoreFence::SubmitWork(vk::CommandBuffer cmdbuf, vk::Semaphore wa
|
||||
.pSignalSemaphores = &signal,
|
||||
};
|
||||
|
||||
const vk::Fence fence = GetFreeFence();
|
||||
|
||||
vk::UniqueFence fence{GetFreeFence()};
|
||||
try {
|
||||
instance.GetGraphicsQueue().submit(submit_info, fence);
|
||||
instance.GetGraphicsQueue().submit(submit_info, *fence);
|
||||
} catch (vk::DeviceLostError& err) {
|
||||
UNREACHABLE_MSG("Device lost during submit: {}", err.what());
|
||||
LOG_CRITICAL(Render_Vulkan, "Device lost during submit: {}", err.what());
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
std::scoped_lock lock{wait_mutex};
|
||||
wait_queue.emplace(fence, signal_value);
|
||||
wait_queue.push({
|
||||
.handle = std::move(fence),
|
||||
.signal_value = signal_value,
|
||||
});
|
||||
wait_cv.notify_one();
|
||||
}
|
||||
|
||||
void MasterSemaphoreFence::WaitThread(std::stop_token token) {
|
||||
const vk::Device device{instance.GetDevice()};
|
||||
while (!token.stop_requested()) {
|
||||
vk::Fence fence;
|
||||
u64 signal_value;
|
||||
Fence fence;
|
||||
{
|
||||
std::unique_lock lock{wait_mutex};
|
||||
Common::CondvarWait(wait_cv, lock, token, [this] { return !wait_queue.empty(); });
|
||||
if (token.stop_requested()) {
|
||||
return;
|
||||
}
|
||||
std::tie(fence, signal_value) = wait_queue.front();
|
||||
fence = std::move(wait_queue.front());
|
||||
wait_queue.pop();
|
||||
}
|
||||
|
||||
const vk::Result result = device.waitForFences(fence, true, WAIT_TIMEOUT);
|
||||
const vk::Result result = device.waitForFences(*fence.handle, true, WAIT_TIMEOUT);
|
||||
if (result != vk::Result::eSuccess) {
|
||||
UNREACHABLE_MSG("Fence wait failed with error {}", vk::to_string(result));
|
||||
LOG_CRITICAL(Render_Vulkan, "Fence wait failed with error {}", vk::to_string(result));
|
||||
UNREACHABLE();
|
||||
}
|
||||
device.resetFences(*fence.handle);
|
||||
|
||||
device.resetFences(fence);
|
||||
gpu_tick.store(signal_value);
|
||||
gpu_tick.store(fence.signal_value);
|
||||
gpu_tick.notify_all();
|
||||
|
||||
std::scoped_lock lock{free_mutex};
|
||||
free_queue.push_back(fence);
|
||||
free_cv.notify_all();
|
||||
free_queue.push(std::move(fence.handle));
|
||||
}
|
||||
}
|
||||
|
||||
vk::Fence MasterSemaphoreFence::GetFreeFence() {
|
||||
vk::UniqueFence MasterSemaphoreFence::GetFreeFence() {
|
||||
std::scoped_lock lock{free_mutex};
|
||||
if (free_queue.empty()) {
|
||||
return instance.GetDevice().createFence({});
|
||||
return instance.GetDevice().createFenceUnique({});
|
||||
}
|
||||
|
||||
const vk::Fence fence = free_queue.front();
|
||||
free_queue.pop_front();
|
||||
vk::UniqueFence fence{std::move(free_queue.front())};
|
||||
free_queue.pop();
|
||||
return fence;
|
||||
}
|
||||
|
||||
|
@@ -72,8 +72,6 @@ private:
|
||||
};
|
||||
|
||||
class MasterSemaphoreFence : public MasterSemaphore {
|
||||
using Waitable = std::pair<vk::Fence, u64>;
|
||||
|
||||
public:
|
||||
explicit MasterSemaphoreFence(const Instance& instance);
|
||||
~MasterSemaphoreFence() override;
|
||||
@@ -88,15 +86,20 @@ public:
|
||||
private:
|
||||
void WaitThread(std::stop_token token);
|
||||
|
||||
vk::Fence GetFreeFence();
|
||||
vk::UniqueFence GetFreeFence();
|
||||
|
||||
private:
|
||||
const Instance& instance;
|
||||
std::deque<vk::Fence> free_queue;
|
||||
std::queue<Waitable> wait_queue;
|
||||
|
||||
struct Fence {
|
||||
vk::UniqueFence handle;
|
||||
u64 signal_value;
|
||||
};
|
||||
|
||||
std::queue<vk::UniqueFence> free_queue;
|
||||
std::queue<Fence> wait_queue;
|
||||
std::mutex free_mutex;
|
||||
std::mutex wait_mutex;
|
||||
std::condition_variable free_cv;
|
||||
std::condition_variable_any wait_cv;
|
||||
std::jthread wait_thread;
|
||||
};
|
||||
|
@@ -11,10 +11,9 @@
|
||||
#include "common/scope_exit.h"
|
||||
#include "common/settings.h"
|
||||
#include "video_core/renderer_vulkan/pica_to_vk.h"
|
||||
#include "video_core/renderer_vulkan/vk_descriptor_update_queue.h"
|
||||
#include "video_core/renderer_vulkan/vk_instance.h"
|
||||
#include "video_core/renderer_vulkan/vk_pipeline_cache.h"
|
||||
#include "video_core/renderer_vulkan/vk_render_manager.h"
|
||||
#include "video_core/renderer_vulkan/vk_renderpass_cache.h"
|
||||
#include "video_core/renderer_vulkan/vk_scheduler.h"
|
||||
#include "video_core/renderer_vulkan/vk_shader_util.h"
|
||||
#include "video_core/shader/generator/glsl_fs_shader_gen.h"
|
||||
@@ -63,34 +62,34 @@ constexpr std::array<vk::DescriptorSetLayoutBinding, 6> BUFFER_BINDINGS = {{
|
||||
{5, vk::DescriptorType::eUniformTexelBuffer, 1, vk::ShaderStageFlagBits::eFragment},
|
||||
}};
|
||||
|
||||
template <u32 NumTex0>
|
||||
constexpr std::array<vk::DescriptorSetLayoutBinding, 3> TEXTURE_BINDINGS = {{
|
||||
{0, vk::DescriptorType::eCombinedImageSampler, NumTex0,
|
||||
vk::ShaderStageFlagBits::eFragment}, // tex0
|
||||
{1, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment}, // tex1
|
||||
{2, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment}, // tex2
|
||||
{0, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment},
|
||||
{1, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment},
|
||||
{2, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment},
|
||||
}};
|
||||
|
||||
constexpr std::array<vk::DescriptorSetLayoutBinding, 2> UTILITY_BINDINGS = {{
|
||||
{0, vk::DescriptorType::eStorageImage, 1, vk::ShaderStageFlagBits::eFragment}, // shadow_buffer
|
||||
{1, vk::DescriptorType::eCombinedImageSampler, 1,
|
||||
vk::ShaderStageFlagBits::eFragment}, // tex_normal
|
||||
// TODO: Use descriptor array for shadow cube
|
||||
constexpr std::array<vk::DescriptorSetLayoutBinding, 7> SHADOW_BINDINGS = {{
|
||||
{0, vk::DescriptorType::eStorageImage, 1, vk::ShaderStageFlagBits::eFragment},
|
||||
{1, vk::DescriptorType::eStorageImage, 1, vk::ShaderStageFlagBits::eFragment},
|
||||
{2, vk::DescriptorType::eStorageImage, 1, vk::ShaderStageFlagBits::eFragment},
|
||||
{3, vk::DescriptorType::eStorageImage, 1, vk::ShaderStageFlagBits::eFragment},
|
||||
{4, vk::DescriptorType::eStorageImage, 1, vk::ShaderStageFlagBits::eFragment},
|
||||
{5, vk::DescriptorType::eStorageImage, 1, vk::ShaderStageFlagBits::eFragment},
|
||||
{6, vk::DescriptorType::eStorageImage, 1, vk::ShaderStageFlagBits::eFragment},
|
||||
}};
|
||||
|
||||
PipelineCache::PipelineCache(const Instance& instance_, Scheduler& scheduler_,
|
||||
RenderManager& render_manager_, DescriptorUpdateQueue& update_queue_)
|
||||
: instance{instance_}, scheduler{scheduler_}, render_manager{render_manager_},
|
||||
update_queue{update_queue_},
|
||||
num_worker_threads{std::max(std::thread::hardware_concurrency(), 2U) >> 1},
|
||||
RenderpassCache& renderpass_cache_, DescriptorPool& pool_)
|
||||
: instance{instance_}, scheduler{scheduler_}, renderpass_cache{renderpass_cache_}, pool{pool_},
|
||||
num_worker_threads{std::max(std::thread::hardware_concurrency(), 2U)},
|
||||
workers{num_worker_threads, "Pipeline workers"},
|
||||
descriptor_heaps{
|
||||
DescriptorHeap{instance, scheduler.GetMasterSemaphore(), BUFFER_BINDINGS, 32},
|
||||
DescriptorHeap{instance, scheduler.GetMasterSemaphore(), TEXTURE_BINDINGS<1>},
|
||||
DescriptorHeap{instance, scheduler.GetMasterSemaphore(), UTILITY_BINDINGS, 32}},
|
||||
descriptor_set_providers{DescriptorSetProvider{instance, pool, BUFFER_BINDINGS},
|
||||
DescriptorSetProvider{instance, pool, TEXTURE_BINDINGS},
|
||||
DescriptorSetProvider{instance, pool, SHADOW_BINDINGS}},
|
||||
trivial_vertex_shader{
|
||||
instance, vk::ShaderStageFlagBits::eVertex,
|
||||
GLSL::GenerateTrivialVertexShader(instance.IsShaderClipDistanceSupported(), true)} {
|
||||
scheduler.RegisterOnDispatch([this] { update_queue.Flush(); });
|
||||
profile = Pica::Shader::Profile{
|
||||
.has_separable_shaders = true,
|
||||
.has_clip_planes = instance.IsShaderClipDistanceSupported(),
|
||||
@@ -107,13 +106,13 @@ PipelineCache::PipelineCache(const Instance& instance_, Scheduler& scheduler_,
|
||||
}
|
||||
|
||||
void PipelineCache::BuildLayout() {
|
||||
std::array<vk::DescriptorSetLayout, NumRasterizerSets> descriptor_set_layouts;
|
||||
descriptor_set_layouts[0] = descriptor_heaps[0].Layout();
|
||||
descriptor_set_layouts[1] = descriptor_heaps[1].Layout();
|
||||
descriptor_set_layouts[2] = descriptor_heaps[2].Layout();
|
||||
std::array<vk::DescriptorSetLayout, NUM_RASTERIZER_SETS> descriptor_set_layouts;
|
||||
std::transform(descriptor_set_providers.begin(), descriptor_set_providers.end(),
|
||||
descriptor_set_layouts.begin(),
|
||||
[](const auto& provider) { return provider.Layout(); });
|
||||
|
||||
const vk::PipelineLayoutCreateInfo layout_info = {
|
||||
.setLayoutCount = NumRasterizerSets,
|
||||
.setLayoutCount = NUM_RASTERIZER_SETS,
|
||||
.pSetLayouts = descriptor_set_layouts.data(),
|
||||
.pushConstantRangeCount = 0,
|
||||
.pPushConstantRanges = nullptr,
|
||||
@@ -206,7 +205,7 @@ bool PipelineCache::BindPipeline(const PipelineInfo& info, bool wait_built) {
|
||||
auto [it, new_pipeline] = graphics_pipelines.try_emplace(pipeline_hash);
|
||||
if (new_pipeline) {
|
||||
it.value() =
|
||||
std::make_unique<GraphicsPipeline>(instance, render_manager, info, *pipeline_cache,
|
||||
std::make_unique<GraphicsPipeline>(instance, renderpass_cache, info, *pipeline_cache,
|
||||
*pipeline_layout, current_shaders, &workers);
|
||||
}
|
||||
|
||||
@@ -215,11 +214,55 @@ bool PipelineCache::BindPipeline(const PipelineInfo& info, bool wait_built) {
|
||||
return false;
|
||||
}
|
||||
|
||||
u32 new_descriptors_start = 0;
|
||||
std::span<vk::DescriptorSet> new_descriptors_span{};
|
||||
std::span<u32> new_offsets_span{};
|
||||
|
||||
// Ensure all the descriptor sets are set at least once at the beginning.
|
||||
if (scheduler.IsStateDirty(StateFlags::DescriptorSets)) {
|
||||
set_dirty.set();
|
||||
}
|
||||
|
||||
if (set_dirty.any()) {
|
||||
for (u32 i = 0; i < NUM_RASTERIZER_SETS; i++) {
|
||||
if (!set_dirty.test(i)) {
|
||||
continue;
|
||||
}
|
||||
bound_descriptor_sets[i] = descriptor_set_providers[i].Acquire(update_data[i]);
|
||||
}
|
||||
new_descriptors_span = bound_descriptor_sets;
|
||||
|
||||
// Only send new offsets if the buffer descriptor-set changed.
|
||||
if (set_dirty.test(0)) {
|
||||
new_offsets_span = offsets;
|
||||
}
|
||||
|
||||
// Try to compact the number of updated descriptor-set slots to the ones that have actually
|
||||
// changed
|
||||
if (!set_dirty.all()) {
|
||||
const u64 dirty_mask = set_dirty.to_ulong();
|
||||
new_descriptors_start = static_cast<u32>(std::countr_zero(dirty_mask));
|
||||
const u32 new_descriptors_end = 64u - static_cast<u32>(std::countl_zero(dirty_mask));
|
||||
const u32 new_descriptors_size = new_descriptors_end - new_descriptors_start;
|
||||
|
||||
new_descriptors_span =
|
||||
new_descriptors_span.subspan(new_descriptors_start, new_descriptors_size);
|
||||
}
|
||||
|
||||
set_dirty.reset();
|
||||
}
|
||||
|
||||
boost::container::static_vector<vk::DescriptorSet, NUM_RASTERIZER_SETS> new_descriptors(
|
||||
new_descriptors_span.begin(), new_descriptors_span.end());
|
||||
boost::container::static_vector<u32, NUM_DYNAMIC_OFFSETS> new_offsets(new_offsets_span.begin(),
|
||||
new_offsets_span.end());
|
||||
|
||||
const bool is_dirty = scheduler.IsStateDirty(StateFlags::Pipeline);
|
||||
const bool pipeline_dirty = (current_pipeline != pipeline) || is_dirty;
|
||||
scheduler.Record([this, is_dirty, pipeline_dirty, pipeline,
|
||||
current_dynamic = current_info.dynamic, dynamic = info.dynamic,
|
||||
descriptor_sets = bound_descriptor_sets, offsets = offsets,
|
||||
new_descriptors_start, descriptor_sets = std::move(new_descriptors),
|
||||
offsets = std::move(new_offsets),
|
||||
current_rasterization = current_info.rasterization,
|
||||
current_depth_stencil = current_info.depth_stencil,
|
||||
rasterization = info.rasterization,
|
||||
@@ -321,8 +364,10 @@ bool PipelineCache::BindPipeline(const PipelineInfo& info, bool wait_built) {
|
||||
cmdbuf.bindPipeline(vk::PipelineBindPoint::eGraphics, pipeline->Handle());
|
||||
}
|
||||
|
||||
cmdbuf.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, *pipeline_layout, 0,
|
||||
descriptor_sets, offsets);
|
||||
if (descriptor_sets.size()) {
|
||||
cmdbuf.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, *pipeline_layout,
|
||||
new_descriptors_start, descriptor_sets, offsets);
|
||||
}
|
||||
});
|
||||
|
||||
current_info = info;
|
||||
@@ -340,6 +385,7 @@ bool PipelineCache::UseProgrammableVertexShader(const Pica::RegsInternal& regs,
|
||||
// We also don't need the geometry shader if we have the barycentric extension.
|
||||
const bool use_geometry_shader = instance.UseGeometryShaders() && !regs.lighting.disable &&
|
||||
!instance.IsFragmentShaderBarycentricSupported();
|
||||
|
||||
PicaVSConfig config{regs, setup, instance.IsShaderClipDistanceSupported(), use_geometry_shader};
|
||||
|
||||
for (u32 i = 0; i < layout.attribute_count; i++) {
|
||||
@@ -356,7 +402,7 @@ bool PipelineCache::UseProgrammableVertexShader(const Pica::RegsInternal& regs,
|
||||
}
|
||||
}
|
||||
|
||||
const auto [it, new_config] = programmable_vertex_map.try_emplace(config);
|
||||
auto [it, new_config] = programmable_vertex_map.try_emplace(config);
|
||||
if (new_config) {
|
||||
auto program = GLSL::GenerateVertexShader(setup, config, true);
|
||||
if (program.empty()) {
|
||||
@@ -451,6 +497,59 @@ void PipelineCache::UseFragmentShader(const Pica::RegsInternal& regs,
|
||||
shader_hashes[ProgramType::FS] = fs_config.Hash();
|
||||
}
|
||||
|
||||
void PipelineCache::BindTexture(u32 binding, vk::ImageView image_view, vk::Sampler sampler) {
|
||||
auto& info = update_data[1][binding].image_info;
|
||||
if (info.imageView == image_view && info.sampler == sampler) {
|
||||
return;
|
||||
}
|
||||
set_dirty[1] = true;
|
||||
info = vk::DescriptorImageInfo{
|
||||
.sampler = sampler,
|
||||
.imageView = image_view,
|
||||
.imageLayout = vk::ImageLayout::eGeneral,
|
||||
};
|
||||
}
|
||||
|
||||
void PipelineCache::BindStorageImage(u32 binding, vk::ImageView image_view) {
|
||||
auto& info = update_data[2][binding].image_info;
|
||||
if (info.imageView == image_view) {
|
||||
return;
|
||||
}
|
||||
set_dirty[2] = true;
|
||||
info = vk::DescriptorImageInfo{
|
||||
.imageView = image_view,
|
||||
.imageLayout = vk::ImageLayout::eGeneral,
|
||||
};
|
||||
}
|
||||
|
||||
void PipelineCache::BindBuffer(u32 binding, vk::Buffer buffer, u32 offset, u32 size) {
|
||||
auto& info = update_data[0][binding].buffer_info;
|
||||
if (info.buffer == buffer && info.offset == offset && info.range == size) {
|
||||
return;
|
||||
}
|
||||
set_dirty[0] = true;
|
||||
info = vk::DescriptorBufferInfo{
|
||||
.buffer = buffer,
|
||||
.offset = offset,
|
||||
.range = size,
|
||||
};
|
||||
}
|
||||
|
||||
void PipelineCache::BindTexelBuffer(u32 binding, vk::BufferView buffer_view) {
|
||||
auto& view = update_data[0][binding].buffer_view;
|
||||
if (view != buffer_view) {
|
||||
set_dirty[0] = true;
|
||||
view = buffer_view;
|
||||
}
|
||||
}
|
||||
|
||||
void PipelineCache::SetBufferOffset(u32 binding, std::size_t offset) {
|
||||
if (offsets[binding] != static_cast<u32>(offset)) {
|
||||
offsets[binding] = static_cast<u32>(offset);
|
||||
set_dirty[0] = true;
|
||||
}
|
||||
}
|
||||
|
||||
bool PipelineCache::IsCacheValid(std::span<const u8> data) const {
|
||||
if (data.size() < sizeof(vk::PipelineCacheHeaderVersionOne)) {
|
||||
LOG_ERROR(Render_Vulkan, "Pipeline cache failed validation: Invalid header");
|
||||
|
@@ -7,8 +7,8 @@
|
||||
#include <bitset>
|
||||
#include <tsl/robin_map.h>
|
||||
|
||||
#include "video_core/renderer_vulkan/vk_descriptor_pool.h"
|
||||
#include "video_core/renderer_vulkan/vk_graphics_pipeline.h"
|
||||
#include "video_core/renderer_vulkan/vk_resource_pool.h"
|
||||
#include "video_core/shader/generator/pica_fs_config.h"
|
||||
#include "video_core/shader/generator/profile.h"
|
||||
#include "video_core/shader/generator/shader_gen.h"
|
||||
@@ -22,39 +22,23 @@ namespace Vulkan {
|
||||
|
||||
class Instance;
|
||||
class Scheduler;
|
||||
class RenderManager;
|
||||
class DescriptorUpdateQueue;
|
||||
class RenderpassCache;
|
||||
class DescriptorPool;
|
||||
|
||||
enum class DescriptorHeapType : u32 {
|
||||
Buffer,
|
||||
Texture,
|
||||
Utility,
|
||||
};
|
||||
constexpr u32 NUM_RASTERIZER_SETS = 3;
|
||||
constexpr u32 NUM_DYNAMIC_OFFSETS = 3;
|
||||
|
||||
/**
|
||||
* Stores a collection of rasterizer pipelines used during rendering.
|
||||
*/
|
||||
class PipelineCache {
|
||||
static constexpr u32 NumRasterizerSets = 3;
|
||||
static constexpr u32 NumDescriptorHeaps = 3;
|
||||
static constexpr u32 NumDynamicOffsets = 3;
|
||||
|
||||
public:
|
||||
explicit PipelineCache(const Instance& instance, Scheduler& scheduler,
|
||||
RenderManager& render_manager, DescriptorUpdateQueue& update_queue);
|
||||
RenderpassCache& renderpass_cache, DescriptorPool& pool);
|
||||
~PipelineCache();
|
||||
|
||||
/// Acquires and binds a free descriptor set from the appropriate heap.
|
||||
vk::DescriptorSet Acquire(DescriptorHeapType type) {
|
||||
const u32 index = static_cast<u32>(type);
|
||||
const auto descriptor_set = descriptor_heaps[index].Commit();
|
||||
bound_descriptor_sets[index] = descriptor_set;
|
||||
return descriptor_set;
|
||||
}
|
||||
|
||||
/// Sets the dynamic offset for the uniform buffer at binding
|
||||
void UpdateRange(u8 binding, u32 offset) {
|
||||
offsets[binding] = offset;
|
||||
[[nodiscard]] DescriptorSetProvider& TextureProvider() noexcept {
|
||||
return descriptor_set_providers[1];
|
||||
}
|
||||
|
||||
/// Loads the pipeline cache stored to disk
|
||||
@@ -82,6 +66,21 @@ public:
|
||||
/// Binds a fragment shader generated from PICA state
|
||||
void UseFragmentShader(const Pica::RegsInternal& regs, const Pica::Shader::UserConfig& user);
|
||||
|
||||
/// Binds a texture to the specified binding
|
||||
void BindTexture(u32 binding, vk::ImageView image_view, vk::Sampler sampler);
|
||||
|
||||
/// Binds a storage image to the specified binding
|
||||
void BindStorageImage(u32 binding, vk::ImageView image_view);
|
||||
|
||||
/// Binds a buffer to the specified binding
|
||||
void BindBuffer(u32 binding, vk::Buffer buffer, u32 offset, u32 size);
|
||||
|
||||
/// Binds a buffer to the specified binding
|
||||
void BindTexelBuffer(u32 binding, vk::BufferView buffer_view);
|
||||
|
||||
/// Sets the dynamic offset for the uniform buffer at binding
|
||||
void SetBufferOffset(u32 binding, std::size_t offset);
|
||||
|
||||
private:
|
||||
/// Builds the rasterizer pipeline layout
|
||||
void BuildLayout();
|
||||
@@ -98,8 +97,8 @@ private:
|
||||
private:
|
||||
const Instance& instance;
|
||||
Scheduler& scheduler;
|
||||
RenderManager& render_manager;
|
||||
DescriptorUpdateQueue& update_queue;
|
||||
RenderpassCache& renderpass_cache;
|
||||
DescriptorPool& pool;
|
||||
|
||||
Pica::Shader::Profile profile{};
|
||||
vk::UniquePipelineCache pipeline_cache;
|
||||
@@ -111,9 +110,11 @@ private:
|
||||
tsl::robin_map<u64, std::unique_ptr<GraphicsPipeline>, Common::IdentityHash<u64>>
|
||||
graphics_pipelines;
|
||||
|
||||
std::array<DescriptorHeap, NumDescriptorHeaps> descriptor_heaps;
|
||||
std::array<vk::DescriptorSet, NumRasterizerSets> bound_descriptor_sets{};
|
||||
std::array<u32, NumDynamicOffsets> offsets{};
|
||||
std::array<DescriptorSetProvider, NUM_RASTERIZER_SETS> descriptor_set_providers;
|
||||
std::array<DescriptorSetData, NUM_RASTERIZER_SETS> update_data{};
|
||||
std::array<vk::DescriptorSet, NUM_RASTERIZER_SETS> bound_descriptor_sets{};
|
||||
std::array<u32, NUM_DYNAMIC_OFFSETS> offsets{};
|
||||
std::bitset<NUM_RASTERIZER_SETS> set_dirty{};
|
||||
|
||||
std::array<u64, MAX_SHADER_STAGES> shader_hashes;
|
||||
std::array<Shader*, MAX_SHADER_STAGES> current_shaders;
|
||||
|
@@ -17,7 +17,6 @@
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <boost/container/static_vector.hpp>
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/logging/log.h"
|
||||
@@ -32,9 +31,8 @@ static VKAPI_ATTR VkBool32 VKAPI_CALL DebugUtilsCallback(
|
||||
VkDebugUtilsMessageSeverityFlagBitsEXT severity, VkDebugUtilsMessageTypeFlagsEXT type,
|
||||
const VkDebugUtilsMessengerCallbackDataEXT* callback_data, void* user_data) {
|
||||
|
||||
switch (static_cast<u32>(callback_data->messageIdNumber)) {
|
||||
switch (callback_data->messageIdNumber) {
|
||||
case 0x609a13b: // Vertex attribute at location not consumed by shader
|
||||
case 0xc81ad50e:
|
||||
return VK_FALSE;
|
||||
default:
|
||||
break;
|
||||
@@ -292,14 +290,13 @@ vk::UniqueInstance CreateInstance(const Common::DynamicLibrary& library,
|
||||
}
|
||||
VULKAN_HPP_DEFAULT_DISPATCHER.init(vkGetInstanceProcAddr);
|
||||
|
||||
const u32 available_version = VULKAN_HPP_DEFAULT_DISPATCHER.vkEnumerateInstanceVersion
|
||||
? vk::enumerateInstanceVersion()
|
||||
: VK_API_VERSION_1_0;
|
||||
if (available_version < TargetVulkanApiVersion) {
|
||||
throw std::runtime_error(fmt::format(
|
||||
"Vulkan {}.{} is required, but only {}.{} is supported by instance!",
|
||||
VK_VERSION_MAJOR(TargetVulkanApiVersion), VK_VERSION_MINOR(TargetVulkanApiVersion),
|
||||
VK_VERSION_MAJOR(available_version), VK_VERSION_MINOR(available_version)));
|
||||
if (!VULKAN_HPP_DEFAULT_DISPATCHER.vkEnumerateInstanceVersion) {
|
||||
throw std::runtime_error("Vulkan 1.0 is not supported, 1.1 is required!");
|
||||
}
|
||||
|
||||
const u32 available_version = vk::enumerateInstanceVersion();
|
||||
if (available_version < VK_API_VERSION_1_1) {
|
||||
throw std::runtime_error("Vulkan 1.0 is not supported, 1.1 is required!");
|
||||
}
|
||||
|
||||
const auto extensions = GetInstanceExtensions(window_type, enable_validation);
|
||||
@@ -309,7 +306,7 @@ vk::UniqueInstance CreateInstance(const Common::DynamicLibrary& library,
|
||||
.applicationVersion = VK_MAKE_VERSION(1, 0, 0),
|
||||
.pEngineName = "Citra Vulkan",
|
||||
.engineVersion = VK_MAKE_VERSION(1, 0, 0),
|
||||
.apiVersion = TargetVulkanApiVersion,
|
||||
.apiVersion = VK_API_VERSION_1_3,
|
||||
};
|
||||
|
||||
boost::container::static_vector<const char*, 2> layers;
|
||||
|
@@ -19,8 +19,6 @@ enum class WindowSystemType : u8;
|
||||
|
||||
namespace Vulkan {
|
||||
|
||||
constexpr u32 TargetVulkanApiVersion = VK_API_VERSION_1_1;
|
||||
|
||||
using DebugCallback =
|
||||
std::variant<vk::UniqueDebugUtilsMessengerEXT, vk::UniqueDebugReportCallbackEXT>;
|
||||
|
||||
|
@@ -138,11 +138,11 @@ PresentWindow::PresentWindow(Frontend::EmuWindow& emu_window_, const Instance& i
|
||||
|
||||
if (instance.HasDebuggingToolAttached()) {
|
||||
for (u32 i = 0; i < num_images; ++i) {
|
||||
SetObjectName(device, swap_chain[i].cmdbuf, "Swapchain Command Buffer {}", i);
|
||||
SetObjectName(device, swap_chain[i].render_ready,
|
||||
"Swapchain Semaphore: render_ready {}", i);
|
||||
SetObjectName(device, swap_chain[i].present_done, "Swapchain Fence: present_done {}",
|
||||
i);
|
||||
Vulkan::SetObjectName(device, swap_chain[i].cmdbuf, "Swapchain Command Buffer {}", i);
|
||||
Vulkan::SetObjectName(device, swap_chain[i].render_ready,
|
||||
"Swapchain Semaphore: render_ready {}", i);
|
||||
Vulkan::SetObjectName(device, swap_chain[i].present_done,
|
||||
"Swapchain Fence: present_done {}", i);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -20,7 +20,7 @@ namespace Vulkan {
|
||||
class Instance;
|
||||
class Swapchain;
|
||||
class Scheduler;
|
||||
class RenderManager;
|
||||
class RenderpassCache;
|
||||
|
||||
struct Frame {
|
||||
u32 width;
|
||||
|
@@ -58,15 +58,13 @@ RasterizerVulkan::RasterizerVulkan(Memory::MemorySystem& memory, Pica::PicaCore&
|
||||
VideoCore::CustomTexManager& custom_tex_manager,
|
||||
VideoCore::RendererBase& renderer,
|
||||
Frontend::EmuWindow& emu_window, const Instance& instance,
|
||||
Scheduler& scheduler, RenderManager& render_manager,
|
||||
DescriptorUpdateQueue& update_queue_, u32 image_count)
|
||||
Scheduler& scheduler, DescriptorPool& pool,
|
||||
RenderpassCache& renderpass_cache, u32 image_count)
|
||||
: RasterizerAccelerated{memory, pica}, instance{instance}, scheduler{scheduler},
|
||||
render_manager{render_manager}, update_queue{update_queue_},
|
||||
pipeline_cache{instance, scheduler, render_manager, update_queue}, runtime{instance,
|
||||
scheduler,
|
||||
render_manager,
|
||||
update_queue,
|
||||
image_count},
|
||||
renderpass_cache{renderpass_cache}, pipeline_cache{instance, scheduler, renderpass_cache,
|
||||
pool},
|
||||
runtime{instance, scheduler, renderpass_cache, pool, pipeline_cache.TextureProvider(),
|
||||
image_count},
|
||||
res_cache{memory, custom_tex_manager, runtime, regs, renderer},
|
||||
stream_buffer{instance, scheduler, BUFFER_USAGE, STREAM_BUFFER_SIZE},
|
||||
uniform_buffer{instance, scheduler, vk::BufferUsageFlagBits::eUniformBuffer,
|
||||
@@ -79,12 +77,11 @@ RasterizerVulkan::RasterizerVulkan(Memory::MemorySystem& memory, Pica::PicaCore&
|
||||
|
||||
vertex_buffers.fill(stream_buffer.Handle());
|
||||
|
||||
// Query uniform buffer alignment.
|
||||
uniform_buffer_alignment = instance.UniformMinAlignment();
|
||||
uniform_size_aligned_vs_pica =
|
||||
Common::AlignUp<u32>(sizeof(VSPicaUniformData), uniform_buffer_alignment);
|
||||
uniform_size_aligned_vs = Common::AlignUp<u32>(sizeof(VSUniformData), uniform_buffer_alignment);
|
||||
uniform_size_aligned_fs = Common::AlignUp<u32>(sizeof(FSUniformData), uniform_buffer_alignment);
|
||||
Common::AlignUp(sizeof(VSPicaUniformData), uniform_buffer_alignment);
|
||||
uniform_size_aligned_vs = Common::AlignUp(sizeof(VSUniformData), uniform_buffer_alignment);
|
||||
uniform_size_aligned_fs = Common::AlignUp(sizeof(FSUniformData), uniform_buffer_alignment);
|
||||
|
||||
// Define vertex layout for software shaders
|
||||
MakeSoftwareVertexLayout();
|
||||
@@ -110,32 +107,24 @@ RasterizerVulkan::RasterizerVulkan(Memory::MemorySystem& memory, Pica::PicaCore&
|
||||
.range = VK_WHOLE_SIZE,
|
||||
});
|
||||
|
||||
scheduler.RegisterOnSubmit([&render_manager] { render_manager.EndRendering(); });
|
||||
// Since we don't have access to VK_EXT_descriptor_indexing we need to intiallize
|
||||
// all descriptor sets even the ones we don't use.
|
||||
pipeline_cache.BindBuffer(0, uniform_buffer.Handle(), 0, sizeof(VSPicaUniformData));
|
||||
pipeline_cache.BindBuffer(1, uniform_buffer.Handle(), 0, sizeof(VSUniformData));
|
||||
pipeline_cache.BindBuffer(2, uniform_buffer.Handle(), 0, sizeof(FSUniformData));
|
||||
pipeline_cache.BindTexelBuffer(3, *texture_lf_view);
|
||||
pipeline_cache.BindTexelBuffer(4, *texture_rg_view);
|
||||
pipeline_cache.BindTexelBuffer(5, *texture_rgba_view);
|
||||
|
||||
// Prepare the static buffer descriptor set.
|
||||
const auto buffer_set = pipeline_cache.Acquire(DescriptorHeapType::Buffer);
|
||||
update_queue.AddBuffer(buffer_set, 0, uniform_buffer.Handle(), 0, sizeof(VSPicaUniformData));
|
||||
update_queue.AddBuffer(buffer_set, 1, uniform_buffer.Handle(), 0, sizeof(VSUniformData));
|
||||
update_queue.AddBuffer(buffer_set, 2, uniform_buffer.Handle(), 0, sizeof(FSUniformData));
|
||||
update_queue.AddTexelBuffer(buffer_set, 3, *texture_lf_view);
|
||||
update_queue.AddTexelBuffer(buffer_set, 4, *texture_rg_view);
|
||||
update_queue.AddTexelBuffer(buffer_set, 5, *texture_rgba_view);
|
||||
|
||||
const auto texture_set = pipeline_cache.Acquire(DescriptorHeapType::Texture);
|
||||
Surface& null_surface = res_cache.GetSurface(VideoCore::NULL_SURFACE_ID);
|
||||
Sampler& null_sampler = res_cache.GetSampler(VideoCore::NULL_SAMPLER_ID);
|
||||
|
||||
// Prepare texture and utility descriptor sets.
|
||||
for (u32 i = 0; i < 3; i++) {
|
||||
update_queue.AddImageSampler(texture_set, i, 0, null_surface.ImageView(),
|
||||
null_sampler.Handle());
|
||||
pipeline_cache.BindTexture(i, null_surface.ImageView(), null_sampler.Handle());
|
||||
}
|
||||
|
||||
const auto utility_set = pipeline_cache.Acquire(DescriptorHeapType::Utility);
|
||||
update_queue.AddStorageImage(utility_set, 0, null_surface.StorageView());
|
||||
update_queue.AddImageSampler(utility_set, 1, 0, null_surface.ImageView(),
|
||||
null_sampler.Handle());
|
||||
update_queue.Flush();
|
||||
for (u32 i = 0; i < 7; i++) {
|
||||
pipeline_cache.BindStorageImage(i, null_surface.StorageView());
|
||||
}
|
||||
|
||||
SyncEntireState();
|
||||
}
|
||||
@@ -488,6 +477,13 @@ bool RasterizerVulkan::Draw(bool accelerate, bool is_indexed) {
|
||||
pipeline_info.attachments.color = framebuffer->Format(SurfaceType::Color);
|
||||
pipeline_info.attachments.depth = framebuffer->Format(SurfaceType::Depth);
|
||||
|
||||
if (shadow_rendering) {
|
||||
pipeline_cache.BindStorageImage(6, framebuffer->ImageView(SurfaceType::Color));
|
||||
} else {
|
||||
Surface& null_surface = res_cache.GetSurface(VideoCore::NULL_SURFACE_ID);
|
||||
pipeline_cache.BindStorageImage(6, null_surface.StorageView());
|
||||
}
|
||||
|
||||
// Update scissor uniforms
|
||||
const auto [scissor_x1, scissor_y2, scissor_x2, scissor_y1] = fb_helper.Scissor();
|
||||
if (fs_uniform_block_data.data.scissor_x1 != scissor_x1 ||
|
||||
@@ -504,7 +500,6 @@ bool RasterizerVulkan::Draw(bool accelerate, bool is_indexed) {
|
||||
|
||||
// Sync and bind the texture surfaces
|
||||
SyncTextureUnits(framebuffer);
|
||||
SyncUtilityTextures(framebuffer);
|
||||
|
||||
// Sync and bind the shader
|
||||
if (shader_dirty) {
|
||||
@@ -519,7 +514,7 @@ bool RasterizerVulkan::Draw(bool accelerate, bool is_indexed) {
|
||||
|
||||
// Begin rendering
|
||||
const auto draw_rect = fb_helper.DrawRect();
|
||||
render_manager.BeginRendering(framebuffer, draw_rect);
|
||||
renderpass_cache.BeginRendering(framebuffer, draw_rect);
|
||||
|
||||
// Configure viewport and scissor
|
||||
const auto viewport = fb_helper.Viewport();
|
||||
@@ -538,8 +533,8 @@ bool RasterizerVulkan::Draw(bool accelerate, bool is_indexed) {
|
||||
} else {
|
||||
pipeline_cache.BindPipeline(pipeline_info, true);
|
||||
|
||||
const u64 vertex_size = vertex_batch.size() * sizeof(HardwareVertex);
|
||||
const u32 vertex_count = static_cast<u32>(vertex_batch.size());
|
||||
const u32 vertex_size = vertex_count * sizeof(HardwareVertex);
|
||||
const auto [buffer, offset, _] = stream_buffer.Map(vertex_size, sizeof(HardwareVertex));
|
||||
|
||||
std::memcpy(buffer, vertex_batch.data(), vertex_size);
|
||||
@@ -559,11 +554,6 @@ void RasterizerVulkan::SyncTextureUnits(const Framebuffer* framebuffer) {
|
||||
using TextureType = Pica::TexturingRegs::TextureConfig::TextureType;
|
||||
|
||||
const auto pica_textures = regs.texturing.GetTextures();
|
||||
const bool use_cube_heap =
|
||||
pica_textures[0].enabled && pica_textures[0].config.type == TextureType::ShadowCube;
|
||||
const auto texture_set = pipeline_cache.Acquire(use_cube_heap ? DescriptorHeapType::Texture
|
||||
: DescriptorHeapType::Texture);
|
||||
|
||||
for (u32 texture_index = 0; texture_index < pica_textures.size(); ++texture_index) {
|
||||
const auto& texture = pica_textures[texture_index];
|
||||
|
||||
@@ -571,8 +561,8 @@ void RasterizerVulkan::SyncTextureUnits(const Framebuffer* framebuffer) {
|
||||
if (!texture.enabled) {
|
||||
const Surface& null_surface = res_cache.GetSurface(VideoCore::NULL_SURFACE_ID);
|
||||
const Sampler& null_sampler = res_cache.GetSampler(VideoCore::NULL_SAMPLER_ID);
|
||||
update_queue.AddImageSampler(texture_set, texture_index, 0, null_surface.ImageView(),
|
||||
null_sampler.Handle());
|
||||
pipeline_cache.BindTexture(texture_index, null_surface.ImageView(),
|
||||
null_sampler.Handle());
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -581,21 +571,20 @@ void RasterizerVulkan::SyncTextureUnits(const Framebuffer* framebuffer) {
|
||||
switch (texture.config.type.Value()) {
|
||||
case TextureType::Shadow2D: {
|
||||
Surface& surface = res_cache.GetTextureSurface(texture);
|
||||
Sampler& sampler = res_cache.GetSampler(texture.config);
|
||||
surface.flags |= VideoCore::SurfaceFlagBits::ShadowMap;
|
||||
update_queue.AddImageSampler(texture_set, texture_index, 0, surface.StorageView(),
|
||||
sampler.Handle());
|
||||
pipeline_cache.BindStorageImage(0, surface.StorageView());
|
||||
continue;
|
||||
}
|
||||
case TextureType::ShadowCube: {
|
||||
BindShadowCube(texture, texture_set);
|
||||
BindShadowCube(texture);
|
||||
continue;
|
||||
}
|
||||
case TextureType::TextureCube: {
|
||||
BindTextureCube(texture, texture_set);
|
||||
BindTextureCube(texture);
|
||||
continue;
|
||||
}
|
||||
default:
|
||||
UnbindSpecial();
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -603,26 +592,13 @@ void RasterizerVulkan::SyncTextureUnits(const Framebuffer* framebuffer) {
|
||||
// Bind the texture provided by the rasterizer cache
|
||||
Surface& surface = res_cache.GetTextureSurface(texture);
|
||||
Sampler& sampler = res_cache.GetSampler(texture.config);
|
||||
const vk::ImageView color_view = framebuffer->ImageView(SurfaceType::Color);
|
||||
const bool is_feedback_loop = color_view == surface.ImageView();
|
||||
const vk::ImageView texture_view =
|
||||
is_feedback_loop ? surface.CopyImageView() : surface.ImageView();
|
||||
update_queue.AddImageSampler(texture_set, texture_index, 0, texture_view, sampler.Handle());
|
||||
if (!IsFeedbackLoop(texture_index, framebuffer, surface, sampler)) {
|
||||
pipeline_cache.BindTexture(texture_index, surface.ImageView(), sampler.Handle());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RasterizerVulkan::SyncUtilityTextures(const Framebuffer* framebuffer) {
|
||||
const bool shadow_rendering = regs.framebuffer.IsShadowRendering();
|
||||
if (!shadow_rendering) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto utility_set = pipeline_cache.Acquire(DescriptorHeapType::Utility);
|
||||
update_queue.AddStorageImage(utility_set, 0, framebuffer->ImageView(SurfaceType::Color));
|
||||
}
|
||||
|
||||
void RasterizerVulkan::BindShadowCube(const Pica::TexturingRegs::FullTextureConfig& texture,
|
||||
vk::DescriptorSet texture_set) {
|
||||
void RasterizerVulkan::BindShadowCube(const Pica::TexturingRegs::FullTextureConfig& texture) {
|
||||
using CubeFace = Pica::TexturingRegs::CubeFace;
|
||||
auto info = Pica::Texture::TextureInfo::FromPicaRegister(texture.config, texture.format);
|
||||
constexpr std::array faces = {
|
||||
@@ -630,8 +606,6 @@ void RasterizerVulkan::BindShadowCube(const Pica::TexturingRegs::FullTextureConf
|
||||
CubeFace::NegativeY, CubeFace::PositiveZ, CubeFace::NegativeZ,
|
||||
};
|
||||
|
||||
Sampler& sampler = res_cache.GetSampler(texture.config);
|
||||
|
||||
for (CubeFace face : faces) {
|
||||
const u32 binding = static_cast<u32>(face);
|
||||
info.physical_address = regs.texturing.GetCubePhysicalAddress(face);
|
||||
@@ -639,13 +613,11 @@ void RasterizerVulkan::BindShadowCube(const Pica::TexturingRegs::FullTextureConf
|
||||
const VideoCore::SurfaceId surface_id = res_cache.GetTextureSurface(info);
|
||||
Surface& surface = res_cache.GetSurface(surface_id);
|
||||
surface.flags |= VideoCore::SurfaceFlagBits::ShadowMap;
|
||||
update_queue.AddImageSampler(texture_set, 0, binding, surface.StorageView(),
|
||||
sampler.Handle());
|
||||
pipeline_cache.BindStorageImage(binding, surface.StorageView());
|
||||
}
|
||||
}
|
||||
|
||||
void RasterizerVulkan::BindTextureCube(const Pica::TexturingRegs::FullTextureConfig& texture,
|
||||
vk::DescriptorSet texture_set) {
|
||||
void RasterizerVulkan::BindTextureCube(const Pica::TexturingRegs::FullTextureConfig& texture) {
|
||||
using CubeFace = Pica::TexturingRegs::CubeFace;
|
||||
const VideoCore::TextureCubeConfig config = {
|
||||
.px = regs.texturing.GetCubePhysicalAddress(CubeFace::PositiveX),
|
||||
@@ -661,7 +633,27 @@ void RasterizerVulkan::BindTextureCube(const Pica::TexturingRegs::FullTextureCon
|
||||
|
||||
Surface& surface = res_cache.GetTextureCube(config);
|
||||
Sampler& sampler = res_cache.GetSampler(texture.config);
|
||||
update_queue.AddImageSampler(texture_set, 0, 0, surface.ImageView(), sampler.Handle());
|
||||
pipeline_cache.BindTexture(0, surface.ImageView(), sampler.Handle());
|
||||
}
|
||||
|
||||
bool RasterizerVulkan::IsFeedbackLoop(u32 texture_index, const Framebuffer* framebuffer,
|
||||
Surface& surface, Sampler& sampler) {
|
||||
const vk::ImageView color_view = framebuffer->ImageView(SurfaceType::Color);
|
||||
const bool is_feedback_loop = color_view == surface.ImageView();
|
||||
if (!is_feedback_loop) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Make a temporary copy of the framebuffer to sample from
|
||||
pipeline_cache.BindTexture(texture_index, surface.CopyImageView(), sampler.Handle());
|
||||
return true;
|
||||
}
|
||||
|
||||
void RasterizerVulkan::UnbindSpecial() {
|
||||
Surface& null_surface = res_cache.GetSurface(VideoCore::NULL_SURFACE_ID);
|
||||
for (u32 i = 0; i < 6; i++) {
|
||||
pipeline_cache.BindStorageImage(i, null_surface.StorageView());
|
||||
}
|
||||
}
|
||||
|
||||
void RasterizerVulkan::NotifyFixedFunctionPicaRegisterChanged(u32 id) {
|
||||
@@ -1099,7 +1091,7 @@ void RasterizerVulkan::UploadUniforms(bool accelerate_draw) {
|
||||
return;
|
||||
}
|
||||
|
||||
const u32 uniform_size =
|
||||
const u64 uniform_size =
|
||||
uniform_size_aligned_vs_pica + uniform_size_aligned_vs + uniform_size_aligned_fs;
|
||||
auto [uniforms, offset, invalidate] =
|
||||
uniform_buffer.Map(uniform_size, uniform_buffer_alignment);
|
||||
@@ -1110,18 +1102,18 @@ void RasterizerVulkan::UploadUniforms(bool accelerate_draw) {
|
||||
std::memcpy(uniforms + used_bytes, &vs_uniform_block_data.data,
|
||||
sizeof(vs_uniform_block_data.data));
|
||||
|
||||
pipeline_cache.UpdateRange(1, offset + used_bytes);
|
||||
pipeline_cache.SetBufferOffset(1, offset + used_bytes);
|
||||
vs_uniform_block_data.dirty = false;
|
||||
used_bytes += uniform_size_aligned_vs;
|
||||
used_bytes += static_cast<u32>(uniform_size_aligned_vs);
|
||||
}
|
||||
|
||||
if (sync_fs || invalidate) {
|
||||
std::memcpy(uniforms + used_bytes, &fs_uniform_block_data.data,
|
||||
sizeof(fs_uniform_block_data.data));
|
||||
|
||||
pipeline_cache.UpdateRange(2, offset + used_bytes);
|
||||
pipeline_cache.SetBufferOffset(2, offset + used_bytes);
|
||||
fs_uniform_block_data.dirty = false;
|
||||
used_bytes += uniform_size_aligned_fs;
|
||||
used_bytes += static_cast<u32>(uniform_size_aligned_fs);
|
||||
}
|
||||
|
||||
if (sync_vs_pica) {
|
||||
@@ -1129,8 +1121,8 @@ void RasterizerVulkan::UploadUniforms(bool accelerate_draw) {
|
||||
vs_uniforms.uniforms.SetFromRegs(regs.vs, pica.vs_setup);
|
||||
std::memcpy(uniforms + used_bytes, &vs_uniforms, sizeof(vs_uniforms));
|
||||
|
||||
pipeline_cache.UpdateRange(0, offset + used_bytes);
|
||||
used_bytes += uniform_size_aligned_vs_pica;
|
||||
pipeline_cache.SetBufferOffset(0, offset + used_bytes);
|
||||
used_bytes += static_cast<u32>(uniform_size_aligned_vs_pica);
|
||||
}
|
||||
|
||||
uniform_buffer.Commit(used_bytes);
|
||||
|
@@ -5,9 +5,8 @@
|
||||
#pragma once
|
||||
|
||||
#include "video_core/rasterizer_accelerated.h"
|
||||
#include "video_core/renderer_vulkan/vk_descriptor_update_queue.h"
|
||||
#include "video_core/renderer_vulkan/vk_pipeline_cache.h"
|
||||
#include "video_core/renderer_vulkan/vk_render_manager.h"
|
||||
#include "video_core/renderer_vulkan/vk_renderpass_cache.h"
|
||||
#include "video_core/renderer_vulkan/vk_stream_buffer.h"
|
||||
#include "video_core/renderer_vulkan/vk_texture_runtime.h"
|
||||
|
||||
@@ -32,16 +31,16 @@ struct ScreenInfo;
|
||||
|
||||
class Instance;
|
||||
class Scheduler;
|
||||
class RenderManager;
|
||||
class RenderpassCache;
|
||||
class DescriptorPool;
|
||||
|
||||
class RasterizerVulkan : public VideoCore::RasterizerAccelerated {
|
||||
public:
|
||||
explicit RasterizerVulkan(Memory::MemorySystem& memory, Pica::PicaCore& pica,
|
||||
VideoCore::CustomTexManager& custom_tex_manager,
|
||||
VideoCore::RendererBase& renderer, Frontend::EmuWindow& emu_window,
|
||||
const Instance& instance, Scheduler& scheduler,
|
||||
RenderManager& render_manager, DescriptorUpdateQueue& update_queue,
|
||||
u32 image_count);
|
||||
const Instance& instance, Scheduler& scheduler, DescriptorPool& pool,
|
||||
RenderpassCache& renderpass_cache, u32 image_count);
|
||||
~RasterizerVulkan() override;
|
||||
|
||||
void TickFrame();
|
||||
@@ -103,16 +102,18 @@ private:
|
||||
/// Syncs all enabled PICA texture units
|
||||
void SyncTextureUnits(const Framebuffer* framebuffer);
|
||||
|
||||
/// Syncs all utility textures in the fragment shader.
|
||||
void SyncUtilityTextures(const Framebuffer* framebuffer);
|
||||
|
||||
/// Binds the PICA shadow cube required for shadow mapping
|
||||
void BindShadowCube(const Pica::TexturingRegs::FullTextureConfig& texture,
|
||||
vk::DescriptorSet texture_set);
|
||||
void BindShadowCube(const Pica::TexturingRegs::FullTextureConfig& texture);
|
||||
|
||||
/// Binds a texture cube to texture unit 0
|
||||
void BindTextureCube(const Pica::TexturingRegs::FullTextureConfig& texture,
|
||||
vk::DescriptorSet texture_set);
|
||||
void BindTextureCube(const Pica::TexturingRegs::FullTextureConfig& texture);
|
||||
|
||||
/// Makes a temporary copy of the framebuffer if a feedback loop is detected
|
||||
bool IsFeedbackLoop(u32 texture_index, const Framebuffer* framebuffer, Surface& surface,
|
||||
Sampler& sampler);
|
||||
|
||||
/// Unbinds all special texture unit 0 texture configurations
|
||||
void UnbindSpecial();
|
||||
|
||||
/// Upload the uniform blocks to the uniform buffer object
|
||||
void UploadUniforms(bool accelerate_draw);
|
||||
@@ -144,8 +145,7 @@ private:
|
||||
private:
|
||||
const Instance& instance;
|
||||
Scheduler& scheduler;
|
||||
RenderManager& render_manager;
|
||||
DescriptorUpdateQueue& update_queue;
|
||||
RenderpassCache& renderpass_cache;
|
||||
PipelineCache pipeline_cache;
|
||||
TextureRuntime runtime;
|
||||
RasterizerCache res_cache;
|
||||
@@ -164,10 +164,10 @@ private:
|
||||
vk::UniqueBufferView texture_lf_view;
|
||||
vk::UniqueBufferView texture_rg_view;
|
||||
vk::UniqueBufferView texture_rgba_view;
|
||||
vk::DeviceSize uniform_buffer_alignment;
|
||||
u32 uniform_size_aligned_vs_pica;
|
||||
u32 uniform_size_aligned_vs;
|
||||
u32 uniform_size_aligned_fs;
|
||||
u64 uniform_buffer_alignment;
|
||||
u64 uniform_size_aligned_vs_pica;
|
||||
u64 uniform_size_aligned_vs;
|
||||
u64 uniform_size_aligned_fs;
|
||||
bool async_shaders{false};
|
||||
};
|
||||
|
||||
|
@@ -1,4 +1,4 @@
|
||||
// Copyright 2024 Citra Emulator Project
|
||||
// Copyright 2023 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
#include "common/assert.h"
|
||||
#include "video_core/rasterizer_cache/pixel_format.h"
|
||||
#include "video_core/renderer_vulkan/vk_instance.h"
|
||||
#include "video_core/renderer_vulkan/vk_render_manager.h"
|
||||
#include "video_core/renderer_vulkan/vk_renderpass_cache.h"
|
||||
#include "video_core/renderer_vulkan/vk_scheduler.h"
|
||||
#include "video_core/renderer_vulkan/vk_texture_runtime.h"
|
||||
|
||||
@@ -17,13 +17,13 @@ constexpr u32 MIN_DRAWS_TO_FLUSH = 20;
|
||||
using VideoCore::PixelFormat;
|
||||
using VideoCore::SurfaceType;
|
||||
|
||||
RenderManager::RenderManager(const Instance& instance, Scheduler& scheduler)
|
||||
RenderpassCache::RenderpassCache(const Instance& instance, Scheduler& scheduler)
|
||||
: instance{instance}, scheduler{scheduler} {}
|
||||
|
||||
RenderManager::~RenderManager() = default;
|
||||
RenderpassCache::~RenderpassCache() = default;
|
||||
|
||||
void RenderManager::BeginRendering(const Framebuffer* framebuffer,
|
||||
Common::Rectangle<u32> draw_rect) {
|
||||
void RenderpassCache::BeginRendering(const Framebuffer* framebuffer,
|
||||
Common::Rectangle<u32> draw_rect) {
|
||||
const vk::Rect2D render_area = {
|
||||
.offset{
|
||||
.x = static_cast<s32>(draw_rect.left),
|
||||
@@ -46,7 +46,7 @@ void RenderManager::BeginRendering(const Framebuffer* framebuffer,
|
||||
BeginRendering(new_pass);
|
||||
}
|
||||
|
||||
void RenderManager::BeginRendering(const RenderPass& new_pass) {
|
||||
void RenderpassCache::BeginRendering(const RenderPass& new_pass) {
|
||||
if (pass == new_pass) [[likely]] {
|
||||
num_draws++;
|
||||
return;
|
||||
@@ -67,11 +67,12 @@ void RenderManager::BeginRendering(const RenderPass& new_pass) {
|
||||
pass = new_pass;
|
||||
}
|
||||
|
||||
void RenderManager::EndRendering() {
|
||||
void RenderpassCache::EndRendering() {
|
||||
if (!pass.render_pass) {
|
||||
return;
|
||||
}
|
||||
|
||||
pass.render_pass = vk::RenderPass{};
|
||||
scheduler.Record([images = images, aspects = aspects](vk::CommandBuffer cmdbuf) {
|
||||
u32 num_barriers = 0;
|
||||
vk::PipelineStageFlags pipeline_flags{};
|
||||
@@ -107,9 +108,6 @@ void RenderManager::EndRendering() {
|
||||
};
|
||||
}
|
||||
cmdbuf.endRenderPass();
|
||||
if (num_barriers == 0) {
|
||||
return;
|
||||
}
|
||||
cmdbuf.pipelineBarrier(pipeline_flags,
|
||||
vk::PipelineStageFlagBits::eFragmentShader |
|
||||
vk::PipelineStageFlagBits::eTransfer,
|
||||
@@ -117,11 +115,6 @@ void RenderManager::EndRendering() {
|
||||
num_barriers, barriers.data());
|
||||
});
|
||||
|
||||
// Reset state.
|
||||
pass.render_pass = vk::RenderPass{};
|
||||
images = {};
|
||||
aspects = {};
|
||||
|
||||
// The Mali guide recommends flushing at the end of each major renderpass
|
||||
// Testing has shown this has a significant effect on rendering performance
|
||||
if (num_draws > MIN_DRAWS_TO_FLUSH && instance.ShouldFlush()) {
|
||||
@@ -130,8 +123,8 @@ void RenderManager::EndRendering() {
|
||||
}
|
||||
}
|
||||
|
||||
vk::RenderPass RenderManager::GetRenderpass(VideoCore::PixelFormat color,
|
||||
VideoCore::PixelFormat depth, bool is_clear) {
|
||||
vk::RenderPass RenderpassCache::GetRenderpass(VideoCore::PixelFormat color,
|
||||
VideoCore::PixelFormat depth, bool is_clear) {
|
||||
std::scoped_lock lock{cache_mutex};
|
||||
|
||||
const u32 color_index =
|
||||
@@ -155,8 +148,8 @@ vk::RenderPass RenderManager::GetRenderpass(VideoCore::PixelFormat color,
|
||||
return *renderpass;
|
||||
}
|
||||
|
||||
vk::UniqueRenderPass RenderManager::CreateRenderPass(vk::Format color, vk::Format depth,
|
||||
vk::AttachmentLoadOp load_op) const {
|
||||
vk::UniqueRenderPass RenderpassCache::CreateRenderPass(vk::Format color, vk::Format depth,
|
||||
vk::AttachmentLoadOp load_op) const {
|
||||
u32 attachment_count = 0;
|
||||
std::array<vk::AttachmentDescription, 2> attachments;
|
||||
|
@@ -1,4 +1,4 @@
|
||||
// Copyright 2024 Citra Emulator Project
|
||||
// Copyright 2023 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
@@ -24,7 +24,7 @@ struct RenderPass {
|
||||
vk::RenderPass render_pass;
|
||||
vk::Rect2D render_area;
|
||||
vk::ClearValue clear;
|
||||
u32 do_clear;
|
||||
bool do_clear;
|
||||
|
||||
bool operator==(const RenderPass& other) const noexcept {
|
||||
return std::tie(framebuffer, render_pass, render_area, do_clear) ==
|
||||
@@ -34,13 +34,13 @@ struct RenderPass {
|
||||
}
|
||||
};
|
||||
|
||||
class RenderManager {
|
||||
class RenderpassCache {
|
||||
static constexpr std::size_t MAX_COLOR_FORMATS = 13;
|
||||
static constexpr std::size_t MAX_DEPTH_FORMATS = 4;
|
||||
|
||||
public:
|
||||
explicit RenderManager(const Instance& instance, Scheduler& scheduler);
|
||||
~RenderManager();
|
||||
explicit RenderpassCache(const Instance& instance, Scheduler& scheduler);
|
||||
~RenderpassCache();
|
||||
|
||||
/// Begins a new renderpass with the provided framebuffer as render target.
|
||||
void BeginRendering(const Framebuffer* framebuffer, Common::Rectangle<u32> draw_rect);
|
@@ -4,7 +4,6 @@
|
||||
|
||||
#include <cstddef>
|
||||
#include <optional>
|
||||
#include <unordered_map>
|
||||
#include "video_core/renderer_vulkan/vk_instance.h"
|
||||
#include "video_core/renderer_vulkan/vk_master_semaphore.h"
|
||||
#include "video_core/renderer_vulkan/vk_resource_pool.h"
|
||||
@@ -15,7 +14,9 @@ ResourcePool::ResourcePool(MasterSemaphore* master_semaphore_, std::size_t grow_
|
||||
: master_semaphore{master_semaphore_}, grow_step{grow_step_} {}
|
||||
|
||||
std::size_t ResourcePool::CommitResource() {
|
||||
u64 gpu_tick = master_semaphore->KnownGpuTick();
|
||||
// Refresh semaphore to query updated results
|
||||
master_semaphore->Refresh();
|
||||
const u64 gpu_tick = master_semaphore->KnownGpuTick();
|
||||
const auto search = [this, gpu_tick](std::size_t begin,
|
||||
std::size_t end) -> std::optional<std::size_t> {
|
||||
for (std::size_t iterator = begin; iterator < end; ++iterator) {
|
||||
@@ -28,13 +29,7 @@ std::size_t ResourcePool::CommitResource() {
|
||||
};
|
||||
|
||||
// Try to find a free resource from the hinted position to the end.
|
||||
auto found = search(hint_iterator, ticks.size());
|
||||
if (!found) {
|
||||
// Refresh semaphore to query updated results
|
||||
master_semaphore->Refresh();
|
||||
gpu_tick = master_semaphore->KnownGpuTick();
|
||||
found = search(hint_iterator, ticks.size());
|
||||
}
|
||||
std::optional<std::size_t> found = search(hint_iterator, ticks.size());
|
||||
if (!found) {
|
||||
// Search from beginning to the hinted position.
|
||||
found = search(0, hint_iterator);
|
||||
@@ -53,137 +48,75 @@ std::size_t ResourcePool::CommitResource() {
|
||||
}
|
||||
|
||||
std::size_t ResourcePool::ManageOverflow() {
|
||||
const std::size_t old_capacity = ticks.size();
|
||||
Grow();
|
||||
|
||||
// The last entry is guaranted to be free, since it's the first element of the freshly
|
||||
// allocated resources.
|
||||
return old_capacity;
|
||||
}
|
||||
|
||||
void ResourcePool::Grow() {
|
||||
const std::size_t old_capacity = ticks.size();
|
||||
ticks.resize(old_capacity + grow_step);
|
||||
Allocate(old_capacity, old_capacity + grow_step);
|
||||
return old_capacity;
|
||||
}
|
||||
|
||||
constexpr std::size_t COMMAND_BUFFER_POOL_SIZE = 4;
|
||||
|
||||
struct CommandPool::Pool {
|
||||
vk::CommandPool handle;
|
||||
std::array<vk::CommandBuffer, COMMAND_BUFFER_POOL_SIZE> cmdbufs;
|
||||
};
|
||||
|
||||
CommandPool::CommandPool(const Instance& instance, MasterSemaphore* master_semaphore)
|
||||
: ResourcePool{master_semaphore, COMMAND_BUFFER_POOL_SIZE}, instance{instance} {
|
||||
: ResourcePool{master_semaphore, COMMAND_BUFFER_POOL_SIZE}, instance{instance} {}
|
||||
|
||||
CommandPool::~CommandPool() {
|
||||
vk::Device device = instance.GetDevice();
|
||||
for (Pool& pool : pools) {
|
||||
device.destroyCommandPool(pool.handle);
|
||||
}
|
||||
}
|
||||
|
||||
void CommandPool::Allocate(std::size_t begin, std::size_t end) {
|
||||
// Command buffers are going to be commited, recorded, executed every single usage cycle.
|
||||
// They are also going to be reseted when commited.
|
||||
Pool& pool = pools.emplace_back();
|
||||
|
||||
const vk::CommandPoolCreateInfo pool_create_info = {
|
||||
.flags = vk::CommandPoolCreateFlagBits::eTransient |
|
||||
vk::CommandPoolCreateFlagBits::eResetCommandBuffer,
|
||||
.queueFamilyIndex = instance.GetGraphicsQueueFamilyIndex(),
|
||||
};
|
||||
const vk::Device device = instance.GetDevice();
|
||||
cmd_pool = device.createCommandPoolUnique(pool_create_info);
|
||||
if (instance.HasDebuggingToolAttached()) {
|
||||
SetObjectName(device, *cmd_pool, "CommandPool");
|
||||
}
|
||||
}
|
||||
|
||||
CommandPool::~CommandPool() = default;
|
||||
|
||||
void CommandPool::Allocate(std::size_t begin, std::size_t end) {
|
||||
cmd_buffers.resize(end);
|
||||
vk::Device device = instance.GetDevice();
|
||||
pool.handle = device.createCommandPool(pool_create_info);
|
||||
|
||||
const vk::CommandBufferAllocateInfo buffer_alloc_info = {
|
||||
.commandPool = *cmd_pool,
|
||||
.commandPool = pool.handle,
|
||||
.level = vk::CommandBufferLevel::ePrimary,
|
||||
.commandBufferCount = COMMAND_BUFFER_POOL_SIZE,
|
||||
};
|
||||
|
||||
const vk::Device device = instance.GetDevice();
|
||||
const auto result =
|
||||
device.allocateCommandBuffers(&buffer_alloc_info, cmd_buffers.data() + begin);
|
||||
ASSERT(result == vk::Result::eSuccess);
|
||||
auto buffers = device.allocateCommandBuffers(buffer_alloc_info);
|
||||
std::copy(buffers.begin(), buffers.end(), pool.cmdbufs.begin());
|
||||
|
||||
if (instance.HasDebuggingToolAttached()) {
|
||||
for (std::size_t i = begin; i < end; ++i) {
|
||||
SetObjectName(device, cmd_buffers[i], "CommandPool: Command Buffer {}", i);
|
||||
Vulkan::SetObjectName(device, pool.handle, "CommandPool: Pool({})",
|
||||
COMMAND_BUFFER_POOL_SIZE);
|
||||
|
||||
for (u32 i = 0; i < pool.cmdbufs.size(); ++i) {
|
||||
Vulkan::SetObjectName(device, pool.cmdbufs[i], "CommandPool: Command Buffer {}", i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
vk::CommandBuffer CommandPool::Commit() {
|
||||
const std::size_t index = CommitResource();
|
||||
return cmd_buffers[index];
|
||||
}
|
||||
|
||||
constexpr u32 DESCRIPTOR_SET_BATCH = 32;
|
||||
|
||||
DescriptorHeap::DescriptorHeap(const Instance& instance, MasterSemaphore* master_semaphore,
|
||||
std::span<const vk::DescriptorSetLayoutBinding> bindings,
|
||||
u32 descriptor_heap_count_)
|
||||
: ResourcePool{master_semaphore, DESCRIPTOR_SET_BATCH}, device{instance.GetDevice()},
|
||||
descriptor_heap_count{descriptor_heap_count_} {
|
||||
// Create descriptor set layout.
|
||||
const vk::DescriptorSetLayoutCreateInfo layout_ci = {
|
||||
.bindingCount = static_cast<u32>(bindings.size()),
|
||||
.pBindings = bindings.data(),
|
||||
};
|
||||
descriptor_set_layout = device.createDescriptorSetLayoutUnique(layout_ci);
|
||||
if (instance.HasDebuggingToolAttached()) {
|
||||
SetObjectName(device, *descriptor_set_layout, "DescriptorSetLayout");
|
||||
}
|
||||
|
||||
// Build descriptor set pool counts.
|
||||
std::unordered_map<vk::DescriptorType, u16> descriptor_type_counts;
|
||||
for (const auto& binding : bindings) {
|
||||
descriptor_type_counts[binding.descriptorType] += binding.descriptorCount;
|
||||
}
|
||||
for (const auto& [type, count] : descriptor_type_counts) {
|
||||
auto& pool_size = pool_sizes.emplace_back();
|
||||
pool_size.descriptorCount = count * descriptor_heap_count;
|
||||
pool_size.type = type;
|
||||
}
|
||||
|
||||
// Create descriptor pool
|
||||
AppendDescriptorPool();
|
||||
}
|
||||
|
||||
DescriptorHeap::~DescriptorHeap() = default;
|
||||
|
||||
void DescriptorHeap::Allocate(std::size_t begin, std::size_t end) {
|
||||
ASSERT(end - begin == DESCRIPTOR_SET_BATCH);
|
||||
descriptor_sets.resize(end);
|
||||
|
||||
std::array<vk::DescriptorSetLayout, DESCRIPTOR_SET_BATCH> layouts;
|
||||
layouts.fill(*descriptor_set_layout);
|
||||
|
||||
u32 current_pool = 0;
|
||||
vk::DescriptorSetAllocateInfo alloc_info = {
|
||||
.descriptorPool = *pools[current_pool],
|
||||
.descriptorSetCount = DESCRIPTOR_SET_BATCH,
|
||||
.pSetLayouts = layouts.data(),
|
||||
};
|
||||
|
||||
// Attempt to allocate the descriptor set batch. If the pool has run out of space, use a new
|
||||
// one.
|
||||
while (true) {
|
||||
const auto result =
|
||||
device.allocateDescriptorSets(&alloc_info, descriptor_sets.data() + begin);
|
||||
if (result == vk::Result::eSuccess) {
|
||||
break;
|
||||
}
|
||||
if (result == vk::Result::eErrorOutOfPoolMemory) {
|
||||
current_pool++;
|
||||
if (current_pool == pools.size()) {
|
||||
LOG_INFO(Render_Vulkan, "Run out of pools, creating new one!");
|
||||
AppendDescriptorPool();
|
||||
}
|
||||
alloc_info.descriptorPool = *pools[current_pool];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
vk::DescriptorSet DescriptorHeap::Commit() {
|
||||
const std::size_t index = CommitResource();
|
||||
return descriptor_sets[index];
|
||||
}
|
||||
|
||||
void DescriptorHeap::AppendDescriptorPool() {
|
||||
const vk::DescriptorPoolCreateInfo pool_info = {
|
||||
.flags = vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet,
|
||||
.maxSets = descriptor_heap_count,
|
||||
.poolSizeCount = static_cast<u32>(pool_sizes.size()),
|
||||
.pPoolSizes = pool_sizes.data(),
|
||||
};
|
||||
auto& pool = pools.emplace_back();
|
||||
pool = device.createDescriptorPoolUnique(pool_info);
|
||||
const auto pool_index = index / COMMAND_BUFFER_POOL_SIZE;
|
||||
const auto sub_index = index % COMMAND_BUFFER_POOL_SIZE;
|
||||
return pools[pool_index].cmdbufs[sub_index];
|
||||
}
|
||||
|
||||
} // namespace Vulkan
|
||||
|
@@ -39,6 +39,9 @@ private:
|
||||
/// Manages pool overflow allocating new resources.
|
||||
std::size_t ManageOverflow();
|
||||
|
||||
/// Allocates a new page of resources.
|
||||
void Grow();
|
||||
|
||||
protected:
|
||||
MasterSemaphore* master_semaphore{nullptr};
|
||||
std::size_t grow_step = 0; ///< Number of new resources created after an overflow
|
||||
@@ -56,36 +59,9 @@ public:
|
||||
vk::CommandBuffer Commit();
|
||||
|
||||
private:
|
||||
struct Pool;
|
||||
const Instance& instance;
|
||||
vk::UniqueCommandPool cmd_pool;
|
||||
std::vector<vk::CommandBuffer> cmd_buffers;
|
||||
};
|
||||
|
||||
class DescriptorHeap final : public ResourcePool {
|
||||
public:
|
||||
explicit DescriptorHeap(const Instance& instance, MasterSemaphore* master_semaphore,
|
||||
std::span<const vk::DescriptorSetLayoutBinding> bindings,
|
||||
u32 descriptor_heap_count = 1024);
|
||||
~DescriptorHeap() override;
|
||||
|
||||
const vk::DescriptorSetLayout& Layout() const {
|
||||
return *descriptor_set_layout;
|
||||
}
|
||||
|
||||
void Allocate(std::size_t begin, std::size_t end) override;
|
||||
|
||||
vk::DescriptorSet Commit();
|
||||
|
||||
private:
|
||||
void AppendDescriptorPool();
|
||||
|
||||
private:
|
||||
vk::Device device;
|
||||
vk::UniqueDescriptorSetLayout descriptor_set_layout;
|
||||
u32 descriptor_heap_count;
|
||||
std::vector<vk::DescriptorPoolSize> pool_sizes;
|
||||
std::vector<vk::UniqueDescriptorPool> pools;
|
||||
std::vector<vk::DescriptorSet> descriptor_sets;
|
||||
std::vector<Pool> pools;
|
||||
};
|
||||
|
||||
} // namespace Vulkan
|
||||
|
@@ -5,8 +5,10 @@
|
||||
#include <mutex>
|
||||
#include <utility>
|
||||
#include "common/microprofile.h"
|
||||
#include "common/settings.h"
|
||||
#include "common/thread.h"
|
||||
#include "video_core/renderer_vulkan/vk_instance.h"
|
||||
#include "video_core/renderer_vulkan/vk_renderpass_cache.h"
|
||||
#include "video_core/renderer_vulkan/vk_scheduler.h"
|
||||
|
||||
MICROPROFILE_DEFINE(Vulkan_WaitForWorker, "Vulkan", "Wait for worker", MP_RGB(255, 192, 192));
|
||||
@@ -96,8 +98,6 @@ void Scheduler::DispatchWork() {
|
||||
return;
|
||||
}
|
||||
|
||||
on_dispatch();
|
||||
|
||||
{
|
||||
std::scoped_lock ql{queue_mutex};
|
||||
work_queue.push(std::move(chunk));
|
||||
@@ -173,16 +173,12 @@ void Scheduler::SubmitExecution(vk::Semaphore signal_semaphore, vk::Semaphore wa
|
||||
state = StateFlags::AllDirty;
|
||||
const u64 signal_value = master_semaphore->NextTick();
|
||||
|
||||
on_submit();
|
||||
|
||||
Record([signal_semaphore, wait_semaphore, signal_value, this](vk::CommandBuffer cmdbuf) {
|
||||
MICROPROFILE_SCOPE(Vulkan_Submit);
|
||||
std::scoped_lock lock{submit_mutex};
|
||||
master_semaphore->SubmitWork(cmdbuf, wait_semaphore, signal_semaphore, signal_value);
|
||||
});
|
||||
|
||||
master_semaphore->Refresh();
|
||||
|
||||
if (!use_worker_thread) {
|
||||
AllocateWorkerCommandBuffers();
|
||||
} else {
|
||||
|
@@ -4,7 +4,6 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#include "common/alignment.h"
|
||||
@@ -50,6 +49,11 @@ public:
|
||||
/// Records the command to the current chunk.
|
||||
template <typename T>
|
||||
void Record(T&& command) {
|
||||
if (!use_worker_thread) {
|
||||
command(current_cmdbuf);
|
||||
return;
|
||||
}
|
||||
|
||||
if (chunk->Record(command)) {
|
||||
return;
|
||||
}
|
||||
@@ -72,16 +76,6 @@ public:
|
||||
return False(state & flag);
|
||||
}
|
||||
|
||||
/// Registers a callback to perform on queue submission.
|
||||
void RegisterOnSubmit(std::function<void()>&& func) {
|
||||
on_submit = std::move(func);
|
||||
}
|
||||
|
||||
/// Registers a callback to perform on queue submission.
|
||||
void RegisterOnDispatch(std::function<void()>&& func) {
|
||||
on_dispatch = std::move(func);
|
||||
}
|
||||
|
||||
/// Returns the current command buffer tick.
|
||||
[[nodiscard]] u64 CurrentTick() const noexcept {
|
||||
return master_semaphore->CurrentTick();
|
||||
@@ -200,8 +194,6 @@ private:
|
||||
std::vector<std::unique_ptr<CommandChunk>> chunk_reserve;
|
||||
vk::CommandBuffer current_cmdbuf;
|
||||
StateFlags state{};
|
||||
std::function<void()> on_submit;
|
||||
std::function<void()> on_dispatch;
|
||||
std::mutex execution_mutex;
|
||||
std::mutex reserve_mutex;
|
||||
std::mutex queue_mutex;
|
||||
|
@@ -182,7 +182,6 @@ vk::ShaderModule Compile(std::string_view code, vk::ShaderStageFlagBits stage, v
|
||||
includer)) [[unlikely]] {
|
||||
LOG_INFO(Render_Vulkan, "Shader Info Log:\n{}\n{}", shader->getInfoLog(),
|
||||
shader->getInfoDebugLog());
|
||||
LOG_INFO(Render_Vulkan, "Shader Source:\n{}", code);
|
||||
return {};
|
||||
}
|
||||
|
||||
|
@@ -82,7 +82,7 @@ StreamBuffer::~StreamBuffer() {
|
||||
device.freeMemory(memory);
|
||||
}
|
||||
|
||||
std::tuple<u8*, u32, bool> StreamBuffer::Map(u32 size, u64 alignment) {
|
||||
std::tuple<u8*, u64, bool> StreamBuffer::Map(u64 size, u64 alignment) {
|
||||
if (!is_coherent && type == BufferType::Stream) {
|
||||
size = Common::AlignUp(size, instance.NonCoherentAtomSize());
|
||||
}
|
||||
@@ -114,7 +114,7 @@ std::tuple<u8*, u32, bool> StreamBuffer::Map(u32 size, u64 alignment) {
|
||||
return std::make_tuple(mapped + offset, offset, invalidate);
|
||||
}
|
||||
|
||||
void StreamBuffer::Commit(u32 size) {
|
||||
void StreamBuffer::Commit(u64 size) {
|
||||
if (!is_coherent && type == BufferType::Stream) {
|
||||
size = Common::AlignUp(size, instance.NonCoherentAtomSize());
|
||||
}
|
||||
@@ -200,10 +200,11 @@ void StreamBuffer::CreateBuffers(u64 prefered_size) {
|
||||
mapped = reinterpret_cast<u8*>(device.mapMemory(memory, 0, VK_WHOLE_SIZE));
|
||||
|
||||
if (instance.HasDebuggingToolAttached()) {
|
||||
SetObjectName(device, buffer, "StreamBuffer({}): {} KiB {}", BufferTypeName(type),
|
||||
stream_buffer_size / 1024, vk::to_string(mem_type.propertyFlags));
|
||||
SetObjectName(device, memory, "StreamBufferMemory({}): {} Kib {}", BufferTypeName(type),
|
||||
stream_buffer_size / 1024, vk::to_string(mem_type.propertyFlags));
|
||||
Vulkan::SetObjectName(device, buffer, "StreamBuffer({}): {} KiB {}", BufferTypeName(type),
|
||||
stream_buffer_size / 1024, vk::to_string(mem_type.propertyFlags));
|
||||
Vulkan::SetObjectName(device, memory, "StreamBufferMemory({}): {} Kib {}",
|
||||
BufferTypeName(type), stream_buffer_size / 1024,
|
||||
vk::to_string(mem_type.propertyFlags));
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -35,10 +35,10 @@ public:
|
||||
* @param size Size to reserve.
|
||||
* @returns A pair of a raw memory pointer (with offset added), and the buffer offset
|
||||
*/
|
||||
std::tuple<u8*, u32, bool> Map(u32 size, u64 alignment);
|
||||
std::tuple<u8*, u64, bool> Map(u64 size, u64 alignment);
|
||||
|
||||
/// Ensures that "size" bytes of memory are available to the GPU, potentially recording a copy.
|
||||
void Commit(u32 size);
|
||||
void Commit(u64 size);
|
||||
|
||||
vk::Buffer Handle() const noexcept {
|
||||
return buffer;
|
||||
@@ -70,8 +70,8 @@ private:
|
||||
vk::BufferUsageFlags usage{};
|
||||
BufferType type;
|
||||
|
||||
u32 offset{}; ///< Buffer iterator.
|
||||
u32 mapped_size{}; ///< Size reserved for the current copy.
|
||||
u64 offset{}; ///< Buffer iterator.
|
||||
u64 mapped_size{}; ///< Size reserved for the current copy.
|
||||
bool is_coherent{}; ///< True if the buffer is coherent
|
||||
|
||||
std::vector<Watch> current_watches; ///< Watches recorded in the current iteration.
|
||||
|
@@ -250,8 +250,10 @@ void Swapchain::RefreshSemaphores() {
|
||||
|
||||
if (instance.HasDebuggingToolAttached()) {
|
||||
for (u32 i = 0; i < image_count; ++i) {
|
||||
SetObjectName(device, image_acquired[i], "Swapchain Semaphore: image_acquired {}", i);
|
||||
SetObjectName(device, present_ready[i], "Swapchain Semaphore: present_ready {}", i);
|
||||
Vulkan::SetObjectName(device, image_acquired[i],
|
||||
"Swapchain Semaphore: image_acquired {}", i);
|
||||
Vulkan::SetObjectName(device, present_ready[i], "Swapchain Semaphore: present_ready {}",
|
||||
i);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -263,7 +265,7 @@ void Swapchain::SetupImages() {
|
||||
|
||||
if (instance.HasDebuggingToolAttached()) {
|
||||
for (u32 i = 0; i < image_count; ++i) {
|
||||
SetObjectName(device, images[i], "Swapchain Image {}", i);
|
||||
Vulkan::SetObjectName(device, images[i], "Swapchain Image {}", i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -3,7 +3,6 @@
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <boost/container/small_vector.hpp>
|
||||
#include <boost/container/static_vector.hpp>
|
||||
|
||||
#include "common/literals.h"
|
||||
#include "common/microprofile.h"
|
||||
@@ -12,8 +11,9 @@
|
||||
#include "video_core/rasterizer_cache/texture_codec.h"
|
||||
#include "video_core/rasterizer_cache/utils.h"
|
||||
#include "video_core/renderer_vulkan/pica_to_vk.h"
|
||||
#include "video_core/renderer_vulkan/vk_descriptor_pool.h"
|
||||
#include "video_core/renderer_vulkan/vk_instance.h"
|
||||
#include "video_core/renderer_vulkan/vk_render_manager.h"
|
||||
#include "video_core/renderer_vulkan/vk_renderpass_cache.h"
|
||||
#include "video_core/renderer_vulkan/vk_scheduler.h"
|
||||
#include "video_core/renderer_vulkan/vk_texture_runtime.h"
|
||||
|
||||
@@ -119,9 +119,9 @@ u32 UnpackDepthStencil(const VideoCore::StagingData& data, vk::Format dest) {
|
||||
}
|
||||
|
||||
boost::container::small_vector<vk::ImageMemoryBarrier, 3> MakeInitBarriers(
|
||||
vk::ImageAspectFlags aspect, std::span<const vk::Image> images) {
|
||||
vk::ImageAspectFlags aspect, std::span<const vk::Image> images, std::size_t num_images) {
|
||||
boost::container::small_vector<vk::ImageMemoryBarrier, 3> barriers;
|
||||
for (const vk::Image& image : images) {
|
||||
for (std::size_t i = 0; i < num_images; i++) {
|
||||
barriers.push_back(vk::ImageMemoryBarrier{
|
||||
.srcAccessMask = vk::AccessFlagBits::eNone,
|
||||
.dstAccessMask = vk::AccessFlagBits::eNone,
|
||||
@@ -129,7 +129,7 @@ boost::container::small_vector<vk::ImageMemoryBarrier, 3> MakeInitBarriers(
|
||||
.newLayout = vk::ImageLayout::eGeneral,
|
||||
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.image = image,
|
||||
.image = images[i],
|
||||
.subresourceRange{
|
||||
.aspectMask = aspect,
|
||||
.baseMipLevel = 0,
|
||||
@@ -206,9 +206,9 @@ Handle MakeHandle(const Instance* instance, u32 width, u32 height, u32 levels, T
|
||||
vk::UniqueImageView image_view = instance->GetDevice().createImageViewUnique(view_info);
|
||||
|
||||
if (!debug_name.empty() && instance->HasDebuggingToolAttached()) {
|
||||
SetObjectName(instance->GetDevice(), image, debug_name);
|
||||
SetObjectName(instance->GetDevice(), image_view.get(), "{} View({})", debug_name,
|
||||
vk::to_string(aspect));
|
||||
Vulkan::SetObjectName(instance->GetDevice(), image, debug_name);
|
||||
Vulkan::SetObjectName(instance->GetDevice(), image_view.get(), "{} View({})", debug_name,
|
||||
vk::to_string(aspect));
|
||||
}
|
||||
|
||||
return Handle{
|
||||
@@ -219,10 +219,11 @@ Handle MakeHandle(const Instance* instance, u32 width, u32 height, u32 levels, T
|
||||
}
|
||||
|
||||
vk::UniqueFramebuffer MakeFramebuffer(vk::Device device, vk::RenderPass render_pass, u32 width,
|
||||
u32 height, std::span<const vk::ImageView> attachments) {
|
||||
u32 height, std::span<const vk::ImageView> attachments,
|
||||
u32 num_attachments) {
|
||||
const vk::FramebufferCreateInfo framebuffer_info = {
|
||||
.renderPass = render_pass,
|
||||
.attachmentCount = static_cast<u32>(attachments.size()),
|
||||
.attachmentCount = num_attachments,
|
||||
.pAttachments = attachments.data(),
|
||||
.width = width,
|
||||
.height = height,
|
||||
@@ -248,10 +249,10 @@ constexpr u64 DOWNLOAD_BUFFER_SIZE = 16_MiB;
|
||||
} // Anonymous namespace
|
||||
|
||||
TextureRuntime::TextureRuntime(const Instance& instance, Scheduler& scheduler,
|
||||
RenderManager& render_manager, DescriptorUpdateQueue& update_queue,
|
||||
u32 num_swapchain_images_)
|
||||
: instance{instance}, scheduler{scheduler}, render_manager{render_manager},
|
||||
blit_helper{instance, scheduler, render_manager, update_queue},
|
||||
RenderpassCache& renderpass_cache, DescriptorPool& pool,
|
||||
DescriptorSetProvider& texture_provider_, u32 num_swapchain_images_)
|
||||
: instance{instance}, scheduler{scheduler}, renderpass_cache{renderpass_cache},
|
||||
texture_provider{texture_provider_}, blit_helper{instance, scheduler, pool, renderpass_cache},
|
||||
upload_buffer{instance, scheduler, vk::BufferUsageFlagBits::eTransferSrc, UPLOAD_BUFFER_SIZE,
|
||||
BufferType::Upload},
|
||||
download_buffer{instance, scheduler,
|
||||
@@ -267,7 +268,7 @@ VideoCore::StagingData TextureRuntime::FindStaging(u32 size, bool upload) {
|
||||
const auto [data, offset, invalidate] = buffer.Map(size, 16);
|
||||
return VideoCore::StagingData{
|
||||
.size = size,
|
||||
.offset = offset,
|
||||
.offset = static_cast<u32>(offset),
|
||||
.mapped = std::span{data, size},
|
||||
};
|
||||
}
|
||||
@@ -304,7 +305,7 @@ bool TextureRuntime::Reinterpret(Surface& source, Surface& dest,
|
||||
}
|
||||
|
||||
bool TextureRuntime::ClearTexture(Surface& surface, const VideoCore::TextureClear& clear) {
|
||||
render_manager.EndRendering();
|
||||
renderpass_cache.EndRendering();
|
||||
|
||||
const RecordParams params = {
|
||||
.aspect = surface.Aspect(),
|
||||
@@ -376,7 +377,7 @@ void TextureRuntime::ClearTextureWithRenderpass(Surface& surface,
|
||||
|
||||
const auto color_format = is_color ? surface.pixel_format : PixelFormat::Invalid;
|
||||
const auto depth_format = is_color ? PixelFormat::Invalid : surface.pixel_format;
|
||||
const auto render_pass = render_manager.GetRenderpass(color_format, depth_format, true);
|
||||
const auto render_pass = renderpass_cache.GetRenderpass(color_format, depth_format, true);
|
||||
|
||||
const RecordParams params = {
|
||||
.aspect = surface.Aspect(),
|
||||
@@ -452,8 +453,8 @@ void TextureRuntime::ClearTextureWithRenderpass(Surface& surface,
|
||||
}
|
||||
|
||||
bool TextureRuntime::CopyTextures(Surface& source, Surface& dest,
|
||||
std::span<const VideoCore::TextureCopy> copies) {
|
||||
render_manager.EndRendering();
|
||||
const VideoCore::TextureCopy& copy) {
|
||||
renderpass_cache.EndRendering();
|
||||
|
||||
const RecordParams params = {
|
||||
.aspect = source.Aspect(),
|
||||
@@ -465,9 +466,8 @@ bool TextureRuntime::CopyTextures(Surface& source, Surface& dest,
|
||||
.dst_image = dest.Image(),
|
||||
};
|
||||
|
||||
boost::container::small_vector<vk::ImageCopy, 2> vk_copies;
|
||||
std::ranges::transform(copies, std::back_inserter(vk_copies), [&](const auto& copy) {
|
||||
return vk::ImageCopy{
|
||||
scheduler.Record([params, copy](vk::CommandBuffer cmdbuf) {
|
||||
const vk::ImageCopy image_copy = {
|
||||
.srcSubresource{
|
||||
.aspectMask = params.aspect,
|
||||
.mipLevel = copy.src_level,
|
||||
@@ -486,9 +486,7 @@ bool TextureRuntime::CopyTextures(Surface& source, Surface& dest,
|
||||
0},
|
||||
.extent = {copy.extent.width, copy.extent.height, 1},
|
||||
};
|
||||
});
|
||||
|
||||
scheduler.Record([params, copies = std::move(vk_copies)](vk::CommandBuffer cmdbuf) {
|
||||
const bool self_copy = params.src_image == params.dst_image;
|
||||
const vk::ImageLayout new_src_layout =
|
||||
self_copy ? vk::ImageLayout::eGeneral : vk::ImageLayout::eTransferSrcOptimal;
|
||||
@@ -504,7 +502,7 @@ bool TextureRuntime::CopyTextures(Surface& source, Surface& dest,
|
||||
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.image = params.src_image,
|
||||
.subresourceRange = MakeSubresourceRange(params.aspect, 0, VK_REMAINING_MIP_LEVELS),
|
||||
.subresourceRange = MakeSubresourceRange(params.aspect, copy.src_level),
|
||||
},
|
||||
vk::ImageMemoryBarrier{
|
||||
.srcAccessMask = params.dst_access,
|
||||
@@ -514,7 +512,7 @@ bool TextureRuntime::CopyTextures(Surface& source, Surface& dest,
|
||||
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.image = params.dst_image,
|
||||
.subresourceRange = MakeSubresourceRange(params.aspect, 0, VK_REMAINING_MIP_LEVELS),
|
||||
.subresourceRange = MakeSubresourceRange(params.aspect, copy.dst_level),
|
||||
},
|
||||
};
|
||||
const std::array post_barriers = {
|
||||
@@ -526,7 +524,7 @@ bool TextureRuntime::CopyTextures(Surface& source, Surface& dest,
|
||||
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.image = params.src_image,
|
||||
.subresourceRange = MakeSubresourceRange(params.aspect, 0, VK_REMAINING_MIP_LEVELS),
|
||||
.subresourceRange = MakeSubresourceRange(params.aspect, copy.src_level),
|
||||
},
|
||||
vk::ImageMemoryBarrier{
|
||||
.srcAccessMask = vk::AccessFlagBits::eTransferWrite,
|
||||
@@ -536,7 +534,7 @@ bool TextureRuntime::CopyTextures(Surface& source, Surface& dest,
|
||||
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.image = params.dst_image,
|
||||
.subresourceRange = MakeSubresourceRange(params.aspect, 0, VK_REMAINING_MIP_LEVELS),
|
||||
.subresourceRange = MakeSubresourceRange(params.aspect, copy.dst_level),
|
||||
},
|
||||
};
|
||||
|
||||
@@ -544,7 +542,7 @@ bool TextureRuntime::CopyTextures(Surface& source, Surface& dest,
|
||||
vk::DependencyFlagBits::eByRegion, {}, {}, pre_barriers);
|
||||
|
||||
cmdbuf.copyImage(params.src_image, new_src_layout, params.dst_image, new_dst_layout,
|
||||
copies);
|
||||
image_copy);
|
||||
|
||||
cmdbuf.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer, params.pipeline_flags,
|
||||
vk::DependencyFlagBits::eByRegion, {}, {}, post_barriers);
|
||||
@@ -561,7 +559,7 @@ bool TextureRuntime::BlitTextures(Surface& source, Surface& dest,
|
||||
return blit_helper.BlitDepthStencil(source, dest, blit);
|
||||
}
|
||||
|
||||
render_manager.EndRendering();
|
||||
renderpass_cache.EndRendering();
|
||||
|
||||
const RecordParams params = {
|
||||
.aspect = source.Aspect(),
|
||||
@@ -669,7 +667,7 @@ void TextureRuntime::GenerateMipmaps(Surface& surface) {
|
||||
return;
|
||||
}
|
||||
|
||||
render_manager.EndRendering();
|
||||
renderpass_cache.EndRendering();
|
||||
|
||||
auto [width, height] = surface.RealExtent();
|
||||
const u32 levels = surface.levels;
|
||||
@@ -696,6 +694,13 @@ bool TextureRuntime::NeedsConversion(VideoCore::PixelFormat format) const {
|
||||
traits.aspect != (vk::ImageAspectFlagBits::eDepth | vk::ImageAspectFlagBits::eStencil);
|
||||
}
|
||||
|
||||
void TextureRuntime::FreeDescriptorSetsWithImage(vk::ImageView image_view) {
|
||||
texture_provider.FreeWithImage(image_view);
|
||||
blit_helper.compute_provider.FreeWithImage(image_view);
|
||||
blit_helper.compute_buffer_provider.FreeWithImage(image_view);
|
||||
blit_helper.two_textures_provider.FreeWithImage(image_view);
|
||||
}
|
||||
|
||||
Surface::Surface(TextureRuntime& runtime_, const VideoCore::SurfaceParams& params)
|
||||
: SurfaceBase{params}, runtime{&runtime_}, instance{&runtime_.GetInstance()},
|
||||
scheduler{&runtime_.GetScheduler()}, traits{instance->GetTraits(pixel_format)} {
|
||||
@@ -710,7 +715,8 @@ Surface::Surface(TextureRuntime& runtime_, const VideoCore::SurfaceParams& param
|
||||
ASSERT_MSG(format != vk::Format::eUndefined && levels >= 1,
|
||||
"Image allocation parameters are invalid");
|
||||
|
||||
boost::container::static_vector<vk::Image, 3> raw_images;
|
||||
u32 num_images = 0;
|
||||
std::array<vk::Image, 3> raw_images;
|
||||
|
||||
vk::ImageCreateFlags flags{};
|
||||
if (texture_type == VideoCore::TextureType::CubeMap) {
|
||||
@@ -723,18 +729,18 @@ Surface::Surface(TextureRuntime& runtime_, const VideoCore::SurfaceParams& param
|
||||
const bool need_format_list = is_mutable && instance->IsImageFormatListSupported();
|
||||
handles[0] = MakeHandle(instance, width, height, levels, texture_type, format, traits.usage,
|
||||
flags, traits.aspect, need_format_list, DebugName(false));
|
||||
raw_images.emplace_back(handles[0].image);
|
||||
raw_images[num_images++] = handles[0].image;
|
||||
|
||||
if (res_scale != 1) {
|
||||
handles[1] =
|
||||
MakeHandle(instance, GetScaledWidth(), GetScaledHeight(), levels, texture_type, format,
|
||||
traits.usage, flags, traits.aspect, need_format_list, DebugName(true));
|
||||
raw_images.emplace_back(handles[1].image);
|
||||
raw_images[num_images++] = handles[1].image;
|
||||
}
|
||||
|
||||
runtime->render_manager.EndRendering();
|
||||
scheduler->Record([raw_images, aspect = traits.aspect](vk::CommandBuffer cmdbuf) {
|
||||
const auto barriers = MakeInitBarriers(aspect, raw_images);
|
||||
runtime->renderpass_cache.EndRendering();
|
||||
scheduler->Record([raw_images, num_images, aspect = traits.aspect](vk::CommandBuffer cmdbuf) {
|
||||
const auto barriers = MakeInitBarriers(aspect, raw_images, num_images);
|
||||
cmdbuf.pipelineBarrier(vk::PipelineStageFlagBits::eTopOfPipe,
|
||||
vk::PipelineStageFlagBits::eTopOfPipe,
|
||||
vk::DependencyFlagBits::eByRegion, {}, {}, barriers);
|
||||
@@ -752,7 +758,8 @@ Surface::Surface(TextureRuntime& runtime_, const VideoCore::SurfaceBase& surface
|
||||
const bool has_normal = mat && mat->Map(MapType::Normal);
|
||||
const vk::Format format = traits.native;
|
||||
|
||||
boost::container::static_vector<vk::Image, 2> raw_images;
|
||||
u32 num_images = 0;
|
||||
std::array<vk::Image, 2> raw_images;
|
||||
|
||||
vk::ImageCreateFlags flags{};
|
||||
if (texture_type == VideoCore::TextureType::CubeMap) {
|
||||
@@ -762,23 +769,23 @@ Surface::Surface(TextureRuntime& runtime_, const VideoCore::SurfaceBase& surface
|
||||
const std::string debug_name = DebugName(false, true);
|
||||
handles[0] = MakeHandle(instance, mat->width, mat->height, levels, texture_type, format,
|
||||
traits.usage, flags, traits.aspect, false, debug_name);
|
||||
raw_images.emplace_back(handles[0].image);
|
||||
raw_images[num_images++] = handles[0].image;
|
||||
|
||||
if (res_scale != 1) {
|
||||
handles[1] = MakeHandle(instance, mat->width, mat->height, levels, texture_type,
|
||||
vk::Format::eR8G8B8A8Unorm, traits.usage, flags, traits.aspect,
|
||||
false, debug_name);
|
||||
raw_images.emplace_back(handles[1].image);
|
||||
raw_images[num_images++] = handles[1].image;
|
||||
}
|
||||
if (has_normal) {
|
||||
handles[2] = MakeHandle(instance, mat->width, mat->height, levels, texture_type, format,
|
||||
traits.usage, flags, traits.aspect, false, debug_name);
|
||||
raw_images.emplace_back(handles[2].image);
|
||||
raw_images[num_images++] = handles[2].image;
|
||||
}
|
||||
|
||||
runtime->render_manager.EndRendering();
|
||||
scheduler->Record([raw_images, aspect = traits.aspect](vk::CommandBuffer cmdbuf) {
|
||||
const auto barriers = MakeInitBarriers(aspect, raw_images);
|
||||
runtime->renderpass_cache.EndRendering();
|
||||
scheduler->Record([raw_images, num_images, aspect = traits.aspect](vk::CommandBuffer cmdbuf) {
|
||||
const auto barriers = MakeInitBarriers(aspect, raw_images, num_images);
|
||||
cmdbuf.pipelineBarrier(vk::PipelineStageFlagBits::eTopOfPipe,
|
||||
vk::PipelineStageFlagBits::eTopOfPipe,
|
||||
vk::DependencyFlagBits::eByRegion, {}, {}, barriers);
|
||||
@@ -793,6 +800,9 @@ Surface::~Surface() {
|
||||
return;
|
||||
}
|
||||
for (const auto& [alloc, image, image_view] : handles) {
|
||||
if (image_view) {
|
||||
runtime->FreeDescriptorSetsWithImage(*image_view);
|
||||
}
|
||||
if (image) {
|
||||
vmaDestroyImage(instance->GetAllocator(), image, alloc);
|
||||
}
|
||||
@@ -804,7 +814,7 @@ Surface::~Surface() {
|
||||
|
||||
void Surface::Upload(const VideoCore::BufferTextureCopy& upload,
|
||||
const VideoCore::StagingData& staging) {
|
||||
runtime->render_manager.EndRendering();
|
||||
runtime->renderpass_cache.EndRendering();
|
||||
|
||||
const RecordParams params = {
|
||||
.aspect = Aspect(),
|
||||
@@ -815,10 +825,11 @@ void Surface::Upload(const VideoCore::BufferTextureCopy& upload,
|
||||
|
||||
scheduler->Record([buffer = runtime->upload_buffer.Handle(), format = traits.native, params,
|
||||
staging, upload](vk::CommandBuffer cmdbuf) {
|
||||
boost::container::static_vector<vk::BufferImageCopy, 2> buffer_image_copies;
|
||||
u32 num_copies = 1;
|
||||
std::array<vk::BufferImageCopy, 2> buffer_image_copies;
|
||||
|
||||
const auto rect = upload.texture_rect;
|
||||
buffer_image_copies.emplace_back(vk::BufferImageCopy{
|
||||
buffer_image_copies[0] = vk::BufferImageCopy{
|
||||
.bufferOffset = upload.buffer_offset,
|
||||
.bufferRowLength = rect.GetWidth(),
|
||||
.bufferImageHeight = rect.GetHeight(),
|
||||
@@ -830,16 +841,15 @@ void Surface::Upload(const VideoCore::BufferTextureCopy& upload,
|
||||
},
|
||||
.imageOffset = {static_cast<s32>(rect.left), static_cast<s32>(rect.bottom), 0},
|
||||
.imageExtent = {rect.GetWidth(), rect.GetHeight(), 1},
|
||||
});
|
||||
};
|
||||
|
||||
if (params.aspect & vk::ImageAspectFlagBits::eStencil) {
|
||||
buffer_image_copies[0].imageSubresource.aspectMask = vk::ImageAspectFlagBits::eDepth;
|
||||
|
||||
vk::BufferImageCopy& stencil_copy =
|
||||
buffer_image_copies.emplace_back(buffer_image_copies[0]);
|
||||
vk::BufferImageCopy& stencil_copy = buffer_image_copies[1];
|
||||
stencil_copy = buffer_image_copies[0];
|
||||
stencil_copy.bufferOffset += UnpackDepthStencil(staging, format);
|
||||
stencil_copy.imageSubresource.aspectMask = vk::ImageAspectFlagBits::eStencil;
|
||||
num_copies++;
|
||||
}
|
||||
|
||||
const vk::ImageMemoryBarrier read_barrier = {
|
||||
@@ -867,7 +877,7 @@ void Surface::Upload(const VideoCore::BufferTextureCopy& upload,
|
||||
vk::DependencyFlagBits::eByRegion, {}, {}, read_barrier);
|
||||
|
||||
cmdbuf.copyBufferToImage(buffer, params.src_image, vk::ImageLayout::eTransferDstOptimal,
|
||||
buffer_image_copies);
|
||||
num_copies, buffer_image_copies.data());
|
||||
|
||||
cmdbuf.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer, params.pipeline_flags,
|
||||
vk::DependencyFlagBits::eByRegion, {}, {}, write_barrier);
|
||||
@@ -894,7 +904,7 @@ void Surface::UploadCustom(const VideoCore::Material* material, u32 level) {
|
||||
const Common::Rectangle rect{0U, height, width, 0U};
|
||||
|
||||
const auto upload = [&](u32 index, VideoCore::CustomTexture* texture) {
|
||||
const u32 custom_size = static_cast<u32>(texture->data.size());
|
||||
const u64 custom_size = texture->data.size();
|
||||
const RecordParams params = {
|
||||
.aspect = vk::ImageAspectFlagBits::eColor,
|
||||
.pipeline_flags = PipelineStageFlags(),
|
||||
@@ -972,7 +982,7 @@ void Surface::Download(const VideoCore::BufferTextureCopy& download,
|
||||
runtime->download_buffer.Commit(staging.size);
|
||||
});
|
||||
|
||||
runtime->render_manager.EndRendering();
|
||||
runtime->renderpass_cache.EndRendering();
|
||||
|
||||
if (pixel_format == PixelFormat::D24S8) {
|
||||
runtime->blit_helper.DepthToBuffer(*this, runtime->download_buffer.Handle(), download);
|
||||
@@ -1072,15 +1082,15 @@ void Surface::ScaleUp(u32 new_scale) {
|
||||
MakeHandle(instance, GetScaledWidth(), GetScaledHeight(), levels, texture_type,
|
||||
traits.native, traits.usage, flags, traits.aspect, false, DebugName(true));
|
||||
|
||||
runtime->render_manager.EndRendering();
|
||||
runtime->renderpass_cache.EndRendering();
|
||||
scheduler->Record(
|
||||
[raw_images = std::array{Image()}, aspect = traits.aspect](vk::CommandBuffer cmdbuf) {
|
||||
const auto barriers = MakeInitBarriers(aspect, raw_images);
|
||||
const auto barriers = MakeInitBarriers(aspect, raw_images, raw_images.size());
|
||||
cmdbuf.pipelineBarrier(vk::PipelineStageFlagBits::eTopOfPipe,
|
||||
vk::PipelineStageFlagBits::eTopOfPipe,
|
||||
vk::DependencyFlagBits::eByRegion, {}, {}, barriers);
|
||||
});
|
||||
|
||||
LOG_INFO(HW_GPU, "Surface scale up!");
|
||||
for (u32 level = 0; level < levels; level++) {
|
||||
const VideoCore::TextureBlit blit = {
|
||||
.src_level = level,
|
||||
@@ -1150,7 +1160,7 @@ vk::ImageView Surface::CopyImageView() noexcept {
|
||||
copy_layout = vk::ImageLayout::eUndefined;
|
||||
}
|
||||
|
||||
runtime->render_manager.EndRendering();
|
||||
runtime->renderpass_cache.EndRendering();
|
||||
|
||||
const RecordParams params = {
|
||||
.aspect = Aspect(),
|
||||
@@ -1338,10 +1348,10 @@ vk::Framebuffer Surface::Framebuffer() noexcept {
|
||||
const auto color_format = is_depth ? PixelFormat::Invalid : pixel_format;
|
||||
const auto depth_format = is_depth ? pixel_format : PixelFormat::Invalid;
|
||||
const auto render_pass =
|
||||
runtime->render_manager.GetRenderpass(color_format, depth_format, false);
|
||||
runtime->renderpass_cache.GetRenderpass(color_format, depth_format, false);
|
||||
const auto attachments = std::array{ImageView()};
|
||||
framebuffers[index] = MakeFramebuffer(instance->GetDevice(), render_pass, GetScaledWidth(),
|
||||
GetScaledHeight(), attachments);
|
||||
GetScaledHeight(), attachments, 1);
|
||||
return framebuffers[index].get();
|
||||
}
|
||||
|
||||
@@ -1452,7 +1462,7 @@ Framebuffer::Framebuffer(TextureRuntime& runtime, const VideoCore::FramebufferPa
|
||||
Surface* color, Surface* depth)
|
||||
: VideoCore::FramebufferParams{params}, res_scale{color ? color->res_scale
|
||||
: (depth ? depth->res_scale : 1u)} {
|
||||
auto& render_manager = runtime.GetRenderpassCache();
|
||||
auto& renderpass_cache = runtime.GetRenderpassCache();
|
||||
if (shadow_rendering && !color) {
|
||||
return;
|
||||
}
|
||||
@@ -1471,27 +1481,29 @@ Framebuffer::Framebuffer(TextureRuntime& runtime, const VideoCore::FramebufferPa
|
||||
image_views[index] = shadow_rendering ? surface->StorageView() : surface->FramebufferView();
|
||||
};
|
||||
|
||||
boost::container::static_vector<vk::ImageView, 2> attachments;
|
||||
u32 num_attachments = 0;
|
||||
std::array<vk::ImageView, 2> attachments;
|
||||
|
||||
if (color) {
|
||||
prepare(0, color);
|
||||
attachments.emplace_back(image_views[0]);
|
||||
attachments[num_attachments++] = image_views[0];
|
||||
}
|
||||
|
||||
if (depth) {
|
||||
prepare(1, depth);
|
||||
attachments.emplace_back(image_views[1]);
|
||||
attachments[num_attachments++] = image_views[1];
|
||||
}
|
||||
|
||||
const vk::Device device = runtime.GetInstance().GetDevice();
|
||||
if (shadow_rendering) {
|
||||
render_pass =
|
||||
render_manager.GetRenderpass(PixelFormat::Invalid, PixelFormat::Invalid, false);
|
||||
renderpass_cache.GetRenderpass(PixelFormat::Invalid, PixelFormat::Invalid, false);
|
||||
framebuffer = MakeFramebuffer(device, render_pass, color->GetScaledWidth(),
|
||||
color->GetScaledHeight(), {});
|
||||
color->GetScaledHeight(), {}, 0);
|
||||
} else {
|
||||
render_pass = render_manager.GetRenderpass(formats[0], formats[1], false);
|
||||
framebuffer = MakeFramebuffer(device, render_pass, width, height, attachments);
|
||||
render_pass = renderpass_cache.GetRenderpass(formats[0], formats[1], false);
|
||||
framebuffer =
|
||||
MakeFramebuffer(device, render_pass, width, height, attachments, num_attachments);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1506,7 +1518,7 @@ Sampler::Sampler(TextureRuntime& runtime, const VideoCore::SamplerParams& params
|
||||
instance.IsCustomBorderColorSupported() && (params.wrap_s == TextureConfig::ClampToBorder ||
|
||||
params.wrap_t == TextureConfig::ClampToBorder);
|
||||
|
||||
const auto color = PicaToVK::ColorRGBA8(params.border_color);
|
||||
const Common::Vec4f color = PicaToVK::ColorRGBA8(params.border_color);
|
||||
const vk::SamplerCustomBorderColorCreateInfoEXT border_color_info = {
|
||||
.customBorderColor = MakeClearColorValue(color),
|
||||
.format = vk::Format::eUndefined,
|
||||
|
@@ -22,9 +22,10 @@ struct Material;
|
||||
namespace Vulkan {
|
||||
|
||||
class Instance;
|
||||
class RenderManager;
|
||||
class RenderpassCache;
|
||||
class DescriptorPool;
|
||||
class DescriptorSetProvider;
|
||||
class Surface;
|
||||
class DescriptorUpdateQueue;
|
||||
|
||||
struct Handle {
|
||||
VmaAllocation alloc;
|
||||
@@ -41,8 +42,8 @@ class TextureRuntime {
|
||||
|
||||
public:
|
||||
explicit TextureRuntime(const Instance& instance, Scheduler& scheduler,
|
||||
RenderManager& render_manager, DescriptorUpdateQueue& update_queue,
|
||||
u32 num_swapchain_images);
|
||||
RenderpassCache& renderpass_cache, DescriptorPool& pool,
|
||||
DescriptorSetProvider& texture_provider, u32 num_swapchain_images);
|
||||
~TextureRuntime();
|
||||
|
||||
const Instance& GetInstance() const {
|
||||
@@ -53,8 +54,8 @@ public:
|
||||
return scheduler;
|
||||
}
|
||||
|
||||
RenderManager& GetRenderpassCache() {
|
||||
return render_manager;
|
||||
RenderpassCache& GetRenderpassCache() {
|
||||
return renderpass_cache;
|
||||
}
|
||||
|
||||
/// Returns the removal threshold ticks for the garbage collector
|
||||
@@ -73,12 +74,7 @@ public:
|
||||
bool ClearTexture(Surface& surface, const VideoCore::TextureClear& clear);
|
||||
|
||||
/// Copies a rectangle of src_tex to another rectange of dst_rect
|
||||
bool CopyTextures(Surface& source, Surface& dest,
|
||||
std::span<const VideoCore::TextureCopy> copies);
|
||||
|
||||
bool CopyTextures(Surface& source, Surface& dest, const VideoCore::TextureCopy& copy) {
|
||||
return CopyTextures(source, dest, std::array{copy});
|
||||
}
|
||||
bool CopyTextures(Surface& source, Surface& dest, const VideoCore::TextureCopy& copy);
|
||||
|
||||
/// Blits a rectangle of src_tex to another rectange of dst_rect
|
||||
bool BlitTextures(Surface& surface, Surface& dest, const VideoCore::TextureBlit& blit);
|
||||
@@ -89,6 +85,9 @@ public:
|
||||
/// Returns true if the provided pixel format needs convertion
|
||||
bool NeedsConversion(VideoCore::PixelFormat format) const;
|
||||
|
||||
/// Removes any descriptor sets that contain the provided image view.
|
||||
void FreeDescriptorSetsWithImage(vk::ImageView image_view);
|
||||
|
||||
private:
|
||||
/// Clears a partial texture rect using a clear rectangle
|
||||
void ClearTextureWithRenderpass(Surface& surface, const VideoCore::TextureClear& clear);
|
||||
@@ -96,7 +95,8 @@ private:
|
||||
private:
|
||||
const Instance& instance;
|
||||
Scheduler& scheduler;
|
||||
RenderManager& render_manager;
|
||||
RenderpassCache& renderpass_cache;
|
||||
DescriptorSetProvider& texture_provider;
|
||||
BlitHelper blit_helper;
|
||||
StreamBuffer upload_buffer;
|
||||
StreamBuffer download_buffer;
|
||||
|
@@ -106,11 +106,7 @@ FragmentModule::FragmentModule(const FSConfig& config_, const Profile& profile_)
|
||||
out.reserve(RESERVE_SIZE);
|
||||
DefineExtensions();
|
||||
DefineInterface();
|
||||
if (profile.is_vulkan) {
|
||||
DefineBindingsVK();
|
||||
} else {
|
||||
DefineBindingsGL();
|
||||
}
|
||||
DefineBindings();
|
||||
DefineHelpers();
|
||||
DefineShadowHelpers();
|
||||
DefineLightingHelpers();
|
||||
@@ -1276,43 +1272,7 @@ void FragmentModule::DefineInterface() {
|
||||
out += "layout (location = 0) out vec4 color;\n\n";
|
||||
}
|
||||
|
||||
void FragmentModule::DefineBindingsVK() {
|
||||
// Uniform and texture buffers
|
||||
out += FSUniformBlockDef;
|
||||
out += "layout(set = 0, binding = 3) uniform samplerBuffer texture_buffer_lut_lf;\n";
|
||||
out += "layout(set = 0, binding = 4) uniform samplerBuffer texture_buffer_lut_rg;\n";
|
||||
out += "layout(set = 0, binding = 5) uniform samplerBuffer texture_buffer_lut_rgba;\n\n";
|
||||
|
||||
// Texture samplers
|
||||
const auto texture_type = config.texture.texture0_type.Value();
|
||||
const auto sampler_tex0 = [&] {
|
||||
switch (texture_type) {
|
||||
case TextureType::Shadow2D:
|
||||
case TextureType::ShadowCube:
|
||||
return "usampler2D";
|
||||
case TextureType::TextureCube:
|
||||
return "samplerCube";
|
||||
default:
|
||||
return "sampler2D";
|
||||
}
|
||||
}();
|
||||
for (u32 i = 0; i < 3; i++) {
|
||||
const auto sampler = i == 0 ? sampler_tex0 : "sampler2D";
|
||||
const auto num_descriptors = i == 0 && texture_type == TextureType::ShadowCube ? "[6]" : "";
|
||||
out += fmt::format("layout(set = 1, binding = {0}) uniform {1} tex{0}{2};\n", i, sampler,
|
||||
num_descriptors);
|
||||
}
|
||||
|
||||
// Utility textures
|
||||
if (config.framebuffer.shadow_rendering) {
|
||||
out += "layout(set = 2, binding = 0, r32ui) uniform uimage2D shadow_buffer;\n\n";
|
||||
}
|
||||
if (config.user.use_custom_normal) {
|
||||
out += "layout(set = 2, binding = 1) uniform sampler2D tex_normal;\n";
|
||||
}
|
||||
}
|
||||
|
||||
void FragmentModule::DefineBindingsGL() {
|
||||
void FragmentModule::DefineBindings() {
|
||||
// Uniform and texture buffers
|
||||
out += FSUniformBlockDef;
|
||||
out += "layout(binding = 3) uniform samplerBuffer texture_buffer_lut_lf;\n";
|
||||
@@ -1320,32 +1280,33 @@ void FragmentModule::DefineBindingsGL() {
|
||||
out += "layout(binding = 5) uniform samplerBuffer texture_buffer_lut_rgba;\n\n";
|
||||
|
||||
// Texture samplers
|
||||
const auto texunit_set = profile.is_vulkan ? "set = 1, " : "";
|
||||
const auto texture_type = config.texture.texture0_type.Value();
|
||||
for (u32 i = 0; i < 3; i++) {
|
||||
const auto sampler =
|
||||
i == 0 && texture_type == TextureType::TextureCube ? "samplerCube" : "sampler2D";
|
||||
out += fmt::format("layout(binding = {0}) uniform {1} tex{0};\n", i, sampler);
|
||||
out +=
|
||||
fmt::format("layout({0}binding = {1}) uniform {2} tex{1};\n", texunit_set, i, sampler);
|
||||
}
|
||||
|
||||
// Utility textures
|
||||
if (config.user.use_custom_normal) {
|
||||
if (config.user.use_custom_normal && !profile.is_vulkan) {
|
||||
out += "layout(binding = 6) uniform sampler2D tex_normal;\n";
|
||||
}
|
||||
if (use_blend_fallback) {
|
||||
if (use_blend_fallback && !profile.is_vulkan) {
|
||||
out += "layout(location = 7) uniform sampler2D tex_color;\n";
|
||||
}
|
||||
|
||||
// Shadow textures
|
||||
if (texture_type == TextureType::Shadow2D || texture_type == TextureType::ShadowCube) {
|
||||
static constexpr std::array postfixes = {"px", "nx", "py", "ny", "pz", "nz"};
|
||||
for (u32 i = 0; i < postfixes.size(); i++) {
|
||||
out += fmt::format(
|
||||
"layout(binding = {}, r32ui) uniform readonly uimage2D shadow_texture_{};\n", i,
|
||||
postfixes[i]);
|
||||
}
|
||||
// Storage images
|
||||
static constexpr std::array postfixes = {"px", "nx", "py", "ny", "pz", "nz"};
|
||||
const auto shadow_set = profile.is_vulkan ? "set = 2, " : "";
|
||||
for (u32 i = 0; i < postfixes.size(); i++) {
|
||||
out += fmt::format(
|
||||
"layout({}binding = {}, r32ui) uniform readonly uimage2D shadow_texture_{};\n",
|
||||
shadow_set, i, postfixes[i]);
|
||||
}
|
||||
if (config.framebuffer.shadow_rendering) {
|
||||
out += "layout(binding = 6, r32ui) uniform uimage2D shadow_buffer;\n\n";
|
||||
out += fmt::format("layout({}binding = 6, r32ui) uniform uimage2D shadow_buffer;\n\n",
|
||||
shadow_set);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1453,48 +1414,19 @@ float mix2(vec4 s, vec2 a) {
|
||||
)";
|
||||
|
||||
if (config.texture.texture0_type == TexturingRegs::TextureConfig::Shadow2D) {
|
||||
if (profile.is_vulkan) {
|
||||
out += R"(
|
||||
out += R"(
|
||||
float SampleShadow2D(ivec2 uv, uint z) {
|
||||
if (any(bvec4(lessThan(uv, ivec2(0)), greaterThanEqual(uv, textureSize(tex0, 0)))))
|
||||
return 1.0;
|
||||
return CompareShadow(texelFetch(tex0, uv, 0).x, z);
|
||||
}
|
||||
|
||||
vec4 shadowTexture(vec2 uv, float w) {
|
||||
)";
|
||||
if (!config.texture.shadow_texture_orthographic) {
|
||||
out += "uv /= w;";
|
||||
}
|
||||
out += R"(
|
||||
uint z = uint(max(0, int(min(abs(w), 1.0) * float(0xFFFFFF)) - shadow_texture_bias));
|
||||
vec2 coord = vec2(textureSize(tex0, 0)) * uv - vec2(0.5);
|
||||
vec2 coord_floor = floor(coord);
|
||||
vec2 f = coord - coord_floor;
|
||||
ivec2 i = ivec2(coord_floor);
|
||||
vec4 s = vec4(
|
||||
SampleShadow2D(i , z),
|
||||
SampleShadow2D(i + ivec2(1, 0), z),
|
||||
SampleShadow2D(i + ivec2(0, 1), z),
|
||||
SampleShadow2D(i + ivec2(1, 1), z));
|
||||
return vec4(mix2(s, f));
|
||||
}
|
||||
)";
|
||||
|
||||
} else {
|
||||
out += R"(
|
||||
float SampleShadow2D(ivec2 uv, uint z) {
|
||||
if (any(bvec4(lessThan(uv, ivec2(0)), greaterThanEqual(uv, imageSize(shadow_texture_px)))))
|
||||
if (any(bvec4( lessThan(uv, ivec2(0)), greaterThanEqual(uv, imageSize(shadow_texture_px)) )))
|
||||
return 1.0;
|
||||
return CompareShadow(imageLoad(shadow_texture_px, uv).x, z);
|
||||
}
|
||||
|
||||
vec4 shadowTexture(vec2 uv, float w) {
|
||||
)";
|
||||
if (!config.texture.shadow_texture_orthographic) {
|
||||
out += "uv /= w;";
|
||||
}
|
||||
out += R"(
|
||||
if (!config.texture.shadow_texture_orthographic) {
|
||||
out += "uv /= w;";
|
||||
}
|
||||
out += R"(
|
||||
uint z = uint(max(0, int(min(abs(w), 1.0) * float(0xFFFFFF)) - shadow_texture_bias));
|
||||
vec2 coord = vec2(imageSize(shadow_texture_px)) * uv - vec2(0.5);
|
||||
vec2 coord_floor = floor(coord);
|
||||
@@ -1508,75 +1440,8 @@ vec4 shadowTexture(vec2 uv, float w) {
|
||||
return vec4(mix2(s, f));
|
||||
}
|
||||
)";
|
||||
}
|
||||
} else if (config.texture.texture0_type == TexturingRegs::TextureConfig::ShadowCube) {
|
||||
if (profile.is_vulkan) {
|
||||
out += R"(
|
||||
uvec4 SampleShadowCube(int face, ivec2 i00, ivec2 i10, ivec2 i01, ivec2 i11) {
|
||||
return uvec4(
|
||||
texelFetch(tex0[face], i00, 0).r,
|
||||
texelFetch(tex0[face], i10, 0).r,
|
||||
texelFetch(tex0[face], i01, 0).r,
|
||||
texelFetch(tex0[face], i11, 0).r);
|
||||
}
|
||||
|
||||
vec4 shadowTextureCube(vec2 uv, float w) {
|
||||
ivec2 size = textureSize(tex0[0], 0);
|
||||
vec3 c = vec3(uv, w);
|
||||
vec3 a = abs(c);
|
||||
if (a.x > a.y && a.x > a.z) {
|
||||
w = a.x;
|
||||
uv = -c.zy;
|
||||
if (c.x < 0.0) uv.x = -uv.x;
|
||||
} else if (a.y > a.z) {
|
||||
w = a.y;
|
||||
uv = c.xz;
|
||||
if (c.y < 0.0) uv.y = -uv.y;
|
||||
} else {
|
||||
w = a.z;
|
||||
uv = -c.xy;
|
||||
if (c.z > 0.0) uv.x = -uv.x;
|
||||
}
|
||||
uint z = uint(max(0, int(min(w, 1.0) * float(0xFFFFFF)) - shadow_texture_bias));
|
||||
vec2 coord = vec2(size) * (uv / w * vec2(0.5) + vec2(0.5)) - vec2(0.5);
|
||||
vec2 coord_floor = floor(coord);
|
||||
vec2 f = coord - coord_floor;
|
||||
ivec2 i00 = ivec2(coord_floor);
|
||||
ivec2 i10 = i00 + ivec2(1, 0);
|
||||
ivec2 i01 = i00 + ivec2(0, 1);
|
||||
ivec2 i11 = i00 + ivec2(1, 1);
|
||||
ivec2 cmin = ivec2(0), cmax = size - ivec2(1, 1);
|
||||
i00 = clamp(i00, cmin, cmax);
|
||||
i10 = clamp(i10, cmin, cmax);
|
||||
i01 = clamp(i01, cmin, cmax);
|
||||
i11 = clamp(i11, cmin, cmax);
|
||||
uvec4 pixels;
|
||||
if (a.x > a.y && a.x > a.z) {
|
||||
if (c.x > 0.0)
|
||||
pixels = SampleShadowCube(0, i00, i10, i01, i11);
|
||||
else
|
||||
pixels = SampleShadowCube(1, i00, i10, i01, i11);
|
||||
} else if (a.y > a.z) {
|
||||
if (c.y > 0.0)
|
||||
pixels = SampleShadowCube(2, i00, i10, i01, i11);
|
||||
else
|
||||
pixels = SampleShadowCube(3, i00, i10, i01, i11);
|
||||
} else {
|
||||
if (c.z > 0.0)
|
||||
pixels = SampleShadowCube(4, i00, i10, i01, i11);
|
||||
else
|
||||
pixels = SampleShadowCube(5, i00, i10, i01, i11);
|
||||
}
|
||||
vec4 s = vec4(
|
||||
CompareShadow(pixels.x, z),
|
||||
CompareShadow(pixels.y, z),
|
||||
CompareShadow(pixels.z, z),
|
||||
CompareShadow(pixels.w, z));
|
||||
return vec4(mix2(s, f));
|
||||
}
|
||||
)";
|
||||
} else {
|
||||
out += R"(
|
||||
out += R"(
|
||||
vec4 shadowTextureCube(vec2 uv, float w) {
|
||||
ivec2 size = imageSize(shadow_texture_px);
|
||||
vec3 c = vec3(uv, w);
|
||||
@@ -1658,7 +1523,6 @@ vec4 shadowTextureCube(vec2 uv, float w) {
|
||||
return vec4(mix2(s, f));
|
||||
}
|
||||
)";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -74,8 +74,7 @@ private:
|
||||
|
||||
void DefineExtensions();
|
||||
void DefineInterface();
|
||||
void DefineBindingsVK();
|
||||
void DefineBindingsGL();
|
||||
void DefineBindings();
|
||||
void DefineHelpers();
|
||||
void DefineLightingHelpers();
|
||||
void DefineShadowHelpers();
|
||||
|
Reference in New Issue
Block a user