summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/input_common/drivers/udp_client.cpp74
-rw-r--r--src/input_common/helpers/udp_protocol.h21
-rw-r--r--src/tests/CMakeLists.txt3
-rw-r--r--src/tests/input_common/calibration_configuration_job.cpp136
-rw-r--r--src/video_core/renderer_opengl/gl_texture_cache.h2
-rw-r--r--src/video_core/renderer_opengl/renderer_opengl.cpp50
-rw-r--r--src/video_core/renderer_vulkan/blit_image.cpp123
-rw-r--r--src/video_core/renderer_vulkan/blit_image.h30
-rw-r--r--src/video_core/renderer_vulkan/vk_blit_screen.cpp37
-rw-r--r--src/video_core/renderer_vulkan/vk_texture_cache.cpp34
-rw-r--r--src/video_core/renderer_vulkan/vk_texture_cache.h8
-rw-r--r--src/video_core/texture_cache/texture_cache.h15
-rw-r--r--src/yuzu/configuration/config.cpp2
-rw-r--r--src/yuzu/game_list.cpp19
-rw-r--r--src/yuzu/uisettings.h3
15 files changed, 354 insertions, 203 deletions
diff --git a/src/input_common/drivers/udp_client.cpp b/src/input_common/drivers/udp_client.cpp
index 4ab991a7d..a1ce4525d 100644
--- a/src/input_common/drivers/udp_client.cpp
+++ b/src/input_common/drivers/udp_client.cpp
@@ -536,42 +536,46 @@ CalibrationConfigurationJob::CalibrationConfigurationJob(
536 std::function<void(u16, u16, u16, u16)> data_callback) { 536 std::function<void(u16, u16, u16, u16)> data_callback) {
537 537
538 std::thread([=, this] { 538 std::thread([=, this] {
539 u16 min_x{UINT16_MAX};
540 u16 min_y{UINT16_MAX};
541 u16 max_x{};
542 u16 max_y{};
543
539 Status current_status{Status::Initialized}; 544 Status current_status{Status::Initialized};
540 SocketCallback callback{ 545 SocketCallback callback{[](Response::Version) {}, [](Response::PortInfo) {},
541 [](Response::Version) {}, [](Response::PortInfo) {}, 546 [&](Response::PadData data) {
542 [&](Response::PadData data) { 547 constexpr u16 CALIBRATION_THRESHOLD = 100;
543 static constexpr u16 CALIBRATION_THRESHOLD = 100; 548
544 static constexpr u16 MAX_VALUE = UINT16_MAX; 549 if (current_status == Status::Initialized) {
545 550 // Receiving data means the communication is ready now
546 if (current_status == Status::Initialized) { 551 current_status = Status::Ready;
547 // Receiving data means the communication is ready now 552 status_callback(current_status);
548 current_status = Status::Ready; 553 }
549 status_callback(current_status); 554 if (data.touch[0].is_active == 0) {
550 } 555 return;
551 const auto& touchpad_0 = data.touch[0]; 556 }
552 if (touchpad_0.is_active == 0) { 557 LOG_DEBUG(Input, "Current touch: {} {}", data.touch[0].x,
553 return; 558 data.touch[0].y);
554 } 559 min_x = std::min(min_x, static_cast<u16>(data.touch[0].x));
555 LOG_DEBUG(Input, "Current touch: {} {}", touchpad_0.x, touchpad_0.y); 560 min_y = std::min(min_y, static_cast<u16>(data.touch[0].y));
556 const u16 min_x = std::min(MAX_VALUE, static_cast<u16>(touchpad_0.x)); 561 if (current_status == Status::Ready) {
557 const u16 min_y = std::min(MAX_VALUE, static_cast<u16>(touchpad_0.y)); 562 // First touch - min data (min_x/min_y)
558 if (current_status == Status::Ready) { 563 current_status = Status::Stage1Completed;
559 // First touch - min data (min_x/min_y) 564 status_callback(current_status);
560 current_status = Status::Stage1Completed; 565 }
561 status_callback(current_status); 566 if (data.touch[0].x - min_x > CALIBRATION_THRESHOLD &&
562 } 567 data.touch[0].y - min_y > CALIBRATION_THRESHOLD) {
563 if (touchpad_0.x - min_x > CALIBRATION_THRESHOLD && 568 // Set the current position as max value and finishes
564 touchpad_0.y - min_y > CALIBRATION_THRESHOLD) { 569 // configuration
565 // Set the current position as max value and finishes configuration 570 max_x = data.touch[0].x;
566 const u16 max_x = touchpad_0.x; 571 max_y = data.touch[0].y;
567 const u16 max_y = touchpad_0.y; 572 current_status = Status::Completed;
568 current_status = Status::Completed; 573 data_callback(min_x, min_y, max_x, max_y);
569 data_callback(min_x, min_y, max_x, max_y); 574 status_callback(current_status);
570 status_callback(current_status); 575
571 576 complete_event.Set();
572 complete_event.Set(); 577 }
573 } 578 }};
574 }};
575 Socket socket{host, port, std::move(callback)}; 579 Socket socket{host, port, std::move(callback)};
576 std::thread worker_thread{SocketLoop, &socket}; 580 std::thread worker_thread{SocketLoop, &socket};
577 complete_event.Wait(); 581 complete_event.Wait();
diff --git a/src/input_common/helpers/udp_protocol.h b/src/input_common/helpers/udp_protocol.h
index bcba12c58..2d5d54ddb 100644
--- a/src/input_common/helpers/udp_protocol.h
+++ b/src/input_common/helpers/udp_protocol.h
@@ -54,6 +54,18 @@ struct Message {
54template <typename T> 54template <typename T>
55constexpr Type GetMessageType(); 55constexpr Type GetMessageType();
56 56
57template <typename T>
58Message<T> CreateMessage(const u32 magic, const T data, const u32 sender_id) {
59 boost::crc_32_type crc;
60 Header header{
61 magic, PROTOCOL_VERSION, sizeof(T) + sizeof(Type), 0, sender_id, GetMessageType<T>(),
62 };
63 Message<T> message{header, data};
64 crc.process_bytes(&message, sizeof(Message<T>));
65 message.header.crc = crc.checksum();
66 return message;
67}
68
57namespace Request { 69namespace Request {
58 70
59enum RegisterFlags : u8 { 71enum RegisterFlags : u8 {
@@ -101,14 +113,7 @@ static_assert(std::is_trivially_copyable_v<PadData>,
101 */ 113 */
102template <typename T> 114template <typename T>
103Message<T> Create(const T data, const u32 client_id = 0) { 115Message<T> Create(const T data, const u32 client_id = 0) {
104 boost::crc_32_type crc; 116 return CreateMessage(CLIENT_MAGIC, data, client_id);
105 Header header{
106 CLIENT_MAGIC, PROTOCOL_VERSION, sizeof(T) + sizeof(Type), 0, client_id, GetMessageType<T>(),
107 };
108 Message<T> message{header, data};
109 crc.process_bytes(&message, sizeof(Message<T>));
110 message.header.crc = crc.checksum();
111 return message;
112} 117}
113} // namespace Request 118} // namespace Request
114 119
diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt
index c4c012f3d..4a20c0768 100644
--- a/src/tests/CMakeLists.txt
+++ b/src/tests/CMakeLists.txt
@@ -10,11 +10,12 @@ add_executable(tests
10 core/network/network.cpp 10 core/network/network.cpp
11 tests.cpp 11 tests.cpp
12 video_core/buffer_base.cpp 12 video_core/buffer_base.cpp
13 input_common/calibration_configuration_job.cpp
13) 14)
14 15
15create_target_directory_groups(tests) 16create_target_directory_groups(tests)
16 17
17target_link_libraries(tests PRIVATE common core) 18target_link_libraries(tests PRIVATE common core input_common)
18target_link_libraries(tests PRIVATE ${PLATFORM_LIBRARIES} catch-single-include Threads::Threads) 19target_link_libraries(tests PRIVATE ${PLATFORM_LIBRARIES} catch-single-include Threads::Threads)
19 20
20add_test(NAME tests COMMAND tests) 21add_test(NAME tests COMMAND tests)
diff --git a/src/tests/input_common/calibration_configuration_job.cpp b/src/tests/input_common/calibration_configuration_job.cpp
new file mode 100644
index 000000000..8c77d81e9
--- /dev/null
+++ b/src/tests/input_common/calibration_configuration_job.cpp
@@ -0,0 +1,136 @@
1// Copyright 2020 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <array>
6#include <string>
7#include <thread>
8#include <boost/asio.hpp>
9#include <boost/crc.hpp>
10#include <catch2/catch.hpp>
11
12#include "input_common/drivers/udp_client.h"
13#include "input_common/helpers/udp_protocol.h"
14
15class FakeCemuhookServer {
16public:
17 FakeCemuhookServer()
18 : socket(io_service, boost::asio::ip::udp::endpoint(boost::asio::ip::udp::v4(), 0)) {}
19
20 ~FakeCemuhookServer() {
21 is_running = false;
22 boost::system::error_code error_code;
23 socket.shutdown(boost::asio::socket_base::shutdown_both, error_code);
24 socket.close();
25 if (handler.joinable()) {
26 handler.join();
27 }
28 }
29
30 u16 GetPort() {
31 return socket.local_endpoint().port();
32 }
33
34 std::string GetHost() {
35 return socket.local_endpoint().address().to_string();
36 }
37
38 void Run(const std::vector<InputCommon::CemuhookUDP::Response::TouchPad> touch_movement_path) {
39 constexpr size_t HeaderSize = sizeof(InputCommon::CemuhookUDP::Header);
40 constexpr size_t PadDataSize =
41 sizeof(InputCommon::CemuhookUDP::Message<InputCommon::CemuhookUDP::Response::PadData>);
42
43 REQUIRE(touch_movement_path.size() > 0);
44 is_running = true;
45 handler = std::thread([touch_movement_path, this]() {
46 auto current_touch_position = touch_movement_path.begin();
47 while (is_running) {
48 boost::asio::ip::udp::endpoint sender_endpoint;
49 boost::system::error_code error_code;
50 auto received_size = socket.receive_from(boost::asio::buffer(receive_buffer),
51 sender_endpoint, 0, error_code);
52
53 if (received_size < HeaderSize) {
54 continue;
55 }
56
57 InputCommon::CemuhookUDP::Header header{};
58 std::memcpy(&header, receive_buffer.data(), HeaderSize);
59 switch (header.type) {
60 case InputCommon::CemuhookUDP::Type::PadData: {
61 InputCommon::CemuhookUDP::Response::PadData pad_data{};
62 pad_data.touch[0] = *current_touch_position;
63 const auto pad_message = InputCommon::CemuhookUDP::CreateMessage(
64 InputCommon::CemuhookUDP::SERVER_MAGIC, pad_data, 0);
65 std::memcpy(send_buffer.data(), &pad_message, PadDataSize);
66 socket.send_to(boost::asio::buffer(send_buffer, PadDataSize), sender_endpoint,
67 0, error_code);
68
69 bool can_advance =
70 std::next(current_touch_position) != touch_movement_path.end();
71 if (can_advance) {
72 std::advance(current_touch_position, 1);
73 }
74 break;
75 }
76 case InputCommon::CemuhookUDP::Type::PortInfo:
77 case InputCommon::CemuhookUDP::Type::Version:
78 default:
79 break;
80 }
81 }
82 });
83 }
84
85private:
86 boost::asio::io_service io_service;
87 boost::asio::ip::udp::socket socket;
88 std::array<u8, InputCommon::CemuhookUDP::MAX_PACKET_SIZE> send_buffer;
89 std::array<u8, InputCommon::CemuhookUDP::MAX_PACKET_SIZE> receive_buffer;
90 bool is_running = false;
91 std::thread handler;
92};
93
94TEST_CASE("CalibrationConfigurationJob completed", "[input_common]") {
95 Common::Event complete_event;
96 FakeCemuhookServer server;
97 server.Run({{
98 .is_active = 1,
99 .x = 0,
100 .y = 0,
101 },
102 {
103 .is_active = 1,
104 .x = 200,
105 .y = 200,
106 }});
107
108 InputCommon::CemuhookUDP::CalibrationConfigurationJob::Status status{};
109 u16 min_x{};
110 u16 min_y{};
111 u16 max_x{};
112 u16 max_y{};
113 InputCommon::CemuhookUDP::CalibrationConfigurationJob job(
114 server.GetHost(), server.GetPort(),
115 [&status,
116 &complete_event](InputCommon::CemuhookUDP::CalibrationConfigurationJob::Status status_) {
117 status = status_;
118 if (status ==
119 InputCommon::CemuhookUDP::CalibrationConfigurationJob::Status::Completed) {
120 complete_event.Set();
121 }
122 },
123 [&](u16 min_x_, u16 min_y_, u16 max_x_, u16 max_y_) {
124 min_x = min_x_;
125 min_y = min_y_;
126 max_x = max_x_;
127 max_y = max_y_;
128 });
129
130 complete_event.WaitUntil(std::chrono::system_clock::now() + std::chrono::seconds(10));
131 REQUIRE(status == InputCommon::CemuhookUDP::CalibrationConfigurationJob::Status::Completed);
132 REQUIRE(min_x == 0);
133 REQUIRE(min_y == 0);
134 REQUIRE(max_x == 200);
135 REQUIRE(max_y == 200);
136}
diff --git a/src/video_core/renderer_opengl/gl_texture_cache.h b/src/video_core/renderer_opengl/gl_texture_cache.h
index 37d5e6a6b..dbf1df79c 100644
--- a/src/video_core/renderer_opengl/gl_texture_cache.h
+++ b/src/video_core/renderer_opengl/gl_texture_cache.h
@@ -92,7 +92,7 @@ public:
92 92
93 void ReinterpretImage(Image& dst, Image& src, std::span<const VideoCommon::ImageCopy> copies); 93 void ReinterpretImage(Image& dst, Image& src, std::span<const VideoCommon::ImageCopy> copies);
94 94
95 void ConvertImage(Framebuffer* dst, ImageView& dst_view, ImageView& src_view, bool rescaled) { 95 void ConvertImage(Framebuffer* dst, ImageView& dst_view, ImageView& src_view) {
96 UNIMPLEMENTED(); 96 UNIMPLEMENTED();
97 } 97 }
98 98
diff --git a/src/video_core/renderer_opengl/renderer_opengl.cpp b/src/video_core/renderer_opengl/renderer_opengl.cpp
index 28daacd82..f81c1b233 100644
--- a/src/video_core/renderer_opengl/renderer_opengl.cpp
+++ b/src/video_core/renderer_opengl/renderer_opengl.cpp
@@ -437,39 +437,29 @@ void RendererOpenGL::DrawScreen(const Layout::FramebufferLayout& layout) {
437 437
438 glBindTextureUnit(0, fxaa_texture.handle); 438 glBindTextureUnit(0, fxaa_texture.handle);
439 } 439 }
440
441 // Set projection matrix
442 const std::array ortho_matrix = 440 const std::array ortho_matrix =
443 MakeOrthographicMatrix(static_cast<float>(layout.width), static_cast<float>(layout.height)); 441 MakeOrthographicMatrix(static_cast<float>(layout.width), static_cast<float>(layout.height));
444 442
445 GLuint fragment_handle; 443 const auto fragment_handle = [this]() {
446 const auto filter = Settings::values.scaling_filter.GetValue(); 444 switch (Settings::values.scaling_filter.GetValue()) {
447 switch (filter) { 445 case Settings::ScalingFilter::NearestNeighbor:
448 case Settings::ScalingFilter::NearestNeighbor: 446 case Settings::ScalingFilter::Bilinear:
449 fragment_handle = present_bilinear_fragment.handle; 447 return present_bilinear_fragment.handle;
450 break; 448 case Settings::ScalingFilter::Bicubic:
451 case Settings::ScalingFilter::Bilinear: 449 return present_bicubic_fragment.handle;
452 fragment_handle = present_bilinear_fragment.handle; 450 case Settings::ScalingFilter::Gaussian:
453 break; 451 return present_gaussian_fragment.handle;
454 case Settings::ScalingFilter::Bicubic: 452 case Settings::ScalingFilter::ScaleForce:
455 fragment_handle = present_bicubic_fragment.handle; 453 return present_scaleforce_fragment.handle;
456 break; 454 case Settings::ScalingFilter::Fsr:
457 case Settings::ScalingFilter::Gaussian: 455 LOG_WARNING(
458 fragment_handle = present_gaussian_fragment.handle; 456 Render_OpenGL,
459 break; 457 "FidelityFX Super Resolution is not supported in OpenGL, changing to ScaleForce");
460 case Settings::ScalingFilter::ScaleForce: 458 return present_scaleforce_fragment.handle;
461 fragment_handle = present_scaleforce_fragment.handle; 459 default:
462 break; 460 return present_bilinear_fragment.handle;
463 case Settings::ScalingFilter::Fsr: 461 }
464 LOG_WARNING( 462 }();
465 Render_OpenGL,
466 "FidelityFX FSR Super Sampling is not supported in OpenGL, changing to ScaleForce");
467 fragment_handle = present_scaleforce_fragment.handle;
468 break;
469 default:
470 fragment_handle = present_bilinear_fragment.handle;
471 break;
472 }
473 program_manager.BindPresentPrograms(present_vertex.handle, fragment_handle); 463 program_manager.BindPresentPrograms(present_vertex.handle, fragment_handle);
474 glProgramUniformMatrix3x2fv(present_vertex.handle, ModelViewMatrixLocation, 1, GL_FALSE, 464 glProgramUniformMatrix3x2fv(present_vertex.handle, ModelViewMatrixLocation, 1, GL_FALSE,
475 ortho_matrix.data()); 465 ortho_matrix.data());
diff --git a/src/video_core/renderer_vulkan/blit_image.cpp b/src/video_core/renderer_vulkan/blit_image.cpp
index 9a38b6b34..cd5995897 100644
--- a/src/video_core/renderer_vulkan/blit_image.cpp
+++ b/src/video_core/renderer_vulkan/blit_image.cpp
@@ -4,6 +4,7 @@
4 4
5#include <algorithm> 5#include <algorithm>
6 6
7#include "common/settings.h"
7#include "video_core/host_shaders/convert_abgr8_to_d24s8_frag_spv.h" 8#include "video_core/host_shaders/convert_abgr8_to_d24s8_frag_spv.h"
8#include "video_core/host_shaders/convert_d24s8_to_abgr8_frag_spv.h" 9#include "video_core/host_shaders/convert_d24s8_to_abgr8_frag_spv.h"
9#include "video_core/host_shaders/convert_depth_to_float_frag_spv.h" 10#include "video_core/host_shaders/convert_depth_to_float_frag_spv.h"
@@ -335,6 +336,17 @@ void BindBlitState(vk::CommandBuffer cmdbuf, VkPipelineLayout layout, const Regi
335 cmdbuf.SetScissor(0, scissor); 336 cmdbuf.SetScissor(0, scissor);
336 cmdbuf.PushConstants(layout, VK_SHADER_STAGE_VERTEX_BIT, push_constants); 337 cmdbuf.PushConstants(layout, VK_SHADER_STAGE_VERTEX_BIT, push_constants);
337} 338}
339
340VkExtent2D GetConversionExtent(const ImageView& src_image_view) {
341 const auto& resolution = Settings::values.resolution_info;
342 const bool is_rescaled = src_image_view.IsRescaled();
343 u32 width = src_image_view.size.width;
344 u32 height = src_image_view.size.height;
345 return VkExtent2D{
346 .width = is_rescaled ? resolution.ScaleUp(width) : width,
347 .height = is_rescaled ? resolution.ScaleUp(height) : height,
348 };
349}
338} // Anonymous namespace 350} // Anonymous namespace
339 351
340BlitImageHelper::BlitImageHelper(const Device& device_, VKScheduler& scheduler_, 352BlitImageHelper::BlitImageHelper(const Device& device_, VKScheduler& scheduler_,
@@ -425,61 +437,52 @@ void BlitImageHelper::BlitDepthStencil(const Framebuffer* dst_framebuffer,
425} 437}
426 438
427void BlitImageHelper::ConvertD32ToR32(const Framebuffer* dst_framebuffer, 439void BlitImageHelper::ConvertD32ToR32(const Framebuffer* dst_framebuffer,
428 const ImageView& src_image_view, u32 up_scale, 440 const ImageView& src_image_view) {
429 u32 down_shift) {
430 ConvertDepthToColorPipeline(convert_d32_to_r32_pipeline, dst_framebuffer->RenderPass()); 441 ConvertDepthToColorPipeline(convert_d32_to_r32_pipeline, dst_framebuffer->RenderPass());
431 Convert(*convert_d32_to_r32_pipeline, dst_framebuffer, src_image_view, up_scale, down_shift); 442 Convert(*convert_d32_to_r32_pipeline, dst_framebuffer, src_image_view);
432} 443}
433 444
434void BlitImageHelper::ConvertR32ToD32(const Framebuffer* dst_framebuffer, 445void BlitImageHelper::ConvertR32ToD32(const Framebuffer* dst_framebuffer,
435 const ImageView& src_image_view, u32 up_scale, 446 const ImageView& src_image_view) {
436 u32 down_shift) {
437 ConvertColorToDepthPipeline(convert_r32_to_d32_pipeline, dst_framebuffer->RenderPass()); 447 ConvertColorToDepthPipeline(convert_r32_to_d32_pipeline, dst_framebuffer->RenderPass());
438 Convert(*convert_r32_to_d32_pipeline, dst_framebuffer, src_image_view, up_scale, down_shift); 448 Convert(*convert_r32_to_d32_pipeline, dst_framebuffer, src_image_view);
439} 449}
440 450
441void BlitImageHelper::ConvertD16ToR16(const Framebuffer* dst_framebuffer, 451void BlitImageHelper::ConvertD16ToR16(const Framebuffer* dst_framebuffer,
442 const ImageView& src_image_view, u32 up_scale, 452 const ImageView& src_image_view) {
443 u32 down_shift) {
444 ConvertDepthToColorPipeline(convert_d16_to_r16_pipeline, dst_framebuffer->RenderPass()); 453 ConvertDepthToColorPipeline(convert_d16_to_r16_pipeline, dst_framebuffer->RenderPass());
445 Convert(*convert_d16_to_r16_pipeline, dst_framebuffer, src_image_view, up_scale, down_shift); 454 Convert(*convert_d16_to_r16_pipeline, dst_framebuffer, src_image_view);
446} 455}
447 456
448void BlitImageHelper::ConvertR16ToD16(const Framebuffer* dst_framebuffer, 457void BlitImageHelper::ConvertR16ToD16(const Framebuffer* dst_framebuffer,
449 const ImageView& src_image_view, u32 up_scale, 458 const ImageView& src_image_view) {
450 u32 down_shift) {
451 ConvertColorToDepthPipeline(convert_r16_to_d16_pipeline, dst_framebuffer->RenderPass()); 459 ConvertColorToDepthPipeline(convert_r16_to_d16_pipeline, dst_framebuffer->RenderPass());
452 Convert(*convert_r16_to_d16_pipeline, dst_framebuffer, src_image_view, up_scale, down_shift); 460 Convert(*convert_r16_to_d16_pipeline, dst_framebuffer, src_image_view);
453} 461}
454 462
455void BlitImageHelper::ConvertABGR8ToD24S8(const Framebuffer* dst_framebuffer, 463void BlitImageHelper::ConvertABGR8ToD24S8(const Framebuffer* dst_framebuffer,
456 ImageView& src_image_view, u32 up_scale, u32 down_shift) { 464 const ImageView& src_image_view) {
457 ConvertPipelineDepthTargetEx(convert_abgr8_to_d24s8_pipeline, dst_framebuffer->RenderPass(), 465 ConvertPipelineDepthTargetEx(convert_abgr8_to_d24s8_pipeline, dst_framebuffer->RenderPass(),
458 convert_abgr8_to_d24s8_frag, true); 466 convert_abgr8_to_d24s8_frag);
459 ConvertColor(*convert_abgr8_to_d24s8_pipeline, dst_framebuffer, src_image_view, up_scale, 467 Convert(*convert_abgr8_to_d24s8_pipeline, dst_framebuffer, src_image_view);
460 down_shift);
461} 468}
462 469
463void BlitImageHelper::ConvertD24S8ToABGR8(const Framebuffer* dst_framebuffer, 470void BlitImageHelper::ConvertD24S8ToABGR8(const Framebuffer* dst_framebuffer,
464 ImageView& src_image_view, u32 up_scale, u32 down_shift) { 471 ImageView& src_image_view) {
465 ConvertPipelineColorTargetEx(convert_d24s8_to_abgr8_pipeline, dst_framebuffer->RenderPass(), 472 ConvertPipelineColorTargetEx(convert_d24s8_to_abgr8_pipeline, dst_framebuffer->RenderPass(),
466 convert_d24s8_to_abgr8_frag, false); 473 convert_d24s8_to_abgr8_frag);
467 ConvertDepthStencil(*convert_d24s8_to_abgr8_pipeline, dst_framebuffer, src_image_view, up_scale, 474 ConvertDepthStencil(*convert_d24s8_to_abgr8_pipeline, dst_framebuffer, src_image_view);
468 down_shift);
469} 475}
470 476
471void BlitImageHelper::Convert(VkPipeline pipeline, const Framebuffer* dst_framebuffer, 477void BlitImageHelper::Convert(VkPipeline pipeline, const Framebuffer* dst_framebuffer,
472 const ImageView& src_image_view, u32 up_scale, u32 down_shift) { 478 const ImageView& src_image_view) {
473 const VkPipelineLayout layout = *one_texture_pipeline_layout; 479 const VkPipelineLayout layout = *one_texture_pipeline_layout;
474 const VkImageView src_view = src_image_view.Handle(Shader::TextureType::Color2D); 480 const VkImageView src_view = src_image_view.Handle(Shader::TextureType::Color2D);
475 const VkSampler sampler = *nearest_sampler; 481 const VkSampler sampler = *nearest_sampler;
476 const VkExtent2D extent{ 482 const VkExtent2D extent = GetConversionExtent(src_image_view);
477 .width = std::max((src_image_view.size.width * up_scale) >> down_shift, 1U), 483
478 .height = std::max((src_image_view.size.height * up_scale) >> down_shift, 1U),
479 };
480 scheduler.RequestRenderpass(dst_framebuffer); 484 scheduler.RequestRenderpass(dst_framebuffer);
481 scheduler.Record([pipeline, layout, sampler, src_view, extent, up_scale, down_shift, 485 scheduler.Record([pipeline, layout, sampler, src_view, extent, this](vk::CommandBuffer cmdbuf) {
482 this](vk::CommandBuffer cmdbuf) {
483 const VkOffset2D offset{ 486 const VkOffset2D offset{
484 .x = 0, 487 .x = 0,
485 .y = 0, 488 .y = 0,
@@ -563,18 +566,16 @@ void BlitImageHelper::ConvertColor(VkPipeline pipeline, const Framebuffer* dst_f
563} 566}
564 567
565void BlitImageHelper::ConvertDepthStencil(VkPipeline pipeline, const Framebuffer* dst_framebuffer, 568void BlitImageHelper::ConvertDepthStencil(VkPipeline pipeline, const Framebuffer* dst_framebuffer,
566 ImageView& src_image_view, u32 up_scale, u32 down_shift) { 569 ImageView& src_image_view) {
567 const VkPipelineLayout layout = *two_textures_pipeline_layout; 570 const VkPipelineLayout layout = *two_textures_pipeline_layout;
568 const VkImageView src_depth_view = src_image_view.DepthView(); 571 const VkImageView src_depth_view = src_image_view.DepthView();
569 const VkImageView src_stencil_view = src_image_view.StencilView(); 572 const VkImageView src_stencil_view = src_image_view.StencilView();
570 const VkSampler sampler = *nearest_sampler; 573 const VkSampler sampler = *nearest_sampler;
571 const VkExtent2D extent{ 574 const VkExtent2D extent = GetConversionExtent(src_image_view);
572 .width = std::max((src_image_view.size.width * up_scale) >> down_shift, 1U), 575
573 .height = std::max((src_image_view.size.height * up_scale) >> down_shift, 1U),
574 };
575 scheduler.RequestRenderpass(dst_framebuffer); 576 scheduler.RequestRenderpass(dst_framebuffer);
576 scheduler.Record([pipeline, layout, sampler, src_depth_view, src_stencil_view, extent, up_scale, 577 scheduler.Record([pipeline, layout, sampler, src_depth_view, src_stencil_view, extent,
577 down_shift, this](vk::CommandBuffer cmdbuf) { 578 this](vk::CommandBuffer cmdbuf) {
578 const VkOffset2D offset{ 579 const VkOffset2D offset{
579 .x = 0, 580 .x = 0,
580 .y = 0, 581 .y = 0,
@@ -695,11 +696,14 @@ VkPipeline BlitImageHelper::FindOrEmplaceDepthStencilPipeline(const BlitImagePip
695 return *blit_depth_stencil_pipelines.back(); 696 return *blit_depth_stencil_pipelines.back();
696} 697}
697 698
698void BlitImageHelper::ConvertDepthToColorPipeline(vk::Pipeline& pipeline, VkRenderPass renderpass) { 699void BlitImageHelper::ConvertPipeline(vk::Pipeline& pipeline, VkRenderPass renderpass,
700 bool is_target_depth) {
699 if (pipeline) { 701 if (pipeline) {
700 return; 702 return;
701 } 703 }
702 const std::array stages = MakeStages(*full_screen_vert, *convert_depth_to_float_frag); 704 VkShaderModule frag_shader =
705 is_target_depth ? *convert_float_to_depth_frag : *convert_depth_to_float_frag;
706 const std::array stages = MakeStages(*full_screen_vert, frag_shader);
703 pipeline = device.GetLogical().CreateGraphicsPipeline({ 707 pipeline = device.GetLogical().CreateGraphicsPipeline({
704 .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, 708 .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
705 .pNext = nullptr, 709 .pNext = nullptr,
@@ -712,8 +716,9 @@ void BlitImageHelper::ConvertDepthToColorPipeline(vk::Pipeline& pipeline, VkRend
712 .pViewportState = &PIPELINE_VIEWPORT_STATE_CREATE_INFO, 716 .pViewportState = &PIPELINE_VIEWPORT_STATE_CREATE_INFO,
713 .pRasterizationState = &PIPELINE_RASTERIZATION_STATE_CREATE_INFO, 717 .pRasterizationState = &PIPELINE_RASTERIZATION_STATE_CREATE_INFO,
714 .pMultisampleState = &PIPELINE_MULTISAMPLE_STATE_CREATE_INFO, 718 .pMultisampleState = &PIPELINE_MULTISAMPLE_STATE_CREATE_INFO,
715 .pDepthStencilState = nullptr, 719 .pDepthStencilState = is_target_depth ? &PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO : nullptr,
716 .pColorBlendState = &PIPELINE_COLOR_BLEND_STATE_GENERIC_CREATE_INFO, 720 .pColorBlendState = is_target_depth ? &PIPELINE_COLOR_BLEND_STATE_EMPTY_CREATE_INFO
721 : &PIPELINE_COLOR_BLEND_STATE_GENERIC_CREATE_INFO,
717 .pDynamicState = &PIPELINE_DYNAMIC_STATE_CREATE_INFO, 722 .pDynamicState = &PIPELINE_DYNAMIC_STATE_CREATE_INFO,
718 .layout = *one_texture_pipeline_layout, 723 .layout = *one_texture_pipeline_layout,
719 .renderPass = renderpass, 724 .renderPass = renderpass,
@@ -723,37 +728,17 @@ void BlitImageHelper::ConvertDepthToColorPipeline(vk::Pipeline& pipeline, VkRend
723 }); 728 });
724} 729}
725 730
731void BlitImageHelper::ConvertDepthToColorPipeline(vk::Pipeline& pipeline, VkRenderPass renderpass) {
732 ConvertPipeline(pipeline, renderpass, false);
733}
734
726void BlitImageHelper::ConvertColorToDepthPipeline(vk::Pipeline& pipeline, VkRenderPass renderpass) { 735void BlitImageHelper::ConvertColorToDepthPipeline(vk::Pipeline& pipeline, VkRenderPass renderpass) {
727 if (pipeline) { 736 ConvertPipeline(pipeline, renderpass, true);
728 return;
729 }
730 const std::array stages = MakeStages(*full_screen_vert, *convert_float_to_depth_frag);
731 pipeline = device.GetLogical().CreateGraphicsPipeline({
732 .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
733 .pNext = nullptr,
734 .flags = 0,
735 .stageCount = static_cast<u32>(stages.size()),
736 .pStages = stages.data(),
737 .pVertexInputState = &PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO,
738 .pInputAssemblyState = &PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO,
739 .pTessellationState = nullptr,
740 .pViewportState = &PIPELINE_VIEWPORT_STATE_CREATE_INFO,
741 .pRasterizationState = &PIPELINE_RASTERIZATION_STATE_CREATE_INFO,
742 .pMultisampleState = &PIPELINE_MULTISAMPLE_STATE_CREATE_INFO,
743 .pDepthStencilState = &PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO,
744 .pColorBlendState = &PIPELINE_COLOR_BLEND_STATE_EMPTY_CREATE_INFO,
745 .pDynamicState = &PIPELINE_DYNAMIC_STATE_CREATE_INFO,
746 .layout = *one_texture_pipeline_layout,
747 .renderPass = renderpass,
748 .subpass = 0,
749 .basePipelineHandle = VK_NULL_HANDLE,
750 .basePipelineIndex = 0,
751 });
752} 737}
753 738
754void BlitImageHelper::ConvertPipelineEx(vk::Pipeline& pipeline, VkRenderPass renderpass, 739void BlitImageHelper::ConvertPipelineEx(vk::Pipeline& pipeline, VkRenderPass renderpass,
755 vk::ShaderModule& module, bool is_target_depth, 740 vk::ShaderModule& module, bool single_texture,
756 bool single_texture) { 741 bool is_target_depth) {
757 if (pipeline) { 742 if (pipeline) {
758 return; 743 return;
759 } 744 }
@@ -782,13 +767,13 @@ void BlitImageHelper::ConvertPipelineEx(vk::Pipeline& pipeline, VkRenderPass ren
782} 767}
783 768
784void BlitImageHelper::ConvertPipelineColorTargetEx(vk::Pipeline& pipeline, VkRenderPass renderpass, 769void BlitImageHelper::ConvertPipelineColorTargetEx(vk::Pipeline& pipeline, VkRenderPass renderpass,
785 vk::ShaderModule& module, bool single_texture) { 770 vk::ShaderModule& module) {
786 ConvertPipelineEx(pipeline, renderpass, module, false, single_texture); 771 ConvertPipelineEx(pipeline, renderpass, module, false, false);
787} 772}
788 773
789void BlitImageHelper::ConvertPipelineDepthTargetEx(vk::Pipeline& pipeline, VkRenderPass renderpass, 774void BlitImageHelper::ConvertPipelineDepthTargetEx(vk::Pipeline& pipeline, VkRenderPass renderpass,
790 vk::ShaderModule& module, bool single_texture) { 775 vk::ShaderModule& module) {
791 ConvertPipelineEx(pipeline, renderpass, module, true, single_texture); 776 ConvertPipelineEx(pipeline, renderpass, module, true, true);
792} 777}
793 778
794} // namespace Vulkan 779} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/blit_image.h b/src/video_core/renderer_vulkan/blit_image.h
index b1a717090..1d9f61a52 100644
--- a/src/video_core/renderer_vulkan/blit_image.h
+++ b/src/video_core/renderer_vulkan/blit_image.h
@@ -44,50 +44,46 @@ public:
44 const Region2D& src_region, Tegra::Engines::Fermi2D::Filter filter, 44 const Region2D& src_region, Tegra::Engines::Fermi2D::Filter filter,
45 Tegra::Engines::Fermi2D::Operation operation); 45 Tegra::Engines::Fermi2D::Operation operation);
46 46
47 void ConvertD32ToR32(const Framebuffer* dst_framebuffer, const ImageView& src_image_view, 47 void ConvertD32ToR32(const Framebuffer* dst_framebuffer, const ImageView& src_image_view);
48 u32 up_scale, u32 down_shift);
49 48
50 void ConvertR32ToD32(const Framebuffer* dst_framebuffer, const ImageView& src_image_view, 49 void ConvertR32ToD32(const Framebuffer* dst_framebuffer, const ImageView& src_image_view);
51 u32 up_scale, u32 down_shift);
52 50
53 void ConvertD16ToR16(const Framebuffer* dst_framebuffer, const ImageView& src_image_view, 51 void ConvertD16ToR16(const Framebuffer* dst_framebuffer, const ImageView& src_image_view);
54 u32 up_scale, u32 down_shift);
55 52
56 void ConvertR16ToD16(const Framebuffer* dst_framebuffer, const ImageView& src_image_view, 53 void ConvertR16ToD16(const Framebuffer* dst_framebuffer, const ImageView& src_image_view);
57 u32 up_scale, u32 down_shift);
58 54
59 void ConvertABGR8ToD24S8(const Framebuffer* dst_framebuffer, ImageView& src_image_view, 55 void ConvertABGR8ToD24S8(const Framebuffer* dst_framebuffer, const ImageView& src_image_view);
60 u32 up_scale, u32 down_shift);
61 56
62 void ConvertD24S8ToABGR8(const Framebuffer* dst_framebuffer, ImageView& src_image_view, 57 void ConvertD24S8ToABGR8(const Framebuffer* dst_framebuffer, ImageView& src_image_view);
63 u32 up_scale, u32 down_shift);
64 58
65private: 59private:
66 void Convert(VkPipeline pipeline, const Framebuffer* dst_framebuffer, 60 void Convert(VkPipeline pipeline, const Framebuffer* dst_framebuffer,
67 const ImageView& src_image_view, u32 up_scale, u32 down_shift); 61 const ImageView& src_image_view);
68 62
69 void ConvertColor(VkPipeline pipeline, const Framebuffer* dst_framebuffer, 63 void ConvertColor(VkPipeline pipeline, const Framebuffer* dst_framebuffer,
70 ImageView& src_image_view, u32 up_scale, u32 down_shift); 64 ImageView& src_image_view, u32 up_scale, u32 down_shift);
71 65
72 void ConvertDepthStencil(VkPipeline pipeline, const Framebuffer* dst_framebuffer, 66 void ConvertDepthStencil(VkPipeline pipeline, const Framebuffer* dst_framebuffer,
73 ImageView& src_image_view, u32 up_scale, u32 down_shift); 67 ImageView& src_image_view);
74 68
75 [[nodiscard]] VkPipeline FindOrEmplaceColorPipeline(const BlitImagePipelineKey& key); 69 [[nodiscard]] VkPipeline FindOrEmplaceColorPipeline(const BlitImagePipelineKey& key);
76 70
77 [[nodiscard]] VkPipeline FindOrEmplaceDepthStencilPipeline(const BlitImagePipelineKey& key); 71 [[nodiscard]] VkPipeline FindOrEmplaceDepthStencilPipeline(const BlitImagePipelineKey& key);
78 72
73 void ConvertPipeline(vk::Pipeline& pipeline, VkRenderPass renderpass, bool is_target_depth);
74
79 void ConvertDepthToColorPipeline(vk::Pipeline& pipeline, VkRenderPass renderpass); 75 void ConvertDepthToColorPipeline(vk::Pipeline& pipeline, VkRenderPass renderpass);
80 76
81 void ConvertColorToDepthPipeline(vk::Pipeline& pipeline, VkRenderPass renderpass); 77 void ConvertColorToDepthPipeline(vk::Pipeline& pipeline, VkRenderPass renderpass);
82 78
83 void ConvertPipelineEx(vk::Pipeline& pipeline, VkRenderPass renderpass, 79 void ConvertPipelineEx(vk::Pipeline& pipeline, VkRenderPass renderpass,
84 vk::ShaderModule& module, bool is_target_depth, bool single_texture); 80 vk::ShaderModule& module, bool single_texture, bool is_target_depth);
85 81
86 void ConvertPipelineColorTargetEx(vk::Pipeline& pipeline, VkRenderPass renderpass, 82 void ConvertPipelineColorTargetEx(vk::Pipeline& pipeline, VkRenderPass renderpass,
87 vk::ShaderModule& module, bool single_texture); 83 vk::ShaderModule& module);
88 84
89 void ConvertPipelineDepthTargetEx(vk::Pipeline& pipeline, VkRenderPass renderpass, 85 void ConvertPipelineDepthTargetEx(vk::Pipeline& pipeline, VkRenderPass renderpass,
90 vk::ShaderModule& module, bool single_texture); 86 vk::ShaderModule& module);
91 87
92 const Device& device; 88 const Device& device;
93 VKScheduler& scheduler; 89 VKScheduler& scheduler;
diff --git a/src/video_core/renderer_vulkan/vk_blit_screen.cpp b/src/video_core/renderer_vulkan/vk_blit_screen.cpp
index 1e447e621..c71a1f44d 100644
--- a/src/video_core/renderer_vulkan/vk_blit_screen.cpp
+++ b/src/video_core/renderer_vulkan/vk_blit_screen.cpp
@@ -391,28 +391,23 @@ VkSemaphore VKBlitScreen::Draw(const Tegra::FramebufferConfig& framebuffer,
391 .offset = {0, 0}, 391 .offset = {0, 0},
392 .extent = size, 392 .extent = size,
393 }; 393 };
394 const auto filter = Settings::values.scaling_filter.GetValue();
395 cmdbuf.BeginRenderPass(renderpass_bi, VK_SUBPASS_CONTENTS_INLINE); 394 cmdbuf.BeginRenderPass(renderpass_bi, VK_SUBPASS_CONTENTS_INLINE);
396 switch (filter) { 395 auto graphics_pipeline = [this]() {
397 case Settings::ScalingFilter::NearestNeighbor: 396 switch (Settings::values.scaling_filter.GetValue()) {
398 cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, *bilinear_pipeline); 397 case Settings::ScalingFilter::NearestNeighbor:
399 break; 398 case Settings::ScalingFilter::Bilinear:
400 case Settings::ScalingFilter::Bilinear: 399 return *bilinear_pipeline;
401 cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, *bilinear_pipeline); 400 case Settings::ScalingFilter::Bicubic:
402 break; 401 return *bicubic_pipeline;
403 case Settings::ScalingFilter::Bicubic: 402 case Settings::ScalingFilter::Gaussian:
404 cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, *bicubic_pipeline); 403 return *gaussian_pipeline;
405 break; 404 case Settings::ScalingFilter::ScaleForce:
406 case Settings::ScalingFilter::Gaussian: 405 return *scaleforce_pipeline;
407 cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, *gaussian_pipeline); 406 default:
408 break; 407 return *bilinear_pipeline;
409 case Settings::ScalingFilter::ScaleForce: 408 }
410 cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, *scaleforce_pipeline); 409 }();
411 break; 410 cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, graphics_pipeline);
412 default:
413 cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, *bilinear_pipeline);
414 break;
415 }
416 cmdbuf.SetViewport(0, viewport); 411 cmdbuf.SetViewport(0, viewport);
417 cmdbuf.SetScissor(0, scissor); 412 cmdbuf.SetScissor(0, scissor);
418 413
diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.cpp b/src/video_core/renderer_vulkan/vk_texture_cache.cpp
index 197cba8e3..1941170cb 100644
--- a/src/video_core/renderer_vulkan/vk_texture_cache.cpp
+++ b/src/video_core/renderer_vulkan/vk_texture_cache.cpp
@@ -1057,37 +1057,37 @@ void TextureCacheRuntime::BlitImage(Framebuffer* dst_framebuffer, ImageView& dst
1057 }); 1057 });
1058} 1058}
1059 1059
1060void TextureCacheRuntime::ConvertImage(Framebuffer* dst, ImageView& dst_view, ImageView& src_view, 1060void TextureCacheRuntime::ConvertImage(Framebuffer* dst, ImageView& dst_view, ImageView& src_view) {
1061 bool rescaled) {
1062 const u32 up_scale = rescaled ? resolution.up_scale : 1;
1063 const u32 down_shift = rescaled ? resolution.down_shift : 0;
1064 switch (dst_view.format) { 1061 switch (dst_view.format) {
1065 case PixelFormat::R16_UNORM: 1062 case PixelFormat::R16_UNORM:
1066 if (src_view.format == PixelFormat::D16_UNORM) { 1063 if (src_view.format == PixelFormat::D16_UNORM) {
1067 return blit_image_helper.ConvertD16ToR16(dst, src_view, up_scale, down_shift); 1064 return blit_image_helper.ConvertD16ToR16(dst, src_view);
1068 } 1065 }
1069 break; 1066 break;
1070 case PixelFormat::A8B8G8R8_UNORM: 1067 case PixelFormat::A8B8G8R8_UNORM:
1071 if (src_view.format == PixelFormat::S8_UINT_D24_UNORM) { 1068 if (src_view.format == PixelFormat::S8_UINT_D24_UNORM) {
1072 return blit_image_helper.ConvertD24S8ToABGR8(dst, src_view, up_scale, down_shift); 1069 return blit_image_helper.ConvertD24S8ToABGR8(dst, src_view);
1073 } 1070 }
1074 break; 1071 break;
1075 case PixelFormat::R32_FLOAT: 1072 case PixelFormat::R32_FLOAT:
1076 if (src_view.format == PixelFormat::D32_FLOAT) { 1073 if (src_view.format == PixelFormat::D32_FLOAT) {
1077 return blit_image_helper.ConvertD32ToR32(dst, src_view, up_scale, down_shift); 1074 return blit_image_helper.ConvertD32ToR32(dst, src_view);
1078 } 1075 }
1079 break; 1076 break;
1080 case PixelFormat::D16_UNORM: 1077 case PixelFormat::D16_UNORM:
1081 if (src_view.format == PixelFormat::R16_UNORM) { 1078 if (src_view.format == PixelFormat::R16_UNORM) {
1082 return blit_image_helper.ConvertR16ToD16(dst, src_view, up_scale, down_shift); 1079 return blit_image_helper.ConvertR16ToD16(dst, src_view);
1083 } 1080 }
1084 break; 1081 break;
1085 case PixelFormat::S8_UINT_D24_UNORM: 1082 case PixelFormat::S8_UINT_D24_UNORM:
1086 return blit_image_helper.ConvertABGR8ToD24S8(dst, src_view, up_scale, down_shift); 1083 if (src_view.format == PixelFormat::A8B8G8R8_UNORM ||
1084 src_view.format == PixelFormat::B8G8R8A8_UNORM) {
1085 return blit_image_helper.ConvertABGR8ToD24S8(dst, src_view);
1086 }
1087 break; 1087 break;
1088 case PixelFormat::D32_FLOAT: 1088 case PixelFormat::D32_FLOAT:
1089 if (src_view.format == PixelFormat::R32_FLOAT) { 1089 if (src_view.format == PixelFormat::R32_FLOAT) {
1090 return blit_image_helper.ConvertR32ToD32(dst, src_view, up_scale, down_shift); 1090 return blit_image_helper.ConvertR32ToD32(dst, src_view);
1091 } 1091 }
1092 break; 1092 break;
1093 default: 1093 default:
@@ -1329,6 +1329,10 @@ void Image::DownloadMemory(const StagingBufferRef& map, std::span<const BufferIm
1329 } 1329 }
1330} 1330}
1331 1331
1332bool Image::IsRescaled() const noexcept {
1333 return True(flags & ImageFlagBits::Rescaled);
1334}
1335
1332bool Image::ScaleUp(bool ignore) { 1336bool Image::ScaleUp(bool ignore) {
1333 if (True(flags & ImageFlagBits::Rescaled)) { 1337 if (True(flags & ImageFlagBits::Rescaled)) {
1334 return false; 1338 return false;
@@ -1469,7 +1473,8 @@ bool Image::BlitScaleHelper(bool scale_up) {
1469ImageView::ImageView(TextureCacheRuntime& runtime, const VideoCommon::ImageViewInfo& info, 1473ImageView::ImageView(TextureCacheRuntime& runtime, const VideoCommon::ImageViewInfo& info,
1470 ImageId image_id_, Image& image) 1474 ImageId image_id_, Image& image)
1471 : VideoCommon::ImageViewBase{info, image.info, image_id_}, device{&runtime.device}, 1475 : VideoCommon::ImageViewBase{info, image.info, image_id_}, device{&runtime.device},
1472 image_handle{image.Handle()}, samples{ConvertSampleCount(image.info.num_samples)} { 1476 src_image{&image}, image_handle{image.Handle()},
1477 samples(ConvertSampleCount(image.info.num_samples)) {
1473 using Shader::TextureType; 1478 using Shader::TextureType;
1474 1479
1475 const VkImageAspectFlags aspect_mask = ImageViewAspectMask(info); 1480 const VkImageAspectFlags aspect_mask = ImageViewAspectMask(info);
@@ -1607,6 +1612,13 @@ VkImageView ImageView::StorageView(Shader::TextureType texture_type,
1607 return *view; 1612 return *view;
1608} 1613}
1609 1614
1615bool ImageView::IsRescaled() const noexcept {
1616 if (!src_image) {
1617 return false;
1618 }
1619 return src_image->IsRescaled();
1620}
1621
1610vk::ImageView ImageView::MakeView(VkFormat vk_format, VkImageAspectFlags aspect_mask) { 1622vk::ImageView ImageView::MakeView(VkFormat vk_format, VkImageAspectFlags aspect_mask) {
1611 return device->GetLogical().CreateImageView({ 1623 return device->GetLogical().CreateImageView({
1612 .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, 1624 .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.h b/src/video_core/renderer_vulkan/vk_texture_cache.h
index 753e3e8a1..c592f2666 100644
--- a/src/video_core/renderer_vulkan/vk_texture_cache.h
+++ b/src/video_core/renderer_vulkan/vk_texture_cache.h
@@ -65,7 +65,7 @@ public:
65 65
66 void ReinterpretImage(Image& dst, Image& src, std::span<const VideoCommon::ImageCopy> copies); 66 void ReinterpretImage(Image& dst, Image& src, std::span<const VideoCommon::ImageCopy> copies);
67 67
68 void ConvertImage(Framebuffer* dst, ImageView& dst_view, ImageView& src_view, bool rescaled); 68 void ConvertImage(Framebuffer* dst, ImageView& dst_view, ImageView& src_view);
69 69
70 bool CanAccelerateImageUpload(Image&) const noexcept { 70 bool CanAccelerateImageUpload(Image&) const noexcept {
71 return false; 71 return false;
@@ -139,6 +139,8 @@ public:
139 return std::exchange(initialized, true); 139 return std::exchange(initialized, true);
140 } 140 }
141 141
142 bool IsRescaled() const noexcept;
143
142 bool ScaleUp(bool ignore = false); 144 bool ScaleUp(bool ignore = false);
143 145
144 bool ScaleDown(bool ignore = false); 146 bool ScaleDown(bool ignore = false);
@@ -189,6 +191,8 @@ public:
189 [[nodiscard]] VkImageView StorageView(Shader::TextureType texture_type, 191 [[nodiscard]] VkImageView StorageView(Shader::TextureType texture_type,
190 Shader::ImageFormat image_format); 192 Shader::ImageFormat image_format);
191 193
194 [[nodiscard]] bool IsRescaled() const noexcept;
195
192 [[nodiscard]] VkImageView Handle(Shader::TextureType texture_type) const noexcept { 196 [[nodiscard]] VkImageView Handle(Shader::TextureType texture_type) const noexcept {
193 return *image_views[static_cast<size_t>(texture_type)]; 197 return *image_views[static_cast<size_t>(texture_type)];
194 } 198 }
@@ -222,6 +226,8 @@ private:
222 [[nodiscard]] vk::ImageView MakeView(VkFormat vk_format, VkImageAspectFlags aspect_mask); 226 [[nodiscard]] vk::ImageView MakeView(VkFormat vk_format, VkImageAspectFlags aspect_mask);
223 227
224 const Device* device = nullptr; 228 const Device* device = nullptr;
229 const Image* src_image{};
230
225 std::array<vk::ImageView, Shader::NUM_TEXTURE_TYPES> image_views; 231 std::array<vk::ImageView, Shader::NUM_TEXTURE_TYPES> image_views;
226 std::unique_ptr<StorageViews> storage_views; 232 std::unique_ptr<StorageViews> storage_views;
227 vk::ImageView depth_view; 233 vk::ImageView depth_view;
diff --git a/src/video_core/texture_cache/texture_cache.h b/src/video_core/texture_cache/texture_cache.h
index 5aaeb16ca..2e19fced2 100644
--- a/src/video_core/texture_cache/texture_cache.h
+++ b/src/video_core/texture_cache/texture_cache.h
@@ -1855,9 +1855,20 @@ void TextureCache<P>::CopyImage(ImageId dst_id, ImageId src_id, std::vector<Imag
1855 .height = std::min(dst_view.size.height, src_view.size.height), 1855 .height = std::min(dst_view.size.height, src_view.size.height),
1856 .depth = std::min(dst_view.size.depth, src_view.size.depth), 1856 .depth = std::min(dst_view.size.depth, src_view.size.depth),
1857 }; 1857 };
1858 UNIMPLEMENTED_IF(copy.extent != expected_size); 1858 const Extent3D scaled_extent = [is_rescaled, expected_size]() {
1859 if (!is_rescaled) {
1860 return expected_size;
1861 }
1862 const auto& resolution = Settings::values.resolution_info;
1863 return Extent3D{
1864 .width = resolution.ScaleUp(expected_size.width),
1865 .height = resolution.ScaleUp(expected_size.height),
1866 .depth = expected_size.depth,
1867 };
1868 }();
1869 UNIMPLEMENTED_IF(copy.extent != scaled_extent);
1859 1870
1860 runtime.ConvertImage(dst_framebuffer, dst_view, src_view, is_rescaled); 1871 runtime.ConvertImage(dst_framebuffer, dst_view, src_view);
1861 } 1872 }
1862} 1873}
1863 1874
diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp
index 463d500c2..0f679c37e 100644
--- a/src/yuzu/configuration/config.cpp
+++ b/src/yuzu/configuration/config.cpp
@@ -776,6 +776,7 @@ void Config::ReadUIGamelistValues() {
776 ReadBasicSetting(UISettings::values.row_1_text_id); 776 ReadBasicSetting(UISettings::values.row_1_text_id);
777 ReadBasicSetting(UISettings::values.row_2_text_id); 777 ReadBasicSetting(UISettings::values.row_2_text_id);
778 ReadBasicSetting(UISettings::values.cache_game_list); 778 ReadBasicSetting(UISettings::values.cache_game_list);
779 ReadBasicSetting(UISettings::values.favorites_expanded);
779 const int favorites_size = qt_config->beginReadArray(QStringLiteral("favorites")); 780 const int favorites_size = qt_config->beginReadArray(QStringLiteral("favorites"));
780 for (int i = 0; i < favorites_size; i++) { 781 for (int i = 0; i < favorites_size; i++) {
781 qt_config->setArrayIndex(i); 782 qt_config->setArrayIndex(i);
@@ -1300,6 +1301,7 @@ void Config::SaveUIGamelistValues() {
1300 WriteBasicSetting(UISettings::values.row_1_text_id); 1301 WriteBasicSetting(UISettings::values.row_1_text_id);
1301 WriteBasicSetting(UISettings::values.row_2_text_id); 1302 WriteBasicSetting(UISettings::values.row_2_text_id);
1302 WriteBasicSetting(UISettings::values.cache_game_list); 1303 WriteBasicSetting(UISettings::values.cache_game_list);
1304 WriteBasicSetting(UISettings::values.favorites_expanded);
1303 qt_config->beginWriteArray(QStringLiteral("favorites")); 1305 qt_config->beginWriteArray(QStringLiteral("favorites"));
1304 for (int i = 0; i < UISettings::values.favorited_ids.size(); i++) { 1306 for (int i = 0; i < UISettings::values.favorited_ids.size(); i++) {
1305 qt_config->setArrayIndex(i); 1307 qt_config->setArrayIndex(i);
diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp
index 1a5e41588..8b5c4a10a 100644
--- a/src/yuzu/game_list.cpp
+++ b/src/yuzu/game_list.cpp
@@ -173,13 +173,17 @@ void GameList::OnItemExpanded(const QModelIndex& item) {
173 const bool is_dir = type == GameListItemType::CustomDir || type == GameListItemType::SdmcDir || 173 const bool is_dir = type == GameListItemType::CustomDir || type == GameListItemType::SdmcDir ||
174 type == GameListItemType::UserNandDir || 174 type == GameListItemType::UserNandDir ||
175 type == GameListItemType::SysNandDir; 175 type == GameListItemType::SysNandDir;
176 176 const bool is_fave = type == GameListItemType::Favorites;
177 if (!is_dir) { 177 if (!is_dir && !is_fave) {
178 return; 178 return;
179 } 179 }
180 180 const bool is_expanded = tree_view->isExpanded(item);
181 UISettings::values.game_dirs[item.data(GameListDir::GameDirRole).toInt()].expanded = 181 if (is_fave) {
182 tree_view->isExpanded(item); 182 UISettings::values.favorites_expanded = is_expanded;
183 return;
184 }
185 const int item_dir_index = item.data(GameListDir::GameDirRole).toInt();
186 UISettings::values.game_dirs[item_dir_index].expanded = is_expanded;
183} 187}
184 188
185// Event in order to filter the gamelist after editing the searchfield 189// Event in order to filter the gamelist after editing the searchfield
@@ -458,10 +462,13 @@ void GameList::DonePopulating(const QStringList& watch_list) {
458 emit ShowList(!IsEmpty()); 462 emit ShowList(!IsEmpty());
459 463
460 item_model->invisibleRootItem()->appendRow(new GameListAddDir()); 464 item_model->invisibleRootItem()->appendRow(new GameListAddDir());
465
466 // Add favorites row
461 item_model->invisibleRootItem()->insertRow(0, new GameListFavorites()); 467 item_model->invisibleRootItem()->insertRow(0, new GameListFavorites());
462 tree_view->setRowHidden(0, item_model->invisibleRootItem()->index(), 468 tree_view->setRowHidden(0, item_model->invisibleRootItem()->index(),
463 UISettings::values.favorited_ids.size() == 0); 469 UISettings::values.favorited_ids.size() == 0);
464 tree_view->expand(item_model->invisibleRootItem()->child(0)->index()); 470 tree_view->setExpanded(item_model->invisibleRootItem()->child(0)->index(),
471 UISettings::values.favorites_expanded.GetValue());
465 for (const auto id : UISettings::values.favorited_ids) { 472 for (const auto id : UISettings::values.favorited_ids) {
466 AddFavorite(id); 473 AddFavorite(id);
467 } 474 }
diff --git a/src/yuzu/uisettings.h b/src/yuzu/uisettings.h
index 936914ef3..a610e7e25 100644
--- a/src/yuzu/uisettings.h
+++ b/src/yuzu/uisettings.h
@@ -74,7 +74,6 @@ struct Values {
74 QString game_dir_deprecated; 74 QString game_dir_deprecated;
75 bool game_dir_deprecated_deepscan; 75 bool game_dir_deprecated_deepscan;
76 QVector<UISettings::GameDir> game_dirs; 76 QVector<UISettings::GameDir> game_dirs;
77 QVector<u64> favorited_ids;
78 QStringList recent_files; 77 QStringList recent_files;
79 QString language; 78 QString language;
80 79
@@ -96,6 +95,8 @@ struct Values {
96 Settings::BasicSetting<uint8_t> row_2_text_id{2, "row_2_text_id"}; 95 Settings::BasicSetting<uint8_t> row_2_text_id{2, "row_2_text_id"};
97 std::atomic_bool is_game_list_reload_pending{false}; 96 std::atomic_bool is_game_list_reload_pending{false};
98 Settings::BasicSetting<bool> cache_game_list{true, "cache_game_list"}; 97 Settings::BasicSetting<bool> cache_game_list{true, "cache_game_list"};
98 Settings::BasicSetting<bool> favorites_expanded{true, "favorites_expanded"};
99 QVector<u64> favorited_ids;
99 100
100 bool configuration_applied; 101 bool configuration_applied;
101 bool reset_to_defaults; 102 bool reset_to_defaults;