Compare commits

..

1 Commits

Author SHA1 Message Date
GPUCode
bac61d012c service: Stub mcu::HWC 2024-02-09 18:41:39 +02:00
80 changed files with 1064 additions and 1388 deletions

View File

@@ -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:

View File

@@ -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

View File

@@ -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:

View File

@@ -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

View File

@@ -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)

View File

@@ -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.

View File

@@ -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"

View File

@@ -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

View File

@@ -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()
}

View File

@@ -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)!!) {

View File

@@ -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!!)
}

View File

@@ -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)
}

View File

@@ -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)

View File

@@ -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"

View File

@@ -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>

View File

@@ -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"/>

View File

@@ -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")
}
}

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -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) {

View File

@@ -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);

View File

@@ -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

View File

@@ -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([&] {

View File

@@ -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() {

View File

@@ -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 {

View File

@@ -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"};
};

View File

@@ -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

View File

@@ -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 {

View File

@@ -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

View File

@@ -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() {

View File

@@ -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) {

View File

@@ -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();
}
}

View File

@@ -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) {}

View File

@@ -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

View File

@@ -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

View File

@@ -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");
}

View File

@@ -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},
}};
}

View File

@@ -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];

View File

@@ -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;
}

View File

@@ -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);

View File

@@ -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;
}

View File

@@ -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];
}

View File

@@ -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,

View File

@@ -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{};
};

View File

@@ -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(),

View File

@@ -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;

View File

@@ -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>

View 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

View 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

View File

@@ -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

View File

@@ -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

View File

@@ -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) {

View File

@@ -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;

View File

@@ -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);

View File

@@ -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;
}

View File

@@ -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;
};

View File

@@ -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");

View File

@@ -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;

View File

@@ -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;

View File

@@ -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>;

View File

@@ -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);
}
}

View File

@@ -20,7 +20,7 @@ namespace Vulkan {
class Instance;
class Swapchain;
class Scheduler;
class RenderManager;
class RenderpassCache;
struct Frame {
u32 width;

View File

@@ -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);

View File

@@ -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};
};

View File

@@ -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;

View File

@@ -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);

View File

@@ -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

View File

@@ -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

View File

@@ -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 {

View File

@@ -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;

View File

@@ -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 {};
}

View File

@@ -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));
}
}

View File

@@ -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.

View File

@@ -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);
}
}
}

View File

@@ -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,

View File

@@ -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;

View File

@@ -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));
}
)";
}
}
}
}

View File

@@ -74,8 +74,7 @@ private:
void DefineExtensions();
void DefineInterface();
void DefineBindingsVK();
void DefineBindingsGL();
void DefineBindings();
void DefineHelpers();
void DefineLightingHelpers();
void DefineShadowHelpers();