Compare commits
74 Commits
__refs_pul
...
__refs_pul
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
75f00f756f | ||
|
|
e16d1b85f1 | ||
|
|
2572b0a5ea | ||
|
|
e8cbc3b4c5 | ||
|
|
64965cc658 | ||
|
|
20b62dbd30 | ||
|
|
9d081a8729 | ||
|
|
826e0785bf | ||
|
|
3b582d5fb2 | ||
|
|
7356ab1de6 | ||
|
|
daf2ef8f1c | ||
|
|
5fbd6954ef | ||
|
|
957840be91 | ||
|
|
97f273e94e | ||
|
|
59335f6796 | ||
|
|
c31f19b6d1 | ||
|
|
9abceaed61 | ||
|
|
cdb2e4eaff | ||
|
|
168c9ee341 | ||
|
|
8d99aae45b | ||
|
|
f047f376d4 | ||
|
|
84d4da89a5 | ||
|
|
3027917f39 | ||
|
|
7e0f70e5a1 | ||
|
|
aab68674c0 | ||
|
|
7f1c6def1f | ||
|
|
db7bcd51ae | ||
|
|
eb0713f781 | ||
|
|
57a05b1653 | ||
|
|
69c92b8156 | ||
|
|
4acbf3a193 | ||
|
|
54d6273975 | ||
|
|
98a6cd02c8 | ||
|
|
52acdafa95 | ||
|
|
975e17aa13 | ||
|
|
72118935a1 | ||
|
|
109c31c90f | ||
|
|
344e171cc7 | ||
|
|
bcbc25eeb3 | ||
|
|
b0365a81c2 | ||
|
|
327d225c3e | ||
|
|
aa075a0c08 | ||
|
|
38c48cf8d8 | ||
|
|
4975f60162 | ||
|
|
0d033e6b45 | ||
|
|
9c67334031 | ||
|
|
1fb33bd1e1 | ||
|
|
405d685101 | ||
|
|
e5a446a0df | ||
|
|
0e61d711e2 | ||
|
|
bc95753107 | ||
|
|
60e0d4a177 | ||
|
|
4c42655a2d | ||
|
|
ece0c1095d | ||
|
|
f426fd95fe | ||
|
|
fa660190ff | ||
|
|
7c50a916c7 | ||
|
|
48b4eca28a | ||
|
|
e94bcf03cb | ||
|
|
8ca02794c5 | ||
|
|
ef5184cf1c | ||
|
|
9e27624a19 | ||
|
|
cb971ad654 | ||
|
|
cf202f3718 | ||
|
|
18fcc03b3c | ||
|
|
6fa3faec65 | ||
|
|
040a01a5dd | ||
|
|
8cc5ad8742 | ||
|
|
540c1696d1 | ||
|
|
651f6598ac | ||
|
|
70ea1c2000 | ||
|
|
b832942b6e | ||
|
|
18123ff958 | ||
|
|
ceb829cc33 |
10
.github/ISSUE_TEMPLATE/blank_issue_template.yml
vendored
Normal file
10
.github/ISSUE_TEMPLATE/blank_issue_template.yml
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
name: New Issue (Developers Only)
|
||||
description: A blank issue template for developers only. If you are not a developer, do not use this issue template. Your issue WILL BE CLOSED if you do not use the appropriate issue template.
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
**If you are not a developer, do not use this issue template. Your issue WILL BE CLOSED if you do not use the appropriate issue template.**
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: "Issue"
|
||||
@@ -1,39 +0,0 @@
|
||||
---
|
||||
name: Bug Report / Feature Request
|
||||
about: Tech support does not belong here. You should only file an issue here if you think you have experienced an actual bug with yuzu or you are requesting a feature you believe would make yuzu better.
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
<!---
|
||||
Please keep in mind yuzu is EXPERIMENTAL SOFTWARE.
|
||||
|
||||
Please read the FAQ:
|
||||
https://yuzu-emu.org/wiki/faq/
|
||||
|
||||
THIS IS NOT A SUPPORT FORUM, FOR SUPPORT GO TO:
|
||||
https://community.citra-emu.org/
|
||||
|
||||
If the FAQ does not answer your question, please go to:
|
||||
https://community.citra-emu.org/
|
||||
|
||||
When submitting an issue, please check the following:
|
||||
|
||||
- You have read the above.
|
||||
- You have provided the version (commit hash) of yuzu you are using.
|
||||
- You have provided sufficient detail for the issue to be reproduced.
|
||||
- You have provided system specs (if relevant).
|
||||
- Please also provide:
|
||||
- For any issues, a log file
|
||||
- For crashes, a backtrace.
|
||||
- For graphical issues, comparison screenshots with real hardware.
|
||||
- For emulation inaccuracies, a test-case (if able).
|
||||
|
||||
-->
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
64
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
64
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
@@ -0,0 +1,64 @@
|
||||
name: Bug Report
|
||||
description: File a bug report
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: Tech support does not belong here. You should only file an issue here if you think you have experienced an actual bug with yuzu.
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Is there an existing issue for this?
|
||||
description: Please search to see if an issue already exists for the bug you encountered.
|
||||
options:
|
||||
- label: I have searched the existing issues
|
||||
required: true
|
||||
- type: input
|
||||
attributes:
|
||||
label: Affected Build(s)
|
||||
description: List the affected build(s) that this issue applies to.
|
||||
placeholder: Mainline 1234 / Early Access 1234
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: issue-desc
|
||||
attributes:
|
||||
label: Description of Issue
|
||||
description: A brief description of the issue encountered along with any images and/or videos.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: expected-behavior
|
||||
attributes:
|
||||
label: Expected Behavior
|
||||
description: A brief description of how it is expected to work along with any images and/or videos.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: reproduction-steps
|
||||
attributes:
|
||||
label: Reproduction Steps
|
||||
description: A brief explanation of how to reproduce this issue. If possible, provide a save file to aid in reproducing the issue.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: log
|
||||
attributes:
|
||||
label: Log File
|
||||
description: A log file will help our developers to better diagnose and fix the issue.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: system-config
|
||||
attributes:
|
||||
label: System Configuration
|
||||
placeholder: |
|
||||
CPU: Intel i5-10400 / AMD Ryzen 5 3600
|
||||
GPU/Driver: NVIDIA GeForce GTX 1060 (Driver 512.95)
|
||||
RAM: 16GB DDR4-3200
|
||||
OS: Windows 11 22H2 (Build 22621.819)
|
||||
value: |
|
||||
CPU:
|
||||
GPU/Driver:
|
||||
RAM:
|
||||
OS:
|
||||
validations:
|
||||
required: true
|
||||
28
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
Normal file
28
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
name: Feature Request
|
||||
description: File a feature request
|
||||
labels: "request"
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: Tech support does not belong here. You should only file an issue here if you are requesting a feature you believe would make yuzu better.
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Is there an existing issue for this?
|
||||
description: Please search to see if an issue already exists for the feature you are requesting.
|
||||
options:
|
||||
- label: I have searched the existing issues
|
||||
required: true
|
||||
- type: textarea
|
||||
id: what-feature
|
||||
attributes:
|
||||
label: What feature are you suggesting?
|
||||
description: A brief description of the requested feature.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: why-feature
|
||||
attributes:
|
||||
label: Why would this feature be useful?
|
||||
description: A brief description of why this feature would make yuzu better.
|
||||
validations:
|
||||
required: true
|
||||
@@ -132,10 +132,6 @@ Files: vcpkg.json
|
||||
Copyright: 2022 yuzu Emulator Project
|
||||
License: GPL-3.0-or-later
|
||||
|
||||
Files: .github/ISSUE_TEMPLATE/config.yml
|
||||
Copyright: 2020 tgsm <doodrabbit@hotmail.com>
|
||||
License: GPL-2.0-or-later
|
||||
|
||||
Files: .github/ISSUE_TEMPLATE/bug-report-feature-request.md
|
||||
Copyright: 2016 MerryMage
|
||||
Files: .github/ISSUE_TEMPLATE/*
|
||||
Copyright: 2022 yuzu Emulator Project
|
||||
License: GPL-2.0-or-later
|
||||
|
||||
12
externals/CMakeLists.txt
vendored
12
externals/CMakeLists.txt
vendored
@@ -92,10 +92,14 @@ endif()
|
||||
add_subdirectory(sirit)
|
||||
|
||||
if (ENABLE_WEB_SERVICE)
|
||||
find_package(OpenSSL 1.1)
|
||||
if (OPENSSL_FOUND)
|
||||
set(OPENSSL_LIBRARIES OpenSSL::SSL OpenSSL::Crypto)
|
||||
else()
|
||||
if (NOT WIN32)
|
||||
find_package(OpenSSL 1.1)
|
||||
if (OPENSSL_FOUND)
|
||||
set(OPENSSL_LIBRARIES OpenSSL::SSL OpenSSL::Crypto)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if (WIN32 OR NOT OPENSSL_FOUND)
|
||||
# LibreSSL
|
||||
set(LIBRESSL_SKIP_INSTALL ON CACHE BOOL "")
|
||||
set(OPENSSLDIR "/etc/ssl/")
|
||||
|
||||
2
externals/dynarmic
vendored
2
externals/dynarmic
vendored
Submodule externals/dynarmic updated: 424fdb5c50...07c614f91b
4
externals/microprofile/microprofileui.h
vendored
4
externals/microprofile/microprofileui.h
vendored
@@ -845,8 +845,6 @@ inline void MicroProfileDrawDetailedBars(uint32_t nWidth, uint32_t nHeight, int
|
||||
MicroProfile& S = *MicroProfileGet();
|
||||
MP_DEBUG_DUMP_RANGE();
|
||||
int nY = nBaseY - UI.nOffsetY;
|
||||
int64_t nNumBoxes = 0;
|
||||
int64_t nNumLines = 0;
|
||||
|
||||
uint32_t nFrameNext = (S.nFrameCurrent+1) % MICROPROFILE_MAX_FRAME_HISTORY;
|
||||
MicroProfileFrameState* pFrameCurrent = &S.Frames[S.nFrameCurrent];
|
||||
@@ -1149,7 +1147,6 @@ inline void MicroProfileDrawDetailedBars(uint32_t nWidth, uint32_t nHeight, int
|
||||
}
|
||||
}
|
||||
#endif
|
||||
++nNumBoxes;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1165,7 +1162,6 @@ inline void MicroProfileDrawDetailedBars(uint32_t nWidth, uint32_t nHeight, int
|
||||
}
|
||||
nLinesDrawn[nStackPos] = nLineX;
|
||||
MicroProfileDrawLineVertical(nLineX, fYStart + 0.5f, fYEnd + 0.5f, nColor|UI.nOpacityForeground);
|
||||
++nNumLines;
|
||||
}
|
||||
}
|
||||
nStackPos--;
|
||||
|
||||
@@ -460,21 +460,23 @@ void CommandBuffer::GenerateDeviceSinkCommand(const s32 node_id, const s16 buffe
|
||||
|
||||
cmd.session_id = session_id;
|
||||
|
||||
cmd.input_count = parameter.input_count;
|
||||
s16 max_input{0};
|
||||
for (u32 i = 0; i < parameter.input_count; i++) {
|
||||
cmd.inputs[i] = buffer_offset + parameter.inputs[i];
|
||||
max_input = std::max(max_input, cmd.inputs[i]);
|
||||
}
|
||||
|
||||
if (state.upsampler_info != nullptr) {
|
||||
const auto size_{state.upsampler_info->sample_count * parameter.input_count};
|
||||
const auto size_bytes{size_ * sizeof(s32)};
|
||||
const auto addr{memory_pool->Translate(state.upsampler_info->samples_pos, size_bytes)};
|
||||
cmd.sample_buffer = {reinterpret_cast<s32*>(addr),
|
||||
parameter.input_count * state.upsampler_info->sample_count};
|
||||
(max_input + 1) * state.upsampler_info->sample_count};
|
||||
} else {
|
||||
cmd.sample_buffer = samples_buffer;
|
||||
}
|
||||
|
||||
cmd.input_count = parameter.input_count;
|
||||
for (u32 i = 0; i < parameter.input_count; i++) {
|
||||
cmd.inputs[i] = buffer_offset + parameter.inputs[i];
|
||||
}
|
||||
|
||||
GenerateEnd<DeviceSinkCommand>(cmd);
|
||||
}
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ void PerformanceManager::CreateImpl(const size_t version) {
|
||||
impl = std::make_unique<
|
||||
PerformanceManagerImpl<PerformanceVersion::Version1, PerformanceFrameHeaderVersion1,
|
||||
PerformanceEntryVersion1, PerformanceDetailVersion1>>();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -34,6 +34,8 @@ add_library(common STATIC
|
||||
bit_util.h
|
||||
cityhash.cpp
|
||||
cityhash.h
|
||||
cache_management.cpp
|
||||
cache_management.h
|
||||
common_funcs.h
|
||||
common_types.h
|
||||
concepts.h
|
||||
|
||||
@@ -156,6 +156,7 @@ AE_FORCEINLINE void compiler_fence(memory_order order) AE_NO_TSAN {
|
||||
break;
|
||||
default:
|
||||
assert(false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
60
src/common/cache_management.cpp
Normal file
60
src/common/cache_management.cpp
Normal file
@@ -0,0 +1,60 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <cstring>
|
||||
|
||||
#include "alignment.h"
|
||||
#include "cache_management.h"
|
||||
#include "common_types.h"
|
||||
|
||||
namespace Common {
|
||||
|
||||
#if defined(ARCHITECTURE_x86_64)
|
||||
|
||||
// Most cache operations are no-ops on x86
|
||||
|
||||
void DataCacheLineCleanByVAToPoU(void* start, size_t size) {}
|
||||
void DataCacheLineCleanAndInvalidateByVAToPoC(void* start, size_t size) {}
|
||||
void DataCacheLineCleanByVAToPoC(void* start, size_t size) {}
|
||||
void DataCacheZeroByVA(void* start, size_t size) {
|
||||
std::memset(start, 0, size);
|
||||
}
|
||||
|
||||
#elif defined(ARCHITECTURE_arm64)
|
||||
|
||||
// BS/DminLine is log2(cache size in words), we want size in bytes
|
||||
#define EXTRACT_DMINLINE(ctr_el0) (1 << ((((ctr_el0) >> 16) & 0xf) + 2))
|
||||
#define EXTRACT_BS(dczid_el0) (1 << (((dczid_el0)&0xf) + 2))
|
||||
|
||||
#define DEFINE_DC_OP(op_name, function_name) \
|
||||
void function_name(void* start, size_t size) { \
|
||||
size_t ctr_el0; \
|
||||
asm volatile("mrs %[ctr_el0], ctr_el0\n\t" : [ctr_el0] "=r"(ctr_el0)); \
|
||||
size_t cacheline_size = EXTRACT_DMINLINE(ctr_el0); \
|
||||
uintptr_t va_start = reinterpret_cast<uintptr_t>(start); \
|
||||
uintptr_t va_end = va_start + size; \
|
||||
for (uintptr_t va = va_start; va < va_end; va += cacheline_size) { \
|
||||
asm volatile("dc " #op_name ", %[va]\n\t" : : [va] "r"(va) : "memory"); \
|
||||
} \
|
||||
}
|
||||
|
||||
#define DEFINE_DC_OP_DCZID(op_name, function_name) \
|
||||
void function_name(void* start, size_t size) { \
|
||||
size_t dczid_el0; \
|
||||
asm volatile("mrs %[dczid_el0], dczid_el0\n\t" : [dczid_el0] "=r"(dczid_el0)); \
|
||||
size_t cacheline_size = EXTRACT_BS(dczid_el0); \
|
||||
uintptr_t va_start = reinterpret_cast<uintptr_t>(start); \
|
||||
uintptr_t va_end = va_start + size; \
|
||||
for (uintptr_t va = va_start; va < va_end; va += cacheline_size) { \
|
||||
asm volatile("dc " #op_name ", %[va]\n\t" : : [va] "r"(va) : "memory"); \
|
||||
} \
|
||||
}
|
||||
|
||||
DEFINE_DC_OP(cvau, DataCacheLineCleanByVAToPoU);
|
||||
DEFINE_DC_OP(civac, DataCacheLineCleanAndInvalidateByVAToPoC);
|
||||
DEFINE_DC_OP(cvac, DataCacheLineCleanByVAToPoC);
|
||||
DEFINE_DC_OP_DCZID(zva, DataCacheZeroByVA);
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace Common
|
||||
27
src/common/cache_management.h
Normal file
27
src/common/cache_management.h
Normal file
@@ -0,0 +1,27 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "stdlib.h"
|
||||
|
||||
namespace Common {
|
||||
|
||||
// Data cache instructions enabled at EL0 by SCTLR_EL1.UCI.
|
||||
// VA = virtual address
|
||||
// PoC = point of coherency
|
||||
// PoU = point of unification
|
||||
|
||||
// dc cvau
|
||||
void DataCacheLineCleanByVAToPoU(void* start, size_t size);
|
||||
|
||||
// dc civac
|
||||
void DataCacheLineCleanAndInvalidateByVAToPoC(void* start, size_t size);
|
||||
|
||||
// dc cvac
|
||||
void DataCacheLineCleanByVAToPoC(void* start, size_t size);
|
||||
|
||||
// dc zva
|
||||
void DataCacheZeroByVA(void* start, size_t size);
|
||||
|
||||
} // namespace Common
|
||||
@@ -530,6 +530,11 @@ add_library(core STATIC
|
||||
hle/service/ncm/ncm.h
|
||||
hle/service/nfc/nfc.cpp
|
||||
hle/service/nfc/nfc.h
|
||||
hle/service/nfc/nfc_device.cpp
|
||||
hle/service/nfc/nfc_device.h
|
||||
hle/service/nfc/nfc_result.h
|
||||
hle/service/nfc/nfc_user.cpp
|
||||
hle/service/nfc/nfc_user.h
|
||||
hle/service/nfp/amiibo_crypto.cpp
|
||||
hle/service/nfp/amiibo_crypto.h
|
||||
hle/service/nfp/nfp.cpp
|
||||
|
||||
@@ -348,7 +348,6 @@ std::shared_ptr<Dynarmic::A64::Jit> ARM_Dynarmic_64::MakeJit(Common::PageTable*
|
||||
if (Settings::values.cpu_accuracy.GetValue() == Settings::CPUAccuracy::Auto) {
|
||||
config.unsafe_optimizations = true;
|
||||
config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_UnfuseFMA;
|
||||
config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_InaccurateNaN;
|
||||
config.fastmem_address_space_bits = 64;
|
||||
config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_IgnoreGlobalMonitor;
|
||||
}
|
||||
|
||||
@@ -27,12 +27,21 @@ static void AsyncReceiveInto(Readable& r, Buffer& buffer, Callback&& c) {
|
||||
const u8* buffer_start = reinterpret_cast<const u8*>(&buffer);
|
||||
std::span<const u8> received_data{buffer_start, buffer_start + bytes_read};
|
||||
c(received_data);
|
||||
AsyncReceiveInto(r, buffer, c);
|
||||
}
|
||||
|
||||
AsyncReceiveInto(r, buffer, c);
|
||||
});
|
||||
}
|
||||
|
||||
template <typename Callback>
|
||||
static void AsyncAccept(boost::asio::ip::tcp::acceptor& acceptor, Callback&& c) {
|
||||
acceptor.async_accept([&, c](const boost::system::error_code& error, auto&& peer_socket) {
|
||||
if (!error.failed()) {
|
||||
c(peer_socket);
|
||||
AsyncAccept(acceptor, c);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
template <typename Readable, typename Buffer>
|
||||
static std::span<const u8> ReceiveInto(Readable& r, Buffer& buffer) {
|
||||
static_assert(std::is_trivial_v<Buffer>);
|
||||
@@ -59,9 +68,7 @@ namespace Core {
|
||||
|
||||
class DebuggerImpl : public DebuggerBackend {
|
||||
public:
|
||||
explicit DebuggerImpl(Core::System& system_, u16 port)
|
||||
: system{system_}, signal_pipe{io_context}, client_socket{io_context} {
|
||||
frontend = std::make_unique<GDBStub>(*this, system);
|
||||
explicit DebuggerImpl(Core::System& system_, u16 port) : system{system_} {
|
||||
InitializeServer(port);
|
||||
}
|
||||
|
||||
@@ -70,39 +77,42 @@ public:
|
||||
}
|
||||
|
||||
bool SignalDebugger(SignalInfo signal_info) {
|
||||
{
|
||||
std::scoped_lock lk{connection_lock};
|
||||
std::scoped_lock lk{connection_lock};
|
||||
|
||||
if (stopped) {
|
||||
// Do not notify the debugger about another event.
|
||||
// It should be ignored.
|
||||
return false;
|
||||
}
|
||||
|
||||
// Set up the state.
|
||||
stopped = true;
|
||||
info = signal_info;
|
||||
if (stopped || !state) {
|
||||
// Do not notify the debugger about another event.
|
||||
// It should be ignored.
|
||||
return false;
|
||||
}
|
||||
|
||||
// Set up the state.
|
||||
stopped = true;
|
||||
state->info = signal_info;
|
||||
|
||||
// Write a single byte into the pipe to wake up the debug interface.
|
||||
boost::asio::write(signal_pipe, boost::asio::buffer(&stopped, sizeof(stopped)));
|
||||
boost::asio::write(state->signal_pipe, boost::asio::buffer(&stopped, sizeof(stopped)));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// These functions are callbacks from the frontend, and the lock will be held.
|
||||
// There is no need to relock it.
|
||||
|
||||
std::span<const u8> ReadFromClient() override {
|
||||
return ReceiveInto(client_socket, client_data);
|
||||
return ReceiveInto(state->client_socket, state->client_data);
|
||||
}
|
||||
|
||||
void WriteToClient(std::span<const u8> data) override {
|
||||
boost::asio::write(client_socket, boost::asio::buffer(data.data(), data.size_bytes()));
|
||||
boost::asio::write(state->client_socket,
|
||||
boost::asio::buffer(data.data(), data.size_bytes()));
|
||||
}
|
||||
|
||||
void SetActiveThread(Kernel::KThread* thread) override {
|
||||
active_thread = thread;
|
||||
state->active_thread = thread;
|
||||
}
|
||||
|
||||
Kernel::KThread* GetActiveThread() override {
|
||||
return active_thread;
|
||||
return state->active_thread;
|
||||
}
|
||||
|
||||
private:
|
||||
@@ -113,65 +123,78 @@ private:
|
||||
|
||||
// Run the connection thread.
|
||||
connection_thread = std::jthread([&, port](std::stop_token stop_token) {
|
||||
Common::SetCurrentThreadName("Debugger");
|
||||
|
||||
try {
|
||||
// Initialize the listening socket and accept a new client.
|
||||
tcp::endpoint endpoint{boost::asio::ip::address_v4::any(), port};
|
||||
tcp::acceptor acceptor{io_context, endpoint};
|
||||
|
||||
acceptor.async_accept(client_socket, [](const auto&) {});
|
||||
io_context.run_one();
|
||||
io_context.restart();
|
||||
AsyncAccept(acceptor, [&](auto&& peer) { AcceptConnection(std::move(peer)); });
|
||||
|
||||
if (stop_token.stop_requested()) {
|
||||
return;
|
||||
while (!stop_token.stop_requested() && io_context.run()) {
|
||||
}
|
||||
|
||||
ThreadLoop(stop_token);
|
||||
} catch (const std::exception& ex) {
|
||||
LOG_CRITICAL(Debug_GDBStub, "Stopping server: {}", ex.what());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void AcceptConnection(boost::asio::ip::tcp::socket&& peer) {
|
||||
LOG_INFO(Debug_GDBStub, "Accepting new peer connection");
|
||||
|
||||
std::scoped_lock lk{connection_lock};
|
||||
|
||||
// Ensure everything is stopped.
|
||||
PauseEmulation();
|
||||
|
||||
// Set up the new frontend.
|
||||
frontend = std::make_unique<GDBStub>(*this, system);
|
||||
|
||||
// Set the new state. This will tear down any existing state.
|
||||
state = ConnectionState{
|
||||
.client_socket{std::move(peer)},
|
||||
.signal_pipe{io_context},
|
||||
.info{},
|
||||
.active_thread{},
|
||||
.client_data{},
|
||||
.pipe_data{},
|
||||
};
|
||||
|
||||
// Set up the client signals for new data.
|
||||
AsyncReceiveInto(state->signal_pipe, state->pipe_data, [&](auto d) { PipeData(d); });
|
||||
AsyncReceiveInto(state->client_socket, state->client_data, [&](auto d) { ClientData(d); });
|
||||
|
||||
// Set the active thread.
|
||||
UpdateActiveThread();
|
||||
|
||||
// Set up the frontend.
|
||||
frontend->Connected();
|
||||
}
|
||||
|
||||
void ShutdownServer() {
|
||||
connection_thread.request_stop();
|
||||
io_context.stop();
|
||||
connection_thread.join();
|
||||
}
|
||||
|
||||
void ThreadLoop(std::stop_token stop_token) {
|
||||
Common::SetCurrentThreadName("Debugger");
|
||||
|
||||
// Set up the client signals for new data.
|
||||
AsyncReceiveInto(signal_pipe, pipe_data, [&](auto d) { PipeData(d); });
|
||||
AsyncReceiveInto(client_socket, client_data, [&](auto d) { ClientData(d); });
|
||||
|
||||
// Set the active thread.
|
||||
UpdateActiveThread();
|
||||
|
||||
// Set up the frontend.
|
||||
frontend->Connected();
|
||||
|
||||
// Main event loop.
|
||||
while (!stop_token.stop_requested() && io_context.run()) {
|
||||
}
|
||||
}
|
||||
|
||||
void PipeData(std::span<const u8> data) {
|
||||
switch (info.type) {
|
||||
std::scoped_lock lk{connection_lock};
|
||||
|
||||
switch (state->info.type) {
|
||||
case SignalType::Stopped:
|
||||
case SignalType::Watchpoint:
|
||||
// Stop emulation.
|
||||
PauseEmulation();
|
||||
|
||||
// Notify the client.
|
||||
active_thread = info.thread;
|
||||
state->active_thread = state->info.thread;
|
||||
UpdateActiveThread();
|
||||
|
||||
if (info.type == SignalType::Watchpoint) {
|
||||
frontend->Watchpoint(active_thread, *info.watchpoint);
|
||||
if (state->info.type == SignalType::Watchpoint) {
|
||||
frontend->Watchpoint(state->active_thread, *state->info.watchpoint);
|
||||
} else {
|
||||
frontend->Stopped(active_thread);
|
||||
frontend->Stopped(state->active_thread);
|
||||
}
|
||||
|
||||
break;
|
||||
@@ -179,8 +202,8 @@ private:
|
||||
frontend->ShuttingDown();
|
||||
|
||||
// Wait for emulation to shut down gracefully now.
|
||||
signal_pipe.close();
|
||||
client_socket.shutdown(boost::asio::socket_base::shutdown_both);
|
||||
state->signal_pipe.close();
|
||||
state->client_socket.shutdown(boost::asio::socket_base::shutdown_both);
|
||||
LOG_INFO(Debug_GDBStub, "Shut down server");
|
||||
|
||||
break;
|
||||
@@ -188,17 +211,16 @@ private:
|
||||
}
|
||||
|
||||
void ClientData(std::span<const u8> data) {
|
||||
std::scoped_lock lk{connection_lock};
|
||||
|
||||
const auto actions{frontend->ClientData(data)};
|
||||
for (const auto action : actions) {
|
||||
switch (action) {
|
||||
case DebuggerAction::Interrupt: {
|
||||
{
|
||||
std::scoped_lock lk{connection_lock};
|
||||
stopped = true;
|
||||
}
|
||||
stopped = true;
|
||||
PauseEmulation();
|
||||
UpdateActiveThread();
|
||||
frontend->Stopped(active_thread);
|
||||
frontend->Stopped(state->active_thread);
|
||||
break;
|
||||
}
|
||||
case DebuggerAction::Continue:
|
||||
@@ -206,15 +228,15 @@ private:
|
||||
break;
|
||||
case DebuggerAction::StepThreadUnlocked:
|
||||
MarkResumed([&] {
|
||||
active_thread->SetStepState(Kernel::StepState::StepPending);
|
||||
active_thread->Resume(Kernel::SuspendType::Debug);
|
||||
ResumeEmulation(active_thread);
|
||||
state->active_thread->SetStepState(Kernel::StepState::StepPending);
|
||||
state->active_thread->Resume(Kernel::SuspendType::Debug);
|
||||
ResumeEmulation(state->active_thread);
|
||||
});
|
||||
break;
|
||||
case DebuggerAction::StepThreadLocked: {
|
||||
MarkResumed([&] {
|
||||
active_thread->SetStepState(Kernel::StepState::StepPending);
|
||||
active_thread->Resume(Kernel::SuspendType::Debug);
|
||||
state->active_thread->SetStepState(Kernel::StepState::StepPending);
|
||||
state->active_thread->Resume(Kernel::SuspendType::Debug);
|
||||
});
|
||||
break;
|
||||
}
|
||||
@@ -254,15 +276,14 @@ private:
|
||||
template <typename Callback>
|
||||
void MarkResumed(Callback&& cb) {
|
||||
Kernel::KScopedSchedulerLock sl{system.Kernel()};
|
||||
std::scoped_lock cl{connection_lock};
|
||||
stopped = false;
|
||||
cb();
|
||||
}
|
||||
|
||||
void UpdateActiveThread() {
|
||||
const auto& threads{ThreadList()};
|
||||
if (std::find(threads.begin(), threads.end(), active_thread) == threads.end()) {
|
||||
active_thread = threads[0];
|
||||
if (std::find(threads.begin(), threads.end(), state->active_thread) == threads.end()) {
|
||||
state->active_thread = threads[0];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -274,18 +295,22 @@ private:
|
||||
System& system;
|
||||
std::unique_ptr<DebuggerFrontend> frontend;
|
||||
|
||||
boost::asio::io_context io_context;
|
||||
std::jthread connection_thread;
|
||||
std::mutex connection_lock;
|
||||
boost::asio::io_context io_context;
|
||||
boost::process::async_pipe signal_pipe;
|
||||
boost::asio::ip::tcp::socket client_socket;
|
||||
|
||||
SignalInfo info;
|
||||
Kernel::KThread* active_thread;
|
||||
bool pipe_data;
|
||||
bool stopped;
|
||||
struct ConnectionState {
|
||||
boost::asio::ip::tcp::socket client_socket;
|
||||
boost::process::async_pipe signal_pipe;
|
||||
|
||||
std::array<u8, 4096> client_data;
|
||||
SignalInfo info;
|
||||
Kernel::KThread* active_thread;
|
||||
std::array<u8, 4096> client_data;
|
||||
bool pipe_data;
|
||||
};
|
||||
|
||||
std::optional<ConnectionState> state{};
|
||||
bool stopped{};
|
||||
};
|
||||
|
||||
Debugger::Debugger(Core::System& system, u16 port) {
|
||||
|
||||
@@ -606,6 +606,8 @@ void GDBStub::HandleQuery(std::string_view command) {
|
||||
} else if (command.starts_with("StartNoAckMode")) {
|
||||
no_ack = true;
|
||||
SendReply(GDB_STUB_REPLY_OK);
|
||||
} else if (command.starts_with("Rcmd,")) {
|
||||
HandleRcmd(Common::HexStringToVector(command.substr(5), false));
|
||||
} else {
|
||||
SendReply(GDB_STUB_REPLY_EMPTY);
|
||||
}
|
||||
@@ -645,6 +647,155 @@ void GDBStub::HandleVCont(std::string_view command, std::vector<DebuggerAction>&
|
||||
}
|
||||
}
|
||||
|
||||
constexpr std::array<std::pair<const char*, Kernel::Svc::MemoryState>, 22> MemoryStateNames{{
|
||||
{"----- Free -----", Kernel::Svc::MemoryState::Free},
|
||||
{"Io ", Kernel::Svc::MemoryState::Io},
|
||||
{"Static ", Kernel::Svc::MemoryState::Static},
|
||||
{"Code ", Kernel::Svc::MemoryState::Code},
|
||||
{"CodeData ", Kernel::Svc::MemoryState::CodeData},
|
||||
{"Normal ", Kernel::Svc::MemoryState::Normal},
|
||||
{"Shared ", Kernel::Svc::MemoryState::Shared},
|
||||
{"AliasCode ", Kernel::Svc::MemoryState::AliasCode},
|
||||
{"AliasCodeData ", Kernel::Svc::MemoryState::AliasCodeData},
|
||||
{"Ipc ", Kernel::Svc::MemoryState::Ipc},
|
||||
{"Stack ", Kernel::Svc::MemoryState::Stack},
|
||||
{"ThreadLocal ", Kernel::Svc::MemoryState::ThreadLocal},
|
||||
{"Transfered ", Kernel::Svc::MemoryState::Transfered},
|
||||
{"SharedTransfered", Kernel::Svc::MemoryState::SharedTransfered},
|
||||
{"SharedCode ", Kernel::Svc::MemoryState::SharedCode},
|
||||
{"Inaccessible ", Kernel::Svc::MemoryState::Inaccessible},
|
||||
{"NonSecureIpc ", Kernel::Svc::MemoryState::NonSecureIpc},
|
||||
{"NonDeviceIpc ", Kernel::Svc::MemoryState::NonDeviceIpc},
|
||||
{"Kernel ", Kernel::Svc::MemoryState::Kernel},
|
||||
{"GeneratedCode ", Kernel::Svc::MemoryState::GeneratedCode},
|
||||
{"CodeOut ", Kernel::Svc::MemoryState::CodeOut},
|
||||
{"Coverage ", Kernel::Svc::MemoryState::Coverage},
|
||||
}};
|
||||
|
||||
static constexpr const char* GetMemoryStateName(Kernel::Svc::MemoryState state) {
|
||||
for (size_t i = 0; i < MemoryStateNames.size(); i++) {
|
||||
if (std::get<1>(MemoryStateNames[i]) == state) {
|
||||
return std::get<0>(MemoryStateNames[i]);
|
||||
}
|
||||
}
|
||||
return "Unknown ";
|
||||
}
|
||||
|
||||
static constexpr const char* GetMemoryPermissionString(const Kernel::Svc::MemoryInfo& info) {
|
||||
if (info.state == Kernel::Svc::MemoryState::Free) {
|
||||
return " ";
|
||||
}
|
||||
|
||||
switch (info.permission) {
|
||||
case Kernel::Svc::MemoryPermission::ReadExecute:
|
||||
return "r-x";
|
||||
case Kernel::Svc::MemoryPermission::Read:
|
||||
return "r--";
|
||||
case Kernel::Svc::MemoryPermission::ReadWrite:
|
||||
return "rw-";
|
||||
default:
|
||||
return "---";
|
||||
}
|
||||
}
|
||||
|
||||
static VAddr GetModuleEnd(Kernel::KPageTable& page_table, VAddr base) {
|
||||
Kernel::Svc::MemoryInfo mem_info;
|
||||
VAddr cur_addr{base};
|
||||
|
||||
// Expect: r-x Code (.text)
|
||||
mem_info = page_table.QueryInfo(cur_addr).GetSvcMemoryInfo();
|
||||
cur_addr = mem_info.base_address + mem_info.size;
|
||||
if (mem_info.state != Kernel::Svc::MemoryState::Code ||
|
||||
mem_info.permission != Kernel::Svc::MemoryPermission::ReadExecute) {
|
||||
return cur_addr - 1;
|
||||
}
|
||||
|
||||
// Expect: r-- Code (.rodata)
|
||||
mem_info = page_table.QueryInfo(cur_addr).GetSvcMemoryInfo();
|
||||
cur_addr = mem_info.base_address + mem_info.size;
|
||||
if (mem_info.state != Kernel::Svc::MemoryState::Code ||
|
||||
mem_info.permission != Kernel::Svc::MemoryPermission::Read) {
|
||||
return cur_addr - 1;
|
||||
}
|
||||
|
||||
// Expect: rw- CodeData (.data)
|
||||
mem_info = page_table.QueryInfo(cur_addr).GetSvcMemoryInfo();
|
||||
cur_addr = mem_info.base_address + mem_info.size;
|
||||
return cur_addr - 1;
|
||||
}
|
||||
|
||||
void GDBStub::HandleRcmd(const std::vector<u8>& command) {
|
||||
std::string_view command_str{reinterpret_cast<const char*>(&command[0]), command.size()};
|
||||
std::string reply;
|
||||
|
||||
auto* process = system.CurrentProcess();
|
||||
auto& page_table = process->PageTable();
|
||||
|
||||
if (command_str == "get info") {
|
||||
Loader::AppLoader::Modules modules;
|
||||
system.GetAppLoader().ReadNSOModules(modules);
|
||||
|
||||
reply = fmt::format("Process: {:#x} ({})\n"
|
||||
"Program Id: {:#018x}\n",
|
||||
process->GetProcessID(), process->GetName(), process->GetProgramID());
|
||||
reply +=
|
||||
fmt::format("Layout:\n"
|
||||
" Alias: {:#012x} - {:#012x}\n"
|
||||
" Heap: {:#012x} - {:#012x}\n"
|
||||
" Aslr: {:#012x} - {:#012x}\n"
|
||||
" Stack: {:#012x} - {:#012x}\n"
|
||||
"Modules:\n",
|
||||
page_table.GetAliasRegionStart(), page_table.GetAliasRegionEnd(),
|
||||
page_table.GetHeapRegionStart(), page_table.GetHeapRegionEnd(),
|
||||
page_table.GetAliasCodeRegionStart(), page_table.GetAliasCodeRegionEnd(),
|
||||
page_table.GetStackRegionStart(), page_table.GetStackRegionEnd());
|
||||
|
||||
for (const auto& [vaddr, name] : modules) {
|
||||
reply += fmt::format(" {:#012x} - {:#012x} {}\n", vaddr,
|
||||
GetModuleEnd(page_table, vaddr), name);
|
||||
}
|
||||
} else if (command_str == "get mappings") {
|
||||
reply = "Mappings:\n";
|
||||
VAddr cur_addr = 0;
|
||||
|
||||
while (true) {
|
||||
using MemoryAttribute = Kernel::Svc::MemoryAttribute;
|
||||
|
||||
auto mem_info = page_table.QueryInfo(cur_addr).GetSvcMemoryInfo();
|
||||
|
||||
if (mem_info.state != Kernel::Svc::MemoryState::Inaccessible ||
|
||||
mem_info.base_address + mem_info.size - 1 != std::numeric_limits<u64>::max()) {
|
||||
const char* state = GetMemoryStateName(mem_info.state);
|
||||
const char* perm = GetMemoryPermissionString(mem_info);
|
||||
|
||||
const char l = True(mem_info.attribute & MemoryAttribute::Locked) ? 'L' : '-';
|
||||
const char i = True(mem_info.attribute & MemoryAttribute::IpcLocked) ? 'I' : '-';
|
||||
const char d = True(mem_info.attribute & MemoryAttribute::DeviceShared) ? 'D' : '-';
|
||||
const char u = True(mem_info.attribute & MemoryAttribute::Uncached) ? 'U' : '-';
|
||||
|
||||
reply +=
|
||||
fmt::format(" {:#012x} - {:#012x} {} {} {}{}{}{} [{}, {}]\n",
|
||||
mem_info.base_address, mem_info.base_address + mem_info.size - 1,
|
||||
perm, state, l, i, d, u, mem_info.ipc_count, mem_info.device_count);
|
||||
}
|
||||
|
||||
const uintptr_t next_address = mem_info.base_address + mem_info.size;
|
||||
if (next_address <= cur_addr) {
|
||||
break;
|
||||
}
|
||||
|
||||
cur_addr = next_address;
|
||||
}
|
||||
} else if (command_str == "help") {
|
||||
reply = "Commands:\n get info\n get mappings\n";
|
||||
} else {
|
||||
reply = "Unknown command.\nCommands:\n get info\n get mappings\n";
|
||||
}
|
||||
|
||||
std::span<const u8> reply_span{reinterpret_cast<u8*>(&reply.front()), reply.size()};
|
||||
SendReply(Common::HexToString(reply_span, false));
|
||||
}
|
||||
|
||||
Kernel::KThread* GDBStub::GetThreadByID(u64 thread_id) {
|
||||
const auto& threads{system.GlobalSchedulerContext().GetThreadList()};
|
||||
for (auto* thread : threads) {
|
||||
|
||||
@@ -32,6 +32,7 @@ private:
|
||||
void ExecuteCommand(std::string_view packet, std::vector<DebuggerAction>& actions);
|
||||
void HandleVCont(std::string_view command, std::vector<DebuggerAction>& actions);
|
||||
void HandleQuery(std::string_view command);
|
||||
void HandleRcmd(const std::vector<u8>& command);
|
||||
void HandleBreakpointInsert(std::string_view command);
|
||||
void HandleBreakpointRemove(std::string_view command);
|
||||
std::vector<char>::const_iterator CommandEnd() const;
|
||||
|
||||
@@ -19,27 +19,26 @@ void EmulatedConsole::ReloadFromSettings() {
|
||||
}
|
||||
|
||||
void EmulatedConsole::SetTouchParams() {
|
||||
// TODO(german77): Support any number of fingers
|
||||
std::size_t index = 0;
|
||||
|
||||
// Hardcode mouse, touchscreen and cemuhook parameters
|
||||
// We can't use mouse as touch if native mouse is enabled
|
||||
if (!Settings::values.mouse_enabled) {
|
||||
// We can't use mouse as touch if native mouse is enabled
|
||||
touch_params[index++] = Common::ParamPackage{"engine:mouse,axis_x:10,axis_y:11,button:0"};
|
||||
}
|
||||
|
||||
touch_params[index++] =
|
||||
Common::ParamPackage{"engine:touch,axis_x:0,axis_y:1,button:0,touch_id:0"};
|
||||
Common::ParamPackage{"engine:cemuhookudp,axis_x:17,axis_y:18,button:65536"};
|
||||
touch_params[index++] =
|
||||
Common::ParamPackage{"engine:touch,axis_x:2,axis_y:3,button:1,touch_id:1"};
|
||||
touch_params[index++] =
|
||||
Common::ParamPackage{"engine:touch,axis_x:4,axis_y:5,button:2,touch_id:2"};
|
||||
touch_params[index++] =
|
||||
Common::ParamPackage{"engine:touch,axis_x:6,axis_y:7,button:3,touch_id:3"};
|
||||
touch_params[index++] =
|
||||
Common::ParamPackage{"engine:cemuhookudp,axis_x:17,axis_y:18,button:65536,touch_id:0"};
|
||||
touch_params[index++] =
|
||||
Common::ParamPackage{"engine:cemuhookudp,axis_x:19,axis_y:20,button:131072,touch_id:1"};
|
||||
Common::ParamPackage{"engine:cemuhookudp,axis_x:19,axis_y:20,button:131072"};
|
||||
|
||||
for (int i = 0; i < static_cast<int>(MaxActiveTouchInputs); i++) {
|
||||
Common::ParamPackage touchscreen_param{};
|
||||
touchscreen_param.Set("engine", "touch");
|
||||
touchscreen_param.Set("axis_x", i * 2);
|
||||
touchscreen_param.Set("axis_y", (i * 2) + 1);
|
||||
touchscreen_param.Set("button", i);
|
||||
touch_params[index++] = touchscreen_param;
|
||||
}
|
||||
|
||||
const auto button_index =
|
||||
static_cast<u64>(Settings::values.touch_from_button_map_index.GetValue());
|
||||
@@ -47,7 +46,7 @@ void EmulatedConsole::SetTouchParams() {
|
||||
|
||||
// Map the rest of the fingers from touch from button configuration
|
||||
for (const auto& config_entry : touch_buttons) {
|
||||
if (index >= touch_params.size()) {
|
||||
if (index >= MaxTouchDevices) {
|
||||
continue;
|
||||
}
|
||||
Common::ParamPackage params{config_entry};
|
||||
@@ -60,7 +59,6 @@ void EmulatedConsole::SetTouchParams() {
|
||||
touch_button_params.Set("button", params.Serialize());
|
||||
touch_button_params.Set("x", x);
|
||||
touch_button_params.Set("y", y);
|
||||
touch_button_params.Set("touch_id", static_cast<int>(index));
|
||||
touch_params[index] = touch_button_params;
|
||||
index++;
|
||||
}
|
||||
@@ -178,12 +176,38 @@ void EmulatedConsole::SetMotion(const Common::Input::CallbackStatus& callback) {
|
||||
}
|
||||
|
||||
void EmulatedConsole::SetTouch(const Common::Input::CallbackStatus& callback, std::size_t index) {
|
||||
if (index >= console.touch_values.size()) {
|
||||
if (index >= MaxTouchDevices) {
|
||||
return;
|
||||
}
|
||||
std::unique_lock lock{mutex};
|
||||
|
||||
console.touch_values[index] = TransformToTouch(callback);
|
||||
const auto touch_input = TransformToTouch(callback);
|
||||
auto touch_index = GetIndexFromFingerId(index);
|
||||
bool is_new_input = false;
|
||||
|
||||
if (!touch_index.has_value() && touch_input.pressed.value) {
|
||||
touch_index = GetNextFreeIndex();
|
||||
is_new_input = true;
|
||||
}
|
||||
|
||||
// No free entries or invalid state. Ignore input
|
||||
if (!touch_index.has_value()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto& touch_value = console.touch_values[touch_index.value()];
|
||||
|
||||
if (is_new_input) {
|
||||
touch_value.pressed.value = true;
|
||||
touch_value.id = static_cast<u32>(index);
|
||||
}
|
||||
|
||||
touch_value.x = touch_input.x;
|
||||
touch_value.y = touch_input.y;
|
||||
|
||||
if (!touch_input.pressed.value) {
|
||||
touch_value.pressed.value = false;
|
||||
}
|
||||
|
||||
if (is_configuring) {
|
||||
lock.unlock();
|
||||
@@ -191,11 +215,15 @@ void EmulatedConsole::SetTouch(const Common::Input::CallbackStatus& callback, st
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO(german77): Remap touch id in sequential order
|
||||
console.touch_state[index] = {
|
||||
.position = {console.touch_values[index].x.value, console.touch_values[index].y.value},
|
||||
.id = static_cast<u32>(console.touch_values[index].id),
|
||||
.pressed = console.touch_values[index].pressed.value,
|
||||
// Touch outside allowed range. Ignore input
|
||||
if (touch_index.value() >= MaxActiveTouchInputs) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.touch_state[touch_index.value()] = {
|
||||
.position = {touch_value.x.value, touch_value.y.value},
|
||||
.id = static_cast<u32>(touch_index.value()),
|
||||
.pressed = touch_input.pressed.value,
|
||||
};
|
||||
|
||||
lock.unlock();
|
||||
@@ -222,6 +250,28 @@ TouchFingerState EmulatedConsole::GetTouch() const {
|
||||
return console.touch_state;
|
||||
}
|
||||
|
||||
std::optional<std::size_t> EmulatedConsole::GetIndexFromFingerId(std::size_t finger_id) const {
|
||||
for (std::size_t index = 0; index < MaxTouchDevices; ++index) {
|
||||
const auto& finger = console.touch_values[index];
|
||||
if (!finger.pressed.value) {
|
||||
continue;
|
||||
}
|
||||
if (finger.id == static_cast<int>(finger_id)) {
|
||||
return index;
|
||||
}
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<std::size_t> EmulatedConsole::GetNextFreeIndex() const {
|
||||
for (std::size_t index = 0; index < MaxTouchDevices; ++index) {
|
||||
if (!console.touch_values[index].pressed.value) {
|
||||
return index;
|
||||
}
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
void EmulatedConsole::TriggerOnChange(ConsoleTriggerType type) {
|
||||
std::scoped_lock lock{callback_mutex};
|
||||
for (const auto& poller_pair : callback_list) {
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "common/common_funcs.h"
|
||||
@@ -20,6 +21,8 @@
|
||||
#include "core/hid/motion_input.h"
|
||||
|
||||
namespace Core::HID {
|
||||
static constexpr std::size_t MaxTouchDevices = 32;
|
||||
static constexpr std::size_t MaxActiveTouchInputs = 16;
|
||||
|
||||
struct ConsoleMotionInfo {
|
||||
Common::Input::MotionStatus raw_status{};
|
||||
@@ -27,13 +30,13 @@ struct ConsoleMotionInfo {
|
||||
};
|
||||
|
||||
using ConsoleMotionDevices = std::unique_ptr<Common::Input::InputDevice>;
|
||||
using TouchDevices = std::array<std::unique_ptr<Common::Input::InputDevice>, 16>;
|
||||
using TouchDevices = std::array<std::unique_ptr<Common::Input::InputDevice>, MaxTouchDevices>;
|
||||
|
||||
using ConsoleMotionParams = Common::ParamPackage;
|
||||
using TouchParams = std::array<Common::ParamPackage, 16>;
|
||||
using TouchParams = std::array<Common::ParamPackage, MaxTouchDevices>;
|
||||
|
||||
using ConsoleMotionValues = ConsoleMotionInfo;
|
||||
using TouchValues = std::array<Common::Input::TouchStatus, 16>;
|
||||
using TouchValues = std::array<Common::Input::TouchStatus, MaxTouchDevices>;
|
||||
|
||||
struct TouchFinger {
|
||||
u64 last_touch{};
|
||||
@@ -55,7 +58,7 @@ struct ConsoleMotion {
|
||||
bool is_at_rest{};
|
||||
};
|
||||
|
||||
using TouchFingerState = std::array<TouchFinger, 16>;
|
||||
using TouchFingerState = std::array<TouchFinger, MaxActiveTouchInputs>;
|
||||
|
||||
struct ConsoleStatus {
|
||||
// Data from input_common
|
||||
@@ -166,6 +169,10 @@ private:
|
||||
*/
|
||||
void SetTouch(const Common::Input::CallbackStatus& callback, std::size_t index);
|
||||
|
||||
std::optional<std::size_t> GetIndexFromFingerId(std::size_t finger_id) const;
|
||||
|
||||
std::optional<std::size_t> GetNextFreeIndex() const;
|
||||
|
||||
/**
|
||||
* Triggers a callback that something has changed on the console status
|
||||
* @param type Input type of the event to trigger
|
||||
|
||||
@@ -200,9 +200,6 @@ Common::Input::TouchStatus TransformToTouch(const Common::Input::CallbackStatus&
|
||||
x = std::clamp(x, 0.0f, 1.0f);
|
||||
y = std::clamp(y, 0.0f, 1.0f);
|
||||
|
||||
// Limit id to maximum number of fingers
|
||||
status.id = std::clamp(status.id, 0, 16);
|
||||
|
||||
if (status.pressed.inverted) {
|
||||
status.pressed.value = !status.pressed.value;
|
||||
}
|
||||
|
||||
@@ -199,7 +199,7 @@ public:
|
||||
~HLERequestContext();
|
||||
|
||||
/// Returns a pointer to the IPC command buffer for this request.
|
||||
u32* CommandBuffer() {
|
||||
[[nodiscard]] u32* CommandBuffer() {
|
||||
return cmd_buf.data();
|
||||
}
|
||||
|
||||
@@ -207,7 +207,7 @@ public:
|
||||
* Returns the session through which this request was made. This can be used as a map key to
|
||||
* access per-client data on services.
|
||||
*/
|
||||
Kernel::KServerSession* Session() {
|
||||
[[nodiscard]] Kernel::KServerSession* Session() {
|
||||
return server_session;
|
||||
}
|
||||
|
||||
@@ -217,61 +217,61 @@ public:
|
||||
/// Writes data from this context back to the requesting process/thread.
|
||||
Result WriteToOutgoingCommandBuffer(KThread& requesting_thread);
|
||||
|
||||
u32_le GetHipcCommand() const {
|
||||
[[nodiscard]] u32_le GetHipcCommand() const {
|
||||
return command;
|
||||
}
|
||||
|
||||
u32_le GetTipcCommand() const {
|
||||
[[nodiscard]] u32_le GetTipcCommand() const {
|
||||
return static_cast<u32_le>(command_header->type.Value()) -
|
||||
static_cast<u32_le>(IPC::CommandType::TIPC_CommandRegion);
|
||||
}
|
||||
|
||||
u32_le GetCommand() const {
|
||||
[[nodiscard]] u32_le GetCommand() const {
|
||||
return command_header->IsTipc() ? GetTipcCommand() : GetHipcCommand();
|
||||
}
|
||||
|
||||
bool IsTipc() const {
|
||||
[[nodiscard]] bool IsTipc() const {
|
||||
return command_header->IsTipc();
|
||||
}
|
||||
|
||||
IPC::CommandType GetCommandType() const {
|
||||
[[nodiscard]] IPC::CommandType GetCommandType() const {
|
||||
return command_header->type;
|
||||
}
|
||||
|
||||
u64 GetPID() const {
|
||||
[[nodiscard]] u64 GetPID() const {
|
||||
return pid;
|
||||
}
|
||||
|
||||
u32 GetDataPayloadOffset() const {
|
||||
[[nodiscard]] u32 GetDataPayloadOffset() const {
|
||||
return data_payload_offset;
|
||||
}
|
||||
|
||||
const std::vector<IPC::BufferDescriptorX>& BufferDescriptorX() const {
|
||||
[[nodiscard]] const std::vector<IPC::BufferDescriptorX>& BufferDescriptorX() const {
|
||||
return buffer_x_desciptors;
|
||||
}
|
||||
|
||||
const std::vector<IPC::BufferDescriptorABW>& BufferDescriptorA() const {
|
||||
[[nodiscard]] const std::vector<IPC::BufferDescriptorABW>& BufferDescriptorA() const {
|
||||
return buffer_a_desciptors;
|
||||
}
|
||||
|
||||
const std::vector<IPC::BufferDescriptorABW>& BufferDescriptorB() const {
|
||||
[[nodiscard]] const std::vector<IPC::BufferDescriptorABW>& BufferDescriptorB() const {
|
||||
return buffer_b_desciptors;
|
||||
}
|
||||
|
||||
const std::vector<IPC::BufferDescriptorC>& BufferDescriptorC() const {
|
||||
[[nodiscard]] const std::vector<IPC::BufferDescriptorC>& BufferDescriptorC() const {
|
||||
return buffer_c_desciptors;
|
||||
}
|
||||
|
||||
const IPC::DomainMessageHeader& GetDomainMessageHeader() const {
|
||||
[[nodiscard]] const IPC::DomainMessageHeader& GetDomainMessageHeader() const {
|
||||
return domain_message_header.value();
|
||||
}
|
||||
|
||||
bool HasDomainMessageHeader() const {
|
||||
[[nodiscard]] bool HasDomainMessageHeader() const {
|
||||
return domain_message_header.has_value();
|
||||
}
|
||||
|
||||
/// Helper function to read a buffer using the appropriate buffer descriptor
|
||||
std::vector<u8> ReadBuffer(std::size_t buffer_index = 0) const;
|
||||
[[nodiscard]] std::vector<u8> ReadBuffer(std::size_t buffer_index = 0) const;
|
||||
|
||||
/// Helper function to write a buffer using the appropriate buffer descriptor
|
||||
std::size_t WriteBuffer(const void* buffer, std::size_t size,
|
||||
@@ -308,22 +308,34 @@ public:
|
||||
}
|
||||
|
||||
/// Helper function to get the size of the input buffer
|
||||
std::size_t GetReadBufferSize(std::size_t buffer_index = 0) const;
|
||||
[[nodiscard]] std::size_t GetReadBufferSize(std::size_t buffer_index = 0) const;
|
||||
|
||||
/// Helper function to get the size of the output buffer
|
||||
std::size_t GetWriteBufferSize(std::size_t buffer_index = 0) const;
|
||||
[[nodiscard]] std::size_t GetWriteBufferSize(std::size_t buffer_index = 0) const;
|
||||
|
||||
/// Helper function to derive the number of elements able to be contained in the read buffer
|
||||
template <typename T>
|
||||
[[nodiscard]] std::size_t GetReadBufferNumElements(std::size_t buffer_index = 0) const {
|
||||
return GetReadBufferSize(buffer_index) / sizeof(T);
|
||||
}
|
||||
|
||||
/// Helper function to derive the number of elements able to be contained in the write buffer
|
||||
template <typename T>
|
||||
[[nodiscard]] std::size_t GetWriteBufferNumElements(std::size_t buffer_index = 0) const {
|
||||
return GetWriteBufferSize(buffer_index) / sizeof(T);
|
||||
}
|
||||
|
||||
/// Helper function to test whether the input buffer at buffer_index can be read
|
||||
bool CanReadBuffer(std::size_t buffer_index = 0) const;
|
||||
[[nodiscard]] bool CanReadBuffer(std::size_t buffer_index = 0) const;
|
||||
|
||||
/// Helper function to test whether the output buffer at buffer_index can be written
|
||||
bool CanWriteBuffer(std::size_t buffer_index = 0) const;
|
||||
[[nodiscard]] bool CanWriteBuffer(std::size_t buffer_index = 0) const;
|
||||
|
||||
Handle GetCopyHandle(std::size_t index) const {
|
||||
[[nodiscard]] Handle GetCopyHandle(std::size_t index) const {
|
||||
return incoming_copy_handles.at(index);
|
||||
}
|
||||
|
||||
Handle GetMoveHandle(std::size_t index) const {
|
||||
[[nodiscard]] Handle GetMoveHandle(std::size_t index) const {
|
||||
return incoming_move_handles.at(index);
|
||||
}
|
||||
|
||||
@@ -348,13 +360,13 @@ public:
|
||||
manager = manager_;
|
||||
}
|
||||
|
||||
std::string Description() const;
|
||||
[[nodiscard]] std::string Description() const;
|
||||
|
||||
KThread& GetThread() {
|
||||
[[nodiscard]] KThread& GetThread() {
|
||||
return *thread;
|
||||
}
|
||||
|
||||
std::shared_ptr<SessionRequestManager> GetManager() const {
|
||||
[[nodiscard]] std::shared_ptr<SessionRequestManager> GetManager() const {
|
||||
return manager.lock();
|
||||
}
|
||||
|
||||
|
||||
@@ -243,6 +243,7 @@ void InitializeSlabHeaps(Core::System& system, KMemoryLayout& memory_layout) {
|
||||
// If we somehow get an invalid type, abort.
|
||||
default:
|
||||
ASSERT_MSG(false, "Unknown slab type: {}", slab_types[i]);
|
||||
break;
|
||||
}
|
||||
|
||||
// If we've hit the end of a gap, free it.
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "core/hle/kernel/k_handle_table.h"
|
||||
#include "core/hle/kernel/k_process.h"
|
||||
|
||||
namespace Kernel {
|
||||
|
||||
@@ -82,6 +83,22 @@ Result KHandleTable::Add(Handle* out_handle, KAutoObject* obj) {
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
KScopedAutoObject<KAutoObject> KHandleTable::GetObjectForIpc(Handle handle,
|
||||
KThread* cur_thread) const {
|
||||
// Handle pseudo-handles.
|
||||
ASSERT(cur_thread != nullptr);
|
||||
if (handle == Svc::PseudoHandle::CurrentProcess) {
|
||||
auto* const cur_process = cur_thread->GetOwnerProcess();
|
||||
ASSERT(cur_process != nullptr);
|
||||
return cur_process;
|
||||
}
|
||||
if (handle == Svc::PseudoHandle::CurrentThread) {
|
||||
return cur_thread;
|
||||
}
|
||||
|
||||
return GetObjectForIpcWithoutPseudoHandle(handle);
|
||||
}
|
||||
|
||||
Result KHandleTable::Reserve(Handle* out_handle) {
|
||||
KScopedDisableDispatch dd{m_kernel};
|
||||
KScopedSpinLock lk(m_lock);
|
||||
|
||||
@@ -113,21 +113,7 @@ public:
|
||||
return this->GetObjectImpl(handle);
|
||||
}
|
||||
|
||||
KScopedAutoObject<KAutoObject> GetObjectForIpc(Handle handle, KThread* cur_thread) const {
|
||||
// Handle pseudo-handles.
|
||||
ASSERT(cur_thread != nullptr);
|
||||
if (handle == Svc::PseudoHandle::CurrentProcess) {
|
||||
auto* const cur_process =
|
||||
static_cast<KAutoObject*>(static_cast<void*>(cur_thread->GetOwnerProcess()));
|
||||
ASSERT(cur_process != nullptr);
|
||||
return cur_process;
|
||||
}
|
||||
if (handle == Svc::PseudoHandle::CurrentThread) {
|
||||
return static_cast<KAutoObject*>(cur_thread);
|
||||
}
|
||||
|
||||
return GetObjectForIpcWithoutPseudoHandle(handle);
|
||||
}
|
||||
KScopedAutoObject<KAutoObject> GetObjectForIpc(Handle handle, KThread* cur_thread) const;
|
||||
|
||||
KScopedAutoObject<KAutoObject> GetObjectByIndex(Handle* out_handle, size_t index) const {
|
||||
KScopedDisableDispatch dd{m_kernel};
|
||||
|
||||
@@ -2301,6 +2301,7 @@ Result KPageTable::SetProcessMemoryPermission(VAddr addr, size_t size,
|
||||
break;
|
||||
default:
|
||||
ASSERT(false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2803,6 +2804,7 @@ Result KPageTable::Operate(VAddr addr, size_t num_pages, const KPageGroup& page_
|
||||
break;
|
||||
default:
|
||||
ASSERT(false);
|
||||
break;
|
||||
}
|
||||
|
||||
addr += size;
|
||||
@@ -2838,6 +2840,7 @@ Result KPageTable::Operate(VAddr addr, size_t num_pages, KMemoryPermission perm,
|
||||
break;
|
||||
default:
|
||||
ASSERT(false);
|
||||
break;
|
||||
}
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
@@ -320,6 +320,9 @@ public:
|
||||
constexpr VAddr GetAliasCodeRegionStart() const {
|
||||
return m_alias_code_region_start;
|
||||
}
|
||||
constexpr VAddr GetAliasCodeRegionEnd() const {
|
||||
return m_alias_code_region_end;
|
||||
}
|
||||
constexpr VAddr GetAliasCodeRegionSize() const {
|
||||
return m_alias_code_region_end - m_alias_code_region_start;
|
||||
}
|
||||
|
||||
@@ -395,6 +395,7 @@ Result KProcess::LoadFromMetadata(const FileSys::ProgramMetadata& metadata, std:
|
||||
|
||||
default:
|
||||
ASSERT(false);
|
||||
break;
|
||||
}
|
||||
|
||||
// Create TLS region
|
||||
|
||||
@@ -2701,14 +2701,24 @@ static Result GetThreadList(Core::System& system, u32* out_num_threads, VAddr ou
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
static Result FlushProcessDataCache32([[maybe_unused]] Core::System& system,
|
||||
[[maybe_unused]] Handle handle, [[maybe_unused]] u32 address,
|
||||
[[maybe_unused]] u32 size) {
|
||||
// Note(Blinkhawk): For emulation purposes of the data cache this is mostly a no-op,
|
||||
// as all emulation is done in the same cache level in host architecture, thus data cache
|
||||
// does not need flushing.
|
||||
LOG_DEBUG(Kernel_SVC, "called");
|
||||
return ResultSuccess;
|
||||
static Result FlushProcessDataCache32(Core::System& system, Handle process_handle, u64 address,
|
||||
u64 size) {
|
||||
// Validate address/size.
|
||||
R_UNLESS(size > 0, ResultInvalidSize);
|
||||
R_UNLESS(address == static_cast<uintptr_t>(address), ResultInvalidCurrentMemory);
|
||||
R_UNLESS(size == static_cast<size_t>(size), ResultInvalidCurrentMemory);
|
||||
|
||||
// Get the process from its handle.
|
||||
KScopedAutoObject process =
|
||||
system.Kernel().CurrentProcess()->GetHandleTable().GetObject<KProcess>(process_handle);
|
||||
R_UNLESS(process.IsNotNull(), ResultInvalidHandle);
|
||||
|
||||
// Verify the region is within range.
|
||||
auto& page_table = process->PageTable();
|
||||
R_UNLESS(page_table.Contains(address, size), ResultInvalidCurrentMemory);
|
||||
|
||||
// Perform the operation.
|
||||
R_RETURN(system.Memory().FlushDataCache(*process, address, size));
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -722,4 +722,12 @@ void SvcWrap32(Core::System& system) {
|
||||
FuncReturn(system, retval);
|
||||
}
|
||||
|
||||
// Used by Invalidate/Store/FlushProcessDataCache32
|
||||
template <Result func(Core::System&, Handle, u64, u64)>
|
||||
void SvcWrap32(Core::System& system) {
|
||||
const u64 address = (Param(system, 3) << 32) | Param(system, 2);
|
||||
const u64 size = (Param(system, 4) << 32) | Param(system, 1);
|
||||
FuncReturn32(system, func(system, Param32(system, 0), address, size).raw);
|
||||
}
|
||||
|
||||
} // namespace Kernel
|
||||
|
||||
@@ -28,30 +28,49 @@ enum class ErrorModule : u32 {
|
||||
Loader = 9,
|
||||
CMIF = 10,
|
||||
HIPC = 11,
|
||||
TMA = 12,
|
||||
DMNT = 13,
|
||||
GDS = 14,
|
||||
PM = 15,
|
||||
NS = 16,
|
||||
BSDSockets = 17,
|
||||
HTC = 18,
|
||||
TSC = 19,
|
||||
NCMContent = 20,
|
||||
SM = 21,
|
||||
RO = 22,
|
||||
GC = 23,
|
||||
SDMMC = 24,
|
||||
OVLN = 25,
|
||||
SPL = 26,
|
||||
Socket = 27,
|
||||
HTCLOW = 29,
|
||||
DDSF = 30,
|
||||
HTCFS = 31,
|
||||
Async = 32,
|
||||
Util = 33,
|
||||
TIPC = 35,
|
||||
ANIF = 37,
|
||||
ETHC = 100,
|
||||
I2C = 101,
|
||||
GPIO = 102,
|
||||
UART = 103,
|
||||
CPAD = 104,
|
||||
Settings = 105,
|
||||
FTM = 106,
|
||||
WLAN = 107,
|
||||
XCD = 108,
|
||||
TMP451 = 109,
|
||||
NIFM = 110,
|
||||
Hwopus = 111,
|
||||
LSM6DS3 = 112,
|
||||
Bluetooth = 113,
|
||||
VI = 114,
|
||||
NFP = 115,
|
||||
Time = 116,
|
||||
FGM = 117,
|
||||
OE = 118,
|
||||
BH1730FVC = 119,
|
||||
PCIe = 120,
|
||||
Friends = 121,
|
||||
BCAT = 122,
|
||||
@@ -65,7 +84,7 @@ enum class ErrorModule : u32 {
|
||||
AHID = 130,
|
||||
Qlaunch = 132,
|
||||
PCV = 133,
|
||||
OMM = 134,
|
||||
USBPD = 134,
|
||||
BPC = 135,
|
||||
PSM = 136,
|
||||
NIM = 137,
|
||||
@@ -75,18 +94,22 @@ enum class ErrorModule : u32 {
|
||||
NSD = 141,
|
||||
PCTL = 142,
|
||||
BTM = 143,
|
||||
LA = 144,
|
||||
ETicket = 145,
|
||||
NGC = 146,
|
||||
ERPT = 147,
|
||||
APM = 148,
|
||||
CEC = 149,
|
||||
Profiler = 150,
|
||||
ErrorUpload = 151,
|
||||
LIDBE = 152,
|
||||
Audio = 153,
|
||||
NPNS = 154,
|
||||
NPNSHTTPSTREAM = 155,
|
||||
ARP = 157,
|
||||
SWKBD = 158,
|
||||
BOOT = 159,
|
||||
NetDiag = 160,
|
||||
NFCMifare = 161,
|
||||
UserlandAssert = 162,
|
||||
Fatal = 163,
|
||||
@@ -94,17 +117,68 @@ enum class ErrorModule : u32 {
|
||||
SPSM = 165,
|
||||
BGTC = 167,
|
||||
UserlandCrash = 168,
|
||||
SASBUS = 169,
|
||||
PI = 170,
|
||||
AudioCtrl = 172,
|
||||
LBL = 173,
|
||||
JIT = 175,
|
||||
HDCP = 176,
|
||||
OMM = 177,
|
||||
PDM = 178,
|
||||
OLSC = 179,
|
||||
SREPO = 180,
|
||||
Dauth = 181,
|
||||
STDFU = 182,
|
||||
DBG = 183,
|
||||
DHCPS = 186,
|
||||
SPI = 187,
|
||||
AVM = 188,
|
||||
PWM = 189,
|
||||
RTC = 191,
|
||||
Regulator = 192,
|
||||
LED = 193,
|
||||
SIO = 195,
|
||||
PCM = 196,
|
||||
CLKRST = 197,
|
||||
POWCTL = 198,
|
||||
AudioOld = 201,
|
||||
HID = 202,
|
||||
LDN = 203,
|
||||
CS = 204,
|
||||
Irsensor = 205,
|
||||
Capture = 206,
|
||||
Manu = 208,
|
||||
ATK = 209,
|
||||
WEB = 210,
|
||||
LCS = 211,
|
||||
GRC = 212,
|
||||
Repair = 213,
|
||||
Album = 214,
|
||||
RID = 215,
|
||||
Migration = 216,
|
||||
MigrationLdcServ = 217,
|
||||
HIDBUS = 218,
|
||||
ENS = 219,
|
||||
WebSocket = 223,
|
||||
DCDMTP = 227,
|
||||
PGL = 228,
|
||||
Notification = 229,
|
||||
INS = 230,
|
||||
LP2P = 231,
|
||||
RCD = 232,
|
||||
LCM40607 = 233,
|
||||
PRC = 235,
|
||||
TMAHTC = 237,
|
||||
ECTX = 238,
|
||||
MNPP = 239,
|
||||
HSHL = 240,
|
||||
CAPMTP = 242,
|
||||
DP2HDMI = 244,
|
||||
Cradle = 245,
|
||||
SProfile = 246,
|
||||
NDRM = 250,
|
||||
TSPM = 499,
|
||||
DevMenu = 500,
|
||||
GeneralWebApplet = 800,
|
||||
WifiWebAuthApplet = 809,
|
||||
WhitelistedApplet = 810,
|
||||
|
||||
@@ -144,6 +144,7 @@ void Error::Initialize() {
|
||||
break;
|
||||
default:
|
||||
UNIMPLEMENTED_MSG("Unimplemented LibAppletError mode={:02X}!", mode);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -129,6 +129,7 @@ void Auth::Execute() {
|
||||
}
|
||||
default:
|
||||
unimplemented_log();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -192,6 +193,7 @@ void PhotoViewer::Execute() {
|
||||
break;
|
||||
default:
|
||||
UNIMPLEMENTED_MSG("Unimplemented PhotoViewer applet mode={:02X}!", mode);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -122,10 +122,10 @@ private:
|
||||
}
|
||||
|
||||
void GetReleasedAudioInBuffer(Kernel::HLERequestContext& ctx) {
|
||||
auto write_buffer_size = ctx.GetWriteBufferSize() / sizeof(u64);
|
||||
std::vector<u64> released_buffers(write_buffer_size, 0);
|
||||
const auto write_buffer_size = ctx.GetWriteBufferNumElements<u64>();
|
||||
std::vector<u64> released_buffers(write_buffer_size);
|
||||
|
||||
auto count = impl->GetReleasedBuffers(released_buffers);
|
||||
const auto count = impl->GetReleasedBuffers(released_buffers);
|
||||
|
||||
[[maybe_unused]] std::string tags{};
|
||||
for (u32 i = 0; i < count; i++) {
|
||||
@@ -228,7 +228,7 @@ void AudInU::ListAudioIns(Kernel::HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Service_Audio, "called");
|
||||
|
||||
const auto write_count =
|
||||
static_cast<u32>(ctx.GetWriteBufferSize() / sizeof(AudioDevice::AudioDeviceName));
|
||||
static_cast<u32>(ctx.GetWriteBufferNumElements<AudioDevice::AudioDeviceName>());
|
||||
std::vector<AudioDevice::AudioDeviceName> device_names{};
|
||||
|
||||
u32 out_count{0};
|
||||
@@ -248,7 +248,7 @@ void AudInU::ListAudioInsAutoFiltered(Kernel::HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Service_Audio, "called");
|
||||
|
||||
const auto write_count =
|
||||
static_cast<u32>(ctx.GetWriteBufferSize() / sizeof(AudioDevice::AudioDeviceName));
|
||||
static_cast<u32>(ctx.GetWriteBufferNumElements<AudioDevice::AudioDeviceName>());
|
||||
std::vector<AudioDevice::AudioDeviceName> device_names{};
|
||||
|
||||
u32 out_count{0};
|
||||
|
||||
@@ -129,16 +129,16 @@ private:
|
||||
}
|
||||
|
||||
void GetReleasedAudioOutBuffers(Kernel::HLERequestContext& ctx) {
|
||||
auto write_buffer_size = ctx.GetWriteBufferSize() / sizeof(u64);
|
||||
std::vector<u64> released_buffers(write_buffer_size, 0);
|
||||
const auto write_buffer_size = ctx.GetWriteBufferNumElements<u64>();
|
||||
std::vector<u64> released_buffers(write_buffer_size);
|
||||
|
||||
auto count = impl->GetReleasedBuffers(released_buffers);
|
||||
const auto count = impl->GetReleasedBuffers(released_buffers);
|
||||
|
||||
[[maybe_unused]] std::string tags{};
|
||||
for (u32 i = 0; i < count; i++) {
|
||||
tags += fmt::format("{:08X}, ", released_buffers[i]);
|
||||
}
|
||||
[[maybe_unused]] auto sessionid{impl->GetSystem().GetSessionId()};
|
||||
[[maybe_unused]] const auto sessionid{impl->GetSystem().GetSessionId()};
|
||||
LOG_TRACE(Service_Audio, "called. Session {} released {} buffers: {}", sessionid, count,
|
||||
tags);
|
||||
|
||||
@@ -244,7 +244,7 @@ void AudOutU::ListAudioOuts(Kernel::HLERequestContext& ctx) {
|
||||
std::scoped_lock l{impl->mutex};
|
||||
|
||||
const auto write_count =
|
||||
static_cast<u32>(ctx.GetWriteBufferSize() / sizeof(AudioDevice::AudioDeviceName));
|
||||
static_cast<u32>(ctx.GetWriteBufferNumElements<AudioDevice::AudioDeviceName>());
|
||||
std::vector<AudioDevice::AudioDeviceName> device_names{};
|
||||
if (write_count > 0) {
|
||||
device_names.emplace_back("DeviceOut");
|
||||
|
||||
@@ -274,7 +274,7 @@ public:
|
||||
|
||||
private:
|
||||
void ListAudioDeviceName(Kernel::HLERequestContext& ctx) {
|
||||
const size_t in_count = ctx.GetWriteBufferSize() / sizeof(AudioDevice::AudioDeviceName);
|
||||
const size_t in_count = ctx.GetWriteBufferNumElements<AudioDevice::AudioDeviceName>();
|
||||
|
||||
std::vector<AudioDevice::AudioDeviceName> out_names{};
|
||||
|
||||
@@ -335,7 +335,7 @@ private:
|
||||
}
|
||||
|
||||
void GetActiveAudioDeviceName(Kernel::HLERequestContext& ctx) {
|
||||
const auto write_size = ctx.GetWriteBufferSize() / sizeof(char);
|
||||
const auto write_size = ctx.GetWriteBufferSize();
|
||||
std::string out_name{"AudioTvOutput"};
|
||||
|
||||
LOG_DEBUG(Service_Audio, "(STUBBED) called. Name={}", out_name);
|
||||
@@ -387,7 +387,7 @@ private:
|
||||
}
|
||||
|
||||
void ListAudioOutputDeviceName(Kernel::HLERequestContext& ctx) {
|
||||
const size_t in_count = ctx.GetWriteBufferSize() / sizeof(AudioDevice::AudioDeviceName);
|
||||
const size_t in_count = ctx.GetWriteBufferNumElements<AudioDevice::AudioDeviceName>();
|
||||
|
||||
std::vector<AudioDevice::AudioDeviceName> out_names{};
|
||||
|
||||
|
||||
@@ -68,7 +68,7 @@ private:
|
||||
ExtraBehavior extra_behavior) {
|
||||
u32 consumed = 0;
|
||||
u32 sample_count = 0;
|
||||
std::vector<opus_int16> samples(ctx.GetWriteBufferSize() / sizeof(opus_int16));
|
||||
std::vector<opus_int16> samples(ctx.GetWriteBufferNumElements<opus_int16>());
|
||||
|
||||
if (extra_behavior == ExtraBehavior::ResetContext) {
|
||||
ResetDecoderContext();
|
||||
|
||||
@@ -443,7 +443,7 @@ private:
|
||||
}
|
||||
|
||||
void Read(Kernel::HLERequestContext& ctx) {
|
||||
auto write_size = ctx.GetWriteBufferSize() / sizeof(DeliveryCacheDirectoryEntry);
|
||||
auto write_size = ctx.GetWriteBufferNumElements<DeliveryCacheDirectoryEntry>();
|
||||
|
||||
LOG_DEBUG(Service_BCAT, "called, write_size={:016X}", write_size);
|
||||
|
||||
@@ -533,7 +533,7 @@ private:
|
||||
}
|
||||
|
||||
void EnumerateDeliveryCacheDirectory(Kernel::HLERequestContext& ctx) {
|
||||
auto size = ctx.GetWriteBufferSize() / sizeof(DirectoryName);
|
||||
auto size = ctx.GetWriteBufferNumElements<DirectoryName>();
|
||||
|
||||
LOG_DEBUG(Service_BCAT, "called, size={:016X}", size);
|
||||
|
||||
|
||||
@@ -192,12 +192,10 @@ private:
|
||||
}
|
||||
|
||||
void ListCommonTicketRightsIds(Kernel::HLERequestContext& ctx) {
|
||||
u32 out_entries;
|
||||
if (keys.GetCommonTickets().empty())
|
||||
out_entries = 0;
|
||||
else
|
||||
out_entries = static_cast<u32>(ctx.GetWriteBufferSize() / sizeof(u128));
|
||||
|
||||
size_t out_entries = 0;
|
||||
if (!keys.GetCommonTickets().empty()) {
|
||||
out_entries = ctx.GetWriteBufferNumElements<u128>();
|
||||
}
|
||||
LOG_DEBUG(Service_ETicket, "called, entries={:016X}", out_entries);
|
||||
|
||||
keys.PopulateTickets();
|
||||
@@ -206,20 +204,19 @@ private:
|
||||
std::transform(tickets.begin(), tickets.end(), std::back_inserter(ids),
|
||||
[](const auto& pair) { return pair.first; });
|
||||
|
||||
out_entries = static_cast<u32>(std::min<std::size_t>(ids.size(), out_entries));
|
||||
out_entries = std::min(ids.size(), out_entries);
|
||||
ctx.WriteBuffer(ids.data(), out_entries * sizeof(u128));
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(ResultSuccess);
|
||||
rb.Push<u32>(out_entries);
|
||||
rb.Push<u32>(static_cast<u32>(out_entries));
|
||||
}
|
||||
|
||||
void ListPersonalizedTicketRightsIds(Kernel::HLERequestContext& ctx) {
|
||||
u32 out_entries;
|
||||
if (keys.GetPersonalizedTickets().empty())
|
||||
out_entries = 0;
|
||||
else
|
||||
out_entries = static_cast<u32>(ctx.GetWriteBufferSize() / sizeof(u128));
|
||||
size_t out_entries = 0;
|
||||
if (!keys.GetPersonalizedTickets().empty()) {
|
||||
out_entries = ctx.GetWriteBufferNumElements<u128>();
|
||||
}
|
||||
|
||||
LOG_DEBUG(Service_ETicket, "called, entries={:016X}", out_entries);
|
||||
|
||||
@@ -229,12 +226,12 @@ private:
|
||||
std::transform(tickets.begin(), tickets.end(), std::back_inserter(ids),
|
||||
[](const auto& pair) { return pair.first; });
|
||||
|
||||
out_entries = static_cast<u32>(std::min<std::size_t>(ids.size(), out_entries));
|
||||
out_entries = std::min(ids.size(), out_entries);
|
||||
ctx.WriteBuffer(ids.data(), out_entries * sizeof(u128));
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(ResultSuccess);
|
||||
rb.Push<u32>(out_entries);
|
||||
rb.Push<u32>(static_cast<u32>(out_entries));
|
||||
}
|
||||
|
||||
void GetCommonTicketSize(Kernel::HLERequestContext& ctx) {
|
||||
|
||||
@@ -277,7 +277,7 @@ private:
|
||||
LOG_DEBUG(Service_FS, "called.");
|
||||
|
||||
// Calculate how many entries we can fit in the output buffer
|
||||
const u64 count_entries = ctx.GetWriteBufferSize() / sizeof(FileSys::Entry);
|
||||
const u64 count_entries = ctx.GetWriteBufferNumElements<FileSys::Entry>();
|
||||
|
||||
// Cap at total number of entries.
|
||||
const u64 actual_entries = std::min(count_entries, entries.size() - next_entry_index);
|
||||
@@ -543,7 +543,7 @@ public:
|
||||
LOG_DEBUG(Service_FS, "called");
|
||||
|
||||
// Calculate how many entries we can fit in the output buffer
|
||||
const u64 count_entries = ctx.GetWriteBufferSize() / sizeof(SaveDataInfo);
|
||||
const u64 count_entries = ctx.GetWriteBufferNumElements<SaveDataInfo>();
|
||||
|
||||
// Cap at total number of entries.
|
||||
const u64 actual_entries = std::min(count_entries, info.size() - next_entry_index);
|
||||
|
||||
@@ -36,8 +36,9 @@ namespace Service::HID {
|
||||
|
||||
// Updating period for each HID device.
|
||||
// Period time is obtained by measuring the number of samples in a second on HW using a homebrew
|
||||
// Correct pad_update_ns is 4ms this is overclocked to lower input lag
|
||||
constexpr auto pad_update_ns = std::chrono::nanoseconds{1 * 1000 * 1000}; // (1ms, 1000Hz)
|
||||
// Correct npad_update_ns is 4ms this is overclocked to lower input lag
|
||||
constexpr auto npad_update_ns = std::chrono::nanoseconds{1 * 1000 * 1000}; // (1ms, 1000Hz)
|
||||
constexpr auto default_update_ns = std::chrono::nanoseconds{4 * 1000 * 1000}; // (4ms, 1000Hz)
|
||||
constexpr auto mouse_keyboard_update_ns = std::chrono::nanoseconds{8 * 1000 * 1000}; // (8ms, 125Hz)
|
||||
constexpr auto motion_update_ns = std::chrono::nanoseconds{5 * 1000 * 1000}; // (5ms, 200Hz)
|
||||
|
||||
@@ -75,8 +76,16 @@ IAppletResource::IAppletResource(Core::System& system_,
|
||||
GetController<Controller_Stubbed>(HidController::UniquePad).SetCommonHeaderOffset(0x5A00);
|
||||
|
||||
// Register update callbacks
|
||||
pad_update_event = Core::Timing::CreateEvent(
|
||||
npad_update_event = Core::Timing::CreateEvent(
|
||||
"HID::UpdatePadCallback",
|
||||
[this](std::uintptr_t user_data, s64 time,
|
||||
std::chrono::nanoseconds ns_late) -> std::optional<std::chrono::nanoseconds> {
|
||||
const auto guard = LockService();
|
||||
UpdateNpad(user_data, ns_late);
|
||||
return std::nullopt;
|
||||
});
|
||||
default_update_event = Core::Timing::CreateEvent(
|
||||
"HID::UpdateDefaultCallback",
|
||||
[this](std::uintptr_t user_data, s64 time,
|
||||
std::chrono::nanoseconds ns_late) -> std::optional<std::chrono::nanoseconds> {
|
||||
const auto guard = LockService();
|
||||
@@ -100,7 +109,9 @@ IAppletResource::IAppletResource(Core::System& system_,
|
||||
return std::nullopt;
|
||||
});
|
||||
|
||||
system.CoreTiming().ScheduleLoopingEvent(pad_update_ns, pad_update_ns, pad_update_event);
|
||||
system.CoreTiming().ScheduleLoopingEvent(npad_update_ns, npad_update_ns, npad_update_event);
|
||||
system.CoreTiming().ScheduleLoopingEvent(default_update_ns, default_update_ns,
|
||||
default_update_event);
|
||||
system.CoreTiming().ScheduleLoopingEvent(mouse_keyboard_update_ns, mouse_keyboard_update_ns,
|
||||
mouse_keyboard_update_event);
|
||||
system.CoreTiming().ScheduleLoopingEvent(motion_update_ns, motion_update_ns,
|
||||
@@ -118,7 +129,8 @@ void IAppletResource::DeactivateController(HidController controller) {
|
||||
}
|
||||
|
||||
IAppletResource::~IAppletResource() {
|
||||
system.CoreTiming().UnscheduleEvent(pad_update_event, 0);
|
||||
system.CoreTiming().UnscheduleEvent(npad_update_event, 0);
|
||||
system.CoreTiming().UnscheduleEvent(default_update_event, 0);
|
||||
system.CoreTiming().UnscheduleEvent(mouse_keyboard_update_event, 0);
|
||||
system.CoreTiming().UnscheduleEvent(motion_update_event, 0);
|
||||
}
|
||||
@@ -144,10 +156,20 @@ void IAppletResource::UpdateControllers(std::uintptr_t user_data,
|
||||
if (controller == controllers[static_cast<size_t>(HidController::Mouse)]) {
|
||||
continue;
|
||||
}
|
||||
// Npad has it's own update event
|
||||
if (controller == controllers[static_cast<size_t>(HidController::NPad)]) {
|
||||
continue;
|
||||
}
|
||||
controller->OnUpdate(core_timing);
|
||||
}
|
||||
}
|
||||
|
||||
void IAppletResource::UpdateNpad(std::uintptr_t user_data, std::chrono::nanoseconds ns_late) {
|
||||
auto& core_timing = system.CoreTiming();
|
||||
|
||||
controllers[static_cast<size_t>(HidController::NPad)]->OnUpdate(core_timing);
|
||||
}
|
||||
|
||||
void IAppletResource::UpdateMouseKeyboard(std::uintptr_t user_data,
|
||||
std::chrono::nanoseconds ns_late) {
|
||||
auto& core_timing = system.CoreTiming();
|
||||
|
||||
@@ -71,12 +71,14 @@ private:
|
||||
|
||||
void GetSharedMemoryHandle(Kernel::HLERequestContext& ctx);
|
||||
void UpdateControllers(std::uintptr_t user_data, std::chrono::nanoseconds ns_late);
|
||||
void UpdateNpad(std::uintptr_t user_data, std::chrono::nanoseconds ns_late);
|
||||
void UpdateMouseKeyboard(std::uintptr_t user_data, std::chrono::nanoseconds ns_late);
|
||||
void UpdateMotion(std::uintptr_t user_data, std::chrono::nanoseconds ns_late);
|
||||
|
||||
KernelHelpers::ServiceContext& service_context;
|
||||
|
||||
std::shared_ptr<Core::Timing::EventType> pad_update_event;
|
||||
std::shared_ptr<Core::Timing::EventType> npad_update_event;
|
||||
std::shared_ptr<Core::Timing::EventType> default_update_event;
|
||||
std::shared_ptr<Core::Timing::EventType> mouse_keyboard_update_event;
|
||||
std::shared_ptr<Core::Timing::EventType> motion_update_event;
|
||||
|
||||
|
||||
@@ -292,7 +292,7 @@ public:
|
||||
|
||||
void GetNetworkInfoLatestUpdate(Kernel::HLERequestContext& ctx) {
|
||||
const std::size_t network_buffer_size = ctx.GetWriteBufferSize(0);
|
||||
const std::size_t node_buffer_count = ctx.GetWriteBufferSize(1) / sizeof(NodeLatestUpdate);
|
||||
const std::size_t node_buffer_count = ctx.GetWriteBufferNumElements<NodeLatestUpdate>(1);
|
||||
|
||||
if (node_buffer_count == 0 || network_buffer_size != sizeof(NetworkInfo)) {
|
||||
LOG_ERROR(Service_LDN, "Invalid buffer, size = {}, count = {}", network_buffer_size,
|
||||
@@ -333,7 +333,7 @@ public:
|
||||
const auto channel{rp.PopEnum<WifiChannel>()};
|
||||
const auto scan_filter{rp.PopRaw<ScanFilter>()};
|
||||
|
||||
const std::size_t network_info_size = ctx.GetWriteBufferSize() / sizeof(NetworkInfo);
|
||||
const std::size_t network_info_size = ctx.GetWriteBufferNumElements<NetworkInfo>();
|
||||
|
||||
if (network_info_size == 0) {
|
||||
LOG_ERROR(Service_LDN, "Invalid buffer size {}", network_info_size);
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "common/settings.h"
|
||||
#include "core/hle/ipc_helpers.h"
|
||||
#include "core/hle/service/nfc/nfc.h"
|
||||
#include "core/hle/service/nfc/nfc_user.h"
|
||||
#include "core/hle/service/service.h"
|
||||
#include "core/hle/service/sm/sm.h"
|
||||
|
||||
@@ -97,76 +98,6 @@ private:
|
||||
}
|
||||
};
|
||||
|
||||
class IUser final : public ServiceFramework<IUser> {
|
||||
public:
|
||||
explicit IUser(Core::System& system_) : ServiceFramework{system_, "NFC::IUser"} {
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, &IUser::InitializeOld, "InitializeOld"},
|
||||
{1, &IUser::FinalizeOld, "FinalizeOld"},
|
||||
{2, &IUser::GetStateOld, "GetStateOld"},
|
||||
{3, &IUser::IsNfcEnabledOld, "IsNfcEnabledOld"},
|
||||
{400, &IUser::InitializeOld, "Initialize"},
|
||||
{401, &IUser::FinalizeOld, "Finalize"},
|
||||
{402, &IUser::GetStateOld, "GetState"},
|
||||
{403, &IUser::IsNfcEnabledOld, "IsNfcEnabled"},
|
||||
{404, nullptr, "ListDevices"},
|
||||
{405, nullptr, "GetDeviceState"},
|
||||
{406, nullptr, "GetNpadId"},
|
||||
{407, nullptr, "AttachAvailabilityChangeEvent"},
|
||||
{408, nullptr, "StartDetection"},
|
||||
{409, nullptr, "StopDetection"},
|
||||
{410, nullptr, "GetTagInfo"},
|
||||
{411, nullptr, "AttachActivateEvent"},
|
||||
{412, nullptr, "AttachDeactivateEvent"},
|
||||
{1000, nullptr, "ReadMifare"},
|
||||
{1001, nullptr, "WriteMifare"},
|
||||
{1300, nullptr, "SendCommandByPassThrough"},
|
||||
{1301, nullptr, "KeepPassThroughSession"},
|
||||
{1302, nullptr, "ReleasePassThroughSession"},
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
|
||||
private:
|
||||
enum class NfcStates : u32 {
|
||||
Finalized = 6,
|
||||
};
|
||||
|
||||
void InitializeOld(Kernel::HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Service_NFC, "called");
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2, 0};
|
||||
rb.Push(ResultSuccess);
|
||||
// We don't deal with hardware initialization so we can just stub this.
|
||||
}
|
||||
|
||||
void IsNfcEnabledOld(Kernel::HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Service_NFC, "IsNfcEnabledOld");
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(ResultSuccess);
|
||||
rb.PushRaw<u8>(true);
|
||||
}
|
||||
|
||||
void GetStateOld(Kernel::HLERequestContext& ctx) {
|
||||
LOG_WARNING(Service_NFC, "(STUBBED) called");
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(ResultSuccess);
|
||||
rb.PushEnum(NfcStates::Finalized); // TODO(ogniK): Figure out if this matches nfp
|
||||
}
|
||||
|
||||
void FinalizeOld(Kernel::HLERequestContext& ctx) {
|
||||
LOG_WARNING(Service_NFC, "(STUBBED) called");
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(ResultSuccess);
|
||||
}
|
||||
};
|
||||
|
||||
class NFC_U final : public ServiceFramework<NFC_U> {
|
||||
public:
|
||||
explicit NFC_U(Core::System& system_) : ServiceFramework{system_, "nfc:user"} {
|
||||
|
||||
197
src/core/hle/service/nfc/nfc_device.cpp
Normal file
197
src/core/hle/service/nfc/nfc_device.cpp
Normal file
@@ -0,0 +1,197 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "common/input.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/core.h"
|
||||
#include "core/hid/emulated_controller.h"
|
||||
#include "core/hid/hid_core.h"
|
||||
#include "core/hid/hid_types.h"
|
||||
#include "core/hle/ipc_helpers.h"
|
||||
#include "core/hle/kernel/k_event.h"
|
||||
#include "core/hle/service/nfc/nfc_device.h"
|
||||
#include "core/hle/service/nfc/nfc_result.h"
|
||||
#include "core/hle/service/nfc/nfc_user.h"
|
||||
|
||||
namespace Service::NFC {
|
||||
NfcDevice::NfcDevice(Core::HID::NpadIdType npad_id_, Core::System& system_,
|
||||
KernelHelpers::ServiceContext& service_context_,
|
||||
Kernel::KEvent* availability_change_event_)
|
||||
: npad_id{npad_id_}, system{system_}, service_context{service_context_},
|
||||
availability_change_event{availability_change_event_} {
|
||||
activate_event = service_context.CreateEvent("IUser:NFCActivateEvent");
|
||||
deactivate_event = service_context.CreateEvent("IUser:NFCDeactivateEvent");
|
||||
npad_device = system.HIDCore().GetEmulatedController(npad_id);
|
||||
|
||||
Core::HID::ControllerUpdateCallback engine_callback{
|
||||
.on_change = [this](Core::HID::ControllerTriggerType type) { NpadUpdate(type); },
|
||||
.is_npad_service = false,
|
||||
};
|
||||
is_controller_set = true;
|
||||
callback_key = npad_device->SetCallback(engine_callback);
|
||||
}
|
||||
|
||||
NfcDevice::~NfcDevice() {
|
||||
activate_event->Close();
|
||||
deactivate_event->Close();
|
||||
if (!is_controller_set) {
|
||||
return;
|
||||
}
|
||||
npad_device->DeleteCallback(callback_key);
|
||||
is_controller_set = false;
|
||||
};
|
||||
|
||||
void NfcDevice::NpadUpdate(Core::HID::ControllerTriggerType type) {
|
||||
if (type == Core::HID::ControllerTriggerType::Connected ||
|
||||
type == Core::HID::ControllerTriggerType::Disconnected) {
|
||||
availability_change_event->Signal();
|
||||
return;
|
||||
}
|
||||
|
||||
if (type != Core::HID::ControllerTriggerType::Nfc) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!npad_device->IsConnected()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto nfc_status = npad_device->GetNfc();
|
||||
switch (nfc_status.state) {
|
||||
case Common::Input::NfcState::NewAmiibo:
|
||||
LoadNfcTag(nfc_status.data);
|
||||
break;
|
||||
case Common::Input::NfcState::AmiiboRemoved:
|
||||
if (device_state != NFP::DeviceState::SearchingForTag) {
|
||||
CloseNfcTag();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool NfcDevice::LoadNfcTag(std::span<const u8> data) {
|
||||
if (device_state != NFP::DeviceState::SearchingForTag) {
|
||||
LOG_ERROR(Service_NFC, "Game is not looking for nfc tag, current state {}", device_state);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (data.size() != sizeof(NFP::EncryptedNTAG215File)) {
|
||||
LOG_ERROR(Service_NFC, "Not an amiibo, size={}", data.size());
|
||||
return false;
|
||||
}
|
||||
|
||||
memcpy(&encrypted_tag_data, data.data(), sizeof(NFP::EncryptedNTAG215File));
|
||||
|
||||
device_state = NFP::DeviceState::TagFound;
|
||||
deactivate_event->GetReadableEvent().Clear();
|
||||
activate_event->Signal();
|
||||
return true;
|
||||
}
|
||||
|
||||
void NfcDevice::CloseNfcTag() {
|
||||
LOG_INFO(Service_NFC, "Remove nfc tag");
|
||||
|
||||
device_state = NFP::DeviceState::TagRemoved;
|
||||
encrypted_tag_data = {};
|
||||
activate_event->GetReadableEvent().Clear();
|
||||
deactivate_event->Signal();
|
||||
}
|
||||
|
||||
Kernel::KReadableEvent& NfcDevice::GetActivateEvent() const {
|
||||
return activate_event->GetReadableEvent();
|
||||
}
|
||||
|
||||
Kernel::KReadableEvent& NfcDevice::GetDeactivateEvent() const {
|
||||
return deactivate_event->GetReadableEvent();
|
||||
}
|
||||
|
||||
void NfcDevice::Initialize() {
|
||||
device_state =
|
||||
npad_device->HasNfc() ? NFP::DeviceState::Initialized : NFP::DeviceState::Unavailable;
|
||||
encrypted_tag_data = {};
|
||||
}
|
||||
|
||||
void NfcDevice::Finalize() {
|
||||
if (device_state == NFP::DeviceState::SearchingForTag ||
|
||||
device_state == NFP::DeviceState::TagRemoved) {
|
||||
StopDetection();
|
||||
}
|
||||
device_state = NFP::DeviceState::Unavailable;
|
||||
}
|
||||
|
||||
Result NfcDevice::StartDetection(s32 protocol_) {
|
||||
if (device_state != NFP::DeviceState::Initialized &&
|
||||
device_state != NFP::DeviceState::TagRemoved) {
|
||||
LOG_ERROR(Service_NFC, "Wrong device state {}", device_state);
|
||||
return WrongDeviceState;
|
||||
}
|
||||
|
||||
if (!npad_device->SetPollingMode(Common::Input::PollingMode::NFC)) {
|
||||
LOG_ERROR(Service_NFC, "Nfc not supported");
|
||||
return NfcDisabled;
|
||||
}
|
||||
|
||||
device_state = NFP::DeviceState::SearchingForTag;
|
||||
protocol = protocol_;
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result NfcDevice::StopDetection() {
|
||||
npad_device->SetPollingMode(Common::Input::PollingMode::Active);
|
||||
|
||||
if (device_state == NFP::DeviceState::Initialized) {
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
if (device_state == NFP::DeviceState::TagFound ||
|
||||
device_state == NFP::DeviceState::TagMounted) {
|
||||
CloseNfcTag();
|
||||
return ResultSuccess;
|
||||
}
|
||||
if (device_state == NFP::DeviceState::SearchingForTag ||
|
||||
device_state == NFP::DeviceState::TagRemoved) {
|
||||
device_state = NFP::DeviceState::Initialized;
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
LOG_ERROR(Service_NFC, "Wrong device state {}", device_state);
|
||||
return WrongDeviceState;
|
||||
}
|
||||
|
||||
Result NfcDevice::GetTagInfo(NFP::TagInfo& tag_info) const {
|
||||
if (device_state != NFP::DeviceState::TagFound &&
|
||||
device_state != NFP::DeviceState::TagMounted) {
|
||||
LOG_ERROR(Service_NFC, "Wrong device state {}", device_state);
|
||||
if (device_state == NFP::DeviceState::TagRemoved) {
|
||||
return TagRemoved;
|
||||
}
|
||||
return WrongDeviceState;
|
||||
}
|
||||
|
||||
// Protocol and tag type may change here
|
||||
tag_info = {
|
||||
.uuid = encrypted_tag_data.uuid.uid,
|
||||
.uuid_length = static_cast<u8>(encrypted_tag_data.uuid.uid.size()),
|
||||
.protocol = NFP::TagProtocol::TypeA,
|
||||
.tag_type = NFP::TagType::Type2,
|
||||
};
|
||||
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
u64 NfcDevice::GetHandle() const {
|
||||
// Generate a handle based of the npad id
|
||||
return static_cast<u64>(npad_id);
|
||||
}
|
||||
|
||||
NFP::DeviceState NfcDevice::GetCurrentState() const {
|
||||
return device_state;
|
||||
}
|
||||
|
||||
Core::HID::NpadIdType NfcDevice::GetNpadId() const {
|
||||
return npad_id;
|
||||
}
|
||||
|
||||
} // namespace Service::NFC
|
||||
70
src/core/hle/service/nfc/nfc_device.h
Normal file
70
src/core/hle/service/nfc/nfc_device.h
Normal file
@@ -0,0 +1,70 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "core/hle/service/kernel_helpers.h"
|
||||
#include "core/hle/service/nfp/nfp_types.h"
|
||||
#include "core/hle/service/service.h"
|
||||
|
||||
namespace Kernel {
|
||||
class KEvent;
|
||||
class KReadableEvent;
|
||||
} // namespace Kernel
|
||||
|
||||
namespace Core {
|
||||
class System;
|
||||
} // namespace Core
|
||||
|
||||
namespace Core::HID {
|
||||
class EmulatedController;
|
||||
enum class ControllerTriggerType;
|
||||
enum class NpadIdType : u32;
|
||||
} // namespace Core::HID
|
||||
|
||||
namespace Service::NFC {
|
||||
class NfcDevice {
|
||||
public:
|
||||
NfcDevice(Core::HID::NpadIdType npad_id_, Core::System& system_,
|
||||
KernelHelpers::ServiceContext& service_context_,
|
||||
Kernel::KEvent* availability_change_event_);
|
||||
~NfcDevice();
|
||||
|
||||
void Initialize();
|
||||
void Finalize();
|
||||
|
||||
Result StartDetection(s32 protocol_);
|
||||
Result StopDetection();
|
||||
|
||||
Result GetTagInfo(NFP::TagInfo& tag_info) const;
|
||||
|
||||
u64 GetHandle() const;
|
||||
NFP::DeviceState GetCurrentState() const;
|
||||
Core::HID::NpadIdType GetNpadId() const;
|
||||
|
||||
Kernel::KReadableEvent& GetActivateEvent() const;
|
||||
Kernel::KReadableEvent& GetDeactivateEvent() const;
|
||||
|
||||
private:
|
||||
void NpadUpdate(Core::HID::ControllerTriggerType type);
|
||||
bool LoadNfcTag(std::span<const u8> data);
|
||||
void CloseNfcTag();
|
||||
|
||||
bool is_controller_set{};
|
||||
int callback_key;
|
||||
const Core::HID::NpadIdType npad_id;
|
||||
Core::System& system;
|
||||
Core::HID::EmulatedController* npad_device = nullptr;
|
||||
KernelHelpers::ServiceContext& service_context;
|
||||
Kernel::KEvent* activate_event = nullptr;
|
||||
Kernel::KEvent* deactivate_event = nullptr;
|
||||
Kernel::KEvent* availability_change_event = nullptr;
|
||||
|
||||
s32 protocol{};
|
||||
NFP::DeviceState device_state{NFP::DeviceState::Unavailable};
|
||||
|
||||
NFP::EncryptedNTAG215File encrypted_tag_data{};
|
||||
};
|
||||
|
||||
} // namespace Service::NFC
|
||||
17
src/core/hle/service/nfc/nfc_result.h
Normal file
17
src/core/hle/service/nfc/nfc_result.h
Normal file
@@ -0,0 +1,17 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/hle/result.h"
|
||||
|
||||
namespace Service::NFC {
|
||||
|
||||
constexpr Result DeviceNotFound(ErrorModule::NFC, 64);
|
||||
constexpr Result InvalidArgument(ErrorModule::NFC, 65);
|
||||
constexpr Result WrongDeviceState(ErrorModule::NFC, 73);
|
||||
constexpr Result NfcDisabled(ErrorModule::NFC, 80);
|
||||
constexpr Result TagRemoved(ErrorModule::NFC, 97);
|
||||
constexpr Result CorruptedData(ErrorModule::NFC, 144);
|
||||
|
||||
} // namespace Service::NFC
|
||||
365
src/core/hle/service/nfc/nfc_user.cpp
Normal file
365
src/core/hle/service/nfc/nfc_user.cpp
Normal file
@@ -0,0 +1,365 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "common/logging/log.h"
|
||||
#include "core/core.h"
|
||||
#include "core/hid/hid_types.h"
|
||||
#include "core/hle/ipc_helpers.h"
|
||||
#include "core/hle/kernel/k_event.h"
|
||||
#include "core/hle/service/nfc/nfc_device.h"
|
||||
#include "core/hle/service/nfc/nfc_result.h"
|
||||
#include "core/hle/service/nfc/nfc_user.h"
|
||||
#include "core/hle/service/time/clock_types.h"
|
||||
|
||||
namespace Service::NFC {
|
||||
|
||||
IUser::IUser(Core::System& system_)
|
||||
: ServiceFramework{system_, "NFC::IUser"}, service_context{system_, service_name} {
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, &IUser::Initialize, "InitializeOld"},
|
||||
{1, &IUser::Finalize, "FinalizeOld"},
|
||||
{2, &IUser::GetState, "GetStateOld"},
|
||||
{3, &IUser::IsNfcEnabled, "IsNfcEnabledOld"},
|
||||
{400, &IUser::Initialize, "Initialize"},
|
||||
{401, &IUser::Finalize, "Finalize"},
|
||||
{402, &IUser::GetState, "GetState"},
|
||||
{403, &IUser::IsNfcEnabled, "IsNfcEnabled"},
|
||||
{404, &IUser::ListDevices, "ListDevices"},
|
||||
{405, &IUser::GetDeviceState, "GetDeviceState"},
|
||||
{406, &IUser::GetNpadId, "GetNpadId"},
|
||||
{407, &IUser::AttachAvailabilityChangeEvent, "AttachAvailabilityChangeEvent"},
|
||||
{408, &IUser::StartDetection, "StartDetection"},
|
||||
{409, &IUser::StopDetection, "StopDetection"},
|
||||
{410, &IUser::GetTagInfo, "GetTagInfo"},
|
||||
{411, &IUser::AttachActivateEvent, "AttachActivateEvent"},
|
||||
{412, &IUser::AttachDeactivateEvent, "AttachDeactivateEvent"},
|
||||
{1000, nullptr, "ReadMifare"},
|
||||
{1001, nullptr, "WriteMifare"},
|
||||
{1300, &IUser::SendCommandByPassThrough, "SendCommandByPassThrough"},
|
||||
{1301, nullptr, "KeepPassThroughSession"},
|
||||
{1302, nullptr, "ReleasePassThroughSession"},
|
||||
};
|
||||
RegisterHandlers(functions);
|
||||
|
||||
availability_change_event = service_context.CreateEvent("IUser:AvailabilityChangeEvent");
|
||||
|
||||
for (u32 device_index = 0; device_index < 10; device_index++) {
|
||||
devices[device_index] =
|
||||
std::make_shared<NfcDevice>(Core::HID::IndexToNpadIdType(device_index), system,
|
||||
service_context, availability_change_event);
|
||||
}
|
||||
}
|
||||
|
||||
IUser ::~IUser() {
|
||||
availability_change_event->Close();
|
||||
}
|
||||
|
||||
void IUser::Initialize(Kernel::HLERequestContext& ctx) {
|
||||
LOG_INFO(Service_NFC, "called");
|
||||
|
||||
state = State::Initialized;
|
||||
|
||||
for (auto& device : devices) {
|
||||
device->Initialize();
|
||||
}
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2, 0};
|
||||
rb.Push(ResultSuccess);
|
||||
}
|
||||
|
||||
void IUser::Finalize(Kernel::HLERequestContext& ctx) {
|
||||
LOG_INFO(Service_NFC, "called");
|
||||
|
||||
state = State::NonInitialized;
|
||||
|
||||
for (auto& device : devices) {
|
||||
device->Finalize();
|
||||
}
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(ResultSuccess);
|
||||
}
|
||||
|
||||
void IUser::GetState(Kernel::HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Service_NFC, "called");
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(ResultSuccess);
|
||||
rb.PushEnum(state);
|
||||
}
|
||||
|
||||
void IUser::IsNfcEnabled(Kernel::HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Service_NFC, "called");
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(ResultSuccess);
|
||||
rb.Push(state != State::NonInitialized);
|
||||
}
|
||||
|
||||
void IUser::ListDevices(Kernel::HLERequestContext& ctx) {
|
||||
LOG_INFO(Service_NFC, "called");
|
||||
|
||||
if (state == State::NonInitialized) {
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(NfcDisabled);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ctx.CanWriteBuffer()) {
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(InvalidArgument);
|
||||
return;
|
||||
}
|
||||
|
||||
if (ctx.GetWriteBufferSize() == 0) {
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(InvalidArgument);
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<u64> nfp_devices;
|
||||
const std::size_t max_allowed_devices = ctx.GetWriteBufferNumElements<u64>();
|
||||
|
||||
for (auto& device : devices) {
|
||||
if (nfp_devices.size() >= max_allowed_devices) {
|
||||
continue;
|
||||
}
|
||||
if (device->GetCurrentState() != NFP::DeviceState::Unavailable) {
|
||||
nfp_devices.push_back(device->GetHandle());
|
||||
}
|
||||
}
|
||||
|
||||
if (nfp_devices.empty()) {
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(DeviceNotFound);
|
||||
return;
|
||||
}
|
||||
|
||||
ctx.WriteBuffer(nfp_devices);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(ResultSuccess);
|
||||
rb.Push(static_cast<s32>(nfp_devices.size()));
|
||||
}
|
||||
|
||||
void IUser::GetDeviceState(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto device_handle{rp.Pop<u64>()};
|
||||
LOG_DEBUG(Service_NFC, "called, device_handle={}", device_handle);
|
||||
|
||||
auto device = GetNfcDevice(device_handle);
|
||||
|
||||
if (!device.has_value()) {
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(DeviceNotFound);
|
||||
return;
|
||||
}
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(ResultSuccess);
|
||||
rb.PushEnum(device.value()->GetCurrentState());
|
||||
}
|
||||
|
||||
void IUser::GetNpadId(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto device_handle{rp.Pop<u64>()};
|
||||
LOG_DEBUG(Service_NFC, "called, device_handle={}", device_handle);
|
||||
|
||||
if (state == State::NonInitialized) {
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(NfcDisabled);
|
||||
return;
|
||||
}
|
||||
|
||||
auto device = GetNfcDevice(device_handle);
|
||||
|
||||
if (!device.has_value()) {
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(DeviceNotFound);
|
||||
return;
|
||||
}
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(ResultSuccess);
|
||||
rb.PushEnum(device.value()->GetNpadId());
|
||||
}
|
||||
|
||||
void IUser::AttachAvailabilityChangeEvent(Kernel::HLERequestContext& ctx) {
|
||||
LOG_INFO(Service_NFC, "called");
|
||||
|
||||
if (state == State::NonInitialized) {
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(NfcDisabled);
|
||||
return;
|
||||
}
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2, 1};
|
||||
rb.Push(ResultSuccess);
|
||||
rb.PushCopyObjects(availability_change_event->GetReadableEvent());
|
||||
}
|
||||
|
||||
void IUser::StartDetection(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto device_handle{rp.Pop<u64>()};
|
||||
const auto nfp_protocol{rp.Pop<s32>()};
|
||||
LOG_INFO(Service_NFC, "called, device_handle={}, nfp_protocol={}", device_handle, nfp_protocol);
|
||||
|
||||
if (state == State::NonInitialized) {
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(NfcDisabled);
|
||||
return;
|
||||
}
|
||||
|
||||
auto device = GetNfcDevice(device_handle);
|
||||
|
||||
if (!device.has_value()) {
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(DeviceNotFound);
|
||||
return;
|
||||
}
|
||||
|
||||
const auto result = device.value()->StartDetection(nfp_protocol);
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(result);
|
||||
}
|
||||
|
||||
void IUser::StopDetection(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto device_handle{rp.Pop<u64>()};
|
||||
LOG_INFO(Service_NFC, "called, device_handle={}", device_handle);
|
||||
|
||||
if (state == State::NonInitialized) {
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(NfcDisabled);
|
||||
return;
|
||||
}
|
||||
|
||||
auto device = GetNfcDevice(device_handle);
|
||||
|
||||
if (!device.has_value()) {
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(DeviceNotFound);
|
||||
return;
|
||||
}
|
||||
|
||||
const auto result = device.value()->StopDetection();
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(result);
|
||||
}
|
||||
|
||||
void IUser::GetTagInfo(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto device_handle{rp.Pop<u64>()};
|
||||
LOG_INFO(Service_NFC, "called, device_handle={}", device_handle);
|
||||
|
||||
if (state == State::NonInitialized) {
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(NfcDisabled);
|
||||
return;
|
||||
}
|
||||
|
||||
auto device = GetNfcDevice(device_handle);
|
||||
|
||||
if (!device.has_value()) {
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(DeviceNotFound);
|
||||
return;
|
||||
}
|
||||
|
||||
NFP::TagInfo tag_info{};
|
||||
const auto result = device.value()->GetTagInfo(tag_info);
|
||||
ctx.WriteBuffer(tag_info);
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(result);
|
||||
}
|
||||
|
||||
void IUser::AttachActivateEvent(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto device_handle{rp.Pop<u64>()};
|
||||
LOG_DEBUG(Service_NFC, "called, device_handle={}", device_handle);
|
||||
|
||||
if (state == State::NonInitialized) {
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(NfcDisabled);
|
||||
return;
|
||||
}
|
||||
|
||||
auto device = GetNfcDevice(device_handle);
|
||||
|
||||
if (!device.has_value()) {
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(DeviceNotFound);
|
||||
return;
|
||||
}
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2, 1};
|
||||
rb.Push(ResultSuccess);
|
||||
rb.PushCopyObjects(device.value()->GetActivateEvent());
|
||||
}
|
||||
|
||||
void IUser::AttachDeactivateEvent(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto device_handle{rp.Pop<u64>()};
|
||||
LOG_DEBUG(Service_NFC, "called, device_handle={}", device_handle);
|
||||
|
||||
if (state == State::NonInitialized) {
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(NfcDisabled);
|
||||
return;
|
||||
}
|
||||
|
||||
auto device = GetNfcDevice(device_handle);
|
||||
|
||||
if (!device.has_value()) {
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(DeviceNotFound);
|
||||
return;
|
||||
}
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2, 1};
|
||||
rb.Push(ResultSuccess);
|
||||
rb.PushCopyObjects(device.value()->GetDeactivateEvent());
|
||||
}
|
||||
|
||||
void IUser::SendCommandByPassThrough(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto device_handle{rp.Pop<u64>()};
|
||||
const auto timeout{rp.PopRaw<Time::Clock::TimeSpanType>()};
|
||||
const auto command_data{ctx.ReadBuffer()};
|
||||
|
||||
LOG_INFO(Service_NFC, "(STUBBED) called, device_handle={}, timeout={}, data_size={}",
|
||||
device_handle, timeout.ToSeconds(), command_data.size());
|
||||
|
||||
if (state == State::NonInitialized) {
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(NfcDisabled);
|
||||
return;
|
||||
}
|
||||
|
||||
auto device = GetNfcDevice(device_handle);
|
||||
|
||||
if (!device.has_value()) {
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(DeviceNotFound);
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<u8> out_data(1);
|
||||
// TODO: Request data from nfc device
|
||||
ctx.WriteBuffer(out_data);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(ResultSuccess);
|
||||
rb.Push(static_cast<u32>(out_data.size()));
|
||||
}
|
||||
|
||||
std::optional<std::shared_ptr<NfcDevice>> IUser::GetNfcDevice(u64 handle) {
|
||||
for (auto& device : devices) {
|
||||
if (device->GetHandle() == handle) {
|
||||
return device;
|
||||
}
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
} // namespace Service::NFC
|
||||
52
src/core/hle/service/nfc/nfc_user.h
Normal file
52
src/core/hle/service/nfc/nfc_user.h
Normal file
@@ -0,0 +1,52 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
|
||||
#include "core/hle/service/kernel_helpers.h"
|
||||
#include "core/hle/service/service.h"
|
||||
|
||||
namespace Service::NFC {
|
||||
class NfcDevice;
|
||||
|
||||
class IUser final : public ServiceFramework<IUser> {
|
||||
public:
|
||||
explicit IUser(Core::System& system_);
|
||||
~IUser();
|
||||
|
||||
private:
|
||||
enum class State : u32 {
|
||||
NonInitialized,
|
||||
Initialized,
|
||||
};
|
||||
|
||||
void Initialize(Kernel::HLERequestContext& ctx);
|
||||
void Finalize(Kernel::HLERequestContext& ctx);
|
||||
void GetState(Kernel::HLERequestContext& ctx);
|
||||
void IsNfcEnabled(Kernel::HLERequestContext& ctx);
|
||||
void ListDevices(Kernel::HLERequestContext& ctx);
|
||||
void GetDeviceState(Kernel::HLERequestContext& ctx);
|
||||
void GetNpadId(Kernel::HLERequestContext& ctx);
|
||||
void AttachAvailabilityChangeEvent(Kernel::HLERequestContext& ctx);
|
||||
void StartDetection(Kernel::HLERequestContext& ctx);
|
||||
void StopDetection(Kernel::HLERequestContext& ctx);
|
||||
void GetTagInfo(Kernel::HLERequestContext& ctx);
|
||||
void AttachActivateEvent(Kernel::HLERequestContext& ctx);
|
||||
void AttachDeactivateEvent(Kernel::HLERequestContext& ctx);
|
||||
void SendCommandByPassThrough(Kernel::HLERequestContext& ctx);
|
||||
|
||||
std::optional<std::shared_ptr<NfcDevice>> GetNfcDevice(u64 handle);
|
||||
|
||||
KernelHelpers::ServiceContext service_context;
|
||||
|
||||
std::array<std::shared_ptr<NfcDevice>, 10> devices{};
|
||||
|
||||
State state{State::NonInitialized};
|
||||
Kernel::KEvent* availability_change_event;
|
||||
};
|
||||
|
||||
} // namespace Service::NFC
|
||||
@@ -12,7 +12,6 @@
|
||||
#include "common/fs/fs.h"
|
||||
#include "common/fs/path_util.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/hle/service/mii/mii_manager.h"
|
||||
#include "core/hle/service/nfp/amiibo_crypto.h"
|
||||
|
||||
namespace Service::NFP::AmiiboCrypto {
|
||||
|
||||
@@ -2,10 +2,7 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
|
||||
#include "common/fs/file.h"
|
||||
#include "common/fs/path_util.h"
|
||||
#include "common/input.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/string_util.h"
|
||||
@@ -19,7 +16,6 @@
|
||||
#include "core/hle/service/mii/mii_manager.h"
|
||||
#include "core/hle/service/mii/types.h"
|
||||
#include "core/hle/service/nfp/amiibo_crypto.h"
|
||||
#include "core/hle/service/nfp/nfp.h"
|
||||
#include "core/hle/service/nfp/nfp_device.h"
|
||||
#include "core/hle/service/nfp/nfp_result.h"
|
||||
#include "core/hle/service/nfp/nfp_user.h"
|
||||
@@ -49,6 +45,8 @@ NfpDevice::NfpDevice(Core::HID::NpadIdType npad_id_, Core::System& system_,
|
||||
}
|
||||
|
||||
NfpDevice::~NfpDevice() {
|
||||
activate_event->Close();
|
||||
deactivate_event->Close();
|
||||
if (!is_controller_set) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -3,11 +3,10 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <span>
|
||||
#include <vector>
|
||||
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/common_types.h"
|
||||
#include "core/hle/service/kernel_helpers.h"
|
||||
#include "core/hle/service/nfp/nfp_types.h"
|
||||
#include "core/hle/service/service.h"
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
|
||||
#include "common/logging/log.h"
|
||||
#include "core/core.h"
|
||||
#include "core/hid/hid_types.h"
|
||||
@@ -55,8 +52,12 @@ IUser::IUser(Core::System& system_)
|
||||
}
|
||||
}
|
||||
|
||||
IUser ::~IUser() {
|
||||
availability_change_event->Close();
|
||||
}
|
||||
|
||||
void IUser::Initialize(Kernel::HLERequestContext& ctx) {
|
||||
LOG_INFO(Service_NFC, "called");
|
||||
LOG_INFO(Service_NFP, "called");
|
||||
|
||||
state = State::Initialized;
|
||||
|
||||
@@ -64,7 +65,7 @@ void IUser::Initialize(Kernel::HLERequestContext& ctx) {
|
||||
device->Initialize();
|
||||
}
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2, 0};
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(ResultSuccess);
|
||||
}
|
||||
|
||||
@@ -103,9 +104,9 @@ void IUser::ListDevices(Kernel::HLERequestContext& ctx) {
|
||||
}
|
||||
|
||||
std::vector<u64> nfp_devices;
|
||||
const std::size_t max_allowed_devices = ctx.GetWriteBufferSize() / sizeof(u64);
|
||||
const std::size_t max_allowed_devices = ctx.GetWriteBufferNumElements<u64>();
|
||||
|
||||
for (auto& device : devices) {
|
||||
for (const auto& device : devices) {
|
||||
if (nfp_devices.size() >= max_allowed_devices) {
|
||||
continue;
|
||||
}
|
||||
@@ -114,7 +115,7 @@ void IUser::ListDevices(Kernel::HLERequestContext& ctx) {
|
||||
}
|
||||
}
|
||||
|
||||
if (nfp_devices.size() == 0) {
|
||||
if (nfp_devices.empty()) {
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(DeviceNotFound);
|
||||
return;
|
||||
@@ -551,9 +552,9 @@ void IUser::AttachDeactivateEvent(Kernel::HLERequestContext& ctx) {
|
||||
}
|
||||
|
||||
void IUser::GetState(Kernel::HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Service_NFC, "called");
|
||||
LOG_DEBUG(Service_NFP, "called");
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 3, 0};
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(ResultSuccess);
|
||||
rb.PushEnum(state);
|
||||
}
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
|
||||
#include "core/hle/service/kernel_helpers.h"
|
||||
#include "core/hle/service/service.h"
|
||||
|
||||
@@ -12,6 +16,7 @@ class NfpDevice;
|
||||
class IUser final : public ServiceFramework<IUser> {
|
||||
public:
|
||||
explicit IUser(Core::System& system_);
|
||||
~IUser();
|
||||
|
||||
private:
|
||||
enum class State : u32 {
|
||||
|
||||
@@ -279,13 +279,10 @@ void IPlatformServiceManager::GetSharedFontInOrderOfPriority(Kernel::HLERequestC
|
||||
font_sizes.push_back(region.size);
|
||||
}
|
||||
|
||||
// Resize buffers if game requests smaller size output.
|
||||
font_codes.resize(
|
||||
std::min<std::size_t>(font_codes.size(), ctx.GetWriteBufferSize(0) / sizeof(u32)));
|
||||
font_offsets.resize(
|
||||
std::min<std::size_t>(font_offsets.size(), ctx.GetWriteBufferSize(1) / sizeof(u32)));
|
||||
font_sizes.resize(
|
||||
std::min<std::size_t>(font_sizes.size(), ctx.GetWriteBufferSize(2) / sizeof(u32)));
|
||||
// Resize buffers if game requests smaller size output
|
||||
font_codes.resize(std::min(font_codes.size(), ctx.GetWriteBufferNumElements<u32>(0)));
|
||||
font_offsets.resize(std::min(font_offsets.size(), ctx.GetWriteBufferNumElements<u32>(1)));
|
||||
font_sizes.resize(std::min(font_sizes.size(), ctx.GetWriteBufferNumElements<u32>(2)));
|
||||
|
||||
ctx.WriteBuffer(font_codes, 0);
|
||||
ctx.WriteBuffer(font_offsets, 1);
|
||||
|
||||
@@ -300,11 +300,10 @@ Kernel::KEvent* nvhost_ctrl_gpu::QueryEvent(u32 event_id) {
|
||||
return error_notifier_event;
|
||||
case 2:
|
||||
return unknown_event;
|
||||
default: {
|
||||
default:
|
||||
LOG_CRITICAL(Service_NVDRV, "Unknown Ctrl GPU Event {}", event_id);
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
} // namespace Service::Nvidia::Devices
|
||||
|
||||
@@ -364,11 +364,10 @@ Kernel::KEvent* nvhost_gpu::QueryEvent(u32 event_id) {
|
||||
return sm_exception_breakpoint_pause_report_event;
|
||||
case 3:
|
||||
return error_notifier_event;
|
||||
default: {
|
||||
default:
|
||||
LOG_CRITICAL(Service_NVDRV, "Unknown Ctrl GPU Event {}", event_id);
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
} // namespace Service::Nvidia::Devices
|
||||
|
||||
@@ -23,15 +23,17 @@ void BufferQueueCore::NotifyShutdown() {
|
||||
}
|
||||
|
||||
void BufferQueueCore::SignalDequeueCondition() {
|
||||
dequeue_possible.store(true);
|
||||
dequeue_condition.notify_all();
|
||||
}
|
||||
|
||||
bool BufferQueueCore::WaitForDequeueCondition() {
|
||||
bool BufferQueueCore::WaitForDequeueCondition(std::unique_lock<std::mutex>& lk) {
|
||||
if (is_shutting_down) {
|
||||
return false;
|
||||
}
|
||||
|
||||
dequeue_condition.wait(mutex);
|
||||
dequeue_condition.wait(lk, [&] { return dequeue_possible.load(); });
|
||||
dequeue_possible.store(false);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ public:
|
||||
|
||||
private:
|
||||
void SignalDequeueCondition();
|
||||
bool WaitForDequeueCondition();
|
||||
bool WaitForDequeueCondition(std::unique_lock<std::mutex>& lk);
|
||||
|
||||
s32 GetMinUndequeuedBufferCountLocked(bool async) const;
|
||||
s32 GetMinMaxBufferCountLocked(bool async) const;
|
||||
@@ -60,7 +60,8 @@ private:
|
||||
BufferQueueDefs::SlotsType slots{};
|
||||
std::vector<BufferItem> queue;
|
||||
s32 override_max_buffer_count{};
|
||||
mutable std::condition_variable_any dequeue_condition;
|
||||
std::condition_variable dequeue_condition;
|
||||
std::atomic<bool> dequeue_possible{};
|
||||
const bool use_async_buffer{}; // This is always disabled on HOS
|
||||
bool dequeue_buffer_cannot_block{};
|
||||
PixelFormat default_buffer_format{PixelFormat::Rgba8888};
|
||||
|
||||
@@ -121,8 +121,8 @@ Status BufferQueueProducer::SetBufferCount(s32 buffer_count) {
|
||||
return Status::NoError;
|
||||
}
|
||||
|
||||
Status BufferQueueProducer::WaitForFreeSlotThenRelock(bool async, s32* found,
|
||||
Status* return_flags) const {
|
||||
Status BufferQueueProducer::WaitForFreeSlotThenRelock(bool async, s32* found, Status* return_flags,
|
||||
std::unique_lock<std::mutex>& lk) const {
|
||||
bool try_again = true;
|
||||
|
||||
while (try_again) {
|
||||
@@ -214,7 +214,7 @@ Status BufferQueueProducer::WaitForFreeSlotThenRelock(bool async, s32* found,
|
||||
return Status::WouldBlock;
|
||||
}
|
||||
|
||||
if (!core->WaitForDequeueCondition()) {
|
||||
if (!core->WaitForDequeueCondition(lk)) {
|
||||
// We are no longer running
|
||||
return Status::NoError;
|
||||
}
|
||||
@@ -237,7 +237,7 @@ Status BufferQueueProducer::DequeueBuffer(s32* out_slot, Fence* out_fence, bool
|
||||
Status return_flags = Status::NoError;
|
||||
bool attached_by_consumer = false;
|
||||
{
|
||||
std::scoped_lock lock{core->mutex};
|
||||
std::unique_lock lock{core->mutex};
|
||||
core->WaitWhileAllocatingLocked();
|
||||
|
||||
if (format == PixelFormat::NoFormat) {
|
||||
@@ -248,7 +248,7 @@ Status BufferQueueProducer::DequeueBuffer(s32* out_slot, Fence* out_fence, bool
|
||||
usage |= core->consumer_usage_bit;
|
||||
|
||||
s32 found{};
|
||||
Status status = WaitForFreeSlotThenRelock(async, &found, &return_flags);
|
||||
Status status = WaitForFreeSlotThenRelock(async, &found, &return_flags, lock);
|
||||
if (status != Status::NoError) {
|
||||
return status;
|
||||
}
|
||||
@@ -400,13 +400,13 @@ Status BufferQueueProducer::AttachBuffer(s32* out_slot,
|
||||
return Status::BadValue;
|
||||
}
|
||||
|
||||
std::scoped_lock lock{core->mutex};
|
||||
std::unique_lock lock{core->mutex};
|
||||
core->WaitWhileAllocatingLocked();
|
||||
|
||||
Status return_flags = Status::NoError;
|
||||
s32 found{};
|
||||
|
||||
const auto status = WaitForFreeSlotThenRelock(false, &found, &return_flags);
|
||||
const auto status = WaitForFreeSlotThenRelock(false, &found, &return_flags, lock);
|
||||
if (status != Status::NoError) {
|
||||
return status;
|
||||
}
|
||||
|
||||
@@ -70,7 +70,8 @@ public:
|
||||
private:
|
||||
BufferQueueProducer(const BufferQueueProducer&) = delete;
|
||||
|
||||
Status WaitForFreeSlotThenRelock(bool async, s32* found, Status* return_flags) const;
|
||||
Status WaitForFreeSlotThenRelock(bool async, s32* found, Status* return_flags,
|
||||
std::unique_lock<std::mutex>& lk) const;
|
||||
|
||||
Kernel::KEvent* buffer_wait_event{};
|
||||
Service::KernelHelpers::ServiceContext& service_context;
|
||||
|
||||
@@ -228,6 +228,7 @@ Result ServiceFrameworkBase::HandleSyncRequest(Kernel::KServerSession& session,
|
||||
}
|
||||
|
||||
UNIMPLEMENTED_MSG("command_type={}", ctx.GetCommandType());
|
||||
break;
|
||||
}
|
||||
|
||||
// If emulation was shutdown, we are closing service threads, do not write the response back to
|
||||
|
||||
@@ -83,7 +83,7 @@ void PushResponseLanguageCode(Kernel::HLERequestContext& ctx, std::size_t num_la
|
||||
}
|
||||
|
||||
void GetAvailableLanguageCodesImpl(Kernel::HLERequestContext& ctx, std::size_t max_entries) {
|
||||
const std::size_t requested_amount = ctx.GetWriteBufferSize() / sizeof(LanguageCode);
|
||||
const std::size_t requested_amount = ctx.GetWriteBufferNumElements<LanguageCode>();
|
||||
const std::size_t max_amount = std::min(requested_amount, max_entries);
|
||||
const std::size_t copy_amount = std::min(available_language_codes.size(), max_amount);
|
||||
const std::size_t copy_size = copy_amount * sizeof(LanguageCode);
|
||||
|
||||
@@ -280,6 +280,7 @@ static constexpr int TransitionTime(int year, Rule rule, int offset) {
|
||||
}
|
||||
default:
|
||||
ASSERT(false);
|
||||
break;
|
||||
}
|
||||
return value + rule.transition_time + offset;
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/atomic_ops.h"
|
||||
#include "common/cache_management.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/page_table.h"
|
||||
@@ -329,6 +330,55 @@ struct Memory::Impl {
|
||||
});
|
||||
}
|
||||
|
||||
template <typename Callback>
|
||||
Result PerformCacheOperation(const Kernel::KProcess& process, VAddr dest_addr, std::size_t size,
|
||||
Callback&& cb) {
|
||||
class InvalidMemoryException : public std::exception {};
|
||||
|
||||
try {
|
||||
WalkBlock(
|
||||
process, dest_addr, size,
|
||||
[&](const std::size_t block_size, const VAddr current_vaddr) {
|
||||
LOG_ERROR(HW_Memory, "Unmapped cache maintenance @ {:#018X}", current_vaddr);
|
||||
throw InvalidMemoryException();
|
||||
},
|
||||
[&](const std::size_t block_size, u8* const host_ptr) { cb(block_size, host_ptr); },
|
||||
[&](const VAddr current_vaddr, const std::size_t block_size, u8* const host_ptr) {
|
||||
system.GPU().FlushRegion(current_vaddr, block_size);
|
||||
cb(block_size, host_ptr);
|
||||
},
|
||||
[](const std::size_t block_size) {});
|
||||
} catch (InvalidMemoryException&) {
|
||||
return Kernel::ResultInvalidCurrentMemory;
|
||||
}
|
||||
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result InvalidateDataCache(const Kernel::KProcess& process, VAddr dest_addr, std::size_t size) {
|
||||
auto perform = [&](const std::size_t block_size, u8* const host_ptr) {
|
||||
// Do nothing; this operation (dc ivac) cannot be supported
|
||||
// from EL0
|
||||
};
|
||||
return PerformCacheOperation(process, dest_addr, size, perform);
|
||||
}
|
||||
|
||||
Result StoreDataCache(const Kernel::KProcess& process, VAddr dest_addr, std::size_t size) {
|
||||
auto perform = [&](const std::size_t block_size, u8* const host_ptr) {
|
||||
// dc cvac: Store to point of coherency
|
||||
Common::DataCacheLineCleanByVAToPoC(host_ptr, block_size);
|
||||
};
|
||||
return PerformCacheOperation(process, dest_addr, size, perform);
|
||||
}
|
||||
|
||||
Result FlushDataCache(const Kernel::KProcess& process, VAddr dest_addr, std::size_t size) {
|
||||
auto perform = [&](const std::size_t block_size, u8* const host_ptr) {
|
||||
// dc civac: Store to point of coherency, and invalidate from cache
|
||||
Common::DataCacheLineCleanAndInvalidateByVAToPoC(host_ptr, block_size);
|
||||
};
|
||||
return PerformCacheOperation(process, dest_addr, size, perform);
|
||||
}
|
||||
|
||||
void MarkRegionDebug(VAddr vaddr, u64 size, bool debug) {
|
||||
if (vaddr == 0) {
|
||||
return;
|
||||
@@ -786,6 +836,21 @@ void Memory::ZeroBlock(const Kernel::KProcess& process, VAddr dest_addr, const s
|
||||
impl->ZeroBlock(process, dest_addr, size);
|
||||
}
|
||||
|
||||
Result Memory::InvalidateDataCache(const Kernel::KProcess& process, VAddr dest_addr,
|
||||
const std::size_t size) {
|
||||
return impl->InvalidateDataCache(process, dest_addr, size);
|
||||
}
|
||||
|
||||
Result Memory::StoreDataCache(const Kernel::KProcess& process, VAddr dest_addr,
|
||||
const std::size_t size) {
|
||||
return impl->StoreDataCache(process, dest_addr, size);
|
||||
}
|
||||
|
||||
Result Memory::FlushDataCache(const Kernel::KProcess& process, VAddr dest_addr,
|
||||
const std::size_t size) {
|
||||
return impl->FlushDataCache(process, dest_addr, size);
|
||||
}
|
||||
|
||||
void Memory::RasterizerMarkRegionCached(VAddr vaddr, u64 size, bool cached) {
|
||||
impl->RasterizerMarkRegionCached(vaddr, size, cached);
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include "common/common_types.h"
|
||||
#include "core/hle/result.h"
|
||||
|
||||
namespace Common {
|
||||
struct PageTable;
|
||||
@@ -449,6 +450,39 @@ public:
|
||||
*/
|
||||
void ZeroBlock(const Kernel::KProcess& process, VAddr dest_addr, std::size_t size);
|
||||
|
||||
/**
|
||||
* Invalidates a range of bytes within the current process' address space at the specified
|
||||
* virtual address.
|
||||
*
|
||||
* @param process The process that will have data invalidated within its address space.
|
||||
* @param dest_addr The destination virtual address to invalidate the data from.
|
||||
* @param size The size of the range to invalidate, in bytes.
|
||||
*
|
||||
*/
|
||||
Result InvalidateDataCache(const Kernel::KProcess& process, VAddr dest_addr, std::size_t size);
|
||||
|
||||
/**
|
||||
* Stores a range of bytes within the current process' address space at the specified
|
||||
* virtual address.
|
||||
*
|
||||
* @param process The process that will have data stored within its address space.
|
||||
* @param dest_addr The destination virtual address to store the data from.
|
||||
* @param size The size of the range to store, in bytes.
|
||||
*
|
||||
*/
|
||||
Result StoreDataCache(const Kernel::KProcess& process, VAddr dest_addr, std::size_t size);
|
||||
|
||||
/**
|
||||
* Flushes a range of bytes within the current process' address space at the specified
|
||||
* virtual address.
|
||||
*
|
||||
* @param process The process that will have data flushed within its address space.
|
||||
* @param dest_addr The destination virtual address to flush the data from.
|
||||
* @param size The size of the range to flush, in bytes.
|
||||
*
|
||||
*/
|
||||
Result FlushDataCache(const Kernel::KProcess& process, VAddr dest_addr, std::size_t size);
|
||||
|
||||
/**
|
||||
* Marks each page within the specified address range as cached or uncached.
|
||||
*
|
||||
|
||||
@@ -761,7 +761,7 @@ void DmntCheatVm::Execute(const CheatProcessMetadata& metadata) {
|
||||
u64 src_address =
|
||||
GetCheatProcessAddress(metadata, begin_cond->mem_type, begin_cond->rel_address);
|
||||
u64 src_value = 0;
|
||||
switch (store_static->bit_width) {
|
||||
switch (begin_cond->bit_width) {
|
||||
case 1:
|
||||
case 2:
|
||||
case 4:
|
||||
|
||||
@@ -10,8 +10,8 @@ namespace InputCommon {
|
||||
class TouchFromButtonDevice final : public Common::Input::InputDevice {
|
||||
public:
|
||||
using Button = std::unique_ptr<Common::Input::InputDevice>;
|
||||
TouchFromButtonDevice(Button button_, int touch_id_, float x_, float y_)
|
||||
: button(std::move(button_)), touch_id(touch_id_), x(x_), y(y_) {
|
||||
TouchFromButtonDevice(Button button_, float x_, float y_)
|
||||
: button(std::move(button_)), x(x_), y(y_) {
|
||||
last_button_value = false;
|
||||
button->SetCallback({
|
||||
.on_change =
|
||||
@@ -34,7 +34,6 @@ public:
|
||||
.pressed = button_status,
|
||||
.x = {},
|
||||
.y = {},
|
||||
.id = touch_id,
|
||||
};
|
||||
status.x.properties = properties;
|
||||
status.y.properties = properties;
|
||||
@@ -62,7 +61,6 @@ public:
|
||||
private:
|
||||
Button button;
|
||||
bool last_button_value;
|
||||
const int touch_id;
|
||||
const float x;
|
||||
const float y;
|
||||
const Common::Input::AnalogProperties properties{0.0f, 1.0f, 0.5f, 0.0f, false};
|
||||
@@ -73,10 +71,9 @@ std::unique_ptr<Common::Input::InputDevice> TouchFromButton::Create(
|
||||
const std::string null_engine = Common::ParamPackage{{"engine", "null"}}.Serialize();
|
||||
auto button = Common::Input::CreateDeviceFromString<Common::Input::InputDevice>(
|
||||
params.Get("button", null_engine));
|
||||
const auto touch_id = params.Get("touch_id", 0);
|
||||
const float x = params.Get("x", 0.0f) / 1280.0f;
|
||||
const float y = params.Get("y", 0.0f) / 720.0f;
|
||||
return std::make_unique<TouchFromButtonDevice>(std::move(button), touch_id, x, y);
|
||||
return std::make_unique<TouchFromButtonDevice>(std::move(button), x, y);
|
||||
}
|
||||
|
||||
} // namespace InputCommon
|
||||
|
||||
@@ -229,13 +229,12 @@ private:
|
||||
|
||||
class InputFromTouch final : public Common::Input::InputDevice {
|
||||
public:
|
||||
explicit InputFromTouch(PadIdentifier identifier_, int touch_id_, int button_, bool toggle_,
|
||||
bool inverted_, int axis_x_, int axis_y_,
|
||||
Common::Input::AnalogProperties properties_x_,
|
||||
explicit InputFromTouch(PadIdentifier identifier_, int button_, bool toggle_, bool inverted_,
|
||||
int axis_x_, int axis_y_, Common::Input::AnalogProperties properties_x_,
|
||||
Common::Input::AnalogProperties properties_y_,
|
||||
InputEngine* input_engine_)
|
||||
: identifier(identifier_), touch_id(touch_id_), button(button_), toggle(toggle_),
|
||||
inverted(inverted_), axis_x(axis_x_), axis_y(axis_y_), properties_x(properties_x_),
|
||||
: identifier(identifier_), button(button_), toggle(toggle_), inverted(inverted_),
|
||||
axis_x(axis_x_), axis_y(axis_y_), properties_x(properties_x_),
|
||||
properties_y(properties_y_), input_engine(input_engine_) {
|
||||
UpdateCallback engine_callback{[this]() { OnChange(); }};
|
||||
const InputIdentifier button_input_identifier{
|
||||
@@ -271,8 +270,7 @@ public:
|
||||
}
|
||||
|
||||
Common::Input::TouchStatus GetStatus() const {
|
||||
Common::Input::TouchStatus status;
|
||||
status.id = touch_id;
|
||||
Common::Input::TouchStatus status{};
|
||||
status.pressed = {
|
||||
.value = input_engine->GetButton(identifier, button),
|
||||
.inverted = inverted,
|
||||
@@ -307,7 +305,6 @@ public:
|
||||
|
||||
private:
|
||||
const PadIdentifier identifier;
|
||||
const int touch_id;
|
||||
const int button;
|
||||
const bool toggle;
|
||||
const bool inverted;
|
||||
@@ -919,7 +916,6 @@ std::unique_ptr<Common::Input::InputDevice> InputFactory::CreateTriggerDevice(
|
||||
|
||||
std::unique_ptr<Common::Input::InputDevice> InputFactory::CreateTouchDevice(
|
||||
const Common::ParamPackage& params) {
|
||||
const auto touch_id = params.Get("touch_id", 0);
|
||||
const auto deadzone = std::clamp(params.Get("deadzone", 0.0f), 0.0f, 1.0f);
|
||||
const auto range = std::clamp(params.Get("range", 1.0f), 0.25f, 1.50f);
|
||||
const auto threshold = std::clamp(params.Get("threshold", 0.5f), 0.0f, 1.0f);
|
||||
@@ -954,8 +950,8 @@ std::unique_ptr<Common::Input::InputDevice> InputFactory::CreateTouchDevice(
|
||||
input_engine->PreSetAxis(identifier, axis_x);
|
||||
input_engine->PreSetAxis(identifier, axis_y);
|
||||
input_engine->PreSetButton(identifier, button);
|
||||
return std::make_unique<InputFromTouch>(identifier, touch_id, button, toggle, inverted, axis_x,
|
||||
axis_y, properties_x, properties_y, input_engine.get());
|
||||
return std::make_unique<InputFromTouch>(identifier, button, toggle, inverted, axis_x, axis_y,
|
||||
properties_x, properties_y, input_engine.get());
|
||||
}
|
||||
|
||||
std::unique_ptr<Common::Input::InputDevice> InputFactory::CreateBatteryDevice(
|
||||
|
||||
@@ -320,6 +320,7 @@ void SetupOptions(const IR::Program& program, const Profile& profile,
|
||||
}
|
||||
if (stage == Stage::Fragment) {
|
||||
header += "OPTION ARB_draw_buffers;";
|
||||
header += "OPTION ARB_fragment_layer_viewport;";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -104,6 +104,9 @@ void EmitGetAttribute(EmitContext& ctx, IR::Inst& inst, IR::Attribute attr, Scal
|
||||
case IR::Attribute::PrimitiveId:
|
||||
ctx.Add("MOV.F {}.x,primitive.id;", inst);
|
||||
break;
|
||||
case IR::Attribute::Layer:
|
||||
ctx.Add("MOV.F {}.x,fragment.layer;", inst);
|
||||
break;
|
||||
case IR::Attribute::PositionX:
|
||||
case IR::Attribute::PositionY:
|
||||
case IR::Attribute::PositionZ:
|
||||
|
||||
@@ -205,6 +205,9 @@ void EmitGetAttribute(EmitContext& ctx, IR::Inst& inst, IR::Attribute attr,
|
||||
case IR::Attribute::PrimitiveId:
|
||||
ctx.AddF32("{}=itof(gl_PrimitiveID);", inst);
|
||||
break;
|
||||
case IR::Attribute::Layer:
|
||||
ctx.AddF32("{}=itof(gl_Layer);", inst);
|
||||
break;
|
||||
case IR::Attribute::PositionX:
|
||||
case IR::Attribute::PositionY:
|
||||
case IR::Attribute::PositionZ:
|
||||
|
||||
@@ -315,6 +315,8 @@ Id EmitGetAttribute(EmitContext& ctx, IR::Attribute attr, Id vertex) {
|
||||
switch (attr) {
|
||||
case IR::Attribute::PrimitiveId:
|
||||
return ctx.OpBitcast(ctx.F32[1], ctx.OpLoad(ctx.U32[1], ctx.primitive_id));
|
||||
case IR::Attribute::Layer:
|
||||
return ctx.OpBitcast(ctx.F32[1], ctx.OpLoad(ctx.U32[1], ctx.layer));
|
||||
case IR::Attribute::PositionX:
|
||||
case IR::Attribute::PositionY:
|
||||
case IR::Attribute::PositionZ:
|
||||
|
||||
@@ -1359,6 +1359,11 @@ void EmitContext::DefineInputs(const IR::Program& program) {
|
||||
if (loads[IR::Attribute::PrimitiveId]) {
|
||||
primitive_id = DefineInput(*this, U32[1], false, spv::BuiltIn::PrimitiveId);
|
||||
}
|
||||
if (loads[IR::Attribute::Layer]) {
|
||||
AddCapability(spv::Capability::Geometry);
|
||||
layer = DefineInput(*this, U32[1], false, spv::BuiltIn::Layer);
|
||||
Decorate(layer, spv::Decoration::Flat);
|
||||
}
|
||||
if (loads.AnyComponent(IR::Attribute::PositionX)) {
|
||||
const bool is_fragment{stage != Stage::Fragment};
|
||||
const spv::BuiltIn built_in{is_fragment ? spv::BuiltIn::Position : spv::BuiltIn::FragCoord};
|
||||
|
||||
@@ -28,6 +28,10 @@ add_library(video_core STATIC
|
||||
dirty_flags.h
|
||||
dma_pusher.cpp
|
||||
dma_pusher.h
|
||||
engines/sw_blitter/blitter.cpp
|
||||
engines/sw_blitter/blitter.h
|
||||
engines/sw_blitter/converter.cpp
|
||||
engines/sw_blitter/converter.h
|
||||
engines/const_buffer_info.h
|
||||
engines/engine_interface.h
|
||||
engines/engine_upload.cpp
|
||||
|
||||
@@ -992,7 +992,20 @@ void BufferCache<P>::BindHostIndexBuffer() {
|
||||
TouchBuffer(buffer, index_buffer.buffer_id);
|
||||
const u32 offset = buffer.Offset(index_buffer.cpu_addr);
|
||||
const u32 size = index_buffer.size;
|
||||
SynchronizeBuffer(buffer, index_buffer.cpu_addr, size);
|
||||
if (maxwell3d->inline_index_draw_indexes.size()) {
|
||||
if constexpr (USE_MEMORY_MAPS) {
|
||||
auto upload_staging = runtime.UploadStagingBuffer(size);
|
||||
std::array<BufferCopy, 1> copies{
|
||||
{BufferCopy{.src_offset = upload_staging.offset, .dst_offset = 0, .size = size}}};
|
||||
std::memcpy(upload_staging.mapped_span.data(),
|
||||
maxwell3d->inline_index_draw_indexes.data(), size);
|
||||
runtime.CopyBuffer(buffer, upload_staging.buffer, copies);
|
||||
} else {
|
||||
buffer.ImmediateUpload(0, maxwell3d->inline_index_draw_indexes);
|
||||
}
|
||||
} else {
|
||||
SynchronizeBuffer(buffer, index_buffer.cpu_addr, size);
|
||||
}
|
||||
if constexpr (HAS_FULL_INDEX_AND_PRIMITIVE_SUPPORT) {
|
||||
const u32 new_offset = offset + maxwell3d->regs.index_buffer.first *
|
||||
maxwell3d->regs.index_buffer.FormatSizeInBytes();
|
||||
@@ -1275,7 +1288,15 @@ void BufferCache<P>::UpdateIndexBuffer() {
|
||||
}
|
||||
flags[Dirty::IndexBuffer] = false;
|
||||
last_index_count = index_array.count;
|
||||
|
||||
if (maxwell3d->inline_index_draw_indexes.size()) {
|
||||
auto inline_index_size = static_cast<u32>(maxwell3d->inline_index_draw_indexes.size());
|
||||
index_buffer = Binding{
|
||||
.cpu_addr = 0,
|
||||
.size = inline_index_size,
|
||||
.buffer_id = CreateBuffer(0, inline_index_size),
|
||||
};
|
||||
return;
|
||||
}
|
||||
const GPUVAddr gpu_addr_begin = index_array.StartAddress();
|
||||
const GPUVAddr gpu_addr_end = index_array.EndAddress();
|
||||
const std::optional<VAddr> cpu_addr = gpu_memory->GpuToCpuAddress(gpu_addr_begin);
|
||||
@@ -1491,6 +1512,14 @@ typename BufferCache<P>::OverlapResult BufferCache<P>::ResolveOverlaps(VAddr cpu
|
||||
VAddr end = cpu_addr + wanted_size;
|
||||
int stream_score = 0;
|
||||
bool has_stream_leap = false;
|
||||
if (begin == 0) {
|
||||
return OverlapResult{
|
||||
.ids = std::move(overlap_ids),
|
||||
.begin = begin,
|
||||
.end = end,
|
||||
.has_stream_leap = has_stream_leap,
|
||||
};
|
||||
}
|
||||
for (; cpu_addr >> YUZU_PAGEBITS < Common::DivCeil(end, YUZU_PAGESIZE);
|
||||
cpu_addr += YUZU_PAGESIZE) {
|
||||
const BufferId overlap_id = page_table[cpu_addr >> YUZU_PAGEBITS];
|
||||
@@ -1713,12 +1742,12 @@ bool BufferCache<P>::InlineMemory(VAddr dest_address, size_t copy_size,
|
||||
SynchronizeBuffer(buffer, dest_address, static_cast<u32>(copy_size));
|
||||
|
||||
if constexpr (USE_MEMORY_MAPS) {
|
||||
auto upload_staging = runtime.UploadStagingBuffer(copy_size);
|
||||
std::array copies{BufferCopy{
|
||||
.src_offset = 0,
|
||||
.src_offset = upload_staging.offset,
|
||||
.dst_offset = buffer.Offset(dest_address),
|
||||
.size = copy_size,
|
||||
}};
|
||||
auto upload_staging = runtime.UploadStagingBuffer(copy_size);
|
||||
u8* const src_pointer = upload_staging.mapped_span.data();
|
||||
std::memcpy(src_pointer, inlined_buffer.data(), copy_size);
|
||||
runtime.CopyBuffer(buffer, upload_staging.buffer, copies);
|
||||
|
||||
@@ -20,7 +20,7 @@ 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>();
|
||||
fermi_2d = std::make_unique<Engines::Fermi2D>(*memory_manager);
|
||||
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);
|
||||
|
||||
@@ -51,11 +51,11 @@ void State::ProcessData(std::span<const u8> read_buffer) {
|
||||
} else {
|
||||
for (u32 line = 0; line < regs.line_count; ++line) {
|
||||
const GPUVAddr dest_line = address + static_cast<size_t>(line) * regs.dest.pitch;
|
||||
memory_manager.WriteBlockUnsafe(
|
||||
dest_line, read_buffer.data() + static_cast<size_t>(line) * regs.line_length_in,
|
||||
regs.line_length_in);
|
||||
std::span<const u8> buffer(read_buffer.data() +
|
||||
static_cast<size_t>(line) * regs.line_length_in,
|
||||
regs.line_length_in);
|
||||
rasterizer->AccelerateInlineToMemory(dest_line, regs.line_length_in, buffer);
|
||||
}
|
||||
memory_manager.InvalidateRegion(address, regs.dest.pitch * regs.line_count);
|
||||
}
|
||||
} else {
|
||||
u32 width = regs.dest.width;
|
||||
|
||||
@@ -3,17 +3,25 @@
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/microprofile.h"
|
||||
#include "video_core/engines/fermi_2d.h"
|
||||
#include "video_core/memory_manager.h"
|
||||
#include "video_core/engines/sw_blitter/blitter.h"
|
||||
#include "video_core/rasterizer_interface.h"
|
||||
#include "video_core/surface.h"
|
||||
#include "video_core/textures/decoders.h"
|
||||
|
||||
MICROPROFILE_DECLARE(GPU_BlitEngine);
|
||||
MICROPROFILE_DEFINE(GPU_BlitEngine, "GPU", "Blit Engine", MP_RGB(224, 224, 128));
|
||||
|
||||
using VideoCore::Surface::BytesPerBlock;
|
||||
using VideoCore::Surface::PixelFormatFromRenderTargetFormat;
|
||||
|
||||
namespace Tegra::Engines {
|
||||
|
||||
Fermi2D::Fermi2D() {
|
||||
using namespace Texture;
|
||||
|
||||
Fermi2D::Fermi2D(MemoryManager& memory_manager_) {
|
||||
sw_blitter = std::make_unique<Blitter::SoftwareBlitEngine>(memory_manager_);
|
||||
// Nvidia's OpenGL driver seems to assume these values
|
||||
regs.src.depth = 1;
|
||||
regs.dst.depth = 1;
|
||||
@@ -42,6 +50,7 @@ void Fermi2D::CallMultiMethod(u32 method, const u32* base_start, u32 amount, u32
|
||||
}
|
||||
|
||||
void Fermi2D::Blit() {
|
||||
MICROPROFILE_SCOPE(GPU_BlitEngine);
|
||||
LOG_DEBUG(HW_GPU, "called. source address=0x{:x}, destination address=0x{:x}",
|
||||
regs.src.Address(), regs.dst.Address());
|
||||
|
||||
@@ -52,9 +61,16 @@ void Fermi2D::Blit() {
|
||||
UNIMPLEMENTED_IF_MSG(regs.clip_enable != 0, "Clipped blit enabled");
|
||||
|
||||
const auto& args = regs.pixels_from_memory;
|
||||
constexpr s64 null_derivate = 1ULL << 32;
|
||||
Surface src = regs.src;
|
||||
const auto bytes_per_pixel = BytesPerBlock(PixelFormatFromRenderTargetFormat(src.format));
|
||||
const bool delegate_to_gpu = src.width > 512 && src.height > 512 && bytes_per_pixel <= 8 &&
|
||||
src.format != regs.dst.format;
|
||||
Config config{
|
||||
.operation = regs.operation,
|
||||
.filter = args.sample_mode.filter,
|
||||
.must_accelerate =
|
||||
args.du_dx != null_derivate || args.dv_dy != null_derivate || delegate_to_gpu,
|
||||
.dst_x0 = args.dst_x0,
|
||||
.dst_y0 = args.dst_y0,
|
||||
.dst_x1 = args.dst_x0 + args.dst_width,
|
||||
@@ -64,8 +80,7 @@ void Fermi2D::Blit() {
|
||||
.src_x1 = static_cast<s32>((args.du_dx * args.dst_width + args.src_x0) >> 32),
|
||||
.src_y1 = static_cast<s32>((args.dv_dy * args.dst_height + args.src_y0) >> 32),
|
||||
};
|
||||
Surface src = regs.src;
|
||||
const auto bytes_per_pixel = BytesPerBlock(PixelFormatFromRenderTargetFormat(src.format));
|
||||
|
||||
const auto need_align_to_pitch =
|
||||
src.linear == Tegra::Engines::Fermi2D::MemoryLayout::Pitch &&
|
||||
static_cast<s32>(src.width) == config.src_x1 &&
|
||||
@@ -78,8 +93,9 @@ void Fermi2D::Blit() {
|
||||
config.src_x1 -= config.src_x0;
|
||||
config.src_x0 = 0;
|
||||
}
|
||||
|
||||
if (!rasterizer->AccelerateSurfaceCopy(src, regs.dst, config)) {
|
||||
UNIMPLEMENTED();
|
||||
sw_blitter->Blit(src, regs.dst, config);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
#include "common/bit_field.h"
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/common_types.h"
|
||||
@@ -21,6 +22,10 @@ class RasterizerInterface;
|
||||
|
||||
namespace Tegra::Engines {
|
||||
|
||||
namespace Blitter {
|
||||
class SoftwareBlitEngine;
|
||||
}
|
||||
|
||||
/**
|
||||
* This Engine is known as G80_2D. Documentation can be found in:
|
||||
* https://github.com/envytools/envytools/blob/master/rnndb/graph/g80_2d.xml
|
||||
@@ -32,7 +37,7 @@ namespace Tegra::Engines {
|
||||
|
||||
class Fermi2D final : public EngineInterface {
|
||||
public:
|
||||
explicit Fermi2D();
|
||||
explicit Fermi2D(MemoryManager& memory_manager_);
|
||||
~Fermi2D() override;
|
||||
|
||||
/// Binds a rasterizer to this engine.
|
||||
@@ -286,6 +291,7 @@ public:
|
||||
struct Config {
|
||||
Operation operation;
|
||||
Filter filter;
|
||||
bool must_accelerate;
|
||||
s32 dst_x0;
|
||||
s32 dst_y0;
|
||||
s32 dst_x1;
|
||||
@@ -298,6 +304,7 @@ public:
|
||||
|
||||
private:
|
||||
VideoCore::RasterizerInterface* rasterizer = nullptr;
|
||||
std::unique_ptr<Blitter::SoftwareBlitEngine> sw_blitter;
|
||||
|
||||
/// Performs the copy from the source surface to the destination surface as configured in the
|
||||
/// registers.
|
||||
|
||||
@@ -232,7 +232,7 @@ void Maxwell3D::ProcessMethodCall(u32 method, u32 argument, u32 nonshadow_argume
|
||||
use_topology_override = true;
|
||||
return;
|
||||
case MAXWELL3D_REG_INDEX(clear_surface):
|
||||
return ProcessClearBuffers();
|
||||
return ProcessClearBuffers(1);
|
||||
case MAXWELL3D_REG_INDEX(report_semaphore.query):
|
||||
return ProcessQueryGet();
|
||||
case MAXWELL3D_REG_INDEX(render_enable.mode):
|
||||
@@ -249,9 +249,6 @@ void Maxwell3D::ProcessMethodCall(u32 method, u32 argument, u32 nonshadow_argume
|
||||
return;
|
||||
case MAXWELL3D_REG_INDEX(fragment_barrier):
|
||||
return rasterizer->FragmentBarrier();
|
||||
case MAXWELL3D_REG_INDEX(invalidate_texture_data_cache):
|
||||
rasterizer->InvalidateGPUCache();
|
||||
return rasterizer->WaitForIdle();
|
||||
case MAXWELL3D_REG_INDEX(tiled_cache_barrier):
|
||||
return rasterizer->TiledCacheBarrier();
|
||||
}
|
||||
@@ -511,10 +508,7 @@ void Maxwell3D::ProcessCounterReset() {
|
||||
|
||||
void Maxwell3D::ProcessSyncPoint() {
|
||||
const u32 sync_point = regs.sync_info.sync_point.Value();
|
||||
const u32 cache_flush = regs.sync_info.clean_l2.Value();
|
||||
if (cache_flush != 0) {
|
||||
rasterizer->InvalidateGPUCache();
|
||||
}
|
||||
[[maybe_unused]] const u32 cache_flush = regs.sync_info.clean_l2.Value();
|
||||
rasterizer->SignalSyncPoint(sync_point);
|
||||
}
|
||||
|
||||
@@ -596,8 +590,8 @@ u32 Maxwell3D::GetRegisterValue(u32 method) const {
|
||||
return regs.reg_array[method];
|
||||
}
|
||||
|
||||
void Maxwell3D::ProcessClearBuffers() {
|
||||
rasterizer->Clear();
|
||||
void Maxwell3D::ProcessClearBuffers(u32 layer_count) {
|
||||
rasterizer->Clear(layer_count);
|
||||
}
|
||||
|
||||
void Maxwell3D::ProcessDraw(u32 instance_count) {
|
||||
|
||||
@@ -3086,6 +3086,9 @@ public:
|
||||
|
||||
std::vector<u8> inline_index_draw_indexes;
|
||||
|
||||
/// Handles a write to the CLEAR_BUFFERS register.
|
||||
void ProcessClearBuffers(u32 layer_count);
|
||||
|
||||
private:
|
||||
void InitializeRegisterDefaults();
|
||||
|
||||
@@ -3120,9 +3123,6 @@ private:
|
||||
/// Handles firmware blob 4
|
||||
void ProcessFirmwareCall4();
|
||||
|
||||
/// Handles a write to the CLEAR_BUFFERS register.
|
||||
void ProcessClearBuffers();
|
||||
|
||||
/// Handles a write to the QUERY_GET register.
|
||||
void ProcessQueryGet();
|
||||
|
||||
|
||||
@@ -62,7 +62,8 @@ void MaxwellDMA::Launch() {
|
||||
|
||||
if (!is_src_pitch && !is_dst_pitch) {
|
||||
// If both the source and the destination are in block layout, assert.
|
||||
UNIMPLEMENTED_MSG("Tiled->Tiled DMA transfers are not yet implemented");
|
||||
CopyBlockLinearToBlockLinear();
|
||||
ReleaseSemaphore();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -291,6 +292,70 @@ void MaxwellDMA::FastCopyBlockLinearToPitch() {
|
||||
memory_manager.WriteBlock(regs.offset_out, write_buffer.data(), dst_size);
|
||||
}
|
||||
|
||||
void MaxwellDMA::CopyBlockLinearToBlockLinear() {
|
||||
UNIMPLEMENTED_IF(regs.src_params.block_size.width != 0);
|
||||
|
||||
const bool is_remapping = regs.launch_dma.remap_enable != 0;
|
||||
|
||||
// Deswizzle the input and copy it over.
|
||||
const Parameters& src = regs.src_params;
|
||||
const Parameters& dst = regs.dst_params;
|
||||
|
||||
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 src_width = src.width;
|
||||
u32 dst_width = dst.width;
|
||||
u32 x_elements = regs.line_length_in;
|
||||
u32 src_x_offset = src.origin.x;
|
||||
u32 dst_x_offset = dst.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))); },
|
||||
src_width, dst_width, x_elements, src_x_offset, dst_x_offset,
|
||||
static_cast<u32>(regs.offset_in), static_cast<u32>(regs.offset_out));
|
||||
src_width >>= bpp_shift;
|
||||
dst_width >>= bpp_shift;
|
||||
x_elements >>= bpp_shift;
|
||||
src_x_offset >>= bpp_shift;
|
||||
dst_x_offset >>= bpp_shift;
|
||||
}
|
||||
|
||||
const u32 bytes_per_pixel = base_bpp << bpp_shift;
|
||||
const size_t src_size = CalculateSize(true, bytes_per_pixel, src_width, src.height, src.depth,
|
||||
src.block_size.height, src.block_size.depth);
|
||||
const size_t dst_size = CalculateSize(true, bytes_per_pixel, dst_width, dst.height, dst.depth,
|
||||
dst.block_size.height, dst.block_size.depth);
|
||||
|
||||
const u32 pitch = x_elements * bytes_per_pixel;
|
||||
const size_t mid_buffer_size = pitch * regs.line_count;
|
||||
|
||||
if (read_buffer.size() < src_size) {
|
||||
read_buffer.resize(src_size);
|
||||
}
|
||||
if (write_buffer.size() < dst_size) {
|
||||
write_buffer.resize(dst_size);
|
||||
}
|
||||
|
||||
intermediate_buffer.resize(mid_buffer_size);
|
||||
|
||||
memory_manager.ReadBlock(regs.offset_in, read_buffer.data(), src_size);
|
||||
memory_manager.ReadBlock(regs.offset_out, write_buffer.data(), dst_size);
|
||||
|
||||
UnswizzleSubrect(intermediate_buffer, read_buffer, bytes_per_pixel, src_width, src.height,
|
||||
src.depth, src_x_offset, src.origin.y, x_elements, regs.line_count,
|
||||
src.block_size.height, src.block_size.depth, pitch);
|
||||
|
||||
SwizzleSubrect(write_buffer, intermediate_buffer, bytes_per_pixel, dst_width, dst.height,
|
||||
dst.depth, dst_x_offset, dst.origin.y, x_elements, regs.line_count,
|
||||
dst.block_size.height, dst.block_size.depth, pitch);
|
||||
|
||||
memory_manager.WriteBlock(regs.offset_out, write_buffer.data(), dst_size);
|
||||
}
|
||||
|
||||
void MaxwellDMA::ReleaseSemaphore() {
|
||||
const auto type = regs.launch_dma.semaphore_type;
|
||||
const GPUVAddr address = regs.semaphore.address;
|
||||
@@ -314,6 +379,7 @@ void MaxwellDMA::ReleaseSemaphore() {
|
||||
}
|
||||
default:
|
||||
ASSERT_MSG(false, "Unknown semaphore type: {}", static_cast<u32>(type.Value()));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -223,6 +223,8 @@ private:
|
||||
|
||||
void CopyPitchToBlockLinear();
|
||||
|
||||
void CopyBlockLinearToBlockLinear();
|
||||
|
||||
void FastCopyBlockLinearToPitch();
|
||||
|
||||
void ReleaseSemaphore();
|
||||
@@ -234,6 +236,7 @@ private:
|
||||
|
||||
std::vector<u8> read_buffer;
|
||||
std::vector<u8> write_buffer;
|
||||
std::vector<u8> intermediate_buffer;
|
||||
|
||||
static constexpr std::size_t NUM_REGS = 0x800;
|
||||
struct Regs {
|
||||
|
||||
@@ -50,6 +50,7 @@ void Puller::ProcessBindMethod(const MethodCall& method_call) {
|
||||
break;
|
||||
default:
|
||||
UNIMPLEMENTED_MSG("Unimplemented engine {:04X}", engine_id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,6 +66,7 @@ void Puller::ProcessFenceActionMethod() {
|
||||
break;
|
||||
default:
|
||||
UNIMPLEMENTED_MSG("Unimplemented operation {}", regs.fence_action.op.Value());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,7 +118,7 @@ void Puller::ProcessSemaphoreRelease() {
|
||||
std::function<void()> operation([this, sequence_address, payload] {
|
||||
memory_manager.Write<u32>(sequence_address, payload);
|
||||
});
|
||||
rasterizer->SyncOperation(std::move(operation));
|
||||
rasterizer->SignalFence(std::move(operation));
|
||||
}
|
||||
|
||||
void Puller::ProcessSemaphoreAcquire() {
|
||||
@@ -149,8 +151,8 @@ void Puller::CallPullerMethod(const MethodCall& method_call) {
|
||||
case BufferMethods::SemaphoreAddressLow:
|
||||
case BufferMethods::SemaphoreSequencePayload:
|
||||
case BufferMethods::SyncpointPayload:
|
||||
break;
|
||||
case BufferMethods::WrcacheFlush:
|
||||
break;
|
||||
case BufferMethods::RefCnt:
|
||||
rasterizer->SignalReference();
|
||||
break;
|
||||
@@ -228,6 +230,7 @@ void Puller::CallEngineMethod(const MethodCall& method_call) {
|
||||
break;
|
||||
default:
|
||||
UNIMPLEMENTED_MSG("Unimplemented engine");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -254,6 +257,7 @@ void Puller::CallEngineMultiMethod(u32 method, u32 subchannel, const u32* base_s
|
||||
break;
|
||||
default:
|
||||
UNIMPLEMENTED_MSG("Unimplemented engine");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
238
src/video_core/engines/sw_blitter/blitter.cpp
Normal file
238
src/video_core/engines/sw_blitter/blitter.cpp
Normal file
@@ -0,0 +1,238 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <vector>
|
||||
|
||||
#include "video_core/engines/sw_blitter/blitter.h"
|
||||
#include "video_core/engines/sw_blitter/converter.h"
|
||||
#include "video_core/memory_manager.h"
|
||||
#include "video_core/surface.h"
|
||||
#include "video_core/textures/decoders.h"
|
||||
|
||||
namespace Tegra {
|
||||
class MemoryManager;
|
||||
}
|
||||
|
||||
using VideoCore::Surface::BytesPerBlock;
|
||||
using VideoCore::Surface::PixelFormatFromRenderTargetFormat;
|
||||
|
||||
namespace Tegra::Engines::Blitter {
|
||||
|
||||
using namespace Texture;
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr size_t ir_components = 4;
|
||||
|
||||
void NearestNeighbor(std::span<const u8> input, std::span<u8> output, u32 src_width, u32 src_height,
|
||||
u32 dst_width, u32 dst_height, size_t bpp) {
|
||||
const size_t dx_du = std::llround((static_cast<f64>(src_width) / dst_width) * (1ULL << 32));
|
||||
const size_t dy_dv = std::llround((static_cast<f64>(src_height) / dst_height) * (1ULL << 32));
|
||||
size_t src_y = 0;
|
||||
for (u32 y = 0; y < dst_height; y++) {
|
||||
size_t src_x = 0;
|
||||
for (u32 x = 0; x < dst_width; x++) {
|
||||
const size_t read_from = ((src_y * src_width + src_x) >> 32) * bpp;
|
||||
const size_t write_to = (y * dst_width + x) * bpp;
|
||||
|
||||
std::memcpy(&output[write_to], &input[read_from], bpp);
|
||||
src_x += dx_du;
|
||||
}
|
||||
src_y += dy_dv;
|
||||
}
|
||||
}
|
||||
|
||||
void NearestNeighborFast(std::span<const f32> input, std::span<f32> output, u32 src_width,
|
||||
u32 src_height, u32 dst_width, u32 dst_height) {
|
||||
const size_t dx_du = std::llround((static_cast<f64>(src_width) / dst_width) * (1ULL << 32));
|
||||
const size_t dy_dv = std::llround((static_cast<f64>(src_height) / dst_height) * (1ULL << 32));
|
||||
size_t src_y = 0;
|
||||
for (u32 y = 0; y < dst_height; y++) {
|
||||
size_t src_x = 0;
|
||||
for (u32 x = 0; x < dst_width; x++) {
|
||||
const size_t read_from = ((src_y * src_width + src_x) >> 32) * ir_components;
|
||||
const size_t write_to = (y * dst_width + x) * ir_components;
|
||||
|
||||
std::memcpy(&output[write_to], &input[read_from], sizeof(f32) * ir_components);
|
||||
src_x += dx_du;
|
||||
}
|
||||
src_y += dy_dv;
|
||||
}
|
||||
}
|
||||
|
||||
void Bilinear(std::span<const f32> input, std::span<f32> output, size_t src_width,
|
||||
size_t src_height, size_t dst_width, size_t dst_height) {
|
||||
const auto bilinear_sample = [](std::span<const f32> x0_y0, std::span<const f32> x1_y0,
|
||||
std::span<const f32> x0_y1, std::span<const f32> x1_y1,
|
||||
f32 weight_x, f32 weight_y) {
|
||||
std::array<f32, ir_components> result{};
|
||||
for (size_t i = 0; i < ir_components; i++) {
|
||||
const f32 a = std::lerp(x0_y0[i], x1_y0[i], weight_x);
|
||||
const f32 b = std::lerp(x0_y1[i], x1_y1[i], weight_x);
|
||||
result[i] = std::lerp(a, b, weight_y);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
const f32 dx_du =
|
||||
dst_width > 1 ? static_cast<f32>(src_width - 1) / static_cast<f32>(dst_width - 1) : 0.f;
|
||||
const f32 dy_dv =
|
||||
dst_height > 1 ? static_cast<f32>(src_height - 1) / static_cast<f32>(dst_height - 1) : 0.f;
|
||||
for (u32 y = 0; y < dst_height; y++) {
|
||||
for (u32 x = 0; x < dst_width; x++) {
|
||||
const f32 x_low = std::floor(static_cast<f32>(x) * dx_du);
|
||||
const f32 y_low = std::floor(static_cast<f32>(y) * dy_dv);
|
||||
const f32 x_high = std::ceil(static_cast<f32>(x) * dx_du);
|
||||
const f32 y_high = std::ceil(static_cast<f32>(y) * dy_dv);
|
||||
const f32 weight_x = (static_cast<f32>(x) * dx_du) - x_low;
|
||||
const f32 weight_y = (static_cast<f32>(y) * dy_dv) - y_low;
|
||||
|
||||
const auto read_src = [&](f32 in_x, f32 in_y) {
|
||||
const size_t read_from =
|
||||
((static_cast<size_t>(in_x) * src_width + static_cast<size_t>(in_y)) >> 32) *
|
||||
ir_components;
|
||||
return std::span<const f32>(&input[read_from], ir_components);
|
||||
};
|
||||
|
||||
auto x0_y0 = read_src(x_low, y_low);
|
||||
auto x1_y0 = read_src(x_high, y_low);
|
||||
auto x0_y1 = read_src(x_low, y_high);
|
||||
auto x1_y1 = read_src(x_high, y_high);
|
||||
|
||||
const auto result = bilinear_sample(x0_y0, x1_y0, x0_y1, x1_y1, weight_x, weight_y);
|
||||
|
||||
const size_t write_to = (y * dst_width + x) * ir_components;
|
||||
|
||||
std::memcpy(&output[write_to], &result, sizeof(f32) * ir_components);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
struct SoftwareBlitEngine::BlitEngineImpl {
|
||||
std::vector<u8> tmp_buffer;
|
||||
std::vector<u8> src_buffer;
|
||||
std::vector<u8> dst_buffer;
|
||||
std::vector<f32> intermediate_src;
|
||||
std::vector<f32> intermediate_dst;
|
||||
ConverterFactory converter_factory;
|
||||
};
|
||||
|
||||
SoftwareBlitEngine::SoftwareBlitEngine(MemoryManager& memory_manager_)
|
||||
: memory_manager{memory_manager_} {
|
||||
impl = std::make_unique<BlitEngineImpl>();
|
||||
}
|
||||
|
||||
SoftwareBlitEngine::~SoftwareBlitEngine() = default;
|
||||
|
||||
bool SoftwareBlitEngine::Blit(Fermi2D::Surface& src, Fermi2D::Surface& dst,
|
||||
Fermi2D::Config& config) {
|
||||
const auto get_surface_size = [](Fermi2D::Surface& surface, u32 bytes_per_pixel) {
|
||||
if (surface.linear == Fermi2D::MemoryLayout::BlockLinear) {
|
||||
return CalculateSize(true, bytes_per_pixel, surface.width, surface.height,
|
||||
surface.depth, surface.block_height, surface.block_depth);
|
||||
}
|
||||
return static_cast<size_t>(surface.pitch * surface.height);
|
||||
};
|
||||
const auto process_pitch_linear = [](bool unpack, std::span<const u8> input,
|
||||
std::span<u8> output, u32 extent_x, u32 extent_y,
|
||||
u32 pitch, u32 x0, u32 y0, size_t bpp) {
|
||||
const size_t base_offset = x0 * bpp;
|
||||
const size_t copy_size = extent_x * bpp;
|
||||
for (u32 y = y0; y < extent_y; y++) {
|
||||
const size_t first_offset = y * pitch + base_offset;
|
||||
const size_t second_offset = y * extent_x * bpp;
|
||||
u8* write_to = unpack ? &output[first_offset] : &output[second_offset];
|
||||
const u8* read_from = unpack ? &input[second_offset] : &input[first_offset];
|
||||
std::memcpy(write_to, read_from, copy_size);
|
||||
}
|
||||
};
|
||||
|
||||
const u32 src_extent_x = config.src_x1 - config.src_x0;
|
||||
const u32 src_extent_y = config.src_y1 - config.src_y0;
|
||||
|
||||
const u32 dst_extent_x = config.dst_x1 - config.dst_x0;
|
||||
const u32 dst_extent_y = config.dst_y1 - config.dst_y0;
|
||||
const auto src_bytes_per_pixel = BytesPerBlock(PixelFormatFromRenderTargetFormat(src.format));
|
||||
const auto dst_bytes_per_pixel = BytesPerBlock(PixelFormatFromRenderTargetFormat(dst.format));
|
||||
const size_t src_size = get_surface_size(src, src_bytes_per_pixel);
|
||||
impl->tmp_buffer.resize(src_size);
|
||||
memory_manager.ReadBlock(src.Address(), impl->tmp_buffer.data(), src_size);
|
||||
|
||||
const size_t src_copy_size = src_extent_x * src_extent_y * src_bytes_per_pixel;
|
||||
|
||||
const size_t dst_copy_size = dst_extent_x * dst_extent_y * dst_bytes_per_pixel;
|
||||
|
||||
impl->src_buffer.resize(src_copy_size);
|
||||
|
||||
const bool no_passthrough =
|
||||
src.format != dst.format || src_extent_x != dst_extent_x || src_extent_y != dst_extent_y;
|
||||
|
||||
const auto convertion_phase_same_format = [&]() {
|
||||
NearestNeighbor(impl->src_buffer, impl->dst_buffer, src_extent_x, src_extent_y,
|
||||
dst_extent_x, dst_extent_y, dst_bytes_per_pixel);
|
||||
};
|
||||
|
||||
const auto convertion_phase_ir = [&]() {
|
||||
auto* input_converter = impl->converter_factory.GetFormatConverter(src.format);
|
||||
impl->intermediate_src.resize((src_copy_size / src_bytes_per_pixel) * ir_components);
|
||||
impl->intermediate_dst.resize((dst_copy_size / dst_bytes_per_pixel) * ir_components);
|
||||
input_converter->ConvertTo(impl->src_buffer, impl->intermediate_src);
|
||||
|
||||
if (config.filter != Fermi2D::Filter::Bilinear) {
|
||||
NearestNeighborFast(impl->intermediate_src, impl->intermediate_dst, src_extent_x,
|
||||
src_extent_y, dst_extent_x, dst_extent_y);
|
||||
} else {
|
||||
Bilinear(impl->intermediate_src, impl->intermediate_dst, src_extent_x, src_extent_y,
|
||||
dst_extent_x, dst_extent_y);
|
||||
}
|
||||
|
||||
auto* output_converter = impl->converter_factory.GetFormatConverter(dst.format);
|
||||
output_converter->ConvertFrom(impl->intermediate_dst, impl->dst_buffer);
|
||||
};
|
||||
|
||||
// Do actuall Blit
|
||||
|
||||
impl->dst_buffer.resize(dst_copy_size);
|
||||
if (src.linear == Fermi2D::MemoryLayout::BlockLinear) {
|
||||
UnswizzleSubrect(impl->src_buffer, impl->tmp_buffer, src_bytes_per_pixel, src.width,
|
||||
src.height, src.depth, config.src_x0, config.src_y0, src_extent_x,
|
||||
src_extent_y, src.block_height, src.block_depth,
|
||||
src_extent_x * src_bytes_per_pixel);
|
||||
} else {
|
||||
process_pitch_linear(false, impl->tmp_buffer, impl->src_buffer, src_extent_x, src_extent_y,
|
||||
src.pitch, config.src_x0, config.src_y0, src_bytes_per_pixel);
|
||||
}
|
||||
|
||||
// Conversion Phase
|
||||
if (no_passthrough) {
|
||||
if (src.format != dst.format || config.filter == Fermi2D::Filter::Bilinear) {
|
||||
convertion_phase_ir();
|
||||
} else {
|
||||
convertion_phase_same_format();
|
||||
}
|
||||
} else {
|
||||
impl->dst_buffer.swap(impl->src_buffer);
|
||||
}
|
||||
|
||||
const size_t dst_size = get_surface_size(dst, dst_bytes_per_pixel);
|
||||
impl->tmp_buffer.resize(dst_size);
|
||||
memory_manager.ReadBlock(dst.Address(), impl->tmp_buffer.data(), dst_size);
|
||||
|
||||
if (dst.linear == Fermi2D::MemoryLayout::BlockLinear) {
|
||||
SwizzleSubrect(impl->tmp_buffer, impl->dst_buffer, dst_bytes_per_pixel, dst.width,
|
||||
dst.height, dst.depth, config.dst_x0, config.dst_y0, dst_extent_x,
|
||||
dst_extent_y, dst.block_height, dst.block_depth,
|
||||
dst_extent_x * dst_bytes_per_pixel);
|
||||
} else {
|
||||
process_pitch_linear(true, impl->dst_buffer, impl->tmp_buffer, dst_extent_x, dst_extent_y,
|
||||
dst.pitch, config.dst_x0, config.dst_y0,
|
||||
static_cast<size_t>(dst_bytes_per_pixel));
|
||||
}
|
||||
memory_manager.WriteBlock(dst.Address(), impl->tmp_buffer.data(), dst_size);
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace Tegra::Engines::Blitter
|
||||
27
src/video_core/engines/sw_blitter/blitter.h
Normal file
27
src/video_core/engines/sw_blitter/blitter.h
Normal file
@@ -0,0 +1,27 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "video_core/engines/fermi_2d.h"
|
||||
|
||||
namespace Tegra {
|
||||
class MemoryManager;
|
||||
}
|
||||
|
||||
namespace Tegra::Engines::Blitter {
|
||||
|
||||
class SoftwareBlitEngine {
|
||||
public:
|
||||
explicit SoftwareBlitEngine(MemoryManager& memory_manager_);
|
||||
~SoftwareBlitEngine();
|
||||
|
||||
bool Blit(Fermi2D::Surface& src, Fermi2D::Surface& dst, Fermi2D::Config& copy_config);
|
||||
|
||||
private:
|
||||
MemoryManager& memory_manager;
|
||||
struct BlitEngineImpl;
|
||||
std::unique_ptr<BlitEngineImpl> impl;
|
||||
};
|
||||
|
||||
} // namespace Tegra::Engines::Blitter
|
||||
1234
src/video_core/engines/sw_blitter/converter.cpp
Normal file
1234
src/video_core/engines/sw_blitter/converter.cpp
Normal file
File diff suppressed because it is too large
Load Diff
36
src/video_core/engines/sw_blitter/converter.h
Normal file
36
src/video_core/engines/sw_blitter/converter.h
Normal file
@@ -0,0 +1,36 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <span>
|
||||
|
||||
#include "common/common_types.h"
|
||||
|
||||
#include "video_core/gpu.h"
|
||||
|
||||
namespace Tegra::Engines::Blitter {
|
||||
|
||||
class Converter {
|
||||
public:
|
||||
virtual void ConvertTo(std::span<const u8> input, std::span<f32> output) = 0;
|
||||
virtual void ConvertFrom(std::span<const f32> input, std::span<u8> output) = 0;
|
||||
virtual ~Converter() = default;
|
||||
};
|
||||
|
||||
class ConverterFactory {
|
||||
public:
|
||||
ConverterFactory();
|
||||
~ConverterFactory();
|
||||
|
||||
Converter* GetFormatConverter(RenderTargetFormat format);
|
||||
|
||||
private:
|
||||
Converter* BuildConverter(RenderTargetFormat format);
|
||||
|
||||
struct ConverterFactoryImpl;
|
||||
std::unique_ptr<ConverterFactoryImpl> impl;
|
||||
};
|
||||
|
||||
} // namespace Tegra::Engines::Blitter
|
||||
136
src/video_core/engines/sw_blitter/generate_converters.py
Normal file
136
src/video_core/engines/sw_blitter/generate_converters.py
Normal file
@@ -0,0 +1,136 @@
|
||||
# SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import re
|
||||
|
||||
class Format:
|
||||
def __init__(self, string_value):
|
||||
self.name = string_value
|
||||
tmp = string_value.split('_')
|
||||
self.component_type = tmp[1]
|
||||
component_data = re.findall(r"\w\d+", tmp[0])
|
||||
self.num_components = len(component_data)
|
||||
sizes = []
|
||||
swizzle = []
|
||||
for data in component_data:
|
||||
swizzle.append(data[0])
|
||||
sizes.append(int(data[1:]))
|
||||
self.sizes = sizes
|
||||
self.swizzle = swizzle
|
||||
|
||||
def build_component_type_array(self):
|
||||
result = "{ "
|
||||
b = False
|
||||
for i in range(0, self.num_components):
|
||||
if b:
|
||||
result += ", "
|
||||
b = True
|
||||
result += "ComponentType::" + self.component_type
|
||||
result += " }"
|
||||
return result
|
||||
|
||||
def build_component_sizes_array(self):
|
||||
result = "{ "
|
||||
b = False
|
||||
for i in range(0, self.num_components):
|
||||
if b:
|
||||
result += ", "
|
||||
b = True
|
||||
result += str(self.sizes[i])
|
||||
result += " }"
|
||||
return result
|
||||
|
||||
def build_component_swizzle_array(self):
|
||||
result = "{ "
|
||||
b = False
|
||||
for i in range(0, self.num_components):
|
||||
if b:
|
||||
result += ", "
|
||||
b = True
|
||||
swizzle = self.swizzle[i]
|
||||
if swizzle == "X":
|
||||
swizzle = "None"
|
||||
result += "Swizzle::" + swizzle
|
||||
result += " }"
|
||||
return result
|
||||
|
||||
def print_declaration(self):
|
||||
print("struct " + self.name + "Traits {")
|
||||
print(" static constexpr size_t num_components = " + str(self.num_components) + ";")
|
||||
print(" static constexpr std::array<ComponentType, num_components> component_types = " + self.build_component_type_array() + ";")
|
||||
print(" static constexpr std::array<size_t, num_components> component_sizes = " + self.build_component_sizes_array() + ";")
|
||||
print(" static constexpr std::array<Swizzle, num_components> component_swizzle = " + self.build_component_swizzle_array() + ";")
|
||||
print("};\n")
|
||||
|
||||
def print_case(self):
|
||||
print("case RenderTargetFormat::" + self.name + ":")
|
||||
print(" return impl->converters_cache")
|
||||
print(" .emplace(format, std::make_unique<ConverterImpl<" + self.name + "Traits>>())")
|
||||
print(" .first->second.get();")
|
||||
print(" break;")
|
||||
|
||||
txt = """
|
||||
R32G32B32A32_FLOAT
|
||||
R32G32B32A32_SINT
|
||||
R32G32B32A32_UINT
|
||||
R32G32B32X32_FLOAT
|
||||
R32G32B32X32_SINT
|
||||
R32G32B32X32_UINT
|
||||
R16G16B16A16_UNORM
|
||||
R16G16B16A16_SNORM
|
||||
R16G16B16A16_SINT
|
||||
R16G16B16A16_UINT
|
||||
R16G16B16A16_FLOAT
|
||||
R32G32_FLOAT
|
||||
R32G32_SINT
|
||||
R32G32_UINT
|
||||
R16G16B16X16_FLOAT
|
||||
A8R8G8B8_UNORM
|
||||
A8R8G8B8_SRGB
|
||||
A2B10G10R10_UNORM
|
||||
A2B10G10R10_UINT
|
||||
A2R10G10B10_UNORM
|
||||
A8B8G8R8_UNORM
|
||||
A8B8G8R8_SRGB
|
||||
A8B8G8R8_SNORM
|
||||
A8B8G8R8_SINT
|
||||
A8B8G8R8_UINT
|
||||
R16G16_UNORM
|
||||
R16G16_SNORM
|
||||
R16G16_SINT
|
||||
R16G16_UINT
|
||||
R16G16_FLOAT
|
||||
B10G11R11_FLOAT
|
||||
R32_SINT
|
||||
R32_UINT
|
||||
R32_FLOAT
|
||||
X8R8G8B8_UNORM
|
||||
X8R8G8B8_SRGB
|
||||
R5G6B5_UNORM
|
||||
A1R5G5B5_UNORM
|
||||
R8G8_UNORM
|
||||
R8G8_SNORM
|
||||
R8G8_SINT
|
||||
R8G8_UINT
|
||||
R16_UNORM
|
||||
R16_SNORM
|
||||
R16_SINT
|
||||
R16_UINT
|
||||
R16_FLOAT
|
||||
R8_UNORM
|
||||
R8_SNORM
|
||||
R8_SINT
|
||||
R8_UINT
|
||||
X1R5G5B5_UNORM
|
||||
X8B8G8R8_UNORM
|
||||
X8B8G8R8_SRGB
|
||||
"""
|
||||
|
||||
x = txt.split()
|
||||
y = list(map(lambda a: Format(a), x))
|
||||
formats = list(y)
|
||||
for format in formats:
|
||||
format.print_declaration()
|
||||
|
||||
for format in formats:
|
||||
format.print_case()
|
||||
@@ -27,12 +27,12 @@ struct CommandList;
|
||||
// TODO: Implement the commented ones
|
||||
enum class RenderTargetFormat : u32 {
|
||||
NONE = 0x0,
|
||||
R32B32G32A32_FLOAT = 0xC0,
|
||||
R32G32B32A32_FLOAT = 0xC0,
|
||||
R32G32B32A32_SINT = 0xC1,
|
||||
R32G32B32A32_UINT = 0xC2,
|
||||
// R32G32B32X32_FLOAT = 0xC3,
|
||||
// R32G32B32X32_SINT = 0xC4,
|
||||
// R32G32B32X32_UINT = 0xC5,
|
||||
R32G32B32X32_FLOAT = 0xC3,
|
||||
R32G32B32X32_SINT = 0xC4,
|
||||
R32G32B32X32_UINT = 0xC5,
|
||||
R16G16B16A16_UNORM = 0xC6,
|
||||
R16G16B16A16_SNORM = 0xC7,
|
||||
R16G16B16A16_SINT = 0xC8,
|
||||
@@ -56,13 +56,13 @@ enum class RenderTargetFormat : u32 {
|
||||
R16G16_SINT = 0xDC,
|
||||
R16G16_UINT = 0xDD,
|
||||
R16G16_FLOAT = 0xDE,
|
||||
// A2R10G10B10_UNORM = 0xDF,
|
||||
A2R10G10B10_UNORM = 0xDF,
|
||||
B10G11R11_FLOAT = 0xE0,
|
||||
R32_SINT = 0xE3,
|
||||
R32_UINT = 0xE4,
|
||||
R32_FLOAT = 0xE5,
|
||||
// X8R8G8B8_UNORM = 0xE6,
|
||||
// X8R8G8B8_SRGB = 0xE7,
|
||||
X8R8G8B8_UNORM = 0xE6,
|
||||
X8R8G8B8_SRGB = 0xE7,
|
||||
R5G6B5_UNORM = 0xE8,
|
||||
A1R5G5B5_UNORM = 0xE9,
|
||||
R8G8_UNORM = 0xEA,
|
||||
@@ -79,11 +79,11 @@ enum class RenderTargetFormat : u32 {
|
||||
R8_SINT = 0xF5,
|
||||
R8_UINT = 0xF6,
|
||||
|
||||
/*
|
||||
A8_UNORM = 0xF7,
|
||||
// A8_UNORM = 0xF7,
|
||||
X1R5G5B5_UNORM = 0xF8,
|
||||
X8B8G8R8_UNORM = 0xF9,
|
||||
X8B8G8R8_SRGB = 0xFA,
|
||||
/*
|
||||
Z1R5G5B5_UNORM = 0xFB,
|
||||
O1R5G5B5_UNORM = 0xFC,
|
||||
Z8R8G8B8_UNORM = 0xFD,
|
||||
|
||||
@@ -126,11 +126,25 @@ void HLE_3F5E74B9C9A50164(Engines::Maxwell3D& maxwell3d, const std::vector<u32>&
|
||||
}
|
||||
}
|
||||
|
||||
constexpr std::array<std::pair<u64, HLEFunction>, 4> hle_funcs{{
|
||||
// Multi-layer Clear
|
||||
void HLE_EAD26C3E2109B06B(Engines::Maxwell3D& maxwell3d, const std::vector<u32>& parameters) {
|
||||
ASSERT(parameters.size() == 1);
|
||||
|
||||
const Engines::Maxwell3D::Regs::ClearSurface clear_params{parameters[0]};
|
||||
const u32 rt_index = clear_params.RT;
|
||||
const u32 num_layers = maxwell3d.regs.rt[rt_index].depth;
|
||||
ASSERT(clear_params.layer == 0);
|
||||
|
||||
maxwell3d.regs.clear_surface.raw = clear_params.raw;
|
||||
maxwell3d.ProcessClearBuffers(num_layers);
|
||||
}
|
||||
|
||||
constexpr std::array<std::pair<u64, HLEFunction>, 5> hle_funcs{{
|
||||
{0x771BB18C62444DA0, &HLE_771BB18C62444DA0},
|
||||
{0x0D61FC9FAAC9FCAD, &HLE_0D61FC9FAAC9FCAD},
|
||||
{0x0217920100488FF7, &HLE_0217920100488FF7},
|
||||
{0x3F5E74B9C9A50164, &HLE_3F5E74B9C9A50164},
|
||||
{0xEAD26C3E2109B06B, &HLE_EAD26C3E2109B06B},
|
||||
}};
|
||||
|
||||
class HLEMacroImpl final : public CachedMacro {
|
||||
|
||||
@@ -201,6 +201,7 @@ bool MacroInterpreterImpl::Step(bool is_delay_slot) {
|
||||
}
|
||||
default:
|
||||
UNIMPLEMENTED_MSG("Unimplemented macro operation {}", opcode.operation.Value());
|
||||
break;
|
||||
}
|
||||
|
||||
// An instruction with the Exit flag will not actually
|
||||
@@ -297,6 +298,7 @@ void MacroInterpreterImpl::ProcessResult(Macro::ResultOperation operation, u32 r
|
||||
break;
|
||||
default:
|
||||
UNIMPLEMENTED_MSG("Unimplemented result operation {}", operation);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -652,6 +652,7 @@ void MacroJITx64Impl::Compile_ProcessResult(Macro::ResultOperation operation, u3
|
||||
break;
|
||||
default:
|
||||
UNIMPLEMENTED_MSG("Unimplemented macro operation {}", operation);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ public:
|
||||
virtual void Draw(bool is_indexed, u32 instance_count) = 0;
|
||||
|
||||
/// Clear the current framebuffer
|
||||
virtual void Clear() = 0;
|
||||
virtual void Clear(u32 layer_count) = 0;
|
||||
|
||||
/// Dispatches a compute shader invocation
|
||||
virtual void DispatchCompute() = 0;
|
||||
|
||||
@@ -136,7 +136,7 @@ void RasterizerOpenGL::LoadDiskResources(u64 title_id, std::stop_token stop_load
|
||||
shader_cache.LoadDiskResources(title_id, stop_loading, callback);
|
||||
}
|
||||
|
||||
void RasterizerOpenGL::Clear() {
|
||||
void RasterizerOpenGL::Clear(u32 layer_count) {
|
||||
MICROPROFILE_SCOPE(OpenGL_Clears);
|
||||
if (!maxwell3d->ShouldExecute()) {
|
||||
return;
|
||||
@@ -222,8 +222,6 @@ void RasterizerOpenGL::Draw(bool is_indexed, u32 instance_count) {
|
||||
pipeline->SetEngine(maxwell3d, gpu_memory);
|
||||
pipeline->Configure(is_indexed);
|
||||
|
||||
BindInlineIndexBuffer();
|
||||
|
||||
SyncState();
|
||||
|
||||
const GLenum primitive_mode = MaxwellToGL::PrimitiveTopology(maxwell3d->regs.draw.topology);
|
||||
@@ -468,8 +466,7 @@ bool RasterizerOpenGL::AccelerateSurfaceCopy(const Tegra::Engines::Fermi2D::Surf
|
||||
const Tegra::Engines::Fermi2D::Config& copy_config) {
|
||||
MICROPROFILE_SCOPE(OpenGL_Blits);
|
||||
std::scoped_lock lock{texture_cache.mutex};
|
||||
texture_cache.BlitImage(dst, src, copy_config);
|
||||
return true;
|
||||
return texture_cache.BlitImage(dst, src, copy_config);
|
||||
}
|
||||
|
||||
Tegra::Engines::AccelerateDMAInterface& RasterizerOpenGL::AccessAccelerateDMA() {
|
||||
@@ -1140,16 +1137,6 @@ void RasterizerOpenGL::ReleaseChannel(s32 channel_id) {
|
||||
query_cache.EraseChannel(channel_id);
|
||||
}
|
||||
|
||||
void RasterizerOpenGL::BindInlineIndexBuffer() {
|
||||
if (maxwell3d->inline_index_draw_indexes.empty()) {
|
||||
return;
|
||||
}
|
||||
const auto data_count = static_cast<u32>(maxwell3d->inline_index_draw_indexes.size());
|
||||
auto buffer = Buffer(buffer_cache_runtime, *this, 0, data_count);
|
||||
buffer.ImmediateUpload(0, maxwell3d->inline_index_draw_indexes);
|
||||
buffer_cache_runtime.BindIndexBuffer(buffer, 0, data_count);
|
||||
}
|
||||
|
||||
AccelerateDMA::AccelerateDMA(BufferCache& buffer_cache_) : buffer_cache{buffer_cache_} {}
|
||||
|
||||
bool AccelerateDMA::BufferCopy(GPUVAddr src_address, GPUVAddr dest_address, u64 amount) {
|
||||
|
||||
@@ -69,7 +69,7 @@ public:
|
||||
~RasterizerOpenGL() override;
|
||||
|
||||
void Draw(bool is_indexed, u32 instance_count) override;
|
||||
void Clear() override;
|
||||
void Clear(u32 layer_count) override;
|
||||
void DispatchCompute() override;
|
||||
void ResetCounter(VideoCore::QueryType type) override;
|
||||
void Query(GPUVAddr gpu_addr, VideoCore::QueryType type, std::optional<u64> timestamp) override;
|
||||
@@ -199,8 +199,6 @@ private:
|
||||
/// End a transform feedback
|
||||
void EndTransformFeedback();
|
||||
|
||||
void BindInlineIndexBuffer();
|
||||
|
||||
Tegra::GPU& gpu;
|
||||
|
||||
const Device& device;
|
||||
|
||||
@@ -891,6 +891,7 @@ void Image::CopyBufferToImage(const VideoCommon::BufferImageCopy& copy, size_t b
|
||||
break;
|
||||
default:
|
||||
ASSERT(false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -927,6 +928,7 @@ void Image::CopyImageToBuffer(const VideoCommon::BufferImageCopy& copy, size_t b
|
||||
break;
|
||||
default:
|
||||
ASSERT(false);
|
||||
break;
|
||||
}
|
||||
// Compressed formats don't have a pixel format or type
|
||||
const bool is_compressed = gl_format == GL_NONE;
|
||||
|
||||
@@ -28,6 +28,7 @@ constexpr std::array<FormatTuple, VideoCore::Surface::MaxPixelFormat> FORMAT_TAB
|
||||
{GL_RGB5_A1, GL_BGRA, GL_UNSIGNED_SHORT_1_5_5_5_REV}, // A1R5G5B5_UNORM
|
||||
{GL_RGB10_A2, GL_RGBA, GL_UNSIGNED_INT_2_10_10_10_REV}, // A2B10G10R10_UNORM
|
||||
{GL_RGB10_A2UI, GL_RGBA_INTEGER, GL_UNSIGNED_INT_2_10_10_10_REV}, // A2B10G10R10_UINT
|
||||
{GL_RGB10_A2, GL_BGRA, GL_UNSIGNED_INT_2_10_10_10_REV}, // A2R10G10B10_UNORM
|
||||
{GL_RGB5_A1, GL_RGBA, GL_UNSIGNED_SHORT_1_5_5_5_REV}, // A1B5G5R5_UNORM
|
||||
{GL_RGB5_A1, GL_RGBA, GL_UNSIGNED_SHORT_5_5_5_1}, // A5B5G5R1_UNORM
|
||||
{GL_R8, GL_RED, GL_UNSIGNED_BYTE}, // R8_UNORM
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user