From efef7462998e072daed9e6468c53fb8284599ad9 Mon Sep 17 00:00:00 2001 From: Zephyron Date: Thu, 1 Jan 2026 18:18:05 +1000 Subject: [PATCH] feat(renderer): add CRT shader filter with configurable effects Add CRT (Cathode Ray Tube) shader implementation as scaling filter options (CRT EasyMode and CRT Royale) in the Window Adapting Filter dropdown. Provides classic TV effects including scanlines, phosphor masks, curvature distortion, gamma correction, bloom, brightness, and alpha transparency. - Add CRTEasyMode and CRTRoyale to ScalingFilter enum - Implement vulkan_crt_easymode.frag shader with single-pass effects - Integrate CRT filter into WindowAdaptPass rendering pipeline - Add configurable CRT parameters to settings with user-friendly labels - Add UI translations for desktop and Android platforms - Support CRT push constants in present pipeline The CRT filter appears alongside other scaling filters like FSR and FSR 2.0. CRT parameter settings are only active when a CRT filter is selected. Signed-off-by: Zephyron --- .../app/src/main/res/values/arrays.xml | 8 ++ .../app/src/main/res/values/strings.xml | 4 + .../configuration/shared_translation.cpp | 18 ++- src/citron/configuration/shared_translation.h | 6 +- src/common/settings.h | 67 ++++++++- src/common/settings_enums.h | 9 +- src/video_core/host_shaders/CMakeLists.txt | 3 +- .../host_shaders/vulkan_crt_easymode.frag | 133 ++++++++++++++++++ .../renderer_vulkan/present/filters.cpp | 8 +- .../renderer_vulkan/present/filters.h | 3 +- .../present/window_adapt_pass.cpp | 70 ++++++++- .../renderer_vulkan/vk_blit_screen.cpp | 7 +- 12 files changed, 320 insertions(+), 16 deletions(-) create mode 100644 src/video_core/host_shaders/vulkan_crt_easymode.frag diff --git a/src/android/app/src/main/res/values/arrays.xml b/src/android/app/src/main/res/values/arrays.xml index 9bcb046b3..e00d54ea5 100644 --- a/src/android/app/src/main/res/values/arrays.xml +++ b/src/android/app/src/main/res/values/arrays.xml @@ -163,8 +163,12 @@ @string/scaling_filter_bicubic @string/scaling_filter_gaussian @string/scaling_filter_scale_force + @string/scaling_filter_scale_fx + @string/scaling_filter_lanczos @string/scaling_filter_fsr @string/scaling_filter_fsr2 + @string/scaling_filter_crt_easymode + @string/scaling_filter_crt_royale @@ -175,6 +179,10 @@ 4 5 6 + 7 + 8 + 9 + 10 diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml index 461436a3d..58dfbc2a1 100644 --- a/src/android/app/src/main/res/values/strings.xml +++ b/src/android/app/src/main/res/values/strings.xml @@ -654,8 +654,12 @@ Bicubic Gaussian ScaleForce + ScaleFX + Lanczos AMD FidelityFX™ Super Resolution AMD FidelityFX™ Super Resolution 2.0 + CRT EasyMode + CRT Royale None diff --git a/src/citron/configuration/shared_translation.cpp b/src/citron/configuration/shared_translation.cpp index 9d8e48701..46fa6cfcf 100644 --- a/src/citron/configuration/shared_translation.cpp +++ b/src/citron/configuration/shared_translation.cpp @@ -1,5 +1,5 @@ // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project -// SPDX-FileCopyrightText: Copyright 2025 citron Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 citron Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include "citron/configuration/shared_translation.h" @@ -130,6 +130,20 @@ std::unique_ptr InitializeTranslations(QWidget* parent) { INSERT(Settings, lanczos_quality, tr("Lanczos Quality:"), tr("The quality of the Lanczos filter. Higher is sharper but more expensive.")); INSERT(Settings, fsr2_quality_mode, tr("FSR 2.0 Quality Mode:"), tr("Selects the quality mode for FSR 2.0 upscaling. Quality provides better image quality, Performance provides better performance.")); + INSERT(Settings, crt_scanline_strength, tr("CRT Scanline Strength:"), + tr("Controls the intensity of scanlines. Higher values create more pronounced horizontal lines.")); + INSERT(Settings, crt_curvature, tr("CRT Curvature:"), + tr("Applies barrel distortion to simulate the curved screen of a CRT monitor.")); + INSERT(Settings, crt_gamma, tr("CRT Gamma:"), + tr("Adjusts the gamma correction curve. Higher values brighten the image, lower values darken it.")); + INSERT(Settings, crt_bloom, tr("CRT Bloom:"), + tr("Controls the glow effect around bright areas, simulating phosphor persistence.")); + INSERT(Settings, crt_mask_type, tr("CRT Mask Type:"), + tr("Selects the phosphor mask pattern: None, Aperture Grille (vertical stripes), or Shadow Mask (triangular pattern).")); + INSERT(Settings, crt_brightness, tr("CRT Brightness:"), + tr("Adjusts overall brightness of the CRT effect. Use to compensate for darkening from other effects.")); + INSERT(Settings, crt_alpha, tr("CRT Alpha:"), + tr("Controls transparency of the CRT effect. Lower values make the effect more transparent.")); INSERT(Settings, frame_skipping, tr("Frame Skipping:"), tr("Skips frames to maintain performance when the system cannot keep up with the target frame rate.")); @@ -426,6 +440,8 @@ std::unique_ptr ComboboxEnumeration(QWidget* parent) { PAIR(ScalingFilter, ScaleFx, tr("ScaleFX")), PAIR(ScalingFilter, Fsr, tr("AMD FidelityFX™️ Super Resolution")), PAIR(ScalingFilter, Fsr2, tr("AMD FidelityFX™️ Super Resolution 2.0")), + PAIR(ScalingFilter, CRTEasyMode, tr("CRT EasyMode")), + PAIR(ScalingFilter, CRTRoyale, tr("CRT Royale")), }}); translations->insert({Settings::EnumMetadata::Index(), { diff --git a/src/citron/configuration/shared_translation.h b/src/citron/configuration/shared_translation.h index fad796d8d..71ec60f88 100644 --- a/src/citron/configuration/shared_translation.h +++ b/src/citron/configuration/shared_translation.h @@ -1,5 +1,5 @@ // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project -// SPDX-FileCopyrightText: Copyright 2025 citron Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 citron Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #pragma once @@ -46,6 +46,10 @@ static const std::map scaling_filter_texts_map QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "ScaleFX"))}, {Settings::ScalingFilter::Fsr, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "FSR"))}, {Settings::ScalingFilter::Fsr2, QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "FSR 2.0"))}, + {Settings::ScalingFilter::CRTEasyMode, + QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "CRT EasyMode"))}, + {Settings::ScalingFilter::CRTRoyale, + QStringLiteral(QT_TRANSLATE_NOOP("GMainWindow", "CRT Royale"))}, }; static const std::map use_docked_mode_texts_map = { diff --git a/src/common/settings.h b/src/common/settings.h index e6c0f5500..7dd29b402 100644 --- a/src/common/settings.h +++ b/src/common/settings.h @@ -1,5 +1,5 @@ // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project -// SPDX-FileCopyrightText: Copyright 2025 citron Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 citron Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #pragma once @@ -350,6 +350,71 @@ struct Values { true, true}; + // CRT Shader Settings (only active when CRT filter is selected) + SwitchableSetting crt_scanline_strength{linkage, + 1.0f, // 100/100 = 1.0 (range 0-200, actual 0.0-2.0) + 0.0f, + 2.0f, + "crt_scanline_strength", + Category::Renderer, + Specialization::Scalar, + true, + true}; + SwitchableSetting crt_curvature{linkage, + 0.0f, + 0.0f, + 1.0f, + "crt_curvature", + Category::Renderer, + Specialization::Scalar, + true, + true}; + SwitchableSetting crt_gamma{linkage, + 1.0f, // 100 maps to 1.0 (range 1-300, actual 1.0-3.0) + 1.0f, + 3.0f, + "crt_gamma", + Category::Renderer, + Specialization::Scalar, + true, + true}; + SwitchableSetting crt_bloom{linkage, + 0.33f, // 33/100 = 0.33 (range 0-100, actual 0.0-1.0) + 0.0f, + 1.0f, + "crt_bloom", + Category::Renderer, + Specialization::Scalar, + true, + true}; + SwitchableSetting crt_mask_type{linkage, + 1, // Already correct + 0, + 2, + "crt_mask_type", + Category::Renderer, + Specialization::Scalar, + true, + true}; // 0=none, 1=aperture, 2=shadow + SwitchableSetting crt_brightness{linkage, + 1.0f, // Default brightness (1.0 = no change) + 0.0f, + 2.0f, + "crt_brightness", + Category::Renderer, + Specialization::Scalar, + true, + true}; + SwitchableSetting crt_alpha{linkage, + 1.0f, // Default alpha (1.0 = fully opaque) + 0.0f, + 1.0f, + "crt_alpha", + Category::Renderer, + Specialization::Scalar, + true, + true}; + SwitchableSetting lanczos_quality{linkage, 3, // Default value diff --git a/src/common/settings_enums.h b/src/common/settings_enums.h index 2fcf4b79b..1b654bfb0 100644 --- a/src/common/settings_enums.h +++ b/src/common/settings_enums.h @@ -1,5 +1,5 @@ // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project -// SPDX-FileCopyrightText: Copyright 2025 citron Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 citron Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #pragma once @@ -673,7 +673,9 @@ enum class ScalingFilter : u32 { Lanczos = 6, Fsr = 7, Fsr2 = 8, - MaxEnum = 9, + CRTEasyMode = 9, + CRTRoyale = 10, + MaxEnum = 11, }; template <> @@ -689,6 +691,8 @@ EnumMetadata::Canonicalizations() { {"Lanczos", ScalingFilter::Lanczos}, {"Fsr", ScalingFilter::Fsr}, {"Fsr2", ScalingFilter::Fsr2}, + {"CRTEasyMode", ScalingFilter::CRTEasyMode}, + {"CRTRoyale", ScalingFilter::CRTRoyale}, {"MaxEnum", ScalingFilter::MaxEnum}, }; } @@ -876,6 +880,7 @@ inline u32 EnumMetadata::Index() { return 26; } + template inline std::string CanonicalizeEnum(Type id) { const auto group = EnumMetadata::Canonicalizations(); diff --git a/src/video_core/host_shaders/CMakeLists.txt b/src/video_core/host_shaders/CMakeLists.txt index a14010350..a24cc5ecc 100644 --- a/src/video_core/host_shaders/CMakeLists.txt +++ b/src/video_core/host_shaders/CMakeLists.txt @@ -1,5 +1,5 @@ # SPDX-FileCopyrightText: 2018 yuzu Emulator Project -# SPDX-FileCopyrightText: Copyright 2025 citron Emulator Project +# SPDX-FileCopyrightText: 2026 citron Emulator Project # SPDX-License-Identifier: GPL-2.0-or-later set(FIDELITYFX_INCLUDE_DIR ${CMAKE_SOURCE_DIR}/externals/FidelityFX-FSR/ffx-fsr) @@ -74,6 +74,7 @@ set(SHADER_FILES vulkan_present_scaleforce_fp32.frag vulkan_present_scalefx_fp16.frag vulkan_present_scalefx_fp32.frag + vulkan_crt_easymode.frag vulkan_quad_indexed.comp vulkan_taa.frag vulkan_taa.vert diff --git a/src/video_core/host_shaders/vulkan_crt_easymode.frag b/src/video_core/host_shaders/vulkan_crt_easymode.frag new file mode 100644 index 000000000..bae9dd3aa --- /dev/null +++ b/src/video_core/host_shaders/vulkan_crt_easymode.frag @@ -0,0 +1,133 @@ +// SPDX-FileCopyrightText: Copyright 2026 citron Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later +// +// CRT EasyMode shader - Single-pass CRT effects +// Based on Libretro's crt-easymode shader +// https://github.com/libretro/common-shaders/blob/master/crt/shaders/crt-easymode.cg + +#version 460 core + +layout(location = 0) in vec2 frag_tex_coord; +layout(location = 0) out vec4 color; + +layout(binding = 0) uniform sampler2D color_texture; + +layout(push_constant) uniform CRTPushConstants { + layout(offset = 132) float scanline_strength; + layout(offset = 136) float curvature; + layout(offset = 140) float gamma; + layout(offset = 144) float bloom; + layout(offset = 148) int mask_type; + layout(offset = 152) float brightness; + layout(offset = 156) float alpha; + layout(offset = 160) float screen_width; + layout(offset = 164) float screen_height; +} crt_params; + +const float PI = 3.141592653589793; + +// Apply barrel distortion (curvature) +vec2 applyCurvature(vec2 coord) { + if (crt_params.curvature <= 0.0) { + return coord; + } + + vec2 centered = coord - 0.5; + float dist = length(centered); + float distortion = 1.0 + crt_params.curvature * dist * dist; + vec2 curved = centered * distortion + 0.5; + + // Clamp to valid texture coordinates + return clamp(curved, vec2(0.0), vec2(1.0)); +} + +// Generate scanlines +float scanline(float y) { + if (crt_params.scanline_strength <= 0.0) { + return 1.0; + } + + float scanline_pos = y * crt_params.screen_height; + float scanline_factor = abs(sin(scanline_pos * PI)); + + // Make scanlines more subtle + return 1.0 - crt_params.scanline_strength * scanline_factor * 0.5; +} + +// Apply phosphor mask (aperture grille or shadow mask) +vec3 applyMask(vec2 coord) { + if (crt_params.mask_type == 0) { + return vec3(1.0); // No mask + } + + vec2 screen_pos = coord * vec2(crt_params.screen_width, crt_params.screen_height); + + if (crt_params.mask_type == 1) { + // Aperture grille (vertical RGB stripes) + float mask = sin(screen_pos.x * PI * 3.0) * 0.5 + 0.5; + return vec3( + 1.0 - mask * 0.2, + 1.0 - mask * 0.15, + 1.0 - mask * 0.2 + ); + } else if (crt_params.mask_type == 2) { + // Shadow mask (triangular pattern) + float x = screen_pos.x * 3.0; + float y = screen_pos.y * 3.0; + float mask = sin(x * PI) * sin(y * PI) * 0.5 + 0.5; + return vec3(1.0 - mask * 0.15); + } + + return vec3(1.0); +} + +// Simple bloom effect (multi-tap blur approximation) +vec3 applyBloom(vec2 coord, vec3 original) { + if (crt_params.bloom <= 0.0) { + return original; + } + + vec2 texel_size = 1.0 / vec2(crt_params.screen_width, crt_params.screen_height); + vec3 bloom_color = original; + + // Simple 5-tap horizontal blur + for (int i = -2; i <= 2; i++) { + vec2 offset = vec2(float(i) * texel_size.x, 0.0); + vec3 sample_color = texture(color_texture, clamp(coord + offset, vec2(0.0), vec2(1.0))).rgb; + bloom_color += sample_color; + } + + bloom_color /= 6.0; // Average of 5 taps + original + + // Mix original with bloom + return mix(original, bloom_color, crt_params.bloom * 0.3); +} + +void main() { + // Apply curvature distortion first + vec2 curved_coord = applyCurvature(frag_tex_coord); + + // Sample the texture + vec3 rgb = texture(color_texture, curved_coord).rgb; + + // Apply bloom + rgb = applyBloom(curved_coord, rgb); + + // Apply phosphor mask + rgb *= applyMask(curved_coord); + + // Apply scanlines + float scan = scanline(curved_coord.y); + rgb *= scan; + + // Gamma correction + if (crt_params.gamma > 0.0 && crt_params.gamma != 1.0) { + rgb = pow(clamp(rgb, vec3(0.0), vec3(1.0)), vec3(1.0 / crt_params.gamma)); + } + + // Apply brightness adjustment + rgb *= crt_params.brightness; + + // Clamp to valid range and apply alpha + color = vec4(clamp(rgb, vec3(0.0), vec3(1.0)), crt_params.alpha); +} diff --git a/src/video_core/renderer_vulkan/present/filters.cpp b/src/video_core/renderer_vulkan/present/filters.cpp index e9dc2be22..673b1ea8a 100644 --- a/src/video_core/renderer_vulkan/present/filters.cpp +++ b/src/video_core/renderer_vulkan/present/filters.cpp @@ -1,5 +1,5 @@ // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project -// SPDX-FileCopyrightText: Copyright 2025 citron Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 citron Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include "common/common_types.h" @@ -12,6 +12,7 @@ #include "video_core/host_shaders/vulkan_present_scaleforce_fp32_frag_spv.h" #include "video_core/host_shaders/vulkan_present_scalefx_fp16_frag_spv.h" #include "video_core/host_shaders/vulkan_present_scalefx_fp32_frag_spv.h" +#include "video_core/host_shaders/vulkan_crt_easymode_frag_spv.h" #include "video_core/renderer_vulkan/present/filters.h" #include "video_core/renderer_vulkan/present/util.h" #include "video_core/renderer_vulkan/vk_shader_util.h" @@ -75,4 +76,9 @@ std::unique_ptr MakeLanczos(const Device& device, VkFormat fram BuildShader(device, PRESENT_LANCZOS_FRAG_SPV)); } +std::unique_ptr MakeCRT(const Device& device, VkFormat frame_format) { + return std::make_unique(device, frame_format, CreateBilinearSampler(device), + BuildShader(device, VULKAN_CRT_EASYMODE_FRAG_SPV)); +} + } // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/present/filters.h b/src/video_core/renderer_vulkan/present/filters.h index a69abf209..d7d047cb1 100644 --- a/src/video_core/renderer_vulkan/present/filters.h +++ b/src/video_core/renderer_vulkan/present/filters.h @@ -1,5 +1,5 @@ // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project -// SPDX-FileCopyrightText: Copyright 2025 citron Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 citron Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #pragma once @@ -17,5 +17,6 @@ std::unique_ptr MakeLanczos(const Device& device, VkFormat fram std::unique_ptr MakeGaussian(const Device& device, VkFormat frame_format); std::unique_ptr MakeScaleForce(const Device& device, VkFormat frame_format); std::unique_ptr MakeScaleFx(const Device& device, VkFormat frame_format); +std::unique_ptr MakeCRT(const Device& device, VkFormat frame_format); } // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/present/window_adapt_pass.cpp b/src/video_core/renderer_vulkan/present/window_adapt_pass.cpp index 46f477c82..2f576b997 100644 --- a/src/video_core/renderer_vulkan/present/window_adapt_pass.cpp +++ b/src/video_core/renderer_vulkan/present/window_adapt_pass.cpp @@ -1,5 +1,5 @@ // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project -// SPDX-FileCopyrightText: Copyright 2025 citron Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 citron Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include "core/frontend/framebuffer_layout.h" @@ -13,7 +13,7 @@ #include "video_core/renderer_vulkan/vk_shader_util.h" #include "video_core/vulkan_common/vulkan_device.h" #include "video_core/vulkan_common/vulkan_memory_allocator.h" -#include "common/settings.h" +#include "common/settings.h" namespace Vulkan { @@ -92,18 +92,51 @@ void WindowAdaptPass::Draw(RasterizerVulkan& rasterizer, Scheduler& scheduler, s BeginRenderPass(cmdbuf, renderpass, host_framebuffer, render_area); cmdbuf.ClearAttachments({clear_attachment}, {clear_rect}); + const auto current_scaling_filter = Settings::values.scaling_filter.GetValue(); + const bool is_crt_enabled = current_scaling_filter == Settings::ScalingFilter::CRTEasyMode || + current_scaling_filter == Settings::ScalingFilter::CRTRoyale; + for (size_t i = 0; i < layer_count; i++) { cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, graphics_pipelines[i]); cmdbuf.PushConstants(graphics_pipeline_layout, VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(PresentPushConstants), &push_constants[i]); - - if (Settings::values.scaling_filter.GetValue() == Settings::ScalingFilter::Lanczos) { + // Push Lanczos quality if using Lanczos filter + if (current_scaling_filter == Settings::ScalingFilter::Lanczos && !is_crt_enabled) { const s32 lanczos_a = Settings::values.lanczos_quality.GetValue(); cmdbuf.PushConstants(graphics_pipeline_layout, VK_SHADER_STAGE_FRAGMENT_BIT, sizeof(PresentPushConstants), sizeof(s32), &lanczos_a); } + // Push CRT parameters if CRT filter is enabled + if (is_crt_enabled) { + struct CRTPushConstants { + float scanline_strength; + float curvature; + float gamma; + float bloom; + int mask_type; + float brightness; + float alpha; + float screen_width; + float screen_height; + } crt_constants; + + crt_constants.scanline_strength = Settings::values.crt_scanline_strength.GetValue(); + crt_constants.curvature = Settings::values.crt_curvature.GetValue(); + crt_constants.gamma = Settings::values.crt_gamma.GetValue(); + crt_constants.bloom = Settings::values.crt_bloom.GetValue(); + crt_constants.mask_type = Settings::values.crt_mask_type.GetValue(); + crt_constants.brightness = Settings::values.crt_brightness.GetValue(); + crt_constants.alpha = Settings::values.crt_alpha.GetValue(); + crt_constants.screen_width = static_cast(render_area.width); + crt_constants.screen_height = static_cast(render_area.height); + + cmdbuf.PushConstants(graphics_pipeline_layout, VK_SHADER_STAGE_FRAGMENT_BIT, + sizeof(PresentPushConstants) + sizeof(s32), + sizeof(CRTPushConstants), &crt_constants); + } + cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_GRAPHICS, graphics_pipeline_layout, 0, descriptor_sets[i], {}); cmdbuf.Draw(4, 1, 0, 0); @@ -127,8 +160,11 @@ void WindowAdaptPass::CreateDescriptorSetLayout() { } void WindowAdaptPass::CreatePipelineLayout() { - - std::array ranges{}; + // Support up to 3 push constant ranges: + // 0: PresentPushConstants (vertex shader) + // 1: Lanczos quality (fragment shader) - optional + // 2: CRT parameters (fragment shader) - optional + std::array ranges{}; // Range 0: The existing constants for the Vertex Shader ranges[0] = { @@ -137,13 +173,33 @@ void WindowAdaptPass::CreatePipelineLayout() { .size = sizeof(PresentPushConstants), }; - // Range 1: Our new constant for the Fragment Shader + // Range 1: Lanczos quality for the Fragment Shader ranges[1] = { .stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT, .offset = sizeof(PresentPushConstants), .size = sizeof(s32), }; + // Range 2: CRT parameters for the Fragment Shader + // Offset after PresentPushConstants + Lanczos (if used) + // CRT constants: 8 floats + 1 int = 36 bytes + struct CRTPushConstants { + float scanline_strength; + float curvature; + float gamma; + float bloom; + int mask_type; + float brightness; + float alpha; + float screen_width; + float screen_height; + }; + ranges[2] = { + .stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT, + .offset = sizeof(PresentPushConstants) + sizeof(s32), + .size = sizeof(CRTPushConstants), + }; + pipeline_layout = device.GetLogical().CreatePipelineLayout(VkPipelineLayoutCreateInfo{ .sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, .pNext = nullptr, diff --git a/src/video_core/renderer_vulkan/vk_blit_screen.cpp b/src/video_core/renderer_vulkan/vk_blit_screen.cpp index 07d329872..d59da892c 100644 --- a/src/video_core/renderer_vulkan/vk_blit_screen.cpp +++ b/src/video_core/renderer_vulkan/vk_blit_screen.cpp @@ -1,7 +1,8 @@ // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project -// SPDX-FileCopyrightText: Copyright 2025 citron Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 citron Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include "common/settings.h" #include "video_core/framebuffer_config.h" #include "video_core/present.h" #include "video_core/renderer_vulkan/present/filters.h" @@ -50,6 +51,10 @@ void BlitScreen::SetWindowAdaptPass() { case Settings::ScalingFilter::ScaleFx: window_adapt = MakeScaleFx(device, swapchain_view_format); break; + case Settings::ScalingFilter::CRTEasyMode: + case Settings::ScalingFilter::CRTRoyale: + window_adapt = MakeCRT(device, swapchain_view_format); + break; case Settings::ScalingFilter::Fsr: case Settings::ScalingFilter::Fsr2: case Settings::ScalingFilter::Bilinear: