Browse Source
Merge pull request #4729 from ameerj/nvdec-prod
Merge pull request #4729 from ameerj/nvdec-prod
video_core: NVDEC Implementationnce_cpp
committed by
GitHub
53 changed files with 4033 additions and 310 deletions
-
14CMakeLists.txt
-
10CMakeModules/CopyYuzuFFmpegDeps.cmake
-
100externals/find-modules/FindFFmpeg.cmake
-
2src/common/CMakeLists.txt
-
47src/common/stream.cpp
-
50src/common/stream.h
-
2src/core/CMakeLists.txt
-
100src/core/hle/service/nvdrv/devices/nvhost_nvdec.cpp
-
71src/core/hle/service/nvdrv/devices/nvhost_nvdec.h
-
234src/core/hle/service/nvdrv/devices/nvhost_nvdec_common.cpp
-
168src/core/hle/service/nvdrv/devices/nvhost_nvdec_common.h
-
90src/core/hle/service/nvdrv/devices/nvhost_vic.cpp
-
88src/core/hle/service/nvdrv/devices/nvhost_vic.h
-
1src/core/hle/service/nvdrv/devices/nvmap.h
-
4src/core/hle/service/nvdrv/nvdrv.cpp
-
2src/core/settings.cpp
-
1src/core/settings.h
-
2src/core/telemetry_session.cpp
-
26src/video_core/CMakeLists.txt
-
171src/video_core/cdma_pusher.cpp
-
138src/video_core/cdma_pusher.h
-
114src/video_core/command_classes/codecs/codec.cpp
-
68src/video_core/command_classes/codecs/codec.h
-
276src/video_core/command_classes/codecs/h264.cpp
-
130src/video_core/command_classes/codecs/h264.h
-
1010src/video_core/command_classes/codecs/vp9.cpp
-
216src/video_core/command_classes/codecs/vp9.h
-
369src/video_core/command_classes/codecs/vp9_types.h
-
39src/video_core/command_classes/host1x.cpp
-
78src/video_core/command_classes/host1x.h
-
56src/video_core/command_classes/nvdec.cpp
-
39src/video_core/command_classes/nvdec.h
-
48src/video_core/command_classes/nvdec_common.h
-
60src/video_core/command_classes/sync_manager.cpp
-
64src/video_core/command_classes/sync_manager.h
-
180src/video_core/command_classes/vic.cpp
-
110src/video_core/command_classes/vic.h
-
11src/video_core/gpu.cpp
-
23src/video_core/gpu.h
-
26src/video_core/gpu_asynch.cpp
-
3src/video_core/gpu_asynch.h
-
18src/video_core/gpu_synch.cpp
-
3src/video_core/gpu_synch.h
-
16src/video_core/gpu_thread.cpp
-
19src/video_core/gpu_thread.h
-
12src/video_core/memory_manager.cpp
-
5src/video_core/memory_manager.h
-
5src/video_core/video_core.cpp
-
2src/yuzu/CMakeLists.txt
-
4src/yuzu/configuration/config.cpp
-
10src/yuzu/configuration/configure_graphics.cpp
-
1src/yuzu/configuration/configure_graphics.h
-
7src/yuzu/configuration/configure_graphics.ui
@ -0,0 +1,10 @@ |
|||
function(copy_yuzu_FFmpeg_deps target_dir) |
|||
include(WindowsCopyFiles) |
|||
set(DLL_DEST "${CMAKE_BINARY_DIR}/bin/$<CONFIG>/") |
|||
windows_copy_files(${target_dir} ${FFMPEG_DLL_DIR} ${DLL_DEST} |
|||
avcodec-58.dll |
|||
avutil-56.dll |
|||
swresample-3.dll |
|||
swscale-5.dll |
|||
) |
|||
endfunction(copy_yuzu_FFmpeg_deps) |
|||
@ -0,0 +1,100 @@ |
|||
# - Try to find ffmpeg libraries (libavcodec, libavformat and libavutil) |
|||
# Once done this will define |
|||
# |
|||
# FFMPEG_FOUND - system has ffmpeg or libav |
|||
# FFMPEG_INCLUDE_DIR - the ffmpeg include directory |
|||
# FFMPEG_LIBRARIES - Link these to use ffmpeg |
|||
# FFMPEG_LIBAVCODEC |
|||
# FFMPEG_LIBAVFORMAT |
|||
# FFMPEG_LIBAVUTIL |
|||
# |
|||
# Copyright (c) 2008 Andreas Schneider <mail@cynapses.org> |
|||
# Modified for other libraries by Lasse Kärkkäinen <tronic> |
|||
# Modified for Hedgewars by Stepik777 |
|||
# Modified for FFmpeg-example Tuukka Pasanen 2018 |
|||
# Modified for yuzu toastUnlimted 2020 |
|||
# |
|||
# Redistribution and use is allowed according to the terms of the New |
|||
# BSD license. |
|||
# |
|||
|
|||
include(FindPackageHandleStandardArgs) |
|||
|
|||
find_package_handle_standard_args(FFMPEG |
|||
FOUND_VAR FFMPEG_FOUND |
|||
REQUIRED_VARS |
|||
FFMPEG_LIBRARY |
|||
FFMPEG_INCLUDE_DIR |
|||
VERSION_VAR FFMPEG_VERSION |
|||
) |
|||
|
|||
if(FFMPEG_LIBRARIES AND FFMPEG_INCLUDE_DIR) |
|||
# in cache already |
|||
set(FFMPEG_FOUND TRUE) |
|||
else() |
|||
# use pkg-config to get the directories and then use these values |
|||
# in the FIND_PATH() and FIND_LIBRARY() calls |
|||
find_package(PkgConfig) |
|||
if(PKG_CONFIG_FOUND) |
|||
pkg_check_modules(_FFMPEG_AVCODEC libavcodec) |
|||
pkg_check_modules(_FFMPEG_AVUTIL libavutil) |
|||
pkg_check_modules(_FFMPEG_SWSCALE libswscale) |
|||
endif() |
|||
|
|||
find_path(FFMPEG_AVCODEC_INCLUDE_DIR |
|||
NAMES libavcodec/avcodec.h |
|||
PATHS ${_FFMPEG_AVCODEC_INCLUDE_DIRS} |
|||
/usr/include |
|||
/usr/local/include |
|||
/opt/local/include |
|||
/sw/include |
|||
PATH_SUFFIXES ffmpeg libav) |
|||
|
|||
find_library(FFMPEG_LIBAVCODEC |
|||
NAMES avcodec |
|||
PATHS ${_FFMPEG_AVCODEC_LIBRARY_DIRS} |
|||
/usr/lib |
|||
/usr/local/lib |
|||
/opt/local/lib |
|||
/sw/lib) |
|||
|
|||
find_library(FFMPEG_LIBAVUTIL |
|||
NAMES avutil |
|||
PATHS ${_FFMPEG_AVUTIL_LIBRARY_DIRS} |
|||
/usr/lib |
|||
/usr/local/lib |
|||
/opt/local/lib |
|||
/sw/lib) |
|||
|
|||
find_library(FFMPEG_LIBSWSCALE |
|||
NAMES swscale |
|||
PATHS ${_FFMPEG_SWSCALE_LIBRARY_DIRS} |
|||
/usr/lib |
|||
/usr/local/lib |
|||
/opt/local/lib |
|||
/sw/lib) |
|||
|
|||
if(FFMPEG_LIBAVCODEC AND FFMPEG_LIBAVUTIL AND FFMPEG_LIBSWSCALE) |
|||
set(FFMPEG_FOUND TRUE) |
|||
endif() |
|||
|
|||
if(FFMPEG_FOUND) |
|||
set(FFMPEG_INCLUDE_DIR ${FFMPEG_AVCODEC_INCLUDE_DIR}) |
|||
set(FFMPEG_LIBRARIES |
|||
${FFMPEG_LIBAVCODEC} |
|||
${FFMPEG_LIBAVUTIL} |
|||
${FFMPEG_LIBSWSCALE}) |
|||
endif() |
|||
|
|||
if(FFMPEG_FOUND) |
|||
if(NOT FFMPEG_FIND_QUIETLY) |
|||
message(STATUS |
|||
"Found FFMPEG or Libav: ${FFMPEG_LIBRARIES}, ${FFMPEG_INCLUDE_DIR}") |
|||
endif() |
|||
else() |
|||
if(FFMPEG_FIND_REQUIRED) |
|||
message(FATAL_ERROR |
|||
"Could not find libavcodec or libavutil or libswscale") |
|||
endif() |
|||
endif() |
|||
endif() |
|||
@ -0,0 +1,47 @@ |
|||
// Copyright 2020 yuzu Emulator Project
|
|||
// Licensed under GPLv2 or any later version
|
|||
// Refer to the license.txt file included.
|
|||
|
|||
#include <stdexcept>
|
|||
#include "common/common_types.h"
|
|||
#include "common/stream.h"
|
|||
|
|||
namespace Common { |
|||
|
|||
Stream::Stream() = default; |
|||
Stream::~Stream() = default; |
|||
|
|||
void Stream::Seek(s32 offset, SeekOrigin origin) { |
|||
if (origin == SeekOrigin::SetOrigin) { |
|||
if (offset < 0) { |
|||
position = 0; |
|||
} else if (position >= buffer.size()) { |
|||
position = buffer.size(); |
|||
} else { |
|||
position = offset; |
|||
} |
|||
} else if (origin == SeekOrigin::FromCurrentPos) { |
|||
Seek(static_cast<s32>(position) + offset, SeekOrigin::SetOrigin); |
|||
} else if (origin == SeekOrigin::FromEnd) { |
|||
Seek(static_cast<s32>(buffer.size()) - offset, SeekOrigin::SetOrigin); |
|||
} |
|||
} |
|||
|
|||
u8 Stream::ReadByte() { |
|||
if (position < buffer.size()) { |
|||
return buffer[position++]; |
|||
} else { |
|||
throw std::out_of_range("Attempting to read a byte not within the buffer range"); |
|||
} |
|||
} |
|||
|
|||
void Stream::WriteByte(u8 byte) { |
|||
if (position == buffer.size()) { |
|||
buffer.push_back(byte); |
|||
position++; |
|||
} else { |
|||
buffer.insert(buffer.begin() + position, byte); |
|||
} |
|||
} |
|||
|
|||
} // namespace Common
|
|||
@ -0,0 +1,50 @@ |
|||
// Copyright 2020 yuzu Emulator Project |
|||
// Licensed under GPLv2 or any later version |
|||
// Refer to the license.txt file included. |
|||
|
|||
#pragma once |
|||
|
|||
#include <vector> |
|||
#include "common/common_types.h" |
|||
|
|||
namespace Common { |
|||
|
|||
enum class SeekOrigin { |
|||
SetOrigin, |
|||
FromCurrentPos, |
|||
FromEnd, |
|||
}; |
|||
|
|||
class Stream { |
|||
public: |
|||
/// Stream creates a bitstream and provides common functionality on the stream. |
|||
explicit Stream(); |
|||
~Stream(); |
|||
|
|||
/// Reposition bitstream "cursor" to the specified offset from origin |
|||
void Seek(s32 offset, SeekOrigin origin); |
|||
|
|||
/// Reads next byte in the stream buffer and increments position |
|||
u8 ReadByte(); |
|||
|
|||
/// Writes byte at current position |
|||
void WriteByte(u8 byte); |
|||
|
|||
std::size_t GetPosition() const { |
|||
return position; |
|||
} |
|||
|
|||
std::vector<u8>& GetBuffer() { |
|||
return buffer; |
|||
} |
|||
|
|||
const std::vector<u8>& GetBuffer() const { |
|||
return buffer; |
|||
} |
|||
|
|||
private: |
|||
std::vector<u8> buffer; |
|||
std::size_t position{0}; |
|||
}; |
|||
|
|||
} // namespace Common |
|||
@ -0,0 +1,234 @@ |
|||
// Copyright 2020 yuzu emulator team
|
|||
// Licensed under GPLv2 or any later version
|
|||
// Refer to the license.txt file included.
|
|||
|
|||
#include <algorithm>
|
|||
#include <cstring>
|
|||
|
|||
#include "common/assert.h"
|
|||
#include "common/common_types.h"
|
|||
#include "common/logging/log.h"
|
|||
#include "core/core.h"
|
|||
#include "core/hle/service/nvdrv/devices/nvhost_nvdec_common.h"
|
|||
#include "core/hle/service/nvdrv/devices/nvmap.h"
|
|||
#include "core/memory.h"
|
|||
#include "video_core/memory_manager.h"
|
|||
#include "video_core/renderer_base.h"
|
|||
|
|||
namespace Service::Nvidia::Devices { |
|||
|
|||
namespace { |
|||
// Splice vectors will copy count amount of type T from the input vector into the dst vector.
|
|||
template <typename T> |
|||
std::size_t SpliceVectors(const std::vector<u8>& input, std::vector<T>& dst, std::size_t count, |
|||
std::size_t offset) { |
|||
std::memcpy(dst.data(), input.data() + offset, count * sizeof(T)); |
|||
offset += count * sizeof(T); |
|||
return offset; |
|||
} |
|||
|
|||
// Write vectors will write data to the output buffer
|
|||
template <typename T> |
|||
std::size_t WriteVectors(std::vector<u8>& dst, const std::vector<T>& src, std::size_t offset) { |
|||
std::memcpy(dst.data() + offset, src.data(), src.size() * sizeof(T)); |
|||
offset += src.size() * sizeof(T); |
|||
return offset; |
|||
} |
|||
} // Anonymous namespace
|
|||
|
|||
namespace NvErrCodes { |
|||
constexpr u32 Success{}; |
|||
constexpr u32 OutOfMemory{static_cast<u32>(-12)}; |
|||
constexpr u32 InvalidInput{static_cast<u32>(-22)}; |
|||
} // namespace NvErrCodes
|
|||
|
|||
nvhost_nvdec_common::nvhost_nvdec_common(Core::System& system, std::shared_ptr<nvmap> nvmap_dev) |
|||
: nvdevice(system), nvmap_dev(std::move(nvmap_dev)) {} |
|||
nvhost_nvdec_common::~nvhost_nvdec_common() = default; |
|||
|
|||
u32 nvhost_nvdec_common::SetNVMAPfd(const std::vector<u8>& input) { |
|||
IoctlSetNvmapFD params{}; |
|||
std::memcpy(¶ms, input.data(), sizeof(IoctlSetNvmapFD)); |
|||
LOG_DEBUG(Service_NVDRV, "called, fd={}", params.nvmap_fd); |
|||
|
|||
nvmap_fd = params.nvmap_fd; |
|||
return 0; |
|||
} |
|||
|
|||
u32 nvhost_nvdec_common::Submit(const std::vector<u8>& input, std::vector<u8>& output) { |
|||
IoctlSubmit params{}; |
|||
std::memcpy(¶ms, input.data(), sizeof(IoctlSubmit)); |
|||
LOG_DEBUG(Service_NVDRV, "called NVDEC Submit, cmd_buffer_count={}", params.cmd_buffer_count); |
|||
|
|||
// Instantiate param buffers
|
|||
std::size_t offset = sizeof(IoctlSubmit); |
|||
std::vector<CommandBuffer> command_buffers(params.cmd_buffer_count); |
|||
std::vector<Reloc> relocs(params.relocation_count); |
|||
std::vector<u32> reloc_shifts(params.relocation_count); |
|||
std::vector<SyncptIncr> syncpt_increments(params.syncpoint_count); |
|||
std::vector<SyncptIncr> wait_checks(params.syncpoint_count); |
|||
std::vector<Fence> fences(params.fence_count); |
|||
|
|||
// Splice input into their respective buffers
|
|||
offset = SpliceVectors(input, command_buffers, params.cmd_buffer_count, offset); |
|||
offset = SpliceVectors(input, relocs, params.relocation_count, offset); |
|||
offset = SpliceVectors(input, reloc_shifts, params.relocation_count, offset); |
|||
offset = SpliceVectors(input, syncpt_increments, params.syncpoint_count, offset); |
|||
offset = SpliceVectors(input, wait_checks, params.syncpoint_count, offset); |
|||
offset = SpliceVectors(input, fences, params.fence_count, offset); |
|||
|
|||
// TODO(ameerj): For async gpu, utilize fences for syncpoint 'max' increment
|
|||
|
|||
auto& gpu = system.GPU(); |
|||
|
|||
for (const auto& cmd_buffer : command_buffers) { |
|||
auto object = nvmap_dev->GetObject(cmd_buffer.memory_id); |
|||
ASSERT_OR_EXECUTE(object, return NvErrCodes::InvalidInput;); |
|||
const auto map = FindBufferMap(object->dma_map_addr); |
|||
if (!map) { |
|||
LOG_ERROR(Service_NVDRV, "Tried to submit an invalid offset 0x{:X} dma 0x{:X}", |
|||
object->addr, object->dma_map_addr); |
|||
return 0; |
|||
} |
|||
Tegra::ChCommandHeaderList cmdlist(cmd_buffer.word_count); |
|||
gpu.MemoryManager().ReadBlock(map->StartAddr() + cmd_buffer.offset, cmdlist.data(), |
|||
cmdlist.size() * sizeof(u32)); |
|||
gpu.PushCommandBuffer(cmdlist); |
|||
} |
|||
|
|||
std::memcpy(output.data(), ¶ms, sizeof(IoctlSubmit)); |
|||
// Some games expect command_buffers to be written back
|
|||
offset = sizeof(IoctlSubmit); |
|||
offset = WriteVectors(output, command_buffers, offset); |
|||
offset = WriteVectors(output, relocs, offset); |
|||
offset = WriteVectors(output, reloc_shifts, offset); |
|||
offset = WriteVectors(output, syncpt_increments, offset); |
|||
offset = WriteVectors(output, wait_checks, offset); |
|||
|
|||
return NvErrCodes::Success; |
|||
} |
|||
|
|||
u32 nvhost_nvdec_common::GetSyncpoint(const std::vector<u8>& input, std::vector<u8>& output) { |
|||
IoctlGetSyncpoint params{}; |
|||
std::memcpy(¶ms, input.data(), sizeof(IoctlGetSyncpoint)); |
|||
LOG_DEBUG(Service_NVDRV, "called GetSyncpoint, id={}", params.param); |
|||
|
|||
// We found that implementing this causes deadlocks with async gpu, along with degraded
|
|||
// performance. TODO: RE the nvdec async implementation
|
|||
params.value = 0; |
|||
std::memcpy(output.data(), ¶ms, sizeof(IoctlGetSyncpoint)); |
|||
|
|||
return NvErrCodes::Success; |
|||
} |
|||
|
|||
u32 nvhost_nvdec_common::GetWaitbase(const std::vector<u8>& input, std::vector<u8>& output) { |
|||
IoctlGetWaitbase params{}; |
|||
std::memcpy(¶ms, input.data(), sizeof(IoctlGetWaitbase)); |
|||
params.value = 0; // Seems to be hard coded at 0
|
|||
std::memcpy(output.data(), ¶ms, sizeof(IoctlGetWaitbase)); |
|||
return 0; |
|||
} |
|||
|
|||
u32 nvhost_nvdec_common::MapBuffer(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); |
|||
|
|||
SpliceVectors(input, cmd_buffer_handles, params.num_entries, sizeof(IoctlMapBuffer)); |
|||
|
|||
auto& gpu = system.GPU(); |
|||
|
|||
for (auto& cmf_buff : cmd_buffer_handles) { |
|||
auto object{nvmap_dev->GetObject(cmf_buff.map_handle)}; |
|||
if (!object) { |
|||
LOG_ERROR(Service_NVDRV, "invalid cmd_buffer nvmap_handle={:X}", cmf_buff.map_handle); |
|||
std::memcpy(output.data(), ¶ms, output.size()); |
|||
return NvErrCodes::InvalidInput; |
|||
} |
|||
if (object->dma_map_addr == 0) { |
|||
// NVDEC and VIC memory is in the 32-bit address space
|
|||
// MapAllocate32 will attempt to map a lower 32-bit value in the shared gpu memory space
|
|||
const GPUVAddr low_addr = gpu.MemoryManager().MapAllocate32(object->addr, object->size); |
|||
object->dma_map_addr = static_cast<u32>(low_addr); |
|||
// Ensure that the dma_map_addr is indeed in the lower 32-bit address space.
|
|||
ASSERT(object->dma_map_addr == low_addr); |
|||
} |
|||
if (!object->dma_map_addr) { |
|||
LOG_ERROR(Service_NVDRV, "failed to map size={}", object->size); |
|||
} else { |
|||
cmf_buff.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)); |
|||
std::memcpy(output.data() + sizeof(IoctlMapBuffer), cmd_buffer_handles.data(), |
|||
cmd_buffer_handles.size() * sizeof(MapBufferEntry)); |
|||
|
|||
return NvErrCodes::Success; |
|||
} |
|||
|
|||
u32 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); |
|||
SpliceVectors(input, cmd_buffer_handles, params.num_entries, sizeof(IoctlMapBuffer)); |
|||
|
|||
auto& gpu = system.GPU(); |
|||
|
|||
for (auto& cmf_buff : cmd_buffer_handles) { |
|||
const auto object{nvmap_dev->GetObject(cmf_buff.map_handle)}; |
|||
if (!object) { |
|||
LOG_ERROR(Service_NVDRV, "invalid cmd_buffer nvmap_handle={:X}", cmf_buff.map_handle); |
|||
std::memcpy(output.data(), ¶ms, output.size()); |
|||
return NvErrCodes::InvalidInput; |
|||
} |
|||
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; |
|||
} |
|||
std::memset(output.data(), 0, output.size()); |
|||
return NvErrCodes::Success; |
|||
} |
|||
|
|||
u32 nvhost_nvdec_common::SetSubmitTimeout(const std::vector<u8>& input, std::vector<u8>& output) { |
|||
std::memcpy(&submit_timeout, input.data(), input.size()); |
|||
LOG_WARNING(Service_NVDRV, "(STUBBED) called"); |
|||
return NvErrCodes::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
|
|||
@ -0,0 +1,168 @@ |
|||
// Copyright 2020 yuzu emulator team |
|||
// Licensed under GPLv2 or any later version |
|||
// Refer to the license.txt file included. |
|||
|
|||
#pragma once |
|||
|
|||
#include <map> |
|||
#include <vector> |
|||
#include "common/common_types.h" |
|||
#include "common/swap.h" |
|||
#include "core/hle/service/nvdrv/devices/nvdevice.h" |
|||
|
|||
namespace Service::Nvidia::Devices { |
|||
class nvmap; |
|||
|
|||
class nvhost_nvdec_common : public nvdevice { |
|||
public: |
|||
explicit nvhost_nvdec_common(Core::System& system, std::shared_ptr<nvmap> nvmap_dev); |
|||
~nvhost_nvdec_common() override; |
|||
|
|||
virtual u32 ioctl(Ioctl command, const std::vector<u8>& input, const std::vector<u8>& input2, |
|||
std::vector<u8>& output, std::vector<u8>& output2, IoctlCtrl& ctrl, |
|||
IoctlVersion version) = 0; |
|||
|
|||
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 { |
|||
u32_le nvmap_fd; |
|||
}; |
|||
static_assert(sizeof(IoctlSetNvmapFD) == 4, "IoctlSetNvmapFD is incorrect size"); |
|||
|
|||
struct IoctlSubmitCommandBuffer { |
|||
u32_le id; |
|||
u32_le offset; |
|||
u32_le count; |
|||
}; |
|||
static_assert(sizeof(IoctlSubmitCommandBuffer) == 0xC, |
|||
"IoctlSubmitCommandBuffer is incorrect size"); |
|||
struct IoctlSubmit { |
|||
u32_le cmd_buffer_count; |
|||
u32_le relocation_count; |
|||
u32_le syncpoint_count; |
|||
u32_le fence_count; |
|||
}; |
|||
static_assert(sizeof(IoctlSubmit) == 0x10, "IoctlSubmit has incorrect size"); |
|||
|
|||
struct CommandBuffer { |
|||
s32 memory_id; |
|||
u32 offset; |
|||
s32 word_count; |
|||
}; |
|||
static_assert(sizeof(CommandBuffer) == 0xC, "CommandBuffer has incorrect size"); |
|||
|
|||
struct Reloc { |
|||
s32 cmdbuffer_memory; |
|||
s32 cmdbuffer_offset; |
|||
s32 target; |
|||
s32 target_offset; |
|||
}; |
|||
static_assert(sizeof(Reloc) == 0x10, "CommandBuffer has incorrect size"); |
|||
|
|||
struct SyncptIncr { |
|||
u32 id; |
|||
u32 increments; |
|||
}; |
|||
static_assert(sizeof(SyncptIncr) == 0x8, "CommandBuffer has incorrect size"); |
|||
|
|||
struct Fence { |
|||
u32 id; |
|||
u32 value; |
|||
}; |
|||
static_assert(sizeof(Fence) == 0x8, "CommandBuffer has incorrect size"); |
|||
|
|||
struct IoctlGetSyncpoint { |
|||
// Input |
|||
u32_le param; |
|||
// Output |
|||
u32_le value; |
|||
}; |
|||
static_assert(sizeof(IoctlGetSyncpoint) == 8, "IocGetIdParams has wrong size"); |
|||
|
|||
struct IoctlGetWaitbase { |
|||
u32_le unknown; // seems to be ignored? Nintendo added this |
|||
u32_le value; |
|||
}; |
|||
static_assert(sizeof(IoctlGetWaitbase) == 0x8, "IoctlGetWaitbase is incorrect size"); |
|||
|
|||
struct IoctlMapBuffer { |
|||
u32_le num_entries; |
|||
u32_le data_address; // Ignored by the driver. |
|||
u32_le attach_host_ch_das; |
|||
}; |
|||
static_assert(sizeof(IoctlMapBuffer) == 0x0C, "IoctlMapBuffer is incorrect size"); |
|||
|
|||
struct IocGetIdParams { |
|||
// Input |
|||
u32_le param; |
|||
// Output |
|||
u32_le value; |
|||
}; |
|||
static_assert(sizeof(IocGetIdParams) == 8, "IocGetIdParams has wrong size"); |
|||
|
|||
// Used for mapping and unmapping command buffers |
|||
struct MapBufferEntry { |
|||
u32_le map_handle; |
|||
u32_le map_address; |
|||
}; |
|||
static_assert(sizeof(IoctlMapBuffer) == 0x0C, "IoctlMapBuffer is incorrect size"); |
|||
|
|||
/// Ioctl command implementations |
|||
u32 SetNVMAPfd(const std::vector<u8>& input); |
|||
u32 Submit(const std::vector<u8>& input, std::vector<u8>& output); |
|||
u32 GetSyncpoint(const std::vector<u8>& input, std::vector<u8>& output); |
|||
u32 GetWaitbase(const std::vector<u8>& input, std::vector<u8>& output); |
|||
u32 MapBuffer(const std::vector<u8>& input, std::vector<u8>& output); |
|||
u32 UnmapBuffer(const std::vector<u8>& input, std::vector<u8>& output); |
|||
u32 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); |
|||
|
|||
u32_le nvmap_fd{}; |
|||
u32_le submit_timeout{}; |
|||
std::shared_ptr<nvmap> nvmap_dev; |
|||
|
|||
// This is expected to be ordered, therefore we must use a map, not unordered_map |
|||
std::map<GPUVAddr, BufferMap> buffer_mappings; |
|||
}; |
|||
}; // namespace Service::Nvidia::Devices |
|||
@ -0,0 +1,171 @@ |
|||
// MIT License
|
|||
//
|
|||
// Copyright (c) Ryujinx Team and Contributors
|
|||
//
|
|||
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
|
|||
// associated documentation files (the "Software"), to deal in the Software without restriction,
|
|||
// including without limitation the rights to use, copy, modify, merge, publish, distribute,
|
|||
// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
|
|||
// furnished to do so, subject to the following conditions:
|
|||
//
|
|||
// The above copyright notice and this permission notice shall be included in all copies or
|
|||
// substantial portions of the Software.
|
|||
//
|
|||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
|
|||
// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
|||
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|||
//
|
|||
|
|||
#include "command_classes/host1x.h"
|
|||
#include "command_classes/nvdec.h"
|
|||
#include "command_classes/vic.h"
|
|||
#include "common/bit_util.h"
|
|||
#include "video_core/cdma_pusher.h"
|
|||
#include "video_core/command_classes/nvdec_common.h"
|
|||
#include "video_core/engines/maxwell_3d.h"
|
|||
#include "video_core/gpu.h"
|
|||
#include "video_core/memory_manager.h"
|
|||
|
|||
namespace Tegra { |
|||
CDmaPusher::CDmaPusher(GPU& gpu) |
|||
: gpu(gpu), nvdec_processor(std::make_shared<Nvdec>(gpu)), |
|||
vic_processor(std::make_unique<Vic>(gpu, nvdec_processor)), |
|||
host1x_processor(std::make_unique<Host1x>(gpu)), |
|||
nvdec_sync(std::make_unique<SyncptIncrManager>(gpu)), |
|||
vic_sync(std::make_unique<SyncptIncrManager>(gpu)) {} |
|||
|
|||
CDmaPusher::~CDmaPusher() = default; |
|||
|
|||
void CDmaPusher::Push(ChCommandHeaderList&& entries) { |
|||
cdma_queue.push(std::move(entries)); |
|||
} |
|||
|
|||
void CDmaPusher::DispatchCalls() { |
|||
while (!cdma_queue.empty()) { |
|||
Step(); |
|||
} |
|||
} |
|||
|
|||
void CDmaPusher::Step() { |
|||
const auto entries{cdma_queue.front()}; |
|||
cdma_queue.pop(); |
|||
|
|||
std::vector<u32> values(entries.size()); |
|||
std::memcpy(values.data(), entries.data(), entries.size() * sizeof(u32)); |
|||
|
|||
for (const u32 value : values) { |
|||
if (mask != 0) { |
|||
const u32 lbs = Common::CountTrailingZeroes32(mask); |
|||
mask &= ~(1U << lbs); |
|||
ExecuteCommand(static_cast<u32>(offset + lbs), value); |
|||
continue; |
|||
} else if (count != 0) { |
|||
--count; |
|||
ExecuteCommand(static_cast<u32>(offset), value); |
|||
if (incrementing) { |
|||
++offset; |
|||
} |
|||
continue; |
|||
} |
|||
const auto mode = static_cast<ChSubmissionMode>((value >> 28) & 0xf); |
|||
switch (mode) { |
|||
case ChSubmissionMode::SetClass: { |
|||
mask = value & 0x3f; |
|||
offset = (value >> 16) & 0xfff; |
|||
current_class = static_cast<ChClassId>((value >> 6) & 0x3ff); |
|||
break; |
|||
} |
|||
case ChSubmissionMode::Incrementing: |
|||
case ChSubmissionMode::NonIncrementing: |
|||
count = value & 0xffff; |
|||
offset = (value >> 16) & 0xfff; |
|||
incrementing = mode == ChSubmissionMode::Incrementing; |
|||
break; |
|||
case ChSubmissionMode::Mask: |
|||
mask = value & 0xffff; |
|||
offset = (value >> 16) & 0xfff; |
|||
break; |
|||
case ChSubmissionMode::Immediate: { |
|||
const u32 data = value & 0xfff; |
|||
offset = (value >> 16) & 0xfff; |
|||
ExecuteCommand(static_cast<u32>(offset), data); |
|||
break; |
|||
} |
|||
default: |
|||
UNIMPLEMENTED_MSG("ChSubmission mode {} is not implemented!", static_cast<u32>(mode)); |
|||
break; |
|||
} |
|||
} |
|||
} |
|||
|
|||
void CDmaPusher::ExecuteCommand(u32 offset, u32 data) { |
|||
switch (current_class) { |
|||
case ChClassId::NvDec: |
|||
ThiStateWrite(nvdec_thi_state, offset, {data}); |
|||
switch (static_cast<ThiMethod>(offset)) { |
|||
case ThiMethod::IncSyncpt: { |
|||
LOG_DEBUG(Service_NVDRV, "NVDEC Class IncSyncpt Method"); |
|||
const auto syncpoint_id = static_cast<u32>(data & 0xFF); |
|||
const auto cond = static_cast<u32>((data >> 8) & 0xFF); |
|||
if (cond == 0) { |
|||
nvdec_sync->Increment(syncpoint_id); |
|||
} else { |
|||
nvdec_sync->IncrementWhenDone(static_cast<u32>(current_class), syncpoint_id); |
|||
nvdec_sync->SignalDone(syncpoint_id); |
|||
} |
|||
break; |
|||
} |
|||
case ThiMethod::SetMethod1: |
|||
LOG_DEBUG(Service_NVDRV, "NVDEC method 0x{:X}", |
|||
static_cast<u32>(nvdec_thi_state.method_0)); |
|||
nvdec_processor->ProcessMethod( |
|||
static_cast<Tegra::Nvdec::Method>(nvdec_thi_state.method_0), {data}); |
|||
break; |
|||
default: |
|||
break; |
|||
} |
|||
break; |
|||
case ChClassId::GraphicsVic: |
|||
ThiStateWrite(vic_thi_state, static_cast<u32>(offset), {data}); |
|||
switch (static_cast<ThiMethod>(offset)) { |
|||
case ThiMethod::IncSyncpt: { |
|||
LOG_DEBUG(Service_NVDRV, "VIC Class IncSyncpt Method"); |
|||
const auto syncpoint_id = static_cast<u32>(data & 0xFF); |
|||
const auto cond = static_cast<u32>((data >> 8) & 0xFF); |
|||
if (cond == 0) { |
|||
vic_sync->Increment(syncpoint_id); |
|||
} else { |
|||
vic_sync->IncrementWhenDone(static_cast<u32>(current_class), syncpoint_id); |
|||
vic_sync->SignalDone(syncpoint_id); |
|||
} |
|||
break; |
|||
} |
|||
case ThiMethod::SetMethod1: |
|||
LOG_DEBUG(Service_NVDRV, "VIC method 0x{:X}, Args=({})", |
|||
static_cast<u32>(vic_thi_state.method_0)); |
|||
vic_processor->ProcessMethod(static_cast<Tegra::Vic::Method>(vic_thi_state.method_0), |
|||
{data}); |
|||
break; |
|||
default: |
|||
break; |
|||
} |
|||
break; |
|||
case ChClassId::Host1x: |
|||
// This device is mainly for syncpoint synchronization
|
|||
LOG_DEBUG(Service_NVDRV, "Host1X Class Method"); |
|||
host1x_processor->ProcessMethod(static_cast<Tegra::Host1x::Method>(offset), {data}); |
|||
break; |
|||
default: |
|||
UNIMPLEMENTED_MSG("Current class not implemented {:X}", static_cast<u32>(current_class)); |
|||
break; |
|||
} |
|||
} |
|||
|
|||
void CDmaPusher::ThiStateWrite(ThiRegisters& state, u32 offset, const std::vector<u32>& arguments) { |
|||
u8* const state_offset = reinterpret_cast<u8*>(&state) + sizeof(u32) * offset; |
|||
std::memcpy(state_offset, arguments.data(), sizeof(u32) * arguments.size()); |
|||
} |
|||
|
|||
} // namespace Tegra
|
|||
@ -0,0 +1,138 @@ |
|||
// Copyright 2020 yuzu Emulator Project |
|||
// Licensed under GPLv2 or any later version |
|||
// Refer to the license.txt file included. |
|||
|
|||
#pragma once |
|||
|
|||
#include <memory> |
|||
#include <unordered_map> |
|||
#include <vector> |
|||
#include <queue> |
|||
|
|||
#include "common/bit_field.h" |
|||
#include "common/common_types.h" |
|||
#include "video_core/command_classes/sync_manager.h" |
|||
|
|||
namespace Tegra { |
|||
|
|||
class GPU; |
|||
class Nvdec; |
|||
class Vic; |
|||
class Host1x; |
|||
|
|||
enum class ChSubmissionMode : u32 { |
|||
SetClass = 0, |
|||
Incrementing = 1, |
|||
NonIncrementing = 2, |
|||
Mask = 3, |
|||
Immediate = 4, |
|||
Restart = 5, |
|||
Gather = 6, |
|||
}; |
|||
|
|||
enum class ChClassId : u32 { |
|||
NoClass = 0x0, |
|||
Host1x = 0x1, |
|||
VideoEncodeMpeg = 0x20, |
|||
VideoEncodeNvEnc = 0x21, |
|||
VideoStreamingVi = 0x30, |
|||
VideoStreamingIsp = 0x32, |
|||
VideoStreamingIspB = 0x34, |
|||
VideoStreamingViI2c = 0x36, |
|||
GraphicsVic = 0x5d, |
|||
Graphics3D = 0x60, |
|||
GraphicsGpu = 0x61, |
|||
Tsec = 0xe0, |
|||
TsecB = 0xe1, |
|||
NvJpg = 0xc0, |
|||
NvDec = 0xf0 |
|||
}; |
|||
|
|||
enum class ChMethod : u32 { |
|||
Empty = 0, |
|||
SetMethod = 0x10, |
|||
SetData = 0x11, |
|||
}; |
|||
|
|||
union ChCommandHeader { |
|||
u32 raw; |
|||
BitField<0, 16, u32> value; |
|||
BitField<16, 12, ChMethod> method_offset; |
|||
BitField<28, 4, ChSubmissionMode> submission_mode; |
|||
}; |
|||
static_assert(sizeof(ChCommandHeader) == sizeof(u32), "ChCommand header is an invalid size"); |
|||
|
|||
struct ChCommand { |
|||
ChClassId class_id{}; |
|||
int method_offset{}; |
|||
std::vector<u32> arguments; |
|||
}; |
|||
|
|||
using ChCommandHeaderList = std::vector<Tegra::ChCommandHeader>; |
|||
using ChCommandList = std::vector<Tegra::ChCommand>; |
|||
|
|||
struct ThiRegisters { |
|||
u32_le increment_syncpt{}; |
|||
INSERT_PADDING_WORDS(1); |
|||
u32_le increment_syncpt_error{}; |
|||
u32_le ctx_switch_incremement_syncpt{}; |
|||
INSERT_PADDING_WORDS(4); |
|||
u32_le ctx_switch{}; |
|||
INSERT_PADDING_WORDS(1); |
|||
u32_le ctx_syncpt_eof{}; |
|||
INSERT_PADDING_WORDS(5); |
|||
u32_le method_0{}; |
|||
u32_le method_1{}; |
|||
INSERT_PADDING_WORDS(12); |
|||
u32_le int_status{}; |
|||
u32_le int_mask{}; |
|||
}; |
|||
|
|||
enum class ThiMethod : u32 { |
|||
IncSyncpt = offsetof(ThiRegisters, increment_syncpt) / sizeof(u32), |
|||
SetMethod0 = offsetof(ThiRegisters, method_0) / sizeof(u32), |
|||
SetMethod1 = offsetof(ThiRegisters, method_1) / sizeof(u32), |
|||
}; |
|||
|
|||
class CDmaPusher { |
|||
public: |
|||
explicit CDmaPusher(GPU& gpu); |
|||
~CDmaPusher(); |
|||
|
|||
/// Push NVDEC command buffer entries into queue |
|||
void Push(ChCommandHeaderList&& entries); |
|||
|
|||
/// Process queued command buffer entries |
|||
void DispatchCalls(); |
|||
|
|||
/// Process one queue element |
|||
void Step(); |
|||
|
|||
/// Invoke command class devices to execute the command based on the current state |
|||
void ExecuteCommand(u32 offset, u32 data); |
|||
|
|||
private: |
|||
/// Write arguments value to the ThiRegisters member at the specified offset |
|||
void ThiStateWrite(ThiRegisters& state, u32 offset, const std::vector<u32>& arguments); |
|||
|
|||
GPU& gpu; |
|||
|
|||
std::shared_ptr<Tegra::Nvdec> nvdec_processor; |
|||
std::unique_ptr<Tegra::Vic> vic_processor; |
|||
std::unique_ptr<Tegra::Host1x> host1x_processor; |
|||
std::unique_ptr<SyncptIncrManager> nvdec_sync; |
|||
std::unique_ptr<SyncptIncrManager> vic_sync; |
|||
ChClassId current_class{}; |
|||
ThiRegisters vic_thi_state{}; |
|||
ThiRegisters nvdec_thi_state{}; |
|||
|
|||
s32 count{}; |
|||
s32 offset{}; |
|||
s32 mask{}; |
|||
bool incrementing{}; |
|||
|
|||
// Queue of command lists to be processed |
|||
std::queue<ChCommandHeaderList> cdma_queue; |
|||
}; |
|||
|
|||
} // namespace Tegra |
|||
@ -0,0 +1,114 @@ |
|||
// Copyright 2020 yuzu Emulator Project
|
|||
// Licensed under GPLv2 or any later version
|
|||
// Refer to the license.txt file included.
|
|||
|
|||
#include <cstring>
|
|||
#include <fstream>
|
|||
#include "common/assert.h"
|
|||
#include "video_core/command_classes/codecs/codec.h"
|
|||
#include "video_core/command_classes/codecs/h264.h"
|
|||
#include "video_core/command_classes/codecs/vp9.h"
|
|||
#include "video_core/gpu.h"
|
|||
#include "video_core/memory_manager.h"
|
|||
|
|||
extern "C" { |
|||
#include <libavutil/opt.h>
|
|||
} |
|||
|
|||
namespace Tegra { |
|||
|
|||
Codec::Codec(GPU& gpu_) |
|||
: gpu(gpu_), h264_decoder(std::make_unique<Decoder::H264>(gpu)), |
|||
vp9_decoder(std::make_unique<Decoder::VP9>(gpu)) {} |
|||
|
|||
Codec::~Codec() { |
|||
if (!initialized) { |
|||
return; |
|||
} |
|||
// Free libav memory
|
|||
avcodec_send_packet(av_codec_ctx, nullptr); |
|||
avcodec_receive_frame(av_codec_ctx, av_frame); |
|||
avcodec_flush_buffers(av_codec_ctx); |
|||
|
|||
av_frame_unref(av_frame); |
|||
av_free(av_frame); |
|||
avcodec_close(av_codec_ctx); |
|||
} |
|||
|
|||
void Codec::SetTargetCodec(NvdecCommon::VideoCodec codec) { |
|||
LOG_INFO(Service_NVDRV, "NVDEC video codec initialized to {}", static_cast<u32>(codec)); |
|||
current_codec = codec; |
|||
} |
|||
|
|||
void Codec::StateWrite(u32 offset, u64 arguments) { |
|||
u8* const state_offset = reinterpret_cast<u8*>(&state) + offset * sizeof(u64); |
|||
std::memcpy(state_offset, &arguments, sizeof(u64)); |
|||
} |
|||
|
|||
void Codec::Decode() { |
|||
bool is_first_frame = false; |
|||
|
|||
if (!initialized) { |
|||
if (current_codec == NvdecCommon::VideoCodec::H264) { |
|||
av_codec = avcodec_find_decoder(AV_CODEC_ID_H264); |
|||
} else if (current_codec == NvdecCommon::VideoCodec::Vp9) { |
|||
av_codec = avcodec_find_decoder(AV_CODEC_ID_VP9); |
|||
} else { |
|||
LOG_ERROR(Service_NVDRV, "Unknown video codec {}", static_cast<u32>(current_codec)); |
|||
return; |
|||
} |
|||
|
|||
av_codec_ctx = avcodec_alloc_context3(av_codec); |
|||
av_frame = av_frame_alloc(); |
|||
av_opt_set(av_codec_ctx->priv_data, "tune", "zerolatency", 0); |
|||
|
|||
// TODO(ameerj): libavcodec gpu hw acceleration
|
|||
|
|||
const auto av_error = avcodec_open2(av_codec_ctx, av_codec, nullptr); |
|||
if (av_error < 0) { |
|||
LOG_ERROR(Service_NVDRV, "avcodec_open2() Failed."); |
|||
av_frame_unref(av_frame); |
|||
av_free(av_frame); |
|||
avcodec_close(av_codec_ctx); |
|||
return; |
|||
} |
|||
initialized = true; |
|||
is_first_frame = true; |
|||
} |
|||
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(); |
|||
} |
|||
|
|||
packet.data = frame_data.data(); |
|||
packet.size = static_cast<int>(frame_data.size()); |
|||
|
|||
avcodec_send_packet(av_codec_ctx, &packet); |
|||
|
|||
if (!vp9_hidden_frame) { |
|||
// Only receive/store visible frames
|
|||
avcodec_receive_frame(av_codec_ctx, av_frame); |
|||
} |
|||
} |
|||
|
|||
AVFrame* Codec::GetCurrentFrame() { |
|||
return av_frame; |
|||
} |
|||
|
|||
const AVFrame* Codec::GetCurrentFrame() const { |
|||
return av_frame; |
|||
} |
|||
|
|||
NvdecCommon::VideoCodec Codec::GetCurrentCodec() const { |
|||
return current_codec; |
|||
} |
|||
|
|||
} // namespace Tegra
|
|||
@ -0,0 +1,68 @@ |
|||
// Copyright 2020 yuzu Emulator Project |
|||
// Licensed under GPLv2 or any later version |
|||
// Refer to the license.txt file included. |
|||
|
|||
#pragma once |
|||
|
|||
#include <memory> |
|||
#include <vector> |
|||
#include "common/common_funcs.h" |
|||
#include "common/common_types.h" |
|||
#include "video_core/command_classes/nvdec_common.h" |
|||
|
|||
extern "C" { |
|||
#if defined(__GNUC__) || defined(__clang__) |
|||
#pragma GCC diagnostic ignored "-Wconversion" |
|||
#endif |
|||
#include <libavcodec/avcodec.h> |
|||
#if defined(__GNUC__) || defined(__clang__) |
|||
#pragma GCC diagnostic pop |
|||
#endif |
|||
} |
|||
|
|||
namespace Tegra { |
|||
class GPU; |
|||
struct VicRegisters; |
|||
|
|||
namespace Decoder { |
|||
class H264; |
|||
class VP9; |
|||
} // namespace Decoder |
|||
|
|||
class Codec { |
|||
public: |
|||
explicit Codec(GPU& gpu); |
|||
~Codec(); |
|||
|
|||
/// Sets NVDEC video stream codec |
|||
void SetTargetCodec(NvdecCommon::VideoCodec codec); |
|||
|
|||
/// Populate NvdecRegisters state with argument value at the provided offset |
|||
void StateWrite(u32 offset, u64 arguments); |
|||
|
|||
/// Call decoders to construct headers, decode AVFrame with ffmpeg |
|||
void Decode(); |
|||
|
|||
/// Returns most recently decoded frame |
|||
AVFrame* GetCurrentFrame(); |
|||
const AVFrame* GetCurrentFrame() const; |
|||
|
|||
/// Returns the value of current_codec |
|||
NvdecCommon::VideoCodec GetCurrentCodec() const; |
|||
|
|||
private: |
|||
bool initialized{}; |
|||
NvdecCommon::VideoCodec current_codec{NvdecCommon::VideoCodec::None}; |
|||
|
|||
AVCodec* av_codec{nullptr}; |
|||
AVCodecContext* av_codec_ctx{nullptr}; |
|||
AVFrame* av_frame{nullptr}; |
|||
|
|||
GPU& gpu; |
|||
std::unique_ptr<Decoder::H264> h264_decoder; |
|||
std::unique_ptr<Decoder::VP9> vp9_decoder; |
|||
|
|||
NvdecCommon::NvdecRegisters state{}; |
|||
}; |
|||
|
|||
} // namespace Tegra |
|||
@ -0,0 +1,276 @@ |
|||
// MIT License
|
|||
//
|
|||
// Copyright (c) Ryujinx Team and Contributors
|
|||
//
|
|||
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
|
|||
// associated documentation files (the "Software"), to deal in the Software without restriction,
|
|||
// including without limitation the rights to use, copy, modify, merge, publish, distribute,
|
|||
// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
|
|||
// furnished to do so, subject to the following conditions:
|
|||
//
|
|||
// The above copyright notice and this permission notice shall be included in all copies or
|
|||
// substantial portions of the Software.
|
|||
//
|
|||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
|
|||
// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
|||
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|||
//
|
|||
|
|||
#include "common/bit_util.h"
|
|||
#include "video_core/command_classes/codecs/h264.h"
|
|||
#include "video_core/gpu.h"
|
|||
#include "video_core/memory_manager.h"
|
|||
|
|||
namespace Tegra::Decoder { |
|||
H264::H264(GPU& gpu_) : gpu(gpu_) {} |
|||
|
|||
H264::~H264() = default; |
|||
|
|||
std::vector<u8>& H264::ComposeFrameHeader(NvdecCommon::NvdecRegisters& state, bool is_first_frame) { |
|||
H264DecoderContext context{}; |
|||
gpu.MemoryManager().ReadBlock(state.picture_info_offset, &context, sizeof(H264DecoderContext)); |
|||
|
|||
const s32 frame_number = static_cast<s32>((context.h264_parameter_set.flags >> 46) & 0x1ffff); |
|||
if (!is_first_frame && frame_number != 0) { |
|||
frame.resize(context.frame_data_size); |
|||
|
|||
gpu.MemoryManager().ReadBlock(state.frame_bitstream_offset, frame.data(), frame.size()); |
|||
} else { |
|||
/// Encode header
|
|||
H264BitWriter writer{}; |
|||
writer.WriteU(1, 24); |
|||
writer.WriteU(0, 1); |
|||
writer.WriteU(3, 2); |
|||
writer.WriteU(7, 5); |
|||
writer.WriteU(100, 8); |
|||
writer.WriteU(0, 8); |
|||
writer.WriteU(31, 8); |
|||
writer.WriteUe(0); |
|||
const s32 chroma_format_idc = (context.h264_parameter_set.flags >> 12) & 0x3; |
|||
writer.WriteUe(chroma_format_idc); |
|||
if (chroma_format_idc == 3) { |
|||
writer.WriteBit(false); |
|||
} |
|||
|
|||
writer.WriteUe(0); |
|||
writer.WriteUe(0); |
|||
writer.WriteBit(false); // QpprimeYZeroTransformBypassFlag
|
|||
writer.WriteBit(false); // Scaling matrix present flag
|
|||
|
|||
const s32 order_cnt_type = static_cast<s32>((context.h264_parameter_set.flags >> 14) & 3); |
|||
writer.WriteUe(static_cast<s32>((context.h264_parameter_set.flags >> 8) & 0xf)); |
|||
writer.WriteUe(order_cnt_type); |
|||
if (order_cnt_type == 0) { |
|||
writer.WriteUe(context.h264_parameter_set.log2_max_pic_order_cnt); |
|||
} else if (order_cnt_type == 1) { |
|||
writer.WriteBit(context.h264_parameter_set.delta_pic_order_always_zero_flag != 0); |
|||
|
|||
writer.WriteSe(0); |
|||
writer.WriteSe(0); |
|||
writer.WriteUe(0); |
|||
} |
|||
|
|||
const s32 pic_height = context.h264_parameter_set.pic_height_in_map_units / |
|||
(context.h264_parameter_set.frame_mbs_only_flag ? 1 : 2); |
|||
|
|||
writer.WriteUe(16); |
|||
writer.WriteBit(false); |
|||
writer.WriteUe(context.h264_parameter_set.pic_width_in_mbs - 1); |
|||
writer.WriteUe(pic_height - 1); |
|||
writer.WriteBit(context.h264_parameter_set.frame_mbs_only_flag != 0); |
|||
|
|||
if (!context.h264_parameter_set.frame_mbs_only_flag) { |
|||
writer.WriteBit(((context.h264_parameter_set.flags >> 0) & 1) != 0); |
|||
} |
|||
|
|||
writer.WriteBit(((context.h264_parameter_set.flags >> 1) & 1) != 0); |
|||
writer.WriteBit(false); // Frame cropping flag
|
|||
writer.WriteBit(false); // VUI parameter present flag
|
|||
|
|||
writer.End(); |
|||
|
|||
// H264 PPS
|
|||
writer.WriteU(1, 24); |
|||
writer.WriteU(0, 1); |
|||
writer.WriteU(3, 2); |
|||
writer.WriteU(8, 5); |
|||
|
|||
writer.WriteUe(0); |
|||
writer.WriteUe(0); |
|||
|
|||
writer.WriteBit(context.h264_parameter_set.entropy_coding_mode_flag); |
|||
writer.WriteBit(false); |
|||
writer.WriteUe(0); |
|||
writer.WriteUe(context.h264_parameter_set.num_refidx_l0_default_active); |
|||
writer.WriteUe(context.h264_parameter_set.num_refidx_l1_default_active); |
|||
writer.WriteBit(((context.h264_parameter_set.flags >> 2) & 1) != 0); |
|||
writer.WriteU(static_cast<s32>((context.h264_parameter_set.flags >> 32) & 0x3), 2); |
|||
s32 pic_init_qp = static_cast<s32>((context.h264_parameter_set.flags >> 16) & 0x3f); |
|||
pic_init_qp = (pic_init_qp << 26) >> 26; |
|||
writer.WriteSe(pic_init_qp); |
|||
writer.WriteSe(0); |
|||
s32 chroma_qp_index_offset = |
|||
static_cast<s32>((context.h264_parameter_set.flags >> 22) & 0x1f); |
|||
chroma_qp_index_offset = (chroma_qp_index_offset << 27) >> 27; |
|||
|
|||
writer.WriteSe(chroma_qp_index_offset); |
|||
writer.WriteBit(context.h264_parameter_set.deblocking_filter_control_flag != 0); |
|||
writer.WriteBit(((context.h264_parameter_set.flags >> 3) & 1) != 0); |
|||
writer.WriteBit(context.h264_parameter_set.redundant_pic_count_flag != 0); |
|||
writer.WriteBit(context.h264_parameter_set.transform_8x8_mode_flag != 0); |
|||
|
|||
writer.WriteBit(true); |
|||
|
|||
for (s32 index = 0; index < 6; index++) { |
|||
writer.WriteBit(true); |
|||
const auto matrix_x4 = |
|||
std::vector<u8>(context.scaling_matrix_4.begin(), context.scaling_matrix_4.end()); |
|||
writer.WriteScalingList(matrix_x4, index * 16, 16); |
|||
} |
|||
|
|||
if (context.h264_parameter_set.transform_8x8_mode_flag) { |
|||
for (s32 index = 0; index < 2; index++) { |
|||
writer.WriteBit(true); |
|||
const auto matrix_x8 = std::vector<u8>(context.scaling_matrix_8.begin(), |
|||
context.scaling_matrix_8.end()); |
|||
|
|||
writer.WriteScalingList(matrix_x8, index * 64, 64); |
|||
} |
|||
} |
|||
|
|||
s32 chroma_qp_index_offset2 = |
|||
static_cast<s32>((context.h264_parameter_set.flags >> 27) & 0x1f); |
|||
chroma_qp_index_offset2 = (chroma_qp_index_offset2 << 27) >> 27; |
|||
|
|||
writer.WriteSe(chroma_qp_index_offset2); |
|||
|
|||
writer.End(); |
|||
|
|||
const auto& encoded_header = writer.GetByteArray(); |
|||
frame.resize(encoded_header.size() + context.frame_data_size); |
|||
std::memcpy(frame.data(), encoded_header.data(), encoded_header.size()); |
|||
|
|||
gpu.MemoryManager().ReadBlock(state.frame_bitstream_offset, |
|||
frame.data() + encoded_header.size(), |
|||
context.frame_data_size); |
|||
} |
|||
|
|||
return frame; |
|||
} |
|||
|
|||
H264BitWriter::H264BitWriter() = default; |
|||
|
|||
H264BitWriter::~H264BitWriter() = default; |
|||
|
|||
void H264BitWriter::WriteU(s32 value, s32 value_sz) { |
|||
WriteBits(value, value_sz); |
|||
} |
|||
|
|||
void H264BitWriter::WriteSe(s32 value) { |
|||
WriteExpGolombCodedInt(value); |
|||
} |
|||
|
|||
void H264BitWriter::WriteUe(s32 value) { |
|||
WriteExpGolombCodedUInt((u32)value); |
|||
} |
|||
|
|||
void H264BitWriter::End() { |
|||
WriteBit(true); |
|||
Flush(); |
|||
} |
|||
|
|||
void H264BitWriter::WriteBit(bool state) { |
|||
WriteBits(state ? 1 : 0, 1); |
|||
} |
|||
|
|||
void H264BitWriter::WriteScalingList(const std::vector<u8>& list, s32 start, s32 count) { |
|||
std::vector<u8> scan(count); |
|||
if (count == 16) { |
|||
std::memcpy(scan.data(), zig_zag_scan.data(), scan.size()); |
|||
} else { |
|||
std::memcpy(scan.data(), zig_zag_direct.data(), scan.size()); |
|||
} |
|||
u8 last_scale = 8; |
|||
|
|||
for (s32 index = 0; index < count; index++) { |
|||
const u8 value = list[start + scan[index]]; |
|||
const s32 delta_scale = static_cast<s32>(value - last_scale); |
|||
|
|||
WriteSe(delta_scale); |
|||
|
|||
last_scale = value; |
|||
} |
|||
} |
|||
|
|||
std::vector<u8>& H264BitWriter::GetByteArray() { |
|||
return byte_array; |
|||
} |
|||
|
|||
const std::vector<u8>& H264BitWriter::GetByteArray() const { |
|||
return byte_array; |
|||
} |
|||
|
|||
void H264BitWriter::WriteBits(s32 value, s32 bit_count) { |
|||
s32 value_pos = 0; |
|||
|
|||
s32 remaining = bit_count; |
|||
|
|||
while (remaining > 0) { |
|||
s32 copy_size = remaining; |
|||
|
|||
const s32 free_bits = GetFreeBufferBits(); |
|||
|
|||
if (copy_size > free_bits) { |
|||
copy_size = free_bits; |
|||
} |
|||
|
|||
const s32 mask = (1 << copy_size) - 1; |
|||
|
|||
const s32 src_shift = (bit_count - value_pos) - copy_size; |
|||
const s32 dst_shift = (buffer_size - buffer_pos) - copy_size; |
|||
|
|||
buffer |= ((value >> src_shift) & mask) << dst_shift; |
|||
|
|||
value_pos += copy_size; |
|||
buffer_pos += copy_size; |
|||
remaining -= copy_size; |
|||
} |
|||
} |
|||
|
|||
void H264BitWriter::WriteExpGolombCodedInt(s32 value) { |
|||
const s32 sign = value <= 0 ? 0 : 1; |
|||
if (value < 0) { |
|||
value = -value; |
|||
} |
|||
value = (value << 1) - sign; |
|||
WriteExpGolombCodedUInt(value); |
|||
} |
|||
|
|||
void H264BitWriter::WriteExpGolombCodedUInt(u32 value) { |
|||
const s32 size = 32 - Common::CountLeadingZeroes32(static_cast<s32>(value + 1)); |
|||
WriteBits(1, size); |
|||
|
|||
value -= (1U << (size - 1)) - 1; |
|||
WriteBits(static_cast<s32>(value), size - 1); |
|||
} |
|||
|
|||
s32 H264BitWriter::GetFreeBufferBits() { |
|||
if (buffer_pos == buffer_size) { |
|||
Flush(); |
|||
} |
|||
|
|||
return buffer_size - buffer_pos; |
|||
} |
|||
|
|||
void H264BitWriter::Flush() { |
|||
if (buffer_pos == 0) { |
|||
return; |
|||
} |
|||
byte_array.push_back(static_cast<u8>(buffer)); |
|||
|
|||
buffer = 0; |
|||
buffer_pos = 0; |
|||
} |
|||
} // namespace Tegra::Decoder
|
|||
@ -0,0 +1,130 @@ |
|||
// MIT License |
|||
// |
|||
// Copyright (c) Ryujinx Team and Contributors |
|||
// |
|||
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and |
|||
// associated documentation files (the "Software"), to deal in the Software without restriction, |
|||
// including without limitation the rights to use, copy, modify, merge, publish, distribute, |
|||
// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is |
|||
// furnished to do so, subject to the following conditions: |
|||
// |
|||
// The above copyright notice and this permission notice shall be included in all copies or |
|||
// substantial portions of the Software. |
|||
// |
|||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT |
|||
// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
|||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, |
|||
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
|||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
|||
// |
|||
|
|||
#pragma once |
|||
|
|||
#include <vector> |
|||
#include "common/common_funcs.h" |
|||
#include "common/common_types.h" |
|||
#include "video_core/command_classes/nvdec_common.h" |
|||
|
|||
namespace Tegra { |
|||
class GPU; |
|||
namespace Decoder { |
|||
|
|||
class H264BitWriter { |
|||
public: |
|||
H264BitWriter(); |
|||
~H264BitWriter(); |
|||
|
|||
/// The following Write methods are based on clause 9.1 in the H.264 specification. |
|||
/// WriteSe and WriteUe write in the Exp-Golomb-coded syntax |
|||
void WriteU(s32 value, s32 value_sz); |
|||
void WriteSe(s32 value); |
|||
void WriteUe(s32 value); |
|||
|
|||
/// Finalize the bitstream |
|||
void End(); |
|||
|
|||
/// append a bit to the stream, equivalent value to the state parameter |
|||
void WriteBit(bool state); |
|||
|
|||
/// Based on section 7.3.2.1.1.1 and Table 7-4 in the H.264 specification |
|||
/// Writes the scaling matrices of the sream |
|||
void WriteScalingList(const std::vector<u8>& list, s32 start, s32 count); |
|||
|
|||
/// Return the bitstream as a vector. |
|||
std::vector<u8>& GetByteArray(); |
|||
const std::vector<u8>& GetByteArray() const; |
|||
|
|||
private: |
|||
// ZigZag LUTs from libavcodec. |
|||
static constexpr std::array<u8, 64> zig_zag_direct{ |
|||
0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18, 11, 4, 5, 12, 19, 26, 33, 40, 48, |
|||
41, 34, 27, 20, 13, 6, 7, 14, 21, 28, 35, 42, 49, 56, 57, 50, 43, 36, 29, 22, 15, 23, |
|||
30, 37, 44, 51, 58, 59, 52, 45, 38, 31, 39, 46, 53, 60, 61, 54, 47, 55, 62, 63, |
|||
}; |
|||
|
|||
static constexpr std::array<u8, 16> zig_zag_scan{ |
|||
0 + 0 * 4, 1 + 0 * 4, 0 + 1 * 4, 0 + 2 * 4, 1 + 1 * 4, 2 + 0 * 4, 3 + 0 * 4, 2 + 1 * 4, |
|||
1 + 2 * 4, 0 + 3 * 4, 1 + 3 * 4, 2 + 2 * 4, 3 + 1 * 4, 3 + 2 * 4, 2 + 3 * 4, 3 + 3 * 4, |
|||
}; |
|||
|
|||
void WriteBits(s32 value, s32 bit_count); |
|||
void WriteExpGolombCodedInt(s32 value); |
|||
void WriteExpGolombCodedUInt(u32 value); |
|||
s32 GetFreeBufferBits(); |
|||
void Flush(); |
|||
|
|||
s32 buffer_size{8}; |
|||
|
|||
s32 buffer{}; |
|||
s32 buffer_pos{}; |
|||
std::vector<u8> byte_array; |
|||
}; |
|||
|
|||
class H264 { |
|||
public: |
|||
explicit H264(GPU& gpu); |
|||
~H264(); |
|||
|
|||
/// Compose the H264 header of the frame for FFmpeg decoding |
|||
std::vector<u8>& ComposeFrameHeader(NvdecCommon::NvdecRegisters& state, |
|||
bool is_first_frame = false); |
|||
|
|||
private: |
|||
struct H264ParameterSet { |
|||
u32 log2_max_pic_order_cnt{}; |
|||
u32 delta_pic_order_always_zero_flag{}; |
|||
u32 frame_mbs_only_flag{}; |
|||
u32 pic_width_in_mbs{}; |
|||
u32 pic_height_in_map_units{}; |
|||
INSERT_PADDING_WORDS(1); |
|||
u32 entropy_coding_mode_flag{}; |
|||
u32 bottom_field_pic_order_flag{}; |
|||
u32 num_refidx_l0_default_active{}; |
|||
u32 num_refidx_l1_default_active{}; |
|||
u32 deblocking_filter_control_flag{}; |
|||
u32 redundant_pic_count_flag{}; |
|||
u32 transform_8x8_mode_flag{}; |
|||
INSERT_PADDING_WORDS(9); |
|||
u64 flags{}; |
|||
u32 frame_number{}; |
|||
u32 frame_number2{}; |
|||
}; |
|||
static_assert(sizeof(H264ParameterSet) == 0x68, "H264ParameterSet is an invalid size"); |
|||
|
|||
struct H264DecoderContext { |
|||
INSERT_PADDING_BYTES(0x48); |
|||
u32 frame_data_size{}; |
|||
INSERT_PADDING_BYTES(0xc); |
|||
H264ParameterSet h264_parameter_set{}; |
|||
INSERT_PADDING_BYTES(0x100); |
|||
std::array<u8, 0x60> scaling_matrix_4; |
|||
std::array<u8, 0x80> scaling_matrix_8; |
|||
}; |
|||
static_assert(sizeof(H264DecoderContext) == 0x2a0, "H264DecoderContext is an invalid size"); |
|||
|
|||
std::vector<u8> frame; |
|||
GPU& gpu; |
|||
}; |
|||
|
|||
} // namespace Decoder |
|||
} // namespace Tegra |
|||
1010
src/video_core/command_classes/codecs/vp9.cpp
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
@ -0,0 +1,216 @@ |
|||
// Copyright 2020 yuzu Emulator Project |
|||
// Licensed under GPLv2 or any later version |
|||
// Refer to the license.txt file included. |
|||
|
|||
#pragma once |
|||
|
|||
#include <unordered_map> |
|||
#include <vector> |
|||
#include "common/common_funcs.h" |
|||
#include "common/common_types.h" |
|||
#include "common/stream.h" |
|||
#include "video_core/command_classes/codecs/vp9_types.h" |
|||
#include "video_core/command_classes/nvdec_common.h" |
|||
|
|||
namespace Tegra { |
|||
class GPU; |
|||
enum class FrameType { KeyFrame = 0, InterFrame = 1 }; |
|||
namespace Decoder { |
|||
|
|||
/// The VpxRangeEncoder, and VpxBitStreamWriter classes are used to compose the |
|||
/// VP9 header bitstreams. |
|||
|
|||
class VpxRangeEncoder { |
|||
public: |
|||
VpxRangeEncoder(); |
|||
~VpxRangeEncoder(); |
|||
|
|||
/// Writes the rightmost value_size bits from value into the stream |
|||
void Write(s32 value, s32 value_size); |
|||
|
|||
/// Writes a single bit with half probability |
|||
void Write(bool bit); |
|||
|
|||
/// Writes a bit to the base_stream encoded with probability |
|||
void Write(bool bit, s32 probability); |
|||
|
|||
/// Signal the end of the bitstream |
|||
void End(); |
|||
|
|||
std::vector<u8>& GetBuffer() { |
|||
return base_stream.GetBuffer(); |
|||
} |
|||
|
|||
const std::vector<u8>& GetBuffer() const { |
|||
return base_stream.GetBuffer(); |
|||
} |
|||
|
|||
private: |
|||
u8 PeekByte(); |
|||
Common::Stream base_stream{}; |
|||
u32 low_value{}; |
|||
u32 range{0xff}; |
|||
s32 count{-24}; |
|||
s32 half_probability{128}; |
|||
static constexpr std::array<s32, 256> norm_lut{ |
|||
0, 7, 6, 6, 5, 5, 5, 5, 4, 4, 4, 4, 4, 4, 4, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, |
|||
3, 3, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, |
|||
2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, |
|||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, |
|||
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
|||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
|||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
|||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
|||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, |
|||
}; |
|||
}; |
|||
|
|||
class VpxBitStreamWriter { |
|||
public: |
|||
VpxBitStreamWriter(); |
|||
~VpxBitStreamWriter(); |
|||
|
|||
/// Write an unsigned integer value |
|||
void WriteU(u32 value, u32 value_size); |
|||
|
|||
/// Write a signed integer value |
|||
void WriteS(s32 value, u32 value_size); |
|||
|
|||
/// Based on 6.2.10 of VP9 Spec, writes a delta coded value |
|||
void WriteDeltaQ(u32 value); |
|||
|
|||
/// Write a single bit. |
|||
void WriteBit(bool state); |
|||
|
|||
/// Pushes current buffer into buffer_array, resets buffer |
|||
void Flush(); |
|||
|
|||
/// Returns byte_array |
|||
std::vector<u8>& GetByteArray(); |
|||
|
|||
/// Returns const byte_array |
|||
const std::vector<u8>& GetByteArray() const; |
|||
|
|||
private: |
|||
/// Write bit_count bits from value into buffer |
|||
void WriteBits(u32 value, u32 bit_count); |
|||
|
|||
/// Gets next available position in buffer, invokes Flush() if buffer is full |
|||
s32 GetFreeBufferBits(); |
|||
|
|||
s32 buffer_size{8}; |
|||
|
|||
s32 buffer{}; |
|||
s32 buffer_pos{}; |
|||
std::vector<u8> byte_array; |
|||
}; |
|||
|
|||
class VP9 { |
|||
public: |
|||
explicit VP9(GPU& gpu); |
|||
~VP9(); |
|||
|
|||
/// Composes the VP9 frame from the GPU state information. Based on the official VP9 spec |
|||
/// documentation |
|||
std::vector<u8>& ComposeFrameHeader(NvdecCommon::NvdecRegisters& state); |
|||
|
|||
/// Returns true if the most recent frame was a hidden frame. |
|||
bool WasFrameHidden() const { |
|||
return hidden; |
|||
} |
|||
|
|||
private: |
|||
/// Generates compressed header probability updates in the bitstream writer |
|||
template <typename T, std::size_t N> |
|||
void WriteProbabilityUpdate(VpxRangeEncoder& writer, const std::array<T, N>& new_prob, |
|||
const std::array<T, N>& old_prob); |
|||
|
|||
/// Generates compressed header probability updates in the bitstream writer |
|||
/// If probs are not equal, WriteProbabilityDelta is invoked |
|||
void WriteProbabilityUpdate(VpxRangeEncoder& writer, u8 new_prob, u8 old_prob); |
|||
|
|||
/// Generates compressed header probability deltas in the bitstream writer |
|||
void WriteProbabilityDelta(VpxRangeEncoder& writer, u8 new_prob, u8 old_prob); |
|||
|
|||
/// Adjusts old_prob depending on new_prob. Based on section 6.3.5 of VP9 Specification |
|||
s32 RemapProbability(s32 new_prob, s32 old_prob); |
|||
|
|||
/// Recenters probability. Based on section 6.3.6 of VP9 Specification |
|||
s32 RecenterNonNeg(s32 new_prob, s32 old_prob); |
|||
|
|||
/// Inverse of 6.3.4 Decode term subexp |
|||
void EncodeTermSubExp(VpxRangeEncoder& writer, s32 value); |
|||
|
|||
/// Writes if the value is less than the test value |
|||
bool WriteLessThan(VpxRangeEncoder& writer, s32 value, s32 test); |
|||
|
|||
/// Writes probability updates for the Coef probabilities |
|||
void WriteCoefProbabilityUpdate(VpxRangeEncoder& writer, s32 tx_mode, |
|||
const std::array<u8, 2304>& new_prob, |
|||
const std::array<u8, 2304>& old_prob); |
|||
|
|||
/// Write probabilities for 4-byte aligned structures |
|||
template <typename T, std::size_t N> |
|||
void WriteProbabilityUpdateAligned4(VpxRangeEncoder& writer, const std::array<T, N>& new_prob, |
|||
const std::array<T, N>& old_prob); |
|||
|
|||
/// Write motion vector probability updates. 6.3.17 in the spec |
|||
void WriteMvProbabilityUpdate(VpxRangeEncoder& writer, u8 new_prob, u8 old_prob); |
|||
|
|||
/// 6.2.14 Tile size calculation |
|||
s32 CalcMinLog2TileCols(s32 frame_width); |
|||
s32 CalcMaxLog2TileCols(s32 frame_width); |
|||
|
|||
/// Returns VP9 information from NVDEC provided offset and size |
|||
Vp9PictureInfo GetVp9PictureInfo(const NvdecCommon::NvdecRegisters& state); |
|||
|
|||
/// Read and convert NVDEC provided entropy probs to Vp9EntropyProbs struct |
|||
void InsertEntropy(u64 offset, Vp9EntropyProbs& dst); |
|||
|
|||
/// Returns frame to be decoded after buffering |
|||
Vp9FrameContainer GetCurrentFrame(const NvdecCommon::NvdecRegisters& state); |
|||
|
|||
/// Use NVDEC providied information to compose the headers for the current frame |
|||
std::vector<u8> ComposeCompressedHeader(); |
|||
VpxBitStreamWriter ComposeUncompressedHeader(); |
|||
|
|||
GPU& gpu; |
|||
std::vector<u8> frame; |
|||
|
|||
std::array<s8, 4> loop_filter_ref_deltas{}; |
|||
std::array<s8, 2> loop_filter_mode_deltas{}; |
|||
|
|||
bool hidden; |
|||
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{}; |
|||
|
|||
Vp9PictureInfo current_frame_info{}; |
|||
Vp9EntropyProbs prev_frame_probs{}; |
|||
|
|||
s32 diff_update_probability = 252; |
|||
s32 frame_sync_code = 0x498342; |
|||
static constexpr std::array<s32, 254> map_lut = { |
|||
20, 21, 22, 23, 24, 25, 0, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, |
|||
36, 37, 1, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 2, 50, |
|||
51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 3, 62, 63, 64, 65, 66, |
|||
67, 68, 69, 70, 71, 72, 73, 4, 74, 75, 76, 77, 78, 79, 80, 81, 82, |
|||
83, 84, 85, 5, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 6, |
|||
98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 7, 110, 111, 112, 113, |
|||
114, 115, 116, 117, 118, 119, 120, 121, 8, 122, 123, 124, 125, 126, 127, 128, 129, |
|||
130, 131, 132, 133, 9, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, |
|||
10, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 11, 158, 159, 160, |
|||
161, 162, 163, 164, 165, 166, 167, 168, 169, 12, 170, 171, 172, 173, 174, 175, 176, |
|||
177, 178, 179, 180, 181, 13, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, |
|||
193, 14, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 15, 206, 207, |
|||
208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 16, 218, 219, 220, 221, 222, 223, |
|||
224, 225, 226, 227, 228, 229, 17, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, |
|||
240, 241, 18, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 19, |
|||
}; |
|||
}; |
|||
|
|||
} // namespace Decoder |
|||
} // namespace Tegra |
|||
@ -0,0 +1,369 @@ |
|||
// Copyright 2020 yuzu Emulator Project |
|||
// Licensed under GPLv2 or any later version |
|||
// Refer to the license.txt file included. |
|||
|
|||
#pragma once |
|||
|
|||
#include <algorithm> |
|||
#include <list> |
|||
#include <vector> |
|||
#include "common/cityhash.h" |
|||
#include "common/common_funcs.h" |
|||
#include "common/common_types.h" |
|||
#include "video_core/command_classes/nvdec_common.h" |
|||
|
|||
namespace Tegra { |
|||
class GPU; |
|||
|
|||
namespace Decoder { |
|||
struct Vp9FrameDimensions { |
|||
s16 width{}; |
|||
s16 height{}; |
|||
s16 luma_pitch{}; |
|||
s16 chroma_pitch{}; |
|||
}; |
|||
static_assert(sizeof(Vp9FrameDimensions) == 0x8, "Vp9 Vp9FrameDimensions is an invalid size"); |
|||
|
|||
enum FrameFlags : u32 { |
|||
IsKeyFrame = 1 << 0, |
|||
LastFrameIsKeyFrame = 1 << 1, |
|||
FrameSizeChanged = 1 << 2, |
|||
ErrorResilientMode = 1 << 3, |
|||
LastShowFrame = 1 << 4, |
|||
IntraOnly = 1 << 5, |
|||
}; |
|||
|
|||
enum class MvJointType { |
|||
MvJointZero = 0, /* Zero vector */ |
|||
MvJointHnzvz = 1, /* Vert zero, hor nonzero */ |
|||
MvJointHzvnz = 2, /* Hor zero, vert nonzero */ |
|||
MvJointHnzvnz = 3, /* Both components nonzero */ |
|||
}; |
|||
enum class MvClassType { |
|||
MvClass0 = 0, /* (0, 2] integer pel */ |
|||
MvClass1 = 1, /* (2, 4] integer pel */ |
|||
MvClass2 = 2, /* (4, 8] integer pel */ |
|||
MvClass3 = 3, /* (8, 16] integer pel */ |
|||
MvClass4 = 4, /* (16, 32] integer pel */ |
|||
MvClass5 = 5, /* (32, 64] integer pel */ |
|||
MvClass6 = 6, /* (64, 128] integer pel */ |
|||
MvClass7 = 7, /* (128, 256] integer pel */ |
|||
MvClass8 = 8, /* (256, 512] integer pel */ |
|||
MvClass9 = 9, /* (512, 1024] integer pel */ |
|||
MvClass10 = 10, /* (1024,2048] integer pel */ |
|||
}; |
|||
|
|||
enum class BlockSize { |
|||
Block4x4 = 0, |
|||
Block4x8 = 1, |
|||
Block8x4 = 2, |
|||
Block8x8 = 3, |
|||
Block8x16 = 4, |
|||
Block16x8 = 5, |
|||
Block16x16 = 6, |
|||
Block16x32 = 7, |
|||
Block32x16 = 8, |
|||
Block32x32 = 9, |
|||
Block32x64 = 10, |
|||
Block64x32 = 11, |
|||
Block64x64 = 12, |
|||
BlockSizes = 13, |
|||
BlockInvalid = BlockSizes |
|||
}; |
|||
|
|||
enum class PredictionMode { |
|||
DcPred = 0, // Average of above and left pixels |
|||
VPred = 1, // Vertical |
|||
HPred = 2, // Horizontal |
|||
D45Pred = 3, // Directional 45 deg = round(arctan(1 / 1) * 180 / pi) |
|||
D135Pred = 4, // Directional 135 deg = 180 - 45 |
|||
D117Pred = 5, // Directional 117 deg = 180 - 63 |
|||
D153Pred = 6, // Directional 153 deg = 180 - 27 |
|||
D207Pred = 7, // Directional 207 deg = 180 + 27 |
|||
D63Pred = 8, // Directional 63 deg = round(arctan(2 / 1) * 180 / pi) |
|||
TmPred = 9, // True-motion |
|||
NearestMv = 10, |
|||
NearMv = 11, |
|||
ZeroMv = 12, |
|||
NewMv = 13, |
|||
MbModeCount = 14 |
|||
}; |
|||
|
|||
enum class TxSize { |
|||
Tx4x4 = 0, // 4x4 transform |
|||
Tx8x8 = 1, // 8x8 transform |
|||
Tx16x16 = 2, // 16x16 transform |
|||
Tx32x32 = 3, // 32x32 transform |
|||
TxSizes = 4 |
|||
}; |
|||
|
|||
enum class TxMode { |
|||
Only4X4 = 0, // Only 4x4 transform used |
|||
Allow8X8 = 1, // Allow block transform size up to 8x8 |
|||
Allow16X16 = 2, // Allow block transform size up to 16x16 |
|||
Allow32X32 = 3, // Allow block transform size up to 32x32 |
|||
TxModeSelect = 4, // Transform specified for each block |
|||
TxModes = 5 |
|||
}; |
|||
|
|||
enum class reference_mode { |
|||
SingleReference = 0, |
|||
CompoundReference = 1, |
|||
ReferenceModeSelect = 2, |
|||
ReferenceModes = 3 |
|||
}; |
|||
|
|||
struct Segmentation { |
|||
u8 enabled{}; |
|||
u8 update_map{}; |
|||
u8 temporal_update{}; |
|||
u8 abs_delta{}; |
|||
std::array<u32, 8> feature_mask{}; |
|||
std::array<std::array<s16, 4>, 8> feature_data{}; |
|||
}; |
|||
static_assert(sizeof(Segmentation) == 0x64, "Segmentation is an invalid size"); |
|||
|
|||
struct LoopFilter { |
|||
u8 mode_ref_delta_enabled{}; |
|||
std::array<s8, 4> ref_deltas{}; |
|||
std::array<s8, 2> mode_deltas{}; |
|||
}; |
|||
static_assert(sizeof(LoopFilter) == 0x7, "LoopFilter is an invalid size"); |
|||
|
|||
struct Vp9EntropyProbs { |
|||
std::array<u8, 36> y_mode_prob{}; |
|||
std::array<u8, 64> partition_prob{}; |
|||
std::array<u8, 2304> coef_probs{}; |
|||
std::array<u8, 8> switchable_interp_prob{}; |
|||
std::array<u8, 28> inter_mode_prob{}; |
|||
std::array<u8, 4> intra_inter_prob{}; |
|||
std::array<u8, 5> comp_inter_prob{}; |
|||
std::array<u8, 10> single_ref_prob{}; |
|||
std::array<u8, 5> comp_ref_prob{}; |
|||
std::array<u8, 6> tx_32x32_prob{}; |
|||
std::array<u8, 4> tx_16x16_prob{}; |
|||
std::array<u8, 2> tx_8x8_prob{}; |
|||
std::array<u8, 3> skip_probs{}; |
|||
std::array<u8, 3> joints{}; |
|||
std::array<u8, 2> sign{}; |
|||
std::array<u8, 20> classes{}; |
|||
std::array<u8, 2> class_0{}; |
|||
std::array<u8, 20> prob_bits{}; |
|||
std::array<u8, 12> class_0_fr{}; |
|||
std::array<u8, 6> fr{}; |
|||
std::array<u8, 2> class_0_hp{}; |
|||
std::array<u8, 2> high_precision{}; |
|||
}; |
|||
static_assert(sizeof(Vp9EntropyProbs) == 0x9F4, "Vp9EntropyProbs is an invalid size"); |
|||
|
|||
struct Vp9PictureInfo { |
|||
bool is_key_frame{}; |
|||
bool intra_only{}; |
|||
bool last_frame_was_key{}; |
|||
bool frame_size_changed{}; |
|||
bool error_resilient_mode{}; |
|||
bool last_frame_shown{}; |
|||
bool show_frame{}; |
|||
std::array<s8, 4> ref_frame_sign_bias{}; |
|||
s32 base_q_index{}; |
|||
s32 y_dc_delta_q{}; |
|||
s32 uv_dc_delta_q{}; |
|||
s32 uv_ac_delta_q{}; |
|||
bool lossless{}; |
|||
s32 transform_mode{}; |
|||
bool allow_high_precision_mv{}; |
|||
s32 interp_filter{}; |
|||
s32 reference_mode{}; |
|||
s8 comp_fixed_ref{}; |
|||
std::array<s8, 2> comp_var_ref{}; |
|||
s32 log2_tile_cols{}; |
|||
s32 log2_tile_rows{}; |
|||
bool segment_enabled{}; |
|||
bool segment_map_update{}; |
|||
bool segment_map_temporal_update{}; |
|||
s32 segment_abs_delta{}; |
|||
std::array<u32, 8> segment_feature_enable{}; |
|||
std::array<std::array<s16, 4>, 8> segment_feature_data{}; |
|||
bool mode_ref_delta_enabled{}; |
|||
bool use_prev_in_find_mv_refs{}; |
|||
std::array<s8, 4> ref_deltas{}; |
|||
std::array<s8, 2> mode_deltas{}; |
|||
Vp9EntropyProbs entropy{}; |
|||
Vp9FrameDimensions frame_size{}; |
|||
u8 first_level{}; |
|||
u8 sharpness_level{}; |
|||
u32 bitstream_size{}; |
|||
std::array<u64, 4> frame_offsets{}; |
|||
std::array<bool, 4> refresh_frame{}; |
|||
}; |
|||
|
|||
struct Vp9FrameContainer { |
|||
Vp9PictureInfo info{}; |
|||
std::vector<u8> bit_stream; |
|||
}; |
|||
|
|||
struct PictureInfo { |
|||
INSERT_PADDING_WORDS(12); |
|||
u32 bitstream_size{}; |
|||
INSERT_PADDING_WORDS(5); |
|||
Vp9FrameDimensions last_frame_size{}; |
|||
Vp9FrameDimensions golden_frame_size{}; |
|||
Vp9FrameDimensions alt_frame_size{}; |
|||
Vp9FrameDimensions current_frame_size{}; |
|||
u32 vp9_flags{}; |
|||
std::array<s8, 4> ref_frame_sign_bias{}; |
|||
u8 first_level{}; |
|||
u8 sharpness_level{}; |
|||
u8 base_q_index{}; |
|||
u8 y_dc_delta_q{}; |
|||
u8 uv_ac_delta_q{}; |
|||
u8 uv_dc_delta_q{}; |
|||
u8 lossless{}; |
|||
u8 tx_mode{}; |
|||
u8 allow_high_precision_mv{}; |
|||
u8 interp_filter{}; |
|||
u8 reference_mode{}; |
|||
s8 comp_fixed_ref{}; |
|||
std::array<s8, 2> comp_var_ref{}; |
|||
u8 log2_tile_cols{}; |
|||
u8 log2_tile_rows{}; |
|||
Segmentation segmentation{}; |
|||
LoopFilter loop_filter{}; |
|||
INSERT_PADDING_BYTES(5); |
|||
u32 surface_params{}; |
|||
INSERT_PADDING_WORDS(3); |
|||
|
|||
Vp9PictureInfo Convert() const { |
|||
|
|||
return Vp9PictureInfo{ |
|||
.is_key_frame = (vp9_flags & FrameFlags::IsKeyFrame) != 0, |
|||
.intra_only = (vp9_flags & FrameFlags::IntraOnly) != 0, |
|||
.last_frame_was_key = (vp9_flags & FrameFlags::LastFrameIsKeyFrame) != 0, |
|||
.frame_size_changed = (vp9_flags & FrameFlags::FrameSizeChanged) != 0, |
|||
.error_resilient_mode = (vp9_flags & FrameFlags::ErrorResilientMode) != 0, |
|||
.last_frame_shown = (vp9_flags & FrameFlags::LastShowFrame) != 0, |
|||
.ref_frame_sign_bias = ref_frame_sign_bias, |
|||
.base_q_index = base_q_index, |
|||
.y_dc_delta_q = y_dc_delta_q, |
|||
.uv_dc_delta_q = uv_dc_delta_q, |
|||
.uv_ac_delta_q = uv_ac_delta_q, |
|||
.lossless = lossless != 0, |
|||
.transform_mode = tx_mode, |
|||
.allow_high_precision_mv = allow_high_precision_mv != 0, |
|||
.interp_filter = interp_filter, |
|||
.reference_mode = reference_mode, |
|||
.comp_fixed_ref = comp_fixed_ref, |
|||
.comp_var_ref = comp_var_ref, |
|||
.log2_tile_cols = log2_tile_cols, |
|||
.log2_tile_rows = log2_tile_rows, |
|||
.segment_enabled = segmentation.enabled != 0, |
|||
.segment_map_update = segmentation.update_map != 0, |
|||
.segment_map_temporal_update = segmentation.temporal_update != 0, |
|||
.segment_abs_delta = segmentation.abs_delta, |
|||
.segment_feature_enable = segmentation.feature_mask, |
|||
.segment_feature_data = segmentation.feature_data, |
|||
.mode_ref_delta_enabled = loop_filter.mode_ref_delta_enabled != 0, |
|||
.use_prev_in_find_mv_refs = !(vp9_flags == (FrameFlags::ErrorResilientMode)) && |
|||
!(vp9_flags == (FrameFlags::FrameSizeChanged)) && |
|||
!(vp9_flags == (FrameFlags::IntraOnly)) && |
|||
(vp9_flags == (FrameFlags::LastShowFrame)) && |
|||
!(vp9_flags == (FrameFlags::LastFrameIsKeyFrame)), |
|||
.ref_deltas = loop_filter.ref_deltas, |
|||
.mode_deltas = loop_filter.mode_deltas, |
|||
.frame_size = current_frame_size, |
|||
.first_level = first_level, |
|||
.sharpness_level = sharpness_level, |
|||
.bitstream_size = bitstream_size, |
|||
}; |
|||
} |
|||
}; |
|||
static_assert(sizeof(PictureInfo) == 0x100, "PictureInfo is an invalid size"); |
|||
|
|||
struct EntropyProbs { |
|||
INSERT_PADDING_BYTES(1024); |
|||
std::array<std::array<u8, 4>, 7> inter_mode_prob{}; |
|||
std::array<u8, 4> intra_inter_prob{}; |
|||
INSERT_PADDING_BYTES(80); |
|||
std::array<std::array<u8, 1>, 2> tx_8x8_prob{}; |
|||
std::array<std::array<u8, 2>, 2> tx_16x16_prob{}; |
|||
std::array<std::array<u8, 3>, 2> tx_32x32_prob{}; |
|||
std::array<u8, 4> y_mode_prob_e8{}; |
|||
std::array<std::array<u8, 8>, 4> y_mode_prob_e0e7{}; |
|||
INSERT_PADDING_BYTES(64); |
|||
std::array<std::array<u8, 4>, 16> partition_prob{}; |
|||
INSERT_PADDING_BYTES(10); |
|||
std::array<std::array<u8, 2>, 4> switchable_interp_prob{}; |
|||
std::array<u8, 5> comp_inter_prob{}; |
|||
std::array<u8, 4> skip_probs{}; |
|||
std::array<u8, 3> joints{}; |
|||
std::array<u8, 2> sign{}; |
|||
std::array<std::array<u8, 1>, 2> class_0{}; |
|||
std::array<std::array<u8, 3>, 2> fr{}; |
|||
std::array<u8, 2> class_0_hp{}; |
|||
std::array<u8, 2> high_precision{}; |
|||
std::array<std::array<u8, 10>, 2> classes{}; |
|||
std::array<std::array<std::array<u8, 3>, 2>, 2> class_0_fr{}; |
|||
std::array<std::array<u8, 10>, 2> pred_bits{}; |
|||
std::array<std::array<u8, 2>, 5> single_ref_prob{}; |
|||
std::array<u8, 5> comp_ref_prob{}; |
|||
INSERT_PADDING_BYTES(17); |
|||
std::array<std::array<std::array<std::array<std::array<std::array<u8, 4>, 6>, 6>, 2>, 2>, 4> |
|||
coef_probs{}; |
|||
|
|||
void Convert(Vp9EntropyProbs& fc) { |
|||
std::memcpy(fc.inter_mode_prob.data(), inter_mode_prob.data(), fc.inter_mode_prob.size()); |
|||
|
|||
std::memcpy(fc.intra_inter_prob.data(), intra_inter_prob.data(), |
|||
fc.intra_inter_prob.size()); |
|||
|
|||
std::memcpy(fc.tx_8x8_prob.data(), tx_8x8_prob.data(), fc.tx_8x8_prob.size()); |
|||
std::memcpy(fc.tx_16x16_prob.data(), tx_16x16_prob.data(), fc.tx_16x16_prob.size()); |
|||
std::memcpy(fc.tx_32x32_prob.data(), tx_32x32_prob.data(), fc.tx_32x32_prob.size()); |
|||
|
|||
for (s32 i = 0; i < 4; i++) { |
|||
for (s32 j = 0; j < 9; j++) { |
|||
fc.y_mode_prob[j + 9 * i] = j < 8 ? y_mode_prob_e0e7[i][j] : y_mode_prob_e8[i]; |
|||
} |
|||
} |
|||
|
|||
std::memcpy(fc.partition_prob.data(), partition_prob.data(), fc.partition_prob.size()); |
|||
|
|||
std::memcpy(fc.switchable_interp_prob.data(), switchable_interp_prob.data(), |
|||
fc.switchable_interp_prob.size()); |
|||
std::memcpy(fc.comp_inter_prob.data(), comp_inter_prob.data(), fc.comp_inter_prob.size()); |
|||
std::memcpy(fc.skip_probs.data(), skip_probs.data(), fc.skip_probs.size()); |
|||
|
|||
std::memcpy(fc.joints.data(), joints.data(), fc.joints.size()); |
|||
|
|||
std::memcpy(fc.sign.data(), sign.data(), fc.sign.size()); |
|||
std::memcpy(fc.class_0.data(), class_0.data(), fc.class_0.size()); |
|||
std::memcpy(fc.fr.data(), fr.data(), fc.fr.size()); |
|||
std::memcpy(fc.class_0_hp.data(), class_0_hp.data(), fc.class_0_hp.size()); |
|||
std::memcpy(fc.high_precision.data(), high_precision.data(), fc.high_precision.size()); |
|||
std::memcpy(fc.classes.data(), classes.data(), fc.classes.size()); |
|||
std::memcpy(fc.class_0_fr.data(), class_0_fr.data(), fc.class_0_fr.size()); |
|||
std::memcpy(fc.prob_bits.data(), pred_bits.data(), fc.prob_bits.size()); |
|||
std::memcpy(fc.single_ref_prob.data(), single_ref_prob.data(), fc.single_ref_prob.size()); |
|||
std::memcpy(fc.comp_ref_prob.data(), comp_ref_prob.data(), fc.comp_ref_prob.size()); |
|||
|
|||
std::memcpy(fc.coef_probs.data(), coef_probs.data(), fc.coef_probs.size()); |
|||
} |
|||
}; |
|||
static_assert(sizeof(EntropyProbs) == 0xEA0, "EntropyProbs is an invalid size"); |
|||
|
|||
enum class Ref { Last, Golden, AltRef }; |
|||
|
|||
struct RefPoolElement { |
|||
s64 frame{}; |
|||
Ref ref{}; |
|||
bool refresh{}; |
|||
}; |
|||
|
|||
struct FrameContexts { |
|||
s64 from{}; |
|||
bool adapted{}; |
|||
Vp9EntropyProbs probs{}; |
|||
}; |
|||
|
|||
}; // namespace Decoder |
|||
}; // namespace Tegra |
|||
@ -0,0 +1,39 @@ |
|||
// Copyright 2020 yuzu Emulator Project
|
|||
// Licensed under GPLv2 or any later version
|
|||
// Refer to the license.txt file included.
|
|||
|
|||
#include "common/assert.h"
|
|||
#include "video_core/command_classes/host1x.h"
|
|||
#include "video_core/gpu.h"
|
|||
|
|||
Tegra::Host1x::Host1x(GPU& gpu_) : gpu(gpu_) {} |
|||
|
|||
Tegra::Host1x::~Host1x() = default; |
|||
|
|||
void Tegra::Host1x::StateWrite(u32 offset, u32 arguments) { |
|||
u8* const state_offset = reinterpret_cast<u8*>(&state) + offset * sizeof(u32); |
|||
std::memcpy(state_offset, &arguments, sizeof(u32)); |
|||
} |
|||
|
|||
void Tegra::Host1x::ProcessMethod(Host1x::Method method, const std::vector<u32>& arguments) { |
|||
StateWrite(static_cast<u32>(method), arguments[0]); |
|||
switch (method) { |
|||
case Method::WaitSyncpt: |
|||
Execute(arguments[0]); |
|||
break; |
|||
case Method::LoadSyncptPayload32: |
|||
syncpoint_value = arguments[0]; |
|||
break; |
|||
case Method::WaitSyncpt32: |
|||
Execute(arguments[0]); |
|||
break; |
|||
default: |
|||
UNIMPLEMENTED_MSG("Host1x method 0x{:X}", static_cast<u32>(method)); |
|||
break; |
|||
} |
|||
} |
|||
|
|||
void Tegra::Host1x::Execute(u32 data) { |
|||
// This method waits on a valid syncpoint.
|
|||
// TODO: Implement when proper Async is in place
|
|||
} |
|||
@ -0,0 +1,78 @@ |
|||
// Copyright 2020 yuzu Emulator Project |
|||
// Licensed under GPLv2 or any later version |
|||
// Refer to the license.txt file included. |
|||
|
|||
#pragma once |
|||
|
|||
#include <vector> |
|||
#include "common/common_funcs.h" |
|||
#include "common/common_types.h" |
|||
|
|||
namespace Tegra { |
|||
class GPU; |
|||
class Nvdec; |
|||
|
|||
class Host1x { |
|||
public: |
|||
struct Host1xClassRegisters { |
|||
u32 incr_syncpt{}; |
|||
u32 incr_syncpt_ctrl{}; |
|||
u32 incr_syncpt_error{}; |
|||
INSERT_PADDING_WORDS(5); |
|||
u32 wait_syncpt{}; |
|||
u32 wait_syncpt_base{}; |
|||
u32 wait_syncpt_incr{}; |
|||
u32 load_syncpt_base{}; |
|||
u32 incr_syncpt_base{}; |
|||
u32 clear{}; |
|||
u32 wait{}; |
|||
u32 wait_with_interrupt{}; |
|||
u32 delay_use{}; |
|||
u32 tick_count_high{}; |
|||
u32 tick_count_low{}; |
|||
u32 tick_ctrl{}; |
|||
INSERT_PADDING_WORDS(23); |
|||
u32 ind_ctrl{}; |
|||
u32 ind_off2{}; |
|||
u32 ind_off{}; |
|||
std::array<u32, 31> ind_data{}; |
|||
INSERT_PADDING_WORDS(1); |
|||
u32 load_syncpoint_payload32{}; |
|||
u32 stall_ctrl{}; |
|||
u32 wait_syncpt32{}; |
|||
u32 wait_syncpt_base32{}; |
|||
u32 load_syncpt_base32{}; |
|||
u32 incr_syncpt_base32{}; |
|||
u32 stall_count_high{}; |
|||
u32 stall_count_low{}; |
|||
u32 xref_ctrl{}; |
|||
u32 channel_xref_high{}; |
|||
u32 channel_xref_low{}; |
|||
}; |
|||
static_assert(sizeof(Host1xClassRegisters) == 0x164, "Host1xClassRegisters is an invalid size"); |
|||
|
|||
enum class Method : u32 { |
|||
WaitSyncpt = offsetof(Host1xClassRegisters, wait_syncpt) / 4, |
|||
LoadSyncptPayload32 = offsetof(Host1xClassRegisters, load_syncpoint_payload32) / 4, |
|||
WaitSyncpt32 = offsetof(Host1xClassRegisters, wait_syncpt32) / 4, |
|||
}; |
|||
|
|||
explicit Host1x(GPU& gpu); |
|||
~Host1x(); |
|||
|
|||
/// Writes the method into the state, Invoke Execute() if encountered |
|||
void ProcessMethod(Host1x::Method method, const std::vector<u32>& arguments); |
|||
|
|||
private: |
|||
/// For Host1x, execute is waiting on a syncpoint previously written into the state |
|||
void Execute(u32 data); |
|||
|
|||
/// Write argument into the provided offset |
|||
void StateWrite(u32 offset, u32 arguments); |
|||
|
|||
u32 syncpoint_value{}; |
|||
Host1xClassRegisters state{}; |
|||
GPU& gpu; |
|||
}; |
|||
|
|||
} // namespace Tegra |
|||
@ -0,0 +1,56 @@ |
|||
// Copyright 2020 yuzu Emulator Project
|
|||
// Licensed under GPLv2 or any later version
|
|||
// Refer to the license.txt file included.
|
|||
|
|||
#include <bitset>
|
|||
#include "common/assert.h"
|
|||
#include "common/bit_util.h"
|
|||
#include "core/memory.h"
|
|||
#include "video_core/command_classes/nvdec.h"
|
|||
#include "video_core/gpu.h"
|
|||
#include "video_core/memory_manager.h"
|
|||
|
|||
namespace Tegra { |
|||
|
|||
Nvdec::Nvdec(GPU& gpu_) : gpu(gpu_), codec(std::make_unique<Codec>(gpu)) {} |
|||
|
|||
Nvdec::~Nvdec() = default; |
|||
|
|||
void Nvdec::ProcessMethod(Nvdec::Method method, const std::vector<u32>& arguments) { |
|||
if (method == Method::SetVideoCodec) { |
|||
codec->StateWrite(static_cast<u32>(method), arguments[0]); |
|||
} else { |
|||
codec->StateWrite(static_cast<u32>(method), static_cast<u64>(arguments[0]) << 8); |
|||
} |
|||
|
|||
switch (method) { |
|||
case Method::SetVideoCodec: |
|||
codec->SetTargetCodec(static_cast<NvdecCommon::VideoCodec>(arguments[0])); |
|||
break; |
|||
case Method::Execute: |
|||
Execute(); |
|||
break; |
|||
} |
|||
} |
|||
|
|||
AVFrame* Nvdec::GetFrame() { |
|||
return codec->GetCurrentFrame(); |
|||
} |
|||
|
|||
const AVFrame* Nvdec::GetFrame() const { |
|||
return codec->GetCurrentFrame(); |
|||
} |
|||
|
|||
void Nvdec::Execute() { |
|||
switch (codec->GetCurrentCodec()) { |
|||
case NvdecCommon::VideoCodec::H264: |
|||
case NvdecCommon::VideoCodec::Vp9: |
|||
codec->Decode(); |
|||
break; |
|||
default: |
|||
UNIMPLEMENTED_MSG("Unknown codec {}", static_cast<u32>(codec->GetCurrentCodec())); |
|||
break; |
|||
} |
|||
} |
|||
|
|||
} // namespace Tegra
|
|||
@ -0,0 +1,39 @@ |
|||
// Copyright 2020 yuzu Emulator Project |
|||
// Licensed under GPLv2 or any later version |
|||
// Refer to the license.txt file included. |
|||
|
|||
#pragma once |
|||
|
|||
#include <vector> |
|||
#include "common/common_funcs.h" |
|||
#include "common/common_types.h" |
|||
#include "video_core/command_classes/codecs/codec.h" |
|||
|
|||
namespace Tegra { |
|||
class GPU; |
|||
|
|||
class Nvdec { |
|||
public: |
|||
enum class Method : u32 { |
|||
SetVideoCodec = 0x80, |
|||
Execute = 0xc0, |
|||
}; |
|||
|
|||
explicit Nvdec(GPU& gpu); |
|||
~Nvdec(); |
|||
|
|||
/// Writes the method into the state, Invoke Execute() if encountered |
|||
void ProcessMethod(Nvdec::Method method, const std::vector<u32>& arguments); |
|||
|
|||
/// Return most recently decoded frame |
|||
AVFrame* GetFrame(); |
|||
const AVFrame* GetFrame() const; |
|||
|
|||
private: |
|||
/// Invoke codec to decode a frame |
|||
void Execute(); |
|||
|
|||
GPU& gpu; |
|||
std::unique_ptr<Tegra::Codec> codec; |
|||
}; |
|||
} // namespace Tegra |
|||
@ -0,0 +1,48 @@ |
|||
// Copyright 2020 yuzu Emulator Project |
|||
// Licensed under GPLv2 or any later version |
|||
// Refer to the license.txt file included. |
|||
|
|||
#pragma once |
|||
|
|||
#include "common/common_funcs.h" |
|||
#include "common/common_types.h" |
|||
|
|||
namespace Tegra::NvdecCommon { |
|||
|
|||
struct NvdecRegisters { |
|||
INSERT_PADDING_WORDS(256); |
|||
u64 set_codec_id{}; |
|||
INSERT_PADDING_WORDS(254); |
|||
u64 set_platform_id{}; |
|||
u64 picture_info_offset{}; |
|||
u64 frame_bitstream_offset{}; |
|||
u64 frame_number{}; |
|||
u64 h264_slice_data_offsets{}; |
|||
u64 h264_mv_dump_offset{}; |
|||
INSERT_PADDING_WORDS(6); |
|||
u64 frame_stats_offset{}; |
|||
u64 h264_last_surface_luma_offset{}; |
|||
u64 h264_last_surface_chroma_offset{}; |
|||
std::array<u64, 17> surface_luma_offset{}; |
|||
std::array<u64, 17> surface_chroma_offset{}; |
|||
INSERT_PADDING_WORDS(132); |
|||
u64 vp9_entropy_probs_offset{}; |
|||
u64 vp9_backward_updates_offset{}; |
|||
u64 vp9_last_frame_segmap_offset{}; |
|||
u64 vp9_curr_frame_segmap_offset{}; |
|||
INSERT_PADDING_WORDS(2); |
|||
u64 vp9_last_frame_mvs_offset{}; |
|||
u64 vp9_curr_frame_mvs_offset{}; |
|||
INSERT_PADDING_WORDS(2); |
|||
}; |
|||
static_assert(sizeof(NvdecRegisters) == (0xBC0), "NvdecRegisters is incorrect size"); |
|||
|
|||
enum class VideoCodec : u32 { |
|||
None = 0x0, |
|||
H264 = 0x3, |
|||
Vp8 = 0x5, |
|||
H265 = 0x7, |
|||
Vp9 = 0x9, |
|||
}; |
|||
|
|||
} // namespace Tegra::NvdecCommon |
|||
@ -0,0 +1,60 @@ |
|||
// MIT License
|
|||
//
|
|||
// Copyright (c) Ryujinx Team and Contributors
|
|||
//
|
|||
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
|
|||
// associated documentation files (the "Software"), to deal in the Software without restriction,
|
|||
// including without limitation the rights to use, copy, modify, merge, publish, distribute,
|
|||
// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
|
|||
// furnished to do so, subject to the following conditions:
|
|||
//
|
|||
// The above copyright notice and this permission notice shall be included in all copies or
|
|||
// substantial portions of the Software.
|
|||
//
|
|||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
|
|||
// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
|||
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|||
//
|
|||
|
|||
#include <algorithm>
|
|||
#include "sync_manager.h"
|
|||
#include "video_core/gpu.h"
|
|||
|
|||
namespace Tegra { |
|||
SyncptIncrManager::SyncptIncrManager(GPU& gpu_) : gpu(gpu_) {} |
|||
SyncptIncrManager::~SyncptIncrManager() = default; |
|||
|
|||
void SyncptIncrManager::Increment(u32 id) { |
|||
increments.push_back(SyncptIncr{0, id, true}); |
|||
IncrementAllDone(); |
|||
} |
|||
|
|||
u32 SyncptIncrManager::IncrementWhenDone(u32 class_id, u32 id) { |
|||
const u32 handle = current_id++; |
|||
increments.push_back(SyncptIncr{handle, class_id, id}); |
|||
return handle; |
|||
} |
|||
|
|||
void SyncptIncrManager::SignalDone(u32 handle) { |
|||
auto done_incr = std::find_if(increments.begin(), increments.end(), |
|||
[handle](SyncptIncr incr) { return incr.id == handle; }); |
|||
if (done_incr != increments.end()) { |
|||
const SyncptIncr incr = *done_incr; |
|||
*done_incr = SyncptIncr{incr.id, incr.class_id, incr.syncpt_id, true}; |
|||
} |
|||
IncrementAllDone(); |
|||
} |
|||
|
|||
void SyncptIncrManager::IncrementAllDone() { |
|||
std::size_t done_count = 0; |
|||
for (; done_count < increments.size(); ++done_count) { |
|||
if (!increments[done_count].complete) { |
|||
break; |
|||
} |
|||
gpu.IncrementSyncPoint(increments[done_count].syncpt_id); |
|||
} |
|||
increments.erase(increments.begin(), increments.begin() + done_count); |
|||
} |
|||
} // namespace Tegra
|
|||
@ -0,0 +1,64 @@ |
|||
// MIT License |
|||
// |
|||
// Copyright (c) Ryujinx Team and Contributors |
|||
// |
|||
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and |
|||
// associated documentation files (the "Software"), to deal in the Software without restriction, |
|||
// including without limitation the rights to use, copy, modify, merge, publish, distribute, |
|||
// sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is |
|||
// furnished to do so, subject to the following conditions: |
|||
// |
|||
// The above copyright notice and this permission notice shall be included in all copies or |
|||
// substantial portions of the Software. |
|||
// |
|||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT |
|||
// NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
|||
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, |
|||
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
|||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
|||
// |
|||
|
|||
#pragma once |
|||
|
|||
#include <mutex> |
|||
#include <vector> |
|||
#include "common/common_types.h" |
|||
|
|||
namespace Tegra { |
|||
class GPU; |
|||
struct SyncptIncr { |
|||
u32 id; |
|||
u32 class_id; |
|||
u32 syncpt_id; |
|||
bool complete; |
|||
|
|||
SyncptIncr(u32 id, u32 syncpt_id_, u32 class_id_, bool done = false) |
|||
: id(id), class_id(class_id_), syncpt_id(syncpt_id_), complete(done) {} |
|||
}; |
|||
|
|||
class SyncptIncrManager { |
|||
public: |
|||
explicit SyncptIncrManager(GPU& gpu); |
|||
~SyncptIncrManager(); |
|||
|
|||
/// Add syncpoint id and increment all |
|||
void Increment(u32 id); |
|||
|
|||
/// Returns a handle to increment later |
|||
u32 IncrementWhenDone(u32 class_id, u32 id); |
|||
|
|||
/// IncrememntAllDone, including handle |
|||
void SignalDone(u32 handle); |
|||
|
|||
/// Increment all sequential pending increments that are already done. |
|||
void IncrementAllDone(); |
|||
|
|||
private: |
|||
std::vector<SyncptIncr> increments; |
|||
std::mutex increment_lock; |
|||
u32 current_id{}; |
|||
|
|||
GPU& gpu; |
|||
}; |
|||
|
|||
} // namespace Tegra |
|||
@ -0,0 +1,180 @@ |
|||
// Copyright 2020 yuzu Emulator Project
|
|||
// Licensed under GPLv2 or any later version
|
|||
// Refer to the license.txt file included.
|
|||
|
|||
#include <array>
|
|||
#include "common/assert.h"
|
|||
#include "video_core/command_classes/nvdec.h"
|
|||
#include "video_core/command_classes/vic.h"
|
|||
#include "video_core/engines/maxwell_3d.h"
|
|||
#include "video_core/gpu.h"
|
|||
#include "video_core/memory_manager.h"
|
|||
#include "video_core/texture_cache/surface_params.h"
|
|||
|
|||
extern "C" { |
|||
#include <libswscale/swscale.h>
|
|||
} |
|||
|
|||
namespace Tegra { |
|||
|
|||
Vic::Vic(GPU& gpu_, std::shared_ptr<Nvdec> nvdec_processor_) |
|||
: gpu(gpu_), nvdec_processor(std::move(nvdec_processor_)) {} |
|||
Vic::~Vic() = default; |
|||
|
|||
void Vic::VicStateWrite(u32 offset, u32 arguments) { |
|||
u8* const state_offset = reinterpret_cast<u8*>(&vic_state) + offset * sizeof(u32); |
|||
std::memcpy(state_offset, &arguments, sizeof(u32)); |
|||
} |
|||
|
|||
void Vic::ProcessMethod(Vic::Method method, const std::vector<u32>& arguments) { |
|||
LOG_DEBUG(HW_GPU, "Vic method 0x{:X}", static_cast<u32>(method)); |
|||
VicStateWrite(static_cast<u32>(method), arguments[0]); |
|||
const u64 arg = static_cast<u64>(arguments[0]) << 8; |
|||
switch (method) { |
|||
case Method::Execute: |
|||
Execute(); |
|||
break; |
|||
case Method::SetConfigStructOffset: |
|||
config_struct_address = arg; |
|||
break; |
|||
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; |
|||
break; |
|||
default: |
|||
break; |
|||
} |
|||
} |
|||
|
|||
void Vic::Execute() { |
|||
if (output_surface_luma_address == 0) { |
|||
LOG_ERROR(Service_NVDRV, "VIC Luma address not set. Recieved 0x{:X}", |
|||
vic_state.output_surface.luma_offset); |
|||
return; |
|||
} |
|||
const VicConfig config{gpu.MemoryManager().Read<u64>(config_struct_address + 0x20)}; |
|||
const VideoPixelFormat pixel_format = |
|||
static_cast<VideoPixelFormat>(config.pixel_format.Value()); |
|||
switch (pixel_format) { |
|||
case VideoPixelFormat::BGRA8: |
|||
case VideoPixelFormat::RGBA8: { |
|||
LOG_TRACE(Service_NVDRV, "Writing RGB Frame"); |
|||
const auto* frame = nvdec_processor->GetFrame(); |
|||
|
|||
if (!frame || frame->width == 0 || frame->height == 0) { |
|||
return; |
|||
} |
|||
if (scaler_ctx == nullptr || frame->width != scaler_width || |
|||
frame->height != scaler_height) { |
|||
const AVPixelFormat target_format = |
|||
(pixel_format == VideoPixelFormat::RGBA8) ? AV_PIX_FMT_RGBA : AV_PIX_FMT_BGRA; |
|||
|
|||
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); |
|||
|
|||
scaler_width = frame->width; |
|||
scaler_height = frame->height; |
|||
} |
|||
// Get Converted frame
|
|||
const std::size_t linear_size = frame->width * frame->height * 4; |
|||
|
|||
using AVMallocPtr = std::unique_ptr<u8, decltype(&av_free)>; |
|||
AVMallocPtr converted_frame_buffer{static_cast<u8*>(av_malloc(linear_size)), av_free}; |
|||
|
|||
const int converted_stride{frame->width * 4}; |
|||
u8* const converted_frame_buf_addr{converted_frame_buffer.get()}; |
|||
|
|||
sws_scale(scaler_ctx, frame->data, frame->linesize, 0, frame->height, |
|||
&converted_frame_buf_addr, &converted_stride); |
|||
|
|||
const u32 blk_kind = static_cast<u32>(config.block_linear_kind); |
|||
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); |
|||
std::vector<u8> swizzled_data(size); |
|||
Tegra::Texture::CopySwizzledData(frame->width, frame->height, 1, 4, 4, |
|||
swizzled_data.data(), converted_frame_buffer.get(), |
|||
false, block_height, 0, 1); |
|||
|
|||
gpu.MemoryManager().WriteBlock(output_surface_luma_address, swizzled_data.data(), size); |
|||
gpu.Maxwell3D().OnMemoryWrite(); |
|||
} else { |
|||
// send pitch linear frame
|
|||
gpu.MemoryManager().WriteBlock(output_surface_luma_address, converted_frame_buf_addr, |
|||
linear_size); |
|||
gpu.Maxwell3D().OnMemoryWrite(); |
|||
} |
|||
break; |
|||
} |
|||
case VideoPixelFormat::Yuv420: { |
|||
LOG_TRACE(Service_NVDRV, "Writing YUV420 Frame"); |
|||
|
|||
const auto* frame = nvdec_processor->GetFrame(); |
|||
|
|||
if (!frame || frame->width == 0 || frame->height == 0) { |
|||
return; |
|||
} |
|||
|
|||
const std::size_t surface_width = config.surface_width_minus1 + 1; |
|||
const std::size_t surface_height = config.surface_height_minus1 + 1; |
|||
const std::size_t half_width = surface_width / 2; |
|||
const std::size_t half_height = config.surface_height_minus1 / 2; |
|||
const std::size_t aligned_width = (surface_width + 0xff) & ~0xff; |
|||
|
|||
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 = frame->linesize[0]; |
|||
const auto half_stride = frame->linesize[1]; |
|||
|
|||
std::vector<u8> luma_buffer(aligned_width * surface_height); |
|||
std::vector<u8> chroma_buffer(aligned_width * half_height); |
|||
|
|||
// Populate luma buffer
|
|||
for (std::size_t y = 0; y < surface_height - 1; ++y) { |
|||
std::size_t src = y * stride; |
|||
std::size_t dst = y * aligned_width; |
|||
|
|||
std::size_t size = surface_width; |
|||
|
|||
for (std::size_t offset = 0; offset < size; ++offset) { |
|||
luma_buffer[dst + offset] = luma_ptr[src + offset]; |
|||
} |
|||
} |
|||
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) { |
|||
std::size_t src = y * half_stride; |
|||
std::size_t dst = y * aligned_width; |
|||
|
|||
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]; |
|||
} |
|||
} |
|||
gpu.MemoryManager().WriteBlock(output_surface_chroma_u_address, chroma_buffer.data(), |
|||
chroma_buffer.size()); |
|||
gpu.Maxwell3D().OnMemoryWrite(); |
|||
break; |
|||
} |
|||
default: |
|||
UNIMPLEMENTED_MSG("Unknown video pixel format {}", config.pixel_format.Value()); |
|||
break; |
|||
} |
|||
} |
|||
|
|||
} // namespace Tegra
|
|||
@ -0,0 +1,110 @@ |
|||
// Copyright 2020 yuzu Emulator Project |
|||
// Licensed under GPLv2 or any later version |
|||
// Refer to the license.txt file included. |
|||
|
|||
#pragma once |
|||
|
|||
#include <memory> |
|||
#include <vector> |
|||
#include "common/bit_field.h" |
|||
#include "common/common_types.h" |
|||
|
|||
struct SwsContext; |
|||
|
|||
namespace Tegra { |
|||
class GPU; |
|||
class Nvdec; |
|||
|
|||
struct PlaneOffsets { |
|||
u32 luma_offset{}; |
|||
u32 chroma_u_offset{}; |
|||
u32 chroma_v_offset{}; |
|||
}; |
|||
|
|||
struct VicRegisters { |
|||
INSERT_PADDING_WORDS(64); |
|||
u32 nop{}; |
|||
INSERT_PADDING_WORDS(15); |
|||
u32 pm_trigger{}; |
|||
INSERT_PADDING_WORDS(47); |
|||
u32 set_application_id{}; |
|||
u32 set_watchdog_timer{}; |
|||
INSERT_PADDING_WORDS(17); |
|||
u32 context_save_area{}; |
|||
u32 context_switch{}; |
|||
INSERT_PADDING_WORDS(43); |
|||
u32 execute{}; |
|||
INSERT_PADDING_WORDS(63); |
|||
std::array<std::array<PlaneOffsets, 8>, 8> surfacex_slots{}; |
|||
u32 picture_index{}; |
|||
u32 control_params{}; |
|||
u32 config_struct_offset{}; |
|||
u32 filter_struct_offset{}; |
|||
u32 palette_offset{}; |
|||
u32 hist_offset{}; |
|||
u32 context_id{}; |
|||
u32 fce_ucode_size{}; |
|||
PlaneOffsets output_surface{}; |
|||
u32 fce_ucode_offset{}; |
|||
INSERT_PADDING_WORDS(4); |
|||
std::array<u32, 8> slot_context_id{}; |
|||
INSERT_PADDING_WORDS(16); |
|||
}; |
|||
static_assert(sizeof(VicRegisters) == 0x7A0, "VicRegisters is an invalid size"); |
|||
|
|||
class Vic { |
|||
public: |
|||
enum class Method : u32 { |
|||
Execute = 0xc0, |
|||
SetControlParams = 0x1c1, |
|||
SetConfigStructOffset = 0x1c2, |
|||
SetOutputSurfaceLumaOffset = 0x1c8, |
|||
SetOutputSurfaceChromaUOffset = 0x1c9, |
|||
SetOutputSurfaceChromaVOffset = 0x1ca |
|||
}; |
|||
|
|||
explicit Vic(GPU& gpu, std::shared_ptr<Tegra::Nvdec> nvdec_processor); |
|||
~Vic(); |
|||
|
|||
/// Write to the device state. |
|||
void ProcessMethod(Vic::Method method, const std::vector<u32>& arguments); |
|||
|
|||
private: |
|||
void Execute(); |
|||
|
|||
void VicStateWrite(u32 offset, u32 arguments); |
|||
VicRegisters vic_state{}; |
|||
|
|||
enum class VideoPixelFormat : u64_le { |
|||
RGBA8 = 0x1f, |
|||
BGRA8 = 0x20, |
|||
Yuv420 = 0x44, |
|||
}; |
|||
|
|||
union VicConfig { |
|||
u64_le raw{}; |
|||
BitField<0, 7, u64_le> pixel_format; |
|||
BitField<7, 2, u64_le> chroma_loc_horiz; |
|||
BitField<9, 2, u64_le> chroma_loc_vert; |
|||
BitField<11, 4, u64_le> block_linear_kind; |
|||
BitField<15, 4, u64_le> block_linear_height_log2; |
|||
BitField<19, 3, u64_le> reserved0; |
|||
BitField<22, 10, u64_le> reserved1; |
|||
BitField<32, 14, u64_le> surface_width_minus1; |
|||
BitField<46, 14, u64_le> surface_height_minus1; |
|||
}; |
|||
|
|||
GPU& gpu; |
|||
std::shared_ptr<Tegra::Nvdec> nvdec_processor; |
|||
|
|||
GPUVAddr config_struct_address{}; |
|||
GPUVAddr output_surface_luma_address{}; |
|||
GPUVAddr output_surface_chroma_u_address{}; |
|||
GPUVAddr output_surface_chroma_v_address{}; |
|||
|
|||
SwsContext* scaler_ctx{}; |
|||
s32 scaler_width{}; |
|||
s32 scaler_height{}; |
|||
}; |
|||
|
|||
} // namespace Tegra |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue