Compare commits
32
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e9e7631cc2 | ||
|
|
4bbd0b01ee | ||
|
|
4f57e9d36b | ||
|
|
47b6bb8a47 | ||
|
|
2d1e6961f8 | ||
|
|
e09141750b | ||
|
|
ef2fbfaf1e | ||
|
|
4bbb22a477 | ||
|
|
d49ed4a421 | ||
|
|
74f515e8b6 | ||
|
|
e36814d6d5 | ||
|
|
ef2b6733d0 | ||
|
|
dc70a36b44 | ||
|
|
40cd4df584 | ||
|
|
2f79cc3ef5 | ||
|
|
2883cc1658 | ||
|
|
560cfbc21a | ||
|
|
be9f80ef56 | ||
|
|
526e533e90 | ||
|
|
3c1b6b5723 | ||
|
|
5c7253f8d3 | ||
|
|
930b7c18a6 | ||
|
|
b2c7636710 | ||
|
|
ff64c3951a | ||
|
|
fb6cf12a17 | ||
|
|
c34da106ed | ||
|
|
c3d3b173d3 | ||
|
|
cc81c0ce64 | ||
|
|
c8473f399e | ||
|
|
cd0f5dfc17 | ||
|
|
f3d1b370aa | ||
|
|
95137a04e1 |
No files matched your search
@@ -39,6 +39,7 @@ mkdir "artifacts"
|
||||
# Build a tar.xz for the source of the release
|
||||
Copy-Item .\license.txt -Destination $MSVC_SOURCE
|
||||
Copy-Item .\README.md -Destination $MSVC_SOURCE
|
||||
Copy-Item .\CMakeLists.txt -Destination $MSVC_SOURCE
|
||||
Copy-Item .\src -Recurse -Destination $MSVC_SOURCE
|
||||
Copy-Item .\externals -Recurse -Destination $MSVC_SOURCE
|
||||
Copy-Item .\dist -Recurse -Destination $MSVC_SOURCE
|
||||
@@ -60,4 +61,4 @@ Get-ChildItem "$BUILD_DIR" -Recurse -Filter "QtWebEngineProcess*.exe" | Copy-Ite
|
||||
|
||||
Get-ChildItem . -Filter "*.zip" | Copy-Item -destination "artifacts"
|
||||
Get-ChildItem . -Filter "*.7z" | Copy-Item -destination "artifacts"
|
||||
Get-ChildItem . -Filter "*.tar.xz" | Copy-Item -destination "artifacts"
|
||||
Get-ChildItem . -Filter "*.tar.xz" | Copy-Item -destination "artifacts"
|
||||
+5
-2
@@ -29,7 +29,7 @@ option(ENABLE_VULKAN "Enables Vulkan backend" ON)
|
||||
|
||||
option(USE_DISCORD_PRESENCE "Enables Discord Rich Presence" OFF)
|
||||
|
||||
if(NOT EXISTS ${PROJECT_SOURCE_DIR}/.git/hooks/pre-commit)
|
||||
if(EXISTS ${PROJECT_SOURCE_DIR}/hooks/pre-commit AND NOT EXISTS ${PROJECT_SOURCE_DIR}/.git/hooks/pre-commit)
|
||||
message(STATUS "Copying pre-commit hook")
|
||||
file(COPY hooks/pre-commit
|
||||
DESTINATION ${PROJECT_SOURCE_DIR}/.git/hooks)
|
||||
@@ -49,7 +49,10 @@ function(check_submodules_present)
|
||||
endif()
|
||||
endforeach()
|
||||
endfunction()
|
||||
check_submodules_present()
|
||||
|
||||
if(EXISTS ${PROJECT_SOURCE_DIR}/.gitmodules)
|
||||
check_submodules_present()
|
||||
endif()
|
||||
|
||||
configure_file(${PROJECT_SOURCE_DIR}/dist/compatibility_list/compatibility_list.qrc
|
||||
${PROJECT_BINARY_DIR}/dist/compatibility_list/compatibility_list.qrc
|
||||
|
||||
+9
-12
@@ -28,22 +28,19 @@ __declspec(noinline, noreturn)
|
||||
}
|
||||
|
||||
#define ASSERT(_a_) \
|
||||
do \
|
||||
if (!(_a_)) { \
|
||||
assert_noinline_call([] { LOG_CRITICAL(Debug, "Assertion Failed!"); }); \
|
||||
} \
|
||||
while (0)
|
||||
if (!(_a_)) { \
|
||||
LOG_CRITICAL(Debug, "Assertion Failed!"); \
|
||||
}
|
||||
|
||||
#define ASSERT_MSG(_a_, ...) \
|
||||
do \
|
||||
if (!(_a_)) { \
|
||||
assert_noinline_call([&] { LOG_CRITICAL(Debug, "Assertion Failed!\n" __VA_ARGS__); }); \
|
||||
} \
|
||||
while (0)
|
||||
if (!(_a_)) { \
|
||||
LOG_CRITICAL(Debug, "Assertion Failed! " __VA_ARGS__); \
|
||||
}
|
||||
|
||||
#define UNREACHABLE() assert_noinline_call([] { LOG_CRITICAL(Debug, "Unreachable code!"); })
|
||||
#define UNREACHABLE() \
|
||||
{ LOG_CRITICAL(Debug, "Unreachable code!"); }
|
||||
#define UNREACHABLE_MSG(...) \
|
||||
assert_noinline_call([&] { LOG_CRITICAL(Debug, "Unreachable code!\n" __VA_ARGS__); })
|
||||
{ LOG_CRITICAL(Debug, "Unreachable code!\n" __VA_ARGS__); }
|
||||
|
||||
#ifdef _DEBUG
|
||||
#define DEBUG_ASSERT(_a_) ASSERT(_a_)
|
||||
|
||||
@@ -46,9 +46,8 @@ public:
|
||||
ElementPtr* new_ptr = new ElementPtr();
|
||||
write_ptr->next.store(new_ptr, std::memory_order_release);
|
||||
write_ptr = new_ptr;
|
||||
cv.notify_one();
|
||||
|
||||
++size;
|
||||
cv.notify_one();
|
||||
}
|
||||
|
||||
void Pop() {
|
||||
|
||||
@@ -170,6 +170,7 @@ add_library(core STATIC
|
||||
hle/kernel/server_port.h
|
||||
hle/kernel/server_session.cpp
|
||||
hle/kernel/server_session.h
|
||||
hle/kernel/session.cpp
|
||||
hle/kernel/session.h
|
||||
hle/kernel/shared_memory.cpp
|
||||
hle/kernel/shared_memory.h
|
||||
|
||||
@@ -96,6 +96,8 @@ void Cpu::RunLoop(bool tight_loop) {
|
||||
} else {
|
||||
arm_interface->Step();
|
||||
}
|
||||
// We are stopping a run, exclusive state must be cleared
|
||||
arm_interface->ClearExclusiveState();
|
||||
}
|
||||
core_timing.Advance();
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#include "core/hle/kernel/hle_ipc.h"
|
||||
#include "core/hle/kernel/object.h"
|
||||
#include "core/hle/kernel/server_session.h"
|
||||
#include "core/hle/kernel/session.h"
|
||||
#include "core/hle/result.h"
|
||||
|
||||
namespace IPC {
|
||||
@@ -139,10 +140,9 @@ public:
|
||||
context->AddDomainObject(std::move(iface));
|
||||
} else {
|
||||
auto& kernel = Core::System::GetInstance().Kernel();
|
||||
auto [server, client] =
|
||||
Kernel::ServerSession::CreateSessionPair(kernel, iface->GetServiceName());
|
||||
iface->ClientConnected(server);
|
||||
auto [client, server] = Kernel::Session::Create(kernel, iface->GetServiceName());
|
||||
context->AddMoveObject(std::move(client));
|
||||
iface->ClientConnected(std::move(server));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "core/hle/kernel/object.h"
|
||||
#include "core/hle/kernel/server_port.h"
|
||||
#include "core/hle/kernel/server_session.h"
|
||||
#include "core/hle/kernel/session.h"
|
||||
|
||||
namespace Kernel {
|
||||
|
||||
@@ -20,28 +21,23 @@ std::shared_ptr<ServerPort> ClientPort::GetServerPort() const {
|
||||
}
|
||||
|
||||
ResultVal<std::shared_ptr<ClientSession>> ClientPort::Connect() {
|
||||
// Note: Threads do not wait for the server endpoint to call
|
||||
// AcceptSession before returning from this call.
|
||||
|
||||
if (active_sessions >= max_sessions) {
|
||||
return ERR_MAX_CONNECTIONS_REACHED;
|
||||
}
|
||||
active_sessions++;
|
||||
|
||||
// Create a new session pair, let the created sessions inherit the parent port's HLE handler.
|
||||
auto [server, client] =
|
||||
ServerSession::CreateSessionPair(kernel, server_port->GetName(), SharedFrom(this));
|
||||
auto [client, server] = Kernel::Session::Create(kernel, name);
|
||||
|
||||
if (server_port->HasHLEHandler()) {
|
||||
server_port->GetHLEHandler()->ClientConnected(server);
|
||||
server_port->GetHLEHandler()->ClientConnected(std::move(server));
|
||||
} else {
|
||||
server_port->AppendPendingSession(server);
|
||||
server_port->AppendPendingSession(std::move(server));
|
||||
}
|
||||
|
||||
// Wake the threads waiting on the ServerPort
|
||||
server_port->WakeupAllWaitingThreads();
|
||||
|
||||
return MakeResult(client);
|
||||
return MakeResult(std::move(client));
|
||||
}
|
||||
|
||||
void ClientPort::ConnectionClosed() {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2016 Citra Emulator Project
|
||||
// Copyright 2019 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
@@ -12,22 +12,44 @@
|
||||
|
||||
namespace Kernel {
|
||||
|
||||
ClientSession::ClientSession(KernelCore& kernel) : Object{kernel} {}
|
||||
ClientSession::ClientSession(KernelCore& kernel) : WaitObject{kernel} {}
|
||||
|
||||
ClientSession::~ClientSession() {
|
||||
// This destructor will be called automatically when the last ClientSession handle is closed by
|
||||
// the emulated application.
|
||||
if (auto server = parent->server.lock()) {
|
||||
server->ClientDisconnected();
|
||||
if (parent->Server()) {
|
||||
parent->Server()->ClientDisconnected();
|
||||
}
|
||||
}
|
||||
|
||||
ResultCode ClientSession::SendSyncRequest(Thread* thread, Memory::Memory& memory) {
|
||||
// Signal the server session that new data is available
|
||||
if (auto server = parent->server.lock()) {
|
||||
return server->HandleSyncRequest(SharedFrom(thread), memory);
|
||||
bool ClientSession::ShouldWait(const Thread* thread) const {
|
||||
UNIMPLEMENTED();
|
||||
return {};
|
||||
}
|
||||
|
||||
void ClientSession::Acquire(Thread* thread) {
|
||||
UNIMPLEMENTED();
|
||||
}
|
||||
|
||||
ResultVal<std::shared_ptr<ClientSession>> ClientSession::Create(KernelCore& kernel,
|
||||
std::shared_ptr<Session> parent,
|
||||
std::string name) {
|
||||
std::shared_ptr<ClientSession> client_session{std::make_shared<ClientSession>(kernel)};
|
||||
|
||||
client_session->name = std::move(name);
|
||||
client_session->parent = std::move(parent);
|
||||
|
||||
return MakeResult(std::move(client_session));
|
||||
}
|
||||
|
||||
ResultCode ClientSession::SendSyncRequest(std::shared_ptr<Thread> thread, Memory::Memory& memory) {
|
||||
// Keep ServerSession alive until we're done working with it.
|
||||
if (!parent->Server()) {
|
||||
return ERR_SESSION_CLOSED_BY_REMOTE;
|
||||
}
|
||||
|
||||
return ERR_SESSION_CLOSED_BY_REMOTE;
|
||||
// Signal the server session that new data is available
|
||||
return parent->Server()->HandleSyncRequest(std::move(thread), memory);
|
||||
}
|
||||
|
||||
} // namespace Kernel
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2016 Citra Emulator Project
|
||||
// Copyright 2019 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
@@ -6,7 +6,9 @@
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include "core/hle/kernel/object.h"
|
||||
|
||||
#include "core/hle/kernel/wait_object.h"
|
||||
#include "core/hle/result.h"
|
||||
|
||||
union ResultCode;
|
||||
|
||||
@@ -18,15 +20,14 @@ namespace Kernel {
|
||||
|
||||
class KernelCore;
|
||||
class Session;
|
||||
class ServerSession;
|
||||
class Thread;
|
||||
|
||||
class ClientSession final : public Object {
|
||||
class ClientSession final : public WaitObject {
|
||||
public:
|
||||
explicit ClientSession(KernelCore& kernel);
|
||||
~ClientSession() override;
|
||||
|
||||
friend class ServerSession;
|
||||
friend class Session;
|
||||
|
||||
std::string GetTypeName() const override {
|
||||
return "ClientSession";
|
||||
@@ -41,9 +42,17 @@ public:
|
||||
return HANDLE_TYPE;
|
||||
}
|
||||
|
||||
ResultCode SendSyncRequest(Thread* thread, Memory::Memory& memory);
|
||||
ResultCode SendSyncRequest(std::shared_ptr<Thread> thread, Memory::Memory& memory);
|
||||
|
||||
bool ShouldWait(const Thread* thread) const override;
|
||||
|
||||
void Acquire(Thread* thread) override;
|
||||
|
||||
private:
|
||||
static ResultVal<std::shared_ptr<ClientSession>> Create(KernelCore& kernel,
|
||||
std::shared_ptr<Session> parent,
|
||||
std::string name = "Unknown");
|
||||
|
||||
/// The parent session, which links to the server endpoint.
|
||||
std::shared_ptr<Session> parent;
|
||||
|
||||
|
||||
@@ -74,6 +74,8 @@ std::shared_ptr<WritableEvent> HLERequestContext::SleepClientThread(
|
||||
thread->WakeAfterDelay(timeout);
|
||||
}
|
||||
|
||||
is_thread_waiting = true;
|
||||
|
||||
return writable_event;
|
||||
}
|
||||
|
||||
|
||||
@@ -264,6 +264,18 @@ public:
|
||||
|
||||
std::string Description() const;
|
||||
|
||||
Thread& GetThread() {
|
||||
return *thread;
|
||||
}
|
||||
|
||||
const Thread& GetThread() const {
|
||||
return *thread;
|
||||
}
|
||||
|
||||
bool IsThreadWaiting() const {
|
||||
return is_thread_waiting;
|
||||
}
|
||||
|
||||
private:
|
||||
void ParseCommandBuffer(const HandleTable& handle_table, u32_le* src_cmdbuf, bool incoming);
|
||||
|
||||
@@ -290,6 +302,7 @@ private:
|
||||
u32_le command{};
|
||||
|
||||
std::vector<std::shared_ptr<SessionRequestHandler>> domain_request_handlers;
|
||||
bool is_thread_waiting{};
|
||||
};
|
||||
|
||||
} // namespace Kernel
|
||||
@@ -27,6 +27,7 @@ bool Object::IsWaitable() const {
|
||||
case HandleType::ResourceLimit:
|
||||
case HandleType::ClientPort:
|
||||
case HandleType::ClientSession:
|
||||
case HandleType::Session:
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@ enum class HandleType : u32 {
|
||||
ServerPort,
|
||||
ClientSession,
|
||||
ServerSession,
|
||||
Session,
|
||||
};
|
||||
|
||||
class Object : NonCopyable, public std::enable_shared_from_this<Object> {
|
||||
|
||||
@@ -458,7 +458,6 @@ void Scheduler::SwitchContext() {
|
||||
cpu_core.LoadContext(new_thread->GetContext());
|
||||
cpu_core.SetTlsAddress(new_thread->GetTLSAddress());
|
||||
cpu_core.SetTPIDR_EL0(new_thread->GetTPIDR_EL0());
|
||||
cpu_core.ClearExclusiveState();
|
||||
} else {
|
||||
current_thread = nullptr;
|
||||
// Note: We do not reset the current process and current page table when idling because
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2016 Citra Emulator Project
|
||||
// Copyright 2019 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "common/common_types.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/core.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/hle/ipc_helpers.h"
|
||||
#include "core/hle/kernel/client_port.h"
|
||||
#include "core/hle/kernel/client_session.h"
|
||||
@@ -24,34 +25,29 @@
|
||||
namespace Kernel {
|
||||
|
||||
ServerSession::ServerSession(KernelCore& kernel) : WaitObject{kernel} {}
|
||||
ServerSession::~ServerSession() {
|
||||
// This destructor will be called automatically when the last ServerSession handle is closed by
|
||||
// the emulated application.
|
||||
|
||||
// Decrease the port's connection count.
|
||||
if (parent->port) {
|
||||
parent->port->ConnectionClosed();
|
||||
}
|
||||
}
|
||||
ServerSession::~ServerSession() = default;
|
||||
|
||||
ResultVal<std::shared_ptr<ServerSession>> ServerSession::Create(KernelCore& kernel,
|
||||
std::shared_ptr<Session> parent,
|
||||
std::string name) {
|
||||
std::shared_ptr<ServerSession> server_session = std::make_shared<ServerSession>(kernel);
|
||||
std::shared_ptr<ServerSession> session{std::make_shared<ServerSession>(kernel)};
|
||||
|
||||
server_session->name = std::move(name);
|
||||
server_session->parent = nullptr;
|
||||
session->request_event = Core::Timing::CreateEvent(
|
||||
name, [session](u64 userdata, s64 cycles_late) { session->CompleteSyncRequest(); });
|
||||
session->name = std::move(name);
|
||||
session->parent = std::move(parent);
|
||||
|
||||
return MakeResult(std::move(server_session));
|
||||
return MakeResult(std::move(session));
|
||||
}
|
||||
|
||||
bool ServerSession::ShouldWait(const Thread* thread) const {
|
||||
// Wait if we have no pending requests, or if we're currently handling a request.
|
||||
if (auto client = parent->client.lock()) {
|
||||
return pending_requesting_threads.empty() || currently_handling != nullptr;
|
||||
// Closed sessions should never wait, an error will be returned from svcReplyAndReceive.
|
||||
if (!parent->Client()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Closed sessions should never wait, an error will be returned from svcReplyAndReceive.
|
||||
return {};
|
||||
// Wait if we have no pending requests, or if we're currently handling a request.
|
||||
return pending_requesting_threads.empty() || currently_handling != nullptr;
|
||||
}
|
||||
|
||||
void ServerSession::Acquire(Thread* thread) {
|
||||
@@ -128,14 +124,21 @@ ResultCode ServerSession::HandleDomainSyncRequest(Kernel::HLERequestContext& con
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
ResultCode ServerSession::HandleSyncRequest(std::shared_ptr<Thread> thread,
|
||||
Memory::Memory& memory) {
|
||||
// The ServerSession received a sync request, this means that there's new data available
|
||||
// from its ClientSession, so wake up any threads that may be waiting on a svcReplyAndReceive or
|
||||
// similar.
|
||||
Kernel::HLERequestContext context(SharedFrom(this), thread);
|
||||
u32* cmd_buf = (u32*)memory.GetPointer(thread->GetTLSAddress());
|
||||
context.PopulateFromIncomingCommandBuffer(kernel.CurrentProcess()->GetHandleTable(), cmd_buf);
|
||||
ResultCode ServerSession::QueueSyncRequest(std::shared_ptr<Thread> thread, Memory::Memory& memory) {
|
||||
u32* cmd_buf{reinterpret_cast<u32*>(memory.GetPointer(thread->GetTLSAddress()))};
|
||||
std::shared_ptr<Kernel::HLERequestContext> context{
|
||||
std::make_shared<Kernel::HLERequestContext>(SharedFrom(this), std::move(thread))};
|
||||
|
||||
context->PopulateFromIncomingCommandBuffer(kernel.CurrentProcess()->GetHandleTable(), cmd_buf);
|
||||
request_queue.Push(std::move(context));
|
||||
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
ResultCode ServerSession::CompleteSyncRequest() {
|
||||
ASSERT(!request_queue.Empty());
|
||||
|
||||
auto& context = *request_queue.Front();
|
||||
|
||||
ResultCode result = RESULT_SUCCESS;
|
||||
// If the session has been converted to a domain, handle the domain request
|
||||
@@ -147,61 +150,27 @@ ResultCode ServerSession::HandleSyncRequest(std::shared_ptr<Thread> thread,
|
||||
result = hle_handler->HandleSyncRequest(context);
|
||||
}
|
||||
|
||||
if (thread->GetStatus() == ThreadStatus::Running) {
|
||||
// Put the thread to sleep until the server replies, it will be awoken in
|
||||
// svcReplyAndReceive for LLE servers.
|
||||
thread->SetStatus(ThreadStatus::WaitIPC);
|
||||
|
||||
if (hle_handler != nullptr) {
|
||||
// For HLE services, we put the request threads to sleep for a short duration to
|
||||
// simulate IPC overhead, but only if the HLE handler didn't put the thread to sleep for
|
||||
// other reasons like an async callback. The IPC overhead is needed to prevent
|
||||
// starvation when a thread only does sync requests to HLE services while a
|
||||
// lower-priority thread is waiting to run.
|
||||
|
||||
// This delay was approximated in a homebrew application by measuring the average time
|
||||
// it takes for svcSendSyncRequest to return when performing the SetLcdForceBlack IPC
|
||||
// request to the GSP:GPU service in a n3DS with firmware 11.6. The measured values have
|
||||
// a high variance and vary between models.
|
||||
static constexpr u64 IPCDelayNanoseconds = 39000;
|
||||
thread->WakeAfterDelay(IPCDelayNanoseconds);
|
||||
} else {
|
||||
// Add the thread to the list of threads that have issued a sync request with this
|
||||
// server.
|
||||
pending_requesting_threads.push_back(std::move(thread));
|
||||
}
|
||||
}
|
||||
|
||||
// If this ServerSession does not have an HLE implementation, just wake up the threads waiting
|
||||
// on it.
|
||||
WakeupAllWaitingThreads();
|
||||
|
||||
// Handle scenario when ConvertToDomain command was issued, as we must do the conversion at the
|
||||
// end of the command such that only commands following this one are handled as domains
|
||||
if (convert_to_domain) {
|
||||
ASSERT_MSG(IsSession(), "ServerSession is already a domain instance.");
|
||||
domain_request_handlers = {hle_handler};
|
||||
convert_to_domain = false;
|
||||
}
|
||||
|
||||
// Some service requests require the thread to block
|
||||
if (!context.IsThreadWaiting()) {
|
||||
context.GetThread().ResumeFromWait();
|
||||
context.GetThread().SetWaitSynchronizationResult(result);
|
||||
}
|
||||
|
||||
request_queue.Pop();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
ServerSession::SessionPair ServerSession::CreateSessionPair(KernelCore& kernel,
|
||||
const std::string& name,
|
||||
std::shared_ptr<ClientPort> port) {
|
||||
auto server_session = ServerSession::Create(kernel, name + "_Server").Unwrap();
|
||||
std::shared_ptr<ClientSession> client_session = std::make_shared<ClientSession>(kernel);
|
||||
client_session->name = name + "_Client";
|
||||
|
||||
std::shared_ptr<Session> parent = std::make_shared<Session>();
|
||||
parent->client = client_session;
|
||||
parent->server = server_session;
|
||||
parent->port = std::move(port);
|
||||
|
||||
client_session->parent = parent;
|
||||
server_session->parent = parent;
|
||||
|
||||
return std::make_pair(std::move(server_session), std::move(client_session));
|
||||
ResultCode ServerSession::HandleSyncRequest(std::shared_ptr<Thread> thread,
|
||||
Memory::Memory& memory) {
|
||||
Core::System::GetInstance().CoreTiming().ScheduleEvent(20000, request_event, {});
|
||||
return QueueSyncRequest(std::move(thread), memory);
|
||||
}
|
||||
|
||||
} // namespace Kernel
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2014 Citra Emulator Project
|
||||
// Copyright 2019 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "core/hle/kernel/object.h"
|
||||
#include "common/threadsafe_queue.h"
|
||||
#include "core/hle/kernel/wait_object.h"
|
||||
#include "core/hle/result.h"
|
||||
|
||||
@@ -17,13 +17,14 @@ namespace Memory {
|
||||
class Memory;
|
||||
}
|
||||
|
||||
namespace Core::Timing {
|
||||
struct EventType;
|
||||
}
|
||||
|
||||
namespace Kernel {
|
||||
|
||||
class ClientPort;
|
||||
class ClientSession;
|
||||
class HLERequestContext;
|
||||
class KernelCore;
|
||||
class ServerSession;
|
||||
class Session;
|
||||
class SessionRequestHandler;
|
||||
class Thread;
|
||||
@@ -45,6 +46,12 @@ public:
|
||||
explicit ServerSession(KernelCore& kernel);
|
||||
~ServerSession() override;
|
||||
|
||||
friend class Session;
|
||||
|
||||
static ResultVal<std::shared_ptr<ServerSession>> Create(KernelCore& kernel,
|
||||
std::shared_ptr<Session> parent,
|
||||
std::string name = "Unknown");
|
||||
|
||||
std::string GetTypeName() const override {
|
||||
return "ServerSession";
|
||||
}
|
||||
@@ -66,18 +73,6 @@ public:
|
||||
return parent.get();
|
||||
}
|
||||
|
||||
using SessionPair = std::pair<std::shared_ptr<ServerSession>, std::shared_ptr<ClientSession>>;
|
||||
|
||||
/**
|
||||
* Creates a pair of ServerSession and an associated ClientSession.
|
||||
* @param kernel The kernal instance to create the session pair under.
|
||||
* @param name Optional name of the ports.
|
||||
* @param client_port Optional The ClientPort that spawned this session.
|
||||
* @return The created session tuple
|
||||
*/
|
||||
static SessionPair CreateSessionPair(KernelCore& kernel, const std::string& name = "Unknown",
|
||||
std::shared_ptr<ClientPort> client_port = nullptr);
|
||||
|
||||
/**
|
||||
* Sets the HLE handler for the session. This handler will be called to service IPC requests
|
||||
* instead of the regular IPC machinery. (The regular IPC machinery is currently not
|
||||
@@ -128,15 +123,11 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
/**
|
||||
* Creates a server session. The server session can have an optional HLE handler,
|
||||
* which will be invoked to handle the IPC requests that this session receives.
|
||||
* @param kernel The kernel instance to create this server session under.
|
||||
* @param name Optional name of the server session.
|
||||
* @return The created server session
|
||||
*/
|
||||
static ResultVal<std::shared_ptr<ServerSession>> Create(KernelCore& kernel,
|
||||
std::string name = "Unknown");
|
||||
/// Queues a sync request from the emulated application.
|
||||
ResultCode QueueSyncRequest(std::shared_ptr<Thread> thread, Memory::Memory& memory);
|
||||
|
||||
/// Completes a sync request from the emulated application.
|
||||
ResultCode CompleteSyncRequest();
|
||||
|
||||
/// Handles a SyncRequest to a domain, forwarding the request to the proper object or closing an
|
||||
/// object handle.
|
||||
@@ -166,6 +157,12 @@ private:
|
||||
|
||||
/// The name of this session (optional)
|
||||
std::string name;
|
||||
|
||||
/// Core timing event used to schedule the service request at some point in the future
|
||||
std::shared_ptr<Core::Timing::EventType> request_event;
|
||||
|
||||
/// Queue of scheduled service requests
|
||||
Common::MPSCQueue<std::shared_ptr<Kernel::HLERequestContext>> request_queue;
|
||||
};
|
||||
|
||||
} // namespace Kernel
|
||||
@@ -1,12 +1,36 @@
|
||||
// Copyright 2015 Citra Emulator Project
|
||||
// Copyright 2019 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "core/hle/kernel/client_session.h"
|
||||
#include "core/hle/kernel/server_session.h"
|
||||
#include "core/hle/kernel/session.h"
|
||||
#include "core/hle/kernel/thread.h"
|
||||
|
||||
namespace Kernel {
|
||||
|
||||
Session::Session() {}
|
||||
Session::~Session() {}
|
||||
Session::Session(KernelCore& kernel) : WaitObject{kernel} {}
|
||||
Session::~Session() = default;
|
||||
|
||||
Session::SessionPair Session::Create(KernelCore& kernel, std::string name) {
|
||||
auto session{std::make_shared<Session>(kernel)};
|
||||
auto client_session{Kernel::ClientSession::Create(kernel, session, name + "_Client").Unwrap()};
|
||||
auto server_session{Kernel::ServerSession::Create(kernel, session, name + "_Server").Unwrap()};
|
||||
|
||||
session->name = std::move(name);
|
||||
session->client = client_session;
|
||||
session->server = server_session;
|
||||
|
||||
return std::make_pair(std::move(client_session), std::move(server_session));
|
||||
}
|
||||
|
||||
bool Session::ShouldWait(const Thread* thread) const {
|
||||
UNIMPLEMENTED();
|
||||
return {};
|
||||
}
|
||||
|
||||
void Session::Acquire(Thread* thread) {
|
||||
UNIMPLEMENTED();
|
||||
}
|
||||
|
||||
} // namespace Kernel
|
||||
@@ -1,27 +1,64 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Copyright 2019 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/hle/kernel/object.h"
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "core/hle/kernel/wait_object.h"
|
||||
#include "core/hle/result.h"
|
||||
|
||||
namespace Kernel {
|
||||
|
||||
class ClientSession;
|
||||
class ClientPort;
|
||||
class ServerSession;
|
||||
|
||||
/**
|
||||
* Parent structure to link the client and server endpoints of a session with their associated
|
||||
* client port. The client port need not exist, as is the case for portless sessions like the
|
||||
* FS File and Directory sessions. When one of the endpoints of a session is destroyed, its
|
||||
* corresponding field in this structure will be set to nullptr.
|
||||
* client port.
|
||||
*/
|
||||
class Session final {
|
||||
class Session final : public WaitObject {
|
||||
public:
|
||||
std::weak_ptr<ClientSession> client; ///< The client endpoint of the session.
|
||||
std::weak_ptr<ServerSession> server; ///< The server endpoint of the session.
|
||||
std::shared_ptr<ClientPort> port; ///< The port that this session is associated with (optional).
|
||||
explicit Session(KernelCore& kernel);
|
||||
~Session() override;
|
||||
|
||||
using SessionPair = std::pair<std::shared_ptr<ClientSession>, std::shared_ptr<ServerSession>>;
|
||||
|
||||
static SessionPair Create(KernelCore& kernel, std::string name = "Unknown");
|
||||
|
||||
std::string GetName() const override {
|
||||
return name;
|
||||
}
|
||||
|
||||
static constexpr HandleType HANDLE_TYPE = HandleType::Session;
|
||||
HandleType GetHandleType() const override {
|
||||
return HANDLE_TYPE;
|
||||
}
|
||||
|
||||
bool ShouldWait(const Thread* thread) const override;
|
||||
|
||||
void Acquire(Thread* thread) override;
|
||||
|
||||
std::shared_ptr<ClientSession> Client() {
|
||||
if (auto result{client.lock()}) {
|
||||
return result;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
std::shared_ptr<ServerSession> Server() {
|
||||
if (auto result{server.lock()}) {
|
||||
return result;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
private:
|
||||
std::string name;
|
||||
std::weak_ptr<ClientSession> client;
|
||||
std::weak_ptr<ServerSession> server;
|
||||
};
|
||||
|
||||
} // namespace Kernel
|
||||
@@ -381,11 +381,12 @@ static ResultCode SendSyncRequest(Core::System& system, Handle handle) {
|
||||
|
||||
LOG_TRACE(Kernel_SVC, "called handle=0x{:08X}({})", handle, session->GetName());
|
||||
|
||||
system.PrepareReschedule();
|
||||
auto thread = system.CurrentScheduler().GetCurrentThread();
|
||||
thread->InvalidateWakeupCallback();
|
||||
thread->SetStatus(ThreadStatus::WaitIPC);
|
||||
system.PrepareReschedule(thread->GetProcessorID());
|
||||
|
||||
// TODO(Subv): svcSendSyncRequest should put the caller thread to sleep while the server
|
||||
// responds and cause a reschedule.
|
||||
return session->SendSyncRequest(system.CurrentScheduler().GetCurrentThread(), system.Memory());
|
||||
return session->SendSyncRequest(SharedFrom(thread), system.Memory());
|
||||
}
|
||||
|
||||
/// Get the ID for the specified thread.
|
||||
|
||||
@@ -23,6 +23,8 @@ std::shared_ptr<TransferMemory> TransferMemory::Create(KernelCore& kernel, VAddr
|
||||
transfer_memory->owner_permissions = permissions;
|
||||
transfer_memory->owner_process = kernel.CurrentProcess();
|
||||
|
||||
transfer_memory->MapMemory(base_address, size, permissions);
|
||||
|
||||
return transfer_memory;
|
||||
}
|
||||
|
||||
|
||||
@@ -51,17 +51,8 @@ std::shared_ptr<Thread> WaitObject::GetHighestPriorityReadyThread() const {
|
||||
if (ShouldWait(thread.get()))
|
||||
continue;
|
||||
|
||||
// A thread is ready to run if it's either in ThreadStatus::WaitSynch
|
||||
// and the rest of the objects it is waiting on are ready.
|
||||
bool ready_to_run = true;
|
||||
if (thread_status == ThreadStatus::WaitSynch) {
|
||||
ready_to_run = thread->AllWaitObjectsReady();
|
||||
}
|
||||
|
||||
if (ready_to_run) {
|
||||
candidate = thread.get();
|
||||
candidate_priority = thread->GetPriority();
|
||||
}
|
||||
candidate = thread.get();
|
||||
candidate_priority = thread->GetPriority();
|
||||
}
|
||||
|
||||
return SharedFrom(candidate);
|
||||
|
||||
@@ -857,17 +857,16 @@ private:
|
||||
void PopInteractiveOutData(Kernel::HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Service_AM, "called");
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
|
||||
|
||||
const auto storage = applet->GetBroker().PopInteractiveDataToGame();
|
||||
if (storage == nullptr) {
|
||||
LOG_ERROR(Service_AM,
|
||||
"storage is a nullptr. There is no data in the current interactive channel");
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(ERR_NO_DATA_IN_CHANNEL);
|
||||
return;
|
||||
}
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.PushIpcInterface<IStorage>(std::move(*storage));
|
||||
}
|
||||
|
||||
@@ -88,6 +88,7 @@ std::unique_ptr<IStorage> AppletDataBroker::PopInteractiveDataToApplet() {
|
||||
|
||||
void AppletDataBroker::PushNormalDataFromGame(IStorage storage) {
|
||||
in_channel.push_back(std::make_unique<IStorage>(storage));
|
||||
pop_out_data_event.writable->Clear();
|
||||
}
|
||||
|
||||
void AppletDataBroker::PushNormalDataFromApplet(IStorage storage) {
|
||||
@@ -97,6 +98,7 @@ void AppletDataBroker::PushNormalDataFromApplet(IStorage storage) {
|
||||
|
||||
void AppletDataBroker::PushInteractiveDataFromGame(IStorage storage) {
|
||||
in_interactive_channel.push_back(std::make_unique<IStorage>(storage));
|
||||
pop_interactive_out_data_event.writable->Clear();
|
||||
}
|
||||
|
||||
void AppletDataBroker::PushInteractiveDataFromApplet(IStorage storage) {
|
||||
|
||||
@@ -91,6 +91,7 @@ void SoftwareKeyboard::ExecuteInteractive() {
|
||||
|
||||
if (status == INTERACTIVE_STATUS_OK) {
|
||||
complete = true;
|
||||
broker.SignalStateChanged();
|
||||
} else {
|
||||
std::array<char16_t, SWKBD_OUTPUT_INTERACTIVE_BUFFER_SIZE / 2 - 2> string;
|
||||
std::memcpy(string.data(), data.data() + 4, string.size() * 2);
|
||||
|
||||
@@ -40,7 +40,10 @@ static FileSys::VirtualDir GetDirectoryRelativeWrapped(FileSys::VirtualDir base,
|
||||
if (dir_name.empty() || dir_name == "." || dir_name == "/" || dir_name == "\\")
|
||||
return base;
|
||||
|
||||
return base->GetDirectoryRelative(dir_name);
|
||||
const auto res = base->GetDirectoryRelative(dir_name);
|
||||
if (res == nullptr)
|
||||
return base->CreateDirectoryRelative(dir_name);
|
||||
return res;
|
||||
}
|
||||
|
||||
VfsDirectoryServiceWrapper::VfsDirectoryServiceWrapper(FileSys::VirtualDir backing_)
|
||||
|
||||
@@ -189,7 +189,7 @@ private:
|
||||
LOG_DEBUG(Service_NFP, "called");
|
||||
|
||||
auto nfc_event = nfp_interface.GetNFCEvent();
|
||||
if (!nfc_event->ShouldWait(Kernel::GetCurrentThread()) && !has_attached_handle) {
|
||||
if (!nfc_event->ShouldWait(&ctx.GetThread()) && !has_attached_handle) {
|
||||
device_state = DeviceState::TagFound;
|
||||
nfc_event->Clear();
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "core/hle/kernel/writable_event.h"
|
||||
#include "core/hle/service/nifm/nifm.h"
|
||||
#include "core/hle/service/service.h"
|
||||
#include "core/settings.h"
|
||||
|
||||
namespace Service::NIFM {
|
||||
|
||||
@@ -86,7 +87,12 @@ private:
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.PushEnum(RequestState::Connected);
|
||||
|
||||
if (Settings::values.bcat_backend == "none") {
|
||||
rb.PushEnum(RequestState::NotSubmitted);
|
||||
} else {
|
||||
rb.PushEnum(RequestState::Connected);
|
||||
}
|
||||
}
|
||||
|
||||
void GetResult(Kernel::HLERequestContext& ctx) {
|
||||
@@ -194,14 +200,22 @@ private:
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push<u8>(1);
|
||||
if (Settings::values.bcat_backend == "none") {
|
||||
rb.Push<u8>(0);
|
||||
} else {
|
||||
rb.Push<u8>(1);
|
||||
}
|
||||
}
|
||||
void IsAnyInternetRequestAccepted(Kernel::HLERequestContext& ctx) {
|
||||
LOG_WARNING(Service_NIFM, "(STUBBED) called");
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push<u8>(1);
|
||||
if (Settings::values.bcat_backend == "none") {
|
||||
rb.Push<u8>(0);
|
||||
} else {
|
||||
rb.Push<u8>(1);
|
||||
}
|
||||
}
|
||||
Core::System& system;
|
||||
};
|
||||
|
||||
@@ -22,6 +22,18 @@ u32 nvhost_nvdec::ioctl(Ioctl command, const std::vector<u8>& input, const std::
|
||||
switch (static_cast<IoctlCommand>(command.raw)) {
|
||||
case IoctlCommand::IocSetNVMAPfdCommand:
|
||||
return SetNVMAPfd(input, output);
|
||||
case IoctlCommand::IocSubmit:
|
||||
return Submit(input, output);
|
||||
case IoctlCommand::IocGetSyncpoint:
|
||||
return GetSyncpoint(input, output);
|
||||
case IoctlCommand::IocGetWaitbase:
|
||||
return GetWaitbase(input, output);
|
||||
case IoctlCommand::IocMapBuffer:
|
||||
return MapBuffer(input, output);
|
||||
case IoctlCommand::IocMapBufferEx:
|
||||
return MapBufferEx(input, output);
|
||||
case IoctlCommand::IocUnmapBufferEx:
|
||||
return UnmapBufferEx(input, output);
|
||||
}
|
||||
|
||||
UNIMPLEMENTED_MSG("Unimplemented ioctl");
|
||||
@@ -30,11 +42,67 @@ u32 nvhost_nvdec::ioctl(Ioctl command, const std::vector<u8>& input, const std::
|
||||
|
||||
u32 nvhost_nvdec::SetNVMAPfd(const std::vector<u8>& input, std::vector<u8>& output) {
|
||||
IoctlSetNvmapFD params{};
|
||||
std::memcpy(¶ms, input.data(), input.size());
|
||||
std::memcpy(¶ms, input.data(), sizeof(IoctlSetNvmapFD));
|
||||
LOG_DEBUG(Service_NVDRV, "called, fd={}", params.nvmap_fd);
|
||||
|
||||
nvmap_fd = params.nvmap_fd;
|
||||
return 0;
|
||||
}
|
||||
|
||||
u32 nvhost_nvdec::Submit(const std::vector<u8>& input, std::vector<u8>& output) {
|
||||
IoctlSubmit params{};
|
||||
std::memcpy(¶ms, input.data(), sizeof(IoctlSubmit));
|
||||
LOG_WARNING(Service_NVDRV, "(STUBBED) called");
|
||||
std::memcpy(output.data(), ¶ms, sizeof(IoctlSubmit));
|
||||
return 0;
|
||||
}
|
||||
|
||||
u32 nvhost_nvdec::GetSyncpoint(const std::vector<u8>& input, std::vector<u8>& output) {
|
||||
IoctlGetSyncpoint params{};
|
||||
std::memcpy(¶ms, input.data(), sizeof(IoctlGetSyncpoint));
|
||||
LOG_INFO(Service_NVDRV, "called, unknown=0x{:X}", params.unknown);
|
||||
params.value = 0; // Seems to be hard coded at 0
|
||||
std::memcpy(output.data(), ¶ms, sizeof(IoctlGetSyncpoint));
|
||||
return 0;
|
||||
}
|
||||
|
||||
u32 nvhost_nvdec::GetWaitbase(const std::vector<u8>& input, std::vector<u8>& output) {
|
||||
IoctlGetWaitbase params{};
|
||||
std::memcpy(¶ms, input.data(), sizeof(IoctlGetWaitbase));
|
||||
LOG_INFO(Service_NVDRV, "called, unknown=0x{:X}", params.unknown);
|
||||
params.value = 0; // Seems to be hard coded at 0
|
||||
std::memcpy(output.data(), ¶ms, sizeof(IoctlGetWaitbase));
|
||||
return 0;
|
||||
}
|
||||
|
||||
u32 nvhost_nvdec::MapBuffer(const std::vector<u8>& input, std::vector<u8>& output) {
|
||||
IoctlMapBuffer params{};
|
||||
std::memcpy(¶ms, input.data(), sizeof(IoctlMapBuffer));
|
||||
LOG_WARNING(Service_NVDRV, "(STUBBED) called with address={:08X}{:08X}", params.address_2,
|
||||
params.address_1);
|
||||
params.address_1 = 0;
|
||||
params.address_2 = 0;
|
||||
std::memcpy(output.data(), ¶ms, sizeof(IoctlMapBuffer));
|
||||
return 0;
|
||||
}
|
||||
|
||||
u32 nvhost_nvdec::MapBufferEx(const std::vector<u8>& input, std::vector<u8>& output) {
|
||||
IoctlMapBufferEx params{};
|
||||
std::memcpy(¶ms, input.data(), sizeof(IoctlMapBufferEx));
|
||||
LOG_WARNING(Service_NVDRV, "(STUBBED) called with address={:08X}{:08X}", params.address_2,
|
||||
params.address_1);
|
||||
params.address_1 = 0;
|
||||
params.address_2 = 0;
|
||||
std::memcpy(output.data(), ¶ms, sizeof(IoctlMapBufferEx));
|
||||
return 0;
|
||||
}
|
||||
|
||||
u32 nvhost_nvdec::UnmapBufferEx(const std::vector<u8>& input, std::vector<u8>& output) {
|
||||
IoctlUnmapBufferEx params{};
|
||||
std::memcpy(¶ms, input.data(), sizeof(IoctlUnmapBufferEx));
|
||||
LOG_WARNING(Service_NVDRV, "(STUBBED) called");
|
||||
std::memcpy(output.data(), ¶ms, sizeof(IoctlUnmapBufferEx));
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // namespace Service::Nvidia::Devices
|
||||
@@ -23,16 +23,66 @@ public:
|
||||
private:
|
||||
enum class IoctlCommand : u32_le {
|
||||
IocSetNVMAPfdCommand = 0x40044801,
|
||||
IocSubmit = 0xC0400001,
|
||||
IocGetSyncpoint = 0xC0080002,
|
||||
IocGetWaitbase = 0xC0080003,
|
||||
IocMapBuffer = 0xC01C0009,
|
||||
IocMapBufferEx = 0xC0A40009,
|
||||
IocUnmapBufferEx = 0xC0A4000A,
|
||||
};
|
||||
|
||||
struct IoctlSetNvmapFD {
|
||||
u32_le nvmap_fd;
|
||||
};
|
||||
static_assert(sizeof(IoctlSetNvmapFD) == 4, "IoctlSetNvmapFD is incorrect size");
|
||||
static_assert(sizeof(IoctlSetNvmapFD) == 0x4, "IoctlSetNvmapFD is incorrect size");
|
||||
|
||||
struct IoctlSubmit {
|
||||
INSERT_PADDING_BYTES(0x40); // TODO(DarkLordZach): RE this structure
|
||||
};
|
||||
static_assert(sizeof(IoctlSubmit) == 0x40, "IoctlSubmit has incorrect size");
|
||||
|
||||
struct IoctlGetSyncpoint {
|
||||
u32 unknown; // seems to be ignored? Nintendo added this
|
||||
u32 value;
|
||||
};
|
||||
static_assert(sizeof(IoctlGetSyncpoint) == 0x08, "IoctlGetSyncpoint has incorrect size");
|
||||
|
||||
struct IoctlGetWaitbase {
|
||||
u32 unknown; // seems to be ignored? Nintendo added this
|
||||
u32 value;
|
||||
};
|
||||
static_assert(sizeof(IoctlGetWaitbase) == 0x08, "IoctlGetWaitbase has incorrect size");
|
||||
|
||||
struct IoctlMapBuffer {
|
||||
u32 unknown;
|
||||
u32 address_1;
|
||||
u32 address_2;
|
||||
INSERT_PADDING_BYTES(0x10); // TODO(DarkLordZach): RE this structure
|
||||
};
|
||||
static_assert(sizeof(IoctlMapBuffer) == 0x1C, "IoctlMapBuffer is incorrect size");
|
||||
|
||||
struct IoctlMapBufferEx {
|
||||
u32 unknown;
|
||||
u32 address_1;
|
||||
u32 address_2;
|
||||
INSERT_PADDING_BYTES(0x98); // TODO(DarkLordZach): RE this structure
|
||||
};
|
||||
static_assert(sizeof(IoctlMapBufferEx) == 0xA4, "IoctlMapBufferEx has incorrect size");
|
||||
|
||||
struct IoctlUnmapBufferEx {
|
||||
INSERT_PADDING_BYTES(0xA4); // TODO(DarkLordZach): RE this structure
|
||||
};
|
||||
static_assert(sizeof(IoctlUnmapBufferEx) == 0xA4, "IoctlUnmapBufferEx has incorrect size");
|
||||
|
||||
u32_le nvmap_fd{};
|
||||
|
||||
u32 SetNVMAPfd(const std::vector<u8>& input, std::vector<u8>& output);
|
||||
u32 Submit(const std::vector<u8>& input, std::vector<u8>& output);
|
||||
u32 GetSyncpoint(const std::vector<u8>& input, std::vector<u8>& output);
|
||||
u32 GetWaitbase(const std::vector<u8>& input, std::vector<u8>& output);
|
||||
u32 MapBuffer(const std::vector<u8>& input, std::vector<u8>& output);
|
||||
u32 MapBufferEx(const std::vector<u8>& input, std::vector<u8>& output);
|
||||
u32 UnmapBufferEx(const std::vector<u8>& input, std::vector<u8>& output);
|
||||
};
|
||||
|
||||
} // namespace Service::Nvidia::Devices
|
||||
@@ -22,6 +22,18 @@ u32 nvhost_vic::ioctl(Ioctl command, const std::vector<u8>& input, const std::ve
|
||||
switch (static_cast<IoctlCommand>(command.raw)) {
|
||||
case IoctlCommand::IocSetNVMAPfdCommand:
|
||||
return SetNVMAPfd(input, output);
|
||||
case IoctlCommand::IocSubmit:
|
||||
return Submit(input, output);
|
||||
case IoctlCommand::IocGetSyncpoint:
|
||||
return GetSyncpoint(input, output);
|
||||
case IoctlCommand::IocGetWaitbase:
|
||||
return GetWaitbase(input, output);
|
||||
case IoctlCommand::IocMapBuffer:
|
||||
return MapBuffer(input, output);
|
||||
case IoctlCommand::IocMapBufferEx:
|
||||
return MapBuffer(input, output);
|
||||
case IoctlCommand::IocUnmapBufferEx:
|
||||
return UnmapBufferEx(input, output);
|
||||
}
|
||||
|
||||
UNIMPLEMENTED_MSG("Unimplemented ioctl");
|
||||
@@ -30,11 +42,67 @@ u32 nvhost_vic::ioctl(Ioctl command, const std::vector<u8>& input, const std::ve
|
||||
|
||||
u32 nvhost_vic::SetNVMAPfd(const std::vector<u8>& input, std::vector<u8>& output) {
|
||||
IoctlSetNvmapFD params{};
|
||||
std::memcpy(¶ms, input.data(), input.size());
|
||||
std::memcpy(¶ms, input.data(), sizeof(IoctlSetNvmapFD));
|
||||
LOG_DEBUG(Service_NVDRV, "called, fd={}", params.nvmap_fd);
|
||||
|
||||
nvmap_fd = params.nvmap_fd;
|
||||
return 0;
|
||||
}
|
||||
|
||||
u32 nvhost_vic::Submit(const std::vector<u8>& input, std::vector<u8>& output) {
|
||||
IoctlSubmit params{};
|
||||
std::memcpy(¶ms, input.data(), sizeof(IoctlSubmit));
|
||||
LOG_WARNING(Service_NVDRV, "(STUBBED) called");
|
||||
std::memcpy(output.data(), ¶ms, sizeof(IoctlSubmit));
|
||||
return 0;
|
||||
}
|
||||
|
||||
u32 nvhost_vic::GetSyncpoint(const std::vector<u8>& input, std::vector<u8>& output) {
|
||||
IoctlGetSyncpoint params{};
|
||||
std::memcpy(¶ms, input.data(), sizeof(IoctlGetSyncpoint));
|
||||
LOG_INFO(Service_NVDRV, "called, unknown=0x{:X}", params.unknown);
|
||||
params.value = 0; // Seems to be hard coded at 0
|
||||
std::memcpy(output.data(), ¶ms, sizeof(IoctlGetSyncpoint));
|
||||
return 0;
|
||||
}
|
||||
|
||||
u32 nvhost_vic::GetWaitbase(const std::vector<u8>& input, std::vector<u8>& output) {
|
||||
IoctlGetWaitbase params{};
|
||||
std::memcpy(¶ms, input.data(), sizeof(IoctlGetWaitbase));
|
||||
LOG_INFO(Service_NVDRV, "called, unknown=0x{:X}", params.unknown);
|
||||
params.value = 0; // Seems to be hard coded at 0
|
||||
std::memcpy(output.data(), ¶ms, sizeof(IoctlGetWaitbase));
|
||||
return 0;
|
||||
}
|
||||
|
||||
u32 nvhost_vic::MapBuffer(const std::vector<u8>& input, std::vector<u8>& output) {
|
||||
IoctlMapBuffer params{};
|
||||
std::memcpy(¶ms, input.data(), sizeof(IoctlMapBuffer));
|
||||
LOG_WARNING(Service_NVDRV, "(STUBBED) called with address={:08X}{:08X}", params.address_2,
|
||||
params.address_1);
|
||||
params.address_1 = 0;
|
||||
params.address_2 = 0;
|
||||
std::memcpy(output.data(), ¶ms, sizeof(IoctlMapBuffer));
|
||||
return 0;
|
||||
}
|
||||
|
||||
u32 nvhost_vic::MapBufferEx(const std::vector<u8>& input, std::vector<u8>& output) {
|
||||
IoctlMapBufferEx params{};
|
||||
std::memcpy(¶ms, input.data(), sizeof(IoctlMapBufferEx));
|
||||
LOG_WARNING(Service_NVDRV, "(STUBBED) called with address={:08X}{:08X}", params.address_2,
|
||||
params.address_1);
|
||||
params.address_1 = 0;
|
||||
params.address_2 = 0;
|
||||
std::memcpy(output.data(), ¶ms, sizeof(IoctlMapBufferEx));
|
||||
return 0;
|
||||
}
|
||||
|
||||
u32 nvhost_vic::UnmapBufferEx(const std::vector<u8>& input, std::vector<u8>& output) {
|
||||
IoctlUnmapBufferEx params{};
|
||||
std::memcpy(¶ms, input.data(), sizeof(IoctlUnmapBufferEx));
|
||||
LOG_WARNING(Service_NVDRV, "(STUBBED) called");
|
||||
std::memcpy(output.data(), ¶ms, sizeof(IoctlUnmapBufferEx));
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // namespace Service::Nvidia::Devices
|
||||
@@ -23,6 +23,12 @@ public:
|
||||
private:
|
||||
enum class IoctlCommand : u32_le {
|
||||
IocSetNVMAPfdCommand = 0x40044801,
|
||||
IocSubmit = 0xC0400001,
|
||||
IocGetSyncpoint = 0xC0080002,
|
||||
IocGetWaitbase = 0xC0080003,
|
||||
IocMapBuffer = 0xC01C0009,
|
||||
IocMapBufferEx = 0xC03C0009,
|
||||
IocUnmapBufferEx = 0xC03C000A,
|
||||
};
|
||||
|
||||
struct IoctlSetNvmapFD {
|
||||
@@ -30,9 +36,53 @@ private:
|
||||
};
|
||||
static_assert(sizeof(IoctlSetNvmapFD) == 4, "IoctlSetNvmapFD is incorrect size");
|
||||
|
||||
struct IoctlSubmit {
|
||||
INSERT_PADDING_BYTES(0x40); // TODO(DarkLordZach): RE this structure
|
||||
};
|
||||
static_assert(sizeof(IoctlSubmit) == 0x40, "IoctlSubmit is incorrect size");
|
||||
|
||||
struct IoctlGetSyncpoint {
|
||||
u32 unknown; // seems to be ignored? Nintendo added this
|
||||
u32 value;
|
||||
};
|
||||
static_assert(sizeof(IoctlGetSyncpoint) == 0x8, "IoctlGetSyncpoint is incorrect size");
|
||||
|
||||
struct IoctlGetWaitbase {
|
||||
u32 unknown; // seems to be ignored? Nintendo added this
|
||||
u32 value;
|
||||
};
|
||||
static_assert(sizeof(IoctlGetWaitbase) == 0x8, "IoctlGetWaitbase is incorrect size");
|
||||
|
||||
struct IoctlMapBuffer {
|
||||
u32 unknown;
|
||||
u32 address_1;
|
||||
u32 address_2;
|
||||
INSERT_PADDING_BYTES(0x10); // TODO(DarkLordZach): RE this structure
|
||||
};
|
||||
static_assert(sizeof(IoctlMapBuffer) == 0x1C, "IoctlMapBuffer is incorrect size");
|
||||
|
||||
struct IoctlMapBufferEx {
|
||||
u32 unknown;
|
||||
u32 address_1;
|
||||
u32 address_2;
|
||||
INSERT_PADDING_BYTES(0x30); // TODO(DarkLordZach): RE this structure
|
||||
};
|
||||
static_assert(sizeof(IoctlMapBufferEx) == 0x3C, "IoctlMapBufferEx is incorrect size");
|
||||
|
||||
struct IoctlUnmapBufferEx {
|
||||
INSERT_PADDING_BYTES(0x3C); // TODO(DarkLordZach): RE this structure
|
||||
};
|
||||
static_assert(sizeof(IoctlUnmapBufferEx) == 0x3C, "IoctlUnmapBufferEx is incorrect size");
|
||||
|
||||
u32_le nvmap_fd{};
|
||||
|
||||
u32 SetNVMAPfd(const std::vector<u8>& input, std::vector<u8>& output);
|
||||
u32 Submit(const std::vector<u8>& input, std::vector<u8>& output);
|
||||
u32 GetSyncpoint(const std::vector<u8>& input, std::vector<u8>& output);
|
||||
u32 GetWaitbase(const std::vector<u8>& input, std::vector<u8>& output);
|
||||
u32 MapBuffer(const std::vector<u8>& input, std::vector<u8>& output);
|
||||
u32 MapBufferEx(const std::vector<u8>& input, std::vector<u8>& output);
|
||||
u32 UnmapBufferEx(const std::vector<u8>& input, std::vector<u8>& output);
|
||||
};
|
||||
|
||||
} // namespace Service::Nvidia::Devices
|
||||
@@ -186,7 +186,7 @@ ResultCode ServiceFrameworkBase::HandleSyncRequest(Kernel::HLERequestContext& co
|
||||
UNIMPLEMENTED_MSG("command_type={}", static_cast<int>(context.GetCommandType()));
|
||||
}
|
||||
|
||||
context.WriteToOutgoingCommandBuffer(*Kernel::GetCurrentThread());
|
||||
context.WriteToOutgoingCommandBuffer(context.GetThread());
|
||||
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
@@ -201,7 +201,7 @@ void Init(std::shared_ptr<SM::ServiceManager>& sm, Core::System& system) {
|
||||
auto nv_flinger = std::make_shared<NVFlinger::NVFlinger>(system);
|
||||
system.GetFileSystemController().CreateFactories(*system.GetFilesystem(), false);
|
||||
|
||||
SM::ServiceManager::InstallInterfaces(sm);
|
||||
SM::ServiceManager::InstallInterfaces(sm, system.Kernel());
|
||||
|
||||
Account::InstallInterfaces(system);
|
||||
AM::InstallInterfaces(*sm, nv_flinger, system);
|
||||
|
||||
@@ -30,10 +30,7 @@ void Controller::DuplicateSession(Kernel::HLERequestContext& ctx) {
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2, 0, 1, IPC::ResponseBuilder::Flags::AlwaysMoveHandles};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
std::shared_ptr<Kernel::ClientSession> session{ctx.Session()->GetParent()->client};
|
||||
rb.PushMoveObjects(session);
|
||||
|
||||
LOG_DEBUG(Service, "session={}", session->GetObjectId());
|
||||
rb.PushMoveObjects(ctx.Session()->GetParent()->Client());
|
||||
}
|
||||
|
||||
void Controller::DuplicateSessionEx(Kernel::HLERequestContext& ctx) {
|
||||
|
||||
@@ -36,10 +36,11 @@ static ResultCode ValidateServiceName(const std::string& name) {
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
void ServiceManager::InstallInterfaces(std::shared_ptr<ServiceManager> self) {
|
||||
void ServiceManager::InstallInterfaces(std::shared_ptr<ServiceManager> self,
|
||||
Kernel::KernelCore& kernel) {
|
||||
ASSERT(self->sm_interface.expired());
|
||||
|
||||
auto sm = std::make_shared<SM>(self);
|
||||
auto sm = std::make_shared<SM>(self, kernel);
|
||||
sm->InstallAsNamedPort();
|
||||
self->sm_interface = sm;
|
||||
self->controller_interface = std::make_unique<Controller>();
|
||||
@@ -114,8 +115,6 @@ void SM::GetService(Kernel::HLERequestContext& ctx) {
|
||||
|
||||
std::string name(name_buf.begin(), end);
|
||||
|
||||
// TODO(yuriks): Permission checks go here
|
||||
|
||||
auto client_port = service_manager->GetServicePort(name);
|
||||
if (client_port.Failed()) {
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
@@ -127,14 +126,22 @@ void SM::GetService(Kernel::HLERequestContext& ctx) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto session = client_port.Unwrap()->Connect();
|
||||
ASSERT(session.Succeeded());
|
||||
if (session.Succeeded()) {
|
||||
LOG_DEBUG(Service_SM, "called service={} -> session={}", name, (*session)->GetObjectId());
|
||||
IPC::ResponseBuilder rb{ctx, 2, 0, 1, IPC::ResponseBuilder::Flags::AlwaysMoveHandles};
|
||||
rb.Push(session.Code());
|
||||
rb.PushMoveObjects(std::move(session).Unwrap());
|
||||
auto [client, server] = Kernel::Session::Create(kernel, name);
|
||||
|
||||
const auto& server_port = client_port.Unwrap()->GetServerPort();
|
||||
if (server_port->GetHLEHandler()) {
|
||||
server_port->GetHLEHandler()->ClientConnected(server);
|
||||
} else {
|
||||
server_port->AppendPendingSession(server);
|
||||
}
|
||||
|
||||
// Wake the threads waiting on the ServerPort
|
||||
server_port->WakeupAllWaitingThreads();
|
||||
|
||||
LOG_DEBUG(Service_SM, "called service={} -> session={}", name, client->GetObjectId());
|
||||
IPC::ResponseBuilder rb{ctx, 2, 0, 1, IPC::ResponseBuilder::Flags::AlwaysMoveHandles};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.PushMoveObjects(std::move(client));
|
||||
}
|
||||
|
||||
void SM::RegisterService(Kernel::HLERequestContext& ctx) {
|
||||
@@ -178,8 +185,8 @@ void SM::UnregisterService(Kernel::HLERequestContext& ctx) {
|
||||
rb.Push(service_manager->UnregisterService(name));
|
||||
}
|
||||
|
||||
SM::SM(std::shared_ptr<ServiceManager> service_manager)
|
||||
: ServiceFramework("sm:", 4), service_manager(std::move(service_manager)) {
|
||||
SM::SM(std::shared_ptr<ServiceManager> service_manager, Kernel::KernelCore& kernel)
|
||||
: ServiceFramework{"sm:", 4}, service_manager{std::move(service_manager)}, kernel{kernel} {
|
||||
static const FunctionInfo functions[] = {
|
||||
{0x00000000, &SM::Initialize, "Initialize"},
|
||||
{0x00000001, &SM::GetService, "GetService"},
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
namespace Kernel {
|
||||
class ClientPort;
|
||||
class ClientSession;
|
||||
class KernelCore;
|
||||
class ServerPort;
|
||||
class SessionRequestHandler;
|
||||
} // namespace Kernel
|
||||
@@ -29,7 +30,7 @@ class Controller;
|
||||
/// Interface to "sm:" service
|
||||
class SM final : public ServiceFramework<SM> {
|
||||
public:
|
||||
explicit SM(std::shared_ptr<ServiceManager> service_manager);
|
||||
explicit SM(std::shared_ptr<ServiceManager> service_manager, Kernel::KernelCore& kernel);
|
||||
~SM() override;
|
||||
|
||||
private:
|
||||
@@ -39,11 +40,12 @@ private:
|
||||
void UnregisterService(Kernel::HLERequestContext& ctx);
|
||||
|
||||
std::shared_ptr<ServiceManager> service_manager;
|
||||
Kernel::KernelCore& kernel;
|
||||
};
|
||||
|
||||
class ServiceManager {
|
||||
public:
|
||||
static void InstallInterfaces(std::shared_ptr<ServiceManager> self);
|
||||
static void InstallInterfaces(std::shared_ptr<ServiceManager> self, Kernel::KernelCore& kernel);
|
||||
|
||||
ServiceManager();
|
||||
~ServiceManager();
|
||||
|
||||
@@ -165,24 +165,20 @@ void TelemetrySession::AddInitialInfo(Loader::AppLoader& app_loader) {
|
||||
Telemetry::AppendOSInfo(field_collection);
|
||||
|
||||
// Log user configuration information
|
||||
AddField(Telemetry::FieldType::UserConfig, "Audio_SinkId", Settings::values.sink_id);
|
||||
AddField(Telemetry::FieldType::UserConfig, "Audio_EnableAudioStretching",
|
||||
Settings::values.enable_audio_stretching);
|
||||
AddField(Telemetry::FieldType::UserConfig, "Core_UseMultiCore",
|
||||
Settings::values.use_multi_core);
|
||||
AddField(Telemetry::FieldType::UserConfig, "Renderer_ResolutionFactor",
|
||||
Settings::values.resolution_factor);
|
||||
AddField(Telemetry::FieldType::UserConfig, "Renderer_UseFrameLimit",
|
||||
Settings::values.use_frame_limit);
|
||||
AddField(Telemetry::FieldType::UserConfig, "Renderer_FrameLimit", Settings::values.frame_limit);
|
||||
AddField(Telemetry::FieldType::UserConfig, "Renderer_UseDiskShaderCache",
|
||||
Settings::values.use_disk_shader_cache);
|
||||
AddField(Telemetry::FieldType::UserConfig, "Renderer_UseAccurateGpuEmulation",
|
||||
constexpr auto field_type = Telemetry::FieldType::UserConfig;
|
||||
AddField(field_type, "Audio_SinkId", Settings::values.sink_id);
|
||||
AddField(field_type, "Audio_EnableAudioStretching", Settings::values.enable_audio_stretching);
|
||||
AddField(field_type, "Core_UseMultiCore", Settings::values.use_multi_core);
|
||||
AddField(field_type, "Renderer_Backend", "OpenGL");
|
||||
AddField(field_type, "Renderer_ResolutionFactor", Settings::values.resolution_factor);
|
||||
AddField(field_type, "Renderer_UseFrameLimit", Settings::values.use_frame_limit);
|
||||
AddField(field_type, "Renderer_FrameLimit", Settings::values.frame_limit);
|
||||
AddField(field_type, "Renderer_UseDiskShaderCache", Settings::values.use_disk_shader_cache);
|
||||
AddField(field_type, "Renderer_UseAccurateGpuEmulation",
|
||||
Settings::values.use_accurate_gpu_emulation);
|
||||
AddField(Telemetry::FieldType::UserConfig, "Renderer_UseAsynchronousGpuEmulation",
|
||||
AddField(field_type, "Renderer_UseAsynchronousGpuEmulation",
|
||||
Settings::values.use_asynchronous_gpu_emulation);
|
||||
AddField(Telemetry::FieldType::UserConfig, "System_UseDockedMode",
|
||||
Settings::values.use_docked_mode);
|
||||
AddField(field_type, "System_UseDockedMode", Settings::values.use_docked_mode);
|
||||
}
|
||||
|
||||
bool TelemetrySession::SubmitTestcase() {
|
||||
|
||||
@@ -799,6 +799,12 @@ union Instruction {
|
||||
BitField<40, 1, u64> invert;
|
||||
} popc;
|
||||
|
||||
union {
|
||||
BitField<41, 1, u64> sh;
|
||||
BitField<40, 1, u64> invert;
|
||||
BitField<48, 1, u64> is_signed;
|
||||
} flo;
|
||||
|
||||
union {
|
||||
BitField<39, 3, u64> pred;
|
||||
BitField<42, 1, u64> neg_pred;
|
||||
@@ -1439,6 +1445,26 @@ union Instruction {
|
||||
}
|
||||
} tlds;
|
||||
|
||||
union {
|
||||
BitField<28, 1, u64> is_array;
|
||||
BitField<29, 2, TextureType> texture_type;
|
||||
BitField<35, 1, u64> aoffi_flag;
|
||||
BitField<49, 1, u64> nodep_flag;
|
||||
|
||||
bool UsesMiscMode(TextureMiscMode mode) const {
|
||||
switch (mode) {
|
||||
case TextureMiscMode::AOFFI:
|
||||
return aoffi_flag != 0;
|
||||
case TextureMiscMode::NODEP:
|
||||
return nodep_flag != 0;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} txd;
|
||||
|
||||
union {
|
||||
BitField<24, 2, StoreCacheManagement> cache_management;
|
||||
BitField<33, 3, ImageType> image_type;
|
||||
@@ -1632,6 +1658,8 @@ public:
|
||||
TLD4S, // Texture Load 4 with scalar / non - vec4 source / destinations
|
||||
TMML_B, // Texture Mip Map Level
|
||||
TMML, // Texture Mip Map Level
|
||||
TXD, // Texture Gradient/Load with Derivates
|
||||
TXD_B, // Texture Gradient/Load with Derivates Bindless
|
||||
SUST, // Surface Store
|
||||
SULD, // Surface Load
|
||||
SUATOM, // Surface Atomic Operation
|
||||
@@ -1664,6 +1692,9 @@ public:
|
||||
ISCADD_C, // Scale and Add
|
||||
ISCADD_R,
|
||||
ISCADD_IMM,
|
||||
FLO_R,
|
||||
FLO_C,
|
||||
FLO_IMM,
|
||||
LEA_R1,
|
||||
LEA_R2,
|
||||
LEA_RZ,
|
||||
@@ -1727,6 +1758,10 @@ public:
|
||||
SHR_C,
|
||||
SHR_R,
|
||||
SHR_IMM,
|
||||
SHF_RIGHT_R,
|
||||
SHF_RIGHT_IMM,
|
||||
SHF_LEFT_R,
|
||||
SHF_LEFT_IMM,
|
||||
FMNMX_C,
|
||||
FMNMX_R,
|
||||
FMNMX_IMM,
|
||||
@@ -1924,6 +1959,8 @@ private:
|
||||
INST("1101111100------", Id::TLD4S, Type::Texture, "TLD4S"),
|
||||
INST("110111110110----", Id::TMML_B, Type::Texture, "TMML_B"),
|
||||
INST("1101111101011---", Id::TMML, Type::Texture, "TMML"),
|
||||
INST("11011110011110--", Id::TXD_B, Type::Texture, "TXD_B"),
|
||||
INST("11011110001110--", Id::TXD, Type::Texture, "TXD"),
|
||||
INST("11101011001-----", Id::SUST, Type::Image, "SUST"),
|
||||
INST("11101011000-----", Id::SULD, Type::Image, "SULD"),
|
||||
INST("1110101000------", Id::SUATOM, Type::Image, "SUATOM_D"),
|
||||
@@ -1965,6 +2002,9 @@ private:
|
||||
INST("010110110100----", Id::ICMP_R, Type::ArithmeticInteger, "ICMP_R"),
|
||||
INST("010010110100----", Id::ICMP_CR, Type::ArithmeticInteger, "ICMP_CR"),
|
||||
INST("0011011-0100----", Id::ICMP_IMM, Type::ArithmeticInteger, "ICMP_IMM"),
|
||||
INST("0101110000110---", Id::FLO_R, Type::ArithmeticInteger, "FLO_R"),
|
||||
INST("0100110000110---", Id::FLO_C, Type::ArithmeticInteger, "FLO_C"),
|
||||
INST("0011100-00110---", Id::FLO_IMM, Type::ArithmeticInteger, "FLO_IMM"),
|
||||
INST("0101101111011---", Id::LEA_R2, Type::ArithmeticInteger, "LEA_R2"),
|
||||
INST("0101101111010---", Id::LEA_R1, Type::ArithmeticInteger, "LEA_R1"),
|
||||
INST("001101101101----", Id::LEA_IMM, Type::ArithmeticInteger, "LEA_IMM"),
|
||||
@@ -2022,6 +2062,10 @@ private:
|
||||
INST("0100110000101---", Id::SHR_C, Type::Shift, "SHR_C"),
|
||||
INST("0101110000101---", Id::SHR_R, Type::Shift, "SHR_R"),
|
||||
INST("0011100-00101---", Id::SHR_IMM, Type::Shift, "SHR_IMM"),
|
||||
INST("0101110011111---", Id::SHF_RIGHT_R, Type::Shift, "SHF_RIGHT_R"),
|
||||
INST("0011100-11111---", Id::SHF_RIGHT_IMM, Type::Shift, "SHF_RIGHT_IMM"),
|
||||
INST("0101101111111---", Id::SHF_LEFT_R, Type::Shift, "SHF_LEFT_R"),
|
||||
INST("0011011-11111---", Id::SHF_LEFT_IMM, Type::Shift, "SHF_LEFT_IMM"),
|
||||
INST("0100110011100---", Id::I2I_C, Type::Conversion, "I2I_C"),
|
||||
INST("0101110011100---", Id::I2I_R, Type::Conversion, "I2I_R"),
|
||||
INST("0011101-11100---", Id::I2I_IMM, Type::Conversion, "I2I_IMM"),
|
||||
|
||||
@@ -19,8 +19,7 @@ static void RunThread(VideoCore::RendererBase& renderer, Tegra::DmaPusher& dma_p
|
||||
MicroProfileOnThreadCreate("GpuThread");
|
||||
|
||||
// Wait for first GPU command before acquiring the window context
|
||||
while (state.queue.Empty())
|
||||
;
|
||||
state.sync_event.Wait();
|
||||
|
||||
// If emulation was stopped during disk shader loading, abort before trying to acquire context
|
||||
if (!state.is_running) {
|
||||
@@ -31,22 +30,25 @@ static void RunThread(VideoCore::RendererBase& renderer, Tegra::DmaPusher& dma_p
|
||||
|
||||
CommandDataContainer next;
|
||||
while (state.is_running) {
|
||||
next = state.queue.PopWait();
|
||||
if (const auto submit_list = std::get_if<SubmitListCommand>(&next.data)) {
|
||||
dma_pusher.Push(std::move(submit_list->entries));
|
||||
dma_pusher.DispatchCalls();
|
||||
} else if (const auto data = std::get_if<SwapBuffersCommand>(&next.data)) {
|
||||
renderer.SwapBuffers(data->framebuffer ? &*data->framebuffer : nullptr);
|
||||
} else if (const auto data = std::get_if<FlushRegionCommand>(&next.data)) {
|
||||
renderer.Rasterizer().FlushRegion(data->addr, data->size);
|
||||
} else if (const auto data = std::get_if<InvalidateRegionCommand>(&next.data)) {
|
||||
renderer.Rasterizer().InvalidateRegion(data->addr, data->size);
|
||||
} else if (std::holds_alternative<EndProcessingCommand>(next.data)) {
|
||||
return;
|
||||
} else {
|
||||
UNREACHABLE();
|
||||
while (!state.queue.Empty()) {
|
||||
state.queue.Pop(next);
|
||||
if (const auto submit_list = std::get_if<SubmitListCommand>(&next.data)) {
|
||||
dma_pusher.Push(std::move(submit_list->entries));
|
||||
dma_pusher.DispatchCalls();
|
||||
} else if (const auto data = std::get_if<SwapBuffersCommand>(&next.data)) {
|
||||
renderer.SwapBuffers(data->framebuffer ? &*data->framebuffer : nullptr);
|
||||
} else if (const auto data = std::get_if<FlushRegionCommand>(&next.data)) {
|
||||
renderer.Rasterizer().FlushRegion(data->addr, data->size);
|
||||
} else if (const auto data = std::get_if<InvalidateRegionCommand>(&next.data)) {
|
||||
renderer.Rasterizer().InvalidateRegion(data->addr, data->size);
|
||||
} else if (std::holds_alternative<EndProcessingCommand>(next.data)) {
|
||||
return;
|
||||
} else {
|
||||
UNREACHABLE();
|
||||
}
|
||||
state.signaled_fence.store(next.fence);
|
||||
}
|
||||
state.signaled_fence.store(next.fence);
|
||||
state.sync_event.Wait();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,6 +97,7 @@ void ThreadManager::WaitIdle() const {
|
||||
u64 ThreadManager::PushCommand(CommandData&& command_data) {
|
||||
const u64 fence{++state.last_fence};
|
||||
state.queue.Push(CommandDataContainer(std::move(command_data), fence));
|
||||
state.sync_event.Set();
|
||||
return fence;
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include <thread>
|
||||
#include <variant>
|
||||
|
||||
#include "common/thread.h"
|
||||
#include "common/threadsafe_queue.h"
|
||||
#include "video_core/gpu.h"
|
||||
|
||||
@@ -90,6 +91,7 @@ struct SynchState final {
|
||||
CommandQueue queue;
|
||||
u64 last_fence{};
|
||||
std::atomic<u64> signaled_fence{};
|
||||
Common::Event sync_event{};
|
||||
};
|
||||
|
||||
/// Class used to manage the GPU thread
|
||||
|
||||
@@ -3,9 +3,12 @@
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <tuple>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
|
||||
#include "common/cityhash.h"
|
||||
#include "common/scope_exit.h"
|
||||
#include <glad/glad.h>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "video_core/engines/maxwell_3d.h"
|
||||
#include "video_core/renderer_opengl/gl_framebuffer_cache.h"
|
||||
#include "video_core/renderer_opengl/gl_state.h"
|
||||
@@ -13,6 +16,7 @@
|
||||
namespace OpenGL {
|
||||
|
||||
using Maxwell = Tegra::Engines::Maxwell3D::Regs;
|
||||
using VideoCore::Surface::SurfaceType;
|
||||
|
||||
FramebufferCacheOpenGL::FramebufferCacheOpenGL() = default;
|
||||
|
||||
@@ -35,36 +39,49 @@ OGLFramebuffer FramebufferCacheOpenGL::CreateFramebuffer(const FramebufferCacheK
|
||||
local_state.draw.draw_framebuffer = framebuffer.handle;
|
||||
local_state.ApplyFramebufferState();
|
||||
|
||||
for (std::size_t index = 0; index < Maxwell::NumRenderTargets; ++index) {
|
||||
if (key.colors[index]) {
|
||||
key.colors[index]->Attach(GL_COLOR_ATTACHMENT0 + static_cast<GLenum>(index),
|
||||
GL_DRAW_FRAMEBUFFER);
|
||||
}
|
||||
}
|
||||
if (key.colors_count) {
|
||||
glDrawBuffers(key.colors_count, key.color_attachments.data());
|
||||
} else {
|
||||
glDrawBuffer(GL_NONE);
|
||||
if (key.zeta) {
|
||||
const bool stencil = key.zeta->GetSurfaceParams().type == SurfaceType::DepthStencil;
|
||||
const GLenum attach_target = stencil ? GL_DEPTH_STENCIL_ATTACHMENT : GL_DEPTH_ATTACHMENT;
|
||||
key.zeta->Attach(attach_target, GL_DRAW_FRAMEBUFFER);
|
||||
}
|
||||
|
||||
if (key.zeta) {
|
||||
key.zeta->Attach(key.stencil_enable ? GL_DEPTH_STENCIL_ATTACHMENT : GL_DEPTH_ATTACHMENT,
|
||||
GL_DRAW_FRAMEBUFFER);
|
||||
std::size_t num_buffers = 0;
|
||||
std::array<GLenum, Maxwell::NumRenderTargets> targets;
|
||||
|
||||
for (std::size_t index = 0; index < Maxwell::NumRenderTargets; ++index) {
|
||||
if (!key.colors[index]) {
|
||||
targets[index] = GL_NONE;
|
||||
continue;
|
||||
}
|
||||
const GLenum attach_target = GL_COLOR_ATTACHMENT0 + static_cast<GLenum>(index);
|
||||
key.colors[index]->Attach(attach_target, GL_DRAW_FRAMEBUFFER);
|
||||
|
||||
const u32 attachment = (key.color_attachments >> (BitsPerAttachment * index)) & 0b1111;
|
||||
targets[index] = GL_COLOR_ATTACHMENT0 + attachment;
|
||||
num_buffers = index + 1;
|
||||
}
|
||||
|
||||
if (num_buffers > 0) {
|
||||
glDrawBuffers(static_cast<GLsizei>(num_buffers), std::data(targets));
|
||||
} else {
|
||||
glDrawBuffer(GL_NONE);
|
||||
}
|
||||
|
||||
return framebuffer;
|
||||
}
|
||||
|
||||
std::size_t FramebufferCacheKey::Hash() const {
|
||||
static_assert(sizeof(*this) % sizeof(u64) == 0, "Unaligned struct");
|
||||
return static_cast<std::size_t>(
|
||||
Common::CityHash64(reinterpret_cast<const char*>(this), sizeof(*this)));
|
||||
std::size_t FramebufferCacheKey::Hash() const noexcept {
|
||||
std::size_t hash = std::hash<View>{}(zeta);
|
||||
for (const auto& color : colors) {
|
||||
hash ^= std::hash<View>{}(color);
|
||||
}
|
||||
hash ^= static_cast<std::size_t>(color_attachments) << 16;
|
||||
return hash;
|
||||
}
|
||||
|
||||
bool FramebufferCacheKey::operator==(const FramebufferCacheKey& rhs) const {
|
||||
return std::tie(stencil_enable, colors_count, color_attachments, colors, zeta) ==
|
||||
std::tie(rhs.stencil_enable, rhs.colors_count, rhs.color_attachments, rhs.colors,
|
||||
rhs.zeta);
|
||||
bool FramebufferCacheKey::operator==(const FramebufferCacheKey& rhs) const noexcept {
|
||||
return std::tie(colors, zeta, color_attachments) ==
|
||||
std::tie(rhs.colors, rhs.zeta, rhs.color_attachments);
|
||||
}
|
||||
|
||||
} // namespace OpenGL
|
||||
@@ -18,21 +18,24 @@
|
||||
|
||||
namespace OpenGL {
|
||||
|
||||
struct alignas(sizeof(u64)) FramebufferCacheKey {
|
||||
bool stencil_enable = false;
|
||||
u16 colors_count = 0;
|
||||
constexpr std::size_t BitsPerAttachment = 4;
|
||||
|
||||
std::array<GLenum, Tegra::Engines::Maxwell3D::Regs::NumRenderTargets> color_attachments{};
|
||||
std::array<View, Tegra::Engines::Maxwell3D::Regs::NumRenderTargets> colors;
|
||||
struct FramebufferCacheKey {
|
||||
View zeta;
|
||||
std::array<View, Tegra::Engines::Maxwell3D::Regs::NumRenderTargets> colors;
|
||||
u32 color_attachments = 0;
|
||||
|
||||
std::size_t Hash() const;
|
||||
std::size_t Hash() const noexcept;
|
||||
|
||||
bool operator==(const FramebufferCacheKey& rhs) const;
|
||||
bool operator==(const FramebufferCacheKey& rhs) const noexcept;
|
||||
|
||||
bool operator!=(const FramebufferCacheKey& rhs) const {
|
||||
bool operator!=(const FramebufferCacheKey& rhs) const noexcept {
|
||||
return !operator==(rhs);
|
||||
}
|
||||
|
||||
void SetAttachment(std::size_t index, u32 attachment) {
|
||||
color_attachments |= attachment << (BitsPerAttachment * index);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace OpenGL
|
||||
|
||||
@@ -93,7 +93,6 @@ RasterizerOpenGL::RasterizerOpenGL(Core::System& system, Core::Frontend::EmuWind
|
||||
shader_program_manager = std::make_unique<GLShader::ProgramManager>();
|
||||
state.draw.shader_program = 0;
|
||||
state.Apply();
|
||||
clear_framebuffer.Create();
|
||||
|
||||
LOG_DEBUG(Render_OpenGL, "Sync fixed function OpenGL state here");
|
||||
CheckExtensions();
|
||||
@@ -373,78 +372,58 @@ void RasterizerOpenGL::ConfigureFramebuffers() {
|
||||
UNIMPLEMENTED_IF(regs.rt_separate_frag_data == 0);
|
||||
|
||||
// Bind the framebuffer surfaces
|
||||
FramebufferCacheKey fbkey;
|
||||
for (std::size_t index = 0; index < Maxwell::NumRenderTargets; ++index) {
|
||||
FramebufferCacheKey key;
|
||||
const auto colors_count = static_cast<std::size_t>(regs.rt_control.count);
|
||||
for (std::size_t index = 0; index < colors_count; ++index) {
|
||||
View color_surface{texture_cache.GetColorBufferSurface(index, true)};
|
||||
|
||||
if (color_surface) {
|
||||
// Assume that a surface will be written to if it is used as a framebuffer, even
|
||||
// if the shader doesn't actually write to it.
|
||||
texture_cache.MarkColorBufferInUse(index);
|
||||
if (!color_surface) {
|
||||
continue;
|
||||
}
|
||||
// Assume that a surface will be written to if it is used as a framebuffer, even
|
||||
// if the shader doesn't actually write to it.
|
||||
texture_cache.MarkColorBufferInUse(index);
|
||||
|
||||
fbkey.color_attachments[index] = GL_COLOR_ATTACHMENT0 + regs.rt_control.GetMap(index);
|
||||
fbkey.colors[index] = std::move(color_surface);
|
||||
key.SetAttachment(index, regs.rt_control.GetMap(index));
|
||||
key.colors[index] = std::move(color_surface);
|
||||
}
|
||||
fbkey.colors_count = static_cast<u16>(regs.rt_control.count);
|
||||
|
||||
if (depth_surface) {
|
||||
// Assume that a surface will be written to if it is used as a framebuffer, even if
|
||||
// the shader doesn't actually write to it.
|
||||
texture_cache.MarkDepthBufferInUse();
|
||||
|
||||
fbkey.stencil_enable = depth_surface->GetSurfaceParams().type == SurfaceType::DepthStencil;
|
||||
fbkey.zeta = std::move(depth_surface);
|
||||
key.zeta = std::move(depth_surface);
|
||||
}
|
||||
|
||||
texture_cache.GuardRenderTargets(false);
|
||||
|
||||
state.draw.draw_framebuffer = framebuffer_cache.GetFramebuffer(fbkey);
|
||||
state.draw.draw_framebuffer = framebuffer_cache.GetFramebuffer(key);
|
||||
SyncViewport(state);
|
||||
}
|
||||
|
||||
void RasterizerOpenGL::ConfigureClearFramebuffer(OpenGLState& current_state, bool using_color_fb,
|
||||
bool using_depth_fb, bool using_stencil_fb) {
|
||||
using VideoCore::Surface::SurfaceType;
|
||||
|
||||
auto& gpu = system.GPU().Maxwell3D();
|
||||
const auto& regs = gpu.regs;
|
||||
|
||||
texture_cache.GuardRenderTargets(true);
|
||||
View color_surface{};
|
||||
View color_surface;
|
||||
if (using_color_fb) {
|
||||
color_surface = texture_cache.GetColorBufferSurface(regs.clear_buffers.RT, false);
|
||||
}
|
||||
View depth_surface{};
|
||||
View depth_surface;
|
||||
if (using_depth_fb || using_stencil_fb) {
|
||||
depth_surface = texture_cache.GetDepthBufferSurface(false);
|
||||
}
|
||||
texture_cache.GuardRenderTargets(false);
|
||||
|
||||
current_state.draw.draw_framebuffer = clear_framebuffer.handle;
|
||||
FramebufferCacheKey key;
|
||||
key.colors[0] = color_surface;
|
||||
key.zeta = depth_surface;
|
||||
|
||||
current_state.draw.draw_framebuffer = framebuffer_cache.GetFramebuffer(key);
|
||||
current_state.ApplyFramebufferState();
|
||||
|
||||
if (color_surface) {
|
||||
color_surface->Attach(GL_COLOR_ATTACHMENT0, GL_DRAW_FRAMEBUFFER);
|
||||
} else {
|
||||
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
|
||||
}
|
||||
|
||||
if (depth_surface) {
|
||||
const auto& params = depth_surface->GetSurfaceParams();
|
||||
switch (params.type) {
|
||||
case VideoCore::Surface::SurfaceType::Depth:
|
||||
depth_surface->Attach(GL_DEPTH_ATTACHMENT, GL_DRAW_FRAMEBUFFER);
|
||||
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0);
|
||||
break;
|
||||
case VideoCore::Surface::SurfaceType::DepthStencil:
|
||||
depth_surface->Attach(GL_DEPTH_STENCIL_ATTACHMENT, GL_DRAW_FRAMEBUFFER);
|
||||
break;
|
||||
default:
|
||||
UNIMPLEMENTED();
|
||||
}
|
||||
} else {
|
||||
glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0,
|
||||
0);
|
||||
}
|
||||
}
|
||||
|
||||
void RasterizerOpenGL::Clear() {
|
||||
|
||||
@@ -223,8 +223,6 @@ private:
|
||||
|
||||
enum class AccelDraw { Disabled, Arrays, Indexed };
|
||||
AccelDraw accelerate_draw = AccelDraw::Disabled;
|
||||
|
||||
OGLFramebuffer clear_framebuffer;
|
||||
};
|
||||
|
||||
} // namespace OpenGL
|
||||
@@ -49,8 +49,9 @@ class ExprDecompiler;
|
||||
enum class Type { Void, Bool, Bool2, Float, Int, Uint, HalfFloat };
|
||||
|
||||
struct TextureAoffi {};
|
||||
struct TextureDerivates {};
|
||||
using TextureArgument = std::pair<Type, Node>;
|
||||
using TextureIR = std::variant<TextureAoffi, TextureArgument>;
|
||||
using TextureIR = std::variant<TextureAoffi, TextureDerivates, TextureArgument>;
|
||||
|
||||
constexpr u32 MAX_CONSTBUFFER_ELEMENTS =
|
||||
static_cast<u32>(Maxwell::MaxConstBufferSize) / (4 * sizeof(float));
|
||||
@@ -1112,6 +1113,8 @@ private:
|
||||
expr += GenerateTextureArgument(*argument);
|
||||
} else if (std::holds_alternative<TextureAoffi>(variant)) {
|
||||
expr += GenerateTextureAoffi(meta->aoffi);
|
||||
} else if (std::holds_alternative<TextureDerivates>(variant)) {
|
||||
expr += GenerateTextureDerivates(meta->derivates);
|
||||
} else {
|
||||
UNREACHABLE();
|
||||
}
|
||||
@@ -1181,6 +1184,36 @@ private:
|
||||
return expr;
|
||||
}
|
||||
|
||||
std::string GenerateTextureDerivates(const std::vector<Node>& derivates) {
|
||||
if (derivates.empty()) {
|
||||
return {};
|
||||
}
|
||||
constexpr std::array coord_constructors = {"float", "vec2", "vec3"};
|
||||
std::string expr = ", ";
|
||||
const std::size_t components = derivates.size() / 2;
|
||||
std::string dx = coord_constructors.at(components - 1);
|
||||
std::string dy = coord_constructors.at(components - 1);
|
||||
dx += '(';
|
||||
dy += '(';
|
||||
|
||||
for (std::size_t index = 0; index < components; ++index) {
|
||||
const auto operand_x{derivates.at(index * 2)};
|
||||
const auto operand_y{derivates.at(index * 2 + 1)};
|
||||
dx += Visit(operand_x).AsFloat();
|
||||
dy += Visit(operand_y).AsFloat();
|
||||
|
||||
if (index + 1 < components) {
|
||||
dx += ", ";
|
||||
dy += ", ";
|
||||
}
|
||||
}
|
||||
dx += ')';
|
||||
dy += ')';
|
||||
expr += dx + ", " + dy;
|
||||
|
||||
return expr;
|
||||
}
|
||||
|
||||
std::string BuildIntegerCoordinates(Operation operation) {
|
||||
constexpr std::array constructors{"int(", "ivec2(", "ivec3(", "ivec4("};
|
||||
const std::size_t coords_count{operation.GetOperandsCount()};
|
||||
@@ -1450,6 +1483,11 @@ private:
|
||||
return GenerateUnary(operation, "bitCount", type, type);
|
||||
}
|
||||
|
||||
template <Type type>
|
||||
Expression BitMSB(Operation operation) {
|
||||
return GenerateUnary(operation, "findMSB", type, type);
|
||||
}
|
||||
|
||||
Expression HNegate(Operation operation) {
|
||||
const auto GetNegate = [&](std::size_t index) {
|
||||
return VisitOperand(operation, index).AsBool() + " ? -1 : 1";
|
||||
@@ -1738,6 +1776,14 @@ private:
|
||||
return {std::move(expr), Type::Float};
|
||||
}
|
||||
|
||||
Expression TextureGradient(Operation operation) {
|
||||
const auto meta = std::get_if<MetaTexture>(&operation.GetMeta());
|
||||
ASSERT(meta);
|
||||
|
||||
std::string expr = GenerateTexture(operation, "Grad", {TextureDerivates{}, TextureAoffi{}});
|
||||
return {std::move(expr) + GetSwizzle(meta->element), Type::Float};
|
||||
}
|
||||
|
||||
Expression ImageLoad(Operation operation) {
|
||||
if (!device.HasImageLoadFormatted()) {
|
||||
LOG_ERROR(Render_OpenGL,
|
||||
@@ -2003,6 +2049,7 @@ private:
|
||||
&GLSLDecompiler::BitfieldInsert<Type::Int>,
|
||||
&GLSLDecompiler::BitfieldExtract<Type::Int>,
|
||||
&GLSLDecompiler::BitCount<Type::Int>,
|
||||
&GLSLDecompiler::BitMSB<Type::Int>,
|
||||
|
||||
&GLSLDecompiler::Add<Type::Uint>,
|
||||
&GLSLDecompiler::Mul<Type::Uint>,
|
||||
@@ -2021,6 +2068,7 @@ private:
|
||||
&GLSLDecompiler::BitfieldInsert<Type::Uint>,
|
||||
&GLSLDecompiler::BitfieldExtract<Type::Uint>,
|
||||
&GLSLDecompiler::BitCount<Type::Uint>,
|
||||
&GLSLDecompiler::BitMSB<Type::Uint>,
|
||||
|
||||
&GLSLDecompiler::Add<Type::HalfFloat>,
|
||||
&GLSLDecompiler::Mul<Type::HalfFloat>,
|
||||
@@ -2084,6 +2132,7 @@ private:
|
||||
&GLSLDecompiler::TextureQueryDimensions,
|
||||
&GLSLDecompiler::TextureQueryLod,
|
||||
&GLSLDecompiler::TexelFetch,
|
||||
&GLSLDecompiler::TextureGradient,
|
||||
|
||||
&GLSLDecompiler::ImageLoad,
|
||||
&GLSLDecompiler::ImageStore,
|
||||
|
||||
@@ -983,6 +983,11 @@ private:
|
||||
return {};
|
||||
}
|
||||
|
||||
Id TextureGradient(Operation operation) {
|
||||
UNIMPLEMENTED();
|
||||
return {};
|
||||
}
|
||||
|
||||
Id ImageLoad(Operation operation) {
|
||||
UNIMPLEMENTED();
|
||||
return {};
|
||||
@@ -1391,6 +1396,7 @@ private:
|
||||
&SPIRVDecompiler::Quaternary<&Module::OpBitFieldInsert, Type::Int>,
|
||||
&SPIRVDecompiler::Ternary<&Module::OpBitFieldSExtract, Type::Int>,
|
||||
&SPIRVDecompiler::Unary<&Module::OpBitCount, Type::Int>,
|
||||
&SPIRVDecompiler::Unary<&Module::OpFindSMsb, Type::Int>,
|
||||
|
||||
&SPIRVDecompiler::Binary<&Module::OpIAdd, Type::Uint>,
|
||||
&SPIRVDecompiler::Binary<&Module::OpIMul, Type::Uint>,
|
||||
@@ -1409,6 +1415,7 @@ private:
|
||||
&SPIRVDecompiler::Quaternary<&Module::OpBitFieldInsert, Type::Uint>,
|
||||
&SPIRVDecompiler::Ternary<&Module::OpBitFieldUExtract, Type::Uint>,
|
||||
&SPIRVDecompiler::Unary<&Module::OpBitCount, Type::Uint>,
|
||||
&SPIRVDecompiler::Unary<&Module::OpFindUMsb, Type::Uint>,
|
||||
|
||||
&SPIRVDecompiler::Binary<&Module::OpFAdd, Type::HalfFloat>,
|
||||
&SPIRVDecompiler::Binary<&Module::OpFMul, Type::HalfFloat>,
|
||||
@@ -1473,6 +1480,7 @@ private:
|
||||
&SPIRVDecompiler::TextureQueryDimensions,
|
||||
&SPIRVDecompiler::TextureQueryLod,
|
||||
&SPIRVDecompiler::TexelFetch,
|
||||
&SPIRVDecompiler::TextureGradient,
|
||||
|
||||
&SPIRVDecompiler::ImageLoad,
|
||||
&SPIRVDecompiler::ImageStore,
|
||||
|
||||
@@ -130,6 +130,25 @@ u32 ShaderIR::DecodeArithmeticInteger(NodeBlock& bb, u32 pc) {
|
||||
SetRegister(bb, instr.gpr0, value);
|
||||
break;
|
||||
}
|
||||
case OpCode::Id::FLO_R:
|
||||
case OpCode::Id::FLO_C:
|
||||
case OpCode::Id::FLO_IMM: {
|
||||
Node value;
|
||||
if (instr.flo.invert) {
|
||||
op_b = Operation(OperationCode::IBitwiseNot, NO_PRECISE, std::move(op_b));
|
||||
}
|
||||
if (instr.flo.is_signed) {
|
||||
value = Operation(OperationCode::IBitMSB, NO_PRECISE, std::move(op_b));
|
||||
} else {
|
||||
value = Operation(OperationCode::UBitMSB, NO_PRECISE, std::move(op_b));
|
||||
}
|
||||
if (instr.flo.sh) {
|
||||
value =
|
||||
Operation(OperationCode::UBitwiseXor, NO_PRECISE, std::move(value), Immediate(31));
|
||||
}
|
||||
SetRegister(bb, instr.gpr0, std::move(value));
|
||||
break;
|
||||
}
|
||||
case OpCode::Id::SEL_C:
|
||||
case OpCode::Id::SEL_R:
|
||||
case OpCode::Id::SEL_IMM: {
|
||||
|
||||
@@ -134,13 +134,52 @@ u32 ShaderIR::DecodeTexture(NodeBlock& bb, u32 pc) {
|
||||
Node4 values;
|
||||
for (u32 element = 0; element < values.size(); ++element) {
|
||||
auto coords_copy = coords;
|
||||
MetaTexture meta{sampler, {}, {}, {}, {}, {}, component, element};
|
||||
MetaTexture meta{sampler, {}, {}, {}, {}, {}, {}, component, element};
|
||||
values[element] = Operation(OperationCode::TextureGather, meta, std::move(coords_copy));
|
||||
}
|
||||
|
||||
WriteTexsInstructionFloat(bb, instr, values, true);
|
||||
break;
|
||||
}
|
||||
case OpCode::Id::TXD_B:
|
||||
is_bindless = true;
|
||||
[[fallthrough]];
|
||||
case OpCode::Id::TXD: {
|
||||
UNIMPLEMENTED_IF_MSG(instr.txd.UsesMiscMode(TextureMiscMode::AOFFI),
|
||||
"AOFFI is not implemented");
|
||||
UNIMPLEMENTED_IF_MSG(instr.txd.is_array != 0, "TXD Array is not implemented");
|
||||
|
||||
u64 base_reg = instr.gpr8.Value();
|
||||
const auto derivate_reg = instr.gpr20.Value();
|
||||
const auto texture_type = instr.txd.texture_type.Value();
|
||||
const auto coord_count = GetCoordCount(texture_type);
|
||||
|
||||
const auto& sampler = is_bindless
|
||||
? GetBindlessSampler(base_reg, {{texture_type, false, false}})
|
||||
: GetSampler(instr.sampler, {{texture_type, false, false}});
|
||||
if (is_bindless) {
|
||||
base_reg++;
|
||||
}
|
||||
|
||||
std::vector<Node> coords;
|
||||
std::vector<Node> derivates;
|
||||
for (std::size_t i = 0; i < coord_count; ++i) {
|
||||
coords.push_back(GetRegister(base_reg + i));
|
||||
const std::size_t derivate = i * 2;
|
||||
derivates.push_back(GetRegister(derivate_reg + derivate));
|
||||
derivates.push_back(GetRegister(derivate_reg + derivate + 1));
|
||||
}
|
||||
|
||||
Node4 values;
|
||||
for (u32 element = 0; element < values.size(); ++element) {
|
||||
MetaTexture meta{sampler, {}, {}, {}, derivates, {}, {}, {}, element};
|
||||
values[element] = Operation(OperationCode::TextureGradient, std::move(meta), coords);
|
||||
}
|
||||
|
||||
WriteTexInstructionFloat(bb, instr, values);
|
||||
|
||||
break;
|
||||
}
|
||||
case OpCode::Id::TXQ_B:
|
||||
is_bindless = true;
|
||||
[[fallthrough]];
|
||||
@@ -158,7 +197,7 @@ u32 ShaderIR::DecodeTexture(NodeBlock& bb, u32 pc) {
|
||||
if (!instr.txq.IsComponentEnabled(element)) {
|
||||
continue;
|
||||
}
|
||||
MetaTexture meta{sampler, {}, {}, {}, {}, {}, {}, element};
|
||||
MetaTexture meta{sampler, {}, {}, {}, {}, {}, {}, {}, element};
|
||||
const Node value =
|
||||
Operation(OperationCode::TextureQueryDimensions, meta,
|
||||
GetRegister(instr.gpr8.Value() + (is_bindless ? 1 : 0)));
|
||||
@@ -212,7 +251,7 @@ u32 ShaderIR::DecodeTexture(NodeBlock& bb, u32 pc) {
|
||||
continue;
|
||||
}
|
||||
auto params = coords;
|
||||
MetaTexture meta{sampler, {}, {}, {}, {}, {}, {}, element};
|
||||
MetaTexture meta{sampler, {}, {}, {}, {}, {}, {}, {}, element};
|
||||
const Node value = Operation(OperationCode::TextureQueryLod, meta, std::move(params));
|
||||
SetTemporary(bb, indexer++, value);
|
||||
}
|
||||
@@ -442,7 +481,7 @@ Node4 ShaderIR::GetTextureCode(Instruction instr, TextureType texture_type,
|
||||
Node4 values;
|
||||
for (u32 element = 0; element < values.size(); ++element) {
|
||||
auto copy_coords = coords;
|
||||
MetaTexture meta{sampler, array, depth_compare, aoffi, bias, lod, {}, element};
|
||||
MetaTexture meta{sampler, array, depth_compare, aoffi, {}, bias, lod, {}, element};
|
||||
values[element] = Operation(read_method, meta, std::move(copy_coords));
|
||||
}
|
||||
|
||||
@@ -574,7 +613,7 @@ Node4 ShaderIR::GetTld4Code(Instruction instr, TextureType texture_type, bool de
|
||||
Node4 values;
|
||||
for (u32 element = 0; element < values.size(); ++element) {
|
||||
auto coords_copy = coords;
|
||||
MetaTexture meta{sampler, GetRegister(array_register), dc, aoffi, {}, {}, component,
|
||||
MetaTexture meta{sampler, GetRegister(array_register), dc, aoffi, {}, {}, {}, component,
|
||||
element};
|
||||
values[element] = Operation(OperationCode::TextureGather, meta, std::move(coords_copy));
|
||||
}
|
||||
@@ -608,7 +647,7 @@ Node4 ShaderIR::GetTldCode(Tegra::Shader::Instruction instr) {
|
||||
Node4 values;
|
||||
for (u32 element = 0; element < values.size(); ++element) {
|
||||
auto coords_copy = coords;
|
||||
MetaTexture meta{sampler, array_register, {}, {}, {}, lod, {}, element};
|
||||
MetaTexture meta{sampler, array_register, {}, {}, {}, {}, lod, {}, element};
|
||||
values[element] = Operation(OperationCode::TexelFetch, meta, std::move(coords_copy));
|
||||
}
|
||||
|
||||
@@ -653,7 +692,7 @@ Node4 ShaderIR::GetTldsCode(Instruction instr, TextureType texture_type, bool is
|
||||
Node4 values;
|
||||
for (u32 element = 0; element < values.size(); ++element) {
|
||||
auto coords_copy = coords;
|
||||
MetaTexture meta{sampler, array, {}, {}, {}, lod, {}, element};
|
||||
MetaTexture meta{sampler, array, {}, {}, {}, {}, lod, {}, element};
|
||||
values[element] = Operation(OperationCode::TexelFetch, meta, std::move(coords_copy));
|
||||
}
|
||||
return values;
|
||||
|
||||
@@ -68,6 +68,7 @@ enum class OperationCode {
|
||||
IBitfieldInsert, /// (MetaArithmetic, int base, int insert, int offset, int bits) -> int
|
||||
IBitfieldExtract, /// (MetaArithmetic, int value, int offset, int offset) -> int
|
||||
IBitCount, /// (MetaArithmetic, int) -> int
|
||||
IBitMSB, /// (MetaArithmetic, int) -> int
|
||||
|
||||
UAdd, /// (MetaArithmetic, uint a, uint b) -> uint
|
||||
UMul, /// (MetaArithmetic, uint a, uint b) -> uint
|
||||
@@ -86,6 +87,7 @@ enum class OperationCode {
|
||||
UBitfieldInsert, /// (MetaArithmetic, uint base, uint insert, int offset, int bits) -> uint
|
||||
UBitfieldExtract, /// (MetaArithmetic, uint value, int offset, int offset) -> uint
|
||||
UBitCount, /// (MetaArithmetic, uint) -> uint
|
||||
UBitMSB, /// (MetaArithmetic, uint) -> uint
|
||||
|
||||
HAdd, /// (MetaArithmetic, f16vec2 a, f16vec2 b) -> f16vec2
|
||||
HMul, /// (MetaArithmetic, f16vec2 a, f16vec2 b) -> f16vec2
|
||||
@@ -149,6 +151,7 @@ enum class OperationCode {
|
||||
TextureQueryDimensions, /// (MetaTexture, float a) -> float4
|
||||
TextureQueryLod, /// (MetaTexture, float[N] coords) -> float4
|
||||
TexelFetch, /// (MetaTexture, int[N], int) -> float4
|
||||
TextureGradient, /// (MetaTexture, float[N] coords, float[N*2] derivates) -> float4
|
||||
|
||||
ImageLoad, /// (MetaImage, int[N] coords) -> void
|
||||
ImageStore, /// (MetaImage, int[N] coords) -> void
|
||||
@@ -367,6 +370,7 @@ struct MetaTexture {
|
||||
Node array;
|
||||
Node depth_compare;
|
||||
std::vector<Node> aoffi;
|
||||
std::vector<Node> derivates;
|
||||
Node bias;
|
||||
Node lod;
|
||||
Node component{};
|
||||
|
||||
@@ -254,16 +254,14 @@ public:
|
||||
if (!layer_mipmap) {
|
||||
return {};
|
||||
}
|
||||
const u32 end_layer{layer_mipmap->first};
|
||||
const u32 end_mipmap{layer_mipmap->second};
|
||||
const auto [end_layer, end_mipmap] = *layer_mipmap;
|
||||
if (layer != end_layer) {
|
||||
if (mipmap == 0 && end_mipmap == 0) {
|
||||
return GetView(ViewParams(view_params.target, layer, end_layer - layer + 1, 0, 1));
|
||||
return GetView(ViewParams(view_params.target, layer, end_layer - layer, 0, 1));
|
||||
}
|
||||
return {};
|
||||
} else {
|
||||
return GetView(
|
||||
ViewParams(view_params.target, layer, 1, mipmap, end_mipmap - mipmap + 1));
|
||||
return GetView(ViewParams(view_params.target, layer, 1, mipmap, end_mipmap - mipmap));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -278,8 +276,7 @@ public:
|
||||
if (!layer_mipmap) {
|
||||
return {};
|
||||
}
|
||||
const u32 layer{layer_mipmap->first};
|
||||
const u32 mipmap{layer_mipmap->second};
|
||||
const auto [layer, mipmap] = *layer_mipmap;
|
||||
if (GetMipmapSize(mipmap) != candidate_size) {
|
||||
return EmplaceIrregularView(view_params, view_addr, candidate_size, mipmap, layer);
|
||||
}
|
||||
|
||||
@@ -246,6 +246,16 @@ SurfaceParams SurfaceParams::CreateForFermiCopySurface(
|
||||
return params;
|
||||
}
|
||||
|
||||
VideoCore::Surface::SurfaceTarget SurfaceParams::ExpectedTarget(
|
||||
const VideoCommon::Shader::Sampler& entry) {
|
||||
return TextureTypeToSurfaceTarget(entry.GetType(), entry.IsArray());
|
||||
}
|
||||
|
||||
VideoCore::Surface::SurfaceTarget SurfaceParams::ExpectedTarget(
|
||||
const VideoCommon::Shader::Image& entry) {
|
||||
return ImageTypeToSurfaceTarget(entry.GetType());
|
||||
}
|
||||
|
||||
bool SurfaceParams::IsLayered() const {
|
||||
switch (target) {
|
||||
case SurfaceTarget::Texture1DArray:
|
||||
|
||||
@@ -45,6 +45,14 @@ public:
|
||||
static SurfaceParams CreateForFermiCopySurface(
|
||||
const Tegra::Engines::Fermi2D::Regs::Surface& config);
|
||||
|
||||
/// Obtains the texture target from a shader's sampler entry.
|
||||
static VideoCore::Surface::SurfaceTarget ExpectedTarget(
|
||||
const VideoCommon::Shader::Sampler& entry);
|
||||
|
||||
/// Obtains the texture target from a shader's sampler entry.
|
||||
static VideoCore::Surface::SurfaceTarget ExpectedTarget(
|
||||
const VideoCommon::Shader::Image& entry);
|
||||
|
||||
std::size_t Hash() const {
|
||||
return static_cast<std::size_t>(
|
||||
Common::CityHash64(reinterpret_cast<const char*>(this), sizeof(*this)));
|
||||
|
||||
@@ -95,10 +95,16 @@ public:
|
||||
std::lock_guard lock{mutex};
|
||||
const auto gpu_addr{tic.Address()};
|
||||
if (!gpu_addr) {
|
||||
return {};
|
||||
return GetNullSurface(SurfaceParams::ExpectedTarget(entry));
|
||||
}
|
||||
|
||||
const auto host_ptr{system.GPU().MemoryManager().GetPointer(gpu_addr)};
|
||||
const auto cache_addr{ToCacheAddr(host_ptr)};
|
||||
if (!cache_addr) {
|
||||
return GetNullSurface(SurfaceParams::ExpectedTarget(entry));
|
||||
}
|
||||
const auto params{SurfaceParams::CreateForTexture(format_lookup_table, tic, entry)};
|
||||
const auto [surface, view] = GetSurface(gpu_addr, params, true, false);
|
||||
const auto [surface, view] = GetSurface(gpu_addr, cache_addr, params, true, false);
|
||||
if (guard_samplers) {
|
||||
sampled_textures.push_back(surface);
|
||||
}
|
||||
@@ -110,10 +116,15 @@ public:
|
||||
std::lock_guard lock{mutex};
|
||||
const auto gpu_addr{tic.Address()};
|
||||
if (!gpu_addr) {
|
||||
return {};
|
||||
return GetNullSurface(SurfaceParams::ExpectedTarget(entry));
|
||||
}
|
||||
const auto host_ptr{system.GPU().MemoryManager().GetPointer(gpu_addr)};
|
||||
const auto cache_addr{ToCacheAddr(host_ptr)};
|
||||
if (!cache_addr) {
|
||||
return GetNullSurface(SurfaceParams::ExpectedTarget(entry));
|
||||
}
|
||||
const auto params{SurfaceParams::CreateForImage(format_lookup_table, tic, entry)};
|
||||
const auto [surface, view] = GetSurface(gpu_addr, params, true, false);
|
||||
const auto [surface, view] = GetSurface(gpu_addr, cache_addr, params, true, false);
|
||||
if (guard_samplers) {
|
||||
sampled_textures.push_back(surface);
|
||||
}
|
||||
@@ -143,11 +154,17 @@ public:
|
||||
SetEmptyDepthBuffer();
|
||||
return {};
|
||||
}
|
||||
const auto host_ptr{system.GPU().MemoryManager().GetPointer(gpu_addr)};
|
||||
const auto cache_addr{ToCacheAddr(host_ptr)};
|
||||
if (!cache_addr) {
|
||||
SetEmptyDepthBuffer();
|
||||
return {};
|
||||
}
|
||||
const auto depth_params{SurfaceParams::CreateForDepthBuffer(
|
||||
system, regs.zeta_width, regs.zeta_height, regs.zeta.format,
|
||||
regs.zeta.memory_layout.block_width, regs.zeta.memory_layout.block_height,
|
||||
regs.zeta.memory_layout.block_depth, regs.zeta.memory_layout.type)};
|
||||
auto surface_view = GetSurface(gpu_addr, depth_params, preserve_contents, true);
|
||||
auto surface_view = GetSurface(gpu_addr, cache_addr, depth_params, preserve_contents, true);
|
||||
if (depth_buffer.target)
|
||||
depth_buffer.target->MarkAsRenderTarget(false, NO_RT);
|
||||
depth_buffer.target = surface_view.first;
|
||||
@@ -180,8 +197,16 @@ public:
|
||||
return {};
|
||||
}
|
||||
|
||||
auto surface_view = GetSurface(gpu_addr, SurfaceParams::CreateForFramebuffer(system, index),
|
||||
preserve_contents, true);
|
||||
const auto host_ptr{system.GPU().MemoryManager().GetPointer(gpu_addr)};
|
||||
const auto cache_addr{ToCacheAddr(host_ptr)};
|
||||
if (!cache_addr) {
|
||||
SetEmptyColorBuffer(index);
|
||||
return {};
|
||||
}
|
||||
|
||||
auto surface_view =
|
||||
GetSurface(gpu_addr, cache_addr, SurfaceParams::CreateForFramebuffer(system, index),
|
||||
preserve_contents, true);
|
||||
if (render_targets[index].target)
|
||||
render_targets[index].target->MarkAsRenderTarget(false, NO_RT);
|
||||
render_targets[index].target = surface_view.first;
|
||||
@@ -230,8 +255,14 @@ public:
|
||||
const GPUVAddr src_gpu_addr = src_config.Address();
|
||||
const GPUVAddr dst_gpu_addr = dst_config.Address();
|
||||
DeduceBestBlit(src_params, dst_params, src_gpu_addr, dst_gpu_addr);
|
||||
std::pair<TSurface, TView> dst_surface = GetSurface(dst_gpu_addr, dst_params, true, false);
|
||||
std::pair<TSurface, TView> src_surface = GetSurface(src_gpu_addr, src_params, true, false);
|
||||
const auto dst_host_ptr{system.GPU().MemoryManager().GetPointer(dst_gpu_addr)};
|
||||
const auto dst_cache_addr{ToCacheAddr(dst_host_ptr)};
|
||||
const auto src_host_ptr{system.GPU().MemoryManager().GetPointer(src_gpu_addr)};
|
||||
const auto src_cache_addr{ToCacheAddr(src_host_ptr)};
|
||||
std::pair<TSurface, TView> dst_surface =
|
||||
GetSurface(dst_gpu_addr, dst_cache_addr, dst_params, true, false);
|
||||
std::pair<TSurface, TView> src_surface =
|
||||
GetSurface(src_gpu_addr, src_cache_addr, src_params, true, false);
|
||||
ImageBlit(src_surface.second, dst_surface.second, copy_config);
|
||||
dst_surface.first->MarkAsModified(true, Tick());
|
||||
}
|
||||
@@ -347,13 +378,6 @@ protected:
|
||||
return new_surface;
|
||||
}
|
||||
|
||||
std::pair<TSurface, TView> GetFermiSurface(
|
||||
const Tegra::Engines::Fermi2D::Regs::Surface& config) {
|
||||
SurfaceParams params = SurfaceParams::CreateForFermiCopySurface(config);
|
||||
const GPUVAddr gpu_addr = config.Address();
|
||||
return GetSurface(gpu_addr, params, true, false);
|
||||
}
|
||||
|
||||
Core::System& system;
|
||||
|
||||
private:
|
||||
@@ -614,22 +638,9 @@ private:
|
||||
* left blank.
|
||||
* @param is_render Whether or not the surface is a render target.
|
||||
**/
|
||||
std::pair<TSurface, TView> GetSurface(const GPUVAddr gpu_addr, const SurfaceParams& params,
|
||||
bool preserve_contents, bool is_render) {
|
||||
const auto host_ptr{system.GPU().MemoryManager().GetPointer(gpu_addr)};
|
||||
const auto cache_addr{ToCacheAddr(host_ptr)};
|
||||
|
||||
// Step 0: guarantee a valid surface
|
||||
if (!cache_addr) {
|
||||
// Return a null surface if it's invalid
|
||||
SurfaceParams new_params = params;
|
||||
new_params.width = 1;
|
||||
new_params.height = 1;
|
||||
new_params.depth = 1;
|
||||
new_params.block_height = 0;
|
||||
new_params.block_depth = 0;
|
||||
return InitializeSurface(gpu_addr, new_params, false);
|
||||
}
|
||||
std::pair<TSurface, TView> GetSurface(const GPUVAddr gpu_addr, const CacheAddr cache_addr,
|
||||
const SurfaceParams& params, bool preserve_contents,
|
||||
bool is_render) {
|
||||
|
||||
// Step 1
|
||||
// Check Level 1 Cache for a fast structural match. If candidate surface
|
||||
@@ -793,6 +804,41 @@ private:
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a null surface based on a target texture.
|
||||
* @param target The target of the null surface.
|
||||
*/
|
||||
TView GetNullSurface(SurfaceTarget target) {
|
||||
const u32 i_target = static_cast<u32>(target);
|
||||
if (const auto it = invalid_cache.find(i_target); it != invalid_cache.end()) {
|
||||
return it->second->GetMainView();
|
||||
}
|
||||
SurfaceParams params{};
|
||||
params.target = target;
|
||||
params.is_tiled = false;
|
||||
params.srgb_conversion = false;
|
||||
params.is_layered = false;
|
||||
params.block_width = 0;
|
||||
params.block_height = 0;
|
||||
params.block_depth = 0;
|
||||
params.tile_width_spacing = 1;
|
||||
params.width = 1;
|
||||
params.height = 1;
|
||||
params.depth = 1;
|
||||
params.pitch = 4;
|
||||
params.num_levels = 1;
|
||||
params.emulated_levels = 1;
|
||||
params.pixel_format = VideoCore::Surface::PixelFormat::RGBA16F;
|
||||
params.type = VideoCore::Surface::SurfaceType::ColorTexture;
|
||||
auto surface = CreateSurface(0ULL, params);
|
||||
invalid_memory.clear();
|
||||
invalid_memory.resize(surface->GetHostSizeInBytes(), 0U);
|
||||
surface->UploadTexture(invalid_memory);
|
||||
surface->MarkAsModified(false, Tick());
|
||||
invalid_cache.emplace(i_target, surface);
|
||||
return surface->GetMainView();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the a source and destination starting address and parameters,
|
||||
* and tries to deduce if they are supposed to be depth textures. If so, their
|
||||
@@ -991,6 +1037,11 @@ private:
|
||||
|
||||
std::vector<TSurface> sampled_textures;
|
||||
|
||||
/// This cache stores null surfaces in order to be used as a placeholder
|
||||
/// for invalid texture calls.
|
||||
std::unordered_map<u32, TSurface> invalid_cache;
|
||||
std::vector<u8> invalid_memory;
|
||||
|
||||
StagingCache staging_cache;
|
||||
std::recursive_mutex mutex;
|
||||
};
|
||||
|
||||
Reference in new issue
Block a user