Compare commits

...

8 commits

Author SHA1 Message Date
e6face74c6 add MMPX filter
All checks were successful
eden-license / license-header (pull_request) Successful in 21s
Signed-off-by: lizzie <lizzie@eden-emu.dev>
2025-09-26 17:31:00 +02:00
43ac4adbb7 better logic
Signed-off-by: lizzie <lizzie@eden-emu.dev>
2025-09-26 17:31:00 +02:00
3348312d2a fix vk
Signed-off-by: lizzie <lizzie@eden-emu.dev>
2025-09-26 17:31:00 +02:00
3ecd929a18 fix ogl
Signed-off-by: lizzie <lizzie@eden-emu.dev>
2025-09-26 17:31:00 +02:00
87c2579b63 [vk, ogl] VK_QCOM ZTC, Bspline, Mitchell filter weights
Signed-off-by: lizzie <lizzie@eden-emu.dev>
2025-09-26 17:31:00 +02:00
19eb8272b1
[video_core] Fix a bug in buffer cache that caused flickering in some games when using fast buffering (#2584)
This fixes a bug in the buffer cache that caused flickering in some games when using fast buffering. This fixes Kirby Star Allies, Yoshi's Crafted World, and possibly many others.

Reviewed-on: #2584
Reviewed-by: Lizzie <lizzie@eden-emu.dev>
Co-authored-by: MaranBr <maranbr@outlook.com>
Co-committed-by: MaranBr <maranbr@outlook.com>
2025-09-26 05:13:08 +02:00
nyx
86ddb51a87
[android] Implement foreground notification service (#480)
A notification is shown when emulation is active. This, in theory should help preventing Eden for getting destroyed in the background. It is also a nice Q.O.L feature to have.

Credits go to t895 for the initial implementation. This was back-ported from older official Citra builds, although with crashes which i fixed myself.

Reviewed-on: #480
Reviewed-by: crueter <crueter@eden-emu.dev>
Co-authored-by: nyx <contact@innix.space>
Co-committed-by: nyx <contact@innix.space>
2025-09-26 05:01:33 +02:00
10aca2f90c
[Vulkan] Descriptor Pool bug fix (#2564)
a bank could be (incorrectly) considered a superset if it had enough image buffer descriptors but not enough storage image descriptors, causing the allocator to pick a bank that can’t actually satisfy VK_DESCRIPTOR_TYPE_STORAGE_IMAGE demand resulting in sham allocations and creation of new pools.
Note to testers,
please look for any regressions in terms of visuals and most importantly please test the performance and ram usage.

Reviewed-on: #2564
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
Reviewed-by: Shinmegumi <shinmegumi@eden-emu.dev>
Reviewed-by: Lizzie <lizzie@eden-emu.dev>
Reviewed-by: crueter <crueter@eden-emu.dev>
Co-authored-by: wildcard <wildcard@eden-emu.dev>
Co-committed-by: wildcard <wildcard@eden-emu.dev>
2025-09-26 04:58:09 +02:00
28 changed files with 552 additions and 66 deletions

View file

@ -9,3 +9,21 @@ Eden will store configuration in the following directories:
- **Linux, macOS, FreeBSD, Solaris, OpenBSD**: `$XDG_DATA_HOME`, `$XDG_CACHE_HOME`, `$XDG_CONFIG_HOME`.
If a `user` directory is present in the current working directory, that will override all global configuration directories and the emulator will use that instead.
# Enhancements
## Filters
Various graphical filters exist - each of them aimed at a specific target/image quality preset.
- **Nearest**: Provides no filtering - useful for debugging.
- **Bilinear**: Provides the hardware default filtering of the Tegra X1.
- **Bicubic**: Provides a bicubic interpolation using a Catmull-Rom (or hardware-accelerated) implementation.
- **Zero-Tangent, B-Spline, Mitchell**: Provides bicubic interpolation using the respective matrix weights. They're normally not hardware accelerated unless the device supports the `VK_QCOM_filter_cubic_weights` extension. The matrix weights are those matching [the specification itself](https://registry.khronos.org/vulkan/specs/latest/html/vkspec.html#VkSamplerCubicWeightsCreateInfoQCOM).
- **Spline-1**: Bicubic interpolation (similar to Mitchell) but with a faster texel fetch method. Generally less blurry than bicubic.
- **Gaussian**: Whole-area blur, an applied gaussian blur is done to the entire frame.
- **Lanczos**: An implementation using `a = 3` (49 texel fetches). Provides sharper edges but blurrier artifacts.
- **ScaleForce**: Experimental texture upscale method, see [ScaleFish](https://github.com/BreadFish64/ScaleFish).
- **FSR**: Uses AMD FidelityFX Super Resolution to enhance image quality.
- **Area**: Area interpolation (high kernel count).
- **MMPX**: Nearest-neighbour filter aimed at providing higher pixel-art quality.

View file

@ -27,6 +27,8 @@ SPDX-License-Identifier: GPL-3.0-or-later
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" android:required="false" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
<uses-permission android:name="android.permission.BLUETOOTH" android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" android:maxSdkVersion="30" />
@ -93,6 +95,10 @@ SPDX-License-Identifier: GPL-3.0-or-later
<meta-data android:name="android.nfc.action.TECH_DISCOVERED" android:resource="@xml/nfc_tech_filter" />
</activity>
<service android:name="org.yuzu.yuzu_emu.utils.ForegroundService" android:foregroundServiceType="specialUse">
<property android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE" android:value="Keep emulation running in background"/>
</service>
<provider
android:name=".features.DocumentProvider"
android:authorities="${applicationId}.user"

View file

@ -22,6 +22,17 @@ fun Context.getPublicFilesDir(): File = getExternalFilesDir(null) ?: filesDir
class YuzuApplication : Application() {
private fun createNotificationChannels() {
val name: CharSequence = getString(R.string.app_notification_channel_name)
val description = getString(R.string.app_notification_channel_description)
val foregroundService = NotificationChannel(
getString(R.string.app_notification_channel_id),
name,
NotificationManager.IMPORTANCE_DEFAULT
)
foregroundService.description = description
foregroundService.setSound(null, null)
foregroundService.vibrationPattern = null
val noticeChannel = NotificationChannel(
getString(R.string.notice_notification_channel_id),
getString(R.string.notice_notification_channel_name),
@ -34,6 +45,7 @@ class YuzuApplication : Application() {
// or other notification behaviors after this
val notificationManager = getSystemService(NotificationManager::class.java)
notificationManager.createNotificationChannel(noticeChannel)
notificationManager.createNotificationChannel(foregroundService)
}
override fun onCreate() {

View file

@ -7,6 +7,7 @@
package org.yuzu.yuzu_emu.activities
import android.annotation.SuppressLint
import android.app.Activity
import android.app.PendingIntent
import android.app.PictureInPictureParams
import android.app.RemoteAction
@ -59,6 +60,7 @@ import org.yuzu.yuzu_emu.utils.ParamPackage
import org.yuzu.yuzu_emu.utils.ThemeHelper
import java.text.NumberFormat
import kotlin.math.roundToInt
import org.yuzu.yuzu_emu.utils.ForegroundService
class EmulationActivity : AppCompatActivity(), SensorEventListener {
private lateinit var binding: ActivityEmulationBinding
@ -78,6 +80,8 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
private val emulationViewModel: EmulationViewModel by viewModels()
private var foregroundService: Intent? = null
override fun onCreate(savedInstanceState: Bundle?) {
Log.gameLaunched = true
ThemeHelper.setTheme(this)
@ -128,6 +132,9 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
nfcReader = NfcReader(this)
nfcReader.initialize()
foregroundService = Intent(this, ForegroundService::class.java)
startForegroundService(foregroundService)
val preferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
if (!preferences.getBoolean(Settings.PREF_MEMORY_WARNING_SHOWN, false)) {
if (MemoryUtil.isLessThan(MemoryUtil.REQUIRED_MEMORY, MemoryUtil.totalMemory)) {
@ -189,6 +196,12 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
stopMotionSensorListener()
}
override fun onDestroy() {
super.onDestroy()
stopForegroundService(this)
}
override fun onUserLeaveHint() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
if (BooleanSetting.PICTURE_IN_PICTURE.getBoolean() && !isInPictureInPictureMode) {
@ -511,6 +524,12 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
companion object {
const val EXTRA_SELECTED_GAME = "SelectedGame"
fun stopForegroundService(activity: Activity) {
val startIntent = Intent(activity, ForegroundService::class.java)
startIntent.action = ForegroundService.ACTION_STOP
activity.startForegroundService(startIntent)
}
fun launch(activity: AppCompatActivity, game: Game) {
val launcher = Intent(activity, EmulationActivity::class.java)
launcher.putExtra(EXTRA_SELECTED_GAME, game)

View file

@ -48,6 +48,7 @@ import java.io.BufferedOutputStream
import java.util.zip.ZipEntry
import java.util.zip.ZipInputStream
import androidx.core.content.edit
import org.yuzu.yuzu_emu.activities.EmulationActivity
import kotlin.text.compareTo
class MainActivity : AppCompatActivity(), ThemeProvider {
@ -188,6 +189,9 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
if (it) checkKeys()
}
// Dismiss previous notifications (should not happen unless a crash occurred)
EmulationActivity.stopForegroundService(this)
setInsets()
}
@ -293,6 +297,11 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
themeId = resId
}
override fun onDestroy() {
EmulationActivity.stopForegroundService(this)
super.onDestroy()
}
val getGamesDirectory =
registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) { result ->
if (result != null) {

View file

@ -0,0 +1,79 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// Copyright 2023 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
package org.yuzu.yuzu_emu.utils
import android.app.PendingIntent
import android.app.Service
import android.content.Intent
import android.os.IBinder
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.activities.EmulationActivity
/**
* A service that shows a permanent notification in the background to avoid the app getting
* cleared from memory by the system.
*/
class ForegroundService : Service() {
companion object {
const val EMULATION_RUNNING_NOTIFICATION = 0x1000
const val ACTION_STOP = "stop"
}
private fun showRunningNotification() {
// Intent is used to resume emulation if the notification is clicked
val contentIntent = PendingIntent.getActivity(
this,
0,
Intent(this, EmulationActivity::class.java),
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
)
val builder =
NotificationCompat.Builder(this, getString(R.string.app_notification_channel_id))
.setSmallIcon(R.drawable.ic_stat_notification_logo)
.setContentTitle(getString(R.string.app_name))
.setContentText(getString(R.string.app_notification_running))
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.setOngoing(true)
.setVibrate(null)
.setSound(null)
.setContentIntent(contentIntent)
startForeground(EMULATION_RUNNING_NOTIFICATION, builder.build())
}
override fun onBind(intent: Intent): IBinder? {
return null
}
override fun onCreate() {
showRunningNotification()
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
if (intent?.action == ACTION_STOP) {
try {
NotificationManagerCompat.from(this).cancel(EMULATION_RUNNING_NOTIFICATION)
stopForeground(STOP_FOREGROUND_REMOVE)
} catch (e: Exception) {
Log.error("Failed to stop foreground service")
}
stopSelfResult(startId)
return START_NOT_STICKY
}
if (intent != null) {
showRunningNotification()
}
return START_STICKY
}
override fun onDestroy() =
NotificationManagerCompat.from(this).cancel(EMULATION_RUNNING_NOTIFICATION)
}

View file

@ -251,10 +251,16 @@
<item>@string/scaling_filter_nearest_neighbor</item>
<item>@string/scaling_filter_bilinear</item>
<item>@string/scaling_filter_bicubic</item>
<item>@string/scaling_filter_zero_tangent</item>
<item>@string/scaling_filter_bspline</item>
<item>@string/scaling_filter_mitchell</item>
<item>@string/scaling_filter_spline1</item>
<item>@string/scaling_filter_gaussian</item>
<item>@string/scaling_filter_lanczos</item>
<item>@string/scaling_filter_scale_force</item>
<item>@string/scaling_filter_fsr</item>
<item>@string/scaling_filter_area</item>
<item>@string/scaling_filter_mmpx</item>
</string-array>
<integer-array name="rendererScalingFilterValues">
@ -265,6 +271,12 @@
<item>4</item>
<item>5</item>
<item>6</item>
<item>7</item>
<item>8</item>
<item>9</item>
<item>10</item>
<item>11</item>
<item>12</item>
</integer-array>
<string-array name="rendererAntiAliasingNames">

View file

@ -8,6 +8,11 @@
<string name="notice_notification_channel_id" translatable="false">noticesAndErrors</string>
<string name="notice_notification_channel_description">Shows notifications when something goes wrong.</string>
<string name="notification_permission_not_granted">Notification permission not granted!</string>
<string name="app_notification_channel_name" translatable="false">Eden</string>
<string name="app_notification_channel_id" translatable="false">Eden</string>
<string name="app_notification_channel_description">Eden Switch emulator notifications</string>
<string name="app_notification_running">Eden is Running</string>
<!-- Stats Overlay settings -->
<string name="enhanced_fps_suffix">(Enhanced)</string>
@ -991,6 +996,10 @@
<string name="scaling_filter_scale_force">ScaleForce</string>
<string name="scaling_filter_fsr">AMD FidelityFX™ Super Resolution</string>
<string name="scaling_filter_area">Area</string>
<string name="scaling_filter_zero_tangent">Zero-Tangent</string>
<string name="scaling_filter_bspline">B-Spline</string>
<string name="scaling_filter_mitchell">Mitchell</string>
<string name="scaling_filter_mmpx">MMPX</string>
<!-- Anti-Aliasing -->
<string name="anti_aliasing_none">None</string>

View file

@ -166,7 +166,7 @@ ENUM(ResolutionSetup,
Res7X,
Res8X);
ENUM(ScalingFilter, NearestNeighbor, Bilinear, Bicubic, Spline1, Gaussian, Lanczos, ScaleForce, Fsr, Area, MaxEnum);
ENUM(ScalingFilter, NearestNeighbor, Bilinear, Bicubic, ZeroTangent, BSpline, Mitchell, Spline1, Gaussian, Lanczos, ScaleForce, Fsr, Area, Mmpx, MaxEnum);
ENUM(AntiAliasing, None, Fxaa, Smaa, MaxEnum);

View file

@ -570,12 +570,16 @@ std::unique_ptr<ComboboxTranslationMap> ComboboxEnumeration(QObject* parent)
PAIR(ScalingFilter, NearestNeighbor, tr("Nearest Neighbor")),
PAIR(ScalingFilter, Bilinear, tr("Bilinear")),
PAIR(ScalingFilter, Bicubic, tr("Bicubic")),
PAIR(ScalingFilter, ZeroTangent, tr("Zero-Tangent")),
PAIR(ScalingFilter, BSpline, tr("B-Spline")),
PAIR(ScalingFilter, Mitchell, tr("Mitchell")),
PAIR(ScalingFilter, Spline1, tr("Spline-1")),
PAIR(ScalingFilter, Gaussian, tr("Gaussian")),
PAIR(ScalingFilter, Lanczos, tr("Lanczos")),
PAIR(ScalingFilter, ScaleForce, tr("ScaleForce")),
PAIR(ScalingFilter, Fsr, tr("AMD FidelityFX™ Super Resolution")),
PAIR(ScalingFilter, Area, tr("Area")),
PAIR(ScalingFilter, Mmpx, tr("MMPX")),
}});
translations->insert({Settings::EnumMetadata<Settings::AntiAliasing>::Index(),
{

View file

@ -38,6 +38,9 @@ static const std::map<Settings::ScalingFilter, QString> scaling_filter_texts_map
{Settings::ScalingFilter::Bilinear,
QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "Bilinear"))},
{Settings::ScalingFilter::Bicubic, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "Bicubic"))},
{Settings::ScalingFilter::ZeroTangent, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "Zero-Tangent"))},
{Settings::ScalingFilter::BSpline, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "B-Spline"))},
{Settings::ScalingFilter::Mitchell, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "Mitchell"))},
{Settings::ScalingFilter::Spline1,
QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "Spline-1"))},
{Settings::ScalingFilter::Gaussian,
@ -48,6 +51,7 @@ static const std::map<Settings::ScalingFilter, QString> scaling_filter_texts_map
QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "ScaleForce"))},
{Settings::ScalingFilter::Fsr, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "FSR"))},
{Settings::ScalingFilter::Area, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "Area"))},
{Settings::ScalingFilter::Mmpx, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "MMPX"))},
};
static const std::map<Settings::ConsoleMode, QString> use_docked_mode_texts_map = {

View file

@ -792,6 +792,11 @@ void BufferCache<P>::BindHostGraphicsUniformBuffer(size_t stage, u32 index, u32
const u32 size = (std::min)(binding.size, (*channel_state->uniform_buffer_sizes)[stage][index]);
Buffer& buffer = slot_buffers[binding.buffer_id];
TouchBuffer(buffer, binding.buffer_id);
const bool sync_buffer = SynchronizeBuffer(buffer, device_addr, size);
if (sync_buffer) {
++channel_state->uniform_cache_hits[0];
}
++channel_state->uniform_cache_shots[0];
const bool use_fast_buffer = binding.buffer_id != NULL_BUFFER_ID &&
size <= channel_state->uniform_buffer_skip_cache_size &&
!memory_tracker.IsRegionGpuModified(device_addr, size);
@ -822,12 +827,6 @@ void BufferCache<P>::BindHostGraphicsUniformBuffer(size_t stage, u32 index, u32
device_memory.ReadBlockUnsafe(device_addr, span.data(), size);
return;
}
// Classic cached path
const bool sync_cached = SynchronizeBuffer(buffer, device_addr, size);
if (sync_cached) {
++channel_state->uniform_cache_hits[0];
}
++channel_state->uniform_cache_shots[0];
// Skip binding if it's not needed and if the bound buffer is not the fast version
// This exists to avoid instances where the fast buffer is bound and a GPU write happens

View file

@ -44,9 +44,13 @@ set(SHADER_FILES
pitch_unswizzle.comp
present_area.frag
present_bicubic.frag
present_zero_tangent.frag
present_bspline.frag
present_mitchell.frag
present_gaussian.frag
present_lanczos.frag
present_spline1.frag
present_mmpx.frag
queries_prefix_scan_sum.comp
queries_prefix_scan_sum_nosubgroups.comp
resolve_conditional_render.comp

View file

@ -1,56 +1,37 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#version 460 core
layout (location = 0) in vec2 frag_tex_coord;
layout (location = 0) out vec4 color;
layout (binding = 0) uniform sampler2D color_texture;
vec4 cubic(float v) {
vec4 n = vec4(1.0, 2.0, 3.0, 4.0) - v;
vec4 s = n * n * n;
float x = s.x;
float y = s.y - 4.0 * s.x;
float z = s.z - 4.0 * s.y + 6.0 * s.x;
float w = 6.0 - x - y - z;
return vec4(x, y, z, w) * (1.0 / 6.0);
vec4 cubic(float x) {
float x2 = x * x;
float x3 = x2 * x;
return vec4(1.0, x, x2, x3) * transpose(mat4x4(
0.0, 2.0, 0.0, 0.0,
-1.0, 0.0, 1.0, 0.0,
2.0, -5.0, 4.0, -1.0,
-1.0, 3.0, -3.0, 1.0
) * (1.0 / 2.0));
}
vec4 textureBicubic( sampler2D textureSampler, vec2 texCoords ) {
vec2 texSize = textureSize(textureSampler, 0);
vec2 invTexSize = 1.0 / texSize;
texCoords = texCoords * texSize - 0.5;
vec2 fxy = fract(texCoords);
texCoords -= fxy;
vec4 xcubic = cubic(fxy.x);
vec4 ycubic = cubic(fxy.y);
vec4 c = texCoords.xxyy + vec2(-0.5, +1.5).xyxy;
vec4 s = vec4(xcubic.xz + xcubic.yw, ycubic.xz + ycubic.yw);
vec4 offset = c + vec4(xcubic.yw, ycubic.yw) / s;
offset *= invTexSize.xxyy;
vec4 sample0 = texture(textureSampler, offset.xz);
vec4 sample1 = texture(textureSampler, offset.yz);
vec4 sample2 = texture(textureSampler, offset.xw);
vec4 sample3 = texture(textureSampler, offset.yw);
float sx = s.x / (s.x + s.y);
float sy = s.z / (s.z + s.w);
return mix(mix(sample3, sample2, sx), mix(sample1, sample0, sx), sy);
vec4 textureBicubic(sampler2D samp, vec2 uv) {
vec2 tex_size = vec2(textureSize(samp, 0));
vec2 cc_tex = uv * tex_size - 0.5f;
vec2 fex = cc_tex - floor(cc_tex);
vec4 xcubic = cubic(fex.x);
vec4 ycubic = cubic(fex.y);
vec4 c = floor(cc_tex).xxyy + vec2(-0.5f, 1.5f).xyxy;
vec4 z = vec4(xcubic.yw, ycubic.yw);
vec4 s = vec4(xcubic.xz, ycubic.xz) + z;
vec4 offset = (c + z / s) * (1.0f / tex_size).xxyy;
vec2 n = vec2(s.x / (s.x + s.y), s.z / (s.z + s.w));
return mix(
mix(texture(samp, offset.yw), texture(samp, offset.xw), n.x),
mix(texture(samp, offset.yz), texture(samp, offset.xz), n.x),
n.y);
}
void main() {
color = textureBicubic(color_texture, frag_tex_coord);
}

View file

@ -0,0 +1,35 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#version 460 core
layout (location = 0) in vec2 frag_tex_coord;
layout (location = 0) out vec4 color;
layout (binding = 0) uniform sampler2D color_texture;
vec4 cubic(float x) {
float x2 = x * x;
float x3 = x2 * x;
return vec4(1.0, x, x2, x3) * transpose(mat4x4(
1.0, 4.0, 1.0, 0.0,
-3.0, 0.0, 3.0, 0.0,
3.0, -6.0, 3.0, 0.0,
-1.0, 3.0, -3.0, 1.0
) * (1.0 / 6.0));
}
vec4 textureBicubic(sampler2D samp, vec2 uv) {
vec2 tex_size = vec2(textureSize(samp, 0));
vec2 cc_tex = uv * tex_size - 0.5f;
vec2 fex = cc_tex - floor(cc_tex);
vec4 xcubic = cubic(fex.x);
vec4 ycubic = cubic(fex.y);
vec4 c = floor(cc_tex).xxyy + vec2(-0.5f, 1.5f).xyxy;
vec4 z = vec4(xcubic.yw, ycubic.yw);
vec4 s = vec4(xcubic.xz, ycubic.xz) + z;
vec4 offset = (c + z / s) * (1.0f / tex_size).xxyy;
vec2 n = vec2(s.x / (s.x + s.y), s.z / (s.z + s.w));
return mix(
mix(texture(samp, offset.yw), texture(samp, offset.xw), n.x),
mix(texture(samp, offset.yz), texture(samp, offset.xz), n.x),
n.y);
}
void main() {
color = textureBicubic(color_texture, frag_tex_coord);
}

View file

@ -0,0 +1,35 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#version 460 core
layout (location = 0) in vec2 frag_tex_coord;
layout (location = 0) out vec4 color;
layout (binding = 0) uniform sampler2D color_texture;
vec4 cubic(float x) {
float x2 = x * x;
float x3 = x2 * x;
return vec4(1.0, x, x2, x3) * transpose(mat4x4(
1.0, 16.0, 1.0, 0.0,
-9.0, 0.0, 9.0, 0.0,
15.0, -36.0, 27.0, -6.0,
-7.0, 21.0, -21.0, 7.0
) * (1.0 / 18.0));
}
vec4 textureBicubic(sampler2D samp, vec2 uv) {
vec2 tex_size = vec2(textureSize(samp, 0));
vec2 cc_tex = uv * tex_size - 0.5f;
vec2 fex = cc_tex - floor(cc_tex);
vec4 xcubic = cubic(fex.x);
vec4 ycubic = cubic(fex.y);
vec4 c = floor(cc_tex).xxyy + vec2(-0.5f, 1.5f).xyxy;
vec4 z = vec4(xcubic.yw, ycubic.yw);
vec4 s = vec4(xcubic.xz, ycubic.xz) + z;
vec4 offset = (c + z / s) * (1.0f / tex_size).xxyy;
vec2 n = vec2(s.x / (s.x + s.y), s.z / (s.z + s.w));
return mix(
mix(texture(samp, offset.yw), texture(samp, offset.xw), n.x),
mix(texture(samp, offset.yz), texture(samp, offset.xz), n.x),
n.y);
}
void main() {
color = textureBicubic(color_texture, frag_tex_coord);
}

View file

@ -0,0 +1,131 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// Copyright 2023 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#version 460 core
layout(location = 0) in vec2 tex_coord;
layout(location = 0) out vec4 frag_color;
layout(binding = 0) uniform sampler2D tex;
#define src(x, y) texture(tex, coord + vec2(x, y) * 1.0 / source_size)
float luma(vec4 col) {
return dot(col.rgb, vec3(0.2126, 0.7152, 0.0722)) * (1.0 - col.a);
}
bool same(vec4 B, vec4 A0) {
return all(equal(B, A0));
}
bool notsame(vec4 B, vec4 A0) {
return any(notEqual(B, A0));
}
bool all_eq2(vec4 B, vec4 A0, vec4 A1) {
return (same(B,A0) && same(B,A1));
}
bool all_eq3(vec4 B, vec4 A0, vec4 A1, vec4 A2) {
return (same(B,A0) && same(B,A1) && same(B,A2));
}
bool all_eq4(vec4 B, vec4 A0, vec4 A1, vec4 A2, vec4 A3) {
return (same(B,A0) && same(B,A1) && same(B,A2) && same(B,A3));
}
bool any_eq3(vec4 B, vec4 A0, vec4 A1, vec4 A2) {
return (same(B,A0) || same(B,A1) || same(B,A2));
}
bool none_eq2(vec4 B, vec4 A0, vec4 A1) {
return (notsame(B,A0) && notsame(B,A1));
}
bool none_eq4(vec4 B, vec4 A0, vec4 A1, vec4 A2, vec4 A3) {
return (notsame(B,A0) && notsame(B,A1) && notsame(B,A2) && notsame(B,A3));
}
void main()
{
vec2 source_size = vec2(textureSize(tex, 0));
vec2 pos = fract(tex_coord * source_size) - vec2(0.5, 0.5);
vec2 coord = tex_coord - pos / source_size;
vec4 E = src(0.0,0.0);
vec4 A = src(-1.0,-1.0);
vec4 B = src(0.0,-1.0);
vec4 C = src(1.0,-1.0);
vec4 D = src(-1.0,0.0);
vec4 F = src(1.0,0.0);
vec4 G = src(-1.0,1.0);
vec4 H = src(0.0,1.0);
vec4 I = src(1.0,1.0);
vec4 J = E;
vec4 K = E;
vec4 L = E;
vec4 M = E;
frag_color = E;
if(same(E,A) && same(E,B) && same(E,C) && same(E,D) && same(E,F) && same(E,G) && same(E,H) && same(E,I)) return;
vec4 P = src(0.0,2.0);
vec4 Q = src(-2.0,0.0);
vec4 R = src(2.0,0.0);
vec4 S = src(0.0,2.0);
float Bl = luma(B);
float Dl = luma(D);
float El = luma(E);
float Fl = luma(F);
float Hl = luma(H);
if (((same(D,B) && notsame(D,H) && notsame(D,F))) && ((El>=Dl) || same(E,A)) && any_eq3(E,A,C,G) && ((El<Dl) || notsame(A,D) || notsame(E,P) || notsame(E,Q))) J=mix(D, J, 0.5);
if (((same(B,F) && notsame(B,D) && notsame(B,H))) && ((El>=Bl) || same(E,C)) && any_eq3(E,A,C,I) && ((El<Bl) || notsame(C,B) || notsame(E,P) || notsame(E,R))) K=mix(B, K, 0.5);
if (((same(H,D) && notsame(H,F) && notsame(H,B))) && ((El>=Hl) || same(E,G)) && any_eq3(E,A,G,I) && ((El<Hl) || notsame(G,H) || notsame(E,S) || notsame(E,Q))) L=mix(H, L, 0.5);
if (((same(F,H) && notsame(F,B) && notsame(F,D))) && ((El>=Fl) || same(E,I)) && any_eq3(E,C,G,I) && ((El<Fl) || notsame(I,H) || notsame(E,R) || notsame(E,S))) M=mix(F, M, 0.5);
if ((notsame(E,F) && all_eq4(E,C,I,D,Q) && all_eq2(F,B,H)) && notsame(F,src(3.0,0.0))) {M=mix(M, F, 0.5); K=mix(K, M, 0.5);};
if ((notsame(E,D) && all_eq4(E,A,G,F,R) && all_eq2(D,B,H)) && notsame(D,src(-3.0,0.0))) {L=mix(L, D, 0.5); J=mix(J, L, 0.5);};
if ((notsame(E,H) && all_eq4(E,G,I,B,P) && all_eq2(H,D,F)) && notsame(H,src(0.0,3.0))) {M=mix(M, H, 0.5); L=mix(L, M, 0.5);};
if ((notsame(E,B) && all_eq4(E,A,C,H,S) && all_eq2(B,D,F)) && notsame(B,src(0.0,-3.0))) {K=mix(K, B, 0.5); J=mix(J, K, 0.5);};
if ((Bl<El) && all_eq4(E,G,H,I,S) && none_eq4(E,A,D,C,F)) {K=mix(K, B, 0.5); J=mix(J, K, 0.5);}
if ((Hl<El) && all_eq4(E,A,B,C,P) && none_eq4(E,D,G,I,F)) {M=mix(M, H, 0.5); L=mix(L, M, 0.5);}
if ((Fl<El) && all_eq4(E,A,D,G,Q) && none_eq4(E,B,C,I,H)) {M=mix(M, F, 0.5); K=mix(K, M, 0.5);}
if ((Dl<El) && all_eq4(E,C,F,I,R) && none_eq4(E,B,A,G,H)) {L=mix(L, D, 0.5); J=mix(J, L, 0.5);}
if (notsame(H,B)) {
if (notsame(H,A) && notsame(H,E) && notsame(H,C)) {
if (all_eq3(H,G,F,R) && none_eq2(H,D,src(2.0,-1.0))) L=mix(M, L, 0.5);
if (all_eq3(H,I,D,Q) && none_eq2(H,F,src(-2.0,-1.0))) M=mix(L, M, 0.5);
}
if (notsame(B,I) && notsame(B,G) && notsame(B,E)) {
if (all_eq3(B,A,F,R) && none_eq2(B,D,src(2.0,1.0))) J=mix(K, L, 0.5);
if (all_eq3(B,C,D,Q) && none_eq2(B,F,src(-2.0,1.0))) K=mix(J, K, 0.5);
}
}
if (notsame(F,D)) {
if (notsame(D,I) && notsame(D,E) && notsame(D,C)) {
if (all_eq3(D,A,H,S) && none_eq2(D,B,src(1.0,2.0))) J=mix(L, J, 0.5);
if (all_eq3(D,G,B,P) && none_eq2(D,H,src(1.0,2.0))) L=mix(J, L, 0.5);
}
if (notsame(F,E) && notsame(F,A) && notsame(F,G)) {
if (all_eq3(F,C,H,S) && none_eq2(F,B,src(-1.0,2.0))) K=mix(M, K, 0.5);
if (all_eq3(F,I,B,P) && none_eq2(F,H,src(-1.0,-2.0))) M=mix(K, M, 0.5);
}
}
vec2 a = fract(tex_coord * source_size);
vec4 colour = (a.x < 0.5) ? (a.y < 0.5 ? J : L) : (a.y < 0.5 ? K : M);
frag_color = colour;
}

View file

@ -0,0 +1,35 @@
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#version 460 core
layout (location = 0) in vec2 frag_tex_coord;
layout (location = 0) out vec4 color;
layout (binding = 0) uniform sampler2D color_texture;
vec4 cubic(float x) {
float x2 = x * x;
float x3 = x2 * x;
return vec4(1.0, x, x2, x3) * transpose(mat4x4(
0.0, 2.0, 0.0, 0.0,
-2.0, 0.0, 2.0, 0.0,
4.0, -4.0, 2.0, -2.0,
-2.0, 2.0, -2.0, 1.0
) * (1.0 / 2.0));
}
vec4 textureBicubic(sampler2D samp, vec2 uv) {
vec2 tex_size = vec2(textureSize(samp, 0));
vec2 cc_tex = uv * tex_size - 0.5f;
vec2 fex = cc_tex - floor(cc_tex);
vec4 xcubic = cubic(fex.x);
vec4 ycubic = cubic(fex.y);
vec4 c = floor(cc_tex).xxyy + vec2(-0.5f, 1.5f).xyxy;
vec4 z = vec4(xcubic.yw, ycubic.yw);
vec4 s = vec4(xcubic.xz, ycubic.xz) + z;
vec4 offset = (c + z / s) * (1.0f / tex_size).xxyy;
vec2 n = vec2(s.x / (s.x + s.y), s.z / (s.z + s.w));
return mix(
mix(texture(samp, offset.yw), texture(samp, offset.xw), n.x),
mix(texture(samp, offset.yz), texture(samp, offset.xz), n.x),
n.y);
}
void main() {
color = textureBicubic(color_texture, frag_tex_coord);
}

View file

@ -8,6 +8,7 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/settings.h"
#include "common/settings_enums.h"
#include "video_core/present.h"
#include "video_core/renderer_opengl/gl_blit_screen.h"
#include "video_core/renderer_opengl/gl_state_tracker.h"
@ -86,6 +87,15 @@ void BlitScreen::CreateWindowAdapt() {
case Settings::ScalingFilter::Bicubic:
window_adapt = MakeBicubic(device);
break;
case Settings::ScalingFilter::ZeroTangent:
window_adapt = MakeZeroTangent(device);
break;
case Settings::ScalingFilter::BSpline:
window_adapt = MakeBSpline(device);
break;
case Settings::ScalingFilter::Mitchell:
window_adapt = MakeMitchell(device);
break;
case Settings::ScalingFilter::Gaussian:
window_adapt = MakeGaussian(device);
break;
@ -101,6 +111,9 @@ void BlitScreen::CreateWindowAdapt() {
case Settings::ScalingFilter::Area:
window_adapt = MakeArea(device);
break;
case Settings::ScalingFilter::Mmpx:
window_adapt = MakeMmpx(device);
break;
case Settings::ScalingFilter::Fsr:
case Settings::ScalingFilter::Bilinear:
default:

View file

@ -14,6 +14,10 @@
#include "video_core/host_shaders/present_gaussian_frag.h"
#include "video_core/host_shaders/present_lanczos_frag.h"
#include "video_core/host_shaders/present_spline1_frag.h"
#include "video_core/host_shaders/present_mitchell_frag.h"
#include "video_core/host_shaders/present_bspline_frag.h"
#include "video_core/host_shaders/present_zero_tangent_frag.h"
#include "video_core/host_shaders/present_mmpx_frag.h"
#include "video_core/renderer_opengl/present/filters.h"
#include "video_core/renderer_opengl/present/util.h"
@ -39,6 +43,21 @@ std::unique_ptr<WindowAdaptPass> MakeBicubic(const Device& device) {
HostShaders::PRESENT_BICUBIC_FRAG);
}
std::unique_ptr<WindowAdaptPass> MakeMitchell(const Device& device) {
return std::make_unique<WindowAdaptPass>(device, CreateBilinearSampler(),
HostShaders::PRESENT_MITCHELL_FRAG);
}
std::unique_ptr<WindowAdaptPass> MakeZeroTangent(const Device& device) {
return std::make_unique<WindowAdaptPass>(device, CreateBilinearSampler(),
HostShaders::PRESENT_ZERO_TANGENT_FRAG);
}
std::unique_ptr<WindowAdaptPass> MakeBSpline(const Device& device) {
return std::make_unique<WindowAdaptPass>(device, CreateBilinearSampler(),
HostShaders::PRESENT_BSPLINE_FRAG);
}
std::unique_ptr<WindowAdaptPass> MakeGaussian(const Device& device) {
return std::make_unique<WindowAdaptPass>(device, CreateBilinearSampler(),
HostShaders::PRESENT_GAUSSIAN_FRAG);
@ -60,4 +79,9 @@ std::unique_ptr<WindowAdaptPass> MakeArea(const Device& device) {
HostShaders::PRESENT_AREA_FRAG);
}
std::unique_ptr<WindowAdaptPass> MakeMmpx(const Device& device) {
return std::make_unique<WindowAdaptPass>(device, CreateNearestNeighborSampler(),
HostShaders::PRESENT_MMPX_FRAG);
}
} // namespace OpenGL

View file

@ -17,10 +17,14 @@ namespace OpenGL {
std::unique_ptr<WindowAdaptPass> MakeNearestNeighbor(const Device& device);
std::unique_ptr<WindowAdaptPass> MakeBilinear(const Device& device);
std::unique_ptr<WindowAdaptPass> MakeBicubic(const Device& device);
std::unique_ptr<WindowAdaptPass> MakeZeroTangent(const Device& device);
std::unique_ptr<WindowAdaptPass> MakeMitchell(const Device& device);
std::unique_ptr<WindowAdaptPass> MakeBSpline(const Device& device);
std::unique_ptr<WindowAdaptPass> MakeGaussian(const Device& device);
std::unique_ptr<WindowAdaptPass> MakeSpline1(const Device& device);
std::unique_ptr<WindowAdaptPass> MakeLanczos(const Device& device);
std::unique_ptr<WindowAdaptPass> MakeScaleForce(const Device& device);
std::unique_ptr<WindowAdaptPass> MakeArea(const Device& device);
std::unique_ptr<WindowAdaptPass> MakeMmpx(const Device& device);
} // namespace OpenGL

View file

@ -7,6 +7,8 @@
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <vulkan/vulkan_core.h>
#include "common/assert.h"
#include "common/common_types.h"
#include "video_core/host_shaders/present_area_frag_spv.h"
@ -14,6 +16,10 @@
#include "video_core/host_shaders/present_gaussian_frag_spv.h"
#include "video_core/host_shaders/present_lanczos_frag_spv.h"
#include "video_core/host_shaders/present_spline1_frag_spv.h"
#include "video_core/host_shaders/present_mitchell_frag_spv.h"
#include "video_core/host_shaders/present_bspline_frag_spv.h"
#include "video_core/host_shaders/present_zero_tangent_frag_spv.h"
#include "video_core/host_shaders/present_mmpx_frag_spv.h"
#include "video_core/host_shaders/vulkan_present_frag_spv.h"
#include "video_core/host_shaders/vulkan_present_scaleforce_fp16_frag_spv.h"
#include "video_core/host_shaders/vulkan_present_scaleforce_fp32_frag_spv.h"
@ -52,13 +58,28 @@ std::unique_ptr<WindowAdaptPass> MakeSpline1(const Device& device, VkFormat fram
BuildShader(device, PRESENT_SPLINE1_FRAG_SPV));
}
std::unique_ptr<WindowAdaptPass> MakeBicubic(const Device& device, VkFormat frame_format) {
std::unique_ptr<WindowAdaptPass> MakeBicubic(const Device& device, VkFormat frame_format, VkCubicFilterWeightsQCOM qcom_weights) {
// No need for handrolled shader -- if the VK impl can do it for us ;)
if (device.IsExtFilterCubicSupported())
return std::make_unique<WindowAdaptPass>(device, frame_format, CreateCubicSampler(device),
BuildShader(device, VULKAN_PRESENT_FRAG_SPV));
return std::make_unique<WindowAdaptPass>(device, frame_format, CreateBilinearSampler(device),
BuildShader(device, PRESENT_BICUBIC_FRAG_SPV));
// Catmull-Rom is default bicubic for all implementations...
if (device.IsExtFilterCubicSupported() && (device.IsQcomFilterCubicWeightsSupported() || qcom_weights == VK_CUBIC_FILTER_WEIGHTS_CATMULL_ROM_QCOM)) {
return std::make_unique<WindowAdaptPass>(device, frame_format, CreateCubicSampler(device,
qcom_weights), BuildShader(device, VULKAN_PRESENT_FRAG_SPV));
} else {
return std::make_unique<WindowAdaptPass>(device, frame_format, CreateBilinearSampler(device), [&](){
switch (qcom_weights) {
case VK_CUBIC_FILTER_WEIGHTS_CATMULL_ROM_QCOM:
return BuildShader(device, PRESENT_BICUBIC_FRAG_SPV);
case VK_CUBIC_FILTER_WEIGHTS_ZERO_TANGENT_CARDINAL_QCOM:
return BuildShader(device, PRESENT_ZERO_TANGENT_FRAG_SPV);
case VK_CUBIC_FILTER_WEIGHTS_B_SPLINE_QCOM:
return BuildShader(device, PRESENT_BSPLINE_FRAG_SPV);
case VK_CUBIC_FILTER_WEIGHTS_MITCHELL_NETRAVALI_QCOM:
return BuildShader(device, PRESENT_MITCHELL_FRAG_SPV);
default:
UNREACHABLE();
}
}());
}
}
std::unique_ptr<WindowAdaptPass> MakeGaussian(const Device& device, VkFormat frame_format) {
@ -81,4 +102,9 @@ std::unique_ptr<WindowAdaptPass> MakeArea(const Device& device, VkFormat frame_f
BuildShader(device, PRESENT_AREA_FRAG_SPV));
}
std::unique_ptr<WindowAdaptPass> MakeMmpx(const Device& device, VkFormat frame_format) {
return std::make_unique<WindowAdaptPass>(device, frame_format, CreateNearestNeighborSampler(device),
BuildShader(device, PRESENT_MMPX_FRAG_SPV));
}
} // namespace Vulkan

View file

@ -17,11 +17,12 @@ class MemoryAllocator;
std::unique_ptr<WindowAdaptPass> MakeNearestNeighbor(const Device& device, VkFormat frame_format);
std::unique_ptr<WindowAdaptPass> MakeBilinear(const Device& device, VkFormat frame_format);
std::unique_ptr<WindowAdaptPass> MakeBicubic(const Device& device, VkFormat frame_format);
std::unique_ptr<WindowAdaptPass> MakeBicubic(const Device& device, VkFormat frame_format, VkCubicFilterWeightsQCOM qcom_weights);
std::unique_ptr<WindowAdaptPass> MakeSpline1(const Device& device, VkFormat frame_format);
std::unique_ptr<WindowAdaptPass> MakeGaussian(const Device& device, VkFormat frame_format);
std::unique_ptr<WindowAdaptPass> MakeLanczos(const Device& device, VkFormat frame_format);
std::unique_ptr<WindowAdaptPass> MakeScaleForce(const Device& device, VkFormat frame_format);
std::unique_ptr<WindowAdaptPass> MakeArea(const Device& device, VkFormat frame_format);
std::unique_ptr<WindowAdaptPass> MakeMmpx(const Device& device, VkFormat frame_format);
} // namespace Vulkan

View file

@ -624,8 +624,8 @@ vk::Sampler CreateNearestNeighborSampler(const Device& device) {
return device.GetLogical().CreateSampler(ci_nn);
}
vk::Sampler CreateCubicSampler(const Device& device) {
const VkSamplerCreateInfo ci_nn{
vk::Sampler CreateCubicSampler(const Device& device, VkCubicFilterWeightsQCOM qcom_weights) {
VkSamplerCreateInfo ci_nn{
.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO,
.pNext = nullptr,
.flags = 0,
@ -645,7 +645,14 @@ vk::Sampler CreateCubicSampler(const Device& device) {
.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_BLACK,
.unnormalizedCoordinates = VK_FALSE,
};
const VkSamplerCubicWeightsCreateInfoQCOM ci_qcom_nn{
.sType = VK_STRUCTURE_TYPE_SAMPLER_CUBIC_WEIGHTS_CREATE_INFO_QCOM,
.pNext = nullptr,
.cubicWeights = qcom_weights
};
// If not specified, assume Catmull-Rom
if (qcom_weights != VK_CUBIC_FILTER_WEIGHTS_CATMULL_ROM_QCOM)
ci_nn.pNext = &ci_qcom_nn;
return device.GetLogical().CreateSampler(ci_nn);
}

View file

@ -57,7 +57,7 @@ VkWriteDescriptorSet CreateWriteDescriptorSet(std::vector<VkDescriptorImageInfo>
VkDescriptorSet set, u32 binding);
vk::Sampler CreateBilinearSampler(const Device& device);
vk::Sampler CreateNearestNeighborSampler(const Device& device);
vk::Sampler CreateCubicSampler(const Device& device);
vk::Sampler CreateCubicSampler(const Device& device, VkCubicFilterWeightsQCOM qcom_weights);
void BeginRenderPass(vk::CommandBuffer& cmdbuf, VkRenderPass render_pass, VkFramebuffer framebuffer,
VkExtent2D extent);

View file

@ -7,6 +7,7 @@
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <vulkan/vulkan_core.h>
#include "video_core/framebuffer_config.h"
#include "video_core/present.h"
#include "video_core/renderer_vulkan/present/filters.h"
@ -41,7 +42,16 @@ void BlitScreen::SetWindowAdaptPass() {
window_adapt = MakeNearestNeighbor(device, swapchain_view_format);
break;
case Settings::ScalingFilter::Bicubic:
window_adapt = MakeBicubic(device, swapchain_view_format);
window_adapt = MakeBicubic(device, swapchain_view_format, VK_CUBIC_FILTER_WEIGHTS_CATMULL_ROM_QCOM);
break;
case Settings::ScalingFilter::ZeroTangent:
window_adapt = MakeBicubic(device, swapchain_view_format, VK_CUBIC_FILTER_WEIGHTS_ZERO_TANGENT_CARDINAL_QCOM);
break;
case Settings::ScalingFilter::BSpline:
window_adapt = MakeBicubic(device, swapchain_view_format, VK_CUBIC_FILTER_WEIGHTS_B_SPLINE_QCOM);
break;
case Settings::ScalingFilter::Mitchell:
window_adapt = MakeBicubic(device, swapchain_view_format, VK_CUBIC_FILTER_WEIGHTS_MITCHELL_NETRAVALI_QCOM);
break;
case Settings::ScalingFilter::Spline1:
window_adapt = MakeSpline1(device, swapchain_view_format);
@ -58,6 +68,9 @@ void BlitScreen::SetWindowAdaptPass() {
case Settings::ScalingFilter::Area:
window_adapt = MakeArea(device, swapchain_view_format);
break;
case Settings::ScalingFilter::Mmpx:
window_adapt = MakeMmpx(device, swapchain_view_format);
break;
case Settings::ScalingFilter::Fsr:
case Settings::ScalingFilter::Bilinear:
default:

View file

@ -31,7 +31,7 @@ struct DescriptorBank {
bool DescriptorBankInfo::IsSuperset(const DescriptorBankInfo& subset) const noexcept {
return uniform_buffers >= subset.uniform_buffers && storage_buffers >= subset.storage_buffers &&
texture_buffers >= subset.texture_buffers && image_buffers >= subset.image_buffers &&
textures >= subset.textures && images >= subset.image_buffers;
textures >= subset.textures && images >= subset.images;
}
template <typename Descriptors>

View file

@ -89,7 +89,8 @@ VK_DEFINE_HANDLE(VmaAllocator)
EXTENSION(NV, VIEWPORT_ARRAY2, viewport_array2) \
EXTENSION(NV, VIEWPORT_SWIZZLE, viewport_swizzle) \
EXTENSION(EXT, DESCRIPTOR_INDEXING, descriptor_indexing) \
EXTENSION(EXT, FILTER_CUBIC, filter_cubic)
EXTENSION(EXT, FILTER_CUBIC, filter_cubic) \
EXTENSION(QCOM, FILTER_CUBIC_WEIGHTS, filter_cubic_weights)
// Define extensions which must be supported.
#define FOR_EACH_VK_MANDATORY_EXTENSION(EXTENSION_NAME) \
@ -558,6 +559,11 @@ public:
return extensions.filter_cubic;
}
/// Returns true if the device supports VK_QCOM_filter_cubic_weights
bool IsQcomFilterCubicWeightsSupported() const {
return extensions.filter_cubic_weights;
}
/// Returns true if the device supports VK_EXT_line_rasterization.
bool IsExtLineRasterizationSupported() const {
return extensions.line_rasterization;