summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-x.ci/scripts/linux/docker.sh3
-rw-r--r--CMakeLists.txt43
-rw-r--r--externals/libusb/CMakeLists.txt2
-rw-r--r--src/common/assert.h8
-rw-r--r--src/common/hex_util.h2
-rw-r--r--src/common/uuid.cpp54
-rw-r--r--src/common/uuid.h19
-rw-r--r--src/core/hle/service/am/applets/applet_software_keyboard.cpp6
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_nvdec_common.cpp58
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_nvdec_common.h45
-rw-r--r--src/core/hle/service/set/set.cpp3
-rw-r--r--src/core/network/network.cpp2
-rw-r--r--src/video_core/CMakeLists.txt5
-rw-r--r--src/video_core/command_classes/codecs/codec.cpp144
-rw-r--r--src/video_core/command_classes/codecs/codec.h4
-rw-r--r--src/video_core/command_classes/codecs/vp9.cpp134
-rw-r--r--src/video_core/command_classes/codecs/vp9.h14
-rw-r--r--src/video_core/command_classes/codecs/vp9_types.h6
-rw-r--r--src/video_core/command_classes/vic.cpp87
-rw-r--r--src/video_core/command_classes/vic.h7
-rw-r--r--src/video_core/renderer_base.cpp2
-rw-r--r--src/video_core/renderer_base.h4
-rw-r--r--src/video_core/renderer_opengl/renderer_opengl.cpp2
-rw-r--r--src/video_core/renderer_vulkan/renderer_vulkan.cpp152
-rw-r--r--src/video_core/renderer_vulkan/renderer_vulkan.h2
-rw-r--r--src/video_core/renderer_vulkan/vk_blit_screen.cpp143
-rw-r--r--src/video_core/renderer_vulkan/vk_blit_screen.h13
-rw-r--r--src/video_core/renderer_vulkan/vk_rasterizer.cpp9
-rw-r--r--src/video_core/textures/decoders.cpp52
-rw-r--r--src/yuzu/bootmanager.cpp4
-rw-r--r--src/yuzu/configuration/config.cpp17
-rw-r--r--src/yuzu/configuration/configure_ui.cpp50
-rw-r--r--src/yuzu/configuration/configure_ui.ui22
-rw-r--r--src/yuzu/game_list.cpp4
-rw-r--r--src/yuzu/game_list_p.h13
-rw-r--r--src/yuzu/uisettings.h3
-rw-r--r--src/yuzu_cmd/config.cpp3
37 files changed, 732 insertions, 409 deletions
diff --git a/.ci/scripts/linux/docker.sh b/.ci/scripts/linux/docker.sh
index 9b451d3ab..5070b92d1 100755
--- a/.ci/scripts/linux/docker.sh
+++ b/.ci/scripts/linux/docker.sh
@@ -18,7 +18,8 @@ cmake .. \
18 -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON \ 18 -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON \
19 -DENABLE_QT_TRANSLATION=ON \ 19 -DENABLE_QT_TRANSLATION=ON \
20 -DUSE_DISCORD_PRESENCE=ON \ 20 -DUSE_DISCORD_PRESENCE=ON \
21 -DYUZU_ENABLE_COMPATIBILITY_REPORTING=${ENABLE_COMPATIBILITY_REPORTING:-"OFF"} 21 -DYUZU_ENABLE_COMPATIBILITY_REPORTING=${ENABLE_COMPATIBILITY_REPORTING:-"OFF"} \
22 -DYUZU_USE_BUNDLED_FFMPEG=ON
22 23
23make -j$(nproc) 24make -j$(nproc)
24 25
diff --git a/CMakeLists.txt b/CMakeLists.txt
index d98ba7767..de2413843 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -25,7 +25,7 @@ option(YUZU_USE_BUNDLED_BOOST "Download bundled Boost" OFF)
25 25
26option(YUZU_USE_BUNDLED_LIBUSB "Compile bundled libusb" OFF) 26option(YUZU_USE_BUNDLED_LIBUSB "Compile bundled libusb" OFF)
27 27
28CMAKE_DEPENDENT_OPTION(YUZU_USE_BUNDLED_FFMPEG "Download/Build bundled FFmpeg" ON "WIN32" OFF) 28option(YUZU_USE_BUNDLED_FFMPEG "Download/Build bundled FFmpeg" "${WIN32}")
29 29
30option(YUZU_USE_QT_WEB_ENGINE "Use QtWebEngine for web applet implementation" OFF) 30option(YUZU_USE_QT_WEB_ENGINE "Use QtWebEngine for web applet implementation" OFF)
31 31
@@ -583,8 +583,32 @@ if (YUZU_USE_BUNDLED_FFMPEG)
583 "${FFmpeg_PREFIX};${FFmpeg_BUILD_DIR}" 583 "${FFmpeg_PREFIX};${FFmpeg_BUILD_DIR}"
584 CACHE PATH "Path to FFmpeg headers" FORCE) 584 CACHE PATH "Path to FFmpeg headers" FORCE)
585 585
586 if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
587 Include(FindPkgConfig REQUIRED)
588 pkg_check_modules(LIBVA libva)
589 endif()
590 if(LIBVA_FOUND)
591 pkg_check_modules(LIBDRM libdrm REQUIRED)
592 find_package(X11 REQUIRED)
593 pkg_check_modules(LIBVA-DRM libva-drm REQUIRED)
594 pkg_check_modules(LIBVA-X11 libva-x11 REQUIRED)
595 set(FFmpeg_LIBVA_LIBRARIES
596 ${LIBDRM_LIBRARIES}
597 ${X11_LIBRARIES}
598 ${LIBVA-DRM_LIBRARIES}
599 ${LIBVA-X11_LIBRARIES}
600 ${LIBVA_LIBRARIES})
601 set(FFmpeg_HWACCEL_FLAGS
602 --enable-hwaccel=h264_vaapi
603 --enable-hwaccel=vp9_vaapi
604 --enable-libdrm)
605 message(STATUS "VA-API found")
606 else()
607 set(FFmpeg_HWACCEL_FLAGS --disable-vaapi)
608 endif()
609
586 # `configure` parameters builds only exactly what yuzu needs from FFmpeg 610 # `configure` parameters builds only exactly what yuzu needs from FFmpeg
587 # `--disable-{vaapi,vdpau}` is needed to avoid linking issues 611 # `--disable-vdpau` is needed to avoid linking issues
588 add_custom_command( 612 add_custom_command(
589 OUTPUT 613 OUTPUT
590 ${FFmpeg_MAKEFILE} 614 ${FFmpeg_MAKEFILE}
@@ -600,13 +624,16 @@ if (YUZU_USE_BUNDLED_FFMPEG)
600 --disable-network 624 --disable-network
601 --disable-postproc 625 --disable-postproc
602 --disable-swresample 626 --disable-swresample
603 --disable-vaapi
604 --disable-vdpau 627 --disable-vdpau
605 --enable-decoder=h264 628 --enable-decoder=h264
606 --enable-decoder=vp9 629 --enable-decoder=vp9
630 --cc="${CMAKE_C_COMPILER}"
631 --cxx="${CMAKE_CXX_COMPILER}"
632 ${FFmpeg_HWACCEL_FLAGS}
607 WORKING_DIRECTORY 633 WORKING_DIRECTORY
608 ${FFmpeg_BUILD_DIR} 634 ${FFmpeg_BUILD_DIR}
609 ) 635 )
636 unset(FFmpeg_HWACCEL_FLAGS)
610 637
611 # Workaround for Ubuntu 18.04's older version of make not being able to call make as a child 638 # Workaround for Ubuntu 18.04's older version of make not being able to call make as a child
612 # with context of the jobserver. Also helps ninja users. 639 # with context of the jobserver. Also helps ninja users.
@@ -616,9 +643,10 @@ if (YUZU_USE_BUNDLED_FFMPEG)
616 OUTPUT_VARIABLE 643 OUTPUT_VARIABLE
617 SYSTEM_THREADS) 644 SYSTEM_THREADS)
618 645
646 set(FFmpeg_BUILD_LIBRARIES ${FFmpeg_LIBRARIES})
619 add_custom_command( 647 add_custom_command(
620 OUTPUT 648 OUTPUT
621 ${FFmpeg_LIBRARIES} 649 ${FFmpeg_BUILD_LIBRARIES}
622 COMMAND 650 COMMAND
623 make -j${SYSTEM_THREADS} 651 make -j${SYSTEM_THREADS}
624 WORKING_DIRECTORY 652 WORKING_DIRECTORY
@@ -628,7 +656,12 @@ if (YUZU_USE_BUNDLED_FFMPEG)
628 # ALL makes this custom target build every time 656 # ALL makes this custom target build every time
629 # but it won't actually build if the DEPENDS parameter is up to date 657 # but it won't actually build if the DEPENDS parameter is up to date
630 add_custom_target(ffmpeg-configure ALL DEPENDS ${FFmpeg_MAKEFILE}) 658 add_custom_target(ffmpeg-configure ALL DEPENDS ${FFmpeg_MAKEFILE})
631 add_custom_target(ffmpeg-build ALL DEPENDS ${FFmpeg_LIBRARIES} ffmpeg-configure) 659 add_custom_target(ffmpeg-build ALL DEPENDS ${FFmpeg_BUILD_LIBRARIES} ffmpeg-configure)
660 link_libraries(${FFmpeg_LIBVA_LIBRARIES})
661 set(FFmpeg_LIBRARIES ${FFmpeg_LIBVA_LIBRARIES} ${FFmpeg_BUILD_LIBRARIES}
662 CACHE PATH "Paths to FFmpeg libraries" FORCE)
663 unset(FFmpeg_BUILD_LIBRARIES)
664 unset(FFmpeg_LIBVA_LIBRARIES)
632 665
633 if (FFmpeg_FOUND) 666 if (FFmpeg_FOUND)
634 message(STATUS "Found FFmpeg version ${FFmpeg_VERSION}") 667 message(STATUS "Found FFmpeg version ${FFmpeg_VERSION}")
diff --git a/externals/libusb/CMakeLists.txt b/externals/libusb/CMakeLists.txt
index 151ddc462..12bdc097a 100644
--- a/externals/libusb/CMakeLists.txt
+++ b/externals/libusb/CMakeLists.txt
@@ -67,6 +67,8 @@ if (MINGW OR (${CMAKE_SYSTEM_NAME} MATCHES "Linux") OR APPLE)
67 "${LIBUSB_MAKEFILE}" 67 "${LIBUSB_MAKEFILE}"
68 COMMAND 68 COMMAND
69 env 69 env
70 CC="${CMAKE_C_COMPILER}"
71 CXX="${CMAKE_CXX_COMPILER}"
70 CFLAGS="${LIBUSB_CFLAGS}" 72 CFLAGS="${LIBUSB_CFLAGS}"
71 sh "${LIBUSB_CONFIGURE}" 73 sh "${LIBUSB_CONFIGURE}"
72 ${LIBUSB_CONFIGURE_ARGS} 74 ${LIBUSB_CONFIGURE_ARGS}
diff --git a/src/common/assert.h b/src/common/assert.h
index b3ba35c0f..33060d865 100644
--- a/src/common/assert.h
+++ b/src/common/assert.h
@@ -52,8 +52,12 @@ assert_noinline_call(const Fn& fn) {
52#define DEBUG_ASSERT(_a_) ASSERT(_a_) 52#define DEBUG_ASSERT(_a_) ASSERT(_a_)
53#define DEBUG_ASSERT_MSG(_a_, ...) ASSERT_MSG(_a_, __VA_ARGS__) 53#define DEBUG_ASSERT_MSG(_a_, ...) ASSERT_MSG(_a_, __VA_ARGS__)
54#else // not debug 54#else // not debug
55#define DEBUG_ASSERT(_a_) 55#define DEBUG_ASSERT(_a_) \
56#define DEBUG_ASSERT_MSG(_a_, _desc_, ...) 56 do { \
57 } while (0)
58#define DEBUG_ASSERT_MSG(_a_, _desc_, ...) \
59 do { \
60 } while (0)
57#endif 61#endif
58 62
59#define UNIMPLEMENTED() ASSERT_MSG(false, "Unimplemented code!") 63#define UNIMPLEMENTED() ASSERT_MSG(false, "Unimplemented code!")
diff --git a/src/common/hex_util.h b/src/common/hex_util.h
index f5f9e4507..5e9b6ef8b 100644
--- a/src/common/hex_util.h
+++ b/src/common/hex_util.h
@@ -61,7 +61,7 @@ template <typename ContiguousContainer>
61 return out; 61 return out;
62} 62}
63 63
64[[nodiscard]] constexpr std::array<u8, 16> AsArray(const char (&data)[17]) { 64[[nodiscard]] constexpr std::array<u8, 16> AsArray(const char (&data)[33]) {
65 return HexStringToArray<16>(data); 65 return HexStringToArray<16>(data);
66} 66}
67 67
diff --git a/src/common/uuid.cpp b/src/common/uuid.cpp
index 18303a1e3..d7435a6e9 100644
--- a/src/common/uuid.cpp
+++ b/src/common/uuid.cpp
@@ -6,10 +6,64 @@
6 6
7#include <fmt/format.h> 7#include <fmt/format.h>
8 8
9#include "common/assert.h"
9#include "common/uuid.h" 10#include "common/uuid.h"
10 11
11namespace Common { 12namespace Common {
12 13
14namespace {
15
16bool IsHexDigit(char c) {
17 return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F');
18}
19
20u8 HexCharToByte(char c) {
21 if (c >= '0' && c <= '9') {
22 return static_cast<u8>(c - '0');
23 }
24 if (c >= 'a' && c <= 'f') {
25 return static_cast<u8>(c - 'a' + 10);
26 }
27 if (c >= 'A' && c <= 'F') {
28 return static_cast<u8>(c - 'A' + 10);
29 }
30 ASSERT_MSG(false, "{} is not a hexadecimal digit!", c);
31 return u8{0};
32}
33
34} // Anonymous namespace
35
36u128 HexStringToU128(std::string_view hex_string) {
37 const size_t length = hex_string.length();
38
39 // Detect "0x" prefix.
40 const bool has_0x_prefix = length > 2 && hex_string[0] == '0' && hex_string[1] == 'x';
41 const size_t offset = has_0x_prefix ? 2 : 0;
42
43 // Check length.
44 if (length > 32 + offset) {
45 ASSERT_MSG(false, "hex_string has more than 32 hexadecimal characters!");
46 return INVALID_UUID;
47 }
48
49 u64 lo = 0;
50 u64 hi = 0;
51 for (size_t i = 0; i < length - offset; ++i) {
52 const char c = hex_string[length - 1 - i];
53 if (!IsHexDigit(c)) {
54 ASSERT_MSG(false, "{} is not a hexadecimal digit!", c);
55 return INVALID_UUID;
56 }
57 if (i < 16) {
58 lo |= u64{HexCharToByte(c)} << (i * 4);
59 }
60 if (i >= 16) {
61 hi |= u64{HexCharToByte(c)} << ((i - 16) * 4);
62 }
63 }
64 return u128{lo, hi};
65}
66
13UUID UUID::Generate() { 67UUID UUID::Generate() {
14 std::random_device device; 68 std::random_device device;
15 std::mt19937 gen(device()); 69 std::mt19937 gen(device());
diff --git a/src/common/uuid.h b/src/common/uuid.h
index 0ffa37e7c..aeb36939a 100644
--- a/src/common/uuid.h
+++ b/src/common/uuid.h
@@ -5,6 +5,7 @@
5#pragma once 5#pragma once
6 6
7#include <string> 7#include <string>
8#include <string_view>
8 9
9#include "common/common_types.h" 10#include "common/common_types.h"
10 11
@@ -12,12 +13,30 @@ namespace Common {
12 13
13constexpr u128 INVALID_UUID{{0, 0}}; 14constexpr u128 INVALID_UUID{{0, 0}};
14 15
16/**
17 * Converts a hex string to a 128-bit unsigned integer.
18 *
19 * The hex string can be formatted in lowercase or uppercase, with or without the "0x" prefix.
20 *
21 * This function will assert and return INVALID_UUID under the following conditions:
22 * - If the hex string is more than 32 characters long
23 * - If the hex string contains non-hexadecimal characters
24 *
25 * @param hex_string Hexadecimal string
26 *
27 * @returns A 128-bit unsigned integer if successfully converted, INVALID_UUID otherwise.
28 */
29[[nodiscard]] u128 HexStringToU128(std::string_view hex_string);
30
15struct UUID { 31struct UUID {
16 // UUIDs which are 0 are considered invalid! 32 // UUIDs which are 0 are considered invalid!
17 u128 uuid; 33 u128 uuid;
18 UUID() = default; 34 UUID() = default;
19 constexpr explicit UUID(const u128& id) : uuid{id} {} 35 constexpr explicit UUID(const u128& id) : uuid{id} {}
20 constexpr explicit UUID(const u64 lo, const u64 hi) : uuid{{lo, hi}} {} 36 constexpr explicit UUID(const u64 lo, const u64 hi) : uuid{{lo, hi}} {}
37 explicit UUID(std::string_view hex_string) {
38 uuid = HexStringToU128(hex_string);
39 }
21 40
22 [[nodiscard]] constexpr explicit operator bool() const { 41 [[nodiscard]] constexpr explicit operator bool() const {
23 return uuid != INVALID_UUID; 42 return uuid != INVALID_UUID;
diff --git a/src/core/hle/service/am/applets/applet_software_keyboard.cpp b/src/core/hle/service/am/applets/applet_software_keyboard.cpp
index 673abb755..c89aa1bbf 100644
--- a/src/core/hle/service/am/applets/applet_software_keyboard.cpp
+++ b/src/core/hle/service/am/applets/applet_software_keyboard.cpp
@@ -377,7 +377,8 @@ void SoftwareKeyboard::SubmitForTextCheck(std::u16string submitted_text) {
377 377
378 if (swkbd_config_common.use_utf8) { 378 if (swkbd_config_common.use_utf8) {
379 std::string utf8_submitted_text = Common::UTF16ToUTF8(current_text); 379 std::string utf8_submitted_text = Common::UTF16ToUTF8(current_text);
380 const u64 buffer_size = utf8_submitted_text.size(); 380 // Include the null terminator in the buffer size.
381 const u64 buffer_size = utf8_submitted_text.size() + 1;
381 382
382 LOG_DEBUG(Service_AM, "\nBuffer Size: {}\nUTF-8 Submitted Text: {}", buffer_size, 383 LOG_DEBUG(Service_AM, "\nBuffer Size: {}\nUTF-8 Submitted Text: {}", buffer_size,
383 utf8_submitted_text); 384 utf8_submitted_text);
@@ -386,7 +387,8 @@ void SoftwareKeyboard::SubmitForTextCheck(std::u16string submitted_text) {
386 std::memcpy(out_data.data() + sizeof(u64), utf8_submitted_text.data(), 387 std::memcpy(out_data.data() + sizeof(u64), utf8_submitted_text.data(),
387 utf8_submitted_text.size()); 388 utf8_submitted_text.size());
388 } else { 389 } else {
389 const u64 buffer_size = current_text.size() * sizeof(char16_t); 390 // Include the null terminator in the buffer size.
391 const u64 buffer_size = (current_text.size() + 1) * sizeof(char16_t);
390 392
391 LOG_DEBUG(Service_AM, "\nBuffer Size: {}\nUTF-16 Submitted Text: {}", buffer_size, 393 LOG_DEBUG(Service_AM, "\nBuffer Size: {}\nUTF-16 Submitted Text: {}", buffer_size,
392 Common::UTF16ToUTF8(current_text)); 394 Common::UTF16ToUTF8(current_text));
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_nvdec_common.cpp b/src/core/hle/service/nvdrv/devices/nvhost_nvdec_common.cpp
index 1403a39d0..845de724d 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_nvdec_common.cpp
+++ b/src/core/hle/service/nvdrv/devices/nvhost_nvdec_common.cpp
@@ -166,8 +166,6 @@ NvResult nvhost_nvdec_common::MapBuffer(const std::vector<u8>& input, std::vecto
166 LOG_ERROR(Service_NVDRV, "failed to map size={}", object->size); 166 LOG_ERROR(Service_NVDRV, "failed to map size={}", object->size);
167 } else { 167 } else {
168 cmd_buffer.map_address = object->dma_map_addr; 168 cmd_buffer.map_address = object->dma_map_addr;
169 AddBufferMap(object->dma_map_addr, object->size, object->addr,
170 object->status == nvmap::Object::Status::Allocated);
171 } 169 }
172 } 170 }
173 std::memcpy(output.data(), &params, sizeof(IoctlMapBuffer)); 171 std::memcpy(output.data(), &params, sizeof(IoctlMapBuffer));
@@ -178,30 +176,11 @@ NvResult nvhost_nvdec_common::MapBuffer(const std::vector<u8>& input, std::vecto
178} 176}
179 177
180NvResult nvhost_nvdec_common::UnmapBuffer(const std::vector<u8>& input, std::vector<u8>& output) { 178NvResult nvhost_nvdec_common::UnmapBuffer(const std::vector<u8>& input, std::vector<u8>& output) {
181 IoctlMapBuffer params{}; 179 // This is intntionally stubbed.
182 std::memcpy(&params, input.data(), sizeof(IoctlMapBuffer)); 180 // Skip unmapping buffers here, as to not break the continuity of the VP9 reference frame
183 std::vector<MapBufferEntry> cmd_buffer_handles(params.num_entries); 181 // addresses, and risk invalidating data before the async GPU thread is done with it
184 SliceVectors(input, cmd_buffer_handles, params.num_entries, sizeof(IoctlMapBuffer));
185
186 auto& gpu = system.GPU();
187
188 for (auto& cmd_buffer : cmd_buffer_handles) {
189 const auto object{nvmap_dev->GetObject(cmd_buffer.map_handle)};
190 if (!object) {
191 LOG_ERROR(Service_NVDRV, "invalid cmd_buffer nvmap_handle={:X}", cmd_buffer.map_handle);
192 std::memcpy(output.data(), &params, output.size());
193 return NvResult::InvalidState;
194 }
195 if (const auto size{RemoveBufferMap(object->dma_map_addr)}; size) {
196 gpu.MemoryManager().Unmap(object->dma_map_addr, *size);
197 } else {
198 // This occurs quite frequently, however does not seem to impact functionality
199 LOG_DEBUG(Service_NVDRV, "invalid offset=0x{:X} dma=0x{:X}", object->addr,
200 object->dma_map_addr);
201 }
202 object->dma_map_addr = 0;
203 }
204 std::memset(output.data(), 0, output.size()); 182 std::memset(output.data(), 0, output.size());
183 LOG_DEBUG(Service_NVDRV, "(STUBBED) called");
205 return NvResult::Success; 184 return NvResult::Success;
206} 185}
207 186
@@ -212,33 +191,4 @@ NvResult nvhost_nvdec_common::SetSubmitTimeout(const std::vector<u8>& input,
212 return NvResult::Success; 191 return NvResult::Success;
213} 192}
214 193
215std::optional<nvhost_nvdec_common::BufferMap> nvhost_nvdec_common::FindBufferMap(
216 GPUVAddr gpu_addr) const {
217 const auto it = std::find_if(
218 buffer_mappings.begin(), buffer_mappings.upper_bound(gpu_addr), [&](const auto& entry) {
219 return (gpu_addr >= entry.second.StartAddr() && gpu_addr < entry.second.EndAddr());
220 });
221
222 ASSERT(it != buffer_mappings.end());
223 return it->second;
224}
225
226void nvhost_nvdec_common::AddBufferMap(GPUVAddr gpu_addr, std::size_t size, VAddr cpu_addr,
227 bool is_allocated) {
228 buffer_mappings.insert_or_assign(gpu_addr, BufferMap{gpu_addr, size, cpu_addr, is_allocated});
229}
230
231std::optional<std::size_t> nvhost_nvdec_common::RemoveBufferMap(GPUVAddr gpu_addr) {
232 const auto iter{buffer_mappings.find(gpu_addr)};
233 if (iter == buffer_mappings.end()) {
234 return std::nullopt;
235 }
236 std::size_t size = 0;
237 if (iter->second.IsAllocated()) {
238 size = iter->second.Size();
239 }
240 buffer_mappings.erase(iter);
241 return size;
242}
243
244} // namespace Service::Nvidia::Devices 194} // namespace Service::Nvidia::Devices
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_nvdec_common.h b/src/core/hle/service/nvdrv/devices/nvhost_nvdec_common.h
index da10f5f41..af59f00d2 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_nvdec_common.h
+++ b/src/core/hle/service/nvdrv/devices/nvhost_nvdec_common.h
@@ -23,45 +23,6 @@ public:
23 ~nvhost_nvdec_common() override; 23 ~nvhost_nvdec_common() override;
24 24
25protected: 25protected:
26 class BufferMap final {
27 public:
28 constexpr BufferMap() = default;
29
30 constexpr BufferMap(GPUVAddr start_addr_, std::size_t size_)
31 : start_addr{start_addr_}, end_addr{start_addr_ + size_} {}
32
33 constexpr BufferMap(GPUVAddr start_addr_, std::size_t size_, VAddr cpu_addr_,
34 bool is_allocated_)
35 : start_addr{start_addr_}, end_addr{start_addr_ + size_}, cpu_addr{cpu_addr_},
36 is_allocated{is_allocated_} {}
37
38 constexpr VAddr StartAddr() const {
39 return start_addr;
40 }
41
42 constexpr VAddr EndAddr() const {
43 return end_addr;
44 }
45
46 constexpr std::size_t Size() const {
47 return end_addr - start_addr;
48 }
49
50 constexpr VAddr CpuAddr() const {
51 return cpu_addr;
52 }
53
54 constexpr bool IsAllocated() const {
55 return is_allocated;
56 }
57
58 private:
59 GPUVAddr start_addr{};
60 GPUVAddr end_addr{};
61 VAddr cpu_addr{};
62 bool is_allocated{};
63 };
64
65 struct IoctlSetNvmapFD { 26 struct IoctlSetNvmapFD {
66 s32_le nvmap_fd{}; 27 s32_le nvmap_fd{};
67 }; 28 };
@@ -154,17 +115,11 @@ protected:
154 NvResult UnmapBuffer(const std::vector<u8>& input, std::vector<u8>& output); 115 NvResult UnmapBuffer(const std::vector<u8>& input, std::vector<u8>& output);
155 NvResult SetSubmitTimeout(const std::vector<u8>& input, std::vector<u8>& output); 116 NvResult SetSubmitTimeout(const std::vector<u8>& input, std::vector<u8>& output);
156 117
157 std::optional<BufferMap> FindBufferMap(GPUVAddr gpu_addr) const;
158 void AddBufferMap(GPUVAddr gpu_addr, std::size_t size, VAddr cpu_addr, bool is_allocated);
159 std::optional<std::size_t> RemoveBufferMap(GPUVAddr gpu_addr);
160
161 s32_le nvmap_fd{}; 118 s32_le nvmap_fd{};
162 u32_le submit_timeout{}; 119 u32_le submit_timeout{};
163 std::shared_ptr<nvmap> nvmap_dev; 120 std::shared_ptr<nvmap> nvmap_dev;
164 SyncpointManager& syncpoint_manager; 121 SyncpointManager& syncpoint_manager;
165 std::array<u32, MaxSyncPoints> device_syncpoints{}; 122 std::array<u32, MaxSyncPoints> device_syncpoints{};
166 // This is expected to be ordered, therefore we must use a map, not unordered_map
167 std::map<GPUVAddr, BufferMap> buffer_mappings;
168}; 123};
169}; // namespace Devices 124}; // namespace Devices
170} // namespace Service::Nvidia 125} // namespace Service::Nvidia
diff --git a/src/core/hle/service/set/set.cpp b/src/core/hle/service/set/set.cpp
index f2e2e8306..8795eb6b7 100644
--- a/src/core/hle/service/set/set.cpp
+++ b/src/core/hle/service/set/set.cpp
@@ -85,7 +85,8 @@ void PushResponseLanguageCode(Kernel::HLERequestContext& ctx, std::size_t num_la
85 85
86void GetAvailableLanguageCodesImpl(Kernel::HLERequestContext& ctx, std::size_t max_entries) { 86void GetAvailableLanguageCodesImpl(Kernel::HLERequestContext& ctx, std::size_t max_entries) {
87 const std::size_t requested_amount = ctx.GetWriteBufferSize() / sizeof(LanguageCode); 87 const std::size_t requested_amount = ctx.GetWriteBufferSize() / sizeof(LanguageCode);
88 const std::size_t copy_amount = std::min(requested_amount, max_entries); 88 const std::size_t max_amount = std::min(requested_amount, max_entries);
89 const std::size_t copy_amount = std::min(available_language_codes.size(), max_amount);
89 const std::size_t copy_size = copy_amount * sizeof(LanguageCode); 90 const std::size_t copy_size = copy_amount * sizeof(LanguageCode);
90 91
91 ctx.WriteBuffer(available_language_codes.data(), copy_size); 92 ctx.WriteBuffer(available_language_codes.data(), copy_size);
diff --git a/src/core/network/network.cpp b/src/core/network/network.cpp
index 526bfa110..375bc79ec 100644
--- a/src/core/network/network.cpp
+++ b/src/core/network/network.cpp
@@ -570,7 +570,7 @@ std::pair<s32, Errno> Socket::SendTo(u32 flags, const std::vector<u8>& message,
570 ASSERT(flags == 0); 570 ASSERT(flags == 0);
571 571
572 const sockaddr* to = nullptr; 572 const sockaddr* to = nullptr;
573 const int tolen = addr ? 0 : sizeof(sockaddr); 573 const int tolen = addr ? sizeof(sockaddr) : 0;
574 sockaddr host_addr_in; 574 sockaddr host_addr_in;
575 575
576 if (addr) { 576 if (addr) {
diff --git a/src/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt
index 333f6f35f..1eb67c051 100644
--- a/src/video_core/CMakeLists.txt
+++ b/src/video_core/CMakeLists.txt
@@ -1,5 +1,10 @@
1add_subdirectory(host_shaders) 1add_subdirectory(host_shaders)
2 2
3if(LIBVA_FOUND)
4 set_source_files_properties(command_classes/codecs/codec.cpp
5 PROPERTIES COMPILE_DEFINITIONS LIBVA_FOUND=1)
6endif()
7
3add_library(video_core STATIC 8add_library(video_core STATIC
4 buffer_cache/buffer_base.h 9 buffer_cache/buffer_base.h
5 buffer_cache/buffer_cache.cpp 10 buffer_cache/buffer_cache.cpp
diff --git a/src/video_core/command_classes/codecs/codec.cpp b/src/video_core/command_classes/codecs/codec.cpp
index 1b4bbc8ac..f798a0053 100644
--- a/src/video_core/command_classes/codecs/codec.cpp
+++ b/src/video_core/command_classes/codecs/codec.cpp
@@ -2,7 +2,6 @@
2// Licensed under GPLv2 or any later version 2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included. 3// Refer to the license.txt file included.
4 4
5#include <cstring>
6#include <fstream> 5#include <fstream>
7#include <vector> 6#include <vector>
8#include "common/assert.h" 7#include "common/assert.h"
@@ -17,10 +16,47 @@ extern "C" {
17} 16}
18 17
19namespace Tegra { 18namespace Tegra {
19#if defined(LIBVA_FOUND)
20// Hardware acceleration code from FFmpeg/doc/examples/hw_decode.c originally under MIT license
21namespace {
22constexpr std::array<const char*, 2> VAAPI_DRIVERS = {
23 "i915",
24 "amdgpu",
25};
26
27AVPixelFormat GetHwFormat(AVCodecContext*, const AVPixelFormat* pix_fmts) {
28 for (const AVPixelFormat* p = pix_fmts; *p != AV_PIX_FMT_NONE; ++p) {
29 if (*p == AV_PIX_FMT_VAAPI) {
30 return AV_PIX_FMT_VAAPI;
31 }
32 }
33 LOG_INFO(Service_NVDRV, "Could not find compatible GPU AV format, falling back to CPU");
34 return *pix_fmts;
35}
36
37bool CreateVaapiHwdevice(AVBufferRef** av_hw_device) {
38 AVDictionary* hwdevice_options = nullptr;
39 av_dict_set(&hwdevice_options, "connection_type", "drm", 0);
40 for (const auto& driver : VAAPI_DRIVERS) {
41 av_dict_set(&hwdevice_options, "kernel_driver", driver, 0);
42 const int hwdevice_error = av_hwdevice_ctx_create(av_hw_device, AV_HWDEVICE_TYPE_VAAPI,
43 nullptr, hwdevice_options, 0);
44 if (hwdevice_error >= 0) {
45 LOG_INFO(Service_NVDRV, "Using VA-API with {}", driver);
46 av_dict_free(&hwdevice_options);
47 return true;
48 }
49 LOG_DEBUG(Service_NVDRV, "VA-API av_hwdevice_ctx_create failed {}", hwdevice_error);
50 }
51 LOG_DEBUG(Service_NVDRV, "VA-API av_hwdevice_ctx_create failed for all drivers");
52 av_dict_free(&hwdevice_options);
53 return false;
54}
55} // namespace
56#endif
20 57
21void AVFrameDeleter(AVFrame* ptr) { 58void AVFrameDeleter(AVFrame* ptr) {
22 av_frame_unref(ptr); 59 av_frame_free(&ptr);
23 av_free(ptr);
24} 60}
25 61
26Codec::Codec(GPU& gpu_, const NvdecCommon::NvdecRegisters& regs) 62Codec::Codec(GPU& gpu_, const NvdecCommon::NvdecRegisters& regs)
@@ -32,19 +68,31 @@ Codec::~Codec() {
32 return; 68 return;
33 } 69 }
34 // Free libav memory 70 // Free libav memory
35 AVFrame* av_frame{nullptr};
36 avcodec_send_packet(av_codec_ctx, nullptr); 71 avcodec_send_packet(av_codec_ctx, nullptr);
37 av_frame = av_frame_alloc(); 72 AVFrame* av_frame = av_frame_alloc();
38 avcodec_receive_frame(av_codec_ctx, av_frame); 73 avcodec_receive_frame(av_codec_ctx, av_frame);
39 avcodec_flush_buffers(av_codec_ctx); 74 avcodec_flush_buffers(av_codec_ctx);
40 75 av_frame_free(&av_frame);
41 av_frame_unref(av_frame);
42 av_free(av_frame);
43 avcodec_close(av_codec_ctx); 76 avcodec_close(av_codec_ctx);
77 av_buffer_unref(&av_hw_device);
78}
79
80void Codec::InitializeHwdec() {
81 // Prioritize integrated GPU to mitigate bandwidth bottlenecks
82#if defined(LIBVA_FOUND)
83 if (CreateVaapiHwdevice(&av_hw_device)) {
84 const auto hw_device_ctx = av_buffer_ref(av_hw_device);
85 ASSERT_MSG(hw_device_ctx, "av_buffer_ref failed");
86 av_codec_ctx->hw_device_ctx = hw_device_ctx;
87 av_codec_ctx->get_format = GetHwFormat;
88 return;
89 }
90#endif
91 // TODO more GPU accelerated decoders
44} 92}
45 93
46void Codec::Initialize() { 94void Codec::Initialize() {
47 AVCodecID codec{AV_CODEC_ID_NONE}; 95 AVCodecID codec;
48 switch (current_codec) { 96 switch (current_codec) {
49 case NvdecCommon::VideoCodec::H264: 97 case NvdecCommon::VideoCodec::H264:
50 codec = AV_CODEC_ID_H264; 98 codec = AV_CODEC_ID_H264;
@@ -53,22 +101,24 @@ void Codec::Initialize() {
53 codec = AV_CODEC_ID_VP9; 101 codec = AV_CODEC_ID_VP9;
54 break; 102 break;
55 default: 103 default:
104 UNIMPLEMENTED_MSG("Unknown codec {}", current_codec);
56 return; 105 return;
57 } 106 }
58 av_codec = avcodec_find_decoder(codec); 107 av_codec = avcodec_find_decoder(codec);
59 av_codec_ctx = avcodec_alloc_context3(av_codec); 108 av_codec_ctx = avcodec_alloc_context3(av_codec);
60 av_opt_set(av_codec_ctx->priv_data, "tune", "zerolatency", 0); 109 av_opt_set(av_codec_ctx->priv_data, "tune", "zerolatency", 0);
61 110 InitializeHwdec();
62 // TODO(ameerj): libavcodec gpu hw acceleration 111 if (!av_codec_ctx->hw_device_ctx) {
63 112 LOG_INFO(Service_NVDRV, "Using FFmpeg software decoding");
113 }
64 const auto av_error = avcodec_open2(av_codec_ctx, av_codec, nullptr); 114 const auto av_error = avcodec_open2(av_codec_ctx, av_codec, nullptr);
65 if (av_error < 0) { 115 if (av_error < 0) {
66 LOG_ERROR(Service_NVDRV, "avcodec_open2() Failed."); 116 LOG_ERROR(Service_NVDRV, "avcodec_open2() Failed.");
67 avcodec_close(av_codec_ctx); 117 avcodec_close(av_codec_ctx);
118 av_buffer_unref(&av_hw_device);
68 return; 119 return;
69 } 120 }
70 initialized = true; 121 initialized = true;
71 return;
72} 122}
73 123
74void Codec::SetTargetCodec(NvdecCommon::VideoCodec codec) { 124void Codec::SetTargetCodec(NvdecCommon::VideoCodec codec) {
@@ -80,36 +130,64 @@ void Codec::SetTargetCodec(NvdecCommon::VideoCodec codec) {
80 130
81void Codec::Decode() { 131void Codec::Decode() {
82 const bool is_first_frame = !initialized; 132 const bool is_first_frame = !initialized;
83 if (!initialized) { 133 if (is_first_frame) {
84 Initialize(); 134 Initialize();
85 } 135 }
86
87 bool vp9_hidden_frame = false; 136 bool vp9_hidden_frame = false;
88 AVPacket packet{};
89 av_init_packet(&packet);
90 std::vector<u8> frame_data; 137 std::vector<u8> frame_data;
91
92 if (current_codec == NvdecCommon::VideoCodec::H264) { 138 if (current_codec == NvdecCommon::VideoCodec::H264) {
93 frame_data = h264_decoder->ComposeFrameHeader(state, is_first_frame); 139 frame_data = h264_decoder->ComposeFrameHeader(state, is_first_frame);
94 } else if (current_codec == NvdecCommon::VideoCodec::Vp9) { 140 } else if (current_codec == NvdecCommon::VideoCodec::Vp9) {
95 frame_data = vp9_decoder->ComposeFrameHeader(state); 141 frame_data = vp9_decoder->ComposeFrameHeader(state);
96 vp9_hidden_frame = vp9_decoder->WasFrameHidden(); 142 vp9_hidden_frame = vp9_decoder->WasFrameHidden();
97 } 143 }
98 144 AVPacket packet{};
145 av_init_packet(&packet);
99 packet.data = frame_data.data(); 146 packet.data = frame_data.data();
100 packet.size = static_cast<s32>(frame_data.size()); 147 packet.size = static_cast<s32>(frame_data.size());
101 148 if (const int ret = avcodec_send_packet(av_codec_ctx, &packet); ret) {
102 avcodec_send_packet(av_codec_ctx, &packet); 149 LOG_DEBUG(Service_NVDRV, "avcodec_send_packet error {}", ret);
103 150 return;
104 if (!vp9_hidden_frame) { 151 }
105 // Only receive/store visible frames 152 // Only receive/store visible frames
106 AVFramePtr frame = AVFramePtr{av_frame_alloc(), AVFrameDeleter}; 153 if (vp9_hidden_frame) {
107 avcodec_receive_frame(av_codec_ctx, frame.get()); 154 return;
108 av_frames.push(std::move(frame)); 155 }
109 // Limit queue to 10 frames. Workaround for ZLA decode and queue spam 156 AVFrame* hw_frame = av_frame_alloc();
110 if (av_frames.size() > 10) { 157 AVFrame* sw_frame = hw_frame;
111 av_frames.pop(); 158 ASSERT_MSG(hw_frame, "av_frame_alloc hw_frame failed");
112 } 159 if (const int ret = avcodec_receive_frame(av_codec_ctx, hw_frame); ret) {
160 LOG_DEBUG(Service_NVDRV, "avcodec_receive_frame error {}", ret);
161 av_frame_free(&hw_frame);
162 return;
163 }
164 if (!hw_frame->width || !hw_frame->height) {
165 LOG_WARNING(Service_NVDRV, "Zero width or height in frame");
166 av_frame_free(&hw_frame);
167 return;
168 }
169#if defined(LIBVA_FOUND)
170 // Hardware acceleration code from FFmpeg/doc/examples/hw_decode.c under MIT license
171 if (hw_frame->format == AV_PIX_FMT_VAAPI) {
172 sw_frame = av_frame_alloc();
173 ASSERT_MSG(sw_frame, "av_frame_alloc sw_frame failed");
174 // Can't use AV_PIX_FMT_YUV420P and share code with software decoding in vic.cpp
175 // because Intel drivers crash unless using AV_PIX_FMT_NV12
176 sw_frame->format = AV_PIX_FMT_NV12;
177 const int transfer_data_ret = av_hwframe_transfer_data(sw_frame, hw_frame, 0);
178 ASSERT_MSG(!transfer_data_ret, "av_hwframe_transfer_data error {}", transfer_data_ret);
179 av_frame_free(&hw_frame);
180 }
181#endif
182 if (sw_frame->format != AV_PIX_FMT_YUV420P && sw_frame->format != AV_PIX_FMT_NV12) {
183 UNIMPLEMENTED_MSG("Unexpected video format from host graphics: {}", sw_frame->format);
184 av_frame_free(&sw_frame);
185 return;
186 }
187 av_frames.push(AVFramePtr{sw_frame, AVFrameDeleter});
188 if (av_frames.size() > 10) {
189 LOG_TRACE(Service_NVDRV, "av_frames.push overflow dropped frame");
190 av_frames.pop();
113 } 191 }
114} 192}
115 193
@@ -119,7 +197,6 @@ AVFramePtr Codec::GetCurrentFrame() {
119 if (av_frames.empty()) { 197 if (av_frames.empty()) {
120 return AVFramePtr{nullptr, AVFrameDeleter}; 198 return AVFramePtr{nullptr, AVFrameDeleter};
121 } 199 }
122
123 AVFramePtr frame = std::move(av_frames.front()); 200 AVFramePtr frame = std::move(av_frames.front());
124 av_frames.pop(); 201 av_frames.pop();
125 return frame; 202 return frame;
@@ -144,6 +221,5 @@ std::string_view Codec::GetCurrentCodecName() const {
144 default: 221 default:
145 return "Unknown"; 222 return "Unknown";
146 } 223 }
147}; 224}
148
149} // namespace Tegra 225} // namespace Tegra
diff --git a/src/video_core/command_classes/codecs/codec.h b/src/video_core/command_classes/codecs/codec.h
index 96c823c76..71936203f 100644
--- a/src/video_core/command_classes/codecs/codec.h
+++ b/src/video_core/command_classes/codecs/codec.h
@@ -22,7 +22,6 @@ extern "C" {
22 22
23namespace Tegra { 23namespace Tegra {
24class GPU; 24class GPU;
25struct VicRegisters;
26 25
27void AVFrameDeleter(AVFrame* ptr); 26void AVFrameDeleter(AVFrame* ptr);
28using AVFramePtr = std::unique_ptr<AVFrame, decltype(&AVFrameDeleter)>; 27using AVFramePtr = std::unique_ptr<AVFrame, decltype(&AVFrameDeleter)>;
@@ -55,10 +54,13 @@ public:
55 [[nodiscard]] std::string_view GetCurrentCodecName() const; 54 [[nodiscard]] std::string_view GetCurrentCodecName() const;
56 55
57private: 56private:
57 void InitializeHwdec();
58
58 bool initialized{}; 59 bool initialized{};
59 NvdecCommon::VideoCodec current_codec{NvdecCommon::VideoCodec::None}; 60 NvdecCommon::VideoCodec current_codec{NvdecCommon::VideoCodec::None};
60 61
61 AVCodec* av_codec{nullptr}; 62 AVCodec* av_codec{nullptr};
63 AVBufferRef* av_hw_device{nullptr};
62 AVCodecContext* av_codec_ctx{nullptr}; 64 AVCodecContext* av_codec_ctx{nullptr};
63 65
64 GPU& gpu; 66 GPU& gpu;
diff --git a/src/video_core/command_classes/codecs/vp9.cpp b/src/video_core/command_classes/codecs/vp9.cpp
index 902bc2a98..7eecb3991 100644
--- a/src/video_core/command_classes/codecs/vp9.cpp
+++ b/src/video_core/command_classes/codecs/vp9.cpp
@@ -11,6 +11,9 @@
11 11
12namespace Tegra::Decoder { 12namespace Tegra::Decoder {
13namespace { 13namespace {
14constexpr u32 diff_update_probability = 252;
15constexpr u32 frame_sync_code = 0x498342;
16
14// Default compressed header probabilities once frame context resets 17// Default compressed header probabilities once frame context resets
15constexpr Vp9EntropyProbs default_probs{ 18constexpr Vp9EntropyProbs default_probs{
16 .y_mode_prob{ 19 .y_mode_prob{
@@ -361,8 +364,7 @@ Vp9PictureInfo VP9::GetVp9PictureInfo(const NvdecCommon::NvdecRegisters& state)
361 InsertEntropy(state.vp9_entropy_probs_offset, vp9_info.entropy); 364 InsertEntropy(state.vp9_entropy_probs_offset, vp9_info.entropy);
362 365
363 // surface_luma_offset[0:3] contains the address of the reference frame offsets in the following 366 // surface_luma_offset[0:3] contains the address of the reference frame offsets in the following
364 // order: last, golden, altref, current. It may be worthwhile to track the updates done here 367 // order: last, golden, altref, current.
365 // to avoid buffering frame data needed for reference frame updating in the header composition.
366 std::copy(state.surface_luma_offset.begin(), state.surface_luma_offset.begin() + 4, 368 std::copy(state.surface_luma_offset.begin(), state.surface_luma_offset.begin() + 4,
367 vp9_info.frame_offsets.begin()); 369 vp9_info.frame_offsets.begin());
368 370
@@ -384,33 +386,18 @@ Vp9FrameContainer VP9::GetCurrentFrame(const NvdecCommon::NvdecRegisters& state)
384 gpu.MemoryManager().ReadBlock(state.frame_bitstream_offset, current_frame.bit_stream.data(), 386 gpu.MemoryManager().ReadBlock(state.frame_bitstream_offset, current_frame.bit_stream.data(),
385 current_frame.info.bitstream_size); 387 current_frame.info.bitstream_size);
386 } 388 }
387 // Buffer two frames, saving the last show frame info 389 if (!next_frame.bit_stream.empty()) {
388 if (!next_next_frame.bit_stream.empty()) {
389 Vp9FrameContainer temp{ 390 Vp9FrameContainer temp{
390 .info = current_frame.info, 391 .info = current_frame.info,
391 .bit_stream = std::move(current_frame.bit_stream), 392 .bit_stream = std::move(current_frame.bit_stream),
392 }; 393 };
393 next_next_frame.info.show_frame = current_frame.info.last_frame_shown; 394 next_frame.info.show_frame = current_frame.info.last_frame_shown;
394 current_frame.info = next_next_frame.info; 395 current_frame.info = next_frame.info;
395 current_frame.bit_stream = std::move(next_next_frame.bit_stream); 396 current_frame.bit_stream = std::move(next_frame.bit_stream);
396 next_next_frame = std::move(temp); 397 next_frame = std::move(temp);
397
398 if (!next_frame.bit_stream.empty()) {
399 Vp9FrameContainer temp2{
400 .info = current_frame.info,
401 .bit_stream = std::move(current_frame.bit_stream),
402 };
403 next_frame.info.show_frame = current_frame.info.last_frame_shown;
404 current_frame.info = next_frame.info;
405 current_frame.bit_stream = std::move(next_frame.bit_stream);
406 next_frame = std::move(temp2);
407 } else {
408 next_frame.info = current_frame.info;
409 next_frame.bit_stream = std::move(current_frame.bit_stream);
410 }
411 } else { 398 } else {
412 next_next_frame.info = current_frame.info; 399 next_frame.info = current_frame.info;
413 next_next_frame.bit_stream = std::move(current_frame.bit_stream); 400 next_frame.bit_stream = std::move(current_frame.bit_stream);
414 } 401 }
415 return current_frame; 402 return current_frame;
416} 403}
@@ -613,86 +600,64 @@ VpxBitStreamWriter VP9::ComposeUncompressedHeader() {
613 600
614 // Reset context 601 // Reset context
615 prev_frame_probs = default_probs; 602 prev_frame_probs = default_probs;
616 swap_next_golden = false; 603 swap_ref_indices = false;
617 loop_filter_ref_deltas.fill(0); 604 loop_filter_ref_deltas.fill(0);
618 loop_filter_mode_deltas.fill(0); 605 loop_filter_mode_deltas.fill(0);
619 606 frame_ctxs.fill(default_probs);
620 // allow frames offsets to stabilize before checking for golden frames
621 grace_period = 4;
622
623 // On key frames, all frame slots are set to the current frame,
624 // so the value of the selected slot doesn't really matter.
625 frame_ctxs.fill({current_frame_number, false, default_probs});
626 607
627 // intra only, meaning the frame can be recreated with no other references 608 // intra only, meaning the frame can be recreated with no other references
628 current_frame_info.intra_only = true; 609 current_frame_info.intra_only = true;
629
630 } else { 610 } else {
631
632 if (!current_frame_info.show_frame) { 611 if (!current_frame_info.show_frame) {
633 uncomp_writer.WriteBit(current_frame_info.intra_only); 612 uncomp_writer.WriteBit(current_frame_info.intra_only);
634 if (!current_frame_info.last_frame_was_key) {
635 swap_next_golden = !swap_next_golden;
636 }
637 } else { 613 } else {
638 current_frame_info.intra_only = false; 614 current_frame_info.intra_only = false;
639 } 615 }
640 if (!current_frame_info.error_resilient_mode) { 616 if (!current_frame_info.error_resilient_mode) {
641 uncomp_writer.WriteU(0, 2); // Reset frame context. 617 uncomp_writer.WriteU(0, 2); // Reset frame context.
642 } 618 }
643 619 const auto& curr_offsets = current_frame_info.frame_offsets;
644 // Last, Golden, Altref frames 620 const auto& next_offsets = next_frame.info.frame_offsets;
645 std::array<s32, 3> ref_frame_index{0, 1, 2}; 621 const bool ref_frames_different = curr_offsets[1] != curr_offsets[2];
646 622 const bool next_references_swap =
647 // Set when next frame is hidden 623 (next_offsets[1] == curr_offsets[2]) || (next_offsets[2] == curr_offsets[1]);
648 // altref and golden references are swapped 624 const bool needs_ref_swap = ref_frames_different && next_references_swap;
649 if (swap_next_golden) { 625 if (needs_ref_swap) {
650 ref_frame_index = std::array<s32, 3>{0, 2, 1}; 626 swap_ref_indices = !swap_ref_indices;
651 } 627 }
652 628 union {
653 // update Last Frame 629 u32 raw;
654 u64 refresh_frame_flags = 1; 630 BitField<0, 1, u32> refresh_last;
655 631 BitField<1, 2, u32> refresh_golden;
656 // golden frame may refresh, determined if the next golden frame offset is changed 632 BitField<2, 1, u32> refresh_alt;
657 bool golden_refresh = false; 633 } refresh_frame_flags;
658 if (grace_period <= 0) { 634
659 for (s32 index = 1; index < 3; ++index) { 635 refresh_frame_flags.raw = 0;
660 if (current_frame_info.frame_offsets[index] != 636 for (u32 index = 0; index < 3; ++index) {
661 next_frame.info.frame_offsets[index]) { 637 // Refresh indices that use the current frame as an index
662 current_frame_info.refresh_frame[index] = true; 638 if (curr_offsets[3] == next_offsets[index]) {
663 golden_refresh = true; 639 refresh_frame_flags.raw |= 1u << index;
664 grace_period = 3;
665 }
666 } 640 }
667 } 641 }
668 642 if (swap_ref_indices) {
669 if (current_frame_info.show_frame && 643 const u32 temp = refresh_frame_flags.refresh_golden;
670 (!next_frame.info.show_frame || next_frame.info.is_key_frame)) { 644 refresh_frame_flags.refresh_golden.Assign(refresh_frame_flags.refresh_alt.Value());
671 // Update golden frame 645 refresh_frame_flags.refresh_alt.Assign(temp);
672 refresh_frame_flags = swap_next_golden ? 2 : 4;
673 }
674
675 if (!current_frame_info.show_frame) {
676 // Update altref
677 refresh_frame_flags = swap_next_golden ? 2 : 4;
678 } else if (golden_refresh) {
679 refresh_frame_flags = 3;
680 } 646 }
681
682 if (current_frame_info.intra_only) { 647 if (current_frame_info.intra_only) {
683 uncomp_writer.WriteU(frame_sync_code, 24); 648 uncomp_writer.WriteU(frame_sync_code, 24);
684 uncomp_writer.WriteU(static_cast<s32>(refresh_frame_flags), 8); 649 uncomp_writer.WriteU(refresh_frame_flags.raw, 8);
685 uncomp_writer.WriteU(current_frame_info.frame_size.width - 1, 16); 650 uncomp_writer.WriteU(current_frame_info.frame_size.width - 1, 16);
686 uncomp_writer.WriteU(current_frame_info.frame_size.height - 1, 16); 651 uncomp_writer.WriteU(current_frame_info.frame_size.height - 1, 16);
687 uncomp_writer.WriteBit(false); // Render and frame size different. 652 uncomp_writer.WriteBit(false); // Render and frame size different.
688 } else { 653 } else {
689 uncomp_writer.WriteU(static_cast<s32>(refresh_frame_flags), 8); 654 const bool swap_indices = needs_ref_swap ^ swap_ref_indices;
690 655 const auto ref_frame_index = swap_indices ? std::array{0, 2, 1} : std::array{0, 1, 2};
691 for (s32 index = 1; index < 4; index++) { 656 uncomp_writer.WriteU(refresh_frame_flags.raw, 8);
657 for (size_t index = 1; index < 4; index++) {
692 uncomp_writer.WriteU(ref_frame_index[index - 1], 3); 658 uncomp_writer.WriteU(ref_frame_index[index - 1], 3);
693 uncomp_writer.WriteU(current_frame_info.ref_frame_sign_bias[index], 1); 659 uncomp_writer.WriteU(current_frame_info.ref_frame_sign_bias[index], 1);
694 } 660 }
695
696 uncomp_writer.WriteBit(true); // Frame size with refs. 661 uncomp_writer.WriteBit(true); // Frame size with refs.
697 uncomp_writer.WriteBit(false); // Render and frame size different. 662 uncomp_writer.WriteBit(false); // Render and frame size different.
698 uncomp_writer.WriteBit(current_frame_info.allow_high_precision_mv); 663 uncomp_writer.WriteBit(current_frame_info.allow_high_precision_mv);
@@ -714,10 +679,9 @@ VpxBitStreamWriter VP9::ComposeUncompressedHeader() {
714 frame_ctx_idx = 1; 679 frame_ctx_idx = 1;
715 } 680 }
716 681
717 uncomp_writer.WriteU(frame_ctx_idx, 2); // Frame context index. 682 uncomp_writer.WriteU(frame_ctx_idx, 2); // Frame context index.
718 prev_frame_probs = 683 prev_frame_probs = frame_ctxs[frame_ctx_idx]; // reference probabilities for compressed header
719 frame_ctxs[frame_ctx_idx].probs; // reference probabilities for compressed header 684 frame_ctxs[frame_ctx_idx] = current_frame_info.entropy;
720 frame_ctxs[frame_ctx_idx] = {current_frame_number, false, current_frame_info.entropy};
721 685
722 uncomp_writer.WriteU(current_frame_info.first_level, 6); 686 uncomp_writer.WriteU(current_frame_info.first_level, 6);
723 uncomp_writer.WriteU(current_frame_info.sharpness_level, 3); 687 uncomp_writer.WriteU(current_frame_info.sharpness_level, 3);
@@ -812,7 +776,6 @@ const std::vector<u8>& VP9::ComposeFrameHeader(const NvdecCommon::NvdecRegisters
812 current_frame_info = curr_frame.info; 776 current_frame_info = curr_frame.info;
813 bitstream = std::move(curr_frame.bit_stream); 777 bitstream = std::move(curr_frame.bit_stream);
814 } 778 }
815
816 // The uncompressed header routine sets PrevProb parameters needed for the compressed header 779 // The uncompressed header routine sets PrevProb parameters needed for the compressed header
817 auto uncomp_writer = ComposeUncompressedHeader(); 780 auto uncomp_writer = ComposeUncompressedHeader();
818 std::vector<u8> compressed_header = ComposeCompressedHeader(); 781 std::vector<u8> compressed_header = ComposeCompressedHeader();
@@ -828,13 +791,6 @@ const std::vector<u8>& VP9::ComposeFrameHeader(const NvdecCommon::NvdecRegisters
828 frame.begin() + uncompressed_header.size()); 791 frame.begin() + uncompressed_header.size());
829 std::copy(bitstream.begin(), bitstream.end(), 792 std::copy(bitstream.begin(), bitstream.end(),
830 frame.begin() + uncompressed_header.size() + compressed_header.size()); 793 frame.begin() + uncompressed_header.size() + compressed_header.size());
831
832 // keep track of frame number
833 current_frame_number++;
834 grace_period--;
835
836 // don't display hidden frames
837 hidden = !current_frame_info.show_frame;
838 return frame; 794 return frame;
839} 795}
840 796
diff --git a/src/video_core/command_classes/codecs/vp9.h b/src/video_core/command_classes/codecs/vp9.h
index 8396c8105..e6e9fc17e 100644
--- a/src/video_core/command_classes/codecs/vp9.h
+++ b/src/video_core/command_classes/codecs/vp9.h
@@ -14,7 +14,6 @@
14 14
15namespace Tegra { 15namespace Tegra {
16class GPU; 16class GPU;
17enum class FrameType { KeyFrame = 0, InterFrame = 1 };
18namespace Decoder { 17namespace Decoder {
19 18
20/// The VpxRangeEncoder, and VpxBitStreamWriter classes are used to compose the 19/// The VpxRangeEncoder, and VpxBitStreamWriter classes are used to compose the
@@ -124,7 +123,7 @@ public:
124 123
125 /// Returns true if the most recent frame was a hidden frame. 124 /// Returns true if the most recent frame was a hidden frame.
126 [[nodiscard]] bool WasFrameHidden() const { 125 [[nodiscard]] bool WasFrameHidden() const {
127 return hidden; 126 return !current_frame_info.show_frame;
128 } 127 }
129 128
130private: 129private:
@@ -178,19 +177,12 @@ private:
178 std::array<s8, 4> loop_filter_ref_deltas{}; 177 std::array<s8, 4> loop_filter_ref_deltas{};
179 std::array<s8, 2> loop_filter_mode_deltas{}; 178 std::array<s8, 2> loop_filter_mode_deltas{};
180 179
181 bool hidden = false;
182 s64 current_frame_number = -2; // since we buffer 2 frames
183 s32 grace_period = 6; // frame offsets need to stabilize
184 std::array<FrameContexts, 4> frame_ctxs{};
185 Vp9FrameContainer next_frame{}; 180 Vp9FrameContainer next_frame{};
186 Vp9FrameContainer next_next_frame{}; 181 std::array<Vp9EntropyProbs, 4> frame_ctxs{};
187 bool swap_next_golden{}; 182 bool swap_ref_indices{};
188 183
189 Vp9PictureInfo current_frame_info{}; 184 Vp9PictureInfo current_frame_info{};
190 Vp9EntropyProbs prev_frame_probs{}; 185 Vp9EntropyProbs prev_frame_probs{};
191
192 s32 diff_update_probability = 252;
193 s32 frame_sync_code = 0x498342;
194}; 186};
195 187
196} // namespace Decoder 188} // namespace Decoder
diff --git a/src/video_core/command_classes/codecs/vp9_types.h b/src/video_core/command_classes/codecs/vp9_types.h
index 2da14f3ca..6820afa26 100644
--- a/src/video_core/command_classes/codecs/vp9_types.h
+++ b/src/video_core/command_classes/codecs/vp9_types.h
@@ -296,12 +296,6 @@ struct RefPoolElement {
296 bool refresh{}; 296 bool refresh{};
297}; 297};
298 298
299struct FrameContexts {
300 s64 from;
301 bool adapted;
302 Vp9EntropyProbs probs;
303};
304
305#define ASSERT_POSITION(field_name, position) \ 299#define ASSERT_POSITION(field_name, position) \
306 static_assert(offsetof(Vp9EntropyProbs, field_name) == position, \ 300 static_assert(offsetof(Vp9EntropyProbs, field_name) == position, \
307 "Field " #field_name " has invalid position") 301 "Field " #field_name " has invalid position")
diff --git a/src/video_core/command_classes/vic.cpp b/src/video_core/command_classes/vic.cpp
index ffb7c82a1..d5e77941c 100644
--- a/src/video_core/command_classes/vic.cpp
+++ b/src/video_core/command_classes/vic.cpp
@@ -46,11 +46,8 @@ void Vic::ProcessMethod(Method method, u32 argument) {
46 case Method::SetOutputSurfaceLumaOffset: 46 case Method::SetOutputSurfaceLumaOffset:
47 output_surface_luma_address = arg; 47 output_surface_luma_address = arg;
48 break; 48 break;
49 case Method::SetOutputSurfaceChromaUOffset: 49 case Method::SetOutputSurfaceChromaOffset:
50 output_surface_chroma_u_address = arg; 50 output_surface_chroma_address = arg;
51 break;
52 case Method::SetOutputSurfaceChromaVOffset:
53 output_surface_chroma_v_address = arg;
54 break; 51 break;
55 default: 52 default:
56 break; 53 break;
@@ -65,11 +62,10 @@ void Vic::Execute() {
65 const VicConfig config{gpu.MemoryManager().Read<u64>(config_struct_address + 0x20)}; 62 const VicConfig config{gpu.MemoryManager().Read<u64>(config_struct_address + 0x20)};
66 const AVFramePtr frame_ptr = nvdec_processor->GetFrame(); 63 const AVFramePtr frame_ptr = nvdec_processor->GetFrame();
67 const auto* frame = frame_ptr.get(); 64 const auto* frame = frame_ptr.get();
68 if (!frame || frame->width == 0 || frame->height == 0) { 65 if (!frame) {
69 return; 66 return;
70 } 67 }
71 const VideoPixelFormat pixel_format = 68 const auto pixel_format = static_cast<VideoPixelFormat>(config.pixel_format.Value());
72 static_cast<VideoPixelFormat>(config.pixel_format.Value());
73 switch (pixel_format) { 69 switch (pixel_format) {
74 case VideoPixelFormat::BGRA8: 70 case VideoPixelFormat::BGRA8:
75 case VideoPixelFormat::RGBA8: { 71 case VideoPixelFormat::RGBA8: {
@@ -83,16 +79,18 @@ void Vic::Execute() {
83 sws_freeContext(scaler_ctx); 79 sws_freeContext(scaler_ctx);
84 scaler_ctx = nullptr; 80 scaler_ctx = nullptr;
85 81
86 // FFmpeg returns all frames in YUV420, convert it into expected format 82 // Frames are decoded into either YUV420 or NV12 formats. Convert to desired format
87 scaler_ctx = 83 scaler_ctx = sws_getContext(frame->width, frame->height,
88 sws_getContext(frame->width, frame->height, AV_PIX_FMT_YUV420P, frame->width, 84 static_cast<AVPixelFormat>(frame->format), frame->width,
89 frame->height, target_format, 0, nullptr, nullptr, nullptr); 85 frame->height, target_format, 0, nullptr, nullptr, nullptr);
90 86
91 scaler_width = frame->width; 87 scaler_width = frame->width;
92 scaler_height = frame->height; 88 scaler_height = frame->height;
93 } 89 }
94 // Get Converted frame 90 // Get Converted frame
95 const std::size_t linear_size = frame->width * frame->height * 4; 91 const u32 width = static_cast<u32>(frame->width);
92 const u32 height = static_cast<u32>(frame->height);
93 const std::size_t linear_size = width * height * 4;
96 94
97 // Only allocate frame_buffer once per stream, as the size is not expected to change 95 // Only allocate frame_buffer once per stream, as the size is not expected to change
98 if (!converted_frame_buffer) { 96 if (!converted_frame_buffer) {
@@ -109,11 +107,10 @@ void Vic::Execute() {
109 if (blk_kind != 0) { 107 if (blk_kind != 0) {
110 // swizzle pitch linear to block linear 108 // swizzle pitch linear to block linear
111 const u32 block_height = static_cast<u32>(config.block_linear_height_log2); 109 const u32 block_height = static_cast<u32>(config.block_linear_height_log2);
112 const auto size = Tegra::Texture::CalculateSize(true, 4, frame->width, frame->height, 1, 110 const auto size =
113 block_height, 0); 111 Tegra::Texture::CalculateSize(true, 4, width, height, 1, block_height, 0);
114 luma_buffer.resize(size); 112 luma_buffer.resize(size);
115 Tegra::Texture::SwizzleSubrect(frame->width, frame->height, frame->width * 4, 113 Tegra::Texture::SwizzleSubrect(width, height, width * 4, width, 4, luma_buffer.data(),
116 frame->width, 4, luma_buffer.data(),
117 converted_frame_buffer.get(), block_height, 0, 0); 114 converted_frame_buffer.get(), block_height, 0, 0);
118 115
119 gpu.MemoryManager().WriteBlock(output_surface_luma_address, luma_buffer.data(), size); 116 gpu.MemoryManager().WriteBlock(output_surface_luma_address, luma_buffer.data(), size);
@@ -131,41 +128,65 @@ void Vic::Execute() {
131 const std::size_t surface_height = config.surface_height_minus1 + 1; 128 const std::size_t surface_height = config.surface_height_minus1 + 1;
132 const auto frame_width = std::min(surface_width, static_cast<size_t>(frame->width)); 129 const auto frame_width = std::min(surface_width, static_cast<size_t>(frame->width));
133 const auto frame_height = std::min(surface_height, static_cast<size_t>(frame->height)); 130 const auto frame_height = std::min(surface_height, static_cast<size_t>(frame->height));
134 const std::size_t half_width = frame_width / 2; 131 const std::size_t aligned_width = (surface_width + 0xff) & ~0xffUL;
135 const std::size_t half_height = frame_height / 2;
136 const std::size_t aligned_width = (surface_width + 0xff) & ~0xff;
137 132
138 const auto* luma_ptr = frame->data[0];
139 const auto* chroma_b_ptr = frame->data[1];
140 const auto* chroma_r_ptr = frame->data[2];
141 const auto stride = static_cast<size_t>(frame->linesize[0]); 133 const auto stride = static_cast<size_t>(frame->linesize[0]);
142 const auto half_stride = static_cast<size_t>(frame->linesize[1]);
143 134
144 luma_buffer.resize(aligned_width * surface_height); 135 luma_buffer.resize(aligned_width * surface_height);
145 chroma_buffer.resize(aligned_width * surface_height / 2); 136 chroma_buffer.resize(aligned_width * surface_height / 2);
146 137
147 // Populate luma buffer 138 // Populate luma buffer
139 const u8* luma_src = frame->data[0];
148 for (std::size_t y = 0; y < frame_height; ++y) { 140 for (std::size_t y = 0; y < frame_height; ++y) {
149 const std::size_t src = y * stride; 141 const std::size_t src = y * stride;
150 const std::size_t dst = y * aligned_width; 142 const std::size_t dst = y * aligned_width;
151 for (std::size_t x = 0; x < frame_width; ++x) { 143 for (std::size_t x = 0; x < frame_width; ++x) {
152 luma_buffer[dst + x] = luma_ptr[src + x]; 144 luma_buffer[dst + x] = luma_src[src + x];
153 } 145 }
154 } 146 }
155 gpu.MemoryManager().WriteBlock(output_surface_luma_address, luma_buffer.data(), 147 gpu.MemoryManager().WriteBlock(output_surface_luma_address, luma_buffer.data(),
156 luma_buffer.size()); 148 luma_buffer.size());
157 149
158 // Populate chroma buffer from both channels with interleaving. 150 // Chroma
159 for (std::size_t y = 0; y < half_height; ++y) { 151 const std::size_t half_height = frame_height / 2;
160 const std::size_t src = y * half_stride; 152 const auto half_stride = static_cast<size_t>(frame->linesize[1]);
161 const std::size_t dst = y * aligned_width;
162 153
163 for (std::size_t x = 0; x < half_width; ++x) { 154 switch (frame->format) {
164 chroma_buffer[dst + x * 2] = chroma_b_ptr[src + x]; 155 case AV_PIX_FMT_YUV420P: {
165 chroma_buffer[dst + x * 2 + 1] = chroma_r_ptr[src + x]; 156 // Frame from FFmpeg software
157 // Populate chroma buffer from both channels with interleaving.
158 const std::size_t half_width = frame_width / 2;
159 const u8* chroma_b_src = frame->data[1];
160 const u8* chroma_r_src = frame->data[2];
161 for (std::size_t y = 0; y < half_height; ++y) {
162 const std::size_t src = y * half_stride;
163 const std::size_t dst = y * aligned_width;
164
165 for (std::size_t x = 0; x < half_width; ++x) {
166 chroma_buffer[dst + x * 2] = chroma_b_src[src + x];
167 chroma_buffer[dst + x * 2 + 1] = chroma_r_src[src + x];
168 }
169 }
170 break;
171 }
172 case AV_PIX_FMT_NV12: {
173 // Frame from VA-API hardware
174 // This is already interleaved so just copy
175 const u8* chroma_src = frame->data[1];
176 for (std::size_t y = 0; y < half_height; ++y) {
177 const std::size_t src = y * stride;
178 const std::size_t dst = y * aligned_width;
179 for (std::size_t x = 0; x < frame_width; ++x) {
180 chroma_buffer[dst + x] = chroma_src[src + x];
181 }
166 } 182 }
183 break;
184 }
185 default:
186 UNREACHABLE();
187 break;
167 } 188 }
168 gpu.MemoryManager().WriteBlock(output_surface_chroma_u_address, chroma_buffer.data(), 189 gpu.MemoryManager().WriteBlock(output_surface_chroma_address, chroma_buffer.data(),
169 chroma_buffer.size()); 190 chroma_buffer.size());
170 break; 191 break;
171 } 192 }
diff --git a/src/video_core/command_classes/vic.h b/src/video_core/command_classes/vic.h
index f5a2ed100..74246e08c 100644
--- a/src/video_core/command_classes/vic.h
+++ b/src/video_core/command_classes/vic.h
@@ -22,8 +22,8 @@ public:
22 SetControlParams = 0x1c1, 22 SetControlParams = 0x1c1,
23 SetConfigStructOffset = 0x1c2, 23 SetConfigStructOffset = 0x1c2,
24 SetOutputSurfaceLumaOffset = 0x1c8, 24 SetOutputSurfaceLumaOffset = 0x1c8,
25 SetOutputSurfaceChromaUOffset = 0x1c9, 25 SetOutputSurfaceChromaOffset = 0x1c9,
26 SetOutputSurfaceChromaVOffset = 0x1ca 26 SetOutputSurfaceChromaUnusedOffset = 0x1ca
27 }; 27 };
28 28
29 explicit Vic(GPU& gpu, std::shared_ptr<Nvdec> nvdec_processor); 29 explicit Vic(GPU& gpu, std::shared_ptr<Nvdec> nvdec_processor);
@@ -64,8 +64,7 @@ private:
64 64
65 GPUVAddr config_struct_address{}; 65 GPUVAddr config_struct_address{};
66 GPUVAddr output_surface_luma_address{}; 66 GPUVAddr output_surface_luma_address{};
67 GPUVAddr output_surface_chroma_u_address{}; 67 GPUVAddr output_surface_chroma_address{};
68 GPUVAddr output_surface_chroma_v_address{};
69 68
70 SwsContext* scaler_ctx{}; 69 SwsContext* scaler_ctx{};
71 s32 scaler_width{}; 70 s32 scaler_width{};
diff --git a/src/video_core/renderer_base.cpp b/src/video_core/renderer_base.cpp
index 3ea72fda9..a99c33c37 100644
--- a/src/video_core/renderer_base.cpp
+++ b/src/video_core/renderer_base.cpp
@@ -27,7 +27,7 @@ void RendererBase::UpdateCurrentFramebufferLayout() {
27 render_window.UpdateCurrentFramebufferLayout(layout.width, layout.height); 27 render_window.UpdateCurrentFramebufferLayout(layout.width, layout.height);
28} 28}
29 29
30void RendererBase::RequestScreenshot(void* data, std::function<void()> callback, 30void RendererBase::RequestScreenshot(void* data, std::function<void(bool)> callback,
31 const Layout::FramebufferLayout& layout) { 31 const Layout::FramebufferLayout& layout) {
32 if (renderer_settings.screenshot_requested) { 32 if (renderer_settings.screenshot_requested) {
33 LOG_ERROR(Render, "A screenshot is already requested or in progress, ignoring the request"); 33 LOG_ERROR(Render, "A screenshot is already requested or in progress, ignoring the request");
diff --git a/src/video_core/renderer_base.h b/src/video_core/renderer_base.h
index 22b80c328..bb204454e 100644
--- a/src/video_core/renderer_base.h
+++ b/src/video_core/renderer_base.h
@@ -24,7 +24,7 @@ struct RendererSettings {
24 // Screenshot 24 // Screenshot
25 std::atomic<bool> screenshot_requested{false}; 25 std::atomic<bool> screenshot_requested{false};
26 void* screenshot_bits{}; 26 void* screenshot_bits{};
27 std::function<void()> screenshot_complete_callback; 27 std::function<void(bool)> screenshot_complete_callback;
28 Layout::FramebufferLayout screenshot_framebuffer_layout; 28 Layout::FramebufferLayout screenshot_framebuffer_layout;
29}; 29};
30 30
@@ -80,7 +80,7 @@ public:
80 void RefreshBaseSettings(); 80 void RefreshBaseSettings();
81 81
82 /// Request a screenshot of the next frame 82 /// Request a screenshot of the next frame
83 void RequestScreenshot(void* data, std::function<void()> callback, 83 void RequestScreenshot(void* data, std::function<void(bool)> callback,
84 const Layout::FramebufferLayout& layout); 84 const Layout::FramebufferLayout& layout);
85 85
86protected: 86protected:
diff --git a/src/video_core/renderer_opengl/renderer_opengl.cpp b/src/video_core/renderer_opengl/renderer_opengl.cpp
index f1b00c24c..7d7cba69c 100644
--- a/src/video_core/renderer_opengl/renderer_opengl.cpp
+++ b/src/video_core/renderer_opengl/renderer_opengl.cpp
@@ -486,7 +486,7 @@ void RendererOpenGL::RenderScreenshot() {
486 glBindFramebuffer(GL_READ_FRAMEBUFFER, old_read_fb); 486 glBindFramebuffer(GL_READ_FRAMEBUFFER, old_read_fb);
487 glBindFramebuffer(GL_DRAW_FRAMEBUFFER, old_draw_fb); 487 glBindFramebuffer(GL_DRAW_FRAMEBUFFER, old_draw_fb);
488 488
489 renderer_settings.screenshot_complete_callback(); 489 renderer_settings.screenshot_complete_callback(true);
490 renderer_settings.screenshot_requested = false; 490 renderer_settings.screenshot_requested = false;
491} 491}
492 492
diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.cpp b/src/video_core/renderer_vulkan/renderer_vulkan.cpp
index a8d04dc61..7c9b0d6db 100644
--- a/src/video_core/renderer_vulkan/renderer_vulkan.cpp
+++ b/src/video_core/renderer_vulkan/renderer_vulkan.cpp
@@ -138,6 +138,7 @@ void RendererVulkan::SwapBuffers(const Tegra::FramebufferConfig* framebuffer) {
138 const bool use_accelerated = 138 const bool use_accelerated =
139 rasterizer.AccelerateDisplay(*framebuffer, framebuffer_addr, framebuffer->stride); 139 rasterizer.AccelerateDisplay(*framebuffer, framebuffer_addr, framebuffer->stride);
140 const bool is_srgb = use_accelerated && screen_info.is_srgb; 140 const bool is_srgb = use_accelerated && screen_info.is_srgb;
141 RenderScreenshot(*framebuffer, use_accelerated);
141 142
142 bool has_been_recreated = false; 143 bool has_been_recreated = false;
143 const auto recreate_swapchain = [&] { 144 const auto recreate_swapchain = [&] {
@@ -162,7 +163,7 @@ void RendererVulkan::SwapBuffers(const Tegra::FramebufferConfig* framebuffer) {
162 if (has_been_recreated) { 163 if (has_been_recreated) {
163 blit_screen.Recreate(); 164 blit_screen.Recreate();
164 } 165 }
165 const VkSemaphore render_semaphore = blit_screen.Draw(*framebuffer, use_accelerated); 166 const VkSemaphore render_semaphore = blit_screen.DrawToSwapchain(*framebuffer, use_accelerated);
166 scheduler.Flush(render_semaphore); 167 scheduler.Flush(render_semaphore);
167 scheduler.WaitWorker(); 168 scheduler.WaitWorker();
168 swapchain.Present(render_semaphore); 169 swapchain.Present(render_semaphore);
@@ -193,4 +194,153 @@ void RendererVulkan::Report() const {
193 telemetry_session.AddField(field, "GPU_Vulkan_Extensions", extensions); 194 telemetry_session.AddField(field, "GPU_Vulkan_Extensions", extensions);
194} 195}
195 196
197void Vulkan::RendererVulkan::RenderScreenshot(const Tegra::FramebufferConfig& framebuffer,
198 bool use_accelerated) {
199 if (!renderer_settings.screenshot_requested) {
200 return;
201 }
202 const Layout::FramebufferLayout layout{renderer_settings.screenshot_framebuffer_layout};
203 vk::Image staging_image = device.GetLogical().CreateImage(VkImageCreateInfo{
204 .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
205 .pNext = nullptr,
206 .flags = VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT,
207 .imageType = VK_IMAGE_TYPE_2D,
208 .format = VK_FORMAT_B8G8R8A8_UNORM,
209 .extent =
210 {
211 .width = layout.width,
212 .height = layout.height,
213 .depth = 1,
214 },
215 .mipLevels = 1,
216 .arrayLayers = 1,
217 .samples = VK_SAMPLE_COUNT_1_BIT,
218 .tiling = VK_IMAGE_TILING_OPTIMAL,
219 .usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT |
220 VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT,
221 .sharingMode = VK_SHARING_MODE_EXCLUSIVE,
222 .queueFamilyIndexCount = 0,
223 .pQueueFamilyIndices = nullptr,
224 .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
225 });
226 const auto image_commit = memory_allocator.Commit(staging_image, MemoryUsage::DeviceLocal);
227
228 const vk::ImageView dst_view = device.GetLogical().CreateImageView(VkImageViewCreateInfo{
229 .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
230 .pNext = nullptr,
231 .flags = 0,
232 .image = *staging_image,
233 .viewType = VK_IMAGE_VIEW_TYPE_2D,
234 .format = screen_info.is_srgb ? VK_FORMAT_B8G8R8A8_SRGB : VK_FORMAT_B8G8R8A8_UNORM,
235 .components{
236 .r = VK_COMPONENT_SWIZZLE_IDENTITY,
237 .g = VK_COMPONENT_SWIZZLE_IDENTITY,
238 .b = VK_COMPONENT_SWIZZLE_IDENTITY,
239 .a = VK_COMPONENT_SWIZZLE_IDENTITY,
240 },
241 .subresourceRange{
242 .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
243 .baseMipLevel = 0,
244 .levelCount = 1,
245 .baseArrayLayer = 0,
246 .layerCount = VK_REMAINING_ARRAY_LAYERS,
247 },
248 });
249 const VkExtent2D render_area{.width = layout.width, .height = layout.height};
250 const vk::Framebuffer screenshot_fb = blit_screen.CreateFramebuffer(*dst_view, render_area);
251 // Since we're not rendering to the screen, ignore the render semaphore.
252 void(blit_screen.Draw(framebuffer, *screenshot_fb, layout, render_area, use_accelerated));
253
254 const auto buffer_size = static_cast<VkDeviceSize>(layout.width * layout.height * 4);
255 const VkBufferCreateInfo dst_buffer_info{
256 .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
257 .pNext = nullptr,
258 .flags = 0,
259 .size = buffer_size,
260 .usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT,
261 .sharingMode = VK_SHARING_MODE_EXCLUSIVE,
262 .queueFamilyIndexCount = 0,
263 .pQueueFamilyIndices = nullptr,
264 };
265 const vk::Buffer dst_buffer = device.GetLogical().CreateBuffer(dst_buffer_info);
266 MemoryCommit dst_buffer_memory = memory_allocator.Commit(dst_buffer, MemoryUsage::Download);
267
268 scheduler.RequestOutsideRenderPassOperationContext();
269 scheduler.Record([&](vk::CommandBuffer cmdbuf) {
270 const VkImageMemoryBarrier read_barrier{
271 .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
272 .pNext = nullptr,
273 .srcAccessMask = VK_ACCESS_MEMORY_WRITE_BIT,
274 .dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT,
275 .oldLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
276 .newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
277 .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
278 .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
279 .image = *staging_image,
280 .subresourceRange{
281 .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
282 .baseMipLevel = 0,
283 .levelCount = VK_REMAINING_MIP_LEVELS,
284 .baseArrayLayer = 0,
285 .layerCount = VK_REMAINING_ARRAY_LAYERS,
286 },
287 };
288 const VkImageMemoryBarrier image_write_barrier{
289 .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
290 .pNext = nullptr,
291 .srcAccessMask = 0,
292 .dstAccessMask = VK_ACCESS_MEMORY_WRITE_BIT,
293 .oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
294 .newLayout = VK_IMAGE_LAYOUT_GENERAL,
295 .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
296 .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
297 .image = *staging_image,
298 .subresourceRange{
299 .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
300 .baseMipLevel = 0,
301 .levelCount = VK_REMAINING_MIP_LEVELS,
302 .baseArrayLayer = 0,
303 .layerCount = VK_REMAINING_ARRAY_LAYERS,
304 },
305 };
306 static constexpr VkMemoryBarrier memory_write_barrier{
307 .sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER,
308 .pNext = nullptr,
309 .srcAccessMask = VK_ACCESS_MEMORY_WRITE_BIT,
310 .dstAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT,
311 };
312 const VkBufferImageCopy copy{
313 .bufferOffset = 0,
314 .bufferRowLength = 0,
315 .bufferImageHeight = 0,
316 .imageSubresource{
317 .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
318 .mipLevel = 0,
319 .baseArrayLayer = 0,
320 .layerCount = 1,
321 },
322 .imageOffset{.x = 0, .y = 0, .z = 0},
323 .imageExtent{
324 .width = layout.width,
325 .height = layout.height,
326 .depth = 1,
327 },
328 };
329 cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT,
330 0, read_barrier);
331 cmdbuf.CopyImageToBuffer(*staging_image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, *dst_buffer,
332 copy);
333 cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
334 0, memory_write_barrier, nullptr, image_write_barrier);
335 });
336 // Ensure the copy is fully completed before saving the screenshot
337 scheduler.Finish();
338
339 // Copy backing image data to the QImage screenshot buffer
340 const auto dst_memory_map = dst_buffer_memory.Map();
341 std::memcpy(renderer_settings.screenshot_bits, dst_memory_map.data(), dst_memory_map.size());
342 renderer_settings.screenshot_complete_callback(false);
343 renderer_settings.screenshot_requested = false;
344}
345
196} // namespace Vulkan 346} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.h b/src/video_core/renderer_vulkan/renderer_vulkan.h
index d7d17e110..6dc985109 100644
--- a/src/video_core/renderer_vulkan/renderer_vulkan.h
+++ b/src/video_core/renderer_vulkan/renderer_vulkan.h
@@ -54,6 +54,8 @@ public:
54private: 54private:
55 void Report() const; 55 void Report() const;
56 56
57 void RenderScreenshot(const Tegra::FramebufferConfig& framebuffer, bool use_accelerated);
58
57 Core::TelemetrySession& telemetry_session; 59 Core::TelemetrySession& telemetry_session;
58 Core::Memory::Memory& cpu_memory; 60 Core::Memory::Memory& cpu_memory;
59 Tegra::GPU& gpu; 61 Tegra::GPU& gpu;
diff --git a/src/video_core/renderer_vulkan/vk_blit_screen.cpp b/src/video_core/renderer_vulkan/vk_blit_screen.cpp
index 516f428e7..5c43b8acf 100644
--- a/src/video_core/renderer_vulkan/vk_blit_screen.cpp
+++ b/src/video_core/renderer_vulkan/vk_blit_screen.cpp
@@ -130,7 +130,10 @@ void VKBlitScreen::Recreate() {
130 CreateDynamicResources(); 130 CreateDynamicResources();
131} 131}
132 132
133VkSemaphore VKBlitScreen::Draw(const Tegra::FramebufferConfig& framebuffer, bool use_accelerated) { 133VkSemaphore VKBlitScreen::Draw(const Tegra::FramebufferConfig& framebuffer,
134 const VkFramebuffer& host_framebuffer,
135 const Layout::FramebufferLayout layout, VkExtent2D render_area,
136 bool use_accelerated) {
134 RefreshResources(framebuffer); 137 RefreshResources(framebuffer);
135 138
136 // Finish any pending renderpass 139 // Finish any pending renderpass
@@ -145,8 +148,8 @@ VkSemaphore VKBlitScreen::Draw(const Tegra::FramebufferConfig& framebuffer, bool
145 use_accelerated ? screen_info.image_view : *raw_image_views[image_index]); 148 use_accelerated ? screen_info.image_view : *raw_image_views[image_index]);
146 149
147 BufferData data; 150 BufferData data;
148 SetUniformData(data, framebuffer); 151 SetUniformData(data, layout);
149 SetVertexData(data, framebuffer); 152 SetVertexData(data, framebuffer, layout);
150 153
151 const std::span<u8> mapped_span = buffer_commit.Map(); 154 const std::span<u8> mapped_span = buffer_commit.Map();
152 std::memcpy(mapped_span.data(), &data, sizeof(data)); 155 std::memcpy(mapped_span.data(), &data, sizeof(data));
@@ -220,52 +223,75 @@ VkSemaphore VKBlitScreen::Draw(const Tegra::FramebufferConfig& framebuffer, bool
220 VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, write_barrier); 223 VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, write_barrier);
221 }); 224 });
222 } 225 }
223 scheduler.Record([this, image_index, size = swapchain.GetSize()](vk::CommandBuffer cmdbuf) { 226 scheduler.Record(
224 const f32 bg_red = Settings::values.bg_red.GetValue() / 255.0f; 227 [this, host_framebuffer, image_index, size = render_area](vk::CommandBuffer cmdbuf) {
225 const f32 bg_green = Settings::values.bg_green.GetValue() / 255.0f; 228 const f32 bg_red = Settings::values.bg_red.GetValue() / 255.0f;
226 const f32 bg_blue = Settings::values.bg_blue.GetValue() / 255.0f; 229 const f32 bg_green = Settings::values.bg_green.GetValue() / 255.0f;
227 const VkClearValue clear_color{ 230 const f32 bg_blue = Settings::values.bg_blue.GetValue() / 255.0f;
228 .color = {.float32 = {bg_red, bg_green, bg_blue, 1.0f}}, 231 const VkClearValue clear_color{
229 }; 232 .color = {.float32 = {bg_red, bg_green, bg_blue, 1.0f}},
230 const VkRenderPassBeginInfo renderpass_bi{ 233 };
231 .sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO, 234 const VkRenderPassBeginInfo renderpass_bi{
232 .pNext = nullptr, 235 .sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO,
233 .renderPass = *renderpass, 236 .pNext = nullptr,
234 .framebuffer = *framebuffers[image_index], 237 .renderPass = *renderpass,
235 .renderArea = 238 .framebuffer = host_framebuffer,
236 { 239 .renderArea =
237 .offset = {0, 0}, 240 {
238 .extent = size, 241 .offset = {0, 0},
239 }, 242 .extent = size,
240 .clearValueCount = 1, 243 },
241 .pClearValues = &clear_color, 244 .clearValueCount = 1,
242 }; 245 .pClearValues = &clear_color,
243 const VkViewport viewport{ 246 };
244 .x = 0.0f, 247 const VkViewport viewport{
245 .y = 0.0f, 248 .x = 0.0f,
246 .width = static_cast<float>(size.width), 249 .y = 0.0f,
247 .height = static_cast<float>(size.height), 250 .width = static_cast<float>(size.width),
248 .minDepth = 0.0f, 251 .height = static_cast<float>(size.height),
249 .maxDepth = 1.0f, 252 .minDepth = 0.0f,
250 }; 253 .maxDepth = 1.0f,
251 const VkRect2D scissor{ 254 };
252 .offset = {0, 0}, 255 const VkRect2D scissor{
253 .extent = size, 256 .offset = {0, 0},
254 }; 257 .extent = size,
255 cmdbuf.BeginRenderPass(renderpass_bi, VK_SUBPASS_CONTENTS_INLINE); 258 };
256 cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, *pipeline); 259 cmdbuf.BeginRenderPass(renderpass_bi, VK_SUBPASS_CONTENTS_INLINE);
257 cmdbuf.SetViewport(0, viewport); 260 cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, *pipeline);
258 cmdbuf.SetScissor(0, scissor); 261 cmdbuf.SetViewport(0, viewport);
259 262 cmdbuf.SetScissor(0, scissor);
260 cmdbuf.BindVertexBuffer(0, *buffer, offsetof(BufferData, vertices)); 263
261 cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_GRAPHICS, *pipeline_layout, 0, 264 cmdbuf.BindVertexBuffer(0, *buffer, offsetof(BufferData, vertices));
262 descriptor_sets[image_index], {}); 265 cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_GRAPHICS, *pipeline_layout, 0,
263 cmdbuf.Draw(4, 1, 0, 0); 266 descriptor_sets[image_index], {});
264 cmdbuf.EndRenderPass(); 267 cmdbuf.Draw(4, 1, 0, 0);
265 }); 268 cmdbuf.EndRenderPass();
269 });
266 return *semaphores[image_index]; 270 return *semaphores[image_index];
267} 271}
268 272
273VkSemaphore VKBlitScreen::DrawToSwapchain(const Tegra::FramebufferConfig& framebuffer,
274 bool use_accelerated) {
275 const std::size_t image_index = swapchain.GetImageIndex();
276 const VkExtent2D render_area = swapchain.GetSize();
277 const Layout::FramebufferLayout layout = render_window.GetFramebufferLayout();
278 return Draw(framebuffer, *framebuffers[image_index], layout, render_area, use_accelerated);
279}
280
281vk::Framebuffer VKBlitScreen::CreateFramebuffer(const VkImageView& image_view, VkExtent2D extent) {
282 return device.GetLogical().CreateFramebuffer(VkFramebufferCreateInfo{
283 .sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO,
284 .pNext = nullptr,
285 .flags = 0,
286 .renderPass = *renderpass,
287 .attachmentCount = 1,
288 .pAttachments = &image_view,
289 .width = extent.width,
290 .height = extent.height,
291 .layers = 1,
292 });
293}
294
269void VKBlitScreen::CreateStaticResources() { 295void VKBlitScreen::CreateStaticResources() {
270 CreateShaders(); 296 CreateShaders();
271 CreateSemaphores(); 297 CreateSemaphores();
@@ -609,22 +635,9 @@ void VKBlitScreen::CreateFramebuffers() {
609 const VkExtent2D size{swapchain.GetSize()}; 635 const VkExtent2D size{swapchain.GetSize()};
610 framebuffers.resize(image_count); 636 framebuffers.resize(image_count);
611 637
612 VkFramebufferCreateInfo ci{
613 .sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO,
614 .pNext = nullptr,
615 .flags = 0,
616 .renderPass = *renderpass,
617 .attachmentCount = 1,
618 .pAttachments = nullptr,
619 .width = size.width,
620 .height = size.height,
621 .layers = 1,
622 };
623
624 for (std::size_t i = 0; i < image_count; ++i) { 638 for (std::size_t i = 0; i < image_count; ++i) {
625 const VkImageView image_view{swapchain.GetImageViewIndex(i)}; 639 const VkImageView image_view{swapchain.GetImageViewIndex(i)};
626 ci.pAttachments = &image_view; 640 framebuffers[i] = CreateFramebuffer(image_view, size);
627 framebuffers[i] = device.GetLogical().CreateFramebuffer(ci);
628 } 641 }
629} 642}
630 643
@@ -752,15 +765,13 @@ void VKBlitScreen::UpdateDescriptorSet(std::size_t image_index, VkImageView imag
752 device.GetLogical().UpdateDescriptorSets(std::array{ubo_write, sampler_write}, {}); 765 device.GetLogical().UpdateDescriptorSets(std::array{ubo_write, sampler_write}, {});
753} 766}
754 767
755void VKBlitScreen::SetUniformData(BufferData& data, 768void VKBlitScreen::SetUniformData(BufferData& data, const Layout::FramebufferLayout layout) const {
756 const Tegra::FramebufferConfig& framebuffer) const {
757 const auto& layout = render_window.GetFramebufferLayout();
758 data.uniform.modelview_matrix = 769 data.uniform.modelview_matrix =
759 MakeOrthographicMatrix(static_cast<f32>(layout.width), static_cast<f32>(layout.height)); 770 MakeOrthographicMatrix(static_cast<f32>(layout.width), static_cast<f32>(layout.height));
760} 771}
761 772
762void VKBlitScreen::SetVertexData(BufferData& data, 773void VKBlitScreen::SetVertexData(BufferData& data, const Tegra::FramebufferConfig& framebuffer,
763 const Tegra::FramebufferConfig& framebuffer) const { 774 const Layout::FramebufferLayout layout) const {
764 const auto& framebuffer_transform_flags = framebuffer.transform_flags; 775 const auto& framebuffer_transform_flags = framebuffer.transform_flags;
765 const auto& framebuffer_crop_rect = framebuffer.crop_rect; 776 const auto& framebuffer_crop_rect = framebuffer.crop_rect;
766 777
@@ -798,7 +809,7 @@ void VKBlitScreen::SetVertexData(BufferData& data,
798 static_cast<f32>(screen_info.height); 809 static_cast<f32>(screen_info.height);
799 } 810 }
800 811
801 const auto& screen = render_window.GetFramebufferLayout().screen; 812 const auto& screen = layout.screen;
802 const auto x = static_cast<f32>(screen.left); 813 const auto x = static_cast<f32>(screen.left);
803 const auto y = static_cast<f32>(screen.top); 814 const auto y = static_cast<f32>(screen.top);
804 const auto w = static_cast<f32>(screen.GetWidth()); 815 const auto w = static_cast<f32>(screen.GetWidth());
diff --git a/src/video_core/renderer_vulkan/vk_blit_screen.h b/src/video_core/renderer_vulkan/vk_blit_screen.h
index 5e3177685..430bcfbca 100644
--- a/src/video_core/renderer_vulkan/vk_blit_screen.h
+++ b/src/video_core/renderer_vulkan/vk_blit_screen.h
@@ -56,8 +56,16 @@ public:
56 void Recreate(); 56 void Recreate();
57 57
58 [[nodiscard]] VkSemaphore Draw(const Tegra::FramebufferConfig& framebuffer, 58 [[nodiscard]] VkSemaphore Draw(const Tegra::FramebufferConfig& framebuffer,
59 const VkFramebuffer& host_framebuffer,
60 const Layout::FramebufferLayout layout, VkExtent2D render_area,
59 bool use_accelerated); 61 bool use_accelerated);
60 62
63 [[nodiscard]] VkSemaphore DrawToSwapchain(const Tegra::FramebufferConfig& framebuffer,
64 bool use_accelerated);
65
66 [[nodiscard]] vk::Framebuffer CreateFramebuffer(const VkImageView& image_view,
67 VkExtent2D extent);
68
61private: 69private:
62 struct BufferData; 70 struct BufferData;
63 71
@@ -81,8 +89,9 @@ private:
81 void CreateRawImages(const Tegra::FramebufferConfig& framebuffer); 89 void CreateRawImages(const Tegra::FramebufferConfig& framebuffer);
82 90
83 void UpdateDescriptorSet(std::size_t image_index, VkImageView image_view) const; 91 void UpdateDescriptorSet(std::size_t image_index, VkImageView image_view) const;
84 void SetUniformData(BufferData& data, const Tegra::FramebufferConfig& framebuffer) const; 92 void SetUniformData(BufferData& data, const Layout::FramebufferLayout layout) const;
85 void SetVertexData(BufferData& data, const Tegra::FramebufferConfig& framebuffer) const; 93 void SetVertexData(BufferData& data, const Tegra::FramebufferConfig& framebuffer,
94 const Layout::FramebufferLayout layout) const;
86 95
87 u64 CalculateBufferSize(const Tegra::FramebufferConfig& framebuffer) const; 96 u64 CalculateBufferSize(const Tegra::FramebufferConfig& framebuffer) const;
88 u64 GetRawImageOffset(const Tegra::FramebufferConfig& framebuffer, 97 u64 GetRawImageOffset(const Tegra::FramebufferConfig& framebuffer,
diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp
index c7a07fdd8..23cef2996 100644
--- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp
+++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp
@@ -61,11 +61,16 @@ struct DrawParams {
61VkViewport GetViewportState(const Device& device, const Maxwell& regs, size_t index) { 61VkViewport GetViewportState(const Device& device, const Maxwell& regs, size_t index) {
62 const auto& src = regs.viewport_transform[index]; 62 const auto& src = regs.viewport_transform[index];
63 const float width = src.scale_x * 2.0f; 63 const float width = src.scale_x * 2.0f;
64 const float height = src.scale_y * 2.0f; 64 float y = src.translate_y - src.scale_y;
65 float height = src.scale_y * 2.0f;
66 if (regs.screen_y_control.y_negate) {
67 y += height;
68 height = -height;
69 }
65 const float reduce_z = regs.depth_mode == Maxwell::DepthMode::MinusOneToOne ? 1.0f : 0.0f; 70 const float reduce_z = regs.depth_mode == Maxwell::DepthMode::MinusOneToOne ? 1.0f : 0.0f;
66 VkViewport viewport{ 71 VkViewport viewport{
67 .x = src.translate_x - src.scale_x, 72 .x = src.translate_x - src.scale_x,
68 .y = src.translate_y - src.scale_y, 73 .y = y,
69 .width = width != 0.0f ? width : 1.0f, 74 .width = width != 0.0f ? width : 1.0f,
70 .height = height != 0.0f ? height : 1.0f, 75 .height = height != 0.0f ? height : 1.0f,
71 .minDepth = src.translate_z - src.scale_z * reduce_z, 76 .minDepth = src.translate_z - src.scale_z * reduce_z,
diff --git a/src/video_core/textures/decoders.cpp b/src/video_core/textures/decoders.cpp
index f1f523ad1..c32ae956a 100644
--- a/src/video_core/textures/decoders.cpp
+++ b/src/video_core/textures/decoders.cpp
@@ -18,9 +18,9 @@
18 18
19namespace Tegra::Texture { 19namespace Tegra::Texture {
20namespace { 20namespace {
21template <bool TO_LINEAR> 21template <bool TO_LINEAR, u32 BYTES_PER_PIXEL>
22void Swizzle(std::span<u8> output, std::span<const u8> input, u32 bytes_per_pixel, u32 width, 22void SwizzleImpl(std::span<u8> output, std::span<const u8> input, u32 width, u32 height, u32 depth,
23 u32 height, u32 depth, u32 block_height, u32 block_depth, u32 stride_alignment) { 23 u32 block_height, u32 block_depth, u32 stride_alignment) {
24 // The origin of the transformation can be configured here, leave it as zero as the current API 24 // The origin of the transformation can be configured here, leave it as zero as the current API
25 // doesn't expose it. 25 // doesn't expose it.
26 static constexpr u32 origin_x = 0; 26 static constexpr u32 origin_x = 0;
@@ -28,9 +28,9 @@ void Swizzle(std::span<u8> output, std::span<const u8> input, u32 bytes_per_pixe
28 static constexpr u32 origin_z = 0; 28 static constexpr u32 origin_z = 0;
29 29
30 // We can configure here a custom pitch 30 // We can configure here a custom pitch
31 // As it's not exposed 'width * bpp' will be the expected pitch. 31 // As it's not exposed 'width * BYTES_PER_PIXEL' will be the expected pitch.
32 const u32 pitch = width * bytes_per_pixel; 32 const u32 pitch = width * BYTES_PER_PIXEL;
33 const u32 stride = Common::AlignUpLog2(width, stride_alignment) * bytes_per_pixel; 33 const u32 stride = Common::AlignUpLog2(width, stride_alignment) * BYTES_PER_PIXEL;
34 34
35 const u32 gobs_in_x = Common::DivCeilLog2(stride, GOB_SIZE_X_SHIFT); 35 const u32 gobs_in_x = Common::DivCeilLog2(stride, GOB_SIZE_X_SHIFT);
36 const u32 block_size = gobs_in_x << (GOB_SIZE_SHIFT + block_height + block_depth); 36 const u32 block_size = gobs_in_x << (GOB_SIZE_SHIFT + block_height + block_depth);
@@ -54,14 +54,14 @@ void Swizzle(std::span<u8> output, std::span<const u8> input, u32 bytes_per_pixe
54 ((block_y & block_height_mask) << GOB_SIZE_SHIFT); 54 ((block_y & block_height_mask) << GOB_SIZE_SHIFT);
55 55
56 for (u32 column = 0; column < width; ++column) { 56 for (u32 column = 0; column < width; ++column) {
57 const u32 x = (column + origin_x) * bytes_per_pixel; 57 const u32 x = (column + origin_x) * BYTES_PER_PIXEL;
58 const u32 offset_x = (x >> GOB_SIZE_X_SHIFT) << x_shift; 58 const u32 offset_x = (x >> GOB_SIZE_X_SHIFT) << x_shift;
59 59
60 const u32 base_swizzled_offset = offset_z + offset_y + offset_x; 60 const u32 base_swizzled_offset = offset_z + offset_y + offset_x;
61 const u32 swizzled_offset = base_swizzled_offset + table[x % GOB_SIZE_X]; 61 const u32 swizzled_offset = base_swizzled_offset + table[x % GOB_SIZE_X];
62 62
63 const u32 unswizzled_offset = 63 const u32 unswizzled_offset =
64 slice * pitch * height + line * pitch + column * bytes_per_pixel; 64 slice * pitch * height + line * pitch + column * BYTES_PER_PIXEL;
65 65
66 if (const auto offset = (TO_LINEAR ? unswizzled_offset : swizzled_offset); 66 if (const auto offset = (TO_LINEAR ? unswizzled_offset : swizzled_offset);
67 offset >= input.size()) { 67 offset >= input.size()) {
@@ -73,11 +73,45 @@ void Swizzle(std::span<u8> output, std::span<const u8> input, u32 bytes_per_pixe
73 73
74 u8* const dst = &output[TO_LINEAR ? swizzled_offset : unswizzled_offset]; 74 u8* const dst = &output[TO_LINEAR ? swizzled_offset : unswizzled_offset];
75 const u8* const src = &input[TO_LINEAR ? unswizzled_offset : swizzled_offset]; 75 const u8* const src = &input[TO_LINEAR ? unswizzled_offset : swizzled_offset];
76 std::memcpy(dst, src, bytes_per_pixel); 76
77 std::memcpy(dst, src, BYTES_PER_PIXEL);
77 } 78 }
78 } 79 }
79 } 80 }
80} 81}
82
83template <bool TO_LINEAR>
84void Swizzle(std::span<u8> output, std::span<const u8> input, u32 bytes_per_pixel, u32 width,
85 u32 height, u32 depth, u32 block_height, u32 block_depth, u32 stride_alignment) {
86 switch (bytes_per_pixel) {
87 case 1:
88 return SwizzleImpl<TO_LINEAR, 1>(output, input, width, height, depth, block_height,
89 block_depth, stride_alignment);
90 case 2:
91 return SwizzleImpl<TO_LINEAR, 2>(output, input, width, height, depth, block_height,
92 block_depth, stride_alignment);
93 case 3:
94 return SwizzleImpl<TO_LINEAR, 3>(output, input, width, height, depth, block_height,
95 block_depth, stride_alignment);
96 case 4:
97 return SwizzleImpl<TO_LINEAR, 4>(output, input, width, height, depth, block_height,
98 block_depth, stride_alignment);
99 case 6:
100 return SwizzleImpl<TO_LINEAR, 6>(output, input, width, height, depth, block_height,
101 block_depth, stride_alignment);
102 case 8:
103 return SwizzleImpl<TO_LINEAR, 8>(output, input, width, height, depth, block_height,
104 block_depth, stride_alignment);
105 case 12:
106 return SwizzleImpl<TO_LINEAR, 12>(output, input, width, height, depth, block_height,
107 block_depth, stride_alignment);
108 case 16:
109 return SwizzleImpl<TO_LINEAR, 16>(output, input, width, height, depth, block_height,
110 block_depth, stride_alignment);
111 default:
112 UNREACHABLE_MSG("Invalid bytes_per_pixel={}", bytes_per_pixel);
113 }
114}
81} // Anonymous namespace 115} // Anonymous namespace
82 116
83void UnswizzleTexture(std::span<u8> output, std::span<const u8> input, u32 bytes_per_pixel, 117void UnswizzleTexture(std::span<u8> output, std::span<const u8> input, u32 bytes_per_pixel,
diff --git a/src/yuzu/bootmanager.cpp b/src/yuzu/bootmanager.cpp
index 25b658b2a..2e0ade815 100644
--- a/src/yuzu/bootmanager.cpp
+++ b/src/yuzu/bootmanager.cpp
@@ -632,9 +632,9 @@ void GRenderWindow::CaptureScreenshot(u32 res_scale, const QString& screenshot_p
632 screenshot_image = QImage(QSize(layout.width, layout.height), QImage::Format_RGB32); 632 screenshot_image = QImage(QSize(layout.width, layout.height), QImage::Format_RGB32);
633 renderer.RequestScreenshot( 633 renderer.RequestScreenshot(
634 screenshot_image.bits(), 634 screenshot_image.bits(),
635 [=, this] { 635 [=, this](bool invert_y) {
636 const std::string std_screenshot_path = screenshot_path.toStdString(); 636 const std::string std_screenshot_path = screenshot_path.toStdString();
637 if (screenshot_image.mirrored(false, true).save(screenshot_path)) { 637 if (screenshot_image.mirrored(false, invert_y).save(screenshot_path)) {
638 LOG_INFO(Frontend, "Screenshot saved to \"{}\"", std_screenshot_path); 638 LOG_INFO(Frontend, "Screenshot saved to \"{}\"", std_screenshot_path);
639 } else { 639 } else {
640 LOG_ERROR(Frontend, "Failed to save screenshot to \"{}\"", std_screenshot_path); 640 LOG_ERROR(Frontend, "Failed to save screenshot to \"{}\"", std_screenshot_path);
diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp
index f3b8787f5..380379eb4 100644
--- a/src/yuzu/configuration/config.cpp
+++ b/src/yuzu/configuration/config.cpp
@@ -873,10 +873,6 @@ void Config::ReadShortcutValues() {
873void Config::ReadSystemValues() { 873void Config::ReadSystemValues() {
874 qt_config->beginGroup(QStringLiteral("System")); 874 qt_config->beginGroup(QStringLiteral("System"));
875 875
876 ReadBasicSetting(Settings::values.current_user);
877 Settings::values.current_user = std::clamp<int>(Settings::values.current_user.GetValue(), 0,
878 Service::Account::MAX_USERS - 1);
879
880 ReadGlobalSetting(Settings::values.language_index); 876 ReadGlobalSetting(Settings::values.language_index);
881 877
882 ReadGlobalSetting(Settings::values.region_index); 878 ReadGlobalSetting(Settings::values.region_index);
@@ -897,6 +893,10 @@ void Config::ReadSystemValues() {
897 } 893 }
898 894
899 if (global) { 895 if (global) {
896 ReadBasicSetting(Settings::values.current_user);
897 Settings::values.current_user = std::clamp<int>(Settings::values.current_user.GetValue(), 0,
898 Service::Account::MAX_USERS - 1);
899
900 const auto custom_rtc_enabled = 900 const auto custom_rtc_enabled =
901 ReadSetting(QStringLiteral("custom_rtc_enabled"), false).toBool(); 901 ReadSetting(QStringLiteral("custom_rtc_enabled"), false).toBool();
902 if (custom_rtc_enabled) { 902 if (custom_rtc_enabled) {
@@ -946,7 +946,8 @@ void Config::ReadUIGamelistValues() {
946 qt_config->beginGroup(QStringLiteral("UIGameList")); 946 qt_config->beginGroup(QStringLiteral("UIGameList"));
947 947
948 ReadBasicSetting(UISettings::values.show_add_ons); 948 ReadBasicSetting(UISettings::values.show_add_ons);
949 ReadBasicSetting(UISettings::values.icon_size); 949 ReadBasicSetting(UISettings::values.game_icon_size);
950 ReadBasicSetting(UISettings::values.folder_icon_size);
950 ReadBasicSetting(UISettings::values.row_1_text_id); 951 ReadBasicSetting(UISettings::values.row_1_text_id);
951 ReadBasicSetting(UISettings::values.row_2_text_id); 952 ReadBasicSetting(UISettings::values.row_2_text_id);
952 ReadBasicSetting(UISettings::values.cache_game_list); 953 ReadBasicSetting(UISettings::values.cache_game_list);
@@ -1405,7 +1406,6 @@ void Config::SaveShortcutValues() {
1405void Config::SaveSystemValues() { 1406void Config::SaveSystemValues() {
1406 qt_config->beginGroup(QStringLiteral("System")); 1407 qt_config->beginGroup(QStringLiteral("System"));
1407 1408
1408 WriteBasicSetting(Settings::values.current_user);
1409 WriteGlobalSetting(Settings::values.language_index); 1409 WriteGlobalSetting(Settings::values.language_index);
1410 WriteGlobalSetting(Settings::values.region_index); 1410 WriteGlobalSetting(Settings::values.region_index);
1411 WriteGlobalSetting(Settings::values.time_zone_index); 1411 WriteGlobalSetting(Settings::values.time_zone_index);
@@ -1417,6 +1417,8 @@ void Config::SaveSystemValues() {
1417 0, Settings::values.rng_seed.UsingGlobal()); 1417 0, Settings::values.rng_seed.UsingGlobal());
1418 1418
1419 if (global) { 1419 if (global) {
1420 WriteBasicSetting(Settings::values.current_user);
1421
1420 WriteSetting(QStringLiteral("custom_rtc_enabled"), Settings::values.custom_rtc.has_value(), 1422 WriteSetting(QStringLiteral("custom_rtc_enabled"), Settings::values.custom_rtc.has_value(),
1421 false); 1423 false);
1422 WriteSetting(QStringLiteral("custom_rtc"), 1424 WriteSetting(QStringLiteral("custom_rtc"),
@@ -1463,7 +1465,8 @@ void Config::SaveUIGamelistValues() {
1463 qt_config->beginGroup(QStringLiteral("UIGameList")); 1465 qt_config->beginGroup(QStringLiteral("UIGameList"));
1464 1466
1465 WriteBasicSetting(UISettings::values.show_add_ons); 1467 WriteBasicSetting(UISettings::values.show_add_ons);
1466 WriteBasicSetting(UISettings::values.icon_size); 1468 WriteBasicSetting(UISettings::values.game_icon_size);
1469 WriteBasicSetting(UISettings::values.folder_icon_size);
1467 WriteBasicSetting(UISettings::values.row_1_text_id); 1470 WriteBasicSetting(UISettings::values.row_1_text_id);
1468 WriteBasicSetting(UISettings::values.row_2_text_id); 1471 WriteBasicSetting(UISettings::values.row_2_text_id);
1469 WriteBasicSetting(UISettings::values.cache_game_list); 1472 WriteBasicSetting(UISettings::values.cache_game_list);
diff --git a/src/yuzu/configuration/configure_ui.cpp b/src/yuzu/configuration/configure_ui.cpp
index e8f41bf65..9d7d51126 100644
--- a/src/yuzu/configuration/configure_ui.cpp
+++ b/src/yuzu/configuration/configure_ui.cpp
@@ -16,7 +16,7 @@
16#include "yuzu/uisettings.h" 16#include "yuzu/uisettings.h"
17 17
18namespace { 18namespace {
19constexpr std::array default_icon_sizes{ 19constexpr std::array default_game_icon_sizes{
20 std::make_pair(0, QT_TRANSLATE_NOOP("ConfigureUI", "None")), 20 std::make_pair(0, QT_TRANSLATE_NOOP("ConfigureUI", "None")),
21 std::make_pair(32, QT_TRANSLATE_NOOP("ConfigureUI", "Small (32x32)")), 21 std::make_pair(32, QT_TRANSLATE_NOOP("ConfigureUI", "Small (32x32)")),
22 std::make_pair(64, QT_TRANSLATE_NOOP("ConfigureUI", "Standard (64x64)")), 22 std::make_pair(64, QT_TRANSLATE_NOOP("ConfigureUI", "Standard (64x64)")),
@@ -24,6 +24,13 @@ constexpr std::array default_icon_sizes{
24 std::make_pair(256, QT_TRANSLATE_NOOP("ConfigureUI", "Full Size (256x256)")), 24 std::make_pair(256, QT_TRANSLATE_NOOP("ConfigureUI", "Full Size (256x256)")),
25}; 25};
26 26
27constexpr std::array default_folder_icon_sizes{
28 std::make_pair(0, QT_TRANSLATE_NOOP("ConfigureUI", "None")),
29 std::make_pair(24, QT_TRANSLATE_NOOP("ConfigureUI", "Small (24x24)")),
30 std::make_pair(48, QT_TRANSLATE_NOOP("ConfigureUI", "Standard (48x48)")),
31 std::make_pair(72, QT_TRANSLATE_NOOP("ConfigureUI", "Large (72x72)")),
32};
33
27// clang-format off 34// clang-format off
28constexpr std::array row_text_names{ 35constexpr std::array row_text_names{
29 QT_TRANSLATE_NOOP("ConfigureUI", "Filename"), 36 QT_TRANSLATE_NOOP("ConfigureUI", "Filename"),
@@ -34,8 +41,12 @@ constexpr std::array row_text_names{
34}; 41};
35// clang-format on 42// clang-format on
36 43
37QString GetTranslatedIconSize(size_t index) { 44QString GetTranslatedGameIconSize(size_t index) {
38 return QCoreApplication::translate("ConfigureUI", default_icon_sizes[index].second); 45 return QCoreApplication::translate("ConfigureUI", default_game_icon_sizes[index].second);
46}
47
48QString GetTranslatedFolderIconSize(size_t index) {
49 return QCoreApplication::translate("ConfigureUI", default_folder_icon_sizes[index].second);
39} 50}
40 51
41QString GetTranslatedRowTextName(size_t index) { 52QString GetTranslatedRowTextName(size_t index) {
@@ -60,8 +71,10 @@ ConfigureUi::ConfigureUi(QWidget* parent) : QWidget(parent), ui(new Ui::Configur
60 71
61 // Force game list reload if any of the relevant settings are changed. 72 // Force game list reload if any of the relevant settings are changed.
62 connect(ui->show_add_ons, &QCheckBox::stateChanged, this, &ConfigureUi::RequestGameListUpdate); 73 connect(ui->show_add_ons, &QCheckBox::stateChanged, this, &ConfigureUi::RequestGameListUpdate);
63 connect(ui->icon_size_combobox, QOverload<int>::of(&QComboBox::currentIndexChanged), this, 74 connect(ui->game_icon_size_combobox, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
64 &ConfigureUi::RequestGameListUpdate); 75 &ConfigureUi::RequestGameListUpdate);
76 connect(ui->folder_icon_size_combobox, QOverload<int>::of(&QComboBox::currentIndexChanged),
77 this, &ConfigureUi::RequestGameListUpdate);
65 connect(ui->row_1_text_combobox, QOverload<int>::of(&QComboBox::currentIndexChanged), this, 78 connect(ui->row_1_text_combobox, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
66 &ConfigureUi::RequestGameListUpdate); 79 &ConfigureUi::RequestGameListUpdate);
67 connect(ui->row_2_text_combobox, QOverload<int>::of(&QComboBox::currentIndexChanged), this, 80 connect(ui->row_2_text_combobox, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
@@ -95,7 +108,8 @@ void ConfigureUi::ApplyConfiguration() {
95 UISettings::values.theme = 108 UISettings::values.theme =
96 ui->theme_combobox->itemData(ui->theme_combobox->currentIndex()).toString(); 109 ui->theme_combobox->itemData(ui->theme_combobox->currentIndex()).toString();
97 UISettings::values.show_add_ons = ui->show_add_ons->isChecked(); 110 UISettings::values.show_add_ons = ui->show_add_ons->isChecked();
98 UISettings::values.icon_size = ui->icon_size_combobox->currentData().toUInt(); 111 UISettings::values.game_icon_size = ui->game_icon_size_combobox->currentData().toUInt();
112 UISettings::values.folder_icon_size = ui->folder_icon_size_combobox->currentData().toUInt();
99 UISettings::values.row_1_text_id = ui->row_1_text_combobox->currentData().toUInt(); 113 UISettings::values.row_1_text_id = ui->row_1_text_combobox->currentData().toUInt();
100 UISettings::values.row_2_text_id = ui->row_2_text_combobox->currentData().toUInt(); 114 UISettings::values.row_2_text_id = ui->row_2_text_combobox->currentData().toUInt();
101 115
@@ -114,8 +128,10 @@ void ConfigureUi::SetConfiguration() {
114 ui->language_combobox->setCurrentIndex( 128 ui->language_combobox->setCurrentIndex(
115 ui->language_combobox->findData(UISettings::values.language)); 129 ui->language_combobox->findData(UISettings::values.language));
116 ui->show_add_ons->setChecked(UISettings::values.show_add_ons.GetValue()); 130 ui->show_add_ons->setChecked(UISettings::values.show_add_ons.GetValue());
117 ui->icon_size_combobox->setCurrentIndex( 131 ui->game_icon_size_combobox->setCurrentIndex(
118 ui->icon_size_combobox->findData(UISettings::values.icon_size.GetValue())); 132 ui->game_icon_size_combobox->findData(UISettings::values.game_icon_size.GetValue()));
133 ui->folder_icon_size_combobox->setCurrentIndex(
134 ui->folder_icon_size_combobox->findData(UISettings::values.folder_icon_size.GetValue()));
119 135
120 ui->enable_screenshot_save_as->setChecked( 136 ui->enable_screenshot_save_as->setChecked(
121 UISettings::values.enable_screenshot_save_as.GetValue()); 137 UISettings::values.enable_screenshot_save_as.GetValue());
@@ -134,8 +150,14 @@ void ConfigureUi::changeEvent(QEvent* event) {
134void ConfigureUi::RetranslateUI() { 150void ConfigureUi::RetranslateUI() {
135 ui->retranslateUi(this); 151 ui->retranslateUi(this);
136 152
137 for (int i = 0; i < ui->icon_size_combobox->count(); i++) { 153 for (int i = 0; i < ui->game_icon_size_combobox->count(); i++) {
138 ui->icon_size_combobox->setItemText(i, GetTranslatedIconSize(static_cast<size_t>(i))); 154 ui->game_icon_size_combobox->setItemText(i,
155 GetTranslatedGameIconSize(static_cast<size_t>(i)));
156 }
157
158 for (int i = 0; i < ui->folder_icon_size_combobox->count(); i++) {
159 ui->folder_icon_size_combobox->setItemText(
160 i, GetTranslatedFolderIconSize(static_cast<size_t>(i)));
139 } 161 }
140 162
141 for (int i = 0; i < ui->row_1_text_combobox->count(); i++) { 163 for (int i = 0; i < ui->row_1_text_combobox->count(); i++) {
@@ -166,9 +188,13 @@ void ConfigureUi::InitializeLanguageComboBox() {
166} 188}
167 189
168void ConfigureUi::InitializeIconSizeComboBox() { 190void ConfigureUi::InitializeIconSizeComboBox() {
169 for (size_t i = 0; i < default_icon_sizes.size(); i++) { 191 for (size_t i = 0; i < default_game_icon_sizes.size(); i++) {
170 const auto size = default_icon_sizes[i].first; 192 const auto size = default_game_icon_sizes[i].first;
171 ui->icon_size_combobox->addItem(GetTranslatedIconSize(i), size); 193 ui->game_icon_size_combobox->addItem(GetTranslatedGameIconSize(i), size);
194 }
195 for (size_t i = 0; i < default_folder_icon_sizes.size(); i++) {
196 const auto size = default_folder_icon_sizes[i].first;
197 ui->folder_icon_size_combobox->addItem(GetTranslatedFolderIconSize(i), size);
172 } 198 }
173} 199}
174 200
diff --git a/src/yuzu/configuration/configure_ui.ui b/src/yuzu/configuration/configure_ui.ui
index d895b799f..394f9fe04 100644
--- a/src/yuzu/configuration/configure_ui.ui
+++ b/src/yuzu/configuration/configure_ui.ui
@@ -81,16 +81,30 @@
81 </widget> 81 </widget>
82 </item> 82 </item>
83 <item> 83 <item>
84 <layout class="QHBoxLayout" name="icon_size_qhbox_layout_2"> 84 <layout class="QHBoxLayout" name="game_icon_size_qhbox_layout_2">
85 <item> 85 <item>
86 <widget class="QLabel" name="icon_size_label"> 86 <widget class="QLabel" name="game_icon_size_label">
87 <property name="text"> 87 <property name="text">
88 <string>Icon Size:</string> 88 <string>Game Icon Size:</string>
89 </property> 89 </property>
90 </widget> 90 </widget>
91 </item> 91 </item>
92 <item> 92 <item>
93 <widget class="QComboBox" name="icon_size_combobox"/> 93 <widget class="QComboBox" name="game_icon_size_combobox"/>
94 </item>
95 </layout>
96 </item>
97 <item>
98 <layout class="QHBoxLayout" name="folder_icon_size_qhbox_layout_2">
99 <item>
100 <widget class="QLabel" name="folder_icon_size_label">
101 <property name="text">
102 <string>Folder Icon Size:</string>
103 </property>
104 </widget>
105 </item>
106 <item>
107 <widget class="QComboBox" name="folder_icon_size_combobox"/>
94 </item> 108 </item>
95 </layout> 109 </layout>
96 </item> 110 </item>
diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp
index f746bd85d..e97804220 100644
--- a/src/yuzu/game_list.cpp
+++ b/src/yuzu/game_list.cpp
@@ -244,8 +244,8 @@ void GameList::OnUpdateThemedIcons() {
244 for (int i = 0; i < item_model->invisibleRootItem()->rowCount(); i++) { 244 for (int i = 0; i < item_model->invisibleRootItem()->rowCount(); i++) {
245 QStandardItem* child = item_model->invisibleRootItem()->child(i); 245 QStandardItem* child = item_model->invisibleRootItem()->child(i);
246 246
247 const int icon_size = 247 const int icon_size = UISettings::values.folder_icon_size.GetValue();
248 std::min(static_cast<int>(UISettings::values.icon_size.GetValue()), 64); 248
249 switch (child->data(GameListItem::TypeRole).value<GameListItemType>()) { 249 switch (child->data(GameListItem::TypeRole).value<GameListItemType>()) {
250 case GameListItemType::SdmcDir: 250 case GameListItemType::SdmcDir:
251 child->setData( 251 child->setData(
diff --git a/src/yuzu/game_list_p.h b/src/yuzu/game_list_p.h
index 982c0789d..9dc3cc7c3 100644
--- a/src/yuzu/game_list_p.h
+++ b/src/yuzu/game_list_p.h
@@ -80,7 +80,7 @@ public:
80 setData(qulonglong(program_id), ProgramIdRole); 80 setData(qulonglong(program_id), ProgramIdRole);
81 setData(game_type, FileTypeRole); 81 setData(game_type, FileTypeRole);
82 82
83 const u32 size = UISettings::values.icon_size.GetValue(); 83 const u32 size = UISettings::values.game_icon_size.GetValue();
84 84
85 QPixmap picture; 85 QPixmap picture;
86 if (!picture.loadFromData(picture_data.data(), static_cast<u32>(picture_data.size()))) { 86 if (!picture.loadFromData(picture_data.data(), static_cast<u32>(picture_data.size()))) {
@@ -233,8 +233,7 @@ public:
233 UISettings::GameDir* game_dir = &directory; 233 UISettings::GameDir* game_dir = &directory;
234 setData(QVariant(UISettings::values.game_dirs.indexOf(directory)), GameDirRole); 234 setData(QVariant(UISettings::values.game_dirs.indexOf(directory)), GameDirRole);
235 235
236 const int icon_size = 236 const int icon_size = UISettings::values.folder_icon_size.GetValue();
237 std::min(static_cast<int>(UISettings::values.icon_size.GetValue()), 64);
238 switch (dir_type) { 237 switch (dir_type) {
239 case GameListItemType::SdmcDir: 238 case GameListItemType::SdmcDir:
240 setData( 239 setData(
@@ -295,8 +294,8 @@ public:
295 explicit GameListAddDir() { 294 explicit GameListAddDir() {
296 setData(type(), TypeRole); 295 setData(type(), TypeRole);
297 296
298 const int icon_size = 297 const int icon_size = UISettings::values.folder_icon_size.GetValue();
299 std::min(static_cast<int>(UISettings::values.icon_size.GetValue()), 64); 298
300 setData(QIcon::fromTheme(QStringLiteral("plus")) 299 setData(QIcon::fromTheme(QStringLiteral("plus"))
301 .pixmap(icon_size) 300 .pixmap(icon_size)
302 .scaled(icon_size, icon_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation), 301 .scaled(icon_size, icon_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation),
@@ -318,8 +317,8 @@ public:
318 explicit GameListFavorites() { 317 explicit GameListFavorites() {
319 setData(type(), TypeRole); 318 setData(type(), TypeRole);
320 319
321 const int icon_size = 320 const int icon_size = UISettings::values.folder_icon_size.GetValue();
322 std::min(static_cast<int>(UISettings::values.icon_size.GetValue()), 64); 321
323 setData(QIcon::fromTheme(QStringLiteral("star")) 322 setData(QIcon::fromTheme(QStringLiteral("star"))
324 .pixmap(icon_size) 323 .pixmap(icon_size)
325 .scaled(icon_size, icon_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation), 324 .scaled(icon_size, icon_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation),
diff --git a/src/yuzu/uisettings.h b/src/yuzu/uisettings.h
index 7b9d2dd53..81f741f20 100644
--- a/src/yuzu/uisettings.h
+++ b/src/yuzu/uisettings.h
@@ -91,7 +91,8 @@ struct Values {
91 91
92 // Game List 92 // Game List
93 Settings::BasicSetting<bool> show_add_ons{true, "show_add_ons"}; 93 Settings::BasicSetting<bool> show_add_ons{true, "show_add_ons"};
94 Settings::BasicSetting<uint32_t> icon_size{64, "icon_size"}; 94 Settings::BasicSetting<uint32_t> game_icon_size{64, "game_icon_size"};
95 Settings::BasicSetting<uint32_t> folder_icon_size{48, "folder_icon_size"};
95 Settings::BasicSetting<uint8_t> row_1_text_id{3, "row_1_text_id"}; 96 Settings::BasicSetting<uint8_t> row_1_text_id{3, "row_1_text_id"};
96 Settings::BasicSetting<uint8_t> row_2_text_id{2, "row_2_text_id"}; 97 Settings::BasicSetting<uint8_t> row_2_text_id{2, "row_2_text_id"};
97 std::atomic_bool is_game_list_reload_pending{false}; 98 std::atomic_bool is_game_list_reload_pending{false};
diff --git a/src/yuzu_cmd/config.cpp b/src/yuzu_cmd/config.cpp
index 064ecaafa..4f14be524 100644
--- a/src/yuzu_cmd/config.cpp
+++ b/src/yuzu_cmd/config.cpp
@@ -278,6 +278,9 @@ void Config::ReadValues() {
278 if (Settings::values.players.GetValue()[p].analogs[i].empty()) 278 if (Settings::values.players.GetValue()[p].analogs[i].empty())
279 Settings::values.players.GetValue()[p].analogs[i] = default_param; 279 Settings::values.players.GetValue()[p].analogs[i] = default_param;
280 } 280 }
281
282 Settings::values.players.GetValue()[p].connected =
283 sdl2_config->GetBoolean(group, "connected", false);
281 } 284 }
282 285
283 ReadSetting("ControlsGeneral", Settings::values.mouse_enabled); 286 ReadSetting("ControlsGeneral", Settings::values.mouse_enabled);