mirror of
https://github.com/xenia-project/xenia.git
synced 2025-12-06 07:12:03 +01:00
Merge branch 'master' into vulkan
This commit is contained in:
commit
44e4849e1a
10
.gdbinit
Normal file
10
.gdbinit
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
# Ignore HighResolutionTimer custom event
|
||||||
|
handle SIG34 nostop noprint
|
||||||
|
# Ignore PosixTimer custom event
|
||||||
|
handle SIG35 nostop noprint
|
||||||
|
# Ignore PosixThread exit event
|
||||||
|
handle SIG32 nostop noprint
|
||||||
|
# Ignore PosixThread suspend event
|
||||||
|
handle SIG36 nostop noprint
|
||||||
|
# Ignore PosixThread user callback event
|
||||||
|
handle SIG37 nostop noprint
|
||||||
|
|
@ -62,7 +62,7 @@ that there are some major work areas still untouched:
|
||||||
|
|
||||||
* Help work through [missing functionality/bugs in games](https://github.com/xenia-project/xenia/labels/compat)
|
* Help work through [missing functionality/bugs in games](https://github.com/xenia-project/xenia/labels/compat)
|
||||||
* Add input drivers for [DualShock4 (PS4) controllers](https://github.com/xenia-project/xenia/issues/60) (or anything else)
|
* Add input drivers for [DualShock4 (PS4) controllers](https://github.com/xenia-project/xenia/issues/60) (or anything else)
|
||||||
* Skilled with Linux? A strong contributor is needed to [help with porting](https://github.com/xenia-project/xenia/labels/cross%20platform)
|
* Skilled with Linux? A strong contributor is needed to [help with porting](https://github.com/xenia-project/xenia/labels/platform-linux)
|
||||||
|
|
||||||
See more projects [good for contributors](https://github.com/xenia-project/xenia/labels/good%20first%20issue). It's a good idea to ask on Discord and check the issues page before beginning work on
|
See more projects [good for contributors](https://github.com/xenia-project/xenia/labels/good%20first%20issue). It's a good idea to ask on Discord and check the issues page before beginning work on
|
||||||
something.
|
something.
|
||||||
|
|
|
||||||
|
|
@ -40,9 +40,10 @@ void DiscordPresence::NotPlaying() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void DiscordPresence::PlayingTitle(const std::string_view game_title) {
|
void DiscordPresence::PlayingTitle(const std::string_view game_title) {
|
||||||
|
auto details = std::string(game_title);
|
||||||
DiscordRichPresence discordPresence = {};
|
DiscordRichPresence discordPresence = {};
|
||||||
discordPresence.state = "In Game";
|
discordPresence.state = "In Game";
|
||||||
discordPresence.details = std::string(game_title).c_str();
|
discordPresence.details = details.c_str();
|
||||||
// TODO(gibbed): we don't have state icons yet.
|
// TODO(gibbed): we don't have state icons yet.
|
||||||
// discordPresence.smallImageKey = "app";
|
// discordPresence.smallImageKey = "app";
|
||||||
// discordPresence.largeImageKey = "state_ingame";
|
// discordPresence.largeImageKey = "state_ingame";
|
||||||
|
|
|
||||||
|
|
@ -65,8 +65,8 @@ std::unique_ptr<EmulatorWindow> EmulatorWindow::Create(Emulator* emulator) {
|
||||||
std::unique_ptr<EmulatorWindow> emulator_window(new EmulatorWindow(emulator));
|
std::unique_ptr<EmulatorWindow> emulator_window(new EmulatorWindow(emulator));
|
||||||
|
|
||||||
emulator_window->loop()->PostSynchronous([&emulator_window]() {
|
emulator_window->loop()->PostSynchronous([&emulator_window]() {
|
||||||
xe::threading::set_name("Win32 Loop");
|
xe::threading::set_name("Windowing Loop");
|
||||||
xe::Profiler::ThreadEnter("Win32 Loop");
|
xe::Profiler::ThreadEnter("Windowing Loop");
|
||||||
|
|
||||||
if (!emulator_window->Initialize()) {
|
if (!emulator_window->Initialize()) {
|
||||||
xe::FatalError("Failed to initialize main window");
|
xe::FatalError("Failed to initialize main window");
|
||||||
|
|
|
||||||
|
|
@ -302,6 +302,7 @@ void XmaContext::DecodePackets(XMA_CONTEXT_DATA* data) {
|
||||||
|
|
||||||
// No available data.
|
// No available data.
|
||||||
if (!data->input_buffer_0_valid && !data->input_buffer_1_valid) {
|
if (!data->input_buffer_0_valid && !data->input_buffer_1_valid) {
|
||||||
|
data->output_buffer_valid = 0;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -144,7 +144,7 @@ X_STATUS XmaDecoder::Setup(kernel::KernelState* kernel_state) {
|
||||||
WorkerThreadMain();
|
WorkerThreadMain();
|
||||||
return 0;
|
return 0;
|
||||||
}));
|
}));
|
||||||
worker_thread_->set_name("XMA Decoder Worker");
|
worker_thread_->set_name("XMA Decoder");
|
||||||
worker_thread_->set_can_debugger_suspend(true);
|
worker_thread_->set_can_debugger_suspend(true);
|
||||||
worker_thread_->Create();
|
worker_thread_->Create();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -36,10 +36,8 @@
|
||||||
|
|
||||||
#include "third_party/fmt/include/fmt/format.h"
|
#include "third_party/fmt/include/fmt/format.h"
|
||||||
|
|
||||||
DEFINE_path(
|
DEFINE_path(log_file, "", "Logs are written to the given file", "Logging");
|
||||||
log_file, "",
|
DEFINE_bool(log_to_stdout, true, "Write log output to stdout", "Logging");
|
||||||
"Logs are written to the given file (specify stdout for command line)",
|
|
||||||
"Logging");
|
|
||||||
DEFINE_bool(log_to_debugprint, false, "Dump the log to DebugPrint.", "Logging");
|
DEFINE_bool(log_to_debugprint, false, "Dump the log to DebugPrint.", "Logging");
|
||||||
DEFINE_bool(flush_log, true, "Flush log file after each log line batch.",
|
DEFINE_bool(flush_log, true, "Flush log file after each log line batch.",
|
||||||
"Logging");
|
"Logging");
|
||||||
|
|
@ -66,41 +64,39 @@ struct LogLine {
|
||||||
|
|
||||||
thread_local char thread_log_buffer_[64 * 1024];
|
thread_local char thread_log_buffer_[64 * 1024];
|
||||||
|
|
||||||
|
void FileLogSink::Write(const char* buf, size_t size) {
|
||||||
|
if (file_) {
|
||||||
|
fwrite(buf, 1, size, file_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void FileLogSink::Flush() {
|
||||||
|
if (file_) {
|
||||||
|
fflush(file_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class Logger {
|
class Logger {
|
||||||
public:
|
public:
|
||||||
explicit Logger(const std::string_view app_name)
|
explicit Logger(const std::string_view app_name)
|
||||||
: file_(nullptr),
|
: wait_strategy_(),
|
||||||
running_(true),
|
|
||||||
wait_strategy_(),
|
|
||||||
claim_strategy_(kBlockCount, wait_strategy_),
|
claim_strategy_(kBlockCount, wait_strategy_),
|
||||||
consumed_(wait_strategy_) {
|
consumed_(wait_strategy_),
|
||||||
|
running_(true) {
|
||||||
claim_strategy_.add_claim_barrier(consumed_);
|
claim_strategy_.add_claim_barrier(consumed_);
|
||||||
|
|
||||||
if (cvars::log_file.empty()) {
|
|
||||||
// Default to app name.
|
|
||||||
auto file_name = fmt::format("{}.log", app_name);
|
|
||||||
auto file_path = std::filesystem::path(file_name);
|
|
||||||
xe::filesystem::CreateParentFolder(file_path);
|
|
||||||
file_ = xe::filesystem::OpenFile(file_path, "wt");
|
|
||||||
} else {
|
|
||||||
if (cvars::log_file == "stdout") {
|
|
||||||
file_ = stdout;
|
|
||||||
} else {
|
|
||||||
xe::filesystem::CreateParentFolder(cvars::log_file);
|
|
||||||
file_ = xe::filesystem::OpenFile(cvars::log_file, "wt");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
write_thread_ =
|
write_thread_ =
|
||||||
xe::threading::Thread::Create({}, [this]() { WriteThread(); });
|
xe::threading::Thread::Create({}, [this]() { WriteThread(); });
|
||||||
write_thread_->set_name("xe::FileLogSink Writer");
|
write_thread_->set_name("Logging Writer");
|
||||||
}
|
}
|
||||||
|
|
||||||
~Logger() {
|
~Logger() {
|
||||||
running_ = false;
|
running_ = false;
|
||||||
xe::threading::Wait(write_thread_.get(), true);
|
xe::threading::Wait(write_thread_.get(), true);
|
||||||
fflush(file_);
|
}
|
||||||
fclose(file_);
|
|
||||||
|
void AddLogSink(std::unique_ptr<LogSink>&& sink) {
|
||||||
|
sinks_.push_back(std::move(sink));
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
@ -126,14 +122,14 @@ class Logger {
|
||||||
dp::multi_threaded_claim_strategy<dp::spin_wait_strategy> claim_strategy_;
|
dp::multi_threaded_claim_strategy<dp::spin_wait_strategy> claim_strategy_;
|
||||||
dp::sequence_barrier<dp::spin_wait_strategy> consumed_;
|
dp::sequence_barrier<dp::spin_wait_strategy> consumed_;
|
||||||
|
|
||||||
FILE* file_;
|
std::vector<std::unique_ptr<LogSink>> sinks_;
|
||||||
|
|
||||||
std::atomic<bool> running_;
|
std::atomic<bool> running_;
|
||||||
std::unique_ptr<xe::threading::Thread> write_thread_;
|
std::unique_ptr<xe::threading::Thread> write_thread_;
|
||||||
|
|
||||||
void Write(const char* buf, size_t size) {
|
void Write(const char* buf, size_t size) {
|
||||||
if (file_) {
|
for (const auto& sink : sinks_) {
|
||||||
fwrite(buf, 1, size, file_);
|
sink->Write(buf, size);
|
||||||
}
|
}
|
||||||
if (cvars::log_to_debugprint) {
|
if (cvars::log_to_debugprint) {
|
||||||
debugging::DebugPrint("{}", std::string_view(buf, size));
|
debugging::DebugPrint("{}", std::string_view(buf, size));
|
||||||
|
|
@ -246,7 +242,9 @@ class Logger {
|
||||||
desired_count = 1;
|
desired_count = 1;
|
||||||
|
|
||||||
if (cvars::flush_log) {
|
if (cvars::flush_log) {
|
||||||
fflush(file_);
|
for (const auto& sink : sinks_) {
|
||||||
|
sink->Flush();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
idle_loops = 0;
|
idle_loops = 0;
|
||||||
|
|
@ -291,6 +289,27 @@ class Logger {
|
||||||
void InitializeLogging(const std::string_view app_name) {
|
void InitializeLogging(const std::string_view app_name) {
|
||||||
auto mem = memory::AlignedAlloc<Logger>(0x10);
|
auto mem = memory::AlignedAlloc<Logger>(0x10);
|
||||||
logger_ = new (mem) Logger(app_name);
|
logger_ = new (mem) Logger(app_name);
|
||||||
|
|
||||||
|
FILE* log_file = nullptr;
|
||||||
|
|
||||||
|
if (cvars::log_file.empty()) {
|
||||||
|
// Default to app name.
|
||||||
|
auto file_name = fmt::format("{}.log", app_name);
|
||||||
|
auto file_path = std::filesystem::path(file_name);
|
||||||
|
xe::filesystem::CreateParentFolder(file_path);
|
||||||
|
|
||||||
|
log_file = xe::filesystem::OpenFile(file_path, "wt");
|
||||||
|
} else {
|
||||||
|
xe::filesystem::CreateParentFolder(cvars::log_file);
|
||||||
|
log_file = xe::filesystem::OpenFile(cvars::log_file, "wt");
|
||||||
|
}
|
||||||
|
auto sink = std::make_unique<FileLogSink>(log_file);
|
||||||
|
logger_->AddLogSink(std::move(sink));
|
||||||
|
|
||||||
|
if (cvars::log_to_stdout) {
|
||||||
|
auto stdout_sink = std::make_unique<FileLogSink>(stdout);
|
||||||
|
logger_->AddLogSink(std::move(stdout_sink));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ShutdownLogging() {
|
void ShutdownLogging() {
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,31 @@ enum class LogLevel {
|
||||||
Trace,
|
Trace,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class LogSink {
|
||||||
|
public:
|
||||||
|
virtual ~LogSink() = default;
|
||||||
|
|
||||||
|
virtual void Write(const char* buf, size_t size) = 0;
|
||||||
|
virtual void Flush() = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
class FileLogSink final : public LogSink {
|
||||||
|
public:
|
||||||
|
explicit FileLogSink(FILE* file) : file_(file) {}
|
||||||
|
virtual ~FileLogSink() {
|
||||||
|
if (file_) {
|
||||||
|
fflush(file_);
|
||||||
|
fclose(file_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Write(const char* buf, size_t size) override;
|
||||||
|
void Flush() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
FILE* file_;
|
||||||
|
};
|
||||||
|
|
||||||
// Initializes the logging system and any outputs requested.
|
// Initializes the logging system and any outputs requested.
|
||||||
// Must be called on startup.
|
// Must be called on startup.
|
||||||
void InitializeLogging(const std::string_view app_name);
|
void InitializeLogging(const std::string_view app_name);
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,8 @@
|
||||||
|
|
||||||
DEFINE_bool(win32_high_freq, true,
|
DEFINE_bool(win32_high_freq, true,
|
||||||
"Requests high performance from the NT kernel", "Kernel");
|
"Requests high performance from the NT kernel", "Kernel");
|
||||||
|
DEFINE_bool(enable_console, false, "Open a console window with the main window",
|
||||||
|
"General");
|
||||||
|
|
||||||
namespace xe {
|
namespace xe {
|
||||||
|
|
||||||
|
|
@ -37,27 +39,23 @@ bool has_console_attached_ = true;
|
||||||
bool has_console_attached() { return has_console_attached_; }
|
bool has_console_attached() { return has_console_attached_; }
|
||||||
|
|
||||||
void AttachConsole() {
|
void AttachConsole() {
|
||||||
bool has_console = ::AttachConsole(ATTACH_PARENT_PROCESS) == TRUE;
|
if (!cvars::enable_console) {
|
||||||
if (!has_console) {
|
|
||||||
// We weren't launched from a console, so just return.
|
|
||||||
// We could alloc our own console, but meh:
|
|
||||||
// has_console = AllocConsole() == TRUE;
|
|
||||||
has_console_attached_ = false;
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AllocConsole();
|
||||||
|
|
||||||
has_console_attached_ = true;
|
has_console_attached_ = true;
|
||||||
|
|
||||||
auto std_handle = (intptr_t)GetStdHandle(STD_OUTPUT_HANDLE);
|
auto std_handle = (intptr_t)GetStdHandle(STD_OUTPUT_HANDLE);
|
||||||
auto con_handle = _open_osfhandle(std_handle, _O_TEXT);
|
auto con_handle = _open_osfhandle(std_handle, _O_TEXT);
|
||||||
auto fp = _fdopen(con_handle, "w");
|
auto fp = _fdopen(con_handle, "w");
|
||||||
*stdout = *fp;
|
freopen_s(&fp, "CONOUT$", "w", stdout);
|
||||||
setvbuf(stdout, nullptr, _IONBF, 0);
|
|
||||||
|
|
||||||
std_handle = (intptr_t)GetStdHandle(STD_ERROR_HANDLE);
|
std_handle = (intptr_t)GetStdHandle(STD_ERROR_HANDLE);
|
||||||
con_handle = _open_osfhandle(std_handle, _O_TEXT);
|
con_handle = _open_osfhandle(std_handle, _O_TEXT);
|
||||||
fp = _fdopen(con_handle, "w");
|
fp = _fdopen(con_handle, "w");
|
||||||
*stderr = *fp;
|
freopen_s(&fp, "CONOUT$", "w", stderr);
|
||||||
setvbuf(stderr, nullptr, _IONBF, 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void RequestHighPerformance() {
|
static void RequestHighPerformance() {
|
||||||
|
|
@ -125,6 +123,10 @@ int Main() {
|
||||||
return 1;
|
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.
|
||||||
|
xe::AttachConsole();
|
||||||
|
|
||||||
// Setup COM on the main thread.
|
// Setup COM on the main thread.
|
||||||
// NOTE: this may fail if COM has already been initialized - that's OK.
|
// NOTE: this may fail if COM has already been initialized - that's OK.
|
||||||
#pragma warning(suppress : 6031)
|
#pragma warning(suppress : 6031)
|
||||||
|
|
@ -163,10 +165,6 @@ int main(int argc_ignored, char** argv_ignored) { return xe::Main(); }
|
||||||
|
|
||||||
// Used in windowed apps; automatically picked based on subsystem.
|
// Used in windowed apps; automatically picked based on subsystem.
|
||||||
int WINAPI wWinMain(HINSTANCE, HINSTANCE, LPWSTR command_line, int) {
|
int WINAPI wWinMain(HINSTANCE, HINSTANCE, LPWSTR command_line, int) {
|
||||||
// Attach a console so we can write output to stdout. If the user hasn't
|
|
||||||
// redirected output themselves it'll pop up a window.
|
|
||||||
xe::AttachConsole();
|
|
||||||
|
|
||||||
// Run normal entry point.
|
// Run normal entry point.
|
||||||
return xe::Main();
|
return xe::Main();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,11 +10,15 @@
|
||||||
#ifndef XENIA_BASE_STRING_UTIL_H_
|
#ifndef XENIA_BASE_STRING_UTIL_H_
|
||||||
#define XENIA_BASE_STRING_UTIL_H_
|
#define XENIA_BASE_STRING_UTIL_H_
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
#include <charconv>
|
#include <charconv>
|
||||||
|
#include <cstddef>
|
||||||
|
#include <cstring>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
#include "third_party/fmt/include/fmt/format.h"
|
#include "third_party/fmt/include/fmt/format.h"
|
||||||
#include "xenia/base/assert.h"
|
#include "xenia/base/assert.h"
|
||||||
|
#include "xenia/base/memory.h"
|
||||||
#include "xenia/base/platform.h"
|
#include "xenia/base/platform.h"
|
||||||
#include "xenia/base/string.h"
|
#include "xenia/base/string.h"
|
||||||
#include "xenia/base/vec128.h"
|
#include "xenia/base/vec128.h"
|
||||||
|
|
@ -30,6 +34,40 @@
|
||||||
namespace xe {
|
namespace xe {
|
||||||
namespace string_util {
|
namespace string_util {
|
||||||
|
|
||||||
|
inline size_t copy_truncating(char* dest, const std::string_view source,
|
||||||
|
size_t dest_buffer_count) {
|
||||||
|
if (!dest_buffer_count) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
size_t chars_copied = std::min(source.size(), dest_buffer_count - size_t(1));
|
||||||
|
std::memcpy(dest, source.data(), chars_copied);
|
||||||
|
dest[chars_copied] = '\0';
|
||||||
|
return chars_copied;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline size_t copy_truncating(char16_t* dest, const std::u16string_view source,
|
||||||
|
size_t dest_buffer_count) {
|
||||||
|
if (!dest_buffer_count) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
size_t chars_copied = std::min(source.size(), dest_buffer_count - size_t(1));
|
||||||
|
std::memcpy(dest, source.data(), chars_copied * sizeof(char16_t));
|
||||||
|
dest[chars_copied] = u'\0';
|
||||||
|
return chars_copied;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline size_t copy_and_swap_truncating(char16_t* dest,
|
||||||
|
const std::u16string_view source,
|
||||||
|
size_t dest_buffer_count) {
|
||||||
|
if (!dest_buffer_count) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
size_t chars_copied = std::min(source.size(), dest_buffer_count - size_t(1));
|
||||||
|
xe::copy_and_swap(dest, source.data(), chars_copied);
|
||||||
|
dest[chars_copied] = u'\0';
|
||||||
|
return chars_copied;
|
||||||
|
}
|
||||||
|
|
||||||
inline std::string to_hex_string(uint32_t value) {
|
inline std::string to_hex_string(uint32_t value) {
|
||||||
return fmt::format("{:08X}", value);
|
return fmt::format("{:08X}", value);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ namespace xe {
|
||||||
|
|
||||||
void LaunchWebBrowser(const std::string& url) {
|
void LaunchWebBrowser(const std::string& url) {
|
||||||
auto temp = xe::to_utf16(url);
|
auto temp = xe::to_utf16(url);
|
||||||
ShellExecuteW(nullptr, L"open", reinterpret_cast<LPCWSTR>(url.c_str()),
|
ShellExecuteW(nullptr, L"open", reinterpret_cast<LPCWSTR>(temp.c_str()),
|
||||||
nullptr, nullptr, SW_SHOWNORMAL);
|
nullptr, nullptr, SW_SHOWNORMAL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
967
src/xenia/base/testing/threading_test.cc
Normal file
967
src/xenia/base/testing/threading_test.cc
Normal file
|
|
@ -0,0 +1,967 @@
|
||||||
|
/**
|
||||||
|
******************************************************************************
|
||||||
|
* Xenia : Xbox 360 Emulator Research Project *
|
||||||
|
******************************************************************************
|
||||||
|
* Copyright 2018 Ben Vanik. All rights reserved. *
|
||||||
|
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||||
|
******************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
|
||||||
|
#include "xenia/base/threading.h"
|
||||||
|
|
||||||
|
#include "third_party/catch/include/catch.hpp"
|
||||||
|
|
||||||
|
namespace xe {
|
||||||
|
namespace base {
|
||||||
|
namespace test {
|
||||||
|
using namespace threading;
|
||||||
|
using namespace std::chrono_literals;
|
||||||
|
|
||||||
|
TEST_CASE("Fence") {
|
||||||
|
std::unique_ptr<threading::Fence> pFence;
|
||||||
|
std::unique_ptr<threading::HighResolutionTimer> pTimer;
|
||||||
|
|
||||||
|
// Signal without wait
|
||||||
|
pFence = std::make_unique<threading::Fence>();
|
||||||
|
pFence->Signal();
|
||||||
|
|
||||||
|
// Signal once and wait
|
||||||
|
pFence = std::make_unique<threading::Fence>();
|
||||||
|
pFence->Signal();
|
||||||
|
pFence->Wait();
|
||||||
|
|
||||||
|
// Signal twice and wait
|
||||||
|
pFence = std::make_unique<threading::Fence>();
|
||||||
|
pFence->Signal();
|
||||||
|
pFence->Signal();
|
||||||
|
pFence->Wait();
|
||||||
|
|
||||||
|
// Signal and wait two times
|
||||||
|
pFence = std::make_unique<threading::Fence>();
|
||||||
|
pFence->Signal();
|
||||||
|
pFence->Wait();
|
||||||
|
pFence->Signal();
|
||||||
|
pFence->Wait();
|
||||||
|
|
||||||
|
// Test to synchronize multiple threads
|
||||||
|
std::atomic<int> started(0);
|
||||||
|
std::atomic<int> finished(0);
|
||||||
|
pFence = std::make_unique<threading::Fence>();
|
||||||
|
auto func = [&pFence, &started, &finished] {
|
||||||
|
started.fetch_add(1);
|
||||||
|
pFence->Wait();
|
||||||
|
finished.fetch_add(1);
|
||||||
|
};
|
||||||
|
|
||||||
|
auto threads = std::array<std::thread, 5>({
|
||||||
|
std::thread(func),
|
||||||
|
std::thread(func),
|
||||||
|
std::thread(func),
|
||||||
|
std::thread(func),
|
||||||
|
std::thread(func),
|
||||||
|
});
|
||||||
|
|
||||||
|
Sleep(100ms);
|
||||||
|
REQUIRE(started.load() == threads.size());
|
||||||
|
REQUIRE(finished.load() == 0);
|
||||||
|
|
||||||
|
pFence->Signal();
|
||||||
|
|
||||||
|
for (auto& t : threads) t.join();
|
||||||
|
REQUIRE(finished.load() == threads.size());
|
||||||
|
} // namespace test
|
||||||
|
|
||||||
|
TEST_CASE("Get number of logical processors") {
|
||||||
|
auto count = std::thread::hardware_concurrency();
|
||||||
|
REQUIRE(logical_processor_count() == count);
|
||||||
|
REQUIRE(logical_processor_count() == count);
|
||||||
|
REQUIRE(logical_processor_count() == count);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Enable process to set thread affinity") {
|
||||||
|
EnableAffinityConfiguration();
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Yield Current Thread", "MaybeYield") {
|
||||||
|
// Run to see if there are any errors
|
||||||
|
MaybeYield();
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Sync with Memory Barrier", "SyncMemory") {
|
||||||
|
// Run to see if there are any errors
|
||||||
|
SyncMemory();
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Sleep Current Thread", "Sleep") {
|
||||||
|
auto wait_time = 50ms;
|
||||||
|
auto start = std::chrono::steady_clock::now();
|
||||||
|
Sleep(wait_time);
|
||||||
|
auto duration = std::chrono::steady_clock::now() - start;
|
||||||
|
REQUIRE(duration >= wait_time);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Sleep Current Thread in Alertable State", "Sleep") {
|
||||||
|
auto wait_time = 50ms;
|
||||||
|
auto start = std::chrono::steady_clock::now();
|
||||||
|
auto result = threading::AlertableSleep(wait_time);
|
||||||
|
auto duration = std::chrono::steady_clock::now() - start;
|
||||||
|
REQUIRE(duration >= wait_time);
|
||||||
|
REQUIRE(result == threading::SleepResult::kSuccess);
|
||||||
|
|
||||||
|
// TODO(bwrsandman): Test a Thread to return kAlerted.
|
||||||
|
// Need callback to call extended I/O function (ReadFileEx or WriteFileEx)
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("TlsHandle") {
|
||||||
|
// Test Allocate
|
||||||
|
auto handle = threading::AllocateTlsHandle();
|
||||||
|
|
||||||
|
// Test Free
|
||||||
|
REQUIRE(threading::FreeTlsHandle(handle));
|
||||||
|
REQUIRE(!threading::FreeTlsHandle(handle));
|
||||||
|
REQUIRE(!threading::FreeTlsHandle(threading::kInvalidTlsHandle));
|
||||||
|
|
||||||
|
// Test setting values
|
||||||
|
handle = threading::AllocateTlsHandle();
|
||||||
|
REQUIRE(threading::GetTlsValue(handle) == 0);
|
||||||
|
uint32_t value = 0xDEADBEEF;
|
||||||
|
threading::SetTlsValue(handle, reinterpret_cast<uintptr_t>(&value));
|
||||||
|
auto p_received_value = threading::GetTlsValue(handle);
|
||||||
|
REQUIRE(threading::GetTlsValue(handle) != 0);
|
||||||
|
auto received_value = *reinterpret_cast<uint32_t*>(p_received_value);
|
||||||
|
REQUIRE(received_value == value);
|
||||||
|
|
||||||
|
uintptr_t non_thread_local_value = 0;
|
||||||
|
auto thread = Thread::Create({}, [&non_thread_local_value, &handle] {
|
||||||
|
non_thread_local_value = threading::GetTlsValue(handle);
|
||||||
|
});
|
||||||
|
|
||||||
|
auto result = Wait(thread.get(), false, 50ms);
|
||||||
|
REQUIRE(result == WaitResult::kSuccess);
|
||||||
|
REQUIRE(non_thread_local_value == 0);
|
||||||
|
|
||||||
|
// Cleanup
|
||||||
|
REQUIRE(threading::FreeTlsHandle(handle));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("HighResolutionTimer") {
|
||||||
|
// The wait time is 500ms with an interval of 50ms
|
||||||
|
// Smaller values are not as precise and fail the test
|
||||||
|
const auto wait_time = 500ms;
|
||||||
|
|
||||||
|
// Time the actual sleep duration
|
||||||
|
{
|
||||||
|
const auto interval = 50ms;
|
||||||
|
std::atomic<uint64_t> counter;
|
||||||
|
auto start = std::chrono::steady_clock::now();
|
||||||
|
auto cb = [&counter] { ++counter; };
|
||||||
|
auto pTimer = HighResolutionTimer::CreateRepeating(interval, cb);
|
||||||
|
Sleep(wait_time);
|
||||||
|
pTimer.reset();
|
||||||
|
auto duration = std::chrono::steady_clock::now() - start;
|
||||||
|
|
||||||
|
// Should have run as many times as wait_time / timer_interval plus or
|
||||||
|
// minus 1 due to imprecision of Sleep
|
||||||
|
REQUIRE(duration.count() >= wait_time.count());
|
||||||
|
auto ratio = static_cast<uint64_t>(duration / interval);
|
||||||
|
REQUIRE(counter >= ratio - 1);
|
||||||
|
REQUIRE(counter <= ratio + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test concurrent timers
|
||||||
|
{
|
||||||
|
const auto interval1 = 100ms;
|
||||||
|
const auto interval2 = 200ms;
|
||||||
|
std::atomic<uint64_t> counter1(0);
|
||||||
|
std::atomic<uint64_t> counter2(0);
|
||||||
|
auto start = std::chrono::steady_clock::now();
|
||||||
|
auto cb1 = [&counter1] { ++counter1; };
|
||||||
|
auto cb2 = [&counter2] { ++counter2; };
|
||||||
|
auto pTimer1 = HighResolutionTimer::CreateRepeating(interval1, cb1);
|
||||||
|
auto pTimer2 = HighResolutionTimer::CreateRepeating(interval2, cb2);
|
||||||
|
Sleep(wait_time);
|
||||||
|
pTimer1.reset();
|
||||||
|
pTimer2.reset();
|
||||||
|
auto duration = std::chrono::steady_clock::now() - start;
|
||||||
|
|
||||||
|
// Should have run as many times as wait_time / timer_interval plus or
|
||||||
|
// minus 1 due to imprecision of Sleep
|
||||||
|
REQUIRE(duration.count() >= wait_time.count());
|
||||||
|
auto ratio1 = static_cast<uint64_t>(duration / interval1);
|
||||||
|
auto ratio2 = static_cast<uint64_t>(duration / interval2);
|
||||||
|
REQUIRE(counter1 >= ratio1 - 1);
|
||||||
|
REQUIRE(counter1 <= ratio1 + 1);
|
||||||
|
REQUIRE(counter2 >= ratio2 - 1);
|
||||||
|
REQUIRE(counter2 <= ratio2 + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(bwrsandman): Check on which thread callbacks are executed when
|
||||||
|
// spawned from differing threads
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Wait on Multiple Handles", "Wait") {
|
||||||
|
auto mutant = Mutant::Create(true);
|
||||||
|
auto semaphore = Semaphore::Create(10, 10);
|
||||||
|
auto event_ = Event::CreateManualResetEvent(false);
|
||||||
|
auto thread = Thread::Create({}, [&mutant, &semaphore, &event_] {
|
||||||
|
event_->Set();
|
||||||
|
Wait(mutant.get(), false, 25ms);
|
||||||
|
semaphore->Release(1, nullptr);
|
||||||
|
Wait(mutant.get(), false, 25ms);
|
||||||
|
mutant->Release();
|
||||||
|
});
|
||||||
|
|
||||||
|
std::vector<WaitHandle*> handles = {
|
||||||
|
mutant.get(),
|
||||||
|
semaphore.get(),
|
||||||
|
event_.get(),
|
||||||
|
thread.get(),
|
||||||
|
};
|
||||||
|
|
||||||
|
auto any_result = WaitAny(handles, false, 100ms);
|
||||||
|
REQUIRE(any_result.first == WaitResult::kSuccess);
|
||||||
|
REQUIRE(any_result.second == 0);
|
||||||
|
|
||||||
|
auto all_result = WaitAll(handles, false, 100ms);
|
||||||
|
REQUIRE(all_result == WaitResult::kSuccess);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Signal and Wait") {
|
||||||
|
WaitResult result;
|
||||||
|
auto mutant = Mutant::Create(true);
|
||||||
|
auto event_ = Event::CreateAutoResetEvent(false);
|
||||||
|
auto thread = Thread::Create({}, [&mutant, &event_] {
|
||||||
|
Wait(mutant.get(), false);
|
||||||
|
event_->Set();
|
||||||
|
});
|
||||||
|
result = Wait(event_.get(), false, 50ms);
|
||||||
|
REQUIRE(result == WaitResult::kTimeout);
|
||||||
|
result = SignalAndWait(mutant.get(), event_.get(), false, 50ms);
|
||||||
|
REQUIRE(result == WaitResult::kSuccess);
|
||||||
|
result = Wait(thread.get(), false, 50ms);
|
||||||
|
REQUIRE(result == WaitResult::kSuccess);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Wait on Event", "Event") {
|
||||||
|
auto evt = Event::CreateAutoResetEvent(false);
|
||||||
|
WaitResult result;
|
||||||
|
|
||||||
|
// Call wait on unset Event
|
||||||
|
result = Wait(evt.get(), false, 50ms);
|
||||||
|
REQUIRE(result == WaitResult::kTimeout);
|
||||||
|
|
||||||
|
// Call wait on set Event
|
||||||
|
evt->Set();
|
||||||
|
result = Wait(evt.get(), false, 50ms);
|
||||||
|
REQUIRE(result == WaitResult::kSuccess);
|
||||||
|
|
||||||
|
// Call wait on now consumed Event
|
||||||
|
result = Wait(evt.get(), false, 50ms);
|
||||||
|
REQUIRE(result == WaitResult::kTimeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Reset Event", "Event") {
|
||||||
|
auto evt = Event::CreateAutoResetEvent(false);
|
||||||
|
WaitResult result;
|
||||||
|
|
||||||
|
// Call wait on reset Event
|
||||||
|
evt->Set();
|
||||||
|
evt->Reset();
|
||||||
|
result = Wait(evt.get(), false, 50ms);
|
||||||
|
REQUIRE(result == WaitResult::kTimeout);
|
||||||
|
|
||||||
|
// Test resetting the unset event
|
||||||
|
evt->Reset();
|
||||||
|
result = Wait(evt.get(), false, 50ms);
|
||||||
|
REQUIRE(result == WaitResult::kTimeout);
|
||||||
|
|
||||||
|
// Test setting the reset event
|
||||||
|
evt->Set();
|
||||||
|
result = Wait(evt.get(), false, 50ms);
|
||||||
|
REQUIRE(result == WaitResult::kSuccess);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Wait on Multiple Events", "Event") {
|
||||||
|
auto events = std::array<std::unique_ptr<Event>, 4>{
|
||||||
|
Event::CreateAutoResetEvent(false),
|
||||||
|
Event::CreateAutoResetEvent(false),
|
||||||
|
Event::CreateAutoResetEvent(false),
|
||||||
|
Event::CreateManualResetEvent(false),
|
||||||
|
};
|
||||||
|
|
||||||
|
std::array<char, 8> order = {0};
|
||||||
|
std::atomic_uint index(0);
|
||||||
|
auto sign_in = [&order, &index](uint32_t id) {
|
||||||
|
auto i = index.fetch_add(1, std::memory_order::memory_order_relaxed);
|
||||||
|
order[i] = static_cast<char>('0' + id);
|
||||||
|
};
|
||||||
|
|
||||||
|
auto threads = std::array<std::thread, 4>{
|
||||||
|
std::thread([&events, &sign_in] {
|
||||||
|
auto res = WaitAll({events[1].get(), events[3].get()}, false, 100ms);
|
||||||
|
if (res == WaitResult::kSuccess) {
|
||||||
|
sign_in(1);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
std::thread([&events, &sign_in] {
|
||||||
|
auto res = WaitAny({events[0].get(), events[2].get()}, false, 100ms);
|
||||||
|
if (res.first == WaitResult::kSuccess) {
|
||||||
|
sign_in(2);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
std::thread([&events, &sign_in] {
|
||||||
|
auto res = WaitAll({events[0].get(), events[2].get(), events[3].get()},
|
||||||
|
false, 100ms);
|
||||||
|
if (res == WaitResult::kSuccess) {
|
||||||
|
sign_in(3);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
std::thread([&events, &sign_in] {
|
||||||
|
auto res = WaitAny({events[1].get(), events[3].get()}, false, 100ms);
|
||||||
|
if (res.first == WaitResult::kSuccess) {
|
||||||
|
sign_in(4);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
Sleep(10ms);
|
||||||
|
events[3]->Set(); // Signals thread id=4 and stays on for 1 and 3
|
||||||
|
Sleep(10ms);
|
||||||
|
events[1]->Set(); // Signals thread id=1
|
||||||
|
Sleep(10ms);
|
||||||
|
events[0]->Set(); // Signals thread id=2
|
||||||
|
Sleep(10ms);
|
||||||
|
events[2]->Set(); // Partial signals thread id=3
|
||||||
|
events[0]->Set(); // Signals thread id=3
|
||||||
|
|
||||||
|
for (auto& t : threads) {
|
||||||
|
t.join();
|
||||||
|
}
|
||||||
|
|
||||||
|
INFO(order.data());
|
||||||
|
REQUIRE(order[0] == '4');
|
||||||
|
// TODO(bwrsandman): Order is not always maintained on linux
|
||||||
|
// REQUIRE(order[1] == '1');
|
||||||
|
// REQUIRE(order[2] == '2');
|
||||||
|
// REQUIRE(order[3] == '3');
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Wait on Semaphore", "Semaphore") {
|
||||||
|
WaitResult result;
|
||||||
|
std::unique_ptr<Semaphore> sem;
|
||||||
|
int previous_count = 0;
|
||||||
|
|
||||||
|
// Wait on semaphore with no room
|
||||||
|
sem = Semaphore::Create(0, 5);
|
||||||
|
result = Wait(sem.get(), false, 10ms);
|
||||||
|
REQUIRE(result == WaitResult::kTimeout);
|
||||||
|
|
||||||
|
// Add room in semaphore
|
||||||
|
REQUIRE(sem->Release(2, &previous_count));
|
||||||
|
REQUIRE(previous_count == 0);
|
||||||
|
REQUIRE(sem->Release(1, &previous_count));
|
||||||
|
REQUIRE(previous_count == 2);
|
||||||
|
result = Wait(sem.get(), false, 10ms);
|
||||||
|
REQUIRE(result == WaitResult::kSuccess);
|
||||||
|
REQUIRE(sem->Release(1, &previous_count));
|
||||||
|
REQUIRE(previous_count == 2);
|
||||||
|
|
||||||
|
// Set semaphore over maximum_count
|
||||||
|
sem = Semaphore::Create(5, 5);
|
||||||
|
previous_count = -1;
|
||||||
|
REQUIRE_FALSE(sem->Release(1, &previous_count));
|
||||||
|
REQUIRE(previous_count == -1);
|
||||||
|
REQUIRE_FALSE(sem->Release(10, &previous_count));
|
||||||
|
REQUIRE(previous_count == -1);
|
||||||
|
sem = Semaphore::Create(0, 5);
|
||||||
|
REQUIRE_FALSE(sem->Release(10, &previous_count));
|
||||||
|
REQUIRE(previous_count == -1);
|
||||||
|
REQUIRE_FALSE(sem->Release(10, &previous_count));
|
||||||
|
REQUIRE(previous_count == -1);
|
||||||
|
|
||||||
|
// Test invalid Release parameters
|
||||||
|
REQUIRE_FALSE(sem->Release(0, &previous_count));
|
||||||
|
REQUIRE(previous_count == -1);
|
||||||
|
REQUIRE_FALSE(sem->Release(-1, &previous_count));
|
||||||
|
REQUIRE(previous_count == -1);
|
||||||
|
|
||||||
|
// Wait on fully available semaphore
|
||||||
|
sem = Semaphore::Create(5, 5);
|
||||||
|
result = Wait(sem.get(), false, 10ms);
|
||||||
|
REQUIRE(result == WaitResult::kSuccess);
|
||||||
|
result = Wait(sem.get(), false, 10ms);
|
||||||
|
REQUIRE(result == WaitResult::kSuccess);
|
||||||
|
result = Wait(sem.get(), false, 10ms);
|
||||||
|
REQUIRE(result == WaitResult::kSuccess);
|
||||||
|
result = Wait(sem.get(), false, 10ms);
|
||||||
|
REQUIRE(result == WaitResult::kSuccess);
|
||||||
|
result = Wait(sem.get(), false, 10ms);
|
||||||
|
REQUIRE(result == WaitResult::kSuccess);
|
||||||
|
result = Wait(sem.get(), false, 10ms);
|
||||||
|
REQUIRE(result == WaitResult::kTimeout);
|
||||||
|
|
||||||
|
// Semaphore between threads
|
||||||
|
sem = Semaphore::Create(5, 5);
|
||||||
|
Sleep(10ms);
|
||||||
|
// Occupy the semaphore with 5 threads
|
||||||
|
auto func = [&sem] {
|
||||||
|
auto res = Wait(sem.get(), false, 100ms);
|
||||||
|
Sleep(500ms);
|
||||||
|
if (res == WaitResult::kSuccess) {
|
||||||
|
sem->Release(1, nullptr);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
auto threads = std::array<std::thread, 5>{
|
||||||
|
std::thread(func), std::thread(func), std::thread(func),
|
||||||
|
std::thread(func), std::thread(func),
|
||||||
|
};
|
||||||
|
// Give threads time to acquire semaphore
|
||||||
|
Sleep(10ms);
|
||||||
|
// Attempt to acquire full semaphore with current (6th) thread
|
||||||
|
result = Wait(sem.get(), false, 20ms);
|
||||||
|
REQUIRE(result == WaitResult::kTimeout);
|
||||||
|
// Give threads time to release semaphore
|
||||||
|
for (auto& t : threads) {
|
||||||
|
t.join();
|
||||||
|
}
|
||||||
|
result = Wait(sem.get(), false, 10ms);
|
||||||
|
REQUIRE(result == WaitResult::kSuccess);
|
||||||
|
sem->Release(1, &previous_count);
|
||||||
|
REQUIRE(previous_count == 4);
|
||||||
|
|
||||||
|
// Test invalid construction parameters
|
||||||
|
// These are invalid according to documentation
|
||||||
|
// TODO(bwrsandman): Many of these invalid invocations succeed
|
||||||
|
sem = Semaphore::Create(-1, 5);
|
||||||
|
// REQUIRE(sem.get() == nullptr);
|
||||||
|
sem = Semaphore::Create(10, 5);
|
||||||
|
// REQUIRE(sem.get() == nullptr);
|
||||||
|
sem = Semaphore::Create(0, 0);
|
||||||
|
// REQUIRE(sem.get() == nullptr);
|
||||||
|
sem = Semaphore::Create(0, -1);
|
||||||
|
// REQUIRE(sem.get() == nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Wait on Multiple Semaphores", "Semaphore") {
|
||||||
|
WaitResult all_result;
|
||||||
|
std::pair<WaitResult, size_t> any_result;
|
||||||
|
int previous_count;
|
||||||
|
std::unique_ptr<Semaphore> sem0, sem1;
|
||||||
|
|
||||||
|
// Test Wait all which should fail
|
||||||
|
sem0 = Semaphore::Create(0, 5);
|
||||||
|
sem1 = Semaphore::Create(5, 5);
|
||||||
|
all_result = WaitAll({sem0.get(), sem1.get()}, false, 10ms);
|
||||||
|
REQUIRE(all_result == WaitResult::kTimeout);
|
||||||
|
previous_count = -1;
|
||||||
|
REQUIRE(sem0->Release(1, &previous_count));
|
||||||
|
REQUIRE(previous_count == 0);
|
||||||
|
previous_count = -1;
|
||||||
|
REQUIRE_FALSE(sem1->Release(1, &previous_count));
|
||||||
|
REQUIRE(previous_count == -1);
|
||||||
|
|
||||||
|
// Test Wait all again which should succeed
|
||||||
|
sem0 = Semaphore::Create(1, 5);
|
||||||
|
sem1 = Semaphore::Create(5, 5);
|
||||||
|
all_result = WaitAll({sem0.get(), sem1.get()}, false, 10ms);
|
||||||
|
REQUIRE(all_result == WaitResult::kSuccess);
|
||||||
|
previous_count = -1;
|
||||||
|
REQUIRE(sem0->Release(1, &previous_count));
|
||||||
|
REQUIRE(previous_count == 0);
|
||||||
|
previous_count = -1;
|
||||||
|
REQUIRE(sem1->Release(1, &previous_count));
|
||||||
|
REQUIRE(previous_count == 4);
|
||||||
|
|
||||||
|
// Test Wait Any which should fail
|
||||||
|
sem0 = Semaphore::Create(0, 5);
|
||||||
|
sem1 = Semaphore::Create(0, 5);
|
||||||
|
any_result = WaitAny({sem0.get(), sem1.get()}, false, 10ms);
|
||||||
|
REQUIRE(any_result.first == WaitResult::kTimeout);
|
||||||
|
REQUIRE(any_result.second == 0);
|
||||||
|
previous_count = -1;
|
||||||
|
REQUIRE(sem0->Release(1, &previous_count));
|
||||||
|
REQUIRE(previous_count == 0);
|
||||||
|
previous_count = -1;
|
||||||
|
REQUIRE(sem1->Release(1, &previous_count));
|
||||||
|
REQUIRE(previous_count == 0);
|
||||||
|
|
||||||
|
// Test Wait Any which should succeed
|
||||||
|
sem0 = Semaphore::Create(0, 5);
|
||||||
|
sem1 = Semaphore::Create(5, 5);
|
||||||
|
any_result = WaitAny({sem0.get(), sem1.get()}, false, 10ms);
|
||||||
|
REQUIRE(any_result.first == WaitResult::kSuccess);
|
||||||
|
REQUIRE(any_result.second == 1);
|
||||||
|
previous_count = -1;
|
||||||
|
REQUIRE(sem0->Release(1, &previous_count));
|
||||||
|
REQUIRE(previous_count == 0);
|
||||||
|
previous_count = -1;
|
||||||
|
REQUIRE(sem1->Release(1, &previous_count));
|
||||||
|
REQUIRE(previous_count == 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Wait on Mutant", "Mutant") {
|
||||||
|
WaitResult result;
|
||||||
|
std::unique_ptr<Mutant> mut;
|
||||||
|
|
||||||
|
// Release on initially owned mutant
|
||||||
|
mut = Mutant::Create(true);
|
||||||
|
REQUIRE(mut->Release());
|
||||||
|
REQUIRE_FALSE(mut->Release());
|
||||||
|
|
||||||
|
// Release on initially not-owned mutant
|
||||||
|
mut = Mutant::Create(false);
|
||||||
|
REQUIRE_FALSE(mut->Release());
|
||||||
|
|
||||||
|
// Wait on initially owned mutant
|
||||||
|
mut = Mutant::Create(true);
|
||||||
|
result = Wait(mut.get(), false, 1ms);
|
||||||
|
REQUIRE(result == WaitResult::kSuccess);
|
||||||
|
REQUIRE(mut->Release());
|
||||||
|
REQUIRE(mut->Release());
|
||||||
|
REQUIRE_FALSE(mut->Release());
|
||||||
|
|
||||||
|
// Wait on initially not owned mutant
|
||||||
|
mut = Mutant::Create(false);
|
||||||
|
result = Wait(mut.get(), false, 1ms);
|
||||||
|
REQUIRE(result == WaitResult::kSuccess);
|
||||||
|
REQUIRE(mut->Release());
|
||||||
|
REQUIRE_FALSE(mut->Release());
|
||||||
|
|
||||||
|
// Multiple waits (or locks)
|
||||||
|
mut = Mutant::Create(false);
|
||||||
|
for (int i = 0; i < 10; ++i) {
|
||||||
|
result = Wait(mut.get(), false, 1ms);
|
||||||
|
REQUIRE(result == WaitResult::kSuccess);
|
||||||
|
}
|
||||||
|
for (int i = 0; i < 10; ++i) {
|
||||||
|
REQUIRE(mut->Release());
|
||||||
|
}
|
||||||
|
REQUIRE_FALSE(mut->Release());
|
||||||
|
|
||||||
|
// Test mutants on other threads
|
||||||
|
auto thread1 = std::thread([&mut] {
|
||||||
|
Sleep(5ms);
|
||||||
|
mut = Mutant::Create(true);
|
||||||
|
Sleep(100ms);
|
||||||
|
mut->Release();
|
||||||
|
});
|
||||||
|
Sleep(10ms);
|
||||||
|
REQUIRE_FALSE(mut->Release());
|
||||||
|
Sleep(10ms);
|
||||||
|
result = Wait(mut.get(), false, 50ms);
|
||||||
|
REQUIRE(result == WaitResult::kTimeout);
|
||||||
|
thread1.join();
|
||||||
|
result = Wait(mut.get(), false, 1ms);
|
||||||
|
REQUIRE(result == WaitResult::kSuccess);
|
||||||
|
REQUIRE(mut->Release());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Wait on Multiple Mutants", "Mutant") {
|
||||||
|
WaitResult all_result;
|
||||||
|
std::pair<WaitResult, size_t> any_result;
|
||||||
|
std::unique_ptr<Mutant> mut0, mut1;
|
||||||
|
|
||||||
|
// Test which should fail for WaitAll and WaitAny
|
||||||
|
auto thread0 = std::thread([&mut0, &mut1] {
|
||||||
|
mut0 = Mutant::Create(true);
|
||||||
|
mut1 = Mutant::Create(true);
|
||||||
|
Sleep(50ms);
|
||||||
|
mut0->Release();
|
||||||
|
mut1->Release();
|
||||||
|
});
|
||||||
|
Sleep(10ms);
|
||||||
|
all_result = WaitAll({mut0.get(), mut1.get()}, false, 10ms);
|
||||||
|
REQUIRE(all_result == WaitResult::kTimeout);
|
||||||
|
REQUIRE_FALSE(mut0->Release());
|
||||||
|
REQUIRE_FALSE(mut1->Release());
|
||||||
|
any_result = WaitAny({mut0.get(), mut1.get()}, false, 10ms);
|
||||||
|
REQUIRE(any_result.first == WaitResult::kTimeout);
|
||||||
|
REQUIRE(any_result.second == 0);
|
||||||
|
REQUIRE_FALSE(mut0->Release());
|
||||||
|
REQUIRE_FALSE(mut1->Release());
|
||||||
|
thread0.join();
|
||||||
|
|
||||||
|
// Test which should fail for WaitAll but not WaitAny
|
||||||
|
auto thread1 = std::thread([&mut0, &mut1] {
|
||||||
|
mut0 = Mutant::Create(true);
|
||||||
|
mut1 = Mutant::Create(false);
|
||||||
|
Sleep(50ms);
|
||||||
|
mut0->Release();
|
||||||
|
});
|
||||||
|
Sleep(10ms);
|
||||||
|
all_result = WaitAll({mut0.get(), mut1.get()}, false, 10ms);
|
||||||
|
REQUIRE(all_result == WaitResult::kTimeout);
|
||||||
|
REQUIRE_FALSE(mut0->Release());
|
||||||
|
REQUIRE_FALSE(mut1->Release());
|
||||||
|
any_result = WaitAny({mut0.get(), mut1.get()}, false, 10ms);
|
||||||
|
REQUIRE(any_result.first == WaitResult::kSuccess);
|
||||||
|
REQUIRE(any_result.second == 1);
|
||||||
|
REQUIRE_FALSE(mut0->Release());
|
||||||
|
REQUIRE(mut1->Release());
|
||||||
|
thread1.join();
|
||||||
|
|
||||||
|
// Test which should pass for WaitAll and WaitAny
|
||||||
|
auto thread2 = std::thread([&mut0, &mut1] {
|
||||||
|
mut0 = Mutant::Create(false);
|
||||||
|
mut1 = Mutant::Create(false);
|
||||||
|
Sleep(50ms);
|
||||||
|
});
|
||||||
|
Sleep(10ms);
|
||||||
|
all_result = WaitAll({mut0.get(), mut1.get()}, false, 10ms);
|
||||||
|
REQUIRE(all_result == WaitResult::kSuccess);
|
||||||
|
REQUIRE(mut0->Release());
|
||||||
|
REQUIRE(mut1->Release());
|
||||||
|
any_result = WaitAny({mut0.get(), mut1.get()}, false, 10ms);
|
||||||
|
REQUIRE(any_result.first == WaitResult::kSuccess);
|
||||||
|
REQUIRE(any_result.second == 0);
|
||||||
|
REQUIRE(mut0->Release());
|
||||||
|
REQUIRE_FALSE(mut1->Release());
|
||||||
|
thread2.join();
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Wait on Timer", "Timer") {
|
||||||
|
WaitResult result;
|
||||||
|
std::unique_ptr<Timer> timer;
|
||||||
|
|
||||||
|
// Test Manual Reset
|
||||||
|
timer = Timer::CreateManualResetTimer();
|
||||||
|
result = Wait(timer.get(), false, 1ms);
|
||||||
|
REQUIRE(result == WaitResult::kTimeout);
|
||||||
|
REQUIRE(timer->SetOnce(1ms)); // Signals it
|
||||||
|
result = Wait(timer.get(), false, 2ms);
|
||||||
|
REQUIRE(result == WaitResult::kSuccess);
|
||||||
|
result = Wait(timer.get(), false, 1ms);
|
||||||
|
REQUIRE(result == WaitResult::kSuccess); // Did not reset
|
||||||
|
|
||||||
|
// Test Synchronization
|
||||||
|
timer = Timer::CreateSynchronizationTimer();
|
||||||
|
result = Wait(timer.get(), false, 1ms);
|
||||||
|
REQUIRE(result == WaitResult::kTimeout);
|
||||||
|
REQUIRE(timer->SetOnce(1ms)); // Signals it
|
||||||
|
result = Wait(timer.get(), false, 2ms);
|
||||||
|
REQUIRE(result == WaitResult::kSuccess);
|
||||||
|
result = Wait(timer.get(), false, 1ms);
|
||||||
|
REQUIRE(result == WaitResult::kTimeout); // Did reset
|
||||||
|
|
||||||
|
// TODO(bwrsandman): This test unexpectedly fails under windows
|
||||||
|
// Test long due time
|
||||||
|
// timer = Timer::CreateSynchronizationTimer();
|
||||||
|
// REQUIRE(timer->SetOnce(10s));
|
||||||
|
// result = Wait(timer.get(), false, 10ms); // Still signals under windows
|
||||||
|
// REQUIRE(result == WaitResult::kTimeout);
|
||||||
|
|
||||||
|
// Test Repeating
|
||||||
|
REQUIRE(timer->SetRepeating(1ms, 10ms));
|
||||||
|
for (int i = 0; i < 10; ++i) {
|
||||||
|
result = Wait(timer.get(), false, 20ms);
|
||||||
|
INFO(i);
|
||||||
|
REQUIRE(result == WaitResult::kSuccess);
|
||||||
|
}
|
||||||
|
MaybeYield();
|
||||||
|
Sleep(10ms); // Skip a few events
|
||||||
|
for (int i = 0; i < 10; ++i) {
|
||||||
|
result = Wait(timer.get(), false, 20ms);
|
||||||
|
REQUIRE(result == WaitResult::kSuccess);
|
||||||
|
}
|
||||||
|
// Cancel it
|
||||||
|
timer->Cancel();
|
||||||
|
result = Wait(timer.get(), false, 20ms);
|
||||||
|
REQUIRE(result == WaitResult::kTimeout);
|
||||||
|
MaybeYield();
|
||||||
|
Sleep(10ms); // Skip a few events
|
||||||
|
result = Wait(timer.get(), false, 20ms);
|
||||||
|
REQUIRE(result == WaitResult::kTimeout);
|
||||||
|
// Cancel with SetOnce
|
||||||
|
REQUIRE(timer->SetRepeating(1ms, 10ms));
|
||||||
|
for (int i = 0; i < 10; ++i) {
|
||||||
|
result = Wait(timer.get(), false, 20ms);
|
||||||
|
REQUIRE(result == WaitResult::kSuccess);
|
||||||
|
}
|
||||||
|
REQUIRE(timer->SetOnce(1ms));
|
||||||
|
result = Wait(timer.get(), false, 20ms);
|
||||||
|
REQUIRE(result == WaitResult::kSuccess); // Signal from Set Once
|
||||||
|
result = Wait(timer.get(), false, 20ms);
|
||||||
|
REQUIRE(result == WaitResult::kTimeout); // No more signals from repeating
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Wait on Multiple Timers", "Timer") {
|
||||||
|
WaitResult all_result;
|
||||||
|
std::pair<WaitResult, size_t> any_result;
|
||||||
|
|
||||||
|
auto timer0 = Timer::CreateSynchronizationTimer();
|
||||||
|
auto timer1 = Timer::CreateManualResetTimer();
|
||||||
|
|
||||||
|
// None signaled
|
||||||
|
all_result = WaitAll({timer0.get(), timer1.get()}, false, 1ms);
|
||||||
|
REQUIRE(all_result == WaitResult::kTimeout);
|
||||||
|
any_result = WaitAny({timer0.get(), timer1.get()}, false, 1ms);
|
||||||
|
REQUIRE(any_result.first == WaitResult::kTimeout);
|
||||||
|
REQUIRE(any_result.second == 0);
|
||||||
|
|
||||||
|
// Some signaled
|
||||||
|
REQUIRE(timer1->SetOnce(1ms));
|
||||||
|
all_result = WaitAll({timer0.get(), timer1.get()}, false, 100ms);
|
||||||
|
REQUIRE(all_result == WaitResult::kTimeout);
|
||||||
|
any_result = WaitAny({timer0.get(), timer1.get()}, false, 100ms);
|
||||||
|
REQUIRE(any_result.first == WaitResult::kSuccess);
|
||||||
|
REQUIRE(any_result.second == 1);
|
||||||
|
|
||||||
|
// All signaled
|
||||||
|
REQUIRE(timer0->SetOnce(1ms));
|
||||||
|
all_result = WaitAll({timer0.get(), timer1.get()}, false, 100ms);
|
||||||
|
REQUIRE(all_result == WaitResult::kSuccess);
|
||||||
|
REQUIRE(timer0->SetOnce(1ms));
|
||||||
|
Sleep(1ms);
|
||||||
|
any_result = WaitAny({timer0.get(), timer1.get()}, false, 100ms);
|
||||||
|
REQUIRE(any_result.first == WaitResult::kSuccess);
|
||||||
|
REQUIRE(any_result.second == 0);
|
||||||
|
|
||||||
|
// Check that timer0 reset
|
||||||
|
any_result = WaitAny({timer0.get(), timer1.get()}, false, 100ms);
|
||||||
|
REQUIRE(any_result.first == WaitResult::kSuccess);
|
||||||
|
REQUIRE(any_result.second == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Create and Trigger Timer Callbacks", "Timer") {
|
||||||
|
// TODO(bwrsandman): Check which thread performs callback and timing of
|
||||||
|
// callback
|
||||||
|
REQUIRE(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Set and Test Current Thread ID", "Thread") {
|
||||||
|
// System ID
|
||||||
|
auto system_id = current_thread_system_id();
|
||||||
|
REQUIRE(system_id > 0);
|
||||||
|
|
||||||
|
// Thread ID
|
||||||
|
auto thread_id = current_thread_id();
|
||||||
|
REQUIRE(thread_id == system_id);
|
||||||
|
|
||||||
|
// Set a new thread id
|
||||||
|
const uint32_t new_thread_id = 0xDEADBEEF;
|
||||||
|
set_current_thread_id(new_thread_id);
|
||||||
|
REQUIRE(current_thread_id() == new_thread_id);
|
||||||
|
|
||||||
|
// Set back original thread id of system
|
||||||
|
set_current_thread_id(std::numeric_limits<uint32_t>::max());
|
||||||
|
REQUIRE(current_thread_id() == system_id);
|
||||||
|
|
||||||
|
// TODO(bwrsandman): Test on Thread object
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Set and Test Current Thread Name", "Thread") {
|
||||||
|
auto current_thread = Thread::GetCurrentThread();
|
||||||
|
REQUIRE(current_thread);
|
||||||
|
auto old_thread_name = current_thread->name();
|
||||||
|
|
||||||
|
std::string new_thread_name = "Threading Test";
|
||||||
|
REQUIRE_NOTHROW(set_name(new_thread_name));
|
||||||
|
|
||||||
|
// Restore the old catch.hpp thread name
|
||||||
|
REQUIRE_NOTHROW(set_name(old_thread_name));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Create and Run Thread", "Thread") {
|
||||||
|
std::unique_ptr<Thread> thread;
|
||||||
|
WaitResult result;
|
||||||
|
Thread::CreationParameters params = {};
|
||||||
|
auto func = [] { Sleep(20ms); };
|
||||||
|
|
||||||
|
// Create most basic case of thread
|
||||||
|
thread = Thread::Create(params, func);
|
||||||
|
REQUIRE(thread->native_handle() != nullptr);
|
||||||
|
REQUIRE_NOTHROW(thread->affinity_mask());
|
||||||
|
REQUIRE(thread->name().empty());
|
||||||
|
result = Wait(thread.get(), false, 50ms);
|
||||||
|
REQUIRE(result == WaitResult::kSuccess);
|
||||||
|
|
||||||
|
// Add thread name
|
||||||
|
std::string new_name = "Test thread name";
|
||||||
|
thread = Thread::Create(params, func);
|
||||||
|
auto name = thread->name();
|
||||||
|
INFO(name.c_str());
|
||||||
|
REQUIRE(name.empty());
|
||||||
|
thread->set_name(new_name);
|
||||||
|
REQUIRE(thread->name() == new_name);
|
||||||
|
result = Wait(thread.get(), false, 50ms);
|
||||||
|
REQUIRE(result == WaitResult::kSuccess);
|
||||||
|
|
||||||
|
// Use Terminate to end an infinitely looping thread
|
||||||
|
thread = Thread::Create(params, [] {
|
||||||
|
while (true) {
|
||||||
|
Sleep(1ms);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
result = Wait(thread.get(), false, 50ms);
|
||||||
|
REQUIRE(result == WaitResult::kTimeout);
|
||||||
|
thread->Terminate(-1);
|
||||||
|
result = Wait(thread.get(), false, 50ms);
|
||||||
|
REQUIRE(result == WaitResult::kSuccess);
|
||||||
|
|
||||||
|
// Call Exit from inside an infinitely looping thread
|
||||||
|
thread = Thread::Create(params, [] {
|
||||||
|
while (true) {
|
||||||
|
Thread::Exit(-1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
result = Wait(thread.get(), false, 50ms);
|
||||||
|
REQUIRE(result == WaitResult::kSuccess);
|
||||||
|
|
||||||
|
// Call timeout wait on self
|
||||||
|
result = Wait(Thread::GetCurrentThread(), false, 50ms);
|
||||||
|
REQUIRE(result == WaitResult::kTimeout);
|
||||||
|
|
||||||
|
params.stack_size = 16 * 1024;
|
||||||
|
thread = Thread::Create(params, [] {
|
||||||
|
while (true) {
|
||||||
|
Thread::Exit(-1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
REQUIRE(thread != nullptr);
|
||||||
|
result = Wait(thread.get(), false, 50ms);
|
||||||
|
REQUIRE(result == WaitResult::kSuccess);
|
||||||
|
|
||||||
|
// TODO(bwrsandman): Test with different priorities
|
||||||
|
// TODO(bwrsandman): Test setting and getting thread affinity
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Test Suspending Thread", "Thread") {
|
||||||
|
std::unique_ptr<Thread> thread;
|
||||||
|
WaitResult result;
|
||||||
|
Thread::CreationParameters params = {};
|
||||||
|
auto func = [] { Sleep(20ms); };
|
||||||
|
|
||||||
|
// Create initially suspended
|
||||||
|
params.create_suspended = true;
|
||||||
|
thread = threading::Thread::Create(params, func);
|
||||||
|
result = threading::Wait(thread.get(), false, 50ms);
|
||||||
|
REQUIRE(result == threading::WaitResult::kTimeout);
|
||||||
|
thread->Resume();
|
||||||
|
result = threading::Wait(thread.get(), false, 50ms);
|
||||||
|
REQUIRE(result == threading::WaitResult::kSuccess);
|
||||||
|
params.create_suspended = false;
|
||||||
|
|
||||||
|
// Create and then suspend
|
||||||
|
thread = threading::Thread::Create(params, func);
|
||||||
|
thread->Suspend();
|
||||||
|
result = threading::Wait(thread.get(), false, 50ms);
|
||||||
|
REQUIRE(result == threading::WaitResult::kTimeout);
|
||||||
|
thread->Resume();
|
||||||
|
result = threading::Wait(thread.get(), false, 50ms);
|
||||||
|
REQUIRE(result == threading::WaitResult::kSuccess);
|
||||||
|
|
||||||
|
// Test recursive suspend
|
||||||
|
thread = threading::Thread::Create(params, func);
|
||||||
|
thread->Suspend();
|
||||||
|
thread->Suspend();
|
||||||
|
result = threading::Wait(thread.get(), false, 50ms);
|
||||||
|
REQUIRE(result == threading::WaitResult::kTimeout);
|
||||||
|
thread->Resume();
|
||||||
|
result = threading::Wait(thread.get(), false, 50ms);
|
||||||
|
REQUIRE(result == threading::WaitResult::kTimeout);
|
||||||
|
thread->Resume();
|
||||||
|
result = threading::Wait(thread.get(), false, 50ms);
|
||||||
|
REQUIRE(result == threading::WaitResult::kSuccess);
|
||||||
|
|
||||||
|
// Test suspend count
|
||||||
|
uint32_t suspend_count = 0;
|
||||||
|
thread = threading::Thread::Create(params, func);
|
||||||
|
thread->Suspend(&suspend_count);
|
||||||
|
REQUIRE(suspend_count == 0);
|
||||||
|
thread->Suspend(&suspend_count);
|
||||||
|
REQUIRE(suspend_count == 1);
|
||||||
|
thread->Suspend(&suspend_count);
|
||||||
|
REQUIRE(suspend_count == 2);
|
||||||
|
thread->Resume(&suspend_count);
|
||||||
|
REQUIRE(suspend_count == 3);
|
||||||
|
thread->Resume(&suspend_count);
|
||||||
|
REQUIRE(suspend_count == 2);
|
||||||
|
thread->Resume(&suspend_count);
|
||||||
|
REQUIRE(suspend_count == 1);
|
||||||
|
thread->Suspend(&suspend_count);
|
||||||
|
REQUIRE(suspend_count == 0);
|
||||||
|
thread->Resume(&suspend_count);
|
||||||
|
REQUIRE(suspend_count == 1);
|
||||||
|
result = threading::Wait(thread.get(), false, 50ms);
|
||||||
|
REQUIRE(result == threading::WaitResult::kSuccess);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Test Thread QueueUserCallback", "Thread") {
|
||||||
|
std::unique_ptr<Thread> thread;
|
||||||
|
WaitResult result;
|
||||||
|
Thread::CreationParameters params = {};
|
||||||
|
std::atomic_int order;
|
||||||
|
int is_modified;
|
||||||
|
int has_finished;
|
||||||
|
auto callback = [&is_modified, &order] {
|
||||||
|
is_modified = std::atomic_fetch_add_explicit(
|
||||||
|
&order, 1, std::memory_order::memory_order_relaxed);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Without alertable
|
||||||
|
order = 0;
|
||||||
|
is_modified = -1;
|
||||||
|
has_finished = -1;
|
||||||
|
thread = Thread::Create(params, [&has_finished, &order] {
|
||||||
|
// Not using Alertable so callback is not registered
|
||||||
|
Sleep(90ms);
|
||||||
|
has_finished = std::atomic_fetch_add_explicit(
|
||||||
|
&order, 1, std::memory_order::memory_order_relaxed);
|
||||||
|
});
|
||||||
|
result = Wait(thread.get(), true, 50ms);
|
||||||
|
REQUIRE(result == WaitResult::kTimeout);
|
||||||
|
REQUIRE(is_modified == -1);
|
||||||
|
thread->QueueUserCallback(callback);
|
||||||
|
result = Wait(thread.get(), true, 100ms);
|
||||||
|
REQUIRE(result == WaitResult::kSuccess);
|
||||||
|
REQUIRE(is_modified == -1);
|
||||||
|
REQUIRE(has_finished == 0);
|
||||||
|
|
||||||
|
// With alertable
|
||||||
|
order = 0;
|
||||||
|
is_modified = -1;
|
||||||
|
has_finished = -1;
|
||||||
|
thread = Thread::Create(params, [&has_finished, &order] {
|
||||||
|
// Using Alertable so callback is registered
|
||||||
|
AlertableSleep(90ms);
|
||||||
|
has_finished = std::atomic_fetch_add_explicit(
|
||||||
|
&order, 1, std::memory_order::memory_order_relaxed);
|
||||||
|
});
|
||||||
|
result = Wait(thread.get(), true, 50ms);
|
||||||
|
REQUIRE(result == WaitResult::kTimeout);
|
||||||
|
REQUIRE(is_modified == -1);
|
||||||
|
thread->QueueUserCallback(callback);
|
||||||
|
result = Wait(thread.get(), true, 100ms);
|
||||||
|
REQUIRE(result == WaitResult::kSuccess);
|
||||||
|
REQUIRE(is_modified == 0);
|
||||||
|
REQUIRE(has_finished == 1);
|
||||||
|
|
||||||
|
// Test Exit command with QueueUserCallback
|
||||||
|
order = 0;
|
||||||
|
is_modified = -1;
|
||||||
|
has_finished = -1;
|
||||||
|
thread = Thread::Create(params, [&is_modified, &has_finished, &order] {
|
||||||
|
is_modified = std::atomic_fetch_add_explicit(
|
||||||
|
&order, 1, std::memory_order::memory_order_relaxed);
|
||||||
|
// Using Alertable so callback is registered
|
||||||
|
AlertableSleep(200ms);
|
||||||
|
has_finished = std::atomic_fetch_add_explicit(
|
||||||
|
&order, 1, std::memory_order::memory_order_relaxed);
|
||||||
|
});
|
||||||
|
result = Wait(thread.get(), true, 100ms);
|
||||||
|
REQUIRE(result == WaitResult::kTimeout);
|
||||||
|
thread->QueueUserCallback([] { Thread::Exit(0); });
|
||||||
|
result = Wait(thread.get(), true, 500ms);
|
||||||
|
REQUIRE(result == WaitResult::kSuccess);
|
||||||
|
REQUIRE(is_modified == 0);
|
||||||
|
REQUIRE(has_finished == -1);
|
||||||
|
|
||||||
|
// TODO(bwrsandman): Test alertable wait returning kUserCallback by using IO
|
||||||
|
// callbacks.
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace test
|
||||||
|
} // namespace base
|
||||||
|
} // namespace xe
|
||||||
|
|
@ -24,29 +24,56 @@
|
||||||
#include <utility>
|
#include <utility>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#include "xenia/base/assert.h"
|
||||||
|
|
||||||
namespace xe {
|
namespace xe {
|
||||||
namespace threading {
|
namespace threading {
|
||||||
|
|
||||||
|
// This is more like an Event with self-reset when returning from Wait()
|
||||||
class Fence {
|
class Fence {
|
||||||
public:
|
public:
|
||||||
Fence() : signaled_(false) {}
|
Fence() : signal_state_(0) {}
|
||||||
|
|
||||||
void Signal() {
|
void Signal() {
|
||||||
std::unique_lock<std::mutex> lock(mutex_);
|
std::unique_lock<std::mutex> lock(mutex_);
|
||||||
signaled_.store(true);
|
signal_state_ |= SIGMASK_;
|
||||||
cond_.notify_all();
|
cond_.notify_all();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Wait for the Fence to be signaled. Clears the signal on return.
|
||||||
void Wait() {
|
void Wait() {
|
||||||
std::unique_lock<std::mutex> lock(mutex_);
|
std::unique_lock<std::mutex> lock(mutex_);
|
||||||
while (!signaled_.load()) {
|
assert_true((signal_state_ & ~SIGMASK_) < (SIGMASK_ - 1) &&
|
||||||
|
"Too many threads?");
|
||||||
|
|
||||||
|
// keep local copy to minimize loads
|
||||||
|
auto signal_state = ++signal_state_;
|
||||||
|
for (; !(signal_state & SIGMASK_); signal_state = signal_state_) {
|
||||||
cond_.wait(lock);
|
cond_.wait(lock);
|
||||||
}
|
}
|
||||||
signaled_.store(false);
|
|
||||||
|
// We can't just clear the signal as other threads may not have read it yet
|
||||||
|
assert_true((signal_state & ~SIGMASK_) > 0); // wait_count > 0
|
||||||
|
if (signal_state == (1 | SIGMASK_)) { // wait_count == 1
|
||||||
|
// Last one out turn off the lights
|
||||||
|
signal_state_ = 0;
|
||||||
|
} else {
|
||||||
|
// Oops, another thread is still waiting, set the new count and keep the
|
||||||
|
// signal.
|
||||||
|
signal_state_ = --signal_state;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
using state_t_ = uint_fast32_t;
|
||||||
|
static constexpr state_t_ SIGMASK_ = state_t_(1)
|
||||||
|
<< (sizeof(state_t_) * 8 - 1);
|
||||||
|
|
||||||
std::mutex mutex_;
|
std::mutex mutex_;
|
||||||
std::condition_variable cond_;
|
std::condition_variable cond_;
|
||||||
std::atomic<bool> signaled_;
|
// Use the highest bit (sign bit) as the signal flag and the rest to count
|
||||||
|
// waiting threads.
|
||||||
|
volatile state_t_ signal_state_;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Returns the total number of logical processors in the host system.
|
// Returns the total number of logical processors in the host system.
|
||||||
|
|
@ -308,11 +335,11 @@ class Timer : public WaitHandle {
|
||||||
std::chrono::milliseconds period,
|
std::chrono::milliseconds period,
|
||||||
std::function<void()> opt_callback = nullptr) = 0;
|
std::function<void()> opt_callback = nullptr) = 0;
|
||||||
template <typename Rep, typename Period>
|
template <typename Rep, typename Period>
|
||||||
void SetRepeating(std::chrono::nanoseconds due_time,
|
bool SetRepeating(std::chrono::nanoseconds due_time,
|
||||||
std::chrono::duration<Rep, Period> period,
|
std::chrono::duration<Rep, Period> period,
|
||||||
std::function<void()> opt_callback = nullptr) {
|
std::function<void()> opt_callback = nullptr) {
|
||||||
SetRepeating(due_time,
|
return SetRepeating(
|
||||||
std::chrono::duration_cast<std::chrono::milliseconds>(period),
|
due_time, std::chrono::duration_cast<std::chrono::milliseconds>(period),
|
||||||
std::move(opt_callback));
|
std::move(opt_callback));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -391,7 +418,7 @@ class Thread : public WaitHandle {
|
||||||
|
|
||||||
// Decrements a thread's suspend count. When the suspend count is decremented
|
// Decrements a thread's suspend count. When the suspend count is decremented
|
||||||
// to zero, the execution of the thread is resumed.
|
// to zero, the execution of the thread is resumed.
|
||||||
virtual bool Resume(uint32_t* out_new_suspend_count = nullptr) = 0;
|
virtual bool Resume(uint32_t* out_previous_suspend_count = nullptr) = 0;
|
||||||
|
|
||||||
// Suspends the specified thread.
|
// Suspends the specified thread.
|
||||||
virtual bool Suspend(uint32_t* out_previous_suspend_count = nullptr) = 0;
|
virtual bool Suspend(uint32_t* out_previous_suspend_count = nullptr) = 0;
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -388,16 +388,16 @@ class Win32Thread : public Win32Handle<Thread> {
|
||||||
QueueUserAPC(DispatchApc, handle_, reinterpret_cast<ULONG_PTR>(apc_data));
|
QueueUserAPC(DispatchApc, handle_, reinterpret_cast<ULONG_PTR>(apc_data));
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Resume(uint32_t* out_new_suspend_count = nullptr) override {
|
bool Resume(uint32_t* out_previous_suspend_count = nullptr) override {
|
||||||
if (out_new_suspend_count) {
|
if (out_previous_suspend_count) {
|
||||||
*out_new_suspend_count = 0;
|
*out_previous_suspend_count = 0;
|
||||||
}
|
}
|
||||||
DWORD result = ResumeThread(handle_);
|
DWORD result = ResumeThread(handle_);
|
||||||
if (result == UINT_MAX) {
|
if (result == UINT_MAX) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (out_new_suspend_count) {
|
if (out_previous_suspend_count) {
|
||||||
*out_new_suspend_count = result;
|
*out_previous_suspend_count = result;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -73,7 +73,7 @@ bool CommandProcessor::Initialize(
|
||||||
WorkerThreadMain();
|
WorkerThreadMain();
|
||||||
return 0;
|
return 0;
|
||||||
}));
|
}));
|
||||||
worker_thread_->set_name("GraphicsSystem Command Processor");
|
worker_thread_->set_name("GPU Commands");
|
||||||
worker_thread_->Create();
|
worker_thread_->Create();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
|
||||||
|
|
@ -1996,15 +1996,44 @@ bool D3D12CommandProcessor::IssueDraw(xenos::PrimitiveType primitive_type,
|
||||||
current_external_pipeline_ = nullptr;
|
current_external_pipeline_ = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get dynamic rasterizer state.
|
||||||
|
// Supersampling replacing multisampling due to difficulties of emulating
|
||||||
|
// EDRAM with multisampling with RTV/DSV (with ROV, there's MSAA), and also
|
||||||
|
// resolution scale.
|
||||||
|
uint32_t pixel_size_x, pixel_size_y;
|
||||||
|
if (edram_rov_used_) {
|
||||||
|
pixel_size_x = 1;
|
||||||
|
pixel_size_y = 1;
|
||||||
|
} else {
|
||||||
|
xenos::MsaaSamples msaa_samples =
|
||||||
|
regs.Get<reg::RB_SURFACE_INFO>().msaa_samples;
|
||||||
|
pixel_size_x = msaa_samples >= xenos::MsaaSamples::k4X ? 2 : 1;
|
||||||
|
pixel_size_y = msaa_samples >= xenos::MsaaSamples::k2X ? 2 : 1;
|
||||||
|
}
|
||||||
|
if (texture_cache_->IsResolutionScale2X()) {
|
||||||
|
pixel_size_x *= 2;
|
||||||
|
pixel_size_y *= 2;
|
||||||
|
}
|
||||||
|
draw_util::ViewportInfo viewport_info;
|
||||||
|
draw_util::GetHostViewportInfo(regs, float(pixel_size_x), float(pixel_size_y),
|
||||||
|
true, float(D3D12_VIEWPORT_BOUNDS_MAX), false,
|
||||||
|
viewport_info);
|
||||||
|
draw_util::Scissor scissor;
|
||||||
|
draw_util::GetScissor(regs, scissor);
|
||||||
|
scissor.left *= pixel_size_x;
|
||||||
|
scissor.top *= pixel_size_y;
|
||||||
|
scissor.width *= pixel_size_x;
|
||||||
|
scissor.height *= pixel_size_y;
|
||||||
|
|
||||||
// Update viewport, scissor, blend factor and stencil reference.
|
// Update viewport, scissor, blend factor and stencil reference.
|
||||||
UpdateFixedFunctionState(primitive_two_faced);
|
UpdateFixedFunctionState(viewport_info, scissor, primitive_two_faced);
|
||||||
|
|
||||||
// Update system constants before uploading them.
|
// Update system constants before uploading them.
|
||||||
UpdateSystemConstantValues(
|
UpdateSystemConstantValues(
|
||||||
memexport_used, primitive_two_faced, line_loop_closing_index,
|
memexport_used, primitive_two_faced, line_loop_closing_index,
|
||||||
indexed ? index_buffer_info->endianness : xenos::Endian::kNone,
|
indexed ? index_buffer_info->endianness : xenos::Endian::kNone,
|
||||||
used_texture_mask, early_z, GetCurrentColorMask(pixel_shader),
|
viewport_info, pixel_size_x, pixel_size_y, used_texture_mask, early_z,
|
||||||
pipeline_render_targets);
|
GetCurrentColorMask(pixel_shader), pipeline_render_targets);
|
||||||
|
|
||||||
// Update constant buffers, descriptors and root parameters.
|
// Update constant buffers, descriptors and root parameters.
|
||||||
if (!UpdateBindings(vertex_shader, pixel_shader, root_signature)) {
|
if (!UpdateBindings(vertex_shader, pixel_shader, root_signature)) {
|
||||||
|
|
@ -2753,87 +2782,21 @@ void D3D12CommandProcessor::ClearCommandAllocatorCache() {
|
||||||
command_allocator_writable_last_ = nullptr;
|
command_allocator_writable_last_ = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
void D3D12CommandProcessor::UpdateFixedFunctionState(bool primitive_two_faced) {
|
void D3D12CommandProcessor::UpdateFixedFunctionState(
|
||||||
|
const draw_util::ViewportInfo& viewport_info,
|
||||||
|
const draw_util::Scissor& scissor, bool primitive_two_faced) {
|
||||||
#if XE_UI_D3D12_FINE_GRAINED_DRAW_SCOPES
|
#if XE_UI_D3D12_FINE_GRAINED_DRAW_SCOPES
|
||||||
SCOPE_profile_cpu_f("gpu");
|
SCOPE_profile_cpu_f("gpu");
|
||||||
#endif // XE_UI_D3D12_FINE_GRAINED_DRAW_SCOPES
|
#endif // XE_UI_D3D12_FINE_GRAINED_DRAW_SCOPES
|
||||||
|
|
||||||
const RegisterFile& regs = *register_file_;
|
|
||||||
|
|
||||||
// Window parameters.
|
|
||||||
// http://ftp.tku.edu.tw/NetBSD/NetBSD-current/xsrc/external/mit/xf86-video-ati/dist/src/r600_reg_auto_r6xx.h
|
|
||||||
// See r200UpdateWindow:
|
|
||||||
// https://github.com/freedreno/mesa/blob/master/src/mesa/drivers/dri/r200/r200_state.c
|
|
||||||
auto pa_sc_window_offset = regs.Get<reg::PA_SC_WINDOW_OFFSET>();
|
|
||||||
|
|
||||||
// Supersampling replacing multisampling due to difficulties of emulating
|
|
||||||
// EDRAM with multisampling with RTV/DSV (with ROV, there's MSAA), and also
|
|
||||||
// resolution scale.
|
|
||||||
uint32_t pixel_size_x, pixel_size_y;
|
|
||||||
if (edram_rov_used_) {
|
|
||||||
pixel_size_x = 1;
|
|
||||||
pixel_size_y = 1;
|
|
||||||
} else {
|
|
||||||
xenos::MsaaSamples msaa_samples =
|
|
||||||
regs.Get<reg::RB_SURFACE_INFO>().msaa_samples;
|
|
||||||
pixel_size_x = msaa_samples >= xenos::MsaaSamples::k4X ? 2 : 1;
|
|
||||||
pixel_size_y = msaa_samples >= xenos::MsaaSamples::k2X ? 2 : 1;
|
|
||||||
}
|
|
||||||
if (texture_cache_->IsResolutionScale2X()) {
|
|
||||||
pixel_size_x *= 2;
|
|
||||||
pixel_size_y *= 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Viewport.
|
// Viewport.
|
||||||
// PA_CL_VTE_CNTL contains whether offsets and scales are enabled.
|
|
||||||
// http://www.x.org/docs/AMD/old/evergreen_3D_registers_v2.pdf
|
|
||||||
// In games, either all are enabled (for regular drawing) or none are (for
|
|
||||||
// rectangle lists usually).
|
|
||||||
//
|
|
||||||
// If scale/offset is enabled, the Xenos shader is writing (neglecting W
|
|
||||||
// division) position in the NDC (-1, -1, dx_clip_space_def - 1) -> (1, 1, 1)
|
|
||||||
// box. If it's not, the position is in screen space. Since we can only use
|
|
||||||
// the NDC in PC APIs, we use a viewport of the largest possible size, and
|
|
||||||
// divide the position by it in translated shaders.
|
|
||||||
auto pa_cl_vte_cntl = regs.Get<reg::PA_CL_VTE_CNTL>();
|
|
||||||
float viewport_scale_x =
|
|
||||||
pa_cl_vte_cntl.vport_x_scale_ena
|
|
||||||
? std::abs(regs[XE_GPU_REG_PA_CL_VPORT_XSCALE].f32)
|
|
||||||
: 4096.0f;
|
|
||||||
float viewport_scale_y =
|
|
||||||
pa_cl_vte_cntl.vport_y_scale_ena
|
|
||||||
? std::abs(regs[XE_GPU_REG_PA_CL_VPORT_YSCALE].f32)
|
|
||||||
: 4096.0f;
|
|
||||||
float viewport_scale_z = pa_cl_vte_cntl.vport_z_scale_ena
|
|
||||||
? regs[XE_GPU_REG_PA_CL_VPORT_ZSCALE].f32
|
|
||||||
: 1.0f;
|
|
||||||
float viewport_offset_x = pa_cl_vte_cntl.vport_x_offset_ena
|
|
||||||
? regs[XE_GPU_REG_PA_CL_VPORT_XOFFSET].f32
|
|
||||||
: std::abs(viewport_scale_x);
|
|
||||||
float viewport_offset_y = pa_cl_vte_cntl.vport_y_offset_ena
|
|
||||||
? regs[XE_GPU_REG_PA_CL_VPORT_YOFFSET].f32
|
|
||||||
: std::abs(viewport_scale_y);
|
|
||||||
float viewport_offset_z = pa_cl_vte_cntl.vport_z_offset_ena
|
|
||||||
? regs[XE_GPU_REG_PA_CL_VPORT_ZOFFSET].f32
|
|
||||||
: 0.0f;
|
|
||||||
if (regs.Get<reg::PA_SU_SC_MODE_CNTL>().vtx_window_offset_enable) {
|
|
||||||
viewport_offset_x += float(pa_sc_window_offset.window_x_offset);
|
|
||||||
viewport_offset_y += float(pa_sc_window_offset.window_y_offset);
|
|
||||||
}
|
|
||||||
D3D12_VIEWPORT viewport;
|
D3D12_VIEWPORT viewport;
|
||||||
viewport.TopLeftX =
|
viewport.TopLeftX = viewport_info.left;
|
||||||
(viewport_offset_x - viewport_scale_x) * float(pixel_size_x);
|
viewport.TopLeftY = viewport_info.top;
|
||||||
viewport.TopLeftY =
|
viewport.Width = viewport_info.width;
|
||||||
(viewport_offset_y - viewport_scale_y) * float(pixel_size_y);
|
viewport.Height = viewport_info.height;
|
||||||
viewport.Width = viewport_scale_x * 2.0f * float(pixel_size_x);
|
viewport.MinDepth = viewport_info.z_min;
|
||||||
viewport.Height = viewport_scale_y * 2.0f * float(pixel_size_y);
|
viewport.MaxDepth = viewport_info.z_max;
|
||||||
viewport.MinDepth = viewport_offset_z;
|
|
||||||
viewport.MaxDepth = viewport_offset_z + viewport_scale_z;
|
|
||||||
if (viewport_scale_z < 0.0f) {
|
|
||||||
// MinDepth > MaxDepth doesn't work on Nvidia, emulating it in vertex
|
|
||||||
// shaders and when applying polygon offset.
|
|
||||||
std::swap(viewport.MinDepth, viewport.MaxDepth);
|
|
||||||
}
|
|
||||||
ff_viewport_update_needed_ |= ff_viewport_.TopLeftX != viewport.TopLeftX;
|
ff_viewport_update_needed_ |= ff_viewport_.TopLeftX != viewport.TopLeftX;
|
||||||
ff_viewport_update_needed_ |= ff_viewport_.TopLeftY != viewport.TopLeftY;
|
ff_viewport_update_needed_ |= ff_viewport_.TopLeftY != viewport.TopLeftY;
|
||||||
ff_viewport_update_needed_ |= ff_viewport_.Width != viewport.Width;
|
ff_viewport_update_needed_ |= ff_viewport_.Width != viewport.Width;
|
||||||
|
|
@ -2847,13 +2810,11 @@ void D3D12CommandProcessor::UpdateFixedFunctionState(bool primitive_two_faced) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Scissor.
|
// Scissor.
|
||||||
draw_util::Scissor scissor;
|
|
||||||
draw_util::GetScissor(regs, scissor);
|
|
||||||
D3D12_RECT scissor_rect;
|
D3D12_RECT scissor_rect;
|
||||||
scissor_rect.left = LONG(scissor.left * pixel_size_x);
|
scissor_rect.left = LONG(scissor.left);
|
||||||
scissor_rect.top = LONG(scissor.top * pixel_size_y);
|
scissor_rect.top = LONG(scissor.top);
|
||||||
scissor_rect.right = LONG((scissor.left + scissor.width) * pixel_size_x);
|
scissor_rect.right = LONG(scissor.left + scissor.width);
|
||||||
scissor_rect.bottom = LONG((scissor.top + scissor.height) * pixel_size_y);
|
scissor_rect.bottom = LONG(scissor.top + scissor.height);
|
||||||
ff_scissor_update_needed_ |= ff_scissor_.left != scissor_rect.left;
|
ff_scissor_update_needed_ |= ff_scissor_.left != scissor_rect.left;
|
||||||
ff_scissor_update_needed_ |= ff_scissor_.top != scissor_rect.top;
|
ff_scissor_update_needed_ |= ff_scissor_.top != scissor_rect.top;
|
||||||
ff_scissor_update_needed_ |= ff_scissor_.right != scissor_rect.right;
|
ff_scissor_update_needed_ |= ff_scissor_.right != scissor_rect.right;
|
||||||
|
|
@ -2865,6 +2826,8 @@ void D3D12CommandProcessor::UpdateFixedFunctionState(bool primitive_two_faced) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!edram_rov_used_) {
|
if (!edram_rov_used_) {
|
||||||
|
const RegisterFile& regs = *register_file_;
|
||||||
|
|
||||||
// Blend factor.
|
// Blend factor.
|
||||||
ff_blend_factor_update_needed_ |=
|
ff_blend_factor_update_needed_ |=
|
||||||
ff_blend_factor_[0] != regs[XE_GPU_REG_RB_BLEND_RED].f32;
|
ff_blend_factor_[0] != regs[XE_GPU_REG_RB_BLEND_RED].f32;
|
||||||
|
|
@ -2908,7 +2871,9 @@ void D3D12CommandProcessor::UpdateFixedFunctionState(bool primitive_two_faced) {
|
||||||
void D3D12CommandProcessor::UpdateSystemConstantValues(
|
void D3D12CommandProcessor::UpdateSystemConstantValues(
|
||||||
bool shared_memory_is_uav, bool primitive_two_faced,
|
bool shared_memory_is_uav, bool primitive_two_faced,
|
||||||
uint32_t line_loop_closing_index, xenos::Endian index_endian,
|
uint32_t line_loop_closing_index, xenos::Endian index_endian,
|
||||||
uint32_t used_texture_mask, bool early_z, uint32_t color_mask,
|
const draw_util::ViewportInfo& viewport_info, uint32_t pixel_size_x,
|
||||||
|
uint32_t pixel_size_y, uint32_t used_texture_mask, bool early_z,
|
||||||
|
uint32_t color_mask,
|
||||||
const RenderTargetCache::PipelineRenderTarget render_targets[4]) {
|
const RenderTargetCache::PipelineRenderTarget render_targets[4]) {
|
||||||
#if XE_UI_D3D12_FINE_GRAINED_DRAW_SCOPES
|
#if XE_UI_D3D12_FINE_GRAINED_DRAW_SCOPES
|
||||||
SCOPE_profile_cpu_f("gpu");
|
SCOPE_profile_cpu_f("gpu");
|
||||||
|
|
@ -2920,7 +2885,6 @@ void D3D12CommandProcessor::UpdateSystemConstantValues(
|
||||||
auto pa_su_point_minmax = regs.Get<reg::PA_SU_POINT_MINMAX>();
|
auto pa_su_point_minmax = regs.Get<reg::PA_SU_POINT_MINMAX>();
|
||||||
auto pa_su_point_size = regs.Get<reg::PA_SU_POINT_SIZE>();
|
auto pa_su_point_size = regs.Get<reg::PA_SU_POINT_SIZE>();
|
||||||
auto pa_su_sc_mode_cntl = regs.Get<reg::PA_SU_SC_MODE_CNTL>();
|
auto pa_su_sc_mode_cntl = regs.Get<reg::PA_SU_SC_MODE_CNTL>();
|
||||||
auto pa_su_vtx_cntl = regs.Get<reg::PA_SU_VTX_CNTL>();
|
|
||||||
float rb_alpha_ref = regs[XE_GPU_REG_RB_ALPHA_REF].f32;
|
float rb_alpha_ref = regs[XE_GPU_REG_RB_ALPHA_REF].f32;
|
||||||
auto rb_colorcontrol = regs.Get<reg::RB_COLORCONTROL>();
|
auto rb_colorcontrol = regs.Get<reg::RB_COLORCONTROL>();
|
||||||
auto rb_depth_info = regs.Get<reg::RB_DEPTH_INFO>();
|
auto rb_depth_info = regs.Get<reg::RB_DEPTH_INFO>();
|
||||||
|
|
@ -2986,11 +2950,6 @@ void D3D12CommandProcessor::UpdateSystemConstantValues(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get viewport Z scale - needed for flags and ROV output.
|
|
||||||
float viewport_scale_z = pa_cl_vte_cntl.vport_z_scale_ena
|
|
||||||
? regs[XE_GPU_REG_PA_CL_VPORT_ZSCALE].f32
|
|
||||||
: 1.0f;
|
|
||||||
|
|
||||||
bool dirty = false;
|
bool dirty = false;
|
||||||
|
|
||||||
// Flags.
|
// Flags.
|
||||||
|
|
@ -3023,10 +2982,6 @@ void D3D12CommandProcessor::UpdateSystemConstantValues(
|
||||||
flags |= (pa_cl_clip_cntl.value & 0b111111)
|
flags |= (pa_cl_clip_cntl.value & 0b111111)
|
||||||
<< DxbcShaderTranslator::kSysFlag_UserClipPlane0_Shift;
|
<< DxbcShaderTranslator::kSysFlag_UserClipPlane0_Shift;
|
||||||
}
|
}
|
||||||
// Reversed depth.
|
|
||||||
if (viewport_scale_z < 0.0f) {
|
|
||||||
flags |= DxbcShaderTranslator::kSysFlag_ReverseZ;
|
|
||||||
}
|
|
||||||
// Whether SV_IsFrontFace matters.
|
// Whether SV_IsFrontFace matters.
|
||||||
if (primitive_two_faced) {
|
if (primitive_two_faced) {
|
||||||
flags |= DxbcShaderTranslator::kSysFlag_PrimitiveTwoFaced;
|
flags |= DxbcShaderTranslator::kSysFlag_PrimitiveTwoFaced;
|
||||||
|
|
@ -3122,81 +3077,24 @@ void D3D12CommandProcessor::UpdateSystemConstantValues(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Conversion to Direct3D 12 normalized device coordinates.
|
// Conversion to Direct3D 12 normalized device coordinates.
|
||||||
// See viewport configuration in UpdateFixedFunctionState for explanations.
|
|
||||||
// X and Y scale/offset is to convert unnormalized coordinates generated by
|
|
||||||
// shaders (for rectangle list drawing, for instance) to the viewport of the
|
|
||||||
// largest possible render target size that is used to emulate unnormalized
|
|
||||||
// coordinates. Z scale/offset is to convert from OpenGL NDC to Direct3D NDC
|
|
||||||
// if needed. Also apply half-pixel offset to reproduce Direct3D 9
|
|
||||||
// rasterization rules - must be done before clipping, not through the
|
|
||||||
// viewport, for SSAA and resolution scale to work correctly.
|
|
||||||
float viewport_scale_x = regs[XE_GPU_REG_PA_CL_VPORT_XSCALE].f32;
|
|
||||||
float viewport_scale_y = regs[XE_GPU_REG_PA_CL_VPORT_YSCALE].f32;
|
|
||||||
// Kill all primitives if multipass or both faces are culled, but still need
|
// Kill all primitives if multipass or both faces are culled, but still need
|
||||||
// to do memexport.
|
// to do memexport.
|
||||||
if (sq_program_cntl.vs_export_mode ==
|
if (sq_program_cntl.vs_export_mode ==
|
||||||
xenos::VertexShaderExportMode::kMultipass ||
|
xenos::VertexShaderExportMode::kMultipass ||
|
||||||
(primitive_two_faced && pa_su_sc_mode_cntl.cull_front &&
|
(primitive_two_faced && pa_su_sc_mode_cntl.cull_front &&
|
||||||
pa_su_sc_mode_cntl.cull_back)) {
|
pa_su_sc_mode_cntl.cull_back)) {
|
||||||
dirty |= !std::isnan(system_constants_.ndc_scale[0]);
|
|
||||||
dirty |= !std::isnan(system_constants_.ndc_scale[1]);
|
|
||||||
dirty |= !std::isnan(system_constants_.ndc_scale[2]);
|
|
||||||
dirty |= !std::isnan(system_constants_.ndc_offset[0]);
|
|
||||||
dirty |= !std::isnan(system_constants_.ndc_offset[1]);
|
|
||||||
dirty |= !std::isnan(system_constants_.ndc_offset[2]);
|
|
||||||
float nan_value = std::nanf("");
|
float nan_value = std::nanf("");
|
||||||
system_constants_.ndc_scale[0] = nan_value;
|
for (uint32_t i = 0; i < 3; ++i) {
|
||||||
system_constants_.ndc_scale[1] = nan_value;
|
dirty |= !std::isnan(system_constants_.ndc_scale[i]);
|
||||||
system_constants_.ndc_scale[2] = nan_value;
|
system_constants_.ndc_scale[i] = nan_value;
|
||||||
system_constants_.ndc_offset[0] = nan_value;
|
|
||||||
system_constants_.ndc_offset[1] = nan_value;
|
|
||||||
system_constants_.ndc_offset[2] = nan_value;
|
|
||||||
} else {
|
|
||||||
// When VPORT_Z_SCALE_ENA is disabled, Z/W is directly what is expected to
|
|
||||||
// be written to the depth buffer, and for some reason DX_CLIP_SPACE_DEF
|
|
||||||
// isn't set in this case in draws in games.
|
|
||||||
bool gl_clip_space_def =
|
|
||||||
!pa_cl_clip_cntl.dx_clip_space_def && pa_cl_vte_cntl.vport_z_scale_ena;
|
|
||||||
float ndc_scale_x = pa_cl_vte_cntl.vport_x_scale_ena
|
|
||||||
? (viewport_scale_x >= 0.0f ? 1.0f : -1.0f)
|
|
||||||
: (1.0f / 4096.0f);
|
|
||||||
float ndc_scale_y = pa_cl_vte_cntl.vport_y_scale_ena
|
|
||||||
? (viewport_scale_y >= 0.0f ? -1.0f : 1.0f)
|
|
||||||
: (-1.0f / 4096.0f);
|
|
||||||
float ndc_scale_z = gl_clip_space_def ? 0.5f : 1.0f;
|
|
||||||
float ndc_offset_x = pa_cl_vte_cntl.vport_x_offset_ena ? 0.0f : -1.0f;
|
|
||||||
float ndc_offset_y = pa_cl_vte_cntl.vport_y_offset_ena ? 0.0f : 1.0f;
|
|
||||||
float ndc_offset_z = gl_clip_space_def ? 0.5f : 0.0f;
|
|
||||||
if (cvars::half_pixel_offset && !pa_su_vtx_cntl.pix_center) {
|
|
||||||
// Signs are hopefully correct here, tested in GTA IV on both clearing
|
|
||||||
// (without a viewport) and drawing things near the edges of the screen.
|
|
||||||
if (pa_cl_vte_cntl.vport_x_scale_ena) {
|
|
||||||
if (viewport_scale_x != 0.0f) {
|
|
||||||
ndc_offset_x += 0.5f / viewport_scale_x;
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ndc_offset_x += 1.0f / xenos::kTexture2DCubeMaxWidthHeight;
|
for (uint32_t i = 0; i < 3; ++i) {
|
||||||
|
dirty |= system_constants_.ndc_scale[i] != viewport_info.ndc_scale[i];
|
||||||
|
dirty |= system_constants_.ndc_offset[i] != viewport_info.ndc_offset[i];
|
||||||
|
system_constants_.ndc_scale[i] = viewport_info.ndc_scale[i];
|
||||||
|
system_constants_.ndc_offset[i] = viewport_info.ndc_offset[i];
|
||||||
}
|
}
|
||||||
if (pa_cl_vte_cntl.vport_y_scale_ena) {
|
|
||||||
if (viewport_scale_y != 0.0f) {
|
|
||||||
ndc_offset_y += 0.5f / viewport_scale_y;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ndc_offset_y -= 1.0f / xenos::kTexture2DCubeMaxWidthHeight;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
dirty |= system_constants_.ndc_scale[0] != ndc_scale_x;
|
|
||||||
dirty |= system_constants_.ndc_scale[1] != ndc_scale_y;
|
|
||||||
dirty |= system_constants_.ndc_scale[2] != ndc_scale_z;
|
|
||||||
dirty |= system_constants_.ndc_offset[0] != ndc_offset_x;
|
|
||||||
dirty |= system_constants_.ndc_offset[1] != ndc_offset_y;
|
|
||||||
dirty |= system_constants_.ndc_offset[2] != ndc_offset_z;
|
|
||||||
system_constants_.ndc_scale[0] = ndc_scale_x;
|
|
||||||
system_constants_.ndc_scale[1] = ndc_scale_y;
|
|
||||||
system_constants_.ndc_scale[2] = ndc_scale_z;
|
|
||||||
system_constants_.ndc_offset[0] = ndc_offset_x;
|
|
||||||
system_constants_.ndc_offset[1] = ndc_offset_y;
|
|
||||||
system_constants_.ndc_offset[2] = ndc_offset_z;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Point size.
|
// Point size.
|
||||||
|
|
@ -3212,19 +3110,10 @@ void D3D12CommandProcessor::UpdateSystemConstantValues(
|
||||||
system_constants_.point_size[1] = point_size_y;
|
system_constants_.point_size[1] = point_size_y;
|
||||||
system_constants_.point_size_min_max[0] = point_size_min;
|
system_constants_.point_size_min_max[0] = point_size_min;
|
||||||
system_constants_.point_size_min_max[1] = point_size_max;
|
system_constants_.point_size_min_max[1] = point_size_max;
|
||||||
float point_screen_to_ndc_x, point_screen_to_ndc_y;
|
float point_screen_to_ndc_x =
|
||||||
if (pa_cl_vte_cntl.vport_x_scale_ena) {
|
(0.5f * 2.0f * pixel_size_x) / viewport_info.width;
|
||||||
point_screen_to_ndc_x =
|
float point_screen_to_ndc_y =
|
||||||
(viewport_scale_x != 0.0f) ? (0.5f / viewport_scale_x) : 0.0f;
|
(0.5f * 2.0f * pixel_size_y) / viewport_info.height;
|
||||||
} else {
|
|
||||||
point_screen_to_ndc_x = 1.0f / xenos::kTexture2DCubeMaxWidthHeight;
|
|
||||||
}
|
|
||||||
if (pa_cl_vte_cntl.vport_y_scale_ena) {
|
|
||||||
point_screen_to_ndc_y =
|
|
||||||
(viewport_scale_y != 0.0f) ? (-0.5f / viewport_scale_y) : 0.0f;
|
|
||||||
} else {
|
|
||||||
point_screen_to_ndc_y = -1.0f / xenos::kTexture2DCubeMaxWidthHeight;
|
|
||||||
}
|
|
||||||
dirty |= system_constants_.point_screen_to_ndc[0] != point_screen_to_ndc_x;
|
dirty |= system_constants_.point_screen_to_ndc[0] != point_screen_to_ndc_x;
|
||||||
dirty |= system_constants_.point_screen_to_ndc[1] != point_screen_to_ndc_y;
|
dirty |= system_constants_.point_screen_to_ndc[1] != point_screen_to_ndc_y;
|
||||||
system_constants_.point_screen_to_ndc[0] = point_screen_to_ndc_x;
|
system_constants_.point_screen_to_ndc[0] = point_screen_to_ndc_x;
|
||||||
|
|
@ -3374,20 +3263,11 @@ void D3D12CommandProcessor::UpdateSystemConstantValues(
|
||||||
dirty |= system_constants_.edram_depth_base_dwords != depth_base_dwords;
|
dirty |= system_constants_.edram_depth_base_dwords != depth_base_dwords;
|
||||||
system_constants_.edram_depth_base_dwords = depth_base_dwords;
|
system_constants_.edram_depth_base_dwords = depth_base_dwords;
|
||||||
|
|
||||||
// The Z range is reversed in the vertex shader if it's reverse - use the
|
float depth_range_scale = viewport_info.z_max - viewport_info.z_min;
|
||||||
// absolute value of the scale.
|
|
||||||
float depth_range_scale = std::abs(viewport_scale_z);
|
|
||||||
dirty |= system_constants_.edram_depth_range_scale != depth_range_scale;
|
dirty |= system_constants_.edram_depth_range_scale != depth_range_scale;
|
||||||
system_constants_.edram_depth_range_scale = depth_range_scale;
|
system_constants_.edram_depth_range_scale = depth_range_scale;
|
||||||
float depth_range_offset = pa_cl_vte_cntl.vport_z_offset_ena
|
dirty |= system_constants_.edram_depth_range_offset != viewport_info.z_min;
|
||||||
? regs[XE_GPU_REG_PA_CL_VPORT_ZOFFSET].f32
|
system_constants_.edram_depth_range_offset = viewport_info.z_min;
|
||||||
: 0.0f;
|
|
||||||
if (viewport_scale_z < 0.0f) {
|
|
||||||
// Similar to MinDepth in fixed-function viewport calculation.
|
|
||||||
depth_range_offset += viewport_scale_z;
|
|
||||||
}
|
|
||||||
dirty |= system_constants_.edram_depth_range_offset != depth_range_offset;
|
|
||||||
system_constants_.edram_depth_range_offset = depth_range_offset;
|
|
||||||
|
|
||||||
// For non-polygons, front polygon offset is used, and it's enabled if
|
// For non-polygons, front polygon offset is used, and it's enabled if
|
||||||
// POLY_OFFSET_PARA_ENABLED is set, for polygons, separate front and back
|
// POLY_OFFSET_PARA_ENABLED is set, for polygons, separate front and back
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@
|
||||||
#include "xenia/gpu/d3d12/primitive_converter.h"
|
#include "xenia/gpu/d3d12/primitive_converter.h"
|
||||||
#include "xenia/gpu/d3d12/render_target_cache.h"
|
#include "xenia/gpu/d3d12/render_target_cache.h"
|
||||||
#include "xenia/gpu/d3d12/texture_cache.h"
|
#include "xenia/gpu/d3d12/texture_cache.h"
|
||||||
|
#include "xenia/gpu/draw_util.h"
|
||||||
#include "xenia/gpu/dxbc_shader_translator.h"
|
#include "xenia/gpu/dxbc_shader_translator.h"
|
||||||
#include "xenia/gpu/xenos.h"
|
#include "xenia/gpu/xenos.h"
|
||||||
#include "xenia/kernel/kernel_state.h"
|
#include "xenia/kernel/kernel_state.h"
|
||||||
|
|
@ -345,11 +346,15 @@ class D3D12CommandProcessor : public CommandProcessor {
|
||||||
D3D12_CPU_DESCRIPTOR_HANDLE& cpu_handle_out,
|
D3D12_CPU_DESCRIPTOR_HANDLE& cpu_handle_out,
|
||||||
D3D12_GPU_DESCRIPTOR_HANDLE& gpu_handle_out);
|
D3D12_GPU_DESCRIPTOR_HANDLE& gpu_handle_out);
|
||||||
|
|
||||||
void UpdateFixedFunctionState(bool primitive_two_faced);
|
void UpdateFixedFunctionState(const draw_util::ViewportInfo& viewport_info,
|
||||||
|
const draw_util::Scissor& scissor,
|
||||||
|
bool primitive_two_faced);
|
||||||
void UpdateSystemConstantValues(
|
void UpdateSystemConstantValues(
|
||||||
bool shared_memory_is_uav, bool primitive_two_faced,
|
bool shared_memory_is_uav, bool primitive_two_faced,
|
||||||
uint32_t line_loop_closing_index, xenos::Endian index_endian,
|
uint32_t line_loop_closing_index, xenos::Endian index_endian,
|
||||||
uint32_t used_texture_mask, bool early_z, uint32_t color_mask,
|
const draw_util::ViewportInfo& viewport_info, uint32_t pixel_size_x,
|
||||||
|
uint32_t pixel_size_y, uint32_t used_texture_mask, bool early_z,
|
||||||
|
uint32_t color_mask,
|
||||||
const RenderTargetCache::PipelineRenderTarget render_targets[4]);
|
const RenderTargetCache::PipelineRenderTarget render_targets[4]);
|
||||||
bool UpdateBindings(const D3D12Shader* vertex_shader,
|
bool UpdateBindings(const D3D12Shader* vertex_shader,
|
||||||
const D3D12Shader* pixel_shader,
|
const D3D12Shader* pixel_shader,
|
||||||
|
|
|
||||||
|
|
@ -111,6 +111,178 @@ int32_t FloatToD3D11Fixed16p8(float f32) {
|
||||||
return result.s;
|
return result.s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GetHostViewportInfo(const RegisterFile& regs, float pixel_size_x,
|
||||||
|
float pixel_size_y, bool origin_bottom_left,
|
||||||
|
float xy_max, bool allow_reverse_z,
|
||||||
|
ViewportInfo& viewport_info_out) {
|
||||||
|
assert_true(pixel_size_x >= 1.0f);
|
||||||
|
assert_true(pixel_size_y >= 1.0f);
|
||||||
|
assert_true(xy_max >= 1.0f);
|
||||||
|
|
||||||
|
// PA_CL_VTE_CNTL contains whether offsets and scales are enabled.
|
||||||
|
// http://www.x.org/docs/AMD/old/evergreen_3D_registers_v2.pdf
|
||||||
|
// In games, either all are enabled (for regular drawing) or none are (for
|
||||||
|
// rectangle lists usually).
|
||||||
|
//
|
||||||
|
// If scale/offset is enabled, the Xenos shader is writing (neglecting W
|
||||||
|
// division) position in the NDC (-1, -1, dx_clip_space_def - 1) -> (1, 1, 1)
|
||||||
|
// box. If it's not, the position is in screen space. Since we can only use
|
||||||
|
// the NDC in PC APIs, we use a viewport of the largest possible size, and
|
||||||
|
// divide the position by it in translated shaders.
|
||||||
|
|
||||||
|
auto pa_cl_clip_cntl = regs.Get<reg::PA_CL_CLIP_CNTL>();
|
||||||
|
auto pa_cl_vte_cntl = regs.Get<reg::PA_CL_VTE_CNTL>();
|
||||||
|
auto pa_su_sc_mode_cntl = regs.Get<reg::PA_SU_SC_MODE_CNTL>();
|
||||||
|
auto pa_su_vtx_cntl = regs.Get<reg::PA_SU_VTX_CNTL>();
|
||||||
|
|
||||||
|
float viewport_left, viewport_top;
|
||||||
|
float viewport_width, viewport_height;
|
||||||
|
float ndc_scale_x, ndc_scale_y;
|
||||||
|
float ndc_offset_x, ndc_offset_y;
|
||||||
|
// To avoid zero size viewports, which would harm division and aren't allowed
|
||||||
|
// on Vulkan. Nothing will ever be covered by a viewport of this size - this
|
||||||
|
// is 2 orders of magnitude smaller than a .8 subpixel, and thus shouldn't
|
||||||
|
// have any effect on rounding, n and n + 1 / 1024 would be rounded to the
|
||||||
|
// same .8 fixed-point value, thus in fixed-point, the viewport would have
|
||||||
|
// zero size.
|
||||||
|
const float size_min = 1.0f / 1024.0f;
|
||||||
|
|
||||||
|
float viewport_offset_x = pa_cl_vte_cntl.vport_x_offset_ena
|
||||||
|
? regs[XE_GPU_REG_PA_CL_VPORT_XOFFSET].f32
|
||||||
|
: 0.0f;
|
||||||
|
float viewport_offset_y = pa_cl_vte_cntl.vport_y_offset_ena
|
||||||
|
? regs[XE_GPU_REG_PA_CL_VPORT_YOFFSET].f32
|
||||||
|
: 0.0f;
|
||||||
|
if (pa_su_sc_mode_cntl.vtx_window_offset_enable) {
|
||||||
|
auto pa_sc_window_offset = regs.Get<reg::PA_SC_WINDOW_OFFSET>();
|
||||||
|
viewport_offset_x += float(pa_sc_window_offset.window_x_offset);
|
||||||
|
viewport_offset_y += float(pa_sc_window_offset.window_y_offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pa_cl_vte_cntl.vport_x_scale_ena) {
|
||||||
|
float pa_cl_vport_xscale = regs[XE_GPU_REG_PA_CL_VPORT_XSCALE].f32;
|
||||||
|
float viewport_scale_x_abs = std::abs(pa_cl_vport_xscale) * pixel_size_x;
|
||||||
|
viewport_left = viewport_offset_x * pixel_size_x - viewport_scale_x_abs;
|
||||||
|
float viewport_right = viewport_left + viewport_scale_x_abs * 2.0f;
|
||||||
|
// Keep the viewport in the positive quarter-plane for simplicity of
|
||||||
|
// clamping to the maximum supported bounds.
|
||||||
|
float cutoff_left = std::fmax(-viewport_left, 0.0f);
|
||||||
|
float cutoff_right = std::fmax(viewport_right - xy_max, 0.0f);
|
||||||
|
viewport_left = std::fmax(viewport_left, 0.0f);
|
||||||
|
viewport_right = std::fmin(viewport_right, xy_max);
|
||||||
|
viewport_width = viewport_right - viewport_left;
|
||||||
|
if (viewport_width > size_min) {
|
||||||
|
ndc_scale_x =
|
||||||
|
(viewport_width + cutoff_left + cutoff_right) / viewport_width;
|
||||||
|
if (pa_cl_vport_xscale < 0.0f) {
|
||||||
|
ndc_scale_x = -ndc_scale_x;
|
||||||
|
}
|
||||||
|
ndc_offset_x =
|
||||||
|
((cutoff_right - cutoff_left) * (0.5f * 2.0f)) / viewport_width;
|
||||||
|
} else {
|
||||||
|
// Empty viewport, but don't pass 0 because that's against the Vulkan
|
||||||
|
// specification.
|
||||||
|
viewport_left = 0.0f;
|
||||||
|
viewport_width = size_min;
|
||||||
|
ndc_scale_x = 0.0f;
|
||||||
|
ndc_offset_x = 0.0f;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Drawing without a viewport and without clipping to one - use a viewport
|
||||||
|
// covering the entire potential guest render target or the positive part of
|
||||||
|
// the host viewport area, whichever is smaller, and apply the offset, if
|
||||||
|
// enabled, via the shader.
|
||||||
|
viewport_left = 0.0f;
|
||||||
|
viewport_width = std::min(
|
||||||
|
float(xenos::kTexture2DCubeMaxWidthHeight) * pixel_size_x, xy_max);
|
||||||
|
ndc_scale_x = (2.0f * pixel_size_x) / viewport_width;
|
||||||
|
ndc_offset_x = viewport_offset_x * ndc_scale_x - 1.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pa_cl_vte_cntl.vport_y_scale_ena) {
|
||||||
|
float pa_cl_vport_yscale = regs[XE_GPU_REG_PA_CL_VPORT_YSCALE].f32;
|
||||||
|
float viewport_scale_y_abs = std::abs(pa_cl_vport_yscale) * pixel_size_y;
|
||||||
|
viewport_top = viewport_offset_y * pixel_size_y - viewport_scale_y_abs;
|
||||||
|
float viewport_bottom = viewport_top + viewport_scale_y_abs * 2.0f;
|
||||||
|
float cutoff_top = std::fmax(-viewport_top, 0.0f);
|
||||||
|
float cutoff_bottom = std::fmax(viewport_bottom - xy_max, 0.0f);
|
||||||
|
viewport_top = std::fmax(viewport_top, 0.0f);
|
||||||
|
viewport_bottom = std::fmin(viewport_bottom, xy_max);
|
||||||
|
viewport_height = viewport_bottom - viewport_top;
|
||||||
|
if (viewport_height > size_min) {
|
||||||
|
ndc_scale_y =
|
||||||
|
(viewport_height + cutoff_top + cutoff_bottom) / viewport_height;
|
||||||
|
if (pa_cl_vport_yscale < 0.0f) {
|
||||||
|
ndc_scale_y = -ndc_scale_y;
|
||||||
|
}
|
||||||
|
ndc_offset_y =
|
||||||
|
((cutoff_bottom - cutoff_top) * (0.5f * 2.0f)) / viewport_height;
|
||||||
|
} else {
|
||||||
|
// Empty viewport, but don't pass 0 because that's against the Vulkan
|
||||||
|
// specification.
|
||||||
|
viewport_top = 0.0f;
|
||||||
|
viewport_height = size_min;
|
||||||
|
ndc_scale_y = 0.0f;
|
||||||
|
ndc_offset_y = 0.0f;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
viewport_height = std::min(
|
||||||
|
float(xenos::kTexture2DCubeMaxWidthHeight) * pixel_size_y, xy_max);
|
||||||
|
ndc_scale_y = (2.0f * pixel_size_y) / viewport_height;
|
||||||
|
ndc_offset_y = viewport_offset_y * ndc_scale_y - 1.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply the vertex half-pixel offset via the shader (it must not affect
|
||||||
|
// clipping, otherwise with SSAA or resolution scale, samples in the left/top
|
||||||
|
// half will never be covered).
|
||||||
|
if (cvars::half_pixel_offset && !pa_su_vtx_cntl.pix_center) {
|
||||||
|
ndc_offset_x += (0.5f * 2.0f * pixel_size_x) / viewport_width;
|
||||||
|
ndc_offset_y += (0.5f * 2.0f * pixel_size_y) / viewport_height;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (origin_bottom_left) {
|
||||||
|
ndc_scale_y = -ndc_scale_y;
|
||||||
|
ndc_offset_y = -ndc_offset_y;
|
||||||
|
}
|
||||||
|
|
||||||
|
float viewport_scale_z = pa_cl_vte_cntl.vport_z_scale_ena
|
||||||
|
? regs[XE_GPU_REG_PA_CL_VPORT_ZSCALE].f32
|
||||||
|
: 1.0f;
|
||||||
|
float viewport_offset_z = pa_cl_vte_cntl.vport_z_offset_ena
|
||||||
|
? regs[XE_GPU_REG_PA_CL_VPORT_ZOFFSET].f32
|
||||||
|
: 0.0f;
|
||||||
|
// Vulkan requires the depth bounds to be in the 0 to 1 range without
|
||||||
|
// VK_EXT_depth_range_unrestricted (which isn't used on the Xbox 360).
|
||||||
|
float viewport_z_min = std::min(std::fmax(viewport_offset_z, 0.0f), 1.0f);
|
||||||
|
float viewport_z_max =
|
||||||
|
std::min(std::fmax(viewport_offset_z + viewport_scale_z, 0.0f), 1.0f);
|
||||||
|
// When VPORT_Z_SCALE_ENA is disabled, Z/W is directly what is expected to be
|
||||||
|
// written to the depth buffer, and for some reason DX_CLIP_SPACE_DEF isn't
|
||||||
|
// set in this case in draws in games.
|
||||||
|
bool gl_clip_space_def =
|
||||||
|
!pa_cl_clip_cntl.dx_clip_space_def && pa_cl_vte_cntl.vport_z_scale_ena;
|
||||||
|
float ndc_scale_z = gl_clip_space_def ? 0.5f : 1.0f;
|
||||||
|
float ndc_offset_z = gl_clip_space_def ? 0.5f : 0.0f;
|
||||||
|
if (viewport_z_min > viewport_z_max && !allow_reverse_z) {
|
||||||
|
std::swap(viewport_z_min, viewport_z_max);
|
||||||
|
ndc_scale_z = -ndc_scale_z;
|
||||||
|
ndc_offset_z = 1.0f - ndc_offset_z;
|
||||||
|
}
|
||||||
|
|
||||||
|
viewport_info_out.left = viewport_left;
|
||||||
|
viewport_info_out.top = viewport_top;
|
||||||
|
viewport_info_out.width = viewport_width;
|
||||||
|
viewport_info_out.height = viewport_height;
|
||||||
|
viewport_info_out.z_min = viewport_z_min;
|
||||||
|
viewport_info_out.z_max = viewport_z_max;
|
||||||
|
viewport_info_out.ndc_scale[0] = ndc_scale_x;
|
||||||
|
viewport_info_out.ndc_scale[1] = ndc_scale_y;
|
||||||
|
viewport_info_out.ndc_scale[2] = ndc_scale_z;
|
||||||
|
viewport_info_out.ndc_offset[0] = ndc_offset_x;
|
||||||
|
viewport_info_out.ndc_offset[1] = ndc_offset_y;
|
||||||
|
viewport_info_out.ndc_offset[2] = ndc_offset_z;
|
||||||
|
}
|
||||||
|
|
||||||
void GetScissor(const RegisterFile& regs, Scissor& scissor_out) {
|
void GetScissor(const RegisterFile& regs, Scissor& scissor_out) {
|
||||||
// FIXME(Triang3l): Screen scissor isn't applied here, but it seems to be
|
// FIXME(Triang3l): Screen scissor isn't applied here, but it seems to be
|
||||||
// unused on Xbox 360 Direct3D 9.
|
// unused on Xbox 360 Direct3D 9.
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,28 @@ namespace draw_util {
|
||||||
// for use with the top-left rasterization rule later.
|
// for use with the top-left rasterization rule later.
|
||||||
int32_t FloatToD3D11Fixed16p8(float f32);
|
int32_t FloatToD3D11Fixed16p8(float f32);
|
||||||
|
|
||||||
|
struct ViewportInfo {
|
||||||
|
// The returned viewport will always be in the positive quarter-plane for
|
||||||
|
// simplicity of clamping to the maximum size supported by the host, negative
|
||||||
|
// offset will be applied via ndc_offset.
|
||||||
|
float left;
|
||||||
|
float top;
|
||||||
|
float width;
|
||||||
|
float height;
|
||||||
|
float z_min;
|
||||||
|
float z_max;
|
||||||
|
float ndc_scale[3];
|
||||||
|
float ndc_offset[3];
|
||||||
|
};
|
||||||
|
// Converts the guest viewport (or fakes one if drawing without a viewport) to
|
||||||
|
// a viewport, plus values to multiply-add the returned position by, usable on
|
||||||
|
// host graphics APIs such as Direct3D 11+ and Vulkan, also forcing it to the
|
||||||
|
// Direct3D clip space with 0...W Z rather than -W...W.
|
||||||
|
void GetHostViewportInfo(const RegisterFile& regs, float pixel_size_x,
|
||||||
|
float pixel_size_y, bool origin_bottom_left,
|
||||||
|
float xy_max, bool allow_reverse_z,
|
||||||
|
ViewportInfo& viewport_info_out);
|
||||||
|
|
||||||
struct Scissor {
|
struct Scissor {
|
||||||
uint32_t left;
|
uint32_t left;
|
||||||
uint32_t top;
|
uint32_t top;
|
||||||
|
|
|
||||||
|
|
@ -1044,10 +1044,9 @@ void DxbcShaderTranslator::CompleteVertexOrDomainShader() {
|
||||||
DxbcOpEndIf();
|
DxbcOpEndIf();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply scale for drawing without a viewport, and also remap from OpenGL
|
// Apply scale for guest to host viewport and clip space conversion. Also, if
|
||||||
// Z clip space to Direct3D if needed. Also, if the vertex shader is
|
// the vertex shader is multipass, the NDC scale constant can be used to set
|
||||||
// multipass, the NDC scale constant can be used to set position to NaN to
|
// position to NaN to kill all primitives.
|
||||||
// kill all primitives.
|
|
||||||
system_constants_used_ |= 1ull << kSysConst_NDCScale_Index;
|
system_constants_used_ |= 1ull << kSysConst_NDCScale_Index;
|
||||||
DxbcOpMul(DxbcDest::R(system_temp_position_, 0b0111),
|
DxbcOpMul(DxbcDest::R(system_temp_position_, 0b0111),
|
||||||
DxbcSrc::R(system_temp_position_),
|
DxbcSrc::R(system_temp_position_),
|
||||||
|
|
@ -1056,16 +1055,7 @@ void DxbcShaderTranslator::CompleteVertexOrDomainShader() {
|
||||||
kSysConst_NDCScale_Vec,
|
kSysConst_NDCScale_Vec,
|
||||||
kSysConst_NDCScale_Comp * 0b010101 + 0b100100));
|
kSysConst_NDCScale_Comp * 0b010101 + 0b100100));
|
||||||
|
|
||||||
// Reverse Z (Z = W - Z) if the viewport depth is inverted.
|
// Apply offset (multiplied by W) used for the same purposes.
|
||||||
DxbcOpAnd(temp_x_dest, flags_src, DxbcSrc::LU(kSysFlag_ReverseZ));
|
|
||||||
DxbcOpIf(true, temp_x_src);
|
|
||||||
DxbcOpAdd(DxbcDest::R(system_temp_position_, 0b0100),
|
|
||||||
DxbcSrc::R(system_temp_position_, DxbcSrc::kWWWW),
|
|
||||||
-DxbcSrc::R(system_temp_position_, DxbcSrc::kZZZZ));
|
|
||||||
DxbcOpEndIf();
|
|
||||||
|
|
||||||
// Apply offset (multiplied by W) for drawing without a viewport and for half
|
|
||||||
// pixel offset.
|
|
||||||
system_constants_used_ |= 1ull << kSysConst_NDCOffset_Index;
|
system_constants_used_ |= 1ull << kSysConst_NDCOffset_Index;
|
||||||
DxbcOpMAd(DxbcDest::R(system_temp_position_, 0b0111),
|
DxbcOpMAd(DxbcDest::R(system_temp_position_, 0b0111),
|
||||||
DxbcSrc::CB(cbuffer_index_system_constants_,
|
DxbcSrc::CB(cbuffer_index_system_constants_,
|
||||||
|
|
|
||||||
|
|
@ -123,7 +123,6 @@ class DxbcShaderTranslator : public ShaderTranslator {
|
||||||
kSysFlag_UserClipPlane3_Shift,
|
kSysFlag_UserClipPlane3_Shift,
|
||||||
kSysFlag_UserClipPlane4_Shift,
|
kSysFlag_UserClipPlane4_Shift,
|
||||||
kSysFlag_UserClipPlane5_Shift,
|
kSysFlag_UserClipPlane5_Shift,
|
||||||
kSysFlag_ReverseZ_Shift,
|
|
||||||
kSysFlag_KillIfAnyVertexKilled_Shift,
|
kSysFlag_KillIfAnyVertexKilled_Shift,
|
||||||
kSysFlag_PrimitiveTwoFaced_Shift,
|
kSysFlag_PrimitiveTwoFaced_Shift,
|
||||||
kSysFlag_AlphaPassIfLess_Shift,
|
kSysFlag_AlphaPassIfLess_Shift,
|
||||||
|
|
@ -165,7 +164,6 @@ class DxbcShaderTranslator : public ShaderTranslator {
|
||||||
kSysFlag_UserClipPlane3 = 1u << kSysFlag_UserClipPlane3_Shift,
|
kSysFlag_UserClipPlane3 = 1u << kSysFlag_UserClipPlane3_Shift,
|
||||||
kSysFlag_UserClipPlane4 = 1u << kSysFlag_UserClipPlane4_Shift,
|
kSysFlag_UserClipPlane4 = 1u << kSysFlag_UserClipPlane4_Shift,
|
||||||
kSysFlag_UserClipPlane5 = 1u << kSysFlag_UserClipPlane5_Shift,
|
kSysFlag_UserClipPlane5 = 1u << kSysFlag_UserClipPlane5_Shift,
|
||||||
kSysFlag_ReverseZ = 1u << kSysFlag_ReverseZ_Shift,
|
|
||||||
kSysFlag_KillIfAnyVertexKilled = 1u << kSysFlag_KillIfAnyVertexKilled_Shift,
|
kSysFlag_KillIfAnyVertexKilled = 1u << kSysFlag_KillIfAnyVertexKilled_Shift,
|
||||||
kSysFlag_PrimitiveTwoFaced = 1u << kSysFlag_PrimitiveTwoFaced_Shift,
|
kSysFlag_PrimitiveTwoFaced = 1u << kSysFlag_PrimitiveTwoFaced_Shift,
|
||||||
kSysFlag_AlphaPassIfLess = 1u << kSysFlag_AlphaPassIfLess_Shift,
|
kSysFlag_AlphaPassIfLess = 1u << kSysFlag_AlphaPassIfLess_Shift,
|
||||||
|
|
@ -220,8 +218,7 @@ class DxbcShaderTranslator : public ShaderTranslator {
|
||||||
float point_size[2];
|
float point_size[2];
|
||||||
|
|
||||||
float point_size_min_max[2];
|
float point_size_min_max[2];
|
||||||
// Inverse scale of the host viewport (but not supersampled), with signs
|
// Screen point size * 2 (but not supersampled) -> size in NDC.
|
||||||
// pre-applied.
|
|
||||||
float point_screen_to_ndc[2];
|
float point_screen_to_ndc[2];
|
||||||
|
|
||||||
float user_clip_planes[6][4];
|
float user_clip_planes[6][4];
|
||||||
|
|
|
||||||
|
|
@ -136,7 +136,7 @@ X_STATUS GraphicsSystem::Setup(cpu::Processor* processor,
|
||||||
}));
|
}));
|
||||||
// As we run vblank interrupts the debugger must be able to suspend us.
|
// As we run vblank interrupts the debugger must be able to suspend us.
|
||||||
vsync_worker_thread_->set_can_debugger_suspend(true);
|
vsync_worker_thread_->set_can_debugger_suspend(true);
|
||||||
vsync_worker_thread_->set_name("GraphicsSystem Vsync");
|
vsync_worker_thread_->set_name("GPU VSync");
|
||||||
vsync_worker_thread_->Create();
|
vsync_worker_thread_->Create();
|
||||||
|
|
||||||
if (cvars::trace_gpu_stream) {
|
if (cvars::trace_gpu_stream) {
|
||||||
|
|
|
||||||
Binary file not shown.
|
|
@ -1,11 +1,11 @@
|
||||||
// generated from `xb buildhlsl`
|
// generated from `xb buildhlsl`
|
||||||
// source: primitive_point_list.gs.hlsl
|
// source: primitive_point_list.gs.hlsl
|
||||||
const uint8_t primitive_point_list_gs[] = {
|
const uint8_t primitive_point_list_gs[] = {
|
||||||
0x44, 0x58, 0x42, 0x43, 0x6F, 0x7A, 0xE0, 0xA0, 0x82, 0xF0, 0x8E, 0x77,
|
0x44, 0x58, 0x42, 0x43, 0x16, 0x84, 0x10, 0x1C, 0xE9, 0xAD, 0x76, 0xF9,
|
||||||
0x2B, 0x62, 0x44, 0x00, 0xA3, 0x34, 0x47, 0x40, 0x01, 0x00, 0x00, 0x00,
|
0x92, 0xF2, 0xD5, 0x65, 0x7C, 0x8A, 0x5F, 0xC5, 0x01, 0x00, 0x00, 0x00,
|
||||||
0x0C, 0x1E, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00,
|
0x20, 0x1E, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x34, 0x00, 0x00, 0x00,
|
||||||
0xD0, 0x0A, 0x00, 0x00, 0x28, 0x0D, 0x00, 0x00, 0xAC, 0x0F, 0x00, 0x00,
|
0xD0, 0x0A, 0x00, 0x00, 0x28, 0x0D, 0x00, 0x00, 0xAC, 0x0F, 0x00, 0x00,
|
||||||
0x70, 0x1D, 0x00, 0x00, 0x52, 0x44, 0x45, 0x46, 0x94, 0x0A, 0x00, 0x00,
|
0x84, 0x1D, 0x00, 0x00, 0x52, 0x44, 0x45, 0x46, 0x94, 0x0A, 0x00, 0x00,
|
||||||
0x01, 0x00, 0x00, 0x00, 0x78, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
|
0x01, 0x00, 0x00, 0x00, 0x78, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
|
||||||
0x3C, 0x00, 0x00, 0x00, 0x01, 0x05, 0x53, 0x47, 0x00, 0x05, 0x00, 0x00,
|
0x3C, 0x00, 0x00, 0x00, 0x01, 0x05, 0x53, 0x47, 0x00, 0x05, 0x00, 0x00,
|
||||||
0x6A, 0x0A, 0x00, 0x00, 0x13, 0x13, 0x44, 0x25, 0x3C, 0x00, 0x00, 0x00,
|
0x6A, 0x0A, 0x00, 0x00, 0x13, 0x13, 0x44, 0x25, 0x3C, 0x00, 0x00, 0x00,
|
||||||
|
|
@ -335,8 +335,8 @@ const uint8_t primitive_point_list_gs[] = {
|
||||||
0x54, 0x45, 0x58, 0x43, 0x4F, 0x4F, 0x52, 0x44, 0x00, 0x53, 0x56, 0x5F,
|
0x54, 0x45, 0x58, 0x43, 0x4F, 0x4F, 0x52, 0x44, 0x00, 0x53, 0x56, 0x5F,
|
||||||
0x50, 0x6F, 0x73, 0x69, 0x74, 0x69, 0x6F, 0x6E, 0x00, 0x53, 0x56, 0x5F,
|
0x50, 0x6F, 0x73, 0x69, 0x74, 0x69, 0x6F, 0x6E, 0x00, 0x53, 0x56, 0x5F,
|
||||||
0x43, 0x6C, 0x69, 0x70, 0x44, 0x69, 0x73, 0x74, 0x61, 0x6E, 0x63, 0x65,
|
0x43, 0x6C, 0x69, 0x70, 0x44, 0x69, 0x73, 0x74, 0x61, 0x6E, 0x63, 0x65,
|
||||||
0x00, 0xAB, 0xAB, 0xAB, 0x53, 0x48, 0x45, 0x58, 0xBC, 0x0D, 0x00, 0x00,
|
0x00, 0xAB, 0xAB, 0xAB, 0x53, 0x48, 0x45, 0x58, 0xD0, 0x0D, 0x00, 0x00,
|
||||||
0x51, 0x00, 0x02, 0x00, 0x6F, 0x03, 0x00, 0x00, 0x6A, 0x08, 0x00, 0x01,
|
0x51, 0x00, 0x02, 0x00, 0x74, 0x03, 0x00, 0x00, 0x6A, 0x08, 0x00, 0x01,
|
||||||
0x59, 0x00, 0x00, 0x07, 0x46, 0x8E, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00,
|
0x59, 0x00, 0x00, 0x07, 0x46, 0x8E, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
|
||||||
0x00, 0x00, 0x00, 0x00, 0x5F, 0x00, 0x00, 0x04, 0xF2, 0x10, 0x20, 0x00,
|
0x00, 0x00, 0x00, 0x00, 0x5F, 0x00, 0x00, 0x04, 0xF2, 0x10, 0x20, 0x00,
|
||||||
|
|
@ -369,7 +369,7 @@ const uint8_t primitive_point_list_gs[] = {
|
||||||
0x13, 0x00, 0x00, 0x00, 0x5F, 0x00, 0x00, 0x04, 0x32, 0x10, 0x20, 0x00,
|
0x13, 0x00, 0x00, 0x00, 0x5F, 0x00, 0x00, 0x04, 0x32, 0x10, 0x20, 0x00,
|
||||||
0x01, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x5F, 0x00, 0x00, 0x04,
|
0x01, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x5F, 0x00, 0x00, 0x04,
|
||||||
0x42, 0x10, 0x20, 0x00, 0x01, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00,
|
0x42, 0x10, 0x20, 0x00, 0x01, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00,
|
||||||
0x68, 0x00, 0x00, 0x02, 0x02, 0x00, 0x00, 0x00, 0x5D, 0x08, 0x00, 0x01,
|
0x68, 0x00, 0x00, 0x02, 0x03, 0x00, 0x00, 0x00, 0x5D, 0x08, 0x00, 0x01,
|
||||||
0x8F, 0x00, 0x00, 0x03, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00,
|
0x8F, 0x00, 0x00, 0x03, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
0x5C, 0x28, 0x00, 0x01, 0x65, 0x00, 0x00, 0x03, 0xF2, 0x20, 0x10, 0x00,
|
0x5C, 0x28, 0x00, 0x01, 0x65, 0x00, 0x00, 0x03, 0xF2, 0x20, 0x10, 0x00,
|
||||||
0x00, 0x00, 0x00, 0x00, 0x65, 0x00, 0x00, 0x03, 0xF2, 0x20, 0x10, 0x00,
|
0x00, 0x00, 0x00, 0x00, 0x65, 0x00, 0x00, 0x03, 0xF2, 0x20, 0x10, 0x00,
|
||||||
|
|
@ -426,113 +426,13 @@ const uint8_t primitive_point_list_gs[] = {
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
|
||||||
0x38, 0x00, 0x18, 0x08, 0x32, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00,
|
0x38, 0x00, 0x18, 0x08, 0x32, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
0x46, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF6, 0x1F, 0x20, 0x00,
|
0x46, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF6, 0x1F, 0x20, 0x00,
|
||||||
0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x38, 0x00, 0x78, 0x0A,
|
0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x36, 0x00, 0x38, 0x06,
|
||||||
0xF2, 0x00, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x46, 0x04, 0x10, 0x00,
|
0x72, 0x00, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x06, 0x01, 0x10, 0x80,
|
||||||
0x00, 0x00, 0x00, 0x00, 0x02, 0x40, 0x00, 0x00, 0x00, 0x00, 0x80, 0xBF,
|
0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x36, 0x00, 0x40, 0x05,
|
||||||
0x00, 0x00, 0x80, 0x3F, 0x00, 0x00, 0x80, 0x3F, 0x00, 0x00, 0x80, 0xBF,
|
0x82, 0x00, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x1A, 0x00, 0x10, 0x00,
|
||||||
0x00, 0x00, 0x78, 0x08, 0xF2, 0x00, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00,
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x08, 0xF2, 0x00, 0x10, 0x00,
|
||||||
0x46, 0x0E, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x46, 0x14, 0x20, 0x00,
|
0x02, 0x00, 0x00, 0x00, 0xC6, 0x09, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00,
|
||||||
0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06,
|
0x46, 0x14, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00,
|
||||||
0xF2, 0x20, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, 0x1E, 0x20, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06,
|
|
||||||
0xF2, 0x20, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x46, 0x1E, 0x20, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06,
|
|
||||||
0xF2, 0x20, 0x10, 0x00, 0x02, 0x00, 0x00, 0x00, 0x46, 0x1E, 0x20, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06,
|
|
||||||
0xF2, 0x20, 0x10, 0x00, 0x03, 0x00, 0x00, 0x00, 0x46, 0x1E, 0x20, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06,
|
|
||||||
0xF2, 0x20, 0x10, 0x00, 0x04, 0x00, 0x00, 0x00, 0x46, 0x1E, 0x20, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06,
|
|
||||||
0xF2, 0x20, 0x10, 0x00, 0x05, 0x00, 0x00, 0x00, 0x46, 0x1E, 0x20, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06,
|
|
||||||
0xF2, 0x20, 0x10, 0x00, 0x06, 0x00, 0x00, 0x00, 0x46, 0x1E, 0x20, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06,
|
|
||||||
0xF2, 0x20, 0x10, 0x00, 0x07, 0x00, 0x00, 0x00, 0x46, 0x1E, 0x20, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06,
|
|
||||||
0xF2, 0x20, 0x10, 0x00, 0x08, 0x00, 0x00, 0x00, 0x46, 0x1E, 0x20, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06,
|
|
||||||
0xF2, 0x20, 0x10, 0x00, 0x09, 0x00, 0x00, 0x00, 0x46, 0x1E, 0x20, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06,
|
|
||||||
0xF2, 0x20, 0x10, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x46, 0x1E, 0x20, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06,
|
|
||||||
0xF2, 0x20, 0x10, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x46, 0x1E, 0x20, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06,
|
|
||||||
0xF2, 0x20, 0x10, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x46, 0x1E, 0x20, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06,
|
|
||||||
0xF2, 0x20, 0x10, 0x00, 0x0D, 0x00, 0x00, 0x00, 0x46, 0x1E, 0x20, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x0D, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06,
|
|
||||||
0xF2, 0x20, 0x10, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x46, 0x1E, 0x20, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06,
|
|
||||||
0xF2, 0x20, 0x10, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x46, 0x1E, 0x20, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x08,
|
|
||||||
0x32, 0x20, 0x10, 0x00, 0x10, 0x00, 0x00, 0x00, 0x02, 0x40, 0x00, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x3F, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06, 0x42, 0x20, 0x10, 0x00,
|
|
||||||
0x10, 0x00, 0x00, 0x00, 0x2A, 0x10, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
0x10, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06, 0x32, 0x20, 0x10, 0x00,
|
|
||||||
0x11, 0x00, 0x00, 0x00, 0x46, 0x10, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
0x11, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x05, 0x32, 0x20, 0x10, 0x00,
|
|
||||||
0x12, 0x00, 0x00, 0x00, 0x46, 0x00, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00,
|
|
||||||
0x36, 0x00, 0x00, 0x06, 0xC2, 0x20, 0x10, 0x00, 0x12, 0x00, 0x00, 0x00,
|
|
||||||
0xA6, 0x1E, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00,
|
|
||||||
0x36, 0x00, 0x00, 0x06, 0xF2, 0x20, 0x10, 0x00, 0x13, 0x00, 0x00, 0x00,
|
|
||||||
0x46, 0x1E, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00,
|
|
||||||
0x36, 0x00, 0x00, 0x06, 0x32, 0x20, 0x10, 0x00, 0x14, 0x00, 0x00, 0x00,
|
|
||||||
0x46, 0x10, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00,
|
|
||||||
0x75, 0x00, 0x00, 0x03, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
0x00, 0x00, 0x60, 0x08, 0xC2, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
0x06, 0x04, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x14, 0x20, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06,
|
|
||||||
0xF2, 0x20, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, 0x1E, 0x20, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06,
|
|
||||||
0xF2, 0x20, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x46, 0x1E, 0x20, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06,
|
|
||||||
0xF2, 0x20, 0x10, 0x00, 0x02, 0x00, 0x00, 0x00, 0x46, 0x1E, 0x20, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06,
|
|
||||||
0xF2, 0x20, 0x10, 0x00, 0x03, 0x00, 0x00, 0x00, 0x46, 0x1E, 0x20, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06,
|
|
||||||
0xF2, 0x20, 0x10, 0x00, 0x04, 0x00, 0x00, 0x00, 0x46, 0x1E, 0x20, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06,
|
|
||||||
0xF2, 0x20, 0x10, 0x00, 0x05, 0x00, 0x00, 0x00, 0x46, 0x1E, 0x20, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06,
|
|
||||||
0xF2, 0x20, 0x10, 0x00, 0x06, 0x00, 0x00, 0x00, 0x46, 0x1E, 0x20, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06,
|
|
||||||
0xF2, 0x20, 0x10, 0x00, 0x07, 0x00, 0x00, 0x00, 0x46, 0x1E, 0x20, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06,
|
|
||||||
0xF2, 0x20, 0x10, 0x00, 0x08, 0x00, 0x00, 0x00, 0x46, 0x1E, 0x20, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06,
|
|
||||||
0xF2, 0x20, 0x10, 0x00, 0x09, 0x00, 0x00, 0x00, 0x46, 0x1E, 0x20, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06,
|
|
||||||
0xF2, 0x20, 0x10, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x46, 0x1E, 0x20, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06,
|
|
||||||
0xF2, 0x20, 0x10, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x46, 0x1E, 0x20, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06,
|
|
||||||
0xF2, 0x20, 0x10, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x46, 0x1E, 0x20, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06,
|
|
||||||
0xF2, 0x20, 0x10, 0x00, 0x0D, 0x00, 0x00, 0x00, 0x46, 0x1E, 0x20, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x0D, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06,
|
|
||||||
0xF2, 0x20, 0x10, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x46, 0x1E, 0x20, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06,
|
|
||||||
0xF2, 0x20, 0x10, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x46, 0x1E, 0x20, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x08,
|
|
||||||
0x32, 0x20, 0x10, 0x00, 0x10, 0x00, 0x00, 0x00, 0x02, 0x40, 0x00, 0x00,
|
|
||||||
0x00, 0x00, 0x80, 0x3F, 0x00, 0x00, 0x80, 0x3F, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06, 0x42, 0x20, 0x10, 0x00,
|
|
||||||
0x10, 0x00, 0x00, 0x00, 0x2A, 0x10, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
0x10, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06, 0x32, 0x20, 0x10, 0x00,
|
|
||||||
0x11, 0x00, 0x00, 0x00, 0x46, 0x10, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
0x11, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x05, 0x32, 0x20, 0x10, 0x00,
|
|
||||||
0x12, 0x00, 0x00, 0x00, 0xE6, 0x0A, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
0x36, 0x00, 0x00, 0x06, 0xC2, 0x20, 0x10, 0x00, 0x12, 0x00, 0x00, 0x00,
|
|
||||||
0xA6, 0x1E, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00,
|
|
||||||
0x36, 0x00, 0x00, 0x06, 0xF2, 0x20, 0x10, 0x00, 0x13, 0x00, 0x00, 0x00,
|
|
||||||
0x46, 0x1E, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00,
|
|
||||||
0x36, 0x00, 0x00, 0x06, 0x32, 0x20, 0x10, 0x00, 0x14, 0x00, 0x00, 0x00,
|
|
||||||
0x46, 0x10, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00,
|
|
||||||
0x75, 0x00, 0x00, 0x03, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
0x00, 0x00, 0x18, 0x09, 0x32, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
0x46, 0x00, 0x10, 0x80, 0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
0x46, 0x10, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00,
|
|
||||||
0x36, 0x00, 0x00, 0x06, 0xF2, 0x20, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00,
|
0x36, 0x00, 0x00, 0x06, 0xF2, 0x20, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
0x46, 0x1E, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
0x46, 0x1E, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
0x36, 0x00, 0x00, 0x06, 0xF2, 0x20, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00,
|
0x36, 0x00, 0x00, 0x06, 0xF2, 0x20, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00,
|
||||||
|
|
@ -573,7 +473,7 @@ const uint8_t primitive_point_list_gs[] = {
|
||||||
0x32, 0x20, 0x10, 0x00, 0x11, 0x00, 0x00, 0x00, 0x46, 0x10, 0x20, 0x00,
|
0x32, 0x20, 0x10, 0x00, 0x11, 0x00, 0x00, 0x00, 0x46, 0x10, 0x20, 0x00,
|
||||||
0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x05,
|
0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x05,
|
||||||
0x32, 0x20, 0x10, 0x00, 0x12, 0x00, 0x00, 0x00, 0x46, 0x00, 0x10, 0x00,
|
0x32, 0x20, 0x10, 0x00, 0x12, 0x00, 0x00, 0x00, 0x46, 0x00, 0x10, 0x00,
|
||||||
0x00, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06, 0xC2, 0x20, 0x10, 0x00,
|
0x02, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06, 0xC2, 0x20, 0x10, 0x00,
|
||||||
0x12, 0x00, 0x00, 0x00, 0xA6, 0x1E, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00,
|
0x12, 0x00, 0x00, 0x00, 0xA6, 0x1E, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
0x12, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06, 0xF2, 0x20, 0x10, 0x00,
|
0x12, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06, 0xF2, 0x20, 0x10, 0x00,
|
||||||
0x13, 0x00, 0x00, 0x00, 0x46, 0x1E, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00,
|
0x13, 0x00, 0x00, 0x00, 0x46, 0x1E, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
|
@ -613,6 +513,56 @@ const uint8_t primitive_point_list_gs[] = {
|
||||||
0x0E, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06, 0xF2, 0x20, 0x10, 0x00,
|
0x0E, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06, 0xF2, 0x20, 0x10, 0x00,
|
||||||
0x0F, 0x00, 0x00, 0x00, 0x46, 0x1E, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00,
|
0x0F, 0x00, 0x00, 0x00, 0x46, 0x1E, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
0x0F, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x08, 0x32, 0x20, 0x10, 0x00,
|
0x0F, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x08, 0x32, 0x20, 0x10, 0x00,
|
||||||
|
0x10, 0x00, 0x00, 0x00, 0x02, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x80, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x36, 0x00, 0x00, 0x06, 0x42, 0x20, 0x10, 0x00, 0x10, 0x00, 0x00, 0x00,
|
||||||
|
0x2A, 0x10, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00,
|
||||||
|
0x36, 0x00, 0x00, 0x06, 0x32, 0x20, 0x10, 0x00, 0x11, 0x00, 0x00, 0x00,
|
||||||
|
0x46, 0x10, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00,
|
||||||
|
0x36, 0x00, 0x00, 0x05, 0x32, 0x20, 0x10, 0x00, 0x12, 0x00, 0x00, 0x00,
|
||||||
|
0xE6, 0x0A, 0x10, 0x00, 0x02, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06,
|
||||||
|
0xC2, 0x20, 0x10, 0x00, 0x12, 0x00, 0x00, 0x00, 0xA6, 0x1E, 0x20, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06,
|
||||||
|
0xF2, 0x20, 0x10, 0x00, 0x13, 0x00, 0x00, 0x00, 0x46, 0x1E, 0x20, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06,
|
||||||
|
0x32, 0x20, 0x10, 0x00, 0x14, 0x00, 0x00, 0x00, 0x46, 0x10, 0x20, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x75, 0x00, 0x00, 0x03,
|
||||||
|
0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x08,
|
||||||
|
0xA2, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x04, 0x10, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x06, 0x14, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x12, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06, 0xF2, 0x20, 0x10, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x46, 0x1E, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06, 0xF2, 0x20, 0x10, 0x00,
|
||||||
|
0x01, 0x00, 0x00, 0x00, 0x46, 0x1E, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x01, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06, 0xF2, 0x20, 0x10, 0x00,
|
||||||
|
0x02, 0x00, 0x00, 0x00, 0x46, 0x1E, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x02, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06, 0xF2, 0x20, 0x10, 0x00,
|
||||||
|
0x03, 0x00, 0x00, 0x00, 0x46, 0x1E, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x03, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06, 0xF2, 0x20, 0x10, 0x00,
|
||||||
|
0x04, 0x00, 0x00, 0x00, 0x46, 0x1E, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x04, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06, 0xF2, 0x20, 0x10, 0x00,
|
||||||
|
0x05, 0x00, 0x00, 0x00, 0x46, 0x1E, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x05, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06, 0xF2, 0x20, 0x10, 0x00,
|
||||||
|
0x06, 0x00, 0x00, 0x00, 0x46, 0x1E, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x06, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06, 0xF2, 0x20, 0x10, 0x00,
|
||||||
|
0x07, 0x00, 0x00, 0x00, 0x46, 0x1E, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x07, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06, 0xF2, 0x20, 0x10, 0x00,
|
||||||
|
0x08, 0x00, 0x00, 0x00, 0x46, 0x1E, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x08, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06, 0xF2, 0x20, 0x10, 0x00,
|
||||||
|
0x09, 0x00, 0x00, 0x00, 0x46, 0x1E, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x09, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06, 0xF2, 0x20, 0x10, 0x00,
|
||||||
|
0x0A, 0x00, 0x00, 0x00, 0x46, 0x1E, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x0A, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06, 0xF2, 0x20, 0x10, 0x00,
|
||||||
|
0x0B, 0x00, 0x00, 0x00, 0x46, 0x1E, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x0B, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06, 0xF2, 0x20, 0x10, 0x00,
|
||||||
|
0x0C, 0x00, 0x00, 0x00, 0x46, 0x1E, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x0C, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06, 0xF2, 0x20, 0x10, 0x00,
|
||||||
|
0x0D, 0x00, 0x00, 0x00, 0x46, 0x1E, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x0D, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06, 0xF2, 0x20, 0x10, 0x00,
|
||||||
|
0x0E, 0x00, 0x00, 0x00, 0x46, 0x1E, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x0E, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06, 0xF2, 0x20, 0x10, 0x00,
|
||||||
|
0x0F, 0x00, 0x00, 0x00, 0x46, 0x1E, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x0F, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x08, 0x32, 0x20, 0x10, 0x00,
|
||||||
0x10, 0x00, 0x00, 0x00, 0x02, 0x40, 0x00, 0x00, 0x00, 0x00, 0x80, 0x3F,
|
0x10, 0x00, 0x00, 0x00, 0x02, 0x40, 0x00, 0x00, 0x00, 0x00, 0x80, 0x3F,
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
0x36, 0x00, 0x00, 0x06, 0x42, 0x20, 0x10, 0x00, 0x10, 0x00, 0x00, 0x00,
|
0x36, 0x00, 0x00, 0x06, 0x42, 0x20, 0x10, 0x00, 0x10, 0x00, 0x00, 0x00,
|
||||||
|
|
@ -620,26 +570,78 @@ const uint8_t primitive_point_list_gs[] = {
|
||||||
0x36, 0x00, 0x00, 0x06, 0x32, 0x20, 0x10, 0x00, 0x11, 0x00, 0x00, 0x00,
|
0x36, 0x00, 0x00, 0x06, 0x32, 0x20, 0x10, 0x00, 0x11, 0x00, 0x00, 0x00,
|
||||||
0x46, 0x10, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00,
|
0x46, 0x10, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00,
|
||||||
0x36, 0x00, 0x00, 0x05, 0x32, 0x20, 0x10, 0x00, 0x12, 0x00, 0x00, 0x00,
|
0x36, 0x00, 0x00, 0x05, 0x32, 0x20, 0x10, 0x00, 0x12, 0x00, 0x00, 0x00,
|
||||||
0xE6, 0x0A, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06,
|
0xD6, 0x05, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06,
|
||||||
0xC2, 0x20, 0x10, 0x00, 0x12, 0x00, 0x00, 0x00, 0xA6, 0x1E, 0x20, 0x00,
|
0xC2, 0x20, 0x10, 0x00, 0x12, 0x00, 0x00, 0x00, 0xA6, 0x1E, 0x20, 0x00,
|
||||||
0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06,
|
0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06,
|
||||||
0xF2, 0x20, 0x10, 0x00, 0x13, 0x00, 0x00, 0x00, 0x46, 0x1E, 0x20, 0x00,
|
0xF2, 0x20, 0x10, 0x00, 0x13, 0x00, 0x00, 0x00, 0x46, 0x1E, 0x20, 0x00,
|
||||||
0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06,
|
0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06,
|
||||||
0x32, 0x20, 0x10, 0x00, 0x14, 0x00, 0x00, 0x00, 0x46, 0x10, 0x20, 0x00,
|
0x32, 0x20, 0x10, 0x00, 0x14, 0x00, 0x00, 0x00, 0x46, 0x10, 0x20, 0x00,
|
||||||
0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x75, 0x00, 0x00, 0x03,
|
0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x75, 0x00, 0x00, 0x03,
|
||||||
0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x76, 0x00, 0x00, 0x03,
|
0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x36, 0x00, 0x20, 0x05,
|
||||||
0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x01,
|
0x42, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2A, 0x00, 0x10, 0x00,
|
||||||
0x53, 0x54, 0x41, 0x54, 0x94, 0x00, 0x00, 0x00, 0x74, 0x00, 0x00, 0x00,
|
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x08, 0x32, 0x00, 0x10, 0x00,
|
||||||
0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2B, 0x00, 0x00, 0x00,
|
0x00, 0x00, 0x00, 0x00, 0x86, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
0x0B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
|
0x46, 0x10, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00,
|
||||||
0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
0x36, 0x00, 0x00, 0x06, 0xF2, 0x20, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
|
0x46, 0x1E, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
0x36, 0x00, 0x00, 0x06, 0xF2, 0x20, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00,
|
||||||
|
0x46, 0x1E, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
|
||||||
|
0x36, 0x00, 0x00, 0x06, 0xF2, 0x20, 0x10, 0x00, 0x02, 0x00, 0x00, 0x00,
|
||||||
|
0x46, 0x1E, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
|
||||||
|
0x36, 0x00, 0x00, 0x06, 0xF2, 0x20, 0x10, 0x00, 0x03, 0x00, 0x00, 0x00,
|
||||||
|
0x46, 0x1E, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
|
||||||
|
0x36, 0x00, 0x00, 0x06, 0xF2, 0x20, 0x10, 0x00, 0x04, 0x00, 0x00, 0x00,
|
||||||
|
0x46, 0x1E, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
|
||||||
|
0x36, 0x00, 0x00, 0x06, 0xF2, 0x20, 0x10, 0x00, 0x05, 0x00, 0x00, 0x00,
|
||||||
|
0x46, 0x1E, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00,
|
||||||
|
0x36, 0x00, 0x00, 0x06, 0xF2, 0x20, 0x10, 0x00, 0x06, 0x00, 0x00, 0x00,
|
||||||
|
0x46, 0x1E, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
|
||||||
|
0x36, 0x00, 0x00, 0x06, 0xF2, 0x20, 0x10, 0x00, 0x07, 0x00, 0x00, 0x00,
|
||||||
|
0x46, 0x1E, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00,
|
||||||
|
0x36, 0x00, 0x00, 0x06, 0xF2, 0x20, 0x10, 0x00, 0x08, 0x00, 0x00, 0x00,
|
||||||
|
0x46, 0x1E, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
|
||||||
|
0x36, 0x00, 0x00, 0x06, 0xF2, 0x20, 0x10, 0x00, 0x09, 0x00, 0x00, 0x00,
|
||||||
|
0x46, 0x1E, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
|
||||||
|
0x36, 0x00, 0x00, 0x06, 0xF2, 0x20, 0x10, 0x00, 0x0A, 0x00, 0x00, 0x00,
|
||||||
|
0x46, 0x1E, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x00,
|
||||||
|
0x36, 0x00, 0x00, 0x06, 0xF2, 0x20, 0x10, 0x00, 0x0B, 0x00, 0x00, 0x00,
|
||||||
|
0x46, 0x1E, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00,
|
||||||
|
0x36, 0x00, 0x00, 0x06, 0xF2, 0x20, 0x10, 0x00, 0x0C, 0x00, 0x00, 0x00,
|
||||||
|
0x46, 0x1E, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00,
|
||||||
|
0x36, 0x00, 0x00, 0x06, 0xF2, 0x20, 0x10, 0x00, 0x0D, 0x00, 0x00, 0x00,
|
||||||
|
0x46, 0x1E, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0D, 0x00, 0x00, 0x00,
|
||||||
|
0x36, 0x00, 0x00, 0x06, 0xF2, 0x20, 0x10, 0x00, 0x0E, 0x00, 0x00, 0x00,
|
||||||
|
0x46, 0x1E, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00,
|
||||||
|
0x36, 0x00, 0x00, 0x06, 0xF2, 0x20, 0x10, 0x00, 0x0F, 0x00, 0x00, 0x00,
|
||||||
|
0x46, 0x1E, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00,
|
||||||
|
0x36, 0x00, 0x00, 0x08, 0x32, 0x20, 0x10, 0x00, 0x10, 0x00, 0x00, 0x00,
|
||||||
|
0x02, 0x40, 0x00, 0x00, 0x00, 0x00, 0x80, 0x3F, 0x00, 0x00, 0x80, 0x3F,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06,
|
||||||
|
0x42, 0x20, 0x10, 0x00, 0x10, 0x00, 0x00, 0x00, 0x2A, 0x10, 0x20, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06,
|
||||||
|
0x32, 0x20, 0x10, 0x00, 0x11, 0x00, 0x00, 0x00, 0x46, 0x10, 0x20, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x05,
|
||||||
|
0x32, 0x20, 0x10, 0x00, 0x12, 0x00, 0x00, 0x00, 0x46, 0x00, 0x10, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06, 0xC2, 0x20, 0x10, 0x00,
|
||||||
|
0x12, 0x00, 0x00, 0x00, 0xA6, 0x1E, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x12, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06, 0xF2, 0x20, 0x10, 0x00,
|
||||||
|
0x13, 0x00, 0x00, 0x00, 0x46, 0x1E, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x13, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x06, 0x32, 0x20, 0x10, 0x00,
|
||||||
|
0x14, 0x00, 0x00, 0x00, 0x46, 0x10, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x14, 0x00, 0x00, 0x00, 0x75, 0x00, 0x00, 0x03, 0x00, 0x00, 0x11, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x76, 0x00, 0x00, 0x03, 0x00, 0x00, 0x11, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x3E, 0x00, 0x00, 0x01, 0x53, 0x54, 0x41, 0x54,
|
||||||
|
0x94, 0x00, 0x00, 0x00, 0x76, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x2B, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
|
||||||
|
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
|
||||||
0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00,
|
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
0x01, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -130,7 +130,7 @@ dcl_input_siv v[1][18].xyzw, position
|
||||||
dcl_input v[1][19].xyzw
|
dcl_input v[1][19].xyzw
|
||||||
dcl_input v[1][20].xy
|
dcl_input v[1][20].xy
|
||||||
dcl_input v[1][20].z
|
dcl_input v[1][20].z
|
||||||
dcl_temps 2
|
dcl_temps 3
|
||||||
dcl_inputprimitive point
|
dcl_inputprimitive point
|
||||||
dcl_stream m0
|
dcl_stream m0
|
||||||
dcl_outputtopology trianglestrip
|
dcl_outputtopology trianglestrip
|
||||||
|
|
@ -170,58 +170,9 @@ max [precise(xy)] r0.xy, r0.xyxx, CB0[0][2].xxxx
|
||||||
min [precise(xy)] r0.xy, r0.xyxx, CB0[0][2].yyyy
|
min [precise(xy)] r0.xy, r0.xyxx, CB0[0][2].yyyy
|
||||||
mul [precise(xy)] r0.xy, r0.xyxx, CB0[0][2].zwzz
|
mul [precise(xy)] r0.xy, r0.xyxx, CB0[0][2].zwzz
|
||||||
mul [precise(xy)] r0.xy, r0.xyxx, v[0][18].wwww
|
mul [precise(xy)] r0.xy, r0.xyxx, v[0][18].wwww
|
||||||
mul [precise] r1.xyzw, r0.xyxy, l(-1.000000, 1.000000, 1.000000, -1.000000)
|
mov [precise(xyz)] r1.xyz, -r0.xxyx
|
||||||
add [precise] r1.xyzw, r1.xyzw, v[0][18].xyxy
|
mov [precise(w)] r1.w, r0.y
|
||||||
mov o0.xyzw, v[0][0].xyzw
|
add [precise] r2.xyzw, r1.xwyz, v[0][18].xyxy
|
||||||
mov o1.xyzw, v[0][1].xyzw
|
|
||||||
mov o2.xyzw, v[0][2].xyzw
|
|
||||||
mov o3.xyzw, v[0][3].xyzw
|
|
||||||
mov o4.xyzw, v[0][4].xyzw
|
|
||||||
mov o5.xyzw, v[0][5].xyzw
|
|
||||||
mov o6.xyzw, v[0][6].xyzw
|
|
||||||
mov o7.xyzw, v[0][7].xyzw
|
|
||||||
mov o8.xyzw, v[0][8].xyzw
|
|
||||||
mov o9.xyzw, v[0][9].xyzw
|
|
||||||
mov o10.xyzw, v[0][10].xyzw
|
|
||||||
mov o11.xyzw, v[0][11].xyzw
|
|
||||||
mov o12.xyzw, v[0][12].xyzw
|
|
||||||
mov o13.xyzw, v[0][13].xyzw
|
|
||||||
mov o14.xyzw, v[0][14].xyzw
|
|
||||||
mov o15.xyzw, v[0][15].xyzw
|
|
||||||
mov o16.xy, l(0,1.000000,0,0)
|
|
||||||
mov o16.z, v[0][16].z
|
|
||||||
mov o17.xy, v[0][17].xyxx
|
|
||||||
mov o18.xy, r1.xyxx
|
|
||||||
mov o18.zw, v[0][18].zzzw
|
|
||||||
mov o19.xyzw, v[0][19].xyzw
|
|
||||||
mov o20.xy, v[0][20].xyxx
|
|
||||||
emit_stream m0
|
|
||||||
add [precise(zw)] r0.zw, r0.xxxy, v[0][18].xxxy
|
|
||||||
mov o0.xyzw, v[0][0].xyzw
|
|
||||||
mov o1.xyzw, v[0][1].xyzw
|
|
||||||
mov o2.xyzw, v[0][2].xyzw
|
|
||||||
mov o3.xyzw, v[0][3].xyzw
|
|
||||||
mov o4.xyzw, v[0][4].xyzw
|
|
||||||
mov o5.xyzw, v[0][5].xyzw
|
|
||||||
mov o6.xyzw, v[0][6].xyzw
|
|
||||||
mov o7.xyzw, v[0][7].xyzw
|
|
||||||
mov o8.xyzw, v[0][8].xyzw
|
|
||||||
mov o9.xyzw, v[0][9].xyzw
|
|
||||||
mov o10.xyzw, v[0][10].xyzw
|
|
||||||
mov o11.xyzw, v[0][11].xyzw
|
|
||||||
mov o12.xyzw, v[0][12].xyzw
|
|
||||||
mov o13.xyzw, v[0][13].xyzw
|
|
||||||
mov o14.xyzw, v[0][14].xyzw
|
|
||||||
mov o15.xyzw, v[0][15].xyzw
|
|
||||||
mov o16.xy, l(1.000000,1.000000,0,0)
|
|
||||||
mov o16.z, v[0][16].z
|
|
||||||
mov o17.xy, v[0][17].xyxx
|
|
||||||
mov o18.xy, r0.zwzz
|
|
||||||
mov o18.zw, v[0][18].zzzw
|
|
||||||
mov o19.xyzw, v[0][19].xyzw
|
|
||||||
mov o20.xy, v[0][20].xyxx
|
|
||||||
emit_stream m0
|
|
||||||
add [precise(xy)] r0.xy, -r0.xyxx, v[0][18].xyxx
|
|
||||||
mov o0.xyzw, v[0][0].xyzw
|
mov o0.xyzw, v[0][0].xyzw
|
||||||
mov o1.xyzw, v[0][1].xyzw
|
mov o1.xyzw, v[0][1].xyzw
|
||||||
mov o2.xyzw, v[0][2].xyzw
|
mov o2.xyzw, v[0][2].xyzw
|
||||||
|
|
@ -241,7 +192,7 @@ mov o15.xyzw, v[0][15].xyzw
|
||||||
mov o16.xy, l(0,0,0,0)
|
mov o16.xy, l(0,0,0,0)
|
||||||
mov o16.z, v[0][16].z
|
mov o16.z, v[0][16].z
|
||||||
mov o17.xy, v[0][17].xyxx
|
mov o17.xy, v[0][17].xyxx
|
||||||
mov o18.xy, r0.xyxx
|
mov o18.xy, r2.xyxx
|
||||||
mov o18.zw, v[0][18].zzzw
|
mov o18.zw, v[0][18].zzzw
|
||||||
mov o19.xyzw, v[0][19].xyzw
|
mov o19.xyzw, v[0][19].xyzw
|
||||||
mov o20.xy, v[0][20].xyxx
|
mov o20.xy, v[0][20].xyxx
|
||||||
|
|
@ -262,14 +213,65 @@ mov o12.xyzw, v[0][12].xyzw
|
||||||
mov o13.xyzw, v[0][13].xyzw
|
mov o13.xyzw, v[0][13].xyzw
|
||||||
mov o14.xyzw, v[0][14].xyzw
|
mov o14.xyzw, v[0][14].xyzw
|
||||||
mov o15.xyzw, v[0][15].xyzw
|
mov o15.xyzw, v[0][15].xyzw
|
||||||
|
mov o16.xy, l(0,1.000000,0,0)
|
||||||
|
mov o16.z, v[0][16].z
|
||||||
|
mov o17.xy, v[0][17].xyxx
|
||||||
|
mov o18.xy, r2.zwzz
|
||||||
|
mov o18.zw, v[0][18].zzzw
|
||||||
|
mov o19.xyzw, v[0][19].xyzw
|
||||||
|
mov o20.xy, v[0][20].xyxx
|
||||||
|
emit_stream m0
|
||||||
|
add [precise(yw)] r0.yw, r0.xxxy, v[0][18].xxxy
|
||||||
|
mov o0.xyzw, v[0][0].xyzw
|
||||||
|
mov o1.xyzw, v[0][1].xyzw
|
||||||
|
mov o2.xyzw, v[0][2].xyzw
|
||||||
|
mov o3.xyzw, v[0][3].xyzw
|
||||||
|
mov o4.xyzw, v[0][4].xyzw
|
||||||
|
mov o5.xyzw, v[0][5].xyzw
|
||||||
|
mov o6.xyzw, v[0][6].xyzw
|
||||||
|
mov o7.xyzw, v[0][7].xyzw
|
||||||
|
mov o8.xyzw, v[0][8].xyzw
|
||||||
|
mov o9.xyzw, v[0][9].xyzw
|
||||||
|
mov o10.xyzw, v[0][10].xyzw
|
||||||
|
mov o11.xyzw, v[0][11].xyzw
|
||||||
|
mov o12.xyzw, v[0][12].xyzw
|
||||||
|
mov o13.xyzw, v[0][13].xyzw
|
||||||
|
mov o14.xyzw, v[0][14].xyzw
|
||||||
|
mov o15.xyzw, v[0][15].xyzw
|
||||||
mov o16.xy, l(1.000000,0,0,0)
|
mov o16.xy, l(1.000000,0,0,0)
|
||||||
mov o16.z, v[0][16].z
|
mov o16.z, v[0][16].z
|
||||||
mov o17.xy, v[0][17].xyxx
|
mov o17.xy, v[0][17].xyxx
|
||||||
mov o18.xy, r1.zwzz
|
mov o18.xy, r0.ywyy
|
||||||
|
mov o18.zw, v[0][18].zzzw
|
||||||
|
mov o19.xyzw, v[0][19].xyzw
|
||||||
|
mov o20.xy, v[0][20].xyxx
|
||||||
|
emit_stream m0
|
||||||
|
mov [precise(z)] r0.z, r1.z
|
||||||
|
add [precise(xy)] r0.xy, r0.xzxx, v[0][18].xyxx
|
||||||
|
mov o0.xyzw, v[0][0].xyzw
|
||||||
|
mov o1.xyzw, v[0][1].xyzw
|
||||||
|
mov o2.xyzw, v[0][2].xyzw
|
||||||
|
mov o3.xyzw, v[0][3].xyzw
|
||||||
|
mov o4.xyzw, v[0][4].xyzw
|
||||||
|
mov o5.xyzw, v[0][5].xyzw
|
||||||
|
mov o6.xyzw, v[0][6].xyzw
|
||||||
|
mov o7.xyzw, v[0][7].xyzw
|
||||||
|
mov o8.xyzw, v[0][8].xyzw
|
||||||
|
mov o9.xyzw, v[0][9].xyzw
|
||||||
|
mov o10.xyzw, v[0][10].xyzw
|
||||||
|
mov o11.xyzw, v[0][11].xyzw
|
||||||
|
mov o12.xyzw, v[0][12].xyzw
|
||||||
|
mov o13.xyzw, v[0][13].xyzw
|
||||||
|
mov o14.xyzw, v[0][14].xyzw
|
||||||
|
mov o15.xyzw, v[0][15].xyzw
|
||||||
|
mov o16.xy, l(1.000000,1.000000,0,0)
|
||||||
|
mov o16.z, v[0][16].z
|
||||||
|
mov o17.xy, v[0][17].xyxx
|
||||||
|
mov o18.xy, r0.xyxx
|
||||||
mov o18.zw, v[0][18].zzzw
|
mov o18.zw, v[0][18].zzzw
|
||||||
mov o19.xyzw, v[0][19].xyzw
|
mov o19.xyzw, v[0][19].xyzw
|
||||||
mov o20.xy, v[0][20].xyxx
|
mov o20.xy, v[0][20].xyxx
|
||||||
emit_stream m0
|
emit_stream m0
|
||||||
cut_stream m0
|
cut_stream m0
|
||||||
ret
|
ret
|
||||||
// Approximately 116 instruction slots used
|
// Approximately 118 instruction slots used
|
||||||
|
|
|
||||||
|
|
@ -26,19 +26,22 @@ void main(point XeVertexPreGS xe_in[1],
|
||||||
clamp(point_size, xe_point_size_min_max.xx, xe_point_size_min_max.yy) *
|
clamp(point_size, xe_point_size_min_max.xx, xe_point_size_min_max.yy) *
|
||||||
xe_point_screen_to_ndc * xe_in[0].post_gs.position.w;
|
xe_point_screen_to_ndc * xe_in[0].post_gs.position.w;
|
||||||
|
|
||||||
xe_out.point_params.xy = float2(0.0, 1.0);
|
|
||||||
xe_out.position.xy =
|
|
||||||
xe_in[0].post_gs.position.xy + float2(-1.0, 1.0) * point_size;
|
|
||||||
xe_stream.Append(xe_out);
|
|
||||||
xe_out.point_params.xy = float2(1.0, 1.0);
|
|
||||||
xe_out.position.xy = xe_in[0].post_gs.position.xy + point_size;
|
|
||||||
xe_stream.Append(xe_out);
|
|
||||||
xe_out.point_params.xy = float2(0.0, 0.0);
|
xe_out.point_params.xy = float2(0.0, 0.0);
|
||||||
|
// TODO(Triang3l): On Vulkan, sign of Y needs to inverted because of
|
||||||
|
// upper-left origin.
|
||||||
|
// TODO(Triang3l): Investigate the true signs of point sprites.
|
||||||
|
xe_out.position.xy =
|
||||||
|
xe_in[0].post_gs.position.xy + float2(-point_size.x, point_size.y);
|
||||||
|
xe_stream.Append(xe_out);
|
||||||
|
xe_out.point_params.xy = float2(0.0, 1.0);
|
||||||
xe_out.position.xy = xe_in[0].post_gs.position.xy - point_size;
|
xe_out.position.xy = xe_in[0].post_gs.position.xy - point_size;
|
||||||
xe_stream.Append(xe_out);
|
xe_stream.Append(xe_out);
|
||||||
xe_out.point_params.xy = float2(1.0, 0.0);
|
xe_out.point_params.xy = float2(1.0, 0.0);
|
||||||
|
xe_out.position.xy = xe_in[0].post_gs.position.xy + point_size;
|
||||||
|
xe_stream.Append(xe_out);
|
||||||
|
xe_out.point_params.xy = float2(1.0, 1.0);
|
||||||
xe_out.position.xy =
|
xe_out.position.xy =
|
||||||
xe_in[0].post_gs.position.xy + float2(1.0, -1.0) * point_size;
|
xe_in[0].post_gs.position.xy + float2(point_size.x, -point_size.y);
|
||||||
xe_stream.Append(xe_out);
|
xe_stream.Append(xe_out);
|
||||||
xe_stream.RestartStrip();
|
xe_stream.RestartStrip();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -245,7 +245,7 @@ object_ref<XThread> KernelState::LaunchModule(object_ref<UserModule> module) {
|
||||||
module->entry_point(), 0, X_CREATE_SUSPENDED, true, true));
|
module->entry_point(), 0, X_CREATE_SUSPENDED, true, true));
|
||||||
|
|
||||||
// We know this is the 'main thread'.
|
// We know this is the 'main thread'.
|
||||||
thread->set_name(fmt::format("Main XThread{:08X}", thread->handle()));
|
thread->set_name("Main XThread");
|
||||||
|
|
||||||
X_STATUS result = thread->Create();
|
X_STATUS result = thread->Create();
|
||||||
if (XFAILED(result)) {
|
if (XFAILED(result)) {
|
||||||
|
|
@ -340,7 +340,7 @@ void KernelState::SetExecutableModule(object_ref<UserModule> module) {
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}));
|
}));
|
||||||
dispatch_thread_->set_name("Kernel Dispatch Thread");
|
dispatch_thread_->set_name("Kernel Dispatch");
|
||||||
dispatch_thread_->Create();
|
dispatch_thread_->Create();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "xenia/base/logging.h"
|
#include "xenia/base/logging.h"
|
||||||
|
#include "xenia/base/math.h"
|
||||||
#include "xenia/kernel/kernel_state.h"
|
#include "xenia/kernel/kernel_state.h"
|
||||||
#include "xenia/kernel/util/shim_utils.h"
|
#include "xenia/kernel/util/shim_utils.h"
|
||||||
#include "xenia/kernel/xam/xam_private.h"
|
#include "xenia/kernel/xam/xam_private.h"
|
||||||
|
|
@ -45,13 +46,11 @@ struct DeviceInfo {
|
||||||
// they incorrectly only look at the lower 32-bits of free_bytes,
|
// they incorrectly only look at the lower 32-bits of free_bytes,
|
||||||
// when it is a 64-bit value. Which means any size above ~4GB
|
// when it is a 64-bit value. Which means any size above ~4GB
|
||||||
// will not be recognized properly.
|
// will not be recognized properly.
|
||||||
//
|
|
||||||
// NOTE(randprint): you can use 120 GB and 42 GB 'fullness'
|
|
||||||
// with the proper deviceID feel free to change at your discression
|
|
||||||
#define ONE_GB (1024ull * 1024ull * 1024ull)
|
#define ONE_GB (1024ull * 1024ull * 1024ull)
|
||||||
static const DeviceInfo dummy_device_info_ = {
|
static const DeviceInfo dummy_device_info_ = {
|
||||||
0x00000001, 1, // found from debugging / reversing UE3 engine titles
|
0x00000001, // id
|
||||||
4ull * ONE_GB, // 4GB
|
1, // 1=HDD
|
||||||
|
20ull * ONE_GB, // 20GB
|
||||||
3ull * ONE_GB, // 3GB, so it looks a little used.
|
3ull * ONE_GB, // 3GB, so it looks a little used.
|
||||||
u"Dummy HDD",
|
u"Dummy HDD",
|
||||||
};
|
};
|
||||||
|
|
@ -117,7 +116,7 @@ DECLARE_XAM_EXPORT1(XamContentGetDeviceState, kContent, kStub);
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
xe::be<uint32_t> device_id;
|
xe::be<uint32_t> device_id;
|
||||||
xe::be<uint32_t> unknown;
|
xe::be<uint32_t> device_type;
|
||||||
xe::be<uint64_t> total_bytes;
|
xe::be<uint64_t> total_bytes;
|
||||||
xe::be<uint64_t> free_bytes;
|
xe::be<uint64_t> free_bytes;
|
||||||
xe::be<uint16_t> name[28];
|
xe::be<uint16_t> name[28];
|
||||||
|
|
@ -134,7 +133,7 @@ dword_result_t XamContentGetDeviceData(
|
||||||
device_data.Zero();
|
device_data.Zero();
|
||||||
const auto& device_info = dummy_device_info_;
|
const auto& device_info = dummy_device_info_;
|
||||||
device_data->device_id = device_info.device_id;
|
device_data->device_id = device_info.device_id;
|
||||||
device_data->unknown = device_id & 0xFFFF; // Fake it.
|
device_data->device_type = device_info.device_type;
|
||||||
device_data->total_bytes = device_info.total_bytes;
|
device_data->total_bytes = device_info.total_bytes;
|
||||||
device_data->free_bytes = device_info.free_bytes;
|
device_data->free_bytes = device_info.free_bytes;
|
||||||
xe::store_and_swap<std::u16string>(&device_data->name[0], device_info.name);
|
xe::store_and_swap<std::u16string>(&device_data->name[0], device_info.name);
|
||||||
|
|
@ -223,7 +222,8 @@ dword_result_t XamContentCreateDeviceEnumerator(dword_t content_type,
|
||||||
xe::store_and_swap(&dev->device_type, dummy_device_info_.device_type);
|
xe::store_and_swap(&dev->device_type, dummy_device_info_.device_type);
|
||||||
xe::store_and_swap(&dev->total_bytes, dummy_device_info_.total_bytes);
|
xe::store_and_swap(&dev->total_bytes, dummy_device_info_.total_bytes);
|
||||||
xe::store_and_swap(&dev->free_bytes, dummy_device_info_.free_bytes);
|
xe::store_and_swap(&dev->free_bytes, dummy_device_info_.free_bytes);
|
||||||
xe::copy_and_swap(dev->name, dummy_device_info_.name, 28);
|
xe::copy_and_swap(dev->name, dummy_device_info_.name,
|
||||||
|
xe::countof(dev->name));
|
||||||
}
|
}
|
||||||
|
|
||||||
*handle_out = e->handle();
|
*handle_out = e->handle();
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "xenia/base/logging.h"
|
#include "xenia/base/logging.h"
|
||||||
|
#include "xenia/base/string_util.h"
|
||||||
#include "xenia/kernel/kernel_state.h"
|
#include "xenia/kernel/kernel_state.h"
|
||||||
#include "xenia/kernel/user_module.h"
|
#include "xenia/kernel/user_module.h"
|
||||||
#include "xenia/kernel/util/shim_utils.h"
|
#include "xenia/kernel/util/shim_utils.h"
|
||||||
|
|
@ -74,15 +75,15 @@ static SYSTEMTIME xeGetLocalSystemTime(uint64_t filetime) {
|
||||||
|
|
||||||
void XamFormatDateString(dword_t unk, qword_t filetime, lpvoid_t output_buffer,
|
void XamFormatDateString(dword_t unk, qword_t filetime, lpvoid_t output_buffer,
|
||||||
dword_t output_count) {
|
dword_t output_count) {
|
||||||
std::memset(output_buffer, 0, output_count * 2);
|
std::memset(output_buffer, 0, output_count * sizeof(char16_t));
|
||||||
|
|
||||||
// TODO: implement this for other platforms
|
// TODO: implement this for other platforms
|
||||||
#if XE_PLATFORM_WIN32
|
#if XE_PLATFORM_WIN32
|
||||||
auto st = xeGetLocalSystemTime(filetime);
|
auto st = xeGetLocalSystemTime(filetime);
|
||||||
// TODO: format this depending on users locale?
|
// TODO: format this depending on users locale?
|
||||||
auto str = fmt::format(u"{:02d}/{:02d}/{}", st.wMonth, st.wDay, st.wYear);
|
auto str = fmt::format(u"{:02d}/{:02d}/{}", st.wMonth, st.wDay, st.wYear);
|
||||||
auto copy_length = std::min(size_t(output_count), str.size()) * 2;
|
xe::string_util::copy_and_swap_truncating(output_buffer.as<char16_t*>(), str,
|
||||||
xe::copy_and_swap(output_buffer.as<char16_t*>(), str.c_str(), copy_length);
|
output_count);
|
||||||
#else
|
#else
|
||||||
assert_always();
|
assert_always();
|
||||||
#endif
|
#endif
|
||||||
|
|
@ -91,15 +92,15 @@ DECLARE_XAM_EXPORT1(XamFormatDateString, kNone, kImplemented);
|
||||||
|
|
||||||
void XamFormatTimeString(dword_t unk, qword_t filetime, lpvoid_t output_buffer,
|
void XamFormatTimeString(dword_t unk, qword_t filetime, lpvoid_t output_buffer,
|
||||||
dword_t output_count) {
|
dword_t output_count) {
|
||||||
std::memset(output_buffer, 0, output_count * 2);
|
std::memset(output_buffer, 0, output_count * sizeof(char16_t));
|
||||||
|
|
||||||
// TODO: implement this for other platforms
|
// TODO: implement this for other platforms
|
||||||
#if XE_PLATFORM_WIN32
|
#if XE_PLATFORM_WIN32
|
||||||
auto st = xeGetLocalSystemTime(filetime);
|
auto st = xeGetLocalSystemTime(filetime);
|
||||||
// TODO: format this depending on users locale?
|
// TODO: format this depending on users locale?
|
||||||
auto str = fmt::format(u"{:02d}:{:02d}", st.wHour, st.wMinute);
|
auto str = fmt::format(u"{:02d}:{:02d}", st.wHour, st.wMinute);
|
||||||
auto copy_count = std::min(size_t(output_count), str.size());
|
xe::string_util::copy_and_swap_truncating(output_buffer.as<char16_t*>(), str,
|
||||||
xe::copy_and_swap(output_buffer.as<char16_t*>(), str.c_str(), copy_count);
|
output_count);
|
||||||
#else
|
#else
|
||||||
assert_always();
|
assert_always();
|
||||||
#endif
|
#endif
|
||||||
|
|
@ -113,7 +114,7 @@ dword_result_t keXamBuildResourceLocator(uint64_t module,
|
||||||
uint32_t buffer_count) {
|
uint32_t buffer_count) {
|
||||||
std::u16string path;
|
std::u16string path;
|
||||||
if (!module) {
|
if (!module) {
|
||||||
path = fmt::format(u"file://media:/{0}.xzp#{0}", container, resource);
|
path = fmt::format(u"file://media:/{}.xzp#{}", container, resource);
|
||||||
XELOGD(
|
XELOGD(
|
||||||
"XamBuildResourceLocator({0}) returning locator to local file {0}.xzp",
|
"XamBuildResourceLocator({0}) returning locator to local file {0}.xzp",
|
||||||
xe::to_utf8(container));
|
xe::to_utf8(container));
|
||||||
|
|
@ -121,8 +122,8 @@ dword_result_t keXamBuildResourceLocator(uint64_t module,
|
||||||
path = fmt::format(u"section://{:X},{}#{}", (uint32_t)module, container,
|
path = fmt::format(u"section://{:X},{}#{}", (uint32_t)module, container,
|
||||||
resource);
|
resource);
|
||||||
}
|
}
|
||||||
auto copy_count = std::min(size_t(buffer_count), path.size());
|
xe::string_util::copy_and_swap_truncating(buffer_ptr.as<char16_t*>(), path,
|
||||||
xe::copy_and_swap(buffer_ptr.as<char16_t*>(), path.c_str(), copy_count);
|
buffer_count);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@
|
||||||
|
|
||||||
#include "third_party/imgui/imgui.h"
|
#include "third_party/imgui/imgui.h"
|
||||||
#include "xenia/base/logging.h"
|
#include "xenia/base/logging.h"
|
||||||
|
#include "xenia/base/string_util.h"
|
||||||
#include "xenia/emulator.h"
|
#include "xenia/emulator.h"
|
||||||
#include "xenia/kernel/kernel_flags.h"
|
#include "xenia/kernel/kernel_flags.h"
|
||||||
#include "xenia/kernel/kernel_state.h"
|
#include "xenia/kernel/kernel_state.h"
|
||||||
|
|
@ -188,8 +189,8 @@ class KeyboardInputDialog : public xe::ui::ImGuiDialog {
|
||||||
*out_text_ = default_text;
|
*out_text_ = default_text;
|
||||||
}
|
}
|
||||||
text_buffer_.resize(max_length);
|
text_buffer_.resize(max_length);
|
||||||
std::strncpy(text_buffer_.data(), default_text_.c_str(),
|
xe::string_util::copy_truncating(text_buffer_.data(), default_text_,
|
||||||
std::min(text_buffer_.size() - 1, default_text_.size()));
|
text_buffer_.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
void OnDraw(ImGuiIO& io) override {
|
void OnDraw(ImGuiIO& io) override {
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,8 @@
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
|
||||||
#include "xenia/base/logging.h"
|
#include "xenia/base/logging.h"
|
||||||
|
#include "xenia/base/math.h"
|
||||||
|
#include "xenia/base/string_util.h"
|
||||||
#include "xenia/kernel/kernel_state.h"
|
#include "xenia/kernel/kernel_state.h"
|
||||||
#include "xenia/kernel/util/shim_utils.h"
|
#include "xenia/kernel/util/shim_utils.h"
|
||||||
#include "xenia/kernel/xam/xam_private.h"
|
#include "xenia/kernel/xam/xam_private.h"
|
||||||
|
|
@ -91,7 +93,8 @@ X_HRESULT_result_t XamUserGetSigninInfo(dword_t user_index, dword_t flags,
|
||||||
const auto& user_profile = kernel_state()->user_profile();
|
const auto& user_profile = kernel_state()->user_profile();
|
||||||
info->xuid = user_profile->xuid();
|
info->xuid = user_profile->xuid();
|
||||||
info->signin_state = user_profile->signin_state();
|
info->signin_state = user_profile->signin_state();
|
||||||
std::strncpy(info->name, user_profile->name().data(), 15);
|
xe::string_util::copy_truncating(info->name, user_profile->name(),
|
||||||
|
xe::countof(info->name));
|
||||||
return X_E_SUCCESS;
|
return X_E_SUCCESS;
|
||||||
}
|
}
|
||||||
DECLARE_XAM_EXPORT1(XamUserGetSigninInfo, kUserProfiles, kImplemented);
|
DECLARE_XAM_EXPORT1(XamUserGetSigninInfo, kUserProfiles, kImplemented);
|
||||||
|
|
@ -110,10 +113,8 @@ dword_result_t XamUserGetName(dword_t user_index, lpstring_t buffer,
|
||||||
const auto& user_name = user_profile->name();
|
const auto& user_name = user_profile->name();
|
||||||
|
|
||||||
// Real XAM will only copy a maximum of 15 characters out.
|
// Real XAM will only copy a maximum of 15 characters out.
|
||||||
size_t copy_length = std::min(
|
xe::string_util::copy_truncating(buffer, user_name,
|
||||||
{size_t(15), user_name.size(), static_cast<size_t>(buffer_len) - 1});
|
std::min(buffer_len.value(), uint32_t(15)));
|
||||||
std::memcpy(buffer, user_name.data(), copy_length);
|
|
||||||
buffer[copy_length] = '\0';
|
|
||||||
return X_ERROR_SUCCESS;
|
return X_ERROR_SUCCESS;
|
||||||
}
|
}
|
||||||
DECLARE_XAM_EXPORT1(XamUserGetName, kUserProfiles, kImplemented);
|
DECLARE_XAM_EXPORT1(XamUserGetName, kUserProfiles, kImplemented);
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
/**
|
/**
|
||||||
******************************************************************************
|
******************************************************************************
|
||||||
* Xenia : Xbox 360 Emulator Research Project *
|
* Xenia : Xbox 360 Emulator Research Project *
|
||||||
******************************************************************************
|
******************************************************************************
|
||||||
|
|
@ -41,10 +41,23 @@ struct CreateOptions {
|
||||||
|
|
||||||
static bool IsValidPath(const std::string_view s, bool is_pattern) {
|
static bool IsValidPath(const std::string_view s, bool is_pattern) {
|
||||||
// TODO(gibbed): validate path components individually
|
// TODO(gibbed): validate path components individually
|
||||||
|
bool got_asterisk = false;
|
||||||
for (const auto& c : s) {
|
for (const auto& c : s) {
|
||||||
if (c <= 31 || c >= 127) {
|
if (c <= 31 || c >= 127) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (got_asterisk) {
|
||||||
|
// * must be followed by a . (*.)
|
||||||
|
//
|
||||||
|
// Viva Piñata: Party Animals (4D530819) has a bug in its game code where
|
||||||
|
// it attempts to FindFirstFile() with filters of "Game:\\*_X3.rkv",
|
||||||
|
// "Game:\\m*_X3.rkv", and "Game:\\w*_X3.rkv" and will infinite loop if
|
||||||
|
// the path filter is allowed.
|
||||||
|
if (c != '.') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
got_asterisk = false;
|
||||||
|
}
|
||||||
switch (c) {
|
switch (c) {
|
||||||
case '"':
|
case '"':
|
||||||
// case '*':
|
// case '*':
|
||||||
|
|
@ -59,12 +72,20 @@ static bool IsValidPath(const std::string_view s, bool is_pattern) {
|
||||||
case '|': {
|
case '|': {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
case '*':
|
case '*': {
|
||||||
|
// Pattern-specific (for NtQueryDirectoryFile)
|
||||||
|
if (!is_pattern) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
got_asterisk = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
case '?': {
|
case '?': {
|
||||||
// Pattern-specific (for NtQueryDirectoryFile)
|
// Pattern-specific (for NtQueryDirectoryFile)
|
||||||
if (!is_pattern) {
|
if (!is_pattern) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
default: {
|
default: {
|
||||||
break;
|
break;
|
||||||
|
|
@ -425,7 +446,7 @@ dword_result_t NtQueryDirectoryFile(
|
||||||
|
|
||||||
// Enforce that the path is ASCII.
|
// Enforce that the path is ASCII.
|
||||||
if (!IsValidPath(name, true)) {
|
if (!IsValidPath(name, true)) {
|
||||||
return X_STATUS_OBJECT_NAME_INVALID;
|
return X_STATUS_INVALID_PARAMETER;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (file) {
|
if (file) {
|
||||||
|
|
|
||||||
|
|
@ -156,11 +156,14 @@ XboxkrnlModule::XboxkrnlModule(Emulator* emulator, KernelState* kernel_state)
|
||||||
//
|
//
|
||||||
// aomega08 says the value is 0x02000817, bit 27: debug mode on.
|
// aomega08 says the value is 0x02000817, bit 27: debug mode on.
|
||||||
// When that is set, though, allocs crash in weird ways.
|
// When that is set, though, allocs crash in weird ways.
|
||||||
|
//
|
||||||
|
// From kernel dissasembly, after storage is initialized
|
||||||
|
// XboxHardwareInfo flags is set with flag 5 (0x20).
|
||||||
uint32_t pXboxHardwareInfo = memory_->SystemHeapAlloc(16);
|
uint32_t pXboxHardwareInfo = memory_->SystemHeapAlloc(16);
|
||||||
auto lpXboxHardwareInfo = memory_->TranslateVirtual(pXboxHardwareInfo);
|
auto lpXboxHardwareInfo = memory_->TranslateVirtual(pXboxHardwareInfo);
|
||||||
export_resolver_->SetVariableMapping(
|
export_resolver_->SetVariableMapping(
|
||||||
"xboxkrnl.exe", ordinals::XboxHardwareInfo, pXboxHardwareInfo);
|
"xboxkrnl.exe", ordinals::XboxHardwareInfo, pXboxHardwareInfo);
|
||||||
xe::store_and_swap<uint32_t>(lpXboxHardwareInfo + 0, 0); // flags
|
xe::store_and_swap<uint32_t>(lpXboxHardwareInfo + 0, 0x20); // flags
|
||||||
xe::store_and_swap<uint8_t>(lpXboxHardwareInfo + 4, 0x06); // cpu count
|
xe::store_and_swap<uint8_t>(lpXboxHardwareInfo + 4, 0x06); // cpu count
|
||||||
// Remaining 11b are zeroes?
|
// Remaining 11b are zeroes?
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -370,10 +370,6 @@ X_STATUS XThread::Create() {
|
||||||
|
|
||||||
pcr->dpc_active = 0; // DPC active bool?
|
pcr->dpc_active = 0; // DPC active bool?
|
||||||
|
|
||||||
// Assign the thread to the logical processor, and also set up the current CPU
|
|
||||||
// in KPCR and KTHREAD.
|
|
||||||
SetActiveCpu(cpu_index);
|
|
||||||
|
|
||||||
// Always retain when starting - the thread owns itself until exited.
|
// Always retain when starting - the thread owns itself until exited.
|
||||||
RetainHandle();
|
RetainHandle();
|
||||||
|
|
||||||
|
|
@ -434,6 +430,10 @@ X_STATUS XThread::Create() {
|
||||||
thread_->set_priority(creation_params_.creation_flags & 0x20 ? 1 : 0);
|
thread_->set_priority(creation_params_.creation_flags & 0x20 ? 1 : 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Assign the newly created thread to the logical processor, and also set up
|
||||||
|
// the current CPU in KPCR and KTHREAD.
|
||||||
|
SetActiveCpu(cpu_index);
|
||||||
|
|
||||||
// Notify processor of our creation.
|
// Notify processor of our creation.
|
||||||
emulator()->processor()->OnThreadCreated(handle(), thread_state_, this);
|
emulator()->processor()->OnThreadCreated(handle(), thread_state_, this);
|
||||||
|
|
||||||
|
|
@ -728,12 +728,13 @@ void XThread::SetActiveCpu(uint8_t cpu_index) {
|
||||||
thread_object.current_cpu = cpu_index;
|
thread_object.current_cpu = cpu_index;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (xe::threading::logical_processor_count() < 6) {
|
if (xe::threading::logical_processor_count() >= 6) {
|
||||||
XELOGW("Too few processors - scheduling will be wonky");
|
|
||||||
}
|
|
||||||
if (!cvars::ignore_thread_affinities) {
|
if (!cvars::ignore_thread_affinities) {
|
||||||
thread_->set_affinity_mask(uint64_t(1) << cpu_index);
|
thread_->set_affinity_mask(uint64_t(1) << cpu_index);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
XELOGW("Too few processor cores - scheduling will be wonky");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool XThread::GetTLSValue(uint32_t slot, uint32_t* value_out) {
|
bool XThread::GetTLSValue(uint32_t slot, uint32_t* value_out) {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue