mirror of
https://github.com/thejerf/baijiSteamVR.git
synced 2026-10-17 21:01:42 +02:00
Compare commits
15
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
70d096f8d7 | ||
|
|
38fc6d9a95 | ||
|
|
3d27e75026 | ||
|
|
f395e45ddf | ||
|
|
14effdb34e | ||
|
|
8b4a0723fc | ||
|
|
982671b53f | ||
|
|
38cf5cf933 | ||
|
|
24634ba1bd | ||
|
|
0775c13109 | ||
|
|
6b197206e8 | ||
|
|
89dc31c6e8 | ||
|
|
46a987148d | ||
|
|
0cef1d3651 | ||
|
|
dbb7283447 |
No files matched your search
@@ -56,14 +56,24 @@
|
||||
sensor values stayed zero while the user rotated the controller. The captured trace
|
||||
did show d-pad, trigger, and stick events, so it did not reproduce total SDL input
|
||||
loss. Keep the thumbstick IR fallback; do not assume SDL sensor support.
|
||||
- Frame audio quirk (2026-10-11): AudioCommon sends stereo through Cubeb/PipeWire;
|
||||
SteamOS WirePlumber creates a `spatialize_filter_chain` for output streams when the
|
||||
built-in speaker sink is default. `node.stream.enable-spatial-audio` is a global
|
||||
setting, and the current policy has no per-application opt-out. Do not change this
|
||||
global setting for Baiji; leave the UI-position spatialization documented as a quirk
|
||||
unless Frame exposes a supported per-stream opt-out.
|
||||
- Baiji is a Steam Frame-only Dolphin cut; changes may be intentionally more
|
||||
aggressive than upstream Dolphin. This branch is temporary and can end once
|
||||
upstream incorporates its features. Inspect Baiji data only under
|
||||
`~/.config/baiji` and `~/.local/share/baiji`; ignore the separate Dolphin
|
||||
installation/config. Legacy INI filenames may remain, but Dolphin installation
|
||||
or user-data paths inside Baiji config are suspicious and must not be followed.
|
||||
- Never modify Frame/SteamOS/SteamVR system or runtime settings as part of Baiji
|
||||
development, testing, or deployment, even for temporary experiments. Treat
|
||||
`~/.config/openvr/config/steamvr.vrsettings` and other non-Baiji settings as
|
||||
read-only; keep testing changes within Baiji-owned code, binaries, and config.
|
||||
- Frame build: from the repository root run
|
||||
`INSTALL_PREFIX=/home/steamos/baiju ./scripts/compile-cross-sdk.sh`, then
|
||||
`INSTALL_PREFIX=/home/steamos/baiji ./scripts/compile-cross-sdk.sh`, then
|
||||
`./scripts/deploy-frame.sh frame`. The build uses Podman, installs the AArch64 KDE SDK
|
||||
into ignored `state/` via Flatpak when missing, rebuilds the image when
|
||||
`Containerfile.cross` changes, and stages to
|
||||
@@ -84,15 +94,15 @@
|
||||
`state/build-aarch64/Binaries/baiji`, but this uses the Ubuntu 26.04 build image
|
||||
(glibc 2.43). The Frame has glibc 2.39, so do not copy that raw executable there;
|
||||
use `compile-cross-sdk.sh` and its Frame-compatible SDK/staging output instead.
|
||||
- For a binary-only Frame debug iteration, copy
|
||||
`state/stage-frame/home/steamos/baiju/bin/baiji` to
|
||||
`frame:baiju/bin/baiji`; use `deploy-frame.sh` when staged libraries/assets also
|
||||
changed. The Big Picture shortcut launches `~/baiju/bin/baiji-vr`, which runs
|
||||
`~/baiju/bin/baiji`, so keep replacing that binary path to reuse the shortcut.
|
||||
- For binary-only Frame builds, atomically replace
|
||||
`~/baiji/bin/baiji` with `state/stage-frame/home/steamos/baiji/bin/baiji`; this is
|
||||
the emulator executable launched by `~/baiji/bin/baiji-vr` from the Big Picture /
|
||||
Gamescope shortcut. Leave `baiji-vr` in place. Use `deploy-frame.sh` when staged
|
||||
libraries/assets also changed.
|
||||
The user promoted the current Frame build to known-good on 2026-10-04 despite
|
||||
uncertainty about intermittent controller input and reported performance improvement.
|
||||
Preserve `~/baiju/bin/baiji-known-good`; only replace it on explicit user promotion.
|
||||
To restore the Big Picture shortcut to that build, copy it back to `~/baiju/bin/baiji`.
|
||||
Preserve `~/baiji/bin/baiji-known-good`; only replace it on explicit user promotion.
|
||||
To restore the Big Picture shortcut to that build, copy it back to `~/baiji/bin/baiji`.
|
||||
We cannot test headset controls directly from the development host: collaborate with
|
||||
the user on debug runs and retrieve logs over SSH.
|
||||
- On the Frame, creating `~/.config/baiji/debug-logging` makes the existing
|
||||
@@ -104,8 +114,9 @@
|
||||
- The configured SSH alias is `frame`. If it is absent on a development machine,
|
||||
ask the user to create it; it makes build transfer and device debugging much easier.
|
||||
- Steam Frame debugging guidance, especially core-dump access and processing, is at
|
||||
<https://partner.steamgames.com/doc/steamhardware/steamframe/debugging>.
|
||||
- The deploy script copies Baiji-named files under `~/baiju` on the target. CLI
|
||||
<https://partner.steamgames.com/doc/steamhardware/steamframe/debugging>; consult it
|
||||
before retrieving or processing Frame crash dumps.
|
||||
- The deploy script copies Baiji-named files under `~/baiji` on the target. CLI
|
||||
smoke tests are documented in `docs/build-runbook.md`; the GUI launcher is
|
||||
`baiji-vr`.
|
||||
- For a host Linux VR build, configure with
|
||||
@@ -124,3 +135,33 @@
|
||||
or alter unrelated existing Frame binaries, user data, or Steam shortcuts;
|
||||
follow the runbook's Baiji-only paths. Do not publish from this checkout unless
|
||||
explicitly requested.
|
||||
- This project is not particularly worried about linting. Don't worry
|
||||
about trying to run clang-format. Nor is there any practical way to
|
||||
unit test most of the work this project is doing as it is all UI work.
|
||||
|
||||
## External technical references
|
||||
|
||||
- [Steam Frame developer setup](https://partner.steamgames.com/doc/steamhardware/steamframe/setup)
|
||||
covers Developer Mode, SSH/ADB access, and recommended beta channels; consult it
|
||||
when setting up or troubleshooting access to a Frame development headset.
|
||||
- [Steam Frame OpenXR engine integrations](https://partner.steamgames.com/doc/steamhardware/steamframe/engines)
|
||||
collects platform-specific OpenXR integration guidance for supported engines.
|
||||
Start here when adapting general OpenXR behavior to the Frame runtime.
|
||||
- [Steam Frame custom engine integration](https://partner.steamgames.com/doc/steamhardware/steamframe/engines/custom)
|
||||
is the more direct reference for applications integrating OpenXR without one of
|
||||
the documented game engines; check its runtime and extension guidance for
|
||||
Frame-specific requirements.
|
||||
- [Steam Frame Input](https://partner.steamgames.com/doc/steamhardware/steamframe/input)
|
||||
documents the headset's controller/input behavior and is useful when investigating
|
||||
controller profiles, bindings, or input expectations on-device.
|
||||
- [Khronos OpenXR Tutorial](https://openxr-tutorial.com/)
|
||||
provides practical, API-level examples of initialization, graphics/swapchains,
|
||||
actions, and extensions across platforms; use it for implementation patterns, then
|
||||
verify runtime-specific support separately.
|
||||
- [Khronos OpenXR 1.1 Specification](https://registry.khronos.org/OpenXR/specs/1.1/html/xrspec.html)
|
||||
is the normative reference for API semantics, synchronization, and extension behavior;
|
||||
consult it when tutorial examples leave behavior ambiguous.
|
||||
- [Khronos OpenXR SDK Source](https://github.com/KhronosGroup/OpenXR-SDK-Source)
|
||||
contains the OpenXR loader and SDK source; use it to investigate loader behavior or
|
||||
SDK integration details, not as a substitute for checking extensions exposed by the
|
||||
Frame runtime.
|
||||
+62
-302
@@ -1,302 +1,62 @@
|
||||
# <a name="main-section-overview"></a>Dolphin Coding Style & Legal Requirements
|
||||
|
||||
- [Legal](#legal)
|
||||
- [Coding style introduction](#introduction)
|
||||
- [C++ coding style and formatting](#cpp-coding-style-and-formatting)
|
||||
- [C++ code-specific guidelines](#cpp-code-specific-guidelines)
|
||||
- [Android](#android)
|
||||
- [Help](#help)
|
||||
|
||||
# <a name="legal"></a>Legal
|
||||
|
||||
Summary:
|
||||
|
||||
- [Trade secrets](#trade-secrets)
|
||||
- [Code licensing](#code-licensing)
|
||||
|
||||
## <a name="trade-secrets"></a>Trade secrets
|
||||
|
||||
Following all relevant laws is of utmost importance for an emulation project like Dolphin.
|
||||
|
||||
If you know any confidential information related to the GameCube, Wii, or Triforce, either because you signed a non-disclosure agreement or because you looked at leaked materials, we ask that you don't contribute code to Dolphin **at all**. While accepting code from contributors who know confidential information is legal if the code is unrelated to the confidential information, we refuse to accept code from such contributors because it greatly increases our review burden and increases the legal risk we take.
|
||||
|
||||
Also, this probably goes without saying, but piracy is strictly forbidden both on GitHub and in all other Dolphin channels.
|
||||
|
||||
## <a name="code-licensing"></a>Code licensing
|
||||
|
||||
If you make any contributions to Dolphin after December 1st, 2014, you are agreeing that any code you have contributed will be licensed under the GNU GPL version 2 (or any later version).
|
||||
|
||||
# <a name="introduction"></a>Coding style introduction
|
||||
|
||||
Summary:
|
||||
|
||||
- [Aims](#intro-aims)
|
||||
- [Checking and fixing formatting issues](#intro-formatting-issues)
|
||||
|
||||
## <a name="intro-aims"></a>Aims
|
||||
|
||||
This guide is for developers who wish to contribute to the Dolphin codebase. It details how to properly style and format code for this project. This guide also offers suggestions on specific functions and other elements that may be used in code.
|
||||
|
||||
Following this guide and formatting your code as detailed will likely get your pull request merged much faster than if you don't (assuming the code itself has no mistakes).
|
||||
|
||||
This project uses clang-format 19.1 to check for common style issues. In case of conflicts between this guide and clang-format rules, the latter should be followed instead of this guide.
|
||||
|
||||
## <a name="intro-formatting-issues"></a>Checking and fixing formatting issues
|
||||
|
||||
Windows users need to be careful about line endings. Windows users should configure git to checkout UNIX-style line endings to keep clang-format simple.
|
||||
|
||||
In most cases, clang-format can and **should** be used to automatically reformat code and solve most formatting issues.
|
||||
|
||||
- To run clang-format on all staged files:
|
||||
```
|
||||
git diff --cached --name-only | grep -E '[.](cpp|h|mm)$' | xargs -I {} clang-format -i {}
|
||||
```
|
||||
|
||||
- Formatting issues can be checked for before committing with a lint script that is included with the codebase. To enable it as a pre-commit hook (assuming you are in the repository root):
|
||||
```
|
||||
ln -s ../../Tools/lint.sh .git/hooks/pre-commit
|
||||
```
|
||||
|
||||
- Alternatively, a custom git filter driver can be used to automatically and transparently reformat any changes:
|
||||
```
|
||||
git config filter.clang_format.smudge 'cat'
|
||||
git config filter.clang_format.clean 'clang-format %f'
|
||||
echo '/Source/Core/**/*.cpp filter=clang_format' >> .git/info/attributes
|
||||
echo '/Source/Core/**/*.h filter=clang_format' >> .git/info/attributes
|
||||
echo '/Source/Core/**/*.mm filter=clang_format' >> .git/info/attributes
|
||||
```
|
||||
|
||||
- Visual Studio supports automatically formatting the current document according to the clang-format configuration by pressing <kbd>Control</kbd>+<kbd>K</kbd> followed by <kbd>Control</kbd>+<kbd>D</kbd> (or selecting Edit → Advanced → Format Document). This can be used without separately installing clang-format.
|
||||
|
||||
# <a name="cpp-coding-style-and-formatting"></a>C++ coding style and formatting
|
||||
|
||||
Summary:
|
||||
|
||||
- [General](#cpp-style-general)
|
||||
- [Naming](#cpp-style-naming)
|
||||
- [Conditionals](#cpp-style-conditionals)
|
||||
- [Classes and structs](#cpp-style-classes-and-structs)
|
||||
|
||||
## <a name="cpp-style-general"></a>General
|
||||
- Try to limit lines of code to a maximum of 100 characters.
|
||||
- Note that this does not mean you should try and use all 100 characters every time you have the chance. Typically with well formatted code, you normally shouldn't hit a line count of anything over 80 or 90 characters.
|
||||
- The indentation style we use is 2 spaces per level.
|
||||
- The opening brace for namespaces, classes, functions, enums, structs, unions, conditionals, and loops go on the next line.
|
||||
- With array initializer lists and lambda expressions it is OK to keep the brace on the same line.
|
||||
- References and pointers have the ampersand or asterisk against the type name, not the variable name. Example: `int* var`, not `int *var`.
|
||||
- Don't use multi-line comments (`/* Comment text */`), use single-line comments (`// Comment text`) instead.
|
||||
- Don't collapse single line conditional or loop bodies onto the same line as its header. Put it on the next line.
|
||||
- Yes:
|
||||
|
||||
```c++
|
||||
if (condition)
|
||||
return 0;
|
||||
|
||||
while (var != 0)
|
||||
var--;
|
||||
```
|
||||
- No:
|
||||
|
||||
```c++
|
||||
if (condition) return 0;
|
||||
|
||||
while (var != 0) var--;
|
||||
```
|
||||
|
||||
## <a name="cpp-style-naming"></a>Naming
|
||||
- All class, enum, function, and struct names should be in upper CamelCase. If the name contains an abbreviation uppercase it.
|
||||
- `class SomeClassName`
|
||||
- `enum IPCCommandType`
|
||||
- All compile time constants should be fully uppercased. With constants that have more than one word in them, use an underscore to separate them.
|
||||
- `constexpr double PI = 3.14159;`
|
||||
- `constexpr int MAX_PATH = 260;`
|
||||
- All variables should be lowercase with underscores separating the individual words in the name.
|
||||
- `int this_variable_name;`
|
||||
- Please do not use [Hungarian notation](https://en.wikipedia.org/wiki/Hungarian_notation) prefixes with variables. The only exceptions to this are the variable prefixes below.
|
||||
- Global variables – `g_`
|
||||
- Class variables – `m_`
|
||||
- Static variables – `s_`
|
||||
|
||||
## <a name="cpp-style-conditionals"></a>Conditionals
|
||||
- Do not leave `else` or `else if` conditions dangling unless the `if` condition lacks braces.
|
||||
- Yes:
|
||||
|
||||
```c++
|
||||
if (condition)
|
||||
{
|
||||
// code
|
||||
}
|
||||
else
|
||||
{
|
||||
// code
|
||||
}
|
||||
```
|
||||
- Acceptable:
|
||||
|
||||
```c++
|
||||
if (condition)
|
||||
// code line
|
||||
else
|
||||
// code line
|
||||
```
|
||||
- No:
|
||||
|
||||
```c++
|
||||
if (condition)
|
||||
{
|
||||
// code
|
||||
}
|
||||
else
|
||||
// code line
|
||||
```
|
||||
|
||||
|
||||
## <a name="cpp-style-classes-and-structs"></a>Classes and structs
|
||||
- If making a [POD](https://en.wikipedia.org/wiki/Passive_data_structure) type, use a `struct` for this. Use a `class` otherwise.
|
||||
- Class layout should be in the order, `public`, `protected`, and then `private`.
|
||||
- If one or more of these sections are not needed, then simply don't include them.
|
||||
- For each of the above specified access levels, the contents of each should follow this given order: constructor, destructor, operator overloads, functions, then variables.
|
||||
- When defining the variables, define `static` variables before the non-static ones.
|
||||
|
||||
```c++
|
||||
class ExampleClass : public SomeParent
|
||||
{
|
||||
public:
|
||||
ExampleClass(int x, int y);
|
||||
|
||||
int GetX() const;
|
||||
int GetY() const;
|
||||
|
||||
protected:
|
||||
virtual void SomeProtectedFunction() = 0;
|
||||
static float s_some_variable;
|
||||
|
||||
private:
|
||||
int m_x;
|
||||
int m_y;
|
||||
};
|
||||
```
|
||||
|
||||
# <a name="cpp-code-specific-guidelines"></a>C++ code-specific guidelines
|
||||
|
||||
Summary:
|
||||
|
||||
- [General](#cpp-code-general)
|
||||
- [Headers](#cpp-code-headers)
|
||||
- [Loops](#cpp-code-loops)
|
||||
- [Functions](#cpp-code-functions)
|
||||
- [Classes and Structs](#cpp-code-classes-and-structs)
|
||||
|
||||
## <a name="cpp-code-general"></a>General
|
||||
- The codebase currently uses C++20, though not all compilers support all C++20 features.
|
||||
- See CMakeLists.txt "Enforce minimum compiler versions" for the currently supported compilers.
|
||||
- Use the [nullptr](https://en.cppreference.com/w/cpp/language/nullptr) type over the macro `NULL`.
|
||||
- If a [range-based for loop](https://en.cppreference.com/w/cpp/language/range-for) can be used instead of container iterators, use it.
|
||||
- Obviously, try not to use `goto` unless you have a *really* good reason for it.
|
||||
- If a compiler warning is found, please try and fix it.
|
||||
- Try to avoid using raw pointers (pointers allocated with `new`) as much as possible. There are cases where using a raw pointer is unavoidable, and in these situations it is OK to use them. An example of this is functions from a C library that require them. In cases where it is avoidable, the STL usually has a means to solve this (`vector`, `unique_ptr`, etc).
|
||||
- Do not use the `auto` keyword everywhere. While it's nice that the type can be determined by the compiler, it cannot be resolved at 'readtime' by the developer as easily. Use auto only in cases where it is obvious what the type being assigned is (note: 'obvious' means not having to open other files or reading the header file). Some situations where it is appropriate to use `auto` is when iterating over a `std::map` container in a foreach loop, or to shorten the length of container iterator variable declarations.
|
||||
- Do not use `using namespace [x];` in headers. Try not to use it at all if you can.
|
||||
- The preferred form of the increment and decrement operator in for-loops is prefix-form (e.g. `++var`).
|
||||
|
||||
## <a name="cpp-code-headers"></a>Headers
|
||||
- If a header is not necessary in a certain source file, remove them.
|
||||
- If you find duplicate includes of a certain header, remove it.
|
||||
- When declaring includes in a source file, make sure they follow the given pattern:
|
||||
- The header for the source file
|
||||
- Standard library headers
|
||||
- System-specific headers (these should also likely be in an `#ifdef` block unless the source file itself is system-specific).
|
||||
- Other Dolphin source file headers
|
||||
- Each of the above header sections should also be in alphabetical order
|
||||
- Project source file headers should be included in a way that is relative to the `[Dolphin Root]/Source/Core` directory.
|
||||
- This project uses `#pragma once` as header guards.
|
||||
|
||||
## <a name="cpp-code-loops"></a>Loops
|
||||
- If an infinite loop is required, do not use `for (;;)`, use `while (true)`.
|
||||
- Empty-bodied loops should use braces after their header, not a semicolon.
|
||||
- Yes: `while (condition) {}`
|
||||
- No: `while (condition);`
|
||||
- For do-while loops, place 'while' on the same line as the closing brackets
|
||||
|
||||
```c++
|
||||
do
|
||||
{
|
||||
// code
|
||||
} while (false);
|
||||
```
|
||||
|
||||
## <a name="cpp-code-functions"></a>Functions
|
||||
- If a function parameter is a pointer or reference and its value or data isn't intended to be changed, please mark that parameter as `const`.
|
||||
- Functions that specifically modify their parameters should have the respective parameter(s) marked as a pointer so that the variables being modified are syntactically obvious.
|
||||
- What not to do:
|
||||
|
||||
```c++
|
||||
template<class T>
|
||||
inline void Clamp(T& val, const T& min, const T& max)
|
||||
{
|
||||
if (val < min)
|
||||
val = min;
|
||||
else if (val > max)
|
||||
val = max;
|
||||
}
|
||||
```
|
||||
|
||||
Example call: `Clamp(var, 1000, 5000);`
|
||||
|
||||
- What to do:
|
||||
|
||||
```c++
|
||||
template<class T>
|
||||
inline void Clamp(T* val, const T& min, const T& max)
|
||||
{
|
||||
if (*val < min)
|
||||
*val = min;
|
||||
else if (*val > max)
|
||||
*val = max;
|
||||
}
|
||||
```
|
||||
|
||||
Example call: `Clamp(&var, 1000, 5000);`
|
||||
|
||||
- Class member functions that you do not want to be overridden in inheriting classes should be marked with the `final` specifier.
|
||||
|
||||
```c++
|
||||
class ClassName : ParentClass
|
||||
{
|
||||
public:
|
||||
void Update() final;
|
||||
};
|
||||
```
|
||||
|
||||
- Overridden member functions that can also be inherited should be marked with the `override` specifier to make it easier to see which functions belong to the parent class.
|
||||
|
||||
```c++
|
||||
class ClassName : ParentClass
|
||||
{
|
||||
public:
|
||||
void Update() override;
|
||||
};
|
||||
```
|
||||
|
||||
## <a name="cpp-code-classes-and-structs"></a>Classes and structs
|
||||
- Classes and structs that are not intended to be extended through inheritance should be marked with the `final` specifier.
|
||||
|
||||
```c++
|
||||
class ClassName final : ParentClass
|
||||
{
|
||||
// Class definitions
|
||||
};
|
||||
```
|
||||
|
||||
# <a name="android"></a>Android
|
||||
|
||||
If you are using Kotlin, just use the built-in official Kotlin code style.
|
||||
|
||||
To install the Java code style in Android Studio, select the gear icon in the Code Style settings as shown, select `Import Scheme...` and select `dolphin/Source/Android/code-style-java.xml`. The Code Style menu should look like this when complete. ![Code Style Window][code-style]
|
||||
|
||||
You can now select any section of code and press `Ctrl + Alt + L` to automatically format it.
|
||||
|
||||
# <a name="help"></a>Help
|
||||
If you have any questions about Dolphin's development or would like some help, Dolphin developers use `#dolphin-emu @ irc.libera.chat` to communicate. If you are new to IRC, [Libera.Chat has resources to get started chatting with IRC.](https://libera.chat/)
|
||||
|
||||
[code-style]: https://i.imgur.com/3b3UBhb.png
|
||||
# Contributing to Baiji
|
||||
|
||||
In an era of AI, the value you bring to a PR
|
||||
submitted to any project *IS NOT THE CODE*. Code may not be free yet,
|
||||
but its value is rapidly dropping.
|
||||
|
||||
Or, in other words, I have an AI too. The utility to me of just
|
||||
shoving some AI code at the project is fairly limited.
|
||||
|
||||
The value you bring is [in the
|
||||
*TESTING*](https://jerf.org/iri/post/2026/what_value_code_in_ai_era/).
|
||||
|
||||
You may develop code in any way you like for Baiji, but the value is
|
||||
in the testing, not a code dump.
|
||||
|
||||
Once you have a tested branch in your local repo(s), the AI can walk
|
||||
you through the process of creating a PR if you don't already
|
||||
know. The PR prompt will hopefull guide you in this as well, but
|
||||
please tell us all what testing you have done on your PR.
|
||||
|
||||
That doesn't mean you need to have tested every possible interaction
|
||||
with every possible feature on every possible game. Goodness knows
|
||||
Baiji's work doesn't even remotely rise to that standard either! But
|
||||
it is very helpful to know what testing has and has not been done, so
|
||||
we can know what to look out for, and others may know to make their
|
||||
own PRs against your PR. Do not be shy to say "I haven't tested this
|
||||
with X". The space of "all possible Wii and Gamecube games" is
|
||||
huge.
|
||||
|
||||
Because the true value of a PR is testing, note that _testing_ someone
|
||||
else's PR can be as valuable or even more valuable than coming up with
|
||||
one of your own. Before starting a PR check the PR tab and see if
|
||||
there's anything either close to your work or is what you'd have
|
||||
made. If so, please join with that PR rather than source a new
|
||||
one by building that PR yourself and running it on your local Steam
|
||||
Frame. There is nothing as valuable as the _second_ pair of eyes on a
|
||||
PR!
|
||||
|
||||
# Baiji's Scope
|
||||
|
||||
The scope of Baiji is:
|
||||
|
||||
* The UI as it is presented in Steam OS.
|
||||
* The stereoscopic and VR views.
|
||||
|
||||
The Baiji project will not fix game-specific emulation bugs, unless
|
||||
someone can make an extremely good case for why it really is a Steam
|
||||
Frame-specific issue. Fixing emulation bugs is a frought process of
|
||||
testing hundreds of games for regressions, having deep knowledge of
|
||||
the relevant systems, and testing, testing, testing. Resources Baiji
|
||||
does not have.
|
||||
|
||||
At most, if someone wants to port in the latest Dolphin work, and then
|
||||
test it and see that it is some sort of improvement, I might take
|
||||
that. But it's the only emulation improvement we're going to take.
|
||||
|
||||
As such, all Issues filed about games will be closed without
|
||||
work. Replicate them in the latest official Dolphin release and file
|
||||
them there, if they are still a problem.
|
||||
|
||||
Plus this keeps us out of hot water with Nintendo. We haven't touched
|
||||
the parts of Dolphin Nintendo cares about.
|
||||
@@ -4,8 +4,8 @@ Buttons/A = `Right Button A`
|
||||
Buttons/B = `Right Trigger`
|
||||
Buttons/1 = `Right Button X`
|
||||
Buttons/2 = `Right Button Y`
|
||||
Buttons/- = `Left Button View`
|
||||
Buttons/+ = `Right Button Menu`
|
||||
Buttons/- = `Left Button Bumper`
|
||||
Buttons/+ = `Right Button Bumper`
|
||||
Buttons/Home = `Left Button System`
|
||||
D-Pad/Up = `Left D-Pad Up`
|
||||
D-Pad/Down = `Left D-Pad Down`
|
||||
|
||||
@@ -5,8 +5,8 @@ Name=BaijiSteamVR
|
||||
GenericName=VR GameCube/Wii Emulator
|
||||
Comment=GameCube and Wii emulator for Steam Frame
|
||||
Icon=baiji
|
||||
Exec=/home/steamos/baiju/bin/baiji-vr
|
||||
TryExec=/home/steamos/baiju/bin/baiji-vr
|
||||
Exec=/home/steamos/baiji/bin/baiji-vr
|
||||
TryExec=/home/steamos/baiji/bin/baiji-vr
|
||||
Terminal=false
|
||||
Categories=Game;Emulator;
|
||||
StartupWMClass=BaijiSteamVR
|
||||
@@ -92,13 +92,32 @@ release this as a Flatpak, because Flatpak software can not get to the
|
||||
VR infrastructure. I expect this to change over time, but who knows
|
||||
when.
|
||||
|
||||
Snapshots are not available yet. For now, install by building from source
|
||||
with the instructions below.
|
||||
## Install From Snapshot
|
||||
|
||||
## Install From Snapshot (Not Available Yet)
|
||||
(These instructions assume you haven't changed the `steamos` user. If
|
||||
you don't know what that means, you haven't. I think some paths end up
|
||||
in the executable. If you have changed it, you may need to do some
|
||||
symlinking or hardlinking to have this end up unpacked into
|
||||
`/home/steamos/baiji`.)
|
||||
|
||||
There are no published snapshots to install yet. This option will be
|
||||
documented when snapshots are available.
|
||||
1. Start the Desktop environment in your Steam Frame.
|
||||
2. Navigate to this URL in a browser.
|
||||
3. Start `Konsole`, or the terminal of your choice.
|
||||
4. Copy this by clicking on the little double-square next to these
|
||||
commands:
|
||||
|
||||
```bash
|
||||
cd
|
||||
wget https://github.com/thejerf/baijiSteamVR/releases/download/baiji-v0.1-alpha/baiji-v0.1-alpha.tar.bz2
|
||||
tar xjf baiji-v0.1-alpha.tar.bz2
|
||||
ssh frame "steamos-add-to-steam ./baiji/bin/baiji-vr"
|
||||
```
|
||||
5. Paste that into your Konsole session and hit "enter" on the
|
||||
keyboard.
|
||||
|
||||
`baiji-vr` will appear as an option in your Non-Steam Games library.
|
||||
|
||||
From there you'll need to load it up with your legally-acquired ROMs.
|
||||
|
||||
## Build and Install From an x86_64 Linux Machine
|
||||
|
||||
@@ -155,6 +174,17 @@ you an "emulation-based" toolchain, where it runs a native ARM
|
||||
toolchain on an x86 CPU, decline that. It was miserably slow. I don't
|
||||
know about ARM-based Mac OS machines.
|
||||
|
||||
# Performance Issues
|
||||
|
||||
* Remarkably, there are still even some Gamecube games that the Steam
|
||||
Frame can struggle with. I have verified this with a native build of
|
||||
Dolphin 2609: The Steam Frame can only barely push Wind Waker
|
||||
at native resolution on one screen, and it still makes some stutters
|
||||
even so. The good news is, if you sit through the emulation stuttering,
|
||||
it actually doesn't look all that great in 3D. The cell shading effect
|
||||
flattens everything out into a rather visually boring 3D effect,
|
||||
rather than looking awesome, so we're not missing anything.
|
||||
|
||||
# Ownership
|
||||
|
||||
I see a number of other people are working on similar forks. Honestly,
|
||||
@@ -187,143 +217,20 @@ like. I don't care.
|
||||
laser mode when you click "play". You *must* be in gamepad mode to
|
||||
actually play the game. I panicked several times during
|
||||
development that I had broken controller support but it was just
|
||||
that I had forgotten to switch.
|
||||
* I believe this is the case in core Dolphin as well, but the main UI
|
||||
that I had forgotten to switch. There is a dialog that should remind
|
||||
you. Note this is an aspect of the Steam OS, not Baiji.
|
||||
* The main UI
|
||||
is mostly frozen during emulation. But only mostly. Still, best just
|
||||
to treat it as frozen until you stop the emulation.
|
||||
* I left the "immersive VR" work in from the dolphinXR branch if you
|
||||
want to play with it, but something seems to be pretty wrong with
|
||||
this port of it. I didn't spend much time diagnosing it and probably
|
||||
sent the AI down the completely wrong track anyhow.
|
||||
* The "Immersive VR" only works with a certain set of settings,
|
||||
but they are not locked in when it is selected. This is next
|
||||
on the agenda.
|
||||
|
||||
# The Usual Fake FAQs
|
||||
|
||||
Most FAQs aren't really "frequently asked", they're just made up
|
||||
things the developer expects to be asked. This list is no different.
|
||||
|
||||
## Why Are Issues Closed On This Repo?
|
||||
|
||||
My dear friend gamer. I'm very happy to see you. I hope this
|
||||
repository brings you fun and joy.
|
||||
|
||||
But can I level with you?
|
||||
|
||||
I didn't do this work so I could spend the next several months
|
||||
addressing issues like _When I Jam The Wii Virtual Console "Ocarina Of
|
||||
Time" Into Baiji, Equipping The Boomerang Makes Link's Head
|
||||
Disappear Unless I Am Standing On My Head In Real Life_.
|
||||
|
||||
This is shared in a spirit of fun... and that includes me, having fun,
|
||||
bulding something together with the community, not volunteering to be
|
||||
personally responsible for getting Baiji running on every Steam Frame
|
||||
in the world.
|
||||
|
||||
Moreover, the truth is, this is a very complicated repo to make issues
|
||||
into:
|
||||
|
||||
* This project is not competent to address emulation issues inside
|
||||
of Dolphin.
|
||||
|
||||
Making emulation changes is an extremely complicated and
|
||||
frought process; what fixes one issue in a game breaks hundreds of
|
||||
other games. If there is an emulation issue in Baiji, the simple, but
|
||||
harsh truth is, it's going to stay there.
|
||||
|
||||
The upstream Dolphin team has the skills, the testing
|
||||
infrastructure, the decades of awareness, and the community to make
|
||||
changes. Even with the mighty power of
|
||||
*AI*... AI... Ai... ai... i... I do not have the skills, the testing
|
||||
infrastructure, or the community. So the best thing is just not to
|
||||
start accepting issues that are not going to go anywhere anyhow.
|
||||
|
||||
The only exception is that some blessed soul may make a PR that does
|
||||
an upstream pull from the Dolphin project. That I may be able to
|
||||
accept. But that's the only emulation fix this project is likely to
|
||||
make.
|
||||
|
||||
* No Wii or Gamecube game was designed to run this way. They will do
|
||||
weird things. They will put all their action into stuff that is
|
||||
very, very out-of-frame for the TV that is virtually impossible to
|
||||
focus on. The entire UI will sit in a 2D plane that is somehow
|
||||
behind the action visually, yet sitting on top of it.
|
||||
|
||||
No game quirk of this nature is in scope for Baiji (or Dolphin, for
|
||||
that matter). "Fixing" this would require someone to directly modify
|
||||
the ROM as a fan patch; there's nothing much the emulator can do
|
||||
about it. Some games are going to be like Super Mario Galaxy -
|
||||
quirky, but ultimately quite enhanced by the 3D. Some games will
|
||||
probably be rendered unplayable. Many games will be in between. All
|
||||
you can do is try them.
|
||||
|
||||
* The only things that are really "Baiji" about this project are the
|
||||
utilization of OpenXR to implement the stereoscopic 3D, a few
|
||||
additional controls for the stereoscopic 3D, and the
|
||||
specialization of the UI to the Steam Frame.
|
||||
|
||||
Basically everything else is out-of-scope. Immersive VR is part of
|
||||
the dolphinXR project. Which, as I write this, is still under
|
||||
development and I am now at least a week behind them, which means
|
||||
it's not an accurate representation of their current state. If that
|
||||
project asks me to remove the immersive VR from this one, I will
|
||||
comply. If someone does a tested PR, we can pull it in.
|
||||
|
||||
Really, the only useful things to the project are PRs, not bug
|
||||
reports.
|
||||
|
||||
So, on that topic...
|
||||
|
||||
## How Do I Fix A Bug?
|
||||
|
||||
This is VERY IMPORTANT: In an era of AI, the value you bring to a PR
|
||||
submitted to any project *IS NOT THE CODE*. Code is not free, but its
|
||||
value is rapidly dropping.
|
||||
|
||||
The value you bring is [in the
|
||||
*TESTING*](https://jerf.org/iri/post/2026/what_value_code_in_ai_era/).
|
||||
|
||||
Code used to have some value. Most of that value is gone. Not quite
|
||||
all, but most. What Baiji and other products need is *TESTING*. Both
|
||||
for quality, and for the user experience.
|
||||
|
||||
I have an AI too. I too can run a prompt that says "make a Minecraft
|
||||
clone, make no mistakes, kthxbye". The result will be superficially
|
||||
playable, but not even remotely fun or good.
|
||||
|
||||
It may seem like I'm not actually answering the question in the
|
||||
heading, but the answer is, the way you fix a bug is to set up your
|
||||
coding environment, you can use whatever you like to fix something,
|
||||
but then you need to test it. It is *WORSE THAN USELESS* for you to
|
||||
type a prompt into your AI and immediately rush to submit it as a
|
||||
PR. I would literally _rather not have that PR at all_.
|
||||
|
||||
What I need is for you to _test_ your PRs.
|
||||
|
||||
If you create a PR, and you test it, you have something that may be of
|
||||
value to the project.
|
||||
|
||||
Once you have a tested branch in your local repo(s), the AI can walk
|
||||
you through the process of creating a PR if you don't already
|
||||
know. The PR prompt will hopefull guide you in this as well, but
|
||||
please tell us all what testing you have done on your PR.
|
||||
|
||||
That doesn't mean you need to have tested every possible interaction
|
||||
with every possible feature on every possible game. Goodness knows
|
||||
Baiji's work doesn't even remotely rise to that standard either! But
|
||||
it is very helpful to know what testing has and has not been done, so
|
||||
we can know what to look out for, and others may know to make their
|
||||
own PRs against your PR. Do not be shy to say "I haven't tested this
|
||||
with X". The space of "all possible Wii and Gamecube games" is
|
||||
huge.
|
||||
|
||||
Because the true value of a PR is testing, note that _testing_ someone
|
||||
else's PR can be as valuable or even more valuable than coming up with
|
||||
one of your own. Before starting a PR check the PR tab and see if
|
||||
there's anything either close to your work or is what you'd have
|
||||
made. If so, please join with that PR rather than source a new
|
||||
one by building that PR yourself and running it on your local Steam
|
||||
Frame. There is nothing as valuable as the _second_ pair of eyes on a
|
||||
PR!
|
||||
|
||||
## Why Not A Flatpak?
|
||||
|
||||
As near as I can tell, it is currently *impossible* to get to VR from
|
||||
@@ -355,8 +262,7 @@ The Dolphin team is not behind this project. In particular that means,
|
||||
if the pieces break, you own them, maybe I have a bit of
|
||||
responsibility for them, but the Dolphin team has no responsibility
|
||||
whatsoever. Not even the polite one of at least listening to your
|
||||
problem. The correct solution for the Dolphin project, if anyone
|
||||
doeesn't pick up what I have repeatedly tried to lay down, and files a
|
||||
problem. The correct solution for the Dolphin project, if anyone files a
|
||||
bug with them, or starts a forum thread about bugs in Baiji, or
|
||||
anything else like that, is to immediately close it/lock it, and scold
|
||||
you a bit for putting it in the wrong place.
|
||||
@@ -376,7 +282,7 @@ Instead: I am a mere minnow next to the awesome Dolphin project. I am standing
|
||||
on the shoulder of their giantness, and to a lesser but still
|
||||
significant extent, on the work of the dolphinXR fork. I am
|
||||
deeply appreciative and while I want to help the Steam Frame, this is
|
||||
a mere trivial "one long weekend" gloss on top of the
|
||||
a mere trivial "couple o' weekends" gloss on top of the
|
||||
who-knows-how-many person-centuries of work the Dolphin project
|
||||
represents. Do not overestimate my "contribution". Nothing in this
|
||||
paragraph is a joke or even slightly sarcastic.
|
||||
@@ -387,69 +293,6 @@ this, I am very open to change, correction, PRs on this matter,
|
||||
etc. This is a sign of me trying to do it very correctly and
|
||||
carefully, not carelessly, and not to steal credit.
|
||||
|
||||
## What PRs Would Be Nice?
|
||||
|
||||
Here's my current wishlist:
|
||||
|
||||
* As mentioned, some way of distributing an unmodified Flatpak, BUT
|
||||
isn't super, super hacky. Remember, you can't really run a script on
|
||||
the outside; this has to work with "flatpak run" to be a "real"
|
||||
flatpak. I'd rather just distribute this as-is than
|
||||
distribute a Flakpak almost certain to break in the near
|
||||
future. This may require changes from Valve.
|
||||
* An uninstall script that gets all the little config tidbits left
|
||||
lying around the home directory, in case the user tires of Baiji.
|
||||
* An informed opinion (from some experience in play) about how to map
|
||||
some of the hotkey actions to the controller. Both Wiimote +
|
||||
Nunchuck and Gamecube controllers are pretty full up on the
|
||||
controls. We _need_ a "stop emulation" button, but it's not clear
|
||||
where to put save/restore state and a whole bunch of other useful
|
||||
functionality like that until we get some use under our belt.
|
||||
* If it becomes clear the core Dolphin project is keying in on support
|
||||
that may obsolete this project, especially direct support for
|
||||
stereoscopic 3D in an OpenXR environment, a migration script to move
|
||||
configuration and saved games into a target Dolphin installation.
|
||||
* A UI accessible in stereoscopic 3D mode that allows real-time
|
||||
modification of the stereo settings, and committing them to
|
||||
game-specific configuration.
|
||||
|
||||
When I wrote the bit about how important testing is, this is the PR
|
||||
I had in mind. It is easy to put together a garbage UI. For
|
||||
instance, it is tempting to make the various numbers a slider. But
|
||||
if _all_ you can do is slide them, it is very difficult to build a
|
||||
slider that is accurate. The especially tend to jerk at the very
|
||||
end just as you are releasing the button, and trying to make very
|
||||
small angular adjustments to change a value is not easy. Something
|
||||
like "point at the control and use thumbstick to adjust" may help,
|
||||
but then you need documentation the user can use to figure that out
|
||||
in realtime as they use it.
|
||||
|
||||
Also bear in mind that once a stereoscopic 3D frame is rendered,
|
||||
it's just two flat images. Modifications must be run on live
|
||||
games, because once a frame is rendered it's locked in.
|
||||
|
||||
Another interesting thing to play around with in such a dialog is a
|
||||
cull plane for the near frame. I haven't played with this long but
|
||||
eye strain is a real issue, and it may be preferable to cull things
|
||||
that are too near than to let users try to see them. It can also be
|
||||
used as a test that the user needs to reduce the stereo or something.
|
||||
* I also don't know if this is technically possible, but I would be
|
||||
interested in seeing if there is some sensible way to offer a slider
|
||||
that just pushes everything _back_, away from the user, in case a
|
||||
game takes place largely in the near frame, but without distorting
|
||||
the game in any other way by compensating in the camera
|
||||
matrix, or, at least, distorting it minimally. Or forward, as the
|
||||
case may be, though "back, out of the part of the 3D space that
|
||||
causes eye strain/pain" seems like the more important use case.
|
||||
* Any bug fixes in the UI, of course.
|
||||
* A PR to make it so when you stop the emulation, the dialog box for
|
||||
"would you like to stop emulation" comes up in VR space, rather than
|
||||
in the QT UI. The latter is very easy to miss.
|
||||
* A PR to update the immersive VR to work correctly.
|
||||
* Emulation issues that you have a _very very_ good case are somehow
|
||||
Steam Frame-specific and justify a Steam Frame-specific
|
||||
fix. Otherwise I'd consider the emulation path off-limits.
|
||||
|
||||
# Command Line Tooling
|
||||
|
||||
## Command Line Usage
|
||||
@@ -591,3 +434,53 @@ Relevant links for the original Dolphin underneath Baiji:
|
||||
Tracker](https://bugs.dolphin-emu.org/projects/emulator/issues) but
|
||||
DO NOT... you ought to have gotten this by now... report Baiji bugs
|
||||
* [FAQ](https://dolphin-emu.org/docs/faq/)
|
||||
|
||||
# Known Quirks/Bugs That Don't Seem Fixable
|
||||
|
||||
For any of these, a PR that proves me wrong would be great, but as far
|
||||
as I know these are currently not reasonable fixable:
|
||||
|
||||
* When the QT UI is visible during 3D emulation, SteamOS spatializes
|
||||
Baiji's emulation output to be coming from the location of the QT
|
||||
UI, not the emulation. The Frame's OS does not seem to offer a way
|
||||
to bypass this for just one application. It seems to have a switch
|
||||
that could be thrown for the whole OS but I'm not touching it.
|
||||
|
||||
# Changelog
|
||||
|
||||
* Todo before release:
|
||||
* Check Room View in VR, can probably leave that rendering while the
|
||||
Steam UI is up
|
||||
* Check the Reddit feedback about the settings that make VR work,
|
||||
and lock the ones that _must_ be set for Immersive VR to work,
|
||||
when Immersive VR is selected.
|
||||
|
||||
* v0.2-alpha (not released yet)
|
||||
* Fixed what seems to be the last major performance regression
|
||||
against a natively-compiled Dolphin.
|
||||
* Added + and - to mapping on left & right bumper for Wii +
|
||||
Nunchuck.
|
||||
* Turning on Room View with the Steam UI stops rendering in Stereo
|
||||
and VR mode and pauses the emulation, rather than continuing to
|
||||
render and emulate and making it look like it was ignoring
|
||||
controls.
|
||||
|
||||
Ideally, we'd leave the game UI up if you bring up the Steam UI,
|
||||
and only stop displaying the game if you go to full room view
|
||||
mode, but once focus is removed from the game, baiji can't tell
|
||||
what you're doing. Since we can't be smart about this, we have to
|
||||
not display the game even if you only are looking at the Steam UI.
|
||||
|
||||
This may be a bug in the UI's Room View support; it shouldn't let
|
||||
us draw on it. If it correctly ignored our draw requests we could
|
||||
get it to work like most other games, which still render in the
|
||||
background.
|
||||
* Improved the way a surface is requested for flat 2D mode. I'm
|
||||
honestly uncertain if v0.1-alpha was broken, but if it was this
|
||||
should fix it, if it wasn't, this should make future builds less
|
||||
likely to break.
|
||||
* v0.1-alpha - Initial release.
|
||||
* Many Frame-specific UI changes to make working with the Frame
|
||||
specifically slicker.
|
||||
* Support for the full Frame controllers with their XR profile.
|
||||
* Support for native display of stereoscopic 3D.
|
||||
@@ -123,6 +123,16 @@ public:
|
||||
++s_state.generation;
|
||||
}
|
||||
|
||||
static void SetSessionFocused(bool focused)
|
||||
{
|
||||
std::lock_guard lk(s_state_mutex);
|
||||
if (s_state.session_focused == focused)
|
||||
return;
|
||||
|
||||
s_state.session_focused = focused;
|
||||
++s_state.generation;
|
||||
}
|
||||
|
||||
static std::string GetDiagnosticString()
|
||||
{
|
||||
std::lock_guard lk(s_state_mutex);
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
|
||||
#include "DolphinQt/Config/ToolTipControls/BalloonTip.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdlib>
|
||||
#include <memory>
|
||||
|
||||
#include <QApplication>
|
||||
@@ -16,7 +18,6 @@
|
||||
#include <QPen>
|
||||
#include <QPoint>
|
||||
#include <QRect>
|
||||
#include <QScreen>
|
||||
#include <QSize>
|
||||
#include <QString>
|
||||
#include <QVBoxLayout>
|
||||
@@ -39,7 +40,8 @@ QWidget* s_parent = nullptr;
|
||||
} // namespace
|
||||
|
||||
void BalloonTip::ShowBalloon(const QString& title, const QString& message,
|
||||
const QPoint& target_arrow_tip_position, QWidget* const parent,
|
||||
const QPoint& target_arrow_tip_position,
|
||||
const QRect& target_widget_rect_global, QWidget* const parent,
|
||||
const ShowArrow show_arrow, const int border_width)
|
||||
{
|
||||
HideBalloon();
|
||||
@@ -47,13 +49,16 @@ void BalloonTip::ShowBalloon(const QString& title, const QString& message,
|
||||
return;
|
||||
|
||||
#if defined(__APPLE__)
|
||||
(void)target_widget_rect_global;
|
||||
QString the_message = message;
|
||||
the_message.replace(QStringLiteral("<dolphin_emphasis>"), QStringLiteral("<b>"));
|
||||
the_message.replace(QStringLiteral("</dolphin_emphasis>"), QStringLiteral("</b>"));
|
||||
QToolTip::showText(target_arrow_tip_position, the_message, parent);
|
||||
#else
|
||||
s_the_balloon_tip = std::make_unique<BalloonTip>(PrivateTag{}, title, message, parent);
|
||||
s_the_balloon_tip->UpdateBoundsAndRedraw(target_arrow_tip_position, show_arrow, border_width);
|
||||
s_the_balloon_tip =
|
||||
std::make_unique<BalloonTip>(PrivateTag{}, title, message, parent, border_width);
|
||||
s_the_balloon_tip->UpdateBoundsAndRedraw(target_arrow_tip_position, target_widget_rect_global,
|
||||
show_arrow, border_width);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -70,12 +75,16 @@ void BalloonTip::HideBalloon()
|
||||
#endif
|
||||
}
|
||||
|
||||
BalloonTip::BalloonTip(PrivateTag, const QString& title, QString message, QWidget* const parent)
|
||||
BalloonTip::BalloonTip(PrivateTag, const QString& title, QString message, QWidget* const parent,
|
||||
const int border_width)
|
||||
: QWidget(nullptr, Qt::ToolTip)
|
||||
{
|
||||
s_parent = parent;
|
||||
setMouseTracking(true);
|
||||
|
||||
const QWidget* const owner_window = parent->window();
|
||||
m_owner_rect = QRect(owner_window->mapToGlobal(QPoint(0, 0)), owner_window->size());
|
||||
|
||||
QColor window_color;
|
||||
QColor text_color;
|
||||
QColor dolphin_emphasis;
|
||||
@@ -92,9 +101,19 @@ BalloonTip::BalloonTip(PrivateTag, const QString& title, QString message, QWidge
|
||||
message.replace(QStringLiteral("</dolphin_emphasis>"), QStringLiteral("</b></font>"));
|
||||
|
||||
auto* const balloontip_layout = new QVBoxLayout;
|
||||
balloontip_layout->setSizeConstraint(QLayout::SetFixedSize);
|
||||
balloontip_layout->setSizeConstraint(QLayout::SetNoConstraint);
|
||||
setLayout(balloontip_layout);
|
||||
|
||||
const float border_half_width = border_width / 2.0;
|
||||
const int rounded_corner_margin = border_half_width / 4;
|
||||
const int horizontal_margin = border_width + rounded_corner_margin + 12;
|
||||
const int bounded_horizontal_margin =
|
||||
std::min(horizontal_margin, std::max(0, (m_owner_rect.width() - 1) / 2));
|
||||
const int max_width = std::max(1, m_owner_rect.width() - 2 * bounded_horizontal_margin);
|
||||
const int vertical_margin = bounded_horizontal_margin;
|
||||
balloontip_layout->setContentsMargins(bounded_horizontal_margin, vertical_margin,
|
||||
bounded_horizontal_margin, vertical_margin);
|
||||
|
||||
const auto create_label = [=](const QString& text) {
|
||||
QLabel* const label = new QLabel;
|
||||
balloontip_layout->addWidget(label);
|
||||
@@ -102,7 +121,6 @@ BalloonTip::BalloonTip(PrivateTag, const QString& title, QString message, QWidge
|
||||
label->setText(text);
|
||||
label->setTextFormat(Qt::RichText);
|
||||
|
||||
const int max_width = label->screen()->availableGeometry().width() / 3;
|
||||
label->setMaximumWidth(max_width);
|
||||
|
||||
if (label->sizeHint().width() > max_width)
|
||||
@@ -185,37 +203,71 @@ void BalloonTip::paintEvent(QPaintEvent* const event)
|
||||
}
|
||||
|
||||
void BalloonTip::UpdateBoundsAndRedraw(const QPoint& target_arrow_tip_position,
|
||||
const QRect& target_widget_rect_global,
|
||||
const ShowArrow show_arrow, const int border_full_width)
|
||||
{
|
||||
const float border_half_width = border_full_width / 2.0;
|
||||
|
||||
// This should be odd so that the arrow tip is a single pixel wide.
|
||||
const int arrow_full_width = 35;
|
||||
const float arrow_half_width = arrow_full_width / 2.0;
|
||||
// The y distance between the inner edge of the rectangle border and the inner tip of the arrow
|
||||
// border, and also the distance between the outer edge of the rectangle border and the outer tip
|
||||
// of the arrow border
|
||||
const int arrow_height = (1 + arrow_full_width) / 2;
|
||||
constexpr int preferred_arrow_full_width = 35;
|
||||
|
||||
// Distance between the label layout and the inner rectangle border edge
|
||||
const int balloon_interior_padding = 12;
|
||||
constexpr int balloon_interior_padding = 12;
|
||||
// Prevent the corners of the label layout from portruding into the rounded rectangle corners at
|
||||
// larger border sizes.
|
||||
const int rounded_corner_margin = border_half_width / 4;
|
||||
const int horizontal_margin =
|
||||
const int nominal_horizontal_margin =
|
||||
border_full_width + rounded_corner_margin + balloon_interior_padding;
|
||||
const int vertical_margin = horizontal_margin + arrow_height;
|
||||
const int horizontal_margin =
|
||||
std::min(nominal_horizontal_margin, std::max(0, (m_owner_rect.width() - 1) / 2));
|
||||
const int preferred_arrow_height =
|
||||
show_arrow == ShowArrow::Yes ? (1 + preferred_arrow_full_width) / 2 : 0;
|
||||
const int vertical_margin = horizontal_margin + preferred_arrow_height;
|
||||
|
||||
// Create enough space around the layout containing the title and message to draw the balloon and
|
||||
// both arrow tips (at most one of which will be visible)
|
||||
// Reserve space for the chevron only when one is requested.
|
||||
layout()->setContentsMargins(horizontal_margin, vertical_margin, horizontal_margin,
|
||||
vertical_margin);
|
||||
|
||||
QSize size_hint = sizeHint();
|
||||
size_hint.setWidth(std::min(size_hint.width(), m_owner_rect.width()));
|
||||
size_hint.setHeight(std::min(size_hint.height(), m_owner_rect.height()));
|
||||
|
||||
// Keep the balloon beside its target, choosing the side with more room if it must be clipped.
|
||||
const int space_above = std::max(0, target_widget_rect_global.top() - m_owner_rect.top());
|
||||
const int space_below =
|
||||
std::max(0, m_owner_rect.bottom() - target_widget_rect_global.bottom());
|
||||
const bool popup_above_widget =
|
||||
size_hint.height() <= space_above ||
|
||||
(size_hint.height() > space_below && space_above >= space_below);
|
||||
const int available_height = popup_above_widget ? space_above : space_below;
|
||||
size_hint.setHeight(std::min(size_hint.height(), std::max(1, available_height)));
|
||||
|
||||
// The content layout may prefer a larger size, but the visible tooltip is always bounded by its
|
||||
// owning window.
|
||||
setMaximumSize(m_owner_rect.size());
|
||||
resize(size_hint);
|
||||
|
||||
// Reduce the arrow with the available height/width so even unusually small windows have a
|
||||
// non-negative rounded-rectangle area.
|
||||
int arrow_full_width =
|
||||
show_arrow == ShowArrow::Yes ?
|
||||
std::min(preferred_arrow_full_width, size_hint.width() - border_full_width) :
|
||||
0;
|
||||
if (arrow_full_width > 0 && arrow_full_width % 2 == 0)
|
||||
--arrow_full_width;
|
||||
const int max_arrow_height = std::max(0, (size_hint.height() - border_full_width - 1) / 2);
|
||||
const int max_arrow_width = max_arrow_height > 0 ? 2 * max_arrow_height - 1 : 0;
|
||||
arrow_full_width = std::max(0, std::min(arrow_full_width, max_arrow_width));
|
||||
const float arrow_half_width = arrow_full_width / 2.0;
|
||||
// The y distance between the inner edge of the rectangle border and the inner tip of the arrow
|
||||
// border, and also the distance between the outer edge of the rectangle border and the outer tip
|
||||
// of the arrow border.
|
||||
const int arrow_height = (1 + arrow_full_width) / 2;
|
||||
|
||||
// These positions represent the middle of each edge of the BalloonTip's rounded rectangle
|
||||
const float rect_width = size_hint.width() - border_full_width;
|
||||
const float rect_height = size_hint.height() - border_full_width - 2 * arrow_height;
|
||||
const float rect_width = std::max(0, size_hint.width() - border_full_width);
|
||||
const float rect_height =
|
||||
std::max(0, size_hint.height() - border_full_width - 2 * arrow_height);
|
||||
const float rect_top = border_half_width + arrow_height;
|
||||
const float rect_bottom = rect_top + rect_height;
|
||||
const float rect_left = border_half_width;
|
||||
@@ -240,22 +292,29 @@ void BalloonTip::UpdateBoundsAndRedraw(const QPoint& target_arrow_tip_position,
|
||||
// the left edge of the arrow interior; otherwise the distance between the right edges.
|
||||
const int arrow_nearest_edge_x_offset =
|
||||
std::min(adjusted_arrow_x_offset, centered_arrow_x_offset);
|
||||
const int arrow_tip_x_offset = arrow_nearest_edge_x_offset + arrow_half_width;
|
||||
|
||||
// The BalloonTip should be contained entirely within the screen that contains the target
|
||||
// position.
|
||||
QScreen* screen = QGuiApplication::screenAt(target_arrow_tip_position);
|
||||
if (screen == nullptr)
|
||||
{
|
||||
// If the target position isn't on any screen (which can happen if the window is partly off the
|
||||
// screen and the user hovers over the label) then use the screen containing the cursor instead.
|
||||
screen = QGuiApplication::screenAt(QCursor::pos());
|
||||
}
|
||||
const QRect screen_rect = screen->geometry();
|
||||
const int left_arrow_tip_x = static_cast<int>(arrow_nearest_edge_x_offset + arrow_half_width);
|
||||
const int right_arrow_tip_x =
|
||||
static_cast<int>(size_hint.width() - arrow_nearest_edge_x_offset - arrow_full_width +
|
||||
arrow_half_width);
|
||||
const int rightmost_valid_x =
|
||||
std::max(m_owner_rect.left(), m_owner_rect.right() - size_hint.width() + 1);
|
||||
const int left_arrow_window_x = std::clamp(target_arrow_tip_position.x() - left_arrow_tip_x,
|
||||
m_owner_rect.left(), rightmost_valid_x);
|
||||
const int right_arrow_window_x = std::clamp(target_arrow_tip_position.x() - right_arrow_tip_x,
|
||||
m_owner_rect.left(), rightmost_valid_x);
|
||||
const int left_arrow_error =
|
||||
std::abs(left_arrow_window_x + left_arrow_tip_x - target_arrow_tip_position.x());
|
||||
const int right_arrow_error =
|
||||
std::abs(right_arrow_window_x + right_arrow_tip_x - target_arrow_tip_position.x());
|
||||
const bool arrow_at_left = left_arrow_error <= right_arrow_error;
|
||||
const int actual_balloontip_global_x = arrow_at_left ? left_arrow_window_x : right_arrow_window_x;
|
||||
|
||||
QPainterPath rect_path;
|
||||
rect_path.addRoundedRect(rect_left, rect_top, rect_width, rect_height, corner_outer_radius,
|
||||
corner_outer_radius);
|
||||
if (rect_width > 0 && rect_height > 0)
|
||||
{
|
||||
rect_path.addRoundedRect(rect_left, rect_top, rect_width, rect_height, corner_outer_radius,
|
||||
corner_outer_radius);
|
||||
}
|
||||
|
||||
// The default pen cap style Qt::SquareCap extends drawn lines one half width before the starting
|
||||
// point and one half width after the ending point such that the starting and ending points are
|
||||
@@ -290,25 +349,18 @@ void BalloonTip::UpdateBoundsAndRedraw(const QPoint& target_arrow_tip_position,
|
||||
mask_painter.setBrush(QBrush(Qt::color1));
|
||||
mask_painter.drawPath(rect_path);
|
||||
|
||||
const bool arrow_at_bottom =
|
||||
target_arrow_tip_position.y() - size_hint.height() + arrow_height >= 0;
|
||||
const bool arrow_at_left =
|
||||
target_arrow_tip_position.x() + size_hint.width() - arrow_tip_x_offset < screen_rect.width();
|
||||
|
||||
const float arrow_base_y =
|
||||
arrow_at_bottom ? rect_bottom - border_half_width : rect_top + border_half_width;
|
||||
popup_above_widget ? rect_bottom - border_half_width : rect_top + border_half_width;
|
||||
|
||||
const float arrow_tip_vertical_offset = arrow_at_bottom ? arrow_height : -arrow_height;
|
||||
const float arrow_tip_vertical_offset = popup_above_widget ? arrow_height : -arrow_height;
|
||||
const float arrow_tip_interior_y = arrow_base_y + arrow_tip_vertical_offset;
|
||||
const float arrow_tip_exterior_y =
|
||||
arrow_tip_interior_y + (arrow_at_bottom ? border_full_width : -border_full_width);
|
||||
const float arrow_base_left_edge_x =
|
||||
arrow_at_left ? arrow_nearest_edge_x_offset :
|
||||
size_hint.width() - arrow_nearest_edge_x_offset - arrow_full_width;
|
||||
const float arrow_base_right_edge_x = arrow_base_left_edge_x + arrow_full_width;
|
||||
const float arrow_tip_x = arrow_base_left_edge_x + arrow_half_width;
|
||||
|
||||
if (show_arrow == ShowArrow::Yes)
|
||||
if (show_arrow == ShowArrow::Yes && arrow_full_width > 0)
|
||||
{
|
||||
// Drawing diagonal lines in Qt is filled with edge cases and inexplicable behavior. Getting it
|
||||
// to do what you want at one border size is simple enough, but doing so flexibly is an exercise
|
||||
@@ -324,14 +376,14 @@ void BalloonTip::UpdateBoundsAndRedraw(const QPoint& target_arrow_tip_position,
|
||||
|
||||
QPainterPath arrow_border_path;
|
||||
QPainterPath arrow_interior_fill_path;
|
||||
const float y_end_offset = arrow_at_bottom ? border_full_width : -border_full_width;
|
||||
const float y_end_offset = popup_above_widget ? border_full_width : -border_full_width;
|
||||
|
||||
// Draw the arrow border and interior lines from the outside inward. Each loop iteration draws
|
||||
// one pair of border lines and one pair of interior lines.
|
||||
for (int i = 1; i <= arrow_half_width; i++)
|
||||
{
|
||||
const float x_offset_from_arrow_base_edge = i - 0.5;
|
||||
const float border_y_start = arrow_base_y + (arrow_at_bottom ? i : -i);
|
||||
const float border_y_start = arrow_base_y + (popup_above_widget ? i : -i);
|
||||
const float border_y_end = border_y_start + y_end_offset;
|
||||
const float interior_y_start = arrow_base_y;
|
||||
const float interior_y_end = border_y_start;
|
||||
@@ -377,22 +429,15 @@ void BalloonTip::UpdateBoundsAndRedraw(const QPoint& target_arrow_tip_position,
|
||||
|
||||
setMask(mask_bitmap);
|
||||
|
||||
// Place the arrow tip at the target position whether the arrow tip is drawn or not
|
||||
const int target_balloontip_global_x =
|
||||
target_arrow_tip_position.x() - static_cast<int>(arrow_tip_x);
|
||||
const int rightmost_valid_balloontip_global_x =
|
||||
screen_rect.left() + screen_rect.width() - size_hint.width();
|
||||
// If the balloon would extend off the screen, push it left or right until it's not
|
||||
const int actual_balloontip_global_x =
|
||||
std::max(screen_rect.left(),
|
||||
std::min(rightmost_valid_balloontip_global_x, target_balloontip_global_x));
|
||||
// The tip pixel should be in the middle of the control, and arrow_tip_exterior_y is at the bottom
|
||||
// of that pixel. When arrow_at_bottom is true the arrow is above arrow_tip_exterior_y and so the
|
||||
// tip pixel is in the right place, but when it's false the arrow is below arrow_tip_exterior_y
|
||||
// so the tip pixel would be the one below that. Make this adjustment to fix that.
|
||||
const int tip_pixel_adjustment = arrow_at_bottom ? 0 : 1;
|
||||
// Anchor the popup edge to the source widget edge selected for this side, so its border doesn't
|
||||
// cut through the widget.
|
||||
const int preferred_balloontip_global_y =
|
||||
popup_above_widget ? target_widget_rect_global.top() - size_hint.height() :
|
||||
target_widget_rect_global.bottom() + 1;
|
||||
const int lowest_valid_y =
|
||||
std::max(m_owner_rect.top(), m_owner_rect.bottom() - size_hint.height() + 1);
|
||||
const int actual_balloontip_global_y =
|
||||
target_arrow_tip_position.y() - arrow_tip_exterior_y - tip_pixel_adjustment;
|
||||
std::clamp(preferred_balloontip_global_y, m_owner_rect.top(), lowest_valid_y);
|
||||
|
||||
move(actual_balloontip_global_x, actual_balloontip_global_y);
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
#include <QColor>
|
||||
#include <QPixmap>
|
||||
#include <QRect>
|
||||
#include <QWidget>
|
||||
|
||||
class QEnterEvent;
|
||||
@@ -29,14 +30,15 @@ public:
|
||||
No
|
||||
};
|
||||
static void ShowBalloon(const QString& title, const QString& message,
|
||||
const QPoint& target_arrow_tip_position, QWidget* parent,
|
||||
const QPoint& target_arrow_tip_position,
|
||||
const QRect& target_widget_rect_global, QWidget* parent,
|
||||
ShowArrow show_arrow = ShowArrow::Yes, int border_width = 1);
|
||||
static void HideBalloon();
|
||||
static bool IsCursorInsideWidgetBoundingBox(const QWidget& widget);
|
||||
static bool IsCursorOnBalloonTip();
|
||||
static bool IsWidgetBalloonTipActive(const QWidget& widget);
|
||||
|
||||
BalloonTip(PrivateTag, const QString& title, QString message, QWidget* parent);
|
||||
BalloonTip(PrivateTag, const QString& title, QString message, QWidget* parent, int border_width);
|
||||
|
||||
protected:
|
||||
void enterEvent(QEnterEvent* event) override;
|
||||
@@ -45,9 +47,11 @@ protected:
|
||||
void paintEvent(QPaintEvent* event) override;
|
||||
|
||||
private:
|
||||
void UpdateBoundsAndRedraw(const QPoint& target_arrow_tip_position, ShowArrow show_arrow,
|
||||
void UpdateBoundsAndRedraw(const QPoint& target_arrow_tip_position,
|
||||
const QRect& target_widget_rect_global, ShowArrow show_arrow,
|
||||
int border_width);
|
||||
|
||||
QColor m_border_color;
|
||||
QPixmap m_pixmap;
|
||||
QRect m_owner_rect;
|
||||
};
|
||||
@@ -60,8 +60,11 @@ private:
|
||||
this->killTimer(*m_timer_id);
|
||||
m_timer_id.reset();
|
||||
|
||||
BalloonTip::ShowBalloon(m_title, m_description,
|
||||
this->parentWidget()->mapToGlobal(GetToolTipPosition()), this);
|
||||
const QPoint target_position = this->parentWidget()->mapToGlobal(GetToolTipPosition());
|
||||
const QRect target_widget_rect_global(this->mapToGlobal(this->rect().topLeft()),
|
||||
this->rect().size());
|
||||
BalloonTip::ShowBalloon(m_title, m_description, target_position, target_widget_rect_global,
|
||||
this, BalloonTip::ShowArrow::No);
|
||||
|
||||
Derived::timerEvent(event);
|
||||
}
|
||||
|
||||
@@ -331,10 +331,10 @@ bool CommandBufferManager::CreateSubmitThread()
|
||||
#endif
|
||||
}
|
||||
|
||||
SubmitCommandBuffer(submit.command_buffer_index, submit.present_swap_chain,
|
||||
submit.present_image_index);
|
||||
const VkResult submit_result = SubmitCommandBuffer(
|
||||
submit.command_buffer_index, submit.present_swap_chain, submit.present_image_index);
|
||||
if (submit.post_submit_callback)
|
||||
submit.post_submit_callback();
|
||||
submit.post_submit_callback(submit_result);
|
||||
CmdBufferResources& resources = m_command_buffers[submit.command_buffer_index];
|
||||
resources.waiting_for_submit.store(false, std::memory_order_release);
|
||||
});
|
||||
@@ -434,10 +434,10 @@ void CommandBufferManager::WaitForCommandBufferCompletion(u32 index)
|
||||
}
|
||||
|
||||
bool CommandBufferManager::SubmitCommandBuffer(bool submit_on_worker_thread,
|
||||
bool wait_for_completion, bool advance_to_next_frame,
|
||||
VkSwapchainKHR present_swap_chain,
|
||||
uint32_t present_image_index,
|
||||
std::function<void()> post_submit_callback)
|
||||
bool wait_for_completion, bool advance_to_next_frame,
|
||||
VkSwapchainKHR present_swap_chain,
|
||||
uint32_t present_image_index,
|
||||
std::function<void(VkResult)> post_submit_callback)
|
||||
{
|
||||
if (IsDeviceLost())
|
||||
return false;
|
||||
@@ -511,7 +511,7 @@ bool CommandBufferManager::SubmitCommandBuffer(bool submit_on_worker_thread,
|
||||
const VkResult submit_result =
|
||||
SubmitCommandBuffer(m_current_cmd_buffer, present_swap_chain, present_image_index);
|
||||
if (post_submit_callback)
|
||||
post_submit_callback();
|
||||
post_submit_callback(submit_result);
|
||||
if (submit_result != VK_SUCCESS)
|
||||
return false;
|
||||
if (wait_for_completion)
|
||||
|
||||
@@ -55,6 +55,8 @@ public:
|
||||
auto& resources = m_command_buffers[m_current_cmd_buffer];
|
||||
return resources.fence_counter;
|
||||
}
|
||||
VkFence GetCurrentFence() const { return m_command_buffers[m_current_cmd_buffer].fence; }
|
||||
bool UsesThreadedSubmission() const { return m_use_threaded_submission; }
|
||||
|
||||
// Returns the semaphore for the current command buffer, which can be used to ensure the
|
||||
// swap chain image is ready before the command buffer executes.
|
||||
@@ -86,7 +88,7 @@ public:
|
||||
bool advance_to_next_frame = false,
|
||||
VkSwapchainKHR present_swap_chain = VK_NULL_HANDLE,
|
||||
uint32_t present_image_index = 0xFFFFFFFF,
|
||||
std::function<void()> post_submit_callback = {});
|
||||
std::function<void(VkResult)> post_submit_callback = {});
|
||||
|
||||
bool IsDeviceLost() const { return m_device_lost.load(std::memory_order_acquire); }
|
||||
|
||||
@@ -174,7 +176,7 @@ private:
|
||||
VkSwapchainKHR present_swap_chain;
|
||||
u32 present_image_index;
|
||||
u32 command_buffer_index;
|
||||
std::function<void()> post_submit_callback;
|
||||
std::function<void(VkResult)> post_submit_callback;
|
||||
};
|
||||
Common::WorkQueueThreadSP<PendingCommandBufferSubmit> m_submit_thread;
|
||||
std::vector<VkSemaphore> m_present_semaphores = {};
|
||||
|
||||
@@ -9,7 +9,10 @@
|
||||
#define VK_USE_PLATFORM_WIN32_KHR
|
||||
#endif
|
||||
|
||||
#if defined(HAVE_X11)
|
||||
// The Frame's Qt launcher uses XCB/Xwayland, but Vulkan's surface is created from the
|
||||
// Xlib handles exposed by Qt. Keep this WSI available even when the cross-build omits
|
||||
// Dolphin's optional X11 input dependencies.
|
||||
#if defined(HAVE_X11) || (defined(BAIJI_STEAMVR) && __has_include(<X11/Xlib.h>))
|
||||
#define VK_USE_PLATFORM_XLIB_KHR
|
||||
#endif
|
||||
|
||||
|
||||
@@ -285,7 +285,8 @@ std::unique_lock<std::mutex> VulkanOpenXR::AcquireGraphicsQueueLock()
|
||||
bool VulkanOpenXR::WaitForPendingFrameFinalization(std::string_view reason)
|
||||
{
|
||||
#if defined(ANDROID)
|
||||
if (m_async_frame_finalization_in_flight.load(std::memory_order_acquire))
|
||||
if (m_async_frame_finalization_in_flight.load(std::memory_order_acquire) ||
|
||||
m_stereo_finalization_in_flight.load(std::memory_order_acquire))
|
||||
{
|
||||
static uint32_t s_async_wait_log_count = 0;
|
||||
if (!g_command_buffer_mgr)
|
||||
@@ -324,7 +325,7 @@ bool VulkanOpenXR::WaitForPendingFrameFinalization(std::string_view reason)
|
||||
return true;
|
||||
}
|
||||
|
||||
void VulkanOpenXR::FinalizePendingXRFrame(PendingXRFrame frame)
|
||||
void VulkanOpenXR::FinalizePendingXRFrame(PendingXRFrame frame, VkResult submit_result)
|
||||
{
|
||||
const uint64_t finalize_start_us = Common::Timer::NowUs();
|
||||
uint64_t release_total_us = 0;
|
||||
@@ -361,6 +362,18 @@ void VulkanOpenXR::FinalizePendingXRFrame(PendingXRFrame frame)
|
||||
release_swapchain(frame.eye_swapchains[eye], eye == 0 ? "eye 0" : "eye 1");
|
||||
}
|
||||
|
||||
if (submit_result != VK_SUCCESS)
|
||||
{
|
||||
m_async_frame_finalization_failed.store(true, std::memory_order_release);
|
||||
success = false;
|
||||
}
|
||||
|
||||
if (!success)
|
||||
{
|
||||
m_async_frame_finalization_in_flight.store(false, std::memory_order_release);
|
||||
return;
|
||||
}
|
||||
|
||||
const uint64_t end_frame_start_us = Common::Timer::NowUs();
|
||||
if (!VR::g_openxr)
|
||||
{
|
||||
@@ -1311,6 +1324,13 @@ void VulkanOpenXR::DestroySwapchains()
|
||||
AbstractFramebuffer* VulkanOpenXR::AcquireEyeFramebuffer(uint32_t eye_index)
|
||||
{
|
||||
ASSERT(eye_index < 2);
|
||||
#if defined(BAIJI_STEAMVR)
|
||||
// Do not acquire a second XR image pair while the previous pair is still being written.
|
||||
// The submit worker releases/publishes the pending pair after its fence signals; meanwhile
|
||||
// the pacing thread keeps reprojecting the last completed pair.
|
||||
if (m_stereo_finalization_in_flight.load(std::memory_order_acquire))
|
||||
return nullptr;
|
||||
#endif
|
||||
static unsigned int s_openxr_vk_acquire_log_count = 0;
|
||||
const uint64_t perf_start_us = Common::Timer::NowUs();
|
||||
Common::ScopeGuard perf_guard([perf_start_us] {
|
||||
@@ -1641,8 +1661,8 @@ bool VulkanOpenXR::SubmitFrame()
|
||||
m_async_frame_finalization_in_flight.store(true, std::memory_order_release);
|
||||
g_command_buffer_mgr->SubmitCommandBuffer(
|
||||
true, false, true, VK_NULL_HANDLE, 0xFFFFFFFF,
|
||||
[this, frame = std::move(pending_frame)]() mutable {
|
||||
FinalizePendingXRFrame(std::move(frame));
|
||||
[this, frame = std::move(pending_frame)](VkResult submit_result) mutable {
|
||||
FinalizePendingXRFrame(std::move(frame), submit_result);
|
||||
});
|
||||
StateTracker::GetInstance()->InvalidateCachedState();
|
||||
|
||||
@@ -1740,16 +1760,116 @@ bool VulkanOpenXR::SubmitFlatFrame()
|
||||
m_eye_swapchains[0].width, m_eye_swapchains[0].height);
|
||||
}
|
||||
|
||||
void VulkanOpenXR::FinalizeStereoFlatFrameAfterGPU(VkFence fence, bool have_pair,
|
||||
VkResult submit_result)
|
||||
{
|
||||
Common::ScopeGuard completion_guard([this] {
|
||||
m_stereo_finalization_in_flight.store(false, std::memory_order_release);
|
||||
});
|
||||
|
||||
if (submit_result == VK_SUCCESS)
|
||||
{
|
||||
const VkResult wait_result =
|
||||
vkWaitForFences(g_vulkan_context->GetDevice(), 1, &fence, VK_TRUE, UINT64_MAX);
|
||||
if (wait_result != VK_SUCCESS)
|
||||
{
|
||||
LOG_VULKAN_ERROR(wait_result, "StereoScreen async completion fence wait failed: ");
|
||||
m_async_frame_finalization_failed.store(true, std::memory_order_release);
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG_VULKAN_ERROR(submit_result, "StereoScreen async queue submission failed: ");
|
||||
m_async_frame_finalization_failed.store(true, std::memory_order_release);
|
||||
}
|
||||
|
||||
if (!VR::g_openxr)
|
||||
{
|
||||
ERROR_LOG_FMT(VIDEO, "OpenXR Vulkan: XR manager disappeared before stereo pair finalization.");
|
||||
return;
|
||||
}
|
||||
|
||||
bool released = true;
|
||||
{
|
||||
// The image writes are now complete. Keep the existing handoff -> graphics queue -> publish
|
||||
// order for the short release/publish transaction, but do not hold either lock during the
|
||||
// GPU fence wait above.
|
||||
VR::OpenXRManager::ScopedVideoFrameHandoff handoff(VR::g_openxr.get());
|
||||
{
|
||||
auto queue_lock = AcquireGraphicsQueueLock();
|
||||
XrSwapchainImageReleaseInfo release_info{XR_TYPE_SWAPCHAIN_IMAGE_RELEASE_INFO};
|
||||
for (uint32_t eye = 0; eye < 2; ++eye)
|
||||
{
|
||||
if (!m_image_acquired[eye])
|
||||
continue;
|
||||
|
||||
const XrResult result =
|
||||
xrReleaseSwapchainImage(m_eye_swapchains[eye].swapchain, &release_info);
|
||||
if (XR_FAILED(result))
|
||||
{
|
||||
WARN_LOG_FMT(VIDEO, "OpenXR: async completed stereo eye {} release failed ({}).", eye,
|
||||
static_cast<int>(result));
|
||||
released = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_image_acquired[eye] = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!released || submit_result != VK_SUCCESS)
|
||||
return;
|
||||
|
||||
if (have_pair && VR::g_openxr->IsFrameThreadActive())
|
||||
{
|
||||
if (!VR::g_openxr->SubmitStereoQuadFrame({m_eye_swapchains[0].swapchain,
|
||||
m_eye_swapchains[1].swapchain},
|
||||
m_eye_swapchains[0].width,
|
||||
m_eye_swapchains[0].height))
|
||||
{
|
||||
m_async_frame_finalization_failed.store(true, std::memory_order_release);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool VulkanOpenXR::SubmitStereoFlatFrame()
|
||||
{
|
||||
ASSERT(VR::g_openxr != nullptr);
|
||||
|
||||
#if defined(BAIJI_STEAMVR)
|
||||
// Keep displaying the last completed pair while the submit worker finishes the prior pair.
|
||||
if (m_stereo_finalization_in_flight.load(std::memory_order_acquire))
|
||||
return true;
|
||||
|
||||
if (m_image_acquired[0] || m_image_acquired[1])
|
||||
{
|
||||
const bool have_pair = m_image_acquired[0] && m_image_acquired[1];
|
||||
StateTracker::GetInstance()->EndRenderPass();
|
||||
|
||||
if (VR::g_openxr->IsFrameThreadActive() && g_command_buffer_mgr->UsesThreadedSubmission())
|
||||
{
|
||||
// Let the Vulkan worker wait for GPU completion. Until its fence signals, the XR pacing
|
||||
// thread can safely reproject the last released pair and this video thread can continue
|
||||
// emulation; AcquireEyeFramebuffer skips another XR pair while finalization is pending.
|
||||
const VkFence completion_fence = g_command_buffer_mgr->GetCurrentFence();
|
||||
m_stereo_finalization_in_flight.store(true, std::memory_order_release);
|
||||
const bool queued = g_command_buffer_mgr->SubmitCommandBuffer(
|
||||
true, false, false, VK_NULL_HANDLE, 0xFFFFFFFF,
|
||||
[this, completion_fence, have_pair](VkResult submit_result) {
|
||||
FinalizeStereoFlatFrameAfterGPU(completion_fence, have_pair, submit_result);
|
||||
});
|
||||
StateTracker::GetInstance()->InvalidateCachedState();
|
||||
if (!queued)
|
||||
{
|
||||
m_stereo_finalization_in_flight.store(false, std::memory_order_release);
|
||||
return false;
|
||||
}
|
||||
return have_pair;
|
||||
}
|
||||
|
||||
// Submitted writes alone left visible doubled/stale images on Steam Frame.
|
||||
// Finish BOTH eye blits before releasing either image to the runtime. Wait
|
||||
// once for the pair, rather than draining the GPU separately for each eye.
|
||||
|
||||
@@ -190,7 +190,8 @@ private:
|
||||
std::vector<VkImageView>* out_views);
|
||||
|
||||
void DestroySwapchains();
|
||||
void FinalizePendingXRFrame(PendingXRFrame frame);
|
||||
void FinalizePendingXRFrame(PendingXRFrame frame, VkResult submit_result);
|
||||
void FinalizeStereoFlatFrameAfterGPU(VkFence fence, bool have_pair, VkResult submit_result);
|
||||
|
||||
std::array<XRVkEyeSwapchain, 2> m_eye_swapchains{};
|
||||
XRVkLayeredSwapchain m_layered_swapchain{};
|
||||
@@ -212,6 +213,9 @@ private:
|
||||
|
||||
std::atomic<bool> m_async_frame_finalization_in_flight{false};
|
||||
std::atomic<bool> m_async_frame_finalization_failed{false};
|
||||
// StereoScreen keeps acquired eye images until their blits finish on the GPU while the
|
||||
// Vulkan submit worker waits for the pair fence and finalizes the handoff.
|
||||
std::atomic<bool> m_stereo_finalization_in_flight{false};
|
||||
};
|
||||
|
||||
// Global Vulkan OpenXR instance — valid between VideoBackend::Initialize() and Shutdown().
|
||||
|
||||
@@ -50,6 +50,8 @@
|
||||
#include "Common/Timer.h"
|
||||
#include "Common/VR/OpenXRInputState.h"
|
||||
#include "Core/Config/GraphicsSettings.h"
|
||||
#include "Core/Core.h"
|
||||
#include "Core/System.h"
|
||||
#include "VideoCommon/OnScreenDisplay.h"
|
||||
#include "VideoCommon/VideoConfig.h"
|
||||
|
||||
@@ -1167,9 +1169,9 @@ void OpenXRManager::DestroySession()
|
||||
// Everything below belongs to a session or its frame loop. Keep only the instance/system and
|
||||
// instance-level extension state so a subsequent game starts from the same state as a newly
|
||||
// constructed manager without forcing the runtime through VR_Shutdown/VR_Init again.
|
||||
SetSessionFocused(false, false);
|
||||
m_session_state = XR_SESSION_STATE_UNKNOWN;
|
||||
m_session_running.store(false, std::memory_order_release);
|
||||
m_session_focused.store(false, std::memory_order_release);
|
||||
m_exit_render_loop = false;
|
||||
m_frame_state = {XR_TYPE_FRAME_STATE};
|
||||
m_view_state_flags = 0;
|
||||
@@ -1342,7 +1344,10 @@ void OpenXRManager::FrameThreadLoop()
|
||||
if (!BeginFrame())
|
||||
continue;
|
||||
|
||||
const bool should_render = m_should_render_snapshot.load(std::memory_order_acquire);
|
||||
const bool runtime_should_render =
|
||||
m_should_render_snapshot.load(std::memory_order_acquire);
|
||||
const bool should_render = runtime_should_render &&
|
||||
m_session_focused.load(std::memory_order_acquire);
|
||||
const int64_t period_ns = m_predicted_display_period_snapshot.load(std::memory_order_acquire);
|
||||
|
||||
// Wait for the game to hand over a fresh frame for as long as this cycle can afford:
|
||||
@@ -1453,8 +1458,8 @@ void OpenXRManager::FrameThreadLoop()
|
||||
}
|
||||
}
|
||||
|
||||
EndFrameDetached(m_frame_state.predictedDisplayTime, GetActiveBlendMode(), submit_content,
|
||||
layers);
|
||||
EndFrameDetached(m_frame_state.predictedDisplayTime, GetActiveBlendMode(),
|
||||
runtime_should_render, layers);
|
||||
#ifdef BAIJI_STEAMVR
|
||||
handoff_lock.unlock();
|
||||
#endif
|
||||
@@ -2117,6 +2122,88 @@ void OpenXRManager::ResetInputActionsState()
|
||||
Common::VR::OpenXRInputState::Reset();
|
||||
}
|
||||
|
||||
void OpenXRManager::SetSessionFocused(bool focused, bool pause_emulation_when_unfocused)
|
||||
{
|
||||
Core::System& system = Core::System::GetInstance();
|
||||
const Core::State core_state = Core::GetState(system);
|
||||
bool pause_emulation = false;
|
||||
bool resume_emulation = false;
|
||||
{
|
||||
std::lock_guard focus_lock(m_session_focus_mutex);
|
||||
m_session_focused.store(focused, std::memory_order_release);
|
||||
Common::VR::OpenXRInputState::SetSessionFocused(focused);
|
||||
|
||||
if (focused)
|
||||
{
|
||||
m_focus_loss_pause_attempted = false;
|
||||
if (!m_focus_pause_pending && m_emulation_paused_for_focus_loss)
|
||||
{
|
||||
m_emulation_paused_for_focus_loss = false;
|
||||
resume_emulation = true;
|
||||
}
|
||||
}
|
||||
else if (!pause_emulation_when_unfocused)
|
||||
{
|
||||
m_focus_loss_pause_attempted = false;
|
||||
if (m_focus_pause_pending)
|
||||
{
|
||||
m_resume_after_pending_focus_pause = true;
|
||||
}
|
||||
else if (m_emulation_paused_for_focus_loss)
|
||||
{
|
||||
m_emulation_paused_for_focus_loss = false;
|
||||
resume_emulation = true;
|
||||
}
|
||||
}
|
||||
else if (!m_focus_loss_pause_attempted && !m_focus_pause_pending)
|
||||
{
|
||||
if (core_state == Core::State::Paused)
|
||||
{
|
||||
m_focus_loss_pause_attempted = true;
|
||||
}
|
||||
else if (core_state == Core::State::Running)
|
||||
{
|
||||
m_focus_loss_pause_attempted = true;
|
||||
m_focus_pause_pending = true;
|
||||
m_resume_after_pending_focus_pause = false;
|
||||
pause_emulation = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (resume_emulation)
|
||||
{
|
||||
if (Core::GetState(system) == Core::State::Paused)
|
||||
{
|
||||
Core::SetState(system, Core::State::Running);
|
||||
}
|
||||
}
|
||||
|
||||
if (!pause_emulation)
|
||||
return;
|
||||
|
||||
// Do not hold the focus-state mutex while pausing the core. The video thread can
|
||||
// report XR_SESSION_NOT_FOCUSED while the XR pacing thread handles the matching
|
||||
// session event; Core::SetState may wait for that video thread to reach a safe point.
|
||||
Core::SetState(system, Core::State::Paused);
|
||||
const bool core_is_paused = Core::GetState(system) == Core::State::Paused;
|
||||
bool resume_after_pause = false;
|
||||
{
|
||||
std::lock_guard focus_lock(m_session_focus_mutex);
|
||||
m_focus_pause_pending = false;
|
||||
resume_after_pause = core_is_paused &&
|
||||
(m_session_focused.load(std::memory_order_acquire) ||
|
||||
m_resume_after_pending_focus_pause);
|
||||
m_resume_after_pending_focus_pause = false;
|
||||
m_emulation_paused_for_focus_loss = core_is_paused && !resume_after_pause;
|
||||
if (resume_after_pause)
|
||||
m_focus_loss_pause_attempted = false;
|
||||
}
|
||||
|
||||
if (core_is_paused && resume_after_pause)
|
||||
Core::SetState(system, Core::State::Running);
|
||||
}
|
||||
|
||||
void OpenXRManager::UpdateHaptics()
|
||||
{
|
||||
if (!m_session_running || m_session == XR_NULL_HANDLE || m_action_haptic == XR_NULL_HANDLE)
|
||||
@@ -2222,6 +2309,7 @@ void OpenXRManager::UpdateInputActions()
|
||||
{
|
||||
// XR_SESSION_NOT_FOCUSED is a success code but means input is inactive.
|
||||
// Don't reset state — keep previous connected status to avoid flicker in UI.
|
||||
SetSessionFocused(false);
|
||||
return;
|
||||
}
|
||||
if (XR_FAILED(sync_result))
|
||||
@@ -2229,6 +2317,7 @@ void OpenXRManager::UpdateInputActions()
|
||||
ResetInputActionsState();
|
||||
return;
|
||||
}
|
||||
SetSessionFocused(true);
|
||||
|
||||
std::array<Common::VR::OpenXRControllerState, 2> controllers{};
|
||||
std::array<bool, 2> actions_active{};
|
||||
@@ -2558,15 +2647,15 @@ void OpenXRManager::HandleSessionStateChange(XrSessionState new_state)
|
||||
break;
|
||||
}
|
||||
case XR_SESSION_STATE_SYNCHRONIZED:
|
||||
m_session_focused = false;
|
||||
SetSessionFocused(false);
|
||||
break;
|
||||
|
||||
case XR_SESSION_STATE_VISIBLE:
|
||||
m_session_focused = false;
|
||||
SetSessionFocused(false);
|
||||
break;
|
||||
|
||||
case XR_SESSION_STATE_FOCUSED:
|
||||
m_session_focused = true;
|
||||
SetSessionFocused(true);
|
||||
INFO_LOG_FMT(OPENXR, "OpenXR: Session FOCUSED — controller input is now active.");
|
||||
#if defined(ANDROID)
|
||||
// Meta Quest may ignore perf settings before the session is FOCUSED. Re-issue
|
||||
@@ -2584,7 +2673,7 @@ void OpenXRManager::HandleSessionStateChange(XrSessionState new_state)
|
||||
static_cast<int>(end_result));
|
||||
}
|
||||
m_session_running = false;
|
||||
m_session_focused = false;
|
||||
SetSessionFocused(false, false);
|
||||
ResetInputActionsState();
|
||||
INFO_LOG_FMT(OPENXR, "OpenXR: Session stopped.");
|
||||
break;
|
||||
@@ -2594,7 +2683,7 @@ void OpenXRManager::HandleSessionStateChange(XrSessionState new_state)
|
||||
case XR_SESSION_STATE_EXITING:
|
||||
m_exit_render_loop = true;
|
||||
m_session_running = false;
|
||||
m_session_focused = false;
|
||||
SetSessionFocused(false, false);
|
||||
ResetInputActionsState();
|
||||
break;
|
||||
|
||||
@@ -2821,15 +2910,20 @@ bool OpenXRManager::EndFrameDetached(XrTime display_time,
|
||||
const std::vector<XrCompositionLayerBaseHeader*>& layers,
|
||||
bool lock_graphics_queue)
|
||||
{
|
||||
const bool session_focused = m_session_focused.load(std::memory_order_acquire);
|
||||
const bool can_submit_layers = should_render && session_focused;
|
||||
const bool passthrough_requested =
|
||||
g_ActiveConfig.VRPassthroughBackgroundEnabled() && m_swapchain != nullptr &&
|
||||
session_focused;
|
||||
// Keep the FB passthrough feed in sync with the Passthrough setting. Gated on a
|
||||
// registered swapchain so the controller-binding utility session never starts it.
|
||||
UpdateFBPassthrough(g_ActiveConfig.VRPassthroughBackgroundEnabled() && m_swapchain != nullptr);
|
||||
UpdateFBPassthrough(passthrough_requested);
|
||||
|
||||
// The passthrough layer composites first (behind the projection layer), replacing the
|
||||
// black void with the camera feed wherever the projection layer's alpha is 0.
|
||||
std::vector<XrCompositionLayerBaseHeader*> submit_layers;
|
||||
submit_layers.reserve(layers.size() + 1);
|
||||
if (m_fb_passthrough_running && m_fb_passthrough_layer != XR_NULL_HANDLE)
|
||||
if (can_submit_layers && m_fb_passthrough_running && m_fb_passthrough_layer != XR_NULL_HANDLE)
|
||||
{
|
||||
m_fb_passthrough_composition_layer = {XR_TYPE_COMPOSITION_LAYER_PASSTHROUGH_FB};
|
||||
m_fb_passthrough_composition_layer.flags = XR_COMPOSITION_LAYER_BLEND_TEXTURE_SOURCE_ALPHA_BIT;
|
||||
@@ -2838,15 +2932,16 @@ bool OpenXRManager::EndFrameDetached(XrTime display_time,
|
||||
submit_layers.push_back(
|
||||
reinterpret_cast<XrCompositionLayerBaseHeader*>(&m_fb_passthrough_composition_layer));
|
||||
}
|
||||
submit_layers.insert(submit_layers.end(), layers.begin(), layers.end());
|
||||
if (can_submit_layers)
|
||||
submit_layers.insert(submit_layers.end(), layers.begin(), layers.end());
|
||||
|
||||
XrFrameEndInfo end_info{XR_TYPE_FRAME_END_INFO};
|
||||
end_info.displayTime = display_time;
|
||||
end_info.environmentBlendMode = environment_blend_mode;
|
||||
|
||||
// Only submit layers when the runtime requests rendering; otherwise submit 0 layers
|
||||
// (this handles the VISIBLE/SYNCHRONIZED states correctly).
|
||||
if (should_render)
|
||||
// Only submit layers when the runtime requests rendering and the session is focused.
|
||||
// Submit 0 layers in VISIBLE/SYNCHRONIZED states, including system room view.
|
||||
if (can_submit_layers)
|
||||
{
|
||||
end_info.layerCount = static_cast<uint32_t>(submit_layers.size());
|
||||
end_info.layers = submit_layers.data();
|
||||
|
||||
@@ -501,6 +501,7 @@ private:
|
||||
void EnsureHomePositionFromCurrentViews() const;
|
||||
std::array<XREyeView, 2> GetTrackingAdjustedEyeViews() const;
|
||||
void ResetInputActionsState();
|
||||
void SetSessionFocused(bool focused, bool pause_emulation_when_unfocused = true);
|
||||
void HandleSessionStateChange(XrSessionState new_state);
|
||||
// World-locked quad pose for the flat panel. Refresh from video-thread view data after a
|
||||
// reference-space rebase; serialize readers because input sampling also queries this pose.
|
||||
@@ -622,6 +623,11 @@ private:
|
||||
XrSessionState m_session_state = XR_SESSION_STATE_UNKNOWN;
|
||||
std::atomic<bool> m_session_running{false};
|
||||
std::atomic<bool> m_session_focused{false};
|
||||
std::mutex m_session_focus_mutex;
|
||||
bool m_focus_loss_pause_attempted = false;
|
||||
bool m_emulation_paused_for_focus_loss = false;
|
||||
bool m_focus_pause_pending = false;
|
||||
bool m_resume_after_pending_focus_pause = false;
|
||||
bool m_exit_render_loop = false;
|
||||
|
||||
XrFrameState m_frame_state{XR_TYPE_FRAME_STATE};
|
||||
|
||||
+11
-7
@@ -25,14 +25,18 @@ The old Flatpak application build and deployment scripts are archived in
|
||||
From the project root:
|
||||
|
||||
```bash
|
||||
INSTALL_PREFIX=/home/steamos/baiju ./scripts/compile-cross-sdk.sh
|
||||
INSTALL_PREFIX=/home/steamos/baiji ./scripts/compile-cross-sdk.sh
|
||||
./scripts/deploy-frame.sh frame
|
||||
```
|
||||
|
||||
The build installs locally under `state/stage-frame/home/steamos/baiju` before
|
||||
Steam Frame StereoScreen submits both eye blits together. The submit worker waits for the pair's
|
||||
GPU fence, then releases both swapchain images and publishes the quad layers; while the pair is
|
||||
pending, the video thread can advance and the XR pacing thread reprojects the last completed pair.
|
||||
|
||||
The build installs locally under `state/stage-frame/home/steamos/baiji` before
|
||||
deployment. App executables use the `baiji` names. Private dynamic libraries are
|
||||
installed below `~/baiju/lib/baiji`; application data is in `~/baiju/share/baiji`.
|
||||
Localized catalogs, when available, are kept under `~/baiju/share/baiji/locale` so the
|
||||
installed below `~/baiji/lib/baiji`; application data is in `~/baiji/share/baiji`.
|
||||
Localized catalogs, when available, are kept under `~/baiji/share/baiji/locale` so the
|
||||
app never reads the existing install's shared locale directory.
|
||||
No Dolphin-named executable, last-good binary, or legacy user-data directory is used
|
||||
or modified.
|
||||
@@ -43,14 +47,14 @@ builds.
|
||||
Smoke-test the CLI on the Frame:
|
||||
|
||||
```bash
|
||||
ssh frame 'LD_LIBRARY_PATH="$HOME/baiju/lib/baiji" "$HOME/baiju/bin/baiji-tool" --help'
|
||||
ssh frame 'LD_LIBRARY_PATH="$HOME/baiju/lib/baiji" "$HOME/baiju/bin/baiji-nogui" --help'
|
||||
ssh frame 'LD_LIBRARY_PATH="$HOME/baiji/lib/baiji" "$HOME/baiji/bin/baiji-tool" --help'
|
||||
ssh frame 'LD_LIBRARY_PATH="$HOME/baiji/lib/baiji" "$HOME/baiji/bin/baiji-nogui" --help'
|
||||
```
|
||||
|
||||
To launch the Baiji GUI in the nested X11 desktop with the SteamVR runtime:
|
||||
|
||||
```bash
|
||||
ssh frame 'BAIJI_DISPLAY=:0 "$HOME/baiju/bin/baiji-vr"'
|
||||
ssh frame 'BAIJI_DISPLAY=:0 "$HOME/baiji/bin/baiji-vr"'
|
||||
```
|
||||
|
||||
The launcher preserves the Steam-provided `DISPLAY` (defaulting to `:2` for a direct
|
||||
|
||||
+2
-4
@@ -2,9 +2,6 @@
|
||||
set -euo pipefail
|
||||
|
||||
BAIJI_ROOT="${BAIJI_ROOT:-$HOME/baiji}"
|
||||
# LOG_DIR="$HOME/.local/share/baiji/Logs"
|
||||
# mkdir -p "$LOG_DIR" "$HOME/.config/baiji" "$HOME/.cache/baiji"
|
||||
|
||||
export DISPLAY="${BAIJI_DISPLAY:-${DISPLAY:-:2}}"
|
||||
export XDG_RUNTIME_DIR="${XDG_RUNTIME_DIR:-/run/user/$(id -u)}"
|
||||
export XR_RUNTIME_JSON="${XR_RUNTIME_JSON:-/opt/steamvr/steamxr_linuxarm64.json}"
|
||||
@@ -21,6 +18,8 @@ for auth in /run/user/1000/nested_plasma/xauth_*; do
|
||||
done
|
||||
|
||||
if [[ -f "$HOME/.config/baiji/debug-logging" ]]; then
|
||||
LOG_DIR="$HOME/.local/share/baiji/Logs"
|
||||
mkdir -p "$LOG_DIR"
|
||||
exec "$BAIJI_ROOT/bin/baiji" \
|
||||
-C Logger.Options.WriteToFile=True \
|
||||
-C Logger.Options.Verbosity=5 \
|
||||
@@ -31,4 +30,3 @@ if [[ -f "$HOME/.config/baiji/debug-logging" ]]; then
|
||||
fi
|
||||
|
||||
exec "$BAIJI_ROOT/bin/baiji" "$@" 2>&1 > /dev/null
|
||||
|
||||
Reference in new issue
Block a user