Merge branch 'master' into vulkan
This commit is contained in:
commit
ecccd02f8a
|
@ -34,6 +34,63 @@ up later, so be sure to check it out.
|
|||
|
||||
Basically: run `xb format` before you add a commit and you won't have a problem.
|
||||
|
||||
## Referencing Sources
|
||||
|
||||
In code interacting with guest interfaces or protocols, where applicable, please
|
||||
leave comments describing how the information included in it was obtained. For
|
||||
code based on analysis of the response of the original Xbox 360 hardware or
|
||||
software, please provide reproduction information, such as an outline of the
|
||||
algorithm executed, arguments and results of function calls or processor
|
||||
instructions involved, GPU or XMA commands and state register changes. Having
|
||||
traceable sources helps solve multiple issues:
|
||||
|
||||
* The legality of the submitted code can be verified easily.
|
||||
* Additional analysis based on reproduction steps from prior research can be
|
||||
done to discover more details or to research the behavior of other related
|
||||
features.
|
||||
* The accuracy and completeness of the information can be evaluated. Knowing
|
||||
whether something is ground truth about the console's behavior or an
|
||||
approximation (for example, based on similar functionality in Windows, the
|
||||
Qualcomm Adreno 200 GPU, AMD's desktop GPU architectures; the Direct3D 11.3
|
||||
functional specification, which may be used as a generic fallback when no
|
||||
information specific to the Xenos or Direct3D 9 is available) may help avoid
|
||||
redoing work that has already been done if the findings are accurate, or
|
||||
making potentially wrong conclusions about related functionality if there's no
|
||||
certainty about the correctness of the information. In addition, it's useful
|
||||
to describe how complete the implementation of a feature is — such as edge
|
||||
cases checked and covered. If you are unsure if your code accurately reflects
|
||||
the behavior of the console, or you have deliberately made deviations due to
|
||||
the lack of prerequisites for an accurate implementation or for performance
|
||||
reasons (in case of the latter, it's recommended to provide both options,
|
||||
selectable with a configuration variable), or you just want more research to
|
||||
be done in the future, please leave a TODO comment in the format provided in
|
||||
[style_guide.md](../docs/style_guide.md).
|
||||
|
||||
If you have verified your code by checking the correctness of the behavior of a
|
||||
game, **do not** refer to it by its title trademark. To avoid unnecessary
|
||||
dependencies on third parties, instead, use the hexadecimal title ID number
|
||||
displayed in the title bar beside the name of the game. It's also recommended to
|
||||
avoid using proper names of game content if they can be replaced with easily
|
||||
understandable pointers not including them, such as "first mission",
|
||||
"protagonist", "enemy aircraft".
|
||||
|
||||
Do not leave any hard-coded references to specific games, even in title ID form,
|
||||
in any part of the user interface, including the configuration file. If you want
|
||||
to provide an example of a game where changing a configuration variable may have
|
||||
a noticeable effect, use a code comment near the declaration of the variable
|
||||
rather than its description string. Any game identifiers referenced in the user
|
||||
interface must be obtained only from information provided by the user such as
|
||||
game data files.
|
||||
|
||||
Also, do not put any conditionals based on hard-coded identifiers of games — the
|
||||
task of the project is researching the Xbox 360 console itself and documenting
|
||||
its behavior by creating open implementations of its interfaces. Game-specific
|
||||
hacks provide no help in achieving that, instead only complicating research by
|
||||
introducing incorrect state and hiding the symptoms of actual issues. While
|
||||
temporary workarounds, though discouraged, may be added in cases when progress
|
||||
would be blocked otherwise in other areas, they must be expressed and reasoned
|
||||
in terms of the common interface rather than logic internal to a specific game.
|
||||
|
||||
## Clean Git History
|
||||
|
||||
Tools such as `git bisect` are used on the repository regularly to check for and
|
||||
|
|
12
premake5.lua
12
premake5.lua
|
@ -189,6 +189,12 @@ filter("platforms:Windows")
|
|||
"bcrypt",
|
||||
})
|
||||
|
||||
-- Embed the manifest for things like dependencies and DPI awareness.
|
||||
filter({"platforms:Windows", "kind:ConsoleApp or WindowedApp"})
|
||||
files({
|
||||
"src/xenia/base/app_win32.manifest"
|
||||
})
|
||||
|
||||
-- Create scratch/ path
|
||||
if not os.isdir("scratch") then
|
||||
os.mkdir("scratch")
|
||||
|
@ -242,9 +248,13 @@ workspace("xenia")
|
|||
include("third_party/SDL2.lua")
|
||||
end
|
||||
|
||||
-- Disable treating warnings as fatal errors for all third party projects:
|
||||
-- Disable treating warnings as fatal errors for all third party projects, as
|
||||
-- well as other things relevant only to Xenia itself.
|
||||
for _, prj in ipairs(premake.api.scope.current.solution.projects) do
|
||||
project(prj.name)
|
||||
removefiles({
|
||||
"src/xenia/base/app_win32.manifest"
|
||||
})
|
||||
removeflags({
|
||||
"FatalWarnings",
|
||||
})
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
|
||||
#include "third_party/fmt/include/fmt/format.h"
|
||||
#include "third_party/imgui/imgui.h"
|
||||
#include "xenia/base/assert.h"
|
||||
#include "xenia/base/clock.h"
|
||||
#include "xenia/base/cvar.h"
|
||||
#include "xenia/base/debugging.h"
|
||||
|
@ -40,12 +41,13 @@ using xe::ui::MenuItem;
|
|||
using xe::ui::MouseEvent;
|
||||
using xe::ui::UIEvent;
|
||||
|
||||
const std::string kBaseTitle = "xenia";
|
||||
const std::string kBaseTitle = "Xenia";
|
||||
|
||||
EmulatorWindow::EmulatorWindow(Emulator* emulator)
|
||||
EmulatorWindow::EmulatorWindow(Emulator* emulator,
|
||||
ui::WindowedAppContext& app_context)
|
||||
: emulator_(emulator),
|
||||
loop_(ui::Loop::Create()),
|
||||
window_(ui::Window::Create(loop_.get(), kBaseTitle)) {
|
||||
app_context_(app_context),
|
||||
window_(ui::Window::Create(app_context, kBaseTitle)) {
|
||||
base_title_ = kBaseTitle +
|
||||
#ifdef DEBUG
|
||||
#if _NO_DEBUG_HEAP == 1
|
||||
|
@ -54,27 +56,23 @@ EmulatorWindow::EmulatorWindow(Emulator* emulator)
|
|||
" CHECKED"
|
||||
#endif
|
||||
#endif
|
||||
" (" XE_BUILD_BRANCH "/" XE_BUILD_COMMIT_SHORT "/" XE_BUILD_DATE
|
||||
" ("
|
||||
#ifdef XE_BUILD_IS_PR
|
||||
"PR#" XE_BUILD_PR_NUMBER " " XE_BUILD_PR_REPO
|
||||
" " XE_BUILD_PR_BRANCH "@" XE_BUILD_PR_COMMIT_SHORT " against "
|
||||
#endif
|
||||
XE_BUILD_BRANCH "@" XE_BUILD_COMMIT_SHORT " on " XE_BUILD_DATE
|
||||
")";
|
||||
}
|
||||
|
||||
EmulatorWindow::~EmulatorWindow() {
|
||||
loop_->PostSynchronous([this]() { window_.reset(); });
|
||||
}
|
||||
|
||||
std::unique_ptr<EmulatorWindow> EmulatorWindow::Create(Emulator* emulator) {
|
||||
std::unique_ptr<EmulatorWindow> emulator_window(new EmulatorWindow(emulator));
|
||||
|
||||
emulator_window->loop()->PostSynchronous([&emulator_window]() {
|
||||
xe::threading::set_name("Windowing Loop");
|
||||
xe::Profiler::ThreadEnter("Windowing Loop");
|
||||
|
||||
if (!emulator_window->Initialize()) {
|
||||
xe::FatalError("Failed to initialize main window");
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
std::unique_ptr<EmulatorWindow> EmulatorWindow::Create(
|
||||
Emulator* emulator, ui::WindowedAppContext& app_context) {
|
||||
assert_true(app_context.IsInUIThread());
|
||||
std::unique_ptr<EmulatorWindow> emulator_window(
|
||||
new EmulatorWindow(emulator, app_context));
|
||||
if (!emulator_window->Initialize()) {
|
||||
return nullptr;
|
||||
}
|
||||
return emulator_window;
|
||||
}
|
||||
|
||||
|
@ -86,8 +84,8 @@ bool EmulatorWindow::Initialize() {
|
|||
|
||||
UpdateTitle();
|
||||
|
||||
window_->on_closed.AddListener([this](UIEvent* e) { loop_->Quit(); });
|
||||
loop_->on_quit.AddListener([this](UIEvent* e) { window_.reset(); });
|
||||
window_->on_closed.AddListener(
|
||||
[this](UIEvent* e) { app_context_.QuitFromUIThread(); });
|
||||
|
||||
window_->on_file_drop.AddListener(
|
||||
[this](FileDropEvent* e) { FileDrop(e->filename()); });
|
||||
|
@ -308,7 +306,7 @@ void EmulatorWindow::FileOpen() {
|
|||
file_picker->set_multi_selection(false);
|
||||
file_picker->set_title("Select Content Package");
|
||||
file_picker->set_extensions({
|
||||
{"Supported Files", "*.iso;*.xex;*.xcp;*.*"},
|
||||
{"Supported Files", "*.iso;*.xex;*.*"},
|
||||
{"Disc Image (*.iso)", "*.iso"},
|
||||
{"Xbox Executable (*.xex)", "*.xex"},
|
||||
//{"Content Package (*.xcp)", "*.xcp" },
|
||||
|
@ -426,8 +424,13 @@ void EmulatorWindow::ToggleFullscreen() {
|
|||
void EmulatorWindow::ShowHelpWebsite() { LaunchWebBrowser("https://xenia.jp"); }
|
||||
|
||||
void EmulatorWindow::ShowCommitID() {
|
||||
#ifdef XE_BUILD_IS_PR
|
||||
LaunchWebBrowser(
|
||||
"https://github.com/xenia-project/xenia/pull/" XE_BUILD_PR_NUMBER);
|
||||
#else
|
||||
LaunchWebBrowser(
|
||||
"https://github.com/xenia-project/xenia/commit/" XE_BUILD_COMMIT "/");
|
||||
#endif
|
||||
}
|
||||
|
||||
void EmulatorWindow::UpdateTitle() {
|
||||
|
|
|
@ -13,9 +13,9 @@
|
|||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "xenia/ui/loop.h"
|
||||
#include "xenia/ui/menu_item.h"
|
||||
#include "xenia/ui/window.h"
|
||||
#include "xenia/ui/windowed_app_context.h"
|
||||
#include "xenia/xbox.h"
|
||||
|
||||
namespace xe {
|
||||
|
@ -27,12 +27,11 @@ namespace app {
|
|||
|
||||
class EmulatorWindow {
|
||||
public:
|
||||
virtual ~EmulatorWindow();
|
||||
|
||||
static std::unique_ptr<EmulatorWindow> Create(Emulator* emulator);
|
||||
static std::unique_ptr<EmulatorWindow> Create(
|
||||
Emulator* emulator, ui::WindowedAppContext& app_context);
|
||||
|
||||
Emulator* emulator() const { return emulator_; }
|
||||
ui::Loop* loop() const { return loop_.get(); }
|
||||
ui::WindowedAppContext& app_context() const { return app_context_; }
|
||||
ui::Window* window() const { return window_.get(); }
|
||||
|
||||
void UpdateTitle();
|
||||
|
@ -40,7 +39,8 @@ class EmulatorWindow {
|
|||
void SetInitializingShaderStorage(bool initializing);
|
||||
|
||||
private:
|
||||
explicit EmulatorWindow(Emulator* emulator);
|
||||
explicit EmulatorWindow(Emulator* emulator,
|
||||
ui::WindowedAppContext& app_context);
|
||||
|
||||
bool Initialize();
|
||||
|
||||
|
@ -60,7 +60,7 @@ class EmulatorWindow {
|
|||
void ShowCommitID();
|
||||
|
||||
Emulator* emulator_;
|
||||
std::unique_ptr<ui::Loop> loop_;
|
||||
ui::WindowedAppContext& app_context_;
|
||||
std::unique_ptr<ui::Window> window_;
|
||||
std::string base_title_;
|
||||
uint64_t cursor_hide_time_ = 0;
|
||||
|
|
|
@ -50,8 +50,8 @@ project("xenia-app")
|
|||
local_platform_files()
|
||||
files({
|
||||
"xenia_main.cc",
|
||||
"../base/main_"..platform_suffix..".cc",
|
||||
"../base/main_init_"..platform_suffix..".cc",
|
||||
"../ui/windowed_app_main_"..platform_suffix..".cc",
|
||||
})
|
||||
|
||||
resincludedirs({
|
||||
|
@ -71,7 +71,6 @@ project("xenia-app")
|
|||
"X11",
|
||||
"xcb",
|
||||
"X11-xcb",
|
||||
"vulkan",
|
||||
"SDL2",
|
||||
})
|
||||
|
||||
|
|
|
@ -7,18 +7,29 @@
|
|||
******************************************************************************
|
||||
*/
|
||||
|
||||
#include <atomic>
|
||||
#include <cstdlib>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
#include "xenia/app/discord/discord_presence.h"
|
||||
#include "xenia/app/emulator_window.h"
|
||||
#include "xenia/base/cvar.h"
|
||||
#include "xenia/base/debugging.h"
|
||||
#include "xenia/base/logging.h"
|
||||
#include "xenia/base/main.h"
|
||||
#include "xenia/base/platform.h"
|
||||
#include "xenia/base/profiling.h"
|
||||
#include "xenia/base/threading.h"
|
||||
#include "xenia/config.h"
|
||||
#include "xenia/debug/ui/debug_window.h"
|
||||
#include "xenia/emulator.h"
|
||||
#include "xenia/ui/file_picker.h"
|
||||
#include "xenia/ui/window.h"
|
||||
#include "xenia/ui/windowed_app.h"
|
||||
#include "xenia/ui/windowed_app_context.h"
|
||||
#include "xenia/vfs/devices/host_path_device.h"
|
||||
|
||||
// Available audio systems:
|
||||
|
@ -44,7 +55,6 @@
|
|||
#endif // XE_PLATFORM_WIN32
|
||||
|
||||
#include "third_party/fmt/include/fmt/format.h"
|
||||
#include "third_party/xbyak/xbyak/xbyak_util.h"
|
||||
|
||||
DEFINE_string(apu, "any", "Audio system. Use: [any, nop, sdl, xaudio2]", "APU");
|
||||
DEFINE_string(gpu, "any", "Graphics system. Use: [any, d3d12, vulkan, null]",
|
||||
|
@ -91,83 +101,134 @@ DEFINE_bool(discord, true, "Enable Discord rich presence", "General");
|
|||
namespace xe {
|
||||
namespace app {
|
||||
|
||||
template <typename T, typename... Args>
|
||||
class Factory {
|
||||
class EmulatorApp final : public xe::ui::WindowedApp {
|
||||
public:
|
||||
static std::unique_ptr<xe::ui::WindowedApp> Create(
|
||||
xe::ui::WindowedAppContext& app_context) {
|
||||
return std::unique_ptr<xe::ui::WindowedApp>(new EmulatorApp(app_context));
|
||||
}
|
||||
|
||||
~EmulatorApp();
|
||||
|
||||
bool OnInitialize() override;
|
||||
|
||||
protected:
|
||||
void OnDestroy() override;
|
||||
|
||||
private:
|
||||
struct Creator {
|
||||
std::string name;
|
||||
std::function<bool()> is_available;
|
||||
std::function<std::unique_ptr<T>(Args...)> instantiate;
|
||||
template <typename T, typename... Args>
|
||||
class Factory {
|
||||
private:
|
||||
struct Creator {
|
||||
std::string name;
|
||||
std::function<bool()> is_available;
|
||||
std::function<std::unique_ptr<T>(Args...)> instantiate;
|
||||
};
|
||||
|
||||
std::vector<Creator> creators_;
|
||||
|
||||
public:
|
||||
void Add(const std::string_view name, std::function<bool()> is_available,
|
||||
std::function<std::unique_ptr<T>(Args...)> instantiate) {
|
||||
creators_.push_back({std::string(name), is_available, instantiate});
|
||||
}
|
||||
|
||||
void Add(const std::string_view name,
|
||||
std::function<std::unique_ptr<T>(Args...)> instantiate) {
|
||||
auto always_available = []() { return true; };
|
||||
Add(name, always_available, instantiate);
|
||||
}
|
||||
|
||||
template <typename DT>
|
||||
void Add(const std::string_view name) {
|
||||
Add(name, DT::IsAvailable, [](Args... args) {
|
||||
return std::make_unique<DT>(std::forward<Args>(args)...);
|
||||
});
|
||||
}
|
||||
|
||||
std::unique_ptr<T> Create(const std::string_view name, Args... args) {
|
||||
if (!name.empty() && name != "any") {
|
||||
auto it = std::find_if(
|
||||
creators_.cbegin(), creators_.cend(),
|
||||
[&name](const auto& f) { return name.compare(f.name) == 0; });
|
||||
if (it != creators_.cend() && (*it).is_available()) {
|
||||
return (*it).instantiate(std::forward<Args>(args)...);
|
||||
}
|
||||
return nullptr;
|
||||
} else {
|
||||
for (const auto& creator : creators_) {
|
||||
if (!creator.is_available()) continue;
|
||||
auto instance = creator.instantiate(std::forward<Args>(args)...);
|
||||
if (!instance) continue;
|
||||
return instance;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::unique_ptr<T>> CreateAll(const std::string_view name,
|
||||
Args... args) {
|
||||
std::vector<std::unique_ptr<T>> instances;
|
||||
if (!name.empty() && name != "any") {
|
||||
auto it = std::find_if(
|
||||
creators_.cbegin(), creators_.cend(),
|
||||
[&name](const auto& f) { return name.compare(f.name) == 0; });
|
||||
if (it != creators_.cend() && (*it).is_available()) {
|
||||
auto instance = (*it).instantiate(std::forward<Args>(args)...);
|
||||
if (instance) {
|
||||
instances.emplace_back(std::move(instance));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (const auto& creator : creators_) {
|
||||
if (!creator.is_available()) continue;
|
||||
auto instance = creator.instantiate(std::forward<Args>(args)...);
|
||||
if (instance) {
|
||||
instances.emplace_back(std::move(instance));
|
||||
}
|
||||
}
|
||||
}
|
||||
return instances;
|
||||
}
|
||||
};
|
||||
|
||||
std::vector<Creator> creators_;
|
||||
explicit EmulatorApp(xe::ui::WindowedAppContext& app_context);
|
||||
|
||||
public:
|
||||
void Add(const std::string_view name, std::function<bool()> is_available,
|
||||
std::function<std::unique_ptr<T>(Args...)> instantiate) {
|
||||
creators_.push_back({std::string(name), is_available, instantiate});
|
||||
}
|
||||
static std::unique_ptr<apu::AudioSystem> CreateAudioSystem(
|
||||
cpu::Processor* processor);
|
||||
static std::unique_ptr<gpu::GraphicsSystem> CreateGraphicsSystem();
|
||||
static std::vector<std::unique_ptr<hid::InputDriver>> CreateInputDrivers(
|
||||
ui::Window* window);
|
||||
|
||||
void Add(const std::string_view name,
|
||||
std::function<std::unique_ptr<T>(Args...)> instantiate) {
|
||||
auto always_available = []() { return true; };
|
||||
Add(name, always_available, instantiate);
|
||||
}
|
||||
void EmulatorThread();
|
||||
void ShutdownEmulatorThreadFromUIThread();
|
||||
|
||||
template <typename DT>
|
||||
void Add(const std::string_view name) {
|
||||
Add(name, DT::IsAvailable, [](Args... args) {
|
||||
return std::make_unique<DT>(std::forward<Args>(args)...);
|
||||
});
|
||||
}
|
||||
std::unique_ptr<Emulator> emulator_;
|
||||
std::unique_ptr<EmulatorWindow> emulator_window_;
|
||||
|
||||
std::unique_ptr<T> Create(const std::string_view name, Args... args) {
|
||||
if (!name.empty() && name != "any") {
|
||||
auto it = std::find_if(
|
||||
creators_.cbegin(), creators_.cend(),
|
||||
[&name](const auto& f) { return name.compare(f.name) == 0; });
|
||||
if (it != creators_.cend() && (*it).is_available()) {
|
||||
return (*it).instantiate(std::forward<Args>(args)...);
|
||||
}
|
||||
return nullptr;
|
||||
} else {
|
||||
for (const auto& creator : creators_) {
|
||||
if (!creator.is_available()) continue;
|
||||
auto instance = creator.instantiate(std::forward<Args>(args)...);
|
||||
if (!instance) continue;
|
||||
return instance;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
// Created on demand, used by the emulator.
|
||||
std::unique_ptr<xe::debug::ui::DebugWindow> debug_window_;
|
||||
|
||||
std::vector<std::unique_ptr<T>> CreateAll(const std::string_view name,
|
||||
Args... args) {
|
||||
std::vector<std::unique_ptr<T>> instances;
|
||||
if (!name.empty() && name != "any") {
|
||||
auto it = std::find_if(
|
||||
creators_.cbegin(), creators_.cend(),
|
||||
[&name](const auto& f) { return name.compare(f.name) == 0; });
|
||||
if (it != creators_.cend() && (*it).is_available()) {
|
||||
auto instance = (*it).instantiate(std::forward<Args>(args)...);
|
||||
if (instance) {
|
||||
instances.emplace_back(std::move(instance));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (const auto& creator : creators_) {
|
||||
if (!creator.is_available()) continue;
|
||||
auto instance = creator.instantiate(std::forward<Args>(args)...);
|
||||
if (instance) {
|
||||
instances.emplace_back(std::move(instance));
|
||||
}
|
||||
}
|
||||
}
|
||||
return instances;
|
||||
}
|
||||
// Refreshing the emulator - placed after its dependencies.
|
||||
std::atomic<bool> emulator_thread_quit_requested_;
|
||||
std::unique_ptr<xe::threading::Event> emulator_thread_event_;
|
||||
std::thread emulator_thread_;
|
||||
};
|
||||
|
||||
std::unique_ptr<apu::AudioSystem> CreateAudioSystem(cpu::Processor* processor) {
|
||||
EmulatorApp::EmulatorApp(xe::ui::WindowedAppContext& app_context)
|
||||
: xe::ui::WindowedApp(app_context, "xenia", "[Path to .iso/.xex]") {
|
||||
AddPositionalOption("target");
|
||||
}
|
||||
|
||||
EmulatorApp::~EmulatorApp() {
|
||||
// Should be shut down from OnDestroy if OnInitialize has ever been done, but
|
||||
// for the most safety as a running thread may be destroyed only after
|
||||
// joining.
|
||||
ShutdownEmulatorThreadFromUIThread();
|
||||
}
|
||||
|
||||
std::unique_ptr<apu::AudioSystem> EmulatorApp::CreateAudioSystem(
|
||||
cpu::Processor* processor) {
|
||||
Factory<apu::AudioSystem, cpu::Processor*> factory;
|
||||
#if XE_PLATFORM_WIN32
|
||||
factory.Add<apu::xaudio2::XAudio2AudioSystem>("xaudio2");
|
||||
|
@ -177,7 +238,7 @@ std::unique_ptr<apu::AudioSystem> CreateAudioSystem(cpu::Processor* processor) {
|
|||
return factory.Create(cvars::apu, processor);
|
||||
}
|
||||
|
||||
std::unique_ptr<gpu::GraphicsSystem> CreateGraphicsSystem() {
|
||||
std::unique_ptr<gpu::GraphicsSystem> EmulatorApp::CreateGraphicsSystem() {
|
||||
Factory<gpu::GraphicsSystem> factory;
|
||||
factory.Add<gpu::vulkan::VulkanGraphicsSystem>("vulkan");
|
||||
// TODO(Triang3l): Move D3D12 back to the top.
|
||||
|
@ -188,7 +249,7 @@ std::unique_ptr<gpu::GraphicsSystem> CreateGraphicsSystem() {
|
|||
return factory.Create(cvars::gpu);
|
||||
}
|
||||
|
||||
std::vector<std::unique_ptr<hid::InputDriver>> CreateInputDrivers(
|
||||
std::vector<std::unique_ptr<hid::InputDriver>> EmulatorApp::CreateInputDrivers(
|
||||
ui::Window* window) {
|
||||
std::vector<std::unique_ptr<hid::InputDriver>> drivers;
|
||||
if (cvars::hid.compare("nop") == 0) {
|
||||
|
@ -216,9 +277,9 @@ std::vector<std::unique_ptr<hid::InputDriver>> CreateInputDrivers(
|
|||
return drivers;
|
||||
}
|
||||
|
||||
int xenia_main(const std::vector<std::string>& args) {
|
||||
bool EmulatorApp::OnInitialize() {
|
||||
Profiler::Initialize();
|
||||
Profiler::ThreadEnter("main");
|
||||
Profiler::ThreadEnter("Main");
|
||||
|
||||
// Figure out where internal files and content should go.
|
||||
std::filesystem::path storage_root = cvars::storage_root;
|
||||
|
@ -276,20 +337,55 @@ int xenia_main(const std::vector<std::string>& args) {
|
|||
}
|
||||
|
||||
// Create the emulator but don't initialize so we can setup the window.
|
||||
auto emulator =
|
||||
emulator_ =
|
||||
std::make_unique<Emulator>("", storage_root, content_root, cache_root);
|
||||
|
||||
// Main emulator display window.
|
||||
auto emulator_window = EmulatorWindow::Create(emulator.get());
|
||||
emulator_window_ = EmulatorWindow::Create(emulator_.get(), app_context());
|
||||
if (!emulator_window_) {
|
||||
XELOGE("Failed to create the main emulator window");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Setup the emulator and run its loop in a separate thread.
|
||||
emulator_thread_quit_requested_.store(false, std::memory_order_relaxed);
|
||||
emulator_thread_event_ = xe::threading::Event::CreateAutoResetEvent(false);
|
||||
emulator_thread_ = std::thread(&EmulatorApp::EmulatorThread, this);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void EmulatorApp::OnDestroy() {
|
||||
ShutdownEmulatorThreadFromUIThread();
|
||||
|
||||
if (cvars::discord) {
|
||||
discord::DiscordPresence::Shutdown();
|
||||
}
|
||||
|
||||
Profiler::Dump();
|
||||
// The profiler needs to shut down before the graphics context.
|
||||
Profiler::Shutdown();
|
||||
|
||||
// TODO(DrChat): Remove this code and do a proper exit.
|
||||
XELOGI("Cheap-skate exit!");
|
||||
std::quick_exit(EXIT_SUCCESS);
|
||||
}
|
||||
|
||||
void EmulatorApp::EmulatorThread() {
|
||||
assert_not_null(emulator_thread_event_);
|
||||
|
||||
xe::threading::set_name("Emulator");
|
||||
Profiler::ThreadEnter("Emulator");
|
||||
|
||||
// Setup and initialize all subsystems. If we can't do something
|
||||
// (unsupported system, memory issues, etc) this will fail early.
|
||||
X_STATUS result =
|
||||
emulator->Setup(emulator_window->window(), CreateAudioSystem,
|
||||
CreateGraphicsSystem, CreateInputDrivers);
|
||||
emulator_->Setup(emulator_window_->window(), CreateAudioSystem,
|
||||
CreateGraphicsSystem, CreateInputDrivers);
|
||||
if (XFAILED(result)) {
|
||||
XELOGE("Failed to setup emulator: {:08X}", result);
|
||||
return 1;
|
||||
app_context().RequestDeferredQuit();
|
||||
return;
|
||||
}
|
||||
|
||||
if (cvars::mount_scratch) {
|
||||
|
@ -298,10 +394,11 @@ int xenia_main(const std::vector<std::string>& args) {
|
|||
if (!scratch_device->Initialize()) {
|
||||
XELOGE("Unable to scan scratch path");
|
||||
} else {
|
||||
if (!emulator->file_system()->RegisterDevice(std::move(scratch_device))) {
|
||||
if (!emulator_->file_system()->RegisterDevice(
|
||||
std::move(scratch_device))) {
|
||||
XELOGE("Unable to register scratch path");
|
||||
} else {
|
||||
emulator->file_system()->RegisterSymbolicLink("scratch:", "\\SCRATCH");
|
||||
emulator_->file_system()->RegisterSymbolicLink("scratch:", "\\SCRATCH");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -312,10 +409,10 @@ int xenia_main(const std::vector<std::string>& args) {
|
|||
if (!cache0_device->Initialize()) {
|
||||
XELOGE("Unable to scan cache0 path");
|
||||
} else {
|
||||
if (!emulator->file_system()->RegisterDevice(std::move(cache0_device))) {
|
||||
if (!emulator_->file_system()->RegisterDevice(std::move(cache0_device))) {
|
||||
XELOGE("Unable to register cache0 path");
|
||||
} else {
|
||||
emulator->file_system()->RegisterSymbolicLink("cache0:", "\\CACHE0");
|
||||
emulator_->file_system()->RegisterSymbolicLink("cache0:", "\\CACHE0");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -324,79 +421,78 @@ int xenia_main(const std::vector<std::string>& args) {
|
|||
if (!cache1_device->Initialize()) {
|
||||
XELOGE("Unable to scan cache1 path");
|
||||
} else {
|
||||
if (!emulator->file_system()->RegisterDevice(std::move(cache1_device))) {
|
||||
if (!emulator_->file_system()->RegisterDevice(std::move(cache1_device))) {
|
||||
XELOGE("Unable to register cache1 path");
|
||||
} else {
|
||||
emulator->file_system()->RegisterSymbolicLink("cache1:", "\\CACHE1");
|
||||
emulator_->file_system()->RegisterSymbolicLink("cache1:", "\\CACHE1");
|
||||
}
|
||||
}
|
||||
|
||||
// Some (older?) games try accessing cache:\ too
|
||||
// NOTE: this must be registered _after_ the cache0/cache1 devices, due to
|
||||
// substring/start_with logic inside VirtualFileSystem::ResolvePath, else
|
||||
// accesses to those devices will go here instead
|
||||
auto cache_device =
|
||||
std::make_unique<xe::vfs::HostPathDevice>("\\CACHE", "cache", false);
|
||||
if (!cache_device->Initialize()) {
|
||||
XELOGE("Unable to scan cache path");
|
||||
} else {
|
||||
if (!emulator_->file_system()->RegisterDevice(std::move(cache_device))) {
|
||||
XELOGE("Unable to register cache path");
|
||||
} else {
|
||||
emulator_->file_system()->RegisterSymbolicLink("cache:", "\\CACHE");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set a debug handler.
|
||||
// This will respond to debugging requests so we can open the debug UI.
|
||||
std::unique_ptr<xe::debug::ui::DebugWindow> debug_window;
|
||||
if (cvars::debug) {
|
||||
emulator->processor()->set_debug_listener_request_handler(
|
||||
[&](xe::cpu::Processor* processor) {
|
||||
if (debug_window) {
|
||||
return debug_window.get();
|
||||
emulator_->processor()->set_debug_listener_request_handler(
|
||||
[this](xe::cpu::Processor* processor) {
|
||||
if (debug_window_) {
|
||||
return debug_window_.get();
|
||||
}
|
||||
emulator_window->loop()->PostSynchronous([&]() {
|
||||
debug_window = xe::debug::ui::DebugWindow::Create(
|
||||
emulator.get(), emulator_window->loop());
|
||||
debug_window->window()->on_closed.AddListener(
|
||||
[&](xe::ui::UIEvent* e) {
|
||||
emulator->processor()->set_debug_listener(nullptr);
|
||||
emulator_window->loop()->Post(
|
||||
[&]() { debug_window.reset(); });
|
||||
app_context().CallInUIThreadSynchronous([this]() {
|
||||
debug_window_ = xe::debug::ui::DebugWindow::Create(emulator_.get(),
|
||||
app_context());
|
||||
debug_window_->window()->on_closed.AddListener(
|
||||
[this](xe::ui::UIEvent* e) {
|
||||
emulator_->processor()->set_debug_listener(nullptr);
|
||||
app_context().CallInUIThread(
|
||||
[this]() { debug_window_.reset(); });
|
||||
});
|
||||
});
|
||||
return debug_window.get();
|
||||
// If failed to enqueue the UI thread call, this will just be null.
|
||||
return debug_window_.get();
|
||||
});
|
||||
}
|
||||
|
||||
auto evt = xe::threading::Event::CreateAutoResetEvent(false);
|
||||
emulator->on_launch.AddListener([&](auto title_id, const auto& game_title) {
|
||||
emulator_->on_launch.AddListener([&](auto title_id, const auto& game_title) {
|
||||
if (cvars::discord) {
|
||||
discord::DiscordPresence::PlayingTitle(
|
||||
game_title.empty() ? "Unknown Title" : std::string(game_title));
|
||||
}
|
||||
emulator_window->UpdateTitle();
|
||||
evt->Set();
|
||||
app_context().CallInUIThread([this]() { emulator_window_->UpdateTitle(); });
|
||||
emulator_thread_event_->Set();
|
||||
});
|
||||
|
||||
emulator->on_shader_storage_initialization.AddListener(
|
||||
[&](bool initializing) {
|
||||
emulator_window->SetInitializingShaderStorage(initializing);
|
||||
emulator_->on_shader_storage_initialization.AddListener(
|
||||
[this](bool initializing) {
|
||||
app_context().CallInUIThread([this, initializing]() {
|
||||
emulator_window_->SetInitializingShaderStorage(initializing);
|
||||
});
|
||||
});
|
||||
|
||||
emulator->on_terminate.AddListener([&]() {
|
||||
emulator_->on_terminate.AddListener([]() {
|
||||
if (cvars::discord) {
|
||||
discord::DiscordPresence::NotPlaying();
|
||||
}
|
||||
});
|
||||
|
||||
emulator_window->window()->on_closing.AddListener([&](ui::UIEvent* e) {
|
||||
// This needs to shut down before the graphics context.
|
||||
Profiler::Shutdown();
|
||||
});
|
||||
|
||||
bool exiting = false;
|
||||
emulator_window->loop()->on_quit.AddListener([&](ui::UIEvent* e) {
|
||||
exiting = true;
|
||||
evt->Set();
|
||||
|
||||
if (cvars::discord) {
|
||||
discord::DiscordPresence::Shutdown();
|
||||
}
|
||||
|
||||
// TODO(DrChat): Remove this code and do a proper exit.
|
||||
XELOGI("Cheap-skate exit!");
|
||||
exit(0);
|
||||
});
|
||||
|
||||
// Enable the main menu now that the emulator is properly loaded
|
||||
emulator_window->window()->EnableMainMenu();
|
||||
app_context().CallInUIThread(
|
||||
[this]() { emulator_window_->window()->EnableMainMenu(); });
|
||||
|
||||
// Grab path from the flag or unnamed argument.
|
||||
std::filesystem::path path;
|
||||
|
@ -405,50 +501,56 @@ int xenia_main(const std::vector<std::string>& args) {
|
|||
}
|
||||
|
||||
// Toggles fullscreen
|
||||
if (cvars::fullscreen) emulator_window->ToggleFullscreen();
|
||||
if (cvars::fullscreen) {
|
||||
app_context().CallInUIThread(
|
||||
[this]() { emulator_window_->ToggleFullscreen(); });
|
||||
}
|
||||
|
||||
if (!path.empty()) {
|
||||
// Normalize the path and make absolute.
|
||||
auto abs_path = std::filesystem::absolute(path);
|
||||
result = emulator->LaunchPath(abs_path);
|
||||
result = emulator_->LaunchPath(abs_path);
|
||||
if (XFAILED(result)) {
|
||||
xe::FatalError(fmt::format("Failed to launch target: {:08X}", result));
|
||||
emulator.reset();
|
||||
emulator_window.reset();
|
||||
return 1;
|
||||
app_context().RequestDeferredQuit();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Now, we're going to use the main thread to drive events related to
|
||||
// emulation.
|
||||
while (!exiting) {
|
||||
xe::threading::Wait(evt.get(), false);
|
||||
|
||||
// Now, we're going to use this thread to drive events related to emulation.
|
||||
while (!emulator_thread_quit_requested_.load(std::memory_order_relaxed)) {
|
||||
xe::threading::Wait(emulator_thread_event_.get(), false);
|
||||
while (true) {
|
||||
emulator->WaitUntilExit();
|
||||
if (emulator->TitleRequested()) {
|
||||
emulator->LaunchNextTitle();
|
||||
emulator_->WaitUntilExit();
|
||||
if (emulator_->TitleRequested()) {
|
||||
emulator_->LaunchNextTitle();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
debug_window.reset();
|
||||
emulator.reset();
|
||||
|
||||
if (cvars::discord) {
|
||||
discord::DiscordPresence::Shutdown();
|
||||
void EmulatorApp::ShutdownEmulatorThreadFromUIThread() {
|
||||
// TODO(Triang3l): Proper shutdown of the emulator (relying on std::quick_exit
|
||||
// for now) - currently WaitUntilExit loops forever otherwise (plus possibly
|
||||
// lots of other things not shutting down correctly now). Some parts of the
|
||||
// code call the regular std::exit, which seems to be calling destructors (at
|
||||
// least on Linux), so the entire join is currently commented out.
|
||||
#if 0
|
||||
// Same thread as the one created it, to make sure there's zero possibility of
|
||||
// a race with the creation of the emulator thread.
|
||||
assert_true(app_context().IsInUIThread());
|
||||
emulator_thread_quit_requested_.store(true, std::memory_order_relaxed);
|
||||
if (!emulator_thread_.joinable()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Profiler::Dump();
|
||||
Profiler::Shutdown();
|
||||
emulator_window.reset();
|
||||
return 0;
|
||||
emulator_thread_event_->Set();
|
||||
emulator_thread_.join();
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace app
|
||||
} // namespace xe
|
||||
|
||||
DEFINE_ENTRY_POINT("xenia", xe::app::xenia_main, "[Path to .iso/.xex]",
|
||||
"target");
|
||||
XE_DEFINE_WINDOWED_APP(xenia, xe::app::EmulatorApp::Create);
|
||||
|
|
|
@ -9,13 +9,14 @@
|
|||
|
||||
#include "xenia/apu/xaudio2/xaudio2_audio_driver.h"
|
||||
|
||||
// Must be included before xaudio2.h so we get the right windows.h include.
|
||||
#include "xenia/base/platform_win.h"
|
||||
|
||||
#include "xenia/apu/apu_flags.h"
|
||||
#include "xenia/apu/conversion.h"
|
||||
#include "xenia/apu/xaudio2/xaudio2_api.h"
|
||||
#include "xenia/base/assert.h"
|
||||
#include "xenia/base/clock.h"
|
||||
#include "xenia/base/logging.h"
|
||||
#include "xenia/base/platform_win.h"
|
||||
#include "xenia/base/threading.h"
|
||||
|
||||
namespace xe {
|
||||
namespace apu {
|
||||
|
@ -73,11 +74,6 @@ bool XAudio2AudioDriver::Initialize() {
|
|||
}
|
||||
}
|
||||
|
||||
// First CPU (2.8 default). XAUDIO2_ANY_PROCESSOR (2.7 default) steals too
|
||||
// much time from other things. Ideally should process audio on what roughly
|
||||
// represents thread 4 (5th) on the Xbox 360 (2.7 default on the console), or
|
||||
// even beyond the 6 guest cores.
|
||||
api::XAUDIO2_PROCESSOR processor = 0x00000001;
|
||||
if (api_minor_version_ >= 8) {
|
||||
union {
|
||||
// clang-format off
|
||||
|
@ -94,7 +90,7 @@ bool XAudio2AudioDriver::Initialize() {
|
|||
assert_always();
|
||||
return false;
|
||||
}
|
||||
hr = xaudio2_create(&objects_.api_2_8.audio, 0, processor);
|
||||
hr = xaudio2_create(&objects_.api_2_8.audio, 0, kProcessor);
|
||||
if (FAILED(hr)) {
|
||||
XELOGE("XAudio2Create failed with {:08X}", hr);
|
||||
assert_always();
|
||||
|
@ -102,20 +98,38 @@ bool XAudio2AudioDriver::Initialize() {
|
|||
}
|
||||
return InitializeObjects(objects_.api_2_8);
|
||||
} else {
|
||||
hr = CoCreateInstance(__uuidof(api::XAudio2_7), NULL, CLSCTX_INPROC_SERVER,
|
||||
IID_PPV_ARGS(&objects_.api_2_7.audio));
|
||||
if (FAILED(hr)) {
|
||||
XELOGE("CoCreateInstance for XAudio2 failed with {:08X}", hr);
|
||||
assert_always();
|
||||
// We need to be able to accept frames from any non-STA thread - primarily
|
||||
// from any guest thread, so MTA needs to be used. The AudioDriver, however,
|
||||
// may be initialized from the UI thread, which has the STA concurrency
|
||||
// model, or from another thread regardless of its concurrency model. So,
|
||||
// all management of the objects needs to be performed in MTA. Launch the
|
||||
// lifecycle management thread, which will handle initialization and
|
||||
// shutdown, and also provide a scope for implicit MTA in threads that have
|
||||
// never initialized COM explicitly, which lasts until all threads that have
|
||||
// initialized MTA explicitly have uninitialized it - the thread that holds
|
||||
// the MTA scope needs to be running while other threads are able to submit
|
||||
// frames as they might have not initialized MTA explicitly.
|
||||
// https://devblogs.microsoft.com/oldnewthing/?p=4613
|
||||
assert_false(mta_thread_.joinable());
|
||||
mta_thread_initialization_attempt_completed_ = false;
|
||||
mta_thread_shutdown_requested_ = false;
|
||||
mta_thread_ = std::thread(&XAudio2AudioDriver::MTAThread, this);
|
||||
{
|
||||
std::unique_lock<std::mutex> mta_thread_initialization_completion_lock(
|
||||
mta_thread_initialization_completion_mutex_);
|
||||
while (true) {
|
||||
if (mta_thread_initialization_attempt_completed_) {
|
||||
break;
|
||||
}
|
||||
mta_thread_initialization_completion_cond_.wait(
|
||||
mta_thread_initialization_completion_lock);
|
||||
}
|
||||
}
|
||||
if (!mta_thread_initialization_completion_result_) {
|
||||
mta_thread_.join();
|
||||
return false;
|
||||
}
|
||||
hr = objects_.api_2_7.audio->Initialize(0, processor);
|
||||
if (FAILED(hr)) {
|
||||
XELOGE("IXAudio2::Initialize failed with {:08X}", hr);
|
||||
assert_always();
|
||||
return false;
|
||||
}
|
||||
return InitializeObjects(objects_.api_2_7);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -249,7 +263,16 @@ void XAudio2AudioDriver::Shutdown() {
|
|||
if (api_minor_version_ >= 8) {
|
||||
ShutdownObjects(objects_.api_2_8);
|
||||
} else {
|
||||
ShutdownObjects(objects_.api_2_7);
|
||||
// XAudio 2.7 lifecycle is managed by the MTA thread.
|
||||
if (mta_thread_.joinable()) {
|
||||
{
|
||||
std::unique_lock<std::mutex> mta_thread_shutdown_request_lock(
|
||||
mta_thread_shutdown_request_mutex_);
|
||||
mta_thread_shutdown_requested_ = true;
|
||||
}
|
||||
mta_thread_shutdown_request_cond_.notify_all();
|
||||
mta_thread_.join();
|
||||
}
|
||||
}
|
||||
|
||||
if (xaudio2_module_) {
|
||||
|
@ -283,6 +306,82 @@ void XAudio2AudioDriver::ShutdownObjects(Objects& objects) {
|
|||
}
|
||||
}
|
||||
|
||||
void XAudio2AudioDriver::MTAThread() {
|
||||
xe::threading::set_name("XAudio 2.7 MTA");
|
||||
|
||||
assert_false(mta_thread_initialization_attempt_completed_);
|
||||
|
||||
bool initialized = false;
|
||||
|
||||
// Initializing MTA COM in this thread, as well making other (guest) threads
|
||||
// that don't explicitly call CoInitializeEx implicitly MTA for the period of
|
||||
// time when they can interact with XAudio 2.7 through the XAudio2AudioDriver,
|
||||
// until the CoUninitialize (to be more precise, the CoUninitialize for the
|
||||
// last remaining MTA thread, but we need implicit MTA for the audio here).
|
||||
// https://devblogs.microsoft.com/oldnewthing/?p=4613
|
||||
HRESULT hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
|
||||
if (FAILED(hr)) {
|
||||
XELOGE("XAudio 2.7 MTA thread CoInitializeEx failed with {:08X}", hr);
|
||||
} else {
|
||||
hr = CoCreateInstance(__uuidof(api::XAudio2_7), nullptr,
|
||||
CLSCTX_INPROC_SERVER,
|
||||
IID_PPV_ARGS(&objects_.api_2_7.audio));
|
||||
if (FAILED(hr)) {
|
||||
XELOGE("CoCreateInstance for XAudio2 failed with {:08X}", hr);
|
||||
} else {
|
||||
hr = objects_.api_2_7.audio->Initialize(0, kProcessor);
|
||||
if (FAILED(hr)) {
|
||||
XELOGE("IXAudio2::Initialize failed with {:08X}", hr);
|
||||
} else {
|
||||
if (InitializeObjects(objects_.api_2_7)) {
|
||||
initialized = true;
|
||||
|
||||
// Initialized successfully, await a shutdown request while keeping an
|
||||
// implicit COM MTA scope.
|
||||
|
||||
mta_thread_initialization_completion_result_ = true;
|
||||
{
|
||||
std::unique_lock<std::mutex>
|
||||
mta_thread_initialization_completion_lock(
|
||||
mta_thread_initialization_completion_mutex_);
|
||||
mta_thread_initialization_attempt_completed_ = true;
|
||||
}
|
||||
mta_thread_initialization_completion_cond_.notify_all();
|
||||
|
||||
{
|
||||
std::unique_lock<std::mutex> mta_thread_shutdown_request_lock(
|
||||
mta_thread_shutdown_request_mutex_);
|
||||
while (true) {
|
||||
if (mta_thread_shutdown_requested_) {
|
||||
break;
|
||||
}
|
||||
mta_thread_shutdown_request_cond_.wait(
|
||||
mta_thread_shutdown_request_lock);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Even if InitializeObjects has failed, need to clean up with
|
||||
// ShutdownObjects.
|
||||
ShutdownObjects(objects_.api_2_7);
|
||||
}
|
||||
}
|
||||
CoUninitialize();
|
||||
}
|
||||
|
||||
if (!initialized) {
|
||||
mta_thread_initialization_completion_result_ = false;
|
||||
{
|
||||
// Failed to initialize - notify the threads waiting for the
|
||||
// initialization.
|
||||
std::unique_lock<std::mutex> mta_thread_initialization_completion_lock(
|
||||
mta_thread_initialization_completion_mutex_);
|
||||
mta_thread_initialization_attempt_completed_ = true;
|
||||
}
|
||||
mta_thread_initialization_completion_cond_.notify_all();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace xaudio2
|
||||
} // namespace apu
|
||||
} // namespace xe
|
||||
|
|
|
@ -10,8 +10,13 @@
|
|||
#ifndef XENIA_APU_XAUDIO2_XAUDIO2_AUDIO_DRIVER_H_
|
||||
#define XENIA_APU_XAUDIO2_XAUDIO2_AUDIO_DRIVER_H_
|
||||
|
||||
#include <condition_variable>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
|
||||
#include "xenia/apu/audio_driver.h"
|
||||
#include "xenia/apu/xaudio2/xaudio2_api.h"
|
||||
#include "xenia/base/platform.h"
|
||||
#include "xenia/base/threading.h"
|
||||
|
||||
struct IXAudio2;
|
||||
|
@ -28,17 +33,45 @@ class XAudio2AudioDriver : public AudioDriver {
|
|||
~XAudio2AudioDriver() override;
|
||||
|
||||
bool Initialize();
|
||||
// Must not be called from COM STA threads as MTA XAudio 2.7 may be used. It's
|
||||
// fine to call this from threads that have never initialized COM as
|
||||
// initializing MTA for any thread implicitly initializes it for all threads
|
||||
// not explicitly requesting STA (until COM is uninitialized all threads that
|
||||
// have initialized MTA).
|
||||
// https://devblogs.microsoft.com/oldnewthing/?p=4613
|
||||
void SubmitFrame(uint32_t frame_ptr) override;
|
||||
void Shutdown();
|
||||
|
||||
private:
|
||||
// First CPU (2.8 default). XAUDIO2_ANY_PROCESSOR (2.7 default) steals too
|
||||
// much time from other things. Ideally should process audio on what roughly
|
||||
// represents thread 4 (5th) on the Xbox 360 (2.7 default on the console), or
|
||||
// even beyond the 6 guest cores.
|
||||
api::XAUDIO2_PROCESSOR kProcessor = 0x00000001;
|
||||
|
||||
// For XAudio 2.7, InitializeObjects and ShutdownObjects must be called only
|
||||
// in the lifecycle management thread with COM MTA initialized.
|
||||
template <typename Objects>
|
||||
bool InitializeObjects(Objects& objects);
|
||||
template <typename Objects>
|
||||
void ShutdownObjects(Objects& objects);
|
||||
|
||||
void MTAThread();
|
||||
|
||||
void* xaudio2_module_ = nullptr;
|
||||
uint32_t api_minor_version_ = 7;
|
||||
|
||||
bool mta_thread_initialization_completion_result_;
|
||||
std::mutex mta_thread_initialization_completion_mutex_;
|
||||
std::condition_variable mta_thread_initialization_completion_cond_;
|
||||
bool mta_thread_initialization_attempt_completed_;
|
||||
|
||||
std::mutex mta_thread_shutdown_request_mutex_;
|
||||
std::condition_variable mta_thread_shutdown_request_cond_;
|
||||
bool mta_thread_shutdown_requested_;
|
||||
|
||||
std::thread mta_thread_;
|
||||
|
||||
union {
|
||||
struct {
|
||||
api::IXAudio2_7* audio;
|
||||
|
|
|
@ -811,12 +811,13 @@ void XmaContext::ConvertFrame(const uint8_t** samples, bool is_two_channel,
|
|||
// Loop through every sample, convert and drop it into the output array.
|
||||
// If more than one channel, we need to interleave the samples from each
|
||||
// channel next to each other. Always saturate because FFmpeg output is
|
||||
// not limited to [-1, 1] (for example 1.095 as seen in RDR)
|
||||
// not limited to [-1, 1] (for example 1.095 as seen in 5454082B).
|
||||
constexpr float scale = (1 << 15) - 1;
|
||||
auto out = reinterpret_cast<int16_t*>(output_buffer);
|
||||
|
||||
// For testing of vectorized versions, stereo audio is common in Halo 3, since
|
||||
// the first menu frame; the intro cutscene also has more than 2 channels.
|
||||
// For testing of vectorized versions, stereo audio is common in 4D5307E6,
|
||||
// since the first menu frame; the intro cutscene also has more than 2
|
||||
// channels.
|
||||
#if XE_ARCH_AMD64
|
||||
static_assert(kSamplesPerFrame % 8 == 0);
|
||||
const auto in_channel_0 = reinterpret_cast<const float*>(samples[0]);
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3" manifestVersion="1.0">
|
||||
<dependency>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity type="win32" name="Microsoft.Windows.Common-Controls" version="6.0.0.0" processorArchitecture="*" publicKeyToken="6595b64144ccf1df" language="*" />
|
||||
</dependentAssembly>
|
||||
</dependency>
|
||||
<asmv3:application>
|
||||
<asmv3:windowsSettings>
|
||||
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">True/PM</dpiAware>
|
||||
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitor</dpiAwareness>
|
||||
</asmv3:windowsSettings>
|
||||
</asmv3:application>
|
||||
</assembly>
|
|
@ -0,0 +1,22 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2021 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#ifndef XENIA_BASE_CONSOLE_H_
|
||||
#define XENIA_BASE_CONSOLE_H_
|
||||
|
||||
namespace xe {
|
||||
|
||||
// Returns true if there is a user-visible console attached to receive stdout.
|
||||
bool has_console_attached();
|
||||
|
||||
void AttachConsole();
|
||||
|
||||
} // namespace xe
|
||||
|
||||
#endif // XENIA_BASE_CONSOLE_H_
|
|
@ -2,54 +2,50 @@
|
|||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2020 Ben Vanik. All rights reserved. *
|
||||
* Copyright 2021 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#ifndef XENIA_BASE_MAIN_H_
|
||||
#define XENIA_BASE_MAIN_H_
|
||||
#ifndef XENIA_BASE_CONSOLE_APP_MAIN_H_
|
||||
#define XENIA_BASE_CONSOLE_APP_MAIN_H_
|
||||
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "xenia/base/cvar.h"
|
||||
#include "xenia/base/platform.h"
|
||||
|
||||
namespace xe {
|
||||
|
||||
// Returns true if there is a user-visible console attached to receive stdout.
|
||||
bool has_console_attached();
|
||||
|
||||
void AttachConsole();
|
||||
|
||||
// Extern defined by user code. This must be present for the application to
|
||||
// launch.
|
||||
struct EntryInfo {
|
||||
struct ConsoleAppEntryInfo {
|
||||
std::string name;
|
||||
int (*entry_point)(const std::vector<std::string>& args);
|
||||
bool transparent_options; // no argument parsing
|
||||
std::optional<std::string> positional_usage;
|
||||
std::optional<std::vector<std::string>> positional_options;
|
||||
std::string positional_usage;
|
||||
std::vector<std::string> positional_options;
|
||||
};
|
||||
EntryInfo GetEntryInfo();
|
||||
ConsoleAppEntryInfo GetConsoleAppEntryInfo();
|
||||
|
||||
#define DEFINE_ENTRY_POINT(name, entry_point, positional_usage, ...) \
|
||||
xe::EntryInfo xe::GetEntryInfo() { \
|
||||
// TODO(Triang3l): Multiple console app entry points on Android when a console
|
||||
// activity is added. This is currently for individual executables running on
|
||||
// Termux.
|
||||
|
||||
#define XE_DEFINE_CONSOLE_APP(name, entry_point, positional_usage, ...) \
|
||||
xe::ConsoleAppEntryInfo xe::GetConsoleAppEntryInfo() { \
|
||||
std::initializer_list<std::string> positional_options = {__VA_ARGS__}; \
|
||||
return xe::EntryInfo{ \
|
||||
return xe::ConsoleAppEntryInfo{ \
|
||||
name, entry_point, false, positional_usage, \
|
||||
std::vector<std::string>(std::move(positional_options))}; \
|
||||
}
|
||||
|
||||
// TODO(Joel Linn): Add some way to filter consumed arguments in
|
||||
// cvar::ParseLaunchArguments()
|
||||
#define DEFINE_ENTRY_POINT_TRANSPARENT(name, entry_point) \
|
||||
xe::EntryInfo xe::GetEntryInfo() { \
|
||||
return xe::EntryInfo{name, entry_point, true, std::nullopt, std::nullopt}; \
|
||||
#define XE_DEFINE_CONSOLE_APP_TRANSPARENT(name, entry_point) \
|
||||
xe::ConsoleAppEntryInfo xe::GetConsoleAppEntryInfo() { \
|
||||
return xe::ConsoleAppEntryInfo{name, entry_point, true, std::string(), \
|
||||
std::vector<std::string>()}; \
|
||||
}
|
||||
|
||||
} // namespace xe
|
||||
|
||||
#endif // XENIA_BASE_MAIN_H_
|
||||
#endif // XENIA_BASE_CONSOLE_APP_MAIN_H_
|
|
@ -0,0 +1,10 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2021 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#include "xenia/base/console_app_posix.cc"
|
|
@ -7,42 +7,32 @@
|
|||
******************************************************************************
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "xenia/base/console_app_main.h"
|
||||
#include "xenia/base/cvar.h"
|
||||
#include "xenia/base/main.h"
|
||||
|
||||
#include "xenia/base/filesystem.h"
|
||||
#include "xenia/base/logging.h"
|
||||
#include "xenia/base/string.h"
|
||||
|
||||
namespace xe {
|
||||
|
||||
bool has_console_attached() { return isatty(fileno(stdin)) == 1; }
|
||||
|
||||
void AttachConsole() {}
|
||||
|
||||
} // namespace xe
|
||||
|
||||
extern "C" int main(int argc, char** argv) {
|
||||
auto entry_info = xe::GetEntryInfo();
|
||||
xe::ConsoleAppEntryInfo entry_info = xe::GetConsoleAppEntryInfo();
|
||||
|
||||
if (!entry_info.transparent_options) {
|
||||
cvar::ParseLaunchArguments(argc, argv, entry_info.positional_usage.value(),
|
||||
entry_info.positional_options.value());
|
||||
cvar::ParseLaunchArguments(argc, argv, entry_info.positional_usage,
|
||||
entry_info.positional_options);
|
||||
}
|
||||
|
||||
// Initialize logging. Needs parsed cvars.
|
||||
xe::InitializeLogging(entry_info.name);
|
||||
|
||||
std::vector<std::string> args;
|
||||
for (int n = 0; n < argc; n++) {
|
||||
args.push_back(argv[n]);
|
||||
args.emplace_back(argv[n]);
|
||||
}
|
||||
|
||||
// Initialize logging. Needs parsed FLAGS.
|
||||
xe::InitializeLogging(entry_info.name);
|
||||
|
||||
// Call app-provided entry point.
|
||||
int result = entry_info.entry_point(args);
|
||||
|
||||
xe::ShutdownLogging();
|
||||
|
||||
return result;
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2021 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#include <cstdlib>
|
||||
#include <vector>
|
||||
|
||||
#include "xenia/base/console_app_main.h"
|
||||
#include "xenia/base/main_win.h"
|
||||
|
||||
int main(int argc_ignored, char** argv_ignored) {
|
||||
xe::ConsoleAppEntryInfo entry_info = xe::GetConsoleAppEntryInfo();
|
||||
|
||||
std::vector<std::string> args;
|
||||
if (!xe::ParseWin32LaunchArguments(entry_info.transparent_options,
|
||||
entry_info.positional_usage,
|
||||
entry_info.positional_options, &args)) {
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
int result = xe::InitializeWin32App(entry_info.name);
|
||||
if (result) {
|
||||
return result;
|
||||
}
|
||||
|
||||
result = entry_info.entry_point(args);
|
||||
|
||||
xe::ShutdownWin32App();
|
||||
|
||||
return result;
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2021 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "xenia/base/console.h"
|
||||
|
||||
namespace xe {
|
||||
|
||||
bool has_console_attached() { return isatty(fileno(stdin)) == 1; }
|
||||
|
||||
void AttachConsole() {}
|
||||
|
||||
} // namespace xe
|
|
@ -0,0 +1,60 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2021 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <io.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "xenia/base/console.h"
|
||||
#include "xenia/base/platform_win.h"
|
||||
|
||||
namespace xe {
|
||||
|
||||
// TODO(Triang3l): Set the default depending on the actual subsystem. Currently
|
||||
// it inhibits message boxes.
|
||||
static bool has_console_attached_ = true;
|
||||
|
||||
bool has_console_attached() { return has_console_attached_; }
|
||||
|
||||
static bool has_shell_environment_variable() {
|
||||
size_t size = 0;
|
||||
// Check if SHELL exists
|
||||
// If it doesn't, then we are in a Windows Terminal
|
||||
auto error = getenv_s(&size, nullptr, 0, "SHELL");
|
||||
if (error) {
|
||||
return false;
|
||||
}
|
||||
return !!size;
|
||||
}
|
||||
|
||||
void AttachConsole() {
|
||||
bool has_console = ::AttachConsole(ATTACH_PARENT_PROCESS) == TRUE;
|
||||
if (!has_console || !has_shell_environment_variable()) {
|
||||
// We weren't launched from a console, so just return.
|
||||
has_console_attached_ = false;
|
||||
return;
|
||||
}
|
||||
|
||||
AllocConsole();
|
||||
|
||||
has_console_attached_ = true;
|
||||
|
||||
auto std_handle = (intptr_t)GetStdHandle(STD_OUTPUT_HANDLE);
|
||||
auto con_handle = _open_osfhandle(std_handle, _O_TEXT);
|
||||
auto fp = _fdopen(con_handle, "w");
|
||||
freopen_s(&fp, "CONOUT$", "w", stdout);
|
||||
|
||||
std_handle = (intptr_t)GetStdHandle(STD_ERROR_HANDLE);
|
||||
con_handle = _open_osfhandle(std_handle, _O_TEXT);
|
||||
fp = _fdopen(con_handle, "w");
|
||||
freopen_s(&fp, "CONOUT$", "w", stderr);
|
||||
}
|
||||
|
||||
} // namespace xe
|
|
@ -14,8 +14,8 @@
|
|||
#define UTF_CPP_CPLUSPLUS 201703L
|
||||
#include "third_party/utfcpp/source/utf8.h"
|
||||
|
||||
#include "xenia/base/console.h"
|
||||
#include "xenia/base/logging.h"
|
||||
#include "xenia/base/main.h"
|
||||
#include "xenia/base/system.h"
|
||||
|
||||
namespace utfcpp = utf8;
|
||||
|
|
|
@ -15,8 +15,9 @@
|
|||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "cpptoml/include/cpptoml.h"
|
||||
#include "cxxopts/include/cxxopts.hpp"
|
||||
#include "third_party/cpptoml/include/cpptoml.h"
|
||||
#include "third_party/cxxopts/include/cxxopts.hpp"
|
||||
#include "third_party/fmt/include/fmt/format.h"
|
||||
#include "xenia/base/assert.h"
|
||||
#include "xenia/base/filesystem.h"
|
||||
#include "xenia/base/string_util.h"
|
||||
|
@ -216,7 +217,10 @@ inline std::string CommandVar<std::filesystem::path>::ToString(
|
|||
|
||||
template <class T>
|
||||
std::string CommandVar<T>::ToString(T val) {
|
||||
return std::to_string(val);
|
||||
// Use fmt::format instead of std::to_string for locale-neutral formatting of
|
||||
// floats, always with a period rather than a comma, which is treated as an
|
||||
// unidentified trailing character by cpptoml.
|
||||
return fmt::format("{}", val);
|
||||
}
|
||||
|
||||
template <class T>
|
||||
|
|
|
@ -18,10 +18,10 @@
|
|||
#include "third_party/disruptorplus/include/disruptorplus/sequence_barrier.hpp"
|
||||
#include "third_party/disruptorplus/include/disruptorplus/spin_wait_strategy.hpp"
|
||||
#include "xenia/base/atomic.h"
|
||||
#include "xenia/base/console.h"
|
||||
#include "xenia/base/cvar.h"
|
||||
#include "xenia/base/debugging.h"
|
||||
#include "xenia/base/filesystem.h"
|
||||
#include "xenia/base/main.h"
|
||||
#include "xenia/base/math.h"
|
||||
#include "xenia/base/memory.h"
|
||||
#include "xenia/base/ring_buffer.h"
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2021 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
// Nothing. Stub.
|
|
@ -2,13 +2,9 @@
|
|||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2014 Ben Vanik. All rights reserved. *
|
||||
* Copyright 2021 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#include "xenia/base/main.h"
|
||||
|
||||
#include <cstdlib>
|
||||
|
||||
// Nothing. Stub.
|
||||
|
|
|
@ -7,10 +7,11 @@
|
|||
******************************************************************************
|
||||
*/
|
||||
|
||||
#include "xenia/base/main.h"
|
||||
#include "xenia/base/platform_win.h"
|
||||
|
||||
#include <cstdlib>
|
||||
|
||||
// Includes Windows headers, so it goes after platform_win.h.
|
||||
#include "third_party/xbyak/xbyak/xbyak_util.h"
|
||||
|
||||
class StartupAvxCheck {
|
||||
|
|
|
@ -7,14 +7,12 @@
|
|||
******************************************************************************
|
||||
*/
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <io.h>
|
||||
|
||||
#include <cstdlib>
|
||||
#include <malloc.h>
|
||||
#include <cstring>
|
||||
|
||||
#include "xenia/base/cvar.h"
|
||||
#include "xenia/base/logging.h"
|
||||
#include "xenia/base/main.h"
|
||||
#include "xenia/base/main_win.h"
|
||||
#include "xenia/base/platform_win.h"
|
||||
#include "xenia/base/string.h"
|
||||
|
||||
|
@ -24,54 +22,11 @@
|
|||
// For RequestHighPerformance.
|
||||
#include <winternl.h>
|
||||
|
||||
// Includes Windows headers, so it goes here.
|
||||
#include "third_party/xbyak/xbyak/xbyak_util.h"
|
||||
|
||||
DEFINE_bool(win32_high_freq, true,
|
||||
"Requests high performance from the NT kernel", "Kernel");
|
||||
DEFINE_bool(enable_console, false, "Open a console window with the main window",
|
||||
"General");
|
||||
|
||||
namespace xe {
|
||||
|
||||
bool has_console_attached_ = true;
|
||||
|
||||
bool has_console_attached() { return has_console_attached_; }
|
||||
|
||||
bool has_shell_environment_variable() {
|
||||
size_t size = 0;
|
||||
// Check if SHELL exists
|
||||
// If it doesn't, then we are in a Windows Terminal
|
||||
auto error = getenv_s(&size, nullptr, 0, "SHELL");
|
||||
if (error) {
|
||||
return false;
|
||||
}
|
||||
return !!size;
|
||||
}
|
||||
|
||||
void AttachConsole() {
|
||||
bool has_console = ::AttachConsole(ATTACH_PARENT_PROCESS) == TRUE;
|
||||
if (!has_console || !has_shell_environment_variable()) {
|
||||
// We weren't launched from a console, so just return.
|
||||
has_console_attached_ = false;
|
||||
return;
|
||||
}
|
||||
|
||||
AllocConsole();
|
||||
|
||||
has_console_attached_ = true;
|
||||
|
||||
auto std_handle = (intptr_t)GetStdHandle(STD_OUTPUT_HANDLE);
|
||||
auto con_handle = _open_osfhandle(std_handle, _O_TEXT);
|
||||
auto fp = _fdopen(con_handle, "w");
|
||||
freopen_s(&fp, "CONOUT$", "w", stdout);
|
||||
|
||||
std_handle = (intptr_t)GetStdHandle(STD_ERROR_HANDLE);
|
||||
con_handle = _open_osfhandle(std_handle, _O_TEXT);
|
||||
fp = _fdopen(con_handle, "w");
|
||||
freopen_s(&fp, "CONOUT$", "w", stderr);
|
||||
}
|
||||
|
||||
static void RequestHighPerformance() {
|
||||
#if XE_PLATFORM_WIN32
|
||||
NTSTATUS(*NtQueryTimerResolution)
|
||||
|
@ -97,8 +52,10 @@ static void RequestHighPerformance() {
|
|||
#endif
|
||||
}
|
||||
|
||||
static bool parse_launch_arguments(const xe::EntryInfo& entry_info,
|
||||
std::vector<std::string>& args) {
|
||||
bool ParseWin32LaunchArguments(
|
||||
bool transparent_options, const std::string_view positional_usage,
|
||||
const std::vector<std::string>& positional_options,
|
||||
std::vector<std::string>* args_out) {
|
||||
auto command_line = GetCommandLineW();
|
||||
|
||||
int wargc;
|
||||
|
@ -118,89 +75,42 @@ static bool parse_launch_arguments(const xe::EntryInfo& entry_info,
|
|||
|
||||
LocalFree(wargv);
|
||||
|
||||
if (!entry_info.transparent_options) {
|
||||
cvar::ParseLaunchArguments(argc, argv, entry_info.positional_usage.value(),
|
||||
entry_info.positional_options.value());
|
||||
if (!transparent_options) {
|
||||
cvar::ParseLaunchArguments(argc, argv, positional_usage,
|
||||
positional_options);
|
||||
}
|
||||
|
||||
args.clear();
|
||||
for (int n = 0; n < argc; n++) {
|
||||
args.push_back(std::string(argv[n]));
|
||||
if (args_out) {
|
||||
args_out->clear();
|
||||
for (int n = 0; n < argc; n++) {
|
||||
args_out->push_back(std::string(argv[n]));
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int Main() {
|
||||
auto entry_info = xe::GetEntryInfo();
|
||||
|
||||
std::vector<std::string> args;
|
||||
if (!parse_launch_arguments(entry_info, args)) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Attach a console so we can write output to stdout. If the user hasn't
|
||||
// redirected output themselves it'll pop up a window.
|
||||
if (cvars::enable_console) {
|
||||
xe::AttachConsole();
|
||||
}
|
||||
|
||||
// Setup COM on the main thread.
|
||||
// NOTE: this may fail if COM has already been initialized - that's OK.
|
||||
#pragma warning(suppress : 6031)
|
||||
CoInitializeEx(nullptr, COINIT_MULTITHREADED);
|
||||
|
||||
int InitializeWin32App(const std::string_view app_name) {
|
||||
// Initialize logging. Needs parsed FLAGS.
|
||||
xe::InitializeLogging(entry_info.name);
|
||||
|
||||
Xbyak::util::Cpu cpu;
|
||||
if (!cpu.has(Xbyak::util::Cpu::tAVX)) {
|
||||
xe::FatalError(
|
||||
"Your CPU does not support AVX, which is required by Xenia. See the "
|
||||
"FAQ for system requirements at https://xenia.jp");
|
||||
return -1;
|
||||
}
|
||||
xe::InitializeLogging(app_name);
|
||||
|
||||
// Print version info.
|
||||
XELOGI("Build: " XE_BUILD_BRANCH " / " XE_BUILD_COMMIT " on " XE_BUILD_DATE);
|
||||
XELOGI(
|
||||
"Build: "
|
||||
#ifdef XE_BUILD_IS_PR
|
||||
"PR#" XE_BUILD_PR_NUMBER " " XE_BUILD_PR_REPO " " XE_BUILD_PR_BRANCH
|
||||
"@" XE_BUILD_PR_COMMIT_SHORT " against "
|
||||
#endif
|
||||
XE_BUILD_BRANCH "@" XE_BUILD_COMMIT_SHORT " on " XE_BUILD_DATE);
|
||||
|
||||
// Request high performance timing.
|
||||
if (cvars::win32_high_freq) {
|
||||
RequestHighPerformance();
|
||||
}
|
||||
|
||||
// Call app-provided entry point.
|
||||
int result = entry_info.entry_point(args);
|
||||
|
||||
xe::ShutdownLogging();
|
||||
return result;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void ShutdownWin32App() { xe::ShutdownLogging(); }
|
||||
|
||||
} // namespace xe
|
||||
|
||||
// Used in console mode apps; automatically picked based on subsystem.
|
||||
int main(int argc_ignored, char** argv_ignored) { return xe::Main(); }
|
||||
|
||||
// Used in windowed apps; automatically picked based on subsystem.
|
||||
int WINAPI wWinMain(HINSTANCE, HINSTANCE, LPWSTR command_line, int) {
|
||||
// Run normal entry point.
|
||||
return xe::Main();
|
||||
}
|
||||
|
||||
#if defined _M_IX86
|
||||
#pragma comment( \
|
||||
linker, \
|
||||
"/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='x86' publicKeyToken='6595b64144ccf1df' language='*'\"") // NOLINT(whitespace/line_length)
|
||||
#elif defined _M_IA64
|
||||
#pragma comment( \
|
||||
linker, \
|
||||
"/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='ia64' publicKeyToken='6595b64144ccf1df' language='*'\"") // NOLINT(whitespace/line_length)
|
||||
#elif defined _M_X64
|
||||
#pragma comment( \
|
||||
linker, \
|
||||
"/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='amd64' publicKeyToken='6595b64144ccf1df' language='*'\"") // NOLINT(whitespace/line_length)
|
||||
#else
|
||||
#pragma comment( \
|
||||
linker, \
|
||||
"/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"") // NOLINT(whitespace/line_length)
|
||||
#endif
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2021 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#ifndef XENIA_BASE_MAIN_WIN_H_
|
||||
#define XENIA_BASE_MAIN_WIN_H_
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace xe {
|
||||
|
||||
// Functions for calling in both windowed and console entry points.
|
||||
bool ParseWin32LaunchArguments(
|
||||
bool transparent_options, const std::string_view positional_usage,
|
||||
const std::vector<std::string>& positional_options,
|
||||
std::vector<std::string>* args_out);
|
||||
// InitializeWin32App uses cvars, call ParseWin32LaunchArguments before.
|
||||
int InitializeWin32App(const std::string_view app_name);
|
||||
void ShutdownWin32App();
|
||||
|
||||
} // namespace xe
|
||||
|
||||
#endif // XENIA_BASE_MAIN_WIN_H_
|
|
@ -11,7 +11,8 @@ project("xenia-base")
|
|||
defines({
|
||||
})
|
||||
local_platform_files()
|
||||
removefiles({"main_*.cc"})
|
||||
removefiles({"console_app_main_*.cc"})
|
||||
removefiles({"main_init_*.cc"})
|
||||
files({
|
||||
"debug_visualizers.natvis",
|
||||
})
|
||||
|
|
|
@ -221,7 +221,7 @@ inline T fpfs(const std::string_view value, bool force_hex) {
|
|||
} else {
|
||||
#if XE_COMPILER_CLANG || XE_COMPILER_GNUC
|
||||
auto temp = std::string(range);
|
||||
result = std::strtof(temp.c_str(), nullptr);
|
||||
result = std::strtod(temp.c_str(), nullptr);
|
||||
#else
|
||||
auto [p, error] = std::from_chars(range.data(), range.data() + range.size(),
|
||||
result, std::chars_format::general);
|
||||
|
|
|
@ -1862,8 +1862,8 @@ struct PACK : Sequence<PACK, I<OPCODE_PACK, V128Op, V128Op, V128Op>> {
|
|||
src = i.src1;
|
||||
}
|
||||
// Saturate to [3,3....] so that only values between 3...[00] and 3...[FF]
|
||||
// are valid - max before min to pack NaN as zero (Red Dead Redemption is
|
||||
// heavily affected by the order - packs 0xFFFFFFFF in matrix code to get 0
|
||||
// are valid - max before min to pack NaN as zero (5454082B is heavily
|
||||
// affected by the order - packs 0xFFFFFFFF in matrix code to get a 0
|
||||
// constant).
|
||||
e.vmaxps(i.dest, src, e.GetXmmConstPtr(XMM3333));
|
||||
e.vminps(i.dest, i.dest, e.GetXmmConstPtr(XMMPackD3DCOLORSat));
|
||||
|
|
|
@ -2069,7 +2069,8 @@ int InstrEmit_vpkd3d128(PPCHIRBuilder& f, const InstrData& i) {
|
|||
v = f.Pack(v, PACK_TYPE_FLOAT16_4);
|
||||
break;
|
||||
case 6: // VPACK_NORMPACKED64 4_20_20_20 w_z_y_x
|
||||
// Used in 2K games like NBA 2K9, pretty rarely in general.
|
||||
// Used in 54540829 and other installments in the series, pretty rarely in
|
||||
// general.
|
||||
v = f.Pack(v, PACK_TYPE_ULONG_4202020);
|
||||
break;
|
||||
default:
|
||||
|
|
|
@ -7,10 +7,10 @@
|
|||
******************************************************************************
|
||||
*/
|
||||
|
||||
#include "xenia/base/console_app_main.h"
|
||||
#include "xenia/base/cvar.h"
|
||||
#include "xenia/base/filesystem.h"
|
||||
#include "xenia/base/logging.h"
|
||||
#include "xenia/base/main.h"
|
||||
#include "xenia/base/math.h"
|
||||
#include "xenia/base/platform.h"
|
||||
#include "xenia/base/string_buffer.h"
|
||||
|
@ -483,5 +483,5 @@ int main(const std::vector<std::string>& args) {
|
|||
} // namespace cpu
|
||||
} // namespace xe
|
||||
|
||||
DEFINE_ENTRY_POINT("xenia-cpu-ppc-test", xe::cpu::test::main, "[test name]",
|
||||
"test_name");
|
||||
XE_DEFINE_CONSOLE_APP("xenia-cpu-ppc-test", xe::cpu::test::main, "[test name]",
|
||||
"test_name");
|
||||
|
|
|
@ -7,6 +7,8 @@
|
|||
******************************************************************************
|
||||
*/
|
||||
|
||||
#include "xenia/base/console_app_main.h"
|
||||
#include "xenia/base/cvar.h"
|
||||
#include "xenia/base/filesystem.h"
|
||||
#include "xenia/base/logging.h"
|
||||
#include "xenia/base/main.h"
|
||||
|
@ -23,6 +25,7 @@ DEFINE_string(test_path, "src/xenia/cpu/ppc/testing/",
|
|||
"Directory scanned for test files.");
|
||||
DEFINE_string(test_bin_path, "src/xenia/cpu/ppc/testing/bin/",
|
||||
"Directory with binary outputs of the test files.");
|
||||
DEFINE_transient_string(test_name, "", "Test suite name.", "General");
|
||||
|
||||
extern "C" void xe_call_native(void* context, void* fn);
|
||||
|
||||
|
@ -505,19 +508,13 @@ bool RunTests(const std::wstring& test_name) {
|
|||
return failed_count ? false : true;
|
||||
}
|
||||
|
||||
int main(const std::vector<std::wstring>& args) {
|
||||
// Grab test name, if present.
|
||||
std::wstring test_name;
|
||||
if (args.size() >= 2) {
|
||||
test_name = args[1];
|
||||
}
|
||||
|
||||
return RunTests(test_name) ? 0 : 1;
|
||||
int main(const std::vector<std::string>& args) {
|
||||
return RunTests(cvars::test_name) ? 0 : 1;
|
||||
}
|
||||
|
||||
} // namespace test
|
||||
} // namespace cpu
|
||||
} // namespace xe
|
||||
|
||||
DEFINE_ENTRY_POINT(L"xenia-cpu-ppc-test", L"xenia-cpu-ppc-test [test name]",
|
||||
xe::cpu::test::main);
|
||||
XE_DEFINE_CONSOLE_APP("xenia-cpu-ppc-test", xe::cpu::test::main, "[test name]",
|
||||
"test_name");
|
||||
|
|
|
@ -17,7 +17,7 @@ project("xenia-cpu-ppc-tests")
|
|||
})
|
||||
files({
|
||||
"ppc_testing_main.cc",
|
||||
"../../../base/main_"..platform_suffix..".cc",
|
||||
"../../../base/console_app_main_"..platform_suffix..".cc",
|
||||
})
|
||||
files({
|
||||
"*.s",
|
||||
|
@ -46,7 +46,7 @@ project("xenia-cpu-ppc-nativetests")
|
|||
})
|
||||
files({
|
||||
"ppc_testing_native_main.cc",
|
||||
"../../../base/main_"..platform_suffix..".cc",
|
||||
"../../../base/console_app_main_"..platform_suffix..".cc",
|
||||
})
|
||||
files({
|
||||
"instr_*.s",
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
******************************************************************************
|
||||
*/
|
||||
|
||||
#include "xenia/base/main.h"
|
||||
#include "xenia/base/console_app_main.h"
|
||||
#include "xenia/cpu/backend/x64/x64_backend.h"
|
||||
#include "xenia/cpu/cpu.h"
|
||||
#include "xenia/cpu/ppc/ppc_context.h"
|
||||
|
@ -23,10 +23,10 @@ using xe::cpu::ppc::PPCContext;
|
|||
|
||||
// TODO(benvanik): simple memory? move more into core?
|
||||
|
||||
int main(std::vector<std::wstring>& args) {
|
||||
int main(std::vector<std::string>& args) {
|
||||
#if XE_OPTION_PROFILING
|
||||
xe::Profiler::Initialize();
|
||||
xe::Profiler::ThreadEnter("main");
|
||||
xe::Profiler::ThreadEnter("Main");
|
||||
#endif // XE_OPTION_PROFILING
|
||||
|
||||
size_t memory_size = 16 * 1024 * 1024;
|
||||
|
@ -79,4 +79,4 @@ int main(std::vector<std::wstring>& args) {
|
|||
} // namespace cpu
|
||||
} // namespace xe
|
||||
|
||||
DEFINE_ENTRY_POINT(L"xenia-cpu-sandbox", L"?", xe::cpu::sandbox::main);
|
||||
XE_DEFINE_CONSOLE_APP("xenia-cpu-sandbox", xe::cpu::sandbox::main, "");
|
||||
|
|
|
@ -12,7 +12,6 @@
|
|||
|
||||
#include <vector>
|
||||
|
||||
#include "xenia/base/main.h"
|
||||
#include "xenia/cpu/backend/x64/x64_backend.h"
|
||||
#include "xenia/cpu/hir/hir_builder.h"
|
||||
#include "xenia/cpu/ppc/ppc_context.h"
|
||||
|
|
|
@ -32,6 +32,7 @@
|
|||
#include "xenia/kernel/xthread.h"
|
||||
#include "xenia/ui/graphics_provider.h"
|
||||
#include "xenia/ui/imgui_drawer.h"
|
||||
#include "xenia/ui/windowed_app_context.h"
|
||||
|
||||
DEFINE_bool(imgui_debug, false, "Show ImGui debugging tools.", "UI");
|
||||
|
||||
|
@ -50,11 +51,12 @@ using xe::ui::UIEvent;
|
|||
|
||||
const std::string kBaseTitle = "Xenia Debugger";
|
||||
|
||||
DebugWindow::DebugWindow(Emulator* emulator, xe::ui::Loop* loop)
|
||||
DebugWindow::DebugWindow(Emulator* emulator,
|
||||
xe::ui::WindowedAppContext& app_context)
|
||||
: emulator_(emulator),
|
||||
processor_(emulator->processor()),
|
||||
loop_(loop),
|
||||
window_(xe::ui::Window::Create(loop_, kBaseTitle)) {
|
||||
app_context_(app_context),
|
||||
window_(xe::ui::Window::Create(app_context_, kBaseTitle)) {
|
||||
if (cs_open(CS_ARCH_X86, CS_MODE_64, &capstone_handle_) != CS_ERR_OK) {
|
||||
assert_always("Failed to initialize capstone");
|
||||
}
|
||||
|
@ -63,16 +65,18 @@ DebugWindow::DebugWindow(Emulator* emulator, xe::ui::Loop* loop)
|
|||
}
|
||||
|
||||
DebugWindow::~DebugWindow() {
|
||||
loop_->PostSynchronous([this]() { window_.reset(); });
|
||||
// Make sure pending functions referencing the DebugWindow are executed.
|
||||
app_context_.ExecutePendingFunctionsFromUIThread();
|
||||
|
||||
if (capstone_handle_) {
|
||||
cs_close(&capstone_handle_);
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<DebugWindow> DebugWindow::Create(Emulator* emulator,
|
||||
xe::ui::Loop* loop) {
|
||||
std::unique_ptr<DebugWindow> debug_window(new DebugWindow(emulator, loop));
|
||||
std::unique_ptr<DebugWindow> DebugWindow::Create(
|
||||
Emulator* emulator, xe::ui::WindowedAppContext& app_context) {
|
||||
std::unique_ptr<DebugWindow> debug_window(
|
||||
new DebugWindow(emulator, app_context));
|
||||
if (!debug_window->Initialize()) {
|
||||
xe::FatalError("Failed to initialize debug window");
|
||||
return nullptr;
|
||||
|
@ -87,8 +91,6 @@ bool DebugWindow::Initialize() {
|
|||
return false;
|
||||
}
|
||||
|
||||
loop_->on_quit.AddListener([this](UIEvent* e) { window_.reset(); });
|
||||
|
||||
// Main menu.
|
||||
auto main_menu = MenuItem::Create(MenuItem::Type::kNormal);
|
||||
auto file_menu = MenuItem::Create(MenuItem::Type::kPopup, "&File");
|
||||
|
@ -1425,7 +1427,7 @@ void DebugWindow::UpdateCache() {
|
|||
auto kernel_state = emulator_->kernel_state();
|
||||
auto object_table = kernel_state->object_table();
|
||||
|
||||
loop_->Post([this]() {
|
||||
app_context_.CallInUIThread([this]() {
|
||||
std::string title = kBaseTitle;
|
||||
switch (processor_->execution_state()) {
|
||||
case cpu::ExecutionState::kEnded:
|
||||
|
@ -1531,9 +1533,7 @@ Breakpoint* DebugWindow::LookupBreakpointAtAddress(
|
|||
}
|
||||
}
|
||||
|
||||
void DebugWindow::OnFocus() {
|
||||
loop_->Post([this]() { window_->set_focus(true); });
|
||||
}
|
||||
void DebugWindow::OnFocus() { Focus(); }
|
||||
|
||||
void DebugWindow::OnDetached() {
|
||||
UpdateCache();
|
||||
|
@ -1546,30 +1546,34 @@ void DebugWindow::OnDetached() {
|
|||
|
||||
void DebugWindow::OnExecutionPaused() {
|
||||
UpdateCache();
|
||||
loop_->Post([this]() { window_->set_focus(true); });
|
||||
Focus();
|
||||
}
|
||||
|
||||
void DebugWindow::OnExecutionContinued() {
|
||||
UpdateCache();
|
||||
loop_->Post([this]() { window_->set_focus(true); });
|
||||
Focus();
|
||||
}
|
||||
|
||||
void DebugWindow::OnExecutionEnded() {
|
||||
UpdateCache();
|
||||
loop_->Post([this]() { window_->set_focus(true); });
|
||||
Focus();
|
||||
}
|
||||
|
||||
void DebugWindow::OnStepCompleted(cpu::ThreadDebugInfo* thread_info) {
|
||||
UpdateCache();
|
||||
SelectThreadStackFrame(thread_info, 0, true);
|
||||
loop_->Post([this]() { window_->set_focus(true); });
|
||||
Focus();
|
||||
}
|
||||
|
||||
void DebugWindow::OnBreakpointHit(Breakpoint* breakpoint,
|
||||
cpu::ThreadDebugInfo* thread_info) {
|
||||
UpdateCache();
|
||||
SelectThreadStackFrame(thread_info, 0, true);
|
||||
loop_->Post([this]() { window_->set_focus(true); });
|
||||
Focus();
|
||||
}
|
||||
|
||||
void DebugWindow::Focus() const {
|
||||
app_context_.CallInUIThread([this]() { window_->set_focus(true); });
|
||||
}
|
||||
|
||||
} // namespace ui
|
||||
|
|
|
@ -18,9 +18,9 @@
|
|||
#include "xenia/cpu/debug_listener.h"
|
||||
#include "xenia/cpu/processor.h"
|
||||
#include "xenia/emulator.h"
|
||||
#include "xenia/ui/loop.h"
|
||||
#include "xenia/ui/menu_item.h"
|
||||
#include "xenia/ui/window.h"
|
||||
#include "xenia/ui/windowed_app_context.h"
|
||||
#include "xenia/xbox.h"
|
||||
|
||||
namespace xe {
|
||||
|
@ -31,11 +31,11 @@ class DebugWindow : public cpu::DebugListener {
|
|||
public:
|
||||
~DebugWindow();
|
||||
|
||||
static std::unique_ptr<DebugWindow> Create(Emulator* emulator,
|
||||
xe::ui::Loop* loop);
|
||||
static std::unique_ptr<DebugWindow> Create(
|
||||
Emulator* emulator, xe::ui::WindowedAppContext& app_context);
|
||||
|
||||
Emulator* emulator() const { return emulator_; }
|
||||
xe::ui::Loop* loop() const { return loop_; }
|
||||
xe::ui::WindowedAppContext& app_context() const { return app_context_; }
|
||||
xe::ui::Window* window() const { return window_.get(); }
|
||||
|
||||
void OnFocus() override;
|
||||
|
@ -48,7 +48,8 @@ class DebugWindow : public cpu::DebugListener {
|
|||
cpu::ThreadDebugInfo* thread_info) override;
|
||||
|
||||
private:
|
||||
explicit DebugWindow(Emulator* emulator, xe::ui::Loop* loop);
|
||||
explicit DebugWindow(Emulator* emulator,
|
||||
xe::ui::WindowedAppContext& app_context);
|
||||
bool Initialize();
|
||||
|
||||
void DrawFrame();
|
||||
|
@ -86,9 +87,11 @@ class DebugWindow : public cpu::DebugListener {
|
|||
cpu::Breakpoint* LookupBreakpointAtAddress(
|
||||
cpu::Breakpoint::AddressType address_type, uint64_t address);
|
||||
|
||||
void Focus() const;
|
||||
|
||||
Emulator* emulator_ = nullptr;
|
||||
cpu::Processor* processor_ = nullptr;
|
||||
xe::ui::Loop* loop_ = nullptr;
|
||||
xe::ui::WindowedAppContext& app_context_;
|
||||
std::unique_ptr<xe::ui::Window> window_;
|
||||
uint64_t last_draw_tick_count_ = 0;
|
||||
|
||||
|
|
|
@ -40,8 +40,11 @@
|
|||
#include "xenia/kernel/xboxkrnl/xboxkrnl_module.h"
|
||||
#include "xenia/memory.h"
|
||||
#include "xenia/ui/imgui_dialog.h"
|
||||
#include "xenia/ui/window.h"
|
||||
#include "xenia/ui/windowed_app_context.h"
|
||||
#include "xenia/vfs/devices/disc_image_device.h"
|
||||
#include "xenia/vfs/devices/host_path_device.h"
|
||||
#include "xenia/vfs/devices/null_device.h"
|
||||
#include "xenia/vfs/devices/stfs_container_device.h"
|
||||
#include "xenia/vfs/virtual_file_system.h"
|
||||
|
||||
|
@ -232,7 +235,7 @@ X_STATUS Emulator::Setup(
|
|||
|
||||
if (display_window_) {
|
||||
// Finish initializing the display.
|
||||
display_window_->loop()->PostSynchronous([this]() {
|
||||
display_window_->app_context().CallInUIThreadSynchronous([this]() {
|
||||
xe::ui::GraphicsContextLock context_lock(display_window_->context());
|
||||
Profiler::set_window(display_window_);
|
||||
});
|
||||
|
@ -279,7 +282,7 @@ X_STATUS Emulator::LaunchXexFile(const std::filesystem::path& path) {
|
|||
// and then get that symlinked to game:\, so
|
||||
// -> game:\foo.xex
|
||||
|
||||
auto mount_path = "\\Device\\Harddisk0\\Partition0";
|
||||
auto mount_path = "\\Device\\Harddisk0\\Partition1";
|
||||
|
||||
// Register the local directory in the virtual filesystem.
|
||||
auto parent_path = path.parent_path();
|
||||
|
@ -582,7 +585,7 @@ bool Emulator::ExceptionCallback(Exception* ex) {
|
|||
}
|
||||
|
||||
// Display a dialog telling the user the guest has crashed.
|
||||
display_window()->loop()->PostSynchronous([&]() {
|
||||
display_window()->app_context().CallInUIThreadSynchronous([this]() {
|
||||
xe::ui::ImGuiDialog::ShowMessageBox(
|
||||
display_window(), "Uh-oh!",
|
||||
"The guest has crashed.\n\n"
|
||||
|
@ -672,6 +675,25 @@ static std::string format_version(xex2_version version) {
|
|||
|
||||
X_STATUS Emulator::CompleteLaunch(const std::filesystem::path& path,
|
||||
const std::string_view module_path) {
|
||||
// Setup NullDevices for raw HDD partition accesses
|
||||
// Cache/STFC code baked into games tries reading/writing to these
|
||||
// By using a NullDevice that just returns success to all IO requests it
|
||||
// should allow games to believe cache/raw disk was accessed successfully
|
||||
|
||||
// NOTE: this should probably be moved to xenia_main.cc, but right now we need
|
||||
// to register the \Device\Harddisk0\ NullDevice _after_ the
|
||||
// \Device\Harddisk0\Partition1 HostPathDevice, otherwise requests to
|
||||
// Partition1 will go to this. Registering during CompleteLaunch allows us to
|
||||
// make sure any HostPathDevices are ready beforehand.
|
||||
// (see comment above cache:\ device registration for more info about why)
|
||||
auto null_paths = {std::string("\\Partition0"), std::string("\\Cache0"),
|
||||
std::string("\\Cache1")};
|
||||
auto null_device =
|
||||
std::make_unique<vfs::NullDevice>("\\Device\\Harddisk0", null_paths);
|
||||
if (null_device->Initialize()) {
|
||||
file_system_->RegisterDevice(std::move(null_device));
|
||||
}
|
||||
|
||||
// Reset state.
|
||||
title_id_ = std::nullopt;
|
||||
title_name_ = "";
|
||||
|
|
|
@ -738,7 +738,7 @@ bool CommandProcessor::ExecutePacketType3(RingBuffer* reader, uint32_t packet) {
|
|||
break;
|
||||
}
|
||||
case PM4_WAIT_FOR_IDLE: {
|
||||
// This opcode is used by "Duke Nukem Forever" while going/being ingame
|
||||
// This opcode is used by 5454084E while going / being ingame.
|
||||
assert_true(count == 1);
|
||||
uint32_t value = reader->ReadAndSwap<uint32_t>();
|
||||
XELOGGPU("GPU wait for idle = {:08X}", value);
|
||||
|
@ -1168,7 +1168,7 @@ bool CommandProcessor::ExecutePacketType3_EVENT_WRITE_ZPD(RingBuffer* reader,
|
|||
// and used to detect a finished query.
|
||||
bool is_end_via_z_pass = pSampleCounts->ZPass_A == kQueryFinished &&
|
||||
pSampleCounts->ZPass_B == kQueryFinished;
|
||||
// Older versions of D3D also checks for ZFail (First Gears of War)
|
||||
// Older versions of D3D also checks for ZFail (4D5307D5).
|
||||
bool is_end_via_z_fail = pSampleCounts->ZFail_A == kQueryFinished &&
|
||||
pSampleCounts->ZFail_B == kQueryFinished;
|
||||
std::memset(pSampleCounts, 0, sizeof(xe_gpu_depth_sample_counts));
|
||||
|
|
|
@ -1662,7 +1662,7 @@ void D3D12CommandProcessor::PerformSwap(uint32_t frontbuffer_ptr,
|
|||
gamma_ramp_upload_mapping_ + gamma_ramp_footprint.Offset);
|
||||
for (uint32_t i = 0; i < 256; ++i) {
|
||||
uint32_t value = gamma_ramp_.normal[i].value;
|
||||
// Swap red and blue (Project Sylpheed has settings allowing separate
|
||||
// Swap red and blue (535107D4 has settings allowing separate
|
||||
// configuration).
|
||||
mapping[i] = ((value & 1023) << 20) | (value & (1023 << 10)) |
|
||||
((value >> 20) & 1023);
|
||||
|
@ -2076,7 +2076,7 @@ bool D3D12CommandProcessor::IssueDraw(xenos::PrimitiveType primitive_type,
|
|||
memexport_stream.index_count * memexport_format_size;
|
||||
// Try to reduce the number of shared memory operations when writing
|
||||
// different elements into the same buffer through different exports
|
||||
// (happens in Halo 3).
|
||||
// (happens in 4D5307E6).
|
||||
bool memexport_range_reused = false;
|
||||
for (uint32_t i = 0; i < memexport_range_count; ++i) {
|
||||
MemExportRange& memexport_range = memexport_ranges[i];
|
||||
|
@ -2878,8 +2878,9 @@ void D3D12CommandProcessor::UpdateSystemConstantValues(
|
|||
// Get the color info register values for each render target. Also, for ROV,
|
||||
// exclude components that don't exist in the format from the write mask.
|
||||
// Don't exclude fully overlapping render targets, however - two render
|
||||
// targets with the same base address are used in the lighting pass of Halo 3,
|
||||
// for example, with the needed one picked with dynamic control flow.
|
||||
// targets with the same base address are used in the lighting pass of
|
||||
// 4D5307E6, for example, with the needed one picked with dynamic control
|
||||
// flow.
|
||||
reg::RB_COLOR_INFO color_infos[4];
|
||||
float rt_clamp[4][4];
|
||||
uint32_t rt_keep_masks[4][2];
|
||||
|
@ -2898,8 +2899,8 @@ void D3D12CommandProcessor::UpdateSystemConstantValues(
|
|||
}
|
||||
|
||||
// Disable depth and stencil if it aliases a color render target (for
|
||||
// instance, during the XBLA logo in Banjo-Kazooie, though depth writing is
|
||||
// already disabled there).
|
||||
// instance, during the XBLA logo in 58410954, though depth writing is already
|
||||
// disabled there).
|
||||
bool depth_stencil_enabled =
|
||||
rb_depthcontrol.stencil_enable || rb_depthcontrol.z_enable;
|
||||
if (edram_rov_used && depth_stencil_enabled) {
|
||||
|
|
|
@ -83,9 +83,9 @@ class D3D12CommandProcessor : public CommandProcessor {
|
|||
|
||||
// Gets the current color write mask, taking the pixel shader's write mask
|
||||
// into account. If a shader doesn't write to a render target, it shouldn't be
|
||||
// written to and it shouldn't be even bound - otherwise, in Halo 3, one
|
||||
// written to and it shouldn't be even bound - otherwise, in 4D5307E6, one
|
||||
// render target is being destroyed by a shader not writing anything, and in
|
||||
// Banjo-Tooie, the result of clearing the top tile is being ignored because
|
||||
// 58410955, the result of clearing the top tile is being ignored because
|
||||
// there are 4 render targets bound with the same EDRAM base (clearly not
|
||||
// correct usage), but the shader only clears 1, and then EDRAM buffer stores
|
||||
// conflict with each other.
|
||||
|
|
|
@ -49,7 +49,7 @@ std::string D3D12GraphicsSystem::name() const {
|
|||
X_STATUS D3D12GraphicsSystem::Setup(cpu::Processor* processor,
|
||||
kernel::KernelState* kernel_state,
|
||||
ui::Window* target_window) {
|
||||
provider_ = xe::ui::d3d12::D3D12Provider::Create(target_window);
|
||||
provider_ = xe::ui::d3d12::D3D12Provider::Create();
|
||||
auto d3d12_provider = static_cast<xe::ui::d3d12::D3D12Provider*>(provider());
|
||||
auto device = d3d12_provider->GetDevice();
|
||||
|
||||
|
|
|
@ -3619,7 +3619,7 @@ D3D12RenderTargetCache::GetOrCreateTransferPipelines(TransferShaderKey key) {
|
|||
case xenos::DepthRenderTargetFormat::kD24S8:
|
||||
// Round to the nearest even integer. This seems to be the correct,
|
||||
// adding +0.5 and rounding towards zero results in red instead of
|
||||
// black in GTA IV and Halo 3 clear shaders.
|
||||
// black in the 4D5307E6 clear shader.
|
||||
a.OpMul(dxbc::Dest::R(i, 0b1000), dxbc::Src::R(i, dxbc::Src::kWWWW),
|
||||
dxbc::Src::LF(float(0xFFFFFF)));
|
||||
a.OpRoundNE(dxbc::Dest::R(i, 0b1000),
|
||||
|
@ -3804,7 +3804,7 @@ D3D12RenderTargetCache::GetOrCreateTransferPipelines(TransferShaderKey key) {
|
|||
case xenos::DepthRenderTargetFormat::kD24S8:
|
||||
// Round to the nearest even integer. This seems to be the correct,
|
||||
// adding +0.5 and rounding towards zero results in red instead of
|
||||
// black in GTA IV and Halo 3 clear shaders.
|
||||
// black in the 4D5307E6 clear shader.
|
||||
a.OpMul(dxbc::Dest::R(1, 0b1000), dxbc::Src::R(1, dxbc::Src::kWWWW),
|
||||
dxbc::Src::LF(float(0xFFFFFF)));
|
||||
a.OpRoundNE(dxbc::Dest::R(1, 0b1000),
|
||||
|
@ -4181,7 +4181,7 @@ D3D12RenderTargetCache::GetOrCreateTransferPipelines(TransferShaderKey key) {
|
|||
case xenos::DepthRenderTargetFormat::kD24S8:
|
||||
// Round to the nearest even integer. This seems to be the
|
||||
// correct, adding +0.5 and rounding towards zero results in red
|
||||
// instead of black in GTA IV and Halo 3 clear shaders.
|
||||
// instead of black in the 4D5307E6 clear shader.
|
||||
a.OpMul(dxbc::Dest::R(0, 0b0010),
|
||||
dxbc::Src::R(0, dxbc::Src::kXXXX),
|
||||
dxbc::Src::LF(float(0xFFFFFF)));
|
||||
|
@ -6228,7 +6228,7 @@ ID3D12PipelineState* D3D12RenderTargetCache::GetOrCreateDumpPipeline(
|
|||
case xenos::DepthRenderTargetFormat::kD24S8:
|
||||
// Round to the nearest even integer. This seems to be the correct,
|
||||
// adding +0.5 and rounding towards zero results in red instead of
|
||||
// black in GTA IV and Halo 3 clear shaders.
|
||||
// black in the 4D5307E6 clear shader.
|
||||
a.OpMul(dxbc::Dest::R(1, 0b0001), dxbc::Src::R(1, dxbc::Src::kXXXX),
|
||||
dxbc::Src::LF(float(0xFFFFFF)));
|
||||
a.OpRoundNE(dxbc::Dest::R(1, 0b0001),
|
||||
|
|
|
@ -7,8 +7,8 @@
|
|||
******************************************************************************
|
||||
*/
|
||||
|
||||
#include "xenia/base/console_app_main.h"
|
||||
#include "xenia/base/logging.h"
|
||||
#include "xenia/base/main.h"
|
||||
#include "xenia/gpu/d3d12/d3d12_command_processor.h"
|
||||
#include "xenia/gpu/d3d12/d3d12_graphics_system.h"
|
||||
#include "xenia/gpu/trace_dump.h"
|
||||
|
@ -54,6 +54,6 @@ int trace_dump_main(const std::vector<std::string>& args) {
|
|||
} // namespace gpu
|
||||
} // namespace xe
|
||||
|
||||
DEFINE_ENTRY_POINT("xenia-gpu-d3d12-trace-dump",
|
||||
xe::gpu::d3d12::trace_dump_main, "some.trace",
|
||||
"target_trace_file");
|
||||
XE_DEFINE_CONSOLE_APP("xenia-gpu-d3d12-trace-dump",
|
||||
xe::gpu::d3d12::trace_dump_main, "some.trace",
|
||||
"target_trace_file");
|
||||
|
|
|
@ -2,13 +2,15 @@
|
|||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2020 Ben Vanik. All rights reserved. *
|
||||
* Copyright 2021 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include "xenia/base/logging.h"
|
||||
#include "xenia/base/main.h"
|
||||
#include "xenia/gpu/d3d12/d3d12_command_processor.h"
|
||||
#include "xenia/gpu/d3d12/d3d12_graphics_system.h"
|
||||
#include "xenia/gpu/trace_viewer.h"
|
||||
|
@ -17,10 +19,13 @@ namespace xe {
|
|||
namespace gpu {
|
||||
namespace d3d12 {
|
||||
|
||||
using namespace xe::gpu::xenos;
|
||||
|
||||
class D3D12TraceViewer : public TraceViewer {
|
||||
class D3D12TraceViewer final : public TraceViewer {
|
||||
public:
|
||||
static std::unique_ptr<WindowedApp> Create(
|
||||
xe::ui::WindowedAppContext& app_context) {
|
||||
return std::unique_ptr<WindowedApp>(new D3D12TraceViewer(app_context));
|
||||
}
|
||||
|
||||
std::unique_ptr<gpu::GraphicsSystem> CreateGraphicsSystem() override {
|
||||
return std::unique_ptr<gpu::GraphicsSystem>(new D3D12GraphicsSystem());
|
||||
}
|
||||
|
@ -45,17 +50,15 @@ class D3D12TraceViewer : public TraceViewer {
|
|||
// TextureInfo/SamplerInfo which are going away.
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
int trace_viewer_main(const std::vector<std::string>& args) {
|
||||
D3D12TraceViewer trace_viewer;
|
||||
return trace_viewer.Main(args);
|
||||
}
|
||||
private:
|
||||
explicit D3D12TraceViewer(xe::ui::WindowedAppContext& app_context)
|
||||
: TraceViewer(app_context, "xenia-gpu-d3d12-trace-viewer") {}
|
||||
};
|
||||
|
||||
} // namespace d3d12
|
||||
} // namespace gpu
|
||||
} // namespace xe
|
||||
|
||||
DEFINE_ENTRY_POINT("xenia-gpu-d3d12-trace-viewer",
|
||||
xe::gpu::d3d12::trace_viewer_main, "some.trace",
|
||||
"target_trace_file");
|
||||
XE_DEFINE_WINDOWED_APP(xenia_gpu_d3d12_trace_viewer,
|
||||
xe::gpu::d3d12::D3D12TraceViewer::Create);
|
||||
|
|
|
@ -1567,7 +1567,8 @@ bool PipelineCache::GetCurrentStateDescription(
|
|||
/* 16 */ PipelineBlendFactor::kSrcAlphaSat,
|
||||
};
|
||||
// Like kBlendFactorMap, but with color modes changed to alpha. Some
|
||||
// pipelines aren't created in Prey because a color mode is used for alpha.
|
||||
// pipelines aren't created in 545407E0 because a color mode is used for
|
||||
// alpha.
|
||||
static const PipelineBlendFactor kBlendFactorAlphaMap[32] = {
|
||||
/* 0 */ PipelineBlendFactor::kZero,
|
||||
/* 1 */ PipelineBlendFactor::kOne,
|
||||
|
@ -1599,7 +1600,7 @@ bool PipelineCache::GetCurrentStateDescription(
|
|||
// have their sample count matching the one set in the pipeline - however if
|
||||
// we set NumRenderTargets to 0 and also disable depth / stencil, the sample
|
||||
// count must be set to 1 - while the command list may still have
|
||||
// multisampled render targets bound (happens in Halo 3 main menu).
|
||||
// multisampled render targets bound (happens in 4D5307E6 main menu).
|
||||
// TODO(Triang3l): Investigate interaction of OMSetRenderTargets with
|
||||
// non-null depth and DSVFormat DXGI_FORMAT_UNKNOWN in the same case.
|
||||
for (uint32_t i = 0; i < 4; ++i) {
|
||||
|
@ -2005,7 +2006,7 @@ ID3D12PipelineState* PipelineCache::CreateD3D12Pipeline(
|
|||
state_desc.BlendState.RenderTarget[i];
|
||||
// Treat 1 * src + 0 * dest as disabled blending (there are opaque
|
||||
// surfaces drawn with blending enabled, but it's 1 * src + 0 * dest, in
|
||||
// Call of Duty 4 - GPU performance is better when not blending.
|
||||
// 415607E6 - GPU performance is better when not blending.
|
||||
if (rt.src_blend != PipelineBlendFactor::kOne ||
|
||||
rt.dest_blend != PipelineBlendFactor::kZero ||
|
||||
rt.blend_op != xenos::BlendOp::kAdd ||
|
||||
|
|
|
@ -54,7 +54,7 @@ project("xenia-gpu-d3d12-trace-viewer")
|
|||
})
|
||||
files({
|
||||
"d3d12_trace_viewer_main.cc",
|
||||
"../../base/main_"..platform_suffix..".cc",
|
||||
"../../ui/windowed_app_main_"..platform_suffix..".cc",
|
||||
})
|
||||
-- Only create the .user file if it doesn't already exist.
|
||||
local user_file = project_root.."/build/xenia-gpu-d3d12-trace-viewer.vcxproj.user"
|
||||
|
@ -101,7 +101,7 @@ project("xenia-gpu-d3d12-trace-dump")
|
|||
})
|
||||
files({
|
||||
"d3d12_trace_dump_main.cc",
|
||||
"../../base/main_"..platform_suffix..".cc",
|
||||
"../../base/console_app_main_"..platform_suffix..".cc",
|
||||
})
|
||||
-- Only create the .user file if it doesn't already exist.
|
||||
local user_file = project_root.."/build/xenia-gpu-d3d12-trace-dump.vcxproj.user"
|
||||
|
|
|
@ -121,8 +121,8 @@ namespace shaders {
|
|||
// components of operands in shaders.
|
||||
// For DXT3A and DXT5A, RRRR swizzle is specified in:
|
||||
// http://fileadmin.cs.lth.se/cs/Personal/Michael_Doggett/talks/unc-xenos-doggett.pdf
|
||||
// Halo 3 also expects replicated components in k_8 sprites.
|
||||
// DXN is read as RG in Halo 3, but as RA in Call of Duty.
|
||||
// 4D5307E6 also expects replicated components in k_8 sprites.
|
||||
// DXN is read as RG in 4D5307E6, but as RA in 415607E6.
|
||||
// TODO(Triang3l): Find out the correct contents of unused texture components.
|
||||
const TextureCache::HostFormat TextureCache::host_formats_[64] = {
|
||||
// k_1_REVERSE
|
||||
|
@ -250,9 +250,9 @@ const TextureCache::HostFormat TextureCache::host_formats_[64] = {
|
|||
LoadMode::kUnknown,
|
||||
{2, 1, 0, 3}},
|
||||
// k_Y1_Cr_Y0_Cb_REP
|
||||
// Used for videos in NBA 2K9. Red and blue must be swapped.
|
||||
// Used for videos in 54540829. Red and blue must be swapped.
|
||||
// TODO(Triang3l): D3DFMT_G8R8_G8B8 is DXGI_FORMAT_R8G8_B8G8_UNORM * 255.0f,
|
||||
// watch out for num_format int, division in shaders, etc., in NBA 2K9 it
|
||||
// watch out for num_format int, division in shaders, etc., in 54540829 it
|
||||
// works as is. Also need to decompress if the size is uneven, but should be
|
||||
// a very rare case.
|
||||
{DXGI_FORMAT_R8G8_B8G8_UNORM,
|
||||
|
@ -1309,7 +1309,7 @@ void TextureCache::RequestTextures(uint32_t used_texture_mask) {
|
|||
// Clear the bindings not only for this draw call, but entirely, because
|
||||
// loading may be needed in some draw call later, which may have the same
|
||||
// key for some binding as before the invalidation, but texture_invalidated_
|
||||
// being false (menu background in Halo 3).
|
||||
// being false (menu background in 4D5307E6).
|
||||
for (size_t i = 0; i < xe::countof(texture_bindings_); ++i) {
|
||||
texture_bindings_[i].Clear();
|
||||
}
|
||||
|
|
|
@ -418,7 +418,7 @@ class TextureCache {
|
|||
// Uncompression info for when the regular host format for this texture is
|
||||
// block-compressed, but the size is not block-aligned, and thus such
|
||||
// texture cannot be created in Direct3D on PC and needs decompression,
|
||||
// however, such textures are common, for instance, in Halo 3. This only
|
||||
// however, such textures are common, for instance, in 4D5307E6. This only
|
||||
// supports unsigned normalized formats - let's hope GPUSIGN_SIGNED was not
|
||||
// used for DXN and DXT5A.
|
||||
DXGI_FORMAT dxgi_format_uncompressed;
|
||||
|
|
|
@ -24,12 +24,13 @@
|
|||
#include "xenia/gpu/texture_util.h"
|
||||
#include "xenia/gpu/xenos.h"
|
||||
|
||||
// Very prominent in 545407F2.
|
||||
DEFINE_bool(
|
||||
resolve_resolution_scale_duplicate_second_pixel, true,
|
||||
"When using resolution scale, apply the hack that duplicates the "
|
||||
"right/lower host pixel in the left and top sides of render target resolve "
|
||||
"areas to eliminate the gap caused by half-pixel offset (this is necessary "
|
||||
"for certain games like GTA IV to work).",
|
||||
"for certain games to display the scene graphics).",
|
||||
"GPU");
|
||||
|
||||
DEFINE_bool(
|
||||
|
@ -952,11 +953,11 @@ bool GetResolveInfo(const RegisterFile& regs, const Memory& memory,
|
|||
dest_dimension = xenos::DataDimension::k2DOrStacked;
|
||||
// RB_COPY_DEST_PITCH::copy_dest_height is the real texture height used
|
||||
// for 3D texture pitch, it's not relative to 0,0 of the coordinate space
|
||||
// (in Halo 3, the sniper rifle scope has copy_dest_height of 192, but the
|
||||
// rectangle's Y is 64...256) - provide the real height of the rectangle
|
||||
// since 32x32 tiles are stored linearly anyway. In addition, the height
|
||||
// in RB_COPY_DEST_PITCH may be larger than needed - in Red Dead
|
||||
// Redemption, a UI texture for the letterbox bars alpha is located within
|
||||
// (in 4D5307E6, the sniper rifle scope has copy_dest_height of 192, but
|
||||
// the rectangle's Y is 64...256) - provide the real height of the
|
||||
// rectangle since 32x32 tiles are stored linearly anyway. In addition,
|
||||
// the height in RB_COPY_DEST_PITCH may be larger than needed - in
|
||||
// 5454082B, a UI texture for the letterbox bars alpha is located within
|
||||
// the range of a 1280x720 resolve target, so with resolution scaling it's
|
||||
// also wrongly detected as scaled, while only 1280x208 is being resolved.
|
||||
dest_height = uint32_t(y1 - y0);
|
||||
|
|
|
@ -67,7 +67,7 @@ constexpr bool IsPrimitivePolygonal(bool vgt_output_path_is_tessellation_enable,
|
|||
// TODO(Triang3l): Investigate how kRectangleList should be treated - possibly
|
||||
// actually drawn as two polygons on the console, however, the current
|
||||
// geometry shader doesn't care about the winding order - allowing backface
|
||||
// culling for rectangles currently breaks Gears of War 2.
|
||||
// culling for rectangles currently breaks 4D53082D.
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -112,10 +112,10 @@ constexpr float GetD3D10PolygonOffsetFactor(
|
|||
return float(1 << 24);
|
||||
}
|
||||
// 20 explicit + 1 implicit (1.) mantissa bits.
|
||||
// 2^20 is not enough for Call of Duty 4 retail version's first mission F.N.G.
|
||||
// shooting range floor (with the number 1) on Direct3D 12. Tested on Nvidia
|
||||
// GeForce GTX 1070, the exact formula (taking into account the 0...1 to
|
||||
// 0...0.5 remapping described below) used for testing is
|
||||
// 2^20 is not enough for 415607E6 retail version's training mission shooting
|
||||
// range floor (with the number 1) on Direct3D 12. Tested on Nvidia GeForce
|
||||
// GTX 1070, the exact formula (taking into account the 0...1 to 0...0.5
|
||||
// remapping described below) used for testing is
|
||||
// `int(ceil(offset * 2^20 * 0.5)) * sign(offset)`. With 2^20 * 0.5, there
|
||||
// are various kinds of stripes dependending on the view angle in that
|
||||
// location. With 2^21 * 0.5, the issue is not present.
|
||||
|
@ -141,7 +141,7 @@ inline bool DoesCoverageDependOnAlpha(reg::RB_COLORCONTROL rb_colorcontrol) {
|
|||
// pre-passes and shadowmaps. The shader must have its ucode analyzed. If
|
||||
// IsRasterizationPotentiallyDone, this shouldn't be called, and assumed false
|
||||
// instead. Helps reject the pixel shader in some cases - memexport draws in
|
||||
// Halo 3, and also most of some 1-point draws not covering anything done for
|
||||
// 4D5307E6, and also most of some 1-point draws not covering anything done for
|
||||
// some reason in different games with a leftover pixel shader from the previous
|
||||
// draw, but with SQ_PROGRAM_CNTL destroyed, reducing the number of
|
||||
// unpredictable unneeded translations of random shaders with different host
|
||||
|
|
|
@ -23,11 +23,12 @@
|
|||
#include "xenia/gpu/xenos.h"
|
||||
#include "xenia/ui/graphics_provider.h"
|
||||
|
||||
// The test case for AMD is 4D5307E6 (checked in 2018).
|
||||
DEFINE_bool(dxbc_switch, true,
|
||||
"Use switch rather than if for flow control. Turning this off or "
|
||||
"on may improve stability, though this heavily depends on the "
|
||||
"driver - on AMD, it's recommended to have this set to true, as "
|
||||
"Halo 3 appears to crash when if is used for flow control "
|
||||
"some titles appear to crash when if is used for flow control "
|
||||
"(possibly the shader compiler tries to flatten them). On Intel "
|
||||
"HD Graphics, this is ignored because of a crash with the switch "
|
||||
"instruction.",
|
||||
|
@ -398,7 +399,7 @@ void DxbcShaderTranslator::StartVertexOrDomainShader() {
|
|||
assert_true(register_count() >= 2);
|
||||
if (register_count() >= 1) {
|
||||
// Copy the domain location to r0.xyz.
|
||||
// ZYX swizzle according to Call of Duty 3 and Viva Pinata.
|
||||
// ZYX swizzle according to 415607E1 and 4D5307F2.
|
||||
in_domain_location_used_ |= 0b0111;
|
||||
a_.OpMov(uses_register_dynamic_addressing ? dxbc::Dest::X(0, 0, 0b0111)
|
||||
: dxbc::Dest::R(0, 0b0111),
|
||||
|
@ -425,7 +426,7 @@ void DxbcShaderTranslator::StartVertexOrDomainShader() {
|
|||
if (register_count() >= 1) {
|
||||
// Copy the domain location to r0.xyz.
|
||||
// ZYX swizzle with r1.y == 0, according to the water shader in
|
||||
// Banjo-Kazooie: Nuts & Bolts.
|
||||
// 4D5307ED.
|
||||
in_domain_location_used_ |= 0b0111;
|
||||
a_.OpMov(uses_register_dynamic_addressing ? dxbc::Dest::X(0, 0, 0b0111)
|
||||
: dxbc::Dest::R(0, 0b0111),
|
||||
|
@ -447,10 +448,10 @@ void DxbcShaderTranslator::StartVertexOrDomainShader() {
|
|||
// appears that the tessellator offloads the reordering of coordinates
|
||||
// for edges to game shaders.
|
||||
//
|
||||
// In Banjo-Kazooie: Nuts & Bolts, the water shader multiplies the
|
||||
// first control point's position by r0.z, the second CP's by r0.y,
|
||||
// and the third CP's by r0.x. But before doing that it swizzles
|
||||
// r0.xyz the following way depending on the value in r1.y:
|
||||
// In 4D5307ED, the water shader multiplies the first control point's
|
||||
// position by r0.z, the second CP's by r0.y, and the third CP's by
|
||||
// r0.x. But before doing that it swizzles r0.xyz the following way
|
||||
// depending on the value in r1.y:
|
||||
// - ZXY for 1.0.
|
||||
// - YZX for 2.0.
|
||||
// - XZY for 4.0.
|
||||
|
@ -478,9 +479,9 @@ void DxbcShaderTranslator::StartVertexOrDomainShader() {
|
|||
a_.OpMov(uses_register_dynamic_addressing ? dxbc::Dest::X(0, 0, 0b0011)
|
||||
: dxbc::Dest::R(0, 0b0011),
|
||||
dxbc::Src::VDomain());
|
||||
// Control point indices according to the shader from the main menu of
|
||||
// Defender, which starts from `cndeq r2, c255.xxxy, r1.xyzz, r0.zzzz`,
|
||||
// where c255.x is 0, and c255.y is 1.
|
||||
// Control point indices according the main menu of 58410823, with
|
||||
// `cndeq r2, c255.xxxy, r1.xyzz, r0.zzzz` in the prologue of the
|
||||
// shader, where c255.x is 0, and c255.y is 1.
|
||||
// r0.z for (1 - r0.x) * (1 - r0.y)
|
||||
// r1.x for r0.x * (1 - r0.y)
|
||||
// r1.y for r0.x * r0.y
|
||||
|
@ -509,7 +510,7 @@ void DxbcShaderTranslator::StartVertexOrDomainShader() {
|
|||
assert_true(register_count() >= 2);
|
||||
if (register_count() >= 1) {
|
||||
// Copy the domain location to r0.yz.
|
||||
// XY swizzle according to the ground shader in Viva Pinata.
|
||||
// XY swizzle according to the ground shader in 4D5307F2.
|
||||
in_domain_location_used_ |= 0b0011;
|
||||
a_.OpMov(uses_register_dynamic_addressing ? dxbc::Dest::X(0, 0, 0b0110)
|
||||
: dxbc::Dest::R(0, 0b0110),
|
||||
|
@ -530,9 +531,8 @@ void DxbcShaderTranslator::StartVertexOrDomainShader() {
|
|||
// the tessellator offloads the reordering of coordinates for edges to
|
||||
// game shaders.
|
||||
//
|
||||
// In Viva Pinata, if we assume that r0.y is U and r0.z is V, the
|
||||
// factors each control point value is multiplied by are the
|
||||
// following:
|
||||
// In 4D5307F2, if we assume that r0.y is U and r0.z is V, the factors
|
||||
// each control point value is multiplied by are the following:
|
||||
// - (1-u)*(1-v), u*(1-v), (1-u)*v, u*v for 0.0 (identity swizzle).
|
||||
// - u*(1-v), (1-u)*(1-v), u*v, (1-u)*v for 1.0 (YXWZ).
|
||||
// - u*v, (1-u)*v, u*(1-v), (1-u)*(1-v) for 2.0 (WZYX).
|
||||
|
@ -1452,7 +1452,7 @@ void DxbcShaderTranslator::StoreResult(const InstructionResult& result,
|
|||
dest = dxbc::Dest::R(system_temp_point_size_edge_flag_kill_vertex_);
|
||||
break;
|
||||
case InstructionStorageTarget::kExportAddress:
|
||||
// Validate memexport writes (Halo 3 has some weird invalid ones).
|
||||
// Validate memexport writes (4D5307E6 has some completely invalid ones).
|
||||
if (!can_store_memexport_address || memexport_alloc_current_count_ == 0 ||
|
||||
memexport_alloc_current_count_ > Shader::kMaxMemExports ||
|
||||
system_temps_memexport_address_[memexport_alloc_current_count_ - 1] ==
|
||||
|
@ -1463,7 +1463,7 @@ void DxbcShaderTranslator::StoreResult(const InstructionResult& result,
|
|||
system_temps_memexport_address_[memexport_alloc_current_count_ - 1]);
|
||||
break;
|
||||
case InstructionStorageTarget::kExportData: {
|
||||
// Validate memexport writes (Halo 3 has some weird invalid ones).
|
||||
// Validate memexport writes (4D5307E6 has some completely invalid ones).
|
||||
if (memexport_alloc_current_count_ == 0 ||
|
||||
memexport_alloc_current_count_ > Shader::kMaxMemExports ||
|
||||
system_temps_memexport_data_[memexport_alloc_current_count_ - 1]
|
||||
|
|
|
@ -705,10 +705,10 @@ void DxbcShaderTranslator::ProcessTextureFetchInstruction(
|
|||
// Add a small epsilon to the offset (1.5/4 the fixed-point texture
|
||||
// coordinate ULP - shouldn't significantly effect the fixed-point
|
||||
// conversion; 1/4 is also not enough with 3x resolution scaling very
|
||||
// noticeably on the weapon in Halo 3) to resolve ambiguity when fetching
|
||||
// noticeably on the weapon in 4D5307E6) to resolve ambiguity when fetching
|
||||
// point-sampled textures between texels. This applies to both normalized
|
||||
// (Banjo-Kazooie Xbox Live Arcade logo, coordinates interpolated between
|
||||
// vertices with half-pixel offset) and unnormalized (Halo 3 lighting
|
||||
// (58410954 Xbox Live Arcade logo, coordinates interpolated between
|
||||
// vertices with half-pixel offset) and unnormalized (4D5307E6 lighting
|
||||
// G-buffer reading, ps_param_gen pixels) coordinates. On Nvidia Pascal,
|
||||
// without this adjustment, blockiness is visible in both cases. Possibly
|
||||
// there is a better way, however, an attempt was made to error-correct
|
||||
|
@ -1595,13 +1595,12 @@ void DxbcShaderTranslator::ProcessTextureFetchInstruction(
|
|||
|
||||
// - Data.
|
||||
|
||||
// Viva Pinata uses vertex displacement map textures for tessellated
|
||||
// models like the beehive tree with explicit LOD with point sampling
|
||||
// (they store values packed in two components), however, the fetch
|
||||
// constant has anisotropic filtering enabled. However, Direct3D 12
|
||||
// doesn't allow mixing anisotropic and point filtering. Possibly
|
||||
// anistropic filtering should be disabled when explicit LOD is used - do
|
||||
// this here.
|
||||
// 4D5307F2 uses vertex displacement map textures for tessellated models
|
||||
// like the beehive tree with explicit LOD with point sampling (they store
|
||||
// values packed in two components), however, the fetch constant has
|
||||
// anisotropic filtering enabled. However, Direct3D 12 doesn't allow
|
||||
// mixing anisotropic and point filtering. Possibly anistropic filtering
|
||||
// should be disabled when explicit LOD is used - do this here.
|
||||
uint32_t sampler_binding_index = FindOrAddSamplerBinding(
|
||||
tfetch_index, instr.attributes.mag_filter,
|
||||
instr.attributes.min_filter, instr.attributes.mip_filter,
|
||||
|
|
|
@ -287,8 +287,7 @@ void DxbcShaderTranslator::StartPixelShader_LoadROVParameters() {
|
|||
dxbc::Src::R(system_temp_rov_params_, dxbc::Src::kYYYY));
|
||||
// Choose in which 40-sample half of the tile the pixel is, for swapping
|
||||
// 40-sample columns when accessing the depth buffer - games expect this
|
||||
// behavior when writing depth back to the EDRAM via color writing (GTA IV,
|
||||
// Halo 3).
|
||||
// behavior when writing depth back to the EDRAM via color writing (4D5307E6).
|
||||
// system_temp_rov_params_.x = tile-local sample 0 X >= 40
|
||||
// system_temp_rov_params_.y = row offset
|
||||
// system_temp_rov_params_.z = X sample 0 position within the tile
|
||||
|
@ -3282,7 +3281,7 @@ void DxbcShaderTranslator::ROV_DepthTo24Bit(uint32_t d24_temp,
|
|||
dxbc::Src::LF(float(0xFFFFFF)));
|
||||
// Round to the nearest even integer. This seems to be the correct way:
|
||||
// rounding towards zero gives 0xFF instead of 0x100 in clear shaders in,
|
||||
// for instance, Halo 3, but other clear shaders in it are also broken if
|
||||
// for instance, 4D5307E6, but other clear shaders in it are also broken if
|
||||
// 0.5 is added before ftou instead of round_ne.
|
||||
a_.OpRoundNE(d24_dest, d24_src);
|
||||
// Convert to fixed-point.
|
||||
|
|
|
@ -28,16 +28,18 @@ DEFINE_bool(
|
|||
"the real reason why they're invalid is found.",
|
||||
"GPU");
|
||||
|
||||
// Extremely bright screen borders in 4D5307E6.
|
||||
// Reading between texels with half-pixel offset in 58410954.
|
||||
DEFINE_bool(
|
||||
half_pixel_offset, true,
|
||||
"Enable support of vertex half-pixel offset (D3D9 PA_SU_VTX_CNTL "
|
||||
"PIX_CENTER). Generally games are aware of the half-pixel offset, and "
|
||||
"having this enabled is the correct behavior (disabling this may "
|
||||
"significantly break post-processing in some games, like Halo 3), but in "
|
||||
"some games it might have been ignored, resulting in slight blurriness of "
|
||||
"UI textures, for instance, when they are read between texels rather than "
|
||||
"at texel centers (Banjo-Kazooie), or the leftmost/topmost pixels may not "
|
||||
"be fully covered when MSAA is used with fullscreen passes.",
|
||||
"significantly break post-processing in some games), but in certain games "
|
||||
"it might have been ignored, resulting in slight blurriness of UI "
|
||||
"textures, for instance, when they are read between texels rather than "
|
||||
"at texel centers, or the leftmost/topmost pixels may not be fully covered "
|
||||
"when MSAA is used with fullscreen passes.",
|
||||
"GPU");
|
||||
|
||||
DEFINE_int32(query_occlusion_fake_sample_count, 1000,
|
||||
|
|
|
@ -18,7 +18,8 @@
|
|||
#include "xenia/gpu/command_processor.h"
|
||||
#include "xenia/gpu/gpu_flags.h"
|
||||
#include "xenia/ui/graphics_provider.h"
|
||||
#include "xenia/ui/loop.h"
|
||||
#include "xenia/ui/window.h"
|
||||
#include "xenia/ui/windowed_app_context.h"
|
||||
|
||||
DEFINE_bool(
|
||||
store_shaders, true,
|
||||
|
@ -57,23 +58,24 @@ X_STATUS GraphicsSystem::Setup(cpu::Processor* processor,
|
|||
// This must happen on the UI thread.
|
||||
std::unique_ptr<xe::ui::GraphicsContext> processor_context = nullptr;
|
||||
if (provider_) {
|
||||
if (target_window_) {
|
||||
target_window_->loop()->PostSynchronous([&]() {
|
||||
// Create the context used for presentation.
|
||||
assert_null(target_window->context());
|
||||
target_window_->set_context(
|
||||
provider_->CreateHostContext(target_window_));
|
||||
|
||||
// Setup the context the command processor will do all its drawing in.
|
||||
// It's shared with the display context so that we can resolve
|
||||
// framebuffers from it.
|
||||
processor_context = provider()->CreateEmulationContext();
|
||||
});
|
||||
// Setup the context the command processor will do all its drawing in.
|
||||
bool contexts_initialized = true;
|
||||
processor_context = provider()->CreateEmulationContext();
|
||||
if (processor_context) {
|
||||
if (target_window_) {
|
||||
if (!target_window_->app_context().CallInUIThreadSynchronous([&]() {
|
||||
// Create the context used for presentation.
|
||||
assert_null(target_window->context());
|
||||
target_window_->set_context(
|
||||
provider_->CreateHostContext(target_window_));
|
||||
})) {
|
||||
contexts_initialized = false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
processor_context = provider()->CreateEmulationContext();
|
||||
contexts_initialized = false;
|
||||
}
|
||||
|
||||
if (!processor_context) {
|
||||
if (!contexts_initialized) {
|
||||
xe::FatalError(
|
||||
"Unable to initialize graphics context. Xenia requires Vulkan "
|
||||
"support.\n"
|
||||
|
|
|
@ -26,7 +26,7 @@ X_STATUS NullGraphicsSystem::Setup(cpu::Processor* processor,
|
|||
ui::Window* target_window) {
|
||||
// This is a null graphics system, but we still setup vulkan because UI needs
|
||||
// it through us :|
|
||||
provider_ = xe::ui::vulkan::VulkanProvider::Create(target_window);
|
||||
provider_ = xe::ui::vulkan::VulkanProvider::Create();
|
||||
|
||||
return GraphicsSystem::Setup(processor, kernel_state, target_window);
|
||||
}
|
||||
|
|
|
@ -36,7 +36,7 @@ project("xenia-gpu-shader-compiler")
|
|||
})
|
||||
files({
|
||||
"shader_compiler_main.cc",
|
||||
"../base/main_"..platform_suffix..".cc",
|
||||
"../base/console_app_main_"..platform_suffix..".cc",
|
||||
})
|
||||
|
||||
filter("platforms:Windows")
|
||||
|
|
|
@ -57,7 +57,7 @@ DEFINE_bool(
|
|||
// TODO(Triang3l): More investigation of the cache threshold as cache lookups
|
||||
// and insertions require global critical region locking, and insertions also
|
||||
// require protecting pages. At 1024, the cache only made the performance worse
|
||||
// (Tony Hawk's American Wasteland, 16-bit primitive reset index replacement).
|
||||
// (415607D4, 16-bit primitive reset index replacement).
|
||||
DEFINE_int32(
|
||||
primitive_processor_cache_min_indices, 4096,
|
||||
"Smallest number of guest indices to store in the cache to try reusing "
|
||||
|
@ -247,14 +247,14 @@ bool PrimitiveProcessor::Process(ProcessingResult& result_out) {
|
|||
// games using tessellated strips / fans so far.
|
||||
switch (tessellation_mode) {
|
||||
case xenos::TessellationMode::kDiscrete:
|
||||
// - Call of Duty 3 - nets above barrels in the beginning of the
|
||||
// first mission (turn right after the end of the intro) -
|
||||
// - 415607E1 - nets above barrels in the beginning of the first
|
||||
// mission (turn right after the end of the intro) -
|
||||
// kTriangleList.
|
||||
host_vertex_shader_type =
|
||||
Shader::HostVertexShaderType::kTriangleDomainCPIndexed;
|
||||
break;
|
||||
case xenos::TessellationMode::kContinuous:
|
||||
// - Viva Pinata - tree building with a beehive in the beginning
|
||||
// - 4D5307F2 - tree building with a beehive in the beginning
|
||||
// (visible on the start screen behind the logo), waterfall in the
|
||||
// beginning - kTriangleList.
|
||||
host_vertex_shader_type =
|
||||
|
@ -276,7 +276,7 @@ bool PrimitiveProcessor::Process(ProcessingResult& result_out) {
|
|||
Shader::HostVertexShaderType::kQuadDomainCPIndexed;
|
||||
break;
|
||||
case xenos::TessellationMode::kContinuous:
|
||||
// - Defender - retro screen and beams in the main menu - kQuadList.
|
||||
// - 58410823 - retro screen and beams in the main menu - kQuadList.
|
||||
host_vertex_shader_type =
|
||||
Shader::HostVertexShaderType::kQuadDomainCPIndexed;
|
||||
break;
|
||||
|
@ -285,14 +285,14 @@ bool PrimitiveProcessor::Process(ProcessingResult& result_out) {
|
|||
}
|
||||
break;
|
||||
case xenos::PrimitiveType::kTrianglePatch:
|
||||
// - Banjo-Kazooie: Nuts & Bolts - water - adaptive.
|
||||
// - Halo 3 - water - adaptive.
|
||||
// - 4D5307E6 - water - adaptive.
|
||||
// - 4D5307ED - water - adaptive.
|
||||
host_vertex_shader_type =
|
||||
Shader::HostVertexShaderType::kTriangleDomainPatchIndexed;
|
||||
break;
|
||||
case xenos::PrimitiveType::kQuadPatch:
|
||||
// - Fable II - continuous.
|
||||
// - Viva Pinata - garden ground - adaptive.
|
||||
// - 4D5307F1 - continuous.
|
||||
// - 4D5307F2 - garden ground - adaptive.
|
||||
host_vertex_shader_type =
|
||||
Shader::HostVertexShaderType::kQuadDomainPatchIndexed;
|
||||
break;
|
||||
|
|
|
@ -335,10 +335,10 @@ union alignas(uint32_t) PA_SU_SC_MODE_CNTL {
|
|||
uint32_t cull_back : 1; // +1
|
||||
// 0 - front is CCW, 1 - front is CW.
|
||||
uint32_t face : 1; // +2
|
||||
// The game Fuse uses poly_mode 2 for triangles, which is "reserved" on R6xx
|
||||
// and not defined on Adreno 2xx, but polymode_front/back_ptype are 0
|
||||
// (points) in this case in Fuse, which should not be respected for
|
||||
// non-kDualMode as the game wants to draw filled triangles.
|
||||
// 4541096E uses poly_mode 2 for triangles, which is "reserved" on R6xx and
|
||||
// not defined on Adreno 2xx, but polymode_front/back_ptype are 0 (points)
|
||||
// in this case in 4541096E, which should not be respected for non-kDualMode
|
||||
// as the title wants to draw filled triangles.
|
||||
xenos::PolygonModeEnable poly_mode : 2; // +3
|
||||
xenos::PolygonType polymode_front_ptype : 3; // +5
|
||||
xenos::PolygonType polymode_back_ptype : 3; // +8
|
||||
|
@ -559,16 +559,16 @@ union alignas(uint32_t) RB_COLORCONTROL {
|
|||
// (gl_FragCoord.y near 0 in the top, near 1 in the bottom here - D3D-like.)
|
||||
// For 2 samples, the top sample (closer to gl_FragCoord.y 0) is covered
|
||||
// when alpha is in [0.5, 1), the bottom sample is covered when the alpha is
|
||||
// [1. With these thresholds, however, in Red Dead Redemption, almost all
|
||||
// distant trees are transparent, this is asymmetric - fully transparent for
|
||||
// a quarter of the range (or even half of the range for 2x and almost the
|
||||
// entire range for 1x), but fully opaque only in one value.
|
||||
// [1. With these thresholds, however, in 5454082B, almost all distant trees
|
||||
// are transparent, this is asymmetric - fully transparent for a quarter of
|
||||
// the range (or even half of the range for 2x and almost the entire range
|
||||
// for 1x), but fully opaque only in one value.
|
||||
// Though, 2, 2, 2, 2 offset values are commonly used for undithered alpha
|
||||
// to coverage (in games such as Red Dead Redemption, and overall in AMD
|
||||
// driver implementations) - it appears that 2, 2, 2, 2 offsets are supposed
|
||||
// to make this symmetric.
|
||||
// Both Red Dead Redemption and RADV (which used AMDVLK as a reference) use
|
||||
// 3, 1, 0, 2 offsets for dithered alpha to mask.
|
||||
// to coverage (in games such as 5454082B, and overall in AMD driver
|
||||
// implementations) - it appears that 2, 2, 2, 2 offsets are supposed to
|
||||
// make this symmetric.
|
||||
// Both 5454082B and RADV (which used AMDVLK as a reference) use 3, 1, 0, 2
|
||||
// offsets for dithered alpha to mask.
|
||||
// https://gitlab.freedesktop.org/nchery/mesa/commit/8a52e4cc4fad4f1c75acc0badd624778f9dfe202
|
||||
// It appears that the offsets lower the thresholds by (offset / 4 /
|
||||
// sample count). That's consistent with both 2, 2, 2, 2 making the test
|
||||
|
|
|
@ -40,6 +40,7 @@ DEFINE_bool(
|
|||
"reduce bandwidth usage during transfers as the previous depth won't need "
|
||||
"to be read.",
|
||||
"GPU");
|
||||
// The round trip is done, in particular, in 545407F2.
|
||||
DEFINE_string(
|
||||
depth_float24_conversion, "",
|
||||
"Method for converting 32-bit Z values to 20e4 floating point when using "
|
||||
|
@ -56,8 +57,8 @@ DEFINE_string(
|
|||
" + Highest performance, allows early depth test and writing.\n"
|
||||
" + Host MSAA is possible with pixel-rate shading where supported.\n"
|
||||
" - EDRAM > RAM > EDRAM depth buffer round trip done in certain games "
|
||||
"(such as GTA IV) destroys precision irreparably, causing artifacts if "
|
||||
"another rendering pass is done after the EDRAM reupload.\n"
|
||||
"destroys precision irreparably, causing artifacts if another rendering "
|
||||
"pass is done after the EDRAM reupload.\n"
|
||||
" truncate:\n"
|
||||
" Convert to 20e4 directly in pixel shaders, always rounding down.\n"
|
||||
" + Average performance, conservative early depth test is possible.\n"
|
||||
|
@ -96,18 +97,15 @@ DEFINE_bool(
|
|||
"bloom, etc., in some cases.",
|
||||
"GPU");
|
||||
// Disabled by default because of full-screen effects that occur when game
|
||||
// shaders assume piecewise linear, much more severe than blending-related
|
||||
// issues.
|
||||
// shaders assume piecewise linear (4541080F), much more severe than
|
||||
// blending-related issues.
|
||||
DEFINE_bool(
|
||||
gamma_render_target_as_srgb, false,
|
||||
"When the host can't write piecewise linear gamma directly with correct "
|
||||
"blending, use sRGB output on the host for conceptually correct blending "
|
||||
"in linear color space (to prevent issues such as bright squares around "
|
||||
"bullet holes and overly dark lighting in Halo 3) while having slightly "
|
||||
"different precision distribution in the render target and severely "
|
||||
"incorrect values if the game accesses the resulting colors directly as "
|
||||
"raw data (the whole screen in The Orange Box, for instance, since when "
|
||||
"the first loading bar appears).",
|
||||
"in linear color space while having slightly different precision "
|
||||
"distribution in the render target and severely incorrect values if the "
|
||||
"game accesses the resulting colors directly as raw data.",
|
||||
"GPU");
|
||||
DEFINE_bool(
|
||||
mrt_edram_used_range_clamp_to_min, true,
|
||||
|
@ -493,9 +491,9 @@ bool RenderTargetCache::Update(bool is_rasterization_done,
|
|||
// (issues caused by color and depth render target collisions haven't been
|
||||
// found yet), but render targets with smaller index are considered more
|
||||
// important - specifically, because of the usage in the lighting pass of
|
||||
// Halo 3, which can be checked in the vertical look calibration sequence in
|
||||
// 4D5307E6, which can be checked in the vertical look calibration sequence in
|
||||
// the beginning of the game: if render target 0 is removed in favor of 1, the
|
||||
// UNSC servicemen and the world will be too dark, like fully in shadow -
|
||||
// characters and the world will be too dark, like fully in shadow -
|
||||
// especially prominent on the helmet. This happens because the shader picks
|
||||
// between two render targets to write dynamically (though with a static, bool
|
||||
// constant condition), but all other state is set up in a way that implies
|
||||
|
@ -624,7 +622,7 @@ bool RenderTargetCache::Update(bool is_rasterization_done,
|
|||
// "As if it was 64bpp" (contribution of 32bpp render targets multiplied by 2,
|
||||
// and clamping for 32bpp render targets divides this by 2) because 32bpp
|
||||
// render targets can be combined with twice as long 64bpp render targets. An
|
||||
// example is the Dead Space 3 menu background (1-sample 1152x720, or 1200x720
|
||||
// example is the 4541099D menu background (1-sample 1152x720, or 1200x720
|
||||
// after rounding to tiles, with a 32bpp depth buffer at 0 requiring 675
|
||||
// tiles, and a 64bpp color buffer at 675 requiring 1350 tiles, but the
|
||||
// smallest distance between two render target bases is 675 tiles).
|
||||
|
|
|
@ -70,10 +70,10 @@ class RenderTargetCache {
|
|||
// Significant differences:
|
||||
// - 8_8_8_8_GAMMA - the piecewise linear gamma curve is very different than
|
||||
// sRGB, one possible path is conversion in shaders (resulting in
|
||||
// incorrect blending, especially visible on decals in Halo 3), another is
|
||||
// using sRGB render targets and either conversion on resolve or reading
|
||||
// the resolved data as a true sRGB texture (incorrect when the game
|
||||
// accesses the data directly, like The Orange Box).
|
||||
// incorrect blending, especially visible on decals in 4D5307E6), another
|
||||
// is using sRGB render targets and either conversion on resolve or
|
||||
// reading the resolved data as a true sRGB texture (incorrect when the
|
||||
// game accesses the data directly, like 4541080F).
|
||||
// - 2_10_10_10_FLOAT - ranges significantly different than in float16, much
|
||||
// smaller RGB range, and alpha is fixed-point and has only 2 bits.
|
||||
// - 16_16, 16_16_16_16 - has -32 to 32 range, not -1 to 1 - need either to
|
||||
|
@ -445,9 +445,9 @@ class RenderTargetCache {
|
|||
// aliasing naively, precision may be lost - host depth must only be
|
||||
// overwritten if the new guest value is different than the current host depth
|
||||
// when converted to the guest format (this catches the usual case of
|
||||
// overwriting the depth buffer for clearing it mostly). Sonic the Hedgehog's
|
||||
// intro cutscene, for example, has a good example of corruption that happens
|
||||
// if this is not handled - the upper 1280x384 pixels are rendered in a very
|
||||
// overwriting the depth buffer for clearing it mostly). 534507D6 intro
|
||||
// cutscene, for example, has a good example of corruption that happens if
|
||||
// this is not handled - the upper 1280x384 pixels are rendered in a very
|
||||
// "striped" way if the depth precision is lost (if this is made always return
|
||||
// false).
|
||||
virtual bool IsHostDepthEncodingDifferent(
|
||||
|
@ -627,7 +627,7 @@ class RenderTargetCache {
|
|||
// surface info was changed), to avoid unneeded render target switching (which
|
||||
// is especially undesirable on tile-based GPUs) in the implementation if
|
||||
// simply disabling depth / stencil test or color writes and then re-enabling
|
||||
// (Banjo-Kazooie does this often with color). Must also be used to determine
|
||||
// (58410954 does this often with color). Must also be used to determine
|
||||
// whether it's safe to enable depth / stencil or writing to a specific color
|
||||
// render target in the pipeline for this draw call.
|
||||
// Only valid for non-pixel-shader-interlock paths.
|
||||
|
|
|
@ -551,7 +551,7 @@ struct ParsedAluInstruction {
|
|||
InstructionResult scalar_result;
|
||||
// Both operations must be executed before any result is stored if vector and
|
||||
// scalar operations are paired. There are cases of vector result being used
|
||||
// as scalar operand or vice versa (the halo on Avalanche in Halo 3, for
|
||||
// as scalar operand or vice versa (the ring on Avalanche in 4D5307E6, for
|
||||
// example), in this case there must be no dependency between the two
|
||||
// operations.
|
||||
|
||||
|
@ -854,11 +854,11 @@ class Shader {
|
|||
// highest static register address + 1, or 0 if no registers referenced this
|
||||
// way. SQ_PROGRAM_CNTL is not always reliable - some draws (like single point
|
||||
// draws with oPos = 0001 that are done by Xbox 360's Direct3D 9 sometimes;
|
||||
// can be reproduced by launching Arrival in Halo 3 from the campaign lobby)
|
||||
// that aren't supposed to cover any pixels use an invalid (zero)
|
||||
// SQ_PROGRAM_CNTL, but with an outdated pixel shader loaded, in this case
|
||||
// SQ_PROGRAM_CNTL may contain a number smaller than actually needed by the
|
||||
// pixel shader - SQ_PROGRAM_CNTL should be used to go above this count if
|
||||
// can be reproduced by launching the intro mission in 4D5307E6 from the
|
||||
// campaign lobby) that aren't supposed to cover any pixels use an invalid
|
||||
// (zero) SQ_PROGRAM_CNTL, but with an outdated pixel shader loaded, in this
|
||||
// case SQ_PROGRAM_CNTL may contain a number smaller than actually needed by
|
||||
// the pixel shader - SQ_PROGRAM_CNTL should be used to go above this count if
|
||||
// uses_register_dynamic_addressing is true.
|
||||
uint32_t register_static_address_bound() const {
|
||||
return register_static_address_bound_;
|
||||
|
|
|
@ -16,9 +16,9 @@
|
|||
|
||||
#include "third_party/glslang/SPIRV/disassemble.h"
|
||||
#include "xenia/base/assert.h"
|
||||
#include "xenia/base/console_app_main.h"
|
||||
#include "xenia/base/cvar.h"
|
||||
#include "xenia/base/logging.h"
|
||||
#include "xenia/base/main.h"
|
||||
#include "xenia/base/platform.h"
|
||||
#include "xenia/base/string.h"
|
||||
#include "xenia/base/string_buffer.h"
|
||||
|
@ -250,5 +250,6 @@ int shader_compiler_main(const std::vector<std::string>& args) {
|
|||
} // namespace gpu
|
||||
} // namespace xe
|
||||
|
||||
DEFINE_ENTRY_POINT("xenia-gpu-shader-compiler", xe::gpu::shader_compiler_main,
|
||||
"shader.bin", "shader_input");
|
||||
XE_DEFINE_CONSOLE_APP("xenia-gpu-shader-compiler",
|
||||
xe::gpu::shader_compiler_main, "shader.bin",
|
||||
"shader_input");
|
||||
|
|
|
@ -388,8 +388,8 @@ void Shader::GatherAluInstructionInformation(
|
|||
// allocation in shader translator implementations.
|
||||
// eA is (hopefully) always written to using:
|
||||
// mad eA, r#, const0100, c#
|
||||
// (though there are some exceptions, shaders in Halo 3 for some reason set eA
|
||||
// to zeros, but the swizzle of the constant is not .xyzw in this case, and
|
||||
// (though there are some exceptions, shaders in 4D5307E6 for some reason set
|
||||
// eA to zeros, but the swizzle of the constant is not .xyzw in this case, and
|
||||
// they don't write to eM#).
|
||||
// Export is done to vector_dest of the ucode instruction for both vector and
|
||||
// scalar operations - no need to check separately.
|
||||
|
|
|
@ -36,7 +36,7 @@ XeHSConstantDataOutput XePatchConstant(
|
|||
// 2) r0.zyx -> r0.zyx by the guest (because r1.y is set to 0 by Xenia, which
|
||||
// apparently means identity swizzle to games).
|
||||
// 3) r0.z * v0 + r0.y * v1 + r0.x * v2 by the guest.
|
||||
// With this order, there are no cracks in Halo 3 water.
|
||||
// With this order, there are no cracks in 4D5307E6 water.
|
||||
[unroll] for (i = 0u; i < 3u; ++i) {
|
||||
output.edges[i] = xe_input_patch[(i + 1u) % 3u].edge_factor;
|
||||
}
|
||||
|
|
|
@ -986,11 +986,11 @@ uint4 XeDXT3AAs1111TwoBlocksRowToBGRA4(uint2 halfblocks) {
|
|||
// DXT1/DXT3/DXT5 color components and CTX1 X/Y are ordered in:
|
||||
// http://fileadmin.cs.lth.se/cs/Personal/Michael_Doggett/talks/unc-xenos-doggett.pdf
|
||||
// (LSB on the right, MSB on the left.)
|
||||
// TODO(Triang3l): Investigate this better, Halo: Reach is the only known game
|
||||
// TODO(Triang3l): Investigate this better, 4D53085B is the only known game
|
||||
// that uses it (for lighting in certain places - one of easy to notice usages
|
||||
// is the T-shaped (or somewhat H-shaped) metal beams in the beginning of
|
||||
// Winter Contingency), however the contents don't say anything about the
|
||||
// channel order.
|
||||
// is the T-shaped (or somewhat H-shaped) metal beams in the beginning of the
|
||||
// first mission), however the contents don't say anything about the channel
|
||||
// order.
|
||||
uint4 row = (((halfblocks.xxyy >> uint2(3u, 11u).xyxy) & 1u) << 8u) |
|
||||
(((halfblocks.xxyy >> uint2(7u, 15u).xyxy) & 1u) << 24u) |
|
||||
(((halfblocks.xxyy >> uint2(2u, 10u).xyxy) & 1u) << 4u) |
|
||||
|
|
|
@ -5,8 +5,8 @@ XeHSControlPointInputAdaptive main(uint xe_edge_factor : SV_VertexID) {
|
|||
XeHSControlPointInputAdaptive output;
|
||||
// The Xbox 360's GPU accepts the float32 tessellation factors for edges
|
||||
// through a special kind of an index buffer.
|
||||
// While Viva Pinata sets the factors to 0 for frustum-culled (quad) patches,
|
||||
// in Halo 3 only allowing patches with factors above 0 makes distant
|
||||
// While 4D5307F2 sets the factors to 0 for frustum-culled (quad) patches, in
|
||||
// 4D5307E6 only allowing patches with factors above 0 makes distant
|
||||
// (triangle) patches disappear - it appears that there are no special values
|
||||
// for culled patches on the Xbox 360 (unlike zero, negative and NaN on
|
||||
// Direct3D 11).
|
||||
|
|
|
@ -11,7 +11,7 @@ RWBuffer<uint4> xe_texture_load_dest : register(u0);
|
|||
// Dword 1:
|
||||
// rrrrrrrrgggggggg
|
||||
// RRRRRRRRGGGGGGGG
|
||||
// (R is in the higher bits, according to how this format is used in Halo 3).
|
||||
// (R is in the higher bits, according to how this format is used in 4D5307E6).
|
||||
// Dword 2:
|
||||
// AA BB CC DD
|
||||
// EE FF GG HH
|
||||
|
|
|
@ -465,9 +465,10 @@ std::pair<uint32_t, uint32_t> SharedMemory::MemoryInvalidationCallback(
|
|||
// invalidated - if no GPU-written data nearby that was not intended to be
|
||||
// invalidated since it's not in sync with CPU memory and can't be
|
||||
// reuploaded. It's a lot cheaper to upload some excess data than to catch
|
||||
// access violations - with 4 KB callbacks, the original Doom runs at 4 FPS
|
||||
// on Intel Core i7-3770, with 64 KB the CPU game code takes 3 ms to run per
|
||||
// frame, but with 256 KB it's 0.7 ms.
|
||||
// access violations - with 4 KB callbacks, 58410824 (being a
|
||||
// software-rendered game) runs at 4 FPS on Intel Core i7-3770, with 64 KB,
|
||||
// the CPU game code takes 3 ms to run per frame, but with 256 KB, it's
|
||||
// 0.7 ms.
|
||||
if (page_first & 63) {
|
||||
uint64_t gpu_written_start =
|
||||
system_page_flags_[block_first].valid_and_gpu_written;
|
||||
|
|
|
@ -49,7 +49,8 @@ void CopySwapBlock(xenos::Endian endian, void* output, const void* input,
|
|||
void ConvertTexelCTX1ToR8G8(xenos::Endian endian, void* output,
|
||||
const void* input, size_t length) {
|
||||
// https://fileadmin.cs.lth.se/cs/Personal/Michael_Doggett/talks/unc-xenos-doggett.pdf
|
||||
// (R is in the higher bits, according to how this format is used in Halo 3).
|
||||
// (R is in the higher bits, according to how this format is used in
|
||||
// 4D5307E6).
|
||||
union {
|
||||
uint8_t data[8];
|
||||
struct {
|
||||
|
|
|
@ -352,11 +352,11 @@ TextureGuestLayout GetGuestTextureLayout(
|
|||
xenos::kTextureSubresourceAlignmentBytes);
|
||||
|
||||
// Estimate the memory amount actually referenced by the texture, which may
|
||||
// be smaller (especially in the 1280x720 linear k_8_8_8_8 case in Ridge
|
||||
// Racer Unbounded, for which memory exactly for 1280x720 is allocated, and
|
||||
// aligning the height to 32 would cause access of an unallocated page) or
|
||||
// bigger than the stride. For tiled textures, this is the dimensions
|
||||
// aligned to 32x32x4 blocks (or x1 for the missing dimensions).
|
||||
// be smaller (especially in the 1280x720 linear k_8_8_8_8 case in 4E4D083E,
|
||||
// for which memory exactly for 1280x720 is allocated, and aligning the
|
||||
// height to 32 would cause access of an unallocated page) or bigger than
|
||||
// the stride. For tiled textures, this is the dimensions aligned to 32x32x4
|
||||
// blocks (or x1 for the missing dimensions).
|
||||
uint32_t level_width_blocks =
|
||||
xe::align(std::max(width_texels >> level, uint32_t(1)),
|
||||
format_info->block_width) /
|
||||
|
|
|
@ -64,14 +64,14 @@ bool GetPackedMipOffset(uint32_t width, uint32_t height, uint32_t depth,
|
|||
// implies 32-block alignment for both uncompressed and compressed textures)
|
||||
// stored in the fetch constant, and height aligned to 32 blocks for Z slice
|
||||
// and array layer stride calculation purposes. The pitch can be different
|
||||
// from the actual width - an example is Plants vs. Zombies, using 1408 pitch
|
||||
// for a 1280x menu background).
|
||||
// from the actual width - an example is 584109FF, using 1408 pitch for a
|
||||
// 1280x menu background).
|
||||
// - The mip levels use `max(next_pow2(width or height in texels) >> level, 1)`
|
||||
// aligned to 32 blocks for the same purpose, likely disregarding the pitch
|
||||
// from the fetch constant.
|
||||
//
|
||||
// There is also mip tail packing if the fetch constant specifies that packed
|
||||
// mips are enabled, for both tiled and linear textures (Prey uses linear
|
||||
// mips are enabled, for both tiled and linear textures (545407E0 uses linear
|
||||
// DXT-compressed textures with packed mips very extensively for the game world
|
||||
// materials). In this case, mips with width or height of 16 or smaller are
|
||||
// stored not individually, but instead, in 32-texel (note: not 32-block - mip
|
||||
|
@ -99,7 +99,7 @@ bool GetPackedMipOffset(uint32_t width, uint32_t height, uint32_t depth,
|
|||
// tail, and the offset calculation function doesn't have level == 0 checks in
|
||||
// it, only early-out if level < packed tail level (which can be 0). There are
|
||||
// examples of textures with packed base, for example, in the intro level of
|
||||
// Prey (8x8 linear DXT1 - pairs of orange lights in the bottom of gambling
|
||||
// 545407E0 (8x8 linear DXT1 - pairs of orange lights in the bottom of gambling
|
||||
// machines).
|
||||
//
|
||||
// Linear texture rows are aligned to 256 bytes, for both the base and the mips
|
||||
|
@ -107,22 +107,21 @@ bool GetPackedMipOffset(uint32_t width, uint32_t height, uint32_t depth,
|
|||
// fetch constant).
|
||||
//
|
||||
// However, all the 32x32x4 padding, being just padding, is not necessarily
|
||||
// being actually accessed, especially for linear textures. Ridge Racer
|
||||
// Unbounded has a 1280x720 k_8_8_8_8 linear texture, and allocates memory for
|
||||
// exactly 1280x720, so aligning the height to 32 to 1280x736 results in access
|
||||
// violations. So, while for stride calculations all the padding must be
|
||||
// respected, for actual memory loads it's better to avoid trying to access it
|
||||
// when possible:
|
||||
// being actually accessed, especially for linear textures. 4E4D083E has a
|
||||
// 1280x720 k_8_8_8_8 linear texture, and allocates memory for exactly 1280x720,
|
||||
// so aligning the height to 32 to 1280x736 results in access violations. So,
|
||||
// while for stride calculations all the padding must be respected, for actual
|
||||
// memory loads it's better to avoid trying to access it when possible:
|
||||
// - If the pitch is bigger than the width, it's better to calculate the last
|
||||
// row's length from the width rather than the pitch (this also possibly works
|
||||
// in the other direction though - pitch < width is a weird situation, but
|
||||
// probably legal, and may lead to reading data from beyond the calculated
|
||||
// subresource stride).
|
||||
// - For linear textures (like that 1280x720 example from Ridge Racer
|
||||
// Unbounded), it's easy to calculate the exact memory extent that may be
|
||||
// accessed knowing the dimensions (unlike for tiled textures with complex
|
||||
// addressing within 32x32x4-block tiles), so there's no need to align them to
|
||||
// 32x32x4 for memory extent calculation.
|
||||
// - For linear textures (like that 1280x720 example from 4E4D083E), it's easy
|
||||
// to calculate the exact memory extent that may be accessed knowing the
|
||||
// dimensions (unlike for tiled textures with complex addressing within
|
||||
// 32x32x4-block tiles), so there's no need to align them to 32x32x4 for
|
||||
// memory extent calculation.
|
||||
// - For the linear packed mip tail, the extent can be calculated as max of
|
||||
// (block offsets + block extents) of all levels stored in it.
|
||||
//
|
||||
|
@ -152,16 +151,16 @@ struct TextureGuestLayout {
|
|||
// tiled textures, this will be rounded to 32x32x4 blocks (or 32x32x1
|
||||
// depending on the dimension), but for the linear subresources, this may be
|
||||
// significantly (including less 4 KB pages) smaller than the aligned size
|
||||
// (like for Ridge Racer Unbounded where aligning the height of a 1280x720
|
||||
// linear texture results in access violations). For the linear mip tail,
|
||||
// this includes all the mip levels stored in it. If the width is bigger
|
||||
// than the pitch, this will also be taken into account for the last row so
|
||||
// all memory actually used by the texture will be loaded, and may be bigger
|
||||
// than the distance between array slices or levels. The purpose of this
|
||||
// parameter is to make the memory amount that needs to be resident as close
|
||||
// to the real amount as possible, to make sure all the needed data will be
|
||||
// read, but also, if possible, unneeded memory pages won't be accessed
|
||||
// (since that may trigger an access violation on the CPU).
|
||||
// (like for 4E4D083E where aligning the height of a 1280x720 linear texture
|
||||
// results in access violations). For the linear mip tail, this includes all
|
||||
// the mip levels stored in it. If the width is bigger than the pitch, this
|
||||
// will also be taken into account for the last row so all memory actually
|
||||
// used by the texture will be loaded, and may be bigger than the distance
|
||||
// between array slices or levels. The purpose of this parameter is to make
|
||||
// the memory amount that needs to be resident as close to the real amount
|
||||
// as possible, to make sure all the needed data will be read, but also, if
|
||||
// possible, unneeded memory pages won't be accessed (since that may trigger
|
||||
// an access violation on the CPU).
|
||||
uint32_t x_extent_blocks;
|
||||
uint32_t y_extent_blocks;
|
||||
uint32_t z_extent;
|
||||
|
|
|
@ -100,7 +100,7 @@ bool TraceDump::Setup() {
|
|||
return false;
|
||||
}
|
||||
graphics_system_ = emulator_->graphics_system();
|
||||
player_ = std::make_unique<TracePlayer>(nullptr, graphics_system_);
|
||||
player_ = std::make_unique<TracePlayer>(graphics_system_);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -17,9 +17,8 @@
|
|||
namespace xe {
|
||||
namespace gpu {
|
||||
|
||||
TracePlayer::TracePlayer(xe::ui::Loop* loop, GraphicsSystem* graphics_system)
|
||||
: loop_(loop),
|
||||
graphics_system_(graphics_system),
|
||||
TracePlayer::TracePlayer(GraphicsSystem* graphics_system)
|
||||
: graphics_system_(graphics_system),
|
||||
current_frame_index_(0),
|
||||
current_command_index_(-1) {
|
||||
// Need to allocate all of physical memory so that we can write to it during
|
||||
|
|
|
@ -16,7 +16,6 @@
|
|||
#include "xenia/base/threading.h"
|
||||
#include "xenia/gpu/trace_protocol.h"
|
||||
#include "xenia/gpu/trace_reader.h"
|
||||
#include "xenia/ui/loop.h"
|
||||
|
||||
namespace xe {
|
||||
namespace gpu {
|
||||
|
@ -30,7 +29,7 @@ enum class TracePlaybackMode {
|
|||
|
||||
class TracePlayer : public TraceReader {
|
||||
public:
|
||||
TracePlayer(xe::ui::Loop* loop, GraphicsSystem* graphics_system);
|
||||
TracePlayer(GraphicsSystem* graphics_system);
|
||||
~TracePlayer() override;
|
||||
|
||||
GraphicsSystem* graphics_system() const { return graphics_system_; }
|
||||
|
@ -54,7 +53,6 @@ class TracePlayer : public TraceReader {
|
|||
void PlayTraceOnThread(const uint8_t* trace_data, size_t trace_size,
|
||||
TracePlaybackMode playback_mode, bool clear_caches);
|
||||
|
||||
xe::ui::Loop* loop_;
|
||||
GraphicsSystem* graphics_system_;
|
||||
int current_frame_index_;
|
||||
int current_command_index_;
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2020 Ben Vanik. All rights reserved. *
|
||||
* Copyright 2021 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
@ -13,10 +13,12 @@
|
|||
|
||||
#include "third_party/half/include/half.hpp"
|
||||
#include "third_party/imgui/imgui.h"
|
||||
#include "xenia/base/assert.h"
|
||||
#include "xenia/base/clock.h"
|
||||
#include "xenia/base/logging.h"
|
||||
#include "xenia/base/math.h"
|
||||
#include "xenia/base/string.h"
|
||||
#include "xenia/base/system.h"
|
||||
#include "xenia/base/threading.h"
|
||||
#include "xenia/gpu/command_processor.h"
|
||||
#include "xenia/gpu/gpu_flags.h"
|
||||
|
@ -30,6 +32,7 @@
|
|||
#include "xenia/ui/imgui_drawer.h"
|
||||
#include "xenia/ui/virtual_key.h"
|
||||
#include "xenia/ui/window.h"
|
||||
#include "xenia/ui/windowed_app_context.h"
|
||||
#include "xenia/xbox.h"
|
||||
|
||||
DEFINE_path(target_trace_file, "", "Specifies the trace file to load.", "GPU");
|
||||
|
@ -46,22 +49,16 @@ static const ImVec4 kColorComment =
|
|||
static const ImVec4 kColorIgnored =
|
||||
ImVec4(100 / 255.0f, 100 / 255.0f, 100 / 255.0f, 255 / 255.0f);
|
||||
|
||||
TraceViewer::TraceViewer() = default;
|
||||
TraceViewer::TraceViewer(xe::ui::WindowedAppContext& app_context,
|
||||
const std::string_view name)
|
||||
: xe::ui::WindowedApp(app_context, name, "some.trace") {
|
||||
AddPositionalOption("target_trace_file");
|
||||
}
|
||||
|
||||
TraceViewer::~TraceViewer() = default;
|
||||
|
||||
int TraceViewer::Main(const std::vector<std::string>& args) {
|
||||
// Grab path from the flag or unnamed argument.
|
||||
std::filesystem::path path;
|
||||
if (!cvars::target_trace_file.empty()) {
|
||||
// Passed as a named argument.
|
||||
// TODO(benvanik): find something better than gflags that supports
|
||||
// unicode.
|
||||
path = cvars::target_trace_file;
|
||||
} else if (args.size() >= 2) {
|
||||
// Passed as an unnamed argument.
|
||||
path = xe::to_path(args[1]);
|
||||
}
|
||||
bool TraceViewer::OnInitialize() {
|
||||
std::filesystem::path path = cvars::target_trace_file;
|
||||
|
||||
// If no path passed, ask the user.
|
||||
if (path.empty()) {
|
||||
|
@ -83,42 +80,37 @@ int TraceViewer::Main(const std::vector<std::string>& args) {
|
|||
}
|
||||
|
||||
if (path.empty()) {
|
||||
xe::FatalError("No trace file specified");
|
||||
return 1;
|
||||
xe::ShowSimpleMessageBox(xe::SimpleMessageBoxType::Warning,
|
||||
"No trace file specified");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Normalize the path and make absolute.
|
||||
auto abs_path = std::filesystem::absolute(path);
|
||||
|
||||
if (!Setup()) {
|
||||
xe::FatalError("Unable to setup trace viewer");
|
||||
return 1;
|
||||
xe::ShowSimpleMessageBox(xe::SimpleMessageBoxType::Error,
|
||||
"Unable to setup trace viewer");
|
||||
return false;
|
||||
}
|
||||
if (!Load(std::move(abs_path))) {
|
||||
xe::FatalError("Unable to load trace file; not found?");
|
||||
return 1;
|
||||
xe::ShowSimpleMessageBox(xe::SimpleMessageBoxType::Error,
|
||||
"Unable to load trace file; not found?");
|
||||
return false;
|
||||
}
|
||||
Run();
|
||||
return 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TraceViewer::Setup() {
|
||||
// Main display window.
|
||||
loop_ = ui::Loop::Create();
|
||||
window_ = xe::ui::Window::Create(loop_.get(), "xenia-gpu-trace-viewer");
|
||||
loop_->PostSynchronous([&]() {
|
||||
xe::threading::set_name("Win32 Loop");
|
||||
if (!window_->Initialize()) {
|
||||
xe::FatalError("Failed to initialize main window");
|
||||
return;
|
||||
}
|
||||
});
|
||||
window_->on_closed.AddListener([&](xe::ui::UIEvent* e) {
|
||||
loop_->Quit();
|
||||
XELOGI("User-initiated death!");
|
||||
exit(1);
|
||||
});
|
||||
loop_->on_quit.AddListener([&](xe::ui::UIEvent* e) { window_.reset(); });
|
||||
assert_true(app_context().IsInUIThread());
|
||||
window_ = xe::ui::Window::Create(app_context(), "xenia-gpu-trace-viewer");
|
||||
if (!window_->Initialize()) {
|
||||
XELOGE("Failed to initialize main window");
|
||||
return false;
|
||||
}
|
||||
window_->on_closed.AddListener(
|
||||
[this](xe::ui::UIEvent* e) { app_context().QuitFromUIThread(); });
|
||||
window_->Resize(1920, 1200);
|
||||
|
||||
// Create the emulator but don't initialize so we can setup the window.
|
||||
|
@ -142,9 +134,9 @@ bool TraceViewer::Setup() {
|
|||
}
|
||||
});
|
||||
|
||||
player_ = std::make_unique<TracePlayer>(loop_.get(), graphics_system_);
|
||||
player_ = std::make_unique<TracePlayer>(graphics_system_);
|
||||
|
||||
window_->on_painting.AddListener([&](xe::ui::UIEvent* e) {
|
||||
window_->on_painting.AddListener([this](xe::ui::UIEvent* e) {
|
||||
DrawUI();
|
||||
|
||||
// Continuous paint.
|
||||
|
@ -167,16 +159,6 @@ bool TraceViewer::Load(const std::filesystem::path& trace_file_path) {
|
|||
return true;
|
||||
}
|
||||
|
||||
void TraceViewer::Run() {
|
||||
// Wait until we are exited.
|
||||
loop_->AwaitQuit();
|
||||
|
||||
player_.reset();
|
||||
emulator_.reset();
|
||||
window_.reset();
|
||||
loop_.reset();
|
||||
}
|
||||
|
||||
void TraceViewer::DrawMultilineString(const std::string_view str) {
|
||||
size_t i = 0;
|
||||
bool done = false;
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2020 Ben Vanik. All rights reserved. *
|
||||
* Copyright 2021 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
@ -18,13 +18,8 @@
|
|||
#include "xenia/gpu/trace_protocol.h"
|
||||
#include "xenia/gpu/xenos.h"
|
||||
#include "xenia/memory.h"
|
||||
|
||||
namespace xe {
|
||||
namespace ui {
|
||||
class Loop;
|
||||
class Window;
|
||||
} // namespace ui
|
||||
} // namespace xe
|
||||
#include "xenia/ui/window.h"
|
||||
#include "xenia/ui/windowed_app.h"
|
||||
|
||||
namespace xe {
|
||||
namespace gpu {
|
||||
|
@ -32,14 +27,15 @@ namespace gpu {
|
|||
struct SamplerInfo;
|
||||
struct TextureInfo;
|
||||
|
||||
class TraceViewer {
|
||||
class TraceViewer : public xe::ui::WindowedApp {
|
||||
public:
|
||||
virtual ~TraceViewer();
|
||||
|
||||
int Main(const std::vector<std::string>& args);
|
||||
bool OnInitialize() override;
|
||||
|
||||
protected:
|
||||
TraceViewer();
|
||||
explicit TraceViewer(xe::ui::WindowedAppContext& app_context,
|
||||
const std::string_view name);
|
||||
|
||||
virtual std::unique_ptr<gpu::GraphicsSystem> CreateGraphicsSystem() = 0;
|
||||
|
||||
|
@ -60,7 +56,6 @@ class TraceViewer {
|
|||
|
||||
virtual bool Setup();
|
||||
|
||||
std::unique_ptr<xe::ui::Loop> loop_;
|
||||
std::unique_ptr<xe::ui::Window> window_;
|
||||
std::unique_ptr<Emulator> emulator_;
|
||||
Memory* memory_ = nullptr;
|
||||
|
@ -75,7 +70,6 @@ class TraceViewer {
|
|||
};
|
||||
|
||||
bool Load(const std::filesystem::path& trace_file_path);
|
||||
void Run();
|
||||
|
||||
void DrawUI();
|
||||
void DrawControllerUI();
|
||||
|
|
|
@ -483,7 +483,7 @@ enum class FetchOpcode : uint32_t {
|
|||
// - 3D (used for both 3D and stacked 2D texture): U, V, W (normalized or
|
||||
// unnormalized - same for both 3D W and stack layer; also VolMagFilter /
|
||||
// VolMinFilter between stack layers is supported, used for color correction
|
||||
// in Burnout Revenge).
|
||||
// in 454107DC).
|
||||
// - Cube: SC, TC (between 1 and 2 for normalized), face ID (0.0 to 5.0), the
|
||||
// cube vector ALU instruction is used to calculate them.
|
||||
// https://gpuopen.com/learn/fetching-from-cubes-and-octahedrons/
|
||||
|
@ -495,9 +495,9 @@ enum class FetchOpcode : uint32_t {
|
|||
// The total LOD for a sample is additive and is based on what is enabled.
|
||||
//
|
||||
// For cube maps, according to what texCUBEgrad compiles to in a modified
|
||||
// HLSL shader of Brave: A Warrior's Tale and to XNA assembler output for PC
|
||||
// SM3 texldd, register gradients are in cube space (not in SC/TC space,
|
||||
// unlike the coordinates themselves). This isn't true for the GCN, however.
|
||||
// HLSL shader of 455607D1 and to XNA assembler output for PC SM3 texldd,
|
||||
// register gradients are in cube space (not in SC/TC space, unlike the
|
||||
// coordinates themselves). This isn't true for the GCN, however.
|
||||
//
|
||||
// TODO(Triang3l): Find if gradients are unnormalized for cube maps if
|
||||
// coordinates are unnormalized. Since texldd doesn't perform any
|
||||
|
@ -814,8 +814,8 @@ static_assert_size(TextureFetchInstruction, sizeof(uint32_t) * 3);
|
|||
// (mul, mad, dp, etc.) and for NaN in min/max. It's very important to respect
|
||||
// this rule for multiplication, as games often rely on it in vector
|
||||
// normalization (rcp and mul), Infinity * 0 resulting in NaN breaks a lot of
|
||||
// things in games - causes white screen in Halo 3, white specular on
|
||||
// characters in GTA IV. The result is always positive zero in this case, no
|
||||
// things in games - causes white screen in 4D5307E6, white specular on
|
||||
// characters in 545407F2. The result is always positive zero in this case, no
|
||||
// matter what the signs of the other operands are, according to R5xx
|
||||
// Acceleration section 8.7.5 "Legacy multiply behavior" and testing on
|
||||
// Adreno 200. This means that the following need to be taken into account
|
||||
|
@ -1628,8 +1628,8 @@ enum class ExportRegister : uint32_t {
|
|||
// X - PSIZE (gl_PointSize).
|
||||
// Y - EDGEFLAG (glEdgeFlag) for PrimitiveType::kPolygon wireframe/point
|
||||
// drawing.
|
||||
// Z - KILLVERTEX flag (used in Banjo-Kazooie: Nuts & Bolts for grass), set
|
||||
// for killing primitives based on PA_CL_CLIP_CNTL::VTX_KILL_OR condition.
|
||||
// Z - KILLVERTEX flag (used in 4D5307ED for grass), set for killing
|
||||
// primitives based on PA_CL_CLIP_CNTL::VTX_KILL_OR condition.
|
||||
kVSPointSizeEdgeFlagKillVertex = 63,
|
||||
|
||||
kPSColor0 = 0,
|
||||
|
|
|
@ -24,7 +24,7 @@ VulkanGraphicsSystem::~VulkanGraphicsSystem() {}
|
|||
X_STATUS VulkanGraphicsSystem::Setup(cpu::Processor* processor,
|
||||
kernel::KernelState* kernel_state,
|
||||
ui::Window* target_window) {
|
||||
provider_ = xe::ui::vulkan::VulkanProvider::Create(target_window);
|
||||
provider_ = xe::ui::vulkan::VulkanProvider::Create();
|
||||
|
||||
return GraphicsSystem::Setup(processor, kernel_state, target_window);
|
||||
}
|
||||
|
|
|
@ -235,10 +235,10 @@ enum class SurfaceNumFormat : uint32_t {
|
|||
//
|
||||
// Depth surfaces are also stored as 32bpp tiles, however, as opposed to color
|
||||
// surfaces, 40x16-sample halves of each tile are swapped - game shaders (for
|
||||
// example, in GTA IV, Halo 3) perform this swapping when writing specific
|
||||
// depth/stencil values by drawing to a depth buffer's memory through a color
|
||||
// render target (to reupload a depth/stencil surface previously evicted from
|
||||
// the EDRAM to the main memory, for instance).
|
||||
// example, in 4D5307E6 main menu, 545407F2) perform this swapping when writing
|
||||
// specific depth/stencil values by drawing to a depth buffer's memory through a
|
||||
// color render target (to reupload a depth/stencil surface previously evicted
|
||||
// from the EDRAM to the main memory, for instance).
|
||||
|
||||
enum class MsaaSamples : uint32_t {
|
||||
k1X = 0,
|
||||
|
@ -728,12 +728,12 @@ enum class SampleControl : uint32_t {
|
|||
// - sample_control is SQ_CONTEXT_MISC::sc_sample_cntl.
|
||||
// - interpolator_control_sampling_pattern is
|
||||
// SQ_INTERPOLATOR_CNTL::sampling_pattern.
|
||||
// Centroid interpolation can be tested in Red Dead Redemption. If the GPU host
|
||||
// backend implements guest MSAA properly, using host MSAA, with everything
|
||||
// interpolated at centers, the Diez Coronas start screen background may have
|
||||
// a few distinctly bright pixels on the mesas/buttes, where extrapolation
|
||||
// happens. Interpolating certain values (ones that aren't used for gradient
|
||||
// calculation, not texture coordinates) at centroids fixes this issue.
|
||||
// Centroid interpolation can be tested in 5454082B. If the GPU host backend
|
||||
// implements guest MSAA properly, using host MSAA, with everything interpolated
|
||||
// at centers, the Monument Valley start screen background may have a few
|
||||
// distinctly bright pixels on the mesas/buttes, where extrapolation happens.
|
||||
// Interpolating certain values (ones that aren't used for gradient calculation,
|
||||
// not texture coordinates) at centroids fixes this issue.
|
||||
inline uint32_t GetInterpolatorSamplingPattern(
|
||||
MsaaSamples msaa_samples, SampleControl sample_control,
|
||||
uint32_t interpolator_control_sampling_pattern) {
|
||||
|
@ -763,9 +763,9 @@ enum class TessellationMode : uint32_t {
|
|||
enum class PolygonModeEnable : uint32_t {
|
||||
kDisabled = 0, // Render triangles.
|
||||
kDualMode = 1, // Send 2 sets of 3 polygons with the specified polygon type.
|
||||
// The game Fuse uses 2 for triangles, which is "reserved" on R6xx and not
|
||||
// defined on Adreno 2xx, but polymode_front/back_ptype are 0 (points) in this
|
||||
// case in Fuse, which should not be respected for non-kDualMode as the game
|
||||
// 4541096E uses 2 for triangles, which is "reserved" on R6xx and not defined
|
||||
// on Adreno 2xx, but polymode_front/back_ptype are 0 (points) in this case in
|
||||
// 4541096E, which should not be respected for non-kDualMode as the title
|
||||
// wants to draw filled triangles.
|
||||
};
|
||||
|
||||
|
@ -785,17 +785,15 @@ enum class ModeControl : uint32_t {
|
|||
// for it especially since the Xbox 360 doesn't have early per-sample depth /
|
||||
// stencil, only early hi-Z / hi-stencil, and other registers possibly
|
||||
// toggling pixel shader execution are yet to be found):
|
||||
// - Most of depth pre-pass draws in Call of Duty 4 use the kDepth more with
|
||||
// a `oC0 = tfetch2D(tf0, r0.xy) * r1` shader, some use `oC0 = r0` though.
|
||||
// - Most of depth pre-pass draws in 415607E6 use the kDepth more with a
|
||||
// `oC0 = tfetch2D(tf0, r0.xy) * r1` shader, some use `oC0 = r0` though.
|
||||
// However, when alphatested surfaces are drawn, kColorDepth is explicitly
|
||||
// used with the same shader performing the texture fetch.
|
||||
// - Red Dead Redemption has some kDepth draws with alphatest enabled, but the
|
||||
// shader is `oC0 = r0`, which makes no sense (alphatest based on an
|
||||
// interpolant from the vertex shader) as no texture alpha cutout is
|
||||
// involved.
|
||||
// - Red Dead Redemption also has kDepth draws with pretty complex shaders
|
||||
// clearly for use only in the color pass - even fetching and filtering a
|
||||
// shadowmap.
|
||||
// - 5454082B has some kDepth draws with alphatest enabled, but the shader is
|
||||
// `oC0 = r0`, which makes no sense (alphatest based on an interpolant from
|
||||
// the vertex shader) as no texture alpha cutout is involved.
|
||||
// - 5454082B also has kDepth draws with pretty complex shaders clearly for
|
||||
// use only in the color pass - even fetching and filtering a shadowmap.
|
||||
// For now, based on these, let's assume the pixel shader is never used with
|
||||
// kDepth.
|
||||
kDepth = 5,
|
||||
|
@ -833,10 +831,10 @@ enum class ModeControl : uint32_t {
|
|||
// coordinates of the corners).
|
||||
//
|
||||
// The rectangle is used for both the source render target and the destination
|
||||
// texture, according to how it's used in Tales of Vesperia.
|
||||
// texture, according to how it's used in 4E4D07E9.
|
||||
//
|
||||
// Direct3D 9 gives the rectangle in source render target coordinates (for
|
||||
// example, in Halo 3, the sniper rifle scope has a (128,64)->(448,256)
|
||||
// example, in 4D5307E6, the sniper rifle scope has a (128,64)->(448,256)
|
||||
// rectangle). It doesn't adjust the EDRAM base pointer, otherwise (taking into
|
||||
// account that 4x MSAA is used for the scope) it would have been
|
||||
// (8,0)->(328,192), but it's not. However, it adjusts the destination texture
|
||||
|
@ -851,7 +849,7 @@ enum class ModeControl : uint32_t {
|
|||
// RB_COPY_DEST_PITCH's purpose appears to be not clamping or something like
|
||||
// that, but just specifying pitch for going between rows, and height for going
|
||||
// between 3D texture slices. copy_dest_pitch is rounded to 32 by Direct3D 9,
|
||||
// copy_dest_height is not. In the Halo 3 sniper rifle scope example,
|
||||
// copy_dest_height is not. In the 4D5307E6 sniper rifle scope example,
|
||||
// copy_dest_pitch is 320, and copy_dest_height is 192 - the same as the resolve
|
||||
// rectangle size (resolving from a 320x192 portion of the surface at 128,64 to
|
||||
// the whole texture, at 0,0). Relative to RB_COPY_DEST_BASE, the height should
|
||||
|
@ -860,17 +858,17 @@ enum class ModeControl : uint32_t {
|
|||
// of the register) that it exists purely to be able to go between 3D texture
|
||||
// slices.
|
||||
//
|
||||
// Window scissor must also be applied - in the jigsaw puzzle in Banjo-Tooie,
|
||||
// there are 1280x720 resolve rectangles, but only the scissored 1280x256
|
||||
// needs to be copied, otherwise it overflows even beyond the EDRAM, and the
|
||||
// depth buffer is visible on the screen. It also ensures the coordinates are
|
||||
// not negative (in F.E.A.R., for example, the right tile is resolved with
|
||||
// vertices (-640,0)->(640,720), however, the destination texture pointer is
|
||||
// adjusted properly to the right half of the texture, and the source render
|
||||
// target has a pitch of 800).
|
||||
// Window scissor must also be applied - in the jigsaw puzzle in 58410955, there
|
||||
// are 1280x720 resolve rectangles, but only the scissored 1280x256 needs to be
|
||||
// copied, otherwise it overflows even beyond the EDRAM, and the depth buffer is
|
||||
// visible on the screen. It also ensures the coordinates are not negative (in
|
||||
// 565507D9, for example, the right tile is resolved with vertices
|
||||
// (-640,0)->(640,720), however, the destination texture pointer is adjusted
|
||||
// properly to the right half of the texture, and the source render target has a
|
||||
// pitch of 800).
|
||||
|
||||
// Granularity of offset and size in resolve operations is 8x8 pixels
|
||||
// (GPU_RESOLVE_ALIGNMENT - for example, Halo 3 resolves a 24x16 region for a
|
||||
// (GPU_RESOLVE_ALIGNMENT - for example, 4D5307E6 resolves a 24x16 region for a
|
||||
// 18x10 texture, 8x8 region for a 1x1 texture).
|
||||
// https://github.com/jmfauvel/CSGO-SDK/blob/master/game/client/view.cpp#L944
|
||||
// https://github.com/stanriders/hl2-asw-port/blob/master/src/game/client/vgui_int.cpp#L901
|
||||
|
@ -1072,9 +1070,9 @@ union alignas(uint32_t) xe_gpu_texture_fetch_t {
|
|||
// pitch is irrelevant to them (but the 256-byte alignment requirement still
|
||||
// applies to linear textures).
|
||||
// Examples of pitch > aligned width:
|
||||
// - Plants vs. Zombies (loading screen and menu backgrounds, 1408 for a
|
||||
// 1280x linear k_DXT4_5 texture, which corresponds to 22 * 256 bytes
|
||||
// rather than 20 * 256 for just 1280x).
|
||||
// - 584109FF (loading screen and menu backgrounds, 1408 for a 1280x linear
|
||||
// k_DXT4_5 texture, which corresponds to 22 * 256 bytes rather than
|
||||
// 20 * 256 for just 1280x).
|
||||
uint32_t pitch : 9; // +22
|
||||
uint32_t tiled : 1; // +31
|
||||
|
||||
|
|
|
@ -10,15 +10,17 @@
|
|||
#include <array>
|
||||
#include <cstring>
|
||||
#include <forward_list>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "third_party/fmt/include/fmt/format.h"
|
||||
#include "third_party/imgui/imgui.h"
|
||||
#include "xenia/base/clock.h"
|
||||
#include "xenia/base/cvar.h"
|
||||
#include "xenia/base/logging.h"
|
||||
#include "xenia/base/main.h"
|
||||
#include "xenia/base/threading.h"
|
||||
#include "xenia/hid/hid_flags.h"
|
||||
#include "xenia/hid/input_system.h"
|
||||
|
@ -26,6 +28,7 @@
|
|||
#include "xenia/ui/virtual_key.h"
|
||||
#include "xenia/ui/vulkan/vulkan_provider.h"
|
||||
#include "xenia/ui/window.h"
|
||||
#include "xenia/ui/windowed_app.h"
|
||||
|
||||
// Available input drivers:
|
||||
#include "xenia/hid/nop/nop_hid.h"
|
||||
|
@ -46,10 +49,36 @@ DEFINE_string(hid, "any", "Input system. Use: [any, nop, sdl, winkey, xinput]",
|
|||
namespace xe {
|
||||
namespace hid {
|
||||
|
||||
std::unique_ptr<xe::hid::InputSystem> input_system_;
|
||||
bool is_active = true;
|
||||
class HidDemoApp final : public ui::WindowedApp {
|
||||
public:
|
||||
static std::unique_ptr<ui::WindowedApp> Create(
|
||||
ui::WindowedAppContext& app_context) {
|
||||
return std::unique_ptr<ui::WindowedApp>(new HidDemoApp(app_context));
|
||||
}
|
||||
|
||||
std::vector<std::unique_ptr<hid::InputDriver>> CreateInputDrivers(
|
||||
bool OnInitialize() override;
|
||||
|
||||
private:
|
||||
explicit HidDemoApp(ui::WindowedAppContext& app_context)
|
||||
: ui::WindowedApp(app_context, "xenia-hid-demo") {}
|
||||
|
||||
static std::vector<std::unique_ptr<hid::InputDriver>> CreateInputDrivers(
|
||||
ui::Window* window);
|
||||
|
||||
void DrawUserInputGetState(uint32_t user_index) const;
|
||||
void DrawInputGetState() const;
|
||||
void DrawUserInputGetKeystroke(uint32_t user_index, bool poll,
|
||||
bool hide_repeats, bool clear_log) const;
|
||||
void DrawInputGetKeystroke(bool poll, bool hide_repeats,
|
||||
bool clear_log) const;
|
||||
|
||||
std::unique_ptr<ui::GraphicsProvider> graphics_provider_;
|
||||
std::unique_ptr<ui::Window> window_;
|
||||
std::unique_ptr<InputSystem> input_system_;
|
||||
bool is_active_ = true;
|
||||
};
|
||||
|
||||
std::vector<std::unique_ptr<hid::InputDriver>> HidDemoApp::CreateInputDrivers(
|
||||
ui::Window* window) {
|
||||
std::vector<std::unique_ptr<hid::InputDriver>> drivers;
|
||||
if (cvars::hid.compare("nop") == 0) {
|
||||
|
@ -94,60 +123,46 @@ std::vector<std::unique_ptr<hid::InputDriver>> CreateInputDrivers(
|
|||
return drivers;
|
||||
}
|
||||
|
||||
std::unique_ptr<xe::ui::GraphicsProvider> CreateDemoGraphicsProvider(
|
||||
xe::ui::Window* window) {
|
||||
return xe::ui::vulkan::VulkanProvider::Create(window);
|
||||
}
|
||||
bool HidDemoApp::OnInitialize() {
|
||||
// Create graphics provider that provides the context for the window.
|
||||
graphics_provider_ = xe::ui::vulkan::VulkanProvider::Create();
|
||||
if (!graphics_provider_) {
|
||||
return false;
|
||||
}
|
||||
|
||||
void DrawInputGetState();
|
||||
void DrawInputGetKeystroke(bool poll, bool hide_repeats, bool clear_log);
|
||||
|
||||
int hid_demo_main(const std::vector<std::string>& args) {
|
||||
// Create run loop and the window.
|
||||
auto loop = ui::Loop::Create();
|
||||
auto window = xe::ui::Window::Create(loop.get(), GetEntryInfo().name);
|
||||
loop->PostSynchronous([&window]() {
|
||||
xe::threading::set_name("Win32 Loop");
|
||||
if (!window->Initialize()) {
|
||||
FatalError("Failed to initialize main window");
|
||||
return;
|
||||
}
|
||||
});
|
||||
window->on_closed.AddListener([&loop](xe::ui::UIEvent* e) {
|
||||
loop->Quit();
|
||||
// Create the window.
|
||||
window_ = xe::ui::Window::Create(app_context(), GetName());
|
||||
if (!window_->Initialize()) {
|
||||
XELOGE("Failed to initialize main window");
|
||||
return false;
|
||||
}
|
||||
window_->on_closed.AddListener([this](xe::ui::UIEvent* e) {
|
||||
XELOGI("User-initiated death!");
|
||||
exit(1);
|
||||
app_context().QuitFromUIThread();
|
||||
});
|
||||
loop->on_quit.AddListener([&window](xe::ui::UIEvent* e) { window.reset(); });
|
||||
|
||||
// Initial size setting, done here so that it knows the menu exists.
|
||||
window->Resize(COL_WIDTH_STATE + COL_WIDTH_STROKE, ROW_HEIGHT_GENERAL + 500);
|
||||
window_->Resize(COL_WIDTH_STATE + COL_WIDTH_STROKE, ROW_HEIGHT_GENERAL + 500);
|
||||
|
||||
// Create the graphics context used for drawing and setup the window.
|
||||
std::unique_ptr<xe::ui::GraphicsProvider> graphics_provider;
|
||||
loop->PostSynchronous([&window, &graphics_provider]() {
|
||||
// Create context and give it to the window.
|
||||
// The window will finish initialization wtih the context (loading
|
||||
// resources, etc).
|
||||
graphics_provider = CreateDemoGraphicsProvider(window.get());
|
||||
window->set_context(graphics_provider->CreateHostContext(window.get()));
|
||||
// Create the graphics context for the window. The window will finish
|
||||
// initialization with the context (loading resources, etc).
|
||||
window_->set_context(graphics_provider_->CreateHostContext(window_.get()));
|
||||
|
||||
// Initialize input system and all drivers.
|
||||
input_system_ = std::make_unique<xe::hid::InputSystem>(window.get());
|
||||
auto drivers = CreateInputDrivers(window.get());
|
||||
for (size_t i = 0; i < drivers.size(); ++i) {
|
||||
auto& driver = drivers[i];
|
||||
driver->set_is_active_callback([]() -> bool { return is_active; });
|
||||
input_system_->AddDriver(std::move(driver));
|
||||
}
|
||||
// Initialize input system and all drivers.
|
||||
input_system_ = std::make_unique<xe::hid::InputSystem>(window_.get());
|
||||
auto drivers = CreateInputDrivers(window_.get());
|
||||
for (size_t i = 0; i < drivers.size(); ++i) {
|
||||
auto& driver = drivers[i];
|
||||
driver->set_is_active_callback([this]() -> bool { return is_active_; });
|
||||
input_system_->AddDriver(std::move(driver));
|
||||
}
|
||||
|
||||
window->Invalidate();
|
||||
});
|
||||
window_->Invalidate();
|
||||
|
||||
window->set_imgui_input_enabled(true);
|
||||
window_->set_imgui_input_enabled(true);
|
||||
|
||||
window->on_painting.AddListener([&](xe::ui::UIEvent* e) {
|
||||
auto& io = window->imgui_drawer()->GetIO();
|
||||
window_->on_painting.AddListener([this](xe::ui::UIEvent* e) {
|
||||
auto& io = window_->imgui_drawer()->GetIO();
|
||||
|
||||
const ImGuiWindowFlags wflags =
|
||||
ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove |
|
||||
|
@ -161,7 +176,7 @@ int hid_demo_main(const std::vector<std::string>& args) {
|
|||
ImVec2(COL_WIDTH_STATE + COL_WIDTH_STROKE, ROW_HEIGHT_GENERAL));
|
||||
|
||||
ImGui::Text("Input System (hid) = \"%s\"", cvars::hid.c_str());
|
||||
ImGui::Checkbox("is_active", &is_active);
|
||||
ImGui::Checkbox("is_active", &is_active_);
|
||||
}
|
||||
ImGui::End();
|
||||
|
||||
|
@ -201,21 +216,13 @@ int hid_demo_main(const std::vector<std::string>& args) {
|
|||
ImGui::End();
|
||||
|
||||
// Continuous paint.
|
||||
window->Invalidate();
|
||||
window_->Invalidate();
|
||||
});
|
||||
|
||||
// Wait until we are exited.
|
||||
loop->AwaitQuit();
|
||||
|
||||
input_system_.reset();
|
||||
|
||||
loop->PostSynchronous([&graphics_provider]() { graphics_provider.reset(); });
|
||||
window.reset();
|
||||
loop.reset();
|
||||
return 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
void DrawUserInputGetState(uint32_t user_index) {
|
||||
void HidDemoApp::DrawUserInputGetState(uint32_t user_index) const {
|
||||
ImGui::Text("User %u:", user_index);
|
||||
|
||||
X_INPUT_STATE state;
|
||||
|
@ -274,7 +281,7 @@ void DrawUserInputGetState(uint32_t user_index) {
|
|||
ImGui::Text(" ");
|
||||
}
|
||||
|
||||
void DrawInputGetState() {
|
||||
void HidDemoApp::DrawInputGetState() const {
|
||||
ImGui::BeginChild("##input_get_state_scroll");
|
||||
for (uint32_t user_index = 0; user_index < MAX_USERS; ++user_index) {
|
||||
DrawUserInputGetState(user_index);
|
||||
|
@ -282,46 +289,48 @@ void DrawInputGetState() {
|
|||
ImGui::EndChild();
|
||||
}
|
||||
|
||||
static const std::unordered_map<ui::VirtualKey, const std::string> kVkPretty = {
|
||||
{ui::VirtualKey::kXInputPadA, "A"},
|
||||
{ui::VirtualKey::kXInputPadB, "B"},
|
||||
{ui::VirtualKey::kXInputPadX, "X"},
|
||||
{ui::VirtualKey::kXInputPadY, "Y"},
|
||||
{ui::VirtualKey::kXInputPadRShoulder, "R Shoulder"},
|
||||
{ui::VirtualKey::kXInputPadLShoulder, "L Shoulder"},
|
||||
{ui::VirtualKey::kXInputPadLTrigger, "L Trigger"},
|
||||
{ui::VirtualKey::kXInputPadRTrigger, "R Trigger"},
|
||||
void HidDemoApp::DrawUserInputGetKeystroke(uint32_t user_index, bool poll,
|
||||
bool hide_repeats,
|
||||
bool clear_log) const {
|
||||
static const std::unordered_map<ui::VirtualKey, const std::string> kVkPretty =
|
||||
{
|
||||
{ui::VirtualKey::kXInputPadA, "A"},
|
||||
{ui::VirtualKey::kXInputPadB, "B"},
|
||||
{ui::VirtualKey::kXInputPadX, "X"},
|
||||
{ui::VirtualKey::kXInputPadY, "Y"},
|
||||
{ui::VirtualKey::kXInputPadRShoulder, "R Shoulder"},
|
||||
{ui::VirtualKey::kXInputPadLShoulder, "L Shoulder"},
|
||||
{ui::VirtualKey::kXInputPadLTrigger, "L Trigger"},
|
||||
{ui::VirtualKey::kXInputPadRTrigger, "R Trigger"},
|
||||
|
||||
{ui::VirtualKey::kXInputPadDpadUp, "DPad up"},
|
||||
{ui::VirtualKey::kXInputPadDpadDown, "DPad down"},
|
||||
{ui::VirtualKey::kXInputPadDpadLeft, "DPad left"},
|
||||
{ui::VirtualKey::kXInputPadDpadRight, "DPad right"},
|
||||
{ui::VirtualKey::kXInputPadStart, "Start"},
|
||||
{ui::VirtualKey::kXInputPadBack, "Back"},
|
||||
{ui::VirtualKey::kXInputPadLThumbPress, "L Thumb press"},
|
||||
{ui::VirtualKey::kXInputPadRThumbPress, "R Thumb press"},
|
||||
{ui::VirtualKey::kXInputPadDpadUp, "DPad up"},
|
||||
{ui::VirtualKey::kXInputPadDpadDown, "DPad down"},
|
||||
{ui::VirtualKey::kXInputPadDpadLeft, "DPad left"},
|
||||
{ui::VirtualKey::kXInputPadDpadRight, "DPad right"},
|
||||
{ui::VirtualKey::kXInputPadStart, "Start"},
|
||||
{ui::VirtualKey::kXInputPadBack, "Back"},
|
||||
{ui::VirtualKey::kXInputPadLThumbPress, "L Thumb press"},
|
||||
{ui::VirtualKey::kXInputPadRThumbPress, "R Thumb press"},
|
||||
|
||||
{ui::VirtualKey::kXInputPadLThumbUp, "L Thumb up"},
|
||||
{ui::VirtualKey::kXInputPadLThumbDown, "L Thumb down"},
|
||||
{ui::VirtualKey::kXInputPadLThumbRight, "L Thumb right"},
|
||||
{ui::VirtualKey::kXInputPadLThumbLeft, "L Thumb left"},
|
||||
{ui::VirtualKey::kXInputPadLThumbUpLeft, "L Thumb up & left"},
|
||||
{ui::VirtualKey::kXInputPadLThumbUpRight, "L Thumb up & right"},
|
||||
{ui::VirtualKey::kXInputPadLThumbDownRight, "L Thumb down & right"},
|
||||
{ui::VirtualKey::kXInputPadLThumbDownLeft, "L Thumb down & left"},
|
||||
{ui::VirtualKey::kXInputPadLThumbUp, "L Thumb up"},
|
||||
{ui::VirtualKey::kXInputPadLThumbDown, "L Thumb down"},
|
||||
{ui::VirtualKey::kXInputPadLThumbRight, "L Thumb right"},
|
||||
{ui::VirtualKey::kXInputPadLThumbLeft, "L Thumb left"},
|
||||
{ui::VirtualKey::kXInputPadLThumbUpLeft, "L Thumb up & left"},
|
||||
{ui::VirtualKey::kXInputPadLThumbUpRight, "L Thumb up & right"},
|
||||
{ui::VirtualKey::kXInputPadLThumbDownRight, "L Thumb down & right"},
|
||||
{ui::VirtualKey::kXInputPadLThumbDownLeft, "L Thumb down & left"},
|
||||
|
||||
{ui::VirtualKey::kXInputPadRThumbUp, "R Thumb up"},
|
||||
{ui::VirtualKey::kXInputPadRThumbDown, "R Thumb down"},
|
||||
{ui::VirtualKey::kXInputPadRThumbRight, "R Thumb right"},
|
||||
{ui::VirtualKey::kXInputPadRThumbLeft, "R Thumb left"},
|
||||
{ui::VirtualKey::kXInputPadRThumbUpLeft, "R Thumb up & left"},
|
||||
{ui::VirtualKey::kXInputPadRThumbUpRight, "R Thumb up & right"},
|
||||
{ui::VirtualKey::kXInputPadRThumbDownRight, "R Thumb down & right"},
|
||||
{ui::VirtualKey::kXInputPadRThumbDownLeft, "R Thumb down & left"},
|
||||
};
|
||||
{ui::VirtualKey::kXInputPadRThumbUp, "R Thumb up"},
|
||||
{ui::VirtualKey::kXInputPadRThumbDown, "R Thumb down"},
|
||||
{ui::VirtualKey::kXInputPadRThumbRight, "R Thumb right"},
|
||||
{ui::VirtualKey::kXInputPadRThumbLeft, "R Thumb left"},
|
||||
{ui::VirtualKey::kXInputPadRThumbUpLeft, "R Thumb up & left"},
|
||||
{ui::VirtualKey::kXInputPadRThumbUpRight, "R Thumb up & right"},
|
||||
{ui::VirtualKey::kXInputPadRThumbDownRight, "R Thumb down & right"},
|
||||
{ui::VirtualKey::kXInputPadRThumbDownLeft, "R Thumb down & left"},
|
||||
};
|
||||
|
||||
void DrawUserInputGetKeystroke(uint32_t user_index, bool poll,
|
||||
bool hide_repeats, bool clear_log) {
|
||||
const size_t maxLog = 128;
|
||||
static std::array<std::forward_list<std::string>, MAX_USERS> event_logs;
|
||||
static std::array<uint64_t, MAX_USERS> last_event_times = {};
|
||||
|
@ -400,7 +409,8 @@ void DrawUserInputGetKeystroke(uint32_t user_index, bool poll,
|
|||
}
|
||||
}
|
||||
|
||||
void DrawInputGetKeystroke(bool poll, bool hide_repeats, bool clear_log) {
|
||||
void HidDemoApp::DrawInputGetKeystroke(bool poll, bool hide_repeats,
|
||||
bool clear_log) const {
|
||||
bool tab_bar = ImGui::BeginTabBar("DrawInputGetKeystroke");
|
||||
for (uint32_t user_index = 0; user_index < MAX_USERS; ++user_index) {
|
||||
DrawUserInputGetKeystroke(user_index, poll, hide_repeats, clear_log);
|
||||
|
@ -411,4 +421,4 @@ void DrawInputGetKeystroke(bool poll, bool hide_repeats, bool clear_log) {
|
|||
} // namespace hid
|
||||
} // namespace xe
|
||||
|
||||
DEFINE_ENTRY_POINT("xenia-hid-demo", xe::hid::hid_demo_main, "");
|
||||
XE_DEFINE_WINDOWED_APP(xenia_hid_demo, xe::hid::HidDemoApp::Create);
|
||||
|
|
|
@ -32,7 +32,7 @@ project("xenia-hid-demo")
|
|||
})
|
||||
files({
|
||||
"hid_demo.cc",
|
||||
"../base/main_"..platform_suffix..".cc",
|
||||
"../ui/windowed_app_main_"..platform_suffix..".cc",
|
||||
})
|
||||
resincludedirs({
|
||||
project_root,
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
#include "xenia/hid/hid_flags.h"
|
||||
#include "xenia/ui/virtual_key.h"
|
||||
#include "xenia/ui/window.h"
|
||||
#include "xenia/ui/windowed_app_context.h"
|
||||
|
||||
// TODO(joellinn) make this path relative to the config folder.
|
||||
DEFINE_path(mappings_file, "gamecontrollerdb.txt",
|
||||
|
@ -43,6 +44,12 @@ SDLInputDriver::SDLInputDriver(xe::ui::Window* window)
|
|||
keystroke_states_() {}
|
||||
|
||||
SDLInputDriver::~SDLInputDriver() {
|
||||
// Make sure the CallInUIThread is executed before destroying the references.
|
||||
if (sdl_pumpevents_queued_) {
|
||||
window()->app_context().CallInUIThreadSynchronous([this]() {
|
||||
window()->app_context().ExecutePendingFunctionsFromUIThread();
|
||||
});
|
||||
}
|
||||
for (size_t i = 0; i < controllers_.size(); i++) {
|
||||
if (controllers_.at(i).sdl) {
|
||||
SDL_GameControllerClose(controllers_.at(i).sdl);
|
||||
|
@ -65,8 +72,9 @@ X_STATUS SDLInputDriver::Setup() {
|
|||
}
|
||||
|
||||
// SDL_PumpEvents should only be run in the thread that initialized SDL - we
|
||||
// are hijacking the window loop thread for that.
|
||||
window()->loop()->PostSynchronous([&]() {
|
||||
// are hijacking the UI thread for that. If this function fails to be queued,
|
||||
// the "initialized" variables will be false - that's handled safely.
|
||||
window()->app_context().CallInUIThreadSynchronous([this]() {
|
||||
if (!xe::helper::sdl::SDLHelper::Prepare()) {
|
||||
return;
|
||||
}
|
||||
|
@ -129,7 +137,9 @@ X_STATUS SDLInputDriver::Setup() {
|
|||
}
|
||||
}
|
||||
});
|
||||
return sdl_events_initialized_ && sdl_gamecontroller_initialized_;
|
||||
return (sdl_events_initialized_ && sdl_gamecontroller_initialized_)
|
||||
? X_STATUS_SUCCESS
|
||||
: X_STATUS_UNSUCCESSFUL;
|
||||
}
|
||||
|
||||
X_RESULT SDLInputDriver::GetCapabilities(uint32_t user_index, uint32_t flags,
|
||||
|
@ -344,7 +354,7 @@ X_RESULT SDLInputDriver::GetKeystroke(uint32_t users, uint32_t flags,
|
|||
if (!(butts_changed & fbutton)) {
|
||||
continue;
|
||||
}
|
||||
ui::VirtualKey vk = kVkLookup.at(last.repeat_butt_idx);
|
||||
ui::VirtualKey vk = kVkLookup.at(i);
|
||||
if (vk == ui::VirtualKey::kNone) {
|
||||
continue;
|
||||
}
|
||||
|
@ -693,7 +703,7 @@ void SDLInputDriver::QueueControllerUpdate() {
|
|||
bool is_queued = false;
|
||||
sdl_pumpevents_queued_.compare_exchange_strong(is_queued, true);
|
||||
if (!is_queued) {
|
||||
window()->loop()->Post([this]() {
|
||||
window()->app_context().CallInUIThread([this]() {
|
||||
SDL_PumpEvents();
|
||||
sdl_pumpevents_queued_ = false;
|
||||
});
|
||||
|
|
|
@ -316,6 +316,17 @@ void KernelState::SetExecutableModule(object_ref<UserModule> module) {
|
|||
*variable_ptr = executable_module_->hmodule_ptr();
|
||||
}
|
||||
|
||||
// Setup the kernel's ExLoadedImageName field
|
||||
export_entry = processor()->export_resolver()->GetExportByOrdinal(
|
||||
"xboxkrnl.exe", ordinals::ExLoadedImageName);
|
||||
|
||||
if (export_entry) {
|
||||
char* variable_ptr =
|
||||
memory()->TranslateVirtual<char*>(export_entry->variable_ptr);
|
||||
xe::string_util::copy_truncating(
|
||||
variable_ptr, executable_module_->path(),
|
||||
xboxkrnl::XboxkrnlModule::kExLoadedImageNameSize);
|
||||
}
|
||||
// Spin up deferred dispatch worker.
|
||||
// TODO(benvanik): move someplace more appropriate (out of ctor, but around
|
||||
// here).
|
||||
|
|
|
@ -98,7 +98,7 @@ X_HRESULT XgiApp::DispatchMessageSync(uint32_t message, uint32_t buffer_ptr,
|
|||
return X_E_SUCCESS;
|
||||
}
|
||||
case 0x000B0014: {
|
||||
// Gets Jetpac XBLA in game
|
||||
// Gets 584107FB in game.
|
||||
// get high score table?
|
||||
XELOGD("XGI_unknown");
|
||||
return X_STATUS_SUCCESS;
|
||||
|
|
|
@ -47,7 +47,7 @@ X_HRESULT XLiveBaseApp::DispatchMessageSync(uint32_t message,
|
|||
// XONLINE_SERVICE_INFO structure.
|
||||
XELOGD("CXLiveLogon::GetServiceInfo({:08X}, {:08X})", buffer_ptr,
|
||||
buffer_length);
|
||||
return 1229; // ERROR_CONNECTION_INVALID
|
||||
return 0x80151802; // ERROR_CONNECTION_INVALID
|
||||
}
|
||||
case 0x00058020: {
|
||||
// 0x00058004 is called right before this.
|
||||
|
@ -66,7 +66,7 @@ X_HRESULT XLiveBaseApp::DispatchMessageSync(uint32_t message,
|
|||
return X_E_FAIL;
|
||||
}
|
||||
case 0x00058046: {
|
||||
// Required to be successful for Forza 4 to detect signed-in profile
|
||||
// Required to be successful for 4D530910 to detect signed-in profile
|
||||
// Doesn't seem to set anything in the given buffer, probably only takes
|
||||
// input
|
||||
XELOGD("XLiveBaseUnk58046({:08X}, {:08X}) unimplemented", buffer_ptr,
|
||||
|
|
|
@ -73,8 +73,8 @@ struct XCONTENT_DATA {
|
|||
}
|
||||
|
||||
void set_display_name(const std::u16string_view value) {
|
||||
// Some games (eg Goldeneye XBLA) require multiple null-terminators for it
|
||||
// to read the string properly, blanking the array should take care of that
|
||||
// Some games (e.g. 584108A9) require multiple null-terminators for it to
|
||||
// read the string properly, blanking the array should take care of that
|
||||
|
||||
std::fill_n(display_name_raw.chars, countof(display_name_raw.chars), 0);
|
||||
string_util::copy_and_swap_truncating(display_name_raw.chars, value,
|
||||
|
|
|
@ -20,9 +20,8 @@ namespace kernel {
|
|||
namespace xam {
|
||||
|
||||
UserProfile::UserProfile() {
|
||||
// NeoGeo Battle Coliseum checks the user XUID against a mask of
|
||||
// 0x00C0000000000000 (3<<54), if non-zero, it prevents the user from playing
|
||||
// the game.
|
||||
// 58410A1F checks the user XUID against a mask of 0x00C0000000000000 (3<<54),
|
||||
// if non-zero, it prevents the user from playing the game.
|
||||
// "You do not have permissions to perform this operation."
|
||||
xuid_ = 0xB13EBABEBABEBABE;
|
||||
name_ = "User";
|
||||
|
|
|
@ -139,9 +139,9 @@ dword_result_t xeXamContentCreate(dword_t user_index, lpstring_t root_name,
|
|||
*disposition_ptr = 0;
|
||||
}
|
||||
|
||||
auto run = [content_manager, root_name, flags, content_data, disposition_ptr,
|
||||
license_mask_ptr](uint32_t& extended_error,
|
||||
uint32_t& length) -> X_RESULT {
|
||||
auto run = [content_manager, root_name = root_name.value(), flags,
|
||||
content_data, disposition_ptr, license_mask_ptr](
|
||||
uint32_t& extended_error, uint32_t& length) -> X_RESULT {
|
||||
X_RESULT result = X_ERROR_INVALID_PARAMETER;
|
||||
bool create = false;
|
||||
bool open = false;
|
||||
|
@ -203,9 +203,9 @@ dword_result_t xeXamContentCreate(dword_t user_index, lpstring_t root_name,
|
|||
}
|
||||
|
||||
if (create) {
|
||||
result = content_manager->CreateContent(root_name.value(), content_data);
|
||||
result = content_manager->CreateContent(root_name, content_data);
|
||||
} else if (open) {
|
||||
result = content_manager->OpenContent(root_name.value(), content_data);
|
||||
result = content_manager->OpenContent(root_name, content_data);
|
||||
}
|
||||
|
||||
if (license_mask_ptr && XSUCCEEDED(result)) {
|
||||
|
|
|
@ -40,10 +40,9 @@ uint32_t xeXamEnumerate(uint32_t handle, uint32_t flags, lpvoid_t buffer_ptr,
|
|||
auto run = [e, buffer_ptr](uint32_t& extended_error,
|
||||
uint32_t& length) -> X_RESULT {
|
||||
X_RESULT result;
|
||||
uint32_t item_count;
|
||||
uint32_t item_count = 0;
|
||||
if (!buffer_ptr) {
|
||||
result = X_ERROR_INVALID_PARAMETER;
|
||||
item_count = 0;
|
||||
} else {
|
||||
result = e->WriteItems(buffer_ptr.guest_address(),
|
||||
buffer_ptr.as<uint8_t*>(), &item_count);
|
||||
|
|
|
@ -249,8 +249,8 @@ dword_result_t NetDll_WSAStartup(dword_t caller, word_t version,
|
|||
data_ptr->max_sockets = wsaData.iMaxSockets;
|
||||
data_ptr->max_udpdg = wsaData.iMaxUdpDg;
|
||||
|
||||
// Some games (PoG) want this value round-tripped - they'll compare if it
|
||||
// changes and bugcheck if it does.
|
||||
// Some games (5841099F) want this value round-tripped - they'll compare if
|
||||
// it changes and bugcheck if it does.
|
||||
uint32_t vendor_ptr = xe::load_and_swap<uint32_t>(data_out + 0x190);
|
||||
xe::store_and_swap<uint32_t>(data_out + 0x190, vendor_ptr);
|
||||
}
|
||||
|
@ -459,7 +459,7 @@ dword_result_t NetDll_XNetGetTitleXnAddr(dword_t caller,
|
|||
// TODO(gibbed): A proper mac address.
|
||||
// RakNet's 360 version appears to depend on abEnet to create "random" 64-bit
|
||||
// numbers. A zero value will cause RakPeer::Startup to fail. This causes
|
||||
// Peggle 2 to crash on startup.
|
||||
// 58411436 to crash on startup.
|
||||
// The 360-specific code is scrubbed from the RakNet repo, but there's still
|
||||
// traces of what it's doing which match the game code.
|
||||
// https://github.com/facebookarchive/RakNet/blob/master/Source/RakPeer.cpp#L382
|
||||
|
@ -950,7 +950,7 @@ dword_result_t NetDll_recvfrom(dword_t caller, dword_t socket_handle,
|
|||
from_ptr->sin_family = native_from.sin_family;
|
||||
from_ptr->sin_port = native_from.sin_port;
|
||||
from_ptr->sin_addr = native_from.sin_addr;
|
||||
memset(from_ptr->sin_zero, 0, 8);
|
||||
std::memset(from_ptr->x_sin_zero, 0, sizeof(from_ptr->x_sin_zero));
|
||||
}
|
||||
if (fromlen_ptr) {
|
||||
*fromlen_ptr = native_fromlen;
|
||||
|
|
|
@ -79,8 +79,8 @@ dword_result_t XNotifyGetNext(dword_t handle, dword_t match_id,
|
|||
}
|
||||
|
||||
*id_ptr = dequeued ? id : 0;
|
||||
// param_ptr may be null - Ghost Recon Advanced Warfighter 2 Demo explicitly
|
||||
// passes nullptr in the code.
|
||||
// param_ptr may be null - 555307F0 Demo explicitly passes nullptr in the
|
||||
// code.
|
||||
// https://github.com/xenia-project/xenia/pull/1577
|
||||
if (param_ptr) {
|
||||
*param_ptr = dequeued ? param : 0;
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
#include "xenia/kernel/xam/xam_private.h"
|
||||
#include "xenia/ui/imgui_dialog.h"
|
||||
#include "xenia/ui/window.h"
|
||||
#include "xenia/ui/windowed_app_context.h"
|
||||
#include "xenia/xbox.h"
|
||||
|
||||
namespace xe {
|
||||
|
@ -50,15 +51,16 @@ dword_result_t XamShowNuiTroubleshooterUI(unknown_t unk1, unknown_t unk2,
|
|||
|
||||
auto display_window = kernel_state()->emulator()->display_window();
|
||||
xe::threading::Fence fence;
|
||||
display_window->loop()->PostSynchronous([&]() {
|
||||
xe::ui::ImGuiDialog::ShowMessageBox(
|
||||
display_window, "NUI Troubleshooter",
|
||||
"The game has indicated there is a problem with NUI (Kinect).")
|
||||
->Then(&fence);
|
||||
});
|
||||
++xam_dialogs_shown_;
|
||||
fence.Wait();
|
||||
--xam_dialogs_shown_;
|
||||
if (display_window->app_context().CallInUIThreadSynchronous([&]() {
|
||||
xe::ui::ImGuiDialog::ShowMessageBox(
|
||||
display_window, "NUI Troubleshooter",
|
||||
"The game has indicated there is a problem with NUI (Kinect).")
|
||||
->Then(&fence);
|
||||
})) {
|
||||
++xam_dialogs_shown_;
|
||||
fence.Wait();
|
||||
--xam_dialogs_shown_;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -17,8 +17,7 @@ namespace kernel {
|
|||
namespace xam {
|
||||
|
||||
dword_result_t XamPartyGetUserList(dword_t player_count, lpdword_t party_list) {
|
||||
// Sonic & All-Stars Racing Transformed want specificly this code
|
||||
// to skip loading party data.
|
||||
// 5345085D wants specifically this code to skip loading party data.
|
||||
// This code is not documented in NT_STATUS code list
|
||||
return 0x807D0003;
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
#include "xenia/base/string_util.h"
|
||||
#include "xenia/cpu/processor.h"
|
||||
#include "xenia/kernel/kernel_state.h"
|
||||
#include "xenia/kernel/user_module.h"
|
||||
#include "xenia/kernel/util/shim_utils.h"
|
||||
#include "xenia/kernel/xam/xam_module.h"
|
||||
#include "xenia/kernel/xam/xam_private.h"
|
||||
|
@ -40,24 +41,37 @@ static_assert_size(XTASK_MESSAGE, 0x1C);
|
|||
|
||||
dword_result_t XamTaskSchedule(lpvoid_t callback,
|
||||
pointer_t<XTASK_MESSAGE> message,
|
||||
dword_t unknown, lpdword_t handle_ptr) {
|
||||
assert_zero(unknown);
|
||||
|
||||
lpdword_t unknown, lpdword_t handle_ptr) {
|
||||
// TODO(gibbed): figure out what this is for
|
||||
*handle_ptr = 12345;
|
||||
|
||||
XELOGW("!! Executing scheduled task ({:08X}) synchronously, PROBABLY BAD !! ",
|
||||
uint32_t stack_size = kernel_state()->GetExecutableModule()->stack_size();
|
||||
|
||||
// Stack must be aligned to 16kb pages
|
||||
stack_size = std::max((uint32_t)0x4000, ((stack_size + 0xFFF) & 0xFFFFF000));
|
||||
|
||||
auto thread =
|
||||
object_ref<XThread>(new XThread(kernel_state(), stack_size, 0, callback,
|
||||
message.guest_address(), 0, true));
|
||||
|
||||
X_STATUS result = thread->Create();
|
||||
|
||||
if (XFAILED(result)) {
|
||||
// Failed!
|
||||
XELOGE("XAM task creation failed: {:08X}", result);
|
||||
return result;
|
||||
}
|
||||
|
||||
XELOGD("XAM task ({:08X}) scheduled asynchronously",
|
||||
callback.guest_address());
|
||||
|
||||
// TODO(gibbed): this is supposed to be async... let's cheat.
|
||||
auto thread_state = XThread::GetCurrentThread()->thread_state();
|
||||
uint64_t args[] = {message.guest_address()};
|
||||
auto result = kernel_state()->processor()->Execute(thread_state, callback,
|
||||
args, xe::countof(args));
|
||||
return X_STATUS_SUCCESS;
|
||||
}
|
||||
DECLARE_XAM_EXPORT2(XamTaskSchedule, kNone, kImplemented, kSketchy);
|
||||
|
||||
dword_result_t XamTaskShouldExit(dword_t r3) { return 0; }
|
||||
DECLARE_XAM_EXPORT2(XamTaskShouldExit, kNone, kStub, kSketchy);
|
||||
|
||||
void RegisterTaskExports(xe::cpu::ExportResolver* export_resolver,
|
||||
KernelState* kernel_state) {}
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
#include "xenia/kernel/xam/xam_private.h"
|
||||
#include "xenia/ui/imgui_dialog.h"
|
||||
#include "xenia/ui/window.h"
|
||||
#include "xenia/ui/windowed_app_context.h"
|
||||
#include "xenia/xbox.h"
|
||||
|
||||
namespace xe {
|
||||
|
@ -76,11 +77,16 @@ X_RESULT xeXamDispatchDialog(T* dialog,
|
|||
result = close_callback(dialog);
|
||||
});
|
||||
xe::threading::Fence fence;
|
||||
kernel_state()->emulator()->display_window()->loop()->PostSynchronous(
|
||||
[&dialog, &fence]() { dialog->Then(&fence); });
|
||||
++xam_dialogs_shown_;
|
||||
fence.Wait();
|
||||
--xam_dialogs_shown_;
|
||||
xe::ui::WindowedAppContext& app_context =
|
||||
kernel_state()->emulator()->display_window()->app_context();
|
||||
if (app_context.CallInUIThreadSynchronous(
|
||||
[&dialog, &fence]() { dialog->Then(&fence); })) {
|
||||
++xam_dialogs_shown_;
|
||||
fence.Wait();
|
||||
--xam_dialogs_shown_;
|
||||
} else {
|
||||
delete dialog;
|
||||
}
|
||||
// dialog should be deleted at this point!
|
||||
return result;
|
||||
};
|
||||
|
@ -117,11 +123,14 @@ X_RESULT xeXamDispatchDialogEx(
|
|||
result = close_callback(dialog, extended_error, length);
|
||||
});
|
||||
xe::threading::Fence fence;
|
||||
display_window->loop()->PostSynchronous(
|
||||
[&dialog, &fence]() { dialog->Then(&fence); });
|
||||
++xam_dialogs_shown_;
|
||||
fence.Wait();
|
||||
--xam_dialogs_shown_;
|
||||
if (display_window->app_context().CallInUIThreadSynchronous(
|
||||
[&dialog, &fence]() { dialog->Then(&fence); })) {
|
||||
++xam_dialogs_shown_;
|
||||
fence.Wait();
|
||||
--xam_dialogs_shown_;
|
||||
} else {
|
||||
delete dialog;
|
||||
}
|
||||
// dialog should be deleted at this point!
|
||||
return result;
|
||||
};
|
||||
|
|
|
@ -716,7 +716,7 @@ dword_result_t XamUserCreateAchievementEnumerator(dword_t title_id,
|
|||
i, // dummy image id
|
||||
0,
|
||||
{0, 0},
|
||||
8}; // flags=8 makes dummy achievements show up in Crackdown's
|
||||
8}; // flags=8 makes dummy achievements show up in 4D5307DC
|
||||
// achievements list.
|
||||
e->AppendItem(item);
|
||||
}
|
||||
|
|
|
@ -34,7 +34,7 @@ DECLARE_XBDM_EXPORT1(DmCloseLoadedModules, kDebug, kStub);
|
|||
MAKE_DUMMY_STUB_STATUS(DmFreePool);
|
||||
|
||||
dword_result_t DmGetXbeInfo() {
|
||||
// TODO(gibbed): Crackdown appears to expect this as success?
|
||||
// TODO(gibbed): 4D5307DC appears to expect this as success?
|
||||
// Unknown arguments -- let's hope things don't explode.
|
||||
return 0x02DA0000;
|
||||
}
|
||||
|
|
|
@ -119,7 +119,7 @@ static_assert_size(XMA_CONTEXT_INIT, 56);
|
|||
|
||||
dword_result_t XMAInitializeContext(lpvoid_t context_ptr,
|
||||
pointer_t<XMA_CONTEXT_INIT> context_init) {
|
||||
// Input buffers may be null (buffer 1 in Tony Hawk's American Wasteland).
|
||||
// Input buffers may be null (buffer 1 in 415607D4).
|
||||
// Convert to host endianness.
|
||||
uint32_t input_buffer_0_guest_ptr = context_init->input_buffer_0_ptr;
|
||||
uint32_t input_buffer_0_physical_address = 0;
|
||||
|
|
|
@ -47,8 +47,8 @@ void HandleSetThreadName(pointer_t<X_EXCEPTION_RECORD> record) {
|
|||
return;
|
||||
}
|
||||
|
||||
// Shadowrun (and its demo) has a bug where it ends up passing freed memory
|
||||
// for the name, so at the point of SetThreadName it's filled with junk.
|
||||
// 4D5307D6 (and its demo) has a bug where it ends up passing freed memory for
|
||||
// the name, so at the point of SetThreadName it's filled with junk.
|
||||
|
||||
// TODO(gibbed): cvar for thread name encoding for conversion, some games use
|
||||
// SJIS and there's no way to automatically know this.
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue