Merge branch 'master' into vulkan

This commit is contained in:
Triang3l 2021-09-12 14:10:36 +03:00
commit ecccd02f8a
161 changed files with 3359 additions and 1562 deletions

View File

@ -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

View File

@ -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",
})

View File

@ -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() {

View File

@ -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;

View File

@ -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",
})

View File

@ -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);

View File

@ -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

View File

@ -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;

View File

@ -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]);

View File

@ -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>

22
src/xenia/base/console.h Normal file
View File

@ -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_

View File

@ -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_

View File

@ -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"

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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

View File

@ -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

View File

@ -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;

View File

@ -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>

View File

@ -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"

View File

@ -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.

View File

@ -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.

View File

@ -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 {

View File

@ -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

29
src/xenia/base/main_win.h Normal file
View File

@ -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_

View File

@ -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",
})

View File

@ -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);

View File

@ -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));

View File

@ -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:

View File

@ -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");

View File

@ -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");

View File

@ -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",

View File

@ -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, "");

View File

@ -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"

View File

@ -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

View File

@ -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;

View File

@ -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_ = "";

View File

@ -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));

View File

@ -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) {

View File

@ -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.

View File

@ -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();

View File

@ -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),

View File

@ -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");

View 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);

View File

@ -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 ||

View File

@ -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"

View File

@ -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();
}

View File

@ -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;

View File

@ -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);

View File

@ -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

View File

@ -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]

View File

@ -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,

View File

@ -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.

View File

@ -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,

View File

@ -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"

View File

@ -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);
}

View File

@ -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")

View File

@ -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;

View File

@ -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

View File

@ -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).

View File

@ -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.

View File

@ -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_;

View File

@ -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");

View File

@ -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.

View File

@ -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;
}

View File

@ -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) |

View File

@ -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).

View File

@ -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

View File

@ -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;

View File

@ -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 {

View File

@ -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) /

View File

@ -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;

View File

@ -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;
}

View File

@ -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

View File

@ -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_;

View File

@ -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;

View File

@ -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();

View File

@ -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,

View File

@ -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);
}

View File

@ -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

View File

@ -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);

View File

@ -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,

View File

@ -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;
});

View File

@ -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).

View File

@ -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;

View File

@ -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,

View File

@ -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,

View File

@ -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";

View File

@ -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)) {

View File

@ -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);

View File

@ -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;

View File

@ -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;

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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) {}

View File

@ -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;
};

View File

@ -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);
}

View File

@ -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;
}

View File

@ -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;

View File

@ -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