mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-19 18:36:53 +02:00
Add startup splash screen
This commit is contained in:
@@ -54,6 +54,7 @@ struct ConfigOptions {
|
||||
int32_t gpu_index = -1;
|
||||
bool fullscreen_enabled = false;
|
||||
bool hide_cursor_enabled = false;
|
||||
bool splash_enabled = true;
|
||||
bool vr_enabled = false;
|
||||
bool amd_cpu_enabled = false;
|
||||
uint32_t vblank_frequency = 60;
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include "common/subsystems.h"
|
||||
#include "common/systemInfo.h"
|
||||
#include "common/threads.h"
|
||||
#include "graphics/presentation/systemOverlay.h"
|
||||
#include "graphics/presentation/window.h"
|
||||
#include "kernel/fileSystem.h"
|
||||
#include "kernel/memory.h"
|
||||
@@ -138,6 +139,9 @@ static void Init(const Config::ConfigOptions& cfg, const std::filesystem::path&
|
||||
Libs::LibKernel::Memory::SetFlexibleMemorySize(flexible_memory_size);
|
||||
}
|
||||
}
|
||||
if (cfg.splash_enabled) {
|
||||
Libs::Graphics::InitializeSplashScreen(param_json.parent_path());
|
||||
}
|
||||
|
||||
// Initialization order is explicit; destruction is automatic and reversed.
|
||||
subsystems.Initialize<Loader::Timer::Lifecycle>();
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
#include "graphics/presentation/ddsImage.h"
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/file.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "imgui_impl_vulkan.h"
|
||||
|
||||
#include <array>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
DdsImage::~DdsImage() {
|
||||
if (m_descriptor != VK_NULL_HANDLE) {
|
||||
ImGui_ImplVulkan_RemoveTexture(m_descriptor);
|
||||
}
|
||||
m_graphics.device.destroyImageView(m_view, nullptr);
|
||||
if (m_image.image != nullptr) {
|
||||
m_graphics.DeleteImage(m_image);
|
||||
}
|
||||
if (m_staging != VK_NULL_HANDLE) {
|
||||
vmaDestroyBuffer(m_graphics.allocator, m_staging, m_staging_allocation);
|
||||
}
|
||||
}
|
||||
|
||||
bool DdsImage::Load(const std::filesystem::path& path) {
|
||||
EXIT_IF(m_image.image != nullptr || m_staging != VK_NULL_HANDLE);
|
||||
Common::File file(path, Common::File::Mode::Read);
|
||||
if (file.IsInvalid()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::array<uint32_t, 37> header {};
|
||||
uint32_t read = 0;
|
||||
file.Read(header.data(), sizeof(header), &read);
|
||||
const auto width = header[4];
|
||||
const auto height = header[3];
|
||||
if (read != sizeof(header) || header[0] != 0x20534444 || header[1] != 124 || header[19] != 32 ||
|
||||
header[20] != 4 || header[21] != 0x30315844 || header[32] != 98 || header[33] != 3 ||
|
||||
header[34] != 0 || header[35] != 1 || header[6] != 0 || header[7] != 1 || header[28] != 0 ||
|
||||
width == 0 || height == 0 || width > 3840 || height > 2160) {
|
||||
return false;
|
||||
}
|
||||
const uint32_t size = ((width + 3) / 4) * ((height + 3) / 4) * 16;
|
||||
if (file.Size() != sizeof(header) + size) {
|
||||
return false;
|
||||
}
|
||||
|
||||
constexpr auto format = vk::Format::eBc7UnormBlock;
|
||||
constexpr auto features = vk::FormatFeatureFlagBits::eSampledImage |
|
||||
vk::FormatFeatureFlagBits::eSampledImageFilterLinear |
|
||||
vk::FormatFeatureFlagBits::eTransferDst;
|
||||
if ((m_graphics.GetFormatProperties(format).optimalTilingFeatures & features) != features) {
|
||||
LOGF("[Splash] host does not support BC7 textures\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
vk::BufferCreateInfo buffer {};
|
||||
buffer.size = size;
|
||||
buffer.usage = vk::BufferUsageFlagBits::eTransferSrc;
|
||||
VmaAllocationCreateInfo allocation {};
|
||||
allocation.usage = VMA_MEMORY_USAGE_AUTO_PREFER_HOST;
|
||||
allocation.flags =
|
||||
VMA_ALLOCATION_CREATE_MAPPED_BIT | VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT;
|
||||
VmaAllocationInfo mapped {};
|
||||
if (vmaCreateBuffer(m_graphics.allocator, static_cast<const VkBufferCreateInfo*>(buffer),
|
||||
&allocation, &m_staging, &m_staging_allocation, &mapped) != VK_SUCCESS) {
|
||||
LOGF("[Splash] could not allocate DDS upload buffer\n");
|
||||
return false;
|
||||
}
|
||||
file.Read(mapped.pMappedData, size, &read);
|
||||
if (read != size) {
|
||||
return false;
|
||||
}
|
||||
RequireVulkanSuccess(static_cast<vk::Result>(vmaFlushAllocation(m_graphics.allocator,
|
||||
m_staging_allocation, 0, size)),
|
||||
"flush splash image upload");
|
||||
|
||||
vk::ImageCreateInfo image {};
|
||||
image.imageType = vk::ImageType::e2D;
|
||||
image.extent = {width, height, 1};
|
||||
image.mipLevels = 1;
|
||||
image.arrayLayers = 1;
|
||||
image.format = format;
|
||||
image.tiling = vk::ImageTiling::eOptimal;
|
||||
image.usage = vk::ImageUsageFlagBits::eTransferDst | vk::ImageUsageFlagBits::eSampled;
|
||||
image.samples = vk::SampleCountFlagBits::e1;
|
||||
if (!m_graphics.CreateImage(image, m_image)) {
|
||||
LOGF("[Splash] could not allocate DDS image\n");
|
||||
return false;
|
||||
}
|
||||
vk::ImageViewCreateInfo view {};
|
||||
view.image = m_image.image;
|
||||
view.viewType = vk::ImageViewType::e2D;
|
||||
view.format = format;
|
||||
view.subresourceRange = {vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1};
|
||||
RequireVulkanSuccess(m_graphics.device.createImageView(&view, nullptr, &m_view),
|
||||
"create splash image view");
|
||||
m_descriptor = ImGui_ImplVulkan_AddTexture(static_cast<VkImageView>(m_view),
|
||||
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
|
||||
return true;
|
||||
}
|
||||
|
||||
void DdsImage::Upload(vk::CommandBuffer command) {
|
||||
if (m_image.state.layout == vk::ImageLayout::eShaderReadOnlyOptimal) {
|
||||
return;
|
||||
}
|
||||
vk::ImageMemoryBarrier barrier {};
|
||||
barrier.dstAccessMask = vk::AccessFlagBits::eTransferWrite;
|
||||
barrier.oldLayout = vk::ImageLayout::eUndefined;
|
||||
barrier.newLayout = vk::ImageLayout::eTransferDstOptimal;
|
||||
barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
barrier.image = m_image.image;
|
||||
barrier.subresourceRange = {vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1};
|
||||
command.pipelineBarrier(vk::PipelineStageFlagBits::eTopOfPipe,
|
||||
vk::PipelineStageFlagBits::eTransfer, {}, 0, nullptr, 0, nullptr, 1,
|
||||
&barrier);
|
||||
vk::BufferImageCopy copy {};
|
||||
copy.imageSubresource = {vk::ImageAspectFlagBits::eColor, 0, 0, 1};
|
||||
copy.imageExtent = m_image.extent;
|
||||
command.copyBufferToImage(m_staging, m_image.image, vk::ImageLayout::eTransferDstOptimal, 1,
|
||||
©);
|
||||
barrier.srcAccessMask = vk::AccessFlagBits::eTransferWrite;
|
||||
barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead;
|
||||
barrier.oldLayout = vk::ImageLayout::eTransferDstOptimal;
|
||||
barrier.newLayout = vk::ImageLayout::eShaderReadOnlyOptimal;
|
||||
command.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer,
|
||||
vk::PipelineStageFlagBits::eFragmentShader, {}, 0, nullptr, 0, nullptr,
|
||||
1, &barrier);
|
||||
m_image.state.layout = vk::ImageLayout::eShaderReadOnlyOptimal;
|
||||
}
|
||||
|
||||
ImTextureRef DdsImage::Texture() const {
|
||||
return ImTextureRef {static_cast<ImTextureID>(reinterpret_cast<uintptr_t>(m_descriptor))};
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
@@ -0,0 +1,32 @@
|
||||
#ifndef EMULATOR_SRC_GRAPHICS_PRESENTATION_DDSIMAGE_H_
|
||||
#define EMULATOR_SRC_GRAPHICS_PRESENTATION_DDSIMAGE_H_
|
||||
|
||||
#include "graphics/host_gpu/graphicContext.h"
|
||||
#include "imgui.h"
|
||||
|
||||
#include <filesystem>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
class DdsImage final {
|
||||
public:
|
||||
explicit DdsImage(GraphicContext& graphics): m_graphics(graphics) {}
|
||||
~DdsImage();
|
||||
KYTY_CLASS_NO_COPY(DdsImage);
|
||||
|
||||
bool Load(const std::filesystem::path& path);
|
||||
void Upload(vk::CommandBuffer command);
|
||||
[[nodiscard]] ImTextureRef Texture() const;
|
||||
|
||||
private:
|
||||
GraphicContext& m_graphics;
|
||||
VulkanImage m_image;
|
||||
vk::ImageView m_view = nullptr;
|
||||
VkDescriptorSet m_descriptor = VK_NULL_HANDLE;
|
||||
VkBuffer m_staging = VK_NULL_HANDLE;
|
||||
VmaAllocation m_staging_allocation = nullptr;
|
||||
};
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
#endif // EMULATOR_SRC_GRAPHICS_PRESENTATION_DDSIMAGE_H_
|
||||
@@ -3,9 +3,12 @@
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/emulatorConfig.h"
|
||||
#include "common/file.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/stringUtils.h"
|
||||
#include "graphics/host_gpu/graphicContext.h"
|
||||
#include "graphics/presentation/ddsImage.h"
|
||||
#include "imgui.h"
|
||||
#include "imgui_internal.h"
|
||||
#include "imgui_impl_vulkan.h"
|
||||
@@ -13,6 +16,7 @@
|
||||
#include "libs/dialog.h"
|
||||
#include "libs/ime.h"
|
||||
#include "libs/imeDialog.h"
|
||||
#include "loader/systemContent.h"
|
||||
#include "stb_image.h"
|
||||
|
||||
#include <algorithm>
|
||||
@@ -23,6 +27,7 @@
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <deque>
|
||||
#include <fmt/format.h>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
@@ -37,6 +42,8 @@ namespace Libs::Graphics {
|
||||
|
||||
namespace {
|
||||
|
||||
using Clock = std::chrono::steady_clock;
|
||||
|
||||
namespace CoreIme = Libs::Ime;
|
||||
namespace DialogIme = Libs::Dialog::ImeDialog;
|
||||
namespace SystemDialog = Libs::Dialog::SystemDialog;
|
||||
@@ -194,6 +201,10 @@ struct TrophyNotification {
|
||||
std::mutex g_trophy_notification_mutex;
|
||||
std::deque<TrophyNotification> g_trophy_notifications;
|
||||
std::atomic<bool> g_trophy_notification_active {false};
|
||||
std::array<std::filesystem::path, 2> g_splash_paths;
|
||||
std::atomic<bool> g_splash_active {false};
|
||||
std::atomic<Clock::duration::rep> g_splash_hidden_at {0};
|
||||
bool g_splash_fast = false;
|
||||
|
||||
std::vector<std::string_view> WrapTrophyTitle(std::string_view text, float font_size, float width) {
|
||||
std::vector<std::string_view> lines;
|
||||
@@ -464,6 +475,29 @@ void CheckVulkanResult(VkResult result) {
|
||||
|
||||
} // namespace
|
||||
|
||||
void InitializeSplashScreen(const std::filesystem::path& sce_sys) {
|
||||
const auto language = Config::GetConsoleLanguage();
|
||||
for (size_t i = 0; i < g_splash_paths.size(); ++i) {
|
||||
auto& path = g_splash_paths[i];
|
||||
path = sce_sys / fmt::format("pic{}_{:02}.dds", i + 1, language);
|
||||
if (!Common::File::IsFileExisting(path)) {
|
||||
path = sce_sys / fmt::format("pic{}.dds", i + 1);
|
||||
}
|
||||
}
|
||||
int32_t attribute = 0;
|
||||
Loader::SystemContentParamSfoGetInt("ATTRIBUTE3", &attribute);
|
||||
g_splash_fast = (attribute & 0x1000) != 0;
|
||||
g_splash_hidden_at.store(0, std::memory_order_release);
|
||||
g_splash_active.store(Common::File::IsFileExisting(g_splash_paths[0]) &&
|
||||
Common::File::IsFileExisting(g_splash_paths[1]),
|
||||
std::memory_order_release);
|
||||
}
|
||||
|
||||
void HideSplashScreen() {
|
||||
Clock::duration::rep expected = 0;
|
||||
g_splash_hidden_at.compare_exchange_strong(expected, Clock::now().time_since_epoch().count());
|
||||
}
|
||||
|
||||
void InitializeSystemOverlayInput(SDL_Window* window) {
|
||||
EXIT_IF(window == nullptr);
|
||||
const Uint32 type = SDL_RegisterEvents(1);
|
||||
@@ -481,6 +515,7 @@ void InitializeSystemOverlayInput(SDL_Window* window) {
|
||||
}
|
||||
|
||||
void ShutdownSystemOverlayInput() {
|
||||
g_splash_active.store(false, std::memory_order_release);
|
||||
CoreIme::SetVisibilityCallback(nullptr);
|
||||
DialogIme::SetVisibilityCallback(nullptr);
|
||||
SystemDialog::SetVisibilityCallback(nullptr);
|
||||
@@ -515,8 +550,10 @@ SystemOverlayVisualState GetSystemOverlayVisualState() noexcept {
|
||||
const auto dialog = DialogIme::GetVisualState();
|
||||
const auto system = SystemDialog::GetVisualState();
|
||||
const bool trophy_active = g_trophy_notification_active.load(std::memory_order_acquire);
|
||||
return {core.active || dialog.active || system.active || trophy_active,
|
||||
core.revision + dialog.revision + system.revision + (trophy_active ? 1u : 0u)};
|
||||
const bool splash = g_splash_active.load(std::memory_order_acquire);
|
||||
return {core.active || dialog.active || system.active || trophy_active || splash,
|
||||
core.revision + dialog.revision + system.revision + (trophy_active ? 1u : 0u) +
|
||||
(splash ? 2u : 0u)};
|
||||
}
|
||||
|
||||
bool ProcessSystemOverlayInput(const SDL_Event& event) {
|
||||
@@ -656,8 +693,6 @@ void NotifyTrophyUnlocked(std::string_view name, int32_t grade,
|
||||
}
|
||||
|
||||
struct SystemOverlay::Impl {
|
||||
using Clock = std::chrono::steady_clock;
|
||||
|
||||
explicit Impl(GraphicContext& context): graphics(context) {}
|
||||
|
||||
~Impl() {
|
||||
@@ -770,6 +805,65 @@ struct SystemOverlay::Impl {
|
||||
return texture;
|
||||
}
|
||||
|
||||
void FinishSplash() {
|
||||
g_splash_active.store(false, std::memory_order_release);
|
||||
if (splash_dds[0] || splash_dds[1]) {
|
||||
Common::LockGuard lock(graphics.queue_mutex);
|
||||
RequireVulkanSuccess(graphics.queue.waitIdle(), "retire splash images");
|
||||
splash_dds = {};
|
||||
}
|
||||
}
|
||||
|
||||
void DrawSplash(vk::Extent2D frame_extent) {
|
||||
if (!g_splash_active.load(std::memory_order_acquire)) {
|
||||
return;
|
||||
}
|
||||
for (size_t i = 0; i < g_splash_paths.size(); ++i) {
|
||||
if (splash_dds[i]) {
|
||||
continue;
|
||||
}
|
||||
const auto& path = g_splash_paths[i];
|
||||
auto image = std::make_unique<DdsImage>(graphics);
|
||||
if (!image->Load(path)) {
|
||||
LOGF("[Splash] could not load %s\n", path.string().c_str());
|
||||
FinishSplash();
|
||||
return;
|
||||
}
|
||||
splash_dds[i] = std::move(image);
|
||||
}
|
||||
const auto now = Clock::now();
|
||||
if (splash_born == Clock::time_point {}) {
|
||||
splash_born = now;
|
||||
}
|
||||
const auto hidden = g_splash_hidden_at.load(std::memory_order_acquire);
|
||||
const float fade =
|
||||
hidden == 0
|
||||
? 0.0f
|
||||
: std::chrono::duration<float>(now.time_since_epoch() - Clock::duration {hidden})
|
||||
.count() /
|
||||
0.3f;
|
||||
const float alpha = 1.0f - std::clamp(fade, 0.0f, 1.0f);
|
||||
if (alpha == 0.0f) {
|
||||
FinishSplash();
|
||||
return;
|
||||
}
|
||||
const float age = std::chrono::duration<float>(now - splash_born).count();
|
||||
const float logo = g_splash_fast ? 1.0f : std::clamp((age - 1.4f) / 2.2f, 0.0f, 1.0f);
|
||||
const ImVec2 screen {static_cast<float>(frame_extent.width),
|
||||
static_cast<float>(frame_extent.height)};
|
||||
const float fit = std::min(screen.x / 3840.0f, screen.y / 2160.0f);
|
||||
const ImVec2 size {3840.0f * fit, 2160.0f * fit};
|
||||
const ImVec2 begin {(screen.x - size.x) * 0.5f, (screen.y - size.y) * 0.5f};
|
||||
const ImVec2 end {begin.x + size.x, begin.y + size.y};
|
||||
auto* draw = ImGui::GetBackgroundDrawList();
|
||||
draw->AddRectFilled({0, 0}, screen, IM_COL32(0, 0, 0, static_cast<int>(255.0f * alpha)));
|
||||
for (size_t i = 0; i < g_splash_paths.size(); ++i) {
|
||||
draw->AddImage(
|
||||
splash_dds[i]->Texture(), begin, end, {0, 0}, {1, 1},
|
||||
IM_COL32(255, 255, 255, static_cast<int>(255.0f * alpha * (i == 0 ? 1.0f : logo))));
|
||||
}
|
||||
}
|
||||
|
||||
void EnsureVulkan(vk::Format format, uint32_t image_count) {
|
||||
EnsureContext();
|
||||
if (vulkan_initialized) {
|
||||
@@ -1215,7 +1309,8 @@ struct SystemOverlay::Impl {
|
||||
OverlaySnapshot snapshot;
|
||||
const bool has_overlay = GetOverlaySnapshot(&snapshot);
|
||||
const auto now = Clock::now();
|
||||
if (!has_overlay && !g_trophy_notification_active.load(std::memory_order_acquire)) {
|
||||
if (!has_overlay && !g_trophy_notification_active.load(std::memory_order_acquire) &&
|
||||
!g_splash_active.load(std::memory_order_acquire)) {
|
||||
return false;
|
||||
}
|
||||
const auto prepared_session = snapshot.session;
|
||||
@@ -1245,12 +1340,9 @@ struct SystemOverlay::Impl {
|
||||
last_frame = now;
|
||||
ImGui_ImplVulkan_NewFrame();
|
||||
ImGui::NewFrame();
|
||||
DrawSplash(frame_extent);
|
||||
GetOverlaySnapshot(&snapshot);
|
||||
if (snapshot.session != prepared_session) {
|
||||
ImGui::EndFrame();
|
||||
return false;
|
||||
}
|
||||
if (has_overlay) {
|
||||
if (has_overlay && snapshot.session == prepared_session) {
|
||||
if (snapshot.session.kind == OverlayKind::Dialog) {
|
||||
DrawDialog(snapshot.dialog, frame_extent);
|
||||
} else {
|
||||
@@ -1264,6 +1356,11 @@ struct SystemOverlay::Impl {
|
||||
}
|
||||
|
||||
void Record(vk::CommandBuffer command, vk::ImageView target) {
|
||||
for (const auto& image: splash_dds) {
|
||||
if (image) {
|
||||
image->Upload(command);
|
||||
}
|
||||
}
|
||||
vk::RenderingAttachmentInfo color {};
|
||||
color.sType = vk::StructureType::eRenderingAttachmentInfo;
|
||||
color.imageView = target;
|
||||
@@ -1290,6 +1387,7 @@ struct SystemOverlay::Impl {
|
||||
return;
|
||||
}
|
||||
ImGui::SetCurrentContext(imgui_context);
|
||||
splash_dds = {};
|
||||
ImGui_ImplVulkan_Shutdown();
|
||||
vulkan_initialized = false;
|
||||
}
|
||||
@@ -1309,6 +1407,8 @@ struct SystemOverlay::Impl {
|
||||
std::optional<TrophyNotification> trophy_notification;
|
||||
Clock::time_point trophy_notification_born;
|
||||
std::vector<std::unique_ptr<ImTextureData>> retired_trophy_images;
|
||||
std::array<std::unique_ptr<DdsImage>, 2> splash_dds;
|
||||
Clock::time_point splash_born;
|
||||
SDL_AudioStream* trophy_sound_stream = nullptr;
|
||||
uint8_t* trophy_sound_data = nullptr;
|
||||
uint32_t trophy_sound_size = 0;
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "graphics/host_gpu/vulkanCommon.h"
|
||||
|
||||
#include <cstddef>
|
||||
#include <filesystem>
|
||||
#include <memory>
|
||||
#include <span>
|
||||
#include <string_view>
|
||||
@@ -21,6 +22,8 @@ struct SystemOverlayVisualState {
|
||||
uint64_t revision;
|
||||
};
|
||||
|
||||
void InitializeSplashScreen(const std::filesystem::path& sce_sys);
|
||||
void HideSplashScreen();
|
||||
void InitializeSystemOverlayInput(SDL_Window* window);
|
||||
void ShutdownSystemOverlayInput();
|
||||
bool ProcessSystemOverlayInput(const SDL_Event& event);
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include "common/emulatorConfig.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/stringUtils.h"
|
||||
#include "graphics/presentation/systemOverlay.h"
|
||||
#include "libs/dialog.h"
|
||||
#include "libs/errno.h"
|
||||
#include "libs/libs.h"
|
||||
@@ -94,7 +95,7 @@ struct SystemServiceHdrToneMapLuminance {
|
||||
|
||||
static int KYTY_SYSV_ABI SystemServiceHideSplashScreen() {
|
||||
PRINT_NAME();
|
||||
|
||||
Graphics::HideSplashScreen();
|
||||
return OK;
|
||||
}
|
||||
|
||||
|
||||
@@ -63,6 +63,7 @@ static void PrintUsage() {
|
||||
::printf(" --fullscreen Run in borderless desktop fullscreen.\n");
|
||||
::printf(
|
||||
" --hide-cursor Hide the cursor after 2 s idle. Default: off.\n");
|
||||
::printf(" --no-splash Disable the startup splash screen.\n");
|
||||
::printf(" --vr Enable the virtual VR headset.\n");
|
||||
::printf(" --amd-cpu Apply AMD CPU instruction patches.\n");
|
||||
::printf(" --vblank-frequency <num> Virtual vblank frequency. Default: 60.\n");
|
||||
@@ -219,6 +220,11 @@ static bool ParseArgs(int argc, char* argv[], RunOptions& options, bool& show_he
|
||||
continue;
|
||||
}
|
||||
|
||||
if (arg == "--no-splash") {
|
||||
options.config.splash_enabled = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (arg == "--vr") {
|
||||
options.config.vr_enabled = true;
|
||||
continue;
|
||||
|
||||
Reference in New Issue
Block a user