diff options
Diffstat (limited to '')
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 .. \ -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON \ -DENABLE_QT_TRANSLATION=ON \ -DUSE_DISCORD_PRESENCE=ON \ - -DYUZU_ENABLE_COMPATIBILITY_REPORTING=${ENABLE_COMPATIBILITY_REPORTING:-"OFF"} + -DYUZU_ENABLE_COMPATIBILITY_REPORTING=${ENABLE_COMPATIBILITY_REPORTING:-"OFF"} \ + -DYUZU_USE_BUNDLED_FFMPEG=ON make -j$(nproc) 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) option(YUZU_USE_BUNDLED_LIBUSB "Compile bundled libusb" OFF) -CMAKE_DEPENDENT_OPTION(YUZU_USE_BUNDLED_FFMPEG "Download/Build bundled FFmpeg" ON "WIN32" OFF) +option(YUZU_USE_BUNDLED_FFMPEG "Download/Build bundled FFmpeg" "${WIN32}") option(YUZU_USE_QT_WEB_ENGINE "Use QtWebEngine for web applet implementation" OFF) @@ -583,8 +583,32 @@ if (YUZU_USE_BUNDLED_FFMPEG) "${FFmpeg_PREFIX};${FFmpeg_BUILD_DIR}" CACHE PATH "Path to FFmpeg headers" FORCE) + if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux") + Include(FindPkgConfig REQUIRED) + pkg_check_modules(LIBVA libva) + endif() + if(LIBVA_FOUND) + pkg_check_modules(LIBDRM libdrm REQUIRED) + find_package(X11 REQUIRED) + pkg_check_modules(LIBVA-DRM libva-drm REQUIRED) + pkg_check_modules(LIBVA-X11 libva-x11 REQUIRED) + set(FFmpeg_LIBVA_LIBRARIES + ${LIBDRM_LIBRARIES} + ${X11_LIBRARIES} + ${LIBVA-DRM_LIBRARIES} + ${LIBVA-X11_LIBRARIES} + ${LIBVA_LIBRARIES}) + set(FFmpeg_HWACCEL_FLAGS + --enable-hwaccel=h264_vaapi + --enable-hwaccel=vp9_vaapi + --enable-libdrm) + message(STATUS "VA-API found") + else() + set(FFmpeg_HWACCEL_FLAGS --disable-vaapi) + endif() + # `configure` parameters builds only exactly what yuzu needs from FFmpeg - # `--disable-{vaapi,vdpau}` is needed to avoid linking issues + # `--disable-vdpau` is needed to avoid linking issues add_custom_command( OUTPUT ${FFmpeg_MAKEFILE} @@ -600,13 +624,16 @@ if (YUZU_USE_BUNDLED_FFMPEG) --disable-network --disable-postproc --disable-swresample - --disable-vaapi --disable-vdpau --enable-decoder=h264 --enable-decoder=vp9 + --cc="${CMAKE_C_COMPILER}" + --cxx="${CMAKE_CXX_COMPILER}" + ${FFmpeg_HWACCEL_FLAGS} WORKING_DIRECTORY ${FFmpeg_BUILD_DIR} ) + unset(FFmpeg_HWACCEL_FLAGS) # Workaround for Ubuntu 18.04's older version of make not being able to call make as a child # with context of the jobserver. Also helps ninja users. @@ -616,9 +643,10 @@ if (YUZU_USE_BUNDLED_FFMPEG) OUTPUT_VARIABLE SYSTEM_THREADS) + set(FFmpeg_BUILD_LIBRARIES ${FFmpeg_LIBRARIES}) add_custom_command( OUTPUT - ${FFmpeg_LIBRARIES} + ${FFmpeg_BUILD_LIBRARIES} COMMAND make -j${SYSTEM_THREADS} WORKING_DIRECTORY @@ -628,7 +656,12 @@ if (YUZU_USE_BUNDLED_FFMPEG) # ALL makes this custom target build every time # but it won't actually build if the DEPENDS parameter is up to date add_custom_target(ffmpeg-configure ALL DEPENDS ${FFmpeg_MAKEFILE}) - add_custom_target(ffmpeg-build ALL DEPENDS ${FFmpeg_LIBRARIES} ffmpeg-configure) + add_custom_target(ffmpeg-build ALL DEPENDS ${FFmpeg_BUILD_LIBRARIES} ffmpeg-configure) + link_libraries(${FFmpeg_LIBVA_LIBRARIES}) + set(FFmpeg_LIBRARIES ${FFmpeg_LIBVA_LIBRARIES} ${FFmpeg_BUILD_LIBRARIES} + CACHE PATH "Paths to FFmpeg libraries" FORCE) + unset(FFmpeg_BUILD_LIBRARIES) + unset(FFmpeg_LIBVA_LIBRARIES) if (FFmpeg_FOUND) 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) "${LIBUSB_MAKEFILE}" COMMAND env + CC="${CMAKE_C_COMPILER}" + CXX="${CMAKE_CXX_COMPILER}" CFLAGS="${LIBUSB_CFLAGS}" sh "${LIBUSB_CONFIGURE}" ${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) { #define DEBUG_ASSERT(_a_) ASSERT(_a_) #define DEBUG_ASSERT_MSG(_a_, ...) ASSERT_MSG(_a_, __VA_ARGS__) #else // not debug -#define DEBUG_ASSERT(_a_) -#define DEBUG_ASSERT_MSG(_a_, _desc_, ...) +#define DEBUG_ASSERT(_a_) \ + do { \ + } while (0) +#define DEBUG_ASSERT_MSG(_a_, _desc_, ...) \ + do { \ + } while (0) #endif #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> return out; } -[[nodiscard]] constexpr std::array<u8, 16> AsArray(const char (&data)[17]) { +[[nodiscard]] constexpr std::array<u8, 16> AsArray(const char (&data)[33]) { return HexStringToArray<16>(data); } 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 @@ #include <fmt/format.h> +#include "common/assert.h" #include "common/uuid.h" namespace Common { +namespace { + +bool IsHexDigit(char c) { + return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'); +} + +u8 HexCharToByte(char c) { + if (c >= '0' && c <= '9') { + return static_cast<u8>(c - '0'); + } + if (c >= 'a' && c <= 'f') { + return static_cast<u8>(c - 'a' + 10); + } + if (c >= 'A' && c <= 'F') { + return static_cast<u8>(c - 'A' + 10); + } + ASSERT_MSG(false, "{} is not a hexadecimal digit!", c); + return u8{0}; +} + +} // Anonymous namespace + +u128 HexStringToU128(std::string_view hex_string) { + const size_t length = hex_string.length(); + + // Detect "0x" prefix. + const bool has_0x_prefix = length > 2 && hex_string[0] == '0' && hex_string[1] == 'x'; + const size_t offset = has_0x_prefix ? 2 : 0; + + // Check length. + if (length > 32 + offset) { + ASSERT_MSG(false, "hex_string has more than 32 hexadecimal characters!"); + return INVALID_UUID; + } + + u64 lo = 0; + u64 hi = 0; + for (size_t i = 0; i < length - offset; ++i) { + const char c = hex_string[length - 1 - i]; + if (!IsHexDigit(c)) { + ASSERT_MSG(false, "{} is not a hexadecimal digit!", c); + return INVALID_UUID; + } + if (i < 16) { + lo |= u64{HexCharToByte(c)} << (i * 4); + } + if (i >= 16) { + hi |= u64{HexCharToByte(c)} << ((i - 16) * 4); + } + } + return u128{lo, hi}; +} + UUID UUID::Generate() { std::random_device device; 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 @@ #pragma once #include <string> +#include <string_view> #include "common/common_types.h" @@ -12,12 +13,30 @@ namespace Common { constexpr u128 INVALID_UUID{{0, 0}}; +/** + * Converts a hex string to a 128-bit unsigned integer. + * + * The hex string can be formatted in lowercase or uppercase, with or without the "0x" prefix. + * + * This function will assert and return INVALID_UUID under the following conditions: + * - If the hex string is more than 32 characters long + * - If the hex string contains non-hexadecimal characters + * + * @param hex_string Hexadecimal string + * + * @returns A 128-bit unsigned integer if successfully converted, INVALID_UUID otherwise. + */ +[[nodiscard]] u128 HexStringToU128(std::string_view hex_string); + struct UUID { // UUIDs which are 0 are considered invalid! u128 uuid; UUID() = default; constexpr explicit UUID(const u128& id) : uuid{id} {} constexpr explicit UUID(const u64 lo, const u64 hi) : uuid{{lo, hi}} {} + explicit UUID(std::string_view hex_string) { + uuid = HexStringToU128(hex_string); + } [[nodiscard]] constexpr explicit operator bool() const { 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) { if (swkbd_config_common.use_utf8) { std::string utf8_submitted_text = Common::UTF16ToUTF8(current_text); - const u64 buffer_size = utf8_submitted_text.size(); + // Include the null terminator in the buffer size. + const u64 buffer_size = utf8_submitted_text.size() + 1; LOG_DEBUG(Service_AM, "\nBuffer Size: {}\nUTF-8 Submitted Text: {}", buffer_size, utf8_submitted_text); @@ -386,7 +387,8 @@ void SoftwareKeyboard::SubmitForTextCheck(std::u16string submitted_text) { std::memcpy(out_data.data() + sizeof(u64), utf8_submitted_text.data(), utf8_submitted_text.size()); } else { - const u64 buffer_size = current_text.size() * sizeof(char16_t); + // Include the null terminator in the buffer size. + const u64 buffer_size = (current_text.size() + 1) * sizeof(char16_t); LOG_DEBUG(Service_AM, "\nBuffer Size: {}\nUTF-16 Submitted Text: {}", buffer_size, 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 LOG_ERROR(Service_NVDRV, "failed to map size={}", object->size); } else { cmd_buffer.map_address = object->dma_map_addr; - AddBufferMap(object->dma_map_addr, object->size, object->addr, - object->status == nvmap::Object::Status::Allocated); } } std::memcpy(output.data(), ¶ms, sizeof(IoctlMapBuffer)); @@ -178,30 +176,11 @@ NvResult nvhost_nvdec_common::MapBuffer(const std::vector<u8>& input, std::vecto } NvResult nvhost_nvdec_common::UnmapBuffer(const std::vector<u8>& input, std::vector<u8>& output) { - IoctlMapBuffer params{}; - std::memcpy(¶ms, input.data(), sizeof(IoctlMapBuffer)); - std::vector<MapBufferEntry> cmd_buffer_handles(params.num_entries); - SliceVectors(input, cmd_buffer_handles, params.num_entries, sizeof(IoctlMapBuffer)); - - auto& gpu = system.GPU(); - - for (auto& cmd_buffer : cmd_buffer_handles) { - const auto object{nvmap_dev->GetObject(cmd_buffer.map_handle)}; - if (!object) { - LOG_ERROR(Service_NVDRV, "invalid cmd_buffer nvmap_handle={:X}", cmd_buffer.map_handle); - std::memcpy(output.data(), ¶ms, output.size()); - return NvResult::InvalidState; - } - if (const auto size{RemoveBufferMap(object->dma_map_addr)}; size) { - gpu.MemoryManager().Unmap(object->dma_map_addr, *size); - } else { - // This occurs quite frequently, however does not seem to impact functionality - LOG_DEBUG(Service_NVDRV, "invalid offset=0x{:X} dma=0x{:X}", object->addr, - object->dma_map_addr); - } - object->dma_map_addr = 0; - } + // This is intntionally stubbed. + // Skip unmapping buffers here, as to not break the continuity of the VP9 reference frame + // addresses, and risk invalidating data before the async GPU thread is done with it std::memset(output.data(), 0, output.size()); + LOG_DEBUG(Service_NVDRV, "(STUBBED) called"); return NvResult::Success; } @@ -212,33 +191,4 @@ NvResult nvhost_nvdec_common::SetSubmitTimeout(const std::vector<u8>& input, return NvResult::Success; } -std::optional<nvhost_nvdec_common::BufferMap> nvhost_nvdec_common::FindBufferMap( - GPUVAddr gpu_addr) const { - const auto it = std::find_if( - buffer_mappings.begin(), buffer_mappings.upper_bound(gpu_addr), [&](const auto& entry) { - return (gpu_addr >= entry.second.StartAddr() && gpu_addr < entry.second.EndAddr()); - }); - - ASSERT(it != buffer_mappings.end()); - return it->second; -} - -void nvhost_nvdec_common::AddBufferMap(GPUVAddr gpu_addr, std::size_t size, VAddr cpu_addr, - bool is_allocated) { - buffer_mappings.insert_or_assign(gpu_addr, BufferMap{gpu_addr, size, cpu_addr, is_allocated}); -} - -std::optional<std::size_t> nvhost_nvdec_common::RemoveBufferMap(GPUVAddr gpu_addr) { - const auto iter{buffer_mappings.find(gpu_addr)}; - if (iter == buffer_mappings.end()) { - return std::nullopt; - } - std::size_t size = 0; - if (iter->second.IsAllocated()) { - size = iter->second.Size(); - } - buffer_mappings.erase(iter); - return size; -} - } // 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: ~nvhost_nvdec_common() override; protected: - class BufferMap final { - public: - constexpr BufferMap() = default; - - constexpr BufferMap(GPUVAddr start_addr_, std::size_t size_) - : start_addr{start_addr_}, end_addr{start_addr_ + size_} {} - - constexpr BufferMap(GPUVAddr start_addr_, std::size_t size_, VAddr cpu_addr_, - bool is_allocated_) - : start_addr{start_addr_}, end_addr{start_addr_ + size_}, cpu_addr{cpu_addr_}, - is_allocated{is_allocated_} {} - - constexpr VAddr StartAddr() const { - return start_addr; - } - - constexpr VAddr EndAddr() const { - return end_addr; - } - - constexpr std::size_t Size() const { - return end_addr - start_addr; - } - - constexpr VAddr CpuAddr() const { - return cpu_addr; - } - - constexpr bool IsAllocated() const { - return is_allocated; - } - - private: - GPUVAddr start_addr{}; - GPUVAddr end_addr{}; - VAddr cpu_addr{}; - bool is_allocated{}; - }; - struct IoctlSetNvmapFD { s32_le nvmap_fd{}; }; @@ -154,17 +115,11 @@ protected: NvResult UnmapBuffer(const std::vector<u8>& input, std::vector<u8>& output); NvResult SetSubmitTimeout(const std::vector<u8>& input, std::vector<u8>& output); - std::optional<BufferMap> FindBufferMap(GPUVAddr gpu_addr) const; - void AddBufferMap(GPUVAddr gpu_addr, std::size_t size, VAddr cpu_addr, bool is_allocated); - std::optional<std::size_t> RemoveBufferMap(GPUVAddr gpu_addr); - s32_le nvmap_fd{}; u32_le submit_timeout{}; std::shared_ptr<nvmap> nvmap_dev; SyncpointManager& syncpoint_manager; std::array<u32, MaxSyncPoints> device_syncpoints{}; - // This is expected to be ordered, therefore we must use a map, not unordered_map - std::map<GPUVAddr, BufferMap> buffer_mappings; }; }; // namespace Devices } // 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 void GetAvailableLanguageCodesImpl(Kernel::HLERequestContext& ctx, std::size_t max_entries) { const std::size_t requested_amount = ctx.GetWriteBufferSize() / sizeof(LanguageCode); - const std::size_t copy_amount = std::min(requested_amount, max_entries); + const std::size_t max_amount = std::min(requested_amount, max_entries); + const std::size_t copy_amount = std::min(available_language_codes.size(), max_amount); const std::size_t copy_size = copy_amount * sizeof(LanguageCode); 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, ASSERT(flags == 0); const sockaddr* to = nullptr; - const int tolen = addr ? 0 : sizeof(sockaddr); + const int tolen = addr ? sizeof(sockaddr) : 0; sockaddr host_addr_in; 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 @@ add_subdirectory(host_shaders) +if(LIBVA_FOUND) + set_source_files_properties(command_classes/codecs/codec.cpp + PROPERTIES COMPILE_DEFINITIONS LIBVA_FOUND=1) +endif() + add_library(video_core STATIC buffer_cache/buffer_base.h 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 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include <cstring> #include <fstream> #include <vector> #include "common/assert.h" @@ -17,10 +16,47 @@ extern "C" { } namespace Tegra { +#if defined(LIBVA_FOUND) +// Hardware acceleration code from FFmpeg/doc/examples/hw_decode.c originally under MIT license +namespace { +constexpr std::array<const char*, 2> VAAPI_DRIVERS = { + "i915", + "amdgpu", +}; + +AVPixelFormat GetHwFormat(AVCodecContext*, const AVPixelFormat* pix_fmts) { + for (const AVPixelFormat* p = pix_fmts; *p != AV_PIX_FMT_NONE; ++p) { + if (*p == AV_PIX_FMT_VAAPI) { + return AV_PIX_FMT_VAAPI; + } + } + LOG_INFO(Service_NVDRV, "Could not find compatible GPU AV format, falling back to CPU"); + return *pix_fmts; +} + +bool CreateVaapiHwdevice(AVBufferRef** av_hw_device) { + AVDictionary* hwdevice_options = nullptr; + av_dict_set(&hwdevice_options, "connection_type", "drm", 0); + for (const auto& driver : VAAPI_DRIVERS) { + av_dict_set(&hwdevice_options, "kernel_driver", driver, 0); + const int hwdevice_error = av_hwdevice_ctx_create(av_hw_device, AV_HWDEVICE_TYPE_VAAPI, + nullptr, hwdevice_options, 0); + if (hwdevice_error >= 0) { + LOG_INFO(Service_NVDRV, "Using VA-API with {}", driver); + av_dict_free(&hwdevice_options); + return true; + } + LOG_DEBUG(Service_NVDRV, "VA-API av_hwdevice_ctx_create failed {}", hwdevice_error); + } + LOG_DEBUG(Service_NVDRV, "VA-API av_hwdevice_ctx_create failed for all drivers"); + av_dict_free(&hwdevice_options); + return false; +} +} // namespace +#endif void AVFrameDeleter(AVFrame* ptr) { - av_frame_unref(ptr); - av_free(ptr); + av_frame_free(&ptr); } Codec::Codec(GPU& gpu_, const NvdecCommon::NvdecRegisters& regs) @@ -32,19 +68,31 @@ Codec::~Codec() { return; } // Free libav memory - AVFrame* av_frame{nullptr}; avcodec_send_packet(av_codec_ctx, nullptr); - av_frame = av_frame_alloc(); + AVFrame* av_frame = av_frame_alloc(); avcodec_receive_frame(av_codec_ctx, av_frame); avcodec_flush_buffers(av_codec_ctx); - - av_frame_unref(av_frame); - av_free(av_frame); + av_frame_free(&av_frame); avcodec_close(av_codec_ctx); + av_buffer_unref(&av_hw_device); +} + +void Codec::InitializeHwdec() { + // Prioritize integrated GPU to mitigate bandwidth bottlenecks +#if defined(LIBVA_FOUND) + if (CreateVaapiHwdevice(&av_hw_device)) { + const auto hw_device_ctx = av_buffer_ref(av_hw_device); + ASSERT_MSG(hw_device_ctx, "av_buffer_ref failed"); + av_codec_ctx->hw_device_ctx = hw_device_ctx; + av_codec_ctx->get_format = GetHwFormat; + return; + } +#endif + // TODO more GPU accelerated decoders } void Codec::Initialize() { - AVCodecID codec{AV_CODEC_ID_NONE}; + AVCodecID codec; switch (current_codec) { case NvdecCommon::VideoCodec::H264: codec = AV_CODEC_ID_H264; @@ -53,22 +101,24 @@ void Codec::Initialize() { codec = AV_CODEC_ID_VP9; break; default: + UNIMPLEMENTED_MSG("Unknown codec {}", current_codec); return; } av_codec = avcodec_find_decoder(codec); av_codec_ctx = avcodec_alloc_context3(av_codec); av_opt_set(av_codec_ctx->priv_data, "tune", "zerolatency", 0); - - // TODO(ameerj): libavcodec gpu hw acceleration - + InitializeHwdec(); + if (!av_codec_ctx->hw_device_ctx) { + LOG_INFO(Service_NVDRV, "Using FFmpeg software decoding"); + } const auto av_error = avcodec_open2(av_codec_ctx, av_codec, nullptr); if (av_error < 0) { LOG_ERROR(Service_NVDRV, "avcodec_open2() Failed."); avcodec_close(av_codec_ctx); + av_buffer_unref(&av_hw_device); return; } initialized = true; - return; } void Codec::SetTargetCodec(NvdecCommon::VideoCodec codec) { @@ -80,36 +130,64 @@ void Codec::SetTargetCodec(NvdecCommon::VideoCodec codec) { void Codec::Decode() { const bool is_first_frame = !initialized; - if (!initialized) { + if (is_first_frame) { Initialize(); } - bool vp9_hidden_frame = false; - AVPacket packet{}; - av_init_packet(&packet); std::vector<u8> frame_data; - if (current_codec == NvdecCommon::VideoCodec::H264) { frame_data = h264_decoder->ComposeFrameHeader(state, is_first_frame); } else if (current_codec == NvdecCommon::VideoCodec::Vp9) { frame_data = vp9_decoder->ComposeFrameHeader(state); vp9_hidden_frame = vp9_decoder->WasFrameHidden(); } - + AVPacket packet{}; + av_init_packet(&packet); packet.data = frame_data.data(); packet.size = static_cast<s32>(frame_data.size()); - - avcodec_send_packet(av_codec_ctx, &packet); - - if (!vp9_hidden_frame) { - // Only receive/store visible frames - AVFramePtr frame = AVFramePtr{av_frame_alloc(), AVFrameDeleter}; - avcodec_receive_frame(av_codec_ctx, frame.get()); - av_frames.push(std::move(frame)); - // Limit queue to 10 frames. Workaround for ZLA decode and queue spam - if (av_frames.size() > 10) { - av_frames.pop(); - } + if (const int ret = avcodec_send_packet(av_codec_ctx, &packet); ret) { + LOG_DEBUG(Service_NVDRV, "avcodec_send_packet error {}", ret); + return; + } + // Only receive/store visible frames + if (vp9_hidden_frame) { + return; + } + AVFrame* hw_frame = av_frame_alloc(); + AVFrame* sw_frame = hw_frame; + ASSERT_MSG(hw_frame, "av_frame_alloc hw_frame failed"); + if (const int ret = avcodec_receive_frame(av_codec_ctx, hw_frame); ret) { + LOG_DEBUG(Service_NVDRV, "avcodec_receive_frame error {}", ret); + av_frame_free(&hw_frame); + return; + } + if (!hw_frame->width || !hw_frame->height) { + LOG_WARNING(Service_NVDRV, "Zero width or height in frame"); + av_frame_free(&hw_frame); + return; + } +#if defined(LIBVA_FOUND) + // Hardware acceleration code from FFmpeg/doc/examples/hw_decode.c under MIT license + if (hw_frame->format == AV_PIX_FMT_VAAPI) { + sw_frame = av_frame_alloc(); + ASSERT_MSG(sw_frame, "av_frame_alloc sw_frame failed"); + // Can't use AV_PIX_FMT_YUV420P and share code with software decoding in vic.cpp + // because Intel drivers crash unless using AV_PIX_FMT_NV12 + sw_frame->format = AV_PIX_FMT_NV12; + const int transfer_data_ret = av_hwframe_transfer_data(sw_frame, hw_frame, 0); + ASSERT_MSG(!transfer_data_ret, "av_hwframe_transfer_data error {}", transfer_data_ret); + av_frame_free(&hw_frame); + } +#endif + if (sw_frame->format != AV_PIX_FMT_YUV420P && sw_frame->format != AV_PIX_FMT_NV12) { + UNIMPLEMENTED_MSG("Unexpected video format from host graphics: {}", sw_frame->format); + av_frame_free(&sw_frame); + return; + } + av_frames.push(AVFramePtr{sw_frame, AVFrameDeleter}); + if (av_frames.size() > 10) { + LOG_TRACE(Service_NVDRV, "av_frames.push overflow dropped frame"); + av_frames.pop(); } } @@ -119,7 +197,6 @@ AVFramePtr Codec::GetCurrentFrame() { if (av_frames.empty()) { return AVFramePtr{nullptr, AVFrameDeleter}; } - AVFramePtr frame = std::move(av_frames.front()); av_frames.pop(); return frame; @@ -144,6 +221,5 @@ std::string_view Codec::GetCurrentCodecName() const { default: return "Unknown"; } -}; - +} } // 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" { namespace Tegra { class GPU; -struct VicRegisters; void AVFrameDeleter(AVFrame* ptr); using AVFramePtr = std::unique_ptr<AVFrame, decltype(&AVFrameDeleter)>; @@ -55,10 +54,13 @@ public: [[nodiscard]] std::string_view GetCurrentCodecName() const; private: + void InitializeHwdec(); + bool initialized{}; NvdecCommon::VideoCodec current_codec{NvdecCommon::VideoCodec::None}; AVCodec* av_codec{nullptr}; + AVBufferRef* av_hw_device{nullptr}; AVCodecContext* av_codec_ctx{nullptr}; 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 @@ namespace Tegra::Decoder { namespace { +constexpr u32 diff_update_probability = 252; +constexpr u32 frame_sync_code = 0x498342; + // Default compressed header probabilities once frame context resets constexpr Vp9EntropyProbs default_probs{ .y_mode_prob{ @@ -361,8 +364,7 @@ Vp9PictureInfo VP9::GetVp9PictureInfo(const NvdecCommon::NvdecRegisters& state) InsertEntropy(state.vp9_entropy_probs_offset, vp9_info.entropy); // surface_luma_offset[0:3] contains the address of the reference frame offsets in the following - // order: last, golden, altref, current. It may be worthwhile to track the updates done here - // to avoid buffering frame data needed for reference frame updating in the header composition. + // order: last, golden, altref, current. std::copy(state.surface_luma_offset.begin(), state.surface_luma_offset.begin() + 4, vp9_info.frame_offsets.begin()); @@ -384,33 +386,18 @@ Vp9FrameContainer VP9::GetCurrentFrame(const NvdecCommon::NvdecRegisters& state) gpu.MemoryManager().ReadBlock(state.frame_bitstream_offset, current_frame.bit_stream.data(), current_frame.info.bitstream_size); } - // Buffer two frames, saving the last show frame info - if (!next_next_frame.bit_stream.empty()) { + if (!next_frame.bit_stream.empty()) { Vp9FrameContainer temp{ .info = current_frame.info, .bit_stream = std::move(current_frame.bit_stream), }; - next_next_frame.info.show_frame = current_frame.info.last_frame_shown; - current_frame.info = next_next_frame.info; - current_frame.bit_stream = std::move(next_next_frame.bit_stream); - next_next_frame = std::move(temp); - - if (!next_frame.bit_stream.empty()) { - Vp9FrameContainer temp2{ - .info = current_frame.info, - .bit_stream = std::move(current_frame.bit_stream), - }; - next_frame.info.show_frame = current_frame.info.last_frame_shown; - current_frame.info = next_frame.info; - current_frame.bit_stream = std::move(next_frame.bit_stream); - next_frame = std::move(temp2); - } else { - next_frame.info = current_frame.info; - next_frame.bit_stream = std::move(current_frame.bit_stream); - } + next_frame.info.show_frame = current_frame.info.last_frame_shown; + current_frame.info = next_frame.info; + current_frame.bit_stream = std::move(next_frame.bit_stream); + next_frame = std::move(temp); } else { - next_next_frame.info = current_frame.info; - next_next_frame.bit_stream = std::move(current_frame.bit_stream); + next_frame.info = current_frame.info; + next_frame.bit_stream = std::move(current_frame.bit_stream); } return current_frame; } @@ -613,86 +600,64 @@ VpxBitStreamWriter VP9::ComposeUncompressedHeader() { // Reset context prev_frame_probs = default_probs; - swap_next_golden = false; + swap_ref_indices = false; loop_filter_ref_deltas.fill(0); loop_filter_mode_deltas.fill(0); - - // allow frames offsets to stabilize before checking for golden frames - grace_period = 4; - - // On key frames, all frame slots are set to the current frame, - // so the value of the selected slot doesn't really matter. - frame_ctxs.fill({current_frame_number, false, default_probs}); + frame_ctxs.fill(default_probs); // intra only, meaning the frame can be recreated with no other references current_frame_info.intra_only = true; - } else { - if (!current_frame_info.show_frame) { uncomp_writer.WriteBit(current_frame_info.intra_only); - if (!current_frame_info.last_frame_was_key) { - swap_next_golden = !swap_next_golden; - } } else { current_frame_info.intra_only = false; } if (!current_frame_info.error_resilient_mode) { uncomp_writer.WriteU(0, 2); // Reset frame context. } - - // Last, Golden, Altref frames - std::array<s32, 3> ref_frame_index{0, 1, 2}; - - // Set when next frame is hidden - // altref and golden references are swapped - if (swap_next_golden) { - ref_frame_index = std::array<s32, 3>{0, 2, 1}; + const auto& curr_offsets = current_frame_info.frame_offsets; + const auto& next_offsets = next_frame.info.frame_offsets; + const bool ref_frames_different = curr_offsets[1] != curr_offsets[2]; + const bool next_references_swap = + (next_offsets[1] == curr_offsets[2]) || (next_offsets[2] == curr_offsets[1]); + const bool needs_ref_swap = ref_frames_different && next_references_swap; + if (needs_ref_swap) { + swap_ref_indices = !swap_ref_indices; } - - // update Last Frame - u64 refresh_frame_flags = 1; - - // golden frame may refresh, determined if the next golden frame offset is changed - bool golden_refresh = false; - if (grace_period <= 0) { - for (s32 index = 1; index < 3; ++index) { - if (current_frame_info.frame_offsets[index] != - next_frame.info.frame_offsets[index]) { - current_frame_info.refresh_frame[index] = true; - golden_refresh = true; - grace_period = 3; - } + union { + u32 raw; + BitField<0, 1, u32> refresh_last; + BitField<1, 2, u32> refresh_golden; + BitField<2, 1, u32> refresh_alt; + } refresh_frame_flags; + + refresh_frame_flags.raw = 0; + for (u32 index = 0; index < 3; ++index) { + // Refresh indices that use the current frame as an index + if (curr_offsets[3] == next_offsets[index]) { + refresh_frame_flags.raw |= 1u << index; } } - - if (current_frame_info.show_frame && - (!next_frame.info.show_frame || next_frame.info.is_key_frame)) { - // Update golden frame - refresh_frame_flags = swap_next_golden ? 2 : 4; - } - - if (!current_frame_info.show_frame) { - // Update altref - refresh_frame_flags = swap_next_golden ? 2 : 4; - } else if (golden_refresh) { - refresh_frame_flags = 3; + if (swap_ref_indices) { + const u32 temp = refresh_frame_flags.refresh_golden; + refresh_frame_flags.refresh_golden.Assign(refresh_frame_flags.refresh_alt.Value()); + refresh_frame_flags.refresh_alt.Assign(temp); } - if (current_frame_info.intra_only) { uncomp_writer.WriteU(frame_sync_code, 24); - uncomp_writer.WriteU(static_cast<s32>(refresh_frame_flags), 8); + uncomp_writer.WriteU(refresh_frame_flags.raw, 8); uncomp_writer.WriteU(current_frame_info.frame_size.width - 1, 16); uncomp_writer.WriteU(current_frame_info.frame_size.height - 1, 16); uncomp_writer.WriteBit(false); // Render and frame size different. } else { - uncomp_writer.WriteU(static_cast<s32>(refresh_frame_flags), 8); - - for (s32 index = 1; index < 4; index++) { + const bool swap_indices = needs_ref_swap ^ swap_ref_indices; + const auto ref_frame_index = swap_indices ? std::array{0, 2, 1} : std::array{0, 1, 2}; + uncomp_writer.WriteU(refresh_frame_flags.raw, 8); + for (size_t index = 1; index < 4; index++) { uncomp_writer.WriteU(ref_frame_index[index - 1], 3); uncomp_writer.WriteU(current_frame_info.ref_frame_sign_bias[index], 1); } - uncomp_writer.WriteBit(true); // Frame size with refs. uncomp_writer.WriteBit(false); // Render and frame size different. uncomp_writer.WriteBit(current_frame_info.allow_high_precision_mv); @@ -714,10 +679,9 @@ VpxBitStreamWriter VP9::ComposeUncompressedHeader() { frame_ctx_idx = 1; } - uncomp_writer.WriteU(frame_ctx_idx, 2); // Frame context index. - prev_frame_probs = - frame_ctxs[frame_ctx_idx].probs; // reference probabilities for compressed header - frame_ctxs[frame_ctx_idx] = {current_frame_number, false, current_frame_info.entropy}; + uncomp_writer.WriteU(frame_ctx_idx, 2); // Frame context index. + prev_frame_probs = frame_ctxs[frame_ctx_idx]; // reference probabilities for compressed header + frame_ctxs[frame_ctx_idx] = current_frame_info.entropy; uncomp_writer.WriteU(current_frame_info.first_level, 6); uncomp_writer.WriteU(current_frame_info.sharpness_level, 3); @@ -812,7 +776,6 @@ const std::vector<u8>& VP9::ComposeFrameHeader(const NvdecCommon::NvdecRegisters current_frame_info = curr_frame.info; bitstream = std::move(curr_frame.bit_stream); } - // The uncompressed header routine sets PrevProb parameters needed for the compressed header auto uncomp_writer = ComposeUncompressedHeader(); std::vector<u8> compressed_header = ComposeCompressedHeader(); @@ -828,13 +791,6 @@ const std::vector<u8>& VP9::ComposeFrameHeader(const NvdecCommon::NvdecRegisters frame.begin() + uncompressed_header.size()); std::copy(bitstream.begin(), bitstream.end(), frame.begin() + uncompressed_header.size() + compressed_header.size()); - - // keep track of frame number - current_frame_number++; - grace_period--; - - // don't display hidden frames - hidden = !current_frame_info.show_frame; return frame; } 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 @@ namespace Tegra { class GPU; -enum class FrameType { KeyFrame = 0, InterFrame = 1 }; namespace Decoder { /// The VpxRangeEncoder, and VpxBitStreamWriter classes are used to compose the @@ -124,7 +123,7 @@ public: /// Returns true if the most recent frame was a hidden frame. [[nodiscard]] bool WasFrameHidden() const { - return hidden; + return !current_frame_info.show_frame; } private: @@ -178,19 +177,12 @@ private: std::array<s8, 4> loop_filter_ref_deltas{}; std::array<s8, 2> loop_filter_mode_deltas{}; - bool hidden = false; - s64 current_frame_number = -2; // since we buffer 2 frames - s32 grace_period = 6; // frame offsets need to stabilize - std::array<FrameContexts, 4> frame_ctxs{}; Vp9FrameContainer next_frame{}; - Vp9FrameContainer next_next_frame{}; - bool swap_next_golden{}; + std::array<Vp9EntropyProbs, 4> frame_ctxs{}; + bool swap_ref_indices{}; Vp9PictureInfo current_frame_info{}; Vp9EntropyProbs prev_frame_probs{}; - - s32 diff_update_probability = 252; - s32 frame_sync_code = 0x498342; }; } // 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 { bool refresh{}; }; -struct FrameContexts { - s64 from; - bool adapted; - Vp9EntropyProbs probs; -}; - #define ASSERT_POSITION(field_name, position) \ static_assert(offsetof(Vp9EntropyProbs, field_name) == position, \ "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) { case Method::SetOutputSurfaceLumaOffset: output_surface_luma_address = arg; break; - case Method::SetOutputSurfaceChromaUOffset: - output_surface_chroma_u_address = arg; - break; - case Method::SetOutputSurfaceChromaVOffset: - output_surface_chroma_v_address = arg; + case Method::SetOutputSurfaceChromaOffset: + output_surface_chroma_address = arg; break; default: break; @@ -65,11 +62,10 @@ void Vic::Execute() { const VicConfig config{gpu.MemoryManager().Read<u64>(config_struct_address + 0x20)}; const AVFramePtr frame_ptr = nvdec_processor->GetFrame(); const auto* frame = frame_ptr.get(); - if (!frame || frame->width == 0 || frame->height == 0) { + if (!frame) { return; } - const VideoPixelFormat pixel_format = - static_cast<VideoPixelFormat>(config.pixel_format.Value()); + const auto pixel_format = static_cast<VideoPixelFormat>(config.pixel_format.Value()); switch (pixel_format) { case VideoPixelFormat::BGRA8: case VideoPixelFormat::RGBA8: { @@ -83,16 +79,18 @@ void Vic::Execute() { sws_freeContext(scaler_ctx); scaler_ctx = nullptr; - // FFmpeg returns all frames in YUV420, convert it into expected format - scaler_ctx = - sws_getContext(frame->width, frame->height, AV_PIX_FMT_YUV420P, frame->width, - frame->height, target_format, 0, nullptr, nullptr, nullptr); + // Frames are decoded into either YUV420 or NV12 formats. Convert to desired format + scaler_ctx = sws_getContext(frame->width, frame->height, + static_cast<AVPixelFormat>(frame->format), frame->width, + frame->height, target_format, 0, nullptr, nullptr, nullptr); scaler_width = frame->width; scaler_height = frame->height; } // Get Converted frame - const std::size_t linear_size = frame->width * frame->height * 4; + const u32 width = static_cast<u32>(frame->width); + const u32 height = static_cast<u32>(frame->height); + const std::size_t linear_size = width * height * 4; // Only allocate frame_buffer once per stream, as the size is not expected to change if (!converted_frame_buffer) { @@ -109,11 +107,10 @@ void Vic::Execute() { if (blk_kind != 0) { // swizzle pitch linear to block linear const u32 block_height = static_cast<u32>(config.block_linear_height_log2); - const auto size = Tegra::Texture::CalculateSize(true, 4, frame->width, frame->height, 1, - block_height, 0); + const auto size = + Tegra::Texture::CalculateSize(true, 4, width, height, 1, block_height, 0); luma_buffer.resize(size); - Tegra::Texture::SwizzleSubrect(frame->width, frame->height, frame->width * 4, - frame->width, 4, luma_buffer.data(), + Tegra::Texture::SwizzleSubrect(width, height, width * 4, width, 4, luma_buffer.data(), converted_frame_buffer.get(), block_height, 0, 0); gpu.MemoryManager().WriteBlock(output_surface_luma_address, luma_buffer.data(), size); @@ -131,41 +128,65 @@ void Vic::Execute() { const std::size_t surface_height = config.surface_height_minus1 + 1; const auto frame_width = std::min(surface_width, static_cast<size_t>(frame->width)); const auto frame_height = std::min(surface_height, static_cast<size_t>(frame->height)); - const std::size_t half_width = frame_width / 2; - const std::size_t half_height = frame_height / 2; - const std::size_t aligned_width = (surface_width + 0xff) & ~0xff; + const std::size_t aligned_width = (surface_width + 0xff) & ~0xffUL; - const auto* luma_ptr = frame->data[0]; - const auto* chroma_b_ptr = frame->data[1]; - const auto* chroma_r_ptr = frame->data[2]; const auto stride = static_cast<size_t>(frame->linesize[0]); - const auto half_stride = static_cast<size_t>(frame->linesize[1]); luma_buffer.resize(aligned_width * surface_height); chroma_buffer.resize(aligned_width * surface_height / 2); // Populate luma buffer + const u8* luma_src = frame->data[0]; for (std::size_t y = 0; y < frame_height; ++y) { const std::size_t src = y * stride; const std::size_t dst = y * aligned_width; for (std::size_t x = 0; x < frame_width; ++x) { - luma_buffer[dst + x] = luma_ptr[src + x]; + luma_buffer[dst + x] = luma_src[src + x]; } } gpu.MemoryManager().WriteBlock(output_surface_luma_address, luma_buffer.data(), luma_buffer.size()); - // Populate chroma buffer from both channels with interleaving. - for (std::size_t y = 0; y < half_height; ++y) { - const std::size_t src = y * half_stride; - const std::size_t dst = y * aligned_width; + // Chroma + const std::size_t half_height = frame_height / 2; + const auto half_stride = static_cast<size_t>(frame->linesize[1]); - for (std::size_t x = 0; x < half_width; ++x) { - chroma_buffer[dst + x * 2] = chroma_b_ptr[src + x]; - chroma_buffer[dst + x * 2 + 1] = chroma_r_ptr[src + x]; + switch (frame->format) { + case AV_PIX_FMT_YUV420P: { + // Frame from FFmpeg software + // Populate chroma buffer from both channels with interleaving. + const std::size_t half_width = frame_width / 2; + const u8* chroma_b_src = frame->data[1]; + const u8* chroma_r_src = frame->data[2]; + for (std::size_t y = 0; y < half_height; ++y) { + const std::size_t src = y * half_stride; + const std::size_t dst = y * aligned_width; + + for (std::size_t x = 0; x < half_width; ++x) { + chroma_buffer[dst + x * 2] = chroma_b_src[src + x]; + chroma_buffer[dst + x * 2 + 1] = chroma_r_src[src + x]; + } + } + break; + } + case AV_PIX_FMT_NV12: { + // Frame from VA-API hardware + // This is already interleaved so just copy + const u8* chroma_src = frame->data[1]; + for (std::size_t y = 0; y < half_height; ++y) { + const std::size_t src = y * stride; + const std::size_t dst = y * aligned_width; + for (std::size_t x = 0; x < frame_width; ++x) { + chroma_buffer[dst + x] = chroma_src[src + x]; + } } + break; + } + default: + UNREACHABLE(); + break; } - gpu.MemoryManager().WriteBlock(output_surface_chroma_u_address, chroma_buffer.data(), + gpu.MemoryManager().WriteBlock(output_surface_chroma_address, chroma_buffer.data(), chroma_buffer.size()); break; } 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: SetControlParams = 0x1c1, SetConfigStructOffset = 0x1c2, SetOutputSurfaceLumaOffset = 0x1c8, - SetOutputSurfaceChromaUOffset = 0x1c9, - SetOutputSurfaceChromaVOffset = 0x1ca + SetOutputSurfaceChromaOffset = 0x1c9, + SetOutputSurfaceChromaUnusedOffset = 0x1ca }; explicit Vic(GPU& gpu, std::shared_ptr<Nvdec> nvdec_processor); @@ -64,8 +64,7 @@ private: GPUVAddr config_struct_address{}; GPUVAddr output_surface_luma_address{}; - GPUVAddr output_surface_chroma_u_address{}; - GPUVAddr output_surface_chroma_v_address{}; + GPUVAddr output_surface_chroma_address{}; SwsContext* scaler_ctx{}; 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() { render_window.UpdateCurrentFramebufferLayout(layout.width, layout.height); } -void RendererBase::RequestScreenshot(void* data, std::function<void()> callback, +void RendererBase::RequestScreenshot(void* data, std::function<void(bool)> callback, const Layout::FramebufferLayout& layout) { if (renderer_settings.screenshot_requested) { 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 { // Screenshot std::atomic<bool> screenshot_requested{false}; void* screenshot_bits{}; - std::function<void()> screenshot_complete_callback; + std::function<void(bool)> screenshot_complete_callback; Layout::FramebufferLayout screenshot_framebuffer_layout; }; @@ -80,7 +80,7 @@ public: void RefreshBaseSettings(); /// Request a screenshot of the next frame - void RequestScreenshot(void* data, std::function<void()> callback, + void RequestScreenshot(void* data, std::function<void(bool)> callback, const Layout::FramebufferLayout& layout); protected: 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() { glBindFramebuffer(GL_READ_FRAMEBUFFER, old_read_fb); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, old_draw_fb); - renderer_settings.screenshot_complete_callback(); + renderer_settings.screenshot_complete_callback(true); renderer_settings.screenshot_requested = false; } 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) { const bool use_accelerated = rasterizer.AccelerateDisplay(*framebuffer, framebuffer_addr, framebuffer->stride); const bool is_srgb = use_accelerated && screen_info.is_srgb; + RenderScreenshot(*framebuffer, use_accelerated); bool has_been_recreated = false; const auto recreate_swapchain = [&] { @@ -162,7 +163,7 @@ void RendererVulkan::SwapBuffers(const Tegra::FramebufferConfig* framebuffer) { if (has_been_recreated) { blit_screen.Recreate(); } - const VkSemaphore render_semaphore = blit_screen.Draw(*framebuffer, use_accelerated); + const VkSemaphore render_semaphore = blit_screen.DrawToSwapchain(*framebuffer, use_accelerated); scheduler.Flush(render_semaphore); scheduler.WaitWorker(); swapchain.Present(render_semaphore); @@ -193,4 +194,153 @@ void RendererVulkan::Report() const { telemetry_session.AddField(field, "GPU_Vulkan_Extensions", extensions); } +void Vulkan::RendererVulkan::RenderScreenshot(const Tegra::FramebufferConfig& framebuffer, + bool use_accelerated) { + if (!renderer_settings.screenshot_requested) { + return; + } + const Layout::FramebufferLayout layout{renderer_settings.screenshot_framebuffer_layout}; + vk::Image staging_image = device.GetLogical().CreateImage(VkImageCreateInfo{ + .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, + .pNext = nullptr, + .flags = VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT, + .imageType = VK_IMAGE_TYPE_2D, + .format = VK_FORMAT_B8G8R8A8_UNORM, + .extent = + { + .width = layout.width, + .height = layout.height, + .depth = 1, + }, + .mipLevels = 1, + .arrayLayers = 1, + .samples = VK_SAMPLE_COUNT_1_BIT, + .tiling = VK_IMAGE_TILING_OPTIMAL, + .usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | + VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, + .sharingMode = VK_SHARING_MODE_EXCLUSIVE, + .queueFamilyIndexCount = 0, + .pQueueFamilyIndices = nullptr, + .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED, + }); + const auto image_commit = memory_allocator.Commit(staging_image, MemoryUsage::DeviceLocal); + + const vk::ImageView dst_view = device.GetLogical().CreateImageView(VkImageViewCreateInfo{ + .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .image = *staging_image, + .viewType = VK_IMAGE_VIEW_TYPE_2D, + .format = screen_info.is_srgb ? VK_FORMAT_B8G8R8A8_SRGB : VK_FORMAT_B8G8R8A8_UNORM, + .components{ + .r = VK_COMPONENT_SWIZZLE_IDENTITY, + .g = VK_COMPONENT_SWIZZLE_IDENTITY, + .b = VK_COMPONENT_SWIZZLE_IDENTITY, + .a = VK_COMPONENT_SWIZZLE_IDENTITY, + }, + .subresourceRange{ + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = VK_REMAINING_ARRAY_LAYERS, + }, + }); + const VkExtent2D render_area{.width = layout.width, .height = layout.height}; + const vk::Framebuffer screenshot_fb = blit_screen.CreateFramebuffer(*dst_view, render_area); + // Since we're not rendering to the screen, ignore the render semaphore. + void(blit_screen.Draw(framebuffer, *screenshot_fb, layout, render_area, use_accelerated)); + + const auto buffer_size = static_cast<VkDeviceSize>(layout.width * layout.height * 4); + const VkBufferCreateInfo dst_buffer_info{ + .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .size = buffer_size, + .usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT, + .sharingMode = VK_SHARING_MODE_EXCLUSIVE, + .queueFamilyIndexCount = 0, + .pQueueFamilyIndices = nullptr, + }; + const vk::Buffer dst_buffer = device.GetLogical().CreateBuffer(dst_buffer_info); + MemoryCommit dst_buffer_memory = memory_allocator.Commit(dst_buffer, MemoryUsage::Download); + + scheduler.RequestOutsideRenderPassOperationContext(); + scheduler.Record([&](vk::CommandBuffer cmdbuf) { + const VkImageMemoryBarrier read_barrier{ + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .pNext = nullptr, + .srcAccessMask = VK_ACCESS_MEMORY_WRITE_BIT, + .dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT, + .oldLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, + .newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = *staging_image, + .subresourceRange{ + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .baseMipLevel = 0, + .levelCount = VK_REMAINING_MIP_LEVELS, + .baseArrayLayer = 0, + .layerCount = VK_REMAINING_ARRAY_LAYERS, + }, + }; + const VkImageMemoryBarrier image_write_barrier{ + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .pNext = nullptr, + .srcAccessMask = 0, + .dstAccessMask = VK_ACCESS_MEMORY_WRITE_BIT, + .oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + .newLayout = VK_IMAGE_LAYOUT_GENERAL, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = *staging_image, + .subresourceRange{ + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .baseMipLevel = 0, + .levelCount = VK_REMAINING_MIP_LEVELS, + .baseArrayLayer = 0, + .layerCount = VK_REMAINING_ARRAY_LAYERS, + }, + }; + static constexpr VkMemoryBarrier memory_write_barrier{ + .sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER, + .pNext = nullptr, + .srcAccessMask = VK_ACCESS_MEMORY_WRITE_BIT, + .dstAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT, + }; + const VkBufferImageCopy copy{ + .bufferOffset = 0, + .bufferRowLength = 0, + .bufferImageHeight = 0, + .imageSubresource{ + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .mipLevel = 0, + .baseArrayLayer = 0, + .layerCount = 1, + }, + .imageOffset{.x = 0, .y = 0, .z = 0}, + .imageExtent{ + .width = layout.width, + .height = layout.height, + .depth = 1, + }, + }; + cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, + 0, read_barrier); + cmdbuf.CopyImageToBuffer(*staging_image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, *dst_buffer, + copy); + cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, + 0, memory_write_barrier, nullptr, image_write_barrier); + }); + // Ensure the copy is fully completed before saving the screenshot + scheduler.Finish(); + + // Copy backing image data to the QImage screenshot buffer + const auto dst_memory_map = dst_buffer_memory.Map(); + std::memcpy(renderer_settings.screenshot_bits, dst_memory_map.data(), dst_memory_map.size()); + renderer_settings.screenshot_complete_callback(false); + renderer_settings.screenshot_requested = false; +} + } // 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: private: void Report() const; + void RenderScreenshot(const Tegra::FramebufferConfig& framebuffer, bool use_accelerated); + Core::TelemetrySession& telemetry_session; Core::Memory::Memory& cpu_memory; 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() { CreateDynamicResources(); } -VkSemaphore VKBlitScreen::Draw(const Tegra::FramebufferConfig& framebuffer, bool use_accelerated) { +VkSemaphore VKBlitScreen::Draw(const Tegra::FramebufferConfig& framebuffer, + const VkFramebuffer& host_framebuffer, + const Layout::FramebufferLayout layout, VkExtent2D render_area, + bool use_accelerated) { RefreshResources(framebuffer); // Finish any pending renderpass @@ -145,8 +148,8 @@ VkSemaphore VKBlitScreen::Draw(const Tegra::FramebufferConfig& framebuffer, bool use_accelerated ? screen_info.image_view : *raw_image_views[image_index]); BufferData data; - SetUniformData(data, framebuffer); - SetVertexData(data, framebuffer); + SetUniformData(data, layout); + SetVertexData(data, framebuffer, layout); const std::span<u8> mapped_span = buffer_commit.Map(); std::memcpy(mapped_span.data(), &data, sizeof(data)); @@ -220,52 +223,75 @@ VkSemaphore VKBlitScreen::Draw(const Tegra::FramebufferConfig& framebuffer, bool VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, write_barrier); }); } - scheduler.Record([this, image_index, size = swapchain.GetSize()](vk::CommandBuffer cmdbuf) { - const f32 bg_red = Settings::values.bg_red.GetValue() / 255.0f; - const f32 bg_green = Settings::values.bg_green.GetValue() / 255.0f; - const f32 bg_blue = Settings::values.bg_blue.GetValue() / 255.0f; - const VkClearValue clear_color{ - .color = {.float32 = {bg_red, bg_green, bg_blue, 1.0f}}, - }; - const VkRenderPassBeginInfo renderpass_bi{ - .sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO, - .pNext = nullptr, - .renderPass = *renderpass, - .framebuffer = *framebuffers[image_index], - .renderArea = - { - .offset = {0, 0}, - .extent = size, - }, - .clearValueCount = 1, - .pClearValues = &clear_color, - }; - const VkViewport viewport{ - .x = 0.0f, - .y = 0.0f, - .width = static_cast<float>(size.width), - .height = static_cast<float>(size.height), - .minDepth = 0.0f, - .maxDepth = 1.0f, - }; - const VkRect2D scissor{ - .offset = {0, 0}, - .extent = size, - }; - cmdbuf.BeginRenderPass(renderpass_bi, VK_SUBPASS_CONTENTS_INLINE); - cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, *pipeline); - cmdbuf.SetViewport(0, viewport); - cmdbuf.SetScissor(0, scissor); - - cmdbuf.BindVertexBuffer(0, *buffer, offsetof(BufferData, vertices)); - cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_GRAPHICS, *pipeline_layout, 0, - descriptor_sets[image_index], {}); - cmdbuf.Draw(4, 1, 0, 0); - cmdbuf.EndRenderPass(); - }); + scheduler.Record( + [this, host_framebuffer, image_index, size = render_area](vk::CommandBuffer cmdbuf) { + const f32 bg_red = Settings::values.bg_red.GetValue() / 255.0f; + const f32 bg_green = Settings::values.bg_green.GetValue() / 255.0f; + const f32 bg_blue = Settings::values.bg_blue.GetValue() / 255.0f; + const VkClearValue clear_color{ + .color = {.float32 = {bg_red, bg_green, bg_blue, 1.0f}}, + }; + const VkRenderPassBeginInfo renderpass_bi{ + .sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO, + .pNext = nullptr, + .renderPass = *renderpass, + .framebuffer = host_framebuffer, + .renderArea = + { + .offset = {0, 0}, + .extent = size, + }, + .clearValueCount = 1, + .pClearValues = &clear_color, + }; + const VkViewport viewport{ + .x = 0.0f, + .y = 0.0f, + .width = static_cast<float>(size.width), + .height = static_cast<float>(size.height), + .minDepth = 0.0f, + .maxDepth = 1.0f, + }; + const VkRect2D scissor{ + .offset = {0, 0}, + .extent = size, + }; + cmdbuf.BeginRenderPass(renderpass_bi, VK_SUBPASS_CONTENTS_INLINE); + cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, *pipeline); + cmdbuf.SetViewport(0, viewport); + cmdbuf.SetScissor(0, scissor); + + cmdbuf.BindVertexBuffer(0, *buffer, offsetof(BufferData, vertices)); + cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_GRAPHICS, *pipeline_layout, 0, + descriptor_sets[image_index], {}); + cmdbuf.Draw(4, 1, 0, 0); + cmdbuf.EndRenderPass(); + }); return *semaphores[image_index]; } +VkSemaphore VKBlitScreen::DrawToSwapchain(const Tegra::FramebufferConfig& framebuffer, + bool use_accelerated) { + const std::size_t image_index = swapchain.GetImageIndex(); + const VkExtent2D render_area = swapchain.GetSize(); + const Layout::FramebufferLayout layout = render_window.GetFramebufferLayout(); + return Draw(framebuffer, *framebuffers[image_index], layout, render_area, use_accelerated); +} + +vk::Framebuffer VKBlitScreen::CreateFramebuffer(const VkImageView& image_view, VkExtent2D extent) { + return device.GetLogical().CreateFramebuffer(VkFramebufferCreateInfo{ + .sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .renderPass = *renderpass, + .attachmentCount = 1, + .pAttachments = &image_view, + .width = extent.width, + .height = extent.height, + .layers = 1, + }); +} + void VKBlitScreen::CreateStaticResources() { CreateShaders(); CreateSemaphores(); @@ -609,22 +635,9 @@ void VKBlitScreen::CreateFramebuffers() { const VkExtent2D size{swapchain.GetSize()}; framebuffers.resize(image_count); - VkFramebufferCreateInfo ci{ - .sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO, - .pNext = nullptr, - .flags = 0, - .renderPass = *renderpass, - .attachmentCount = 1, - .pAttachments = nullptr, - .width = size.width, - .height = size.height, - .layers = 1, - }; - for (std::size_t i = 0; i < image_count; ++i) { const VkImageView image_view{swapchain.GetImageViewIndex(i)}; - ci.pAttachments = &image_view; - framebuffers[i] = device.GetLogical().CreateFramebuffer(ci); + framebuffers[i] = CreateFramebuffer(image_view, size); } } @@ -752,15 +765,13 @@ void VKBlitScreen::UpdateDescriptorSet(std::size_t image_index, VkImageView imag device.GetLogical().UpdateDescriptorSets(std::array{ubo_write, sampler_write}, {}); } -void VKBlitScreen::SetUniformData(BufferData& data, - const Tegra::FramebufferConfig& framebuffer) const { - const auto& layout = render_window.GetFramebufferLayout(); +void VKBlitScreen::SetUniformData(BufferData& data, const Layout::FramebufferLayout layout) const { data.uniform.modelview_matrix = MakeOrthographicMatrix(static_cast<f32>(layout.width), static_cast<f32>(layout.height)); } -void VKBlitScreen::SetVertexData(BufferData& data, - const Tegra::FramebufferConfig& framebuffer) const { +void VKBlitScreen::SetVertexData(BufferData& data, const Tegra::FramebufferConfig& framebuffer, + const Layout::FramebufferLayout layout) const { const auto& framebuffer_transform_flags = framebuffer.transform_flags; const auto& framebuffer_crop_rect = framebuffer.crop_rect; @@ -798,7 +809,7 @@ void VKBlitScreen::SetVertexData(BufferData& data, static_cast<f32>(screen_info.height); } - const auto& screen = render_window.GetFramebufferLayout().screen; + const auto& screen = layout.screen; const auto x = static_cast<f32>(screen.left); const auto y = static_cast<f32>(screen.top); 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: void Recreate(); [[nodiscard]] VkSemaphore Draw(const Tegra::FramebufferConfig& framebuffer, + const VkFramebuffer& host_framebuffer, + const Layout::FramebufferLayout layout, VkExtent2D render_area, bool use_accelerated); + [[nodiscard]] VkSemaphore DrawToSwapchain(const Tegra::FramebufferConfig& framebuffer, + bool use_accelerated); + + [[nodiscard]] vk::Framebuffer CreateFramebuffer(const VkImageView& image_view, + VkExtent2D extent); + private: struct BufferData; @@ -81,8 +89,9 @@ private: void CreateRawImages(const Tegra::FramebufferConfig& framebuffer); void UpdateDescriptorSet(std::size_t image_index, VkImageView image_view) const; - void SetUniformData(BufferData& data, const Tegra::FramebufferConfig& framebuffer) const; - void SetVertexData(BufferData& data, const Tegra::FramebufferConfig& framebuffer) const; + void SetUniformData(BufferData& data, const Layout::FramebufferLayout layout) const; + void SetVertexData(BufferData& data, const Tegra::FramebufferConfig& framebuffer, + const Layout::FramebufferLayout layout) const; u64 CalculateBufferSize(const Tegra::FramebufferConfig& framebuffer) const; 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 { VkViewport GetViewportState(const Device& device, const Maxwell& regs, size_t index) { const auto& src = regs.viewport_transform[index]; const float width = src.scale_x * 2.0f; - const float height = src.scale_y * 2.0f; + float y = src.translate_y - src.scale_y; + float height = src.scale_y * 2.0f; + if (regs.screen_y_control.y_negate) { + y += height; + height = -height; + } const float reduce_z = regs.depth_mode == Maxwell::DepthMode::MinusOneToOne ? 1.0f : 0.0f; VkViewport viewport{ .x = src.translate_x - src.scale_x, - .y = src.translate_y - src.scale_y, + .y = y, .width = width != 0.0f ? width : 1.0f, .height = height != 0.0f ? height : 1.0f, .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 @@ namespace Tegra::Texture { namespace { -template <bool TO_LINEAR> -void Swizzle(std::span<u8> output, std::span<const u8> input, u32 bytes_per_pixel, u32 width, - u32 height, u32 depth, u32 block_height, u32 block_depth, u32 stride_alignment) { +template <bool TO_LINEAR, u32 BYTES_PER_PIXEL> +void SwizzleImpl(std::span<u8> output, std::span<const u8> input, u32 width, u32 height, u32 depth, + u32 block_height, u32 block_depth, u32 stride_alignment) { // The origin of the transformation can be configured here, leave it as zero as the current API // doesn't expose it. 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 static constexpr u32 origin_z = 0; // We can configure here a custom pitch - // As it's not exposed 'width * bpp' will be the expected pitch. - const u32 pitch = width * bytes_per_pixel; - const u32 stride = Common::AlignUpLog2(width, stride_alignment) * bytes_per_pixel; + // As it's not exposed 'width * BYTES_PER_PIXEL' will be the expected pitch. + const u32 pitch = width * BYTES_PER_PIXEL; + const u32 stride = Common::AlignUpLog2(width, stride_alignment) * BYTES_PER_PIXEL; const u32 gobs_in_x = Common::DivCeilLog2(stride, GOB_SIZE_X_SHIFT); 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 ((block_y & block_height_mask) << GOB_SIZE_SHIFT); for (u32 column = 0; column < width; ++column) { - const u32 x = (column + origin_x) * bytes_per_pixel; + const u32 x = (column + origin_x) * BYTES_PER_PIXEL; const u32 offset_x = (x >> GOB_SIZE_X_SHIFT) << x_shift; const u32 base_swizzled_offset = offset_z + offset_y + offset_x; const u32 swizzled_offset = base_swizzled_offset + table[x % GOB_SIZE_X]; const u32 unswizzled_offset = - slice * pitch * height + line * pitch + column * bytes_per_pixel; + slice * pitch * height + line * pitch + column * BYTES_PER_PIXEL; if (const auto offset = (TO_LINEAR ? unswizzled_offset : swizzled_offset); offset >= input.size()) { @@ -73,11 +73,45 @@ void Swizzle(std::span<u8> output, std::span<const u8> input, u32 bytes_per_pixe u8* const dst = &output[TO_LINEAR ? swizzled_offset : unswizzled_offset]; const u8* const src = &input[TO_LINEAR ? unswizzled_offset : swizzled_offset]; - std::memcpy(dst, src, bytes_per_pixel); + + std::memcpy(dst, src, BYTES_PER_PIXEL); } } } } + +template <bool TO_LINEAR> +void Swizzle(std::span<u8> output, std::span<const u8> input, u32 bytes_per_pixel, u32 width, + u32 height, u32 depth, u32 block_height, u32 block_depth, u32 stride_alignment) { + switch (bytes_per_pixel) { + case 1: + return SwizzleImpl<TO_LINEAR, 1>(output, input, width, height, depth, block_height, + block_depth, stride_alignment); + case 2: + return SwizzleImpl<TO_LINEAR, 2>(output, input, width, height, depth, block_height, + block_depth, stride_alignment); + case 3: + return SwizzleImpl<TO_LINEAR, 3>(output, input, width, height, depth, block_height, + block_depth, stride_alignment); + case 4: + return SwizzleImpl<TO_LINEAR, 4>(output, input, width, height, depth, block_height, + block_depth, stride_alignment); + case 6: + return SwizzleImpl<TO_LINEAR, 6>(output, input, width, height, depth, block_height, + block_depth, stride_alignment); + case 8: + return SwizzleImpl<TO_LINEAR, 8>(output, input, width, height, depth, block_height, + block_depth, stride_alignment); + case 12: + return SwizzleImpl<TO_LINEAR, 12>(output, input, width, height, depth, block_height, + block_depth, stride_alignment); + case 16: + return SwizzleImpl<TO_LINEAR, 16>(output, input, width, height, depth, block_height, + block_depth, stride_alignment); + default: + UNREACHABLE_MSG("Invalid bytes_per_pixel={}", bytes_per_pixel); + } +} } // Anonymous namespace void 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 screenshot_image = QImage(QSize(layout.width, layout.height), QImage::Format_RGB32); renderer.RequestScreenshot( screenshot_image.bits(), - [=, this] { + [=, this](bool invert_y) { const std::string std_screenshot_path = screenshot_path.toStdString(); - if (screenshot_image.mirrored(false, true).save(screenshot_path)) { + if (screenshot_image.mirrored(false, invert_y).save(screenshot_path)) { LOG_INFO(Frontend, "Screenshot saved to \"{}\"", std_screenshot_path); } else { 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() { void Config::ReadSystemValues() { qt_config->beginGroup(QStringLiteral("System")); - ReadBasicSetting(Settings::values.current_user); - Settings::values.current_user = std::clamp<int>(Settings::values.current_user.GetValue(), 0, - Service::Account::MAX_USERS - 1); - ReadGlobalSetting(Settings::values.language_index); ReadGlobalSetting(Settings::values.region_index); @@ -897,6 +893,10 @@ void Config::ReadSystemValues() { } if (global) { + ReadBasicSetting(Settings::values.current_user); + Settings::values.current_user = std::clamp<int>(Settings::values.current_user.GetValue(), 0, + Service::Account::MAX_USERS - 1); + const auto custom_rtc_enabled = ReadSetting(QStringLiteral("custom_rtc_enabled"), false).toBool(); if (custom_rtc_enabled) { @@ -946,7 +946,8 @@ void Config::ReadUIGamelistValues() { qt_config->beginGroup(QStringLiteral("UIGameList")); ReadBasicSetting(UISettings::values.show_add_ons); - ReadBasicSetting(UISettings::values.icon_size); + ReadBasicSetting(UISettings::values.game_icon_size); + ReadBasicSetting(UISettings::values.folder_icon_size); ReadBasicSetting(UISettings::values.row_1_text_id); ReadBasicSetting(UISettings::values.row_2_text_id); ReadBasicSetting(UISettings::values.cache_game_list); @@ -1405,7 +1406,6 @@ void Config::SaveShortcutValues() { void Config::SaveSystemValues() { qt_config->beginGroup(QStringLiteral("System")); - WriteBasicSetting(Settings::values.current_user); WriteGlobalSetting(Settings::values.language_index); WriteGlobalSetting(Settings::values.region_index); WriteGlobalSetting(Settings::values.time_zone_index); @@ -1417,6 +1417,8 @@ void Config::SaveSystemValues() { 0, Settings::values.rng_seed.UsingGlobal()); if (global) { + WriteBasicSetting(Settings::values.current_user); + WriteSetting(QStringLiteral("custom_rtc_enabled"), Settings::values.custom_rtc.has_value(), false); WriteSetting(QStringLiteral("custom_rtc"), @@ -1463,7 +1465,8 @@ void Config::SaveUIGamelistValues() { qt_config->beginGroup(QStringLiteral("UIGameList")); WriteBasicSetting(UISettings::values.show_add_ons); - WriteBasicSetting(UISettings::values.icon_size); + WriteBasicSetting(UISettings::values.game_icon_size); + WriteBasicSetting(UISettings::values.folder_icon_size); WriteBasicSetting(UISettings::values.row_1_text_id); WriteBasicSetting(UISettings::values.row_2_text_id); 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 @@ #include "yuzu/uisettings.h" namespace { -constexpr std::array default_icon_sizes{ +constexpr std::array default_game_icon_sizes{ std::make_pair(0, QT_TRANSLATE_NOOP("ConfigureUI", "None")), std::make_pair(32, QT_TRANSLATE_NOOP("ConfigureUI", "Small (32x32)")), std::make_pair(64, QT_TRANSLATE_NOOP("ConfigureUI", "Standard (64x64)")), @@ -24,6 +24,13 @@ constexpr std::array default_icon_sizes{ std::make_pair(256, QT_TRANSLATE_NOOP("ConfigureUI", "Full Size (256x256)")), }; +constexpr std::array default_folder_icon_sizes{ + std::make_pair(0, QT_TRANSLATE_NOOP("ConfigureUI", "None")), + std::make_pair(24, QT_TRANSLATE_NOOP("ConfigureUI", "Small (24x24)")), + std::make_pair(48, QT_TRANSLATE_NOOP("ConfigureUI", "Standard (48x48)")), + std::make_pair(72, QT_TRANSLATE_NOOP("ConfigureUI", "Large (72x72)")), +}; + // clang-format off constexpr std::array row_text_names{ QT_TRANSLATE_NOOP("ConfigureUI", "Filename"), @@ -34,8 +41,12 @@ constexpr std::array row_text_names{ }; // clang-format on -QString GetTranslatedIconSize(size_t index) { - return QCoreApplication::translate("ConfigureUI", default_icon_sizes[index].second); +QString GetTranslatedGameIconSize(size_t index) { + return QCoreApplication::translate("ConfigureUI", default_game_icon_sizes[index].second); +} + +QString GetTranslatedFolderIconSize(size_t index) { + return QCoreApplication::translate("ConfigureUI", default_folder_icon_sizes[index].second); } QString GetTranslatedRowTextName(size_t index) { @@ -60,8 +71,10 @@ ConfigureUi::ConfigureUi(QWidget* parent) : QWidget(parent), ui(new Ui::Configur // Force game list reload if any of the relevant settings are changed. connect(ui->show_add_ons, &QCheckBox::stateChanged, this, &ConfigureUi::RequestGameListUpdate); - connect(ui->icon_size_combobox, QOverload<int>::of(&QComboBox::currentIndexChanged), this, + connect(ui->game_icon_size_combobox, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &ConfigureUi::RequestGameListUpdate); + connect(ui->folder_icon_size_combobox, QOverload<int>::of(&QComboBox::currentIndexChanged), + this, &ConfigureUi::RequestGameListUpdate); connect(ui->row_1_text_combobox, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &ConfigureUi::RequestGameListUpdate); connect(ui->row_2_text_combobox, QOverload<int>::of(&QComboBox::currentIndexChanged), this, @@ -95,7 +108,8 @@ void ConfigureUi::ApplyConfiguration() { UISettings::values.theme = ui->theme_combobox->itemData(ui->theme_combobox->currentIndex()).toString(); UISettings::values.show_add_ons = ui->show_add_ons->isChecked(); - UISettings::values.icon_size = ui->icon_size_combobox->currentData().toUInt(); + UISettings::values.game_icon_size = ui->game_icon_size_combobox->currentData().toUInt(); + UISettings::values.folder_icon_size = ui->folder_icon_size_combobox->currentData().toUInt(); UISettings::values.row_1_text_id = ui->row_1_text_combobox->currentData().toUInt(); UISettings::values.row_2_text_id = ui->row_2_text_combobox->currentData().toUInt(); @@ -114,8 +128,10 @@ void ConfigureUi::SetConfiguration() { ui->language_combobox->setCurrentIndex( ui->language_combobox->findData(UISettings::values.language)); ui->show_add_ons->setChecked(UISettings::values.show_add_ons.GetValue()); - ui->icon_size_combobox->setCurrentIndex( - ui->icon_size_combobox->findData(UISettings::values.icon_size.GetValue())); + ui->game_icon_size_combobox->setCurrentIndex( + ui->game_icon_size_combobox->findData(UISettings::values.game_icon_size.GetValue())); + ui->folder_icon_size_combobox->setCurrentIndex( + ui->folder_icon_size_combobox->findData(UISettings::values.folder_icon_size.GetValue())); ui->enable_screenshot_save_as->setChecked( UISettings::values.enable_screenshot_save_as.GetValue()); @@ -134,8 +150,14 @@ void ConfigureUi::changeEvent(QEvent* event) { void ConfigureUi::RetranslateUI() { ui->retranslateUi(this); - for (int i = 0; i < ui->icon_size_combobox->count(); i++) { - ui->icon_size_combobox->setItemText(i, GetTranslatedIconSize(static_cast<size_t>(i))); + for (int i = 0; i < ui->game_icon_size_combobox->count(); i++) { + ui->game_icon_size_combobox->setItemText(i, + GetTranslatedGameIconSize(static_cast<size_t>(i))); + } + + for (int i = 0; i < ui->folder_icon_size_combobox->count(); i++) { + ui->folder_icon_size_combobox->setItemText( + i, GetTranslatedFolderIconSize(static_cast<size_t>(i))); } for (int i = 0; i < ui->row_1_text_combobox->count(); i++) { @@ -166,9 +188,13 @@ void ConfigureUi::InitializeLanguageComboBox() { } void ConfigureUi::InitializeIconSizeComboBox() { - for (size_t i = 0; i < default_icon_sizes.size(); i++) { - const auto size = default_icon_sizes[i].first; - ui->icon_size_combobox->addItem(GetTranslatedIconSize(i), size); + for (size_t i = 0; i < default_game_icon_sizes.size(); i++) { + const auto size = default_game_icon_sizes[i].first; + ui->game_icon_size_combobox->addItem(GetTranslatedGameIconSize(i), size); + } + for (size_t i = 0; i < default_folder_icon_sizes.size(); i++) { + const auto size = default_folder_icon_sizes[i].first; + ui->folder_icon_size_combobox->addItem(GetTranslatedFolderIconSize(i), size); } } 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 @@ </widget> </item> <item> - <layout class="QHBoxLayout" name="icon_size_qhbox_layout_2"> + <layout class="QHBoxLayout" name="game_icon_size_qhbox_layout_2"> <item> - <widget class="QLabel" name="icon_size_label"> + <widget class="QLabel" name="game_icon_size_label"> <property name="text"> - <string>Icon Size:</string> + <string>Game Icon Size:</string> </property> </widget> </item> <item> - <widget class="QComboBox" name="icon_size_combobox"/> + <widget class="QComboBox" name="game_icon_size_combobox"/> + </item> + </layout> + </item> + <item> + <layout class="QHBoxLayout" name="folder_icon_size_qhbox_layout_2"> + <item> + <widget class="QLabel" name="folder_icon_size_label"> + <property name="text"> + <string>Folder Icon Size:</string> + </property> + </widget> + </item> + <item> + <widget class="QComboBox" name="folder_icon_size_combobox"/> </item> </layout> </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() { for (int i = 0; i < item_model->invisibleRootItem()->rowCount(); i++) { QStandardItem* child = item_model->invisibleRootItem()->child(i); - const int icon_size = - std::min(static_cast<int>(UISettings::values.icon_size.GetValue()), 64); + const int icon_size = UISettings::values.folder_icon_size.GetValue(); + switch (child->data(GameListItem::TypeRole).value<GameListItemType>()) { case GameListItemType::SdmcDir: 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: setData(qulonglong(program_id), ProgramIdRole); setData(game_type, FileTypeRole); - const u32 size = UISettings::values.icon_size.GetValue(); + const u32 size = UISettings::values.game_icon_size.GetValue(); QPixmap picture; if (!picture.loadFromData(picture_data.data(), static_cast<u32>(picture_data.size()))) { @@ -233,8 +233,7 @@ public: UISettings::GameDir* game_dir = &directory; setData(QVariant(UISettings::values.game_dirs.indexOf(directory)), GameDirRole); - const int icon_size = - std::min(static_cast<int>(UISettings::values.icon_size.GetValue()), 64); + const int icon_size = UISettings::values.folder_icon_size.GetValue(); switch (dir_type) { case GameListItemType::SdmcDir: setData( @@ -295,8 +294,8 @@ public: explicit GameListAddDir() { setData(type(), TypeRole); - const int icon_size = - std::min(static_cast<int>(UISettings::values.icon_size.GetValue()), 64); + const int icon_size = UISettings::values.folder_icon_size.GetValue(); + setData(QIcon::fromTheme(QStringLiteral("plus")) .pixmap(icon_size) .scaled(icon_size, icon_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation), @@ -318,8 +317,8 @@ public: explicit GameListFavorites() { setData(type(), TypeRole); - const int icon_size = - std::min(static_cast<int>(UISettings::values.icon_size.GetValue()), 64); + const int icon_size = UISettings::values.folder_icon_size.GetValue(); + setData(QIcon::fromTheme(QStringLiteral("star")) .pixmap(icon_size) .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 { // Game List Settings::BasicSetting<bool> show_add_ons{true, "show_add_ons"}; - Settings::BasicSetting<uint32_t> icon_size{64, "icon_size"}; + Settings::BasicSetting<uint32_t> game_icon_size{64, "game_icon_size"}; + Settings::BasicSetting<uint32_t> folder_icon_size{48, "folder_icon_size"}; Settings::BasicSetting<uint8_t> row_1_text_id{3, "row_1_text_id"}; Settings::BasicSetting<uint8_t> row_2_text_id{2, "row_2_text_id"}; 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() { if (Settings::values.players.GetValue()[p].analogs[i].empty()) Settings::values.players.GetValue()[p].analogs[i] = default_param; } + + Settings::values.players.GetValue()[p].connected = + sdl2_config->GetBoolean(group, "connected", false); } ReadSetting("ControlsGeneral", Settings::values.mouse_enabled); |