summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/citra/citra.cpp3
-rw-r--r--src/citra/config.cpp6
-rw-r--r--src/citra_qt/configuration/config.cpp6
-rw-r--r--src/citra_qt/configuration/configure_debug.ui7
-rw-r--r--src/core/CMakeLists.txt2
-rw-r--r--src/core/hle/function_wrappers.h15
-rw-r--r--src/core/hle/kernel/errors.h5
-rw-r--r--src/core/hle/kernel/hle_ipc.cpp24
-rw-r--r--src/core/hle/kernel/hle_ipc.h7
-rw-r--r--src/core/hle/kernel/memory.cpp6
-rw-r--r--src/core/hle/kernel/process.cpp2
-rw-r--r--src/core/hle/kernel/server_port.cpp12
-rw-r--r--src/core/hle/kernel/server_port.h11
-rw-r--r--src/core/hle/kernel/server_session.cpp2
-rw-r--r--src/core/hle/kernel/thread.cpp4
-rw-r--r--src/core/hle/kernel/timer.cpp2
-rw-r--r--src/core/hle/result.h7
-rw-r--r--src/core/hle/service/apt/apt.cpp12
-rw-r--r--src/core/hle/service/cam/cam.cpp6
-rw-r--r--src/core/hle/service/cecd/cecd.cpp8
-rw-r--r--src/core/hle/service/cfg/cfg.cpp4
-rw-r--r--src/core/hle/service/csnd_snd.cpp4
-rw-r--r--src/core/hle/service/dsp_dsp.cpp2
-rw-r--r--src/core/hle/service/fs/archive.cpp4
-rw-r--r--src/core/hle/service/fs/fs_user.cpp6
-rw-r--r--src/core/hle/service/gsp_gpu.cpp4
-rw-r--r--src/core/hle/service/hid/hid.cpp12
-rw-r--r--src/core/hle/service/ir/ir_rst.cpp4
-rw-r--r--src/core/hle/service/ir/ir_user.cpp6
-rw-r--r--src/core/hle/service/mic_u.cpp2
-rw-r--r--src/core/hle/service/nfc/nfc.cpp4
-rw-r--r--src/core/hle/service/nwm/nwm_uds.cpp117
-rw-r--r--src/core/hle/service/nwm/uds_data.cpp278
-rw-r--r--src/core/hle/service/nwm/uds_data.h78
-rw-r--r--src/core/hle/service/ptm/ptm.cpp2
-rw-r--r--src/core/hle/service/service.cpp5
-rw-r--r--src/core/hle/service/sm/srv.cpp4
-rw-r--r--src/core/hle/service/y2r_u.cpp2
-rw-r--r--src/core/hle/svc.cpp45
-rw-r--r--src/core/hw/aes/key.h2
-rw-r--r--src/core/hw/gpu.cpp41
-rw-r--r--src/core/hw/gpu.h2
-rw-r--r--src/core/memory.h4
-rw-r--r--src/tests/CMakeLists.txt5
-rw-r--r--src/tests/core/hle/kernel/hle_ipc.cpp216
-rw-r--r--src/video_core/pica_state.h20
-rw-r--r--src/video_core/regs_lighting.h6
-rw-r--r--src/video_core/regs_texturing.h8
-rw-r--r--src/video_core/renderer_opengl/gl_rasterizer.cpp151
-rw-r--r--src/video_core/renderer_opengl/gl_rasterizer.h10
-rw-r--r--src/video_core/renderer_opengl/gl_shader_gen.cpp189
-rw-r--r--src/video_core/renderer_opengl/gl_shader_gen.h2
-rw-r--r--src/video_core/renderer_opengl/gl_state.cpp46
-rw-r--r--src/video_core/renderer_opengl/gl_state.h29
-rw-r--r--src/video_core/renderer_opengl/pica_to_gl.h13
-rw-r--r--src/video_core/shader/shader_jit_x64_compiler.cpp26
-rw-r--r--src/video_core/swrasterizer/rasterizer.cpp23
-rw-r--r--src/video_core/swrasterizer/texturing.cpp19
58 files changed, 1219 insertions, 323 deletions
diff --git a/src/citra/citra.cpp b/src/citra/citra.cpp
index dd357ff72..14574e56c 100644
--- a/src/citra/citra.cpp
+++ b/src/citra/citra.cpp
@@ -18,7 +18,10 @@
#endif
#ifdef _WIN32
+// windows.h needs to be included before shellapi.h
#include <windows.h>
+
+#include <shellapi.h>
#endif
#include "citra/config.h"
diff --git a/src/citra/config.cpp b/src/citra/config.cpp
index f08b4069c..957d8dc86 100644
--- a/src/citra/config.cpp
+++ b/src/citra/config.cpp
@@ -88,9 +88,9 @@ void Config::ReadValues() {
Settings::values.toggle_framelimit =
sdl2_config->GetBoolean("Renderer", "toggle_framelimit", true);
- Settings::values.bg_red = (float)sdl2_config->GetReal("Renderer", "bg_red", 1.0);
- Settings::values.bg_green = (float)sdl2_config->GetReal("Renderer", "bg_green", 1.0);
- Settings::values.bg_blue = (float)sdl2_config->GetReal("Renderer", "bg_blue", 1.0);
+ Settings::values.bg_red = (float)sdl2_config->GetReal("Renderer", "bg_red", 0.0);
+ Settings::values.bg_green = (float)sdl2_config->GetReal("Renderer", "bg_green", 0.0);
+ Settings::values.bg_blue = (float)sdl2_config->GetReal("Renderer", "bg_blue", 0.0);
// Layout
Settings::values.layout_option =
diff --git a/src/citra_qt/configuration/config.cpp b/src/citra_qt/configuration/config.cpp
index 2b99447ec..64ffc9152 100644
--- a/src/citra_qt/configuration/config.cpp
+++ b/src/citra_qt/configuration/config.cpp
@@ -70,9 +70,9 @@ void Config::ReadValues() {
Settings::values.use_vsync = qt_config->value("use_vsync", false).toBool();
Settings::values.toggle_framelimit = qt_config->value("toggle_framelimit", true).toBool();
- Settings::values.bg_red = qt_config->value("bg_red", 1.0).toFloat();
- Settings::values.bg_green = qt_config->value("bg_green", 1.0).toFloat();
- Settings::values.bg_blue = qt_config->value("bg_blue", 1.0).toFloat();
+ Settings::values.bg_red = qt_config->value("bg_red", 0.0).toFloat();
+ Settings::values.bg_green = qt_config->value("bg_green", 0.0).toFloat();
+ Settings::values.bg_blue = qt_config->value("bg_blue", 0.0).toFloat();
qt_config->endGroup();
qt_config->beginGroup("Layout");
diff --git a/src/citra_qt/configuration/configure_debug.ui b/src/citra_qt/configuration/configure_debug.ui
index bbbb0e3f4..96638ebdb 100644
--- a/src/citra_qt/configuration/configure_debug.ui
+++ b/src/citra_qt/configuration/configure_debug.ui
@@ -23,6 +23,13 @@
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
+ <widget class="QLabel">
+ <property name="text">
+ <string>The GDB Stub only works correctly when the CPU JIT is off.</string>
+ </property>
+ </widget>
+ </item>
+ <item>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QCheckBox" name="toggle_gdbstub">
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index b16a89990..ea09819e5 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -144,6 +144,7 @@ set(SRCS
hle/service/nwm/nwm_tst.cpp
hle/service/nwm/nwm_uds.cpp
hle/service/nwm/uds_beacon.cpp
+ hle/service/nwm/uds_data.cpp
hle/service/pm_app.cpp
hle/service/ptm/ptm.cpp
hle/service/ptm/ptm_gets.cpp
@@ -341,6 +342,7 @@ set(HEADERS
hle/service/nwm/nwm_tst.h
hle/service/nwm/nwm_uds.h
hle/service/nwm/uds_beacon.h
+ hle/service/nwm/uds_data.h
hle/service/pm_app.h
hle/service/ptm/ptm.h
hle/service/ptm/ptm_gets.h
diff --git a/src/core/hle/function_wrappers.h b/src/core/hle/function_wrappers.h
index 2abdfe1b3..5e6002f4e 100644
--- a/src/core/hle/function_wrappers.h
+++ b/src/core/hle/function_wrappers.h
@@ -229,9 +229,18 @@ void Wrap() {
u32 retval = func(&param_1, &param_2,
reinterpret_cast<const char*>(Memory::GetPointer(PARAM(2))), PARAM(3))
.raw;
- // The first out parameter is moved into R2 and the second is moved into R1.
- Core::CPU().SetReg(1, param_2);
- Core::CPU().SetReg(2, param_1);
+ Core::CPU().SetReg(1, param_1);
+ Core::CPU().SetReg(2, param_2);
+ FuncReturn(retval);
+}
+
+template <ResultCode func(Kernel::Handle*, Kernel::Handle*)>
+void Wrap() {
+ Kernel::Handle param_1 = 0;
+ Kernel::Handle param_2 = 0;
+ u32 retval = func(&param_1, &param_2).raw;
+ Core::CPU().SetReg(1, param_1);
+ Core::CPU().SetReg(2, param_2);
FuncReturn(retval);
}
diff --git a/src/core/hle/kernel/errors.h b/src/core/hle/kernel/errors.h
index b3b60e7df..64aa61460 100644
--- a/src/core/hle/kernel/errors.h
+++ b/src/core/hle/kernel/errors.h
@@ -13,6 +13,7 @@ enum {
OutOfHandles = 19,
SessionClosedByRemote = 26,
PortNameTooLong = 30,
+ NoPendingSessions = 35,
WrongPermission = 46,
InvalidBufferDescriptor = 48,
MaxConnectionsReached = 52,
@@ -94,5 +95,9 @@ constexpr ResultCode ERR_OUT_OF_RANGE_KERNEL(ErrorDescription::OutOfRange, Error
ErrorLevel::Permanent); // 0xD8E007FD
constexpr ResultCode RESULT_TIMEOUT(ErrorDescription::Timeout, ErrorModule::OS,
ErrorSummary::StatusChanged, ErrorLevel::Info);
+/// Returned when Accept() is called on a port with no sessions to be accepted.
+constexpr ResultCode ERR_NO_PENDING_SESSIONS(ErrCodes::NoPendingSessions, ErrorModule::OS,
+ ErrorSummary::WouldBlock,
+ ErrorLevel::Permanent); // 0xD8401823
} // namespace Kernel
diff --git a/src/core/hle/kernel/hle_ipc.cpp b/src/core/hle/kernel/hle_ipc.cpp
index 6cf1886cf..5ebe2eca4 100644
--- a/src/core/hle/kernel/hle_ipc.cpp
+++ b/src/core/hle/kernel/hle_ipc.cpp
@@ -23,6 +23,11 @@ void SessionRequestHandler::ClientDisconnected(SharedPtr<ServerSession> server_s
boost::range::remove_erase(connected_sessions, server_session);
}
+HLERequestContext::HLERequestContext(SharedPtr<ServerSession> session)
+ : session(std::move(session)) {
+ cmd_buf[0] = 0;
+}
+
HLERequestContext::~HLERequestContext() = default;
SharedPtr<Object> HLERequestContext::GetIncomingHandle(u32 id_from_cmdbuf) const {
@@ -62,10 +67,13 @@ ResultCode HLERequestContext::PopulateFromIncomingCommandBuffer(const u32_le* sr
ASSERT(i + num_handles <= command_size); // TODO(yuriks): Return error
for (u32 j = 0; j < num_handles; ++j) {
Handle handle = src_cmdbuf[i];
- SharedPtr<Object> object = src_table.GetGeneric(handle);
- ASSERT(object != nullptr); // TODO(yuriks): Return error
- if (descriptor == IPC::DescriptorType::MoveHandle) {
- src_table.Close(handle);
+ SharedPtr<Object> object = nullptr;
+ if (handle != 0) {
+ object = src_table.GetGeneric(handle);
+ ASSERT(object != nullptr); // TODO(yuriks): Return error
+ if (descriptor == IPC::DescriptorType::MoveHandle) {
+ src_table.Close(handle);
+ }
}
cmd_buf[i++] = AddOutgoingHandle(std::move(object));
@@ -107,9 +115,11 @@ ResultCode HLERequestContext::WriteToOutgoingCommandBuffer(u32_le* dst_cmdbuf, P
ASSERT(i + num_handles <= command_size);
for (u32 j = 0; j < num_handles; ++j) {
SharedPtr<Object> object = GetIncomingHandle(cmd_buf[i]);
-
- // TODO(yuriks): Figure out the proper error handling for if this fails
- Handle handle = dst_table.Create(object).Unwrap();
+ Handle handle = 0;
+ if (object != nullptr) {
+ // TODO(yuriks): Figure out the proper error handling for if this fails
+ handle = dst_table.Create(object).Unwrap();
+ }
dst_cmdbuf[i++] = handle;
}
break;
diff --git a/src/core/hle/kernel/hle_ipc.h b/src/core/hle/kernel/hle_ipc.h
index cbb109d8f..35795fc1d 100644
--- a/src/core/hle/kernel/hle_ipc.h
+++ b/src/core/hle/kernel/hle_ipc.h
@@ -84,6 +84,7 @@ protected:
*/
class HLERequestContext {
public:
+ HLERequestContext(SharedPtr<ServerSession> session);
~HLERequestContext();
/// Returns a pointer to the IPC command buffer for this request.
@@ -118,14 +119,14 @@ public:
*/
void ClearIncomingObjects();
-private:
- friend class Service::ServiceFrameworkBase;
-
+ /// Populates this context with data from the requesting process/thread.
ResultCode PopulateFromIncomingCommandBuffer(const u32_le* src_cmdbuf, Process& src_process,
HandleTable& src_table);
+ /// Writes data from this context back to the requesting process/thread.
ResultCode WriteToOutgoingCommandBuffer(u32_le* dst_cmdbuf, Process& dst_process,
HandleTable& dst_table) const;
+private:
std::array<u32, IPC::COMMAND_BUFFER_LENGTH> cmd_buf;
SharedPtr<ServerSession> session;
// TODO(yuriks): Check common usage of this and optimize size accordingly
diff --git a/src/core/hle/kernel/memory.cpp b/src/core/hle/kernel/memory.cpp
index 804f23b1c..496d07cb5 100644
--- a/src/core/hle/kernel/memory.cpp
+++ b/src/core/hle/kernel/memory.cpp
@@ -166,7 +166,7 @@ void HandleSpecialMapping(VMManager& address_space, const AddressMapping& mappin
auto vma = address_space
.MapBackingMemory(mapping.address, target_pointer + offset_into_region,
mapping.size, memory_state)
- .MoveFrom();
+ .Unwrap();
address_space.Reprotect(vma,
mapping.read_only ? VMAPermission::Read : VMAPermission::ReadWrite);
}
@@ -176,14 +176,14 @@ void MapSharedPages(VMManager& address_space) {
.MapBackingMemory(Memory::CONFIG_MEMORY_VADDR,
reinterpret_cast<u8*>(&ConfigMem::config_mem),
Memory::CONFIG_MEMORY_SIZE, MemoryState::Shared)
- .MoveFrom();
+ .Unwrap();
address_space.Reprotect(cfg_mem_vma, VMAPermission::Read);
auto shared_page_vma = address_space
.MapBackingMemory(Memory::SHARED_PAGE_VADDR,
reinterpret_cast<u8*>(&SharedPage::shared_page),
Memory::SHARED_PAGE_SIZE, MemoryState::Shared)
- .MoveFrom();
+ .Unwrap();
address_space.Reprotect(shared_page_vma, VMAPermission::Read);
}
diff --git a/src/core/hle/kernel/process.cpp b/src/core/hle/kernel/process.cpp
index 1c31ec950..522ad2333 100644
--- a/src/core/hle/kernel/process.cpp
+++ b/src/core/hle/kernel/process.cpp
@@ -151,6 +151,8 @@ void Process::Run(s32 main_thread_priority, u32 stack_size) {
}
VAddr Process::GetLinearHeapAreaAddress() const {
+ // Starting from system version 8.0.0 a new linear heap layout is supported to allow usage of
+ // the extra RAM in the n3DS.
return kernel_version < 0x22C ? Memory::LINEAR_HEAP_VADDR : Memory::NEW_LINEAR_HEAP_VADDR;
}
diff --git a/src/core/hle/kernel/server_port.cpp b/src/core/hle/kernel/server_port.cpp
index 4d20c39a1..49a9cdfa3 100644
--- a/src/core/hle/kernel/server_port.cpp
+++ b/src/core/hle/kernel/server_port.cpp
@@ -5,8 +5,10 @@
#include <tuple>
#include "common/assert.h"
#include "core/hle/kernel/client_port.h"
+#include "core/hle/kernel/errors.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/server_port.h"
+#include "core/hle/kernel/server_session.h"
#include "core/hle/kernel/thread.h"
namespace Kernel {
@@ -14,6 +16,16 @@ namespace Kernel {
ServerPort::ServerPort() {}
ServerPort::~ServerPort() {}
+ResultVal<SharedPtr<ServerSession>> ServerPort::Accept() {
+ if (pending_sessions.empty()) {
+ return ERR_NO_PENDING_SESSIONS;
+ }
+
+ auto session = std::move(pending_sessions.back());
+ pending_sessions.pop_back();
+ return MakeResult(std::move(session));
+}
+
bool ServerPort::ShouldWait(Thread* thread) const {
// If there are no pending sessions, we wait until a new one is added.
return pending_sessions.size() == 0;
diff --git a/src/core/hle/kernel/server_port.h b/src/core/hle/kernel/server_port.h
index f1419cd46..6fe7c7f2f 100644
--- a/src/core/hle/kernel/server_port.h
+++ b/src/core/hle/kernel/server_port.h
@@ -14,6 +14,7 @@
namespace Kernel {
class ClientPort;
+class ServerSession;
class SessionRequestHandler;
class ServerPort final : public WaitObject {
@@ -41,6 +42,12 @@ public:
}
/**
+ * Accepts a pending incoming connection on this port. If there are no pending sessions, will
+ * return ERR_NO_PENDING_SESSIONS.
+ */
+ ResultVal<SharedPtr<ServerSession>> Accept();
+
+ /**
* Sets the HLE handler template for the port. ServerSessions crated by connecting to this port
* will inherit a reference to this handler.
*/
@@ -50,8 +57,8 @@ public:
std::string name; ///< Name of port (optional)
- std::vector<SharedPtr<WaitObject>>
- pending_sessions; ///< ServerSessions waiting to be accepted by the port
+ /// ServerSessions waiting to be accepted by the port
+ std::vector<SharedPtr<ServerSession>> pending_sessions;
/// This session's HLE request handler template (optional)
/// ServerSessions created from this port inherit a reference to this handler.
diff --git a/src/core/hle/kernel/server_session.cpp b/src/core/hle/kernel/server_session.cpp
index 970eac5fe..337896abf 100644
--- a/src/core/hle/kernel/server_session.cpp
+++ b/src/core/hle/kernel/server_session.cpp
@@ -81,7 +81,7 @@ ResultCode ServerSession::HandleSyncRequest(SharedPtr<Thread> thread) {
ServerSession::SessionPair ServerSession::CreateSessionPair(const std::string& name,
SharedPtr<ClientPort> port) {
- auto server_session = ServerSession::Create(name + "_Server").MoveFrom();
+ auto server_session = ServerSession::Create(name + "_Server").Unwrap();
SharedPtr<ClientSession> client_session(new ClientSession);
client_session->name = name + "_Client";
diff --git a/src/core/hle/kernel/thread.cpp b/src/core/hle/kernel/thread.cpp
index 75ce626f8..f5f2eb2f7 100644
--- a/src/core/hle/kernel/thread.cpp
+++ b/src/core/hle/kernel/thread.cpp
@@ -389,7 +389,7 @@ ResultVal<SharedPtr<Thread>> Thread::Create(std::string name, VAddr entry_point,
thread->wait_objects.clear();
thread->wait_address = 0;
thread->name = std::move(name);
- thread->callback_handle = wakeup_callback_handle_table.Create(thread).MoveFrom();
+ thread->callback_handle = wakeup_callback_handle_table.Create(thread).Unwrap();
thread->owner_process = g_current_process;
// Find the next available TLS index, and mark it as used
@@ -484,7 +484,7 @@ SharedPtr<Thread> SetupMainThread(u32 entry_point, s32 priority) {
auto thread_res = Thread::Create("main", entry_point, priority, 0, THREADPROCESSORID_0,
Memory::HEAP_VADDR_END);
- SharedPtr<Thread> thread = thread_res.MoveFrom();
+ SharedPtr<Thread> thread = std::move(thread_res).Unwrap();
thread->context.fpscr =
FPSCR_DEFAULT_NAN | FPSCR_FLUSH_TO_ZERO | FPSCR_ROUND_TOZERO | FPSCR_IXC; // 0x03C00010
diff --git a/src/core/hle/kernel/timer.cpp b/src/core/hle/kernel/timer.cpp
index 6f2cf3b02..d7ec93672 100644
--- a/src/core/hle/kernel/timer.cpp
+++ b/src/core/hle/kernel/timer.cpp
@@ -30,7 +30,7 @@ SharedPtr<Timer> Timer::Create(ResetType reset_type, std::string name) {
timer->name = std::move(name);
timer->initial_delay = 0;
timer->interval_delay = 0;
- timer->callback_handle = timer_callback_handle_table.Create(timer).MoveFrom();
+ timer->callback_handle = timer_callback_handle_table.Create(timer).Unwrap();
return timer;
}
diff --git a/src/core/hle/result.h b/src/core/hle/result.h
index 5f2cdbb96..47b6e2b23 100644
--- a/src/core/hle/result.h
+++ b/src/core/hle/result.h
@@ -388,13 +388,14 @@ public:
}
/// Asserts that the result succeeded and returns a reference to it.
- T& Unwrap() {
+ T& Unwrap() & {
ASSERT_MSG(Succeeded(), "Tried to Unwrap empty ResultVal");
return **this;
}
- T&& MoveFrom() {
- return std::move(Unwrap());
+ T&& Unwrap() && {
+ ASSERT_MSG(Succeeded(), "Tried to Unwrap empty ResultVal");
+ return std::move(**this);
}
private:
diff --git a/src/core/hle/service/apt/apt.cpp b/src/core/hle/service/apt/apt.cpp
index 4c587e3c8..25e7b777d 100644
--- a/src/core/hle/service/apt/apt.cpp
+++ b/src/core/hle/service/apt/apt.cpp
@@ -55,8 +55,8 @@ void Initialize(Service::Interface* self) {
u32 flags = rp.Pop<u32>();
IPC::RequestBuilder rb = rp.MakeBuilder(1, 3);
rb.Push(RESULT_SUCCESS);
- rb.PushCopyHandles(Kernel::g_handle_table.Create(notification_event).MoveFrom(),
- Kernel::g_handle_table.Create(parameter_event).MoveFrom());
+ rb.PushCopyHandles(Kernel::g_handle_table.Create(notification_event).Unwrap(),
+ Kernel::g_handle_table.Create(parameter_event).Unwrap());
// TODO(bunnei): Check if these events are cleared every time Initialize is called.
notification_event->Clear();
@@ -93,7 +93,7 @@ void GetSharedFont(Service::Interface* self) {
// allocated, the real APT service calculates this address by scanning the entire address space
// (using svcQueryMemory) and searches for an allocation of the same size as the Shared Font.
rb.Push(target_address);
- rb.PushCopyHandles(Kernel::g_handle_table.Create(shared_font_mem).MoveFrom());
+ rb.PushCopyHandles(Kernel::g_handle_table.Create(shared_font_mem).Unwrap());
}
void NotifyToWait(Service::Interface* self) {
@@ -115,7 +115,7 @@ void GetLockHandle(Service::Interface* self) {
rb.Push(RESULT_SUCCESS); // No error
rb.Push(applet_attributes); // Applet Attributes, this value is passed to Enable.
rb.Push<u32>(0); // Least significant bit = power button state
- Kernel::Handle handle_copy = Kernel::g_handle_table.Create(lock).MoveFrom();
+ Kernel::Handle handle_copy = Kernel::g_handle_table.Create(lock).Unwrap();
rb.PushCopyHandles(handle_copy);
LOG_WARNING(Service_APT, "(STUBBED) called handle=0x%08X applet_attributes=0x%08X", handle_copy,
@@ -231,7 +231,7 @@ void ReceiveParameter(Service::Interface* self) {
rb.Push(static_cast<u32>(next_parameter.buffer.size())); // Parameter buffer size
rb.PushMoveHandles((next_parameter.object != nullptr)
- ? Kernel::g_handle_table.Create(next_parameter.object).MoveFrom()
+ ? Kernel::g_handle_table.Create(next_parameter.object).Unwrap()
: 0);
rb.PushStaticBuffer(buffer, static_cast<u32>(next_parameter.buffer.size()), 0);
@@ -261,7 +261,7 @@ void GlanceParameter(Service::Interface* self) {
rb.Push(static_cast<u32>(next_parameter.buffer.size())); // Parameter buffer size
rb.PushCopyHandles((next_parameter.object != nullptr)
- ? Kernel::g_handle_table.Create(next_parameter.object).MoveFrom()
+ ? Kernel::g_handle_table.Create(next_parameter.object).Unwrap()
: 0);
rb.PushStaticBuffer(buffer, static_cast<u32>(next_parameter.buffer.size()), 0);
diff --git a/src/core/hle/service/cam/cam.cpp b/src/core/hle/service/cam/cam.cpp
index 7394c844f..c9f9e9d95 100644
--- a/src/core/hle/service/cam/cam.cpp
+++ b/src/core/hle/service/cam/cam.cpp
@@ -347,7 +347,7 @@ void GetVsyncInterruptEvent(Service::Interface* self) {
int port = *port_select.begin();
rb.Push(RESULT_SUCCESS);
rb.PushCopyHandles(
- Kernel::g_handle_table.Create(ports[port].vsync_interrupt_event).MoveFrom());
+ Kernel::g_handle_table.Create(ports[port].vsync_interrupt_event).Unwrap());
} else {
LOG_ERROR(Service_CAM, "invalid port_select=%u", port_select.m_val);
rb.Push(ERROR_INVALID_ENUM_VALUE);
@@ -366,7 +366,7 @@ void GetBufferErrorInterruptEvent(Service::Interface* self) {
int port = *port_select.begin();
rb.Push(RESULT_SUCCESS);
rb.PushCopyHandles(
- Kernel::g_handle_table.Create(ports[port].buffer_error_interrupt_event).MoveFrom());
+ Kernel::g_handle_table.Create(ports[port].buffer_error_interrupt_event).Unwrap());
} else {
LOG_ERROR(Service_CAM, "invalid port_select=%u", port_select.m_val);
rb.Push(ERROR_INVALID_ENUM_VALUE);
@@ -400,7 +400,7 @@ void SetReceiving(Service::Interface* self) {
}
rb.Push(RESULT_SUCCESS);
- rb.PushCopyHandles(Kernel::g_handle_table.Create(port.completion_event).MoveFrom());
+ rb.PushCopyHandles(Kernel::g_handle_table.Create(port.completion_event).Unwrap());
} else {
LOG_ERROR(Service_CAM, "invalid port_select=%u", port_select.m_val);
rb.Push(ERROR_INVALID_ENUM_VALUE);
diff --git a/src/core/hle/service/cecd/cecd.cpp b/src/core/hle/service/cecd/cecd.cpp
index bd9814244..421006a9e 100644
--- a/src/core/hle/service/cecd/cecd.cpp
+++ b/src/core/hle/service/cecd/cecd.cpp
@@ -31,8 +31,8 @@ void GetCecStateAbbreviated(Service::Interface* self) {
void GetCecInfoEventHandle(Service::Interface* self) {
u32* cmd_buff = Kernel::GetCommandBuffer();
- cmd_buff[1] = RESULT_SUCCESS.raw; // No error
- cmd_buff[3] = Kernel::g_handle_table.Create(cecinfo_event).MoveFrom(); // Event handle
+ cmd_buff[1] = RESULT_SUCCESS.raw; // No error
+ cmd_buff[3] = Kernel::g_handle_table.Create(cecinfo_event).Unwrap(); // Event handle
LOG_WARNING(Service_CECD, "(STUBBED) called");
}
@@ -40,8 +40,8 @@ void GetCecInfoEventHandle(Service::Interface* self) {
void GetChangeStateEventHandle(Service::Interface* self) {
u32* cmd_buff = Kernel::GetCommandBuffer();
- cmd_buff[1] = RESULT_SUCCESS.raw; // No error
- cmd_buff[3] = Kernel::g_handle_table.Create(change_state_event).MoveFrom(); // Event handle
+ cmd_buff[1] = RESULT_SUCCESS.raw; // No error
+ cmd_buff[3] = Kernel::g_handle_table.Create(change_state_event).Unwrap(); // Event handle
LOG_WARNING(Service_CECD, "(STUBBED) called");
}
diff --git a/src/core/hle/service/cfg/cfg.cpp b/src/core/hle/service/cfg/cfg.cpp
index 5a7878b31..6624f1711 100644
--- a/src/core/hle/service/cfg/cfg.cpp
+++ b/src/core/hle/service/cfg/cfg.cpp
@@ -406,7 +406,7 @@ ResultCode UpdateConfigNANDSavegame() {
auto config_result = Service::FS::OpenFileFromArchive(cfg_system_save_data_archive, path, mode);
ASSERT_MSG(config_result.Succeeded(), "could not open file");
- auto config = config_result.MoveFrom();
+ auto config = std::move(config_result).Unwrap();
config->backend->Write(0, CONFIG_SAVEFILE_SIZE, 1, cfg_config_file_buffer.data());
return RESULT_SUCCESS;
@@ -560,7 +560,7 @@ ResultCode LoadConfigNANDSaveFile() {
// Read the file if it already exists
if (config_result.Succeeded()) {
- auto config = config_result.MoveFrom();
+ auto config = std::move(config_result).Unwrap();
config->backend->Read(0, CONFIG_SAVEFILE_SIZE, cfg_config_file_buffer.data());
return RESULT_SUCCESS;
}
diff --git a/src/core/hle/service/csnd_snd.cpp b/src/core/hle/service/csnd_snd.cpp
index 1455f20ca..9471ec1ef 100644
--- a/src/core/hle/service/csnd_snd.cpp
+++ b/src/core/hle/service/csnd_snd.cpp
@@ -51,8 +51,8 @@ static void Initialize(Interface* self) {
cmd_buff[1] = RESULT_SUCCESS.raw;
cmd_buff[2] = IPC::CopyHandleDesc(2);
- cmd_buff[3] = Kernel::g_handle_table.Create(mutex).MoveFrom();
- cmd_buff[4] = Kernel::g_handle_table.Create(shared_memory).MoveFrom();
+ cmd_buff[3] = Kernel::g_handle_table.Create(mutex).Unwrap();
+ cmd_buff[4] = Kernel::g_handle_table.Create(shared_memory).Unwrap();
LOG_WARNING(Service_CSND, "(STUBBED) called");
}
diff --git a/src/core/hle/service/dsp_dsp.cpp b/src/core/hle/service/dsp_dsp.cpp
index 363066d14..7d746054f 100644
--- a/src/core/hle/service/dsp_dsp.cpp
+++ b/src/core/hle/service/dsp_dsp.cpp
@@ -168,7 +168,7 @@ static void GetSemaphoreEventHandle(Service::Interface* self) {
cmd_buff[0] = IPC::MakeHeader(0x16, 1, 2);
cmd_buff[1] = RESULT_SUCCESS.raw; // No error
// cmd_buff[2] not set
- cmd_buff[3] = Kernel::g_handle_table.Create(semaphore_event).MoveFrom(); // Event handle
+ cmd_buff[3] = Kernel::g_handle_table.Create(semaphore_event).Unwrap(); // Event handle
LOG_WARNING(Service_DSP, "(STUBBED) called");
}
diff --git a/src/core/hle/service/fs/archive.cpp b/src/core/hle/service/fs/archive.cpp
index 3605ef175..033fbc9aa 100644
--- a/src/core/hle/service/fs/archive.cpp
+++ b/src/core/hle/service/fs/archive.cpp
@@ -311,7 +311,7 @@ ResultVal<std::shared_ptr<File>> OpenFileFromArchive(ArchiveHandle archive_handl
if (backend.Failed())
return backend.Code();
- auto file = std::shared_ptr<File>(new File(backend.MoveFrom(), path));
+ auto file = std::shared_ptr<File>(new File(std::move(backend).Unwrap(), path));
return MakeResult<std::shared_ptr<File>>(std::move(file));
}
@@ -401,7 +401,7 @@ ResultVal<std::shared_ptr<Directory>> OpenDirectoryFromArchive(ArchiveHandle arc
if (backend.Failed())
return backend.Code();
- auto directory = std::shared_ptr<Directory>(new Directory(backend.MoveFrom(), path));
+ auto directory = std::shared_ptr<Directory>(new Directory(std::move(backend).Unwrap(), path));
return MakeResult<std::shared_ptr<Directory>>(std::move(directory));
}
diff --git a/src/core/hle/service/fs/fs_user.cpp b/src/core/hle/service/fs/fs_user.cpp
index 34e1783ec..b9eab7838 100644
--- a/src/core/hle/service/fs/fs_user.cpp
+++ b/src/core/hle/service/fs/fs_user.cpp
@@ -87,7 +87,7 @@ static void OpenFile(Service::Interface* self) {
file->ClientConnected(std::get<SharedPtr<ServerSession>>(sessions));
rb.PushMoveHandles(
- Kernel::g_handle_table.Create(std::get<SharedPtr<ClientSession>>(sessions)).MoveFrom());
+ Kernel::g_handle_table.Create(std::get<SharedPtr<ClientSession>>(sessions)).Unwrap());
} else {
rb.PushMoveHandles(0);
LOG_ERROR(Service_FS, "failed to get a handle for file %s", file_path.DebugStr().c_str());
@@ -153,7 +153,7 @@ static void OpenFileDirectly(Service::Interface* self) {
file->ClientConnected(std::get<SharedPtr<ServerSession>>(sessions));
cmd_buff[3] =
- Kernel::g_handle_table.Create(std::get<SharedPtr<ClientSession>>(sessions)).MoveFrom();
+ Kernel::g_handle_table.Create(std::get<SharedPtr<ClientSession>>(sessions)).Unwrap();
} else {
cmd_buff[3] = 0;
LOG_ERROR(Service_FS, "failed to get a handle for file %s mode=%u attributes=%u",
@@ -420,7 +420,7 @@ static void OpenDirectory(Service::Interface* self) {
directory->ClientConnected(std::get<SharedPtr<ServerSession>>(sessions));
cmd_buff[3] =
- Kernel::g_handle_table.Create(std::get<SharedPtr<ClientSession>>(sessions)).MoveFrom();
+ Kernel::g_handle_table.Create(std::get<SharedPtr<ClientSession>>(sessions)).Unwrap();
} else {
LOG_ERROR(Service_FS, "failed to get a handle for directory type=%d size=%d data=%s",
dirname_type, dirname_size, dir_path.DebugStr().c_str());
diff --git a/src/core/hle/service/gsp_gpu.cpp b/src/core/hle/service/gsp_gpu.cpp
index 6ff0f4812..bc964ec60 100644
--- a/src/core/hle/service/gsp_gpu.cpp
+++ b/src/core/hle/service/gsp_gpu.cpp
@@ -389,8 +389,8 @@ static void RegisterInterruptRelayQueue(Interface* self) {
} else {
cmd_buff[1] = RESULT_SUCCESS.raw;
}
- cmd_buff[2] = g_thread_id++; // Thread ID
- cmd_buff[4] = Kernel::g_handle_table.Create(g_shared_memory).MoveFrom(); // GSP shared memory
+ cmd_buff[2] = g_thread_id++; // Thread ID
+ cmd_buff[4] = Kernel::g_handle_table.Create(g_shared_memory).Unwrap(); // GSP shared memory
g_interrupt_event->Signal(); // TODO(bunnei): Is this correct?
diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp
index 5255f6dc8..2014b8461 100644
--- a/src/core/hle/service/hid/hid.cpp
+++ b/src/core/hle/service/hid/hid.cpp
@@ -253,12 +253,12 @@ void GetIPCHandles(Service::Interface* self) {
cmd_buff[1] = 0; // No error
cmd_buff[2] = 0x14000000; // IPC Command Structure translate-header
// TODO(yuriks): Return error from SendSyncRequest is this fails (part of IPC marshalling)
- cmd_buff[3] = Kernel::g_handle_table.Create(Service::HID::shared_mem).MoveFrom();
- cmd_buff[4] = Kernel::g_handle_table.Create(Service::HID::event_pad_or_touch_1).MoveFrom();
- cmd_buff[5] = Kernel::g_handle_table.Create(Service::HID::event_pad_or_touch_2).MoveFrom();
- cmd_buff[6] = Kernel::g_handle_table.Create(Service::HID::event_accelerometer).MoveFrom();
- cmd_buff[7] = Kernel::g_handle_table.Create(Service::HID::event_gyroscope).MoveFrom();
- cmd_buff[8] = Kernel::g_handle_table.Create(Service::HID::event_debug_pad).MoveFrom();
+ cmd_buff[3] = Kernel::g_handle_table.Create(Service::HID::shared_mem).Unwrap();
+ cmd_buff[4] = Kernel::g_handle_table.Create(Service::HID::event_pad_or_touch_1).Unwrap();
+ cmd_buff[5] = Kernel::g_handle_table.Create(Service::HID::event_pad_or_touch_2).Unwrap();
+ cmd_buff[6] = Kernel::g_handle_table.Create(Service::HID::event_accelerometer).Unwrap();
+ cmd_buff[7] = Kernel::g_handle_table.Create(Service::HID::event_gyroscope).Unwrap();
+ cmd_buff[8] = Kernel::g_handle_table.Create(Service::HID::event_debug_pad).Unwrap();
}
void EnableAccelerometer(Service::Interface* self) {
diff --git a/src/core/hle/service/ir/ir_rst.cpp b/src/core/hle/service/ir/ir_rst.cpp
index 0de698003..837413f93 100644
--- a/src/core/hle/service/ir/ir_rst.cpp
+++ b/src/core/hle/service/ir/ir_rst.cpp
@@ -145,8 +145,8 @@ static void GetHandles(Interface* self) {
IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x01, 0, 0);
IPC::RequestBuilder rb = rp.MakeBuilder(1, 3);
rb.Push(RESULT_SUCCESS);
- rb.PushMoveHandles(Kernel::g_handle_table.Create(Service::IR::shared_memory).MoveFrom(),
- Kernel::g_handle_table.Create(Service::IR::update_event).MoveFrom());
+ rb.PushMoveHandles(Kernel::g_handle_table.Create(Service::IR::shared_memory).Unwrap(),
+ Kernel::g_handle_table.Create(Service::IR::update_event).Unwrap());
}
/**
diff --git a/src/core/hle/service/ir/ir_user.cpp b/src/core/hle/service/ir/ir_user.cpp
index fdecdce64..fbdf7a465 100644
--- a/src/core/hle/service/ir/ir_user.cpp
+++ b/src/core/hle/service/ir/ir_user.cpp
@@ -337,7 +337,7 @@ void GetReceiveEvent(Interface* self) {
IPC::RequestBuilder rb(Kernel::GetCommandBuffer(), 0x0A, 1, 2);
rb.Push(RESULT_SUCCESS);
- rb.PushCopyHandles(Kernel::g_handle_table.Create(Service::IR::receive_event).MoveFrom());
+ rb.PushCopyHandles(Kernel::g_handle_table.Create(Service::IR::receive_event).Unwrap());
LOG_INFO(Service_IR, "called");
}
@@ -354,7 +354,7 @@ void GetSendEvent(Interface* self) {
IPC::RequestBuilder rb(Kernel::GetCommandBuffer(), 0x0B, 1, 2);
rb.Push(RESULT_SUCCESS);
- rb.PushCopyHandles(Kernel::g_handle_table.Create(Service::IR::send_event).MoveFrom());
+ rb.PushCopyHandles(Kernel::g_handle_table.Create(Service::IR::send_event).Unwrap());
LOG_INFO(Service_IR, "called");
}
@@ -394,7 +394,7 @@ static void GetConnectionStatusEvent(Interface* self) {
IPC::RequestBuilder rb(Kernel::GetCommandBuffer(), 0x0C, 1, 2);
rb.Push(RESULT_SUCCESS);
- rb.PushCopyHandles(Kernel::g_handle_table.Create(Service::IR::conn_status_event).MoveFrom());
+ rb.PushCopyHandles(Kernel::g_handle_table.Create(Service::IR::conn_status_event).Unwrap());
LOG_INFO(Service_IR, "called");
}
diff --git a/src/core/hle/service/mic_u.cpp b/src/core/hle/service/mic_u.cpp
index 35212b59b..23e1ff094 100644
--- a/src/core/hle/service/mic_u.cpp
+++ b/src/core/hle/service/mic_u.cpp
@@ -160,7 +160,7 @@ static void IsSampling(Interface* self) {
static void GetBufferFullEvent(Interface* self) {
u32* cmd_buff = Kernel::GetCommandBuffer();
cmd_buff[1] = RESULT_SUCCESS.raw; // No error
- cmd_buff[3] = Kernel::g_handle_table.Create(buffer_full_event).MoveFrom();
+ cmd_buff[3] = Kernel::g_handle_table.Create(buffer_full_event).Unwrap();
LOG_WARNING(Service_MIC, "(STUBBED) called");
}
diff --git a/src/core/hle/service/nfc/nfc.cpp b/src/core/hle/service/nfc/nfc.cpp
index b44a9f668..cb09ed0b7 100644
--- a/src/core/hle/service/nfc/nfc.cpp
+++ b/src/core/hle/service/nfc/nfc.cpp
@@ -95,7 +95,7 @@ void GetTagInRangeEvent(Interface* self) {
cmd_buff[0] = IPC::MakeHeader(0xB, 1, 2);
cmd_buff[1] = RESULT_SUCCESS.raw;
cmd_buff[2] = IPC::CopyHandleDesc();
- cmd_buff[3] = Kernel::g_handle_table.Create(tag_in_range_event).MoveFrom();
+ cmd_buff[3] = Kernel::g_handle_table.Create(tag_in_range_event).Unwrap();
LOG_WARNING(Service_NFC, "(STUBBED) called");
}
@@ -105,7 +105,7 @@ void GetTagOutOfRangeEvent(Interface* self) {
cmd_buff[0] = IPC::MakeHeader(0xC, 1, 2);
cmd_buff[1] = RESULT_SUCCESS.raw;
cmd_buff[2] = IPC::CopyHandleDesc();
- cmd_buff[3] = Kernel::g_handle_table.Create(tag_out_of_range_event).MoveFrom();
+ cmd_buff[3] = Kernel::g_handle_table.Create(tag_out_of_range_event).Unwrap();
LOG_WARNING(Service_NFC, "(STUBBED) called");
}
diff --git a/src/core/hle/service/nwm/nwm_uds.cpp b/src/core/hle/service/nwm/nwm_uds.cpp
index e92900d48..6dbdff044 100644
--- a/src/core/hle/service/nwm/nwm_uds.cpp
+++ b/src/core/hle/service/nwm/nwm_uds.cpp
@@ -15,6 +15,7 @@
#include "core/hle/result.h"
#include "core/hle/service/nwm/nwm_uds.h"
#include "core/hle/service/nwm/uds_beacon.h"
+#include "core/hle/service/nwm/uds_data.h"
#include "core/memory.h"
namespace Service {
@@ -190,7 +191,7 @@ static void InitializeWithVersion(Interface* self) {
IPC::RequestBuilder rb = rp.MakeBuilder(1, 2);
rb.Push(RESULT_SUCCESS);
- rb.PushCopyHandles(Kernel::g_handle_table.Create(connection_status_event).MoveFrom());
+ rb.PushCopyHandles(Kernel::g_handle_table.Create(connection_status_event).Unwrap());
LOG_DEBUG(Service_NWM, "called sharedmem_size=0x%08X, version=0x%08X, sharedmem_handle=0x%08X",
sharedmem_size, version, sharedmem_handle);
@@ -265,7 +266,7 @@ static void Bind(Interface* self) {
IPC::RequestBuilder rb = rp.MakeBuilder(1, 2);
rb.Push(RESULT_SUCCESS);
- rb.PushCopyHandles(Kernel::g_handle_table.Create(event).MoveFrom());
+ rb.PushCopyHandles(Kernel::g_handle_table.Create(event).Unwrap());
}
/**
@@ -373,6 +374,80 @@ static void DestroyNetwork(Interface* self) {
}
/**
+ * NWM_UDS::SendTo service function.
+ * Sends a data frame to the UDS network we're connected to.
+ * Inputs:
+ * 0 : Command header.
+ * 1 : Unknown.
+ * 2 : u16 Destination network node id.
+ * 3 : u8 Data channel.
+ * 4 : Buffer size >> 2
+ * 5 : Data size
+ * 6 : Flags
+ * 7 : Input buffer descriptor
+ * 8 : Input buffer address
+ * Outputs:
+ * 0 : Return header
+ * 1 : Result of function, 0 on success, otherwise error code
+ */
+static void SendTo(Interface* self) {
+ IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x17, 6, 2);
+
+ rp.Skip(1, false);
+ u16 dest_node_id = rp.Pop<u16>();
+ u8 data_channel = rp.Pop<u8>();
+ rp.Skip(1, false);
+ u32 data_size = rp.Pop<u32>();
+ u32 flags = rp.Pop<u32>();
+
+ size_t desc_size;
+ const VAddr input_address = rp.PopStaticBuffer(&desc_size, false);
+ ASSERT(desc_size == data_size);
+
+ IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
+
+ if (connection_status.status != static_cast<u32>(NetworkStatus::ConnectedAsClient) &&
+ connection_status.status != static_cast<u32>(NetworkStatus::ConnectedAsHost)) {
+ rb.Push(ResultCode(ErrorDescription::NotAuthorized, ErrorModule::UDS,
+ ErrorSummary::InvalidState, ErrorLevel::Status));
+ return;
+ }
+
+ if (dest_node_id == connection_status.network_node_id) {
+ rb.Push(ResultCode(ErrorDescription::NotFound, ErrorModule::UDS,
+ ErrorSummary::WrongArgument, ErrorLevel::Status));
+ return;
+ }
+
+ // TODO(Subv): Do something with the flags.
+
+ constexpr size_t MaxSize = 0x5C6;
+ if (data_size > MaxSize) {
+ rb.Push(ResultCode(ErrorDescription::TooLarge, ErrorModule::UDS,
+ ErrorSummary::WrongArgument, ErrorLevel::Usage));
+ return;
+ }
+
+ std::vector<u8> data(data_size);
+ Memory::ReadBlock(input_address, data.data(), data.size());
+
+ // TODO(Subv): Increment the sequence number after each sent packet.
+ u16 sequence_number = 0;
+ std::vector<u8> data_payload = GenerateDataPayload(
+ data, data_channel, dest_node_id, connection_status.network_node_id, sequence_number);
+
+ // TODO(Subv): Retrieve the MAC address of the dest_node_id and our own to encrypt
+ // and encapsulate the payload.
+
+ // TODO(Subv): Send the frame.
+
+ rb.Push(RESULT_SUCCESS);
+
+ LOG_WARNING(Service_NWM, "(STUB) called dest_node_id=%u size=%u flags=%u channel=%u",
+ static_cast<u32>(dest_node_id), data_size, flags, static_cast<u32>(data_channel));
+}
+
+/**
* NWM_UDS::GetChannel service function.
* Returns the WiFi channel in which the network we're connected to is transmitting.
* Inputs:
@@ -543,6 +618,42 @@ static void BeaconBroadcastCallback(u64 userdata, int cycles_late) {
beacon_broadcast_event, 0);
}
+/*
+ * Returns an available index in the nodes array for the
+ * currently-hosted UDS network.
+ */
+static u32 GetNextAvailableNodeId() {
+ ASSERT_MSG(connection_status.status == static_cast<u32>(NetworkStatus::ConnectedAsHost),
+ "Can not accept clients if we're not hosting a network");
+
+ for (unsigned index = 0; index < connection_status.max_nodes; ++index) {
+ if ((connection_status.node_bitmask & (1 << index)) == 0)
+ return index;
+ }
+
+ // Any connection attempts to an already full network should have been refused.
+ ASSERT_MSG(false, "No available connection slots in the network");
+}
+
+/*
+ * Called when a client connects to an UDS network we're hosting,
+ * updates the connection status and signals the update event.
+ * @param network_node_id Network Node Id of the connecting client.
+ */
+void OnClientConnected(u16 network_node_id) {
+ ASSERT_MSG(connection_status.status == static_cast<u32>(NetworkStatus::ConnectedAsHost),
+ "Can not accept clients if we're not hosting a network");
+ ASSERT_MSG(connection_status.total_nodes < connection_status.max_nodes,
+ "Can not accept connections on a full network");
+
+ u32 node_id = GetNextAvailableNodeId();
+ connection_status.node_bitmask |= 1 << node_id;
+ connection_status.changed_nodes |= 1 << node_id;
+ connection_status.nodes[node_id] = network_node_id;
+ connection_status.total_nodes++;
+ connection_status_event->Signal();
+}
+
const Interface::FunctionInfo FunctionTable[] = {
{0x00010442, nullptr, "Initialize (deprecated)"},
{0x00020000, nullptr, "Scrap"},
@@ -564,7 +675,7 @@ const Interface::FunctionInfo FunctionTable[] = {
{0x00130040, nullptr, "Unbind"},
{0x001400C0, nullptr, "PullPacket"},
{0x00150080, nullptr, "SetMaxSendDelay"},
- {0x00170182, nullptr, "SendTo"},
+ {0x00170182, SendTo, "SendTo"},
{0x001A0000, GetChannel, "GetChannel"},
{0x001B0302, InitializeWithVersion, "InitializeWithVersion"},
{0x001D0044, BeginHostingNetwork, "BeginHostingNetwork"},
diff --git a/src/core/hle/service/nwm/uds_data.cpp b/src/core/hle/service/nwm/uds_data.cpp
new file mode 100644
index 000000000..8c6742dba
--- /dev/null
+++ b/src/core/hle/service/nwm/uds_data.cpp
@@ -0,0 +1,278 @@
+// Copyright 2017 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <cstring>
+#include <cryptopp/aes.h>
+#include <cryptopp/ccm.h>
+#include <cryptopp/filters.h>
+#include <cryptopp/md5.h>
+#include <cryptopp/modes.h>
+#include "core/hle/service/nwm/nwm_uds.h"
+#include "core/hle/service/nwm/uds_data.h"
+#include "core/hw/aes/key.h"
+
+namespace Service {
+namespace NWM {
+
+using MacAddress = std::array<u8, 6>;
+
+/*
+ * Generates a SNAP-enabled 802.2 LLC header for the specified protocol.
+ * @returns a buffer with the bytes of the generated header.
+ */
+static std::vector<u8> GenerateLLCHeader(EtherType protocol) {
+ LLCHeader header{};
+ header.protocol = static_cast<u16>(protocol);
+
+ std::vector<u8> buffer(sizeof(header));
+ memcpy(buffer.data(), &header, sizeof(header));
+
+ return buffer;
+}
+
+/*
+ * Generates a Nintendo UDS SecureData header with the specified parameters.
+ * @returns a buffer with the bytes of the generated header.
+ */
+static std::vector<u8> GenerateSecureDataHeader(u16 data_size, u8 channel, u16 dest_node_id,
+ u16 src_node_id, u16 sequence_number) {
+ SecureDataHeader header{};
+ header.protocol_size = data_size + sizeof(SecureDataHeader);
+ // Note: This size includes everything except the first 4 bytes of the structure,
+ // reinforcing the hypotheses that the first 4 bytes are actually the header of
+ // another container protocol.
+ header.securedata_size = data_size + sizeof(SecureDataHeader) - 4;
+ // Frames sent by the emulated application are never UDS management frames
+ header.is_management = 0;
+ header.data_channel = channel;
+ header.sequence_number = sequence_number;
+ header.dest_node_id = dest_node_id;
+ header.src_node_id = src_node_id;
+
+ std::vector<u8> buffer(sizeof(header));
+ memcpy(buffer.data(), &header, sizeof(header));
+
+ return buffer;
+}
+
+/*
+ * Calculates the CTR used for the AES-CTR process that calculates
+ * the CCMP crypto key for data frames.
+ * @returns The CTR used for data frames crypto key generation.
+ */
+static std::array<u8, CryptoPP::MD5::DIGESTSIZE> GetDataCryptoCTR(const NetworkInfo& network_info) {
+ DataFrameCryptoCTR data{};
+
+ data.host_mac = network_info.host_mac_address;
+ data.wlan_comm_id = network_info.wlan_comm_id;
+ data.id = network_info.id;
+ data.network_id = network_info.network_id;
+
+ std::array<u8, CryptoPP::MD5::DIGESTSIZE> hash;
+ CryptoPP::MD5().CalculateDigest(hash.data(), reinterpret_cast<u8*>(&data), sizeof(data));
+
+ return hash;
+}
+
+/*
+ * Generates the key used for encrypting the 802.11 data frames generated by UDS.
+ * @returns The key used for data frames crypto.
+ */
+static std::array<u8, CryptoPP::AES::BLOCKSIZE> GenerateDataCCMPKey(
+ const std::vector<u8>& passphrase, const NetworkInfo& network_info) {
+ // Calculate the MD5 hash of the input passphrase.
+ std::array<u8, CryptoPP::MD5::DIGESTSIZE> passphrase_hash;
+ CryptoPP::MD5().CalculateDigest(passphrase_hash.data(), passphrase.data(), passphrase.size());
+
+ std::array<u8, CryptoPP::AES::BLOCKSIZE> ccmp_key;
+
+ // The CCMP key is the result of encrypting the MD5 hash of the passphrase with AES-CTR using
+ // keyslot 0x2D.
+ using CryptoPP::AES;
+ std::array<u8, CryptoPP::MD5::DIGESTSIZE> counter = GetDataCryptoCTR(network_info);
+ std::array<u8, AES::BLOCKSIZE> key = HW::AES::GetNormalKey(HW::AES::KeySlotID::UDSDataKey);
+ CryptoPP::CTR_Mode<AES>::Encryption aes;
+ aes.SetKeyWithIV(key.data(), AES::BLOCKSIZE, counter.data());
+ aes.ProcessData(ccmp_key.data(), passphrase_hash.data(), passphrase_hash.size());
+
+ return ccmp_key;
+}
+
+/*
+ * Generates the Additional Authenticated Data (AAD) for an UDS 802.11 encrypted data frame.
+ * @returns a buffer with the bytes of the AAD.
+ */
+static std::vector<u8> GenerateCCMPAAD(const MacAddress& sender, const MacAddress& receiver,
+ const MacAddress& bssid, u16 frame_control) {
+ // Reference: IEEE 802.11-2007
+
+ // 8.3.3.3.2 Construct AAD (22-30 bytes)
+ // The AAD is constructed from the MPDU header. The AAD does not include the header Duration
+ // field, because the Duration field value can change due to normal IEEE 802.11 operation (e.g.,
+ // a rate change during retransmission). For similar reasons, several subfields in the Frame
+ // Control field are masked to 0.
+ struct {
+ u16_be FC; // MPDU Frame Control field
+ MacAddress A1;
+ MacAddress A2;
+ MacAddress A3;
+ u16_be SC; // MPDU Sequence Control field
+ } aad_struct{};
+
+ constexpr u16 AADFrameControlMask = 0x8FC7;
+ aad_struct.FC = frame_control & AADFrameControlMask;
+ aad_struct.SC = 0;
+
+ bool to_ds = (frame_control & (1 << 0)) != 0;
+ bool from_ds = (frame_control & (1 << 1)) != 0;
+ // In the 802.11 standard, ToDS = 1 and FromDS = 1 is a valid configuration,
+ // however, the 3DS doesn't seem to transmit frames with such combination.
+ ASSERT_MSG(to_ds != from_ds, "Invalid combination");
+
+ // The meaning of the address fields depends on the ToDS and FromDS fields.
+ if (from_ds) {
+ aad_struct.A1 = receiver;
+ aad_struct.A2 = bssid;
+ aad_struct.A3 = sender;
+ }
+
+ if (to_ds) {
+ aad_struct.A1 = bssid;
+ aad_struct.A2 = sender;
+ aad_struct.A3 = receiver;
+ }
+
+ std::vector<u8> aad(sizeof(aad_struct));
+ std::memcpy(aad.data(), &aad_struct, sizeof(aad_struct));
+
+ return aad;
+}
+
+/*
+ * Decrypts the payload of an encrypted 802.11 data frame using the specified key.
+ * @returns The decrypted payload.
+ */
+static std::vector<u8> DecryptDataFrame(const std::vector<u8>& encrypted_payload,
+ const std::array<u8, CryptoPP::AES::BLOCKSIZE>& ccmp_key,
+ const MacAddress& sender, const MacAddress& receiver,
+ const MacAddress& bssid, u16 sequence_number,
+ u16 frame_control) {
+
+ // Reference: IEEE 802.11-2007
+
+ std::vector<u8> aad = GenerateCCMPAAD(sender, receiver, bssid, frame_control);
+
+ std::vector<u8> packet_number{0,
+ 0,
+ 0,
+ 0,
+ static_cast<u8>((sequence_number >> 8) & 0xFF),
+ static_cast<u8>(sequence_number & 0xFF)};
+
+ // 8.3.3.3.3 Construct CCM nonce (13 bytes)
+ std::vector<u8> nonce;
+ nonce.push_back(0); // priority
+ nonce.insert(nonce.end(), sender.begin(), sender.end()); // Address 2
+ nonce.insert(nonce.end(), packet_number.begin(), packet_number.end()); // PN
+
+ try {
+ CryptoPP::CCM<CryptoPP::AES, 8>::Decryption d;
+ d.SetKeyWithIV(ccmp_key.data(), ccmp_key.size(), nonce.data(), nonce.size());
+ d.SpecifyDataLengths(aad.size(), encrypted_payload.size() - 8, 0);
+
+ CryptoPP::AuthenticatedDecryptionFilter df(
+ d, nullptr, CryptoPP::AuthenticatedDecryptionFilter::MAC_AT_END |
+ CryptoPP::AuthenticatedDecryptionFilter::THROW_EXCEPTION);
+ // put aad
+ df.ChannelPut(CryptoPP::AAD_CHANNEL, aad.data(), aad.size());
+
+ // put cipher with mac
+ df.ChannelPut(CryptoPP::DEFAULT_CHANNEL, encrypted_payload.data(),
+ encrypted_payload.size() - 8);
+ df.ChannelPut(CryptoPP::DEFAULT_CHANNEL,
+ encrypted_payload.data() + encrypted_payload.size() - 8, 8);
+
+ df.ChannelMessageEnd(CryptoPP::AAD_CHANNEL);
+ df.ChannelMessageEnd(CryptoPP::DEFAULT_CHANNEL);
+ df.SetRetrievalChannel(CryptoPP::DEFAULT_CHANNEL);
+
+ int size = df.MaxRetrievable();
+
+ std::vector<u8> pdata(size);
+ df.Get(pdata.data(), size);
+ return pdata;
+ } catch (CryptoPP::Exception&) {
+ LOG_ERROR(Service_NWM, "failed to decrypt");
+ }
+
+ return {};
+}
+
+/*
+ * Encrypts the payload of an 802.11 data frame using the specified key.
+ * @returns The encrypted payload.
+ */
+static std::vector<u8> EncryptDataFrame(const std::vector<u8>& payload,
+ const std::array<u8, CryptoPP::AES::BLOCKSIZE>& ccmp_key,
+ const MacAddress& sender, const MacAddress& receiver,
+ const MacAddress& bssid, u16 sequence_number,
+ u16 frame_control) {
+ // Reference: IEEE 802.11-2007
+
+ std::vector<u8> aad = GenerateCCMPAAD(sender, receiver, bssid, frame_control);
+
+ std::vector<u8> packet_number{0,
+ 0,
+ 0,
+ 0,
+ static_cast<u8>((sequence_number >> 8) & 0xFF),
+ static_cast<u8>(sequence_number & 0xFF)};
+
+ // 8.3.3.3.3 Construct CCM nonce (13 bytes)
+ std::vector<u8> nonce;
+ nonce.push_back(0); // priority
+ nonce.insert(nonce.end(), sender.begin(), sender.end()); // Address 2
+ nonce.insert(nonce.end(), packet_number.begin(), packet_number.end()); // PN
+
+ try {
+ CryptoPP::CCM<CryptoPP::AES, 8>::Encryption d;
+ d.SetKeyWithIV(ccmp_key.data(), ccmp_key.size(), nonce.data(), nonce.size());
+ d.SpecifyDataLengths(aad.size(), payload.size(), 0);
+
+ CryptoPP::AuthenticatedEncryptionFilter df(d);
+ // put aad
+ df.ChannelPut(CryptoPP::AAD_CHANNEL, aad.data(), aad.size());
+ df.ChannelMessageEnd(CryptoPP::AAD_CHANNEL);
+
+ // put plaintext
+ df.ChannelPut(CryptoPP::DEFAULT_CHANNEL, payload.data(), payload.size());
+ df.ChannelMessageEnd(CryptoPP::DEFAULT_CHANNEL);
+
+ df.SetRetrievalChannel(CryptoPP::DEFAULT_CHANNEL);
+
+ int size = df.MaxRetrievable();
+
+ std::vector<u8> cipher(size);
+ df.Get(cipher.data(), size);
+ return cipher;
+ } catch (CryptoPP::Exception&) {
+ LOG_ERROR(Service_NWM, "failed to encrypt");
+ }
+
+ return {};
+}
+
+std::vector<u8> GenerateDataPayload(const std::vector<u8>& data, u8 channel, u16 dest_node,
+ u16 src_node, u16 sequence_number) {
+ std::vector<u8> buffer = GenerateLLCHeader(EtherType::SecureData);
+ std::vector<u8> securedata_header =
+ GenerateSecureDataHeader(data.size(), channel, dest_node, src_node, sequence_number);
+
+ buffer.insert(buffer.end(), securedata_header.begin(), securedata_header.end());
+ buffer.insert(buffer.end(), data.begin(), data.end());
+ return buffer;
+}
+
+} // namespace NWM
+} // namespace Service
diff --git a/src/core/hle/service/nwm/uds_data.h b/src/core/hle/service/nwm/uds_data.h
new file mode 100644
index 000000000..a23520a41
--- /dev/null
+++ b/src/core/hle/service/nwm/uds_data.h
@@ -0,0 +1,78 @@
+// Copyright 2017 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <array>
+#include <vector>
+#include "common/common_types.h"
+#include "common/swap.h"
+#include "core/hle/service/service.h"
+
+namespace Service {
+namespace NWM {
+
+enum class SAP : u8 { SNAPExtensionUsed = 0xAA };
+
+enum class PDUControl : u8 { UnnumberedInformation = 3 };
+
+enum class EtherType : u16 { SecureData = 0x876D, EAPoL = 0x888E };
+
+/*
+ * 802.2 header, UDS packets always use SNAP for these headers,
+ * which means the dsap and ssap are always SNAPExtensionUsed (0xAA)
+ * and the OUI is always 0.
+ */
+struct LLCHeader {
+ u8 dsap = static_cast<u8>(SAP::SNAPExtensionUsed);
+ u8 ssap = static_cast<u8>(SAP::SNAPExtensionUsed);
+ u8 control = static_cast<u8>(PDUControl::UnnumberedInformation);
+ std::array<u8, 3> OUI = {};
+ u16_be protocol;
+};
+
+static_assert(sizeof(LLCHeader) == 8, "LLCHeader has the wrong size");
+
+/*
+ * Nintendo SecureData header, every UDS packet contains one,
+ * it is used to store metadata about the transmission such as
+ * the source and destination network node ids.
+ */
+struct SecureDataHeader {
+ // TODO(Subv): It is likely that the first 4 bytes of this header are
+ // actually part of another container protocol.
+ u16_be protocol_size;
+ INSERT_PADDING_BYTES(2);
+ u16_be securedata_size;
+ u8 is_management;
+ u8 data_channel;
+ u16_be sequence_number;
+ u16_be dest_node_id;
+ u16_be src_node_id;
+};
+
+static_assert(sizeof(SecureDataHeader) == 14, "SecureDataHeader has the wrong size");
+
+/*
+ * The raw bytes of this structure are the CTR used in the encryption (AES-CTR)
+ * process used to generate the CCMP key for data frame encryption.
+ */
+struct DataFrameCryptoCTR {
+ u32_le wlan_comm_id;
+ u32_le network_id;
+ std::array<u8, 6> host_mac;
+ u16_le id;
+};
+
+static_assert(sizeof(DataFrameCryptoCTR) == 16, "DataFrameCryptoCTR has the wrong size");
+
+/**
+ * Generates an unencrypted 802.11 data payload.
+ * @returns The generated frame payload.
+ */
+std::vector<u8> GenerateDataPayload(const std::vector<u8>& data, u8 channel, u16 dest_node,
+ u16 src_node, u16 sequence_number);
+
+} // namespace NWM
+} // namespace Service
diff --git a/src/core/hle/service/ptm/ptm.cpp b/src/core/hle/service/ptm/ptm.cpp
index 39382ef09..a0b959797 100644
--- a/src/core/hle/service/ptm/ptm.cpp
+++ b/src/core/hle/service/ptm/ptm.cpp
@@ -152,7 +152,7 @@ void Init() {
auto gamecoin_result =
Service::FS::OpenFileFromArchive(*archive_result, gamecoin_path, open_mode);
if (gamecoin_result.Succeeded()) {
- auto gamecoin = gamecoin_result.MoveFrom();
+ auto gamecoin = std::move(gamecoin_result).Unwrap();
gamecoin->backend->Write(0, sizeof(GameCoin), true,
reinterpret_cast<const u8*>(&default_game_coin));
gamecoin->backend->Close();
diff --git a/src/core/hle/service/service.cpp b/src/core/hle/service/service.cpp
index 791a65c19..aad950e50 100644
--- a/src/core/hle/service/service.cpp
+++ b/src/core/hle/service/service.cpp
@@ -173,8 +173,7 @@ void ServiceFrameworkBase::HandleSyncRequest(SharedPtr<ServerSession> server_ses
// TODO(yuriks): The kernel should be the one handling this as part of translation after
// everything else is migrated
- Kernel::HLERequestContext context;
- context.session = std::move(server_session);
+ Kernel::HLERequestContext context(std::move(server_session));
context.PopulateFromIncomingCommandBuffer(cmd_buf, *Kernel::g_current_process,
Kernel::g_handle_table);
@@ -207,7 +206,7 @@ void AddService(Interface* interface_) {
auto server_port =
SM::g_service_manager
->RegisterService(interface_->GetPortName(), interface_->GetMaxSessions())
- .MoveFrom();
+ .Unwrap();
server_port->SetHleHandler(std::shared_ptr<Interface>(interface_));
}
diff --git a/src/core/hle/service/sm/srv.cpp b/src/core/hle/service/sm/srv.cpp
index 74a1256e0..352941e69 100644
--- a/src/core/hle/service/sm/srv.cpp
+++ b/src/core/hle/service/sm/srv.cpp
@@ -113,13 +113,13 @@ void SRV::GetServiceHandle(Kernel::HLERequestContext& ctx) {
(*session)->GetObjectId());
IPC::RequestBuilder rb = rp.MakeBuilder(1, 2);
rb.Push(session.Code());
- rb.PushObjects(session.MoveFrom());
+ rb.PushObjects(std::move(session).Unwrap());
} else if (session.Code() == Kernel::ERR_MAX_CONNECTIONS_REACHED && return_port_on_failure) {
LOG_WARNING(Service_SRV, "called service=%s -> ERR_MAX_CONNECTIONS_REACHED, *port*=%u",
name.c_str(), (*client_port)->GetObjectId());
IPC::RequestBuilder rb = rp.MakeBuilder(1, 2);
rb.Push(ERR_MAX_CONNECTIONS_REACHED);
- rb.PushObjects(client_port.MoveFrom());
+ rb.PushObjects(std::move(client_port).Unwrap());
} else {
LOG_ERROR(Service_SRV, "called service=%s -> error 0x%08X", name.c_str(), session.Code());
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
diff --git a/src/core/hle/service/y2r_u.cpp b/src/core/hle/service/y2r_u.cpp
index bb7bf2d67..e73971d5f 100644
--- a/src/core/hle/service/y2r_u.cpp
+++ b/src/core/hle/service/y2r_u.cpp
@@ -275,7 +275,7 @@ static void GetTransferEndEvent(Interface* self) {
cmd_buff[0] = IPC::MakeHeader(0xF, 2, 0);
cmd_buff[1] = RESULT_SUCCESS.raw;
- cmd_buff[3] = Kernel::g_handle_table.Create(completion_event).MoveFrom();
+ cmd_buff[3] = Kernel::g_handle_table.Create(completion_event).Unwrap();
LOG_DEBUG(Service_Y2R, "called");
}
diff --git a/src/core/hle/svc.cpp b/src/core/hle/svc.cpp
index 0a7f011f3..e4b803046 100644
--- a/src/core/hle/svc.cpp
+++ b/src/core/hle/svc.cpp
@@ -37,8 +37,9 @@
////////////////////////////////////////////////////////////////////////////////////////////////////
// Namespace SVC
-using Kernel::SharedPtr;
using Kernel::ERR_INVALID_HANDLE;
+using Kernel::Handle;
+using Kernel::SharedPtr;
namespace SVC {
@@ -1040,7 +1041,6 @@ static ResultCode CreatePort(Kernel::Handle* server_port, Kernel::Handle* client
using Kernel::ServerPort;
using Kernel::ClientPort;
- using Kernel::SharedPtr;
auto ports = ServerPort::CreatePortPair(max_sessions);
CASCADE_RESULT(*client_port, Kernel::g_handle_table.Create(
@@ -1054,6 +1054,41 @@ static ResultCode CreatePort(Kernel::Handle* server_port, Kernel::Handle* client
return RESULT_SUCCESS;
}
+static ResultCode CreateSessionToPort(Handle* out_client_session, Handle client_port_handle) {
+ using Kernel::ClientPort;
+ SharedPtr<ClientPort> client_port = Kernel::g_handle_table.Get<ClientPort>(client_port_handle);
+ if (client_port == nullptr)
+ return ERR_INVALID_HANDLE;
+
+ CASCADE_RESULT(auto session, client_port->Connect());
+ CASCADE_RESULT(*out_client_session, Kernel::g_handle_table.Create(std::move(session)));
+ return RESULT_SUCCESS;
+}
+
+static ResultCode CreateSession(Handle* server_session, Handle* client_session) {
+ auto sessions = Kernel::ServerSession::CreateSessionPair();
+
+ auto& server = std::get<SharedPtr<Kernel::ServerSession>>(sessions);
+ CASCADE_RESULT(*server_session, Kernel::g_handle_table.Create(std::move(server)));
+
+ auto& client = std::get<SharedPtr<Kernel::ClientSession>>(sessions);
+ CASCADE_RESULT(*client_session, Kernel::g_handle_table.Create(std::move(client)));
+
+ LOG_TRACE(Kernel_SVC, "called");
+ return RESULT_SUCCESS;
+}
+
+static ResultCode AcceptSession(Handle* out_server_session, Handle server_port_handle) {
+ using Kernel::ServerPort;
+ SharedPtr<ServerPort> server_port = Kernel::g_handle_table.Get<ServerPort>(server_port_handle);
+ if (server_port == nullptr)
+ return ERR_INVALID_HANDLE;
+
+ CASCADE_RESULT(auto session, server_port->Accept());
+ CASCADE_RESULT(*out_server_session, Kernel::g_handle_table.Create(std::move(session)));
+ return RESULT_SUCCESS;
+}
+
static ResultCode GetSystemInfo(s64* out, u32 type, s32 param) {
using Kernel::MemoryRegion;
@@ -1228,9 +1263,9 @@ static const FunctionDef SVC_Table[] = {
{0x45, nullptr, "Unknown"},
{0x46, nullptr, "Unknown"},
{0x47, HLE::Wrap<CreatePort>, "CreatePort"},
- {0x48, nullptr, "CreateSessionToPort"},
- {0x49, nullptr, "CreateSession"},
- {0x4A, nullptr, "AcceptSession"},
+ {0x48, HLE::Wrap<CreateSessionToPort>, "CreateSessionToPort"},
+ {0x49, HLE::Wrap<CreateSession>, "CreateSession"},
+ {0x4A, HLE::Wrap<AcceptSession>, "AcceptSession"},
{0x4B, nullptr, "ReplyAndReceive1"},
{0x4C, nullptr, "ReplyAndReceive2"},
{0x4D, nullptr, "ReplyAndReceive3"},
diff --git a/src/core/hw/aes/key.h b/src/core/hw/aes/key.h
index b01d04f13..c9f1342f4 100644
--- a/src/core/hw/aes/key.h
+++ b/src/core/hw/aes/key.h
@@ -12,6 +12,8 @@ namespace HW {
namespace AES {
enum KeySlotID : size_t {
+ // AES Keyslot used to generate the UDS data frame CCMP key.
+ UDSDataKey = 0x2D,
APTWrap = 0x31,
MaxKeySlotID = 0x40,
diff --git a/src/core/hw/gpu.cpp b/src/core/hw/gpu.cpp
index 42809c731..6838e449c 100644
--- a/src/core/hw/gpu.cpp
+++ b/src/core/hw/gpu.cpp
@@ -5,6 +5,7 @@
#include <cstring>
#include <numeric>
#include <type_traits>
+#include "common/alignment.h"
#include "common/color.h"
#include "common/common_types.h"
#include "common/logging/log.h"
@@ -313,7 +314,7 @@ static void TextureCopy(const Regs::DisplayTransferConfig& config) {
const PAddr src_addr = config.GetPhysicalInputAddress();
const PAddr dst_addr = config.GetPhysicalOutputAddress();
- // TODO: do hwtest with these cases
+ // TODO: do hwtest with invalid addresses
if (!Memory::IsValidPhysicalAddress(src_addr)) {
LOG_CRITICAL(HW_GPU, "invalid input address 0x%08X", src_addr);
return;
@@ -324,31 +325,36 @@ static void TextureCopy(const Regs::DisplayTransferConfig& config) {
return;
}
- if (config.texture_copy.input_width == 0) {
- LOG_CRITICAL(HW_GPU, "zero input width");
+ if (VideoCore::g_renderer->Rasterizer()->AccelerateTextureCopy(config))
return;
- }
- if (config.texture_copy.output_width == 0) {
- LOG_CRITICAL(HW_GPU, "zero output width");
+ u8* src_pointer = Memory::GetPhysicalPointer(src_addr);
+ u8* dst_pointer = Memory::GetPhysicalPointer(dst_addr);
+
+ u32 remaining_size = Common::AlignDown(config.texture_copy.size, 16);
+
+ if (remaining_size == 0) {
+ LOG_CRITICAL(HW_GPU, "zero size. Real hardware freezes on this.");
return;
}
- if (config.texture_copy.size == 0) {
- LOG_CRITICAL(HW_GPU, "zero size");
+ u32 input_gap = config.texture_copy.input_gap * 16;
+ u32 output_gap = config.texture_copy.output_gap * 16;
+
+ // Zero gap means contiguous input/output even if width = 0. To avoid infinite loop below, width
+ // is assigned with the total size if gap = 0.
+ u32 input_width = input_gap == 0 ? remaining_size : config.texture_copy.input_width * 16;
+ u32 output_width = output_gap == 0 ? remaining_size : config.texture_copy.output_width * 16;
+
+ if (input_width == 0) {
+ LOG_CRITICAL(HW_GPU, "zero input width. Real hardware freezes on this.");
return;
}
- if (VideoCore::g_renderer->Rasterizer()->AccelerateTextureCopy(config))
+ if (output_width == 0) {
+ LOG_CRITICAL(HW_GPU, "zero output width. Real hardware freezes on this.");
return;
-
- u8* src_pointer = Memory::GetPhysicalPointer(src_addr);
- u8* dst_pointer = Memory::GetPhysicalPointer(dst_addr);
-
- u32 input_width = config.texture_copy.input_width * 16;
- u32 input_gap = config.texture_copy.input_gap * 16;
- u32 output_width = config.texture_copy.output_width * 16;
- u32 output_gap = config.texture_copy.output_gap * 16;
+ }
size_t contiguous_input_size =
config.texture_copy.size / input_width * (input_width + input_gap);
@@ -360,7 +366,6 @@ static void TextureCopy(const Regs::DisplayTransferConfig& config) {
Memory::RasterizerFlushAndInvalidateRegion(config.GetPhysicalOutputAddress(),
static_cast<u32>(contiguous_output_size));
- u32 remaining_size = config.texture_copy.size;
u32 remaining_input = input_width;
u32 remaining_output = output_width;
while (remaining_size > 0) {
diff --git a/src/core/hw/gpu.h b/src/core/hw/gpu.h
index bdd997b2a..21b127fee 100644
--- a/src/core/hw/gpu.h
+++ b/src/core/hw/gpu.h
@@ -225,7 +225,7 @@ struct Regs {
INSERT_PADDING_WORDS(0x1);
struct {
- u32 size;
+ u32 size; // The lower 4 bits are ignored
union {
u32 input_size;
diff --git a/src/core/memory.h b/src/core/memory.h
index 802aa465e..71fb278ad 100644
--- a/src/core/memory.h
+++ b/src/core/memory.h
@@ -55,8 +55,10 @@ enum : PAddr {
/// Main FCRAM
FCRAM_PADDR = 0x20000000,
- FCRAM_SIZE = 0x08000000, ///< FCRAM size (128MB)
+ FCRAM_SIZE = 0x08000000, ///< FCRAM size on the Old 3DS (128MB)
+ FCRAM_N3DS_SIZE = 0x10000000, ///< FCRAM size on the New 3DS (256MB)
FCRAM_PADDR_END = FCRAM_PADDR + FCRAM_SIZE,
+ FCRAM_N3DS_PADDR_END = FCRAM_PADDR + FCRAM_N3DS_SIZE,
};
/// Virtual user-space memory regions
diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt
index 00d7c636a..a14df325a 100644
--- a/src/tests/CMakeLists.txt
+++ b/src/tests/CMakeLists.txt
@@ -1,8 +1,9 @@
set(SRCS
- glad.cpp
- tests.cpp
common/param_package.cpp
core/file_sys/path_parser.cpp
+ core/hle/kernel/hle_ipc.cpp
+ glad.cpp
+ tests.cpp
)
set(HEADERS
diff --git a/src/tests/core/hle/kernel/hle_ipc.cpp b/src/tests/core/hle/kernel/hle_ipc.cpp
new file mode 100644
index 000000000..52336d027
--- /dev/null
+++ b/src/tests/core/hle/kernel/hle_ipc.cpp
@@ -0,0 +1,216 @@
+// Copyright 2017 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <catch.hpp>
+#include "core/hle/ipc.h"
+#include "core/hle/kernel/client_port.h"
+#include "core/hle/kernel/client_session.h"
+#include "core/hle/kernel/event.h"
+#include "core/hle/kernel/handle_table.h"
+#include "core/hle/kernel/hle_ipc.h"
+#include "core/hle/kernel/process.h"
+#include "core/hle/kernel/server_session.h"
+
+namespace Kernel {
+
+static SharedPtr<Object> MakeObject() {
+ return Event::Create(ResetType::OneShot);
+}
+
+TEST_CASE("HLERequestContext::PopulateFromIncomingCommandBuffer", "[core][kernel]") {
+ auto session = std::get<SharedPtr<ServerSession>>(ServerSession::CreateSessionPair());
+ HLERequestContext context(std::move(session));
+
+ auto process = Process::Create(CodeSet::Create("", 0));
+ HandleTable handle_table;
+
+ SECTION("works with empty cmdbuf") {
+ const u32_le input[]{
+ IPC::MakeHeader(0x1234, 0, 0),
+ };
+
+ context.PopulateFromIncomingCommandBuffer(input, *process, handle_table);
+
+ REQUIRE(context.CommandBuffer()[0] == 0x12340000);
+ }
+
+ SECTION("translates regular params") {
+ const u32_le input[]{
+ IPC::MakeHeader(0, 3, 0), 0x12345678, 0x21122112, 0xAABBCCDD,
+ };
+
+ context.PopulateFromIncomingCommandBuffer(input, *process, handle_table);
+
+ auto* output = context.CommandBuffer();
+ REQUIRE(output[1] == 0x12345678);
+ REQUIRE(output[2] == 0x21122112);
+ REQUIRE(output[3] == 0xAABBCCDD);
+ }
+
+ SECTION("translates move handles") {
+ auto a = MakeObject();
+ Handle a_handle = handle_table.Create(a).Unwrap();
+ const u32_le input[]{
+ IPC::MakeHeader(0, 0, 2), IPC::MoveHandleDesc(1), a_handle,
+ };
+
+ context.PopulateFromIncomingCommandBuffer(input, *process, handle_table);
+
+ auto* output = context.CommandBuffer();
+ REQUIRE(context.GetIncomingHandle(output[2]) == a);
+ REQUIRE(handle_table.GetGeneric(a_handle) == nullptr);
+ }
+
+ SECTION("translates copy handles") {
+ auto a = MakeObject();
+ Handle a_handle = handle_table.Create(a).Unwrap();
+ const u32_le input[]{
+ IPC::MakeHeader(0, 0, 2), IPC::CopyHandleDesc(1), a_handle,
+ };
+
+ context.PopulateFromIncomingCommandBuffer(input, *process, handle_table);
+
+ auto* output = context.CommandBuffer();
+ REQUIRE(context.GetIncomingHandle(output[2]) == a);
+ REQUIRE(handle_table.GetGeneric(a_handle) == a);
+ }
+
+ SECTION("translates multi-handle descriptors") {
+ auto a = MakeObject();
+ auto b = MakeObject();
+ auto c = MakeObject();
+ const u32_le input[]{
+ IPC::MakeHeader(0, 0, 5), IPC::MoveHandleDesc(2),
+ handle_table.Create(a).Unwrap(), handle_table.Create(b).Unwrap(),
+ IPC::MoveHandleDesc(1), handle_table.Create(c).Unwrap(),
+ };
+
+ context.PopulateFromIncomingCommandBuffer(input, *process, handle_table);
+
+ auto* output = context.CommandBuffer();
+ REQUIRE(context.GetIncomingHandle(output[2]) == a);
+ REQUIRE(context.GetIncomingHandle(output[3]) == b);
+ REQUIRE(context.GetIncomingHandle(output[5]) == c);
+ }
+
+ SECTION("translates null handles") {
+ const u32_le input[]{
+ IPC::MakeHeader(0, 0, 2), IPC::MoveHandleDesc(1), 0,
+ };
+
+ auto result = context.PopulateFromIncomingCommandBuffer(input, *process, handle_table);
+
+ REQUIRE(result == RESULT_SUCCESS);
+ auto* output = context.CommandBuffer();
+ REQUIRE(context.GetIncomingHandle(output[2]) == nullptr);
+ }
+
+ SECTION("translates CallingPid descriptors") {
+ const u32_le input[]{
+ IPC::MakeHeader(0, 0, 2), IPC::CallingPidDesc(), 0x98989898,
+ };
+
+ context.PopulateFromIncomingCommandBuffer(input, *process, handle_table);
+
+ REQUIRE(context.CommandBuffer()[2] == process->process_id);
+ }
+
+ SECTION("translates mixed params") {
+ auto a = MakeObject();
+ const u32_le input[]{
+ IPC::MakeHeader(0, 2, 4),
+ 0x12345678,
+ 0xABCDEF00,
+ IPC::MoveHandleDesc(1),
+ handle_table.Create(a).Unwrap(),
+ IPC::CallingPidDesc(),
+ 0,
+ };
+
+ context.PopulateFromIncomingCommandBuffer(input, *process, handle_table);
+
+ auto* output = context.CommandBuffer();
+ REQUIRE(output[1] == 0x12345678);
+ REQUIRE(output[2] == 0xABCDEF00);
+ REQUIRE(context.GetIncomingHandle(output[4]) == a);
+ REQUIRE(output[6] == process->process_id);
+ }
+}
+
+TEST_CASE("HLERequestContext::WriteToOutgoingCommandBuffer", "[core][kernel]") {
+ auto session = std::get<SharedPtr<ServerSession>>(ServerSession::CreateSessionPair());
+ HLERequestContext context(std::move(session));
+
+ auto process = Process::Create(CodeSet::Create("", 0));
+ HandleTable handle_table;
+ auto* input = context.CommandBuffer();
+ u32_le output[IPC::COMMAND_BUFFER_LENGTH];
+
+ SECTION("works with empty cmdbuf") {
+ input[0] = IPC::MakeHeader(0x1234, 0, 0);
+
+ context.WriteToOutgoingCommandBuffer(output, *process, handle_table);
+
+ REQUIRE(output[0] == 0x12340000);
+ }
+
+ SECTION("translates regular params") {
+ input[0] = IPC::MakeHeader(0, 3, 0);
+ input[1] = 0x12345678;
+ input[2] = 0x21122112;
+ input[3] = 0xAABBCCDD;
+
+ context.WriteToOutgoingCommandBuffer(output, *process, handle_table);
+
+ REQUIRE(output[1] == 0x12345678);
+ REQUIRE(output[2] == 0x21122112);
+ REQUIRE(output[3] == 0xAABBCCDD);
+ }
+
+ SECTION("translates move/copy handles") {
+ auto a = MakeObject();
+ auto b = MakeObject();
+ input[0] = IPC::MakeHeader(0, 0, 4);
+ input[1] = IPC::MoveHandleDesc(1);
+ input[2] = context.AddOutgoingHandle(a);
+ input[3] = IPC::CopyHandleDesc(1);
+ input[4] = context.AddOutgoingHandle(b);
+
+ context.WriteToOutgoingCommandBuffer(output, *process, handle_table);
+
+ REQUIRE(handle_table.GetGeneric(output[2]) == a);
+ REQUIRE(handle_table.GetGeneric(output[4]) == b);
+ }
+
+ SECTION("translates null handles") {
+ input[0] = IPC::MakeHeader(0, 0, 2);
+ input[1] = IPC::MoveHandleDesc(1);
+ input[2] = context.AddOutgoingHandle(nullptr);
+
+ auto result = context.WriteToOutgoingCommandBuffer(output, *process, handle_table);
+
+ REQUIRE(result == RESULT_SUCCESS);
+ REQUIRE(output[2] == 0);
+ }
+
+ SECTION("translates multi-handle descriptors") {
+ auto a = MakeObject();
+ auto b = MakeObject();
+ auto c = MakeObject();
+ input[0] = IPC::MakeHeader(0, 0, 5);
+ input[1] = IPC::MoveHandleDesc(2);
+ input[2] = context.AddOutgoingHandle(a);
+ input[3] = context.AddOutgoingHandle(b);
+ input[4] = IPC::CopyHandleDesc(1);
+ input[5] = context.AddOutgoingHandle(c);
+
+ context.WriteToOutgoingCommandBuffer(output, *process, handle_table);
+
+ REQUIRE(handle_table.GetGeneric(output[2]) == a);
+ REQUIRE(handle_table.GetGeneric(output[3]) == b);
+ REQUIRE(handle_table.GetGeneric(output[5]) == c);
+ }
+}
+
+} // namespace Kernel
diff --git a/src/video_core/pica_state.h b/src/video_core/pica_state.h
index f46db09fb..2d23d34e6 100644
--- a/src/video_core/pica_state.h
+++ b/src/video_core/pica_state.h
@@ -87,12 +87,18 @@ struct State {
// LUT value, encoded as 12-bit fixed point, with 12 fraction bits
BitField<0, 12, u32> value; // 0.0.12 fixed point
- // Used by HW for efficient interpolation, Citra does not use these
- BitField<12, 12, s32> difference; // 1.0.11 fixed point
+ // Used for efficient interpolation.
+ BitField<12, 11, u32> difference; // 0.0.11 fixed point
+ BitField<23, 1, u32> neg_difference;
- float ToFloat() {
+ float ToFloat() const {
return static_cast<float>(value) / 4095.f;
}
+
+ float DiffToFloat() const {
+ float diff = static_cast<float>(difference) / 2047.f;
+ return neg_difference ? -diff : diff;
+ }
};
std::array<std::array<LutEntry, 256>, 24> luts;
@@ -105,6 +111,14 @@ struct State {
BitField<0, 13, s32> difference; // 1.1.11 fixed point
BitField<13, 11, u32> value; // 0.0.11 fixed point
+
+ float ToFloat() const {
+ return static_cast<float>(value) / 2047.0f;
+ }
+
+ float DiffToFloat() const {
+ return static_cast<float>(difference) / 2047.0f;
+ }
};
std::array<LutEntry, 128> lut;
diff --git a/src/video_core/regs_lighting.h b/src/video_core/regs_lighting.h
index fbfebc0a7..b89709cfe 100644
--- a/src/video_core/regs_lighting.h
+++ b/src/video_core/regs_lighting.h
@@ -26,6 +26,8 @@ struct LightingRegs {
DistanceAttenuation = 16,
};
+ static constexpr unsigned NumLightingSampler = 24;
+
static LightingSampler SpotlightAttenuationSampler(unsigned index) {
return static_cast<LightingSampler>(
static_cast<unsigned>(LightingSampler::SpotlightAttenuation) + index);
@@ -84,7 +86,7 @@ struct LightingRegs {
NV = 2, // Cosine of the angle between the normal and the view vector
LN = 3, // Cosine of the angle between the light and the normal vectors
SP = 4, // Cosine of the angle between the light and the inverse spotlight vectors
- CP = 5, // TODO: document and implement
+ CP = 5, // Cosine of the angle between the tangent and projection of half-angle vectors
};
enum class LightingBumpMode : u32 {
@@ -168,6 +170,8 @@ struct LightingRegs {
union {
BitField<0, 1, u32> directional;
BitField<1, 1, u32> two_sided_diffuse; // When disabled, clamp dot-product to 0
+ BitField<2, 1, u32> geometric_factor_0;
+ BitField<3, 1, u32> geometric_factor_1;
} config;
BitField<0, 20, u32> dist_atten_bias;
diff --git a/src/video_core/regs_texturing.h b/src/video_core/regs_texturing.h
index 3f5355fa9..0b09f2299 100644
--- a/src/video_core/regs_texturing.h
+++ b/src/video_core/regs_texturing.h
@@ -30,10 +30,10 @@ struct TexturingRegs {
Repeat = 2,
MirroredRepeat = 3,
// Mode 4-7 produces some weird result and may be just invalid:
- // 4: Positive coord: clamp to edge; negative coord: repeat
- // 5: Positive coord: clamp to border; negative coord: repeat
- // 6: Repeat
- // 7: Repeat
+ ClampToEdge2 = 4, // Positive coord: clamp to edge; negative coord: repeat
+ ClampToBorder2 = 5, // Positive coord: clamp to border; negative coord: repeat
+ Repeat2 = 6, // Same as Repeat
+ Repeat3 = 7, // Same as Repeat
};
enum TextureFilter : u32 {
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp
index e6cccebf6..ff3f69ba3 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.cpp
+++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp
@@ -49,9 +49,7 @@ RasterizerOpenGL::RasterizerOpenGL() : shader_dirty(true) {
uniform_block_data.dirty = true;
- for (unsigned index = 0; index < lighting_luts.size(); index++) {
- uniform_block_data.lut_dirty[index] = true;
- }
+ uniform_block_data.lut_dirty.fill(true);
uniform_block_data.fog_lut_dirty = true;
@@ -96,36 +94,32 @@ RasterizerOpenGL::RasterizerOpenGL() : shader_dirty(true) {
framebuffer.Create();
// Allocate and bind lighting lut textures
- for (size_t i = 0; i < lighting_luts.size(); ++i) {
- lighting_luts[i].Create();
- state.lighting_luts[i].texture_1d = lighting_luts[i].handle;
- }
+ lighting_lut.Create();
+ state.lighting_lut.texture_buffer = lighting_lut.handle;
state.Apply();
-
- for (size_t i = 0; i < lighting_luts.size(); ++i) {
- glActiveTexture(static_cast<GLenum>(GL_TEXTURE3 + i));
- glTexImage1D(GL_TEXTURE_1D, 0, GL_RGBA32F, 256, 0, GL_RGBA, GL_FLOAT, nullptr);
- glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
- glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
- }
+ lighting_lut_buffer.Create();
+ glBindBuffer(GL_TEXTURE_BUFFER, lighting_lut_buffer.handle);
+ glBufferData(GL_TEXTURE_BUFFER,
+ sizeof(GLfloat) * 2 * 256 * Pica::LightingRegs::NumLightingSampler, nullptr,
+ GL_DYNAMIC_DRAW);
+ glActiveTexture(TextureUnits::LightingLUT.Enum());
+ glTexBuffer(GL_TEXTURE_BUFFER, GL_RG32F, lighting_lut_buffer.handle);
// Setup the LUT for the fog
- {
- fog_lut.Create();
- state.fog_lut.texture_1d = fog_lut.handle;
- }
+ fog_lut.Create();
+ state.fog_lut.texture_buffer = fog_lut.handle;
state.Apply();
-
- glActiveTexture(GL_TEXTURE9);
- glTexImage1D(GL_TEXTURE_1D, 0, GL_R32UI, 128, 0, GL_RED_INTEGER, GL_UNSIGNED_INT, nullptr);
- glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
- glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+ fog_lut_buffer.Create();
+ glBindBuffer(GL_TEXTURE_BUFFER, fog_lut_buffer.handle);
+ glBufferData(GL_TEXTURE_BUFFER, sizeof(GLfloat) * 2 * 128, nullptr, GL_DYNAMIC_DRAW);
+ glActiveTexture(TextureUnits::FogLUT.Enum());
+ glTexBuffer(GL_TEXTURE_BUFFER, GL_RG32F, fog_lut_buffer.handle);
// Setup the noise LUT for proctex
proctex_noise_lut.Create();
state.proctex_noise_lut.texture_1d = proctex_noise_lut.handle;
state.Apply();
- glActiveTexture(GL_TEXTURE10);
+ glActiveTexture(TextureUnits::ProcTexNoiseLUT.Enum());
glTexImage1D(GL_TEXTURE_1D, 0, GL_RG32F, 128, 0, GL_RG, GL_FLOAT, nullptr);
glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
@@ -134,7 +128,7 @@ RasterizerOpenGL::RasterizerOpenGL() : shader_dirty(true) {
proctex_color_map.Create();
state.proctex_color_map.texture_1d = proctex_color_map.handle;
state.Apply();
- glActiveTexture(GL_TEXTURE11);
+ glActiveTexture(TextureUnits::ProcTexColorMap.Enum());
glTexImage1D(GL_TEXTURE_1D, 0, GL_RG32F, 128, 0, GL_RG, GL_FLOAT, nullptr);
glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
@@ -143,7 +137,7 @@ RasterizerOpenGL::RasterizerOpenGL() : shader_dirty(true) {
proctex_alpha_map.Create();
state.proctex_alpha_map.texture_1d = proctex_alpha_map.handle;
state.Apply();
- glActiveTexture(GL_TEXTURE12);
+ glActiveTexture(TextureUnits::ProcTexAlphaMap.Enum());
glTexImage1D(GL_TEXTURE_1D, 0, GL_RG32F, 128, 0, GL_RG, GL_FLOAT, nullptr);
glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
@@ -152,7 +146,7 @@ RasterizerOpenGL::RasterizerOpenGL() : shader_dirty(true) {
proctex_lut.Create();
state.proctex_lut.texture_1d = proctex_lut.handle;
state.Apply();
- glActiveTexture(GL_TEXTURE13);
+ glActiveTexture(TextureUnits::ProcTexLUT.Enum());
glTexImage1D(GL_TEXTURE_1D, 0, GL_RGBA32F, 256, 0, GL_RGBA, GL_FLOAT, nullptr);
glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
@@ -161,7 +155,7 @@ RasterizerOpenGL::RasterizerOpenGL() : shader_dirty(true) {
proctex_diff_lut.Create();
state.proctex_diff_lut.texture_1d = proctex_diff_lut.handle;
state.Apply();
- glActiveTexture(GL_TEXTURE14);
+ glActiveTexture(TextureUnits::ProcTexDiffLUT.Enum());
glTexImage1D(GL_TEXTURE_1D, 0, GL_RGBA32F, 256, 0, GL_RGBA, GL_FLOAT, nullptr);
glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
@@ -313,7 +307,7 @@ void RasterizerOpenGL::DrawTriangles() {
}
// Sync the lighting luts
- for (unsigned index = 0; index < lighting_luts.size(); index++) {
+ for (unsigned index = 0; index < uniform_block_data.lut_dirty.size(); index++) {
if (uniform_block_data.lut_dirty[index]) {
SyncLightingLUT(index);
uniform_block_data.lut_dirty[index] = false;
@@ -851,7 +845,7 @@ void RasterizerOpenGL::NotifyPicaRegisterChanged(u32 id) {
case PICA_REG_INDEX_WORKAROUND(lighting.lut_data[6], 0x1ce):
case PICA_REG_INDEX_WORKAROUND(lighting.lut_data[7], 0x1cf): {
auto& lut_config = regs.lighting.lut_config;
- uniform_block_data.lut_dirty[lut_config.type / 4] = true;
+ uniform_block_data.lut_dirty[lut_config.type] = true;
break;
}
}
@@ -1187,77 +1181,57 @@ void RasterizerOpenGL::SetShader() {
state.Apply();
// Set the texture samplers to correspond to different texture units
- GLuint uniform_tex = glGetUniformLocation(shader->shader.handle, "tex[0]");
+ GLint uniform_tex = glGetUniformLocation(shader->shader.handle, "tex[0]");
if (uniform_tex != -1) {
- glUniform1i(uniform_tex, 0);
+ glUniform1i(uniform_tex, TextureUnits::PicaTexture(0).id);
}
uniform_tex = glGetUniformLocation(shader->shader.handle, "tex[1]");
if (uniform_tex != -1) {
- glUniform1i(uniform_tex, 1);
+ glUniform1i(uniform_tex, TextureUnits::PicaTexture(1).id);
}
uniform_tex = glGetUniformLocation(shader->shader.handle, "tex[2]");
if (uniform_tex != -1) {
- glUniform1i(uniform_tex, 2);
+ glUniform1i(uniform_tex, TextureUnits::PicaTexture(2).id);
}
// Set the texture samplers to correspond to different lookup table texture units
- GLuint uniform_lut = glGetUniformLocation(shader->shader.handle, "lut[0]");
- if (uniform_lut != -1) {
- glUniform1i(uniform_lut, 3);
- }
- uniform_lut = glGetUniformLocation(shader->shader.handle, "lut[1]");
- if (uniform_lut != -1) {
- glUniform1i(uniform_lut, 4);
- }
- uniform_lut = glGetUniformLocation(shader->shader.handle, "lut[2]");
- if (uniform_lut != -1) {
- glUniform1i(uniform_lut, 5);
- }
- uniform_lut = glGetUniformLocation(shader->shader.handle, "lut[3]");
- if (uniform_lut != -1) {
- glUniform1i(uniform_lut, 6);
- }
- uniform_lut = glGetUniformLocation(shader->shader.handle, "lut[4]");
+ GLint uniform_lut = glGetUniformLocation(shader->shader.handle, "lighting_lut");
if (uniform_lut != -1) {
- glUniform1i(uniform_lut, 7);
- }
- uniform_lut = glGetUniformLocation(shader->shader.handle, "lut[5]");
- if (uniform_lut != -1) {
- glUniform1i(uniform_lut, 8);
+ glUniform1i(uniform_lut, TextureUnits::LightingLUT.id);
}
- GLuint uniform_fog_lut = glGetUniformLocation(shader->shader.handle, "fog_lut");
+ GLint uniform_fog_lut = glGetUniformLocation(shader->shader.handle, "fog_lut");
if (uniform_fog_lut != -1) {
- glUniform1i(uniform_fog_lut, 9);
+ glUniform1i(uniform_fog_lut, TextureUnits::FogLUT.id);
}
- GLuint uniform_proctex_noise_lut =
+ GLint uniform_proctex_noise_lut =
glGetUniformLocation(shader->shader.handle, "proctex_noise_lut");
if (uniform_proctex_noise_lut != -1) {
- glUniform1i(uniform_proctex_noise_lut, 10);
+ glUniform1i(uniform_proctex_noise_lut, TextureUnits::ProcTexNoiseLUT.id);
}
- GLuint uniform_proctex_color_map =
+ GLint uniform_proctex_color_map =
glGetUniformLocation(shader->shader.handle, "proctex_color_map");
if (uniform_proctex_color_map != -1) {
- glUniform1i(uniform_proctex_color_map, 11);
+ glUniform1i(uniform_proctex_color_map, TextureUnits::ProcTexColorMap.id);
}
- GLuint uniform_proctex_alpha_map =
+ GLint uniform_proctex_alpha_map =
glGetUniformLocation(shader->shader.handle, "proctex_alpha_map");
if (uniform_proctex_alpha_map != -1) {
- glUniform1i(uniform_proctex_alpha_map, 12);
+ glUniform1i(uniform_proctex_alpha_map, TextureUnits::ProcTexAlphaMap.id);
}
- GLuint uniform_proctex_lut = glGetUniformLocation(shader->shader.handle, "proctex_lut");
+ GLint uniform_proctex_lut = glGetUniformLocation(shader->shader.handle, "proctex_lut");
if (uniform_proctex_lut != -1) {
- glUniform1i(uniform_proctex_lut, 13);
+ glUniform1i(uniform_proctex_lut, TextureUnits::ProcTexLUT.id);
}
- GLuint uniform_proctex_diff_lut =
+ GLint uniform_proctex_diff_lut =
glGetUniformLocation(shader->shader.handle, "proctex_diff_lut");
if (uniform_proctex_diff_lut != -1) {
- glUniform1i(uniform_proctex_diff_lut, 14);
+ glUniform1i(uniform_proctex_diff_lut, TextureUnits::ProcTexDiffLUT.id);
}
current_shader = shader_cache.emplace(config, std::move(shader)).first->second.get();
@@ -1380,16 +1354,17 @@ void RasterizerOpenGL::SyncFogColor() {
}
void RasterizerOpenGL::SyncFogLUT() {
- std::array<GLuint, 128> new_data;
+ std::array<GLvec2, 128> new_data;
std::transform(Pica::g_state.fog.lut.begin(), Pica::g_state.fog.lut.end(), new_data.begin(),
- [](const auto& entry) { return entry.raw; });
+ [](const auto& entry) {
+ return GLvec2{entry.ToFloat(), entry.DiffToFloat()};
+ });
if (new_data != fog_lut_data) {
fog_lut_data = new_data;
- glActiveTexture(GL_TEXTURE9);
- glTexSubImage1D(GL_TEXTURE_1D, 0, 0, 128, GL_RED_INTEGER, GL_UNSIGNED_INT,
- fog_lut_data.data());
+ glBindBuffer(GL_TEXTURE_BUFFER, fog_lut_buffer.handle);
+ glBufferSubData(GL_TEXTURE_BUFFER, 0, new_data.size() * sizeof(GLvec2), new_data.data());
}
}
@@ -1426,17 +1401,18 @@ static void SyncProcTexValueLUT(const std::array<Pica::State::ProcTex::ValueEntr
}
void RasterizerOpenGL::SyncProcTexNoiseLUT() {
- SyncProcTexValueLUT(Pica::g_state.proctex.noise_table, proctex_noise_lut_data, GL_TEXTURE10);
+ SyncProcTexValueLUT(Pica::g_state.proctex.noise_table, proctex_noise_lut_data,
+ TextureUnits::ProcTexNoiseLUT.Enum());
}
void RasterizerOpenGL::SyncProcTexColorMap() {
SyncProcTexValueLUT(Pica::g_state.proctex.color_map_table, proctex_color_map_data,
- GL_TEXTURE11);
+ TextureUnits::ProcTexColorMap.Enum());
}
void RasterizerOpenGL::SyncProcTexAlphaMap() {
SyncProcTexValueLUT(Pica::g_state.proctex.alpha_map_table, proctex_alpha_map_data,
- GL_TEXTURE12);
+ TextureUnits::ProcTexAlphaMap.Enum());
}
void RasterizerOpenGL::SyncProcTexLUT() {
@@ -1451,7 +1427,7 @@ void RasterizerOpenGL::SyncProcTexLUT() {
if (new_data != proctex_lut_data) {
proctex_lut_data = new_data;
- glActiveTexture(GL_TEXTURE13);
+ glActiveTexture(TextureUnits::ProcTexLUT.Enum());
glTexSubImage1D(GL_TEXTURE_1D, 0, 0, 256, GL_RGBA, GL_FLOAT, proctex_lut_data.data());
}
}
@@ -1468,7 +1444,7 @@ void RasterizerOpenGL::SyncProcTexDiffLUT() {
if (new_data != proctex_diff_lut_data) {
proctex_diff_lut_data = new_data;
- glActiveTexture(GL_TEXTURE14);
+ glActiveTexture(TextureUnits::ProcTexDiffLUT.Enum());
glTexSubImage1D(GL_TEXTURE_1D, 0, 0, 256, GL_RGBA, GL_FLOAT, proctex_diff_lut_data.data());
}
}
@@ -1571,20 +1547,17 @@ void RasterizerOpenGL::SyncGlobalAmbient() {
}
void RasterizerOpenGL::SyncLightingLUT(unsigned lut_index) {
- std::array<GLvec4, 256> new_data;
-
- for (unsigned offset = 0; offset < new_data.size(); ++offset) {
- new_data[offset][0] = Pica::g_state.lighting.luts[(lut_index * 4) + 0][offset].ToFloat();
- new_data[offset][1] = Pica::g_state.lighting.luts[(lut_index * 4) + 1][offset].ToFloat();
- new_data[offset][2] = Pica::g_state.lighting.luts[(lut_index * 4) + 2][offset].ToFloat();
- new_data[offset][3] = Pica::g_state.lighting.luts[(lut_index * 4) + 3][offset].ToFloat();
- }
+ std::array<GLvec2, 256> new_data;
+ const auto& source_lut = Pica::g_state.lighting.luts[lut_index];
+ std::transform(source_lut.begin(), source_lut.end(), new_data.begin(), [](const auto& entry) {
+ return GLvec2{entry.ToFloat(), entry.DiffToFloat()};
+ });
if (new_data != lighting_lut_data[lut_index]) {
lighting_lut_data[lut_index] = new_data;
- glActiveTexture(GL_TEXTURE3 + lut_index);
- glTexSubImage1D(GL_TEXTURE_1D, 0, 0, 256, GL_RGBA, GL_FLOAT,
- lighting_lut_data[lut_index].data());
+ glBindBuffer(GL_TEXTURE_BUFFER, lighting_lut_buffer.handle);
+ glBufferSubData(GL_TEXTURE_BUFFER, lut_index * new_data.size() * sizeof(GLvec2),
+ new_data.size() * sizeof(GLvec2), new_data.data());
}
}
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.h b/src/video_core/renderer_opengl/gl_rasterizer.h
index d9a3e9d1c..a433c1d4a 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.h
+++ b/src/video_core/renderer_opengl/gl_rasterizer.h
@@ -263,7 +263,7 @@ private:
struct {
UniformData data;
- bool lut_dirty[6];
+ std::array<bool, Pica::LightingRegs::NumLightingSampler> lut_dirty;
bool fog_lut_dirty;
bool proctex_noise_lut_dirty;
bool proctex_color_map_dirty;
@@ -279,11 +279,13 @@ private:
OGLBuffer uniform_buffer;
OGLFramebuffer framebuffer;
- std::array<OGLTexture, 6> lighting_luts;
- std::array<std::array<GLvec4, 256>, 6> lighting_lut_data{};
+ OGLBuffer lighting_lut_buffer;
+ OGLTexture lighting_lut;
+ std::array<std::array<GLvec2, 256>, Pica::LightingRegs::NumLightingSampler> lighting_lut_data{};
+ OGLBuffer fog_lut_buffer;
OGLTexture fog_lut;
- std::array<GLuint, 128> fog_lut_data{};
+ std::array<GLvec2, 128> fog_lut_data{};
OGLTexture proctex_noise_lut;
std::array<GLvec2, 128> proctex_noise_lut_data{};
diff --git a/src/video_core/renderer_opengl/gl_shader_gen.cpp b/src/video_core/renderer_opengl/gl_shader_gen.cpp
index db53710aa..c93b108fb 100644
--- a/src/video_core/renderer_opengl/gl_shader_gen.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_gen.cpp
@@ -73,6 +73,8 @@ PicaShaderConfig PicaShaderConfig::BuildFromRegs(const Pica::Regs& regs) {
state.lighting.light[light_index].num = num;
state.lighting.light[light_index].directional = light.config.directional != 0;
state.lighting.light[light_index].two_sided_diffuse = light.config.two_sided_diffuse != 0;
+ state.lighting.light[light_index].geometric_factor_0 = light.config.geometric_factor_0 != 0;
+ state.lighting.light[light_index].geometric_factor_1 = light.config.geometric_factor_1 != 0;
state.lighting.light[light_index].dist_atten_enable =
!regs.lighting.IsDistAttenDisabled(num);
state.lighting.light[light_index].spot_atten_enable =
@@ -518,14 +520,16 @@ static void WriteLighting(std::string& out, const PicaShaderConfig& config) {
"vec4 specular_sum = vec4(0.0, 0.0, 0.0, 1.0);\n"
"vec3 light_vector = vec3(0.0);\n"
"vec3 refl_value = vec3(0.0);\n"
- "vec3 spot_dir = vec3(0.0);\n;";
+ "vec3 spot_dir = vec3(0.0);\n"
+ "vec3 half_vector = vec3(0.0);\n"
+ "float geo_factor = 1.0;\n";
- // Compute fragment normals
+ // Compute fragment normals and tangents
+ const std::string pertubation =
+ "2.0 * (" + SampleTexture(config, lighting.bump_selector) + ").rgb - 1.0";
if (lighting.bump_mode == LightingRegs::LightingBumpMode::NormalMap) {
- // Bump mapping is enabled using a normal map, read perturbation vector from the selected
- // texture
- out += "vec3 surface_normal = 2.0 * (" + SampleTexture(config, lighting.bump_selector) +
- ").rgb - 1.0;\n";
+ // Bump mapping is enabled using a normal map
+ out += "vec3 surface_normal = " + pertubation + ";\n";
// Recompute Z-component of perturbation if 'renorm' is enabled, this provides a higher
// precision result
@@ -534,31 +538,41 @@ static void WriteLighting(std::string& out, const PicaShaderConfig& config) {
"(1.0 - (surface_normal.x*surface_normal.x + surface_normal.y*surface_normal.y))";
out += "surface_normal.z = sqrt(max(" + val + ", 0.0));\n";
}
+
+ // The tangent vector is not perturbed by the normal map and is just a unit vector.
+ out += "vec3 surface_tangent = vec3(1.0, 0.0, 0.0);\n";
} else if (lighting.bump_mode == LightingRegs::LightingBumpMode::TangentMap) {
// Bump mapping is enabled using a tangent map
- LOG_CRITICAL(HW_GPU, "unimplemented bump mapping mode (tangent mapping)");
- UNIMPLEMENTED();
+ out += "vec3 surface_tangent = " + pertubation + ";\n";
+ // Mathematically, recomputing Z-component of the tangent vector won't affect the relevant
+ // computation below, which is also confirmed on 3DS. So we don't bother recomputing here
+ // even if 'renorm' is enabled.
+
+ // The normal vector is not perturbed by the tangent map and is just a unit vector.
+ out += "vec3 surface_normal = vec3(0.0, 0.0, 1.0);\n";
} else {
- // No bump mapping - surface local normal is just a unit normal
+ // No bump mapping - surface local normal and tangent are just unit vectors
out += "vec3 surface_normal = vec3(0.0, 0.0, 1.0);\n";
+ out += "vec3 surface_tangent = vec3(1.0, 0.0, 0.0);\n";
}
// Rotate the surface-local normal by the interpolated normal quaternion to convert it to
// eyespace.
- out += "vec3 normal = quaternion_rotate(normalize(normquat), surface_normal);\n";
+ out += "vec4 normalized_normquat = normalize(normquat);\n";
+ out += "vec3 normal = quaternion_rotate(normalized_normquat, surface_normal);\n";
+ out += "vec3 tangent = quaternion_rotate(normalized_normquat, surface_tangent);\n";
- // Gets the index into the specified lookup table for specular lighting
- auto GetLutIndex = [&lighting](unsigned light_num, LightingRegs::LightingLutInput input,
- bool abs) {
- const std::string half_angle = "normalize(normalize(view) + light_vector)";
+ // Samples the specified lookup table for specular lighting
+ auto GetLutValue = [&lighting](LightingRegs::LightingSampler sampler, unsigned light_num,
+ LightingRegs::LightingLutInput input, bool abs) {
std::string index;
switch (input) {
case LightingRegs::LightingLutInput::NH:
- index = "dot(normal, " + half_angle + ")";
+ index = "dot(normal, normalize(half_vector))";
break;
case LightingRegs::LightingLutInput::VH:
- index = std::string("dot(normalize(view), " + half_angle + ")");
+ index = std::string("dot(normalize(view), normalize(half_vector))");
break;
case LightingRegs::LightingLutInput::NV:
@@ -573,6 +587,22 @@ static void WriteLighting(std::string& out, const PicaShaderConfig& config) {
index = std::string("dot(light_vector, spot_dir)");
break;
+ case LightingRegs::LightingLutInput::CP:
+ // CP input is only available with configuration 7
+ if (lighting.config == LightingRegs::LightingConfig::Config7) {
+ // Note: even if the normal vector is modified by normal map, which is not the
+ // normal of the tangent plane anymore, the half angle vector is still projected
+ // using the modified normal vector.
+ std::string half_angle_proj = "normalize(half_vector) - normal / dot(normal, "
+ "normal) * dot(normal, normalize(half_vector))";
+ // Note: the half angle vector projection is confirmed not normalized before the dot
+ // product. The result is in fact not cos(phi) as the name suggested.
+ index = "dot(" + half_angle_proj + ", tangent)";
+ } else {
+ index = "0.0";
+ }
+ break;
+
default:
LOG_CRITICAL(HW_GPU, "Unknown lighting LUT input %d\n", (int)input);
UNIMPLEMENTED();
@@ -580,22 +610,18 @@ static void WriteLighting(std::string& out, const PicaShaderConfig& config) {
break;
}
+ std::string sampler_string = std::to_string(static_cast<unsigned>(sampler));
+
if (abs) {
// LUT index is in the range of (0.0, 1.0)
index = lighting.light[light_num].two_sided_diffuse ? "abs(" + index + ")"
: "max(" + index + ", 0.0)";
+ return "LookupLightingLUTUnsigned(" + sampler_string + ", " + index + ")";
} else {
// LUT index is in the range of (-1.0, 1.0)
- index = "((" + index + " < 0) ? " + index + " + 2.0 : " + index + ") / 2.0";
+ return "LookupLightingLUTSigned(" + sampler_string + ", " + index + ")";
}
- return "(OFFSET_256 + SCALE_256 * clamp(" + index + ", 0.0, 1.0))";
- };
-
- // Gets the lighting lookup table value given the specified sampler and index
- auto GetLutValue = [](LightingRegs::LightingSampler sampler, std::string lut_index) {
- return std::string("texture(lut[" + std::to_string((unsigned)sampler / 4) + "], " +
- lut_index + ")[" + std::to_string((unsigned)sampler & 3) + "]");
};
// Write the code to emulate each enabled light
@@ -610,6 +636,7 @@ static void WriteLighting(std::string& out, const PicaShaderConfig& config) {
out += "light_vector = normalize(" + light_src + ".position + view);\n";
out += "spot_dir = " + light_src + ".spot_direction;\n";
+ out += "half_vector = normalize(view) + light_vector;\n";
// Compute dot product of light_vector and normal, adjust if lighting is one-sided or
// two-sided
@@ -622,48 +649,57 @@ static void WriteLighting(std::string& out, const PicaShaderConfig& config) {
if (light_config.spot_atten_enable &&
LightingRegs::IsLightingSamplerSupported(
lighting.config, LightingRegs::LightingSampler::SpotlightAttenuation)) {
- std::string index =
- GetLutIndex(light_config.num, lighting.lut_sp.type, lighting.lut_sp.abs_input);
- auto sampler = LightingRegs::SpotlightAttenuationSampler(light_config.num);
- spot_atten = "(" + std::to_string(lighting.lut_sp.scale) + " * " +
- GetLutValue(sampler, index) + ")";
+ std::string value =
+ GetLutValue(LightingRegs::SpotlightAttenuationSampler(light_config.num),
+ light_config.num, lighting.lut_sp.type, lighting.lut_sp.abs_input);
+ spot_atten = "(" + std::to_string(lighting.lut_sp.scale) + " * " + value + ")";
}
// If enabled, compute distance attenuation value
std::string dist_atten = "1.0";
if (light_config.dist_atten_enable) {
- std::string index = "(" + light_src + ".dist_atten_scale * length(-view - " +
- light_src + ".position) + " + light_src + ".dist_atten_bias)";
- index = "(OFFSET_256 + SCALE_256 * clamp(" + index + ", 0.0, 1.0))";
+ std::string index = "clamp(" + light_src + ".dist_atten_scale * length(-view - " +
+ light_src + ".position) + " + light_src +
+ ".dist_atten_bias, 0.0, 1.0)";
auto sampler = LightingRegs::DistanceAttenuationSampler(light_config.num);
- dist_atten = GetLutValue(sampler, index);
+ dist_atten = "LookupLightingLUTUnsigned(" +
+ std::to_string(static_cast<unsigned>(sampler)) + "," + index + ")";
}
// If enabled, clamp specular component if lighting result is negative
std::string clamp_highlights =
lighting.clamp_highlights ? "(dot(light_vector, normal) <= 0.0 ? 0.0 : 1.0)" : "1.0";
+ if (light_config.geometric_factor_0 || light_config.geometric_factor_1) {
+ out += "geo_factor = dot(half_vector, half_vector);\n"
+ "geo_factor = geo_factor == 0.0 ? 0.0 : min(" +
+ dot_product + " / geo_factor, 1.0);\n";
+ }
+
// Specular 0 component
std::string d0_lut_value = "1.0";
if (lighting.lut_d0.enable &&
LightingRegs::IsLightingSamplerSupported(
lighting.config, LightingRegs::LightingSampler::Distribution0)) {
// Lookup specular "distribution 0" LUT value
- std::string index =
- GetLutIndex(light_config.num, lighting.lut_d0.type, lighting.lut_d0.abs_input);
- d0_lut_value = "(" + std::to_string(lighting.lut_d0.scale) + " * " +
- GetLutValue(LightingRegs::LightingSampler::Distribution0, index) + ")";
+ std::string value =
+ GetLutValue(LightingRegs::LightingSampler::Distribution0, light_config.num,
+ lighting.lut_d0.type, lighting.lut_d0.abs_input);
+ d0_lut_value = "(" + std::to_string(lighting.lut_d0.scale) + " * " + value + ")";
}
std::string specular_0 = "(" + d0_lut_value + " * " + light_src + ".specular_0)";
+ if (light_config.geometric_factor_0) {
+ specular_0 = "(" + specular_0 + " * geo_factor)";
+ }
// If enabled, lookup ReflectRed value, otherwise, 1.0 is used
if (lighting.lut_rr.enable &&
LightingRegs::IsLightingSamplerSupported(lighting.config,
LightingRegs::LightingSampler::ReflectRed)) {
- std::string index =
- GetLutIndex(light_config.num, lighting.lut_rr.type, lighting.lut_rr.abs_input);
- std::string value = "(" + std::to_string(lighting.lut_rr.scale) + " * " +
- GetLutValue(LightingRegs::LightingSampler::ReflectRed, index) + ")";
+ std::string value =
+ GetLutValue(LightingRegs::LightingSampler::ReflectRed, light_config.num,
+ lighting.lut_rr.type, lighting.lut_rr.abs_input);
+ value = "(" + std::to_string(lighting.lut_rr.scale) + " * " + value + ")";
out += "refl_value.r = " + value + ";\n";
} else {
out += "refl_value.r = 1.0;\n";
@@ -673,11 +709,10 @@ static void WriteLighting(std::string& out, const PicaShaderConfig& config) {
if (lighting.lut_rg.enable &&
LightingRegs::IsLightingSamplerSupported(lighting.config,
LightingRegs::LightingSampler::ReflectGreen)) {
- std::string index =
- GetLutIndex(light_config.num, lighting.lut_rg.type, lighting.lut_rg.abs_input);
- std::string value = "(" + std::to_string(lighting.lut_rg.scale) + " * " +
- GetLutValue(LightingRegs::LightingSampler::ReflectGreen, index) +
- ")";
+ std::string value =
+ GetLutValue(LightingRegs::LightingSampler::ReflectGreen, light_config.num,
+ lighting.lut_rg.type, lighting.lut_rg.abs_input);
+ value = "(" + std::to_string(lighting.lut_rg.scale) + " * " + value + ")";
out += "refl_value.g = " + value + ";\n";
} else {
out += "refl_value.g = refl_value.r;\n";
@@ -687,11 +722,10 @@ static void WriteLighting(std::string& out, const PicaShaderConfig& config) {
if (lighting.lut_rb.enable &&
LightingRegs::IsLightingSamplerSupported(lighting.config,
LightingRegs::LightingSampler::ReflectBlue)) {
- std::string index =
- GetLutIndex(light_config.num, lighting.lut_rb.type, lighting.lut_rb.abs_input);
- std::string value = "(" + std::to_string(lighting.lut_rb.scale) + " * " +
- GetLutValue(LightingRegs::LightingSampler::ReflectBlue, index) +
- ")";
+ std::string value =
+ GetLutValue(LightingRegs::LightingSampler::ReflectBlue, light_config.num,
+ lighting.lut_rb.type, lighting.lut_rb.abs_input);
+ value = "(" + std::to_string(lighting.lut_rb.scale) + " * " + value + ")";
out += "refl_value.b = " + value + ";\n";
} else {
out += "refl_value.b = refl_value.r;\n";
@@ -703,23 +737,26 @@ static void WriteLighting(std::string& out, const PicaShaderConfig& config) {
LightingRegs::IsLightingSamplerSupported(
lighting.config, LightingRegs::LightingSampler::Distribution1)) {
// Lookup specular "distribution 1" LUT value
- std::string index =
- GetLutIndex(light_config.num, lighting.lut_d1.type, lighting.lut_d1.abs_input);
- d1_lut_value = "(" + std::to_string(lighting.lut_d1.scale) + " * " +
- GetLutValue(LightingRegs::LightingSampler::Distribution1, index) + ")";
+ std::string value =
+ GetLutValue(LightingRegs::LightingSampler::Distribution1, light_config.num,
+ lighting.lut_d1.type, lighting.lut_d1.abs_input);
+ d1_lut_value = "(" + std::to_string(lighting.lut_d1.scale) + " * " + value + ")";
}
std::string specular_1 =
"(" + d1_lut_value + " * refl_value * " + light_src + ".specular_1)";
+ if (light_config.geometric_factor_1) {
+ specular_1 = "(" + specular_1 + " * geo_factor)";
+ }
// Fresnel
if (lighting.lut_fr.enable &&
LightingRegs::IsLightingSamplerSupported(lighting.config,
LightingRegs::LightingSampler::Fresnel)) {
// Lookup fresnel LUT value
- std::string index =
- GetLutIndex(light_config.num, lighting.lut_fr.type, lighting.lut_fr.abs_input);
- std::string value = "(" + std::to_string(lighting.lut_fr.scale) + " * " +
- GetLutValue(LightingRegs::LightingSampler::Fresnel, index) + ")";
+ std::string value =
+ GetLutValue(LightingRegs::LightingSampler::Fresnel, light_config.num,
+ lighting.lut_fr.type, lighting.lut_fr.abs_input);
+ value = "(" + std::to_string(lighting.lut_fr.scale) + " * " + value + ")";
// Enabled for difffuse lighting alpha component
if (lighting.fresnel_selector == LightingRegs::LightingFresnelSelector::PrimaryAlpha ||
@@ -973,10 +1010,6 @@ std::string GenerateFragmentShader(const PicaShaderConfig& config) {
#define NUM_TEV_STAGES 6
#define NUM_LIGHTS 8
-// Texture coordinate offsets and scales
-#define OFFSET_256 (0.5 / 256.0)
-#define SCALE_256 (255.0 / 256.0)
-
in vec4 primary_color;
in vec2 texcoord[3];
in float texcoord0_w;
@@ -1018,8 +1051,8 @@ layout (std140) uniform shader_data {
};
uniform sampler2D tex[3];
-uniform sampler1D lut[6];
-uniform usampler1D fog_lut;
+uniform samplerBuffer lighting_lut;
+uniform samplerBuffer fog_lut;
uniform sampler1D proctex_noise_lut;
uniform sampler1D proctex_color_map;
uniform sampler1D proctex_alpha_map;
@@ -1031,6 +1064,24 @@ vec3 quaternion_rotate(vec4 q, vec3 v) {
return v + 2.0 * cross(q.xyz, cross(q.xyz, v) + q.w * v);
}
+float LookupLightingLUT(int lut_index, int index, float delta) {
+ vec2 entry = texelFetch(lighting_lut, lut_index * 256 + index).rg;
+ return entry.r + entry.g * delta;
+}
+
+float LookupLightingLUTUnsigned(int lut_index, float pos) {
+ int index = clamp(int(pos * 256.0), 0, 255);
+ float delta = pos * 256.0 - index;
+ return LookupLightingLUT(lut_index, index, delta);
+}
+
+float LookupLightingLUTSigned(int lut_index, float pos) {
+ int index = clamp(int(pos * 128.0), -128, 127);
+ float delta = pos * 128.0 - index;
+ if (index < 0) index += 256;
+ return LookupLightingLUT(lut_index, index, delta);
+}
+
)";
if (config.state.proctex.enable)
@@ -1094,12 +1145,8 @@ vec4 secondary_fragment_color = vec4(0.0);
// Generate clamped fog factor from LUT for given fog index
out += "float fog_i = clamp(floor(fog_index), 0.0, 127.0);\n";
out += "float fog_f = fog_index - fog_i;\n";
- out += "uint fog_lut_entry = texelFetch(fog_lut, int(fog_i), 0).r;\n";
- out += "float fog_lut_entry_difference = float(int((fog_lut_entry & 0x1FFFU) << 19U) >> "
- "19);\n"; // Extract signed difference
- out += "float fog_lut_entry_value = float((fog_lut_entry >> 13U) & 0x7FFU);\n";
- out += "float fog_factor = (fog_lut_entry_value + fog_lut_entry_difference * fog_f) / "
- "2047.0;\n";
+ out += "vec2 fog_lut_entry = texelFetch(fog_lut, int(fog_i)).rg;\n";
+ out += "float fog_factor = fog_lut_entry.r + fog_lut_entry.g * fog_f;\n";
out += "fog_factor = clamp(fog_factor, 0.0, 1.0);\n";
// Blend the fog
diff --git a/src/video_core/renderer_opengl/gl_shader_gen.h b/src/video_core/renderer_opengl/gl_shader_gen.h
index 9c90eadf9..2302ae453 100644
--- a/src/video_core/renderer_opengl/gl_shader_gen.h
+++ b/src/video_core/renderer_opengl/gl_shader_gen.h
@@ -94,6 +94,8 @@ union PicaShaderConfig {
bool two_sided_diffuse;
bool dist_atten_enable;
bool spot_atten_enable;
+ bool geometric_factor_0;
+ bool geometric_factor_1;
} light[8];
bool enable;
diff --git a/src/video_core/renderer_opengl/gl_state.cpp b/src/video_core/renderer_opengl/gl_state.cpp
index bf837a7fb..eface2dea 100644
--- a/src/video_core/renderer_opengl/gl_state.cpp
+++ b/src/video_core/renderer_opengl/gl_state.cpp
@@ -52,11 +52,9 @@ OpenGLState::OpenGLState() {
texture_unit.sampler = 0;
}
- for (auto& lut : lighting_luts) {
- lut.texture_1d = 0;
- }
+ lighting_lut.texture_buffer = 0;
- fog_lut.texture_1d = 0;
+ fog_lut.texture_buffer = 0;
proctex_lut.texture_1d = 0;
proctex_diff_lut.texture_1d = 0;
@@ -185,7 +183,7 @@ void OpenGLState::Apply() const {
// Textures
for (unsigned i = 0; i < ARRAY_SIZE(texture_units); ++i) {
if (texture_units[i].texture_2d != cur_state.texture_units[i].texture_2d) {
- glActiveTexture(GL_TEXTURE0 + i);
+ glActiveTexture(TextureUnits::PicaTexture(i).Enum());
glBindTexture(GL_TEXTURE_2D, texture_units[i].texture_2d);
}
if (texture_units[i].sampler != cur_state.texture_units[i].sampler) {
@@ -194,46 +192,44 @@ void OpenGLState::Apply() const {
}
// Lighting LUTs
- for (unsigned i = 0; i < ARRAY_SIZE(lighting_luts); ++i) {
- if (lighting_luts[i].texture_1d != cur_state.lighting_luts[i].texture_1d) {
- glActiveTexture(GL_TEXTURE3 + i);
- glBindTexture(GL_TEXTURE_1D, lighting_luts[i].texture_1d);
- }
+ if (lighting_lut.texture_buffer != cur_state.lighting_lut.texture_buffer) {
+ glActiveTexture(TextureUnits::LightingLUT.Enum());
+ glBindTexture(GL_TEXTURE_BUFFER, cur_state.lighting_lut.texture_buffer);
}
// Fog LUT
- if (fog_lut.texture_1d != cur_state.fog_lut.texture_1d) {
- glActiveTexture(GL_TEXTURE9);
- glBindTexture(GL_TEXTURE_1D, fog_lut.texture_1d);
+ if (fog_lut.texture_buffer != cur_state.fog_lut.texture_buffer) {
+ glActiveTexture(TextureUnits::FogLUT.Enum());
+ glBindTexture(GL_TEXTURE_BUFFER, fog_lut.texture_buffer);
}
// ProcTex Noise LUT
if (proctex_noise_lut.texture_1d != cur_state.proctex_noise_lut.texture_1d) {
- glActiveTexture(GL_TEXTURE10);
+ glActiveTexture(TextureUnits::ProcTexNoiseLUT.Enum());
glBindTexture(GL_TEXTURE_1D, proctex_noise_lut.texture_1d);
}
// ProcTex Color Map
if (proctex_color_map.texture_1d != cur_state.proctex_color_map.texture_1d) {
- glActiveTexture(GL_TEXTURE11);
+ glActiveTexture(TextureUnits::ProcTexColorMap.Enum());
glBindTexture(GL_TEXTURE_1D, proctex_color_map.texture_1d);
}
// ProcTex Alpha Map
if (proctex_alpha_map.texture_1d != cur_state.proctex_alpha_map.texture_1d) {
- glActiveTexture(GL_TEXTURE12);
+ glActiveTexture(TextureUnits::ProcTexAlphaMap.Enum());
glBindTexture(GL_TEXTURE_1D, proctex_alpha_map.texture_1d);
}
// ProcTex LUT
if (proctex_lut.texture_1d != cur_state.proctex_lut.texture_1d) {
- glActiveTexture(GL_TEXTURE13);
+ glActiveTexture(TextureUnits::ProcTexLUT.Enum());
glBindTexture(GL_TEXTURE_1D, proctex_lut.texture_1d);
}
// ProcTex Diff LUT
if (proctex_diff_lut.texture_1d != cur_state.proctex_diff_lut.texture_1d) {
- glActiveTexture(GL_TEXTURE14);
+ glActiveTexture(TextureUnits::ProcTexDiffLUT.Enum());
glBindTexture(GL_TEXTURE_1D, proctex_diff_lut.texture_1d);
}
@@ -274,6 +270,20 @@ void OpenGLState::ResetTexture(GLuint handle) {
unit.texture_2d = 0;
}
}
+ if (cur_state.lighting_lut.texture_buffer == handle)
+ cur_state.lighting_lut.texture_buffer = 0;
+ if (cur_state.fog_lut.texture_buffer == handle)
+ cur_state.fog_lut.texture_buffer = 0;
+ if (cur_state.proctex_noise_lut.texture_1d == handle)
+ cur_state.proctex_noise_lut.texture_1d = 0;
+ if (cur_state.proctex_color_map.texture_1d == handle)
+ cur_state.proctex_color_map.texture_1d = 0;
+ if (cur_state.proctex_alpha_map.texture_1d == handle)
+ cur_state.proctex_alpha_map.texture_1d = 0;
+ if (cur_state.proctex_lut.texture_1d == handle)
+ cur_state.proctex_lut.texture_1d = 0;
+ if (cur_state.proctex_diff_lut.texture_1d == handle)
+ cur_state.proctex_diff_lut.texture_1d = 0;
}
void OpenGLState::ResetSampler(GLuint handle) {
diff --git a/src/video_core/renderer_opengl/gl_state.h b/src/video_core/renderer_opengl/gl_state.h
index 7dcc03bd5..1efcf0811 100644
--- a/src/video_core/renderer_opengl/gl_state.h
+++ b/src/video_core/renderer_opengl/gl_state.h
@@ -6,6 +6,29 @@
#include <glad/glad.h>
+namespace TextureUnits {
+
+struct TextureUnit {
+ GLint id;
+ constexpr GLenum Enum() const {
+ return static_cast<GLenum>(GL_TEXTURE0 + id);
+ }
+};
+
+constexpr TextureUnit PicaTexture(int unit) {
+ return TextureUnit{unit};
+}
+
+constexpr TextureUnit LightingLUT{3};
+constexpr TextureUnit FogLUT{4};
+constexpr TextureUnit ProcTexNoiseLUT{5};
+constexpr TextureUnit ProcTexColorMap{6};
+constexpr TextureUnit ProcTexAlphaMap{7};
+constexpr TextureUnit ProcTexLUT{8};
+constexpr TextureUnit ProcTexDiffLUT{9};
+
+} // namespace TextureUnits
+
class OpenGLState {
public:
struct {
@@ -64,11 +87,11 @@ public:
} texture_units[3];
struct {
- GLuint texture_1d; // GL_TEXTURE_BINDING_1D
- } lighting_luts[6];
+ GLuint texture_buffer; // GL_TEXTURE_BINDING_BUFFER
+ } lighting_lut;
struct {
- GLuint texture_1d; // GL_TEXTURE_BINDING_1D
+ GLuint texture_buffer; // GL_TEXTURE_BINDING_BUFFER
} fog_lut;
struct {
diff --git a/src/video_core/renderer_opengl/pica_to_gl.h b/src/video_core/renderer_opengl/pica_to_gl.h
index 93d7b0b71..70298e211 100644
--- a/src/video_core/renderer_opengl/pica_to_gl.h
+++ b/src/video_core/renderer_opengl/pica_to_gl.h
@@ -55,6 +55,12 @@ inline GLenum WrapMode(Pica::TexturingRegs::TextureConfig::WrapMode mode) {
GL_CLAMP_TO_BORDER, // WrapMode::ClampToBorder
GL_REPEAT, // WrapMode::Repeat
GL_MIRRORED_REPEAT, // WrapMode::MirroredRepeat
+ // TODO(wwylele): ClampToEdge2 and ClampToBorder2 are not properly implemented here. See the
+ // comments in enum WrapMode.
+ GL_CLAMP_TO_EDGE, // WrapMode::ClampToEdge2
+ GL_CLAMP_TO_BORDER, // WrapMode::ClampToBorder2
+ GL_REPEAT, // WrapMode::Repeat2
+ GL_REPEAT, // WrapMode::Repeat3
};
// Range check table for input
@@ -65,6 +71,13 @@ inline GLenum WrapMode(Pica::TexturingRegs::TextureConfig::WrapMode mode) {
return GL_CLAMP_TO_EDGE;
}
+ if (static_cast<u32>(mode) > 3) {
+ // It is still unclear whether mode 4-7 are valid, so log it if a game uses them.
+ // TODO(wwylele): telemetry should be added here so we can collect more info about which
+ // game uses this.
+ LOG_WARNING(Render_OpenGL, "Using texture wrap mode %u", static_cast<u32>(mode));
+ }
+
GLenum gl_mode = wrap_mode_table[mode];
// Check for dummy values indicating an unknown mode
diff --git a/src/video_core/shader/shader_jit_x64_compiler.cpp b/src/video_core/shader/shader_jit_x64_compiler.cpp
index 5d9b6448c..42a57aab1 100644
--- a/src/video_core/shader/shader_jit_x64_compiler.cpp
+++ b/src/video_core/shader/shader_jit_x64_compiler.cpp
@@ -321,27 +321,27 @@ void JitShader::Compile_EvaluateCondition(Instruction instr) {
case Instruction::FlowControlType::Or:
mov(eax, COND0);
mov(ebx, COND1);
- xor(eax, (instr.flow_control.refx.Value() ^ 1));
- xor(ebx, (instr.flow_control.refy.Value() ^ 1));
- or (eax, ebx);
+ xor_(eax, (instr.flow_control.refx.Value() ^ 1));
+ xor_(ebx, (instr.flow_control.refy.Value() ^ 1));
+ or_(eax, ebx);
break;
case Instruction::FlowControlType::And:
mov(eax, COND0);
mov(ebx, COND1);
- xor(eax, (instr.flow_control.refx.Value() ^ 1));
- xor(ebx, (instr.flow_control.refy.Value() ^ 1));
- and(eax, ebx);
+ xor_(eax, (instr.flow_control.refx.Value() ^ 1));
+ xor_(ebx, (instr.flow_control.refy.Value() ^ 1));
+ and_(eax, ebx);
break;
case Instruction::FlowControlType::JustX:
mov(eax, COND0);
- xor(eax, (instr.flow_control.refx.Value() ^ 1));
+ xor_(eax, (instr.flow_control.refx.Value() ^ 1));
break;
case Instruction::FlowControlType::JustY:
mov(eax, COND1);
- xor(eax, (instr.flow_control.refy.Value() ^ 1));
+ xor_(eax, (instr.flow_control.refy.Value() ^ 1));
break;
}
}
@@ -734,10 +734,10 @@ void JitShader::Compile_LOOP(Instruction instr) {
mov(LOOPCOUNT, dword[SETUP + offset]);
mov(LOOPCOUNT_REG, LOOPCOUNT);
shr(LOOPCOUNT_REG, 4);
- and(LOOPCOUNT_REG, 0xFF0); // Y-component is the start
+ and_(LOOPCOUNT_REG, 0xFF0); // Y-component is the start
mov(LOOPINC, LOOPCOUNT);
shr(LOOPINC, 12);
- and(LOOPINC, 0xFF0); // Z-component is the incrementer
+ and_(LOOPINC, 0xFF0); // Z-component is the incrementer
movzx(LOOPCOUNT, LOOPCOUNT.cvt8()); // X-component is iteration count
add(LOOPCOUNT, 1); // Iteration count is X-component + 1
@@ -858,9 +858,9 @@ void JitShader::Compile(const std::array<u32, MAX_PROGRAM_CODE_LENGTH>* program_
mov(STATE, ABI_PARAM2);
// Zero address/loop registers
- xor(ADDROFFS_REG_0.cvt32(), ADDROFFS_REG_0.cvt32());
- xor(ADDROFFS_REG_1.cvt32(), ADDROFFS_REG_1.cvt32());
- xor(LOOPCOUNT_REG, LOOPCOUNT_REG);
+ xor_(ADDROFFS_REG_0.cvt32(), ADDROFFS_REG_0.cvt32());
+ xor_(ADDROFFS_REG_1.cvt32(), ADDROFFS_REG_1.cvt32());
+ xor_(LOOPCOUNT_REG, LOOPCOUNT_REG);
// Used to set a register to one
static const __m128 one = {1.f, 1.f, 1.f, 1.f};
diff --git a/src/video_core/swrasterizer/rasterizer.cpp b/src/video_core/swrasterizer/rasterizer.cpp
index 8b7b1defb..512e81c08 100644
--- a/src/video_core/swrasterizer/rasterizer.cpp
+++ b/src/video_core/swrasterizer/rasterizer.cpp
@@ -357,10 +357,22 @@ static void ProcessTriangleInternal(const Vertex& v0, const Vertex& v1, const Ve
int t = (int)(v * float24::FromFloat32(static_cast<float>(texture.config.height)))
.ToFloat32();
- if ((texture.config.wrap_s == TexturingRegs::TextureConfig::ClampToBorder &&
- (s < 0 || static_cast<u32>(s) >= texture.config.width)) ||
- (texture.config.wrap_t == TexturingRegs::TextureConfig::ClampToBorder &&
- (t < 0 || static_cast<u32>(t) >= texture.config.height))) {
+ bool use_border_s = false;
+ bool use_border_t = false;
+
+ if (texture.config.wrap_s == TexturingRegs::TextureConfig::ClampToBorder) {
+ use_border_s = s < 0 || s >= static_cast<int>(texture.config.width);
+ } else if (texture.config.wrap_s == TexturingRegs::TextureConfig::ClampToBorder2) {
+ use_border_s = s >= static_cast<int>(texture.config.width);
+ }
+
+ if (texture.config.wrap_t == TexturingRegs::TextureConfig::ClampToBorder) {
+ use_border_t = t < 0 || t >= static_cast<int>(texture.config.height);
+ } else if (texture.config.wrap_t == TexturingRegs::TextureConfig::ClampToBorder2) {
+ use_border_t = t >= static_cast<int>(texture.config.height);
+ }
+
+ if (use_border_s || use_border_t) {
auto border_color = texture.config.border_color;
texture_color[i] = {border_color.r, border_color.g, border_color.b,
border_color.a};
@@ -572,8 +584,7 @@ static void ProcessTriangleInternal(const Vertex& v0, const Vertex& v1, const Ve
float fog_i = MathUtil::Clamp(floorf(fog_index), 0.0f, 127.0f);
float fog_f = fog_index - fog_i;
const auto& fog_lut_entry = g_state.fog.lut[static_cast<unsigned int>(fog_i)];
- float fog_factor = (fog_lut_entry.value + fog_lut_entry.difference * fog_f) /
- 2047.0f; // This is signed fixed point 1.11
+ float fog_factor = fog_lut_entry.ToFloat() + fog_lut_entry.DiffToFloat() * fog_f;
fog_factor = MathUtil::Clamp(fog_factor, 0.0f, 1.0f);
// Blend the fog
diff --git a/src/video_core/swrasterizer/texturing.cpp b/src/video_core/swrasterizer/texturing.cpp
index aeb6aeb8c..4f02b93f2 100644
--- a/src/video_core/swrasterizer/texturing.cpp
+++ b/src/video_core/swrasterizer/texturing.cpp
@@ -18,22 +18,33 @@ using TevStageConfig = TexturingRegs::TevStageConfig;
int GetWrappedTexCoord(TexturingRegs::TextureConfig::WrapMode mode, int val, unsigned size) {
switch (mode) {
+ case TexturingRegs::TextureConfig::ClampToEdge2:
+ // For negative coordinate, ClampToEdge2 behaves the same as Repeat
+ if (val < 0) {
+ return static_cast<int>(static_cast<unsigned>(val) % size);
+ }
+ // [[fallthrough]]
case TexturingRegs::TextureConfig::ClampToEdge:
val = std::max(val, 0);
- val = std::min(val, (int)size - 1);
+ val = std::min(val, static_cast<int>(size) - 1);
return val;
case TexturingRegs::TextureConfig::ClampToBorder:
return val;
+ case TexturingRegs::TextureConfig::ClampToBorder2:
+ // For ClampToBorder2, the case of positive coordinate beyond the texture size is already
+ // handled outside. Here we only handle the negative coordinate in the same way as Repeat.
+ case TexturingRegs::TextureConfig::Repeat2:
+ case TexturingRegs::TextureConfig::Repeat3:
case TexturingRegs::TextureConfig::Repeat:
- return (int)((unsigned)val % size);
+ return static_cast<int>(static_cast<unsigned>(val) % size);
case TexturingRegs::TextureConfig::MirroredRepeat: {
- unsigned int coord = ((unsigned)val % (2 * size));
+ unsigned int coord = (static_cast<unsigned>(val) % (2 * size));
if (coord >= size)
coord = 2 * size - 1 - coord;
- return (int)coord;
+ return static_cast<int>(coord);
}
default: