game: Migrate from GLFW to SDL2 & attempt to rewrite / simplify display and input code (#2397)

Co-authored-by: Hat Kid <6624576+Hat-Kid@users.noreply.github.com>
This commit is contained in:
Tyler Wilding
2023-06-04 14:34:37 -05:00
committed by GitHub
parent 08741f0a42
commit bdaf088d4b
1973 changed files with 903017 additions and 118529 deletions
+2
View File
@@ -1,5 +1,6 @@
# logs
/log
prof.json
# for CMake
/Testing
@@ -72,3 +73,4 @@ __pycache__/
# docs
/jak1-*.json
/jak2-*.json
/TODO.md
+5 -5
View File
@@ -45,7 +45,6 @@ if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
if(WIN32)
# Increase the reserved stack size for all threads to 16MB
# Note: this is only _reserved_ memory, not necessarily _committed_ memory
# TODO - test with add_link_options instead
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${LDFLAGS} -Xlinker /STACK:16000000")
else()
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -ggdb -g -Wextra")
@@ -57,7 +56,6 @@ if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
endif()
if(CMAKE_BUILD_TYPE MATCHES "RelWithDebInfo")
# TODO - test with add_link_options instead
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${LDFLAGS} -Xlinker /debug")
endif()
@@ -202,13 +200,15 @@ add_subdirectory(lsp)
# build libco
add_subdirectory(third-party/libco)
# build glfw library
add_subdirectory(third-party/glfw EXCLUDE_FROM_ALL)
add_subdirectory(third-party/zstd EXCLUDE_FROM_ALL)
# build SDL
include(SDLOptions)
add_subdirectory(third-party/SDL EXCLUDE_FROM_ALL)
# build imgui
include_directories(third-party/glad/include)
include_directories(third-party/glfw/include)
include_directories(third-party/SDL/include)
add_subdirectory(third-party/imgui EXCLUDE_FROM_ALL)
string(REPLACE " ${THIRDPARTY_IGNORED_WARNINGS} " "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
+17
View File
@@ -16,6 +16,7 @@
#include "common/common_types.h"
#include "common/util/BinaryReader.h"
#include "common/util/string_util.h"
#include "common/util/unicode_util.h"
// This disables the use of PCLMULQDQ which is probably ok, but let's just be safe and disable it
@@ -201,10 +202,12 @@ bool create_dir_if_needed(const fs::path& path) {
return false;
}
// TODO - explodes if the file path is invalid
bool create_dir_if_needed_for_file(const std::string& path) {
return create_dir_if_needed_for_file(fs::path(path));
}
// TODO - explodes if the file path is invalid
bool create_dir_if_needed_for_file(const fs::path& path) {
return fs::create_directories(path.parent_path());
}
@@ -636,4 +639,18 @@ void copy_file(const fs::path& src, const fs::path& dst) {
fs::copy_file(src, dst, fs::copy_options::overwrite_existing);
}
std::string make_screenshot_filepath(const GameVersion game_version, const std::string& name) {
std::string file_name;
if (name.empty()) {
file_name = fmt::format("{}_{}.png", version_to_game_name(game_version),
str_util::current_local_timestamp_no_colons());
} else {
file_name = fmt::format("{}_{}_{}.png", version_to_game_name(game_version), name,
str_util::current_local_timestamp_no_colons());
}
const auto file_path = file_util::get_file_path({"screenshots", file_name});
file_util::create_dir_if_needed_for_file(file_path);
return file_path;
}
} // namespace file_util
+1
View File
@@ -66,4 +66,5 @@ std::vector<fs::path> find_files_recursively(const fs::path& base_dir, const std
std::vector<fs::path> find_directories_in_dir(const fs::path& base_dir);
/// Will overwrite the destination if it exists
void copy_file(const fs::path& src, const fs::path& dst);
std::string make_screenshot_filepath(const GameVersion game_version, const std::string& name = "");
} // namespace file_util
+41
View File
@@ -1,7 +1,9 @@
#include "string_util.h"
#include <iomanip>
#include <random>
#include <regex>
#include <sstream>
#include "common/util/diff.h"
@@ -141,4 +143,43 @@ std::string repeat(size_t n, const std::string& str) {
ret.append(ret.c_str(), (n - (m / 2)) * period);
return ret;
}
std::string current_local_timestamp() {
std::time_t now = std::time(nullptr);
std::tm local_time = *std::localtime(&now);
const std::string format = "%Y-%m-%dT%H:%M:%S";
std::ostringstream oss;
oss << std::put_time(&local_time, format.c_str());
return oss.str();
}
std::string current_local_timestamp_no_colons() {
std::time_t now = std::time(nullptr);
std::tm local_time = *std::localtime(&now);
const std::string format = "%Y-%m-%dT%H-%M-%S";
std::ostringstream oss;
oss << std::put_time(&local_time, format.c_str());
return oss.str();
}
std::string current_isotimestamp() {
std::time_t now = std::time(nullptr);
std::tm utc_time = *std::gmtime(&now);
const std::string format = "%Y-%m-%dT%H:%M:%SZ";
std::ostringstream oss;
oss << std::put_time(&utc_time, format.c_str());
return oss.str();
}
std::string to_upper(const std::string& str) {
std::string new_str(str.size(), ' ');
std::transform(str.begin(), str.end(), new_str.begin(), ::toupper);
return new_str;
}
std::string to_lower(const std::string& str) {
std::string new_str(str.size(), ' ');
std::transform(str.begin(), str.end(), new_str.begin(), ::tolower);
return new_str;
}
} // namespace str_util
+5
View File
@@ -23,4 +23,9 @@ std::vector<std::string> regex_get_capture_groups(const std::string& str, const
bool replace(std::string& str, const std::string& from, const std::string& to);
std::string uuid();
std::string repeat(size_t n, const std::string& str);
std::string current_local_timestamp();
std::string current_local_timestamp_no_colons();
std::string current_isotimestamp();
std::string to_upper(const std::string& str);
std::string to_lower(const std::string& str);
} // namespace str_util
+38 -1
View File
@@ -1031,7 +1031,7 @@
(sidekick-hint-rounddoor #x23c)
(sidekick-hint-lurkerm #x23d)
(sidekick-hint-tower #x23e)
(sidekick-reminder-fish #x240)
(firecanyon-need-cells #x24f)
@@ -1650,6 +1650,30 @@
(speedrun-hub2-100 #x1522)
(speedrun-hub3-100 #x1523)
(speedrun-all-cutscenes #x1524)
;; input options
(input-options #x1600)
(input-opts-select-controller #x1601)
(input-opts-analog-deadzone #x1602)
(input-opts-ignore-controller-win-focus #x1603)
(input-opts-controller-led-reflect-hp #x1604)
(input-opts-controller-led-reflect-eco #x1605)
(input-opts-mouse-enable-camera #x1606)
(input-opts-mouse-horizontal-sens #x1607)
(input-opts-mouse-vertical-sens #x1608)
(input-opts-mouse-enable-movement #x1609)
(input-opts-binds-controller #x160a)
(input-opts-binds-keyboard #x160b)
(input-opts-binds-mouse #x160c)
(input-opts-controller-opts #x160d)
(input-opts-enable-kb #x160e)
(input-opts-enable-mouse #x160f)
(input-opts-mouse-opts #x1610)
(input-opts-reassign-binds #x1611)
(input-opts-generic-controller #x1612)
(input-opts-auto-hide-cursor #x1613)
(input-opts-binds-unset #x1614)
(input-opts-binds-unknown #x1615)
(progress-no-other-resolution-options #x1616)
;; GAME-TEXT-ID ENUM ENDS
)
@@ -15772,6 +15796,16 @@
(quit 34)
;; extra screens for pc port
;; input options
(input-options)
(select-controller)
(controller-binds) ;; 0x25
(keyboard-binds)
(mouse-binds)
(controller-options)
(mouse-options)
(reassign-binds-options)
(camera-options)
(accessibility-options)
(game-ps2-options)
@@ -15830,6 +15864,9 @@
(button-flava)
(cheat-toggle)
(monitor)
(controller)
(binding-assignment)
(confirmation)
)
(defenum game-option-menu
+10 -3
View File
@@ -6,7 +6,7 @@ set(CMAKE_ASM_NASM_SOURCE_FILE_EXTENSIONS ${CMAKE_ASM_NASM_SOURCE_FILE_EXTENSION
set(CMAKE_ASM_NASM_COMPILE_OBJECT "<CMAKE_ASM_NASM_COMPILER> <INCLUDES> -f ${CMAKE_ASM_NASM_OBJECT_FORMAT} -o <OBJECT> <SOURCE>")
set_source_files_properties(kernel/asm_funcs.asm PROPERTIES COMPILE_FLAGS "-g")
set(RUNTIME_SOURCE
discord.cpp
external/discord.cpp
graphics/display.cpp
graphics/gfx.cpp
graphics/jak2_texture_remap.cpp
@@ -197,10 +197,17 @@ set(RUNTIME_SOURCE
sce/sif_ee_memcard.cpp
sce/sif_ee.cpp
sce/stubs.cpp
settings/settings.cpp
system/Deci2Server.cpp
system/hid/devices/game_controller.cpp
system/hid/devices/keyboard.cpp
system/hid/devices/mouse.cpp
system/hid/display_manager.cpp
system/hid/input_bindings.cpp
system/hid/input_manager.cpp
system/hid/sdl_util.cpp
system/IOP_Kernel.cpp
system/iop_thread.cpp
system/newpad.cpp
system/SystemThread.cpp
system/vm/dmac.cpp
system/vm/vm.cpp
@@ -213,7 +220,7 @@ add_subdirectory(sound)
# we build the runtime as a static library.
add_library(runtime STATIC ${RUNTIME_SOURCE} "../third-party/glad/src/glad.c")
target_link_libraries(runtime common fmt glfw imgui discord-rpc sound stb_image libco SQLiteCpp)
target_link_libraries(runtime common fmt SDL2::SDL2 imgui discord-rpc sound stb_image libco SQLiteCpp)
if(WIN32)
target_link_libraries(runtime mman)
else()
+202
View File
@@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Binary file not shown.
@@ -197,5 +197,28 @@
"1521": "HUB 1 100%",
"1522": "HUB 2 100%",
"1523": "HUB 3 100%",
"1524": "ALL CUTSCENES"
"1524": "ALL CUTSCENES",
"1600": "INPUT OPTIONS",
"1601": "SELECT CONTROLLER",
"1602": "ANALOG DEADZONE",
"1603": "IGNORE IF WINDOW UNFOCUSED",
"1604": "LED REFLECT HP",
"1605": "LED REFLECT ECO",
"1606": "TRACK CAMERA",
"1607": "HORIZONTAL SENSITIVITY",
"1608": "VERTICAL SENSITIVITY",
"1609": "PLAYER MOVEMENT",
"160a": "CONTROLLER BINDS",
"160b": "KEYBOARD BINDS",
"160c": "MOUSE BINDS",
"160d": "CONTROLLER OPTIONS",
"160e": "ENABLE KEYBOARD",
"160f": "ENABLE MOUSE",
"1610": "MOUSE OPTIONS",
"1611": "REASSIGN BINDS",
"1612": "CONTROLLER ~D",
"1613": "AUTO HIDE CURSOR",
"1614": "UNSET",
"1615": "UNKNOWN",
"1616": "NO OTHER OPTIONS FOR ASPECT RATIO"
}
@@ -197,5 +197,28 @@
"1521": "MIDTPUNKT 1 100%",
"1522": "MIDTPUNKT 2 100%",
"1523": "MIDTPUNKT 3 100%",
"1524": "ALLE MELLEMSEKVENSER"
"1524": "ALLE MELLEMSEKVENSER",
"1600": "INPUT OPTIONS",
"1601": "SELECT CONTROLLER",
"1602": "ANALOG DEADZONE",
"1603": "IGNORE IF WINDOW UNFOCUSED",
"1604": "LED REFLECT HP",
"1605": "LED REFLECT ECO",
"1606": "TRACK CAMERA",
"1607": "HORIZONTAL SENSITIVITY",
"1608": "VERTICAL SENSITIVITY",
"1609": "PLAYER MOVEMENT",
"160a": "CONTROLLER BINDS",
"160b": "KEYBOARD BINDS",
"160c": "MOUSE BINDS",
"160d": "CONTROLLER OPTIONS",
"160e": "ENABLE KEYBOARD",
"160f": "ENABLE MOUSE",
"1610": "MOUSE OPTIONS",
"1611": "REASSIGN BINDS",
"1612": "CONTROLLER ~D",
"1613": "AUTO HIDE CURSOR",
"1614": "UNSET",
"1615": "UNKNOWN",
"1616": "NO OTHER OPTIONS FOR ASPECT RATIO"
}
@@ -197,5 +197,28 @@
"1521": "HUB 1 100%",
"1522": "HUB 2 100%",
"1523": "HUB 3 100%",
"1524": "ALLE ZWISCHENSEQUENZEN"
"1524": "ALLE ZWISCHENSEQUENZEN",
"1600": "INPUT OPTIONS",
"1601": "SELECT CONTROLLER",
"1602": "ANALOG DEADZONE",
"1603": "IGNORE IF WINDOW UNFOCUSED",
"1604": "LED REFLECT HP",
"1605": "LED REFLECT ECO",
"1606": "TRACK CAMERA",
"1607": "HORIZONTAL SENSITIVITY",
"1608": "VERTICAL SENSITIVITY",
"1609": "PLAYER MOVEMENT",
"160a": "CONTROLLER BINDS",
"160b": "KEYBOARD BINDS",
"160c": "MOUSE BINDS",
"160d": "CONTROLLER OPTIONS",
"160e": "ENABLE KEYBOARD",
"160f": "ENABLE MOUSE",
"1610": "MOUSE OPTIONS",
"1611": "REASSIGN BINDS",
"1612": "CONTROLLER ~D",
"1613": "AUTO HIDE CURSOR",
"1614": "UNSET",
"1615": "UNKNOWN",
"1616": "NO OTHER OPTIONS FOR ASPECT RATIO"
}
@@ -197,5 +197,28 @@
"1521": "HUB 1 100%",
"1522": "HUB 2 100%",
"1523": "HUB 3 100%",
"1524": "ALL CUTSCENES"
"1524": "ALL CUTSCENES",
"1600": "INPUT OPTIONS",
"1601": "SELECT CONTROLLER",
"1602": "ANALOG DEADZONE",
"1603": "IGNORE IF WINDOW UNFOCUSED",
"1604": "LED REFLECT HP",
"1605": "LED REFLECT ECO",
"1606": "TRACK CAMERA",
"1607": "HORIZONTAL SENSITIVITY",
"1608": "VERTICAL SENSITIVITY",
"1609": "PLAYER MOVEMENT",
"160a": "CONTROLLER BINDS",
"160b": "KEYBOARD BINDS",
"160c": "MOUSE BINDS",
"160d": "CONTROLLER OPTIONS",
"160e": "ENABLE KEYBOARD",
"160f": "ENABLE MOUSE",
"1610": "MOUSE OPTIONS",
"1611": "REASSIGN BINDS",
"1612": "CONTROLLER ~D",
"1613": "AUTO HIDE CURSOR",
"1614": "UNSET",
"1615": "UNKNOWN",
"1616": "NO OTHER OPTIONS FOR ASPECT RATIO"
}
@@ -197,5 +197,28 @@
"1521": "HUB 1 100%",
"1522": "HUB 2 100%",
"1523": "HUB 3 100%",
"1524": "ALL CUTSCENES"
"1524": "ALL CUTSCENES",
"1600": "INPUT OPTIONS",
"1601": "SELECT CONTROLLER",
"1602": "ANALOG DEADZONE",
"1603": "IGNORE IF WINDOW UNFOCUSED",
"1604": "LED REFLECT HP",
"1605": "LED REFLECT ECO",
"1606": "TRACK CAMERA",
"1607": "HORIZONTAL SENSITIVITY",
"1608": "VERTICAL SENSITIVITY",
"1609": "PLAYER MOVEMENT",
"160a": "CONTROLLER BINDS",
"160b": "KEYBOARD BINDS",
"160c": "MOUSE BINDS",
"160d": "CONTROLLER OPTIONS",
"160e": "ENABLE KEYBOARD",
"160f": "ENABLE MOUSE",
"1610": "MOUSE OPTIONS",
"1611": "REASSIGN BINDS",
"1612": "CONTROLLER ~D",
"1613": "AUTO HIDE CURSOR",
"1614": "UNSET",
"1615": "UNKNOWN",
"1616": "NO OTHER OPTIONS FOR ASPECT RATIO"
}
@@ -197,5 +197,28 @@
"1521": "HUB 1 100%",
"1522": "HUB 2 100%",
"1523": "HUB 3 100%",
"1524": "TODAS LAS CINEMÁTICAS"
"1524": "TODAS LAS CINEMÁTICAS",
"1600": "INPUT OPTIONS",
"1601": "SELECT CONTROLLER",
"1602": "ANALOG DEADZONE",
"1603": "IGNORE IF WINDOW UNFOCUSED",
"1604": "LED REFLECT HP",
"1605": "LED REFLECT ECO",
"1606": "TRACK CAMERA",
"1607": "HORIZONTAL SENSITIVITY",
"1608": "VERTICAL SENSITIVITY",
"1609": "PLAYER MOVEMENT",
"160a": "CONTROLLER BINDS",
"160b": "KEYBOARD BINDS",
"160c": "MOUSE BINDS",
"160d": "CONTROLLER OPTIONS",
"160e": "ENABLE KEYBOARD",
"160f": "ENABLE MOUSE",
"1610": "MOUSE OPTIONS",
"1611": "REASSIGN BINDS",
"1612": "CONTROLLER ~D",
"1613": "AUTO HIDE CURSOR",
"1614": "UNSET",
"1615": "UNKNOWN",
"1616": "NO OTHER OPTIONS FOR ASPECT RATIO"
}
@@ -197,5 +197,28 @@
"1521": "HUBI 1 100%",
"1522": "HUBI 2 100%",
"1523": "HUBI 3 100%",
"1524": "KAIKKI VÄLIANIMAATIOT"
"1524": "KAIKKI VÄLIANIMAATIOT",
"1600": "INPUT OPTIONS",
"1601": "SELECT CONTROLLER",
"1602": "ANALOG DEADZONE",
"1603": "IGNORE IF WINDOW UNFOCUSED",
"1604": "LED REFLECT HP",
"1605": "LED REFLECT ECO",
"1606": "TRACK CAMERA",
"1607": "HORIZONTAL SENSITIVITY",
"1608": "VERTICAL SENSITIVITY",
"1609": "PLAYER MOVEMENT",
"160a": "CONTROLLER BINDS",
"160b": "KEYBOARD BINDS",
"160c": "MOUSE BINDS",
"160d": "CONTROLLER OPTIONS",
"160e": "ENABLE KEYBOARD",
"160f": "ENABLE MOUSE",
"1610": "MOUSE OPTIONS",
"1611": "REASSIGN BINDS",
"1612": "CONTROLLER ~D",
"1613": "AUTO HIDE CURSOR",
"1614": "UNSET",
"1615": "UNKNOWN",
"1616": "NO OTHER OPTIONS FOR ASPECT RATIO"
}
@@ -197,5 +197,28 @@
"1521": "HUB 1 100%",
"1522": "HUB 2 100%",
"1523": "HUB 3 100%",
"1524": "TOUTES LES CINÉMATIQUES"
"1524": "TOUTES LES CINÉMATIQUES",
"1600": "INPUT OPTIONS",
"1601": "SELECT CONTROLLER",
"1602": "ANALOG DEADZONE",
"1603": "IGNORE IF WINDOW UNFOCUSED",
"1604": "LED REFLECT HP",
"1605": "LED REFLECT ECO",
"1606": "TRACK CAMERA",
"1607": "HORIZONTAL SENSITIVITY",
"1608": "VERTICAL SENSITIVITY",
"1609": "PLAYER MOVEMENT",
"160a": "CONTROLLER BINDS",
"160b": "KEYBOARD BINDS",
"160c": "MOUSE BINDS",
"160d": "CONTROLLER OPTIONS",
"160e": "ENABLE KEYBOARD",
"160f": "ENABLE MOUSE",
"1610": "MOUSE OPTIONS",
"1611": "REASSIGN BINDS",
"1612": "CONTROLLER ~D",
"1613": "AUTO HIDE CURSOR",
"1614": "UNSET",
"1615": "UNKNOWN",
"1616": "NO OTHER OPTIONS FOR ASPECT RATIO"
}
@@ -197,5 +197,28 @@
"1521": "HUB 1 100%",
"1522": "HUB 2 100%",
"1523": "HUB 3 100%",
"1524": "ÖSSZES JELENET"
"1524": "ÖSSZES JELENET",
"1600": "INPUT OPTIONS",
"1601": "SELECT CONTROLLER",
"1602": "ANALOG DEADZONE",
"1603": "IGNORE IF WINDOW UNFOCUSED",
"1604": "LED REFLECT HP",
"1605": "LED REFLECT ECO",
"1606": "TRACK CAMERA",
"1607": "HORIZONTAL SENSITIVITY",
"1608": "VERTICAL SENSITIVITY",
"1609": "PLAYER MOVEMENT",
"160a": "CONTROLLER BINDS",
"160b": "KEYBOARD BINDS",
"160c": "MOUSE BINDS",
"160d": "CONTROLLER OPTIONS",
"160e": "ENABLE KEYBOARD",
"160f": "ENABLE MOUSE",
"1610": "MOUSE OPTIONS",
"1611": "REASSIGN BINDS",
"1612": "CONTROLLER ~D",
"1613": "AUTO HIDE CURSOR",
"1614": "UNSET",
"1615": "UNKNOWN",
"1616": "NO OTHER OPTIONS FOR ASPECT RATIO"
}
@@ -197,5 +197,28 @@
"1521": "HUB 1 100%",
"1522": "HUB 2 100%",
"1523": "HUB 3 100%",
"1524": "ALL CUTSCENES"
"1524": "ALL CUTSCENES",
"1600": "INPUT OPTIONS",
"1601": "SELECT CONTROLLER",
"1602": "ANALOG DEADZONE",
"1603": "IGNORE IF WINDOW UNFOCUSED",
"1604": "LED REFLECT HP",
"1605": "LED REFLECT ECO",
"1606": "TRACK CAMERA",
"1607": "HORIZONTAL SENSITIVITY",
"1608": "VERTICAL SENSITIVITY",
"1609": "PLAYER MOVEMENT",
"160a": "CONTROLLER BINDS",
"160b": "KEYBOARD BINDS",
"160c": "MOUSE BINDS",
"160d": "CONTROLLER OPTIONS",
"160e": "ENABLE KEYBOARD",
"160f": "ENABLE MOUSE",
"1610": "MOUSE OPTIONS",
"1611": "REASSIGN BINDS",
"1612": "CONTROLLER ~D",
"1613": "AUTO HIDE CURSOR",
"1614": "UNSET",
"1615": "UNKNOWN",
"1616": "NO OTHER OPTIONS FOR ASPECT RATIO"
}
@@ -197,5 +197,28 @@
"1521": "HUB 1 100%",
"1522": "HUB 2 100%",
"1523": "HUB 3 100%",
"1524": "TUTTI I FILMATI"
"1524": "TUTTI I FILMATI",
"1600": "INPUT OPTIONS",
"1601": "SELECT CONTROLLER",
"1602": "ANALOG DEADZONE",
"1603": "IGNORE IF WINDOW UNFOCUSED",
"1604": "LED REFLECT HP",
"1605": "LED REFLECT ECO",
"1606": "TRACK CAMERA",
"1607": "HORIZONTAL SENSITIVITY",
"1608": "VERTICAL SENSITIVITY",
"1609": "PLAYER MOVEMENT",
"160a": "CONTROLLER BINDS",
"160b": "KEYBOARD BINDS",
"160c": "MOUSE BINDS",
"160d": "CONTROLLER OPTIONS",
"160e": "ENABLE KEYBOARD",
"160f": "ENABLE MOUSE",
"1610": "MOUSE OPTIONS",
"1611": "REASSIGN BINDS",
"1612": "CONTROLLER ~D",
"1613": "AUTO HIDE CURSOR",
"1614": "UNSET",
"1615": "UNKNOWN",
"1616": "NO OTHER OPTIONS FOR ASPECT RATIO"
}
@@ -197,5 +197,28 @@
"1521": "HUB 1 100%",
"1522": "HUB 2 100%",
"1523": "HUB 3 100%",
"1524": "ALL CUTSCENES"
"1524": "ALL CUTSCENES",
"1600": "INPUT OPTIONS",
"1601": "SELECT CONTROLLER",
"1602": "ANALOG DEADZONE",
"1603": "IGNORE IF WINDOW UNFOCUSED",
"1604": "LED REFLECT HP",
"1605": "LED REFLECT ECO",
"1606": "TRACK CAMERA",
"1607": "HORIZONTAL SENSITIVITY",
"1608": "VERTICAL SENSITIVITY",
"1609": "PLAYER MOVEMENT",
"160a": "CONTROLLER BINDS",
"160b": "KEYBOARD BINDS",
"160c": "MOUSE BINDS",
"160d": "CONTROLLER OPTIONS",
"160e": "ENABLE KEYBOARD",
"160f": "ENABLE MOUSE",
"1610": "MOUSE OPTIONS",
"1611": "REASSIGN BINDS",
"1612": "CONTROLLER ~D",
"1613": "AUTO HIDE CURSOR",
"1614": "UNSET",
"1615": "UNKNOWN",
"1616": "NO OTHER OPTIONS FOR ASPECT RATIO"
}
@@ -197,5 +197,28 @@
"1521": "CENTRUM 1 100%",
"1522": "CENTRUM 2 100%",
"1523": "CENTRUM 3 100%",
"1524": "ALLE CUTSCÈNES"
"1524": "ALLE CUTSCÈNES",
"1600": "INPUT OPTIONS",
"1601": "SELECT CONTROLLER",
"1602": "ANALOG DEADZONE",
"1603": "IGNORE IF WINDOW UNFOCUSED",
"1604": "LED REFLECT HP",
"1605": "LED REFLECT ECO",
"1606": "TRACK CAMERA",
"1607": "HORIZONTAL SENSITIVITY",
"1608": "VERTICAL SENSITIVITY",
"1609": "PLAYER MOVEMENT",
"160a": "CONTROLLER BINDS",
"160b": "KEYBOARD BINDS",
"160c": "MOUSE BINDS",
"160d": "CONTROLLER OPTIONS",
"160e": "ENABLE KEYBOARD",
"160f": "ENABLE MOUSE",
"1610": "MOUSE OPTIONS",
"1611": "REASSIGN BINDS",
"1612": "CONTROLLER ~D",
"1613": "AUTO HIDE CURSOR",
"1614": "UNSET",
"1615": "UNKNOWN",
"1616": "NO OTHER OPTIONS FOR ASPECT RATIO"
}
@@ -197,5 +197,28 @@
"1521": "KNUTEPUNKT 1 100%",
"1522": "KNUTEPUNKT 2 100%",
"1523": "KNUTEPUNKT 3 100%",
"1524": "ALLE MELLOMSEKVENSER"
"1524": "ALLE MELLOMSEKVENSER",
"1600": "INPUT OPTIONS",
"1601": "SELECT CONTROLLER",
"1602": "ANALOG DEADZONE",
"1603": "IGNORE IF WINDOW UNFOCUSED",
"1604": "LED REFLECT HP",
"1605": "LED REFLECT ECO",
"1606": "TRACK CAMERA",
"1607": "HORIZONTAL SENSITIVITY",
"1608": "VERTICAL SENSITIVITY",
"1609": "PLAYER MOVEMENT",
"160a": "CONTROLLER BINDS",
"160b": "KEYBOARD BINDS",
"160c": "MOUSE BINDS",
"160d": "CONTROLLER OPTIONS",
"160e": "ENABLE KEYBOARD",
"160f": "ENABLE MOUSE",
"1610": "MOUSE OPTIONS",
"1611": "REASSIGN BINDS",
"1612": "CONTROLLER ~D",
"1613": "AUTO HIDE CURSOR",
"1614": "UNSET",
"1615": "UNKNOWN",
"1616": "NO OTHER OPTIONS FOR ASPECT RATIO"
}
@@ -197,5 +197,28 @@
"1521": "CAPÍTULO 1 100%",
"1522": "CAPÍTULO 2 100%",
"1523": "CAPÍTULO 3 100%",
"1524": "TODOS OS CINEMÁTICOS"
"1524": "TODOS OS CINEMÁTICOS",
"1600": "INPUT OPTIONS",
"1601": "SELECT CONTROLLER",
"1602": "ANALOG DEADZONE",
"1603": "IGNORE IF WINDOW UNFOCUSED",
"1604": "LED REFLECT HP",
"1605": "LED REFLECT ECO",
"1606": "TRACK CAMERA",
"1607": "HORIZONTAL SENSITIVITY",
"1608": "VERTICAL SENSITIVITY",
"1609": "PLAYER MOVEMENT",
"160a": "CONTROLLER BINDS",
"160b": "KEYBOARD BINDS",
"160c": "MOUSE BINDS",
"160d": "CONTROLLER OPTIONS",
"160e": "ENABLE KEYBOARD",
"160f": "ENABLE MOUSE",
"1610": "MOUSE OPTIONS",
"1611": "REASSIGN BINDS",
"1612": "CONTROLLER ~D",
"1613": "AUTO HIDE CURSOR",
"1614": "UNSET",
"1615": "UNKNOWN",
"1616": "NO OTHER OPTIONS FOR ASPECT RATIO"
}
@@ -197,5 +197,28 @@
"1521": "HUB 1 100%",
"1522": "HUB 2 100%",
"1523": "HUB 3 100%",
"1524": "ALL CUTSCENES"
"1524": "ALL CUTSCENES",
"1600": "INPUT OPTIONS",
"1601": "SELECT CONTROLLER",
"1602": "ANALOG DEADZONE",
"1603": "IGNORE IF WINDOW UNFOCUSED",
"1604": "LED REFLECT HP",
"1605": "LED REFLECT ECO",
"1606": "TRACK CAMERA",
"1607": "HORIZONTAL SENSITIVITY",
"1608": "VERTICAL SENSITIVITY",
"1609": "PLAYER MOVEMENT",
"160a": "CONTROLLER BINDS",
"160b": "KEYBOARD BINDS",
"160c": "MOUSE BINDS",
"160d": "CONTROLLER OPTIONS",
"160e": "ENABLE KEYBOARD",
"160f": "ENABLE MOUSE",
"1610": "MOUSE OPTIONS",
"1611": "REASSIGN BINDS",
"1612": "CONTROLLER ~D",
"1613": "AUTO HIDE CURSOR",
"1614": "UNSET",
"1615": "UNKNOWN",
"1616": "NO OTHER OPTIONS FOR ASPECT RATIO"
}
@@ -197,5 +197,28 @@
"1521": "PORTEN 1 100%",
"1522": "PORTEN 2 100%",
"1523": "PORTEN 3 100%",
"1524": "ALLA SCENER"
"1524": "ALLA SCENER",
"1600": "INPUT OPTIONS",
"1601": "SELECT CONTROLLER",
"1602": "ANALOG DEADZONE",
"1603": "IGNORE IF WINDOW UNFOCUSED",
"1604": "LED REFLECT HP",
"1605": "LED REFLECT ECO",
"1606": "TRACK CAMERA",
"1607": "HORIZONTAL SENSITIVITY",
"1608": "VERTICAL SENSITIVITY",
"1609": "PLAYER MOVEMENT",
"160a": "CONTROLLER BINDS",
"160b": "KEYBOARD BINDS",
"160c": "MOUSE BINDS",
"160d": "CONTROLLER OPTIONS",
"160e": "ENABLE KEYBOARD",
"160f": "ENABLE MOUSE",
"1610": "MOUSE OPTIONS",
"1611": "REASSIGN BINDS",
"1612": "CONTROLLER ~D",
"1613": "AUTO HIDE CURSOR",
"1614": "UNSET",
"1615": "UNKNOWN",
"1616": "NO OTHER OPTIONS FOR ASPECT RATIO"
}
View File
View File
+3 -72
View File
@@ -35,71 +35,6 @@ void set_main_display(std::shared_ptr<GfxDisplay> display) {
} // namespace
/*
********************************
* GfxDisplay
********************************
*/
void GfxDisplay::set_title(const char* title) {
if (!is_active()) {
lg::error("No window to set title `{}`.", title);
return;
}
// TODO set title?
m_title = title;
}
int GfxDisplay::width() {
int w;
get_size(&w, NULL);
return w;
}
int GfxDisplay::height() {
int h;
get_size(NULL, &h);
#ifdef _WIN32
if (last_fullscreen_mode() == GfxDisplayMode::Borderless) {
// windows borderless hack
h--;
}
#endif
return h;
}
void GfxDisplay::save_display_settings() {
nlohmann::json json;
json["window_xpos"] = m_last_windowed_xpos;
json["window_ypos"] = m_last_windowed_ypos;
std::string file_path =
(file_util::get_user_settings_dir(g_game_version) / "display-settings.json").string();
file_util::create_dir_if_needed_for_file(file_path);
file_util::write_text_file(file_path, json.dump(2));
}
void GfxDisplay::restore_display_settings() {
try {
std::string file_path =
(file_util::get_user_settings_dir(g_game_version) / "display-settings.json").string();
if (!file_util::file_exists(file_path)) {
return;
}
lg::info("reading {}", file_path);
auto raw = file_util::read_text_file(file_path);
auto json = parse_commented_json(raw, "display-settings.json");
if (json.contains("window_xpos")) {
m_last_windowed_xpos = json.at("window_xpos").get<int>();
}
if (json.contains("window_ypos")) {
m_last_windowed_ypos = json.at("window_ypos").get<int>();
}
} catch (std::exception& e) {
// do nothing
}
}
/*
********************************
* DISPLAY
@@ -112,13 +47,13 @@ std::vector<std::shared_ptr<GfxDisplay>> g_displays;
std::shared_ptr<GfxDisplay> GetMainDisplay() {
if (g_displays.size() == 0)
return NULL;
return g_displays.front()->is_active() ? g_displays.front() : NULL;
return g_displays.front()->get_display_manager()->is_window_active() ? g_displays.front() : NULL;
}
int InitMainDisplay(int width,
int height,
const char* title,
GfxSettings& settings,
GfxGlobalSettings& settings,
GameVersion version) {
if (GetMainDisplay() != NULL) {
lg::warn("InitMainDisplay called when main display already exists.");
@@ -132,8 +67,6 @@ int InitMainDisplay(int width,
return 1;
}
set_main_display(display);
// Restore window settings
display->restore_display_settings();
return 0;
}
@@ -143,14 +76,12 @@ void KillMainDisplay() {
void KillDisplay(std::shared_ptr<GfxDisplay> display) {
// lg::debug("kill display #x{:x}", (uintptr_t)display);
if (!display->is_active()) {
if (!display->get_display_manager()->is_window_active()) {
lg::warn("display #x{:x} cant be killed because it is not active");
return;
}
if (GetMainDisplay() == display) {
// Save the main display's position to a file so it can be restored upon re-opening
display->save_display_settings();
// killing the main display, kill all children displays too!
while (g_displays.size() > 1) {
KillDisplay(g_displays.at(1));
+6 -67
View File
@@ -12,92 +12,31 @@
#include "common/util/Assert.h"
#include "game/system/hid/display_manager.h"
// a GfxDisplay class is equivalent to a window that displays stuff. This holds an actual internal
// window pointer used by whichever renderer. It also contains functions for setting and
// retrieving certain window parameters.
// specific renderers should override this class, the methods are abstract.
class GfxDisplay {
const char* m_title;
int m_fullscreen_screen = -1;
int m_fullscreen_target_screen = -1;
bool m_imgui_visible;
protected:
bool m_main;
// next mode
GfxDisplayMode m_fullscreen_target_mode = GfxDisplayMode::Windowed;
// current mode (start as -1 to force an initial fullscreen update)
GfxDisplayMode m_fullscreen_mode = GfxDisplayMode::ForceUpdate;
// previous mode (last frame)
GfxDisplayMode m_last_fullscreen_mode = GfxDisplayMode::Windowed;
// move it a bit away from the top, or the title bar can be hidden
int m_last_windowed_xpos = 50;
int m_last_windowed_ypos = 50;
int m_last_windowed_width = 640;
int m_last_windowed_height = 480;
public:
virtual ~GfxDisplay() {}
virtual void* get_window() const = 0;
virtual void set_size(int w, int h) = 0;
virtual void update_fullscreen(GfxDisplayMode mode, int screen) = 0;
virtual void get_scale(float* x, float* y) = 0;
virtual int get_screen_vmode_count() = 0;
virtual void get_screen_size(int vmode_idx, s32* w, s32* h) = 0;
virtual int get_screen_rate(int vmode_idx) = 0;
virtual int get_monitor_count() = 0;
virtual void get_position(int* x, int* y) = 0;
virtual void get_size(int* w, int* h) = 0;
virtual std::shared_ptr<DisplayManager> get_display_manager() const = 0;
virtual std::shared_ptr<InputManager> get_input_manager() const = 0;
virtual void render() = 0;
virtual void set_lock(bool lock) = 0;
virtual bool minimized() = 0;
virtual bool fullscreen_pending() {
return fullscreen_mode() != m_fullscreen_target_mode ||
m_fullscreen_screen != m_fullscreen_target_screen;
}
virtual void fullscreen_flush() {
update_fullscreen(m_fullscreen_target_mode, m_fullscreen_target_screen);
m_fullscreen_mode = m_fullscreen_target_mode;
m_fullscreen_screen = m_fullscreen_target_screen;
// hack, force a vsync update.
Gfx::g_global_settings.old_vsync = !Gfx::g_global_settings.vsync;
}
virtual std::tuple<double, double> get_mouse_pos() = 0;
bool is_active() const { return get_window() != nullptr; }
void set_title(const char* title);
const char* title() const { return m_title; }
void set_fullscreen(GfxDisplayMode mode, int screen) {
m_fullscreen_target_mode = mode;
m_fullscreen_target_screen = screen;
}
void update_last_fullscreen_mode() { m_last_fullscreen_mode = fullscreen_mode(); }
GfxDisplayMode last_fullscreen_mode() const { return m_last_fullscreen_mode; }
GfxDisplayMode fullscreen_mode() { return m_fullscreen_mode; }
int fullscreen_screen() const { return m_fullscreen_screen; }
void set_imgui_visible(bool visible) {
m_imgui_visible = visible;
Gfx::g_debug_settings.show_imgui = visible;
Gfx::g_debug_settings.save_settings();
}
bool is_imgui_visible() const { return m_imgui_visible; }
bool windowed() { return fullscreen_mode() == GfxDisplayMode::Windowed; }
int width();
int height();
struct DisplaySettings {
int window_xpos;
int window_ypos;
};
void save_display_settings();
void restore_display_settings();
};
namespace Display {
@@ -109,7 +48,7 @@ extern std::vector<std::shared_ptr<GfxDisplay>> g_displays;
int InitMainDisplay(int width,
int height,
const char* title,
GfxSettings& settings,
GfxGlobalSettings& settings,
GameVersion version);
void KillDisplay(std::shared_ptr<GfxDisplay> display);
void KillMainDisplay();
+24 -465
View File
@@ -18,274 +18,16 @@
#include "common/util/json_util.h"
#include "game/common/file_paths.h"
#include "game/kernel/common/kmachine.h"
#include "game/kernel/common/kscheme.h"
#include "game/runtime.h"
#include "game/system/newpad.h"
#include "pipelines/opengl.h"
namespace {
// initializes a gfx settings.
// TODO save and load from file
void InitSettings(GfxSettings& settings) {
// set the current settings version
settings.version = GfxSettings::CURRENT_VERSION;
// use opengl by default for now
settings.renderer = GfxPipeline::OpenGL; // Gfx::renderers[0];
// 1 screen update per frame
settings.vsync = 1;
// debug for now
settings.debug = true;
// use buffered input mode
settings.pad_mapping_info.buffer_mode = true;
// debug input settings
settings.pad_mapping_info.debug = true;
Pad::DefaultMapping(Gfx::g_settings.pad_mapping_info);
}
} // namespace
namespace Gfx {
std::function<void()> vsync_callback;
GfxGlobalSettings g_global_settings;
GfxSettings g_settings;
DebugSettings g_debug_settings;
void DebugSettings::load_settings(const ghc::filesystem::path& filepath) {
auto file_txt = file_util::read_text_file(filepath);
auto json = parse_commented_json(file_txt, filepath.string());
if (json.contains("show_imgui")) {
show_imgui = json["show_imgui"].get<bool>();
}
if (json.contains("ignore_imgui_hide_keybind")) {
ignore_imgui_hide_keybind = json["ignore_imgui_hide_keybind"].get<bool>();
}
if (json.contains("debug_text_check_range")) {
debug_text_check_range = json["debug_text_check_range"].get<bool>();
}
if (json.contains("debug_text_max_range")) {
debug_text_max_range = json["debug_text_max_range"].get<float>();
}
// TODO - not loading filters because they aren't being persisted
}
void DebugSettings::save_settings() {
nlohmann::json json;
json["show_imgui"] = show_imgui;
json["ignore_imgui_hide_keybind"] = ignore_imgui_hide_keybind;
json["debug_text_check_range"] = debug_text_check_range;
json["debug_text_max_range"] = debug_text_max_range;
// TODO - persist the filters as well, not doing it yet because i havn't added a way to remove em
// via the UI
auto debug_settings_filename =
file_util::get_user_misc_dir(g_game_version) / "debug-settings.json";
file_util::create_dir_if_needed_for_file(debug_settings_filename);
file_util::write_text_file(debug_settings_filename, json.dump(2));
}
Pad::MappingInfo& get_button_mapping() {
return g_settings.pad_mapping_info;
}
// const std::vector<const GfxRendererModule*> renderers = {&moduleOpenGL};
// Not crazy about this declaration
const std::pair<std::string, Pad::Button> gamepad_map[] = {{"Select", Pad::Button::Select},
{"L3", Pad::Button::L3},
{"R3", Pad::Button::R3},
{"Start", Pad::Button::Start},
{"Up", Pad::Button::Up},
{"Right", Pad::Button::Right},
{"Down", Pad::Button::Down},
{"Left", Pad::Button::Left},
{"L1", Pad::Button::L1},
{"R1", Pad::Button::R1},
{"Triangle", Pad::Button::Triangle},
{"Circle", Pad::Button::Circle},
{"X", Pad::Button::X},
{"Square", Pad::Button::Square}};
const std::pair<std::string, Pad::Analog> analog_map[] = {
{"Left X Axis", Pad::Analog::Left_X},
{"Left Y Axis", Pad::Analog::Left_Y},
{"Right X Axis", Pad::Analog::Right_X},
{"Right Y Axis", Pad::Analog::Right_Y},
};
void DumpToJson(ghc::filesystem::path& filename) {
nlohmann::json json;
auto& peripherals_json = json["Peripherals"];
json["Use Mouse"] = g_settings.pad_mapping_info.use_mouse;
for (uint32_t i = 0; i < Pad::CONTROLLER_COUNT; ++i) {
nlohmann::json peripheral_json;
peripheral_json["ID"] = i + 1;
auto& controller_json = peripheral_json["Controller"];
auto& controller_buttons_json = controller_json["Buttons"];
for (const auto& [name, value] : gamepad_map) {
controller_buttons_json[name] =
g_settings.pad_mapping_info.controller_button_mapping[i][(int)value];
}
auto& keyboard_json = peripheral_json["Keyboard+Mouse"];
auto& keyboard_buttons_json = keyboard_json["Buttons"];
for (const auto& [name, value] : gamepad_map) {
keyboard_buttons_json[name] =
g_settings.pad_mapping_info.keyboard_button_mapping[i][(int)value];
}
auto& keyboard_analogs_json = keyboard_json["Analog"];
for (const auto& [name, value] : analog_map) {
if (g_settings.pad_mapping_info.keyboard_analog_mapping[i][(int)value].mode ==
Pad::AnalogMappingMode::AnalogInput) {
keyboard_analogs_json[name]["Axis Id"] =
g_settings.pad_mapping_info.keyboard_analog_mapping[i][(int)value].axis_id;
} else {
keyboard_analogs_json[name]["Positive Key"] =
g_settings.pad_mapping_info.keyboard_analog_mapping[i][(int)value].positive_key;
keyboard_analogs_json[name]["Negative Key"] =
g_settings.pad_mapping_info.keyboard_analog_mapping[i][(int)value].negative_key;
}
}
peripheral_json["X-Axis Mouse Sensitivity"] =
g_settings.pad_mapping_info.mouse_x_axis_sensitivities[i];
peripheral_json["Y-Axis Mouse Sensitivity"] =
g_settings.pad_mapping_info.mouse_y_axis_sensitivities[i];
peripherals_json.emplace_back(peripheral_json);
}
file_util::write_text_file(filename, json.dump(4));
}
void SavePeripheralSettings() {
auto filename = (file_util::get_user_config_dir() / "controller" / "controller-settings.json");
file_util::create_dir_if_needed_for_file(filename);
DumpToJson(filename);
lg::info("Saved graphics configuration file.");
}
void LoadPeripheralSettings(const ghc::filesystem::path& filepath) {
Pad::DefaultMapping(g_settings.pad_mapping_info);
lg::info("reading {}", filepath.string());
auto file_txt = file_util::read_text_file(filepath);
auto configuration = parse_commented_json(file_txt, filepath.string());
if (configuration.find("Use Mouse") != configuration.end()) {
g_settings.pad_mapping_info.use_mouse = configuration["Use Mouse"].get<bool>();
}
int controller_index = 0;
for (const auto& peripheral : configuration["Peripherals"]) {
auto& controller_buttons_json = peripheral["Controller"]["Buttons"];
auto& keyboard_buttons_json = peripheral["Keyboard+Mouse"]["Buttons"];
for (const auto& [name, button] : gamepad_map) {
if (controller_buttons_json.find(name) != controller_buttons_json.end()) {
g_settings.pad_mapping_info.controller_button_mapping[controller_index][(int)button] =
controller_buttons_json[name].get<int>();
} else {
lg::warn(
"Controller button override not found for {}. Using controller default value: {}", name,
g_settings.pad_mapping_info.controller_button_mapping[controller_index][(int)button]);
}
if (keyboard_buttons_json.find(name) != keyboard_buttons_json.end()) {
g_settings.pad_mapping_info.keyboard_button_mapping[controller_index][(int)button] =
keyboard_buttons_json[name].get<int>();
} else {
lg::warn(
"Keyboard button override not found for {}. Using keyboard default value: {}", name,
g_settings.pad_mapping_info.keyboard_button_mapping[controller_index][(int)button]);
}
}
auto& keyboard_analogs_json = peripheral["Keyboard+Mouse"]["Analog"];
for (const auto& [name, value] : analog_map) {
Pad::AnalogMappingInfo analog_mapping;
if (keyboard_analogs_json[name].contains("Axis Id") == true) {
analog_mapping.mode = Pad::AnalogMappingMode::AnalogInput;
analog_mapping.axis_id = keyboard_analogs_json[name]["Axis Id"].get<int>();
g_settings.pad_mapping_info.keyboard_analog_mapping[controller_index][(int)value] =
analog_mapping;
continue;
}
if (keyboard_analogs_json[name].contains("Positive Key") == true) {
analog_mapping.positive_key = keyboard_analogs_json[name]["Positive Key"].get<int>();
} else {
lg::warn("Keyboard analog override not found for {}. Using keyboard default value: {}",
name,
g_settings.pad_mapping_info.keyboard_analog_mapping[controller_index][(int)value]
.positive_key);
}
if (keyboard_analogs_json[name].contains("Negative Key") == true) {
analog_mapping.negative_key = keyboard_analogs_json[name]["Negative Key"].get<int>();
} else {
lg::warn("Keyboard analog override not found for {}. Using keyboard default value: {}",
name,
g_settings.pad_mapping_info.keyboard_analog_mapping[controller_index][(int)value]
.negative_key);
}
g_settings.pad_mapping_info.keyboard_analog_mapping[controller_index][(int)value] =
analog_mapping;
}
g_settings.pad_mapping_info.mouse_x_axis_sensitivities[controller_index] =
peripheral["X-Axis Mouse Sensitivity"].get<double>();
g_settings.pad_mapping_info.mouse_y_axis_sensitivities[controller_index] =
peripheral["Y-Axis Mouse Sensitivity"].get<double>();
controller_index++;
}
}
void LoadSettings() {
// load controller settings
// TODO - make this game specific as well
auto controller_settings_filename =
file_util::get_user_config_dir() / "controller" / "controller-settings.json";
try {
if (fs::exists(controller_settings_filename)) {
LoadPeripheralSettings(controller_settings_filename);
lg::info("Loaded controller configuration file.");
} else {
lg::info(
"Couldn't find $USER/controller/controller-settings.json creating new controller "
"settings "
"file.");
SavePeripheralSettings();
}
} catch (std::exception& e) {
lg::info(
"Unable to load $USER/controller/controller-settings.json successfully, re-initializing "
"it");
SavePeripheralSettings();
}
// load debug settings
auto debug_settings_filename =
file_util::get_user_misc_dir(g_game_version) / "debug-settings.json";
try {
if (fs::exists(debug_settings_filename)) {
g_debug_settings.load_settings(debug_settings_filename);
lg::info("Loaded debug settings file.");
} else {
lg::info(
"Couldn't find $USER/misc/debug-settings.json creating new controller settings file.");
g_debug_settings.save_settings();
}
} catch (std::exception& e) {
lg::info("Unable to load $USER/misc/debug-settings.json successfully, re-initializing it");
g_debug_settings.save_settings();
}
}
game_settings::DebugSettings g_debug_settings;
const GfxRendererModule* GetRenderer(GfxPipeline pipeline) {
switch (pipeline) {
@@ -302,7 +44,6 @@ const GfxRendererModule* GetRenderer(GfxPipeline pipeline) {
void SetRenderer(GfxPipeline pipeline) {
g_global_settings.renderer = GetRenderer(pipeline);
g_settings.renderer = pipeline;
}
const GfxRendererModule* GetCurrentRenderer() {
@@ -311,29 +52,35 @@ const GfxRendererModule* GetCurrentRenderer() {
u32 Init(GameVersion version) {
lg::info("GFX Init");
// initialize settings
InitSettings(g_settings);
// guarantee we have no keys detected by pad
Pad::ForceClearKeys();
prof().instant_event("ROOT");
LoadSettings();
SetRenderer(g_settings.renderer);
g_debug_settings = game_settings::DebugSettings();
{
auto p = scoped_prof("startup::gfx::get_renderer");
g_global_settings.renderer = GetRenderer(GfxPipeline::OpenGL);
}
if (GetCurrentRenderer()->init(g_settings)) {
lg::error("Gfx::Init error");
return 1;
{
auto p = scoped_prof("startup::gfx::init_current_renderer");
if (GetCurrentRenderer()->init(g_global_settings)) {
lg::error("Gfx::Init error");
return 1;
}
}
if (g_main_thread_id != std::this_thread::get_id()) {
lg::error("Ran Gfx::Init outside main thread. Init display elsewhere?");
} else {
std::string title = "OpenGOAL";
if (g_game_version == GameVersion::Jak2) {
title += " - Work in Progress";
{
auto p = scoped_prof("startup::gfx::init_main_display");
std::string title = "OpenGOAL";
if (g_game_version == GameVersion::Jak2) {
title += " - Work in Progress";
}
title += fmt::format(" - {} - {}", version_to_game_name_external(g_game_version),
build_revision());
Display::InitMainDisplay(640, 480, title.c_str(), g_global_settings, version);
}
title +=
fmt::format(" - {} - {}", version_to_game_name_external(g_game_version), build_revision());
Display::InitMainDisplay(640, 480, title.c_str(), g_settings, version);
}
return 0;
@@ -345,7 +92,6 @@ void Loop(std::function<bool()> f) {
auto p = scoped_prof("gfx loop");
// check if we have a display
if (Display::GetMainDisplay()) {
// lg::debug("run display");
Display::GetMainDisplay()->render();
}
}
@@ -355,6 +101,7 @@ u32 Exit() {
lg::info("GFX Exit");
Display::KillMainDisplay();
GetCurrentRenderer()->exit();
g_debug_settings.save_settings();
return 0;
}
@@ -383,194 +130,6 @@ u32 sync_path() {
return 0;
}
void send_chain(const void* data, u32 offset) {
if (GetCurrentRenderer()) {
GetCurrentRenderer()->send_chain(data, offset);
}
}
void texture_upload_now(const u8* tpage, int mode, u32 s7_ptr) {
if (GetCurrentRenderer()) {
GetCurrentRenderer()->texture_upload_now(tpage, mode, s7_ptr);
}
}
void texture_relocate(u32 destination, u32 source, u32 format) {
if (GetCurrentRenderer()) {
GetCurrentRenderer()->texture_relocate(destination, source, format);
}
}
void set_levels(const std::vector<std::string>& levels) {
if (GetCurrentRenderer()) {
GetCurrentRenderer()->set_levels(levels);
}
}
void poll_events() {
if (GetCurrentRenderer()) {
GetCurrentRenderer()->poll_events();
}
}
u64 get_window_width() {
if (Display::GetMainDisplay()) {
return Display::GetMainDisplay()->width();
} else {
return 0;
}
}
u64 get_window_height() {
if (Display::GetMainDisplay()) {
return Display::GetMainDisplay()->height();
} else {
return 0;
}
}
void set_window_size(u64 w, u64 h) {
if (Display::GetMainDisplay()) {
Display::GetMainDisplay()->set_size(w, h);
}
}
void get_window_scale(float* x, float* y) {
if (Display::GetMainDisplay()) {
Display::GetMainDisplay()->get_scale(x, y);
}
}
std::tuple<double, double> get_mouse_pos() {
if (Display::GetMainDisplay()) {
return Display::GetMainDisplay()->get_mouse_pos();
} else {
return {0, 0};
}
}
GfxDisplayMode get_fullscreen() {
if (Display::GetMainDisplay()) {
return Display::GetMainDisplay()->fullscreen_mode();
} else {
return GfxDisplayMode::Windowed;
}
}
int get_screen_vmode_count() {
if (Display::GetMainDisplay()) {
return Display::GetMainDisplay()->get_screen_vmode_count();
}
return 0;
}
int get_screen_rate(s64 vmode_idx) {
if (Display::GetMainDisplay()) {
return Display::GetMainDisplay()->get_screen_rate(vmode_idx);
}
return 0;
}
int get_monitor_count() {
if (Display::GetMainDisplay()) {
return Display::GetMainDisplay()->get_monitor_count();
}
return 0;
}
void get_screen_size(s64 vmode_idx, s32* w, s32* h) {
if (Display::GetMainDisplay()) {
Display::GetMainDisplay()->get_screen_size(vmode_idx, w, h);
}
}
void set_vsync(bool vsync) {
g_global_settings.vsync = vsync;
}
void set_frame_rate(int rate) {
g_global_settings.target_fps = rate;
}
void set_letterbox(int w, int h) {
g_global_settings.lbox_w = w;
g_global_settings.lbox_h = h;
}
void set_fullscreen(GfxDisplayMode mode, int screen) {
if (Display::GetMainDisplay()) {
Display::GetMainDisplay()->set_fullscreen(mode, screen);
}
}
void set_window_lock(bool lock) {
if (Display::GetMainDisplay()) {
Display::GetMainDisplay()->set_lock(lock);
}
}
void set_game_resolution(int w, int h) {
g_global_settings.game_res_w = w;
g_global_settings.game_res_h = h;
}
void set_msaa(int samples) {
g_global_settings.msaa_samples = samples;
}
void input_mode_set(u32 enable) {
if (enable == s7.offset + jak1_symbols::FIX_SYM_TRUE) { // #t
Pad::g_input_mode_mapping = g_settings.pad_mapping_info;
Pad::EnterInputMode();
} else {
Pad::ExitInputMode(enable != s7.offset); // use #f for graceful exit, or 'canceled for abrupt
}
}
void input_mode_save() {
if (Pad::input_mode_get() == (u64)Pad::InputModeStatus::Enabled) {
lg::error("Can't save controller mapping while mapping controller.");
} else if (Pad::input_mode_get() == (u64)Pad::InputModeStatus::Disabled) {
g_settings.pad_mapping_info_backup = g_settings.pad_mapping_info; // copy to backup
g_settings.pad_mapping_info = Pad::g_input_mode_mapping; // set current mapping
SavePeripheralSettings();
}
}
s64 get_mapped_button(s64 pad, s64 button) {
if (pad < 0 || pad > Pad::CONTROLLER_COUNT || button < 0 || button > 16) {
lg::error("Invalid parameters to get_mapped_button({}, {})", pad, button);
return -1;
}
return (Pad::GetGamepadState(pad) > -1)
? (s64)g_settings.pad_mapping_info.controller_button_mapping[pad][button]
: (s64)g_settings.pad_mapping_info.keyboard_button_mapping[pad][button];
}
int PadIsPressed(Pad::Button button, int port) {
return Pad::IsPressed(g_settings.pad_mapping_info, button, port);
}
int PadGetAnalogValue(Pad::Analog analog, int port) {
return Pad::GetAnalogValue(g_settings.pad_mapping_info, analog, port);
}
void SetLod(RendererTreeType tree, int lod) {
switch (tree) {
case RendererTreeType::TFRAG3:
g_global_settings.lod_tfrag = lod;
break;
case RendererTreeType::TIE3:
g_global_settings.lod_tie = lod;
break;
default:
lg::error("Invalid tree {} specified for SetLod ({})", fmt::underlying(tree), lod);
break;
}
}
bool CollisionRendererGetMask(GfxGlobalSettings::CollisionRendererMode mode, int mask_id) {
int arr_idx = mask_id / 32;
int arr_ofs = mask_id % 32;
+10 -73
View File
@@ -14,24 +14,24 @@
#include "common/versions/versions.h"
#include "game/kernel/common/kboot.h"
#include "game/system/newpad.h"
#include "game/tools/filter_menu/filter_menu.h"
#include "game/settings/settings.h"
#include "game/system/hid/display_manager.h"
#include "game/system/hid/input_manager.h"
// forward declarations
struct GfxSettings;
struct GfxGlobalSettings;
class GfxDisplay;
// enum for rendering pipeline
enum class GfxPipeline { Invalid = 0, OpenGL };
enum GfxDisplayMode { ForceUpdate = -1, Windowed = 0, Fullscreen = 1, Borderless = 2 };
// module for the different rendering pipelines
struct GfxRendererModule {
std::function<int(GfxSettings&)> init;
std::function<int(GfxGlobalSettings&)> init;
std::function<std::shared_ptr<GfxDisplay>(int width,
int height,
const char* title,
GfxSettings& settings,
GfxGlobalSettings& settings,
GameVersion version,
bool is_main)>
make_display;
@@ -41,38 +41,19 @@ struct GfxRendererModule {
std::function<void(const void*, u32)> send_chain;
std::function<void(const u8*, int, u32)> texture_upload_now;
std::function<void(u32, u32, u32)> texture_relocate;
std::function<void()> poll_events;
std::function<void(const std::vector<std::string>&)> set_levels;
std::function<void(float)> set_pmode_alp;
GfxPipeline pipeline;
const char* name;
};
// store settings related to the gfx systems
// TODO merge with globalsettings
struct GfxSettings {
// current version of the settings. this should be set up so that newer versions are always higher
// than older versions
// increment this whenever you change this struct.
// there's probably a smarter way to do this (automatically deduce size etc.)
static constexpr u64 CURRENT_VERSION = 0x0000'0000'0004'0001;
u64 version; // the version of this settings struct. MUST ALWAYS BE THE FIRST THING!
Pad::MappingInfo pad_mapping_info; // button mapping
Pad::MappingInfo pad_mapping_info_backup; // button mapping backup (see newpad.h)
int vsync; // (temp) number of screen update per frame
bool debug; // graphics debugging
GfxPipeline renderer = GfxPipeline::Invalid; // which rendering pipeline to use.
};
// runtime settings
static constexpr int PAT_MOD_COUNT = 3;
static constexpr int PAT_EVT_COUNT = 7;
static constexpr int PAT_MAT_COUNT = 23;
struct GfxGlobalSettings {
bool debug = true; // graphics debugging
// note: this is actually the size of the display that ISN'T letterboxed
// the excess space is what will be letterboxed away.
int lbox_w = 640;
@@ -83,7 +64,7 @@ struct GfxGlobalSettings {
int game_res_h = 480;
// multi-sampled anti-aliasing sample count. 1 = disabled.
int msaa_samples = 4;
int msaa_samples = 2;
// current renderer
const GfxRendererModule* renderer;
@@ -122,20 +103,7 @@ struct GfxGlobalSettings {
namespace Gfx {
extern GfxGlobalSettings g_global_settings;
extern GfxSettings g_settings;
struct DebugSettings {
bool show_imgui = false;
bool ignore_imgui_hide_keybind = false;
std::vector<DebugTextFilter> debug_text_filters = {};
bool debug_text_check_range = false;
float debug_text_max_range = 0;
void load_settings(const ghc::filesystem::path& filepath);
void save_settings();
};
extern DebugSettings g_debug_settings;
extern game_settings::DebugSettings g_debug_settings;
const GfxRendererModule* GetCurrentRenderer();
@@ -143,44 +111,13 @@ u32 Init(GameVersion version);
void Loop(std::function<bool()> f);
u32 Exit();
Pad::MappingInfo& get_button_mapping();
u32 vsync();
void register_vsync_callback(std::function<void()> f);
void clear_vsync_callback();
u32 sync_path();
void send_chain(const void* data, u32 offset);
void texture_upload_now(const u8* tpage, int mode, u32 s7_ptr);
void texture_relocate(u32 destination, u32 source, u32 format);
void set_levels(const std::vector<std::string>& levels);
void poll_events();
u64 get_window_width();
u64 get_window_height();
void set_window_size(u64 w, u64 h);
void get_window_scale(float* x, float* y);
std::tuple<double, double> get_mouse_pos();
GfxDisplayMode get_fullscreen();
int get_screen_vmode_count();
int get_screen_rate(s64 vmode_idx);
int get_monitor_count();
void get_screen_size(s64 vmode_idx, s32* w, s32* h);
void set_frame_rate(int rate);
void set_vsync(bool vsync);
void set_letterbox(int w, int h);
void set_fullscreen(GfxDisplayMode mode, int screen);
void set_window_lock(bool lock);
void set_game_resolution(int w, int h);
void set_msaa(int samples);
void input_mode_set(u32 enable);
void input_mode_save();
s64 get_mapped_button(s64 pad, s64 button);
int PadIsPressed(Pad::Button button, int port);
int PadGetAnalogValue(Pad::Analog analog, int port);
// matching enum in kernel-defs.gc !!
enum class RendererTreeType { NONE = 0, TFRAG3 = 1, TIE3 = 2, INVALID };
void SetLod(RendererTreeType tree, int lod);
bool CollisionRendererGetMask(GfxGlobalSettings::CollisionRendererMode mode, int mask_id);
void CollisionRendererSetMask(GfxGlobalSettings::CollisionRendererMode mode, int mask_id);
void CollisionRendererClearMask(GfxGlobalSettings::CollisionRendererMode mode, int mask_id);
@@ -771,7 +771,7 @@ void OpenGLRenderer::draw_renderer_selection_window() {
* Pre-render frame setup.
*/
void OpenGLRenderer::setup_frame(const RenderOptions& settings) {
// glfw controls the window framebuffer, so we just update the size:
// SDL controls the window framebuffer, so we just update the size:
auto& window_fb = m_fbo_state.resources.window;
bool window_resized = window_fb.width != settings.window_framebuffer_width ||
@@ -863,12 +863,6 @@ void OpenGLRenderer::setup_frame(const RenderOptions& settings) {
m_render_state.draw_offset_y =
(settings.window_framebuffer_height - m_render_state.draw_region_h) / 2;
if (settings.borderless_windows_hacks) {
// pretend the framebuffer is 1 pixel shorter on borderless. fullscreen issues!
// add one pixel of vertical letterbox on borderless to make up for extra line
m_render_state.draw_offset_y++;
}
m_render_state.render_fb = m_fbo_state.render_fbo->fbo_id;
m_render_state.back_fbo = &m_fbo_state.resources.back_buffer;
@@ -22,7 +22,7 @@ struct RenderOptions {
bool draw_filters_window = false;
// internal rendering settings - The OpenGLRenderer will internally use this resolution/format.
int msaa_samples = 4;
int msaa_samples = 2;
int game_res_w = 640;
int game_res_h = 480;
@@ -37,9 +37,6 @@ struct RenderOptions {
int draw_region_height = 0;
int draw_region_width = 0;
// windows-specific tweaks to the size of the drawing area in borderless.
bool borderless_windows_hacks = false;
bool save_screenshot = false;
std::string screenshot_path;
+1 -1
View File
@@ -115,7 +115,7 @@ void Profiler::draw_node(ProfilerNode& node, bool expand, int depth, float start
color_orange = ImGui::IsItemHovered();
} else {
if (expand) {
ImGui::SetNextTreeNodeOpen(true);
ImGui::SetNextItemOpen(true);
}
if (ImGui::TreeNode(node.m_name.c_str(), "%s", str.c_str())) {
color_orange = ImGui::IsItemHovered();
+44 -24
View File
@@ -3,9 +3,12 @@
#include <algorithm>
#include "common/global_profiler/GlobalProfiler.h"
#include "game/graphics/gfx.h"
#include "third-party/imgui/imgui.h"
#include "third-party/imgui/imgui_style.h"
void FrameTimeRecorder::finish_frame() {
m_frame_times[m_idx++] = m_compute_timer.getMs();
@@ -93,7 +96,7 @@ void OpenGlDebugGui::finish_frame() {
void OpenGlDebugGui::draw(const DmaStats& dma_stats) {
if (ImGui::BeginMainMenuBar()) {
if (ImGui::BeginMenu("Windows")) {
if (ImGui::BeginMenu("Debugging")) {
ImGui::MenuItem("Frame Time Plot", nullptr, &m_draw_frame_time);
ImGui::MenuItem("Render Debug", nullptr, &m_draw_debug);
ImGui::MenuItem("Profiler", nullptr, &m_draw_profiler);
@@ -103,35 +106,52 @@ void OpenGlDebugGui::draw(const DmaStats& dma_stats) {
}
if (ImGui::BeginMenu("Tools")) {
if (ImGui::BeginMenu("Screenshot")) {
ImGui::MenuItem("Screenshot Next Frame!", nullptr, &m_want_screenshot);
ImGui::InputText("File", m_screenshot_save_name, 50);
ImGui::InputInt("Width", &screenshot_width);
ImGui::InputInt("Height", &screenshot_height);
ImGui::InputInt("MSAA", &screenshot_samples);
ImGui::Checkbox("Screenshot on f2", &screenshot_hotkey_enabled);
ImGui::EndMenu();
}
ImGui::MenuItem("Subtitle Editor", nullptr, &m_subtitle_editor);
ImGui::EndMenu();
}
if (ImGui::BeginMenu("Settings")) {
if (ImGui::TreeNode("ImGui Styling (restart required for these)")) {
ImGui::InputInt("Font Size", &Gfx::g_debug_settings.imgui_font_size);
ImGui::Checkbox("Monospaced Font", &Gfx::g_debug_settings.monospaced_font);
if (ImGui::Checkbox("Alternate Style", &Gfx::g_debug_settings.alternate_style)) {
if (Gfx::g_debug_settings.alternate_style) {
ImGui::applyAlternateStyle();
} else {
ImGui::StyleColorsClassic();
}
}
ImGui::TreePop();
}
ImGui::Checkbox("Ignore Hide ImGui Bind", &Gfx::g_debug_settings.ignore_hide_imgui);
if (ImGui::BeginMenu("Frame Rate")) {
ImGui::Checkbox("Framelimiter", &Gfx::g_global_settings.framelimiter);
ImGui::InputFloat("Target FPS", &target_fps_input);
if (ImGui::MenuItem("Apply")) {
Gfx::g_global_settings.target_fps = target_fps_input;
}
ImGui::Separator();
ImGui::Checkbox("Accurate Lag Mode", &Gfx::g_global_settings.experimental_accurate_lag);
ImGui::Checkbox("Sleep in Frame Limiter", &Gfx::g_global_settings.sleep_in_frame_limiter);
ImGui::EndMenu();
}
ImGui::MenuItem("Filters", nullptr, &m_filters_menu);
ImGui::EndMenu();
}
if (ImGui::BeginMenu("Screenshot")) {
ImGui::MenuItem("Screenshot Next Frame!", nullptr, &m_want_screenshot);
ImGui::InputText("File", m_screenshot_save_name, 50);
ImGui::InputInt("Width", &screenshot_width);
ImGui::InputInt("Height", &screenshot_height);
ImGui::InputInt("MSAA", &screenshot_samples);
ImGui::Checkbox("Screenshot on f2", &screenshot_hotkey_enabled);
ImGui::EndMenu();
}
if (ImGui::BeginMenu("Frame Rate")) {
ImGui::Checkbox("Framelimiter", &Gfx::g_global_settings.framelimiter);
ImGui::InputFloat("Target FPS", &target_fps_input);
if (ImGui::MenuItem("Apply")) {
Gfx::g_global_settings.target_fps = target_fps_input;
}
ImGui::Separator();
ImGui::Checkbox("Accurate Lag Mode", &Gfx::g_global_settings.experimental_accurate_lag);
ImGui::Checkbox("Sleep in Frame Limiter", &Gfx::g_global_settings.sleep_in_frame_limiter);
ImGui::EndMenu();
}
if (ImGui::BeginMenu("Event Profiler")) {
ImGui::Checkbox("Record", &record_events);
if (ImGui::Checkbox("Record", &record_events)) {
prof().set_enable(record_events);
}
ImGui::MenuItem("Dump to file", nullptr, &dump_events);
ImGui::EndMenu();
}
File diff suppressed because it is too large Load Diff
+17 -63
View File
@@ -11,84 +11,43 @@
#include "game/graphics/display.h"
#include "game/graphics/gfx.h"
#include "third-party/SDL/include/SDL.h"
#include "third-party/glad/include/glad/glad.h"
#include "third-party/glfw/include/GLFW/glfw3.h"
enum GlfwKeyAction {
Release = GLFW_RELEASE, // falling edge of key press
Press = GLFW_PRESS, // rising edge of key press
Repeat = GLFW_REPEAT // repeated input on hold e.g. when typing something
};
enum GlfwKeyCustomAxis {
CURSOR_X_AXIS = GLFW_GAMEPAD_AXIS_LAST + 1,
CURSOR_Y_AXIS = GLFW_GAMEPAD_AXIS_LAST + 2
};
class GLDisplay : public GfxDisplay {
public:
GLDisplay(GLFWwindow* window, bool is_main);
GLDisplay(SDL_Window* window, SDL_GLContext gl_context, bool is_main);
virtual ~GLDisplay();
void* get_window() const override { return m_window; }
void get_position(int* x, int* y) override;
void get_size(int* w, int* h) override;
void get_scale(float* x, float* y) override;
void get_screen_size(int vmode_idx, s32* w, s32* h) override;
int get_screen_rate(int vmode_idx) override;
int get_screen_vmode_count() override;
int get_monitor_count() override;
std::tuple<double, double> get_mouse_pos() override;
void set_size(int w, int h) override;
void update_fullscreen(GfxDisplayMode mode, int screen) override;
// Overrides
std::shared_ptr<DisplayManager> get_display_manager() const override { return m_display_manager; }
std::shared_ptr<InputManager> get_input_manager() const override { return m_input_manager; }
void render() override;
bool minimized() override;
bool fullscreen_pending() override;
void fullscreen_flush() override;
void set_lock(bool lock) override;
void on_key(GLFWwindow* window, int key, int scancode, int action, int mods);
void on_window_pos(GLFWwindow* window, int xpos, int ypos);
void on_window_size(GLFWwindow* window, int width, int height);
void on_iconify(GLFWwindow* window, int iconified);
void on_mouse_key(GLFWwindow* window, int button, int action, int mode);
void on_cursor_position(GLFWwindow* window, double xposition, double yposition);
void update_cursor_visibility(GLFWwindow* window, bool is_visible);
private:
void update_glfw();
SDL_Window* m_window;
SDL_GLContext m_gl_context;
GLFWwindow* m_window;
bool m_minimized = false;
GLFWvidmode m_last_video_mode = {0, 0, 0, 0, 0, 0};
std::shared_ptr<DisplayManager> m_display_manager;
std::shared_ptr<InputManager> m_input_manager;
bool is_cursor_position_valid = false;
double last_cursor_x_position = 0;
double last_cursor_y_position = 0;
static constexpr int MAX_VMODES = 256;
struct VMode {
void set(const GLFWvidmode* vmode);
int width = 640, height = 480;
int refresh_rate = 60;
};
bool m_should_quit = false;
bool m_take_screenshot_next_frame = false;
void process_sdl_events();
struct DisplayState {
// move it a bit away from the top by default
s32 window_pos_x = 50;
s32 window_pos_y = 50;
int window_size_width = 640, window_size_height = 480;
float window_scale_x = 1.f, window_scale_y = 1.f;
int window_size_width = 640;
int window_size_height = 480;
float window_scale_x = 1.f;
float window_scale_y = 1.f;
bool pending_size_change = false;
s32 requested_size_width = 0;
s32 requested_size_height = 0;
int num_vmodes = 0;
VMode vmodes[MAX_VMODES];
int largest_vmode_width = 640, largest_vmode_height = 480;
int largest_vmode_refresh_rate = 60;
VMode current_vmode;
} m_display_state, m_display_state_copy;
std::mutex m_lock;
@@ -97,11 +56,6 @@ class GLDisplay : public GfxDisplay {
int width = 0;
int height = 0;
} m_pending_size;
GLFWmonitor* get_monitor(int index);
};
extern const GfxRendererModule gRendererOpenGL;
namespace glfw {
static const int NUM_KEYS = GLFW_KEY_LAST + GLFW_MOUSE_BUTTON_LAST + 1;
}
+521 -186
View File
@@ -9,6 +9,8 @@
#include "common/util/Timer.h"
#include "common/util/string_util.h"
#include "game/external/discord.h"
#include "game/graphics/display.h"
#include "game/graphics/gfx.h"
#include "game/kernel/common/Ptr.h"
#include "game/kernel/common/kernel_types.h"
@@ -19,6 +21,7 @@
#include "game/sce/libpad.h"
#include "game/sce/libscf.h"
#include "game/sce/sif_ee.h"
#include "game/system/vm/vm.h"
/*!
* Where does OVERLORD load its data from?
@@ -350,157 +353,6 @@ void DecodeTime(u32 ptr) {
sceCdReadClock(clock.c());
}
/*!
* PC PORT FUNCTIONS BEGIN
*/
/*!
* Get a 300MHz timer value.
* Called from EE thread
*/
u64 read_ee_timer() {
u64 ns = ee_clock_timer.getNs();
return (ns * 3) / 10;
}
/*!
* Do a fast memory copy.
*/
void c_memmove(u32 dst, u32 src, u32 size) {
memmove(Ptr<u8>(dst).c(), Ptr<u8>(src).c(), size);
}
void set_game_resolution(s64 w, s64 h) {
Gfx::set_game_resolution(w, h);
}
void set_msaa(s64 samples) {
Gfx::set_msaa(samples);
}
/*!
* Returns size of window. Called from game thread
*/
void get_window_size(u32 w_ptr, u32 h_ptr) {
if (w_ptr) {
auto w = Ptr<u32>(w_ptr).c();
*w = Gfx::get_window_width();
}
if (h_ptr) {
auto h = Ptr<u32>(h_ptr).c();
*h = Gfx::get_window_height();
}
}
/*!
* Returns scale of window. This is for DPI stuff.
*/
void get_window_scale(u32 x_ptr, u32 y_ptr) {
float* x = x_ptr ? Ptr<float>(x_ptr).c() : NULL;
float* y = y_ptr ? Ptr<float>(y_ptr).c() : NULL;
Gfx::get_window_scale(x, y);
}
/*!
* Returns resolution of the monitor.
*/
void get_screen_size(s64 vmode_idx, u32 w_ptr, u32 h_ptr) {
s32 *w_out = 0, *h_out = 0;
if (w_ptr) {
w_out = Ptr<s32>(w_ptr).c();
}
if (h_ptr) {
h_out = Ptr<s32>(h_ptr).c();
}
Gfx::get_screen_size(vmode_idx, w_out, h_out);
}
/*!
* Returns refresh rate of the monitor.
*/
s64 get_screen_rate(s64 vmode_idx) {
return Gfx::get_screen_rate(vmode_idx);
}
/*!
* Returns amount of video modes of the monitor.
*/
s64 get_screen_vmode_count() {
return Gfx::get_screen_vmode_count();
}
/*!
* Returns the number of available monitors.
*/
int get_monitor_count() {
return Gfx::get_monitor_count();
}
int get_unix_timestamp() {
return std::time(nullptr);
}
void mkdir_path(u32 filepath) {
auto filepath_str = std::string(Ptr<String>(filepath).c()->data());
file_util::create_dir_if_needed_for_file(filepath_str);
}
u64 filepath_exists(u32 filepath) {
auto filepath_str = std::string(Ptr<String>(filepath).c()->data());
if (fs::exists(filepath_str)) {
return s7.offset + true_symbol_offset(g_game_version);
}
return s7.offset;
}
void prof_event(u32 name, u32 kind) {
prof().event(Ptr<String>(name).c()->data(), (ProfNode::Kind)kind);
}
void set_frame_rate(s64 rate) {
Gfx::set_frame_rate(rate);
}
void set_vsync(u32 symptr) {
Gfx::set_vsync(symptr != s7.offset);
}
void set_window_lock(u32 symptr) {
Gfx::set_window_lock(symptr == s7.offset);
}
void set_collision(u32 symptr) {
Gfx::g_global_settings.collision_enable = symptr != s7.offset;
}
void set_collision_wireframe(u32 symptr) {
Gfx::g_global_settings.collision_wireframe = symptr != s7.offset;
}
void set_collision_mask(GfxGlobalSettings::CollisionRendererMode mode, int mask, u32 symptr) {
if (symptr != s7.offset) {
Gfx::CollisionRendererSetMask(mode, mask);
} else {
Gfx::CollisionRendererClearMask(mode, mask);
}
}
u32 get_collision_mask(GfxGlobalSettings::CollisionRendererMode mode, int mask) {
return Gfx::CollisionRendererGetMask(mode, mask) ? s7.offset + true_symbol_offset(g_game_version)
: s7.offset;
}
void set_gfx_hack(u64 which, u32 symptr) {
switch (which) {
case 0: // no tex
Gfx::g_global_settings.hack_no_tex = symptr != s7.offset;
break;
}
}
/*!
* PC PORT FUNCTIONS END
*/
void vif_interrupt_callback(int bucket_id) {
// added for the PC port for faking VIF interrupts from the graphics system.
if (vif1_interrupt_handler && MasterExit == RuntimeExitStatus::RUNNING) {
@@ -508,39 +360,18 @@ void vif_interrupt_callback(int bucket_id) {
}
}
/*!
* Added in PC port.
*/
/// PC PORT FUNCTIONS BEGIN
u32 offset_of_s7() {
return s7.offset;
}
/*!
* Called from the game thread at initialization.
* The game thread is the only one to touch the mips2c function table (through the linker and
* through this function), so no locking is needed.
*/
u64 pc_get_mips2c(u32 name) {
const char* n = Ptr<String>(name).c()->data();
return Mips2C::gLinkedFunctionTable.get(n);
inline bool symbol_to_bool(const u32 symptr) {
return symptr != s7.offset;
}
/*!
* Called from game thread to submit rendering DMA chain.
*/
void send_gfx_dma_chain(u32 /*bank*/, u32 chain) {
Gfx::send_chain(g_ee_main_mem, chain);
}
/*!
* Called from game thread to upload a texture outside of the main DMA chain.
*/
void pc_texture_upload_now(u32 page, u32 mode) {
Gfx::texture_upload_now(Ptr<u8>(page).c(), mode, s7.offset);
}
void pc_texture_relocate(u32 dst, u32 src, u32 format) {
Gfx::texture_relocate(dst, src, format);
inline u64 bool_to_symbol(const bool val) {
return val ? static_cast<u64>(s7.offset) + true_symbol_offset(g_game_version) : s7.offset;
}
u64 pc_filter_debug_string(u32 str_ptr, u32 dist_ptr) {
@@ -549,40 +380,544 @@ u64 pc_filter_debug_string(u32 str_ptr, u32 dist_ptr) {
memcpy(&dist, &dist_ptr, 4);
// Check distance first
if (Gfx::g_debug_settings.debug_text_check_range) {
if (dist / 4096.F > Gfx::g_debug_settings.debug_text_max_range) {
return s7.offset + true_symbol_offset(g_game_version);
if (Gfx::g_debug_settings.text_check_range) {
if (dist / 4096.F > Gfx::g_debug_settings.text_max_range) {
return bool_to_symbol(true);
}
}
// Get the current filters
const auto& filters = Gfx::g_debug_settings.debug_text_filters;
const auto& filters = Gfx::g_debug_settings.text_filters;
if (filters.empty()) {
// there are no filters, exit early
return s7.offset;
return bool_to_symbol(false);
}
// Currently very dumb contains check
for (const auto& filter : filters) {
if (filter.type == DebugTextFilter::Type::CONTAINS) {
if (!str.empty() && !filter.content.empty() && !str_util::contains(str, filter.content)) {
return s7.offset + true_symbol_offset(g_game_version);
return bool_to_symbol(true);
}
} else if (filter.type == DebugTextFilter::Type::NOT_CONTAINS) {
if (!str.empty() && !filter.content.empty() && str_util::contains(str, filter.content)) {
return s7.offset + true_symbol_offset(g_game_version);
return bool_to_symbol(true);
}
} else if (filter.type == DebugTextFilter::Type::REGEX) {
if (str_util::valid_regex(filter.content) &&
std::regex_match(str, std::regex(filter.content))) {
return s7.offset + true_symbol_offset(g_game_version);
return bool_to_symbol(true);
}
}
}
return bool_to_symbol(false);
}
CommonPCPortFunctionWrappers g_pc_port_funcs;
u64 read_ee_timer() {
u64 ns = ee_clock_timer.getNs();
return (ns * 3) / 10;
}
void pc_memmove(u32 dst, u32 src, u32 size) {
memmove(Ptr<u8>(dst).c(), Ptr<u8>(src).c(), size);
}
void send_gfx_dma_chain(u32 bank, u32 chain) {
if (Gfx::GetCurrentRenderer()) {
Gfx::GetCurrentRenderer()->send_chain(g_ee_main_mem, chain);
}
}
void pc_texture_upload_now(u32 page, u32 mode) {
if (Gfx::GetCurrentRenderer()) {
Gfx::GetCurrentRenderer()->texture_upload_now(Ptr<u8>(page).c(), mode, s7.offset);
}
}
void pc_texture_relocate(u32 dst, u32 src, u32 format) {
if (Gfx::GetCurrentRenderer()) {
Gfx::GetCurrentRenderer()->texture_relocate(dst, src, format);
}
}
u64 pc_get_mips2c(u32 name) {
const char* n = Ptr<String>(name).c()->data();
return Mips2C::gLinkedFunctionTable.get(n);
}
u64 pc_get_display_name(u32 id) {
std::string name = "";
if (Display::GetMainDisplay()) {
name = Display::GetMainDisplay()->get_display_manager()->get_connected_display_name(id);
}
if (name.empty()) {
return s7.offset;
}
return g_pc_port_funcs.make_string_from_c(str_util::to_upper(name).c_str());
}
u32 pc_get_display_mode() {
auto display_mode = WindowDisplayMode::Windowed;
if (Display::GetMainDisplay()) {
display_mode = Display::GetMainDisplay()->get_display_manager()->get_window_display_mode();
}
switch (display_mode) {
case WindowDisplayMode::Borderless:
return g_pc_port_funcs.intern_from_c("borderless").offset;
case WindowDisplayMode::Fullscreen:
return g_pc_port_funcs.intern_from_c("fullscreen").offset;
default:
case WindowDisplayMode::Windowed:
return g_pc_port_funcs.intern_from_c("windowed").offset;
}
}
void pc_set_display_mode(u32 symptr) {
if (!Display::GetMainDisplay()) {
return;
}
if (symptr == g_pc_port_funcs.intern_from_c("windowed").offset || symptr == s7.offset) {
Display::GetMainDisplay()->get_display_manager()->set_window_display_mode(
WindowDisplayMode::Windowed);
} else if (symptr == g_pc_port_funcs.intern_from_c("borderless").offset) {
Display::GetMainDisplay()->get_display_manager()->set_window_display_mode(
WindowDisplayMode::Borderless);
} else if (symptr == g_pc_port_funcs.intern_from_c("fullscreen").offset) {
Display::GetMainDisplay()->get_display_manager()->set_window_display_mode(
WindowDisplayMode::Fullscreen);
}
}
u64 pc_get_display_count() {
if (Display::GetMainDisplay()) {
return Display::GetMainDisplay()->get_display_manager()->num_connected_displays();
}
return 0;
}
void pc_get_active_display_size(u32 w_ptr, u32 h_ptr) {
if (!Display::GetMainDisplay()) {
return;
}
if (w_ptr) {
auto w_out = Ptr<u32>(w_ptr).c();
if (w_out) {
*w_out = Display::GetMainDisplay()->get_display_manager()->get_screen_width();
}
}
if (h_ptr) {
auto h_out = Ptr<u32>(h_ptr).c();
if (h_out) {
*h_out = Display::GetMainDisplay()->get_display_manager()->get_screen_height();
}
}
}
s64 pc_get_active_display_refresh_rate() {
if (Display::GetMainDisplay()) {
return Display::GetMainDisplay()->get_display_manager()->get_active_display_refresh_rate();
}
return 0;
}
void pc_get_window_size(u32 w_ptr, u32 h_ptr) {
if (!Display::GetMainDisplay()) {
return;
}
if (w_ptr) {
auto w = Ptr<u32>(w_ptr).c();
if (w) {
*w = Display::GetMainDisplay()->get_display_manager()->get_window_width();
}
}
if (h_ptr) {
auto h = Ptr<u32>(h_ptr).c();
if (h) {
*h = Display::GetMainDisplay()->get_display_manager()->get_window_height();
}
}
}
void pc_get_window_scale(u32 x_ptr, u32 y_ptr) {
if (!Display::GetMainDisplay()) {
return;
}
if (x_ptr) {
auto x = Ptr<float>(x_ptr).c();
if (x) {
*x = Display::GetMainDisplay()->get_display_manager()->get_window_scale_x();
}
}
if (y_ptr) {
auto y = Ptr<float>(y_ptr).c();
if (y) {
*y = Display::GetMainDisplay()->get_display_manager()->get_window_scale_y();
}
}
}
void pc_get_fullscreen_display(u64 display_id) {
if (Display::GetMainDisplay()) {
Display::GetMainDisplay()->get_display_manager()->set_fullscreen_display_id(display_id);
}
}
void pc_set_window_size(u64 width, u64 height) {
if (Display::GetMainDisplay()) {
Display::GetMainDisplay()->get_display_manager()->set_window_size(width, height);
}
}
s64 pc_get_num_resolutions() {
if (Display::GetMainDisplay()) {
return Display::GetMainDisplay()->get_display_manager()->get_num_resolutions();
}
return 0;
}
void pc_get_resolution(u32 id, u32 w_ptr, u32 h_ptr) {
if (Display::GetMainDisplay()) {
auto res = Display::GetMainDisplay()->get_display_manager()->get_resolution(id);
auto w = Ptr<u32>(w_ptr).c();
if (w) {
*w = res.width;
}
auto h = Ptr<u32>(h_ptr).c();
if (h) {
*h = res.height;
}
}
}
u64 pc_get_controller_name(u32 id) {
std::string name = "";
if (Display::GetMainDisplay()) {
name = Display::GetMainDisplay()->get_input_manager()->get_controller_name(id);
}
if (name.empty()) {
return s7.offset;
}
return g_pc_port_funcs.make_string_from_c(str_util::to_upper(name).c_str());
}
u64 pc_get_current_bind(s32 bind_assignment_info) {
if (!Display::GetMainDisplay()) {
// TODO - return something that lets the runtime use a translatable string if unknown
return g_pc_port_funcs.make_string_from_c(str_util::to_upper("unknown").c_str());
}
auto info = bind_assignment_info ? Ptr<BindAssignmentInfo>(bind_assignment_info).c() : NULL;
auto port = (int)info->port;
auto device_type = (int)info->device_type;
auto for_button = info->buttons != s7.offset;
auto input_idx = (int)info->input_idx;
auto analog_min_range = info->analog_min_range != s7.offset;
if (Display::GetMainDisplay()) {
auto name = Display::GetMainDisplay()->get_input_manager()->get_current_bind(
port, (InputDeviceType)device_type, for_button, input_idx, analog_min_range);
if (name.empty()) {
return s7.offset;
}
return g_pc_port_funcs.make_string_from_c(str_util::to_upper(name).c_str());
}
// TODO - return something that lets the runtime use a translatable string if unknown
return g_pc_port_funcs.make_string_from_c(str_util::to_upper("unknown").c_str());
}
u64 pc_get_controller_count() {
if (Display::GetMainDisplay()) {
return Display::GetMainDisplay()->get_input_manager()->get_num_controllers();
}
return 0;
}
void pc_get_controller(u32 controller_id, u32 port) {
if (Display::GetMainDisplay()) {
Display::GetMainDisplay()->get_input_manager()->set_controller_for_port(controller_id, port);
}
}
void pc_set_keyboard_enabled(u32 sym_val) {
if (Display::GetMainDisplay()) {
Display::GetMainDisplay()->get_input_manager()->enable_keyboard(symbol_to_bool(sym_val));
}
}
void pc_set_mouse_options(u32 enabled, u32 control_camera, u32 control_movement) {
if (Display::GetMainDisplay()) {
Display::GetMainDisplay()->get_input_manager()->update_mouse_options(
symbol_to_bool(enabled), symbol_to_bool(control_camera), symbol_to_bool(control_movement));
}
}
void pc_set_mouse_camera_sens(u32 xsens, u32 ysens) {
float xsens_val;
memcpy(&xsens_val, &xsens, 4);
float ysens_val;
memcpy(&ysens_val, &ysens, 4);
if (Display::GetMainDisplay()) {
Display::GetMainDisplay()->get_input_manager()->set_camera_sens(xsens_val, ysens_val);
}
}
void pc_ignore_background_controller_events(u32 sym_val) {
if (Display::GetMainDisplay()) {
Display::GetMainDisplay()->get_input_manager()->ignore_background_controller_events(
symbol_to_bool(sym_val));
}
}
u64 pc_current_controller_has_led() {
if (Display::GetMainDisplay()) {
return bool_to_symbol(Display::GetMainDisplay()->get_input_manager()->controller_has_led(0));
}
return bool_to_symbol(false);
}
void pc_set_controller_led(const int port, const u8 red, const u8 green, const u8 blue) {
if (Display::GetMainDisplay()) {
Display::GetMainDisplay()->get_input_manager()->set_controller_led(port, red, green, blue);
}
}
u64 pc_waiting_for_bind() {
if (Display::GetMainDisplay()) {
return bool_to_symbol(Display::GetMainDisplay()->get_input_manager()->get_waiting_for_bind());
}
return bool_to_symbol(false);
}
void pc_set_waiting_for_bind(InputDeviceType device_type,
u32 for_analog,
u32 for_minimum_analog,
s32 input_idx) {
if (Display::GetMainDisplay()) {
Display::GetMainDisplay()->get_input_manager()->set_wait_for_bind(
device_type, symbol_to_bool(for_analog), symbol_to_bool(for_minimum_analog), input_idx);
}
}
void pc_stop_waiting_for_bind() {
if (Display::GetMainDisplay()) {
return Display::GetMainDisplay()->get_input_manager()->stop_waiting_for_bind();
}
}
void pc_reset_bindings_to_defaults(const int port, const InputDeviceType device_type) {
if (Display::GetMainDisplay()) {
Display::GetMainDisplay()->get_input_manager()->reset_input_bindings_to_defaults(port,
device_type);
}
}
void pc_set_auto_hide_cursor(u32 val) {
if (Display::GetMainDisplay()) {
Display::GetMainDisplay()->get_input_manager()->set_auto_hide_mouse(symbol_to_bool(val));
}
}
void pc_set_vsync(u32 sym_val) {
Gfx::g_global_settings.vsync = symbol_to_bool(sym_val);
}
void pc_set_msaa(int samples) {
Gfx::g_global_settings.msaa_samples = samples;
}
void pc_set_frame_rate(int rate) {
Gfx::g_global_settings.target_fps = rate;
}
void pc_set_game_resolution(int w, int h) {
Gfx::g_global_settings.game_res_w = w;
Gfx::g_global_settings.game_res_h = h;
}
void pc_set_letterbox(int w, int h) {
Gfx::g_global_settings.lbox_w = w;
Gfx::g_global_settings.lbox_h = h;
}
void pc_renderer_tree_set_lod(Gfx::RendererTreeType tree, int lod) {
switch (tree) {
case Gfx::RendererTreeType::TFRAG3:
Gfx::g_global_settings.lod_tfrag = lod;
break;
case Gfx::RendererTreeType::TIE3:
Gfx::g_global_settings.lod_tie = lod;
break;
default:
lg::error("Invalid tree {} specified for SetLod ({})", fmt::underlying(tree), lod);
break;
}
}
void pc_set_collision_mask(GfxGlobalSettings::CollisionRendererMode mode, int mask, u32 symptr) {
if (!symbol_to_bool(symptr)) {
Gfx::CollisionRendererSetMask(mode, mask);
} else {
Gfx::CollisionRendererClearMask(mode, mask);
}
}
u32 pc_get_collision_mask(GfxGlobalSettings::CollisionRendererMode mode, int mask) {
return Gfx::CollisionRendererGetMask(mode, mask) ? s7.offset + true_symbol_offset(g_game_version)
: s7.offset;
}
void pc_set_collision_wireframe(u32 symptr) {
Gfx::g_global_settings.collision_wireframe = symbol_to_bool(symptr);
}
void pc_set_collision(u32 symptr) {
Gfx::g_global_settings.collision_enable = symbol_to_bool(symptr);
}
void pc_set_gfx_hack(u64 which, u32 symptr) {
switch (which) {
case 0: // no tex
Gfx::g_global_settings.hack_no_tex = symbol_to_bool(symptr);
break;
}
}
u32 pc_get_os() {
#ifdef _WIN32
return g_pc_port_funcs.intern_from_c("windows").offset;
#elif __linux__
return g_pc_port_funcs.intern_from_c("linux").offset;
#elif __APPLE__
return g_pc_port_funcs.intern_from_c("darwin").offset;
#else
return s7.offset;
#endif
}
time_t pc_get_unix_timestamp() {
return std::time(nullptr);
}
u64 pc_filepath_exists(u32 filepath) {
auto filepath_str = std::string(Ptr<String>(filepath).c()->data());
return bool_to_symbol(fs::exists(filepath_str));
}
u64 pc_mkdir_filepath(u32 filepath) {
auto filepath_str = std::string(Ptr<String>(filepath).c()->data());
return bool_to_symbol(file_util::create_dir_if_needed_for_file(filepath_str));
}
void pc_prof(u32 name, ProfNode::Kind kind) {
prof().event(Ptr<String>(name).c()->data(), kind);
}
std::mt19937 extra_random_generator;
u32 pc_rand() {
return (u32)extra_random_generator();
}
/// Initializes all functions that are common across all game versions
/// These functions have the same implementation and do not use any game specific functions (other
/// than the one to create a function in the first place)
void init_common_pc_port_functions(
std::function<Ptr<Function>(const char*, void*)> make_func_symbol_func,
std::function<InternFromCInfo(const char*)> intern_from_c_func,
std::function<u64(const char*)> make_string_from_c_func) {
g_pc_port_funcs.intern_from_c = intern_from_c_func;
g_pc_port_funcs.make_string_from_c = make_string_from_c_func;
// Get a 300MHz timer value. Called from EE thread
make_func_symbol_func("__read-ee-timer", (void*)read_ee_timer);
// Do a fast memory copy.
make_func_symbol_func("__mem-move", (void*)pc_memmove);
// Called from game thread to submit rendering DMA chain.
make_func_symbol_func("__send-gfx-dma-chain", (void*)send_gfx_dma_chain);
// Called from game thread to upload a texture outside of the main DMA chain.
make_func_symbol_func("__pc-texture-upload-now", (void*)pc_texture_upload_now);
make_func_symbol_func("__pc-texture-relocate", (void*)pc_texture_relocate);
// Called from the game thread at initialization. The game thread is the only one to touch the
// mips2c function table (through the linker and ugh this function), so no locking is needed.
make_func_symbol_func("__pc-get-mips2c", (void*)pc_get_mips2c);
// -- DISPLAY RELATED --
// Returns the name of the display with the given id or #f if not found / empty
make_func_symbol_func("pc-get-display-name", (void*)pc_get_display_name);
make_func_symbol_func("pc-get-display-mode", (void*)pc_get_display_mode);
make_func_symbol_func("pc-set-display-mode", (void*)pc_set_display_mode);
make_func_symbol_func("pc-get-display-count", (void*)pc_get_display_count);
// Returns resolution of the monitor's current display mode
make_func_symbol_func("pc-get-active-display-size", (void*)pc_get_active_display_size);
// Returns the current refresh rate of the currently selected monitor's display mode.
make_func_symbol_func("pc-get-active-display-refresh-rate",
(void*)pc_get_active_display_refresh_rate);
// Returns size of window. Called from game thread
make_func_symbol_func("pc-get-window-size", (void*)pc_get_window_size);
// Returns scale of window. This is for DPI stuff.
make_func_symbol_func("pc-get-window-scale", (void*)pc_get_window_scale);
make_func_symbol_func("pc-set-fullscreen-display", (void*)pc_get_fullscreen_display);
make_func_symbol_func("pc-set-window-size", (void*)pc_set_window_size);
make_func_symbol_func("pc-get-num-resolutions", (void*)pc_get_num_resolutions);
make_func_symbol_func("pc-get-resolution", (void*)pc_get_resolution);
// -- INPUT RELATED --
// Returns the name of the display with the given id or #f if not found / empty
make_func_symbol_func("pc-get-controller-name", (void*)pc_get_controller_name);
make_func_symbol_func("pc-get-current-bind", (void*)pc_get_current_bind);
make_func_symbol_func("pc-get-controller-count", (void*)pc_get_controller_count);
make_func_symbol_func("pc-set-controller!", (void*)pc_get_controller);
make_func_symbol_func("pc-set-keyboard-enabled!", (void*)pc_set_keyboard_enabled);
make_func_symbol_func("pc-set-mouse-options!", (void*)pc_set_mouse_options);
make_func_symbol_func("pc-set-mouse-camera-sens!", (void*)pc_set_mouse_camera_sens);
make_func_symbol_func("pc-ignore-background-controller-events!",
(void*)pc_ignore_background_controller_events);
make_func_symbol_func("pc-current-controller-has-led?", (void*)pc_current_controller_has_led);
make_func_symbol_func("pc-set-controller-led!", (void*)pc_set_controller_led);
make_func_symbol_func("pc-waiting-for-bind?", (void*)pc_waiting_for_bind);
make_func_symbol_func("pc-set-waiting-for-bind!", (void*)pc_set_waiting_for_bind);
make_func_symbol_func("pc-stop-waiting-for-bind!", (void*)pc_stop_waiting_for_bind);
make_func_symbol_func("pc-reset-bindings-to-defaults!", (void*)pc_reset_bindings_to_defaults);
make_func_symbol_func("pc-set-auto-hide-cursor!", (void*)pc_set_auto_hide_cursor);
// graphics things
make_func_symbol_func("pc-set-vsync", (void*)pc_set_vsync);
make_func_symbol_func("pc-set-msaa", (void*)pc_set_msaa);
make_func_symbol_func("pc-set-frame-rate", (void*)pc_set_frame_rate);
make_func_symbol_func("pc-set-game-resolution", (void*)pc_set_game_resolution);
make_func_symbol_func("pc-set-letterbox", (void*)pc_set_letterbox);
make_func_symbol_func("pc-renderer-tree-set-lod", (void*)pc_renderer_tree_set_lod);
make_func_symbol_func("pc-set-collision-mode", (void*)Gfx::CollisionRendererSetMode);
make_func_symbol_func("pc-set-collision-mask", (void*)pc_set_collision_mask);
make_func_symbol_func("pc-get-collision-mask", (void*)pc_get_collision_mask);
make_func_symbol_func("pc-set-collision-wireframe", (void*)pc_set_collision_wireframe);
make_func_symbol_func("pc-set-collision", (void*)pc_set_collision);
make_func_symbol_func("pc-set-gfx-hack", (void*)pc_set_gfx_hack);
// -- OTHER --
// Return the current OS as a symbol. Actually returns what it was compiled for!
make_func_symbol_func("pc-get-os", (void*)pc_get_os);
make_func_symbol_func("pc-get-unix-timestamp", (void*)pc_get_unix_timestamp);
// file related functions
make_func_symbol_func("pc-filepath-exists?", (void*)pc_filepath_exists);
make_func_symbol_func("pc-mkdir-file-path", (void*)pc_mkdir_filepath);
// discord rich presence
make_func_symbol_func("pc-discord-rpc-set", (void*)set_discord_rpc);
// profiler
make_func_symbol_func("pc-prof", (void*)pc_prof);
// RNG
make_func_symbol_func("pc-rand", (void*)pc_rand);
// debugging tools
make_func_symbol_func("pc-filter-debug-string?", (void*)pc_filter_debug_string);
// init ps2 VM
if (VM::use) {
make_func_symbol_func("vm-ptr", (void*)VM::get_vm_ptr);
VM::vm_init();
}
}
+31 -28
View File
@@ -3,6 +3,7 @@
#include "common/common_types.h"
#include "game/graphics/gfx.h"
#include "game/kernel/common/kscheme.h"
/*!
* Where does OVERLORD load its data from?
@@ -57,33 +58,35 @@ u64 DecodeTerritory();
u64 DecodeTimeout();
u64 DecodeInactiveTimeout();
void DecodeTime(u32 ptr);
u64 read_ee_timer();
void c_memmove(u32 dst, u32 src, u32 size);
void set_game_resolution(s64 w, s64 h);
void set_msaa(s64 samples);
void get_window_size(u32 w_ptr, u32 h_ptr);
void get_window_scale(u32 x_ptr, u32 y_ptr);
void get_screen_size(s64 vmode_idx, u32 w_ptr, u32 h_ptr);
s64 get_screen_rate(s64 vmode_idx);
s64 get_screen_vmode_count();
int get_monitor_count();
int get_unix_timestamp();
void mkdir_path(u32 filepath);
u64 filepath_exists(u32 filepath);
void prof_event(u32 name, u32 kind);
void set_frame_rate(s64 rate);
void set_vsync(u32 symptr);
void set_window_lock(u32 symptr);
void set_collision(u32 symptr);
void set_collision_wireframe(u32 symptr);
void set_collision_mask(GfxGlobalSettings::CollisionRendererMode mode, int mask, u32 symptr);
u32 get_collision_mask(GfxGlobalSettings::CollisionRendererMode mode, int mask);
void set_gfx_hack(u64 which, u32 symptr);
u32 offset_of_s7();
void vif_interrupt_callback(int bucket_id);
u64 pc_get_mips2c(u32 name);
void send_gfx_dma_chain(u32 /*bank*/, u32 chain);
void pc_texture_upload_now(u32 page, u32 mode);
void pc_texture_relocate(u32 dst, u32 src, u32 format);
u64 pc_filter_debug_string(u32 str_ptr, u32 distance);
u32 pc_rand();
struct BindAssignmentInfo {
u32 port;
u32 device_type;
u32 buttons;
u32 input_idx;
u32 analog_min_range;
};
struct InternFromCInfo {
u32 offset;
u32 value;
};
// Holds function references to game specific functions for setting up common PC Port functions
// this is needed because the handlers for the functions are stateless
// and using the functions via the handler's capture lists requires templating nonsense
struct CommonPCPortFunctionWrappers {
std::function<InternFromCInfo(const char*)> intern_from_c;
std::function<u64(const char*)> make_string_from_c;
};
extern CommonPCPortFunctionWrappers g_pc_port_funcs;
/// Initializes all common PC Port functions for all Jak games
void init_common_pc_port_functions(
std::function<Ptr<Function>(const char*, void*)> make_func_symbol_func,
std::function<InternFromCInfo(const char*)> intern_from_c_func,
std::function<u64(const char*)> make_string_from_c_func);
+31 -117
View File
@@ -13,8 +13,10 @@
#include "common/log/log.h"
#include "common/symbols.h"
#include "common/util/FileUtil.h"
#include "common/util/string_util.h"
#include "game/discord.h"
#include "game/external/discord.h"
#include "game/graphics/display.h"
#include "game/graphics/gfx.h"
#include "game/graphics/sceGraphicsInterface.h"
#include "game/kernel/common/fileio.h"
@@ -384,28 +386,6 @@ int ShutdownMachine() {
return 0;
}
// todo, these could probably be moved to common
/*!
* Called from the game thread at each frame to tell the PC rendering code which levels to start
* loading. The loader internally handles locking.
*/
void pc_set_levels(u32 l0, u32 l1) {
std::string l0s = Ptr<String>(l0).c()->data();
std::string l1s = Ptr<String>(l1).c()->data();
std::vector<std::string> levels;
if (l0s != "none" && l0s != "#f") {
levels.push_back(l0s);
}
if (l1s != "none" && l1s != "#f") {
levels.push_back(l1s);
}
Gfx::set_levels(levels);
}
/*!
* Open a file-stream. Name is a GOAL string. Mode is a GOAL symbol. Use 'read for readonly
* and anything else for write only.
@@ -438,19 +418,6 @@ void PutDisplayEnv(u32 ptr) {
}
}
/*!
* Return the current OS as a symbol. Actually returns what it was compiled for!
*/
u64 get_os() {
#ifdef _WIN32
return intern_from_c("windows").offset;
#elif __linux__
return intern_from_c("linux").offset;
#else
return s7.offset;
#endif
}
void update_discord_rpc(u32 discord_info) {
if (gDiscordRpcEnabled) {
DiscordRichPresence rpc;
@@ -556,100 +523,47 @@ void update_discord_rpc(u32 discord_info) {
}
}
u32 get_fullscreen() {
switch (Gfx::get_fullscreen()) {
default:
case GfxDisplayMode::Windowed:
return intern_from_c("windowed").offset;
case GfxDisplayMode::Borderless:
return intern_from_c("borderless").offset;
case GfxDisplayMode::Fullscreen:
return intern_from_c("fullscreen").offset;
void pc_set_levels(u32 l0, u32 l1) {
if (!Gfx::GetCurrentRenderer()) {
return;
}
}
std::string l0s = Ptr<String>(l0).c()->data();
std::string l1s = Ptr<String>(l1).c()->data();
void set_fullscreen(u32 symptr, s64 screen) {
if (symptr == intern_from_c("windowed").offset || symptr == s7.offset) {
Gfx::set_fullscreen(GfxDisplayMode::Windowed, screen);
} else if (symptr == intern_from_c("borderless").offset) {
Gfx::set_fullscreen(GfxDisplayMode::Borderless, screen);
} else if (symptr == intern_from_c("fullscreen").offset) {
Gfx::set_fullscreen(GfxDisplayMode::Fullscreen, screen);
std::vector<std::string> levels;
if (l0s != "none" && l0s != "#f") {
levels.push_back(l0s);
}
if (l1s != "none" && l1s != "#f") {
levels.push_back(l1s);
}
Gfx::GetCurrentRenderer()->set_levels(levels);
}
void InitMachine_PCPort() {
// PC Port added functions
init_common_pc_port_functions(
make_function_symbol_from_c,
[](const char* name) {
const auto result = intern_from_c(name);
InternFromCInfo info{};
info.offset = result.offset;
return info;
},
make_string_from_c);
make_function_symbol_from_c("__read-ee-timer", (void*)read_ee_timer);
make_function_symbol_from_c("__mem-move", (void*)c_memmove);
make_function_symbol_from_c("__send-gfx-dma-chain", (void*)send_gfx_dma_chain);
make_function_symbol_from_c("__pc-texture-upload-now", (void*)pc_texture_upload_now);
make_function_symbol_from_c("__pc-texture-relocate", (void*)pc_texture_relocate);
make_function_symbol_from_c("__pc-get-mips2c", (void*)pc_get_mips2c);
// Game specific functions
// Called from the game thread at each frame to tell the PC rendering code which levels to start
// loading. The loader internally handles locking.
make_function_symbol_from_c("__pc-set-levels", (void*)pc_set_levels);
// pad stuff
make_function_symbol_from_c("pc-pad-get-mapped-button", (void*)Gfx::get_mapped_button);
make_function_symbol_from_c("pc-pad-input-map-save!", (void*)Gfx::input_mode_save);
make_function_symbol_from_c("pc-pad-input-mode-set", (void*)Gfx::input_mode_set);
make_function_symbol_from_c("pc-pad-input-pad-set", (void*)Pad::input_mode_pad_set);
make_function_symbol_from_c("pc-pad-input-mode-get", (void*)Pad::input_mode_get);
make_function_symbol_from_c("pc-pad-input-key-get", (void*)Pad::input_mode_get_key);
make_function_symbol_from_c("pc-pad-input-index-get", (void*)Pad::input_mode_get_index);
// os stuff
make_function_symbol_from_c("pc-get-os", (void*)get_os);
make_function_symbol_from_c("pc-get-window-size", (void*)get_window_size);
make_function_symbol_from_c("pc-get-window-scale", (void*)get_window_scale);
make_function_symbol_from_c("pc-get-fullscreen", (void*)get_fullscreen);
make_function_symbol_from_c("pc-get-screen-size", (void*)get_screen_size);
make_function_symbol_from_c("pc-get-screen-rate", (void*)get_screen_rate);
make_function_symbol_from_c("pc-get-screen-vmode-count", (void*)get_screen_vmode_count);
make_function_symbol_from_c("pc-get-monitor-count", (void*)get_monitor_count);
make_function_symbol_from_c("pc-set-window-size", (void*)Gfx::set_window_size);
make_function_symbol_from_c("pc-set-fullscreen", (void*)set_fullscreen);
make_function_symbol_from_c("pc-set-frame-rate", (void*)set_frame_rate);
make_function_symbol_from_c("pc-set-vsync", (void*)set_vsync);
make_function_symbol_from_c("pc-set-window-lock", (void*)set_window_lock);
make_function_symbol_from_c("pc-set-game-resolution", (void*)set_game_resolution);
make_function_symbol_from_c("pc-set-msaa", (void*)set_msaa);
make_function_symbol_from_c("pc-get-unix-timestamp", (void*)get_unix_timestamp);
// graphics things
make_function_symbol_from_c("pc-set-letterbox", (void*)Gfx::set_letterbox);
make_function_symbol_from_c("pc-renderer-tree-set-lod", (void*)Gfx::SetLod);
make_function_symbol_from_c("pc-set-collision-mode", (void*)Gfx::CollisionRendererSetMode);
make_function_symbol_from_c("pc-set-collision-mask", (void*)set_collision_mask);
make_function_symbol_from_c("pc-get-collision-mask", (void*)get_collision_mask);
make_function_symbol_from_c("pc-set-collision-wireframe", (void*)set_collision_wireframe);
make_function_symbol_from_c("pc-set-collision", (void*)set_collision);
make_function_symbol_from_c("pc-set-gfx-hack", (void*)set_gfx_hack);
// file related functions
make_function_symbol_from_c("pc-filepath-exists?", (void*)filepath_exists);
make_function_symbol_from_c("pc-mkdir-file-path", (void*)mkdir_path);
// discord rich presence
make_function_symbol_from_c("pc-discord-rpc-set", (void*)set_discord_rpc);
make_function_symbol_from_c("pc-discord-rpc-update", (void*)update_discord_rpc);
// profiler
make_function_symbol_from_c("pc-prof", (void*)prof_event);
// debugging tools
make_function_symbol_from_c("pc-filter-debug-string?", (void*)pc_filter_debug_string);
// other
make_function_symbol_from_c("pc-rand", (void*)pc_rand);
// init ps2 VM
if (VM::use) {
make_function_symbol_from_c("vm-ptr", (void*)VM::get_vm_ptr);
VM::vm_init();
}
// setup string constants
// TODO - these may be able to be moved into `init_common_pc_port_functions` but it's trickier
// since they are accessing the Ptr's value
auto user_dir_path = file_util::get_user_config_dir();
intern_from_c("*pc-user-dir-base-path*")->value =
make_string_from_c(user_dir_path.string().c_str());
+2 -1
View File
@@ -514,7 +514,8 @@ Ptr<Function> make_zero_func() {
* name. This effectively creates a global GOAL function with the given name which calls the given C
* function.
*
* This work on both Linux and Windows, but only supports up to 6 arguments.
* This work on both Linux and Windows, but only supports up to 6 arguments on linux and 4 args on
* windows.
*/
Ptr<Function> make_function_symbol_from_c(const char* name, void* f) {
auto sym = intern_from_c(name);
+37 -111
View File
@@ -7,8 +7,10 @@
#include "common/log/log.h"
#include "common/symbols.h"
#include "common/util/FileUtil.h"
#include "common/util/string_util.h"
#include "game/discord.h"
#include "game/external/discord.h"
#include "game/graphics/display.h"
#include "game/graphics/jak2_texture_remap.h"
#include "game/kernel/common/Symbol4.h"
#include "game/kernel/common/fileio.h"
@@ -437,15 +439,23 @@ u32 MouseGetData(u32 _mouse) {
mouse->status = 1;
mouse->button0 = 0;
auto [xpos, ypos] = Gfx::get_mouse_pos();
s32 xpos = 0;
s32 ypos = 0;
if (Display::GetMainDisplay()) {
std::tie(xpos, ypos) = Display::GetMainDisplay()->get_input_manager()->get_mouse_pos();
}
// NOTE - ignoring speed and setting position directly
// the game assumes resolutions, so this makes it a lot easier to make it actually
// line up with the mouse cursor
// TODO - probably factor in scaling as well
auto win_width = Gfx::get_window_width();
auto win_height = Gfx::get_window_height();
auto win_width = 0;
auto win_height = 0;
if (Display::GetMainDisplay()) {
win_width = Display::GetMainDisplay()->get_display_manager()->get_window_width();
win_height = Display::GetMainDisplay()->get_display_manager()->get_window_height();
}
// These are used to calculate the speed at which to move the mouse to it's new coordinates
// zero'd out so they are ignored and don't impact the position we are about to set
@@ -456,8 +466,8 @@ u32 MouseGetData(u32 _mouse) {
// - [-208.0, 208.0] for height
// (then 208 or 256 is always added to them to get the final screen coordinate)
// So just normalize the actual window's values to this range
double width_per = xpos / win_width;
double height_per = ypos / win_height;
double width_per = xpos / double(win_width);
double height_per = ypos / double(win_height);
mouse->posx = (512.0 * width_per) - 256.0;
mouse->posy = (416.0 * height_per) - 208.0;
// fmt::print("Mouse - X:{}({}), Y:{}({})\n", xpos, mouse->posx, ypos, mouse->posy);
@@ -496,32 +506,6 @@ u64 kopen(u64 fs, u64 name, u64 mode) {
* PC port functions START
*/
/*!
* Return the current OS as a symbol. Actually returns what it was compiled for!
*/
u64 get_os() {
#ifdef _WIN32
return intern_from_c("windows").offset;
#elif __linux__
return intern_from_c("linux").offset;
#else
return s7.offset;
#endif
}
void pc_set_levels(u32 lev_list) {
std::vector<std::string> levels;
for (int i = 0; i < LEVEL_MAX; i++) {
u32 lev = *Ptr<u32>(lev_list + i * 4);
std::string ls = Ptr<String>(lev).c()->data();
if (ls != "none" && ls != "#f" && ls != "") {
levels.push_back(ls);
}
}
Gfx::set_levels(levels);
}
void update_discord_rpc(u32 discord_info) {
if (gDiscordRpcEnabled) {
DiscordRichPresence rpc;
@@ -595,26 +579,20 @@ void update_discord_rpc(u32 discord_info) {
}
}
u32 get_fullscreen() {
switch (Gfx::get_fullscreen()) {
default:
case GfxDisplayMode::Windowed:
return intern_from_c("windowed").offset;
case GfxDisplayMode::Borderless:
return intern_from_c("borderless").offset;
case GfxDisplayMode::Fullscreen:
return intern_from_c("fullscreen").offset;
void pc_set_levels(u32 lev_list) {
if (!Gfx::GetCurrentRenderer()) {
return;
}
std::vector<std::string> levels;
for (int i = 0; i < LEVEL_MAX; i++) {
u32 lev = *Ptr<u32>(lev_list + i * 4);
std::string ls = Ptr<String>(lev).c()->data();
if (ls != "none" && ls != "#f" && ls != "") {
levels.push_back(ls);
}
}
}
void set_fullscreen(u32 symptr, s64 screen) {
if (symptr == intern_from_c("windowed").offset || symptr == s7.offset) {
Gfx::set_fullscreen(GfxDisplayMode::Windowed, screen);
} else if (symptr == intern_from_c("borderless").offset) {
Gfx::set_fullscreen(GfxDisplayMode::Borderless, screen);
} else if (symptr == intern_from_c("fullscreen").offset) {
Gfx::set_fullscreen(GfxDisplayMode::Fullscreen, screen);
}
Gfx::GetCurrentRenderer()->set_levels(levels);
}
void init_autosplit_struct() {
@@ -646,78 +624,26 @@ u32 alloc_vagdir_names(u32 heap_sym) {
void InitMachine_PCPort() {
// PC Port added functions
init_common_pc_port_functions(
make_function_symbol_from_c,
[](const char* name) {
const auto result = intern_from_c(name);
InternFromCInfo info{};
info.offset = result.offset;
return info;
},
make_string_from_c);
make_function_symbol_from_c("__read-ee-timer", (void*)read_ee_timer);
make_function_symbol_from_c("__mem-move", (void*)c_memmove);
make_function_symbol_from_c("__send-gfx-dma-chain", (void*)send_gfx_dma_chain);
make_function_symbol_from_c("__pc-texture-upload-now", (void*)pc_texture_upload_now);
make_function_symbol_from_c("__pc-texture-relocate", (void*)pc_texture_relocate);
make_function_symbol_from_c("__pc-get-mips2c", (void*)pc_get_mips2c);
make_function_symbol_from_c("__pc-set-levels", (void*)pc_set_levels);
make_function_symbol_from_c("__pc-get-tex-remap", (void*)lookup_jak2_texture_dest_offset);
make_function_symbol_from_c("pc-get-unix-timestamp", (void*)get_unix_timestamp);
make_function_symbol_from_c("pc-init-autosplitter-struct", (void*)init_autosplit_struct);
// pad stuff
make_function_symbol_from_c("pc-pad-get-mapped-button", (void*)Gfx::get_mapped_button);
make_function_symbol_from_c("pc-pad-input-map-save!", (void*)Gfx::input_mode_save);
make_function_symbol_from_c("pc-pad-input-mode-set", (void*)Gfx::input_mode_set);
make_function_symbol_from_c("pc-pad-input-pad-set", (void*)Pad::input_mode_pad_set);
make_function_symbol_from_c("pc-pad-input-mode-get", (void*)Pad::input_mode_get);
make_function_symbol_from_c("pc-pad-input-key-get", (void*)Pad::input_mode_get_key);
make_function_symbol_from_c("pc-pad-input-index-get", (void*)Pad::input_mode_get_index);
// os stuff
make_function_symbol_from_c("pc-get-os", (void*)get_os);
make_function_symbol_from_c("pc-get-window-size", (void*)get_window_size);
make_function_symbol_from_c("pc-get-window-scale", (void*)get_window_scale);
make_function_symbol_from_c("pc-get-fullscreen", (void*)get_fullscreen);
make_function_symbol_from_c("pc-get-screen-size", (void*)get_screen_size);
make_function_symbol_from_c("pc-get-screen-rate", (void*)get_screen_rate);
make_function_symbol_from_c("pc-get-screen-vmode-count", (void*)get_screen_vmode_count);
make_function_symbol_from_c("pc-get-monitor-count", (void*)get_monitor_count);
make_function_symbol_from_c("pc-set-window-size", (void*)Gfx::set_window_size);
make_function_symbol_from_c("pc-set-fullscreen", (void*)set_fullscreen);
make_function_symbol_from_c("pc-set-frame-rate", (void*)set_frame_rate);
make_function_symbol_from_c("pc-set-vsync", (void*)set_vsync);
make_function_symbol_from_c("pc-set-window-lock", (void*)set_window_lock);
make_function_symbol_from_c("pc-set-game-resolution", (void*)set_game_resolution);
make_function_symbol_from_c("pc-set-msaa", (void*)set_msaa);
make_function_symbol_from_c("pc-get-unix-timestamp", (void*)get_unix_timestamp);
// graphics things
make_function_symbol_from_c("pc-set-letterbox", (void*)Gfx::set_letterbox);
make_function_symbol_from_c("pc-renderer-tree-set-lod", (void*)Gfx::SetLod);
make_function_symbol_from_c("pc-set-collision-mode", (void*)Gfx::CollisionRendererSetMode);
make_function_symbol_from_c("pc-set-collision-mask", (void*)set_collision_mask);
make_function_symbol_from_c("pc-get-collision-mask", (void*)get_collision_mask);
make_function_symbol_from_c("pc-set-collision-wireframe", (void*)set_collision_wireframe);
make_function_symbol_from_c("pc-set-collision", (void*)set_collision);
// file related functions
make_function_symbol_from_c("pc-filepath-exists?", (void*)filepath_exists);
make_function_symbol_from_c("pc-mkdir-file-path", (void*)mkdir_path);
// discord rich presence
make_function_symbol_from_c("pc-discord-rpc-set", (void*)set_discord_rpc);
make_function_symbol_from_c("pc-discord-rpc-update", (void*)update_discord_rpc);
// profiler
make_function_symbol_from_c("pc-prof", (void*)prof_event);
// debugging tools
make_function_symbol_from_c("pc-filter-debug-string?", (void*)pc_filter_debug_string);
make_function_symbol_from_c("alloc-vagdir-names", (void*)alloc_vagdir_names);
// other
make_function_symbol_from_c("pc-rand", (void*)pc_rand);
// init ps2 VM
if (VM::use) {
make_function_symbol_from_c("vm-ptr", (void*)VM::get_vm_ptr);
VM::vm_init();
}
// setup string constants
auto user_dir_path = file_util::get_user_config_dir();
intern_from_c("*pc-user-dir-base-path*")->value() =
+4
View File
@@ -89,6 +89,7 @@ int main(int argc, char** argv) {
bool disable_avx2 = false;
bool disable_display = false;
bool enable_debug_vm = false;
bool enable_profiling = false;
int port_number = -1;
fs::path project_path_override;
std::vector<std::string> game_args;
@@ -102,6 +103,7 @@ int main(int argc, char** argv) {
app.add_flag("--no-avx2", verbose_logging, "Disable AVX2 for testing");
app.add_flag("--no-display", disable_display, "Disable video display");
app.add_flag("--vm", enable_debug_vm, "Enable debug PS2 VM (defaulted to off)");
app.add_flag("--profile", enable_profiling, "Enables profiling immediately from startup");
app.add_option("--proj-path", project_path_override,
"Specify the location of the 'data/' folder");
app.footer(game_arg_documentation());
@@ -115,6 +117,8 @@ int main(int argc, char** argv) {
return 0;
}
prof().set_enable(enable_profiling);
// Create struct with all non-kmachine handled args to pass to the runtime
GameLaunchOptions game_options;
game_options.disable_debug_vm = !enable_debug_vm;
+17 -13
View File
@@ -30,7 +30,7 @@
#include "common/util/FileUtil.h"
#include "common/versions/versions.h"
#include "game/discord.h"
#include "game/external/discord.h"
#include "game/graphics/gfx.h"
#include "game/kernel/common/fileio.h"
#include "game/kernel/common/kdgo.h"
@@ -374,23 +374,25 @@ RuntimeExitStatus exec_runtime(GameLaunchOptions game_options, int argc, const c
g_game_version = game_options.game_version;
g_server_port = game_options.server_port;
// set up discord stuff
gStartTime = time(nullptr);
prof().instant_event("ROOT");
{
auto p = scoped_prof("init-discord");
auto p = scoped_prof("startup::exec_runtime::init_discord_rpc");
init_discord_rpc();
}
// initialize graphics first - the EE code will upload textures during boot and we
// want the graphics system to catch them.
if (enable_display) {
auto p = scoped_prof("init-gfx");
Gfx::Init(g_game_version);
{
auto p = scoped_prof("startup::exec_runtime::init_gfx");
if (enable_display) {
Gfx::Init(g_game_version);
}
}
// step 1: sce library prep
{
auto p = scoped_prof("init-library");
auto p = scoped_prof("startup::exec_runtime::library_prep");
iop::LIBRARY_INIT();
ee::LIBRARY_INIT_sceCd();
ee::LIBRARY_INIT_sceDeci2();
@@ -398,28 +400,30 @@ RuntimeExitStatus exec_runtime(GameLaunchOptions game_options, int argc, const c
}
// step 2: system prep
prof().begin_event("startup::exec_runtime::system_prep");
VM::vm_prepare(); // our fake ps2 VM needs to be prepared
SystemThreadManager tm;
auto& deci_thread = tm.create_thread("DMP");
auto& iop_thread = tm.create_thread("IOP");
auto& ee_thread = tm.create_thread("EE");
auto& vm_dmac_thread = tm.create_thread("VM-DMAC");
prof().end_event();
// step 3: start the EE!
{
auto p = scoped_prof("iop-start");
auto p = scoped_prof("startup::exec_runtime::iop-start");
iop_thread.start([=](SystemThreadInterface& sti) { iop_runner(sti, g_game_version); });
}
{
auto p = scoped_prof("deci-start");
auto p = scoped_prof("startup::exec_runtime::deci-start");
deci_thread.start(deci2_runner);
}
{
auto p = scoped_prof("ee-start");
auto p = scoped_prof("startup::exec_runtime::ee-start");
ee_thread.start(ee_runner);
}
if (VM::use) {
auto p = scoped_prof("dmac-start");
auto p = scoped_prof("startup::exec_runtime::dmac-start");
vm_dmac_thread.start(dmac_runner);
}
@@ -429,8 +433,8 @@ RuntimeExitStatus exec_runtime(GameLaunchOptions game_options, int argc, const c
try {
Gfx::Loop([]() { return MasterExit == RuntimeExitStatus::RUNNING; });
} catch (std::exception& e) {
fmt::print("Exception thrown from graphics loop: {}\n", e.what());
fmt::print("Everything will crash now. good luck\n");
lg::error("Exception thrown from graphics loop: {}\n", e.what());
lg::error("Everything will crash now. good luck\n");
throw;
}
}
+23 -21
View File
@@ -2,8 +2,10 @@
#include "common/util/Assert.h"
#include "game/graphics/display.h"
#include "game/graphics/gfx.h"
#include "game/kernel/common/kernel_types.h"
#include "game/system/hid/input_bindings.h"
/*!
* @file libpad.cpp
@@ -55,38 +57,35 @@ int scePadInfoMode(int /*port*/, int /*slot*/, int term, int offs) {
return 0;
}
// order of pressure sensitive buttons in memory (not the same as their bit order...).
static const Pad::Button libpad_PadPressureButtons[] = {
Pad::Button::Right, Pad::Button::Left, Pad::Button::Up, Pad::Button::Down,
Pad::Button::Triangle, Pad::Button::Circle, Pad::Button::X, Pad::Button::Square,
Pad::Button::L1, Pad::Button::R1, Pad::Button::L2, Pad::Button::R2};
// reads controller data and writes it to a buffer in rdata (must be at least 32 bytes large).
// returns buffer size (32) or 0 on error.
int scePadRead(int port, int /*slot*/, u8* rdata) {
auto cpad = (CPadInfo*)(rdata);
// Gfx::poll_events();
cpad->valid = 0; // success
cpad->status = 0x70 /* (dualshock2) */ | (20 / 2); /* (dualshock2 data size) */
cpad->rightx = Gfx::PadGetAnalogValue(Pad::Analog::Right_X, port);
cpad->righty = Gfx::PadGetAnalogValue(Pad::Analog::Right_Y, port);
cpad->leftx = Gfx::PadGetAnalogValue(Pad::Analog::Left_X, port);
cpad->lefty = Gfx::PadGetAnalogValue(Pad::Analog::Left_Y, port);
// pressure sensitivity. ignore for now.
for (int i = 0; i < 12; ++i) {
cpad->abutton[i] = Gfx::PadIsPressed(libpad_PadPressureButtons[i], port) * 255;
std::optional<std::shared_ptr<PadData>> pad_data = std::nullopt;
if (Display::GetMainDisplay()) {
pad_data = Display::GetMainDisplay()->get_input_manager()->get_current_data(port);
}
cpad->button0 = 0;
for (int i = 0; i < 16; ++i) {
cpad->button0 |= Gfx::PadIsPressed((Pad::Button)i, port) << i;
}
if (pad_data) {
std::tie(cpad->rightx, cpad->righty) = pad_data.value()->analog_right();
std::tie(cpad->leftx, cpad->lefty) = pad_data.value()->analog_left();
// keys polled and read, prepare for new ones.
Pad::ClearKeys();
// pressure sensitivity. ignore for now.
for (int i = 0; i < PAD_DATA_PRESSURE_INDEX_ORDER.size(); i++) {
cpad->abutton[i] =
pad_data.value()->button_data.at(PAD_DATA_PRESSURE_INDEX_ORDER.at(i)) * 255;
}
cpad->button0 = 0;
for (int i = 0; i < pad_data.value()->button_data.size(); i++) {
cpad->button0 |= pad_data.value()->button_data.at(i) << i;
}
}
return 32;
}
@@ -95,7 +94,10 @@ int scePadSetActDirect(int port, int /*slot*/, const u8* data) {
// offsets are set by scePadSetActAlign, but we already know the game uses 0 for big motor and 1
// for small motor
// also, the "slow" motor corresponds to the "large" motor on the PS2
return Pad::rumble(port, ((float)data[1]) / 255, ((float)data[0]));
if (Display::GetMainDisplay()) {
return Display::GetMainDisplay()->get_input_manager()->update_rumble(port, data[1], data[0]);
}
return 0;
}
int scePadSetActAlign(int /*port*/, int /*slot*/, const u8* /*data*/) {
+135
View File
@@ -0,0 +1,135 @@
#include "settings.h"
#include "common/log/log.h"
#include "common/util/json_util.h"
#include "game/runtime.h"
namespace game_settings {
void to_json(json& j, const DebugSettings& obj) {
j = json{{"version", obj.version},
{"show_imgui", obj.show_imgui},
{"imgui_font_size", obj.imgui_font_size},
{"monospaced_font", obj.monospaced_font},
{"alternate_style", obj.alternate_style},
{"ignore_hide_imgui", obj.ignore_hide_imgui},
{"text_filters", obj.text_filters},
{"text_check_range", obj.text_check_range},
{"text_max_range", obj.text_max_range}};
}
void from_json(const json& j, DebugSettings& obj) {
json_deserialize_if_exists(version);
json_deserialize_if_exists(show_imgui);
json_deserialize_if_exists(imgui_font_size);
json_deserialize_if_exists(monospaced_font);
json_deserialize_if_exists(alternate_style);
json_deserialize_if_exists(ignore_hide_imgui);
json_deserialize_if_exists(text_filters);
json_deserialize_if_exists(text_check_range);
json_deserialize_if_exists(text_max_range);
}
DebugSettings::DebugSettings() {
try {
std::string file_path =
(file_util::get_user_misc_dir(g_game_version) / "debug-settings.json").string();
if (!file_util::file_exists(file_path)) {
return;
}
lg::info("Loading display settings at {}", file_path);
auto raw = file_util::read_text_file(file_path);
from_json(parse_commented_json(raw, "debug-settings.json"), *this);
} catch (std::exception& e) {
// do nothing
lg::error("Error encountered when attempting to load debug settings {}", e.what());
// TODO - make sure a bad debug-settings.json is fine, check others below as well
}
}
void DebugSettings::save_settings() {
json data = *this;
auto debug_settings_filename =
file_util::get_user_misc_dir(g_game_version) / "debug-settings.json";
file_util::create_dir_if_needed_for_file(debug_settings_filename);
file_util::write_text_file(debug_settings_filename, data.dump(2));
}
void to_json(json& j, const DisplaySettings& obj) {
j = json{{"display_id", obj.display_id},
{"window_xpos", obj.window_xpos},
{"window_ypos", obj.window_ypos}};
}
void from_json(const json& j, DisplaySettings& obj) {
json_deserialize_if_exists(version);
json_deserialize_if_exists(display_id);
json_deserialize_if_exists(window_xpos);
json_deserialize_if_exists(window_ypos);
}
DisplaySettings::DisplaySettings() {
try {
std::string file_path =
(file_util::get_user_settings_dir(g_game_version) / "display-settings.json").string();
if (!file_util::file_exists(file_path)) {
return;
}
lg::info("Loading display settings at {}", file_path);
auto raw = file_util::read_text_file(file_path);
from_json(parse_commented_json(raw, "display-settings.json"), *this);
} catch (std::exception& e) {
// do nothing
lg::error("Error encountered when attempting to load display settings {}", e.what());
}
}
void DisplaySettings::save_settings() {
json data = *this;
auto file_path = file_util::get_user_settings_dir(g_game_version) / "display-settings.json";
file_util::create_dir_if_needed_for_file(file_path);
file_util::write_text_file(file_path, data.dump(2));
}
void to_json(json& j, const InputSettings& obj) {
j = json{{"version", obj.version},
{"last_selected_controller_guid", obj.last_selected_controller_guid},
{"controller_port_mapping", obj.controller_port_mapping},
{"controller_binds", obj.controller_binds},
{"keyboard_binds", obj.keyboard_binds},
{"mouse_binds", obj.mouse_binds}};
}
void from_json(const json& j, InputSettings& obj) {
json_deserialize_if_exists(version);
json_deserialize_if_exists(last_selected_controller_guid);
json_deserialize_if_exists(controller_port_mapping);
json_deserialize_if_exists(controller_binds);
json_deserialize_if_exists(keyboard_binds);
json_deserialize_if_exists(mouse_binds);
}
InputSettings::InputSettings() {
try {
keyboard_binds = DEFAULT_KEYBOARD_BINDS;
mouse_binds = DEFAULT_MOUSE_BINDS;
std::string file_path =
(file_util::get_user_settings_dir(g_game_version) / "input-settings.json").string();
if (!file_util::file_exists(file_path)) {
return;
}
lg::info("Loading display settings at {}", file_path);
auto raw = file_util::read_text_file(file_path);
from_json(parse_commented_json(raw, "input-settings.json"), *this);
} catch (std::exception& e) {
// do nothing
lg::error("Error encountered when attempting to load input settings {}", e.what());
}
}
void InputSettings::save_settings() {
json data = *this;
auto file_path = file_util::get_user_settings_dir(g_game_version) / "input-settings.json";
file_util::create_dir_if_needed_for_file(file_path);
file_util::write_text_file(file_path, data.dump(2));
}
} // namespace game_settings
+63
View File
@@ -0,0 +1,63 @@
#pragma once
#include "common/util/FileUtil.h"
#include "common/util/json_util.h"
#include "game/system/hid/input_bindings.h"
#include "game/tools/filter_menu/filter_menu.h"
namespace game_settings {
struct DebugSettings {
DebugSettings();
std::string version = "1.1";
bool show_imgui = false;
int imgui_font_size = 14;
bool monospaced_font = true;
bool alternate_style = false;
bool ignore_hide_imgui = false;
std::vector<DebugTextFilter> text_filters = {};
bool text_check_range = false;
float text_max_range = 0;
void save_settings();
};
void to_json(json& j, const DebugSettings& obj);
void from_json(const json& j, DebugSettings& obj);
struct DisplaySettings {
DisplaySettings();
std::string version = "1.1";
int window_xpos = 50;
int window_ypos = 50;
int display_id = 0;
void save_settings();
};
void to_json(json& j, const DisplaySettings& obj);
void from_json(const json& j, DisplaySettings& obj);
struct InputSettings {
InputSettings();
std::string version = "1.0";
// NOTE - assumes only port 0
std::string last_selected_controller_guid = "";
std::unordered_map<std::string, int> controller_port_mapping;
std::unordered_map<std::string, InputBindingGroups> controller_binds;
InputBindingGroups keyboard_binds;
InputBindingGroups mouse_binds;
void save_settings();
};
void to_json(json& j, const InputSettings& obj);
void from_json(const json& j, InputSettings& obj);
} // namespace game_settings
+161
View File
@@ -0,0 +1,161 @@
#include "game_controller.h"
#include "game/system/hid/sdl_util.h"
#include "third-party/fmt/core.h"
GameController::GameController(int sdl_device_id,
std::shared_ptr<game_settings::InputSettings> settings)
: m_sdl_instance_id(sdl_device_id) {
m_settings = settings;
m_loaded = false;
m_device_handle = SDL_GameControllerOpen(sdl_device_id);
if (!m_device_handle) {
sdl_util::log_error(fmt::format("Could not read data from device index: {}", sdl_device_id));
return;
}
auto joystick = SDL_GameControllerGetJoystick(m_device_handle);
if (!joystick) {
sdl_util::log_error(
fmt::format("Could not get underlying joystick for gamecontroller: id {}", sdl_device_id));
return;
}
m_sdl_instance_id = SDL_JoystickInstanceID(joystick);
if (m_sdl_instance_id < 0) {
sdl_util::log_error(
fmt::format("Could not get instance id for gamecontroller: id {}", sdl_device_id));
return;
}
const auto controller_guid = SDL_JoystickGetGUID(joystick);
if (controller_guid.data == 0) {
sdl_util::log_error(fmt::format("Could not get contoller guid with id: {}", sdl_device_id));
return;
}
char guidStr[33];
SDL_JoystickGetGUIDString(controller_guid, guidStr, sizeof(guidStr));
m_guid = guidStr;
auto name = SDL_GameControllerName(m_device_handle);
if (!name) {
sdl_util::log_error(fmt::format("Could not get device name with id: {}", sdl_device_id));
m_device_name = "";
} else {
m_device_name = name;
}
m_has_led = sdl_util::from_sdl_bool(SDL_GameControllerHasLED(m_device_handle));
if (m_settings->controller_binds.find(m_guid) == m_settings->controller_binds.end()) {
m_settings->controller_binds[m_guid] = DEFAULT_CONTROLLER_BINDS;
}
m_loaded = true;
}
void GameController::process_event(const SDL_Event& event,
const CommandBindingGroups& commands,
std::shared_ptr<PadData> data,
std::optional<InputBindAssignmentMeta>& bind_assignment,
bool ignore_inputs) {
if (event.type == SDL_CONTROLLERAXISMOTION && event.caxis.which == m_sdl_instance_id) {
// https://wiki.libsdl.org/SDL2/SDL_GameControllerAxis
if (event.caxis.axis <= SDL_CONTROLLER_AXIS_INVALID ||
event.caxis.axis >= SDL_CONTROLLER_AXIS_MAX) {
return;
}
auto& binds = m_settings->controller_binds.at(m_guid);
// Handle analog stick binds
if (event.caxis.axis >= SDL_CONTROLLER_AXIS_LEFTX &&
event.caxis.axis <= SDL_CONTROLLER_AXIS_RIGHTY && !data->analogs_being_simulated() &&
binds.analog_axii.find(event.caxis.axis) != binds.analog_axii.end()) {
for (const auto& bind : binds.analog_axii.at(event.caxis.axis)) {
// Adjust the value range to 0-255 (127 being neutral)
// Values come out of SDL as -32,768 + 32,767
int axis_val = ((event.caxis.value + 32768) * 256) / 65536;
data->analog_data.at(bind.pad_data_index) = axis_val;
}
} else if (event.caxis.axis >= SDL_CONTROLLER_AXIS_TRIGGERLEFT &&
event.caxis.axis <= SDL_CONTROLLER_AXIS_TRIGGERRIGHT &&
binds.button_axii.find(event.caxis.axis) != binds.button_axii.end()) {
// Binding re-assignment
if (bind_assignment) {
if (bind_assignment->device_type == InputDeviceType::CONTROLLER &&
!bind_assignment->for_analog) {
binds.assign_button_bind(event.caxis.axis, bind_assignment.value(), true);
}
return;
}
// and analog triggers
for (const auto& bind : binds.button_axii.at(event.caxis.axis)) {
data->button_data.at(bind.pad_data_index) = event.caxis.value > 0;
}
}
} else if ((event.type == SDL_CONTROLLERBUTTONDOWN || event.type == SDL_CONTROLLERBUTTONUP) &&
event.cbutton.which == m_sdl_instance_id) {
auto& binds = m_settings->controller_binds.at(m_guid);
// https://wiki.libsdl.org/SDL2/SDL_GameControllerButton
if (event.cbutton.button <= SDL_CONTROLLER_BUTTON_INVALID ||
event.cbutton.button >= SDL_CONTROLLER_BUTTON_MAX) {
return;
}
// Binding re-assignment
if (bind_assignment && event.type == SDL_CONTROLLERBUTTONDOWN) {
if (bind_assignment->device_type == InputDeviceType::CONTROLLER &&
!bind_assignment->for_analog) {
binds.assign_button_bind(event.cbutton.button, bind_assignment.value());
}
return;
}
if (binds.buttons.find(event.cbutton.button) == binds.buttons.end()) {
return;
}
// Iterate the binds, and apply all of them
for (const auto& bind : binds.buttons.at(event.cbutton.button)) {
data->button_data.at(bind.pad_data_index) = event.type == SDL_CONTROLLERBUTTONDOWN;
}
// Check for commands
if (event.type == SDL_CONTROLLERBUTTONDOWN &&
commands.controller_binds.find(event.cbutton.button) != commands.controller_binds.end()) {
for (const auto& command : commands.controller_binds.at(event.cbutton.button)) {
command.command();
}
}
}
}
void GameController::close_device() {
if (m_device_handle) {
SDL_GameControllerClose(m_device_handle);
}
}
int GameController::update_rumble(const u8 low_rumble, const u8 high_rumble) {
if (m_device_handle) {
// https://wiki.libsdl.org/SDL2/SDL_GameControllerRumble
// Arbitrary duration, since it's called every frame anyway, just so the vibration doesn't last
// forever. SDL expects a value in the range of 0-0xFFFF, so the `257` is just normalizing the
// 0-255 we get to that range
if (SDL_GameControllerRumble(m_device_handle, low_rumble * 257, high_rumble * 257, 100) == 0) {
return 1;
}
}
return 0;
}
void GameController::set_led(const u8 red, const u8 green, const u8 blue) {
if (!m_has_led) {
return;
}
auto ok = SDL_GameControllerSetLED(m_device_handle, red, green, blue);
if (ok != 0) {
m_has_led = false;
}
}
+29
View File
@@ -0,0 +1,29 @@
#pragma once
#include "input_device.h"
// https://wiki.libsdl.org/SDL2/CategoryGameController
class GameController : public InputDevice {
public:
GameController(int sdl_device_id, std::shared_ptr<game_settings::InputSettings> settings);
~GameController() { close_device(); }
void process_event(const SDL_Event& event,
const CommandBindingGroups& commands,
std::shared_ptr<PadData> data,
std::optional<InputBindAssignmentMeta>& bind_assignment,
bool ignore_inputs = false) override;
void close_device() override;
int update_rumble(const u8 low_rumble, const u8 high_rumble);
std::string get_name() const { return m_device_name; }
bool has_led() { return m_has_led; }
void set_led(const u8 red, const u8 green, const u8 blue);
std::string get_guid() { return m_guid; }
private:
int m_sdl_instance_id = -1;
SDL_GameController* m_device_handle;
std::string m_device_name = "";
bool m_has_led;
std::string m_guid = "";
};
+26
View File
@@ -0,0 +1,26 @@
#pragma once
#include <optional>
#include "game/settings/settings.h"
#include "game/system/hid/input_bindings.h"
#include "third-party/SDL/include/SDL.h"
// A distinct input device. Only those devices that are "active" should be read
class InputDevice {
protected:
bool m_loaded = false;
std::shared_ptr<game_settings::InputSettings> m_settings;
public:
virtual ~InputDevice(){};
virtual void process_event(const SDL_Event& event,
const CommandBindingGroups& commands,
std::shared_ptr<PadData> data,
std::optional<InputBindAssignmentMeta>& bind_assignment,
bool ignore_inputs = false) = 0;
virtual void close_device() = 0;
bool is_loaded() const { return m_loaded; };
};
+111
View File
@@ -0,0 +1,111 @@
#include "keyboard.h"
#include "game/system/hid/sdl_util.h"
KeyboardDevice::KeyboardDevice(std::shared_ptr<game_settings::InputSettings> settings) {
m_settings = settings;
}
void KeyboardDevice::process_event(const SDL_Event& event,
const CommandBindingGroups& commands,
std::shared_ptr<PadData> data,
std::optional<InputBindAssignmentMeta>& bind_assignment,
bool ignore_inputs) {
if (ignore_inputs) {
return;
}
if (event.type == SDL_KEYDOWN || event.type == SDL_KEYUP) {
const auto key_event = event.key;
if (key_event.repeat != 0) {
return;
}
if (m_ignore_key_on_keyup && m_ignore_key_on_keyup.value() == key_event.keysym.sym &&
event.type == SDL_KEYUP) {
m_ignore_key_on_keyup = std::nullopt;
return;
}
auto& binds = m_settings->keyboard_binds;
// Binding re-assignment
if (bind_assignment) {
if (bind_assignment->device_type != InputDeviceType::KEYBOARD) {
return;
}
// A normal key down event (a new key was pressed) and it's not a modifier
if (event.type == SDL_KEYDOWN && !sdl_util::is_modifier_key(key_event.keysym.sym)) {
if (bind_assignment->for_analog) {
m_ignore_key_on_keyup = key_event.keysym.sym;
binds.assign_analog_bind(key_event.keysym.sym, bind_assignment.value(),
InputModifiers(key_event.keysym.mod));
} else {
binds.assign_button_bind(key_event.keysym.sym, bind_assignment.value(), false,
InputModifiers(key_event.keysym.mod));
}
} else if (event.type == SDL_KEYUP) {
// modifiers are instead inspected on a KEYUP, however if it's one of the keys
// for triggering the binding assignment, and it's the first time we've seen it -- we ignore
// it
if (!bind_assignment->seen_confirm_up) {
for (const auto& confirm_bind : bind_assignment->keyboard_confirmation_binds) {
if (confirm_bind.sdl_idx == key_event.keysym.sym) {
bind_assignment->seen_confirm_up = true;
return;
}
}
} else {
// otherwise, set the bind
if (bind_assignment->for_analog) {
binds.assign_analog_bind(key_event.keysym.sym, bind_assignment.value(),
InputModifiers(key_event.keysym.mod));
} else {
binds.assign_button_bind(key_event.keysym.sym, bind_assignment.value(), false,
InputModifiers(key_event.keysym.mod));
}
}
}
return;
}
// Normal Buttons
if (binds.buttons.find(key_event.keysym.sym) != binds.buttons.end()) {
for (const auto& bind : binds.buttons.at(key_event.keysym.sym)) {
if (bind.modifiers.has_necessary_modifiers(key_event.keysym.mod)) {
data->button_data.at(bind.pad_data_index) = event.type == SDL_KEYDOWN;
}
}
}
// Analog Buttons (useless for keyboards, but here for completeness)
if (binds.button_axii.find(key_event.keysym.sym) != binds.button_axii.end()) {
for (const auto& bind : binds.button_axii.at(key_event.keysym.sym)) {
if (bind.modifiers.has_necessary_modifiers(key_event.keysym.mod)) {
data->button_data.at(bind.pad_data_index) = event.type == SDL_KEYDOWN;
}
}
}
// Analog Sticks simulating
if (binds.analog_axii.find(key_event.keysym.sym) != binds.analog_axii.end()) {
for (const auto& bind : binds.analog_axii.at(key_event.keysym.sym)) {
if (bind.modifiers.has_necessary_modifiers(key_event.keysym.mod)) {
int analog_val = bind.minimum_in_range ? 127 : -127;
if (event.type == SDL_KEYDOWN) {
analog_val = bind.minimum_in_range ? -127 : 127;
}
data->analog_data.at(bind.pad_data_index) += analog_val;
data->update_analog_sim_tracker(event.type != SDL_KEYDOWN);
}
}
}
// Check for commands
if (event.type == SDL_KEYDOWN &&
commands.keyboard_binds.find(key_event.keysym.sym) != commands.keyboard_binds.end()) {
for (const auto& command : commands.keyboard_binds.at(key_event.keysym.sym)) {
if (command.modifiers.has_necessary_modifiers(key_event.keysym.mod)) {
command.command();
}
}
}
}
}
+23
View File
@@ -0,0 +1,23 @@
#pragma once
#include "input_device.h"
class KeyboardDevice : public InputDevice {
public:
KeyboardDevice(){};
KeyboardDevice(std::shared_ptr<game_settings::InputSettings> settings);
~KeyboardDevice() {}
void process_event(const SDL_Event& event,
const CommandBindingGroups& commands,
std::shared_ptr<PadData> data,
std::optional<InputBindAssignmentMeta>& bind_assignment,
bool ignore_inputs = false) override;
void close_device() override{
// there is nothing to close
};
// When we assign a bind on a keydown, we have to ignore it's keyup event
// for analog binds, or it will adjust the position
std::optional<u32> m_ignore_key_on_keyup;
};
+116
View File
@@ -0,0 +1,116 @@
#include "mouse.h"
#include "game/system/hid/sdl_util.h"
MouseDevice::MouseDevice(std::shared_ptr<game_settings::InputSettings> settings) {
m_settings = settings;
enable_relative_mode(m_control_camera);
}
void MouseDevice::process_event(const SDL_Event& event,
const CommandBindingGroups& commands,
std::shared_ptr<PadData> data,
std::optional<InputBindAssignmentMeta>& bind_assignment,
bool ignore_inputs) {
if (event.type == SDL_MOUSEMOTION) {
// https://wiki.libsdl.org/SDL2/SDL_MouseMotionEvent
m_xcoord = event.motion.x;
m_ycoord = event.motion.y;
if (ignore_inputs) {
// We still want to keep track of the cursor location even if we aren't using it for inputs
// return early
return;
}
if (m_control_camera) {
const auto xadjust = std::clamp(127 + int(float(event.motion.xrel) * m_xsens), 0, 255);
const auto yadjust = std::clamp(127 + int(float(event.motion.yrel) * m_ysens), 0, 255);
data->analog_data.at(2) = xadjust;
data->analog_data.at(3) = yadjust;
}
} else if (event.type == SDL_MOUSEBUTTONDOWN || event.type == SDL_MOUSEBUTTONUP) {
// Mouse Button Events
// https://wiki.libsdl.org/SDL2/SDL_MouseButtonEvent
if (ignore_inputs) {
return;
}
const auto button_event = event.button;
auto& binds = m_settings->mouse_binds;
// Binding re-assignment
if (bind_assignment && event.type == SDL_MOUSEBUTTONDOWN) {
if (bind_assignment->device_type == InputDeviceType::MOUSE && !bind_assignment->for_analog) {
binds.assign_button_bind(button_event.button, bind_assignment.value(), false,
InputModifiers(SDL_GetModState()));
}
return;
}
// Normal Buttons
if (binds.buttons.find(button_event.button) != binds.buttons.end()) {
for (const auto& bind : binds.buttons.at(button_event.button)) {
if (bind.modifiers.has_necessary_modifiers(SDL_GetModState())) {
data->button_data.at(bind.pad_data_index) = event.type == SDL_MOUSEBUTTONDOWN;
}
}
}
// Analog Buttons (useless for mice, but here for completeness)
if (binds.button_axii.find(button_event.button) != binds.button_axii.end()) {
for (const auto& bind : binds.button_axii.at(button_event.button)) {
data->button_data.at(bind.pad_data_index) = event.type == SDL_MOUSEBUTTONDOWN;
}
}
// Analog Sticks simulating
if (binds.analog_axii.find(button_event.button) != binds.analog_axii.end()) {
for (const auto& bind : binds.analog_axii.at(button_event.button)) {
if (bind.modifiers.has_necessary_modifiers(SDL_GetModState())) {
int analog_val = event.type == SDL_MOUSEBUTTONDOWN ? 255 : 127;
if (event.type == SDL_MOUSEBUTTONDOWN && bind.minimum_in_range) {
analog_val = 0;
}
data->analog_data.at(bind.pad_data_index) = analog_val;
}
}
}
if (m_control_movement) {
// WoW style mouse movement, if you have both buttons held down, you will move forward
const auto mouse_state = SDL_GetMouseState(NULL, NULL);
if (!m_was_moving_with_mouse && event.type == SDL_MOUSEBUTTONDOWN &&
(mouse_state & SDL_BUTTON_LMASK && mouse_state & SDL_BUTTON_RMASK)) {
data->analog_data.at(1) += -127;
m_was_moving_with_mouse = true;
data->update_analog_sim_tracker(false);
} else if (m_was_moving_with_mouse && event.type == SDL_MOUSEBUTTONUP &&
((mouse_state & SDL_BUTTON_LMASK) == 0 || (mouse_state & SDL_BUTTON_RMASK) == 0)) {
data->analog_data.at(1) += 127;
m_was_moving_with_mouse = false;
data->update_analog_sim_tracker(true);
}
}
// Check for commands
if (event.type == SDL_MOUSEBUTTONDOWN &&
commands.mouse_binds.find(button_event.button) != commands.mouse_binds.end()) {
for (const auto& command : commands.mouse_binds.at(button_event.button)) {
if (command.modifiers.has_necessary_modifiers(SDL_GetModState())) {
command.command();
}
}
}
}
}
void MouseDevice::enable_relative_mode(const bool enable) {
// https://wiki.libsdl.org/SDL2/SDL_SetRelativeMouseMode
SDL_SetRelativeMouseMode(sdl_util::sdl_bool(enable));
}
void MouseDevice::enable_camera_control(const bool enable) {
m_control_camera = enable;
enable_relative_mode(m_control_camera);
}
void MouseDevice::set_camera_sens(const float xsens, const float ysens) {
m_xsens = xsens;
m_ysens = ysens;
}
+35
View File
@@ -0,0 +1,35 @@
#pragma once
#include "input_device.h"
class MouseDevice : public InputDevice {
public:
MouseDevice(){};
MouseDevice(std::shared_ptr<game_settings::InputSettings> settings);
~MouseDevice() {}
void process_event(const SDL_Event& event,
const CommandBindingGroups& commands,
std::shared_ptr<PadData> data,
std::optional<InputBindAssignmentMeta>& bind_assignment,
bool ignore_inputs = false) override;
void close_device() override{
// there is nothing to close
};
void enable_relative_mode(const bool enable);
void enable_camera_control(const bool enable);
void enable_movement_control(const bool enable) { m_control_movement = enable; }
std::pair<int, int> get_mouse_pos() const { return {m_xcoord, m_ycoord}; }
void set_camera_sens(const float xsens, const float ysens);
private:
int m_xcoord = 0;
int m_ycoord = 0;
bool m_control_camera = false;
bool m_control_movement = false;
bool m_was_moving_with_mouse = false;
float m_xsens = -15.0;
float m_ysens = 10.0;
};
+310
View File
@@ -0,0 +1,310 @@
#include "display_manager.h"
#include "sdl_util.h"
#include "third-party/fmt/core.h"
#include "third-party/fmt/format.h"
DisplayManager::DisplayManager(SDL_Window* window)
: m_window(window), m_selected_fullscreen_display_id(0) {
update_curr_display_info();
update_video_modes();
// Load display settings from a file
m_display_settings = game_settings::DisplaySettings();
// Adjust window / monitor position
initialize_window_position_from_settings();
}
DisplayManager::~DisplayManager() {
if (m_window_display_mode == WindowDisplayMode::Windowed) {
m_display_settings.display_id = m_active_display_id;
m_display_settings.window_xpos = m_window_xpos;
m_display_settings.window_ypos = m_window_ypos;
}
m_display_settings.save_settings();
}
void DisplayManager::process_sdl_event(const SDL_Event& event) {
const auto event_type = event.type;
if (event_type == SDL_WINDOWEVENT) {
// https://wiki.libsdl.org/SDL2/SDL_WindowEvent
// https://wiki.libsdl.org/SDL2/SDL_WindowEventID
switch (event.window.event) {
case SDL_WINDOWEVENT_MINIMIZED:
m_window_state = WindowState::Minimized;
break;
case SDL_WINDOWEVENT_MAXIMIZED:
m_window_state = WindowState::Maximized;
break;
case SDL_WINDOWEVENT_RESTORED:
m_window_state = WindowState::Restored;
break;
case SDL_WINDOWEVENT_MOVED:
m_window_xpos = event.window.data1;
m_window_ypos = event.window.data2;
break;
case SDL_WINDOWEVENT_RESIZED:
case SDL_WINDOWEVENT_SIZE_CHANGED:
m_window_width = event.window.data1;
m_window_height = event.window.data2;
break;
case SDL_WINDOWEVENT_DISPLAY_CHANGED:
// NOTE - if the user changes the window to a display that doesn't support the same
// framerate we don't handle that
update_curr_display_info();
break;
}
} else if (event_type == SDL_DISPLAYEVENT) {
// https://wiki.libsdl.org/SDL2/SDL_DisplayEventID
switch (event.display.event) {
case SDL_DISPLAYEVENT_CONNECTED:
case SDL_DISPLAYEVENT_DISCONNECTED:
update_curr_display_info();
update_video_modes();
break;
case SDL_DISPLAYEVENT_ORIENTATION:
update_video_modes();
break;
}
}
}
int DisplayManager::get_active_display_refresh_rate() {
if (m_active_display_id >= 0 &&
m_current_display_modes.find(m_active_display_id) != m_current_display_modes.end()) {
return m_current_display_modes.at(m_active_display_id).refresh_rate;
}
return 0;
}
int DisplayManager::get_screen_width() {
if (m_active_display_id >= 0 &&
m_current_display_modes.find(m_active_display_id) != m_current_display_modes.end()) {
return m_current_display_modes.at(m_active_display_id).screen_width;
}
return 640;
}
int DisplayManager::get_screen_height() {
if (m_active_display_id >= 0 &&
m_current_display_modes.find(m_active_display_id) != m_current_display_modes.end()) {
return m_current_display_modes.at(m_active_display_id).screen_height;
}
return 480;
}
Resolution DisplayManager::get_resolution(int id) {
if (id < m_available_resolutions.size()) {
return m_available_resolutions.at(id);
}
return {0, 0, 0.0};
}
void DisplayManager::set_window_resizable(bool resizable) {
if (m_window) {
SDL_SetWindowResizable(m_window, resizable ? SDL_TRUE : SDL_FALSE);
}
}
void DisplayManager::set_window_size(int width, int height) {
SDL_SetWindowSize(m_window, width, height);
}
void DisplayManager::initialize_window_position_from_settings() {
// Check that the display id is still valid
if (m_current_display_modes.find(m_display_settings.display_id) ==
m_current_display_modes.end()) {
m_display_settings.display_id = 0;
m_display_settings.window_xpos = 50;
m_display_settings.window_ypos = 50;
}
SDL_Rect rect;
const auto ok = SDL_GetDisplayBounds(m_display_settings.display_id, &rect);
if (ok < 0) {
sdl_util::log_error(fmt::format("unable to get display bounds for display id {}",
m_display_settings.display_id));
} else {
// Adjust the settings if they are out of bounds
if (m_display_settings.window_xpos <= rect.x ||
m_display_settings.window_xpos + 50 >= rect.x + rect.w) {
m_display_settings.window_xpos = rect.x + 50;
m_display_settings.save_settings();
}
if (m_display_settings.window_ypos <= rect.y ||
m_display_settings.window_ypos + 50 > rect.y + rect.h) {
m_display_settings.window_ypos = rect.y + 50;
m_display_settings.save_settings();
}
}
SDL_SetWindowPosition(m_window, m_display_settings.window_xpos, m_display_settings.window_ypos);
}
void DisplayManager::set_window_display_mode(WindowDisplayMode mode) {
// https://wiki.libsdl.org/SDL2/SDL_SetWindowFullscreen
int result = 0;
switch (mode) {
case WindowDisplayMode::Windowed:
result = SDL_SetWindowFullscreen(m_window, 0);
if (result == 0) {
SDL_SetWindowSize(m_window, m_window_width, m_window_height);
} else {
sdl_util::log_error("unable to change window to windowed mode");
}
break;
case WindowDisplayMode::Fullscreen:
case WindowDisplayMode::Borderless:
// 1. exit fullscreen
result = SDL_SetWindowFullscreen(m_window, 0);
if (result == 0) {
SDL_Rect display_bounds;
result = SDL_GetDisplayBounds(m_selected_fullscreen_display_id, &display_bounds);
if (result < 0) {
sdl_util::log_error(fmt::format("unable to get display bounds for display id {}",
m_selected_fullscreen_display_id));
} else {
// 2. move it to the right monitor
SDL_SetWindowPosition(m_window, display_bounds.x + 50, display_bounds.y + 50);
if (mode == WindowDisplayMode::Fullscreen) {
update_video_modes();
// If fullscreen, we have to resize the window to take up the full resolution
//
// Some people are weird and don't use the monitor's maximum supported resolution
// in which case, we use what the user actually has selected.
const auto& display_res = m_current_display_modes.at(m_selected_fullscreen_display_id);
set_window_size(display_res.screen_width, display_res.screen_height);
}
// 3. fullscreen it!
result = SDL_SetWindowFullscreen(m_window, mode == WindowDisplayMode::Fullscreen
? SDL_WINDOW_FULLSCREEN
: SDL_WINDOW_FULLSCREEN_DESKTOP);
if (result < 0) {
sdl_util::log_error("unable to switch window fullscreen or borderless fullscreen");
}
}
} else {
sdl_util::log_error(
"unable to switch window to windowed mode in order to change fullscreen modes");
}
break;
}
if (result != 0) {
sdl_util::log_error(
fmt::format("unable to change window display mode to {}", fmt::underlying(mode)));
} else {
// Set the mode, now that we've been successful
m_window_display_mode = mode;
}
}
void DisplayManager::set_fullscreen_display_id(int display_id) {
if (display_id >= m_current_display_modes.size()) {
display_id = 0;
}
bool update_fullscreen = m_window_display_mode != WindowDisplayMode::Windowed &&
m_selected_fullscreen_display_id != display_id;
m_selected_fullscreen_display_id = display_id;
if (update_fullscreen) {
set_window_display_mode(m_window_display_mode);
}
}
void DisplayManager::update_curr_display_info() {
m_active_display_id = SDL_GetWindowDisplayIndex(m_window);
if (m_active_display_id < 0) {
sdl_util::log_error("could not retrieve current window's display index");
}
SDL_GL_GetDrawableSize(m_window, &m_window_width, &m_window_height);
SDL_GetWindowPosition(m_window, &m_window_xpos, &m_window_ypos);
// Update the scale of the display as well
// TODO - figure out how to do this on SDL
// https://github.com/libsdl-org/SDL/commit/ab81a559f43abc0858c96788f8e00bbb352287e8
m_window_scale_x = 1.0;
m_window_scale_y = 1.0;
}
void DisplayManager::update_video_modes() {
const auto num_displays = SDL_GetNumVideoDisplays();
if (num_displays < 0) {
sdl_util::log_error("could not retrieve number of displays");
return;
}
m_current_display_modes.clear();
SDL_DisplayMode curr_mode;
for (int display_id = 0; display_id < num_displays; display_id++) {
const auto success = SDL_GetCurrentDisplayMode(display_id, &curr_mode);
if (success != 0) {
sdl_util::log_error(
fmt::format("couldn't retrieve current display mode for display id {}", display_id));
continue;
}
auto display_orient = SDL_GetDisplayOrientation(display_id);
Orientation orient = Orientation::Unknown;
switch (display_orient) {
case SDL_ORIENTATION_LANDSCAPE:
orient = Orientation::Landscape;
break;
case SDL_ORIENTATION_LANDSCAPE_FLIPPED:
orient = Orientation::LandscapeFlipped;
break;
case SDL_ORIENTATION_PORTRAIT:
orient = Orientation::Portrait;
break;
case SDL_ORIENTATION_PORTRAIT_FLIPPED:
orient = Orientation::PortraitFlipped;
break;
default:
orient = Orientation::Unknown;
}
DisplayMode new_mode = {curr_mode.format, curr_mode.w, curr_mode.h, curr_mode.refresh_rate,
orient};
m_current_display_modes[display_id] = new_mode;
}
update_resolutions();
}
void DisplayManager::update_resolutions() {
// Enumerate display's display modes to get the resolutions
auto num_display_modes = SDL_GetNumDisplayModes(m_active_display_id);
SDL_DisplayMode curr_mode;
for (int i = 0; i < num_display_modes; i++) {
auto ok = SDL_GetDisplayMode(m_active_display_id, i, &curr_mode);
if (ok != 0) {
sdl_util::log_error(fmt::format("unable to get display mode for display {}, index {}",
m_active_display_id, i));
continue;
}
Resolution new_res = {curr_mode.w, curr_mode.h,
static_cast<float>(curr_mode.w) / static_cast<float>(curr_mode.h)};
m_available_resolutions.push_back(new_res);
}
// Add original NTSC/PAL options for weird people that want to act like they are on a PS2?
m_available_resolutions.push_back({512, 448, static_cast<float>(512) / static_cast<float>(448)});
m_available_resolutions.push_back({512, 224, static_cast<float>(512) / static_cast<float>(224)});
// Sort by area
std::sort(m_available_resolutions.begin(), m_available_resolutions.end(),
[](const Resolution& a, const Resolution& b) -> bool {
return a.width * a.height > b.width * b.height;
});
// Remove duplicate resolutions
auto last = std::unique(m_available_resolutions.begin(), m_available_resolutions.end(),
[](const Resolution& a, const Resolution& b) -> bool {
return (a.width == b.width && a.height == b.height);
});
m_available_resolutions.erase(last, m_available_resolutions.end());
}
std::string DisplayManager::get_connected_display_name(int id) {
const auto name = SDL_GetDisplayName(id);
if (name == NULL) {
sdl_util::log_error(fmt::format("couldn't retrieve display name with id {}", id));
return "";
}
return std::string(name);
}
+103
View File
@@ -0,0 +1,103 @@
#pragma once
#include <optional>
#include <string>
#include <unordered_map>
#include "game/settings/settings.h"
#include "third-party/SDL/include/SDL.h"
/*
TODO:
- hiDPI support, see https://wiki.libsdl.org/SDL2/SDL_GetRendererOutputSize
*/
enum class WindowDisplayMode { Windowed = 0, Fullscreen = 1, Borderless = 2 };
enum class WindowState { Minimized, Maximized, Restored };
enum class Orientation { Landscape, LandscapeFlipped, Portrait, PortraitFlipped, Unknown };
/// https://wiki.libsdl.org/SDL2/SDL_DisplayMode
struct DisplayMode {
/// https://wiki.libsdl.org/SDL2/SDL_PixelFormatEnum
uint32_t sdl_pixel_format;
int screen_width;
int screen_height;
/// refresh rate (in Hz), or 0 for unspecified
int refresh_rate;
Orientation orientation;
};
// Describes an available resolution
struct Resolution {
int width;
int height;
float aspect_ratio;
};
/// Monitors and handles all SDL events related to monitors and the window position
/// Stores related info for other parts of the application to use
/// Manages display related operations and querying
class DisplayManager {
public:
DisplayManager(SDL_Window* window);
~DisplayManager();
/// Propagate and handle the SDL event, ignoring it if it's not relevant
void process_sdl_event(const SDL_Event& event);
// Accessors
bool is_window_active() { return m_window != nullptr; }
bool is_minimized() { return m_window_state == WindowState::Minimized; }
int get_window_width() { return m_window_width; }
int get_window_height() { return m_window_height; }
float get_window_scale_x() { return m_window_scale_x; }
float get_window_scale_y() { return m_window_scale_y; }
int num_connected_displays() { return m_current_display_modes.size(); }
std::string get_connected_display_name(int id);
int get_active_display_refresh_rate();
int get_screen_width();
int get_screen_height();
WindowDisplayMode get_window_display_mode() { return m_window_display_mode; }
Resolution get_resolution(int id);
int get_num_resolutions() { return m_available_resolutions.size(); }
// Mutators
void set_window_resizable(bool resizable);
void set_window_size(int width, int height);
void initialize_window_position_from_settings();
void set_window_display_mode(WindowDisplayMode mode);
void set_fullscreen_display_id(int display_id);
private:
SDL_Window* m_window;
game_settings::DisplaySettings m_display_settings;
WindowDisplayMode m_window_display_mode = WindowDisplayMode::Windowed;
int m_active_display_id;
// This can differ from the active display id, this is set by the game
// to explicitly define which monitor the game should be fullscreened with
int m_selected_fullscreen_display_id;
int m_window_xpos = 0;
int m_window_ypos = 0;
int m_window_width = 0;
int m_window_height = 0;
float m_window_scale_x = 1.0;
float m_window_scale_y = 1.0;
WindowState m_window_state = WindowState::Restored;
// The currently set display mode for each display
// There is no reason to keep track of all display modes for all monitors
// the only one that matters is the one the user _currently_ has configured
//
// ie. allowing someone to set 150fps on a monitor set to 60hz is not correct
std::unordered_map<int, DisplayMode> m_current_display_modes;
std::vector<Resolution> m_available_resolutions;
void update_curr_display_info();
void update_video_modes();
void update_resolutions();
};
+339
View File
@@ -0,0 +1,339 @@
#include "input_bindings.h"
#include <algorithm>
#include "common/util/json_util.h"
#include "game/system/hid/sdl_util.h"
#include "third-party/SDL/include/SDL.h"
InputModifiers::InputModifiers(const u16 sdl_mod_state) {
need_shift = sdl_mod_state & KMOD_SHIFT;
need_alt = sdl_mod_state & KMOD_ALT;
need_ctrl = sdl_mod_state & KMOD_CTRL;
need_meta = sdl_mod_state & KMOD_GUI;
}
bool InputModifiers::has_necessary_modifiers(const u16 key_modifiers) const {
if (need_alt && ((key_modifiers & KMOD_ALT) == 0)) {
return false;
}
if (need_ctrl && ((key_modifiers & KMOD_CTRL) == 0)) {
return false;
}
if (need_meta && ((key_modifiers & KMOD_GUI) == 0)) {
return false;
}
if (need_shift && ((key_modifiers & KMOD_SHIFT) == 0)) {
return false;
}
return true;
}
void to_json(json& j, const InputModifiers& obj) {
j = json{{"need_shift", obj.need_shift},
{"need_ctrl", obj.need_ctrl},
{"need_meta", obj.need_meta},
{"need_alt", obj.need_alt}};
}
void from_json(const json& j, InputModifiers& obj) {
json_deserialize_if_exists(need_shift);
json_deserialize_if_exists(need_ctrl);
json_deserialize_if_exists(need_meta);
json_deserialize_if_exists(need_alt);
}
void to_json(json& j, const InputBinding& obj) {
j = json{{"pad_data_index", obj.pad_data_index},
{"minimum_in_range", obj.minimum_in_range},
{"modifiers", obj.modifiers}};
}
void from_json(const json& j, InputBinding& obj) {
json_deserialize_if_exists(pad_data_index);
json_deserialize_if_exists(minimum_in_range);
json_deserialize_if_exists(modifiers);
}
void to_json(json& j, const InputBindingGroups& obj) {
j = json{{"device_type", obj.device_type},
{"analog_axii", obj.analog_axii},
{"button_axii", obj.button_axii},
{"buttons", obj.buttons}};
}
void from_json(const json& j, InputBindingGroups& obj) {
json_deserialize_if_exists(device_type);
json_deserialize_if_exists(analog_axii);
json_deserialize_if_exists(button_axii);
json_deserialize_if_exists(buttons);
}
const std::vector<PadData::ButtonIndex> PAD_DATA_PRESSURE_INDEX_ORDER = {
PadData::ButtonIndex::DPAD_RIGHT, PadData::ButtonIndex::DPAD_LEFT,
PadData::ButtonIndex::DPAD_UP, PadData::ButtonIndex::DPAD_DOWN,
PadData::ButtonIndex::TRIANGLE, PadData::ButtonIndex::CIRCLE,
PadData::ButtonIndex::CROSS, PadData::ButtonIndex::SQUARE,
PadData::ButtonIndex::L1, PadData::ButtonIndex::R1,
PadData::ButtonIndex::L2, PadData::ButtonIndex::R2};
const InputBindingGroups DEFAULT_CONTROLLER_BINDS = InputBindingGroups(
CONTROLLER,
{{SDL_CONTROLLER_AXIS_LEFTX, {InputBinding(PadData::AnalogIndex::LEFT_X)}},
{SDL_CONTROLLER_AXIS_LEFTY, {InputBinding(PadData::AnalogIndex::LEFT_Y)}},
{SDL_CONTROLLER_AXIS_RIGHTX, {InputBinding(PadData::AnalogIndex::RIGHT_X)}},
{SDL_CONTROLLER_AXIS_RIGHTY, {InputBinding(PadData::AnalogIndex::RIGHT_Y)}}},
{
{SDL_CONTROLLER_AXIS_TRIGGERLEFT, {InputBinding(PadData::ButtonIndex::L2)}},
{SDL_CONTROLLER_AXIS_TRIGGERRIGHT, {InputBinding(PadData::ButtonIndex::R2)}},
},
{{SDL_CONTROLLER_BUTTON_A, {InputBinding(PadData::ButtonIndex::CROSS)}},
{SDL_CONTROLLER_BUTTON_B, {InputBinding(PadData::ButtonIndex::CIRCLE)}},
{SDL_CONTROLLER_BUTTON_X, {InputBinding(PadData::ButtonIndex::SQUARE)}},
{SDL_CONTROLLER_BUTTON_Y, {InputBinding(PadData::ButtonIndex::TRIANGLE)}},
{SDL_CONTROLLER_BUTTON_LEFTSTICK, {InputBinding(PadData::ButtonIndex::L3)}},
{SDL_CONTROLLER_BUTTON_RIGHTSTICK, {InputBinding(PadData::ButtonIndex::R3)}},
{SDL_CONTROLLER_BUTTON_BACK, {InputBinding(PadData::ButtonIndex::SELECT)}},
{SDL_CONTROLLER_BUTTON_START, {InputBinding(PadData::ButtonIndex::START)}},
{SDL_CONTROLLER_BUTTON_LEFTSHOULDER, {InputBinding(PadData::ButtonIndex::L1)}},
{SDL_CONTROLLER_BUTTON_RIGHTSHOULDER, {InputBinding(PadData::ButtonIndex::R1)}},
{SDL_CONTROLLER_BUTTON_DPAD_UP, {InputBinding(PadData::ButtonIndex::DPAD_UP)}},
{SDL_CONTROLLER_BUTTON_DPAD_DOWN, {InputBinding(PadData::ButtonIndex::DPAD_DOWN)}},
{SDL_CONTROLLER_BUTTON_DPAD_LEFT, {InputBinding(PadData::ButtonIndex::DPAD_LEFT)}},
{SDL_CONTROLLER_BUTTON_DPAD_RIGHT, {InputBinding(PadData::ButtonIndex::DPAD_RIGHT)}}});
const InputBindingGroups DEFAULT_KEYBOARD_BINDS =
InputBindingGroups(KEYBOARD,
{{SDLK_a, {InputBinding(PadData::AnalogIndex::LEFT_X, true)}},
{SDLK_d, {InputBinding(PadData::AnalogIndex::LEFT_X)}},
{SDLK_s, {InputBinding(PadData::AnalogIndex::LEFT_Y)}},
{SDLK_w, {InputBinding(PadData::AnalogIndex::LEFT_Y, true)}},
{SDLK_l, {InputBinding(PadData::AnalogIndex::RIGHT_X, true)}},
{SDLK_j, {InputBinding(PadData::AnalogIndex::RIGHT_X)}},
{SDLK_k, {InputBinding(PadData::AnalogIndex::RIGHT_Y)}},
{SDLK_i, {InputBinding(PadData::AnalogIndex::RIGHT_Y, true)}}},
{},
{{SDLK_SPACE, {InputBinding(PadData::ButtonIndex::CROSS)}},
{SDLK_f, {InputBinding(PadData::ButtonIndex::CIRCLE)}},
{SDLK_e, {InputBinding(PadData::ButtonIndex::SQUARE)}},
{SDLK_r, {InputBinding(PadData::ButtonIndex::TRIANGLE)}},
{SDLK_COMMA, {InputBinding(PadData::ButtonIndex::L3)}},
{SDLK_PERIOD, {InputBinding(PadData::ButtonIndex::R3)}},
{SDLK_QUOTE, {InputBinding(PadData::ButtonIndex::SELECT)}},
{SDLK_RETURN, {InputBinding(PadData::ButtonIndex::START)}},
{SDLK_o, {InputBinding(PadData::ButtonIndex::L1)}},
{SDLK_q, {InputBinding(PadData::ButtonIndex::R1)}},
{SDLK_1, {InputBinding(PadData::ButtonIndex::L2)}},
{SDLK_p, {InputBinding(PadData::ButtonIndex::R2)}},
{SDLK_UP, {InputBinding(PadData::ButtonIndex::DPAD_UP)}},
{SDLK_DOWN, {InputBinding(PadData::ButtonIndex::DPAD_DOWN)}},
{SDLK_LEFT, {InputBinding(PadData::ButtonIndex::DPAD_LEFT)}},
{SDLK_RIGHT, {InputBinding(PadData::ButtonIndex::DPAD_RIGHT)}}});
const InputBindingGroups DEFAULT_MOUSE_BINDS = InputBindingGroups(MOUSE, {}, {}, {});
std::vector<InputBindingInfo> InputBindingGroups::lookup_analog_binds(PadData::AnalogIndex idx,
bool only_minimum_binds) {
// First see if it's in the cache, if it is return it
if (m_analog_lookup.find({idx, only_minimum_binds}) != m_analog_lookup.end()) {
return m_analog_lookup.at({idx, only_minimum_binds});
}
// Didn't find it, let's construct the cache entry
std::vector<InputBindingInfo> entry = {};
for (const auto& [sdl_code, binds] : analog_axii) {
for (const auto& bind : binds) {
if (bind.pad_data_index != idx || bind.minimum_in_range != only_minimum_binds) {
continue;
}
InputBindingInfo new_info;
switch (device_type) {
case KEYBOARD:
new_info = InputBindingInfo(bind, device_type, sdl_code);
break;
default:
new_info.host_name = "TODO - NON-KB ANALOG BIND LOOKUP";
break;
}
entry.push_back(new_info);
}
}
m_analog_lookup[{idx, only_minimum_binds}] = entry;
return entry;
}
std::vector<InputBindingInfo> InputBindingGroups::lookup_button_binds(PadData::ButtonIndex idx) {
// First see if it's in the cache, if it is return it
if (m_button_lookup.find({idx, true}) != m_button_lookup.end()) {
return m_button_lookup.at({idx, true});
}
// Didn't find it, let's construct the cache entry
std::vector<InputBindingInfo> entry = {};
for (const auto& [sdl_code, binds] : buttons) {
for (const auto& bind : binds) {
if (bind.pad_data_index != idx) {
continue;
}
InputBindingInfo new_info(bind, device_type, sdl_code);
entry.push_back(new_info);
}
}
for (const auto& [sdl_code, binds] : button_axii) {
for (const auto& bind : binds) {
if (bind.pad_data_index != idx) {
continue;
}
InputBindingInfo new_info(bind, device_type, sdl_code);
entry.push_back(new_info);
}
}
m_button_lookup[{idx, true}] = entry;
return entry;
}
void InputBindingGroups::assign_analog_bind(u32 sdl_idx,
InputBindAssignmentMeta& bind_meta,
const std::optional<InputModifiers> modifiers) {
// Find out if the PS2 input is already bound, if it is we will do a swap so no input is ever left
// unmapped
const auto current_binds =
lookup_analog_binds((PadData::AnalogIndex)bind_meta.pad_idx, bind_meta.for_analog_minimum);
if (analog_axii.find(sdl_idx) != analog_axii.end()) {
const auto existing_binds = analog_axii.at(sdl_idx);
analog_axii[sdl_idx] = {InputBinding((PadData::AnalogIndex)bind_meta.pad_idx,
bind_meta.for_analog_minimum, modifiers)};
// there already a bind, so swap (as long as it's not the same key)
if (!current_binds.empty() && current_binds.front().sdl_idx != sdl_idx) {
analog_axii[current_binds.front().sdl_idx] = existing_binds;
}
} else {
analog_axii[sdl_idx] = {InputBinding((PadData::AnalogIndex)bind_meta.pad_idx,
bind_meta.for_analog_minimum, modifiers)};
}
// The underlying data structures support multiple binds for the same input, but the UX doesn't
// so we have to wipe out any shared bindings after an assignment
for (auto it = analog_axii.begin(); it != analog_axii.end();) {
if (it->first == sdl_idx) {
it++;
continue;
}
bool found_match = false;
for (const auto& bind : it->second) {
if (bind.pad_data_index != bind_meta.pad_idx ||
bind.minimum_in_range != bind_meta.for_analog_minimum) {
continue;
}
it = analog_axii.erase(it);
found_match = true;
break;
}
if (!found_match) {
it++;
}
}
// Invalidate lookup cache
m_analog_lookup.clear();
bind_meta.assigned = true;
}
void InputBindingGroups::assign_button_bind(u32 sdl_idx,
InputBindAssignmentMeta& bind_meta,
const bool analog_button,
const std::optional<InputModifiers> modifiers) {
// Find out if the PS2 input is already bound, if it is we will do a swap so no input is ever left
// unmapped
const auto current_binds = lookup_button_binds((PadData::ButtonIndex)bind_meta.pad_idx);
if (buttons.find(sdl_idx) != buttons.end()) {
const auto existing_binds = buttons.at(sdl_idx);
if (analog_button) {
button_axii[sdl_idx] = {InputBinding((PadData::ButtonIndex)bind_meta.pad_idx, modifiers)};
} else {
buttons[sdl_idx] = {InputBinding((PadData::ButtonIndex)bind_meta.pad_idx, modifiers)};
}
// there already a bind, so swap (as long as it's not the same key)
if (!current_binds.empty() && current_binds.front().sdl_idx != sdl_idx) {
if (current_binds.front().analog_button) {
button_axii[current_binds.front().sdl_idx] = existing_binds;
} else {
buttons[current_binds.front().sdl_idx] = existing_binds;
}
}
} else {
if (analog_button) {
button_axii[sdl_idx] = {InputBinding((PadData::ButtonIndex)bind_meta.pad_idx, modifiers)};
} else {
buttons[sdl_idx] = {InputBinding((PadData::ButtonIndex)bind_meta.pad_idx, modifiers)};
}
}
// The underlying data structures support multiple binds for the same input, but the UX doesn't
// so we have to wipe out any shared bindings after an assignment
for (auto it = buttons.begin(); it != buttons.end();) {
if (it->first == sdl_idx) {
it++;
continue;
}
bool found_match = false;
for (const auto& bind : it->second) {
if (bind.pad_data_index != bind_meta.pad_idx) {
continue;
}
it = buttons.erase(it);
found_match = true;
break;
}
if (!found_match) {
it++;
}
}
for (auto it = button_axii.begin(); it != button_axii.end();) {
if (it->first == sdl_idx) {
it++;
continue;
}
bool found_match = false;
for (const auto& bind : it->second) {
if (bind.pad_data_index != bind_meta.pad_idx) {
continue;
}
it = button_axii.erase(it);
found_match = true;
break;
}
if (!found_match) {
it++;
}
}
// Invalidate lookup cache
m_button_lookup.clear();
bind_meta.assigned = true;
}
void InputBindingGroups::set_bindings(const InputBindingGroups& binds) {
analog_axii = binds.analog_axii;
button_axii = binds.button_axii;
buttons = binds.buttons;
m_analog_lookup.clear();
m_button_lookup.clear();
}
InputBindingInfo::InputBindingInfo(const InputBinding bind,
const InputDeviceType device_type,
const s32 sdl_code)
: sdl_idx(sdl_code), pad_idx(bind.pad_data_index), modifiers(bind.modifiers) {
analog_button = false;
switch (device_type) {
case CONTROLLER:
host_name = sdl_util::get_controller_button_name(sdl_code);
break;
case KEYBOARD:
host_name = sdl_util::get_keyboard_button_name(sdl_code, modifiers);
break;
case MOUSE:
host_name = sdl_util::get_mouse_button_name(sdl_code, modifiers);
break;
}
}
+325
View File
@@ -0,0 +1,325 @@
#pragma once
#pragma once
#include <algorithm>
#include <array>
#include <functional>
#include <optional>
#include <string>
#include <unordered_map>
#include <utility>
#include "common/common_types.h"
#include "common/log/log.h"
#include "common/util/json_util.h"
/// A simple abstraction around the PS2 controller data with some convenience functions for
/// pulling specific data out if it's useful.
///
/// Pressure is always assumed to be the max, barely any input library handles pressure properly
/// and the VAST majority of controllers don't even support it either.
struct PadData {
enum AnalogIndex { LEFT_X = 0, LEFT_Y, RIGHT_X, RIGHT_Y = 3 };
enum ButtonIndex {
SELECT = 0,
L3,
R3,
START,
DPAD_UP,
DPAD_RIGHT,
DPAD_DOWN,
DPAD_LEFT,
L2,
R2,
L1,
R1,
TRIANGLE,
CIRCLE,
CROSS,
SQUARE = 15
};
static const int ANALOG_NEUTRAL = 127;
// NOTE - store analog values as larger signed integers and then clamp them to their 0-255
// u8 range. This is to make it easier to properly handle multiple non-analog sources attempting
// to simulate analog sticks
//
// Imagine you have 100 keys bound to moving forward and back, and all those key up / key down
// events are encountered asynchrously, only when their state changes
//
// There are a lot of strategies to handle this:
// - you can delay applying the input by one frame, so you have a wholistic viewpoint. But this
// delay is undesirable and doesn't work elegantly with multiple input sources
// - you can have a bunch of complicated state overseeing it all
// - or (with this approach) you can process the events asynchronously, adding and subtracting
// from the aggregate amount and organically end up with the correct value
//
// For a tangible example, imagine you have `W` bound to move forward, as well as left click
// - subtract `127` when `W` is pressed = 0 (127 is neutral)
// - subtract `127` when `Mouse1` is pressed = -127 (clamp to 0 for the game)
// - add `127` when `W` is released = 0
// - add `127` when `Mouse1` is released = 127 (back to neutral)
//
// Because keys that are pressed, must eventually be released -- this has a fairly strong
// guarantee. About the only hole is if you decided to unplug your keyboard while holding a key,
// which is something we can distinctly detect and handle with a decent compromise (reset the
// inputs)
std::array<int, 4> analog_data = {ANALOG_NEUTRAL, ANALOG_NEUTRAL, ANALOG_NEUTRAL, ANALOG_NEUTRAL};
std::pair<u8, u8> analog_left() const {
return {std::clamp(analog_data.at(AnalogIndex::LEFT_X), 0, 255),
std::clamp(analog_data.at(AnalogIndex::LEFT_Y), 0, 255)};
}
std::pair<u8, u8> analog_right() const {
return {std::clamp(analog_data.at(AnalogIndex::RIGHT_X), 0, 255),
std::clamp(analog_data.at(AnalogIndex::RIGHT_Y), 0, 255)};
}
std::array<bool, 16> button_data = {};
// Normal Buttons
bool select() const { return button_data.at(ButtonIndex::SELECT); };
bool l3() const { return button_data.at(ButtonIndex::L3); };
bool r3() const { return button_data.at(ButtonIndex::R3); };
bool start() const { return button_data.at(ButtonIndex::START); };
// Pressure Buttons
std::pair<bool, u8> dpad_up() const { return {button_data.at(ButtonIndex::DPAD_UP), 255}; };
std::pair<bool, u8> dpad_right() const { return {button_data.at(ButtonIndex::DPAD_RIGHT), 255}; };
std::pair<bool, u8> dpad_down() const { return {button_data.at(ButtonIndex::DPAD_DOWN), 255}; };
std::pair<bool, u8> dpad_left() const { return {button_data.at(ButtonIndex::DPAD_LEFT), 255}; };
std::pair<bool, u8> l2() const { return {button_data.at(ButtonIndex::L2), 255}; };
std::pair<bool, u8> r2() const { return {button_data.at(ButtonIndex::R2), 255}; };
std::pair<bool, u8> l1() const { return {button_data.at(ButtonIndex::L1), 255}; };
std::pair<bool, u8> r1() const { return {button_data.at(ButtonIndex::R1), 255}; };
std::pair<bool, u8> triangle() const { return {button_data.at(ButtonIndex::TRIANGLE), 255}; };
std::pair<bool, u8> circle() const { return {button_data.at(ButtonIndex::CIRCLE), 255}; };
std::pair<bool, u8> cross() const { return {button_data.at(ButtonIndex::CROSS), 255}; };
std::pair<bool, u8> square() const { return {button_data.at(ButtonIndex::SQUARE), 255}; };
// Analog Simulation Tracking
// There exists a flaw with the described analog tracking approach described above, and that has
// to do with imprecise analog drift / fluctuating numbers.
//
// If you switch between a keyboard and a controller, sometimes the controllers analog stick can
// oscillate between values triggering frivolous events. This impacts the usage of the
// alternative input device such as the keyboard or mouse
//
// To solve this, we keep track of if the user is actively using the keyboard or mouse to
// manipulate the analog values and if they are, we can ignore controller analog inputs. Once all
// inputs are released the controller input can resume.
private:
int analog_sim_tracker = 0;
public:
bool analogs_being_simulated() { return analog_sim_tracker > 0; }
void update_analog_sim_tracker(bool released) {
if (released) {
analog_sim_tracker--;
} else {
analog_sim_tracker++;
}
if (analog_sim_tracker < 0) {
analog_sim_tracker = 0;
}
}
void clear() {
for (int i = 0; i < button_data.size(); i++) {
button_data[i] = 0;
}
clear_analogs();
}
void clear_analogs() {
for (int i = 0; i < analog_data.size(); i++) {
analog_data[i] = ANALOG_NEUTRAL;
}
analog_sim_tracker = 0;
}
};
// order of pressure sensitive buttons in memory (not the same as their bit order...).
extern const std::vector<PadData::ButtonIndex> PAD_DATA_PRESSURE_INDEX_ORDER;
// https://wiki.libsdl.org/SDL2/SDL_Keymod
struct InputModifiers {
InputModifiers() = default;
InputModifiers(const u16 sdl_mod_state);
bool need_shift = false;
bool need_ctrl = false;
bool need_meta = false; // aka GUI / windows key
bool need_alt = false;
bool has_necessary_modifiers(const u16 key_modifiers) const;
};
void to_json(json& j, const InputModifiers& obj);
void from_json(const json& j, InputModifiers& obj);
/// Contains all information needed when processing a host input (ie. from SDL)
/// For example -- for a keyboard binding it informs us what modifiers need to be hit at the same
/// time, etc
///
/// All bindings _must_ provide the PS2 button/analog index they map to
///
/// There is also a special case for binary inputs mapping to the analog sticks. In such a
/// situation both keys will be modifying the same underlying value, but one will mutate the value
/// to the minimum of the range and the other to the maximum. The key intended to map to the
/// minimum should specify `true` for `minimum_in_range`.
///
/// For example, pressing both W and S should result in Jak not moving. And then letting go of W
/// should make him move towards the camera.
struct InputBinding {
InputBinding() = default;
InputBinding(int index) : pad_data_index(index){};
InputBinding(int index, const std::optional<InputModifiers> _modifiers) : pad_data_index(index) {
if (_modifiers) {
modifiers = _modifiers.value();
}
};
InputBinding(int index, bool _minimum_in_range)
: pad_data_index(index), minimum_in_range(_minimum_in_range){};
InputBinding(int index, bool _minimum_in_range, const std::optional<InputModifiers> _modifiers)
: pad_data_index(index), minimum_in_range(_minimum_in_range) {
if (_modifiers) {
modifiers = _modifiers.value();
}
};
/// Corresponds to PadData::AnalogIndex or PadData::ButtonIndex
int pad_data_index;
/// If considered pressed, it will invert the value (ie, left/right on an analog stick)
bool minimum_in_range = false;
InputModifiers modifiers;
};
void to_json(json& j, const InputBinding& obj);
void from_json(const json& j, InputBinding& obj);
enum InputDeviceType { CONTROLLER = 0, KEYBOARD = 1, MOUSE = 2 };
struct InputBindingInfo {
s32 sdl_idx;
u32 pad_idx;
std::string host_name;
bool analog_button;
InputModifiers modifiers;
InputBindingInfo() = default;
InputBindingInfo(const InputBinding bind, const InputDeviceType device_type, const s32 sdl_code);
};
// Contains all info related to the current binding we are waiting for
// so the relevant device can successfully apply it
struct InputBindAssignmentMeta {
InputDeviceType device_type = InputDeviceType::CONTROLLER;
int pad_idx;
bool for_analog = false;
bool for_analog_minimum = false;
// For only rebinding keyboards, we have to know what keys were originally bound to the
// confirmation buttons this is because the user has to hit said key to initiate waiting for the
// new assignment
//
// For most input sources this doesn't matter because we listen for the DOWN event, but in order
// to allow modifiers as binds (ie. Shift for X) we have to listen to UP events as well (only for
// the modifiers)
//
// TLDR - we ignore the first UP event if it was bound to a confirmation key. Additionally, this
// depends on the game as Jak 1 treats X or O as a confirm key...
std::vector<InputBindingInfo> keyboard_confirmation_binds = {};
bool seen_confirm_up = false;
// Indicates the binding has been received, assigned, and we can proceed.
bool assigned = false;
};
struct InputBindingGroups {
InputBindingGroups() = default;
InputBindingGroups(const InputDeviceType _device_type,
std::unordered_map<u32, std::vector<InputBinding>> _analog_axii,
std::unordered_map<u32, std::vector<InputBinding>> _button_axii,
std::unordered_map<u32, std::vector<InputBinding>> _buttons)
: device_type(_device_type),
analog_axii(_analog_axii),
button_axii(_button_axii),
buttons(_buttons){};
// TODO - make these private
InputDeviceType device_type;
std::unordered_map<u32, std::vector<InputBinding>> analog_axii;
std::unordered_map<u32, std::vector<InputBinding>> button_axii;
std::unordered_map<u32, std::vector<InputBinding>> buttons;
std::vector<InputBindingInfo> lookup_analog_binds(PadData::AnalogIndex idx,
bool only_minimum_binds = false);
std::vector<InputBindingInfo> lookup_button_binds(PadData::ButtonIndex idx);
void assign_analog_bind(u32 sdl_idx,
InputBindAssignmentMeta& bind_meta,
const std::optional<InputModifiers> modifiers = {});
void assign_button_bind(u32 sdl_idx,
InputBindAssignmentMeta& bind_meta,
const bool analog_button = false,
const std::optional<InputModifiers> modifiers = {});
void set_bindings(const InputBindingGroups& binds);
private:
typedef std::pair<int, bool> BindCacheKey;
struct hash_name {
size_t operator()(const BindCacheKey& key) const {
return std::hash<int>()(key.first) ^ std::hash<bool>()(key.second);
}
};
// These are caches for reverse-lookups (from the mapped bind instead of the host bind)
// for reading inputs we keep things fast -- we start with an SDL host value and map it to the
// required binds
//
// However there are some situations where we want to the reverse -- find out what binds
// correspond with the PS2 value. Such as when remapping a key so you can unbind overlapping binds
std::unordered_map<BindCacheKey, std::vector<InputBindingInfo>, hash_name> m_analog_lookup;
std::unordered_map<BindCacheKey, std::vector<InputBindingInfo>, hash_name> m_button_lookup;
};
void to_json(json& j, const InputBindingGroups& obj);
void from_json(const json& j, InputBindingGroups& obj);
/// https://wiki.libsdl.org/SDL2/SDL_GameControllerButton
extern const InputBindingGroups DEFAULT_CONTROLLER_BINDS;
/// https://wiki.libsdl.org/SDL2/SDL_Keycode
extern const InputBindingGroups DEFAULT_KEYBOARD_BINDS;
/// https://wiki.libsdl.org/SDL2/SDL_MouseButtonEvent
extern const InputBindingGroups DEFAULT_MOUSE_BINDS;
/// A CommandBinding by contrast is a way to map some arbitrary runtime command to
/// a user initiated input.
///
/// These are not used to mutate the state of a PadData object and instead run
/// an arbitrary lambda (with no return value) when triggered.
///
/// An example of these would be taking a screenshot or save-state actions
struct CommandBinding {
enum Source { CONTROLLER, KEYBOARD, MOUSE };
CommandBinding(const u32 _host_key, std::function<void()> _command)
: host_key(_host_key), command(_command){};
u32 host_key;
std::function<void()> command;
InputModifiers modifiers;
};
struct CommandBindingGroups {
std::unordered_map<u32, std::vector<CommandBinding>> controller_binds;
std::unordered_map<u32, std::vector<CommandBinding>> keyboard_binds;
std::unordered_map<u32, std::vector<CommandBinding>> mouse_binds;
};
+375
View File
@@ -0,0 +1,375 @@
#include "input_manager.h"
#include <atomic>
#include <cmath>
#include "input_manager.h"
#include "sdl_util.h"
#include "common/log/log.h"
#include "common/util/Assert.h"
#include "common/util/FileUtil.h"
#include "game/graphics/pipelines/opengl.h"
#include "game/runtime.h"
#include "third-party/imgui/imgui.h"
InputManager::InputManager()
// Load user settings
: m_settings(std::make_shared<game_settings::InputSettings>(game_settings::InputSettings())) {
// Update to latest controller DB file
std::string mapping_path =
(file_util::get_jak_project_dir() / "game" / "assets" / "sdl_controller_db.txt").string();
if (file_util::file_exists(mapping_path)) {
SDL_GameControllerAddMappingsFromFile(mapping_path.c_str());
} else {
lg::error("Could not find SDL Controller DB at path `{}`", mapping_path);
}
// Initialize atleast 2 ports, because that's normal for Jak
// more will be allocated if more controllers are found
m_data[0] = std::make_shared<PadData>();
m_data[1] = std::make_shared<PadData>();
m_keyboard = KeyboardDevice(m_settings);
m_mouse = MouseDevice(m_settings);
if (m_data.find(m_keyboard_and_mouse_port) == m_data.end()) {
m_data[m_keyboard_and_mouse_port] = std::make_shared<PadData>();
}
m_command_binds = CommandBindingGroups();
refresh_device_list();
ignore_background_controller_events(false);
hide_cursor(m_auto_hide_mouse);
}
InputManager::~InputManager() {
for (auto& device : m_available_controllers) {
device->close_device();
}
m_settings->save_settings();
}
void InputManager::refresh_device_list() {
m_available_controllers.clear();
m_controller_port_mapping.clear();
// Enumerate devices
const auto num_joysticks = SDL_NumJoysticks();
if (num_joysticks > 0) {
for (int i = 0; i < num_joysticks; i++) {
if (!SDL_IsGameController(i)) {
lg::error("Controller with device id {} is not avaiable via the GameController API", i);
continue;
}
auto controller = std::make_shared<GameController>(i, m_settings);
m_available_controllers.push_back(controller);
// By default, controller port mapping is on a first-come-first-served basis
//
// However, we will use previously saved controller port mappings to take precedence
// For example, if you previous set your PS5 controller to be port 0, then even
// if another controller is detected first, the PS5 controller should be assigned as expected.
if (m_settings->controller_port_mapping.find(controller->get_guid()) !=
m_settings->controller_port_mapping.end()) {
// Though it's possible for a user to assign multiple controllers to the same port, so the
// last one wins
m_controller_port_mapping[m_settings->controller_port_mapping.at(controller->get_guid())] =
i;
} else {
m_controller_port_mapping[m_available_controllers.size() - 1] = i;
m_settings->controller_port_mapping[controller->get_guid()] =
m_available_controllers.size() - 1;
}
// Allocate a PadData if this is a new port
if (m_data.find(i) == m_data.end()) {
m_data[i] = std::make_shared<PadData>();
}
}
// If the controller that was last selected to be port 0 is around, prioritize it
if (!m_settings->last_selected_controller_guid.empty()) {
for (int i = 0; i < m_available_controllers.size(); i++) {
const auto& controller_guid = m_available_controllers.at(i)->get_guid();
if (controller_guid == m_settings->last_selected_controller_guid) {
m_controller_port_mapping[0] = i;
m_settings->controller_port_mapping[controller_guid] = 0;
break;
}
}
}
}
if (m_available_controllers.empty()) {
lg::warn(
"No active game controllers could be found or loaded successfully - inputs will not work!");
} else {
lg::info("Found {} controllers", m_available_controllers.size());
}
}
void InputManager::ignore_background_controller_events(const bool ignore) {
m_ignore_background_controller_events = ignore;
// TODO - ignoring return value (atleast log it)
SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, ignore ? "0" : "1");
}
void InputManager::hide_cursor(const bool hide_cursor) {
if (hide_cursor == m_mouse_currently_hidden) {
return;
}
auto ok = SDL_ShowCursor(hide_cursor ? SDL_DISABLE : SDL_ENABLE);
if (ok < 0) {
sdl_util::log_error("Unable to show/hide mouse cursor");
} else {
m_mouse_currently_hidden = hide_cursor;
}
}
void InputManager::process_sdl_event(const SDL_Event& event,
const bool ignore_mouse,
const bool ignore_kb) {
// Detect controller connections and disconnects
if (sdl_util::is_any_event_type(event.type,
{SDL_CONTROLLERDEVICEADDED, SDL_CONTROLLERDEVICEREMOVED})) {
lg::info("Controller added or removed. refreshing controller device list");
refresh_device_list();
}
if (!m_ignored_device_last_frame && (ignore_mouse || ignore_kb)) {
clear_inputs();
m_ignored_device_last_frame = true;
} else if (m_ignored_device_last_frame && !ignore_mouse && !ignore_kb) {
m_ignored_device_last_frame = false;
}
if (m_data.find(m_keyboard_and_mouse_port) != m_data.end()) {
m_keyboard.process_event(event, m_command_binds, m_data.at(m_keyboard_and_mouse_port),
m_waiting_for_bind, ignore_kb || !m_keyboard_enabled);
m_mouse.process_event(event, m_command_binds, m_data.at(m_keyboard_and_mouse_port),
m_waiting_for_bind, ignore_mouse || !m_mouse_enabled);
}
// Send event to active controller device
// This goes last so it takes precedence
for (const auto& [port, controller_idx] : m_controller_port_mapping) {
if (m_data.find(port) != m_data.end() && m_available_controllers.size() > controller_idx) {
m_available_controllers.at(controller_idx)
->process_event(event, m_command_binds, m_data.at(port), m_waiting_for_bind);
}
}
// Clear the binding assignment if we got one
if (m_waiting_for_bind && m_waiting_for_bind->assigned) {
stop_waiting_for_bind();
}
// Adjust mouse cursor visibility
if (m_auto_hide_mouse) {
if (event.type == SDL_MOUSEMOTION) {
hide_cursor(false);
} else if (event.type == SDL_KEYDOWN || event.type == SDL_CONTROLLERBUTTONDOWN) {
hide_cursor(true);
}
}
}
std::optional<std::shared_ptr<PadData>> InputManager::get_current_data(const int port) const {
if (m_data.find(port) == m_data.end()) {
return {};
}
return m_data.at(port);
}
int InputManager::update_rumble(int port, u8 low_intensity, u8 high_intensity) {
if (m_controller_port_mapping.find(port) == m_controller_port_mapping.end()) {
return 0;
}
return m_available_controllers.at(m_controller_port_mapping.at(port))
->update_rumble(low_intensity, high_intensity);
}
void InputManager::register_command(const CommandBinding::Source source,
const CommandBinding bind) {
switch (source) {
case CommandBinding::Source::CONTROLLER:
if (m_command_binds.controller_binds.find(bind.host_key) ==
m_command_binds.controller_binds.end()) {
m_command_binds.controller_binds[bind.host_key] = {};
}
m_command_binds.controller_binds[bind.host_key].push_back(bind);
break;
case CommandBinding::Source::KEYBOARD:
if (m_command_binds.keyboard_binds.find(bind.host_key) ==
m_command_binds.keyboard_binds.end()) {
m_command_binds.keyboard_binds[bind.host_key] = {};
}
m_command_binds.keyboard_binds[bind.host_key].push_back(bind);
break;
case CommandBinding::Source::MOUSE:
if (m_command_binds.mouse_binds.find(bind.host_key) == m_command_binds.mouse_binds.end()) {
m_command_binds.mouse_binds[bind.host_key] = {};
}
m_command_binds.mouse_binds[bind.host_key].push_back(bind);
break;
}
}
std::string InputManager::get_controller_name(const int controller_id) {
if (controller_id >= m_available_controllers.size()) {
return "";
}
return m_available_controllers.at(controller_id)->get_name();
}
std::string InputManager::get_current_bind(const int port,
const InputDeviceType device_type,
const bool buttons,
const int input_idx,
const bool analog_for_minimum) {
std::vector<InputBindingInfo> binding_info;
switch (device_type) {
case InputDeviceType::CONTROLLER:
if (m_controller_port_mapping.find(port) != m_controller_port_mapping.end() &&
m_controller_port_mapping.at(port) < m_available_controllers.size() &&
m_settings->controller_binds.find(
m_available_controllers.at(m_controller_port_mapping.at(port))->get_guid()) !=
m_settings->controller_binds.end()) {
binding_info =
m_settings->controller_binds
.at(m_available_controllers.at(m_controller_port_mapping.at(port))->get_guid())
.lookup_button_binds((PadData::ButtonIndex)input_idx);
}
break;
case InputDeviceType::KEYBOARD:
if (!buttons) {
binding_info = m_settings->keyboard_binds.lookup_analog_binds(
(PadData::AnalogIndex)input_idx, analog_for_minimum);
} else {
binding_info =
m_settings->keyboard_binds.lookup_button_binds((PadData::ButtonIndex)input_idx);
}
break;
case InputDeviceType::MOUSE:
binding_info = m_settings->mouse_binds.lookup_button_binds((PadData::ButtonIndex)input_idx);
break;
}
if (binding_info.empty()) {
return "";
}
return binding_info.front().host_name;
}
void InputManager::set_controller_for_port(const int controller_id, const int port) {
if (controller_id < m_available_controllers.size()) {
// Reset inputs as this device won't be able to be read from again!
clear_inputs();
auto& controller = m_available_controllers.at(controller_id);
m_controller_port_mapping[port] = controller_id;
m_settings->controller_port_mapping[controller->get_guid()] = port;
// NOTE - only tracking port 0 for now
if (port == 0) {
m_settings->last_selected_controller_guid = controller->get_guid();
}
m_settings->save_settings();
}
}
bool InputManager::controller_has_led(const int port) {
if (m_controller_port_mapping.find(0) == m_controller_port_mapping.end()) {
return false;
}
const auto id = m_controller_port_mapping.at(port);
if (id >= m_available_controllers.size()) {
return false;
}
return m_available_controllers.at(id)->has_led();
}
void InputManager::set_controller_led(const int port, const u8 red, const u8 green, const u8 blue) {
if (m_controller_port_mapping.find(0) == m_controller_port_mapping.end()) {
return;
}
const auto id = m_controller_port_mapping.at(port);
if (id >= m_available_controllers.size()) {
return;
}
m_available_controllers.at(id)->set_led(red, green, blue);
}
void InputManager::enable_keyboard(const bool enabled) {
m_keyboard_enabled = enabled;
if (!m_keyboard_enabled) {
// Reset inputs as this device won't be able to be read from again!
clear_inputs();
}
}
void InputManager::update_mouse_options(const bool enabled,
const bool control_camera,
const bool control_movement) {
m_mouse_enabled = enabled;
if (!m_mouse_enabled) {
// Reset inputs as this device won't be able to be read from again!
clear_inputs();
}
// Switch relevant flags over related to the mouse
m_mouse.enable_camera_control(enabled && control_camera);
m_mouse.enable_movement_control(enabled && control_movement);
}
void InputManager::set_wait_for_bind(const InputDeviceType device_type,
const bool for_analog,
const bool for_minimum_analog,
const int input_idx) {
m_waiting_for_bind = InputBindAssignmentMeta();
m_waiting_for_bind->device_type = device_type;
m_waiting_for_bind->pad_idx = input_idx;
m_waiting_for_bind->for_analog = for_analog;
m_waiting_for_bind->for_analog_minimum = for_minimum_analog;
m_waiting_for_bind->keyboard_confirmation_binds =
m_settings->keyboard_binds.lookup_button_binds(PadData::CROSS);
if (g_game_version == GameVersion::Jak1) {
auto& circle_binds = m_waiting_for_bind->keyboard_confirmation_binds =
m_settings->keyboard_binds.lookup_button_binds(PadData::CIRCLE);
m_waiting_for_bind->keyboard_confirmation_binds.insert(
m_waiting_for_bind->keyboard_confirmation_binds.end(), circle_binds.begin(),
circle_binds.end());
}
}
void InputManager::set_camera_sens(const float xsens, const float ysens) {
m_mouse.set_camera_sens(xsens, ysens);
}
void InputManager::reset_input_bindings_to_defaults(const int port,
const InputDeviceType device_type) {
switch (device_type) {
case InputDeviceType::CONTROLLER:
if (m_controller_port_mapping.find(port) != m_controller_port_mapping.end() &&
m_controller_port_mapping.at(port) < m_available_controllers.size() &&
m_settings->controller_binds.find(
m_available_controllers.at(m_controller_port_mapping.at(port))->get_guid()) !=
m_settings->controller_binds.end()) {
m_settings->controller_binds
.at(m_available_controllers.at(m_controller_port_mapping.at(port))->get_guid())
.set_bindings(DEFAULT_CONTROLLER_BINDS);
}
break;
case InputDeviceType::KEYBOARD:
m_settings->keyboard_binds.set_bindings(DEFAULT_KEYBOARD_BINDS);
break;
case InputDeviceType::MOUSE:
m_settings->mouse_binds.set_bindings(DEFAULT_MOUSE_BINDS);
break;
}
}
void InputManager::set_auto_hide_mouse(const bool auto_hide_mouse) {
m_auto_hide_mouse = auto_hide_mouse;
if (!auto_hide_mouse) {
hide_cursor(false);
}
}
void InputManager::clear_inputs() {
// Reset inputs as this device won't be able to be read from again!
for (auto& [port, data] : m_data) {
data->clear();
}
}
+103
View File
@@ -0,0 +1,103 @@
#pragma once
#include <array>
#include <memory>
#include <optional>
#include <string>
#include <unordered_map>
#include "common/common_types.h"
#include "devices/game_controller.h"
#include "devices/keyboard.h"
#include "devices/mouse.h"
#include "game/settings/settings.h"
#include "game/system/hid/input_bindings.h"
#include "third-party/SDL/include/SDL.h"
/// Central class that:
/// - keeps track of available input devices
/// - polls data from the input devices considered active
/// - fetches said data to be sent to the game
class InputManager {
public:
InputManager();
~InputManager();
// Propagate and handle the SDL event, ignored it if it's not relevant
void process_sdl_event(const SDL_Event& event, const bool ignore_mouse, const bool ignore_kb);
// TODO - organize the functions here
void refresh_device_list();
void ignore_background_controller_events(const bool ignore);
std::optional<std::shared_ptr<PadData>> get_current_data(const int port) const;
int update_rumble(const int port, const u8 low_intensity, const u8 high_intensity);
std::pair<int, int> get_mouse_pos() const { return m_mouse.get_mouse_pos(); }
void register_command(const CommandBinding::Source source, const CommandBinding bind);
int get_num_controllers() const { return m_available_controllers.size(); }
std::string get_controller_name(const int controller_id);
std::string get_current_bind(const int port,
const InputDeviceType device_type,
const bool buttons,
const int input_idx,
const bool analog_for_minimum);
bool controller_has_led(const int port);
void set_controller_for_port(const int controller_id, const int port);
void set_controller_led(const int port, const u8 red, const u8 green, const u8 blue);
void enable_keyboard(const bool enabled);
void update_mouse_options(const bool enabled,
const bool control_camera,
const bool control_movement);
bool get_waiting_for_bind() const { return m_waiting_for_bind.has_value(); }
void set_wait_for_bind(const InputDeviceType device_type,
const bool for_analog,
const bool for_minimum_analog,
const int input_idx);
void stop_waiting_for_bind() { m_waiting_for_bind = std::nullopt; }
void set_camera_sens(const float xsens, const float ysens);
void reset_input_bindings_to_defaults(const int port, const InputDeviceType device_type);
void set_auto_hide_mouse(const bool auto_hide_mouse);
void clear_inputs();
private:
std::shared_ptr<game_settings::InputSettings> m_settings;
/// This data can be shared throughout the runtime, it is the current state of the
/// aggregate of all input sources for the given port
std::unordered_map<int, std::shared_ptr<PadData>> m_data;
/// A list of all the currently connected controllers that we can potentially read data
/// from if they are mapped to a given port
std::vector<std::shared_ptr<GameController>> m_available_controllers;
/// You can have many keyboards plugged into a computer, but we do not differentiate
/// between them it's all aggregated under one device.
KeyboardDevice m_keyboard;
/// You can have many mice plugged into a computer, but we do not differentiate between
/// them it's all aggregated under one device.
MouseDevice m_mouse;
/// A mapping between port numbers and the controller index. Connect as many controllers as
/// you want.
std::unordered_map<int, int> m_controller_port_mapping;
/// The port that the keyboard and mouse will be used for PadData
int m_keyboard_and_mouse_port = 0;
/// Collection of arbitrary commands to run on user actions
CommandBindingGroups m_command_binds;
bool m_ignored_device_last_frame = false;
bool m_keyboard_enabled = true;
bool m_mouse_enabled = false;
bool m_auto_hide_mouse = true;
bool m_mouse_currently_hidden = false;
void hide_cursor(const bool hide_cursor);
bool m_ignore_background_controller_events = false;
/// No inputs will be processed while in this mode the first input detected from the relevant
/// device type will be used to set the bind and clear the flag
///
/// The game will poll for the status of this flag to know if a bind has been assigned
std::optional<InputBindAssignmentMeta> m_waiting_for_bind = std::nullopt;
};
+99
View File
@@ -0,0 +1,99 @@
#include "sdl_util.h"
#include "common/log/log.h"
#include "third-party/SDL/include/SDL.h"
namespace sdl_util {
void log_error(const std::string& msg) {
std::string sdl_cause = SDL_GetError();
lg::error("SDL Error: {} - Cause: {}", msg, sdl_cause.empty() ? "n/a" : sdl_cause);
}
bool is_any_event_type(uint32_t event_type, const std::vector<uint32_t>& allowed_types) {
for (const auto& allowed_type : allowed_types) {
if (allowed_type == event_type) {
return true;
}
}
return false;
}
SDL_bool sdl_bool(const bool val) {
return val ? SDL_TRUE : SDL_FALSE;
}
bool from_sdl_bool(const SDL_bool val) {
return val == SDL_TRUE ? true : false;
}
std::vector<std::string> get_modifier_strings(InputModifiers modifiers) {
std::vector<std::string> result = {};
if (modifiers.need_ctrl) {
result.push_back("ctrl");
}
if (modifiers.need_shift) {
result.push_back("shift");
}
if (modifiers.need_alt) {
result.push_back("alt");
}
if (modifiers.need_meta) {
result.push_back("meta");
}
return result;
}
std::string get_mouse_button_name(const int sdl_mouse_button_id, InputModifiers modifiers) {
std::string result = "";
switch (sdl_mouse_button_id) {
case SDL_BUTTON_LEFT:
result = "LEFT MOUSE";
break;
case SDL_BUTTON_MIDDLE:
result = "MIDDLE MOUSE";
break;
case SDL_BUTTON_RIGHT:
result = "RIGHT MOUSE";
break;
case SDL_BUTTON_X1:
result = "MOUSE 4";
break;
case SDL_BUTTON_X2:
result = "MOUSE 5";
break;
default:
result = "";
}
if (result.empty()) {
return "unknown";
}
auto tokens = get_modifier_strings(modifiers);
tokens.push_back(result);
return fmt::to_string(fmt::join(tokens, " + "));
}
std::string get_keyboard_button_name(const int sdl_key_code, InputModifiers modifiers) {
const auto result = SDL_GetKeyName((SDL_KeyCode)sdl_key_code);
if (!result) {
return "Unknown";
}
auto tokens = get_modifier_strings(modifiers);
tokens.push_back(result);
return fmt::to_string(fmt::join(tokens, " + "));
}
std::string get_controller_button_name(const int sdl_button_id) {
const auto result = SDL_GameControllerGetStringForButton((SDL_GameControllerButton)sdl_button_id);
if (!result) {
return "Unknown";
}
return result;
}
std::string get_controller_axis_name(const int sdl_axis_id) {
const auto result = SDL_GameControllerGetStringForAxis((SDL_GameControllerAxis)sdl_axis_id);
if (!result) {
return "Unknown";
}
return result;
}
bool is_modifier_key(const SDL_Keycode key_code) {
return key_code == SDLK_LSHIFT || key_code == SDLK_RSHIFT || key_code == SDLK_LALT ||
key_code == SDLK_RALT || key_code == SDLK_LCTRL || key_code == SDLK_RCTRL ||
key_code == SDLK_LGUI || key_code == SDLK_RGUI;
}
} // namespace sdl_util
+22
View File
@@ -0,0 +1,22 @@
#pragma once
#include <string>
#include <vector>
#include "input_bindings.h"
#include "third-party/SDL/include/SDL.h"
namespace sdl_util {
void log_error(const std::string& msg = "");
bool is_any_event_type(uint32_t event_type, const std::vector<uint32_t>& allowed_types);
SDL_bool sdl_bool(const bool val);
bool from_sdl_bool(const SDL_bool val);
std::string get_mouse_button_name(const int sdl_mouse_button_id, InputModifiers modifiers);
std::string get_keyboard_button_name(const int sdl_key_code, InputModifiers modifiers);
std::string get_controller_button_name(const int sdl_button_id);
std::string get_controller_axis_name(const int sdl_axis_id);
bool is_modifier_key(const SDL_Keycode key_code);
} // namespace sdl_util
-569
View File
@@ -1,569 +0,0 @@
/*!
* @file newpad.cpp
* PC-port specific cpad implementation on the C kernel. Monitors button inputs.
* Actual input detection is done through window events and is gfx pipeline-dependent.
*/
#include "newpad.h"
#include <atomic>
#include <cmath>
#include "common/log/log.h"
#include "common/util/Assert.h"
#include "common/util/FileUtil.h"
#include "game/graphics/pipelines/opengl.h" // for GLFW macros
#include "third-party/imgui/imgui.h"
namespace Pad {
/*
********************************
* Key checking
********************************
*/
// key-down status of any detected key.
bool g_key_status[glfw::NUM_KEYS] = {0};
// key-down status of any detected key. this is buffered for the remainder of a frame.
bool g_buffered_key_status[glfw::NUM_KEYS] = {0};
float g_key_analogs[CONTROLLER_COUNT][(int)Analog::Max] = {{0}};
bool g_gamepad_buttons[CONTROLLER_COUNT][(int)Button::Max] = {{0}};
float g_gamepad_analogs[CONTROLLER_COUNT][(int)Analog::Max] = {{0}};
struct GamepadState {
int gamepad_idx[CONTROLLER_COUNT] = {-1, -1, -1, -1};
bool glfw_joystick_used[GLFW_JOYSTICK_LAST + 1] = {false};
} g_gamepads;
// input mode for controller mapping
InputModeStatus input_mode = InputModeStatus::Disabled;
int input_mode_pad = 0;
u64 input_mode_key = -1;
u64 input_mode_mod = 0;
u64 input_mode_index = 0;
MappingInfo g_input_mode_mapping;
void ClearKey(int key) {
if (key < 0 || key > glfw::NUM_KEYS) {
lg::warn("ClearKey failed: Attempted to clear invalid key {}", key);
return;
}
g_key_status[key] = false;
g_buffered_key_status[key] = false;
}
void ClearAnalogAxisValue(MappingInfo& mapping_info, int axis) {
for (int pad = 0; pad < CONTROLLER_COUNT; ++pad) {
for (int analog = 0; analog < (int)Analog::Max; ++analog) {
if (mapping_info.keyboard_analog_mapping[pad][analog].axis_id == axis &&
mapping_info.keyboard_analog_mapping[pad][analog].mode ==
AnalogMappingMode::AnalogInput) {
g_key_analogs[pad][analog] = 0.0f;
}
}
}
}
void ForceClearAnalogValue() {
for (int pad = 0; pad < CONTROLLER_COUNT; ++pad) {
for (int analog = 0; analog < (int)Analog::Max; ++analog) {
g_key_analogs[pad][analog] = 0.0f;
}
}
}
void ForceClearKeys() {
for (auto& key : g_key_status) {
key = false;
}
for (auto& key : g_buffered_key_status) {
key = false;
}
}
void ClearKeys() {
for (int key = 0; key < glfw::NUM_KEYS; key++) {
g_buffered_key_status[key] = g_key_status[key];
}
}
void OnKeyPress(int key) {
if (ImGui::IsAnyItemActive()) {
return;
}
if (input_mode == InputModeStatus::Enabled) {
if (key == GLFW_KEY_ESCAPE) {
ExitInputMode(true);
return;
}
input_mode_key = key;
MapButton(g_input_mode_mapping, (Button)(input_mode_index++), input_mode_pad, key);
if (input_mode_index >= (u64)Button::Max) {
ExitInputMode(false);
}
return;
}
// set absolute key status
ASSERT(key < glfw::NUM_KEYS);
g_key_status[key] = true;
// set buffered key status
g_buffered_key_status[key] = true;
}
void OnKeyRelease(int key) {
if (input_mode == InputModeStatus::Enabled) {
return;
}
ASSERT(key < glfw::NUM_KEYS);
g_key_status[key] = false;
}
/*
********************************
* Pad checking
********************************
*/
static int CheckPadIdx(int pad) {
if (pad < 0 || pad >= CONTROLLER_COUNT) {
lg::error("Invalid pad {}", pad);
return -1;
}
return pad;
}
// returns 1 if either keyboard or controller button is pressed. Controller button has priority.
// returns 0 if invalid or not pressed.
int IsPressed(MappingInfo& mapping, Button button, int pad = 0) {
if (CheckPadIdx(pad) == -1) {
return 0;
}
if (g_gamepad_buttons[pad][(int)button]) {
return 1;
}
int key = mapping.keyboard_button_mapping[pad][(int)button];
if (key == -1)
return 0;
auto& keymap = mapping.buffer_mode ? g_buffered_key_status : g_key_status;
ASSERT(key < glfw::NUM_KEYS);
return keymap[key];
}
void SetAnalogAxisValue(MappingInfo& mapping_info, int axis, double value) {
const double sensitivity_numerator = Gfx::g_global_settings.target_fps;
const double minimum_sensitivity = 1e-4;
for (int pad = 0; pad < CONTROLLER_COUNT; ++pad) {
for (int analog = 0; analog < (int)Analog::Max; ++analog) {
if (mapping_info.keyboard_analog_mapping[pad][analog].axis_id == axis) {
double newValue = value;
if (axis == GlfwKeyCustomAxis::CURSOR_X_AXIS) {
if (mapping_info.mouse_x_axis_sensitivities[pad] < minimum_sensitivity) {
mapping_info.mouse_x_axis_sensitivities[pad] = minimum_sensitivity;
}
newValue /= (sensitivity_numerator / mapping_info.mouse_x_axis_sensitivities[pad]);
} else if (axis == GlfwKeyCustomAxis::CURSOR_Y_AXIS) {
if (mapping_info.mouse_y_axis_sensitivities[pad] < minimum_sensitivity) {
mapping_info.mouse_y_axis_sensitivities[pad] = minimum_sensitivity;
}
newValue /= (sensitivity_numerator / mapping_info.mouse_y_axis_sensitivities[pad]);
}
if (newValue > 1.0) {
g_key_analogs[pad][analog] = 1.0;
} else if (newValue < -1.0) {
g_key_analogs[pad][analog] = -1.0;
} else if (std::isnan(newValue)) {
g_key_analogs[pad][analog] = 0.0;
} else {
g_key_analogs[pad][analog] = newValue;
}
// Invert logic used here. Left Y axis movement is based on towrds the camera.
// In game forward is treated as going away from the camera and backwards is headed towards
// the camera.
if (axis == GlfwKeyCustomAxis::CURSOR_Y_AXIS) {
g_key_analogs[pad][analog] *= -1;
}
}
}
}
}
void UpdateAxisValue(MappingInfo& mapping_info) {
for (int pad = 0; pad < CONTROLLER_COUNT; ++pad) {
for (int analog = 0; analog < (int)Analog::Max; ++analog) {
if (mapping_info.keyboard_analog_mapping[pad][analog].mode ==
AnalogMappingMode::AnalogInput) {
continue; // Assumed Set Axis set value already
}
// Invert logic used here. Left Y axis movement is based on towrds the camera.
// In game forward is treated as going away from the camera and backwards is headed towards
// the camera.
double input = 0.0f;
if (mapping_info.keyboard_analog_mapping[pad][analog].positive_key > -1 &&
mapping_info.keyboard_analog_mapping[pad][analog].positive_key < glfw::NUM_KEYS) {
if (analog == static_cast<int>(Analog::Left_Y)) {
input -=
g_buffered_key_status[mapping_info.keyboard_analog_mapping[pad][analog].positive_key];
} else {
input +=
g_buffered_key_status[mapping_info.keyboard_analog_mapping[pad][analog].positive_key];
}
}
if (mapping_info.keyboard_analog_mapping[pad][analog].negative_key > -1 &&
mapping_info.keyboard_analog_mapping[pad][analog].negative_key < glfw::NUM_KEYS) {
if (analog == static_cast<int>(Analog::Left_Y)) {
input +=
g_buffered_key_status[mapping_info.keyboard_analog_mapping[pad][analog].negative_key];
} else {
input -=
g_buffered_key_status[mapping_info.keyboard_analog_mapping[pad][analog].negative_key];
}
}
g_key_analogs[pad][analog] = input;
}
}
}
// returns the value of the analog axis (in the future, likely pressure sensitive if we support it?)
// if invalid or otherwise -- returns 127 (analog stick neutral position)
int GetAnalogValue(MappingInfo& /*mapping*/, Analog analog, int pad = 0) {
float input = 0.0f;
if (CheckPadIdx(pad) == -1) {
// Pad out of range, return a stable value
return 127;
}
float controller_input = 0.0f;
if (g_gamepads.gamepad_idx[pad] > -1) {
controller_input = g_gamepad_analogs[pad][(int)analog];
}
float keyboard_input = g_key_analogs[pad][(int)analog];
// Hack. Clearing the buffer immediately can lead to inconsistencies on analog input.
// If a mouse is disconnected or can't calculate a new delta it would stay stuck at 1.
// Decreasing the values gradually seems like a good comprise.
g_key_analogs[pad][(int)analog] *= 0.95;
if (fabs(controller_input) > fabs(keyboard_input)) {
input = controller_input;
} else {
input = keyboard_input;
}
// GLFW provides float in range -1 to 1, caller expects 0-255
const float input_low = -1.0f;
const float input_high = 1.0f;
const int output_low = 0;
const int output_high = 255;
// Map from input to output range
return int((input - input_low) * (output_high - output_low) / (input_high - input_low) +
output_low);
}
// map a button on a pad to a key
void MapButton(MappingInfo& mapping, Button button, int pad, int key) {
// check if pad is valid. dont map buttons with invalid pads.
if (CheckPadIdx(pad) == -1) {
return;
}
if (g_gamepads.gamepad_idx[pad] == -1) {
// TODO: Check if other pad is keyboard and if key is already bound
mapping.keyboard_button_mapping[pad][(int)button] = key;
} else {
mapping.controller_button_mapping[pad][(int)button] = key;
}
}
void MapAnalog(MappingInfo& mapping,
Analog button,
int pad,
AnalogMappingInfo& analog_mapping_info) {
// check if pad is valid. dont map buttons with invalid pads.
if (CheckPadIdx(pad) == -1) {
return;
}
if (g_gamepads.gamepad_idx[pad] == -1) {
// TODO: Check if other pad is keyboard and if key is already bound
mapping.keyboard_analog_mapping[pad][(int)button] = analog_mapping_info;
} else {
mapping.controller_analog_mapping[pad][(int)button] = analog_mapping_info;
}
}
// reset button mappings
void DefaultMapping(MappingInfo& mapping) {
// make every button invalid
for (int32_t pad = 0; pad < CONTROLLER_COUNT; ++pad) {
for (int32_t button = 0; button < (int)Pad::Button::Max; ++button) {
mapping.controller_button_mapping[pad][button] = -1;
mapping.keyboard_button_mapping[pad][button] = -1;
}
for (int32_t analog = 0; analog < (int)Pad::Analog::Max; ++analog) {
mapping.controller_analog_mapping[pad][analog] = AnalogMappingInfo();
mapping.keyboard_analog_mapping[pad][analog] = AnalogMappingInfo();
}
}
constexpr std::pair<Button, int> gamepad_map[] = {
{Button::Select, GLFW_GAMEPAD_BUTTON_BACK},
{Button::L3, GLFW_GAMEPAD_BUTTON_LEFT_THUMB},
{Button::R3, GLFW_GAMEPAD_BUTTON_RIGHT_THUMB},
{Button::Start, GLFW_GAMEPAD_BUTTON_START},
{Button::Up, GLFW_GAMEPAD_BUTTON_DPAD_UP},
{Button::Right, GLFW_GAMEPAD_BUTTON_DPAD_RIGHT},
{Button::Down, GLFW_GAMEPAD_BUTTON_DPAD_DOWN},
{Button::Left, GLFW_GAMEPAD_BUTTON_DPAD_LEFT},
{Button::L1, GLFW_GAMEPAD_BUTTON_LEFT_BUMPER},
{Button::R1, GLFW_GAMEPAD_BUTTON_RIGHT_BUMPER},
{Button::Triangle, GLFW_GAMEPAD_BUTTON_TRIANGLE},
{Button::Circle, GLFW_GAMEPAD_BUTTON_CIRCLE},
{Button::X, GLFW_GAMEPAD_BUTTON_CROSS},
{Button::Square, GLFW_GAMEPAD_BUTTON_SQUARE}};
for (int32_t pad = 0; pad < CONTROLLER_COUNT; ++pad) {
for (const auto& [button, value] : gamepad_map) {
mapping.controller_button_mapping[pad][(int)button] = value;
}
}
// TODO - these are different from the analog bindings above and cause
// the keyboard to be bound to controls regardless
//
// Need someway to toggle off -- where do we have access to the game's settings?
// TODO - What should the second pc default controls be?
// R1 / L1
MapButton(mapping, Button::L1, 0, GLFW_KEY_Q);
MapButton(mapping, Button::R1, 0, GLFW_KEY_O);
// R2 / L2
MapButton(mapping, Button::L2, 0, GLFW_KEY_1);
MapButton(mapping, Button::R2, 0, GLFW_KEY_P);
// face buttons
MapButton(mapping, Button::Ecks, 0, GLFW_KEY_SPACE);
MapButton(mapping, Button::Square, 0, GLFW_KEY_F);
MapButton(mapping, Button::Triangle, 0, GLFW_KEY_R);
MapButton(mapping, Button::Circle, 0, GLFW_KEY_E);
// dpad
MapButton(mapping, Button::Up, 0, GLFW_KEY_UP);
MapButton(mapping, Button::Right, 0, GLFW_KEY_RIGHT);
MapButton(mapping, Button::Down, 0, GLFW_KEY_DOWN);
MapButton(mapping, Button::Left, 0, GLFW_KEY_LEFT);
// start for progress
MapButton(mapping, Button::Start, 0, GLFW_KEY_ENTER);
// l3/r3 for menu
MapButton(mapping, Button::L3, 0, GLFW_KEY_COMMA);
MapButton(mapping, Button::R3, 0, GLFW_KEY_PERIOD);
AnalogMappingInfo analog_mapping_info;
analog_mapping_info.positive_key = GLFW_KEY_D;
analog_mapping_info.negative_key = GLFW_KEY_A;
MapAnalog(mapping, Analog::Left_X, 0, analog_mapping_info);
analog_mapping_info.positive_key = GLFW_KEY_W;
analog_mapping_info.negative_key = GLFW_KEY_S;
MapAnalog(mapping, Analog::Left_Y, 0, analog_mapping_info);
analog_mapping_info.positive_key = GLFW_KEY_L;
analog_mapping_info.negative_key = GLFW_KEY_J;
MapAnalog(mapping, Analog::Right_X, 0, analog_mapping_info);
analog_mapping_info.positive_key = GLFW_KEY_I;
analog_mapping_info.negative_key = GLFW_KEY_K;
MapAnalog(mapping, Analog::Right_Y, 0, analog_mapping_info);
const double default_mouse_x_sensitivity = 5.0f;
const double default_mouse_y_sensitivity = 2.0f;
for (int pad = 0; pad < CONTROLLER_COUNT; ++pad) {
mapping.mouse_x_axis_sensitivities[pad] = default_mouse_x_sensitivity;
mapping.mouse_y_axis_sensitivities[pad] = default_mouse_y_sensitivity;
}
}
void EnterInputMode() {
input_mode = InputModeStatus::Enabled;
input_mode_index = 0;
input_mode_pad = 0;
}
void ExitInputMode(bool canceled) {
input_mode = canceled ? InputModeStatus::Canceled : InputModeStatus::Disabled;
}
u64 input_mode_get() {
return (u64)input_mode;
}
u64 input_mode_get_key() {
return input_mode_key;
}
u64 input_mode_get_index() {
return input_mode_index;
}
void input_mode_pad_set(s64 idx) {
input_mode_pad = idx;
}
/*
********************************
* Gamepad Support
********************************
*/
void check_gamepads() {
auto check_pad = [](int pad) { // -> bool
if (g_gamepads.gamepad_idx[pad] == -1) {
for (int i = GLFW_JOYSTICK_1; i <= GLFW_JOYSTICK_LAST; i++) {
if (g_gamepads.glfw_joystick_used[i]) {
continue;
}
if (glfwJoystickPresent(i) && glfwJoystickIsGamepad(i)) {
g_gamepads.gamepad_idx[pad] = i;
g_gamepads.glfw_joystick_used[i] = true;
lg::info("Using joystick {} for pad {}: {}, {}", i, pad, glfwGetJoystickName(i),
glfwGetGamepadName(i));
break;
}
}
} else if (!glfwJoystickPresent(g_gamepads.gamepad_idx[pad])) {
lg::info("Pad {} / joystick {} has been disconnected", pad, g_gamepads.gamepad_idx[pad]);
g_gamepads.glfw_joystick_used[g_gamepads.gamepad_idx[pad]] = false;
g_gamepads.gamepad_idx[pad] = -1;
return false;
}
return true; // pad already exists or was created
};
for (int i = 0; i < CONTROLLER_COUNT; i++) {
check_pad(i);
}
}
void initialize() {
std::string mapping_path =
(file_util::get_jak_project_dir() / "game" / "assets" / "sdl_controller_db.txt").string();
glfwUpdateGamepadMappings(file_util::read_text_file(mapping_path).c_str());
check_gamepads();
if (g_gamepads.gamepad_idx[0] == -1) {
lg::info("No joysticks found.");
}
}
void clear_pad(int pad) {
for (int i = 0; i < (int)Button::Max; ++i) {
g_gamepad_buttons[pad][i] = false;
}
for (int i = 0; i < (int)Analog::Max; ++i) {
g_gamepad_analogs[pad][i] = 0;
}
}
void update_gamepads(MappingInfo& mapping_info) {
check_gamepads();
UpdateAxisValue(mapping_info);
if (g_gamepads.gamepad_idx[0] == -1) {
for (int pad = 0; pad < CONTROLLER_COUNT; ++pad) {
clear_pad(pad);
}
return;
}
constexpr std::pair<Analog, int> gamepad_analog_map[] = {
{Analog::Left_X, GLFW_GAMEPAD_AXIS_LEFT_X},
{Analog::Left_Y, GLFW_GAMEPAD_AXIS_LEFT_Y},
{Analog::Right_X, GLFW_GAMEPAD_AXIS_RIGHT_X},
{Analog::Right_Y, GLFW_GAMEPAD_AXIS_RIGHT_Y}};
auto read_pad_state = [gamepad_analog_map, mapping_info](int pad) {
GLFWgamepadstate state;
glfwGetGamepadState(g_gamepads.gamepad_idx[pad], &state);
for (int32_t button = 0; button < (int)Pad::Button::Max; ++button) {
int key = mapping_info.controller_button_mapping[pad][button];
g_gamepad_buttons[pad][(int)button] = state.buttons[key];
}
g_gamepad_buttons[pad][(int)Button::L2] = state.axes[GLFW_GAMEPAD_AXIS_LEFT_TRIGGER] > 0;
g_gamepad_buttons[pad][(int)Button::R2] = state.axes[GLFW_GAMEPAD_AXIS_RIGHT_TRIGGER] > 0;
for (const auto& [analog_vector, idx] : gamepad_analog_map) {
g_gamepad_analogs[pad][(int)analog_vector] = state.axes[idx];
}
};
for (int i = 0; i < CONTROLLER_COUNT; i++) {
if (g_gamepads.gamepad_idx[i] != -1)
read_pad_state(i);
else
clear_pad(i);
}
}
int rumble(int pad, float slow_motor, float fast_motor) {
if (g_gamepads.gamepad_idx[pad] != -1 &&
glfwSetJoystickRumble(g_gamepads.gamepad_idx[pad], slow_motor, fast_motor)) {
return 1;
}
return 0;
}
int GetGamepadState(int pad) {
return g_gamepads.gamepad_idx[pad];
}
// The following setters/getters are mainly used for unit tests
void SetGamepadState(int pad, int pad_index) {
if (CheckPadIdx(pad) != -1) {
if (pad_index <= GLFW_JOYSTICK_LAST) {
g_gamepads.gamepad_idx[pad] = pad_index;
}
}
}
bool* GetKeyboardInputBuffer() {
return g_key_status;
}
// key-down status of any detected key. this is buffered for the remainder of a frame.
bool* GetKeyboardBufferedInputBuffer() {
return g_buffered_key_status;
}
float* GetKeyboardInputAnalogBuffer(int pad) {
return g_key_analogs[pad];
}
bool* GetControllerInputBuffer(int pad) {
return g_gamepad_buttons[pad];
}
float* GetControllerAnalogInputBuffer(int pad) {
return g_gamepad_analogs[pad];
}
}; // namespace Pad
-133
View File
@@ -1,133 +0,0 @@
#pragma once
/*!
* @file newpad.h
* PC-port specific cpad implementation on the C kernel. Monitors button inputs.
* Actual input detection is done through window events and is gfx pipeline-dependent.
*/
/* NOTE ABOUT KEY VALUES!
* I am using the renderer-dependent key value macros here (at least for now). This means that the
* button mapping may be renderer-dependent. When changing renderers, make sure to backup the
* original button mapping or something so that the user can reset it afterwards. Eventually we
* should fix this (or maybe it's not even a problem).
*/
#include <unordered_map>
#include "common/common_types.h"
namespace Pad {
static constexpr int CONTROLLER_COUNT = 4; // support 4 controllers.
enum class Analog {
Left_X = 0,
Left_Y,
Right_X,
Right_Y,
Max
};
// mirrors goal enum pad-buttons. used as indices to an array!
enum class Button {
Select = 0,
L3 = 1,
R3 = 2,
Start = 3,
Up = 4,
Right = 5,
Down = 6,
Left = 7,
L2 = 8,
R2 = 9,
L1 = 10,
R1 = 11,
Triangle = 12,
Circle = 13,
X = 14,
Square = 15,
Max = 16,
// aliases
Ecks = X,
Cross = X,
O = Circle
};
enum class AnalogMappingMode { DigitalInput = 0, AnalogInput = 1 };
// AnalogMappingInfo allows either button or axes to control analog input(s).
// * In Digital Input Mode, uses both positive_key and negative_key as button indices.
// * In Analog Input mode, only the positive_key is used.
// - The positive_key in Analog Input mode represents an analog axis (i.e
// GLFW_GAMEPAD_AXIS_RIGHT_Y)
struct AnalogMappingInfo {
AnalogMappingMode mode = AnalogMappingMode::DigitalInput;
int axis_id = -1;
int positive_key = -1;
int negative_key = -1;
};
struct MappingInfo {
bool debug = true; // debug mode
bool use_mouse = false;
bool buffer_mode = true; // use buffered inputs
int controller_button_mapping[CONTROLLER_COUNT][(int)Pad::Button::Max];
AnalogMappingInfo controller_analog_mapping[CONTROLLER_COUNT][(int)Pad::Analog::Max];
int keyboard_button_mapping[CONTROLLER_COUNT][(
int)Pad::Button::Max]; // Back up in case controller gets disconnected
AnalogMappingInfo keyboard_analog_mapping[CONTROLLER_COUNT][(int)Pad::Analog::Max];
double mouse_x_axis_sensitivities[CONTROLLER_COUNT];
double mouse_y_axis_sensitivities[CONTROLLER_COUNT];
// TODO complex button mapping & key macros (e.g. shift+x for l2+r2 press etc.)
};
void OnKeyPress(int key);
void OnKeyRelease(int key);
void ClearKey(int key);
void ForceClearKeys();
void ClearKeys();
void DefaultMapping(MappingInfo& mapping);
int IsPressed(MappingInfo& mapping, Button button, int pad);
int GetAnalogValue(MappingInfo& mapping, Analog analog, int pad);
void MapButton(MappingInfo& mapping, Button button, int pad, int key);
void MapAnalog(MappingInfo& mapping, Analog button, int pad, AnalogMappingInfo& analogMapping);
void SetAnalogAxisValue(MappingInfo& mapping, int axis, double value);
void ClearAnalogAxisValue(MappingInfo& mapping, int axis);
// this enum is also in pc-pad-utils.gc
enum class InputModeStatus { Disabled, Enabled, Canceled };
extern MappingInfo g_input_mode_mapping;
void EnterInputMode();
void ExitInputMode(bool);
u64 input_mode_get();
u64 input_mode_get_key();
u64 input_mode_get_index();
void input_mode_pad_set(s64);
void initialize();
void update_gamepads(MappingInfo& mapping_info);
int rumble(int pad, float slow_motor, float fast_motor);
int GetGamepadState(int pad);
void ForceClearAnalogValue();
void clear_pad(int pad);
void UpdateAxisValue(MappingInfo& mapping_info);
void SetGamepadState(int pad, int pad_index);
bool* GetKeyboardInputBuffer();
bool* GetKeyboardBufferedInputBuffer();
float* GetKeyboardInputAnalogBuffer(int pad);
bool* GetControllerInputBuffer(int pad);
float* GetControllerAnalogInputBuffer(int pad);
} // namespace Pad
+14 -7
View File
@@ -6,8 +6,17 @@
#include "third-party/imgui/imgui.h"
#include "third-party/imgui/imgui_stdlib.h"
void to_json(json& j, const DebugTextFilter& obj) {
j = json{{"content", obj.content}, {"type", obj.type}};
}
void from_json(const json& j, DebugTextFilter& obj) {
j.at("content").get_to(obj.content);
j.at("type").get_to(obj.type);
}
// TODO:
// - persist filters (need ability to remove option too)
// - ability to remove individual filter
FiltersMenu::FiltersMenu() {}
@@ -16,7 +25,7 @@ void FiltersMenu::draw_window() {
ImGui::SetNextItemOpen(true);
if (ImGui::TreeNode("Debug Text Filters")) {
auto& current_filters = Gfx::g_debug_settings.debug_text_filters;
auto& current_filters = Gfx::g_debug_settings.text_filters;
// Iterate and display all current debug text filters
for (size_t i = 0; i < current_filters.size(); i++) {
std::string label = "contains?";
@@ -55,12 +64,10 @@ void FiltersMenu::draw_window() {
current_filters.push_back(new_filter);
}*/
if (ImGui::Checkbox("Enable Distance Check", &Gfx::g_debug_settings.debug_text_check_range)) {
Gfx::g_debug_settings.save_settings();
if (ImGui::Checkbox("Enable Distance Check", &Gfx::g_debug_settings.text_check_range)) {
}
if (Gfx::g_debug_settings.debug_text_check_range) {
if (ImGui::SliderFloat("Max Range", &Gfx::g_debug_settings.debug_text_max_range, 0, 250)) {
Gfx::g_debug_settings.save_settings();
if (Gfx::g_debug_settings.text_check_range) {
if (ImGui::SliderFloat("Max Range", &Gfx::g_debug_settings.text_max_range, 0, 250)) {
}
}
+5
View File
@@ -3,6 +3,8 @@
#include <optional>
#include <string>
#include "common/util/json_util.h"
#include "third-party/imgui/imgui.h"
struct DebugTextFilter {
@@ -12,6 +14,9 @@ struct DebugTextFilter {
Type type;
};
void to_json(json& j, const DebugTextFilter& obj);
void from_json(const json& j, DebugTextFilter& obj);
class FiltersMenu {
public:
FiltersMenu();
+2
View File
@@ -547,6 +547,7 @@
(set! (-> obj health-pickup-time) (-> *display* base-frame-counter))
;; increase the health!
(seek! (-> obj health) (-> obj health-max) amount)
(with-pc (set-reactive-controller-led! (-> obj health) (-> obj eco-level) (the-as int (-> obj eco-type))))
)
(else
;; negative health. Subtract.
@@ -770,6 +771,7 @@
)
)
)
(with-pc (set-reactive-controller-led! (-> obj health) (-> obj eco-level) (the-as int (-> obj eco-type))))
(-> obj eco-level)
)
(else
+1
View File
@@ -599,6 +599,7 @@
)
(set! (-> self fact-info-target eco-level) 0.0)
(set! (-> self fact-info-target eco-timeout) 0)
(with-pc (set-reactive-controller-led! (-> self fact-info-target health) (-> self fact-info-target eco-level) (the-as int (-> self fact-info-target eco-type))))
(logclear! (-> self state-flags) (state-flags invuln-powerup))
(send-event self 'reset-collide)
(stop! (-> self sound))
@@ -60,6 +60,7 @@
(none)
)
:code (behavior ((arg0 continue-point))
(with-pc (set-reactive-controller-led! (-> self fact-info-target health) (-> self fact-info-target eco-level) (the-as int (-> self fact-info-target eco-type))))
(set! (-> self state-time) (-> *display* base-frame-counter))
(if (-> *art-control* reserve-buffer)
(reserve-free *art-control* (-> *art-control* reserve-buffer heap))
@@ -965,6 +966,7 @@
(target-hit-setup-anim gp-0)
(target-hit-move gp-0 (target-hit-orient gp-0 s5-0) target-falling-anim-trans (the-as float 1.0))
)
(with-pc (set-reactive-controller-led! (-> self fact-info-target health) (-> self fact-info-target eco-level) (the-as int (-> self fact-info-target eco-type))))
(if (and (= (-> self game mode) 'play) (>= 0.0 (-> self fact-info-target health)))
(go target-death (-> gp-0 mode))
)
+33 -13
View File
@@ -103,6 +103,16 @@
(quit 34)
;; extra screens for pc port
;; input options
(input-options)
(select-controller)
(controller-binds) ;; 0x25
(keyboard-binds)
(mouse-binds)
(controller-options)
(mouse-options)
(reassign-binds-options)
(camera-options)
(accessibility-options)
(game-ps2-options)
@@ -161,9 +171,13 @@
(button-flava)
(cheat-toggle)
(monitor)
(controller)
(binding-assignment)
(confirmation)
)
)
)
) ;; end of PC_PORT = #t branch for enum initialization
) ;; end of PC_PORT cond
(defenum game-option-menu
:type int32
@@ -222,20 +236,26 @@
:flag-assert #x900000034
)
(deftype game-option (basic)
((option-type game-option-type :offset-assert 8)
(name text-id :offset-assert 16)
(scale symbol :offset-assert 20)
(param1 float :offset-assert 24)
(param2 float :offset-assert 28)
(param3 game-option-menu :offset-assert 32)
(value-to-modify pointer :offset-assert 36)
(option-disabled-func (function symbol) :offset-assert 40) ;; added in pc port
((option-type game-option-type :offset-assert 8)
(name text-id :offset-assert 16)
(scale symbol :offset-assert 20)
(param1 float :offset-assert 24)
(param2 float :offset-assert 28)
(param3 game-option-menu :offset-assert 32)
(value-to-modify pointer :offset-assert 36)
;; fields below added in pc port
(option-disabled-func (function symbol) :offset-assert 40)
(name-override string :offset-assert 44)
(on-change (function object none) :offset-assert 48)
(on-confirm (function none) :offset-assert 52)
(slider-step-size float :offset-assert 56)
(slider-show-decimal? symbol :offset-assert 60)
(bind-info bind-assignment-info :inline :offset-assert 64)
)
:method-count-assert 9
:size-assert #x2C
:flag-assert #x90000002C
:size-assert #x54
:flag-assert #x900000054
)
@@ -151,7 +151,8 @@
)
;; maps options to a progress screen
(define *options-remap* (new 'static 'boxed-array :type (array game-option) :length 0 :allocated-length (#if (not PC_PORT) 35 60)))
;; the length matches with `(progress-screen max)`
(define *options-remap* (new 'static 'boxed-array :type (array game-option) :length 0 :allocated-length (#if (not PC_PORT) 35 68)))
;; added in PC port
(defenum level-task-data-index
@@ -179,7 +180,7 @@
(define *level-task-data-remap*
(new 'static 'boxed-array :type int32
(level-task-data-index training)
(level-task-data-index village1)
(level-task-data-index village1)
(level-task-data-index beach)
(level-task-data-index jungle) ;; jungle?
(level-task-data-index jungle) ;; jungleb?
@@ -848,6 +848,14 @@
(= v1-2 (progress-screen speedrun-options))
(= v1-2 (progress-screen speedrun-il-options))
(= v1-2 (progress-screen speedrun-cat-ext-options))
(= v1-2 (progress-screen input-options))
(= v1-2 (progress-screen select-controller))
(= v1-2 (progress-screen controller-binds))
(= v1-2 (progress-screen keyboard-binds))
(= v1-2 (progress-screen mouse-binds))
(= v1-2 (progress-screen controller-options))
(= v1-2 (progress-screen mouse-options))
(= v1-2 (progress-screen reassign-binds-options))
)
)
)
+24 -1
View File
@@ -799,7 +799,30 @@
(speedrun-hub2-100 #x1522)
(speedrun-hub3-100 #x1523)
(speedrun-all-cutscenes #x1524)
;; input options
(input-options #x1600)
(input-opts-select-controller #x1601)
(input-opts-analog-deadzone #x1602)
(input-opts-ignore-controller-win-focus #x1603)
(input-opts-controller-led-reflect-hp #x1604)
(input-opts-controller-led-reflect-eco #x1605)
(input-opts-mouse-enable-camera #x1606)
(input-opts-mouse-horizontal-sens #x1607)
(input-opts-mouse-vertical-sens #x1608)
(input-opts-mouse-enable-movement #x1609)
(input-opts-binds-controller #x160a)
(input-opts-binds-keyboard #x160b)
(input-opts-binds-mouse #x160c)
(input-opts-controller-opts #x160d)
(input-opts-enable-kb #x160e)
(input-opts-enable-mouse #x160f)
(input-opts-mouse-opts #x1610)
(input-opts-reassign-binds #x1611)
(input-opts-generic-controller #x1612)
(input-opts-auto-hide-cursor #x1613)
(input-opts-binds-unset #x1614)
(input-opts-binds-unknown #x1615)
(progress-no-other-resolution-options #x1616)
;; GAME-TEXT-ID ENUM ENDS
)
+37 -14
View File
@@ -322,6 +322,7 @@
(defenum pc-gfx-hack
(no-tex 0))
;; TODO - make a common kernel-defs
(define-extern __read-ee-timer (function uint))
(define-extern __mem-move (function pointer pointer uint none))
(define-extern __send-gfx-dma-chain (function object object none))
@@ -329,23 +330,45 @@
(define-extern __pc-texture-relocate (function object object object none))
(define-extern __pc-get-mips2c (function string function))
(define-extern __pc-set-levels (function string string none))
(define-extern pc-pad-input-mode-set (function symbol none))
(define-extern pc-pad-input-pad-set (function int none))
(define-extern pc-pad-input-mode-get (function int))
(define-extern pc-pad-input-key-get (function int))
(define-extern pc-pad-input-index-get (function int))
(define-extern pc-pad-input-map-save! (function none))
(define-extern pc-pad-get-mapped-button (function int int int))
(define-extern pc-get-fullscreen (function symbol))
(define-extern pc-get-screen-size (function int (pointer int32) (pointer int32) none))
(define-extern pc-get-screen-rate (function int int))
(define-extern pc-get-screen-vmode-count (function int))
(define-extern pc-get-monitor-count (function int))
;; Input Related Functions
(define-extern pc-get-controller-count (function int))
(define-extern pc-get-controller-name (function int string))
(deftype bind-assignment-info (structure)
((port int32)
(device-type int32)
(for-buttons? symbol)
(input-idx int32)
(analog-min-range? symbol)))
(define-extern pc-get-current-bind (function bind-assignment-info string))
(define-extern pc-waiting-for-bind? (function symbol))
(define-extern pc-set-waiting-for-bind! (function int symbol symbol int none))
(define-extern pc-stop-waiting-for-bind! (function none))
(define-extern pc-set-controller! (function int int none))
(define-extern pc-set-keyboard-enabled! (function symbol none))
(define-extern pc-set-mouse-options! (function symbol symbol symbol none))
(define-extern pc-set-mouse-camera-sens! (function float float none))
(define-extern pc-current-controller-has-led? (function symbol))
(define-extern pc-set-controller-led! (function int int int int none))
(define-extern pc-ignore-background-controller-events! (function symbol none))
(define-extern pc-reset-bindings-to-defaults! (function int int none))
(define-extern pc-set-auto-hide-cursor! (function symbol none))
;; Display Related Functions
(define-extern pc-get-display-mode (function symbol))
(define-extern pc-get-active-display-size (function (pointer int32) (pointer int32) none))
(define-extern pc-get-active-display-refresh-rate (function int))
(define-extern pc-get-display-count (function int))
(define-extern pc-get-display-name (function int string))
(define-extern pc-get-os (function symbol))
(define-extern pc-get-window-size (function (pointer int32) (pointer int32) none))
(define-extern pc-get-window-scale (function (pointer float) (pointer float) none))
(define-extern pc-set-window-size (function int int none))
(define-extern pc-set-fullscreen (function symbol int none))
(define-extern pc-set-fullscreen-display (function int none))
(define-extern pc-set-display-mode (function symbol none))
(define-extern pc-get-num-resolutions (function int))
(define-extern pc-get-resolution (function int (pointer int64) (pointer int64) none))
(define-extern pc-set-frame-rate (function int none))
(define-extern pc-set-vsync (function symbol none))
(define-extern pc-renderer-tree-set-lod (function pc-renderer-tree-type int none))
@@ -365,8 +388,8 @@
(define-extern pc-set-game-resolution (function int int none))
(define-extern pc-set-msaa (function int none))
(define-extern pc-set-gfx-hack (function pc-gfx-hack symbol none))
(define-extern pc-get-unix-timestamp (function int))
(define-extern pc-filter-debug-string? (function string float symbol))
(defenum pc-prof-event
(begin 0)
+44 -1
View File
@@ -300,6 +300,48 @@
;; String utilities
;;;;;;;;;;;;;;;;;;;;;;;;;
(defun string-upcase ((in string) (out string))
"Uppercase the given string."
(let* ((in-ptr (-> in data))
(in-data (-> in-ptr 0))
(in-idx 1)
(out-idx 0)
)
(while (nonzero? in-data)
(if (and (>= in-data (the-as uint 97)) (>= (the-as uint 122) in-data))
(+! in-data -32)
)
(set! (-> out data out-idx) in-data)
(set! in-data (-> in-ptr in-idx))
(+! in-idx 1)
(+! out-idx 1)
)
(set! (-> out data out-idx) (the-as uint 0))
)
out
)
(defun string-downcase ((in string) (out string))
"Lowercase the given string."
(let* ((in-ptr (-> in data))
(in-data (-> in-ptr 0))
(in-idx 1)
(out-idx 0)
)
(while (nonzero? in-data)
(if (and (>= in-data (the-as uint 97)) (>= (the-as uint 122) in-data))
(+! in-data 32)
)
(set! (-> out data out-idx) in-data)
(set! in-data (-> in-ptr in-idx))
(+! in-idx 1)
(+! out-idx 1)
)
(set! (-> out data out-idx) (the-as uint 0))
)
out
)
(defun charp-basename ((charp (pointer uint8)))
"Like basename in C"
(let ((ptr charp))
@@ -755,8 +797,9 @@
(define *stdcon1* (new 'global 'string 16384 (the string #f)))
(define *stdcon* *stdcon0*)
;; shared temporary string.
;; shared temporary strings.
(define *temp-string* (new 'global 'string 256 (the string #f)))
(define *temp-string2* (new 'global 'string 256 (the string #f)))
(defmacro string-format (&rest args)
"Formats into *temp-string* and returns it, for in-place string formating.
+78 -41
View File
@@ -53,11 +53,16 @@
(defmethod set-display-mode! pc-settings ((obj pc-settings) (mode symbol))
"sets the game's display mode"
;; change the display mode.
(set! (-> obj display-mode) mode)
;; if windowed mode, set the size properly
(when (= (-> obj display-mode) 'windowed)
(pc-set-window-size (max PC_MIN_WIDTH (-> obj win-width)) (max PC_MIN_HEIGHT (-> obj win-height))))
;; no-op if the display mode hasn't actually changed
(when (!= mode (-> obj display-mode))
;; change the display mode.
(set! (-> obj display-mode) mode)
;; set fullscreen to what we want
(pc-set-display-mode (-> obj display-mode))
;; if windowed mode, set the size properly
(when (= (-> obj display-mode) 'windowed)
;; TODO - this means the user can never have a window smaller than MIN_WIDTH/HEIGHT
(pc-set-window-size (max PC_MIN_WIDTH (-> obj win-width)) (max PC_MIN_HEIGHT (-> obj win-height)))))
0)
(defmethod set-size! pc-settings ((obj pc-settings) (width int) (height int))
@@ -94,16 +99,11 @@
(set! (-> obj aspect-ratio-reciprocal) (/ ASPECT_4X3 aspect))
(none))
(defmethod set-window-lock! pc-settings ((obj pc-settings) (lock symbol))
"set the aspect ratio used for rendering."
(pc-set-window-lock lock)
(set! (-> obj window-lock?) lock))
(defmethod set-frame-rate! pc-settings ((obj pc-settings) (rate int))
"set the target framerate."
(pc-set-frame-rate rate)
(if (and (!= 'fullscreen (-> obj display-mode))
(!= (pc-get-screen-rate -1) rate))
(!= (pc-get-active-display-refresh-rate) rate))
(set! (-> obj vsync?) #f))
(case rate
((50)
@@ -124,13 +124,19 @@
(defmethod set-monitor! pc-settings ((obj pc-settings) (monitor int))
"set the monitor to use when in fullscreen/borderless"
(set! (-> obj monitor) monitor)
;; if monitor selection is out of bounds (e.g. if a monitor got disconnected),
;; then default to the primary monitor
(cond
((>= (-> obj monitor) (pc-get-display-count))
(format 0 "Monitor selection out of bounds, defaulting to primary monitor.~%")
(set! (-> obj monitor) 0))
(else
(set! (-> obj monitor) monitor)))
(pc-set-fullscreen-display (-> obj monitor))
(none))
(defmethod commit-to-file pc-settings ((obj pc-settings))
"commits the current settings to the file"
;; auto load settings if available
(format (clear *pc-temp-string-1*) "~S/pc-settings.gc" *pc-settings-folder*)
(pc-mkdir-file-path *pc-temp-string-1*)
(write-to-file obj *pc-temp-string-1*)
@@ -183,6 +189,7 @@
(defmethod update-to-os pc-settings ((obj pc-settings))
"Update settings from GOAL to the C kernel."
;; TODO - move the below out of this function that runs every frame
(cond
((-> obj letterbox?)
(pc-set-letterbox (-> obj lbox-width) (-> obj lbox-height))
@@ -192,41 +199,30 @@
)
)
;; if monitor selection is out of bounds (e.g. if a monitor got disconnected),
;; then default to the primary monitor
(when (>= (-> obj monitor) (pc-get-monitor-count))
(format 0 "Monitor selection out of bounds, defaulting to primary monitor.~%")
(set! (-> obj monitor) 0)
)
(pc-set-vsync (-> obj vsync?))
(when (!= 'fullscreen (-> obj display-mode))
(if (< (pc-get-active-display-refresh-rate) (-> obj target-fps))
(pc-set-vsync #f))
(pc-set-frame-rate (-> obj target-fps)))
;; set fullscreen to what we want
(pc-set-fullscreen (-> obj display-mode) (-> obj monitor))
;; set window size and fps automatically if the window lock is enabled.
(when (-> obj window-lock?)
(pc-set-vsync (-> obj vsync?))
(when (!= 'fullscreen (-> obj display-mode))
(if (< (pc-get-screen-rate -1) (-> obj target-fps))
(pc-set-vsync #f))
(pc-set-frame-rate (-> obj target-fps)))
)
;; do game resolution
(if (= (-> obj display-mode) 'windowed)
(pc-set-game-resolution (-> obj real-width) (-> obj real-height))
(pc-set-game-resolution (-> obj width) (-> obj height)))
;; set msaa sample rate. if invalid, just reset to 4.
;; set msaa sample rate. if invalid, just reset to 2.
(let ((valid? #f))
(dotimes (i 31)
(if (= (-> obj gfx-msaa) (ash 1 i))
(true! valid?))
)
(if (not valid?) (set! (-> obj gfx-msaa) 4))
(if (not valid?) (set! (-> obj gfx-msaa) 2))
(pc-set-msaa (-> obj gfx-msaa))
)
;; -- end TODO
(pc-discord-rpc-set (if (-> obj discord-rpc?) 1 0))
(when #t ;; (not (-> obj ps2-lod-dist?))
@@ -417,6 +413,24 @@
0)
(defmethod set-ignore-controller-in-bg! pc-settings ((obj pc-settings) (val symbol))
"sets whether or not to ignore controller inputs if the window is in the background"
(set! (-> obj ignore-controller-win-unfocused?) val)
(pc-ignore-background-controller-events! val)
(none))
(defmethod set-enable-keyboard! pc-settings ((obj pc-settings) (val symbol))
"sets whether to ignore keyboard input events"
(set! (-> obj keyboard-enabled?) val)
(pc-set-keyboard-enabled! val)
(none))
(defmethod update-mouse-controls! pc-settings ((obj pc-settings))
"Uses whatever is set on the [[pc-settings]] to update the runtime on how it should interpret mouse events"
(pc-set-mouse-options! (-> obj mouse-enabled?) (-> obj mouse-camera?) (-> obj mouse-movement?))
(pc-set-mouse-camera-sens! (-> obj mouse-xsens) (-> obj mouse-ysens))
(pc-set-auto-hide-cursor! (-> obj auto-hide-cursor?))
(none))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@@ -532,6 +546,8 @@
(file-stream-seek-past-whitespace ,fs)
(when (!= #x29 (file-stream-getc ,fs))
;; NOTE - if you have a setting in the file that isn't handled (even if its valid lisp)
;; this error will be thrown.
(pc-settings-read-throw-error ,fs "invalid char, ) not found")
)
)
@@ -678,7 +694,19 @@
(("game-language") (set-game-language! obj (the-as language-enum (file-stream-read-int file))))
(("text-language") (set! (-> obj text-language) (the-as pc-subtitle-lang (file-stream-read-int file))))
(("subtitle-speaker") (set! (-> obj subtitle-speaker?) (file-stream-read-symbol file)))
(("ignore-controller-win-unfocused?") (set-ignore-controller-in-bg! obj (file-stream-read-symbol file)))
(("controller-hp-led?") (set! (-> obj controller-hp-led?) (file-stream-read-symbol file)))
(("controller-eco-led?") (set! (-> obj controller-eco-led?) (file-stream-read-symbol file)))
(("stick-deadzone") (set! (-> obj stick-deadzone) (file-stream-read-float file)))
(("keyboard-enabled?") (set-enable-keyboard! obj (file-stream-read-symbol file)))
(("mouse-enabled?") (set! (-> obj mouse-enabled?) (file-stream-read-symbol file)))
(("mouse-camera?") (set! (-> obj mouse-camera?) (file-stream-read-symbol file)))
(("mouse-xsens") (set! (-> obj mouse-xsens) (file-stream-read-float file)))
(("mouse-ysens") (set! (-> obj mouse-ysens) (file-stream-read-float file)))
(("mouse-movement?") (set! (-> obj mouse-movement?) (file-stream-read-symbol file)))
(("auto-hide-cursor?") (set! (-> obj auto-hide-cursor?) (file-stream-read-symbol file)))
(("ps2-read-speed?") (set! (-> obj ps2-read-speed?) (file-stream-read-symbol file)))
(("ps2-parts?") (set! (-> obj ps2-parts?) (file-stream-read-symbol file)))
(("ps2-music?") (set! (-> obj ps2-music?) (file-stream-read-symbol file)))
@@ -746,7 +774,7 @@
(defmethod handle-output-settings pc-settings ((obj pc-settings) (file file-stream))
"handle the text writing output for the 'settings' group"
(format file " (fps ~D)~%" (-> obj target-fps))
(format file " (msaa ~D)~%" (-> obj gfx-msaa))
(format file " (aspect-state ~A ~D ~D ~A)~%" (get-game-setting obj 'aspect-ratio)
@@ -775,7 +803,17 @@
(format file " (lod-force-ocean ~D)~%" (-> obj lod-force-ocean))
(format file " (lod-force-actor ~D)~%" (-> obj lod-force-actor))
(format file " (ignore-controller-win-unfocused? ~A)~%" (-> obj ignore-controller-win-unfocused?))
(format file " (controller-hp-led? ~A)~%" (-> obj controller-hp-led?))
(format file " (controller-eco-led? ~A)~%" (-> obj controller-eco-led?))
(format file " (stick-deadzone ~f)~%" (-> obj stick-deadzone))
(format file " (keyboard-enabled? ~A)~%" (-> obj keyboard-enabled?))
(format file " (mouse-enabled? ~A)~%" (-> obj mouse-enabled?))
(format file " (mouse-camera? ~A)~%" (-> obj mouse-camera?))
(format file " (mouse-xsens ~f)~%" (-> obj mouse-xsens))
(format file " (mouse-ysens ~f)~%" (-> obj mouse-ysens))
(format file " (mouse-movement? ~A)~%" (-> obj mouse-movement?))
(format file " (auto-hide-cursor? ~A)~%" (-> obj auto-hide-cursor?))
(format file " (ps2-read-speed? ~A)~%" (-> obj ps2-read-speed?))
(format file " (ps2-parts? ~A)~%" (-> obj ps2-parts?))
@@ -860,7 +898,6 @@
(defmethod load-settings pc-settings ((obj pc-settings))
"load"
(format (clear *pc-temp-string-1*) "~S/pc-settings.gc" *pc-settings-folder*)
(if (pc-filepath-exists? *pc-temp-string-1*)
(begin
@@ -874,12 +911,12 @@
(defmethod new pc-settings ((allocation symbol) (type-to-make type))
"make a new pc-settings"
(let ((obj (object-new allocation type-to-make (the-as int (-> type-to-make size)))))
(reset obj)
;; auto load settings if available
;; if saved settings are corrupted or not found, use defaults
;; load defaults not covered by the file
(set-defaults! obj)
;; if saved settings are corrupted or not found, the object will be fully reset to use all defaults
(load-settings obj)
;; any post-operations that need to be done after loading
(update-mouse-controls! obj)
obj))
+94 -33
View File
@@ -190,24 +190,6 @@
)
;; generic device information: name and some ID
(deftype pc-device-info (structure)
((id int)
(name pc-cstring-64 :inline)
)
)
;; input device information. comes with button mappings!
(deftype pc-pad-info (structure)
((device-info pc-device-info :inline)
(buffered? symbol)
(valid? symbol)
(mapping int 16)
)
)
;; All of the configuration for the PC port in GOAL. Access things from here!
;; Includes some methods to change parameters.
(deftype pc-settings (basic)
@@ -239,7 +221,6 @@
(letterbox? symbol) ;; letterbox. #f = stretched
(vsync? symbol) ;; vsync.
(font-scale float) ;; font scaling.
(window-lock? symbol) ;; whether the window can be resized by the user or not.
(monitor int32) ;; index of which monitor to use when fullscreen or borderless
;; debug settings
@@ -249,12 +230,17 @@
(movie? symbol)
;; device settings
;(device-audio pc-device-info :inline) ;; used audio device
;(device-screen pc-device-info :inline) ;; used display device
;(device-gpu pc-device-info :inline) ;; used graphics device
;(device-pad pc-pad-info 4 :inline) ;; used input devices, like controllers.
;(device-keyboard pc-pad-info :inline) ;; keyboard input information. if nothing else, this must be usable.
(ignore-controller-win-unfocused? symbol)
(controller-hp-led? symbol)
(controller-eco-led? symbol)
(stick-deadzone float) ;; analog stick deadzone. 0-1
(keyboard-enabled? symbol)
(mouse-enabled? symbol)
(mouse-camera? symbol)
(mouse-xsens float)
(mouse-ysens float)
(mouse-movement? symbol)
(auto-hide-cursor? symbol)
;; audio settings
(audio-latency-ms int16) ;; audio latency in milliseconds
@@ -317,6 +303,10 @@
(:methods
(new (symbol type) _type_)
(set-defaults! (_type_) none)
(set-defaults-input! (_type_) none)
(set-defaults-controller! (_type_) none)
(set-defaults-mouse! (_type_) none)
(update (_type_) none)
(update-from-os (_type_) none)
(update-to-os (_type_) none)
@@ -325,7 +315,7 @@
(update-video-hacks (_type_) object)
(reset (_type_) none)
(reset-audio (_type_) none)
(reset-input (_type_) none)
(reset-input (_type_ symbol) none)
(reset-gfx (_type_) none)
(reset-ps2 (_type_) none)
(reset-misc (_type_) none)
@@ -338,7 +328,6 @@
(set-size! (_type_ int int) none)
(set-aspect! (_type_ int int) none)
(set-aspect-ratio! (_type_ float) none)
(set-window-lock! (_type_ symbol) symbol)
(set-frame-rate! (_type_ int) int)
(set-monitor! (_type_ int) none)
(set-game-setting! (_type_ symbol symbol) object)
@@ -356,6 +345,9 @@
(add-to-music-log (_type_ symbol int) int)
(commit-to-file (_type_) none)
(load-settings (_type_) int)
(set-ignore-controller-in-bg! (_type_ symbol) none)
(set-enable-keyboard! (_type_ symbol) none)
(update-mouse-controls! (_type_) none)
)
)
@@ -381,11 +373,76 @@
;;;; resets
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defmethod set-defaults-input! pc-settings ((obj pc-settings))
"Set default general input settings"
(set! (-> obj keyboard-enabled?) #t)
(set! (-> obj mouse-enabled?) #f)
(set! (-> obj auto-hide-cursor?) #t)
(none))
(defmethod set-defaults-controller! pc-settings ((obj pc-settings))
"Set default controller settings"
(set! (-> obj ignore-controller-win-unfocused?) #f)
(set! (-> obj controller-hp-led?) #f)
(set! (-> obj controller-eco-led?) #f)
(set! (-> obj stick-deadzone) 0.3)
(none))
(defmethod set-defaults-mouse! pc-settings ((obj pc-settings))
"Set default mouse settings"
(set! (-> obj mouse-camera?) #f)
(set! (-> obj mouse-xsens) -15.0)
(set! (-> obj mouse-ysens) 10.0)
(set! (-> obj mouse-movement?) #f)
(none))
(defmethod set-defaults! pc-settings ((obj pc-settings))
"Set default settings for the object that aren't handled by the file reading,
this is intended to be called upon object creation.
This is to avoid propagating events to the runtime informing it needs to make changes on startup
when there really is no changes to be made. ie. changing the window size multiple times when ultimately
the true value is within the file that we are about to read.
TODO - it would probably be a lot cleaner to have one struct for the file, and another for settings that
are manipulated at runtime so there is a clear separation of responsibilities"
;; top level settings
(set! (-> obj user) #f)
(set! (-> obj debug?) #f)
(set! (-> obj movie?) #f)
(set! (-> obj aspect-custom-x) 4)
(set! (-> obj aspect-custom-y) 3)
;; graphics
(set! (-> obj aspect-ratio-auto?) #t)
(set! (-> obj win-width) PC_BASE_WIDTH)
(set! (-> obj win-height) PC_BASE_HEIGHT)
;; audio
;; input
(set-defaults-input! obj)
(set-defaults-controller! obj)
(set-defaults-mouse! obj)
;; ps2
;; misc
;; extra
(dotimes (i PC_SPOOL_LOG_LENGTH) (set! (-> obj scenes-seen i) 0))
(set! (-> obj secrets art) (pc-jak1-concept-art))
(set! (-> obj secrets hard-fish-hiscore) 0)
(set! (-> obj secrets hard-rats-hiscore) 0)
(set! (-> obj secrets hard-rats-hiwave) 0)
(set! (-> obj secrets hard-rats?) #f)
(set! (-> obj secrets hero-mode?) #f)
(set! (-> obj secrets hud-map?) #t)
(set! (-> obj secrets hud-counters?) #t)
(set! (-> obj secrets hud-watch?) #f)
(set! (-> obj secrets watch-12hr?) #f)
(none)
)
(defmethod reset pc-settings ((obj pc-settings))
"Set the default settings"
"Reset back to default settings"
;(format #t "pc settings reset~%")
(format 0 "pc settings reset~%")
(set! (-> obj version) PC_KERNEL_VERSION)
@@ -397,11 +454,10 @@
(set! (-> obj aspect-custom-y) 3)
(set! (-> obj discord-rpc?) #t)
(set! (-> obj speedrunner-mode?) #f)
(set! (-> obj window-lock?) #t)
(reset-gfx obj)
(reset-audio obj)
(reset-input obj)
(reset-input obj 'all)
(reset-ps2 obj)
(reset-misc obj)
(reset-extra obj)
@@ -422,7 +478,11 @@
(set! (-> obj vsync?) #t)
(set! (-> obj letterbox?) #t)
(pc-get-screen-size -1 (&-> obj width) (&-> obj height))
;; TODO - why do we change the windowed mode twice
;; - apparently done to simplify changing the resolution, consider simplifying this so
;; the code is more obvious / easier to understand at first glance
;; (also reduces flickering of changing between modes)
(pc-get-active-display-size (&-> obj width) (&-> obj height))
(set-display-mode! obj 'borderless)
(set! (-> obj gfx-msaa) 2) ;; 2x msaa
@@ -441,9 +501,10 @@
(none))
(defmethod reset-input pc-settings ((obj pc-settings))
(defmethod reset-input pc-settings ((obj pc-settings) (device symbol))
"Set the default input settings"
(set! (-> obj controller-hp-led?) #f)
(set! (-> obj controller-eco-led?) #f)
(set! (-> obj stick-deadzone) 0.3)
(none))
+25 -1
View File
@@ -98,4 +98,28 @@
(defun get-video-params () *video-parms*)
(defun set-reactive-controller-led! ((current-hp float) (eco-level float) (eco-kind int))
;; Apply LED color based on HP if enabled and if eco is not active
(when (and (= eco-level 0.0) (-> *pc-settings* controller-hp-led?))
(case current-hp
((0.0)
(pc-set-controller-led! 0 255 0 0))
((1.0)
(pc-set-controller-led! 0 255 127 0))
((2.0)
(pc-set-controller-led! 0 255 255 0))
((3.0)
(pc-set-controller-led! 0 0 255 0))))
;; Otherwise apply LED color based on eco if enabled
(when (and (!= eco-level 0.0) (-> *pc-settings* controller-eco-led?))
(case eco-kind
((2)
(pc-set-controller-led! 0 255 0 0))
((3)
(pc-set-controller-led! 0 0 0 255))
((1)
(pc-set-controller-led! 0 255 255 0))
((4)
(pc-set-controller-led! 0 0 255 0))))
(none)
)
File diff suppressed because it is too large Load Diff
+36 -18
View File
@@ -143,6 +143,7 @@
(defenum pc-gfx-hack
(no-tex 0))
;; TODO - make a common kernel-defs
(define-extern __read-ee-timer (function uint))
(define-extern __mem-move (function pointer pointer uint none))
(define-extern __send-gfx-dma-chain (function object object none))
@@ -151,23 +152,43 @@
(define-extern __pc-get-mips2c (function string function))
(define-extern __pc-set-levels (function (pointer string) none))
(define-extern __pc-get-tex-remap (function int int int))
(define-extern pc-pad-input-mode-set (function symbol none))
(define-extern pc-pad-input-pad-set (function int none))
(define-extern pc-pad-input-mode-get (function int))
(define-extern pc-pad-input-key-get (function int))
(define-extern pc-pad-input-index-get (function int))
(define-extern pc-pad-input-map-save! (function none))
(define-extern pc-pad-get-mapped-button (function int int int))
(define-extern pc-get-fullscreen (function symbol))
(define-extern pc-get-screen-size (function int (pointer int32) (pointer int32) none))
(define-extern pc-get-screen-rate (function int int))
(define-extern pc-get-screen-vmode-count (function int))
(define-extern pc-get-monitor-count (function int))
;; Input Related Functions
(define-extern pc-get-controller-count (function int))
(define-extern pc-get-controller-name (function int string))
(deftype bind-assignment-info (structure)
((port int32)
(device-type int32)
(for-buttons? symbol)
(input-idx int32)
(analog-min-range? symbol)))
(define-extern pc-get-current-bind (function bind-assignment-info string))
(define-extern pc-waiting-for-bind? (function symbol))
(define-extern pc-set-waiting-for-bind! (function int symbol symbol int none))
(define-extern pc-stop-waiting-for-bind! (function none))
(define-extern pc-set-controller! (function int int none))
(define-extern pc-set-keyboard-enabled! (function symbol none))
(define-extern pc-set-mouse-options! (function symbol symbol symbol none))
(define-extern pc-set-mouse-camera-sens! (function float float none))
(define-extern pc-current-controller-has-led? (function symbol))
(define-extern pc-set-controller-led! (function int int int int none))
(define-extern pc-ignore-background-controller-events! (function symbol none))
(define-extern pc-reset-bindings-to-defaults! (function int int none))
(define-extern pc-set-auto-hide-cursor! (function symbol none))
;; Display Related Functions
(define-extern pc-get-display-mode (function symbol))
(define-extern pc-get-active-display-size (function (pointer int32) (pointer int32) none))
(define-extern pc-get-active-display-refresh-rate (function int))
(define-extern pc-get-display-count (function int))
(define-extern pc-get-display-name (function int string))
(define-extern pc-get-os (function symbol))
(define-extern pc-get-window-size (function (pointer int32) (pointer int32) none))
(define-extern pc-get-window-scale (function (pointer float) (pointer float) none))
(define-extern pc-set-window-size (function int int none))
(define-extern pc-set-fullscreen (function symbol int none))
(define-extern pc-set-fullscreen-display (function int none))
(define-extern pc-set-display-mode (function symbol none))
(define-extern pc-set-frame-rate (function int none))
(define-extern pc-set-vsync (function symbol none))
(define-extern pc-renderer-tree-set-lod (function pc-renderer-tree-type int none))
@@ -177,10 +198,6 @@
(define-extern pc-get-collision-mask (function pc-collision-mode int symbol))
(define-extern pc-set-collision-mask (function pc-collision-mode int symbol none))
(define-extern pc-set-collision-mode (function pc-collision-mode none))
(define-extern pc-filepath-exists? (function string symbol))
(define-extern pc-mkdir-file-path (function string none))
(define-extern pc-filter-debug-string? (function string float symbol))
(define-extern pc-rand (function int))
(declare-type discord-info structure)
(define-extern pc-discord-rpc-update (function discord-info none))
(define-extern pc-discord-rpc-set (function int none))
@@ -193,8 +210,9 @@
(define-extern pc-set-game-resolution (function int int none))
(define-extern pc-set-msaa (function int none))
(define-extern pc-set-gfx-hack (function pc-gfx-hack symbol none))
(define-extern pc-get-unix-timestamp (function int))
(define-extern pc-filter-debug-string? (function string float symbol))
(define-extern pc-rand (function int))
(define-extern file-stream-open (function file-stream string symbol file-stream))
(define-extern file-stream-close (function file-stream file-stream))
-1
View File
@@ -34,7 +34,6 @@ add_executable(goalc-test
${CMAKE_CURRENT_LIST_DIR}/decompiler/test_DataParser.cpp
${CMAKE_CURRENT_LIST_DIR}/decompiler/test_DisasmVifDecompile.cpp
${CMAKE_CURRENT_LIST_DIR}/decompiler/test_VuDisasm.cpp
${CMAKE_CURRENT_LIST_DIR}/game/test_newpad.cpp
${CMAKE_CURRENT_LIST_DIR}/common/formatter/test_formatter.cpp
${GOALC_TEST_FRAMEWORK_SOURCES}
${GOALC_TEST_CASES}
-344
View File
@@ -1,344 +0,0 @@
#include <algorithm>
#include <cmath>
#include "game/graphics/pipelines/opengl.h"
#include "game/system/newpad.h"
#include "gtest/gtest.h"
// #include "gmock/gmock.h"
class PeripheralTest : public ::testing::Test {
public:
PeripheralTest() {
Pad::ForceClearKeys();
Pad::ForceClearAnalogValue();
for (int i = 0; i < Pad::CONTROLLER_COUNT; ++i) {
Pad::clear_pad(i);
Pad::SetGamepadState(i, -1);
}
Pad::DefaultMapping(mapping_info_);
};
~PeripheralTest(){};
protected:
Pad::MappingInfo mapping_info_;
};
TEST_F(PeripheralTest, UpdatePad_KeyboardPad_ClearsControllerInputBuffers) {
// Arrange
bool expected_controller_status = false;
bool* controller_input_buffer = Pad::GetControllerInputBuffer(0);
::memset(controller_input_buffer, true,
sizeof(controller_input_buffer[0]) * (int)Pad::Button::Max);
// Act
Pad::update_gamepads(mapping_info_);
// Assert
for (int i = 0; i < (int)Pad::Button::Max; ++i) {
EXPECT_EQ(expected_controller_status, controller_input_buffer[i]);
}
}
TEST_F(PeripheralTest, ClearKey_ValidKey_UpdateKeyboardBuffer) {
// Arrange
int input_key = 127;
bool expected_buffer_key_status = false;
bool* actual_keyboard_buffer = Pad::GetKeyboardBufferedInputBuffer();
::memset(actual_keyboard_buffer, true, sizeof(*actual_keyboard_buffer) * glfw::NUM_KEYS);
// Act
Pad::ClearKey(input_key);
// Assert
bool actual_buffer_key_status = actual_keyboard_buffer[input_key];
EXPECT_EQ(expected_buffer_key_status, actual_buffer_key_status);
}
TEST_F(PeripheralTest, ClearKey_InvalidKey_DoNothing) {
// Arrange
int input_key = glfw::NUM_KEYS + 1;
bool expected_buffer_key_status = true;
bool* actual_keyboard_buffer = Pad::GetKeyboardBufferedInputBuffer();
::memset(actual_keyboard_buffer, true, sizeof(*actual_keyboard_buffer) * glfw::NUM_KEYS);
// Act
Pad::ClearKey(input_key);
// Assert
for (int i = 0; i < glfw::NUM_KEYS; ++i) {
EXPECT_EQ(expected_buffer_key_status, actual_keyboard_buffer[i]);
}
}
TEST_F(PeripheralTest, SetAnalogAxisValue_NominalAnalogAxisX_SetConvertedValue) {
// Arrange
float expected_analog_value = 0;
std::swap(mapping_info_.controller_analog_mapping[0][(int)Pad::Analog::Left_X],
mapping_info_.controller_analog_mapping[0][(int)Pad::Analog::Right_X]);
// Act
Pad::SetAnalogAxisValue(mapping_info_, static_cast<int>(GlfwKeyCustomAxis::CURSOR_X_AXIS), 100.0);
// Assert
float* keyboard_analog_buffer = Pad::GetKeyboardInputAnalogBuffer(0);
EXPECT_FLOAT_EQ(expected_analog_value, keyboard_analog_buffer[(int)Pad::Analog::Left_X]);
}
TEST_F(PeripheralTest, SetAnalogAxisValue_NominalAnalogAxisY_SetConvertedValue) { // Arrange
float expected_analog_value = 0;
std::swap(mapping_info_.controller_analog_mapping[0][(int)Pad::Analog::Left_Y],
mapping_info_.controller_analog_mapping[0][(int)Pad::Analog::Right_Y]);
// Act
Pad::SetAnalogAxisValue(mapping_info_, static_cast<int>(GlfwKeyCustomAxis::CURSOR_X_AXIS), 100.0);
// Assert
float* keyboard_analog_buffer = Pad::GetKeyboardInputAnalogBuffer(0);
EXPECT_FLOAT_EQ(expected_analog_value, keyboard_analog_buffer[(int)Pad::Analog::Left_Y]);
}
TEST_F(PeripheralTest, SetAnalogAxisValue_InputLargerThanMaxValue_SetMaxValue) {
// Arrange
Pad::AnalogMappingInfo analog_mapping_info;
analog_mapping_info.mode = Pad::AnalogMappingMode::AnalogInput;
analog_mapping_info.axis_id = GlfwKeyCustomAxis::CURSOR_X_AXIS;
Pad::MapAnalog(mapping_info_, Pad::Analog::Right_X, 0, analog_mapping_info);
analog_mapping_info.axis_id = GlfwKeyCustomAxis::CURSOR_Y_AXIS;
Pad::MapAnalog(mapping_info_, Pad::Analog::Right_Y, 0, analog_mapping_info);
float expected_analog_value = 1;
// Act
Pad::SetAnalogAxisValue(mapping_info_, static_cast<int>(GlfwKeyCustomAxis::CURSOR_X_AXIS), 100.0);
// Assert
float* keyboard_analog_buffer = Pad::GetKeyboardInputAnalogBuffer(0);
EXPECT_FLOAT_EQ(expected_analog_value, keyboard_analog_buffer[(int)Pad::Analog::Right_X]);
}
TEST_F(PeripheralTest, SetAnalogAxisValue_InputSmallerThanMinValue_SetMinValue) {
// Arrange
float expected_analog_value = -1;
Pad::AnalogMappingInfo analog_mapping_info;
analog_mapping_info.mode = Pad::AnalogMappingMode::AnalogInput;
analog_mapping_info.axis_id = GlfwKeyCustomAxis::CURSOR_X_AXIS;
Pad::MapAnalog(mapping_info_, Pad::Analog::Right_X, 0, analog_mapping_info);
analog_mapping_info.axis_id = GlfwKeyCustomAxis::CURSOR_Y_AXIS;
Pad::MapAnalog(mapping_info_, Pad::Analog::Right_Y, 0, analog_mapping_info);
// Act
Pad::SetAnalogAxisValue(mapping_info_, static_cast<int>(GlfwKeyCustomAxis::CURSOR_X_AXIS),
-100.0);
// Assert
float* keyboard_analog_buffer = Pad::GetKeyboardInputAnalogBuffer(0);
EXPECT_FLOAT_EQ(expected_analog_value, keyboard_analog_buffer[(int)Pad::Analog::Right_X]);
}
TEST_F(PeripheralTest, SetAnalogAxisValue_InputIsNAN_SetZero) {
// Arrange
float expected_analog_value = 0;
// Act
Pad::SetAnalogAxisValue(mapping_info_, static_cast<int>(GlfwKeyCustomAxis::CURSOR_X_AXIS),
std::nan("1"));
// Assert
float* keyboard_analog_buffer = Pad::GetKeyboardInputAnalogBuffer(0);
EXPECT_FLOAT_EQ(expected_analog_value, keyboard_analog_buffer[(int)Pad::Analog::Right_X]);
}
TEST_F(
PeripheralTest,
SetAnalogAxisValue_MouseXAxisSensitivityLowerThanMinimumSensitivty_SetMouseSensitivityToMinimum) {
// Arrange
float expected_x_axis_sensitivity = 1e-4;
mapping_info_.mouse_x_axis_sensitivities[0] = 0;
Pad::AnalogMappingInfo analog_mapping_info;
analog_mapping_info.mode = Pad::AnalogMappingMode::AnalogInput;
analog_mapping_info.axis_id = GlfwKeyCustomAxis::CURSOR_X_AXIS;
Pad::MapAnalog(mapping_info_, Pad::Analog::Right_X, 0, analog_mapping_info);
analog_mapping_info.axis_id = GlfwKeyCustomAxis::CURSOR_Y_AXIS;
Pad::MapAnalog(mapping_info_, Pad::Analog::Right_Y, 0, analog_mapping_info);
// Act
Pad::SetAnalogAxisValue(mapping_info_, static_cast<int>(GlfwKeyCustomAxis::CURSOR_X_AXIS), 100);
// Assert
EXPECT_FLOAT_EQ(expected_x_axis_sensitivity, mapping_info_.mouse_x_axis_sensitivities[0]);
}
TEST_F(
PeripheralTest,
SetAnalogAxisValue_MouseYAxisSensitivityLowerThanMinimumSensitivty_SetMouseSensitivityToMinimum) {
// Arrange
float expected_y_axis_sensitivity = 1e-4;
mapping_info_.mouse_y_axis_sensitivities[0] = 0;
Pad::AnalogMappingInfo analog_mapping_info;
analog_mapping_info.mode = Pad::AnalogMappingMode::AnalogInput;
analog_mapping_info.axis_id = GlfwKeyCustomAxis::CURSOR_X_AXIS;
Pad::MapAnalog(mapping_info_, Pad::Analog::Right_X, 0, analog_mapping_info);
analog_mapping_info.axis_id = GlfwKeyCustomAxis::CURSOR_Y_AXIS;
Pad::MapAnalog(mapping_info_, Pad::Analog::Right_Y, 0, analog_mapping_info);
// Act
Pad::SetAnalogAxisValue(mapping_info_, static_cast<int>(GlfwKeyCustomAxis::CURSOR_Y_AXIS), 100);
// Assert
EXPECT_FLOAT_EQ(expected_y_axis_sensitivity, mapping_info_.mouse_y_axis_sensitivities[0]);
}
TEST_F(PeripheralTest, UpdateAxisValue_XAxisPositiveKey_IncrementValue) {
// Arrange
float expected_analog_value = 1.0f;
int pad_index = 0;
int key = GLFW_KEY_D;
bool* keyboard_buffered_key_buffer = Pad::GetKeyboardBufferedInputBuffer();
keyboard_buffered_key_buffer[key] = true;
// Act
Pad::UpdateAxisValue(mapping_info_);
// Arrange
float* keyboard_analog_buffer = Pad::GetKeyboardInputAnalogBuffer(pad_index);
float actual_analog_value = keyboard_analog_buffer[(int)Pad::Analog::Left_X];
EXPECT_FLOAT_EQ(expected_analog_value, actual_analog_value);
}
TEST_F(PeripheralTest, UpdateAxisValue_YAxisPositiveKey_DecrementValue) {
// Arrange
float expected_analog_value = -1.0f;
int pad_index = 0;
int key = GLFW_KEY_W;
bool* keyboard_buffered_key_buffer = Pad::GetKeyboardBufferedInputBuffer();
keyboard_buffered_key_buffer[key] = true;
// Act
Pad::UpdateAxisValue(mapping_info_);
// Arrange
float* keyboard_analog_buffer = Pad::GetKeyboardInputAnalogBuffer(pad_index);
float actual_analog_value = keyboard_analog_buffer[(int)Pad::Analog::Left_Y];
EXPECT_FLOAT_EQ(expected_analog_value, actual_analog_value);
}
TEST_F(PeripheralTest, UpdateAxisValue_XAxisNegativeKey_DecrementValue) {
// Arrange
int pad_index = 0;
float expected_analog_value = -1.0f;
int key = GLFW_KEY_A;
bool* keyboard_buffered_key_buffer = Pad::GetKeyboardBufferedInputBuffer();
keyboard_buffered_key_buffer[key] = true;
// Act
Pad::UpdateAxisValue(mapping_info_);
// Arrange
float* keyboard_analog_buffer = Pad::GetKeyboardInputAnalogBuffer(pad_index);
float actual_analog_value = keyboard_analog_buffer[(int)Pad::Analog::Left_X];
EXPECT_FLOAT_EQ(expected_analog_value, actual_analog_value);
}
TEST_F(PeripheralTest, UpdateAxisValue_RightYAxisPositiveKey_IncrementValue) {
// Arrange
float expected_analog_value = 1.0f;
int pad_index = 0;
int key = GLFW_KEY_S;
bool* keyboard_buffered_key_buffer = Pad::GetKeyboardBufferedInputBuffer();
keyboard_buffered_key_buffer[key] = true;
// Act
Pad::UpdateAxisValue(mapping_info_);
// Arrange
float* keyboard_analog_buffer = Pad::GetKeyboardInputAnalogBuffer(pad_index);
float actual_analog_value = keyboard_analog_buffer[(int)Pad::Analog::Left_Y];
EXPECT_FLOAT_EQ(expected_analog_value, actual_analog_value);
}
TEST_F(PeripheralTest, GetAnalogValue_InvalidPadId_ReturnsNeutralPosition) {
// Arrange
int expected_analog_status = 127;
// Act
int actual_analog_status = Pad::GetAnalogValue(mapping_info_, Pad::Analog::Left_X, 2);
// Assert
EXPECT_EQ(expected_analog_status, actual_analog_status);
}
TEST_F(PeripheralTest, GetAnalogValue_ControllerPad_ReturnsAnalogValue) {
// Arrange
int expected_analog_status = 0;
int pad_index = 0;
int controller_index = 0;
Pad::SetGamepadState(pad_index, controller_index);
float* controller_analog_buffer = Pad::GetControllerAnalogInputBuffer(pad_index);
controller_analog_buffer[(int)Pad::Analog::Left_X] = -1.0f;
// Act
int actual_analog_status = Pad::GetAnalogValue(mapping_info_, Pad::Analog::Left_X, 0);
// Assert
EXPECT_EQ(expected_analog_status, actual_analog_status);
}
TEST_F(PeripheralTest, GetAnalogValue_KeyboardPad_ReturnsAnalogValue) {
// Arrange
int expected_analog_status = 255;
int pad_index = 0;
float* keyboard_analog_buffer = Pad::GetKeyboardInputAnalogBuffer(pad_index);
keyboard_analog_buffer[(int)Pad::Analog::Left_X] = 1.0f;
// Act
int actual_analog_status = Pad::GetAnalogValue(mapping_info_, Pad::Analog::Left_X, 0);
// Assert
EXPECT_EQ(expected_analog_status, actual_analog_status);
}
TEST_F(PeripheralTest, IsPressed_InvalidPadId_ReturnsFalse) {
// Arrange
int expected_button_status = 0;
// Act
int actual_button_status = Pad::IsPressed(mapping_info_, Pad::Button::X, 2);
// Assert
EXPECT_EQ(expected_button_status, actual_button_status);
}
TEST_F(PeripheralTest, IsPressed_ControllerPad_ReturnsControllerBufferValue) {
// Arrange
int expected_button_status = 1;
int pad_index = 0;
int controller_index = 0;
Pad::SetGamepadState(pad_index, controller_index);
bool* controller_button_status_buffer = Pad::GetControllerInputBuffer(pad_index);
controller_button_status_buffer[(int)Pad::Button::X] = expected_button_status;
// Act
int actual_button_status = Pad::IsPressed(mapping_info_, Pad::Button::X, pad_index);
// Assert
EXPECT_EQ(expected_button_status, actual_button_status);
}
TEST_F(PeripheralTest, IsPressed_KeyboardPad_ReturnsKeyboardBufferValue) {
// Arrange
int expected_button_status = 1;
bool* keyboard_buffered_key_status_buffer = Pad::GetKeyboardBufferedInputBuffer();
keyboard_buffered_key_status_buffer[GLFW_KEY_SPACE] = expected_button_status;
// Act
int actual_button_status = Pad::IsPressed(mapping_info_, Pad::Button::X, 0);
// Assert
EXPECT_EQ(expected_button_status, actual_button_status);
}
// TODO: InputModeStatus Tests
+89
View File
@@ -0,0 +1,89 @@
---
AlignConsecutiveMacros: Consecutive
AlignConsecutiveAssignments: None
AlignConsecutiveBitFields: None
AlignConsecutiveDeclarations: None
AlignEscapedNewlines: Right
AlignOperands: Align
AlignTrailingComments: true
AllowAllArgumentsOnNextLine: true
AllowAllParametersOfDeclarationOnNextLine: true
AllowShortEnumsOnASingleLine: true
AllowShortBlocksOnASingleLine: Never
AllowShortCaseLabelsOnASingleLine: false
AllowShortFunctionsOnASingleLine: All
AllowShortIfStatementsOnASingleLine: Never
AllowShortLoopsOnASingleLine: false
AlwaysBreakAfterDefinitionReturnType: All
AlwaysBreakAfterReturnType: AllDefinitions
AlwaysBreakBeforeMultilineStrings: false
AlwaysBreakTemplateDeclarations: MultiLine
# Custom brace breaking
BreakBeforeBraces: Custom
BraceWrapping:
AfterCaseLabel: true
AfterClass: true
AfterControlStatement: Never
AfterEnum: true
AfterFunction: true
AfterNamespace: true
AfterObjCDeclaration: true
AfterStruct: true
AfterUnion: true
AfterExternBlock: false
BeforeElse: false
BeforeWhile: true
IndentBraces: false
SplitEmptyFunction: true
SplitEmptyRecord: true
# Make the closing brace of container literals go to a new line
Cpp11BracedListStyle: false
# Never format includes
IncludeBlocks: Preserve
# clang-format version 4.0 through 12.0:
#SortIncludes: false
# clang-format version 13.0+:
#SortIncludes: Never
# No length limit, in case it breaks macros, you can
# disable it with /* clang-format off/on */ comments
ColumnLimit: 0
IndentWidth: 4
ContinuationIndentWidth: 4
IndentCaseLabels: false
IndentCaseBlocks: false
IndentGotoLabels: true
IndentPPDirectives: None
IndentExternBlock: NoIndent
SpaceAfterCStyleCast: true
SpacesInCStyleCastParentheses: false
SpacesInConditionalStatement: false
SpacesInContainerLiterals: true
SpaceBeforeAssignmentOperators: true
SpaceBeforeCaseColon: false
SpaceBeforeParens: ControlStatements
SpaceAroundPointerQualifiers: Default
SpaceInEmptyBlock: false
SpaceInEmptyParentheses: false
UseCRLF: false
UseTab: Never
ForEachMacros:
[
"spa_list_for_each",
"spa_list_for_each_safe",
"wl_list_for_each",
"wl_array_for_each",
"udev_list_entry_foreach",
]
---
+77
View File
@@ -0,0 +1,77 @@
# For format see editorconfig.org
# Copyright 2022 Collabora Ltd.
# SPDX-License-Identifier: Zlib
root = true
[*.{c,cg,cpp,gradle,h,java,m,metal,pl,py,S,sh,txt}]
indent_size = 4
indent_style = space
[*.{html,js,json,m4,yml,yaml,vcxproj,vcxproj.filters}]
indent_size = 2
indent_style = space
[*.xml]
indent_size = 4
indent_style = space
[{CMakeLists.txt,sdl2-config*.cmake.in,cmake/*.cmake}]
indent_size = 2
indent_style = space
[{cmake_uninstall.cmake.in,test/CMakeLists.txt}]
indent_size = 4
indent_style = space
[configure.ac]
# Inconsistently 2-, 4- or occasionally 3-space indented, but mostly 4,
# so let's use 4 for new code
indent_size = 4
indent_style = space
[{Makefile.*,*.mk,*.sln,*.pbxproj,*.plist}]
indent_size = 8
indent_style = tab
tab_width = 8
[Makefile.os2]
indent_size = 4
indent_style = space
[test/Makefile.os2]
indent_size = 2
indent_style = space
[{src/core/os2/geniconv/makefile,src/core/os2/geniconv/os2cp.c}]
indent_size = 2
indent_style = space
[src/joystick/controller_type.*]
indent_style = tab
[src/joystick/hidapi/steam/*.h]
indent_style = tab
[src/libm/*.c]
indent_style = tab
[src/test/SDL_test_{crc32,md5,random}.c]
indent_size = 2
indent_style = space
[src/video/yuv2rgb/*.{c,h}]
indent_style = tab
[wayland-protocols/*.xml]
indent_size = 2
indent_style = space
[*.{markdown,md}]
indent_size = 4
indent_style = space
# Markdown syntax treats tabs as 4 spaces
tab_width = 4
[{*.bat,*.rc}]
end_of_line = crlf
+7
View File
@@ -0,0 +1,7 @@
<!--- Provide a general summary of your changes in the Title above -->
## Description
<!--- Describe your changes in detail -->
## Existing Issue(s)
<!--- If it fixes an open issue, please link to the issue here. -->
+81
View File
@@ -0,0 +1,81 @@
name: Build (Android)
on: [push, pull_request]
jobs:
android:
name: ${{ matrix.platform.name }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
platform:
- { name: Android.mk }
- { name: CMake, cmake: 1, android_abi: "arm64-v8a", android_platform: 23, arch: "aarch64" }
steps:
- uses: actions/checkout@v3
- uses: nttld/setup-ndk@v1
id: setup_ndk
with:
ndk-version: r21e
- name: Build (Android.mk)
if: ${{ matrix.platform.name == 'Android.mk' }}
run: |
./build-scripts/androidbuildlibs.sh
- name: Setup (CMake)
if: ${{ matrix.platform.name == 'CMake' }}
run: |
sudo apt-get update
sudo apt-get install ninja-build pkg-config
- name: Configure (CMake)
if: ${{ matrix.platform.name == 'CMake' }}
run: |
cmake -B build \
-DCMAKE_TOOLCHAIN_FILE=${{ steps.setup_ndk.outputs.ndk-path }}/build/cmake/android.toolchain.cmake \
-DSDL_WERROR=ON \
-DANDROID_PLATFORM=${{ matrix.platform.android_platform }} \
-DANDROID_ABI=${{ matrix.platform.android_abi }} \
-DSDL_STATIC_PIC=ON \
-DSDL_VENDOR_INFO="Github Workflow" \
-DCMAKE_INSTALL_PREFIX=prefix \
-DCMAKE_BUILD_TYPE=Release \
-GNinja
- name: Build (CMake)
if: ${{ matrix.platform.name == 'CMake' }}
run: |
cmake --build build --config Release --parallel --verbose
- name: Install (CMake)
if: ${{ matrix.platform.name == 'CMake' }}
run: |
cmake --install build --config Release
echo "SDL2_DIR=$(pwd)/prefix" >> $GITHUB_ENV
( cd prefix; find ) | LC_ALL=C sort -u
- name: Verify CMake configuration files
if: ${{ matrix.platform.name == 'CMake' }}
run: |
cmake -S cmake/test -B cmake_config_build -G Ninja \
-DCMAKE_TOOLCHAIN_FILE=${{ steps.setup_ndk.outputs.ndk-path }}/build/cmake/android.toolchain.cmake \
-DANDROID_PLATFORM=${{ matrix.platform.android_platform }} \
-DANDROID_ABI=${{ matrix.platform.android_abi }} \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_PREFIX_PATH=${{ env.SDL2_DIR }}
cmake --build cmake_config_build --verbose
- name: Verify sdl2-config
if: ${{ matrix.platform.name == 'CMake' }}
run: |
export CC="${{ steps.setup_ndk.outputs.ndk-path }}/toolchains/llvm/prebuilt/linux-x86_64/bin/clang --target=${{ matrix.platform.arch }}-none-linux-androideabi${{ matrix.platform.android_platform }}"
export PATH=${{ env.SDL2_DIR }}/bin:$PATH
cmake/test/test_sdlconfig.sh
- name: Verify sdl2.pc
if: ${{ matrix.platform.name == 'CMake' }}
run: |
export CC="${{ steps.setup_ndk.outputs.ndk-path }}/toolchains/llvm/prebuilt/linux-x86_64/bin/clang --target=${{ matrix.platform.arch }}-none-linux-androideabi${{ matrix.platform.android_platform }}"
export PKG_CONFIG_PATH=${{ env.SDL2_DIR }}/lib/pkgconfig
cmake/test/test_pkgconfig.sh
- name: Verify Android.mk
if: ${{ matrix.platform.name == 'CMake' }}
run: |
export NDK_MODULE_PATH=${{ env.SDL2_DIR }}/share/ndk-modules
ndk-build -C ${{ github.workspace }}/cmake/test APP_PLATFORM=android-${{ matrix.platform.android_platform }} APP_ABI=${{ matrix.platform.android_abi }} NDK_OUT=$PWD NDK_LIBS_OUT=$PWD V=1
+45
View File
@@ -0,0 +1,45 @@
name: Build (Emscripten)
on: [push, pull_request]
jobs:
emscripten:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: mymindstorm/setup-emsdk@v10
with:
version: 2.0.32
- name: Install ninja
run: |
sudo apt-get -y update
sudo apt-get install -y ninja-build
- name: Configure CMake
run: |
emcmake cmake -S . -B build \
-DSDL_WERROR=ON \
-DSDL_TESTS=ON \
-DSDL_INSTALL_TESTS=ON \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_INSTALL_PREFIX=prefix \
-GNinja
- name: Build
run: cmake --build build/ --verbose
- name: Run build-time tests
run: |
set -eu
export SDL_TESTS_QUICK=1
# FIXME: enable Emscripten build time tests
# ctest -VV --test-dir build/
- name: Install
run: |
echo "SDL2_DIR=$(pwd)/prefix" >> $GITHUB_ENV
cmake --install build/
- name: Verify CMake configuration files
run: |
emcmake cmake -S cmake/test -B cmake_config_build \
-DCMAKE_BUILD_TYPE=Release \
-DSDL_VENDOR_INFO="Github Workflow" \
-DTEST_SHARED=FALSE \
-DCMAKE_PREFIX_PATH=${{ env.SDL2_DIR }}
cmake --build cmake_config_build --verbose
+20
View File
@@ -0,0 +1,20 @@
name: Build (iOS/tvOS)
on: [push, pull_request]
jobs:
Build:
name: ${{ matrix.platform.name }}
runs-on: macos-latest
strategy:
fail-fast: false
matrix:
platform:
- { name: iOS, target: Static Library-iOS, sdk: iphoneos }
- { name: tvOS, target: Static Library-tvOS, sdk: appletvos }
steps:
- uses: actions/checkout@v3
- name: Build
run: xcodebuild -project Xcode/SDL/SDL.xcodeproj -target '${{ matrix.platform.target }}' -configuration Release -sdk ${{ matrix.platform.sdk }} clean build
+207
View File
@@ -0,0 +1,207 @@
name: Build
on: [push, pull_request]
jobs:
Build:
name: ${{ matrix.platform.name }}
runs-on: ${{ matrix.platform.os }}
defaults:
run:
shell: ${{ matrix.platform.shell }}
strategy:
fail-fast: false
matrix:
platform:
- { name: Windows (mingw32), os: windows-latest, shell: 'msys2 {0}', msystem: mingw32, msys-env: mingw-w64-i686 }
- { name: Windows (mingw64), os: windows-latest, shell: 'msys2 {0}', msystem: mingw64, msys-env: mingw-w64-x86_64 }
- { name: Windows (clang32), os: windows-latest, shell: 'msys2 {0}', msystem: clang32, msys-env: mingw-w64-clang-i686 }
- { name: Windows (clang64), os: windows-latest, shell: 'msys2 {0}', msystem: clang64, msys-env: mingw-w64-clang-x86_64 }
- { name: Windows (ucrt64), os: windows-latest, shell: 'msys2 {0}', msystem: ucrt64, msys-env: mingw-w64-ucrt-x86_64 }
- { name: Ubuntu 20.04 (CMake), os: ubuntu-20.04, shell: sh }
- { name: Ubuntu 20.04 (autotools), os: ubuntu-20.04, shell: sh, autotools: true }
- { name: Ubuntu 22.04 (CMake), os: ubuntu-22.04, shell: sh }
- { name: Ubuntu 22.04 (autotools), os: ubuntu-22.04, shell: sh, autotools: true }
- { name: MacOS (CMake), os: macos-latest, shell: sh, cmake: '-DCMAKE_OSX_ARCHITECTURES="x86_64;arm64"' }
- { name: MacOS (autotools), os: macos-latest, shell: sh, autotools: true }
steps:
- name: Set up MSYS2
if: matrix.platform.shell == 'msys2 {0}'
uses: msys2/setup-msys2@v2
with:
msystem: ${{ matrix.platform.msystem }}
install: >-
${{ matrix.platform.msys-env }}-cc
${{ matrix.platform.msys-env }}-cmake
${{ matrix.platform.msys-env }}-ninja
${{ matrix.platform.msys-env }}-pkg-config
- name: Setup Linux dependencies
if: runner.os == 'Linux'
run: |
sudo apt-get update
sudo apt-get install build-essential git make autoconf automake libtool \
pkg-config cmake ninja-build gnome-desktop-testing libasound2-dev libpulse-dev \
libaudio-dev libjack-dev libsndio-dev libsamplerate0-dev libx11-dev libxext-dev \
libxrandr-dev libxcursor-dev libxfixes-dev libxi-dev libxss-dev libwayland-dev \
libxkbcommon-dev libdrm-dev libgbm-dev libgl1-mesa-dev libgles2-mesa-dev \
libegl1-mesa-dev libdbus-1-dev libibus-1.0-dev libudev-dev fcitx-libs-dev
- name: Setup extra Ubuntu 22.04 dependencies
if: matrix.platform.os == 'ubuntu-22.04'
run: |
sudo apt-get install libpipewire-0.3-dev libdecor-0-dev
- name: Setup Macos dependencies
if: runner.os == 'macOS'
run: |
brew install \
ninja \
pkg-config
- uses: actions/checkout@v3
- name: Check that versioning is consistent
# We only need to run this once: arbitrarily use the Linux/CMake build
if: "runner.os == 'Linux' && ! matrix.platform.autotools"
run: ./build-scripts/test-versioning.sh
- name: Configure (CMake)
if: "! matrix.platform.autotools"
run: |
cmake -S . -B build -G Ninja \
-DSDL_TESTS=ON \
-DSDL_WERROR=ON \
-DSDL_INSTALL_TESTS=ON \
-DSDL_VENDOR_INFO="Github Workflow" \
-DCMAKE_INSTALL_PREFIX=cmake_prefix \
-DCMAKE_BUILD_TYPE=Release \
${{ matrix.platform.cmake }}
- name: Build (CMake)
if: "! matrix.platform.autotools"
run: |
cmake --build build/ --config Release --verbose --parallel
- name: Run build-time tests (CMake)
if: "! matrix.platform.autotools"
run: |
set -eu
export SDL_TESTS_QUICK=1
ctest -VV --test-dir build/
if test "${{ runner.os }}" = "Linux"; then
# This should show us the SDL_REVISION
strings build/libSDL2-2.0.so.0 | grep SDL-
fi
- name: Install (CMake)
if: "! matrix.platform.autotools"
run: |
set -eu
cmake --install build/ --config Release
echo "SDL2_DIR=$(pwd)/cmake_prefix" >> $GITHUB_ENV
( cd cmake_prefix; find ) | LC_ALL=C sort -u
- name: Configure (Autotools)
if: matrix.platform.autotools
run: |
set -eu
rm -fr build-autotools
mkdir build-autotools
./autogen.sh
(
cd build-autotools
${{ github.workspace }}/configure \
--enable-vendor-info="Github Workflow" \
--enable-werror \
--prefix=${{ github.workspace }}/autotools_prefix \
)
if test "${{ runner.os }}" != "macOS" ; then
curdir="$(pwd)"
multiarch="$(dpkg-architecture -qDEB_HOST_MULTIARCH)"
(
mkdir -p build-autotools/test
cd build-autotools/test
${{ github.workspace }}/test/configure \
--enable-werror \
--x-includes=/usr/include \
--x-libraries="/usr/lib/${multiarch}" \
--prefix=${{ github.workspace }}/autotools_prefix \
SDL_CFLAGS="-I${curdir}/include" \
SDL_LIBS="-L${curdir}/build-autotools/build/.libs -lSDL2" \
ac_cv_lib_SDL2_ttf_TTF_Init=no \
${NULL+}
)
fi
- name: Build (Autotools)
if: matrix.platform.autotools
run: |
set -eu
parallel="$(getconf _NPROCESSORS_ONLN)"
make -j"${parallel}" -C build-autotools V=1
if test "${{ runner.os }}" != "macOS" ; then
make -j"${parallel}" -C build-autotools/test V=1
fi
- name: Run build-time tests (Autotools)
if: ${{ matrix.platform.autotools && (runner.os != 'macOS') }}
run: |
set -eu
curdir="$(pwd)"
parallel="$(getconf _NPROCESSORS_ONLN)"
export SDL_TESTS_QUICK=1
make -j"${parallel}" -C build-autotools/test check LD_LIBRARY_PATH="${curdir}/build-autotools/build/.libs"
if test "${{ runner.os }}" = "Linux"; then
# This should show us the SDL_REVISION
strings "${curdir}/build-autotools/build/.libs/libSDL2-2.0.so.0" | grep SDL-
fi
- name: Install (Autotools)
if: matrix.platform.autotools
run: |
set -eu
curdir="$(pwd)"
parallel="$(getconf _NPROCESSORS_ONLN)"
make -j"${parallel}" -C build-autotools install V=1
if test "${{ runner.os }}" != "macOS" ; then
make -j"${parallel}" -C build-autotools/test install V=1
fi
( cd autotools_prefix; find . ) | LC_ALL=C sort -u
echo "SDL2_DIR=$(pwd)/autotools_prefix" >> $GITHUB_ENV
- name: Verify CMake configuration files
run: |
cmake -S cmake/test -B cmake_config_build -G Ninja \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_PREFIX_PATH=${{ env.SDL2_DIR }}
cmake --build cmake_config_build --verbose
- name: Verify sdl2-config
run: |
export PATH=${{ env.SDL2_DIR }}/bin:$PATH
cmake/test/test_sdlconfig.sh
- name: Verify sdl2.pc
run: |
export PKG_CONFIG_PATH=${{ env.SDL2_DIR }}/lib/pkgconfig
cmake/test/test_pkgconfig.sh
- name: Distcheck (Autotools)
if: matrix.platform.autotools
run: |
set -eu
parallel="$(getconf _NPROCESSORS_ONLN)"
make -j"${parallel}" -C build-autotools dist V=1
# Similar to Automake `make distcheck`: check that the tarball
# release is sufficient to do a new build
mkdir distcheck
tar -C distcheck -zxf build-autotools/SDL2-*.tar.gz
( cd distcheck/SDL2-* && ./configure )
make -j"${parallel}" -C distcheck/SDL2-*
- name: Run installed-tests (Autotools)
if: "runner.os == 'Linux' && matrix.platform.autotools"
run: |
set -eu
parallel="$(getconf _NPROCESSORS_ONLN)"
sudo make -j"${parallel}" -C build-autotools install
sudo make -j"${parallel}" -C build-autotools/test install
export SDL_TESTS_QUICK=1
# We need to set LD_LIBRARY_PATH because it isn't in the default
# linker search path. We don't need to set XDG_DATA_DIRS for
# ginsttest-runner, because /usr/local/share *is* in the default
# search path for that.
env --chdir=/ \
LD_LIBRARY_PATH=/usr/local/lib \
SDL_AUDIODRIVER=dummy \
SDL_VIDEODRIVER=dummy \
ginsttest-runner --tap SDL2
+74
View File
@@ -0,0 +1,74 @@
name: Build (MSVC)
on: [push, pull_request]
jobs:
Build:
name: ${{ matrix.platform.name }}
runs-on: windows-latest
strategy:
fail-fast: false
matrix:
platform:
- { name: Windows (x64), flags: -A x64, project: VisualC/SDL.sln, projectflags: '/p:Platform=x64' }
- { name: Windows (x86), flags: -A Win32, project: VisualC/SDL.sln, projectflags: '/p:Platform=Win32' }
- { name: Windows static VCRT (x64), flags: -A x64 -DSDL_FORCE_STATIC_VCRT=ON }
- { name: Windows static VCRT (x86), flags: -A Win32 -DSDL_FORCE_STATIC_VCRT=ON }
- { name: Windows (clang-cl x64), flags: -T ClangCL -A x64 }
- { name: Windows (clang-cl x86), flags: -T ClangCL -A Win32 }
- { name: Windows (ARM), flags: -A ARM }
- { name: Windows (ARM64), flags: -A ARM64 }
- { name: UWP (x64), flags: -A x64 -DCMAKE_SYSTEM_NAME=WindowsStore -DCMAKE_SYSTEM_VERSION="10.0" -DSDL_TESTS=OFF, nowerror: true,
project: VisualC-WinRT/SDL-UWP.sln, projectflags: '/p:Platform=x64 /p:WindowsTargetPlatformVersion=10.0.17763.0' }
steps:
- uses: actions/checkout@v3
- name: Create CMake project using SDL as a subproject
shell: python
run: |
import os
import textwrap
srcdir = r"${{ github.workspace }}".replace("\\", "/")
builddir = f"{ srcdir }/build"
os.makedirs(builddir)
with open(f"{ builddir }/CMakeLists.txt", "w") as f:
f.write(textwrap.dedent(f"""\
cmake_minimum_required(VERSION 3.0)
project(sdl_user)
add_subdirectory("{ srcdir }" SDL)
"""))
- name: Configure (CMake)
run: cmake -S build -B build `
-DSDL_WERROR=${{ !matrix.platform.nowerror }} `
-DSDL_TESTS=ON `
-DSDL_INSTALL_TESTS=ON `
-DSDL_VENDOR_INFO="Github Workflow" `
-DSDL2_DISABLE_INSTALL=OFF `
${{ matrix.platform.flags }} `
-DCMAKE_INSTALL_PREFIX=prefix
- name: Build (CMake)
run: cmake --build build/ --config Release --parallel
- name: Run build-time tests
if: "! contains(matrix.platform.name, 'ARM')"
run: |
$env:SDL_TESTS_QUICK=1
ctest -VV --test-dir build/ -C Release
- name: Install (CMake)
run: |
echo "SDL2_DIR=$Env:GITHUB_WORKSPACE/prefix" >> $Env:GITHUB_ENV
cmake --install build/
- name: Verify CMake configuration files
if: ${{ !contains(matrix.platform.name, 'UWP') }} # FIXME: cmake/test/CMakeLists.txt should support UWP
run: |
cmake -S cmake/test -B cmake_config_build `
-DCMAKE_PREFIX_PATH=${{ env.SDL2_DIR }} `
${{ matrix.platform.flags }}
cmake --build cmake_config_build --config Release
- name: Add msbuild to PATH
if: ${{ matrix.platform.project != '' }}
uses: microsoft/setup-msbuild@v1.1.3
- name: Build msbuild
if: ${{ matrix.platform.project != '' }}
run: msbuild ${{ matrix.platform.project }} /m /p:BuildInParallel=true /p:Configuration=Release ${{ matrix.platform.projectflags }}
+42
View File
@@ -0,0 +1,42 @@
name: Build (Nintendo 3DS)
on: [push, pull_request]
jobs:
n3ds:
runs-on: ubuntu-latest
container:
image: devkitpro/devkitarm:latest
steps:
- uses: actions/checkout@v3
- name: Install build requirements
run: |
apt update
apt install ninja-build
- name: Configure CMake
run: |
cmake -S . -B build -G Ninja \
-DCMAKE_TOOLCHAIN_FILE=${DEVKITPRO}/cmake/3DS.cmake \
-DSDL_WERROR=ON \
-DSDL_TESTS=ON \
-DSDL_INSTALL_TESTS=ON \
-DSDL_VENDOR_INFO="Github Workflow" \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_INSTALL_PREFIX=prefix
- name: Build
run: cmake --build build --verbose
- name: Install CMake
run: |
echo "SDL2_DIR=$(pwd)/prefix" >> $GITHUB_ENV
cmake --install build/
( cd prefix; find ) | LC_ALL=C sort -u
- name: Verify CMake configuration files
run: |
cmake -S cmake/test -B cmake_config_build -G Ninja \
-DCMAKE_TOOLCHAIN_FILE=${DEVKITPRO}/cmake/3DS.cmake \
-DTEST_SHARED=FALSE \
-DCMAKE_PREFIX_PATH=${{ env.SDL2_DIR }} \
-DCMAKE_BUILD_TYPE=Release
cmake --build cmake_config_build --verbose
# Not running test_pkgconfig.sh and test_sdlconfig.sh
# as invoking the compiler manually is not supported
+73
View File
@@ -0,0 +1,73 @@
name: Build (Sony Playstation 2)
on: [push, pull_request]
jobs:
ps2:
runs-on: ubuntu-latest
container: ps2dev/ps2dev:latest
steps:
- uses: actions/checkout@v3
- name: Setup dependencies
run: |
apk update
apk add cmake gmp mpc1 mpfr4 ninja pkgconf make git
# To be removed once ps2_drivers is part of PS2DEV
- name: Install ps2_drivers lib
run: |
git clone https://github.com/fjtrujy/ps2_drivers.git
cd ps2_drivers
make -j $(getconf _NPROCESSORS_ONLN) clean
make -j $(getconf _NPROCESSORS_ONLN)
make -j $(getconf _NPROCESSORS_ONLN) install
- name: Configure (CMake)
run: |
cmake -S . -B build -G Ninja \
-DCMAKE_TOOLCHAIN_FILE=$PS2DEV/ps2sdk/ps2dev.cmake \
-DSDL_WERROR=ON \
-DSDL_TESTS=ON \
-DCMAKE_INSTALL_PREFIX=cmake_prefix \
-DCMAKE_BUILD_TYPE=Release
- name: Build
run: cmake --build build --config Release --verbose --parallel
- name: Install (CMake)
run: |
set -eu
cmake --install build/ --config Release
echo "SDL2_DIR=$(pwd)/cmake_prefix" >> $GITHUB_ENV
( cd cmake_prefix; find ) | LC_ALL=C sort -u
- name: Verify CMake configuration files
run: |
cmake -S cmake/test -B cmake_config_build -G Ninja \
-DCMAKE_TOOLCHAIN_FILE=$PS2DEV/ps2sdk/ps2dev.cmake \
-DTEST_SHARED=FALSE \
-DCMAKE_PREFIX_PATH=${{ env.SDL2_DIR }} \
-DCMAKE_BUILD_TYPE=Release
cmake --build cmake_config_build --verbose
- name: Verify sdl2-config
run: |
export CC=mips64r5900el-ps2-elf-gcc
export PATH=${{ env.SDL2_DIR }}/bin:$PATH
export EXTRA_LDFLAGS="-L$PS2DEV/ps2sdk/ee/lib -L$PS2DEV/gsKit/lib -L$PS2DEV/ps2sdk/ports/lib"
cmake/test/test_sdlconfig.sh
- name: Verify sdl2.pc
run: |
export CC=mips64r5900el-ps2-elf-gcc
export EXTRA_LDFLAGS="-L$PS2DEV/ps2sdk/ee/lib -L$PS2DEV/gsKit/lib -L$PS2DEV/ps2sdk/ports/lib"
export PKG_CONFIG_PATH=${{ env.SDL2_DIR }}/lib/pkgconfig
cmake/test/test_pkgconfig.sh
- name: Get short SHA
id: slug
run: echo "::set-output name=sha8::$(echo ${GITHUB_SHA} | cut -c1-8)"
- name: Upload artifacts
if: ${{ success() }}
uses: actions/upload-artifact@v3
with:
name: tests-${{ steps.slug.outputs.sha8 }}
path: |
build/test
+50
View File
@@ -0,0 +1,50 @@
name: Build (Sony Playstation Portable)
on: [push, pull_request]
jobs:
psp:
runs-on: ubuntu-latest
container: pspdev/pspdev:latest
steps:
- uses: actions/checkout@v3
- name: Setup dependencies
run: |
apk update
apk add cmake gmp mpc1 mpfr4 make pkgconf
- name: Configure CMake
run: |
cmake -S . -B build \
-DCMAKE_TOOLCHAIN_FILE=$PSPDEV/psp/share/pspdev.cmake \
-DSDL_WERROR=ON \
-DSDL_TESTS=ON \
-DSDL_INSTALL_TESTS=ON \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_INSTALL_PREFIX=prefix
- name: Build
run: cmake --build build --config Release
- name: Install
run: |
echo "SDL2_DIR=$(pwd)/prefix" >> $GITHUB_ENV
cmake --install build --config Release
( cd prefix; find ) | LC_ALL=C sort -u
- name: Verify CMake configuration files
run: |
cmake -S cmake/test -B cmake_config_build \
-DCMAKE_TOOLCHAIN_FILE=$PSPDEV/psp/share/pspdev.cmake \
-DCMAKE_PREFIX_PATH=${{ env.SDL2_DIR }} \
-DTEST_SHARED=FALSE \
-DCMAKE_BUILD_TYPE=Release
cmake --build cmake_config_build --verbose
- name: Verify sdl2-config
run: |
export CC=psp-gcc
export PATH=${{ env.SDL2_DIR }}/bin:$PATH
export EXTRA_LDFLAGS="-L$PSPDEV/lib -L$PSPDEV/psp/lib -L$PSPDEV/psp/sdk/lib"
cmake/test/test_sdlconfig.sh
- name: Verify sdl2.pc
run: |
export CC=psp-gcc
export PKG_CONFIG_PATH=${{ env.SDL2_DIR }}/lib/pkgconfig
export EXTRA_LDFLAGS="-L$PSPDEV/lib -L$PSPDEV/psp/lib -L$PSPDEV/psp/sdk/lib"
cmake/test/test_pkgconfig.sh
+68
View File
@@ -0,0 +1,68 @@
name: Build (RISC OS)
on: [push, pull_request]
jobs:
Build:
name: ${{ matrix.platform.name }}
runs-on: ubuntu-latest
container: riscosdotinfo/riscos-gccsdk-4.7:latest
strategy:
fail-fast: false
matrix:
platform:
- { name: autotools, test_args: '-DTEST_SHARED=FALSE' } # FIXME: autotools should build and install shared libraries
- { name: CMake }
steps:
- name: Setup dependencies
run: apt-get update && apt-get install -y cmake ninja-build
- uses: actions/checkout@v3
- name: Configure (autotools)
if: ${{ contains(matrix.platform.name, 'autotools') }}
run: |
mkdir build_autotools
cd build_autotools
../configure \
--host=arm-unknown-riscos \
--disable-gcc-atomics \
--prefix=${{ github.workspace }}/prefix_autotools
- name: Build (autotools)
if: ${{ contains(matrix.platform.name, 'autotools') }}
run: make -C build_autotools -j`nproc` V=1
- name: Install (autotools)
if: ${{ contains(matrix.platform.name, 'autotools') }}
run: |
echo "SDL2_DIR=${{ github.workspace }}/prefix_autotools" >> $GITHUB_ENV
make -C build_autotools install
( cd ${{ github.workspace }}/prefix_autotools; find ) | LC_ALL=C sort -u
- name: Configure (CMake)
if: ${{ contains(matrix.platform.name, 'CMake') }}
run: |
cmake -S . -B build -G Ninja \
-DCMAKE_TOOLCHAIN_FILE=/home/riscos/env/toolchain-riscos.cmake \
-DRISCOS=ON \
-DSDL_GCC_ATOMICS=OFF \
-DSDL_TESTS=ON \
-DSDL_INSTALL_TESTS=ON \
-DSDL_VENDOR_INFO="Github Workflow" \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_INSTALL_PREFIX=${{ github.workspace }}/prefix_cmake
- name: Build (CMake)
if: ${{ contains(matrix.platform.name, 'CMake') }}
run: cmake --build build --verbose
- name: Install (CMake)
if: ${{ contains(matrix.platform.name, 'CMake') }}
run: |
echo "SDL2_DIR=${{ github.workspace }}/prefix_cmake" >> $GITHUB_ENV
cmake --install build/
( cd ${{ github.workspace }}/prefix_cmake; find ) | LC_ALL=C sort -u
- name: Verify CMake configuration files
run: |
cmake -S cmake/test -B cmake_config_build -G Ninja \
-DCMAKE_TOOLCHAIN_FILE=/home/riscos/env/toolchain-riscos.cmake \
-DCMAKE_PREFIX_PATH=${{ env.SDL2_DIR }} \
-DCMAKE_BUILD_TYPE=Release \
${{ matrix.platform.test_args }}
cmake --build cmake_config_build --verbose
+53
View File
@@ -0,0 +1,53 @@
name: Build (Sony Playstation Vita)
on: [push, pull_request]
defaults:
run:
shell: sh
jobs:
vita:
runs-on: ubuntu-latest
container:
image: vitasdk/vitasdk:latest
steps:
- uses: actions/checkout@v3
- name: Install build requirements
run: |
apk update
apk add cmake ninja pkgconf bash
- name: Configure CMake
run: |
cmake -S . -B build -G Ninja \
-DCMAKE_TOOLCHAIN_FILE=${VITASDK}/share/vita.toolchain.cmake \
-DSDL_WERROR=ON \
-DSDL_TESTS=ON \
-DSDL_INSTALL_TESTS=ON \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_INSTALL_PREFIX=prefix
- name: Build
run: cmake --build build --verbose
- name: Install CMake
run: |
echo "SDL2_DIR=$(pwd)/prefix" >> $GITHUB_ENV
cmake --install build/
( cd prefix; find ) | LC_ALL=C sort -u
- name: Verify CMake configuration files
run: |
cmake -S cmake/test -B cmake_config_build -G Ninja \
-DCMAKE_TOOLCHAIN_FILE=${VITASDK}/share/vita.toolchain.cmake \
-DTEST_SHARED=FALSE \
-DCMAKE_PREFIX_PATH=${{ env.SDL2_DIR }} \
-DCMAKE_BUILD_TYPE=Release
cmake --build cmake_config_build --verbose
- name: Verify sdl2-config
run: |
export CC=arm-vita-eabi-gcc
export PATH=${{ env.SDL2_DIR }}/bin:$PATH
cmake/test/test_sdlconfig.sh
- name: Verify sdl2.pc
run: |
export CC=arm-vita-eabi-gcc
export PKG_CONFIG_PATH=${{ env.SDL2_DIR }}/lib/pkgconfig
cmake/test/test_pkgconfig.sh
+49
View File
@@ -0,0 +1,49 @@
name: Build (VM Actions)
on: [push, pull_request]
jobs:
freebsd:
runs-on: macos-12
name: FreeBSD
steps:
- uses: actions/checkout@v3
- name: Build
uses: vmactions/freebsd-vm@v0
with:
usesh: true
prepare: |
pkg install -y \
gmake \
pkgconf \
libXcursor \
libXext \
libXinerama \
libXi \
libXfixes \
libXrandr \
libXScrnSaver \
libXxf86vm \
wayland \
wayland-protocols \
libxkbcommon \
mesa-libs \
libglvnd \
evdev-proto \
libinotify \
alsa-lib \
jackit \
nas \
pipewire \
pulseaudio \
sndio \
dbus \
zh-fcitx \
ibus \
libsamplerate \
libudev-devd
run: |
mkdir build_autotools
(cd build_autotools && CFLAGS="-I/usr/local/include" LDFLAGS="-L/usr/local/lib" ../configure)
gmake -C build_autotools -j`sysctl -n hw.ncpu` V=1
+35
View File
@@ -0,0 +1,35 @@
name: Build (OpenWatcom)
on: [push, pull_request]
jobs:
os2:
name: ${{ matrix.platform.name }}
runs-on: windows-latest
strategy:
matrix:
platform:
- { name: Windows, makefile: Makefile.w32 }
- { name: OS/2, makefile: Makefile.os2 }
steps:
- uses: actions/checkout@v3
- uses: open-watcom/setup-watcom@v0
- name: Build SDL2
run: |
wmake -f ${{ matrix.platform.makefile }} ENABLE_WERROR=1
- name: Build tests
run: |
cd test && wmake -f ${{ matrix.platform.makefile }} ENABLE_WERROR=1
cd ..
- name: Run tests
if: "matrix.platform.makefile == 'Makefile.w32'"
run: |
cd test && wmake -f ${{ matrix.platform.makefile }} check-quick
cd ..
- name: distclean
run: |
wmake -f ${{ matrix.platform.makefile }} distclean
cd test && wmake -f ${{ matrix.platform.makefile }} distclean
cd ..
Generated Vendored
+182
View File
@@ -0,0 +1,182 @@
aclocal.m4
autom4te*
config.cache
config.log
config.status
libtool
Makefile
Makefile.rules
sdl2-config
sdl2-config.cmake
sdl2-config-version.cmake
sdl2.pc
SDL2.spec
build
gen
Build
buildbot
/VERSION.txt
*.so
*.so.*
*.dll
*.exe
*.o
*.obj
*.res
*.lib
*.a
*.la
*.dSYM
*,e1f
*,ff8
*.lnk
*.err
*.exp
*.map
*.orig
*~
*.swp
*.tmp
*.rej
# for CMake
CMakeFiles/
CMakeCache.txt
cmake_install.cmake
cmake_uninstall.cmake
SDL2ConfigVersion.cmake
.ninja_*
*.ninja
# for CLion
.idea
cmake-build-*
# for Xcode
*.mode1*
*.perspective*
*.pbxuser
(^|/)build($|/)
.DS_Store
xcuserdata
*.xcworkspace
# for Visual C++
.vs
Debug
Release
*.user
*.ncb
*.suo
*.sdf
VisualC/tests/controllermap/axis.bmp
VisualC/tests/controllermap/button.bmp
VisualC/tests/controllermap/controllermap.bmp
VisualC/tests/controllermap/controllermap_back.bmp
VisualC/tests/loopwave/sample.wav
VisualC/tests/testautomation/CompareSurfaces0001_Reference.bmp
VisualC/tests/testautomation/CompareSurfaces0001_TestOutput.bmp
VisualC/tests/testgamecontroller/axis.bmp
VisualC/tests/testgamecontroller/button.bmp
VisualC/tests/testgamecontroller/controllermap.bmp
VisualC/tests/testgamecontroller/controllermap_back.bmp
VisualC/tests/testoverlay2/moose.dat
VisualC/tests/testrendertarget/icon.bmp
VisualC/tests/testrendertarget/sample.bmp
VisualC/tests/testscale/icon.bmp
VisualC/tests/testscale/sample.bmp
VisualC/tests/testsprite2/icon.bmp
VisualC/tests/testyuv/testyuv.bmp
VisualC/visualtest/icon.bmp
VisualC/visualtest/testquit.actions
VisualC/visualtest/testquit.config
VisualC/visualtest/testquit.exe
VisualC/visualtest/testquit.parameters
VisualC/visualtest/testsprite2.exe
VisualC/visualtest/testsprite2_sample.actions
VisualC/visualtest/testsprite2_sample.config
VisualC/visualtest/testsprite2_sample.parameters
VisualC-GDK/**/Layout
# for Android
android-project/local.properties
android-project/.gradle/
test/checkkeys
test/checkkeysthreads
test/controllermap
test/loopwave
test/loopwavequeue
test/testatomic
test/testaudiocapture
test/testaudiohotplug
test/testaudioinfo
test/testautomation
test/testbounds
test/testcustomcursor
test/testdisplayinfo
test/testdraw2
test/testdrawchessboard
test/testdropfile
test/testerror
test/testevdev
test/testfile
test/testfilesystem
test/testgamecontroller
test/testgeometry
test/testgesture
test/testgl2
test/testgles
test/testgles2
test/testhaptic
test/testhittesting
test/testhotplug
test/testiconv
test/testime
test/testintersections
test/testjoystick
test/testkeys
test/testloadso
test/testlocale
test/testlock
test/testmessage
test/testmouse
test/testmultiaudio
test/testnative
test/testoverlay2
test/testplatform
test/testpower
test/testqsort
test/testrelative
test/testrendercopyex
test/testrendertarget
test/testresample
test/testrumble
test/testscale
test/testsem
test/testsensor
test/testshader
test/testshape
test/testsprite2
test/testspriteminimal
test/teststreaming
test/testsurround
test/testthread
test/testtimer
test/testurl
test/testver
test/testviewport
test/testvulkan
test/testwm2
test/testyuv
test/torturethread
builddir/
debian/*.debhelper.log
debian/*.substvars
debian/*.tar.gz
debian/.debhelper/
debian/files
debian/libsdl*/
debian/tmp/

Some files were not shown because too many files have changed in this diff Show More