// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later #include "common/microprofile.h" #include "common/scope_exit.h" #include "common/settings.h" #include "core/core.h" #include "core/core_timing.h" #include "core/hle/service/nvdrv/devices/nvdisp_disp0.h" #include "core/hle/service/nvdrv/nvdrv.h" #include "core/hle/service/nvdrv/nvdrv_interface.h" #include "core/hle/service/nvnflinger/fb_share_buffer_manager.h" #include "core/hle/service/nvnflinger/hardware_composer.h" #include "core/hle/service/nvnflinger/hos_binder_driver.h" #include "core/hle/service/nvnflinger/hos_binder_driver_server.h" #include "core/hle/service/nvnflinger/nvnflinger.h" #include "core/hle/service/server_manager.h" #include "core/hle/service/sm/sm.h" #include "core/hle/service/vi/display/vi_display.h" #include "core/hle/service/vi/layer/vi_layer.h" #include "core/hle/service/vi/vi_results.h" namespace Service::Nvnflinger { constexpr auto frame_ns = std::chrono::nanoseconds{1000000000 / 60}; void Nvnflinger::SplitVSync(std::stop_token stop_token) { system.RegisterHostThread(); std::string name = "VSyncThread"; MicroProfileOnThreadCreate(name.c_str()); // Cleanup SCOPE_EXIT({ MicroProfileOnThreadExit(); }); Common::SetCurrentThreadName(name.c_str()); Common::SetCurrentThreadPriority(Common::ThreadPriority::High); while (!stop_token.stop_requested()) { vsync_signal.Wait(); if (system.IsShuttingDown()) { ShutdownLayers(); return; } const auto lock_guard = Lock(); if (!is_abandoned) { Compose(); } } } Nvnflinger::Nvnflinger(Core::System& system_, HosBinderDriverServer& hos_binder_driver_server_) : system(system_), service_context(system_, "nvnflinger"), hos_binder_driver_server(hos_binder_driver_server_) { displays.emplace_back(0, "Default", hos_binder_driver_server, service_context, system); displays.emplace_back(1, "External", hos_binder_driver_server, service_context, system); displays.emplace_back(2, "Edid", hos_binder_driver_server, service_context, system); displays.emplace_back(3, "Internal", hos_binder_driver_server, service_context, system); displays.emplace_back(4, "Null", hos_binder_driver_server, service_context, system); guard = std::make_shared(); nvdrv = system.ServiceManager().GetService("nvdrv:s", true)->GetModule(); disp_fd = nvdrv->Open("/dev/nvdisp_disp0", {}); // Schedule the screen composition events multi_composition_event = Core::Timing::CreateEvent( "ScreenComposition", [this](s64 time, std::chrono::nanoseconds ns_late) -> std::optional { vsync_signal.Set(); return std::chrono::nanoseconds(GetNextTicks()); }); single_composition_event = Core::Timing::CreateEvent( "ScreenComposition", [this](s64 time, std::chrono::nanoseconds ns_late) -> std::optional { const auto lock_guard = Lock(); Compose(); return std::chrono::nanoseconds(GetNextTicks()); }); if (system.IsMulticore()) { system.CoreTiming().ScheduleLoopingEvent(frame_ns, frame_ns, multi_composition_event); vsync_thread = std::jthread([this](std::stop_token token) { SplitVSync(token); }); } else { system.CoreTiming().ScheduleLoopingEvent(frame_ns, frame_ns, single_composition_event); } } Nvnflinger::~Nvnflinger() { if (system.IsMulticore()) { system.CoreTiming().UnscheduleEvent(multi_composition_event); vsync_thread.request_stop(); vsync_signal.Set(); } else { system.CoreTiming().UnscheduleEvent(single_composition_event); } ShutdownLayers(); if (nvdrv) { nvdrv->Close(disp_fd); } } void Nvnflinger::ShutdownLayers() { // Abandon consumers. const auto lock_guard = Lock(); for (auto& display : displays) { display.Abandon(); } is_abandoned = true; } std::optional Nvnflinger::OpenDisplay(std::string_view name) { const auto lock_guard = Lock(); LOG_DEBUG(Service_Nvnflinger, "Opening \"{}\" display", name); const auto itr = std::find_if(displays.begin(), displays.end(), [&](const VI::Display& display) { return display.GetName() == name; }); if (itr == displays.end()) { return std::nullopt; } return itr->GetID(); } bool Nvnflinger::CloseDisplay(u64 display_id) { const auto lock_guard = Lock(); auto* const display = FindDisplay(display_id); if (display == nullptr) { return false; } display->Reset(); return true; } std::optional Nvnflinger::CreateLayer(u64 display_id, LayerBlending blending) { const auto lock_guard = Lock(); auto* const display = FindDisplay(display_id); if (display == nullptr) { return std::nullopt; } const u64 layer_id = next_layer_id++; CreateLayerAtId(*display, layer_id, blending); return layer_id; } void Nvnflinger::CreateLayerAtId(VI::Display& display, u64 layer_id, LayerBlending blending) { const auto buffer_id = next_buffer_queue_id++; display.CreateLayer(layer_id, buffer_id, nvdrv->container); display.FindLayer(layer_id)->SetBlending(blending); } bool Nvnflinger::OpenLayer(u64 layer_id) { const auto lock_guard = Lock(); for (auto& display : displays) { if (auto* layer = display.FindLayer(layer_id); layer) { return layer->Open(); } } return false; } bool Nvnflinger::CloseLayer(u64 layer_id) { const auto lock_guard = Lock(); for (auto& display : displays) { if (auto* layer = display.FindLayer(layer_id); layer) { return layer->Close(); } } return false; } void Nvnflinger::SetLayerVisibility(u64 layer_id, bool visible) { const auto lock_guard = Lock(); for (auto& display : displays) { if (auto* layer = display.FindLayer(layer_id); layer) { layer->SetVisibility(visible); } } } void Nvnflinger::DestroyLayer(u64 layer_id) { const auto lock_guard = Lock(); for (auto& display : displays) { display.DestroyLayer(layer_id); } } std::optional Nvnflinger::FindBufferQueueId(u64 display_id, u64 layer_id) { const auto lock_guard = Lock(); const auto* const layer = FindLayer(display_id, layer_id); if (layer == nullptr) { return std::nullopt; } return layer->GetBinderId(); } Result Nvnflinger::FindVsyncEvent(Kernel::KReadableEvent** out_vsync_event, u64 display_id) { const auto lock_guard = Lock(); auto* const display = FindDisplay(display_id); if (display == nullptr) { return VI::ResultNotFound; } *out_vsync_event = display->GetVSyncEvent(); return ResultSuccess; } VI::Display* Nvnflinger::FindDisplay(u64 display_id) { const auto itr = std::find_if(displays.begin(), displays.end(), [&](const VI::Display& display) { return display.GetID() == display_id; }); if (itr == displays.end()) { return nullptr; } return &*itr; } const VI::Display* Nvnflinger::FindDisplay(u64 display_id) const { const auto itr = std::find_if(displays.begin(), displays.end(), [&](const VI::Display& display) { return display.GetID() == display_id; }); if (itr == displays.end()) { return nullptr; } return &*itr; } VI::Layer* Nvnflinger::FindLayer(u64 display_id, u64 layer_id) { auto* const display = FindDisplay(display_id); if (display == nullptr) { return nullptr; } return display->FindLayer(layer_id); } void Nvnflinger::Compose() { for (auto& display : displays) { // Trigger vsync for this display at the end of drawing SCOPE_EXIT({ display.SignalVSyncEvent(); }); // Don't do anything for displays without layers. if (!display.HasLayers()) { continue; } if (!system.IsPoweredOn()) { return; // We are likely shutting down } auto nvdisp = nvdrv->GetDevice(disp_fd); ASSERT(nvdisp); swap_interval = display.GetComposer().ComposeLocked(&compose_speed_scale, display, *nvdisp); } } s64 Nvnflinger::GetNextTicks() const { const auto& settings = Settings::values; auto speed_scale = 1.f; if (settings.use_multi_core.GetValue()) { if (settings.use_speed_limit.GetValue()) { // Scales the speed based on speed_limit setting on MC. SC is handled by // SpeedLimiter::DoSpeedLimiting. speed_scale = 100.f / settings.speed_limit.GetValue(); } else { // Run at unlocked framerate. speed_scale = 0.01f; } } // Adjust by speed limit determined during composition. speed_scale /= compose_speed_scale; if (system.GetNVDECActive() && settings.use_video_framerate.GetValue()) { // Run at intended presentation rate during video playback. speed_scale = 1.f; } const f32 effective_fps = 60.f / static_cast(swap_interval); return static_cast(speed_scale * (1000000000.f / effective_fps)); } FbShareBufferManager& Nvnflinger::GetSystemBufferManager() { const auto lock_guard = Lock(); if (!system_buffer_manager) { system_buffer_manager = std::make_unique(system, *this, nvdrv); } return *system_buffer_manager; } void LoopProcess(Core::System& system) { const auto binder_server = std::make_shared(system); const auto surface_flinger = std::make_shared(system, *binder_server); auto server_manager = std::make_unique(system); server_manager->RegisterNamedService( "dispdrv", std::make_shared(system, binder_server, surface_flinger)); ServerManager::RunServer(std::move(server_manager)); } } // namespace Service::Nvnflinger