diff options
Diffstat (limited to 'src')
23 files changed, 118 insertions, 114 deletions
diff --git a/src/audio_core/audio_core.cpp b/src/audio_core/audio_core.cpp index c845330cd..07a679c32 100644 --- a/src/audio_core/audio_core.cpp +++ b/src/audio_core/audio_core.cpp @@ -8,7 +8,7 @@ namespace AudioCore { -AudioCore::AudioCore(Core::System& system) : audio_manager{std::make_unique<AudioManager>(system)} { +AudioCore::AudioCore(Core::System& system) : audio_manager{std::make_unique<AudioManager>()} { CreateSinks(); // Must be created after the sinks adsp = std::make_unique<AudioRenderer::ADSP::ADSP>(system, *output_sink); diff --git a/src/audio_core/audio_manager.cpp b/src/audio_core/audio_manager.cpp index 2f1bba9c3..2acde668e 100644 --- a/src/audio_core/audio_manager.cpp +++ b/src/audio_core/audio_manager.cpp @@ -1,14 +1,13 @@ // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later -#include "audio_core/audio_in_manager.h" #include "audio_core/audio_manager.h" -#include "audio_core/audio_out_manager.h" #include "core/core.h" +#include "core/hle/service/audio/errors.h" namespace AudioCore { -AudioManager::AudioManager(Core::System& system_) : system{system_} { +AudioManager::AudioManager() { thread = std::jthread([this]() { ThreadFunc(); }); } @@ -27,7 +26,7 @@ Result AudioManager::SetOutManager(BufferEventFunc buffer_func) { const auto index{events.GetManagerIndex(Event::Type::AudioOutManager)}; if (buffer_events[index] == nullptr) { - buffer_events[index] = buffer_func; + buffer_events[index] = std::move(buffer_func); needs_update = true; events.SetAudioEvent(Event::Type::AudioOutManager, true); } @@ -43,7 +42,7 @@ Result AudioManager::SetInManager(BufferEventFunc buffer_func) { const auto index{events.GetManagerIndex(Event::Type::AudioInManager)}; if (buffer_events[index] == nullptr) { - buffer_events[index] = buffer_func; + buffer_events[index] = std::move(buffer_func); needs_update = true; events.SetAudioEvent(Event::Type::AudioInManager, true); } @@ -60,19 +59,21 @@ void AudioManager::ThreadFunc() { running = true; while (running) { - auto timed_out{events.Wait(l, std::chrono::seconds(2))}; + const auto timed_out{events.Wait(l, std::chrono::seconds(2))}; if (events.CheckAudioEventSet(Event::Type::Max)) { break; } for (size_t i = 0; i < buffer_events.size(); i++) { - if (events.CheckAudioEventSet(Event::Type(i)) || timed_out) { + const auto event_type = static_cast<Event::Type>(i); + + if (events.CheckAudioEventSet(event_type) || timed_out) { if (buffer_events[i]) { buffer_events[i](); } } - events.SetAudioEvent(Event::Type(i), false); + events.SetAudioEvent(event_type, false); } } } diff --git a/src/audio_core/audio_manager.h b/src/audio_core/audio_manager.h index 8cbd95e22..abf077de4 100644 --- a/src/audio_core/audio_manager.h +++ b/src/audio_core/audio_manager.h @@ -10,22 +10,11 @@ #include <thread> #include "audio_core/audio_event.h" -#include "core/hle/service/audio/errors.h" -namespace Core { -class System; -} +union Result; namespace AudioCore { -namespace AudioOut { -class Manager; -} - -namespace AudioIn { -class Manager; -} - /** * The AudioManager's main purpose is to wait for buffer events for the audio in and out managers, * and call an associated callback to release buffers. @@ -43,7 +32,7 @@ class AudioManager { using BufferEventFunc = std::function<void()>; public: - explicit AudioManager(Core::System& system); + explicit AudioManager(); /** * Shutdown the audio manager. @@ -80,10 +69,6 @@ private: */ void ThreadFunc(); - /// Core system - Core::System& system; - /// Have sessions started palying? - bool sessions_started{}; /// Is the main thread running? std::atomic<bool> running{}; /// Unused diff --git a/src/audio_core/renderer/adsp/audio_renderer.cpp b/src/audio_core/renderer/adsp/audio_renderer.cpp index bafe4822a..ab2257bd8 100644 --- a/src/audio_core/renderer/adsp/audio_renderer.cpp +++ b/src/audio_core/renderer/adsp/audio_renderer.cpp @@ -47,7 +47,7 @@ RenderMessage AudioRenderer_Mailbox::ADSPWaitMessage() { return msg; } -CommandBuffer& AudioRenderer_Mailbox::GetCommandBuffer(const s32 session_id) { +CommandBuffer& AudioRenderer_Mailbox::GetCommandBuffer(const u32 session_id) { return command_buffers[session_id]; } diff --git a/src/audio_core/renderer/adsp/audio_renderer.h b/src/audio_core/renderer/adsp/audio_renderer.h index 02e923c84..151f38c1b 100644 --- a/src/audio_core/renderer/adsp/audio_renderer.h +++ b/src/audio_core/renderer/adsp/audio_renderer.h @@ -83,7 +83,7 @@ public: * @param session_id - The session id to get (0 or 1). * @return The command buffer. */ - CommandBuffer& GetCommandBuffer(s32 session_id); + CommandBuffer& GetCommandBuffer(u32 session_id); /** * Set the command buffer with the given session id (0 or 1). diff --git a/src/audio_core/sink/sink_stream.cpp b/src/audio_core/sink/sink_stream.cpp index 37fe725e4..849f862b0 100644 --- a/src/audio_core/sink/sink_stream.cpp +++ b/src/audio_core/sink/sink_stream.cpp @@ -214,8 +214,13 @@ void SinkStream::ProcessAudioOutAndRender(std::span<s16> output_buffer, std::siz // video play out without attempting to stall. // Can hopefully remove this later with a more complete NVDEC implementation. const auto nvdec_active{system.AudioCore().IsNVDECActive()}; - if (!nvdec_active && queued_buffers > max_queue_size) { + + // Core timing cannot be paused in single-core mode, so Stall ends up being called over and over + // and never recovers to a normal state, so just skip attempting to sync things on single-core. + if (system.IsMulticore() && !nvdec_active && queued_buffers > max_queue_size) { Stall(); + } else if (system.IsMulticore() && queued_buffers <= max_queue_size) { + Unstall(); } while (frames_written < num_frames) { @@ -255,7 +260,7 @@ void SinkStream::ProcessAudioOutAndRender(std::span<s16> output_buffer, std::siz std::memcpy(&last_frame[0], &output_buffer[(frames_written - 1) * frame_size], frame_size_bytes); - if (stalled && queued_buffers <= max_queue_size) { + if (system.IsMulticore() && queued_buffers <= max_queue_size) { Unstall(); } } diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index 68436a4bc..3447fabd8 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -14,32 +14,7 @@ if (DEFINED ENV{DISPLAYVERSION}) set(DISPLAY_VERSION $ENV{DISPLAYVERSION}) endif () -# Pass the path to git to the GenerateSCMRev.cmake as well -find_package(Git QUIET) - -add_custom_command(OUTPUT scm_rev.cpp - COMMAND ${CMAKE_COMMAND} - -DSRC_DIR=${PROJECT_SOURCE_DIR} - -DBUILD_REPOSITORY=${BUILD_REPOSITORY} - -DTITLE_BAR_FORMAT_IDLE=${TITLE_BAR_FORMAT_IDLE} - -DTITLE_BAR_FORMAT_RUNNING=${TITLE_BAR_FORMAT_RUNNING} - -DBUILD_TAG=${BUILD_TAG} - -DBUILD_ID=${DISPLAY_VERSION} - -DGIT_REF_SPEC=${GIT_REF_SPEC} - -DGIT_REV=${GIT_REV} - -DGIT_DESC=${GIT_DESC} - -DGIT_BRANCH=${GIT_BRANCH} - -DBUILD_FULLNAME=${BUILD_FULLNAME} - -DGIT_EXECUTABLE=${GIT_EXECUTABLE} - -P ${PROJECT_SOURCE_DIR}/CMakeModules/GenerateSCMRev.cmake - DEPENDS - # Check that the scm_rev files haven't changed - "${CMAKE_CURRENT_SOURCE_DIR}/scm_rev.cpp.in" - "${CMAKE_CURRENT_SOURCE_DIR}/scm_rev.h" - # technically we should regenerate if the git version changed, but its not worth the effort imo - "${PROJECT_SOURCE_DIR}/CMakeModules/GenerateSCMRev.cmake" - VERBATIM -) +include(GenerateSCMRev) add_library(common STATIC algorithm.h @@ -117,7 +92,7 @@ add_library(common STATIC quaternion.h reader_writer_queue.h ring_buffer.h - scm_rev.cpp + ${CMAKE_CURRENT_BINARY_DIR}/scm_rev.cpp scm_rev.h scope_exit.h settings.cpp diff --git a/src/common/settings.h b/src/common/settings.h index 851812f28..d2452c93b 100644 --- a/src/common/settings.h +++ b/src/common/settings.h @@ -531,6 +531,7 @@ struct Values { Setting<bool> use_auto_stub{false, "use_auto_stub"}; Setting<bool> enable_all_controllers{false, "enable_all_controllers"}; Setting<bool> create_crash_dumps{false, "create_crash_dumps"}; + Setting<bool> perform_vulkan_check{true, "perform_vulkan_check"}; // Miscellaneous Setting<std::string> log_filter{"*:Info", "log_filter"}; diff --git a/src/core/hle/service/nfp/nfp.cpp b/src/core/hle/service/nfp/nfp.cpp index e0ed3f771..037b86653 100644 --- a/src/core/hle/service/nfp/nfp.cpp +++ b/src/core/hle/service/nfp/nfp.cpp @@ -800,12 +800,12 @@ Result Module::Interface::Flush() { // Return to the start of the file if (!amiibo_file.Seek(0)) { - LOG_ERROR(Service_NFP, "Error writting to file"); + LOG_ERROR(Service_NFP, "Error writing to file"); return ErrCodes::WriteAmiiboFailed; } if (!amiibo_file.Write(encrypted_tag_data)) { - LOG_ERROR(Service_NFP, "Error writting to file"); + LOG_ERROR(Service_NFP, "Error writing to file"); return ErrCodes::WriteAmiiboFailed; } diff --git a/src/core/hle/service/sockets/bsd.cpp b/src/core/hle/service/sockets/bsd.cpp index cc679cc81..9e94a462f 100644 --- a/src/core/hle/service/sockets/bsd.cpp +++ b/src/core/hle/service/sockets/bsd.cpp @@ -929,7 +929,7 @@ BSD::BSD(Core::System& system_, const char* name) proxy_packet_received = room_member->BindOnProxyPacketReceived( [this](const Network::ProxyPacket& packet) { OnProxyPacketReceived(packet); }); } else { - LOG_ERROR(Service, "Network isn't initalized"); + LOG_ERROR(Service, "Network isn't initialized"); } } diff --git a/src/core/internal_network/network.cpp b/src/core/internal_network/network.cpp index cdf38a2a4..447fbffaa 100644 --- a/src/core/internal_network/network.cpp +++ b/src/core/internal_network/network.cpp @@ -364,7 +364,7 @@ std::pair<s32, Errno> Poll(std::vector<PollFD>& pollfds, s32 timeout) { std::vector<WSAPOLLFD> host_pollfds(pollfds.size()); std::transform(pollfds.begin(), pollfds.end(), host_pollfds.begin(), [](PollFD fd) { WSAPOLLFD result; - result.fd = fd.socket->fd; + result.fd = fd.socket->GetFD(); result.events = TranslatePollEvents(fd.events); result.revents = 0; return result; @@ -430,12 +430,12 @@ std::pair<SocketBase::AcceptResult, Errno> Socket::Accept() { return {AcceptResult{}, GetAndLogLastError()}; } - AcceptResult result; - result.socket = std::make_unique<Socket>(); - result.socket->fd = new_socket; - ASSERT(addrlen == sizeof(sockaddr_in)); - result.sockaddr_in = TranslateToSockAddrIn(addr); + + AcceptResult result{ + .socket = std::make_unique<Socket>(new_socket), + .sockaddr_in = TranslateToSockAddrIn(addr), + }; return {std::move(result), Errno::SUCCESS}; } diff --git a/src/core/internal_network/sockets.h b/src/core/internal_network/sockets.h index a70429b19..2e328c645 100644 --- a/src/core/internal_network/sockets.h +++ b/src/core/internal_network/sockets.h @@ -32,6 +32,10 @@ public: std::unique_ptr<SocketBase> socket; SockAddrIn sockaddr_in; }; + + SocketBase() = default; + explicit SocketBase(SOCKET fd_) : fd{fd_} {} + virtual ~SocketBase() = default; virtual SocketBase& operator=(const SocketBase&) = delete; @@ -89,12 +93,19 @@ public: virtual void HandleProxyPacket(const ProxyPacket& packet) = 0; + [[nodiscard]] SOCKET GetFD() const { + return fd; + } + +protected: SOCKET fd = INVALID_SOCKET; }; class Socket : public SocketBase { public: Socket() = default; + explicit Socket(SOCKET fd_) : SocketBase{fd_} {} + ~Socket() override; Socket(const Socket&) = delete; diff --git a/src/core/loader/loader.cpp b/src/core/loader/loader.cpp index 104d16efa..f24474ed8 100644 --- a/src/core/loader/loader.cpp +++ b/src/core/loader/loader.cpp @@ -244,6 +244,10 @@ static std::unique_ptr<AppLoader> GetFileLoader(Core::System& system, FileSys::V std::unique_ptr<AppLoader> GetLoader(Core::System& system, FileSys::VirtualFile file, u64 program_id, std::size_t program_index) { + if (!file) { + return nullptr; + } + FileType type = IdentifyFile(file); const FileType filename_type = GuessFromFilename(file->GetName()); diff --git a/src/network/network.cpp b/src/network/network.cpp index 0841e4134..6652a186b 100644 --- a/src/network/network.cpp +++ b/src/network/network.cpp @@ -15,7 +15,7 @@ RoomNetwork::RoomNetwork() { bool RoomNetwork::Init() { if (enet_initialize() != 0) { - LOG_ERROR(Network, "Error initalizing ENet"); + LOG_ERROR(Network, "Error initializing ENet"); return false; } m_room = std::make_shared<Room>(); diff --git a/src/shader_recompiler/backend/glasm/emit_glasm.cpp b/src/shader_recompiler/backend/glasm/emit_glasm.cpp index 97a6b383b..01f9abc71 100644 --- a/src/shader_recompiler/backend/glasm/emit_glasm.cpp +++ b/src/shader_recompiler/backend/glasm/emit_glasm.cpp @@ -175,7 +175,7 @@ bool IsReference(IR::Inst& inst) { } void PrecolorInst(IR::Inst& phi) { - // Insert phi moves before references to avoid overwritting other phis + // Insert phi moves before references to avoid overwriting other phis const size_t num_args{phi.NumArgs()}; for (size_t i = 0; i < num_args; ++i) { IR::Block& phi_block{*phi.PhiBlock(i)}; diff --git a/src/shader_recompiler/backend/glsl/emit_glsl.cpp b/src/shader_recompiler/backend/glsl/emit_glsl.cpp index 76c18e488..e8a4390f6 100644 --- a/src/shader_recompiler/backend/glsl/emit_glsl.cpp +++ b/src/shader_recompiler/backend/glsl/emit_glsl.cpp @@ -101,7 +101,7 @@ bool IsReference(IR::Inst& inst) { } void PrecolorInst(IR::Inst& phi) { - // Insert phi moves before references to avoid overwritting other phis + // Insert phi moves before references to avoid overwriting other phis const size_t num_args{phi.NumArgs()}; for (size_t i = 0; i < num_args; ++i) { IR::Block& phi_block{*phi.PhiBlock(i)}; diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp index a4ed68422..195074bf2 100644 --- a/src/yuzu/configuration/config.cpp +++ b/src/yuzu/configuration/config.cpp @@ -546,6 +546,7 @@ void Config::ReadDebuggingValues() { ReadBasicSetting(Settings::values.use_auto_stub); ReadBasicSetting(Settings::values.enable_all_controllers); ReadBasicSetting(Settings::values.create_crash_dumps); + ReadBasicSetting(Settings::values.perform_vulkan_check); qt_config->endGroup(); } @@ -1162,6 +1163,7 @@ void Config::SaveDebuggingValues() { WriteBasicSetting(Settings::values.disable_macro_jit); WriteBasicSetting(Settings::values.enable_all_controllers); WriteBasicSetting(Settings::values.create_crash_dumps); + WriteBasicSetting(Settings::values.perform_vulkan_check); qt_config->endGroup(); } diff --git a/src/yuzu/configuration/configure_debug.cpp b/src/yuzu/configuration/configure_debug.cpp index 622808e94..dacc75a20 100644 --- a/src/yuzu/configuration/configure_debug.cpp +++ b/src/yuzu/configuration/configure_debug.cpp @@ -77,6 +77,7 @@ void ConfigureDebug::SetConfiguration() { ui->disable_loop_safety_checks->setChecked( Settings::values.disable_shader_loop_safety_checks.GetValue()); ui->extended_logging->setChecked(Settings::values.extended_logging.GetValue()); + ui->perform_vulkan_check->setChecked(Settings::values.perform_vulkan_check.GetValue()); #ifdef YUZU_USE_QT_WEB_ENGINE ui->disable_web_applet->setChecked(UISettings::values.disable_web_applet.GetValue()); @@ -117,6 +118,7 @@ void ConfigureDebug::ApplyConfiguration() { ui->disable_loop_safety_checks->isChecked(); Settings::values.disable_macro_jit = ui->disable_macro_jit->isChecked(); Settings::values.extended_logging = ui->extended_logging->isChecked(); + Settings::values.perform_vulkan_check = ui->perform_vulkan_check->isChecked(); UISettings::values.disable_web_applet = ui->disable_web_applet->isChecked(); Debugger::ToggleConsole(); Common::Log::Filter filter; diff --git a/src/yuzu/configuration/configure_debug.ui b/src/yuzu/configuration/configure_debug.ui index 314d47af5..102c8c66c 100644 --- a/src/yuzu/configuration/configure_debug.ui +++ b/src/yuzu/configuration/configure_debug.ui @@ -313,6 +313,16 @@ </property> </widget> </item> + <item row="3" column="0"> + <widget class="QCheckBox" name="perform_vulkan_check"> + <property name="toolTip"> + <string>Enables yuzu to check for a working Vulkan environment when the program starts up. Disable this if this is causing issues with external programs seeing yuzu.</string> + </property> + <property name="text"> + <string>Perform Startup Vulkan Check</string> + </property> + </widget> + </item> </layout> </widget> </item> diff --git a/src/yuzu/configuration/input_profiles.cpp b/src/yuzu/configuration/input_profiles.cpp index 807afbeb2..9bb69cab1 100644 --- a/src/yuzu/configuration/input_profiles.cpp +++ b/src/yuzu/configuration/input_profiles.cpp @@ -67,6 +67,8 @@ std::vector<std::string> InputProfiles::GetInputProfileNames() { profile_names.push_back(profile_name); } + std::stable_sort(profile_names.begin(), profile_names.end()); + return profile_names; } diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index 8c624427f..e2c2b9292 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -4086,7 +4086,8 @@ int main(int argc, char* argv[]) { } #endif - if (StartupChecks(argv[0], &has_broken_vulkan)) { + if (StartupChecks(argv[0], &has_broken_vulkan, + Settings::values.perform_vulkan_check.GetValue())) { return 0; } diff --git a/src/yuzu/startup_checks.cpp b/src/yuzu/startup_checks.cpp index 29b87da05..fc2693f9d 100644 --- a/src/yuzu/startup_checks.cpp +++ b/src/yuzu/startup_checks.cpp @@ -57,7 +57,7 @@ bool CheckEnvVars(bool* is_child) { return false; } -bool StartupChecks(const char* arg0, bool* has_broken_vulkan) { +bool StartupChecks(const char* arg0, bool* has_broken_vulkan, bool perform_vulkan_check) { #ifdef _WIN32 // Set the startup variable for child processes const bool env_var_set = SetEnvironmentVariableA(STARTUP_CHECK_ENV_VAR, ENV_VAR_ENABLED_TEXT); @@ -67,29 +67,32 @@ bool StartupChecks(const char* arg0, bool* has_broken_vulkan) { return false; } - PROCESS_INFORMATION process_info; - std::memset(&process_info, '\0', sizeof(process_info)); - - if (!SpawnChild(arg0, &process_info, 0)) { - return false; - } - - // Wait until the processs exits and get exit code from it - WaitForSingleObject(process_info.hProcess, INFINITE); - DWORD exit_code = STILL_ACTIVE; - const int err = GetExitCodeProcess(process_info.hProcess, &exit_code); - if (err == 0) { - std::fprintf(stderr, "GetExitCodeProcess failed with error %d\n", GetLastError()); - } - - // Vulkan is broken if the child crashed (return value is not zero) - *has_broken_vulkan = (exit_code != 0); - - if (CloseHandle(process_info.hProcess) == 0) { - std::fprintf(stderr, "CloseHandle failed with error %d\n", GetLastError()); - } - if (CloseHandle(process_info.hThread) == 0) { - std::fprintf(stderr, "CloseHandle failed with error %d\n", GetLastError()); + if (perform_vulkan_check) { + // Spawn child process that performs Vulkan check + PROCESS_INFORMATION process_info; + std::memset(&process_info, '\0', sizeof(process_info)); + + if (!SpawnChild(arg0, &process_info, 0)) { + return false; + } + + // Wait until the processs exits and get exit code from it + WaitForSingleObject(process_info.hProcess, INFINITE); + DWORD exit_code = STILL_ACTIVE; + const int err = GetExitCodeProcess(process_info.hProcess, &exit_code); + if (err == 0) { + std::fprintf(stderr, "GetExitCodeProcess failed with error %d\n", GetLastError()); + } + + // Vulkan is broken if the child crashed (return value is not zero) + *has_broken_vulkan = (exit_code != 0); + + if (CloseHandle(process_info.hProcess) == 0) { + std::fprintf(stderr, "CloseHandle failed with error %d\n", GetLastError()); + } + if (CloseHandle(process_info.hThread) == 0) { + std::fprintf(stderr, "CloseHandle failed with error %d\n", GetLastError()); + } } if (!SetEnvironmentVariableA(STARTUP_CHECK_ENV_VAR, nullptr)) { @@ -98,26 +101,28 @@ bool StartupChecks(const char* arg0, bool* has_broken_vulkan) { } #elif defined(YUZU_UNIX) - const pid_t pid = fork(); - if (pid == 0) { - CheckVulkan(); - return true; - } else if (pid == -1) { - const int err = errno; - std::fprintf(stderr, "fork failed with error %d\n", err); - return false; - } - - // Get exit code from child process - int status; - const int r_val = wait(&status); - if (r_val == -1) { - const int err = errno; - std::fprintf(stderr, "wait failed with error %d\n", err); - return false; + if (perform_vulkan_check) { + const pid_t pid = fork(); + if (pid == 0) { + CheckVulkan(); + return true; + } else if (pid == -1) { + const int err = errno; + std::fprintf(stderr, "fork failed with error %d\n", err); + return false; + } + + // Get exit code from child process + int status; + const int r_val = wait(&status); + if (r_val == -1) { + const int err = errno; + std::fprintf(stderr, "wait failed with error %d\n", err); + return false; + } + // Vulkan is broken if the child crashed (return value is not zero) + *has_broken_vulkan = (status != 0); } - // Vulkan is broken if the child crashed (return value is not zero) - *has_broken_vulkan = (status != 0); #endif return false; } diff --git a/src/yuzu/startup_checks.h b/src/yuzu/startup_checks.h index f2fc2d9d4..d8e563be6 100644 --- a/src/yuzu/startup_checks.h +++ b/src/yuzu/startup_checks.h @@ -13,7 +13,7 @@ constexpr char ENV_VAR_ENABLED_TEXT[] = "ON"; void CheckVulkan(); bool CheckEnvVars(bool* is_child); -bool StartupChecks(const char* arg0, bool* has_broken_vulkan); +bool StartupChecks(const char* arg0, bool* has_broken_vulkan, bool perform_vulkan_check); #ifdef _WIN32 bool SpawnChild(const char* arg0, PROCESS_INFORMATION* pi, int flags); |