diff options
m--------- | externals/boost | 0 | ||||
-rw-r--r-- | src/core/hle/ipc.h | 3 | ||||
-rw-r--r-- | src/core/hle/ipc_helpers.h | 149 | ||||
-rw-r--r-- | src/core/hle/kernel/hle_ipc.cpp | 99 | ||||
-rw-r--r-- | src/core/hle/kernel/hle_ipc.h | 54 | ||||
-rw-r--r-- | src/core/hle/service/nwm/nwm_uds.cpp | 10 | ||||
-rw-r--r-- | src/core/hle/service/nwm/nwm_uds.h | 7 | ||||
-rw-r--r-- | src/core/hle/service/nwm/uds_beacon.h | 3 | ||||
-rw-r--r-- | src/core/hle/service/service.cpp | 19 | ||||
-rw-r--r-- | src/core/hle/service/sm/srv.cpp | 105 | ||||
-rw-r--r-- | src/video_core/regs_texturing.h | 8 | ||||
-rw-r--r-- | src/video_core/renderer_opengl/gl_rasterizer.cpp | 19 | ||||
-rw-r--r-- | src/video_core/renderer_opengl/pica_to_gl.h | 13 | ||||
-rw-r--r-- | src/video_core/swrasterizer/rasterizer.cpp | 20 | ||||
-rw-r--r-- | src/video_core/swrasterizer/texturing.cpp | 19 |
15 files changed, 434 insertions, 94 deletions
diff --git a/externals/boost b/externals/boost -Subproject 351972396392c97a659b9a02f34ce9269293d21 +Subproject 3abc84abaf63a068cb59a9f9b5675c1947bc6fd diff --git a/src/core/hle/ipc.h b/src/core/hle/ipc.h index 303ca090d..f7f96125a 100644 --- a/src/core/hle/ipc.h +++ b/src/core/hle/ipc.h @@ -44,6 +44,9 @@ inline u32* GetStaticBuffers(const int offset = 0) { namespace IPC { +/// Size of the command buffer area, in 32-bit words. +constexpr size_t COMMAND_BUFFER_LENGTH = 0x100 / sizeof(u32); + // These errors are commonly returned by invalid IPC translations, so alias them here for // convenience. // TODO(yuriks): These will probably go away once translation is implemented inside the kernel. diff --git a/src/core/hle/ipc_helpers.h b/src/core/hle/ipc_helpers.h index d7348c09d..f0d89cffe 100644 --- a/src/core/hle/ipc_helpers.h +++ b/src/core/hle/ipc_helpers.h @@ -4,19 +4,28 @@ #pragma once +#include <array> +#include <tuple> +#include <type_traits> +#include <utility> #include "core/hle/ipc.h" #include "core/hle/kernel/handle_table.h" +#include "core/hle/kernel/hle_ipc.h" #include "core/hle/kernel/kernel.h" namespace IPC { class RequestHelperBase { protected: + Kernel::HLERequestContext* context = nullptr; u32* cmdbuf; ptrdiff_t index = 1; Header header; public: + RequestHelperBase(Kernel::HLERequestContext& context, Header desired_header) + : context(&context), cmdbuf(context.CommandBuffer()), header(desired_header) {} + RequestHelperBase(u32* command_buffer, Header command_header) : cmdbuf(command_buffer), header(command_header) {} @@ -51,12 +60,27 @@ public: class RequestBuilder : public RequestHelperBase { public: + RequestBuilder(Kernel::HLERequestContext& context, Header command_header) + : RequestHelperBase(context, command_header) { + // From this point we will start overwriting the existing command buffer, so it's safe to + // release all previous incoming Object pointers since they won't be usable anymore. + context.ClearIncomingObjects(); + cmdbuf[0] = header.raw; + } + + RequestBuilder(Kernel::HLERequestContext& context, u16 command_id, unsigned normal_params_size, + unsigned translate_params_size) + : RequestBuilder( + context, Header{MakeHeader(command_id, normal_params_size, translate_params_size)}) {} + RequestBuilder(u32* command_buffer, Header command_header) : RequestHelperBase(command_buffer, command_header) { cmdbuf[0] = header.raw; } + explicit RequestBuilder(u32* command_buffer, u32 command_header) : RequestBuilder(command_buffer, Header{command_header}) {} + RequestBuilder(u32* command_buffer, u16 command_id, unsigned normal_params_size, unsigned translate_params_size) : RequestBuilder(command_buffer, @@ -88,6 +112,9 @@ public: template <typename... H> void PushMoveHandles(H... handles); + template <typename... O> + void PushObjects(Kernel::SharedPtr<O>... pointers); + void PushCurrentPIDHandle(); void PushStaticBuffer(VAddr buffer_vaddr, u32 size, u8 buffer_id); @@ -153,6 +180,11 @@ inline void RequestBuilder::PushMoveHandles(H... handles) { Push(static_cast<Kernel::Handle>(handles)...); } +template <typename... O> +inline void RequestBuilder::PushObjects(Kernel::SharedPtr<O>... pointers) { + PushMoveHandles(context->AddOutgoingHandle(std::move(pointers))...); +} + inline void RequestBuilder::PushCurrentPIDHandle() { Push(CallingPidDesc()); Push(u32(0)); @@ -171,10 +203,21 @@ inline void RequestBuilder::PushMappedBuffer(VAddr buffer_vaddr, u32 size, class RequestParser : public RequestHelperBase { public: + RequestParser(Kernel::HLERequestContext& context, Header desired_header) + : RequestHelperBase(context, desired_header) {} + + RequestParser(Kernel::HLERequestContext& context, u16 command_id, unsigned normal_params_size, + unsigned translate_params_size) + : RequestParser(context, + Header{MakeHeader(command_id, normal_params_size, translate_params_size)}) { + } + RequestParser(u32* command_buffer, Header command_header) : RequestHelperBase(command_buffer, command_header) {} + explicit RequestParser(u32* command_buffer, u32 command_header) : RequestParser(command_buffer, Header{command_header}) {} + RequestParser(u32* command_buffer, u16 command_id, unsigned normal_params_size, unsigned translate_params_size) : RequestParser(command_buffer, @@ -186,7 +229,10 @@ public: ValidateHeader(); Header builderHeader{ MakeHeader(header.command_id, normal_params_size, translate_params_size)}; - return {cmdbuf, builderHeader}; + if (context != nullptr) + return {*context, builderHeader}; + else + return {cmdbuf, builderHeader}; } template <typename T> @@ -198,10 +244,52 @@ public: template <typename First, typename... Other> void Pop(First& first_value, Other&... other_values); + /// Equivalent to calling `PopHandles<1>()[0]`. Kernel::Handle PopHandle(); + /** + * Pops a descriptor containing `N` handles. The handles are returned as an array. The + * descriptor must contain exactly `N` handles, it is not permitted to, for example, call + * PopHandles<1>() twice to read a multi-handle descriptor with 2 handles, or to make a single + * PopHandles<2>() call to read 2 single-handle descriptors. + */ + template <unsigned int N> + std::array<Kernel::Handle, N> PopHandles(); + + /// Convenience wrapper around PopHandles() which assigns the handles to the passed references. template <typename... H> - void PopHandles(H&... handles); + void PopHandles(H&... handles) { + std::tie(handles...) = PopHandles<sizeof...(H)>(); + } + + /// Equivalent to calling `PopGenericObjects<1>()[0]`. + Kernel::SharedPtr<Kernel::Object> PopGenericObject(); + + /// Equivalent to calling `std::get<0>(PopObjects<T>())`. + template <typename T> + Kernel::SharedPtr<T> PopObject(); + + /** + * Pop a descriptor containing `N` handles and resolves them to Kernel::Object pointers. If a + * handle is invalid, null is returned for that object instead. The same caveats from + * PopHandles() apply regarding `N` matching the number of handles in the descriptor. + */ + template <unsigned int N> + std::array<Kernel::SharedPtr<Kernel::Object>, N> PopGenericObjects(); + + /** + * Resolves handles to Kernel::Objects as in PopGenericsObjects(), but then also casts them to + * the passed `T` types, while verifying that the cast is valid. If the type of an object does + * not match, null is returned instead. + */ + template <typename... T> + std::tuple<Kernel::SharedPtr<T>...> PopObjects(); + + /// Convenience wrapper around PopObjects() which assigns the handles to the passed references. + template <typename... T> + void PopObjects(Kernel::SharedPtr<T>&... pointers) { + std::tie(pointers...) = PopObjects<T...>(); + } /** * @brief Pops the static buffer vaddr @@ -313,15 +401,54 @@ inline Kernel::Handle RequestParser::PopHandle() { return Pop<Kernel::Handle>(); } -template <typename... H> -void RequestParser::PopHandles(H&... handles) { - const u32 handle_descriptor = Pop<u32>(); - const int handles_number = sizeof...(H); - DEBUG_ASSERT_MSG(IsHandleDescriptor(handle_descriptor), - "Tried to pop handle(s) but the descriptor is not a handle descriptor"); - DEBUG_ASSERT_MSG(handles_number == HandleNumberFromDesc(handle_descriptor), - "Number of handles doesn't match the descriptor"); - Pop(static_cast<Kernel::Handle&>(handles)...); +template <unsigned int N> +std::array<Kernel::Handle, N> RequestParser::PopHandles() { + u32 handle_descriptor = Pop<u32>(); + ASSERT_MSG(IsHandleDescriptor(handle_descriptor), + "Tried to pop handle(s) but the descriptor is not a handle descriptor"); + ASSERT_MSG(N == HandleNumberFromDesc(handle_descriptor), + "Number of handles doesn't match the descriptor"); + + std::array<Kernel::Handle, N> handles{}; + for (Kernel::Handle& handle : handles) { + handle = Pop<Kernel::Handle>(); + } + return handles; +} + +inline Kernel::SharedPtr<Kernel::Object> RequestParser::PopGenericObject() { + Kernel::Handle handle = PopHandle(); + return context->GetIncomingHandle(handle); +} + +template <typename T> +Kernel::SharedPtr<T> RequestParser::PopObject() { + return Kernel::DynamicObjectCast<T>(PopGenericObject()); +} + +template <unsigned int N> +inline std::array<Kernel::SharedPtr<Kernel::Object>, N> RequestParser::PopGenericObjects() { + std::array<Kernel::Handle, N> handles = PopHandles<N>(); + std::array<Kernel::SharedPtr<Kernel::Object>, N> pointers; + for (int i = 0; i < N; ++i) { + pointers[i] = context->GetIncomingHandle(handles[i]); + } + return pointers; +} + +namespace detail { +template <typename... T, size_t... I> +std::tuple<Kernel::SharedPtr<T>...> PopObjectsHelper( + std::array<Kernel::SharedPtr<Kernel::Object>, sizeof...(T)>&& pointers, + std::index_sequence<I...>) { + return std::make_tuple(Kernel::DynamicObjectCast<T>(std::move(pointers[I]))...); +} +} // namespace detail + +template <typename... T> +inline std::tuple<Kernel::SharedPtr<T>...> RequestParser::PopObjects() { + return detail::PopObjectsHelper<T...>(PopGenericObjects<sizeof...(T)>(), + std::index_sequence_for<T...>{}); } inline VAddr RequestParser::PopStaticBuffer(size_t* data_size, bool useStaticBuffersToGetVaddr) { diff --git a/src/core/hle/kernel/hle_ipc.cpp b/src/core/hle/kernel/hle_ipc.cpp index a60b8ef00..6cf1886cf 100644 --- a/src/core/hle/kernel/hle_ipc.cpp +++ b/src/core/hle/kernel/hle_ipc.cpp @@ -5,8 +5,10 @@ #include <boost/range/algorithm_ext/erase.hpp> #include "common/assert.h" #include "common/common_types.h" +#include "core/hle/kernel/handle_table.h" #include "core/hle/kernel/hle_ipc.h" #include "core/hle/kernel/kernel.h" +#include "core/hle/kernel/process.h" #include "core/hle/kernel/server_session.h" namespace Kernel { @@ -23,4 +25,101 @@ void SessionRequestHandler::ClientDisconnected(SharedPtr<ServerSession> server_s HLERequestContext::~HLERequestContext() = default; +SharedPtr<Object> HLERequestContext::GetIncomingHandle(u32 id_from_cmdbuf) const { + ASSERT(id_from_cmdbuf < request_handles.size()); + return request_handles[id_from_cmdbuf]; +} + +u32 HLERequestContext::AddOutgoingHandle(SharedPtr<Object> object) { + request_handles.push_back(std::move(object)); + return request_handles.size() - 1; +} + +void HLERequestContext::ClearIncomingObjects() { + request_handles.clear(); +} + +ResultCode HLERequestContext::PopulateFromIncomingCommandBuffer(const u32_le* src_cmdbuf, + Process& src_process, + HandleTable& src_table) { + IPC::Header header{src_cmdbuf[0]}; + + size_t untranslated_size = 1u + header.normal_params_size; + size_t command_size = untranslated_size + header.translate_params_size; + ASSERT(command_size <= IPC::COMMAND_BUFFER_LENGTH); // TODO(yuriks): Return error + + std::copy_n(src_cmdbuf, untranslated_size, cmd_buf.begin()); + + size_t i = untranslated_size; + while (i < command_size) { + u32 descriptor = cmd_buf[i] = src_cmdbuf[i]; + i += 1; + + switch (IPC::GetDescriptorType(descriptor)) { + case IPC::DescriptorType::CopyHandle: + case IPC::DescriptorType::MoveHandle: { + u32 num_handles = IPC::HandleNumberFromDesc(descriptor); + 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); + } + + cmd_buf[i++] = AddOutgoingHandle(std::move(object)); + } + break; + } + case IPC::DescriptorType::CallingPid: { + cmd_buf[i++] = src_process.process_id; + break; + } + default: + UNIMPLEMENTED_MSG("Unsupported handle translation: 0x%08X", descriptor); + } + } + + return RESULT_SUCCESS; +} + +ResultCode HLERequestContext::WriteToOutgoingCommandBuffer(u32_le* dst_cmdbuf, Process& dst_process, + HandleTable& dst_table) const { + IPC::Header header{cmd_buf[0]}; + + size_t untranslated_size = 1u + header.normal_params_size; + size_t command_size = untranslated_size + header.translate_params_size; + ASSERT(command_size <= IPC::COMMAND_BUFFER_LENGTH); + + std::copy_n(cmd_buf.begin(), untranslated_size, dst_cmdbuf); + + size_t i = untranslated_size; + while (i < command_size) { + u32 descriptor = dst_cmdbuf[i] = cmd_buf[i]; + i += 1; + + switch (IPC::GetDescriptorType(descriptor)) { + case IPC::DescriptorType::CopyHandle: + case IPC::DescriptorType::MoveHandle: { + // HLE services don't use handles, so we treat both CopyHandle and MoveHandle equally + u32 num_handles = IPC::HandleNumberFromDesc(descriptor); + 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(); + dst_cmdbuf[i++] = handle; + } + break; + } + default: + UNIMPLEMENTED_MSG("Unsupported handle translation: 0x%08X", descriptor); + } + } + + return RESULT_SUCCESS; +} + } // namespace Kernel diff --git a/src/core/hle/kernel/hle_ipc.h b/src/core/hle/kernel/hle_ipc.h index c30184eab..cbb109d8f 100644 --- a/src/core/hle/kernel/hle_ipc.h +++ b/src/core/hle/kernel/hle_ipc.h @@ -4,8 +4,13 @@ #pragma once +#include <array> #include <memory> #include <vector> +#include <boost/container/small_vector.hpp> +#include "common/common_types.h" +#include "common/swap.h" +#include "core/hle/ipc.h" #include "core/hle/kernel/kernel.h" #include "core/hle/kernel/server_session.h" @@ -15,6 +20,9 @@ class ServiceFrameworkBase; namespace Kernel { +class HandleTable; +class Process; + /** * Interface implemented by HLE Session handlers. * This can be provided to a ServerSession in order to hook into several relevant events @@ -59,14 +67,28 @@ protected: * Class containing information about an in-flight IPC request being handled by an HLE service * implementation. Services should avoid using old global APIs (e.g. Kernel::GetCommandBuffer()) and * when possible use the APIs in this class to service the request. + * + * HLE handle protocol + * =================== + * + * To avoid needing HLE services to keep a separate handle table, or having to directly modify the + * requester's table, a tweaked protocol is used to receive and send handles in requests. The kernel + * will decode the incoming handles into object pointers and insert a id in the buffer where the + * handle would normally be. The service then calls GetIncomingHandle() with that id to get the + * pointer to the object. Similarly, instead of inserting a handle into the command buffer, the + * service calls AddOutgoingHandle() and stores the returned id where the handle would normally go. + * + * The end result is similar to just giving services their own real handle tables, but since these + * ids are local to a specific context, it avoids requiring services to manage handles for objects + * across multiple calls and ensuring that unneeded handles are cleaned up. */ class HLERequestContext { public: ~HLERequestContext(); /// Returns a pointer to the IPC command buffer for this request. - u32* CommandBuffer() const { - return cmd_buf; + u32* CommandBuffer() { + return cmd_buf.data(); } /** @@ -77,11 +99,37 @@ public: return session; } + /** + * Resolves a object id from the request command buffer into a pointer to an object. See the + * "HLE handle protocol" section in the class documentation for more details. + */ + SharedPtr<Object> GetIncomingHandle(u32 id_from_cmdbuf) const; + + /** + * Adds an outgoing object to the response, returning the id which should be used to reference + * it. See the "HLE handle protocol" section in the class documentation for more details. + */ + u32 AddOutgoingHandle(SharedPtr<Object> object); + + /** + * Discards all Objects from the context, invalidating all ids. This may be called after reading + * out all incoming objects, so that the buffer memory can be re-used for outgoing handles, but + * this is not required. + */ + void ClearIncomingObjects(); + private: friend class Service::ServiceFrameworkBase; - u32* cmd_buf = nullptr; + ResultCode PopulateFromIncomingCommandBuffer(const u32_le* src_cmdbuf, Process& src_process, + HandleTable& src_table); + ResultCode WriteToOutgoingCommandBuffer(u32_le* dst_cmdbuf, Process& dst_process, + HandleTable& dst_table) const; + + std::array<u32, IPC::COMMAND_BUFFER_LENGTH> cmd_buf; SharedPtr<ServerSession> session; + // TODO(yuriks): Check common usage of this and optimize size accordingly + boost::container::small_vector<SharedPtr<Object>, 8> request_handles; }; } // namespace Kernel diff --git a/src/core/hle/service/nwm/nwm_uds.cpp b/src/core/hle/service/nwm/nwm_uds.cpp index 6c4600f25..e92900d48 100644 --- a/src/core/hle/service/nwm/nwm_uds.cpp +++ b/src/core/hle/service/nwm/nwm_uds.cpp @@ -215,6 +215,11 @@ static void GetConnectionStatus(Interface* self) { rb.Push(RESULT_SUCCESS); rb.PushRaw(connection_status); + // Reset the bitmask of changed nodes after each call to this + // function to prevent falsely informing games of outstanding + // changes in subsequent calls. + connection_status.changed_nodes = 0; + LOG_DEBUG(Service_NWM, "called"); } @@ -314,8 +319,11 @@ static void BeginHostingNetwork(Interface* self) { // The host is always the first node connection_status.network_node_id = 1; node_info[0].network_node_id = 1; + connection_status.nodes[0] = connection_status.network_node_id; // Set the bit 0 in the nodes bitmask to indicate that node 1 is already taken. connection_status.node_bitmask |= 1; + // Notify the application that the first node was set. + connection_status.changed_nodes |= 1; // If the game has a preferred channel, use that instead. if (network_info.channel != 0) @@ -352,6 +360,8 @@ static void DestroyNetwork(Interface* self) { // Unschedule the beacon broadcast event. CoreTiming::UnscheduleEvent(beacon_broadcast_event, 0); + // TODO(Subv): Check if connection_status is indeed reset after this call. + connection_status = {}; connection_status.status = static_cast<u8>(NetworkStatus::NotConnected); connection_status_event->Signal(); diff --git a/src/core/hle/service/nwm/nwm_uds.h b/src/core/hle/service/nwm/nwm_uds.h index 29b146569..141f49f9c 100644 --- a/src/core/hle/service/nwm/nwm_uds.h +++ b/src/core/hle/service/nwm/nwm_uds.h @@ -24,6 +24,9 @@ const double MillisecondsPerTU = 1.024; // Interval measured in TU, the default value is 100TU = 102.4ms const u16 DefaultBeaconInterval = 100; +/// The maximum number of nodes that can exist in an UDS session. +constexpr u32 UDSMaxNodes = 16; + struct NodeInfo { u64_le friend_code_seed; std::array<u16_le, 10> username; @@ -47,8 +50,8 @@ struct ConnectionStatus { u32_le status; INSERT_PADDING_WORDS(1); u16_le network_node_id; - INSERT_PADDING_BYTES(2); - INSERT_PADDING_BYTES(32); + u16_le changed_nodes; + u16_le nodes[UDSMaxNodes]; u8 total_nodes; u8 max_nodes; u16_le node_bitmask; diff --git a/src/core/hle/service/nwm/uds_beacon.h b/src/core/hle/service/nwm/uds_beacon.h index 6df4c4f47..caacf4c6f 100644 --- a/src/core/hle/service/nwm/uds_beacon.h +++ b/src/core/hle/service/nwm/uds_beacon.h @@ -15,9 +15,6 @@ namespace Service { namespace NWM { using MacAddress = std::array<u8, 6>; - -/// The maximum number of nodes that can exist in an UDS session. -constexpr u32 UDSMaxNodes = 16; constexpr std::array<u8, 3> NintendoOUI = {0x00, 0x1F, 0x32}; /// Additional block tag ids in the Beacon frames diff --git a/src/core/hle/service/service.cpp b/src/core/hle/service/service.cpp index d34968428..791a65c19 100644 --- a/src/core/hle/service/service.cpp +++ b/src/core/hle/service/service.cpp @@ -2,10 +2,14 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <algorithm> #include <fmt/format.h> +#include "common/assert.h" #include "common/logging/log.h" #include "common/string_util.h" +#include "core/hle/ipc.h" #include "core/hle/kernel/client_port.h" +#include "core/hle/kernel/process.h" #include "core/hle/kernel/server_port.h" #include "core/hle/kernel/server_session.h" #include "core/hle/service/ac/ac.h" @@ -160,12 +164,6 @@ void ServiceFrameworkBase::ReportUnimplementedFunction(u32* cmd_buf, const Funct void ServiceFrameworkBase::HandleSyncRequest(SharedPtr<ServerSession> server_session) { u32* cmd_buf = Kernel::GetCommandBuffer(); - // TODO(yuriks): The kernel should be the one handling this as part of translation after - // everything else is migrated - Kernel::HLERequestContext context; - context.cmd_buf = cmd_buf; - context.session = std::move(server_session); - u32 header_code = cmd_buf[0]; auto itr = handlers.find(header_code); const FunctionInfoBase* info = itr == handlers.end() ? nullptr : &itr->second; @@ -173,9 +171,18 @@ void ServiceFrameworkBase::HandleSyncRequest(SharedPtr<ServerSession> server_ses return ReportUnimplementedFunction(cmd_buf, info); } + // 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); + context.PopulateFromIncomingCommandBuffer(cmd_buf, *Kernel::g_current_process, + Kernel::g_handle_table); + LOG_TRACE(Service, "%s", MakeFunctionString(info->name, GetServiceName().c_str(), cmd_buf).c_str()); handler_invoker(this, info->handler_callback, context); + context.WriteToOutgoingCommandBuffer(cmd_buf, *Kernel::g_current_process, + Kernel::g_handle_table); } //////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/src/core/hle/service/sm/srv.cpp b/src/core/hle/service/sm/srv.cpp index b8b62b068..74a1256e0 100644 --- a/src/core/hle/service/sm/srv.cpp +++ b/src/core/hle/service/sm/srv.cpp @@ -7,9 +7,11 @@ #include "common/common_types.h" #include "common/logging/log.h" #include "core/hle/ipc.h" +#include "core/hle/ipc_helpers.h" #include "core/hle/kernel/client_port.h" #include "core/hle/kernel/client_session.h" -#include "core/hle/kernel/handle_table.h" +#include "core/hle/kernel/errors.h" +#include "core/hle/kernel/hle_ipc.h" #include "core/hle/kernel/semaphore.h" #include "core/hle/kernel/server_session.h" #include "core/hle/service/sm/sm.h" @@ -30,15 +32,18 @@ constexpr int MAX_PENDING_NOTIFICATIONS = 16; * 1: ResultCode */ void SRV::RegisterClient(Kernel::HLERequestContext& ctx) { - u32* cmd_buff = ctx.CommandBuffer(); + IPC::RequestParser rp(ctx, 0x1, 0, 2); - if (cmd_buff[1] != IPC::CallingPidDesc()) { - cmd_buff[0] = IPC::MakeHeader(0x0, 0x1, 0); // 0x40 - cmd_buff[1] = IPC::ERR_INVALID_BUFFER_DESCRIPTOR.raw; + u32 pid_descriptor = rp.Pop<u32>(); + if (pid_descriptor != IPC::CallingPidDesc()) { + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(IPC::ERR_INVALID_BUFFER_DESCRIPTOR); return; } - cmd_buff[0] = IPC::MakeHeader(0x1, 0x1, 0); // 0x10040 - cmd_buff[1] = RESULT_SUCCESS.raw; // No error + u32 caller_pid = rp.Pop<u32>(); + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(RESULT_SUCCESS); LOG_WARNING(Service_SRV, "(STUBBED) called"); } @@ -53,15 +58,14 @@ void SRV::RegisterClient(Kernel::HLERequestContext& ctx) { * 3: Handle to semaphore signaled on process notification */ void SRV::EnableNotification(Kernel::HLERequestContext& ctx) { - u32* cmd_buff = ctx.CommandBuffer(); + IPC::RequestParser rp(ctx, 0x2, 0, 0); notification_semaphore = Kernel::Semaphore::Create(0, MAX_PENDING_NOTIFICATIONS, "SRV:Notification").Unwrap(); - cmd_buff[0] = IPC::MakeHeader(0x2, 0x1, 0x2); // 0x20042 - cmd_buff[1] = RESULT_SUCCESS.raw; // No error - cmd_buff[2] = IPC::CopyHandleDesc(1); - cmd_buff[3] = Kernel::g_handle_table.Create(notification_semaphore).MoveFrom(); + IPC::RequestBuilder rb = rp.MakeBuilder(1, 2); + rb.Push(RESULT_SUCCESS); + rb.PushObjects(notification_semaphore); LOG_WARNING(Service_SRV, "(STUBBED) called"); } @@ -77,43 +81,49 @@ void SRV::EnableNotification(Kernel::HLERequestContext& ctx) { * 3: Service handle */ void SRV::GetServiceHandle(Kernel::HLERequestContext& ctx) { - ResultCode res = RESULT_SUCCESS; - u32* cmd_buff = ctx.CommandBuffer(); + IPC::RequestParser rp(ctx, 0x5, 4, 0); + auto name_buf = rp.PopRaw<std::array<char, 8>>(); + size_t name_len = rp.Pop<u32>(); + u32 flags = rp.Pop<u32>(); + + bool return_port_on_failure = (flags & 1) == 0; - size_t name_len = cmd_buff[3]; if (name_len > Service::kMaxPortSize) { - cmd_buff[1] = ERR_INVALID_NAME_SIZE.raw; - LOG_ERROR(Service_SRV, "called name_len=0x%X, failed with code=0x%08X", name_len, - cmd_buff[1]); + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(ERR_INVALID_NAME_SIZE); + LOG_ERROR(Service_SRV, "called name_len=0x%X -> ERR_INVALID_NAME_SIZE", name_len); return; } - std::string name(reinterpret_cast<const char*>(&cmd_buff[1]), name_len); - bool return_port_on_failure = (cmd_buff[4] & 1) == 0; + std::string name(name_buf.data(), name_len); // TODO(yuriks): Permission checks go here auto client_port = service_manager->GetServicePort(name); if (client_port.Failed()) { - cmd_buff[1] = client_port.Code().raw; - LOG_ERROR(Service_SRV, "called service=%s, failed with code=0x%08X", name.c_str(), - cmd_buff[1]); + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(client_port.Code()); + LOG_ERROR(Service_SRV, "called service=%s -> error 0x%08X", name.c_str(), + client_port.Code().raw); return; } auto session = client_port.Unwrap()->Connect(); - cmd_buff[1] = session.Code().raw; if (session.Succeeded()) { - cmd_buff[3] = Kernel::g_handle_table.Create(session.MoveFrom()).MoveFrom(); - LOG_DEBUG(Service_SRV, "called service=%s, session handle=0x%08X", name.c_str(), - cmd_buff[3]); + LOG_DEBUG(Service_SRV, "called service=%s -> session=%u", name.c_str(), + (*session)->GetObjectId()); + IPC::RequestBuilder rb = rp.MakeBuilder(1, 2); + rb.Push(session.Code()); + rb.PushObjects(session.MoveFrom()); } else if (session.Code() == Kernel::ERR_MAX_CONNECTIONS_REACHED && return_port_on_failure) { - cmd_buff[1] = ERR_MAX_CONNECTIONS_REACHED.raw; - cmd_buff[3] = Kernel::g_handle_table.Create(client_port.MoveFrom()).MoveFrom(); - LOG_WARNING(Service_SRV, "called service=%s, *port* handle=0x%08X", name.c_str(), - cmd_buff[3]); + 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()); } else { - LOG_ERROR(Service_SRV, "called service=%s, failed with code=0x%08X", name.c_str(), - cmd_buff[1]); + LOG_ERROR(Service_SRV, "called service=%s -> error 0x%08X", name.c_str(), session.Code()); + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(session.Code()); } } @@ -127,12 +137,11 @@ void SRV::GetServiceHandle(Kernel::HLERequestContext& ctx) { * 1: ResultCode */ void SRV::Subscribe(Kernel::HLERequestContext& ctx) { - u32* cmd_buff = ctx.CommandBuffer(); - - u32 notification_id = cmd_buff[1]; + IPC::RequestParser rp(ctx, 0x9, 1, 0); + u32 notification_id = rp.Pop<u32>(); - cmd_buff[0] = IPC::MakeHeader(0x9, 0x1, 0); // 0x90040 - cmd_buff[1] = RESULT_SUCCESS.raw; // No error + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(RESULT_SUCCESS); LOG_WARNING(Service_SRV, "(STUBBED) called, notification_id=0x%X", notification_id); } @@ -146,12 +155,11 @@ void SRV::Subscribe(Kernel::HLERequestContext& ctx) { * 1: ResultCode */ void SRV::Unsubscribe(Kernel::HLERequestContext& ctx) { - u32* cmd_buff = ctx.CommandBuffer(); + IPC::RequestParser rp(ctx, 0xA, 1, 0); + u32 notification_id = rp.Pop<u32>(); - u32 notification_id = cmd_buff[1]; - - cmd_buff[0] = IPC::MakeHeader(0xA, 0x1, 0); // 0xA0040 - cmd_buff[1] = RESULT_SUCCESS.raw; // No error + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(RESULT_SUCCESS); LOG_WARNING(Service_SRV, "(STUBBED) called, notification_id=0x%X", notification_id); } @@ -166,13 +174,12 @@ void SRV::Unsubscribe(Kernel::HLERequestContext& ctx) { * 1: ResultCode */ void SRV::PublishToSubscriber(Kernel::HLERequestContext& ctx) { - u32* cmd_buff = ctx.CommandBuffer(); - - u32 notification_id = cmd_buff[1]; - u8 flags = cmd_buff[2] & 0xFF; + IPC::RequestParser rp(ctx, 0xC, 2, 0); + u32 notification_id = rp.Pop<u32>(); + u8 flags = rp.Pop<u8>(); - cmd_buff[0] = IPC::MakeHeader(0xC, 0x1, 0); // 0xC0040 - cmd_buff[1] = RESULT_SUCCESS.raw; // No error + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(RESULT_SUCCESS); LOG_WARNING(Service_SRV, "(STUBBED) called, notification_id=0x%X, flags=%u", notification_id, flags); } 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 57d5e8253..e6cccebf6 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp @@ -182,19 +182,22 @@ RasterizerOpenGL::RasterizerOpenGL() : shader_dirty(true) { RasterizerOpenGL::~RasterizerOpenGL() {} /** - * This is a helper function to resolve an issue with opposite quaternions being interpolated by - * OpenGL. See below for a detailed description of this issue (yuriks): + * This is a helper function to resolve an issue when interpolating opposite quaternions. See below + * for a detailed description of this issue (yuriks): * * For any rotation, there are two quaternions Q, and -Q, that represent the same rotation. If you * interpolate two quaternions that are opposite, instead of going from one rotation to another * using the shortest path, you'll go around the longest path. You can test if two quaternions are - * opposite by checking if Dot(Q1, W2) < 0. In that case, you can flip either of them, therefore - * making Dot(-Q1, W2) positive. + * opposite by checking if Dot(Q1, Q2) < 0. In that case, you can flip either of them, therefore + * making Dot(Q1, -Q2) positive. * - * NOTE: This solution corrects this issue per-vertex before passing the quaternions to OpenGL. This - * should be correct for nearly all cases, however a more correct implementation (but less trivial - * and perhaps unnecessary) would be to handle this per-fragment, by interpolating the quaternions - * manually using two Lerps, and doing this correction before each Lerp. + * This solution corrects this issue per-vertex before passing the quaternions to OpenGL. This is + * correct for most cases but can still rotate around the long way sometimes. An implementation + * which did `lerp(lerp(Q1, Q2), Q3)` (with proper weighting), applying the dot product check + * between each step would work for those cases at the cost of being more complex to implement. + * + * Fortunately however, the 3DS hardware happens to also use this exact same logic to work around + * these issues, making this basic implementation actually more accurate to the hardware. */ static bool AreQuaternionsOpposite(Math::Vec4<Pica::float24> qa, Math::Vec4<Pica::float24> qb) { Math::Vec4f a{qa.x.ToFloat32(), qa.y.ToFloat32(), qa.z.ToFloat32(), qa.w.ToFloat32()}; 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/swrasterizer/rasterizer.cpp b/src/video_core/swrasterizer/rasterizer.cpp index 8b7b1defb..cd7b6c39d 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}; 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: |