Merge branch 'azahar-emu:master' into master

This commit is contained in:
Dragoon Dorise 2026-01-03 23:55:06 +01:00 committed by GitHub
commit f2cfaa3de4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
58 changed files with 1149 additions and 361 deletions

View File

@ -28,6 +28,7 @@ import org.citra.citra_emu.activities.EmulationActivity
import org.citra.citra_emu.utils.FileUtil
import org.citra.citra_emu.utils.Log
import org.citra.citra_emu.utils.RemovableStorageHelper
import org.citra.citra_emu.viewmodel.CompressProgressDialogViewModel
import java.lang.ref.WeakReference
import java.util.Date
@ -600,6 +601,47 @@ object NativeLibrary {
*/
external fun logDeviceInfo()
enum class CompressStatus(val value: Int) {
SUCCESS(0),
COMPRESS_UNSUPPORTED(1),
COMPRESS_ALREADY_COMPRESSED(2),
COMPRESS_FAILED(3),
DECOMPRESS_UNSUPPORTED(4),
DECOMPRESS_NOT_COMPRESSED(5),
DECOMPRESS_FAILED(6),
INSTALLED_APPLICATION(7);
companion object {
fun fromValue(value: Int): CompressStatus =
CompressStatus.entries.first { it.value == value }
}
}
// Compression / Decompression
private external fun compressFileNative(inputPath: String?, outputPath: String): Int
fun compressFile(inputPath: String?, outputPath: String): CompressStatus {
return CompressStatus.fromValue(
compressFileNative(inputPath, outputPath)
)
}
private external fun decompressFileNative(inputPath: String?, outputPath: String): Int
fun decompressFile(inputPath: String?, outputPath: String): CompressStatus {
return CompressStatus.fromValue(
decompressFileNative(inputPath, outputPath)
)
}
external fun getRecommendedExtension(inputPath: String?, shouldCompress: Boolean): String
@Keep
@JvmStatic
fun onCompressProgress(total: Long, current: Long) {
CompressProgressDialogViewModel.update(total, current)
}
@Keep
@JvmStatic
fun createFile(directory: String, filename: String): Boolean =

View File

@ -342,6 +342,7 @@ class EmulationActivity : AppCompatActivity() {
preferences.getInt(InputBindingSetting.getInputAxisButtonKey(axis), -1)
val guestOrientation =
preferences.getInt(InputBindingSetting.getInputAxisOrientationKey(axis), -1)
val inverted = preferences.getBoolean(InputBindingSetting.getInputAxisInvertedKey(axis),false);
if (nextMapping == -1 || guestOrientation == -1) {
// Axis is unmapped
continue
@ -350,6 +351,8 @@ class EmulationActivity : AppCompatActivity() {
// Skip joystick wobble
value = 0f
}
if (inverted) value = -value;
when (nextMapping) {
NativeLibrary.ButtonType.STICK_LEFT -> {
axisValuesCirclePad[guestOrientation] = value

View File

@ -57,7 +57,12 @@ import org.citra.citra_emu.utils.FileUtil
import org.citra.citra_emu.utils.GameIconUtils
import org.citra.citra_emu.viewmodel.GamesViewModel
class GameAdapter(private val activity: AppCompatActivity, private val inflater: LayoutInflater, private val openImageLauncher: ActivityResultLauncher<String>?) :
class GameAdapter(
private val activity: AppCompatActivity,
private val inflater: LayoutInflater,
private val openImageLauncher: ActivityResultLauncher<String>?,
private val onRequestCompressOrDecompress: ((inputPath: String, suggestedName: String, shouldCompress: Boolean) -> Unit)? = null
) :
ListAdapter<Game, GameViewHolder>(AsyncDifferConfig.Builder(DiffCallback()).build()),
View.OnClickListener, View.OnLongClickListener {
private var lastClickTime = 0L
@ -441,6 +446,27 @@ class GameAdapter(private val activity: AppCompatActivity, private val inflater:
bottomSheetDialog.dismiss()
}
val compressDecompressButton = bottomSheetView.findViewById<MaterialButton>(R.id.compress_decompress)
if (game.isInstalled) {
compressDecompressButton.setOnClickListener {
Toast.makeText(
context,
context.getString(R.string.compress_decompress_installed_app),
Toast.LENGTH_LONG
).show()
}
compressDecompressButton.alpha = 0.38f
} else {
compressDecompressButton.setOnClickListener {
val shouldCompress = !game.isCompressed
val recommendedExt = NativeLibrary.getRecommendedExtension(holder.game.path, shouldCompress)
val baseName = holder.game.filename.substringBeforeLast('.')
onRequestCompressOrDecompress?.invoke(holder.game.path, "$baseName.$recommendedExt", shouldCompress)
bottomSheetDialog.dismiss()
}
}
compressDecompressButton.text = context.getString(if (!game.isCompressed) R.string.compress else R.string.decompress)
bottomSheetView.findViewById<MaterialButton>(R.id.menu_button_open).setOnClickListener {
showOpenContextMenu(it, game)
}

View File

@ -63,4 +63,37 @@ enum class SecondaryDisplayLayout(val int: Int) {
return entries.firstOrNull { it.int == int } ?: NONE
}
}
}
enum class StereoWhichDisplay(val int: Int) {
// These must match what is defined in src/common/settings.h
NONE(0), // equivalent to StereoRenderOption = Off
BOTH(1),
PRIMARY_ONLY(2),
SECONDARY_ONLY(3);
companion object {
fun from(int: Int): StereoWhichDisplay {
return entries.firstOrNull { it.int == int } ?: NONE
}
}
}
enum class StereoMode(val int: Int) {
// These must match what is defined in src/common/settings.h
OFF(0),
SIDE_BY_SIDE(1),
SIDE_BY_SIDE_FULL(2),
ANAGLYPH(3),
INTERLACED(4),
REVERSE_INTERLACED (5),
CARDBOARD_VR (6);
companion object {
fun from(int: Int): StereoMode {
return entries.firstOrNull { it.int == int } ?: OFF
}
}
}

View File

@ -19,6 +19,7 @@ enum class BooleanSetting(
INSTANT_DEBUG_LOG("instant_debug_log", Settings.SECTION_DEBUG, false),
ENABLE_RPC_SERVER("enable_rpc_server", Settings.SECTION_DEBUG, false),
CUSTOM_LAYOUT("custom_layout",Settings.SECTION_LAYOUT,false),
SWAP_EYES_3D("swap_eyes_3d",Settings.SECTION_RENDERER,false),
PERF_OVERLAY_ENABLE("performance_overlay_enable", Settings.SECTION_LAYOUT, false),
PERF_OVERLAY_SHOW_FPS("performance_overlay_show_fps", Settings.SECTION_LAYOUT, true),
PERF_OVERLAY_SHOW_FRAMETIME("performance_overlay_show_frame_time", Settings.SECTION_LAYOUT, false),
@ -87,7 +88,8 @@ enum class BooleanSetting(
USE_ARTIC_BASE_CONTROLLER,
COMPRESS_INSTALLED_CIA_CONTENT,
ANDROID_HIDE_IMAGES,
PERF_OVERLAY_ENABLE // Works in overlay options, but not from the settings menu
PERF_OVERLAY_ENABLE, // Works in overlay options, but not from the settings menu
APPLY_REGION_FREE_PATCH
)
fun from(key: String): BooleanSetting? =

View File

@ -17,7 +17,7 @@ enum class IntSetting(
CAMERA_OUTER_RIGHT_FLIP("camera_outer_right_flip", Settings.SECTION_CAMERA, 0),
GRAPHICS_API("graphics_api", Settings.SECTION_RENDERER, 1),
RESOLUTION_FACTOR("resolution_factor", Settings.SECTION_RENDERER, 1),
STEREOSCOPIC_3D_MODE("render_3d", Settings.SECTION_RENDERER, 0),
STEREOSCOPIC_3D_MODE("render_3d", Settings.SECTION_RENDERER, 2),
STEREOSCOPIC_3D_DEPTH("factor_3d", Settings.SECTION_RENDERER, 0),
STEPS_PER_HOUR("steps_per_hour", Settings.SECTION_SYSTEM, 0),
CARDBOARD_SCREEN_SIZE("cardboard_screen_size", Settings.SECTION_LAYOUT, 85),
@ -53,6 +53,7 @@ enum class IntSetting(
ORIENTATION_OPTION("screen_orientation", Settings.SECTION_LAYOUT, 2),
TURBO_LIMIT("turbo_limit", Settings.SECTION_CORE, 200),
PERFORMANCE_OVERLAY_POSITION("performance_overlay_position", Settings.SECTION_LAYOUT, 0),
RENDER_3D_WHICH_DISPLAY("render_3d_which_display",Settings.SECTION_RENDERER,0),
ASPECT_RATIO("aspect_ratio", Settings.SECTION_LAYOUT, 0);
override var int: Int = defaultValue

View File

@ -170,6 +170,7 @@ class InputBindingSetting(
.remove(oldKey) // Used for button mapping
.remove(oldKey + "_GuestOrientation") // Used for axis orientation
.remove(oldKey + "_GuestButton") // Used for axis button
.remove(oldKey + "_Inverted") // used for axis inversion
.apply()
// Also clear from config.ini
@ -209,7 +210,7 @@ class InputBindingSetting(
/**
* Helper function to write a gamepad axis mapping for the setting.
*/
private fun writeAxisMapping(axis: Int, value: Int) {
private fun writeAxisMapping(axis: Int, value: Int, inverted: Boolean) {
// Cleanup old mapping
removeOldMapping()
@ -217,6 +218,7 @@ class InputBindingSetting(
preferences.edit()
.putInt(getInputAxisOrientationKey(axis), if (isHorizontalOrientation()) 0 else 1)
.putInt(getInputAxisButtonKey(axis), value)
.putBoolean(getInputAxisInvertedKey(axis),inverted)
// Write next reverse mapping for future cleanup
.putString(reverseKey, getInputAxisKey(axis))
.apply()
@ -251,7 +253,7 @@ class InputBindingSetting(
*
* @param device InputDevice from which the input event originated.
* @param motionRange MotionRange of the movement
* @param axisDir Either '-' or '+' (currently unused)
* @param axisDir Either '-' or '+'
*/
fun onMotionInput(device: InputDevice, motionRange: MotionRange, axisDir: Char) {
if (!isAxisMappingSupported()) {
@ -267,8 +269,8 @@ class InputBindingSetting(
} else {
buttonCode
}
writeAxisMapping(motionRange.axis, button)
val uiString = "${device.name}: Axis ${motionRange.axis}"
writeAxisMapping(motionRange.axis, button, axisDir == '-')
val uiString = "${device.name}: Axis ${motionRange.axis}" + axisDir
value = uiString
}
@ -323,6 +325,11 @@ class InputBindingSetting(
*/
fun getInputAxisButtonKey(axis: Int): String = "${getInputAxisKey(axis)}_GuestButton"
/**
* Helper function to get the settings key for an whether a gamepad axis is inverted.
*/
fun getInputAxisInvertedKey(axis: Int): String = "${getInputAxisKey(axis)}_Inverted"
/**
* Helper function to get the settings key for an gamepad axis orientation.
*/

View File

@ -16,6 +16,9 @@ import androidx.preference.PreferenceManager
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import org.citra.citra_emu.CitraApplication
import org.citra.citra_emu.R
import org.citra.citra_emu.display.ScreenLayout
import org.citra.citra_emu.display.StereoMode
import org.citra.citra_emu.display.StereoWhichDisplay
import org.citra.citra_emu.features.settings.model.AbstractBooleanSetting
import org.citra.citra_emu.features.settings.model.AbstractIntSetting
import org.citra.citra_emu.features.settings.model.AbstractSetting
@ -44,7 +47,6 @@ import org.citra.citra_emu.utils.BirthdayMonth
import org.citra.citra_emu.utils.Log
import org.citra.citra_emu.utils.SystemSaveGame
import org.citra.citra_emu.utils.ThemeUtil
import org.citra.citra_emu.utils.EmulationMenuSettings
class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView) {
private var menuTag: String? = null
@ -111,20 +113,24 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
}
/** Returns the portrait mode width */
private fun getWidth(): Int {
val dm = Resources.getSystem().displayMetrics;
return if (dm.widthPixels < dm.heightPixels)
dm.widthPixels
else
dm.heightPixels
private fun getDimensions(): IntArray {
val dm = Resources.getSystem().displayMetrics
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
val wm = settingsActivity.windowManager.maximumWindowMetrics
val height = wm.bounds.height().coerceAtLeast(dm.heightPixels)
val width = wm.bounds.width().coerceAtLeast(dm.widthPixels)
intArrayOf(width, height)
} else {
intArrayOf(dm.widthPixels, dm.heightPixels)
}
}
private fun getHeight(): Int {
val dm = Resources.getSystem().displayMetrics;
return if (dm.widthPixels < dm.heightPixels)
dm.heightPixels
else
dm.widthPixels
private fun getSmallerDimension(): Int {
return getDimensions().min()
}
private fun getLargerDimension(): Int {
return getDimensions().max()
}
private fun addConfigSettings(sl: ArrayList<SettingsItem>) {
@ -945,17 +951,30 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
)
add(HeaderSetting(R.string.stereoscopy))
add(
SingleChoiceSetting(
IntSetting.RENDER_3D_WHICH_DISPLAY,
R.string.render_3d_which_display,
R.string.render_3d_which_display_description,
R.array.render3dWhichDisplay,
R.array.render3dDisplayValues,
IntSetting.RENDER_3D_WHICH_DISPLAY.key,
IntSetting.RENDER_3D_WHICH_DISPLAY.defaultValue
)
)
add(
SingleChoiceSetting(
IntSetting.STEREOSCOPIC_3D_MODE,
R.string.render3d,
0,
R.string.render3d_description,
R.array.render3dModes,
R.array.render3dValues,
IntSetting.STEREOSCOPIC_3D_MODE.key,
IntSetting.STEREOSCOPIC_3D_MODE.defaultValue
IntSetting.STEREOSCOPIC_3D_MODE.defaultValue,
isEnabled = IntSetting.RENDER_3D_WHICH_DISPLAY.int != StereoWhichDisplay.NONE.int
)
)
add(
SliderSetting(
IntSetting.STEREOSCOPIC_3D_DEPTH,
@ -978,6 +997,17 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
)
)
add(
SwitchSetting(
BooleanSetting.SWAP_EYES_3D,
R.string.swap_eyes_3d,
R.string.swap_eyes_3d_description,
BooleanSetting.SWAP_EYES_3D.key,
BooleanSetting.SWAP_EYES_3D.defaultValue,
isEnabled = IntSetting.RENDER_3D_WHICH_DISPLAY.int != StereoWhichDisplay.NONE.int
)
)
add(HeaderSetting(R.string.cardboard_vr))
add(
SliderSetting(
@ -988,7 +1018,8 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
100,
"%",
IntSetting.CARDBOARD_SCREEN_SIZE.key,
IntSetting.CARDBOARD_SCREEN_SIZE.defaultValue.toFloat()
IntSetting.CARDBOARD_SCREEN_SIZE.defaultValue.toFloat(),
isEnabled = IntSetting.STEREOSCOPIC_3D_MODE.int == StereoMode.CARDBOARD_VR.int
)
)
add(
@ -1000,7 +1031,8 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
100,
"%",
IntSetting.CARDBOARD_X_SHIFT.key,
IntSetting.CARDBOARD_X_SHIFT.defaultValue.toFloat()
IntSetting.CARDBOARD_X_SHIFT.defaultValue.toFloat(),
isEnabled = IntSetting.STEREOSCOPIC_3D_MODE.int == StereoMode.CARDBOARD_VR.int
)
)
add(
@ -1012,7 +1044,8 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
100,
"%",
IntSetting.CARDBOARD_Y_SHIFT.key,
IntSetting.CARDBOARD_Y_SHIFT.defaultValue.toFloat()
IntSetting.CARDBOARD_Y_SHIFT.defaultValue.toFloat(),
isEnabled = IntSetting.STEREOSCOPIC_3D_MODE.int == StereoMode.CARDBOARD_VR.int
)
)
@ -1146,7 +1179,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
R.array.aspectRatioValues,
IntSetting.ASPECT_RATIO.key,
IntSetting.ASPECT_RATIO.defaultValue,
isEnabled = IntSetting.SCREEN_LAYOUT.int == 1,
isEnabled = IntSetting.SCREEN_LAYOUT.int == ScreenLayout.SINGLE_SCREEN.int,
)
)
add(
@ -1194,7 +1227,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
"%",
FloatSetting.SECOND_SCREEN_OPACITY.key,
FloatSetting.SECOND_SCREEN_OPACITY.defaultValue,
isEnabled = IntSetting.SCREEN_LAYOUT.int == 5
isEnabled = IntSetting.SCREEN_LAYOUT.int == ScreenLayout.CUSTOM_LAYOUT.int
)
)
add(HeaderSetting(R.string.bg_color, R.string.bg_color_description))
@ -1405,7 +1438,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
R.string.emulation_custom_layout_x,
0,
0,
getHeight(),
getLargerDimension(),
"px",
IntSetting.LANDSCAPE_TOP_X.key,
IntSetting.LANDSCAPE_TOP_X.defaultValue.toFloat()
@ -1417,7 +1450,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
R.string.emulation_custom_layout_y,
0,
0,
getWidth(),
getSmallerDimension(),
"px",
IntSetting.LANDSCAPE_TOP_Y.key,
IntSetting.LANDSCAPE_TOP_Y.defaultValue.toFloat()
@ -1429,7 +1462,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
R.string.emulation_custom_layout_width,
0,
0,
getHeight(),
getLargerDimension(),
"px",
IntSetting.LANDSCAPE_TOP_WIDTH.key,
IntSetting.LANDSCAPE_TOP_WIDTH.defaultValue.toFloat()
@ -1441,7 +1474,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
R.string.emulation_custom_layout_height,
0,
0,
getWidth(),
getSmallerDimension(),
"px",
IntSetting.LANDSCAPE_TOP_HEIGHT.key,
IntSetting.LANDSCAPE_TOP_HEIGHT.defaultValue.toFloat()
@ -1454,7 +1487,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
R.string.emulation_custom_layout_x,
0,
0,
getHeight(),
getLargerDimension(),
"px",
IntSetting.LANDSCAPE_BOTTOM_X.key,
IntSetting.LANDSCAPE_BOTTOM_X.defaultValue.toFloat()
@ -1466,7 +1499,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
R.string.emulation_custom_layout_y,
0,
0,
getWidth(),
getSmallerDimension(),
"px",
IntSetting.LANDSCAPE_BOTTOM_Y.key,
IntSetting.LANDSCAPE_BOTTOM_Y.defaultValue.toFloat()
@ -1478,7 +1511,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
R.string.emulation_custom_layout_width,
0,
0,
getHeight(),
getLargerDimension(),
"px",
IntSetting.LANDSCAPE_BOTTOM_WIDTH.key,
IntSetting.LANDSCAPE_BOTTOM_WIDTH.defaultValue.toFloat()
@ -1490,7 +1523,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
R.string.emulation_custom_layout_height,
0,
0,
getWidth(),
getSmallerDimension(),
"px",
IntSetting.LANDSCAPE_BOTTOM_HEIGHT.key,
IntSetting.LANDSCAPE_BOTTOM_HEIGHT.defaultValue.toFloat()
@ -1510,7 +1543,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
R.string.emulation_custom_layout_x,
0,
0,
getWidth(),
getSmallerDimension(),
"px",
IntSetting.PORTRAIT_TOP_X.key,
IntSetting.PORTRAIT_TOP_X.defaultValue.toFloat()
@ -1522,7 +1555,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
R.string.emulation_custom_layout_y,
0,
0,
getHeight(),
getLargerDimension(),
"px",
IntSetting.PORTRAIT_TOP_Y.key,
IntSetting.PORTRAIT_TOP_Y.defaultValue.toFloat()
@ -1534,7 +1567,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
R.string.emulation_custom_layout_width,
0,
0,
getWidth(),
getSmallerDimension(),
"px",
IntSetting.PORTRAIT_TOP_WIDTH.key,
IntSetting.PORTRAIT_TOP_WIDTH.defaultValue.toFloat()
@ -1546,7 +1579,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
R.string.emulation_custom_layout_height,
0,
0,
getHeight(),
getLargerDimension(),
"px",
IntSetting.PORTRAIT_TOP_HEIGHT.key,
IntSetting.PORTRAIT_TOP_HEIGHT.defaultValue.toFloat()
@ -1559,7 +1592,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
R.string.emulation_custom_layout_x,
0,
0,
getWidth(),
getSmallerDimension(),
"px",
IntSetting.PORTRAIT_BOTTOM_X.key,
IntSetting.PORTRAIT_BOTTOM_X.defaultValue.toFloat()
@ -1571,7 +1604,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
R.string.emulation_custom_layout_y,
0,
0,
getHeight(),
getLargerDimension(),
"px",
IntSetting.PORTRAIT_BOTTOM_Y.key,
IntSetting.PORTRAIT_BOTTOM_Y.defaultValue.toFloat()
@ -1583,7 +1616,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
R.string.emulation_custom_layout_width,
0,
0,
getWidth(),
getSmallerDimension(),
"px",
IntSetting.PORTRAIT_BOTTOM_WIDTH.key,
IntSetting.PORTRAIT_BOTTOM_WIDTH.defaultValue.toFloat()
@ -1595,7 +1628,7 @@ class SettingsFragmentPresenter(private val fragmentView: SettingsFragmentView)
R.string.emulation_custom_layout_height,
0,
0,
getHeight(),
getLargerDimension(),
"px",
IntSetting.PORTRAIT_BOTTOM_HEIGHT.key,
IntSetting.PORTRAIT_BOTTOM_HEIGHT.defaultValue.toFloat()

View File

@ -0,0 +1,89 @@
// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
package org.citra.citra_emu.fragments
import android.app.Dialog
import android.os.Bundle
import android.view.View
import android.widget.ProgressBar
import androidx.fragment.app.DialogFragment
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.lifecycle.Lifecycle
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.launch
import org.citra.citra_emu.R
import org.citra.citra_emu.viewmodel.CompressProgressDialogViewModel
import org.citra.citra_emu.NativeLibrary
class CompressProgressDialogFragment : DialogFragment() {
private lateinit var progressBar: ProgressBar
private var outputPath: String? = null
private var isCompressing: Boolean = true
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
arguments?.let {
isCompressing = it.getBoolean(ARG_IS_COMPRESSING, true)
outputPath = it.getString(ARG_OUTPUT_PATH)
}
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val view = layoutInflater.inflate(R.layout.dialog_compress_progress, null)
progressBar = view.findViewById(R.id.compress_progress)
val label = view.findViewById<android.widget.TextView>(R.id.compress_label)
label.text = if (isCompressing) getString(R.string.compressing) else getString(R.string.decompressing)
isCancelable = false
progressBar.isIndeterminate = true
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
combine(CompressProgressDialogViewModel.total, CompressProgressDialogViewModel.progress) { total, progress ->
total to progress
}.collectLatest { (total, progress) ->
if (total <= 0) {
progressBar.isIndeterminate = true
label.visibility = View.GONE
} else {
progressBar.isIndeterminate = false
label.visibility = View.VISIBLE
progressBar.max = total
progressBar.setProgress(progress, true)
}
}
}
}
val builder = MaterialAlertDialogBuilder(requireContext())
.setView(view)
.setCancelable(false)
.setNegativeButton(android.R.string.cancel) { _: android.content.DialogInterface, _: Int ->
outputPath?.let { path ->
NativeLibrary.deleteDocument(path)
}
}
return builder.show()
}
companion object {
const val TAG = "CompressProgressDialog"
private const val ARG_IS_COMPRESSING = "isCompressing"
private const val ARG_OUTPUT_PATH = "outputPath"
fun newInstance(isCompressing: Boolean, outputPath: String?): CompressProgressDialogFragment {
val frag = CompressProgressDialogFragment()
val args = Bundle()
args.putBoolean(ARG_IS_COMPRESSING, isCompressing)
args.putString(ARG_OUTPUT_PATH, outputPath)
frag.arguments = args
return frag
}
}
}

View File

@ -30,14 +30,17 @@ import androidx.recyclerview.widget.GridLayoutManager
import com.google.android.material.color.MaterialColors
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.transition.MaterialFadeThrough
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
import org.citra.citra_emu.CitraApplication
import org.citra.citra_emu.NativeLibrary
import org.citra.citra_emu.R
import org.citra.citra_emu.adapters.GameAdapter
import org.citra.citra_emu.databinding.FragmentGamesBinding
import org.citra.citra_emu.features.settings.model.Settings
import org.citra.citra_emu.model.Game
import org.citra.citra_emu.viewmodel.CompressProgressDialogViewModel
import org.citra.citra_emu.viewmodel.GamesViewModel
import org.citra.citra_emu.viewmodel.HomeViewModel
@ -56,6 +59,58 @@ class GamesFragment : Fragment() {
gameAdapter.handleShortcutImageResult(uri)
}
private var shouldCompress: Boolean = true
private var pendingCompressInvocation: String? = null
companion object {
fun doCompression(fragment: Fragment, gamesViewModel: GamesViewModel, inputPath: String?, outputUri: Uri?, shouldCompress: Boolean) {
if (outputUri != null) {
CompressProgressDialogViewModel.reset()
val dialog = CompressProgressDialogFragment.newInstance(shouldCompress, outputUri.toString())
dialog.showNow(
fragment.requireActivity().supportFragmentManager,
CompressProgressDialogFragment.TAG
)
fragment.lifecycleScope.launch(Dispatchers.IO) {
val status = if (shouldCompress) {
NativeLibrary.compressFile(inputPath, outputUri.toString())
} else {
NativeLibrary.decompressFile(inputPath, outputUri.toString())
}
fragment.requireActivity().runOnUiThread {
dialog.dismiss()
val resId = when (status) {
NativeLibrary.CompressStatus.SUCCESS -> if (shouldCompress) R.string.compress_success else R.string.decompress_success
NativeLibrary.CompressStatus.COMPRESS_UNSUPPORTED -> R.string.compress_unsupported
NativeLibrary.CompressStatus.COMPRESS_ALREADY_COMPRESSED -> R.string.compress_already
NativeLibrary.CompressStatus.COMPRESS_FAILED -> R.string.compress_failed
NativeLibrary.CompressStatus.DECOMPRESS_UNSUPPORTED -> R.string.decompress_unsupported
NativeLibrary.CompressStatus.DECOMPRESS_NOT_COMPRESSED -> R.string.decompress_not_compressed
NativeLibrary.CompressStatus.DECOMPRESS_FAILED -> R.string.decompress_failed
NativeLibrary.CompressStatus.INSTALLED_APPLICATION -> R.string.compress_decompress_installed_app
}
MaterialAlertDialogBuilder(fragment.requireContext())
.setMessage(fragment.getString(resId))
.setPositiveButton(android.R.string.ok, null)
.show()
gamesViewModel.reloadGames(false)
}
}
}
}
}
private val onCompressDecompressLauncher = registerForActivityResult(
ActivityResultContracts.CreateDocument("application/octet-stream")
) { uri: Uri? ->
doCompression(this, gamesViewModel, pendingCompressInvocation, uri, shouldCompress)
pendingCompressInvocation = null
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enterTransition = MaterialFadeThrough()
@ -81,7 +136,12 @@ class GamesFragment : Fragment() {
gameAdapter = GameAdapter(
requireActivity() as AppCompatActivity,
inflater,
openImageLauncher
openImageLauncher,
onRequestCompressOrDecompress = { inputPath, suggestedName, shouldCompress ->
pendingCompressInvocation = inputPath
onCompressDecompressLauncher.launch(suggestedName)
this.shouldCompress = shouldCompress
}
)
binding.gridGames.apply {

View File

@ -7,11 +7,13 @@ package org.citra.citra_emu.fragments
import android.annotation.SuppressLint
import android.content.Context
import android.content.SharedPreferences
import android.net.Uri
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.inputmethod.InputMethodManager
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
@ -26,18 +28,19 @@ import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.GridLayoutManager
import info.debatty.java.stringsimilarity.Jaccard
import info.debatty.java.stringsimilarity.JaroWinkler
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.citra.citra_emu.CitraApplication
import org.citra.citra_emu.R
import org.citra.citra_emu.NativeLibrary
import org.citra.citra_emu.adapters.GameAdapter
import org.citra.citra_emu.databinding.FragmentSearchBinding
import org.citra.citra_emu.model.Game
import org.citra.citra_emu.viewmodel.CompressProgressDialogViewModel
import org.citra.citra_emu.viewmodel.GamesViewModel
import org.citra.citra_emu.viewmodel.HomeViewModel
import java.time.temporal.ChronoField
import java.util.Locale
import android.net.Uri
import androidx.activity.result.contract.ActivityResultContracts
class SearchFragment : Fragment() {
private var _binding: FragmentSearchBinding? = null
@ -53,6 +56,15 @@ class SearchFragment : Fragment() {
gameAdapter.handleShortcutImageResult(uri)
}
private var shouldCompress: Boolean = true
private var pendingCompressInvocation: String? = null
private val onCompressDecompressLauncher = registerForActivityResult(
ActivityResultContracts.CreateDocument("application/octet-stream")
) { uri: Uri? ->
GamesFragment.doCompression(this, gamesViewModel, pendingCompressInvocation, uri, shouldCompress)
pendingCompressInvocation = null
}
private lateinit var preferences: SharedPreferences
companion object {
@ -85,7 +97,13 @@ class SearchFragment : Fragment() {
gameAdapter = GameAdapter(
requireActivity() as AppCompatActivity,
inflater,
openImageLauncher
openImageLauncher,
onRequestCompressOrDecompress = { inputPath, suggestedName, shouldCompress ->
pendingCompressInvocation = inputPath
onCompressDecompressLauncher.launch(suggestedName)
this.shouldCompress = shouldCompress
}
)
binding.gridGamesSearch.apply {

View File

@ -27,6 +27,7 @@ class Game(
val isVisibleSystemTitle: Boolean = false,
val icon: IntArray? = null,
val fileType: String = "",
val isCompressed: Boolean = false,
val filename: String,
) : Parcelable {
val keyAddedToLibraryTime get() = "${filename}_AddedToLibraryTime"

View File

@ -90,6 +90,7 @@ object GameHelper {
gameInfo?.getIsVisibleSystemTitle() ?: false,
gameInfo?.getIcon(),
gameInfo?.getFileType() ?: "",
gameInfo?.getFileType()?.contains("(Z)") ?: false,
if (FileUtil.isNativePath(filePath)) {
CitraApplication.documentsTree.getFilename(filePath)
} else {

View File

@ -0,0 +1,33 @@
// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
package org.citra.citra_emu.viewmodel
import androidx.lifecycle.ViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
object CompressProgressDialogViewModel: ViewModel() {
private val _progress = MutableStateFlow(0)
val progress = _progress.asStateFlow()
private val _total = MutableStateFlow(0)
val total = _total.asStateFlow()
private val _message = MutableStateFlow("")
val message = _message.asStateFlow()
fun update(totalBytes: Long, currentBytes: Long) {
val percent = ((currentBytes * 100L) / totalBytes).coerceIn(0L, 100L).toInt()
_total.value = 100
_progress.value = percent
_message.value = ""
}
fun reset() {
_progress.value = 0
_total.value = 0
_message.value = ""
}
}

View File

@ -175,7 +175,8 @@ void Config::ReadValues() {
ReadSetting("Renderer", Settings::values.custom_second_layer_opacity);
ReadSetting("Renderer", Settings::values.delay_game_render_thread_us);
ReadSetting("Renderer", Settings::values.disable_right_eye_render);
ReadSetting("Renderer", Settings::values.swap_eyes_3d);
ReadSetting("Renderer", Settings::values.render_3d_which_display);
// Layout
// Somewhat inelegant solution to ensure layout value is between 0 and 5 on read
// since older config files may have other values

View File

@ -170,13 +170,23 @@ bg_green =
custom_second_layer_opacity =
# Whether and how Stereoscopic 3D should be rendered
# 0 (default): Off, 1: Side by Side, 2: Reverse Side by Side, 3: Anaglyph, 4: Interlaced, 5: Reverse Interlaced, 6: Cardboard VR
# 0: Off, 1: Half Width Side by Side, 2 (default): Full Width Side by Side, 3: Anaglyph, 4: Interlaced, 5: Reverse Interlaced, 6: Cardboard VR
# 0 is no longer supported in the interface, as using render_3d_which_display = 0 has the same effect, but supported here for backwards compatibility
render_3d =
# Change 3D Intensity
# 0 - 255: Intensity. 0 (default)
factor_3d =
# Swap Eyes in 3d
# true: Swap eyes, false (default): Do not swap eyes
swap_eyes_3d =
# Which Display to render 3d mode to
# 0 (default) - None. Equivalent to render_3d=0
# 1: Both, 2: Primary Only, 3: Secondary Only
render_3d_which_display =
# The name of the post processing shader to apply.
# Loaded from shaders if render_3d is off or side by side.
pp_shader_name =

View File

@ -222,7 +222,7 @@ jboolean Java_org_citra_citra_1emu_model_GameInfo_getIsVisibleSystemTitle(JNIEnv
return false;
}
return smdh->flags & Loader::SMDH::Flags::Visible;
return smdh->flags.visible;
}
jstring Java_org_citra_citra_1emu_model_GameInfo_getFileType(JNIEnv* env, jobject obj) {

View File

@ -1,4 +1,4 @@
// Copyright 2019 Citra Emulator Project
// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@ -40,6 +40,7 @@ static jfieldID s_game_info_pointer;
static jclass s_disk_cache_progress_class;
static jmethodID s_disk_cache_load_progress;
static jmethodID s_compress_progress_method;
static std::unordered_map<VideoCore::LoadCallbackStage, jobject> s_java_load_callback_stages;
static jclass s_cia_install_helper_class;
@ -131,6 +132,10 @@ jmethodID GetDiskCacheLoadProgress() {
return s_disk_cache_load_progress;
}
jmethodID GetCompressProgressMethod() {
return s_compress_progress_method;
}
jobject GetJavaLoadCallbackStage(VideoCore::LoadCallbackStage stage) {
const auto it = s_java_load_callback_stages.find(stage);
ASSERT_MSG(it != s_java_load_callback_stages.end(), "Invalid LoadCallbackStage: {}", stage);
@ -205,6 +210,8 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved) {
s_disk_cache_load_progress = env->GetStaticMethodID(
s_disk_cache_progress_class, "loadProgress",
"(Lorg/citra/citra_emu/utils/DiskShaderCacheProgress$LoadCallbackStage;II)V");
s_compress_progress_method =
env->GetStaticMethodID(s_native_library_class, "onCompressProgress", "(JJ)V");
// Initialize LoadCallbackStage map
const auto to_java_load_callback_stage = [env,
load_callback_stage_class](const std::string& stage) {

View File

@ -1,4 +1,4 @@
// Copyright 2019 Citra Emulator Project
// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@ -40,6 +40,7 @@ jfieldID GetGameInfoPointer();
jclass GetDiskCacheProgressClass();
jmethodID GetDiskCacheLoadProgress();
jmethodID GetCompressProgressMethod();
jobject GetJavaLoadCallbackStage(VideoCore::LoadCallbackStage stage);
jclass GetCiaInstallHelperClass();

View File

@ -34,10 +34,12 @@
#include "common/scope_exit.h"
#include "common/settings.h"
#include "common/string_util.h"
#include "common/zstd_compression.h"
#include "core/core.h"
#include "core/frontend/applets/default_applets.h"
#include "core/frontend/camera/factory.h"
#include "core/hle/service/am/am.h"
#include "core/hle/service/fs/archive.h"
#include "core/hle/service/nfc/nfc.h"
#include "core/hw/unique_data.h"
#include "core/loader/loader.h"
@ -74,6 +76,17 @@ namespace {
ANativeWindow* s_surface;
ANativeWindow* s_secondary_surface;
enum class CompressionStatus : jint {
Success = 0,
Compress_Unsupported = 1,
Compress_AlreadyCompressed = 2,
Compress_Failed = 3,
Decompress_Unsupported = 4,
Decompress_NotCompressed = 5,
Decompress_Failed = 6,
Installed_Application = 7,
};
std::shared_ptr<Common::DynamicLibrary> vulkan_library{};
std::unique_ptr<EmuWindow_Android> window;
std::unique_ptr<EmuWindow_Android> secondary_window;
@ -464,6 +477,163 @@ jstring Java_org_citra_citra_1emu_NativeLibrary_getHomeMenuPath(JNIEnv* env,
return ToJString(env, "");
}
static CompressionStatus GetCompressFileInfo(Loader::AppLoader::CompressFileInfo& out_info,
size_t& out_frame_size, const std::string& filepath,
bool compress) {
if (Service::FS::IsInstalledApplication(filepath)) {
return CompressionStatus::Installed_Application;
}
Loader::AppLoader::CompressFileInfo compress_info{};
compress_info.is_supported = false;
size_t frame_size{};
auto loader = Loader::GetLoader(filepath);
if (loader) {
compress_info = loader->GetCompressFileInfo();
frame_size = FileUtil::Z3DSWriteIOFile::DEFAULT_FRAME_SIZE;
} else {
bool is_compressed = false;
if (Service::AM::CheckCIAToInstall(filepath, is_compressed, compress ? true : false) ==
Service::AM::InstallStatus::Success) {
compress_info.is_supported = true;
compress_info.is_compressed = is_compressed;
compress_info.recommended_compressed_extension = "zcia";
compress_info.recommended_uncompressed_extension = "cia";
compress_info.underlying_magic = std::array<u8, 4>({'C', 'I', 'A', '\0'});
frame_size = FileUtil::Z3DSWriteIOFile::DEFAULT_CIA_FRAME_SIZE;
if (compress) {
auto meta_info = Service::AM::GetCIAInfos(filepath);
if (meta_info.Succeeded()) {
const auto& meta_info_val = meta_info.Unwrap();
std::vector<u8> value(sizeof(Service::AM::TitleInfo));
memcpy(value.data(), &meta_info_val.first, sizeof(Service::AM::TitleInfo));
compress_info.default_metadata.emplace("titleinfo", value);
if (meta_info_val.second) {
value.resize(sizeof(Loader::SMDH));
memcpy(value.data(), meta_info_val.second.get(), sizeof(Loader::SMDH));
compress_info.default_metadata.emplace("smdh", value);
}
}
}
}
}
if (!compress_info.is_supported) {
LOG_ERROR(Frontend,
"Error {} file {}, the selected file is not a compatible 3DS ROM format or is "
"encrypted.",
compress ? "compressing" : "decompressing", filepath);
return compress ? CompressionStatus::Compress_Unsupported
: CompressionStatus::Decompress_Unsupported;
}
if (compress_info.is_compressed && compress) {
LOG_ERROR(Frontend, "Error compressing file {}, the selected file is already compressed",
filepath);
return CompressionStatus::Compress_AlreadyCompressed;
}
if (!compress_info.is_compressed && !compress) {
LOG_ERROR(Frontend,
"Error decompressing file {}, the selected file is already decompressed",
filepath);
return CompressionStatus::Decompress_NotCompressed;
}
out_info = compress_info;
out_frame_size = frame_size;
return CompressionStatus::Success;
}
jint Java_org_citra_citra_1emu_NativeLibrary_compressFileNative(JNIEnv* env, jobject obj,
jstring j_input_path,
jstring j_output_path) {
const std::string input_path = GetJString(env, j_input_path);
const std::string output_path = GetJString(env, j_output_path);
Loader::AppLoader::CompressFileInfo compress_info{};
size_t frame_size{};
CompressionStatus stat = GetCompressFileInfo(compress_info, frame_size, input_path, true);
if (stat != CompressionStatus::Success) {
return static_cast<jint>(stat);
}
auto progress = [](std::size_t processed, std::size_t total) {
JNIEnv* env = IDCache::GetEnvForThread();
env->CallStaticVoidMethod(IDCache::GetNativeLibraryClass(),
IDCache::GetCompressProgressMethod(), static_cast<jlong>(total),
static_cast<jlong>(processed));
};
bool success =
FileUtil::CompressZ3DSFile(input_path, output_path, compress_info.underlying_magic,
frame_size, progress, compress_info.default_metadata);
if (!success) {
FileUtil::Delete(output_path);
return static_cast<jint>(CompressionStatus::Compress_Failed);
}
return static_cast<jint>(CompressionStatus::Success);
}
jint Java_org_citra_citra_1emu_NativeLibrary_decompressFileNative(JNIEnv* env, jobject obj,
jstring j_input_path,
jstring j_output_path) {
const std::string input_path = GetJString(env, j_input_path);
const std::string output_path = GetJString(env, j_output_path);
Loader::AppLoader::CompressFileInfo compress_info{};
size_t frame_size{};
CompressionStatus stat = GetCompressFileInfo(compress_info, frame_size, input_path, false);
if (stat != CompressionStatus::Success) {
return static_cast<jint>(stat);
}
auto progress = [](std::size_t processed, std::size_t total) {
JNIEnv* env = IDCache::GetEnvForThread();
env->CallStaticVoidMethod(IDCache::GetNativeLibraryClass(),
IDCache::GetCompressProgressMethod(), static_cast<jlong>(total),
static_cast<jlong>(processed));
};
bool success = FileUtil::DeCompressZ3DSFile(input_path, output_path, progress);
if (!success) {
FileUtil::Delete(output_path);
return static_cast<jint>(CompressionStatus::Decompress_Failed);
}
return static_cast<jint>(CompressionStatus::Success);
}
jstring Java_org_citra_citra_1emu_NativeLibrary_getRecommendedExtension(
JNIEnv* env, jobject obj, jstring j_input_path, jboolean j_should_compress) {
const std::string input_path = GetJString(env, j_input_path);
std::string compressed_ext;
std::string uncompressed_ext;
auto loader = Loader::GetLoader(input_path);
if (loader) {
auto compress_info = loader->GetCompressFileInfo();
if (compress_info.is_supported) {
compressed_ext = compress_info.recommended_compressed_extension;
uncompressed_ext = compress_info.recommended_uncompressed_extension;
}
} else {
bool is_compressed = false;
if (Service::AM::CheckCIAToInstall(input_path, is_compressed, true) ==
Service::AM::InstallStatus::Success) {
compressed_ext = "zcia";
uncompressed_ext = "cia";
}
}
if (compressed_ext.empty()) {
return env->NewStringUTF("");
}
return env->NewStringUTF(j_should_compress ? compressed_ext.c_str() : uncompressed_ext.c_str());
}
void Java_org_citra_citra_1emu_NativeLibrary_setUserDirectory(JNIEnv* env,
[[maybe_unused]] jobject obj,
jstring j_directory) {

View File

@ -45,7 +45,8 @@
android:layout_height="wrap_content"
android:textAlignment="viewStart"
android:textSize="15sp"
android:textStyle="bold" app:layout_constraintStart_toStartOf="parent"
android:textStyle="bold"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="Application Title" />
@ -166,7 +167,6 @@
android:layout_marginTop="16dp"
android:gravity="start|center"
android:orientation="horizontal"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/horizontal_layout">
@ -179,6 +179,29 @@
android:contentDescription="@string/cheats"
android:text="@string/cheats" />
</LinearLayout>
<LinearLayout
android:id="@+id/compress_tray"
style="@style/ThemeOverlay.Material3.Button.IconButton.Filled.Tonal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:gravity="start|center"
android:orientation="horizontal"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/game_button_tray">
<com.google.android.material.button.MaterialButton
android:id="@+id/compress_decompress"
style="@style/Widget.Material3.Button.TonalButton.Icon"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:contentDescription="@string/compress"
android:text="@string/compress" />
</LinearLayout>

View File

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="24dp">
<TextView
android:id="@+id/compress_label"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="?attr/textAppearanceBodyMedium"
android:textColor="?attr/colorOnSurface"
android:visibility="visible"
android:paddingBottom="8dp" />
<ProgressBar
android:id="@+id/compress_progress"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_height="48dp"
android:indeterminate="false"
android:max="100"
android:progress="0"
android:layout_marginTop="8dp" />
</LinearLayout>

View File

@ -238,9 +238,8 @@
</integer-array>
<string-array name="render3dModes">
<item>@string/off</item>
<item>@string/side_by_side</item>
<item>@string/reverse_side_by_side</item>
<item>@string/side_by_side_full</item>
<item>@string/anaglyph</item>
<item>@string/interlaced</item>
<item>@string/reverse_interlaced</item>
@ -248,7 +247,6 @@
</string-array>
<integer-array name="render3dValues">
<item>0</item>
<item>1</item>
<item>2</item>
<item>3</item>
@ -257,6 +255,20 @@
<item>6</item>
</integer-array>
<string-array name="render3dWhichDisplay">
<item>@string/off</item>
<item>@string/render_3d_which_display_both</item>
<item>@string/render_3d_which_display_primary</item>
<item>@string/render_3d_which_display_secondary</item>
</string-array>
<integer-array name="render3dDisplayValues">
<item>0</item>
<item>1</item>
<item>2</item>
<item>3</item>
</integer-array>
<string-array name="graphicsApiNames">
<item>@string/opengles</item>
<item>@string/vulkan</item>

View File

@ -140,8 +140,8 @@
<string name="input_dialog_description">Press or move an input.</string>
<string name="input_binding">Input Binding</string>
<string name="input_binding_description">Press or move an input to bind it to %1$s.</string>
<string name="input_binding_description_vertical_axis">Move your joystick up or down.</string>
<string name="input_binding_description_horizontal_axis">Move your joystick left or right.</string>
<string name="input_binding_description_vertical_axis">Move your joystick down</string>
<string name="input_binding_description_horizontal_axis">Move your joystick right</string>
<string name="button_a" translatable="false">A</string>
<string name="button_b" translatable="false">B</string>
<string name="button_select" translatable="false">SELECT</string>
@ -289,10 +289,18 @@
<string name="debug_warning">Warning: Modifying these settings will slow emulation</string>
<string name="stereoscopy">Stereoscopy</string>
<string name="render3d">Stereoscopic 3D Mode</string>
<string name="render3d_description">Choose the stereoscopic 3D mode for 3D rendering. Side by Side modes are most common in modern use. Anaglyph and Interlaced modes will always apply to all connected displays.</string>
<string name="factor3d">Depth</string>
<string name="factor3d_description">Specifies the value of the 3D slider. This should be set to higher than 0% when Stereoscopic 3D is enabled.\nNote: Depth values over 100% are not possible on real hardware and may cause graphical issues</string>
<string name="disable_right_eye_render">Disable Right Eye Render</string>
<string name="disable_right_eye_render_description">Greatly improves performance in some applications, but can cause flickering in others.</string>
<string name="swap_eyes_3d">Swap Eyes</string>
<string name="swap_eyes_3d_description">Swaps which eye is shown in which side. Combined with Side by Side mode makes it possible to see 3D by crossing your eyes!</string>
<string name="render_3d_which_display">Render Stereoscopic 3D</string>
<string name="render_3d_which_display_description">Whether to enable stereoscopic 3D, and on which displays. The single display options are only relevant when multiple displays are connected.</string>
<string name="render_3d_which_display_both">On (All Displays)</string>
<string name="render_3d_which_display_primary">On (Primary Display Only)</string>
<string name="render_3d_which_display_secondary">On (Secondary Display Only)</string>
<string name="cardboard_vr">Cardboard VR</string>
<string name="cardboard_screen_size">Cardboard Screen Size</string>
<string name="cardboard_screen_size_description">Scales the screen to a percentage of its original size.</string>
@ -723,7 +731,7 @@
<!-- Render 3D modes -->
<string name="side_by_side">Side by Side</string>
<string name="reverse_side_by_side">Reverse Side by Side</string>
<string name="side_by_side_full">Side by Side Full Width</string>
<string name="anaglyph">Anaglyph</string>
<string name="interlaced">Interlaced</string>
<string name="reverse_interlaced">Reverse Interlaced</string>
@ -912,4 +920,19 @@
<string name="emulation_occupied_quicksave_slot">Quicksave - %1$tF %1$tR</string>
<string name="quickload_not_found">No Quicksave available.</string>
<!-- File Compression -->
<string name="compress">Compress</string>
<string name="compressing">Compressing…</string>
<string name="decompress">Decompress</string>
<string name="decompressing">Decompressing…</string>
<string name="compress_success">Compression completed successfully.</string>
<string name="compress_unsupported">Compression not supported for this file.</string>
<string name="compress_already">File is already compressed.</string>
<string name="compress_failed">Compression failed.</string>
<string name="decompress_success">Decompression completed successfully.</string>
<string name="decompress_unsupported">Decompression not supported for this file.</string>
<string name="decompress_not_compressed">File is not compressed.</string>
<string name="decompress_failed">Decompression failed.</string>
<string name="compress_decompress_installed_app">Already installed applications cannot be compressed or decompressed.</string>
</resources>

View File

@ -517,6 +517,8 @@ void QtConfig::ReadLayoutValues() {
ReadGlobalSetting(Settings::values.render_3d);
ReadGlobalSetting(Settings::values.factor_3d);
ReadGlobalSetting(Settings::values.swap_eyes_3d);
ReadGlobalSetting(Settings::values.render_3d_which_display);
ReadGlobalSetting(Settings::values.filter_mode);
ReadGlobalSetting(Settings::values.pp_shader_name);
ReadGlobalSetting(Settings::values.anaglyph_shader_name);
@ -1083,6 +1085,8 @@ void QtConfig::SaveLayoutValues() {
WriteGlobalSetting(Settings::values.render_3d);
WriteGlobalSetting(Settings::values.factor_3d);
WriteGlobalSetting(Settings::values.swap_eyes_3d);
WriteGlobalSetting(Settings::values.render_3d_which_display);
WriteGlobalSetting(Settings::values.filter_mode);
WriteGlobalSetting(Settings::values.pp_shader_name);
WriteGlobalSetting(Settings::values.anaglyph_shader_name);

View File

@ -57,6 +57,7 @@ void ConfigureEnhancements::SetConfiguration() {
ui->render_3d_combobox->setCurrentIndex(
static_cast<int>(Settings::values.render_3d.GetValue()));
ui->swap_eyes_3d->setChecked(Settings::values.swap_eyes_3d.GetValue());
ui->factor_3d->setValue(Settings::values.factor_3d.GetValue());
ui->mono_rendering_eye->setCurrentIndex(
static_cast<int>(Settings::values.mono_render_option.GetValue()));
@ -111,6 +112,7 @@ void ConfigureEnhancements::ApplyConfiguration() {
ui->resolution_factor_combobox);
Settings::values.render_3d =
static_cast<Settings::StereoRenderOption>(ui->render_3d_combobox->currentIndex());
Settings::values.swap_eyes_3d = ui->swap_eyes_3d->isChecked();
Settings::values.factor_3d = ui->factor_3d->value();
Settings::values.mono_render_option =
static_cast<Settings::MonoRenderOption>(ui->mono_rendering_eye->currentIndex());

View File

@ -113,7 +113,7 @@
<item>
<widget class="QCheckBox" name="toggle_linear_filter">
<property name="text">
<string>Enable linear filtering</string>
<string>Enable Linear Filtering</string>
</property>
</widget>
</item>
@ -236,7 +236,7 @@
</item>
<item>
<property name="text">
<string>Reverse Side by Side</string>
<string>Side by Side Full Width</string>
</property>
</item>
<item>
@ -318,14 +318,21 @@
</layout>
</item>
<item>
<widget class="QCheckBox" name="disable_right_eye_render">
<property name="text">
<string>Disable right eye rendering</string>
</property>
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Disable Right Eye Rendering&lt;/p&gt;&lt;p&gt;Disables rendering the right eye image when not using stereoscopic mode. Greatly improves performance in some applications, but can cause flickering in others.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
<widget class="QCheckBox" name="disable_right_eye_render">
<property name="text">
<string>Disable Right Eye Rendering</string>
</property>
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Disable Right Eye Rendering&lt;/p&gt;&lt;p&gt;Disables rendering the right eye image when not using stereoscopic mode. Greatly improves performance in some applications, but can cause flickering in others.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="swap_eyes_3d">
<property name="text">
<string>Swap Eyes</string>
</property>
</widget>
</item>
</layout>
</widget>

View File

@ -33,6 +33,7 @@
#include "citra_qt/uisettings.h"
#include "common/logging/log.h"
#include "common/settings.h"
#include "core/core.h"
#include "core/file_sys/archive_extsavedata.h"
#include "core/file_sys/archive_source_sd_savedata.h"
#include "core/hle/service/am/am.h"
@ -1054,6 +1055,13 @@ const QStringList GameList::supported_file_extensions = {
};
void GameList::RefreshGameDirectory() {
// Do not scan directories when the system is powered on, it will be
// repopulated on shutdown anyways.
if (Core::System::GetInstance().IsPoweredOn()) {
return;
}
if (!UISettings::values.game_dirs.isEmpty() && current_worker != nullptr) {
LOG_INFO(Frontend, "Change detected in the applications directory. Reloading game list.");
PopulateAsync(UISettings::values.game_dirs);

View File

@ -92,7 +92,7 @@ void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsign
if (Loader::IsValidSMDH(smdh)) {
if (system_title) {
auto smdh_struct = reinterpret_cast<Loader::SMDH*>(smdh.data());
if (!(smdh_struct->flags & Loader::SMDH::Flags::Visible)) {
if (!smdh_struct->flags.visible) {
// Skip system titles without the visible flag.
return true;
}

View File

@ -163,6 +163,10 @@ render_3d =
# 0 - 100: Intensity. 0 (default)
factor_3d =
# Swap Eyes in 3D
# true or false (default)
swap_eyes_3d =
# Change Default Eye to Render When in Monoscopic Mode
# 0 (default): Left, 1: Right
mono_render_option =

View File

@ -305,7 +305,7 @@ bool DeleteDir(const std::string& filename) {
}
bool Rename(const std::string& srcFullPath, const std::string& destFullPath) {
LOG_TRACE(Common_Filesystem, "{} --> {}", srcPath, destFullPath);
LOG_TRACE(Common_Filesystem, "{} --> {}", srcFullPath, destFullPath);
#ifdef _WIN32
if (_wrename(Common::UTF8ToUTF16W(srcFullPath).c_str(),
Common::UTF8ToUTF16W(destFullPath).c_str()) == 0)

View File

@ -108,6 +108,9 @@ void LogSettings() {
log_setting("Renderer_DisableRightEyeRender", values.disable_right_eye_render.GetValue());
log_setting("Stereoscopy_Render3d", values.render_3d.GetValue());
log_setting("Stereoscopy_Factor3d", values.factor_3d.GetValue());
log_setting("Stereoscopy_Swap_Eyes", values.swap_eyes_3d.GetValue());
log_setting("Stereoscopy_Render_3d_to_which_display",
values.render_3d_which_display.GetValue());
log_setting("Stereoscopy_MonoRenderOption", values.mono_render_option.GetValue());
if (values.render_3d.GetValue() == StereoRenderOption::Anaglyph) {
log_setting("Renderer_AnaglyphShader", values.anaglyph_shader_name.GetValue());
@ -219,6 +222,7 @@ void RestoreGlobalState(bool is_powered_on) {
values.bg_green.SetGlobal(true);
values.bg_blue.SetGlobal(true);
values.render_3d.SetGlobal(true);
values.swap_eyes_3d.SetGlobal(true);
values.factor_3d.SetGlobal(true);
values.filter_mode.SetGlobal(true);
values.pp_shader_name.SetGlobal(true);

View File

@ -72,7 +72,7 @@ enum class SmallScreenPosition : u32 {
enum class StereoRenderOption : u32 {
Off = 0,
SideBySide = 1,
ReverseSideBySide = 2,
SideBySideFull = 2,
Anaglyph = 3,
Interlaced = 4,
ReverseInterlaced = 5,
@ -86,6 +86,14 @@ enum class MonoRenderOption : u32 {
RightEye = 1,
};
// on android, which displays to render stereo mode to
enum class StereoWhichDisplay : u32 {
None = 0, // equivalent to StereoRenderOption = Off
Both = 1,
PrimaryOnly = 2,
SecondaryOnly = 3
};
enum class AudioEmulation : u32 {
HLE = 0,
LLE = 1,
@ -562,6 +570,10 @@ struct Values {
SwitchableSetting<StereoRenderOption> render_3d{StereoRenderOption::Off, "render_3d"};
SwitchableSetting<u32> factor_3d{0, "factor_3d"};
SwitchableSetting<bool> swap_eyes_3d{false, "swap_eyes_3d"};
SwitchableSetting<StereoWhichDisplay> render_3d_which_display{StereoWhichDisplay::None,
"render_3d_which_display"};
SwitchableSetting<MonoRenderOption> mono_render_option{MonoRenderOption::LeftEye,
"mono_render_option"};

View File

@ -362,8 +362,7 @@ System::ResultStatus System::Load(Frontend::EmuWindow& emu_window, const std::st
ASSERT(n3ds_hw_caps.first);
app_n3ds_hw_capabilities = n3ds_hw_caps.first.value();
if (!Settings::values.is_new_3ds.GetValue() &&
app_n3ds_hw_capabilities.memory_mode != Kernel::New3dsMemoryMode::Legacy) {
if (!Settings::values.is_new_3ds.GetValue() && app_loader->IsN3DSExclusive()) {
return ResultStatus::ErrorN3DSApplication;
}
@ -374,14 +373,10 @@ System::ResultStatus System::Load(Frontend::EmuWindow& emu_window, const std::st
// proper memory mode.
if (used_default_mem_mode) {
// If we are on the Old 3DS prod mode, the application is not a New 3DS application and
// the application memory mode does not match, we need to adjust it. We do not need
// adjustment if we are on the New 3DS prod mode, as that one overrides all the Old 3DS
// memory modes.
if (system_mem_mode == Kernel::MemoryMode::Prod &&
app_n3ds_hw_capabilities.memory_mode == Kernel::New3dsMemoryMode::Legacy &&
app_mem_mode != system_mem_mode) {
// If we are on the Old 3DS prod mode and the application memory mode does not match, we
// need to adjust it. We do not need adjustment if we are on the New 3DS prod mode, as that
// one overrides all the Old 3DS memory modes.
if (system_mem_mode == Kernel::MemoryMode::Prod && app_mem_mode != system_mem_mode) {
system_mem_mode = app_mem_mode;
}
@ -389,7 +384,6 @@ System::ResultStatus System::Load(Frontend::EmuWindow& emu_window, const std::st
// memory mode (only CTRAging is known to do this), adjust the memory mode.
else if (system_mem_mode == Kernel::MemoryMode::NewProd &&
app_n3ds_hw_capabilities.memory_mode == Kernel::New3dsMemoryMode::NewDev1) {
system_mem_mode = Kernel::MemoryMode::NewDev1;
}
}

View File

@ -1,7 +1,9 @@
// Copyright 2022 Citra Emulator Project
// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
// Originally MIT-licensed code from The Pixellizer Group
// Copyright 2022 The Pixellizer Group
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
@ -22,6 +24,7 @@
#include "core/file_sys/file_backend.h"
#include "core/file_sys/plugin_3gx.h"
#include "core/file_sys/plugin_3gx_bootloader.h"
#include "core/hle/kernel/config_mem.h"
#include "core/hle/kernel/vm_manager.h"
#include "core/loader/loader.h"
@ -173,10 +176,10 @@ Loader::ResultStatus FileSys::Plugin3GXLoader::Map(
}
const std::array<u32, 4> mem_region_sizes = {
5 * 1024 * 1024, // 5 MiB
2 * 1024 * 1024, // 2 MiB
3 * 1024 * 1024, // 3 MiB
4 * 1024 * 1024 // 4 MiB
5 * 1024 * 1024, // 5 MiB
2 * 1024 * 1024, // 2 MiB
10 * 1024 * 1024, // 10 MiB
5 * 1024 * 1024 // 5 MiB (reserved)
};
const bool is_mem_private = header.infos.flags.use_private_memory != 0;
@ -184,65 +187,64 @@ Loader::ResultStatus FileSys::Plugin3GXLoader::Map(
// Map memory block. This behaviour mimics how plugins are loaded on 3DS as much as possible.
// Calculate the sizes of the different memory regions
const u32 block_size = mem_region_sizes[header.infos.flags.memory_region_size.Value()];
const u32 exe_offset = 0;
const u32 exe_size = (sizeof(PluginHeader) + text_section.size() + rodata_section.size() +
data_section.size() + header.executable.bss_size + 0x1000) &
~0xFFFu;
const u32 bootloader_offset = exe_offset + exe_size;
const u32 bootloader_size = bootloader_memory_size;
const u32 heap_offset = bootloader_offset + bootloader_size;
const u32 heap_size = block_size - heap_offset;
// Allocate the framebuffer block so that is in the highest FCRAM position possible
auto offset_fb =
kernel.GetMemoryRegion(Kernel::MemoryRegion::SYSTEM)->RLinearAllocate(_3GX_fb_size);
if (!offset_fb) {
LOG_ERROR(Service_PLGLDR, "Failed to load 3GX plugin. Not enough memory: {}",
plg_context.plugin_path);
return Loader::ResultStatus::ErrorMemoryAllocationFailed;
// Allocate a block of memory for the plugin
std::optional<u32> offset;
if (kernel.GetMemoryMode() == Kernel::MemoryMode::NewProd ||
(plg_context.use_user_load_parameters &&
plg_context.user_load_parameters.plugin_memory_strategy ==
Service::PLGLDR::PLG_LDR::PluginMemoryStrategy::PLG_STRATEGY_MODE3)) {
// Allocate memory block from the end of the APPLICATION region
offset =
kernel.GetMemoryRegion(Kernel::MemoryRegion::APPLICATION)->RLinearAllocate(block_size);
// If the reported available APP mem equals the actual size, remove the plugin block size.
if (offset) {
auto& config_mem = kernel.GetConfigMemHandler();
if (config_mem.GetConfigMem().app_mem_alloc ==
kernel.GetMemoryRegion(Kernel::MemoryRegion::APPLICATION)->size) {
config_mem.GetConfigMem().app_mem_alloc -= block_size;
}
}
plg_context.memory_region = Kernel::MemoryRegion::APPLICATION;
} else {
// Allocate memory block from the start of the SYSTEM region
offset = kernel.GetMemoryRegion(Kernel::MemoryRegion::SYSTEM)->LinearAllocate(block_size);
plg_context.memory_region = Kernel::MemoryRegion::SYSTEM;
}
auto backing_memory_fb = kernel.memory.GetFCRAMRef(*offset_fb);
plg_ldr.SetPluginFBAddr(Memory::FCRAM_PADDR + *offset_fb);
std::fill(backing_memory_fb.GetPtr(), backing_memory_fb.GetPtr() + _3GX_fb_size, 0);
auto vma_heap_fb = process.vm_manager.MapBackingMemory(
_3GX_heap_load_addr, backing_memory_fb, _3GX_fb_size,
is_mem_private ? Kernel::MemoryState::Private : Kernel::MemoryState::Shared);
ASSERT(vma_heap_fb.Succeeded());
process.vm_manager.Reprotect(vma_heap_fb.Unwrap(), Kernel::VMAPermission::ReadWrite);
// Allocate a block from the end of FCRAM and clear it
auto offset = kernel.GetMemoryRegion(Kernel::MemoryRegion::SYSTEM)
->RLinearAllocate(block_size - _3GX_fb_size);
if (!offset) {
kernel.GetMemoryRegion(Kernel::MemoryRegion::SYSTEM)->Free(*offset_fb, _3GX_fb_size);
LOG_ERROR(Service_PLGLDR, "Failed to load 3GX plugin. Not enough memory: {}",
plg_context.plugin_path);
return Loader::ResultStatus::ErrorMemoryAllocationFailed;
}
auto backing_memory = kernel.memory.GetFCRAMRef(*offset);
std::fill(backing_memory.GetPtr(), backing_memory.GetPtr() + block_size - _3GX_fb_size, 0);
// Then we map part of the memory, which contains the executable
auto vma = process.vm_manager.MapBackingMemory(_3GX_exe_load_addr, backing_memory, exe_size,
is_mem_private ? Kernel::MemoryState::Private
: Kernel::MemoryState::Shared);
ASSERT(vma.Succeeded());
process.vm_manager.Reprotect(vma.Unwrap(), Kernel::VMAPermission::ReadWriteExecute);
u32 fcram_offset = offset.value();
auto backing_memory_exe = kernel.memory.GetFCRAMRef(fcram_offset + exe_offset);
std::fill(backing_memory_exe.GetPtr(), backing_memory_exe.GetPtr() + exe_size, 0);
// Map the executable
auto vma_exe = process.vm_manager.MapBackingMemory(
_3GX_exe_load_addr, backing_memory_exe, exe_size,
is_mem_private ? Kernel::MemoryState::Private : Kernel::MemoryState::Shared);
ASSERT(vma_exe.Succeeded());
process.vm_manager.Reprotect(vma_exe.Unwrap(), Kernel::VMAPermission::ReadWriteExecute);
// Write text section
kernel.memory.WriteBlock(process, _3GX_exe_load_addr + sizeof(PluginHeader),
text_section.data(), header.executable.code_size);
// Write rodata section
kernel.memory.WriteBlock(
process, _3GX_exe_load_addr + sizeof(PluginHeader) + header.executable.code_size,
rodata_section.data(), header.executable.rodata_size);
// Write data section
kernel.memory.WriteBlock(process,
_3GX_exe_load_addr + sizeof(PluginHeader) +
header.executable.code_size + header.executable.rodata_size,
data_section.data(), header.executable.data_size);
// Prepare plugin header and write it
PluginHeader plugin_header = {0};
plugin_header.version = header.version;
plugin_header.exe_size = exe_size;
plugin_header.heap_VA = _3GX_heap_load_addr;
plugin_header.heap_size = block_size - exe_size;
plugin_header.heap_size = heap_size;
plg_context.plg_event = _3GX_exe_load_addr - 0x4;
plg_context.plg_reply = _3GX_exe_load_addr - 0x8;
plugin_header.plgldr_event = plg_context.plg_event;
@ -254,39 +256,46 @@ Loader::ResultStatus FileSys::Plugin3GXLoader::Map(
}
kernel.memory.WriteBlock(process, _3GX_exe_load_addr, &plugin_header, sizeof(PluginHeader));
// Map plugin heap
auto backing_memory_heap = kernel.memory.GetFCRAMRef(*offset + exe_size);
// Write text section
kernel.memory.WriteBlock(process, _3GX_exe_load_addr + sizeof(PluginHeader),
text_section.data(), header.executable.code_size);
// Write rodata section
kernel.memory.WriteBlock(
process, _3GX_exe_load_addr + sizeof(PluginHeader) + header.executable.code_size,
rodata_section.data(), header.executable.rodata_size);
// Map the rest of the memory at the heap location
auto vma_heap = process.vm_manager.MapBackingMemory(
_3GX_heap_load_addr + _3GX_fb_size, backing_memory_heap,
block_size - exe_size - _3GX_fb_size,
is_mem_private ? Kernel::MemoryState::Private : Kernel::MemoryState::Shared);
ASSERT(vma_heap.Succeeded());
process.vm_manager.Reprotect(vma_heap.Unwrap(), Kernel::VMAPermission::ReadWriteExecute);
// Write data section
kernel.memory.WriteBlock(process,
_3GX_exe_load_addr + sizeof(PluginHeader) +
header.executable.code_size + header.executable.rodata_size,
data_section.data(), header.executable.data_size);
// Allocate a block from the end of FCRAM and clear it
auto bootloader_offset = kernel.GetMemoryRegion(Kernel::MemoryRegion::SYSTEM)
->RLinearAllocate(bootloader_memory_size);
if (!bootloader_offset) {
kernel.GetMemoryRegion(Kernel::MemoryRegion::SYSTEM)->Free(*offset_fb, _3GX_fb_size);
kernel.GetMemoryRegion(Kernel::MemoryRegion::SYSTEM)
->Free(*offset, block_size - _3GX_fb_size);
LOG_ERROR(Service_PLGLDR, "Failed to load 3GX plugin. Not enough memory: {}",
plg_context.plugin_path);
return Loader::ResultStatus::ErrorMemoryAllocationFailed;
}
// Map bootloader
const bool use_internal = plg_context.load_exe_func.empty();
MapBootloader(
process, kernel, *bootloader_offset,
process, kernel, fcram_offset + bootloader_offset,
(use_internal) ? exe_load_func : plg_context.load_exe_func,
(use_internal) ? exe_load_args : plg_context.load_exe_args,
header.executable.code_size + header.executable.rodata_size + header.executable.data_size,
header.infos.exe_load_checksum,
plg_context.use_user_load_parameters ? plg_context.user_load_parameters.no_flash : 0);
// Map plugin heap
auto backing_memory_heap = kernel.memory.GetFCRAMRef(fcram_offset + heap_offset);
std::fill(backing_memory_heap.GetPtr(), backing_memory_heap.GetPtr() + heap_size, 0);
auto vma_heap = process.vm_manager.MapBackingMemory(
_3GX_heap_load_addr, backing_memory_heap, heap_size,
is_mem_private ? Kernel::MemoryState::Private : Kernel::MemoryState::Shared);
ASSERT(vma_heap.Succeeded());
process.vm_manager.Reprotect(vma_heap.Unwrap(), Kernel::VMAPermission::ReadWriteExecute);
plg_ldr.SetPluginFBAddr(Memory::FCRAM_PADDR + fcram_offset + heap_offset);
plg_context.plugin_loaded = true;
plg_context.plugin_process_id = process.process_id;
plg_context.use_user_load_parameters = false;
plg_context.memory_block = {fcram_offset, block_size};
return Loader::ResultStatus::Success;
}
@ -355,7 +364,7 @@ void FileSys::Plugin3GXLoader::MapBootloader(Kernel::Process& process, Kernel::K
std::fill(backing_memory.GetPtr(), backing_memory.GetPtr() + bootloader_memory_size, 0);
auto vma = process.vm_manager.MapBackingMemory(_3GX_exe_load_addr - bootloader_memory_size,
backing_memory, bootloader_memory_size,
Kernel::MemoryState::Continuous);
Kernel::MemoryState::Private);
ASSERT(vma.Succeeded());
process.vm_manager.Reprotect(vma.Unwrap(), Kernel::VMAPermission::ReadWriteExecute);

View File

@ -25,9 +25,8 @@ public:
std::mutex mutex;
bool touch_pressed = false; ///< True if touchpad area is currently pressed, otherwise false
float touch_x = 0.0f; ///< Touchpad X-position
float touch_y = 0.0f; ///< Touchpad Y-position
float touch_x = 0.0f; ///< Touchpad X-position
float touch_y = 0.0f; ///< Touchpad Y-position
private:
class Device : public Input::TouchDevice {
@ -56,24 +55,44 @@ EmuWindow::EmuWindow(bool is_secondary_) : is_secondary{is_secondary_} {
EmuWindow::~EmuWindow() = default;
Settings::StereoRenderOption EmuWindow::get3DMode() const {
Settings::StereoRenderOption render_3d_mode = Settings::values.render_3d.GetValue();
#ifndef ANDROID
// on desktop, if separate windows and this is the bottom screen, then no stereo
if (Settings::values.layout_option.GetValue() == Settings::LayoutOption::SeparateWindows &&
((!is_secondary && Settings::values.swap_screen.GetValue()) ||
(is_secondary && !Settings::values.swap_screen.GetValue()))) {
render_3d_mode = Settings::StereoRenderOption::Off;
}
#else
// adjust the StereoRenderOption setting to Off if appropriate on mobile
Settings::StereoWhichDisplay whichDisplay = Settings::values.render_3d_which_display.GetValue();
if (whichDisplay == Settings::StereoWhichDisplay::None ||
whichDisplay == Settings::StereoWhichDisplay::PrimaryOnly && is_secondary ||
whichDisplay == Settings::StereoWhichDisplay::SecondaryOnly && !is_secondary) {
render_3d_mode = Settings::StereoRenderOption::Off;
}
#endif
return render_3d_mode;
}
bool EmuWindow::IsWithinTouchscreen(const Layout::FramebufferLayout& layout, unsigned framebuffer_x,
unsigned framebuffer_y) {
#ifndef ANDROID
// If separate windows and the touch is in the primary (top) screen, ignore it.
if (Settings::values.layout_option.GetValue() == Settings::LayoutOption::SeparateWindows &&
!is_secondary && !Settings::values.swap_screen.GetValue()) {
((!is_secondary && !Settings::values.swap_screen.GetValue()) ||
(is_secondary && Settings::values.swap_screen.GetValue()))) {
return false;
}
#endif
Settings::StereoRenderOption render_3d_mode = get3DMode();
if (!layout.bottom_screen_enabled) {
return false;
if (framebuffer_x > layout.width / 2 &&
render_3d_mode == Settings::StereoRenderOption::SideBySideFull) {
framebuffer_x = static_cast<unsigned>(framebuffer_x - layout.width / 2);
}
Settings::StereoRenderOption render_3d_mode = Settings::values.render_3d.GetValue();
if (render_3d_mode == Settings::StereoRenderOption::SideBySide ||
render_3d_mode == Settings::StereoRenderOption::ReverseSideBySide) {
if (render_3d_mode == Settings::StereoRenderOption::SideBySide) {
return (framebuffer_y >= layout.bottom_screen.top &&
framebuffer_y < layout.bottom_screen.bottom &&
((framebuffer_x >= layout.bottom_screen.left / 2 &&
@ -97,25 +116,19 @@ bool EmuWindow::IsWithinTouchscreen(const Layout::FramebufferLayout& layout, uns
}
std::tuple<unsigned, unsigned> EmuWindow::ClipToTouchScreen(unsigned new_x, unsigned new_y) const {
Settings::StereoRenderOption render_3d_mode = Settings::values.render_3d.GetValue();
bool separate_win = false;
#ifndef ANDROID
separate_win =
(Settings::values.layout_option.GetValue() == Settings::LayoutOption::SeparateWindows);
#endif
Settings::StereoRenderOption render_3d_mode = get3DMode();
if (new_x >= framebuffer_layout.width / 2) {
if ((render_3d_mode == Settings::StereoRenderOption::SideBySide ||
render_3d_mode == Settings::StereoRenderOption::ReverseSideBySide) &&
!separate_win)
if (render_3d_mode == Settings::StereoRenderOption::SideBySide ||
render_3d_mode == Settings::StereoRenderOption::SideBySideFull)
new_x -= framebuffer_layout.width / 2;
else if (render_3d_mode == Settings::StereoRenderOption::CardboardVR)
new_x -=
(framebuffer_layout.width / 2) - (framebuffer_layout.cardboard.user_x_shift * 2);
}
if ((render_3d_mode == Settings::StereoRenderOption::SideBySide ||
render_3d_mode == Settings::StereoRenderOption::ReverseSideBySide) &&
!separate_win) {
if (render_3d_mode == Settings::StereoRenderOption::SideBySide) {
new_x = std::max(new_x, framebuffer_layout.bottom_screen.left / 2);
new_x = std::min(new_x, framebuffer_layout.bottom_screen.right / 2 - 1);
} else {
@ -140,29 +153,22 @@ void EmuWindow::CreateTouchState() {
}
bool EmuWindow::TouchPressed(unsigned framebuffer_x, unsigned framebuffer_y) {
Settings::StereoRenderOption render_3d_mode = Settings::values.render_3d.GetValue();
bool separate_win = false;
#ifndef ANDROID
separate_win =
(Settings::values.layout_option.GetValue() == Settings::LayoutOption::SeparateWindows);
#endif
if (!IsWithinTouchscreen(framebuffer_layout, framebuffer_x, framebuffer_y))
return false;
Settings::StereoRenderOption render_3d_mode = get3DMode();
if (framebuffer_x >= framebuffer_layout.width / 2) {
if ((render_3d_mode == Settings::StereoRenderOption::SideBySide ||
render_3d_mode == Settings::StereoRenderOption::ReverseSideBySide) &&
!separate_win)
if (render_3d_mode == Settings::StereoRenderOption::SideBySide ||
render_3d_mode == Settings::StereoRenderOption::SideBySideFull)
framebuffer_x -= framebuffer_layout.width / 2;
else if (render_3d_mode == Settings::StereoRenderOption::CardboardVR)
framebuffer_x -=
(framebuffer_layout.width / 2) - (framebuffer_layout.cardboard.user_x_shift * 2);
}
std::scoped_lock guard(touch_state->mutex);
if ((render_3d_mode == Settings::StereoRenderOption::SideBySide ||
render_3d_mode == Settings::StereoRenderOption::ReverseSideBySide) &&
!separate_win) {
if (render_3d_mode == Settings::StereoRenderOption::SideBySide) {
touch_state->touch_x =
static_cast<float>(framebuffer_x - framebuffer_layout.bottom_screen.left / 2) /
(framebuffer_layout.bottom_screen.right / 2 -
@ -204,8 +210,13 @@ void EmuWindow::TouchMoved(unsigned framebuffer_x, unsigned framebuffer_y) {
void EmuWindow::UpdateCurrentFramebufferLayout(u32 width, u32 height, bool is_portrait_mode) {
Layout::FramebufferLayout layout;
const Settings::LayoutOption layout_option = Settings::values.layout_option.GetValue();
const Settings::StereoRenderOption stereo_option = get3DMode();
bool render_full_stereo = (stereo_option == Settings::StereoRenderOption::SideBySideFull);
bool is_bottom = is_secondary;
if (Settings::values.swap_screen.GetValue())
is_bottom = !is_bottom;
const Settings::PortraitLayoutOption portrait_layout_option =
Settings::values.portrait_layout_option.GetValue();
const auto min_size = is_portrait_mode
@ -215,6 +226,9 @@ void EmuWindow::UpdateCurrentFramebufferLayout(u32 width, u32 height, bool is_po
width = std::max(width, min_size.first);
height = std::max(height, min_size.second);
if (render_full_stereo) {
width = width / 2;
}
if (is_portrait_mode) {
switch (portrait_layout_option) {
case Settings::PortraitLayoutOption::PortraitTopFullWidth:
@ -280,11 +294,16 @@ void EmuWindow::UpdateCurrentFramebufferLayout(u32 width, u32 height, bool is_po
layout = Layout::AndroidSecondaryLayout(width, height);
}
#endif
if (render_full_stereo) {
layout.width = width * 2;
}
UpdateMinimumWindowSize(min_size);
if (Settings::values.render_3d.GetValue() == Settings::StereoRenderOption::CardboardVR) {
layout = Layout::GetCardboardSettings(layout);
}
layout.render_3d_mode = stereo_option;
NotifyFramebufferLayoutChanged(layout);
}

View File

@ -258,6 +258,8 @@ public:
return is_secondary;
}
Settings::StereoRenderOption get3DMode() const;
protected:
EmuWindow();
EmuWindow(bool is_secondary);

View File

@ -79,7 +79,7 @@ FramebufferLayout SingleFrameLayout(u32 width, u32 height, bool swapped, bool up
// TODO: This is kind of gross, make it platform agnostic. -OS
#ifdef ANDROID
const float window_aspect_ratio = static_cast<float>(height) / static_cast<float>(width);
const float window_aspect_ratio = static_cast<float>(height) / width;
const auto aspect_ratio_setting = Settings::values.aspect_ratio.GetValue();
float emulation_aspect_ratio = (swapped) ? BOT_SCREEN_ASPECT_RATIO : TOP_SCREEN_ASPECT_RATIO;
@ -413,53 +413,56 @@ FramebufferLayout FrameLayoutFromResolutionScale(u32 res_scale, bool is_secondar
bool is_portrait) {
u32 width, height, gap;
gap = (int)(Settings::values.screen_gap.GetValue()) * res_scale;
FramebufferLayout layout;
if (is_portrait) {
auto layout_option = Settings::values.portrait_layout_option.GetValue();
switch (layout_option) {
case Settings::PortraitLayoutOption::PortraitCustomLayout:
return CustomFrameLayout(
std::max(Settings::values.custom_portrait_top_x.GetValue() +
Settings::values.custom_portrait_top_width.GetValue(),
Settings::values.custom_portrait_bottom_x.GetValue() +
Settings::values.custom_portrait_bottom_width.GetValue()),
std::max(Settings::values.custom_portrait_top_y.GetValue() +
Settings::values.custom_portrait_top_height.GetValue(),
Settings::values.custom_portrait_bottom_y.GetValue() +
Settings::values.custom_portrait_bottom_height.GetValue()),
Settings::values.swap_screen.GetValue(), is_portrait);
width = std::max(Settings::values.custom_portrait_top_x.GetValue() +
Settings::values.custom_portrait_top_width.GetValue(),
Settings::values.custom_portrait_bottom_x.GetValue() +
Settings::values.custom_portrait_bottom_width.GetValue());
height = std::max(Settings::values.custom_portrait_top_y.GetValue() +
Settings::values.custom_portrait_top_height.GetValue(),
Settings::values.custom_portrait_bottom_y.GetValue() +
Settings::values.custom_portrait_bottom_height.GetValue());
layout = CustomFrameLayout(width, height, Settings::values.swap_screen.GetValue(),
is_portrait);
break;
case Settings::PortraitLayoutOption::PortraitTopFullWidth:
width = Core::kScreenTopWidth * res_scale;
// clang-format off
height = (static_cast<int>(Core::kScreenTopHeight + Core::kScreenBottomHeight * 1.25) *
res_scale) + gap;
// clang-format on
return PortraitTopFullFrameLayout(width, height,
Settings::values.swap_screen.GetValue(),
Settings::values.upright_screen.GetValue());
layout =
PortraitTopFullFrameLayout(width, height, Settings::values.swap_screen.GetValue(),
Settings::values.upright_screen.GetValue());
break;
case Settings::PortraitLayoutOption::PortraitOriginal:
width = Core::kScreenTopWidth * res_scale;
height = (Core::kScreenTopHeight + Core::kScreenBottomHeight) * res_scale;
return PortraitOriginalLayout(width, height, Settings::values.swap_screen.GetValue());
layout = PortraitOriginalLayout(width, height, Settings::values.swap_screen.GetValue());
break;
}
} else {
auto layout_option = Settings::values.layout_option.GetValue();
switch (layout_option) {
case Settings::LayoutOption::CustomLayout:
return CustomFrameLayout(std::max(Settings::values.custom_top_x.GetValue() +
Settings::values.custom_top_width.GetValue(),
Settings::values.custom_bottom_x.GetValue() +
Settings::values.custom_bottom_width.GetValue()),
std::max(Settings::values.custom_top_y.GetValue() +
Settings::values.custom_top_height.GetValue(),
Settings::values.custom_bottom_y.GetValue() +
Settings::values.custom_bottom_height.GetValue()),
Settings::values.swap_screen.GetValue(), is_portrait);
case Settings::LayoutOption::SingleScreen:
#ifndef ANDROID
case Settings::LayoutOption::SeparateWindows:
#endif
{
layout =
CustomFrameLayout(std::max(Settings::values.custom_top_x.GetValue() +
Settings::values.custom_top_width.GetValue(),
Settings::values.custom_bottom_x.GetValue() +
Settings::values.custom_bottom_width.GetValue()),
std::max(Settings::values.custom_top_y.GetValue() +
Settings::values.custom_top_height.GetValue(),
Settings::values.custom_bottom_y.GetValue() +
Settings::values.custom_bottom_height.GetValue()),
Settings::values.swap_screen.GetValue(), is_portrait);
break;
case Settings::LayoutOption::SingleScreen: {
const bool swap_screens = is_secondary || Settings::values.swap_screen.GetValue();
if (swap_screens) {
width = Core::kScreenBottomWidth * res_scale;
@ -471,8 +474,10 @@ FramebufferLayout FrameLayoutFromResolutionScale(u32 res_scale, bool is_secondar
if (Settings::values.upright_screen.GetValue()) {
std::swap(width, height);
}
return SingleFrameLayout(width, height, swap_screens,
Settings::values.upright_screen.GetValue());
layout = SingleFrameLayout(width, height, swap_screens,
Settings::values.upright_screen.GetValue());
break;
}
case Settings::LayoutOption::LargeScreen: {
@ -501,10 +506,11 @@ FramebufferLayout FrameLayoutFromResolutionScale(u32 res_scale, bool is_secondar
if (Settings::values.upright_screen.GetValue()) {
std::swap(width, height);
}
return LargeFrameLayout(width, height, Settings::values.swap_screen.GetValue(),
Settings::values.upright_screen.GetValue(),
Settings::values.large_screen_proportion.GetValue(),
Settings::values.small_screen_position.GetValue());
layout = LargeFrameLayout(width, height, Settings::values.swap_screen.GetValue(),
Settings::values.upright_screen.GetValue(),
Settings::values.large_screen_proportion.GetValue(),
Settings::values.small_screen_position.GetValue());
break;
}
case Settings::LayoutOption::SideScreen:
width = (Core::kScreenTopWidth + Core::kScreenBottomWidth) * res_scale + gap;
@ -513,10 +519,10 @@ FramebufferLayout FrameLayoutFromResolutionScale(u32 res_scale, bool is_secondar
if (Settings::values.upright_screen.GetValue()) {
std::swap(width, height);
}
return LargeFrameLayout(width, height, Settings::values.swap_screen.GetValue(),
Settings::values.upright_screen.GetValue(), 1,
Settings::SmallScreenPosition::MiddleRight);
layout = LargeFrameLayout(width, height, Settings::values.swap_screen.GetValue(),
Settings::values.upright_screen.GetValue(), 1,
Settings::SmallScreenPosition::MiddleRight);
break;
case Settings::LayoutOption::HybridScreen:
height = Core::kScreenTopHeight * res_scale;
@ -532,9 +538,9 @@ FramebufferLayout FrameLayoutFromResolutionScale(u32 res_scale, bool is_secondar
std::swap(width, height);
}
return HybridScreenLayout(width, height, Settings::values.swap_screen.GetValue(),
Settings::values.upright_screen.GetValue());
layout = HybridScreenLayout(width, height, Settings::values.swap_screen.GetValue(),
Settings::values.upright_screen.GetValue());
break;
case Settings::LayoutOption::Default:
default:
width = Core::kScreenTopWidth * res_scale;
@ -543,10 +549,13 @@ FramebufferLayout FrameLayoutFromResolutionScale(u32 res_scale, bool is_secondar
if (Settings::values.upright_screen.GetValue()) {
std::swap(width, height);
}
return DefaultFrameLayout(width, height, Settings::values.swap_screen.GetValue(),
Settings::values.upright_screen.GetValue());
layout = DefaultFrameLayout(width, height, Settings::values.swap_screen.GetValue(),
Settings::values.upright_screen.GetValue());
break;
}
}
return layout;
UNREACHABLE();
}

View File

@ -48,6 +48,8 @@ struct FramebufferLayout {
u32 GetScalingRatio() const;
static float GetAspectRatioValue(Settings::AspectRatio aspect_ratio);
Settings::StereoRenderOption render_3d_mode = Settings::values.render_3d.GetValue();
};
/**

View File

@ -7,6 +7,7 @@
#include <boost/serialization/vector.hpp>
#include "common/archives.h"
#include "common/serialization/atomic.h"
#include "common/settings.h"
#include "core/hle/kernel/client_port.h"
#include "core/hle/kernel/config_mem.h"
#include "core/hle/kernel/handle_table.h"
@ -172,7 +173,9 @@ void KernelSystem::ResetThreadIDs() {
void KernelSystem::UpdateCPUAndMemoryState(u64 title_id, MemoryMode memory_mode,
New3dsHwCapabilities n3ds_hw_cap) {
SetRunning804MHz(n3ds_hw_cap.enable_804MHz_cpu);
if (Settings::values.is_new_3ds) {
SetRunning804MHz(n3ds_hw_cap.enable_804MHz_cpu);
}
u32 tid_high = static_cast<u32>(title_id >> 32);

View File

@ -119,6 +119,11 @@ enum class SystemInfoType {
* For the ARM11 NATIVE_FIRM kernel, this is 5, for processes sm, fs, pm, loader, and pxi."
*/
KERNEL_SPAWNED_PIDS = 26,
/**
* Check various Luma3DS config values. This parameter is not available on real systems,
* but can be used by homebrew applications.
*/
LUMA_CFW_INFO = 0x10000,
/**
* Check if the current system is a new 3DS. This parameter is not available on real systems,
* but can be used by homebrew applications.
@ -270,6 +275,13 @@ enum class SystemInfoMemUsageRegion {
BASE = 3,
};
enum class SystemInfoLumaCFWInformation {
REAL_APP_REGION_SIZE = 0x80, // Gets the real APPLICATION region size,
// instead of the one reported by the kernel shared page
// which depends on the memory mode.
IS_N3DS = 0x201, // Checks if the system is a N3DS or not.
};
/**
* Accepted by svcGetSystemInfo param with CITRA_INFORMATION type. Selects which information
* to fetch from Citra. Some string params don't fit in 7 bytes, so they are split.
@ -1827,6 +1839,20 @@ Result SVC::GetSystemInfo(s64* out, u32 type, s32 param) {
case SystemInfoType::KERNEL_SPAWNED_PIDS:
*out = 5;
break;
case SystemInfoType::LUMA_CFW_INFO:
switch ((SystemInfoLumaCFWInformation)param) {
case SystemInfoLumaCFWInformation::REAL_APP_REGION_SIZE:
*out = kernel.GetMemoryRegion(MemoryRegion::APPLICATION)->size;
break;
case SystemInfoLumaCFWInformation::IS_N3DS:
*out = Settings::values.is_new_3ds ? 1 : 0;
break;
default:
LOG_ERROR(Kernel_SVC, "unknown GetSystemInfo type=0x10000 region: param={}", param);
*out = 0;
break;
}
break;
case SystemInfoType::NEW_3DS_INFO:
// The actual subtypes are not implemented, homebrew just check
// this doesn't return an error in n3ds to know the system type

View File

@ -1,4 +1,4 @@
// Copyright 2014 Citra Emulator Project
// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@ -30,11 +30,16 @@
namespace Service::FS {
bool IsInstalledApplication(std::string_view path) {
return path.rfind(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) + "title", 0) == 0 ||
path.rfind(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir) + "Nintendo 3DS", 0) == 0;
}
MediaType GetMediaTypeFromPath(std::string_view path) {
if (path.rfind(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir), 0) == 0) {
if (path.rfind(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) + "title", 0) == 0) {
return MediaType::NAND;
}
if (path.rfind(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir), 0) == 0) {
if (path.rfind(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir) + "Nintendo 3DS", 0) == 0) {
return MediaType::SDMC;
}
return MediaType::GameCard;

View File

@ -1,4 +1,4 @@
// Copyright 2014 Citra Emulator Project
// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@ -51,6 +51,7 @@ enum class ArchiveIdCode : u32 {
/// Media types for the archives
enum class MediaType : u32 { NAND = 0, SDMC = 1, GameCard = 2 };
bool IsInstalledApplication(std::string_view path);
MediaType GetMediaTypeFromPath(std::string_view path);
enum class SpecialContentType : u8 {

View File

@ -2,6 +2,8 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
// Originally MIT-licensed code from The Pixellizer Group
// Copyright 2022 The Pixellizer Group
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
@ -70,6 +72,9 @@ void PLG_LDR::PluginLoaderContext::serialize(Archive& ar, const unsigned int) {
ar & plugin_loaded;
ar & is_default_path;
ar & plugin_path;
ar & memory_region;
ar & memory_block;
ar & plugin_process_id;
ar & use_user_load_parameters;
ar & user_load_parameters;
ar & plg_event;
@ -148,12 +153,21 @@ void PLG_LDR::OnProcessRun(Kernel::Process& process, Kernel::KernelSystem& kerne
}
void PLG_LDR::OnProcessExit(Kernel::Process& process, Kernel::KernelSystem& kernel) {
if (plgldr_context.plugin_loaded) {
if (plgldr_context.plugin_loaded && process.process_id == plgldr_context.plugin_process_id) {
u32 status = kernel.memory.Read32(FileSys::Plugin3GXLoader::_3GX_exe_load_addr - 0xC);
if (status == 0) {
LOG_CRITICAL(Service_PLGLDR, "Failed to launch {}: Checksum failed",
plgldr_context.plugin_path);
}
if (plgldr_context.memory_region != static_cast<Kernel::MemoryRegion>(0)) {
kernel.GetMemoryRegion(plgldr_context.memory_region)
->Free(plgldr_context.memory_block.first, plgldr_context.memory_block.second);
plgldr_context.memory_region = static_cast<Kernel::MemoryRegion>(0);
}
plgldr_context.plugin_loaded = false;
plgldr_context.plugin_process_id = UINT32_MAX;
plgldr_context.memory_changed_handle = 0;
LOG_INFO(Service_PLGLDR, "Plugin unloaded successfully.");
}
}

View File

@ -1,7 +1,9 @@
// Copyright 2022 Citra Emulator Project
// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
// Originally MIT-licensed code from The Pixellizer Group
// Copyright 2022 The Pixellizer Group
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
@ -62,6 +64,9 @@ public:
bool plugin_loaded = false;
bool is_default_path = false;
std::string plugin_path = "";
u32 plugin_process_id = UINT32_MAX;
Kernel::MemoryRegion memory_region{};
std::pair<u32, u32> memory_block{};
bool use_user_load_parameters = false;
PluginLoadParameters user_load_parameters;

View File

@ -134,6 +134,16 @@ Apploader_Artic::LoadNew3dsHwCapabilities() {
return std::make_pair(std::move(caps), ResultStatus::Success);
}
bool Apploader_Artic::IsN3DSExclusive() {
std::vector<u8> smdh_buffer;
if (ReadIcon(smdh_buffer) == ResultStatus::Success && IsValidSMDH(smdh_buffer)) {
SMDH* smdh = reinterpret_cast<SMDH*>(smdh_buffer.data());
return smdh->flags.n3ds_exclusive != 0;
}
return false;
}
ResultStatus Apploader_Artic::LoadExec(std::shared_ptr<Kernel::Process>& process) {
if (!is_loaded)

View File

@ -56,6 +56,8 @@ public:
std::pair<std::optional<Kernel::New3dsHwCapabilities>, ResultStatus> LoadNew3dsHwCapabilities()
override;
bool IsN3DSExclusive() override;
ResultStatus IsExecutable(bool& out_executable) override;
ResultStatus ReadCode(std::vector<u8>& buffer) override;

View File

@ -163,6 +163,10 @@ public:
ResultStatus::Success);
}
virtual bool IsN3DSExclusive() {
return false;
}
/**
* Get whether this application is executable.
* @param out_executable Reference to store the executable flag into.

View File

@ -125,6 +125,23 @@ AppLoader_NCCH::LoadNew3dsHwCapabilities() {
return std::make_pair(std::move(caps), ResultStatus::Success);
}
bool AppLoader_NCCH::IsN3DSExclusive() {
if (!is_loaded) {
ResultStatus res = base_ncch.Load();
if (res != ResultStatus::Success) {
return false;
}
}
std::vector<u8> smdh_buffer;
if (ReadIcon(smdh_buffer) == ResultStatus::Success && IsValidSMDH(smdh_buffer)) {
SMDH* smdh = reinterpret_cast<SMDH*>(smdh_buffer.data());
return smdh->flags.n3ds_exclusive != 0;
}
return false;
}
ResultStatus AppLoader_NCCH::LoadExec(std::shared_ptr<Kernel::Process>& process) {
using Kernel::CodeSet;

View File

@ -50,6 +50,8 @@ public:
std::pair<std::optional<Kernel::New3dsHwCapabilities>, ResultStatus> LoadNew3dsHwCapabilities()
override;
bool IsN3DSExclusive() override;
ResultStatus IsExecutable(bool& out_executable) override;
ResultStatus ReadCode(std::vector<u8>& buffer) override;

View File

@ -7,6 +7,7 @@
#include <array>
#include <span>
#include <vector>
#include "common/bit_field.h"
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "common/swap.h"
@ -37,7 +38,23 @@ struct SMDH {
u32_le region_lockout;
u32_le match_maker_id;
u64_le match_maker_bit_id;
u32_le flags;
union {
u32_le raw;
BitField<0, 1, u32> visible;
BitField<1, 1, u32> autoboot;
BitField<2, 1, u32> allow_3D;
BitField<3, 1, u32> require_eula;
BitField<4, 1, u32> autosave;
BitField<5, 1, u32> extended_banner;
BitField<6, 1, u32> rating_required;
BitField<7, 1, u32> uses_savedata;
BitField<8, 1, u32> record_usage;
BitField<10, 1, u32> disable_save_backup;
BitField<12, 1, u32> n3ds_exclusive;
BitField<14, 1, u32> parental_restricted;
} flags;
u16_le eula_version;
INSERT_PADDING_BYTES(2);
float_le banner_animation_frame;
@ -73,10 +90,6 @@ struct SMDH {
Taiwan = 6,
};
enum Flags {
Visible = 1 << 0,
};
/**
* Checks if SMDH is valid.
*/

View File

@ -326,7 +326,7 @@ void RendererOpenGL::InitOpenGLObjects() {
glSamplerParameteri(samplers[i].handle, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
}
ReloadShader();
ReloadShader(Settings::values.render_3d.GetValue());
// Generate VBO handle for drawing
vertex_buffer.Create();
@ -372,10 +372,10 @@ void RendererOpenGL::InitOpenGLObjects() {
state.Apply();
}
void RendererOpenGL::ReloadShader() {
void RendererOpenGL::ReloadShader(Settings::StereoRenderOption render_3d) {
// Link shaders and get variable locations
std::string shader_data = fragment_shader_precision_OES;
if (Settings::values.render_3d.GetValue() == Settings::StereoRenderOption::Anaglyph) {
if (render_3d == Settings::StereoRenderOption::Anaglyph) {
if (Settings::values.anaglyph_shader_name.GetValue() == "Dubois (builtin)") {
shader_data += HostShaders::OPENGL_PRESENT_ANAGLYPH_FRAG;
} else {
@ -388,9 +388,8 @@ void RendererOpenGL::ReloadShader() {
shader_data += shader_text;
}
}
} else if (Settings::values.render_3d.GetValue() == Settings::StereoRenderOption::Interlaced ||
Settings::values.render_3d.GetValue() ==
Settings::StereoRenderOption::ReverseInterlaced) {
} else if (render_3d == Settings::StereoRenderOption::Interlaced ||
render_3d == Settings::StereoRenderOption::ReverseInterlaced) {
shader_data += HostShaders::OPENGL_PRESENT_INTERLACED_FRAG;
} else {
if (Settings::values.pp_shader_name.GetValue() == "None (builtin)") {
@ -411,17 +410,16 @@ void RendererOpenGL::ReloadShader() {
state.Apply();
uniform_modelview_matrix = glGetUniformLocation(shader.handle, "modelview_matrix");
uniform_color_texture = glGetUniformLocation(shader.handle, "color_texture");
if (Settings::values.render_3d.GetValue() == Settings::StereoRenderOption::Anaglyph ||
Settings::values.render_3d.GetValue() == Settings::StereoRenderOption::Interlaced ||
Settings::values.render_3d.GetValue() == Settings::StereoRenderOption::ReverseInterlaced) {
if (render_3d == Settings::StereoRenderOption::Anaglyph ||
render_3d == Settings::StereoRenderOption::Interlaced ||
render_3d == Settings::StereoRenderOption::ReverseInterlaced) {
uniform_color_texture_r = glGetUniformLocation(shader.handle, "color_texture_r");
}
if (Settings::values.render_3d.GetValue() == Settings::StereoRenderOption::Interlaced ||
Settings::values.render_3d.GetValue() == Settings::StereoRenderOption::ReverseInterlaced) {
if (render_3d == Settings::StereoRenderOption::Interlaced ||
render_3d == Settings::StereoRenderOption::ReverseInterlaced) {
GLuint uniform_reverse_interlaced =
glGetUniformLocation(shader.handle, "reverse_interlaced");
if (Settings::values.render_3d.GetValue() ==
Settings::StereoRenderOption::ReverseInterlaced)
if (render_3d == Settings::StereoRenderOption::ReverseInterlaced)
glUniform1i(uniform_reverse_interlaced, 1);
else
glUniform1i(uniform_reverse_interlaced, 0);
@ -658,7 +656,7 @@ void RendererOpenGL::DrawScreens(const Layout::FramebufferLayout& layout, bool f
// Update fragment shader before drawing
shader.Release();
// Link shaders and get variable locations
ReloadShader();
ReloadShader(layout.render_3d_mode);
}
const auto& top_screen = layout.top_screen;
@ -676,9 +674,9 @@ void RendererOpenGL::DrawScreens(const Layout::FramebufferLayout& layout, bool f
glUniform1i(uniform_color_texture, 0);
const bool stereo_single_screen =
Settings::values.render_3d.GetValue() == Settings::StereoRenderOption::Anaglyph ||
Settings::values.render_3d.GetValue() == Settings::StereoRenderOption::Interlaced ||
Settings::values.render_3d.GetValue() == Settings::StereoRenderOption::ReverseInterlaced;
layout.render_3d_mode == Settings::StereoRenderOption::Anaglyph ||
layout.render_3d_mode == Settings::StereoRenderOption::Interlaced ||
layout.render_3d_mode == Settings::StereoRenderOption::ReverseInterlaced;
// Bind a second texture for the right eye if in Anaglyph mode
if (stereo_single_screen) {
@ -730,6 +728,9 @@ void RendererOpenGL::DrawTopScreen(const Layout::FramebufferLayout& layout,
if (!layout.top_screen_enabled) {
return;
}
int leftside, rightside;
leftside = Settings::values.swap_eyes_3d.GetValue() ? 1 : 0;
rightside = Settings::values.swap_eyes_3d.GetValue() ? 0 : 1;
const float top_screen_left = static_cast<float>(top_screen.left);
const float top_screen_top = static_cast<float>(top_screen.top);
@ -738,7 +739,7 @@ void RendererOpenGL::DrawTopScreen(const Layout::FramebufferLayout& layout,
const auto orientation = layout.is_rotated ? Layout::DisplayOrientation::Landscape
: Layout::DisplayOrientation::Portrait;
switch (Settings::values.render_3d.GetValue()) {
switch (layout.render_3d_mode) {
case Settings::StereoRenderOption::Off: {
const int eye = static_cast<int>(Settings::values.mono_render_option.GetValue());
DrawSingleScreen(screen_infos[eye], top_screen_left, top_screen_top, top_screen_width,
@ -746,29 +747,29 @@ void RendererOpenGL::DrawTopScreen(const Layout::FramebufferLayout& layout,
break;
}
case Settings::StereoRenderOption::SideBySide: {
DrawSingleScreen(screen_infos[0], top_screen_left / 2, top_screen_top, top_screen_width / 2,
top_screen_height, orientation);
DrawSingleScreen(screen_infos[leftside], top_screen_left / 2, top_screen_top,
top_screen_width / 2, top_screen_height, orientation);
glUniform1i(uniform_layer, 1);
DrawSingleScreen(screen_infos[1],
DrawSingleScreen(screen_infos[rightside],
static_cast<float>((top_screen_left / 2) + (layout.width / 2)),
top_screen_top, top_screen_width / 2, top_screen_height, orientation);
break;
}
case Settings::StereoRenderOption::ReverseSideBySide: {
DrawSingleScreen(screen_infos[1], top_screen_left / 2, top_screen_top, top_screen_width / 2,
case Settings::StereoRenderOption::SideBySideFull: {
DrawSingleScreen(screen_infos[leftside], top_screen_left, top_screen_top, top_screen_width,
top_screen_height, orientation);
glUniform1i(uniform_layer, 1);
DrawSingleScreen(screen_infos[0],
static_cast<float>((top_screen_left / 2) + (layout.width / 2)),
top_screen_top, top_screen_width / 2, top_screen_height, orientation);
DrawSingleScreen(screen_infos[rightside],
static_cast<float>(top_screen_left + layout.width / 2), top_screen_top,
top_screen_width, top_screen_height, orientation);
break;
}
case Settings::StereoRenderOption::CardboardVR: {
DrawSingleScreen(screen_infos[0], top_screen_left, top_screen_top, top_screen_width,
DrawSingleScreen(screen_infos[leftside], top_screen_left, top_screen_top, top_screen_width,
top_screen_height, orientation);
glUniform1i(uniform_layer, 1);
DrawSingleScreen(
screen_infos[1],
screen_infos[rightside],
static_cast<float>(layout.cardboard.top_screen_right_eye + (layout.width / 2)),
top_screen_top, top_screen_width, top_screen_height, orientation);
break;
@ -776,8 +777,8 @@ void RendererOpenGL::DrawTopScreen(const Layout::FramebufferLayout& layout,
case Settings::StereoRenderOption::Anaglyph:
case Settings::StereoRenderOption::Interlaced:
case Settings::StereoRenderOption::ReverseInterlaced: {
DrawSingleScreenStereo(screen_infos[0], screen_infos[1], top_screen_left, top_screen_top,
top_screen_width, top_screen_height, orientation);
DrawSingleScreenStereo(screen_infos[leftside], screen_infos[rightside], top_screen_left,
top_screen_top, top_screen_width, top_screen_height, orientation);
break;
}
}
@ -797,31 +798,30 @@ void RendererOpenGL::DrawBottomScreen(const Layout::FramebufferLayout& layout,
const auto orientation = layout.is_rotated ? Layout::DisplayOrientation::Landscape
: Layout::DisplayOrientation::Portrait;
bool separate_win = false;
#ifndef ANDROID
separate_win =
(Settings::values.layout_option.GetValue() == Settings::LayoutOption::SeparateWindows);
#endif
switch (Settings::values.render_3d.GetValue()) {
switch (layout.render_3d_mode) {
case Settings::StereoRenderOption::Off: {
DrawSingleScreen(screen_infos[2], bottom_screen_left, bottom_screen_top,
bottom_screen_width, bottom_screen_height, orientation);
break;
}
case Settings::StereoRenderOption::SideBySide: // Bottom screen is identical on both sides
case Settings::StereoRenderOption::ReverseSideBySide: {
if (separate_win) {
DrawSingleScreen(screen_infos[2], bottom_screen_left, bottom_screen_top,
bottom_screen_width, bottom_screen_height, orientation);
} else {
DrawSingleScreen(screen_infos[2], bottom_screen_left / 2, bottom_screen_top,
bottom_screen_width / 2, bottom_screen_height, orientation);
glUniform1i(uniform_layer, 1);
DrawSingleScreen(
screen_infos[2], static_cast<float>((bottom_screen_left / 2) + (layout.width / 2)),
bottom_screen_top, bottom_screen_width / 2, bottom_screen_height, orientation);
}
{
DrawSingleScreen(screen_infos[2], bottom_screen_left / 2, bottom_screen_top,
bottom_screen_width / 2, bottom_screen_height, orientation);
glUniform1i(uniform_layer, 1);
DrawSingleScreen(
screen_infos[2], static_cast<float>((bottom_screen_left / 2) + (layout.width / 2)),
bottom_screen_top, bottom_screen_width / 2, bottom_screen_height, orientation);
break;
}
case Settings::StereoRenderOption::SideBySideFull: {
DrawSingleScreen(screen_infos[2], bottom_screen_left, bottom_screen_top,
bottom_screen_width, bottom_screen_height, orientation);
glUniform1i(uniform_layer, 1);
DrawSingleScreen(screen_infos[2], bottom_screen_left + layout.width / 2, bottom_screen_top,
bottom_screen_width, bottom_screen_height, orientation);
break;
}
case Settings::StereoRenderOption::CardboardVR: {

View File

@ -56,7 +56,7 @@ public:
private:
void InitOpenGLObjects();
void ReloadShader();
void ReloadShader(Settings::StereoRenderOption render_3d);
void PrepareRendertarget();
void RenderScreenshot();
void RenderToMailbox(const Layout::FramebufferLayout& layout,

View File

@ -575,8 +575,7 @@ void RendererVulkan::FillScreen(Common::Vec3<u8> color, const TextureInfo& textu
});
}
void RendererVulkan::ReloadPipeline() {
const Settings::StereoRenderOption render_3d = Settings::values.render_3d.GetValue();
void RendererVulkan::ReloadPipeline(Settings::StereoRenderOption render_3d) {
switch (render_3d) {
case Settings::StereoRenderOption::Anaglyph:
current_pipeline = 1;
@ -748,7 +747,9 @@ void RendererVulkan::DrawTopScreen(const Layout::FramebufferLayout& layout,
if (!layout.top_screen_enabled) {
return;
}
int leftside, rightside;
leftside = Settings::values.swap_eyes_3d.GetValue() ? 1 : 0;
rightside = Settings::values.swap_eyes_3d.GetValue() ? 0 : 1;
const float top_screen_left = static_cast<float>(top_screen.left);
const float top_screen_top = static_cast<float>(top_screen.top);
const float top_screen_width = static_cast<float>(top_screen.GetWidth());
@ -756,7 +757,7 @@ void RendererVulkan::DrawTopScreen(const Layout::FramebufferLayout& layout,
const auto orientation = layout.is_rotated ? Layout::DisplayOrientation::Landscape
: Layout::DisplayOrientation::Portrait;
switch (Settings::values.render_3d.GetValue()) {
switch (layout.render_3d_mode) {
case Settings::StereoRenderOption::Off: {
const int eye = static_cast<int>(Settings::values.mono_render_option.GetValue());
DrawSingleScreen(eye, top_screen_left, top_screen_top, top_screen_width, top_screen_height,
@ -764,35 +765,36 @@ void RendererVulkan::DrawTopScreen(const Layout::FramebufferLayout& layout,
break;
}
case Settings::StereoRenderOption::SideBySide: {
DrawSingleScreen(0, top_screen_left / 2, top_screen_top, top_screen_width / 2,
DrawSingleScreen(leftside, top_screen_left / 2, top_screen_top, top_screen_width / 2,
top_screen_height, orientation);
draw_info.layer = 1;
DrawSingleScreen(1, static_cast<float>((top_screen_left / 2) + (layout.width / 2)),
DrawSingleScreen(rightside, static_cast<float>((top_screen_left / 2) + (layout.width / 2)),
top_screen_top, top_screen_width / 2, top_screen_height, orientation);
break;
}
case Settings::StereoRenderOption::ReverseSideBySide: {
DrawSingleScreen(1, top_screen_left / 2, top_screen_top, top_screen_width / 2,
case Settings::StereoRenderOption::SideBySideFull: {
DrawSingleScreen(leftside, top_screen_left, top_screen_top, top_screen_width,
top_screen_height, orientation);
draw_info.layer = 1;
DrawSingleScreen(0, static_cast<float>((top_screen_left / 2) + (layout.width / 2)),
top_screen_top, top_screen_width / 2, top_screen_height, orientation);
DrawSingleScreen(rightside, top_screen_left + layout.width / 2, top_screen_top,
top_screen_width, top_screen_height, orientation);
break;
}
case Settings::StereoRenderOption::CardboardVR: {
DrawSingleScreen(0, top_screen_left, top_screen_top, top_screen_width, top_screen_height,
orientation);
DrawSingleScreen(leftside, top_screen_left, top_screen_top, top_screen_width,
top_screen_height, orientation);
draw_info.layer = 1;
DrawSingleScreen(
1, static_cast<float>(layout.cardboard.top_screen_right_eye + (layout.width / 2)),
rightside,
static_cast<float>(layout.cardboard.top_screen_right_eye + (layout.width / 2)),
top_screen_top, top_screen_width, top_screen_height, orientation);
break;
}
case Settings::StereoRenderOption::Anaglyph:
case Settings::StereoRenderOption::Interlaced:
case Settings::StereoRenderOption::ReverseInterlaced: {
DrawSingleScreenStereo(0, 1, top_screen_left, top_screen_top, top_screen_width,
top_screen_height, orientation);
DrawSingleScreenStereo(leftside, rightside, top_screen_left, top_screen_top,
top_screen_width, top_screen_height, orientation);
break;
}
}
@ -812,31 +814,29 @@ void RendererVulkan::DrawBottomScreen(const Layout::FramebufferLayout& layout,
const auto orientation = layout.is_rotated ? Layout::DisplayOrientation::Landscape
: Layout::DisplayOrientation::Portrait;
bool separate_win = false;
#ifndef ANDROID
separate_win =
(Settings::values.layout_option.GetValue() == Settings::LayoutOption::SeparateWindows);
#endif
switch (Settings::values.render_3d.GetValue()) {
switch (layout.render_3d_mode) {
case Settings::StereoRenderOption::Off: {
DrawSingleScreen(2, bottom_screen_left, bottom_screen_top, bottom_screen_width,
bottom_screen_height, orientation);
break;
}
case Settings::StereoRenderOption::SideBySide: // Bottom screen is identical on both sides
case Settings::StereoRenderOption::ReverseSideBySide: {
if (separate_win) {
DrawSingleScreen(2, bottom_screen_left, bottom_screen_top, bottom_screen_width,
bottom_screen_height, orientation);
} else {
DrawSingleScreen(2, bottom_screen_left / 2, bottom_screen_top, bottom_screen_width / 2,
bottom_screen_height, orientation);
draw_info.layer = 1;
DrawSingleScreen(2, static_cast<float>((bottom_screen_left / 2) + (layout.width / 2)),
bottom_screen_top, bottom_screen_width / 2, bottom_screen_height,
orientation);
}
{
DrawSingleScreen(2, bottom_screen_left / 2, bottom_screen_top, bottom_screen_width / 2,
bottom_screen_height, orientation);
draw_info.layer = 1;
DrawSingleScreen(2, static_cast<float>((bottom_screen_left / 2) + (layout.width / 2)),
bottom_screen_top, bottom_screen_width / 2, bottom_screen_height,
orientation);
break;
}
case Settings::StereoRenderOption::SideBySideFull: {
DrawSingleScreen(2, bottom_screen_left, bottom_screen_top, bottom_screen_width,
bottom_screen_height, orientation);
draw_info.layer = 1;
DrawSingleScreen(2, bottom_screen_left + layout.width / 2, bottom_screen_top,
bottom_screen_width, bottom_screen_height, orientation);
break;
}
case Settings::StereoRenderOption::CardboardVR: {
@ -851,13 +851,8 @@ void RendererVulkan::DrawBottomScreen(const Layout::FramebufferLayout& layout,
case Settings::StereoRenderOption::Anaglyph:
case Settings::StereoRenderOption::Interlaced:
case Settings::StereoRenderOption::ReverseInterlaced: {
if (separate_win) {
DrawSingleScreen(2, bottom_screen_left, bottom_screen_top, bottom_screen_width,
bottom_screen_height, orientation);
} else {
DrawSingleScreenStereo(2, 2, bottom_screen_left, bottom_screen_top, bottom_screen_width,
bottom_screen_height, orientation);
}
DrawSingleScreenStereo(2, 2, bottom_screen_left, bottom_screen_top, bottom_screen_width,
bottom_screen_height, orientation);
break;
}
}
@ -871,7 +866,7 @@ void RendererVulkan::DrawScreens(Frame* frame, const Layout::FramebufferLayout&
clear_color.float32[2] = Settings::values.bg_blue.GetValue();
}
if (settings.shader_update_requested.exchange(false)) {
ReloadPipeline();
ReloadPipeline(layout.render_3d_mode);
}
PrepareDraw(frame, layout);

View File

@ -80,7 +80,7 @@ public:
void TryPresent(int timeout_ms, bool is_secondary) override {}
private:
void ReloadPipeline();
void ReloadPipeline(Settings::StereoRenderOption render_3d);
void CompileShaders();
void BuildLayouts();
void BuildPipelines();

View File

@ -1,4 +1,4 @@
// Copyright 2022 Citra Emulator Project
// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@ -229,7 +229,7 @@ public:
}
/// Returns the maximum supported elements in a texel buffer
u32 MaxTexelBufferElements() const {
u64 MaxTexelBufferElements() const {
return properties.limits.maxTexelBufferElements;
}

View File

@ -1369,17 +1369,19 @@ vk::ImageView Surface::ImageView(u32 index) const noexcept {
vk::ImageView Surface::FramebufferView() noexcept {
is_framebuffer = true;
const u32 index = res_scale == 1 ? 0u : 1u;
// If we already have a framebuffer-compatible view, return it
if (framebuffer_view) {
return framebuffer_view.get();
if (framebuffer_view[index]) {
return framebuffer_view[index].get();
}
// Create a new view with a single mip level for framebuffer compatibility
// This is critical to avoid VUID-VkFramebufferCreateInfo-pAttachments-00883 validation errors
framebuffer_view = MakeFramebufferImageView(
framebuffer_view[index] = MakeFramebufferImageView(
instance->GetDevice(), Image(), instance->GetTraits(pixel_format).native, Aspect(), 0);
return framebuffer_view.get();
return framebuffer_view[index].get();
}
vk::ImageView Surface::DepthView() noexcept {
@ -1482,9 +1484,7 @@ void Surface::BlitScale(const VideoCore::TextureBlit& blit, bool up_scale) {
if (is_depth_stencil && !depth_traits.blit_support) {
LOG_WARNING(Render_Vulkan, "Depth scale unsupported by hardware");
return;
} // Check if texture filtering is enabled
const bool texture_filter_enabled =
Settings::values.texture_filter.GetValue() != Settings::TextureFilter::NoFilter;
}
// Always use consistent source and destination images for proper scaling
// When upscaling: source = unscaled (0), destination = scaled (1)
@ -1493,8 +1493,8 @@ void Surface::BlitScale(const VideoCore::TextureBlit& blit, bool up_scale) {
const vk::Image dst_image = up_scale ? Image(1) : Image(0);
scheduler->Record([src_image, aspect = Aspect(), filter = MakeFilter(pixel_format), dst_image,
src_access = AccessFlags(), dst_access = AccessFlags(), blit,
texture_filter_enabled](vk::CommandBuffer render_cmdbuf) {
src_access = AccessFlags(), dst_access = AccessFlags(),
blit](vk::CommandBuffer render_cmdbuf) {
// Adjust blitting parameters for filtered upscaling
const std::array source_offsets = {
vk::Offset3D{static_cast<s32>(blit.src_rect.left),
@ -1508,15 +1508,7 @@ void Surface::BlitScale(const VideoCore::TextureBlit& blit, bool up_scale) {
static_cast<s32>(blit.dst_rect.bottom), 0},
vk::Offset3D{static_cast<s32>(blit.dst_rect.right), static_cast<s32>(blit.dst_rect.top),
1},
}; // Ensure we're using the right filter for texture filtered upscaling
vk::Filter actual_filter;
if (texture_filter_enabled) {
// When texture filtering is enabled, always use LINEAR filtering
actual_filter = vk::Filter::eLinear;
} else {
// When texture filtering is disabled, use the filter appropriate for the texture format
actual_filter = filter;
}
};
const vk::ImageBlit blit_area = {
.srcSubresource{
@ -1585,7 +1577,7 @@ void Surface::BlitScale(const VideoCore::TextureBlit& blit, bool up_scale) {
vk::DependencyFlagBits::eByRegion, {}, {}, read_barriers);
render_cmdbuf.blitImage(src_image, vk::ImageLayout::eTransferSrcOptimal, dst_image,
vk::ImageLayout::eTransferDstOptimal, blit_area, actual_filter);
vk::ImageLayout::eTransferDstOptimal, blit_area, filter);
render_cmdbuf.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer,
vk::PipelineStageFlagBits::eAllCommands,

View File

@ -201,7 +201,7 @@ public:
std::array<Handle, 3> handles{};
std::array<vk::UniqueFramebuffer, 2> framebuffers{};
Handle copy_handle;
vk::UniqueImageView framebuffer_view;
std::array<vk::UniqueImageView, 2> framebuffer_view;
vk::UniqueImageView depth_view;
vk::UniqueImageView stencil_view;
vk::UniqueImageView storage_view;