diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ad0665c5b..b848e93d5 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -12,13 +12,13 @@ jobs: if: ${{ !github.head_ref }} runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: submodules: recursive - name: Pack run: ./.ci/source.sh - name: Upload - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: source path: artifacts/ @@ -37,11 +37,11 @@ jobs: OS: linux TARGET: ${{ matrix.target }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: submodules: recursive - name: Set up cache - uses: actions/cache@v3 + uses: actions/cache@v4 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@v3 + uses: actions/upload-artifact@v4 if: ${{ matrix.target == 'appimage' }} with: name: ${{ env.OS }}-${{ env.TARGET }} @@ -70,11 +70,11 @@ jobs: OS: macos TARGET: ${{ matrix.target }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: submodules: recursive - name: Set up cache - uses: actions/cache@v3 + uses: actions/cache@v4 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@v3 + uses: actions/cache/save@v4 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@v3 + - uses: actions/checkout@v4 - name: Download x86_64 build from cache - uses: actions/cache/restore@v3 + uses: actions/cache/restore@v4 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@v3 + uses: actions/cache/restore@v4 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@v3 + uses: actions/upload-artifact@v4 with: name: ${{ env.OS }}-${{ env.TARGET }} path: artifacts/ @@ -137,11 +137,11 @@ jobs: OS: windows TARGET: ${{ matrix.target }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: submodules: recursive - name: Set up cache - uses: actions/cache@v3 + uses: actions/cache@v4 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@v3 + uses: actions/upload-artifact@v4 with: name: ${{ env.OS }}-${{ env.TARGET }} path: artifacts/ @@ -192,11 +192,11 @@ jobs: OS: android TARGET: universal steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: submodules: recursive - name: Set up cache - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: | ~/.gradle/caches @@ -228,7 +228,7 @@ jobs: env: UNPACKED: 1 - name: Upload - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: ${{ env.OS }}-${{ env.TARGET }} path: src/android/app/artifacts/ @@ -242,11 +242,11 @@ jobs: OS: ios TARGET: arm64 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: submodules: recursive - name: Set up cache - uses: actions/cache@v3 + uses: actions/cache@v4 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@v3 + - uses: actions/download-artifact@v4 - name: Create release uses: actions/create-release@v1 env: diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml index 574a24e62..0d9a30caf 100644 --- a/.github/workflows/format.yml +++ b/.github/workflows/format.yml @@ -13,7 +13,7 @@ jobs: image: citraemu/build-environments:linux-fresh options: -u 1001 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 - name: Build diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 4c46597f4..8c63dc6bf 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -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@v3 + - uses: actions/checkout@v4 name: Pre-checkout with: submodules: false - - uses: actions/github-script@v6 + - uses: actions/github-script@v7 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@v3 + - uses: actions/checkout@v4 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@v6 + - uses: actions/github-script@v7 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@v3 + - uses: actions/checkout@v4 name: Pre-checkout with: submodules: false - - uses: actions/github-script@v6 + - uses: actions/github-script@v7 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@v3 + - uses: actions/checkout@v4 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@v6 + - uses: actions/github-script@v7 name: 'Check and merge canary changes' if: ${{ steps.check-changes.outputs.result == 'true' }} env: diff --git a/.github/workflows/transifex.yml b/.github/workflows/transifex.yml index 64d6e5866..b7a695f2b 100644 --- a/.github/workflows/transifex.yml +++ b/.github/workflows/transifex.yml @@ -10,7 +10,7 @@ jobs: container: citraemu/build-environments:linux-fresh if: ${{ github.repository == 'citra-emu/citra' }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: submodules: recursive fetch-depth: 0 diff --git a/externals/CMakeLists.txt b/externals/CMakeLists.txt index 5f4c23ad3..fc45473b6 100644 --- a/externals/CMakeLists.txt +++ b/externals/CMakeLists.txt @@ -294,11 +294,20 @@ endif() add_library(httplib INTERFACE) if(USE_SYSTEM_CPP_HTTPLIB) find_package(CppHttp 0.14.1) - 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...") + # 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) + 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() endif() else() target_include_directories(httplib SYSTEM INTERFACE ./httplib) diff --git a/src/android/app/src/main/java/org/citra/citra_emu/CitraApplication.kt b/src/android/app/src/main/java/org/citra/citra_emu/CitraApplication.kt index c414d4246..75a88baf1 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/CitraApplication.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/CitraApplication.kt @@ -9,10 +9,13 @@ 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() { @@ -53,9 +56,20 @@ 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 diff --git a/src/android/app/src/main/java/org/citra/citra_emu/NativeLibrary.kt b/src/android/app/src/main/java/org/citra/citra_emu/NativeLibrary.kt index 75150fd16..bfbe658f8 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/NativeLibrary.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/NativeLibrary.kt @@ -413,12 +413,12 @@ object NativeLibrary { } fun setEmulationActivity(emulationActivity: EmulationActivity?) { - Log.verbose("[NativeLibrary] Registering EmulationActivity.") + Log.debug("[NativeLibrary] Registering EmulationActivity.") sEmulationActivity = WeakReference(emulationActivity) } fun clearEmulationActivity() { - Log.verbose("[NativeLibrary] Unregistering EmulationActivity.") + Log.debug("[NativeLibrary] Unregistering EmulationActivity.") sEmulationActivity.clear() } diff --git a/src/android/app/src/main/java/org/citra/citra_emu/utils/DirectoryInitialization.kt b/src/android/app/src/main/java/org/citra/citra_emu/utils/DirectoryInitialization.kt index 10e509f23..c48f9d22e 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/utils/DirectoryInitialization.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/utils/DirectoryInitialization.kt @@ -94,14 +94,14 @@ object DirectoryInitialization { val dataPath = PermissionsHandler.citraDirectory if (dataPath.toString().isNotEmpty()) { userPath = dataPath.toString() - Log.debug("[DirectoryInitialization] User Dir: $userPath") + android.util.Log.d("[Citra Frontend]", "[DirectoryInitialization] User Dir: $userPath") return true } return false } private fun copyAsset(asset: String, output: File, overwrite: Boolean, context: Context) { - Log.verbose("[DirectoryInitialization] Copying File $asset to $output") + Log.debug("[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.verbose("[DirectoryInitialization] Copying Folder $assetFolder to $outputFolder") + Log.debug("[DirectoryInitialization] Copying Folder $assetFolder to $outputFolder") try { var createdFolder = false for (file in context.assets.list(assetFolder)!!) { diff --git a/src/android/app/src/main/java/org/citra/citra_emu/utils/Log.kt b/src/android/app/src/main/java/org/citra/citra_emu/utils/Log.kt index 26c41bc98..f691d51b0 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/utils/Log.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/utils/Log.kt @@ -4,34 +4,17 @@ 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" - fun verbose(message: String?) { - if (BuildConfig.DEBUG) { - Log.v(TAG, message!!) - } - } + external fun debug(message: String) - fun debug(message: String?) { - if (BuildConfig.DEBUG) { - Log.d(TAG, message!!) - } - } + external fun warning(message: String) - fun info(message: String?) = Log.i(TAG, message!!) + external fun info(message: String) - fun warning(message: String?) = Log.w(TAG, message!!) + external fun error(message: String) - fun error(message: String?) = Log.e(TAG, message!!) + external fun critical(message: String) } diff --git a/src/android/app/src/main/java/org/citra/citra_emu/utils/MemoryUtil.kt b/src/android/app/src/main/java/org/citra/citra_emu/utils/MemoryUtil.kt new file mode 100644 index 000000000..4bf1d88c7 --- /dev/null +++ b/src/android/app/src/main/java/org/citra/citra_emu/utils/MemoryUtil.kt @@ -0,0 +1,108 @@ +// 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) +} diff --git a/src/android/app/src/main/jni/CMakeLists.txt b/src/android/app/src/main/jni/CMakeLists.txt index 84a34441a..233d568ed 100644 --- a/src/android/app/src/main/jni/CMakeLists.txt +++ b/src/android/app/src/main/jni/CMakeLists.txt @@ -28,6 +28,7 @@ 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) diff --git a/src/android/app/src/main/jni/native_log.cpp b/src/android/app/src/main/jni/native_log.cpp new file mode 100644 index 000000000..86a57b99f --- /dev/null +++ b/src/android/app/src/main/jni/native_log.cpp @@ -0,0 +1,30 @@ +// SPDX-FileCopyrightText: 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include +#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" diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml index 303632162..781e78da1 100644 --- a/src/android/app/src/main/res/values/strings.xml +++ b/src/android/app/src/main/res/values/strings.xml @@ -442,6 +442,17 @@ \"%s\" must be decrypted before being used with Citra.\n A real 3DS is required An unknown error occurred while installing \"%s\".\n Please see the log for more details + + %1$s %2$s + Byte + B + KB + MB + GB + TB + PB + EB + Change Theme Mode Follow System diff --git a/src/audio_core/hle/source.cpp b/src/audio_core/hle/source.cpp index 54a28bee3..d3fbfd0cb 100644 --- a/src/audio_core/hle/source.cpp +++ b/src/audio_core/hle/source.cpp @@ -298,9 +298,9 @@ void Source::ParseConfig(SourceConfiguration::Configuration& config, b.buffer_id, state.mono_or_stereo, state.format, - true, - {}, // 0 in u32_dsp - false, + true, // from_queue + 0, // play_position + false, // has_played }); } LOG_TRACE(Audio_DSP, "enqueuing queued {} addr={:#010x} len={} id={}", i, @@ -321,7 +321,11 @@ void Source::ParseConfig(SourceConfiguration::Configuration& config, void Source::GenerateFrame() { current_frame.fill({}); - if (state.current_buffer.empty() && !DequeueBuffer()) { + if (state.current_buffer.empty()) { + // TODO(SachinV): Should dequeue happen at the end of the frame generation? + if (DequeueBuffer()) { + return; + } state.enabled = false; state.buffer_update = true; state.last_buffer_id = state.current_buffer_id; @@ -330,8 +334,6 @@ 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; @@ -358,7 +360,7 @@ void Source::GenerateFrame() { } // TODO(jroweboy): Keep track of frame_position independently so that it doesn't lose precision // over time - state.next_sample_number += static_cast(frame_position * state.rate_multiplier); + state.current_sample_number += static_cast(frame_position * state.rate_multiplier); state.filters.ProcessFrame(current_frame); } @@ -409,7 +411,6 @@ 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; @@ -420,8 +421,17 @@ bool Source::DequeueBuffer() { state.input_queue.push(buf); } - 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()); + // 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); return true; } diff --git a/src/audio_core/hle/source.h b/src/audio_core/hle/source.h index a3af06d27..d7114952b 100644 --- a/src/audio_core/hle/source.h +++ b/src/audio_core/hle/source.h @@ -87,8 +87,8 @@ private: Format format; bool from_queue; - u32_dsp play_position; // = 0; - bool has_played; // = false; + u32 play_position; // = 0; + bool has_played; // = false; private: template @@ -136,7 +136,6 @@ private: // Current buffer u32 current_sample_number = 0; - u32 next_sample_number = 0; PAddr current_buffer_physical_address = 0; AudioInterp::StereoBuffer16 current_buffer = {}; @@ -171,7 +170,6 @@ 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; diff --git a/src/citra_qt/configuration/config.cpp b/src/citra_qt/configuration/config.cpp index 389a3becf..54c8c4f4b 100644 --- a/src/citra_qt/configuration/config.cpp +++ b/src/citra_qt/configuration/config.cpp @@ -54,7 +54,7 @@ const std::array, 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 Config::default_hotkeys {{ +const std::array 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,6 +71,11 @@ const std::array 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}}, @@ -557,6 +562,15 @@ 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); @@ -1074,6 +1088,15 @@ 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) { diff --git a/src/citra_qt/configuration/config.h b/src/citra_qt/configuration/config.h index 27be93711..521c6baf9 100644 --- a/src/citra_qt/configuration/config.h +++ b/src/citra_qt/configuration/config.h @@ -26,7 +26,7 @@ public: static const std::array default_buttons; static const std::array, Settings::NativeAnalog::NumAnalogs> default_analogs; - static const std::array default_hotkeys; + static const std::array default_hotkeys; private: void Initialize(const std::string& config_name); diff --git a/src/citra_qt/main.cpp b/src/citra_qt/main.cpp index 807da30e3..5d8b4204b 100644 --- a/src/citra_qt/main.cpp +++ b/src/citra_qt/main.cpp @@ -647,6 +647,13 @@ 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 @@ -3190,8 +3197,10 @@ int main(int argc, char* argv[]) { QApplication::setHighDpiScaleFactorRoundingPolicy(rounding_policy); #ifdef __APPLE__ - std::string bin_path = FileUtil::GetBundleDirectory() + DIR_SEP + ".."; - chdir(bin_path.c_str()); + auto bundle_dir = FileUtil::GetBundleDirectory(); + if (bundle_dir) { + FileUtil::SetCurrentDir(bundle_dir.value() + ".."); + } #endif #ifdef ENABLE_OPENGL diff --git a/src/citra_qt/multiplayer/direct_connect.cpp b/src/citra_qt/multiplayer/direct_connect.cpp index 936fd0435..bdc866a75 100644 --- a/src/citra_qt/multiplayer/direct_connect.cpp +++ b/src/citra_qt/multiplayer/direct_connect.cpp @@ -80,9 +80,8 @@ void DirectConnectWindow::Connect() { // Store settings UISettings::values.nickname = ui->nickname->text(); UISettings::values.ip = ui->ip->text(); - UISettings::values.port = (ui->port->isModified() && !ui->port->text().isEmpty()) - ? ui->port->text() - : UISettings::values.port; + UISettings::values.port = + !ui->port->text().isEmpty() ? ui->port->text() : UISettings::values.port; // attempt to connect in a different thread QFuture f = QtConcurrent::run([&] { diff --git a/src/citra_qt/multiplayer/lobby.cpp b/src/citra_qt/multiplayer/lobby.cpp index 7cd2ae7e1..00671b842 100644 --- a/src/citra_qt/multiplayer/lobby.cpp +++ b/src/citra_qt/multiplayer/lobby.cpp @@ -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,6 +74,12 @@ Lobby::Lobby(Core::System& system_, QWidget* parent, QStandardItemModel* list, connect(&room_list_watcher, &QFutureWatcher::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 @@ -180,6 +186,10 @@ 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() { diff --git a/src/citra_qt/multiplayer/lobby_p.h b/src/citra_qt/multiplayer/lobby_p.h index 16db78eae..f1890fdef 100644 --- a/src/citra_qt/multiplayer/lobby_p.h +++ b/src/citra_qt/multiplayer/lobby_p.h @@ -188,12 +188,37 @@ public: } QVariant data(int role) const override { - if (role != Qt::DisplayRole) { + 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: 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 { diff --git a/src/citra_qt/uisettings.h b/src/citra_qt/uisettings.h index 8e671f49f..6dedff0f0 100644 --- a/src/citra_qt/uisettings.h +++ b/src/citra_qt/uisettings.h @@ -138,6 +138,11 @@ struct Values { QString room_description; std::pair, std::vector> ban_list; + QString multiplayer_filter_text; + bool multiplayer_filter_games_owned; + bool multiplayer_filter_hide_empty; + bool multiplayer_filter_hide_full; + // logging Settings::Setting show_console{false, "showConsole"}; }; diff --git a/src/common/common_paths.h b/src/common/common_paths.h index da9c04f2f..f12eedb0c 100644 --- a/src/common/common_paths.h +++ b/src/common/common_paths.h @@ -13,7 +13,6 @@ #endif // The user data dir -#define ROOT_DIR "." #define USERDATA_DIR "user" #ifdef USER_DIR #define EMU_DATA_DIR USER_DIR diff --git a/src/common/file_util.cpp b/src/common/file_util.cpp index 1acbb2724..cda752e5f 100644 --- a/src/common/file_util.cpp +++ b/src/common/file_util.cpp @@ -634,6 +634,10 @@ std::optional GetCurrentDir() { std::string strDir = dir; #endif free(dir); + + if (!strDir.ends_with(DIR_SEP)) { + strDir += DIR_SEP; + } return strDir; } // namespace FileUtil @@ -646,17 +650,36 @@ bool SetCurrentDir(const std::string& directory) { } #if defined(__APPLE__) -std::string GetBundleDirectory() { - CFURLRef BundleRef; - char AppBundlePath[MAXPATHLEN]; +std::optional GetBundleDirectory() { // Get the main bundle for the app - BundleRef = CFBundleCopyBundleURL(CFBundleGetMainBundle()); - CFStringRef BundlePath = CFURLCopyFileSystemPath(BundleRef, kCFURLPOSIXPathStyle); - CFStringGetFileSystemRepresentation(BundlePath, AppBundlePath, sizeof(AppBundlePath)); - CFRelease(BundleRef); - CFRelease(BundlePath); + CFBundleRef bundle_ref = CFBundleGetMainBundle(); + if (!bundle_ref) { + return {}; + } - return AppBundlePath; + 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; } #endif @@ -732,22 +755,6 @@ 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 g_paths; std::unordered_map g_default_paths; @@ -777,8 +784,10 @@ 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 - if (FileUtil::Exists(ROOT_DIR DIR_SEP USERDATA_DIR)) { - user_path = ROOT_DIR DIR_SEP USERDATA_DIR DIR_SEP; + 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; g_paths.emplace(UserPath::ConfigDir, user_path + CONFIG_DIR DIR_SEP); g_paths.emplace(UserPath::CacheDir, user_path + CACHE_DIR DIR_SEP); } else { diff --git a/src/common/file_util.h b/src/common/file_util.h index 2a4cc70ef..6595fead9 100644 --- a/src/common/file_util.h +++ b/src/common/file_util.h @@ -193,11 +193,8 @@ 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::string GetBundleDirectory(); +[[nodiscard]] std::optional GetBundleDirectory(); #endif #ifdef _WIN32 diff --git a/src/core/hle/kernel/config_mem.cpp b/src/core/hle/kernel/config_mem.cpp index e8f612160..eab8a9f67 100644 --- a/src/core/hle/kernel/config_mem.cpp +++ b/src/core/hle/kernel/config_mem.cpp @@ -14,18 +14,18 @@ namespace ConfigMem { Handler::Handler() { std::memset(&config_mem, 0, sizeof(config_mem)); - // Values extracted from firmware 11.2.0-35E - config_mem.kernel_version_min = 0x34; + // Values extracted from firmware 11.17.0-50E + config_mem.kernel_version_min = 0x3a; 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 = 0x0000F297; - config_mem.firm_version_min = 0x34; + config_mem.ctr_sdk_ver = 0x0000F450; + config_mem.firm_version_min = 0x3a; config_mem.firm_version_maj = 0x2; config_mem.firm_sys_core_ver = 0x2; - config_mem.firm_ctr_sdk_ver = 0x0000F297; + config_mem.firm_ctr_sdk_ver = 0x0000F450; } ConfigMemDef& Handler::GetConfigMem() { diff --git a/src/core/hle/kernel/process.cpp b/src/core/hle/kernel/process.cpp index 17f142cfd..3d0a315ef 100644 --- a/src/core/hle/kernel/process.cpp +++ b/src/core/hle/kernel/process.cpp @@ -210,10 +210,10 @@ void Process::Set3dsxKernelCaps() { }; // Similar to Rosalina, we set kernel version to a recent one. - // This is 11.2.0, to be consistent with core/hle/kernel/config_mem.cpp + // This is 11.17.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 = 0x234; + kernel_version = 0x23a; } void Process::Run(s32 main_thread_priority, u32 stack_size) { diff --git a/src/core/hle/service/apt/applet_manager.cpp b/src/core/hle/service/apt/applet_manager.cpp index fe640b5c5..0d657cd9f 100644 --- a/src/core/hle/service/apt/applet_manager.cpp +++ b/src/core/hle/service/apt/applet_manager.cpp @@ -373,7 +373,10 @@ ResultVal AppletManager::Initialize(AppletId ap if (active_slot == AppletSlot::Error) { active_slot = slot; - // Wake up the application. + // APT automatically calls enable on the first registered applet. + Enable(attributes); + + // Wake up the applet. SendParameter({ .sender_id = AppletId::None, .destination_id = app_id, @@ -398,7 +401,8 @@ Result AppletManager::Enable(AppletAttributes attributes) { auto slot_data = GetAppletSlot(slot); slot_data->registered = true; - if (slot_data->attributes.applet_pos == AppletPos::System && + if (slot_data->applet_id != AppletId::None && + 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}.", @@ -786,16 +790,23 @@ Result AppletManager::PrepareToStartSystemApplet(AppletId applet_id) { Result AppletManager::StartSystemApplet(AppletId applet_id, std::shared_ptr object, const std::vector& buffer) { - auto source_applet_id = AppletId::None; + auto source_applet_id = AppletId::Application; if (last_system_launcher_slot != AppletSlot::Error) { - const auto slot_data = GetAppletSlot(last_system_launcher_slot); - source_applet_id = slot_data->applet_id; + const auto launcher_slot_data = GetAppletSlot(last_system_launcher_slot); + source_applet_id = launcher_slot_data->applet_id; - // 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(); + // 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. } } diff --git a/src/core/hle/service/apt/applet_manager.h b/src/core/hle/service/apt/applet_manager.h index 2c4d0879e..dee8afa4e 100644 --- a/src/core/hle/service/apt/applet_manager.h +++ b/src/core/hle/service/apt/applet_manager.h @@ -152,6 +152,7 @@ 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) {} diff --git a/src/video_core/renderer_opengl/pica_to_gl.h b/src/video_core/renderer_opengl/pica_to_gl.h index 15a53b771..fa9409465 100644 --- a/src/video_core/renderer_opengl/pica_to_gl.h +++ b/src/video_core/renderer_opengl/pica_to_gl.h @@ -148,8 +148,6 @@ 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; } diff --git a/src/video_core/renderer_vulkan/pica_to_vk.h b/src/video_core/renderer_vulkan/pica_to_vk.h index 6067c07ae..b0dfe891c 100644 --- a/src/video_core/renderer_vulkan/pica_to_vk.h +++ b/src/video_core/renderer_vulkan/pica_to_vk.h @@ -96,7 +96,10 @@ inline vk::BlendFactor BlendFunc(Pica::FramebufferRegs::BlendFactor factor) { }}; const auto index = static_cast(factor); - ASSERT_MSG(index < blend_func_table.size(), "Unknown blend factor {}", index); + if (index >= blend_func_table.size()) { + LOG_CRITICAL(Render_Vulkan, "Unknown blend factor {}", index); + return vk::BlendFactor::eOne; + } return blend_func_table[index]; } diff --git a/src/video_core/renderer_vulkan/vk_instance.cpp b/src/video_core/renderer_vulkan/vk_instance.cpp index 2303df203..c0e14e4a2 100644 --- a/src/video_core/renderer_vulkan/vk_instance.cpp +++ b/src/video_core/renderer_vulkan/vk_instance.cpp @@ -4,6 +4,7 @@ #include #include +#include #include "common/assert.h" #include "common/settings.h" @@ -153,6 +154,12 @@ 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(); @@ -629,7 +636,7 @@ void Instance::CreateAllocator() { .device = *device, .pVulkanFunctions = &functions, .instance = *instance, - .vulkanApiVersion = properties.apiVersion, + .vulkanApiVersion = TargetVulkanApiVersion, }; const VkResult result = vmaCreateAllocator(&allocator_info, &allocator); @@ -670,7 +677,7 @@ void Instance::CollectToolingInfo() { if (!tooling_info) { return; } - const auto tools = physical_device.getToolProperties(); + const auto tools = physical_device.getToolPropertiesEXT(); for (const vk::PhysicalDeviceToolProperties& tool : tools) { const std::string_view name = tool.name; LOG_INFO(Render_Vulkan, "Attached debugging tool: {}", name); diff --git a/src/video_core/renderer_vulkan/vk_platform.cpp b/src/video_core/renderer_vulkan/vk_platform.cpp index e8b13e938..fa8f76199 100644 --- a/src/video_core/renderer_vulkan/vk_platform.cpp +++ b/src/video_core/renderer_vulkan/vk_platform.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include "common/assert.h" #include "common/logging/log.h" @@ -291,13 +292,14 @@ vk::UniqueInstance CreateInstance(const Common::DynamicLibrary& library, } VULKAN_HPP_DEFAULT_DISPATCHER.init(vkGetInstanceProcAddr); - 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 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))); } const auto extensions = GetInstanceExtensions(window_type, enable_validation); @@ -307,7 +309,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 = VK_API_VERSION_1_3, + .apiVersion = TargetVulkanApiVersion, }; boost::container::static_vector layers; diff --git a/src/video_core/renderer_vulkan/vk_platform.h b/src/video_core/renderer_vulkan/vk_platform.h index 20e9ebb0d..68f6457e0 100644 --- a/src/video_core/renderer_vulkan/vk_platform.h +++ b/src/video_core/renderer_vulkan/vk_platform.h @@ -19,6 +19,8 @@ enum class WindowSystemType : u8; namespace Vulkan { +constexpr u32 TargetVulkanApiVersion = VK_API_VERSION_1_1; + using DebugCallback = std::variant; diff --git a/src/video_core/renderer_vulkan/vk_texture_runtime.cpp b/src/video_core/renderer_vulkan/vk_texture_runtime.cpp index 0688d439b..1b9728c1e 100644 --- a/src/video_core/renderer_vulkan/vk_texture_runtime.cpp +++ b/src/video_core/renderer_vulkan/vk_texture_runtime.cpp @@ -3,6 +3,7 @@ // Refer to the license.txt file included. #include +#include #include "common/literals.h" #include "common/microprofile.h" @@ -118,9 +119,9 @@ u32 UnpackDepthStencil(const VideoCore::StagingData& data, vk::Format dest) { } boost::container::small_vector MakeInitBarriers( - vk::ImageAspectFlags aspect, std::span images, std::size_t num_images) { + vk::ImageAspectFlags aspect, std::span images) { boost::container::small_vector barriers; - for (std::size_t i = 0; i < num_images; i++) { + for (const vk::Image& image : images) { barriers.push_back(vk::ImageMemoryBarrier{ .srcAccessMask = vk::AccessFlagBits::eNone, .dstAccessMask = vk::AccessFlagBits::eNone, @@ -128,7 +129,7 @@ boost::container::small_vector MakeInitBarriers( .newLayout = vk::ImageLayout::eGeneral, .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, - .image = images[i], + .image = image, .subresourceRange{ .aspectMask = aspect, .baseMipLevel = 0, @@ -218,11 +219,10 @@ 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 attachments, - u32 num_attachments) { + u32 height, std::span attachments) { const vk::FramebufferCreateInfo framebuffer_info = { .renderPass = render_pass, - .attachmentCount = num_attachments, + .attachmentCount = static_cast(attachments.size()), .pAttachments = attachments.data(), .width = width, .height = height, @@ -710,8 +710,7 @@ Surface::Surface(TextureRuntime& runtime_, const VideoCore::SurfaceParams& param ASSERT_MSG(format != vk::Format::eUndefined && levels >= 1, "Image allocation parameters are invalid"); - u32 num_images = 0; - std::array raw_images; + boost::container::static_vector raw_images; vk::ImageCreateFlags flags{}; if (texture_type == VideoCore::TextureType::CubeMap) { @@ -724,18 +723,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[num_images++] = handles[0].image; + raw_images.emplace_back(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[num_images++] = handles[1].image; + raw_images.emplace_back(handles[1].image); } runtime->render_manager.EndRendering(); - scheduler->Record([raw_images, num_images, aspect = traits.aspect](vk::CommandBuffer cmdbuf) { - const auto barriers = MakeInitBarriers(aspect, raw_images, num_images); + scheduler->Record([raw_images, aspect = traits.aspect](vk::CommandBuffer cmdbuf) { + const auto barriers = MakeInitBarriers(aspect, raw_images); cmdbuf.pipelineBarrier(vk::PipelineStageFlagBits::eTopOfPipe, vk::PipelineStageFlagBits::eTopOfPipe, vk::DependencyFlagBits::eByRegion, {}, {}, barriers); @@ -753,8 +752,7 @@ Surface::Surface(TextureRuntime& runtime_, const VideoCore::SurfaceBase& surface const bool has_normal = mat && mat->Map(MapType::Normal); const vk::Format format = traits.native; - u32 num_images = 0; - std::array raw_images; + boost::container::static_vector raw_images; vk::ImageCreateFlags flags{}; if (texture_type == VideoCore::TextureType::CubeMap) { @@ -764,23 +762,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[num_images++] = handles[0].image; + raw_images.emplace_back(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[num_images++] = handles[1].image; + raw_images.emplace_back(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[num_images++] = handles[2].image; + raw_images.emplace_back(handles[2].image); } runtime->render_manager.EndRendering(); - scheduler->Record([raw_images, num_images, aspect = traits.aspect](vk::CommandBuffer cmdbuf) { - const auto barriers = MakeInitBarriers(aspect, raw_images, num_images); + scheduler->Record([raw_images, aspect = traits.aspect](vk::CommandBuffer cmdbuf) { + const auto barriers = MakeInitBarriers(aspect, raw_images); cmdbuf.pipelineBarrier(vk::PipelineStageFlagBits::eTopOfPipe, vk::PipelineStageFlagBits::eTopOfPipe, vk::DependencyFlagBits::eByRegion, {}, {}, barriers); @@ -817,11 +815,10 @@ void Surface::Upload(const VideoCore::BufferTextureCopy& upload, scheduler->Record([buffer = runtime->upload_buffer.Handle(), format = traits.native, params, staging, upload](vk::CommandBuffer cmdbuf) { - u32 num_copies = 1; - std::array buffer_image_copies; + boost::container::static_vector buffer_image_copies; const auto rect = upload.texture_rect; - buffer_image_copies[0] = vk::BufferImageCopy{ + buffer_image_copies.emplace_back(vk::BufferImageCopy{ .bufferOffset = upload.buffer_offset, .bufferRowLength = rect.GetWidth(), .bufferImageHeight = rect.GetHeight(), @@ -833,15 +830,16 @@ void Surface::Upload(const VideoCore::BufferTextureCopy& upload, }, .imageOffset = {static_cast(rect.left), static_cast(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[1]; + + vk::BufferImageCopy& stencil_copy = + buffer_image_copies.emplace_back(buffer_image_copies[0]); 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 = { @@ -869,7 +867,7 @@ void Surface::Upload(const VideoCore::BufferTextureCopy& upload, vk::DependencyFlagBits::eByRegion, {}, {}, read_barrier); cmdbuf.copyBufferToImage(buffer, params.src_image, vk::ImageLayout::eTransferDstOptimal, - num_copies, buffer_image_copies.data()); + buffer_image_copies); cmdbuf.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer, params.pipeline_flags, vk::DependencyFlagBits::eByRegion, {}, {}, write_barrier); @@ -1077,7 +1075,7 @@ void Surface::ScaleUp(u32 new_scale) { runtime->render_manager.EndRendering(); scheduler->Record( [raw_images = std::array{Image()}, aspect = traits.aspect](vk::CommandBuffer cmdbuf) { - const auto barriers = MakeInitBarriers(aspect, raw_images, raw_images.size()); + const auto barriers = MakeInitBarriers(aspect, raw_images); cmdbuf.pipelineBarrier(vk::PipelineStageFlagBits::eTopOfPipe, vk::PipelineStageFlagBits::eTopOfPipe, vk::DependencyFlagBits::eByRegion, {}, {}, barriers); @@ -1343,7 +1341,7 @@ vk::Framebuffer Surface::Framebuffer() noexcept { runtime->render_manager.GetRenderpass(color_format, depth_format, false); const auto attachments = std::array{ImageView()}; framebuffers[index] = MakeFramebuffer(instance->GetDevice(), render_pass, GetScaledWidth(), - GetScaledHeight(), attachments, 1); + GetScaledHeight(), attachments); return framebuffers[index].get(); } @@ -1473,17 +1471,16 @@ Framebuffer::Framebuffer(TextureRuntime& runtime, const VideoCore::FramebufferPa image_views[index] = shadow_rendering ? surface->StorageView() : surface->FramebufferView(); }; - u32 num_attachments = 0; - std::array attachments; + boost::container::static_vector attachments; if (color) { prepare(0, color); - attachments[num_attachments++] = image_views[0]; + attachments.emplace_back(image_views[0]); } if (depth) { prepare(1, depth); - attachments[num_attachments++] = image_views[1]; + attachments.emplace_back(image_views[1]); } const vk::Device device = runtime.GetInstance().GetDevice(); @@ -1491,11 +1488,10 @@ Framebuffer::Framebuffer(TextureRuntime& runtime, const VideoCore::FramebufferPa render_pass = render_manager.GetRenderpass(PixelFormat::Invalid, PixelFormat::Invalid, false); framebuffer = MakeFramebuffer(device, render_pass, color->GetScaledWidth(), - color->GetScaledHeight(), {}, 0); + color->GetScaledHeight(), {}); } else { render_pass = render_manager.GetRenderpass(formats[0], formats[1], false); - framebuffer = - MakeFramebuffer(device, render_pass, width, height, attachments, num_attachments); + framebuffer = MakeFramebuffer(device, render_pass, width, height, attachments); } }