Compare commits

...

31 Commits

Author SHA1 Message Date
ameerj 928b64d2ce nvdec: Better logging for unimplemented codecs 2021-08-07 01:08:33 -04:00
bunnei f183668a87 Merge pull request #6799 from ameerj/vp9-fixes
nvdec: Fix VP9 reference frame refreshes
2021-08-06 17:46:46 -07:00
ameerj 156ea746a3 nvhost_nvdec_common: Remove BufferMap
This was mainly used to keep track of mapped buffers for later unmapping.  Since unmap is no longer implemented, this no longer seves a valuable purpose.
2021-08-06 20:11:12 -04:00
ameerj e3688f0627 vp9: Cleanup unused variables
With reference frames refreshes fix, we no longer need to buffer two frames in advance.
We can also remove other unused or otherwise unneeded variables.
2021-08-06 20:08:11 -04:00
ameerj a3f80a97a3 vp9: Fix reference frame refreshes
This resolves the artifacting when decoding VP9 streams.
2021-08-06 20:08:08 -04:00
ameerj cc8ac112fc nvhost_nvdec_common: Stub UnmapBuffer Ioctl
Skip unmapping nvdec buffers to avoid breaking the continuity of the VP9 reference frame addresses, and the risk of invalidating data before the async GPU thread is done with it.
2021-08-06 20:06:30 -04:00
bunnei 42d8e08f78 Merge pull request #6822 from yzct12345/clion-assert
assert: Avoid empty macros
2021-08-05 22:29:45 -07:00
bunnei e1a92db519 Merge pull request #6813 from Morph1984/hex-string-to-uuid
common: uuid: Add hex string to UUID constructor
2021-08-05 13:29:11 -07:00
yzct12345 7e846be376 assert: Verify formatting 2021-08-05 17:46:22 +00:00
yzct12345 346149dcf9 assert: Avoid empty macros 2021-08-05 17:21:56 +00:00
Mai M a1cb453470 Merge pull request #6819 from Morph1984/i-am-dumb
applet_swkbd: Include the null terminator in the buffer size calculation
2021-08-04 23:32:01 -04:00
Mai M 9a7d2e3659 Merge pull request #6818 from Morph1984/hex-util-bug
hex_util: Fix incorrect array size in AsArray
2021-08-04 23:31:04 -04:00
Morph f10dc35dd0 applet_swkbd: Include the null terminator in the buffer size calculation
Some games may interpret the read string as a null-terminated string instead of just reading the string up to buffer_size.
2021-08-04 22:32:09 -04:00
Morph 7b39215c8a hex_util: Fix incorrect array size in AsArray
Although this isn't used, this is a potential bug as HexStringToArray will perform an out-of-bounds read.
2021-08-04 22:16:29 -04:00
Morph edb9c72e26 Merge pull request #6816 from lat9nq/fix-mult-contrl
config: Read connected setting for controllers
2021-08-04 22:05:53 -04:00
lat9nq be16d92060 config: Read connected setting for controllers
Currently yuzu will read the mapping but does not connect a controller
despite adding subsequent configurations for it. Read the `connected`
setting for now as a boolean like the Qt frontend.
2021-08-04 17:09:35 -04:00
Morph 705f111058 common: uuid: Add hex string to UUID constructor
This allows for easily converting a hex string into a Common::UUID, which is backed by a 128 bit unsigned integer.
2021-08-04 10:45:41 -04:00
yzct12345 2868d4ba84 nvdec: Implement VA-API hardware video acceleration (#6713)
* nvdec: VA-API

* Verify formatting

* Forgot a semicolon for Windows

* Clarify comment about AV_PIX_FMT_NV12

* Fix assert log spam from missing negation

* vic: Remove forgotten debug code

* Address lioncash's review

* Mention VA-API is Intel/AMD

* Address v1993's review

* Hopefully fix CMakeLists style this time

* vic: Improve cache locality

* vic: Fix off-by-one error

* codec: Async

* codec: Forgot the GetValue()

* nvdec: Address ameerj's review

* codec: Fallback to CPU without VA-API support

* cmake: Address lat9nq's review

* cmake: Make VA-API optional

* vaapi: Multiple GPU

* Apply suggestions from code review

Co-authored-by: Ameer J <52414509+ameerj@users.noreply.github.com>

* nvdec: Address ameerj's review

* codec: Use anonymous instead of static

* nvdec: Remove enum and fix memory leak

* nvdec: Address ameerj's review

* codec: Remove preparation for threading

Co-authored-by: Ameer J <52414509+ameerj@users.noreply.github.com>
2021-08-03 23:43:11 -04:00
Morph d16a337d98 Merge pull request #6805 from lat9nq/fix-user-profiles
config: Only read/write current_user on global config
2021-08-02 23:58:41 -04:00
lat9nq 6ab0c6a808 config: Only read/write current_user on global config 2021-08-02 18:29:24 -04:00
Morph ffe553edbf Merge pull request #6801 from spholz/spholz-patch-1
network: fix ternary operator in Socket::SendTo
2021-08-02 14:56:16 -04:00
spholz e71f78d04c network: fix ternary operator in Socket::SendTo 2021-08-02 20:12:12 +02:00
yzct12345 f56d0db5bd decoders: Optimize swizzle copy performance (#6790)
This makes UnswizzleTexture up to two times faster. It is the main bottleneck in NVDEC video decoding.
2021-08-02 11:18:58 -04:00
Malte Jürgens 381aacdbb1 game_list: Make game list folder icons smaller (#6762)
Makes the default game list folder icons 48x48 by default instead of 64x64, and allows for selecting small (24x24) and large (72x72) icon sizes.
2021-08-01 12:59:36 -04:00
Morph d20bcb7faf Merge pull request #6793 from Morph1984/lang-fix
service: set: Correct copy amount in GetAvailableLanguageCodes
2021-08-01 12:47:47 -04:00
Morph 3b4d427993 service: set: Correct copy amount in GetAvailableLanguageCodes 2021-08-01 11:59:52 -04:00
Fernando S 30f0b7cf31 Merge pull request #6720 from ameerj/vk-screenshot
renderer_vulkan: Implement screenshots
2021-08-01 13:31:33 +02:00
ameerj 7ac99bb127 renderers: Add explicit invert_y bool to screenshot callback
OpenGL and Vulkan images render in different coordinate systems. This allows us to specify the coordinate system of the screenshot within each renderer
2021-07-28 21:46:08 -04:00
ameerj 75e7f54fb0 renderer_vulkan: Implement screenshots 2021-07-28 21:45:55 -04:00
ameerj 548bac8989 vk_blit_screen: Add public CreateFramebuffer method 2021-07-28 21:43:02 -04:00
ameerj 1e6c5d323d vk_blit_screen: Make Draw method more generic
Allows specifying the framebuffer and render area dimensions, rather than being hard coded for the render window.
2021-07-28 21:37:30 -04:00
35 changed files with 717 additions and 404 deletions
+35 -4
View File
@@ -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,15 +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.
@@ -618,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
@@ -630,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}")
+6 -2
View File
@@ -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!")
+1 -1
View File
@@ -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);
}
+54
View File
@@ -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());
+19
View File
@@ -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;
@@ -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));
@@ -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(), &params, 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(&params, 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(), &params, 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
@@ -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
+2 -1
View File
@@ -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);
+1 -1
View File
@@ -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) {
+5
View File
@@ -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
+110 -34
View File
@@ -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
@@ -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;
+44 -88
View File
@@ -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;
}
union {
u32 raw;
BitField<0, 1, u32> refresh_last;
BitField<1, 2, u32> refresh_golden;
BitField<2, 1, u32> refresh_alt;
} refresh_frame_flags;
// 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;
}
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 (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.show_frame) {
// Update altref
refresh_frame_flags = swap_next_golden ? 2 : 4;
} else if (golden_refresh) {
refresh_frame_flags = 3;
}
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;
}
+3 -11
View File
@@ -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
@@ -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")
+1 -1
View File
@@ -39,7 +39,7 @@ void Nvdec::Execute() {
codec->Decode();
break;
default:
UNIMPLEMENTED_MSG("Unknown codec {}", static_cast<u32>(codec->GetCurrentCodec()));
UNIMPLEMENTED_MSG("Codec {}", codec->GetCurrentCodecName());
break;
}
}
+54 -33
View File
@@ -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;
}
gpu.MemoryManager().WriteBlock(output_surface_chroma_u_address, chroma_buffer.data(),
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_address, chroma_buffer.data(),
chroma_buffer.size());
break;
}
+3 -4
View File
@@ -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{};
+1 -1
View File
@@ -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");
+2 -2
View File
@@ -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:
@@ -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;
}
@@ -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
@@ -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;
@@ -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);
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();
});
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());
@@ -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,
+43 -9
View File
@@ -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,
+2 -2
View File
@@ -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);
+10 -7
View File
@@ -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);
+38 -12
View File
@@ -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);
}
}
+18 -4
View File
@@ -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>
+2 -2
View File
@@ -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(
+6 -7
View File
@@ -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),
+2 -1
View File
@@ -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};
+3
View File
@@ -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);