add macOS platform support
This commit is contained in:
parent
b845372efa
commit
3184592404
73 changed files with 3139 additions and 332 deletions
|
|
@ -54,7 +54,15 @@ set(REXGLUE_ROOT "${CMAKE_CURRENT_SOURCE_DIR}")
|
||||||
include(GNUInstallDirs)
|
include(GNUInstallDirs)
|
||||||
include(CMakePackageConfigHelpers)
|
include(CMakePackageConfigHelpers)
|
||||||
|
|
||||||
if(UNIX AND NOT APPLE)
|
if(APPLE)
|
||||||
|
# Keep app bundles and command-line launches able to find dylibs staged
|
||||||
|
# beside the executable.
|
||||||
|
list(APPEND CMAKE_INSTALL_RPATH
|
||||||
|
"@loader_path"
|
||||||
|
"@loader_path/../${CMAKE_INSTALL_LIBDIR}"
|
||||||
|
)
|
||||||
|
set(CMAKE_INSTALL_RPATH_USE_LINK_PATH ON)
|
||||||
|
elseif(UNIX AND NOT APPLE)
|
||||||
# Keep installed binaries self-contained by resolving shared libs relative
|
# Keep installed binaries self-contained by resolving shared libs relative
|
||||||
# to the install prefix (for example, bin -> ../lib).
|
# to the install prefix (for example, bin -> ../lib).
|
||||||
set(CMAKE_BUILD_RPATH_USE_ORIGIN ON)
|
set(CMAKE_BUILD_RPATH_USE_ORIGIN ON)
|
||||||
|
|
@ -86,15 +94,26 @@ set(CMAKE_CXX_EXTENSIONS OFF)
|
||||||
set(CMAKE_DEBUG_POSTFIX d)
|
set(CMAKE_DEBUG_POSTFIX d)
|
||||||
set(CMAKE_RELWITHDEBINFO_POSTFIX rd)
|
set(CMAKE_RELWITHDEBINFO_POSTFIX rd)
|
||||||
|
|
||||||
if(NOT WIN32)
|
set(REX_TARGET_PROCESSOR "${CMAKE_SYSTEM_PROCESSOR}")
|
||||||
|
if(APPLE AND CMAKE_OSX_ARCHITECTURES)
|
||||||
|
list(LENGTH CMAKE_OSX_ARCHITECTURES REX_OSX_ARCH_COUNT)
|
||||||
|
if(REX_OSX_ARCH_COUNT EQUAL 1)
|
||||||
|
set(REX_TARGET_PROCESSOR "${CMAKE_OSX_ARCHITECTURES}")
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(UNIX AND NOT APPLE)
|
||||||
# Use appropriate code model based on architecture
|
# Use appropriate code model based on architecture
|
||||||
if(CMAKE_SYSTEM_PROCESSOR MATCHES "AMD64|x86_64")
|
if(REX_TARGET_PROCESSOR MATCHES "AMD64|x86_64")
|
||||||
# Large code model on x86_64 for linking with very large recompiled executables (35MB+)
|
# Large code model on x86_64 for linking with very large recompiled executables (35MB+)
|
||||||
add_compile_options(-mcmodel=large)
|
add_compile_options(-mcmodel=large)
|
||||||
else()
|
else()
|
||||||
# Small code model for other architectures (ARM64, etc.)
|
# Small code model for other architectures (ARM64, etc.)
|
||||||
add_compile_options(-mcmodel=small)
|
add_compile_options(-mcmodel=small)
|
||||||
endif()
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(NOT WIN32)
|
||||||
# Position-independent code for all targets
|
# Position-independent code for all targets
|
||||||
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
|
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
|
||||||
endif()
|
endif()
|
||||||
|
|
@ -112,25 +131,32 @@ add_compile_options(
|
||||||
|
|
||||||
# Platform detection (supports AMD64 and ARM64)
|
# Platform detection (supports AMD64 and ARM64)
|
||||||
if(WIN32)
|
if(WIN32)
|
||||||
if(CMAKE_SYSTEM_PROCESSOR MATCHES "AMD64|x86_64")
|
if(REX_TARGET_PROCESSOR MATCHES "AMD64|x86_64")
|
||||||
set(REX_PLATFORM "win-amd64")
|
set(REX_PLATFORM "win-amd64")
|
||||||
elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64|ARM64")
|
elseif(REX_TARGET_PROCESSOR MATCHES "aarch64|ARM64")
|
||||||
set(REX_PLATFORM "win-arm64")
|
set(REX_PLATFORM "win-arm64")
|
||||||
else()
|
else()
|
||||||
message(FATAL_ERROR "Unsupported Windows architecture: ${CMAKE_SYSTEM_PROCESSOR}")
|
message(FATAL_ERROR "Unsupported Windows architecture: ${REX_TARGET_PROCESSOR}")
|
||||||
endif()
|
endif()
|
||||||
add_compile_definitions(REX_PLATFORM_WINDOWS=1)
|
add_compile_definitions(REX_PLATFORM_WINDOWS=1)
|
||||||
|
elseif(APPLE)
|
||||||
|
if(REX_TARGET_PROCESSOR MATCHES "aarch64|arm64|ARM64")
|
||||||
|
set(REX_PLATFORM "macos-arm64")
|
||||||
|
else()
|
||||||
|
message(FATAL_ERROR "Unsupported macOS architecture: ${REX_TARGET_PROCESSOR}. macOS builds are ARM64-only.")
|
||||||
|
endif()
|
||||||
|
add_compile_definitions(REX_PLATFORM_MAC=1)
|
||||||
elseif(UNIX AND NOT APPLE)
|
elseif(UNIX AND NOT APPLE)
|
||||||
if(CMAKE_SYSTEM_PROCESSOR MATCHES "AMD64|x86_64")
|
if(REX_TARGET_PROCESSOR MATCHES "AMD64|x86_64")
|
||||||
set(REX_PLATFORM "linux-amd64")
|
set(REX_PLATFORM "linux-amd64")
|
||||||
elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64|ARM64")
|
elseif(REX_TARGET_PROCESSOR MATCHES "aarch64|ARM64")
|
||||||
set(REX_PLATFORM "linux-arm64")
|
set(REX_PLATFORM "linux-arm64")
|
||||||
else()
|
else()
|
||||||
set(REX_PLATFORM "linux-${CMAKE_SYSTEM_PROCESSOR}")
|
set(REX_PLATFORM "linux-${REX_TARGET_PROCESSOR}")
|
||||||
endif()
|
endif()
|
||||||
add_compile_definitions(REX_PLATFORM_LINUX=1)
|
add_compile_definitions(REX_PLATFORM_LINUX=1)
|
||||||
else()
|
else()
|
||||||
message(FATAL_ERROR "Unsupported platform. ReXGlue supports Windows and Linux only.")
|
message(FATAL_ERROR "Unsupported platform. ReXGlue supports Windows, Linux and macOS only.")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# Build config as compile definition (multi-config generators don't know config at configure time)
|
# Build config as compile definition (multi-config generators don't know config at configure time)
|
||||||
|
|
|
||||||
|
|
@ -14,22 +14,30 @@
|
||||||
# runtime DLL staging is the host's job (see rexglue_configure_target).
|
# runtime DLL staging is the host's job (see rexglue_configure_target).
|
||||||
#==========================================================
|
#==========================================================
|
||||||
function(rexglue_apply_target_settings target_name)
|
function(rexglue_apply_target_settings target_name)
|
||||||
|
set(_rexglue_target_processor "${CMAKE_SYSTEM_PROCESSOR}")
|
||||||
|
if(APPLE AND CMAKE_OSX_ARCHITECTURES)
|
||||||
|
list(LENGTH CMAKE_OSX_ARCHITECTURES _rexglue_osx_arch_count)
|
||||||
|
if(_rexglue_osx_arch_count EQUAL 1)
|
||||||
|
set(_rexglue_target_processor "${CMAKE_OSX_ARCHITECTURES}")
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
if(UNIX AND NOT APPLE)
|
if(UNIX AND NOT APPLE)
|
||||||
find_package(PkgConfig REQUIRED)
|
find_package(PkgConfig REQUIRED)
|
||||||
pkg_check_modules(GTK3 REQUIRED gtk+-3.0)
|
pkg_check_modules(GTK3 REQUIRED gtk+-3.0)
|
||||||
target_include_directories(${target_name} PRIVATE ${GTK3_INCLUDE_DIRS})
|
target_include_directories(${target_name} PRIVATE ${GTK3_INCLUDE_DIRS})
|
||||||
target_link_libraries(${target_name} PRIVATE ${GTK3_LIBRARIES})
|
target_link_libraries(${target_name} PRIVATE ${GTK3_LIBRARIES})
|
||||||
# Large executable support
|
# Large executable support
|
||||||
if(CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64|AMD64")
|
if(_rexglue_target_processor MATCHES "x86_64|AMD64")
|
||||||
target_link_options(${target_name} PRIVATE -Wl,--no-relax)
|
target_link_options(${target_name} PRIVATE -Wl,--no-relax)
|
||||||
target_compile_options(${target_name} PRIVATE -mcmodel=large)
|
target_compile_options(${target_name} PRIVATE -mcmodel=large)
|
||||||
elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64|ARM64")
|
elseif(_rexglue_target_processor MATCHES "aarch64|ARM64")
|
||||||
target_compile_options(${target_name} PRIVATE -march=armv8-a)
|
target_compile_options(${target_name} PRIVATE -march=armv8-a)
|
||||||
endif()
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(NOT MSVC)
|
if(NOT MSVC AND NOT APPLE)
|
||||||
if(CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64|AMD64")
|
if(_rexglue_target_processor MATCHES "x86_64|AMD64")
|
||||||
target_compile_options(${target_name} PRIVATE -msse4.1)
|
target_compile_options(${target_name} PRIVATE -msse4.1)
|
||||||
endif()
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
@ -51,6 +59,9 @@ function(rexglue_configure_target target_name)
|
||||||
if(WIN32)
|
if(WIN32)
|
||||||
target_sources(${target_name} PRIVATE
|
target_sources(${target_name} PRIVATE
|
||||||
${REXGLUE_SHARE_DIR}/windowed_app_main_win.cpp)
|
${REXGLUE_SHARE_DIR}/windowed_app_main_win.cpp)
|
||||||
|
elseif(APPLE)
|
||||||
|
target_sources(${target_name} PRIVATE
|
||||||
|
${REXGLUE_SHARE_DIR}/windowed_app_main_sdl.cpp)
|
||||||
else()
|
else()
|
||||||
target_sources(${target_name} PRIVATE
|
target_sources(${target_name} PRIVATE
|
||||||
${REXGLUE_SHARE_DIR}/windowed_app_main_posix.cpp)
|
${REXGLUE_SHARE_DIR}/windowed_app_main_posix.cpp)
|
||||||
|
|
@ -68,7 +79,12 @@ function(rexglue_configure_target target_name)
|
||||||
target_include_directories(${target_name} PRIVATE
|
target_include_directories(${target_name} PRIVATE
|
||||||
${CMAKE_CURRENT_BINARY_DIR}/rexglue-sdk/include)
|
${CMAKE_CURRENT_BINARY_DIR}/rexglue-sdk/include)
|
||||||
|
|
||||||
if(UNIX AND NOT APPLE)
|
if(APPLE)
|
||||||
|
set_target_properties(${target_name} PROPERTIES
|
||||||
|
INSTALL_RPATH "@loader_path"
|
||||||
|
BUILD_WITH_INSTALL_RPATH ON
|
||||||
|
)
|
||||||
|
elseif(UNIX AND NOT APPLE)
|
||||||
set_target_properties(${target_name} PROPERTIES
|
set_target_properties(${target_name} PROPERTIES
|
||||||
INSTALL_RPATH "$ORIGIN"
|
INSTALL_RPATH "$ORIGIN"
|
||||||
BUILD_WITH_INSTALL_RPATH ON
|
BUILD_WITH_INSTALL_RPATH ON
|
||||||
|
|
@ -97,6 +113,56 @@ function(rexglue_configure_target target_name)
|
||||||
)
|
)
|
||||||
endif()
|
endif()
|
||||||
endforeach()
|
endforeach()
|
||||||
|
elseif(APPLE)
|
||||||
|
set(_rexglue_macos_vulkan_lib_dirs)
|
||||||
|
set(_rexglue_macos_vulkan_roots)
|
||||||
|
if(DEFINED ENV{VULKAN_SDK})
|
||||||
|
list(APPEND _rexglue_macos_vulkan_lib_dirs "$ENV{VULKAN_SDK}/lib")
|
||||||
|
list(APPEND _rexglue_macos_vulkan_roots "$ENV{VULKAN_SDK}")
|
||||||
|
endif()
|
||||||
|
list(APPEND _rexglue_macos_vulkan_lib_dirs /opt/homebrew/lib /usr/local/lib)
|
||||||
|
|
||||||
|
foreach(_vk_lib libvulkan.1.dylib libvulkan.dylib libMoltenVK.dylib)
|
||||||
|
foreach(_vk_lib_dir ${_rexglue_macos_vulkan_lib_dirs})
|
||||||
|
if(EXISTS "${_vk_lib_dir}/${_vk_lib}")
|
||||||
|
add_custom_command(TARGET ${target_name} POST_BUILD
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||||
|
"${_vk_lib_dir}/${_vk_lib}"
|
||||||
|
$<TARGET_FILE_DIR:${target_name}>
|
||||||
|
VERBATIM
|
||||||
|
)
|
||||||
|
break()
|
||||||
|
endif()
|
||||||
|
endforeach()
|
||||||
|
endforeach()
|
||||||
|
|
||||||
|
foreach(_vk_lib_dir ${_rexglue_macos_vulkan_lib_dirs})
|
||||||
|
if(EXISTS "${_vk_lib_dir}/libMoltenVK.dylib")
|
||||||
|
add_custom_command(TARGET ${target_name} POST_BUILD
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E make_directory
|
||||||
|
$<TARGET_FILE_DIR:${target_name}>/lib
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||||
|
"${_vk_lib_dir}/libMoltenVK.dylib"
|
||||||
|
$<TARGET_FILE_DIR:${target_name}>/lib
|
||||||
|
VERBATIM
|
||||||
|
)
|
||||||
|
break()
|
||||||
|
endif()
|
||||||
|
endforeach()
|
||||||
|
|
||||||
|
foreach(_vk_root ${_rexglue_macos_vulkan_roots})
|
||||||
|
if(EXISTS "${_vk_root}/share/vulkan/icd.d/MoltenVK_icd.json")
|
||||||
|
add_custom_command(TARGET ${target_name} POST_BUILD
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E make_directory
|
||||||
|
$<TARGET_FILE_DIR:${target_name}>/share/vulkan/icd.d
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||||
|
"${_vk_root}/share/vulkan/icd.d/MoltenVK_icd.json"
|
||||||
|
$<TARGET_FILE_DIR:${target_name}>/share/vulkan/icd.d
|
||||||
|
VERBATIM
|
||||||
|
)
|
||||||
|
break()
|
||||||
|
endif()
|
||||||
|
endforeach()
|
||||||
endif()
|
endif()
|
||||||
endfunction()
|
endfunction()
|
||||||
|
|
||||||
|
|
@ -131,7 +197,12 @@ function(rexglue_configure_module_target target_name)
|
||||||
)
|
)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(UNIX AND NOT APPLE)
|
if(APPLE)
|
||||||
|
set_target_properties(${target_name} PROPERTIES
|
||||||
|
INSTALL_RPATH "@loader_path"
|
||||||
|
BUILD_WITH_INSTALL_RPATH ON
|
||||||
|
)
|
||||||
|
elseif(UNIX AND NOT APPLE)
|
||||||
set_target_properties(${target_name} PROPERTIES
|
set_target_properties(${target_name} PROPERTIES
|
||||||
INSTALL_RPATH "$ORIGIN"
|
INSTALL_RPATH "$ORIGIN"
|
||||||
BUILD_WITH_INSTALL_RPATH ON
|
BUILD_WITH_INSTALL_RPATH ON
|
||||||
|
|
|
||||||
|
|
@ -116,6 +116,7 @@ endif()
|
||||||
install(FILES
|
install(FILES
|
||||||
src/ui/windowed_app_main_win.cpp
|
src/ui/windowed_app_main_win.cpp
|
||||||
src/ui/windowed_app_main_posix.cpp
|
src/ui/windowed_app_main_posix.cpp
|
||||||
|
src/ui/windowed_app_main_sdl.cpp
|
||||||
src/ui/rex_app.cpp
|
src/ui/rex_app.cpp
|
||||||
DESTINATION ${CMAKE_INSTALL_DATADIR}/rexglue
|
DESTINATION ${CMAKE_INSTALL_DATADIR}/rexglue
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,20 @@
|
||||||
|
|
||||||
#include <rex/chrono/clock.h>
|
#include <rex/chrono/clock.h>
|
||||||
|
|
||||||
|
#if !defined(__cpp_lib_chrono) || __cpp_lib_chrono < 201907L
|
||||||
|
namespace std::chrono {
|
||||||
|
|
||||||
|
template <typename DestClock, typename SourceClock>
|
||||||
|
struct clock_time_conversion;
|
||||||
|
|
||||||
|
template <typename DestClock, typename SourceClock, typename Duration>
|
||||||
|
auto clock_cast(const std::chrono::time_point<SourceClock, Duration>& t) {
|
||||||
|
return clock_time_conversion<DestClock, SourceClock>{}(t);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace std::chrono
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace rex {
|
namespace rex {
|
||||||
using hundrednano = std::ratio<1, 10000000>;
|
using hundrednano = std::ratio<1, 10000000>;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -122,6 +122,7 @@ class GraphicsSystem : public system::IGraphicsSystem {
|
||||||
uint32_t interrupt_callback_ = 0;
|
uint32_t interrupt_callback_ = 0;
|
||||||
uint32_t interrupt_callback_data_ = 0;
|
uint32_t interrupt_callback_data_ = 0;
|
||||||
|
|
||||||
|
std::atomic<bool> shutting_down_{false};
|
||||||
std::atomic<bool> vsync_worker_running_;
|
std::atomic<bool> vsync_worker_running_;
|
||||||
system::object_ref<system::XHostThread> vsync_worker_thread_;
|
system::object_ref<system::XHostThread> vsync_worker_thread_;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -991,8 +991,11 @@ class SpirvShaderTranslator : public ShaderTranslator {
|
||||||
spv::Id main_memexport_allowed_;
|
spv::Id main_memexport_allowed_;
|
||||||
// VS rectangle-list fallback only - uint.
|
// VS rectangle-list fallback only - uint.
|
||||||
spv::Id var_main_rectangle_guest_vertex_index_;
|
spv::Id var_main_rectangle_guest_vertex_index_;
|
||||||
// VS rectangle-list fallback only - gl_PerVertex[3].
|
// VS rectangle-list fallback only - float4[3].
|
||||||
spv::Id var_main_rectangle_per_vertex_;
|
spv::Id var_main_rectangle_position_;
|
||||||
|
// VS rectangle-list fallback only - float[3 * distance_count], when needed.
|
||||||
|
spv::Id var_main_rectangle_clip_distances_;
|
||||||
|
spv::Id var_main_rectangle_cull_distances_;
|
||||||
// VS rectangle-list fallback only - float4[3] for each used interpolator.
|
// VS rectangle-list fallback only - float4[3] for each used interpolator.
|
||||||
std::array<spv::Id, xenos::kMaxInterpolators> var_main_rectangle_interpolators_;
|
std::array<spv::Id, xenos::kMaxInterpolators> var_main_rectangle_interpolators_;
|
||||||
// VS only - float3 (special exports).
|
// VS only - float3 (special exports).
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,7 @@
|
||||||
#include <rex/graphics/vulkan/texture_cache.h>
|
#include <rex/graphics/vulkan/texture_cache.h>
|
||||||
#include <rex/graphics/xenos.h>
|
#include <rex/graphics/xenos.h>
|
||||||
#include <rex/hash.h>
|
#include <rex/hash.h>
|
||||||
|
#include <rex/perf/counter.h>
|
||||||
#include <rex/system/kernel_state.h>
|
#include <rex/system/kernel_state.h>
|
||||||
#include <rex/ui/vulkan/linked_type_descriptor_set_allocator.h>
|
#include <rex/ui/vulkan/linked_type_descriptor_set_allocator.h>
|
||||||
#include <rex/ui/vulkan/presenter.h>
|
#include <rex/ui/vulkan/presenter.h>
|
||||||
|
|
@ -242,6 +243,9 @@ class VulkanCommandProcessor : public CommandProcessor {
|
||||||
bool keep_dynamic_blend_constants = false,
|
bool keep_dynamic_blend_constants = false,
|
||||||
bool keep_dynamic_stencil_mask_ref = false);
|
bool keep_dynamic_stencil_mask_ref = false);
|
||||||
void BindExternalComputePipeline(VkPipeline pipeline);
|
void BindExternalComputePipeline(VkPipeline pipeline);
|
||||||
|
uint32_t BeginGpuTimestampedRegion(rex::perf::DrawBucket bucket) {
|
||||||
|
return BeginGpuTimestampedDraw(bucket);
|
||||||
|
}
|
||||||
void SetViewport(const VkViewport& viewport);
|
void SetViewport(const VkViewport& viewport);
|
||||||
void SetScissor(const VkRect2D& scissor);
|
void SetScissor(const VkRect2D& scissor);
|
||||||
|
|
||||||
|
|
@ -446,6 +450,14 @@ class VulkanCommandProcessor : public CommandProcessor {
|
||||||
void DisableHostOcclusionQueries();
|
void DisableHostOcclusionQueries();
|
||||||
uint64_t NormalizeOcclusionSamples(uint64_t samples) const;
|
uint64_t NormalizeOcclusionSamples(uint64_t samples) const;
|
||||||
void WriteGuestOcclusionResult(uint32_t sample_count_address, uint64_t samples);
|
void WriteGuestOcclusionResult(uint32_t sample_count_address, uint64_t samples);
|
||||||
|
bool InitializeGpuTimestampResources();
|
||||||
|
void ShutdownGpuTimestampResources();
|
||||||
|
void BeginGpuTimestampFrame();
|
||||||
|
void EndGpuTimestampFrame();
|
||||||
|
void ResolveGpuTimestampFrame(VkCommandBuffer command_buffer);
|
||||||
|
void ProcessGpuTimestampResults();
|
||||||
|
uint32_t BeginGpuTimestampedDraw(rex::perf::DrawBucket bucket);
|
||||||
|
void EndGpuTimestampedDraw(uint32_t start_query);
|
||||||
void InvalidateAllVertexBufferResidency();
|
void InvalidateAllVertexBufferResidency();
|
||||||
void InvalidateVertexBufferResidency(uint32_t vfetch_index);
|
void InvalidateVertexBufferResidency(uint32_t vfetch_index);
|
||||||
void InvalidateVertexBufferResidencyRange(uint32_t first_vfetch, uint32_t last_vfetch);
|
void InvalidateVertexBufferResidencyRange(uint32_t first_vfetch, uint32_t last_vfetch);
|
||||||
|
|
@ -747,6 +759,32 @@ class VulkanCommandProcessor : public CommandProcessor {
|
||||||
uint32_t host_index = UINT32_MAX;
|
uint32_t host_index = UINT32_MAX;
|
||||||
bool valid = false;
|
bool valid = false;
|
||||||
} active_occlusion_query_;
|
} active_occlusion_query_;
|
||||||
|
// MoltenVK maps timestamp queries to Metal counter sample buffers with a
|
||||||
|
// small per-pool limit, so keep the per-frame query budget modest.
|
||||||
|
static constexpr uint32_t kMaxGpuTimestampQueriesPerFrame = 1024;
|
||||||
|
VkQueryPool gpu_timestamp_query_pool_ = VK_NULL_HANDLE;
|
||||||
|
VkBuffer gpu_timestamp_readback_buffer_ = VK_NULL_HANDLE;
|
||||||
|
VkDeviceMemory gpu_timestamp_readback_memory_ = VK_NULL_HANDLE;
|
||||||
|
uint32_t gpu_timestamp_readback_memory_type_ = UINT32_MAX;
|
||||||
|
VkDeviceSize gpu_timestamp_readback_memory_size_ = 0;
|
||||||
|
uint64_t* gpu_timestamp_readback_mapping_ = nullptr;
|
||||||
|
bool gpu_timestamp_resources_available_ = false;
|
||||||
|
struct GpuTimestampFrame {
|
||||||
|
uint64_t frame = 0;
|
||||||
|
uint64_t submission = 0;
|
||||||
|
uint32_t query_base = 0;
|
||||||
|
uint32_t query_count = 0;
|
||||||
|
uint32_t frame_start_query = UINT32_MAX;
|
||||||
|
uint32_t frame_end_query = UINT32_MAX;
|
||||||
|
bool pending = false;
|
||||||
|
bool active_bucket_valid = false;
|
||||||
|
rex::perf::DrawBucket active_bucket = rex::perf::DrawBucket::kMainColorDepth;
|
||||||
|
uint32_t active_bucket_index = UINT32_MAX;
|
||||||
|
std::vector<rex::perf::DrawBucket> buckets;
|
||||||
|
std::vector<uint32_t> bucket_start_queries;
|
||||||
|
std::vector<uint32_t> bucket_end_queries;
|
||||||
|
};
|
||||||
|
std::array<GpuTimestampFrame, kMaxFramesInFlight> gpu_timestamp_frames_;
|
||||||
struct VertexBufferState {
|
struct VertexBufferState {
|
||||||
uint32_t address = UINT32_MAX;
|
uint32_t address = UINT32_MAX;
|
||||||
uint32_t size = UINT32_MAX;
|
uint32_t size = UINT32_MAX;
|
||||||
|
|
|
||||||
|
|
@ -351,6 +351,15 @@ class DeferredCommandBuffer {
|
||||||
std::memcpy(args_ptr + header_size, viewports, sizeof(VkViewport) * viewport_count);
|
std::memcpy(args_ptr + header_size, viewports, sizeof(VkViewport) * viewport_count);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CmdVkWriteTimestamp(VkPipelineStageFlagBits pipeline_stage, VkQueryPool query_pool,
|
||||||
|
uint32_t query) {
|
||||||
|
auto& args = *reinterpret_cast<ArgsVkWriteTimestamp*>(
|
||||||
|
WriteCommand(Command::kVkWriteTimestamp, sizeof(ArgsVkWriteTimestamp)));
|
||||||
|
args.pipeline_stage = pipeline_stage;
|
||||||
|
args.query_pool = query_pool;
|
||||||
|
args.query = query;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
enum class Command {
|
enum class Command {
|
||||||
kVkBeginRenderPass,
|
kVkBeginRenderPass,
|
||||||
|
|
@ -381,6 +390,7 @@ class DeferredCommandBuffer {
|
||||||
kVkSetStencilReference,
|
kVkSetStencilReference,
|
||||||
kVkSetStencilWriteMask,
|
kVkSetStencilWriteMask,
|
||||||
kVkSetViewport,
|
kVkSetViewport,
|
||||||
|
kVkWriteTimestamp,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct CommandHeader {
|
struct CommandHeader {
|
||||||
|
|
@ -526,6 +536,12 @@ class DeferredCommandBuffer {
|
||||||
uint32_t query_count;
|
uint32_t query_count;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct ArgsVkWriteTimestamp {
|
||||||
|
VkPipelineStageFlagBits pipeline_stage;
|
||||||
|
VkQueryPool query_pool;
|
||||||
|
uint32_t query;
|
||||||
|
};
|
||||||
|
|
||||||
struct ArgsVkPipelineBarrier {
|
struct ArgsVkPipelineBarrier {
|
||||||
VkPipelineStageFlags src_stage_mask;
|
VkPipelineStageFlags src_stage_mask;
|
||||||
VkPipelineStageFlags dst_stage_mask;
|
VkPipelineStageFlags dst_stage_mask;
|
||||||
|
|
|
||||||
|
|
@ -682,7 +682,7 @@ class VulkanRenderTargetCache final : public RenderTargetCache {
|
||||||
// need to be dumped). Using 8x16 since that's 128 - the minimum required
|
// need to be dumped). Using 8x16 since that's 128 - the minimum required
|
||||||
// group size on Vulkan, and the maximum number of lanes in a subgroup on
|
// group size on Vulkan, and the maximum number of lanes in a subgroup on
|
||||||
// Vulkan.
|
// Vulkan.
|
||||||
static constexpr uint32_t kDumpSamplesPerGroupX = 8;
|
static constexpr uint32_t kDumpSamplesPerGroupX = 40;
|
||||||
static constexpr uint32_t kDumpSamplesPerGroupY = 16;
|
static constexpr uint32_t kDumpSamplesPerGroupY = 16;
|
||||||
|
|
||||||
union DumpPitches {
|
union DumpPitches {
|
||||||
|
|
|
||||||
|
|
@ -75,6 +75,7 @@ class SDLInputDriver final : public InputDriver, public rex::ui::WindowListener
|
||||||
void HandleEvent(const SDL_Event& event);
|
void HandleEvent(const SDL_Event& event);
|
||||||
std::unique_lock<std::mutex> DrainAndLock();
|
std::unique_lock<std::mutex> DrainAndLock();
|
||||||
void ProcessEventLocked(const SDL_Event& event);
|
void ProcessEventLocked(const SDL_Event& event);
|
||||||
|
void StopRumbleLocked(ControllerState& state);
|
||||||
void OnControllerDeviceAddedLocked(const SDL_Event& event);
|
void OnControllerDeviceAddedLocked(const SDL_Event& event);
|
||||||
void OnControllerDeviceRemovedLocked(const SDL_Event& event);
|
void OnControllerDeviceRemovedLocked(const SDL_Event& event);
|
||||||
void OnControllerDeviceAxisMotionLocked(const SDL_Event& event);
|
void OnControllerDeviceAxisMotionLocked(const SDL_Event& event);
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,11 @@ enum class DrawBucket : uint8_t {
|
||||||
kCopyResolve,
|
kCopyResolve,
|
||||||
kMemexport,
|
kMemexport,
|
||||||
kNoPixelShader,
|
kNoPixelShader,
|
||||||
|
kCopyDump,
|
||||||
|
kCopyResolveShader,
|
||||||
|
kCopyResolveFast32,
|
||||||
|
kCopyResolveFull32,
|
||||||
|
kResolveDownscale,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct DrawFingerprint {
|
struct DrawFingerprint {
|
||||||
|
|
@ -91,6 +96,11 @@ enum class CounterId : uint16_t {
|
||||||
kGpuCopyUs,
|
kGpuCopyUs,
|
||||||
kGpuMemexportUs,
|
kGpuMemexportUs,
|
||||||
kGpuNoPixelShaderUs,
|
kGpuNoPixelShaderUs,
|
||||||
|
kGpuCopyDumpUs,
|
||||||
|
kGpuCopyResolveUs,
|
||||||
|
kGpuCopyResolveFast32Us,
|
||||||
|
kGpuCopyResolveFull32Us,
|
||||||
|
kGpuResolveDownscaleUs,
|
||||||
kGpuTimestampedDraws,
|
kGpuTimestampedDraws,
|
||||||
kGpuCommandProcessorFrameUs,
|
kGpuCommandProcessorFrameUs,
|
||||||
kGpuTimestampedFrames,
|
kGpuTimestampedFrames,
|
||||||
|
|
@ -178,6 +188,37 @@ enum class CounterId : uint16_t {
|
||||||
kRtResolveTransferClearUs,
|
kRtResolveTransferClearUs,
|
||||||
kRtResolveReadbackCalls,
|
kRtResolveReadbackCalls,
|
||||||
kRtResolveReadbackUs,
|
kRtResolveReadbackUs,
|
||||||
|
kRtDumpCalls,
|
||||||
|
kRtDumpRectangles,
|
||||||
|
kRtDumpDispatches,
|
||||||
|
kRtDumpGroups,
|
||||||
|
kRtResolveCopyDispatches,
|
||||||
|
kRtResolveCopyGroups,
|
||||||
|
kRtResolveScaledBytes,
|
||||||
|
kRtScaledResolveWriteBarriers,
|
||||||
|
kRtScaledResolveWriteBarrierOverlaps,
|
||||||
|
kRtScaledResolveWriteBarrierSkipped,
|
||||||
|
kRtScaledResolveWriteBarrierBytes,
|
||||||
|
kRtResolveCopyFast32bpp1x2xMsaaDispatches,
|
||||||
|
kRtResolveCopyFast32bpp1x2xMsaaGroups,
|
||||||
|
kRtResolveCopyFast32bpp4xMsaaDispatches,
|
||||||
|
kRtResolveCopyFast32bpp4xMsaaGroups,
|
||||||
|
kRtResolveCopyFast64bpp1x2xMsaaDispatches,
|
||||||
|
kRtResolveCopyFast64bpp1x2xMsaaGroups,
|
||||||
|
kRtResolveCopyFast64bpp4xMsaaDispatches,
|
||||||
|
kRtResolveCopyFast64bpp4xMsaaGroups,
|
||||||
|
kRtResolveCopyFull8bppDispatches,
|
||||||
|
kRtResolveCopyFull8bppGroups,
|
||||||
|
kRtResolveCopyFull16bppDispatches,
|
||||||
|
kRtResolveCopyFull16bppGroups,
|
||||||
|
kRtResolveCopyFull32bppDispatches,
|
||||||
|
kRtResolveCopyFull32bppGroups,
|
||||||
|
kRtResolveCopyFull64bppDispatches,
|
||||||
|
kRtResolveCopyFull64bppGroups,
|
||||||
|
kRtResolveCopyFull128bppDispatches,
|
||||||
|
kRtResolveCopyFull128bppGroups,
|
||||||
|
kDrawNoPixelDepthTestCalls,
|
||||||
|
kDrawNoPixelDepthWriteCalls,
|
||||||
|
|
||||||
// Audio
|
// Audio
|
||||||
kXmaFramesDecoded,
|
kXmaFramesDecoded,
|
||||||
|
|
|
||||||
|
|
@ -56,6 +56,14 @@ inline constexpr const char* kVulkanLoader = "libvulkan.so.1";
|
||||||
inline constexpr const char* kRenderDoc = "librenderdoc.so";
|
inline constexpr const char* kRenderDoc = "librenderdoc.so";
|
||||||
inline constexpr const char* kSpirvToolsSdkPath = "bin/libSPIRV-Tools-shared.so";
|
inline constexpr const char* kSpirvToolsSdkPath = "bin/libSPIRV-Tools-shared.so";
|
||||||
|
|
||||||
|
#elif REX_PLATFORM_MAC
|
||||||
|
|
||||||
|
inline constexpr const char* kVulkanLoader = "libvulkan.1.dylib";
|
||||||
|
inline constexpr const char* kVulkanLoaderFallback = "libvulkan.dylib";
|
||||||
|
inline constexpr const char* kMoltenVK = "libMoltenVK.dylib";
|
||||||
|
inline constexpr const char* kRenderDoc = "librenderdoc.dylib";
|
||||||
|
inline constexpr const char* kSpirvToolsSdkPath = "bin/libSPIRV-Tools-shared.dylib";
|
||||||
|
|
||||||
#else
|
#else
|
||||||
#error No library names provided for the target platform.
|
#error No library names provided for the target platform.
|
||||||
#endif
|
#endif
|
||||||
|
|
|
||||||
|
|
@ -75,7 +75,10 @@ struct FPSCRPlatform {
|
||||||
return csr;
|
return csr;
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline void setcsr(u32 csr) noexcept { __asm__ __volatile__("msr fpcr, %0" : : "r"(csr)); }
|
static inline void setcsr(u32 csr) noexcept {
|
||||||
|
u64 fpcr = csr;
|
||||||
|
__asm__ __volatile__("msr fpcr, %0" : : "r"(fpcr));
|
||||||
|
}
|
||||||
|
|
||||||
static inline void InitHostExceptions(u32& csr) noexcept {
|
static inline void InitHostExceptions(u32& csr) noexcept {
|
||||||
csr &= ~ExceptionMask; // Clear enable bits to disable exceptions
|
csr &= ~ExceptionMask; // Clear enable bits to disable exceptions
|
||||||
|
|
|
||||||
|
|
@ -205,7 +205,8 @@ struct FPSCRRegister {
|
||||||
Platform::InitHostExceptions(fpu_csr);
|
Platform::InitHostExceptions(fpu_csr);
|
||||||
fpu_csr &= ~FlushMask;
|
fpu_csr &= ~FlushMask;
|
||||||
if (vmx_csr == 0) {
|
if (vmx_csr == 0) {
|
||||||
vmx_csr = ExceptionMask | FlushMask | Platform::GuestToHost[kRoundNearest];
|
vmx_csr = FlushMask | Platform::GuestToHost[kRoundNearest];
|
||||||
|
Platform::InitHostExceptions(vmx_csr);
|
||||||
}
|
}
|
||||||
csr = fpu_csr;
|
csr = fpu_csr;
|
||||||
}
|
}
|
||||||
|
|
@ -216,7 +217,7 @@ struct FPSCRRegister {
|
||||||
fpu_csr &= ~RoundMaskVal;
|
fpu_csr &= ~RoundMaskVal;
|
||||||
fpu_csr |= Platform::GuestToHost[value & kRoundMask];
|
fpu_csr |= Platform::GuestToHost[value & kRoundMask];
|
||||||
fpu_csr &= ~FlushMask;
|
fpu_csr &= ~FlushMask;
|
||||||
fpu_csr |= ExceptionMask;
|
Platform::InitHostExceptions(fpu_csr);
|
||||||
csr = fpu_csr;
|
csr = fpu_csr;
|
||||||
vmx_mode = false;
|
vmx_mode = false;
|
||||||
setcsr(csr);
|
setcsr(csr);
|
||||||
|
|
@ -254,7 +255,8 @@ struct FPSCRRegister {
|
||||||
fpu_csr = getcsr();
|
fpu_csr = getcsr();
|
||||||
Platform::InitHostExceptions(fpu_csr);
|
Platform::InitHostExceptions(fpu_csr);
|
||||||
fpu_csr &= ~FlushMask;
|
fpu_csr &= ~FlushMask;
|
||||||
vmx_csr = ExceptionMask | FlushMask | Platform::GuestToHost[kRoundNearest];
|
vmx_csr = FlushMask | Platform::GuestToHost[kRoundNearest];
|
||||||
|
Platform::InitHostExceptions(vmx_csr);
|
||||||
csr = fpu_csr;
|
csr = fpu_csr;
|
||||||
vmx_mode = false;
|
vmx_mode = false;
|
||||||
setcsr(csr);
|
setcsr(csr);
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,11 @@
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
|
||||||
#include <rex/image_info.h>
|
#include <rex/image_info.h>
|
||||||
|
#include <rex/platform.h>
|
||||||
#include <rex/runtime.h>
|
#include <rex/runtime.h>
|
||||||
|
#if REX_PLATFORM_MAC
|
||||||
|
#include <rex/system/xthread.h>
|
||||||
|
#endif
|
||||||
#include <rex/ui/imgui_dialog.h>
|
#include <rex/ui/imgui_dialog.h>
|
||||||
#include <rex/ui/imgui_drawer.h>
|
#include <rex/ui/imgui_drawer.h>
|
||||||
#include <rex/ui/immediate_drawer.h>
|
#include <rex/ui/immediate_drawer.h>
|
||||||
|
|
@ -221,6 +225,9 @@ class ReXApp : public ui::WindowedApp, public ui::WindowListener, public ui::Win
|
||||||
std::filesystem::path cache_root_;
|
std::filesystem::path cache_root_;
|
||||||
std::unique_ptr<Runtime> runtime_;
|
std::unique_ptr<Runtime> runtime_;
|
||||||
std::unique_ptr<ui::Window> window_;
|
std::unique_ptr<ui::Window> window_;
|
||||||
|
#if REX_PLATFORM_MAC
|
||||||
|
system::object_ref<system::XThread> main_thread_;
|
||||||
|
#endif
|
||||||
std::thread module_thread_;
|
std::thread module_thread_;
|
||||||
std::atomic<bool> shutting_down_{false};
|
std::atomic<bool> shutting_down_{false};
|
||||||
std::unique_ptr<ui::ImmediateDrawer> immediate_drawer_;
|
std::unique_ptr<ui::ImmediateDrawer> immediate_drawer_;
|
||||||
|
|
|
||||||
76
include/rex/std_compat.h
Normal file
76
include/rex/std_compat.h
Normal file
|
|
@ -0,0 +1,76 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
#include <memory>
|
||||||
|
#include <type_traits>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
#ifndef __cpp_lib_move_only_function
|
||||||
|
namespace std {
|
||||||
|
|
||||||
|
template <typename>
|
||||||
|
class move_only_function;
|
||||||
|
|
||||||
|
template <typename R, typename... Args>
|
||||||
|
class move_only_function<R(Args...)> {
|
||||||
|
public:
|
||||||
|
move_only_function() = default;
|
||||||
|
move_only_function(nullptr_t) {}
|
||||||
|
|
||||||
|
move_only_function(const move_only_function&) = delete;
|
||||||
|
move_only_function& operator=(const move_only_function&) = delete;
|
||||||
|
move_only_function(move_only_function&&) noexcept = default;
|
||||||
|
move_only_function& operator=(move_only_function&&) noexcept = default;
|
||||||
|
|
||||||
|
template <typename F>
|
||||||
|
requires(!std::is_same_v<std::remove_cvref_t<F>, move_only_function> &&
|
||||||
|
!std::is_same_v<std::remove_cvref_t<F>, nullptr_t> &&
|
||||||
|
std::is_invocable_r_v<R, F&, Args...>)
|
||||||
|
move_only_function(F&& f)
|
||||||
|
: callable_(std::make_unique<Callable<std::remove_cvref_t<F>>>(std::forward<F>(f))) {}
|
||||||
|
|
||||||
|
move_only_function& operator=(nullptr_t) {
|
||||||
|
callable_.reset();
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
explicit operator bool() const noexcept { return callable_ != nullptr; }
|
||||||
|
|
||||||
|
friend bool operator==(const move_only_function& f, nullptr_t) noexcept {
|
||||||
|
return !static_cast<bool>(f);
|
||||||
|
}
|
||||||
|
friend bool operator==(nullptr_t, const move_only_function& f) noexcept {
|
||||||
|
return !static_cast<bool>(f);
|
||||||
|
}
|
||||||
|
friend bool operator!=(const move_only_function& f, nullptr_t) noexcept {
|
||||||
|
return static_cast<bool>(f);
|
||||||
|
}
|
||||||
|
friend bool operator!=(nullptr_t, const move_only_function& f) noexcept {
|
||||||
|
return static_cast<bool>(f);
|
||||||
|
}
|
||||||
|
|
||||||
|
R operator()(Args... args) {
|
||||||
|
return callable_->Call(std::forward<Args>(args)...);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct CallableBase {
|
||||||
|
virtual ~CallableBase() = default;
|
||||||
|
virtual R Call(Args&&... args) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename F>
|
||||||
|
struct Callable final : CallableBase {
|
||||||
|
explicit Callable(F&& f) : f_(std::move(f)) {}
|
||||||
|
explicit Callable(const F& f) : f_(f) {}
|
||||||
|
|
||||||
|
R Call(Args&&... args) override { return std::invoke(f_, std::forward<Args>(args)...); }
|
||||||
|
|
||||||
|
F f_;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::unique_ptr<CallableBase> callable_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace std
|
||||||
|
#endif
|
||||||
|
|
@ -25,6 +25,7 @@
|
||||||
|
|
||||||
#include <rex/filesystem/vfs.h>
|
#include <rex/filesystem/vfs.h>
|
||||||
#include <rex/logging.h>
|
#include <rex/logging.h>
|
||||||
|
#include <rex/std_compat.h>
|
||||||
#include <rex/system/thread_state.h>
|
#include <rex/system/thread_state.h>
|
||||||
#include <rex/thread/fiber.h>
|
#include <rex/thread/fiber.h>
|
||||||
#include <rex/system/util/native_list.h>
|
#include <rex/system/util/native_list.h>
|
||||||
|
|
@ -278,7 +279,6 @@ class KernelState {
|
||||||
void OnThreadExecute(XThread* thread);
|
void OnThreadExecute(XThread* thread);
|
||||||
void OnThreadExit(XThread* thread);
|
void OnThreadExit(XThread* thread);
|
||||||
object_ref<XThread> GetThreadByID(uint32_t thread_id);
|
object_ref<XThread> GetThreadByID(uint32_t thread_id);
|
||||||
|
|
||||||
FiberInfo* LookupFiber(uint32_t guest_addr);
|
FiberInfo* LookupFiber(uint32_t guest_addr);
|
||||||
void RegisterFiber(uint32_t guest_addr, const FiberInfo& info);
|
void RegisterFiber(uint32_t guest_addr, const FiberInfo& info);
|
||||||
void UnregisterFiber(uint32_t guest_addr);
|
void UnregisterFiber(uint32_t guest_addr);
|
||||||
|
|
|
||||||
|
|
@ -28,15 +28,12 @@ class ByteStream;
|
||||||
|
|
||||||
namespace rex::memory::detail {
|
namespace rex::memory::detail {
|
||||||
|
|
||||||
/// Compensates for Windows 64KB allocation granularity on the 0xE0 physical heap.
|
/// Compensates for host allocation granularities larger than Xenon's 4 KB
|
||||||
/// The backing file maps the 0xE0 heap at a 0x1000-byte offset, but MapViewOfFileEx
|
/// pages on the 0xE0000000 physical heap. The memory views are rounded down to
|
||||||
/// rounds down to 64KB boundaries. Linux mmap handles 4KB offsets natively.
|
/// the host granularity, while the heap tracks the requested 4 KB-aligned guest
|
||||||
constexpr u32 PhysicalHostOffset([[maybe_unused]] u32 guest_addr) noexcept {
|
/// range via host_address_offset.
|
||||||
#if REX_PLATFORM_WIN32
|
inline u32 PhysicalHostOffset(u32 guest_addr) noexcept {
|
||||||
return (guest_addr >= 0xE0000000u) ? 0x1000u : 0u;
|
return (guest_addr >= 0xE0000000u && allocation_granularity() > 0x1000) ? 0x1000u : 0u;
|
||||||
#else
|
|
||||||
return 0u;
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace rex::memory::detail
|
} // namespace rex::memory::detail
|
||||||
|
|
|
||||||
|
|
@ -14,10 +14,13 @@
|
||||||
#include <rex/platform.h>
|
#include <rex/platform.h>
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
|
|
||||||
#if REX_PLATFORM_LINUX || REX_PLATFORM_MAC
|
#if REX_PLATFORM_LINUX
|
||||||
#include <ucontext.h>
|
#include <ucontext.h>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#elif REX_PLATFORM_MAC
|
||||||
|
#include <cstdint>
|
||||||
|
#include <vector>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
namespace rex::thread {
|
namespace rex::thread {
|
||||||
|
|
@ -58,6 +61,14 @@ struct Fiber {
|
||||||
void* arg_ = nullptr;
|
void* arg_ = nullptr;
|
||||||
bool is_thread_fiber_ = false;
|
bool is_thread_fiber_ = false;
|
||||||
|
|
||||||
|
static void Trampoline();
|
||||||
|
#elif REX_PLATFORM_MAC
|
||||||
|
void* context_ = nullptr;
|
||||||
|
std::vector<uint8_t> stack_;
|
||||||
|
void (*entry_)(void*) = nullptr;
|
||||||
|
void* arg_ = nullptr;
|
||||||
|
bool is_thread_fiber_ = false;
|
||||||
|
|
||||||
static void Trampoline();
|
static void Trampoline();
|
||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,8 @@
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
|
#include <rex/std_compat.h>
|
||||||
|
|
||||||
// This is a platform independent implementation of a timer queue similar to
|
// This is a platform independent implementation of a timer queue similar to
|
||||||
// Windows CreateTimerQueueTimer with WT_EXECUTEINTIMERTHREAD.
|
// Windows CreateTimerQueueTimer with WT_EXECUTEINTIMERTHREAD.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -33,11 +33,14 @@ class Surface {
|
||||||
kTypeIndex_XcbWindow,
|
kTypeIndex_XcbWindow,
|
||||||
// Windows.
|
// Windows.
|
||||||
kTypeIndex_Win32Hwnd,
|
kTypeIndex_Win32Hwnd,
|
||||||
|
// macOS.
|
||||||
|
kTypeIndex_SDLMetalView,
|
||||||
};
|
};
|
||||||
using TypeFlags = uint32_t;
|
using TypeFlags = uint32_t;
|
||||||
enum : TypeFlags {
|
enum : TypeFlags {
|
||||||
kTypeFlag_AndroidNativeWindow = TypeFlags(1) << kTypeIndex_AndroidNativeWindow,
|
kTypeFlag_AndroidNativeWindow = TypeFlags(1) << kTypeIndex_AndroidNativeWindow,
|
||||||
kTypeFlag_XcbWindow = TypeFlags(1) << kTypeIndex_XcbWindow,
|
kTypeFlag_XcbWindow = TypeFlags(1) << kTypeIndex_XcbWindow,
|
||||||
|
kTypeFlag_SDLMetalView = TypeFlags(1) << kTypeIndex_SDLMetalView,
|
||||||
kTypeFlag_Win32Hwnd = TypeFlags(1) << kTypeIndex_Win32Hwnd,
|
kTypeFlag_Win32Hwnd = TypeFlags(1) << kTypeIndex_Win32Hwnd,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
31
include/rex/ui/surface_sdl.h
Normal file
31
include/rex/ui/surface_sdl.h
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <rex/ui/surface.h>
|
||||||
|
|
||||||
|
#include <SDL3/SDL_metal.h>
|
||||||
|
#include <SDL3/SDL_video.h>
|
||||||
|
|
||||||
|
namespace rex {
|
||||||
|
namespace ui {
|
||||||
|
|
||||||
|
class SDLMetalViewSurface final : public Surface {
|
||||||
|
public:
|
||||||
|
explicit SDLMetalViewSurface(SDL_Window* window, SDL_MetalView metal_view,
|
||||||
|
void* metal_layer)
|
||||||
|
: window_(window), metal_view_(metal_view), metal_layer_(metal_layer) {}
|
||||||
|
|
||||||
|
TypeIndex GetType() const override { return kTypeIndex_SDLMetalView; }
|
||||||
|
SDL_MetalView metal_view() const { return metal_view_; }
|
||||||
|
void* metal_layer() const { return metal_layer_; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
bool GetSizeImpl(uint32_t& width_out, uint32_t& height_out) const override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
SDL_Window* window_;
|
||||||
|
SDL_MetalView metal_view_;
|
||||||
|
void* metal_layer_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace ui
|
||||||
|
} // namespace rex
|
||||||
|
|
@ -38,6 +38,12 @@
|
||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if REX_PLATFORM_MAC
|
||||||
|
#ifndef VK_USE_PLATFORM_METAL_EXT
|
||||||
|
#define VK_USE_PLATFORM_METAL_EXT
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
#if REX_PLATFORM_WIN32
|
#if REX_PLATFORM_WIN32
|
||||||
#ifndef WIN32_LEAN_AND_MEAN
|
#ifndef WIN32_LEAN_AND_MEAN
|
||||||
#define WIN32_LEAN_AND_MEAN
|
#define WIN32_LEAN_AND_MEAN
|
||||||
|
|
|
||||||
|
|
@ -93,6 +93,8 @@ class VulkanDevice {
|
||||||
VkDeviceSize optimalBufferCopyOffsetAlignment = 1;
|
VkDeviceSize optimalBufferCopyOffsetAlignment = 1;
|
||||||
VkDeviceSize optimalBufferCopyRowPitchAlignment = 1;
|
VkDeviceSize optimalBufferCopyRowPitchAlignment = 1;
|
||||||
VkDeviceSize nonCoherentAtomSize = 256;
|
VkDeviceSize nonCoherentAtomSize = 256;
|
||||||
|
bool timestampComputeAndGraphics = false;
|
||||||
|
float timestampPeriod = 0.0f;
|
||||||
|
|
||||||
bool robustBufferAccess = false;
|
bool robustBufferAccess = false;
|
||||||
bool fullDrawIndexUint32 = false;
|
bool fullDrawIndexUint32 = false;
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@ XE_UI_VULKAN_FUNCTION(vkCmdSetStencilCompareMask)
|
||||||
XE_UI_VULKAN_FUNCTION(vkCmdSetStencilReference)
|
XE_UI_VULKAN_FUNCTION(vkCmdSetStencilReference)
|
||||||
XE_UI_VULKAN_FUNCTION(vkCmdSetStencilWriteMask)
|
XE_UI_VULKAN_FUNCTION(vkCmdSetStencilWriteMask)
|
||||||
XE_UI_VULKAN_FUNCTION(vkCmdSetViewport)
|
XE_UI_VULKAN_FUNCTION(vkCmdSetViewport)
|
||||||
|
XE_UI_VULKAN_FUNCTION(vkCmdWriteTimestamp)
|
||||||
XE_UI_VULKAN_FUNCTION(vkCreateBuffer)
|
XE_UI_VULKAN_FUNCTION(vkCreateBuffer)
|
||||||
XE_UI_VULKAN_FUNCTION(vkCreateBufferView)
|
XE_UI_VULKAN_FUNCTION(vkCreateBufferView)
|
||||||
XE_UI_VULKAN_FUNCTION(vkCreateCommandPool)
|
XE_UI_VULKAN_FUNCTION(vkCreateCommandPool)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
// VK_EXT_metal_surface functions used in ReXGlue.
|
||||||
|
XE_UI_VULKAN_FUNCTION(vkCreateMetalSurfaceEXT)
|
||||||
|
|
@ -66,6 +66,10 @@ class VulkanInstance {
|
||||||
// VK_KHR_win32_surface (#10)
|
// VK_KHR_win32_surface (#10)
|
||||||
#ifdef VK_USE_PLATFORM_WIN32_KHR
|
#ifdef VK_USE_PLATFORM_WIN32_KHR
|
||||||
#include <rex/ui/vulkan/functions/instance_khr_win32_surface.inc>
|
#include <rex/ui/vulkan/functions/instance_khr_win32_surface.inc>
|
||||||
|
#endif
|
||||||
|
// VK_EXT_metal_surface (#218)
|
||||||
|
#ifdef VK_USE_PLATFORM_METAL_EXT
|
||||||
|
#include <rex/ui/vulkan/functions/instance_ext_metal_surface.inc>
|
||||||
#endif
|
#endif
|
||||||
// VK_KHR_get_physical_device_properties2 (#60, promoted to 1.1)
|
// VK_KHR_get_physical_device_properties2 (#60, promoted to 1.1)
|
||||||
#include <rex/ui/vulkan/functions/instance_1_1_khr_get_physical_device_properties2.inc>
|
#include <rex/ui/vulkan/functions/instance_1_1_khr_get_physical_device_properties2.inc>
|
||||||
|
|
@ -91,6 +95,9 @@ class VulkanInstance {
|
||||||
#endif
|
#endif
|
||||||
#ifdef VK_USE_PLATFORM_WIN32_KHR
|
#ifdef VK_USE_PLATFORM_WIN32_KHR
|
||||||
bool ext_KHR_win32_surface = false; // #10
|
bool ext_KHR_win32_surface = false; // #10
|
||||||
|
#endif
|
||||||
|
#ifdef VK_USE_PLATFORM_METAL_EXT
|
||||||
|
bool ext_EXT_metal_surface = false; // #218
|
||||||
#endif
|
#endif
|
||||||
bool ext_1_1_KHR_get_physical_device_properties2 = false; // #60
|
bool ext_1_1_KHR_get_physical_device_properties2 = false; // #60
|
||||||
bool ext_EXT_debug_utils = false; // #129
|
bool ext_EXT_debug_utils = false; // #129
|
||||||
|
|
|
||||||
77
include/rex/ui/window_sdl.h
Normal file
77
include/rex/ui/window_sdl.h
Normal file
|
|
@ -0,0 +1,77 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
#include <atomic>
|
||||||
|
|
||||||
|
#include <rex/ui/menu_item.h>
|
||||||
|
#include <rex/ui/window.h>
|
||||||
|
|
||||||
|
#include <SDL3/SDL_events.h>
|
||||||
|
#include <SDL3/SDL_metal.h>
|
||||||
|
#include <SDL3/SDL_timer.h>
|
||||||
|
#include <SDL3/SDL_video.h>
|
||||||
|
|
||||||
|
namespace rex {
|
||||||
|
namespace ui {
|
||||||
|
|
||||||
|
class SDLWindow final : public Window {
|
||||||
|
using super = Window;
|
||||||
|
|
||||||
|
public:
|
||||||
|
SDLWindow(WindowedAppContext& app_context, const std::string_view title,
|
||||||
|
uint32_t desired_logical_width, uint32_t desired_logical_height);
|
||||||
|
~SDLWindow() override;
|
||||||
|
|
||||||
|
static void HandleSDLEvent(const SDL_Event& event);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
uint32_t GetLatestDpiImpl() const override;
|
||||||
|
bool OpenImpl() override;
|
||||||
|
void RequestCloseImpl() override;
|
||||||
|
|
||||||
|
void ApplyNewFullscreen() override;
|
||||||
|
void ApplyNewTitle() override;
|
||||||
|
void ApplyNewMouseCapture() override;
|
||||||
|
void ApplyNewMouseRelease() override;
|
||||||
|
void ApplyNewCursorVisibility(CursorVisibility old_cursor_visibility) override;
|
||||||
|
void FocusImpl() override;
|
||||||
|
|
||||||
|
std::unique_ptr<Surface> CreateSurfaceImpl(Surface::TypeFlags allowed_types) override;
|
||||||
|
void RequestPaintImpl() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void HandleEvent(const SDL_Event& event);
|
||||||
|
void HandleSizeUpdate(WindowDestructionReceiver& destruction_receiver);
|
||||||
|
void HandleKey(const SDL_KeyboardEvent& event, bool down,
|
||||||
|
WindowDestructionReceiver& destruction_receiver);
|
||||||
|
void HandleMouseButton(const SDL_MouseButtonEvent& event, bool down,
|
||||||
|
WindowDestructionReceiver& destruction_receiver);
|
||||||
|
void HandleMouseMotion(const SDL_MouseMotionEvent& event,
|
||||||
|
WindowDestructionReceiver& destruction_receiver);
|
||||||
|
void HandleMouseWheel(const SDL_MouseWheelEvent& event,
|
||||||
|
WindowDestructionReceiver& destruction_receiver);
|
||||||
|
void RevealAutoHiddenCursor();
|
||||||
|
void SetCursorAutoHideTimer();
|
||||||
|
void RemoveCursorAutoHideTimer();
|
||||||
|
void WindowPointToPhysical(float x, float y, int32_t& physical_x, int32_t& physical_y) const;
|
||||||
|
uint32_t QueryDpi() const;
|
||||||
|
|
||||||
|
SDL_Window* window_ = nullptr;
|
||||||
|
SDL_MetalView metal_view_ = nullptr;
|
||||||
|
void* metal_layer_ = nullptr;
|
||||||
|
std::atomic<bool> paint_event_queued_{false};
|
||||||
|
SDL_TimerID cursor_auto_hide_timer_ = 0;
|
||||||
|
bool cursor_currently_auto_hidden_ = false;
|
||||||
|
uint32_t dpi_ = 96;
|
||||||
|
};
|
||||||
|
|
||||||
|
class SDLMenuItem final : public MenuItem {
|
||||||
|
public:
|
||||||
|
SDLMenuItem(Type type, const std::string& text, const std::string& hotkey,
|
||||||
|
std::function<void()> callback)
|
||||||
|
: MenuItem(type, text, hotkey, std::move(callback)) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace ui
|
||||||
|
} // namespace rex
|
||||||
29
include/rex/ui/windowed_app_context_sdl.h
Normal file
29
include/rex/ui/windowed_app_context_sdl.h
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
|
||||||
|
#include <rex/ui/windowed_app_context.h>
|
||||||
|
|
||||||
|
#include <SDL3/SDL_events.h>
|
||||||
|
|
||||||
|
namespace rex {
|
||||||
|
namespace ui {
|
||||||
|
|
||||||
|
class SDLWindowedAppContext final : public WindowedAppContext {
|
||||||
|
public:
|
||||||
|
SDLWindowedAppContext() = default;
|
||||||
|
~SDLWindowedAppContext() override = default;
|
||||||
|
|
||||||
|
void NotifyUILoopOfPendingFunctions() override;
|
||||||
|
void PlatformQuitFromUIThread() override;
|
||||||
|
|
||||||
|
int RunMainLoop();
|
||||||
|
|
||||||
|
private:
|
||||||
|
void DispatchEvent(const SDL_Event& event);
|
||||||
|
|
||||||
|
std::atomic_bool pending_functions_event_queued_ = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace ui
|
||||||
|
} // namespace rex
|
||||||
|
|
@ -60,9 +60,18 @@ extern PPCFuncMapping {{ project }}_PPCFuncMappings[];
|
||||||
REX_EXTERN(name); \
|
REX_EXTERN(name); \
|
||||||
REX_EXTERN(__imp__##name)
|
REX_EXTERN(__imp__##name)
|
||||||
|
|
||||||
|
#if defined(__APPLE__)
|
||||||
|
#define DEFINE_REX_FUNC(name) \
|
||||||
|
REX_EXTERN(__imp__##name); \
|
||||||
|
REX_WEAK_FUNC(name) { \
|
||||||
|
__imp__##name(ctx, base); \
|
||||||
|
} \
|
||||||
|
REX_EXTERN(__imp__##name)
|
||||||
|
#else
|
||||||
#define DEFINE_REX_FUNC(name) \
|
#define DEFINE_REX_FUNC(name) \
|
||||||
__attribute__((alias("__imp__" #name))) REX_WEAK_FUNC(name); \
|
__attribute__((alias("__imp__" #name))) REX_WEAK_FUNC(name); \
|
||||||
REX_EXTERN(__imp__##name)
|
REX_EXTERN(__imp__##name)
|
||||||
|
#endif
|
||||||
|
|
||||||
//=============================================================================
|
//=============================================================================
|
||||||
// Function Prologue
|
// Function Prologue
|
||||||
|
|
|
||||||
|
|
@ -55,10 +55,19 @@
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifndef DEFINE_REX_FUNC
|
#ifndef DEFINE_REX_FUNC
|
||||||
|
#if defined(__APPLE__)
|
||||||
|
#define DEFINE_REX_FUNC(name) \
|
||||||
|
REX_EXTERN(__imp__##name); \
|
||||||
|
REX_WEAK_FUNC(name) { \
|
||||||
|
__imp__##name(ctx, base); \
|
||||||
|
} \
|
||||||
|
REX_EXTERN(__imp__##name)
|
||||||
|
#else
|
||||||
#define DEFINE_REX_FUNC(name) \
|
#define DEFINE_REX_FUNC(name) \
|
||||||
__attribute__((alias("__imp__" #name))) REX_WEAK_FUNC(name); \
|
__attribute__((alias("__imp__" #name))) REX_WEAK_FUNC(name); \
|
||||||
REX_EXTERN(__imp__##name)
|
REX_EXTERN(__imp__##name)
|
||||||
#endif
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
#if REX_PLATFORM_WIN32
|
#if REX_PLATFORM_WIN32
|
||||||
#define REX_PHYS_HOST_OFFSET(addr) (((u32)(addr) >= 0xE0000000u) ? 0x1000u : 0u)
|
#define REX_PHYS_HOST_OFFSET(addr) (((u32)(addr) >= 0xE0000000u) ? 0x1000u : 0u)
|
||||||
|
|
@ -177,4 +186,3 @@ inline std::atomic<i32>& ppc_global_lock_count_() {
|
||||||
assert(old_count_ >= 1 && "LeaveGlobalLock called without matching EnterGlobalLock"); \
|
assert(old_count_ >= 1 && "LeaveGlobalLock called without matching EnterGlobalLock"); \
|
||||||
rex::thread::global_critical_region::mutex().unlock(); \
|
rex::thread::global_critical_region::mutex().unlock(); \
|
||||||
} while (0)
|
} while (0)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -130,11 +130,10 @@ bool build_vrefp(BuilderContext& ctx) {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool build_vrsqrtefp(BuilderContext& ctx) {
|
bool build_vrsqrtefp(BuilderContext& ctx) {
|
||||||
// Match Xenia's OPCODE_RSQRT lowering. The x86 estimate is not accurate
|
// Match the Xenon reciprocal square-root estimate semantics instead of the
|
||||||
// enough for collision-sensitive code, so use sqrt + divide.
|
// host's exact sqrt/divide behavior.
|
||||||
ctx.emit_set_flush_mode(true);
|
ctx.emit_set_flush_mode(true);
|
||||||
ctx.emit_vec_fp_unary_expr(
|
ctx.emit_vec_fp_unary_expr("rex::ppc::simde_mm_vrsqrtefp_ps(simde_mm_load_ps({vA}.f32))");
|
||||||
"simde_mm_div_ps(simde_mm_set1_ps(1.0f), simde_mm_sqrt_ps(simde_mm_load_ps({vA}.f32)))");
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -76,7 +76,10 @@ target_include_directories(rexcore PRIVATE
|
||||||
)
|
)
|
||||||
|
|
||||||
if(UNIX)
|
if(UNIX)
|
||||||
target_link_libraries(rexcore PRIVATE pthread rt dl)
|
target_link_libraries(rexcore PRIVATE pthread dl)
|
||||||
|
if(NOT APPLE)
|
||||||
|
target_link_libraries(rexcore PRIVATE rt)
|
||||||
|
endif()
|
||||||
elseif(WIN32)
|
elseif(WIN32)
|
||||||
target_link_libraries(rexcore PUBLIC ws2_32)
|
target_link_libraries(rexcore PUBLIC ws2_32)
|
||||||
endif()
|
endif()
|
||||||
|
|
|
||||||
|
|
@ -16,19 +16,27 @@ static_assert(REX_PLATFORM_LINUX || REX_PLATFORM_MAC, "This file is POSIX-only")
|
||||||
|
|
||||||
namespace rex::chrono {
|
namespace rex::chrono {
|
||||||
|
|
||||||
|
#if REX_PLATFORM_MAC
|
||||||
|
constexpr clockid_t kHostClock = CLOCK_MONOTONIC;
|
||||||
|
#else
|
||||||
|
constexpr clockid_t kHostClock = CLOCK_MONOTONIC_RAW;
|
||||||
|
#endif
|
||||||
|
|
||||||
uint64_t Clock::host_tick_frequency_platform() {
|
uint64_t Clock::host_tick_frequency_platform() {
|
||||||
timespec res;
|
timespec res;
|
||||||
int error = clock_getres(CLOCK_MONOTONIC_RAW, &res);
|
int error = clock_getres(kHostClock, &res);
|
||||||
assert_zero(error);
|
assert_zero(error);
|
||||||
assert_zero(res.tv_sec); // Sub second resolution is required.
|
assert_zero(res.tv_sec); // Sub second resolution is required.
|
||||||
|
assert_true(res.tv_nsec > 0);
|
||||||
|
|
||||||
// Convert nano seconds to hertz. Resolution is 1ns on most systems.
|
// host_tick_count_platform returns elapsed nanoseconds, not ticks in units of
|
||||||
return 1000000000ull / res.tv_nsec;
|
// clock_getres. Report the frequency for those returned units.
|
||||||
|
return 1000000000ull;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint64_t Clock::host_tick_count_platform() {
|
uint64_t Clock::host_tick_count_platform() {
|
||||||
timespec tp;
|
timespec tp;
|
||||||
int error = clock_gettime(CLOCK_MONOTONIC_RAW, &tp);
|
int error = clock_gettime(kHostClock, &tp);
|
||||||
assert_zero(error);
|
assert_zero(error);
|
||||||
|
|
||||||
return tp.tv_nsec + tp.tv_sec * 1000000000ull;
|
return tp.tv_nsec + tp.tv_sec * 1000000000ull;
|
||||||
|
|
|
||||||
|
|
@ -49,6 +49,17 @@ std::unordered_map<std::string, size_t>& GetRegistryIndex() {
|
||||||
return index;
|
return index;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::vector<std::pair<std::string, std::string>>& GetCommandLineOverrides() {
|
||||||
|
static std::vector<std::pair<std::string, std::string>> overrides;
|
||||||
|
return overrides;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ApplyCommandLineOverrides() {
|
||||||
|
for (const auto& [name, value] : GetCommandLineOverrides()) {
|
||||||
|
SetFlagByName(name, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Convert flag name to environment variable: gpu_vsync -> REX_GPU_VSYNC
|
// Convert flag name to environment variable: gpu_vsync -> REX_GPU_VSYNC
|
||||||
std::string FlagNameToEnvVar(std::string_view name) {
|
std::string FlagNameToEnvVar(std::string_view name) {
|
||||||
std::string result = "REX_";
|
std::string result = "REX_";
|
||||||
|
|
@ -538,11 +549,19 @@ std::vector<std::string> Init(int argc, char** argv) {
|
||||||
if (entry.type == FlagType::Boolean) {
|
if (entry.type == FlagType::Boolean) {
|
||||||
app.add_flag_function(
|
app.add_flag_function(
|
||||||
"--" + entry.name + ",!--no-" + entry.name,
|
"--" + entry.name + ",!--no-" + entry.name,
|
||||||
[&entry](int64_t count) { entry.setter(count > 0 ? "true" : "false"); },
|
[&entry](int64_t count) {
|
||||||
|
std::string value = count > 0 ? "true" : "false";
|
||||||
|
GetCommandLineOverrides().emplace_back(entry.name, value);
|
||||||
|
entry.setter(value);
|
||||||
|
},
|
||||||
entry.description);
|
entry.description);
|
||||||
} else {
|
} else {
|
||||||
app.add_option_function<std::string>(
|
app.add_option_function<std::string>(
|
||||||
"--" + entry.name, [&entry](const std::string& val) { entry.setter(val); },
|
"--" + entry.name,
|
||||||
|
[&entry](const std::string& val) {
|
||||||
|
GetCommandLineOverrides().emplace_back(entry.name, val);
|
||||||
|
entry.setter(val);
|
||||||
|
},
|
||||||
entry.description);
|
entry.description);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -567,6 +586,8 @@ void LoadConfig(const std::filesystem::path& config_path) {
|
||||||
try {
|
try {
|
||||||
auto config = toml::parse_file(config_path.string());
|
auto config = toml::parse_file(config_path.string());
|
||||||
ApplyTomlTable(config, "");
|
ApplyTomlTable(config, "");
|
||||||
|
ApplyCommandLineOverrides();
|
||||||
|
ApplyEnvironment();
|
||||||
REXLOG_INFO("Loaded config from {}", config_path.string());
|
REXLOG_INFO("Loaded config from {}", config_path.string());
|
||||||
} catch (const toml::parse_error& err) {
|
} catch (const toml::parse_error& err) {
|
||||||
REXLOG_ERROR("Failed to parse config {}: {}", config_path.string(), err.what());
|
REXLOG_ERROR("Failed to parse config {}: {}", config_path.string(), err.what());
|
||||||
|
|
|
||||||
|
|
@ -9,12 +9,17 @@
|
||||||
* @modified Tom Clay, 2026 - Adapted for ReXGlue runtime
|
* @modified Tom Clay, 2026 - Adapted for ReXGlue runtime
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#if defined(__APPLE__) && !defined(_XOPEN_SOURCE)
|
||||||
|
#define _XOPEN_SOURCE 700
|
||||||
|
#endif
|
||||||
|
|
||||||
#include <rex/exception_handler.h>
|
#include <rex/exception_handler.h>
|
||||||
|
|
||||||
#if REX_PLATFORM_LINUX || REX_PLATFORM_MAC
|
#if REX_PLATFORM_LINUX || REX_PLATFORM_MAC
|
||||||
|
|
||||||
#include <signal.h>
|
#include <signal.h>
|
||||||
|
|
||||||
|
#include <cstdlib>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
|
||||||
|
|
@ -30,6 +35,7 @@ namespace rex::arch {
|
||||||
bool signal_handlers_installed_ = false;
|
bool signal_handlers_installed_ = false;
|
||||||
struct sigaction original_sigill_handler_;
|
struct sigaction original_sigill_handler_;
|
||||||
struct sigaction original_sigsegv_handler_;
|
struct sigaction original_sigsegv_handler_;
|
||||||
|
struct sigaction original_sigbus_handler_;
|
||||||
|
|
||||||
// This can be as large as needed, but isn't often needed.
|
// This can be as large as needed, but isn't often needed.
|
||||||
// As we will be sometimes firing many exceptions we want to avoid having to
|
// As we will be sometimes firing many exceptions we want to avoid having to
|
||||||
|
|
@ -40,9 +46,45 @@ constexpr size_t kMaxHandlerCount = 8;
|
||||||
// Executed in order.
|
// Executed in order.
|
||||||
std::pair<ExceptionHandler::Handler, void*> handlers_[kMaxHandlerCount];
|
std::pair<ExceptionHandler::Handler, void*> handlers_[kMaxHandlerCount];
|
||||||
|
|
||||||
|
static void DispatchUnhandledSignal(int signal_number, siginfo_t* signal_info,
|
||||||
|
void* signal_context) {
|
||||||
|
struct sigaction* original_handler = nullptr;
|
||||||
|
if (signal_number == SIGILL) {
|
||||||
|
original_handler = &original_sigill_handler_;
|
||||||
|
} else if (signal_number == SIGSEGV) {
|
||||||
|
original_handler = &original_sigsegv_handler_;
|
||||||
|
} else if (signal_number == SIGBUS) {
|
||||||
|
original_handler = &original_sigbus_handler_;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (original_handler) {
|
||||||
|
if ((original_handler->sa_flags & SA_SIGINFO) && original_handler->sa_sigaction) {
|
||||||
|
original_handler->sa_sigaction(signal_number, signal_info, signal_context);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (original_handler->sa_handler == SIG_IGN) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (original_handler->sa_handler != SIG_DFL && original_handler->sa_handler) {
|
||||||
|
original_handler->sa_handler(signal_number);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sigaction(signal_number, original_handler, nullptr);
|
||||||
|
} else {
|
||||||
|
signal(signal_number, SIG_DFL);
|
||||||
|
}
|
||||||
|
raise(signal_number);
|
||||||
|
std::_Exit(128 + signal_number);
|
||||||
|
}
|
||||||
|
|
||||||
static void ExceptionHandlerCallback(int signal_number, siginfo_t* signal_info,
|
static void ExceptionHandlerCallback(int signal_number, siginfo_t* signal_info,
|
||||||
void* signal_context) {
|
void* signal_context) {
|
||||||
|
#if REX_PLATFORM_MAC
|
||||||
|
mcontext_t mcontext = reinterpret_cast<ucontext_t*>(signal_context)->uc_mcontext;
|
||||||
|
#else
|
||||||
mcontext_t& mcontext = reinterpret_cast<ucontext_t*>(signal_context)->uc_mcontext;
|
mcontext_t& mcontext = reinterpret_cast<ucontext_t*>(signal_context)->uc_mcontext;
|
||||||
|
#endif
|
||||||
|
|
||||||
HostThreadContext thread_context;
|
HostThreadContext thread_context;
|
||||||
|
|
||||||
|
|
@ -70,6 +112,19 @@ static void ExceptionHandlerCallback(int signal_number, siginfo_t* signal_info,
|
||||||
std::memcpy(thread_context.xmm_registers, mcontext.fpregs->_xmm,
|
std::memcpy(thread_context.xmm_registers, mcontext.fpregs->_xmm,
|
||||||
sizeof(thread_context.xmm_registers));
|
sizeof(thread_context.xmm_registers));
|
||||||
#elif REX_ARCH_ARM64
|
#elif REX_ARCH_ARM64
|
||||||
|
#if REX_PLATFORM_MAC
|
||||||
|
auto& thread_state = mcontext->__ss;
|
||||||
|
auto& neon_state = mcontext->__ns;
|
||||||
|
std::memcpy(thread_context.x, thread_state.__x, sizeof(thread_state.__x));
|
||||||
|
thread_context.x[29] = thread_state.__fp;
|
||||||
|
thread_context.x[30] = thread_state.__lr;
|
||||||
|
thread_context.sp = thread_state.__sp;
|
||||||
|
thread_context.pc = thread_state.__pc;
|
||||||
|
thread_context.pstate = thread_state.__cpsr;
|
||||||
|
thread_context.fpsr = neon_state.__fpsr;
|
||||||
|
thread_context.fpcr = neon_state.__fpcr;
|
||||||
|
std::memcpy(thread_context.v, neon_state.__v, sizeof(thread_context.v));
|
||||||
|
#else
|
||||||
std::memcpy(thread_context.x, mcontext.regs, sizeof(thread_context.x));
|
std::memcpy(thread_context.x, mcontext.regs, sizeof(thread_context.x));
|
||||||
thread_context.sp = mcontext.sp;
|
thread_context.sp = mcontext.sp;
|
||||||
thread_context.pc = mcontext.pc;
|
thread_context.pc = mcontext.pc;
|
||||||
|
|
@ -98,6 +153,7 @@ static void ExceptionHandlerCallback(int signal_number, siginfo_t* signal_info,
|
||||||
thread_context.fpcr = mcontext_fpsimd->fpcr;
|
thread_context.fpcr = mcontext_fpsimd->fpcr;
|
||||||
std::memcpy(thread_context.v, mcontext_fpsimd->vregs, sizeof(thread_context.v));
|
std::memcpy(thread_context.v, mcontext_fpsimd->vregs, sizeof(thread_context.v));
|
||||||
}
|
}
|
||||||
|
#endif // REX_PLATFORM_MAC
|
||||||
#endif // REX_ARCH
|
#endif // REX_ARCH
|
||||||
|
|
||||||
Exception ex;
|
Exception ex;
|
||||||
|
|
@ -105,7 +161,8 @@ static void ExceptionHandlerCallback(int signal_number, siginfo_t* signal_info,
|
||||||
case SIGILL:
|
case SIGILL:
|
||||||
ex.InitializeIllegalInstruction(&thread_context);
|
ex.InitializeIllegalInstruction(&thread_context);
|
||||||
break;
|
break;
|
||||||
case SIGSEGV: {
|
case SIGSEGV:
|
||||||
|
case SIGBUS: {
|
||||||
Exception::AccessViolationOperation access_violation_operation;
|
Exception::AccessViolationOperation access_violation_operation;
|
||||||
#if REX_ARCH_AMD64
|
#if REX_ARCH_AMD64
|
||||||
// x86_pf_error_code::X86_PF_WRITE
|
// x86_pf_error_code::X86_PF_WRITE
|
||||||
|
|
@ -114,6 +171,24 @@ static void ExceptionHandlerCallback(int signal_number, siginfo_t* signal_info,
|
||||||
? Exception::AccessViolationOperation::kWrite
|
? Exception::AccessViolationOperation::kWrite
|
||||||
: Exception::AccessViolationOperation::kRead;
|
: Exception::AccessViolationOperation::kRead;
|
||||||
#elif REX_ARCH_ARM64
|
#elif REX_ARCH_ARM64
|
||||||
|
#if REX_PLATFORM_MAC
|
||||||
|
const uint32_t esr = mcontext->__es.__esr;
|
||||||
|
if (((esr >> 26) & 0b111110) == 0b100100) {
|
||||||
|
access_violation_operation = (esr & (UINT64_C(1) << 6))
|
||||||
|
? Exception::AccessViolationOperation::kWrite
|
||||||
|
: Exception::AccessViolationOperation::kRead;
|
||||||
|
} else {
|
||||||
|
bool instruction_is_store;
|
||||||
|
if (IsArm64LoadPrefetchStore(*reinterpret_cast<const uint32_t*>(thread_context.pc),
|
||||||
|
instruction_is_store)) {
|
||||||
|
access_violation_operation = instruction_is_store
|
||||||
|
? Exception::AccessViolationOperation::kWrite
|
||||||
|
: Exception::AccessViolationOperation::kRead;
|
||||||
|
} else {
|
||||||
|
access_violation_operation = Exception::AccessViolationOperation::kUnknown;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#else
|
||||||
// For a Data Abort (EC - ESR_EL1 bits 31:26 - 0b100100 from a lower
|
// For a Data Abort (EC - ESR_EL1 bits 31:26 - 0b100100 from a lower
|
||||||
// Exception Level, 0b100101 without a change in the Exception Level),
|
// Exception Level, 0b100101 without a change in the Exception Level),
|
||||||
// bit 6 is 0 for reading from a memory location, 1 for writing to a
|
// bit 6 is 0 for reading from a memory location, 1 for writing to a
|
||||||
|
|
@ -145,6 +220,7 @@ static void ExceptionHandlerCallback(int signal_number, siginfo_t* signal_info,
|
||||||
access_violation_operation = Exception::AccessViolationOperation::kUnknown;
|
access_violation_operation = Exception::AccessViolationOperation::kUnknown;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#endif // REX_PLATFORM_MAC
|
||||||
#else
|
#else
|
||||||
access_violation_operation = Exception::AccessViolationOperation::kUnknown;
|
access_violation_operation = Exception::AccessViolationOperation::kUnknown;
|
||||||
#endif // REX_ARCH
|
#endif // REX_ARCH
|
||||||
|
|
@ -181,6 +257,34 @@ static void ExceptionHandlerCallback(int signal_number, siginfo_t* signal_info,
|
||||||
&thread_context.xmm_registers[modified_register_index], sizeof(vec128_t));
|
&thread_context.xmm_registers[modified_register_index], sizeof(vec128_t));
|
||||||
}
|
}
|
||||||
#elif REX_ARCH_ARM64
|
#elif REX_ARCH_ARM64
|
||||||
|
#if REX_PLATFORM_MAC
|
||||||
|
auto& thread_state = mcontext->__ss;
|
||||||
|
auto& neon_state = mcontext->__ns;
|
||||||
|
uint32_t modified_register_index;
|
||||||
|
uint32_t modified_x_registers_remaining = ex.modified_x_registers();
|
||||||
|
while (rex::bit_scan_forward(modified_x_registers_remaining, &modified_register_index)) {
|
||||||
|
modified_x_registers_remaining &= ~(UINT32_C(1) << modified_register_index);
|
||||||
|
if (modified_register_index < 29) {
|
||||||
|
thread_state.__x[modified_register_index] =
|
||||||
|
thread_context.x[modified_register_index];
|
||||||
|
} else if (modified_register_index == 29) {
|
||||||
|
thread_state.__fp = thread_context.x[29];
|
||||||
|
} else if (modified_register_index == 30) {
|
||||||
|
thread_state.__lr = thread_context.x[30];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
thread_state.__sp = thread_context.sp;
|
||||||
|
thread_state.__pc = thread_context.pc;
|
||||||
|
thread_state.__cpsr = uint32_t(thread_context.pstate);
|
||||||
|
neon_state.__fpsr = thread_context.fpsr;
|
||||||
|
neon_state.__fpcr = thread_context.fpcr;
|
||||||
|
uint32_t modified_v_registers_remaining = ex.modified_v_registers();
|
||||||
|
while (rex::bit_scan_forward(modified_v_registers_remaining, &modified_register_index)) {
|
||||||
|
modified_v_registers_remaining &= ~(UINT32_C(1) << modified_register_index);
|
||||||
|
std::memcpy(&neon_state.__v[modified_register_index],
|
||||||
|
&thread_context.v[modified_register_index], sizeof(vec128_t));
|
||||||
|
}
|
||||||
|
#else
|
||||||
uint32_t modified_register_index;
|
uint32_t modified_register_index;
|
||||||
uint32_t modified_x_registers_remaining = ex.modified_x_registers();
|
uint32_t modified_x_registers_remaining = ex.modified_x_registers();
|
||||||
while (rex::bit_scan_forward(modified_x_registers_remaining, &modified_register_index)) {
|
while (rex::bit_scan_forward(modified_x_registers_remaining, &modified_register_index)) {
|
||||||
|
|
@ -201,10 +305,13 @@ static void ExceptionHandlerCallback(int signal_number, siginfo_t* signal_info,
|
||||||
mcontext.regs[modified_register_index] = thread_context.x[modified_register_index];
|
mcontext.regs[modified_register_index] = thread_context.x[modified_register_index];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#endif // REX_PLATFORM_MAC
|
||||||
#endif // REX_ARCH
|
#endif // REX_ARCH
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DispatchUnhandledSignal(signal_number, signal_info, signal_context);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ExceptionHandler::Install(Handler fn, void* data) {
|
void ExceptionHandler::Install(Handler fn, void* data) {
|
||||||
|
|
@ -221,6 +328,9 @@ void ExceptionHandler::Install(Handler fn, void* data) {
|
||||||
if (sigaction(SIGSEGV, &signal_handler, &original_sigsegv_handler_) != 0) {
|
if (sigaction(SIGSEGV, &signal_handler, &original_sigsegv_handler_) != 0) {
|
||||||
assert_always("Failed to install new SIGSEGV handler");
|
assert_always("Failed to install new SIGSEGV handler");
|
||||||
}
|
}
|
||||||
|
if (sigaction(SIGBUS, &signal_handler, &original_sigbus_handler_) != 0) {
|
||||||
|
assert_always("Failed to install new SIGBUS handler");
|
||||||
|
}
|
||||||
signal_handlers_installed_ = true;
|
signal_handlers_installed_ = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -261,6 +371,9 @@ void ExceptionHandler::Uninstall(Handler fn, void* data) {
|
||||||
if (sigaction(SIGSEGV, &original_sigsegv_handler_, NULL) != 0) {
|
if (sigaction(SIGSEGV, &original_sigsegv_handler_, NULL) != 0) {
|
||||||
assert_always("Failed to restore original SIGSEGV handler");
|
assert_always("Failed to restore original SIGSEGV handler");
|
||||||
}
|
}
|
||||||
|
if (sigaction(SIGBUS, &original_sigbus_handler_, NULL) != 0) {
|
||||||
|
assert_always("Failed to restore original SIGBUS handler");
|
||||||
|
}
|
||||||
signal_handlers_installed_ = false;
|
signal_handlers_installed_ = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,10 @@
|
||||||
* See LICENSE file in the project root for full license text.
|
* See LICENSE file in the project root for full license text.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#if defined(__APPLE__) && !defined(_XOPEN_SOURCE)
|
||||||
|
#define _XOPEN_SOURCE 700
|
||||||
|
#endif
|
||||||
|
|
||||||
#include <rex/platform.h>
|
#include <rex/platform.h>
|
||||||
#if REX_PLATFORM_LINUX || REX_PLATFORM_MAC
|
#if REX_PLATFORM_LINUX || REX_PLATFORM_MAC
|
||||||
|
|
||||||
|
|
@ -21,9 +25,25 @@ namespace rex::thread {
|
||||||
|
|
||||||
thread_local Fiber* Fiber::tls_current_ = nullptr;
|
thread_local Fiber* Fiber::tls_current_ = nullptr;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
#if REX_PLATFORM_MAC
|
||||||
|
ucontext_t* ToContext(void* context) { return static_cast<ucontext_t*>(context); }
|
||||||
|
#else
|
||||||
|
ucontext_t* ToContext(ucontext_t& context) { return &context; }
|
||||||
|
#endif
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
Fiber* Fiber::ConvertCurrentThread() {
|
Fiber* Fiber::ConvertCurrentThread() {
|
||||||
auto* f = new Fiber();
|
auto* f = new Fiber();
|
||||||
if (getcontext(&f->context_) == -1) {
|
#if REX_PLATFORM_MAC
|
||||||
|
f->context_ = new ucontext_t{};
|
||||||
|
#endif
|
||||||
|
if (getcontext(ToContext(f->context_)) == -1) {
|
||||||
|
#if REX_PLATFORM_MAC
|
||||||
|
delete ToContext(f->context_);
|
||||||
|
#endif
|
||||||
delete f;
|
delete f;
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
@ -37,16 +57,23 @@ Fiber* Fiber::Create(size_t stack_size, void (*entry)(void*), void* arg) {
|
||||||
f->entry_ = entry;
|
f->entry_ = entry;
|
||||||
f->arg_ = arg;
|
f->arg_ = arg;
|
||||||
f->stack_.resize(stack_size);
|
f->stack_.resize(stack_size);
|
||||||
|
#if REX_PLATFORM_MAC
|
||||||
|
f->context_ = new ucontext_t{};
|
||||||
|
#endif
|
||||||
|
|
||||||
if (getcontext(&f->context_) == -1) {
|
auto* context = ToContext(f->context_);
|
||||||
|
if (getcontext(context) == -1) {
|
||||||
|
#if REX_PLATFORM_MAC
|
||||||
|
delete context;
|
||||||
|
#endif
|
||||||
delete f;
|
delete f;
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
f->context_.uc_stack.ss_sp = f->stack_.data();
|
context->uc_stack.ss_sp = f->stack_.data();
|
||||||
f->context_.uc_stack.ss_size = f->stack_.size();
|
context->uc_stack.ss_size = f->stack_.size();
|
||||||
f->context_.uc_link = nullptr;
|
context->uc_link = nullptr;
|
||||||
// Trampoline reads entry_/arg_ from tls_current_ — no pointer splitting needed.
|
// Trampoline reads entry_/arg_ from tls_current_ — no pointer splitting needed.
|
||||||
makecontext(&f->context_, &Fiber::Trampoline, 0);
|
makecontext(context, &Fiber::Trampoline, 0);
|
||||||
return f;
|
return f;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -59,7 +86,7 @@ Fiber* Fiber::Create(size_t stack_size, void (*entry)(void*), void* arg) {
|
||||||
void Fiber::SwitchTo(Fiber* target) {
|
void Fiber::SwitchTo(Fiber* target) {
|
||||||
Fiber* from = tls_current_;
|
Fiber* from = tls_current_;
|
||||||
tls_current_ = target;
|
tls_current_ = target;
|
||||||
swapcontext(&from->context_, &target->context_);
|
swapcontext(ToContext(from->context_), ToContext(target->context_));
|
||||||
}
|
}
|
||||||
|
|
||||||
void Fiber::Destroy() {
|
void Fiber::Destroy() {
|
||||||
|
|
@ -70,6 +97,10 @@ void Fiber::Destroy() {
|
||||||
assert(this != tls_current_ && "Destroy called on the currently running fiber");
|
assert(this != tls_current_ && "Destroy called on the currently running fiber");
|
||||||
}
|
}
|
||||||
// No POSIX equivalent of ConvertFiberToThread; stack_ is freed by the vector destructor.
|
// No POSIX equivalent of ConvertFiberToThread; stack_ is freed by the vector destructor.
|
||||||
|
#if REX_PLATFORM_MAC
|
||||||
|
delete ToContext(context_);
|
||||||
|
context_ = nullptr;
|
||||||
|
#endif
|
||||||
delete this;
|
delete this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,10 +10,13 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
#include <limits.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
#include <sys/stat.h>
|
#include <sys/stat.h>
|
||||||
|
|
@ -23,6 +26,7 @@
|
||||||
#include <rex/assert.h>
|
#include <rex/assert.h>
|
||||||
#include <rex/filesystem.h>
|
#include <rex/filesystem.h>
|
||||||
#include <rex/logging.h>
|
#include <rex/logging.h>
|
||||||
|
#include <rex/platform.h>
|
||||||
#include <rex/string.h>
|
#include <rex/string.h>
|
||||||
|
|
||||||
#include <dirent.h>
|
#include <dirent.h>
|
||||||
|
|
@ -30,6 +34,15 @@
|
||||||
#include <libgen.h>
|
#include <libgen.h>
|
||||||
#include <pwd.h>
|
#include <pwd.h>
|
||||||
|
|
||||||
|
#if REX_PLATFORM_MAC
|
||||||
|
#include <mach-o/dyld.h>
|
||||||
|
|
||||||
|
using off64_t = off_t;
|
||||||
|
#define fseeko64 fseeko
|
||||||
|
#define ftello64 ftello
|
||||||
|
#define ftruncate64 ftruncate
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace rex {
|
namespace rex {
|
||||||
|
|
||||||
std::string path_to_utf8(const std::filesystem::path& path) {
|
std::string path_to_utf8(const std::filesystem::path& path) {
|
||||||
|
|
@ -51,10 +64,31 @@ std::filesystem::path to_path(const std::u16string_view source) {
|
||||||
namespace filesystem {
|
namespace filesystem {
|
||||||
|
|
||||||
std::filesystem::path GetExecutablePath() {
|
std::filesystem::path GetExecutablePath() {
|
||||||
|
#if REX_PLATFORM_MAC
|
||||||
|
uint32_t size = PATH_MAX;
|
||||||
|
std::vector<char> path(size);
|
||||||
|
if (_NSGetExecutablePath(path.data(), &size) != 0) {
|
||||||
|
path.resize(size);
|
||||||
|
if (_NSGetExecutablePath(path.data(), &size) != 0) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
char resolved_path[PATH_MAX] = {};
|
||||||
|
if (realpath(path.data(), resolved_path)) {
|
||||||
|
return resolved_path;
|
||||||
|
}
|
||||||
|
return path.data();
|
||||||
|
#else
|
||||||
char buff[FILENAME_MAX] = "";
|
char buff[FILENAME_MAX] = "";
|
||||||
readlink("/proc/self/exe", buff, FILENAME_MAX);
|
ssize_t length = readlink("/proc/self/exe", buff, FILENAME_MAX - 1);
|
||||||
|
if (length < 0) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
buff[length] = '\0';
|
||||||
std::string s(buff);
|
std::string s(buff);
|
||||||
return s;
|
return s;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
std::filesystem::path GetExecutableFolder() {
|
std::filesystem::path GetExecutableFolder() {
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,13 @@
|
||||||
#include <rex/memory/mapped_memory.h>
|
#include <rex/memory/mapped_memory.h>
|
||||||
#include <rex/platform.h>
|
#include <rex/platform.h>
|
||||||
|
|
||||||
|
#if REX_PLATFORM_MAC
|
||||||
|
using off64_t = off_t;
|
||||||
|
#define stat64 stat
|
||||||
|
#define fstat64 fstat
|
||||||
|
#define ftruncate64 ftruncate
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace rex::memory {
|
namespace rex::memory {
|
||||||
|
|
||||||
class PosixMappedMemory : public MappedMemory {
|
class PosixMappedMemory : public MappedMemory {
|
||||||
|
|
|
||||||
|
|
@ -38,9 +38,30 @@
|
||||||
// #include "xenia/base/main_android.h"
|
// #include "xenia/base/main_android.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if REX_PLATFORM_MAC
|
||||||
|
#define ftruncate64 ftruncate
|
||||||
|
#define mmap64 mmap
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace rex {
|
namespace rex {
|
||||||
namespace memory {
|
namespace memory {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
#if REX_PLATFORM_MAC
|
||||||
|
void AlignHostPageRange(void*& base_address, size_t& length) {
|
||||||
|
const uintptr_t page_mask = uintptr_t(page_size() - 1);
|
||||||
|
const uintptr_t start = reinterpret_cast<uintptr_t>(base_address);
|
||||||
|
const uintptr_t end = start + length;
|
||||||
|
const uintptr_t aligned_start = start & ~page_mask;
|
||||||
|
const uintptr_t aligned_end = (end + page_mask) & ~page_mask;
|
||||||
|
base_address = reinterpret_cast<void*>(aligned_start);
|
||||||
|
length = static_cast<size_t>(aligned_end - aligned_start);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
// Convert filesystem path to valid shm_open name (must start with /, no other slashes)
|
// Convert filesystem path to valid shm_open name (must start with /, no other slashes)
|
||||||
static std::string MakeShmName(const std::filesystem::path& path) {
|
static std::string MakeShmName(const std::filesystem::path& path) {
|
||||||
std::string name = path.string();
|
std::string name = path.string();
|
||||||
|
|
@ -222,6 +243,19 @@ void* AllocFixed(void* base_address, size_t length, AllocationType allocation_ty
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if REX_PLATFORM_MAC
|
||||||
|
if (base_address &&
|
||||||
|
(allocation_type == AllocationType::kCommit ||
|
||||||
|
allocation_type == AllocationType::kReserveCommit)) {
|
||||||
|
void* protect_base = base_address;
|
||||||
|
size_t protect_length = length;
|
||||||
|
AlignHostPageRange(protect_base, protect_length);
|
||||||
|
if (mprotect(protect_base, protect_length, static_cast<int>(prot_requested)) == 0) {
|
||||||
|
return base_address;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
// Build flags - always use MAP_FIXED_NOREPLACE for fixed addresses
|
// Build flags - always use MAP_FIXED_NOREPLACE for fixed addresses
|
||||||
int flags = MAP_PRIVATE | MAP_ANONYMOUS;
|
int flags = MAP_PRIVATE | MAP_ANONYMOUS;
|
||||||
#if defined(MAP_FIXED_NOREPLACE)
|
#if defined(MAP_FIXED_NOREPLACE)
|
||||||
|
|
@ -260,6 +294,9 @@ bool DeallocFixed(void* base_address, size_t length, DeallocationType deallocati
|
||||||
switch (deallocation_type) {
|
switch (deallocation_type) {
|
||||||
case DeallocationType::kDecommit: {
|
case DeallocationType::kDecommit: {
|
||||||
// Decommit: remove access first, then release physical pages
|
// Decommit: remove access first, then release physical pages
|
||||||
|
#if REX_PLATFORM_MAC
|
||||||
|
AlignHostPageRange(base_address, length);
|
||||||
|
#endif
|
||||||
if (mprotect(base_address, length, PROT_NONE) != 0) {
|
if (mprotect(base_address, length, PROT_NONE) != 0) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -298,6 +335,9 @@ bool Protect(void* base_address, size_t length, PageAccess access, PageAccess* o
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
uint32_t prot = ToPosixProtectFlags(access);
|
uint32_t prot = ToPosixProtectFlags(access);
|
||||||
|
#if REX_PLATFORM_MAC
|
||||||
|
AlignHostPageRange(base_address, length);
|
||||||
|
#endif
|
||||||
return mprotect(base_address, length, prot) == 0;
|
return mprotect(base_address, length, prot) == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -112,6 +112,12 @@ bool IsDelayedGpuTimingCounter(size_t counter_index) {
|
||||||
return id == CounterId::kGpuMainUs || id == CounterId::kGpuDepthUs ||
|
return id == CounterId::kGpuMainUs || id == CounterId::kGpuDepthUs ||
|
||||||
id == CounterId::kGpuCopyUs || id == CounterId::kGpuMemexportUs ||
|
id == CounterId::kGpuCopyUs || id == CounterId::kGpuMemexportUs ||
|
||||||
id == CounterId::kGpuNoPixelShaderUs ||
|
id == CounterId::kGpuNoPixelShaderUs ||
|
||||||
|
id == CounterId::kGpuCopyDumpUs ||
|
||||||
|
id == CounterId::kGpuCopyResolveUs ||
|
||||||
|
id == CounterId::kGpuCopyResolveFast32Us ||
|
||||||
|
id == CounterId::kGpuCopyResolveFull32Us ||
|
||||||
|
id == CounterId::kGpuResolveDownscaleUs ||
|
||||||
|
id == CounterId::kGpuTimestampedDraws ||
|
||||||
id == CounterId::kGpuCommandProcessorFrameUs ||
|
id == CounterId::kGpuCommandProcessorFrameUs ||
|
||||||
id == CounterId::kGpuTimestampedFrames ||
|
id == CounterId::kGpuTimestampedFrames ||
|
||||||
id == CounterId::kGpuBarrierUs || id == CounterId::kGpuClearUs ||
|
id == CounterId::kGpuBarrierUs || id == CounterId::kGpuClearUs ||
|
||||||
|
|
@ -158,6 +164,11 @@ constexpr const char* kCounterNames[] = {
|
||||||
"gpu_copy_us",
|
"gpu_copy_us",
|
||||||
"gpu_memexport_us",
|
"gpu_memexport_us",
|
||||||
"gpu_no_pixel_shader_us",
|
"gpu_no_pixel_shader_us",
|
||||||
|
"gpu_copy_dump_us",
|
||||||
|
"gpu_copy_resolve_us",
|
||||||
|
"gpu_copy_resolve_fast32_us",
|
||||||
|
"gpu_copy_resolve_full32_us",
|
||||||
|
"gpu_resolve_downscale_us",
|
||||||
"gpu_timestamped_draws",
|
"gpu_timestamped_draws",
|
||||||
"gpu_command_processor_frame_us",
|
"gpu_command_processor_frame_us",
|
||||||
"gpu_timestamped_frames",
|
"gpu_timestamped_frames",
|
||||||
|
|
@ -245,6 +256,37 @@ constexpr const char* kCounterNames[] = {
|
||||||
"rt_resolve_transfer_clear_us",
|
"rt_resolve_transfer_clear_us",
|
||||||
"rt_resolve_readback_calls",
|
"rt_resolve_readback_calls",
|
||||||
"rt_resolve_readback_us",
|
"rt_resolve_readback_us",
|
||||||
|
"rt_dump_calls",
|
||||||
|
"rt_dump_rectangles",
|
||||||
|
"rt_dump_dispatches",
|
||||||
|
"rt_dump_groups",
|
||||||
|
"rt_resolve_copy_dispatches",
|
||||||
|
"rt_resolve_copy_groups",
|
||||||
|
"rt_resolve_scaled_bytes",
|
||||||
|
"rt_scaled_resolve_write_barriers",
|
||||||
|
"rt_scaled_resolve_write_barrier_overlaps",
|
||||||
|
"rt_scaled_resolve_write_barrier_skipped",
|
||||||
|
"rt_scaled_resolve_write_barrier_bytes",
|
||||||
|
"rt_resolve_copy_fast32_1x2x_dispatches",
|
||||||
|
"rt_resolve_copy_fast32_1x2x_groups",
|
||||||
|
"rt_resolve_copy_fast32_4x_dispatches",
|
||||||
|
"rt_resolve_copy_fast32_4x_groups",
|
||||||
|
"rt_resolve_copy_fast64_1x2x_dispatches",
|
||||||
|
"rt_resolve_copy_fast64_1x2x_groups",
|
||||||
|
"rt_resolve_copy_fast64_4x_dispatches",
|
||||||
|
"rt_resolve_copy_fast64_4x_groups",
|
||||||
|
"rt_resolve_copy_full8_dispatches",
|
||||||
|
"rt_resolve_copy_full8_groups",
|
||||||
|
"rt_resolve_copy_full16_dispatches",
|
||||||
|
"rt_resolve_copy_full16_groups",
|
||||||
|
"rt_resolve_copy_full32_dispatches",
|
||||||
|
"rt_resolve_copy_full32_groups",
|
||||||
|
"rt_resolve_copy_full64_dispatches",
|
||||||
|
"rt_resolve_copy_full64_groups",
|
||||||
|
"rt_resolve_copy_full128_dispatches",
|
||||||
|
"rt_resolve_copy_full128_groups",
|
||||||
|
"draw_no_pixel_depth_test_calls",
|
||||||
|
"draw_no_pixel_depth_write_calls",
|
||||||
"xma_frames_decoded",
|
"xma_frames_decoded",
|
||||||
"audio_frame_latency_us",
|
"audio_frame_latency_us",
|
||||||
"buffer_queue_depth",
|
"buffer_queue_depth",
|
||||||
|
|
@ -300,6 +342,11 @@ constexpr bool kIsGauge[] = {
|
||||||
false, // kGpuCopyUs
|
false, // kGpuCopyUs
|
||||||
false, // kGpuMemexportUs
|
false, // kGpuMemexportUs
|
||||||
false, // kGpuNoPixelShaderUs
|
false, // kGpuNoPixelShaderUs
|
||||||
|
false, // kGpuCopyDumpUs
|
||||||
|
false, // kGpuCopyResolveUs
|
||||||
|
false, // kGpuCopyResolveFast32Us
|
||||||
|
false, // kGpuCopyResolveFull32Us
|
||||||
|
false, // kGpuResolveDownscaleUs
|
||||||
false, // kGpuTimestampedDraws
|
false, // kGpuTimestampedDraws
|
||||||
false, // kGpuCommandProcessorFrameUs
|
false, // kGpuCommandProcessorFrameUs
|
||||||
false, // kGpuTimestampedFrames
|
false, // kGpuTimestampedFrames
|
||||||
|
|
@ -387,6 +434,37 @@ constexpr bool kIsGauge[] = {
|
||||||
false, // kRtResolveTransferClearUs
|
false, // kRtResolveTransferClearUs
|
||||||
false, // kRtResolveReadbackCalls
|
false, // kRtResolveReadbackCalls
|
||||||
false, // kRtResolveReadbackUs
|
false, // kRtResolveReadbackUs
|
||||||
|
false, // kRtDumpCalls
|
||||||
|
false, // kRtDumpRectangles
|
||||||
|
false, // kRtDumpDispatches
|
||||||
|
false, // kRtDumpGroups
|
||||||
|
false, // kRtResolveCopyDispatches
|
||||||
|
false, // kRtResolveCopyGroups
|
||||||
|
false, // kRtResolveScaledBytes
|
||||||
|
false, // kRtScaledResolveWriteBarriers
|
||||||
|
false, // kRtScaledResolveWriteBarrierOverlaps
|
||||||
|
false, // kRtScaledResolveWriteBarrierSkipped
|
||||||
|
false, // kRtScaledResolveWriteBarrierBytes
|
||||||
|
false, // kRtResolveCopyFast32bpp1x2xMsaaDispatches
|
||||||
|
false, // kRtResolveCopyFast32bpp1x2xMsaaGroups
|
||||||
|
false, // kRtResolveCopyFast32bpp4xMsaaDispatches
|
||||||
|
false, // kRtResolveCopyFast32bpp4xMsaaGroups
|
||||||
|
false, // kRtResolveCopyFast64bpp1x2xMsaaDispatches
|
||||||
|
false, // kRtResolveCopyFast64bpp1x2xMsaaGroups
|
||||||
|
false, // kRtResolveCopyFast64bpp4xMsaaDispatches
|
||||||
|
false, // kRtResolveCopyFast64bpp4xMsaaGroups
|
||||||
|
false, // kRtResolveCopyFull8bppDispatches
|
||||||
|
false, // kRtResolveCopyFull8bppGroups
|
||||||
|
false, // kRtResolveCopyFull16bppDispatches
|
||||||
|
false, // kRtResolveCopyFull16bppGroups
|
||||||
|
false, // kRtResolveCopyFull32bppDispatches
|
||||||
|
false, // kRtResolveCopyFull32bppGroups
|
||||||
|
false, // kRtResolveCopyFull64bppDispatches
|
||||||
|
false, // kRtResolveCopyFull64bppGroups
|
||||||
|
false, // kRtResolveCopyFull128bppDispatches
|
||||||
|
false, // kRtResolveCopyFull128bppGroups
|
||||||
|
false, // kDrawNoPixelDepthTestCalls
|
||||||
|
false, // kDrawNoPixelDepthWriteCalls
|
||||||
false, // kXmaFramesDecoded
|
false, // kXmaFramesDecoded
|
||||||
false, // kAudioFrameLatencyUs
|
false, // kAudioFrameLatencyUs
|
||||||
false, // kBufferQueueDepth (set each frame)
|
false, // kBufferQueueDepth (set each frame)
|
||||||
|
|
@ -421,6 +499,16 @@ const char* DrawBucketName(DrawBucket bucket) {
|
||||||
return "MemExp";
|
return "MemExp";
|
||||||
case DrawBucket::kNoPixelShader:
|
case DrawBucket::kNoPixelShader:
|
||||||
return "NoPS";
|
return "NoPS";
|
||||||
|
case DrawBucket::kCopyDump:
|
||||||
|
return "CopyDump";
|
||||||
|
case DrawBucket::kCopyResolveShader:
|
||||||
|
return "CopyResolve";
|
||||||
|
case DrawBucket::kCopyResolveFast32:
|
||||||
|
return "CopyResolveFast32";
|
||||||
|
case DrawBucket::kCopyResolveFull32:
|
||||||
|
return "CopyResolveFull32";
|
||||||
|
case DrawBucket::kResolveDownscale:
|
||||||
|
return "ResolveDownscale";
|
||||||
default:
|
default:
|
||||||
return "Unknown";
|
return "Unknown";
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,12 +24,15 @@ static_assert(REX_PLATFORM_LINUX || REX_PLATFORM_MAC, "This file is POSIX-only")
|
||||||
|
|
||||||
#include <pthread.h>
|
#include <pthread.h>
|
||||||
#include <semaphore.h>
|
#include <semaphore.h>
|
||||||
#include <sys/eventfd.h>
|
|
||||||
#include <sys/syscall.h>
|
|
||||||
#include <sys/time.h>
|
#include <sys/time.h>
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#if REX_PLATFORM_LINUX
|
||||||
|
#include <sys/eventfd.h>
|
||||||
|
#include <sys/syscall.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
#include <rex/assert.h>
|
#include <rex/assert.h>
|
||||||
#include <rex/chrono/chrono_steady_cast.h>
|
#include <rex/chrono/chrono_steady_cast.h>
|
||||||
#include <rex/logging.h>
|
#include <rex/logging.h>
|
||||||
|
|
@ -110,13 +113,37 @@ enum class SignalType {
|
||||||
};
|
};
|
||||||
|
|
||||||
int GetSystemSignal(SignalType num) {
|
int GetSystemSignal(SignalType num) {
|
||||||
|
#if REX_PLATFORM_MAC
|
||||||
|
switch (num) {
|
||||||
|
case SignalType::kThreadSuspend:
|
||||||
|
return SIGUSR1;
|
||||||
|
case SignalType::kThreadUserCallback:
|
||||||
|
return SIGUSR2;
|
||||||
|
default:
|
||||||
|
assert_unhandled_case(num);
|
||||||
|
return SIGUSR2;
|
||||||
|
}
|
||||||
|
#else
|
||||||
auto result = SIGRTMIN + static_cast<int>(num);
|
auto result = SIGRTMIN + static_cast<int>(num);
|
||||||
assert_true(result < SIGRTMAX);
|
assert_true(result < SIGRTMAX);
|
||||||
return result;
|
return result;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
SignalType GetSystemSignalType(int num) {
|
SignalType GetSystemSignalType(int num) {
|
||||||
|
#if REX_PLATFORM_MAC
|
||||||
|
switch (num) {
|
||||||
|
case SIGUSR1:
|
||||||
|
return SignalType::kThreadSuspend;
|
||||||
|
case SIGUSR2:
|
||||||
|
return SignalType::kThreadUserCallback;
|
||||||
|
default:
|
||||||
|
assert_unhandled_case(num);
|
||||||
|
return SignalType::kThreadUserCallback;
|
||||||
|
}
|
||||||
|
#else
|
||||||
return static_cast<SignalType>(num - SIGRTMIN);
|
return static_cast<SignalType>(num - SIGRTMIN);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
std::array<std::atomic<bool>, static_cast<size_t>(SignalType::k_Count)> signal_handler_installed =
|
std::array<std::atomic<bool>, static_cast<size_t>(SignalType::k_Count)> signal_handler_installed =
|
||||||
|
|
@ -145,7 +172,13 @@ void EnableAffinityConfiguration() {}
|
||||||
// uint64_t ticks() { return mach_absolute_time(); }
|
// uint64_t ticks() { return mach_absolute_time(); }
|
||||||
|
|
||||||
uint32_t current_thread_system_id() {
|
uint32_t current_thread_system_id() {
|
||||||
|
#if REX_PLATFORM_MAC
|
||||||
|
uint64_t thread_id;
|
||||||
|
pthread_threadid_np(nullptr, &thread_id);
|
||||||
|
return static_cast<uint32_t>(thread_id);
|
||||||
|
#else
|
||||||
return static_cast<uint32_t>(syscall(SYS_gettid));
|
return static_cast<uint32_t>(syscall(SYS_gettid));
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void MaybeYield() {
|
void MaybeYield() {
|
||||||
|
|
@ -669,7 +702,13 @@ class PosixCondition<Thread> : public PosixConditionBase {
|
||||||
WaitStarted();
|
WaitStarted();
|
||||||
std::unique_lock<std::mutex> lock(state_mutex_);
|
std::unique_lock<std::mutex> lock(state_mutex_);
|
||||||
if (state_ != State::kUninitialized && state_ != State::kFinished) {
|
if (state_ != State::kUninitialized && state_ != State::kFinished) {
|
||||||
|
#if REX_PLATFORM_MAC
|
||||||
|
if (pthread_equal(thread_, pthread_self())) {
|
||||||
|
pthread_setname_np(std::string(name).c_str());
|
||||||
|
}
|
||||||
|
#else
|
||||||
pthread_setname_np(thread_, std::string(name).c_str());
|
pthread_setname_np(thread_, std::string(name).c_str());
|
||||||
|
#endif
|
||||||
#if REX_PLATFORM_ANDROID
|
#if REX_PLATFORM_ANDROID
|
||||||
SetAndroidPreApi26Name(name);
|
SetAndroidPreApi26Name(name);
|
||||||
#endif
|
#endif
|
||||||
|
|
@ -687,9 +726,22 @@ class PosixCondition<Thread> : public PosixConditionBase {
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
uint32_t system_id() const { return static_cast<uint32_t>(thread_); }
|
uint32_t system_id() const {
|
||||||
|
#if REX_PLATFORM_MAC
|
||||||
|
uint64_t thread_id;
|
||||||
|
if (pthread_threadid_np(thread_, &thread_id) != 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return static_cast<uint32_t>(thread_id);
|
||||||
|
#else
|
||||||
|
return static_cast<uint32_t>(thread_);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
uint64_t affinity_mask() {
|
uint64_t affinity_mask() {
|
||||||
|
#if REX_PLATFORM_MAC
|
||||||
|
return 0;
|
||||||
|
#else
|
||||||
WaitStarted();
|
WaitStarted();
|
||||||
cpu_set_t cpu_set;
|
cpu_set_t cpu_set;
|
||||||
#if REX_PLATFORM_ANDROID
|
#if REX_PLATFORM_ANDROID
|
||||||
|
|
@ -708,9 +760,13 @@ class PosixCondition<Thread> : public PosixConditionBase {
|
||||||
result |= set << i;
|
result |= set << i;
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void set_affinity_mask(uint64_t mask) {
|
void set_affinity_mask(uint64_t mask) {
|
||||||
|
#if REX_PLATFORM_MAC
|
||||||
|
(void)mask;
|
||||||
|
#else
|
||||||
WaitStarted();
|
WaitStarted();
|
||||||
cpu_set_t cpu_set;
|
cpu_set_t cpu_set;
|
||||||
CPU_ZERO(&cpu_set);
|
CPU_ZERO(&cpu_set);
|
||||||
|
|
@ -727,6 +783,7 @@ class PosixCondition<Thread> : public PosixConditionBase {
|
||||||
if (pthread_setaffinity_np(thread_, sizeof(cpu_set_t), &cpu_set) != 0) {
|
if (pthread_setaffinity_np(thread_, sizeof(cpu_set_t), &cpu_set) != 0) {
|
||||||
assert_always();
|
assert_always();
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -784,6 +841,9 @@ class PosixCondition<Thread> : public PosixConditionBase {
|
||||||
#if REX_PLATFORM_ANDROID
|
#if REX_PLATFORM_ANDROID
|
||||||
int result = sigqueue(pthread_gettid_np(thread_),
|
int result = sigqueue(pthread_gettid_np(thread_),
|
||||||
GetSystemSignal(SignalType::kThreadUserCallback), value);
|
GetSystemSignal(SignalType::kThreadUserCallback), value);
|
||||||
|
#elif REX_PLATFORM_MAC
|
||||||
|
(void)value;
|
||||||
|
int result = pthread_kill(thread_, GetSystemSignal(SignalType::kThreadUserCallback));
|
||||||
#else
|
#else
|
||||||
int result = pthread_sigqueue(thread_, GetSystemSignal(SignalType::kThreadUserCallback), value);
|
int result = pthread_sigqueue(thread_, GetSystemSignal(SignalType::kThreadUserCallback), value);
|
||||||
#endif
|
#endif
|
||||||
|
|
@ -1394,7 +1454,11 @@ void Thread::Exit(int exit_code) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void set_current_thread_name(const std::string_view name) {
|
void set_current_thread_name(const std::string_view name) {
|
||||||
|
#if REX_PLATFORM_MAC
|
||||||
|
pthread_setname_np(std::string(name).c_str());
|
||||||
|
#else
|
||||||
pthread_setname_np(pthread_self(), std::string(name).c_str());
|
pthread_setname_np(pthread_self(), std::string(name).c_str());
|
||||||
|
#endif
|
||||||
#if REX_PLATFORM_ANDROID
|
#if REX_PLATFORM_ANDROID
|
||||||
if (!android_pthread_getname_np_ && current_thread_) {
|
if (!android_pthread_getname_np_ && current_thread_) {
|
||||||
current_thread_->condition().SetAndroidPreApi26Name(name);
|
current_thread_->condition().SetAndroidPreApi26Name(name);
|
||||||
|
|
|
||||||
|
|
@ -330,6 +330,8 @@ X_STATUS GraphicsSystem::SetupGuestGpu(runtime::FunctionDispatcher* function_dis
|
||||||
}
|
}
|
||||||
|
|
||||||
void GraphicsSystem::Shutdown() {
|
void GraphicsSystem::Shutdown() {
|
||||||
|
shutting_down_.store(true, std::memory_order_release);
|
||||||
|
|
||||||
if (command_processor_) {
|
if (command_processor_) {
|
||||||
EndTracing();
|
EndTracing();
|
||||||
command_processor_->Shutdown();
|
command_processor_->Shutdown();
|
||||||
|
|
@ -447,7 +449,7 @@ void GraphicsSystem::SetInterruptCallback(uint32_t callback, uint32_t user_data)
|
||||||
}
|
}
|
||||||
|
|
||||||
void GraphicsSystem::DispatchInterruptCallback(uint32_t source, uint32_t cpu) {
|
void GraphicsSystem::DispatchInterruptCallback(uint32_t source, uint32_t cpu) {
|
||||||
if (!interrupt_callback_) {
|
if (shutting_down_.load(std::memory_order_acquire) || !interrupt_callback_) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -145,7 +145,9 @@ void SpirvShaderTranslator::Reset() {
|
||||||
var_main_memexport_data_written_ = spv::NoResult;
|
var_main_memexport_data_written_ = spv::NoResult;
|
||||||
main_memexport_allowed_ = spv::NoResult;
|
main_memexport_allowed_ = spv::NoResult;
|
||||||
var_main_rectangle_guest_vertex_index_ = spv::NoResult;
|
var_main_rectangle_guest_vertex_index_ = spv::NoResult;
|
||||||
var_main_rectangle_per_vertex_ = spv::NoResult;
|
var_main_rectangle_position_ = spv::NoResult;
|
||||||
|
var_main_rectangle_clip_distances_ = spv::NoResult;
|
||||||
|
var_main_rectangle_cull_distances_ = spv::NoResult;
|
||||||
std::fill(var_main_rectangle_interpolators_.begin(), var_main_rectangle_interpolators_.end(),
|
std::fill(var_main_rectangle_interpolators_.begin(), var_main_rectangle_interpolators_.end(),
|
||||||
spv::NoResult);
|
spv::NoResult);
|
||||||
var_main_point_size_edge_flag_kill_vertex_ = spv::NoResult;
|
var_main_point_size_edge_flag_kill_vertex_ = spv::NoResult;
|
||||||
|
|
@ -514,11 +516,34 @@ void SpirvShaderTranslator::StartTranslation() {
|
||||||
var_main_rectangle_guest_vertex_index_ =
|
var_main_rectangle_guest_vertex_index_ =
|
||||||
builder_->createVariable(spv::NoPrecision, spv::StorageClassFunction, type_uint_,
|
builder_->createVariable(spv::NoPrecision, spv::StorageClassFunction, type_uint_,
|
||||||
"xe_var_rectangle_guest_vertex_index", const_uint_0_);
|
"xe_var_rectangle_guest_vertex_index", const_uint_0_);
|
||||||
spv::Id type_rectangle_per_vertex_array =
|
spv::Id type_rectangle_position_array =
|
||||||
builder_->makeArrayType(type_output_per_vertex_, builder_->makeUintConstant(3), 0);
|
builder_->makeArrayType(type_float4_, builder_->makeUintConstant(3), sizeof(float) * 4);
|
||||||
var_main_rectangle_per_vertex_ =
|
builder_->addDecoration(type_rectangle_position_array, spv::DecorationArrayStride,
|
||||||
builder_->createVariable(spv::NoPrecision, spv::StorageClassFunction,
|
sizeof(float) * 4);
|
||||||
type_rectangle_per_vertex_array, "xe_var_rectangle_per_vertex");
|
var_main_rectangle_position_ = builder_->createVariable(
|
||||||
|
spv::NoPrecision, spv::StorageClassFunction, type_rectangle_position_array,
|
||||||
|
"xe_var_rectangle_position");
|
||||||
|
Modification shader_modification = GetSpirvShaderModification();
|
||||||
|
uint32_t clip_distance_count = shader_modification.GetVertexClipDistanceCount();
|
||||||
|
if (clip_distance_count) {
|
||||||
|
spv::Id type_rectangle_clip_distance_array = builder_->makeArrayType(
|
||||||
|
type_float_, builder_->makeUintConstant(3 * clip_distance_count), sizeof(float));
|
||||||
|
builder_->addDecoration(type_rectangle_clip_distance_array, spv::DecorationArrayStride,
|
||||||
|
sizeof(float));
|
||||||
|
var_main_rectangle_clip_distances_ = builder_->createVariable(
|
||||||
|
spv::NoPrecision, spv::StorageClassFunction, type_rectangle_clip_distance_array,
|
||||||
|
"xe_var_rectangle_clip_distances");
|
||||||
|
}
|
||||||
|
uint32_t cull_distance_count = shader_modification.GetVertexCullDistanceCount();
|
||||||
|
if (cull_distance_count) {
|
||||||
|
spv::Id type_rectangle_cull_distance_array = builder_->makeArrayType(
|
||||||
|
type_float_, builder_->makeUintConstant(3 * cull_distance_count), sizeof(float));
|
||||||
|
builder_->addDecoration(type_rectangle_cull_distance_array, spv::DecorationArrayStride,
|
||||||
|
sizeof(float));
|
||||||
|
var_main_rectangle_cull_distances_ = builder_->createVariable(
|
||||||
|
spv::NoPrecision, spv::StorageClassFunction, type_rectangle_cull_distance_array,
|
||||||
|
"xe_var_rectangle_cull_distances");
|
||||||
|
}
|
||||||
uint32_t interpolators_remaining = GetModificationInterpolatorMask();
|
uint32_t interpolators_remaining = GetModificationInterpolatorMask();
|
||||||
uint32_t interpolator_index;
|
uint32_t interpolator_index;
|
||||||
while (rex::bit_scan_forward(interpolators_remaining, &interpolator_index)) {
|
while (rex::bit_scan_forward(interpolators_remaining, &interpolator_index)) {
|
||||||
|
|
@ -693,13 +718,51 @@ std::vector<uint8_t> SpirvShaderTranslator::CompleteTranslation() {
|
||||||
spv::Id rectangle_guest_vertex_index_int =
|
spv::Id rectangle_guest_vertex_index_int =
|
||||||
builder_->createUnaryOp(spv::OpBitcast, type_int_, rectangle_guest_vertex_index);
|
builder_->createUnaryOp(spv::OpBitcast, type_int_, rectangle_guest_vertex_index);
|
||||||
|
|
||||||
// Store current iteration outputs.
|
// Store current iteration outputs. Keep system values in plain function
|
||||||
|
// arrays rather than copying the gl_PerVertex block because SPIRV-Cross
|
||||||
|
// emits invalid MSL for gl_PerVertex used as a local function type.
|
||||||
|
id_vector_temp_.clear();
|
||||||
|
id_vector_temp_.push_back(builder_->makeIntConstant(kOutputPerVertexMemberPosition));
|
||||||
|
spv::Id rectangle_output_position = builder_->createLoad(
|
||||||
|
builder_->createAccessChain(spv::StorageClassOutput, output_per_vertex_, id_vector_temp_),
|
||||||
|
spv::NoPrecision);
|
||||||
id_vector_temp_.clear();
|
id_vector_temp_.clear();
|
||||||
id_vector_temp_.push_back(rectangle_guest_vertex_index_int);
|
id_vector_temp_.push_back(rectangle_guest_vertex_index_int);
|
||||||
builder_->createStore(
|
builder_->createStore(rectangle_output_position,
|
||||||
builder_->createLoad(output_per_vertex_, spv::NoPrecision),
|
builder_->createAccessChain(spv::StorageClassFunction,
|
||||||
builder_->createAccessChain(spv::StorageClassFunction, var_main_rectangle_per_vertex_,
|
var_main_rectangle_position_,
|
||||||
id_vector_temp_));
|
id_vector_temp_));
|
||||||
|
auto store_rectangle_distance_member =
|
||||||
|
[this, rectangle_guest_vertex_index_int](uint32_t output_member_index,
|
||||||
|
uint32_t component_count, spv::Id scratch_var) {
|
||||||
|
for (uint32_t i = 0; i < component_count; ++i) {
|
||||||
|
id_vector_temp_.clear();
|
||||||
|
id_vector_temp_.push_back(builder_->makeIntConstant(int(output_member_index)));
|
||||||
|
id_vector_temp_.push_back(builder_->makeIntConstant(int(i)));
|
||||||
|
spv::Id output_value = builder_->createLoad(
|
||||||
|
builder_->createAccessChain(spv::StorageClassOutput, output_per_vertex_,
|
||||||
|
id_vector_temp_),
|
||||||
|
spv::NoPrecision);
|
||||||
|
spv::Id scratch_index = builder_->createBinOp(
|
||||||
|
spv::OpIAdd, type_int_,
|
||||||
|
builder_->createBinOp(spv::OpIMul, type_int_, rectangle_guest_vertex_index_int,
|
||||||
|
builder_->makeIntConstant(int(component_count))),
|
||||||
|
builder_->makeIntConstant(int(i)));
|
||||||
|
id_vector_temp_.clear();
|
||||||
|
id_vector_temp_.push_back(scratch_index);
|
||||||
|
builder_->createStore(output_value,
|
||||||
|
builder_->createAccessChain(spv::StorageClassFunction,
|
||||||
|
scratch_var, id_vector_temp_));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if (clip_distance_count && var_main_rectangle_clip_distances_ != spv::NoResult) {
|
||||||
|
store_rectangle_distance_member(output_per_vertex_member_clip_distance_,
|
||||||
|
clip_distance_count, var_main_rectangle_clip_distances_);
|
||||||
|
}
|
||||||
|
if (cull_distance_count && var_main_rectangle_cull_distances_ != spv::NoResult) {
|
||||||
|
store_rectangle_distance_member(output_per_vertex_member_cull_distance_,
|
||||||
|
cull_distance_count, var_main_rectangle_cull_distances_);
|
||||||
|
}
|
||||||
uint32_t interpolators_remaining = GetModificationInterpolatorMask();
|
uint32_t interpolators_remaining = GetModificationInterpolatorMask();
|
||||||
uint32_t interpolator_index;
|
uint32_t interpolator_index;
|
||||||
while (rex::bit_scan_forward(interpolators_remaining, &interpolator_index)) {
|
while (rex::bit_scan_forward(interpolators_remaining, &interpolator_index)) {
|
||||||
|
|
@ -884,7 +947,7 @@ std::vector<uint8_t> SpirvShaderTranslator::CompleteTranslation() {
|
||||||
}
|
}
|
||||||
builder_->createBranch(main_loop_continue_);
|
builder_->createBranch(main_loop_continue_);
|
||||||
}
|
}
|
||||||
if_not_last_guest_vertex.makeBeginElse();
|
if_not_last_guest_vertex.makeBeginElse(false);
|
||||||
{
|
{
|
||||||
spv::Id rectangle_host_vertex_in_primitive = builder_->createBinOp(
|
spv::Id rectangle_host_vertex_in_primitive = builder_->createBinOp(
|
||||||
spv::OpBitwiseAnd, type_uint_,
|
spv::OpBitwiseAnd, type_uint_,
|
||||||
|
|
@ -903,10 +966,9 @@ std::vector<uint8_t> SpirvShaderTranslator::CompleteTranslation() {
|
||||||
auto load_rectangle_position_xy = [this](spv::Id vertex_index, uint32_t component) {
|
auto load_rectangle_position_xy = [this](spv::Id vertex_index, uint32_t component) {
|
||||||
id_vector_temp_.clear();
|
id_vector_temp_.clear();
|
||||||
id_vector_temp_.push_back(vertex_index);
|
id_vector_temp_.push_back(vertex_index);
|
||||||
id_vector_temp_.push_back(builder_->makeIntConstant(kOutputPerVertexMemberPosition));
|
|
||||||
id_vector_temp_.push_back(builder_->makeIntConstant(int(component)));
|
id_vector_temp_.push_back(builder_->makeIntConstant(int(component)));
|
||||||
return builder_->createLoad(
|
return builder_->createLoad(
|
||||||
builder_->createAccessChain(spv::StorageClassFunction, var_main_rectangle_per_vertex_,
|
builder_->createAccessChain(spv::StorageClassFunction, var_main_rectangle_position_,
|
||||||
id_vector_temp_),
|
id_vector_temp_),
|
||||||
spv::NoPrecision);
|
spv::NoPrecision);
|
||||||
};
|
};
|
||||||
|
|
@ -1002,17 +1064,27 @@ std::vector<uint8_t> SpirvShaderTranslator::CompleteTranslation() {
|
||||||
input_output_interpolators_[interpolator_index]);
|
input_output_interpolators_[interpolator_index]);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto load_rectangle_per_vertex_component =
|
auto load_rectangle_position = [this](spv::Id vertex_index) {
|
||||||
[this](spv::Id vertex_index, uint32_t member_index, spv::Id component_index) {
|
id_vector_temp_.clear();
|
||||||
|
id_vector_temp_.push_back(vertex_index);
|
||||||
|
return builder_->createLoad(
|
||||||
|
builder_->createAccessChain(spv::StorageClassFunction, var_main_rectangle_position_,
|
||||||
|
id_vector_temp_),
|
||||||
|
spv::NoPrecision);
|
||||||
|
};
|
||||||
|
auto load_rectangle_distance_component =
|
||||||
|
[this](spv::Id vertex_index, uint32_t component_count, spv::Id component_index,
|
||||||
|
spv::Id scratch_var) {
|
||||||
|
spv::Id scratch_index = builder_->createBinOp(
|
||||||
|
spv::OpIAdd, type_int_,
|
||||||
|
builder_->createBinOp(spv::OpIMul, type_int_, vertex_index,
|
||||||
|
builder_->makeIntConstant(int(component_count))),
|
||||||
|
component_index);
|
||||||
id_vector_temp_.clear();
|
id_vector_temp_.clear();
|
||||||
id_vector_temp_.push_back(vertex_index);
|
id_vector_temp_.push_back(scratch_index);
|
||||||
id_vector_temp_.push_back(builder_->makeIntConstant(int(member_index)));
|
|
||||||
if (component_index != spv::NoResult) {
|
|
||||||
id_vector_temp_.push_back(component_index);
|
|
||||||
}
|
|
||||||
return builder_->createLoad(
|
return builder_->createLoad(
|
||||||
builder_->createAccessChain(spv::StorageClassFunction,
|
builder_->createAccessChain(spv::StorageClassFunction, scratch_var,
|
||||||
var_main_rectangle_per_vertex_, id_vector_temp_),
|
id_vector_temp_),
|
||||||
spv::NoPrecision);
|
spv::NoPrecision);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -1020,12 +1092,9 @@ std::vector<uint8_t> SpirvShaderTranslator::CompleteTranslation() {
|
||||||
id_vector_temp_.push_back(builder_->makeIntConstant(kOutputPerVertexMemberPosition));
|
id_vector_temp_.push_back(builder_->makeIntConstant(kOutputPerVertexMemberPosition));
|
||||||
spv::Id rectangle_position_ptr = builder_->createAccessChain(
|
spv::Id rectangle_position_ptr = builder_->createAccessChain(
|
||||||
spv::StorageClassOutput, output_per_vertex_, id_vector_temp_);
|
spv::StorageClassOutput, output_per_vertex_, id_vector_temp_);
|
||||||
spv::Id position_0 = load_rectangle_per_vertex_component(
|
spv::Id position_0 = load_rectangle_position(rectangle_vertex_indices[0]);
|
||||||
rectangle_vertex_indices[0], kOutputPerVertexMemberPosition, spv::NoResult);
|
spv::Id position_1 = load_rectangle_position(rectangle_vertex_indices[1]);
|
||||||
spv::Id position_1 = load_rectangle_per_vertex_component(
|
spv::Id position_2 = load_rectangle_position(rectangle_vertex_indices[2]);
|
||||||
rectangle_vertex_indices[1], kOutputPerVertexMemberPosition, spv::NoResult);
|
|
||||||
spv::Id position_2 = load_rectangle_per_vertex_component(
|
|
||||||
rectangle_vertex_indices[2], kOutputPerVertexMemberPosition, spv::NoResult);
|
|
||||||
spv::Id position_original =
|
spv::Id position_original =
|
||||||
select_rectangle_vertex_component(position_0, position_1, position_2, type_float4_);
|
select_rectangle_vertex_component(position_0, position_1, position_2, type_float4_);
|
||||||
spv::Id position_synthetic = builder_->createNoContractionBinOp(
|
spv::Id position_synthetic = builder_->createNoContractionBinOp(
|
||||||
|
|
@ -1039,16 +1108,17 @@ std::vector<uint8_t> SpirvShaderTranslator::CompleteTranslation() {
|
||||||
|
|
||||||
auto write_rectangle_distance_member =
|
auto write_rectangle_distance_member =
|
||||||
[this, &select_rectangle_vertex_component, rectangle_host_vertex_is_synthetic,
|
[this, &select_rectangle_vertex_component, rectangle_host_vertex_is_synthetic,
|
||||||
&load_rectangle_per_vertex_component,
|
&load_rectangle_distance_component,
|
||||||
rectangle_vertex_indices](uint32_t member_index, uint32_t component_count) {
|
rectangle_vertex_indices](uint32_t member_index, uint32_t component_count,
|
||||||
|
spv::Id scratch_var) {
|
||||||
for (uint32_t i = 0; i < component_count; ++i) {
|
for (uint32_t i = 0; i < component_count; ++i) {
|
||||||
spv::Id component_index = builder_->makeIntConstant(int(i));
|
spv::Id component_index = builder_->makeIntConstant(int(i));
|
||||||
spv::Id value_0 = load_rectangle_per_vertex_component(
|
spv::Id value_0 = load_rectangle_distance_component(
|
||||||
rectangle_vertex_indices[0], member_index, component_index);
|
rectangle_vertex_indices[0], component_count, component_index, scratch_var);
|
||||||
spv::Id value_1 = load_rectangle_per_vertex_component(
|
spv::Id value_1 = load_rectangle_distance_component(
|
||||||
rectangle_vertex_indices[1], member_index, component_index);
|
rectangle_vertex_indices[1], component_count, component_index, scratch_var);
|
||||||
spv::Id value_2 = load_rectangle_per_vertex_component(
|
spv::Id value_2 = load_rectangle_distance_component(
|
||||||
rectangle_vertex_indices[2], member_index, component_index);
|
rectangle_vertex_indices[2], component_count, component_index, scratch_var);
|
||||||
spv::Id value_original =
|
spv::Id value_original =
|
||||||
select_rectangle_vertex_component(value_0, value_1, value_2, type_float_);
|
select_rectangle_vertex_component(value_0, value_1, value_2, type_float_);
|
||||||
spv::Id value_synthetic = builder_->createNoContractionBinOp(
|
spv::Id value_synthetic = builder_->createNoContractionBinOp(
|
||||||
|
|
@ -1069,11 +1139,11 @@ std::vector<uint8_t> SpirvShaderTranslator::CompleteTranslation() {
|
||||||
|
|
||||||
if (clip_distance_count && output_per_vertex_member_clip_distance_ != UINT32_MAX) {
|
if (clip_distance_count && output_per_vertex_member_clip_distance_ != UINT32_MAX) {
|
||||||
write_rectangle_distance_member(output_per_vertex_member_clip_distance_,
|
write_rectangle_distance_member(output_per_vertex_member_clip_distance_,
|
||||||
clip_distance_count);
|
clip_distance_count, var_main_rectangle_clip_distances_);
|
||||||
}
|
}
|
||||||
if (cull_distance_count && output_per_vertex_member_cull_distance_ != UINT32_MAX) {
|
if (cull_distance_count && output_per_vertex_member_cull_distance_ != UINT32_MAX) {
|
||||||
write_rectangle_distance_member(output_per_vertex_member_cull_distance_,
|
write_rectangle_distance_member(output_per_vertex_member_cull_distance_,
|
||||||
cull_distance_count);
|
cull_distance_count, var_main_rectangle_cull_distances_);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if_not_last_guest_vertex.makeEndIf();
|
if_not_last_guest_vertex.makeEndIf();
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -314,6 +314,11 @@ void DeferredCommandBuffer::Execute(VkCommandBuffer command_buffer) {
|
||||||
rex::align(sizeof(ArgsVkSetViewport), alignof(VkViewport))));
|
rex::align(sizeof(ArgsVkSetViewport), alignof(VkViewport))));
|
||||||
} break;
|
} break;
|
||||||
|
|
||||||
|
case Command::kVkWriteTimestamp: {
|
||||||
|
auto& args = *reinterpret_cast<const ArgsVkWriteTimestamp*>(stream);
|
||||||
|
dfn.vkCmdWriteTimestamp(command_buffer, args.pipeline_stage, args.query_pool, args.query);
|
||||||
|
} break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
assert_unhandled_case(header.command);
|
assert_unhandled_case(header.command);
|
||||||
break;
|
break;
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@
|
||||||
#include <rex/filesystem.h>
|
#include <rex/filesystem.h>
|
||||||
#include <rex/logging.h>
|
#include <rex/logging.h>
|
||||||
#include <rex/math.h>
|
#include <rex/math.h>
|
||||||
|
#include <rex/platform.h>
|
||||||
#include <rex/thread.h>
|
#include <rex/thread.h>
|
||||||
#include <rex/memory.h>
|
#include <rex/memory.h>
|
||||||
#include <rex/graphics/util/draw.h>
|
#include <rex/graphics/util/draw.h>
|
||||||
|
|
@ -3213,7 +3214,14 @@ bool VulkanPipelineCache::EnsurePipelineCreated(const PipelineCreationArguments&
|
||||||
assert_unhandled_case(description.primitive_topology);
|
assert_unhandled_case(description.primitive_topology);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
#if REX_PLATFORM_MAC
|
||||||
|
// Metal always has primitive restart enabled in the paths MoltenVK exposes.
|
||||||
|
// Requesting it disabled produces VK_ERROR_FEATURE_NOT_PRESENT pipeline
|
||||||
|
// creation warnings and repeated failed/retried pipeline setup on macOS.
|
||||||
|
input_assembly_state.primitiveRestartEnable = VK_TRUE;
|
||||||
|
#else
|
||||||
input_assembly_state.primitiveRestartEnable = description.primitive_restart ? VK_TRUE : VK_FALSE;
|
input_assembly_state.primitiveRestartEnable = description.primitive_restart ? VK_TRUE : VK_FALSE;
|
||||||
|
#endif
|
||||||
|
|
||||||
VkPipelineViewportStateCreateInfo viewport_state;
|
VkPipelineViewportStateCreateInfo viewport_state;
|
||||||
viewport_state.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
|
viewport_state.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,8 @@
|
||||||
#include <rex/graphics/xenos.h>
|
#include <rex/graphics/xenos.h>
|
||||||
#include <rex/logging.h>
|
#include <rex/logging.h>
|
||||||
#include <rex/math.h>
|
#include <rex/math.h>
|
||||||
|
#include <rex/perf/counter.h>
|
||||||
|
#include <rex/platform.h>
|
||||||
#include <rex/ui/vulkan/util.h>
|
#include <rex/ui/vulkan/util.h>
|
||||||
|
|
||||||
REXCVAR_DEFINE_STRING(render_target_path_vulkan, "", "GPU/Vulkan",
|
REXCVAR_DEFINE_STRING(render_target_path_vulkan, "", "GPU/Vulkan",
|
||||||
|
|
@ -387,6 +389,12 @@ bool VulkanRenderTargetCache::Initialize(uint32_t shared_memory_binding_count) {
|
||||||
msaa_2x_attachments_supported_ = false;
|
msaa_2x_attachments_supported_ = false;
|
||||||
msaa_2x_no_attachments_supported_ = false;
|
msaa_2x_no_attachments_supported_ = false;
|
||||||
}
|
}
|
||||||
|
REXGPU_INFO(
|
||||||
|
"VulkanRenderTargetCache: Using {} render target path, native 2x MSAA attachments {}, "
|
||||||
|
"native 2x MSAA no-attachments {}",
|
||||||
|
path_ == Path::kHostRenderTargets ? "host/FBO" : "fragment shader interlock/FSI",
|
||||||
|
msaa_2x_attachments_supported_ ? "available" : "unavailable",
|
||||||
|
msaa_2x_no_attachments_supported_ ? "available" : "unavailable");
|
||||||
bool integer_transfer_sample_1x_supported =
|
bool integer_transfer_sample_1x_supported =
|
||||||
(device_properties.framebufferColorSampleCounts & VK_SAMPLE_COUNT_1_BIT) != 0 &&
|
(device_properties.framebufferColorSampleCounts & VK_SAMPLE_COUNT_1_BIT) != 0 &&
|
||||||
(device_properties.sampledImageIntegerSampleCounts & VK_SAMPLE_COUNT_1_BIT) != 0;
|
(device_properties.sampledImageIntegerSampleCounts & VK_SAMPLE_COUNT_1_BIT) != 0;
|
||||||
|
|
@ -1466,6 +1474,14 @@ bool VulkanRenderTargetCache::Resolve(const memory::Memory& memory,
|
||||||
UseEdramBuffer(EdramBufferUsage::kComputeRead);
|
UseEdramBuffer(EdramBufferUsage::kComputeRead);
|
||||||
command_processor_.BindExternalComputePipeline(
|
command_processor_.BindExternalComputePipeline(
|
||||||
resolve_copy_pipelines_[size_t(copy_shader)]);
|
resolve_copy_pipelines_[size_t(copy_shader)]);
|
||||||
|
rex::perf::DrawBucket resolve_timing_bucket =
|
||||||
|
rex::perf::DrawBucket::kCopyResolveShader;
|
||||||
|
if (copy_shader == draw_util::ResolveCopyShaderIndex::kFast32bpp1x2xMSAA) {
|
||||||
|
resolve_timing_bucket = rex::perf::DrawBucket::kCopyResolveFast32;
|
||||||
|
} else if (copy_shader == draw_util::ResolveCopyShaderIndex::kFull32bpp) {
|
||||||
|
resolve_timing_bucket = rex::perf::DrawBucket::kCopyResolveFull32;
|
||||||
|
}
|
||||||
|
command_processor_.BeginGpuTimestampedRegion(resolve_timing_bucket);
|
||||||
VkDescriptorSet descriptor_sets[kResolveCopyDescriptorSetCount] = {};
|
VkDescriptorSet descriptor_sets[kResolveCopyDescriptorSetCount] = {};
|
||||||
descriptor_sets[kResolveCopyDescriptorSetEdram] = edram_storage_buffer_descriptor_set_;
|
descriptor_sets[kResolveCopyDescriptorSetEdram] = edram_storage_buffer_descriptor_set_;
|
||||||
descriptor_sets[kResolveCopyDescriptorSetDest] = descriptor_set_dest;
|
descriptor_sets[kResolveCopyDescriptorSetDest] = descriptor_set_dest;
|
||||||
|
|
@ -1487,6 +1503,44 @@ bool VulkanRenderTargetCache::Resolve(const memory::Memory& memory,
|
||||||
sizeof(copy_shader_constants), ©_shader_constants);
|
sizeof(copy_shader_constants), ©_shader_constants);
|
||||||
}
|
}
|
||||||
command_processor_.SubmitBarriers(true);
|
command_processor_.SubmitBarriers(true);
|
||||||
|
PERF_counter_inc(kRtResolveCopyDispatches);
|
||||||
|
PERF_counter_add(kRtResolveCopyGroups,
|
||||||
|
uint64_t(copy_group_count_x) * copy_group_count_y);
|
||||||
|
#ifdef REXGLUE_ENABLE_PERF_COUNTERS
|
||||||
|
static constexpr rex::perf::CounterId kResolveCopyDispatchCounters[] = {
|
||||||
|
rex::perf::CounterId::kRtResolveCopyFast32bpp1x2xMsaaDispatches,
|
||||||
|
rex::perf::CounterId::kRtResolveCopyFast32bpp4xMsaaDispatches,
|
||||||
|
rex::perf::CounterId::kRtResolveCopyFast64bpp1x2xMsaaDispatches,
|
||||||
|
rex::perf::CounterId::kRtResolveCopyFast64bpp4xMsaaDispatches,
|
||||||
|
rex::perf::CounterId::kRtResolveCopyFull8bppDispatches,
|
||||||
|
rex::perf::CounterId::kRtResolveCopyFull16bppDispatches,
|
||||||
|
rex::perf::CounterId::kRtResolveCopyFull32bppDispatches,
|
||||||
|
rex::perf::CounterId::kRtResolveCopyFull64bppDispatches,
|
||||||
|
rex::perf::CounterId::kRtResolveCopyFull128bppDispatches,
|
||||||
|
};
|
||||||
|
static constexpr rex::perf::CounterId kResolveCopyGroupCounters[] = {
|
||||||
|
rex::perf::CounterId::kRtResolveCopyFast32bpp1x2xMsaaGroups,
|
||||||
|
rex::perf::CounterId::kRtResolveCopyFast32bpp4xMsaaGroups,
|
||||||
|
rex::perf::CounterId::kRtResolveCopyFast64bpp1x2xMsaaGroups,
|
||||||
|
rex::perf::CounterId::kRtResolveCopyFast64bpp4xMsaaGroups,
|
||||||
|
rex::perf::CounterId::kRtResolveCopyFull8bppGroups,
|
||||||
|
rex::perf::CounterId::kRtResolveCopyFull16bppGroups,
|
||||||
|
rex::perf::CounterId::kRtResolveCopyFull32bppGroups,
|
||||||
|
rex::perf::CounterId::kRtResolveCopyFull64bppGroups,
|
||||||
|
rex::perf::CounterId::kRtResolveCopyFull128bppGroups,
|
||||||
|
};
|
||||||
|
static_assert(std::size(kResolveCopyDispatchCounters) ==
|
||||||
|
size_t(draw_util::ResolveCopyShaderIndex::kCount));
|
||||||
|
static_assert(std::size(kResolveCopyGroupCounters) ==
|
||||||
|
size_t(draw_util::ResolveCopyShaderIndex::kCount));
|
||||||
|
size_t copy_shader_index = size_t(copy_shader);
|
||||||
|
rex::perf::IncrementCounter(kResolveCopyDispatchCounters[copy_shader_index]);
|
||||||
|
rex::perf::IncrementCounter(kResolveCopyGroupCounters[copy_shader_index],
|
||||||
|
uint64_t(copy_group_count_x) * copy_group_count_y);
|
||||||
|
#endif
|
||||||
|
if (draw_resolution_scaled) {
|
||||||
|
PERF_counter_add(kRtResolveScaledBytes, copy_dest_use_length);
|
||||||
|
}
|
||||||
command_buffer.CmdVkDispatch(copy_group_count_x, copy_group_count_y, 1);
|
command_buffer.CmdVkDispatch(copy_group_count_x, copy_group_count_y, 1);
|
||||||
|
|
||||||
// Invalidate textures and mark the range as scaled if needed.
|
// Invalidate textures and mark the range as scaled if needed.
|
||||||
|
|
@ -4558,7 +4612,11 @@ VkPipeline const* VulkanRenderTargetCache::GetTransferPipelines(TransferPipeline
|
||||||
input_assembly_state.pNext = nullptr;
|
input_assembly_state.pNext = nullptr;
|
||||||
input_assembly_state.flags = 0;
|
input_assembly_state.flags = 0;
|
||||||
input_assembly_state.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
|
input_assembly_state.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
|
||||||
|
#if REX_PLATFORM_MAC
|
||||||
|
input_assembly_state.primitiveRestartEnable = VK_TRUE;
|
||||||
|
#else
|
||||||
input_assembly_state.primitiveRestartEnable = VK_FALSE;
|
input_assembly_state.primitiveRestartEnable = VK_FALSE;
|
||||||
|
#endif
|
||||||
|
|
||||||
// Dynamic, to stay within maxViewportDimensions while preferring a
|
// Dynamic, to stay within maxViewportDimensions while preferring a
|
||||||
// power-of-two factor for converting from pixel coordinates to NDC for exact
|
// power-of-two factor for converting from pixel coordinates to NDC for exact
|
||||||
|
|
@ -6226,9 +6284,11 @@ bool VulkanRenderTargetCache::TryResolveCopyDirectly(const draw_util::ResolveInf
|
||||||
bool VulkanRenderTargetCache::DumpRenderTargets(uint32_t dump_base, uint32_t dump_row_length_used,
|
bool VulkanRenderTargetCache::DumpRenderTargets(uint32_t dump_base, uint32_t dump_row_length_used,
|
||||||
uint32_t dump_rows, uint32_t dump_pitch) {
|
uint32_t dump_rows, uint32_t dump_pitch) {
|
||||||
assert_true(GetPath() == Path::kHostRenderTargets);
|
assert_true(GetPath() == Path::kHostRenderTargets);
|
||||||
|
PERF_counter_inc(kRtDumpCalls);
|
||||||
|
|
||||||
GetResolveCopyRectanglesToDump(dump_base, dump_row_length_used, dump_rows, dump_pitch,
|
GetResolveCopyRectanglesToDump(dump_base, dump_row_length_used, dump_rows, dump_pitch,
|
||||||
dump_rectangles_);
|
dump_rectangles_);
|
||||||
|
PERF_counter_add(kRtDumpRectangles, dump_rectangles_.size());
|
||||||
if (dump_rectangles_.empty()) {
|
if (dump_rectangles_.empty()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
@ -6270,6 +6330,7 @@ bool VulkanRenderTargetCache::DumpRenderTargets(uint32_t dump_base, uint32_t dum
|
||||||
|
|
||||||
// Dump the render targets.
|
// Dump the render targets.
|
||||||
DeferredCommandBuffer& command_buffer = command_processor_.deferred_command_buffer();
|
DeferredCommandBuffer& command_buffer = command_processor_.deferred_command_buffer();
|
||||||
|
command_processor_.BeginGpuTimestampedRegion(rex::perf::DrawBucket::kCopyDump);
|
||||||
bool edram_buffer_bound = false;
|
bool edram_buffer_bound = false;
|
||||||
VkDescriptorSet last_source_descriptor_set = VK_NULL_HANDLE;
|
VkDescriptorSet last_source_descriptor_set = VK_NULL_HANDLE;
|
||||||
DumpPitches last_pitches;
|
DumpPitches last_pitches;
|
||||||
|
|
@ -6340,16 +6401,19 @@ bool VulkanRenderTargetCache::DumpRenderTargets(uint32_t dump_base, uint32_t dum
|
||||||
sizeof(last_offsets), &last_offsets);
|
sizeof(last_offsets), &last_offsets);
|
||||||
}
|
}
|
||||||
command_processor_.SubmitBarriers(true);
|
command_processor_.SubmitBarriers(true);
|
||||||
command_buffer.CmdVkDispatch(
|
const uint32_t group_count_x =
|
||||||
(draw_resolution_scale_x() *
|
(draw_resolution_scale_x() *
|
||||||
(xenos::kEdramTileWidthSamples >> uint32_t(rt_key.Is64bpp())) *
|
(xenos::kEdramTileWidthSamples >> uint32_t(rt_key.Is64bpp())) *
|
||||||
dispatch.width_tiles +
|
dispatch.width_tiles +
|
||||||
(kDumpSamplesPerGroupX - 1)) /
|
(kDumpSamplesPerGroupX - 1)) /
|
||||||
kDumpSamplesPerGroupX,
|
kDumpSamplesPerGroupX;
|
||||||
|
const uint32_t group_count_y =
|
||||||
(draw_resolution_scale_y() * xenos::kEdramTileHeightSamples * dispatch.height_tiles +
|
(draw_resolution_scale_y() * xenos::kEdramTileHeightSamples * dispatch.height_tiles +
|
||||||
(kDumpSamplesPerGroupY - 1)) /
|
(kDumpSamplesPerGroupY - 1)) /
|
||||||
kDumpSamplesPerGroupY,
|
kDumpSamplesPerGroupY;
|
||||||
1);
|
PERF_counter_inc(kRtDumpDispatches);
|
||||||
|
PERF_counter_add(kRtDumpGroups, uint64_t(group_count_x) * group_count_y);
|
||||||
|
command_buffer.CmdVkDispatch(group_count_x, group_count_y, 1);
|
||||||
}
|
}
|
||||||
MarkEdramBufferModified();
|
MarkEdramBufferModified();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,11 +29,16 @@
|
||||||
#include <rex/graphics/vulkan/texture_cache.h>
|
#include <rex/graphics/vulkan/texture_cache.h>
|
||||||
#include <rex/logging.h>
|
#include <rex/logging.h>
|
||||||
#include <rex/math.h>
|
#include <rex/math.h>
|
||||||
|
#include <rex/perf/counter.h>
|
||||||
#include <rex/ui/vulkan/mem_alloc.h>
|
#include <rex/ui/vulkan/mem_alloc.h>
|
||||||
#include <rex/ui/vulkan/ui_samplers.h>
|
#include <rex/ui/vulkan/ui_samplers.h>
|
||||||
#include <rex/ui/vulkan/util.h>
|
#include <rex/ui/vulkan/util.h>
|
||||||
|
|
||||||
REXCVAR_DEFINE_BOOL(non_seamless_cube_map, false, "GPU", "Use non-seamless cube map sampling");
|
REXCVAR_DEFINE_BOOL(non_seamless_cube_map, false, "GPU", "Use non-seamless cube map sampling");
|
||||||
|
REXCVAR_DEFINE_BOOL(vulkan_scaled_resolve_write_barrier_overlap_only, false, "GPU/Vulkan",
|
||||||
|
"Skip consecutive scaled resolve write barriers when destination ranges do "
|
||||||
|
"not overlap")
|
||||||
|
.lifecycle(rex::cvar::Lifecycle::kHotReload);
|
||||||
|
|
||||||
namespace rex::graphics::vulkan {
|
namespace rex::graphics::vulkan {
|
||||||
|
|
||||||
|
|
@ -2293,13 +2298,27 @@ void VulkanTextureCache::UseScaledResolveBufferForWrite(uint64_t written_start_s
|
||||||
std::min(written_length_scaled, scaled_resolve_buffer_size_ - written_start_scaled);
|
std::min(written_length_scaled, scaled_resolve_buffer_size_ - written_start_scaled);
|
||||||
|
|
||||||
if (scaled_resolve_last_usage_write_ && scaled_resolve_last_written_range_.second) {
|
if (scaled_resolve_last_usage_write_ && scaled_resolve_last_written_range_.second) {
|
||||||
VkPipelineStageFlags stage_mask;
|
uint64_t previous_start = scaled_resolve_last_written_range_.first;
|
||||||
VkAccessFlags access_mask;
|
uint64_t previous_end = previous_start + scaled_resolve_last_written_range_.second;
|
||||||
GetScaledResolveUsageMasks(stage_mask, access_mask, true);
|
uint64_t written_end = written_start_scaled + written_length_scaled;
|
||||||
command_processor_.PushBufferMemoryBarrier(
|
bool ranges_overlap = previous_start < written_end && written_start_scaled < previous_end;
|
||||||
scaled_resolve_buffer_, VkDeviceSize(scaled_resolve_last_written_range_.first),
|
if (ranges_overlap) {
|
||||||
VkDeviceSize(scaled_resolve_last_written_range_.second), stage_mask, stage_mask,
|
PERF_counter_inc(kRtScaledResolveWriteBarrierOverlaps);
|
||||||
access_mask, access_mask, VK_QUEUE_FAMILY_IGNORED, VK_QUEUE_FAMILY_IGNORED, false);
|
}
|
||||||
|
if (ranges_overlap || !REXCVAR_GET(vulkan_scaled_resolve_write_barrier_overlap_only)) {
|
||||||
|
VkPipelineStageFlags stage_mask;
|
||||||
|
VkAccessFlags access_mask;
|
||||||
|
GetScaledResolveUsageMasks(stage_mask, access_mask, true);
|
||||||
|
command_processor_.PushBufferMemoryBarrier(
|
||||||
|
scaled_resolve_buffer_, VkDeviceSize(scaled_resolve_last_written_range_.first),
|
||||||
|
VkDeviceSize(scaled_resolve_last_written_range_.second), stage_mask, stage_mask,
|
||||||
|
access_mask, access_mask, VK_QUEUE_FAMILY_IGNORED, VK_QUEUE_FAMILY_IGNORED, false);
|
||||||
|
PERF_counter_inc(kRtScaledResolveWriteBarriers);
|
||||||
|
PERF_counter_add(kRtScaledResolveWriteBarrierBytes,
|
||||||
|
scaled_resolve_last_written_range_.second);
|
||||||
|
} else {
|
||||||
|
PERF_counter_inc(kRtScaledResolveWriteBarrierSkipped);
|
||||||
|
}
|
||||||
} else if (!scaled_resolve_last_usage_write_) {
|
} else if (!scaled_resolve_last_usage_write_) {
|
||||||
VkPipelineStageFlags src_stage_mask, dst_stage_mask;
|
VkPipelineStageFlags src_stage_mask, dst_stage_mask;
|
||||||
VkAccessFlags src_access_mask, dst_access_mask;
|
VkAccessFlags src_access_mask, dst_access_mask;
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,8 @@
|
||||||
#include <rex/input/xinput/xinput_input_driver.h>
|
#include <rex/input/xinput/xinput_input_driver.h>
|
||||||
#include <rex/logging.h>
|
#include <rex/logging.h>
|
||||||
#include <rex/platform.h>
|
#include <rex/platform.h>
|
||||||
|
#include <rex/ui/ui_event.h>
|
||||||
|
#include <rex/ui/window_listener.h>
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
|
|
@ -50,6 +52,15 @@ X_STATUS InputSystem::Setup() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void InputSystem::Shutdown() {
|
void InputSystem::Shutdown() {
|
||||||
|
if (window_) {
|
||||||
|
rex::ui::UIEvent closing_event(window_);
|
||||||
|
for (auto& driver : drivers_) {
|
||||||
|
if (auto* window_listener = dynamic_cast<rex::ui::WindowListener*>(driver.get())) {
|
||||||
|
window_listener->OnClosing(closing_event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
window_ = nullptr;
|
||||||
|
}
|
||||||
drivers_.clear();
|
drivers_.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@
|
||||||
* @modified Tom Clay, 2026 - Adapted for ReXGlue runtime
|
* @modified Tom Clay, 2026 - Adapted for ReXGlue runtime
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
|
|
||||||
|
|
@ -22,6 +23,8 @@
|
||||||
|
|
||||||
REXCVAR_DEFINE_STRING(hid_mappings_file, "gamecontrollerdb.txt", "Input",
|
REXCVAR_DEFINE_STRING(hid_mappings_file, "gamecontrollerdb.txt", "Input",
|
||||||
"Path to SDL gamecontroller mappings file");
|
"Path to SDL gamecontroller mappings file");
|
||||||
|
REXCVAR_DEFINE_UINT32(hid_sdl_rumble_duration_ms, 100, "Input",
|
||||||
|
"SDL rumble pulse duration for nonzero XInput vibration");
|
||||||
|
|
||||||
namespace rex::input::sdl {
|
namespace rex::input::sdl {
|
||||||
|
|
||||||
|
|
@ -124,6 +127,7 @@ void SDLInputDriver::OnClosing(rex::ui::UIEvent&) {
|
||||||
}
|
}
|
||||||
for (size_t i = 0; i < controllers_.size(); i++) {
|
for (size_t i = 0; i < controllers_.size(); i++) {
|
||||||
if (controllers_.at(i).sdl) {
|
if (controllers_.at(i).sdl) {
|
||||||
|
StopRumbleLocked(controllers_.at(i));
|
||||||
SDL_CloseGamepad(controllers_.at(i).sdl);
|
SDL_CloseGamepad(controllers_.at(i).sdl);
|
||||||
controllers_.at(i) = {};
|
controllers_.at(i) = {};
|
||||||
}
|
}
|
||||||
|
|
@ -220,16 +224,16 @@ X_RESULT SDLInputDriver::SetState(uint32_t user_index, X_INPUT_VIBRATION* vibrat
|
||||||
return X_ERROR_DEVICE_NOT_CONNECTED;
|
return X_ERROR_DEVICE_NOT_CONNECTED;
|
||||||
}
|
}
|
||||||
|
|
||||||
#if SDL_VERSION_ATLEAST(2, 0, 9)
|
const bool has_rumble = vibration->left_motor_speed || vibration->right_motor_speed;
|
||||||
if (SDL_RumbleGamepad(controller->sdl, vibration->left_motor_speed, vibration->right_motor_speed,
|
const uint32_t duration_ms =
|
||||||
0)) {
|
has_rumble ? std::clamp(REXCVAR_GET(hid_sdl_rumble_duration_ms), uint32_t(1),
|
||||||
|
uint32_t(1000))
|
||||||
|
: 0;
|
||||||
|
if (!SDL_RumbleGamepad(controller->sdl, vibration->left_motor_speed,
|
||||||
|
vibration->right_motor_speed, duration_ms)) {
|
||||||
return X_ERROR_FUNCTION_FAILED;
|
return X_ERROR_FUNCTION_FAILED;
|
||||||
} else {
|
|
||||||
return X_ERROR_SUCCESS;
|
|
||||||
}
|
}
|
||||||
#else
|
|
||||||
return X_ERROR_SUCCESS;
|
return X_ERROR_SUCCESS;
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
X_RESULT SDLInputDriver::GetKeystroke(uint32_t users, uint32_t flags,
|
X_RESULT SDLInputDriver::GetKeystroke(uint32_t users, uint32_t flags,
|
||||||
|
|
@ -437,6 +441,12 @@ void SDLInputDriver::ProcessEventLocked(const SDL_Event& event) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SDLInputDriver::StopRumbleLocked(ControllerState& state) {
|
||||||
|
if (state.sdl) {
|
||||||
|
SDL_RumbleGamepad(state.sdl, 0, 0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void SDLInputDriver::OnControllerDeviceAddedLocked(const SDL_Event& event) {
|
void SDLInputDriver::OnControllerDeviceAddedLocked(const SDL_Event& event) {
|
||||||
// Open the controller.
|
// Open the controller.
|
||||||
const auto controller = SDL_OpenGamepad(event.cdevice.which);
|
const auto controller = SDL_OpenGamepad(event.cdevice.which);
|
||||||
|
|
@ -493,6 +503,7 @@ void SDLInputDriver::OnControllerDeviceRemovedLocked(const SDL_Event& event) {
|
||||||
// Find the disconnected gamecontroller and close it.
|
// Find the disconnected gamecontroller and close it.
|
||||||
auto idx = GetControllerIndexFromInstanceID(event.cdevice.which);
|
auto idx = GetControllerIndexFromInstanceID(event.cdevice.which);
|
||||||
if (idx) {
|
if (idx) {
|
||||||
|
StopRumbleLocked(controllers_.at(*idx));
|
||||||
SDL_CloseGamepad(controllers_.at(*idx).sdl);
|
SDL_CloseGamepad(controllers_.at(*idx).sdl);
|
||||||
controllers_.at(*idx) = {};
|
controllers_.at(*idx) = {};
|
||||||
keystroke_states_.at(*idx) = {};
|
keystroke_states_.at(*idx) = {};
|
||||||
|
|
|
||||||
|
|
@ -318,7 +318,8 @@ HeapDiagnostics ReXHeap::GetDiagnosticsLocked() const {
|
||||||
diagnostics.capacity += d.capacity;
|
diagnostics.capacity += d.capacity;
|
||||||
diagnostics.allocated += d.allocated;
|
diagnostics.allocated += d.allocated;
|
||||||
diagnostics.peak_allocated += d.peak_allocated;
|
diagnostics.peak_allocated += d.peak_allocated;
|
||||||
diagnostics.peak_request_size = std::max(diagnostics.peak_request_size, d.peak_request_size);
|
diagnostics.peak_request_size =
|
||||||
|
std::max(diagnostics.peak_request_size, static_cast<uint64_t>(d.peak_request_size));
|
||||||
diagnostics.oom_count += d.oom_count;
|
diagnostics.oom_count += d.oom_count;
|
||||||
}
|
}
|
||||||
return diagnostics;
|
return diagnostics;
|
||||||
|
|
|
||||||
|
|
@ -33,10 +33,11 @@
|
||||||
// NOTE: must be included last as it expects windows.h to already be included.
|
// NOTE: must be included last as it expects windows.h to already be included.
|
||||||
#define _WINSOCK_DEPRECATED_NO_WARNINGS // inet_addr
|
#define _WINSOCK_DEPRECATED_NO_WARNINGS // inet_addr
|
||||||
#include <winsock2.h> // NOLINT(build/include_order)
|
#include <winsock2.h> // NOLINT(build/include_order)
|
||||||
#elif REX_PLATFORM_LINUX
|
#elif REX_PLATFORM_LINUX || REX_PLATFORM_MAC
|
||||||
#include <arpa/inet.h>
|
#include <arpa/inet.h>
|
||||||
#include <netinet/in.h>
|
#include <netinet/in.h>
|
||||||
#include <netinet/ip.h>
|
#include <netinet/ip.h>
|
||||||
|
#include <sys/select.h>
|
||||||
#include <sys/socket.h>
|
#include <sys/socket.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@
|
||||||
#include <rex/assert.h>
|
#include <rex/assert.h>
|
||||||
#include <rex/logging.h>
|
#include <rex/logging.h>
|
||||||
#include <rex/math.h>
|
#include <rex/math.h>
|
||||||
|
#include <rex/platform.h>
|
||||||
#include <rex/ppc/function.h>
|
#include <rex/ppc/function.h>
|
||||||
#include <rex/runtime.h>
|
#include <rex/runtime.h>
|
||||||
#include <rex/stream.h>
|
#include <rex/stream.h>
|
||||||
|
|
@ -841,6 +842,26 @@ void KernelState::TerminateTitle() {
|
||||||
REXSYS_DEBUG("KernelState::TerminateTitle");
|
REXSYS_DEBUG("KernelState::TerminateTitle");
|
||||||
auto global_lock = global_critical_region_.Acquire();
|
auto global_lock = global_critical_region_.Acquire();
|
||||||
|
|
||||||
|
#if REX_PLATFORM_MAC
|
||||||
|
// pthread_cancel is asynchronous on macOS in the host thread wrapper, but
|
||||||
|
// suspending a guest thread first can strand it in the suspend wait during
|
||||||
|
// title teardown. Terminate directly on macOS and let the thread wrapper
|
||||||
|
// signal waiters before requesting cancellation.
|
||||||
|
std::vector<object_ref<XThread>> threads_to_terminate;
|
||||||
|
for (auto it = threads_by_id_.begin(); it != threads_by_id_.end(); ++it) {
|
||||||
|
if (!XThread::IsInThread(it->second) && it->second->is_guest_thread() &&
|
||||||
|
it->second->is_running()) {
|
||||||
|
threads_to_terminate.push_back(retain_object(it->second));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
global_lock.unlock();
|
||||||
|
for (auto& thread : threads_to_terminate) {
|
||||||
|
thread->Terminate(0);
|
||||||
|
}
|
||||||
|
threads_to_terminate.clear();
|
||||||
|
global_lock.lock();
|
||||||
|
#else
|
||||||
// Suspend all running guest threads so they stop touching shared state.
|
// Suspend all running guest threads so they stop touching shared state.
|
||||||
std::vector<XThread*> suspended_threads;
|
std::vector<XThread*> suspended_threads;
|
||||||
for (auto it = threads_by_id_.begin(); it != threads_by_id_.end(); ++it) {
|
for (auto it = threads_by_id_.begin(); it != threads_by_id_.end(); ++it) {
|
||||||
|
|
@ -857,6 +878,7 @@ void KernelState::TerminateTitle() {
|
||||||
thread->Terminate(0);
|
thread->Terminate(0);
|
||||||
}
|
}
|
||||||
global_lock.lock();
|
global_lock.lock();
|
||||||
|
#endif
|
||||||
|
|
||||||
// Remove all guest threads from the map.
|
// Remove all guest threads from the map.
|
||||||
for (auto it = threads_by_id_.begin(); it != threads_by_id_.end();) {
|
for (auto it = threads_by_id_.begin(); it != threads_by_id_.end();) {
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@
|
||||||
#include <rex/filesystem/vfs.h>
|
#include <rex/filesystem/vfs.h>
|
||||||
#include <rex/logging.h>
|
#include <rex/logging.h>
|
||||||
#include <rex/perf/counter.h>
|
#include <rex/perf/counter.h>
|
||||||
|
#include <rex/platform.h>
|
||||||
#include <rex/ppc/context.h> // PPCFuncMapping
|
#include <rex/ppc/context.h> // PPCFuncMapping
|
||||||
#include <rex/platform/exceptions.h> // SEH exception support
|
#include <rex/platform/exceptions.h> // SEH exception support
|
||||||
#include <rex/kernel/crt/heap.h>
|
#include <rex/kernel/crt/heap.h>
|
||||||
|
|
@ -244,10 +245,22 @@ void Runtime::Shutdown() {
|
||||||
input_system_.reset();
|
input_system_.reset();
|
||||||
}
|
}
|
||||||
kernel_state_.reset();
|
kernel_state_.reset();
|
||||||
|
#if REX_PLATFORM_MAC
|
||||||
|
// macOS tears down guest threads asynchronously. During process exit, a late
|
||||||
|
// guest callback can still hold references into the dispatcher or mapped
|
||||||
|
// guest memory after the owning systems have stopped. Keep those mappings
|
||||||
|
// alive until the process exits; the OS reclaims them immediately afterward.
|
||||||
|
function_dispatcher_.release();
|
||||||
|
#else
|
||||||
function_dispatcher_.reset();
|
function_dispatcher_.reset();
|
||||||
|
#endif
|
||||||
export_resolver_.reset();
|
export_resolver_.reset();
|
||||||
file_system_.reset();
|
file_system_.reset();
|
||||||
|
#if REX_PLATFORM_MAC
|
||||||
|
memory_.release();
|
||||||
|
#else
|
||||||
memory_.reset();
|
memory_.reset();
|
||||||
|
#endif
|
||||||
|
|
||||||
rex::perf::Profiler::Shutdown();
|
rex::perf::Profiler::Shutdown();
|
||||||
setup_complete_ = false;
|
setup_complete_ = false;
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,10 @@
|
||||||
// TODO(benvanik): move xbox.h out
|
// TODO(benvanik): move xbox.h out
|
||||||
#include <rex/system/xtypes.h>
|
#include <rex/system/xtypes.h>
|
||||||
|
|
||||||
|
#if REX_PLATFORM_MAC
|
||||||
|
#include <sys/mman.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
REXCVAR_DEFINE_BOOL(protect_zero, false, "Memory", "Protect the zero page from reads and writes")
|
REXCVAR_DEFINE_BOOL(protect_zero, false, "Memory", "Protect the zero page from reads and writes")
|
||||||
.lifecycle(rex::cvar::Lifecycle::kRequiresRestart);
|
.lifecycle(rex::cvar::Lifecycle::kRequiresRestart);
|
||||||
|
|
||||||
|
|
@ -77,6 +81,22 @@ uint32_t get_page_count(uint32_t value, uint32_t page_size, uint32_t page_size_s
|
||||||
|
|
||||||
static memory::Memory* active_memory_ = nullptr;
|
static memory::Memory* active_memory_ = nullptr;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
constexpr size_t kGuestBackingLength = 0x120001000ull;
|
||||||
|
|
||||||
|
bool ShouldSkipHostCommit(const BaseHeap& heap) {
|
||||||
|
#if REX_PLATFORM_MAC
|
||||||
|
return heap.heap_type() == HeapType::kGuestPhysical && heap.heap_base() == 0x00000000 &&
|
||||||
|
rex::memory::page_size() > 0x1000;
|
||||||
|
#else
|
||||||
|
(void)heap;
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
void CrashDump() {
|
void CrashDump() {
|
||||||
static std::atomic<int> in_crash_dump(0);
|
static std::atomic<int> in_crash_dump(0);
|
||||||
if (in_crash_dump.fetch_add(1)) {
|
if (in_crash_dump.fetch_add(1)) {
|
||||||
|
|
@ -135,7 +155,8 @@ bool Memory::Initialize() {
|
||||||
mapping_ =
|
mapping_ =
|
||||||
rex::memory::CreateFileMappingHandle(file_name_,
|
rex::memory::CreateFileMappingHandle(file_name_,
|
||||||
// entire 4gb space + 512mb physical:
|
// entire 4gb space + 512mb physical:
|
||||||
0x11FFFFFFF, rex::memory::PageAccess::kReadWrite, false);
|
kGuestBackingLength,
|
||||||
|
rex::memory::PageAccess::kReadWrite, false);
|
||||||
if (mapping_ == rex::memory::kFileMappingHandleInvalid) {
|
if (mapping_ == rex::memory::kFileMappingHandleInvalid) {
|
||||||
REXSYS_ERROR("Unable to reserve the 4gb guest address space.");
|
REXSYS_ERROR("Unable to reserve the 4gb guest address space.");
|
||||||
assert_always();
|
assert_always();
|
||||||
|
|
@ -148,10 +169,17 @@ bool Memory::Initialize() {
|
||||||
for (size_t n = 32; n < 64; n++) {
|
for (size_t n = 32; n < 64; n++) {
|
||||||
auto mapping_base = reinterpret_cast<uint8_t*>(1ull << n);
|
auto mapping_base = reinterpret_cast<uint8_t*>(1ull << n);
|
||||||
if (!MapViews(mapping_base)) {
|
if (!MapViews(mapping_base)) {
|
||||||
mapping_base_ = mapping_base;
|
mapping_base_ = views_.all_views[0];
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#if REX_PLATFORM_MAC
|
||||||
|
if (!mapping_base_) {
|
||||||
|
if (!MapViews(nullptr)) {
|
||||||
|
mapping_base_ = views_.all_views[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
if (!mapping_base_) {
|
if (!mapping_base_) {
|
||||||
REXSYS_ERROR("Unable to find a continuous block in the 64bit address space.");
|
REXSYS_ERROR("Unable to find a continuous block in the 64bit address space.");
|
||||||
assert_always();
|
assert_always();
|
||||||
|
|
@ -209,6 +237,12 @@ bool Memory::Initialize() {
|
||||||
rex::memory::AllocFixed(heaps_.physical.TranslateRelative(0), heaps_.physical.heap_size(),
|
rex::memory::AllocFixed(heaps_.physical.TranslateRelative(0), heaps_.physical.heap_size(),
|
||||||
rex::memory::AllocationType::kCommit,
|
rex::memory::AllocationType::kCommit,
|
||||||
rex::memory::PageAccess::kReadWrite);
|
rex::memory::PageAccess::kReadWrite);
|
||||||
|
#if REX_PLATFORM_MAC
|
||||||
|
// The 0x7F000000-0x7FFFFFFF view aliases the start of physical memory. Touch
|
||||||
|
// it explicitly on macOS so raw generated loads through this alias don't SIGBUS
|
||||||
|
// before any guest write has materialized the shared-memory pages.
|
||||||
|
std::memset(virtual_membase_ + 0x7F000000, 0, 0x01000000);
|
||||||
|
#endif
|
||||||
|
|
||||||
// Install MMIO handler for physical address translation and MMIO ranges
|
// Install MMIO handler for physical address translation and MMIO ranges
|
||||||
mmio_handler_ = runtime::MMIOHandler::Install(
|
mmio_handler_ = runtime::MMIOHandler::Install(
|
||||||
|
|
@ -300,8 +334,34 @@ static const struct {
|
||||||
0x0000000100000000ull,
|
0x0000000100000000ull,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
constexpr size_t kGuestMappingLength = 0x120000000ull;
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
int Memory::MapViews(uint8_t* mapping_base) {
|
int Memory::MapViews(uint8_t* mapping_base) {
|
||||||
assert_true(rex::countof(map_info) == rex::countof(views_.all_views));
|
assert_true(rex::countof(map_info) == rex::countof(views_.all_views));
|
||||||
|
|
||||||
|
#if REX_PLATFORM_MAC
|
||||||
|
// macOS doesn't provide MAP_FIXED_NOREPLACE. Direct MAP_FIXED probing can
|
||||||
|
// overwrite unrelated host mappings, so first reserve the entire range as a
|
||||||
|
// hint and only proceed with a fixed base we own.
|
||||||
|
void* reservation =
|
||||||
|
mmap(mapping_base, kGuestMappingLength, PROT_NONE, MAP_PRIVATE | MAP_ANON, -1, 0);
|
||||||
|
if (reservation == MAP_FAILED) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
if (mapping_base && reservation != mapping_base) {
|
||||||
|
if (reservation != MAP_FAILED) {
|
||||||
|
munmap(reservation, kGuestMappingLength);
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
mapping_base = reinterpret_cast<uint8_t*>(reservation);
|
||||||
|
#endif
|
||||||
|
|
||||||
// 0xE0000000 4 KB offset is emulated via host_address_offset and on the CPU
|
// 0xE0000000 4 KB offset is emulated via host_address_offset and on the CPU
|
||||||
// side if system allocation granularity is bigger than 4 KB.
|
// side if system allocation granularity is bigger than 4 KB.
|
||||||
uint64_t granularity_mask = ~uint64_t(system_allocation_granularity_ - 1);
|
uint64_t granularity_mask = ~uint64_t(system_allocation_granularity_ - 1);
|
||||||
|
|
@ -312,7 +372,12 @@ int Memory::MapViews(uint8_t* mapping_base) {
|
||||||
rex::memory::PageAccess::kReadWrite, map_info[n].target_address & granularity_mask));
|
rex::memory::PageAccess::kReadWrite, map_info[n].target_address & granularity_mask));
|
||||||
if (!views_.all_views[n]) {
|
if (!views_.all_views[n]) {
|
||||||
// Failed, so bail and try again.
|
// Failed, so bail and try again.
|
||||||
|
#if REX_PLATFORM_MAC
|
||||||
|
munmap(mapping_base, kGuestMappingLength);
|
||||||
|
std::fill(std::begin(views_.all_views), std::end(views_.all_views), nullptr);
|
||||||
|
#else
|
||||||
UnmapViews();
|
UnmapViews();
|
||||||
|
#endif
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1111,7 +1176,7 @@ bool BaseHeap::AllocFixed(uint32_t base_address, uint32_t size, uint32_t alignme
|
||||||
// Allocate from host.
|
// Allocate from host.
|
||||||
if (allocation_type == memory::kMemoryAllocationReserve) {
|
if (allocation_type == memory::kMemoryAllocationReserve) {
|
||||||
// Reserve is not needed, as we are mapped already.
|
// Reserve is not needed, as we are mapped already.
|
||||||
} else {
|
} else if (!ShouldSkipHostCommit(*this)) {
|
||||||
auto alloc_type = (allocation_type & memory::kMemoryAllocationCommit)
|
auto alloc_type = (allocation_type & memory::kMemoryAllocationCommit)
|
||||||
? rex::memory::AllocationType::kCommit
|
? rex::memory::AllocationType::kCommit
|
||||||
: rex::memory::AllocationType::kReserve;
|
: rex::memory::AllocationType::kReserve;
|
||||||
|
|
@ -1265,7 +1330,7 @@ bool BaseHeap::AllocRange(uint32_t low_address, uint32_t high_address, uint32_t
|
||||||
// Allocate from host.
|
// Allocate from host.
|
||||||
if (allocation_type == memory::kMemoryAllocationReserve) {
|
if (allocation_type == memory::kMemoryAllocationReserve) {
|
||||||
// Reserve is not needed, as we are mapped already.
|
// Reserve is not needed, as we are mapped already.
|
||||||
} else {
|
} else if (!ShouldSkipHostCommit(*this)) {
|
||||||
auto alloc_type = (allocation_type & memory::kMemoryAllocationCommit)
|
auto alloc_type = (allocation_type & memory::kMemoryAllocationCommit)
|
||||||
? rex::memory::AllocationType::kCommit
|
? rex::memory::AllocationType::kCommit
|
||||||
: rex::memory::AllocationType::kReserve;
|
: rex::memory::AllocationType::kReserve;
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,14 @@
|
||||||
#include <netinet/in.h>
|
#include <netinet/in.h>
|
||||||
#include <netinet/ip.h>
|
#include <netinet/ip.h>
|
||||||
#include <sys/socket.h>
|
#include <sys/socket.h>
|
||||||
|
#if REX_PLATFORM_MAC
|
||||||
|
#ifdef IPPROTO_TCP
|
||||||
|
#undef IPPROTO_TCP
|
||||||
|
#endif
|
||||||
|
#ifdef IPPROTO_UDP
|
||||||
|
#undef IPPROTO_UDP
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
namespace rex::system {
|
namespace rex::system {
|
||||||
|
|
@ -247,4 +255,4 @@ bool XSocket::QueuePacket(uint32_t src_ip, uint16_t src_port, const uint8_t* buf
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace rex::system
|
} // namespace rex::system
|
||||||
|
|
|
||||||
|
|
@ -63,6 +63,12 @@ if(WIN32)
|
||||||
window_win.cpp
|
window_win.cpp
|
||||||
windowed_app_context_win.cpp
|
windowed_app_context_win.cpp
|
||||||
)
|
)
|
||||||
|
elseif(APPLE)
|
||||||
|
set(REXUI_PLATFORM_SOURCES
|
||||||
|
surface_sdl.cpp
|
||||||
|
window_sdl.cpp
|
||||||
|
windowed_app_context_sdl.cpp
|
||||||
|
)
|
||||||
else()
|
else()
|
||||||
set(REXUI_PLATFORM_SOURCES
|
set(REXUI_PLATFORM_SOURCES
|
||||||
surface_gnulinux.cpp
|
surface_gnulinux.cpp
|
||||||
|
|
@ -138,6 +144,8 @@ if(WIN32)
|
||||||
dwmapi
|
dwmapi
|
||||||
Shcore
|
Shcore
|
||||||
)
|
)
|
||||||
|
elseif(APPLE)
|
||||||
|
target_link_libraries(rexui PUBLIC SDL3::SDL3)
|
||||||
else()
|
else()
|
||||||
find_package(PkgConfig REQUIRED)
|
find_package(PkgConfig REQUIRED)
|
||||||
pkg_check_modules(GTK3 REQUIRED gtk+-3.0)
|
pkg_check_modules(GTK3 REQUIRED gtk+-3.0)
|
||||||
|
|
|
||||||
|
|
@ -364,8 +364,15 @@ void SimpleSettingsDialog::OnDraw(ImGuiIO& io) {
|
||||||
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0.38f, 0.08f, 0.07f, 1.00f));
|
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0.38f, 0.08f, 0.07f, 1.00f));
|
||||||
if (ImGui::Button("Close Game", ImVec2(170.0f, 40.0f))) {
|
if (ImGui::Button("Close Game", ImVec2(170.0f, 40.0f))) {
|
||||||
if (close_game_) {
|
if (close_game_) {
|
||||||
|
Hide();
|
||||||
close_game_();
|
close_game_();
|
||||||
}
|
}
|
||||||
|
ImGui::PopStyleColor(3);
|
||||||
|
ImGui::End();
|
||||||
|
ImGui::PopStyleColor(17);
|
||||||
|
ImGui::PopStyleVar(7);
|
||||||
|
ImGui::PopFont();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
ImGui::PopStyleColor(3);
|
ImGui::PopStyleColor(3);
|
||||||
ImGui::Spacing();
|
ImGui::Spacing();
|
||||||
|
|
|
||||||
|
|
@ -425,8 +425,10 @@ void Presenter::OnSurfaceResizeFromUIThread() {
|
||||||
bool request_repaint;
|
bool request_repaint;
|
||||||
UpdateSurfacePaintConnectionFromUIThread(&request_repaint, true);
|
UpdateSurfacePaintConnectionFromUIThread(&request_repaint, true);
|
||||||
|
|
||||||
// Request to repaint as soon as possible in the UI thread if needed.
|
// Request to repaint as soon as possible in the UI thread if needed. Active
|
||||||
if (request_repaint) {
|
// UI drawers need a repaint after every resize even if the connection reports
|
||||||
|
// unchanged, because UI-only presentation has no guest frame to kick it.
|
||||||
|
if (request_repaint || !ui_drawers_.empty()) {
|
||||||
RequestPaintOrConnectionRecoveryViaWindow(true);
|
RequestPaintOrConnectionRecoveryViaWindow(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1453,13 +1455,13 @@ bool Presenter::RequestPaintOrConnectionRecoveryViaWindow(bool force_ui_thread_p
|
||||||
// theoretically. For safety, check whether the window exists unconditionally.
|
// theoretically. For safety, check whether the window exists unconditionally.
|
||||||
assert_not_null(window_);
|
assert_not_null(window_);
|
||||||
assert_not_null(surface_);
|
assert_not_null(surface_);
|
||||||
|
if (force_ui_thread_paint_tick) {
|
||||||
|
ForceUIThreadPaintTick();
|
||||||
|
}
|
||||||
if (ui_thread_paint_requested_.exchange(true, std::memory_order_relaxed)) {
|
if (ui_thread_paint_requested_.exchange(true, std::memory_order_relaxed)) {
|
||||||
// Invalidation pending already, no need to do it twice.
|
// Invalidation pending already, no need to do it twice.
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (force_ui_thread_paint_tick) {
|
|
||||||
ForceUIThreadPaintTick();
|
|
||||||
}
|
|
||||||
window_->RequestPaint();
|
window_->RequestPaint();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,7 @@
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
namespace rex {
|
namespace rex {
|
||||||
|
|
||||||
|
|
@ -98,9 +99,24 @@ bool ReXApp::OnInitialize() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if REX_PLATFORM_MAC
|
||||||
|
// Let the platform event loop run once before runtime setup. SDL creates the
|
||||||
|
// Cocoa window synchronously, but the app may not visibly activate if guest
|
||||||
|
// initialization starts before the first event-loop pass.
|
||||||
|
app_context().CallInUIThreadDeferred([this, paths = std::move(*paths)]() mutable {
|
||||||
|
if (shutting_down_.load(std::memory_order_acquire))
|
||||||
|
return;
|
||||||
|
if (!ConstructRuntime(paths)) {
|
||||||
|
app_context().QuitFromUIThread();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
LaunchModule();
|
||||||
|
});
|
||||||
|
#else
|
||||||
if (!ConstructRuntime(*paths))
|
if (!ConstructRuntime(*paths))
|
||||||
return false;
|
return false;
|
||||||
LaunchModule();
|
LaunchModule();
|
||||||
|
#endif
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -432,6 +448,9 @@ void ReXApp::LaunchModule() {
|
||||||
app_context().QuitFromUIThread();
|
app_context().QuitFromUIThread();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
#if REX_PLATFORM_MAC
|
||||||
|
main_thread_ = main_thread;
|
||||||
|
#endif
|
||||||
|
|
||||||
auto* graphics_system =
|
auto* graphics_system =
|
||||||
static_cast<rex::graphics::GraphicsSystem*>(runtime_->graphics_system());
|
static_cast<rex::graphics::GraphicsSystem*>(runtime_->graphics_system());
|
||||||
|
|
@ -478,6 +497,11 @@ void ReXApp::OnClosing(ui::UIEvent& e) {
|
||||||
(void)e;
|
(void)e;
|
||||||
REXLOG_INFO("Window closing, shutting down...");
|
REXLOG_INFO("Window closing, shutting down...");
|
||||||
shutting_down_.store(true, std::memory_order_release);
|
shutting_down_.store(true, std::memory_order_release);
|
||||||
|
#if REX_PLATFORM_MAC
|
||||||
|
if (main_thread_ && main_thread_->is_running()) {
|
||||||
|
main_thread_->Terminate(0);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
if (runtime_ && runtime_->kernel_state()) {
|
if (runtime_ && runtime_->kernel_state()) {
|
||||||
runtime_->kernel_state()->TerminateTitle();
|
runtime_->kernel_state()->TerminateTitle();
|
||||||
}
|
}
|
||||||
|
|
@ -488,6 +512,13 @@ void ReXApp::OnDestroy() {
|
||||||
// Notify subclass before cleanup
|
// Notify subclass before cleanup
|
||||||
OnShutdown();
|
OnShutdown();
|
||||||
|
|
||||||
|
#if REX_PLATFORM_MAC
|
||||||
|
shutting_down_.store(true, std::memory_order_release);
|
||||||
|
if (main_thread_ && main_thread_->is_running()) {
|
||||||
|
main_thread_->Terminate(0);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
// Unregister overlay keybinds before destroying dialogs
|
// Unregister overlay keybinds before destroying dialogs
|
||||||
rex::ui::UnregisterBind("bind_debug_overlay");
|
rex::ui::UnregisterBind("bind_debug_overlay");
|
||||||
rex::ui::UnregisterBind("bind_console");
|
rex::ui::UnregisterBind("bind_console");
|
||||||
|
|
@ -516,6 +547,15 @@ void ReXApp::OnDestroy() {
|
||||||
if (module_thread_.joinable()) {
|
if (module_thread_.joinable()) {
|
||||||
module_thread_.join();
|
module_thread_.join();
|
||||||
}
|
}
|
||||||
|
#if REX_PLATFORM_MAC
|
||||||
|
main_thread_ = nullptr;
|
||||||
|
#endif
|
||||||
|
// Input drivers may still be registered as window listeners. Detach them
|
||||||
|
// before destroying the SDL window so their backend state is released in
|
||||||
|
// listener order.
|
||||||
|
if (runtime_ && runtime_->input_system()) {
|
||||||
|
static_cast<rex::input::InputSystem*>(runtime_->input_system())->Shutdown();
|
||||||
|
}
|
||||||
if (window_) {
|
if (window_) {
|
||||||
window_->RemoveInputListener(this);
|
window_->RemoveInputListener(this);
|
||||||
window_->RemoveListener(this);
|
window_->RemoveListener(this);
|
||||||
|
|
|
||||||
21
src/ui/surface_sdl.cpp
Normal file
21
src/ui/surface_sdl.cpp
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
#include <rex/ui/surface_sdl.h>
|
||||||
|
|
||||||
|
namespace rex {
|
||||||
|
namespace ui {
|
||||||
|
|
||||||
|
bool SDLMetalViewSurface::GetSizeImpl(uint32_t& width_out, uint32_t& height_out) const {
|
||||||
|
int width = 0;
|
||||||
|
int height = 0;
|
||||||
|
if (!window_ || !SDL_GetWindowSizeInPixels(window_, &width, &height) || width <= 0 ||
|
||||||
|
height <= 0) {
|
||||||
|
width_out = 0;
|
||||||
|
height_out = 0;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
width_out = uint32_t(width);
|
||||||
|
height_out = uint32_t(height);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace ui
|
||||||
|
} // namespace rex
|
||||||
|
|
@ -29,7 +29,13 @@ REXCVAR_DEFINE_BOOL(vulkan_require_vertex_pipeline_stores_and_atomics, true, "UI
|
||||||
"Deprecated and ignored for parity; vertexPipelineStoresAndAtomics is always "
|
"Deprecated and ignored for parity; vertexPipelineStoresAndAtomics is always "
|
||||||
"required for Vulkan GPU emulation")
|
"required for Vulkan GPU emulation")
|
||||||
.lifecycle(rex::cvar::Lifecycle::kInitOnly);
|
.lifecycle(rex::cvar::Lifecycle::kInitOnly);
|
||||||
REXCVAR_DEFINE_BOOL(vulkan_require_geometry_shader, true, "UI/Vulkan",
|
REXCVAR_DEFINE_BOOL(vulkan_require_geometry_shader,
|
||||||
|
#if REX_PLATFORM_MAC
|
||||||
|
false,
|
||||||
|
#else
|
||||||
|
true,
|
||||||
|
#endif
|
||||||
|
"UI/Vulkan",
|
||||||
"Require geometryShader support for Vulkan GPU emulation (disable to allow "
|
"Require geometryShader support for Vulkan GPU emulation (disable to allow "
|
||||||
"fallback primitive emulation paths)")
|
"fallback primitive emulation paths)")
|
||||||
.lifecycle(rex::cvar::Lifecycle::kInitOnly);
|
.lifecycle(rex::cvar::Lifecycle::kInitOnly);
|
||||||
|
|
@ -621,6 +627,8 @@ std::unique_ptr<VulkanDevice> VulkanDevice::CreateIfSupported(
|
||||||
XE_UI_VULKAN_LIMIT(optimalBufferCopyOffsetAlignment)
|
XE_UI_VULKAN_LIMIT(optimalBufferCopyOffsetAlignment)
|
||||||
XE_UI_VULKAN_LIMIT(optimalBufferCopyRowPitchAlignment)
|
XE_UI_VULKAN_LIMIT(optimalBufferCopyRowPitchAlignment)
|
||||||
XE_UI_VULKAN_LIMIT(nonCoherentAtomSize)
|
XE_UI_VULKAN_LIMIT(nonCoherentAtomSize)
|
||||||
|
XE_UI_VULKAN_LIMIT(timestampComputeAndGraphics)
|
||||||
|
XE_UI_VULKAN_LIMIT(timestampPeriod)
|
||||||
|
|
||||||
if (with_gpu_emulation) {
|
if (with_gpu_emulation) {
|
||||||
XE_UI_VULKAN_FEATURE(robustBufferAccess)
|
XE_UI_VULKAN_FEATURE(robustBufferAccess)
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@
|
||||||
#include <rex/assert.h>
|
#include <rex/assert.h>
|
||||||
#include <rex/logging.h>
|
#include <rex/logging.h>
|
||||||
#include <rex/math.h>
|
#include <rex/math.h>
|
||||||
|
#include <rex/platform.h>
|
||||||
#include <rex/ui/vulkan/immediate_drawer.h>
|
#include <rex/ui/vulkan/immediate_drawer.h>
|
||||||
#include <rex/ui/vulkan/presenter.h>
|
#include <rex/ui/vulkan/presenter.h>
|
||||||
#include <rex/ui/vulkan/util.h>
|
#include <rex/ui/vulkan/util.h>
|
||||||
|
|
@ -603,7 +604,11 @@ bool VulkanImmediateDrawer::EnsurePipelinesCreatedForCurrentRenderPass() {
|
||||||
input_assembly_state.pNext = nullptr;
|
input_assembly_state.pNext = nullptr;
|
||||||
input_assembly_state.flags = 0;
|
input_assembly_state.flags = 0;
|
||||||
input_assembly_state.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
|
input_assembly_state.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
|
||||||
|
#if REX_PLATFORM_MAC
|
||||||
|
input_assembly_state.primitiveRestartEnable = VK_TRUE;
|
||||||
|
#else
|
||||||
input_assembly_state.primitiveRestartEnable = VK_FALSE;
|
input_assembly_state.primitiveRestartEnable = VK_FALSE;
|
||||||
|
#endif
|
||||||
|
|
||||||
VkPipelineViewportStateCreateInfo viewport_state;
|
VkPipelineViewportStateCreateInfo viewport_state;
|
||||||
viewport_state.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
|
viewport_state.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@
|
||||||
* @modified Tom Clay, 2026 - Adapted for ReXGlue runtime
|
* @modified Tom Clay, 2026 - Adapted for ReXGlue runtime
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include <cstdlib>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
|
|
@ -16,6 +17,7 @@
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include <rex/cvar.h>
|
#include <rex/cvar.h>
|
||||||
|
#include <rex/filesystem.h>
|
||||||
#include <rex/logging.h>
|
#include <rex/logging.h>
|
||||||
#include <rex/platform.h>
|
#include <rex/platform.h>
|
||||||
#include <rex/ui/vulkan/instance.h>
|
#include <rex/ui/vulkan/instance.h>
|
||||||
|
|
@ -31,6 +33,21 @@ std::unique_ptr<VulkanInstance> VulkanInstance::Create(const bool with_surface,
|
||||||
const bool try_enable_validation) {
|
const bool try_enable_validation) {
|
||||||
std::unique_ptr<VulkanInstance> vulkan_instance(new VulkanInstance());
|
std::unique_ptr<VulkanInstance> vulkan_instance(new VulkanInstance());
|
||||||
|
|
||||||
|
#if REX_PLATFORM_MAC
|
||||||
|
// The Vulkan loader does not discover MoltenVK from an executable-local SDK
|
||||||
|
// layout by default. Prefer user-provided loader settings, but make local
|
||||||
|
// command-line builds run without requiring VK_ICD_FILENAMES in the shell.
|
||||||
|
if (!std::getenv("VK_ICD_FILENAMES") && !std::getenv("VK_DRIVER_FILES")) {
|
||||||
|
auto local_icd_path =
|
||||||
|
rex::filesystem::GetExecutableFolder() / "share" / "vulkan" / "icd.d" / "MoltenVK_icd.json";
|
||||||
|
if (std::filesystem::is_regular_file(local_icd_path)) {
|
||||||
|
std::string local_icd_path_string = local_icd_path.string();
|
||||||
|
setenv("VK_ICD_FILENAMES", local_icd_path_string.c_str(), 0);
|
||||||
|
setenv("VK_DRIVER_FILES", local_icd_path_string.c_str(), 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
// Load the RenderDoc API if connected.
|
// Load the RenderDoc API if connected.
|
||||||
|
|
||||||
vulkan_instance->renderdoc_api_ = RenderDocAPI::CreateIfConnected();
|
vulkan_instance->renderdoc_api_ = RenderDocAPI::CreateIfConnected();
|
||||||
|
|
@ -40,7 +57,12 @@ std::unique_ptr<VulkanInstance> VulkanInstance::Create(const bool with_surface,
|
||||||
Functions& ifn = vulkan_instance->functions_;
|
Functions& ifn = vulkan_instance->functions_;
|
||||||
|
|
||||||
bool functions_loaded = true;
|
bool functions_loaded = true;
|
||||||
if (!vulkan_instance->loader_.Load(platform::lib_names::kVulkanLoader)) {
|
if (!vulkan_instance->loader_.Load(platform::lib_names::kVulkanLoader)
|
||||||
|
#if REX_PLATFORM_MAC
|
||||||
|
&& !vulkan_instance->loader_.Load(platform::lib_names::kVulkanLoaderFallback)
|
||||||
|
&& !vulkan_instance->loader_.Load(platform::lib_names::kMoltenVK)
|
||||||
|
#endif
|
||||||
|
) {
|
||||||
REXLOG_ERROR("Failed to load {}", platform::lib_names::kVulkanLoader);
|
REXLOG_ERROR("Failed to load {}", platform::lib_names::kVulkanLoader);
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
@ -116,6 +138,11 @@ std::unique_ptr<VulkanInstance> VulkanInstance::Create(const bool with_surface,
|
||||||
// #10.
|
// #10.
|
||||||
requested_extensions.emplace("VK_KHR_win32_surface",
|
requested_extensions.emplace("VK_KHR_win32_surface",
|
||||||
&vulkan_instance->extensions_.ext_KHR_win32_surface);
|
&vulkan_instance->extensions_.ext_KHR_win32_surface);
|
||||||
|
#endif
|
||||||
|
#ifdef VK_USE_PLATFORM_METAL_EXT
|
||||||
|
// #218.
|
||||||
|
requested_extensions.emplace("VK_EXT_metal_surface",
|
||||||
|
&vulkan_instance->extensions_.ext_EXT_metal_surface);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -379,6 +406,11 @@ std::unique_ptr<VulkanInstance> VulkanInstance::Create(const bool with_surface,
|
||||||
if (vulkan_instance->extensions_.ext_KHR_win32_surface) {
|
if (vulkan_instance->extensions_.ext_KHR_win32_surface) {
|
||||||
#include <rex/ui/vulkan/functions/instance_khr_win32_surface.inc>
|
#include <rex/ui/vulkan/functions/instance_khr_win32_surface.inc>
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
#ifdef VK_USE_PLATFORM_METAL_EXT
|
||||||
|
if (vulkan_instance->extensions_.ext_EXT_metal_surface) {
|
||||||
|
#include <rex/ui/vulkan/functions/instance_ext_metal_surface.inc>
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
if (vulkan_instance->extensions_.ext_KHR_surface) {
|
if (vulkan_instance->extensions_.ext_KHR_surface) {
|
||||||
#include <rex/ui/vulkan/functions/instance_khr_surface.inc>
|
#include <rex/ui/vulkan/functions/instance_khr_surface.inc>
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
|
#include <chrono>
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
|
@ -39,6 +40,9 @@
|
||||||
#if REX_PLATFORM_GNU_LINUX
|
#if REX_PLATFORM_GNU_LINUX
|
||||||
#include <rex/ui/surface_gnulinux.h>
|
#include <rex/ui/surface_gnulinux.h>
|
||||||
#endif
|
#endif
|
||||||
|
#if REX_PLATFORM_MAC
|
||||||
|
#include <rex/ui/surface_sdl.h>
|
||||||
|
#endif
|
||||||
#if REX_PLATFORM_WIN32
|
#if REX_PLATFORM_WIN32
|
||||||
#include <rex/ui/surface_win.h>
|
#include <rex/ui/surface_win.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
@ -55,10 +59,77 @@ REXCVAR_DEFINE_BOOL(vulkan_allow_present_mode_mailbox, true, "UI/Vulkan",
|
||||||
REXCVAR_DEFINE_BOOL(vulkan_allow_present_mode_fifo_relaxed, true, "UI/Vulkan",
|
REXCVAR_DEFINE_BOOL(vulkan_allow_present_mode_fifo_relaxed, true, "UI/Vulkan",
|
||||||
"Allow FIFO relaxed present mode");
|
"Allow FIFO relaxed present mode");
|
||||||
|
|
||||||
|
REXCVAR_DEFINE_BOOL(vulkan_present_timing_log, false, "UI/Vulkan",
|
||||||
|
"Log averaged Vulkan host presentation timing")
|
||||||
|
.lifecycle(rex::cvar::Lifecycle::kHotReload)
|
||||||
|
.debug_only();
|
||||||
|
|
||||||
|
REXCVAR_DEFINE_UINT32(vulkan_present_timing_interval, 120, "UI/Vulkan",
|
||||||
|
"Number of presented frames per Vulkan timing log entry")
|
||||||
|
.range(1, 10000)
|
||||||
|
.lifecycle(rex::cvar::Lifecycle::kHotReload)
|
||||||
|
.debug_only();
|
||||||
|
|
||||||
namespace rex {
|
namespace rex {
|
||||||
namespace ui {
|
namespace ui {
|
||||||
namespace vulkan {
|
namespace vulkan {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
struct VulkanPresentTimingStats {
|
||||||
|
uint64_t frames = 0;
|
||||||
|
uint64_t ui_thread_frames = 0;
|
||||||
|
uint64_t acquire_us = 0;
|
||||||
|
uint64_t consume_and_record_us = 0;
|
||||||
|
uint64_t end_command_buffer_us = 0;
|
||||||
|
uint64_t submit_us = 0;
|
||||||
|
uint64_t present_us = 0;
|
||||||
|
uint64_t total_us = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
uint64_t ElapsedUs(std::chrono::steady_clock::time_point start,
|
||||||
|
std::chrono::steady_clock::time_point end) {
|
||||||
|
return uint64_t(std::chrono::duration_cast<std::chrono::microseconds>(end - start).count());
|
||||||
|
}
|
||||||
|
|
||||||
|
void AccumulateAndMaybeLogPresentTiming(bool execute_ui_drawers, uint64_t acquire_us,
|
||||||
|
uint64_t consume_and_record_us,
|
||||||
|
uint64_t end_command_buffer_us, uint64_t submit_us,
|
||||||
|
uint64_t present_us, uint64_t total_us) {
|
||||||
|
static VulkanPresentTimingStats stats;
|
||||||
|
if (!REXCVAR_GET(vulkan_present_timing_log)) {
|
||||||
|
stats = {};
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
++stats.frames;
|
||||||
|
if (execute_ui_drawers) {
|
||||||
|
++stats.ui_thread_frames;
|
||||||
|
}
|
||||||
|
stats.acquire_us += acquire_us;
|
||||||
|
stats.consume_and_record_us += consume_and_record_us;
|
||||||
|
stats.end_command_buffer_us += end_command_buffer_us;
|
||||||
|
stats.submit_us += submit_us;
|
||||||
|
stats.present_us += present_us;
|
||||||
|
stats.total_us += total_us;
|
||||||
|
|
||||||
|
const uint32_t interval = std::max(uint32_t(1), REXCVAR_GET(vulkan_present_timing_interval));
|
||||||
|
if (stats.frames < interval) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint64_t frames = stats.frames;
|
||||||
|
REXLOG_INFO(
|
||||||
|
"VulkanPresenter timing: frames={}, ui_thread={}, avg_total_us={}, avg_acquire_us={}, "
|
||||||
|
"avg_record_us={}, avg_end_cmd_us={}, avg_submit_us={}, avg_present_us={}",
|
||||||
|
frames, stats.ui_thread_frames, stats.total_us / frames, stats.acquire_us / frames,
|
||||||
|
stats.consume_and_record_us / frames, stats.end_command_buffer_us / frames,
|
||||||
|
stats.submit_us / frames, stats.present_us / frames);
|
||||||
|
stats = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
#if defined(REX_HAS_FIDELITYFX_RUNTIME) && REX_HAS_FIDELITYFX_RUNTIME
|
#if defined(REX_HAS_FIDELITYFX_RUNTIME) && REX_HAS_FIDELITYFX_RUNTIME
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
|
|
@ -430,6 +501,11 @@ Surface::TypeFlags VulkanPresenter::GetSurfaceTypesSupportedByInstance(
|
||||||
type_flags |= Surface::kTypeFlag_XcbWindow;
|
type_flags |= Surface::kTypeFlag_XcbWindow;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
#if REX_PLATFORM_MAC
|
||||||
|
if (instance_extensions.ext_EXT_metal_surface) {
|
||||||
|
type_flags |= Surface::kTypeFlag_SDLMetalView;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
#if REX_PLATFORM_WIN32
|
#if REX_PLATFORM_WIN32
|
||||||
if (instance_extensions.ext_KHR_win32_surface) {
|
if (instance_extensions.ext_KHR_win32_surface) {
|
||||||
type_flags |= Surface::kTypeFlag_Win32Hwnd;
|
type_flags |= Surface::kTypeFlag_Win32Hwnd;
|
||||||
|
|
@ -752,6 +828,14 @@ VulkanPresenter::ConnectOrReconnectPaintingToSurfaceFromUIThread(Surface& new_su
|
||||||
// The retirement or destruction of the swapchain here will also cause
|
// The retirement or destruction of the swapchain here will also cause
|
||||||
// awaiting completion of the usage of the swapchain and the surface on the
|
// awaiting completion of the usage of the swapchain and the surface on the
|
||||||
// GPU.
|
// GPU.
|
||||||
|
#if REX_PLATFORM_MAC
|
||||||
|
// Native macOS fullscreen transitions can reconfigure the CAMetalLayer behind
|
||||||
|
// MoltenVK without making the existing VkSurface visibly recoverable. Rebind
|
||||||
|
// from the layer instead of preserving the old surface through the transition.
|
||||||
|
if (paint_context_.vulkan_surface != VK_NULL_HANDLE) {
|
||||||
|
paint_context_.DestroySwapchainAndVulkanSurface();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
if (paint_context_.vulkan_surface != VK_NULL_HANDLE) {
|
if (paint_context_.vulkan_surface != VK_NULL_HANDLE) {
|
||||||
VkSwapchainKHR old_swapchain = paint_context_.PrepareForSwapchainRetirement();
|
VkSwapchainKHR old_swapchain = paint_context_.PrepareForSwapchainRetirement();
|
||||||
bool surface_unusable;
|
bool surface_unusable;
|
||||||
|
|
@ -823,6 +907,18 @@ VulkanPresenter::ConnectOrReconnectPaintingToSurfaceFromUIThread(Surface& new_su
|
||||||
vulkan_surface_create_result = ifn.vkCreateWin32SurfaceKHR(
|
vulkan_surface_create_result = ifn.vkCreateWin32SurfaceKHR(
|
||||||
instance, &surface_create_info, nullptr, &paint_context_.vulkan_surface);
|
instance, &surface_create_info, nullptr, &paint_context_.vulkan_surface);
|
||||||
} break;
|
} break;
|
||||||
|
#endif
|
||||||
|
#if REX_PLATFORM_MAC
|
||||||
|
case Surface::kTypeIndex_SDLMetalView: {
|
||||||
|
auto& sdl_metal_surface = static_cast<const SDLMetalViewSurface&>(new_surface);
|
||||||
|
VkMetalSurfaceCreateInfoEXT surface_create_info;
|
||||||
|
surface_create_info.sType = VK_STRUCTURE_TYPE_METAL_SURFACE_CREATE_INFO_EXT;
|
||||||
|
surface_create_info.pNext = nullptr;
|
||||||
|
surface_create_info.flags = 0;
|
||||||
|
surface_create_info.pLayer = sdl_metal_surface.metal_layer();
|
||||||
|
vulkan_surface_create_result = ifn.vkCreateMetalSurfaceEXT(
|
||||||
|
instance, &surface_create_info, nullptr, &paint_context_.vulkan_surface);
|
||||||
|
} break;
|
||||||
#endif
|
#endif
|
||||||
default:
|
default:
|
||||||
assert_unhandled_case(surface_type);
|
assert_unhandled_case(surface_type);
|
||||||
|
|
@ -1347,7 +1443,8 @@ VkSwapchainKHR VulkanPresenter::PaintContext::CreateSwapchainForVulkanSurface(
|
||||||
// interfering with GPU command processing, and also to allow tearing so
|
// interfering with GPU command processing, and also to allow tearing so
|
||||||
// variable refresh rate may be used where it's available.
|
// variable refresh rate may be used where it's available.
|
||||||
// Note: If the priorities here are changes, update the cvar descriptions.
|
// Note: If the priorities here are changes, update the cvar descriptions.
|
||||||
if (REXCVAR_GET(vulkan_allow_present_mode_immediate) &&
|
constexpr bool allow_immediate_present_mode = true;
|
||||||
|
if (allow_immediate_present_mode && REXCVAR_GET(vulkan_allow_present_mode_immediate) &&
|
||||||
std::find(present_modes.cbegin(), present_modes.cend(), VK_PRESENT_MODE_IMMEDIATE_KHR) !=
|
std::find(present_modes.cbegin(), present_modes.cend(), VK_PRESENT_MODE_IMMEDIATE_KHR) !=
|
||||||
present_modes.cend()) {
|
present_modes.cend()) {
|
||||||
// Allowing tearing to reduce latency, and possibly variable refresh rate
|
// Allowing tearing to reduce latency, and possibly variable refresh rate
|
||||||
|
|
@ -1495,6 +1592,7 @@ bool VulkanPresenter::GuestOutputImage::Initialize() {
|
||||||
}
|
}
|
||||||
|
|
||||||
Presenter::PaintResult VulkanPresenter::PaintAndPresentImpl(bool execute_ui_drawers) {
|
Presenter::PaintResult VulkanPresenter::PaintAndPresentImpl(bool execute_ui_drawers) {
|
||||||
|
const auto timing_start = std::chrono::steady_clock::now();
|
||||||
// Begin the submission in place of the one not currently potentially used on
|
// Begin the submission in place of the one not currently potentially used on
|
||||||
// the GPU.
|
// the GPU.
|
||||||
uint64_t current_paint_submission_index =
|
uint64_t current_paint_submission_index =
|
||||||
|
|
@ -1535,9 +1633,11 @@ Presenter::PaintResult VulkanPresenter::PaintAndPresentImpl(bool execute_ui_draw
|
||||||
|
|
||||||
VkSemaphore acquire_semaphore = paint_submission.acquire_semaphore();
|
VkSemaphore acquire_semaphore = paint_submission.acquire_semaphore();
|
||||||
uint32_t swapchain_image_index;
|
uint32_t swapchain_image_index;
|
||||||
|
const auto timing_acquire_start = std::chrono::steady_clock::now();
|
||||||
VkResult acquire_result =
|
VkResult acquire_result =
|
||||||
dfn.vkAcquireNextImageKHR(device, paint_context_.swapchain, UINT64_MAX, acquire_semaphore,
|
dfn.vkAcquireNextImageKHR(device, paint_context_.swapchain, UINT64_MAX, acquire_semaphore,
|
||||||
VK_NULL_HANDLE, &swapchain_image_index);
|
VK_NULL_HANDLE, &swapchain_image_index);
|
||||||
|
const auto timing_acquire_end = std::chrono::steady_clock::now();
|
||||||
switch (acquire_result) {
|
switch (acquire_result) {
|
||||||
case VK_SUCCESS:
|
case VK_SUCCESS:
|
||||||
case VK_SUBOPTIMAL_KHR:
|
case VK_SUBOPTIMAL_KHR:
|
||||||
|
|
@ -1798,6 +1898,7 @@ Presenter::PaintResult VulkanPresenter::PaintAndPresentImpl(bool execute_ui_draw
|
||||||
paint_context_.guest_output_image_paint_last_submission);
|
paint_context_.guest_output_image_paint_last_submission);
|
||||||
util::DestroyAndNullHandle(dfn.vkDestroyPipeline, device,
|
util::DestroyAndNullHandle(dfn.vkDestroyPipeline, device,
|
||||||
swapchain_effect_pipeline.swapchain_pipeline);
|
swapchain_effect_pipeline.swapchain_pipeline);
|
||||||
|
swapchain_effect_pipeline.swapchain_format = VK_FORMAT_UNDEFINED;
|
||||||
}
|
}
|
||||||
if (swapchain_effect_pipeline.swapchain_pipeline == VK_NULL_HANDLE) {
|
if (swapchain_effect_pipeline.swapchain_pipeline == VK_NULL_HANDLE) {
|
||||||
assert_true(CanGuestOutputPaintEffectBeFinal(swapchain_effect));
|
assert_true(CanGuestOutputPaintEffectBeFinal(swapchain_effect));
|
||||||
|
|
@ -1806,6 +1907,9 @@ Presenter::PaintResult VulkanPresenter::PaintAndPresentImpl(bool execute_ui_draw
|
||||||
swapchain_effect, paint_context_.swapchain_render_pass);
|
swapchain_effect, paint_context_.swapchain_render_pass);
|
||||||
if (swapchain_effect_pipeline.swapchain_pipeline == VK_NULL_HANDLE) {
|
if (swapchain_effect_pipeline.swapchain_pipeline == VK_NULL_HANDLE) {
|
||||||
guest_output_flow.effect_count = 0;
|
guest_output_flow.effect_count = 0;
|
||||||
|
} else {
|
||||||
|
swapchain_effect_pipeline.swapchain_format =
|
||||||
|
paint_context_.swapchain_render_pass_format;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -2119,7 +2223,9 @@ Presenter::PaintResult VulkanPresenter::PaintAndPresentImpl(bool execute_ui_draw
|
||||||
|
|
||||||
dfn.vkCmdEndRenderPass(draw_command_buffer);
|
dfn.vkCmdEndRenderPass(draw_command_buffer);
|
||||||
|
|
||||||
|
const auto timing_end_command_buffer_start = std::chrono::steady_clock::now();
|
||||||
dfn.vkEndCommandBuffer(draw_command_buffer);
|
dfn.vkEndCommandBuffer(draw_command_buffer);
|
||||||
|
const auto timing_end_command_buffer_end = std::chrono::steady_clock::now();
|
||||||
|
|
||||||
VkPipelineStageFlags acquire_semaphore_wait_stage = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
|
VkPipelineStageFlags acquire_semaphore_wait_stage = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
|
||||||
VkCommandBuffer command_buffers[2];
|
VkCommandBuffer command_buffers[2];
|
||||||
|
|
@ -2151,6 +2257,7 @@ Presenter::PaintResult VulkanPresenter::PaintAndPresentImpl(bool execute_ui_draw
|
||||||
submit_info.pCommandBuffers = command_buffers;
|
submit_info.pCommandBuffers = command_buffers;
|
||||||
submit_info.signalSemaphoreCount = 1;
|
submit_info.signalSemaphoreCount = 1;
|
||||||
submit_info.pSignalSemaphores = &present_semaphore;
|
submit_info.pSignalSemaphores = &present_semaphore;
|
||||||
|
const auto timing_submit_start = std::chrono::steady_clock::now();
|
||||||
{
|
{
|
||||||
VulkanSubmissionTracker::FenceAcquisition fence_acqusition(
|
VulkanSubmissionTracker::FenceAcquisition fence_acqusition(
|
||||||
paint_context_.submission_tracker.AcquireFenceToAdvanceSubmission());
|
paint_context_.submission_tracker.AcquireFenceToAdvanceSubmission());
|
||||||
|
|
@ -2192,6 +2299,7 @@ Presenter::PaintResult VulkanPresenter::PaintAndPresentImpl(bool execute_ui_draw
|
||||||
return PaintResult::kNotPresentedConnectionOutdated;
|
return PaintResult::kNotPresentedConnectionOutdated;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
const auto timing_submit_end = std::chrono::steady_clock::now();
|
||||||
|
|
||||||
VkPresentInfoKHR present_info;
|
VkPresentInfoKHR present_info;
|
||||||
present_info.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
|
present_info.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
|
||||||
|
|
@ -2203,11 +2311,20 @@ Presenter::PaintResult VulkanPresenter::PaintAndPresentImpl(bool execute_ui_draw
|
||||||
present_info.pImageIndices = &swapchain_image_index;
|
present_info.pImageIndices = &swapchain_image_index;
|
||||||
present_info.pResults = nullptr;
|
present_info.pResults = nullptr;
|
||||||
VkResult present_result;
|
VkResult present_result;
|
||||||
|
const auto timing_present_start = std::chrono::steady_clock::now();
|
||||||
{
|
{
|
||||||
const VulkanDevice::Queue::Acquisition queue_acquisition =
|
const VulkanDevice::Queue::Acquisition queue_acquisition =
|
||||||
vulkan_device_->AcquireQueue(paint_context_.present_queue_family, 0);
|
vulkan_device_->AcquireQueue(paint_context_.present_queue_family, 0);
|
||||||
present_result = dfn.vkQueuePresentKHR(queue_acquisition.queue(), &present_info);
|
present_result = dfn.vkQueuePresentKHR(queue_acquisition.queue(), &present_info);
|
||||||
}
|
}
|
||||||
|
const auto timing_present_end = std::chrono::steady_clock::now();
|
||||||
|
AccumulateAndMaybeLogPresentTiming(
|
||||||
|
execute_ui_drawers, ElapsedUs(timing_acquire_start, timing_acquire_end),
|
||||||
|
ElapsedUs(timing_acquire_end, timing_submit_start),
|
||||||
|
ElapsedUs(timing_end_command_buffer_start, timing_end_command_buffer_end),
|
||||||
|
ElapsedUs(timing_submit_start, timing_submit_end),
|
||||||
|
ElapsedUs(timing_present_start, timing_present_end),
|
||||||
|
ElapsedUs(timing_start, timing_present_end));
|
||||||
switch (present_result) {
|
switch (present_result) {
|
||||||
case VK_SUCCESS:
|
case VK_SUCCESS:
|
||||||
return PaintResult::kPresented;
|
return PaintResult::kPresented;
|
||||||
|
|
@ -2555,6 +2672,9 @@ VkPipeline VulkanPresenter::CreateGuestOutputPaintPipeline(GuestOutputPaintEffec
|
||||||
VkPipelineInputAssemblyStateCreateInfo input_assembly_state = {};
|
VkPipelineInputAssemblyStateCreateInfo input_assembly_state = {};
|
||||||
input_assembly_state.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
|
input_assembly_state.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
|
||||||
input_assembly_state.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP;
|
input_assembly_state.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP;
|
||||||
|
#if REX_PLATFORM_MAC
|
||||||
|
input_assembly_state.primitiveRestartEnable = VK_TRUE;
|
||||||
|
#endif
|
||||||
|
|
||||||
VkPipelineViewportStateCreateInfo viewport_state = {};
|
VkPipelineViewportStateCreateInfo viewport_state = {};
|
||||||
viewport_state.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
|
viewport_state.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
|
||||||
|
|
|
||||||
|
|
@ -150,6 +150,7 @@ void Window::AddInputListener(WindowInputListener* listener, size_t z_order) {
|
||||||
// indices will result in the skipping being cancelled in the outer loop if
|
// indices will result in the skipping being cancelled in the outer loop if
|
||||||
// an inner one is done, a "greater than" comparison will cause the inner
|
// an inner one is done, a "greater than" comparison will cause the inner
|
||||||
// loop to effectively terminate all outer ones).
|
// loop to effectively terminate all outer ones).
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
auto it_new = std::prev(std::make_reverse_iterator(input_listeners_.emplace(z_order, listener)));
|
auto it_new = std::prev(std::make_reverse_iterator(input_listeners_.emplace(z_order, listener)));
|
||||||
// If adding to layers in between the currently being processed ones (from
|
// If adding to layers in between the currently being processed ones (from
|
||||||
|
|
|
||||||
743
src/ui/window_sdl.cpp
Normal file
743
src/ui/window_sdl.cpp
Normal file
|
|
@ -0,0 +1,743 @@
|
||||||
|
#include <rex/ui/window_sdl.h>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cmath>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
#include <rex/assert.h>
|
||||||
|
#include <rex/cvar.h>
|
||||||
|
#include <rex/graphics/flags.h>
|
||||||
|
#include <rex/graphics/video_mode_util.h>
|
||||||
|
#include <rex/logging.h>
|
||||||
|
#include <rex/platform.h>
|
||||||
|
#include <rex/ui/surface_sdl.h>
|
||||||
|
#include <rex/ui/virtual_key.h>
|
||||||
|
|
||||||
|
#include <SDL3/SDL_hints.h>
|
||||||
|
#include <SDL3/SDL_keyboard.h>
|
||||||
|
#include <SDL3/SDL_mouse.h>
|
||||||
|
#include <SDL3/SDL_scancode.h>
|
||||||
|
#include <SDL3/SDL_timer.h>
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
constexpr uint32_t kDefaultDpi = 96;
|
||||||
|
constexpr uint32_t kDefaultCursorAutoHideMilliseconds = 1000;
|
||||||
|
constexpr uint32_t kCursorAutoHideEvent = SDL_EVENT_USER + 1;
|
||||||
|
|
||||||
|
REXCVAR_DEFINE_BOOL(sdl_high_pixel_density, !REX_PLATFORM_MAC, "UI/SDL",
|
||||||
|
"Request high-DPI backing pixels for SDL windows")
|
||||||
|
.lifecycle(rex::cvar::Lifecycle::kRequiresRestart);
|
||||||
|
|
||||||
|
std::unordered_map<SDL_WindowID, rex::ui::SDLWindow*>& WindowMap() {
|
||||||
|
static std::unordered_map<SDL_WindowID, rex::ui::SDLWindow*> windows;
|
||||||
|
return windows;
|
||||||
|
}
|
||||||
|
|
||||||
|
Uint32 SDLCALL AutoHideCursorTimerCallback(void* userdata, SDL_TimerID timer_id,
|
||||||
|
Uint32 interval) {
|
||||||
|
(void)interval;
|
||||||
|
SDL_Event event = {};
|
||||||
|
event.type = kCursorAutoHideEvent;
|
||||||
|
event.user.data1 = userdata;
|
||||||
|
event.user.data2 = reinterpret_cast<void*>(uintptr_t(timer_id));
|
||||||
|
SDL_PushEvent(&event);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t ResolveWindowWidth(uint32_t requested_width) {
|
||||||
|
if (REXCVAR_GET(window_width) > 0) {
|
||||||
|
return uint32_t(REXCVAR_GET(window_width));
|
||||||
|
}
|
||||||
|
if (!rex::cvar::HasNonDefaultValue("window_width")) {
|
||||||
|
if (rex::cvar::HasNonDefaultValue("video_mode_width") && REXCVAR_GET(video_mode_width) > 0) {
|
||||||
|
return uint32_t(std::clamp(REXCVAR_GET(video_mode_width), 1, 8192));
|
||||||
|
}
|
||||||
|
int32_t preset_width = 0;
|
||||||
|
int32_t preset_height = 0;
|
||||||
|
if (rex::graphics::video_mode_util::TryGetResolutionPresetFromCVar(preset_width,
|
||||||
|
preset_height)) {
|
||||||
|
return uint32_t(std::clamp(preset_width, 1, 8192));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return requested_width;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t ResolveWindowHeight(uint32_t requested_height) {
|
||||||
|
if (REXCVAR_GET(window_height) > 0) {
|
||||||
|
return uint32_t(REXCVAR_GET(window_height));
|
||||||
|
}
|
||||||
|
if (!rex::cvar::HasNonDefaultValue("window_height")) {
|
||||||
|
if (rex::cvar::HasNonDefaultValue("video_mode_height") && REXCVAR_GET(video_mode_height) > 0) {
|
||||||
|
return uint32_t(std::clamp(REXCVAR_GET(video_mode_height), 1, 8192));
|
||||||
|
}
|
||||||
|
int32_t preset_width = 0;
|
||||||
|
int32_t preset_height = 0;
|
||||||
|
if (rex::graphics::video_mode_util::TryGetResolutionPresetFromCVar(preset_width,
|
||||||
|
preset_height)) {
|
||||||
|
return uint32_t(std::clamp(preset_height, 1, 8192));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return requested_height;
|
||||||
|
}
|
||||||
|
|
||||||
|
using rex::ui::VirtualKey;
|
||||||
|
|
||||||
|
VirtualKey TranslateSDLScancode(SDL_Scancode scancode) {
|
||||||
|
switch (scancode) {
|
||||||
|
case SDL_SCANCODE_F1:
|
||||||
|
return VirtualKey::kF1;
|
||||||
|
case SDL_SCANCODE_F2:
|
||||||
|
return VirtualKey::kF2;
|
||||||
|
case SDL_SCANCODE_F3:
|
||||||
|
return VirtualKey::kF3;
|
||||||
|
case SDL_SCANCODE_F4:
|
||||||
|
return VirtualKey::kF4;
|
||||||
|
case SDL_SCANCODE_F5:
|
||||||
|
return VirtualKey::kF5;
|
||||||
|
case SDL_SCANCODE_F6:
|
||||||
|
return VirtualKey::kF6;
|
||||||
|
case SDL_SCANCODE_F7:
|
||||||
|
return VirtualKey::kF7;
|
||||||
|
case SDL_SCANCODE_F8:
|
||||||
|
return VirtualKey::kF8;
|
||||||
|
case SDL_SCANCODE_F9:
|
||||||
|
return VirtualKey::kF9;
|
||||||
|
case SDL_SCANCODE_F10:
|
||||||
|
return VirtualKey::kF10;
|
||||||
|
case SDL_SCANCODE_F11:
|
||||||
|
return VirtualKey::kF11;
|
||||||
|
case SDL_SCANCODE_F12:
|
||||||
|
return VirtualKey::kF12;
|
||||||
|
case SDL_SCANCODE_ESCAPE:
|
||||||
|
return VirtualKey::kEscape;
|
||||||
|
case SDL_SCANCODE_RETURN:
|
||||||
|
case SDL_SCANCODE_KP_ENTER:
|
||||||
|
return VirtualKey::kReturn;
|
||||||
|
case SDL_SCANCODE_SPACE:
|
||||||
|
return VirtualKey::kSpace;
|
||||||
|
case SDL_SCANCODE_TAB:
|
||||||
|
return VirtualKey::kTab;
|
||||||
|
case SDL_SCANCODE_BACKSPACE:
|
||||||
|
return VirtualKey::kBack;
|
||||||
|
case SDL_SCANCODE_DELETE:
|
||||||
|
return VirtualKey::kDelete;
|
||||||
|
case SDL_SCANCODE_INSERT:
|
||||||
|
return VirtualKey::kInsert;
|
||||||
|
case SDL_SCANCODE_HOME:
|
||||||
|
return VirtualKey::kHome;
|
||||||
|
case SDL_SCANCODE_END:
|
||||||
|
return VirtualKey::kEnd;
|
||||||
|
case SDL_SCANCODE_PAGEUP:
|
||||||
|
return VirtualKey::kPrior;
|
||||||
|
case SDL_SCANCODE_PAGEDOWN:
|
||||||
|
return VirtualKey::kNext;
|
||||||
|
case SDL_SCANCODE_LEFT:
|
||||||
|
return VirtualKey::kLeft;
|
||||||
|
case SDL_SCANCODE_RIGHT:
|
||||||
|
return VirtualKey::kRight;
|
||||||
|
case SDL_SCANCODE_UP:
|
||||||
|
return VirtualKey::kUp;
|
||||||
|
case SDL_SCANCODE_DOWN:
|
||||||
|
return VirtualKey::kDown;
|
||||||
|
case SDL_SCANCODE_LSHIFT:
|
||||||
|
return VirtualKey::kLShift;
|
||||||
|
case SDL_SCANCODE_RSHIFT:
|
||||||
|
return VirtualKey::kRShift;
|
||||||
|
case SDL_SCANCODE_LCTRL:
|
||||||
|
return VirtualKey::kLControl;
|
||||||
|
case SDL_SCANCODE_RCTRL:
|
||||||
|
return VirtualKey::kRControl;
|
||||||
|
case SDL_SCANCODE_LALT:
|
||||||
|
return VirtualKey::kLMenu;
|
||||||
|
case SDL_SCANCODE_RALT:
|
||||||
|
return VirtualKey::kRMenu;
|
||||||
|
case SDL_SCANCODE_LGUI:
|
||||||
|
return VirtualKey::kLWin;
|
||||||
|
case SDL_SCANCODE_RGUI:
|
||||||
|
return VirtualKey::kRWin;
|
||||||
|
case SDL_SCANCODE_APPLICATION:
|
||||||
|
return VirtualKey::kApps;
|
||||||
|
case SDL_SCANCODE_CAPSLOCK:
|
||||||
|
return VirtualKey::kCapital;
|
||||||
|
case SDL_SCANCODE_NUMLOCKCLEAR:
|
||||||
|
return VirtualKey::kNumLock;
|
||||||
|
case SDL_SCANCODE_SCROLLLOCK:
|
||||||
|
return VirtualKey::kScroll;
|
||||||
|
case SDL_SCANCODE_PRINTSCREEN:
|
||||||
|
return VirtualKey::kSnapshot;
|
||||||
|
case SDL_SCANCODE_PAUSE:
|
||||||
|
return VirtualKey::kPause;
|
||||||
|
case SDL_SCANCODE_0:
|
||||||
|
return VirtualKey::k0;
|
||||||
|
case SDL_SCANCODE_1:
|
||||||
|
return VirtualKey::k1;
|
||||||
|
case SDL_SCANCODE_2:
|
||||||
|
return VirtualKey::k2;
|
||||||
|
case SDL_SCANCODE_3:
|
||||||
|
return VirtualKey::k3;
|
||||||
|
case SDL_SCANCODE_4:
|
||||||
|
return VirtualKey::k4;
|
||||||
|
case SDL_SCANCODE_5:
|
||||||
|
return VirtualKey::k5;
|
||||||
|
case SDL_SCANCODE_6:
|
||||||
|
return VirtualKey::k6;
|
||||||
|
case SDL_SCANCODE_7:
|
||||||
|
return VirtualKey::k7;
|
||||||
|
case SDL_SCANCODE_8:
|
||||||
|
return VirtualKey::k8;
|
||||||
|
case SDL_SCANCODE_9:
|
||||||
|
return VirtualKey::k9;
|
||||||
|
case SDL_SCANCODE_A:
|
||||||
|
return VirtualKey::kA;
|
||||||
|
case SDL_SCANCODE_B:
|
||||||
|
return VirtualKey::kB;
|
||||||
|
case SDL_SCANCODE_C:
|
||||||
|
return VirtualKey::kC;
|
||||||
|
case SDL_SCANCODE_D:
|
||||||
|
return VirtualKey::kD;
|
||||||
|
case SDL_SCANCODE_E:
|
||||||
|
return VirtualKey::kE;
|
||||||
|
case SDL_SCANCODE_F:
|
||||||
|
return VirtualKey::kF;
|
||||||
|
case SDL_SCANCODE_G:
|
||||||
|
return VirtualKey::kG;
|
||||||
|
case SDL_SCANCODE_H:
|
||||||
|
return VirtualKey::kH;
|
||||||
|
case SDL_SCANCODE_I:
|
||||||
|
return VirtualKey::kI;
|
||||||
|
case SDL_SCANCODE_J:
|
||||||
|
return VirtualKey::kJ;
|
||||||
|
case SDL_SCANCODE_K:
|
||||||
|
return VirtualKey::kK;
|
||||||
|
case SDL_SCANCODE_L:
|
||||||
|
return VirtualKey::kL;
|
||||||
|
case SDL_SCANCODE_M:
|
||||||
|
return VirtualKey::kM;
|
||||||
|
case SDL_SCANCODE_N:
|
||||||
|
return VirtualKey::kN;
|
||||||
|
case SDL_SCANCODE_O:
|
||||||
|
return VirtualKey::kO;
|
||||||
|
case SDL_SCANCODE_P:
|
||||||
|
return VirtualKey::kP;
|
||||||
|
case SDL_SCANCODE_Q:
|
||||||
|
return VirtualKey::kQ;
|
||||||
|
case SDL_SCANCODE_R:
|
||||||
|
return VirtualKey::kR;
|
||||||
|
case SDL_SCANCODE_S:
|
||||||
|
return VirtualKey::kS;
|
||||||
|
case SDL_SCANCODE_T:
|
||||||
|
return VirtualKey::kT;
|
||||||
|
case SDL_SCANCODE_U:
|
||||||
|
return VirtualKey::kU;
|
||||||
|
case SDL_SCANCODE_V:
|
||||||
|
return VirtualKey::kV;
|
||||||
|
case SDL_SCANCODE_W:
|
||||||
|
return VirtualKey::kW;
|
||||||
|
case SDL_SCANCODE_X:
|
||||||
|
return VirtualKey::kX;
|
||||||
|
case SDL_SCANCODE_Y:
|
||||||
|
return VirtualKey::kY;
|
||||||
|
case SDL_SCANCODE_Z:
|
||||||
|
return VirtualKey::kZ;
|
||||||
|
case SDL_SCANCODE_KP_0:
|
||||||
|
return VirtualKey::kNumpad0;
|
||||||
|
case SDL_SCANCODE_KP_1:
|
||||||
|
return VirtualKey::kNumpad1;
|
||||||
|
case SDL_SCANCODE_KP_2:
|
||||||
|
return VirtualKey::kNumpad2;
|
||||||
|
case SDL_SCANCODE_KP_3:
|
||||||
|
return VirtualKey::kNumpad3;
|
||||||
|
case SDL_SCANCODE_KP_4:
|
||||||
|
return VirtualKey::kNumpad4;
|
||||||
|
case SDL_SCANCODE_KP_5:
|
||||||
|
return VirtualKey::kNumpad5;
|
||||||
|
case SDL_SCANCODE_KP_6:
|
||||||
|
return VirtualKey::kNumpad6;
|
||||||
|
case SDL_SCANCODE_KP_7:
|
||||||
|
return VirtualKey::kNumpad7;
|
||||||
|
case SDL_SCANCODE_KP_8:
|
||||||
|
return VirtualKey::kNumpad8;
|
||||||
|
case SDL_SCANCODE_KP_9:
|
||||||
|
return VirtualKey::kNumpad9;
|
||||||
|
case SDL_SCANCODE_KP_PLUS:
|
||||||
|
return VirtualKey::kAdd;
|
||||||
|
case SDL_SCANCODE_KP_MINUS:
|
||||||
|
return VirtualKey::kSubtract;
|
||||||
|
case SDL_SCANCODE_KP_MULTIPLY:
|
||||||
|
return VirtualKey::kMultiply;
|
||||||
|
case SDL_SCANCODE_KP_DIVIDE:
|
||||||
|
return VirtualKey::kDivide;
|
||||||
|
case SDL_SCANCODE_KP_DECIMAL:
|
||||||
|
return VirtualKey::kDecimal;
|
||||||
|
case SDL_SCANCODE_GRAVE:
|
||||||
|
return VirtualKey::kOem3;
|
||||||
|
case SDL_SCANCODE_MINUS:
|
||||||
|
return VirtualKey::kOemMinus;
|
||||||
|
case SDL_SCANCODE_EQUALS:
|
||||||
|
return VirtualKey::kOemPlus;
|
||||||
|
case SDL_SCANCODE_COMMA:
|
||||||
|
return VirtualKey::kOemComma;
|
||||||
|
case SDL_SCANCODE_PERIOD:
|
||||||
|
return VirtualKey::kOemPeriod;
|
||||||
|
case SDL_SCANCODE_SEMICOLON:
|
||||||
|
return VirtualKey::kOem1;
|
||||||
|
case SDL_SCANCODE_SLASH:
|
||||||
|
return VirtualKey::kOem2;
|
||||||
|
case SDL_SCANCODE_BACKSLASH:
|
||||||
|
return VirtualKey::kOem5;
|
||||||
|
case SDL_SCANCODE_LEFTBRACKET:
|
||||||
|
return VirtualKey::kOem4;
|
||||||
|
case SDL_SCANCODE_RIGHTBRACKET:
|
||||||
|
return VirtualKey::kOem6;
|
||||||
|
case SDL_SCANCODE_APOSTROPHE:
|
||||||
|
return VirtualKey::kOem7;
|
||||||
|
default:
|
||||||
|
return VirtualKey::kNone;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rex::ui::MouseEvent::Button TranslateMouseButton(uint8_t button) {
|
||||||
|
switch (button) {
|
||||||
|
case SDL_BUTTON_LEFT:
|
||||||
|
return rex::ui::MouseEvent::Button::kLeft;
|
||||||
|
case SDL_BUTTON_RIGHT:
|
||||||
|
return rex::ui::MouseEvent::Button::kRight;
|
||||||
|
case SDL_BUTTON_MIDDLE:
|
||||||
|
return rex::ui::MouseEvent::Button::kMiddle;
|
||||||
|
case SDL_BUTTON_X1:
|
||||||
|
return rex::ui::MouseEvent::Button::kX1;
|
||||||
|
case SDL_BUTTON_X2:
|
||||||
|
return rex::ui::MouseEvent::Button::kX2;
|
||||||
|
default:
|
||||||
|
return rex::ui::MouseEvent::Button::kNone;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
namespace rex {
|
||||||
|
namespace ui {
|
||||||
|
|
||||||
|
std::unique_ptr<Window> Window::Create(WindowedAppContext& app_context,
|
||||||
|
const std::string_view title, uint32_t desired_logical_width,
|
||||||
|
uint32_t desired_logical_height) {
|
||||||
|
desired_logical_width = ResolveWindowWidth(desired_logical_width);
|
||||||
|
desired_logical_height = ResolveWindowHeight(desired_logical_height);
|
||||||
|
return std::make_unique<SDLWindow>(app_context, title, desired_logical_width,
|
||||||
|
desired_logical_height);
|
||||||
|
}
|
||||||
|
|
||||||
|
SDLWindow::SDLWindow(WindowedAppContext& app_context, const std::string_view title,
|
||||||
|
uint32_t desired_logical_width, uint32_t desired_logical_height)
|
||||||
|
: Window(app_context, title, desired_logical_width, desired_logical_height) {}
|
||||||
|
|
||||||
|
SDLWindow::~SDLWindow() {
|
||||||
|
EnterDestructor();
|
||||||
|
RemoveCursorAutoHideTimer();
|
||||||
|
if (window_) {
|
||||||
|
WindowMap().erase(SDL_GetWindowID(window_));
|
||||||
|
}
|
||||||
|
if (metal_view_) {
|
||||||
|
SDL_Metal_DestroyView(metal_view_);
|
||||||
|
metal_view_ = nullptr;
|
||||||
|
metal_layer_ = nullptr;
|
||||||
|
}
|
||||||
|
if (window_) {
|
||||||
|
SDL_DestroyWindow(window_);
|
||||||
|
window_ = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SDLWindow::HandleSDLEvent(const SDL_Event& event) {
|
||||||
|
SDL_WindowID window_id = 0;
|
||||||
|
switch (event.type) {
|
||||||
|
case SDL_EVENT_WINDOW_SHOWN:
|
||||||
|
case SDL_EVENT_WINDOW_HIDDEN:
|
||||||
|
case SDL_EVENT_WINDOW_EXPOSED:
|
||||||
|
case SDL_EVENT_WINDOW_MOVED:
|
||||||
|
case SDL_EVENT_WINDOW_RESIZED:
|
||||||
|
case SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED:
|
||||||
|
case SDL_EVENT_WINDOW_METAL_VIEW_RESIZED:
|
||||||
|
case SDL_EVENT_WINDOW_FOCUS_GAINED:
|
||||||
|
case SDL_EVENT_WINDOW_FOCUS_LOST:
|
||||||
|
case SDL_EVENT_WINDOW_CLOSE_REQUESTED:
|
||||||
|
case SDL_EVENT_WINDOW_DISPLAY_SCALE_CHANGED:
|
||||||
|
case SDL_EVENT_WINDOW_ENTER_FULLSCREEN:
|
||||||
|
case SDL_EVENT_WINDOW_LEAVE_FULLSCREEN:
|
||||||
|
window_id = event.window.windowID;
|
||||||
|
break;
|
||||||
|
case kCursorAutoHideEvent:
|
||||||
|
window_id = SDL_WindowID(reinterpret_cast<uintptr_t>(event.user.data1));
|
||||||
|
break;
|
||||||
|
case SDL_EVENT_KEY_DOWN:
|
||||||
|
case SDL_EVENT_KEY_UP:
|
||||||
|
window_id = event.key.windowID;
|
||||||
|
break;
|
||||||
|
case SDL_EVENT_MOUSE_BUTTON_DOWN:
|
||||||
|
case SDL_EVENT_MOUSE_BUTTON_UP:
|
||||||
|
window_id = event.button.windowID;
|
||||||
|
break;
|
||||||
|
case SDL_EVENT_MOUSE_MOTION:
|
||||||
|
window_id = event.motion.windowID;
|
||||||
|
break;
|
||||||
|
case SDL_EVENT_MOUSE_WHEEL:
|
||||||
|
window_id = event.wheel.windowID;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto it = WindowMap().find(window_id);
|
||||||
|
if (it != WindowMap().end()) {
|
||||||
|
it->second->HandleEvent(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t SDLWindow::GetLatestDpiImpl() const { return dpi_ ? dpi_ : kDefaultDpi; }
|
||||||
|
|
||||||
|
bool SDLWindow::OpenImpl() {
|
||||||
|
SDL_SetHint(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, "1");
|
||||||
|
SDL_WindowFlags flags = SDL_WINDOW_RESIZABLE;
|
||||||
|
if (REXCVAR_GET(sdl_high_pixel_density)) {
|
||||||
|
flags |= SDL_WINDOW_HIGH_PIXEL_DENSITY;
|
||||||
|
}
|
||||||
|
if (IsFullscreen()) {
|
||||||
|
flags |= SDL_WINDOW_FULLSCREEN;
|
||||||
|
}
|
||||||
|
|
||||||
|
window_ = SDL_CreateWindow(GetTitle().c_str(), int(GetDesiredLogicalWidth()),
|
||||||
|
int(GetDesiredLogicalHeight()), flags);
|
||||||
|
if (!window_) {
|
||||||
|
REXLOG_ERROR("SDLWindow: Failed to create window: {}", SDL_GetError());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
WindowMap().emplace(SDL_GetWindowID(window_), this);
|
||||||
|
|
||||||
|
metal_view_ = SDL_Metal_CreateView(window_);
|
||||||
|
if (!metal_view_) {
|
||||||
|
REXLOG_ERROR("SDLWindow: Failed to create Metal view: {}", SDL_GetError());
|
||||||
|
WindowMap().erase(SDL_GetWindowID(window_));
|
||||||
|
SDL_DestroyWindow(window_);
|
||||||
|
window_ = nullptr;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
metal_layer_ = SDL_Metal_GetLayer(metal_view_);
|
||||||
|
if (!metal_layer_) {
|
||||||
|
REXLOG_ERROR("SDLWindow: Failed to get Metal layer: {}", SDL_GetError());
|
||||||
|
SDL_Metal_DestroyView(metal_view_);
|
||||||
|
metal_view_ = nullptr;
|
||||||
|
WindowMap().erase(SDL_GetWindowID(window_));
|
||||||
|
SDL_DestroyWindow(window_);
|
||||||
|
window_ = nullptr;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
dpi_ = QueryDpi();
|
||||||
|
SDL_ShowWindow(window_);
|
||||||
|
if (IsMouseCaptureRequested()) {
|
||||||
|
SDL_SetWindowMouseGrab(window_, true);
|
||||||
|
}
|
||||||
|
ApplyNewCursorVisibility(CursorVisibility::kVisible);
|
||||||
|
|
||||||
|
WindowDestructionReceiver destruction_receiver(this);
|
||||||
|
HandleSizeUpdate(destruction_receiver);
|
||||||
|
if (destruction_receiver.IsWindowDestroyedOrClosed()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
OnFocusUpdate((SDL_GetWindowFlags(window_) & SDL_WINDOW_INPUT_FOCUS) != 0, destruction_receiver);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SDLWindow::RequestCloseImpl() {
|
||||||
|
WindowDestructionReceiver destruction_receiver(this);
|
||||||
|
OnBeforeClose(destruction_receiver);
|
||||||
|
if (!destruction_receiver.IsWindowDestroyed()) {
|
||||||
|
RemoveCursorAutoHideTimer();
|
||||||
|
if (metal_view_) {
|
||||||
|
SDL_Metal_DestroyView(metal_view_);
|
||||||
|
metal_view_ = nullptr;
|
||||||
|
metal_layer_ = nullptr;
|
||||||
|
}
|
||||||
|
if (window_) {
|
||||||
|
WindowMap().erase(SDL_GetWindowID(window_));
|
||||||
|
SDL_DestroyWindow(window_);
|
||||||
|
window_ = nullptr;
|
||||||
|
}
|
||||||
|
OnAfterClose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SDLWindow::ApplyNewFullscreen() {
|
||||||
|
if (window_) {
|
||||||
|
SDL_SetWindowFullscreen(window_, IsFullscreen());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SDLWindow::ApplyNewTitle() {
|
||||||
|
if (window_) {
|
||||||
|
SDL_SetWindowTitle(window_, GetTitle().c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SDLWindow::ApplyNewMouseCapture() {
|
||||||
|
if (window_) {
|
||||||
|
SDL_SetWindowMouseGrab(window_, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SDLWindow::ApplyNewMouseRelease() {
|
||||||
|
if (window_) {
|
||||||
|
SDL_SetWindowMouseGrab(window_, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SDLWindow::ApplyNewCursorVisibility(CursorVisibility old_cursor_visibility) {
|
||||||
|
(void)old_cursor_visibility;
|
||||||
|
RemoveCursorAutoHideTimer();
|
||||||
|
switch (GetCursorVisibility()) {
|
||||||
|
case CursorVisibility::kVisible:
|
||||||
|
cursor_currently_auto_hidden_ = false;
|
||||||
|
SDL_ShowCursor();
|
||||||
|
break;
|
||||||
|
case CursorVisibility::kAutoHidden:
|
||||||
|
cursor_currently_auto_hidden_ = true;
|
||||||
|
SDL_HideCursor();
|
||||||
|
break;
|
||||||
|
case CursorVisibility::kHidden:
|
||||||
|
cursor_currently_auto_hidden_ = true;
|
||||||
|
SDL_HideCursor();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SDLWindow::FocusImpl() {
|
||||||
|
if (window_) {
|
||||||
|
SDL_RaiseWindow(window_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<Surface> SDLWindow::CreateSurfaceImpl(Surface::TypeFlags allowed_types) {
|
||||||
|
if (!(allowed_types & Surface::kTypeFlag_SDLMetalView)) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return std::make_unique<SDLMetalViewSurface>(window_, metal_view_, metal_layer_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SDLWindow::RequestPaintImpl() {
|
||||||
|
bool expected = false;
|
||||||
|
if (!paint_event_queued_.compare_exchange_strong(expected, true, std::memory_order_acq_rel,
|
||||||
|
std::memory_order_acquire)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_Event event = {};
|
||||||
|
event.type = SDL_EVENT_WINDOW_EXPOSED;
|
||||||
|
event.window.windowID = SDL_GetWindowID(window_);
|
||||||
|
if (!SDL_PushEvent(&event)) {
|
||||||
|
paint_event_queued_.store(false, std::memory_order_release);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SDLWindow::HandleEvent(const SDL_Event& event) {
|
||||||
|
WindowDestructionReceiver destruction_receiver(this);
|
||||||
|
switch (event.type) {
|
||||||
|
case SDL_EVENT_WINDOW_EXPOSED:
|
||||||
|
paint_event_queued_.store(false, std::memory_order_release);
|
||||||
|
OnPaint();
|
||||||
|
break;
|
||||||
|
case SDL_EVENT_WINDOW_RESIZED:
|
||||||
|
case SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED:
|
||||||
|
case SDL_EVENT_WINDOW_METAL_VIEW_RESIZED:
|
||||||
|
case SDL_EVENT_WINDOW_DISPLAY_SCALE_CHANGED:
|
||||||
|
HandleSizeUpdate(destruction_receiver);
|
||||||
|
break;
|
||||||
|
case SDL_EVENT_WINDOW_FOCUS_GAINED:
|
||||||
|
OnFocusUpdate(true, destruction_receiver);
|
||||||
|
break;
|
||||||
|
case SDL_EVENT_WINDOW_FOCUS_LOST:
|
||||||
|
OnFocusUpdate(false, destruction_receiver);
|
||||||
|
break;
|
||||||
|
case SDL_EVENT_WINDOW_ENTER_FULLSCREEN:
|
||||||
|
OnDesiredFullscreenUpdate(true);
|
||||||
|
break;
|
||||||
|
case SDL_EVENT_WINDOW_LEAVE_FULLSCREEN:
|
||||||
|
OnDesiredFullscreenUpdate(false);
|
||||||
|
break;
|
||||||
|
case SDL_EVENT_WINDOW_CLOSE_REQUESTED:
|
||||||
|
#if REX_PLATFORM_MAC
|
||||||
|
app_context().QuitFromUIThread();
|
||||||
|
#else
|
||||||
|
RequestCloseImpl();
|
||||||
|
app_context().RequestDeferredQuit();
|
||||||
|
#endif
|
||||||
|
break;
|
||||||
|
case kCursorAutoHideEvent:
|
||||||
|
if (GetCursorVisibility() == CursorVisibility::kAutoHidden &&
|
||||||
|
SDL_TimerID(reinterpret_cast<uintptr_t>(event.user.data2)) == cursor_auto_hide_timer_) {
|
||||||
|
cursor_auto_hide_timer_ = 0;
|
||||||
|
cursor_currently_auto_hidden_ = true;
|
||||||
|
SDL_HideCursor();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case SDL_EVENT_KEY_DOWN:
|
||||||
|
HandleKey(event.key, true, destruction_receiver);
|
||||||
|
break;
|
||||||
|
case SDL_EVENT_KEY_UP:
|
||||||
|
HandleKey(event.key, false, destruction_receiver);
|
||||||
|
break;
|
||||||
|
case SDL_EVENT_MOUSE_BUTTON_DOWN:
|
||||||
|
HandleMouseButton(event.button, true, destruction_receiver);
|
||||||
|
break;
|
||||||
|
case SDL_EVENT_MOUSE_BUTTON_UP:
|
||||||
|
HandleMouseButton(event.button, false, destruction_receiver);
|
||||||
|
break;
|
||||||
|
case SDL_EVENT_MOUSE_MOTION:
|
||||||
|
HandleMouseMotion(event.motion, destruction_receiver);
|
||||||
|
break;
|
||||||
|
case SDL_EVENT_MOUSE_WHEEL:
|
||||||
|
HandleMouseWheel(event.wheel, destruction_receiver);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SDLWindow::HandleSizeUpdate(WindowDestructionReceiver& destruction_receiver) {
|
||||||
|
dpi_ = QueryDpi();
|
||||||
|
int width = 0;
|
||||||
|
int height = 0;
|
||||||
|
if (!SDL_GetWindowSizeInPixels(window_, &width, &height)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!IsFullscreen()) {
|
||||||
|
int logical_width = 0;
|
||||||
|
int logical_height = 0;
|
||||||
|
if (SDL_GetWindowSize(window_, &logical_width, &logical_height)) {
|
||||||
|
OnDesiredLogicalSizeUpdate(uint32_t(std::max(logical_width, 0)),
|
||||||
|
uint32_t(std::max(logical_height, 0)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
OnActualSizeUpdate(uint32_t(std::max(width, 0)), uint32_t(std::max(height, 0)),
|
||||||
|
destruction_receiver);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SDLWindow::HandleKey(const SDL_KeyboardEvent& event, bool down,
|
||||||
|
WindowDestructionReceiver& destruction_receiver) {
|
||||||
|
SDL_Keymod mod = SDL_GetModState();
|
||||||
|
KeyEvent e(this, TranslateSDLScancode(event.scancode), event.repeat ? 2 : 1, !down,
|
||||||
|
(mod & SDL_KMOD_SHIFT) != 0, (mod & SDL_KMOD_CTRL) != 0,
|
||||||
|
(mod & SDL_KMOD_ALT) != 0, (mod & SDL_KMOD_GUI) != 0);
|
||||||
|
if (down) {
|
||||||
|
OnKeyDown(e, destruction_receiver);
|
||||||
|
} else {
|
||||||
|
OnKeyUp(e, destruction_receiver);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SDLWindow::HandleMouseButton(const SDL_MouseButtonEvent& event, bool down,
|
||||||
|
WindowDestructionReceiver& destruction_receiver) {
|
||||||
|
RevealAutoHiddenCursor();
|
||||||
|
int32_t physical_x = 0;
|
||||||
|
int32_t physical_y = 0;
|
||||||
|
WindowPointToPhysical(event.x, event.y, physical_x, physical_y);
|
||||||
|
MouseEvent e(this, TranslateMouseButton(event.button), physical_x, physical_y);
|
||||||
|
if (down) {
|
||||||
|
OnMouseDown(e, destruction_receiver);
|
||||||
|
} else {
|
||||||
|
OnMouseUp(e, destruction_receiver);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SDLWindow::HandleMouseMotion(const SDL_MouseMotionEvent& event,
|
||||||
|
WindowDestructionReceiver& destruction_receiver) {
|
||||||
|
RevealAutoHiddenCursor();
|
||||||
|
int32_t physical_x = 0;
|
||||||
|
int32_t physical_y = 0;
|
||||||
|
WindowPointToPhysical(event.x, event.y, physical_x, physical_y);
|
||||||
|
MouseEvent e(this, MouseEvent::Button::kNone, physical_x, physical_y);
|
||||||
|
OnMouseMove(e, destruction_receiver);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SDLWindow::HandleMouseWheel(const SDL_MouseWheelEvent& event,
|
||||||
|
WindowDestructionReceiver& destruction_receiver) {
|
||||||
|
RevealAutoHiddenCursor();
|
||||||
|
int32_t physical_x = 0;
|
||||||
|
int32_t physical_y = 0;
|
||||||
|
WindowPointToPhysical(event.mouse_x, event.mouse_y, physical_x, physical_y);
|
||||||
|
MouseEvent e(this, MouseEvent::Button::kNone, physical_x, physical_y,
|
||||||
|
int32_t(std::lround(event.x * double(MouseEvent::kScrollPerDetent))),
|
||||||
|
int32_t(std::lround(event.y * double(MouseEvent::kScrollPerDetent))));
|
||||||
|
OnMouseWheel(e, destruction_receiver);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SDLWindow::RevealAutoHiddenCursor() {
|
||||||
|
if (GetCursorVisibility() != CursorVisibility::kAutoHidden) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (cursor_currently_auto_hidden_) {
|
||||||
|
cursor_currently_auto_hidden_ = false;
|
||||||
|
SDL_ShowCursor();
|
||||||
|
}
|
||||||
|
SetCursorAutoHideTimer();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SDLWindow::SetCursorAutoHideTimer() {
|
||||||
|
RemoveCursorAutoHideTimer();
|
||||||
|
if (!window_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
cursor_auto_hide_timer_ =
|
||||||
|
SDL_AddTimer(kDefaultCursorAutoHideMilliseconds, AutoHideCursorTimerCallback,
|
||||||
|
reinterpret_cast<void*>(uintptr_t(SDL_GetWindowID(window_))));
|
||||||
|
}
|
||||||
|
|
||||||
|
void SDLWindow::RemoveCursorAutoHideTimer() {
|
||||||
|
if (!cursor_auto_hide_timer_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
SDL_RemoveTimer(cursor_auto_hide_timer_);
|
||||||
|
cursor_auto_hide_timer_ = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SDLWindow::WindowPointToPhysical(float x, float y, int32_t& physical_x,
|
||||||
|
int32_t& physical_y) const {
|
||||||
|
int logical_width = 0;
|
||||||
|
int logical_height = 0;
|
||||||
|
int physical_width = 0;
|
||||||
|
int physical_height = 0;
|
||||||
|
if (window_ && SDL_GetWindowSize(window_, &logical_width, &logical_height) &&
|
||||||
|
SDL_GetWindowSizeInPixels(window_, &physical_width, &physical_height) &&
|
||||||
|
logical_width > 0 && logical_height > 0 && physical_width > 0 && physical_height > 0) {
|
||||||
|
physical_x = int32_t(std::lround(double(x) * double(physical_width) / double(logical_width)));
|
||||||
|
physical_y = int32_t(std::lround(double(y) * double(physical_height) / double(logical_height)));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
physical_x = int32_t(std::lround(x));
|
||||||
|
physical_y = int32_t(std::lround(y));
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t SDLWindow::QueryDpi() const {
|
||||||
|
if (!window_) {
|
||||||
|
return kDefaultDpi;
|
||||||
|
}
|
||||||
|
float scale = SDL_GetWindowDisplayScale(window_);
|
||||||
|
if (scale <= 0.0f) {
|
||||||
|
scale = 1.0f;
|
||||||
|
}
|
||||||
|
return std::clamp(uint32_t(std::lround(double(kDefaultDpi) * scale)), uint32_t(48),
|
||||||
|
uint32_t(384));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<ui::MenuItem> MenuItem::Create(Type type, const std::string& text,
|
||||||
|
const std::string& hotkey,
|
||||||
|
std::function<void()> callback) {
|
||||||
|
return std::make_unique<SDLMenuItem>(type, text, hotkey, std::move(callback));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace ui
|
||||||
|
} // namespace rex
|
||||||
70
src/ui/windowed_app_context_sdl.cpp
Normal file
70
src/ui/windowed_app_context_sdl.cpp
Normal file
|
|
@ -0,0 +1,70 @@
|
||||||
|
#include <rex/ui/windowed_app_context_sdl.h>
|
||||||
|
#include <rex/ui/window_sdl.h>
|
||||||
|
|
||||||
|
#include <rex/logging.h>
|
||||||
|
|
||||||
|
#include <cstdlib>
|
||||||
|
|
||||||
|
namespace rex {
|
||||||
|
namespace ui {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
constexpr uint32_t kPendingFunctionsEvent = SDL_EVENT_USER;
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
void SDLWindowedAppContext::NotifyUILoopOfPendingFunctions() {
|
||||||
|
bool expected = false;
|
||||||
|
if (!pending_functions_event_queued_.compare_exchange_strong(expected, true)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_Event event = {};
|
||||||
|
event.type = kPendingFunctionsEvent;
|
||||||
|
if (!SDL_PushEvent(&event)) {
|
||||||
|
pending_functions_event_queued_ = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SDLWindowedAppContext::PlatformQuitFromUIThread() {
|
||||||
|
SDL_Event event = {};
|
||||||
|
event.type = SDL_EVENT_QUIT;
|
||||||
|
SDL_PushEvent(&event);
|
||||||
|
}
|
||||||
|
|
||||||
|
int SDLWindowedAppContext::RunMainLoop() {
|
||||||
|
if (HasQuitFromUIThread()) {
|
||||||
|
return EXIT_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_Event event;
|
||||||
|
while (!HasQuitFromUIThread() && SDL_WaitEvent(&event)) {
|
||||||
|
DispatchEvent(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!HasQuitFromUIThread()) {
|
||||||
|
REXLOG_WARN("SDL event loop exited unexpectedly: {}", SDL_GetError());
|
||||||
|
QuitFromUIThread();
|
||||||
|
}
|
||||||
|
return EXIT_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SDLWindowedAppContext::DispatchEvent(const SDL_Event& event) {
|
||||||
|
if (event.type == kPendingFunctionsEvent) {
|
||||||
|
pending_functions_event_queued_ = false;
|
||||||
|
ExecutePendingFunctionsFromUIThread();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.type == SDL_EVENT_QUIT) {
|
||||||
|
REXLOG_INFO("SDL quit event received");
|
||||||
|
QuitFromUIThread();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDLWindow::HandleSDLEvent(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace ui
|
||||||
|
} // namespace rex
|
||||||
61
src/ui/windowed_app_main_sdl.cpp
Normal file
61
src/ui/windowed_app_main_sdl.cpp
Normal file
|
|
@ -0,0 +1,61 @@
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cstdio>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <map>
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <rex/cvar.h>
|
||||||
|
#include <rex/logging.h>
|
||||||
|
#include <rex/platform.h>
|
||||||
|
#include <rex/ui/windowed_app.h>
|
||||||
|
#include <rex/ui/windowed_app_context_sdl.h>
|
||||||
|
|
||||||
|
#include <SDL3/SDL.h>
|
||||||
|
|
||||||
|
int main(int argc, char** argv) {
|
||||||
|
auto remaining = rex::cvar::Init(argc, argv);
|
||||||
|
rex::cvar::ApplyEnvironment();
|
||||||
|
rex::InitLoggingEarly();
|
||||||
|
|
||||||
|
#if REX_PLATFORM_MAC
|
||||||
|
// Use native macOS fullscreen so the menu bar and app switching behave like
|
||||||
|
// other Mac apps. MoltenVK presentation is paced separately on macOS.
|
||||||
|
SDL_SetHint(SDL_HINT_VIDEO_MAC_FULLSCREEN_SPACES, "1");
|
||||||
|
SDL_SetHint(SDL_HINT_VIDEO_MAC_FULLSCREEN_MENU_VISIBILITY, "1");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (!SDL_Init(SDL_INIT_VIDEO)) {
|
||||||
|
std::fprintf(stderr, "Failed to initialize SDL video: %s\n", SDL_GetError());
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
int result = EXIT_FAILURE;
|
||||||
|
{
|
||||||
|
rex::ui::SDLWindowedAppContext app_context;
|
||||||
|
std::unique_ptr<rex::ui::WindowedApp> app = rex::ui::GetWindowedAppCreator()(app_context);
|
||||||
|
|
||||||
|
const auto& option_names = app->GetPositionalOptions();
|
||||||
|
std::map<std::string, std::string> parsed;
|
||||||
|
size_t count = std::min(remaining.size(), option_names.size());
|
||||||
|
for (size_t i = 0; i < count; ++i) {
|
||||||
|
parsed[option_names[i]] = remaining[i];
|
||||||
|
}
|
||||||
|
app->SetParsedArguments(std::move(parsed));
|
||||||
|
|
||||||
|
bool initialized = app->OnInitialize();
|
||||||
|
result = initialized ? app_context.RunMainLoop() : EXIT_FAILURE;
|
||||||
|
app->InvokeOnDestroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
rex::ShutdownLogging();
|
||||||
|
#if REX_PLATFORM_MAC
|
||||||
|
// After explicit app teardown and log flushing, avoid macOS/SDL/global static
|
||||||
|
// teardown paths that can change a clean Close Game exit into status 1.
|
||||||
|
std::_Exit(result);
|
||||||
|
#else
|
||||||
|
SDL_Quit();
|
||||||
|
#endif
|
||||||
|
return result;
|
||||||
|
}
|
||||||
31
thirdparty/CMakeLists.txt
vendored
31
thirdparty/CMakeLists.txt
vendored
|
|
@ -5,10 +5,22 @@ set(BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE)
|
||||||
#=============================================================================
|
#=============================================================================
|
||||||
# Architecture detection
|
# Architecture detection
|
||||||
#=============================================================================
|
#=============================================================================
|
||||||
if(CMAKE_SYSTEM_PROCESSOR MATCHES "^(x86_64|AMD64)$")
|
set(REX_THIRDPARTY_TARGET_PROCESSOR "${CMAKE_SYSTEM_PROCESSOR}")
|
||||||
|
if(APPLE AND CMAKE_OSX_ARCHITECTURES)
|
||||||
|
list(LENGTH CMAKE_OSX_ARCHITECTURES REX_THIRDPARTY_OSX_ARCH_COUNT)
|
||||||
|
if(REX_THIRDPARTY_OSX_ARCH_COUNT EQUAL 1)
|
||||||
|
set(REX_THIRDPARTY_TARGET_PROCESSOR "${CMAKE_OSX_ARCHITECTURES}")
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(APPLE AND NOT REX_THIRDPARTY_TARGET_PROCESSOR MATCHES "^(aarch64|arm64|ARM64)$")
|
||||||
|
message(FATAL_ERROR "Unsupported macOS architecture: ${REX_THIRDPARTY_TARGET_PROCESSOR}. macOS builds are ARM64-only.")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(REX_THIRDPARTY_TARGET_PROCESSOR MATCHES "^(x86_64|AMD64)$")
|
||||||
set(IS_ARM64 FALSE)
|
set(IS_ARM64 FALSE)
|
||||||
set(IS_X86_64 TRUE)
|
set(IS_X86_64 TRUE)
|
||||||
elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "^(aarch64|ARM64)$")
|
elseif(REX_THIRDPARTY_TARGET_PROCESSOR MATCHES "^(aarch64|arm64|ARM64)$")
|
||||||
set(IS_ARM64 TRUE)
|
set(IS_ARM64 TRUE)
|
||||||
set(IS_X86_64 FALSE)
|
set(IS_X86_64 FALSE)
|
||||||
else()
|
else()
|
||||||
|
|
@ -197,6 +209,9 @@ if(REXGLUE_USE_VULKAN)
|
||||||
add_subdirectory(vulkan-memory-allocator)
|
add_subdirectory(vulkan-memory-allocator)
|
||||||
|
|
||||||
# volk
|
# volk
|
||||||
|
if(APPLE)
|
||||||
|
set(VOLK_STATIC_DEFINES VK_USE_PLATFORM_METAL_EXT CACHE STRING "" FORCE)
|
||||||
|
endif()
|
||||||
set(VOLK_INSTALL ON CACHE BOOL "" FORCE)
|
set(VOLK_INSTALL ON CACHE BOOL "" FORCE)
|
||||||
set(VOLK_PULL_IN_VULKAN OFF CACHE BOOL "" FORCE)
|
set(VOLK_PULL_IN_VULKAN OFF CACHE BOOL "" FORCE)
|
||||||
add_subdirectory(volk)
|
add_subdirectory(volk)
|
||||||
|
|
@ -237,7 +252,11 @@ set(SDL_TEST OFF CACHE BOOL "" FORCE)
|
||||||
set(SDL_UNIX_CONSOLE_BUILD ON CACHE BOOL "" FORCE)
|
set(SDL_UNIX_CONSOLE_BUILD ON CACHE BOOL "" FORCE)
|
||||||
|
|
||||||
# Disable unused subsystems
|
# Disable unused subsystems
|
||||||
set(SDL_VIDEO OFF CACHE BOOL "" FORCE)
|
if(APPLE)
|
||||||
|
set(SDL_VIDEO ON CACHE BOOL "" FORCE)
|
||||||
|
else()
|
||||||
|
set(SDL_VIDEO OFF CACHE BOOL "" FORCE)
|
||||||
|
endif()
|
||||||
set(SDL_RENDER OFF CACHE BOOL "" FORCE)
|
set(SDL_RENDER OFF CACHE BOOL "" FORCE)
|
||||||
set(SDL_HAPTIC OFF CACHE BOOL "" FORCE)
|
set(SDL_HAPTIC OFF CACHE BOOL "" FORCE)
|
||||||
set(SDL_SENSOR OFF CACHE BOOL "" FORCE)
|
set(SDL_SENSOR OFF CACHE BOOL "" FORCE)
|
||||||
|
|
@ -254,7 +273,11 @@ set(SDL_CAMERA OFF CACHE BOOL "" FORCE)
|
||||||
set(SDL_OPENGL OFF CACHE BOOL "" FORCE)
|
set(SDL_OPENGL OFF CACHE BOOL "" FORCE)
|
||||||
set(SDL_OPENGLES OFF CACHE BOOL "" FORCE)
|
set(SDL_OPENGLES OFF CACHE BOOL "" FORCE)
|
||||||
set(SDL_VULKAN OFF CACHE BOOL "" FORCE)
|
set(SDL_VULKAN OFF CACHE BOOL "" FORCE)
|
||||||
set(SDL_METAL OFF CACHE BOOL "" FORCE)
|
if(APPLE)
|
||||||
|
set(SDL_METAL ON CACHE BOOL "" FORCE)
|
||||||
|
else()
|
||||||
|
set(SDL_METAL OFF CACHE BOOL "" FORCE)
|
||||||
|
endif()
|
||||||
|
|
||||||
# Disable dummy drivers
|
# Disable dummy drivers
|
||||||
set(SDL_DISKAUDIO OFF CACHE BOOL "" FORCE)
|
set(SDL_DISKAUDIO OFF CACHE BOOL "" FORCE)
|
||||||
|
|
|
||||||
12
thirdparty/crypto/sha256.cpp
vendored
12
thirdparty/crypto/sha256.cpp
vendored
|
|
@ -9,8 +9,18 @@
|
||||||
|
|
||||||
// big endian architectures need #define __BYTE_ORDER __BIG_ENDIAN
|
// big endian architectures need #define __BYTE_ORDER __BIG_ENDIAN
|
||||||
#ifndef _MSC_VER
|
#ifndef _MSC_VER
|
||||||
|
#ifdef __APPLE__
|
||||||
|
#include <machine/endian.h>
|
||||||
|
#ifndef __BYTE_ORDER
|
||||||
|
#define __BYTE_ORDER BYTE_ORDER
|
||||||
|
#endif
|
||||||
|
#ifndef __BIG_ENDIAN
|
||||||
|
#define __BIG_ENDIAN BIG_ENDIAN
|
||||||
|
#endif
|
||||||
|
#else
|
||||||
#include <endian.h>
|
#include <endian.h>
|
||||||
#endif
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace sha256 {
|
namespace sha256 {
|
||||||
|
|
||||||
|
|
@ -573,4 +583,4 @@ std::string SHA256::operator()(const std::string& text) {
|
||||||
return getHash();
|
return getHash();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
2
thirdparty/renderdoc/renderdoc_app.h
vendored
2
thirdparty/renderdoc/renderdoc_app.h
vendored
|
|
@ -30,7 +30,7 @@
|
||||||
|
|
||||||
#if defined(WIN32) || defined(_WIN32)
|
#if defined(WIN32) || defined(_WIN32)
|
||||||
#define RENDERDOC_CC __cdecl
|
#define RENDERDOC_CC __cdecl
|
||||||
#elif defined(__linux__)
|
#elif defined(__linux__) || defined(__APPLE__)
|
||||||
#define RENDERDOC_CC
|
#define RENDERDOC_CC
|
||||||
#else
|
#else
|
||||||
#error "Unknown platform"
|
#error "Unknown platform"
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue