mirror of
https://git.suyu.dev/suyu/suyu
synced 2024-12-25 19:02:45 -06:00
Merge pull request #8467 from FernandoS27/yfc-rel-1
Project yuzu Fried Chicken (Y.F.C.) Part 1
This commit is contained in:
commit
1effa578f1
169 changed files with 6514 additions and 3210 deletions
|
@ -121,6 +121,7 @@ else()
|
||||||
|
|
||||||
if (ARCHITECTURE_x86_64)
|
if (ARCHITECTURE_x86_64)
|
||||||
add_compile_options("-mcx16")
|
add_compile_options("-mcx16")
|
||||||
|
add_compile_options("-fwrapv")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if (APPLE AND CMAKE_CXX_COMPILER_ID STREQUAL Clang)
|
if (APPLE AND CMAKE_CXX_COMPILER_ID STREQUAL Clang)
|
||||||
|
|
|
@ -17,6 +17,8 @@ endif ()
|
||||||
include(GenerateSCMRev)
|
include(GenerateSCMRev)
|
||||||
|
|
||||||
add_library(common STATIC
|
add_library(common STATIC
|
||||||
|
address_space.cpp
|
||||||
|
address_space.h
|
||||||
algorithm.h
|
algorithm.h
|
||||||
alignment.h
|
alignment.h
|
||||||
announce_multiplayer_room.h
|
announce_multiplayer_room.h
|
||||||
|
@ -81,6 +83,8 @@ add_library(common STATIC
|
||||||
microprofile.cpp
|
microprofile.cpp
|
||||||
microprofile.h
|
microprofile.h
|
||||||
microprofileui.h
|
microprofileui.h
|
||||||
|
multi_level_page_table.cpp
|
||||||
|
multi_level_page_table.h
|
||||||
nvidia_flags.cpp
|
nvidia_flags.cpp
|
||||||
nvidia_flags.h
|
nvidia_flags.h
|
||||||
page_table.cpp
|
page_table.cpp
|
||||||
|
|
10
src/common/address_space.cpp
Normal file
10
src/common/address_space.cpp
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
// SPDX-FileCopyrightText: 2021 Skyline Team and Contributors
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
#include "common/address_space.inc"
|
||||||
|
|
||||||
|
namespace Common {
|
||||||
|
|
||||||
|
template class Common::FlatAllocator<u32, 0, 32>;
|
||||||
|
|
||||||
|
}
|
150
src/common/address_space.h
Normal file
150
src/common/address_space.h
Normal file
|
@ -0,0 +1,150 @@
|
||||||
|
// SPDX-FileCopyrightText: 2021 Skyline Team and Contributors
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <concepts>
|
||||||
|
#include <functional>
|
||||||
|
#include <mutex>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "common/common_types.h"
|
||||||
|
|
||||||
|
namespace Common {
|
||||||
|
template <typename VaType, size_t AddressSpaceBits>
|
||||||
|
concept AddressSpaceValid = std::is_unsigned_v<VaType> && sizeof(VaType) * 8 >= AddressSpaceBits;
|
||||||
|
|
||||||
|
struct EmptyStruct {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief FlatAddressSpaceMap provides a generic VA->PA mapping implementation using a sorted vector
|
||||||
|
*/
|
||||||
|
template <typename VaType, VaType UnmappedVa, typename PaType, PaType UnmappedPa,
|
||||||
|
bool PaContigSplit, size_t AddressSpaceBits, typename ExtraBlockInfo = EmptyStruct>
|
||||||
|
requires AddressSpaceValid<VaType, AddressSpaceBits>
|
||||||
|
class FlatAddressSpaceMap {
|
||||||
|
public:
|
||||||
|
/// The maximum VA that this AS can technically reach
|
||||||
|
static constexpr VaType VaMaximum{(1ULL << (AddressSpaceBits - 1)) +
|
||||||
|
((1ULL << (AddressSpaceBits - 1)) - 1)};
|
||||||
|
|
||||||
|
explicit FlatAddressSpaceMap(VaType va_limit,
|
||||||
|
std::function<void(VaType, VaType)> unmap_callback = {});
|
||||||
|
|
||||||
|
FlatAddressSpaceMap() = default;
|
||||||
|
|
||||||
|
void Map(VaType virt, PaType phys, VaType size, ExtraBlockInfo extra_info = {}) {
|
||||||
|
std::scoped_lock lock(block_mutex);
|
||||||
|
MapLocked(virt, phys, size, extra_info);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Unmap(VaType virt, VaType size) {
|
||||||
|
std::scoped_lock lock(block_mutex);
|
||||||
|
UnmapLocked(virt, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
VaType GetVALimit() const {
|
||||||
|
return va_limit;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
/**
|
||||||
|
* @brief Represents a block of memory in the AS, the physical mapping is contiguous until
|
||||||
|
* another block with a different phys address is hit
|
||||||
|
*/
|
||||||
|
struct Block {
|
||||||
|
/// VA of the block
|
||||||
|
VaType virt{UnmappedVa};
|
||||||
|
/// PA of the block, will increase 1-1 with VA until a new block is encountered
|
||||||
|
PaType phys{UnmappedPa};
|
||||||
|
[[no_unique_address]] ExtraBlockInfo extra_info;
|
||||||
|
|
||||||
|
Block() = default;
|
||||||
|
|
||||||
|
Block(VaType virt_, PaType phys_, ExtraBlockInfo extra_info_)
|
||||||
|
: virt(virt_), phys(phys_), extra_info(extra_info_) {}
|
||||||
|
|
||||||
|
bool Valid() const {
|
||||||
|
return virt != UnmappedVa;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Mapped() const {
|
||||||
|
return phys != UnmappedPa;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Unmapped() const {
|
||||||
|
return phys == UnmappedPa;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator<(const VaType& p_virt) const {
|
||||||
|
return virt < p_virt;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Maps a PA range into the given AS region
|
||||||
|
* @note block_mutex MUST be locked when calling this
|
||||||
|
*/
|
||||||
|
void MapLocked(VaType virt, PaType phys, VaType size, ExtraBlockInfo extra_info);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Unmaps the given range and merges it with other unmapped regions
|
||||||
|
* @note block_mutex MUST be locked when calling this
|
||||||
|
*/
|
||||||
|
void UnmapLocked(VaType virt, VaType size);
|
||||||
|
|
||||||
|
std::mutex block_mutex;
|
||||||
|
std::vector<Block> blocks{Block{}};
|
||||||
|
|
||||||
|
/// a soft limit on the maximum VA of the AS
|
||||||
|
VaType va_limit{VaMaximum};
|
||||||
|
|
||||||
|
private:
|
||||||
|
/// Callback called when the mappings in an region have changed
|
||||||
|
std::function<void(VaType, VaType)> unmap_callback{};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief FlatMemoryManager specialises FlatAddressSpaceMap to work as an allocator, with an
|
||||||
|
* initial, fast linear pass and a subsequent slower pass that iterates until it finds a free block
|
||||||
|
*/
|
||||||
|
template <typename VaType, VaType UnmappedVa, size_t AddressSpaceBits>
|
||||||
|
requires AddressSpaceValid<VaType, AddressSpaceBits>
|
||||||
|
class FlatAllocator
|
||||||
|
: public FlatAddressSpaceMap<VaType, UnmappedVa, bool, false, false, AddressSpaceBits> {
|
||||||
|
private:
|
||||||
|
using Base = FlatAddressSpaceMap<VaType, UnmappedVa, bool, false, false, AddressSpaceBits>;
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit FlatAllocator(VaType virt_start, VaType va_limit = Base::VaMaximum);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Allocates a region in the AS of the given size and returns its address
|
||||||
|
*/
|
||||||
|
VaType Allocate(VaType size);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Marks the given region in the AS as allocated
|
||||||
|
*/
|
||||||
|
void AllocateFixed(VaType virt, VaType size);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Frees an AS region so it can be used again
|
||||||
|
*/
|
||||||
|
void Free(VaType virt, VaType size);
|
||||||
|
|
||||||
|
VaType GetVAStart() const {
|
||||||
|
return virt_start;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
/// The base VA of the allocator, no allocations will be below this
|
||||||
|
VaType virt_start;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The end address for the initial linear allocation pass
|
||||||
|
* Once this reaches the AS limit the slower allocation path will be used
|
||||||
|
*/
|
||||||
|
VaType current_linear_alloc_end;
|
||||||
|
};
|
||||||
|
} // namespace Common
|
366
src/common/address_space.inc
Normal file
366
src/common/address_space.inc
Normal file
|
@ -0,0 +1,366 @@
|
||||||
|
// SPDX-FileCopyrightText: 2021 Skyline Team and Contributors
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
#include "common/address_space.h"
|
||||||
|
#include "common/assert.h"
|
||||||
|
|
||||||
|
#define MAP_MEMBER(returnType) \
|
||||||
|
template <typename VaType, VaType UnmappedVa, typename PaType, PaType UnmappedPa, \
|
||||||
|
bool PaContigSplit, size_t AddressSpaceBits, typename ExtraBlockInfo> \
|
||||||
|
requires AddressSpaceValid<VaType, AddressSpaceBits> returnType FlatAddressSpaceMap< \
|
||||||
|
VaType, UnmappedVa, PaType, UnmappedPa, PaContigSplit, AddressSpaceBits, ExtraBlockInfo>
|
||||||
|
#define MAP_MEMBER_CONST() \
|
||||||
|
template <typename VaType, VaType UnmappedVa, typename PaType, PaType UnmappedPa, \
|
||||||
|
bool PaContigSplit, size_t AddressSpaceBits, typename ExtraBlockInfo> \
|
||||||
|
requires AddressSpaceValid<VaType, AddressSpaceBits> FlatAddressSpaceMap< \
|
||||||
|
VaType, UnmappedVa, PaType, UnmappedPa, PaContigSplit, AddressSpaceBits, ExtraBlockInfo>
|
||||||
|
|
||||||
|
#define MM_MEMBER(returnType) \
|
||||||
|
template <typename VaType, VaType UnmappedVa, size_t AddressSpaceBits> \
|
||||||
|
requires AddressSpaceValid<VaType, AddressSpaceBits> returnType \
|
||||||
|
FlatMemoryManager<VaType, UnmappedVa, AddressSpaceBits>
|
||||||
|
|
||||||
|
#define ALLOC_MEMBER(returnType) \
|
||||||
|
template <typename VaType, VaType UnmappedVa, size_t AddressSpaceBits> \
|
||||||
|
requires AddressSpaceValid<VaType, AddressSpaceBits> returnType \
|
||||||
|
FlatAllocator<VaType, UnmappedVa, AddressSpaceBits>
|
||||||
|
#define ALLOC_MEMBER_CONST() \
|
||||||
|
template <typename VaType, VaType UnmappedVa, size_t AddressSpaceBits> \
|
||||||
|
requires AddressSpaceValid<VaType, AddressSpaceBits> \
|
||||||
|
FlatAllocator<VaType, UnmappedVa, AddressSpaceBits>
|
||||||
|
|
||||||
|
namespace Common {
|
||||||
|
MAP_MEMBER_CONST()::FlatAddressSpaceMap(VaType va_limit_,
|
||||||
|
std::function<void(VaType, VaType)> unmap_callback_)
|
||||||
|
: va_limit{va_limit_}, unmap_callback{std::move(unmap_callback_)} {
|
||||||
|
if (va_limit > VaMaximum) {
|
||||||
|
ASSERT_MSG(false, "Invalid VA limit!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MAP_MEMBER(void)::MapLocked(VaType virt, PaType phys, VaType size, ExtraBlockInfo extra_info) {
|
||||||
|
VaType virt_end{virt + size};
|
||||||
|
|
||||||
|
if (virt_end > va_limit) {
|
||||||
|
ASSERT_MSG(false,
|
||||||
|
"Trying to map a block past the VA limit: virt_end: 0x{:X}, va_limit: 0x{:X}",
|
||||||
|
virt_end, va_limit);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto block_end_successor{std::lower_bound(blocks.begin(), blocks.end(), virt_end)};
|
||||||
|
if (block_end_successor == blocks.begin()) {
|
||||||
|
ASSERT_MSG(false, "Trying to map a block before the VA start: virt_end: 0x{:X}", virt_end);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto block_end_predecessor{std::prev(block_end_successor)};
|
||||||
|
|
||||||
|
if (block_end_successor != blocks.end()) {
|
||||||
|
// We have blocks in front of us, if one is directly in front then we don't have to add a
|
||||||
|
// tail
|
||||||
|
if (block_end_successor->virt != virt_end) {
|
||||||
|
PaType tailPhys{[&]() -> PaType {
|
||||||
|
if constexpr (!PaContigSplit) {
|
||||||
|
// Always propagate unmapped regions rather than calculating offset
|
||||||
|
return block_end_predecessor->phys;
|
||||||
|
} else {
|
||||||
|
if (block_end_predecessor->Unmapped()) {
|
||||||
|
// Always propagate unmapped regions rather than calculating offset
|
||||||
|
return block_end_predecessor->phys;
|
||||||
|
} else {
|
||||||
|
return block_end_predecessor->phys + virt_end - block_end_predecessor->virt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()};
|
||||||
|
|
||||||
|
if (block_end_predecessor->virt >= virt) {
|
||||||
|
// If this block's start would be overlapped by the map then reuse it as a tail
|
||||||
|
// block
|
||||||
|
block_end_predecessor->virt = virt_end;
|
||||||
|
block_end_predecessor->phys = tailPhys;
|
||||||
|
block_end_predecessor->extra_info = block_end_predecessor->extra_info;
|
||||||
|
|
||||||
|
// No longer predecessor anymore
|
||||||
|
block_end_successor = block_end_predecessor--;
|
||||||
|
} else {
|
||||||
|
// Else insert a new one and we're done
|
||||||
|
blocks.insert(block_end_successor,
|
||||||
|
{Block(virt, phys, extra_info),
|
||||||
|
Block(virt_end, tailPhys, block_end_predecessor->extra_info)});
|
||||||
|
if (unmap_callback) {
|
||||||
|
unmap_callback(virt, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// block_end_predecessor will always be unmapped as blocks has to be terminated by an
|
||||||
|
// unmapped chunk
|
||||||
|
if (block_end_predecessor != blocks.begin() && block_end_predecessor->virt >= virt) {
|
||||||
|
// Move the unmapped block start backwards
|
||||||
|
block_end_predecessor->virt = virt_end;
|
||||||
|
|
||||||
|
// No longer predecessor anymore
|
||||||
|
block_end_successor = block_end_predecessor--;
|
||||||
|
} else {
|
||||||
|
// Else insert a new one and we're done
|
||||||
|
blocks.insert(block_end_successor,
|
||||||
|
{Block(virt, phys, extra_info), Block(virt_end, UnmappedPa, {})});
|
||||||
|
if (unmap_callback) {
|
||||||
|
unmap_callback(virt, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto block_start_successor{block_end_successor};
|
||||||
|
|
||||||
|
// Walk the block vector to find the start successor as this is more efficient than another
|
||||||
|
// binary search in most scenarios
|
||||||
|
while (std::prev(block_start_successor)->virt >= virt) {
|
||||||
|
block_start_successor--;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that the start successor is either the end block or something in between
|
||||||
|
if (block_start_successor->virt > virt_end) {
|
||||||
|
ASSERT_MSG(false, "Unsorted block in AS map: virt: 0x{:X}", block_start_successor->virt);
|
||||||
|
} else if (block_start_successor->virt == virt_end) {
|
||||||
|
// We need to create a new block as there are none spare that we would overwrite
|
||||||
|
blocks.insert(block_start_successor, Block(virt, phys, extra_info));
|
||||||
|
} else {
|
||||||
|
// Erase overwritten blocks
|
||||||
|
if (auto eraseStart{std::next(block_start_successor)}; eraseStart != block_end_successor) {
|
||||||
|
blocks.erase(eraseStart, block_end_successor);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reuse a block that would otherwise be overwritten as a start block
|
||||||
|
block_start_successor->virt = virt;
|
||||||
|
block_start_successor->phys = phys;
|
||||||
|
block_start_successor->extra_info = extra_info;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (unmap_callback) {
|
||||||
|
unmap_callback(virt, size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MAP_MEMBER(void)::UnmapLocked(VaType virt, VaType size) {
|
||||||
|
VaType virt_end{virt + size};
|
||||||
|
|
||||||
|
if (virt_end > va_limit) {
|
||||||
|
ASSERT_MSG(false,
|
||||||
|
"Trying to map a block past the VA limit: virt_end: 0x{:X}, va_limit: 0x{:X}",
|
||||||
|
virt_end, va_limit);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto block_end_successor{std::lower_bound(blocks.begin(), blocks.end(), virt_end)};
|
||||||
|
if (block_end_successor == blocks.begin()) {
|
||||||
|
ASSERT_MSG(false, "Trying to unmap a block before the VA start: virt_end: 0x{:X}",
|
||||||
|
virt_end);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto block_end_predecessor{std::prev(block_end_successor)};
|
||||||
|
|
||||||
|
auto walk_back_to_predecessor{[&](auto iter) {
|
||||||
|
while (iter->virt >= virt) {
|
||||||
|
iter--;
|
||||||
|
}
|
||||||
|
|
||||||
|
return iter;
|
||||||
|
}};
|
||||||
|
|
||||||
|
auto erase_blocks_with_end_unmapped{[&](auto unmappedEnd) {
|
||||||
|
auto block_start_predecessor{walk_back_to_predecessor(unmappedEnd)};
|
||||||
|
auto block_start_successor{std::next(block_start_predecessor)};
|
||||||
|
|
||||||
|
auto eraseEnd{[&]() {
|
||||||
|
if (block_start_predecessor->Unmapped()) {
|
||||||
|
// If the start predecessor is unmapped then we can erase everything in our region
|
||||||
|
// and be done
|
||||||
|
return std::next(unmappedEnd);
|
||||||
|
} else {
|
||||||
|
// Else reuse the end predecessor as the start of our unmapped region then erase all
|
||||||
|
// up to it
|
||||||
|
unmappedEnd->virt = virt;
|
||||||
|
return unmappedEnd;
|
||||||
|
}
|
||||||
|
}()};
|
||||||
|
|
||||||
|
// We can't have two unmapped regions after each other
|
||||||
|
if (eraseEnd != blocks.end() &&
|
||||||
|
(eraseEnd == block_start_successor ||
|
||||||
|
(block_start_predecessor->Unmapped() && eraseEnd->Unmapped()))) {
|
||||||
|
ASSERT_MSG(false, "Multiple contiguous unmapped regions are unsupported!");
|
||||||
|
}
|
||||||
|
|
||||||
|
blocks.erase(block_start_successor, eraseEnd);
|
||||||
|
}};
|
||||||
|
|
||||||
|
// We can avoid any splitting logic if these are the case
|
||||||
|
if (block_end_predecessor->Unmapped()) {
|
||||||
|
if (block_end_predecessor->virt > virt) {
|
||||||
|
erase_blocks_with_end_unmapped(block_end_predecessor);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (unmap_callback) {
|
||||||
|
unmap_callback(virt, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
return; // The region is unmapped, bail out early
|
||||||
|
} else if (block_end_successor->virt == virt_end && block_end_successor->Unmapped()) {
|
||||||
|
erase_blocks_with_end_unmapped(block_end_successor);
|
||||||
|
|
||||||
|
if (unmap_callback) {
|
||||||
|
unmap_callback(virt, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
return; // The region is unmapped here and doesn't need splitting, bail out early
|
||||||
|
} else if (block_end_successor == blocks.end()) {
|
||||||
|
// This should never happen as the end should always follow an unmapped block
|
||||||
|
ASSERT_MSG(false, "Unexpected Memory Manager state!");
|
||||||
|
} else if (block_end_successor->virt != virt_end) {
|
||||||
|
// If one block is directly in front then we don't have to add a tail
|
||||||
|
|
||||||
|
// The previous block is mapped so we will need to add a tail with an offset
|
||||||
|
PaType tailPhys{[&]() {
|
||||||
|
if constexpr (PaContigSplit) {
|
||||||
|
return block_end_predecessor->phys + virt_end - block_end_predecessor->virt;
|
||||||
|
} else {
|
||||||
|
return block_end_predecessor->phys;
|
||||||
|
}
|
||||||
|
}()};
|
||||||
|
|
||||||
|
if (block_end_predecessor->virt >= virt) {
|
||||||
|
// If this block's start would be overlapped by the unmap then reuse it as a tail block
|
||||||
|
block_end_predecessor->virt = virt_end;
|
||||||
|
block_end_predecessor->phys = tailPhys;
|
||||||
|
|
||||||
|
// No longer predecessor anymore
|
||||||
|
block_end_successor = block_end_predecessor--;
|
||||||
|
} else {
|
||||||
|
blocks.insert(block_end_successor,
|
||||||
|
{Block(virt, UnmappedPa, {}),
|
||||||
|
Block(virt_end, tailPhys, block_end_predecessor->extra_info)});
|
||||||
|
if (unmap_callback) {
|
||||||
|
unmap_callback(virt, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
// The previous block is mapped and ends before
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Walk the block vector to find the start predecessor as this is more efficient than another
|
||||||
|
// binary search in most scenarios
|
||||||
|
auto block_start_predecessor{walk_back_to_predecessor(block_end_successor)};
|
||||||
|
auto block_start_successor{std::next(block_start_predecessor)};
|
||||||
|
|
||||||
|
if (block_start_successor->virt > virt_end) {
|
||||||
|
ASSERT_MSG(false, "Unsorted block in AS map: virt: 0x{:X}", block_start_successor->virt);
|
||||||
|
} else if (block_start_successor->virt == virt_end) {
|
||||||
|
// There are no blocks between the start and the end that would let us skip inserting a new
|
||||||
|
// one for head
|
||||||
|
|
||||||
|
// The previous block is may be unmapped, if so we don't need to insert any unmaps after it
|
||||||
|
if (block_start_predecessor->Mapped()) {
|
||||||
|
blocks.insert(block_start_successor, Block(virt, UnmappedPa, {}));
|
||||||
|
}
|
||||||
|
} else if (block_start_predecessor->Unmapped()) {
|
||||||
|
// If the previous block is unmapped
|
||||||
|
blocks.erase(block_start_successor, block_end_predecessor);
|
||||||
|
} else {
|
||||||
|
// Erase overwritten blocks, skipping the first one as we have written the unmapped start
|
||||||
|
// block there
|
||||||
|
if (auto eraseStart{std::next(block_start_successor)}; eraseStart != block_end_successor) {
|
||||||
|
blocks.erase(eraseStart, block_end_successor);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add in the unmapped block header
|
||||||
|
block_start_successor->virt = virt;
|
||||||
|
block_start_successor->phys = UnmappedPa;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (unmap_callback)
|
||||||
|
unmap_callback(virt, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
ALLOC_MEMBER_CONST()::FlatAllocator(VaType virt_start_, VaType va_limit_)
|
||||||
|
: Base{va_limit_}, virt_start{virt_start_}, current_linear_alloc_end{virt_start_} {}
|
||||||
|
|
||||||
|
ALLOC_MEMBER(VaType)::Allocate(VaType size) {
|
||||||
|
std::scoped_lock lock(this->block_mutex);
|
||||||
|
|
||||||
|
VaType alloc_start{UnmappedVa};
|
||||||
|
VaType alloc_end{current_linear_alloc_end + size};
|
||||||
|
|
||||||
|
// Avoid searching backwards in the address space if possible
|
||||||
|
if (alloc_end >= current_linear_alloc_end && alloc_end <= this->va_limit) {
|
||||||
|
auto alloc_end_successor{
|
||||||
|
std::lower_bound(this->blocks.begin(), this->blocks.end(), alloc_end)};
|
||||||
|
if (alloc_end_successor == this->blocks.begin()) {
|
||||||
|
ASSERT_MSG(false, "First block in AS map is invalid!");
|
||||||
|
}
|
||||||
|
|
||||||
|
auto alloc_end_predecessor{std::prev(alloc_end_successor)};
|
||||||
|
if (alloc_end_predecessor->virt <= current_linear_alloc_end) {
|
||||||
|
alloc_start = current_linear_alloc_end;
|
||||||
|
} else {
|
||||||
|
// Skip over fixed any mappings in front of us
|
||||||
|
while (alloc_end_successor != this->blocks.end()) {
|
||||||
|
if (alloc_end_successor->virt - alloc_end_predecessor->virt < size ||
|
||||||
|
alloc_end_predecessor->Mapped()) {
|
||||||
|
alloc_start = alloc_end_predecessor->virt;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
alloc_end_predecessor = alloc_end_successor++;
|
||||||
|
|
||||||
|
// Use the VA limit to calculate if we can fit in the final block since it has no
|
||||||
|
// successor
|
||||||
|
if (alloc_end_successor == this->blocks.end()) {
|
||||||
|
alloc_end = alloc_end_predecessor->virt + size;
|
||||||
|
|
||||||
|
if (alloc_end >= alloc_end_predecessor->virt && alloc_end <= this->va_limit) {
|
||||||
|
alloc_start = alloc_end_predecessor->virt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (alloc_start != UnmappedVa) {
|
||||||
|
current_linear_alloc_end = alloc_start + size;
|
||||||
|
} else { // If linear allocation overflows the AS then find a gap
|
||||||
|
if (this->blocks.size() <= 2) {
|
||||||
|
ASSERT_MSG(false, "Unexpected allocator state!");
|
||||||
|
}
|
||||||
|
|
||||||
|
auto search_predecessor{this->blocks.begin()};
|
||||||
|
auto search_successor{std::next(search_predecessor)};
|
||||||
|
|
||||||
|
while (search_successor != this->blocks.end() &&
|
||||||
|
(search_successor->virt - search_predecessor->virt < size ||
|
||||||
|
search_predecessor->Mapped())) {
|
||||||
|
search_predecessor = search_successor++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (search_successor != this->blocks.end()) {
|
||||||
|
alloc_start = search_predecessor->virt;
|
||||||
|
} else {
|
||||||
|
return {}; // AS is full
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this->MapLocked(alloc_start, true, size, {});
|
||||||
|
return alloc_start;
|
||||||
|
}
|
||||||
|
|
||||||
|
ALLOC_MEMBER(void)::AllocateFixed(VaType virt, VaType size) {
|
||||||
|
this->Map(virt, true, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
ALLOC_MEMBER(void)::Free(VaType virt, VaType size) {
|
||||||
|
this->Unmap(virt, size);
|
||||||
|
}
|
||||||
|
} // namespace Common
|
|
@ -24,4 +24,12 @@ template <class ForwardIt, class T, class Compare = std::less<>>
|
||||||
return first != last && !comp(value, *first) ? first : last;
|
return first != last && !comp(value, *first) ? first : last;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <typename T, typename Func, typename... Args>
|
||||||
|
T FoldRight(T initial_value, Func&& func, Args&&... args) {
|
||||||
|
T value{initial_value};
|
||||||
|
const auto high_func = [&value, &func]<typename U>(U x) { value = func(value, x); };
|
||||||
|
(std::invoke(high_func, std::forward<Args>(args)), ...);
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace Common
|
} // namespace Common
|
||||||
|
|
|
@ -18,4 +18,11 @@ struct PairHash {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct IdentityHash {
|
||||||
|
[[nodiscard]] size_t operator()(T value) const noexcept {
|
||||||
|
return static_cast<size_t>(value);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace Common
|
} // namespace Common
|
||||||
|
|
9
src/common/multi_level_page_table.cpp
Normal file
9
src/common/multi_level_page_table.cpp
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
// SPDX-FileCopyrightText: 2021 yuzu Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include "common/multi_level_page_table.inc"
|
||||||
|
|
||||||
|
namespace Common {
|
||||||
|
template class Common::MultiLevelPageTable<u64>;
|
||||||
|
template class Common::MultiLevelPageTable<u32>;
|
||||||
|
} // namespace Common
|
78
src/common/multi_level_page_table.h
Normal file
78
src/common/multi_level_page_table.h
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
// SPDX-FileCopyrightText: 2021 yuzu Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <type_traits>
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "common/common_types.h"
|
||||||
|
|
||||||
|
namespace Common {
|
||||||
|
|
||||||
|
template <typename BaseAddr>
|
||||||
|
class MultiLevelPageTable final {
|
||||||
|
public:
|
||||||
|
constexpr MultiLevelPageTable() = default;
|
||||||
|
explicit MultiLevelPageTable(std::size_t address_space_bits, std::size_t first_level_bits,
|
||||||
|
std::size_t page_bits);
|
||||||
|
|
||||||
|
~MultiLevelPageTable() noexcept;
|
||||||
|
|
||||||
|
MultiLevelPageTable(const MultiLevelPageTable&) = delete;
|
||||||
|
MultiLevelPageTable& operator=(const MultiLevelPageTable&) = delete;
|
||||||
|
|
||||||
|
MultiLevelPageTable(MultiLevelPageTable&& other) noexcept
|
||||||
|
: address_space_bits{std::exchange(other.address_space_bits, 0)},
|
||||||
|
first_level_bits{std::exchange(other.first_level_bits, 0)}, page_bits{std::exchange(
|
||||||
|
other.page_bits, 0)},
|
||||||
|
first_level_shift{std::exchange(other.first_level_shift, 0)},
|
||||||
|
first_level_chunk_size{std::exchange(other.first_level_chunk_size, 0)},
|
||||||
|
first_level_map{std::move(other.first_level_map)}, base_ptr{std::exchange(other.base_ptr,
|
||||||
|
nullptr)} {}
|
||||||
|
|
||||||
|
MultiLevelPageTable& operator=(MultiLevelPageTable&& other) noexcept {
|
||||||
|
address_space_bits = std::exchange(other.address_space_bits, 0);
|
||||||
|
first_level_bits = std::exchange(other.first_level_bits, 0);
|
||||||
|
page_bits = std::exchange(other.page_bits, 0);
|
||||||
|
first_level_shift = std::exchange(other.first_level_shift, 0);
|
||||||
|
first_level_chunk_size = std::exchange(other.first_level_chunk_size, 0);
|
||||||
|
alloc_size = std::exchange(other.alloc_size, 0);
|
||||||
|
first_level_map = std::move(other.first_level_map);
|
||||||
|
base_ptr = std::exchange(other.base_ptr, nullptr);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ReserveRange(u64 start, std::size_t size);
|
||||||
|
|
||||||
|
[[nodiscard]] const BaseAddr& operator[](std::size_t index) const {
|
||||||
|
return base_ptr[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] BaseAddr& operator[](std::size_t index) {
|
||||||
|
return base_ptr[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] BaseAddr* data() {
|
||||||
|
return base_ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] const BaseAddr* data() const {
|
||||||
|
return base_ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
void AllocateLevel(u64 level);
|
||||||
|
|
||||||
|
std::size_t address_space_bits{};
|
||||||
|
std::size_t first_level_bits{};
|
||||||
|
std::size_t page_bits{};
|
||||||
|
std::size_t first_level_shift{};
|
||||||
|
std::size_t first_level_chunk_size{};
|
||||||
|
std::size_t alloc_size{};
|
||||||
|
std::vector<void*> first_level_map{};
|
||||||
|
BaseAddr* base_ptr{};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Common
|
84
src/common/multi_level_page_table.inc
Normal file
84
src/common/multi_level_page_table.inc
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
// SPDX-FileCopyrightText: 2021 yuzu Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
#include <windows.h>
|
||||||
|
#else
|
||||||
|
#include <sys/mman.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "common/assert.h"
|
||||||
|
#include "common/multi_level_page_table.h"
|
||||||
|
|
||||||
|
namespace Common {
|
||||||
|
|
||||||
|
template <typename BaseAddr>
|
||||||
|
MultiLevelPageTable<BaseAddr>::MultiLevelPageTable(std::size_t address_space_bits_,
|
||||||
|
std::size_t first_level_bits_,
|
||||||
|
std::size_t page_bits_)
|
||||||
|
: address_space_bits{address_space_bits_},
|
||||||
|
first_level_bits{first_level_bits_}, page_bits{page_bits_} {
|
||||||
|
if (page_bits == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
first_level_shift = address_space_bits - first_level_bits;
|
||||||
|
first_level_chunk_size = (1ULL << (first_level_shift - page_bits)) * sizeof(BaseAddr);
|
||||||
|
alloc_size = (1ULL << (address_space_bits - page_bits)) * sizeof(BaseAddr);
|
||||||
|
std::size_t first_level_size = 1ULL << first_level_bits;
|
||||||
|
first_level_map.resize(first_level_size, nullptr);
|
||||||
|
#ifdef _WIN32
|
||||||
|
void* base{VirtualAlloc(nullptr, alloc_size, MEM_RESERVE, PAGE_READWRITE)};
|
||||||
|
#else
|
||||||
|
void* base{mmap(nullptr, alloc_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)};
|
||||||
|
|
||||||
|
if (base == MAP_FAILED) {
|
||||||
|
base = nullptr;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
ASSERT(base);
|
||||||
|
base_ptr = reinterpret_cast<BaseAddr*>(base);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename BaseAddr>
|
||||||
|
MultiLevelPageTable<BaseAddr>::~MultiLevelPageTable() noexcept {
|
||||||
|
if (!base_ptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
#ifdef _WIN32
|
||||||
|
ASSERT(VirtualFree(base_ptr, 0, MEM_RELEASE));
|
||||||
|
#else
|
||||||
|
ASSERT(munmap(base_ptr, alloc_size) == 0);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename BaseAddr>
|
||||||
|
void MultiLevelPageTable<BaseAddr>::ReserveRange(u64 start, std::size_t size) {
|
||||||
|
const u64 new_start = start >> first_level_shift;
|
||||||
|
const u64 new_end = (start + size) >> first_level_shift;
|
||||||
|
for (u64 i = new_start; i <= new_end; i++) {
|
||||||
|
if (!first_level_map[i]) {
|
||||||
|
AllocateLevel(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename BaseAddr>
|
||||||
|
void MultiLevelPageTable<BaseAddr>::AllocateLevel(u64 level) {
|
||||||
|
void* ptr = reinterpret_cast<char *>(base_ptr) + level * first_level_chunk_size;
|
||||||
|
#ifdef _WIN32
|
||||||
|
void* base{VirtualAlloc(ptr, first_level_chunk_size, MEM_COMMIT, PAGE_READWRITE)};
|
||||||
|
#else
|
||||||
|
void* base{mmap(ptr, first_level_chunk_size, PROT_READ | PROT_WRITE,
|
||||||
|
MAP_ANONYMOUS | MAP_PRIVATE, -1, 0)};
|
||||||
|
|
||||||
|
if (base == MAP_FAILED) {
|
||||||
|
base = nullptr;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
ASSERT(base);
|
||||||
|
|
||||||
|
first_level_map[level] = base;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Common
|
|
@ -138,8 +138,6 @@ add_library(core STATIC
|
||||||
frontend/emu_window.h
|
frontend/emu_window.h
|
||||||
frontend/framebuffer_layout.cpp
|
frontend/framebuffer_layout.cpp
|
||||||
frontend/framebuffer_layout.h
|
frontend/framebuffer_layout.h
|
||||||
hardware_interrupt_manager.cpp
|
|
||||||
hardware_interrupt_manager.h
|
|
||||||
hid/emulated_console.cpp
|
hid/emulated_console.cpp
|
||||||
hid/emulated_console.h
|
hid/emulated_console.h
|
||||||
hid/emulated_controller.cpp
|
hid/emulated_controller.cpp
|
||||||
|
@ -550,6 +548,12 @@ add_library(core STATIC
|
||||||
hle/service/ns/ns.h
|
hle/service/ns/ns.h
|
||||||
hle/service/ns/pdm_qry.cpp
|
hle/service/ns/pdm_qry.cpp
|
||||||
hle/service/ns/pdm_qry.h
|
hle/service/ns/pdm_qry.h
|
||||||
|
hle/service/nvdrv/core/container.cpp
|
||||||
|
hle/service/nvdrv/core/container.h
|
||||||
|
hle/service/nvdrv/core/nvmap.cpp
|
||||||
|
hle/service/nvdrv/core/nvmap.h
|
||||||
|
hle/service/nvdrv/core/syncpoint_manager.cpp
|
||||||
|
hle/service/nvdrv/core/syncpoint_manager.h
|
||||||
hle/service/nvdrv/devices/nvdevice.h
|
hle/service/nvdrv/devices/nvdevice.h
|
||||||
hle/service/nvdrv/devices/nvdisp_disp0.cpp
|
hle/service/nvdrv/devices/nvdisp_disp0.cpp
|
||||||
hle/service/nvdrv/devices/nvdisp_disp0.h
|
hle/service/nvdrv/devices/nvdisp_disp0.h
|
||||||
|
@ -578,8 +582,6 @@ add_library(core STATIC
|
||||||
hle/service/nvdrv/nvdrv_interface.h
|
hle/service/nvdrv/nvdrv_interface.h
|
||||||
hle/service/nvdrv/nvmemp.cpp
|
hle/service/nvdrv/nvmemp.cpp
|
||||||
hle/service/nvdrv/nvmemp.h
|
hle/service/nvdrv/nvmemp.h
|
||||||
hle/service/nvdrv/syncpoint_manager.cpp
|
|
||||||
hle/service/nvdrv/syncpoint_manager.h
|
|
||||||
hle/service/nvflinger/binder.h
|
hle/service/nvflinger/binder.h
|
||||||
hle/service/nvflinger/buffer_item.h
|
hle/service/nvflinger/buffer_item.h
|
||||||
hle/service/nvflinger/buffer_item_consumer.cpp
|
hle/service/nvflinger/buffer_item_consumer.cpp
|
||||||
|
|
|
@ -27,7 +27,6 @@
|
||||||
#include "core/file_sys/savedata_factory.h"
|
#include "core/file_sys/savedata_factory.h"
|
||||||
#include "core/file_sys/vfs_concat.h"
|
#include "core/file_sys/vfs_concat.h"
|
||||||
#include "core/file_sys/vfs_real.h"
|
#include "core/file_sys/vfs_real.h"
|
||||||
#include "core/hardware_interrupt_manager.h"
|
|
||||||
#include "core/hid/hid_core.h"
|
#include "core/hid/hid_core.h"
|
||||||
#include "core/hle/kernel/k_memory_manager.h"
|
#include "core/hle/kernel/k_memory_manager.h"
|
||||||
#include "core/hle/kernel/k_process.h"
|
#include "core/hle/kernel/k_process.h"
|
||||||
|
@ -51,6 +50,7 @@
|
||||||
#include "core/telemetry_session.h"
|
#include "core/telemetry_session.h"
|
||||||
#include "core/tools/freezer.h"
|
#include "core/tools/freezer.h"
|
||||||
#include "network/network.h"
|
#include "network/network.h"
|
||||||
|
#include "video_core/host1x/host1x.h"
|
||||||
#include "video_core/renderer_base.h"
|
#include "video_core/renderer_base.h"
|
||||||
#include "video_core/video_core.h"
|
#include "video_core/video_core.h"
|
||||||
|
|
||||||
|
@ -215,6 +215,7 @@ struct System::Impl {
|
||||||
|
|
||||||
telemetry_session = std::make_unique<Core::TelemetrySession>();
|
telemetry_session = std::make_unique<Core::TelemetrySession>();
|
||||||
|
|
||||||
|
host1x_core = std::make_unique<Tegra::Host1x::Host1x>(system);
|
||||||
gpu_core = VideoCore::CreateGPU(emu_window, system);
|
gpu_core = VideoCore::CreateGPU(emu_window, system);
|
||||||
if (!gpu_core) {
|
if (!gpu_core) {
|
||||||
return SystemResultStatus::ErrorVideoCore;
|
return SystemResultStatus::ErrorVideoCore;
|
||||||
|
@ -224,7 +225,6 @@ struct System::Impl {
|
||||||
|
|
||||||
service_manager = std::make_shared<Service::SM::ServiceManager>(kernel);
|
service_manager = std::make_shared<Service::SM::ServiceManager>(kernel);
|
||||||
services = std::make_unique<Service::Services>(service_manager, system);
|
services = std::make_unique<Service::Services>(service_manager, system);
|
||||||
interrupt_manager = std::make_unique<Hardware::InterruptManager>(system);
|
|
||||||
|
|
||||||
// Initialize time manager, which must happen after kernel is created
|
// Initialize time manager, which must happen after kernel is created
|
||||||
time_manager.Initialize();
|
time_manager.Initialize();
|
||||||
|
@ -373,6 +373,7 @@ struct System::Impl {
|
||||||
app_loader.reset();
|
app_loader.reset();
|
||||||
audio_core.reset();
|
audio_core.reset();
|
||||||
gpu_core.reset();
|
gpu_core.reset();
|
||||||
|
host1x_core.reset();
|
||||||
perf_stats.reset();
|
perf_stats.reset();
|
||||||
kernel.Shutdown();
|
kernel.Shutdown();
|
||||||
memory.Reset();
|
memory.Reset();
|
||||||
|
@ -450,7 +451,7 @@ struct System::Impl {
|
||||||
/// AppLoader used to load the current executing application
|
/// AppLoader used to load the current executing application
|
||||||
std::unique_ptr<Loader::AppLoader> app_loader;
|
std::unique_ptr<Loader::AppLoader> app_loader;
|
||||||
std::unique_ptr<Tegra::GPU> gpu_core;
|
std::unique_ptr<Tegra::GPU> gpu_core;
|
||||||
std::unique_ptr<Hardware::InterruptManager> interrupt_manager;
|
std::unique_ptr<Tegra::Host1x::Host1x> host1x_core;
|
||||||
std::unique_ptr<Core::DeviceMemory> device_memory;
|
std::unique_ptr<Core::DeviceMemory> device_memory;
|
||||||
std::unique_ptr<AudioCore::AudioCore> audio_core;
|
std::unique_ptr<AudioCore::AudioCore> audio_core;
|
||||||
Core::Memory::Memory memory;
|
Core::Memory::Memory memory;
|
||||||
|
@ -668,12 +669,12 @@ const Tegra::GPU& System::GPU() const {
|
||||||
return *impl->gpu_core;
|
return *impl->gpu_core;
|
||||||
}
|
}
|
||||||
|
|
||||||
Core::Hardware::InterruptManager& System::InterruptManager() {
|
Tegra::Host1x::Host1x& System::Host1x() {
|
||||||
return *impl->interrupt_manager;
|
return *impl->host1x_core;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Core::Hardware::InterruptManager& System::InterruptManager() const {
|
const Tegra::Host1x::Host1x& System::Host1x() const {
|
||||||
return *impl->interrupt_manager;
|
return *impl->host1x_core;
|
||||||
}
|
}
|
||||||
|
|
||||||
VideoCore::RendererBase& System::Renderer() {
|
VideoCore::RendererBase& System::Renderer() {
|
||||||
|
|
|
@ -74,6 +74,9 @@ class TimeManager;
|
||||||
namespace Tegra {
|
namespace Tegra {
|
||||||
class DebugContext;
|
class DebugContext;
|
||||||
class GPU;
|
class GPU;
|
||||||
|
namespace Host1x {
|
||||||
|
class Host1x;
|
||||||
|
} // namespace Host1x
|
||||||
} // namespace Tegra
|
} // namespace Tegra
|
||||||
|
|
||||||
namespace VideoCore {
|
namespace VideoCore {
|
||||||
|
@ -88,10 +91,6 @@ namespace Core::Timing {
|
||||||
class CoreTiming;
|
class CoreTiming;
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace Core::Hardware {
|
|
||||||
class InterruptManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace Core::HID {
|
namespace Core::HID {
|
||||||
class HIDCore;
|
class HIDCore;
|
||||||
}
|
}
|
||||||
|
@ -260,6 +259,12 @@ public:
|
||||||
/// Gets an immutable reference to the GPU interface.
|
/// Gets an immutable reference to the GPU interface.
|
||||||
[[nodiscard]] const Tegra::GPU& GPU() const;
|
[[nodiscard]] const Tegra::GPU& GPU() const;
|
||||||
|
|
||||||
|
/// Gets a mutable reference to the Host1x interface
|
||||||
|
[[nodiscard]] Tegra::Host1x::Host1x& Host1x();
|
||||||
|
|
||||||
|
/// Gets an immutable reference to the Host1x interface.
|
||||||
|
[[nodiscard]] const Tegra::Host1x::Host1x& Host1x() const;
|
||||||
|
|
||||||
/// Gets a mutable reference to the renderer.
|
/// Gets a mutable reference to the renderer.
|
||||||
[[nodiscard]] VideoCore::RendererBase& Renderer();
|
[[nodiscard]] VideoCore::RendererBase& Renderer();
|
||||||
|
|
||||||
|
@ -296,12 +301,6 @@ public:
|
||||||
/// Provides a constant reference to the core timing instance.
|
/// Provides a constant reference to the core timing instance.
|
||||||
[[nodiscard]] const Timing::CoreTiming& CoreTiming() const;
|
[[nodiscard]] const Timing::CoreTiming& CoreTiming() const;
|
||||||
|
|
||||||
/// Provides a reference to the interrupt manager instance.
|
|
||||||
[[nodiscard]] Core::Hardware::InterruptManager& InterruptManager();
|
|
||||||
|
|
||||||
/// Provides a constant reference to the interrupt manager instance.
|
|
||||||
[[nodiscard]] const Core::Hardware::InterruptManager& InterruptManager() const;
|
|
||||||
|
|
||||||
/// Provides a reference to the kernel instance.
|
/// Provides a reference to the kernel instance.
|
||||||
[[nodiscard]] Kernel::KernelCore& Kernel();
|
[[nodiscard]] Kernel::KernelCore& Kernel();
|
||||||
|
|
||||||
|
|
|
@ -1,32 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
#include "core/core.h"
|
|
||||||
#include "core/core_timing.h"
|
|
||||||
#include "core/hardware_interrupt_manager.h"
|
|
||||||
#include "core/hle/service/nvdrv/nvdrv_interface.h"
|
|
||||||
#include "core/hle/service/sm/sm.h"
|
|
||||||
|
|
||||||
namespace Core::Hardware {
|
|
||||||
|
|
||||||
InterruptManager::InterruptManager(Core::System& system_in) : system(system_in) {
|
|
||||||
gpu_interrupt_event = Core::Timing::CreateEvent(
|
|
||||||
"GPUInterrupt",
|
|
||||||
[this](std::uintptr_t message, u64 time,
|
|
||||||
std::chrono::nanoseconds) -> std::optional<std::chrono::nanoseconds> {
|
|
||||||
auto nvdrv = system.ServiceManager().GetService<Service::Nvidia::NVDRV>("nvdrv");
|
|
||||||
const u32 syncpt = static_cast<u32>(message >> 32);
|
|
||||||
const u32 value = static_cast<u32>(message);
|
|
||||||
nvdrv->SignalGPUInterruptSyncpt(syncpt, value);
|
|
||||||
return std::nullopt;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
InterruptManager::~InterruptManager() = default;
|
|
||||||
|
|
||||||
void InterruptManager::GPUInterruptSyncpt(const u32 syncpoint_id, const u32 value) {
|
|
||||||
const u64 msg = (static_cast<u64>(syncpoint_id) << 32ULL) | value;
|
|
||||||
system.CoreTiming().ScheduleEvent(std::chrono::nanoseconds{10}, gpu_interrupt_event, msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace Core::Hardware
|
|
|
@ -1,32 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <memory>
|
|
||||||
|
|
||||||
#include "common/common_types.h"
|
|
||||||
|
|
||||||
namespace Core {
|
|
||||||
class System;
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace Core::Timing {
|
|
||||||
struct EventType;
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace Core::Hardware {
|
|
||||||
|
|
||||||
class InterruptManager {
|
|
||||||
public:
|
|
||||||
explicit InterruptManager(Core::System& system);
|
|
||||||
~InterruptManager();
|
|
||||||
|
|
||||||
void GPUInterruptSyncpt(u32 syncpoint_id, u32 value);
|
|
||||||
|
|
||||||
private:
|
|
||||||
Core::System& system;
|
|
||||||
std::shared_ptr<Core::Timing::EventType> gpu_interrupt_event;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace Core::Hardware
|
|
50
src/core/hle/service/nvdrv/core/container.cpp
Normal file
50
src/core/hle/service/nvdrv/core/container.cpp
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
// SPDX-FileCopyrightText: 2022 yuzu Emulator Project
|
||||||
|
// SPDX-FileCopyrightText: 2022 Skyline Team and Contributors
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
#include "core/hle/service/nvdrv/core/container.h"
|
||||||
|
#include "core/hle/service/nvdrv/core/nvmap.h"
|
||||||
|
#include "core/hle/service/nvdrv/core/syncpoint_manager.h"
|
||||||
|
#include "video_core/host1x/host1x.h"
|
||||||
|
|
||||||
|
namespace Service::Nvidia::NvCore {
|
||||||
|
|
||||||
|
struct ContainerImpl {
|
||||||
|
explicit ContainerImpl(Tegra::Host1x::Host1x& host1x_)
|
||||||
|
: file{host1x_}, manager{host1x_}, device_file_data{} {}
|
||||||
|
NvMap file;
|
||||||
|
SyncpointManager manager;
|
||||||
|
Container::Host1xDeviceFileData device_file_data;
|
||||||
|
};
|
||||||
|
|
||||||
|
Container::Container(Tegra::Host1x::Host1x& host1x_) {
|
||||||
|
impl = std::make_unique<ContainerImpl>(host1x_);
|
||||||
|
}
|
||||||
|
|
||||||
|
Container::~Container() = default;
|
||||||
|
|
||||||
|
NvMap& Container::GetNvMapFile() {
|
||||||
|
return impl->file;
|
||||||
|
}
|
||||||
|
|
||||||
|
const NvMap& Container::GetNvMapFile() const {
|
||||||
|
return impl->file;
|
||||||
|
}
|
||||||
|
|
||||||
|
Container::Host1xDeviceFileData& Container::Host1xDeviceFile() {
|
||||||
|
return impl->device_file_data;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Container::Host1xDeviceFileData& Container::Host1xDeviceFile() const {
|
||||||
|
return impl->device_file_data;
|
||||||
|
}
|
||||||
|
|
||||||
|
SyncpointManager& Container::GetSyncpointManager() {
|
||||||
|
return impl->manager;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SyncpointManager& Container::GetSyncpointManager() const {
|
||||||
|
return impl->manager;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Service::Nvidia::NvCore
|
52
src/core/hle/service/nvdrv/core/container.h
Normal file
52
src/core/hle/service/nvdrv/core/container.h
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
// SPDX-FileCopyrightText: 2022 yuzu Emulator Project
|
||||||
|
// SPDX-FileCopyrightText: 2022 Skyline Team and Contributors
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <deque>
|
||||||
|
#include <memory>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
#include "core/hle/service/nvdrv/nvdata.h"
|
||||||
|
|
||||||
|
namespace Tegra::Host1x {
|
||||||
|
class Host1x;
|
||||||
|
} // namespace Tegra::Host1x
|
||||||
|
|
||||||
|
namespace Service::Nvidia::NvCore {
|
||||||
|
|
||||||
|
class NvMap;
|
||||||
|
class SyncpointManager;
|
||||||
|
|
||||||
|
struct ContainerImpl;
|
||||||
|
|
||||||
|
class Container {
|
||||||
|
public:
|
||||||
|
explicit Container(Tegra::Host1x::Host1x& host1x);
|
||||||
|
~Container();
|
||||||
|
|
||||||
|
NvMap& GetNvMapFile();
|
||||||
|
|
||||||
|
const NvMap& GetNvMapFile() const;
|
||||||
|
|
||||||
|
SyncpointManager& GetSyncpointManager();
|
||||||
|
|
||||||
|
const SyncpointManager& GetSyncpointManager() const;
|
||||||
|
|
||||||
|
struct Host1xDeviceFileData {
|
||||||
|
std::unordered_map<DeviceFD, u32> fd_to_id{};
|
||||||
|
std::deque<u32> syncpts_accumulated{};
|
||||||
|
u32 nvdec_next_id{};
|
||||||
|
u32 vic_next_id{};
|
||||||
|
};
|
||||||
|
|
||||||
|
Host1xDeviceFileData& Host1xDeviceFile();
|
||||||
|
|
||||||
|
const Host1xDeviceFileData& Host1xDeviceFile() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::unique_ptr<ContainerImpl> impl;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Service::Nvidia::NvCore
|
272
src/core/hle/service/nvdrv/core/nvmap.cpp
Normal file
272
src/core/hle/service/nvdrv/core/nvmap.cpp
Normal file
|
@ -0,0 +1,272 @@
|
||||||
|
// SPDX-FileCopyrightText: 2022 yuzu Emulator Project
|
||||||
|
// SPDX-FileCopyrightText: 2022 Skyline Team and Contributors
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
#include "common/alignment.h"
|
||||||
|
#include "common/assert.h"
|
||||||
|
#include "common/logging/log.h"
|
||||||
|
#include "core/hle/service/nvdrv/core/nvmap.h"
|
||||||
|
#include "core/memory.h"
|
||||||
|
#include "video_core/host1x/host1x.h"
|
||||||
|
|
||||||
|
using Core::Memory::YUZU_PAGESIZE;
|
||||||
|
|
||||||
|
namespace Service::Nvidia::NvCore {
|
||||||
|
NvMap::Handle::Handle(u64 size_, Id id_)
|
||||||
|
: size(size_), aligned_size(size), orig_size(size), id(id_) {
|
||||||
|
flags.raw = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
NvResult NvMap::Handle::Alloc(Flags pFlags, u32 pAlign, u8 pKind, u64 pAddress) {
|
||||||
|
std::scoped_lock lock(mutex);
|
||||||
|
|
||||||
|
// Handles cannot be allocated twice
|
||||||
|
if (allocated) {
|
||||||
|
return NvResult::AccessDenied;
|
||||||
|
}
|
||||||
|
|
||||||
|
flags = pFlags;
|
||||||
|
kind = pKind;
|
||||||
|
align = pAlign < YUZU_PAGESIZE ? YUZU_PAGESIZE : pAlign;
|
||||||
|
|
||||||
|
// This flag is only applicable for handles with an address passed
|
||||||
|
if (pAddress) {
|
||||||
|
flags.keep_uncached_after_free.Assign(0);
|
||||||
|
} else {
|
||||||
|
LOG_CRITICAL(Service_NVDRV,
|
||||||
|
"Mapping nvmap handles without a CPU side address is unimplemented!");
|
||||||
|
}
|
||||||
|
|
||||||
|
size = Common::AlignUp(size, YUZU_PAGESIZE);
|
||||||
|
aligned_size = Common::AlignUp(size, align);
|
||||||
|
address = pAddress;
|
||||||
|
allocated = true;
|
||||||
|
|
||||||
|
return NvResult::Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
NvResult NvMap::Handle::Duplicate(bool internal_session) {
|
||||||
|
std::scoped_lock lock(mutex);
|
||||||
|
// Unallocated handles cannot be duplicated as duplication requires memory accounting (in HOS)
|
||||||
|
if (!allocated) [[unlikely]] {
|
||||||
|
return NvResult::BadValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we internally use FromId the duplication tracking of handles won't work accurately due to
|
||||||
|
// us not implementing per-process handle refs.
|
||||||
|
if (internal_session) {
|
||||||
|
internal_dupes++;
|
||||||
|
} else {
|
||||||
|
dupes++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return NvResult::Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
NvMap::NvMap(Tegra::Host1x::Host1x& host1x_) : host1x{host1x_} {}
|
||||||
|
|
||||||
|
void NvMap::AddHandle(std::shared_ptr<Handle> handle_description) {
|
||||||
|
std::scoped_lock lock(handles_lock);
|
||||||
|
|
||||||
|
handles.emplace(handle_description->id, std::move(handle_description));
|
||||||
|
}
|
||||||
|
|
||||||
|
void NvMap::UnmapHandle(Handle& handle_description) {
|
||||||
|
// Remove pending unmap queue entry if needed
|
||||||
|
if (handle_description.unmap_queue_entry) {
|
||||||
|
unmap_queue.erase(*handle_description.unmap_queue_entry);
|
||||||
|
handle_description.unmap_queue_entry.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Free and unmap the handle from the SMMU
|
||||||
|
host1x.MemoryManager().Unmap(static_cast<GPUVAddr>(handle_description.pin_virt_address),
|
||||||
|
handle_description.aligned_size);
|
||||||
|
host1x.Allocator().Free(handle_description.pin_virt_address,
|
||||||
|
static_cast<u32>(handle_description.aligned_size));
|
||||||
|
handle_description.pin_virt_address = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool NvMap::TryRemoveHandle(const Handle& handle_description) {
|
||||||
|
// No dupes left, we can remove from handle map
|
||||||
|
if (handle_description.dupes == 0 && handle_description.internal_dupes == 0) {
|
||||||
|
std::scoped_lock lock(handles_lock);
|
||||||
|
|
||||||
|
auto it{handles.find(handle_description.id)};
|
||||||
|
if (it != handles.end()) {
|
||||||
|
handles.erase(it);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
NvResult NvMap::CreateHandle(u64 size, std::shared_ptr<NvMap::Handle>& result_out) {
|
||||||
|
if (!size) [[unlikely]] {
|
||||||
|
return NvResult::BadValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 id{next_handle_id.fetch_add(HandleIdIncrement, std::memory_order_relaxed)};
|
||||||
|
auto handle_description{std::make_shared<Handle>(size, id)};
|
||||||
|
AddHandle(handle_description);
|
||||||
|
|
||||||
|
result_out = handle_description;
|
||||||
|
return NvResult::Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<NvMap::Handle> NvMap::GetHandle(Handle::Id handle) {
|
||||||
|
std::scoped_lock lock(handles_lock);
|
||||||
|
try {
|
||||||
|
return handles.at(handle);
|
||||||
|
} catch (std::out_of_range&) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
VAddr NvMap::GetHandleAddress(Handle::Id handle) {
|
||||||
|
std::scoped_lock lock(handles_lock);
|
||||||
|
try {
|
||||||
|
return handles.at(handle)->address;
|
||||||
|
} catch (std::out_of_range&) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 NvMap::PinHandle(NvMap::Handle::Id handle) {
|
||||||
|
auto handle_description{GetHandle(handle)};
|
||||||
|
if (!handle_description) [[unlikely]] {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::scoped_lock lock(handle_description->mutex);
|
||||||
|
if (!handle_description->pins) {
|
||||||
|
// If we're in the unmap queue we can just remove ourselves and return since we're already
|
||||||
|
// mapped
|
||||||
|
{
|
||||||
|
// Lock now to prevent our queue entry from being removed for allocation in-between the
|
||||||
|
// following check and erase
|
||||||
|
std::scoped_lock queueLock(unmap_queue_lock);
|
||||||
|
if (handle_description->unmap_queue_entry) {
|
||||||
|
unmap_queue.erase(*handle_description->unmap_queue_entry);
|
||||||
|
handle_description->unmap_queue_entry.reset();
|
||||||
|
|
||||||
|
handle_description->pins++;
|
||||||
|
return handle_description->pin_virt_address;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If not then allocate some space and map it
|
||||||
|
u32 address{};
|
||||||
|
auto& smmu_allocator = host1x.Allocator();
|
||||||
|
auto& smmu_memory_manager = host1x.MemoryManager();
|
||||||
|
while (!(address =
|
||||||
|
smmu_allocator.Allocate(static_cast<u32>(handle_description->aligned_size)))) {
|
||||||
|
// Free handles until the allocation succeeds
|
||||||
|
std::scoped_lock queueLock(unmap_queue_lock);
|
||||||
|
if (auto freeHandleDesc{unmap_queue.front()}) {
|
||||||
|
// Handles in the unmap queue are guaranteed not to be pinned so don't bother
|
||||||
|
// checking if they are before unmapping
|
||||||
|
std::scoped_lock freeLock(freeHandleDesc->mutex);
|
||||||
|
if (handle_description->pin_virt_address)
|
||||||
|
UnmapHandle(*freeHandleDesc);
|
||||||
|
} else {
|
||||||
|
LOG_CRITICAL(Service_NVDRV, "Ran out of SMMU address space!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
smmu_memory_manager.Map(static_cast<GPUVAddr>(address), handle_description->address,
|
||||||
|
handle_description->aligned_size);
|
||||||
|
handle_description->pin_virt_address = address;
|
||||||
|
}
|
||||||
|
|
||||||
|
handle_description->pins++;
|
||||||
|
return handle_description->pin_virt_address;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NvMap::UnpinHandle(Handle::Id handle) {
|
||||||
|
auto handle_description{GetHandle(handle)};
|
||||||
|
if (!handle_description) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::scoped_lock lock(handle_description->mutex);
|
||||||
|
if (--handle_description->pins < 0) {
|
||||||
|
LOG_WARNING(Service_NVDRV, "Pin count imbalance detected!");
|
||||||
|
} else if (!handle_description->pins) {
|
||||||
|
std::scoped_lock queueLock(unmap_queue_lock);
|
||||||
|
|
||||||
|
// Add to the unmap queue allowing this handle's memory to be freed if needed
|
||||||
|
unmap_queue.push_back(handle_description);
|
||||||
|
handle_description->unmap_queue_entry = std::prev(unmap_queue.end());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void NvMap::DuplicateHandle(Handle::Id handle, bool internal_session) {
|
||||||
|
auto handle_description{GetHandle(handle)};
|
||||||
|
if (!handle_description) {
|
||||||
|
LOG_CRITICAL(Service_NVDRV, "Unregistered handle!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto result = handle_description->Duplicate(internal_session);
|
||||||
|
if (result != NvResult::Success) {
|
||||||
|
LOG_CRITICAL(Service_NVDRV, "Could not duplicate handle!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<NvMap::FreeInfo> NvMap::FreeHandle(Handle::Id handle, bool internal_session) {
|
||||||
|
std::weak_ptr<Handle> hWeak{GetHandle(handle)};
|
||||||
|
FreeInfo freeInfo;
|
||||||
|
|
||||||
|
// We use a weak ptr here so we can tell when the handle has been freed and report that back to
|
||||||
|
// guest
|
||||||
|
if (auto handle_description = hWeak.lock()) {
|
||||||
|
std::scoped_lock lock(handle_description->mutex);
|
||||||
|
|
||||||
|
if (internal_session) {
|
||||||
|
if (--handle_description->internal_dupes < 0)
|
||||||
|
LOG_WARNING(Service_NVDRV, "Internal duplicate count imbalance detected!");
|
||||||
|
} else {
|
||||||
|
if (--handle_description->dupes < 0) {
|
||||||
|
LOG_WARNING(Service_NVDRV, "User duplicate count imbalance detected!");
|
||||||
|
} else if (handle_description->dupes == 0) {
|
||||||
|
// Force unmap the handle
|
||||||
|
if (handle_description->pin_virt_address) {
|
||||||
|
std::scoped_lock queueLock(unmap_queue_lock);
|
||||||
|
UnmapHandle(*handle_description);
|
||||||
|
}
|
||||||
|
|
||||||
|
handle_description->pins = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to remove the shared ptr to the handle from the map, if nothing else is using the
|
||||||
|
// handle then it will now be freed when `handle_description` goes out of scope
|
||||||
|
if (TryRemoveHandle(*handle_description)) {
|
||||||
|
LOG_DEBUG(Service_NVDRV, "Removed nvmap handle: {}", handle);
|
||||||
|
} else {
|
||||||
|
LOG_DEBUG(Service_NVDRV,
|
||||||
|
"Tried to free nvmap handle: {} but didn't as it still has duplicates",
|
||||||
|
handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
freeInfo = {
|
||||||
|
.address = handle_description->address,
|
||||||
|
.size = handle_description->size,
|
||||||
|
.was_uncached = handle_description->flags.map_uncached.Value() != 0,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle hasn't been freed from memory, set address to 0 to mark that the handle wasn't freed
|
||||||
|
if (!hWeak.expired()) {
|
||||||
|
LOG_DEBUG(Service_NVDRV, "nvmap handle: {} wasn't freed as it is still in use", handle);
|
||||||
|
freeInfo.address = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return freeInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Service::Nvidia::NvCore
|
175
src/core/hle/service/nvdrv/core/nvmap.h
Normal file
175
src/core/hle/service/nvdrv/core/nvmap.h
Normal file
|
@ -0,0 +1,175 @@
|
||||||
|
// SPDX-FileCopyrightText: 2022 yuzu Emulator Project
|
||||||
|
// SPDX-FileCopyrightText: 2022 Skyline Team and Contributors
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
#include <list>
|
||||||
|
#include <memory>
|
||||||
|
#include <mutex>
|
||||||
|
#include <optional>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
|
#include "common/bit_field.h"
|
||||||
|
#include "common/common_types.h"
|
||||||
|
#include "core/hle/service/nvdrv/nvdata.h"
|
||||||
|
|
||||||
|
namespace Tegra {
|
||||||
|
|
||||||
|
namespace Host1x {
|
||||||
|
class Host1x;
|
||||||
|
} // namespace Host1x
|
||||||
|
|
||||||
|
} // namespace Tegra
|
||||||
|
|
||||||
|
namespace Service::Nvidia::NvCore {
|
||||||
|
/**
|
||||||
|
* @brief The nvmap core class holds the global state for nvmap and provides methods to manage
|
||||||
|
* handles
|
||||||
|
*/
|
||||||
|
class NvMap {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief A handle to a contiguous block of memory in an application's address space
|
||||||
|
*/
|
||||||
|
struct Handle {
|
||||||
|
std::mutex mutex;
|
||||||
|
|
||||||
|
u64 align{}; //!< The alignment to use when pinning the handle onto the SMMU
|
||||||
|
u64 size; //!< Page-aligned size of the memory the handle refers to
|
||||||
|
u64 aligned_size; //!< `align`-aligned size of the memory the handle refers to
|
||||||
|
u64 orig_size; //!< Original unaligned size of the memory this handle refers to
|
||||||
|
|
||||||
|
s32 dupes{1}; //!< How many guest references there are to this handle
|
||||||
|
s32 internal_dupes{0}; //!< How many emulator-internal references there are to this handle
|
||||||
|
|
||||||
|
using Id = u32;
|
||||||
|
Id id; //!< A globally unique identifier for this handle
|
||||||
|
|
||||||
|
s32 pins{};
|
||||||
|
u32 pin_virt_address{};
|
||||||
|
std::optional<typename std::list<std::shared_ptr<Handle>>::iterator> unmap_queue_entry{};
|
||||||
|
|
||||||
|
union Flags {
|
||||||
|
u32 raw;
|
||||||
|
BitField<0, 1, u32> map_uncached; //!< If the handle should be mapped as uncached
|
||||||
|
BitField<2, 1, u32> keep_uncached_after_free; //!< Only applicable when the handle was
|
||||||
|
//!< allocated with a fixed address
|
||||||
|
BitField<4, 1, u32> _unk0_; //!< Passed to IOVMM for pins
|
||||||
|
} flags{};
|
||||||
|
static_assert(sizeof(Flags) == sizeof(u32));
|
||||||
|
|
||||||
|
u64 address{}; //!< The memory location in the guest's AS that this handle corresponds to,
|
||||||
|
//!< this can also be in the nvdrv tmem
|
||||||
|
bool is_shared_mem_mapped{}; //!< If this nvmap has been mapped with the MapSharedMem IPC
|
||||||
|
//!< call
|
||||||
|
|
||||||
|
u8 kind{}; //!< Used for memory compression
|
||||||
|
bool allocated{}; //!< If the handle has been allocated with `Alloc`
|
||||||
|
|
||||||
|
u64 dma_map_addr{}; //! remove me after implementing pinning.
|
||||||
|
|
||||||
|
Handle(u64 size, Id id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Sets up the handle with the given memory config, can allocate memory from the tmem
|
||||||
|
* if a 0 address is passed
|
||||||
|
*/
|
||||||
|
[[nodiscard]] NvResult Alloc(Flags pFlags, u32 pAlign, u8 pKind, u64 pAddress);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Increases the dupe counter of the handle for the given session
|
||||||
|
*/
|
||||||
|
[[nodiscard]] NvResult Duplicate(bool internal_session);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Obtains a pointer to the handle's memory and marks the handle it as having been
|
||||||
|
* mapped
|
||||||
|
*/
|
||||||
|
u8* GetPointer() {
|
||||||
|
if (!address) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
is_shared_mem_mapped = true;
|
||||||
|
return reinterpret_cast<u8*>(address);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Encapsulates the result of a FreeHandle operation
|
||||||
|
*/
|
||||||
|
struct FreeInfo {
|
||||||
|
u64 address; //!< Address the handle referred to before deletion
|
||||||
|
u64 size; //!< Page-aligned handle size
|
||||||
|
bool was_uncached; //!< If the handle was allocated as uncached
|
||||||
|
};
|
||||||
|
|
||||||
|
explicit NvMap(Tegra::Host1x::Host1x& host1x);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Creates an unallocated handle of the given size
|
||||||
|
*/
|
||||||
|
[[nodiscard]] NvResult CreateHandle(u64 size, std::shared_ptr<NvMap::Handle>& result_out);
|
||||||
|
|
||||||
|
std::shared_ptr<Handle> GetHandle(Handle::Id handle);
|
||||||
|
|
||||||
|
VAddr GetHandleAddress(Handle::Id handle);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Maps a handle into the SMMU address space
|
||||||
|
* @note This operation is refcounted, the number of calls to this must eventually match the
|
||||||
|
* number of calls to `UnpinHandle`
|
||||||
|
* @return The SMMU virtual address that the handle has been mapped to
|
||||||
|
*/
|
||||||
|
u32 PinHandle(Handle::Id handle);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief When this has been called an equal number of times to `PinHandle` for the supplied
|
||||||
|
* handle it will be added to a list of handles to be freed when necessary
|
||||||
|
*/
|
||||||
|
void UnpinHandle(Handle::Id handle);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Tries to duplicate a handle
|
||||||
|
*/
|
||||||
|
void DuplicateHandle(Handle::Id handle, bool internal_session = false);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Tries to free a handle and remove a single dupe
|
||||||
|
* @note If a handle has no dupes left and has no other users a FreeInfo struct will be returned
|
||||||
|
* describing the prior state of the handle
|
||||||
|
*/
|
||||||
|
std::optional<FreeInfo> FreeHandle(Handle::Id handle, bool internal_session);
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::list<std::shared_ptr<Handle>> unmap_queue{};
|
||||||
|
std::mutex unmap_queue_lock{}; //!< Protects access to `unmap_queue`
|
||||||
|
|
||||||
|
std::unordered_map<Handle::Id, std::shared_ptr<Handle>>
|
||||||
|
handles{}; //!< Main owning map of handles
|
||||||
|
std::mutex handles_lock; //!< Protects access to `handles`
|
||||||
|
|
||||||
|
static constexpr u32 HandleIdIncrement{
|
||||||
|
4}; //!< Each new handle ID is an increment of 4 from the previous
|
||||||
|
std::atomic<u32> next_handle_id{HandleIdIncrement};
|
||||||
|
Tegra::Host1x::Host1x& host1x;
|
||||||
|
|
||||||
|
void AddHandle(std::shared_ptr<Handle> handle);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Unmaps and frees the SMMU memory region a handle is mapped to
|
||||||
|
* @note Both `unmap_queue_lock` and `handle_description.mutex` MUST be locked when calling this
|
||||||
|
*/
|
||||||
|
void UnmapHandle(Handle& handle_description);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Removes a handle from the map taking its dupes into account
|
||||||
|
* @note handle_description.mutex MUST be locked when calling this
|
||||||
|
* @return If the handle was removed from the map
|
||||||
|
*/
|
||||||
|
bool TryRemoveHandle(const Handle& handle_description);
|
||||||
|
};
|
||||||
|
} // namespace Service::Nvidia::NvCore
|
121
src/core/hle/service/nvdrv/core/syncpoint_manager.cpp
Normal file
121
src/core/hle/service/nvdrv/core/syncpoint_manager.cpp
Normal file
|
@ -0,0 +1,121 @@
|
||||||
|
// SPDX-FileCopyrightText: 2022 yuzu Emulator Project
|
||||||
|
// SPDX-FileCopyrightText: 2022 Skyline Team and Contributors
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
#include "common/assert.h"
|
||||||
|
#include "core/hle/service/nvdrv/core/syncpoint_manager.h"
|
||||||
|
#include "video_core/host1x/host1x.h"
|
||||||
|
|
||||||
|
namespace Service::Nvidia::NvCore {
|
||||||
|
|
||||||
|
SyncpointManager::SyncpointManager(Tegra::Host1x::Host1x& host1x_) : host1x{host1x_} {
|
||||||
|
constexpr u32 VBlank0SyncpointId{26};
|
||||||
|
constexpr u32 VBlank1SyncpointId{27};
|
||||||
|
|
||||||
|
// Reserve both vblank syncpoints as client managed as they use Continuous Mode
|
||||||
|
// Refer to section 14.3.5.3 of the TRM for more information on Continuous Mode
|
||||||
|
// https://github.com/Jetson-TX1-AndroidTV/android_kernel_jetson_tx1_hdmi_primary/blob/8f74a72394efb871cb3f886a3de2998cd7ff2990/drivers/gpu/host1x/drm/dc.c#L660
|
||||||
|
ReserveSyncpoint(VBlank0SyncpointId, true);
|
||||||
|
ReserveSyncpoint(VBlank1SyncpointId, true);
|
||||||
|
|
||||||
|
for (u32 syncpoint_id : channel_syncpoints) {
|
||||||
|
if (syncpoint_id) {
|
||||||
|
ReserveSyncpoint(syncpoint_id, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SyncpointManager::~SyncpointManager() = default;
|
||||||
|
|
||||||
|
u32 SyncpointManager::ReserveSyncpoint(u32 id, bool client_managed) {
|
||||||
|
if (syncpoints.at(id).reserved) {
|
||||||
|
ASSERT_MSG(false, "Requested syncpoint is in use");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
syncpoints.at(id).reserved = true;
|
||||||
|
syncpoints.at(id).interface_managed = client_managed;
|
||||||
|
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 SyncpointManager::FindFreeSyncpoint() {
|
||||||
|
for (u32 i{1}; i < syncpoints.size(); i++) {
|
||||||
|
if (!syncpoints[i].reserved) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ASSERT_MSG(false, "Failed to find a free syncpoint!");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 SyncpointManager::AllocateSyncpoint(bool client_managed) {
|
||||||
|
std::lock_guard lock(reservation_lock);
|
||||||
|
return ReserveSyncpoint(FindFreeSyncpoint(), client_managed);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SyncpointManager::FreeSyncpoint(u32 id) {
|
||||||
|
std::lock_guard lock(reservation_lock);
|
||||||
|
ASSERT(syncpoints.at(id).reserved);
|
||||||
|
syncpoints.at(id).reserved = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SyncpointManager::IsSyncpointAllocated(u32 id) {
|
||||||
|
return (id <= SyncpointCount) && syncpoints[id].reserved;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SyncpointManager::HasSyncpointExpired(u32 id, u32 threshold) const {
|
||||||
|
const SyncpointInfo& syncpoint{syncpoints.at(id)};
|
||||||
|
|
||||||
|
if (!syncpoint.reserved) {
|
||||||
|
ASSERT(false);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the interface manages counters then we don't keep track of the maximum value as it handles
|
||||||
|
// sanity checking the values then
|
||||||
|
if (syncpoint.interface_managed) {
|
||||||
|
return static_cast<s32>(syncpoint.counter_min - threshold) >= 0;
|
||||||
|
} else {
|
||||||
|
return (syncpoint.counter_max - threshold) >= (syncpoint.counter_min - threshold);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 SyncpointManager::IncrementSyncpointMaxExt(u32 id, u32 amount) {
|
||||||
|
if (!syncpoints.at(id).reserved) {
|
||||||
|
ASSERT(false);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return syncpoints.at(id).counter_max += amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 SyncpointManager::ReadSyncpointMinValue(u32 id) {
|
||||||
|
if (!syncpoints.at(id).reserved) {
|
||||||
|
ASSERT(false);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return syncpoints.at(id).counter_min;
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 SyncpointManager::UpdateMin(u32 id) {
|
||||||
|
if (!syncpoints.at(id).reserved) {
|
||||||
|
ASSERT(false);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
syncpoints.at(id).counter_min = host1x.GetSyncpointManager().GetHostSyncpointValue(id);
|
||||||
|
return syncpoints.at(id).counter_min;
|
||||||
|
}
|
||||||
|
|
||||||
|
NvFence SyncpointManager::GetSyncpointFence(u32 id) {
|
||||||
|
if (!syncpoints.at(id).reserved) {
|
||||||
|
ASSERT(false);
|
||||||
|
return NvFence{};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {.id = static_cast<s32>(id), .value = syncpoints.at(id).counter_max};
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Service::Nvidia::NvCore
|
134
src/core/hle/service/nvdrv/core/syncpoint_manager.h
Normal file
134
src/core/hle/service/nvdrv/core/syncpoint_manager.h
Normal file
|
@ -0,0 +1,134 @@
|
||||||
|
// SPDX-FileCopyrightText: 2022 yuzu Emulator Project
|
||||||
|
// SPDX-FileCopyrightText: 2022 Skyline Team and Contributors
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <atomic>
|
||||||
|
#include <mutex>
|
||||||
|
|
||||||
|
#include "common/common_types.h"
|
||||||
|
#include "core/hle/service/nvdrv/nvdata.h"
|
||||||
|
|
||||||
|
namespace Tegra::Host1x {
|
||||||
|
class Host1x;
|
||||||
|
} // namespace Tegra::Host1x
|
||||||
|
|
||||||
|
namespace Service::Nvidia::NvCore {
|
||||||
|
|
||||||
|
enum class ChannelType : u32 {
|
||||||
|
MsEnc = 0,
|
||||||
|
VIC = 1,
|
||||||
|
GPU = 2,
|
||||||
|
NvDec = 3,
|
||||||
|
Display = 4,
|
||||||
|
NvJpg = 5,
|
||||||
|
TSec = 6,
|
||||||
|
Max = 7
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief SyncpointManager handles allocating and accessing host1x syncpoints, these are cached
|
||||||
|
* versions of the HW syncpoints which are intermittently synced
|
||||||
|
* @note Refer to Chapter 14 of the Tegra X1 TRM for an exhaustive overview of them
|
||||||
|
* @url https://http.download.nvidia.com/tegra-public-appnotes/host1x.html
|
||||||
|
* @url
|
||||||
|
* https://github.com/Jetson-TX1-AndroidTV/android_kernel_jetson_tx1_hdmi_primary/blob/jetson-tx1/drivers/video/tegra/host/nvhost_syncpt.c
|
||||||
|
*/
|
||||||
|
class SyncpointManager final {
|
||||||
|
public:
|
||||||
|
explicit SyncpointManager(Tegra::Host1x::Host1x& host1x);
|
||||||
|
~SyncpointManager();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Checks if the given syncpoint is both allocated and below the number of HW syncpoints
|
||||||
|
*/
|
||||||
|
bool IsSyncpointAllocated(u32 id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Finds a free syncpoint and reserves it
|
||||||
|
* @return The ID of the reserved syncpoint
|
||||||
|
*/
|
||||||
|
u32 AllocateSyncpoint(bool client_managed);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @url
|
||||||
|
* https://github.com/Jetson-TX1-AndroidTV/android_kernel_jetson_tx1_hdmi_primary/blob/8f74a72394efb871cb3f886a3de2998cd7ff2990/drivers/gpu/host1x/syncpt.c#L259
|
||||||
|
*/
|
||||||
|
bool HasSyncpointExpired(u32 id, u32 threshold) const;
|
||||||
|
|
||||||
|
bool IsFenceSignalled(NvFence fence) const {
|
||||||
|
return HasSyncpointExpired(fence.id, fence.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Atomically increments the maximum value of a syncpoint by the given amount
|
||||||
|
* @return The new max value of the syncpoint
|
||||||
|
*/
|
||||||
|
u32 IncrementSyncpointMaxExt(u32 id, u32 amount);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The minimum value of the syncpoint
|
||||||
|
*/
|
||||||
|
u32 ReadSyncpointMinValue(u32 id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Synchronises the minimum value of the syncpoint to with the GPU
|
||||||
|
* @return The new minimum value of the syncpoint
|
||||||
|
*/
|
||||||
|
u32 UpdateMin(u32 id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Frees the usage of a syncpoint.
|
||||||
|
*/
|
||||||
|
void FreeSyncpoint(u32 id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return A fence that will be signalled once this syncpoint hits its maximum value
|
||||||
|
*/
|
||||||
|
NvFence GetSyncpointFence(u32 id);
|
||||||
|
|
||||||
|
static constexpr std::array<u32, static_cast<u32>(ChannelType::Max)> channel_syncpoints{
|
||||||
|
0x0, // `MsEnc` is unimplemented
|
||||||
|
0xC, // `VIC`
|
||||||
|
0x0, // `GPU` syncpoints are allocated per-channel instead
|
||||||
|
0x36, // `NvDec`
|
||||||
|
0x0, // `Display` is unimplemented
|
||||||
|
0x37, // `NvJpg`
|
||||||
|
0x0, // `TSec` is unimplemented
|
||||||
|
}; //!< Maps each channel ID to a constant syncpoint
|
||||||
|
|
||||||
|
private:
|
||||||
|
/**
|
||||||
|
* @note reservation_lock should be locked when calling this
|
||||||
|
*/
|
||||||
|
u32 ReserveSyncpoint(u32 id, bool client_managed);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return The ID of the first free syncpoint
|
||||||
|
*/
|
||||||
|
u32 FindFreeSyncpoint();
|
||||||
|
|
||||||
|
struct SyncpointInfo {
|
||||||
|
std::atomic<u32> counter_min; //!< The least value the syncpoint can be (The value it was
|
||||||
|
//!< when it was last synchronized with host1x)
|
||||||
|
std::atomic<u32> counter_max; //!< The maximum value the syncpoint can reach according to
|
||||||
|
//!< the current usage
|
||||||
|
bool interface_managed; //!< If the syncpoint is managed by a host1x client interface, a
|
||||||
|
//!< client interface is a HW block that can handle host1x
|
||||||
|
//!< transactions on behalf of a host1x client (Which would
|
||||||
|
//!< otherwise need to be manually synced using PIO which is
|
||||||
|
//!< synchronous and requires direct cooperation of the CPU)
|
||||||
|
bool reserved; //!< If the syncpoint is reserved or not, not to be confused with a reserved
|
||||||
|
//!< value
|
||||||
|
};
|
||||||
|
|
||||||
|
constexpr static std::size_t SyncpointCount{192};
|
||||||
|
std::array<SyncpointInfo, SyncpointCount> syncpoints{};
|
||||||
|
std::mutex reservation_lock;
|
||||||
|
|
||||||
|
Tegra::Host1x::Host1x& host1x;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Service::Nvidia::NvCore
|
|
@ -11,6 +11,10 @@ namespace Core {
|
||||||
class System;
|
class System;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace Kernel {
|
||||||
|
class KEvent;
|
||||||
|
}
|
||||||
|
|
||||||
namespace Service::Nvidia::Devices {
|
namespace Service::Nvidia::Devices {
|
||||||
|
|
||||||
/// Represents an abstract nvidia device node. It is to be subclassed by concrete device nodes to
|
/// Represents an abstract nvidia device node. It is to be subclassed by concrete device nodes to
|
||||||
|
@ -64,6 +68,10 @@ public:
|
||||||
*/
|
*/
|
||||||
virtual void OnClose(DeviceFD fd) = 0;
|
virtual void OnClose(DeviceFD fd) = 0;
|
||||||
|
|
||||||
|
virtual Kernel::KEvent* QueryEvent(u32 event_id) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
Core::System& system;
|
Core::System& system;
|
||||||
};
|
};
|
||||||
|
|
|
@ -5,15 +5,16 @@
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
#include "core/core.h"
|
#include "core/core.h"
|
||||||
#include "core/core_timing.h"
|
#include "core/core_timing.h"
|
||||||
|
#include "core/hle/service/nvdrv/core/container.h"
|
||||||
|
#include "core/hle/service/nvdrv/core/nvmap.h"
|
||||||
#include "core/hle/service/nvdrv/devices/nvdisp_disp0.h"
|
#include "core/hle/service/nvdrv/devices/nvdisp_disp0.h"
|
||||||
#include "core/hle/service/nvdrv/devices/nvmap.h"
|
|
||||||
#include "core/perf_stats.h"
|
#include "core/perf_stats.h"
|
||||||
#include "video_core/gpu.h"
|
#include "video_core/gpu.h"
|
||||||
|
|
||||||
namespace Service::Nvidia::Devices {
|
namespace Service::Nvidia::Devices {
|
||||||
|
|
||||||
nvdisp_disp0::nvdisp_disp0(Core::System& system_, std::shared_ptr<nvmap> nvmap_dev_)
|
nvdisp_disp0::nvdisp_disp0(Core::System& system_, NvCore::Container& core)
|
||||||
: nvdevice{system_}, nvmap_dev{std::move(nvmap_dev_)} {}
|
: nvdevice{system_}, container{core}, nvmap{core.GetNvMapFile()} {}
|
||||||
nvdisp_disp0::~nvdisp_disp0() = default;
|
nvdisp_disp0::~nvdisp_disp0() = default;
|
||||||
|
|
||||||
NvResult nvdisp_disp0::Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8>& input,
|
NvResult nvdisp_disp0::Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8>& input,
|
||||||
|
@ -39,8 +40,9 @@ void nvdisp_disp0::OnClose(DeviceFD fd) {}
|
||||||
|
|
||||||
void nvdisp_disp0::flip(u32 buffer_handle, u32 offset, android::PixelFormat format, u32 width,
|
void nvdisp_disp0::flip(u32 buffer_handle, u32 offset, android::PixelFormat format, u32 width,
|
||||||
u32 height, u32 stride, android::BufferTransformFlags transform,
|
u32 height, u32 stride, android::BufferTransformFlags transform,
|
||||||
const Common::Rectangle<int>& crop_rect) {
|
const Common::Rectangle<int>& crop_rect,
|
||||||
const VAddr addr = nvmap_dev->GetObjectAddress(buffer_handle);
|
std::array<Service::Nvidia::NvFence, 4>& fences, u32 num_fences) {
|
||||||
|
const VAddr addr = nvmap.GetHandleAddress(buffer_handle);
|
||||||
LOG_TRACE(Service,
|
LOG_TRACE(Service,
|
||||||
"Drawing from address {:X} offset {:08X} Width {} Height {} Stride {} Format {}",
|
"Drawing from address {:X} offset {:08X} Width {} Height {} Stride {} Format {}",
|
||||||
addr, offset, width, height, stride, format);
|
addr, offset, width, height, stride, format);
|
||||||
|
@ -48,10 +50,15 @@ void nvdisp_disp0::flip(u32 buffer_handle, u32 offset, android::PixelFormat form
|
||||||
const Tegra::FramebufferConfig framebuffer{addr, offset, width, height,
|
const Tegra::FramebufferConfig framebuffer{addr, offset, width, height,
|
||||||
stride, format, transform, crop_rect};
|
stride, format, transform, crop_rect};
|
||||||
|
|
||||||
|
system.GPU().RequestSwapBuffers(&framebuffer, fences, num_fences);
|
||||||
system.GetPerfStats().EndSystemFrame();
|
system.GetPerfStats().EndSystemFrame();
|
||||||
system.GPU().SwapBuffers(&framebuffer);
|
|
||||||
system.SpeedLimiter().DoSpeedLimiting(system.CoreTiming().GetGlobalTimeUs());
|
system.SpeedLimiter().DoSpeedLimiting(system.CoreTiming().GetGlobalTimeUs());
|
||||||
system.GetPerfStats().BeginSystemFrame();
|
system.GetPerfStats().BeginSystemFrame();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Kernel::KEvent* nvdisp_disp0::QueryEvent(u32 event_id) {
|
||||||
|
LOG_CRITICAL(Service_NVDRV, "Unknown DISP Event {}", event_id);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace Service::Nvidia::Devices
|
} // namespace Service::Nvidia::Devices
|
||||||
|
|
|
@ -11,13 +11,18 @@
|
||||||
#include "core/hle/service/nvflinger/buffer_transform_flags.h"
|
#include "core/hle/service/nvflinger/buffer_transform_flags.h"
|
||||||
#include "core/hle/service/nvflinger/pixel_format.h"
|
#include "core/hle/service/nvflinger/pixel_format.h"
|
||||||
|
|
||||||
|
namespace Service::Nvidia::NvCore {
|
||||||
|
class Container;
|
||||||
|
class NvMap;
|
||||||
|
} // namespace Service::Nvidia::NvCore
|
||||||
|
|
||||||
namespace Service::Nvidia::Devices {
|
namespace Service::Nvidia::Devices {
|
||||||
|
|
||||||
class nvmap;
|
class nvmap;
|
||||||
|
|
||||||
class nvdisp_disp0 final : public nvdevice {
|
class nvdisp_disp0 final : public nvdevice {
|
||||||
public:
|
public:
|
||||||
explicit nvdisp_disp0(Core::System& system_, std::shared_ptr<nvmap> nvmap_dev_);
|
explicit nvdisp_disp0(Core::System& system_, NvCore::Container& core);
|
||||||
~nvdisp_disp0() override;
|
~nvdisp_disp0() override;
|
||||||
|
|
||||||
NvResult Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8>& input,
|
NvResult Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8>& input,
|
||||||
|
@ -33,10 +38,14 @@ public:
|
||||||
/// Performs a screen flip, drawing the buffer pointed to by the handle.
|
/// Performs a screen flip, drawing the buffer pointed to by the handle.
|
||||||
void flip(u32 buffer_handle, u32 offset, android::PixelFormat format, u32 width, u32 height,
|
void flip(u32 buffer_handle, u32 offset, android::PixelFormat format, u32 width, u32 height,
|
||||||
u32 stride, android::BufferTransformFlags transform,
|
u32 stride, android::BufferTransformFlags transform,
|
||||||
const Common::Rectangle<int>& crop_rect);
|
const Common::Rectangle<int>& crop_rect,
|
||||||
|
std::array<Service::Nvidia::NvFence, 4>& fences, u32 num_fences);
|
||||||
|
|
||||||
|
Kernel::KEvent* QueryEvent(u32 event_id) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::shared_ptr<nvmap> nvmap_dev;
|
NvCore::Container& container;
|
||||||
|
NvCore::NvMap& nvmap;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Service::Nvidia::Devices
|
} // namespace Service::Nvidia::Devices
|
||||||
|
|
|
@ -1,21 +1,30 @@
|
||||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
// SPDX-FileCopyrightText: 2021 yuzu Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-FileCopyrightText: 2021 Skyline Team and Contributors
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
|
#include "common/alignment.h"
|
||||||
#include "common/assert.h"
|
#include "common/assert.h"
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
#include "core/core.h"
|
#include "core/core.h"
|
||||||
|
#include "core/hle/service/nvdrv/core/container.h"
|
||||||
|
#include "core/hle/service/nvdrv/core/nvmap.h"
|
||||||
#include "core/hle/service/nvdrv/devices/nvhost_as_gpu.h"
|
#include "core/hle/service/nvdrv/devices/nvhost_as_gpu.h"
|
||||||
#include "core/hle/service/nvdrv/devices/nvmap.h"
|
#include "core/hle/service/nvdrv/devices/nvhost_gpu.h"
|
||||||
|
#include "core/hle/service/nvdrv/nvdrv.h"
|
||||||
|
#include "video_core/control/channel_state.h"
|
||||||
|
#include "video_core/gpu.h"
|
||||||
#include "video_core/memory_manager.h"
|
#include "video_core/memory_manager.h"
|
||||||
#include "video_core/rasterizer_interface.h"
|
#include "video_core/rasterizer_interface.h"
|
||||||
|
|
||||||
namespace Service::Nvidia::Devices {
|
namespace Service::Nvidia::Devices {
|
||||||
|
|
||||||
nvhost_as_gpu::nvhost_as_gpu(Core::System& system_, std::shared_ptr<nvmap> nvmap_dev_)
|
nvhost_as_gpu::nvhost_as_gpu(Core::System& system_, Module& module_, NvCore::Container& core)
|
||||||
: nvdevice{system_}, nvmap_dev{std::move(nvmap_dev_)} {}
|
: nvdevice{system_}, module{module_}, container{core}, nvmap{core.GetNvMapFile()}, vm{},
|
||||||
|
gmmu{} {}
|
||||||
|
|
||||||
nvhost_as_gpu::~nvhost_as_gpu() = default;
|
nvhost_as_gpu::~nvhost_as_gpu() = default;
|
||||||
|
|
||||||
NvResult nvhost_as_gpu::Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8>& input,
|
NvResult nvhost_as_gpu::Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8>& input,
|
||||||
|
@ -82,12 +91,52 @@ NvResult nvhost_as_gpu::AllocAsEx(const std::vector<u8>& input, std::vector<u8>&
|
||||||
IoctlAllocAsEx params{};
|
IoctlAllocAsEx params{};
|
||||||
std::memcpy(¶ms, input.data(), input.size());
|
std::memcpy(¶ms, input.data(), input.size());
|
||||||
|
|
||||||
LOG_WARNING(Service_NVDRV, "(STUBBED) called, big_page_size=0x{:X}", params.big_page_size);
|
LOG_DEBUG(Service_NVDRV, "called, big_page_size=0x{:X}", params.big_page_size);
|
||||||
if (params.big_page_size == 0) {
|
|
||||||
params.big_page_size = DEFAULT_BIG_PAGE_SIZE;
|
std::scoped_lock lock(mutex);
|
||||||
|
|
||||||
|
if (vm.initialised) {
|
||||||
|
ASSERT_MSG(false, "Cannot initialise an address space twice!");
|
||||||
|
return NvResult::InvalidState;
|
||||||
}
|
}
|
||||||
|
|
||||||
big_page_size = params.big_page_size;
|
if (params.big_page_size) {
|
||||||
|
if (!std::has_single_bit(params.big_page_size)) {
|
||||||
|
LOG_ERROR(Service_NVDRV, "Non power-of-2 big page size: 0x{:X}!", params.big_page_size);
|
||||||
|
return NvResult::BadValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((params.big_page_size & VM::SUPPORTED_BIG_PAGE_SIZES) == 0) {
|
||||||
|
LOG_ERROR(Service_NVDRV, "Unsupported big page size: 0x{:X}!", params.big_page_size);
|
||||||
|
return NvResult::BadValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
vm.big_page_size = params.big_page_size;
|
||||||
|
vm.big_page_size_bits = static_cast<u32>(std::countr_zero(params.big_page_size));
|
||||||
|
|
||||||
|
vm.va_range_start = params.big_page_size << VM::VA_START_SHIFT;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If this is unspecified then default values should be used
|
||||||
|
if (params.va_range_start) {
|
||||||
|
vm.va_range_start = params.va_range_start;
|
||||||
|
vm.va_range_split = params.va_range_split;
|
||||||
|
vm.va_range_end = params.va_range_end;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto start_pages{static_cast<u32>(vm.va_range_start >> VM::PAGE_SIZE_BITS)};
|
||||||
|
const auto end_pages{static_cast<u32>(vm.va_range_split >> VM::PAGE_SIZE_BITS)};
|
||||||
|
vm.small_page_allocator = std::make_shared<VM::Allocator>(start_pages, end_pages);
|
||||||
|
|
||||||
|
const auto start_big_pages{static_cast<u32>(vm.va_range_split >> vm.big_page_size_bits)};
|
||||||
|
const auto end_big_pages{
|
||||||
|
static_cast<u32>((vm.va_range_end - vm.va_range_split) >> vm.big_page_size_bits)};
|
||||||
|
vm.big_page_allocator = std::make_unique<VM::Allocator>(start_big_pages, end_big_pages);
|
||||||
|
|
||||||
|
gmmu = std::make_shared<Tegra::MemoryManager>(system, 40, vm.big_page_size_bits,
|
||||||
|
VM::PAGE_SIZE_BITS);
|
||||||
|
system.GPU().InitAddressSpace(*gmmu);
|
||||||
|
vm.initialised = true;
|
||||||
|
|
||||||
return NvResult::Success;
|
return NvResult::Success;
|
||||||
}
|
}
|
||||||
|
@ -99,21 +148,76 @@ NvResult nvhost_as_gpu::AllocateSpace(const std::vector<u8>& input, std::vector<
|
||||||
LOG_DEBUG(Service_NVDRV, "called, pages={:X}, page_size={:X}, flags={:X}", params.pages,
|
LOG_DEBUG(Service_NVDRV, "called, pages={:X}, page_size={:X}, flags={:X}", params.pages,
|
||||||
params.page_size, params.flags);
|
params.page_size, params.flags);
|
||||||
|
|
||||||
const auto size{static_cast<u64>(params.pages) * static_cast<u64>(params.page_size)};
|
std::scoped_lock lock(mutex);
|
||||||
if ((params.flags & AddressSpaceFlags::FixedOffset) != AddressSpaceFlags::None) {
|
|
||||||
params.offset = *system.GPU().MemoryManager().AllocateFixed(params.offset, size);
|
if (!vm.initialised) {
|
||||||
} else {
|
return NvResult::BadValue;
|
||||||
params.offset = system.GPU().MemoryManager().Allocate(size, params.align);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto result = NvResult::Success;
|
if (params.page_size != VM::YUZU_PAGESIZE && params.page_size != vm.big_page_size) {
|
||||||
if (!params.offset) {
|
return NvResult::BadValue;
|
||||||
LOG_CRITICAL(Service_NVDRV, "allocation failed for size {}", size);
|
|
||||||
result = NvResult::InsufficientMemory;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (params.page_size != vm.big_page_size &&
|
||||||
|
((params.flags & MappingFlags::Sparse) != MappingFlags::None)) {
|
||||||
|
UNIMPLEMENTED_MSG("Sparse small pages are not implemented!");
|
||||||
|
return NvResult::NotImplemented;
|
||||||
|
}
|
||||||
|
|
||||||
|
const u32 page_size_bits{params.page_size == VM::YUZU_PAGESIZE ? VM::PAGE_SIZE_BITS
|
||||||
|
: vm.big_page_size_bits};
|
||||||
|
|
||||||
|
auto& allocator{params.page_size == VM::YUZU_PAGESIZE ? *vm.small_page_allocator
|
||||||
|
: *vm.big_page_allocator};
|
||||||
|
|
||||||
|
if ((params.flags & MappingFlags::Fixed) != MappingFlags::None) {
|
||||||
|
allocator.AllocateFixed(static_cast<u32>(params.offset >> page_size_bits), params.pages);
|
||||||
|
} else {
|
||||||
|
params.offset = static_cast<u64>(allocator.Allocate(params.pages)) << page_size_bits;
|
||||||
|
if (!params.offset) {
|
||||||
|
ASSERT_MSG(false, "Failed to allocate free space in the GPU AS!");
|
||||||
|
return NvResult::InsufficientMemory;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
u64 size{static_cast<u64>(params.pages) * params.page_size};
|
||||||
|
|
||||||
|
if ((params.flags & MappingFlags::Sparse) != MappingFlags::None) {
|
||||||
|
gmmu->MapSparse(params.offset, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
allocation_map[params.offset] = {
|
||||||
|
.size = size,
|
||||||
|
.mappings{},
|
||||||
|
.page_size = params.page_size,
|
||||||
|
.sparse = (params.flags & MappingFlags::Sparse) != MappingFlags::None,
|
||||||
|
.big_pages = params.page_size != VM::YUZU_PAGESIZE,
|
||||||
|
};
|
||||||
|
|
||||||
std::memcpy(output.data(), ¶ms, output.size());
|
std::memcpy(output.data(), ¶ms, output.size());
|
||||||
return result;
|
return NvResult::Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
void nvhost_as_gpu::FreeMappingLocked(u64 offset) {
|
||||||
|
auto mapping{mapping_map.at(offset)};
|
||||||
|
|
||||||
|
if (!mapping->fixed) {
|
||||||
|
auto& allocator{mapping->big_page ? *vm.big_page_allocator : *vm.small_page_allocator};
|
||||||
|
u32 page_size_bits{mapping->big_page ? vm.big_page_size_bits : VM::PAGE_SIZE_BITS};
|
||||||
|
|
||||||
|
allocator.Free(static_cast<u32>(mapping->offset >> page_size_bits),
|
||||||
|
static_cast<u32>(mapping->size >> page_size_bits));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sparse mappings shouldn't be fully unmapped, just returned to their sparse state
|
||||||
|
// Only FreeSpace can unmap them fully
|
||||||
|
if (mapping->sparse_alloc) {
|
||||||
|
gmmu->MapSparse(offset, mapping->size, mapping->big_page);
|
||||||
|
} else {
|
||||||
|
gmmu->Unmap(offset, mapping->size);
|
||||||
|
}
|
||||||
|
|
||||||
|
mapping_map.erase(offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
NvResult nvhost_as_gpu::FreeSpace(const std::vector<u8>& input, std::vector<u8>& output) {
|
NvResult nvhost_as_gpu::FreeSpace(const std::vector<u8>& input, std::vector<u8>& output) {
|
||||||
|
@ -123,8 +227,40 @@ NvResult nvhost_as_gpu::FreeSpace(const std::vector<u8>& input, std::vector<u8>&
|
||||||
LOG_DEBUG(Service_NVDRV, "called, offset={:X}, pages={:X}, page_size={:X}", params.offset,
|
LOG_DEBUG(Service_NVDRV, "called, offset={:X}, pages={:X}, page_size={:X}", params.offset,
|
||||||
params.pages, params.page_size);
|
params.pages, params.page_size);
|
||||||
|
|
||||||
system.GPU().MemoryManager().Unmap(params.offset,
|
std::scoped_lock lock(mutex);
|
||||||
static_cast<std::size_t>(params.pages) * params.page_size);
|
|
||||||
|
if (!vm.initialised) {
|
||||||
|
return NvResult::BadValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
auto allocation{allocation_map[params.offset]};
|
||||||
|
|
||||||
|
if (allocation.page_size != params.page_size ||
|
||||||
|
allocation.size != (static_cast<u64>(params.pages) * params.page_size)) {
|
||||||
|
return NvResult::BadValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& mapping : allocation.mappings) {
|
||||||
|
FreeMappingLocked(mapping->offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unset sparse flag if required
|
||||||
|
if (allocation.sparse) {
|
||||||
|
gmmu->Unmap(params.offset, allocation.size);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto& allocator{params.page_size == VM::YUZU_PAGESIZE ? *vm.small_page_allocator
|
||||||
|
: *vm.big_page_allocator};
|
||||||
|
u32 page_size_bits{params.page_size == VM::YUZU_PAGESIZE ? VM::PAGE_SIZE_BITS
|
||||||
|
: vm.big_page_size_bits};
|
||||||
|
|
||||||
|
allocator.Free(static_cast<u32>(params.offset >> page_size_bits),
|
||||||
|
static_cast<u32>(allocation.size >> page_size_bits));
|
||||||
|
allocation_map.erase(params.offset);
|
||||||
|
} catch (const std::out_of_range&) {
|
||||||
|
return NvResult::BadValue;
|
||||||
|
}
|
||||||
|
|
||||||
std::memcpy(output.data(), ¶ms, output.size());
|
std::memcpy(output.data(), ¶ms, output.size());
|
||||||
return NvResult::Success;
|
return NvResult::Success;
|
||||||
|
@ -135,35 +271,52 @@ NvResult nvhost_as_gpu::Remap(const std::vector<u8>& input, std::vector<u8>& out
|
||||||
|
|
||||||
LOG_DEBUG(Service_NVDRV, "called, num_entries=0x{:X}", num_entries);
|
LOG_DEBUG(Service_NVDRV, "called, num_entries=0x{:X}", num_entries);
|
||||||
|
|
||||||
auto result = NvResult::Success;
|
|
||||||
std::vector<IoctlRemapEntry> entries(num_entries);
|
std::vector<IoctlRemapEntry> entries(num_entries);
|
||||||
std::memcpy(entries.data(), input.data(), input.size());
|
std::memcpy(entries.data(), input.data(), input.size());
|
||||||
|
|
||||||
for (const auto& entry : entries) {
|
std::scoped_lock lock(mutex);
|
||||||
LOG_DEBUG(Service_NVDRV, "remap entry, offset=0x{:X} handle=0x{:X} pages=0x{:X}",
|
|
||||||
entry.offset, entry.nvmap_handle, entry.pages);
|
|
||||||
|
|
||||||
const auto object{nvmap_dev->GetObject(entry.nvmap_handle)};
|
if (!vm.initialised) {
|
||||||
if (!object) {
|
return NvResult::BadValue;
|
||||||
LOG_CRITICAL(Service_NVDRV, "invalid nvmap_handle={:X}", entry.nvmap_handle);
|
|
||||||
result = NvResult::InvalidState;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto offset{static_cast<GPUVAddr>(entry.offset) << 0x10};
|
for (const auto& entry : entries) {
|
||||||
const auto size{static_cast<u64>(entry.pages) << 0x10};
|
GPUVAddr virtual_address{static_cast<u64>(entry.as_offset_big_pages)
|
||||||
const auto map_offset{static_cast<u64>(entry.map_offset) << 0x10};
|
<< vm.big_page_size_bits};
|
||||||
const auto addr{system.GPU().MemoryManager().Map(object->addr + map_offset, offset, size)};
|
u64 size{static_cast<u64>(entry.big_pages) << vm.big_page_size_bits};
|
||||||
|
|
||||||
if (!addr) {
|
auto alloc{allocation_map.upper_bound(virtual_address)};
|
||||||
LOG_CRITICAL(Service_NVDRV, "map returned an invalid address!");
|
|
||||||
result = NvResult::InvalidState;
|
if (alloc-- == allocation_map.begin() ||
|
||||||
break;
|
(virtual_address - alloc->first) + size > alloc->second.size) {
|
||||||
|
LOG_WARNING(Service_NVDRV, "Cannot remap into an unallocated region!");
|
||||||
|
return NvResult::BadValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!alloc->second.sparse) {
|
||||||
|
LOG_WARNING(Service_NVDRV, "Cannot remap a non-sparse mapping!");
|
||||||
|
return NvResult::BadValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool use_big_pages = alloc->second.big_pages;
|
||||||
|
if (!entry.handle) {
|
||||||
|
gmmu->MapSparse(virtual_address, size, use_big_pages);
|
||||||
|
} else {
|
||||||
|
auto handle{nvmap.GetHandle(entry.handle)};
|
||||||
|
if (!handle) {
|
||||||
|
return NvResult::BadValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
VAddr cpu_address{static_cast<VAddr>(
|
||||||
|
handle->address +
|
||||||
|
(static_cast<u64>(entry.handle_offset_big_pages) << vm.big_page_size_bits))};
|
||||||
|
|
||||||
|
gmmu->Map(virtual_address, cpu_address, size, use_big_pages);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::memcpy(output.data(), entries.data(), output.size());
|
std::memcpy(output.data(), entries.data(), output.size());
|
||||||
return result;
|
return NvResult::Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
NvResult nvhost_as_gpu::MapBufferEx(const std::vector<u8>& input, std::vector<u8>& output) {
|
NvResult nvhost_as_gpu::MapBufferEx(const std::vector<u8>& input, std::vector<u8>& output) {
|
||||||
|
@ -173,79 +326,98 @@ NvResult nvhost_as_gpu::MapBufferEx(const std::vector<u8>& input, std::vector<u8
|
||||||
LOG_DEBUG(Service_NVDRV,
|
LOG_DEBUG(Service_NVDRV,
|
||||||
"called, flags={:X}, nvmap_handle={:X}, buffer_offset={}, mapping_size={}"
|
"called, flags={:X}, nvmap_handle={:X}, buffer_offset={}, mapping_size={}"
|
||||||
", offset={}",
|
", offset={}",
|
||||||
params.flags, params.nvmap_handle, params.buffer_offset, params.mapping_size,
|
params.flags, params.handle, params.buffer_offset, params.mapping_size,
|
||||||
params.offset);
|
params.offset);
|
||||||
|
|
||||||
const auto object{nvmap_dev->GetObject(params.nvmap_handle)};
|
std::scoped_lock lock(mutex);
|
||||||
if (!object) {
|
|
||||||
LOG_CRITICAL(Service_NVDRV, "invalid nvmap_handle={:X}", params.nvmap_handle);
|
if (!vm.initialised) {
|
||||||
std::memcpy(output.data(), ¶ms, output.size());
|
return NvResult::BadValue;
|
||||||
return NvResult::InvalidState;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// The real nvservices doesn't make a distinction between handles and ids, and
|
// Remaps a subregion of an existing mapping to a different PA
|
||||||
// object can only have one handle and it will be the same as its id. Assert that this is the
|
if ((params.flags & MappingFlags::Remap) != MappingFlags::None) {
|
||||||
// case to prevent unexpected behavior.
|
try {
|
||||||
ASSERT(object->id == params.nvmap_handle);
|
auto mapping{mapping_map.at(params.offset)};
|
||||||
auto& gpu = system.GPU();
|
|
||||||
|
|
||||||
u64 page_size{params.page_size};
|
if (mapping->size < params.mapping_size) {
|
||||||
if (!page_size) {
|
LOG_WARNING(Service_NVDRV,
|
||||||
page_size = object->align;
|
"Cannot remap a partially mapped GPU address space region: 0x{:X}",
|
||||||
|
params.offset);
|
||||||
|
return NvResult::BadValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((params.flags & AddressSpaceFlags::Remap) != AddressSpaceFlags::None) {
|
u64 gpu_address{static_cast<u64>(params.offset + params.buffer_offset)};
|
||||||
if (const auto buffer_map{FindBufferMap(params.offset)}; buffer_map) {
|
VAddr cpu_address{mapping->ptr + params.buffer_offset};
|
||||||
const auto cpu_addr{static_cast<VAddr>(buffer_map->CpuAddr() + params.buffer_offset)};
|
|
||||||
const auto gpu_addr{static_cast<GPUVAddr>(params.offset + params.buffer_offset)};
|
|
||||||
|
|
||||||
if (!gpu.MemoryManager().Map(cpu_addr, gpu_addr, params.mapping_size)) {
|
gmmu->Map(gpu_address, cpu_address, params.mapping_size, mapping->big_page);
|
||||||
LOG_CRITICAL(Service_NVDRV,
|
|
||||||
"remap failed, flags={:X}, nvmap_handle={:X}, buffer_offset={}, "
|
|
||||||
"mapping_size = {}, offset={}",
|
|
||||||
params.flags, params.nvmap_handle, params.buffer_offset,
|
|
||||||
params.mapping_size, params.offset);
|
|
||||||
|
|
||||||
std::memcpy(output.data(), ¶ms, output.size());
|
return NvResult::Success;
|
||||||
return NvResult::InvalidState;
|
} catch (const std::out_of_range&) {
|
||||||
|
LOG_WARNING(Service_NVDRV, "Cannot remap an unmapped GPU address space region: 0x{:X}",
|
||||||
|
params.offset);
|
||||||
|
return NvResult::BadValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto handle{nvmap.GetHandle(params.handle)};
|
||||||
|
if (!handle) {
|
||||||
|
return NvResult::BadValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
VAddr cpu_address{static_cast<VAddr>(handle->address + params.buffer_offset)};
|
||||||
|
u64 size{params.mapping_size ? params.mapping_size : handle->orig_size};
|
||||||
|
|
||||||
|
bool big_page{[&]() {
|
||||||
|
if (Common::IsAligned(handle->align, vm.big_page_size)) {
|
||||||
|
return true;
|
||||||
|
} else if (Common::IsAligned(handle->align, VM::YUZU_PAGESIZE)) {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
ASSERT(false);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}()};
|
||||||
|
|
||||||
|
if ((params.flags & MappingFlags::Fixed) != MappingFlags::None) {
|
||||||
|
auto alloc{allocation_map.upper_bound(params.offset)};
|
||||||
|
|
||||||
|
if (alloc-- == allocation_map.begin() ||
|
||||||
|
(params.offset - alloc->first) + size > alloc->second.size) {
|
||||||
|
ASSERT_MSG(false, "Cannot perform a fixed mapping into an unallocated region!");
|
||||||
|
return NvResult::BadValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool use_big_pages = alloc->second.big_pages && big_page;
|
||||||
|
gmmu->Map(params.offset, cpu_address, size, use_big_pages);
|
||||||
|
|
||||||
|
auto mapping{std::make_shared<Mapping>(cpu_address, params.offset, size, true,
|
||||||
|
use_big_pages, alloc->second.sparse)};
|
||||||
|
alloc->second.mappings.push_back(mapping);
|
||||||
|
mapping_map[params.offset] = mapping;
|
||||||
|
} else {
|
||||||
|
|
||||||
|
auto& allocator{big_page ? *vm.big_page_allocator : *vm.small_page_allocator};
|
||||||
|
u32 page_size{big_page ? vm.big_page_size : VM::YUZU_PAGESIZE};
|
||||||
|
u32 page_size_bits{big_page ? vm.big_page_size_bits : VM::PAGE_SIZE_BITS};
|
||||||
|
|
||||||
|
params.offset = static_cast<u64>(allocator.Allocate(
|
||||||
|
static_cast<u32>(Common::AlignUp(size, page_size) >> page_size_bits)))
|
||||||
|
<< page_size_bits;
|
||||||
|
if (!params.offset) {
|
||||||
|
ASSERT_MSG(false, "Failed to allocate free space in the GPU AS!");
|
||||||
|
return NvResult::InsufficientMemory;
|
||||||
|
}
|
||||||
|
|
||||||
|
gmmu->Map(params.offset, cpu_address, Common::AlignUp(size, page_size), big_page);
|
||||||
|
|
||||||
|
auto mapping{
|
||||||
|
std::make_shared<Mapping>(cpu_address, params.offset, size, false, big_page, false)};
|
||||||
|
mapping_map[params.offset] = mapping;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::memcpy(output.data(), ¶ms, output.size());
|
std::memcpy(output.data(), ¶ms, output.size());
|
||||||
return NvResult::Success;
|
return NvResult::Success;
|
||||||
} else {
|
|
||||||
LOG_CRITICAL(Service_NVDRV, "address not mapped offset={}", params.offset);
|
|
||||||
|
|
||||||
std::memcpy(output.data(), ¶ms, output.size());
|
|
||||||
return NvResult::InvalidState;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// We can only map objects that have already been assigned a CPU address.
|
|
||||||
ASSERT(object->status == nvmap::Object::Status::Allocated);
|
|
||||||
|
|
||||||
const auto physical_address{object->addr + params.buffer_offset};
|
|
||||||
u64 size{params.mapping_size};
|
|
||||||
if (!size) {
|
|
||||||
size = object->size;
|
|
||||||
}
|
|
||||||
|
|
||||||
const bool is_alloc{(params.flags & AddressSpaceFlags::FixedOffset) == AddressSpaceFlags::None};
|
|
||||||
if (is_alloc) {
|
|
||||||
params.offset = gpu.MemoryManager().MapAllocate(physical_address, size, page_size);
|
|
||||||
} else {
|
|
||||||
params.offset = gpu.MemoryManager().Map(physical_address, params.offset, size);
|
|
||||||
}
|
|
||||||
|
|
||||||
auto result = NvResult::Success;
|
|
||||||
if (!params.offset) {
|
|
||||||
LOG_CRITICAL(Service_NVDRV, "failed to map size={}", size);
|
|
||||||
result = NvResult::InvalidState;
|
|
||||||
} else {
|
|
||||||
AddBufferMap(params.offset, size, physical_address, is_alloc);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::memcpy(output.data(), ¶ms, output.size());
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
NvResult nvhost_as_gpu::UnmapBuffer(const std::vector<u8>& input, std::vector<u8>& output) {
|
NvResult nvhost_as_gpu::UnmapBuffer(const std::vector<u8>& input, std::vector<u8>& output) {
|
||||||
|
@ -254,47 +426,82 @@ NvResult nvhost_as_gpu::UnmapBuffer(const std::vector<u8>& input, std::vector<u8
|
||||||
|
|
||||||
LOG_DEBUG(Service_NVDRV, "called, offset=0x{:X}", params.offset);
|
LOG_DEBUG(Service_NVDRV, "called, offset=0x{:X}", params.offset);
|
||||||
|
|
||||||
if (const auto size{RemoveBufferMap(params.offset)}; size) {
|
std::scoped_lock lock(mutex);
|
||||||
system.GPU().MemoryManager().Unmap(params.offset, *size);
|
|
||||||
} else {
|
if (!vm.initialised) {
|
||||||
LOG_ERROR(Service_NVDRV, "invalid offset=0x{:X}", params.offset);
|
return NvResult::BadValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
auto mapping{mapping_map.at(params.offset)};
|
||||||
|
|
||||||
|
if (!mapping->fixed) {
|
||||||
|
auto& allocator{mapping->big_page ? *vm.big_page_allocator : *vm.small_page_allocator};
|
||||||
|
u32 page_size_bits{mapping->big_page ? vm.big_page_size_bits : VM::PAGE_SIZE_BITS};
|
||||||
|
|
||||||
|
allocator.Free(static_cast<u32>(mapping->offset >> page_size_bits),
|
||||||
|
static_cast<u32>(mapping->size >> page_size_bits));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sparse mappings shouldn't be fully unmapped, just returned to their sparse state
|
||||||
|
// Only FreeSpace can unmap them fully
|
||||||
|
if (mapping->sparse_alloc) {
|
||||||
|
gmmu->MapSparse(params.offset, mapping->size, mapping->big_page);
|
||||||
|
} else {
|
||||||
|
gmmu->Unmap(params.offset, mapping->size);
|
||||||
|
}
|
||||||
|
|
||||||
|
mapping_map.erase(params.offset);
|
||||||
|
} catch (const std::out_of_range&) {
|
||||||
|
LOG_WARNING(Service_NVDRV, "Couldn't find region to unmap at 0x{:X}", params.offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::memcpy(output.data(), ¶ms, output.size());
|
|
||||||
return NvResult::Success;
|
return NvResult::Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
NvResult nvhost_as_gpu::BindChannel(const std::vector<u8>& input, std::vector<u8>& output) {
|
NvResult nvhost_as_gpu::BindChannel(const std::vector<u8>& input, std::vector<u8>& output) {
|
||||||
IoctlBindChannel params{};
|
IoctlBindChannel params{};
|
||||||
std::memcpy(¶ms, input.data(), input.size());
|
std::memcpy(¶ms, input.data(), input.size());
|
||||||
LOG_WARNING(Service_NVDRV, "(STUBBED) called, fd={:X}", params.fd);
|
LOG_DEBUG(Service_NVDRV, "called, fd={:X}", params.fd);
|
||||||
|
|
||||||
channel = params.fd;
|
auto gpu_channel_device = module.GetDevice<nvhost_gpu>(params.fd);
|
||||||
|
gpu_channel_device->channel_state->memory_manager = gmmu;
|
||||||
return NvResult::Success;
|
return NvResult::Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void nvhost_as_gpu::GetVARegionsImpl(IoctlGetVaRegions& params) {
|
||||||
|
params.buf_size = 2 * sizeof(VaRegion);
|
||||||
|
|
||||||
|
params.regions = std::array<VaRegion, 2>{
|
||||||
|
VaRegion{
|
||||||
|
.offset = vm.small_page_allocator->GetVAStart() << VM::PAGE_SIZE_BITS,
|
||||||
|
.page_size = VM::YUZU_PAGESIZE,
|
||||||
|
._pad0_{},
|
||||||
|
.pages = vm.small_page_allocator->GetVALimit() - vm.small_page_allocator->GetVAStart(),
|
||||||
|
},
|
||||||
|
VaRegion{
|
||||||
|
.offset = vm.big_page_allocator->GetVAStart() << vm.big_page_size_bits,
|
||||||
|
.page_size = vm.big_page_size,
|
||||||
|
._pad0_{},
|
||||||
|
.pages = vm.big_page_allocator->GetVALimit() - vm.big_page_allocator->GetVAStart(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
NvResult nvhost_as_gpu::GetVARegions(const std::vector<u8>& input, std::vector<u8>& output) {
|
NvResult nvhost_as_gpu::GetVARegions(const std::vector<u8>& input, std::vector<u8>& output) {
|
||||||
IoctlGetVaRegions params{};
|
IoctlGetVaRegions params{};
|
||||||
std::memcpy(¶ms, input.data(), input.size());
|
std::memcpy(¶ms, input.data(), input.size());
|
||||||
|
|
||||||
LOG_WARNING(Service_NVDRV, "(STUBBED) called, buf_addr={:X}, buf_size={:X}", params.buf_addr,
|
LOG_DEBUG(Service_NVDRV, "called, buf_addr={:X}, buf_size={:X}", params.buf_addr,
|
||||||
params.buf_size);
|
params.buf_size);
|
||||||
|
|
||||||
params.buf_size = 0x30;
|
std::scoped_lock lock(mutex);
|
||||||
|
|
||||||
params.small = IoctlVaRegion{
|
if (!vm.initialised) {
|
||||||
.offset = 0x04000000,
|
return NvResult::BadValue;
|
||||||
.page_size = DEFAULT_SMALL_PAGE_SIZE,
|
}
|
||||||
.pages = 0x3fbfff,
|
|
||||||
};
|
|
||||||
|
|
||||||
params.big = IoctlVaRegion{
|
GetVARegionsImpl(params);
|
||||||
.offset = 0x04000000,
|
|
||||||
.page_size = big_page_size,
|
|
||||||
.pages = 0x1bffff,
|
|
||||||
};
|
|
||||||
|
|
||||||
// TODO(ogniK): This probably can stay stubbed but should add support way way later
|
|
||||||
|
|
||||||
std::memcpy(output.data(), ¶ms, output.size());
|
std::memcpy(output.data(), ¶ms, output.size());
|
||||||
return NvResult::Success;
|
return NvResult::Success;
|
||||||
|
@ -305,62 +512,27 @@ NvResult nvhost_as_gpu::GetVARegions(const std::vector<u8>& input, std::vector<u
|
||||||
IoctlGetVaRegions params{};
|
IoctlGetVaRegions params{};
|
||||||
std::memcpy(¶ms, input.data(), input.size());
|
std::memcpy(¶ms, input.data(), input.size());
|
||||||
|
|
||||||
LOG_WARNING(Service_NVDRV, "(STUBBED) called, buf_addr={:X}, buf_size={:X}", params.buf_addr,
|
LOG_DEBUG(Service_NVDRV, "called, buf_addr={:X}, buf_size={:X}", params.buf_addr,
|
||||||
params.buf_size);
|
params.buf_size);
|
||||||
|
|
||||||
params.buf_size = 0x30;
|
std::scoped_lock lock(mutex);
|
||||||
|
|
||||||
params.small = IoctlVaRegion{
|
if (!vm.initialised) {
|
||||||
.offset = 0x04000000,
|
return NvResult::BadValue;
|
||||||
.page_size = 0x1000,
|
}
|
||||||
.pages = 0x3fbfff,
|
|
||||||
};
|
|
||||||
|
|
||||||
params.big = IoctlVaRegion{
|
GetVARegionsImpl(params);
|
||||||
.offset = 0x04000000,
|
|
||||||
.page_size = big_page_size,
|
|
||||||
.pages = 0x1bffff,
|
|
||||||
};
|
|
||||||
|
|
||||||
// TODO(ogniK): This probably can stay stubbed but should add support way way later
|
|
||||||
|
|
||||||
std::memcpy(output.data(), ¶ms, output.size());
|
std::memcpy(output.data(), ¶ms, output.size());
|
||||||
std::memcpy(inline_output.data(), ¶ms.small, sizeof(IoctlVaRegion));
|
std::memcpy(inline_output.data(), ¶ms.regions[0], sizeof(VaRegion));
|
||||||
std::memcpy(inline_output.data() + sizeof(IoctlVaRegion), ¶ms.big, sizeof(IoctlVaRegion));
|
std::memcpy(inline_output.data() + sizeof(VaRegion), ¶ms.regions[1], sizeof(VaRegion));
|
||||||
|
|
||||||
return NvResult::Success;
|
return NvResult::Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<nvhost_as_gpu::BufferMap> nvhost_as_gpu::FindBufferMap(GPUVAddr gpu_addr) const {
|
Kernel::KEvent* nvhost_as_gpu::QueryEvent(u32 event_id) {
|
||||||
const auto end{buffer_mappings.upper_bound(gpu_addr)};
|
LOG_CRITICAL(Service_NVDRV, "Unknown AS GPU Event {}", event_id);
|
||||||
for (auto iter{buffer_mappings.begin()}; iter != end; ++iter) {
|
return nullptr;
|
||||||
if (gpu_addr >= iter->second.StartAddr() && gpu_addr < iter->second.EndAddr()) {
|
|
||||||
return iter->second;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
|
|
||||||
void nvhost_as_gpu::AddBufferMap(GPUVAddr gpu_addr, std::size_t size, VAddr cpu_addr,
|
|
||||||
bool is_allocated) {
|
|
||||||
buffer_mappings[gpu_addr] = {gpu_addr, size, cpu_addr, is_allocated};
|
|
||||||
}
|
|
||||||
|
|
||||||
std::optional<std::size_t> nvhost_as_gpu::RemoveBufferMap(GPUVAddr gpu_addr) {
|
|
||||||
if (const auto iter{buffer_mappings.find(gpu_addr)}; iter != buffer_mappings.end()) {
|
|
||||||
std::size_t size{};
|
|
||||||
|
|
||||||
if (iter->second.IsAllocated()) {
|
|
||||||
size = iter->second.Size();
|
|
||||||
}
|
|
||||||
|
|
||||||
buffer_mappings.erase(iter);
|
|
||||||
|
|
||||||
return size;
|
|
||||||
}
|
|
||||||
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Service::Nvidia::Devices
|
} // namespace Service::Nvidia::Devices
|
||||||
|
|
|
@ -1,35 +1,50 @@
|
||||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
// SPDX-FileCopyrightText: 2021 yuzu Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-FileCopyrightText: 2021 Skyline Team and Contributors
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <bit>
|
||||||
|
#include <list>
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <mutex>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#include "common/address_space.h"
|
||||||
#include "common/common_funcs.h"
|
#include "common/common_funcs.h"
|
||||||
#include "common/common_types.h"
|
#include "common/common_types.h"
|
||||||
#include "common/swap.h"
|
#include "common/swap.h"
|
||||||
|
#include "core/hle/service/nvdrv/core/nvmap.h"
|
||||||
#include "core/hle/service/nvdrv/devices/nvdevice.h"
|
#include "core/hle/service/nvdrv/devices/nvdevice.h"
|
||||||
|
|
||||||
|
namespace Tegra {
|
||||||
|
class MemoryManager;
|
||||||
|
} // namespace Tegra
|
||||||
|
|
||||||
|
namespace Service::Nvidia {
|
||||||
|
class Module;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace Service::Nvidia::NvCore {
|
||||||
|
class Container;
|
||||||
|
class NvMap;
|
||||||
|
} // namespace Service::Nvidia::NvCore
|
||||||
|
|
||||||
namespace Service::Nvidia::Devices {
|
namespace Service::Nvidia::Devices {
|
||||||
|
|
||||||
constexpr u32 DEFAULT_BIG_PAGE_SIZE = 1 << 16;
|
enum class MappingFlags : u32 {
|
||||||
constexpr u32 DEFAULT_SMALL_PAGE_SIZE = 1 << 12;
|
None = 0,
|
||||||
|
Fixed = 1 << 0,
|
||||||
class nvmap;
|
Sparse = 1 << 1,
|
||||||
|
Remap = 1 << 8,
|
||||||
enum class AddressSpaceFlags : u32 {
|
|
||||||
None = 0x0,
|
|
||||||
FixedOffset = 0x1,
|
|
||||||
Remap = 0x100,
|
|
||||||
};
|
};
|
||||||
DECLARE_ENUM_FLAG_OPERATORS(AddressSpaceFlags);
|
DECLARE_ENUM_FLAG_OPERATORS(MappingFlags);
|
||||||
|
|
||||||
class nvhost_as_gpu final : public nvdevice {
|
class nvhost_as_gpu final : public nvdevice {
|
||||||
public:
|
public:
|
||||||
explicit nvhost_as_gpu(Core::System& system_, std::shared_ptr<nvmap> nvmap_dev_);
|
explicit nvhost_as_gpu(Core::System& system_, Module& module, NvCore::Container& core);
|
||||||
~nvhost_as_gpu() override;
|
~nvhost_as_gpu() override;
|
||||||
|
|
||||||
NvResult Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8>& input,
|
NvResult Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8>& input,
|
||||||
|
@ -42,46 +57,17 @@ public:
|
||||||
void OnOpen(DeviceFD fd) override;
|
void OnOpen(DeviceFD fd) override;
|
||||||
void OnClose(DeviceFD fd) override;
|
void OnClose(DeviceFD fd) override;
|
||||||
|
|
||||||
private:
|
Kernel::KEvent* QueryEvent(u32 event_id) override;
|
||||||
class BufferMap final {
|
|
||||||
public:
|
|
||||||
constexpr BufferMap() = default;
|
|
||||||
|
|
||||||
constexpr BufferMap(GPUVAddr start_addr_, std::size_t size_)
|
struct VaRegion {
|
||||||
: start_addr{start_addr_}, end_addr{start_addr_ + size_} {}
|
u64 offset;
|
||||||
|
u32 page_size;
|
||||||
constexpr BufferMap(GPUVAddr start_addr_, std::size_t size_, VAddr cpu_addr_,
|
u32 _pad0_;
|
||||||
bool is_allocated_)
|
u64 pages;
|
||||||
: 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{};
|
|
||||||
};
|
};
|
||||||
|
static_assert(sizeof(VaRegion) == 0x18);
|
||||||
|
|
||||||
|
private:
|
||||||
struct IoctlAllocAsEx {
|
struct IoctlAllocAsEx {
|
||||||
u32_le flags{}; // usually passes 1
|
u32_le flags{}; // usually passes 1
|
||||||
s32_le as_fd{}; // ignored; passes 0
|
s32_le as_fd{}; // ignored; passes 0
|
||||||
|
@ -96,7 +82,7 @@ private:
|
||||||
struct IoctlAllocSpace {
|
struct IoctlAllocSpace {
|
||||||
u32_le pages{};
|
u32_le pages{};
|
||||||
u32_le page_size{};
|
u32_le page_size{};
|
||||||
AddressSpaceFlags flags{};
|
MappingFlags flags{};
|
||||||
INSERT_PADDING_WORDS(1);
|
INSERT_PADDING_WORDS(1);
|
||||||
union {
|
union {
|
||||||
u64_le offset;
|
u64_le offset;
|
||||||
|
@ -113,19 +99,19 @@ private:
|
||||||
static_assert(sizeof(IoctlFreeSpace) == 16, "IoctlFreeSpace is incorrect size");
|
static_assert(sizeof(IoctlFreeSpace) == 16, "IoctlFreeSpace is incorrect size");
|
||||||
|
|
||||||
struct IoctlRemapEntry {
|
struct IoctlRemapEntry {
|
||||||
u16_le flags{};
|
u16 flags;
|
||||||
u16_le kind{};
|
u16 kind;
|
||||||
u32_le nvmap_handle{};
|
NvCore::NvMap::Handle::Id handle;
|
||||||
u32_le map_offset{};
|
u32 handle_offset_big_pages;
|
||||||
u32_le offset{};
|
u32 as_offset_big_pages;
|
||||||
u32_le pages{};
|
u32 big_pages;
|
||||||
};
|
};
|
||||||
static_assert(sizeof(IoctlRemapEntry) == 20, "IoctlRemapEntry is incorrect size");
|
static_assert(sizeof(IoctlRemapEntry) == 20, "IoctlRemapEntry is incorrect size");
|
||||||
|
|
||||||
struct IoctlMapBufferEx {
|
struct IoctlMapBufferEx {
|
||||||
AddressSpaceFlags flags{}; // bit0: fixed_offset, bit2: cacheable
|
MappingFlags flags{}; // bit0: fixed_offset, bit2: cacheable
|
||||||
u32_le kind{}; // -1 is default
|
u32_le kind{}; // -1 is default
|
||||||
u32_le nvmap_handle{};
|
NvCore::NvMap::Handle::Id handle;
|
||||||
u32_le page_size{}; // 0 means don't care
|
u32_le page_size{}; // 0 means don't care
|
||||||
s64_le buffer_offset{};
|
s64_le buffer_offset{};
|
||||||
u64_le mapping_size{};
|
u64_le mapping_size{};
|
||||||
|
@ -143,27 +129,15 @@ private:
|
||||||
};
|
};
|
||||||
static_assert(sizeof(IoctlBindChannel) == 4, "IoctlBindChannel is incorrect size");
|
static_assert(sizeof(IoctlBindChannel) == 4, "IoctlBindChannel is incorrect size");
|
||||||
|
|
||||||
struct IoctlVaRegion {
|
|
||||||
u64_le offset{};
|
|
||||||
u32_le page_size{};
|
|
||||||
INSERT_PADDING_WORDS(1);
|
|
||||||
u64_le pages{};
|
|
||||||
};
|
|
||||||
static_assert(sizeof(IoctlVaRegion) == 24, "IoctlVaRegion is incorrect size");
|
|
||||||
|
|
||||||
struct IoctlGetVaRegions {
|
struct IoctlGetVaRegions {
|
||||||
u64_le buf_addr{}; // (contained output user ptr on linux, ignored)
|
u64_le buf_addr{}; // (contained output user ptr on linux, ignored)
|
||||||
u32_le buf_size{}; // forced to 2*sizeof(struct va_region)
|
u32_le buf_size{}; // forced to 2*sizeof(struct va_region)
|
||||||
u32_le reserved{};
|
u32_le reserved{};
|
||||||
IoctlVaRegion small{};
|
std::array<VaRegion, 2> regions{};
|
||||||
IoctlVaRegion big{};
|
|
||||||
};
|
};
|
||||||
static_assert(sizeof(IoctlGetVaRegions) == 16 + sizeof(IoctlVaRegion) * 2,
|
static_assert(sizeof(IoctlGetVaRegions) == 16 + sizeof(VaRegion) * 2,
|
||||||
"IoctlGetVaRegions is incorrect size");
|
"IoctlGetVaRegions is incorrect size");
|
||||||
|
|
||||||
s32 channel{};
|
|
||||||
u32 big_page_size{DEFAULT_BIG_PAGE_SIZE};
|
|
||||||
|
|
||||||
NvResult AllocAsEx(const std::vector<u8>& input, std::vector<u8>& output);
|
NvResult AllocAsEx(const std::vector<u8>& input, std::vector<u8>& output);
|
||||||
NvResult AllocateSpace(const std::vector<u8>& input, std::vector<u8>& output);
|
NvResult AllocateSpace(const std::vector<u8>& input, std::vector<u8>& output);
|
||||||
NvResult Remap(const std::vector<u8>& input, std::vector<u8>& output);
|
NvResult Remap(const std::vector<u8>& input, std::vector<u8>& output);
|
||||||
|
@ -172,18 +146,75 @@ private:
|
||||||
NvResult FreeSpace(const std::vector<u8>& input, std::vector<u8>& output);
|
NvResult FreeSpace(const std::vector<u8>& input, std::vector<u8>& output);
|
||||||
NvResult BindChannel(const std::vector<u8>& input, std::vector<u8>& output);
|
NvResult BindChannel(const std::vector<u8>& input, std::vector<u8>& output);
|
||||||
|
|
||||||
|
void GetVARegionsImpl(IoctlGetVaRegions& params);
|
||||||
NvResult GetVARegions(const std::vector<u8>& input, std::vector<u8>& output);
|
NvResult GetVARegions(const std::vector<u8>& input, std::vector<u8>& output);
|
||||||
NvResult GetVARegions(const std::vector<u8>& input, std::vector<u8>& output,
|
NvResult GetVARegions(const std::vector<u8>& input, std::vector<u8>& output,
|
||||||
std::vector<u8>& inline_output);
|
std::vector<u8>& inline_output);
|
||||||
|
|
||||||
std::optional<BufferMap> FindBufferMap(GPUVAddr gpu_addr) const;
|
void FreeMappingLocked(u64 offset);
|
||||||
void AddBufferMap(GPUVAddr gpu_addr, std::size_t size, VAddr cpu_addr, bool is_allocated);
|
|
||||||
std::optional<std::size_t> RemoveBufferMap(GPUVAddr gpu_addr);
|
|
||||||
|
|
||||||
std::shared_ptr<nvmap> nvmap_dev;
|
Module& module;
|
||||||
|
|
||||||
// This is expected to be ordered, therefore we must use a map, not unordered_map
|
NvCore::Container& container;
|
||||||
std::map<GPUVAddr, BufferMap> buffer_mappings;
|
NvCore::NvMap& nvmap;
|
||||||
|
|
||||||
|
struct Mapping {
|
||||||
|
VAddr ptr;
|
||||||
|
u64 offset;
|
||||||
|
u64 size;
|
||||||
|
bool fixed;
|
||||||
|
bool big_page; // Only valid if fixed == false
|
||||||
|
bool sparse_alloc;
|
||||||
|
|
||||||
|
Mapping(VAddr ptr_, u64 offset_, u64 size_, bool fixed_, bool big_page_, bool sparse_alloc_)
|
||||||
|
: ptr(ptr_), offset(offset_), size(size_), fixed(fixed_), big_page(big_page_),
|
||||||
|
sparse_alloc(sparse_alloc_) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Allocation {
|
||||||
|
u64 size;
|
||||||
|
std::list<std::shared_ptr<Mapping>> mappings;
|
||||||
|
u32 page_size;
|
||||||
|
bool sparse;
|
||||||
|
bool big_pages;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::map<u64, std::shared_ptr<Mapping>>
|
||||||
|
mapping_map; //!< This maps the base addresses of mapped buffers to their total sizes and
|
||||||
|
//!< mapping type, this is needed as what was originally a single buffer may
|
||||||
|
//!< have been split into multiple GPU side buffers with the remap flag.
|
||||||
|
std::map<u64, Allocation> allocation_map; //!< Holds allocations created by AllocSpace from
|
||||||
|
//!< which fixed buffers can be mapped into
|
||||||
|
std::mutex mutex; //!< Locks all AS operations
|
||||||
|
|
||||||
|
struct VM {
|
||||||
|
static constexpr u32 YUZU_PAGESIZE{0x1000};
|
||||||
|
static constexpr u32 PAGE_SIZE_BITS{std::countr_zero(YUZU_PAGESIZE)};
|
||||||
|
|
||||||
|
static constexpr u32 SUPPORTED_BIG_PAGE_SIZES{0x30000};
|
||||||
|
static constexpr u32 DEFAULT_BIG_PAGE_SIZE{0x20000};
|
||||||
|
u32 big_page_size{DEFAULT_BIG_PAGE_SIZE};
|
||||||
|
u32 big_page_size_bits{std::countr_zero(DEFAULT_BIG_PAGE_SIZE)};
|
||||||
|
|
||||||
|
static constexpr u32 VA_START_SHIFT{10};
|
||||||
|
static constexpr u64 DEFAULT_VA_SPLIT{1ULL << 34};
|
||||||
|
static constexpr u64 DEFAULT_VA_RANGE{1ULL << 37};
|
||||||
|
u64 va_range_start{DEFAULT_BIG_PAGE_SIZE << VA_START_SHIFT};
|
||||||
|
u64 va_range_split{DEFAULT_VA_SPLIT};
|
||||||
|
u64 va_range_end{DEFAULT_VA_RANGE};
|
||||||
|
|
||||||
|
using Allocator = Common::FlatAllocator<u32, 0, 32>;
|
||||||
|
|
||||||
|
std::unique_ptr<Allocator> big_page_allocator;
|
||||||
|
std::shared_ptr<Allocator>
|
||||||
|
small_page_allocator; //! Shared as this is also used by nvhost::GpuChannel
|
||||||
|
|
||||||
|
bool initialised{};
|
||||||
|
} vm;
|
||||||
|
std::shared_ptr<Tegra::MemoryManager> gmmu;
|
||||||
|
|
||||||
|
// s32 channel{};
|
||||||
|
// u32 big_page_size{VM::DEFAULT_BIG_PAGE_SIZE};
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Service::Nvidia::Devices
|
} // namespace Service::Nvidia::Devices
|
||||||
|
|
|
@ -1,24 +1,39 @@
|
||||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
// SPDX-FileCopyrightText: 2021 yuzu Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-FileCopyrightText: 2021 Skyline Team and Contributors
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
#include <bit>
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
|
||||||
|
#include <fmt/format.h>
|
||||||
#include "common/assert.h"
|
#include "common/assert.h"
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
|
#include "common/scope_exit.h"
|
||||||
#include "core/core.h"
|
#include "core/core.h"
|
||||||
#include "core/hle/kernel/k_event.h"
|
#include "core/hle/kernel/k_event.h"
|
||||||
#include "core/hle/kernel/k_writable_event.h"
|
#include "core/hle/kernel/k_writable_event.h"
|
||||||
|
#include "core/hle/service/nvdrv/core/container.h"
|
||||||
|
#include "core/hle/service/nvdrv/core/syncpoint_manager.h"
|
||||||
#include "core/hle/service/nvdrv/devices/nvhost_ctrl.h"
|
#include "core/hle/service/nvdrv/devices/nvhost_ctrl.h"
|
||||||
#include "video_core/gpu.h"
|
#include "video_core/gpu.h"
|
||||||
|
#include "video_core/host1x/host1x.h"
|
||||||
|
|
||||||
namespace Service::Nvidia::Devices {
|
namespace Service::Nvidia::Devices {
|
||||||
|
|
||||||
nvhost_ctrl::nvhost_ctrl(Core::System& system_, EventInterface& events_interface_,
|
nvhost_ctrl::nvhost_ctrl(Core::System& system_, EventInterface& events_interface_,
|
||||||
SyncpointManager& syncpoint_manager_)
|
NvCore::Container& core_)
|
||||||
: nvdevice{system_}, events_interface{events_interface_}, syncpoint_manager{
|
: nvdevice{system_}, events_interface{events_interface_}, core{core_},
|
||||||
syncpoint_manager_} {}
|
syncpoint_manager{core_.GetSyncpointManager()} {}
|
||||||
nvhost_ctrl::~nvhost_ctrl() = default;
|
|
||||||
|
nvhost_ctrl::~nvhost_ctrl() {
|
||||||
|
for (auto& event : events) {
|
||||||
|
if (!event.registered) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
events_interface.FreeEvent(event.kevent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
NvResult nvhost_ctrl::Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8>& input,
|
NvResult nvhost_ctrl::Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8>& input,
|
||||||
std::vector<u8>& output) {
|
std::vector<u8>& output) {
|
||||||
|
@ -30,13 +45,15 @@ NvResult nvhost_ctrl::Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8>&
|
||||||
case 0x1c:
|
case 0x1c:
|
||||||
return IocCtrlClearEventWait(input, output);
|
return IocCtrlClearEventWait(input, output);
|
||||||
case 0x1d:
|
case 0x1d:
|
||||||
return IocCtrlEventWait(input, output, false);
|
|
||||||
case 0x1e:
|
|
||||||
return IocCtrlEventWait(input, output, true);
|
return IocCtrlEventWait(input, output, true);
|
||||||
|
case 0x1e:
|
||||||
|
return IocCtrlEventWait(input, output, false);
|
||||||
case 0x1f:
|
case 0x1f:
|
||||||
return IocCtrlEventRegister(input, output);
|
return IocCtrlEventRegister(input, output);
|
||||||
case 0x20:
|
case 0x20:
|
||||||
return IocCtrlEventUnregister(input, output);
|
return IocCtrlEventUnregister(input, output);
|
||||||
|
case 0x21:
|
||||||
|
return IocCtrlEventUnregisterBatch(input, output);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
@ -60,6 +77,7 @@ NvResult nvhost_ctrl::Ioctl3(DeviceFD fd, Ioctl command, const std::vector<u8>&
|
||||||
}
|
}
|
||||||
|
|
||||||
void nvhost_ctrl::OnOpen(DeviceFD fd) {}
|
void nvhost_ctrl::OnOpen(DeviceFD fd) {}
|
||||||
|
|
||||||
void nvhost_ctrl::OnClose(DeviceFD fd) {}
|
void nvhost_ctrl::OnClose(DeviceFD fd) {}
|
||||||
|
|
||||||
NvResult nvhost_ctrl::NvOsGetConfigU32(const std::vector<u8>& input, std::vector<u8>& output) {
|
NvResult nvhost_ctrl::NvOsGetConfigU32(const std::vector<u8>& input, std::vector<u8>& output) {
|
||||||
|
@ -71,116 +89,167 @@ NvResult nvhost_ctrl::NvOsGetConfigU32(const std::vector<u8>& input, std::vector
|
||||||
}
|
}
|
||||||
|
|
||||||
NvResult nvhost_ctrl::IocCtrlEventWait(const std::vector<u8>& input, std::vector<u8>& output,
|
NvResult nvhost_ctrl::IocCtrlEventWait(const std::vector<u8>& input, std::vector<u8>& output,
|
||||||
bool is_async) {
|
bool is_allocation) {
|
||||||
IocCtrlEventWaitParams params{};
|
IocCtrlEventWaitParams params{};
|
||||||
std::memcpy(¶ms, input.data(), sizeof(params));
|
std::memcpy(¶ms, input.data(), sizeof(params));
|
||||||
LOG_DEBUG(Service_NVDRV, "syncpt_id={}, threshold={}, timeout={}, is_async={}",
|
LOG_DEBUG(Service_NVDRV, "syncpt_id={}, threshold={}, timeout={}, is_allocation={}",
|
||||||
params.syncpt_id, params.threshold, params.timeout, is_async);
|
params.fence.id, params.fence.value, params.timeout, is_allocation);
|
||||||
|
|
||||||
if (params.syncpt_id >= MaxSyncPoints) {
|
bool must_unmark_fail = !is_allocation;
|
||||||
|
const u32 event_id = params.value.raw;
|
||||||
|
SCOPE_EXIT({
|
||||||
|
std::memcpy(output.data(), ¶ms, sizeof(params));
|
||||||
|
if (must_unmark_fail) {
|
||||||
|
events[event_id].fails = 0;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const u32 fence_id = static_cast<u32>(params.fence.id);
|
||||||
|
|
||||||
|
if (fence_id >= MaxSyncPoints) {
|
||||||
return NvResult::BadParameter;
|
return NvResult::BadParameter;
|
||||||
}
|
}
|
||||||
|
|
||||||
u32 event_id = params.value & 0x00FF;
|
if (params.fence.value == 0) {
|
||||||
|
if (!syncpoint_manager.IsSyncpointAllocated(params.fence.id)) {
|
||||||
|
LOG_WARNING(Service_NVDRV,
|
||||||
|
"Unallocated syncpt_id={}, threshold={}, timeout={}, is_allocation={}",
|
||||||
|
params.fence.id, params.fence.value, params.timeout, is_allocation);
|
||||||
|
} else {
|
||||||
|
params.value.raw = syncpoint_manager.ReadSyncpointMinValue(fence_id);
|
||||||
|
}
|
||||||
|
return NvResult::Success;
|
||||||
|
}
|
||||||
|
|
||||||
if (event_id >= MaxNvEvents) {
|
if (syncpoint_manager.IsFenceSignalled(params.fence)) {
|
||||||
std::memcpy(output.data(), ¶ms, sizeof(params));
|
params.value.raw = syncpoint_manager.ReadSyncpointMinValue(fence_id);
|
||||||
|
return NvResult::Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (const auto new_value = syncpoint_manager.UpdateMin(fence_id);
|
||||||
|
syncpoint_manager.IsFenceSignalled(params.fence)) {
|
||||||
|
params.value.raw = new_value;
|
||||||
|
return NvResult::Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto& host1x_syncpoint_manager = system.Host1x().GetSyncpointManager();
|
||||||
|
const u32 target_value = params.fence.value;
|
||||||
|
|
||||||
|
auto lock = NvEventsLock();
|
||||||
|
|
||||||
|
u32 slot = [&]() {
|
||||||
|
if (is_allocation) {
|
||||||
|
params.value.raw = 0;
|
||||||
|
return FindFreeNvEvent(fence_id);
|
||||||
|
} else {
|
||||||
|
return params.value.raw;
|
||||||
|
}
|
||||||
|
}();
|
||||||
|
|
||||||
|
must_unmark_fail = false;
|
||||||
|
|
||||||
|
const auto check_failing = [&]() {
|
||||||
|
if (events[slot].fails > 2) {
|
||||||
|
{
|
||||||
|
auto lk = system.StallProcesses();
|
||||||
|
host1x_syncpoint_manager.WaitHost(fence_id, target_value);
|
||||||
|
system.UnstallProcesses();
|
||||||
|
}
|
||||||
|
params.value.raw = target_value;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (slot >= MaxNvEvents) {
|
||||||
return NvResult::BadParameter;
|
return NvResult::BadParameter;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (syncpoint_manager.IsSyncpointExpired(params.syncpt_id, params.threshold)) {
|
|
||||||
params.value = syncpoint_manager.GetSyncpointMin(params.syncpt_id);
|
|
||||||
std::memcpy(output.data(), ¶ms, sizeof(params));
|
|
||||||
events_interface.failed[event_id] = false;
|
|
||||||
return NvResult::Success;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (const auto new_value = syncpoint_manager.RefreshSyncpoint(params.syncpt_id);
|
|
||||||
syncpoint_manager.IsSyncpointExpired(params.syncpt_id, params.threshold)) {
|
|
||||||
params.value = new_value;
|
|
||||||
std::memcpy(output.data(), ¶ms, sizeof(params));
|
|
||||||
events_interface.failed[event_id] = false;
|
|
||||||
return NvResult::Success;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto& event = events_interface.events[event_id];
|
|
||||||
auto& gpu = system.GPU();
|
|
||||||
|
|
||||||
// This is mostly to take into account unimplemented features. As synced
|
|
||||||
// gpu is always synced.
|
|
||||||
if (!gpu.IsAsync()) {
|
|
||||||
event.event->GetWritableEvent().Signal();
|
|
||||||
return NvResult::Success;
|
|
||||||
}
|
|
||||||
const u32 current_syncpoint_value = event.fence.value;
|
|
||||||
const s32 diff = current_syncpoint_value - params.threshold;
|
|
||||||
if (diff >= 0) {
|
|
||||||
event.event->GetWritableEvent().Signal();
|
|
||||||
params.value = current_syncpoint_value;
|
|
||||||
std::memcpy(output.data(), ¶ms, sizeof(params));
|
|
||||||
events_interface.failed[event_id] = false;
|
|
||||||
return NvResult::Success;
|
|
||||||
}
|
|
||||||
const u32 target_value = current_syncpoint_value - diff;
|
|
||||||
|
|
||||||
if (!is_async) {
|
|
||||||
params.value = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (params.timeout == 0) {
|
if (params.timeout == 0) {
|
||||||
std::memcpy(output.data(), ¶ms, sizeof(params));
|
if (check_failing()) {
|
||||||
|
events[slot].fails = 0;
|
||||||
|
return NvResult::Success;
|
||||||
|
}
|
||||||
return NvResult::Timeout;
|
return NvResult::Timeout;
|
||||||
}
|
}
|
||||||
|
|
||||||
EventState status = events_interface.status[event_id];
|
auto& event = events[slot];
|
||||||
const bool bad_parameter = status == EventState::Busy;
|
|
||||||
if (bad_parameter) {
|
if (!event.registered) {
|
||||||
std::memcpy(output.data(), ¶ms, sizeof(params));
|
|
||||||
return NvResult::BadParameter;
|
return NvResult::BadParameter;
|
||||||
}
|
}
|
||||||
events_interface.SetEventStatus(event_id, EventState::Waiting);
|
|
||||||
events_interface.assigned_syncpt[event_id] = params.syncpt_id;
|
if (event.IsBeingUsed()) {
|
||||||
events_interface.assigned_value[event_id] = target_value;
|
return NvResult::BadParameter;
|
||||||
if (is_async) {
|
|
||||||
params.value = params.syncpt_id << 4;
|
|
||||||
} else {
|
|
||||||
params.value = ((params.syncpt_id & 0xfff) << 16) | 0x10000000;
|
|
||||||
}
|
}
|
||||||
params.value |= event_id;
|
|
||||||
event.event->GetWritableEvent().Clear();
|
if (check_failing()) {
|
||||||
if (events_interface.failed[event_id]) {
|
event.fails = 0;
|
||||||
{
|
|
||||||
auto lk = system.StallProcesses();
|
|
||||||
gpu.WaitFence(params.syncpt_id, target_value);
|
|
||||||
system.UnstallProcesses();
|
|
||||||
}
|
|
||||||
std::memcpy(output.data(), ¶ms, sizeof(params));
|
|
||||||
events_interface.failed[event_id] = false;
|
|
||||||
return NvResult::Success;
|
return NvResult::Success;
|
||||||
}
|
}
|
||||||
gpu.RegisterSyncptInterrupt(params.syncpt_id, target_value);
|
|
||||||
std::memcpy(output.data(), ¶ms, sizeof(params));
|
params.value.raw = 0;
|
||||||
|
|
||||||
|
event.status.store(EventState::Waiting, std::memory_order_release);
|
||||||
|
event.assigned_syncpt = fence_id;
|
||||||
|
event.assigned_value = target_value;
|
||||||
|
if (is_allocation) {
|
||||||
|
params.value.syncpoint_id_for_allocation.Assign(static_cast<u16>(fence_id));
|
||||||
|
params.value.event_allocated.Assign(1);
|
||||||
|
} else {
|
||||||
|
params.value.syncpoint_id.Assign(fence_id);
|
||||||
|
}
|
||||||
|
params.value.raw |= slot;
|
||||||
|
|
||||||
|
event.wait_handle =
|
||||||
|
host1x_syncpoint_manager.RegisterHostAction(fence_id, target_value, [this, slot]() {
|
||||||
|
auto& event_ = events[slot];
|
||||||
|
if (event_.status.exchange(EventState::Signalling, std::memory_order_acq_rel) ==
|
||||||
|
EventState::Waiting) {
|
||||||
|
event_.kevent->GetWritableEvent().Signal();
|
||||||
|
}
|
||||||
|
event_.status.store(EventState::Signalled, std::memory_order_release);
|
||||||
|
});
|
||||||
return NvResult::Timeout;
|
return NvResult::Timeout;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
NvResult nvhost_ctrl::FreeEvent(u32 slot) {
|
||||||
|
if (slot >= MaxNvEvents) {
|
||||||
|
return NvResult::BadParameter;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto& event = events[slot];
|
||||||
|
|
||||||
|
if (!event.registered) {
|
||||||
|
return NvResult::Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.IsBeingUsed()) {
|
||||||
|
return NvResult::Busy;
|
||||||
|
}
|
||||||
|
|
||||||
|
FreeNvEvent(slot);
|
||||||
|
return NvResult::Success;
|
||||||
|
}
|
||||||
|
|
||||||
NvResult nvhost_ctrl::IocCtrlEventRegister(const std::vector<u8>& input, std::vector<u8>& output) {
|
NvResult nvhost_ctrl::IocCtrlEventRegister(const std::vector<u8>& input, std::vector<u8>& output) {
|
||||||
IocCtrlEventRegisterParams params{};
|
IocCtrlEventRegisterParams params{};
|
||||||
std::memcpy(¶ms, input.data(), sizeof(params));
|
std::memcpy(¶ms, input.data(), sizeof(params));
|
||||||
const u32 event_id = params.user_event_id & 0x00FF;
|
const u32 event_id = params.user_event_id;
|
||||||
LOG_DEBUG(Service_NVDRV, " called, user_event_id: {:X}", event_id);
|
LOG_DEBUG(Service_NVDRV, " called, user_event_id: {:X}", event_id);
|
||||||
if (event_id >= MaxNvEvents) {
|
if (event_id >= MaxNvEvents) {
|
||||||
return NvResult::BadParameter;
|
return NvResult::BadParameter;
|
||||||
}
|
}
|
||||||
if (events_interface.registered[event_id]) {
|
|
||||||
const auto event_state = events_interface.status[event_id];
|
auto lock = NvEventsLock();
|
||||||
if (event_state != EventState::Free) {
|
|
||||||
LOG_WARNING(Service_NVDRV, "Event already registered! Unregistering previous event");
|
if (events[event_id].registered) {
|
||||||
events_interface.UnregisterEvent(event_id);
|
const auto result = FreeEvent(event_id);
|
||||||
} else {
|
if (result != NvResult::Success) {
|
||||||
return NvResult::BadParameter;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
events_interface.RegisterEvent(event_id);
|
CreateNvEvent(event_id);
|
||||||
return NvResult::Success;
|
return NvResult::Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -190,34 +259,142 @@ NvResult nvhost_ctrl::IocCtrlEventUnregister(const std::vector<u8>& input,
|
||||||
std::memcpy(¶ms, input.data(), sizeof(params));
|
std::memcpy(¶ms, input.data(), sizeof(params));
|
||||||
const u32 event_id = params.user_event_id & 0x00FF;
|
const u32 event_id = params.user_event_id & 0x00FF;
|
||||||
LOG_DEBUG(Service_NVDRV, " called, user_event_id: {:X}", event_id);
|
LOG_DEBUG(Service_NVDRV, " called, user_event_id: {:X}", event_id);
|
||||||
if (event_id >= MaxNvEvents) {
|
|
||||||
return NvResult::BadParameter;
|
auto lock = NvEventsLock();
|
||||||
|
return FreeEvent(event_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
NvResult nvhost_ctrl::IocCtrlEventUnregisterBatch(const std::vector<u8>& input,
|
||||||
|
std::vector<u8>& output) {
|
||||||
|
IocCtrlEventUnregisterBatchParams params{};
|
||||||
|
std::memcpy(¶ms, input.data(), sizeof(params));
|
||||||
|
u64 event_mask = params.user_events;
|
||||||
|
LOG_DEBUG(Service_NVDRV, " called, event_mask: {:X}", event_mask);
|
||||||
|
|
||||||
|
auto lock = NvEventsLock();
|
||||||
|
while (event_mask != 0) {
|
||||||
|
const u64 event_id = std::countr_zero(event_mask);
|
||||||
|
event_mask &= ~(1ULL << event_id);
|
||||||
|
const auto result = FreeEvent(static_cast<u32>(event_id));
|
||||||
|
if (result != NvResult::Success) {
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
if (!events_interface.registered[event_id]) {
|
|
||||||
return NvResult::BadParameter;
|
|
||||||
}
|
}
|
||||||
events_interface.UnregisterEvent(event_id);
|
|
||||||
return NvResult::Success;
|
return NvResult::Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
NvResult nvhost_ctrl::IocCtrlClearEventWait(const std::vector<u8>& input, std::vector<u8>& output) {
|
NvResult nvhost_ctrl::IocCtrlClearEventWait(const std::vector<u8>& input, std::vector<u8>& output) {
|
||||||
IocCtrlEventSignalParams params{};
|
IocCtrlEventClearParams params{};
|
||||||
std::memcpy(¶ms, input.data(), sizeof(params));
|
std::memcpy(¶ms, input.data(), sizeof(params));
|
||||||
|
|
||||||
u32 event_id = params.event_id & 0x00FF;
|
u32 event_id = params.event_id.slot;
|
||||||
LOG_WARNING(Service_NVDRV, "cleared event wait on, event_id: {:X}", event_id);
|
LOG_DEBUG(Service_NVDRV, "called, event_id: {:X}", event_id);
|
||||||
|
|
||||||
if (event_id >= MaxNvEvents) {
|
if (event_id >= MaxNvEvents) {
|
||||||
return NvResult::BadParameter;
|
return NvResult::BadParameter;
|
||||||
}
|
}
|
||||||
if (events_interface.status[event_id] == EventState::Waiting) {
|
|
||||||
events_interface.LiberateEvent(event_id);
|
|
||||||
}
|
|
||||||
events_interface.failed[event_id] = true;
|
|
||||||
|
|
||||||
syncpoint_manager.RefreshSyncpoint(events_interface.events[event_id].fence.id);
|
auto lock = NvEventsLock();
|
||||||
|
|
||||||
|
auto& event = events[event_id];
|
||||||
|
if (event.status.exchange(EventState::Cancelling, std::memory_order_acq_rel) ==
|
||||||
|
EventState::Waiting) {
|
||||||
|
auto& host1x_syncpoint_manager = system.Host1x().GetSyncpointManager();
|
||||||
|
host1x_syncpoint_manager.DeregisterHostAction(event.assigned_syncpt, event.wait_handle);
|
||||||
|
syncpoint_manager.UpdateMin(event.assigned_syncpt);
|
||||||
|
event.wait_handle = {};
|
||||||
|
}
|
||||||
|
event.fails++;
|
||||||
|
event.status.store(EventState::Cancelled, std::memory_order_release);
|
||||||
|
event.kevent->GetWritableEvent().Clear();
|
||||||
|
|
||||||
return NvResult::Success;
|
return NvResult::Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Kernel::KEvent* nvhost_ctrl::QueryEvent(u32 event_id) {
|
||||||
|
const auto desired_event = SyncpointEventValue{.raw = event_id};
|
||||||
|
|
||||||
|
const bool allocated = desired_event.event_allocated.Value() != 0;
|
||||||
|
const u32 slot{allocated ? desired_event.partial_slot.Value()
|
||||||
|
: static_cast<u32>(desired_event.slot)};
|
||||||
|
if (slot >= MaxNvEvents) {
|
||||||
|
ASSERT(false);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
const u32 syncpoint_id{allocated ? desired_event.syncpoint_id_for_allocation.Value()
|
||||||
|
: desired_event.syncpoint_id.Value()};
|
||||||
|
|
||||||
|
auto lock = NvEventsLock();
|
||||||
|
|
||||||
|
auto& event = events[slot];
|
||||||
|
if (event.registered && event.assigned_syncpt == syncpoint_id) {
|
||||||
|
ASSERT(event.kevent);
|
||||||
|
return event.kevent;
|
||||||
|
}
|
||||||
|
// Is this possible in hardware?
|
||||||
|
ASSERT_MSG(false, "Slot:{}, SyncpointID:{}, requested", slot, syncpoint_id);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_lock<std::mutex> nvhost_ctrl::NvEventsLock() {
|
||||||
|
return std::unique_lock<std::mutex>(events_mutex);
|
||||||
|
}
|
||||||
|
|
||||||
|
void nvhost_ctrl::CreateNvEvent(u32 event_id) {
|
||||||
|
auto& event = events[event_id];
|
||||||
|
ASSERT(!event.kevent);
|
||||||
|
ASSERT(!event.registered);
|
||||||
|
ASSERT(!event.IsBeingUsed());
|
||||||
|
event.kevent = events_interface.CreateEvent(fmt::format("NVCTRL::NvEvent_{}", event_id));
|
||||||
|
event.status = EventState::Available;
|
||||||
|
event.registered = true;
|
||||||
|
const u64 mask = 1ULL << event_id;
|
||||||
|
event.fails = 0;
|
||||||
|
events_mask |= mask;
|
||||||
|
event.assigned_syncpt = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void nvhost_ctrl::FreeNvEvent(u32 event_id) {
|
||||||
|
auto& event = events[event_id];
|
||||||
|
ASSERT(event.kevent);
|
||||||
|
ASSERT(event.registered);
|
||||||
|
ASSERT(!event.IsBeingUsed());
|
||||||
|
events_interface.FreeEvent(event.kevent);
|
||||||
|
event.kevent = nullptr;
|
||||||
|
event.status = EventState::Available;
|
||||||
|
event.registered = false;
|
||||||
|
const u64 mask = ~(1ULL << event_id);
|
||||||
|
events_mask &= mask;
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 nvhost_ctrl::FindFreeNvEvent(u32 syncpoint_id) {
|
||||||
|
u32 slot{MaxNvEvents};
|
||||||
|
u32 free_slot{MaxNvEvents};
|
||||||
|
for (u32 i = 0; i < MaxNvEvents; i++) {
|
||||||
|
auto& event = events[i];
|
||||||
|
if (event.registered) {
|
||||||
|
if (!event.IsBeingUsed()) {
|
||||||
|
slot = i;
|
||||||
|
if (event.assigned_syncpt == syncpoint_id) {
|
||||||
|
return slot;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (free_slot == MaxNvEvents) {
|
||||||
|
free_slot = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (free_slot < MaxNvEvents) {
|
||||||
|
CreateNvEvent(free_slot);
|
||||||
|
return free_slot;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (slot < MaxNvEvents) {
|
||||||
|
return slot;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_CRITICAL(Service_NVDRV, "Failed to allocate an event");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace Service::Nvidia::Devices
|
} // namespace Service::Nvidia::Devices
|
||||||
|
|
|
@ -1,20 +1,28 @@
|
||||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
// SPDX-FileCopyrightText: 2021 yuzu Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-FileCopyrightText: 2021 Skyline Team and Contributors
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#include "common/bit_field.h"
|
||||||
#include "common/common_types.h"
|
#include "common/common_types.h"
|
||||||
#include "core/hle/service/nvdrv/devices/nvdevice.h"
|
#include "core/hle/service/nvdrv/devices/nvdevice.h"
|
||||||
#include "core/hle/service/nvdrv/nvdrv.h"
|
#include "core/hle/service/nvdrv/nvdrv.h"
|
||||||
|
#include "video_core/host1x/syncpoint_manager.h"
|
||||||
|
|
||||||
|
namespace Service::Nvidia::NvCore {
|
||||||
|
class Container;
|
||||||
|
class SyncpointManager;
|
||||||
|
} // namespace Service::Nvidia::NvCore
|
||||||
|
|
||||||
namespace Service::Nvidia::Devices {
|
namespace Service::Nvidia::Devices {
|
||||||
|
|
||||||
class nvhost_ctrl final : public nvdevice {
|
class nvhost_ctrl final : public nvdevice {
|
||||||
public:
|
public:
|
||||||
explicit nvhost_ctrl(Core::System& system_, EventInterface& events_interface_,
|
explicit nvhost_ctrl(Core::System& system_, EventInterface& events_interface_,
|
||||||
SyncpointManager& syncpoint_manager_);
|
NvCore::Container& core);
|
||||||
~nvhost_ctrl() override;
|
~nvhost_ctrl() override;
|
||||||
|
|
||||||
NvResult Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8>& input,
|
NvResult Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8>& input,
|
||||||
|
@ -27,7 +35,70 @@ public:
|
||||||
void OnOpen(DeviceFD fd) override;
|
void OnOpen(DeviceFD fd) override;
|
||||||
void OnClose(DeviceFD fd) override;
|
void OnClose(DeviceFD fd) override;
|
||||||
|
|
||||||
|
Kernel::KEvent* QueryEvent(u32 event_id) override;
|
||||||
|
|
||||||
|
union SyncpointEventValue {
|
||||||
|
u32 raw;
|
||||||
|
|
||||||
|
union {
|
||||||
|
BitField<0, 4, u32> partial_slot;
|
||||||
|
BitField<4, 28, u32> syncpoint_id;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct {
|
||||||
|
u16 slot;
|
||||||
|
union {
|
||||||
|
BitField<0, 12, u16> syncpoint_id_for_allocation;
|
||||||
|
BitField<12, 1, u16> event_allocated;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
static_assert(sizeof(SyncpointEventValue) == sizeof(u32));
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
struct InternalEvent {
|
||||||
|
// Mask representing registered events
|
||||||
|
|
||||||
|
// Each kernel event associated to an NV event
|
||||||
|
Kernel::KEvent* kevent{};
|
||||||
|
// The status of the current NVEvent
|
||||||
|
std::atomic<EventState> status{};
|
||||||
|
|
||||||
|
// Tells the NVEvent that it has failed.
|
||||||
|
u32 fails{};
|
||||||
|
// When an NVEvent is waiting on GPU interrupt, this is the sync_point
|
||||||
|
// associated with it.
|
||||||
|
u32 assigned_syncpt{};
|
||||||
|
// This is the value of the GPU interrupt for which the NVEvent is waiting
|
||||||
|
// for.
|
||||||
|
u32 assigned_value{};
|
||||||
|
|
||||||
|
// Tells if an NVEvent is registered or not
|
||||||
|
bool registered{};
|
||||||
|
|
||||||
|
// Used for waiting on a syncpoint & canceling it.
|
||||||
|
Tegra::Host1x::SyncpointManager::ActionHandle wait_handle{};
|
||||||
|
|
||||||
|
bool IsBeingUsed() const {
|
||||||
|
const auto current_status = status.load(std::memory_order_acquire);
|
||||||
|
return current_status == EventState::Waiting ||
|
||||||
|
current_status == EventState::Cancelling ||
|
||||||
|
current_status == EventState::Signalling;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
std::unique_lock<std::mutex> NvEventsLock();
|
||||||
|
|
||||||
|
void CreateNvEvent(u32 event_id);
|
||||||
|
|
||||||
|
void FreeNvEvent(u32 event_id);
|
||||||
|
|
||||||
|
u32 FindFreeNvEvent(u32 syncpoint_id);
|
||||||
|
|
||||||
|
std::array<InternalEvent, MaxNvEvents> events{};
|
||||||
|
std::mutex events_mutex;
|
||||||
|
u64 events_mask{};
|
||||||
|
|
||||||
struct IocSyncptReadParams {
|
struct IocSyncptReadParams {
|
||||||
u32_le id{};
|
u32_le id{};
|
||||||
u32_le value{};
|
u32_le value{};
|
||||||
|
@ -83,27 +154,18 @@ private:
|
||||||
};
|
};
|
||||||
static_assert(sizeof(IocGetConfigParams) == 387, "IocGetConfigParams is incorrect size");
|
static_assert(sizeof(IocGetConfigParams) == 387, "IocGetConfigParams is incorrect size");
|
||||||
|
|
||||||
struct IocCtrlEventSignalParams {
|
struct IocCtrlEventClearParams {
|
||||||
u32_le event_id{};
|
SyncpointEventValue event_id{};
|
||||||
};
|
};
|
||||||
static_assert(sizeof(IocCtrlEventSignalParams) == 4,
|
static_assert(sizeof(IocCtrlEventClearParams) == 4,
|
||||||
"IocCtrlEventSignalParams is incorrect size");
|
"IocCtrlEventClearParams is incorrect size");
|
||||||
|
|
||||||
struct IocCtrlEventWaitParams {
|
struct IocCtrlEventWaitParams {
|
||||||
u32_le syncpt_id{};
|
NvFence fence{};
|
||||||
u32_le threshold{};
|
|
||||||
s32_le timeout{};
|
|
||||||
u32_le value{};
|
|
||||||
};
|
|
||||||
static_assert(sizeof(IocCtrlEventWaitParams) == 16, "IocCtrlEventWaitParams is incorrect size");
|
|
||||||
|
|
||||||
struct IocCtrlEventWaitAsyncParams {
|
|
||||||
u32_le syncpt_id{};
|
|
||||||
u32_le threshold{};
|
|
||||||
u32_le timeout{};
|
u32_le timeout{};
|
||||||
u32_le value{};
|
SyncpointEventValue value{};
|
||||||
};
|
};
|
||||||
static_assert(sizeof(IocCtrlEventWaitAsyncParams) == 16,
|
static_assert(sizeof(IocCtrlEventWaitParams) == 16,
|
||||||
"IocCtrlEventWaitAsyncParams is incorrect size");
|
"IocCtrlEventWaitAsyncParams is incorrect size");
|
||||||
|
|
||||||
struct IocCtrlEventRegisterParams {
|
struct IocCtrlEventRegisterParams {
|
||||||
|
@ -118,19 +180,25 @@ private:
|
||||||
static_assert(sizeof(IocCtrlEventUnregisterParams) == 4,
|
static_assert(sizeof(IocCtrlEventUnregisterParams) == 4,
|
||||||
"IocCtrlEventUnregisterParams is incorrect size");
|
"IocCtrlEventUnregisterParams is incorrect size");
|
||||||
|
|
||||||
struct IocCtrlEventKill {
|
struct IocCtrlEventUnregisterBatchParams {
|
||||||
u64_le user_events{};
|
u64_le user_events{};
|
||||||
};
|
};
|
||||||
static_assert(sizeof(IocCtrlEventKill) == 8, "IocCtrlEventKill is incorrect size");
|
static_assert(sizeof(IocCtrlEventUnregisterBatchParams) == 8,
|
||||||
|
"IocCtrlEventKill is incorrect size");
|
||||||
|
|
||||||
NvResult NvOsGetConfigU32(const std::vector<u8>& input, std::vector<u8>& output);
|
NvResult NvOsGetConfigU32(const std::vector<u8>& input, std::vector<u8>& output);
|
||||||
NvResult IocCtrlEventWait(const std::vector<u8>& input, std::vector<u8>& output, bool is_async);
|
NvResult IocCtrlEventWait(const std::vector<u8>& input, std::vector<u8>& output,
|
||||||
|
bool is_allocation);
|
||||||
NvResult IocCtrlEventRegister(const std::vector<u8>& input, std::vector<u8>& output);
|
NvResult IocCtrlEventRegister(const std::vector<u8>& input, std::vector<u8>& output);
|
||||||
NvResult IocCtrlEventUnregister(const std::vector<u8>& input, std::vector<u8>& output);
|
NvResult IocCtrlEventUnregister(const std::vector<u8>& input, std::vector<u8>& output);
|
||||||
|
NvResult IocCtrlEventUnregisterBatch(const std::vector<u8>& input, std::vector<u8>& output);
|
||||||
NvResult IocCtrlClearEventWait(const std::vector<u8>& input, std::vector<u8>& output);
|
NvResult IocCtrlClearEventWait(const std::vector<u8>& input, std::vector<u8>& output);
|
||||||
|
|
||||||
|
NvResult FreeEvent(u32 slot);
|
||||||
|
|
||||||
EventInterface& events_interface;
|
EventInterface& events_interface;
|
||||||
SyncpointManager& syncpoint_manager;
|
NvCore::Container& core;
|
||||||
|
NvCore::SyncpointManager& syncpoint_manager;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Service::Nvidia::Devices
|
} // namespace Service::Nvidia::Devices
|
||||||
|
|
|
@ -7,11 +7,19 @@
|
||||||
#include "core/core.h"
|
#include "core/core.h"
|
||||||
#include "core/core_timing.h"
|
#include "core/core_timing.h"
|
||||||
#include "core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.h"
|
#include "core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.h"
|
||||||
|
#include "core/hle/service/nvdrv/nvdrv.h"
|
||||||
|
|
||||||
namespace Service::Nvidia::Devices {
|
namespace Service::Nvidia::Devices {
|
||||||
|
|
||||||
nvhost_ctrl_gpu::nvhost_ctrl_gpu(Core::System& system_) : nvdevice{system_} {}
|
nvhost_ctrl_gpu::nvhost_ctrl_gpu(Core::System& system_, EventInterface& events_interface_)
|
||||||
nvhost_ctrl_gpu::~nvhost_ctrl_gpu() = default;
|
: nvdevice{system_}, events_interface{events_interface_} {
|
||||||
|
error_notifier_event = events_interface.CreateEvent("CtrlGpuErrorNotifier");
|
||||||
|
unknown_event = events_interface.CreateEvent("CtrlGpuUknownEvent");
|
||||||
|
}
|
||||||
|
nvhost_ctrl_gpu::~nvhost_ctrl_gpu() {
|
||||||
|
events_interface.FreeEvent(error_notifier_event);
|
||||||
|
events_interface.FreeEvent(unknown_event);
|
||||||
|
}
|
||||||
|
|
||||||
NvResult nvhost_ctrl_gpu::Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8>& input,
|
NvResult nvhost_ctrl_gpu::Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8>& input,
|
||||||
std::vector<u8>& output) {
|
std::vector<u8>& output) {
|
||||||
|
@ -286,4 +294,17 @@ NvResult nvhost_ctrl_gpu::GetGpuTime(const std::vector<u8>& input, std::vector<u
|
||||||
return NvResult::Success;
|
return NvResult::Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Kernel::KEvent* nvhost_ctrl_gpu::QueryEvent(u32 event_id) {
|
||||||
|
switch (event_id) {
|
||||||
|
case 1:
|
||||||
|
return error_notifier_event;
|
||||||
|
case 2:
|
||||||
|
return unknown_event;
|
||||||
|
default: {
|
||||||
|
LOG_CRITICAL(Service_NVDRV, "Unknown Ctrl GPU Event {}", event_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace Service::Nvidia::Devices
|
} // namespace Service::Nvidia::Devices
|
||||||
|
|
|
@ -10,11 +10,15 @@
|
||||||
#include "common/swap.h"
|
#include "common/swap.h"
|
||||||
#include "core/hle/service/nvdrv/devices/nvdevice.h"
|
#include "core/hle/service/nvdrv/devices/nvdevice.h"
|
||||||
|
|
||||||
|
namespace Service::Nvidia {
|
||||||
|
class EventInterface;
|
||||||
|
}
|
||||||
|
|
||||||
namespace Service::Nvidia::Devices {
|
namespace Service::Nvidia::Devices {
|
||||||
|
|
||||||
class nvhost_ctrl_gpu final : public nvdevice {
|
class nvhost_ctrl_gpu final : public nvdevice {
|
||||||
public:
|
public:
|
||||||
explicit nvhost_ctrl_gpu(Core::System& system_);
|
explicit nvhost_ctrl_gpu(Core::System& system_, EventInterface& events_interface_);
|
||||||
~nvhost_ctrl_gpu() override;
|
~nvhost_ctrl_gpu() override;
|
||||||
|
|
||||||
NvResult Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8>& input,
|
NvResult Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8>& input,
|
||||||
|
@ -27,6 +31,8 @@ public:
|
||||||
void OnOpen(DeviceFD fd) override;
|
void OnOpen(DeviceFD fd) override;
|
||||||
void OnClose(DeviceFD fd) override;
|
void OnClose(DeviceFD fd) override;
|
||||||
|
|
||||||
|
Kernel::KEvent* QueryEvent(u32 event_id) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct IoctlGpuCharacteristics {
|
struct IoctlGpuCharacteristics {
|
||||||
u32_le arch; // 0x120 (NVGPU_GPU_ARCH_GM200)
|
u32_le arch; // 0x120 (NVGPU_GPU_ARCH_GM200)
|
||||||
|
@ -160,6 +166,12 @@ private:
|
||||||
NvResult ZBCQueryTable(const std::vector<u8>& input, std::vector<u8>& output);
|
NvResult ZBCQueryTable(const std::vector<u8>& input, std::vector<u8>& output);
|
||||||
NvResult FlushL2(const std::vector<u8>& input, std::vector<u8>& output);
|
NvResult FlushL2(const std::vector<u8>& input, std::vector<u8>& output);
|
||||||
NvResult GetGpuTime(const std::vector<u8>& input, std::vector<u8>& output);
|
NvResult GetGpuTime(const std::vector<u8>& input, std::vector<u8>& output);
|
||||||
|
|
||||||
|
EventInterface& events_interface;
|
||||||
|
|
||||||
|
// Events
|
||||||
|
Kernel::KEvent* error_notifier_event;
|
||||||
|
Kernel::KEvent* unknown_event;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Service::Nvidia::Devices
|
} // namespace Service::Nvidia::Devices
|
||||||
|
|
|
@ -5,29 +5,46 @@
|
||||||
#include "common/assert.h"
|
#include "common/assert.h"
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
#include "core/core.h"
|
#include "core/core.h"
|
||||||
|
#include "core/hle/service/nvdrv/core/container.h"
|
||||||
|
#include "core/hle/service/nvdrv/core/nvmap.h"
|
||||||
|
#include "core/hle/service/nvdrv/core/syncpoint_manager.h"
|
||||||
#include "core/hle/service/nvdrv/devices/nvhost_gpu.h"
|
#include "core/hle/service/nvdrv/devices/nvhost_gpu.h"
|
||||||
#include "core/hle/service/nvdrv/syncpoint_manager.h"
|
#include "core/hle/service/nvdrv/nvdrv.h"
|
||||||
#include "core/memory.h"
|
#include "core/memory.h"
|
||||||
|
#include "video_core/control/channel_state.h"
|
||||||
|
#include "video_core/engines/puller.h"
|
||||||
#include "video_core/gpu.h"
|
#include "video_core/gpu.h"
|
||||||
|
#include "video_core/host1x/host1x.h"
|
||||||
|
|
||||||
namespace Service::Nvidia::Devices {
|
namespace Service::Nvidia::Devices {
|
||||||
namespace {
|
namespace {
|
||||||
Tegra::CommandHeader BuildFenceAction(Tegra::GPU::FenceOperation op, u32 syncpoint_id) {
|
Tegra::CommandHeader BuildFenceAction(Tegra::Engines::Puller::FenceOperation op, u32 syncpoint_id) {
|
||||||
Tegra::GPU::FenceAction result{};
|
Tegra::Engines::Puller::FenceAction result{};
|
||||||
result.op.Assign(op);
|
result.op.Assign(op);
|
||||||
result.syncpoint_id.Assign(syncpoint_id);
|
result.syncpoint_id.Assign(syncpoint_id);
|
||||||
return {result.raw};
|
return {result.raw};
|
||||||
}
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
nvhost_gpu::nvhost_gpu(Core::System& system_, std::shared_ptr<nvmap> nvmap_dev_,
|
nvhost_gpu::nvhost_gpu(Core::System& system_, EventInterface& events_interface_,
|
||||||
SyncpointManager& syncpoint_manager_)
|
NvCore::Container& core_)
|
||||||
: nvdevice{system_}, nvmap_dev{std::move(nvmap_dev_)}, syncpoint_manager{syncpoint_manager_} {
|
: nvdevice{system_}, events_interface{events_interface_}, core{core_},
|
||||||
channel_fence.id = syncpoint_manager_.AllocateSyncpoint();
|
syncpoint_manager{core_.GetSyncpointManager()}, nvmap{core.GetNvMapFile()},
|
||||||
channel_fence.value = system_.GPU().GetSyncpointValue(channel_fence.id);
|
channel_state{system.GPU().AllocateChannel()} {
|
||||||
|
channel_syncpoint = syncpoint_manager.AllocateSyncpoint(false);
|
||||||
|
sm_exception_breakpoint_int_report_event =
|
||||||
|
events_interface.CreateEvent("GpuChannelSMExceptionBreakpointInt");
|
||||||
|
sm_exception_breakpoint_pause_report_event =
|
||||||
|
events_interface.CreateEvent("GpuChannelSMExceptionBreakpointPause");
|
||||||
|
error_notifier_event = events_interface.CreateEvent("GpuChannelErrorNotifier");
|
||||||
}
|
}
|
||||||
|
|
||||||
nvhost_gpu::~nvhost_gpu() = default;
|
nvhost_gpu::~nvhost_gpu() {
|
||||||
|
events_interface.FreeEvent(sm_exception_breakpoint_int_report_event);
|
||||||
|
events_interface.FreeEvent(sm_exception_breakpoint_pause_report_event);
|
||||||
|
events_interface.FreeEvent(error_notifier_event);
|
||||||
|
syncpoint_manager.FreeSyncpoint(channel_syncpoint);
|
||||||
|
}
|
||||||
|
|
||||||
NvResult nvhost_gpu::Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8>& input,
|
NvResult nvhost_gpu::Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8>& input,
|
||||||
std::vector<u8>& output) {
|
std::vector<u8>& output) {
|
||||||
|
@ -167,9 +184,14 @@ NvResult nvhost_gpu::AllocGPFIFOEx2(const std::vector<u8>& input, std::vector<u8
|
||||||
params.num_entries, params.flags, params.unk0, params.unk1, params.unk2,
|
params.num_entries, params.flags, params.unk0, params.unk1, params.unk2,
|
||||||
params.unk3);
|
params.unk3);
|
||||||
|
|
||||||
channel_fence.value = system.GPU().GetSyncpointValue(channel_fence.id);
|
if (channel_state->initialized) {
|
||||||
|
LOG_CRITICAL(Service_NVDRV, "Already allocated!");
|
||||||
|
return NvResult::AlreadyAllocated;
|
||||||
|
}
|
||||||
|
|
||||||
params.fence_out = channel_fence;
|
system.GPU().InitChannel(*channel_state);
|
||||||
|
|
||||||
|
params.fence_out = syncpoint_manager.GetSyncpointFence(channel_syncpoint);
|
||||||
|
|
||||||
std::memcpy(output.data(), ¶ms, output.size());
|
std::memcpy(output.data(), ¶ms, output.size());
|
||||||
return NvResult::Success;
|
return NvResult::Success;
|
||||||
|
@ -188,39 +210,37 @@ NvResult nvhost_gpu::AllocateObjectContext(const std::vector<u8>& input, std::ve
|
||||||
|
|
||||||
static std::vector<Tegra::CommandHeader> BuildWaitCommandList(NvFence fence) {
|
static std::vector<Tegra::CommandHeader> BuildWaitCommandList(NvFence fence) {
|
||||||
return {
|
return {
|
||||||
Tegra::BuildCommandHeader(Tegra::BufferMethods::FenceValue, 1,
|
Tegra::BuildCommandHeader(Tegra::BufferMethods::SyncpointPayload, 1,
|
||||||
Tegra::SubmissionMode::Increasing),
|
Tegra::SubmissionMode::Increasing),
|
||||||
{fence.value},
|
{fence.value},
|
||||||
Tegra::BuildCommandHeader(Tegra::BufferMethods::FenceAction, 1,
|
Tegra::BuildCommandHeader(Tegra::BufferMethods::SyncpointOperation, 1,
|
||||||
Tegra::SubmissionMode::Increasing),
|
Tegra::SubmissionMode::Increasing),
|
||||||
BuildFenceAction(Tegra::GPU::FenceOperation::Acquire, fence.id),
|
BuildFenceAction(Tegra::Engines::Puller::FenceOperation::Acquire, fence.id),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
static std::vector<Tegra::CommandHeader> BuildIncrementCommandList(NvFence fence,
|
static std::vector<Tegra::CommandHeader> BuildIncrementCommandList(NvFence fence) {
|
||||||
u32 add_increment) {
|
|
||||||
std::vector<Tegra::CommandHeader> result{
|
std::vector<Tegra::CommandHeader> result{
|
||||||
Tegra::BuildCommandHeader(Tegra::BufferMethods::FenceValue, 1,
|
Tegra::BuildCommandHeader(Tegra::BufferMethods::SyncpointPayload, 1,
|
||||||
Tegra::SubmissionMode::Increasing),
|
Tegra::SubmissionMode::Increasing),
|
||||||
{}};
|
{}};
|
||||||
|
|
||||||
for (u32 count = 0; count < add_increment; ++count) {
|
for (u32 count = 0; count < 2; ++count) {
|
||||||
result.emplace_back(Tegra::BuildCommandHeader(Tegra::BufferMethods::FenceAction, 1,
|
result.emplace_back(Tegra::BuildCommandHeader(Tegra::BufferMethods::SyncpointOperation, 1,
|
||||||
Tegra::SubmissionMode::Increasing));
|
Tegra::SubmissionMode::Increasing));
|
||||||
result.emplace_back(BuildFenceAction(Tegra::GPU::FenceOperation::Increment, fence.id));
|
result.emplace_back(
|
||||||
|
BuildFenceAction(Tegra::Engines::Puller::FenceOperation::Increment, fence.id));
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
static std::vector<Tegra::CommandHeader> BuildIncrementWithWfiCommandList(NvFence fence,
|
static std::vector<Tegra::CommandHeader> BuildIncrementWithWfiCommandList(NvFence fence) {
|
||||||
u32 add_increment) {
|
|
||||||
std::vector<Tegra::CommandHeader> result{
|
std::vector<Tegra::CommandHeader> result{
|
||||||
Tegra::BuildCommandHeader(Tegra::BufferMethods::WaitForInterrupt, 1,
|
Tegra::BuildCommandHeader(Tegra::BufferMethods::WaitForIdle, 1,
|
||||||
Tegra::SubmissionMode::Increasing),
|
Tegra::SubmissionMode::Increasing),
|
||||||
{}};
|
{}};
|
||||||
const std::vector<Tegra::CommandHeader> increment{
|
const std::vector<Tegra::CommandHeader> increment{BuildIncrementCommandList(fence)};
|
||||||
BuildIncrementCommandList(fence, add_increment)};
|
|
||||||
|
|
||||||
result.insert(result.end(), increment.begin(), increment.end());
|
result.insert(result.end(), increment.begin(), increment.end());
|
||||||
|
|
||||||
|
@ -234,32 +254,40 @@ NvResult nvhost_gpu::SubmitGPFIFOImpl(IoctlSubmitGpfifo& params, std::vector<u8>
|
||||||
|
|
||||||
auto& gpu = system.GPU();
|
auto& gpu = system.GPU();
|
||||||
|
|
||||||
params.fence_out.id = channel_fence.id;
|
std::scoped_lock lock(channel_mutex);
|
||||||
|
|
||||||
if (params.flags.add_wait.Value() &&
|
const auto bind_id = channel_state->bind_id;
|
||||||
!syncpoint_manager.IsSyncpointExpired(params.fence_out.id, params.fence_out.value)) {
|
|
||||||
gpu.PushGPUEntries(Tegra::CommandList{BuildWaitCommandList(params.fence_out)});
|
auto& flags = params.flags;
|
||||||
|
|
||||||
|
if (flags.fence_wait.Value()) {
|
||||||
|
if (flags.increment_value.Value()) {
|
||||||
|
return NvResult::BadParameter;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (params.flags.add_increment.Value() || params.flags.increment.Value()) {
|
if (!syncpoint_manager.IsFenceSignalled(params.fence)) {
|
||||||
const u32 increment_value = params.flags.increment.Value() ? params.fence_out.value : 0;
|
gpu.PushGPUEntries(bind_id, Tegra::CommandList{BuildWaitCommandList(params.fence)});
|
||||||
params.fence_out.value = syncpoint_manager.IncreaseSyncpoint(
|
}
|
||||||
params.fence_out.id, params.AddIncrementValue() + increment_value);
|
}
|
||||||
|
|
||||||
|
params.fence.id = channel_syncpoint;
|
||||||
|
|
||||||
|
u32 increment{(flags.fence_increment.Value() != 0 ? 2 : 0) +
|
||||||
|
(flags.increment_value.Value() != 0 ? params.fence.value : 0)};
|
||||||
|
params.fence.value = syncpoint_manager.IncrementSyncpointMaxExt(channel_syncpoint, increment);
|
||||||
|
gpu.PushGPUEntries(bind_id, std::move(entries));
|
||||||
|
|
||||||
|
if (flags.fence_increment.Value()) {
|
||||||
|
if (flags.suppress_wfi.Value()) {
|
||||||
|
gpu.PushGPUEntries(bind_id,
|
||||||
|
Tegra::CommandList{BuildIncrementCommandList(params.fence)});
|
||||||
} else {
|
} else {
|
||||||
params.fence_out.value = syncpoint_manager.GetSyncpointMax(params.fence_out.id);
|
gpu.PushGPUEntries(bind_id,
|
||||||
|
Tegra::CommandList{BuildIncrementWithWfiCommandList(params.fence)});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
gpu.PushGPUEntries(std::move(entries));
|
flags.raw = 0;
|
||||||
|
|
||||||
if (params.flags.add_increment.Value()) {
|
|
||||||
if (params.flags.suppress_wfi) {
|
|
||||||
gpu.PushGPUEntries(Tegra::CommandList{
|
|
||||||
BuildIncrementCommandList(params.fence_out, params.AddIncrementValue())});
|
|
||||||
} else {
|
|
||||||
gpu.PushGPUEntries(Tegra::CommandList{
|
|
||||||
BuildIncrementWithWfiCommandList(params.fence_out, params.AddIncrementValue())});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::memcpy(output.data(), ¶ms, sizeof(IoctlSubmitGpfifo));
|
std::memcpy(output.data(), ¶ms, sizeof(IoctlSubmitGpfifo));
|
||||||
return NvResult::Success;
|
return NvResult::Success;
|
||||||
|
@ -328,4 +356,19 @@ NvResult nvhost_gpu::ChannelSetTimeslice(const std::vector<u8>& input, std::vect
|
||||||
return NvResult::Success;
|
return NvResult::Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Kernel::KEvent* nvhost_gpu::QueryEvent(u32 event_id) {
|
||||||
|
switch (event_id) {
|
||||||
|
case 1:
|
||||||
|
return sm_exception_breakpoint_int_report_event;
|
||||||
|
case 2:
|
||||||
|
return sm_exception_breakpoint_pause_report_event;
|
||||||
|
case 3:
|
||||||
|
return error_notifier_event;
|
||||||
|
default: {
|
||||||
|
LOG_CRITICAL(Service_NVDRV, "Unknown Ctrl GPU Event {}", event_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace Service::Nvidia::Devices
|
} // namespace Service::Nvidia::Devices
|
||||||
|
|
|
@ -13,17 +13,31 @@
|
||||||
#include "core/hle/service/nvdrv/nvdata.h"
|
#include "core/hle/service/nvdrv/nvdata.h"
|
||||||
#include "video_core/dma_pusher.h"
|
#include "video_core/dma_pusher.h"
|
||||||
|
|
||||||
namespace Service::Nvidia {
|
namespace Tegra {
|
||||||
class SyncpointManager;
|
namespace Control {
|
||||||
|
struct ChannelState;
|
||||||
}
|
}
|
||||||
|
} // namespace Tegra
|
||||||
|
|
||||||
|
namespace Service::Nvidia {
|
||||||
|
|
||||||
|
namespace NvCore {
|
||||||
|
class Container;
|
||||||
|
class NvMap;
|
||||||
|
class SyncpointManager;
|
||||||
|
} // namespace NvCore
|
||||||
|
|
||||||
|
class EventInterface;
|
||||||
|
} // namespace Service::Nvidia
|
||||||
|
|
||||||
namespace Service::Nvidia::Devices {
|
namespace Service::Nvidia::Devices {
|
||||||
|
|
||||||
|
class nvhost_as_gpu;
|
||||||
class nvmap;
|
class nvmap;
|
||||||
class nvhost_gpu final : public nvdevice {
|
class nvhost_gpu final : public nvdevice {
|
||||||
public:
|
public:
|
||||||
explicit nvhost_gpu(Core::System& system_, std::shared_ptr<nvmap> nvmap_dev_,
|
explicit nvhost_gpu(Core::System& system_, EventInterface& events_interface_,
|
||||||
SyncpointManager& syncpoint_manager_);
|
NvCore::Container& core);
|
||||||
~nvhost_gpu() override;
|
~nvhost_gpu() override;
|
||||||
|
|
||||||
NvResult Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8>& input,
|
NvResult Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8>& input,
|
||||||
|
@ -36,7 +50,10 @@ public:
|
||||||
void OnOpen(DeviceFD fd) override;
|
void OnOpen(DeviceFD fd) override;
|
||||||
void OnClose(DeviceFD fd) override;
|
void OnClose(DeviceFD fd) override;
|
||||||
|
|
||||||
|
Kernel::KEvent* QueryEvent(u32 event_id) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
friend class nvhost_as_gpu;
|
||||||
enum class CtxObjects : u32_le {
|
enum class CtxObjects : u32_le {
|
||||||
Ctx2D = 0x902D,
|
Ctx2D = 0x902D,
|
||||||
Ctx3D = 0xB197,
|
Ctx3D = 0xB197,
|
||||||
|
@ -146,17 +163,13 @@ private:
|
||||||
u32_le num_entries{}; // number of fence objects being submitted
|
u32_le num_entries{}; // number of fence objects being submitted
|
||||||
union {
|
union {
|
||||||
u32_le raw;
|
u32_le raw;
|
||||||
BitField<0, 1, u32_le> add_wait; // append a wait sync_point to the list
|
BitField<0, 1, u32_le> fence_wait; // append a wait sync_point to the list
|
||||||
BitField<1, 1, u32_le> add_increment; // append an increment to the list
|
BitField<1, 1, u32_le> fence_increment; // append an increment to the list
|
||||||
BitField<2, 1, u32_le> new_hw_format; // mostly ignored
|
BitField<2, 1, u32_le> new_hw_format; // mostly ignored
|
||||||
BitField<4, 1, u32_le> suppress_wfi; // suppress wait for interrupt
|
BitField<4, 1, u32_le> suppress_wfi; // suppress wait for interrupt
|
||||||
BitField<8, 1, u32_le> increment; // increment the returned fence
|
BitField<8, 1, u32_le> increment_value; // increment the returned fence
|
||||||
} flags;
|
} flags;
|
||||||
NvFence fence_out{}; // returned new fence object for others to wait on
|
NvFence fence{}; // returned new fence object for others to wait on
|
||||||
|
|
||||||
u32 AddIncrementValue() const {
|
|
||||||
return flags.add_increment.Value() << 1;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
static_assert(sizeof(IoctlSubmitGpfifo) == 16 + sizeof(NvFence),
|
static_assert(sizeof(IoctlSubmitGpfifo) == 16 + sizeof(NvFence),
|
||||||
"IoctlSubmitGpfifo is incorrect size");
|
"IoctlSubmitGpfifo is incorrect size");
|
||||||
|
@ -191,9 +204,18 @@ private:
|
||||||
NvResult ChannelSetTimeout(const std::vector<u8>& input, std::vector<u8>& output);
|
NvResult ChannelSetTimeout(const std::vector<u8>& input, std::vector<u8>& output);
|
||||||
NvResult ChannelSetTimeslice(const std::vector<u8>& input, std::vector<u8>& output);
|
NvResult ChannelSetTimeslice(const std::vector<u8>& input, std::vector<u8>& output);
|
||||||
|
|
||||||
std::shared_ptr<nvmap> nvmap_dev;
|
EventInterface& events_interface;
|
||||||
SyncpointManager& syncpoint_manager;
|
NvCore::Container& core;
|
||||||
NvFence channel_fence;
|
NvCore::SyncpointManager& syncpoint_manager;
|
||||||
|
NvCore::NvMap& nvmap;
|
||||||
|
std::shared_ptr<Tegra::Control::ChannelState> channel_state;
|
||||||
|
u32 channel_syncpoint;
|
||||||
|
std::mutex channel_mutex;
|
||||||
|
|
||||||
|
// Events
|
||||||
|
Kernel::KEvent* sm_exception_breakpoint_int_report_event;
|
||||||
|
Kernel::KEvent* sm_exception_breakpoint_pause_report_event;
|
||||||
|
Kernel::KEvent* error_notifier_event;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Service::Nvidia::Devices
|
} // namespace Service::Nvidia::Devices
|
||||||
|
|
|
@ -5,14 +5,14 @@
|
||||||
#include "common/assert.h"
|
#include "common/assert.h"
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
#include "core/core.h"
|
#include "core/core.h"
|
||||||
|
#include "core/hle/service/nvdrv/core/container.h"
|
||||||
#include "core/hle/service/nvdrv/devices/nvhost_nvdec.h"
|
#include "core/hle/service/nvdrv/devices/nvhost_nvdec.h"
|
||||||
#include "video_core/renderer_base.h"
|
#include "video_core/renderer_base.h"
|
||||||
|
|
||||||
namespace Service::Nvidia::Devices {
|
namespace Service::Nvidia::Devices {
|
||||||
|
|
||||||
nvhost_nvdec::nvhost_nvdec(Core::System& system_, std::shared_ptr<nvmap> nvmap_dev_,
|
nvhost_nvdec::nvhost_nvdec(Core::System& system_, NvCore::Container& core_)
|
||||||
SyncpointManager& syncpoint_manager_)
|
: nvhost_nvdec_common{system_, core_, NvCore::ChannelType::NvDec} {}
|
||||||
: nvhost_nvdec_common{system_, std::move(nvmap_dev_), syncpoint_manager_} {}
|
|
||||||
nvhost_nvdec::~nvhost_nvdec() = default;
|
nvhost_nvdec::~nvhost_nvdec() = default;
|
||||||
|
|
||||||
NvResult nvhost_nvdec::Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8>& input,
|
NvResult nvhost_nvdec::Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8>& input,
|
||||||
|
@ -21,8 +21,9 @@ NvResult nvhost_nvdec::Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8>&
|
||||||
case 0x0:
|
case 0x0:
|
||||||
switch (command.cmd) {
|
switch (command.cmd) {
|
||||||
case 0x1: {
|
case 0x1: {
|
||||||
if (!fd_to_id.contains(fd)) {
|
auto& host1x_file = core.Host1xDeviceFile();
|
||||||
fd_to_id[fd] = next_id++;
|
if (!host1x_file.fd_to_id.contains(fd)) {
|
||||||
|
host1x_file.fd_to_id[fd] = host1x_file.nvdec_next_id++;
|
||||||
}
|
}
|
||||||
return Submit(fd, input, output);
|
return Submit(fd, input, output);
|
||||||
}
|
}
|
||||||
|
@ -73,8 +74,9 @@ void nvhost_nvdec::OnOpen(DeviceFD fd) {
|
||||||
|
|
||||||
void nvhost_nvdec::OnClose(DeviceFD fd) {
|
void nvhost_nvdec::OnClose(DeviceFD fd) {
|
||||||
LOG_INFO(Service_NVDRV, "NVDEC video stream ended");
|
LOG_INFO(Service_NVDRV, "NVDEC video stream ended");
|
||||||
const auto iter = fd_to_id.find(fd);
|
auto& host1x_file = core.Host1xDeviceFile();
|
||||||
if (iter != fd_to_id.end()) {
|
const auto iter = host1x_file.fd_to_id.find(fd);
|
||||||
|
if (iter != host1x_file.fd_to_id.end()) {
|
||||||
system.GPU().ClearCdmaInstance(iter->second);
|
system.GPU().ClearCdmaInstance(iter->second);
|
||||||
}
|
}
|
||||||
system.AudioCore().SetNVDECActive(false);
|
system.AudioCore().SetNVDECActive(false);
|
||||||
|
|
|
@ -10,8 +10,7 @@ namespace Service::Nvidia::Devices {
|
||||||
|
|
||||||
class nvhost_nvdec final : public nvhost_nvdec_common {
|
class nvhost_nvdec final : public nvhost_nvdec_common {
|
||||||
public:
|
public:
|
||||||
explicit nvhost_nvdec(Core::System& system_, std::shared_ptr<nvmap> nvmap_dev_,
|
explicit nvhost_nvdec(Core::System& system_, NvCore::Container& core);
|
||||||
SyncpointManager& syncpoint_manager_);
|
|
||||||
~nvhost_nvdec() override;
|
~nvhost_nvdec() override;
|
||||||
|
|
||||||
NvResult Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8>& input,
|
NvResult Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8>& input,
|
||||||
|
@ -23,9 +22,6 @@ public:
|
||||||
|
|
||||||
void OnOpen(DeviceFD fd) override;
|
void OnOpen(DeviceFD fd) override;
|
||||||
void OnClose(DeviceFD fd) override;
|
void OnClose(DeviceFD fd) override;
|
||||||
|
|
||||||
private:
|
|
||||||
u32 next_id{};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Service::Nvidia::Devices
|
} // namespace Service::Nvidia::Devices
|
||||||
|
|
|
@ -8,10 +8,12 @@
|
||||||
#include "common/common_types.h"
|
#include "common/common_types.h"
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
#include "core/core.h"
|
#include "core/core.h"
|
||||||
|
#include "core/hle/service/nvdrv/core/container.h"
|
||||||
|
#include "core/hle/service/nvdrv/core/nvmap.h"
|
||||||
|
#include "core/hle/service/nvdrv/core/syncpoint_manager.h"
|
||||||
#include "core/hle/service/nvdrv/devices/nvhost_nvdec_common.h"
|
#include "core/hle/service/nvdrv/devices/nvhost_nvdec_common.h"
|
||||||
#include "core/hle/service/nvdrv/devices/nvmap.h"
|
|
||||||
#include "core/hle/service/nvdrv/syncpoint_manager.h"
|
|
||||||
#include "core/memory.h"
|
#include "core/memory.h"
|
||||||
|
#include "video_core/host1x/host1x.h"
|
||||||
#include "video_core/memory_manager.h"
|
#include "video_core/memory_manager.h"
|
||||||
#include "video_core/renderer_base.h"
|
#include "video_core/renderer_base.h"
|
||||||
|
|
||||||
|
@ -44,10 +46,22 @@ std::size_t WriteVectors(std::vector<u8>& dst, const std::vector<T>& src, std::s
|
||||||
}
|
}
|
||||||
} // Anonymous namespace
|
} // Anonymous namespace
|
||||||
|
|
||||||
nvhost_nvdec_common::nvhost_nvdec_common(Core::System& system_, std::shared_ptr<nvmap> nvmap_dev_,
|
nvhost_nvdec_common::nvhost_nvdec_common(Core::System& system_, NvCore::Container& core_,
|
||||||
SyncpointManager& syncpoint_manager_)
|
NvCore::ChannelType channel_type_)
|
||||||
: nvdevice{system_}, nvmap_dev{std::move(nvmap_dev_)}, syncpoint_manager{syncpoint_manager_} {}
|
: nvdevice{system_}, core{core_}, syncpoint_manager{core.GetSyncpointManager()},
|
||||||
nvhost_nvdec_common::~nvhost_nvdec_common() = default;
|
nvmap{core.GetNvMapFile()}, channel_type{channel_type_} {
|
||||||
|
auto& syncpts_accumulated = core.Host1xDeviceFile().syncpts_accumulated;
|
||||||
|
if (syncpts_accumulated.empty()) {
|
||||||
|
channel_syncpoint = syncpoint_manager.AllocateSyncpoint(false);
|
||||||
|
} else {
|
||||||
|
channel_syncpoint = syncpts_accumulated.front();
|
||||||
|
syncpts_accumulated.pop_front();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
nvhost_nvdec_common::~nvhost_nvdec_common() {
|
||||||
|
core.Host1xDeviceFile().syncpts_accumulated.push_back(channel_syncpoint);
|
||||||
|
}
|
||||||
|
|
||||||
NvResult nvhost_nvdec_common::SetNVMAPfd(const std::vector<u8>& input) {
|
NvResult nvhost_nvdec_common::SetNVMAPfd(const std::vector<u8>& input) {
|
||||||
IoctlSetNvmapFD params{};
|
IoctlSetNvmapFD params{};
|
||||||
|
@ -84,16 +98,16 @@ NvResult nvhost_nvdec_common::Submit(DeviceFD fd, const std::vector<u8>& input,
|
||||||
for (std::size_t i = 0; i < syncpt_increments.size(); i++) {
|
for (std::size_t i = 0; i < syncpt_increments.size(); i++) {
|
||||||
const SyncptIncr& syncpt_incr = syncpt_increments[i];
|
const SyncptIncr& syncpt_incr = syncpt_increments[i];
|
||||||
fence_thresholds[i] =
|
fence_thresholds[i] =
|
||||||
syncpoint_manager.IncreaseSyncpoint(syncpt_incr.id, syncpt_incr.increments);
|
syncpoint_manager.IncrementSyncpointMaxExt(syncpt_incr.id, syncpt_incr.increments);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (const auto& cmd_buffer : command_buffers) {
|
for (const auto& cmd_buffer : command_buffers) {
|
||||||
const auto object = nvmap_dev->GetObject(cmd_buffer.memory_id);
|
const auto object = nvmap.GetHandle(cmd_buffer.memory_id);
|
||||||
ASSERT_OR_EXECUTE(object, return NvResult::InvalidState;);
|
ASSERT_OR_EXECUTE(object, return NvResult::InvalidState;);
|
||||||
Tegra::ChCommandHeaderList cmdlist(cmd_buffer.word_count);
|
Tegra::ChCommandHeaderList cmdlist(cmd_buffer.word_count);
|
||||||
system.Memory().ReadBlock(object->addr + cmd_buffer.offset, cmdlist.data(),
|
system.Memory().ReadBlock(object->address + cmd_buffer.offset, cmdlist.data(),
|
||||||
cmdlist.size() * sizeof(u32));
|
cmdlist.size() * sizeof(u32));
|
||||||
gpu.PushCommandBuffer(fd_to_id[fd], cmdlist);
|
gpu.PushCommandBuffer(core.Host1xDeviceFile().fd_to_id[fd], cmdlist);
|
||||||
}
|
}
|
||||||
std::memcpy(output.data(), ¶ms, sizeof(IoctlSubmit));
|
std::memcpy(output.data(), ¶ms, sizeof(IoctlSubmit));
|
||||||
// Some games expect command_buffers to be written back
|
// Some games expect command_buffers to be written back
|
||||||
|
@ -112,10 +126,8 @@ NvResult nvhost_nvdec_common::GetSyncpoint(const std::vector<u8>& input, std::ve
|
||||||
std::memcpy(¶ms, input.data(), sizeof(IoctlGetSyncpoint));
|
std::memcpy(¶ms, input.data(), sizeof(IoctlGetSyncpoint));
|
||||||
LOG_DEBUG(Service_NVDRV, "called GetSyncpoint, id={}", params.param);
|
LOG_DEBUG(Service_NVDRV, "called GetSyncpoint, id={}", params.param);
|
||||||
|
|
||||||
if (device_syncpoints[params.param] == 0 && system.GPU().UseNvdec()) {
|
// const u32 id{NvCore::SyncpointManager::channel_syncpoints[static_cast<u32>(channel_type)]};
|
||||||
device_syncpoints[params.param] = syncpoint_manager.AllocateSyncpoint();
|
params.value = channel_syncpoint;
|
||||||
}
|
|
||||||
params.value = device_syncpoints[params.param];
|
|
||||||
std::memcpy(output.data(), ¶ms, sizeof(IoctlGetSyncpoint));
|
std::memcpy(output.data(), ¶ms, sizeof(IoctlGetSyncpoint));
|
||||||
|
|
||||||
return NvResult::Success;
|
return NvResult::Success;
|
||||||
|
@ -123,6 +135,7 @@ NvResult nvhost_nvdec_common::GetSyncpoint(const std::vector<u8>& input, std::ve
|
||||||
|
|
||||||
NvResult nvhost_nvdec_common::GetWaitbase(const std::vector<u8>& input, std::vector<u8>& output) {
|
NvResult nvhost_nvdec_common::GetWaitbase(const std::vector<u8>& input, std::vector<u8>& output) {
|
||||||
IoctlGetWaitbase params{};
|
IoctlGetWaitbase params{};
|
||||||
|
LOG_CRITICAL(Service_NVDRV, "called WAITBASE");
|
||||||
std::memcpy(¶ms, input.data(), sizeof(IoctlGetWaitbase));
|
std::memcpy(¶ms, input.data(), sizeof(IoctlGetWaitbase));
|
||||||
params.value = 0; // Seems to be hard coded at 0
|
params.value = 0; // Seems to be hard coded at 0
|
||||||
std::memcpy(output.data(), ¶ms, sizeof(IoctlGetWaitbase));
|
std::memcpy(output.data(), ¶ms, sizeof(IoctlGetWaitbase));
|
||||||
|
@ -136,28 +149,8 @@ NvResult nvhost_nvdec_common::MapBuffer(const std::vector<u8>& input, std::vecto
|
||||||
|
|
||||||
SliceVectors(input, cmd_buffer_handles, params.num_entries, sizeof(IoctlMapBuffer));
|
SliceVectors(input, cmd_buffer_handles, params.num_entries, sizeof(IoctlMapBuffer));
|
||||||
|
|
||||||
auto& gpu = system.GPU();
|
|
||||||
|
|
||||||
for (auto& cmd_buffer : cmd_buffer_handles) {
|
for (auto& cmd_buffer : cmd_buffer_handles) {
|
||||||
auto object{nvmap_dev->GetObject(cmd_buffer.map_handle)};
|
cmd_buffer.map_address = nvmap.PinHandle(cmd_buffer.map_handle);
|
||||||
if (!object) {
|
|
||||||
LOG_ERROR(Service_NVDRV, "invalid cmd_buffer nvmap_handle={:X}", cmd_buffer.map_handle);
|
|
||||||
std::memcpy(output.data(), ¶ms, output.size());
|
|
||||||
return NvResult::InvalidState;
|
|
||||||
}
|
|
||||||
if (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 {
|
|
||||||
cmd_buffer.map_address = object->dma_map_addr;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
std::memcpy(output.data(), ¶ms, sizeof(IoctlMapBuffer));
|
std::memcpy(output.data(), ¶ms, sizeof(IoctlMapBuffer));
|
||||||
std::memcpy(output.data() + sizeof(IoctlMapBuffer), cmd_buffer_handles.data(),
|
std::memcpy(output.data() + sizeof(IoctlMapBuffer), cmd_buffer_handles.data(),
|
||||||
|
@ -167,11 +160,16 @@ NvResult nvhost_nvdec_common::MapBuffer(const std::vector<u8>& input, std::vecto
|
||||||
}
|
}
|
||||||
|
|
||||||
NvResult nvhost_nvdec_common::UnmapBuffer(const std::vector<u8>& input, std::vector<u8>& output) {
|
NvResult nvhost_nvdec_common::UnmapBuffer(const std::vector<u8>& input, std::vector<u8>& output) {
|
||||||
// This is intntionally stubbed.
|
IoctlMapBuffer params{};
|
||||||
// Skip unmapping buffers here, as to not break the continuity of the VP9 reference frame
|
std::memcpy(¶ms, input.data(), sizeof(IoctlMapBuffer));
|
||||||
// addresses, and risk invalidating data before the async GPU thread is done with it
|
std::vector<MapBufferEntry> cmd_buffer_handles(params.num_entries);
|
||||||
|
|
||||||
|
SliceVectors(input, cmd_buffer_handles, params.num_entries, sizeof(IoctlMapBuffer));
|
||||||
|
for (auto& cmd_buffer : cmd_buffer_handles) {
|
||||||
|
nvmap.UnpinHandle(cmd_buffer.map_handle);
|
||||||
|
}
|
||||||
|
|
||||||
std::memset(output.data(), 0, output.size());
|
std::memset(output.data(), 0, output.size());
|
||||||
LOG_DEBUG(Service_NVDRV, "(STUBBED) called");
|
|
||||||
return NvResult::Success;
|
return NvResult::Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -182,4 +180,9 @@ NvResult nvhost_nvdec_common::SetSubmitTimeout(const std::vector<u8>& input,
|
||||||
return NvResult::Success;
|
return NvResult::Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Kernel::KEvent* nvhost_nvdec_common::QueryEvent(u32 event_id) {
|
||||||
|
LOG_CRITICAL(Service_NVDRV, "Unknown HOSTX1 Event {}", event_id);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace Service::Nvidia::Devices
|
} // namespace Service::Nvidia::Devices
|
||||||
|
|
|
@ -3,21 +3,26 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <deque>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include "common/common_types.h"
|
#include "common/common_types.h"
|
||||||
#include "common/swap.h"
|
#include "common/swap.h"
|
||||||
|
#include "core/hle/service/nvdrv/core/syncpoint_manager.h"
|
||||||
#include "core/hle/service/nvdrv/devices/nvdevice.h"
|
#include "core/hle/service/nvdrv/devices/nvdevice.h"
|
||||||
|
|
||||||
namespace Service::Nvidia {
|
namespace Service::Nvidia {
|
||||||
class SyncpointManager;
|
|
||||||
|
namespace NvCore {
|
||||||
|
class Container;
|
||||||
|
class NvMap;
|
||||||
|
} // namespace NvCore
|
||||||
|
|
||||||
namespace Devices {
|
namespace Devices {
|
||||||
class nvmap;
|
|
||||||
|
|
||||||
class nvhost_nvdec_common : public nvdevice {
|
class nvhost_nvdec_common : public nvdevice {
|
||||||
public:
|
public:
|
||||||
explicit nvhost_nvdec_common(Core::System& system_, std::shared_ptr<nvmap> nvmap_dev_,
|
explicit nvhost_nvdec_common(Core::System& system_, NvCore::Container& core,
|
||||||
SyncpointManager& syncpoint_manager_);
|
NvCore::ChannelType channel_type);
|
||||||
~nvhost_nvdec_common() override;
|
~nvhost_nvdec_common() override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
@ -110,11 +115,15 @@ protected:
|
||||||
NvResult UnmapBuffer(const std::vector<u8>& input, std::vector<u8>& output);
|
NvResult UnmapBuffer(const std::vector<u8>& input, std::vector<u8>& output);
|
||||||
NvResult SetSubmitTimeout(const std::vector<u8>& input, std::vector<u8>& output);
|
NvResult SetSubmitTimeout(const std::vector<u8>& input, std::vector<u8>& output);
|
||||||
|
|
||||||
std::unordered_map<DeviceFD, u32> fd_to_id{};
|
Kernel::KEvent* QueryEvent(u32 event_id) override;
|
||||||
|
|
||||||
|
u32 channel_syncpoint;
|
||||||
s32_le nvmap_fd{};
|
s32_le nvmap_fd{};
|
||||||
u32_le submit_timeout{};
|
u32_le submit_timeout{};
|
||||||
std::shared_ptr<nvmap> nvmap_dev;
|
NvCore::Container& core;
|
||||||
SyncpointManager& syncpoint_manager;
|
NvCore::SyncpointManager& syncpoint_manager;
|
||||||
|
NvCore::NvMap& nvmap;
|
||||||
|
NvCore::ChannelType channel_type;
|
||||||
std::array<u32, MaxSyncPoints> device_syncpoints{};
|
std::array<u32, MaxSyncPoints> device_syncpoints{};
|
||||||
};
|
};
|
||||||
}; // namespace Devices
|
}; // namespace Devices
|
||||||
|
|
|
@ -4,13 +4,14 @@
|
||||||
#include "common/assert.h"
|
#include "common/assert.h"
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
#include "core/core.h"
|
#include "core/core.h"
|
||||||
|
#include "core/hle/service/nvdrv/core/container.h"
|
||||||
#include "core/hle/service/nvdrv/devices/nvhost_vic.h"
|
#include "core/hle/service/nvdrv/devices/nvhost_vic.h"
|
||||||
#include "video_core/renderer_base.h"
|
#include "video_core/renderer_base.h"
|
||||||
|
|
||||||
namespace Service::Nvidia::Devices {
|
namespace Service::Nvidia::Devices {
|
||||||
nvhost_vic::nvhost_vic(Core::System& system_, std::shared_ptr<nvmap> nvmap_dev_,
|
|
||||||
SyncpointManager& syncpoint_manager_)
|
nvhost_vic::nvhost_vic(Core::System& system_, NvCore::Container& core_)
|
||||||
: nvhost_nvdec_common{system_, std::move(nvmap_dev_), syncpoint_manager_} {}
|
: nvhost_nvdec_common{system_, core_, NvCore::ChannelType::VIC} {}
|
||||||
|
|
||||||
nvhost_vic::~nvhost_vic() = default;
|
nvhost_vic::~nvhost_vic() = default;
|
||||||
|
|
||||||
|
@ -19,11 +20,13 @@ NvResult nvhost_vic::Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8>& i
|
||||||
switch (command.group) {
|
switch (command.group) {
|
||||||
case 0x0:
|
case 0x0:
|
||||||
switch (command.cmd) {
|
switch (command.cmd) {
|
||||||
case 0x1:
|
case 0x1: {
|
||||||
if (!fd_to_id.contains(fd)) {
|
auto& host1x_file = core.Host1xDeviceFile();
|
||||||
fd_to_id[fd] = next_id++;
|
if (!host1x_file.fd_to_id.contains(fd)) {
|
||||||
|
host1x_file.fd_to_id[fd] = host1x_file.vic_next_id++;
|
||||||
}
|
}
|
||||||
return Submit(fd, input, output);
|
return Submit(fd, input, output);
|
||||||
|
}
|
||||||
case 0x2:
|
case 0x2:
|
||||||
return GetSyncpoint(input, output);
|
return GetSyncpoint(input, output);
|
||||||
case 0x3:
|
case 0x3:
|
||||||
|
@ -67,8 +70,9 @@ NvResult nvhost_vic::Ioctl3(DeviceFD fd, Ioctl command, const std::vector<u8>& i
|
||||||
void nvhost_vic::OnOpen(DeviceFD fd) {}
|
void nvhost_vic::OnOpen(DeviceFD fd) {}
|
||||||
|
|
||||||
void nvhost_vic::OnClose(DeviceFD fd) {
|
void nvhost_vic::OnClose(DeviceFD fd) {
|
||||||
const auto iter = fd_to_id.find(fd);
|
auto& host1x_file = core.Host1xDeviceFile();
|
||||||
if (iter != fd_to_id.end()) {
|
const auto iter = host1x_file.fd_to_id.find(fd);
|
||||||
|
if (iter != host1x_file.fd_to_id.end()) {
|
||||||
system.GPU().ClearCdmaInstance(iter->second);
|
system.GPU().ClearCdmaInstance(iter->second);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,8 +9,7 @@ namespace Service::Nvidia::Devices {
|
||||||
|
|
||||||
class nvhost_vic final : public nvhost_nvdec_common {
|
class nvhost_vic final : public nvhost_nvdec_common {
|
||||||
public:
|
public:
|
||||||
explicit nvhost_vic(Core::System& system_, std::shared_ptr<nvmap> nvmap_dev_,
|
explicit nvhost_vic(Core::System& system_, NvCore::Container& core);
|
||||||
SyncpointManager& syncpoint_manager_);
|
|
||||||
~nvhost_vic();
|
~nvhost_vic();
|
||||||
|
|
||||||
NvResult Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8>& input,
|
NvResult Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8>& input,
|
||||||
|
@ -22,8 +21,5 @@ public:
|
||||||
|
|
||||||
void OnOpen(DeviceFD fd) override;
|
void OnOpen(DeviceFD fd) override;
|
||||||
void OnClose(DeviceFD fd) override;
|
void OnClose(DeviceFD fd) override;
|
||||||
|
|
||||||
private:
|
|
||||||
u32 next_id{};
|
|
||||||
};
|
};
|
||||||
} // namespace Service::Nvidia::Devices
|
} // namespace Service::Nvidia::Devices
|
||||||
|
|
|
@ -2,19 +2,26 @@
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <bit>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
|
||||||
|
#include "common/alignment.h"
|
||||||
#include "common/assert.h"
|
#include "common/assert.h"
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
|
#include "core/core.h"
|
||||||
|
#include "core/hle/kernel/k_page_table.h"
|
||||||
|
#include "core/hle/kernel/k_process.h"
|
||||||
|
#include "core/hle/service/nvdrv/core/container.h"
|
||||||
|
#include "core/hle/service/nvdrv/core/nvmap.h"
|
||||||
#include "core/hle/service/nvdrv/devices/nvmap.h"
|
#include "core/hle/service/nvdrv/devices/nvmap.h"
|
||||||
|
#include "core/memory.h"
|
||||||
|
|
||||||
|
using Core::Memory::YUZU_PAGESIZE;
|
||||||
|
|
||||||
namespace Service::Nvidia::Devices {
|
namespace Service::Nvidia::Devices {
|
||||||
|
|
||||||
nvmap::nvmap(Core::System& system_) : nvdevice{system_} {
|
nvmap::nvmap(Core::System& system_, NvCore::Container& container_)
|
||||||
// Handle 0 appears to be used when remapping, so we create a placeholder empty nvmap object to
|
: nvdevice{system_}, container{container_}, file{container.GetNvMapFile()} {}
|
||||||
// represent this.
|
|
||||||
CreateObject(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
nvmap::~nvmap() = default;
|
nvmap::~nvmap() = default;
|
||||||
|
|
||||||
|
@ -62,39 +69,21 @@ NvResult nvmap::Ioctl3(DeviceFD fd, Ioctl command, const std::vector<u8>& input,
|
||||||
void nvmap::OnOpen(DeviceFD fd) {}
|
void nvmap::OnOpen(DeviceFD fd) {}
|
||||||
void nvmap::OnClose(DeviceFD fd) {}
|
void nvmap::OnClose(DeviceFD fd) {}
|
||||||
|
|
||||||
VAddr nvmap::GetObjectAddress(u32 handle) const {
|
|
||||||
auto object = GetObject(handle);
|
|
||||||
ASSERT(object);
|
|
||||||
ASSERT(object->status == Object::Status::Allocated);
|
|
||||||
return object->addr;
|
|
||||||
}
|
|
||||||
|
|
||||||
u32 nvmap::CreateObject(u32 size) {
|
|
||||||
// Create a new nvmap object and obtain a handle to it.
|
|
||||||
auto object = std::make_shared<Object>();
|
|
||||||
object->id = next_id++;
|
|
||||||
object->size = size;
|
|
||||||
object->status = Object::Status::Created;
|
|
||||||
object->refcount = 1;
|
|
||||||
|
|
||||||
const u32 handle = next_handle++;
|
|
||||||
|
|
||||||
handles.insert_or_assign(handle, std::move(object));
|
|
||||||
|
|
||||||
return handle;
|
|
||||||
}
|
|
||||||
|
|
||||||
NvResult nvmap::IocCreate(const std::vector<u8>& input, std::vector<u8>& output) {
|
NvResult nvmap::IocCreate(const std::vector<u8>& input, std::vector<u8>& output) {
|
||||||
IocCreateParams params;
|
IocCreateParams params;
|
||||||
std::memcpy(¶ms, input.data(), sizeof(params));
|
std::memcpy(¶ms, input.data(), sizeof(params));
|
||||||
LOG_DEBUG(Service_NVDRV, "size=0x{:08X}", params.size);
|
LOG_DEBUG(Service_NVDRV, "called, size=0x{:08X}", params.size);
|
||||||
|
|
||||||
if (!params.size) {
|
std::shared_ptr<NvCore::NvMap::Handle> handle_description{};
|
||||||
LOG_ERROR(Service_NVDRV, "Size is 0");
|
auto result =
|
||||||
return NvResult::BadValue;
|
file.CreateHandle(Common::AlignUp(params.size, YUZU_PAGESIZE), handle_description);
|
||||||
|
if (result != NvResult::Success) {
|
||||||
|
LOG_CRITICAL(Service_NVDRV, "Failed to create Object");
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
handle_description->orig_size = params.size; // Orig size is the unaligned size
|
||||||
params.handle = CreateObject(params.size);
|
params.handle = handle_description->id;
|
||||||
|
LOG_DEBUG(Service_NVDRV, "handle: {}, size: 0x{:X}", handle_description->id, params.size);
|
||||||
|
|
||||||
std::memcpy(output.data(), ¶ms, sizeof(params));
|
std::memcpy(output.data(), ¶ms, sizeof(params));
|
||||||
return NvResult::Success;
|
return NvResult::Success;
|
||||||
|
@ -103,63 +92,68 @@ NvResult nvmap::IocCreate(const std::vector<u8>& input, std::vector<u8>& output)
|
||||||
NvResult nvmap::IocAlloc(const std::vector<u8>& input, std::vector<u8>& output) {
|
NvResult nvmap::IocAlloc(const std::vector<u8>& input, std::vector<u8>& output) {
|
||||||
IocAllocParams params;
|
IocAllocParams params;
|
||||||
std::memcpy(¶ms, input.data(), sizeof(params));
|
std::memcpy(¶ms, input.data(), sizeof(params));
|
||||||
LOG_DEBUG(Service_NVDRV, "called, addr={:X}", params.addr);
|
LOG_DEBUG(Service_NVDRV, "called, addr={:X}", params.address);
|
||||||
|
|
||||||
if (!params.handle) {
|
if (!params.handle) {
|
||||||
LOG_ERROR(Service_NVDRV, "Handle is 0");
|
LOG_CRITICAL(Service_NVDRV, "Handle is 0");
|
||||||
return NvResult::BadValue;
|
return NvResult::BadValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((params.align - 1) & params.align) {
|
if ((params.align - 1) & params.align) {
|
||||||
LOG_ERROR(Service_NVDRV, "Incorrect alignment used, alignment={:08X}", params.align);
|
LOG_CRITICAL(Service_NVDRV, "Incorrect alignment used, alignment={:08X}", params.align);
|
||||||
return NvResult::BadValue;
|
return NvResult::BadValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const u32 min_alignment = 0x1000;
|
// Force page size alignment at a minimum
|
||||||
if (params.align < min_alignment) {
|
if (params.align < YUZU_PAGESIZE) {
|
||||||
params.align = min_alignment;
|
params.align = YUZU_PAGESIZE;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto object = GetObject(params.handle);
|
auto handle_description{file.GetHandle(params.handle)};
|
||||||
if (!object) {
|
if (!handle_description) {
|
||||||
LOG_ERROR(Service_NVDRV, "Object does not exist, handle={:08X}", params.handle);
|
LOG_CRITICAL(Service_NVDRV, "Object does not exist, handle={:08X}", params.handle);
|
||||||
return NvResult::BadValue;
|
return NvResult::BadValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (object->status == Object::Status::Allocated) {
|
if (handle_description->allocated) {
|
||||||
LOG_ERROR(Service_NVDRV, "Object is already allocated, handle={:08X}", params.handle);
|
LOG_CRITICAL(Service_NVDRV, "Object is already allocated, handle={:08X}", params.handle);
|
||||||
return NvResult::InsufficientMemory;
|
return NvResult::InsufficientMemory;
|
||||||
}
|
}
|
||||||
|
|
||||||
object->flags = params.flags;
|
const auto result =
|
||||||
object->align = params.align;
|
handle_description->Alloc(params.flags, params.align, params.kind, params.address);
|
||||||
object->kind = params.kind;
|
if (result != NvResult::Success) {
|
||||||
object->addr = params.addr;
|
LOG_CRITICAL(Service_NVDRV, "Object failed to allocate, handle={:08X}", params.handle);
|
||||||
object->status = Object::Status::Allocated;
|
return result;
|
||||||
|
}
|
||||||
|
ASSERT(system.CurrentProcess()
|
||||||
|
->PageTable()
|
||||||
|
.LockForDeviceAddressSpace(handle_description->address, handle_description->size)
|
||||||
|
.IsSuccess());
|
||||||
std::memcpy(output.data(), ¶ms, sizeof(params));
|
std::memcpy(output.data(), ¶ms, sizeof(params));
|
||||||
return NvResult::Success;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
NvResult nvmap::IocGetId(const std::vector<u8>& input, std::vector<u8>& output) {
|
NvResult nvmap::IocGetId(const std::vector<u8>& input, std::vector<u8>& output) {
|
||||||
IocGetIdParams params;
|
IocGetIdParams params;
|
||||||
std::memcpy(¶ms, input.data(), sizeof(params));
|
std::memcpy(¶ms, input.data(), sizeof(params));
|
||||||
|
|
||||||
LOG_WARNING(Service_NVDRV, "called");
|
LOG_DEBUG(Service_NVDRV, "called");
|
||||||
|
|
||||||
|
// See the comment in FromId for extra info on this function
|
||||||
if (!params.handle) {
|
if (!params.handle) {
|
||||||
LOG_ERROR(Service_NVDRV, "Handle is zero");
|
LOG_CRITICAL(Service_NVDRV, "Error!");
|
||||||
return NvResult::BadValue;
|
return NvResult::BadValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto object = GetObject(params.handle);
|
auto handle_description{file.GetHandle(params.handle)};
|
||||||
if (!object) {
|
if (!handle_description) {
|
||||||
LOG_ERROR(Service_NVDRV, "Object does not exist, handle={:08X}", params.handle);
|
LOG_CRITICAL(Service_NVDRV, "Error!");
|
||||||
return NvResult::BadValue;
|
return NvResult::AccessDenied; // This will always return EPERM irrespective of if the
|
||||||
|
// handle exists or not
|
||||||
}
|
}
|
||||||
|
|
||||||
params.id = object->id;
|
params.id = handle_description->id;
|
||||||
|
|
||||||
std::memcpy(output.data(), ¶ms, sizeof(params));
|
std::memcpy(output.data(), ¶ms, sizeof(params));
|
||||||
return NvResult::Success;
|
return NvResult::Success;
|
||||||
}
|
}
|
||||||
|
@ -168,26 +162,29 @@ NvResult nvmap::IocFromId(const std::vector<u8>& input, std::vector<u8>& output)
|
||||||
IocFromIdParams params;
|
IocFromIdParams params;
|
||||||
std::memcpy(¶ms, input.data(), sizeof(params));
|
std::memcpy(¶ms, input.data(), sizeof(params));
|
||||||
|
|
||||||
LOG_WARNING(Service_NVDRV, "(STUBBED) called");
|
LOG_DEBUG(Service_NVDRV, "called, id:{}", params.id);
|
||||||
|
|
||||||
auto itr = std::find_if(handles.begin(), handles.end(),
|
// Handles and IDs are always the same value in nvmap however IDs can be used globally given the
|
||||||
[&](const auto& entry) { return entry.second->id == params.id; });
|
// right permissions.
|
||||||
if (itr == handles.end()) {
|
// Since we don't plan on ever supporting multiprocess we can skip implementing handle refs and
|
||||||
LOG_ERROR(Service_NVDRV, "Object does not exist, handle={:08X}", params.handle);
|
// so this function just does simple validation and passes through the handle id.
|
||||||
|
if (!params.id) {
|
||||||
|
LOG_CRITICAL(Service_NVDRV, "Zero Id is invalid!");
|
||||||
return NvResult::BadValue;
|
return NvResult::BadValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto& object = itr->second;
|
auto handle_description{file.GetHandle(params.id)};
|
||||||
if (object->status != Object::Status::Allocated) {
|
if (!handle_description) {
|
||||||
LOG_ERROR(Service_NVDRV, "Object is not allocated, handle={:08X}", params.handle);
|
LOG_CRITICAL(Service_NVDRV, "Unregistered handle!");
|
||||||
return NvResult::BadValue;
|
return NvResult::BadValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
itr->second->refcount++;
|
auto result = handle_description->Duplicate(false);
|
||||||
|
if (result != NvResult::Success) {
|
||||||
// Return the existing handle instead of creating a new one.
|
LOG_CRITICAL(Service_NVDRV, "Could not duplicate handle!");
|
||||||
params.handle = itr->first;
|
return result;
|
||||||
|
}
|
||||||
|
params.handle = handle_description->id;
|
||||||
std::memcpy(output.data(), ¶ms, sizeof(params));
|
std::memcpy(output.data(), ¶ms, sizeof(params));
|
||||||
return NvResult::Success;
|
return NvResult::Success;
|
||||||
}
|
}
|
||||||
|
@ -198,35 +195,43 @@ NvResult nvmap::IocParam(const std::vector<u8>& input, std::vector<u8>& output)
|
||||||
IocParamParams params;
|
IocParamParams params;
|
||||||
std::memcpy(¶ms, input.data(), sizeof(params));
|
std::memcpy(¶ms, input.data(), sizeof(params));
|
||||||
|
|
||||||
LOG_DEBUG(Service_NVDRV, "(STUBBED) called type={}", params.param);
|
LOG_DEBUG(Service_NVDRV, "called type={}", params.param);
|
||||||
|
|
||||||
auto object = GetObject(params.handle);
|
if (!params.handle) {
|
||||||
if (!object) {
|
LOG_CRITICAL(Service_NVDRV, "Invalid handle!");
|
||||||
LOG_ERROR(Service_NVDRV, "Object does not exist, handle={:08X}", params.handle);
|
|
||||||
return NvResult::BadValue;
|
return NvResult::BadValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (object->status != Object::Status::Allocated) {
|
auto handle_description{file.GetHandle(params.handle)};
|
||||||
LOG_ERROR(Service_NVDRV, "Object is not allocated, handle={:08X}", params.handle);
|
if (!handle_description) {
|
||||||
|
LOG_CRITICAL(Service_NVDRV, "Not registered handle!");
|
||||||
return NvResult::BadValue;
|
return NvResult::BadValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (static_cast<ParamTypes>(params.param)) {
|
switch (params.param) {
|
||||||
case ParamTypes::Size:
|
case HandleParameterType::Size:
|
||||||
params.result = object->size;
|
params.result = static_cast<u32_le>(handle_description->orig_size);
|
||||||
break;
|
break;
|
||||||
case ParamTypes::Alignment:
|
case HandleParameterType::Alignment:
|
||||||
params.result = object->align;
|
params.result = static_cast<u32_le>(handle_description->align);
|
||||||
break;
|
break;
|
||||||
case ParamTypes::Heap:
|
case HandleParameterType::Base:
|
||||||
// TODO(Subv): Seems to be a hardcoded value?
|
params.result = static_cast<u32_le>(-22); // posix EINVAL
|
||||||
|
break;
|
||||||
|
case HandleParameterType::Heap:
|
||||||
|
if (handle_description->allocated)
|
||||||
params.result = 0x40000000;
|
params.result = 0x40000000;
|
||||||
|
else
|
||||||
|
params.result = 0;
|
||||||
break;
|
break;
|
||||||
case ParamTypes::Kind:
|
case HandleParameterType::Kind:
|
||||||
params.result = object->kind;
|
params.result = handle_description->kind;
|
||||||
|
break;
|
||||||
|
case HandleParameterType::IsSharedMemMapped:
|
||||||
|
params.result = handle_description->is_shared_mem_mapped;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
UNIMPLEMENTED();
|
return NvResult::BadValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::memcpy(output.data(), ¶ms, sizeof(params));
|
std::memcpy(output.data(), ¶ms, sizeof(params));
|
||||||
|
@ -234,46 +239,29 @@ NvResult nvmap::IocParam(const std::vector<u8>& input, std::vector<u8>& output)
|
||||||
}
|
}
|
||||||
|
|
||||||
NvResult nvmap::IocFree(const std::vector<u8>& input, std::vector<u8>& output) {
|
NvResult nvmap::IocFree(const std::vector<u8>& input, std::vector<u8>& output) {
|
||||||
// TODO(Subv): These flags are unconfirmed.
|
|
||||||
enum FreeFlags {
|
|
||||||
Freed = 0,
|
|
||||||
NotFreedYet = 1,
|
|
||||||
};
|
|
||||||
|
|
||||||
IocFreeParams params;
|
IocFreeParams params;
|
||||||
std::memcpy(¶ms, input.data(), sizeof(params));
|
std::memcpy(¶ms, input.data(), sizeof(params));
|
||||||
|
|
||||||
LOG_DEBUG(Service_NVDRV, "(STUBBED) called");
|
LOG_DEBUG(Service_NVDRV, "called");
|
||||||
|
|
||||||
auto itr = handles.find(params.handle);
|
if (!params.handle) {
|
||||||
if (itr == handles.end()) {
|
LOG_CRITICAL(Service_NVDRV, "Handle null freed?");
|
||||||
LOG_ERROR(Service_NVDRV, "Object does not exist, handle={:08X}", params.handle);
|
return NvResult::Success;
|
||||||
return NvResult::BadValue;
|
|
||||||
}
|
|
||||||
if (!itr->second->refcount) {
|
|
||||||
LOG_ERROR(
|
|
||||||
Service_NVDRV,
|
|
||||||
"There is no references to this object. The object is already freed. handle={:08X}",
|
|
||||||
params.handle);
|
|
||||||
return NvResult::BadValue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
itr->second->refcount--;
|
if (auto freeInfo{file.FreeHandle(params.handle, false)}) {
|
||||||
|
ASSERT(system.CurrentProcess()
|
||||||
params.size = itr->second->size;
|
->PageTable()
|
||||||
|
.UnlockForDeviceAddressSpace(freeInfo->address, freeInfo->size)
|
||||||
if (itr->second->refcount == 0) {
|
.IsSuccess());
|
||||||
params.flags = Freed;
|
params.address = freeInfo->address;
|
||||||
// The address of the nvmap is written to the output if we're finally freeing it, otherwise
|
params.size = static_cast<u32>(freeInfo->size);
|
||||||
// 0 is written.
|
params.flags.raw = 0;
|
||||||
params.address = itr->second->addr;
|
params.flags.map_uncached.Assign(freeInfo->was_uncached);
|
||||||
} else {
|
} else {
|
||||||
params.flags = NotFreedYet;
|
// This is possible when there's internel dups or other duplicates.
|
||||||
params.address = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handles.erase(params.handle);
|
|
||||||
|
|
||||||
std::memcpy(output.data(), ¶ms, sizeof(params));
|
std::memcpy(output.data(), ¶ms, sizeof(params));
|
||||||
return NvResult::Success;
|
return NvResult::Success;
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,15 +9,23 @@
|
||||||
#include "common/common_funcs.h"
|
#include "common/common_funcs.h"
|
||||||
#include "common/common_types.h"
|
#include "common/common_types.h"
|
||||||
#include "common/swap.h"
|
#include "common/swap.h"
|
||||||
|
#include "core/hle/service/nvdrv/core/nvmap.h"
|
||||||
#include "core/hle/service/nvdrv/devices/nvdevice.h"
|
#include "core/hle/service/nvdrv/devices/nvdevice.h"
|
||||||
|
|
||||||
|
namespace Service::Nvidia::NvCore {
|
||||||
|
class Container;
|
||||||
|
} // namespace Service::Nvidia::NvCore
|
||||||
|
|
||||||
namespace Service::Nvidia::Devices {
|
namespace Service::Nvidia::Devices {
|
||||||
|
|
||||||
class nvmap final : public nvdevice {
|
class nvmap final : public nvdevice {
|
||||||
public:
|
public:
|
||||||
explicit nvmap(Core::System& system_);
|
explicit nvmap(Core::System& system_, NvCore::Container& container);
|
||||||
~nvmap() override;
|
~nvmap() override;
|
||||||
|
|
||||||
|
nvmap(const nvmap&) = delete;
|
||||||
|
nvmap& operator=(const nvmap&) = delete;
|
||||||
|
|
||||||
NvResult Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8>& input,
|
NvResult Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8>& input,
|
||||||
std::vector<u8>& output) override;
|
std::vector<u8>& output) override;
|
||||||
NvResult Ioctl2(DeviceFD fd, Ioctl command, const std::vector<u8>& input,
|
NvResult Ioctl2(DeviceFD fd, Ioctl command, const std::vector<u8>& input,
|
||||||
|
@ -28,31 +36,15 @@ public:
|
||||||
void OnOpen(DeviceFD fd) override;
|
void OnOpen(DeviceFD fd) override;
|
||||||
void OnClose(DeviceFD fd) override;
|
void OnClose(DeviceFD fd) override;
|
||||||
|
|
||||||
/// Returns the allocated address of an nvmap object given its handle.
|
enum class HandleParameterType : u32_le {
|
||||||
VAddr GetObjectAddress(u32 handle) const;
|
Size = 1,
|
||||||
|
Alignment = 2,
|
||||||
/// Represents an nvmap object.
|
Base = 3,
|
||||||
struct Object {
|
Heap = 4,
|
||||||
enum class Status { Created, Allocated };
|
Kind = 5,
|
||||||
u32 id;
|
IsSharedMemMapped = 6
|
||||||
u32 size;
|
|
||||||
u32 flags;
|
|
||||||
u32 align;
|
|
||||||
u8 kind;
|
|
||||||
VAddr addr;
|
|
||||||
Status status;
|
|
||||||
u32 refcount;
|
|
||||||
u32 dma_map_addr;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
std::shared_ptr<Object> GetObject(u32 handle) const {
|
|
||||||
auto itr = handles.find(handle);
|
|
||||||
if (itr != handles.end()) {
|
|
||||||
return itr->second;
|
|
||||||
}
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/// Id to use for the next handle that is created.
|
/// Id to use for the next handle that is created.
|
||||||
u32 next_handle = 0;
|
u32 next_handle = 0;
|
||||||
|
@ -60,9 +52,6 @@ private:
|
||||||
/// Id to use for the next object that is created.
|
/// Id to use for the next object that is created.
|
||||||
u32 next_id = 0;
|
u32 next_id = 0;
|
||||||
|
|
||||||
/// Mapping of currently allocated handles to the objects they represent.
|
|
||||||
std::unordered_map<u32, std::shared_ptr<Object>> handles;
|
|
||||||
|
|
||||||
struct IocCreateParams {
|
struct IocCreateParams {
|
||||||
// Input
|
// Input
|
||||||
u32_le size{};
|
u32_le size{};
|
||||||
|
@ -83,11 +72,11 @@ private:
|
||||||
// Input
|
// Input
|
||||||
u32_le handle{};
|
u32_le handle{};
|
||||||
u32_le heap_mask{};
|
u32_le heap_mask{};
|
||||||
u32_le flags{};
|
NvCore::NvMap::Handle::Flags flags{};
|
||||||
u32_le align{};
|
u32_le align{};
|
||||||
u8 kind{};
|
u8 kind{};
|
||||||
INSERT_PADDING_BYTES(7);
|
INSERT_PADDING_BYTES(7);
|
||||||
u64_le addr{};
|
u64_le address{};
|
||||||
};
|
};
|
||||||
static_assert(sizeof(IocAllocParams) == 32, "IocAllocParams has wrong size");
|
static_assert(sizeof(IocAllocParams) == 32, "IocAllocParams has wrong size");
|
||||||
|
|
||||||
|
@ -96,14 +85,14 @@ private:
|
||||||
INSERT_PADDING_BYTES(4);
|
INSERT_PADDING_BYTES(4);
|
||||||
u64_le address{};
|
u64_le address{};
|
||||||
u32_le size{};
|
u32_le size{};
|
||||||
u32_le flags{};
|
NvCore::NvMap::Handle::Flags flags{};
|
||||||
};
|
};
|
||||||
static_assert(sizeof(IocFreeParams) == 24, "IocFreeParams has wrong size");
|
static_assert(sizeof(IocFreeParams) == 24, "IocFreeParams has wrong size");
|
||||||
|
|
||||||
struct IocParamParams {
|
struct IocParamParams {
|
||||||
// Input
|
// Input
|
||||||
u32_le handle{};
|
u32_le handle{};
|
||||||
u32_le param{};
|
HandleParameterType param{};
|
||||||
// Output
|
// Output
|
||||||
u32_le result{};
|
u32_le result{};
|
||||||
};
|
};
|
||||||
|
@ -117,14 +106,15 @@ private:
|
||||||
};
|
};
|
||||||
static_assert(sizeof(IocGetIdParams) == 8, "IocGetIdParams has wrong size");
|
static_assert(sizeof(IocGetIdParams) == 8, "IocGetIdParams has wrong size");
|
||||||
|
|
||||||
u32 CreateObject(u32 size);
|
|
||||||
|
|
||||||
NvResult IocCreate(const std::vector<u8>& input, std::vector<u8>& output);
|
NvResult IocCreate(const std::vector<u8>& input, std::vector<u8>& output);
|
||||||
NvResult IocAlloc(const std::vector<u8>& input, std::vector<u8>& output);
|
NvResult IocAlloc(const std::vector<u8>& input, std::vector<u8>& output);
|
||||||
NvResult IocGetId(const std::vector<u8>& input, std::vector<u8>& output);
|
NvResult IocGetId(const std::vector<u8>& input, std::vector<u8>& output);
|
||||||
NvResult IocFromId(const std::vector<u8>& input, std::vector<u8>& output);
|
NvResult IocFromId(const std::vector<u8>& input, std::vector<u8>& output);
|
||||||
NvResult IocParam(const std::vector<u8>& input, std::vector<u8>& output);
|
NvResult IocParam(const std::vector<u8>& input, std::vector<u8>& output);
|
||||||
NvResult IocFree(const std::vector<u8>& input, std::vector<u8>& output);
|
NvResult IocFree(const std::vector<u8>& input, std::vector<u8>& output);
|
||||||
|
|
||||||
|
NvCore::Container& container;
|
||||||
|
NvCore::NvMap& file;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Service::Nvidia::Devices
|
} // namespace Service::Nvidia::Devices
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
|
// SPDX-FileCopyrightText: 2021 yuzu Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-FileCopyrightText: 2021 Skyline Team and Contributors
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
@ -78,11 +79,15 @@ enum class NvResult : u32 {
|
||||||
ModuleNotPresent = 0xA000E,
|
ModuleNotPresent = 0xA000E,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// obtained from
|
||||||
|
// https://github.com/skyline-emu/skyline/blob/nvdec-dev/app/src/main/cpp/skyline/services/nvdrv/devices/nvhost/ctrl.h#L47
|
||||||
enum class EventState {
|
enum class EventState {
|
||||||
Free = 0,
|
Available = 0,
|
||||||
Registered = 1,
|
Waiting = 1,
|
||||||
Waiting = 2,
|
Cancelling = 2,
|
||||||
Busy = 3,
|
Signalling = 3,
|
||||||
|
Signalled = 4,
|
||||||
|
Cancelled = 5,
|
||||||
};
|
};
|
||||||
|
|
||||||
union Ioctl {
|
union Ioctl {
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
// SPDX-FileCopyrightText: 2021 yuzu Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-FileCopyrightText: 2021 Skyline Team and Contributors
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
|
@ -8,6 +9,7 @@
|
||||||
#include "core/hle/ipc_helpers.h"
|
#include "core/hle/ipc_helpers.h"
|
||||||
#include "core/hle/kernel/k_event.h"
|
#include "core/hle/kernel/k_event.h"
|
||||||
#include "core/hle/kernel/k_writable_event.h"
|
#include "core/hle/kernel/k_writable_event.h"
|
||||||
|
#include "core/hle/service/nvdrv/core/container.h"
|
||||||
#include "core/hle/service/nvdrv/devices/nvdevice.h"
|
#include "core/hle/service/nvdrv/devices/nvdevice.h"
|
||||||
#include "core/hle/service/nvdrv/devices/nvdisp_disp0.h"
|
#include "core/hle/service/nvdrv/devices/nvdisp_disp0.h"
|
||||||
#include "core/hle/service/nvdrv/devices/nvhost_as_gpu.h"
|
#include "core/hle/service/nvdrv/devices/nvhost_as_gpu.h"
|
||||||
|
@ -15,17 +17,31 @@
|
||||||
#include "core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.h"
|
#include "core/hle/service/nvdrv/devices/nvhost_ctrl_gpu.h"
|
||||||
#include "core/hle/service/nvdrv/devices/nvhost_gpu.h"
|
#include "core/hle/service/nvdrv/devices/nvhost_gpu.h"
|
||||||
#include "core/hle/service/nvdrv/devices/nvhost_nvdec.h"
|
#include "core/hle/service/nvdrv/devices/nvhost_nvdec.h"
|
||||||
|
#include "core/hle/service/nvdrv/devices/nvhost_nvdec_common.h"
|
||||||
#include "core/hle/service/nvdrv/devices/nvhost_nvjpg.h"
|
#include "core/hle/service/nvdrv/devices/nvhost_nvjpg.h"
|
||||||
#include "core/hle/service/nvdrv/devices/nvhost_vic.h"
|
#include "core/hle/service/nvdrv/devices/nvhost_vic.h"
|
||||||
#include "core/hle/service/nvdrv/devices/nvmap.h"
|
#include "core/hle/service/nvdrv/devices/nvmap.h"
|
||||||
#include "core/hle/service/nvdrv/nvdrv.h"
|
#include "core/hle/service/nvdrv/nvdrv.h"
|
||||||
#include "core/hle/service/nvdrv/nvdrv_interface.h"
|
#include "core/hle/service/nvdrv/nvdrv_interface.h"
|
||||||
#include "core/hle/service/nvdrv/nvmemp.h"
|
#include "core/hle/service/nvdrv/nvmemp.h"
|
||||||
#include "core/hle/service/nvdrv/syncpoint_manager.h"
|
|
||||||
#include "core/hle/service/nvflinger/nvflinger.h"
|
#include "core/hle/service/nvflinger/nvflinger.h"
|
||||||
|
#include "video_core/gpu.h"
|
||||||
|
|
||||||
namespace Service::Nvidia {
|
namespace Service::Nvidia {
|
||||||
|
|
||||||
|
EventInterface::EventInterface(Module& module_) : module{module_}, guard{}, on_signal{} {}
|
||||||
|
|
||||||
|
EventInterface::~EventInterface() = default;
|
||||||
|
|
||||||
|
Kernel::KEvent* EventInterface::CreateEvent(std::string name) {
|
||||||
|
Kernel::KEvent* new_event = module.service_context.CreateEvent(std::move(name));
|
||||||
|
return new_event;
|
||||||
|
}
|
||||||
|
|
||||||
|
void EventInterface::FreeEvent(Kernel::KEvent* event) {
|
||||||
|
module.service_context.CloseEvent(event);
|
||||||
|
}
|
||||||
|
|
||||||
void InstallInterfaces(SM::ServiceManager& service_manager, NVFlinger::NVFlinger& nvflinger,
|
void InstallInterfaces(SM::ServiceManager& service_manager, NVFlinger::NVFlinger& nvflinger,
|
||||||
Core::System& system) {
|
Core::System& system) {
|
||||||
auto module_ = std::make_shared<Module>(system);
|
auto module_ = std::make_shared<Module>(system);
|
||||||
|
@ -38,34 +54,54 @@ void InstallInterfaces(SM::ServiceManager& service_manager, NVFlinger::NVFlinger
|
||||||
}
|
}
|
||||||
|
|
||||||
Module::Module(Core::System& system)
|
Module::Module(Core::System& system)
|
||||||
: syncpoint_manager{system.GPU()}, service_context{system, "nvdrv"} {
|
: service_context{system, "nvdrv"}, events_interface{*this}, container{system.Host1x()} {
|
||||||
for (u32 i = 0; i < MaxNvEvents; i++) {
|
builders["/dev/nvhost-as-gpu"] = [this, &system](DeviceFD fd) {
|
||||||
events_interface.events[i].event =
|
std::shared_ptr<Devices::nvdevice> device =
|
||||||
service_context.CreateEvent(fmt::format("NVDRV::NvEvent_{}", i));
|
std::make_shared<Devices::nvhost_as_gpu>(system, *this, container);
|
||||||
events_interface.status[i] = EventState::Free;
|
return open_files.emplace(fd, device).first;
|
||||||
events_interface.registered[i] = false;
|
};
|
||||||
}
|
builders["/dev/nvhost-gpu"] = [this, &system](DeviceFD fd) {
|
||||||
auto nvmap_dev = std::make_shared<Devices::nvmap>(system);
|
std::shared_ptr<Devices::nvdevice> device =
|
||||||
devices["/dev/nvhost-as-gpu"] = std::make_shared<Devices::nvhost_as_gpu>(system, nvmap_dev);
|
std::make_shared<Devices::nvhost_gpu>(system, events_interface, container);
|
||||||
devices["/dev/nvhost-gpu"] =
|
return open_files.emplace(fd, device).first;
|
||||||
std::make_shared<Devices::nvhost_gpu>(system, nvmap_dev, syncpoint_manager);
|
};
|
||||||
devices["/dev/nvhost-ctrl-gpu"] = std::make_shared<Devices::nvhost_ctrl_gpu>(system);
|
builders["/dev/nvhost-ctrl-gpu"] = [this, &system](DeviceFD fd) {
|
||||||
devices["/dev/nvmap"] = nvmap_dev;
|
std::shared_ptr<Devices::nvdevice> device =
|
||||||
devices["/dev/nvdisp_disp0"] = std::make_shared<Devices::nvdisp_disp0>(system, nvmap_dev);
|
std::make_shared<Devices::nvhost_ctrl_gpu>(system, events_interface);
|
||||||
devices["/dev/nvhost-ctrl"] =
|
return open_files.emplace(fd, device).first;
|
||||||
std::make_shared<Devices::nvhost_ctrl>(system, events_interface, syncpoint_manager);
|
};
|
||||||
devices["/dev/nvhost-nvdec"] =
|
builders["/dev/nvmap"] = [this, &system](DeviceFD fd) {
|
||||||
std::make_shared<Devices::nvhost_nvdec>(system, nvmap_dev, syncpoint_manager);
|
std::shared_ptr<Devices::nvdevice> device =
|
||||||
devices["/dev/nvhost-nvjpg"] = std::make_shared<Devices::nvhost_nvjpg>(system);
|
std::make_shared<Devices::nvmap>(system, container);
|
||||||
devices["/dev/nvhost-vic"] =
|
return open_files.emplace(fd, device).first;
|
||||||
std::make_shared<Devices::nvhost_vic>(system, nvmap_dev, syncpoint_manager);
|
};
|
||||||
|
builders["/dev/nvdisp_disp0"] = [this, &system](DeviceFD fd) {
|
||||||
|
std::shared_ptr<Devices::nvdevice> device =
|
||||||
|
std::make_shared<Devices::nvdisp_disp0>(system, container);
|
||||||
|
return open_files.emplace(fd, device).first;
|
||||||
|
};
|
||||||
|
builders["/dev/nvhost-ctrl"] = [this, &system](DeviceFD fd) {
|
||||||
|
std::shared_ptr<Devices::nvdevice> device =
|
||||||
|
std::make_shared<Devices::nvhost_ctrl>(system, events_interface, container);
|
||||||
|
return open_files.emplace(fd, device).first;
|
||||||
|
};
|
||||||
|
builders["/dev/nvhost-nvdec"] = [this, &system](DeviceFD fd) {
|
||||||
|
std::shared_ptr<Devices::nvdevice> device =
|
||||||
|
std::make_shared<Devices::nvhost_nvdec>(system, container);
|
||||||
|
return open_files.emplace(fd, device).first;
|
||||||
|
};
|
||||||
|
builders["/dev/nvhost-nvjpg"] = [this, &system](DeviceFD fd) {
|
||||||
|
std::shared_ptr<Devices::nvdevice> device = std::make_shared<Devices::nvhost_nvjpg>(system);
|
||||||
|
return open_files.emplace(fd, device).first;
|
||||||
|
};
|
||||||
|
builders["/dev/nvhost-vic"] = [this, &system](DeviceFD fd) {
|
||||||
|
std::shared_ptr<Devices::nvdevice> device =
|
||||||
|
std::make_shared<Devices::nvhost_vic>(system, container);
|
||||||
|
return open_files.emplace(fd, device).first;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
Module::~Module() {
|
Module::~Module() {}
|
||||||
for (u32 i = 0; i < MaxNvEvents; i++) {
|
|
||||||
service_context.CloseEvent(events_interface.events[i].event);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
NvResult Module::VerifyFD(DeviceFD fd) const {
|
NvResult Module::VerifyFD(DeviceFD fd) const {
|
||||||
if (fd < 0) {
|
if (fd < 0) {
|
||||||
|
@ -82,18 +118,18 @@ NvResult Module::VerifyFD(DeviceFD fd) const {
|
||||||
}
|
}
|
||||||
|
|
||||||
DeviceFD Module::Open(const std::string& device_name) {
|
DeviceFD Module::Open(const std::string& device_name) {
|
||||||
if (devices.find(device_name) == devices.end()) {
|
auto it = builders.find(device_name);
|
||||||
|
if (it == builders.end()) {
|
||||||
LOG_ERROR(Service_NVDRV, "Trying to open unknown device {}", device_name);
|
LOG_ERROR(Service_NVDRV, "Trying to open unknown device {}", device_name);
|
||||||
return INVALID_NVDRV_FD;
|
return INVALID_NVDRV_FD;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto device = devices[device_name];
|
|
||||||
const DeviceFD fd = next_fd++;
|
const DeviceFD fd = next_fd++;
|
||||||
|
auto& builder = it->second;
|
||||||
|
auto device = builder(fd)->second;
|
||||||
|
|
||||||
device->OnOpen(fd);
|
device->OnOpen(fd);
|
||||||
|
|
||||||
open_files[fd] = std::move(device);
|
|
||||||
|
|
||||||
return fd;
|
return fd;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -168,22 +204,24 @@ NvResult Module::Close(DeviceFD fd) {
|
||||||
return NvResult::Success;
|
return NvResult::Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Module::SignalSyncpt(const u32 syncpoint_id, const u32 value) {
|
NvResult Module::QueryEvent(DeviceFD fd, u32 event_id, Kernel::KEvent*& event) {
|
||||||
for (u32 i = 0; i < MaxNvEvents; i++) {
|
if (fd < 0) {
|
||||||
if (events_interface.assigned_syncpt[i] == syncpoint_id &&
|
LOG_ERROR(Service_NVDRV, "Invalid DeviceFD={}!", fd);
|
||||||
events_interface.assigned_value[i] == value) {
|
return NvResult::InvalidState;
|
||||||
events_interface.LiberateEvent(i);
|
|
||||||
events_interface.events[i].event->GetWritableEvent().Signal();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const auto itr = open_files.find(fd);
|
||||||
|
|
||||||
|
if (itr == open_files.end()) {
|
||||||
|
LOG_ERROR(Service_NVDRV, "Could not find DeviceFD={}!", fd);
|
||||||
|
return NvResult::NotImplemented;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
Kernel::KReadableEvent& Module::GetEvent(const u32 event_id) {
|
event = itr->second->QueryEvent(event_id);
|
||||||
return events_interface.events[event_id].event->GetReadableEvent();
|
if (!event) {
|
||||||
}
|
return NvResult::BadParameter;
|
||||||
|
}
|
||||||
Kernel::KWritableEvent& Module::GetEventWriteable(const u32 event_id) {
|
return NvResult::Success;
|
||||||
return events_interface.events[event_id].event->GetWritableEvent();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Service::Nvidia
|
} // namespace Service::Nvidia
|
||||||
|
|
|
@ -1,16 +1,20 @@
|
||||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
// SPDX-FileCopyrightText: 2021 yuzu Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-FileCopyrightText: 2021 Skyline Team and Contributors
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
#include <list>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "common/common_types.h"
|
#include "common/common_types.h"
|
||||||
#include "core/hle/service/kernel_helpers.h"
|
#include "core/hle/service/kernel_helpers.h"
|
||||||
|
#include "core/hle/service/nvdrv/core/container.h"
|
||||||
#include "core/hle/service/nvdrv/nvdata.h"
|
#include "core/hle/service/nvdrv/nvdata.h"
|
||||||
#include "core/hle/service/nvdrv/syncpoint_manager.h"
|
|
||||||
#include "core/hle/service/nvflinger/ui/fence.h"
|
#include "core/hle/service/nvflinger/ui/fence.h"
|
||||||
#include "core/hle/service/service.h"
|
#include "core/hle/service/service.h"
|
||||||
|
|
||||||
|
@ -28,81 +32,31 @@ class NVFlinger;
|
||||||
|
|
||||||
namespace Service::Nvidia {
|
namespace Service::Nvidia {
|
||||||
|
|
||||||
|
namespace NvCore {
|
||||||
|
class Container;
|
||||||
class SyncpointManager;
|
class SyncpointManager;
|
||||||
|
} // namespace NvCore
|
||||||
|
|
||||||
namespace Devices {
|
namespace Devices {
|
||||||
class nvdevice;
|
class nvdevice;
|
||||||
}
|
class nvhost_ctrl;
|
||||||
|
} // namespace Devices
|
||||||
|
|
||||||
/// Represents an Nvidia event
|
class Module;
|
||||||
struct NvEvent {
|
|
||||||
Kernel::KEvent* event{};
|
|
||||||
NvFence fence{};
|
|
||||||
};
|
|
||||||
|
|
||||||
struct EventInterface {
|
class EventInterface {
|
||||||
// Mask representing currently busy events
|
public:
|
||||||
u64 events_mask{};
|
explicit EventInterface(Module& module_);
|
||||||
// Each kernel event associated to an NV event
|
~EventInterface();
|
||||||
std::array<NvEvent, MaxNvEvents> events;
|
|
||||||
// The status of the current NVEvent
|
Kernel::KEvent* CreateEvent(std::string name);
|
||||||
std::array<EventState, MaxNvEvents> status{};
|
|
||||||
// Tells if an NVEvent is registered or not
|
void FreeEvent(Kernel::KEvent* event);
|
||||||
std::array<bool, MaxNvEvents> registered{};
|
|
||||||
// Tells the NVEvent that it has failed.
|
private:
|
||||||
std::array<bool, MaxNvEvents> failed{};
|
Module& module;
|
||||||
// When an NVEvent is waiting on GPU interrupt, this is the sync_point
|
std::mutex guard;
|
||||||
// associated with it.
|
std::list<Devices::nvhost_ctrl*> on_signal;
|
||||||
std::array<u32, MaxNvEvents> assigned_syncpt{};
|
|
||||||
// This is the value of the GPU interrupt for which the NVEvent is waiting
|
|
||||||
// for.
|
|
||||||
std::array<u32, MaxNvEvents> assigned_value{};
|
|
||||||
// Constant to denote an unasigned syncpoint.
|
|
||||||
static constexpr u32 unassigned_syncpt = 0xFFFFFFFF;
|
|
||||||
std::optional<u32> GetFreeEvent() const {
|
|
||||||
u64 mask = events_mask;
|
|
||||||
for (u32 i = 0; i < MaxNvEvents; i++) {
|
|
||||||
const bool is_free = (mask & 0x1) == 0;
|
|
||||||
if (is_free) {
|
|
||||||
if (status[i] == EventState::Registered || status[i] == EventState::Free) {
|
|
||||||
return {i};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
mask = mask >> 1;
|
|
||||||
}
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
void SetEventStatus(const u32 event_id, EventState new_status) {
|
|
||||||
EventState old_status = status[event_id];
|
|
||||||
if (old_status == new_status) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
status[event_id] = new_status;
|
|
||||||
if (new_status == EventState::Registered) {
|
|
||||||
registered[event_id] = true;
|
|
||||||
}
|
|
||||||
if (new_status == EventState::Waiting || new_status == EventState::Busy) {
|
|
||||||
events_mask |= (1ULL << event_id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
void RegisterEvent(const u32 event_id) {
|
|
||||||
registered[event_id] = true;
|
|
||||||
if (status[event_id] == EventState::Free) {
|
|
||||||
status[event_id] = EventState::Registered;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
void UnregisterEvent(const u32 event_id) {
|
|
||||||
registered[event_id] = false;
|
|
||||||
if (status[event_id] == EventState::Registered) {
|
|
||||||
status[event_id] = EventState::Free;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
void LiberateEvent(const u32 event_id) {
|
|
||||||
status[event_id] = registered[event_id] ? EventState::Registered : EventState::Free;
|
|
||||||
events_mask &= ~(1ULL << event_id);
|
|
||||||
assigned_syncpt[event_id] = unassigned_syncpt;
|
|
||||||
assigned_value[event_id] = 0;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class Module final {
|
class Module final {
|
||||||
|
@ -112,9 +66,9 @@ public:
|
||||||
|
|
||||||
/// Returns a pointer to one of the available devices, identified by its name.
|
/// Returns a pointer to one of the available devices, identified by its name.
|
||||||
template <typename T>
|
template <typename T>
|
||||||
std::shared_ptr<T> GetDevice(const std::string& name) {
|
std::shared_ptr<T> GetDevice(DeviceFD fd) {
|
||||||
auto itr = devices.find(name);
|
auto itr = open_files.find(fd);
|
||||||
if (itr == devices.end())
|
if (itr == open_files.end())
|
||||||
return nullptr;
|
return nullptr;
|
||||||
return std::static_pointer_cast<T>(itr->second);
|
return std::static_pointer_cast<T>(itr->second);
|
||||||
}
|
}
|
||||||
|
@ -137,28 +91,27 @@ public:
|
||||||
/// Closes a device file descriptor and returns operation success.
|
/// Closes a device file descriptor and returns operation success.
|
||||||
NvResult Close(DeviceFD fd);
|
NvResult Close(DeviceFD fd);
|
||||||
|
|
||||||
void SignalSyncpt(const u32 syncpoint_id, const u32 value);
|
NvResult QueryEvent(DeviceFD fd, u32 event_id, Kernel::KEvent*& event);
|
||||||
|
|
||||||
Kernel::KReadableEvent& GetEvent(u32 event_id);
|
|
||||||
|
|
||||||
Kernel::KWritableEvent& GetEventWriteable(u32 event_id);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/// Manages syncpoints on the host
|
friend class EventInterface;
|
||||||
SyncpointManager syncpoint_manager;
|
friend class Service::NVFlinger::NVFlinger;
|
||||||
|
|
||||||
/// Id to use for the next open file descriptor.
|
/// Id to use for the next open file descriptor.
|
||||||
DeviceFD next_fd = 1;
|
DeviceFD next_fd = 1;
|
||||||
|
|
||||||
|
using FilesContainerType = std::unordered_map<DeviceFD, std::shared_ptr<Devices::nvdevice>>;
|
||||||
/// Mapping of file descriptors to the devices they reference.
|
/// Mapping of file descriptors to the devices they reference.
|
||||||
std::unordered_map<DeviceFD, std::shared_ptr<Devices::nvdevice>> open_files;
|
FilesContainerType open_files;
|
||||||
|
|
||||||
/// Mapping of device node names to their implementation.
|
KernelHelpers::ServiceContext service_context;
|
||||||
std::unordered_map<std::string, std::shared_ptr<Devices::nvdevice>> devices;
|
|
||||||
|
|
||||||
EventInterface events_interface;
|
EventInterface events_interface;
|
||||||
|
|
||||||
KernelHelpers::ServiceContext service_context;
|
/// Manages syncpoints on the host
|
||||||
|
NvCore::Container container;
|
||||||
|
|
||||||
|
std::unordered_map<std::string, std::function<FilesContainerType::iterator(DeviceFD)>> builders;
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Registers all NVDRV services with the specified service manager.
|
/// Registers all NVDRV services with the specified service manager.
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
// SPDX-FileCopyrightText: 2021 yuzu Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-FileCopyrightText: 2021 Skyline Team and Contributors
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#include <cinttypes>
|
#include <cinttypes>
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
#include "core/core.h"
|
#include "core/core.h"
|
||||||
#include "core/hle/ipc_helpers.h"
|
#include "core/hle/ipc_helpers.h"
|
||||||
|
#include "core/hle/kernel/k_event.h"
|
||||||
#include "core/hle/kernel/k_readable_event.h"
|
#include "core/hle/kernel/k_readable_event.h"
|
||||||
#include "core/hle/service/nvdrv/nvdata.h"
|
#include "core/hle/service/nvdrv/nvdata.h"
|
||||||
#include "core/hle/service/nvdrv/nvdrv.h"
|
#include "core/hle/service/nvdrv/nvdrv.h"
|
||||||
|
@ -12,10 +14,6 @@
|
||||||
|
|
||||||
namespace Service::Nvidia {
|
namespace Service::Nvidia {
|
||||||
|
|
||||||
void NVDRV::SignalGPUInterruptSyncpt(const u32 syncpoint_id, const u32 value) {
|
|
||||||
nvdrv->SignalSyncpt(syncpoint_id, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
void NVDRV::Open(Kernel::HLERequestContext& ctx) {
|
void NVDRV::Open(Kernel::HLERequestContext& ctx) {
|
||||||
LOG_DEBUG(Service_NVDRV, "called");
|
LOG_DEBUG(Service_NVDRV, "called");
|
||||||
IPC::ResponseBuilder rb{ctx, 4};
|
IPC::ResponseBuilder rb{ctx, 4};
|
||||||
|
@ -164,8 +162,7 @@ void NVDRV::Initialize(Kernel::HLERequestContext& ctx) {
|
||||||
void NVDRV::QueryEvent(Kernel::HLERequestContext& ctx) {
|
void NVDRV::QueryEvent(Kernel::HLERequestContext& ctx) {
|
||||||
IPC::RequestParser rp{ctx};
|
IPC::RequestParser rp{ctx};
|
||||||
const auto fd = rp.Pop<DeviceFD>();
|
const auto fd = rp.Pop<DeviceFD>();
|
||||||
const auto event_id = rp.Pop<u32>() & 0x00FF;
|
const auto event_id = rp.Pop<u32>();
|
||||||
LOG_WARNING(Service_NVDRV, "(STUBBED) called, fd={:X}, event_id={:X}", fd, event_id);
|
|
||||||
|
|
||||||
if (!is_initialized) {
|
if (!is_initialized) {
|
||||||
ServiceError(ctx, NvResult::NotInitialized);
|
ServiceError(ctx, NvResult::NotInitialized);
|
||||||
|
@ -173,24 +170,20 @@ void NVDRV::QueryEvent(Kernel::HLERequestContext& ctx) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto nv_result = nvdrv->VerifyFD(fd);
|
Kernel::KEvent* event = nullptr;
|
||||||
if (nv_result != NvResult::Success) {
|
NvResult result = nvdrv->QueryEvent(fd, event_id, event);
|
||||||
LOG_ERROR(Service_NVDRV, "Invalid FD specified DeviceFD={}!", fd);
|
|
||||||
ServiceError(ctx, nv_result);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (event_id < MaxNvEvents) {
|
if (result == NvResult::Success) {
|
||||||
IPC::ResponseBuilder rb{ctx, 3, 1};
|
IPC::ResponseBuilder rb{ctx, 3, 1};
|
||||||
rb.Push(ResultSuccess);
|
rb.Push(ResultSuccess);
|
||||||
auto& event = nvdrv->GetEvent(event_id);
|
auto& readable_event = event->GetReadableEvent();
|
||||||
event.Clear();
|
rb.PushCopyObjects(readable_event);
|
||||||
rb.PushCopyObjects(event);
|
|
||||||
rb.PushEnum(NvResult::Success);
|
rb.PushEnum(NvResult::Success);
|
||||||
} else {
|
} else {
|
||||||
|
LOG_ERROR(Service_NVDRV, "Invalid event request!");
|
||||||
IPC::ResponseBuilder rb{ctx, 3};
|
IPC::ResponseBuilder rb{ctx, 3};
|
||||||
rb.Push(ResultSuccess);
|
rb.Push(ResultSuccess);
|
||||||
rb.PushEnum(NvResult::BadParameter);
|
rb.PushEnum(result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,8 +18,6 @@ public:
|
||||||
explicit NVDRV(Core::System& system_, std::shared_ptr<Module> nvdrv_, const char* name);
|
explicit NVDRV(Core::System& system_, std::shared_ptr<Module> nvdrv_, const char* name);
|
||||||
~NVDRV() override;
|
~NVDRV() override;
|
||||||
|
|
||||||
void SignalGPUInterruptSyncpt(u32 syncpoint_id, u32 value);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void Open(Kernel::HLERequestContext& ctx);
|
void Open(Kernel::HLERequestContext& ctx);
|
||||||
void Ioctl1(Kernel::HLERequestContext& ctx);
|
void Ioctl1(Kernel::HLERequestContext& ctx);
|
||||||
|
|
|
@ -1,38 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
#include "common/assert.h"
|
|
||||||
#include "core/hle/service/nvdrv/syncpoint_manager.h"
|
|
||||||
#include "video_core/gpu.h"
|
|
||||||
|
|
||||||
namespace Service::Nvidia {
|
|
||||||
|
|
||||||
SyncpointManager::SyncpointManager(Tegra::GPU& gpu_) : gpu{gpu_} {}
|
|
||||||
|
|
||||||
SyncpointManager::~SyncpointManager() = default;
|
|
||||||
|
|
||||||
u32 SyncpointManager::RefreshSyncpoint(u32 syncpoint_id) {
|
|
||||||
syncpoints[syncpoint_id].min = gpu.GetSyncpointValue(syncpoint_id);
|
|
||||||
return GetSyncpointMin(syncpoint_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
u32 SyncpointManager::AllocateSyncpoint() {
|
|
||||||
for (u32 syncpoint_id = 1; syncpoint_id < MaxSyncPoints; syncpoint_id++) {
|
|
||||||
if (!syncpoints[syncpoint_id].is_allocated) {
|
|
||||||
syncpoints[syncpoint_id].is_allocated = true;
|
|
||||||
return syncpoint_id;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ASSERT_MSG(false, "No more available syncpoints!");
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
u32 SyncpointManager::IncreaseSyncpoint(u32 syncpoint_id, u32 value) {
|
|
||||||
for (u32 index = 0; index < value; ++index) {
|
|
||||||
syncpoints[syncpoint_id].max.fetch_add(1, std::memory_order_relaxed);
|
|
||||||
}
|
|
||||||
|
|
||||||
return GetSyncpointMax(syncpoint_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace Service::Nvidia
|
|
|
@ -1,84 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <array>
|
|
||||||
#include <atomic>
|
|
||||||
|
|
||||||
#include "common/common_types.h"
|
|
||||||
#include "core/hle/service/nvdrv/nvdata.h"
|
|
||||||
|
|
||||||
namespace Tegra {
|
|
||||||
class GPU;
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace Service::Nvidia {
|
|
||||||
|
|
||||||
class SyncpointManager final {
|
|
||||||
public:
|
|
||||||
explicit SyncpointManager(Tegra::GPU& gpu_);
|
|
||||||
~SyncpointManager();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if the specified syncpoint is expired for the given value.
|
|
||||||
* @param syncpoint_id Syncpoint ID to check.
|
|
||||||
* @param value Value to check against the specified syncpoint.
|
|
||||||
* @returns True if the specified syncpoint is expired for the given value, otherwise False.
|
|
||||||
*/
|
|
||||||
bool IsSyncpointExpired(u32 syncpoint_id, u32 value) const {
|
|
||||||
return (GetSyncpointMax(syncpoint_id) - value) >= (GetSyncpointMin(syncpoint_id) - value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the lower bound for the specified syncpoint.
|
|
||||||
* @param syncpoint_id Syncpoint ID to get the lower bound for.
|
|
||||||
* @returns The lower bound for the specified syncpoint.
|
|
||||||
*/
|
|
||||||
u32 GetSyncpointMin(u32 syncpoint_id) const {
|
|
||||||
return syncpoints.at(syncpoint_id).min.load(std::memory_order_relaxed);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the uper bound for the specified syncpoint.
|
|
||||||
* @param syncpoint_id Syncpoint ID to get the upper bound for.
|
|
||||||
* @returns The upper bound for the specified syncpoint.
|
|
||||||
*/
|
|
||||||
u32 GetSyncpointMax(u32 syncpoint_id) const {
|
|
||||||
return syncpoints.at(syncpoint_id).max.load(std::memory_order_relaxed);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Refreshes the minimum value for the specified syncpoint.
|
|
||||||
* @param syncpoint_id Syncpoint ID to be refreshed.
|
|
||||||
* @returns The new syncpoint minimum value.
|
|
||||||
*/
|
|
||||||
u32 RefreshSyncpoint(u32 syncpoint_id);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Allocates a new syncoint.
|
|
||||||
* @returns The syncpoint ID for the newly allocated syncpoint.
|
|
||||||
*/
|
|
||||||
u32 AllocateSyncpoint();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Increases the maximum value for the specified syncpoint.
|
|
||||||
* @param syncpoint_id Syncpoint ID to be increased.
|
|
||||||
* @param value Value to increase the specified syncpoint by.
|
|
||||||
* @returns The new syncpoint maximum value.
|
|
||||||
*/
|
|
||||||
u32 IncreaseSyncpoint(u32 syncpoint_id, u32 value);
|
|
||||||
|
|
||||||
private:
|
|
||||||
struct Syncpoint {
|
|
||||||
std::atomic<u32> min;
|
|
||||||
std::atomic<u32> max;
|
|
||||||
std::atomic<bool> is_allocated;
|
|
||||||
};
|
|
||||||
|
|
||||||
std::array<Syncpoint, MaxSyncPoints> syncpoints{};
|
|
||||||
|
|
||||||
Tegra::GPU& gpu;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace Service::Nvidia
|
|
|
@ -5,15 +5,18 @@
|
||||||
// https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/libs/gui/BufferQueueConsumer.cpp
|
// https://cs.android.com/android/platform/superproject/+/android-5.1.1_r38:frameworks/native/libs/gui/BufferQueueConsumer.cpp
|
||||||
|
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
|
#include "core/hle/service/nvdrv/core/nvmap.h"
|
||||||
#include "core/hle/service/nvflinger/buffer_item.h"
|
#include "core/hle/service/nvflinger/buffer_item.h"
|
||||||
#include "core/hle/service/nvflinger/buffer_queue_consumer.h"
|
#include "core/hle/service/nvflinger/buffer_queue_consumer.h"
|
||||||
#include "core/hle/service/nvflinger/buffer_queue_core.h"
|
#include "core/hle/service/nvflinger/buffer_queue_core.h"
|
||||||
#include "core/hle/service/nvflinger/producer_listener.h"
|
#include "core/hle/service/nvflinger/producer_listener.h"
|
||||||
|
#include "core/hle/service/nvflinger/ui/graphic_buffer.h"
|
||||||
|
|
||||||
namespace Service::android {
|
namespace Service::android {
|
||||||
|
|
||||||
BufferQueueConsumer::BufferQueueConsumer(std::shared_ptr<BufferQueueCore> core_)
|
BufferQueueConsumer::BufferQueueConsumer(std::shared_ptr<BufferQueueCore> core_,
|
||||||
: core{std::move(core_)}, slots{core->slots} {}
|
Service::Nvidia::NvCore::NvMap& nvmap_)
|
||||||
|
: core{std::move(core_)}, slots{core->slots}, nvmap(nvmap_) {}
|
||||||
|
|
||||||
BufferQueueConsumer::~BufferQueueConsumer() = default;
|
BufferQueueConsumer::~BufferQueueConsumer() = default;
|
||||||
|
|
||||||
|
@ -133,6 +136,8 @@ Status BufferQueueConsumer::ReleaseBuffer(s32 slot, u64 frame_number, const Fenc
|
||||||
|
|
||||||
slots[slot].buffer_state = BufferState::Free;
|
slots[slot].buffer_state = BufferState::Free;
|
||||||
|
|
||||||
|
nvmap.FreeHandle(slots[slot].graphic_buffer->BufferId(), true);
|
||||||
|
|
||||||
listener = core->connected_producer_listener;
|
listener = core->connected_producer_listener;
|
||||||
|
|
||||||
LOG_DEBUG(Service_NVFlinger, "releasing slot {}", slot);
|
LOG_DEBUG(Service_NVFlinger, "releasing slot {}", slot);
|
||||||
|
|
|
@ -13,6 +13,10 @@
|
||||||
#include "core/hle/service/nvflinger/buffer_queue_defs.h"
|
#include "core/hle/service/nvflinger/buffer_queue_defs.h"
|
||||||
#include "core/hle/service/nvflinger/status.h"
|
#include "core/hle/service/nvflinger/status.h"
|
||||||
|
|
||||||
|
namespace Service::Nvidia::NvCore {
|
||||||
|
class NvMap;
|
||||||
|
} // namespace Service::Nvidia::NvCore
|
||||||
|
|
||||||
namespace Service::android {
|
namespace Service::android {
|
||||||
|
|
||||||
class BufferItem;
|
class BufferItem;
|
||||||
|
@ -21,7 +25,8 @@ class IConsumerListener;
|
||||||
|
|
||||||
class BufferQueueConsumer final {
|
class BufferQueueConsumer final {
|
||||||
public:
|
public:
|
||||||
explicit BufferQueueConsumer(std::shared_ptr<BufferQueueCore> core_);
|
explicit BufferQueueConsumer(std::shared_ptr<BufferQueueCore> core_,
|
||||||
|
Service::Nvidia::NvCore::NvMap& nvmap_);
|
||||||
~BufferQueueConsumer();
|
~BufferQueueConsumer();
|
||||||
|
|
||||||
Status AcquireBuffer(BufferItem* out_buffer, std::chrono::nanoseconds expected_present);
|
Status AcquireBuffer(BufferItem* out_buffer, std::chrono::nanoseconds expected_present);
|
||||||
|
@ -32,6 +37,7 @@ public:
|
||||||
private:
|
private:
|
||||||
std::shared_ptr<BufferQueueCore> core;
|
std::shared_ptr<BufferQueueCore> core;
|
||||||
BufferQueueDefs::SlotsType& slots;
|
BufferQueueDefs::SlotsType& slots;
|
||||||
|
Service::Nvidia::NvCore::NvMap& nvmap;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Service::android
|
} // namespace Service::android
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
#include "core/hle/kernel/k_writable_event.h"
|
#include "core/hle/kernel/k_writable_event.h"
|
||||||
#include "core/hle/kernel/kernel.h"
|
#include "core/hle/kernel/kernel.h"
|
||||||
#include "core/hle/service/kernel_helpers.h"
|
#include "core/hle/service/kernel_helpers.h"
|
||||||
#include "core/hle/service/nvdrv/nvdrv.h"
|
#include "core/hle/service/nvdrv/core/nvmap.h"
|
||||||
#include "core/hle/service/nvflinger/buffer_queue_core.h"
|
#include "core/hle/service/nvflinger/buffer_queue_core.h"
|
||||||
#include "core/hle/service/nvflinger/buffer_queue_producer.h"
|
#include "core/hle/service/nvflinger/buffer_queue_producer.h"
|
||||||
#include "core/hle/service/nvflinger/consumer_listener.h"
|
#include "core/hle/service/nvflinger/consumer_listener.h"
|
||||||
|
@ -26,8 +26,10 @@
|
||||||
namespace Service::android {
|
namespace Service::android {
|
||||||
|
|
||||||
BufferQueueProducer::BufferQueueProducer(Service::KernelHelpers::ServiceContext& service_context_,
|
BufferQueueProducer::BufferQueueProducer(Service::KernelHelpers::ServiceContext& service_context_,
|
||||||
std::shared_ptr<BufferQueueCore> buffer_queue_core_)
|
std::shared_ptr<BufferQueueCore> buffer_queue_core_,
|
||||||
: service_context{service_context_}, core{std::move(buffer_queue_core_)}, slots(core->slots) {
|
Service::Nvidia::NvCore::NvMap& nvmap_)
|
||||||
|
: service_context{service_context_}, core{std::move(buffer_queue_core_)}, slots(core->slots),
|
||||||
|
nvmap(nvmap_) {
|
||||||
buffer_wait_event = service_context.CreateEvent("BufferQueue:WaitEvent");
|
buffer_wait_event = service_context.CreateEvent("BufferQueue:WaitEvent");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -530,6 +532,8 @@ Status BufferQueueProducer::QueueBuffer(s32 slot, const QueueBufferInput& input,
|
||||||
item.is_droppable = core->dequeue_buffer_cannot_block || async;
|
item.is_droppable = core->dequeue_buffer_cannot_block || async;
|
||||||
item.swap_interval = swap_interval;
|
item.swap_interval = swap_interval;
|
||||||
|
|
||||||
|
nvmap.DuplicateHandle(item.graphic_buffer->BufferId(), true);
|
||||||
|
|
||||||
sticky_transform = sticky_transform_;
|
sticky_transform = sticky_transform_;
|
||||||
|
|
||||||
if (core->queue.empty()) {
|
if (core->queue.empty()) {
|
||||||
|
|
|
@ -31,6 +31,10 @@ namespace Service::KernelHelpers {
|
||||||
class ServiceContext;
|
class ServiceContext;
|
||||||
} // namespace Service::KernelHelpers
|
} // namespace Service::KernelHelpers
|
||||||
|
|
||||||
|
namespace Service::Nvidia::NvCore {
|
||||||
|
class NvMap;
|
||||||
|
} // namespace Service::Nvidia::NvCore
|
||||||
|
|
||||||
namespace Service::android {
|
namespace Service::android {
|
||||||
|
|
||||||
class BufferQueueCore;
|
class BufferQueueCore;
|
||||||
|
@ -39,7 +43,8 @@ class IProducerListener;
|
||||||
class BufferQueueProducer final : public IBinder {
|
class BufferQueueProducer final : public IBinder {
|
||||||
public:
|
public:
|
||||||
explicit BufferQueueProducer(Service::KernelHelpers::ServiceContext& service_context_,
|
explicit BufferQueueProducer(Service::KernelHelpers::ServiceContext& service_context_,
|
||||||
std::shared_ptr<BufferQueueCore> buffer_queue_core_);
|
std::shared_ptr<BufferQueueCore> buffer_queue_core_,
|
||||||
|
Service::Nvidia::NvCore::NvMap& nvmap_);
|
||||||
~BufferQueueProducer();
|
~BufferQueueProducer();
|
||||||
|
|
||||||
void Transact(Kernel::HLERequestContext& ctx, android::TransactionId code, u32 flags) override;
|
void Transact(Kernel::HLERequestContext& ctx, android::TransactionId code, u32 flags) override;
|
||||||
|
@ -78,6 +83,8 @@ private:
|
||||||
s32 next_callback_ticket{};
|
s32 next_callback_ticket{};
|
||||||
s32 current_callback_ticket{};
|
s32 current_callback_ticket{};
|
||||||
std::condition_variable_any callback_condition;
|
std::condition_variable_any callback_condition;
|
||||||
|
|
||||||
|
Service::Nvidia::NvCore::NvMap& nvmap;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Service::android
|
} // namespace Service::android
|
||||||
|
|
|
@ -24,6 +24,8 @@
|
||||||
#include "core/hle/service/vi/layer/vi_layer.h"
|
#include "core/hle/service/vi/layer/vi_layer.h"
|
||||||
#include "core/hle/service/vi/vi_results.h"
|
#include "core/hle/service/vi/vi_results.h"
|
||||||
#include "video_core/gpu.h"
|
#include "video_core/gpu.h"
|
||||||
|
#include "video_core/host1x/host1x.h"
|
||||||
|
#include "video_core/host1x/syncpoint_manager.h"
|
||||||
|
|
||||||
namespace Service::NVFlinger {
|
namespace Service::NVFlinger {
|
||||||
|
|
||||||
|
@ -105,10 +107,15 @@ NVFlinger::~NVFlinger() {
|
||||||
display.GetLayer(layer).Core().NotifyShutdown();
|
display.GetLayer(layer).Core().NotifyShutdown();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (nvdrv) {
|
||||||
|
nvdrv->Close(disp_fd);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void NVFlinger::SetNVDrvInstance(std::shared_ptr<Nvidia::Module> instance) {
|
void NVFlinger::SetNVDrvInstance(std::shared_ptr<Nvidia::Module> instance) {
|
||||||
nvdrv = std::move(instance);
|
nvdrv = std::move(instance);
|
||||||
|
disp_fd = nvdrv->Open("/dev/nvdisp_disp0");
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<u64> NVFlinger::OpenDisplay(std::string_view name) {
|
std::optional<u64> NVFlinger::OpenDisplay(std::string_view name) {
|
||||||
|
@ -142,7 +149,7 @@ std::optional<u64> NVFlinger::CreateLayer(u64 display_id) {
|
||||||
|
|
||||||
void NVFlinger::CreateLayerAtId(VI::Display& display, u64 layer_id) {
|
void NVFlinger::CreateLayerAtId(VI::Display& display, u64 layer_id) {
|
||||||
const auto buffer_id = next_buffer_queue_id++;
|
const auto buffer_id = next_buffer_queue_id++;
|
||||||
display.CreateLayer(layer_id, buffer_id);
|
display.CreateLayer(layer_id, buffer_id, nvdrv->container);
|
||||||
}
|
}
|
||||||
|
|
||||||
void NVFlinger::CloseLayer(u64 layer_id) {
|
void NVFlinger::CloseLayer(u64 layer_id) {
|
||||||
|
@ -262,30 +269,24 @@ void NVFlinger::Compose() {
|
||||||
return; // We are likely shutting down
|
return; // We are likely shutting down
|
||||||
}
|
}
|
||||||
|
|
||||||
auto& gpu = system.GPU();
|
|
||||||
const auto& multi_fence = buffer.fence;
|
|
||||||
guard->unlock();
|
|
||||||
for (u32 fence_id = 0; fence_id < multi_fence.num_fences; fence_id++) {
|
|
||||||
const auto& fence = multi_fence.fences[fence_id];
|
|
||||||
gpu.WaitFence(fence.id, fence.value);
|
|
||||||
}
|
|
||||||
guard->lock();
|
|
||||||
|
|
||||||
MicroProfileFlip();
|
|
||||||
|
|
||||||
// Now send the buffer to the GPU for drawing.
|
// Now send the buffer to the GPU for drawing.
|
||||||
// TODO(Subv): Support more than just disp0. The display device selection is probably based
|
// TODO(Subv): Support more than just disp0. The display device selection is probably based
|
||||||
// on which display we're drawing (Default, Internal, External, etc)
|
// on which display we're drawing (Default, Internal, External, etc)
|
||||||
auto nvdisp = nvdrv->GetDevice<Nvidia::Devices::nvdisp_disp0>("/dev/nvdisp_disp0");
|
auto nvdisp = nvdrv->GetDevice<Nvidia::Devices::nvdisp_disp0>(disp_fd);
|
||||||
ASSERT(nvdisp);
|
ASSERT(nvdisp);
|
||||||
|
|
||||||
|
guard->unlock();
|
||||||
Common::Rectangle<int> crop_rect{
|
Common::Rectangle<int> crop_rect{
|
||||||
static_cast<int>(buffer.crop.Left()), static_cast<int>(buffer.crop.Top()),
|
static_cast<int>(buffer.crop.Left()), static_cast<int>(buffer.crop.Top()),
|
||||||
static_cast<int>(buffer.crop.Right()), static_cast<int>(buffer.crop.Bottom())};
|
static_cast<int>(buffer.crop.Right()), static_cast<int>(buffer.crop.Bottom())};
|
||||||
|
|
||||||
nvdisp->flip(igbp_buffer.BufferId(), igbp_buffer.Offset(), igbp_buffer.ExternalFormat(),
|
nvdisp->flip(igbp_buffer.BufferId(), igbp_buffer.Offset(), igbp_buffer.ExternalFormat(),
|
||||||
igbp_buffer.Width(), igbp_buffer.Height(), igbp_buffer.Stride(),
|
igbp_buffer.Width(), igbp_buffer.Height(), igbp_buffer.Stride(),
|
||||||
static_cast<android::BufferTransformFlags>(buffer.transform), crop_rect);
|
static_cast<android::BufferTransformFlags>(buffer.transform), crop_rect,
|
||||||
|
buffer.fence.fences, buffer.fence.num_fences);
|
||||||
|
|
||||||
|
MicroProfileFlip();
|
||||||
|
guard->lock();
|
||||||
|
|
||||||
swap_interval = buffer.swap_interval;
|
swap_interval = buffer.swap_interval;
|
||||||
|
|
||||||
|
|
|
@ -116,6 +116,7 @@ private:
|
||||||
void SplitVSync(std::stop_token stop_token);
|
void SplitVSync(std::stop_token stop_token);
|
||||||
|
|
||||||
std::shared_ptr<Nvidia::Module> nvdrv;
|
std::shared_ptr<Nvidia::Module> nvdrv;
|
||||||
|
s32 disp_fd;
|
||||||
|
|
||||||
std::list<VI::Display> displays;
|
std::list<VI::Display> displays;
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
#include "core/hle/kernel/k_readable_event.h"
|
#include "core/hle/kernel/k_readable_event.h"
|
||||||
#include "core/hle/kernel/k_writable_event.h"
|
#include "core/hle/kernel/k_writable_event.h"
|
||||||
#include "core/hle/service/kernel_helpers.h"
|
#include "core/hle/service/kernel_helpers.h"
|
||||||
|
#include "core/hle/service/nvdrv/core/container.h"
|
||||||
#include "core/hle/service/nvflinger/buffer_item_consumer.h"
|
#include "core/hle/service/nvflinger/buffer_item_consumer.h"
|
||||||
#include "core/hle/service/nvflinger/buffer_queue_consumer.h"
|
#include "core/hle/service/nvflinger/buffer_queue_consumer.h"
|
||||||
#include "core/hle/service/nvflinger/buffer_queue_core.h"
|
#include "core/hle/service/nvflinger/buffer_queue_core.h"
|
||||||
|
@ -29,11 +30,13 @@ struct BufferQueue {
|
||||||
std::unique_ptr<android::BufferQueueConsumer> consumer;
|
std::unique_ptr<android::BufferQueueConsumer> consumer;
|
||||||
};
|
};
|
||||||
|
|
||||||
static BufferQueue CreateBufferQueue(KernelHelpers::ServiceContext& service_context) {
|
static BufferQueue CreateBufferQueue(KernelHelpers::ServiceContext& service_context,
|
||||||
|
Service::Nvidia::NvCore::NvMap& nvmap) {
|
||||||
auto buffer_queue_core = std::make_shared<android::BufferQueueCore>();
|
auto buffer_queue_core = std::make_shared<android::BufferQueueCore>();
|
||||||
return {buffer_queue_core,
|
return {
|
||||||
std::make_unique<android::BufferQueueProducer>(service_context, buffer_queue_core),
|
buffer_queue_core,
|
||||||
std::make_unique<android::BufferQueueConsumer>(buffer_queue_core)};
|
std::make_unique<android::BufferQueueProducer>(service_context, buffer_queue_core, nvmap),
|
||||||
|
std::make_unique<android::BufferQueueConsumer>(buffer_queue_core, nvmap)};
|
||||||
}
|
}
|
||||||
|
|
||||||
Display::Display(u64 id, std::string name_,
|
Display::Display(u64 id, std::string name_,
|
||||||
|
@ -74,10 +77,11 @@ void Display::SignalVSyncEvent() {
|
||||||
vsync_event->GetWritableEvent().Signal();
|
vsync_event->GetWritableEvent().Signal();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Display::CreateLayer(u64 layer_id, u32 binder_id) {
|
void Display::CreateLayer(u64 layer_id, u32 binder_id,
|
||||||
|
Service::Nvidia::NvCore::Container& nv_core) {
|
||||||
ASSERT_MSG(layers.empty(), "Only one layer is supported per display at the moment");
|
ASSERT_MSG(layers.empty(), "Only one layer is supported per display at the moment");
|
||||||
|
|
||||||
auto [core, producer, consumer] = CreateBufferQueue(service_context);
|
auto [core, producer, consumer] = CreateBufferQueue(service_context, nv_core.GetNvMapFile());
|
||||||
|
|
||||||
auto buffer_item_consumer = std::make_shared<android::BufferItemConsumer>(std::move(consumer));
|
auto buffer_item_consumer = std::make_shared<android::BufferItemConsumer>(std::move(consumer));
|
||||||
buffer_item_consumer->Connect(false);
|
buffer_item_consumer->Connect(false);
|
||||||
|
|
|
@ -27,6 +27,11 @@ namespace Service::NVFlinger {
|
||||||
class HosBinderDriverServer;
|
class HosBinderDriverServer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace Service::Nvidia::NvCore {
|
||||||
|
class Container;
|
||||||
|
class NvMap;
|
||||||
|
} // namespace Service::Nvidia::NvCore
|
||||||
|
|
||||||
namespace Service::VI {
|
namespace Service::VI {
|
||||||
|
|
||||||
class Layer;
|
class Layer;
|
||||||
|
@ -93,7 +98,7 @@ public:
|
||||||
/// @param layer_id The ID to assign to the created layer.
|
/// @param layer_id The ID to assign to the created layer.
|
||||||
/// @param binder_id The ID assigned to the buffer queue.
|
/// @param binder_id The ID assigned to the buffer queue.
|
||||||
///
|
///
|
||||||
void CreateLayer(u64 layer_id, u32 binder_id);
|
void CreateLayer(u64 layer_id, u32 binder_id, Service::Nvidia::NvCore::Container& core);
|
||||||
|
|
||||||
/// Closes and removes a layer from this display with the given ID.
|
/// Closes and removes a layer from this display with the given ID.
|
||||||
///
|
///
|
||||||
|
|
|
@ -58,6 +58,7 @@ static_assert(sizeof(DisplayInfo) == 0x60, "DisplayInfo has wrong size");
|
||||||
class NativeWindow final {
|
class NativeWindow final {
|
||||||
public:
|
public:
|
||||||
constexpr explicit NativeWindow(u32 id_) : id{id_} {}
|
constexpr explicit NativeWindow(u32 id_) : id{id_} {}
|
||||||
|
constexpr explicit NativeWindow(const NativeWindow& other) = default;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
const u32 magic = 2;
|
const u32 magic = 2;
|
||||||
|
|
|
@ -551,6 +551,11 @@ struct Memory::Impl {
|
||||||
[]() {});
|
[]() {});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] u8* GetPointerSilent(const VAddr vaddr) const {
|
||||||
|
return GetPointerImpl(
|
||||||
|
vaddr, []() {}, []() {});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reads a particular data type out of memory at the given virtual address.
|
* Reads a particular data type out of memory at the given virtual address.
|
||||||
*
|
*
|
||||||
|
@ -686,6 +691,10 @@ u8* Memory::GetPointer(VAddr vaddr) {
|
||||||
return impl->GetPointer(vaddr);
|
return impl->GetPointer(vaddr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
u8* Memory::GetPointerSilent(VAddr vaddr) {
|
||||||
|
return impl->GetPointerSilent(vaddr);
|
||||||
|
}
|
||||||
|
|
||||||
const u8* Memory::GetPointer(VAddr vaddr) const {
|
const u8* Memory::GetPointer(VAddr vaddr) const {
|
||||||
return impl->GetPointer(vaddr);
|
return impl->GetPointer(vaddr);
|
||||||
}
|
}
|
||||||
|
|
|
@ -114,6 +114,7 @@ public:
|
||||||
* If the address is not valid, nullptr will be returned.
|
* If the address is not valid, nullptr will be returned.
|
||||||
*/
|
*/
|
||||||
u8* GetPointer(VAddr vaddr);
|
u8* GetPointer(VAddr vaddr);
|
||||||
|
u8* GetPointerSilent(VAddr vaddr);
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
T* GetPointer(VAddr vaddr) {
|
T* GetPointer(VAddr vaddr) {
|
||||||
|
|
|
@ -964,9 +964,9 @@ private:
|
||||||
demote_endif_node.type = Type::EndIf;
|
demote_endif_node.type = Type::EndIf;
|
||||||
demote_endif_node.data.end_if.merge = return_block_it->data.block;
|
demote_endif_node.data.end_if.merge = return_block_it->data.block;
|
||||||
|
|
||||||
asl.insert(return_block_it, demote_endif_node);
|
const auto next_it_1 = asl.insert(return_block_it, demote_endif_node);
|
||||||
asl.insert(return_block_it, demote_node);
|
const auto next_it_2 = asl.insert(next_it_1, demote_node);
|
||||||
asl.insert(return_block_it, demote_if_node);
|
asl.insert(next_it_2, demote_if_node);
|
||||||
}
|
}
|
||||||
|
|
||||||
ObjectPool<Statement>& stmt_pool;
|
ObjectPool<Statement>& stmt_pool;
|
||||||
|
|
|
@ -19,8 +19,10 @@ namespace {
|
||||||
struct ConstBufferAddr {
|
struct ConstBufferAddr {
|
||||||
u32 index;
|
u32 index;
|
||||||
u32 offset;
|
u32 offset;
|
||||||
|
u32 shift_left;
|
||||||
u32 secondary_index;
|
u32 secondary_index;
|
||||||
u32 secondary_offset;
|
u32 secondary_offset;
|
||||||
|
u32 secondary_shift_left;
|
||||||
IR::U32 dynamic_offset;
|
IR::U32 dynamic_offset;
|
||||||
u32 count;
|
u32 count;
|
||||||
bool has_secondary;
|
bool has_secondary;
|
||||||
|
@ -172,19 +174,41 @@ bool IsTextureInstruction(const IR::Inst& inst) {
|
||||||
return IndexedInstruction(inst) != IR::Opcode::Void;
|
return IndexedInstruction(inst) != IR::Opcode::Void;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<ConstBufferAddr> TryGetConstBuffer(const IR::Inst* inst);
|
std::optional<ConstBufferAddr> TryGetConstBuffer(const IR::Inst* inst, Environment& env);
|
||||||
|
|
||||||
std::optional<ConstBufferAddr> Track(const IR::Value& value) {
|
std::optional<ConstBufferAddr> Track(const IR::Value& value, Environment& env) {
|
||||||
return IR::BreadthFirstSearch(value, TryGetConstBuffer);
|
return IR::BreadthFirstSearch(
|
||||||
|
value, [&env](const IR::Inst* inst) { return TryGetConstBuffer(inst, env); });
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<ConstBufferAddr> TryGetConstBuffer(const IR::Inst* inst) {
|
std::optional<u32> TryGetConstant(IR::Value& value, Environment& env) {
|
||||||
|
const IR::Inst* inst = value.InstRecursive();
|
||||||
|
if (inst->GetOpcode() != IR::Opcode::GetCbufU32) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
const IR::Value index{inst->Arg(0)};
|
||||||
|
const IR::Value offset{inst->Arg(1)};
|
||||||
|
if (!index.IsImmediate()) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
if (!offset.IsImmediate()) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
const auto index_number = index.U32();
|
||||||
|
if (index_number != 1) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
const auto offset_number = offset.U32();
|
||||||
|
return env.ReadCbufValue(index_number, offset_number);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<ConstBufferAddr> TryGetConstBuffer(const IR::Inst* inst, Environment& env) {
|
||||||
switch (inst->GetOpcode()) {
|
switch (inst->GetOpcode()) {
|
||||||
default:
|
default:
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
case IR::Opcode::BitwiseOr32: {
|
case IR::Opcode::BitwiseOr32: {
|
||||||
std::optional lhs{Track(inst->Arg(0))};
|
std::optional lhs{Track(inst->Arg(0), env)};
|
||||||
std::optional rhs{Track(inst->Arg(1))};
|
std::optional rhs{Track(inst->Arg(1), env)};
|
||||||
if (!lhs || !rhs) {
|
if (!lhs || !rhs) {
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
@ -194,19 +218,62 @@ std::optional<ConstBufferAddr> TryGetConstBuffer(const IR::Inst* inst) {
|
||||||
if (lhs->count > 1 || rhs->count > 1) {
|
if (lhs->count > 1 || rhs->count > 1) {
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
if (lhs->index > rhs->index || lhs->offset > rhs->offset) {
|
if (lhs->shift_left > 0 || lhs->index > rhs->index || lhs->offset > rhs->offset) {
|
||||||
std::swap(lhs, rhs);
|
std::swap(lhs, rhs);
|
||||||
}
|
}
|
||||||
return ConstBufferAddr{
|
return ConstBufferAddr{
|
||||||
.index = lhs->index,
|
.index = lhs->index,
|
||||||
.offset = lhs->offset,
|
.offset = lhs->offset,
|
||||||
|
.shift_left = lhs->shift_left,
|
||||||
.secondary_index = rhs->index,
|
.secondary_index = rhs->index,
|
||||||
.secondary_offset = rhs->offset,
|
.secondary_offset = rhs->offset,
|
||||||
|
.secondary_shift_left = rhs->shift_left,
|
||||||
.dynamic_offset = {},
|
.dynamic_offset = {},
|
||||||
.count = 1,
|
.count = 1,
|
||||||
.has_secondary = true,
|
.has_secondary = true,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
case IR::Opcode::ShiftLeftLogical32: {
|
||||||
|
const IR::Value shift{inst->Arg(1)};
|
||||||
|
if (!shift.IsImmediate()) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
std::optional lhs{Track(inst->Arg(0), env)};
|
||||||
|
if (lhs) {
|
||||||
|
lhs->shift_left = shift.U32();
|
||||||
|
}
|
||||||
|
return lhs;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case IR::Opcode::BitwiseAnd32: {
|
||||||
|
IR::Value op1{inst->Arg(0)};
|
||||||
|
IR::Value op2{inst->Arg(1)};
|
||||||
|
if (op1.IsImmediate()) {
|
||||||
|
std::swap(op1, op2);
|
||||||
|
}
|
||||||
|
if (!op2.IsImmediate() && !op1.IsImmediate()) {
|
||||||
|
do {
|
||||||
|
auto try_index = TryGetConstant(op1, env);
|
||||||
|
if (try_index) {
|
||||||
|
op1 = op2;
|
||||||
|
op2 = IR::Value{*try_index};
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
auto try_index_2 = TryGetConstant(op2, env);
|
||||||
|
if (try_index_2) {
|
||||||
|
op2 = IR::Value{*try_index_2};
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return std::nullopt;
|
||||||
|
} while (false);
|
||||||
|
}
|
||||||
|
std::optional lhs{Track(op1, env)};
|
||||||
|
if (lhs) {
|
||||||
|
lhs->shift_left = static_cast<u32>(std::countr_zero(op2.U32()));
|
||||||
|
}
|
||||||
|
return lhs;
|
||||||
|
break;
|
||||||
|
}
|
||||||
case IR::Opcode::GetCbufU32x2:
|
case IR::Opcode::GetCbufU32x2:
|
||||||
case IR::Opcode::GetCbufU32:
|
case IR::Opcode::GetCbufU32:
|
||||||
break;
|
break;
|
||||||
|
@ -222,8 +289,10 @@ std::optional<ConstBufferAddr> TryGetConstBuffer(const IR::Inst* inst) {
|
||||||
return ConstBufferAddr{
|
return ConstBufferAddr{
|
||||||
.index = index.U32(),
|
.index = index.U32(),
|
||||||
.offset = offset.U32(),
|
.offset = offset.U32(),
|
||||||
|
.shift_left = 0,
|
||||||
.secondary_index = 0,
|
.secondary_index = 0,
|
||||||
.secondary_offset = 0,
|
.secondary_offset = 0,
|
||||||
|
.secondary_shift_left = 0,
|
||||||
.dynamic_offset = {},
|
.dynamic_offset = {},
|
||||||
.count = 1,
|
.count = 1,
|
||||||
.has_secondary = false,
|
.has_secondary = false,
|
||||||
|
@ -247,8 +316,10 @@ std::optional<ConstBufferAddr> TryGetConstBuffer(const IR::Inst* inst) {
|
||||||
return ConstBufferAddr{
|
return ConstBufferAddr{
|
||||||
.index = index.U32(),
|
.index = index.U32(),
|
||||||
.offset = base_offset,
|
.offset = base_offset,
|
||||||
|
.shift_left = 0,
|
||||||
.secondary_index = 0,
|
.secondary_index = 0,
|
||||||
.secondary_offset = 0,
|
.secondary_offset = 0,
|
||||||
|
.secondary_shift_left = 0,
|
||||||
.dynamic_offset = dynamic_offset,
|
.dynamic_offset = dynamic_offset,
|
||||||
.count = 8,
|
.count = 8,
|
||||||
.has_secondary = false,
|
.has_secondary = false,
|
||||||
|
@ -258,7 +329,7 @@ std::optional<ConstBufferAddr> TryGetConstBuffer(const IR::Inst* inst) {
|
||||||
TextureInst MakeInst(Environment& env, IR::Block* block, IR::Inst& inst) {
|
TextureInst MakeInst(Environment& env, IR::Block* block, IR::Inst& inst) {
|
||||||
ConstBufferAddr addr;
|
ConstBufferAddr addr;
|
||||||
if (IsBindless(inst)) {
|
if (IsBindless(inst)) {
|
||||||
const std::optional<ConstBufferAddr> track_addr{Track(inst.Arg(0))};
|
const std::optional<ConstBufferAddr> track_addr{Track(inst.Arg(0), env)};
|
||||||
if (!track_addr) {
|
if (!track_addr) {
|
||||||
throw NotImplementedException("Failed to track bindless texture constant buffer");
|
throw NotImplementedException("Failed to track bindless texture constant buffer");
|
||||||
}
|
}
|
||||||
|
@ -267,8 +338,10 @@ TextureInst MakeInst(Environment& env, IR::Block* block, IR::Inst& inst) {
|
||||||
addr = ConstBufferAddr{
|
addr = ConstBufferAddr{
|
||||||
.index = env.TextureBoundBuffer(),
|
.index = env.TextureBoundBuffer(),
|
||||||
.offset = inst.Arg(0).U32(),
|
.offset = inst.Arg(0).U32(),
|
||||||
|
.shift_left = 0,
|
||||||
.secondary_index = 0,
|
.secondary_index = 0,
|
||||||
.secondary_offset = 0,
|
.secondary_offset = 0,
|
||||||
|
.secondary_shift_left = 0,
|
||||||
.dynamic_offset = {},
|
.dynamic_offset = {},
|
||||||
.count = 1,
|
.count = 1,
|
||||||
.has_secondary = false,
|
.has_secondary = false,
|
||||||
|
@ -284,8 +357,9 @@ TextureInst MakeInst(Environment& env, IR::Block* block, IR::Inst& inst) {
|
||||||
TextureType ReadTextureType(Environment& env, const ConstBufferAddr& cbuf) {
|
TextureType ReadTextureType(Environment& env, const ConstBufferAddr& cbuf) {
|
||||||
const u32 secondary_index{cbuf.has_secondary ? cbuf.secondary_index : cbuf.index};
|
const u32 secondary_index{cbuf.has_secondary ? cbuf.secondary_index : cbuf.index};
|
||||||
const u32 secondary_offset{cbuf.has_secondary ? cbuf.secondary_offset : cbuf.offset};
|
const u32 secondary_offset{cbuf.has_secondary ? cbuf.secondary_offset : cbuf.offset};
|
||||||
const u32 lhs_raw{env.ReadCbufValue(cbuf.index, cbuf.offset)};
|
const u32 lhs_raw{env.ReadCbufValue(cbuf.index, cbuf.offset) << cbuf.shift_left};
|
||||||
const u32 rhs_raw{env.ReadCbufValue(secondary_index, secondary_offset)};
|
const u32 rhs_raw{env.ReadCbufValue(secondary_index, secondary_offset)
|
||||||
|
<< cbuf.secondary_shift_left};
|
||||||
return env.ReadTextureType(lhs_raw | rhs_raw);
|
return env.ReadTextureType(lhs_raw | rhs_raw);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -487,8 +561,10 @@ void TexturePass(Environment& env, IR::Program& program) {
|
||||||
.has_secondary = cbuf.has_secondary,
|
.has_secondary = cbuf.has_secondary,
|
||||||
.cbuf_index = cbuf.index,
|
.cbuf_index = cbuf.index,
|
||||||
.cbuf_offset = cbuf.offset,
|
.cbuf_offset = cbuf.offset,
|
||||||
|
.shift_left = cbuf.shift_left,
|
||||||
.secondary_cbuf_index = cbuf.secondary_index,
|
.secondary_cbuf_index = cbuf.secondary_index,
|
||||||
.secondary_cbuf_offset = cbuf.secondary_offset,
|
.secondary_cbuf_offset = cbuf.secondary_offset,
|
||||||
|
.secondary_shift_left = cbuf.secondary_shift_left,
|
||||||
.count = cbuf.count,
|
.count = cbuf.count,
|
||||||
.size_shift = DESCRIPTOR_SIZE_SHIFT,
|
.size_shift = DESCRIPTOR_SIZE_SHIFT,
|
||||||
});
|
});
|
||||||
|
@ -499,8 +575,10 @@ void TexturePass(Environment& env, IR::Program& program) {
|
||||||
.has_secondary = cbuf.has_secondary,
|
.has_secondary = cbuf.has_secondary,
|
||||||
.cbuf_index = cbuf.index,
|
.cbuf_index = cbuf.index,
|
||||||
.cbuf_offset = cbuf.offset,
|
.cbuf_offset = cbuf.offset,
|
||||||
|
.shift_left = cbuf.shift_left,
|
||||||
.secondary_cbuf_index = cbuf.secondary_index,
|
.secondary_cbuf_index = cbuf.secondary_index,
|
||||||
.secondary_cbuf_offset = cbuf.secondary_offset,
|
.secondary_cbuf_offset = cbuf.secondary_offset,
|
||||||
|
.secondary_shift_left = cbuf.secondary_shift_left,
|
||||||
.count = cbuf.count,
|
.count = cbuf.count,
|
||||||
.size_shift = DESCRIPTOR_SIZE_SHIFT,
|
.size_shift = DESCRIPTOR_SIZE_SHIFT,
|
||||||
});
|
});
|
||||||
|
|
|
@ -61,8 +61,10 @@ struct TextureBufferDescriptor {
|
||||||
bool has_secondary;
|
bool has_secondary;
|
||||||
u32 cbuf_index;
|
u32 cbuf_index;
|
||||||
u32 cbuf_offset;
|
u32 cbuf_offset;
|
||||||
|
u32 shift_left;
|
||||||
u32 secondary_cbuf_index;
|
u32 secondary_cbuf_index;
|
||||||
u32 secondary_cbuf_offset;
|
u32 secondary_cbuf_offset;
|
||||||
|
u32 secondary_shift_left;
|
||||||
u32 count;
|
u32 count;
|
||||||
u32 size_shift;
|
u32 size_shift;
|
||||||
};
|
};
|
||||||
|
@ -85,8 +87,10 @@ struct TextureDescriptor {
|
||||||
bool has_secondary;
|
bool has_secondary;
|
||||||
u32 cbuf_index;
|
u32 cbuf_index;
|
||||||
u32 cbuf_offset;
|
u32 cbuf_offset;
|
||||||
|
u32 shift_left;
|
||||||
u32 secondary_cbuf_index;
|
u32 secondary_cbuf_index;
|
||||||
u32 secondary_cbuf_offset;
|
u32 secondary_cbuf_offset;
|
||||||
|
u32 secondary_shift_left;
|
||||||
u32 count;
|
u32 count;
|
||||||
u32 size_shift;
|
u32 size_shift;
|
||||||
};
|
};
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
add_subdirectory(host_shaders)
|
add_subdirectory(host_shaders)
|
||||||
|
|
||||||
if(LIBVA_FOUND)
|
if(LIBVA_FOUND)
|
||||||
set_source_files_properties(command_classes/codecs/codec.cpp
|
set_source_files_properties(host1x/codecs/codec.cpp
|
||||||
PROPERTIES COMPILE_DEFINITIONS LIBVA_FOUND=1)
|
PROPERTIES COMPILE_DEFINITIONS LIBVA_FOUND=1)
|
||||||
list(APPEND FFmpeg_LIBRARIES ${LIBVA_LIBRARIES})
|
list(APPEND FFmpeg_LIBRARIES ${LIBVA_LIBRARIES})
|
||||||
endif()
|
endif()
|
||||||
|
@ -15,26 +15,14 @@ add_library(video_core STATIC
|
||||||
buffer_cache/buffer_cache.h
|
buffer_cache/buffer_cache.h
|
||||||
cdma_pusher.cpp
|
cdma_pusher.cpp
|
||||||
cdma_pusher.h
|
cdma_pusher.h
|
||||||
command_classes/codecs/codec.cpp
|
|
||||||
command_classes/codecs/codec.h
|
|
||||||
command_classes/codecs/h264.cpp
|
|
||||||
command_classes/codecs/h264.h
|
|
||||||
command_classes/codecs/vp8.cpp
|
|
||||||
command_classes/codecs/vp8.h
|
|
||||||
command_classes/codecs/vp9.cpp
|
|
||||||
command_classes/codecs/vp9.h
|
|
||||||
command_classes/codecs/vp9_types.h
|
|
||||||
command_classes/host1x.cpp
|
|
||||||
command_classes/host1x.h
|
|
||||||
command_classes/nvdec.cpp
|
|
||||||
command_classes/nvdec.h
|
|
||||||
command_classes/nvdec_common.h
|
|
||||||
command_classes/sync_manager.cpp
|
|
||||||
command_classes/sync_manager.h
|
|
||||||
command_classes/vic.cpp
|
|
||||||
command_classes/vic.h
|
|
||||||
compatible_formats.cpp
|
compatible_formats.cpp
|
||||||
compatible_formats.h
|
compatible_formats.h
|
||||||
|
control/channel_state.cpp
|
||||||
|
control/channel_state.h
|
||||||
|
control/channel_state_cache.cpp
|
||||||
|
control/channel_state_cache.h
|
||||||
|
control/scheduler.cpp
|
||||||
|
control/scheduler.h
|
||||||
delayed_destruction_ring.h
|
delayed_destruction_ring.h
|
||||||
dirty_flags.cpp
|
dirty_flags.cpp
|
||||||
dirty_flags.h
|
dirty_flags.h
|
||||||
|
@ -54,7 +42,31 @@ add_library(video_core STATIC
|
||||||
engines/maxwell_3d.h
|
engines/maxwell_3d.h
|
||||||
engines/maxwell_dma.cpp
|
engines/maxwell_dma.cpp
|
||||||
engines/maxwell_dma.h
|
engines/maxwell_dma.h
|
||||||
|
engines/puller.cpp
|
||||||
|
engines/puller.h
|
||||||
framebuffer_config.h
|
framebuffer_config.h
|
||||||
|
host1x/codecs/codec.cpp
|
||||||
|
host1x/codecs/codec.h
|
||||||
|
host1x/codecs/h264.cpp
|
||||||
|
host1x/codecs/h264.h
|
||||||
|
host1x/codecs/vp8.cpp
|
||||||
|
host1x/codecs/vp8.h
|
||||||
|
host1x/codecs/vp9.cpp
|
||||||
|
host1x/codecs/vp9.h
|
||||||
|
host1x/codecs/vp9_types.h
|
||||||
|
host1x/control.cpp
|
||||||
|
host1x/control.h
|
||||||
|
host1x/host1x.cpp
|
||||||
|
host1x/host1x.h
|
||||||
|
host1x/nvdec.cpp
|
||||||
|
host1x/nvdec.h
|
||||||
|
host1x/nvdec_common.h
|
||||||
|
host1x/sync_manager.cpp
|
||||||
|
host1x/sync_manager.h
|
||||||
|
host1x/syncpoint_manager.cpp
|
||||||
|
host1x/syncpoint_manager.h
|
||||||
|
host1x/vic.cpp
|
||||||
|
host1x/vic.h
|
||||||
macro/macro.cpp
|
macro/macro.cpp
|
||||||
macro/macro.h
|
macro/macro.h
|
||||||
macro/macro_hle.cpp
|
macro/macro_hle.cpp
|
||||||
|
@ -195,6 +207,7 @@ add_library(video_core STATIC
|
||||||
texture_cache/render_targets.h
|
texture_cache/render_targets.h
|
||||||
texture_cache/samples_helper.h
|
texture_cache/samples_helper.h
|
||||||
texture_cache/slot_vector.h
|
texture_cache/slot_vector.h
|
||||||
|
texture_cache/texture_cache.cpp
|
||||||
texture_cache/texture_cache.h
|
texture_cache/texture_cache.h
|
||||||
texture_cache/texture_cache_base.h
|
texture_cache/texture_cache_base.h
|
||||||
texture_cache/types.h
|
texture_cache/types.h
|
||||||
|
|
|
@ -5,7 +5,6 @@
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <deque>
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <numeric>
|
#include <numeric>
|
||||||
|
@ -23,6 +22,7 @@
|
||||||
#include "common/settings.h"
|
#include "common/settings.h"
|
||||||
#include "core/memory.h"
|
#include "core/memory.h"
|
||||||
#include "video_core/buffer_cache/buffer_base.h"
|
#include "video_core/buffer_cache/buffer_base.h"
|
||||||
|
#include "video_core/control/channel_state_cache.h"
|
||||||
#include "video_core/delayed_destruction_ring.h"
|
#include "video_core/delayed_destruction_ring.h"
|
||||||
#include "video_core/dirty_flags.h"
|
#include "video_core/dirty_flags.h"
|
||||||
#include "video_core/engines/kepler_compute.h"
|
#include "video_core/engines/kepler_compute.h"
|
||||||
|
@ -56,7 +56,7 @@ using UniformBufferSizes = std::array<std::array<u32, NUM_GRAPHICS_UNIFORM_BUFFE
|
||||||
using ComputeUniformBufferSizes = std::array<u32, NUM_COMPUTE_UNIFORM_BUFFERS>;
|
using ComputeUniformBufferSizes = std::array<u32, NUM_COMPUTE_UNIFORM_BUFFERS>;
|
||||||
|
|
||||||
template <typename P>
|
template <typename P>
|
||||||
class BufferCache {
|
class BufferCache : public VideoCommon::ChannelSetupCaches<VideoCommon::ChannelInfo> {
|
||||||
|
|
||||||
// Page size for caching purposes.
|
// Page size for caching purposes.
|
||||||
// This is unrelated to the CPU page size and it can be changed as it seems optimal.
|
// This is unrelated to the CPU page size and it can be changed as it seems optimal.
|
||||||
|
@ -116,10 +116,7 @@ public:
|
||||||
static constexpr u32 DEFAULT_SKIP_CACHE_SIZE = static_cast<u32>(4_KiB);
|
static constexpr u32 DEFAULT_SKIP_CACHE_SIZE = static_cast<u32>(4_KiB);
|
||||||
|
|
||||||
explicit BufferCache(VideoCore::RasterizerInterface& rasterizer_,
|
explicit BufferCache(VideoCore::RasterizerInterface& rasterizer_,
|
||||||
Tegra::Engines::Maxwell3D& maxwell3d_,
|
Core::Memory::Memory& cpu_memory_, Runtime& runtime_);
|
||||||
Tegra::Engines::KeplerCompute& kepler_compute_,
|
|
||||||
Tegra::MemoryManager& gpu_memory_, Core::Memory::Memory& cpu_memory_,
|
|
||||||
Runtime& runtime_);
|
|
||||||
|
|
||||||
void TickFrame();
|
void TickFrame();
|
||||||
|
|
||||||
|
@ -129,7 +126,7 @@ public:
|
||||||
|
|
||||||
void DownloadMemory(VAddr cpu_addr, u64 size);
|
void DownloadMemory(VAddr cpu_addr, u64 size);
|
||||||
|
|
||||||
bool InlineMemory(VAddr dest_address, size_t copy_size, std::span<u8> inlined_buffer);
|
bool InlineMemory(VAddr dest_address, size_t copy_size, std::span<const u8> inlined_buffer);
|
||||||
|
|
||||||
void BindGraphicsUniformBuffer(size_t stage, u32 index, GPUVAddr gpu_addr, u32 size);
|
void BindGraphicsUniformBuffer(size_t stage, u32 index, GPUVAddr gpu_addr, u32 size);
|
||||||
|
|
||||||
|
@ -353,7 +350,7 @@ private:
|
||||||
|
|
||||||
void NotifyBufferDeletion();
|
void NotifyBufferDeletion();
|
||||||
|
|
||||||
[[nodiscard]] Binding StorageBufferBinding(GPUVAddr ssbo_addr) const;
|
[[nodiscard]] Binding StorageBufferBinding(GPUVAddr ssbo_addr, bool is_written = false) const;
|
||||||
|
|
||||||
[[nodiscard]] TextureBufferBinding GetTextureBufferBinding(GPUVAddr gpu_addr, u32 size,
|
[[nodiscard]] TextureBufferBinding GetTextureBufferBinding(GPUVAddr gpu_addr, u32 size,
|
||||||
PixelFormat format);
|
PixelFormat format);
|
||||||
|
@ -367,9 +364,6 @@ private:
|
||||||
void ClearDownload(IntervalType subtract_interval);
|
void ClearDownload(IntervalType subtract_interval);
|
||||||
|
|
||||||
VideoCore::RasterizerInterface& rasterizer;
|
VideoCore::RasterizerInterface& rasterizer;
|
||||||
Tegra::Engines::Maxwell3D& maxwell3d;
|
|
||||||
Tegra::Engines::KeplerCompute& kepler_compute;
|
|
||||||
Tegra::MemoryManager& gpu_memory;
|
|
||||||
Core::Memory::Memory& cpu_memory;
|
Core::Memory::Memory& cpu_memory;
|
||||||
|
|
||||||
SlotVector<Buffer> slot_buffers;
|
SlotVector<Buffer> slot_buffers;
|
||||||
|
@ -444,12 +438,8 @@ private:
|
||||||
|
|
||||||
template <class P>
|
template <class P>
|
||||||
BufferCache<P>::BufferCache(VideoCore::RasterizerInterface& rasterizer_,
|
BufferCache<P>::BufferCache(VideoCore::RasterizerInterface& rasterizer_,
|
||||||
Tegra::Engines::Maxwell3D& maxwell3d_,
|
Core::Memory::Memory& cpu_memory_, Runtime& runtime_)
|
||||||
Tegra::Engines::KeplerCompute& kepler_compute_,
|
: runtime{runtime_}, rasterizer{rasterizer_}, cpu_memory{cpu_memory_} {
|
||||||
Tegra::MemoryManager& gpu_memory_, Core::Memory::Memory& cpu_memory_,
|
|
||||||
Runtime& runtime_)
|
|
||||||
: runtime{runtime_}, rasterizer{rasterizer_}, maxwell3d{maxwell3d_},
|
|
||||||
kepler_compute{kepler_compute_}, gpu_memory{gpu_memory_}, cpu_memory{cpu_memory_} {
|
|
||||||
// Ensure the first slot is used for the null buffer
|
// Ensure the first slot is used for the null buffer
|
||||||
void(slot_buffers.insert(runtime, NullBufferParams{}));
|
void(slot_buffers.insert(runtime, NullBufferParams{}));
|
||||||
common_ranges.clear();
|
common_ranges.clear();
|
||||||
|
@ -552,8 +542,8 @@ void BufferCache<P>::ClearDownload(IntervalType subtract_interval) {
|
||||||
|
|
||||||
template <class P>
|
template <class P>
|
||||||
bool BufferCache<P>::DMACopy(GPUVAddr src_address, GPUVAddr dest_address, u64 amount) {
|
bool BufferCache<P>::DMACopy(GPUVAddr src_address, GPUVAddr dest_address, u64 amount) {
|
||||||
const std::optional<VAddr> cpu_src_address = gpu_memory.GpuToCpuAddress(src_address);
|
const std::optional<VAddr> cpu_src_address = gpu_memory->GpuToCpuAddress(src_address);
|
||||||
const std::optional<VAddr> cpu_dest_address = gpu_memory.GpuToCpuAddress(dest_address);
|
const std::optional<VAddr> cpu_dest_address = gpu_memory->GpuToCpuAddress(dest_address);
|
||||||
if (!cpu_src_address || !cpu_dest_address) {
|
if (!cpu_src_address || !cpu_dest_address) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -611,7 +601,7 @@ bool BufferCache<P>::DMACopy(GPUVAddr src_address, GPUVAddr dest_address, u64 am
|
||||||
|
|
||||||
template <class P>
|
template <class P>
|
||||||
bool BufferCache<P>::DMAClear(GPUVAddr dst_address, u64 amount, u32 value) {
|
bool BufferCache<P>::DMAClear(GPUVAddr dst_address, u64 amount, u32 value) {
|
||||||
const std::optional<VAddr> cpu_dst_address = gpu_memory.GpuToCpuAddress(dst_address);
|
const std::optional<VAddr> cpu_dst_address = gpu_memory->GpuToCpuAddress(dst_address);
|
||||||
if (!cpu_dst_address) {
|
if (!cpu_dst_address) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -635,7 +625,7 @@ bool BufferCache<P>::DMAClear(GPUVAddr dst_address, u64 amount, u32 value) {
|
||||||
template <class P>
|
template <class P>
|
||||||
void BufferCache<P>::BindGraphicsUniformBuffer(size_t stage, u32 index, GPUVAddr gpu_addr,
|
void BufferCache<P>::BindGraphicsUniformBuffer(size_t stage, u32 index, GPUVAddr gpu_addr,
|
||||||
u32 size) {
|
u32 size) {
|
||||||
const std::optional<VAddr> cpu_addr = gpu_memory.GpuToCpuAddress(gpu_addr);
|
const std::optional<VAddr> cpu_addr = gpu_memory->GpuToCpuAddress(gpu_addr);
|
||||||
const Binding binding{
|
const Binding binding{
|
||||||
.cpu_addr = *cpu_addr,
|
.cpu_addr = *cpu_addr,
|
||||||
.size = size,
|
.size = size,
|
||||||
|
@ -673,7 +663,7 @@ void BufferCache<P>::BindHostGeometryBuffers(bool is_indexed) {
|
||||||
if (is_indexed) {
|
if (is_indexed) {
|
||||||
BindHostIndexBuffer();
|
BindHostIndexBuffer();
|
||||||
} else if constexpr (!HAS_FULL_INDEX_AND_PRIMITIVE_SUPPORT) {
|
} else if constexpr (!HAS_FULL_INDEX_AND_PRIMITIVE_SUPPORT) {
|
||||||
const auto& regs = maxwell3d.regs;
|
const auto& regs = maxwell3d->regs;
|
||||||
if (regs.draw.topology == Maxwell::PrimitiveTopology::Quads) {
|
if (regs.draw.topology == Maxwell::PrimitiveTopology::Quads) {
|
||||||
runtime.BindQuadArrayIndexBuffer(regs.vertex_buffer.first, regs.vertex_buffer.count);
|
runtime.BindQuadArrayIndexBuffer(regs.vertex_buffer.first, regs.vertex_buffer.count);
|
||||||
}
|
}
|
||||||
|
@ -733,9 +723,9 @@ void BufferCache<P>::BindGraphicsStorageBuffer(size_t stage, size_t ssbo_index,
|
||||||
enabled_storage_buffers[stage] |= 1U << ssbo_index;
|
enabled_storage_buffers[stage] |= 1U << ssbo_index;
|
||||||
written_storage_buffers[stage] |= (is_written ? 1U : 0U) << ssbo_index;
|
written_storage_buffers[stage] |= (is_written ? 1U : 0U) << ssbo_index;
|
||||||
|
|
||||||
const auto& cbufs = maxwell3d.state.shader_stages[stage];
|
const auto& cbufs = maxwell3d->state.shader_stages[stage];
|
||||||
const GPUVAddr ssbo_addr = cbufs.const_buffers[cbuf_index].address + cbuf_offset;
|
const GPUVAddr ssbo_addr = cbufs.const_buffers[cbuf_index].address + cbuf_offset;
|
||||||
storage_buffers[stage][ssbo_index] = StorageBufferBinding(ssbo_addr);
|
storage_buffers[stage][ssbo_index] = StorageBufferBinding(ssbo_addr, is_written);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <class P>
|
template <class P>
|
||||||
|
@ -770,12 +760,12 @@ void BufferCache<P>::BindComputeStorageBuffer(size_t ssbo_index, u32 cbuf_index,
|
||||||
enabled_compute_storage_buffers |= 1U << ssbo_index;
|
enabled_compute_storage_buffers |= 1U << ssbo_index;
|
||||||
written_compute_storage_buffers |= (is_written ? 1U : 0U) << ssbo_index;
|
written_compute_storage_buffers |= (is_written ? 1U : 0U) << ssbo_index;
|
||||||
|
|
||||||
const auto& launch_desc = kepler_compute.launch_description;
|
const auto& launch_desc = kepler_compute->launch_description;
|
||||||
ASSERT(((launch_desc.const_buffer_enable_mask >> cbuf_index) & 1) != 0);
|
ASSERT(((launch_desc.const_buffer_enable_mask >> cbuf_index) & 1) != 0);
|
||||||
|
|
||||||
const auto& cbufs = launch_desc.const_buffer_config;
|
const auto& cbufs = launch_desc.const_buffer_config;
|
||||||
const GPUVAddr ssbo_addr = cbufs[cbuf_index].Address() + cbuf_offset;
|
const GPUVAddr ssbo_addr = cbufs[cbuf_index].Address() + cbuf_offset;
|
||||||
compute_storage_buffers[ssbo_index] = StorageBufferBinding(ssbo_addr);
|
compute_storage_buffers[ssbo_index] = StorageBufferBinding(ssbo_addr, is_written);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <class P>
|
template <class P>
|
||||||
|
@ -836,6 +826,19 @@ void BufferCache<P>::CommitAsyncFlushesHigh() {
|
||||||
const bool is_accuracy_normal =
|
const bool is_accuracy_normal =
|
||||||
Settings::values.gpu_accuracy.GetValue() == Settings::GPUAccuracy::Normal;
|
Settings::values.gpu_accuracy.GetValue() == Settings::GPUAccuracy::Normal;
|
||||||
|
|
||||||
|
auto it = committed_ranges.begin();
|
||||||
|
while (it != committed_ranges.end()) {
|
||||||
|
auto& current_intervals = *it;
|
||||||
|
auto next_it = std::next(it);
|
||||||
|
while (next_it != committed_ranges.end()) {
|
||||||
|
for (auto& interval : *next_it) {
|
||||||
|
current_intervals.subtract(interval);
|
||||||
|
}
|
||||||
|
next_it++;
|
||||||
|
}
|
||||||
|
it++;
|
||||||
|
}
|
||||||
|
|
||||||
boost::container::small_vector<std::pair<BufferCopy, BufferId>, 1> downloads;
|
boost::container::small_vector<std::pair<BufferCopy, BufferId>, 1> downloads;
|
||||||
u64 total_size_bytes = 0;
|
u64 total_size_bytes = 0;
|
||||||
u64 largest_copy = 0;
|
u64 largest_copy = 0;
|
||||||
|
@ -991,19 +994,19 @@ void BufferCache<P>::BindHostIndexBuffer() {
|
||||||
const u32 size = index_buffer.size;
|
const u32 size = index_buffer.size;
|
||||||
SynchronizeBuffer(buffer, index_buffer.cpu_addr, size);
|
SynchronizeBuffer(buffer, index_buffer.cpu_addr, size);
|
||||||
if constexpr (HAS_FULL_INDEX_AND_PRIMITIVE_SUPPORT) {
|
if constexpr (HAS_FULL_INDEX_AND_PRIMITIVE_SUPPORT) {
|
||||||
const u32 new_offset = offset + maxwell3d.regs.index_array.first *
|
const u32 new_offset = offset + maxwell3d->regs.index_array.first *
|
||||||
maxwell3d.regs.index_array.FormatSizeInBytes();
|
maxwell3d->regs.index_array.FormatSizeInBytes();
|
||||||
runtime.BindIndexBuffer(buffer, new_offset, size);
|
runtime.BindIndexBuffer(buffer, new_offset, size);
|
||||||
} else {
|
} else {
|
||||||
runtime.BindIndexBuffer(maxwell3d.regs.draw.topology, maxwell3d.regs.index_array.format,
|
runtime.BindIndexBuffer(maxwell3d->regs.draw.topology, maxwell3d->regs.index_array.format,
|
||||||
maxwell3d.regs.index_array.first, maxwell3d.regs.index_array.count,
|
maxwell3d->regs.index_array.first,
|
||||||
buffer, offset, size);
|
maxwell3d->regs.index_array.count, buffer, offset, size);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
template <class P>
|
template <class P>
|
||||||
void BufferCache<P>::BindHostVertexBuffers() {
|
void BufferCache<P>::BindHostVertexBuffers() {
|
||||||
auto& flags = maxwell3d.dirty.flags;
|
auto& flags = maxwell3d->dirty.flags;
|
||||||
for (u32 index = 0; index < NUM_VERTEX_BUFFERS; ++index) {
|
for (u32 index = 0; index < NUM_VERTEX_BUFFERS; ++index) {
|
||||||
const Binding& binding = vertex_buffers[index];
|
const Binding& binding = vertex_buffers[index];
|
||||||
Buffer& buffer = slot_buffers[binding.buffer_id];
|
Buffer& buffer = slot_buffers[binding.buffer_id];
|
||||||
|
@ -1014,7 +1017,7 @@ void BufferCache<P>::BindHostVertexBuffers() {
|
||||||
}
|
}
|
||||||
flags[Dirty::VertexBuffer0 + index] = false;
|
flags[Dirty::VertexBuffer0 + index] = false;
|
||||||
|
|
||||||
const u32 stride = maxwell3d.regs.vertex_array[index].stride;
|
const u32 stride = maxwell3d->regs.vertex_array[index].stride;
|
||||||
const u32 offset = buffer.Offset(binding.cpu_addr);
|
const u32 offset = buffer.Offset(binding.cpu_addr);
|
||||||
runtime.BindVertexBuffer(index, buffer, offset, binding.size, stride);
|
runtime.BindVertexBuffer(index, buffer, offset, binding.size, stride);
|
||||||
}
|
}
|
||||||
|
@ -1154,7 +1157,7 @@ void BufferCache<P>::BindHostGraphicsTextureBuffers(size_t stage) {
|
||||||
|
|
||||||
template <class P>
|
template <class P>
|
||||||
void BufferCache<P>::BindHostTransformFeedbackBuffers() {
|
void BufferCache<P>::BindHostTransformFeedbackBuffers() {
|
||||||
if (maxwell3d.regs.tfb_enabled == 0) {
|
if (maxwell3d->regs.tfb_enabled == 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
for (u32 index = 0; index < NUM_TRANSFORM_FEEDBACK_BUFFERS; ++index) {
|
for (u32 index = 0; index < NUM_TRANSFORM_FEEDBACK_BUFFERS; ++index) {
|
||||||
|
@ -1239,6 +1242,8 @@ void BufferCache<P>::BindHostComputeTextureBuffers() {
|
||||||
|
|
||||||
template <class P>
|
template <class P>
|
||||||
void BufferCache<P>::DoUpdateGraphicsBuffers(bool is_indexed) {
|
void BufferCache<P>::DoUpdateGraphicsBuffers(bool is_indexed) {
|
||||||
|
do {
|
||||||
|
has_deleted_buffers = false;
|
||||||
if (is_indexed) {
|
if (is_indexed) {
|
||||||
UpdateIndexBuffer();
|
UpdateIndexBuffer();
|
||||||
}
|
}
|
||||||
|
@ -1249,6 +1254,7 @@ void BufferCache<P>::DoUpdateGraphicsBuffers(bool is_indexed) {
|
||||||
UpdateStorageBuffers(stage);
|
UpdateStorageBuffers(stage);
|
||||||
UpdateTextureBuffers(stage);
|
UpdateTextureBuffers(stage);
|
||||||
}
|
}
|
||||||
|
} while (has_deleted_buffers);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <class P>
|
template <class P>
|
||||||
|
@ -1262,8 +1268,8 @@ template <class P>
|
||||||
void BufferCache<P>::UpdateIndexBuffer() {
|
void BufferCache<P>::UpdateIndexBuffer() {
|
||||||
// We have to check for the dirty flags and index count
|
// We have to check for the dirty flags and index count
|
||||||
// The index count is currently changed without updating the dirty flags
|
// The index count is currently changed without updating the dirty flags
|
||||||
const auto& index_array = maxwell3d.regs.index_array;
|
const auto& index_array = maxwell3d->regs.index_array;
|
||||||
auto& flags = maxwell3d.dirty.flags;
|
auto& flags = maxwell3d->dirty.flags;
|
||||||
if (!flags[Dirty::IndexBuffer] && last_index_count == index_array.count) {
|
if (!flags[Dirty::IndexBuffer] && last_index_count == index_array.count) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -1272,7 +1278,7 @@ void BufferCache<P>::UpdateIndexBuffer() {
|
||||||
|
|
||||||
const GPUVAddr gpu_addr_begin = index_array.StartAddress();
|
const GPUVAddr gpu_addr_begin = index_array.StartAddress();
|
||||||
const GPUVAddr gpu_addr_end = index_array.EndAddress();
|
const GPUVAddr gpu_addr_end = index_array.EndAddress();
|
||||||
const std::optional<VAddr> cpu_addr = gpu_memory.GpuToCpuAddress(gpu_addr_begin);
|
const std::optional<VAddr> cpu_addr = gpu_memory->GpuToCpuAddress(gpu_addr_begin);
|
||||||
const u32 address_size = static_cast<u32>(gpu_addr_end - gpu_addr_begin);
|
const u32 address_size = static_cast<u32>(gpu_addr_end - gpu_addr_begin);
|
||||||
const u32 draw_size = (index_array.count + index_array.first) * index_array.FormatSizeInBytes();
|
const u32 draw_size = (index_array.count + index_array.first) * index_array.FormatSizeInBytes();
|
||||||
const u32 size = std::min(address_size, draw_size);
|
const u32 size = std::min(address_size, draw_size);
|
||||||
|
@ -1289,8 +1295,8 @@ void BufferCache<P>::UpdateIndexBuffer() {
|
||||||
|
|
||||||
template <class P>
|
template <class P>
|
||||||
void BufferCache<P>::UpdateVertexBuffers() {
|
void BufferCache<P>::UpdateVertexBuffers() {
|
||||||
auto& flags = maxwell3d.dirty.flags;
|
auto& flags = maxwell3d->dirty.flags;
|
||||||
if (!maxwell3d.dirty.flags[Dirty::VertexBuffers]) {
|
if (!maxwell3d->dirty.flags[Dirty::VertexBuffers]) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
flags[Dirty::VertexBuffers] = false;
|
flags[Dirty::VertexBuffers] = false;
|
||||||
|
@ -1302,33 +1308,25 @@ void BufferCache<P>::UpdateVertexBuffers() {
|
||||||
|
|
||||||
template <class P>
|
template <class P>
|
||||||
void BufferCache<P>::UpdateVertexBuffer(u32 index) {
|
void BufferCache<P>::UpdateVertexBuffer(u32 index) {
|
||||||
if (!maxwell3d.dirty.flags[Dirty::VertexBuffer0 + index]) {
|
if (!maxwell3d->dirty.flags[Dirty::VertexBuffer0 + index]) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const auto& array = maxwell3d.regs.vertex_array[index];
|
const auto& array = maxwell3d->regs.vertex_array[index];
|
||||||
const auto& limit = maxwell3d.regs.vertex_array_limit[index];
|
const auto& limit = maxwell3d->regs.vertex_array_limit[index];
|
||||||
const GPUVAddr gpu_addr_begin = array.StartAddress();
|
const GPUVAddr gpu_addr_begin = array.StartAddress();
|
||||||
const GPUVAddr gpu_addr_end = limit.LimitAddress() + 1;
|
const GPUVAddr gpu_addr_end = limit.LimitAddress() + 1;
|
||||||
const std::optional<VAddr> cpu_addr = gpu_memory.GpuToCpuAddress(gpu_addr_begin);
|
const std::optional<VAddr> cpu_addr = gpu_memory->GpuToCpuAddress(gpu_addr_begin);
|
||||||
u32 address_size = static_cast<u32>(gpu_addr_end - gpu_addr_begin);
|
u32 address_size = static_cast<u32>(
|
||||||
if (address_size >= 64_MiB) {
|
std::min(gpu_addr_end - gpu_addr_begin, static_cast<u64>(std::numeric_limits<u32>::max())));
|
||||||
// Reported vertex buffer size is very large, cap to mapped buffer size
|
if (array.enable == 0 || address_size == 0 || !cpu_addr) {
|
||||||
GPUVAddr submapped_addr_end = gpu_addr_begin;
|
|
||||||
|
|
||||||
const auto ranges{gpu_memory.GetSubmappedRange(gpu_addr_begin, address_size)};
|
|
||||||
if (ranges.size() > 0) {
|
|
||||||
const auto& [addr, size] = *ranges.begin();
|
|
||||||
submapped_addr_end = addr + size;
|
|
||||||
}
|
|
||||||
|
|
||||||
address_size =
|
|
||||||
std::min(address_size, static_cast<u32>(submapped_addr_end - gpu_addr_begin));
|
|
||||||
}
|
|
||||||
const u32 size = address_size; // TODO: Analyze stride and number of vertices
|
|
||||||
if (array.enable == 0 || size == 0 || !cpu_addr) {
|
|
||||||
vertex_buffers[index] = NULL_BINDING;
|
vertex_buffers[index] = NULL_BINDING;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (!gpu_memory->IsWithinGPUAddressRange(gpu_addr_end)) {
|
||||||
|
address_size =
|
||||||
|
static_cast<u32>(gpu_memory->MaxContinousRange(gpu_addr_begin, address_size));
|
||||||
|
}
|
||||||
|
const u32 size = address_size; // TODO: Analyze stride and number of vertices
|
||||||
vertex_buffers[index] = Binding{
|
vertex_buffers[index] = Binding{
|
||||||
.cpu_addr = *cpu_addr,
|
.cpu_addr = *cpu_addr,
|
||||||
.size = size,
|
.size = size,
|
||||||
|
@ -1382,7 +1380,7 @@ void BufferCache<P>::UpdateTextureBuffers(size_t stage) {
|
||||||
|
|
||||||
template <class P>
|
template <class P>
|
||||||
void BufferCache<P>::UpdateTransformFeedbackBuffers() {
|
void BufferCache<P>::UpdateTransformFeedbackBuffers() {
|
||||||
if (maxwell3d.regs.tfb_enabled == 0) {
|
if (maxwell3d->regs.tfb_enabled == 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
for (u32 index = 0; index < NUM_TRANSFORM_FEEDBACK_BUFFERS; ++index) {
|
for (u32 index = 0; index < NUM_TRANSFORM_FEEDBACK_BUFFERS; ++index) {
|
||||||
|
@ -1392,10 +1390,10 @@ void BufferCache<P>::UpdateTransformFeedbackBuffers() {
|
||||||
|
|
||||||
template <class P>
|
template <class P>
|
||||||
void BufferCache<P>::UpdateTransformFeedbackBuffer(u32 index) {
|
void BufferCache<P>::UpdateTransformFeedbackBuffer(u32 index) {
|
||||||
const auto& binding = maxwell3d.regs.tfb_bindings[index];
|
const auto& binding = maxwell3d->regs.tfb_bindings[index];
|
||||||
const GPUVAddr gpu_addr = binding.Address() + binding.buffer_offset;
|
const GPUVAddr gpu_addr = binding.Address() + binding.buffer_offset;
|
||||||
const u32 size = binding.buffer_size;
|
const u32 size = binding.buffer_size;
|
||||||
const std::optional<VAddr> cpu_addr = gpu_memory.GpuToCpuAddress(gpu_addr);
|
const std::optional<VAddr> cpu_addr = gpu_memory->GpuToCpuAddress(gpu_addr);
|
||||||
if (binding.buffer_enable == 0 || size == 0 || !cpu_addr) {
|
if (binding.buffer_enable == 0 || size == 0 || !cpu_addr) {
|
||||||
transform_feedback_buffers[index] = NULL_BINDING;
|
transform_feedback_buffers[index] = NULL_BINDING;
|
||||||
return;
|
return;
|
||||||
|
@ -1414,10 +1412,10 @@ void BufferCache<P>::UpdateComputeUniformBuffers() {
|
||||||
ForEachEnabledBit(enabled_compute_uniform_buffer_mask, [&](u32 index) {
|
ForEachEnabledBit(enabled_compute_uniform_buffer_mask, [&](u32 index) {
|
||||||
Binding& binding = compute_uniform_buffers[index];
|
Binding& binding = compute_uniform_buffers[index];
|
||||||
binding = NULL_BINDING;
|
binding = NULL_BINDING;
|
||||||
const auto& launch_desc = kepler_compute.launch_description;
|
const auto& launch_desc = kepler_compute->launch_description;
|
||||||
if (((launch_desc.const_buffer_enable_mask >> index) & 1) != 0) {
|
if (((launch_desc.const_buffer_enable_mask >> index) & 1) != 0) {
|
||||||
const auto& cbuf = launch_desc.const_buffer_config[index];
|
const auto& cbuf = launch_desc.const_buffer_config[index];
|
||||||
const std::optional<VAddr> cpu_addr = gpu_memory.GpuToCpuAddress(cbuf.Address());
|
const std::optional<VAddr> cpu_addr = gpu_memory->GpuToCpuAddress(cbuf.Address());
|
||||||
if (cpu_addr) {
|
if (cpu_addr) {
|
||||||
binding.cpu_addr = *cpu_addr;
|
binding.cpu_addr = *cpu_addr;
|
||||||
binding.size = cbuf.size;
|
binding.size = cbuf.size;
|
||||||
|
@ -1567,6 +1565,8 @@ BufferId BufferCache<P>::CreateBuffer(VAddr cpu_addr, u32 wanted_size) {
|
||||||
const OverlapResult overlap = ResolveOverlaps(cpu_addr, wanted_size);
|
const OverlapResult overlap = ResolveOverlaps(cpu_addr, wanted_size);
|
||||||
const u32 size = static_cast<u32>(overlap.end - overlap.begin);
|
const u32 size = static_cast<u32>(overlap.end - overlap.begin);
|
||||||
const BufferId new_buffer_id = slot_buffers.insert(runtime, rasterizer, overlap.begin, size);
|
const BufferId new_buffer_id = slot_buffers.insert(runtime, rasterizer, overlap.begin, size);
|
||||||
|
auto& new_buffer = slot_buffers[new_buffer_id];
|
||||||
|
runtime.ClearBuffer(new_buffer, 0, new_buffer.SizeBytes(), 0);
|
||||||
for (const BufferId overlap_id : overlap.ids) {
|
for (const BufferId overlap_id : overlap.ids) {
|
||||||
JoinOverlap(new_buffer_id, overlap_id, !overlap.has_stream_leap);
|
JoinOverlap(new_buffer_id, overlap_id, !overlap.has_stream_leap);
|
||||||
}
|
}
|
||||||
|
@ -1695,7 +1695,7 @@ void BufferCache<P>::MappedUploadMemory(Buffer& buffer, u64 total_size_bytes,
|
||||||
|
|
||||||
template <class P>
|
template <class P>
|
||||||
bool BufferCache<P>::InlineMemory(VAddr dest_address, size_t copy_size,
|
bool BufferCache<P>::InlineMemory(VAddr dest_address, size_t copy_size,
|
||||||
std::span<u8> inlined_buffer) {
|
std::span<const u8> inlined_buffer) {
|
||||||
const bool is_dirty = IsRegionRegistered(dest_address, copy_size);
|
const bool is_dirty = IsRegionRegistered(dest_address, copy_size);
|
||||||
if (!is_dirty) {
|
if (!is_dirty) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -1831,7 +1831,7 @@ void BufferCache<P>::NotifyBufferDeletion() {
|
||||||
dirty_uniform_buffers.fill(~u32{0});
|
dirty_uniform_buffers.fill(~u32{0});
|
||||||
uniform_buffer_binding_sizes.fill({});
|
uniform_buffer_binding_sizes.fill({});
|
||||||
}
|
}
|
||||||
auto& flags = maxwell3d.dirty.flags;
|
auto& flags = maxwell3d->dirty.flags;
|
||||||
flags[Dirty::IndexBuffer] = true;
|
flags[Dirty::IndexBuffer] = true;
|
||||||
flags[Dirty::VertexBuffers] = true;
|
flags[Dirty::VertexBuffers] = true;
|
||||||
for (u32 index = 0; index < NUM_VERTEX_BUFFERS; ++index) {
|
for (u32 index = 0; index < NUM_VERTEX_BUFFERS; ++index) {
|
||||||
|
@ -1841,16 +1841,18 @@ void BufferCache<P>::NotifyBufferDeletion() {
|
||||||
}
|
}
|
||||||
|
|
||||||
template <class P>
|
template <class P>
|
||||||
typename BufferCache<P>::Binding BufferCache<P>::StorageBufferBinding(GPUVAddr ssbo_addr) const {
|
typename BufferCache<P>::Binding BufferCache<P>::StorageBufferBinding(GPUVAddr ssbo_addr,
|
||||||
const GPUVAddr gpu_addr = gpu_memory.Read<u64>(ssbo_addr);
|
bool is_written) const {
|
||||||
const u32 size = gpu_memory.Read<u32>(ssbo_addr + 8);
|
const GPUVAddr gpu_addr = gpu_memory->Read<u64>(ssbo_addr);
|
||||||
const std::optional<VAddr> cpu_addr = gpu_memory.GpuToCpuAddress(gpu_addr);
|
const u32 size = gpu_memory->Read<u32>(ssbo_addr + 8);
|
||||||
|
const std::optional<VAddr> cpu_addr = gpu_memory->GpuToCpuAddress(gpu_addr);
|
||||||
if (!cpu_addr || size == 0) {
|
if (!cpu_addr || size == 0) {
|
||||||
return NULL_BINDING;
|
return NULL_BINDING;
|
||||||
}
|
}
|
||||||
|
const VAddr cpu_end = Common::AlignUp(*cpu_addr + size, Core::Memory::YUZU_PAGESIZE);
|
||||||
const Binding binding{
|
const Binding binding{
|
||||||
.cpu_addr = *cpu_addr,
|
.cpu_addr = *cpu_addr,
|
||||||
.size = size,
|
.size = is_written ? size : static_cast<u32>(cpu_end - *cpu_addr),
|
||||||
.buffer_id = BufferId{},
|
.buffer_id = BufferId{},
|
||||||
};
|
};
|
||||||
return binding;
|
return binding;
|
||||||
|
@ -1859,7 +1861,7 @@ typename BufferCache<P>::Binding BufferCache<P>::StorageBufferBinding(GPUVAddr s
|
||||||
template <class P>
|
template <class P>
|
||||||
typename BufferCache<P>::TextureBufferBinding BufferCache<P>::GetTextureBufferBinding(
|
typename BufferCache<P>::TextureBufferBinding BufferCache<P>::GetTextureBufferBinding(
|
||||||
GPUVAddr gpu_addr, u32 size, PixelFormat format) {
|
GPUVAddr gpu_addr, u32 size, PixelFormat format) {
|
||||||
const std::optional<VAddr> cpu_addr = gpu_memory.GpuToCpuAddress(gpu_addr);
|
const std::optional<VAddr> cpu_addr = gpu_memory->GpuToCpuAddress(gpu_addr);
|
||||||
TextureBufferBinding binding;
|
TextureBufferBinding binding;
|
||||||
if (!cpu_addr || size == 0) {
|
if (!cpu_addr || size == 0) {
|
||||||
binding.cpu_addr = 0;
|
binding.cpu_addr = 0;
|
||||||
|
|
|
@ -2,20 +2,22 @@
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#include <bit>
|
#include <bit>
|
||||||
#include "command_classes/host1x.h"
|
|
||||||
#include "command_classes/nvdec.h"
|
|
||||||
#include "command_classes/vic.h"
|
|
||||||
#include "video_core/cdma_pusher.h"
|
#include "video_core/cdma_pusher.h"
|
||||||
#include "video_core/command_classes/sync_manager.h"
|
|
||||||
#include "video_core/engines/maxwell_3d.h"
|
#include "video_core/engines/maxwell_3d.h"
|
||||||
#include "video_core/gpu.h"
|
#include "video_core/host1x/control.h"
|
||||||
|
#include "video_core/host1x/host1x.h"
|
||||||
|
#include "video_core/host1x/nvdec.h"
|
||||||
|
#include "video_core/host1x/nvdec_common.h"
|
||||||
|
#include "video_core/host1x/sync_manager.h"
|
||||||
|
#include "video_core/host1x/vic.h"
|
||||||
|
#include "video_core/memory_manager.h"
|
||||||
|
|
||||||
namespace Tegra {
|
namespace Tegra {
|
||||||
CDmaPusher::CDmaPusher(GPU& gpu_)
|
CDmaPusher::CDmaPusher(Host1x::Host1x& host1x_)
|
||||||
: gpu{gpu_}, nvdec_processor(std::make_shared<Nvdec>(gpu)),
|
: host1x{host1x_}, nvdec_processor(std::make_shared<Host1x::Nvdec>(host1x)),
|
||||||
vic_processor(std::make_unique<Vic>(gpu, nvdec_processor)),
|
vic_processor(std::make_unique<Host1x::Vic>(host1x, nvdec_processor)),
|
||||||
host1x_processor(std::make_unique<Host1x>(gpu)),
|
host1x_processor(std::make_unique<Host1x::Control>(host1x)),
|
||||||
sync_manager(std::make_unique<SyncptIncrManager>(gpu)) {}
|
sync_manager(std::make_unique<Host1x::SyncptIncrManager>(host1x)) {}
|
||||||
|
|
||||||
CDmaPusher::~CDmaPusher() = default;
|
CDmaPusher::~CDmaPusher() = default;
|
||||||
|
|
||||||
|
@ -109,16 +111,17 @@ void CDmaPusher::ExecuteCommand(u32 state_offset, u32 data) {
|
||||||
case ThiMethod::SetMethod1:
|
case ThiMethod::SetMethod1:
|
||||||
LOG_DEBUG(Service_NVDRV, "VIC method 0x{:X}, Args=({})",
|
LOG_DEBUG(Service_NVDRV, "VIC method 0x{:X}, Args=({})",
|
||||||
static_cast<u32>(vic_thi_state.method_0), data);
|
static_cast<u32>(vic_thi_state.method_0), data);
|
||||||
vic_processor->ProcessMethod(static_cast<Vic::Method>(vic_thi_state.method_0), data);
|
vic_processor->ProcessMethod(static_cast<Host1x::Vic::Method>(vic_thi_state.method_0),
|
||||||
|
data);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case ChClassId::Host1x:
|
case ChClassId::Control:
|
||||||
// This device is mainly for syncpoint synchronization
|
// This device is mainly for syncpoint synchronization
|
||||||
LOG_DEBUG(Service_NVDRV, "Host1X Class Method");
|
LOG_DEBUG(Service_NVDRV, "Host1X Class Method");
|
||||||
host1x_processor->ProcessMethod(static_cast<Host1x::Method>(offset), data);
|
host1x_processor->ProcessMethod(static_cast<Host1x::Control::Method>(offset), data);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
UNIMPLEMENTED_MSG("Current class not implemented {:X}", static_cast<u32>(current_class));
|
UNIMPLEMENTED_MSG("Current class not implemented {:X}", static_cast<u32>(current_class));
|
||||||
|
|
|
@ -12,11 +12,13 @@
|
||||||
|
|
||||||
namespace Tegra {
|
namespace Tegra {
|
||||||
|
|
||||||
class GPU;
|
namespace Host1x {
|
||||||
|
class Control;
|
||||||
class Host1x;
|
class Host1x;
|
||||||
class Nvdec;
|
class Nvdec;
|
||||||
class SyncptIncrManager;
|
class SyncptIncrManager;
|
||||||
class Vic;
|
class Vic;
|
||||||
|
} // namespace Host1x
|
||||||
|
|
||||||
enum class ChSubmissionMode : u32 {
|
enum class ChSubmissionMode : u32 {
|
||||||
SetClass = 0,
|
SetClass = 0,
|
||||||
|
@ -30,7 +32,7 @@ enum class ChSubmissionMode : u32 {
|
||||||
|
|
||||||
enum class ChClassId : u32 {
|
enum class ChClassId : u32 {
|
||||||
NoClass = 0x0,
|
NoClass = 0x0,
|
||||||
Host1x = 0x1,
|
Control = 0x1,
|
||||||
VideoEncodeMpeg = 0x20,
|
VideoEncodeMpeg = 0x20,
|
||||||
VideoEncodeNvEnc = 0x21,
|
VideoEncodeNvEnc = 0x21,
|
||||||
VideoStreamingVi = 0x30,
|
VideoStreamingVi = 0x30,
|
||||||
|
@ -88,7 +90,7 @@ enum class ThiMethod : u32 {
|
||||||
|
|
||||||
class CDmaPusher {
|
class CDmaPusher {
|
||||||
public:
|
public:
|
||||||
explicit CDmaPusher(GPU& gpu_);
|
explicit CDmaPusher(Host1x::Host1x& host1x);
|
||||||
~CDmaPusher();
|
~CDmaPusher();
|
||||||
|
|
||||||
/// Process the command entry
|
/// Process the command entry
|
||||||
|
@ -101,11 +103,11 @@ private:
|
||||||
/// Write arguments value to the ThiRegisters member at the specified offset
|
/// Write arguments value to the ThiRegisters member at the specified offset
|
||||||
void ThiStateWrite(ThiRegisters& state, u32 offset, u32 argument);
|
void ThiStateWrite(ThiRegisters& state, u32 offset, u32 argument);
|
||||||
|
|
||||||
GPU& gpu;
|
Host1x::Host1x& host1x;
|
||||||
std::shared_ptr<Tegra::Nvdec> nvdec_processor;
|
std::shared_ptr<Tegra::Host1x::Nvdec> nvdec_processor;
|
||||||
std::unique_ptr<Tegra::Vic> vic_processor;
|
std::unique_ptr<Tegra::Host1x::Vic> vic_processor;
|
||||||
std::unique_ptr<Tegra::Host1x> host1x_processor;
|
std::unique_ptr<Tegra::Host1x::Control> host1x_processor;
|
||||||
std::unique_ptr<SyncptIncrManager> sync_manager;
|
std::unique_ptr<Host1x::SyncptIncrManager> sync_manager;
|
||||||
ChClassId current_class{};
|
ChClassId current_class{};
|
||||||
ThiRegisters vic_thi_state{};
|
ThiRegisters vic_thi_state{};
|
||||||
ThiRegisters nvdec_thi_state{};
|
ThiRegisters nvdec_thi_state{};
|
||||||
|
|
|
@ -1,29 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
#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::ProcessMethod(Method method, u32 argument) {
|
|
||||||
switch (method) {
|
|
||||||
case Method::LoadSyncptPayload32:
|
|
||||||
syncpoint_value = argument;
|
|
||||||
break;
|
|
||||||
case Method::WaitSyncpt:
|
|
||||||
case Method::WaitSyncpt32:
|
|
||||||
Execute(argument);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
UNIMPLEMENTED_MSG("Host1x method 0x{:X}", static_cast<u32>(method));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Tegra::Host1x::Execute(u32 data) {
|
|
||||||
gpu.WaitFence(data, syncpoint_value);
|
|
||||||
}
|
|
40
src/video_core/control/channel_state.cpp
Normal file
40
src/video_core/control/channel_state.cpp
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
// SPDX-FileCopyrightText: 2022 yuzu Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
#include "common/assert.h"
|
||||||
|
#include "video_core/control/channel_state.h"
|
||||||
|
#include "video_core/dma_pusher.h"
|
||||||
|
#include "video_core/engines/fermi_2d.h"
|
||||||
|
#include "video_core/engines/kepler_compute.h"
|
||||||
|
#include "video_core/engines/kepler_memory.h"
|
||||||
|
#include "video_core/engines/maxwell_3d.h"
|
||||||
|
#include "video_core/engines/maxwell_dma.h"
|
||||||
|
#include "video_core/engines/puller.h"
|
||||||
|
#include "video_core/memory_manager.h"
|
||||||
|
|
||||||
|
namespace Tegra::Control {
|
||||||
|
|
||||||
|
ChannelState::ChannelState(s32 bind_id_) : bind_id{bind_id_}, initialized{} {}
|
||||||
|
|
||||||
|
void ChannelState::Init(Core::System& system, GPU& gpu) {
|
||||||
|
ASSERT(memory_manager);
|
||||||
|
dma_pusher = std::make_unique<Tegra::DmaPusher>(system, gpu, *memory_manager, *this);
|
||||||
|
maxwell_3d = std::make_unique<Engines::Maxwell3D>(system, *memory_manager);
|
||||||
|
fermi_2d = std::make_unique<Engines::Fermi2D>();
|
||||||
|
kepler_compute = std::make_unique<Engines::KeplerCompute>(system, *memory_manager);
|
||||||
|
maxwell_dma = std::make_unique<Engines::MaxwellDMA>(system, *memory_manager);
|
||||||
|
kepler_memory = std::make_unique<Engines::KeplerMemory>(system, *memory_manager);
|
||||||
|
initialized = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChannelState::BindRasterizer(VideoCore::RasterizerInterface* rasterizer) {
|
||||||
|
dma_pusher->BindRasterizer(rasterizer);
|
||||||
|
memory_manager->BindRasterizer(rasterizer);
|
||||||
|
maxwell_3d->BindRasterizer(rasterizer);
|
||||||
|
fermi_2d->BindRasterizer(rasterizer);
|
||||||
|
kepler_memory->BindRasterizer(rasterizer);
|
||||||
|
kepler_compute->BindRasterizer(rasterizer);
|
||||||
|
maxwell_dma->BindRasterizer(rasterizer);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Tegra::Control
|
68
src/video_core/control/channel_state.h
Normal file
68
src/video_core/control/channel_state.h
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
// SPDX-FileCopyrightText: 2022 yuzu Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include "common/common_types.h"
|
||||||
|
|
||||||
|
namespace Core {
|
||||||
|
class System;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace VideoCore {
|
||||||
|
class RasterizerInterface;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace Tegra {
|
||||||
|
|
||||||
|
class GPU;
|
||||||
|
|
||||||
|
namespace Engines {
|
||||||
|
class Puller;
|
||||||
|
class Fermi2D;
|
||||||
|
class Maxwell3D;
|
||||||
|
class MaxwellDMA;
|
||||||
|
class KeplerCompute;
|
||||||
|
class KeplerMemory;
|
||||||
|
} // namespace Engines
|
||||||
|
|
||||||
|
class MemoryManager;
|
||||||
|
class DmaPusher;
|
||||||
|
|
||||||
|
namespace Control {
|
||||||
|
|
||||||
|
struct ChannelState {
|
||||||
|
explicit ChannelState(s32 bind_id);
|
||||||
|
ChannelState(const ChannelState& state) = delete;
|
||||||
|
ChannelState& operator=(const ChannelState&) = delete;
|
||||||
|
ChannelState(ChannelState&& other) noexcept = default;
|
||||||
|
ChannelState& operator=(ChannelState&& other) noexcept = default;
|
||||||
|
|
||||||
|
void Init(Core::System& system, GPU& gpu);
|
||||||
|
|
||||||
|
void BindRasterizer(VideoCore::RasterizerInterface* rasterizer);
|
||||||
|
|
||||||
|
s32 bind_id = -1;
|
||||||
|
/// 3D engine
|
||||||
|
std::unique_ptr<Engines::Maxwell3D> maxwell_3d;
|
||||||
|
/// 2D engine
|
||||||
|
std::unique_ptr<Engines::Fermi2D> fermi_2d;
|
||||||
|
/// Compute engine
|
||||||
|
std::unique_ptr<Engines::KeplerCompute> kepler_compute;
|
||||||
|
/// DMA engine
|
||||||
|
std::unique_ptr<Engines::MaxwellDMA> maxwell_dma;
|
||||||
|
/// Inline memory engine
|
||||||
|
std::unique_ptr<Engines::KeplerMemory> kepler_memory;
|
||||||
|
|
||||||
|
std::shared_ptr<MemoryManager> memory_manager;
|
||||||
|
|
||||||
|
std::unique_ptr<DmaPusher> dma_pusher;
|
||||||
|
|
||||||
|
bool initialized{};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Control
|
||||||
|
|
||||||
|
} // namespace Tegra
|
14
src/video_core/control/channel_state_cache.cpp
Normal file
14
src/video_core/control/channel_state_cache.cpp
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
// SPDX-FileCopyrightText: 2022 yuzu Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
#include "video_core/control/channel_state_cache.inc"
|
||||||
|
|
||||||
|
namespace VideoCommon {
|
||||||
|
|
||||||
|
ChannelInfo::ChannelInfo(Tegra::Control::ChannelState& channel_state)
|
||||||
|
: maxwell3d{*channel_state.maxwell_3d}, kepler_compute{*channel_state.kepler_compute},
|
||||||
|
gpu_memory{*channel_state.memory_manager} {}
|
||||||
|
|
||||||
|
template class VideoCommon::ChannelSetupCaches<VideoCommon::ChannelInfo>;
|
||||||
|
|
||||||
|
} // namespace VideoCommon
|
101
src/video_core/control/channel_state_cache.h
Normal file
101
src/video_core/control/channel_state_cache.h
Normal file
|
@ -0,0 +1,101 @@
|
||||||
|
// SPDX-FileCopyrightText: 2022 yuzu Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <deque>
|
||||||
|
#include <limits>
|
||||||
|
#include <mutex>
|
||||||
|
#include <optional>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "common/common_types.h"
|
||||||
|
|
||||||
|
namespace Tegra {
|
||||||
|
|
||||||
|
namespace Engines {
|
||||||
|
class Maxwell3D;
|
||||||
|
class KeplerCompute;
|
||||||
|
} // namespace Engines
|
||||||
|
|
||||||
|
class MemoryManager;
|
||||||
|
|
||||||
|
namespace Control {
|
||||||
|
struct ChannelState;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Tegra
|
||||||
|
|
||||||
|
namespace VideoCommon {
|
||||||
|
|
||||||
|
class ChannelInfo {
|
||||||
|
public:
|
||||||
|
ChannelInfo() = delete;
|
||||||
|
explicit ChannelInfo(Tegra::Control::ChannelState& state);
|
||||||
|
ChannelInfo(const ChannelInfo& state) = delete;
|
||||||
|
ChannelInfo& operator=(const ChannelInfo&) = delete;
|
||||||
|
ChannelInfo(ChannelInfo&& other) = default;
|
||||||
|
ChannelInfo& operator=(ChannelInfo&& other) = default;
|
||||||
|
|
||||||
|
Tegra::Engines::Maxwell3D& maxwell3d;
|
||||||
|
Tegra::Engines::KeplerCompute& kepler_compute;
|
||||||
|
Tegra::MemoryManager& gpu_memory;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <class P>
|
||||||
|
class ChannelSetupCaches {
|
||||||
|
public:
|
||||||
|
/// Operations for seting the channel of execution.
|
||||||
|
virtual ~ChannelSetupCaches();
|
||||||
|
|
||||||
|
/// Create channel state.
|
||||||
|
virtual void CreateChannel(Tegra::Control::ChannelState& channel);
|
||||||
|
|
||||||
|
/// Bind a channel for execution.
|
||||||
|
void BindToChannel(s32 id);
|
||||||
|
|
||||||
|
/// Erase channel's state.
|
||||||
|
void EraseChannel(s32 id);
|
||||||
|
|
||||||
|
Tegra::MemoryManager* GetFromID(size_t id) const {
|
||||||
|
std::unique_lock<std::mutex> lk(config_mutex);
|
||||||
|
const auto ref = address_spaces.find(id);
|
||||||
|
return ref->second.gpu_memory;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<size_t> getStorageID(size_t id) const {
|
||||||
|
std::unique_lock<std::mutex> lk(config_mutex);
|
||||||
|
const auto ref = address_spaces.find(id);
|
||||||
|
if (ref == address_spaces.end()) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
return ref->second.storage_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
static constexpr size_t UNSET_CHANNEL{std::numeric_limits<size_t>::max()};
|
||||||
|
|
||||||
|
P* channel_state;
|
||||||
|
size_t current_channel_id{UNSET_CHANNEL};
|
||||||
|
size_t current_address_space{};
|
||||||
|
Tegra::Engines::Maxwell3D* maxwell3d;
|
||||||
|
Tegra::Engines::KeplerCompute* kepler_compute;
|
||||||
|
Tegra::MemoryManager* gpu_memory;
|
||||||
|
|
||||||
|
std::deque<P> channel_storage;
|
||||||
|
std::deque<size_t> free_channel_ids;
|
||||||
|
std::unordered_map<s32, size_t> channel_map;
|
||||||
|
std::vector<size_t> active_channel_ids;
|
||||||
|
struct AddresSpaceRef {
|
||||||
|
size_t ref_count;
|
||||||
|
size_t storage_id;
|
||||||
|
Tegra::MemoryManager* gpu_memory;
|
||||||
|
};
|
||||||
|
std::unordered_map<size_t, AddresSpaceRef> address_spaces;
|
||||||
|
mutable std::mutex config_mutex;
|
||||||
|
|
||||||
|
virtual void OnGPUASRegister([[maybe_unused]] size_t map_id) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace VideoCommon
|
86
src/video_core/control/channel_state_cache.inc
Normal file
86
src/video_core/control/channel_state_cache.inc
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
// SPDX-FileCopyrightText: 2022 yuzu Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
#include "video_core/control/channel_state.h"
|
||||||
|
#include "video_core/control/channel_state_cache.h"
|
||||||
|
#include "video_core/engines/kepler_compute.h"
|
||||||
|
#include "video_core/engines/maxwell_3d.h"
|
||||||
|
#include "video_core/memory_manager.h"
|
||||||
|
|
||||||
|
namespace VideoCommon {
|
||||||
|
|
||||||
|
template <class P>
|
||||||
|
ChannelSetupCaches<P>::~ChannelSetupCaches() = default;
|
||||||
|
|
||||||
|
template <class P>
|
||||||
|
void ChannelSetupCaches<P>::CreateChannel(struct Tegra::Control::ChannelState& channel) {
|
||||||
|
std::unique_lock<std::mutex> lk(config_mutex);
|
||||||
|
ASSERT(channel_map.find(channel.bind_id) == channel_map.end() && channel.bind_id >= 0);
|
||||||
|
auto new_id = [this, &channel]() {
|
||||||
|
if (!free_channel_ids.empty()) {
|
||||||
|
auto id = free_channel_ids.front();
|
||||||
|
free_channel_ids.pop_front();
|
||||||
|
new (&channel_storage[id]) P(channel);
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
channel_storage.emplace_back(channel);
|
||||||
|
return channel_storage.size() - 1;
|
||||||
|
}();
|
||||||
|
channel_map.emplace(channel.bind_id, new_id);
|
||||||
|
if (current_channel_id != UNSET_CHANNEL) {
|
||||||
|
channel_state = &channel_storage[current_channel_id];
|
||||||
|
}
|
||||||
|
active_channel_ids.push_back(new_id);
|
||||||
|
auto as_it = address_spaces.find(channel.memory_manager->GetID());
|
||||||
|
if (as_it != address_spaces.end()) {
|
||||||
|
as_it->second.ref_count++;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
AddresSpaceRef new_gpu_mem_ref{
|
||||||
|
.ref_count = 1,
|
||||||
|
.storage_id = address_spaces.size(),
|
||||||
|
.gpu_memory = channel.memory_manager.get(),
|
||||||
|
};
|
||||||
|
address_spaces.emplace(channel.memory_manager->GetID(), new_gpu_mem_ref);
|
||||||
|
OnGPUASRegister(channel.memory_manager->GetID());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Bind a channel for execution.
|
||||||
|
template <class P>
|
||||||
|
void ChannelSetupCaches<P>::BindToChannel(s32 id) {
|
||||||
|
std::unique_lock<std::mutex> lk(config_mutex);
|
||||||
|
auto it = channel_map.find(id);
|
||||||
|
ASSERT(it != channel_map.end() && id >= 0);
|
||||||
|
current_channel_id = it->second;
|
||||||
|
channel_state = &channel_storage[current_channel_id];
|
||||||
|
maxwell3d = &channel_state->maxwell3d;
|
||||||
|
kepler_compute = &channel_state->kepler_compute;
|
||||||
|
gpu_memory = &channel_state->gpu_memory;
|
||||||
|
current_address_space = gpu_memory->GetID();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Erase channel's channel_state.
|
||||||
|
template <class P>
|
||||||
|
void ChannelSetupCaches<P>::EraseChannel(s32 id) {
|
||||||
|
std::unique_lock<std::mutex> lk(config_mutex);
|
||||||
|
const auto it = channel_map.find(id);
|
||||||
|
ASSERT(it != channel_map.end() && id >= 0);
|
||||||
|
const auto this_id = it->second;
|
||||||
|
free_channel_ids.push_back(this_id);
|
||||||
|
channel_map.erase(it);
|
||||||
|
if (this_id == current_channel_id) {
|
||||||
|
current_channel_id = UNSET_CHANNEL;
|
||||||
|
channel_state = nullptr;
|
||||||
|
maxwell3d = nullptr;
|
||||||
|
kepler_compute = nullptr;
|
||||||
|
gpu_memory = nullptr;
|
||||||
|
} else if (current_channel_id != UNSET_CHANNEL) {
|
||||||
|
channel_state = &channel_storage[current_channel_id];
|
||||||
|
}
|
||||||
|
active_channel_ids.erase(
|
||||||
|
std::find(active_channel_ids.begin(), active_channel_ids.end(), this_id));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace VideoCommon
|
32
src/video_core/control/scheduler.cpp
Normal file
32
src/video_core/control/scheduler.cpp
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
// SPDX-FileCopyrightText: 2021 yuzu Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include "common/assert.h"
|
||||||
|
#include "video_core/control/channel_state.h"
|
||||||
|
#include "video_core/control/scheduler.h"
|
||||||
|
#include "video_core/gpu.h"
|
||||||
|
|
||||||
|
namespace Tegra::Control {
|
||||||
|
Scheduler::Scheduler(GPU& gpu_) : gpu{gpu_} {}
|
||||||
|
|
||||||
|
Scheduler::~Scheduler() = default;
|
||||||
|
|
||||||
|
void Scheduler::Push(s32 channel, CommandList&& entries) {
|
||||||
|
std::unique_lock lk(scheduling_guard);
|
||||||
|
auto it = channels.find(channel);
|
||||||
|
ASSERT(it != channels.end());
|
||||||
|
auto channel_state = it->second;
|
||||||
|
gpu.BindChannel(channel_state->bind_id);
|
||||||
|
channel_state->dma_pusher->Push(std::move(entries));
|
||||||
|
channel_state->dma_pusher->DispatchCalls();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Scheduler::DeclareChannel(std::shared_ptr<ChannelState> new_channel) {
|
||||||
|
s32 channel = new_channel->bind_id;
|
||||||
|
std::unique_lock lk(scheduling_guard);
|
||||||
|
channels.emplace(channel, new_channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Tegra::Control
|
37
src/video_core/control/scheduler.h
Normal file
37
src/video_core/control/scheduler.h
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
// SPDX-FileCopyrightText: 2021 yuzu Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <mutex>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
#include "video_core/dma_pusher.h"
|
||||||
|
|
||||||
|
namespace Tegra {
|
||||||
|
|
||||||
|
class GPU;
|
||||||
|
|
||||||
|
namespace Control {
|
||||||
|
|
||||||
|
struct ChannelState;
|
||||||
|
|
||||||
|
class Scheduler {
|
||||||
|
public:
|
||||||
|
explicit Scheduler(GPU& gpu_);
|
||||||
|
~Scheduler();
|
||||||
|
|
||||||
|
void Push(s32 channel, CommandList&& entries);
|
||||||
|
|
||||||
|
void DeclareChannel(std::shared_ptr<ChannelState> new_channel);
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::unordered_map<s32, std::shared_ptr<ChannelState>> channels;
|
||||||
|
std::mutex scheduling_guard;
|
||||||
|
GPU& gpu;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Control
|
||||||
|
|
||||||
|
} // namespace Tegra
|
|
@ -12,7 +12,10 @@
|
||||||
|
|
||||||
namespace Tegra {
|
namespace Tegra {
|
||||||
|
|
||||||
DmaPusher::DmaPusher(Core::System& system_, GPU& gpu_) : gpu{gpu_}, system{system_} {}
|
DmaPusher::DmaPusher(Core::System& system_, GPU& gpu_, MemoryManager& memory_manager_,
|
||||||
|
Control::ChannelState& channel_state_)
|
||||||
|
: gpu{gpu_}, system{system_}, memory_manager{memory_manager_}, puller{gpu_, memory_manager_,
|
||||||
|
*this, channel_state_} {}
|
||||||
|
|
||||||
DmaPusher::~DmaPusher() = default;
|
DmaPusher::~DmaPusher() = default;
|
||||||
|
|
||||||
|
@ -21,8 +24,6 @@ MICROPROFILE_DEFINE(DispatchCalls, "GPU", "Execute command buffer", MP_RGB(128,
|
||||||
void DmaPusher::DispatchCalls() {
|
void DmaPusher::DispatchCalls() {
|
||||||
MICROPROFILE_SCOPE(DispatchCalls);
|
MICROPROFILE_SCOPE(DispatchCalls);
|
||||||
|
|
||||||
gpu.SyncGuestHost();
|
|
||||||
|
|
||||||
dma_pushbuffer_subindex = 0;
|
dma_pushbuffer_subindex = 0;
|
||||||
|
|
||||||
dma_state.is_last_call = true;
|
dma_state.is_last_call = true;
|
||||||
|
@ -33,7 +34,6 @@ void DmaPusher::DispatchCalls() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
gpu.FlushCommands();
|
gpu.FlushCommands();
|
||||||
gpu.SyncGuestHost();
|
|
||||||
gpu.OnCommandListEnd();
|
gpu.OnCommandListEnd();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -76,10 +76,10 @@ bool DmaPusher::Step() {
|
||||||
// Push buffer non-empty, read a word
|
// Push buffer non-empty, read a word
|
||||||
command_headers.resize(command_list_header.size);
|
command_headers.resize(command_list_header.size);
|
||||||
if (Settings::IsGPULevelHigh()) {
|
if (Settings::IsGPULevelHigh()) {
|
||||||
gpu.MemoryManager().ReadBlock(dma_get, command_headers.data(),
|
memory_manager.ReadBlock(dma_get, command_headers.data(),
|
||||||
command_list_header.size * sizeof(u32));
|
command_list_header.size * sizeof(u32));
|
||||||
} else {
|
} else {
|
||||||
gpu.MemoryManager().ReadBlockUnsafe(dma_get, command_headers.data(),
|
memory_manager.ReadBlockUnsafe(dma_get, command_headers.data(),
|
||||||
command_list_header.size * sizeof(u32));
|
command_list_header.size * sizeof(u32));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -154,7 +154,7 @@ void DmaPusher::SetState(const CommandHeader& command_header) {
|
||||||
|
|
||||||
void DmaPusher::CallMethod(u32 argument) const {
|
void DmaPusher::CallMethod(u32 argument) const {
|
||||||
if (dma_state.method < non_puller_methods) {
|
if (dma_state.method < non_puller_methods) {
|
||||||
gpu.CallMethod(GPU::MethodCall{
|
puller.CallPullerMethod(Engines::Puller::MethodCall{
|
||||||
dma_state.method,
|
dma_state.method,
|
||||||
argument,
|
argument,
|
||||||
dma_state.subchannel,
|
dma_state.subchannel,
|
||||||
|
@ -168,7 +168,7 @@ void DmaPusher::CallMethod(u32 argument) const {
|
||||||
|
|
||||||
void DmaPusher::CallMultiMethod(const u32* base_start, u32 num_methods) const {
|
void DmaPusher::CallMultiMethod(const u32* base_start, u32 num_methods) const {
|
||||||
if (dma_state.method < non_puller_methods) {
|
if (dma_state.method < non_puller_methods) {
|
||||||
gpu.CallMultiMethod(dma_state.method, dma_state.subchannel, base_start, num_methods,
|
puller.CallMultiMethod(dma_state.method, dma_state.subchannel, base_start, num_methods,
|
||||||
dma_state.method_count);
|
dma_state.method_count);
|
||||||
} else {
|
} else {
|
||||||
subchannels[dma_state.subchannel]->CallMultiMethod(dma_state.method, base_start,
|
subchannels[dma_state.subchannel]->CallMultiMethod(dma_state.method, base_start,
|
||||||
|
@ -176,4 +176,8 @@ void DmaPusher::CallMultiMethod(const u32* base_start, u32 num_methods) const {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void DmaPusher::BindRasterizer(VideoCore::RasterizerInterface* rasterizer) {
|
||||||
|
puller.BindRasterizer(rasterizer);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace Tegra
|
} // namespace Tegra
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
#include "common/bit_field.h"
|
#include "common/bit_field.h"
|
||||||
#include "common/common_types.h"
|
#include "common/common_types.h"
|
||||||
#include "video_core/engines/engine_interface.h"
|
#include "video_core/engines/engine_interface.h"
|
||||||
|
#include "video_core/engines/puller.h"
|
||||||
|
|
||||||
namespace Core {
|
namespace Core {
|
||||||
class System;
|
class System;
|
||||||
|
@ -17,7 +18,12 @@ class System;
|
||||||
|
|
||||||
namespace Tegra {
|
namespace Tegra {
|
||||||
|
|
||||||
|
namespace Control {
|
||||||
|
struct ChannelState;
|
||||||
|
}
|
||||||
|
|
||||||
class GPU;
|
class GPU;
|
||||||
|
class MemoryManager;
|
||||||
|
|
||||||
enum class SubmissionMode : u32 {
|
enum class SubmissionMode : u32 {
|
||||||
IncreasingOld = 0,
|
IncreasingOld = 0,
|
||||||
|
@ -31,24 +37,32 @@ enum class SubmissionMode : u32 {
|
||||||
// Note that, traditionally, methods are treated as 4-byte addressable locations, and hence
|
// Note that, traditionally, methods are treated as 4-byte addressable locations, and hence
|
||||||
// their numbers are written down multiplied by 4 in Docs. Here we are not multiply by 4.
|
// their numbers are written down multiplied by 4 in Docs. Here we are not multiply by 4.
|
||||||
// So the values you see in docs might be multiplied by 4.
|
// So the values you see in docs might be multiplied by 4.
|
||||||
|
// Register documentation:
|
||||||
|
// https://github.com/NVIDIA/open-gpu-doc/blob/ab27fc22db5de0d02a4cabe08e555663b62db4d4/classes/host/cla26f.h
|
||||||
|
//
|
||||||
|
// Register Description (approx):
|
||||||
|
// https://github.com/NVIDIA/open-gpu-doc/blob/ab27fc22db5de0d02a4cabe08e555663b62db4d4/manuals/volta/gv100/dev_pbdma.ref.txt
|
||||||
enum class BufferMethods : u32 {
|
enum class BufferMethods : u32 {
|
||||||
BindObject = 0x0,
|
BindObject = 0x0,
|
||||||
|
Illegal = 0x1,
|
||||||
Nop = 0x2,
|
Nop = 0x2,
|
||||||
SemaphoreAddressHigh = 0x4,
|
SemaphoreAddressHigh = 0x4,
|
||||||
SemaphoreAddressLow = 0x5,
|
SemaphoreAddressLow = 0x5,
|
||||||
SemaphoreSequence = 0x6,
|
SemaphoreSequencePayload = 0x6,
|
||||||
SemaphoreTrigger = 0x7,
|
SemaphoreOperation = 0x7,
|
||||||
NotifyIntr = 0x8,
|
NonStallInterrupt = 0x8,
|
||||||
WrcacheFlush = 0x9,
|
WrcacheFlush = 0x9,
|
||||||
Unk28 = 0xA,
|
MemOpA = 0xA,
|
||||||
UnkCacheFlush = 0xB,
|
MemOpB = 0xB,
|
||||||
|
MemOpC = 0xC,
|
||||||
|
MemOpD = 0xD,
|
||||||
RefCnt = 0x14,
|
RefCnt = 0x14,
|
||||||
SemaphoreAcquire = 0x1A,
|
SemaphoreAcquire = 0x1A,
|
||||||
SemaphoreRelease = 0x1B,
|
SemaphoreRelease = 0x1B,
|
||||||
FenceValue = 0x1C,
|
SyncpointPayload = 0x1C,
|
||||||
FenceAction = 0x1D,
|
SyncpointOperation = 0x1D,
|
||||||
WaitForInterrupt = 0x1E,
|
WaitForIdle = 0x1E,
|
||||||
Unk7c = 0x1F,
|
CRCCheck = 0x1F,
|
||||||
Yield = 0x20,
|
Yield = 0x20,
|
||||||
NonPullerMethods = 0x40,
|
NonPullerMethods = 0x40,
|
||||||
};
|
};
|
||||||
|
@ -102,7 +116,8 @@ struct CommandList final {
|
||||||
*/
|
*/
|
||||||
class DmaPusher final {
|
class DmaPusher final {
|
||||||
public:
|
public:
|
||||||
explicit DmaPusher(Core::System& system_, GPU& gpu_);
|
explicit DmaPusher(Core::System& system_, GPU& gpu_, MemoryManager& memory_manager_,
|
||||||
|
Control::ChannelState& channel_state_);
|
||||||
~DmaPusher();
|
~DmaPusher();
|
||||||
|
|
||||||
void Push(CommandList&& entries) {
|
void Push(CommandList&& entries) {
|
||||||
|
@ -115,6 +130,8 @@ public:
|
||||||
subchannels[subchannel_id] = engine;
|
subchannels[subchannel_id] = engine;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void BindRasterizer(VideoCore::RasterizerInterface* rasterizer);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static constexpr u32 non_puller_methods = 0x40;
|
static constexpr u32 non_puller_methods = 0x40;
|
||||||
static constexpr u32 max_subchannels = 8;
|
static constexpr u32 max_subchannels = 8;
|
||||||
|
@ -148,6 +165,8 @@ private:
|
||||||
|
|
||||||
GPU& gpu;
|
GPU& gpu;
|
||||||
Core::System& system;
|
Core::System& system;
|
||||||
|
MemoryManager& memory_manager;
|
||||||
|
mutable Engines::Puller puller;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Tegra
|
} // namespace Tegra
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
|
||||||
|
#include "common/algorithm.h"
|
||||||
#include "common/assert.h"
|
#include "common/assert.h"
|
||||||
#include "video_core/engines/engine_upload.h"
|
#include "video_core/engines/engine_upload.h"
|
||||||
#include "video_core/memory_manager.h"
|
#include "video_core/memory_manager.h"
|
||||||
|
@ -34,21 +35,48 @@ void State::ProcessData(const u32 data, const bool is_last_call) {
|
||||||
if (!is_last_call) {
|
if (!is_last_call) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
ProcessData(inner_buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
void State::ProcessData(const u32* data, size_t num_data) {
|
||||||
|
std::span<const u8> read_buffer(reinterpret_cast<const u8*>(data), num_data * sizeof(u32));
|
||||||
|
ProcessData(read_buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
void State::ProcessData(std::span<const u8> read_buffer) {
|
||||||
const GPUVAddr address{regs.dest.Address()};
|
const GPUVAddr address{regs.dest.Address()};
|
||||||
if (is_linear) {
|
if (is_linear) {
|
||||||
rasterizer->AccelerateInlineToMemory(address, copy_size, inner_buffer);
|
if (regs.line_count == 1) {
|
||||||
|
rasterizer->AccelerateInlineToMemory(address, copy_size, read_buffer);
|
||||||
} else {
|
} else {
|
||||||
UNIMPLEMENTED_IF(regs.dest.z != 0);
|
for (u32 line = 0; line < regs.line_count; ++line) {
|
||||||
UNIMPLEMENTED_IF(regs.dest.depth != 1);
|
const GPUVAddr dest_line = address + static_cast<size_t>(line) * regs.dest.pitch;
|
||||||
UNIMPLEMENTED_IF(regs.dest.BlockWidth() != 0);
|
memory_manager.WriteBlockUnsafe(
|
||||||
UNIMPLEMENTED_IF(regs.dest.BlockDepth() != 0);
|
dest_line, read_buffer.data() + static_cast<size_t>(line) * regs.line_length_in,
|
||||||
|
regs.line_length_in);
|
||||||
|
}
|
||||||
|
memory_manager.InvalidateRegion(address, regs.dest.pitch * regs.line_count);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
u32 width = regs.dest.width;
|
||||||
|
u32 x_elements = regs.line_length_in;
|
||||||
|
u32 x_offset = regs.dest.x;
|
||||||
|
const u32 bpp_shift = Common::FoldRight(
|
||||||
|
4U, [](u32 x, u32 y) { return std::min(x, static_cast<u32>(std::countr_zero(y))); },
|
||||||
|
width, x_elements, x_offset, static_cast<u32>(address));
|
||||||
|
width >>= bpp_shift;
|
||||||
|
x_elements >>= bpp_shift;
|
||||||
|
x_offset >>= bpp_shift;
|
||||||
|
const u32 bytes_per_pixel = 1U << bpp_shift;
|
||||||
const std::size_t dst_size = Tegra::Texture::CalculateSize(
|
const std::size_t dst_size = Tegra::Texture::CalculateSize(
|
||||||
true, 1, regs.dest.width, regs.dest.height, 1, regs.dest.BlockHeight(), 0);
|
true, bytes_per_pixel, width, regs.dest.height, regs.dest.depth,
|
||||||
|
regs.dest.BlockHeight(), regs.dest.BlockDepth());
|
||||||
tmp_buffer.resize(dst_size);
|
tmp_buffer.resize(dst_size);
|
||||||
memory_manager.ReadBlock(address, tmp_buffer.data(), dst_size);
|
memory_manager.ReadBlock(address, tmp_buffer.data(), dst_size);
|
||||||
Tegra::Texture::SwizzleKepler(regs.dest.width, regs.dest.height, regs.dest.x, regs.dest.y,
|
Tegra::Texture::SwizzleSubrect(tmp_buffer, read_buffer, bytes_per_pixel, width,
|
||||||
regs.dest.BlockHeight(), copy_size, inner_buffer.data(),
|
regs.dest.height, regs.dest.depth, x_offset, regs.dest.y,
|
||||||
tmp_buffer.data());
|
x_elements, regs.line_count, regs.dest.BlockHeight(),
|
||||||
|
regs.dest.BlockDepth(), regs.line_length_in);
|
||||||
memory_manager.WriteBlock(address, tmp_buffer.data(), dst_size);
|
memory_manager.WriteBlock(address, tmp_buffer.data(), dst_size);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <span>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include "common/bit_field.h"
|
#include "common/bit_field.h"
|
||||||
#include "common/common_types.h"
|
#include "common/common_types.h"
|
||||||
|
@ -33,7 +34,7 @@ struct Registers {
|
||||||
u32 width;
|
u32 width;
|
||||||
u32 height;
|
u32 height;
|
||||||
u32 depth;
|
u32 depth;
|
||||||
u32 z;
|
u32 layer;
|
||||||
u32 x;
|
u32 x;
|
||||||
u32 y;
|
u32 y;
|
||||||
|
|
||||||
|
@ -62,11 +63,14 @@ public:
|
||||||
|
|
||||||
void ProcessExec(bool is_linear_);
|
void ProcessExec(bool is_linear_);
|
||||||
void ProcessData(u32 data, bool is_last_call);
|
void ProcessData(u32 data, bool is_last_call);
|
||||||
|
void ProcessData(const u32* data, size_t num_data);
|
||||||
|
|
||||||
/// Binds a rasterizer to this engine.
|
/// Binds a rasterizer to this engine.
|
||||||
void BindRasterizer(VideoCore::RasterizerInterface* rasterizer);
|
void BindRasterizer(VideoCore::RasterizerInterface* rasterizer);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
void ProcessData(std::span<const u8> read_buffer);
|
||||||
|
|
||||||
u32 write_offset = 0;
|
u32 write_offset = 0;
|
||||||
u32 copy_size = 0;
|
u32 copy_size = 0;
|
||||||
std::vector<u8> inner_buffer;
|
std::vector<u8> inner_buffer;
|
||||||
|
|
|
@ -36,8 +36,6 @@ void KeplerCompute::CallMethod(u32 method, u32 method_argument, bool is_last_cal
|
||||||
}
|
}
|
||||||
case KEPLER_COMPUTE_REG_INDEX(data_upload): {
|
case KEPLER_COMPUTE_REG_INDEX(data_upload): {
|
||||||
upload_state.ProcessData(method_argument, is_last_call);
|
upload_state.ProcessData(method_argument, is_last_call);
|
||||||
if (is_last_call) {
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case KEPLER_COMPUTE_REG_INDEX(launch):
|
case KEPLER_COMPUTE_REG_INDEX(launch):
|
||||||
|
@ -50,9 +48,16 @@ void KeplerCompute::CallMethod(u32 method, u32 method_argument, bool is_last_cal
|
||||||
|
|
||||||
void KeplerCompute::CallMultiMethod(u32 method, const u32* base_start, u32 amount,
|
void KeplerCompute::CallMultiMethod(u32 method, const u32* base_start, u32 amount,
|
||||||
u32 methods_pending) {
|
u32 methods_pending) {
|
||||||
|
switch (method) {
|
||||||
|
case KEPLER_COMPUTE_REG_INDEX(data_upload):
|
||||||
|
upload_state.ProcessData(base_start, static_cast<size_t>(amount));
|
||||||
|
return;
|
||||||
|
default:
|
||||||
for (std::size_t i = 0; i < amount; i++) {
|
for (std::size_t i = 0; i < amount; i++) {
|
||||||
CallMethod(method, base_start[i], methods_pending - static_cast<u32>(i) <= 1);
|
CallMethod(method, base_start[i], methods_pending - static_cast<u32>(i) <= 1);
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void KeplerCompute::ProcessLaunch() {
|
void KeplerCompute::ProcessLaunch() {
|
||||||
|
|
|
@ -33,8 +33,6 @@ void KeplerMemory::CallMethod(u32 method, u32 method_argument, bool is_last_call
|
||||||
}
|
}
|
||||||
case KEPLERMEMORY_REG_INDEX(data): {
|
case KEPLERMEMORY_REG_INDEX(data): {
|
||||||
upload_state.ProcessData(method_argument, is_last_call);
|
upload_state.ProcessData(method_argument, is_last_call);
|
||||||
if (is_last_call) {
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -42,9 +40,16 @@ void KeplerMemory::CallMethod(u32 method, u32 method_argument, bool is_last_call
|
||||||
|
|
||||||
void KeplerMemory::CallMultiMethod(u32 method, const u32* base_start, u32 amount,
|
void KeplerMemory::CallMultiMethod(u32 method, const u32* base_start, u32 amount,
|
||||||
u32 methods_pending) {
|
u32 methods_pending) {
|
||||||
|
switch (method) {
|
||||||
|
case KEPLERMEMORY_REG_INDEX(data):
|
||||||
|
upload_state.ProcessData(base_start, static_cast<size_t>(amount));
|
||||||
|
return;
|
||||||
|
default:
|
||||||
for (std::size_t i = 0; i < amount; i++) {
|
for (std::size_t i = 0; i < amount; i++) {
|
||||||
CallMethod(method, base_start[i], methods_pending - static_cast<u32>(i) <= 1);
|
CallMethod(method, base_start[i], methods_pending - static_cast<u32>(i) <= 1);
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Tegra::Engines
|
} // namespace Tegra::Engines
|
||||||
|
|
|
@ -219,6 +219,8 @@ void Maxwell3D::ProcessMethodCall(u32 method, u32 argument, u32 nonshadow_argume
|
||||||
regs.index_array.count = regs.small_index_2.count;
|
regs.index_array.count = regs.small_index_2.count;
|
||||||
regs.index_array.first = regs.small_index_2.first;
|
regs.index_array.first = regs.small_index_2.first;
|
||||||
dirty.flags[VideoCommon::Dirty::IndexBuffer] = true;
|
dirty.flags[VideoCommon::Dirty::IndexBuffer] = true;
|
||||||
|
// a macro calls this one over and over, should it increase instancing?
|
||||||
|
// Used by Hades and likely other Vulkan games.
|
||||||
return DrawArrays();
|
return DrawArrays();
|
||||||
case MAXWELL3D_REG_INDEX(topology_override):
|
case MAXWELL3D_REG_INDEX(topology_override):
|
||||||
use_topology_override = true;
|
use_topology_override = true;
|
||||||
|
@ -237,11 +239,12 @@ void Maxwell3D::ProcessMethodCall(u32 method, u32 argument, u32 nonshadow_argume
|
||||||
return upload_state.ProcessExec(regs.exec_upload.linear != 0);
|
return upload_state.ProcessExec(regs.exec_upload.linear != 0);
|
||||||
case MAXWELL3D_REG_INDEX(data_upload):
|
case MAXWELL3D_REG_INDEX(data_upload):
|
||||||
upload_state.ProcessData(argument, is_last_call);
|
upload_state.ProcessData(argument, is_last_call);
|
||||||
if (is_last_call) {
|
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
case MAXWELL3D_REG_INDEX(fragment_barrier):
|
case MAXWELL3D_REG_INDEX(fragment_barrier):
|
||||||
return rasterizer->FragmentBarrier();
|
return rasterizer->FragmentBarrier();
|
||||||
|
case MAXWELL3D_REG_INDEX(invalidate_texture_data_cache):
|
||||||
|
rasterizer->InvalidateGPUCache();
|
||||||
|
return rasterizer->WaitForIdle();
|
||||||
case MAXWELL3D_REG_INDEX(tiled_cache_barrier):
|
case MAXWELL3D_REG_INDEX(tiled_cache_barrier):
|
||||||
return rasterizer->TiledCacheBarrier();
|
return rasterizer->TiledCacheBarrier();
|
||||||
}
|
}
|
||||||
|
@ -311,6 +314,9 @@ void Maxwell3D::CallMultiMethod(u32 method, const u32* base_start, u32 amount,
|
||||||
case MAXWELL3D_REG_INDEX(const_buffer.cb_data) + 15:
|
case MAXWELL3D_REG_INDEX(const_buffer.cb_data) + 15:
|
||||||
ProcessCBMultiData(base_start, amount);
|
ProcessCBMultiData(base_start, amount);
|
||||||
break;
|
break;
|
||||||
|
case MAXWELL3D_REG_INDEX(data_upload):
|
||||||
|
upload_state.ProcessData(base_start, static_cast<size_t>(amount));
|
||||||
|
return;
|
||||||
default:
|
default:
|
||||||
for (std::size_t i = 0; i < amount; i++) {
|
for (std::size_t i = 0; i < amount; i++) {
|
||||||
CallMethod(method, base_start[i], methods_pending - static_cast<u32>(i) <= 1);
|
CallMethod(method, base_start[i], methods_pending - static_cast<u32>(i) <= 1);
|
||||||
|
@ -447,18 +453,10 @@ void Maxwell3D::ProcessFirmwareCall4() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Maxwell3D::StampQueryResult(u64 payload, bool long_query) {
|
void Maxwell3D::StampQueryResult(u64 payload, bool long_query) {
|
||||||
struct LongQueryResult {
|
|
||||||
u64_le value;
|
|
||||||
u64_le timestamp;
|
|
||||||
};
|
|
||||||
static_assert(sizeof(LongQueryResult) == 16, "LongQueryResult has wrong size");
|
|
||||||
const GPUVAddr sequence_address{regs.query.QueryAddress()};
|
const GPUVAddr sequence_address{regs.query.QueryAddress()};
|
||||||
if (long_query) {
|
if (long_query) {
|
||||||
// Write the 128-bit result structure in long mode. Note: We emulate an infinitely fast
|
memory_manager.Write<u64>(sequence_address + sizeof(u64), system.GPU().GetTicks());
|
||||||
// GPU, this command may actually take a while to complete in real hardware due to GPU
|
memory_manager.Write<u64>(sequence_address, payload);
|
||||||
// wait queues.
|
|
||||||
LongQueryResult query_result{payload, system.GPU().GetTicks()};
|
|
||||||
memory_manager.WriteBlock(sequence_address, &query_result, sizeof(query_result));
|
|
||||||
} else {
|
} else {
|
||||||
memory_manager.Write<u32>(sequence_address, static_cast<u32>(payload));
|
memory_manager.Write<u32>(sequence_address, static_cast<u32>(payload));
|
||||||
}
|
}
|
||||||
|
@ -472,10 +470,25 @@ void Maxwell3D::ProcessQueryGet() {
|
||||||
|
|
||||||
switch (regs.query.query_get.operation) {
|
switch (regs.query.query_get.operation) {
|
||||||
case Regs::QueryOperation::Release:
|
case Regs::QueryOperation::Release:
|
||||||
if (regs.query.query_get.fence == 1) {
|
if (regs.query.query_get.fence == 1 || regs.query.query_get.short_query != 0) {
|
||||||
rasterizer->SignalSemaphore(regs.query.QueryAddress(), regs.query.query_sequence);
|
const GPUVAddr sequence_address{regs.query.QueryAddress()};
|
||||||
|
const u32 payload = regs.query.query_sequence;
|
||||||
|
std::function<void()> operation([this, sequence_address, payload] {
|
||||||
|
memory_manager.Write<u32>(sequence_address, payload);
|
||||||
|
});
|
||||||
|
rasterizer->SignalFence(std::move(operation));
|
||||||
} else {
|
} else {
|
||||||
StampQueryResult(regs.query.query_sequence, regs.query.query_get.short_query == 0);
|
struct LongQueryResult {
|
||||||
|
u64_le value;
|
||||||
|
u64_le timestamp;
|
||||||
|
};
|
||||||
|
const GPUVAddr sequence_address{regs.query.QueryAddress()};
|
||||||
|
const u32 payload = regs.query.query_sequence;
|
||||||
|
std::function<void()> operation([this, sequence_address, payload] {
|
||||||
|
memory_manager.Write<u64>(sequence_address + sizeof(u64), system.GPU().GetTicks());
|
||||||
|
memory_manager.Write<u64>(sequence_address, payload);
|
||||||
|
});
|
||||||
|
rasterizer->SyncOperation(std::move(operation));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case Regs::QueryOperation::Acquire:
|
case Regs::QueryOperation::Acquire:
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include "common/algorithm.h"
|
||||||
#include "common/assert.h"
|
#include "common/assert.h"
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
#include "common/microprofile.h"
|
#include "common/microprofile.h"
|
||||||
|
@ -54,8 +55,6 @@ void MaxwellDMA::Launch() {
|
||||||
const LaunchDMA& launch = regs.launch_dma;
|
const LaunchDMA& launch = regs.launch_dma;
|
||||||
ASSERT(launch.interrupt_type == LaunchDMA::InterruptType::NONE);
|
ASSERT(launch.interrupt_type == LaunchDMA::InterruptType::NONE);
|
||||||
ASSERT(launch.data_transfer_type == LaunchDMA::DataTransferType::NON_PIPELINED);
|
ASSERT(launch.data_transfer_type == LaunchDMA::DataTransferType::NON_PIPELINED);
|
||||||
ASSERT(regs.dst_params.origin.x == 0);
|
|
||||||
ASSERT(regs.dst_params.origin.y == 0);
|
|
||||||
|
|
||||||
const bool is_src_pitch = launch.src_memory_layout == LaunchDMA::MemoryLayout::PITCH;
|
const bool is_src_pitch = launch.src_memory_layout == LaunchDMA::MemoryLayout::PITCH;
|
||||||
const bool is_dst_pitch = launch.dst_memory_layout == LaunchDMA::MemoryLayout::PITCH;
|
const bool is_dst_pitch = launch.dst_memory_layout == LaunchDMA::MemoryLayout::PITCH;
|
||||||
|
@ -121,23 +120,40 @@ void MaxwellDMA::CopyPitchToPitch() {
|
||||||
|
|
||||||
void MaxwellDMA::CopyBlockLinearToPitch() {
|
void MaxwellDMA::CopyBlockLinearToPitch() {
|
||||||
UNIMPLEMENTED_IF(regs.src_params.block_size.width != 0);
|
UNIMPLEMENTED_IF(regs.src_params.block_size.width != 0);
|
||||||
UNIMPLEMENTED_IF(regs.src_params.block_size.depth != 0);
|
|
||||||
UNIMPLEMENTED_IF(regs.src_params.layer != 0);
|
UNIMPLEMENTED_IF(regs.src_params.layer != 0);
|
||||||
|
|
||||||
|
const bool is_remapping = regs.launch_dma.remap_enable != 0;
|
||||||
|
|
||||||
// Optimized path for micro copies.
|
// Optimized path for micro copies.
|
||||||
const size_t dst_size = static_cast<size_t>(regs.pitch_out) * regs.line_count;
|
const size_t dst_size = static_cast<size_t>(regs.pitch_out) * regs.line_count;
|
||||||
if (dst_size < GOB_SIZE && regs.pitch_out <= GOB_SIZE_X &&
|
if (!is_remapping && dst_size < GOB_SIZE && regs.pitch_out <= GOB_SIZE_X &&
|
||||||
regs.src_params.height > GOB_SIZE_Y) {
|
regs.src_params.height > GOB_SIZE_Y) {
|
||||||
FastCopyBlockLinearToPitch();
|
FastCopyBlockLinearToPitch();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Deswizzle the input and copy it over.
|
// Deswizzle the input and copy it over.
|
||||||
UNIMPLEMENTED_IF(regs.launch_dma.remap_enable != 0);
|
|
||||||
const u32 bytes_per_pixel =
|
|
||||||
regs.launch_dma.remap_enable ? regs.pitch_out / regs.line_length_in : 1;
|
|
||||||
const Parameters& src_params = regs.src_params;
|
const Parameters& src_params = regs.src_params;
|
||||||
const u32 width = src_params.width;
|
|
||||||
|
const u32 num_remap_components = regs.remap_const.num_dst_components_minus_one + 1;
|
||||||
|
const u32 remap_components_size = regs.remap_const.component_size_minus_one + 1;
|
||||||
|
|
||||||
|
const u32 base_bpp = !is_remapping ? 1U : num_remap_components * remap_components_size;
|
||||||
|
|
||||||
|
u32 width = src_params.width;
|
||||||
|
u32 x_elements = regs.line_length_in;
|
||||||
|
u32 x_offset = src_params.origin.x;
|
||||||
|
u32 bpp_shift = 0U;
|
||||||
|
if (!is_remapping) {
|
||||||
|
bpp_shift = Common::FoldRight(
|
||||||
|
4U, [](u32 x, u32 y) { return std::min(x, static_cast<u32>(std::countr_zero(y))); },
|
||||||
|
width, x_elements, x_offset, static_cast<u32>(regs.offset_in));
|
||||||
|
width >>= bpp_shift;
|
||||||
|
x_elements >>= bpp_shift;
|
||||||
|
x_offset >>= bpp_shift;
|
||||||
|
}
|
||||||
|
|
||||||
|
const u32 bytes_per_pixel = base_bpp << bpp_shift;
|
||||||
const u32 height = src_params.height;
|
const u32 height = src_params.height;
|
||||||
const u32 depth = src_params.depth;
|
const u32 depth = src_params.depth;
|
||||||
const u32 block_height = src_params.block_size.height;
|
const u32 block_height = src_params.block_size.height;
|
||||||
|
@ -155,30 +171,45 @@ void MaxwellDMA::CopyBlockLinearToPitch() {
|
||||||
memory_manager.ReadBlock(regs.offset_in, read_buffer.data(), src_size);
|
memory_manager.ReadBlock(regs.offset_in, read_buffer.data(), src_size);
|
||||||
memory_manager.ReadBlock(regs.offset_out, write_buffer.data(), dst_size);
|
memory_manager.ReadBlock(regs.offset_out, write_buffer.data(), dst_size);
|
||||||
|
|
||||||
UnswizzleSubrect(regs.line_length_in, regs.line_count, regs.pitch_out, width, bytes_per_pixel,
|
UnswizzleSubrect(write_buffer, read_buffer, bytes_per_pixel, width, height, depth, x_offset,
|
||||||
block_height, src_params.origin.x, src_params.origin.y, write_buffer.data(),
|
src_params.origin.y, x_elements, regs.line_count, block_height, block_depth,
|
||||||
read_buffer.data());
|
regs.pitch_out);
|
||||||
|
|
||||||
memory_manager.WriteBlock(regs.offset_out, write_buffer.data(), dst_size);
|
memory_manager.WriteBlock(regs.offset_out, write_buffer.data(), dst_size);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MaxwellDMA::CopyPitchToBlockLinear() {
|
void MaxwellDMA::CopyPitchToBlockLinear() {
|
||||||
UNIMPLEMENTED_IF_MSG(regs.dst_params.block_size.width != 0, "Block width is not one");
|
UNIMPLEMENTED_IF_MSG(regs.dst_params.block_size.width != 0, "Block width is not one");
|
||||||
UNIMPLEMENTED_IF(regs.launch_dma.remap_enable != 0);
|
UNIMPLEMENTED_IF(regs.dst_params.layer != 0);
|
||||||
|
|
||||||
|
const bool is_remapping = regs.launch_dma.remap_enable != 0;
|
||||||
|
const u32 num_remap_components = regs.remap_const.num_dst_components_minus_one + 1;
|
||||||
|
const u32 remap_components_size = regs.remap_const.component_size_minus_one + 1;
|
||||||
|
|
||||||
const auto& dst_params = regs.dst_params;
|
const auto& dst_params = regs.dst_params;
|
||||||
const u32 bytes_per_pixel =
|
|
||||||
regs.launch_dma.remap_enable ? regs.pitch_in / regs.line_length_in : 1;
|
const u32 base_bpp = !is_remapping ? 1U : num_remap_components * remap_components_size;
|
||||||
const u32 width = dst_params.width;
|
|
||||||
|
u32 width = dst_params.width;
|
||||||
|
u32 x_elements = regs.line_length_in;
|
||||||
|
u32 x_offset = dst_params.origin.x;
|
||||||
|
u32 bpp_shift = 0U;
|
||||||
|
if (!is_remapping) {
|
||||||
|
bpp_shift = Common::FoldRight(
|
||||||
|
4U, [](u32 x, u32 y) { return std::min(x, static_cast<u32>(std::countr_zero(y))); },
|
||||||
|
width, x_elements, x_offset, static_cast<u32>(regs.offset_out));
|
||||||
|
width >>= bpp_shift;
|
||||||
|
x_elements >>= bpp_shift;
|
||||||
|
x_offset >>= bpp_shift;
|
||||||
|
}
|
||||||
|
|
||||||
|
const u32 bytes_per_pixel = base_bpp << bpp_shift;
|
||||||
const u32 height = dst_params.height;
|
const u32 height = dst_params.height;
|
||||||
const u32 depth = dst_params.depth;
|
const u32 depth = dst_params.depth;
|
||||||
const u32 block_height = dst_params.block_size.height;
|
const u32 block_height = dst_params.block_size.height;
|
||||||
const u32 block_depth = dst_params.block_size.depth;
|
const u32 block_depth = dst_params.block_size.depth;
|
||||||
const size_t dst_size =
|
const size_t dst_size =
|
||||||
CalculateSize(true, bytes_per_pixel, width, height, depth, block_height, block_depth);
|
CalculateSize(true, bytes_per_pixel, width, height, depth, block_height, block_depth);
|
||||||
const size_t dst_layer_size =
|
|
||||||
CalculateSize(true, bytes_per_pixel, width, height, 1, block_height, block_depth);
|
|
||||||
|
|
||||||
const size_t src_size = static_cast<size_t>(regs.pitch_in) * regs.line_count;
|
const size_t src_size = static_cast<size_t>(regs.pitch_in) * regs.line_count;
|
||||||
|
|
||||||
if (read_buffer.size() < src_size) {
|
if (read_buffer.size() < src_size) {
|
||||||
|
@ -188,32 +219,23 @@ void MaxwellDMA::CopyPitchToBlockLinear() {
|
||||||
write_buffer.resize(dst_size);
|
write_buffer.resize(dst_size);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Settings::IsGPULevelExtreme()) {
|
|
||||||
memory_manager.ReadBlock(regs.offset_in, read_buffer.data(), src_size);
|
memory_manager.ReadBlock(regs.offset_in, read_buffer.data(), src_size);
|
||||||
|
if (Settings::IsGPULevelExtreme()) {
|
||||||
memory_manager.ReadBlock(regs.offset_out, write_buffer.data(), dst_size);
|
memory_manager.ReadBlock(regs.offset_out, write_buffer.data(), dst_size);
|
||||||
} else {
|
} else {
|
||||||
memory_manager.ReadBlockUnsafe(regs.offset_in, read_buffer.data(), src_size);
|
|
||||||
memory_manager.ReadBlockUnsafe(regs.offset_out, write_buffer.data(), dst_size);
|
memory_manager.ReadBlockUnsafe(regs.offset_out, write_buffer.data(), dst_size);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the input is linear and the output is tiled, swizzle the input and copy it over.
|
// If the input is linear and the output is tiled, swizzle the input and copy it over.
|
||||||
if (regs.dst_params.block_size.depth > 0) {
|
SwizzleSubrect(write_buffer, read_buffer, bytes_per_pixel, width, height, depth, x_offset,
|
||||||
ASSERT(dst_params.layer == 0);
|
dst_params.origin.y, x_elements, regs.line_count, block_height, block_depth,
|
||||||
SwizzleSliceToVoxel(regs.line_length_in, regs.line_count, regs.pitch_in, width, height,
|
regs.pitch_in);
|
||||||
bytes_per_pixel, block_height, block_depth, dst_params.origin.x,
|
|
||||||
dst_params.origin.y, write_buffer.data(), read_buffer.data());
|
|
||||||
} else {
|
|
||||||
SwizzleSubrect(regs.line_length_in, regs.line_count, regs.pitch_in, width, bytes_per_pixel,
|
|
||||||
write_buffer.data() + dst_layer_size * dst_params.layer, read_buffer.data(),
|
|
||||||
block_height, dst_params.origin.x, dst_params.origin.y);
|
|
||||||
}
|
|
||||||
|
|
||||||
memory_manager.WriteBlock(regs.offset_out, write_buffer.data(), dst_size);
|
memory_manager.WriteBlock(regs.offset_out, write_buffer.data(), dst_size);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MaxwellDMA::FastCopyBlockLinearToPitch() {
|
void MaxwellDMA::FastCopyBlockLinearToPitch() {
|
||||||
const u32 bytes_per_pixel =
|
const u32 bytes_per_pixel = 1U;
|
||||||
regs.launch_dma.remap_enable ? regs.pitch_out / regs.line_length_in : 1;
|
|
||||||
const size_t src_size = GOB_SIZE;
|
const size_t src_size = GOB_SIZE;
|
||||||
const size_t dst_size = static_cast<size_t>(regs.pitch_out) * regs.line_count;
|
const size_t dst_size = static_cast<size_t>(regs.pitch_out) * regs.line_count;
|
||||||
u32 pos_x = regs.src_params.origin.x;
|
u32 pos_x = regs.src_params.origin.x;
|
||||||
|
@ -239,9 +261,10 @@ void MaxwellDMA::FastCopyBlockLinearToPitch() {
|
||||||
memory_manager.ReadBlockUnsafe(regs.offset_out, write_buffer.data(), dst_size);
|
memory_manager.ReadBlockUnsafe(regs.offset_out, write_buffer.data(), dst_size);
|
||||||
}
|
}
|
||||||
|
|
||||||
UnswizzleSubrect(regs.line_length_in, regs.line_count, regs.pitch_out, regs.src_params.width,
|
UnswizzleSubrect(write_buffer, read_buffer, bytes_per_pixel, regs.src_params.width,
|
||||||
bytes_per_pixel, regs.src_params.block_size.height, pos_x, pos_y,
|
regs.src_params.height, 1, pos_x, pos_y, regs.line_length_in, regs.line_count,
|
||||||
write_buffer.data(), read_buffer.data());
|
regs.src_params.block_size.height, regs.src_params.block_size.depth,
|
||||||
|
regs.pitch_out);
|
||||||
|
|
||||||
memory_manager.WriteBlock(regs.offset_out, write_buffer.data(), dst_size);
|
memory_manager.WriteBlock(regs.offset_out, write_buffer.data(), dst_size);
|
||||||
}
|
}
|
||||||
|
@ -249,16 +272,24 @@ void MaxwellDMA::FastCopyBlockLinearToPitch() {
|
||||||
void MaxwellDMA::ReleaseSemaphore() {
|
void MaxwellDMA::ReleaseSemaphore() {
|
||||||
const auto type = regs.launch_dma.semaphore_type;
|
const auto type = regs.launch_dma.semaphore_type;
|
||||||
const GPUVAddr address = regs.semaphore.address;
|
const GPUVAddr address = regs.semaphore.address;
|
||||||
|
const u32 payload = regs.semaphore.payload;
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case LaunchDMA::SemaphoreType::NONE:
|
case LaunchDMA::SemaphoreType::NONE:
|
||||||
break;
|
break;
|
||||||
case LaunchDMA::SemaphoreType::RELEASE_ONE_WORD_SEMAPHORE:
|
case LaunchDMA::SemaphoreType::RELEASE_ONE_WORD_SEMAPHORE: {
|
||||||
memory_manager.Write<u32>(address, regs.semaphore.payload);
|
std::function<void()> operation(
|
||||||
|
[this, address, payload] { memory_manager.Write<u32>(address, payload); });
|
||||||
|
rasterizer->SignalFence(std::move(operation));
|
||||||
break;
|
break;
|
||||||
case LaunchDMA::SemaphoreType::RELEASE_FOUR_WORD_SEMAPHORE:
|
}
|
||||||
memory_manager.Write<u64>(address, static_cast<u64>(regs.semaphore.payload));
|
case LaunchDMA::SemaphoreType::RELEASE_FOUR_WORD_SEMAPHORE: {
|
||||||
memory_manager.Write<u64>(address + 8, system.GPU().GetTicks());
|
std::function<void()> operation([this, address, payload] {
|
||||||
|
memory_manager.Write<u64>(address + sizeof(u64), system.GPU().GetTicks());
|
||||||
|
memory_manager.Write<u64>(address, payload);
|
||||||
|
});
|
||||||
|
rasterizer->SignalFence(std::move(operation));
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
ASSERT_MSG(false, "Unknown semaphore type: {}", static_cast<u32>(type.Value()));
|
ASSERT_MSG(false, "Unknown semaphore type: {}", static_cast<u32>(type.Value()));
|
||||||
}
|
}
|
||||||
|
|
|
@ -189,10 +189,16 @@ public:
|
||||||
BitField<4, 3, Swizzle> dst_y;
|
BitField<4, 3, Swizzle> dst_y;
|
||||||
BitField<8, 3, Swizzle> dst_z;
|
BitField<8, 3, Swizzle> dst_z;
|
||||||
BitField<12, 3, Swizzle> dst_w;
|
BitField<12, 3, Swizzle> dst_w;
|
||||||
|
BitField<0, 12, u32> dst_components_raw;
|
||||||
BitField<16, 2, u32> component_size_minus_one;
|
BitField<16, 2, u32> component_size_minus_one;
|
||||||
BitField<20, 2, u32> num_src_components_minus_one;
|
BitField<20, 2, u32> num_src_components_minus_one;
|
||||||
BitField<24, 2, u32> num_dst_components_minus_one;
|
BitField<24, 2, u32> num_dst_components_minus_one;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Swizzle GetComponent(size_t i) const {
|
||||||
|
const u32 raw = dst_components_raw;
|
||||||
|
return static_cast<Swizzle>((raw >> (i * 3)) & 0x7);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
static_assert(sizeof(RemapConst) == 12);
|
static_assert(sizeof(RemapConst) == 12);
|
||||||
|
|
||||||
|
|
306
src/video_core/engines/puller.cpp
Normal file
306
src/video_core/engines/puller.cpp
Normal file
|
@ -0,0 +1,306 @@
|
||||||
|
// SPDX-FileCopyrightText: 2022 yuzu Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
#include "common/assert.h"
|
||||||
|
#include "common/logging/log.h"
|
||||||
|
#include "common/settings.h"
|
||||||
|
#include "core/core.h"
|
||||||
|
#include "video_core/control/channel_state.h"
|
||||||
|
#include "video_core/dma_pusher.h"
|
||||||
|
#include "video_core/engines/fermi_2d.h"
|
||||||
|
#include "video_core/engines/kepler_compute.h"
|
||||||
|
#include "video_core/engines/kepler_memory.h"
|
||||||
|
#include "video_core/engines/maxwell_3d.h"
|
||||||
|
#include "video_core/engines/maxwell_dma.h"
|
||||||
|
#include "video_core/engines/puller.h"
|
||||||
|
#include "video_core/gpu.h"
|
||||||
|
#include "video_core/memory_manager.h"
|
||||||
|
#include "video_core/rasterizer_interface.h"
|
||||||
|
|
||||||
|
namespace Tegra::Engines {
|
||||||
|
|
||||||
|
Puller::Puller(GPU& gpu_, MemoryManager& memory_manager_, DmaPusher& dma_pusher_,
|
||||||
|
Control::ChannelState& channel_state_)
|
||||||
|
: gpu{gpu_}, memory_manager{memory_manager_}, dma_pusher{dma_pusher_}, channel_state{
|
||||||
|
channel_state_} {}
|
||||||
|
|
||||||
|
Puller::~Puller() = default;
|
||||||
|
|
||||||
|
void Puller::ProcessBindMethod(const MethodCall& method_call) {
|
||||||
|
// Bind the current subchannel to the desired engine id.
|
||||||
|
LOG_DEBUG(HW_GPU, "Binding subchannel {} to engine {}", method_call.subchannel,
|
||||||
|
method_call.argument);
|
||||||
|
const auto engine_id = static_cast<EngineID>(method_call.argument);
|
||||||
|
bound_engines[method_call.subchannel] = static_cast<EngineID>(engine_id);
|
||||||
|
switch (engine_id) {
|
||||||
|
case EngineID::FERMI_TWOD_A:
|
||||||
|
dma_pusher.BindSubchannel(channel_state.fermi_2d.get(), method_call.subchannel);
|
||||||
|
break;
|
||||||
|
case EngineID::MAXWELL_B:
|
||||||
|
dma_pusher.BindSubchannel(channel_state.maxwell_3d.get(), method_call.subchannel);
|
||||||
|
break;
|
||||||
|
case EngineID::KEPLER_COMPUTE_B:
|
||||||
|
dma_pusher.BindSubchannel(channel_state.kepler_compute.get(), method_call.subchannel);
|
||||||
|
break;
|
||||||
|
case EngineID::MAXWELL_DMA_COPY_A:
|
||||||
|
dma_pusher.BindSubchannel(channel_state.maxwell_dma.get(), method_call.subchannel);
|
||||||
|
break;
|
||||||
|
case EngineID::KEPLER_INLINE_TO_MEMORY_B:
|
||||||
|
dma_pusher.BindSubchannel(channel_state.kepler_memory.get(), method_call.subchannel);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
UNIMPLEMENTED_MSG("Unimplemented engine {:04X}", engine_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Puller::ProcessFenceActionMethod() {
|
||||||
|
switch (regs.fence_action.op) {
|
||||||
|
case Puller::FenceOperation::Acquire:
|
||||||
|
// UNIMPLEMENTED_MSG("Channel Scheduling pending.");
|
||||||
|
// WaitFence(regs.fence_action.syncpoint_id, regs.fence_value);
|
||||||
|
rasterizer->ReleaseFences();
|
||||||
|
break;
|
||||||
|
case Puller::FenceOperation::Increment:
|
||||||
|
rasterizer->SignalSyncPoint(regs.fence_action.syncpoint_id);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
UNIMPLEMENTED_MSG("Unimplemented operation {}", regs.fence_action.op.Value());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Puller::ProcessSemaphoreTriggerMethod() {
|
||||||
|
const auto semaphoreOperationMask = 0xF;
|
||||||
|
const auto op =
|
||||||
|
static_cast<GpuSemaphoreOperation>(regs.semaphore_trigger & semaphoreOperationMask);
|
||||||
|
if (op == GpuSemaphoreOperation::WriteLong) {
|
||||||
|
const GPUVAddr sequence_address{regs.semaphore_address.SemaphoreAddress()};
|
||||||
|
const u32 payload = regs.semaphore_sequence;
|
||||||
|
std::function<void()> operation([this, sequence_address, payload] {
|
||||||
|
memory_manager.Write<u64>(sequence_address + sizeof(u64), gpu.GetTicks());
|
||||||
|
memory_manager.Write<u64>(sequence_address, payload);
|
||||||
|
});
|
||||||
|
rasterizer->SignalFence(std::move(operation));
|
||||||
|
} else {
|
||||||
|
do {
|
||||||
|
const u32 word{memory_manager.Read<u32>(regs.semaphore_address.SemaphoreAddress())};
|
||||||
|
regs.acquire_source = true;
|
||||||
|
regs.acquire_value = regs.semaphore_sequence;
|
||||||
|
if (op == GpuSemaphoreOperation::AcquireEqual) {
|
||||||
|
regs.acquire_active = true;
|
||||||
|
regs.acquire_mode = false;
|
||||||
|
if (word != regs.acquire_value) {
|
||||||
|
rasterizer->ReleaseFences();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
} else if (op == GpuSemaphoreOperation::AcquireGequal) {
|
||||||
|
regs.acquire_active = true;
|
||||||
|
regs.acquire_mode = true;
|
||||||
|
if (word < regs.acquire_value) {
|
||||||
|
rasterizer->ReleaseFences();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
} else if (op == GpuSemaphoreOperation::AcquireMask) {
|
||||||
|
if (word && regs.semaphore_sequence == 0) {
|
||||||
|
rasterizer->ReleaseFences();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
LOG_ERROR(HW_GPU, "Invalid semaphore operation");
|
||||||
|
}
|
||||||
|
} while (false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Puller::ProcessSemaphoreRelease() {
|
||||||
|
const GPUVAddr sequence_address{regs.semaphore_address.SemaphoreAddress()};
|
||||||
|
const u32 payload = regs.semaphore_release;
|
||||||
|
std::function<void()> operation([this, sequence_address, payload] {
|
||||||
|
memory_manager.Write<u32>(sequence_address, payload);
|
||||||
|
});
|
||||||
|
rasterizer->SyncOperation(std::move(operation));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Puller::ProcessSemaphoreAcquire() {
|
||||||
|
u32 word = memory_manager.Read<u32>(regs.semaphore_address.SemaphoreAddress());
|
||||||
|
const auto value = regs.semaphore_acquire;
|
||||||
|
while (word != value) {
|
||||||
|
regs.acquire_active = true;
|
||||||
|
regs.acquire_value = value;
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||||
|
rasterizer->ReleaseFences();
|
||||||
|
word = memory_manager.Read<u32>(regs.semaphore_address.SemaphoreAddress());
|
||||||
|
// TODO(kemathe73) figure out how to do the acquire_timeout
|
||||||
|
regs.acquire_mode = false;
|
||||||
|
regs.acquire_source = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Calls a GPU puller method.
|
||||||
|
void Puller::CallPullerMethod(const MethodCall& method_call) {
|
||||||
|
regs.reg_array[method_call.method] = method_call.argument;
|
||||||
|
const auto method = static_cast<BufferMethods>(method_call.method);
|
||||||
|
|
||||||
|
switch (method) {
|
||||||
|
case BufferMethods::BindObject: {
|
||||||
|
ProcessBindMethod(method_call);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case BufferMethods::Nop:
|
||||||
|
case BufferMethods::SemaphoreAddressHigh:
|
||||||
|
case BufferMethods::SemaphoreAddressLow:
|
||||||
|
case BufferMethods::SemaphoreSequencePayload:
|
||||||
|
case BufferMethods::SyncpointPayload:
|
||||||
|
break;
|
||||||
|
case BufferMethods::WrcacheFlush:
|
||||||
|
case BufferMethods::RefCnt:
|
||||||
|
rasterizer->SignalReference();
|
||||||
|
break;
|
||||||
|
case BufferMethods::SyncpointOperation:
|
||||||
|
ProcessFenceActionMethod();
|
||||||
|
break;
|
||||||
|
case BufferMethods::WaitForIdle:
|
||||||
|
rasterizer->WaitForIdle();
|
||||||
|
break;
|
||||||
|
case BufferMethods::SemaphoreOperation: {
|
||||||
|
ProcessSemaphoreTriggerMethod();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case BufferMethods::NonStallInterrupt: {
|
||||||
|
LOG_ERROR(HW_GPU, "Special puller engine method NonStallInterrupt not implemented");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case BufferMethods::MemOpA: {
|
||||||
|
LOG_ERROR(HW_GPU, "Memory Operation A");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case BufferMethods::MemOpB: {
|
||||||
|
// Implement this better.
|
||||||
|
rasterizer->InvalidateGPUCache();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case BufferMethods::MemOpC:
|
||||||
|
case BufferMethods::MemOpD: {
|
||||||
|
LOG_ERROR(HW_GPU, "Memory Operation C,D");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case BufferMethods::SemaphoreAcquire: {
|
||||||
|
ProcessSemaphoreAcquire();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case BufferMethods::SemaphoreRelease: {
|
||||||
|
ProcessSemaphoreRelease();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case BufferMethods::Yield: {
|
||||||
|
// TODO(Kmather73): Research and implement this method.
|
||||||
|
LOG_ERROR(HW_GPU, "Special puller engine method Yield not implemented");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
LOG_ERROR(HW_GPU, "Special puller engine method {:X} not implemented", method);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Calls a GPU engine method.
|
||||||
|
void Puller::CallEngineMethod(const MethodCall& method_call) {
|
||||||
|
const EngineID engine = bound_engines[method_call.subchannel];
|
||||||
|
|
||||||
|
switch (engine) {
|
||||||
|
case EngineID::FERMI_TWOD_A:
|
||||||
|
channel_state.fermi_2d->CallMethod(method_call.method, method_call.argument,
|
||||||
|
method_call.IsLastCall());
|
||||||
|
break;
|
||||||
|
case EngineID::MAXWELL_B:
|
||||||
|
channel_state.maxwell_3d->CallMethod(method_call.method, method_call.argument,
|
||||||
|
method_call.IsLastCall());
|
||||||
|
break;
|
||||||
|
case EngineID::KEPLER_COMPUTE_B:
|
||||||
|
channel_state.kepler_compute->CallMethod(method_call.method, method_call.argument,
|
||||||
|
method_call.IsLastCall());
|
||||||
|
break;
|
||||||
|
case EngineID::MAXWELL_DMA_COPY_A:
|
||||||
|
channel_state.maxwell_dma->CallMethod(method_call.method, method_call.argument,
|
||||||
|
method_call.IsLastCall());
|
||||||
|
break;
|
||||||
|
case EngineID::KEPLER_INLINE_TO_MEMORY_B:
|
||||||
|
channel_state.kepler_memory->CallMethod(method_call.method, method_call.argument,
|
||||||
|
method_call.IsLastCall());
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
UNIMPLEMENTED_MSG("Unimplemented engine");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Calls a GPU engine multivalue method.
|
||||||
|
void Puller::CallEngineMultiMethod(u32 method, u32 subchannel, const u32* base_start, u32 amount,
|
||||||
|
u32 methods_pending) {
|
||||||
|
const EngineID engine = bound_engines[subchannel];
|
||||||
|
|
||||||
|
switch (engine) {
|
||||||
|
case EngineID::FERMI_TWOD_A:
|
||||||
|
channel_state.fermi_2d->CallMultiMethod(method, base_start, amount, methods_pending);
|
||||||
|
break;
|
||||||
|
case EngineID::MAXWELL_B:
|
||||||
|
channel_state.maxwell_3d->CallMultiMethod(method, base_start, amount, methods_pending);
|
||||||
|
break;
|
||||||
|
case EngineID::KEPLER_COMPUTE_B:
|
||||||
|
channel_state.kepler_compute->CallMultiMethod(method, base_start, amount, methods_pending);
|
||||||
|
break;
|
||||||
|
case EngineID::MAXWELL_DMA_COPY_A:
|
||||||
|
channel_state.maxwell_dma->CallMultiMethod(method, base_start, amount, methods_pending);
|
||||||
|
break;
|
||||||
|
case EngineID::KEPLER_INLINE_TO_MEMORY_B:
|
||||||
|
channel_state.kepler_memory->CallMultiMethod(method, base_start, amount, methods_pending);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
UNIMPLEMENTED_MSG("Unimplemented engine");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Calls a GPU method.
|
||||||
|
void Puller::CallMethod(const MethodCall& method_call) {
|
||||||
|
LOG_TRACE(HW_GPU, "Processing method {:08X} on subchannel {}", method_call.method,
|
||||||
|
method_call.subchannel);
|
||||||
|
|
||||||
|
ASSERT(method_call.subchannel < bound_engines.size());
|
||||||
|
|
||||||
|
if (ExecuteMethodOnEngine(method_call.method)) {
|
||||||
|
CallEngineMethod(method_call);
|
||||||
|
} else {
|
||||||
|
CallPullerMethod(method_call);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Calls a GPU multivalue method.
|
||||||
|
void Puller::CallMultiMethod(u32 method, u32 subchannel, const u32* base_start, u32 amount,
|
||||||
|
u32 methods_pending) {
|
||||||
|
LOG_TRACE(HW_GPU, "Processing method {:08X} on subchannel {}", method, subchannel);
|
||||||
|
|
||||||
|
ASSERT(subchannel < bound_engines.size());
|
||||||
|
|
||||||
|
if (ExecuteMethodOnEngine(method)) {
|
||||||
|
CallEngineMultiMethod(method, subchannel, base_start, amount, methods_pending);
|
||||||
|
} else {
|
||||||
|
for (std::size_t i = 0; i < amount; i++) {
|
||||||
|
CallPullerMethod(MethodCall{
|
||||||
|
method,
|
||||||
|
base_start[i],
|
||||||
|
subchannel,
|
||||||
|
methods_pending - static_cast<u32>(i),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Puller::BindRasterizer(VideoCore::RasterizerInterface* rasterizer_) {
|
||||||
|
rasterizer = rasterizer_;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Determines where the method should be executed.
|
||||||
|
[[nodiscard]] bool Puller::ExecuteMethodOnEngine(u32 method) {
|
||||||
|
const auto buffer_method = static_cast<BufferMethods>(method);
|
||||||
|
return buffer_method >= BufferMethods::NonPullerMethods;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Tegra::Engines
|
177
src/video_core/engines/puller.h
Normal file
177
src/video_core/engines/puller.h
Normal file
|
@ -0,0 +1,177 @@
|
||||||
|
// SPDX-FileCopyrightText: 2022 yuzu Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <cstddef>
|
||||||
|
#include <vector>
|
||||||
|
#include "common/bit_field.h"
|
||||||
|
#include "common/common_funcs.h"
|
||||||
|
#include "common/common_types.h"
|
||||||
|
#include "video_core/engines/engine_interface.h"
|
||||||
|
|
||||||
|
namespace Core {
|
||||||
|
class System;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace Tegra {
|
||||||
|
class MemoryManager;
|
||||||
|
class DmaPusher;
|
||||||
|
|
||||||
|
enum class EngineID {
|
||||||
|
FERMI_TWOD_A = 0x902D, // 2D Engine
|
||||||
|
MAXWELL_B = 0xB197, // 3D Engine
|
||||||
|
KEPLER_COMPUTE_B = 0xB1C0,
|
||||||
|
KEPLER_INLINE_TO_MEMORY_B = 0xA140,
|
||||||
|
MAXWELL_DMA_COPY_A = 0xB0B5,
|
||||||
|
};
|
||||||
|
|
||||||
|
namespace Control {
|
||||||
|
struct ChannelState;
|
||||||
|
}
|
||||||
|
} // namespace Tegra
|
||||||
|
|
||||||
|
namespace VideoCore {
|
||||||
|
class RasterizerInterface;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace Tegra::Engines {
|
||||||
|
|
||||||
|
class Puller final {
|
||||||
|
public:
|
||||||
|
struct MethodCall {
|
||||||
|
u32 method{};
|
||||||
|
u32 argument{};
|
||||||
|
u32 subchannel{};
|
||||||
|
u32 method_count{};
|
||||||
|
|
||||||
|
explicit MethodCall(u32 method_, u32 argument_, u32 subchannel_ = 0, u32 method_count_ = 0)
|
||||||
|
: method(method_), argument(argument_), subchannel(subchannel_),
|
||||||
|
method_count(method_count_) {}
|
||||||
|
|
||||||
|
[[nodiscard]] bool IsLastCall() const {
|
||||||
|
return method_count <= 1;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class FenceOperation : u32 {
|
||||||
|
Acquire = 0,
|
||||||
|
Increment = 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
union FenceAction {
|
||||||
|
u32 raw;
|
||||||
|
BitField<0, 1, FenceOperation> op;
|
||||||
|
BitField<8, 24, u32> syncpoint_id;
|
||||||
|
};
|
||||||
|
|
||||||
|
explicit Puller(GPU& gpu_, MemoryManager& memory_manager_, DmaPusher& dma_pusher,
|
||||||
|
Control::ChannelState& channel_state);
|
||||||
|
~Puller();
|
||||||
|
|
||||||
|
void CallMethod(const MethodCall& method_call);
|
||||||
|
|
||||||
|
void CallMultiMethod(u32 method, u32 subchannel, const u32* base_start, u32 amount,
|
||||||
|
u32 methods_pending);
|
||||||
|
|
||||||
|
void BindRasterizer(VideoCore::RasterizerInterface* rasterizer);
|
||||||
|
|
||||||
|
void CallPullerMethod(const MethodCall& method_call);
|
||||||
|
|
||||||
|
void CallEngineMethod(const MethodCall& method_call);
|
||||||
|
|
||||||
|
void CallEngineMultiMethod(u32 method, u32 subchannel, const u32* base_start, u32 amount,
|
||||||
|
u32 methods_pending);
|
||||||
|
|
||||||
|
private:
|
||||||
|
Tegra::GPU& gpu;
|
||||||
|
|
||||||
|
MemoryManager& memory_manager;
|
||||||
|
DmaPusher& dma_pusher;
|
||||||
|
Control::ChannelState& channel_state;
|
||||||
|
VideoCore::RasterizerInterface* rasterizer = nullptr;
|
||||||
|
|
||||||
|
static constexpr std::size_t NUM_REGS = 0x800;
|
||||||
|
struct Regs {
|
||||||
|
static constexpr size_t NUM_REGS = 0x40;
|
||||||
|
|
||||||
|
union {
|
||||||
|
struct {
|
||||||
|
INSERT_PADDING_WORDS_NOINIT(0x4);
|
||||||
|
struct {
|
||||||
|
u32 address_high;
|
||||||
|
u32 address_low;
|
||||||
|
|
||||||
|
[[nodiscard]] GPUVAddr SemaphoreAddress() const {
|
||||||
|
return static_cast<GPUVAddr>((static_cast<GPUVAddr>(address_high) << 32) |
|
||||||
|
address_low);
|
||||||
|
}
|
||||||
|
} semaphore_address;
|
||||||
|
|
||||||
|
u32 semaphore_sequence;
|
||||||
|
u32 semaphore_trigger;
|
||||||
|
INSERT_PADDING_WORDS_NOINIT(0xC);
|
||||||
|
|
||||||
|
// The pusher and the puller share the reference counter, the pusher only has read
|
||||||
|
// access
|
||||||
|
u32 reference_count;
|
||||||
|
INSERT_PADDING_WORDS_NOINIT(0x5);
|
||||||
|
|
||||||
|
u32 semaphore_acquire;
|
||||||
|
u32 semaphore_release;
|
||||||
|
u32 fence_value;
|
||||||
|
FenceAction fence_action;
|
||||||
|
INSERT_PADDING_WORDS_NOINIT(0xE2);
|
||||||
|
|
||||||
|
// Puller state
|
||||||
|
u32 acquire_mode;
|
||||||
|
u32 acquire_source;
|
||||||
|
u32 acquire_active;
|
||||||
|
u32 acquire_timeout;
|
||||||
|
u32 acquire_value;
|
||||||
|
};
|
||||||
|
std::array<u32, NUM_REGS> reg_array;
|
||||||
|
};
|
||||||
|
} regs{};
|
||||||
|
|
||||||
|
void ProcessBindMethod(const MethodCall& method_call);
|
||||||
|
void ProcessFenceActionMethod();
|
||||||
|
void ProcessSemaphoreAcquire();
|
||||||
|
void ProcessSemaphoreRelease();
|
||||||
|
void ProcessSemaphoreTriggerMethod();
|
||||||
|
[[nodiscard]] bool ExecuteMethodOnEngine(u32 method);
|
||||||
|
|
||||||
|
/// Mapping of command subchannels to their bound engine ids
|
||||||
|
std::array<EngineID, 8> bound_engines{};
|
||||||
|
|
||||||
|
enum class GpuSemaphoreOperation {
|
||||||
|
AcquireEqual = 0x1,
|
||||||
|
WriteLong = 0x2,
|
||||||
|
AcquireGequal = 0x4,
|
||||||
|
AcquireMask = 0x8,
|
||||||
|
};
|
||||||
|
|
||||||
|
#define ASSERT_REG_POSITION(field_name, position) \
|
||||||
|
static_assert(offsetof(Regs, field_name) == position * 4, \
|
||||||
|
"Field " #field_name " has invalid position")
|
||||||
|
|
||||||
|
ASSERT_REG_POSITION(semaphore_address, 0x4);
|
||||||
|
ASSERT_REG_POSITION(semaphore_sequence, 0x6);
|
||||||
|
ASSERT_REG_POSITION(semaphore_trigger, 0x7);
|
||||||
|
ASSERT_REG_POSITION(reference_count, 0x14);
|
||||||
|
ASSERT_REG_POSITION(semaphore_acquire, 0x1A);
|
||||||
|
ASSERT_REG_POSITION(semaphore_release, 0x1B);
|
||||||
|
ASSERT_REG_POSITION(fence_value, 0x1C);
|
||||||
|
ASSERT_REG_POSITION(fence_action, 0x1D);
|
||||||
|
|
||||||
|
ASSERT_REG_POSITION(acquire_mode, 0x100);
|
||||||
|
ASSERT_REG_POSITION(acquire_source, 0x101);
|
||||||
|
ASSERT_REG_POSITION(acquire_active, 0x102);
|
||||||
|
ASSERT_REG_POSITION(acquire_timeout, 0x103);
|
||||||
|
ASSERT_REG_POSITION(acquire_value, 0x104);
|
||||||
|
|
||||||
|
#undef ASSERT_REG_POSITION
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Tegra::Engines
|
|
@ -4,40 +4,24 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <cstring>
|
||||||
|
#include <deque>
|
||||||
|
#include <functional>
|
||||||
|
#include <memory>
|
||||||
#include <queue>
|
#include <queue>
|
||||||
|
|
||||||
#include "common/common_types.h"
|
#include "common/common_types.h"
|
||||||
#include "video_core/delayed_destruction_ring.h"
|
#include "video_core/delayed_destruction_ring.h"
|
||||||
#include "video_core/gpu.h"
|
#include "video_core/gpu.h"
|
||||||
#include "video_core/memory_manager.h"
|
#include "video_core/host1x/host1x.h"
|
||||||
|
#include "video_core/host1x/syncpoint_manager.h"
|
||||||
#include "video_core/rasterizer_interface.h"
|
#include "video_core/rasterizer_interface.h"
|
||||||
|
|
||||||
namespace VideoCommon {
|
namespace VideoCommon {
|
||||||
|
|
||||||
class FenceBase {
|
class FenceBase {
|
||||||
public:
|
public:
|
||||||
explicit FenceBase(u32 payload_, bool is_stubbed_)
|
explicit FenceBase(bool is_stubbed_) : is_stubbed{is_stubbed_} {}
|
||||||
: address{}, payload{payload_}, is_semaphore{false}, is_stubbed{is_stubbed_} {}
|
|
||||||
|
|
||||||
explicit FenceBase(GPUVAddr address_, u32 payload_, bool is_stubbed_)
|
|
||||||
: address{address_}, payload{payload_}, is_semaphore{true}, is_stubbed{is_stubbed_} {}
|
|
||||||
|
|
||||||
GPUVAddr GetAddress() const {
|
|
||||||
return address;
|
|
||||||
}
|
|
||||||
|
|
||||||
u32 GetPayload() const {
|
|
||||||
return payload;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IsSemaphore() const {
|
|
||||||
return is_semaphore;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
GPUVAddr address;
|
|
||||||
u32 payload;
|
|
||||||
bool is_semaphore;
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
bool is_stubbed;
|
bool is_stubbed;
|
||||||
|
@ -57,30 +41,28 @@ public:
|
||||||
buffer_cache.AccumulateFlushes();
|
buffer_cache.AccumulateFlushes();
|
||||||
}
|
}
|
||||||
|
|
||||||
void SignalSemaphore(GPUVAddr addr, u32 value) {
|
void SyncOperation(std::function<void()>&& func) {
|
||||||
|
uncommitted_operations.emplace_back(std::move(func));
|
||||||
|
}
|
||||||
|
|
||||||
|
void SignalFence(std::function<void()>&& func) {
|
||||||
TryReleasePendingFences();
|
TryReleasePendingFences();
|
||||||
const bool should_flush = ShouldFlush();
|
const bool should_flush = ShouldFlush();
|
||||||
CommitAsyncFlushes();
|
CommitAsyncFlushes();
|
||||||
TFence new_fence = CreateFence(addr, value, !should_flush);
|
uncommitted_operations.emplace_back(std::move(func));
|
||||||
|
CommitOperations();
|
||||||
|
TFence new_fence = CreateFence(!should_flush);
|
||||||
fences.push(new_fence);
|
fences.push(new_fence);
|
||||||
QueueFence(new_fence);
|
QueueFence(new_fence);
|
||||||
if (should_flush) {
|
if (should_flush) {
|
||||||
rasterizer.FlushCommands();
|
rasterizer.FlushCommands();
|
||||||
}
|
}
|
||||||
rasterizer.SyncGuestHost();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SignalSyncPoint(u32 value) {
|
void SignalSyncPoint(u32 value) {
|
||||||
TryReleasePendingFences();
|
syncpoint_manager.IncrementGuest(value);
|
||||||
const bool should_flush = ShouldFlush();
|
std::function<void()> func([this, value] { syncpoint_manager.IncrementHost(value); });
|
||||||
CommitAsyncFlushes();
|
SignalFence(std::move(func));
|
||||||
TFence new_fence = CreateFence(value, !should_flush);
|
|
||||||
fences.push(new_fence);
|
|
||||||
QueueFence(new_fence);
|
|
||||||
if (should_flush) {
|
|
||||||
rasterizer.FlushCommands();
|
|
||||||
}
|
|
||||||
rasterizer.SyncGuestHost();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void WaitPendingFences() {
|
void WaitPendingFences() {
|
||||||
|
@ -90,11 +72,10 @@ public:
|
||||||
WaitFence(current_fence);
|
WaitFence(current_fence);
|
||||||
}
|
}
|
||||||
PopAsyncFlushes();
|
PopAsyncFlushes();
|
||||||
if (current_fence->IsSemaphore()) {
|
auto operations = std::move(pending_operations.front());
|
||||||
gpu_memory.template Write<u32>(current_fence->GetAddress(),
|
pending_operations.pop_front();
|
||||||
current_fence->GetPayload());
|
for (auto& operation : operations) {
|
||||||
} else {
|
operation();
|
||||||
gpu.IncrementSyncPoint(current_fence->GetPayload());
|
|
||||||
}
|
}
|
||||||
PopFence();
|
PopFence();
|
||||||
}
|
}
|
||||||
|
@ -104,16 +85,14 @@ protected:
|
||||||
explicit FenceManager(VideoCore::RasterizerInterface& rasterizer_, Tegra::GPU& gpu_,
|
explicit FenceManager(VideoCore::RasterizerInterface& rasterizer_, Tegra::GPU& gpu_,
|
||||||
TTextureCache& texture_cache_, TTBufferCache& buffer_cache_,
|
TTextureCache& texture_cache_, TTBufferCache& buffer_cache_,
|
||||||
TQueryCache& query_cache_)
|
TQueryCache& query_cache_)
|
||||||
: rasterizer{rasterizer_}, gpu{gpu_}, gpu_memory{gpu.MemoryManager()},
|
: rasterizer{rasterizer_}, gpu{gpu_}, syncpoint_manager{gpu.Host1x().GetSyncpointManager()},
|
||||||
texture_cache{texture_cache_}, buffer_cache{buffer_cache_}, query_cache{query_cache_} {}
|
texture_cache{texture_cache_}, buffer_cache{buffer_cache_}, query_cache{query_cache_} {}
|
||||||
|
|
||||||
virtual ~FenceManager() = default;
|
virtual ~FenceManager() = default;
|
||||||
|
|
||||||
/// Creates a Sync Point Fence Interface, does not create a backend fence if 'is_stubbed' is
|
/// Creates a Fence Interface, does not create a backend fence if 'is_stubbed' is
|
||||||
/// true
|
/// true
|
||||||
virtual TFence CreateFence(u32 value, bool is_stubbed) = 0;
|
virtual TFence CreateFence(bool is_stubbed) = 0;
|
||||||
/// Creates a Semaphore Fence Interface, does not create a backend fence if 'is_stubbed' is true
|
|
||||||
virtual TFence CreateFence(GPUVAddr addr, u32 value, bool is_stubbed) = 0;
|
|
||||||
/// Queues a fence into the backend if the fence isn't stubbed.
|
/// Queues a fence into the backend if the fence isn't stubbed.
|
||||||
virtual void QueueFence(TFence& fence) = 0;
|
virtual void QueueFence(TFence& fence) = 0;
|
||||||
/// Notifies that the backend fence has been signaled/reached in host GPU.
|
/// Notifies that the backend fence has been signaled/reached in host GPU.
|
||||||
|
@ -123,7 +102,7 @@ protected:
|
||||||
|
|
||||||
VideoCore::RasterizerInterface& rasterizer;
|
VideoCore::RasterizerInterface& rasterizer;
|
||||||
Tegra::GPU& gpu;
|
Tegra::GPU& gpu;
|
||||||
Tegra::MemoryManager& gpu_memory;
|
Tegra::Host1x::SyncpointManager& syncpoint_manager;
|
||||||
TTextureCache& texture_cache;
|
TTextureCache& texture_cache;
|
||||||
TTBufferCache& buffer_cache;
|
TTBufferCache& buffer_cache;
|
||||||
TQueryCache& query_cache;
|
TQueryCache& query_cache;
|
||||||
|
@ -136,11 +115,10 @@ private:
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
PopAsyncFlushes();
|
PopAsyncFlushes();
|
||||||
if (current_fence->IsSemaphore()) {
|
auto operations = std::move(pending_operations.front());
|
||||||
gpu_memory.template Write<u32>(current_fence->GetAddress(),
|
pending_operations.pop_front();
|
||||||
current_fence->GetPayload());
|
for (auto& operation : operations) {
|
||||||
} else {
|
operation();
|
||||||
gpu.IncrementSyncPoint(current_fence->GetPayload());
|
|
||||||
}
|
}
|
||||||
PopFence();
|
PopFence();
|
||||||
}
|
}
|
||||||
|
@ -159,16 +137,20 @@ private:
|
||||||
}
|
}
|
||||||
|
|
||||||
void PopAsyncFlushes() {
|
void PopAsyncFlushes() {
|
||||||
|
{
|
||||||
std::scoped_lock lock{buffer_cache.mutex, texture_cache.mutex};
|
std::scoped_lock lock{buffer_cache.mutex, texture_cache.mutex};
|
||||||
texture_cache.PopAsyncFlushes();
|
texture_cache.PopAsyncFlushes();
|
||||||
buffer_cache.PopAsyncFlushes();
|
buffer_cache.PopAsyncFlushes();
|
||||||
|
}
|
||||||
query_cache.PopAsyncFlushes();
|
query_cache.PopAsyncFlushes();
|
||||||
}
|
}
|
||||||
|
|
||||||
void CommitAsyncFlushes() {
|
void CommitAsyncFlushes() {
|
||||||
|
{
|
||||||
std::scoped_lock lock{buffer_cache.mutex, texture_cache.mutex};
|
std::scoped_lock lock{buffer_cache.mutex, texture_cache.mutex};
|
||||||
texture_cache.CommitAsyncFlushes();
|
texture_cache.CommitAsyncFlushes();
|
||||||
buffer_cache.CommitAsyncFlushes();
|
buffer_cache.CommitAsyncFlushes();
|
||||||
|
}
|
||||||
query_cache.CommitAsyncFlushes();
|
query_cache.CommitAsyncFlushes();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -177,7 +159,13 @@ private:
|
||||||
fences.pop();
|
fences.pop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CommitOperations() {
|
||||||
|
pending_operations.emplace_back(std::move(uncommitted_operations));
|
||||||
|
}
|
||||||
|
|
||||||
std::queue<TFence> fences;
|
std::queue<TFence> fences;
|
||||||
|
std::deque<std::function<void()>> uncommitted_operations;
|
||||||
|
std::deque<std::deque<std::function<void()>>> pending_operations;
|
||||||
|
|
||||||
DelayedDestructionRing<TFence, 6> delayed_destruction_ring;
|
DelayedDestructionRing<TFence, 6> delayed_destruction_ring;
|
||||||
};
|
};
|
||||||
|
|
|
@ -14,10 +14,11 @@
|
||||||
#include "core/core.h"
|
#include "core/core.h"
|
||||||
#include "core/core_timing.h"
|
#include "core/core_timing.h"
|
||||||
#include "core/frontend/emu_window.h"
|
#include "core/frontend/emu_window.h"
|
||||||
#include "core/hardware_interrupt_manager.h"
|
|
||||||
#include "core/hle/service/nvdrv/nvdata.h"
|
#include "core/hle/service/nvdrv/nvdata.h"
|
||||||
#include "core/perf_stats.h"
|
#include "core/perf_stats.h"
|
||||||
#include "video_core/cdma_pusher.h"
|
#include "video_core/cdma_pusher.h"
|
||||||
|
#include "video_core/control/channel_state.h"
|
||||||
|
#include "video_core/control/scheduler.h"
|
||||||
#include "video_core/dma_pusher.h"
|
#include "video_core/dma_pusher.h"
|
||||||
#include "video_core/engines/fermi_2d.h"
|
#include "video_core/engines/fermi_2d.h"
|
||||||
#include "video_core/engines/kepler_compute.h"
|
#include "video_core/engines/kepler_compute.h"
|
||||||
|
@ -26,75 +27,64 @@
|
||||||
#include "video_core/engines/maxwell_dma.h"
|
#include "video_core/engines/maxwell_dma.h"
|
||||||
#include "video_core/gpu.h"
|
#include "video_core/gpu.h"
|
||||||
#include "video_core/gpu_thread.h"
|
#include "video_core/gpu_thread.h"
|
||||||
|
#include "video_core/host1x/host1x.h"
|
||||||
|
#include "video_core/host1x/syncpoint_manager.h"
|
||||||
#include "video_core/memory_manager.h"
|
#include "video_core/memory_manager.h"
|
||||||
#include "video_core/renderer_base.h"
|
#include "video_core/renderer_base.h"
|
||||||
#include "video_core/shader_notify.h"
|
#include "video_core/shader_notify.h"
|
||||||
|
|
||||||
namespace Tegra {
|
namespace Tegra {
|
||||||
|
|
||||||
MICROPROFILE_DEFINE(GPU_wait, "GPU", "Wait for the GPU", MP_RGB(128, 128, 192));
|
|
||||||
|
|
||||||
struct GPU::Impl {
|
struct GPU::Impl {
|
||||||
explicit Impl(GPU& gpu_, Core::System& system_, bool is_async_, bool use_nvdec_)
|
explicit Impl(GPU& gpu_, Core::System& system_, bool is_async_, bool use_nvdec_)
|
||||||
: gpu{gpu_}, system{system_}, memory_manager{std::make_unique<Tegra::MemoryManager>(
|
: gpu{gpu_}, system{system_}, host1x{system.Host1x()}, use_nvdec{use_nvdec_},
|
||||||
system)},
|
|
||||||
dma_pusher{std::make_unique<Tegra::DmaPusher>(system, gpu)}, use_nvdec{use_nvdec_},
|
|
||||||
maxwell_3d{std::make_unique<Engines::Maxwell3D>(system, *memory_manager)},
|
|
||||||
fermi_2d{std::make_unique<Engines::Fermi2D>()},
|
|
||||||
kepler_compute{std::make_unique<Engines::KeplerCompute>(system, *memory_manager)},
|
|
||||||
maxwell_dma{std::make_unique<Engines::MaxwellDMA>(system, *memory_manager)},
|
|
||||||
kepler_memory{std::make_unique<Engines::KeplerMemory>(system, *memory_manager)},
|
|
||||||
shader_notify{std::make_unique<VideoCore::ShaderNotify>()}, is_async{is_async_},
|
shader_notify{std::make_unique<VideoCore::ShaderNotify>()}, is_async{is_async_},
|
||||||
gpu_thread{system_, is_async_} {}
|
gpu_thread{system_, is_async_}, scheduler{std::make_unique<Control::Scheduler>(gpu)} {}
|
||||||
|
|
||||||
~Impl() = default;
|
~Impl() = default;
|
||||||
|
|
||||||
|
std::shared_ptr<Control::ChannelState> CreateChannel(s32 channel_id) {
|
||||||
|
auto channel_state = std::make_shared<Tegra::Control::ChannelState>(channel_id);
|
||||||
|
channels.emplace(channel_id, channel_state);
|
||||||
|
scheduler->DeclareChannel(channel_state);
|
||||||
|
return channel_state;
|
||||||
|
}
|
||||||
|
|
||||||
|
void BindChannel(s32 channel_id) {
|
||||||
|
if (bound_channel == channel_id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto it = channels.find(channel_id);
|
||||||
|
ASSERT(it != channels.end());
|
||||||
|
bound_channel = channel_id;
|
||||||
|
current_channel = it->second.get();
|
||||||
|
|
||||||
|
rasterizer->BindChannel(*current_channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<Control::ChannelState> AllocateChannel() {
|
||||||
|
return CreateChannel(new_channel_id++);
|
||||||
|
}
|
||||||
|
|
||||||
|
void InitChannel(Control::ChannelState& to_init) {
|
||||||
|
to_init.Init(system, gpu);
|
||||||
|
to_init.BindRasterizer(rasterizer);
|
||||||
|
rasterizer->InitializeChannel(to_init);
|
||||||
|
}
|
||||||
|
|
||||||
|
void InitAddressSpace(Tegra::MemoryManager& memory_manager) {
|
||||||
|
memory_manager.BindRasterizer(rasterizer);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ReleaseChannel(Control::ChannelState& to_release) {
|
||||||
|
UNIMPLEMENTED();
|
||||||
|
}
|
||||||
|
|
||||||
/// Binds a renderer to the GPU.
|
/// Binds a renderer to the GPU.
|
||||||
void BindRenderer(std::unique_ptr<VideoCore::RendererBase> renderer_) {
|
void BindRenderer(std::unique_ptr<VideoCore::RendererBase> renderer_) {
|
||||||
renderer = std::move(renderer_);
|
renderer = std::move(renderer_);
|
||||||
rasterizer = renderer->ReadRasterizer();
|
rasterizer = renderer->ReadRasterizer();
|
||||||
|
host1x.MemoryManager().BindRasterizer(rasterizer);
|
||||||
memory_manager->BindRasterizer(rasterizer);
|
|
||||||
maxwell_3d->BindRasterizer(rasterizer);
|
|
||||||
fermi_2d->BindRasterizer(rasterizer);
|
|
||||||
kepler_compute->BindRasterizer(rasterizer);
|
|
||||||
kepler_memory->BindRasterizer(rasterizer);
|
|
||||||
maxwell_dma->BindRasterizer(rasterizer);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Calls a GPU method.
|
|
||||||
void CallMethod(const GPU::MethodCall& method_call) {
|
|
||||||
LOG_TRACE(HW_GPU, "Processing method {:08X} on subchannel {}", method_call.method,
|
|
||||||
method_call.subchannel);
|
|
||||||
|
|
||||||
ASSERT(method_call.subchannel < bound_engines.size());
|
|
||||||
|
|
||||||
if (ExecuteMethodOnEngine(method_call.method)) {
|
|
||||||
CallEngineMethod(method_call);
|
|
||||||
} else {
|
|
||||||
CallPullerMethod(method_call);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Calls a GPU multivalue method.
|
|
||||||
void CallMultiMethod(u32 method, u32 subchannel, const u32* base_start, u32 amount,
|
|
||||||
u32 methods_pending) {
|
|
||||||
LOG_TRACE(HW_GPU, "Processing method {:08X} on subchannel {}", method, subchannel);
|
|
||||||
|
|
||||||
ASSERT(subchannel < bound_engines.size());
|
|
||||||
|
|
||||||
if (ExecuteMethodOnEngine(method)) {
|
|
||||||
CallEngineMultiMethod(method, subchannel, base_start, amount, methods_pending);
|
|
||||||
} else {
|
|
||||||
for (std::size_t i = 0; i < amount; i++) {
|
|
||||||
CallPullerMethod(GPU::MethodCall{
|
|
||||||
method,
|
|
||||||
base_start[i],
|
|
||||||
subchannel,
|
|
||||||
methods_pending - static_cast<u32>(i),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Flush all current written commands into the host GPU for execution.
|
/// Flush all current written commands into the host GPU for execution.
|
||||||
|
@ -103,85 +93,82 @@ struct GPU::Impl {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Synchronizes CPU writes with Host GPU memory.
|
/// Synchronizes CPU writes with Host GPU memory.
|
||||||
void SyncGuestHost() {
|
void InvalidateGPUCache() {
|
||||||
rasterizer->SyncGuestHost();
|
rasterizer->InvalidateGPUCache();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Signal the ending of command list.
|
/// Signal the ending of command list.
|
||||||
void OnCommandListEnd() {
|
void OnCommandListEnd() {
|
||||||
if (is_async) {
|
|
||||||
// This command only applies to asynchronous GPU mode
|
|
||||||
gpu_thread.OnCommandListEnd();
|
gpu_thread.OnCommandListEnd();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/// Request a host GPU memory flush from the CPU.
|
/// Request a host GPU memory flush from the CPU.
|
||||||
[[nodiscard]] u64 RequestFlush(VAddr addr, std::size_t size) {
|
template <typename Func>
|
||||||
std::unique_lock lck{flush_request_mutex};
|
[[nodiscard]] u64 RequestSyncOperation(Func&& action) {
|
||||||
const u64 fence = ++last_flush_fence;
|
std::unique_lock lck{sync_request_mutex};
|
||||||
flush_requests.emplace_back(fence, addr, size);
|
const u64 fence = ++last_sync_fence;
|
||||||
|
sync_requests.emplace_back(action);
|
||||||
return fence;
|
return fence;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Obtains current flush request fence id.
|
/// Obtains current flush request fence id.
|
||||||
[[nodiscard]] u64 CurrentFlushRequestFence() const {
|
[[nodiscard]] u64 CurrentSyncRequestFence() const {
|
||||||
return current_flush_fence.load(std::memory_order_relaxed);
|
return current_sync_fence.load(std::memory_order_relaxed);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WaitForSyncOperation(const u64 fence) {
|
||||||
|
std::unique_lock lck{sync_request_mutex};
|
||||||
|
sync_request_cv.wait(lck, [this, fence] { return CurrentSyncRequestFence() >= fence; });
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Tick pending requests within the GPU.
|
/// Tick pending requests within the GPU.
|
||||||
void TickWork() {
|
void TickWork() {
|
||||||
std::unique_lock lck{flush_request_mutex};
|
std::unique_lock lck{sync_request_mutex};
|
||||||
while (!flush_requests.empty()) {
|
while (!sync_requests.empty()) {
|
||||||
auto& request = flush_requests.front();
|
auto request = std::move(sync_requests.front());
|
||||||
const u64 fence = request.fence;
|
sync_requests.pop_front();
|
||||||
const VAddr addr = request.addr;
|
sync_request_mutex.unlock();
|
||||||
const std::size_t size = request.size;
|
request();
|
||||||
flush_requests.pop_front();
|
current_sync_fence.fetch_add(1, std::memory_order_release);
|
||||||
flush_request_mutex.unlock();
|
sync_request_mutex.lock();
|
||||||
rasterizer->FlushRegion(addr, size);
|
sync_request_cv.notify_all();
|
||||||
current_flush_fence.store(fence);
|
|
||||||
flush_request_mutex.lock();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a reference to the Maxwell3D GPU engine.
|
/// Returns a reference to the Maxwell3D GPU engine.
|
||||||
[[nodiscard]] Engines::Maxwell3D& Maxwell3D() {
|
[[nodiscard]] Engines::Maxwell3D& Maxwell3D() {
|
||||||
return *maxwell_3d;
|
ASSERT(current_channel);
|
||||||
|
return *current_channel->maxwell_3d;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a const reference to the Maxwell3D GPU engine.
|
/// Returns a const reference to the Maxwell3D GPU engine.
|
||||||
[[nodiscard]] const Engines::Maxwell3D& Maxwell3D() const {
|
[[nodiscard]] const Engines::Maxwell3D& Maxwell3D() const {
|
||||||
return *maxwell_3d;
|
ASSERT(current_channel);
|
||||||
|
return *current_channel->maxwell_3d;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a reference to the KeplerCompute GPU engine.
|
/// Returns a reference to the KeplerCompute GPU engine.
|
||||||
[[nodiscard]] Engines::KeplerCompute& KeplerCompute() {
|
[[nodiscard]] Engines::KeplerCompute& KeplerCompute() {
|
||||||
return *kepler_compute;
|
ASSERT(current_channel);
|
||||||
|
return *current_channel->kepler_compute;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a reference to the KeplerCompute GPU engine.
|
/// Returns a reference to the KeplerCompute GPU engine.
|
||||||
[[nodiscard]] const Engines::KeplerCompute& KeplerCompute() const {
|
[[nodiscard]] const Engines::KeplerCompute& KeplerCompute() const {
|
||||||
return *kepler_compute;
|
ASSERT(current_channel);
|
||||||
}
|
return *current_channel->kepler_compute;
|
||||||
|
|
||||||
/// Returns a reference to the GPU memory manager.
|
|
||||||
[[nodiscard]] Tegra::MemoryManager& MemoryManager() {
|
|
||||||
return *memory_manager;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns a const reference to the GPU memory manager.
|
|
||||||
[[nodiscard]] const Tegra::MemoryManager& MemoryManager() const {
|
|
||||||
return *memory_manager;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a reference to the GPU DMA pusher.
|
/// Returns a reference to the GPU DMA pusher.
|
||||||
[[nodiscard]] Tegra::DmaPusher& DmaPusher() {
|
[[nodiscard]] Tegra::DmaPusher& DmaPusher() {
|
||||||
return *dma_pusher;
|
ASSERT(current_channel);
|
||||||
|
return *current_channel->dma_pusher;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a const reference to the GPU DMA pusher.
|
/// Returns a const reference to the GPU DMA pusher.
|
||||||
[[nodiscard]] const Tegra::DmaPusher& DmaPusher() const {
|
[[nodiscard]] const Tegra::DmaPusher& DmaPusher() const {
|
||||||
return *dma_pusher;
|
ASSERT(current_channel);
|
||||||
|
return *current_channel->dma_pusher;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a reference to the underlying renderer.
|
/// Returns a reference to the underlying renderer.
|
||||||
|
@ -204,77 +191,6 @@ struct GPU::Impl {
|
||||||
return *shader_notify;
|
return *shader_notify;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Allows the CPU/NvFlinger to wait on the GPU before presenting a frame.
|
|
||||||
void WaitFence(u32 syncpoint_id, u32 value) {
|
|
||||||
// Synced GPU, is always in sync
|
|
||||||
if (!is_async) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (syncpoint_id == UINT32_MAX) {
|
|
||||||
// TODO: Research what this does.
|
|
||||||
LOG_ERROR(HW_GPU, "Waiting for syncpoint -1 not implemented");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
MICROPROFILE_SCOPE(GPU_wait);
|
|
||||||
std::unique_lock lock{sync_mutex};
|
|
||||||
sync_cv.wait(lock, [=, this] {
|
|
||||||
if (shutting_down.load(std::memory_order_relaxed)) {
|
|
||||||
// We're shutting down, ensure no threads continue to wait for the next syncpoint
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return syncpoints.at(syncpoint_id).load() >= value;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void IncrementSyncPoint(u32 syncpoint_id) {
|
|
||||||
auto& syncpoint = syncpoints.at(syncpoint_id);
|
|
||||||
syncpoint++;
|
|
||||||
std::scoped_lock lock{sync_mutex};
|
|
||||||
sync_cv.notify_all();
|
|
||||||
auto& interrupt = syncpt_interrupts.at(syncpoint_id);
|
|
||||||
if (!interrupt.empty()) {
|
|
||||||
u32 value = syncpoint.load();
|
|
||||||
auto it = interrupt.begin();
|
|
||||||
while (it != interrupt.end()) {
|
|
||||||
if (value >= *it) {
|
|
||||||
TriggerCpuInterrupt(syncpoint_id, *it);
|
|
||||||
it = interrupt.erase(it);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
it++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]] u32 GetSyncpointValue(u32 syncpoint_id) const {
|
|
||||||
return syncpoints.at(syncpoint_id).load();
|
|
||||||
}
|
|
||||||
|
|
||||||
void RegisterSyncptInterrupt(u32 syncpoint_id, u32 value) {
|
|
||||||
std::scoped_lock lock{sync_mutex};
|
|
||||||
auto& interrupt = syncpt_interrupts.at(syncpoint_id);
|
|
||||||
bool contains = std::any_of(interrupt.begin(), interrupt.end(),
|
|
||||||
[value](u32 in_value) { return in_value == value; });
|
|
||||||
if (contains) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
interrupt.emplace_back(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]] bool CancelSyncptInterrupt(u32 syncpoint_id, u32 value) {
|
|
||||||
std::scoped_lock lock{sync_mutex};
|
|
||||||
auto& interrupt = syncpt_interrupts.at(syncpoint_id);
|
|
||||||
const auto iter =
|
|
||||||
std::find_if(interrupt.begin(), interrupt.end(),
|
|
||||||
[value](u32 interrupt_value) { return value == interrupt_value; });
|
|
||||||
|
|
||||||
if (iter == interrupt.end()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
interrupt.erase(iter);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]] u64 GetTicks() const {
|
[[nodiscard]] u64 GetTicks() const {
|
||||||
// This values were reversed engineered by fincs from NVN
|
// This values were reversed engineered by fincs from NVN
|
||||||
// The gpu clock is reported in units of 385/625 nanoseconds
|
// The gpu clock is reported in units of 385/625 nanoseconds
|
||||||
|
@ -306,7 +222,7 @@ struct GPU::Impl {
|
||||||
/// This can be used to launch any necessary threads and register any necessary
|
/// This can be used to launch any necessary threads and register any necessary
|
||||||
/// core timing events.
|
/// core timing events.
|
||||||
void Start() {
|
void Start() {
|
||||||
gpu_thread.StartThread(*renderer, renderer->Context(), *dma_pusher);
|
gpu_thread.StartThread(*renderer, renderer->Context(), *scheduler);
|
||||||
cpu_context = renderer->GetRenderWindow().CreateSharedContext();
|
cpu_context = renderer->GetRenderWindow().CreateSharedContext();
|
||||||
cpu_context->MakeCurrent();
|
cpu_context->MakeCurrent();
|
||||||
}
|
}
|
||||||
|
@ -328,8 +244,8 @@ struct GPU::Impl {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Push GPU command entries to be processed
|
/// Push GPU command entries to be processed
|
||||||
void PushGPUEntries(Tegra::CommandList&& entries) {
|
void PushGPUEntries(s32 channel, Tegra::CommandList&& entries) {
|
||||||
gpu_thread.SubmitList(std::move(entries));
|
gpu_thread.SubmitList(channel, std::move(entries));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Push GPU command buffer entries to be processed
|
/// Push GPU command buffer entries to be processed
|
||||||
|
@ -339,7 +255,7 @@ struct GPU::Impl {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!cdma_pushers.contains(id)) {
|
if (!cdma_pushers.contains(id)) {
|
||||||
cdma_pushers.insert_or_assign(id, std::make_unique<Tegra::CDmaPusher>(gpu));
|
cdma_pushers.insert_or_assign(id, std::make_unique<Tegra::CDmaPusher>(host1x));
|
||||||
}
|
}
|
||||||
|
|
||||||
// SubmitCommandBuffer would make the nvdec operations async, this is not currently working
|
// SubmitCommandBuffer would make the nvdec operations async, this is not currently working
|
||||||
|
@ -376,308 +292,55 @@ struct GPU::Impl {
|
||||||
gpu_thread.FlushAndInvalidateRegion(addr, size);
|
gpu_thread.FlushAndInvalidateRegion(addr, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TriggerCpuInterrupt(u32 syncpoint_id, u32 value) const {
|
void RequestSwapBuffers(const Tegra::FramebufferConfig* framebuffer,
|
||||||
auto& interrupt_manager = system.InterruptManager();
|
std::array<Service::Nvidia::NvFence, 4>& fences, size_t num_fences) {
|
||||||
interrupt_manager.GPUInterruptSyncpt(syncpoint_id, value);
|
size_t current_request_counter{};
|
||||||
}
|
{
|
||||||
|
std::unique_lock<std::mutex> lk(request_swap_mutex);
|
||||||
void ProcessBindMethod(const GPU::MethodCall& method_call) {
|
if (free_swap_counters.empty()) {
|
||||||
// Bind the current subchannel to the desired engine id.
|
current_request_counter = request_swap_counters.size();
|
||||||
LOG_DEBUG(HW_GPU, "Binding subchannel {} to engine {}", method_call.subchannel,
|
request_swap_counters.emplace_back(num_fences);
|
||||||
method_call.argument);
|
|
||||||
const auto engine_id = static_cast<EngineID>(method_call.argument);
|
|
||||||
bound_engines[method_call.subchannel] = static_cast<EngineID>(engine_id);
|
|
||||||
switch (engine_id) {
|
|
||||||
case EngineID::FERMI_TWOD_A:
|
|
||||||
dma_pusher->BindSubchannel(fermi_2d.get(), method_call.subchannel);
|
|
||||||
break;
|
|
||||||
case EngineID::MAXWELL_B:
|
|
||||||
dma_pusher->BindSubchannel(maxwell_3d.get(), method_call.subchannel);
|
|
||||||
break;
|
|
||||||
case EngineID::KEPLER_COMPUTE_B:
|
|
||||||
dma_pusher->BindSubchannel(kepler_compute.get(), method_call.subchannel);
|
|
||||||
break;
|
|
||||||
case EngineID::MAXWELL_DMA_COPY_A:
|
|
||||||
dma_pusher->BindSubchannel(maxwell_dma.get(), method_call.subchannel);
|
|
||||||
break;
|
|
||||||
case EngineID::KEPLER_INLINE_TO_MEMORY_B:
|
|
||||||
dma_pusher->BindSubchannel(kepler_memory.get(), method_call.subchannel);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
UNIMPLEMENTED_MSG("Unimplemented engine {:04X}", engine_id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ProcessFenceActionMethod() {
|
|
||||||
switch (regs.fence_action.op) {
|
|
||||||
case GPU::FenceOperation::Acquire:
|
|
||||||
WaitFence(regs.fence_action.syncpoint_id, regs.fence_value);
|
|
||||||
break;
|
|
||||||
case GPU::FenceOperation::Increment:
|
|
||||||
IncrementSyncPoint(regs.fence_action.syncpoint_id);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
UNIMPLEMENTED_MSG("Unimplemented operation {}", regs.fence_action.op.Value());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ProcessWaitForInterruptMethod() {
|
|
||||||
// TODO(bunnei) ImplementMe
|
|
||||||
LOG_WARNING(HW_GPU, "(STUBBED) called");
|
|
||||||
}
|
|
||||||
|
|
||||||
void ProcessSemaphoreTriggerMethod() {
|
|
||||||
const auto semaphoreOperationMask = 0xF;
|
|
||||||
const auto op =
|
|
||||||
static_cast<GpuSemaphoreOperation>(regs.semaphore_trigger & semaphoreOperationMask);
|
|
||||||
if (op == GpuSemaphoreOperation::WriteLong) {
|
|
||||||
struct Block {
|
|
||||||
u32 sequence;
|
|
||||||
u32 zeros = 0;
|
|
||||||
u64 timestamp;
|
|
||||||
};
|
|
||||||
|
|
||||||
Block block{};
|
|
||||||
block.sequence = regs.semaphore_sequence;
|
|
||||||
// TODO(Kmather73): Generate a real GPU timestamp and write it here instead of
|
|
||||||
// CoreTiming
|
|
||||||
block.timestamp = GetTicks();
|
|
||||||
memory_manager->WriteBlock(regs.semaphore_address.SemaphoreAddress(), &block,
|
|
||||||
sizeof(block));
|
|
||||||
} else {
|
} else {
|
||||||
const u32 word{memory_manager->Read<u32>(regs.semaphore_address.SemaphoreAddress())};
|
current_request_counter = free_swap_counters.front();
|
||||||
if ((op == GpuSemaphoreOperation::AcquireEqual && word == regs.semaphore_sequence) ||
|
request_swap_counters[current_request_counter] = num_fences;
|
||||||
(op == GpuSemaphoreOperation::AcquireGequal &&
|
free_swap_counters.pop_front();
|
||||||
static_cast<s32>(word - regs.semaphore_sequence) > 0) ||
|
|
||||||
(op == GpuSemaphoreOperation::AcquireMask && (word & regs.semaphore_sequence))) {
|
|
||||||
// Nothing to do in this case
|
|
||||||
} else {
|
|
||||||
regs.acquire_source = true;
|
|
||||||
regs.acquire_value = regs.semaphore_sequence;
|
|
||||||
if (op == GpuSemaphoreOperation::AcquireEqual) {
|
|
||||||
regs.acquire_active = true;
|
|
||||||
regs.acquire_mode = false;
|
|
||||||
} else if (op == GpuSemaphoreOperation::AcquireGequal) {
|
|
||||||
regs.acquire_active = true;
|
|
||||||
regs.acquire_mode = true;
|
|
||||||
} else if (op == GpuSemaphoreOperation::AcquireMask) {
|
|
||||||
// TODO(kemathe) The acquire mask operation waits for a value that, ANDed with
|
|
||||||
// semaphore_sequence, gives a non-0 result
|
|
||||||
LOG_ERROR(HW_GPU, "Invalid semaphore operation AcquireMask not implemented");
|
|
||||||
} else {
|
|
||||||
LOG_ERROR(HW_GPU, "Invalid semaphore operation");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
const auto wait_fence =
|
||||||
|
RequestSyncOperation([this, current_request_counter, framebuffer, fences, num_fences] {
|
||||||
|
auto& syncpoint_manager = host1x.GetSyncpointManager();
|
||||||
|
if (num_fences == 0) {
|
||||||
|
renderer->SwapBuffers(framebuffer);
|
||||||
}
|
}
|
||||||
|
const auto executer = [this, current_request_counter,
|
||||||
|
framebuffer_copy = *framebuffer]() {
|
||||||
|
{
|
||||||
|
std::unique_lock<std::mutex> lk(request_swap_mutex);
|
||||||
|
if (--request_swap_counters[current_request_counter] != 0) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
free_swap_counters.push_back(current_request_counter);
|
||||||
void ProcessSemaphoreRelease() {
|
|
||||||
memory_manager->Write<u32>(regs.semaphore_address.SemaphoreAddress(),
|
|
||||||
regs.semaphore_release);
|
|
||||||
}
|
}
|
||||||
|
renderer->SwapBuffers(&framebuffer_copy);
|
||||||
void ProcessSemaphoreAcquire() {
|
|
||||||
const u32 word = memory_manager->Read<u32>(regs.semaphore_address.SemaphoreAddress());
|
|
||||||
const auto value = regs.semaphore_acquire;
|
|
||||||
if (word != value) {
|
|
||||||
regs.acquire_active = true;
|
|
||||||
regs.acquire_value = value;
|
|
||||||
// TODO(kemathe73) figure out how to do the acquire_timeout
|
|
||||||
regs.acquire_mode = false;
|
|
||||||
regs.acquire_source = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Calls a GPU puller method.
|
|
||||||
void CallPullerMethod(const GPU::MethodCall& method_call) {
|
|
||||||
regs.reg_array[method_call.method] = method_call.argument;
|
|
||||||
const auto method = static_cast<BufferMethods>(method_call.method);
|
|
||||||
|
|
||||||
switch (method) {
|
|
||||||
case BufferMethods::BindObject: {
|
|
||||||
ProcessBindMethod(method_call);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case BufferMethods::Nop:
|
|
||||||
case BufferMethods::SemaphoreAddressHigh:
|
|
||||||
case BufferMethods::SemaphoreAddressLow:
|
|
||||||
case BufferMethods::SemaphoreSequence:
|
|
||||||
break;
|
|
||||||
case BufferMethods::UnkCacheFlush:
|
|
||||||
rasterizer->SyncGuestHost();
|
|
||||||
break;
|
|
||||||
case BufferMethods::WrcacheFlush:
|
|
||||||
rasterizer->SignalReference();
|
|
||||||
break;
|
|
||||||
case BufferMethods::FenceValue:
|
|
||||||
break;
|
|
||||||
case BufferMethods::RefCnt:
|
|
||||||
rasterizer->SignalReference();
|
|
||||||
break;
|
|
||||||
case BufferMethods::FenceAction:
|
|
||||||
ProcessFenceActionMethod();
|
|
||||||
break;
|
|
||||||
case BufferMethods::WaitForInterrupt:
|
|
||||||
rasterizer->WaitForIdle();
|
|
||||||
break;
|
|
||||||
case BufferMethods::SemaphoreTrigger: {
|
|
||||||
ProcessSemaphoreTriggerMethod();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case BufferMethods::NotifyIntr: {
|
|
||||||
// TODO(Kmather73): Research and implement this method.
|
|
||||||
LOG_ERROR(HW_GPU, "Special puller engine method NotifyIntr not implemented");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case BufferMethods::Unk28: {
|
|
||||||
// TODO(Kmather73): Research and implement this method.
|
|
||||||
LOG_ERROR(HW_GPU, "Special puller engine method Unk28 not implemented");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case BufferMethods::SemaphoreAcquire: {
|
|
||||||
ProcessSemaphoreAcquire();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case BufferMethods::SemaphoreRelease: {
|
|
||||||
ProcessSemaphoreRelease();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case BufferMethods::Yield: {
|
|
||||||
// TODO(Kmather73): Research and implement this method.
|
|
||||||
LOG_ERROR(HW_GPU, "Special puller engine method Yield not implemented");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
LOG_ERROR(HW_GPU, "Special puller engine method {:X} not implemented", method);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Calls a GPU engine method.
|
|
||||||
void CallEngineMethod(const GPU::MethodCall& method_call) {
|
|
||||||
const EngineID engine = bound_engines[method_call.subchannel];
|
|
||||||
|
|
||||||
switch (engine) {
|
|
||||||
case EngineID::FERMI_TWOD_A:
|
|
||||||
fermi_2d->CallMethod(method_call.method, method_call.argument,
|
|
||||||
method_call.IsLastCall());
|
|
||||||
break;
|
|
||||||
case EngineID::MAXWELL_B:
|
|
||||||
maxwell_3d->CallMethod(method_call.method, method_call.argument,
|
|
||||||
method_call.IsLastCall());
|
|
||||||
break;
|
|
||||||
case EngineID::KEPLER_COMPUTE_B:
|
|
||||||
kepler_compute->CallMethod(method_call.method, method_call.argument,
|
|
||||||
method_call.IsLastCall());
|
|
||||||
break;
|
|
||||||
case EngineID::MAXWELL_DMA_COPY_A:
|
|
||||||
maxwell_dma->CallMethod(method_call.method, method_call.argument,
|
|
||||||
method_call.IsLastCall());
|
|
||||||
break;
|
|
||||||
case EngineID::KEPLER_INLINE_TO_MEMORY_B:
|
|
||||||
kepler_memory->CallMethod(method_call.method, method_call.argument,
|
|
||||||
method_call.IsLastCall());
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
UNIMPLEMENTED_MSG("Unimplemented engine");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Calls a GPU engine multivalue method.
|
|
||||||
void CallEngineMultiMethod(u32 method, u32 subchannel, const u32* base_start, u32 amount,
|
|
||||||
u32 methods_pending) {
|
|
||||||
const EngineID engine = bound_engines[subchannel];
|
|
||||||
|
|
||||||
switch (engine) {
|
|
||||||
case EngineID::FERMI_TWOD_A:
|
|
||||||
fermi_2d->CallMultiMethod(method, base_start, amount, methods_pending);
|
|
||||||
break;
|
|
||||||
case EngineID::MAXWELL_B:
|
|
||||||
maxwell_3d->CallMultiMethod(method, base_start, amount, methods_pending);
|
|
||||||
break;
|
|
||||||
case EngineID::KEPLER_COMPUTE_B:
|
|
||||||
kepler_compute->CallMultiMethod(method, base_start, amount, methods_pending);
|
|
||||||
break;
|
|
||||||
case EngineID::MAXWELL_DMA_COPY_A:
|
|
||||||
maxwell_dma->CallMultiMethod(method, base_start, amount, methods_pending);
|
|
||||||
break;
|
|
||||||
case EngineID::KEPLER_INLINE_TO_MEMORY_B:
|
|
||||||
kepler_memory->CallMultiMethod(method, base_start, amount, methods_pending);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
UNIMPLEMENTED_MSG("Unimplemented engine");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Determines where the method should be executed.
|
|
||||||
[[nodiscard]] bool ExecuteMethodOnEngine(u32 method) {
|
|
||||||
const auto buffer_method = static_cast<BufferMethods>(method);
|
|
||||||
return buffer_method >= BufferMethods::NonPullerMethods;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Regs {
|
|
||||||
static constexpr size_t NUM_REGS = 0x40;
|
|
||||||
|
|
||||||
union {
|
|
||||||
struct {
|
|
||||||
INSERT_PADDING_WORDS_NOINIT(0x4);
|
|
||||||
struct {
|
|
||||||
u32 address_high;
|
|
||||||
u32 address_low;
|
|
||||||
|
|
||||||
[[nodiscard]] GPUVAddr SemaphoreAddress() const {
|
|
||||||
return static_cast<GPUVAddr>((static_cast<GPUVAddr>(address_high) << 32) |
|
|
||||||
address_low);
|
|
||||||
}
|
|
||||||
} semaphore_address;
|
|
||||||
|
|
||||||
u32 semaphore_sequence;
|
|
||||||
u32 semaphore_trigger;
|
|
||||||
INSERT_PADDING_WORDS_NOINIT(0xC);
|
|
||||||
|
|
||||||
// The pusher and the puller share the reference counter, the pusher only has read
|
|
||||||
// access
|
|
||||||
u32 reference_count;
|
|
||||||
INSERT_PADDING_WORDS_NOINIT(0x5);
|
|
||||||
|
|
||||||
u32 semaphore_acquire;
|
|
||||||
u32 semaphore_release;
|
|
||||||
u32 fence_value;
|
|
||||||
GPU::FenceAction fence_action;
|
|
||||||
INSERT_PADDING_WORDS_NOINIT(0xE2);
|
|
||||||
|
|
||||||
// Puller state
|
|
||||||
u32 acquire_mode;
|
|
||||||
u32 acquire_source;
|
|
||||||
u32 acquire_active;
|
|
||||||
u32 acquire_timeout;
|
|
||||||
u32 acquire_value;
|
|
||||||
};
|
};
|
||||||
std::array<u32, NUM_REGS> reg_array;
|
for (size_t i = 0; i < num_fences; i++) {
|
||||||
};
|
syncpoint_manager.RegisterGuestAction(fences[i].id, fences[i].value, executer);
|
||||||
} regs{};
|
}
|
||||||
|
});
|
||||||
|
gpu_thread.TickGPU();
|
||||||
|
WaitForSyncOperation(wait_fence);
|
||||||
|
}
|
||||||
|
|
||||||
GPU& gpu;
|
GPU& gpu;
|
||||||
Core::System& system;
|
Core::System& system;
|
||||||
std::unique_ptr<Tegra::MemoryManager> memory_manager;
|
Host1x::Host1x& host1x;
|
||||||
std::unique_ptr<Tegra::DmaPusher> dma_pusher;
|
|
||||||
std::map<u32, std::unique_ptr<Tegra::CDmaPusher>> cdma_pushers;
|
std::map<u32, std::unique_ptr<Tegra::CDmaPusher>> cdma_pushers;
|
||||||
std::unique_ptr<VideoCore::RendererBase> renderer;
|
std::unique_ptr<VideoCore::RendererBase> renderer;
|
||||||
VideoCore::RasterizerInterface* rasterizer = nullptr;
|
VideoCore::RasterizerInterface* rasterizer = nullptr;
|
||||||
const bool use_nvdec;
|
const bool use_nvdec;
|
||||||
|
|
||||||
/// Mapping of command subchannels to their bound engine ids
|
s32 new_channel_id{1};
|
||||||
std::array<EngineID, 8> bound_engines{};
|
|
||||||
/// 3D engine
|
|
||||||
std::unique_ptr<Engines::Maxwell3D> maxwell_3d;
|
|
||||||
/// 2D engine
|
|
||||||
std::unique_ptr<Engines::Fermi2D> fermi_2d;
|
|
||||||
/// Compute engine
|
|
||||||
std::unique_ptr<Engines::KeplerCompute> kepler_compute;
|
|
||||||
/// DMA engine
|
|
||||||
std::unique_ptr<Engines::MaxwellDMA> maxwell_dma;
|
|
||||||
/// Inline memory engine
|
|
||||||
std::unique_ptr<Engines::KeplerMemory> kepler_memory;
|
|
||||||
/// Shader build notifier
|
/// Shader build notifier
|
||||||
std::unique_ptr<VideoCore::ShaderNotify> shader_notify;
|
std::unique_ptr<VideoCore::ShaderNotify> shader_notify;
|
||||||
/// When true, we are about to shut down emulation session, so terminate outstanding tasks
|
/// When true, we are about to shut down emulation session, so terminate outstanding tasks
|
||||||
|
@ -692,51 +355,25 @@ struct GPU::Impl {
|
||||||
|
|
||||||
std::condition_variable sync_cv;
|
std::condition_variable sync_cv;
|
||||||
|
|
||||||
struct FlushRequest {
|
std::list<std::function<void()>> sync_requests;
|
||||||
explicit FlushRequest(u64 fence_, VAddr addr_, std::size_t size_)
|
std::atomic<u64> current_sync_fence{};
|
||||||
: fence{fence_}, addr{addr_}, size{size_} {}
|
u64 last_sync_fence{};
|
||||||
u64 fence;
|
std::mutex sync_request_mutex;
|
||||||
VAddr addr;
|
std::condition_variable sync_request_cv;
|
||||||
std::size_t size;
|
|
||||||
};
|
|
||||||
|
|
||||||
std::list<FlushRequest> flush_requests;
|
|
||||||
std::atomic<u64> current_flush_fence{};
|
|
||||||
u64 last_flush_fence{};
|
|
||||||
std::mutex flush_request_mutex;
|
|
||||||
|
|
||||||
const bool is_async;
|
const bool is_async;
|
||||||
|
|
||||||
VideoCommon::GPUThread::ThreadManager gpu_thread;
|
VideoCommon::GPUThread::ThreadManager gpu_thread;
|
||||||
std::unique_ptr<Core::Frontend::GraphicsContext> cpu_context;
|
std::unique_ptr<Core::Frontend::GraphicsContext> cpu_context;
|
||||||
|
|
||||||
#define ASSERT_REG_POSITION(field_name, position) \
|
std::unique_ptr<Tegra::Control::Scheduler> scheduler;
|
||||||
static_assert(offsetof(Regs, field_name) == position * 4, \
|
std::unordered_map<s32, std::shared_ptr<Tegra::Control::ChannelState>> channels;
|
||||||
"Field " #field_name " has invalid position")
|
Tegra::Control::ChannelState* current_channel;
|
||||||
|
s32 bound_channel{-1};
|
||||||
|
|
||||||
ASSERT_REG_POSITION(semaphore_address, 0x4);
|
std::deque<size_t> free_swap_counters;
|
||||||
ASSERT_REG_POSITION(semaphore_sequence, 0x6);
|
std::deque<size_t> request_swap_counters;
|
||||||
ASSERT_REG_POSITION(semaphore_trigger, 0x7);
|
std::mutex request_swap_mutex;
|
||||||
ASSERT_REG_POSITION(reference_count, 0x14);
|
|
||||||
ASSERT_REG_POSITION(semaphore_acquire, 0x1A);
|
|
||||||
ASSERT_REG_POSITION(semaphore_release, 0x1B);
|
|
||||||
ASSERT_REG_POSITION(fence_value, 0x1C);
|
|
||||||
ASSERT_REG_POSITION(fence_action, 0x1D);
|
|
||||||
|
|
||||||
ASSERT_REG_POSITION(acquire_mode, 0x100);
|
|
||||||
ASSERT_REG_POSITION(acquire_source, 0x101);
|
|
||||||
ASSERT_REG_POSITION(acquire_active, 0x102);
|
|
||||||
ASSERT_REG_POSITION(acquire_timeout, 0x103);
|
|
||||||
ASSERT_REG_POSITION(acquire_value, 0x104);
|
|
||||||
|
|
||||||
#undef ASSERT_REG_POSITION
|
|
||||||
|
|
||||||
enum class GpuSemaphoreOperation {
|
|
||||||
AcquireEqual = 0x1,
|
|
||||||
WriteLong = 0x2,
|
|
||||||
AcquireGequal = 0x4,
|
|
||||||
AcquireMask = 0x8,
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
GPU::GPU(Core::System& system, bool is_async, bool use_nvdec)
|
GPU::GPU(Core::System& system, bool is_async, bool use_nvdec)
|
||||||
|
@ -744,25 +381,36 @@ GPU::GPU(Core::System& system, bool is_async, bool use_nvdec)
|
||||||
|
|
||||||
GPU::~GPU() = default;
|
GPU::~GPU() = default;
|
||||||
|
|
||||||
|
std::shared_ptr<Control::ChannelState> GPU::AllocateChannel() {
|
||||||
|
return impl->AllocateChannel();
|
||||||
|
}
|
||||||
|
|
||||||
|
void GPU::InitChannel(Control::ChannelState& to_init) {
|
||||||
|
impl->InitChannel(to_init);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GPU::BindChannel(s32 channel_id) {
|
||||||
|
impl->BindChannel(channel_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GPU::ReleaseChannel(Control::ChannelState& to_release) {
|
||||||
|
impl->ReleaseChannel(to_release);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GPU::InitAddressSpace(Tegra::MemoryManager& memory_manager) {
|
||||||
|
impl->InitAddressSpace(memory_manager);
|
||||||
|
}
|
||||||
|
|
||||||
void GPU::BindRenderer(std::unique_ptr<VideoCore::RendererBase> renderer) {
|
void GPU::BindRenderer(std::unique_ptr<VideoCore::RendererBase> renderer) {
|
||||||
impl->BindRenderer(std::move(renderer));
|
impl->BindRenderer(std::move(renderer));
|
||||||
}
|
}
|
||||||
|
|
||||||
void GPU::CallMethod(const MethodCall& method_call) {
|
|
||||||
impl->CallMethod(method_call);
|
|
||||||
}
|
|
||||||
|
|
||||||
void GPU::CallMultiMethod(u32 method, u32 subchannel, const u32* base_start, u32 amount,
|
|
||||||
u32 methods_pending) {
|
|
||||||
impl->CallMultiMethod(method, subchannel, base_start, amount, methods_pending);
|
|
||||||
}
|
|
||||||
|
|
||||||
void GPU::FlushCommands() {
|
void GPU::FlushCommands() {
|
||||||
impl->FlushCommands();
|
impl->FlushCommands();
|
||||||
}
|
}
|
||||||
|
|
||||||
void GPU::SyncGuestHost() {
|
void GPU::InvalidateGPUCache() {
|
||||||
impl->SyncGuestHost();
|
impl->InvalidateGPUCache();
|
||||||
}
|
}
|
||||||
|
|
||||||
void GPU::OnCommandListEnd() {
|
void GPU::OnCommandListEnd() {
|
||||||
|
@ -770,17 +418,32 @@ void GPU::OnCommandListEnd() {
|
||||||
}
|
}
|
||||||
|
|
||||||
u64 GPU::RequestFlush(VAddr addr, std::size_t size) {
|
u64 GPU::RequestFlush(VAddr addr, std::size_t size) {
|
||||||
return impl->RequestFlush(addr, size);
|
return impl->RequestSyncOperation(
|
||||||
|
[this, addr, size]() { impl->rasterizer->FlushRegion(addr, size); });
|
||||||
}
|
}
|
||||||
|
|
||||||
u64 GPU::CurrentFlushRequestFence() const {
|
u64 GPU::CurrentSyncRequestFence() const {
|
||||||
return impl->CurrentFlushRequestFence();
|
return impl->CurrentSyncRequestFence();
|
||||||
|
}
|
||||||
|
|
||||||
|
void GPU::WaitForSyncOperation(u64 fence) {
|
||||||
|
return impl->WaitForSyncOperation(fence);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GPU::TickWork() {
|
void GPU::TickWork() {
|
||||||
impl->TickWork();
|
impl->TickWork();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Gets a mutable reference to the Host1x interface
|
||||||
|
Host1x::Host1x& GPU::Host1x() {
|
||||||
|
return impl->host1x;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets an immutable reference to the Host1x interface.
|
||||||
|
const Host1x::Host1x& GPU::Host1x() const {
|
||||||
|
return impl->host1x;
|
||||||
|
}
|
||||||
|
|
||||||
Engines::Maxwell3D& GPU::Maxwell3D() {
|
Engines::Maxwell3D& GPU::Maxwell3D() {
|
||||||
return impl->Maxwell3D();
|
return impl->Maxwell3D();
|
||||||
}
|
}
|
||||||
|
@ -797,14 +460,6 @@ const Engines::KeplerCompute& GPU::KeplerCompute() const {
|
||||||
return impl->KeplerCompute();
|
return impl->KeplerCompute();
|
||||||
}
|
}
|
||||||
|
|
||||||
Tegra::MemoryManager& GPU::MemoryManager() {
|
|
||||||
return impl->MemoryManager();
|
|
||||||
}
|
|
||||||
|
|
||||||
const Tegra::MemoryManager& GPU::MemoryManager() const {
|
|
||||||
return impl->MemoryManager();
|
|
||||||
}
|
|
||||||
|
|
||||||
Tegra::DmaPusher& GPU::DmaPusher() {
|
Tegra::DmaPusher& GPU::DmaPusher() {
|
||||||
return impl->DmaPusher();
|
return impl->DmaPusher();
|
||||||
}
|
}
|
||||||
|
@ -829,24 +484,9 @@ const VideoCore::ShaderNotify& GPU::ShaderNotify() const {
|
||||||
return impl->ShaderNotify();
|
return impl->ShaderNotify();
|
||||||
}
|
}
|
||||||
|
|
||||||
void GPU::WaitFence(u32 syncpoint_id, u32 value) {
|
void GPU::RequestSwapBuffers(const Tegra::FramebufferConfig* framebuffer,
|
||||||
impl->WaitFence(syncpoint_id, value);
|
std::array<Service::Nvidia::NvFence, 4>& fences, size_t num_fences) {
|
||||||
}
|
impl->RequestSwapBuffers(framebuffer, fences, num_fences);
|
||||||
|
|
||||||
void GPU::IncrementSyncPoint(u32 syncpoint_id) {
|
|
||||||
impl->IncrementSyncPoint(syncpoint_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
u32 GPU::GetSyncpointValue(u32 syncpoint_id) const {
|
|
||||||
return impl->GetSyncpointValue(syncpoint_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
void GPU::RegisterSyncptInterrupt(u32 syncpoint_id, u32 value) {
|
|
||||||
impl->RegisterSyncptInterrupt(syncpoint_id, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool GPU::CancelSyncptInterrupt(u32 syncpoint_id, u32 value) {
|
|
||||||
return impl->CancelSyncptInterrupt(syncpoint_id, value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
u64 GPU::GetTicks() const {
|
u64 GPU::GetTicks() const {
|
||||||
|
@ -881,8 +521,8 @@ void GPU::ReleaseContext() {
|
||||||
impl->ReleaseContext();
|
impl->ReleaseContext();
|
||||||
}
|
}
|
||||||
|
|
||||||
void GPU::PushGPUEntries(Tegra::CommandList&& entries) {
|
void GPU::PushGPUEntries(s32 channel, Tegra::CommandList&& entries) {
|
||||||
impl->PushGPUEntries(std::move(entries));
|
impl->PushGPUEntries(channel, std::move(entries));
|
||||||
}
|
}
|
||||||
|
|
||||||
void GPU::PushCommandBuffer(u32 id, Tegra::ChCommandHeaderList& entries) {
|
void GPU::PushCommandBuffer(u32 id, Tegra::ChCommandHeaderList& entries) {
|
||||||
|
|
|
@ -89,73 +89,58 @@ class Maxwell3D;
|
||||||
class KeplerCompute;
|
class KeplerCompute;
|
||||||
} // namespace Engines
|
} // namespace Engines
|
||||||
|
|
||||||
enum class EngineID {
|
namespace Control {
|
||||||
FERMI_TWOD_A = 0x902D, // 2D Engine
|
struct ChannelState;
|
||||||
MAXWELL_B = 0xB197, // 3D Engine
|
}
|
||||||
KEPLER_COMPUTE_B = 0xB1C0,
|
|
||||||
KEPLER_INLINE_TO_MEMORY_B = 0xA140,
|
namespace Host1x {
|
||||||
MAXWELL_DMA_COPY_A = 0xB0B5,
|
class Host1x;
|
||||||
};
|
} // namespace Host1x
|
||||||
|
|
||||||
class MemoryManager;
|
class MemoryManager;
|
||||||
|
|
||||||
class GPU final {
|
class GPU final {
|
||||||
public:
|
public:
|
||||||
struct MethodCall {
|
|
||||||
u32 method{};
|
|
||||||
u32 argument{};
|
|
||||||
u32 subchannel{};
|
|
||||||
u32 method_count{};
|
|
||||||
|
|
||||||
explicit MethodCall(u32 method_, u32 argument_, u32 subchannel_ = 0, u32 method_count_ = 0)
|
|
||||||
: method(method_), argument(argument_), subchannel(subchannel_),
|
|
||||||
method_count(method_count_) {}
|
|
||||||
|
|
||||||
[[nodiscard]] bool IsLastCall() const {
|
|
||||||
return method_count <= 1;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
enum class FenceOperation : u32 {
|
|
||||||
Acquire = 0,
|
|
||||||
Increment = 1,
|
|
||||||
};
|
|
||||||
|
|
||||||
union FenceAction {
|
|
||||||
u32 raw;
|
|
||||||
BitField<0, 1, FenceOperation> op;
|
|
||||||
BitField<8, 24, u32> syncpoint_id;
|
|
||||||
};
|
|
||||||
|
|
||||||
explicit GPU(Core::System& system, bool is_async, bool use_nvdec);
|
explicit GPU(Core::System& system, bool is_async, bool use_nvdec);
|
||||||
~GPU();
|
~GPU();
|
||||||
|
|
||||||
/// Binds a renderer to the GPU.
|
/// Binds a renderer to the GPU.
|
||||||
void BindRenderer(std::unique_ptr<VideoCore::RendererBase> renderer);
|
void BindRenderer(std::unique_ptr<VideoCore::RendererBase> renderer);
|
||||||
|
|
||||||
/// Calls a GPU method.
|
|
||||||
void CallMethod(const MethodCall& method_call);
|
|
||||||
|
|
||||||
/// Calls a GPU multivalue method.
|
|
||||||
void CallMultiMethod(u32 method, u32 subchannel, const u32* base_start, u32 amount,
|
|
||||||
u32 methods_pending);
|
|
||||||
|
|
||||||
/// Flush all current written commands into the host GPU for execution.
|
/// Flush all current written commands into the host GPU for execution.
|
||||||
void FlushCommands();
|
void FlushCommands();
|
||||||
/// Synchronizes CPU writes with Host GPU memory.
|
/// Synchronizes CPU writes with Host GPU memory.
|
||||||
void SyncGuestHost();
|
void InvalidateGPUCache();
|
||||||
/// Signal the ending of command list.
|
/// Signal the ending of command list.
|
||||||
void OnCommandListEnd();
|
void OnCommandListEnd();
|
||||||
|
|
||||||
|
std::shared_ptr<Control::ChannelState> AllocateChannel();
|
||||||
|
|
||||||
|
void InitChannel(Control::ChannelState& to_init);
|
||||||
|
|
||||||
|
void BindChannel(s32 channel_id);
|
||||||
|
|
||||||
|
void ReleaseChannel(Control::ChannelState& to_release);
|
||||||
|
|
||||||
|
void InitAddressSpace(Tegra::MemoryManager& memory_manager);
|
||||||
|
|
||||||
/// Request a host GPU memory flush from the CPU.
|
/// Request a host GPU memory flush from the CPU.
|
||||||
[[nodiscard]] u64 RequestFlush(VAddr addr, std::size_t size);
|
[[nodiscard]] u64 RequestFlush(VAddr addr, std::size_t size);
|
||||||
|
|
||||||
/// Obtains current flush request fence id.
|
/// Obtains current flush request fence id.
|
||||||
[[nodiscard]] u64 CurrentFlushRequestFence() const;
|
[[nodiscard]] u64 CurrentSyncRequestFence() const;
|
||||||
|
|
||||||
|
void WaitForSyncOperation(u64 fence);
|
||||||
|
|
||||||
/// Tick pending requests within the GPU.
|
/// Tick pending requests within the GPU.
|
||||||
void TickWork();
|
void TickWork();
|
||||||
|
|
||||||
|
/// Gets a mutable reference to the Host1x interface
|
||||||
|
[[nodiscard]] Host1x::Host1x& Host1x();
|
||||||
|
|
||||||
|
/// Gets an immutable reference to the Host1x interface.
|
||||||
|
[[nodiscard]] const Host1x::Host1x& Host1x() const;
|
||||||
|
|
||||||
/// Returns a reference to the Maxwell3D GPU engine.
|
/// Returns a reference to the Maxwell3D GPU engine.
|
||||||
[[nodiscard]] Engines::Maxwell3D& Maxwell3D();
|
[[nodiscard]] Engines::Maxwell3D& Maxwell3D();
|
||||||
|
|
||||||
|
@ -168,12 +153,6 @@ public:
|
||||||
/// Returns a reference to the KeplerCompute GPU engine.
|
/// Returns a reference to the KeplerCompute GPU engine.
|
||||||
[[nodiscard]] const Engines::KeplerCompute& KeplerCompute() const;
|
[[nodiscard]] const Engines::KeplerCompute& KeplerCompute() const;
|
||||||
|
|
||||||
/// Returns a reference to the GPU memory manager.
|
|
||||||
[[nodiscard]] Tegra::MemoryManager& MemoryManager();
|
|
||||||
|
|
||||||
/// Returns a const reference to the GPU memory manager.
|
|
||||||
[[nodiscard]] const Tegra::MemoryManager& MemoryManager() const;
|
|
||||||
|
|
||||||
/// Returns a reference to the GPU DMA pusher.
|
/// Returns a reference to the GPU DMA pusher.
|
||||||
[[nodiscard]] Tegra::DmaPusher& DmaPusher();
|
[[nodiscard]] Tegra::DmaPusher& DmaPusher();
|
||||||
|
|
||||||
|
@ -192,17 +171,6 @@ public:
|
||||||
/// Returns a const reference to the shader notifier.
|
/// Returns a const reference to the shader notifier.
|
||||||
[[nodiscard]] const VideoCore::ShaderNotify& ShaderNotify() const;
|
[[nodiscard]] const VideoCore::ShaderNotify& ShaderNotify() const;
|
||||||
|
|
||||||
/// Allows the CPU/NvFlinger to wait on the GPU before presenting a frame.
|
|
||||||
void WaitFence(u32 syncpoint_id, u32 value);
|
|
||||||
|
|
||||||
void IncrementSyncPoint(u32 syncpoint_id);
|
|
||||||
|
|
||||||
[[nodiscard]] u32 GetSyncpointValue(u32 syncpoint_id) const;
|
|
||||||
|
|
||||||
void RegisterSyncptInterrupt(u32 syncpoint_id, u32 value);
|
|
||||||
|
|
||||||
[[nodiscard]] bool CancelSyncptInterrupt(u32 syncpoint_id, u32 value);
|
|
||||||
|
|
||||||
[[nodiscard]] u64 GetTicks() const;
|
[[nodiscard]] u64 GetTicks() const;
|
||||||
|
|
||||||
[[nodiscard]] bool IsAsync() const;
|
[[nodiscard]] bool IsAsync() const;
|
||||||
|
@ -211,6 +179,9 @@ public:
|
||||||
|
|
||||||
void RendererFrameEndNotify();
|
void RendererFrameEndNotify();
|
||||||
|
|
||||||
|
void RequestSwapBuffers(const Tegra::FramebufferConfig* framebuffer,
|
||||||
|
std::array<Service::Nvidia::NvFence, 4>& fences, size_t num_fences);
|
||||||
|
|
||||||
/// Performs any additional setup necessary in order to begin GPU emulation.
|
/// Performs any additional setup necessary in order to begin GPU emulation.
|
||||||
/// This can be used to launch any necessary threads and register any necessary
|
/// This can be used to launch any necessary threads and register any necessary
|
||||||
/// core timing events.
|
/// core timing events.
|
||||||
|
@ -226,7 +197,7 @@ public:
|
||||||
void ReleaseContext();
|
void ReleaseContext();
|
||||||
|
|
||||||
/// Push GPU command entries to be processed
|
/// Push GPU command entries to be processed
|
||||||
void PushGPUEntries(Tegra::CommandList&& entries);
|
void PushGPUEntries(s32 channel, Tegra::CommandList&& entries);
|
||||||
|
|
||||||
/// Push GPU command buffer entries to be processed
|
/// Push GPU command buffer entries to be processed
|
||||||
void PushCommandBuffer(u32 id, Tegra::ChCommandHeaderList& entries);
|
void PushCommandBuffer(u32 id, Tegra::ChCommandHeaderList& entries);
|
||||||
|
@ -248,7 +219,7 @@ public:
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct Impl;
|
struct Impl;
|
||||||
std::unique_ptr<Impl> impl;
|
mutable std::unique_ptr<Impl> impl;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Tegra
|
} // namespace Tegra
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
#include "common/thread.h"
|
#include "common/thread.h"
|
||||||
#include "core/core.h"
|
#include "core/core.h"
|
||||||
#include "core/frontend/emu_window.h"
|
#include "core/frontend/emu_window.h"
|
||||||
|
#include "video_core/control/scheduler.h"
|
||||||
#include "video_core/dma_pusher.h"
|
#include "video_core/dma_pusher.h"
|
||||||
#include "video_core/gpu.h"
|
#include "video_core/gpu.h"
|
||||||
#include "video_core/gpu_thread.h"
|
#include "video_core/gpu_thread.h"
|
||||||
|
@ -18,7 +19,7 @@ namespace VideoCommon::GPUThread {
|
||||||
/// Runs the GPU thread
|
/// Runs the GPU thread
|
||||||
static void RunThread(std::stop_token stop_token, Core::System& system,
|
static void RunThread(std::stop_token stop_token, Core::System& system,
|
||||||
VideoCore::RendererBase& renderer, Core::Frontend::GraphicsContext& context,
|
VideoCore::RendererBase& renderer, Core::Frontend::GraphicsContext& context,
|
||||||
Tegra::DmaPusher& dma_pusher, SynchState& state) {
|
Tegra::Control::Scheduler& scheduler, SynchState& state) {
|
||||||
std::string name = "GPU";
|
std::string name = "GPU";
|
||||||
MicroProfileOnThreadCreate(name.c_str());
|
MicroProfileOnThreadCreate(name.c_str());
|
||||||
SCOPE_EXIT({ MicroProfileOnThreadExit(); });
|
SCOPE_EXIT({ MicroProfileOnThreadExit(); });
|
||||||
|
@ -36,8 +37,7 @@ static void RunThread(std::stop_token stop_token, Core::System& system,
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (auto* submit_list = std::get_if<SubmitListCommand>(&next.data)) {
|
if (auto* submit_list = std::get_if<SubmitListCommand>(&next.data)) {
|
||||||
dma_pusher.Push(std::move(submit_list->entries));
|
scheduler.Push(submit_list->channel, std::move(submit_list->entries));
|
||||||
dma_pusher.DispatchCalls();
|
|
||||||
} else if (const auto* data = std::get_if<SwapBuffersCommand>(&next.data)) {
|
} else if (const auto* data = std::get_if<SwapBuffersCommand>(&next.data)) {
|
||||||
renderer.SwapBuffers(data->framebuffer ? &*data->framebuffer : nullptr);
|
renderer.SwapBuffers(data->framebuffer ? &*data->framebuffer : nullptr);
|
||||||
} else if (std::holds_alternative<OnCommandListEndCommand>(next.data)) {
|
} else if (std::holds_alternative<OnCommandListEndCommand>(next.data)) {
|
||||||
|
@ -68,14 +68,14 @@ ThreadManager::~ThreadManager() = default;
|
||||||
|
|
||||||
void ThreadManager::StartThread(VideoCore::RendererBase& renderer,
|
void ThreadManager::StartThread(VideoCore::RendererBase& renderer,
|
||||||
Core::Frontend::GraphicsContext& context,
|
Core::Frontend::GraphicsContext& context,
|
||||||
Tegra::DmaPusher& dma_pusher) {
|
Tegra::Control::Scheduler& scheduler) {
|
||||||
rasterizer = renderer.ReadRasterizer();
|
rasterizer = renderer.ReadRasterizer();
|
||||||
thread = std::jthread(RunThread, std::ref(system), std::ref(renderer), std::ref(context),
|
thread = std::jthread(RunThread, std::ref(system), std::ref(renderer), std::ref(context),
|
||||||
std::ref(dma_pusher), std::ref(state));
|
std::ref(scheduler), std::ref(state));
|
||||||
}
|
}
|
||||||
|
|
||||||
void ThreadManager::SubmitList(Tegra::CommandList&& entries) {
|
void ThreadManager::SubmitList(s32 channel, Tegra::CommandList&& entries) {
|
||||||
PushCommand(SubmitListCommand(std::move(entries)));
|
PushCommand(SubmitListCommand(channel, std::move(entries)));
|
||||||
}
|
}
|
||||||
|
|
||||||
void ThreadManager::SwapBuffers(const Tegra::FramebufferConfig* framebuffer) {
|
void ThreadManager::SwapBuffers(const Tegra::FramebufferConfig* framebuffer) {
|
||||||
|
@ -93,8 +93,12 @@ void ThreadManager::FlushRegion(VAddr addr, u64 size) {
|
||||||
}
|
}
|
||||||
auto& gpu = system.GPU();
|
auto& gpu = system.GPU();
|
||||||
u64 fence = gpu.RequestFlush(addr, size);
|
u64 fence = gpu.RequestFlush(addr, size);
|
||||||
PushCommand(GPUTickCommand(), true);
|
TickGPU();
|
||||||
ASSERT(fence <= gpu.CurrentFlushRequestFence());
|
gpu.WaitForSyncOperation(fence);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ThreadManager::TickGPU() {
|
||||||
|
PushCommand(GPUTickCommand());
|
||||||
}
|
}
|
||||||
|
|
||||||
void ThreadManager::InvalidateRegion(VAddr addr, u64 size) {
|
void ThreadManager::InvalidateRegion(VAddr addr, u64 size) {
|
||||||
|
|
|
@ -15,7 +15,9 @@
|
||||||
|
|
||||||
namespace Tegra {
|
namespace Tegra {
|
||||||
struct FramebufferConfig;
|
struct FramebufferConfig;
|
||||||
class DmaPusher;
|
namespace Control {
|
||||||
|
class Scheduler;
|
||||||
|
}
|
||||||
} // namespace Tegra
|
} // namespace Tegra
|
||||||
|
|
||||||
namespace Core {
|
namespace Core {
|
||||||
|
@ -34,8 +36,10 @@ namespace VideoCommon::GPUThread {
|
||||||
|
|
||||||
/// Command to signal to the GPU thread that a command list is ready for processing
|
/// Command to signal to the GPU thread that a command list is ready for processing
|
||||||
struct SubmitListCommand final {
|
struct SubmitListCommand final {
|
||||||
explicit SubmitListCommand(Tegra::CommandList&& entries_) : entries{std::move(entries_)} {}
|
explicit SubmitListCommand(s32 channel_, Tegra::CommandList&& entries_)
|
||||||
|
: channel{channel_}, entries{std::move(entries_)} {}
|
||||||
|
|
||||||
|
s32 channel;
|
||||||
Tegra::CommandList entries;
|
Tegra::CommandList entries;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -112,10 +116,10 @@ public:
|
||||||
|
|
||||||
/// Creates and starts the GPU thread.
|
/// Creates and starts the GPU thread.
|
||||||
void StartThread(VideoCore::RendererBase& renderer, Core::Frontend::GraphicsContext& context,
|
void StartThread(VideoCore::RendererBase& renderer, Core::Frontend::GraphicsContext& context,
|
||||||
Tegra::DmaPusher& dma_pusher);
|
Tegra::Control::Scheduler& scheduler);
|
||||||
|
|
||||||
/// Push GPU command entries to be processed
|
/// Push GPU command entries to be processed
|
||||||
void SubmitList(Tegra::CommandList&& entries);
|
void SubmitList(s32 channel, Tegra::CommandList&& entries);
|
||||||
|
|
||||||
/// Swap buffers (render frame)
|
/// Swap buffers (render frame)
|
||||||
void SwapBuffers(const Tegra::FramebufferConfig* framebuffer);
|
void SwapBuffers(const Tegra::FramebufferConfig* framebuffer);
|
||||||
|
@ -131,6 +135,8 @@ public:
|
||||||
|
|
||||||
void OnCommandListEnd();
|
void OnCommandListEnd();
|
||||||
|
|
||||||
|
void TickGPU();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/// Pushes a command to be executed by the GPU thread
|
/// Pushes a command to be executed by the GPU thread
|
||||||
u64 PushCommand(CommandData&& command_data, bool block = false);
|
u64 PushCommand(CommandData&& command_data, bool block = false);
|
||||||
|
|
|
@ -6,11 +6,11 @@
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include "common/assert.h"
|
#include "common/assert.h"
|
||||||
#include "common/settings.h"
|
#include "common/settings.h"
|
||||||
#include "video_core/command_classes/codecs/codec.h"
|
#include "video_core/host1x/codecs/codec.h"
|
||||||
#include "video_core/command_classes/codecs/h264.h"
|
#include "video_core/host1x/codecs/h264.h"
|
||||||
#include "video_core/command_classes/codecs/vp8.h"
|
#include "video_core/host1x/codecs/vp8.h"
|
||||||
#include "video_core/command_classes/codecs/vp9.h"
|
#include "video_core/host1x/codecs/vp9.h"
|
||||||
#include "video_core/gpu.h"
|
#include "video_core/host1x/host1x.h"
|
||||||
#include "video_core/memory_manager.h"
|
#include "video_core/memory_manager.h"
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
|
@ -73,10 +73,10 @@ void AVFrameDeleter(AVFrame* ptr) {
|
||||||
av_frame_free(&ptr);
|
av_frame_free(&ptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
Codec::Codec(GPU& gpu_, const NvdecCommon::NvdecRegisters& regs)
|
Codec::Codec(Host1x::Host1x& host1x_, const Host1x::NvdecCommon::NvdecRegisters& regs)
|
||||||
: gpu(gpu_), state{regs}, h264_decoder(std::make_unique<Decoder::H264>(gpu)),
|
: host1x(host1x_), state{regs}, h264_decoder(std::make_unique<Decoder::H264>(host1x)),
|
||||||
vp8_decoder(std::make_unique<Decoder::VP8>(gpu)),
|
vp8_decoder(std::make_unique<Decoder::VP8>(host1x)),
|
||||||
vp9_decoder(std::make_unique<Decoder::VP9>(gpu)) {}
|
vp9_decoder(std::make_unique<Decoder::VP9>(host1x)) {}
|
||||||
|
|
||||||
Codec::~Codec() {
|
Codec::~Codec() {
|
||||||
if (!initialized) {
|
if (!initialized) {
|
||||||
|
@ -168,11 +168,11 @@ void Codec::InitializeGpuDecoder() {
|
||||||
void Codec::Initialize() {
|
void Codec::Initialize() {
|
||||||
const AVCodecID codec = [&] {
|
const AVCodecID codec = [&] {
|
||||||
switch (current_codec) {
|
switch (current_codec) {
|
||||||
case NvdecCommon::VideoCodec::H264:
|
case Host1x::NvdecCommon::VideoCodec::H264:
|
||||||
return AV_CODEC_ID_H264;
|
return AV_CODEC_ID_H264;
|
||||||
case NvdecCommon::VideoCodec::VP8:
|
case Host1x::NvdecCommon::VideoCodec::VP8:
|
||||||
return AV_CODEC_ID_VP8;
|
return AV_CODEC_ID_VP8;
|
||||||
case NvdecCommon::VideoCodec::VP9:
|
case Host1x::NvdecCommon::VideoCodec::VP9:
|
||||||
return AV_CODEC_ID_VP9;
|
return AV_CODEC_ID_VP9;
|
||||||
default:
|
default:
|
||||||
UNIMPLEMENTED_MSG("Unknown codec {}", current_codec);
|
UNIMPLEMENTED_MSG("Unknown codec {}", current_codec);
|
||||||
|
@ -197,7 +197,7 @@ void Codec::Initialize() {
|
||||||
initialized = true;
|
initialized = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Codec::SetTargetCodec(NvdecCommon::VideoCodec codec) {
|
void Codec::SetTargetCodec(Host1x::NvdecCommon::VideoCodec codec) {
|
||||||
if (current_codec != codec) {
|
if (current_codec != codec) {
|
||||||
current_codec = codec;
|
current_codec = codec;
|
||||||
LOG_INFO(Service_NVDRV, "NVDEC video codec initialized to {}", GetCurrentCodecName());
|
LOG_INFO(Service_NVDRV, "NVDEC video codec initialized to {}", GetCurrentCodecName());
|
||||||
|
@ -215,11 +215,11 @@ void Codec::Decode() {
|
||||||
bool vp9_hidden_frame = false;
|
bool vp9_hidden_frame = false;
|
||||||
const auto& frame_data = [&]() {
|
const auto& frame_data = [&]() {
|
||||||
switch (current_codec) {
|
switch (current_codec) {
|
||||||
case Tegra::NvdecCommon::VideoCodec::H264:
|
case Tegra::Host1x::NvdecCommon::VideoCodec::H264:
|
||||||
return h264_decoder->ComposeFrame(state, is_first_frame);
|
return h264_decoder->ComposeFrame(state, is_first_frame);
|
||||||
case Tegra::NvdecCommon::VideoCodec::VP8:
|
case Tegra::Host1x::NvdecCommon::VideoCodec::VP8:
|
||||||
return vp8_decoder->ComposeFrame(state);
|
return vp8_decoder->ComposeFrame(state);
|
||||||
case Tegra::NvdecCommon::VideoCodec::VP9:
|
case Tegra::Host1x::NvdecCommon::VideoCodec::VP9:
|
||||||
vp9_decoder->ComposeFrame(state);
|
vp9_decoder->ComposeFrame(state);
|
||||||
vp9_hidden_frame = vp9_decoder->WasFrameHidden();
|
vp9_hidden_frame = vp9_decoder->WasFrameHidden();
|
||||||
return vp9_decoder->GetFrameBytes();
|
return vp9_decoder->GetFrameBytes();
|
||||||
|
@ -287,21 +287,21 @@ AVFramePtr Codec::GetCurrentFrame() {
|
||||||
return frame;
|
return frame;
|
||||||
}
|
}
|
||||||
|
|
||||||
NvdecCommon::VideoCodec Codec::GetCurrentCodec() const {
|
Host1x::NvdecCommon::VideoCodec Codec::GetCurrentCodec() const {
|
||||||
return current_codec;
|
return current_codec;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string_view Codec::GetCurrentCodecName() const {
|
std::string_view Codec::GetCurrentCodecName() const {
|
||||||
switch (current_codec) {
|
switch (current_codec) {
|
||||||
case NvdecCommon::VideoCodec::None:
|
case Host1x::NvdecCommon::VideoCodec::None:
|
||||||
return "None";
|
return "None";
|
||||||
case NvdecCommon::VideoCodec::H264:
|
case Host1x::NvdecCommon::VideoCodec::H264:
|
||||||
return "H264";
|
return "H264";
|
||||||
case NvdecCommon::VideoCodec::VP8:
|
case Host1x::NvdecCommon::VideoCodec::VP8:
|
||||||
return "VP8";
|
return "VP8";
|
||||||
case NvdecCommon::VideoCodec::H265:
|
case Host1x::NvdecCommon::VideoCodec::H265:
|
||||||
return "H265";
|
return "H265";
|
||||||
case NvdecCommon::VideoCodec::VP9:
|
case Host1x::NvdecCommon::VideoCodec::VP9:
|
||||||
return "VP9";
|
return "VP9";
|
||||||
default:
|
default:
|
||||||
return "Unknown";
|
return "Unknown";
|
|
@ -6,8 +6,8 @@
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
#include <queue>
|
#include <queue>
|
||||||
|
#include "common/common_types.h"
|
||||||
#include "video_core/command_classes/nvdec_common.h"
|
#include "video_core/host1x/nvdec_common.h"
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#if defined(__GNUC__) || defined(__clang__)
|
#if defined(__GNUC__) || defined(__clang__)
|
||||||
|
@ -21,7 +21,6 @@ extern "C" {
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace Tegra {
|
namespace Tegra {
|
||||||
class GPU;
|
|
||||||
|
|
||||||
void AVFrameDeleter(AVFrame* ptr);
|
void AVFrameDeleter(AVFrame* ptr);
|
||||||
using AVFramePtr = std::unique_ptr<AVFrame, decltype(&AVFrameDeleter)>;
|
using AVFramePtr = std::unique_ptr<AVFrame, decltype(&AVFrameDeleter)>;
|
||||||
|
@ -32,16 +31,20 @@ class VP8;
|
||||||
class VP9;
|
class VP9;
|
||||||
} // namespace Decoder
|
} // namespace Decoder
|
||||||
|
|
||||||
|
namespace Host1x {
|
||||||
|
class Host1x;
|
||||||
|
} // namespace Host1x
|
||||||
|
|
||||||
class Codec {
|
class Codec {
|
||||||
public:
|
public:
|
||||||
explicit Codec(GPU& gpu, const NvdecCommon::NvdecRegisters& regs);
|
explicit Codec(Host1x::Host1x& host1x, const Host1x::NvdecCommon::NvdecRegisters& regs);
|
||||||
~Codec();
|
~Codec();
|
||||||
|
|
||||||
/// Initialize the codec, returning success or failure
|
/// Initialize the codec, returning success or failure
|
||||||
void Initialize();
|
void Initialize();
|
||||||
|
|
||||||
/// Sets NVDEC video stream codec
|
/// Sets NVDEC video stream codec
|
||||||
void SetTargetCodec(NvdecCommon::VideoCodec codec);
|
void SetTargetCodec(Host1x::NvdecCommon::VideoCodec codec);
|
||||||
|
|
||||||
/// Call decoders to construct headers, decode AVFrame with ffmpeg
|
/// Call decoders to construct headers, decode AVFrame with ffmpeg
|
||||||
void Decode();
|
void Decode();
|
||||||
|
@ -50,7 +53,7 @@ public:
|
||||||
[[nodiscard]] AVFramePtr GetCurrentFrame();
|
[[nodiscard]] AVFramePtr GetCurrentFrame();
|
||||||
|
|
||||||
/// Returns the value of current_codec
|
/// Returns the value of current_codec
|
||||||
[[nodiscard]] NvdecCommon::VideoCodec GetCurrentCodec() const;
|
[[nodiscard]] Host1x::NvdecCommon::VideoCodec GetCurrentCodec() const;
|
||||||
|
|
||||||
/// Return name of the current codec
|
/// Return name of the current codec
|
||||||
[[nodiscard]] std::string_view GetCurrentCodecName() const;
|
[[nodiscard]] std::string_view GetCurrentCodecName() const;
|
||||||
|
@ -63,14 +66,14 @@ private:
|
||||||
bool CreateGpuAvDevice();
|
bool CreateGpuAvDevice();
|
||||||
|
|
||||||
bool initialized{};
|
bool initialized{};
|
||||||
NvdecCommon::VideoCodec current_codec{NvdecCommon::VideoCodec::None};
|
Host1x::NvdecCommon::VideoCodec current_codec{Host1x::NvdecCommon::VideoCodec::None};
|
||||||
|
|
||||||
const AVCodec* av_codec{nullptr};
|
const AVCodec* av_codec{nullptr};
|
||||||
AVCodecContext* av_codec_ctx{nullptr};
|
AVCodecContext* av_codec_ctx{nullptr};
|
||||||
AVBufferRef* av_gpu_decoder{nullptr};
|
AVBufferRef* av_gpu_decoder{nullptr};
|
||||||
|
|
||||||
GPU& gpu;
|
Host1x::Host1x& host1x;
|
||||||
const NvdecCommon::NvdecRegisters& state;
|
const Host1x::NvdecCommon::NvdecRegisters& state;
|
||||||
std::unique_ptr<Decoder::H264> h264_decoder;
|
std::unique_ptr<Decoder::H264> h264_decoder;
|
||||||
std::unique_ptr<Decoder::VP8> vp8_decoder;
|
std::unique_ptr<Decoder::VP8> vp8_decoder;
|
||||||
std::unique_ptr<Decoder::VP9> vp9_decoder;
|
std::unique_ptr<Decoder::VP9> vp9_decoder;
|
|
@ -5,8 +5,8 @@
|
||||||
#include <bit>
|
#include <bit>
|
||||||
|
|
||||||
#include "common/settings.h"
|
#include "common/settings.h"
|
||||||
#include "video_core/command_classes/codecs/h264.h"
|
#include "video_core/host1x/codecs/h264.h"
|
||||||
#include "video_core/gpu.h"
|
#include "video_core/host1x/host1x.h"
|
||||||
#include "video_core/memory_manager.h"
|
#include "video_core/memory_manager.h"
|
||||||
|
|
||||||
namespace Tegra::Decoder {
|
namespace Tegra::Decoder {
|
||||||
|
@ -24,19 +24,20 @@ constexpr std::array<u8, 16> zig_zag_scan{
|
||||||
};
|
};
|
||||||
} // Anonymous namespace
|
} // Anonymous namespace
|
||||||
|
|
||||||
H264::H264(GPU& gpu_) : gpu(gpu_) {}
|
H264::H264(Host1x::Host1x& host1x_) : host1x{host1x_} {}
|
||||||
|
|
||||||
H264::~H264() = default;
|
H264::~H264() = default;
|
||||||
|
|
||||||
const std::vector<u8>& H264::ComposeFrame(const NvdecCommon::NvdecRegisters& state,
|
const std::vector<u8>& H264::ComposeFrame(const Host1x::NvdecCommon::NvdecRegisters& state,
|
||||||
bool is_first_frame) {
|
bool is_first_frame) {
|
||||||
H264DecoderContext context;
|
H264DecoderContext context;
|
||||||
gpu.MemoryManager().ReadBlock(state.picture_info_offset, &context, sizeof(H264DecoderContext));
|
host1x.MemoryManager().ReadBlock(state.picture_info_offset, &context,
|
||||||
|
sizeof(H264DecoderContext));
|
||||||
|
|
||||||
const s64 frame_number = context.h264_parameter_set.frame_number.Value();
|
const s64 frame_number = context.h264_parameter_set.frame_number.Value();
|
||||||
if (!is_first_frame && frame_number != 0) {
|
if (!is_first_frame && frame_number != 0) {
|
||||||
frame.resize(context.stream_len);
|
frame.resize(context.stream_len);
|
||||||
gpu.MemoryManager().ReadBlock(state.frame_bitstream_offset, frame.data(), frame.size());
|
host1x.MemoryManager().ReadBlock(state.frame_bitstream_offset, frame.data(), frame.size());
|
||||||
return frame;
|
return frame;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -155,7 +156,7 @@ const std::vector<u8>& H264::ComposeFrame(const NvdecCommon::NvdecRegisters& sta
|
||||||
frame.resize(encoded_header.size() + context.stream_len);
|
frame.resize(encoded_header.size() + context.stream_len);
|
||||||
std::memcpy(frame.data(), encoded_header.data(), encoded_header.size());
|
std::memcpy(frame.data(), encoded_header.data(), encoded_header.size());
|
||||||
|
|
||||||
gpu.MemoryManager().ReadBlock(state.frame_bitstream_offset,
|
host1x.MemoryManager().ReadBlock(state.frame_bitstream_offset,
|
||||||
frame.data() + encoded_header.size(), context.stream_len);
|
frame.data() + encoded_header.size(), context.stream_len);
|
||||||
|
|
||||||
return frame;
|
return frame;
|
|
@ -8,10 +8,14 @@
|
||||||
#include "common/bit_field.h"
|
#include "common/bit_field.h"
|
||||||
#include "common/common_funcs.h"
|
#include "common/common_funcs.h"
|
||||||
#include "common/common_types.h"
|
#include "common/common_types.h"
|
||||||
#include "video_core/command_classes/nvdec_common.h"
|
#include "video_core/host1x/nvdec_common.h"
|
||||||
|
|
||||||
namespace Tegra {
|
namespace Tegra {
|
||||||
class GPU;
|
|
||||||
|
namespace Host1x {
|
||||||
|
class Host1x;
|
||||||
|
} // namespace Host1x
|
||||||
|
|
||||||
namespace Decoder {
|
namespace Decoder {
|
||||||
|
|
||||||
class H264BitWriter {
|
class H264BitWriter {
|
||||||
|
@ -55,16 +59,16 @@ private:
|
||||||
|
|
||||||
class H264 {
|
class H264 {
|
||||||
public:
|
public:
|
||||||
explicit H264(GPU& gpu);
|
explicit H264(Host1x::Host1x& host1x);
|
||||||
~H264();
|
~H264();
|
||||||
|
|
||||||
/// Compose the H264 frame for FFmpeg decoding
|
/// Compose the H264 frame for FFmpeg decoding
|
||||||
[[nodiscard]] const std::vector<u8>& ComposeFrame(const NvdecCommon::NvdecRegisters& state,
|
[[nodiscard]] const std::vector<u8>& ComposeFrame(
|
||||||
bool is_first_frame = false);
|
const Host1x::NvdecCommon::NvdecRegisters& state, bool is_first_frame = false);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::vector<u8> frame;
|
std::vector<u8> frame;
|
||||||
GPU& gpu;
|
Host1x::Host1x& host1x;
|
||||||
|
|
||||||
struct H264ParameterSet {
|
struct H264ParameterSet {
|
||||||
s32 log2_max_pic_order_cnt_lsb_minus4; ///< 0x00
|
s32 log2_max_pic_order_cnt_lsb_minus4; ///< 0x00
|
|
@ -3,18 +3,18 @@
|
||||||
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "video_core/command_classes/codecs/vp8.h"
|
#include "video_core/host1x/codecs/vp8.h"
|
||||||
#include "video_core/gpu.h"
|
#include "video_core/host1x/host1x.h"
|
||||||
#include "video_core/memory_manager.h"
|
#include "video_core/memory_manager.h"
|
||||||
|
|
||||||
namespace Tegra::Decoder {
|
namespace Tegra::Decoder {
|
||||||
VP8::VP8(GPU& gpu_) : gpu(gpu_) {}
|
VP8::VP8(Host1x::Host1x& host1x_) : host1x{host1x_} {}
|
||||||
|
|
||||||
VP8::~VP8() = default;
|
VP8::~VP8() = default;
|
||||||
|
|
||||||
const std::vector<u8>& VP8::ComposeFrame(const NvdecCommon::NvdecRegisters& state) {
|
const std::vector<u8>& VP8::ComposeFrame(const Host1x::NvdecCommon::NvdecRegisters& state) {
|
||||||
VP8PictureInfo info;
|
VP8PictureInfo info;
|
||||||
gpu.MemoryManager().ReadBlock(state.picture_info_offset, &info, sizeof(VP8PictureInfo));
|
host1x.MemoryManager().ReadBlock(state.picture_info_offset, &info, sizeof(VP8PictureInfo));
|
||||||
|
|
||||||
const bool is_key_frame = info.key_frame == 1u;
|
const bool is_key_frame = info.key_frame == 1u;
|
||||||
const auto bitstream_size = static_cast<size_t>(info.vld_buffer_size);
|
const auto bitstream_size = static_cast<size_t>(info.vld_buffer_size);
|
||||||
|
@ -45,7 +45,7 @@ const std::vector<u8>& VP8::ComposeFrame(const NvdecCommon::NvdecRegisters& stat
|
||||||
frame[9] = static_cast<u8>(((info.frame_height >> 8) & 0x3f));
|
frame[9] = static_cast<u8>(((info.frame_height >> 8) & 0x3f));
|
||||||
}
|
}
|
||||||
const u64 bitstream_offset = state.frame_bitstream_offset;
|
const u64 bitstream_offset = state.frame_bitstream_offset;
|
||||||
gpu.MemoryManager().ReadBlock(bitstream_offset, frame.data() + header_size, bitstream_size);
|
host1x.MemoryManager().ReadBlock(bitstream_offset, frame.data() + header_size, bitstream_size);
|
||||||
|
|
||||||
return frame;
|
return frame;
|
||||||
}
|
}
|
|
@ -8,23 +8,28 @@
|
||||||
|
|
||||||
#include "common/common_funcs.h"
|
#include "common/common_funcs.h"
|
||||||
#include "common/common_types.h"
|
#include "common/common_types.h"
|
||||||
#include "video_core/command_classes/nvdec_common.h"
|
#include "video_core/host1x/nvdec_common.h"
|
||||||
|
|
||||||
namespace Tegra {
|
namespace Tegra {
|
||||||
class GPU;
|
|
||||||
|
namespace Host1x {
|
||||||
|
class Host1x;
|
||||||
|
} // namespace Host1x
|
||||||
|
|
||||||
namespace Decoder {
|
namespace Decoder {
|
||||||
|
|
||||||
class VP8 {
|
class VP8 {
|
||||||
public:
|
public:
|
||||||
explicit VP8(GPU& gpu);
|
explicit VP8(Host1x::Host1x& host1x);
|
||||||
~VP8();
|
~VP8();
|
||||||
|
|
||||||
/// Compose the VP8 frame for FFmpeg decoding
|
/// Compose the VP8 frame for FFmpeg decoding
|
||||||
[[nodiscard]] const std::vector<u8>& ComposeFrame(const NvdecCommon::NvdecRegisters& state);
|
[[nodiscard]] const std::vector<u8>& ComposeFrame(
|
||||||
|
const Host1x::NvdecCommon::NvdecRegisters& state);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::vector<u8> frame;
|
std::vector<u8> frame;
|
||||||
GPU& gpu;
|
Host1x::Host1x& host1x;
|
||||||
|
|
||||||
struct VP8PictureInfo {
|
struct VP8PictureInfo {
|
||||||
INSERT_PADDING_WORDS_NOINIT(14);
|
INSERT_PADDING_WORDS_NOINIT(14);
|
|
@ -4,8 +4,8 @@
|
||||||
#include <algorithm> // for std::copy
|
#include <algorithm> // for std::copy
|
||||||
#include <numeric>
|
#include <numeric>
|
||||||
#include "common/assert.h"
|
#include "common/assert.h"
|
||||||
#include "video_core/command_classes/codecs/vp9.h"
|
#include "video_core/host1x/codecs/vp9.h"
|
||||||
#include "video_core/gpu.h"
|
#include "video_core/host1x/host1x.h"
|
||||||
#include "video_core/memory_manager.h"
|
#include "video_core/memory_manager.h"
|
||||||
|
|
||||||
namespace Tegra::Decoder {
|
namespace Tegra::Decoder {
|
||||||
|
@ -236,7 +236,7 @@ constexpr std::array<u8, 254> map_lut{
|
||||||
}
|
}
|
||||||
} // Anonymous namespace
|
} // Anonymous namespace
|
||||||
|
|
||||||
VP9::VP9(GPU& gpu_) : gpu{gpu_} {}
|
VP9::VP9(Host1x::Host1x& host1x_) : host1x{host1x_} {}
|
||||||
|
|
||||||
VP9::~VP9() = default;
|
VP9::~VP9() = default;
|
||||||
|
|
||||||
|
@ -355,9 +355,9 @@ void VP9::WriteMvProbabilityUpdate(VpxRangeEncoder& writer, u8 new_prob, u8 old_
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Vp9PictureInfo VP9::GetVp9PictureInfo(const NvdecCommon::NvdecRegisters& state) {
|
Vp9PictureInfo VP9::GetVp9PictureInfo(const Host1x::NvdecCommon::NvdecRegisters& state) {
|
||||||
PictureInfo picture_info;
|
PictureInfo picture_info;
|
||||||
gpu.MemoryManager().ReadBlock(state.picture_info_offset, &picture_info, sizeof(PictureInfo));
|
host1x.MemoryManager().ReadBlock(state.picture_info_offset, &picture_info, sizeof(PictureInfo));
|
||||||
Vp9PictureInfo vp9_info = picture_info.Convert();
|
Vp9PictureInfo vp9_info = picture_info.Convert();
|
||||||
|
|
||||||
InsertEntropy(state.vp9_entropy_probs_offset, vp9_info.entropy);
|
InsertEntropy(state.vp9_entropy_probs_offset, vp9_info.entropy);
|
||||||
|
@ -372,17 +372,18 @@ Vp9PictureInfo VP9::GetVp9PictureInfo(const NvdecCommon::NvdecRegisters& state)
|
||||||
|
|
||||||
void VP9::InsertEntropy(u64 offset, Vp9EntropyProbs& dst) {
|
void VP9::InsertEntropy(u64 offset, Vp9EntropyProbs& dst) {
|
||||||
EntropyProbs entropy;
|
EntropyProbs entropy;
|
||||||
gpu.MemoryManager().ReadBlock(offset, &entropy, sizeof(EntropyProbs));
|
host1x.MemoryManager().ReadBlock(offset, &entropy, sizeof(EntropyProbs));
|
||||||
entropy.Convert(dst);
|
entropy.Convert(dst);
|
||||||
}
|
}
|
||||||
|
|
||||||
Vp9FrameContainer VP9::GetCurrentFrame(const NvdecCommon::NvdecRegisters& state) {
|
Vp9FrameContainer VP9::GetCurrentFrame(const Host1x::NvdecCommon::NvdecRegisters& state) {
|
||||||
Vp9FrameContainer current_frame{};
|
Vp9FrameContainer current_frame{};
|
||||||
{
|
{
|
||||||
gpu.SyncGuestHost();
|
// gpu.SyncGuestHost(); epic, why?
|
||||||
current_frame.info = GetVp9PictureInfo(state);
|
current_frame.info = GetVp9PictureInfo(state);
|
||||||
current_frame.bit_stream.resize(current_frame.info.bitstream_size);
|
current_frame.bit_stream.resize(current_frame.info.bitstream_size);
|
||||||
gpu.MemoryManager().ReadBlock(state.frame_bitstream_offset, current_frame.bit_stream.data(),
|
host1x.MemoryManager().ReadBlock(state.frame_bitstream_offset,
|
||||||
|
current_frame.bit_stream.data(),
|
||||||
current_frame.info.bitstream_size);
|
current_frame.info.bitstream_size);
|
||||||
}
|
}
|
||||||
if (!next_frame.bit_stream.empty()) {
|
if (!next_frame.bit_stream.empty()) {
|
||||||
|
@ -769,7 +770,7 @@ VpxBitStreamWriter VP9::ComposeUncompressedHeader() {
|
||||||
return uncomp_writer;
|
return uncomp_writer;
|
||||||
}
|
}
|
||||||
|
|
||||||
void VP9::ComposeFrame(const NvdecCommon::NvdecRegisters& state) {
|
void VP9::ComposeFrame(const Host1x::NvdecCommon::NvdecRegisters& state) {
|
||||||
std::vector<u8> bitstream;
|
std::vector<u8> bitstream;
|
||||||
{
|
{
|
||||||
Vp9FrameContainer curr_frame = GetCurrentFrame(state);
|
Vp9FrameContainer curr_frame = GetCurrentFrame(state);
|
|
@ -8,11 +8,15 @@
|
||||||
|
|
||||||
#include "common/common_types.h"
|
#include "common/common_types.h"
|
||||||
#include "common/stream.h"
|
#include "common/stream.h"
|
||||||
#include "video_core/command_classes/codecs/vp9_types.h"
|
#include "video_core/host1x/codecs/vp9_types.h"
|
||||||
#include "video_core/command_classes/nvdec_common.h"
|
#include "video_core/host1x/nvdec_common.h"
|
||||||
|
|
||||||
namespace Tegra {
|
namespace Tegra {
|
||||||
class GPU;
|
|
||||||
|
namespace Host1x {
|
||||||
|
class Host1x;
|
||||||
|
} // namespace Host1x
|
||||||
|
|
||||||
namespace Decoder {
|
namespace Decoder {
|
||||||
|
|
||||||
/// The VpxRangeEncoder, and VpxBitStreamWriter classes are used to compose the
|
/// The VpxRangeEncoder, and VpxBitStreamWriter classes are used to compose the
|
||||||
|
@ -106,7 +110,7 @@ private:
|
||||||
|
|
||||||
class VP9 {
|
class VP9 {
|
||||||
public:
|
public:
|
||||||
explicit VP9(GPU& gpu_);
|
explicit VP9(Host1x::Host1x& host1x);
|
||||||
~VP9();
|
~VP9();
|
||||||
|
|
||||||
VP9(const VP9&) = delete;
|
VP9(const VP9&) = delete;
|
||||||
|
@ -117,7 +121,7 @@ public:
|
||||||
|
|
||||||
/// Composes the VP9 frame from the GPU state information.
|
/// Composes the VP9 frame from the GPU state information.
|
||||||
/// Based on the official VP9 spec documentation
|
/// Based on the official VP9 spec documentation
|
||||||
void ComposeFrame(const NvdecCommon::NvdecRegisters& state);
|
void ComposeFrame(const Host1x::NvdecCommon::NvdecRegisters& state);
|
||||||
|
|
||||||
/// Returns true if the most recent frame was a hidden frame.
|
/// Returns true if the most recent frame was a hidden frame.
|
||||||
[[nodiscard]] bool WasFrameHidden() const {
|
[[nodiscard]] bool WasFrameHidden() const {
|
||||||
|
@ -162,19 +166,21 @@ private:
|
||||||
void WriteMvProbabilityUpdate(VpxRangeEncoder& writer, u8 new_prob, u8 old_prob);
|
void WriteMvProbabilityUpdate(VpxRangeEncoder& writer, u8 new_prob, u8 old_prob);
|
||||||
|
|
||||||
/// Returns VP9 information from NVDEC provided offset and size
|
/// Returns VP9 information from NVDEC provided offset and size
|
||||||
[[nodiscard]] Vp9PictureInfo GetVp9PictureInfo(const NvdecCommon::NvdecRegisters& state);
|
[[nodiscard]] Vp9PictureInfo GetVp9PictureInfo(
|
||||||
|
const Host1x::NvdecCommon::NvdecRegisters& state);
|
||||||
|
|
||||||
/// Read and convert NVDEC provided entropy probs to Vp9EntropyProbs struct
|
/// Read and convert NVDEC provided entropy probs to Vp9EntropyProbs struct
|
||||||
void InsertEntropy(u64 offset, Vp9EntropyProbs& dst);
|
void InsertEntropy(u64 offset, Vp9EntropyProbs& dst);
|
||||||
|
|
||||||
/// Returns frame to be decoded after buffering
|
/// Returns frame to be decoded after buffering
|
||||||
[[nodiscard]] Vp9FrameContainer GetCurrentFrame(const NvdecCommon::NvdecRegisters& state);
|
[[nodiscard]] Vp9FrameContainer GetCurrentFrame(
|
||||||
|
const Host1x::NvdecCommon::NvdecRegisters& state);
|
||||||
|
|
||||||
/// Use NVDEC providied information to compose the headers for the current frame
|
/// Use NVDEC providied information to compose the headers for the current frame
|
||||||
[[nodiscard]] std::vector<u8> ComposeCompressedHeader();
|
[[nodiscard]] std::vector<u8> ComposeCompressedHeader();
|
||||||
[[nodiscard]] VpxBitStreamWriter ComposeUncompressedHeader();
|
[[nodiscard]] VpxBitStreamWriter ComposeUncompressedHeader();
|
||||||
|
|
||||||
GPU& gpu;
|
Host1x::Host1x& host1x;
|
||||||
std::vector<u8> frame;
|
std::vector<u8> frame;
|
||||||
|
|
||||||
std::array<s8, 4> loop_filter_ref_deltas{};
|
std::array<s8, 4> loop_filter_ref_deltas{};
|
|
@ -9,7 +9,6 @@
|
||||||
#include "common/common_types.h"
|
#include "common/common_types.h"
|
||||||
|
|
||||||
namespace Tegra {
|
namespace Tegra {
|
||||||
class GPU;
|
|
||||||
|
|
||||||
namespace Decoder {
|
namespace Decoder {
|
||||||
struct Vp9FrameDimensions {
|
struct Vp9FrameDimensions {
|
33
src/video_core/host1x/control.cpp
Normal file
33
src/video_core/host1x/control.cpp
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
// SPDX-FileCopyrightText: 2021 yuzu Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
#include "common/assert.h"
|
||||||
|
#include "video_core/host1x/control.h"
|
||||||
|
#include "video_core/host1x/host1x.h"
|
||||||
|
|
||||||
|
namespace Tegra::Host1x {
|
||||||
|
|
||||||
|
Control::Control(Host1x& host1x_) : host1x(host1x_) {}
|
||||||
|
|
||||||
|
Control::~Control() = default;
|
||||||
|
|
||||||
|
void Control::ProcessMethod(Method method, u32 argument) {
|
||||||
|
switch (method) {
|
||||||
|
case Method::LoadSyncptPayload32:
|
||||||
|
syncpoint_value = argument;
|
||||||
|
break;
|
||||||
|
case Method::WaitSyncpt:
|
||||||
|
case Method::WaitSyncpt32:
|
||||||
|
Execute(argument);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
UNIMPLEMENTED_MSG("Control method 0x{:X}", static_cast<u32>(method));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Control::Execute(u32 data) {
|
||||||
|
host1x.GetSyncpointManager().WaitHost(data, syncpoint_value);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Tegra::Host1x
|
|
@ -1,15 +1,19 @@
|
||||||
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
|
// SPDX-FileCopyrightText: 2021 yuzu Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-FileCopyrightText: 2021 Skyline Team and Contributors
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "common/common_types.h"
|
#include "common/common_types.h"
|
||||||
|
|
||||||
namespace Tegra {
|
namespace Tegra {
|
||||||
class GPU;
|
|
||||||
|
namespace Host1x {
|
||||||
|
|
||||||
|
class Host1x;
|
||||||
class Nvdec;
|
class Nvdec;
|
||||||
|
|
||||||
class Host1x {
|
class Control {
|
||||||
public:
|
public:
|
||||||
enum class Method : u32 {
|
enum class Method : u32 {
|
||||||
WaitSyncpt = 0x8,
|
WaitSyncpt = 0x8,
|
||||||
|
@ -17,8 +21,8 @@ public:
|
||||||
WaitSyncpt32 = 0x50,
|
WaitSyncpt32 = 0x50,
|
||||||
};
|
};
|
||||||
|
|
||||||
explicit Host1x(GPU& gpu);
|
explicit Control(Host1x& host1x);
|
||||||
~Host1x();
|
~Control();
|
||||||
|
|
||||||
/// Writes the method into the state, Invoke Execute() if encountered
|
/// Writes the method into the state, Invoke Execute() if encountered
|
||||||
void ProcessMethod(Method method, u32 argument);
|
void ProcessMethod(Method method, u32 argument);
|
||||||
|
@ -28,7 +32,9 @@ private:
|
||||||
void Execute(u32 data);
|
void Execute(u32 data);
|
||||||
|
|
||||||
u32 syncpoint_value{};
|
u32 syncpoint_value{};
|
||||||
GPU& gpu;
|
Host1x& host1x;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
} // namespace Host1x
|
||||||
|
|
||||||
} // namespace Tegra
|
} // namespace Tegra
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue