mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-28 08:13:05 +00:00
Initial commit
This commit is contained in:
commit
147188b143
427 files changed
+205923
No files matched your search
@@ -0,0 +1,200 @@
|
||||
---
|
||||
Language: Cpp
|
||||
# BasedOnStyle: LLVM
|
||||
AccessModifierOffset: -4
|
||||
AlignAfterOpenBracket: Align
|
||||
AlignConsecutiveMacros: Consecutive
|
||||
AlignConsecutiveAssignments: Consecutive
|
||||
AlignConsecutiveBitFields: Consecutive
|
||||
AlignConsecutiveDeclarations: Consecutive
|
||||
AlignEscapedNewlines: Right
|
||||
AlignOperands: Align
|
||||
AlignTrailingComments: true
|
||||
AllowAllArgumentsOnNextLine: true
|
||||
AllowAllConstructorInitializersOnNextLine: true
|
||||
AllowAllParametersOfDeclarationOnNextLine: true
|
||||
AllowShortEnumsOnASingleLine: true
|
||||
AllowShortBlocksOnASingleLine: Never
|
||||
AllowShortCaseLabelsOnASingleLine: true
|
||||
AllowShortFunctionsOnASingleLine: Inline
|
||||
AllowShortLambdasOnASingleLine: All
|
||||
AllowShortIfStatementsOnASingleLine: WithoutElse
|
||||
AllowShortLoopsOnASingleLine: false
|
||||
AlwaysBreakAfterDefinitionReturnType: None
|
||||
AlwaysBreakAfterReturnType: None
|
||||
AlwaysBreakBeforeMultilineStrings: false
|
||||
AlwaysBreakTemplateDeclarations: Yes
|
||||
AttributeMacros:
|
||||
- __capability
|
||||
BinPackArguments: true
|
||||
BinPackParameters: true
|
||||
BraceWrapping:
|
||||
AfterCaseLabel: false
|
||||
AfterClass: false
|
||||
AfterControlStatement: Never
|
||||
AfterEnum: false
|
||||
AfterFunction: false
|
||||
AfterNamespace: false
|
||||
AfterObjCDeclaration: true
|
||||
AfterStruct: false
|
||||
AfterUnion: false
|
||||
AfterExternBlock: false
|
||||
BeforeCatch: false
|
||||
BeforeElse: false
|
||||
BeforeLambdaBody: true
|
||||
BeforeWhile: false
|
||||
IndentBraces: false
|
||||
SplitEmptyFunction: true
|
||||
SplitEmptyRecord: true
|
||||
SplitEmptyNamespace: true
|
||||
BreakBeforeBinaryOperators: None
|
||||
BreakBeforeConceptDeclarations: true
|
||||
BreakBeforeBraces: Attach
|
||||
BreakBeforeInheritanceComma: false
|
||||
BreakInheritanceList: BeforeColon
|
||||
BreakBeforeTernaryOperators: true
|
||||
BreakConstructorInitializersBeforeComma: false
|
||||
BreakConstructorInitializers: BeforeColon
|
||||
BreakAfterJavaFieldAnnotations: false
|
||||
BreakStringLiterals: true
|
||||
ColumnLimit: 100
|
||||
CommentPragmas: '^ IWYU pragma:'
|
||||
CompactNamespaces: false
|
||||
ConstructorInitializerAllOnOneLineOrOnePerLine: false
|
||||
ConstructorInitializerIndentWidth: 4
|
||||
ContinuationIndentWidth: 4
|
||||
Cpp11BracedListStyle: true
|
||||
DeriveLineEnding: true
|
||||
DerivePointerAlignment: false
|
||||
DisableFormat: false
|
||||
EmptyLineBeforeAccessModifier: LogicalBlock
|
||||
ExperimentalAutoDetectBinPacking: false
|
||||
FixNamespaceComments: true
|
||||
ForEachMacros:
|
||||
- foreach
|
||||
- Q_FOREACH
|
||||
- BOOST_FOREACH
|
||||
- FOR
|
||||
- FOR_HASH
|
||||
StatementAttributeLikeMacros:
|
||||
- Q_EMIT
|
||||
IncludeBlocks: Regroup
|
||||
IncludeCategories:
|
||||
- Regex: '^"(Kyty)/'
|
||||
Priority: 1
|
||||
SortPriority: 0
|
||||
CaseSensitive: false
|
||||
- Regex: '^"(Emulator)/'
|
||||
Priority: 2
|
||||
SortPriority: 0
|
||||
CaseSensitive: false
|
||||
- Regex: '^"(gtest)/'
|
||||
Priority: 4
|
||||
SortPriority: 0
|
||||
CaseSensitive: false
|
||||
- Regex: '^<Q'
|
||||
Priority: 5
|
||||
SortPriority: 0
|
||||
CaseSensitive: false
|
||||
- Regex: '"ui_'
|
||||
Priority: 6
|
||||
SortPriority: 0
|
||||
CaseSensitive: false
|
||||
- Regex: '^<winsock2\.h>'
|
||||
Priority: 7
|
||||
SortPriority: 1
|
||||
CaseSensitive: false
|
||||
- Regex: '^<windows\.h>'
|
||||
Priority: 7
|
||||
SortPriority: 2
|
||||
CaseSensitive: false
|
||||
- Regex: '^<iphlpapi\.h>'
|
||||
Priority: 7
|
||||
SortPriority: 3
|
||||
CaseSensitive: false
|
||||
- Regex: '^<ws2tcpip\.h>'
|
||||
Priority: 7
|
||||
SortPriority: 4
|
||||
CaseSensitive: false
|
||||
- Regex: '^<'
|
||||
Priority: 7
|
||||
SortPriority: 5
|
||||
CaseSensitive: false
|
||||
- Regex: '.*'
|
||||
Priority: 3
|
||||
SortPriority: 0
|
||||
CaseSensitive: false
|
||||
IncludeIsMainRegex: '$'
|
||||
IncludeIsMainSourceRegex: ''
|
||||
IndentCaseLabels: true
|
||||
IndentCaseBlocks: false
|
||||
IndentGotoLabels: true
|
||||
IndentPPDirectives: None
|
||||
IndentExternBlock: AfterExternBlock
|
||||
IndentRequires: false
|
||||
IndentWidth: 4
|
||||
IndentWrappedFunctionNames: false
|
||||
InsertTrailingCommas: None
|
||||
JavaScriptQuotes: Leave
|
||||
JavaScriptWrapImports: true
|
||||
KeepEmptyLinesAtTheStartOfBlocks: true
|
||||
MacroBlockBegin: ''
|
||||
MacroBlockEnd: ''
|
||||
MaxEmptyLinesToKeep: 1
|
||||
NamespaceIndentation: None
|
||||
ObjCBinPackProtocolList: Auto
|
||||
ObjCBlockIndentWidth: 2
|
||||
ObjCBreakBeforeNestedBlockParam: true
|
||||
ObjCSpaceAfterProperty: false
|
||||
ObjCSpaceBeforeProtocolList: true
|
||||
PenaltyBreakAssignment: 2
|
||||
PenaltyBreakBeforeFirstCallParameter: 19
|
||||
PenaltyBreakComment: 300
|
||||
PenaltyBreakFirstLessLess: 120
|
||||
PenaltyBreakString: 1000
|
||||
PenaltyBreakTemplateDeclaration: 10
|
||||
PenaltyExcessCharacter: 1000000
|
||||
PenaltyReturnTypeOnItsOwnLine: 60
|
||||
PenaltyIndentedWhitespace: 0
|
||||
PointerAlignment: Left
|
||||
ReflowComments: true
|
||||
SortIncludes: true
|
||||
SortJavaStaticImport: Before
|
||||
SortUsingDeclarations: true
|
||||
SpaceAfterCStyleCast: false
|
||||
SpaceAfterLogicalNot: false
|
||||
SpaceAfterTemplateKeyword: true
|
||||
SpaceBeforeAssignmentOperators: true
|
||||
SpaceBeforeCaseColon: false
|
||||
SpaceBeforeCpp11BracedList: true
|
||||
SpaceBeforeCtorInitializerColon: false
|
||||
SpaceBeforeInheritanceColon: false
|
||||
SpaceBeforeParens: ControlStatements
|
||||
SpaceAroundPointerQualifiers: Default
|
||||
SpaceBeforeRangeBasedForLoopColon: false
|
||||
SpaceInEmptyBlock: false
|
||||
SpaceInEmptyParentheses: false
|
||||
SpacesBeforeTrailingComments: 1
|
||||
SpacesInAngles: false
|
||||
SpacesInConditionalStatement: false
|
||||
SpacesInContainerLiterals: true
|
||||
SpacesInCStyleCastParentheses: false
|
||||
SpacesInParentheses: false
|
||||
SpacesInSquareBrackets: false
|
||||
SpaceBeforeSquareBrackets: false
|
||||
BitFieldColonSpacing: Both
|
||||
Standard: Latest
|
||||
StatementMacros:
|
||||
- Q_UNUSED
|
||||
- QT_REQUIRE_VERSION
|
||||
TabWidth: 4
|
||||
UseCRLF: true
|
||||
UseTab: ForIndentation
|
||||
WhitespaceSensitiveMacros:
|
||||
- STRINGIZE
|
||||
- PP_STRINGIZE
|
||||
- BOOST_PP_STRINGIZE
|
||||
- NS_SWIFT_NAME
|
||||
- CF_SWIFT_NAME
|
||||
...
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
Checks: '-*,bugprone-*,cert-*,clang-analyzer-*,google-*,llvm-*,-llvm-header-guard,misc-*,modernize-*,-modernize-use-trailing-return-type,-modernize-avoid-c-arrays,performance-*,portability-*,readability-*,-readability-magic-numbers,-readability-uppercase-literal-suffix,cppcoreguidelines-*,-cppcoreguidelines-pro-type-vararg,-cppcoreguidelines-avoid-magic-numbers,-cppcoreguidelines-pro-bounds-pointer-arithmetic,-cppcoreguidelines-owning-memory,-cppcoreguidelines-pro-bounds-array-to-pointer-decay,-cppcoreguidelines-pro-type-reinterpret-cast,-cppcoreguidelines-avoid-c-arrays,hicpp-*,-hicpp-vararg,-hicpp-no-array-decay,-hicpp-avoid-c-arrays,-hicpp-uppercase-literal-suffix,-cppcoreguidelines-pro-bounds-constant-array-index,-cppcoreguidelines-pro-type-union-access,-cppcoreguidelines-pro-type-static-cast-downcast,-modernize-pass-by-value,-cert-dcl50-cpp,-misc-no-recursion,-cppcoreguidelines-avoid-non-const-global-variables,-performance-no-int-to-ptr,-readability-identifier-length,-bugprone-easily-swappable-parameters,-bugprone-throw-keyword-missing'
|
||||
CheckOptions:
|
||||
- key: readability-function-cognitive-complexity.Threshold
|
||||
value: 50
|
||||
- key: readability-redundant-access-specifiers.CheckFirstDeclaration
|
||||
value: 1
|
||||
- key: misc-non-private-member-variables-in-classes.IgnorePublicMemberVariables
|
||||
value: 1
|
||||
- key: cppcoreguidelines-pro-type-member-init.IgnoreArrays
|
||||
value: 1
|
||||
- key: cppcoreguidelines-macro-usage.AllowedRegexp
|
||||
value: (KYTY_*)|(EXIT*)|(UT_*)
|
||||
- key: readability-identifier-naming.ClassCase
|
||||
value: CamelCase
|
||||
- key: readability-identifier-naming.MemberCase
|
||||
value: lower_case
|
||||
- key: readability-identifier-naming.PrivateMemberPrefix
|
||||
value: 'm_'
|
||||
- key: readability-identifier-naming.ProtectedMemberPrefix
|
||||
value: 'm_'
|
||||
- key: readability-identifier-naming.PublicMethodCase
|
||||
value: CamelCase
|
||||
- key: readability-identifier-naming.PublicMemberCase
|
||||
value: aNy_CasE
|
||||
- key: readability-identifier-naming.StructCase
|
||||
value: CamelCase
|
||||
- key: readability-identifier-naming.ConstantCase
|
||||
value: UPPER_CASE
|
||||
- key: readability-identifier-naming.EnumConstantCase
|
||||
value: CamelCase
|
||||
- key: readability-identifier-naming.EnumCase
|
||||
value: CamelCase
|
||||
- key: readability-identifier-naming.TemplateParameterCase
|
||||
value: CamelCase
|
||||
- key: readability-identifier-naming.ValueTemplateParameterCase
|
||||
value: CamelCase
|
||||
- key: readability-identifier-naming.LocalConstantCase
|
||||
value: lower_case
|
||||
- key: readability-identifier-naming.GlobalConstantCase
|
||||
value: lower_case
|
||||
- key: readability-identifier-naming.GlobalConstantPrefix
|
||||
value: 'g_'
|
||||
- key: readability-identifier-naming.GlobalVariableCase
|
||||
value: lower_case
|
||||
- key: readability-identifier-naming.GlobalVariablePrefix
|
||||
value: 'g_'
|
||||
- key: readability-identifier-naming.VariableCase
|
||||
value: lower_case
|
||||
- key: readability-identifier-naming.ParameterCase
|
||||
value: lower_case
|
||||
- key: readability-identifier-naming.ConstexprVariableCase
|
||||
value: UPPER_CASE
|
||||
@@ -0,0 +1,3 @@
|
||||
doc/** filter=lfs diff=lfs merge=lfs -text
|
||||
|
||||
*.inc linguist-language=C++
|
||||
@@ -0,0 +1 @@
|
||||
*.pro.user
|
||||
@@ -0,0 +1,338 @@
|
||||
cmake_minimum_required(VERSION 3.12...4.3)
|
||||
|
||||
if(CMAKE_VERSION VERSION_GREATER_EQUAL 4.0)
|
||||
set(CMAKE_POLICY_VERSION_MINIMUM 3.5 CACHE STRING "Minimum policy version for bundled dependencies")
|
||||
endif()
|
||||
|
||||
project(Kyty)
|
||||
|
||||
if(CMAKE_SYSTEM_NAME MATCHES ".*Linux")
|
||||
set(LINUX TRUE)
|
||||
endif()
|
||||
|
||||
if (NOT (WIN32 OR LINUX))
|
||||
message(FATAL_ERROR "only Windows and Linux builds are supported")
|
||||
endif()
|
||||
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_CXX_EXTENSIONS OFF)
|
||||
|
||||
include(utils.cmake)
|
||||
|
||||
set(KYTY_THIRD_PARTY_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../3rdparty")
|
||||
|
||||
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)
|
||||
|
||||
include(TestBigEndian)
|
||||
|
||||
if(NOT CMAKE_SIZEOF_VOID_P EQUAL 8)
|
||||
message(FATAL_ERROR "only 64-bit builds are supported")
|
||||
endif()
|
||||
|
||||
test_big_endian(BIG_ENDIAN)
|
||||
if(BIG_ENDIAN)
|
||||
set(KYTY_ENDIAN KYTY_ENDIAN_BIG)
|
||||
else()
|
||||
set(KYTY_ENDIAN KYTY_ENDIAN_LITTLE)
|
||||
endif()
|
||||
|
||||
if(CMAKE_BUILD_TYPE MATCHES Debug)
|
||||
set(KYTY_BUILD KYTY_BUILD_DEBUG)
|
||||
else()
|
||||
set(KYTY_BUILD KYTY_BUILD_RELEASE)
|
||||
endif()
|
||||
|
||||
if(LINUX)
|
||||
set(KYTY_PLATFORM KYTY_PLATFORM_LINUX)
|
||||
else()
|
||||
set(KYTY_PLATFORM KYTY_PLATFORM_WINDOWS)
|
||||
endif()
|
||||
|
||||
set(CLANG FALSE)
|
||||
set(GCC FALSE)
|
||||
set(KYTY_CLANG_CL FALSE)
|
||||
get_filename_component(KYTY_CXX_COMPILER_NAME "${CMAKE_CXX_COMPILER}" NAME_WE)
|
||||
string(TOLOWER "${KYTY_CXX_COMPILER_NAME}" KYTY_CXX_COMPILER_NAME)
|
||||
|
||||
if(CMAKE_CXX_COMPILER_ID MATCHES "^(Apple)?Clang$")
|
||||
set(CLANG TRUE)
|
||||
set(KYTY_COMPILER CLANG)
|
||||
if(WIN32 AND KYTY_CXX_COMPILER_NAME STREQUAL "clang-cl")
|
||||
set(KYTY_CLANG_CL TRUE)
|
||||
set(KYTY_LINKER LLD_LINK)
|
||||
else()
|
||||
set(KYTY_LINKER LLD)
|
||||
endif()
|
||||
if ((CMAKE_AR MATCHES "llvm") AND (CMAKE_CXX_COMPILER_VERSION VERSION_LESS 12.0.0) AND (CMAKE_CXX_COMPILER MATCHES "winlibs"))
|
||||
string(REPLACE "llvm-ar.exe" "ar.exe" CMAKE_AR ${CMAKE_AR}) # llvm-ar.exe 11.0.0 from winlibs is broken
|
||||
endif()
|
||||
elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
|
||||
set(GCC TRUE)
|
||||
set(KYTY_COMPILER GCC)
|
||||
set(KYTY_LINKER LD)
|
||||
else()
|
||||
message(FATAL_ERROR "only Clang and GCC are supported; found ${CMAKE_CXX_COMPILER_ID}")
|
||||
endif()
|
||||
|
||||
set(KYTY_PROJECT_NAME "Emulator" CACHE STRING "Project name suffix")
|
||||
|
||||
string(TOLOWER ${KYTY_COMPILER} KYTY_COMPILER_ID)
|
||||
string(TOLOWER ${KYTY_LINKER} KYTY_LINKER_ID)
|
||||
|
||||
if (CLANG AND (KYTY_LINKER STREQUAL LLD))
|
||||
set(KYTY_LD_OPTIONS "-fuse-ld=lld")
|
||||
endif()
|
||||
|
||||
if (KYTY_LINKER STREQUAL LD AND NOT LINUX)
|
||||
set(KYTY_LD_OPTIONS "-Wl,--image-base=0x100000000000")
|
||||
endif()
|
||||
|
||||
project(Kyty${KYTY_PROJECT_NAME}${CMAKE_BUILD_TYPE}${KYTY_COMPILER} VERSION 0.2.2)
|
||||
|
||||
include_directories(
|
||||
${KYTY_THIRD_PARTY_DIR}/gtest/include
|
||||
${KYTY_THIRD_PARTY_DIR}/gtest
|
||||
${KYTY_THIRD_PARTY_DIR}/fmt/include
|
||||
${KYTY_THIRD_PARTY_DIR}/magic_enum/include/magic_enum
|
||||
${PROJECT_BINARY_DIR}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}
|
||||
)
|
||||
|
||||
set(KYTY_VERSION "${PROJECT_VERSION}")
|
||||
|
||||
configure_file(
|
||||
${PROJECT_SOURCE_DIR}/cmake_config.h.in
|
||||
${PROJECT_BINARY_DIR}/cmake_config.h
|
||||
)
|
||||
|
||||
find_package(Git)
|
||||
|
||||
add_custom_target( KytyGitVersion
|
||||
COMMAND ${CMAKE_COMMAND}
|
||||
-D INPUT_FILE=${CMAKE_CURRENT_SOURCE_DIR}/kytyGitVersion.h.in
|
||||
-D OUTPUT_FILE=${CMAKE_CURRENT_BINARY_DIR}/kytyGitVersion.h
|
||||
-D GIT_EXECUTABLE=${GIT_EXECUTABLE}
|
||||
-P ${CMAKE_CURRENT_SOURCE_DIR}/generate_version.cmake
|
||||
COMMENT "Generate kytyGitVersion.h"
|
||||
)
|
||||
|
||||
if (CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 12.0.0)
|
||||
list(APPEND KYTY_IWYU
|
||||
kyty_emulator
|
||||
common
|
||||
#launcher
|
||||
)
|
||||
list(APPEND KYTY_CLANG_TYDY
|
||||
kyty_emulator
|
||||
#common
|
||||
launcher
|
||||
)
|
||||
endif()
|
||||
|
||||
option(KYTY_BUILD_LAUNCHER "Build Qt launcher" ON)
|
||||
|
||||
config_compiler_and_linker()
|
||||
|
||||
add_subdirectory("${KYTY_THIRD_PARTY_DIR}" "${CMAKE_CURRENT_BINARY_DIR}/3rdparty")
|
||||
add_subdirectory(common)
|
||||
|
||||
file(GLOB kyty_emulator_src
|
||||
libs/*.cpp
|
||||
libs/*.h
|
||||
graphics/*.cpp
|
||||
graphics/*.h
|
||||
graphics/guest_gpu/*.cpp
|
||||
graphics/guest_gpu/*.h
|
||||
graphics/guest_gpu/command_processor/*.cpp
|
||||
graphics/guest_gpu/command_processor/*.h
|
||||
graphics/host_gpu/*.cpp
|
||||
graphics/host_gpu/*.h
|
||||
graphics/host_gpu/renderer/*.cpp
|
||||
graphics/host_gpu/renderer/*.h
|
||||
graphics/shader/*.cpp
|
||||
graphics/shader/*.h
|
||||
graphics/shader/recompiler/*.cpp
|
||||
graphics/shader/recompiler/*.h
|
||||
graphics/shader/recompiler/shaderIR/*.cpp
|
||||
graphics/shader/recompiler/shaderIR/*.h
|
||||
graphics/shader/recompiler/spirvEmitter/*.cpp
|
||||
graphics/shader/recompiler/spirvEmitter/*.h
|
||||
graphics/host_gpu/objects/*.cpp
|
||||
graphics/host_gpu/objects/*.h
|
||||
graphics/presentation/*.cpp
|
||||
graphics/presentation/*.h
|
||||
graphics/presentation/window/*.cpp
|
||||
graphics/presentation/window/*.h
|
||||
kernel/*.cpp
|
||||
kernel/*.h
|
||||
loader/*.cpp
|
||||
loader/*.h
|
||||
)
|
||||
|
||||
list(APPEND kyty_emulator_src
|
||||
emulator.h
|
||||
emulator.cpp
|
||||
)
|
||||
list(REMOVE_DUPLICATES kyty_emulator_src)
|
||||
|
||||
if (KYTY_CLANG_CL)
|
||||
set_source_files_properties(${kyty_emulator_src} PROPERTIES COMPILE_FLAGS "-Wno-pragma-pack -Wno-deprecated-declarations -D_TIMESPEC_DEFINED")
|
||||
endif()
|
||||
|
||||
set(kyty_emulator_link_libraries common vulkan-1 spirv-tools-opt spirv-tools SDL2-static xxhash FFmpeg::ffmpeg fmt::fmt nlohmann_json::nlohmann_json LibAtrac9)
|
||||
|
||||
set(inc_headers
|
||||
${CMAKE_CURRENT_SOURCE_DIR}
|
||||
${KYTY_THIRD_PARTY_DIR}/SDL2/include
|
||||
${KYTY_THIRD_PARTY_DIR}/Vulkan-Headers/include
|
||||
${KYTY_THIRD_PARTY_DIR}/VulkanMemoryAllocator/include
|
||||
${KYTY_THIRD_PARTY_DIR}/SPIRV-Tools/include
|
||||
${KYTY_THIRD_PARTY_DIR}/fmt/include
|
||||
${KYTY_THIRD_PARTY_DIR}/xxhash
|
||||
${KYTY_THIRD_PARTY_DIR}/cpuinfo/include
|
||||
${KYTY_THIRD_PARTY_DIR}/stb
|
||||
)
|
||||
|
||||
if(DEFINED ENV{VULKAN_SDK})
|
||||
list(APPEND inc_headers $ENV{VULKAN_SDK}/Include)
|
||||
endif()
|
||||
|
||||
if (KYTY_CLANG_CL)
|
||||
list(APPEND kyty_emulator_link_libraries winpthread)
|
||||
list(APPEND inc_headers
|
||||
${KYTY_THIRD_PARTY_DIR}/winpthread/include
|
||||
)
|
||||
endif()
|
||||
|
||||
list(APPEND check_headers
|
||||
${CMAKE_CURRENT_SOURCE_DIR}
|
||||
)
|
||||
|
||||
function(add_kyty_full_emulator_test target source)
|
||||
add_executable(${target} EXCLUDE_FROM_ALL ${source} ${kyty_emulator_src})
|
||||
target_link_libraries(${target} ${kyty_emulator_link_libraries})
|
||||
target_include_directories(${target} PRIVATE ${inc_headers})
|
||||
if (WIN32)
|
||||
target_link_libraries(${target} iphlpapi)
|
||||
endif()
|
||||
if (KYTY_CLANG_CL)
|
||||
target_link_libraries(${target} onecore)
|
||||
add_custom_command(TARGET ${target} POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different "${KYTY_THIRD_PARTY_DIR}/winpthread/bin/libwinpthread-1.dll" $<TARGET_FILE_DIR:${target}>/libwinpthread-1.dll)
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
add_kyty_full_emulator_test(shader_cfg_tests ../tests/shaderCfgTests.cpp)
|
||||
|
||||
add_executable(scalar_provenance_tests EXCLUDE_FROM_ALL
|
||||
../tests/ScalarProvenanceTests.cpp
|
||||
graphics/host_gpu/hostMemory.cpp
|
||||
graphics/shader/recompiler/ScalarProvenance.cpp
|
||||
graphics/shader/recompiler/SrtWalker.cpp
|
||||
)
|
||||
target_link_libraries(scalar_provenance_tests fmt::fmt)
|
||||
target_include_directories(scalar_provenance_tests PRIVATE ${inc_headers})
|
||||
|
||||
add_executable(page_manager_tests EXCLUDE_FROM_ALL
|
||||
../tests/PageManagerTests.cpp
|
||||
graphics/host_gpu/pageManager.cpp
|
||||
)
|
||||
target_include_directories(page_manager_tests PRIVATE ${inc_headers})
|
||||
|
||||
add_executable(memory_tracker_tests EXCLUDE_FROM_ALL
|
||||
../tests/MemoryTrackerTests.cpp
|
||||
graphics/host_gpu/pageManager.cpp
|
||||
graphics/host_gpu/memoryTracker.cpp
|
||||
)
|
||||
target_link_libraries(memory_tracker_tests fmt::fmt common)
|
||||
target_include_directories(memory_tracker_tests PRIVATE ${inc_headers})
|
||||
target_compile_definitions(memory_tracker_tests PRIVATE KYTY_MEMORY_TRACKER_TESTS=1)
|
||||
|
||||
add_executable(shader_vertex_metadata_tests EXCLUDE_FROM_ALL
|
||||
../tests/ShaderVertexMetadataTests.cpp
|
||||
graphics/host_gpu/hostMemory.cpp
|
||||
graphics/shader/shaderVertexMetadata.cpp
|
||||
)
|
||||
target_include_directories(shader_vertex_metadata_tests PRIVATE ${inc_headers})
|
||||
|
||||
add_executable(shader_stage_runtime_tests EXCLUDE_FROM_ALL
|
||||
../tests/ShaderStageRuntimeTests.cpp
|
||||
graphics/guest_gpu/gpu_format.cpp
|
||||
graphics/host_gpu/hostMemory.cpp
|
||||
graphics/shader/shaderStageRuntime.cpp
|
||||
graphics/shader/recompiler/ResourceMaterialization.cpp
|
||||
graphics/shader/recompiler/ScalarProvenance.cpp
|
||||
graphics/shader/recompiler/SrtWalker.cpp
|
||||
)
|
||||
target_link_libraries(shader_stage_runtime_tests fmt::fmt)
|
||||
target_include_directories(shader_stage_runtime_tests PRIVATE ${inc_headers})
|
||||
|
||||
add_executable(resource_tracking_tests EXCLUDE_FROM_ALL
|
||||
../tests/ResourceTrackingTests.cpp
|
||||
graphics/guest_gpu/gpu_format.cpp
|
||||
graphics/host_gpu/hostMemory.cpp
|
||||
graphics/shader/recompiler/ScalarProvenance.cpp
|
||||
graphics/shader/recompiler/SrtWalker.cpp
|
||||
graphics/shader/recompiler/SrtPatcher.cpp
|
||||
graphics/shader/recompiler/ResourceTracking.cpp
|
||||
graphics/shader/recompiler/ResourceMaterialization.cpp
|
||||
graphics/shader/recompiler/ShaderInfoCollection.cpp
|
||||
graphics/shader/recompiler/BindingLayout.cpp
|
||||
)
|
||||
target_link_libraries(resource_tracking_tests fmt::fmt)
|
||||
target_include_directories(resource_tracking_tests PRIVATE ${inc_headers})
|
||||
|
||||
add_executable(resource_mutex_tests EXCLUDE_FROM_ALL
|
||||
../tests/ResourceMutexTests.cpp
|
||||
graphics/host_gpu/renderer/resourceMutex.cpp
|
||||
)
|
||||
target_link_libraries(resource_mutex_tests common)
|
||||
target_include_directories(resource_mutex_tests PRIVATE ${inc_headers})
|
||||
|
||||
add_kyty_full_emulator_test(shader_recompiler_compute_tests ../tests/ShaderRecompilerComputeTests.cpp)
|
||||
|
||||
add_kyty_full_emulator_test(virtual_memory_allocation_tests ../tests/VirtualMemoryAllocationTests.cpp)
|
||||
|
||||
|
||||
add_executable(kyty_emulator main.cpp ${kyty_emulator_src})
|
||||
|
||||
target_link_libraries(kyty_emulator ${kyty_emulator_link_libraries})
|
||||
if (WIN32)
|
||||
target_link_libraries(kyty_emulator opengl32)
|
||||
target_link_libraries(kyty_emulator iphlpapi)
|
||||
target_link_libraries(kyty_emulator setupapi)
|
||||
target_link_libraries(kyty_emulator ws2_32)
|
||||
endif()
|
||||
if (KYTY_CLANG_CL)
|
||||
target_link_libraries(kyty_emulator onecore)
|
||||
endif()
|
||||
if (CLANG AND NOT KYTY_CLANG_CL)
|
||||
target_link_libraries(kyty_emulator pthread)
|
||||
endif()
|
||||
target_include_directories(kyty_emulator PRIVATE ${inc_headers})
|
||||
|
||||
clang_tidy_check(kyty_emulator "" "${check_headers}" "${inc_headers}")
|
||||
|
||||
include_what_you_use(kyty_emulator "${inc_headers}")
|
||||
|
||||
set(KYTY_EMULATOR_MAP_NAME "kyty_emulator_${KYTY_COMPILER_ID}_${KYTY_LINKER_ID}.map")
|
||||
|
||||
set(KYTY_EMULATOR_MAP_LINK_PATH "${CMAKE_CURRENT_BINARY_DIR}/${KYTY_EMULATOR_MAP_NAME}")
|
||||
set(KYTY_EMULATOR_PDB_LINK_PATH "${CMAKE_CURRENT_BINARY_DIR}/kyty_emulator.pdb")
|
||||
|
||||
if(KYTY_CLANG_CL)
|
||||
set_target_properties(kyty_emulator PROPERTIES LINK_FLAGS "/DYNAMICBASE:NO /DEBUG:FULL /PDB:${KYTY_EMULATOR_PDB_LINK_PATH} /lldmap:${KYTY_EMULATOR_MAP_LINK_PATH}")
|
||||
add_custom_command(TARGET kyty_emulator POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different "${KYTY_THIRD_PARTY_DIR}/winpthread/bin/libwinpthread-1.dll" $<TARGET_FILE_DIR:kyty_emulator>/libwinpthread-1.dll)
|
||||
elseif(WIN32 OR LINUX)
|
||||
set_target_properties(kyty_emulator PROPERTIES LINK_FLAGS "${KYTY_LD_OPTIONS} -Wl,-Map=${KYTY_EMULATOR_MAP_LINK_PATH}")
|
||||
endif()
|
||||
|
||||
add_dependencies(kyty_emulator KytyGitVersion)
|
||||
|
||||
install(TARGETS kyty_emulator DESTINATION .)
|
||||
if(KYTY_BUILD_LAUNCHER)
|
||||
add_subdirectory(launcher)
|
||||
endif()
|
||||
if(KYTY_CLANG_CL)
|
||||
install(FILES "${KYTY_THIRD_PARTY_DIR}/winpthread/bin/libwinpthread-1.dll" DESTINATION .)
|
||||
endif()
|
||||
@@ -0,0 +1,8 @@
|
||||
#define KYTY_VERSION "@KYTY_VERSION@"
|
||||
#define KYTY_ENDIAN @KYTY_ENDIAN@
|
||||
#define KYTY_PLATFORM @KYTY_PLATFORM@
|
||||
#define KYTY_COMPILER KYTY_COMPILER_@KYTY_COMPILER@
|
||||
#define KYTY_LINKER KYTY_LINKER_@KYTY_LINKER@
|
||||
#define KYTY_BUILD @KYTY_BUILD@
|
||||
#cmakedefine KYTY_FINAL
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
file(GLOB common_src CONFIGURE_DEPENDS
|
||||
"*.cpp"
|
||||
"logging/*.cpp"
|
||||
"platform/*.cpp"
|
||||
)
|
||||
|
||||
file(GLOB_RECURSE common_headers CONFIGURE_DEPENDS "*.h")
|
||||
|
||||
if(KYTY_CLANG_CL)
|
||||
set_source_files_properties(${common_src} PROPERTIES COMPILE_FLAGS "-Wno-pragma-pack")
|
||||
endif()
|
||||
|
||||
add_library(common STATIC ${common_src} ${common_headers})
|
||||
|
||||
target_include_directories(common
|
||||
PUBLIC ${PROJECT_SOURCE_DIR}
|
||||
PRIVATE
|
||||
${KYTY_THIRD_PARTY_DIR}/cpuinfo/include
|
||||
)
|
||||
|
||||
target_link_libraries(common
|
||||
PUBLIC
|
||||
fmt::fmt
|
||||
Tracy::TracyClient
|
||||
PRIVATE
|
||||
cpuinfo
|
||||
spdlog::spdlog
|
||||
)
|
||||
|
||||
get_property(inc_headers TARGET common PROPERTY INCLUDE_DIRECTORIES)
|
||||
set(check_headers ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
|
||||
clang_tidy_check(common "" "${check_headers}" "${inc_headers}")
|
||||
include_what_you_use(common "${inc_headers}")
|
||||
@@ -0,0 +1,12 @@
|
||||
#ifndef KYTY_COMMON_ABI_H_
|
||||
#define KYTY_COMMON_ABI_H_
|
||||
|
||||
#include "common/common.h"
|
||||
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
|
||||
#define KYTY_MS_ABI __attribute__((ms_abi))
|
||||
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
|
||||
#define KYTY_SYSV_ABI __attribute__((sysv_abi))
|
||||
|
||||
#endif /* KYTY_COMMON_ABI_H_ */
|
||||
@@ -0,0 +1,65 @@
|
||||
#include "common/assert.h"
|
||||
|
||||
#include "common/debug.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/subsystems.h"
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <fmt/format.h>
|
||||
#include <string>
|
||||
|
||||
namespace Common {
|
||||
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS && KYTY_BUILD == KYTY_BUILD_DEBUG && \
|
||||
KYTY_COMPILER == KYTY_COMPILER_CLANG
|
||||
constexpr int PRINT_STACK_FROM = 4;
|
||||
#else
|
||||
constexpr int PRINT_STACK_FROM = 2;
|
||||
#endif
|
||||
|
||||
static std::string BuildFatalReport(const char* title, std::string_view text, const char* file,
|
||||
int line) {
|
||||
DebugStack stack;
|
||||
DebugStack::Trace(&stack);
|
||||
|
||||
std::string report = "--- Stack Trace ---\n";
|
||||
for (int i = PRINT_STACK_FROM; i < stack.depth; i++) {
|
||||
report += fmt::format("[{}] {:016x}\n", i - PRINT_STACK_FROM,
|
||||
static_cast<uint64_t>(stack.GetAddr(i)));
|
||||
}
|
||||
report += fmt::format("{}\n{} in {}:{}\n", title, text, file, line);
|
||||
return report;
|
||||
}
|
||||
|
||||
static int DbgReport(const char* title, std::string_view text, const char* file, int line) {
|
||||
Log::WriteFatal(BuildFatalReport(title, text, file, line));
|
||||
SubsystemsListSingleton::Instance()->ShutdownAll();
|
||||
return 1;
|
||||
}
|
||||
|
||||
int DbgExitIfHandler(const char* expr, const char* file, int line) {
|
||||
return DbgReport("--- Fatal Error ---", fmt::format("Error: condition ({}) is true", expr),
|
||||
file, line);
|
||||
}
|
||||
|
||||
int DbgNotImplementedHandler(const char* expr, const char* file, int line) {
|
||||
return DbgReport("--- Fatal Error ---", fmt::format("Not implemented ({})", expr), file, line);
|
||||
}
|
||||
|
||||
int DbgExitHandler(const char* file, int line, std::string_view text) {
|
||||
Log::WriteFatal(BuildFatalReport("--- Error ---", text, file, line));
|
||||
return 1;
|
||||
}
|
||||
|
||||
int DbgExitHandler(const char* file, int line, fmt::text_style style, std::string_view text) {
|
||||
Log::WriteFatal(style, BuildFatalReport("--- Error ---", text, file, line));
|
||||
return 1;
|
||||
}
|
||||
|
||||
void DbgExit(int status) {
|
||||
std::fflush(nullptr);
|
||||
std::_Exit(status);
|
||||
}
|
||||
|
||||
} // namespace Common
|
||||
@@ -0,0 +1,67 @@
|
||||
#ifndef KYTY_COMMON_ASSERT_H_
|
||||
#define KYTY_COMMON_ASSERT_H_
|
||||
|
||||
#include "common/common.h"
|
||||
#include "common/logging/log.h"
|
||||
|
||||
#include <cstdlib>
|
||||
#include <string_view>
|
||||
|
||||
namespace Common {
|
||||
|
||||
#ifdef __clang__
|
||||
int DbgExitHandler(char const* file, int line, std::string_view text)
|
||||
__attribute__((analyzer_noreturn));
|
||||
int DbgExitHandler(char const* file, int line, fmt::text_style style, std::string_view text)
|
||||
__attribute__((analyzer_noreturn));
|
||||
int DbgExitIfHandler(char const* expr, char const* file, int line)
|
||||
__attribute__((analyzer_noreturn));
|
||||
int DbgNotImplementedHandler(char const* expr, char const* file, int line)
|
||||
__attribute__((analyzer_noreturn));
|
||||
void DbgExit(int status) __attribute__((analyzer_noreturn));
|
||||
#else
|
||||
int DbgExitHandler(char const* file, int line, std::string_view text);
|
||||
int DbgExitHandler(char const* file, int line, fmt::text_style style, std::string_view text);
|
||||
int DbgExitIfHandler(char const* expr, char const* file, int line);
|
||||
int DbgNotImplementedHandler(char const* expr, char const* file, int line);
|
||||
void DbgExit(int status);
|
||||
#endif
|
||||
|
||||
} // namespace Common
|
||||
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS || KYTY_PLATFORM == KYTY_PLATFORM_LINUX
|
||||
#define EXIT_HALT() (Common::DbgExit(321), 1)
|
||||
#else
|
||||
#define EXIT_HALT() (std::_Exit(321), 1)
|
||||
#endif
|
||||
|
||||
#ifndef KYTY_FINAL
|
||||
#define EXIT_IF(x) \
|
||||
((void)((x) && Common::DbgExitIfHandler(#x, __FILE__, __LINE__) != 0 && (EXIT_HALT(), 1) != 0))
|
||||
#else
|
||||
#define EXIT_IF(x) \
|
||||
do { \
|
||||
constexpr bool kyty_exit_if_disabled = false && (x); \
|
||||
(void)kyty_exit_if_disabled; \
|
||||
} while (0)
|
||||
#endif
|
||||
|
||||
#define EXIT(...) \
|
||||
do { \
|
||||
((void)(Common::DbgExitHandler(__FILE__, __LINE__, ::fmt::sprintf(__VA_ARGS__)) && \
|
||||
(EXIT_HALT(), 1))); \
|
||||
} while (0)
|
||||
|
||||
#define EXIT_COLOR(style, ...) \
|
||||
do { \
|
||||
((void)(Common::DbgExitHandler(__FILE__, __LINE__, (style), \
|
||||
::fmt::sprintf(__VA_ARGS__)) && \
|
||||
(EXIT_HALT(), 1))); \
|
||||
} while (0)
|
||||
|
||||
#define EXIT_NOT_IMPLEMENTED(x) \
|
||||
((void)((x) && Common::DbgNotImplementedHandler(#x, __FILE__, __LINE__) != 0 && \
|
||||
(EXIT_HALT(), 1) != 0))
|
||||
#define KYTY_NOT_IMPLEMENTED EXIT_NOT_IMPLEMENTED(true)
|
||||
|
||||
#endif /* KYTY_COMMON_ASSERT_H_ */
|
||||
@@ -0,0 +1,165 @@
|
||||
#ifndef KYTY_COMMON_BYTEBUFFER_H_
|
||||
#define KYTY_COMMON_BYTEBUFFER_H_
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/common.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstddef> // IWYU pragma: export
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace Common {
|
||||
|
||||
using Byte = std::byte;
|
||||
|
||||
class ByteBuffer {
|
||||
public:
|
||||
using DataType = std::vector<Byte>;
|
||||
|
||||
static constexpr uint32_t INVALID_INDEX = static_cast<uint32_t>(-1);
|
||||
|
||||
using iterator = DataType::iterator;
|
||||
using const_iterator = DataType::const_iterator;
|
||||
|
||||
ByteBuffer() = default;
|
||||
ByteBuffer(const ByteBuffer&) = default;
|
||||
ByteBuffer(ByteBuffer&&) = default;
|
||||
|
||||
ByteBuffer& operator=(const ByteBuffer&) = default;
|
||||
ByteBuffer& operator=(ByteBuffer&&) = default;
|
||||
|
||||
explicit ByteBuffer(uint32_t size): m_data(size) {}
|
||||
|
||||
ByteBuffer(std::initializer_list<uint8_t> list) {
|
||||
for (const uint8_t& e: list) {
|
||||
m_data.emplace_back(static_cast<Byte>(e));
|
||||
}
|
||||
}
|
||||
|
||||
explicit ByteBuffer(const void* buf, uint32_t size)
|
||||
: ByteBuffer(size) // @suppress("Ambiguous problem")
|
||||
{
|
||||
EXIT_IF(buf == nullptr && size != 0);
|
||||
|
||||
if (size != 0) {
|
||||
std::memcpy(m_data.data(), buf, size);
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] uint32_t Size() const { return static_cast<uint32_t>(m_data.size()); }
|
||||
[[nodiscard]] bool IsEmpty() const { return m_data.empty(); }
|
||||
|
||||
void Clear() { m_data.clear(); }
|
||||
|
||||
void Add(const Byte& val) { m_data.push_back(val); }
|
||||
void Add(Byte&& val) { m_data.push_back(std::move(val)); }
|
||||
|
||||
void Add(const Byte* val, uint32_t num) {
|
||||
EXIT_IF(val == nullptr && num != 0);
|
||||
m_data.insert(m_data.end(), val, val + num);
|
||||
}
|
||||
|
||||
void Add(const ByteBuffer& v) {
|
||||
if (this == &v) {
|
||||
const ByteBuffer copy(v);
|
||||
Add(copy.GetDataConst(), copy.Size());
|
||||
return;
|
||||
}
|
||||
Add(v.GetDataConst(), v.Size());
|
||||
}
|
||||
|
||||
void InsertAt(uint32_t index, const Byte& val) {
|
||||
EXIT_IF(index > Size());
|
||||
m_data.insert(m_data.begin() + index, val);
|
||||
}
|
||||
|
||||
void InsertAt(uint32_t index, Byte&& val) {
|
||||
EXIT_IF(index > Size());
|
||||
m_data.insert(m_data.begin() + index, std::move(val));
|
||||
}
|
||||
|
||||
[[nodiscard]] uint32_t Find(const Byte& t) const {
|
||||
const auto it = std::find(m_data.begin(), m_data.end(), t);
|
||||
return (it == m_data.end() ? INVALID_INDEX
|
||||
: static_cast<uint32_t>(std::distance(m_data.begin(), it)));
|
||||
}
|
||||
|
||||
[[nodiscard]] bool Contains(const Byte& t) const { return Find(t) != INVALID_INDEX; }
|
||||
|
||||
bool Remove(const Byte& t) {
|
||||
const auto it = std::find(m_data.begin(), m_data.end(), t);
|
||||
if (it == m_data.end()) {
|
||||
return false;
|
||||
}
|
||||
m_data.erase(it);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RemoveAt(uint32_t index, uint32_t count = 1) {
|
||||
if (index >= Size() || count == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const uint32_t erase_count = std::min(count, Size() - index);
|
||||
m_data.erase(m_data.begin() + index, m_data.begin() + index + erase_count);
|
||||
return true;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool IndexValid(uint32_t index) const { return index < Size(); }
|
||||
|
||||
Byte& operator[](uint32_t index) {
|
||||
EXIT_IF(index >= Size());
|
||||
return m_data[index];
|
||||
}
|
||||
|
||||
const Byte& operator[](uint32_t index) const {
|
||||
EXIT_IF(index >= Size());
|
||||
return m_data[index];
|
||||
}
|
||||
|
||||
[[nodiscard]] const Byte& At(uint32_t index) const {
|
||||
EXIT_IF(index >= Size());
|
||||
return m_data[index];
|
||||
}
|
||||
|
||||
Byte* GetData() { return m_data.data(); }
|
||||
[[nodiscard]] const Byte* GetData() const { return m_data.data(); }
|
||||
[[nodiscard]] const Byte* GetDataConst() const { return m_data.data(); }
|
||||
|
||||
void Sort() {
|
||||
std::sort(m_data.begin(), m_data.end(), [](Byte a, Byte b) {
|
||||
return std::to_integer<uint8_t>(a) < std::to_integer<uint8_t>(b);
|
||||
});
|
||||
}
|
||||
|
||||
template <typename OP>
|
||||
void Sort(OP&& comp_func) {
|
||||
std::sort(m_data.begin(), m_data.end(), std::forward<OP>(comp_func));
|
||||
}
|
||||
|
||||
bool operator==(const ByteBuffer& s) const { return m_data == s.m_data; }
|
||||
bool operator!=(const ByteBuffer& s) const { return !(*this == s); }
|
||||
|
||||
iterator begin() { return m_data.begin(); } // NOLINT(readability-identifier-naming)
|
||||
iterator end() { return m_data.end(); } // NOLINT(readability-identifier-naming)
|
||||
[[nodiscard]] const_iterator begin() const {
|
||||
return m_data.begin();
|
||||
} // NOLINT(readability-identifier-naming)
|
||||
[[nodiscard]] const_iterator end() const {
|
||||
return m_data.end();
|
||||
} // NOLINT(readability-identifier-naming)
|
||||
[[nodiscard]] const_iterator cbegin() const {
|
||||
return m_data.cbegin();
|
||||
} // NOLINT(readability-identifier-naming)
|
||||
[[nodiscard]] const_iterator cend() const {
|
||||
return m_data.cend();
|
||||
} // NOLINT(readability-identifier-naming)
|
||||
|
||||
private:
|
||||
DataType m_data;
|
||||
};
|
||||
|
||||
} // namespace Common
|
||||
|
||||
#endif /* KYTY_COMMON_BYTEBUFFER_H_ */
|
||||
@@ -0,0 +1,43 @@
|
||||
#ifndef KYTY_COMMON_COMMON_H_
|
||||
#define KYTY_COMMON_COMMON_H_
|
||||
|
||||
#include "common/config.h" // IWYU pragma: export
|
||||
|
||||
#if __cplusplus < 201703L
|
||||
#undef __cplusplus
|
||||
#define __cplusplus 201703L
|
||||
#endif
|
||||
|
||||
#if defined(__MINGW32__) || defined(__MINGW64__)
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage,cert-dcl51-cpp,cert-dcl37-c,bugprone-reserved-identifier)
|
||||
#define __USE_MINGW_ANSI_STDIO 1
|
||||
#endif
|
||||
|
||||
// IWYU pragma: begin_exports
|
||||
#include <cinttypes>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
// IWYU pragma: end_exports
|
||||
|
||||
#define KYTY_CLASS_NO_COPY(name) \
|
||||
public: \
|
||||
name(const name&) = delete; /* NOLINT(bugprone-macro-parentheses) */ \
|
||||
name& operator=(const name&) = delete; /* NOLINT(bugprone-macro-parentheses) */ \
|
||||
name(name&&) noexcept = delete; /* NOLINT(bugprone-macro-parentheses) */ \
|
||||
name& operator=(name&&) noexcept = delete; /* NOLINT(bugprone-macro-parentheses) */
|
||||
|
||||
#define KYTY_CLASS_DEFAULT_COPY(name) \
|
||||
public: \
|
||||
name(const name&) = default; /* NOLINT(bugprone-macro-parentheses) */ \
|
||||
name& operator=(const name&) = default; /* NOLINT(bugprone-macro-parentheses) */ \
|
||||
name(name&&) noexcept = default; /* NOLINT(bugprone-macro-parentheses) */ \
|
||||
name& operator=(name&&) noexcept = default; /* NOLINT(bugprone-macro-parentheses) */
|
||||
|
||||
#if KYTY_COMPILER == KYTY_COMPILER_GCC
|
||||
#define KYTY_FORMAT_PRINTF(a, b) __attribute__((format(gnu_printf, a, b)))
|
||||
#elif KYTY_COMPILER == KYTY_COMPILER_CLANG
|
||||
#define KYTY_FORMAT_PRINTF(a, b) __attribute__((format(printf, a, b)))
|
||||
#endif
|
||||
|
||||
#endif /* KYTY_COMMON_COMMON_H_ */
|
||||
@@ -0,0 +1,21 @@
|
||||
#include "common/commonSubsystem.h"
|
||||
|
||||
#include "common/byteBuffer.h" // IWYU pragma: associated
|
||||
#include "common/common.h" // IWYU pragma: associated
|
||||
#include "common/debug.h" // IWYU pragma: associated
|
||||
#include "common/hash.h" // IWYU pragma: associated
|
||||
#include "common/magicEnum.h" // IWYU pragma: associated
|
||||
#include "common/singleton.h" // IWYU pragma: associated
|
||||
#include "common/virtualMemory.h"
|
||||
|
||||
namespace Common {
|
||||
|
||||
KYTY_SUBSYSTEM_INIT(Common) {
|
||||
VirtualMemory::Init();
|
||||
}
|
||||
|
||||
KYTY_SUBSYSTEM_UNEXPECTED_SHUTDOWN(Common) {}
|
||||
|
||||
KYTY_SUBSYSTEM_DESTROY(Common) {}
|
||||
|
||||
} // namespace Common
|
||||
@@ -0,0 +1,12 @@
|
||||
#ifndef KYTY_COMMON_COMMON_SUBSYSTEM_H_
|
||||
#define KYTY_COMMON_COMMON_SUBSYSTEM_H_
|
||||
|
||||
#include "common/subsystems.h"
|
||||
|
||||
namespace Common {
|
||||
|
||||
KYTY_SUBSYSTEM_DEFINE(Common);
|
||||
|
||||
} // namespace Common
|
||||
|
||||
#endif /* KYTY_COMMON_COMMON_SUBSYSTEM_H_ */
|
||||
@@ -0,0 +1,22 @@
|
||||
#ifndef KYTY_COMMON_CONFIG_H_
|
||||
#define KYTY_COMMON_CONFIG_H_
|
||||
|
||||
#define KYTY_PLATFORM_WINDOWS 1
|
||||
#define KYTY_PLATFORM_LINUX 5
|
||||
|
||||
#define KYTY_COMPILER_GCC 1
|
||||
#define KYTY_COMPILER_CLANG 2
|
||||
|
||||
#define KYTY_LINKER_LD 1
|
||||
#define KYTY_LINKER_LLD 2
|
||||
#define KYTY_LINKER_LLD_LINK 3
|
||||
|
||||
#define KYTY_BUILD_DEBUG 1
|
||||
#define KYTY_BUILD_RELEASE 2
|
||||
|
||||
#define KYTY_ENDIAN_BIG 1
|
||||
#define KYTY_ENDIAN_LITTLE 2
|
||||
|
||||
#include "cmake_config.h"
|
||||
|
||||
#endif /* KYTY_COMMON_CONFIG_H_ */
|
||||
@@ -0,0 +1,694 @@
|
||||
#include "common/dateTime.h"
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/platform/sysTimer.h"
|
||||
#include "common/stringUtils.h"
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
namespace Common {
|
||||
|
||||
template <class T>
|
||||
constexpr T FloorDiv(T a, T b) {
|
||||
if (b < 0) {
|
||||
if (a < 0) {
|
||||
return a / b;
|
||||
}
|
||||
b = -b;
|
||||
a = -a;
|
||||
}
|
||||
|
||||
return (a - (a < 0 ? b - 1 : 0)) / b;
|
||||
}
|
||||
|
||||
static void YmdToJd(int year, int month, int day, jd_t* jd) {
|
||||
jd_t t_year = year;
|
||||
jd_t t_month = month;
|
||||
jd_t t_day = day;
|
||||
|
||||
if (t_year < 0) {
|
||||
t_year++;
|
||||
}
|
||||
|
||||
jd_t a = FloorDiv<jd_t>(14 - t_month, 12);
|
||||
jd_t y = t_year + 4800 - a;
|
||||
jd_t m = t_month + 12 * a - 3;
|
||||
*jd = t_day + FloorDiv<jd_t>(153 * m + 2, 5) + 365 * y + FloorDiv<jd_t>(y, 4) -
|
||||
FloorDiv<jd_t>(y, 100) + FloorDiv<jd_t>(y, 400) - 32045;
|
||||
}
|
||||
|
||||
static void JdToYmd(int* year, int* month, int* day, jd_t jd) {
|
||||
jd_t a = jd + 32044;
|
||||
jd_t b = FloorDiv<jd_t>(4 * a + 3, 146097);
|
||||
jd_t c = a - FloorDiv<jd_t>(146097 * b, 4);
|
||||
jd_t d = FloorDiv<jd_t>(4 * c + 3, 1461);
|
||||
jd_t e = c - FloorDiv<jd_t>(1461 * d, 4);
|
||||
jd_t m = FloorDiv<jd_t>(5 * e + 2, 153);
|
||||
|
||||
int t_day = e - FloorDiv<jd_t>(153 * m + 2, 5) + 1;
|
||||
int t_month = m + 3 - 12 * FloorDiv<jd_t>(m, 10);
|
||||
int t_year = 100 * b + d - 4800 + FloorDiv<jd_t>(m, 10);
|
||||
|
||||
if (t_year <= 0) {
|
||||
t_year--;
|
||||
}
|
||||
|
||||
if (year != nullptr) {
|
||||
*year = t_year;
|
||||
}
|
||||
if (month != nullptr) {
|
||||
*month = t_month;
|
||||
}
|
||||
if (day != nullptr) {
|
||||
*day = t_day;
|
||||
}
|
||||
}
|
||||
|
||||
static void HmsToMs(int hour, int minute, int second, int msec, int* ms) {
|
||||
*ms = hour * 60 * 60 * 1000 + minute * 60 * 1000 + second * 1000 + msec;
|
||||
}
|
||||
|
||||
static void MsToHms(int* hour, int* minute, int* second, int* msec, int ms) {
|
||||
if (hour != nullptr) {
|
||||
*hour = ms / (60 * 60 * 1000);
|
||||
}
|
||||
if (minute != nullptr) {
|
||||
*minute = (ms % (60 * 60 * 1000)) / (60 * 1000);
|
||||
}
|
||||
if (second != nullptr) {
|
||||
*second = (ms / 1000) % 60;
|
||||
}
|
||||
if (msec != nullptr) {
|
||||
*msec = ms % 1000;
|
||||
}
|
||||
}
|
||||
|
||||
Date::Date(int year, int month, int day) {
|
||||
Set(year, month, day);
|
||||
}
|
||||
|
||||
bool Date::IsValid(int year, int month, int day) {
|
||||
if (year == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (day >= 1) &&
|
||||
(day <= DaysInMonth(month) || (day == 29 && month == 2 && IsLeapYear(year)));
|
||||
}
|
||||
|
||||
int Date::DaysInMonth() const {
|
||||
if (IsInvalid()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int year = 0;
|
||||
int month = 0;
|
||||
JdToYmd(&year, &month, nullptr, m_jd);
|
||||
|
||||
if (month == 2 && IsLeapYear(year)) {
|
||||
return 29;
|
||||
}
|
||||
|
||||
return DaysInMonth(month);
|
||||
}
|
||||
|
||||
bool Date::IsLeapYear() const {
|
||||
if (IsInvalid()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int year = 0;
|
||||
JdToYmd(&year, nullptr, nullptr, m_jd);
|
||||
|
||||
return IsLeapYear(year);
|
||||
}
|
||||
|
||||
int Date::DaysInMonth(int month) {
|
||||
static const int days[] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
|
||||
|
||||
if (month < 1 || month > 12) {
|
||||
month = 0;
|
||||
}
|
||||
|
||||
return days[month];
|
||||
}
|
||||
|
||||
void Date::Set(int year, int month, int day) {
|
||||
if (IsValid(year, month, day)) {
|
||||
YmdToJd(year, month, day, &m_jd);
|
||||
} else {
|
||||
m_jd = DATE_JD_INVALID;
|
||||
}
|
||||
}
|
||||
|
||||
void Date::Get(int* year, int* month, int* day) const {
|
||||
if (IsInvalid()) {
|
||||
if (year != nullptr) {
|
||||
*year = 0;
|
||||
}
|
||||
if (month != nullptr) {
|
||||
*month = 0;
|
||||
}
|
||||
if (day != nullptr) {
|
||||
*day = 0;
|
||||
}
|
||||
} else {
|
||||
JdToYmd(year, month, day, m_jd);
|
||||
}
|
||||
}
|
||||
|
||||
bool Date::IsLeapYear(int year) {
|
||||
if (year < 1) {
|
||||
year++;
|
||||
}
|
||||
|
||||
return ((year % 4) == 0 && (year % 100) != 0) || (year % 400) == 0;
|
||||
}
|
||||
|
||||
int Date::Year() const {
|
||||
if (IsInvalid()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int year = 0;
|
||||
JdToYmd(&year, nullptr, nullptr, m_jd);
|
||||
|
||||
return year;
|
||||
}
|
||||
|
||||
int Date::Month() const {
|
||||
if (IsInvalid()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int month = 0;
|
||||
JdToYmd(nullptr, &month, nullptr, m_jd);
|
||||
|
||||
return month;
|
||||
}
|
||||
|
||||
int Date::Day() const {
|
||||
if (IsInvalid()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int day = 0;
|
||||
JdToYmd(nullptr, nullptr, &day, m_jd);
|
||||
|
||||
return day;
|
||||
}
|
||||
|
||||
int Date::DaysInYear() const {
|
||||
if (IsInvalid()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return IsLeapYear() ? 366 : 365;
|
||||
}
|
||||
|
||||
int Date::DayOfWeek() const {
|
||||
if (IsInvalid()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (m_jd >= 0) {
|
||||
return (m_jd % 7) + 1;
|
||||
}
|
||||
|
||||
return ((m_jd + 1) % 7) + 7;
|
||||
}
|
||||
|
||||
int Date::DayOfYear() const {
|
||||
if (IsInvalid()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
jd_t d = 0;
|
||||
YmdToJd(Year(), 1, 1, &d);
|
||||
|
||||
return m_jd - d + 1;
|
||||
}
|
||||
|
||||
int Date::DaysInYear(int year) {
|
||||
return IsLeapYear(year) ? 366 : 365;
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(readability-non-const-parameter)
|
||||
static bool FormatDate(const Date* d, const std::string& f, size_t* out_i, std::string* out_r) {
|
||||
static const char* month_short[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun",
|
||||
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
|
||||
static const char* month_long[] = {"January", "February", "March", "April",
|
||||
"May", "June", "July", "August",
|
||||
"September", "October", "November", "December"};
|
||||
static const char* day_short[] = {"Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"};
|
||||
static const char* day_long[] = {"Monday", "Tuesday", "Wednesday", "Thursday",
|
||||
"Friday", "Saturday", "Sunday"};
|
||||
|
||||
auto& i = *out_i;
|
||||
auto& r = *out_r;
|
||||
if (f.compare(i, 4, "YYYY") == 0) {
|
||||
int y = d->Year();
|
||||
EXIT_IF(y < 0 || y > 9999);
|
||||
r += fmt::format("{:04d}", y);
|
||||
i += 3;
|
||||
} else if (f.compare(i, 3, "YYY") == 0) {
|
||||
int y = d->Year();
|
||||
EXIT_IF(y < 0 || y > 9999);
|
||||
r += Mid(fmt::format("{:04d}", y), 1);
|
||||
i += 2;
|
||||
} else if (f.compare(i, 2, "YY") == 0) {
|
||||
int y = d->Year();
|
||||
EXIT_IF(y < 0 || y > 9999);
|
||||
r += Mid(fmt::format("{:04d}", y), 2);
|
||||
i += 1;
|
||||
} else if (f.compare(i, 1, "Y") == 0) {
|
||||
int y = d->Year();
|
||||
EXIT_IF(y < 0 || y > 9999);
|
||||
r += Mid(fmt::format("{:04d}", y), 3);
|
||||
} else if (f.compare(i, 1, "Q") == 0) {
|
||||
r += fmt::format("{}", d->QuarterOfYear());
|
||||
} else if (f.compare(i, 2, "MM") == 0) {
|
||||
r += fmt::format("{:02d}", d->Month());
|
||||
i += 1;
|
||||
} else if (f.compare(i, 5, "MONTH") == 0) {
|
||||
r += month_long[d->Month() - 1];
|
||||
i += 4;
|
||||
} else if (f.compare(i, 3, "MON") == 0) {
|
||||
r += month_short[d->Month() - 1];
|
||||
i += 2;
|
||||
} else if (f.compare(i, 3, "DDD") == 0) {
|
||||
r += fmt::format("{:03d}", d->DayOfYear());
|
||||
i += 2;
|
||||
} else if (f.compare(i, 2, "DD") == 0) {
|
||||
r += fmt::format("{:02d}", d->Day());
|
||||
i += 1;
|
||||
} else if (f.compare(i, 2, "DY") == 0) {
|
||||
r += day_short[d->DayOfWeek() - 1];
|
||||
i += 1;
|
||||
} else if (f.compare(i, 3, "DAY") == 0) {
|
||||
r += day_long[d->DayOfWeek() - 1];
|
||||
i += 2;
|
||||
} else if (f.compare(i, 1, "D") == 0) {
|
||||
r += fmt::format("{}", d->DayOfWeek());
|
||||
} else if (f.compare(i, 1, "J") == 0) {
|
||||
r += fmt::format("{}", d->JulianDay());
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* YYYY - 4-digit year
|
||||
* YYY, YY, Y - Last 3, 2, or 1 digit(s) of year.
|
||||
* Q - Quarter of year (1, 2, 3, 4; JAN-MAR = 1).
|
||||
* MM - Month (01-12; JAN = 01).
|
||||
* MON - Abbreviated name of month.
|
||||
* MONTH - Name of month
|
||||
* D - Day of week (1-7).
|
||||
* DAY - Name of day.
|
||||
* DY - Abbreviated name of day.
|
||||
* DD - Day of month (01-31).
|
||||
* DDD - Day of year (001-366).
|
||||
* J - Julian day
|
||||
*/
|
||||
std::string Date::ToString(const char* format) const {
|
||||
if (IsInvalid()) {
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string r;
|
||||
std::string f = format;
|
||||
|
||||
for (size_t i = 0; i < f.size(); i++) {
|
||||
if (!FormatDate(this, f, &i, &r)) {
|
||||
r += f[i];
|
||||
}
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
int Date::QuarterOfYear() const {
|
||||
if (IsInvalid()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return (Month() - 1) / 3 + 1;
|
||||
}
|
||||
|
||||
Date Date::FromMacros(const std::string& date) {
|
||||
auto lst = Split(date, ' ');
|
||||
|
||||
if (lst.size() != 3) {
|
||||
return {};
|
||||
}
|
||||
|
||||
static const char* month_str[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun",
|
||||
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
|
||||
|
||||
int month = -1;
|
||||
for (int i = 0; i < 12; i++) {
|
||||
if (lst[0] == month_str[i]) {
|
||||
month = i + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (month < 0) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return Date(ToInt32(lst[2]), month, ToInt32(lst[1]));
|
||||
}
|
||||
|
||||
Time::Time(int hour, int minute, int second, int msec) {
|
||||
Set(hour, minute, second, msec);
|
||||
}
|
||||
|
||||
void Time::Set(int hour, int minute, int second, int msec) {
|
||||
if (IsValid(hour, minute, second, msec)) {
|
||||
HmsToMs(hour, minute, second, msec, &m_ms);
|
||||
} else {
|
||||
m_ms = TIME_MS_INVALID;
|
||||
}
|
||||
}
|
||||
|
||||
void Time::Get(int* hour, int* minute, int* second, int* msec) const {
|
||||
if (IsInvalid()) {
|
||||
if (hour != nullptr) {
|
||||
*hour = -1;
|
||||
}
|
||||
if (minute != nullptr) {
|
||||
*minute = -1;
|
||||
}
|
||||
if (second != nullptr) {
|
||||
*second = -1;
|
||||
}
|
||||
if (msec != nullptr) {
|
||||
*msec = -1;
|
||||
}
|
||||
} else {
|
||||
MsToHms(hour, minute, second, msec, m_ms);
|
||||
}
|
||||
}
|
||||
|
||||
bool Time::IsValid(int hour, int minute, int second, int msec) {
|
||||
return (hour >= 0 && hour <= 23) && (minute >= 0 && minute <= 59) &&
|
||||
(second >= 0 && second <= 59) && (msec >= 0 && msec <= 999);
|
||||
}
|
||||
|
||||
int Time::Hour24() const {
|
||||
if (IsInvalid()) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int h = 0;
|
||||
MsToHms(&h, nullptr, nullptr, nullptr, m_ms);
|
||||
|
||||
return h;
|
||||
}
|
||||
|
||||
int Time::Hour12() const {
|
||||
if (IsInvalid()) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int h = 0;
|
||||
MsToHms(&h, nullptr, nullptr, nullptr, m_ms);
|
||||
|
||||
h = h % 12;
|
||||
|
||||
return h == 0 ? 12 : h;
|
||||
}
|
||||
|
||||
int Time::Minute() const {
|
||||
if (IsInvalid()) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int m = 0;
|
||||
MsToHms(nullptr, &m, nullptr, nullptr, m_ms);
|
||||
|
||||
return m;
|
||||
}
|
||||
|
||||
int Time::Second() const {
|
||||
if (IsInvalid()) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int s = 0;
|
||||
MsToHms(nullptr, nullptr, &s, nullptr, m_ms);
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
int Time::Msec() const {
|
||||
if (IsInvalid()) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int m = 0;
|
||||
MsToHms(nullptr, nullptr, nullptr, &m, m_ms);
|
||||
|
||||
return m;
|
||||
}
|
||||
|
||||
bool Time::IsAM() const {
|
||||
if (IsInvalid()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int h = 0;
|
||||
MsToHms(&h, nullptr, nullptr, nullptr, m_ms);
|
||||
|
||||
return h < 12;
|
||||
}
|
||||
|
||||
bool Time::IsPM() const {
|
||||
if (IsInvalid()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int h = 0;
|
||||
MsToHms(&h, nullptr, nullptr, nullptr, m_ms);
|
||||
|
||||
return h >= 12;
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(readability-non-const-parameter)
|
||||
static bool FormatTime(const Time* t, const std::string& f, size_t* out_i, std::string* out_r) {
|
||||
auto& i = *out_i;
|
||||
auto& r = *out_r;
|
||||
if (f.compare(i, 4, "HH24") == 0) {
|
||||
r += fmt::format("{:02d}", t->Hour24());
|
||||
i += 3;
|
||||
} else if (f.compare(i, 4, "HH12") == 0) {
|
||||
r += fmt::format("{:02d}", t->Hour12());
|
||||
i += 3;
|
||||
} else if (f.compare(i, 2, "HH") == 0) {
|
||||
r += fmt::format("{:02d}", t->Hour12());
|
||||
i += 1;
|
||||
} else if (f.compare(i, 2, "MI") == 0) {
|
||||
r += fmt::format("{:02d}", t->Minute());
|
||||
i += 1;
|
||||
} else if (f.compare(i, 5, "SSSSS") == 0) {
|
||||
r += fmt::format("{:05d}", t->MsecTotal() / 1000);
|
||||
i += 4;
|
||||
} else if (f.compare(i, 2, "SS") == 0) {
|
||||
r += fmt::format("{:02d}", t->Second());
|
||||
i += 1;
|
||||
} else if (f.compare(i, 3, "FFF") == 0) {
|
||||
r += fmt::format("{:03d}", t->Msec());
|
||||
i += 2;
|
||||
} else if (f.compare(i, 2, "AM") == 0) {
|
||||
r += t->IsAM() ? "AM" : "PM";
|
||||
i += 1;
|
||||
} else if (f.compare(i, 4, "A.M.") == 0) {
|
||||
r += t->IsAM() ? "A.M." : "P.M.";
|
||||
i += 3;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* HH - Hour of day (1-12)
|
||||
* HH12 - Hour of day (1-12)
|
||||
* HH24 - Hour of day (0-23)
|
||||
* MI - Minute (0-59)
|
||||
* SS - Second (0-59)
|
||||
* SSSSS - Seconds past midnight (0-86399)
|
||||
* FFF - Milliseconds
|
||||
* AM - AM or PM
|
||||
* A.M. - A.M. or P.M.
|
||||
*/
|
||||
std::string Time::ToString(const char* format) const {
|
||||
if (IsInvalid()) {
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string r;
|
||||
std::string f = format;
|
||||
|
||||
for (size_t i = 0; i < f.size(); i++) {
|
||||
if (!FormatTime(this, f, &i, &r)) {
|
||||
r += f[i];
|
||||
}
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
Time Time::operator+(int secs) const {
|
||||
int ms_secs = secs * 1000;
|
||||
|
||||
EXIT_IF(ms_secs > TIME_MS_IN_DAY || ms_secs < -TIME_MS_IN_DAY);
|
||||
|
||||
if (IsInvalid()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
int r = m_ms + ms_secs;
|
||||
|
||||
if (r < 0) {
|
||||
r += TIME_MS_IN_DAY;
|
||||
}
|
||||
if (r >= TIME_MS_IN_DAY) {
|
||||
r -= TIME_MS_IN_DAY;
|
||||
}
|
||||
|
||||
return Time(r);
|
||||
}
|
||||
|
||||
Time Time::operator-(int secs) const {
|
||||
int ms_secs = secs * 1000;
|
||||
|
||||
EXIT_IF(ms_secs > TIME_MS_IN_DAY || ms_secs < -TIME_MS_IN_DAY);
|
||||
|
||||
if (IsInvalid()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
int r = m_ms - ms_secs;
|
||||
|
||||
if (r < 0) {
|
||||
r += TIME_MS_IN_DAY;
|
||||
}
|
||||
if (r >= TIME_MS_IN_DAY) {
|
||||
r -= TIME_MS_IN_DAY;
|
||||
}
|
||||
|
||||
return Time(r);
|
||||
}
|
||||
|
||||
Time Time::operator+=(int secs) {
|
||||
int ms_secs = secs * 1000;
|
||||
|
||||
EXIT_IF(ms_secs > TIME_MS_IN_DAY || ms_secs < -TIME_MS_IN_DAY);
|
||||
|
||||
if (!IsInvalid()) {
|
||||
m_ms += ms_secs;
|
||||
|
||||
if (m_ms < 0) {
|
||||
m_ms += TIME_MS_IN_DAY;
|
||||
}
|
||||
if (m_ms >= TIME_MS_IN_DAY) {
|
||||
m_ms -= TIME_MS_IN_DAY;
|
||||
}
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
Time Time::operator-=(int secs) {
|
||||
int ms_secs = secs * 1000;
|
||||
|
||||
EXIT_IF(ms_secs > TIME_MS_IN_DAY || ms_secs < -TIME_MS_IN_DAY);
|
||||
|
||||
if (!IsInvalid()) {
|
||||
m_ms -= ms_secs;
|
||||
|
||||
if (m_ms < 0) {
|
||||
m_ms += TIME_MS_IN_DAY;
|
||||
}
|
||||
if (m_ms >= TIME_MS_IN_DAY) {
|
||||
m_ms -= TIME_MS_IN_DAY;
|
||||
}
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
DateTime DateTime::FromSystem() {
|
||||
SysTimeStruct t {};
|
||||
SysGetSystemTime(t);
|
||||
|
||||
if (t.is_invalid) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return DateTime(Date(t.Year, t.Month, t.Day), Time(t.Hour, t.Minute, t.Second, t.Milliseconds));
|
||||
}
|
||||
|
||||
DateTime DateTime::FromSystemUTC() {
|
||||
SysTimeStruct t {};
|
||||
SysGetSystemTimeUtc(t);
|
||||
|
||||
if (t.is_invalid) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return DateTime(Date(t.Year, t.Month, t.Day), Time(t.Hour, t.Minute, t.Second, t.Milliseconds));
|
||||
}
|
||||
|
||||
/**
|
||||
* YYYY - 4-digit year
|
||||
* YYY, YY, Y - Last 3, 2, or 1 digit(s) of year.
|
||||
* Q - Quarter of year (1, 2, 3, 4; JAN-MAR = 1).
|
||||
* MM - Month (01-12; JAN = 01).
|
||||
* MON - Abbreviated name of month.
|
||||
* MONTH - Name of month
|
||||
* D - Day of week (1-7).
|
||||
* DAY - Name of day.
|
||||
* DY - Abbreviated name of day.
|
||||
* DD - Day of month (01-31).
|
||||
* DDD - Day of year (001-366).
|
||||
* J - Julian day
|
||||
* HH - Hour of day (1-12)
|
||||
* HH12 - Hour of day (1-12)
|
||||
* HH24 - Hour of day (0-23)
|
||||
* MI - Minute (0-59)
|
||||
* SS - Second (0-59)
|
||||
* SSSSS - Seconds past midnight (0-86399)
|
||||
* FFF - Milliseconds
|
||||
* AM - AM or PM
|
||||
* A.M. - A.M. or P.M.
|
||||
*/
|
||||
std::string DateTime::ToString(const char* format) const {
|
||||
if (IsInvalid()) {
|
||||
return "";
|
||||
}
|
||||
|
||||
std::string r;
|
||||
std::string f = format;
|
||||
|
||||
for (size_t i = 0; i < f.size(); i++) {
|
||||
if (!FormatDate(&m_date, f, &i, &r) && !FormatTime(&m_time, f, &i, &r)) {
|
||||
r += f[i];
|
||||
}
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
double DateTime::ToUnix() const {
|
||||
return (static_cast<double>(GetDate().JulianDay()) - 2440588.0) * 86400.0 +
|
||||
static_cast<double>(GetTime().MsecTotal()) / 1000.0;
|
||||
}
|
||||
|
||||
} // namespace Common
|
||||
@@ -0,0 +1,183 @@
|
||||
#ifndef KYTY_COMMON_DATETIME_H_
|
||||
#define KYTY_COMMON_DATETIME_H_
|
||||
|
||||
#include "common/common.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace Common {
|
||||
|
||||
using jd_t = int32_t;
|
||||
|
||||
constexpr jd_t DATE_JD_INVALID = (INT32_MIN);
|
||||
constexpr int TIME_MS_INVALID = (-1);
|
||||
constexpr int TIME_MS_IN_DAY = (24 * 3600 * 1000);
|
||||
|
||||
constexpr int MONTH_JANUARY = 1;
|
||||
constexpr int MONTH_FEBRUARY = 2;
|
||||
constexpr int MONTH_MARCH = 3;
|
||||
constexpr int MONTH_APRIL = 4;
|
||||
constexpr int MONTH_MAY = 5;
|
||||
constexpr int MONTH_JUNE = 6;
|
||||
constexpr int MONTH_JULY = 7;
|
||||
constexpr int MONTH_AUGUST = 8;
|
||||
constexpr int MONTH_SEPTEMBER = 9;
|
||||
constexpr int MONTH_OCTOBER = 10;
|
||||
constexpr int MONTH_NOVEMBER = 11;
|
||||
constexpr int MONTH_DECEMBER = 12;
|
||||
|
||||
class Date final {
|
||||
public:
|
||||
Date() = default;
|
||||
explicit Date(jd_t d): m_jd(d) {}
|
||||
Date(const Date& d) = default;
|
||||
Date(Date&& d) noexcept = default;
|
||||
explicit Date(int year, int month, int day); // month in [1...12], day in [1...31]
|
||||
~Date() = default;
|
||||
|
||||
static Date FromMacros(const std::string& date);
|
||||
|
||||
void Set(int year, int month, int day);
|
||||
void Set(jd_t jd) { this->m_jd = jd; }
|
||||
void Get(int* year, int* month, int* day) const;
|
||||
|
||||
[[nodiscard]] bool IsInvalid() const { return m_jd == DATE_JD_INVALID; }
|
||||
[[nodiscard]] int DaysInMonth() const;
|
||||
[[nodiscard]] int DaysInYear() const;
|
||||
[[nodiscard]] bool IsLeapYear() const;
|
||||
[[nodiscard]] int Year() const;
|
||||
[[nodiscard]] int Month() const;
|
||||
[[nodiscard]] int Day() const;
|
||||
[[nodiscard]] jd_t JulianDay() const { return m_jd; }
|
||||
[[nodiscard]] int DayOfWeek() const;
|
||||
[[nodiscard]] int DayOfYear() const;
|
||||
[[nodiscard]] int QuarterOfYear() const;
|
||||
|
||||
std::string ToString(const char* format = "YYYY.MM.DD") const;
|
||||
|
||||
static bool IsValid(int year, int month, int day);
|
||||
static int DaysInMonth(int month);
|
||||
static int DaysInYear(int year);
|
||||
static bool IsLeapYear(int year);
|
||||
|
||||
bool operator==(const Date& other) const { return m_jd == other.m_jd; }
|
||||
bool operator!=(const Date& other) const { return m_jd != other.m_jd; }
|
||||
bool operator<(const Date& other) const { return m_jd < other.m_jd; }
|
||||
bool operator<=(const Date& other) const { return m_jd <= other.m_jd; }
|
||||
bool operator>(const Date& other) const { return m_jd > other.m_jd; }
|
||||
bool operator>=(const Date& other) const { return m_jd >= other.m_jd; }
|
||||
|
||||
Date& operator=(const Date& other) = default;
|
||||
Date& operator=(Date&& other) noexcept = default;
|
||||
Date operator+(int days) const { return Date(m_jd + days); }
|
||||
Date operator-(int days) const { return Date(m_jd - days); }
|
||||
Date operator+=(int days) {
|
||||
m_jd += days;
|
||||
return *this;
|
||||
}
|
||||
Date operator-=(int days) {
|
||||
m_jd -= days;
|
||||
return *this;
|
||||
}
|
||||
|
||||
private:
|
||||
jd_t m_jd {DATE_JD_INVALID};
|
||||
};
|
||||
|
||||
class Time final {
|
||||
public:
|
||||
Time() = default;
|
||||
explicit Time(int msec): m_ms(msec) {
|
||||
if (m_ms < 0 || m_ms >= TIME_MS_IN_DAY) {
|
||||
m_ms = TIME_MS_INVALID;
|
||||
}
|
||||
}
|
||||
Time(const Time& t) = default;
|
||||
Time(Time&& t) noexcept = default;
|
||||
explicit Time(int hour24, int minute, int second, int msec = 0);
|
||||
~Time() = default;
|
||||
|
||||
void Set(int hour24, int minute, int second, int msec = 0);
|
||||
void Set(int msec) { m_ms = msec; }
|
||||
void Get(int* hour24, int* minute, int* second, int* msec = nullptr) const;
|
||||
|
||||
[[nodiscard]] bool IsInvalid() const { return m_ms < 0 || m_ms >= TIME_MS_IN_DAY; }
|
||||
[[nodiscard]] int Hour12() const;
|
||||
[[nodiscard]] int Hour24() const;
|
||||
[[nodiscard]] bool IsAM() const;
|
||||
[[nodiscard]] bool IsPM() const;
|
||||
[[nodiscard]] int Minute() const;
|
||||
[[nodiscard]] int Second() const;
|
||||
[[nodiscard]] int Msec() const;
|
||||
[[nodiscard]] int MsecTotal() const { return m_ms; }
|
||||
|
||||
std::string ToString(const char* format = "HH24:MI:SS") const;
|
||||
|
||||
static bool IsValid(int hour, int minute, int second, int msec = 0);
|
||||
|
||||
bool operator==(const Time& other) const { return m_ms == other.m_ms; }
|
||||
bool operator!=(const Time& other) const { return m_ms != other.m_ms; }
|
||||
bool operator<(const Time& other) const { return m_ms < other.m_ms; }
|
||||
bool operator<=(const Time& other) const { return m_ms <= other.m_ms; }
|
||||
bool operator>(const Time& other) const { return m_ms > other.m_ms; }
|
||||
bool operator>=(const Time& other) const { return m_ms >= other.m_ms; }
|
||||
|
||||
Time& operator=(const Time& other) = default;
|
||||
Time& operator=(Time&& other) noexcept = default;
|
||||
Time operator+(int secs) const;
|
||||
Time operator-(int secs) const;
|
||||
Time operator+=(int secs);
|
||||
Time operator-=(int secs);
|
||||
|
||||
private:
|
||||
int m_ms {TIME_MS_INVALID};
|
||||
};
|
||||
|
||||
class DateTime final {
|
||||
public:
|
||||
DateTime() = default;
|
||||
explicit DateTime(const Date& d): m_date(d) {}
|
||||
explicit DateTime(const Time& t): m_time(t) {}
|
||||
explicit DateTime(const Date& d, const Time& t): m_date(d), m_time(t) {}
|
||||
DateTime(const DateTime& dt) = default;
|
||||
DateTime(DateTime&& dt) noexcept = default;
|
||||
~DateTime() = default;
|
||||
|
||||
static DateTime FromSystem();
|
||||
static DateTime FromSystemUTC();
|
||||
|
||||
[[nodiscard]] double ToUnix() const;
|
||||
|
||||
[[nodiscard]] bool IsInvalid() const { return m_date.IsInvalid() || m_time.IsInvalid(); }
|
||||
|
||||
void SetDate(const Date& d) { m_date = d; }
|
||||
void SetTime(const Time& t) { m_time = t; }
|
||||
Date& GetDate() { return m_date; }
|
||||
Time& GetTime() { return m_time; }
|
||||
[[nodiscard]] const Date& GetDate() const { return m_date; }
|
||||
[[nodiscard]] const Time& GetTime() const { return m_time; }
|
||||
|
||||
std::string ToString(const char* format = "YYYY.MM.DD HH24:MI:SS") const;
|
||||
|
||||
DateTime& operator=(const DateTime& other) = default;
|
||||
DateTime& operator=(DateTime&& other) noexcept = default;
|
||||
|
||||
bool operator==(const DateTime& other) const {
|
||||
return m_date == other.m_date && m_time == other.m_time;
|
||||
}
|
||||
bool operator!=(const DateTime& other) const { return !(*this == other); }
|
||||
bool operator<(const DateTime& other) const {
|
||||
return m_date < other.m_date || (m_date == other.m_date && m_time < other.m_time);
|
||||
}
|
||||
bool operator<=(const DateTime& other) const { return !(other < *this); }
|
||||
bool operator>(const DateTime& other) const { return other < *this; }
|
||||
bool operator>=(const DateTime& other) const { return !(*this < other); }
|
||||
|
||||
private:
|
||||
Date m_date;
|
||||
Time m_time;
|
||||
};
|
||||
|
||||
} // namespace Common
|
||||
|
||||
#endif /* KYTY_COMMON_DATETIME_H_ */
|
||||
@@ -0,0 +1,44 @@
|
||||
#include "common/debug.h"
|
||||
|
||||
#include "common/platform/sysDbg.h"
|
||||
|
||||
namespace Common {
|
||||
|
||||
void DebugStack::Print(int from, [[maybe_unused]] bool with_name) const {
|
||||
for (int i = from; i < depth; i++) {
|
||||
printf("[%d] %016" PRIx64 "\n", i - from, static_cast<uint64_t>(GetAddr(i)));
|
||||
}
|
||||
}
|
||||
|
||||
void DebugStack::Trace(DebugStack* stack) {
|
||||
stack->depth = DEBUG_MAX_STACK_DEPTH;
|
||||
SysStackWalk(stack->stack.data(), &stack->depth);
|
||||
}
|
||||
|
||||
std::string Debug::GetCompiler() {
|
||||
#if KYTY_COMPILER == KYTY_COMPILER_CLANG
|
||||
return "clang";
|
||||
#elif KYTY_COMPILER == KYTY_COMPILER_GCC
|
||||
return "gcc";
|
||||
#else
|
||||
return "????";
|
||||
#endif
|
||||
}
|
||||
|
||||
std::string Debug::GetLinker() {
|
||||
#if KYTY_LINKER == KYTY_LINKER_LD
|
||||
return "ld";
|
||||
#elif KYTY_LINKER == KYTY_LINKER_LLD
|
||||
return "lld";
|
||||
#elif KYTY_LINKER == KYTY_LINKER_LLD_LINK
|
||||
return "lld_link";
|
||||
#else
|
||||
return "??";
|
||||
#endif
|
||||
}
|
||||
|
||||
std::string Debug::GetBitness() {
|
||||
return "64";
|
||||
}
|
||||
|
||||
} // namespace Common
|
||||
@@ -0,0 +1,42 @@
|
||||
#ifndef KYTY_COMMON_DEBUG_H_
|
||||
#define KYTY_COMMON_DEBUG_H_
|
||||
|
||||
#include "common/common.h"
|
||||
#include "common/stringUtils.h"
|
||||
|
||||
#include <array>
|
||||
|
||||
namespace Common {
|
||||
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS && KYTY_BUILD == KYTY_BUILD_DEBUG && \
|
||||
KYTY_COMPILER == KYTY_COMPILER_CLANG
|
||||
constexpr int DEBUG_MAX_STACK_DEPTH = 20;
|
||||
#else
|
||||
constexpr int DEBUG_MAX_STACK_DEPTH = 15;
|
||||
#endif
|
||||
|
||||
namespace Debug {
|
||||
std::string GetCompiler();
|
||||
std::string GetLinker();
|
||||
std::string GetBitness();
|
||||
} // namespace Debug
|
||||
|
||||
struct DebugStack {
|
||||
int depth {0};
|
||||
std::array<void*, DEBUG_MAX_STACK_DEPTH> stack {};
|
||||
|
||||
[[nodiscard]] uintptr_t GetAddr(int i) const { return reinterpret_cast<uintptr_t>(stack[i]); }
|
||||
|
||||
void Print(int from, bool with_name = true) const;
|
||||
|
||||
void CopyTo(DebugStack* s) const {
|
||||
std::memcpy(s->stack.data(), stack.data(), sizeof(void*) * depth);
|
||||
s->depth = depth;
|
||||
}
|
||||
|
||||
static void Trace(DebugStack* stack);
|
||||
};
|
||||
|
||||
} // namespace Common
|
||||
|
||||
#endif /* KYTY_COMMON_DEBUG_H_ */
|
||||
@@ -0,0 +1,104 @@
|
||||
#include "common/emulatorConfig.h"
|
||||
|
||||
#include "common/assert.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
|
||||
namespace Config {
|
||||
|
||||
static std::unique_ptr<ConfigOptions> g_config;
|
||||
|
||||
KYTY_SUBSYSTEM_INIT(Config) {
|
||||
EXIT_IF(g_config != nullptr);
|
||||
|
||||
g_config = std::make_unique<ConfigOptions>();
|
||||
}
|
||||
|
||||
KYTY_SUBSYSTEM_UNEXPECTED_SHUTDOWN(Config) {}
|
||||
|
||||
KYTY_SUBSYSTEM_DESTROY(Config) {}
|
||||
|
||||
void Load(const ConfigOptions& cfg) {
|
||||
EXIT_IF(g_config == nullptr);
|
||||
|
||||
*g_config = cfg;
|
||||
}
|
||||
|
||||
uint32_t GetScreenWidth() {
|
||||
return g_config->screen_width;
|
||||
}
|
||||
|
||||
uint32_t GetScreenHeight() {
|
||||
return g_config->screen_height;
|
||||
}
|
||||
|
||||
uint32_t GetVblankFrequency() {
|
||||
return std::clamp(g_config->vblank_frequency, 30u, 360u);
|
||||
}
|
||||
|
||||
bool VulkanValidationEnabled() {
|
||||
return g_config->vulkan_validation_enabled;
|
||||
}
|
||||
|
||||
bool ShaderValidationEnabled() {
|
||||
return g_config->shader_validation_enabled;
|
||||
}
|
||||
|
||||
ShaderOptimizationType GetShaderOptimizationType() {
|
||||
return g_config->shader_optimization_type;
|
||||
}
|
||||
|
||||
ShaderLogDirection GetShaderLogDirection() {
|
||||
return g_config->shader_log_direction;
|
||||
}
|
||||
|
||||
std::filesystem::path GetShaderLogFolder() {
|
||||
return g_config->shader_log_folder;
|
||||
}
|
||||
|
||||
bool CommandBufferDumpEnabled() {
|
||||
return g_config->command_buffer_dump_enabled;
|
||||
}
|
||||
|
||||
std::filesystem::path GetCommandBufferDumpFolder() {
|
||||
return g_config->command_buffer_dump_folder;
|
||||
}
|
||||
|
||||
bool GraphicsDebugDumpEnabled() {
|
||||
return g_config->graphics_debug_dump_enabled;
|
||||
}
|
||||
|
||||
OutputDirection GetPrintfDirection() {
|
||||
return g_config->printf_direction;
|
||||
}
|
||||
|
||||
std::filesystem::path GetPrintfOutputFile() {
|
||||
return g_config->printf_output_file;
|
||||
}
|
||||
|
||||
ProfilerDirection GetProfilerDirection() {
|
||||
return g_config->profiler_direction;
|
||||
}
|
||||
|
||||
bool SpirvDebugPrintfEnabled() {
|
||||
return g_config->spirv_debug_printf_enabled;
|
||||
}
|
||||
|
||||
bool PipelineDumpEnabled() {
|
||||
return g_config->pipeline_dump_enabled;
|
||||
}
|
||||
|
||||
std::filesystem::path GetPipelineDumpFolder() {
|
||||
return g_config->pipeline_dump_folder;
|
||||
}
|
||||
|
||||
bool RenderDocEnabled() {
|
||||
return g_config->renderdoc_enabled;
|
||||
}
|
||||
|
||||
bool NggRectlistDrawEnabled() {
|
||||
return g_config->ngg_rectlist_draw_enabled;
|
||||
}
|
||||
|
||||
} // namespace Config
|
||||
@@ -0,0 +1,75 @@
|
||||
#ifndef KYTY_COMMON_EMULATOR_CONFIG_H_
|
||||
#define KYTY_COMMON_EMULATOR_CONFIG_H_
|
||||
|
||||
#include "common/common.h"
|
||||
#include "common/subsystems.h"
|
||||
|
||||
#include <filesystem>
|
||||
|
||||
namespace Config {
|
||||
|
||||
KYTY_SUBSYSTEM_DEFINE(Config);
|
||||
|
||||
enum class ShaderOptimizationType { None, Size, Performance };
|
||||
|
||||
enum class ShaderLogDirection { Silent, Console, File };
|
||||
|
||||
enum class ProfilerDirection { None, Network };
|
||||
|
||||
enum class OutputDirection { Silent, Console, File };
|
||||
|
||||
struct ConfigOptions {
|
||||
uint32_t screen_width = 1280;
|
||||
uint32_t screen_height = 720;
|
||||
uint32_t vblank_frequency = 60;
|
||||
bool vulkan_validation_enabled = false;
|
||||
bool shader_validation_enabled = false;
|
||||
ShaderOptimizationType shader_optimization_type = ShaderOptimizationType::None;
|
||||
ShaderLogDirection shader_log_direction = ShaderLogDirection::Silent;
|
||||
std::filesystem::path shader_log_folder = "_Shaders";
|
||||
bool command_buffer_dump_enabled = false;
|
||||
std::filesystem::path command_buffer_dump_folder = "_Buffers";
|
||||
bool graphics_debug_dump_enabled = false;
|
||||
OutputDirection printf_direction = OutputDirection::Console;
|
||||
std::filesystem::path printf_output_file = "_kyty.txt";
|
||||
ProfilerDirection profiler_direction = ProfilerDirection::None;
|
||||
bool spirv_debug_printf_enabled = false;
|
||||
bool pipeline_dump_enabled = false;
|
||||
std::filesystem::path pipeline_dump_folder = "_Pipelines";
|
||||
bool renderdoc_enabled = false;
|
||||
bool ngg_rectlist_draw_enabled = true;
|
||||
};
|
||||
|
||||
void Load(const ConfigOptions& cfg);
|
||||
|
||||
uint32_t GetScreenWidth();
|
||||
uint32_t GetScreenHeight();
|
||||
uint32_t GetVblankFrequency();
|
||||
bool VulkanValidationEnabled();
|
||||
|
||||
bool ShaderValidationEnabled();
|
||||
ShaderOptimizationType GetShaderOptimizationType();
|
||||
ShaderLogDirection GetShaderLogDirection();
|
||||
std::filesystem::path GetShaderLogFolder();
|
||||
|
||||
bool CommandBufferDumpEnabled();
|
||||
std::filesystem::path GetCommandBufferDumpFolder();
|
||||
|
||||
bool GraphicsDebugDumpEnabled();
|
||||
|
||||
OutputDirection GetPrintfDirection();
|
||||
std::filesystem::path GetPrintfOutputFile();
|
||||
|
||||
ProfilerDirection GetProfilerDirection();
|
||||
|
||||
bool SpirvDebugPrintfEnabled();
|
||||
|
||||
bool PipelineDumpEnabled();
|
||||
std::filesystem::path GetPipelineDumpFolder();
|
||||
|
||||
bool RenderDocEnabled();
|
||||
bool NggRectlistDrawEnabled();
|
||||
|
||||
} // namespace Config
|
||||
|
||||
#endif /* KYTY_COMMON_EMULATOR_CONFIG_H_ */
|
||||
@@ -0,0 +1,561 @@
|
||||
#include "common/file.h"
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/common.h"
|
||||
#include "common/platform/sysFileIO.h"
|
||||
#include "common/platform/sysTimer.h"
|
||||
#include "common/stringUtils.h"
|
||||
|
||||
#include <cstdarg>
|
||||
#include <cstdio>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
// IWYU pragma: no_include <fileapi.h>
|
||||
// IWYU pragma: no_include <windows.h>
|
||||
// IWYU pragma: no_include <winbase.h>
|
||||
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
#ifdef CreateDirectory
|
||||
#undef CreateDirectory
|
||||
#endif
|
||||
#ifdef DeleteFile
|
||||
#undef DeleteFile
|
||||
#endif
|
||||
#ifdef CopyFile
|
||||
#undef CopyFile
|
||||
#endif
|
||||
#ifdef MoveFile
|
||||
#undef MoveFile
|
||||
#endif
|
||||
#endif
|
||||
|
||||
namespace Common {
|
||||
|
||||
namespace {
|
||||
|
||||
SysFileTimeStruct DateTimeToFileTimeUtc(const DateTime& date_time) {
|
||||
SysTimeStruct time {};
|
||||
time.is_invalid = false;
|
||||
|
||||
int year = 0;
|
||||
int month = 0;
|
||||
int day = 0;
|
||||
date_time.GetDate().Get(&year, &month, &day);
|
||||
time.Year = year;
|
||||
time.Month = month;
|
||||
time.Day = day;
|
||||
|
||||
int hour = 0;
|
||||
int minute = 0;
|
||||
int second = 0;
|
||||
int milliseconds = 0;
|
||||
date_time.GetTime().Get(&hour, &minute, &second, &milliseconds);
|
||||
time.Hour = hour;
|
||||
time.Minute = minute;
|
||||
time.Second = second;
|
||||
time.Milliseconds = milliseconds;
|
||||
|
||||
SysFileTimeStruct file_time {};
|
||||
SysSystemToFileTimeUtc(time, file_time);
|
||||
return file_time;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
struct File::FilePrivate {
|
||||
sys_file_t* f;
|
||||
};
|
||||
|
||||
File::File(): m_p(std::make_unique<FilePrivate>()) {
|
||||
m_p->f = nullptr;
|
||||
}
|
||||
|
||||
File::~File() {
|
||||
EXIT_IF(m_p->f != nullptr);
|
||||
}
|
||||
|
||||
File::File(const std::filesystem::path& name): m_p(std::make_unique<FilePrivate>()) {
|
||||
m_p->f = nullptr;
|
||||
|
||||
Create(name);
|
||||
}
|
||||
|
||||
File::File(const std::filesystem::path& name, Mode mode): m_p(std::make_unique<FilePrivate>()) {
|
||||
m_p->f = nullptr;
|
||||
|
||||
Open(name, mode);
|
||||
}
|
||||
|
||||
bool File::IsInvalid() const {
|
||||
return m_p->f == nullptr;
|
||||
}
|
||||
|
||||
bool File::Create(const std::filesystem::path& name) {
|
||||
EXIT_IF(m_p->f != nullptr);
|
||||
|
||||
m_file_name = name;
|
||||
|
||||
m_p->f = SysFileCreate(name);
|
||||
|
||||
if (SysFileIsError(*m_p->f)) {
|
||||
Close();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool File::Open(const std::filesystem::path& name, Mode mode) {
|
||||
EXIT_IF(m_p->f != nullptr);
|
||||
|
||||
m_file_name = name;
|
||||
|
||||
switch (mode) {
|
||||
case Mode::Read: m_p->f = SysFileOpenR(name); break;
|
||||
case Mode::Write: m_p->f = SysFileOpenW(name); break;
|
||||
case Mode::ReadWrite:
|
||||
case Mode::WriteRead: m_p->f = SysFileOpenRw(name); break;
|
||||
}
|
||||
|
||||
if (SysFileIsError(*m_p->f)) {
|
||||
Close();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool File::OpenInMem(void* buf, uint32_t buf_size) {
|
||||
EXIT_IF(m_p->f != nullptr);
|
||||
|
||||
m_p->f = SysFileOpen(static_cast<uint8_t*>(buf), buf_size);
|
||||
|
||||
if (SysFileIsError(*m_p->f)) {
|
||||
Close();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool File::OpenInMem(ByteBuffer& buf) {
|
||||
return OpenInMem(buf.GetData(), buf.Size());
|
||||
}
|
||||
|
||||
bool File::CreateInMem() {
|
||||
EXIT_IF(m_p->f != nullptr);
|
||||
|
||||
m_p->f = SysFileCreate();
|
||||
|
||||
if (SysFileIsError(*m_p->f)) {
|
||||
Close();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void File::Close() {
|
||||
if (m_p->f != nullptr) {
|
||||
SysFileClose(m_p->f);
|
||||
m_p->f = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t File::Size() const {
|
||||
EXIT_IF(m_p->f == nullptr);
|
||||
|
||||
if (m_p->f == nullptr) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return SysFileSize(*m_p->f);
|
||||
}
|
||||
|
||||
uint64_t File::Remaining() const {
|
||||
EXIT_IF(m_p->f == nullptr);
|
||||
|
||||
return Size() - Tell();
|
||||
}
|
||||
|
||||
uint64_t File::Size(const std::filesystem::path& name) {
|
||||
return SysFileSize(name);
|
||||
}
|
||||
|
||||
bool File::Seek(uint64_t offset) {
|
||||
EXIT_IF(m_p->f == nullptr);
|
||||
|
||||
return SysFileSeek(*m_p->f, offset);
|
||||
}
|
||||
|
||||
bool File::Truncate(uint64_t size) {
|
||||
EXIT_IF(m_p->f == nullptr);
|
||||
|
||||
return SysFileTruncate(*m_p->f, size);
|
||||
}
|
||||
|
||||
bool File::Unlink() {
|
||||
EXIT_IF(m_p->f == nullptr);
|
||||
|
||||
return SysFileUnlink(*m_p->f, m_file_name);
|
||||
}
|
||||
|
||||
uint64_t File::Tell() const {
|
||||
EXIT_IF(m_p->f == nullptr);
|
||||
|
||||
return SysFileTell(*m_p->f);
|
||||
}
|
||||
|
||||
void File::Read(void* data, uint32_t size, uint32_t* bytes_read) {
|
||||
EXIT_IF(m_p->f == nullptr);
|
||||
|
||||
if (m_p->f != nullptr) {
|
||||
SysFileRead(data, size, *m_p->f, bytes_read);
|
||||
}
|
||||
}
|
||||
|
||||
void File::Write(const void* data, uint32_t size, uint32_t* bytes_written) {
|
||||
EXIT_IF(m_p->f == nullptr);
|
||||
|
||||
SysFileWrite(data, size, *m_p->f, bytes_written);
|
||||
}
|
||||
|
||||
static std::string FileVformat(const char* format, va_list args) {
|
||||
va_list copy {};
|
||||
va_copy(copy, args);
|
||||
const int size = std::vsnprintf(nullptr, 0, format, copy);
|
||||
va_end(copy);
|
||||
|
||||
if (size <= 0) {
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<char> buffer(static_cast<size_t>(size) + 1u);
|
||||
va_list copy2 {};
|
||||
va_copy(copy2, args);
|
||||
std::vsnprintf(buffer.data(), buffer.size(), format, copy2);
|
||||
va_end(copy2);
|
||||
|
||||
return {buffer.data(), static_cast<size_t>(size)};
|
||||
}
|
||||
|
||||
void File::Printf(const char* format, ...) {
|
||||
va_list args {};
|
||||
va_start(args, format);
|
||||
|
||||
auto s = FileVformat(format, args);
|
||||
|
||||
va_end(args);
|
||||
|
||||
s = Common::ReplaceStr(s, "\n", "\r\n");
|
||||
Write(s.data(), static_cast<uint32_t>(s.size()));
|
||||
}
|
||||
|
||||
bool File::IsDirectoryExisting(const std::filesystem::path& path) {
|
||||
auto path_str = PathToGenericString(path);
|
||||
return SysFileIsDirectoryExisting(
|
||||
Common::EndsWith(path_str, "/") ? Common::RemoveLast(path_str, 1) : path_str);
|
||||
}
|
||||
|
||||
bool File::IsFileExisting(const std::filesystem::path& name) {
|
||||
return SysFileIsFileExisting(name);
|
||||
}
|
||||
|
||||
bool File::CreateDirectory(
|
||||
const std::filesystem::path& path) // @suppress("Member declaration not found")
|
||||
{
|
||||
auto path_str = PathToGenericString(path);
|
||||
return SysFileCreateDirectory(Common::EndsWith(path_str, "/") ? Common::RemoveLast(path_str, 1)
|
||||
: path_str);
|
||||
}
|
||||
|
||||
bool File::DeleteDirectory(const std::filesystem::path& path) {
|
||||
auto path_str = PathToGenericString(path);
|
||||
return SysFileDeleteDirectory(Common::EndsWith(path_str, "/") ? Common::RemoveLast(path_str, 1)
|
||||
: path_str);
|
||||
}
|
||||
|
||||
bool File::CreateDirectories(const std::filesystem::path& path) {
|
||||
std::string real_path = Common::ReplaceChar(PathToGenericString(path), '\\', '/');
|
||||
|
||||
std::vector<std::string> list = Common::Split(real_path, "/");
|
||||
|
||||
std::string p;
|
||||
|
||||
for (uint32_t si = 0; si < list.size(); si++) {
|
||||
const std::string& s = list[si];
|
||||
|
||||
if (si != 0 || Common::StartsWith(real_path, "/")) {
|
||||
p += "/";
|
||||
}
|
||||
|
||||
p += s;
|
||||
|
||||
if (IsDirectoryExisting(p)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!CreateDirectory(p)) // @suppress("Invalid arguments")
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool File::DeleteDirectories(const std::filesystem::path& path) {
|
||||
std::string real_path = Common::ReplaceChar(PathToGenericString(path), '\\', '/');
|
||||
|
||||
std::vector<std::string> list = Common::Split(real_path, "/");
|
||||
|
||||
std::string p;
|
||||
std::vector<std::string> list2;
|
||||
|
||||
for (uint32_t si = 0; si < list.size(); si++) {
|
||||
const std::string& s = list[si];
|
||||
|
||||
if (si != 0 || Common::StartsWith(real_path, "/")) {
|
||||
p += "/";
|
||||
}
|
||||
|
||||
p += s;
|
||||
|
||||
list2.push_back(p);
|
||||
}
|
||||
|
||||
uint32_t num = list2.size();
|
||||
|
||||
for (uint32_t si = num - 1; si < num; si--) {
|
||||
if (!DeleteDirectory(list2[si])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool File::DeleteFile(
|
||||
const std::filesystem::path& name) // @suppress("Member declaration not found")
|
||||
{
|
||||
return SysFileDeleteFile(name);
|
||||
}
|
||||
|
||||
bool File::Flush() {
|
||||
EXIT_IF(m_p->f == nullptr);
|
||||
|
||||
return SysFileFlush(*m_p->f);
|
||||
}
|
||||
|
||||
ByteBuffer File::ReadWholeBuffer() {
|
||||
EXIT_IF(IsInvalid());
|
||||
EXIT_IF(Tell() != 0);
|
||||
|
||||
uint64_t s = Size();
|
||||
|
||||
EXIT_IF((s >> 32u) != 0);
|
||||
|
||||
ByteBuffer buf(s);
|
||||
|
||||
Read(buf.GetData(), s);
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
ByteBuffer File::Read(uint32_t size) {
|
||||
ByteBuffer buf(size);
|
||||
uint32_t b = 0;
|
||||
Read(buf.GetData(), size, &b);
|
||||
buf.RemoveAt(b, size - b);
|
||||
return buf;
|
||||
}
|
||||
|
||||
void File::Write(const ByteBuffer& buf, uint32_t* bytes_written) {
|
||||
Write(buf.GetDataConst(), buf.Size(), bytes_written);
|
||||
}
|
||||
|
||||
DateTime File::GetLastAccessTimeUTC(const std::filesystem::path& name) {
|
||||
SysTimeStruct t {};
|
||||
SysFileToSystemTimeUtc(SysFileGetLastAccessTimeUtc(name), t);
|
||||
|
||||
if (t.is_invalid) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return DateTime(Date(t.Year, t.Month, t.Day), Time(t.Hour, t.Minute, t.Second, t.Milliseconds));
|
||||
}
|
||||
|
||||
DateTime File::GetLastWriteTimeUTC(const std::filesystem::path& name) {
|
||||
SysTimeStruct t {};
|
||||
SysFileToSystemTimeUtc(SysFileGetLastWriteTimeUtc(name), t);
|
||||
|
||||
if (t.is_invalid) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return DateTime(Date(t.Year, t.Month, t.Day), Time(t.Hour, t.Minute, t.Second, t.Milliseconds));
|
||||
}
|
||||
|
||||
void File::GetLastAccessAndWriteTimeUTC(const std::filesystem::path& name, DateTime* access,
|
||||
DateTime* write) {
|
||||
EXIT_IF(access == nullptr);
|
||||
EXIT_IF(write == nullptr);
|
||||
|
||||
SysTimeStruct at {};
|
||||
SysTimeStruct wt {};
|
||||
SysFileTimeStruct af {};
|
||||
SysFileTimeStruct wf {};
|
||||
|
||||
SysFileGetLastAccessAndWriteTimeUtc(name, af, wf);
|
||||
|
||||
SysFileToSystemTimeUtc(af, at);
|
||||
SysFileToSystemTimeUtc(wf, wt);
|
||||
|
||||
if (at.is_invalid || wt.is_invalid) {
|
||||
*access = DateTime();
|
||||
*write = DateTime();
|
||||
} else {
|
||||
*access = DateTime(Date(at.Year, at.Month, at.Day),
|
||||
Time(at.Hour, at.Minute, at.Second, at.Milliseconds));
|
||||
*write = DateTime(Date(wt.Year, wt.Month, wt.Day),
|
||||
Time(wt.Hour, wt.Minute, wt.Second, wt.Milliseconds));
|
||||
}
|
||||
}
|
||||
|
||||
void File::GetLastAccessAndWriteTimeUTC(DateTime* access, DateTime* write) {
|
||||
EXIT_IF(access == nullptr);
|
||||
EXIT_IF(write == nullptr);
|
||||
|
||||
EXIT_IF(m_p->f == nullptr);
|
||||
|
||||
if (m_p->f == nullptr) {
|
||||
*access = DateTime();
|
||||
*write = DateTime();
|
||||
return;
|
||||
}
|
||||
|
||||
SysTimeStruct at {};
|
||||
SysTimeStruct wt {};
|
||||
SysFileTimeStruct af {};
|
||||
SysFileTimeStruct wf {};
|
||||
|
||||
SysFileGetLastAccessAndWriteTimeUtc(*m_p->f, af, wf);
|
||||
|
||||
SysFileToSystemTimeUtc(af, at);
|
||||
SysFileToSystemTimeUtc(wf, wt);
|
||||
|
||||
if (at.is_invalid || wt.is_invalid) {
|
||||
*access = DateTime();
|
||||
*write = DateTime();
|
||||
} else {
|
||||
*access = DateTime(Date(at.Year, at.Month, at.Day),
|
||||
Time(at.Hour, at.Minute, at.Second, at.Milliseconds));
|
||||
*write = DateTime(Date(wt.Year, wt.Month, wt.Day),
|
||||
Time(wt.Hour, wt.Minute, wt.Second, wt.Milliseconds));
|
||||
}
|
||||
}
|
||||
|
||||
bool File::SetLastAccessTimeUTC(const std::filesystem::path& name, const DateTime& dt) {
|
||||
auto f = DateTimeToFileTimeUtc(dt);
|
||||
return SysFileSetLastAccessTimeUtc(name, f);
|
||||
}
|
||||
|
||||
bool File::SetLastWriteTimeUTC(const std::filesystem::path& name, const DateTime& dt) {
|
||||
auto f = DateTimeToFileTimeUtc(dt);
|
||||
return SysFileSetLastWriteTimeUtc(name, f);
|
||||
}
|
||||
|
||||
bool File::SetLastAccessAndWriteTimeUTC(const std::filesystem::path& name, const DateTime& access,
|
||||
const DateTime& write) {
|
||||
auto af = DateTimeToFileTimeUtc(access);
|
||||
auto wf = DateTimeToFileTimeUtc(write);
|
||||
return SysFileSetLastAccessAndWriteTimeUtc(name, af, wf);
|
||||
}
|
||||
|
||||
std::vector<File::FindInfo> File::FindFiles(const std::filesystem::path& path) {
|
||||
std::vector<sys_file_find_t> files;
|
||||
|
||||
SysFileFindFiles(path, files);
|
||||
|
||||
auto path_str = PathToGenericString(path);
|
||||
uint32_t len = static_cast<uint32_t>(path_str.size());
|
||||
|
||||
if (!Common::EndsWith(path_str, "/") && !Common::EndsWith(path_str, "\\")) {
|
||||
len++;
|
||||
}
|
||||
|
||||
std::vector<File::FindInfo> ret;
|
||||
ret.reserve(files.size());
|
||||
|
||||
for (const auto& f: files) {
|
||||
File::FindInfo r {};
|
||||
|
||||
r.path_with_name = f.path_with_name;
|
||||
r.rel_path_with_name = Common::Mid(PathToGenericString(f.path_with_name), len);
|
||||
r.size = f.size;
|
||||
|
||||
SysTimeStruct at {};
|
||||
SysTimeStruct wt {};
|
||||
|
||||
SysFileToSystemTimeUtc(f.last_access_time, at);
|
||||
SysFileToSystemTimeUtc(f.last_write_time, wt);
|
||||
|
||||
if (!at.is_invalid && !wt.is_invalid) {
|
||||
r.last_access_time = DateTime(Date(at.Year, at.Month, at.Day),
|
||||
Time(at.Hour, at.Minute, at.Second, at.Milliseconds));
|
||||
r.last_write_time = DateTime(Date(wt.Year, wt.Month, wt.Day),
|
||||
Time(wt.Hour, wt.Minute, wt.Second, wt.Milliseconds));
|
||||
}
|
||||
|
||||
// printf("%s, %s, %" PRIu64", %s, %s\n", r.path_with_name.c_str(),
|
||||
// r.rel_path_with_name.c_str(), r.size, r.last_access_time.ToString().c_str(),
|
||||
// r.last_write_time.ToString().c_str());
|
||||
|
||||
ret.push_back(std::move(r));
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::vector<File::DirEntry> File::GetDirEntries(const std::filesystem::path& path) {
|
||||
std::vector<sys_dir_entry_t> files;
|
||||
|
||||
SysFileGetDents(path, files);
|
||||
|
||||
std::vector<File::DirEntry> ret;
|
||||
ret.reserve(files.size());
|
||||
|
||||
for (const auto& f: files) {
|
||||
File::DirEntry r {};
|
||||
|
||||
r.name = f.name;
|
||||
r.is_file = f.is_file;
|
||||
|
||||
ret.push_back(std::move(r));
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool File::CopyFile(const std::filesystem::path& src,
|
||||
const std::filesystem::path& dst) // @suppress("Member declaration not found")
|
||||
{
|
||||
return SysFileCopyFile(src, dst);
|
||||
}
|
||||
|
||||
bool File::MoveFile(const std::filesystem::path& src,
|
||||
const std::filesystem::path& dst) // @suppress("Member declaration not found")
|
||||
{
|
||||
if (IsFileExisting(dst)) {
|
||||
DeleteFile(dst); // @suppress("Invalid arguments")
|
||||
}
|
||||
|
||||
return SysFileMoveFile(src, dst);
|
||||
}
|
||||
|
||||
void File::RemoveReadonly(const std::filesystem::path& name) {
|
||||
SysFileRemoveReadonly(name);
|
||||
}
|
||||
|
||||
} // namespace Common
|
||||
@@ -0,0 +1,124 @@
|
||||
#ifndef KYTY_COMMON_FILE_H_
|
||||
#define KYTY_COMMON_FILE_H_
|
||||
|
||||
#include "common/byteBuffer.h"
|
||||
#include "common/common.h"
|
||||
#include "common/dateTime.h"
|
||||
|
||||
#include <filesystem>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
#ifdef CreateDirectory
|
||||
#undef CreateDirectory
|
||||
#endif
|
||||
#ifdef DeleteFile
|
||||
#undef DeleteFile
|
||||
#endif
|
||||
#ifdef CopyFile
|
||||
#undef CopyFile
|
||||
#endif
|
||||
#ifdef MoveFile
|
||||
#undef MoveFile
|
||||
#endif
|
||||
#endif
|
||||
|
||||
namespace Common {
|
||||
|
||||
// SUBSYSTEM_DEFINE(File);
|
||||
|
||||
class File {
|
||||
public:
|
||||
enum class Mode { Read, Write, ReadWrite, WriteRead };
|
||||
|
||||
struct FindInfo {
|
||||
std::filesystem::path path_with_name;
|
||||
std::filesystem::path rel_path_with_name;
|
||||
DateTime last_access_time;
|
||||
DateTime last_write_time;
|
||||
uint64_t size;
|
||||
};
|
||||
|
||||
struct DirEntry {
|
||||
std::string name;
|
||||
bool is_file;
|
||||
};
|
||||
|
||||
File();
|
||||
explicit File(const std::filesystem::path& name);
|
||||
File(const std::filesystem::path& name, Mode mode);
|
||||
virtual ~File();
|
||||
|
||||
bool Create(const std::filesystem::path& name);
|
||||
bool Open(const std::filesystem::path& name, Mode mode);
|
||||
bool OpenInMem(void* buf, uint32_t buf_size);
|
||||
bool OpenInMem(ByteBuffer& buf); // NOLINT(google-runtime-references)
|
||||
bool CreateInMem();
|
||||
|
||||
void Close();
|
||||
|
||||
bool Flush();
|
||||
|
||||
[[nodiscard]] uint64_t Size() const;
|
||||
[[nodiscard]] uint64_t Remaining() const;
|
||||
|
||||
bool Seek(uint64_t offset);
|
||||
[[nodiscard]] uint64_t Tell() const;
|
||||
|
||||
bool Truncate(uint64_t size);
|
||||
bool Unlink();
|
||||
|
||||
[[nodiscard]] bool IsInvalid() const;
|
||||
|
||||
[[nodiscard]] bool IsEOF() const { return Tell() >= Size(); }
|
||||
|
||||
void GetLastAccessAndWriteTimeUTC(DateTime* access, DateTime* write);
|
||||
|
||||
void Read(void* data, uint32_t size, uint32_t* bytes_read = nullptr);
|
||||
ByteBuffer Read(uint32_t size);
|
||||
void Write(const void* data, uint32_t size, uint32_t* bytes_written = nullptr);
|
||||
void Write(const ByteBuffer& buf, uint32_t* bytes_written = nullptr);
|
||||
|
||||
void Printf(const char* format, ...) KYTY_FORMAT_PRINTF(2, 3);
|
||||
|
||||
ByteBuffer ReadWholeBuffer();
|
||||
|
||||
static uint64_t Size(const std::filesystem::path& name);
|
||||
|
||||
static bool IsDirectoryExisting(const std::filesystem::path& path);
|
||||
static bool IsFileExisting(const std::filesystem::path& name);
|
||||
static bool CreateDirectory(const std::filesystem::path& path);
|
||||
static bool CreateDirectories(const std::filesystem::path& path);
|
||||
static bool DeleteDirectory(const std::filesystem::path& path);
|
||||
static bool DeleteDirectories(const std::filesystem::path& path);
|
||||
static bool DeleteFile(const std::filesystem::path& name);
|
||||
|
||||
static DateTime GetLastAccessTimeUTC(const std::filesystem::path& name);
|
||||
static DateTime GetLastWriteTimeUTC(const std::filesystem::path& name);
|
||||
static void GetLastAccessAndWriteTimeUTC(const std::filesystem::path& name, DateTime* access,
|
||||
DateTime* write);
|
||||
static bool SetLastAccessTimeUTC(const std::filesystem::path& name, const DateTime& dt);
|
||||
static bool SetLastWriteTimeUTC(const std::filesystem::path& name, const DateTime& dt);
|
||||
static bool SetLastAccessAndWriteTimeUTC(const std::filesystem::path& name,
|
||||
const DateTime& access, const DateTime& write);
|
||||
|
||||
static std::vector<FindInfo> FindFiles(const std::filesystem::path& path);
|
||||
static std::vector<DirEntry> GetDirEntries(const std::filesystem::path& path);
|
||||
static bool CopyFile(const std::filesystem::path& src, const std::filesystem::path& dst);
|
||||
static bool MoveFile(const std::filesystem::path& src, const std::filesystem::path& dst);
|
||||
static void RemoveReadonly(const std::filesystem::path& name);
|
||||
|
||||
KYTY_CLASS_NO_COPY(File);
|
||||
|
||||
private:
|
||||
struct FilePrivate;
|
||||
|
||||
std::filesystem::path m_file_name;
|
||||
std::unique_ptr<FilePrivate> m_p;
|
||||
};
|
||||
|
||||
} // namespace Common
|
||||
|
||||
#endif /* KYTY_COMMON_FILE_H_ */
|
||||
@@ -0,0 +1,156 @@
|
||||
#ifndef KYTY_COMMON_HASH_H_
|
||||
#define KYTY_COMMON_HASH_H_
|
||||
|
||||
#include "common/common.h"
|
||||
|
||||
namespace Common {
|
||||
|
||||
inline uint32_t hash(const void* key, uint32_t key_len) {
|
||||
uint32_t hash = 0;
|
||||
|
||||
const auto* ptr = static_cast<const uint8_t*>(key);
|
||||
|
||||
while (key_len >= 4) {
|
||||
hash += ptr[0];
|
||||
hash += (hash << 10u);
|
||||
hash ^= (hash >> 6u);
|
||||
hash += ptr[1];
|
||||
hash += (hash << 10u);
|
||||
hash ^= (hash >> 6u);
|
||||
hash += ptr[2];
|
||||
hash += (hash << 10u);
|
||||
hash ^= (hash >> 6u);
|
||||
hash += ptr[3];
|
||||
hash += (hash << 10u);
|
||||
hash ^= (hash >> 6u);
|
||||
|
||||
key_len -= 4;
|
||||
ptr += 4;
|
||||
}
|
||||
|
||||
switch (key_len) {
|
||||
case 3:
|
||||
hash += ptr[2];
|
||||
hash += (hash << 10u);
|
||||
hash ^= (hash >> 6u);
|
||||
[[fallthrough]];
|
||||
case 2:
|
||||
hash += ptr[1];
|
||||
hash += (hash << 10u);
|
||||
hash ^= (hash >> 6u);
|
||||
[[fallthrough]];
|
||||
case 1:
|
||||
hash += ptr[0];
|
||||
hash += (hash << 10u);
|
||||
hash ^= (hash >> 6u);
|
||||
[[fallthrough]];
|
||||
default: break;
|
||||
}
|
||||
|
||||
hash += (hash << 3u);
|
||||
hash ^= (hash >> 11u);
|
||||
hash += (hash << 15u);
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
inline uint32_t hash8(uint8_t key) {
|
||||
uint32_t hash = 0;
|
||||
|
||||
uint8_t* ptr = &key;
|
||||
|
||||
hash += ptr[0];
|
||||
hash += (hash << 10u);
|
||||
hash ^= (hash >> 6u);
|
||||
|
||||
hash += (hash << 3u);
|
||||
hash ^= (hash >> 11u);
|
||||
hash += (hash << 15u);
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
inline uint32_t hash16(uint16_t key) {
|
||||
uint32_t hash = 0;
|
||||
|
||||
auto* ptr = reinterpret_cast<uint8_t*>(&key);
|
||||
|
||||
hash += ptr[0];
|
||||
hash += (hash << 10u);
|
||||
hash ^= (hash >> 6u);
|
||||
hash += ptr[1];
|
||||
hash += (hash << 10u);
|
||||
hash ^= (hash >> 6u);
|
||||
|
||||
hash += (hash << 3u);
|
||||
hash ^= (hash >> 11u);
|
||||
hash += (hash << 15u);
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
inline uint32_t hash32(uint32_t key) {
|
||||
uint32_t hash = 0;
|
||||
|
||||
auto* ptr = reinterpret_cast<uint8_t*>(&key);
|
||||
|
||||
hash += ptr[0];
|
||||
hash += (hash << 10u);
|
||||
hash ^= (hash >> 6u);
|
||||
hash += ptr[1];
|
||||
hash += (hash << 10u);
|
||||
hash ^= (hash >> 6u);
|
||||
hash += ptr[2];
|
||||
hash += (hash << 10u);
|
||||
hash ^= (hash >> 6u);
|
||||
hash += ptr[3];
|
||||
hash += (hash << 10u);
|
||||
hash ^= (hash >> 6u);
|
||||
|
||||
hash += (hash << 3u);
|
||||
hash ^= (hash >> 11u);
|
||||
hash += (hash << 15u);
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
inline uint32_t hash64(uint64_t key) {
|
||||
uint32_t hash = 0;
|
||||
|
||||
auto* ptr = reinterpret_cast<uint8_t*>(&key);
|
||||
|
||||
hash += ptr[0];
|
||||
hash += (hash << 10u);
|
||||
hash ^= (hash >> 6u);
|
||||
hash += ptr[1];
|
||||
hash += (hash << 10u);
|
||||
hash ^= (hash >> 6u);
|
||||
hash += ptr[2];
|
||||
hash += (hash << 10u);
|
||||
hash ^= (hash >> 6u);
|
||||
hash += ptr[3];
|
||||
hash += (hash << 10u);
|
||||
hash ^= (hash >> 6u);
|
||||
hash += ptr[4];
|
||||
hash += (hash << 10u);
|
||||
hash ^= (hash >> 6u);
|
||||
hash += ptr[5];
|
||||
hash += (hash << 10u);
|
||||
hash ^= (hash >> 6u);
|
||||
hash += ptr[6];
|
||||
hash += (hash << 10u);
|
||||
hash ^= (hash >> 6u);
|
||||
hash += ptr[7];
|
||||
hash += (hash << 10u);
|
||||
hash ^= (hash >> 6u);
|
||||
|
||||
hash += (hash << 3u);
|
||||
hash ^= (hash >> 11u);
|
||||
hash += (hash << 15u);
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
} // namespace Common
|
||||
|
||||
#endif /* KYTY_COMMON_HASH_H_ */
|
||||
@@ -0,0 +1,151 @@
|
||||
#include "common/hostException.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
#include <windows.h> // IWYU pragma: keep
|
||||
#endif
|
||||
|
||||
// IWYU pragma: no_include <errhandlingapi.h>
|
||||
// IWYU pragma: no_include <excpt.h>
|
||||
// IWYU pragma: no_include <minwinbase.h>
|
||||
// IWYU pragma: no_include <minwindef.h>
|
||||
// IWYU pragma: no_include <wtypes.h>
|
||||
|
||||
namespace Common::HostException {
|
||||
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
|
||||
static std::atomic<Handler> g_handler {nullptr};
|
||||
static std::atomic_uint32_t g_install_state {0};
|
||||
static thread_local bool g_in_exception_filter = false;
|
||||
|
||||
static_assert(decltype(g_handler)::is_always_lock_free);
|
||||
static_assert(decltype(g_install_state)::is_always_lock_free);
|
||||
|
||||
[[noreturn]] static void FailFast(const char* reason) noexcept {
|
||||
std::fputs("HostException fail-fast: ", stderr);
|
||||
std::fputs(reason != nullptr ? reason : "unspecified", stderr);
|
||||
std::fputc('\n', stderr);
|
||||
std::fflush(stderr);
|
||||
TerminateProcess(GetCurrentProcess(), static_cast<UINT>(EXCEPTION_NONCONTINUABLE_EXCEPTION));
|
||||
std::_Exit(321);
|
||||
}
|
||||
|
||||
class FilterScope final {
|
||||
public:
|
||||
FilterScope() noexcept {
|
||||
if (g_in_exception_filter) {
|
||||
FailFast("nested exception while resolving a host fault");
|
||||
}
|
||||
g_in_exception_filter = true;
|
||||
}
|
||||
|
||||
~FilterScope() { g_in_exception_filter = false; }
|
||||
|
||||
KYTY_CLASS_NO_COPY(FilterScope);
|
||||
};
|
||||
|
||||
static LONG WINAPI ExceptionFilter(PEXCEPTION_POINTERS exception) {
|
||||
FilterScope filter_scope;
|
||||
|
||||
auto* exception_record = exception->ExceptionRecord;
|
||||
|
||||
if (exception_record->ExceptionCode == DBG_PRINTEXCEPTION_C ||
|
||||
exception_record->ExceptionCode == DBG_PRINTEXCEPTION_WIDE_C) {
|
||||
return EXCEPTION_CONTINUE_SEARCH;
|
||||
}
|
||||
|
||||
if (exception_record->ExceptionCode == 0x406D1388) {
|
||||
// Set a thread name.
|
||||
return EXCEPTION_CONTINUE_EXECUTION;
|
||||
}
|
||||
|
||||
ExceptionInfo info {};
|
||||
info.exception_address = reinterpret_cast<uint64_t>(exception_record->ExceptionAddress);
|
||||
info.native_code = exception_record->ExceptionCode;
|
||||
info.native_context = exception->ContextRecord;
|
||||
|
||||
if (exception_record->ExceptionCode == EXCEPTION_ACCESS_VIOLATION) {
|
||||
info.type = ExceptionType::AccessViolation;
|
||||
switch (exception_record->ExceptionInformation[0]) {
|
||||
case 0: info.access_violation_type = AccessViolationType::Read; break;
|
||||
case 1: info.access_violation_type = AccessViolationType::Write; break;
|
||||
case 8: info.access_violation_type = AccessViolationType::Execute; break;
|
||||
default: info.access_violation_type = AccessViolationType::Unknown; break;
|
||||
}
|
||||
info.access_violation_vaddr = exception_record->ExceptionInformation[1];
|
||||
} else if (exception_record->ExceptionCode == EXCEPTION_ILLEGAL_INSTRUCTION) {
|
||||
info.type = ExceptionType::IllegalInstruction;
|
||||
} else {
|
||||
printf("Unhandled win exception: code=0x%08" PRIx32 ", addr=0x%016" PRIx64
|
||||
", rip=0x%016" PRIx64 ", rsp=0x%016" PRIx64 ", rbp=0x%016" PRIx64 "\n",
|
||||
static_cast<uint32_t>(exception_record->ExceptionCode),
|
||||
reinterpret_cast<uint64_t>(exception_record->ExceptionAddress),
|
||||
exception->ContextRecord->Rip, exception->ContextRecord->Rsp,
|
||||
exception->ContextRecord->Rbp);
|
||||
return EXCEPTION_CONTINUE_SEARCH;
|
||||
}
|
||||
|
||||
info.rax = exception->ContextRecord->Rax;
|
||||
info.rbx = exception->ContextRecord->Rbx;
|
||||
info.rcx = exception->ContextRecord->Rcx;
|
||||
info.rdx = exception->ContextRecord->Rdx;
|
||||
info.rsi = exception->ContextRecord->Rsi;
|
||||
info.rdi = exception->ContextRecord->Rdi;
|
||||
info.rbp = exception->ContextRecord->Rbp;
|
||||
info.rsp = exception->ContextRecord->Rsp;
|
||||
info.r8 = exception->ContextRecord->R8;
|
||||
info.r9 = exception->ContextRecord->R9;
|
||||
info.r10 = exception->ContextRecord->R10;
|
||||
info.r11 = exception->ContextRecord->R11;
|
||||
info.r12 = exception->ContextRecord->R12;
|
||||
info.r13 = exception->ContextRecord->R13;
|
||||
info.r14 = exception->ContextRecord->R14;
|
||||
info.r15 = exception->ContextRecord->R15;
|
||||
|
||||
if (g_install_state.load(std::memory_order_acquire) == 0) {
|
||||
FailFast("host exception handler is not installed");
|
||||
}
|
||||
|
||||
const auto handler = g_handler.load(std::memory_order_acquire);
|
||||
if (handler == nullptr) {
|
||||
FailFast("host exception callback is null");
|
||||
}
|
||||
|
||||
return handler(info) ? EXCEPTION_CONTINUE_EXECUTION : EXCEPTION_CONTINUE_SEARCH;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
bool InstallHandler(Handler handler) {
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
if (handler == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t expected_state = 0;
|
||||
if (!g_install_state.compare_exchange_strong(expected_state, 1, std::memory_order_acq_rel)) {
|
||||
return expected_state == 2 && g_handler.load(std::memory_order_acquire) == handler;
|
||||
}
|
||||
|
||||
g_handler.store(handler, std::memory_order_release);
|
||||
|
||||
if (AddVectoredExceptionHandler(1, ExceptionFilter) == nullptr) {
|
||||
g_handler.store(nullptr, std::memory_order_release);
|
||||
g_install_state.store(0, std::memory_order_release);
|
||||
printf("AddVectoredExceptionHandler() failed\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
g_install_state.store(2, std::memory_order_release);
|
||||
return true;
|
||||
#else
|
||||
(void)handler;
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace Common::HostException
|
||||
@@ -0,0 +1,44 @@
|
||||
#ifndef KYTY_COMMON_HOST_EXCEPTION_H_
|
||||
#define KYTY_COMMON_HOST_EXCEPTION_H_
|
||||
|
||||
#include "common/common.h"
|
||||
|
||||
namespace Common::HostException {
|
||||
|
||||
enum class ExceptionType { Unknown, AccessViolation, IllegalInstruction };
|
||||
|
||||
enum class AccessViolationType { Unknown, Read, Write, Execute };
|
||||
|
||||
struct ExceptionInfo {
|
||||
ExceptionType type = ExceptionType::Unknown;
|
||||
AccessViolationType access_violation_type = AccessViolationType::Unknown;
|
||||
uint64_t access_violation_vaddr = 0;
|
||||
uint64_t exception_address = 0;
|
||||
uint64_t rax = 0;
|
||||
uint64_t rbx = 0;
|
||||
uint64_t rcx = 0;
|
||||
uint64_t rdx = 0;
|
||||
uint64_t rsi = 0;
|
||||
uint64_t rdi = 0;
|
||||
uint64_t rbp = 0;
|
||||
uint64_t rsp = 0;
|
||||
uint64_t r8 = 0;
|
||||
uint64_t r9 = 0;
|
||||
uint64_t r10 = 0;
|
||||
uint64_t r11 = 0;
|
||||
uint64_t r12 = 0;
|
||||
uint64_t r13 = 0;
|
||||
uint64_t r14 = 0;
|
||||
uint64_t r15 = 0;
|
||||
uint32_t native_code = 0;
|
||||
// Platform-specific mutable context, valid only for the duration of the handler call.
|
||||
void* native_context = nullptr;
|
||||
};
|
||||
|
||||
using Handler = bool (*)(const ExceptionInfo&);
|
||||
|
||||
bool InstallHandler(Handler handler);
|
||||
|
||||
} // namespace Common::HostException
|
||||
|
||||
#endif /* KYTY_COMMON_HOST_EXCEPTION_H_ */
|
||||
@@ -0,0 +1,189 @@
|
||||
|
||||
#include "common/logging/log.h"
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/emulatorConfig.h"
|
||||
#include "common/stringUtils.h"
|
||||
|
||||
#include <cstdio>
|
||||
#include <filesystem>
|
||||
#include <fmt/format.h>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <spdlog/formatter.h>
|
||||
#include <spdlog/logger.h>
|
||||
#include <spdlog/sinks/basic_file_sink.h>
|
||||
#include <spdlog/sinks/null_sink.h>
|
||||
#include <spdlog/sinks/stdout_sinks.h>
|
||||
#include <string_view>
|
||||
|
||||
namespace {
|
||||
|
||||
class RawFormatter final: public spdlog::formatter {
|
||||
public:
|
||||
void format(const spdlog::details::log_msg& msg, spdlog::memory_buf_t& dest) override {
|
||||
const std::string_view payload(msg.payload.data(), msg.payload.size());
|
||||
dest.append(payload.data(), payload.data() + payload.size());
|
||||
}
|
||||
|
||||
std::unique_ptr<spdlog::formatter> clone() const override {
|
||||
return std::make_unique<RawFormatter>();
|
||||
}
|
||||
};
|
||||
|
||||
std::shared_ptr<spdlog::logger> MakeLogger(std::string name, spdlog::sink_ptr sink) {
|
||||
sink->set_formatter(std::make_unique<RawFormatter>());
|
||||
|
||||
auto logger = std::make_shared<spdlog::logger>(std::move(name), std::move(sink));
|
||||
logger->set_level(spdlog::level::trace);
|
||||
return logger;
|
||||
}
|
||||
|
||||
std::shared_ptr<spdlog::logger> MakeFileLogger(std::string name,
|
||||
const std::filesystem::path& path) {
|
||||
const auto parent = path.parent_path();
|
||||
if (!parent.empty()) {
|
||||
std::filesystem::create_directories(parent);
|
||||
}
|
||||
|
||||
auto sink =
|
||||
std::make_shared<spdlog::sinks::basic_file_sink_mt>(Common::PathToString(path), true);
|
||||
return MakeLogger(std::move(name), std::move(sink));
|
||||
}
|
||||
|
||||
static bool HasStyle(fmt::text_style style) {
|
||||
return style != fmt::text_style {};
|
||||
}
|
||||
|
||||
void WriteStdout(std::string_view text, fmt::text_style style = {}) {
|
||||
if (HasStyle(style)) {
|
||||
fmt::print(stdout, style, "{}", text);
|
||||
} else {
|
||||
std::fwrite(text.data(), 1, text.size(), stdout);
|
||||
}
|
||||
std::fflush(stdout);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace Log {
|
||||
|
||||
static bool g_initialized = false;
|
||||
static Direction g_direction = Direction::Console;
|
||||
static std::filesystem::path g_output_file;
|
||||
static std::mutex g_logger_mutex;
|
||||
static std::shared_ptr<spdlog::logger> g_logger;
|
||||
|
||||
void Flush() {
|
||||
if (g_logger != nullptr) {
|
||||
g_logger->flush();
|
||||
}
|
||||
}
|
||||
|
||||
static void SetupLogger() {
|
||||
std::lock_guard lock(g_logger_mutex);
|
||||
Flush();
|
||||
g_logger.reset();
|
||||
|
||||
switch (g_direction) {
|
||||
case Direction::Silent:
|
||||
g_logger = MakeLogger("kyty", std::make_shared<spdlog::sinks::null_sink_mt>());
|
||||
break;
|
||||
case Direction::Console:
|
||||
g_logger = MakeLogger("kyty", std::make_shared<spdlog::sinks::stdout_sink_mt>());
|
||||
break;
|
||||
case Direction::File:
|
||||
if (!g_output_file.empty()) {
|
||||
g_logger = MakeFileLogger("kyty", g_output_file);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static std::shared_ptr<spdlog::logger> GetLogger() {
|
||||
std::lock_guard lock(g_logger_mutex);
|
||||
return g_logger;
|
||||
}
|
||||
|
||||
static void WriteImpl(std::string_view text, fmt::text_style style = {}) {
|
||||
if (text.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!g_initialized) {
|
||||
WriteStdout(text, style);
|
||||
return;
|
||||
}
|
||||
|
||||
if (g_direction == Direction::Silent) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (auto logger = GetLogger()) {
|
||||
if (g_direction == Direction::Console && HasStyle(style)) {
|
||||
const auto styled = fmt::format(style, "{}", text);
|
||||
logger->log(spdlog::level::info, spdlog::string_view_t(styled.data(), styled.size()));
|
||||
} else {
|
||||
logger->log(spdlog::level::info, spdlog::string_view_t(text.data(), text.size()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void WriteFatal(std::string_view text) {
|
||||
if (g_direction == Direction::Silent || !g_initialized) {
|
||||
WriteStdout(text);
|
||||
} else {
|
||||
WriteStdout(text);
|
||||
WriteImpl(text);
|
||||
}
|
||||
Flush();
|
||||
}
|
||||
|
||||
void WriteFatal(fmt::text_style style, std::string_view text) {
|
||||
if (g_direction == Direction::Silent || !g_initialized) {
|
||||
WriteStdout(text, style);
|
||||
} else {
|
||||
WriteStdout(text, style);
|
||||
WriteImpl(text, style);
|
||||
}
|
||||
Flush();
|
||||
}
|
||||
|
||||
KYTY_SUBSYSTEM_INIT(Log) {
|
||||
g_initialized = true;
|
||||
switch (Config::GetPrintfDirection()) {
|
||||
case Config::OutputDirection::Silent: g_direction = Direction::Silent; break;
|
||||
case Config::OutputDirection::Console: g_direction = Direction::Console; break;
|
||||
case Config::OutputDirection::File: g_direction = Direction::File; break;
|
||||
}
|
||||
g_output_file =
|
||||
(g_direction == Direction::File ? Config::GetPrintfOutputFile() : std::filesystem::path {});
|
||||
SetupLogger();
|
||||
}
|
||||
|
||||
KYTY_SUBSYSTEM_UNEXPECTED_SHUTDOWN(Log) {
|
||||
Flush();
|
||||
std::lock_guard lock(g_logger_mutex);
|
||||
g_logger.reset();
|
||||
}
|
||||
|
||||
KYTY_SUBSYSTEM_DESTROY(Log) {
|
||||
Flush();
|
||||
std::lock_guard lock(g_logger_mutex);
|
||||
g_logger.reset();
|
||||
}
|
||||
|
||||
Direction GetDirection() {
|
||||
EXIT_IF(!g_initialized);
|
||||
return g_direction;
|
||||
}
|
||||
|
||||
void Write(std::string_view text) {
|
||||
WriteImpl(text);
|
||||
}
|
||||
|
||||
void Write(fmt::text_style style, std::string_view text) {
|
||||
WriteImpl(text, style);
|
||||
}
|
||||
|
||||
} // namespace Log
|
||||
@@ -0,0 +1,48 @@
|
||||
#ifndef KYTY_COMMON_LOGGING_LOG_H_
|
||||
#define KYTY_COMMON_LOGGING_LOG_H_
|
||||
|
||||
#include "common/common.h"
|
||||
#include "common/subsystems.h"
|
||||
|
||||
#include <fmt/color.h>
|
||||
#include <fmt/printf.h>
|
||||
#include <string_view>
|
||||
|
||||
namespace Log {
|
||||
|
||||
KYTY_SUBSYSTEM_DEFINE(Log);
|
||||
|
||||
enum class Direction { Silent, Console, File };
|
||||
|
||||
Direction GetDirection();
|
||||
void Write(std::string_view text);
|
||||
void Write(fmt::text_style style, std::string_view text);
|
||||
void WriteFatal(std::string_view text);
|
||||
void WriteFatal(fmt::text_style style, std::string_view text);
|
||||
void Flush();
|
||||
|
||||
namespace Color {
|
||||
|
||||
inline constexpr auto Default = fmt::text_style {};
|
||||
inline constexpr auto Red = fmt::fg(fmt::terminal_color::red);
|
||||
inline constexpr auto Green = fmt::fg(fmt::terminal_color::green);
|
||||
inline constexpr auto Yellow = fmt::fg(fmt::terminal_color::yellow);
|
||||
inline constexpr auto Magenta = fmt::fg(fmt::terminal_color::magenta);
|
||||
inline constexpr auto Cyan = fmt::fg(fmt::terminal_color::cyan);
|
||||
inline constexpr auto White = fmt::fg(fmt::terminal_color::white);
|
||||
inline constexpr auto BrightRed = fmt::fg(fmt::terminal_color::bright_red);
|
||||
inline constexpr auto BrightGreen = fmt::fg(fmt::terminal_color::bright_green);
|
||||
inline constexpr auto BrightYellow = fmt::fg(fmt::terminal_color::bright_yellow);
|
||||
inline constexpr auto BrightMagenta = fmt::fg(fmt::terminal_color::bright_magenta);
|
||||
inline constexpr auto BrightWhite = fmt::fg(fmt::terminal_color::bright_white);
|
||||
|
||||
} // namespace Color
|
||||
|
||||
} // namespace Log
|
||||
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
|
||||
#define LOGF(...) ::Log::Write(::fmt::sprintf(__VA_ARGS__))
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
|
||||
#define LOGF_COLOR(style, ...) ::Log::Write((style), ::fmt::sprintf(__VA_ARGS__))
|
||||
|
||||
#endif /* KYTY_COMMON_LOGGING_LOG_H_ */
|
||||
@@ -0,0 +1,42 @@
|
||||
#ifndef KYTY_COMMON_MAGICENUM_H_
|
||||
#define KYTY_COMMON_MAGICENUM_H_
|
||||
|
||||
#include "common/common.h"
|
||||
#include "common/stringUtils.h"
|
||||
#include "magic_enum.hpp" // IWYU pragma: export
|
||||
|
||||
namespace Common {
|
||||
|
||||
template <typename E>
|
||||
inline std::string EnumName(E v) {
|
||||
auto str = magic_enum::enum_name(v);
|
||||
return std::string(str.data(), str.length());
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
inline std::string EnumName8(E v) {
|
||||
auto str = magic_enum::enum_name(v);
|
||||
return std::string(str.data(), str.length());
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
inline E EnumValue(const std::string& str, E default_value) {
|
||||
auto v = magic_enum::enum_cast<E>(str.c_str());
|
||||
if (v.has_value()) {
|
||||
return v.value();
|
||||
}
|
||||
return default_value;
|
||||
}
|
||||
|
||||
#define KYTY_ENUM_RANGE(e, mx, mn) \
|
||||
namespace magic_enum::customize { \
|
||||
template <> \
|
||||
struct enum_range<e> { \
|
||||
static constexpr int min = (mx); \
|
||||
static constexpr int max = (mn); \
|
||||
}; \
|
||||
}
|
||||
|
||||
} // namespace Common
|
||||
|
||||
#endif /* KYTY_COMMON_MAGICENUM_H_ */
|
||||
@@ -0,0 +1,33 @@
|
||||
#ifndef KYTY_COMMON_PLATFORM_SYSDBG_H_
|
||||
#define KYTY_COMMON_PLATFORM_SYSDBG_H_
|
||||
|
||||
#include "common/common.h"
|
||||
|
||||
// NOLINTNEXTLINE(readability-identifier-naming)
|
||||
struct sys_dbg_stack_info_t {
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
uintptr_t addr;
|
||||
|
||||
uintptr_t reserved_addr;
|
||||
size_t reserved_size;
|
||||
uintptr_t guard_addr;
|
||||
size_t guard_size;
|
||||
uintptr_t commited_addr;
|
||||
size_t commited_size;
|
||||
|
||||
size_t total_size;
|
||||
#elif KYTY_PLATFORM == KYTY_PLATFORM_LINUX
|
||||
uintptr_t code_addr;
|
||||
uintptr_t addr;
|
||||
uintptr_t commited_addr;
|
||||
size_t commited_size;
|
||||
size_t total_size;
|
||||
size_t code_size;
|
||||
#endif
|
||||
};
|
||||
|
||||
void SysStackWalk(void** stack, int* depth);
|
||||
void SysStackUsage(sys_dbg_stack_info_t& s); // NOLINT(google-runtime-references)
|
||||
void SysStackUsagePrint(sys_dbg_stack_info_t& stack); // NOLINT(google-runtime-references)
|
||||
|
||||
#endif /* KYTY_COMMON_PLATFORM_SYSDBG_H_ */
|
||||
@@ -0,0 +1,86 @@
|
||||
#ifndef KYTY_COMMON_PLATFORM_SYSFILEIO_H_
|
||||
#define KYTY_COMMON_PLATFORM_SYSFILEIO_H_
|
||||
|
||||
#include "common/common.h"
|
||||
#include "common/platform/sysTimer.h"
|
||||
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
// The native file representation is private to the platform implementation.
|
||||
struct sys_file_t; // NOLINT(readability-identifier-naming)
|
||||
|
||||
// NOLINTNEXTLINE(readability-identifier-naming)
|
||||
enum sys_file_cache_type_t {
|
||||
SYS_FILE_CACHE_AUTO = 0, // NOLINT(readability-identifier-naming)
|
||||
SYS_FILE_CACHE_RANDOM_ACCESS = 1, // NOLINT(readability-identifier-naming)
|
||||
SYS_FILE_CACHE_SEQUENTIAL_SCAN = 2 // NOLINT(readability-identifier-naming)
|
||||
};
|
||||
|
||||
// NOLINTNEXTLINE(readability-identifier-naming)
|
||||
struct sys_file_find_t {
|
||||
std::filesystem::path path_with_name;
|
||||
SysFileTimeStruct last_access_time;
|
||||
SysFileTimeStruct last_write_time;
|
||||
uint64_t size;
|
||||
};
|
||||
|
||||
// NOLINTNEXTLINE(readability-identifier-naming)
|
||||
struct sys_dir_entry_t {
|
||||
std::string name;
|
||||
bool is_file;
|
||||
};
|
||||
|
||||
void SysFileRead(void* data, uint32_t size, sys_file_t& f,
|
||||
uint32_t* bytes_read = nullptr); // NOLINT(google-runtime-references)
|
||||
void SysFileWrite(const void* data, uint32_t size, sys_file_t& f,
|
||||
uint32_t* bytes_written = nullptr); // NOLINT(google-runtime-references)
|
||||
sys_file_t* SysFileCreate(const std::filesystem::path& file_name);
|
||||
sys_file_t* SysFileOpenR(const std::filesystem::path& file_name,
|
||||
sys_file_cache_type_t cache_type = SYS_FILE_CACHE_AUTO);
|
||||
sys_file_t* SysFileOpenW(const std::filesystem::path& file_name,
|
||||
sys_file_cache_type_t cache_type = SYS_FILE_CACHE_AUTO);
|
||||
sys_file_t* SysFileOpen(uint8_t* buf, uint32_t buf_size);
|
||||
sys_file_t* SysFileCreate();
|
||||
sys_file_t* SysFileOpenRw(const std::filesystem::path& file_name,
|
||||
sys_file_cache_type_t cache_type = SYS_FILE_CACHE_AUTO);
|
||||
void SysFileClose(sys_file_t* f);
|
||||
uint64_t SysFileSize(sys_file_t& f); // NOLINT(google-runtime-references)
|
||||
bool SysFileSeek(sys_file_t& f, uint64_t offset); // NOLINT(google-runtime-references)
|
||||
uint64_t SysFileTell(sys_file_t& f); // NOLINT(google-runtime-references)
|
||||
bool SysFileTruncate(sys_file_t& f, uint64_t size); // NOLINT(google-runtime-references)
|
||||
bool SysFileUnlink(sys_file_t& f,
|
||||
const std::filesystem::path& name); // NOLINT(google-runtime-references)
|
||||
void SysFileWrite(uint32_t n, sys_file_t& f); // NOLINT(google-runtime-references)
|
||||
uint64_t SysFileSize(const std::filesystem::path& file_name);
|
||||
bool SysFileIsError(sys_file_t& f); // NOLINT(google-runtime-references)
|
||||
bool SysFileIsDirectoryExisting(const std::filesystem::path& path);
|
||||
bool SysFileIsFileExisting(const std::filesystem::path& name);
|
||||
bool SysFileCreateDirectory(const std::filesystem::path& path);
|
||||
bool SysFileDeleteDirectory(const std::filesystem::path& path);
|
||||
bool SysFileDeleteFile(const std::filesystem::path& name);
|
||||
bool SysFileFlush(sys_file_t& f); // NOLINT(google-runtime-references)
|
||||
SysFileTimeStruct SysFileGetLastAccessTimeUtc(const std::filesystem::path& name);
|
||||
SysFileTimeStruct SysFileGetLastWriteTimeUtc(const std::filesystem::path& name);
|
||||
// NOLINTNEXTLINE(google-runtime-references)
|
||||
void SysFileGetLastAccessAndWriteTimeUtc(const std::filesystem::path& name, SysFileTimeStruct& a,
|
||||
SysFileTimeStruct& w);
|
||||
// NOLINTNEXTLINE(google-runtime-references)
|
||||
void SysFileGetLastAccessAndWriteTimeUtc(sys_file_t& f, SysFileTimeStruct& a, SysFileTimeStruct& w);
|
||||
// NOLINTNEXTLINE(google-runtime-references)
|
||||
bool SysFileSetLastAccessTimeUtc(const std::filesystem::path& name, SysFileTimeStruct& access);
|
||||
// NOLINTNEXTLINE(google-runtime-references)
|
||||
bool SysFileSetLastWriteTimeUtc(const std::filesystem::path& name, SysFileTimeStruct& write);
|
||||
// NOLINTNEXTLINE(google-runtime-references)
|
||||
bool SysFileSetLastAccessAndWriteTimeUtc(const std::filesystem::path& name,
|
||||
SysFileTimeStruct& access, SysFileTimeStruct& write);
|
||||
// NOLINTNEXTLINE(google-runtime-references)
|
||||
void SysFileFindFiles(const std::filesystem::path& path, std::vector<sys_file_find_t>& out);
|
||||
// NOLINTNEXTLINE(google-runtime-references)
|
||||
void SysFileGetDents(const std::filesystem::path& path, std::vector<sys_dir_entry_t>& out);
|
||||
bool SysFileCopyFile(const std::filesystem::path& src, const std::filesystem::path& dst);
|
||||
bool SysFileMoveFile(const std::filesystem::path& src, const std::filesystem::path& dst);
|
||||
void SysFileRemoveReadonly(const std::filesystem::path& name);
|
||||
|
||||
#endif /* KYTY_COMMON_PLATFORM_SYSFILEIO_H_ */
|
||||
@@ -0,0 +1,123 @@
|
||||
#include "common/common.h"
|
||||
|
||||
#if KYTY_PLATFORM != KYTY_PLATFORM_LINUX
|
||||
// #error "KYTY_PLATFORM != KYTY_PLATFORM_LINUX"
|
||||
#else
|
||||
|
||||
#include "common/platform/sysDbg.h"
|
||||
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <sys/param.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
void SysStackWalk(void** /*stack*/, int* depth) {
|
||||
*depth = 0;
|
||||
}
|
||||
|
||||
void SysStackUsagePrint(sys_dbg_stack_info_t& stack) {
|
||||
printf("stack: (0x%" PRIx64 ", %" PRIu64 ")\n", static_cast<uint64_t>(stack.commited_addr),
|
||||
static_cast<uint64_t>(stack.commited_size));
|
||||
printf("code: (0x%" PRIx64 ", %" PRIu64 ")\n", static_cast<uint64_t>(stack.code_addr),
|
||||
static_cast<uint64_t>(stack.code_size));
|
||||
}
|
||||
|
||||
void SysStackUsage(sys_dbg_stack_info_t& s) {
|
||||
pid_t pid = getpid();
|
||||
|
||||
// printf("pid = %"I64"d\n", (int64_t)pid);
|
||||
|
||||
[[maybe_unused]] int result = 0;
|
||||
|
||||
char str[1024];
|
||||
char str2[1024];
|
||||
result = sprintf(str, "/proc/%d/exe", static_cast<int>(pid));
|
||||
|
||||
ssize_t buff_len = 0;
|
||||
if ((buff_len = readlink(str, str2, 1023)) == -1) {
|
||||
return;
|
||||
}
|
||||
str2[buff_len] = '\0';
|
||||
const char* name = basename(str2);
|
||||
|
||||
result = sprintf(str, "/proc/%d/maps", static_cast<int>(pid));
|
||||
|
||||
memset(&s, 0, sizeof(sys_dbg_stack_info_t));
|
||||
|
||||
FILE* f = fopen(str, "r");
|
||||
|
||||
if (f == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
// printf("&str = %"I64"x\n", (uint64_t)&str);
|
||||
|
||||
uint64_t addr = 0;
|
||||
uint64_t endaddr = 0;
|
||||
[[maybe_unused]] uint64_t size = 0;
|
||||
uint64_t offset = 0;
|
||||
uint64_t inode = 0;
|
||||
char permissions[8] = {};
|
||||
char device[8] = {};
|
||||
char filename[MAXPATHLEN] = {};
|
||||
|
||||
auto check_addr = reinterpret_cast<uintptr_t>(&f);
|
||||
|
||||
while (true) {
|
||||
if (feof(f) != 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (fgets(str, sizeof(str), f) == nullptr) {
|
||||
break;
|
||||
}
|
||||
|
||||
filename[0] = 0;
|
||||
permissions[0] = 0;
|
||||
addr = 0;
|
||||
size = 0;
|
||||
|
||||
// printf("%s", str);
|
||||
|
||||
// NOLINTNEXTLINE(cert-err34-c)
|
||||
result = sscanf(str, "%" SCNx64 "-%" SCNx64 " %s %" SCNx64 " %s %" SCNx64 " %s", &addr,
|
||||
&endaddr, permissions, &offset, device, &inode, filename);
|
||||
|
||||
size = endaddr - addr;
|
||||
|
||||
bool read = (strchr(permissions, 'r') != nullptr);
|
||||
bool write = (strchr(permissions, 'w') != nullptr);
|
||||
bool exec = (strchr(permissions, 'x') != nullptr);
|
||||
|
||||
// printf("%016"I64"x, %"I64"d, %s, %d, %d\n", addr, size, filename, read, write);
|
||||
|
||||
if (read && write && !exec && strncmp(filename, "[stack", 6) == 0) {
|
||||
// printf("stack: %016"I64"x, %"I64"d\n", addr, size);
|
||||
|
||||
if (check_addr >= addr && check_addr < addr + size) {
|
||||
s.addr = addr;
|
||||
s.total_size = size;
|
||||
s.commited_addr = addr;
|
||||
s.commited_size = size;
|
||||
|
||||
if (s.code_addr != 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (read && !write && exec && strstr(filename, name) != nullptr) {
|
||||
s.code_addr = addr;
|
||||
s.code_size = size;
|
||||
|
||||
if (s.addr != 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result = fclose(f);
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,557 @@
|
||||
#include "common/common.h"
|
||||
|
||||
#if KYTY_PLATFORM != KYTY_PLATFORM_LINUX
|
||||
// #error "KYTY_PLATFORM != KYTY_PLATFORM_LINUX"
|
||||
#else
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/platform/sysFileIO.h"
|
||||
#include "common/platform/sysTimer.h"
|
||||
#include "common/stringUtils.h"
|
||||
|
||||
#include <cerrno>
|
||||
#include <cstdlib>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
#include <utime.h>
|
||||
|
||||
// NOLINTNEXTLINE(readability-identifier-naming)
|
||||
enum sys_file_type_t {
|
||||
SYS_FILE_ERROR, // NOLINT(readability-identifier-naming)
|
||||
SYS_FILE_MEMORY_STAT, // NOLINT(readability-identifier-naming)
|
||||
SYS_FILE_FILE, // NOLINT(readability-identifier-naming)
|
||||
SYS_FILE_MEMORY_DYN // NOLINT(readability-identifier-naming)
|
||||
};
|
||||
|
||||
// NOLINTNEXTLINE(readability-identifier-naming)
|
||||
struct sys_file_mem_buf_t {
|
||||
uint8_t* base;
|
||||
uint8_t* ptr;
|
||||
uint32_t size;
|
||||
};
|
||||
|
||||
// NOLINTNEXTLINE(readability-identifier-naming)
|
||||
struct sys_file_t {
|
||||
sys_file_type_t type;
|
||||
union {
|
||||
FILE* f;
|
||||
sys_file_mem_buf_t* buf;
|
||||
};
|
||||
};
|
||||
|
||||
static std::filesystem::path get_internal_name(const std::filesystem::path& name) {
|
||||
return name.is_absolute() ? name : (std::filesystem::path(".") / name);
|
||||
}
|
||||
|
||||
void SysFileRead(void* data, uint32_t size, sys_file_t& f, uint32_t* bytes_read) {
|
||||
if (f.type == SYS_FILE_FILE) {
|
||||
size_t w = fread(data, 1, size, f.f);
|
||||
if (bytes_read != nullptr) {
|
||||
*bytes_read = w;
|
||||
}
|
||||
} else if (f.type == SYS_FILE_MEMORY_STAT) {
|
||||
uint32_t s = size;
|
||||
if (f.buf->size != 0) {
|
||||
uint32_t l = f.buf->size - (f.buf->ptr - f.buf->base);
|
||||
if (s > l) {
|
||||
s = l;
|
||||
}
|
||||
}
|
||||
memcpy(data, f.buf->ptr, s);
|
||||
f.buf->ptr += s;
|
||||
if (bytes_read != nullptr) {
|
||||
*bytes_read = s;
|
||||
}
|
||||
} else if (f.type == SYS_FILE_MEMORY_DYN) {
|
||||
uint32_t s = size;
|
||||
if (f.buf->size != 0) {
|
||||
uint32_t l = f.buf->size - (f.buf->ptr - f.buf->base);
|
||||
if (s > l) {
|
||||
s = l;
|
||||
}
|
||||
} else {
|
||||
s = 0;
|
||||
}
|
||||
memcpy(data, f.buf->ptr, s);
|
||||
f.buf->ptr += s;
|
||||
if (bytes_read != nullptr) {
|
||||
*bytes_read = s;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SysFileWrite(const void* data, uint32_t size, sys_file_t& f, uint32_t* bytes_written) {
|
||||
if (f.type == SYS_FILE_FILE) {
|
||||
size_t w = fwrite(data, 1, size, f.f);
|
||||
if (bytes_written != nullptr) {
|
||||
*bytes_written = w;
|
||||
}
|
||||
} else if (f.type == SYS_FILE_MEMORY_STAT) {
|
||||
uint32_t s = size;
|
||||
if (f.buf->size != 0) {
|
||||
uint32_t l = f.buf->size - (f.buf->ptr - f.buf->base);
|
||||
if (s > l) {
|
||||
s = l;
|
||||
}
|
||||
}
|
||||
memcpy(f.buf->ptr, data, s);
|
||||
f.buf->ptr += s;
|
||||
if (bytes_written != nullptr) {
|
||||
*bytes_written = s;
|
||||
}
|
||||
} else if (f.type == SYS_FILE_MEMORY_DYN) {
|
||||
uint32_t pos = f.buf->ptr - f.buf->base;
|
||||
if (f.buf->size < pos + size) {
|
||||
f.buf->base = static_cast<uint8_t*>(std::realloc(f.buf->base, pos + size));
|
||||
f.buf->ptr = f.buf->base + pos;
|
||||
f.buf->size = pos + size;
|
||||
}
|
||||
memcpy(f.buf->ptr, data, size);
|
||||
f.buf->ptr += size;
|
||||
if (bytes_written != nullptr) {
|
||||
*bytes_written = size;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SysFileWrite(uint32_t n, sys_file_t& f) {
|
||||
SysFileWrite(&n, 4, f);
|
||||
}
|
||||
|
||||
sys_file_t* SysFileCreate(const std::filesystem::path& file_name) {
|
||||
auto* ret = new sys_file_t;
|
||||
|
||||
auto real_name = get_internal_name(file_name);
|
||||
auto real_name_str = real_name.string();
|
||||
|
||||
ret->f = fopen(real_name_str.c_str(), "w+");
|
||||
|
||||
if (ret->f == nullptr) {
|
||||
printf("can't create file: %s\n", real_name_str.c_str());
|
||||
}
|
||||
|
||||
ret->type = SYS_FILE_FILE;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
sys_file_t* SysFileOpenR(const std::filesystem::path& file_name,
|
||||
sys_file_cache_type_t /*cache_type*/) {
|
||||
auto* ret = new sys_file_t;
|
||||
|
||||
ret->type = SYS_FILE_FILE;
|
||||
|
||||
auto internal_name = get_internal_name(file_name);
|
||||
auto internal_name_str = internal_name.string();
|
||||
|
||||
FILE* f = fopen(internal_name_str.c_str(), "r");
|
||||
|
||||
if (f == nullptr) {
|
||||
ret->type = SYS_FILE_ERROR;
|
||||
}
|
||||
|
||||
ret->f = f;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
sys_file_t* SysFileOpen(uint8_t* buf, uint32_t buf_size) {
|
||||
auto* ret = new sys_file_t;
|
||||
|
||||
ret->type = SYS_FILE_MEMORY_STAT;
|
||||
ret->buf = new sys_file_mem_buf_t;
|
||||
ret->buf->base = buf;
|
||||
ret->buf->ptr = buf;
|
||||
ret->buf->size = buf_size;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
sys_file_t* SysFileCreate() {
|
||||
auto* ret = new sys_file_t;
|
||||
|
||||
ret->type = SYS_FILE_MEMORY_DYN;
|
||||
ret->buf = new sys_file_mem_buf_t;
|
||||
ret->buf->base = nullptr;
|
||||
ret->buf->ptr = nullptr;
|
||||
ret->buf->size = 0;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
sys_file_t* SysFileOpenW(const std::filesystem::path& file_name,
|
||||
sys_file_cache_type_t /*cache_type*/) {
|
||||
auto* ret = new sys_file_t;
|
||||
|
||||
auto real_name = get_internal_name(file_name);
|
||||
auto real_name_str = real_name.string();
|
||||
|
||||
FILE* f = fopen(real_name_str.c_str(), "r+");
|
||||
|
||||
if (f == nullptr) {
|
||||
ret->type = SYS_FILE_ERROR;
|
||||
} else {
|
||||
ret->type = SYS_FILE_FILE;
|
||||
}
|
||||
|
||||
ret->f = f;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
sys_file_t* SysFileOpenRw(const std::filesystem::path& file_name,
|
||||
sys_file_cache_type_t /*cache_type*/) {
|
||||
auto* ret = new sys_file_t;
|
||||
|
||||
auto real_name = get_internal_name(file_name);
|
||||
auto real_name_str = real_name.string();
|
||||
|
||||
FILE* f = fopen(real_name_str.c_str(), "r+");
|
||||
|
||||
if (f == nullptr) {
|
||||
ret->type = SYS_FILE_ERROR;
|
||||
} else {
|
||||
ret->type = SYS_FILE_FILE;
|
||||
}
|
||||
|
||||
ret->f = f;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void SysFileClose(sys_file_t* f) {
|
||||
[[maybe_unused]] int result = 0;
|
||||
|
||||
if (f->type == SYS_FILE_FILE && f->f != nullptr) {
|
||||
result = fclose(f->f);
|
||||
} else if (f->type == SYS_FILE_MEMORY_STAT) {
|
||||
delete f->buf;
|
||||
} else if (f->type == SYS_FILE_MEMORY_DYN) {
|
||||
std::free(f->buf->base);
|
||||
delete f->buf;
|
||||
}
|
||||
|
||||
// f.type = SYS_FILE_ERROR;
|
||||
delete f;
|
||||
}
|
||||
|
||||
uint64_t SysFileSize(sys_file_t& f) {
|
||||
[[maybe_unused]] int result = 0;
|
||||
|
||||
if (f.type == SYS_FILE_FILE) {
|
||||
uint32_t pos = ftell(f.f);
|
||||
result = fseek(f.f, 0, SEEK_END);
|
||||
uint32_t size = ftell(f.f);
|
||||
result = fseek(f.f, pos, SEEK_SET);
|
||||
return size;
|
||||
}
|
||||
|
||||
if (f.type == SYS_FILE_MEMORY_STAT || f.type == SYS_FILE_MEMORY_DYN) {
|
||||
return f.buf->size;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint64_t SysFileSize(const std::filesystem::path& file_name) {
|
||||
sys_file_t* f = SysFileOpenR(file_name);
|
||||
uint64_t size = SysFileSize(*f);
|
||||
SysFileClose(f);
|
||||
return size;
|
||||
}
|
||||
|
||||
bool SysFileTruncate(sys_file_t& /*f*/, uint64_t /*size*/) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SysFileUnlink(sys_file_t& /*f*/, const std::filesystem::path& name) {
|
||||
return SysFileDeleteFile(name);
|
||||
}
|
||||
|
||||
bool SysFileSeek(sys_file_t& f, uint64_t offset) {
|
||||
bool ok = true;
|
||||
if (f.type == SYS_FILE_FILE) {
|
||||
ok = (fseek(f.f, static_cast<int64_t>(offset), SEEK_SET) == 0);
|
||||
// LARGE_INTEGER s;
|
||||
// s.QuadPart = offset;
|
||||
// SetFilePointerEx(f.handle, s, 0, FILE_BEGIN);
|
||||
// printf("seek: %u\n", offset);
|
||||
} else if (f.type == SYS_FILE_MEMORY_STAT || f.type == SYS_FILE_MEMORY_DYN) {
|
||||
f.buf->ptr = f.buf->base + offset;
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
uint64_t SysFileTell(sys_file_t& f) {
|
||||
if (f.type == SYS_FILE_FILE) {
|
||||
return ftell(f.f);
|
||||
}
|
||||
|
||||
if (f.type == SYS_FILE_MEMORY_STAT || f.type == SYS_FILE_MEMORY_DYN) {
|
||||
return f.buf->ptr - f.buf->base;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool SysFileIsError(sys_file_t& f) {
|
||||
return f.type == SYS_FILE_ERROR || (f.type == SYS_FILE_FILE && f.f == nullptr);
|
||||
}
|
||||
|
||||
bool SysFileIsDirectoryExisting(const std::filesystem::path& path) {
|
||||
auto real_name = get_internal_name(path);
|
||||
auto real_name_str = real_name.string();
|
||||
|
||||
struct stat s {};
|
||||
|
||||
if (0 != stat(real_name_str.c_str(), &s)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return S_ISDIR(s.st_mode); // NOLINT
|
||||
}
|
||||
|
||||
bool SysFileIsFileExisting(const std::filesystem::path& name) {
|
||||
auto real_name = get_internal_name(name);
|
||||
auto real_name_str = real_name.string();
|
||||
|
||||
struct stat s {};
|
||||
|
||||
if (0 != stat(real_name_str.c_str(), &s)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return !S_ISDIR(s.st_mode); // NOLINT
|
||||
}
|
||||
|
||||
bool SysFileCreateDirectory(const std::filesystem::path& path) {
|
||||
auto real_name = get_internal_name(path);
|
||||
auto real_name_str = real_name.string();
|
||||
|
||||
mode_t m = S_IRWXU | S_IRWXG | S_IRWXO; // NOLINT
|
||||
|
||||
int r = mkdir(real_name_str.c_str(), m);
|
||||
|
||||
if (r != 0) {
|
||||
int e = errno;
|
||||
if (e == EEXIST) {
|
||||
printf("mkdir(%s, %" PRIx32 ") failed: The named file exists\n", real_name_str.c_str(),
|
||||
static_cast<uint32_t>(m));
|
||||
} else {
|
||||
printf("mkdir(%s, %" PRIx32 ") failed: %d\n", real_name_str.c_str(),
|
||||
static_cast<uint32_t>(m), e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SysFileDeleteDirectory(const std::filesystem::path& path) {
|
||||
auto real_name = get_internal_name(path);
|
||||
auto real_name_str = real_name.string();
|
||||
|
||||
return 0 == remove(real_name_str.c_str());
|
||||
}
|
||||
|
||||
bool SysFileDeleteFile(const std::filesystem::path& name) {
|
||||
auto real_name = get_internal_name(name);
|
||||
auto real_name_str = real_name.string();
|
||||
|
||||
return 0 == unlink(real_name_str.c_str());
|
||||
}
|
||||
|
||||
bool SysFileFlush(sys_file_t& f) {
|
||||
if (f.type == SYS_FILE_FILE && f.f != nullptr) {
|
||||
return (fflush(f.f) == 0);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
SysFileTimeStruct SysFileGetLastAccessTimeUtc(const std::filesystem::path& name) {
|
||||
SysFileTimeStruct r {};
|
||||
|
||||
auto real_name = get_internal_name(name);
|
||||
auto real_name_str = real_name.string();
|
||||
|
||||
struct stat s {};
|
||||
|
||||
if (0 != stat(real_name_str.c_str(), &s)) {
|
||||
r.is_invalid = true;
|
||||
} else {
|
||||
r.is_invalid = false;
|
||||
r.time = s.st_atime;
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
SysFileTimeStruct SysFileGetLastWriteTimeUtc(const std::filesystem::path& name) {
|
||||
SysFileTimeStruct r {};
|
||||
|
||||
auto real_name = get_internal_name(name);
|
||||
auto real_name_str = real_name.string();
|
||||
|
||||
struct stat s {};
|
||||
|
||||
if (0 != stat(real_name_str.c_str(), &s)) {
|
||||
r.is_invalid = true;
|
||||
} else {
|
||||
r.is_invalid = false;
|
||||
r.time = s.st_mtime;
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
void SysFileGetLastAccessAndWriteTimeUtc(const std::filesystem::path& name, SysFileTimeStruct& a,
|
||||
SysFileTimeStruct& w) {
|
||||
auto real_name = get_internal_name(name);
|
||||
auto real_name_str = real_name.string();
|
||||
|
||||
struct stat s {};
|
||||
|
||||
if (0 != stat(real_name_str.c_str(), &s)) {
|
||||
a.is_invalid = true;
|
||||
w.is_invalid = true;
|
||||
} else {
|
||||
a.is_invalid = false;
|
||||
w.is_invalid = false;
|
||||
a.time = s.st_atime;
|
||||
w.time = s.st_mtime;
|
||||
}
|
||||
}
|
||||
|
||||
void SysFileGetLastAccessAndWriteTimeUtc(sys_file_t& /*f*/, SysFileTimeStruct& /*a*/,
|
||||
SysFileTimeStruct& /*w*/) {
|
||||
EXIT("not implemented\n");
|
||||
}
|
||||
|
||||
bool SysFileSetLastAccessTimeUtc(const std::filesystem::path& name, SysFileTimeStruct& access) {
|
||||
if (access.is_invalid) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto real_name = get_internal_name(name);
|
||||
auto real_name_str = real_name.string();
|
||||
|
||||
struct stat s {};
|
||||
|
||||
if (0 != stat(real_name_str.c_str(), &s)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
utimbuf times {};
|
||||
times.actime = access.time;
|
||||
times.modtime = s.st_mtime;
|
||||
|
||||
return !(0 != utime(real_name_str.c_str(), ×));
|
||||
|
||||
// if (0 != stat(n.data(), &s))
|
||||
// {
|
||||
// return false;
|
||||
// }
|
||||
//
|
||||
// times.actime += times.actime - s.st_atime;
|
||||
// times.modtime += times.modtime - s.st_mtime;
|
||||
//
|
||||
// if (0 != utime(n.data(), ×))
|
||||
// {
|
||||
// return false;
|
||||
// }
|
||||
}
|
||||
|
||||
bool SysFileSetLastWriteTimeUtc(const std::filesystem::path& name, SysFileTimeStruct& write) {
|
||||
if (write.is_invalid) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto real_name = get_internal_name(name);
|
||||
auto real_name_str = real_name.string();
|
||||
|
||||
struct stat s {};
|
||||
|
||||
if (0 != stat(real_name_str.c_str(), &s)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
utimbuf times {};
|
||||
times.actime = s.st_atime;
|
||||
times.modtime = write.time;
|
||||
|
||||
return !(0 != utime(real_name_str.c_str(), ×));
|
||||
|
||||
// if (0 != stat(n.data(), &s))
|
||||
// {
|
||||
// return false;
|
||||
// }
|
||||
//
|
||||
// times.actime += times.actime - s.st_atime;
|
||||
// times.modtime += times.modtime - s.st_mtime;
|
||||
//
|
||||
// if (0 != utime(n.data(), ×))
|
||||
// {
|
||||
// return false;
|
||||
// }
|
||||
}
|
||||
|
||||
bool SysFileSetLastAccessAndWriteTimeUtc(const std::filesystem::path& name,
|
||||
SysFileTimeStruct& access, SysFileTimeStruct& write) {
|
||||
if (access.is_invalid || write.is_invalid) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto real_name = get_internal_name(name);
|
||||
auto real_name_str = real_name.string();
|
||||
|
||||
struct stat s {};
|
||||
|
||||
if (0 != stat(real_name_str.c_str(), &s)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
utimbuf times {};
|
||||
times.actime = access.time;
|
||||
times.modtime = write.time;
|
||||
|
||||
return !(0 != utime(real_name_str.c_str(), ×));
|
||||
|
||||
// if (0 != stat(n.data(), &s))
|
||||
// {
|
||||
// return false;
|
||||
// }
|
||||
//
|
||||
// times.actime += times.actime - s.st_atime;
|
||||
// times.modtime += times.modtime - s.st_mtime;
|
||||
//
|
||||
// if (0 != utime(n.data(), ×))
|
||||
// {
|
||||
// return false;
|
||||
// }
|
||||
}
|
||||
|
||||
void SysFileFindFiles(const std::filesystem::path& /*path*/,
|
||||
std::vector<sys_file_find_t>& /*out*/) {
|
||||
EXIT("not implemented\n");
|
||||
}
|
||||
|
||||
void SysFileGetDents(const std::filesystem::path& /*path*/, std::vector<sys_dir_entry_t>& /*out*/) {
|
||||
EXIT("not implemented\n");
|
||||
}
|
||||
|
||||
bool SysFileCopyFile(const std::filesystem::path& /*src*/, const std::filesystem::path& /*dst*/) {
|
||||
EXIT("not implemented\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SysFileMoveFile(const std::filesystem::path& /*src*/, const std::filesystem::path& /*dst*/) {
|
||||
EXIT("not implemented\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
void SysFileRemoveReadonly(const std::filesystem::path& /*name*/) {
|
||||
EXIT("not implemented\n");
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,451 @@
|
||||
#include "common/common.h"
|
||||
|
||||
#if KYTY_PLATFORM != KYTY_PLATFORM_LINUX
|
||||
// #error "KYTY_PLATFORM != KYTY_PLATFORM_LINUX"
|
||||
#else
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/platform/sysVirtual.h"
|
||||
#include "common/virtualMemory.h"
|
||||
|
||||
#include <map>
|
||||
#include <pthread.h>
|
||||
#include <sys/mman.h>
|
||||
|
||||
// IWYU pragma: no_include <asm/mman-common.h>
|
||||
// IWYU pragma: no_include <asm/mman.h>
|
||||
// IWYU pragma: no_include <bits/pthread_types.h>
|
||||
// IWYU pragma: no_include <linux/mman.h>
|
||||
|
||||
#if defined(MAP_FIXED_NOREPLACE) && KYTY_PLATFORM == KYTY_PLATFORM_LINUX
|
||||
#define KYTY_FIXED_NOREPLACE
|
||||
#endif
|
||||
|
||||
namespace Common {
|
||||
|
||||
static pthread_mutex_t g_virtual_mutex {};
|
||||
static std::map<uintptr_t, size_t>* g_allocs = nullptr;
|
||||
static std::map<uintptr_t, int>* g_protects = nullptr;
|
||||
|
||||
void SysVirtualInit() {
|
||||
pthread_mutexattr_t attr {};
|
||||
|
||||
pthread_mutexattr_init(&attr);
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_LINUX
|
||||
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_FAST_NP);
|
||||
#else
|
||||
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_NORMAL);
|
||||
#endif
|
||||
pthread_mutex_init(&g_virtual_mutex, &attr);
|
||||
pthread_mutexattr_destroy(&attr);
|
||||
|
||||
g_allocs = new std::map<uintptr_t, size_t>;
|
||||
g_protects = new std::map<uintptr_t, int>;
|
||||
}
|
||||
|
||||
static int get_protection_flag(VirtualMemory::Mode mode) {
|
||||
int protect = PROT_NONE;
|
||||
switch (mode) {
|
||||
case VirtualMemory::Mode::Read: protect = PROT_READ; break;
|
||||
case VirtualMemory::Mode::Write:
|
||||
case VirtualMemory::Mode::ReadWrite: protect = PROT_READ | PROT_WRITE; break; // NOLINT
|
||||
case VirtualMemory::Mode::Execute: protect = PROT_EXEC; break;
|
||||
case VirtualMemory::Mode::ExecuteRead: protect = PROT_EXEC | PROT_READ; break; // NOLINT
|
||||
case VirtualMemory::Mode::ExecuteWrite:
|
||||
case VirtualMemory::Mode::ExecuteReadWrite:
|
||||
protect = PROT_EXEC | PROT_WRITE | PROT_READ;
|
||||
break; // NOLINT
|
||||
case VirtualMemory::Mode::NoAccess:
|
||||
default: protect = PROT_NONE; break;
|
||||
}
|
||||
return protect;
|
||||
}
|
||||
|
||||
static VirtualMemory::Mode get_protection_flag(int mode) {
|
||||
switch (mode) {
|
||||
case PROT_NONE: return VirtualMemory::Mode::NoAccess;
|
||||
case PROT_READ: return VirtualMemory::Mode::Read;
|
||||
case PROT_WRITE: return VirtualMemory::Mode::Write;
|
||||
case PROT_READ | PROT_WRITE: return VirtualMemory::Mode::ReadWrite; // NOLINT
|
||||
case PROT_EXEC: return VirtualMemory::Mode::Execute;
|
||||
case PROT_EXEC | PROT_WRITE: return VirtualMemory::Mode::ExecuteWrite; // NOLINT
|
||||
case PROT_EXEC | PROT_READ: return VirtualMemory::Mode::ExecuteRead; // NOLINT
|
||||
case PROT_EXEC | PROT_WRITE | PROT_READ:
|
||||
return VirtualMemory::Mode::ExecuteReadWrite; // NOLINT
|
||||
default: return VirtualMemory::Mode::NoAccess;
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t SysVirtualAlloc(uint64_t address, uint64_t size, VirtualMemory::Mode mode) {
|
||||
EXIT_IF(g_allocs == nullptr);
|
||||
|
||||
auto addr = static_cast<uintptr_t>(address);
|
||||
|
||||
int protect = get_protection_flag(mode);
|
||||
|
||||
void* ptr =
|
||||
mmap(reinterpret_cast<void*>(addr), size, protect, MAP_PRIVATE | MAP_ANON, -1, 0); // NOLINT
|
||||
|
||||
auto ret_addr = reinterpret_cast<uintptr_t>(ptr);
|
||||
|
||||
if (ptr != MAP_FAILED) {
|
||||
pthread_mutex_lock(&g_virtual_mutex);
|
||||
(*g_allocs)[ret_addr] = size;
|
||||
uintptr_t page_start = ret_addr >> 12u;
|
||||
uintptr_t page_end = (ret_addr + size - 1) >> 12u;
|
||||
for (uintptr_t page = page_start; page <= page_end; page++) {
|
||||
(*g_protects)[page] = protect;
|
||||
}
|
||||
pthread_mutex_unlock(&g_virtual_mutex);
|
||||
}
|
||||
|
||||
return ret_addr;
|
||||
}
|
||||
|
||||
static uintptr_t align_up(uintptr_t addr, uint64_t alignment) {
|
||||
return (addr + alignment - 1) & ~(alignment - 1);
|
||||
}
|
||||
|
||||
uint64_t SysVirtualAllocAligned(uint64_t address, uint64_t size, VirtualMemory::Mode mode,
|
||||
uint64_t alignment) {
|
||||
if (alignment == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
EXIT_IF(g_allocs == nullptr);
|
||||
|
||||
auto addr = static_cast<uintptr_t>(address);
|
||||
int protect = get_protection_flag(mode);
|
||||
|
||||
void* ptr =
|
||||
mmap(reinterpret_cast<void*>(addr), size, protect, MAP_PRIVATE | MAP_ANON, -1, 0); // NOLINT
|
||||
|
||||
auto ret_addr = reinterpret_cast<uintptr_t>(ptr);
|
||||
|
||||
if (ptr != MAP_FAILED && ((ret_addr & (alignment - 1)) != 0)) {
|
||||
munmap(ptr, size);
|
||||
|
||||
ptr = mmap(reinterpret_cast<void*>(addr), size + alignment, protect,
|
||||
MAP_PRIVATE | MAP_ANON | MAP_NORESERVE, -1, 0); // NOLINT
|
||||
ret_addr = reinterpret_cast<uintptr_t>(ptr);
|
||||
if (ptr != MAP_FAILED) {
|
||||
munmap(ptr, size + alignment);
|
||||
auto aligned_addr = align_up(ret_addr, alignment);
|
||||
#ifdef KYTY_FIXED_NOREPLACE
|
||||
// NOLINTNEXTLINE
|
||||
ptr = mmap(reinterpret_cast<void*>(aligned_addr), size, protect,
|
||||
MAP_FIXED_NOREPLACE | MAP_PRIVATE | MAP_ANON, -1, 0);
|
||||
#else
|
||||
// NOLINTNEXTLINE
|
||||
ptr = mmap(reinterpret_cast<void*>(aligned_addr), size, protect,
|
||||
MAP_FIXED | MAP_PRIVATE | MAP_ANON, -1, 0);
|
||||
#endif
|
||||
ret_addr = reinterpret_cast<uintptr_t>(ptr);
|
||||
if (ptr != MAP_FAILED && ((ret_addr & (alignment - 1)) != 0)) {
|
||||
munmap(ptr, size);
|
||||
ret_addr = 0;
|
||||
ptr = MAP_FAILED;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ptr == MAP_FAILED) {
|
||||
return SysVirtualAllocAligned(address, size, mode, alignment << 1u);
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&g_virtual_mutex);
|
||||
(*g_allocs)[ret_addr] = size;
|
||||
uintptr_t page_start = ret_addr >> 12u;
|
||||
uintptr_t page_end = (ret_addr + size - 1) >> 12u;
|
||||
for (uintptr_t page = page_start; page <= page_end; page++) {
|
||||
(*g_protects)[page] = protect;
|
||||
}
|
||||
pthread_mutex_unlock(&g_virtual_mutex);
|
||||
|
||||
return ret_addr;
|
||||
}
|
||||
|
||||
static bool is_mapped(void* ptr, size_t length) {
|
||||
FILE* file = fopen("/proc/self/maps", "r");
|
||||
char line[1024];
|
||||
bool ret = false;
|
||||
auto addr = reinterpret_cast<uintptr_t>(ptr);
|
||||
|
||||
while (feof(file) == 0) {
|
||||
if (fgets(line, 1024, file) == nullptr) {
|
||||
break;
|
||||
}
|
||||
uint64_t start = 0;
|
||||
uint64_t end = 0;
|
||||
// NOLINTNEXTLINE(cert-err34-c)
|
||||
if (sscanf(line, "%" SCNx64 "-%" SCNx64, &start, &end) != 2) {
|
||||
continue;
|
||||
}
|
||||
if (addr >= start && addr + length <= end) {
|
||||
ret = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
fclose(file);
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool SysVirtualAllocFixed(uint64_t address, uint64_t size, VirtualMemory::Mode mode) {
|
||||
EXIT_IF(g_allocs == nullptr);
|
||||
|
||||
auto addr = static_cast<uintptr_t>(address);
|
||||
int protect = get_protection_flag(mode);
|
||||
|
||||
#ifdef KYTY_FIXED_NOREPLACE
|
||||
// NOLINTNEXTLINE
|
||||
void* ptr = mmap(reinterpret_cast<void*>(addr), size, protect,
|
||||
MAP_FIXED_NOREPLACE | MAP_PRIVATE | MAP_ANON, -1, 0);
|
||||
#else
|
||||
// NOLINTNEXTLINE
|
||||
void* ptr = (is_mapped(reinterpret_cast<void*>(addr), size)
|
||||
? MAP_FAILED
|
||||
: mmap(reinterpret_cast<void*>(addr), size, protect,
|
||||
MAP_FIXED | MAP_PRIVATE | MAP_ANON, -1, 0));
|
||||
#endif
|
||||
|
||||
auto ret_addr = reinterpret_cast<uintptr_t>(ptr);
|
||||
|
||||
if (ptr != MAP_FAILED && ret_addr != addr) {
|
||||
munmap(ptr, size);
|
||||
ret_addr = 0;
|
||||
ptr = MAP_FAILED;
|
||||
}
|
||||
|
||||
if (ptr != MAP_FAILED) {
|
||||
pthread_mutex_lock(&g_virtual_mutex);
|
||||
(*g_allocs)[ret_addr] = size;
|
||||
uintptr_t page_start = ret_addr >> 12u;
|
||||
uintptr_t page_end = (ret_addr + size - 1) >> 12u;
|
||||
for (uintptr_t page = page_start; page <= page_end; page++) {
|
||||
(*g_protects)[page] = protect;
|
||||
}
|
||||
pthread_mutex_unlock(&g_virtual_mutex);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SysVirtualCommit(uint64_t address, uint64_t size, VirtualMemory::Mode mode) {
|
||||
return SysVirtualProtect(address, size, mode);
|
||||
}
|
||||
|
||||
uint64_t SysVirtualReserve(uint64_t address, uint64_t size) {
|
||||
return SysVirtualReserveAligned(address, size, 1);
|
||||
}
|
||||
|
||||
uint64_t SysVirtualReserveAligned(uint64_t address, uint64_t size, uint64_t alignment) {
|
||||
if (alignment == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
EXIT_IF(g_allocs == nullptr);
|
||||
|
||||
auto addr = static_cast<uintptr_t>(address);
|
||||
|
||||
void* ptr = mmap(reinterpret_cast<void*>(addr), size, PROT_NONE,
|
||||
MAP_PRIVATE | MAP_ANON | MAP_NORESERVE, -1, 0); // NOLINT
|
||||
|
||||
auto ret_addr = reinterpret_cast<uintptr_t>(ptr);
|
||||
|
||||
if (ptr != MAP_FAILED && ((ret_addr & (alignment - 1)) != 0)) {
|
||||
munmap(ptr, size);
|
||||
|
||||
ptr = mmap(reinterpret_cast<void*>(addr), size + alignment, PROT_NONE,
|
||||
MAP_PRIVATE | MAP_ANON | MAP_NORESERVE, -1, 0); // NOLINT
|
||||
ret_addr = reinterpret_cast<uintptr_t>(ptr);
|
||||
if (ptr != MAP_FAILED) {
|
||||
munmap(ptr, size + alignment);
|
||||
auto aligned_addr = align_up(ret_addr, alignment);
|
||||
#ifdef KYTY_FIXED_NOREPLACE
|
||||
// NOLINTNEXTLINE
|
||||
ptr = mmap(reinterpret_cast<void*>(aligned_addr), size, PROT_NONE,
|
||||
MAP_FIXED_NOREPLACE | MAP_PRIVATE | MAP_ANON | MAP_NORESERVE, -1, 0);
|
||||
#else
|
||||
// NOLINTNEXTLINE
|
||||
ptr = mmap(reinterpret_cast<void*>(aligned_addr), size, PROT_NONE,
|
||||
MAP_FIXED | MAP_PRIVATE | MAP_ANON | MAP_NORESERVE, -1, 0);
|
||||
#endif
|
||||
ret_addr = reinterpret_cast<uintptr_t>(ptr);
|
||||
if (ptr != MAP_FAILED && ((ret_addr & (alignment - 1)) != 0)) {
|
||||
munmap(ptr, size);
|
||||
ret_addr = 0;
|
||||
ptr = MAP_FAILED;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ptr == MAP_FAILED) {
|
||||
return SysVirtualReserveAligned(address, size, alignment << 1u);
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&g_virtual_mutex);
|
||||
(*g_allocs)[ret_addr] = size;
|
||||
uintptr_t page_start = ret_addr >> 12u;
|
||||
uintptr_t page_end = (ret_addr + size - 1) >> 12u;
|
||||
for (uintptr_t page = page_start; page <= page_end; page++) {
|
||||
(*g_protects)[page] = PROT_NONE;
|
||||
}
|
||||
pthread_mutex_unlock(&g_virtual_mutex);
|
||||
|
||||
return ret_addr;
|
||||
}
|
||||
|
||||
bool SysVirtualReserveFixed(uint64_t address, uint64_t size) {
|
||||
EXIT_IF(g_allocs == nullptr);
|
||||
|
||||
auto addr = static_cast<uintptr_t>(address);
|
||||
|
||||
#ifdef KYTY_FIXED_NOREPLACE
|
||||
// NOLINTNEXTLINE
|
||||
void* ptr = mmap(reinterpret_cast<void*>(addr), size, PROT_NONE,
|
||||
MAP_FIXED_NOREPLACE | MAP_PRIVATE | MAP_ANON | MAP_NORESERVE, -1, 0);
|
||||
#else
|
||||
// NOLINTNEXTLINE
|
||||
void* ptr = (is_mapped(reinterpret_cast<void*>(addr), size)
|
||||
? MAP_FAILED
|
||||
: mmap(reinterpret_cast<void*>(addr), size, PROT_NONE,
|
||||
MAP_FIXED | MAP_PRIVATE | MAP_ANON | MAP_NORESERVE, -1, 0));
|
||||
#endif
|
||||
|
||||
auto ret_addr = reinterpret_cast<uintptr_t>(ptr);
|
||||
|
||||
if (ptr != MAP_FAILED && ret_addr != addr) {
|
||||
munmap(ptr, size);
|
||||
ret_addr = 0;
|
||||
ptr = MAP_FAILED;
|
||||
}
|
||||
|
||||
if (ptr != MAP_FAILED) {
|
||||
pthread_mutex_lock(&g_virtual_mutex);
|
||||
(*g_allocs)[ret_addr] = size;
|
||||
uintptr_t page_start = ret_addr >> 12u;
|
||||
uintptr_t page_end = (ret_addr + size - 1) >> 12u;
|
||||
for (uintptr_t page = page_start; page <= page_end; page++) {
|
||||
(*g_protects)[page] = PROT_NONE;
|
||||
}
|
||||
pthread_mutex_unlock(&g_virtual_mutex);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SysVirtualDecommit(uint64_t address, uint64_t size) {
|
||||
return SysVirtualProtect(address, size, VirtualMemory::Mode::NoAccess);
|
||||
}
|
||||
|
||||
bool SysVirtualFree(uint64_t address) {
|
||||
EXIT_IF(g_allocs == nullptr);
|
||||
size_t size = 0;
|
||||
|
||||
auto addr = static_cast<uintptr_t>(address & ~static_cast<uint64_t>(0xfffu));
|
||||
|
||||
pthread_mutex_lock(&g_virtual_mutex);
|
||||
if (auto s = g_allocs->find(addr); s != g_allocs->end()) {
|
||||
size = s->second;
|
||||
g_allocs->erase(s);
|
||||
}
|
||||
pthread_mutex_unlock(&g_virtual_mutex);
|
||||
|
||||
if (size == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (munmap(reinterpret_cast<void*>(addr), size) == 0) {
|
||||
uintptr_t page_start = addr >> 12u;
|
||||
uintptr_t page_end = (addr + size - 1) >> 12u;
|
||||
pthread_mutex_lock(&g_virtual_mutex);
|
||||
for (uintptr_t page = page_start; page <= page_end; page++) {
|
||||
g_protects->erase(page);
|
||||
}
|
||||
pthread_mutex_unlock(&g_virtual_mutex);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SysVirtualFreeRange(uint64_t address, uint64_t size) {
|
||||
EXIT_IF(g_allocs == nullptr);
|
||||
if (size == 0 || (address & 0xfffu) != 0 || (size & 0xfffu) != 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto addr = static_cast<uintptr_t>(address);
|
||||
const auto end = addr + size;
|
||||
if (end < addr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&g_virtual_mutex);
|
||||
auto next = g_allocs->upper_bound(addr);
|
||||
if (next == g_allocs->begin()) {
|
||||
pthread_mutex_unlock(&g_virtual_mutex);
|
||||
return false;
|
||||
}
|
||||
auto it = std::prev(next);
|
||||
const auto alloc_addr = it->first;
|
||||
const auto alloc_end = alloc_addr + it->second;
|
||||
if (addr < alloc_addr || end > alloc_end || munmap(reinterpret_cast<void*>(addr), size) != 0) {
|
||||
pthread_mutex_unlock(&g_virtual_mutex);
|
||||
return false;
|
||||
}
|
||||
|
||||
g_allocs->erase(it);
|
||||
if (alloc_addr < addr) {
|
||||
(*g_allocs)[alloc_addr] = addr - alloc_addr;
|
||||
}
|
||||
if (end < alloc_end) {
|
||||
(*g_allocs)[end] = alloc_end - end;
|
||||
}
|
||||
for (uintptr_t page = addr >> 12u; page <= (end - 1u) >> 12u; page++) {
|
||||
g_protects->erase(page);
|
||||
}
|
||||
pthread_mutex_unlock(&g_virtual_mutex);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SysVirtualProtect(uint64_t address, uint64_t size, VirtualMemory::Mode mode,
|
||||
VirtualMemory::Mode* old_mode) {
|
||||
auto addr = static_cast<uintptr_t>(address);
|
||||
|
||||
pthread_mutex_lock(&g_virtual_mutex);
|
||||
if (old_mode != nullptr) {
|
||||
if (auto s = g_protects->find(addr >> 12u); s != g_protects->end()) {
|
||||
*old_mode = get_protection_flag(s->second);
|
||||
} else {
|
||||
*old_mode = VirtualMemory::Mode::NoAccess;
|
||||
}
|
||||
}
|
||||
pthread_mutex_unlock(&g_virtual_mutex);
|
||||
|
||||
uintptr_t page_start = addr >> 12u;
|
||||
uintptr_t page_end = (addr + size - 1) >> 12u;
|
||||
if (mprotect(reinterpret_cast<void*>(page_start << 12u), (page_end - page_start + 1) << 12u,
|
||||
get_protection_flag(mode)) == 0) {
|
||||
pthread_mutex_lock(&g_virtual_mutex);
|
||||
for (uintptr_t page = page_start; page <= page_end; page++) {
|
||||
(*g_protects)[page] = get_protection_flag(mode);
|
||||
}
|
||||
pthread_mutex_unlock(&g_virtual_mutex);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SysVirtualFlushInstructionCache(uint64_t /*address*/, uint64_t /*size*/) {
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace Common
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,53 @@
|
||||
#ifndef KYTY_COMMON_PLATFORM_SYSSWAPBYTEORDER_H_
|
||||
#define KYTY_COMMON_PLATFORM_SYSSWAPBYTEORDER_H_
|
||||
|
||||
#include "common/common.h"
|
||||
|
||||
#include <type_traits>
|
||||
|
||||
inline uint16_t SwapByteOrder16(uint16_t value) {
|
||||
uint16_t hi = value << 8u;
|
||||
uint16_t lo = value >> 8u;
|
||||
return hi | lo;
|
||||
}
|
||||
|
||||
inline uint32_t SwapByteOrder32(uint32_t value) {
|
||||
return __builtin_bswap32(value);
|
||||
}
|
||||
|
||||
inline uint64_t SwapByteOrder64(uint64_t value) {
|
||||
return __builtin_bswap64(value);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline void SwapByteOrder(T& x) {
|
||||
if (sizeof(x) == 2) {
|
||||
if (std::is_signed_v<T>) {
|
||||
x = static_cast<std::make_signed_t<T>>(
|
||||
SwapByteOrder16(static_cast<std::make_unsigned_t<uint16_t>>(x)));
|
||||
} else {
|
||||
x = SwapByteOrder16(x);
|
||||
}
|
||||
}
|
||||
if (sizeof(x) == 4) {
|
||||
if (std::is_signed_v<T>) {
|
||||
x = static_cast<std::make_signed_t<T>>(
|
||||
SwapByteOrder32(static_cast<std::make_unsigned_t<uint32_t>>(x)));
|
||||
} else {
|
||||
x = SwapByteOrder32(x);
|
||||
}
|
||||
}
|
||||
if (sizeof(x) == 8) {
|
||||
if (std::is_signed_v<T>) {
|
||||
x = static_cast<std::make_signed_t<T>>(
|
||||
SwapByteOrder64(static_cast<std::make_unsigned_t<uint64_t>>(x)));
|
||||
} else {
|
||||
x = SwapByteOrder64(x);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline void NoSwapByteOrder(T& x) {}
|
||||
|
||||
#endif /* KYTY_COMMON_PLATFORM_SYSSWAPBYTEORDER_H_ */
|
||||
@@ -0,0 +1,221 @@
|
||||
#ifndef KYTY_COMMON_PLATFORM_SYSTIMER_H_
|
||||
#define KYTY_COMMON_PLATFORM_SYSTIMER_H_
|
||||
|
||||
#include "common/common.h"
|
||||
|
||||
#include <ctime>
|
||||
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
struct SysTimeStruct {
|
||||
uint16_t Year; // NOLINT(readability-identifier-naming)
|
||||
uint16_t Month; // NOLINT(readability-identifier-naming)
|
||||
uint16_t Day; // NOLINT(readability-identifier-naming)
|
||||
uint16_t Hour; // NOLINT(readability-identifier-naming)
|
||||
uint16_t Minute; // NOLINT(readability-identifier-naming)
|
||||
uint16_t Second; // NOLINT(readability-identifier-naming)
|
||||
uint16_t Milliseconds; // NOLINT(readability-identifier-naming)
|
||||
bool is_invalid; // NOLINT(readability-identifier-naming)
|
||||
};
|
||||
|
||||
struct SysFileTimeStruct {
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
FILETIME time;
|
||||
#elif KYTY_PLATFORM == KYTY_PLATFORM_LINUX
|
||||
time_t time;
|
||||
#endif
|
||||
bool is_invalid;
|
||||
};
|
||||
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
|
||||
// NOLINTNEXTLINE(google-runtime-references)
|
||||
inline void SysFileToSystemTimeUtc(const SysFileTimeStruct& f, SysTimeStruct& t) {
|
||||
SYSTEMTIME s;
|
||||
|
||||
if (f.is_invalid || (FileTimeToSystemTime(&f.time, &s) == 0)) {
|
||||
t.is_invalid = true;
|
||||
return;
|
||||
}
|
||||
|
||||
t.is_invalid = false;
|
||||
t.Year = s.wYear;
|
||||
t.Month = s.wMonth;
|
||||
t.Day = s.wDay;
|
||||
t.Hour = s.wHour;
|
||||
t.Minute = s.wMinute;
|
||||
t.Second = (s.wSecond == 60 ? 59 : s.wSecond);
|
||||
t.Milliseconds = s.wMilliseconds;
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(google-runtime-references)
|
||||
inline void SysTimeTToSystem(time_t t, SysTimeStruct& s) {
|
||||
SysFileTimeStruct ft {};
|
||||
LONGLONG ll = Int32x32To64(t, 10000000) + 116444736000000000;
|
||||
ft.time.dwLowDateTime = static_cast<DWORD>(ll);
|
||||
ft.time.dwHighDateTime = static_cast<DWORD>(static_cast<uint64_t>(ll) >> 32u);
|
||||
ft.is_invalid = false;
|
||||
SysFileToSystemTimeUtc(ft, s);
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(google-runtime-references)
|
||||
inline void SysSystemToFileTimeUtc(const SysTimeStruct& f, SysFileTimeStruct& t) {
|
||||
SYSTEMTIME s;
|
||||
|
||||
s.wYear = f.Year;
|
||||
s.wMonth = f.Month;
|
||||
s.wDay = f.Day;
|
||||
s.wHour = f.Hour;
|
||||
s.wMinute = f.Minute;
|
||||
s.wSecond = f.Second;
|
||||
s.wMilliseconds = f.Milliseconds;
|
||||
|
||||
t.is_invalid = (f.is_invalid || (SystemTimeToFileTime(&s, &t.time) == 0));
|
||||
}
|
||||
|
||||
// Retrieves the current local date and time.
|
||||
// NOLINTNEXTLINE(google-runtime-references)
|
||||
inline void SysGetSystemTime(SysTimeStruct& t) {
|
||||
SYSTEMTIME s;
|
||||
GetLocalTime(&s);
|
||||
|
||||
t.is_invalid = false;
|
||||
t.Year = s.wYear;
|
||||
t.Month = s.wMonth;
|
||||
t.Day = s.wDay;
|
||||
t.Hour = s.wHour;
|
||||
t.Minute = s.wMinute;
|
||||
t.Second = (s.wSecond == 60 ? 59 : s.wSecond);
|
||||
t.Milliseconds = s.wMilliseconds;
|
||||
}
|
||||
|
||||
// Retrieves the current system date and time in Coordinated Universal Time (UTC).
|
||||
// NOLINTNEXTLINE(google-runtime-references)
|
||||
inline void SysGetSystemTimeUtc(SysTimeStruct& t) {
|
||||
SYSTEMTIME s;
|
||||
GetSystemTime(&s);
|
||||
|
||||
t.is_invalid = false;
|
||||
t.Year = s.wYear;
|
||||
t.Month = s.wMonth;
|
||||
t.Day = s.wDay;
|
||||
t.Hour = s.wHour;
|
||||
t.Minute = s.wMinute;
|
||||
t.Second = (s.wSecond == 60 ? 59 : s.wSecond);
|
||||
t.Milliseconds = s.wMilliseconds;
|
||||
}
|
||||
|
||||
inline void SysQueryPerformanceFrequency(uint64_t* freq) {
|
||||
LARGE_INTEGER f;
|
||||
QueryPerformanceFrequency(&f);
|
||||
*freq = f.QuadPart;
|
||||
}
|
||||
|
||||
inline void SysQueryPerformanceCounter(uint64_t* counter) {
|
||||
LARGE_INTEGER c;
|
||||
QueryPerformanceCounter(&c);
|
||||
*counter = c.QuadPart;
|
||||
}
|
||||
|
||||
#elif KYTY_PLATFORM == KYTY_PLATFORM_LINUX
|
||||
|
||||
inline void SysFileToSystemTimeUtc(const SysFileTimeStruct& f, SysTimeStruct& t) {
|
||||
struct tm i {};
|
||||
|
||||
if (f.is_invalid || gmtime_r(&f.time, &i) == nullptr) {
|
||||
t.is_invalid = true;
|
||||
return;
|
||||
}
|
||||
|
||||
t.is_invalid = false;
|
||||
t.Year = i.tm_year + 1900;
|
||||
t.Month = i.tm_mon + 1;
|
||||
t.Day = i.tm_mday;
|
||||
t.Hour = i.tm_hour;
|
||||
t.Minute = i.tm_min;
|
||||
t.Second = (i.tm_sec == 60 ? 59 : i.tm_sec);
|
||||
t.Milliseconds = 0;
|
||||
}
|
||||
|
||||
inline void SysTimeTToSystem(time_t t, SysTimeStruct& s) {
|
||||
SysFileTimeStruct ft {};
|
||||
ft.time = t;
|
||||
ft.is_invalid = false;
|
||||
SysFileToSystemTimeUtc(ft, s);
|
||||
}
|
||||
|
||||
inline time_t SysTimegm(struct tm* tm) {
|
||||
return timegm(tm);
|
||||
}
|
||||
|
||||
inline void SysSystemToFileTimeUtc(const SysTimeStruct& f, SysFileTimeStruct& t) {
|
||||
struct tm i {};
|
||||
|
||||
i.tm_year = f.Year - 1900;
|
||||
i.tm_mon = f.Month - 1;
|
||||
i.tm_mday = f.Day;
|
||||
i.tm_hour = f.Hour;
|
||||
i.tm_min = f.Minute;
|
||||
i.tm_sec = f.Second;
|
||||
|
||||
t.is_invalid = (f.is_invalid || (t.time = SysTimegm(&i)) == static_cast<time_t>(-1));
|
||||
}
|
||||
|
||||
// Retrieves the current local date and time.
|
||||
inline void SysGetSystemTime(SysTimeStruct& t) {
|
||||
time_t st {};
|
||||
struct tm i {};
|
||||
|
||||
if (time(&st) == static_cast<time_t>(-1) || localtime_r(&st, &i) == nullptr) {
|
||||
t.is_invalid = true;
|
||||
return;
|
||||
}
|
||||
|
||||
t.is_invalid = false;
|
||||
t.Year = i.tm_year + 1900;
|
||||
t.Month = i.tm_mon + 1;
|
||||
t.Day = i.tm_mday;
|
||||
t.Hour = i.tm_hour;
|
||||
t.Minute = i.tm_min;
|
||||
t.Second = (i.tm_sec == 60 ? 59 : i.tm_sec);
|
||||
t.Milliseconds = 0;
|
||||
}
|
||||
|
||||
// Retrieves the current system date and time in Coordinated Universal Time (UTC).
|
||||
inline void SysGetSystemTimeUtc(SysTimeStruct& t) {
|
||||
time_t st {};
|
||||
struct tm i {};
|
||||
|
||||
if (time(&st) == static_cast<time_t>(-1) || gmtime_r(&st, &i) == nullptr) {
|
||||
t.is_invalid = true;
|
||||
return;
|
||||
}
|
||||
|
||||
t.is_invalid = false;
|
||||
t.Year = i.tm_year + 1900;
|
||||
t.Month = i.tm_mon + 1;
|
||||
t.Day = i.tm_mday;
|
||||
t.Hour = i.tm_hour;
|
||||
t.Minute = i.tm_min;
|
||||
t.Second = (i.tm_sec == 60 ? 59 : i.tm_sec);
|
||||
t.Milliseconds = 0;
|
||||
}
|
||||
|
||||
inline void SysQueryPerformanceFrequency(uint64_t* freq) {
|
||||
*freq = 1000000000LL;
|
||||
}
|
||||
|
||||
inline void SysQueryPerformanceCounter(uint64_t* counter) {
|
||||
struct timespec now {};
|
||||
clock_gettime(CLOCK_MONOTONIC, &now);
|
||||
*counter = now.tv_sec * 1000000000LL + now.tv_nsec;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#endif /* KYTY_COMMON_PLATFORM_SYSTIMER_H_ */
|
||||
@@ -0,0 +1,28 @@
|
||||
#ifndef KYTY_COMMON_PLATFORM_SYSVIRTUAL_H_
|
||||
#define KYTY_COMMON_PLATFORM_SYSVIRTUAL_H_
|
||||
|
||||
#include "common/common.h"
|
||||
#include "common/virtualMemory.h"
|
||||
|
||||
namespace Common {
|
||||
|
||||
void SysVirtualInit();
|
||||
|
||||
uint64_t SysVirtualAlloc(uint64_t address, uint64_t size, VirtualMemory::Mode mode);
|
||||
uint64_t SysVirtualAllocAligned(uint64_t address, uint64_t size, VirtualMemory::Mode mode,
|
||||
uint64_t alignment);
|
||||
bool SysVirtualAllocFixed(uint64_t address, uint64_t size, VirtualMemory::Mode mode);
|
||||
bool SysVirtualCommit(uint64_t address, uint64_t size, VirtualMemory::Mode mode);
|
||||
uint64_t SysVirtualReserve(uint64_t address, uint64_t size);
|
||||
uint64_t SysVirtualReserveAligned(uint64_t address, uint64_t size, uint64_t alignment);
|
||||
bool SysVirtualReserveFixed(uint64_t address, uint64_t size);
|
||||
bool SysVirtualDecommit(uint64_t address, uint64_t size);
|
||||
bool SysVirtualFree(uint64_t address);
|
||||
bool SysVirtualFreeRange(uint64_t address, uint64_t size);
|
||||
bool SysVirtualProtect(uint64_t address, uint64_t size, VirtualMemory::Mode mode,
|
||||
VirtualMemory::Mode* old_mode = nullptr);
|
||||
bool SysVirtualFlushInstructionCache(uint64_t address, uint64_t size);
|
||||
|
||||
} // namespace Common
|
||||
|
||||
#endif /* KYTY_COMMON_PLATFORM_SYSVIRTUAL_H_ */
|
||||
@@ -0,0 +1,105 @@
|
||||
#include "common/common.h"
|
||||
|
||||
// IWYU pragma: no_include <basetsd.h>
|
||||
// IWYU pragma: no_include <memoryapi.h>
|
||||
// IWYU pragma: no_include <minwindef.h>
|
||||
// IWYU pragma: no_include <processthreadsapi.h>
|
||||
// IWYU pragma: no_include <winbase.h>
|
||||
|
||||
#if KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS
|
||||
// #error "KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS"
|
||||
#else
|
||||
|
||||
#include <windows.h> // IWYU pragma: keep
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/platform/sysDbg.h"
|
||||
|
||||
struct FrameS {
|
||||
struct FrameS* next;
|
||||
void* ret_addr;
|
||||
};
|
||||
|
||||
int WalkStack(int z_stack_depth, void** z_stack_trace) {
|
||||
CONTEXT context;
|
||||
// KNONVOLATILE_CONTEXT_POINTERS NvContext;
|
||||
PRUNTIME_FUNCTION runtime_function = nullptr;
|
||||
PVOID handler_data = nullptr;
|
||||
ULONG64 establisher_frame = 0;
|
||||
ULONG64 image_base = 0;
|
||||
|
||||
RtlCaptureContext(&context);
|
||||
|
||||
int frame = 0;
|
||||
|
||||
while (true) {
|
||||
if (frame >= z_stack_depth) {
|
||||
break;
|
||||
}
|
||||
|
||||
z_stack_trace[frame] = reinterpret_cast<void*>(context.Rip);
|
||||
|
||||
frame++;
|
||||
|
||||
runtime_function = RtlLookupFunctionEntry(context.Rip, &image_base, nullptr);
|
||||
|
||||
if (runtime_function == nullptr) {
|
||||
break;
|
||||
}
|
||||
|
||||
// RtlZeroMemory(&NvContext, sizeof(KNONVOLATILE_CONTEXT_POINTERS));
|
||||
RtlVirtualUnwind(0, image_base, context.Rip, runtime_function, &context, &handler_data,
|
||||
&establisher_frame, nullptr /*&NvContext*/);
|
||||
|
||||
if (context.Rip == 0u) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return frame;
|
||||
}
|
||||
|
||||
void SysStackWalk(void** stack, int* depth) {
|
||||
int n = WalkStack(*depth, stack);
|
||||
*depth = n;
|
||||
// unwind_info_t info = {stack, 0, *depth};
|
||||
// _Unwind_Backtrace(&trace_fcn, &info);
|
||||
// *depth = info.depth;
|
||||
}
|
||||
|
||||
void SysStackUsagePrint(sys_dbg_stack_info_t& stack) {
|
||||
printf("stack: (0x%" PRIx64 ", %" PRIu64 ") + (0x%" PRIx64 ", %" PRIu64 ") + (0x%" PRIx64
|
||||
", %" PRIu64 ")\n",
|
||||
static_cast<uint64_t>(stack.reserved_addr), static_cast<uint64_t>(stack.reserved_size),
|
||||
static_cast<uint64_t>(stack.guard_addr), static_cast<uint64_t>(stack.guard_size),
|
||||
static_cast<uint64_t>(stack.commited_addr), static_cast<uint64_t>(stack.commited_size));
|
||||
}
|
||||
|
||||
void SysStackUsage(sys_dbg_stack_info_t& s) {
|
||||
MEMORY_BASIC_INFORMATION mbi {};
|
||||
[[maybe_unused]] size_t ss = VirtualQuery(&mbi, &mbi, sizeof(mbi));
|
||||
EXIT_IF(ss == 0);
|
||||
PVOID reserved = mbi.AllocationBase;
|
||||
ss = VirtualQuery(reserved, &mbi, sizeof(mbi));
|
||||
EXIT_IF(ss == 0);
|
||||
size_t reserved_size = mbi.RegionSize;
|
||||
ss = VirtualQuery(static_cast<char*>(reserved) + reserved_size, &mbi, sizeof(mbi));
|
||||
EXIT_IF(ss == 0);
|
||||
void* guard_page = mbi.BaseAddress;
|
||||
size_t guard_page_size = mbi.RegionSize;
|
||||
ss = VirtualQuery(static_cast<char*>(guard_page) + guard_page_size, &mbi, sizeof(mbi));
|
||||
EXIT_IF(ss == 0);
|
||||
void* commited = mbi.BaseAddress;
|
||||
size_t commited_size = mbi.RegionSize;
|
||||
s.reserved_addr = reinterpret_cast<uintptr_t>(reserved);
|
||||
s.reserved_size = reserved_size;
|
||||
s.guard_addr = reinterpret_cast<uintptr_t>(guard_page);
|
||||
s.guard_size = guard_page_size;
|
||||
s.commited_addr = reinterpret_cast<uintptr_t>(commited);
|
||||
s.commited_size = commited_size;
|
||||
|
||||
s.addr = s.reserved_addr;
|
||||
s.total_size = s.reserved_size + s.guard_size + s.commited_size;
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,576 @@
|
||||
#include "common/common.h"
|
||||
|
||||
#if KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS
|
||||
// #error "KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS"
|
||||
#else
|
||||
|
||||
#include "common/platform/sysFileIO.h"
|
||||
#include "common/platform/sysTimer.h"
|
||||
#include "common/stringUtils.h"
|
||||
|
||||
#include <cstdlib>
|
||||
#include <vector>
|
||||
#include <windows.h> // IWYU pragma: keep
|
||||
|
||||
// NOLINTNEXTLINE(readability-identifier-naming)
|
||||
enum sys_file_type_t {
|
||||
SYS_FILE_ERROR, // NOLINT(readability-identifier-naming)
|
||||
SYS_FILE_MEMORY_STAT, // NOLINT(readability-identifier-naming)
|
||||
SYS_FILE_FILE, // NOLINT(readability-identifier-naming)
|
||||
SYS_FILE_MEMORY_DYN // NOLINT(readability-identifier-naming)
|
||||
};
|
||||
|
||||
// NOLINTNEXTLINE(readability-identifier-naming)
|
||||
struct sys_file_mem_buf_t {
|
||||
uint8_t* base;
|
||||
uint8_t* ptr;
|
||||
uint32_t size;
|
||||
};
|
||||
|
||||
// NOLINTNEXTLINE(readability-identifier-naming)
|
||||
struct sys_file_t {
|
||||
sys_file_type_t type;
|
||||
union {
|
||||
HANDLE handle;
|
||||
sys_file_mem_buf_t* buf;
|
||||
};
|
||||
};
|
||||
|
||||
// IWYU pragma: no_include <fileapi.h>
|
||||
// IWYU pragma: no_include <handleapi.h>
|
||||
// IWYU pragma: no_include <minwinbase.h>
|
||||
// IWYU pragma: no_include <minwindef.h>
|
||||
// IWYU pragma: no_include <winbase.h>
|
||||
|
||||
constexpr DWORD FILE_SHARE_POSIX = static_cast<DWORD>(FILE_SHARE_READ) |
|
||||
static_cast<DWORD>(FILE_SHARE_WRITE) |
|
||||
static_cast<DWORD>(FILE_SHARE_DELETE);
|
||||
|
||||
static DWORD GetCacheAccessType(sys_file_cache_type_t t) {
|
||||
if (t == SYS_FILE_CACHE_RANDOM_ACCESS) {
|
||||
return FILE_FLAG_RANDOM_ACCESS;
|
||||
}
|
||||
|
||||
if (t == SYS_FILE_CACHE_SEQUENTIAL_SCAN) {
|
||||
return SYS_FILE_CACHE_SEQUENTIAL_SCAN;
|
||||
}
|
||||
|
||||
return SYS_FILE_CACHE_AUTO;
|
||||
}
|
||||
|
||||
void SysFileRead(void* data, uint32_t size, sys_file_t& f, uint32_t* bytes_read) {
|
||||
if (f.type == SYS_FILE_FILE) {
|
||||
DWORD w = 0;
|
||||
ReadFile(f.handle, data, size, &w, nullptr);
|
||||
if (bytes_read != nullptr) {
|
||||
*bytes_read = w;
|
||||
}
|
||||
} else if (f.type == SYS_FILE_MEMORY_STAT) {
|
||||
uint32_t s = size;
|
||||
if (f.buf->size != 0u) {
|
||||
uint32_t l = f.buf->size - (f.buf->ptr - f.buf->base);
|
||||
if (s > l) {
|
||||
s = l;
|
||||
}
|
||||
}
|
||||
std::memcpy(data, f.buf->ptr, s);
|
||||
f.buf->ptr += s;
|
||||
if (bytes_read != nullptr) {
|
||||
*bytes_read = s;
|
||||
}
|
||||
} else if (f.type == SYS_FILE_MEMORY_DYN) {
|
||||
uint32_t s = size;
|
||||
if (f.buf->size != 0u) {
|
||||
uint32_t l = f.buf->size - (f.buf->ptr - f.buf->base);
|
||||
if (s > l) {
|
||||
s = l;
|
||||
}
|
||||
} else {
|
||||
s = 0;
|
||||
}
|
||||
std::memcpy(data, f.buf->ptr, s);
|
||||
f.buf->ptr += s;
|
||||
if (bytes_read != nullptr) {
|
||||
*bytes_read = s;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SysFileWrite(const void* data, uint32_t size, sys_file_t& f, uint32_t* bytes_written) {
|
||||
if (f.type == SYS_FILE_FILE) {
|
||||
DWORD w = 0;
|
||||
WriteFile(f.handle, data, size, &w, nullptr);
|
||||
if (bytes_written != nullptr) {
|
||||
*bytes_written = w;
|
||||
}
|
||||
} else if (f.type == SYS_FILE_MEMORY_STAT) {
|
||||
uint32_t s = size;
|
||||
if (f.buf->size != 0u) {
|
||||
uint32_t l = f.buf->size - (f.buf->ptr - f.buf->base);
|
||||
if (s > l) {
|
||||
s = l;
|
||||
}
|
||||
}
|
||||
std::memcpy(f.buf->ptr, data, s);
|
||||
f.buf->ptr += s;
|
||||
if (bytes_written != nullptr) {
|
||||
*bytes_written = s;
|
||||
}
|
||||
} else if (f.type == SYS_FILE_MEMORY_DYN) {
|
||||
uint32_t pos = f.buf->ptr - f.buf->base;
|
||||
if (f.buf->size < pos + size) {
|
||||
f.buf->base = static_cast<uint8_t*>(std::realloc(f.buf->base, pos + size));
|
||||
f.buf->ptr = f.buf->base + pos;
|
||||
f.buf->size = pos + size;
|
||||
}
|
||||
std::memcpy(f.buf->ptr, data, size);
|
||||
f.buf->ptr += size;
|
||||
if (bytes_written != nullptr) {
|
||||
*bytes_written = size;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SysFileWrite(uint32_t n, sys_file_t& f) {
|
||||
SysFileWrite(&n, 4, f);
|
||||
}
|
||||
|
||||
sys_file_t* SysFileCreate(const std::filesystem::path& file_name) {
|
||||
auto* ret = new sys_file_t;
|
||||
|
||||
auto wide = file_name.wstring();
|
||||
HANDLE h_file = nullptr;
|
||||
h_file = CreateFileW(wide.c_str(),
|
||||
static_cast<DWORD>(GENERIC_READ) | static_cast<DWORD>(GENERIC_WRITE) |
|
||||
static_cast<DWORD>(DELETE),
|
||||
FILE_SHARE_POSIX, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);
|
||||
|
||||
ret->handle = h_file;
|
||||
ret->type = SYS_FILE_FILE;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
sys_file_t* SysFileOpenR(const std::filesystem::path& file_name, sys_file_cache_type_t cache_type) {
|
||||
auto* ret = new sys_file_t;
|
||||
|
||||
auto wide = file_name.wstring();
|
||||
HANDLE h_file = nullptr;
|
||||
h_file = CreateFileW(wide.c_str(), GENERIC_READ, FILE_SHARE_POSIX, nullptr, OPEN_EXISTING,
|
||||
GetCacheAccessType(cache_type), nullptr);
|
||||
|
||||
if (h_file == INVALID_HANDLE_VALUE) {
|
||||
ret->type = SYS_FILE_ERROR;
|
||||
} else {
|
||||
ret->type = SYS_FILE_FILE;
|
||||
}
|
||||
|
||||
ret->handle = h_file;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
sys_file_t* SysFileOpen(uint8_t* buf, uint32_t buf_size) {
|
||||
auto* ret = new sys_file_t;
|
||||
|
||||
ret->type = SYS_FILE_MEMORY_STAT;
|
||||
ret->buf = new sys_file_mem_buf_t;
|
||||
ret->buf->base = buf;
|
||||
ret->buf->ptr = buf;
|
||||
ret->buf->size = buf_size;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
sys_file_t* SysFileCreate() {
|
||||
auto* ret = new sys_file_t;
|
||||
|
||||
ret->type = SYS_FILE_MEMORY_DYN;
|
||||
ret->buf = new sys_file_mem_buf_t;
|
||||
ret->buf->base = nullptr;
|
||||
ret->buf->ptr = nullptr;
|
||||
ret->buf->size = 0;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
sys_file_t* SysFileOpenW(const std::filesystem::path& file_name, sys_file_cache_type_t cache_type) {
|
||||
auto* ret = new sys_file_t;
|
||||
|
||||
auto wide = file_name.wstring();
|
||||
HANDLE h_file = nullptr;
|
||||
h_file = CreateFileW(
|
||||
wide.c_str(), static_cast<DWORD>(GENERIC_WRITE) | static_cast<DWORD>(DELETE),
|
||||
FILE_SHARE_POSIX, nullptr, OPEN_EXISTING, GetCacheAccessType(cache_type), nullptr);
|
||||
|
||||
if (h_file == INVALID_HANDLE_VALUE) {
|
||||
ret->type = SYS_FILE_ERROR;
|
||||
} else {
|
||||
ret->type = SYS_FILE_FILE;
|
||||
}
|
||||
|
||||
ret->handle = h_file;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
sys_file_t* SysFileOpenRw(const std::filesystem::path& file_name,
|
||||
sys_file_cache_type_t cache_type) {
|
||||
auto* ret = new sys_file_t;
|
||||
|
||||
auto wide = file_name.wstring();
|
||||
HANDLE h_file = nullptr;
|
||||
h_file = CreateFileW(wide.c_str(),
|
||||
static_cast<DWORD>(GENERIC_READ) | static_cast<DWORD>(GENERIC_WRITE) |
|
||||
static_cast<DWORD>(DELETE),
|
||||
FILE_SHARE_POSIX, nullptr, OPEN_EXISTING, GetCacheAccessType(cache_type),
|
||||
nullptr);
|
||||
|
||||
if (h_file == INVALID_HANDLE_VALUE) {
|
||||
ret->type = SYS_FILE_ERROR;
|
||||
} else {
|
||||
ret->type = SYS_FILE_FILE;
|
||||
}
|
||||
|
||||
ret->handle = h_file;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void SysFileClose(sys_file_t* f) {
|
||||
if (f->type == SYS_FILE_FILE) {
|
||||
CloseHandle(f->handle);
|
||||
} else if (f->type == SYS_FILE_MEMORY_STAT) {
|
||||
delete f->buf;
|
||||
} else if (f->type == SYS_FILE_MEMORY_DYN) {
|
||||
std::free(f->buf->base);
|
||||
delete f->buf;
|
||||
}
|
||||
|
||||
// f.type = SYS_FILE_ERROR;
|
||||
delete f;
|
||||
}
|
||||
|
||||
uint64_t SysFileSize(sys_file_t& f) {
|
||||
if (f.type == SYS_FILE_FILE) {
|
||||
LARGE_INTEGER s;
|
||||
GetFileSizeEx(f.handle, &s);
|
||||
return s.QuadPart;
|
||||
}
|
||||
|
||||
if (f.type == SYS_FILE_MEMORY_STAT || f.type == SYS_FILE_MEMORY_DYN) {
|
||||
return f.buf->size;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint64_t SysFileSize(const std::filesystem::path& file_name) {
|
||||
LARGE_INTEGER s;
|
||||
WIN32_FILE_ATTRIBUTE_DATA a;
|
||||
|
||||
auto wide = file_name.wstring();
|
||||
if (GetFileAttributesExW(wide.c_str(), GetFileExInfoStandard, &a) == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
s.HighPart = static_cast<LONG>(a.nFileSizeHigh);
|
||||
s.LowPart = a.nFileSizeLow;
|
||||
|
||||
return s.QuadPart;
|
||||
}
|
||||
|
||||
bool SysFileTruncate(sys_file_t& f, uint64_t size) {
|
||||
bool ok = false;
|
||||
if (f.type == SYS_FILE_FILE) {
|
||||
LARGE_INTEGER s {};
|
||||
LARGE_INTEGER r {};
|
||||
s.QuadPart = 0;
|
||||
SetFilePointerEx(f.handle, s, &r, FILE_CURRENT);
|
||||
s.QuadPart = static_cast<LONGLONG>(size);
|
||||
ok = (SetFilePointerEx(f.handle, s, nullptr, FILE_BEGIN) != 0 &&
|
||||
SetEndOfFile(f.handle) != 0);
|
||||
SetFilePointerEx(f.handle, r, nullptr, FILE_BEGIN);
|
||||
}
|
||||
|
||||
return ok;
|
||||
}
|
||||
|
||||
bool SysFileUnlink(sys_file_t& f, const std::filesystem::path& name) {
|
||||
if (f.type == SYS_FILE_FILE) {
|
||||
FILE_DISPOSITION_INFO info {};
|
||||
info.DeleteFile = TRUE;
|
||||
if (SetFileInformationByHandle(f.handle, FileDispositionInfo, &info, sizeof(info)) != 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return SysFileDeleteFile(name);
|
||||
}
|
||||
|
||||
bool SysFileSeek(sys_file_t& f, uint64_t offset) {
|
||||
bool ok = true;
|
||||
if (f.type == SYS_FILE_FILE) {
|
||||
LARGE_INTEGER s;
|
||||
s.QuadPart = static_cast<LONGLONG>(offset);
|
||||
ok = (SetFilePointerEx(f.handle, s, nullptr, FILE_BEGIN) != 0);
|
||||
// printf("seek: %u\n", offset);
|
||||
} else if (f.type == SYS_FILE_MEMORY_STAT || f.type == SYS_FILE_MEMORY_DYN) {
|
||||
f.buf->ptr = f.buf->base + offset;
|
||||
}
|
||||
|
||||
return ok;
|
||||
}
|
||||
|
||||
uint64_t SysFileTell(sys_file_t& f) {
|
||||
if (f.type == SYS_FILE_FILE) {
|
||||
LARGE_INTEGER s {};
|
||||
LARGE_INTEGER r {};
|
||||
s.QuadPart = 0;
|
||||
SetFilePointerEx(f.handle, s, &r, FILE_CURRENT);
|
||||
// printf("tell: %u\n", r.QuadPart);
|
||||
return r.QuadPart;
|
||||
}
|
||||
|
||||
if (f.type == SYS_FILE_MEMORY_STAT || f.type == SYS_FILE_MEMORY_DYN) {
|
||||
return f.buf->ptr - f.buf->base;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool SysFileIsError(sys_file_t& f) {
|
||||
return f.type == SYS_FILE_ERROR ||
|
||||
(f.type == SYS_FILE_FILE && f.handle == INVALID_HANDLE_VALUE);
|
||||
}
|
||||
|
||||
bool SysFileIsDirectoryExisting(const std::filesystem::path& path) {
|
||||
auto wide = path.wstring();
|
||||
DWORD a = GetFileAttributesW(wide.c_str());
|
||||
return a != INVALID_FILE_ATTRIBUTES &&
|
||||
((a & static_cast<DWORD>(FILE_ATTRIBUTE_DIRECTORY)) != 0u);
|
||||
}
|
||||
|
||||
bool SysFileIsFileExisting(const std::filesystem::path& name) {
|
||||
auto wide = name.wstring();
|
||||
DWORD a = GetFileAttributesW(wide.c_str());
|
||||
return a != INVALID_FILE_ATTRIBUTES &&
|
||||
((a & static_cast<DWORD>(FILE_ATTRIBUTE_DIRECTORY)) == 0u);
|
||||
}
|
||||
|
||||
bool SysFileCreateDirectory(const std::filesystem::path& path) {
|
||||
auto wide = path.wstring();
|
||||
return CreateDirectoryW(wide.c_str(), nullptr) != 0;
|
||||
}
|
||||
|
||||
bool SysFileDeleteDirectory(const std::filesystem::path& path) {
|
||||
auto wide = path.wstring();
|
||||
return RemoveDirectoryW(wide.c_str()) != 0;
|
||||
}
|
||||
|
||||
bool SysFileDeleteFile(const std::filesystem::path& name) {
|
||||
auto wide = name.wstring();
|
||||
return DeleteFileW(wide.c_str()) != 0;
|
||||
}
|
||||
|
||||
bool SysFileFlush(sys_file_t& f) {
|
||||
if (f.type == SYS_FILE_FILE && f.handle != INVALID_HANDLE_VALUE) {
|
||||
return (FlushFileBuffers(f.handle) != 0);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
SysFileTimeStruct SysFileGetLastAccessTimeUtc(const std::filesystem::path& name) {
|
||||
SysFileTimeStruct r {};
|
||||
sys_file_t* f = SysFileOpenR(name);
|
||||
r.is_invalid =
|
||||
(f->type == SYS_FILE_ERROR || (GetFileTime(f->handle, nullptr, &r.time, nullptr) == 0));
|
||||
SysFileClose(f);
|
||||
return r;
|
||||
}
|
||||
|
||||
SysFileTimeStruct SysFileGetLastWriteTimeUtc(const std::filesystem::path& name) {
|
||||
SysFileTimeStruct r {};
|
||||
sys_file_t* f = SysFileOpenR(name);
|
||||
r.is_invalid =
|
||||
(f->type == SYS_FILE_ERROR || (GetFileTime(f->handle, nullptr, nullptr, &r.time) == 0));
|
||||
SysFileClose(f);
|
||||
return r;
|
||||
}
|
||||
|
||||
void SysFileGetLastAccessAndWriteTimeUtc(const std::filesystem::path& name, SysFileTimeStruct& a,
|
||||
SysFileTimeStruct& w) {
|
||||
// TODO() open file with dwDesiredAccess = 0
|
||||
// TODO() open directory
|
||||
sys_file_t* f = SysFileOpenR(name);
|
||||
a.is_invalid = w.is_invalid =
|
||||
(f->type == SYS_FILE_ERROR || (GetFileTime(f->handle, nullptr, &a.time, &w.time) == 0));
|
||||
SysFileClose(f);
|
||||
}
|
||||
|
||||
void SysFileGetLastAccessAndWriteTimeUtc(sys_file_t& f, SysFileTimeStruct& a,
|
||||
SysFileTimeStruct& w) {
|
||||
if (f.type == SYS_FILE_FILE) {
|
||||
a.is_invalid = w.is_invalid = (GetFileTime(f.handle, nullptr, &a.time, &w.time) == 0);
|
||||
} else if (f.type == SYS_FILE_MEMORY_STAT || f.type == SYS_FILE_MEMORY_DYN) {
|
||||
SysTimeStruct t {};
|
||||
SysGetSystemTimeUtc(t);
|
||||
SysSystemToFileTimeUtc(t, a);
|
||||
SysSystemToFileTimeUtc(t, w);
|
||||
} else {
|
||||
a.is_invalid = w.is_invalid = true;
|
||||
}
|
||||
}
|
||||
|
||||
bool SysFileSetLastAccessTimeUtc(const std::filesystem::path& name, SysFileTimeStruct& access) {
|
||||
if (access.is_invalid) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ok = true;
|
||||
|
||||
sys_file_t* f = SysFileOpenW(name);
|
||||
|
||||
ok = !(f->type == SYS_FILE_ERROR ||
|
||||
(SetFileTime(f->handle, nullptr, &access.time, nullptr) == 0));
|
||||
|
||||
SysFileClose(f);
|
||||
|
||||
return ok;
|
||||
}
|
||||
|
||||
bool SysFileSetLastWriteTimeUtc(const std::filesystem::path& name, SysFileTimeStruct& write) {
|
||||
if (write.is_invalid) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ok = true;
|
||||
|
||||
sys_file_t* f = SysFileOpenW(name);
|
||||
|
||||
ok = !(f->type == SYS_FILE_ERROR ||
|
||||
(SetFileTime(f->handle, nullptr, nullptr, &write.time) == 0));
|
||||
|
||||
SysFileClose(f);
|
||||
|
||||
return ok;
|
||||
}
|
||||
|
||||
bool SysFileSetLastAccessAndWriteTimeUtc(const std::filesystem::path& name,
|
||||
SysFileTimeStruct& access, SysFileTimeStruct& write) {
|
||||
if (access.is_invalid || write.is_invalid) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ok = true;
|
||||
|
||||
sys_file_t* f = SysFileOpenW(name);
|
||||
|
||||
ok = !(f->type == SYS_FILE_ERROR ||
|
||||
(SetFileTime(f->handle, nullptr, &access.time, &write.time) == 0));
|
||||
|
||||
SysFileClose(f);
|
||||
|
||||
return ok;
|
||||
}
|
||||
|
||||
void SysFileFindFiles(const std::filesystem::path& path, std::vector<sys_file_find_t>& out) {
|
||||
std::string real_path = Common::ReplaceChar(Common::PathToGenericString(path), '\\', '/');
|
||||
if (!Common::EndsWith(real_path, "/")) {
|
||||
real_path += "/";
|
||||
}
|
||||
|
||||
std::string pattern = real_path + "*";
|
||||
|
||||
HANDLE h = nullptr;
|
||||
WIN32_FIND_DATAW data;
|
||||
|
||||
auto wide_pattern = std::filesystem::path(pattern).wstring();
|
||||
h = FindFirstFileW(wide_pattern.c_str(), &data);
|
||||
|
||||
if (h == INVALID_HANDLE_VALUE) {
|
||||
return;
|
||||
}
|
||||
|
||||
do {
|
||||
std::filesystem::path file_name(data.cFileName);
|
||||
auto file_name_str = Common::PathToString(file_name);
|
||||
|
||||
if (file_name_str == "." || file_name_str == "..") {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((data.dwFileAttributes & static_cast<DWORD>(FILE_ATTRIBUTE_DIRECTORY)) != 0u) {
|
||||
SysFileFindFiles(std::filesystem::path(real_path) / file_name, out);
|
||||
} else {
|
||||
sys_file_find_t r {};
|
||||
|
||||
r.path_with_name = std::filesystem::path(real_path) / file_name;
|
||||
r.size = (static_cast<uint64_t>(data.nFileSizeHigh) << 32u) +
|
||||
static_cast<uint64_t>(data.nFileSizeLow);
|
||||
r.last_access_time.is_invalid = false;
|
||||
r.last_access_time.time = data.ftLastAccessTime;
|
||||
r.last_write_time.is_invalid = false;
|
||||
r.last_write_time.time = data.ftLastWriteTime;
|
||||
|
||||
out.push_back(r);
|
||||
}
|
||||
|
||||
} while (FindNextFileW(h, &data) != 0);
|
||||
|
||||
FindClose(h);
|
||||
}
|
||||
|
||||
void SysFileGetDents(const std::filesystem::path& path, std::vector<sys_dir_entry_t>& out) {
|
||||
std::string real_path = Common::ReplaceChar(Common::PathToGenericString(path), '\\', '/');
|
||||
if (!Common::EndsWith(real_path, "/")) {
|
||||
real_path += "/";
|
||||
}
|
||||
|
||||
std::string pattern = real_path + "*";
|
||||
|
||||
HANDLE h = nullptr;
|
||||
WIN32_FIND_DATAW data;
|
||||
|
||||
auto wide_pattern = std::filesystem::path(pattern).wstring();
|
||||
h = FindFirstFileW(wide_pattern.c_str(), &data);
|
||||
|
||||
if (h == INVALID_HANDLE_VALUE) {
|
||||
return;
|
||||
}
|
||||
|
||||
do {
|
||||
std::filesystem::path file_name(data.cFileName);
|
||||
|
||||
sys_dir_entry_t r {};
|
||||
|
||||
r.is_file = ((data.dwFileAttributes & static_cast<DWORD>(FILE_ATTRIBUTE_DIRECTORY)) == 0u);
|
||||
r.name = Common::PathToString(file_name);
|
||||
|
||||
out.push_back(r);
|
||||
|
||||
} while (FindNextFileW(h, &data) != 0);
|
||||
|
||||
FindClose(h);
|
||||
}
|
||||
|
||||
bool SysFileCopyFile(const std::filesystem::path& src, const std::filesystem::path& dst) {
|
||||
auto src_wide = src.wstring();
|
||||
auto dst_wide = dst.wstring();
|
||||
return CopyFileW(src_wide.c_str(), dst_wide.c_str(), FALSE) != 0;
|
||||
}
|
||||
|
||||
bool SysFileMoveFile(const std::filesystem::path& src, const std::filesystem::path& dst) {
|
||||
auto src_wide = src.wstring();
|
||||
auto dst_wide = dst.wstring();
|
||||
return MoveFileW(src_wide.c_str(), dst_wide.c_str()) != 0;
|
||||
}
|
||||
|
||||
void SysFileRemoveReadonly(const std::filesystem::path& name) {
|
||||
auto wide = name.wstring();
|
||||
SetFileAttributesW(wide.c_str(), GetFileAttributesW(wide.c_str()) &
|
||||
(~static_cast<DWORD>(FILE_ATTRIBUTE_READONLY)));
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,355 @@
|
||||
#include "common/common.h"
|
||||
|
||||
#if KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS
|
||||
// #error "KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS"
|
||||
#else
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/platform/sysVirtual.h"
|
||||
#include "common/virtualMemory.h"
|
||||
|
||||
#include <windows.h> // IWYU pragma: keep
|
||||
|
||||
// IWYU pragma: no_include <basetsd.h>
|
||||
// IWYU pragma: no_include <errhandlingapi.h>
|
||||
// IWYU pragma: no_include <memoryapi.h>
|
||||
// IWYU pragma: no_include <minwindef.h>
|
||||
// IWYU pragma: no_include <processthreadsapi.h>
|
||||
// IWYU pragma: no_include <winbase.h>
|
||||
// IWYU pragma: no_include <winerror.h>
|
||||
// IWYU pragma: no_include <wtypes.h>
|
||||
|
||||
namespace Common {
|
||||
|
||||
static DWORD GetProtectionFlag(VirtualMemory::Mode mode) {
|
||||
DWORD protect = PAGE_NOACCESS;
|
||||
switch (mode) {
|
||||
case VirtualMemory::Mode::Read: protect = PAGE_READONLY; break;
|
||||
|
||||
case VirtualMemory::Mode::Write:
|
||||
case VirtualMemory::Mode::ReadWrite: protect = PAGE_READWRITE; break;
|
||||
|
||||
case VirtualMemory::Mode::Execute: protect = PAGE_EXECUTE; break;
|
||||
|
||||
case VirtualMemory::Mode::ExecuteRead: protect = PAGE_EXECUTE_READ; break;
|
||||
|
||||
case VirtualMemory::Mode::ExecuteWrite:
|
||||
case VirtualMemory::Mode::ExecuteReadWrite: protect = PAGE_EXECUTE_READWRITE; break;
|
||||
|
||||
case VirtualMemory::Mode::NoAccess:
|
||||
default: protect = PAGE_NOACCESS; break;
|
||||
}
|
||||
return protect;
|
||||
}
|
||||
|
||||
static VirtualMemory::Mode GetProtectionFlag(DWORD mode) {
|
||||
switch (mode) {
|
||||
case PAGE_NOACCESS: return VirtualMemory::Mode::NoAccess;
|
||||
case PAGE_READONLY: return VirtualMemory::Mode::Read;
|
||||
case PAGE_READWRITE: return VirtualMemory::Mode::ReadWrite;
|
||||
case PAGE_EXECUTE: return VirtualMemory::Mode::Execute;
|
||||
case PAGE_EXECUTE_READ: return VirtualMemory::Mode::ExecuteRead;
|
||||
case PAGE_EXECUTE_READWRITE: return VirtualMemory::Mode::ExecuteReadWrite;
|
||||
default: return VirtualMemory::Mode::NoAccess;
|
||||
}
|
||||
}
|
||||
|
||||
void SysVirtualInit() {}
|
||||
|
||||
uint64_t SysVirtualAlloc(uint64_t address, uint64_t size, VirtualMemory::Mode mode) {
|
||||
auto ptr = (address == 0 ? SysVirtualAllocAligned(address, size, mode, 1)
|
||||
: reinterpret_cast<uintptr_t>(VirtualAlloc(
|
||||
reinterpret_cast<LPVOID>(static_cast<uintptr_t>(address)), size,
|
||||
static_cast<DWORD>(MEM_COMMIT) | static_cast<DWORD>(MEM_RESERVE),
|
||||
GetProtectionFlag(mode))));
|
||||
if (ptr == 0) {
|
||||
auto err = static_cast<uint32_t>(GetLastError());
|
||||
|
||||
if (err != ERROR_INVALID_ADDRESS) {
|
||||
printf("VirtualAlloc() failed: 0x%08" PRIx32 "\n", err);
|
||||
} else {
|
||||
return SysVirtualAllocAligned(address, size, mode, 1);
|
||||
}
|
||||
}
|
||||
return ptr;
|
||||
}
|
||||
|
||||
using VirtualAlloc2_func_t = /*WINBASEAPI*/ PVOID WINAPI (*)(HANDLE, PVOID, SIZE_T, ULONG, ULONG,
|
||||
MEM_EXTENDED_PARAMETER*, ULONG);
|
||||
|
||||
static VirtualAlloc2_func_t ResolveVirtualAlloc2() {
|
||||
HMODULE h = GetModuleHandle("KernelBase"); // @suppress("Invalid arguments")
|
||||
if (h != nullptr) {
|
||||
return reinterpret_cast<VirtualAlloc2_func_t>(GetProcAddress(h, "VirtualAlloc2"));
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static uint64_t AlignUp(uint64_t addr, uint64_t alignment) {
|
||||
return (addr + alignment - 1) & ~(alignment - 1);
|
||||
}
|
||||
|
||||
uint64_t SysVirtualAllocAligned(uint64_t address, uint64_t size, VirtualMemory::Mode mode,
|
||||
uint64_t alignment) {
|
||||
if (alignment == 0) {
|
||||
printf("VirtualAlloc2 failed: 0x%08" PRIx32 "\n", static_cast<uint32_t>(GetLastError()));
|
||||
return 0;
|
||||
}
|
||||
|
||||
static constexpr uint64_t SYSTEM_MANAGED_MIN = 0x0000040000u;
|
||||
static constexpr uint64_t SYSTEM_MANAGED_MAX = 0x07FFFFBFFFu;
|
||||
static constexpr uint64_t USER_MIN = 0x1000000000u;
|
||||
static constexpr uint64_t USER_MAX = 0xFBFFFFFFFFu;
|
||||
|
||||
MEM_ADDRESS_REQUIREMENTS req {};
|
||||
MEM_EXTENDED_PARAMETER param {};
|
||||
req.LowestStartingAddress =
|
||||
(address == 0 ? reinterpret_cast<PVOID>(SYSTEM_MANAGED_MIN)
|
||||
: reinterpret_cast<PVOID>(AlignUp(address, alignment)));
|
||||
req.HighestEndingAddress = (address == 0 ? reinterpret_cast<PVOID>(SYSTEM_MANAGED_MAX)
|
||||
: reinterpret_cast<PVOID>(USER_MAX));
|
||||
req.Alignment = alignment;
|
||||
param.Type = MemExtendedParameterAddressRequirements;
|
||||
param.Pointer = &req;
|
||||
|
||||
MEM_ADDRESS_REQUIREMENTS req2 {};
|
||||
MEM_EXTENDED_PARAMETER param2 {};
|
||||
req2.LowestStartingAddress =
|
||||
(address == 0 ? reinterpret_cast<PVOID>(USER_MIN)
|
||||
: reinterpret_cast<PVOID>(AlignUp(address, alignment)));
|
||||
req2.HighestEndingAddress = reinterpret_cast<PVOID>(USER_MAX);
|
||||
req2.Alignment = alignment;
|
||||
param2.Type = MemExtendedParameterAddressRequirements;
|
||||
param2.Pointer = &req2;
|
||||
|
||||
static auto virtual_alloc2 = ResolveVirtualAlloc2();
|
||||
|
||||
EXIT_NOT_IMPLEMENTED(virtual_alloc2 == nullptr);
|
||||
|
||||
auto ptr = reinterpret_cast<uintptr_t>(
|
||||
virtual_alloc2(GetCurrentProcess(), nullptr, size,
|
||||
static_cast<DWORD>(MEM_COMMIT) | static_cast<DWORD>(MEM_RESERVE),
|
||||
GetProtectionFlag(mode), ¶m, 1));
|
||||
|
||||
if (ptr == 0) {
|
||||
ptr = reinterpret_cast<uintptr_t>(
|
||||
virtual_alloc2(GetCurrentProcess(), nullptr, size,
|
||||
static_cast<DWORD>(MEM_COMMIT) | static_cast<DWORD>(MEM_RESERVE),
|
||||
GetProtectionFlag(mode), ¶m2, 1));
|
||||
}
|
||||
|
||||
if (ptr == 0) {
|
||||
auto err = static_cast<uint32_t>(GetLastError());
|
||||
if (err != ERROR_INVALID_PARAMETER) {
|
||||
printf("VirtualAlloc2(alignment = 0x%016" PRIx64 ") failed: 0x%08" PRIx32 "\n",
|
||||
alignment, err);
|
||||
} else {
|
||||
return SysVirtualAllocAligned(address, size, mode, alignment << 1u);
|
||||
}
|
||||
}
|
||||
return ptr;
|
||||
}
|
||||
|
||||
bool SysVirtualAllocFixed(uint64_t address, uint64_t size, VirtualMemory::Mode mode) {
|
||||
auto ptr = reinterpret_cast<uintptr_t>(VirtualAlloc(
|
||||
reinterpret_cast<LPVOID>(static_cast<uintptr_t>(address)), size,
|
||||
static_cast<DWORD>(MEM_COMMIT) | static_cast<DWORD>(MEM_RESERVE), GetProtectionFlag(mode)));
|
||||
if (ptr == 0) {
|
||||
auto err = static_cast<uint32_t>(GetLastError());
|
||||
|
||||
printf("VirtualAlloc() failed: 0x%08" PRIx32 "\n", err);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ptr != address) {
|
||||
printf("VirtualAlloc() failed: wrong address\n");
|
||||
VirtualFree(reinterpret_cast<LPVOID>(ptr), 0, MEM_RELEASE);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SysVirtualCommit(uint64_t address, uint64_t size, VirtualMemory::Mode mode) {
|
||||
auto ptr = reinterpret_cast<uintptr_t>(
|
||||
VirtualAlloc(reinterpret_cast<LPVOID>(static_cast<uintptr_t>(address)), size,
|
||||
static_cast<DWORD>(MEM_COMMIT), GetProtectionFlag(mode)));
|
||||
if (ptr == 0) {
|
||||
printf("VirtualAlloc(MEM_COMMIT) failed: 0x%08" PRIx32 "\n",
|
||||
static_cast<uint32_t>(GetLastError()));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ptr != address) {
|
||||
printf("VirtualAlloc(MEM_COMMIT) failed: wrong address\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
uint64_t SysVirtualReserve(uint64_t address, uint64_t size) {
|
||||
auto ptr = (address == 0 ? SysVirtualReserveAligned(address, size, 1)
|
||||
: reinterpret_cast<uintptr_t>(VirtualAlloc(
|
||||
reinterpret_cast<LPVOID>(static_cast<uintptr_t>(address)), size,
|
||||
static_cast<DWORD>(MEM_RESERVE), PAGE_NOACCESS)));
|
||||
if (ptr == 0) {
|
||||
auto err = static_cast<uint32_t>(GetLastError());
|
||||
|
||||
if (err != ERROR_INVALID_ADDRESS) {
|
||||
printf("VirtualAlloc(MEM_RESERVE) failed: 0x%08" PRIx32 "\n", err);
|
||||
} else {
|
||||
return SysVirtualReserveAligned(address, size, 1);
|
||||
}
|
||||
}
|
||||
return ptr;
|
||||
}
|
||||
|
||||
uint64_t SysVirtualReserveAligned(uint64_t address, uint64_t size, uint64_t alignment) {
|
||||
if (alignment == 0) {
|
||||
printf("VirtualAlloc2(MEM_RESERVE) failed: 0x%08" PRIx32 "\n",
|
||||
static_cast<uint32_t>(GetLastError()));
|
||||
return 0;
|
||||
}
|
||||
|
||||
static constexpr uint64_t SYSTEM_MANAGED_MIN = 0x0000040000u;
|
||||
static constexpr uint64_t SYSTEM_MANAGED_MAX = 0x07FFFFBFFFu;
|
||||
static constexpr uint64_t USER_MIN = 0x1000000000u;
|
||||
static constexpr uint64_t USER_MAX = 0xFBFFFFFFFFu;
|
||||
|
||||
MEM_ADDRESS_REQUIREMENTS req {};
|
||||
MEM_EXTENDED_PARAMETER param {};
|
||||
req.LowestStartingAddress =
|
||||
(address == 0 ? reinterpret_cast<PVOID>(SYSTEM_MANAGED_MIN)
|
||||
: reinterpret_cast<PVOID>(AlignUp(address, alignment)));
|
||||
req.HighestEndingAddress = (address == 0 ? reinterpret_cast<PVOID>(SYSTEM_MANAGED_MAX)
|
||||
: reinterpret_cast<PVOID>(USER_MAX));
|
||||
req.Alignment = alignment;
|
||||
param.Type = MemExtendedParameterAddressRequirements;
|
||||
param.Pointer = &req;
|
||||
|
||||
MEM_ADDRESS_REQUIREMENTS req2 {};
|
||||
MEM_EXTENDED_PARAMETER param2 {};
|
||||
req2.LowestStartingAddress =
|
||||
(address == 0 ? reinterpret_cast<PVOID>(USER_MIN)
|
||||
: reinterpret_cast<PVOID>(AlignUp(address, alignment)));
|
||||
req2.HighestEndingAddress = reinterpret_cast<PVOID>(USER_MAX);
|
||||
req2.Alignment = alignment;
|
||||
param2.Type = MemExtendedParameterAddressRequirements;
|
||||
param2.Pointer = &req2;
|
||||
|
||||
static auto virtual_alloc2 = ResolveVirtualAlloc2();
|
||||
|
||||
EXIT_NOT_IMPLEMENTED(virtual_alloc2 == nullptr);
|
||||
|
||||
auto ptr = reinterpret_cast<uintptr_t>(virtual_alloc2(GetCurrentProcess(), nullptr, size,
|
||||
static_cast<DWORD>(MEM_RESERVE),
|
||||
PAGE_NOACCESS, ¶m, 1));
|
||||
|
||||
if (ptr == 0) {
|
||||
ptr = reinterpret_cast<uintptr_t>(virtual_alloc2(GetCurrentProcess(), nullptr, size,
|
||||
static_cast<DWORD>(MEM_RESERVE),
|
||||
PAGE_NOACCESS, ¶m2, 1));
|
||||
}
|
||||
|
||||
if (ptr == 0) {
|
||||
auto err = static_cast<uint32_t>(GetLastError());
|
||||
if (err != ERROR_INVALID_PARAMETER) {
|
||||
printf("VirtualAlloc2(MEM_RESERVE, alignment = 0x%016" PRIx64 ") failed: 0x%08" PRIx32
|
||||
"\n",
|
||||
alignment, err);
|
||||
} else {
|
||||
return SysVirtualReserveAligned(address, size, alignment << 1u);
|
||||
}
|
||||
}
|
||||
return ptr;
|
||||
}
|
||||
|
||||
bool SysVirtualReserveFixed(uint64_t address, uint64_t size) {
|
||||
auto ptr = reinterpret_cast<uintptr_t>(
|
||||
VirtualAlloc(reinterpret_cast<LPVOID>(static_cast<uintptr_t>(address)), size,
|
||||
static_cast<DWORD>(MEM_RESERVE), PAGE_NOACCESS));
|
||||
if (ptr == 0) {
|
||||
printf("VirtualAlloc(MEM_RESERVE) failed: address=0x%016" PRIx64 ", size=0x%016" PRIx64
|
||||
", err=0x%08" PRIx32 "\n",
|
||||
address, size, static_cast<uint32_t>(GetLastError()));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ptr != address) {
|
||||
printf("VirtualAlloc(MEM_RESERVE) failed: wrong address\n");
|
||||
VirtualFree(reinterpret_cast<LPVOID>(ptr), 0, MEM_RELEASE);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SysVirtualDecommit(uint64_t address, uint64_t size) {
|
||||
if (VirtualFree(reinterpret_cast<LPVOID>(static_cast<uintptr_t>(address)), size,
|
||||
MEM_DECOMMIT) == 0) {
|
||||
printf("VirtualFree(MEM_DECOMMIT) failed: 0x%08" PRIx32 "\n",
|
||||
static_cast<uint32_t>(GetLastError()));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SysVirtualFree(uint64_t address) {
|
||||
if (VirtualFree(reinterpret_cast<LPVOID>(static_cast<uintptr_t>(address)), 0, MEM_RELEASE) ==
|
||||
0) {
|
||||
printf("VirtualFree() failed: 0x%08" PRIx32 "\n", static_cast<uint32_t>(GetLastError()));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SysVirtualFreeRange(uint64_t address, uint64_t size) {
|
||||
if (address == 0 || size == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint64_t current = address;
|
||||
while (current - address < size) {
|
||||
MEMORY_BASIC_INFORMATION info {};
|
||||
if (VirtualQuery(reinterpret_cast<const void*>(current), &info, sizeof(info)) == 0 ||
|
||||
reinterpret_cast<uint64_t>(info.AllocationBase) != address || info.State == MEM_FREE) {
|
||||
return false;
|
||||
}
|
||||
const auto next = reinterpret_cast<uint64_t>(info.BaseAddress) + info.RegionSize;
|
||||
if (next <= current || next - address > size) {
|
||||
return false;
|
||||
}
|
||||
current = next;
|
||||
}
|
||||
return current - address == size && SysVirtualFree(address);
|
||||
}
|
||||
|
||||
bool SysVirtualProtect(uint64_t address, uint64_t size, VirtualMemory::Mode mode,
|
||||
VirtualMemory::Mode* old_mode) {
|
||||
DWORD old_protect = 0;
|
||||
if (VirtualProtect(reinterpret_cast<LPVOID>(static_cast<uintptr_t>(address)), size,
|
||||
GetProtectionFlag(mode), &old_protect) == 0) {
|
||||
printf("VirtualProtect() failed: 0x%08" PRIx32 "\n", static_cast<uint32_t>(GetLastError()));
|
||||
return false;
|
||||
}
|
||||
if (old_mode != nullptr) {
|
||||
*old_mode = GetProtectionFlag(old_protect);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SysVirtualFlushInstructionCache(uint64_t address, uint64_t size) {
|
||||
if (::FlushInstructionCache(GetCurrentProcess(),
|
||||
reinterpret_cast<LPVOID>(static_cast<uintptr_t>(address)),
|
||||
size) == 0) {
|
||||
printf("FlushInstructionCache() failed: 0x%08" PRIx32 "\n",
|
||||
static_cast<uint32_t>(GetLastError()));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace Common
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,89 @@
|
||||
#include "common/profiler.h"
|
||||
|
||||
#include "common/emulatorConfig.h"
|
||||
#include "common/subsystems.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <common/TracyProtocol.hpp>
|
||||
#include <common/TracyVersion.hpp>
|
||||
#include <cstdio>
|
||||
#include <tracy/Tracy.hpp>
|
||||
#include <vector>
|
||||
|
||||
namespace {
|
||||
|
||||
thread_local std::vector<Profiler::ScopedBlock*> g_block_stack;
|
||||
|
||||
void RemoveBlock(Profiler::ScopedBlock* block) {
|
||||
auto block_it = std::find(g_block_stack.rbegin(), g_block_stack.rend(), block);
|
||||
if (block_it != g_block_stack.rend()) {
|
||||
g_block_stack.erase(std::next(block_it).base());
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace Profiler {
|
||||
|
||||
ScopedBlock::ScopedBlock(const tracy::SourceLocationData* source_location) {
|
||||
if (tracy::ProfilerAvailable()) {
|
||||
m_zone.emplace(source_location, TRACY_CALLSTACK, true);
|
||||
g_block_stack.push_back(this);
|
||||
}
|
||||
}
|
||||
|
||||
ScopedBlock::~ScopedBlock() {
|
||||
End();
|
||||
}
|
||||
|
||||
void ScopedBlock::End() {
|
||||
if (m_zone.has_value()) {
|
||||
m_zone.reset();
|
||||
RemoveBlock(this);
|
||||
}
|
||||
}
|
||||
|
||||
void EndBlock() {
|
||||
if (!g_block_stack.empty()) {
|
||||
g_block_stack.back()->End();
|
||||
}
|
||||
}
|
||||
|
||||
void SetThreadName(const char* name) {
|
||||
if (tracy::ProfilerAvailable() && name != nullptr) {
|
||||
tracy::SetThreadName(name);
|
||||
}
|
||||
}
|
||||
|
||||
void Close() {
|
||||
if (tracy::ProfilerAvailable()) {
|
||||
tracy::ShutdownProfiler();
|
||||
}
|
||||
}
|
||||
|
||||
KYTY_SUBSYSTEM_INIT(Profiler) {
|
||||
switch (Config::GetProfilerDirection()) {
|
||||
case Config::ProfilerDirection::Network:
|
||||
if (!tracy::ProfilerAvailable()) {
|
||||
tracy::StartupProfiler();
|
||||
TracySetProgramName("KytyPS5");
|
||||
::printf("Tracy profiler enabled: client %d.%d.%d, protocol %u, "
|
||||
"broadcast %u, connect to 127.0.0.1:8086\n",
|
||||
tracy::Version::Major, tracy::Version::Minor, tracy::Version::Patch,
|
||||
tracy::ProtocolVersion, tracy::BroadcastVersion);
|
||||
}
|
||||
break;
|
||||
case Config::ProfilerDirection::None:
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
KYTY_SUBSYSTEM_UNEXPECTED_SHUTDOWN(Profiler) {
|
||||
Close();
|
||||
}
|
||||
|
||||
KYTY_SUBSYSTEM_DESTROY(Profiler) {
|
||||
Close();
|
||||
}
|
||||
|
||||
} // namespace Profiler
|
||||
@@ -0,0 +1,78 @@
|
||||
#ifndef KYTY_COMMON_PROFILER_H_
|
||||
#define KYTY_COMMON_PROFILER_H_
|
||||
|
||||
#include "common/common.h"
|
||||
#include "common/subsystems.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
#include <tracy/Tracy.hpp> // IWYU pragma: export
|
||||
|
||||
namespace profiler::colors {
|
||||
|
||||
inline constexpr uint32_t RedA100 = 0xff8a80;
|
||||
inline constexpr uint32_t Blue300 = 0x64b5f6;
|
||||
inline constexpr uint32_t CyanA700 = 0x00b8d4;
|
||||
inline constexpr uint32_t Green100 = 0xc8e6c9;
|
||||
inline constexpr uint32_t Green200 = 0xa5d6a7;
|
||||
inline constexpr uint32_t Green300 = 0x81c784;
|
||||
inline constexpr uint32_t Green400 = 0x66bb6a;
|
||||
inline constexpr uint32_t Amber300 = 0xffd54f;
|
||||
inline constexpr uint32_t DeepOrangeA200 = 0xff6e40;
|
||||
|
||||
} // namespace profiler::colors
|
||||
|
||||
namespace Profiler {
|
||||
|
||||
class ScopedBlock {
|
||||
public:
|
||||
explicit ScopedBlock(const tracy::SourceLocationData* source_location);
|
||||
ScopedBlock(const ScopedBlock&) = delete;
|
||||
ScopedBlock& operator=(const ScopedBlock&) = delete;
|
||||
ScopedBlock(ScopedBlock&&) = delete;
|
||||
ScopedBlock& operator=(ScopedBlock&&) = delete;
|
||||
~ScopedBlock();
|
||||
|
||||
void End();
|
||||
|
||||
private:
|
||||
std::optional<tracy::ScopedZone> m_zone;
|
||||
};
|
||||
|
||||
void EndBlock();
|
||||
void SetThreadName(const char* name);
|
||||
|
||||
KYTY_SUBSYSTEM_DEFINE(Profiler);
|
||||
|
||||
} // namespace Profiler
|
||||
|
||||
#define KYTY_PROFILER_CONCAT_IMPL(a, b) a##b
|
||||
#define KYTY_PROFILER_CONCAT(a, b) KYTY_PROFILER_CONCAT_IMPL(a, b)
|
||||
#define KYTY_PROFILER_COLOR_OR_DEFAULT(default_color, ...) \
|
||||
KYTY_PROFILER_COLOR_OR_DEFAULT_IMPL(default_color __VA_OPT__(, ) __VA_ARGS__, default_color)
|
||||
#define KYTY_PROFILER_COLOR_OR_DEFAULT_IMPL(default_color, color, ...) color
|
||||
|
||||
#define KYTY_PROFILER_BLOCK(name, ...) \
|
||||
KYTY_PROFILER_BLOCK_IMPL(__LINE__, name __VA_OPT__(, ) __VA_ARGS__)
|
||||
#define KYTY_PROFILER_BLOCK_IMPL(line, name, ...) \
|
||||
static constexpr tracy::SourceLocationData KYTY_PROFILER_CONCAT( \
|
||||
kyty_profiler_source_location_, line) {name, TracyFunction, TracyFile, \
|
||||
static_cast<uint32_t>(line), \
|
||||
KYTY_PROFILER_COLOR_OR_DEFAULT(0, __VA_ARGS__)}; \
|
||||
Profiler::ScopedBlock KYTY_PROFILER_CONCAT(kyty_profiler_block_, line)( \
|
||||
&KYTY_PROFILER_CONCAT(kyty_profiler_source_location_, line))
|
||||
|
||||
#define KYTY_PROFILER_FUNCTION(...) KYTY_PROFILER_FUNCTION_IMPL(__LINE__ __VA_OPT__(, ) __VA_ARGS__)
|
||||
#define KYTY_PROFILER_FUNCTION_IMPL(line, ...) \
|
||||
static constexpr tracy::SourceLocationData KYTY_PROFILER_CONCAT( \
|
||||
kyty_profiler_source_location_, line) {nullptr, TracyFunction, TracyFile, \
|
||||
static_cast<uint32_t>(line), \
|
||||
KYTY_PROFILER_COLOR_OR_DEFAULT(0, __VA_ARGS__)}; \
|
||||
Profiler::ScopedBlock KYTY_PROFILER_CONCAT(kyty_profiler_block_, line)( \
|
||||
&KYTY_PROFILER_CONCAT(kyty_profiler_source_location_, line))
|
||||
|
||||
#define KYTY_PROFILER_END_BLOCK Profiler::EndBlock()
|
||||
|
||||
#define KYTY_PROFILER_THREAD(name) Profiler::SetThreadName(name)
|
||||
|
||||
#endif /* KYTY_COMMON_PROFILER_H_ */
|
||||
@@ -0,0 +1,36 @@
|
||||
#ifndef KYTY_COMMON_SINGLETON_H_
|
||||
#define KYTY_COMMON_SINGLETON_H_
|
||||
|
||||
#include <cstdlib>
|
||||
#include <new>
|
||||
|
||||
namespace Common {
|
||||
|
||||
template <class T>
|
||||
class Singleton {
|
||||
public:
|
||||
static T* Instance() {
|
||||
if (!g_m_instance) {
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-no-malloc,hicpp-no-malloc)
|
||||
g_m_instance = static_cast<T*>(std::malloc(sizeof(T)));
|
||||
new (g_m_instance) T;
|
||||
}
|
||||
|
||||
return g_m_instance;
|
||||
}
|
||||
|
||||
KYTY_CLASS_NO_COPY(Singleton);
|
||||
|
||||
protected:
|
||||
Singleton();
|
||||
~Singleton();
|
||||
|
||||
private:
|
||||
static inline T* g_m_instance = nullptr;
|
||||
};
|
||||
|
||||
// template<class T> T* Singleton<T>::instance = 0;
|
||||
|
||||
} // namespace Common
|
||||
|
||||
#endif /* KYTY_COMMON_SINGLETON_H_ */
|
||||
@@ -0,0 +1,344 @@
|
||||
#ifndef KYTY_COMMON_STRING_UTILS_H_
|
||||
#define KYTY_COMMON_STRING_UTILS_H_
|
||||
|
||||
#include "common/byteBuffer.h"
|
||||
#include "common/common.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <charconv>
|
||||
#include <codecvt>
|
||||
#include <filesystem>
|
||||
#include <fmt/format.h>
|
||||
#include <locale>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <system_error>
|
||||
#include <vector>
|
||||
|
||||
namespace Common {
|
||||
|
||||
constexpr uint32_t FIND_INVALID_INDEX = static_cast<uint32_t>(-1);
|
||||
|
||||
inline std::string PathToString(const std::filesystem::path& path) {
|
||||
#if defined(__cpp_char8_t)
|
||||
auto u8 = path.u8string();
|
||||
return {u8.begin(), u8.end()};
|
||||
#else
|
||||
return path.u8string();
|
||||
#endif
|
||||
}
|
||||
|
||||
inline std::string PathToGenericString(const std::filesystem::path& path) {
|
||||
#if defined(__cpp_char8_t)
|
||||
auto u8 = path.generic_u8string();
|
||||
return {u8.begin(), u8.end()};
|
||||
#else
|
||||
return path.generic_u8string();
|
||||
#endif
|
||||
}
|
||||
|
||||
inline bool IsSpace(char c) {
|
||||
return std::isspace(static_cast<unsigned char>(c)) != 0;
|
||||
}
|
||||
|
||||
inline bool EqualNoCase(std::string_view lhs, std::string_view rhs) {
|
||||
if (lhs.size() != rhs.size()) {
|
||||
return false;
|
||||
}
|
||||
for (size_t i = 0; i < lhs.size(); i++) {
|
||||
auto l = static_cast<unsigned char>(lhs[i]);
|
||||
auto r = static_cast<unsigned char>(rhs[i]);
|
||||
if (std::tolower(l) != std::tolower(r)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
inline std::string ToLower(std::string_view text) {
|
||||
std::string ret(text);
|
||||
std::transform(ret.begin(), ret.end(), ret.begin(),
|
||||
[](unsigned char c) { return static_cast<char>(std::tolower(c)); });
|
||||
return ret;
|
||||
}
|
||||
|
||||
inline bool ContainsStr(std::string_view text, std::string_view needle) {
|
||||
return text.find(needle) != std::string_view::npos;
|
||||
}
|
||||
|
||||
inline bool StartsWith(std::string_view text, std::string_view prefix) {
|
||||
return text.starts_with(prefix);
|
||||
}
|
||||
|
||||
inline bool StartsWith(std::string_view text, char prefix) {
|
||||
return !text.empty() && text.front() == prefix;
|
||||
}
|
||||
|
||||
inline bool EndsWith(std::string_view text, std::string_view suffix) {
|
||||
return text.ends_with(suffix);
|
||||
}
|
||||
|
||||
inline bool EndsWith(std::string_view text, char suffix) {
|
||||
return !text.empty() && text.back() == suffix;
|
||||
}
|
||||
|
||||
inline bool ContainsChar(std::string_view text, char needle) {
|
||||
return text.find(needle) != std::string_view::npos;
|
||||
}
|
||||
|
||||
inline bool IndexValid(std::string_view text, uint32_t index) {
|
||||
return index < text.size();
|
||||
}
|
||||
|
||||
inline uint32_t FindIndex(std::string_view text, std::string_view needle, size_t from = 0) {
|
||||
const auto pos = text.find(needle, from);
|
||||
return pos == std::string_view::npos ? FIND_INVALID_INDEX : static_cast<uint32_t>(pos);
|
||||
}
|
||||
|
||||
inline uint32_t FindIndex(std::string_view text, char needle, size_t from = 0) {
|
||||
const auto pos = text.find(needle, from);
|
||||
return pos == std::string_view::npos ? FIND_INVALID_INDEX : static_cast<uint32_t>(pos);
|
||||
}
|
||||
|
||||
inline uint32_t FindLastIndex(std::string_view text, std::string_view needle,
|
||||
size_t from = FIND_INVALID_INDEX) {
|
||||
if (needle.empty()) {
|
||||
return 0;
|
||||
}
|
||||
if (from == FIND_INVALID_INDEX || from > text.size()) {
|
||||
from = text.size();
|
||||
}
|
||||
const auto pos = text.rfind(needle, from);
|
||||
return pos == std::string_view::npos ? FIND_INVALID_INDEX : static_cast<uint32_t>(pos);
|
||||
}
|
||||
|
||||
inline uint32_t FindLastIndex(std::string_view text, char needle,
|
||||
size_t from = FIND_INVALID_INDEX) {
|
||||
if (from == FIND_INVALID_INDEX || from > text.size()) {
|
||||
from = text.size();
|
||||
}
|
||||
const auto pos = text.rfind(needle, from);
|
||||
return pos == std::string_view::npos ? FIND_INVALID_INDEX : static_cast<uint32_t>(pos);
|
||||
}
|
||||
|
||||
inline std::string Mid(std::string_view text, size_t first, size_t count = std::string_view::npos) {
|
||||
if (first >= text.size()) {
|
||||
return {};
|
||||
}
|
||||
return std::string(text.substr(first, count));
|
||||
}
|
||||
|
||||
inline std::string Left(std::string_view text, size_t count) {
|
||||
return Mid(text, 0, count);
|
||||
}
|
||||
|
||||
inline std::string Right(std::string_view text, size_t count) {
|
||||
if (count >= text.size()) {
|
||||
return std::string(text);
|
||||
}
|
||||
return Mid(text, text.size() - count, count);
|
||||
}
|
||||
|
||||
inline std::string RemoveLast(std::string_view text, size_t count) {
|
||||
if (count >= text.size()) {
|
||||
return {};
|
||||
}
|
||||
return Left(text, text.size() - count);
|
||||
}
|
||||
|
||||
inline std::string RemoveFirst(std::string_view text, size_t count) {
|
||||
if (count >= text.size()) {
|
||||
return {};
|
||||
}
|
||||
return Right(text, text.size() - count);
|
||||
}
|
||||
|
||||
inline std::string TrimRight(std::string_view text) {
|
||||
size_t end = text.size();
|
||||
while (end > 0 && IsSpace(text[end - 1])) {
|
||||
end--;
|
||||
}
|
||||
return std::string(text.substr(0, end));
|
||||
}
|
||||
|
||||
inline std::string TrimLeft(std::string_view text) {
|
||||
size_t first = 0;
|
||||
while (first < text.size() && IsSpace(text[first])) {
|
||||
first++;
|
||||
}
|
||||
return std::string(text.substr(first));
|
||||
}
|
||||
|
||||
inline std::string Trim(std::string_view text) {
|
||||
return TrimRight(TrimLeft(text));
|
||||
}
|
||||
|
||||
inline std::string ReplaceChar(std::string_view text, char old_char, char new_char) {
|
||||
std::string ret(text);
|
||||
std::replace(ret.begin(), ret.end(), old_char, new_char);
|
||||
return ret;
|
||||
}
|
||||
|
||||
inline std::string ReplaceStr(std::string text, std::string_view old_str,
|
||||
std::string_view new_str) {
|
||||
if (old_str.empty()) {
|
||||
return text;
|
||||
}
|
||||
|
||||
size_t pos = 0;
|
||||
while ((pos = text.find(old_str, pos)) != std::string::npos) {
|
||||
text.replace(pos, old_str.size(), new_str);
|
||||
pos += new_str.size();
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
inline std::string DirectoryWithoutFilename(std::string_view text) {
|
||||
const auto pos = text.find_last_of('/');
|
||||
return pos == std::string_view::npos ? std::string() : std::string(text.substr(0, pos + 1));
|
||||
}
|
||||
|
||||
inline std::string FilenameWithoutDirectory(std::string_view text) {
|
||||
const auto pos = text.find_last_of('/');
|
||||
return pos == std::string_view::npos ? std::string(text) : std::string(text.substr(pos + 1));
|
||||
}
|
||||
|
||||
inline std::string FilenameWithoutExtension(std::string_view text) {
|
||||
const auto pos = text.find_last_of('.');
|
||||
return pos == std::string_view::npos ? std::string(text) : std::string(text.substr(0, pos));
|
||||
}
|
||||
|
||||
inline std::string ExtensionWithoutFilename(std::string_view text) {
|
||||
const auto pos = text.find_last_of('.');
|
||||
return pos == std::string_view::npos ? std::string() : std::string(text.substr(pos));
|
||||
}
|
||||
|
||||
inline std::string FixFilenameSlash(std::string_view text) {
|
||||
return ReplaceChar(text, '\\', '/');
|
||||
}
|
||||
|
||||
inline std::string FixDirectorySlash(std::string_view text) {
|
||||
auto ret = FixFilenameSlash(text);
|
||||
if (!ret.ends_with('/')) {
|
||||
ret += '/';
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
inline std::vector<std::string> Split(std::string_view text, std::string_view sep,
|
||||
bool keep_empty = false) {
|
||||
std::vector<std::string> ret;
|
||||
if (sep.empty()) {
|
||||
for (char c: text) {
|
||||
ret.emplace_back(1, c);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
size_t start = 0;
|
||||
for (;;) {
|
||||
const auto pos = text.find(sep, start);
|
||||
if (pos == std::string_view::npos) {
|
||||
break;
|
||||
}
|
||||
if (keep_empty || pos != start) {
|
||||
ret.emplace_back(text.substr(start, pos - start));
|
||||
}
|
||||
start = pos + sep.size();
|
||||
}
|
||||
if (keep_empty || start != text.size()) {
|
||||
ret.emplace_back(text.substr(start));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
inline std::vector<std::string> Split(std::string_view text, char sep, bool keep_empty = false) {
|
||||
return Split(text, std::string_view(&sep, 1), keep_empty);
|
||||
}
|
||||
|
||||
inline std::string Concat(const std::vector<std::string>& list, std::string_view sep) {
|
||||
std::string ret;
|
||||
for (size_t i = 0; i < list.size(); i++) {
|
||||
if (i != 0) {
|
||||
ret += sep;
|
||||
}
|
||||
ret += list[i];
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
inline std::string Concat(const std::vector<std::string>& list, char sep) {
|
||||
return Concat(list, std::string_view(&sep, 1));
|
||||
}
|
||||
|
||||
inline uint32_t ToUint32(std::string_view text, int base = 10) {
|
||||
uint32_t value = 0;
|
||||
std::from_chars(text.data(), text.data() + text.size(), value, base);
|
||||
return value;
|
||||
}
|
||||
|
||||
inline uint64_t ToUint64(std::string_view text, int base = 10) {
|
||||
uint64_t value = 0;
|
||||
std::from_chars(text.data(), text.data() + text.size(), value, base);
|
||||
return value;
|
||||
}
|
||||
|
||||
inline int32_t ToInt32(std::string_view text, int base = 10) {
|
||||
int32_t value = 0;
|
||||
std::from_chars(text.data(), text.data() + text.size(), value, base);
|
||||
return value;
|
||||
}
|
||||
|
||||
inline int64_t ToInt64(std::string_view text, int base = 10) {
|
||||
int64_t value = 0;
|
||||
std::from_chars(text.data(), text.data() + text.size(), value, base);
|
||||
return value;
|
||||
}
|
||||
|
||||
inline double ToDouble(std::string_view text) {
|
||||
return std::stod(std::string(text));
|
||||
}
|
||||
|
||||
inline float ToFloat(std::string_view text) {
|
||||
return std::stof(std::string(text));
|
||||
}
|
||||
|
||||
inline std::string SafeCsv(std::string_view text) {
|
||||
const bool add_space =
|
||||
!text.empty() && (text.front() == '+' || text.front() == '=' || text.front() == '-');
|
||||
if (text.find('"') != std::string_view::npos || text.find(';') != std::string_view::npos ||
|
||||
text.find('+') != std::string_view::npos || text.find('=') != std::string_view::npos ||
|
||||
text.find('-') != std::string_view::npos) {
|
||||
return "\"" + std::string(add_space ? " " : "") +
|
||||
ReplaceStr(std::string(text), "\"", "\"\"") + "\"";
|
||||
}
|
||||
return std::string(text);
|
||||
}
|
||||
|
||||
inline std::string Utf16ToUtf8(const char16_t* utf16) {
|
||||
std::u16string input(utf16);
|
||||
std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t> convert;
|
||||
return convert.to_bytes(input);
|
||||
}
|
||||
|
||||
inline ByteBuffer HexToBin(std::string_view text) {
|
||||
ByteBuffer ret;
|
||||
for (size_t i = 0; i + 1 < text.size(); i += 2) {
|
||||
ret.Add(static_cast<Byte>((ToUint32(text.substr(i, 2), 16) & 0xffu)));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
inline std::string HexFromBin(const ByteBuffer& bin) {
|
||||
std::string ret;
|
||||
ret.reserve(bin.Size() * 2);
|
||||
for (uint32_t i = 0; i < bin.Size(); i++) {
|
||||
ret += fmt::format("{:02X}", std::to_integer<uint8_t>(bin.At(i)));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
} // namespace Common
|
||||
|
||||
#endif /* KYTY_COMMON_STRING_UTILS_H_ */
|
||||
@@ -0,0 +1,232 @@
|
||||
#include "common/subsystems.h"
|
||||
|
||||
#include "common/assert.h"
|
||||
|
||||
#include <cstdarg>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
namespace Common {
|
||||
|
||||
class SubsystemPrivate {
|
||||
public:
|
||||
SubsystemPrivate() = default;
|
||||
virtual ~SubsystemPrivate() = default;
|
||||
|
||||
KYTY_CLASS_NO_COPY(SubsystemPrivate);
|
||||
|
||||
bool failed {false};
|
||||
std::string fail_msg;
|
||||
};
|
||||
|
||||
class SubsystemsListPrivate {
|
||||
public:
|
||||
explicit SubsystemsListPrivate(SubsystemsList& p): parent(p) {}
|
||||
|
||||
virtual ~SubsystemsListPrivate() = default;
|
||||
|
||||
void SetArgs(int argc, char** argv) {
|
||||
this->m_argc = argc;
|
||||
this->m_argv = argv;
|
||||
}
|
||||
|
||||
void Add(Subsystem* s, std::initializer_list<Subsystem*> deps) {
|
||||
EXIT_IF(!s);
|
||||
|
||||
const char* name = s->Id();
|
||||
|
||||
EXIT_IF(FindByName(name) != nullptr);
|
||||
|
||||
auto nl = std::make_unique<SubsListStruct>();
|
||||
|
||||
nl->s = s;
|
||||
nl->name = name;
|
||||
nl->deps = nullptr;
|
||||
nl->next = std::move(list);
|
||||
nl->prev_init = nullptr;
|
||||
|
||||
auto* node = nl.get();
|
||||
list = std::move(nl);
|
||||
|
||||
for (auto* dep: deps) {
|
||||
const char* str = dep->Id();
|
||||
|
||||
auto l = std::make_unique<DepsListStruct>();
|
||||
l->dep_name = str;
|
||||
l->next = std::move(node->deps);
|
||||
|
||||
node->deps = std::move(l);
|
||||
}
|
||||
|
||||
node->initialized = false;
|
||||
}
|
||||
|
||||
bool InitAll(bool print_msg) {
|
||||
while (SubsListStruct* n = FindNextToInitialize()) {
|
||||
n->s->Init(&parent);
|
||||
|
||||
if (n->s->m_p->failed) {
|
||||
fail_msg = n->s->m_p->fail_msg.c_str();
|
||||
fail_name = n->name;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (print_msg) {
|
||||
printf("Initialized: %s\n", n->name);
|
||||
}
|
||||
|
||||
n->initialized = true;
|
||||
|
||||
SubsListStruct* last = last_init;
|
||||
last_init = n;
|
||||
n->prev_init = last;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void DestroyAll(bool print_msg) {
|
||||
for (SubsListStruct* n = last_init; n != nullptr; n = n->prev_init) {
|
||||
n->s->Destroy(&parent);
|
||||
n->initialized = false;
|
||||
|
||||
if (print_msg) {
|
||||
printf("Destroyed: %s\n", n->name);
|
||||
}
|
||||
}
|
||||
|
||||
last_init = nullptr;
|
||||
}
|
||||
|
||||
void ShutdownAll() {
|
||||
for (SubsListStruct* n = last_init; n != nullptr; n = n->prev_init) {
|
||||
n->s->UnexpectedShutdown(&parent);
|
||||
n->initialized = false;
|
||||
}
|
||||
|
||||
last_init = nullptr;
|
||||
}
|
||||
|
||||
struct DepsListStruct {
|
||||
const char* dep_name;
|
||||
std::unique_ptr<DepsListStruct> next;
|
||||
};
|
||||
|
||||
struct SubsListStruct {
|
||||
Subsystem* s;
|
||||
const char* name;
|
||||
std::unique_ptr<DepsListStruct> deps;
|
||||
std::unique_ptr<SubsListStruct> next;
|
||||
SubsListStruct* prev_init;
|
||||
bool initialized;
|
||||
};
|
||||
|
||||
[[nodiscard]] SubsListStruct* FindByName(const char* name) const {
|
||||
for (SubsListStruct* n = list.get(); n != nullptr; n = n->next.get()) {
|
||||
if (std::strcmp(n->name, name) == 0) {
|
||||
return n;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
[[nodiscard]] SubsListStruct* FindNextToInitialize() const {
|
||||
for (SubsListStruct* n = list.get(); n != nullptr; n = n->next.get()) {
|
||||
if (n->initialized) {
|
||||
continue;
|
||||
}
|
||||
|
||||
DepsListStruct* d = n->deps.get();
|
||||
for (; d != nullptr; d = d->next.get()) {
|
||||
SubsListStruct* s = FindByName(d->dep_name);
|
||||
if ((s == nullptr) || !s->initialized) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (d == nullptr) {
|
||||
return n;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
KYTY_CLASS_NO_COPY(SubsystemsListPrivate);
|
||||
|
||||
std::unique_ptr<SubsListStruct> list;
|
||||
SubsListStruct* last_init = nullptr;
|
||||
int m_argc = 0;
|
||||
char** m_argv = nullptr;
|
||||
const char* fail_msg = nullptr;
|
||||
const char* fail_name = nullptr;
|
||||
SubsystemsList& parent;
|
||||
};
|
||||
|
||||
SubsystemsList::SubsystemsList(): m_p(std::make_unique<SubsystemsListPrivate>(*this)) {}
|
||||
|
||||
SubsystemsList::~SubsystemsList() = default;
|
||||
|
||||
void SubsystemsList::Add(Subsystem* s, std::initializer_list<Subsystem*> deps) {
|
||||
m_p->Add(s, deps);
|
||||
}
|
||||
|
||||
bool SubsystemsList::InitAll(bool print_msg) {
|
||||
return m_p->InitAll(print_msg);
|
||||
}
|
||||
|
||||
void SubsystemsList::DestroyAll(bool print_msg) {
|
||||
m_p->DestroyAll(print_msg);
|
||||
}
|
||||
|
||||
int* SubsystemsList::GetArgc() {
|
||||
return &m_p->m_argc;
|
||||
}
|
||||
|
||||
char** SubsystemsList::GetArgv() {
|
||||
return m_p->m_argv;
|
||||
}
|
||||
|
||||
Subsystem::Subsystem(): m_p(std::make_unique<SubsystemPrivate>()) {}
|
||||
|
||||
Subsystem::~Subsystem() = default;
|
||||
|
||||
void Subsystem::Fail(const char* format, ...) {
|
||||
va_list args {};
|
||||
va_start(args, format);
|
||||
|
||||
va_list args_copy {};
|
||||
va_copy(args_copy, args);
|
||||
int len = std::vsnprintf(nullptr, 0, format, args_copy);
|
||||
va_end(args_copy);
|
||||
|
||||
if (len > 0) {
|
||||
std::string msg(static_cast<size_t>(len) + 1, '\0');
|
||||
std::vsnprintf(msg.data(), msg.size(), format, args);
|
||||
m_p->fail_msg = msg.c_str();
|
||||
m_p->failed = true;
|
||||
}
|
||||
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
const char* SubsystemsList::GetFailName() const {
|
||||
return m_p->fail_name;
|
||||
}
|
||||
|
||||
const char* SubsystemsList::GetFailMsg() const {
|
||||
return m_p->fail_msg;
|
||||
}
|
||||
|
||||
void SubsystemsList::SetArgs(int argc, char* argv[]) {
|
||||
m_p->SetArgs(argc, argv);
|
||||
}
|
||||
|
||||
void SubsystemsList::ShutdownAll() {
|
||||
m_p->ShutdownAll();
|
||||
}
|
||||
|
||||
} // namespace Common
|
||||
@@ -0,0 +1,91 @@
|
||||
#ifndef KYTY_COMMON_SUBSYSTEMS_H_
|
||||
#define KYTY_COMMON_SUBSYSTEMS_H_
|
||||
|
||||
#include "common/common.h"
|
||||
#include "common/singleton.h"
|
||||
|
||||
#include <initializer_list>
|
||||
#include <memory>
|
||||
|
||||
namespace Common {
|
||||
|
||||
class Subsystem;
|
||||
class SubsystemsListPrivate;
|
||||
class SubsystemPrivate;
|
||||
|
||||
class SubsystemsList {
|
||||
public:
|
||||
SubsystemsList();
|
||||
virtual ~SubsystemsList();
|
||||
void SetArgs(int argc, char* argv[]);
|
||||
|
||||
// void Add(Subsystem* s, const char* name, ...);
|
||||
void Add(Subsystem* s, std::initializer_list<Subsystem*> deps);
|
||||
|
||||
bool InitAll(bool print_msg = false);
|
||||
void DestroyAll(bool print_msg = false);
|
||||
|
||||
int* GetArgc();
|
||||
char** GetArgv();
|
||||
|
||||
[[nodiscard]] const char* GetFailName() const;
|
||||
[[nodiscard]] const char* GetFailMsg() const;
|
||||
|
||||
void ShutdownAll();
|
||||
|
||||
static SubsystemsList* Instance() { return Common::Singleton<SubsystemsList>::Instance(); }
|
||||
|
||||
KYTY_CLASS_NO_COPY(SubsystemsList);
|
||||
|
||||
private:
|
||||
std::unique_ptr<SubsystemsListPrivate> m_p;
|
||||
};
|
||||
|
||||
using SubsystemsListSingleton = Common::Singleton<SubsystemsList>;
|
||||
|
||||
class Subsystem {
|
||||
public:
|
||||
Subsystem();
|
||||
virtual ~Subsystem();
|
||||
|
||||
virtual const char* Id() = 0;
|
||||
virtual void Init(SubsystemsList* parent) = 0;
|
||||
virtual void Destroy(SubsystemsList* parent) = 0;
|
||||
virtual void UnexpectedShutdown(SubsystemsList* parent) = 0;
|
||||
|
||||
friend class SubsystemsListPrivate;
|
||||
|
||||
KYTY_CLASS_NO_COPY(Subsystem);
|
||||
|
||||
protected:
|
||||
void Fail(const char* format, ...) KYTY_FORMAT_PRINTF(2, 3);
|
||||
|
||||
private:
|
||||
std::unique_ptr<SubsystemPrivate> m_p;
|
||||
};
|
||||
|
||||
#define KYTY_SUBSYSTEM_DEFINE(s) \
|
||||
class s##Subsystem: public Common::Subsystem { \
|
||||
public: \
|
||||
static Subsystem* Instance() { return Common::Singleton<s##Subsystem>::Instance(); } \
|
||||
const char* Id() { return #s; } \
|
||||
void Init(Common::SubsystemsList* parent); \
|
||||
void Destroy(Common::SubsystemsList* parent); \
|
||||
void UnexpectedShutdown(Common::SubsystemsList* parent); \
|
||||
}; \
|
||||
typedef Common::Singleton<s##Subsystem> s##SubsystemSingleton;
|
||||
|
||||
#define KYTY_SUBSYSTEM_INIT(s) \
|
||||
void s##Subsystem::Init([[maybe_unused]] Common::SubsystemsList* parent)
|
||||
#define KYTY_SUBSYSTEM_DESTROY(s) \
|
||||
void s##Subsystem::Destroy([[maybe_unused]] Common::SubsystemsList* parent)
|
||||
#define KYTY_SUBSYSTEM_UNEXPECTED_SHUTDOWN(s) \
|
||||
void s##Subsystem::UnexpectedShutdown([[maybe_unused]] Common::SubsystemsList* parent)
|
||||
|
||||
// #define KYTY_SUBSYSTEM_ADD(list, s, ...) list.push_back(s##SubsystemSingleton::Instance(), #s,
|
||||
// __VA_ARGS__); #define KYTY_SUBSYSTEM_ADD2(list, ...) list.Add2(s##SubsystemSingleton::Instance(),
|
||||
// __VA_ARGS__);
|
||||
|
||||
} // namespace Common
|
||||
|
||||
#endif /* KYTY_COMMON_SUBSYSTEMS_H_ */
|
||||
@@ -0,0 +1,18 @@
|
||||
#include "common/systemInfo.h"
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "cpuinfo.h"
|
||||
|
||||
namespace Common {
|
||||
|
||||
SystemInfo GetSystemInfo() {
|
||||
static const bool initialized = cpuinfo_initialize();
|
||||
EXIT_IF(!initialized);
|
||||
|
||||
const auto* package = cpuinfo_get_package(0);
|
||||
EXIT_IF(package == nullptr);
|
||||
|
||||
return {package->name};
|
||||
}
|
||||
|
||||
} // namespace Common
|
||||
@@ -0,0 +1,16 @@
|
||||
#ifndef KYTY_COMMON_SYSTEM_INFO_H_
|
||||
#define KYTY_COMMON_SYSTEM_INFO_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace Common {
|
||||
|
||||
struct SystemInfo {
|
||||
std::string ProcessorName;
|
||||
};
|
||||
|
||||
[[nodiscard]] SystemInfo GetSystemInfo();
|
||||
|
||||
} // namespace Common
|
||||
|
||||
#endif /* KYTY_COMMON_SYSTEM_INFO_H_ */
|
||||
@@ -0,0 +1,419 @@
|
||||
#include "common/threads.h"
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/debug.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <chrono> // IWYU pragma: keep
|
||||
#include <condition_variable> // IWYU pragma: keep
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS && KYTY_COMPILER == KYTY_COMPILER_CLANG
|
||||
#define KYTY_WIN_CS
|
||||
#endif
|
||||
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
|
||||
#ifdef KYTY_WIN_CS
|
||||
#include <windows.h> // IWYU pragma: keep
|
||||
// IWYU pragma: no_include <winbase.h>
|
||||
constexpr DWORD KYTY_CS_SPIN_COUNT = 4000;
|
||||
|
||||
#ifndef CREATE_WAITABLE_TIMER_HIGH_RESOLUTION
|
||||
#define CREATE_WAITABLE_TIMER_HIGH_RESOLUTION 0x00000002
|
||||
#endif
|
||||
|
||||
static void SleepHighResolution100ns(uint64_t units_100ns) {
|
||||
if (units_100ns == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (units_100ns <= 10000) {
|
||||
LARGE_INTEGER frequency {};
|
||||
LARGE_INTEGER start {};
|
||||
if (QueryPerformanceFrequency(&frequency) != 0 && QueryPerformanceCounter(&start) != 0 &&
|
||||
frequency.QuadPart > 0) {
|
||||
const auto wait_ticks =
|
||||
static_cast<LONGLONG>((static_cast<long double>(units_100ns) *
|
||||
static_cast<long double>(frequency.QuadPart)) /
|
||||
10000000.0L);
|
||||
const auto deadline = start.QuadPart + std::max<LONGLONG>(wait_ticks, 1);
|
||||
LARGE_INTEGER now {};
|
||||
do {
|
||||
if (QueryPerformanceCounter(&now) == 0) {
|
||||
break;
|
||||
}
|
||||
YieldProcessor();
|
||||
} while (now.QuadPart < deadline);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
thread_local HANDLE timer = CreateWaitableTimerExW(
|
||||
nullptr, nullptr, CREATE_WAITABLE_TIMER_HIGH_RESOLUTION, TIMER_ALL_ACCESS);
|
||||
if (timer == nullptr) {
|
||||
thread_local HANDLE fallback_timer = CreateWaitableTimerW(nullptr, TRUE, nullptr);
|
||||
timer = fallback_timer;
|
||||
}
|
||||
|
||||
if (timer != nullptr) {
|
||||
LARGE_INTEGER due_time {};
|
||||
due_time.QuadPart = -static_cast<LONGLONG>(units_100ns);
|
||||
if (SetWaitableTimerEx(timer, &due_time, 0, nullptr, nullptr, nullptr, 0) != 0) {
|
||||
WaitForSingleObject(timer, INFINITE);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::nanoseconds(units_100ns * 100));
|
||||
}
|
||||
|
||||
// IWYU pragma: no_include <minwindef.h>
|
||||
// IWYU pragma: no_include <synchapi.h>
|
||||
// IWYU pragma: no_include <minwinbase.h>
|
||||
// IWYU pragma: no_include <__mutex_base>
|
||||
// IWYU pragma: no_include <__threading_support>
|
||||
// IWYU pragma: no_include <errhandlingapi.h>
|
||||
// IWYU pragma: no_include <winerror.h>
|
||||
|
||||
using InitializeConditionVariable_func_t = /*WINBASEAPI*/ VOID WINAPI (*)(PCONDITION_VARIABLE);
|
||||
using WakeConditionVariable_func_t = /*WINBASEAPI*/ VOID WINAPI (*)(PCONDITION_VARIABLE);
|
||||
using WakeAllConditionVariable_func_t = /*WINBASEAPI*/ VOID WINAPI (*)(PCONDITION_VARIABLE);
|
||||
using SleepConditionVariableCS_func_t = /*WINBASEAPI*/ BOOL WINAPI (*)(PCONDITION_VARIABLE,
|
||||
PCRITICAL_SECTION, DWORD);
|
||||
|
||||
static InitializeConditionVariable_func_t ResolveInitializeConditionVariable() {
|
||||
if (HMODULE h = GetModuleHandle("KernelBase"); h != nullptr) // @suppress("Invalid arguments")
|
||||
{
|
||||
return reinterpret_cast<InitializeConditionVariable_func_t>(
|
||||
GetProcAddress(h, "InitializeConditionVariable"));
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
static WakeConditionVariable_func_t ResolveWakeConditionVariable() {
|
||||
if (HMODULE h = GetModuleHandle("KernelBase"); h != nullptr) // @suppress("Invalid arguments")
|
||||
{
|
||||
return reinterpret_cast<WakeConditionVariable_func_t>(
|
||||
GetProcAddress(h, "WakeConditionVariable"));
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
static WakeAllConditionVariable_func_t ResolveWakeAllConditionVariable() {
|
||||
if (HMODULE h = GetModuleHandle("KernelBase"); h != nullptr) // @suppress("Invalid arguments")
|
||||
{
|
||||
return reinterpret_cast<WakeAllConditionVariable_func_t>(
|
||||
GetProcAddress(h, "WakeAllConditionVariable"));
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
static SleepConditionVariableCS_func_t ResolveSleepConditionVariableCS() {
|
||||
if (HMODULE h = GetModuleHandle("KernelBase"); h != nullptr) // @suppress("Invalid arguments")
|
||||
{
|
||||
return reinterpret_cast<SleepConditionVariableCS_func_t>(
|
||||
GetProcAddress(h, "SleepConditionVariableCS"));
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
namespace Common {
|
||||
|
||||
using thread_id_t = std::thread::id;
|
||||
|
||||
struct MutexPrivate {
|
||||
#ifdef KYTY_WIN_CS
|
||||
MutexPrivate() { InitializeCriticalSectionAndSpinCount(&m_cs, KYTY_CS_SPIN_COUNT); }
|
||||
~MutexPrivate() { DeleteCriticalSection(&m_cs); }
|
||||
KYTY_CLASS_NO_COPY(MutexPrivate);
|
||||
CRITICAL_SECTION m_cs {};
|
||||
#else
|
||||
std::recursive_mutex m_mutex;
|
||||
#endif
|
||||
};
|
||||
|
||||
struct CondVarPrivate {
|
||||
#ifdef KYTY_WIN_CS
|
||||
CondVarPrivate() {
|
||||
static auto func = ResolveInitializeConditionVariable();
|
||||
EXIT_NOT_IMPLEMENTED(func == nullptr);
|
||||
func(&m_cv);
|
||||
}
|
||||
~CondVarPrivate() = default;
|
||||
KYTY_CLASS_NO_COPY(CondVarPrivate);
|
||||
CONDITION_VARIABLE m_cv {};
|
||||
#else
|
||||
std::condition_variable_any m_cv;
|
||||
#endif
|
||||
};
|
||||
|
||||
static std::recursive_mutex g_cond_waiters_mutex;
|
||||
static std::vector<std::pair<int, CondVarPrivate*>> g_cond_waiters;
|
||||
static wait_poll_func_t g_cond_wait_poll_callback = nullptr;
|
||||
|
||||
static void WakeCondVar(CondVarPrivate* cond_var) {
|
||||
#ifdef KYTY_WIN_CS
|
||||
static auto func = ResolveWakeAllConditionVariable();
|
||||
EXIT_NOT_IMPLEMENTED(func == nullptr);
|
||||
func(&cond_var->m_cv);
|
||||
#else
|
||||
cond_var->m_cv.notify_all();
|
||||
#endif
|
||||
}
|
||||
|
||||
static void RegisterCondWaiter(CondVarPrivate* cond_var) {
|
||||
std::lock_guard lock(g_cond_waiters_mutex);
|
||||
g_cond_waiters.emplace_back(Thread::GetThreadIdUnique(), cond_var);
|
||||
}
|
||||
|
||||
static void UnregisterCondWaiter(CondVarPrivate* cond_var) {
|
||||
const auto thread_id = Thread::GetThreadIdUnique();
|
||||
std::lock_guard lock(g_cond_waiters_mutex);
|
||||
|
||||
const auto it = std::find_if(g_cond_waiters.begin(), g_cond_waiters.end(),
|
||||
[thread_id, cond_var](const auto& waiter) {
|
||||
return waiter.first == thread_id && waiter.second == cond_var;
|
||||
});
|
||||
if (it != g_cond_waiters.end()) {
|
||||
g_cond_waiters.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
struct ThreadPrivate {
|
||||
ThreadPrivate(thread_func_t f, void* a): func(f), arg(a), m_thread(&Run, this) {}
|
||||
|
||||
static void Run(ThreadPrivate* t) {
|
||||
t->unique_id = Thread::GetThreadIdUnique();
|
||||
t->started = true;
|
||||
t->func(t->arg);
|
||||
}
|
||||
|
||||
thread_func_t func;
|
||||
void* arg;
|
||||
std::atomic_bool finished = false;
|
||||
std::atomic_bool auto_delete = false;
|
||||
std::atomic_bool started = false;
|
||||
int unique_id = 0;
|
||||
std::thread m_thread;
|
||||
};
|
||||
|
||||
static thread_id_t g_main_thread;
|
||||
static int g_main_thread_int;
|
||||
static std::atomic<int> g_thread_counter = 0;
|
||||
|
||||
KYTY_SUBSYSTEM_INIT(Threads) {
|
||||
g_main_thread = std::this_thread::get_id();
|
||||
g_main_thread_int = Thread::GetThreadIdUnique();
|
||||
}
|
||||
|
||||
KYTY_SUBSYSTEM_UNEXPECTED_SHUTDOWN(Threads) {}
|
||||
|
||||
KYTY_SUBSYSTEM_DESTROY(Threads) {}
|
||||
|
||||
Thread::Thread(thread_func_t func, void* arg)
|
||||
: m_thread(std::make_unique<ThreadPrivate>(func, arg)) {
|
||||
while (!m_thread->started) {
|
||||
Common::Thread::SleepMicro(1000);
|
||||
}
|
||||
}
|
||||
|
||||
Thread::~Thread() {
|
||||
EXIT_IF(!m_thread->finished && !m_thread->auto_delete);
|
||||
|
||||
m_thread.reset();
|
||||
}
|
||||
|
||||
void Thread::Join() {
|
||||
EXIT_IF(m_thread->finished || m_thread->auto_delete);
|
||||
|
||||
m_thread->m_thread.join();
|
||||
|
||||
m_thread->finished = true;
|
||||
}
|
||||
|
||||
void Thread::Detach() {
|
||||
EXIT_IF(m_thread->finished || m_thread->auto_delete);
|
||||
|
||||
m_thread->auto_delete = true;
|
||||
m_thread->m_thread.detach();
|
||||
}
|
||||
|
||||
void Thread::Sleep(uint32_t millis) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(millis));
|
||||
}
|
||||
|
||||
void Thread::SleepMicro(uint32_t micros) {
|
||||
#ifdef KYTY_WIN_CS
|
||||
SleepHighResolution100ns(static_cast<uint64_t>(micros) * 10);
|
||||
#else
|
||||
std::this_thread::sleep_for(std::chrono::microseconds(micros));
|
||||
#endif
|
||||
}
|
||||
|
||||
void Thread::SleepNano(uint64_t nanos) {
|
||||
#ifdef KYTY_WIN_CS
|
||||
SleepHighResolution100ns((nanos + 99) / 100);
|
||||
#else
|
||||
std::this_thread::sleep_for(std::chrono::nanoseconds(nanos));
|
||||
#endif
|
||||
}
|
||||
|
||||
bool Thread::IsMainThread() {
|
||||
return g_main_thread == std::this_thread::get_id();
|
||||
}
|
||||
|
||||
std::string Thread::GetId() const {
|
||||
std::stringstream ss;
|
||||
ss << m_thread->m_thread.get_id();
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
int Thread::GetUniqueId() const {
|
||||
return m_thread->unique_id;
|
||||
}
|
||||
|
||||
std::string Thread::GetThreadId() {
|
||||
std::stringstream ss;
|
||||
ss << std::this_thread::get_id();
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
Mutex::Mutex(): m_mutex(std::make_unique<MutexPrivate>()) {}
|
||||
|
||||
Mutex::~Mutex() {
|
||||
m_mutex.reset();
|
||||
}
|
||||
|
||||
void Mutex::Lock() {
|
||||
#ifdef KYTY_WIN_CS
|
||||
EnterCriticalSection(&m_mutex->m_cs);
|
||||
#else
|
||||
m_mutex->m_mutex.lock();
|
||||
#endif
|
||||
}
|
||||
|
||||
void Mutex::Unlock() {
|
||||
#ifdef KYTY_WIN_CS
|
||||
LeaveCriticalSection(&m_mutex->m_cs);
|
||||
#else
|
||||
m_mutex->m_mutex.unlock();
|
||||
#endif
|
||||
}
|
||||
|
||||
bool Mutex::TryLock() {
|
||||
#ifdef KYTY_WIN_CS
|
||||
return (TryEnterCriticalSection(&m_mutex->m_cs) != 0);
|
||||
#else
|
||||
return m_mutex->m_mutex.try_lock();
|
||||
#endif
|
||||
}
|
||||
|
||||
CondVar::CondVar(): m_cond_var(std::make_unique<CondVarPrivate>()) {}
|
||||
|
||||
CondVar::~CondVar() {
|
||||
m_cond_var.reset();
|
||||
}
|
||||
|
||||
void CondVar::Wait(Mutex* mutex) {
|
||||
RegisterCondWaiter(m_cond_var.get());
|
||||
#ifndef KYTY_WIN_CS
|
||||
std::unique_lock<std::recursive_mutex> cpp_lock(mutex->m_mutex->m_mutex, std::adopt_lock_t());
|
||||
#endif
|
||||
auto poll_callback = [&] {
|
||||
auto* callback = g_cond_wait_poll_callback;
|
||||
if (callback == nullptr) {
|
||||
return;
|
||||
}
|
||||
#if defined(KYTY_WIN_CS)
|
||||
LeaveCriticalSection(&mutex->m_mutex->m_cs);
|
||||
callback();
|
||||
EnterCriticalSection(&mutex->m_mutex->m_cs);
|
||||
#else
|
||||
cpp_lock.unlock();
|
||||
callback();
|
||||
cpp_lock.lock();
|
||||
#endif
|
||||
};
|
||||
#ifdef KYTY_WIN_CS
|
||||
static auto func = ResolveSleepConditionVariableCS();
|
||||
EXIT_NOT_IMPLEMENTED(func == nullptr);
|
||||
if (g_cond_wait_poll_callback == nullptr) {
|
||||
func(&m_cond_var->m_cv, &mutex->m_mutex->m_cs, INFINITE);
|
||||
} else {
|
||||
if (func(&m_cond_var->m_cv, &mutex->m_mutex->m_cs, 10) == 0 &&
|
||||
GetLastError() == ERROR_TIMEOUT) {
|
||||
poll_callback();
|
||||
}
|
||||
}
|
||||
#else
|
||||
if (g_cond_wait_poll_callback == nullptr) {
|
||||
m_cond_var->m_cv.wait(cpp_lock);
|
||||
} else {
|
||||
if (m_cond_var->m_cv.wait_for(cpp_lock, std::chrono::microseconds(10000)) ==
|
||||
std::cv_status::timeout) {
|
||||
poll_callback();
|
||||
}
|
||||
}
|
||||
cpp_lock.release();
|
||||
#endif
|
||||
UnregisterCondWaiter(m_cond_var.get());
|
||||
}
|
||||
|
||||
void CondVar::SetWaitPollCallback(wait_poll_func_t callback) {
|
||||
g_cond_wait_poll_callback = callback;
|
||||
}
|
||||
|
||||
bool CondVar::WaitFor(Mutex* mutex, uint32_t micros) {
|
||||
bool ok = false;
|
||||
RegisterCondWaiter(m_cond_var.get());
|
||||
#ifndef KYTY_WIN_CS
|
||||
std::unique_lock<std::recursive_mutex> cpp_lock(mutex->m_mutex->m_mutex, std::adopt_lock_t());
|
||||
#endif
|
||||
#ifdef KYTY_WIN_CS
|
||||
static auto func = ResolveSleepConditionVariableCS();
|
||||
EXIT_NOT_IMPLEMENTED(func == nullptr);
|
||||
ok = !(func(&m_cond_var->m_cv, &mutex->m_mutex->m_cs, (micros < 1000 ? 1 : micros / 1000)) ==
|
||||
0 &&
|
||||
GetLastError() == ERROR_TIMEOUT);
|
||||
#else
|
||||
ok = (m_cond_var->m_cv.wait_for(cpp_lock, std::chrono::microseconds(micros)) ==
|
||||
std::cv_status::no_timeout);
|
||||
cpp_lock.release();
|
||||
#endif
|
||||
UnregisterCondWaiter(m_cond_var.get());
|
||||
return ok;
|
||||
}
|
||||
|
||||
void CondVar::Signal() {
|
||||
#ifdef KYTY_WIN_CS
|
||||
static auto func = ResolveWakeConditionVariable();
|
||||
EXIT_NOT_IMPLEMENTED(func == nullptr);
|
||||
func(&m_cond_var->m_cv);
|
||||
#else
|
||||
m_cond_var->m_cv.notify_one();
|
||||
#endif
|
||||
}
|
||||
|
||||
void CondVar::SignalAll() {
|
||||
WakeCondVar(m_cond_var.get());
|
||||
}
|
||||
|
||||
void CondVar::SignalThread(int thread_id) {
|
||||
std::lock_guard lock(g_cond_waiters_mutex);
|
||||
for (const auto& waiter: g_cond_waiters) {
|
||||
if (waiter.first == thread_id) {
|
||||
WakeCondVar(waiter.second);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int Thread::GetThreadIdUnique() {
|
||||
static thread_local int tid = ++g_thread_counter;
|
||||
return tid;
|
||||
}
|
||||
|
||||
} // namespace Common
|
||||
@@ -0,0 +1,107 @@
|
||||
#ifndef KYTY_COMMON_THREADS_H_
|
||||
#define KYTY_COMMON_THREADS_H_
|
||||
|
||||
#include "common/common.h"
|
||||
#include "common/subsystems.h"
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
namespace Common {
|
||||
|
||||
KYTY_SUBSYSTEM_DEFINE(Threads);
|
||||
|
||||
using thread_func_t = void (*)(void*);
|
||||
using wait_poll_func_t = void (*)();
|
||||
|
||||
struct ThreadPrivate;
|
||||
struct MutexPrivate;
|
||||
struct CondVarPrivate;
|
||||
|
||||
class Thread {
|
||||
public:
|
||||
Thread(thread_func_t func, void* arg);
|
||||
~Thread();
|
||||
|
||||
void Join();
|
||||
void Detach();
|
||||
|
||||
// Once a thread has finished, the id may be reused by another thread.
|
||||
[[nodiscard]] std::string GetId() const;
|
||||
|
||||
// The id is unique and can't be reused by another thread.
|
||||
[[nodiscard]] int GetUniqueId() const;
|
||||
|
||||
static void Sleep(uint32_t millis);
|
||||
static void SleepMicro(uint32_t micros);
|
||||
static void SleepNano(uint64_t nanos);
|
||||
static bool IsMainThread();
|
||||
|
||||
// Get current thread id
|
||||
// Once a thread has finished, the id may be reused by another thread.
|
||||
static std::string GetThreadId();
|
||||
|
||||
// Get current thread id
|
||||
// The id is unique and can't be reused by another thread.
|
||||
static int GetThreadIdUnique();
|
||||
|
||||
KYTY_CLASS_NO_COPY(Thread);
|
||||
|
||||
private:
|
||||
std::unique_ptr<ThreadPrivate> m_thread;
|
||||
};
|
||||
|
||||
class Mutex {
|
||||
public:
|
||||
Mutex();
|
||||
~Mutex();
|
||||
|
||||
void Lock();
|
||||
void Unlock();
|
||||
bool TryLock();
|
||||
|
||||
friend class CondVar;
|
||||
|
||||
KYTY_CLASS_NO_COPY(Mutex);
|
||||
|
||||
private:
|
||||
std::unique_ptr<MutexPrivate> m_mutex;
|
||||
};
|
||||
|
||||
class CondVar {
|
||||
public:
|
||||
CondVar();
|
||||
~CondVar();
|
||||
|
||||
void Wait(Mutex* mutex);
|
||||
bool WaitFor(Mutex* mutex, uint32_t micros);
|
||||
void Signal();
|
||||
void SignalAll();
|
||||
|
||||
static void SignalThread(int thread_id);
|
||||
static void SetWaitPollCallback(wait_poll_func_t callback);
|
||||
|
||||
KYTY_CLASS_NO_COPY(CondVar);
|
||||
|
||||
private:
|
||||
std::unique_ptr<CondVarPrivate> m_cond_var;
|
||||
};
|
||||
|
||||
class LockGuard {
|
||||
public:
|
||||
using mutex_type = Mutex;
|
||||
|
||||
// NOLINTNEXTLINE(google-runtime-references)
|
||||
explicit LockGuard(mutex_type& m): m_mutex(m) { m_mutex.Lock(); }
|
||||
|
||||
~LockGuard() { m_mutex.Unlock(); }
|
||||
|
||||
KYTY_CLASS_NO_COPY(LockGuard);
|
||||
|
||||
private:
|
||||
mutex_type& m_mutex;
|
||||
};
|
||||
|
||||
} // namespace Common
|
||||
|
||||
#endif /* KYTY_COMMON_THREADS_H_ */
|
||||
@@ -0,0 +1,94 @@
|
||||
#include "common/timer.h"
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/platform/sysTimer.h"
|
||||
|
||||
namespace Common {
|
||||
|
||||
Timer::Timer() noexcept {
|
||||
SysQueryPerformanceFrequency(&m_Frequency);
|
||||
}
|
||||
|
||||
void Timer::Start() {
|
||||
SysQueryPerformanceCounter(&m_StartTime);
|
||||
m_is_paused = false;
|
||||
}
|
||||
|
||||
void Timer::Pause() {
|
||||
EXIT_IF(m_is_paused);
|
||||
|
||||
SysQueryPerformanceCounter(&m_PauseTime);
|
||||
m_is_paused = true;
|
||||
}
|
||||
|
||||
void Timer::Resume() {
|
||||
EXIT_IF(!m_is_paused);
|
||||
|
||||
uint64_t current_time = 0;
|
||||
SysQueryPerformanceCounter(¤t_time);
|
||||
|
||||
m_StartTime += current_time - m_PauseTime;
|
||||
|
||||
m_is_paused = false;
|
||||
}
|
||||
|
||||
bool Timer::IsPaused() const {
|
||||
return m_is_paused;
|
||||
}
|
||||
|
||||
// return time in milliseconds
|
||||
double Timer::GetTimeMs() const {
|
||||
if (m_is_paused) {
|
||||
return 1000.0 * (static_cast<double>(m_PauseTime - m_StartTime)) /
|
||||
static_cast<double>(m_Frequency);
|
||||
}
|
||||
|
||||
uint64_t current_time = 0;
|
||||
SysQueryPerformanceCounter(¤t_time);
|
||||
|
||||
return 1000.0 * (static_cast<double>(current_time - m_StartTime)) /
|
||||
static_cast<double>(m_Frequency);
|
||||
}
|
||||
|
||||
// return time in seconds
|
||||
double Timer::GetTimeS() const {
|
||||
if (m_is_paused) {
|
||||
return (static_cast<double>(m_PauseTime - m_StartTime)) / static_cast<double>(m_Frequency);
|
||||
}
|
||||
|
||||
uint64_t current_time = 0;
|
||||
SysQueryPerformanceCounter(¤t_time);
|
||||
|
||||
return (static_cast<double>(current_time - m_StartTime)) / static_cast<double>(m_Frequency);
|
||||
}
|
||||
|
||||
// return time in ticks
|
||||
uint64_t Timer::GetTicks() const {
|
||||
if (m_is_paused) {
|
||||
return (m_PauseTime - m_StartTime);
|
||||
}
|
||||
|
||||
uint64_t current_time = 0;
|
||||
SysQueryPerformanceCounter(¤t_time);
|
||||
|
||||
return (current_time - m_StartTime);
|
||||
}
|
||||
|
||||
// return ticks frequency
|
||||
uint64_t Timer::GetFrequency() const {
|
||||
return m_Frequency;
|
||||
}
|
||||
|
||||
uint64_t Timer::QueryPerformanceFrequency() {
|
||||
uint64_t ret = 0;
|
||||
SysQueryPerformanceFrequency(&ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
uint64_t Timer::QueryPerformanceCounter() {
|
||||
uint64_t ret = 0;
|
||||
SysQueryPerformanceCounter(&ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
} // namespace Common
|
||||
@@ -0,0 +1,47 @@
|
||||
#ifndef KYTY_COMMON_TIMER_H_
|
||||
#define KYTY_COMMON_TIMER_H_
|
||||
|
||||
#include "common/common.h"
|
||||
|
||||
namespace Common {
|
||||
|
||||
class Timer final {
|
||||
public:
|
||||
Timer() noexcept;
|
||||
~Timer() = default;
|
||||
|
||||
void Start();
|
||||
|
||||
void Pause();
|
||||
|
||||
void Resume();
|
||||
|
||||
[[nodiscard]] bool IsPaused() const;
|
||||
|
||||
// return time in milliseconds
|
||||
[[nodiscard]] double GetTimeMs() const;
|
||||
|
||||
// return time in seconds
|
||||
[[nodiscard]] double GetTimeS() const;
|
||||
|
||||
// return time in ticks
|
||||
[[nodiscard]] uint64_t GetTicks() const;
|
||||
|
||||
// return ticks frequency
|
||||
[[nodiscard]] uint64_t GetFrequency() const;
|
||||
|
||||
KYTY_CLASS_NO_COPY(Timer);
|
||||
|
||||
[[nodiscard]] static uint64_t QueryPerformanceFrequency();
|
||||
[[nodiscard]] static uint64_t QueryPerformanceCounter();
|
||||
|
||||
private:
|
||||
bool m_is_paused = true;
|
||||
uint64_t m_Frequency = 0;
|
||||
uint64_t m_StartTime = 0;
|
||||
uint64_t m_PauseTime = 0;
|
||||
};
|
||||
|
||||
} // namespace Common
|
||||
|
||||
#endif /* KYTY_COMMON_TIMER_H_ */
|
||||
@@ -0,0 +1,82 @@
|
||||
#include "common/virtualMemory.h"
|
||||
|
||||
#include "common/platform/sysVirtual.h"
|
||||
|
||||
namespace Common {
|
||||
|
||||
namespace VirtualMemory {
|
||||
|
||||
void Init() {
|
||||
SysVirtualInit();
|
||||
}
|
||||
|
||||
uint64_t Alloc(uint64_t address, uint64_t size, Mode mode) {
|
||||
return SysVirtualAlloc(address, size, mode);
|
||||
}
|
||||
|
||||
uint64_t AllocAligned(uint64_t address, uint64_t size, Mode mode, uint64_t alignment) {
|
||||
return SysVirtualAllocAligned(address, size, mode, alignment);
|
||||
}
|
||||
|
||||
bool AllocFixed(uint64_t address, uint64_t size, Mode mode) {
|
||||
return SysVirtualAllocFixed(address, size, mode);
|
||||
}
|
||||
|
||||
bool Commit(uint64_t address, uint64_t size, Mode mode) {
|
||||
return SysVirtualCommit(address, size, mode);
|
||||
}
|
||||
|
||||
uint64_t Reserve(uint64_t address, uint64_t size) {
|
||||
return SysVirtualReserve(address, size);
|
||||
}
|
||||
|
||||
uint64_t ReserveAligned(uint64_t address, uint64_t size, uint64_t alignment) {
|
||||
return SysVirtualReserveAligned(address, size, alignment);
|
||||
}
|
||||
|
||||
bool ReserveFixed(uint64_t address, uint64_t size) {
|
||||
return SysVirtualReserveFixed(address, size);
|
||||
}
|
||||
|
||||
bool Decommit(uint64_t address, uint64_t size) {
|
||||
return SysVirtualDecommit(address, size);
|
||||
}
|
||||
|
||||
bool Free(uint64_t address) {
|
||||
return SysVirtualFree(address);
|
||||
}
|
||||
|
||||
bool FreeRange(uint64_t address, uint64_t size) {
|
||||
return SysVirtualFreeRange(address, size);
|
||||
}
|
||||
|
||||
bool Protect(uint64_t address, uint64_t size, Mode mode, Mode* old_mode) {
|
||||
return SysVirtualProtect(address, size, mode, old_mode);
|
||||
}
|
||||
|
||||
bool FlushInstructionCache(uint64_t address, uint64_t size) {
|
||||
return SysVirtualFlushInstructionCache(address, size);
|
||||
}
|
||||
|
||||
bool PatchReplace(uint64_t vaddr, uint64_t value) {
|
||||
Mode old_mode {};
|
||||
Protect(vaddr, 8, Mode::ReadWrite, &old_mode);
|
||||
|
||||
auto* ptr = reinterpret_cast<uint64_t*>(vaddr);
|
||||
|
||||
bool ret = (*ptr != value);
|
||||
|
||||
*ptr = value;
|
||||
|
||||
Protect(vaddr, 8, old_mode);
|
||||
|
||||
if (IsExecute(old_mode)) {
|
||||
FlushInstructionCache(vaddr, 8);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
} // namespace VirtualMemory
|
||||
|
||||
} // namespace Common
|
||||
@@ -0,0 +1,46 @@
|
||||
#ifndef KYTY_COMMON_VIRTUALMEMORY_H_
|
||||
#define KYTY_COMMON_VIRTUALMEMORY_H_
|
||||
|
||||
#include "common/common.h"
|
||||
|
||||
namespace Common {
|
||||
|
||||
namespace VirtualMemory {
|
||||
|
||||
enum class Mode : uint32_t {
|
||||
NoAccess = 0,
|
||||
Read = 1,
|
||||
Write = 2,
|
||||
ReadWrite = Read | Write,
|
||||
Execute = 4,
|
||||
ExecuteRead = Execute | Read,
|
||||
ExecuteWrite = Execute | Write,
|
||||
ExecuteReadWrite = Execute | Read | Write,
|
||||
};
|
||||
|
||||
inline bool IsExecute(Mode mode) {
|
||||
return (mode == Mode::Execute || mode == Mode::ExecuteRead || mode == Mode::ExecuteWrite ||
|
||||
mode == Mode::ExecuteReadWrite);
|
||||
}
|
||||
|
||||
void Init();
|
||||
|
||||
uint64_t Alloc(uint64_t address, uint64_t size, Mode mode);
|
||||
uint64_t AllocAligned(uint64_t address, uint64_t size, Mode mode, uint64_t alignment);
|
||||
bool AllocFixed(uint64_t address, uint64_t size, Mode mode);
|
||||
bool Commit(uint64_t address, uint64_t size, Mode mode);
|
||||
uint64_t Reserve(uint64_t address, uint64_t size);
|
||||
uint64_t ReserveAligned(uint64_t address, uint64_t size, uint64_t alignment);
|
||||
bool ReserveFixed(uint64_t address, uint64_t size);
|
||||
bool Decommit(uint64_t address, uint64_t size);
|
||||
bool Free(uint64_t address);
|
||||
bool FreeRange(uint64_t address, uint64_t size);
|
||||
bool Protect(uint64_t address, uint64_t size, Mode mode, Mode* old_mode = nullptr);
|
||||
bool FlushInstructionCache(uint64_t address, uint64_t size);
|
||||
bool PatchReplace(uint64_t vaddr, uint64_t value);
|
||||
|
||||
} // namespace VirtualMemory
|
||||
|
||||
} // namespace Common
|
||||
|
||||
#endif /* KYTY_COMMON_VIRTUALMEMORY_H_ */
|
||||
@@ -0,0 +1,223 @@
|
||||
#include "emulator.h"
|
||||
|
||||
#include "common/abi.h"
|
||||
#include "common/assert.h"
|
||||
#include "common/commonSubsystem.h"
|
||||
#include "common/emulatorConfig.h"
|
||||
#include "common/file.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/profiler.h"
|
||||
#include "common/singleton.h"
|
||||
#include "common/stringUtils.h"
|
||||
#include "common/subsystems.h"
|
||||
#include "common/systemInfo.h"
|
||||
#include "common/threads.h"
|
||||
#include "graphics/presentation/window.h"
|
||||
#include "kernel/fileSystem.h"
|
||||
#include "kernel/memory.h"
|
||||
#include "kernel/pthread.h"
|
||||
#include "libs/agc.h"
|
||||
#include "libs/audio.h"
|
||||
#include "libs/controller.h"
|
||||
#include "libs/libs.h"
|
||||
#include "libs/network.h"
|
||||
#include "loader/runtimeLinker.h"
|
||||
#include "loader/systemContent.h"
|
||||
#include "loader/timer.h"
|
||||
|
||||
#include <cstdlib>
|
||||
#include <filesystem>
|
||||
|
||||
namespace Emulator {
|
||||
|
||||
static void PrintSystemInfo() {
|
||||
Common::SystemInfo info = Common::GetSystemInfo();
|
||||
|
||||
LOGF("ProcessorName = %s\n", info.ProcessorName.c_str());
|
||||
}
|
||||
|
||||
static void KytyClose() {
|
||||
auto* rt = Common::Singleton<Loader::RuntimeLinker>::Instance();
|
||||
|
||||
rt->Clear();
|
||||
|
||||
LOGF("done!\n");
|
||||
|
||||
Common::SubsystemsListSingleton::Instance()->ShutdownAll();
|
||||
}
|
||||
|
||||
static void MountOrCreateDir(const std::filesystem::path& dir, const std::string& point) {
|
||||
if (!Common::File::IsDirectoryExisting(dir)) {
|
||||
Common::File::CreateDirectories(dir);
|
||||
}
|
||||
|
||||
EXIT_NOT_IMPLEMENTED(!Common::File::IsDirectoryExisting(dir));
|
||||
|
||||
Libs::LibKernel::FileSystem::Mount(dir, point);
|
||||
auto dir_text = Common::PathToString(dir);
|
||||
LOGF("Mounted %s -> %s\n", point.c_str(), dir_text.c_str());
|
||||
}
|
||||
|
||||
static void MountSandboxDirs() {
|
||||
std::string title_id;
|
||||
if (!Loader::SystemContentParamSfoGetString("TITLE_ID", &title_id) || title_id.empty()) {
|
||||
title_id = "UNKNOWN";
|
||||
}
|
||||
|
||||
MountOrCreateDir("_DownloadData/" + title_id, "/download0");
|
||||
MountOrCreateDir("_TempData/" + title_id, "/temp0");
|
||||
MountOrCreateDir("_TempData/" + title_id, "/temp");
|
||||
}
|
||||
|
||||
static bool ClearDirectoryContents(const std::filesystem::path& dir) {
|
||||
bool ok = true;
|
||||
|
||||
for (const auto& entry: Common::File::GetDirEntries(dir)) {
|
||||
if (entry.name == "." || entry.name == "..") {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto path = dir / entry.name;
|
||||
|
||||
if (entry.is_file) {
|
||||
Common::File::RemoveReadonly(path);
|
||||
ok = Common::File::DeleteFile(path) && ok;
|
||||
} else {
|
||||
ok = ClearDirectoryContents(path) && ok;
|
||||
ok = Common::File::DeleteDirectory(path) && ok;
|
||||
}
|
||||
}
|
||||
|
||||
return ok;
|
||||
}
|
||||
|
||||
static void ClearDebugTextureFolder() {
|
||||
const std::string debug_texture_folder = "_Textures";
|
||||
|
||||
if (!Common::File::IsDirectoryExisting(debug_texture_folder)) {
|
||||
Common::File::CreateDirectories(debug_texture_folder);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ClearDirectoryContents(debug_texture_folder)) {
|
||||
LOGF_COLOR(Log::Color::BrightYellow, "TextureDump: failed to completely clear %s\n",
|
||||
debug_texture_folder.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
static void Init(const Config::ConfigOptions& cfg) {
|
||||
EXIT_IF(!Common::Thread::IsMainThread());
|
||||
|
||||
auto* slist = Common::SubsystemsList::Instance();
|
||||
|
||||
auto* audio = Libs::Audio::AudioSubsystem::Instance();
|
||||
auto* config = Config::ConfigSubsystem::Instance();
|
||||
auto* controller = Libs::Controller::ControllerSubsystem::Instance();
|
||||
auto* core = Common::CommonSubsystem::Instance();
|
||||
auto* file_system = Libs::LibKernel::FileSystem::FileSystemSubsystem::Instance();
|
||||
auto* graphics = Libs::Graphics::GraphicsSubsystem::Instance();
|
||||
auto* log = Log::LogSubsystem::Instance();
|
||||
auto* memory = Libs::LibKernel::Memory::MemorySubsystem::Instance();
|
||||
auto* network = Libs::Network::NetworkSubsystem::Instance();
|
||||
auto* profiler = Profiler::ProfilerSubsystem::Instance();
|
||||
auto* pthread = Libs::LibKernel::PthreadSubsystem::Instance();
|
||||
auto* timer = Loader::Timer::TimerSubsystem::Instance();
|
||||
|
||||
slist->Add(config, {core});
|
||||
slist->InitAll(true);
|
||||
|
||||
Config::Load(cfg);
|
||||
|
||||
slist->Add(audio, {core, log, pthread, memory});
|
||||
slist->Add(controller, {core, log, config});
|
||||
slist->Add(file_system, {core, log, pthread});
|
||||
slist->Add(graphics, {core, log, pthread, memory, config, profiler, controller});
|
||||
slist->Add(log, {core, config});
|
||||
slist->Add(memory, {core, log});
|
||||
slist->Add(network, {core, log, pthread});
|
||||
slist->Add(profiler, {core, config});
|
||||
slist->Add(pthread, {core, log, timer});
|
||||
slist->Add(timer, {core, log});
|
||||
|
||||
slist->InitAll(true);
|
||||
}
|
||||
|
||||
static void LoadElf(const std::filesystem::path& elf, bool dbg_print_reloc = false,
|
||||
const std::filesystem::path& save_name = {}) {
|
||||
auto* rt = Common::Singleton<Loader::RuntimeLinker>::Instance();
|
||||
|
||||
auto* program = rt->LoadProgram(
|
||||
Libs::LibKernel::FileSystem::GetRealFilename(Common::PathToGenericString(elf)));
|
||||
|
||||
if (dbg_print_reloc) {
|
||||
program->dbg_print_reloc = true;
|
||||
}
|
||||
|
||||
if (!save_name.empty()) {
|
||||
rt->SaveProgram(program, Libs::LibKernel::FileSystem::GetRealFilename(
|
||||
Common::PathToGenericString(save_name)));
|
||||
}
|
||||
}
|
||||
|
||||
static void Execute() {
|
||||
int thread_model = 1;
|
||||
|
||||
if (thread_model == 0) {
|
||||
Common::Thread t([](void* /*unused*/) { Libs::Graphics::WindowRun(); }, nullptr);
|
||||
t.Detach();
|
||||
auto* rt = Common::Singleton<Loader::RuntimeLinker>::Instance();
|
||||
rt->Execute();
|
||||
} else {
|
||||
Common::Thread t(
|
||||
[](void* /*unused*/) {
|
||||
auto* rt = Common::Singleton<Loader::RuntimeLinker>::Instance();
|
||||
rt->Execute();
|
||||
},
|
||||
nullptr);
|
||||
t.Detach();
|
||||
Libs::Graphics::WindowRun();
|
||||
t.Join();
|
||||
}
|
||||
}
|
||||
|
||||
void Run(const RunOptions& options) {
|
||||
if (options.app0_dir.empty()) {
|
||||
EXIT("app0 directory is required\n");
|
||||
}
|
||||
|
||||
if (options.elf.empty()) {
|
||||
EXIT("ELF is required\n");
|
||||
}
|
||||
|
||||
Init(options.config);
|
||||
|
||||
ClearDebugTextureFolder();
|
||||
|
||||
PrintSystemInfo();
|
||||
|
||||
int ok = atexit(KytyClose);
|
||||
EXIT_NOT_IMPLEMENTED(ok != 0);
|
||||
|
||||
Libs::LibKernel::FileSystem::Mount(options.app0_dir, "/app0");
|
||||
Libs::LibKernel::FileSystem::Mount(options.app0_dir, "/hostapp");
|
||||
|
||||
auto param_json = options.app0_dir / "sce_sys" / "param.json";
|
||||
if (Common::File::IsFileExisting(param_json)) {
|
||||
Loader::SystemContentLoadParamSfo(param_json);
|
||||
if (auto flexible_memory_size = Loader::SystemContentGetFlexibleMemorySize();
|
||||
flexible_memory_size != 0) {
|
||||
Libs::LibKernel::Memory::SetFlexibleMemorySize(flexible_memory_size);
|
||||
}
|
||||
}
|
||||
|
||||
MountSandboxDirs();
|
||||
|
||||
auto* rt = Common::Singleton<Loader::RuntimeLinker>::Instance();
|
||||
Libs::InitAll(rt->Symbols());
|
||||
|
||||
LoadElf(options.elf);
|
||||
|
||||
Execute();
|
||||
}
|
||||
|
||||
} // namespace Emulator
|
||||
@@ -0,0 +1,21 @@
|
||||
#ifndef EMULATOR_INCLUDE_EMULATOR_EMULATOR_H_
|
||||
#define EMULATOR_INCLUDE_EMULATOR_EMULATOR_H_
|
||||
|
||||
#include "common/emulatorConfig.h"
|
||||
#include "common/stringUtils.h"
|
||||
|
||||
#include <filesystem>
|
||||
|
||||
namespace Emulator {
|
||||
|
||||
struct RunOptions {
|
||||
Config::ConfigOptions config;
|
||||
std::filesystem::path app0_dir;
|
||||
std::filesystem::path elf;
|
||||
};
|
||||
|
||||
void Run(const RunOptions& options);
|
||||
|
||||
} // namespace Emulator
|
||||
|
||||
#endif /* EMULATOR_INCLUDE_EMULATOR_EMULATOR_H_ */
|
||||
@@ -0,0 +1,8 @@
|
||||
#message("${GIT_EXECUTABLE}")
|
||||
if(GIT_EXECUTABLE)
|
||||
execute_process(COMMAND ${GIT_EXECUTABLE} describe --tags --always OUTPUT_VARIABLE KYTY_GIT_VERSION)
|
||||
else()
|
||||
set(KYTY_GIT_VERSION "unknown")
|
||||
endif()
|
||||
string(STRIP ${KYTY_GIT_VERSION} KYTY_GIT_VERSION)
|
||||
configure_file(${INPUT_FILE} ${OUTPUT_FILE})
|
||||
@@ -0,0 +1,96 @@
|
||||
#ifndef EMULATOR_INCLUDE_EMULATOR_GRAPHICS_ASYNCJOB_H_
|
||||
#define EMULATOR_INCLUDE_EMULATOR_GRAPHICS_ASYNCJOB_H_
|
||||
|
||||
#include "common/common.h"
|
||||
#include "common/profiler.h"
|
||||
#include "common/threads.h"
|
||||
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
class AsyncJob final {
|
||||
public:
|
||||
using Task = std::function<void()>;
|
||||
|
||||
explicit AsyncJob(std::string thread_name = {})
|
||||
: m_thread_name(std::move(thread_name)), m_worker_thread(WorkerEntry, this) {}
|
||||
|
||||
~AsyncJob() {
|
||||
{
|
||||
Common::LockGuard lock(m_mutex);
|
||||
m_stop_requested = true;
|
||||
m_task_available_condition.Signal();
|
||||
}
|
||||
|
||||
m_worker_thread.Join();
|
||||
}
|
||||
|
||||
KYTY_CLASS_NO_COPY(AsyncJob);
|
||||
|
||||
void Execute(Task task) {
|
||||
Common::LockGuard lock(m_mutex);
|
||||
while (m_is_busy) {
|
||||
m_idle_condition.Wait(&m_mutex);
|
||||
}
|
||||
|
||||
m_task = std::move(task);
|
||||
m_is_busy = true;
|
||||
m_task_available_condition.Signal();
|
||||
}
|
||||
|
||||
void Wait() {
|
||||
Common::LockGuard lock(m_mutex);
|
||||
while (m_is_busy) {
|
||||
m_idle_condition.Wait(&m_mutex);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
static void WorkerEntry(void* data) { static_cast<AsyncJob*>(data)->WorkerLoop(); }
|
||||
|
||||
void WorkerLoop() {
|
||||
if (!m_thread_name.empty()) {
|
||||
KYTY_PROFILER_THREAD(m_thread_name.c_str());
|
||||
}
|
||||
|
||||
for (;;) {
|
||||
Task task;
|
||||
{
|
||||
Common::LockGuard lock(m_mutex);
|
||||
while (!m_is_busy && !m_stop_requested) {
|
||||
m_task_available_condition.Wait(&m_mutex);
|
||||
}
|
||||
|
||||
if (!m_is_busy) {
|
||||
return;
|
||||
}
|
||||
|
||||
task = std::move(m_task);
|
||||
}
|
||||
|
||||
task();
|
||||
|
||||
{
|
||||
Common::LockGuard lock(m_mutex);
|
||||
m_is_busy = false;
|
||||
m_idle_condition.SignalAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string m_thread_name;
|
||||
Common::Mutex m_mutex;
|
||||
Common::CondVar m_task_available_condition;
|
||||
Common::CondVar m_idle_condition;
|
||||
Task m_task;
|
||||
bool m_is_busy = false;
|
||||
bool m_stop_requested = false;
|
||||
Common::Thread m_worker_thread;
|
||||
};
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
#endif /* EMULATOR_INCLUDE_EMULATOR_GRAPHICS_ASYNCJOB_H_ */
|
||||
@@ -0,0 +1,268 @@
|
||||
#ifndef GRAPHICS_GUEST_GPU_COMMAND_PROCESSOR_COMMAND_PROCESSOR_H
|
||||
#define GRAPHICS_GUEST_GPU_COMMAND_PROCESSOR_COMMAND_PROCESSOR_H
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/threads.h"
|
||||
#include "graphics/guest_gpu/hardwareContext.h"
|
||||
#include "graphics/host_gpu/renderer/render.h"
|
||||
#include "graphics/host_gpu/renderer/renderContext.h"
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
class CommandProcessorGuestAccessScope;
|
||||
|
||||
inline constexpr uint32_t AcquireGcrGl2Writeback = 1u << 15u;
|
||||
|
||||
bool TestWaitRegMemValue(uint64_t value, uint64_t ref, uint64_t mask, uint32_t func);
|
||||
|
||||
class CommandScheduler {
|
||||
public:
|
||||
static constexpr int BuffersNum = 8;
|
||||
|
||||
void SetQueue(int queue) { m_queue = queue; }
|
||||
int Queue() const { return m_queue; }
|
||||
bool Active() const { return m_current >= 0 && m_current < BuffersNum; }
|
||||
void CheckActive() const { EXIT_IF(!Active()); }
|
||||
|
||||
CommandBuffer* Current() const {
|
||||
CheckActive();
|
||||
EXIT_IF(m_buffers[m_current] == nullptr);
|
||||
return m_buffers[m_current];
|
||||
}
|
||||
|
||||
void Init() {
|
||||
if (m_current >= 0) {
|
||||
return;
|
||||
}
|
||||
for (auto& buf: m_buffers) {
|
||||
EXIT_IF(buf != nullptr);
|
||||
buf = new CommandBuffer(m_queue);
|
||||
}
|
||||
m_current = 0;
|
||||
Current()->Begin();
|
||||
}
|
||||
|
||||
void Flush() {
|
||||
SubmitCurrent();
|
||||
BeginNext();
|
||||
}
|
||||
|
||||
CommandBuffer* FlushAndGetSubmitted() {
|
||||
auto* submitted = SubmitCurrent();
|
||||
BeginNext();
|
||||
return submitted;
|
||||
}
|
||||
|
||||
void CopyBuffers(std::array<CommandBuffer*, BuffersNum>* out) const {
|
||||
EXIT_IF(out == nullptr);
|
||||
for (int i = 0; i < BuffersNum; i++) {
|
||||
auto* buf = m_buffers[i];
|
||||
EXIT_IF(buf == nullptr);
|
||||
(*out)[i] = buf;
|
||||
}
|
||||
}
|
||||
|
||||
void WaitAll() {
|
||||
for (auto* buf: m_buffers) {
|
||||
EXIT_IF(buf == nullptr);
|
||||
buf->WaitForFenceAndReset();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
CommandBuffer* SubmitCurrent() {
|
||||
auto* submitted = Current();
|
||||
submitted->End();
|
||||
submitted->Execute();
|
||||
return submitted;
|
||||
}
|
||||
|
||||
void BeginNext() {
|
||||
m_current = (m_current + 1) % BuffersNum;
|
||||
Current()->WaitForFenceAndReset();
|
||||
Current()->Begin();
|
||||
}
|
||||
|
||||
CommandBuffer* m_buffers[BuffersNum] = {};
|
||||
int m_current = -1;
|
||||
int m_queue = -1;
|
||||
};
|
||||
|
||||
class CommandProcessor {
|
||||
public:
|
||||
struct FlipInfo {
|
||||
int handle = 0;
|
||||
int index = 0;
|
||||
int flip_mode = 0;
|
||||
int64_t flip_arg = 0;
|
||||
};
|
||||
|
||||
CommandProcessor() = default;
|
||||
~CommandProcessor() { KYTY_NOT_IMPLEMENTED; }
|
||||
|
||||
KYTY_CLASS_NO_COPY(CommandProcessor);
|
||||
|
||||
void Reset();
|
||||
|
||||
void BufferInit();
|
||||
void BufferFlush();
|
||||
void BufferFlushAndWait();
|
||||
void BufferWait();
|
||||
[[nodiscard]] int BeginReadbackTransaction(bool mutex_preowned) {
|
||||
if (!mutex_preowned) {
|
||||
m_mutex.Lock();
|
||||
}
|
||||
m_scheduler.Flush();
|
||||
m_scheduler.WaitAll();
|
||||
return m_scheduler.Queue();
|
||||
}
|
||||
void EndReadbackTransaction(bool mutex_preowned) {
|
||||
if (!mutex_preowned) {
|
||||
m_mutex.Unlock();
|
||||
}
|
||||
}
|
||||
|
||||
void RunLock() { m_run_mutex.Lock(); }
|
||||
void RunUnlock() { m_run_mutex.Unlock(); }
|
||||
|
||||
HW::Context* GetCtx() { return &m_ctx; }
|
||||
HW::UserConfig* GetUcfg() { return &m_ucfg; }
|
||||
HW::Shader* GetShCtx() { return &m_sh_ctx; }
|
||||
|
||||
void SetIndexType(uint32_t index_type_and_size);
|
||||
void SetIndexBaseAddress(uint64_t index_base_addr);
|
||||
void SetIndexBufferSize(uint32_t index_buffer_size);
|
||||
void SetDrawIndirectArgsBaseAddress(uint64_t draw_indirect_args_base_addr);
|
||||
void SetDispatchIndirectArgsBaseAddress(uint64_t dispatch_indirect_args_base_addr);
|
||||
void SetNumInstances(uint32_t num_instances);
|
||||
void DrawIndex(uint32_t index_count, const void* index_addr, uint32_t flags, uint32_t type,
|
||||
uint32_t instance_count = 0, const void* object_ids = nullptr,
|
||||
uint32_t render_target_slice_offset = 0, int32_t vertex_offset_add = 0,
|
||||
uint32_t first_instance = 0);
|
||||
void DrawIndexOffset(uint32_t index_offset, uint32_t index_count, uint32_t flags);
|
||||
void DrawIndexAuto(uint32_t index_count, uint32_t flags,
|
||||
uint32_t render_target_slice_offset = 0, uint32_t instance_count = 1,
|
||||
uint32_t first_vertex = 0, uint32_t first_instance = 0);
|
||||
void DrawIndirect(uint32_t data_offset, uint32_t draw_initiator, bool indexed);
|
||||
void DrawIndirectMulti(uint32_t data_offset, uint32_t max_count_or_count,
|
||||
const volatile uint32_t* count_addr, uint32_t stride_in_bytes,
|
||||
uint32_t draw_initiator, bool indexed);
|
||||
void WriteAtEndOfPipe32(uint32_t cache_policy, uint32_t event_write_dest,
|
||||
uint32_t eop_event_type, uint32_t cache_action, uint32_t event_index,
|
||||
uint32_t event_write_source, void* dst_gpu_addr, uint32_t value,
|
||||
uint32_t interrupt_selector, uint32_t interrupt_context_id = 0);
|
||||
void WriteAtEndOfPipe64(uint32_t cache_policy, uint32_t event_write_dest,
|
||||
uint32_t eop_event_type, uint32_t cache_action, uint32_t event_index,
|
||||
uint32_t event_write_source, void* dst_gpu_addr, uint64_t value,
|
||||
uint32_t interrupt_selector, uint32_t interrupt_context_id = 0);
|
||||
void Flip();
|
||||
void Flip(void* dst_gpu_addr, uint32_t value);
|
||||
void FlipWithInterrupt(uint32_t eop_event_type, uint32_t cache_action, void* dst_gpu_addr,
|
||||
uint32_t value);
|
||||
void SynchronizeGpu();
|
||||
void SynchronizeGpuRange(uint64_t vaddr, uint64_t size);
|
||||
void MemoryBarrier();
|
||||
void TriggerEopEventAtEndOfPipe(uint32_t interrupt_context_id);
|
||||
void RenderTextureBarrier(uint64_t vaddr, uint64_t size);
|
||||
void DepthStencilBarrier(uint64_t vaddr, uint64_t size);
|
||||
void DispatchDirect(uint32_t thread_group_x, uint32_t thread_group_y, uint32_t thread_group_z,
|
||||
uint32_t mode);
|
||||
void DispatchIndirect(uint32_t data_offset, uint32_t mode);
|
||||
void WaitFlipDone(uint32_t video_out_handle, uint32_t display_buffer_index);
|
||||
void TriggerEvent(uint32_t event_type, uint32_t event_index);
|
||||
|
||||
void SetUserDataMarker(HW::UserSgprType type) { m_user_data_marker = type; }
|
||||
[[nodiscard]] HW::UserSgprType GetUserDataMarker() const { return m_user_data_marker; }
|
||||
void SetEmbeddedDataMarker(const uint32_t* buffer, uint32_t num_dw, uint32_t align) {}
|
||||
void PushMarker(const char* str) {}
|
||||
void PopMarker() {}
|
||||
|
||||
void PrefetchL2(void* addr, uint32_t size) {}
|
||||
void ClearGds(uint64_t dw_offset, uint32_t dw_num, uint32_t clear_value);
|
||||
void ReadGds(uint32_t* dst, uint32_t dw_offset, uint32_t dw_size);
|
||||
|
||||
void ResetDeCe();
|
||||
void WaitCe();
|
||||
void WaitDeDiff(uint32_t diff);
|
||||
void IncremenetDe();
|
||||
void IncremenetCe();
|
||||
|
||||
void WriteConstRam(uint32_t offset, const uint32_t* src, uint32_t dw_num);
|
||||
void DumpConstRam(uint32_t* dst, uint32_t offset, uint32_t dw_num);
|
||||
|
||||
template <typename T>
|
||||
void WaitRegMem(uint32_t func, const T* addr, T ref, T mask, uint32_t poll, uint32_t wait_op);
|
||||
void WriteData(uint32_t* dst, const uint32_t* src, uint32_t dw_num, uint32_t write_control);
|
||||
void DmaData(uint8_t engine, uint8_t dst_sel, uint8_t dst_cache_policy,
|
||||
uint64_t dst_address_or_offset, uint8_t src_sel, uint8_t src_cache_policy,
|
||||
uint64_t src_address_or_offset_or_immediate, uint32_t num_bytes,
|
||||
uint8_t wait_for_previous, uint8_t write_confirm, uint8_t block_engine);
|
||||
void SetPredication(uint32_t condition, uint32_t op, uint32_t wait_op,
|
||||
const volatile void* address, uint32_t count_in_dwords);
|
||||
[[nodiscard]] bool ShouldSkipPredicatedPackets() const { return m_predicate_skip; }
|
||||
|
||||
void Run(uint32_t* data, uint32_t num_dw);
|
||||
|
||||
void SetQueue(int queue) { m_scheduler.SetQueue(queue); }
|
||||
[[nodiscard]] int GetQueue() const { return m_scheduler.Queue(); }
|
||||
|
||||
[[nodiscard]] const FlipInfo& GetFlip() const { return m_flip; }
|
||||
void SetFlip(const FlipInfo& flip) { m_flip = flip; }
|
||||
|
||||
[[nodiscard]] uint64_t GetSubmitId() const { return m_submit_id; }
|
||||
void SetSubmitId(uint64_t submit_id) { m_submit_id = submit_id; }
|
||||
|
||||
private:
|
||||
friend class CommandProcessorGuestAccessScope;
|
||||
|
||||
struct Counter {
|
||||
Common::Mutex mutex;
|
||||
Common::CondVar cond_var;
|
||||
uint32_t value = 0;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
void WriteAtEndOfPipe(uint32_t cache_policy, uint32_t event_write_dest, uint32_t eop_event_type,
|
||||
uint32_t cache_action, uint32_t event_index, uint32_t event_write_source,
|
||||
void* dst_gpu_addr, T value, uint32_t interrupt_selector,
|
||||
uint32_t interrupt_context_id);
|
||||
|
||||
CommandBuffer* CurrentBuffer() { return m_scheduler.Current(); }
|
||||
void CheckBuffer() const { m_scheduler.CheckActive(); }
|
||||
GpuResourceManager* GetGpuResources() const {
|
||||
EXIT_IF(g_render_ctx == nullptr);
|
||||
return g_render_ctx->GetGpuResources();
|
||||
}
|
||||
|
||||
HW::Context m_ctx;
|
||||
HW::UserConfig m_ucfg;
|
||||
HW::Shader m_sh_ctx;
|
||||
HW::UserSgprType m_user_data_marker = HW::UserSgprType::Unknown;
|
||||
uint32_t m_index_type_and_size = 0;
|
||||
uint32_t m_index_buffer_size = 0;
|
||||
uint64_t m_index_base_addr = 0;
|
||||
uint64_t m_draw_indirect_args_base_addr = 0;
|
||||
uint64_t m_dispatch_indirect_args_base_addr = 0;
|
||||
uint32_t m_num_instances = 1;
|
||||
|
||||
Common::Mutex m_mutex;
|
||||
Common::Mutex m_run_mutex;
|
||||
|
||||
CommandScheduler m_scheduler;
|
||||
|
||||
Counter m_de_counter;
|
||||
Counter m_ce_counter;
|
||||
|
||||
uint32_t m_const_ram[0x3000] = {0};
|
||||
|
||||
FlipInfo m_flip;
|
||||
uint64_t m_submit_id = 0;
|
||||
bool m_predicate_skip = false;
|
||||
};
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
#endif // GRAPHICS_GUEST_GPU_COMMAND_PROCESSOR_COMMAND_PROCESSOR_H
|
||||
@@ -0,0 +1,260 @@
|
||||
#include "graphics/guest_gpu/command_processor/pm4Dispatch.h"
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
hw_ctx_parser_func_t g_hw_ctx_func[Pm4::CX_NUM] = {};
|
||||
hw_ctx_indirect_func_t g_hw_ctx_indirect_func[Pm4::CX_NUM] = {};
|
||||
hw_sh_parser_func_t g_hw_sh_func[Pm4::SH_NUM] = {};
|
||||
hw_sh_indirect_func_t g_hw_sh_indirect_func[Pm4::SH_NUM] = {};
|
||||
hw_uc_parser_func_t g_hw_uc_func[Pm4::UC_NUM] = {};
|
||||
hw_uc_indirect_func_t g_hw_uc_indirect_func[Pm4::UC_NUM] = {};
|
||||
cp_op_parser_func_t g_cp_op_func[256] = {};
|
||||
cp_op_parser_func_t g_cp_op_custom_func[Pm4::R_NUM] = {};
|
||||
|
||||
void GraphicsInitJmpTables() {
|
||||
for (auto& func: g_hw_ctx_func) {
|
||||
func = nullptr;
|
||||
}
|
||||
|
||||
g_hw_ctx_func[Pm4::DB_RENDER_CONTROL] = HwCtxSetRenderControl;
|
||||
g_hw_ctx_func[Pm4::DB_COUNT_CONTROL] = HwCtxSetDepthMetadataRegisters;
|
||||
g_hw_ctx_func[Pm4::DB_RENDER_OVERRIDE] = HwCtxSetDepthMetadataRegisters;
|
||||
g_hw_ctx_func[Pm4::DB_RENDER_OVERRIDE2] = HwCtxSetDepthMetadataRegisters;
|
||||
g_hw_ctx_func[Pm4::DB_DFSM_CONTROL] = HwCtxSetDepthMetadataRegisters;
|
||||
g_hw_ctx_func[Pm4::DB_RMI_L2_CACHE_CONTROL] = HwCtxSetDepthMetadataRegisters;
|
||||
g_hw_ctx_func[Pm4::CB_RMI_GL2_CACHE_CONTROL] = HwCtxSetDepthMetadataRegisters;
|
||||
g_hw_ctx_func[Pm4::TA_BC_BASE_ADDR] = HwCtxSetBorderColorTableAddr;
|
||||
g_hw_ctx_func[Pm4::TA_BC_BASE_ADDR_HI] = HwCtxSetBorderColorTableAddr;
|
||||
g_hw_ctx_func[Pm4::DB_STENCIL_CLEAR] = HwCtxSetStencilClear;
|
||||
g_hw_ctx_func[Pm4::DB_DEPTH_CLEAR] = HwCtxSetDepthClear;
|
||||
g_hw_ctx_func[Pm4::PA_SC_SCREEN_SCISSOR_TL] = HwCtxSetScreenScissor;
|
||||
g_hw_ctx_func[Pm4::DB_Z_INFO] = HwCtxSetDepthRenderTarget;
|
||||
g_hw_ctx_func[Pm4::DB_STENCIL_INFO] = HwCtxSetStencilInfo;
|
||||
g_hw_ctx_func[Pm4::DB_HTILE_SURFACE] = HwCtxSetDepthHtileSurface;
|
||||
g_hw_ctx_func[Pm4::PA_SU_HARDWARE_SCREEN_OFFSET] = HwCtxSetHardwareScreenOffset;
|
||||
g_hw_ctx_func[Pm4::PA_SC_WINDOW_OFFSET] = HwCtxSetWindowOffset;
|
||||
g_hw_ctx_func[Pm4::PA_SC_WINDOW_SCISSOR_TL] = HwCtxSetWindowScissor;
|
||||
for (auto cmd_offset = Pm4::PA_SC_CLIPRECT_RULE; cmd_offset < Pm4::PA_SC_CLIPRECT_0_TL + 8u;
|
||||
cmd_offset++) {
|
||||
g_hw_ctx_func[cmd_offset] = HwCtxSetClipRect;
|
||||
}
|
||||
g_hw_ctx_func[Pm4::CB_TARGET_MASK] = HwCtxSetRenderTargetMask;
|
||||
g_hw_ctx_func[Pm4::PA_SC_GENERIC_SCISSOR_TL] = HwCtxSetGenericScissor;
|
||||
g_hw_ctx_func[Pm4::CB_BLEND_RED] = HwCtxSetBlendColor;
|
||||
g_hw_ctx_func[Pm4::CB_DCC_CONTROL] = HwCtxSetCbDccControl;
|
||||
g_hw_ctx_func[Pm4::DB_STENCIL_CONTROL] = HwCtxSetStencilControl;
|
||||
g_hw_ctx_func[Pm4::DB_STENCILREFMASK] = HwCtxSetStencilMask;
|
||||
g_hw_ctx_func[Pm4::SPI_PS_INPUT_CNTL_0] = HwCtxSetPsInput;
|
||||
g_hw_ctx_func[Pm4::SPI_TMPRING_SIZE] = HwCtxSetSpiTmpringSize;
|
||||
g_hw_ctx_func[Pm4::DB_DEPTH_CONTROL] = HwCtxSetDepthControl;
|
||||
g_hw_ctx_func[Pm4::DB_EQAA] = HwCtxSetEqaaControl;
|
||||
g_hw_ctx_func[Pm4::CB_COLOR_CONTROL] = HwCtxSetColorControl;
|
||||
g_hw_ctx_func[Pm4::DB_DEPTH_BOUNDS_MIN] = HwCtxSetDepthBounds;
|
||||
g_hw_ctx_func[Pm4::DB_DEPTH_BOUNDS_MAX] = HwCtxSetDepthBounds;
|
||||
g_hw_ctx_func[Pm4::PA_SU_POINT_SIZE] = HwCtxSetPointState;
|
||||
g_hw_ctx_func[Pm4::PA_SU_POINT_MINMAX] = HwCtxSetPointState;
|
||||
g_hw_ctx_func[Pm4::PA_CL_CLIP_CNTL] = HwCtxSetClipControl;
|
||||
g_hw_ctx_func[Pm4::PA_SU_SC_MODE_CNTL] = HwCtxSetModeControl;
|
||||
g_hw_ctx_func[Pm4::PA_SU_POLY_OFFSET_DB_FMT_CNTL] = HwCtxSetPolyOffsetRegisters;
|
||||
g_hw_ctx_func[Pm4::PA_SU_POLY_OFFSET_CLAMP] = HwCtxSetPolyOffsetRegisters;
|
||||
g_hw_ctx_func[Pm4::PA_SU_POLY_OFFSET_FRONT_SCALE] = HwCtxSetPolyOffsetRegisters;
|
||||
g_hw_ctx_func[Pm4::PA_SU_POLY_OFFSET_FRONT_OFFSET] = HwCtxSetPolyOffsetRegisters;
|
||||
g_hw_ctx_func[Pm4::PA_SU_POLY_OFFSET_BACK_SCALE] = HwCtxSetPolyOffsetRegisters;
|
||||
g_hw_ctx_func[Pm4::PA_SU_POLY_OFFSET_BACK_OFFSET] = HwCtxSetPolyOffsetRegisters;
|
||||
g_hw_ctx_func[Pm4::PA_CL_VTE_CNTL] = HwCtxSetViewportTransformControl;
|
||||
g_hw_ctx_func[Pm4::PA_SU_LINE_CNTL] = HwCtxSetLineControl;
|
||||
g_hw_ctx_func[Pm4::PA_SC_FOV_WINDOW_LR] = HwCtxSetFovWindow;
|
||||
g_hw_ctx_func[Pm4::PA_SC_FOV_WINDOW_TB] = HwCtxSetFovWindow;
|
||||
g_hw_ctx_func[Pm4::PA_SC_MODE_CNTL_0] = HwCtxSetScanModeControl;
|
||||
g_hw_ctx_func[Pm4::PA_SC_MODE_CNTL_1] = HwCtxSetScanModeControl1;
|
||||
g_hw_ctx_func[Pm4::PA_SC_AA_CONFIG] = HwCtxSetAaConfig;
|
||||
for (auto cmd_offset = Pm4::PA_SC_AA_SAMPLE_LOCS_PIXEL_X0Y0_0;
|
||||
cmd_offset < Pm4::PA_SC_AA_SAMPLE_LOCS_PIXEL_X0Y0_0 + 16; cmd_offset++) {
|
||||
g_hw_ctx_func[cmd_offset] = HwCtxSetAaSampleControl;
|
||||
}
|
||||
g_hw_ctx_func[Pm4::PA_SC_CENTROID_PRIORITY_0] = HwCtxSetCentroidPriority;
|
||||
g_hw_ctx_func[Pm4::PA_SC_CENTROID_PRIORITY_1] = HwCtxSetCentroidPriority;
|
||||
g_hw_ctx_func[Pm4::PA_SC_AA_MASK_X0Y0_X1Y0] = HwCtxSetAaMask;
|
||||
g_hw_ctx_func[Pm4::PA_SC_AA_MASK_X0Y1_X1Y1] = HwCtxSetAaMask;
|
||||
g_hw_ctx_func[Pm4::PA_SU_VTX_CNTL] = HwCtxSetVtxControl;
|
||||
g_hw_ctx_func[Pm4::PS_SHADER_SAMPLE_EXCLUSION_MASK] = HwCtxSetAaMask;
|
||||
g_hw_ctx_func[Pm4::PA_SC_BINNER_CNTL_0] = HwCtxSetPaScExtendedControl;
|
||||
g_hw_ctx_func[Pm4::PA_SC_BINNER_CNTL_1] = HwCtxSetPaScExtendedControl;
|
||||
g_hw_ctx_func[Pm4::PA_SC_CONSERVATIVE_RASTERIZATION_CNTL] = HwCtxSetPaScExtendedControl;
|
||||
g_hw_ctx_func[Pm4::PA_SC_NGG_MODE_CNTL] = HwCtxSetPaScExtendedControl;
|
||||
g_hw_ctx_func[Pm4::DB_ALPHA_TO_MASK] = HwCtxSetAlphaToMask;
|
||||
g_hw_ctx_func[Pm4::VGT_DRAW_PAYLOAD_CNTL] = HwCtxSetDrawPayloadControl;
|
||||
g_hw_ctx_func[Pm4::VGT_PRIMITIVEID_RESET] = HwCtxSetPrimitiveIdReset;
|
||||
g_hw_ctx_func[Pm4::PA_CL_OBJPRIM_ID_CNTL] = HwCtxSetObjprimIdControl;
|
||||
g_hw_ctx_func[Pm4::VGT_SHADER_STAGES_EN] = HwCtxSetShaderStages;
|
||||
g_hw_ctx_func[Pm4::PA_CL_GB_VERT_CLIP_ADJ] = HwCtxSetGuardBands;
|
||||
|
||||
for (uint32_t slot = 0; slot < 8; slot++) {
|
||||
g_hw_ctx_func[Pm4::CB_COLOR0_BASE + slot * 15] = HwCtxSetRenderTarget;
|
||||
g_hw_ctx_func[Pm4::CB_COLOR0_INFO + slot * 15] = HwCtxSetColorInfo;
|
||||
|
||||
g_hw_ctx_func[Pm4::CB_BLEND0_CONTROL + slot * 1] = HwCtxSetBlendControl;
|
||||
}
|
||||
|
||||
for (uint32_t viewport = 0; viewport < 16; viewport++) {
|
||||
g_hw_ctx_func[Pm4::PA_SC_VPORT_SCISSOR_0_TL + viewport * 2] = HwCtxSetViewportScissor;
|
||||
g_hw_ctx_func[Pm4::PA_SC_VPORT_SCISSOR_0_BR + viewport * 2] = HwCtxSetViewportScissor;
|
||||
g_hw_ctx_func[Pm4::PA_SC_VPORT_ZMIN_0 + viewport * 2] = HwCtxSetViewportZ;
|
||||
g_hw_ctx_func[Pm4::PA_SC_VPORT_ZMAX_0 + viewport * 2] = HwCtxSetViewportZ;
|
||||
for (uint32_t field = 0; field < 6; field++) {
|
||||
g_hw_ctx_func[Pm4::PA_CL_VPORT_XSCALE + viewport * 6 + field] =
|
||||
HwCtxSetViewportScaleOffset;
|
||||
}
|
||||
}
|
||||
|
||||
for (auto& func: g_hw_sh_func) {
|
||||
func = nullptr;
|
||||
}
|
||||
|
||||
for (uint32_t slot = 0; slot < 32; slot++) {
|
||||
g_hw_sh_func[Pm4::SPI_SHADER_USER_DATA_PS_0 + slot * 1] = HwShSetPsUserSgpr;
|
||||
g_hw_sh_func[Pm4::SPI_SHADER_USER_DATA_GS_0 + slot * 1] = HwShSetGsUserSgpr;
|
||||
g_hw_sh_func[Pm4::SPI_SHADER_USER_DATA_HS_0 + slot * 1] = HwShSetHsUserSgpr;
|
||||
}
|
||||
for (uint32_t slot = 0; slot < 16; slot++) {
|
||||
g_hw_sh_func[Pm4::COMPUTE_USER_DATA_0 + slot * 1] = HwShSetCsUserSgpr;
|
||||
}
|
||||
|
||||
for (uint32_t slot = 0; slot < 4; slot++) {
|
||||
g_hw_sh_func[Pm4::SPI_SHADER_USER_ACCUM_PS_0 + slot] = HwShSetPsUserAccumSgpr;
|
||||
}
|
||||
|
||||
for (uint32_t slot = 0; slot < 4; slot++) {
|
||||
g_hw_sh_func[Pm4::SPI_SHADER_USER_ACCUM_ESGS_0 + slot] = HwShSetGsUserAccumSgpr;
|
||||
}
|
||||
for (uint32_t slot = 0; slot < 4; slot++) {
|
||||
g_hw_sh_func[Pm4::SPI_SHADER_USER_ACCUM_LSHS_0 + slot] = HwShSetHsUserAccumSgpr;
|
||||
}
|
||||
|
||||
for (uint32_t slot = 0; Pm4::COMPUTE_USER_ACCUM_0 + slot < Pm4::COMPUTE_USER_DATA_0; slot++) {
|
||||
g_hw_sh_func[Pm4::COMPUTE_USER_ACCUM_0 + slot] = HwShSetCsUserAccumSgpr;
|
||||
}
|
||||
g_hw_sh_func[Pm4::COMPUTE_PGM_LO] = HwShSetCsRegisters;
|
||||
g_hw_sh_func[Pm4::COMPUTE_PGM_HI] = HwShSetCsRegisters;
|
||||
g_hw_sh_func[Pm4::COMPUTE_TBA_LO] = HwShSetCsRegisters;
|
||||
g_hw_sh_func[Pm4::COMPUTE_TBA_HI] = HwShSetCsRegisters;
|
||||
g_hw_sh_func[Pm4::COMPUTE_TMA_LO] = HwShSetCsRegisters;
|
||||
g_hw_sh_func[Pm4::COMPUTE_TMA_HI] = HwShSetCsRegisters;
|
||||
g_hw_sh_func[Pm4::COMPUTE_PGM_RSRC1] = HwShSetCsRegisters;
|
||||
g_hw_sh_func[Pm4::COMPUTE_PGM_RSRC2] = HwShSetCsRegisters;
|
||||
g_hw_sh_func[Pm4::COMPUTE_START_X] = HwShSetCsRegisters;
|
||||
g_hw_sh_func[Pm4::COMPUTE_START_Y] = HwShSetCsRegisters;
|
||||
g_hw_sh_func[Pm4::COMPUTE_START_Z] = HwShSetCsRegisters;
|
||||
g_hw_sh_func[Pm4::COMPUTE_NUM_THREAD_X] = HwShSetCsRegisters;
|
||||
g_hw_sh_func[Pm4::COMPUTE_NUM_THREAD_Y] = HwShSetCsRegisters;
|
||||
g_hw_sh_func[Pm4::COMPUTE_NUM_THREAD_Z] = HwShSetCsRegisters;
|
||||
g_hw_sh_func[Pm4::COMPUTE_RESOURCE_LIMITS] = HwShSetCsRegisters;
|
||||
g_hw_sh_func[Pm4::COMPUTE_DESTINATION_EN_SE0] = HwShSetCsRegisters;
|
||||
g_hw_sh_func[Pm4::COMPUTE_DESTINATION_EN_SE1] = HwShSetCsRegisters;
|
||||
g_hw_sh_func[Pm4::COMPUTE_TMPRING_SIZE] = HwShSetCsRegisters;
|
||||
g_hw_sh_func[Pm4::COMPUTE_DESTINATION_EN_SE2] = HwShSetCsRegisters;
|
||||
g_hw_sh_func[Pm4::COMPUTE_DESTINATION_EN_SE3] = HwShSetCsRegisters;
|
||||
g_hw_sh_func[Pm4::COMPUTE_PGM_RSRC3] = HwShSetCsRegisters;
|
||||
g_hw_sh_func[Pm4::COMPUTE_SHADER_CHKSUM] = HwShSetCsRegisters;
|
||||
g_hw_sh_func[Pm4::COMPUTE_DISPATCH_TUNNEL] = HwShSetCsRegisters;
|
||||
g_hw_sh_func[Pm4::SPI_SHADER_TBA_LO_PS] = HwShIgnoreRegisters;
|
||||
g_hw_sh_func[Pm4::SPI_SHADER_PGM_RSRC3_PS] = HwShIgnoreRegisters;
|
||||
g_hw_sh_func[Pm4::SPI_SHADER_PGM_RSRC4_PS] = HwShIgnoreRegisters;
|
||||
g_hw_sh_func[Pm4::SPI_SHADER_TMA_LO_PS] = HwShIgnoreRegisters;
|
||||
g_hw_sh_func[Pm4::SPI_SHADER_TMA_HI_PS] = HwShIgnoreRegisters;
|
||||
g_hw_sh_func[Pm4::SPI_SHADER_REQ_CTRL_PS] = HwShIgnoreRegisters;
|
||||
g_hw_sh_func[Pm4::SPI_SHADER_PGM_RSRC3_GS] = HwShIgnoreRegisters;
|
||||
g_hw_sh_func[Pm4::SPI_SHADER_PGM_RSRC4_GS] = HwShIgnoreRegisters;
|
||||
g_hw_sh_func[Pm4::SPI_SHADER_USER_DATA_ADDR_LO_GS] = HwShIgnoreRegisters;
|
||||
g_hw_sh_func[Pm4::SPI_SHADER_USER_DATA_ADDR_HI_GS] = HwShIgnoreRegisters;
|
||||
g_hw_sh_func[Pm4::SPI_SHADER_REQ_CTRL_ESGS] = HwShIgnoreRegisters;
|
||||
g_hw_sh_func[Pm4::SPI_SHADER_PGM_RSRC3_HS] = HwShIgnoreRegisters;
|
||||
g_hw_sh_func[Pm4::SPI_SHADER_PGM_RSRC4_HS] = HwShIgnoreRegisters;
|
||||
g_hw_sh_func[Pm4::SPI_SHADER_USER_DATA_ADDR_LO_HS] = HwShIgnoreRegisters;
|
||||
g_hw_sh_func[Pm4::SPI_SHADER_USER_DATA_ADDR_HI_HS] = HwShIgnoreRegisters;
|
||||
g_hw_sh_func[Pm4::SPI_SHADER_REQ_CTRL_LSHS] = HwShIgnoreRegisters;
|
||||
|
||||
for (auto& func: g_hw_uc_func) {
|
||||
func = nullptr;
|
||||
}
|
||||
|
||||
g_hw_uc_func[Pm4::VGT_PRIMITIVE_TYPE] = HwUcSetPrimitiveType;
|
||||
g_hw_uc_func[Pm4::VGT_INDEX_TYPE] = HwUcSetIndexType;
|
||||
g_hw_uc_func[Pm4::VGT_OBJECT_ID] = HwUcSetObjectId;
|
||||
g_hw_uc_func[Pm4::TEXTURE_GRADIENT_FACTORS] = HwUcSetTextureGradientFactors;
|
||||
g_hw_uc_func[Pm4::IA_MULTI_VGT_PARAM] = HwUcSetIaMultiVgtParam;
|
||||
g_hw_uc_func[Pm4::GE_MULTI_PRIM_IB_RESET_EN] = HwUcSetMultiPrimIbReset;
|
||||
g_hw_uc_func[Pm4::TA_CS_BC_BASE_ADDR] = HwUcSetBorderColorTableAddr;
|
||||
g_hw_uc_func[Pm4::TA_CS_BC_BASE_ADDR_HI] = HwUcSetBorderColorTableAddr;
|
||||
g_hw_uc_func[Pm4::GE_INDX_OFFSET] = HwUcSetGeIndexOffset;
|
||||
g_hw_uc_func[Pm4::GDS_OA_CNTL] = HwUcSetGdsOaRegisters;
|
||||
g_hw_uc_func[Pm4::GDS_OA_COUNTER] = HwUcSetGdsOaRegisters;
|
||||
g_hw_uc_func[Pm4::GDS_OA_ADDRESS] = HwUcSetGdsOaRegisters;
|
||||
|
||||
for (auto& func: g_cp_op_func) {
|
||||
func = nullptr;
|
||||
}
|
||||
|
||||
g_cp_op_func[Pm4::IT_NOP] = CpOpNop;
|
||||
g_cp_op_func[Pm4::IT_SET_BASE] = CpOpSetBase;
|
||||
g_cp_op_func[Pm4::IT_CLEAR_STATE] = CpOpClearState;
|
||||
g_cp_op_func[Pm4::IT_INDEX_BASE] = CpOpIndexBase;
|
||||
g_cp_op_func[Pm4::IT_INDEX_BUFFER_SIZE] = CpOpIndexBufferSize;
|
||||
g_cp_op_func[Pm4::IT_DISPATCH_DIRECT] = CpOpDispatchDirect;
|
||||
g_cp_op_func[Pm4::IT_DISPATCH_INDIRECT] = CpOpDispatchIndirect;
|
||||
g_cp_op_func[Pm4::IT_DRAW_INDIRECT] = CpOpDrawIndirect;
|
||||
g_cp_op_func[Pm4::IT_DRAW_INDEX_INDIRECT] = CpOpDrawIndirect;
|
||||
g_cp_op_func[Pm4::IT_DRAW_INDIRECT_MULTI] = CpOpDrawIndirectMulti;
|
||||
g_cp_op_func[Pm4::IT_DRAW_INDEX_INDIRECT_MULTI] = CpOpDrawIndirectMulti;
|
||||
g_cp_op_func[Pm4::IT_DRAW_INDEX_2] = CpOpDrawIndex;
|
||||
g_cp_op_func[Pm4::IT_DRAW_INDEX_OFFSET_2] = CpOpDrawIndexOffset;
|
||||
g_cp_op_func[Pm4::IT_DISPATCH_DRAW_PREAMBLE] = CpOpDrawIndex;
|
||||
g_cp_op_func[Pm4::IT_PFP_SYNC_ME] = CpOpPfpSyncMe;
|
||||
g_cp_op_func[Pm4::IT_INDEX_TYPE] = CpOpIndexType;
|
||||
g_cp_op_func[Pm4::IT_NUM_INSTANCES] = CpOpNumInstances;
|
||||
g_cp_op_func[Pm4::IT_DRAW_INDEX_AUTO] = CpOpDrawIndexAuto;
|
||||
g_cp_op_func[Pm4::IT_COND_EXEC] = CpOpCondExec;
|
||||
g_cp_op_func[Pm4::IT_SET_PREDICATION] = CpOpSetPredication;
|
||||
g_cp_op_func[Pm4::IT_WRITE_DATA] = CpOpWriteData;
|
||||
g_cp_op_func[Pm4::IT_INDIRECT_BUFFER] = CpOpIndirectBuffer;
|
||||
g_cp_op_func[Pm4::IT_COPY_DATA] = CpOpCopyData;
|
||||
g_cp_op_func[Pm4::IT_EVENT_WRITE] = CpOpEventWrite;
|
||||
g_cp_op_func[Pm4::IT_EVENT_WRITE_EOP] = CpOpEventWriteEop;
|
||||
g_cp_op_func[Pm4::IT_EVENT_WRITE_EOS] = CpOpEventWriteEos;
|
||||
g_cp_op_func[Pm4::IT_DMA_DATA] = CpOpDmaData;
|
||||
g_cp_op_func[Pm4::IT_ACQUIRE_MEM] = CpOpAcquireMem;
|
||||
g_cp_op_func[Pm4::IT_SET_CONTEXT_REG] = CpOpSetContextReg;
|
||||
g_cp_op_func[Pm4::IT_SET_SH_REG] = CpOpSetShaderReg;
|
||||
g_cp_op_func[Pm4::IT_SET_UCONFIG_REG] = CpOpSetUconfigReg;
|
||||
g_cp_op_func[Pm4::IT_SET_UCONFIG_REG_INDEX] = CpOpSetUconfigReg;
|
||||
g_cp_op_func[Pm4::IT_SET_CONTEXT_REG_INDIRECT] = CpOpIndirectCxRegs;
|
||||
g_cp_op_func[Pm4::IT_SET_SH_REG_INDIRECT] = CpOpIndirectShRegs;
|
||||
g_cp_op_func[Pm4::IT_SET_UCONFIG_REG_INDIRECT] = CpOpIndirectUcRegs;
|
||||
g_cp_op_func[Pm4::IT_WRITE_CONST_RAM] = CpOpWriteConstRam;
|
||||
g_cp_op_func[Pm4::IT_DUMP_CONST_RAM] = CpOpDumpConstRam;
|
||||
g_cp_op_func[Pm4::IT_INCREMENT_CE_COUNTER] = CpOpIncrementCeCounter;
|
||||
g_cp_op_func[Pm4::IT_INCREMENT_DE_COUNTER] = CpOpIncrementDeCounter;
|
||||
g_cp_op_func[Pm4::IT_WAIT_ON_CE_COUNTER] = CpOpWaitOnCeCounter;
|
||||
g_cp_op_func[Pm4::IT_WAIT_ON_DE_COUNTER_DIFF] = CpOpWaitOnDeCounterDiff;
|
||||
g_cp_op_func[Pm4::IT_GET_LOD_STATS] = CpOpGetLodStats;
|
||||
|
||||
for (auto& func: g_cp_op_custom_func) {
|
||||
func = nullptr;
|
||||
}
|
||||
|
||||
g_cp_op_custom_func[Pm4::R_DISPATCH_RESET] = CpOpDispatchReset;
|
||||
g_cp_op_custom_func[Pm4::R_WAIT_MEM_32] = CpOpWaitRegMem32;
|
||||
g_cp_op_custom_func[Pm4::R_WAIT_FLIP_DONE] = CpOpWaitFlipDone;
|
||||
g_cp_op_custom_func[Pm4::R_PUSH_MARKER] = CpOpPushMarker;
|
||||
g_cp_op_custom_func[Pm4::R_POP_MARKER] = CpOpPopMarker;
|
||||
g_cp_op_custom_func[Pm4::R_ACQUIRE_MEM] = CpOpAcquireMem;
|
||||
g_cp_op_custom_func[Pm4::R_WAIT_MEM_64] = CpOpWaitRegMem64;
|
||||
g_cp_op_custom_func[Pm4::R_FLIP] = CpOpFlip;
|
||||
g_cp_op_custom_func[Pm4::R_RELEASE_MEM] = CpOpReleaseMem;
|
||||
|
||||
GraphicsInitJmpTablesCxIndirect();
|
||||
GraphicsInitJmpTablesShIndirect();
|
||||
GraphicsInitJmpTablesUcIndirect();
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
@@ -0,0 +1,171 @@
|
||||
#ifndef GRAPHICS_GUEST_GPU_COMMAND_PROCESSOR_PM4_DISPATCH_H
|
||||
#define GRAPHICS_GUEST_GPU_COMMAND_PROCESSOR_PM4_DISPATCH_H
|
||||
|
||||
#include "graphics/guest_gpu/pm4.h"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
class CommandProcessor;
|
||||
|
||||
using hw_ctx_parser_func_t = uint32_t (*)(CommandProcessor*, uint32_t, uint32_t, const uint32_t*,
|
||||
uint32_t);
|
||||
using hw_uc_parser_func_t = uint32_t (*)(CommandProcessor*, uint32_t, uint32_t, const uint32_t*,
|
||||
uint32_t);
|
||||
using hw_sh_parser_func_t = uint32_t (*)(CommandProcessor*, uint32_t, uint32_t, const uint32_t*,
|
||||
uint32_t);
|
||||
using cp_op_parser_func_t = uint32_t (*)(CommandProcessor*, uint32_t, const uint32_t*, uint32_t,
|
||||
uint32_t);
|
||||
using hw_ctx_indirect_func_t = void (*)(CommandProcessor*, uint32_t, uint32_t);
|
||||
using hw_uc_indirect_func_t = void (*)(CommandProcessor*, uint32_t, uint32_t);
|
||||
using hw_sh_indirect_func_t = void (*)(CommandProcessor*, uint32_t, uint32_t);
|
||||
|
||||
extern hw_ctx_parser_func_t g_hw_ctx_func[Pm4::CX_NUM];
|
||||
extern hw_ctx_indirect_func_t g_hw_ctx_indirect_func[Pm4::CX_NUM];
|
||||
extern hw_sh_parser_func_t g_hw_sh_func[Pm4::SH_NUM];
|
||||
extern hw_sh_indirect_func_t g_hw_sh_indirect_func[Pm4::SH_NUM];
|
||||
extern hw_uc_parser_func_t g_hw_uc_func[Pm4::UC_NUM];
|
||||
extern hw_uc_indirect_func_t g_hw_uc_indirect_func[Pm4::UC_NUM];
|
||||
extern cp_op_parser_func_t g_cp_op_func[256];
|
||||
extern cp_op_parser_func_t g_cp_op_custom_func[Pm4::R_NUM];
|
||||
|
||||
void GraphicsInitJmpTables();
|
||||
void GraphicsInitJmpTablesCxIndirect();
|
||||
void GraphicsInitJmpTablesShIndirect();
|
||||
void GraphicsInitJmpTablesUcIndirect();
|
||||
|
||||
uint32_t HwCtxSetRenderControl(CommandProcessor*, uint32_t, uint32_t, const uint32_t*, uint32_t);
|
||||
uint32_t HwCtxSetDepthMetadataRegisters(CommandProcessor*, uint32_t, uint32_t, const uint32_t*,
|
||||
uint32_t);
|
||||
uint32_t HwCtxSetBorderColorTableAddr(CommandProcessor*, uint32_t, uint32_t, const uint32_t*,
|
||||
uint32_t);
|
||||
uint32_t HwCtxSetStencilClear(CommandProcessor*, uint32_t, uint32_t, const uint32_t*, uint32_t);
|
||||
uint32_t HwCtxSetDepthClear(CommandProcessor*, uint32_t, uint32_t, const uint32_t*, uint32_t);
|
||||
uint32_t HwCtxSetScreenScissor(CommandProcessor*, uint32_t, uint32_t, const uint32_t*, uint32_t);
|
||||
uint32_t HwCtxSetDepthRenderTarget(CommandProcessor*, uint32_t, uint32_t, const uint32_t*,
|
||||
uint32_t);
|
||||
uint32_t HwCtxSetStencilInfo(CommandProcessor*, uint32_t, uint32_t, const uint32_t*, uint32_t);
|
||||
uint32_t HwCtxSetDepthHtileSurface(CommandProcessor*, uint32_t, uint32_t, const uint32_t*,
|
||||
uint32_t);
|
||||
uint32_t HwCtxSetHardwareScreenOffset(CommandProcessor*, uint32_t, uint32_t, const uint32_t*,
|
||||
uint32_t);
|
||||
uint32_t HwCtxSetWindowOffset(CommandProcessor*, uint32_t, uint32_t, const uint32_t*, uint32_t);
|
||||
uint32_t HwCtxSetWindowScissor(CommandProcessor*, uint32_t, uint32_t, const uint32_t*, uint32_t);
|
||||
uint32_t HwCtxSetClipRect(CommandProcessor*, uint32_t, uint32_t, const uint32_t*, uint32_t);
|
||||
uint32_t HwCtxSetRenderTargetMask(CommandProcessor*, uint32_t, uint32_t, const uint32_t*, uint32_t);
|
||||
uint32_t HwCtxSetGenericScissor(CommandProcessor*, uint32_t, uint32_t, const uint32_t*, uint32_t);
|
||||
uint32_t HwCtxSetBlendColor(CommandProcessor*, uint32_t, uint32_t, const uint32_t*, uint32_t);
|
||||
uint32_t HwCtxSetCbDccControl(CommandProcessor*, uint32_t, uint32_t, const uint32_t*, uint32_t);
|
||||
uint32_t HwCtxSetStencilControl(CommandProcessor*, uint32_t, uint32_t, const uint32_t*, uint32_t);
|
||||
uint32_t HwCtxSetStencilMask(CommandProcessor*, uint32_t, uint32_t, const uint32_t*, uint32_t);
|
||||
uint32_t HwCtxSetPsInput(CommandProcessor*, uint32_t, uint32_t, const uint32_t*, uint32_t);
|
||||
uint32_t HwCtxSetSpiTmpringSize(CommandProcessor*, uint32_t, uint32_t, const uint32_t*, uint32_t);
|
||||
uint32_t HwCtxSetDepthControl(CommandProcessor*, uint32_t, uint32_t, const uint32_t*, uint32_t);
|
||||
uint32_t HwCtxSetEqaaControl(CommandProcessor*, uint32_t, uint32_t, const uint32_t*, uint32_t);
|
||||
uint32_t HwCtxSetColorControl(CommandProcessor*, uint32_t, uint32_t, const uint32_t*, uint32_t);
|
||||
uint32_t HwCtxSetDepthBounds(CommandProcessor*, uint32_t, uint32_t, const uint32_t*, uint32_t);
|
||||
uint32_t HwCtxSetPointState(CommandProcessor*, uint32_t, uint32_t, const uint32_t*, uint32_t);
|
||||
uint32_t HwCtxSetClipControl(CommandProcessor*, uint32_t, uint32_t, const uint32_t*, uint32_t);
|
||||
uint32_t HwCtxSetModeControl(CommandProcessor*, uint32_t, uint32_t, const uint32_t*, uint32_t);
|
||||
uint32_t HwCtxSetPolyOffsetRegisters(CommandProcessor*, uint32_t, uint32_t, const uint32_t*,
|
||||
uint32_t);
|
||||
uint32_t HwCtxSetViewportTransformControl(CommandProcessor*, uint32_t, uint32_t, const uint32_t*,
|
||||
uint32_t);
|
||||
uint32_t HwCtxSetLineControl(CommandProcessor*, uint32_t, uint32_t, const uint32_t*, uint32_t);
|
||||
uint32_t HwCtxSetFovWindow(CommandProcessor*, uint32_t, uint32_t, const uint32_t*, uint32_t);
|
||||
uint32_t HwCtxSetScanModeControl(CommandProcessor*, uint32_t, uint32_t, const uint32_t*, uint32_t);
|
||||
uint32_t HwCtxSetScanModeControl1(CommandProcessor*, uint32_t, uint32_t, const uint32_t*, uint32_t);
|
||||
uint32_t HwCtxSetAaConfig(CommandProcessor*, uint32_t, uint32_t, const uint32_t*, uint32_t);
|
||||
uint32_t HwCtxSetAaSampleControl(CommandProcessor*, uint32_t, uint32_t, const uint32_t*, uint32_t);
|
||||
uint32_t HwCtxSetCentroidPriority(CommandProcessor*, uint32_t, uint32_t, const uint32_t*, uint32_t);
|
||||
uint32_t HwCtxSetAaMask(CommandProcessor*, uint32_t, uint32_t, const uint32_t*, uint32_t);
|
||||
uint32_t HwCtxSetVtxControl(CommandProcessor*, uint32_t, uint32_t, const uint32_t*, uint32_t);
|
||||
uint32_t HwCtxSetPaScExtendedControl(CommandProcessor*, uint32_t, uint32_t, const uint32_t*,
|
||||
uint32_t);
|
||||
uint32_t HwCtxSetAlphaToMask(CommandProcessor*, uint32_t, uint32_t, const uint32_t*, uint32_t);
|
||||
uint32_t HwCtxSetDrawPayloadControl(CommandProcessor*, uint32_t, uint32_t, const uint32_t*,
|
||||
uint32_t);
|
||||
uint32_t HwCtxSetPrimitiveIdReset(CommandProcessor*, uint32_t, uint32_t, const uint32_t*, uint32_t);
|
||||
uint32_t HwCtxSetObjprimIdControl(CommandProcessor*, uint32_t, uint32_t, const uint32_t*, uint32_t);
|
||||
uint32_t HwCtxSetShaderStages(CommandProcessor*, uint32_t, uint32_t, const uint32_t*, uint32_t);
|
||||
uint32_t HwCtxSetGuardBands(CommandProcessor*, uint32_t, uint32_t, const uint32_t*, uint32_t);
|
||||
uint32_t HwCtxSetRenderTarget(CommandProcessor*, uint32_t, uint32_t, const uint32_t*, uint32_t);
|
||||
uint32_t HwCtxSetColorInfo(CommandProcessor*, uint32_t, uint32_t, const uint32_t*, uint32_t);
|
||||
uint32_t HwCtxSetBlendControl(CommandProcessor*, uint32_t, uint32_t, const uint32_t*, uint32_t);
|
||||
uint32_t HwCtxSetViewportScissor(CommandProcessor*, uint32_t, uint32_t, const uint32_t*, uint32_t);
|
||||
uint32_t HwCtxSetViewportZ(CommandProcessor*, uint32_t, uint32_t, const uint32_t*, uint32_t);
|
||||
uint32_t HwCtxSetViewportScaleOffset(CommandProcessor*, uint32_t, uint32_t, const uint32_t*,
|
||||
uint32_t);
|
||||
uint32_t HwShSetPsUserSgpr(CommandProcessor*, uint32_t, uint32_t, const uint32_t*, uint32_t);
|
||||
uint32_t HwShSetGsUserSgpr(CommandProcessor*, uint32_t, uint32_t, const uint32_t*, uint32_t);
|
||||
uint32_t HwShSetHsUserSgpr(CommandProcessor*, uint32_t, uint32_t, const uint32_t*, uint32_t);
|
||||
uint32_t HwShSetCsUserSgpr(CommandProcessor*, uint32_t, uint32_t, const uint32_t*, uint32_t);
|
||||
uint32_t HwShSetPsUserAccumSgpr(CommandProcessor*, uint32_t, uint32_t, const uint32_t*, uint32_t);
|
||||
uint32_t HwShSetGsUserAccumSgpr(CommandProcessor*, uint32_t, uint32_t, const uint32_t*, uint32_t);
|
||||
uint32_t HwShSetHsUserAccumSgpr(CommandProcessor*, uint32_t, uint32_t, const uint32_t*, uint32_t);
|
||||
uint32_t HwShSetCsUserAccumSgpr(CommandProcessor*, uint32_t, uint32_t, const uint32_t*, uint32_t);
|
||||
uint32_t HwShSetCsRegisters(CommandProcessor*, uint32_t, uint32_t, const uint32_t*, uint32_t);
|
||||
uint32_t HwShIgnoreRegisters(CommandProcessor*, uint32_t, uint32_t, const uint32_t*, uint32_t);
|
||||
uint32_t HwUcSetPrimitiveType(CommandProcessor*, uint32_t, uint32_t, const uint32_t*, uint32_t);
|
||||
uint32_t HwUcSetIndexType(CommandProcessor*, uint32_t, uint32_t, const uint32_t*, uint32_t);
|
||||
uint32_t HwUcSetObjectId(CommandProcessor*, uint32_t, uint32_t, const uint32_t*, uint32_t);
|
||||
uint32_t HwUcSetTextureGradientFactors(CommandProcessor*, uint32_t, uint32_t, const uint32_t*,
|
||||
uint32_t);
|
||||
uint32_t HwUcSetIaMultiVgtParam(CommandProcessor*, uint32_t, uint32_t, const uint32_t*, uint32_t);
|
||||
uint32_t HwUcSetMultiPrimIbReset(CommandProcessor*, uint32_t, uint32_t, const uint32_t*, uint32_t);
|
||||
uint32_t HwUcSetBorderColorTableAddr(CommandProcessor*, uint32_t, uint32_t, const uint32_t*,
|
||||
uint32_t);
|
||||
uint32_t HwUcSetGeIndexOffset(CommandProcessor*, uint32_t, uint32_t, const uint32_t*, uint32_t);
|
||||
uint32_t HwUcSetGdsOaRegisters(CommandProcessor*, uint32_t, uint32_t, const uint32_t*, uint32_t);
|
||||
|
||||
uint32_t CpOpNop(CommandProcessor*, uint32_t, const uint32_t*, uint32_t, uint32_t);
|
||||
uint32_t CpOpSetBase(CommandProcessor*, uint32_t, const uint32_t*, uint32_t, uint32_t);
|
||||
uint32_t CpOpClearState(CommandProcessor*, uint32_t, const uint32_t*, uint32_t, uint32_t);
|
||||
uint32_t CpOpIndexBase(CommandProcessor*, uint32_t, const uint32_t*, uint32_t, uint32_t);
|
||||
uint32_t CpOpIndexBufferSize(CommandProcessor*, uint32_t, const uint32_t*, uint32_t, uint32_t);
|
||||
uint32_t CpOpDispatchDirect(CommandProcessor*, uint32_t, const uint32_t*, uint32_t, uint32_t);
|
||||
uint32_t CpOpDispatchIndirect(CommandProcessor*, uint32_t, const uint32_t*, uint32_t, uint32_t);
|
||||
uint32_t CpOpDrawIndirect(CommandProcessor*, uint32_t, const uint32_t*, uint32_t, uint32_t);
|
||||
uint32_t CpOpDrawIndirectMulti(CommandProcessor*, uint32_t, const uint32_t*, uint32_t, uint32_t);
|
||||
uint32_t CpOpDrawIndex(CommandProcessor*, uint32_t, const uint32_t*, uint32_t, uint32_t);
|
||||
uint32_t CpOpDrawIndexOffset(CommandProcessor*, uint32_t, const uint32_t*, uint32_t, uint32_t);
|
||||
uint32_t CpOpPfpSyncMe(CommandProcessor*, uint32_t, const uint32_t*, uint32_t, uint32_t);
|
||||
uint32_t CpOpIndexType(CommandProcessor*, uint32_t, const uint32_t*, uint32_t, uint32_t);
|
||||
uint32_t CpOpNumInstances(CommandProcessor*, uint32_t, const uint32_t*, uint32_t, uint32_t);
|
||||
uint32_t CpOpDrawIndexAuto(CommandProcessor*, uint32_t, const uint32_t*, uint32_t, uint32_t);
|
||||
uint32_t CpOpCondExec(CommandProcessor*, uint32_t, const uint32_t*, uint32_t, uint32_t);
|
||||
uint32_t CpOpSetPredication(CommandProcessor*, uint32_t, const uint32_t*, uint32_t, uint32_t);
|
||||
uint32_t CpOpWriteData(CommandProcessor*, uint32_t, const uint32_t*, uint32_t, uint32_t);
|
||||
uint32_t CpOpIndirectBuffer(CommandProcessor*, uint32_t, const uint32_t*, uint32_t, uint32_t);
|
||||
uint32_t CpOpCopyData(CommandProcessor*, uint32_t, const uint32_t*, uint32_t, uint32_t);
|
||||
uint32_t CpOpEventWrite(CommandProcessor*, uint32_t, const uint32_t*, uint32_t, uint32_t);
|
||||
uint32_t CpOpEventWriteEop(CommandProcessor*, uint32_t, const uint32_t*, uint32_t, uint32_t);
|
||||
uint32_t CpOpEventWriteEos(CommandProcessor*, uint32_t, const uint32_t*, uint32_t, uint32_t);
|
||||
uint32_t CpOpDmaData(CommandProcessor*, uint32_t, const uint32_t*, uint32_t, uint32_t);
|
||||
uint32_t CpOpAcquireMem(CommandProcessor*, uint32_t, const uint32_t*, uint32_t, uint32_t);
|
||||
uint32_t CpOpSetContextReg(CommandProcessor*, uint32_t, const uint32_t*, uint32_t, uint32_t);
|
||||
uint32_t CpOpSetShaderReg(CommandProcessor*, uint32_t, const uint32_t*, uint32_t, uint32_t);
|
||||
uint32_t CpOpSetUconfigReg(CommandProcessor*, uint32_t, const uint32_t*, uint32_t, uint32_t);
|
||||
uint32_t CpOpIndirectCxRegs(CommandProcessor*, uint32_t, const uint32_t*, uint32_t, uint32_t);
|
||||
uint32_t CpOpIndirectShRegs(CommandProcessor*, uint32_t, const uint32_t*, uint32_t, uint32_t);
|
||||
uint32_t CpOpIndirectUcRegs(CommandProcessor*, uint32_t, const uint32_t*, uint32_t, uint32_t);
|
||||
uint32_t CpOpWriteConstRam(CommandProcessor*, uint32_t, const uint32_t*, uint32_t, uint32_t);
|
||||
uint32_t CpOpDumpConstRam(CommandProcessor*, uint32_t, const uint32_t*, uint32_t, uint32_t);
|
||||
uint32_t CpOpIncrementCeCounter(CommandProcessor*, uint32_t, const uint32_t*, uint32_t, uint32_t);
|
||||
uint32_t CpOpIncrementDeCounter(CommandProcessor*, uint32_t, const uint32_t*, uint32_t, uint32_t);
|
||||
uint32_t CpOpWaitOnCeCounter(CommandProcessor*, uint32_t, const uint32_t*, uint32_t, uint32_t);
|
||||
uint32_t CpOpWaitOnDeCounterDiff(CommandProcessor*, uint32_t, const uint32_t*, uint32_t, uint32_t);
|
||||
uint32_t CpOpGetLodStats(CommandProcessor*, uint32_t, const uint32_t*, uint32_t, uint32_t);
|
||||
uint32_t CpOpDispatchReset(CommandProcessor*, uint32_t, const uint32_t*, uint32_t, uint32_t);
|
||||
uint32_t CpOpWaitRegMem32(CommandProcessor*, uint32_t, const uint32_t*, uint32_t, uint32_t);
|
||||
uint32_t CpOpWaitFlipDone(CommandProcessor*, uint32_t, const uint32_t*, uint32_t, uint32_t);
|
||||
uint32_t CpOpPushMarker(CommandProcessor*, uint32_t, const uint32_t*, uint32_t, uint32_t);
|
||||
uint32_t CpOpPopMarker(CommandProcessor*, uint32_t, const uint32_t*, uint32_t, uint32_t);
|
||||
uint32_t CpOpWaitRegMem64(CommandProcessor*, uint32_t, const uint32_t*, uint32_t, uint32_t);
|
||||
uint32_t CpOpFlip(CommandProcessor*, uint32_t, const uint32_t*, uint32_t, uint32_t);
|
||||
uint32_t CpOpReleaseMem(CommandProcessor*, uint32_t, const uint32_t*, uint32_t, uint32_t);
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
#endif // GRAPHICS_GUEST_GPU_COMMAND_PROCESSOR_PM4_DISPATCH_H
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,437 @@
|
||||
#ifndef EMULATOR_INCLUDE_EMULATOR_GRAPHICS_GUEST_GPU_GPU_DEFS_H_
|
||||
#define EMULATOR_INCLUDE_EMULATOR_GRAPHICS_GUEST_GPU_GPU_DEFS_H_
|
||||
|
||||
#include "common/common.h"
|
||||
|
||||
#include <type_traits>
|
||||
|
||||
namespace Libs::Graphics::Prospero {
|
||||
|
||||
template <typename T>
|
||||
constexpr auto GpuEnumValue(T value) {
|
||||
return static_cast<std::underlying_type_t<T>>(value);
|
||||
}
|
||||
|
||||
enum class PrimitiveType : uint32_t {
|
||||
kNone = 0,
|
||||
kPointList = 1,
|
||||
kLineList = 2,
|
||||
kLineStrip = 3,
|
||||
kTriList = 4,
|
||||
kTriFan = 5,
|
||||
kTriStrip = 6,
|
||||
kRectList = 7,
|
||||
kPatch = 9,
|
||||
kLineListAdjacency = 10,
|
||||
kLineStripAdjacency = 11,
|
||||
kTriListAdjacency = 12,
|
||||
kTriStripAdjacency = 13,
|
||||
kRectListLegacy = 17,
|
||||
kLineLoop = 18,
|
||||
kQuadListLegacy = 19,
|
||||
kQuadStripLegacy = 20,
|
||||
kPolygon = 21,
|
||||
};
|
||||
|
||||
enum class IndexType : uint32_t {
|
||||
kIndex16 = 0,
|
||||
kIndex32 = 1,
|
||||
kIndex8 = 2,
|
||||
};
|
||||
|
||||
enum class ShaderBinaryType : uint8_t {
|
||||
kCs = 0,
|
||||
kPs = 1,
|
||||
kGs = 2,
|
||||
kHs = 3,
|
||||
kGsFront = 4,
|
||||
kHsFront = 5,
|
||||
kGsBack = 6,
|
||||
kHsBack = 7,
|
||||
kFs = 8,
|
||||
};
|
||||
|
||||
enum class GsOutputPrimitiveType : uint32_t {
|
||||
kPoints = 0,
|
||||
kLines = 1,
|
||||
kTriangles = 2,
|
||||
k2dRectangle = 3,
|
||||
kRectList = 4,
|
||||
};
|
||||
|
||||
enum class ChannelLayout : uint32_t {
|
||||
kInvalid = 0,
|
||||
k8 = 1,
|
||||
k16 = 2,
|
||||
k8_8 = 3,
|
||||
k32 = 4,
|
||||
k16_16 = 5,
|
||||
k11_11_10 = 6,
|
||||
k10_10_10_2 = 9,
|
||||
k8_8_8_8 = 10,
|
||||
k32_32 = 11,
|
||||
k16_16_16_16 = 12,
|
||||
k32_32_32_32 = 14,
|
||||
k5_5_5_1 = 17,
|
||||
k4_4_4_4 = 19,
|
||||
kBc1 = 35,
|
||||
kBc2 = 36,
|
||||
kBc3 = 37,
|
||||
};
|
||||
|
||||
enum class ChannelType : uint32_t {
|
||||
kUNorm = 0,
|
||||
kSNorm = 1,
|
||||
kUInt = 4,
|
||||
kSInt = 5,
|
||||
kSrgb = 6,
|
||||
kFloat = 7,
|
||||
};
|
||||
|
||||
enum class NumberFormat : uint32_t {
|
||||
kUNorm = 0,
|
||||
kSNorm = 1,
|
||||
kUInt = 4,
|
||||
kSInt = 5,
|
||||
kFloat = 7,
|
||||
kSrgb = 9,
|
||||
};
|
||||
|
||||
enum class ChannelOrder : uint32_t {
|
||||
kStandard = 0,
|
||||
kAlt = 1,
|
||||
kReversed = 2,
|
||||
kAltReversed = 3,
|
||||
};
|
||||
|
||||
enum class DepthFormat : uint32_t {
|
||||
kInvalid = 0,
|
||||
kZ16 = 1,
|
||||
kZ32F = 3,
|
||||
};
|
||||
|
||||
enum class StencilFormat : uint32_t {
|
||||
kInvalid = 0,
|
||||
k8UInt = 1,
|
||||
};
|
||||
|
||||
enum class TileMode : uint32_t {
|
||||
kLinear = 0x00,
|
||||
kStandard256B = 0x01,
|
||||
kStandard4KB = 0x05,
|
||||
kStandard64KB = 0x09,
|
||||
kPrt = 0x11,
|
||||
kDepth = 0x18,
|
||||
kRenderTarget = 0x1b,
|
||||
};
|
||||
|
||||
enum class StencilOp : uint32_t {
|
||||
kKeep = 0x00,
|
||||
kZero = 0x01,
|
||||
kOnes = 0x02,
|
||||
kReplaceTest = 0x03,
|
||||
kReplaceOp = 0x04,
|
||||
kAddClamp = 0x05,
|
||||
kSubClamp = 0x06,
|
||||
kInvert = 0x07,
|
||||
kAddWrap = 0x08,
|
||||
kSubWrap = 0x09,
|
||||
kAnd = 0x0a,
|
||||
kOr = 0x0b,
|
||||
kXor = 0x0c,
|
||||
kNand = 0x0d,
|
||||
kNor = 0x0e,
|
||||
kXnor = 0x0f,
|
||||
};
|
||||
|
||||
enum class BlendFactor : uint32_t {
|
||||
kZero = 0x00,
|
||||
kOne = 0x01,
|
||||
kSrcColor = 0x02,
|
||||
kOneMinusSrcColor = 0x03,
|
||||
kSrcAlpha = 0x04,
|
||||
kOneMinusSrcAlpha = 0x05,
|
||||
kDstAlpha = 0x06,
|
||||
kOneMinusDstAlpha = 0x07,
|
||||
kDstColor = 0x08,
|
||||
kOneMinusDstColor = 0x09,
|
||||
kSrcAlphaSaturate = 0x0a,
|
||||
kConstantColor = 0x0d,
|
||||
kOneMinusConstantColor = 0x0e,
|
||||
kSrc1Color = 0x0f,
|
||||
kOneMinusSrc1Color = 0x10,
|
||||
kSrc1Alpha = 0x11,
|
||||
kOneMinusSrc1Alpha = 0x12,
|
||||
kConstantAlpha = 0x13,
|
||||
kOneMinusConstantAlpha = 0x14,
|
||||
};
|
||||
|
||||
enum class BlendOp : uint32_t {
|
||||
kAdd = 0x00,
|
||||
kSubtract = 0x01,
|
||||
kMin = 0x02,
|
||||
kMax = 0x03,
|
||||
kReverseSubtract = 0x04,
|
||||
};
|
||||
|
||||
enum class CompSwizzle : uint32_t {
|
||||
kZero = 0,
|
||||
kOne = 1,
|
||||
kRed = 4,
|
||||
kGreen = 5,
|
||||
kBlue = 6,
|
||||
kAlpha = 7,
|
||||
};
|
||||
|
||||
enum class SamplerClampMode : uint32_t {
|
||||
kWrap = 0,
|
||||
kMirror = 1,
|
||||
kClampLastTexel = 2,
|
||||
kMirrorOnceLastTexel = 3,
|
||||
kClampHalfBorder = 4,
|
||||
kMirrorOnceHalfBorder = 5,
|
||||
kClampBorder = 6,
|
||||
kMirrorOnceBorder = 7,
|
||||
};
|
||||
|
||||
enum class SamplerAnisoRatio : uint32_t {
|
||||
kOne = 0,
|
||||
kTwo = 1,
|
||||
kFour = 2,
|
||||
kEight = 3,
|
||||
kSixteen = 4,
|
||||
};
|
||||
|
||||
enum class SamplerBorderColor : uint32_t {
|
||||
kTransBlack = 0,
|
||||
kOpaqueBlack = 1,
|
||||
kOpaqueWhite = 2,
|
||||
kFromTable = 3,
|
||||
};
|
||||
|
||||
enum class SamplerFilter : uint32_t {
|
||||
kPoint = 0,
|
||||
kBilinear = 1,
|
||||
kAnisoPoint = 2,
|
||||
kAnisoLinear = 3,
|
||||
};
|
||||
|
||||
enum class SamplerMipFilter : uint32_t {
|
||||
kNone = 0,
|
||||
kPoint = 1,
|
||||
kLinear = 2,
|
||||
};
|
||||
|
||||
enum class ImageType : uint32_t {
|
||||
kColor1D = 8,
|
||||
kColor2D = 9,
|
||||
kColor3D = 10,
|
||||
kCube = 11,
|
||||
kColor1DArray = 12,
|
||||
kColor2DArray = 13,
|
||||
kColor2DMsaa = 14,
|
||||
kColor2DMsaaArray = 15,
|
||||
};
|
||||
|
||||
enum class DescriptorKind : uint32_t {
|
||||
kBuffer = 1,
|
||||
kSampler = 2,
|
||||
kUnused = 3,
|
||||
};
|
||||
|
||||
enum class ShaderUsageType : uint32_t {
|
||||
kReadOnlyResource = 0x00,
|
||||
kSampler = 0x01,
|
||||
kConstantBuffer = 0x02,
|
||||
kReadWriteResource = 0x04,
|
||||
kGdsMemoryRange = 0x07,
|
||||
kFetchShader = 0x12,
|
||||
kVertexBufferTable = 0x17,
|
||||
kExtendedUserData = 0x1b,
|
||||
};
|
||||
|
||||
enum class BufferFormat : uint32_t {
|
||||
kInvalid = 0,
|
||||
k8UNorm = 1,
|
||||
k8SNorm = 2,
|
||||
k8UScaled = 3,
|
||||
k8SScaled = 4,
|
||||
k8UInt = 5,
|
||||
k8SInt = 6,
|
||||
k16UNorm = 7,
|
||||
k16SNorm = 8,
|
||||
k16UScaled = 9,
|
||||
k16SScaled = 10,
|
||||
k16UInt = 11,
|
||||
k16SInt = 12,
|
||||
k16Float = 13,
|
||||
k8_8UNorm = 14,
|
||||
k8_8SNorm = 15,
|
||||
k8_8UScaled = 16,
|
||||
k8_8SScaled = 17,
|
||||
k8_8UInt = 18,
|
||||
k8_8SInt = 19,
|
||||
k32UInt = 20,
|
||||
k32SInt = 21,
|
||||
k32Float = 22,
|
||||
k16_16UNorm = 23,
|
||||
k16_16SNorm = 24,
|
||||
k16_16UScaled = 25,
|
||||
k16_16SScaled = 26,
|
||||
k16_16UInt = 27,
|
||||
k16_16SInt = 28,
|
||||
k16_16Float = 29,
|
||||
k11_11_10UNorm = 30,
|
||||
k11_11_10SNorm = 31,
|
||||
k11_11_10UScaled = 32,
|
||||
k11_11_10SScaled = 33,
|
||||
k11_11_10UInt = 34,
|
||||
k11_11_10SInt = 35,
|
||||
k11_11_10Float = 36,
|
||||
k10_11_11UNorm = 37,
|
||||
k10_11_11SNorm = 38,
|
||||
k10_11_11UScaled = 39,
|
||||
k10_11_11SScaled = 40,
|
||||
k10_11_11UInt = 41,
|
||||
k10_11_11SInt = 42,
|
||||
k10_11_11Float = 43,
|
||||
k2_10_10_10UNorm = 44,
|
||||
k2_10_10_10SNorm = 45,
|
||||
k2_10_10_10UScaled = 46,
|
||||
k2_10_10_10SScaled = 47,
|
||||
k2_10_10_10UInt = 48,
|
||||
k2_10_10_10SInt = 49,
|
||||
k10_10_10_2UNorm = 50,
|
||||
k10_10_10_2SNorm = 51,
|
||||
k10_10_10_2UScaled = 52,
|
||||
k10_10_10_2SScaled = 53,
|
||||
k10_10_10_2UInt = 54,
|
||||
k10_10_10_2SInt = 55,
|
||||
k8_8_8_8UNorm = 56,
|
||||
k8_8_8_8SNorm = 57,
|
||||
k8_8_8_8UScaled = 58,
|
||||
k8_8_8_8SScaled = 59,
|
||||
k8_8_8_8UInt = 60,
|
||||
k8_8_8_8SInt = 61,
|
||||
k32_32UInt = 62,
|
||||
k32_32SInt = 63,
|
||||
k32_32Float = 64,
|
||||
k16_16_16_16UNorm = 65,
|
||||
k16_16_16_16SNorm = 66,
|
||||
k16_16_16_16UScaled = 67,
|
||||
k16_16_16_16SScaled = 68,
|
||||
k16_16_16_16UInt = 69,
|
||||
k16_16_16_16SInt = 70,
|
||||
k16_16_16_16Float = 71,
|
||||
k32_32_32UInt = 72,
|
||||
k32_32_32SInt = 73,
|
||||
k32_32_32Float = 74,
|
||||
k32_32_32_32UInt = 75,
|
||||
k32_32_32_32SInt = 76,
|
||||
k32_32_32_32Float = 77,
|
||||
k8_8_8_8Srgb = 130,
|
||||
k9_9_9_5Float = 132,
|
||||
k5_6_5UNorm = 133,
|
||||
k5_5_5_1UNorm = 134,
|
||||
k4_4_4_4UNorm = 136,
|
||||
kFmask8_S4_F4 = 161,
|
||||
kBc1UNorm = 169,
|
||||
kBc1Srgb = 170,
|
||||
kBc2UNorm = 171,
|
||||
kBc2Srgb = 172,
|
||||
kBc3UNorm = 173,
|
||||
kBc3Srgb = 174,
|
||||
kBc4UNorm = 175,
|
||||
kBc4SNorm = 176,
|
||||
kBc5UNorm = 177,
|
||||
kBc5SNorm = 178,
|
||||
kBc6UFloat = 179,
|
||||
kBc6SFloat = 180,
|
||||
kBc7UNorm = 181,
|
||||
kBc7Srgb = 182,
|
||||
};
|
||||
|
||||
enum class VertexAttribFormat : uint32_t {
|
||||
kInvalid = 0,
|
||||
k8UNorm = 4,
|
||||
k8SNorm = 8,
|
||||
k8UScaled = 12,
|
||||
k8SScaled = 16,
|
||||
k8UInt = 20,
|
||||
k8SInt = 24,
|
||||
k16UNorm = 28,
|
||||
k16SNorm = 32,
|
||||
k16UScaled = 36,
|
||||
k16SScaled = 40,
|
||||
k16UInt = 44,
|
||||
k16SInt = 48,
|
||||
k16Float = 52,
|
||||
k8_8UNorm = 57,
|
||||
k8_8SNorm = 61,
|
||||
k8_8UScaled = 65,
|
||||
k8_8SScaled = 69,
|
||||
k8_8UInt = 73,
|
||||
k8_8SInt = 77,
|
||||
k32UInt = 80,
|
||||
k32SInt = 84,
|
||||
k32Float = 88,
|
||||
k16_16UNorm = 93,
|
||||
k16_16SNorm = 97,
|
||||
k16_16UScaled = 101,
|
||||
k16_16SScaled = 105,
|
||||
k16_16UInt = 109,
|
||||
k16_16SInt = 113,
|
||||
k16_16Float = 117,
|
||||
k11_11_10UNorm = 122,
|
||||
k11_11_10SNorm = 126,
|
||||
k11_11_10UScaled = 130,
|
||||
k11_11_10SScaled = 134,
|
||||
k11_11_10UInt = 138,
|
||||
k11_11_10SInt = 142,
|
||||
k11_11_10Float = 146,
|
||||
k10_11_11UNorm = 150,
|
||||
k10_11_11SNorm = 154,
|
||||
k10_11_11UScaled = 158,
|
||||
k10_11_11SScaled = 162,
|
||||
k10_11_11UInt = 166,
|
||||
k10_11_11SInt = 170,
|
||||
k10_11_11Float = 174,
|
||||
k2_10_10_10UNorm = 179,
|
||||
k2_10_10_10SNorm = 183,
|
||||
k2_10_10_10UScaled = 187,
|
||||
k2_10_10_10SScaled = 191,
|
||||
k2_10_10_10UInt = 195,
|
||||
k2_10_10_10SInt = 199,
|
||||
k10_10_10_2UNorm = 203,
|
||||
k10_10_10_2SNorm = 207,
|
||||
k10_10_10_2UScaled = 211,
|
||||
k10_10_10_2SScaled = 215,
|
||||
k10_10_10_2UInt = 219,
|
||||
k10_10_10_2SInt = 223,
|
||||
k8_8_8_8UNorm = 227,
|
||||
k8_8_8_8SNorm = 231,
|
||||
k8_8_8_8UScaled = 235,
|
||||
k8_8_8_8SScaled = 239,
|
||||
k8_8_8_8UInt = 243,
|
||||
k8_8_8_8SInt = 247,
|
||||
k32_32UInt = 249,
|
||||
k32_32SInt = 253,
|
||||
k32_32Float = 257,
|
||||
k16_16_16_16UNorm = 263,
|
||||
k16_16_16_16SNorm = 267,
|
||||
k16_16_16_16UScaled = 271,
|
||||
k16_16_16_16SScaled = 275,
|
||||
k16_16_16_16UInt = 279,
|
||||
k16_16_16_16SInt = 283,
|
||||
k16_16_16_16Float = 287,
|
||||
k32_32_32UInt = 290,
|
||||
k32_32_32SInt = 294,
|
||||
k32_32_32Float = 298,
|
||||
k32_32_32_32UInt = 303,
|
||||
k32_32_32_32SInt = 307,
|
||||
k32_32_32_32Float = 311,
|
||||
};
|
||||
|
||||
} // namespace Libs::Graphics::Prospero
|
||||
|
||||
#endif /* EMULATOR_INCLUDE_EMULATOR_GRAPHICS_GUEST_GPU_GPU_DEFS_H_ */
|
||||
@@ -0,0 +1,145 @@
|
||||
#include "graphics/guest_gpu/gpu_format.h"
|
||||
|
||||
#include "graphics/guest_gpu/gpu_defs.h"
|
||||
|
||||
#include <array>
|
||||
|
||||
namespace Libs::Graphics::Prospero {
|
||||
namespace {
|
||||
|
||||
constexpr FormatInfo kFormatInfo[] = {
|
||||
{GpuEnumValue(BufferFormat::k8UNorm), VK_FORMAT_R8_UNORM, 1, 0, 1, true, false},
|
||||
{GpuEnumValue(BufferFormat::k8SNorm), VK_FORMAT_UNDEFINED, 0, 0, 1, false, false},
|
||||
{GpuEnumValue(BufferFormat::k8UInt), VK_FORMAT_R8_UINT, 1, 0, 1, true, true},
|
||||
{GpuEnumValue(BufferFormat::k16UNorm), VK_FORMAT_R16_UNORM, 2, 0, 2, true, false},
|
||||
{GpuEnumValue(BufferFormat::k16SNorm), VK_FORMAT_R16_SNORM, 2, 0, 2, true, false},
|
||||
{GpuEnumValue(BufferFormat::k16UInt), VK_FORMAT_R16_UINT, 2, 0, 2, true, true},
|
||||
{GpuEnumValue(BufferFormat::k16SInt), VK_FORMAT_R16_SINT, 2, 0, 2, false, false},
|
||||
{GpuEnumValue(BufferFormat::k16Float), VK_FORMAT_R16_SFLOAT, 2, 0, 2, true, false},
|
||||
{GpuEnumValue(BufferFormat::k8_8UNorm), VK_FORMAT_R8G8_UNORM, 2, 0, 2, true, false},
|
||||
{GpuEnumValue(BufferFormat::k8_8SNorm), VK_FORMAT_R8G8_SNORM, 2, 0, 2, true, false},
|
||||
{GpuEnumValue(BufferFormat::k8_8UInt), VK_FORMAT_R8G8_UINT, 2, 0, 2, true, true},
|
||||
{GpuEnumValue(BufferFormat::k8_8SInt), VK_FORMAT_R8G8_SINT, 2, 0, 2, false, false},
|
||||
{GpuEnumValue(BufferFormat::k32UInt), VK_FORMAT_R32_UINT, 4, 0, 4, true, true},
|
||||
{GpuEnumValue(BufferFormat::k32SInt), VK_FORMAT_R32_SINT, 4, 0, 4, false, false},
|
||||
{GpuEnumValue(BufferFormat::k32Float), VK_FORMAT_R32_SFLOAT, 4, 0, 4, true, false},
|
||||
{GpuEnumValue(BufferFormat::k16_16UNorm), VK_FORMAT_R16G16_UNORM, 4, 0, 4, true, false},
|
||||
{GpuEnumValue(BufferFormat::k16_16SNorm), VK_FORMAT_R16G16_SNORM, 4, 0, 4, true, false},
|
||||
{GpuEnumValue(BufferFormat::k16_16UInt), VK_FORMAT_R16G16_UINT, 4, 0, 4, true, true},
|
||||
{GpuEnumValue(BufferFormat::k16_16SInt), VK_FORMAT_R16G16_SINT, 4, 0, 4, false, false},
|
||||
{GpuEnumValue(BufferFormat::k16_16Float), VK_FORMAT_R16G16_SFLOAT, 4, 0, 4, true, false},
|
||||
{GpuEnumValue(BufferFormat::k11_11_10Float), VK_FORMAT_B10G11R11_UFLOAT_PACK32, 4, 0, 4,
|
||||
true, false},
|
||||
{GpuEnumValue(BufferFormat::k10_10_10_2UNorm), VK_FORMAT_A2B10G10R10_UNORM_PACK32, 4, 0, 4,
|
||||
true, false},
|
||||
{GpuEnumValue(BufferFormat::k8_8_8_8UNorm), VK_FORMAT_R8G8B8A8_UNORM, 4, 0, 4, true, false},
|
||||
{GpuEnumValue(BufferFormat::k8_8_8_8SNorm), VK_FORMAT_R8G8B8A8_SNORM, 4, 0, 4, true, false},
|
||||
{GpuEnumValue(BufferFormat::k8_8_8_8UInt), VK_FORMAT_R8G8B8A8_UINT, 4, 0, 4, true, true},
|
||||
{GpuEnumValue(BufferFormat::k8_8_8_8SInt), VK_FORMAT_R8G8B8A8_SINT, 4, 0, 4, false, false},
|
||||
{GpuEnumValue(BufferFormat::k32_32UInt), VK_FORMAT_R32G32_UINT, 8, 0, 8, true, true},
|
||||
{GpuEnumValue(BufferFormat::k32_32SInt), VK_FORMAT_R32G32_SINT, 8, 0, 8, false, false},
|
||||
{GpuEnumValue(BufferFormat::k32_32Float), VK_FORMAT_R32G32_SFLOAT, 8, 0, 8, true, false},
|
||||
{GpuEnumValue(BufferFormat::k16_16_16_16UNorm), VK_FORMAT_R16G16B16A16_UNORM, 8, 0, 8, true,
|
||||
false},
|
||||
{GpuEnumValue(BufferFormat::k16_16_16_16SNorm), VK_FORMAT_R16G16B16A16_SNORM, 8, 0, 8, true,
|
||||
false},
|
||||
{GpuEnumValue(BufferFormat::k16_16_16_16UInt), VK_FORMAT_R16G16B16A16_UINT, 8, 0, 8, true,
|
||||
true},
|
||||
{GpuEnumValue(BufferFormat::k16_16_16_16SInt), VK_FORMAT_R16G16B16A16_SINT, 8, 0, 8, false,
|
||||
false},
|
||||
{GpuEnumValue(BufferFormat::k16_16_16_16Float), VK_FORMAT_R16G16B16A16_SFLOAT, 8, 0, 8, true,
|
||||
false},
|
||||
{GpuEnumValue(BufferFormat::k32_32_32UInt), VK_FORMAT_R32G32B32_UINT, 12, 0, 12, true, true},
|
||||
{GpuEnumValue(BufferFormat::k32_32_32SInt), VK_FORMAT_R32G32B32_SINT, 12, 0, 12, false,
|
||||
false},
|
||||
{GpuEnumValue(BufferFormat::k32_32_32Float), VK_FORMAT_R32G32B32_SFLOAT, 12, 0, 12, true,
|
||||
false},
|
||||
{GpuEnumValue(BufferFormat::k32_32_32_32UInt), VK_FORMAT_R32G32B32A32_UINT, 16, 0, 16, true,
|
||||
true},
|
||||
{GpuEnumValue(BufferFormat::k32_32_32_32SInt), VK_FORMAT_R32G32B32A32_SINT, 16, 0, 16, false,
|
||||
false},
|
||||
{GpuEnumValue(BufferFormat::k32_32_32_32Float), VK_FORMAT_R32G32B32A32_SFLOAT, 16, 0, 16,
|
||||
true, false},
|
||||
{GpuEnumValue(BufferFormat::k8_8_8_8Srgb), VK_FORMAT_R8G8B8A8_SRGB, 4, 0, 4, true, false},
|
||||
{GpuEnumValue(BufferFormat::k9_9_9_5Float), VK_FORMAT_E5B9G9R9_UFLOAT_PACK32, 4, 0, 0, true,
|
||||
false},
|
||||
{GpuEnumValue(BufferFormat::k5_6_5UNorm), VK_FORMAT_B5G6R5_UNORM_PACK16, 2, 0, 2, true,
|
||||
false},
|
||||
{GpuEnumValue(BufferFormat::k5_5_5_1UNorm), VK_FORMAT_R5G5B5A1_UNORM_PACK16, 2, 0, 2, true,
|
||||
false},
|
||||
{GpuEnumValue(BufferFormat::k4_4_4_4UNorm), VK_FORMAT_R4G4B4A4_UNORM_PACK16, 2, 0, 2, true,
|
||||
false},
|
||||
{GpuEnumValue(BufferFormat::kFmask8_S4_F4), VK_FORMAT_R32_SFLOAT, 1, 0, 1, true, false},
|
||||
{GpuEnumValue(BufferFormat::kBc1UNorm), VK_FORMAT_BC1_RGBA_UNORM_BLOCK, 0, 8, 0, true,
|
||||
false},
|
||||
{GpuEnumValue(BufferFormat::kBc1Srgb), VK_FORMAT_BC1_RGBA_SRGB_BLOCK, 0, 8, 0, true, false},
|
||||
{GpuEnumValue(BufferFormat::kBc2UNorm), VK_FORMAT_BC2_UNORM_BLOCK, 0, 16, 0, true, false},
|
||||
{GpuEnumValue(BufferFormat::kBc2Srgb), VK_FORMAT_BC2_SRGB_BLOCK, 0, 16, 0, true, false},
|
||||
{GpuEnumValue(BufferFormat::kBc3UNorm), VK_FORMAT_BC3_UNORM_BLOCK, 0, 16, 0, true, false},
|
||||
{GpuEnumValue(BufferFormat::kBc3Srgb), VK_FORMAT_BC3_SRGB_BLOCK, 0, 16, 0, true, false},
|
||||
{GpuEnumValue(BufferFormat::kBc4UNorm), VK_FORMAT_BC4_UNORM_BLOCK, 0, 8, 0, true, false},
|
||||
{GpuEnumValue(BufferFormat::kBc4SNorm), VK_FORMAT_BC4_SNORM_BLOCK, 0, 8, 0, true, false},
|
||||
{GpuEnumValue(BufferFormat::kBc5UNorm), VK_FORMAT_BC5_UNORM_BLOCK, 0, 16, 0, true, false},
|
||||
{GpuEnumValue(BufferFormat::kBc5SNorm), VK_FORMAT_BC5_SNORM_BLOCK, 0, 16, 0, true, false},
|
||||
{GpuEnumValue(BufferFormat::kBc6UFloat), VK_FORMAT_BC6H_UFLOAT_BLOCK, 0, 16, 0, true, false},
|
||||
{GpuEnumValue(BufferFormat::kBc6SFloat), VK_FORMAT_BC6H_SFLOAT_BLOCK, 0, 16, 0, true, false},
|
||||
{GpuEnumValue(BufferFormat::kBc7UNorm), VK_FORMAT_BC7_UNORM_BLOCK, 0, 16, 0, true, false},
|
||||
{GpuEnumValue(BufferFormat::kBc7Srgb), VK_FORMAT_BC7_SRGB_BLOCK, 0, 16, 0, true, false},
|
||||
};
|
||||
|
||||
constexpr auto MakeFormatInfoLookup() {
|
||||
constexpr uint32_t kMaxFormat = GpuEnumValue(BufferFormat::kBc7Srgb);
|
||||
std::array<const FormatInfo*, kMaxFormat + 1> lookup {};
|
||||
for (const auto& info: kFormatInfo) {
|
||||
lookup[info.format] = &info;
|
||||
}
|
||||
return lookup;
|
||||
}
|
||||
|
||||
constexpr auto kFormatInfoLookup = MakeFormatInfoLookup();
|
||||
|
||||
} // namespace
|
||||
|
||||
std::span<const FormatInfo> Formats() {
|
||||
return kFormatInfo;
|
||||
}
|
||||
|
||||
const FormatInfo* FindFormatInfo(uint32_t format) {
|
||||
return format < kFormatInfoLookup.size() ? kFormatInfoLookup[format] : nullptr;
|
||||
}
|
||||
|
||||
VkFormat SurfaceFormat(uint32_t format) {
|
||||
const auto* info = FindFormatInfo(format);
|
||||
return info != nullptr ? info->vk_format : VK_FORMAT_UNDEFINED;
|
||||
}
|
||||
|
||||
uint32_t NumBytesPerElement(uint32_t format) {
|
||||
const auto* info = FindFormatInfo(format);
|
||||
return info != nullptr ? info->bytes_per_element : 0;
|
||||
}
|
||||
|
||||
uint32_t BlockCompressedBytesPerBlock(uint32_t format) {
|
||||
const auto* info = FindFormatInfo(format);
|
||||
return info != nullptr ? info->block_compressed_bytes_per_block : 0;
|
||||
}
|
||||
|
||||
uint32_t RenderTargetBytesPerElement(uint32_t format) {
|
||||
const auto* info = FindFormatInfo(format);
|
||||
return info != nullptr ? info->render_target_bytes_per_element : 0;
|
||||
}
|
||||
|
||||
bool IsSupportedTextureFormat(uint32_t format) {
|
||||
const auto* info = FindFormatInfo(format);
|
||||
return info != nullptr && info->supported_sampled_texture_format;
|
||||
}
|
||||
|
||||
bool IsUintTextureFormat(uint32_t format) {
|
||||
const auto* info = FindFormatInfo(format);
|
||||
return info != nullptr && info->unsigned_integer_texture_format;
|
||||
}
|
||||
|
||||
bool IsFmaskTextureFormat(uint32_t format) {
|
||||
return format == GpuEnumValue(BufferFormat::kFmask8_S4_F4);
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics::Prospero
|
||||
@@ -0,0 +1,33 @@
|
||||
#ifndef EMULATOR_INCLUDE_EMULATOR_GRAPHICS_GUEST_GPU_GPU_FORMAT_H_
|
||||
#define EMULATOR_INCLUDE_EMULATOR_GRAPHICS_GUEST_GPU_GPU_FORMAT_H_
|
||||
|
||||
#include "common/common.h"
|
||||
|
||||
#include <span>
|
||||
#include <vulkan/vulkan_core.h>
|
||||
|
||||
namespace Libs::Graphics::Prospero {
|
||||
|
||||
struct FormatInfo {
|
||||
uint32_t format = 0;
|
||||
VkFormat vk_format = VK_FORMAT_UNDEFINED;
|
||||
uint32_t bytes_per_element = 0;
|
||||
uint32_t block_compressed_bytes_per_block = 0;
|
||||
uint32_t render_target_bytes_per_element = 0;
|
||||
bool supported_sampled_texture_format = false;
|
||||
bool unsigned_integer_texture_format = false;
|
||||
};
|
||||
|
||||
std::span<const FormatInfo> Formats();
|
||||
const FormatInfo* FindFormatInfo(uint32_t format);
|
||||
VkFormat SurfaceFormat(uint32_t format);
|
||||
uint32_t NumBytesPerElement(uint32_t format);
|
||||
uint32_t BlockCompressedBytesPerBlock(uint32_t format);
|
||||
uint32_t RenderTargetBytesPerElement(uint32_t format);
|
||||
bool IsSupportedTextureFormat(uint32_t format);
|
||||
bool IsUintTextureFormat(uint32_t format);
|
||||
bool IsFmaskTextureFormat(uint32_t format);
|
||||
|
||||
} // namespace Libs::Graphics::Prospero
|
||||
|
||||
#endif /* EMULATOR_INCLUDE_EMULATOR_GRAPHICS_GUEST_GPU_GPU_FORMAT_H_ */
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,38 @@
|
||||
#ifndef EMULATOR_INCLUDE_EMULATOR_GRAPHICS_GRAPHICSRUN_H_
|
||||
#define EMULATOR_INCLUDE_EMULATOR_GRAPHICS_GRAPHICSRUN_H_
|
||||
|
||||
#include "common/abi.h"
|
||||
#include "common/common.h"
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
class GraphicsRunSubmissionLock final {
|
||||
public:
|
||||
GraphicsRunSubmissionLock();
|
||||
~GraphicsRunSubmissionLock();
|
||||
KYTY_CLASS_NO_COPY(GraphicsRunSubmissionLock);
|
||||
};
|
||||
|
||||
void GraphicsRunInit();
|
||||
|
||||
void GraphicsRunSubmit(uint32_t* cmd_draw_buffer, uint32_t num_draw_dw, uint32_t* cmd_const_buffer,
|
||||
uint32_t num_const_dw, bool trigger_agc_interrupt_on_done = false);
|
||||
void GraphicsRunSubmitCompute(uint32_t queue, uint32_t* cmd_buffer, uint32_t num_dw,
|
||||
bool trigger_agc_interrupt_on_done = false);
|
||||
void GraphicsRunWait();
|
||||
void GraphicsRunDone();
|
||||
int GraphicsRunGetFrameNum();
|
||||
[[nodiscard]] bool GraphicsRunIsCommandProcessorThread() noexcept;
|
||||
[[nodiscard]] bool GraphicsRunIsGuestMemoryPoll() noexcept;
|
||||
[[nodiscard]] bool GraphicsRunIsCommandProcessorGuestAccess() noexcept;
|
||||
[[nodiscard]] bool GraphicsRunSubmissionLockHeld() noexcept;
|
||||
[[nodiscard]] bool GraphicsRunGpuLockHeld() noexcept;
|
||||
// Begin/end an exclusive same-command-processor readback transaction. The begin call finishes
|
||||
// work already recorded on that queue and retains the processor lock so its peer draw/constant
|
||||
// job cannot record new work until BufferCache publishes the faulted page.
|
||||
[[nodiscard]] int GraphicsRunBeginCommandProcessorReadback();
|
||||
void GraphicsRunEndCommandProcessorReadback();
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
#endif /* EMULATOR_INCLUDE_EMULATOR_GRAPHICS_GRAPHICSRUN_H_ */
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,153 @@
|
||||
#include "graphics/guest_gpu/pm4.h"
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/file.h"
|
||||
|
||||
namespace Libs::Graphics::Pm4 {
|
||||
|
||||
static const char* g_names[256] = {};
|
||||
static const char* g_r_names[Pm4::R_NUM] = {};
|
||||
static bool g_names_initialized = false;
|
||||
|
||||
static void InitNames() {
|
||||
if (!g_names_initialized) {
|
||||
for (auto& n: g_names) {
|
||||
n = "<unknown>";
|
||||
}
|
||||
|
||||
for (auto& n: g_r_names) {
|
||||
n = "<unknown>";
|
||||
}
|
||||
|
||||
g_r_names[R_ZERO] = "R_ZERO";
|
||||
g_r_names[R_DRAW_RESET] = "R_DRAW_RESET";
|
||||
g_r_names[R_WAIT_FLIP_DONE] = "R_WAIT_FLIP_DONE";
|
||||
g_r_names[R_DISPATCH_RESET] = "R_DISPATCH_RESET";
|
||||
g_r_names[R_WAIT_MEM_32] = "R_WAIT_MEM_32";
|
||||
g_r_names[R_PUSH_MARKER] = "R_PUSH_MARKER";
|
||||
g_r_names[R_POP_MARKER] = "R_POP_MARKER";
|
||||
g_r_names[R_ACQUIRE_MEM] = "R_ACQUIRE_MEM";
|
||||
g_r_names[R_WRITE_DATA] = "R_WRITE_DATA";
|
||||
g_r_names[R_WAIT_MEM_64] = "R_WAIT_MEM_64";
|
||||
g_r_names[R_FLIP] = "R_FLIP";
|
||||
g_r_names[R_RELEASE_MEM] = "R_RELEASE_MEM";
|
||||
g_r_names[R_DMA_DATA] = "R_DMA_DATA";
|
||||
|
||||
g_names[IT_NOP] = "IT_NOP";
|
||||
g_names[IT_SET_BASE] = "IT_SET_BASE";
|
||||
g_names[IT_CLEAR_STATE] = "IT_CLEAR_STATE";
|
||||
g_names[IT_INDEX_BUFFER_SIZE] = "IT_INDEX_BUFFER_SIZE";
|
||||
g_names[IT_DISPATCH_DIRECT] = "IT_DISPATCH_DIRECT";
|
||||
g_names[IT_DISPATCH_INDIRECT] = "IT_DISPATCH_INDIRECT";
|
||||
g_names[IT_SET_PREDICATION] = "IT_SET_PREDICATION";
|
||||
g_names[IT_COND_EXEC] = "IT_COND_EXEC";
|
||||
g_names[IT_DRAW_INDIRECT] = "IT_DRAW_INDIRECT";
|
||||
g_names[IT_DRAW_INDEX_INDIRECT] = "IT_DRAW_INDEX_INDIRECT";
|
||||
g_names[IT_INDEX_BASE] = "IT_INDEX_BASE";
|
||||
g_names[IT_DRAW_INDEX_2] = "IT_DRAW_INDEX_2";
|
||||
g_names[IT_CONTEXT_CONTROL] = "IT_CONTEXT_CONTROL";
|
||||
g_names[IT_INDEX_TYPE] = "IT_INDEX_TYPE";
|
||||
g_names[IT_DRAW_INDIRECT_MULTI] = "IT_DRAW_INDIRECT_MULTI";
|
||||
g_names[IT_DRAW_INDEX_AUTO] = "IT_DRAW_INDEX_AUTO";
|
||||
g_names[IT_NUM_INSTANCES] = "IT_NUM_INSTANCES";
|
||||
g_names[IT_INDIRECT_BUFFER_CNST] = "IT_INDIRECT_BUFFER_CNST";
|
||||
g_names[IT_DRAW_INDEX_OFFSET_2] = "IT_DRAW_INDEX_OFFSET_2";
|
||||
g_names[IT_WRITE_DATA] = "IT_WRITE_DATA";
|
||||
g_names[IT_MEM_SEMAPHORE] = "IT_MEM_SEMAPHORE";
|
||||
g_names[IT_DRAW_INDEX_INDIRECT_MULTI] = "IT_DRAW_INDEX_INDIRECT_MULTI";
|
||||
g_names[IT_INDIRECT_BUFFER] = "IT_INDIRECT_BUFFER";
|
||||
g_names[IT_COPY_DATA] = "IT_COPY_DATA";
|
||||
g_names[IT_CP_DMA] = "IT_CP_DMA";
|
||||
g_names[IT_PFP_SYNC_ME] = "IT_PFP_SYNC_ME";
|
||||
g_names[IT_SURFACE_SYNC] = "IT_SURFACE_SYNC";
|
||||
g_names[IT_EVENT_WRITE] = "IT_EVENT_WRITE";
|
||||
g_names[IT_EVENT_WRITE_EOP] = "IT_EVENT_WRITE_EOP";
|
||||
g_names[IT_EVENT_WRITE_EOS] = "IT_EVENT_WRITE_EOS";
|
||||
g_names[IT_RELEASE_MEM] = "IT_RELEASE_MEM";
|
||||
g_names[IT_DMA_DATA] = "IT_DMA_DATA";
|
||||
g_names[IT_ACQUIRE_MEM] = "IT_ACQUIRE_MEM";
|
||||
g_names[IT_REWIND] = "IT_REWIND";
|
||||
g_names[IT_SET_SH_REG_INDIRECT] = "IT_SET_SH_REG_INDIRECT";
|
||||
g_names[IT_SET_UCONFIG_REG_INDIRECT] = "IT_SET_UCONFIG_REG_INDIRECT";
|
||||
g_names[IT_SET_CONFIG_REG] = "IT_SET_CONFIG_REG";
|
||||
g_names[IT_SET_CONTEXT_REG] = "IT_SET_CONTEXT_REG";
|
||||
g_names[IT_SET_SH_REG] = "IT_SET_SH_REG";
|
||||
g_names[IT_SET_QUEUE_REG] = "IT_SET_QUEUE_REG";
|
||||
g_names[IT_SET_UCONFIG_REG] = "IT_SET_UCONFIG_REG";
|
||||
g_names[IT_SET_UCONFIG_REG_INDEX] = "IT_SET_UCONFIG_REG_INDEX";
|
||||
g_names[IT_WRITE_CONST_RAM] = "IT_WRITE_CONST_RAM";
|
||||
g_names[IT_DUMP_CONST_RAM] = "IT_DUMP_CONST_RAM";
|
||||
g_names[IT_INCREMENT_CE_COUNTER] = "IT_INCREMENT_CE_COUNTER";
|
||||
g_names[IT_INCREMENT_DE_COUNTER] = "IT_INCREMENT_DE_COUNTER";
|
||||
g_names[IT_WAIT_ON_CE_COUNTER] = "IT_WAIT_ON_CE_COUNTER";
|
||||
g_names[IT_WAIT_ON_DE_COUNTER_DIFF] = "IT_WAIT_ON_DE_COUNTER_DIFF";
|
||||
g_names[IT_DISPATCH_DRAW_PREAMBLE] = "IT_DISPATCH_DRAW_PREAMBLE";
|
||||
g_names[IT_DISPATCH_DRAW] = "IT_DISPATCH_DRAW";
|
||||
g_names[IT_GET_LOD_STATS] = "IT_GET_LOD_STATS";
|
||||
g_names[IT_SET_CONTEXT_REG_INDIRECT] = "IT_SET_CONTEXT_REG_INDIRECT";
|
||||
|
||||
g_names_initialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
void DumpPm4PacketStream(Common::File* file, uint32_t* cmd_buffer, uint32_t start_dw,
|
||||
uint32_t num_dw) {
|
||||
InitNames();
|
||||
|
||||
// db_dump();
|
||||
|
||||
file->Printf("----- Buffer --- dwords: 0x%05" PRIx32 ", offset : %u, addr: %016" PRIx64
|
||||
" ----- \n",
|
||||
num_dw, start_dw, reinterpret_cast<uint64_t>(cmd_buffer));
|
||||
|
||||
auto* cmd = cmd_buffer + start_dw;
|
||||
auto dw = num_dw;
|
||||
while (dw != 0) {
|
||||
EXIT_NOT_IMPLEMENTED(dw < 2);
|
||||
EXIT_NOT_IMPLEMENTED(dw > num_dw);
|
||||
|
||||
auto cmd_id = *cmd++;
|
||||
|
||||
file->Printf("%05" PRIx32 " | 0x%08" PRIx32 " | ", start_dw, cmd_id);
|
||||
|
||||
uint32_t len = 0;
|
||||
|
||||
if ((cmd_id & 0xC0000000u) == 0xC0000000u) {
|
||||
const bool sh_gx = (cmd_id & 0x2u) == 0;
|
||||
const uint8_t op = ((cmd_id >> 8u) & 0xffu);
|
||||
const auto r = KYTY_PM4_R(cmd_id);
|
||||
len = ((cmd_id >> 16u) & 0x3fffu) + 1;
|
||||
|
||||
file->Printf("%s %s(OP:0x%02" PRIx8 ") SH:%s CNT:%u\n", g_names[op],
|
||||
(op == IT_NOP ? g_r_names[r] : ""), op, sh_gx ? "GX" : "CX", len);
|
||||
|
||||
for (uint32_t i = 0; i < len; i++) {
|
||||
file->Printf(" | 0x%08" PRIx32 " | \n", cmd[i]);
|
||||
}
|
||||
|
||||
if ((op == IT_SET_CONTEXT_REG_INDIRECT || op == IT_SET_SH_REG_INDIRECT ||
|
||||
op == IT_SET_UCONFIG_REG_INDIRECT) &&
|
||||
len == 4) {
|
||||
auto* indirect_buffer =
|
||||
reinterpret_cast<uint32_t*>((static_cast<uint64_t>(cmd[0]) & 0xfffffffcu) |
|
||||
(static_cast<uint64_t>(cmd[1]) << 32u));
|
||||
const uint32_t indirect_num_regs = cmd[3] & 0x3fffu;
|
||||
|
||||
for (uint32_t i = 0; indirect_buffer != nullptr && i < indirect_num_regs;
|
||||
i++, indirect_buffer += 2) {
|
||||
file->Printf(" | | offset = 0x%08" PRIx32
|
||||
", value = 0x%08" PRIx32 "\n",
|
||||
indirect_buffer[0], indirect_buffer[1]);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
file->Printf("?????\n");
|
||||
}
|
||||
|
||||
cmd += len;
|
||||
dw -= len + 1;
|
||||
start_dw += len + 1;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics::Pm4
|
||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,104 @@
|
||||
#ifndef EMULATOR_INCLUDE_EMULATOR_GRAPHICS_TILE_H_
|
||||
#define EMULATOR_INCLUDE_EMULATOR_GRAPHICS_TILE_H_
|
||||
|
||||
#include "common/abi.h"
|
||||
#include "common/common.h"
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
enum class TileMode {
|
||||
VideoOutLinear,
|
||||
VideoOutTiled,
|
||||
TextureLinear,
|
||||
TextureTiled,
|
||||
// RenderTextureLinear,
|
||||
// RenderTextureTiled,
|
||||
};
|
||||
|
||||
struct TileSizeAlign {
|
||||
uint32_t size = 0;
|
||||
uint32_t align = 0;
|
||||
};
|
||||
|
||||
struct TileSizeOffset {
|
||||
uint32_t size = 0;
|
||||
uint32_t offset = 0;
|
||||
uint32_t src_size = 0;
|
||||
uint32_t src_offset = 0;
|
||||
uint32_t x = 0;
|
||||
uint32_t y = 0;
|
||||
};
|
||||
|
||||
struct TilePaddedSize {
|
||||
uint32_t width = 0;
|
||||
uint32_t height = 0;
|
||||
};
|
||||
|
||||
void TileInit();
|
||||
void TileConvertTiledToLinear(void* dst, const void* src, TileMode mode, uint32_t width,
|
||||
uint32_t height);
|
||||
void TileConvertTiledToLinearRenderTarget(void* dst, const void* src, uint32_t width,
|
||||
uint32_t height, uint32_t pitch,
|
||||
uint32_t bytes_per_element, uint64_t size,
|
||||
uint64_t src_size = 0, uint32_t src_x = 0,
|
||||
uint32_t src_y = 0);
|
||||
void TileConvertLinearToTiledRenderTarget(void* dst, const void* src, uint32_t width,
|
||||
uint32_t height, uint32_t pitch,
|
||||
uint32_t bytes_per_element, uint64_t size);
|
||||
void TileConvertTiledToLinearStandard64KB(void* dst, const void* src, uint32_t format,
|
||||
uint32_t width, uint32_t height, uint32_t pitch,
|
||||
uint64_t size, uint64_t src_size = 0, uint32_t src_x = 0,
|
||||
uint32_t src_y = 0);
|
||||
void TileConvertTiledToLinearStandard64KB32(void* dst, const void* src, uint32_t width,
|
||||
uint32_t height, uint32_t pitch, uint64_t size,
|
||||
uint64_t src_size = 0, uint32_t src_x = 0,
|
||||
uint32_t src_y = 0);
|
||||
void TileConvertTiledToLinearStandard64KB16(void* dst, const void* src, uint32_t width,
|
||||
uint32_t height, uint32_t pitch, uint64_t size,
|
||||
uint64_t src_size = 0, uint32_t src_x = 0,
|
||||
uint32_t src_y = 0);
|
||||
void TileConvertTiledToLinearDepth(void* dst, const void* src, uint32_t format, uint32_t width,
|
||||
uint32_t height, uint32_t pitch, uint64_t size);
|
||||
void TileConvertLinearToTiledDepth(void* dst, const void* src, uint32_t format, uint32_t width,
|
||||
uint32_t height, uint32_t pitch, uint64_t size);
|
||||
void TileConvertTiledToLinearStandard4KB(void* dst, const void* src, uint32_t format,
|
||||
uint32_t width, uint32_t height, uint32_t pitch,
|
||||
uint64_t dst_size, uint64_t src_size, uint32_t src_x = 0,
|
||||
uint32_t src_y = 0);
|
||||
void TileConvertTiledToLinearStandard256B(void* dst, const void* src, uint32_t format,
|
||||
uint32_t width, uint32_t height, uint32_t pitch,
|
||||
uint64_t dst_size, uint64_t src_size);
|
||||
bool TileIsStandard256BTextureSupported(uint32_t format);
|
||||
bool TileIsStandard4KBTextureSupported(uint32_t format);
|
||||
bool TileIsStandard64KBTextureSupported(uint32_t format);
|
||||
bool TileGetStandard4KBVolumeLayout(uint32_t format, uint32_t* bytes_per_element,
|
||||
uint32_t* texels_per_element_wide,
|
||||
uint32_t* texels_per_element_tall, uint32_t* block_width_log2,
|
||||
uint32_t* block_height_log2, uint32_t* block_depth_log2);
|
||||
|
||||
bool TileGetDepthSize(uint32_t width, uint32_t height, uint32_t pitch, uint32_t z_format,
|
||||
uint32_t stencil_format, bool htile, TileSizeAlign* stencil_size,
|
||||
TileSizeAlign* htile_size, TileSizeAlign* depth_size);
|
||||
uint32_t TileGetRenderTargetPitch(uint32_t width, uint32_t bytes_per_element);
|
||||
bool TileGetRenderTargetSize(uint32_t width, uint32_t height, uint32_t pitch,
|
||||
uint32_t bytes_per_element, TileSizeAlign* total_size);
|
||||
bool TileGetRenderTargetMipLayout(uint32_t width, uint32_t height, uint32_t pitch,
|
||||
uint32_t bytes_per_element, uint32_t levels,
|
||||
TileSizeAlign* total_size, TileSizeOffset* level_sizes,
|
||||
TilePaddedSize* padded_size);
|
||||
void TileGetTextureSize(uint32_t format, uint32_t width, uint32_t height, uint32_t pitch,
|
||||
uint32_t levels, uint32_t tile, TileSizeAlign* total_size,
|
||||
TileSizeOffset* level_sizes, TilePaddedSize* padded_size);
|
||||
void TileGetTextureTotalSize(uint32_t format, uint32_t width, uint32_t height, uint32_t depth,
|
||||
uint32_t pitch, uint32_t levels, uint32_t tile, bool volume_texture,
|
||||
TileSizeAlign* total_size);
|
||||
uint32_t TileGetTexturePitch(uint32_t format, uint32_t width, uint32_t levels, uint32_t tile);
|
||||
void TileConvertTiledToLinearStandard4KB3D(void* dst, const void* src, uint32_t format,
|
||||
uint32_t width, uint32_t height, uint32_t depth,
|
||||
uint32_t pitch, uint64_t dst_slice_stride,
|
||||
uint64_t dst_size, uint64_t src_size,
|
||||
bool clear_dst = true);
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
#endif /* EMULATOR_INCLUDE_EMULATOR_GRAPHICS_TILE_H_ */
|
||||
@@ -0,0 +1,179 @@
|
||||
#ifndef EMULATOR_INCLUDE_EMULATOR_GRAPHICS_GRAPHICCONTEXT_H_
|
||||
#define EMULATOR_INCLUDE_EMULATOR_GRAPHICS_GRAPHICCONTEXT_H_
|
||||
|
||||
#include "common/abi.h"
|
||||
#include "common/common.h"
|
||||
#include "common/threads.h"
|
||||
#include "graphics/host_gpu/vulkanInstance.h"
|
||||
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
#include <vk_mem_alloc.h>
|
||||
#include <vulkan/vulkan_core.h> // IWYU pragma: export
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
class CommandBuffer;
|
||||
|
||||
struct VulkanSwapchain {
|
||||
~VulkanSwapchain();
|
||||
|
||||
VkSwapchainKHR swapchain = nullptr;
|
||||
VkFormat swapchain_format = VK_FORMAT_UNDEFINED;
|
||||
VkExtent2D swapchain_extent = {};
|
||||
std::unique_ptr<VkImage[]> swapchain_images;
|
||||
std::unique_ptr<VkImageView[]> swapchain_image_views;
|
||||
uint32_t swapchain_images_count = 0;
|
||||
std::unique_ptr<VkSemaphore[]> image_acquired_semaphores;
|
||||
std::unique_ptr<VkSemaphore[]> render_complete_semaphores;
|
||||
uint32_t current_index = 0;
|
||||
uint32_t present_frame = 0;
|
||||
std::vector<std::unique_ptr<CommandBuffer>> present_command_buffers;
|
||||
};
|
||||
|
||||
struct VulkanCommandPool {
|
||||
Common::Mutex mutex;
|
||||
VkCommandPool pool = nullptr;
|
||||
std::unique_ptr<VkCommandBuffer[]> buffers;
|
||||
std::unique_ptr<VkFence[]> fences;
|
||||
std::unique_ptr<VkSemaphore[]> semaphores;
|
||||
std::unique_ptr<bool[]> busy;
|
||||
uint32_t buffers_count = 0;
|
||||
};
|
||||
|
||||
struct GraphicContext: public VulkanInstance {
|
||||
uint32_t screen_width = 0;
|
||||
uint32_t screen_height = 0;
|
||||
};
|
||||
|
||||
struct VulkanMemory {
|
||||
VkMemoryRequirements requirements = {};
|
||||
VkMemoryPropertyFlags property = 0;
|
||||
VkDeviceMemory memory = nullptr;
|
||||
VmaAllocation allocation = nullptr;
|
||||
VmaAllocationInfo allocation_info = {};
|
||||
VkDeviceSize offset = 0;
|
||||
uint32_t type = 0;
|
||||
uint64_t unique_id = 0;
|
||||
};
|
||||
|
||||
enum class VulkanImageType {
|
||||
Unknown,
|
||||
VideoOut,
|
||||
DepthStencil,
|
||||
Texture,
|
||||
StorageTexture,
|
||||
RenderTexture
|
||||
};
|
||||
|
||||
struct VulkanImage {
|
||||
static constexpr int VIEW_MAX = 21;
|
||||
static constexpr int VIEW_DEFAULT = 0;
|
||||
static constexpr int VIEW_BGRA = 1;
|
||||
static constexpr int VIEW_DEPTH_TEXTURE = 2;
|
||||
static constexpr int VIEW_R001 = 3;
|
||||
static constexpr int VIEW_RGB1 = 4;
|
||||
static constexpr int VIEW_R000 = 5;
|
||||
static constexpr int VIEW_RG01 = 6;
|
||||
static constexpr int VIEW_000R = 7;
|
||||
static constexpr int VIEW_DEFAULT_ARRAY = 8;
|
||||
static constexpr int VIEW_BGRA_ARRAY = 9;
|
||||
static constexpr int VIEW_DEPTH_TEXTURE_ARRAY = 10;
|
||||
static constexpr int VIEW_R001_ARRAY = 11;
|
||||
static constexpr int VIEW_RGB1_ARRAY = 12;
|
||||
static constexpr int VIEW_R000_ARRAY = 13;
|
||||
static constexpr int VIEW_RG01_ARRAY = 14;
|
||||
static constexpr int VIEW_000R_ARRAY = 15;
|
||||
static constexpr int VIEW_STENCIL_TEXTURE = 16;
|
||||
static constexpr int VIEW_STENCIL_TEXTURE_ARRAY = 17;
|
||||
static constexpr int VIEW_STORAGE = 18;
|
||||
static constexpr int VIEW_STORAGE_ARRAY = 19;
|
||||
static constexpr int VIEW_RGBA8_BGRA = 20;
|
||||
|
||||
explicit VulkanImage(VulkanImageType type): type(type) {}
|
||||
|
||||
VulkanImageType type = VulkanImageType::Unknown;
|
||||
VkFormat format = VK_FORMAT_UNDEFINED;
|
||||
VkExtent2D extent = {};
|
||||
uint32_t guest_pitch = 0;
|
||||
uint32_t layers = 1;
|
||||
uint32_t mip_levels = 1;
|
||||
VkImage image = nullptr;
|
||||
VkImageView image_view[VIEW_MAX] = {};
|
||||
VkImageLayout layout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
Graphics::VulkanMemory memory;
|
||||
};
|
||||
|
||||
struct VideoOutVulkanImage: public VulkanImage {
|
||||
VideoOutVulkanImage(): VulkanImage(VulkanImageType::VideoOut) {}
|
||||
};
|
||||
|
||||
struct DepthStencilVulkanImage: public VulkanImage {
|
||||
DepthStencilVulkanImage(): VulkanImage(VulkanImageType::DepthStencil) {}
|
||||
bool compressed = false;
|
||||
};
|
||||
|
||||
struct GpuTextureVulkanImage: public VulkanImage {
|
||||
explicit GpuTextureVulkanImage(VulkanImageType type): VulkanImage(type) {}
|
||||
};
|
||||
|
||||
struct TextureVulkanImage: public GpuTextureVulkanImage {
|
||||
TextureVulkanImage(): GpuTextureVulkanImage(VulkanImageType::Texture) {}
|
||||
};
|
||||
|
||||
struct StorageTextureVulkanImage: public GpuTextureVulkanImage {
|
||||
struct SampledView {
|
||||
VkFormat format = VK_FORMAT_UNDEFINED;
|
||||
uint32_t swizzle = 0;
|
||||
uint32_t type = 0;
|
||||
uint32_t base_level = 0;
|
||||
uint32_t level_count = 0;
|
||||
VkImageView view = nullptr;
|
||||
};
|
||||
StorageTextureVulkanImage(): GpuTextureVulkanImage(VulkanImageType::StorageTexture) {}
|
||||
std::mutex sampled_view_mutex;
|
||||
std::vector<SampledView> sampled_views;
|
||||
};
|
||||
|
||||
struct RenderTextureVulkanImage: public VulkanImage {
|
||||
struct AttachmentView {
|
||||
VkFormat format = VK_FORMAT_UNDEFINED;
|
||||
uint32_t level = 0;
|
||||
VkImageView view = nullptr;
|
||||
};
|
||||
struct SampledView {
|
||||
VkFormat format = VK_FORMAT_UNDEFINED;
|
||||
uint32_t base_level = 0;
|
||||
uint32_t level_count = 0;
|
||||
int variant = VIEW_DEFAULT;
|
||||
VkImageView view = nullptr;
|
||||
};
|
||||
struct StorageView {
|
||||
VkFormat format = VK_FORMAT_UNDEFINED;
|
||||
uint32_t base_level = 0;
|
||||
uint32_t level_count = 0;
|
||||
VkImageView view = nullptr;
|
||||
};
|
||||
RenderTextureVulkanImage(): VulkanImage(VulkanImageType::RenderTexture) {}
|
||||
VkImageView render_view[16] = {};
|
||||
std::mutex attachment_view_mutex;
|
||||
std::vector<AttachmentView> attachment_views;
|
||||
std::mutex sampled_view_mutex;
|
||||
std::vector<SampledView> sampled_views;
|
||||
std::mutex storage_view_mutex;
|
||||
std::vector<StorageView> storage_views;
|
||||
};
|
||||
|
||||
struct VulkanBuffer {
|
||||
VkBuffer buffer = nullptr;
|
||||
VulkanMemory memory;
|
||||
VkBufferUsageFlags usage = 0;
|
||||
uint64_t buffer_size = 0;
|
||||
};
|
||||
|
||||
struct StorageVulkanBuffer: public VulkanBuffer {};
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
#endif /* EMULATOR_INCLUDE_EMULATOR_GRAPHICS_GRAPHICCONTEXT_H_ */
|
||||
@@ -0,0 +1,141 @@
|
||||
#include "graphics/host_gpu/hostMemory.h"
|
||||
|
||||
#include <cinttypes>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
#include <windows.h>
|
||||
#undef min
|
||||
#undef max
|
||||
#endif
|
||||
|
||||
namespace Libs::Graphics {
|
||||
namespace {
|
||||
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
bool IsAccessible(DWORD protect, HostMemoryAccess access) {
|
||||
constexpr DWORD blocked = PAGE_NOACCESS | PAGE_GUARD;
|
||||
constexpr DWORD readable = PAGE_READONLY | PAGE_READWRITE | PAGE_WRITECOPY | PAGE_EXECUTE_READ |
|
||||
PAGE_EXECUTE_READWRITE | PAGE_EXECUTE_WRITECOPY;
|
||||
constexpr DWORD writable =
|
||||
PAGE_READWRITE | PAGE_WRITECOPY | PAGE_EXECUTE_READWRITE | PAGE_EXECUTE_WRITECOPY;
|
||||
return (protect & blocked) == 0 &&
|
||||
(protect & (access == HostMemoryAccess::Write ? writable : readable)) != 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace
|
||||
|
||||
bool HostMemoryQueryRange(uint64_t addr, uint64_t requested_size, HostMemoryAccess access,
|
||||
uint64_t* accessible_size) {
|
||||
if (accessible_size == nullptr) {
|
||||
return false;
|
||||
}
|
||||
*accessible_size = 0;
|
||||
if (addr == 0 || requested_size == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto end = UINT64_MAX - addr < requested_size ? UINT64_MAX : addr + requested_size;
|
||||
uint64_t current = addr;
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
while (current < end) {
|
||||
MEMORY_BASIC_INFORMATION region {};
|
||||
if (::VirtualQuery(reinterpret_cast<const void*>(static_cast<uintptr_t>(current)), ®ion,
|
||||
sizeof(region)) == 0) {
|
||||
break;
|
||||
}
|
||||
const auto begin = reinterpret_cast<uint64_t>(region.BaseAddress);
|
||||
auto finish = begin + region.RegionSize;
|
||||
if (finish < begin) {
|
||||
finish = UINT64_MAX;
|
||||
}
|
||||
if (finish <= current || (region.State & MEM_COMMIT) == 0 ||
|
||||
!IsAccessible(region.Protect, access)) {
|
||||
break;
|
||||
}
|
||||
current = finish < end ? finish : end;
|
||||
}
|
||||
#elif KYTY_PLATFORM == KYTY_PLATFORM_LINUX
|
||||
auto* maps = std::fopen("/proc/self/maps", "r");
|
||||
if (maps == nullptr) {
|
||||
return false;
|
||||
}
|
||||
char line[1024] = {};
|
||||
while (current < end && std::fgets(line, sizeof(line), maps) != nullptr) {
|
||||
uint64_t begin = 0;
|
||||
uint64_t finish = 0;
|
||||
char permissions[5] = {};
|
||||
if (std::sscanf(line, "%" SCNx64 "-%" SCNx64 " %4s", &begin, &finish, permissions) != 3 ||
|
||||
finish <= current) {
|
||||
continue;
|
||||
}
|
||||
if (begin > current) {
|
||||
break;
|
||||
}
|
||||
const bool allowed =
|
||||
access == HostMemoryAccess::Write ? permissions[1] == 'w' : permissions[0] == 'r';
|
||||
if (!allowed) {
|
||||
break;
|
||||
}
|
||||
current = finish < end ? finish : end;
|
||||
}
|
||||
std::fclose(maps);
|
||||
#else
|
||||
(void)access;
|
||||
#endif
|
||||
|
||||
*accessible_size = current - addr;
|
||||
return *accessible_size != 0;
|
||||
}
|
||||
|
||||
bool HostMemoryQueryEnd(uint64_t addr, HostMemoryAccess access, uint64_t* end) {
|
||||
if (addr == 0 || end == nullptr) {
|
||||
return false;
|
||||
}
|
||||
uint64_t accessible_size = 0;
|
||||
if (!HostMemoryQueryRange(addr, UINT64_MAX - addr, access, &accessible_size) ||
|
||||
accessible_size == 0 || UINT64_MAX - addr < accessible_size) {
|
||||
return false;
|
||||
}
|
||||
*end = addr + accessible_size;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool HostMemoryQueryReadable(uint64_t addr, uint64_t requested_size, uint64_t* readable_size) {
|
||||
return HostMemoryQueryRange(addr, requested_size, HostMemoryAccess::Read, readable_size);
|
||||
}
|
||||
|
||||
bool HostMemoryQueryWritable(uint64_t addr, uint64_t requested_size, uint64_t* writable_size) {
|
||||
return HostMemoryQueryRange(addr, requested_size, HostMemoryAccess::Write, writable_size);
|
||||
}
|
||||
|
||||
bool HostMemoryReadDword(void*, uint64_t addr, uint32_t* value) {
|
||||
if (value == nullptr) {
|
||||
return false;
|
||||
}
|
||||
uint64_t readable = 0;
|
||||
if (!HostMemoryQueryReadable(addr, sizeof(*value), &readable) || readable < sizeof(*value)) {
|
||||
return false;
|
||||
}
|
||||
std::memcpy(value, reinterpret_cast<const void*>(static_cast<uintptr_t>(addr)), sizeof(*value));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool HostMemoryIsReadable(uint64_t addr) {
|
||||
return HostMemoryRangeIsReadable(addr, 1);
|
||||
}
|
||||
|
||||
bool HostMemoryRangeIsReadable(uint64_t addr, uint64_t size) {
|
||||
if (addr == 0 || size == 0 || UINT64_MAX - addr < size) {
|
||||
return false;
|
||||
}
|
||||
uint64_t readable_size = 0;
|
||||
return HostMemoryQueryReadable(addr, size, &readable_size) && readable_size >= size;
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
@@ -0,0 +1,21 @@
|
||||
#ifndef EMULATOR_SRC_GRAPHICS_HOST_GPU_HOSTMEMORY_H_
|
||||
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_HOSTMEMORY_H_
|
||||
|
||||
#include "common/common.h"
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
enum class HostMemoryAccess { Read, Write };
|
||||
|
||||
bool HostMemoryQueryRange(uint64_t addr, uint64_t requested_size, HostMemoryAccess access,
|
||||
uint64_t* accessible_size);
|
||||
bool HostMemoryQueryEnd(uint64_t addr, HostMemoryAccess access, uint64_t* end);
|
||||
bool HostMemoryQueryReadable(uint64_t addr, uint64_t requested_size, uint64_t* readable_size);
|
||||
bool HostMemoryQueryWritable(uint64_t addr, uint64_t requested_size, uint64_t* writable_size);
|
||||
bool HostMemoryReadDword(void* userdata, uint64_t addr, uint32_t* value);
|
||||
bool HostMemoryIsReadable(uint64_t addr);
|
||||
bool HostMemoryRangeIsReadable(uint64_t addr, uint64_t size);
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
#endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_HOSTMEMORY_H_
|
||||
@@ -0,0 +1,255 @@
|
||||
#include "graphics/host_gpu/memoryTracker.h"
|
||||
|
||||
#include "common/assert.h"
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
#if defined(KYTY_MEMORY_TRACKER_TESTS)
|
||||
namespace {
|
||||
std::atomic<MemoryTracker::UnmapContentionHook> g_unmap_contention_hook {nullptr};
|
||||
}
|
||||
|
||||
void MemoryTracker::SetUnmapContentionHook(UnmapContentionHook hook) noexcept {
|
||||
g_unmap_contention_hook.store(hook, std::memory_order_release);
|
||||
}
|
||||
#endif
|
||||
|
||||
static_assert(std::atomic<void*>::is_always_lock_free);
|
||||
|
||||
MemoryTracker::MemoryTracker(PageManager& page_manager, PageWatchMode gpu_watch_mode)
|
||||
: m_page_manager(page_manager), m_gpu_watch_mode(gpu_watch_mode) {
|
||||
switch (m_gpu_watch_mode) {
|
||||
case PageWatchMode::Write:
|
||||
case PageWatchMode::ReadWrite: break;
|
||||
default: EXIT("unsupported memory tracker GPU page-watch mode\n");
|
||||
}
|
||||
m_regions = std::make_unique<std::atomic<RegionManager*>[]>(REGION_COUNT);
|
||||
for (size_t i = 0; i < REGION_COUNT; i++) {
|
||||
m_regions[i].store(nullptr, std::memory_order_relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
MemoryTracker::~MemoryTracker() = default;
|
||||
|
||||
void MemoryTracker::ValidateRange(uint64_t vaddr, uint64_t size) {
|
||||
if (vaddr == 0 || size == 0 || vaddr >= TRACKER_ADDRESS_SIZE ||
|
||||
size > TRACKER_ADDRESS_SIZE - vaddr) {
|
||||
EXIT("invalid memory tracker range\n");
|
||||
}
|
||||
}
|
||||
|
||||
RegionManager* MemoryTracker::GetOrCreateRegion(uint64_t index) {
|
||||
if (auto* manager = m_regions[index].load(std::memory_order_acquire); manager != nullptr) {
|
||||
return manager;
|
||||
}
|
||||
std::lock_guard lock(m_region_mutex);
|
||||
if (auto* manager = m_regions[index].load(std::memory_order_acquire); manager != nullptr) {
|
||||
return manager;
|
||||
}
|
||||
auto manager = std::make_unique<RegionManager>(m_page_manager, index * TRACKER_REGION_SIZE);
|
||||
auto* ptr = manager.get();
|
||||
m_region_storage.push_back(std::move(manager));
|
||||
m_regions[index].store(ptr, std::memory_order_release);
|
||||
return ptr;
|
||||
}
|
||||
|
||||
bool MemoryTracker::IsRegionCpuModified(uint64_t vaddr, uint64_t size) {
|
||||
CheckNotInUploadCallback();
|
||||
std::lock_guard access(m_access_mutex);
|
||||
RequireMapped(vaddr, size);
|
||||
return Iterate<true>(vaddr, size, [](RegionManager* manager, uint64_t offset, uint64_t bytes) {
|
||||
std::scoped_lock lock(manager->lock);
|
||||
return manager->IsModified<DirtySource::Cpu>(offset, bytes);
|
||||
});
|
||||
}
|
||||
|
||||
bool MemoryTracker::IsRegionGpuModified(uint64_t vaddr, uint64_t size) {
|
||||
CheckNotInUploadCallback();
|
||||
std::lock_guard access(m_access_mutex);
|
||||
RequireMapped(vaddr, size);
|
||||
return Iterate<false>(vaddr, size, [](RegionManager* manager, uint64_t offset, uint64_t bytes) {
|
||||
std::scoped_lock lock(manager->lock);
|
||||
return manager->IsModified<DirtySource::Gpu>(offset, bytes);
|
||||
});
|
||||
}
|
||||
|
||||
void MemoryTracker::MarkRegionAsCpuModified(uint64_t vaddr, uint64_t size) {
|
||||
CheckNotInUploadCallback();
|
||||
std::lock_guard access(m_access_mutex);
|
||||
RequireMapped(vaddr, size);
|
||||
Iterate<true>(vaddr, size, [](RegionManager* manager, uint64_t offset, uint64_t bytes) {
|
||||
std::scoped_lock lock(manager->lock);
|
||||
const auto changed =
|
||||
manager->ChangeState<DirtySource::Cpu, true>(manager->GetCpuAddr() + offset, bytes);
|
||||
manager->ApplyProtection(changed, false);
|
||||
});
|
||||
}
|
||||
|
||||
void MemoryTracker::MarkRegionAsGpuModified(uint64_t vaddr, uint64_t size) {
|
||||
CheckNotInUploadCallback();
|
||||
std::lock_guard access(m_access_mutex);
|
||||
RequireMapped(vaddr, size);
|
||||
Iterate<true>(vaddr, size, [this](RegionManager* manager, uint64_t offset, uint64_t bytes) {
|
||||
std::scoped_lock lock(manager->lock);
|
||||
const auto changed =
|
||||
manager->ChangeState<DirtySource::Gpu, true>(manager->GetCpuAddr() + offset, bytes);
|
||||
manager->ApplyGpuProtection(changed, true, m_gpu_watch_mode);
|
||||
});
|
||||
}
|
||||
|
||||
void MemoryTracker::UnmarkRegionAsGpuModified(uint64_t vaddr, uint64_t size) {
|
||||
CheckNotInUploadCallback();
|
||||
std::lock_guard access(m_access_mutex);
|
||||
RequireMapped(vaddr, size);
|
||||
Iterate<true>(vaddr, size, [this](RegionManager* manager, uint64_t offset, uint64_t bytes) {
|
||||
std::scoped_lock lock(manager->lock);
|
||||
if (!manager->IsFullyModified<DirtySource::Gpu>(offset, bytes)) {
|
||||
EXIT("cannot clear partially GPU-dirty tracking range\n");
|
||||
}
|
||||
const auto changed =
|
||||
manager->ChangeState<DirtySource::Gpu, false>(manager->GetCpuAddr() + offset, bytes);
|
||||
manager->ApplyGpuProtection(changed, false, m_gpu_watch_mode);
|
||||
});
|
||||
}
|
||||
|
||||
void MemoryTracker::UntrackMemoryLocked(uint64_t vaddr, uint64_t size) {
|
||||
RequireMapped(vaddr, size);
|
||||
|
||||
std::vector<RegionManager*> managers;
|
||||
managers.reserve((vaddr % TRACKER_REGION_SIZE + size + TRACKER_REGION_SIZE - 1) /
|
||||
TRACKER_REGION_SIZE);
|
||||
Iterate<false>(vaddr, size, [&](RegionManager* manager, uint64_t, uint64_t) {
|
||||
managers.push_back(manager);
|
||||
});
|
||||
|
||||
std::vector<std::unique_lock<TrackingSpinLock>> locks;
|
||||
locks.reserve(managers.size());
|
||||
for (auto* manager: managers) {
|
||||
locks.emplace_back(manager->lock);
|
||||
}
|
||||
if (Iterate<false>(vaddr, size, [](RegionManager* manager, uint64_t offset, uint64_t bytes) {
|
||||
return manager->IsModified<DirtySource::Gpu>(offset, bytes);
|
||||
})) {
|
||||
EXIT("cannot untrack GPU-dirty memory\n");
|
||||
}
|
||||
Iterate<false>(vaddr, size, [](RegionManager* manager, uint64_t offset, uint64_t bytes) {
|
||||
const auto changed =
|
||||
manager->ChangeState<DirtySource::Cpu, true>(manager->GetCpuAddr() + offset, bytes);
|
||||
manager->ApplyProtection(changed, false);
|
||||
manager->Untrack(manager->GetCpuAddr() + offset, bytes);
|
||||
});
|
||||
locks.clear();
|
||||
}
|
||||
|
||||
void MemoryTracker::UntrackMemory(uint64_t vaddr, uint64_t size) {
|
||||
CheckNotInUploadCallback();
|
||||
std::lock_guard access(m_access_mutex);
|
||||
UntrackMemoryLocked(vaddr, size);
|
||||
}
|
||||
|
||||
void MemoryTracker::UnmapMemory(uint64_t vaddr, uint64_t size) {
|
||||
CheckNotInUploadCallback();
|
||||
std::unique_lock access(m_access_mutex, std::try_to_lock);
|
||||
if (!access.owns_lock()) {
|
||||
#if defined(KYTY_MEMORY_TRACKER_TESTS)
|
||||
if (const auto hook = g_unmap_contention_hook.load(std::memory_order_acquire);
|
||||
hook != nullptr) {
|
||||
hook();
|
||||
}
|
||||
#endif
|
||||
access.lock();
|
||||
}
|
||||
UntrackMemoryLocked(vaddr, size);
|
||||
m_page_manager.OnGpuUnmap(vaddr, size);
|
||||
}
|
||||
|
||||
bool MemoryTracker::InvalidateRegion(uint64_t vaddr, uint64_t size, PageFaultPhase phase) noexcept {
|
||||
switch (phase) {
|
||||
case PageFaultPhase::Release: return true;
|
||||
case PageFaultPhase::Invalidate: {
|
||||
const auto action = BeginCpuFault(vaddr, size);
|
||||
switch (action) {
|
||||
case CpuFaultAction::Untracked: return false;
|
||||
case CpuFaultAction::Continue: return true;
|
||||
case CpuFaultAction::Download:
|
||||
EXIT("generic region invalidation cannot download GPU-dirty memory\n");
|
||||
}
|
||||
}
|
||||
case PageFaultPhase::Complete:
|
||||
return CompleteCpuFault(vaddr, size, PageFaultAccess::Write, false);
|
||||
}
|
||||
EXIT("unsupported region invalidation phase\n");
|
||||
}
|
||||
|
||||
bool MemoryTracker::InvalidateVirtualGpuWrite(PageFaultAccess access, uint64_t vaddr, uint64_t size,
|
||||
PageFaultPhase phase) noexcept {
|
||||
switch (phase) {
|
||||
case PageFaultPhase::Release: return true;
|
||||
case PageFaultPhase::Invalidate: {
|
||||
const bool gpu_modified = Iterate<false>(
|
||||
vaddr, size, [](RegionManager* manager, uint64_t offset, uint64_t bytes) {
|
||||
std::scoped_lock lock(manager->lock);
|
||||
return manager->IsModified<DirtySource::Gpu>(offset, bytes);
|
||||
});
|
||||
if (!gpu_modified) {
|
||||
return false;
|
||||
}
|
||||
const auto action = BeginCpuFault(vaddr, size);
|
||||
if (access != PageFaultAccess::Write || action != CpuFaultAction::Download) {
|
||||
EXIT("virtual GPU write fault requires write access to GPU-dirty memory\n");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
case PageFaultPhase::Complete: {
|
||||
if (access != PageFaultAccess::Write) {
|
||||
EXIT("virtual GPU write completion requires write access\n");
|
||||
}
|
||||
bool completed = false;
|
||||
Iterate<false>(
|
||||
vaddr, size, [&completed](RegionManager* manager, uint64_t offset, uint64_t bytes) {
|
||||
std::scoped_lock lock(manager->lock);
|
||||
if (completed) {
|
||||
EXIT("virtual GPU write fault spans multiple tracked regions\n");
|
||||
}
|
||||
completed =
|
||||
manager->CompleteVirtualGpuWrite(manager->GetCpuAddr() + offset, bytes);
|
||||
});
|
||||
return completed;
|
||||
}
|
||||
}
|
||||
EXIT("unsupported virtual GPU write invalidation phase\n");
|
||||
}
|
||||
|
||||
CpuFaultAction MemoryTracker::BeginCpuFault(uint64_t vaddr, uint64_t size,
|
||||
PageFaultAccess access) noexcept {
|
||||
CheckNotInUploadCallback();
|
||||
CpuFaultAction action = CpuFaultAction::Untracked;
|
||||
Iterate<false>(
|
||||
vaddr, size, [&action, access](RegionManager* manager, uint64_t offset, uint64_t bytes) {
|
||||
std::scoped_lock lock(manager->lock);
|
||||
if (action != CpuFaultAction::Untracked) {
|
||||
EXIT("CPU fault spans multiple tracked regions\n");
|
||||
}
|
||||
action = manager->BeginCpuFault(manager->GetCpuAddr() + offset, bytes, access);
|
||||
});
|
||||
return action;
|
||||
}
|
||||
|
||||
bool MemoryTracker::CompleteCpuFault(uint64_t vaddr, uint64_t size, PageFaultAccess access,
|
||||
bool downloaded) noexcept {
|
||||
CheckNotInUploadCallback();
|
||||
bool found = false;
|
||||
Iterate<false>(
|
||||
vaddr, size,
|
||||
[&found, access, downloaded](RegionManager* manager, uint64_t offset, uint64_t bytes) {
|
||||
std::scoped_lock lock(manager->lock);
|
||||
if (found) {
|
||||
EXIT("CPU fault completion spans multiple tracked regions\n");
|
||||
}
|
||||
found = manager->CompleteCpuFault(manager->GetCpuAddr() + offset, bytes, access,
|
||||
downloaded);
|
||||
});
|
||||
return found;
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
@@ -0,0 +1,185 @@
|
||||
#ifndef EMULATOR_SRC_GRAPHICS_HOST_GPU_MEMORYTRACKER_H_
|
||||
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_MEMORYTRACKER_H_
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "graphics/host_gpu/pageManager.h"
|
||||
#include "graphics/host_gpu/regionManager.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
class MemoryTracker final {
|
||||
public:
|
||||
explicit MemoryTracker(PageManager& page_manager,
|
||||
PageWatchMode gpu_watch_mode = PageWatchMode::ReadWrite);
|
||||
~MemoryTracker();
|
||||
|
||||
KYTY_CLASS_NO_COPY(MemoryTracker);
|
||||
|
||||
[[nodiscard]] bool IsRegionCpuModified(uint64_t vaddr, uint64_t size);
|
||||
[[nodiscard]] bool IsRegionGpuModified(uint64_t vaddr, uint64_t size);
|
||||
void MarkRegionAsCpuModified(uint64_t vaddr, uint64_t size);
|
||||
void MarkRegionAsGpuModified(uint64_t vaddr, uint64_t size);
|
||||
void UnmarkRegionAsGpuModified(uint64_t vaddr, uint64_t size);
|
||||
void UntrackMemory(uint64_t vaddr, uint64_t size);
|
||||
void UnmapMemory(uint64_t vaddr, uint64_t size);
|
||||
[[nodiscard]] CpuFaultAction
|
||||
BeginCpuFault(uint64_t vaddr, uint64_t size,
|
||||
PageFaultAccess access = PageFaultAccess::Write) noexcept;
|
||||
[[nodiscard]] bool CompleteCpuFault(uint64_t vaddr, uint64_t size, PageFaultAccess access,
|
||||
bool downloaded) noexcept;
|
||||
[[nodiscard]] bool InvalidateRegion(uint64_t vaddr, uint64_t size,
|
||||
PageFaultPhase phase) noexcept;
|
||||
[[nodiscard]] bool InvalidateVirtualGpuWrite(PageFaultAccess access, uint64_t vaddr,
|
||||
uint64_t size, PageFaultPhase phase) noexcept;
|
||||
|
||||
template <bool clear, typename Preflight, typename Func>
|
||||
void ForEachDownloadRange(uint64_t vaddr, uint64_t size, Preflight&& preflight, Func&& func) {
|
||||
static_assert(std::is_nothrow_invocable_v<Preflight&, uint64_t, uint64_t>);
|
||||
static_assert(std::is_nothrow_invocable_v<Func&, uint64_t, uint64_t>);
|
||||
CheckNotInUploadCallback();
|
||||
std::lock_guard access(m_access_mutex);
|
||||
RequireMapped(vaddr, size);
|
||||
std::vector<RegionManager*> managers;
|
||||
Iterate<false>(vaddr, size, [&](RegionManager* manager, uint64_t, uint64_t) {
|
||||
managers.push_back(manager);
|
||||
});
|
||||
std::vector<std::unique_lock<TrackingSpinLock>> locks;
|
||||
locks.reserve(managers.size());
|
||||
for (auto* manager: managers) {
|
||||
locks.emplace_back(manager->lock);
|
||||
}
|
||||
Iterate<false>(vaddr, size, [&](RegionManager* manager, uint64_t offset, uint64_t bytes) {
|
||||
const auto address = manager->GetCpuAddr() + offset;
|
||||
if (manager->HasPendingFault(address, bytes)) {
|
||||
EXIT("GPU download synchronization raced a pending CPU fault\n");
|
||||
}
|
||||
manager->template ForEachModifiedRange<DirtySource::Gpu, false>(address, bytes,
|
||||
preflight);
|
||||
});
|
||||
Iterate<false>(vaddr, size, [&](RegionManager* manager, uint64_t offset, uint64_t bytes) {
|
||||
manager->template ForEachModifiedRange<DirtySource::Gpu, false>(
|
||||
manager->GetCpuAddr() + offset, bytes, func);
|
||||
});
|
||||
if constexpr (clear) {
|
||||
Iterate<false>(vaddr, size,
|
||||
[&](RegionManager* manager, uint64_t offset, uint64_t bytes) {
|
||||
const auto address = manager->GetCpuAddr() + offset;
|
||||
const auto changed =
|
||||
manager->template ForEachModifiedRange<DirtySource::Gpu, true>(
|
||||
address, bytes, [](uint64_t, uint64_t) noexcept {});
|
||||
manager->ApplyGpuProtection(changed, false, m_gpu_watch_mode);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
template <bool clear, typename Func>
|
||||
void ForEachDownloadRange(uint64_t vaddr, uint64_t size, Func&& func) {
|
||||
ForEachDownloadRange<clear>(
|
||||
vaddr, size, [](uint64_t, uint64_t) noexcept {}, std::forward<Func>(func));
|
||||
}
|
||||
|
||||
#if defined(KYTY_MEMORY_TRACKER_TESTS)
|
||||
using UnmapContentionHook = void (*)() noexcept;
|
||||
static void SetUnmapContentionHook(UnmapContentionHook hook) noexcept;
|
||||
#endif
|
||||
|
||||
template <typename RangeFunc, typename UploadFunc>
|
||||
void ForEachUploadRange(uint64_t vaddr, uint64_t size, bool is_written, RangeFunc&& range_func,
|
||||
UploadFunc&& upload_func) {
|
||||
static_assert(std::is_nothrow_invocable_v<RangeFunc&, uint64_t, uint64_t>);
|
||||
static_assert(std::is_nothrow_invocable_v<UploadFunc&>);
|
||||
CheckNotInUploadCallback();
|
||||
std::unique_lock access(m_access_mutex);
|
||||
RequireMapped(vaddr, size);
|
||||
Iterate<true>(vaddr, size, [](RegionManager*, uint64_t, uint64_t) {});
|
||||
s_upload_owner = this;
|
||||
Iterate<false>(vaddr, size, [&](RegionManager* manager, uint64_t offset, uint64_t bytes) {
|
||||
manager->lock.lock();
|
||||
manager->Track(manager->GetCpuAddr() + offset, bytes);
|
||||
manager->ForEachModifiedRange<DirtySource::Cpu, true>(manager->GetCpuAddr() + offset,
|
||||
bytes, range_func);
|
||||
if (!is_written) {
|
||||
manager->lock.unlock();
|
||||
}
|
||||
});
|
||||
upload_func();
|
||||
if (is_written) {
|
||||
Iterate<false>(
|
||||
vaddr, size, [this](RegionManager* manager, uint64_t offset, uint64_t bytes) {
|
||||
const auto changed = manager->template ChangeState<DirtySource::Gpu, true>(
|
||||
manager->GetCpuAddr() + offset, bytes);
|
||||
manager->ApplyGpuProtection(changed, true, m_gpu_watch_mode);
|
||||
manager->lock.unlock();
|
||||
});
|
||||
}
|
||||
s_upload_owner = nullptr;
|
||||
}
|
||||
|
||||
private:
|
||||
static constexpr size_t REGION_COUNT = TRACKER_ADDRESS_SIZE / TRACKER_REGION_SIZE;
|
||||
inline static thread_local const MemoryTracker* s_upload_owner = nullptr;
|
||||
|
||||
static void CheckNotInUploadCallback() noexcept {
|
||||
if (s_upload_owner != nullptr) {
|
||||
EXIT("memory tracker re-entered from upload callback\n");
|
||||
}
|
||||
}
|
||||
|
||||
template <bool create, typename Func>
|
||||
bool Iterate(uint64_t vaddr, uint64_t size, Func&& func) {
|
||||
ValidateRange(vaddr, size);
|
||||
using Result = std::invoke_result_t<Func, RegionManager*, uint64_t, uint64_t>;
|
||||
constexpr bool returns_bool = std::is_same_v<Result, bool>;
|
||||
uint64_t remaining = size;
|
||||
uint64_t index = vaddr / TRACKER_REGION_SIZE;
|
||||
uint64_t offset = vaddr % TRACKER_REGION_SIZE;
|
||||
while (remaining != 0) {
|
||||
const auto bytes = std::min(TRACKER_REGION_SIZE - offset, remaining);
|
||||
auto* manager = m_regions[index].load(std::memory_order_acquire);
|
||||
if (manager == nullptr && create) {
|
||||
manager = GetOrCreateRegion(index);
|
||||
}
|
||||
if (manager != nullptr) {
|
||||
if constexpr (returns_bool) {
|
||||
if (func(manager, offset, bytes)) {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
func(manager, offset, bytes);
|
||||
}
|
||||
}
|
||||
remaining -= bytes;
|
||||
offset = 0;
|
||||
index++;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static void ValidateRange(uint64_t vaddr, uint64_t size);
|
||||
void UntrackMemoryLocked(uint64_t vaddr, uint64_t size);
|
||||
void RequireMapped(uint64_t vaddr, uint64_t size) const {
|
||||
ValidateRange(vaddr, size);
|
||||
if (!m_page_manager.IsMapped(vaddr, size)) {
|
||||
EXIT("memory tracker range is not mapped\n");
|
||||
}
|
||||
}
|
||||
RegionManager* GetOrCreateRegion(uint64_t index);
|
||||
|
||||
std::unique_ptr<std::atomic<RegionManager*>[]> m_regions;
|
||||
std::vector<std::unique_ptr<RegionManager>> m_region_storage;
|
||||
std::mutex m_region_mutex;
|
||||
std::mutex m_access_mutex;
|
||||
PageManager& m_page_manager;
|
||||
PageWatchMode m_gpu_watch_mode = PageWatchMode::ReadWrite;
|
||||
};
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
#endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_MEMORYTRACKER_H_
|
||||
@@ -0,0 +1,432 @@
|
||||
#include "graphics/host_gpu/objects/label.h"
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "kernel/memory.h"
|
||||
#include "common/threads.h"
|
||||
#include "graphics/host_gpu/graphicContext.h"
|
||||
#include "graphics/host_gpu/renderer/render.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
enum LabelStatus {
|
||||
New,
|
||||
Active,
|
||||
ActiveDeleted,
|
||||
NotActive,
|
||||
};
|
||||
|
||||
struct LabelCallbacks {
|
||||
uint64_t* dst_gpu_addr64 = nullptr;
|
||||
uint64_t value64 = 0;
|
||||
uint32_t* dst_gpu_addr32 = nullptr;
|
||||
uint32_t value32 = 0;
|
||||
LabelCallback callback_1 = nullptr;
|
||||
LabelCallback callback_2 = nullptr;
|
||||
uint64_t args[LABEL_ARGS_MAX] = {};
|
||||
};
|
||||
|
||||
struct LabelEvent final {
|
||||
VkDevice device = nullptr;
|
||||
VkEvent event = nullptr;
|
||||
|
||||
~LabelEvent() {
|
||||
if (event != nullptr) {
|
||||
vkDestroyEvent(device, event, nullptr);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct LabelSubmission {
|
||||
std::shared_ptr<LabelEvent> completion;
|
||||
LabelCallbacks callbacks;
|
||||
};
|
||||
|
||||
struct Label {
|
||||
VkDevice device = nullptr;
|
||||
LabelStatus status = LabelStatus::New;
|
||||
LabelCallbacks callbacks;
|
||||
std::vector<LabelSubmission> submissions;
|
||||
};
|
||||
|
||||
class LabelManager {
|
||||
public:
|
||||
LabelManager() {
|
||||
EXIT_NOT_IMPLEMENTED(!Common::Thread::IsMainThread());
|
||||
Common::Thread t(ThreadRun, this);
|
||||
t.Detach();
|
||||
}
|
||||
~LabelManager() { KYTY_NOT_IMPLEMENTED; }
|
||||
KYTY_CLASS_NO_COPY(LabelManager);
|
||||
|
||||
Label* Create64(GraphicContext* ctx, uint64_t* dst_gpu_addr, uint64_t value,
|
||||
LabelCallback callback_1, LabelCallback callback_2, const uint64_t* args);
|
||||
Label* Create32(GraphicContext* ctx, uint32_t* dst_gpu_addr, uint32_t value,
|
||||
LabelCallback callback_1, LabelCallback callback_2, const uint64_t* args);
|
||||
void Delete(Label* label);
|
||||
void Set(CommandBuffer* buffer, Label* label);
|
||||
void Drain();
|
||||
|
||||
private:
|
||||
static void ThreadRun(void* data);
|
||||
|
||||
template <typename T>
|
||||
Label* Create(GraphicContext* ctx, T* dst_gpu_addr, T value, LabelCallback callback_1,
|
||||
LabelCallback callback_2, const uint64_t* args);
|
||||
bool Remove(Label* label);
|
||||
static void Destroy(Label* label);
|
||||
|
||||
Common::Mutex m_mutex;
|
||||
Common::CondVar m_cond_var;
|
||||
std::vector<Label*> m_labels;
|
||||
uint64_t m_callbacks_in_flight = 0;
|
||||
};
|
||||
|
||||
static LabelManager* g_label_manager = nullptr;
|
||||
static thread_local bool g_in_label_callback = false;
|
||||
|
||||
template <typename T>
|
||||
void WriteGuestLabel(T* address, T value) {
|
||||
LabelWriteGuestMemory(address, &value, sizeof(value));
|
||||
}
|
||||
|
||||
class LabelCallbackScope final {
|
||||
public:
|
||||
LabelCallbackScope() {
|
||||
if (g_in_label_callback) {
|
||||
EXIT("recursive GPU label callback\n");
|
||||
}
|
||||
g_in_label_callback = true;
|
||||
}
|
||||
~LabelCallbackScope() {
|
||||
if (!g_in_label_callback) {
|
||||
EXIT("GPU label callback scope is not active\n");
|
||||
}
|
||||
g_in_label_callback = false;
|
||||
}
|
||||
};
|
||||
|
||||
void LabelManager::ThreadRun(void* data) {
|
||||
auto* manager = static_cast<LabelManager*>(data);
|
||||
|
||||
for (;;) {
|
||||
manager->m_mutex.Lock();
|
||||
|
||||
uint64_t active_count = 0;
|
||||
|
||||
std::vector<Label*> deleted_labels;
|
||||
std::vector<LabelCallbacks> fired_labels;
|
||||
std::vector<LabelSubmission> finished_submissions;
|
||||
deleted_labels.reserve(manager->m_labels.size());
|
||||
|
||||
for (auto& label: manager->m_labels) {
|
||||
for (auto it = label->submissions.begin(); it != label->submissions.end();) {
|
||||
active_count++;
|
||||
|
||||
if (it->completion == nullptr || it->completion->device == nullptr ||
|
||||
it->completion->event == nullptr) {
|
||||
EXIT("GPU label submission has no completion event\n");
|
||||
}
|
||||
const auto status =
|
||||
vkGetEventStatus(it->completion->device, it->completion->event);
|
||||
switch (status) {
|
||||
case VK_EVENT_SET:
|
||||
fired_labels.push_back(it->callbacks);
|
||||
finished_submissions.push_back(*it);
|
||||
it = label->submissions.erase(it);
|
||||
break;
|
||||
case VK_EVENT_RESET: ++it; break;
|
||||
default: EXIT("vkGetEventStatus returned an unexpected result\n");
|
||||
}
|
||||
}
|
||||
|
||||
if (label->submissions.empty()) {
|
||||
switch (label->status) {
|
||||
case LabelStatus::ActiveDeleted: deleted_labels.push_back(label); break;
|
||||
case LabelStatus::Active: label->status = LabelStatus::NotActive; break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (active_count == 0) {
|
||||
manager->m_cond_var.Wait(&manager->m_mutex);
|
||||
}
|
||||
if (fired_labels.size() > UINT64_MAX - manager->m_callbacks_in_flight) {
|
||||
EXIT("GPU label callback count overflow\n");
|
||||
}
|
||||
manager->m_callbacks_in_flight += fired_labels.size();
|
||||
|
||||
for (auto& label: deleted_labels) {
|
||||
bool removed = manager->Remove(label);
|
||||
EXIT_NOT_IMPLEMENTED(!removed);
|
||||
}
|
||||
|
||||
manager->m_mutex.Unlock();
|
||||
|
||||
// Each completion event is shared with the recording command buffer's fence retainer.
|
||||
// The event is destroyed only after both the label thread observed it and that command
|
||||
// buffer completed, even when either side wins the race.
|
||||
(void)finished_submissions;
|
||||
|
||||
for (auto& label: deleted_labels) {
|
||||
Destroy(label);
|
||||
}
|
||||
|
||||
for (auto& label: fired_labels) {
|
||||
LabelCallbackScope callback_scope;
|
||||
bool write = true;
|
||||
|
||||
if (label.callback_1 != nullptr) {
|
||||
write = label.callback_1(label.args);
|
||||
}
|
||||
|
||||
if (write && label.dst_gpu_addr64 != nullptr) {
|
||||
// Publishing EOP fences through physical backing keeps a fence word coherent even
|
||||
// when unrelated bytes on its host page are
|
||||
// protected for BufferCache GPU ownership.
|
||||
WriteGuestLabel(label.dst_gpu_addr64, label.value64);
|
||||
|
||||
static std::atomic<uint32_t> log_count {0};
|
||||
if (log_count.fetch_add(1) < 256) {
|
||||
LOGF_COLOR(Log::Color::BrightGreen,
|
||||
"EndOfPipe Signal!!! [0x%016" PRIx64 "] <- 0x%016" PRIx64 "\n",
|
||||
reinterpret_cast<uint64_t>(label.dst_gpu_addr64), label.value64);
|
||||
}
|
||||
}
|
||||
|
||||
if (write && label.dst_gpu_addr32 != nullptr) {
|
||||
WriteGuestLabel(label.dst_gpu_addr32, label.value32);
|
||||
|
||||
static std::atomic<uint32_t> log_count {0};
|
||||
if (log_count.fetch_add(1) < 256) {
|
||||
LOGF_COLOR(Log::Color::BrightGreen,
|
||||
"EndOfPipe Signal!!! [0x%016" PRIx64 "] <- 0x%08" PRIx32 "\n",
|
||||
reinterpret_cast<uint64_t>(label.dst_gpu_addr32), label.value32);
|
||||
}
|
||||
}
|
||||
|
||||
if (label.callback_2 != nullptr) {
|
||||
label.callback_2(label.args);
|
||||
}
|
||||
}
|
||||
|
||||
if (!fired_labels.empty()) {
|
||||
Common::LockGuard lock(manager->m_mutex);
|
||||
if (manager->m_callbacks_in_flight < fired_labels.size()) {
|
||||
EXIT("GPU label callback count underflow\n");
|
||||
}
|
||||
manager->m_callbacks_in_flight -= fired_labels.size();
|
||||
manager->m_cond_var.SignalAll();
|
||||
}
|
||||
|
||||
Common::Thread::SleepMicro(100);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
Label* LabelManager::Create(GraphicContext* ctx, T* dst_gpu_addr, T value, LabelCallback callback_1,
|
||||
LabelCallback callback_2, const uint64_t* args) {
|
||||
static_assert(sizeof(T) == sizeof(uint32_t) || sizeof(T) == sizeof(uint64_t));
|
||||
|
||||
EXIT_IF(ctx == nullptr);
|
||||
|
||||
Common::LockGuard lock(m_mutex);
|
||||
|
||||
auto* label = new Label;
|
||||
|
||||
label->status = LabelStatus::New;
|
||||
if constexpr (sizeof(T) == sizeof(uint64_t)) {
|
||||
label->callbacks.dst_gpu_addr64 = dst_gpu_addr;
|
||||
label->callbacks.value64 = value;
|
||||
label->callbacks.dst_gpu_addr32 = nullptr;
|
||||
label->callbacks.value32 = 0;
|
||||
} else {
|
||||
label->callbacks.dst_gpu_addr32 = dst_gpu_addr;
|
||||
label->callbacks.value32 = value;
|
||||
label->callbacks.dst_gpu_addr64 = nullptr;
|
||||
label->callbacks.value64 = 0;
|
||||
}
|
||||
label->device = ctx->device;
|
||||
label->callbacks.callback_1 = callback_1;
|
||||
label->callbacks.callback_2 = callback_2;
|
||||
|
||||
if (args != nullptr) {
|
||||
for (int i = 0; i < LABEL_ARGS_MAX; i++) {
|
||||
label->callbacks.args[i] = args[i];
|
||||
}
|
||||
}
|
||||
|
||||
m_labels.push_back(label);
|
||||
|
||||
return label;
|
||||
}
|
||||
|
||||
Label* LabelManager::Create64(GraphicContext* ctx, uint64_t* dst_gpu_addr, uint64_t value,
|
||||
LabelCallback callback_1, LabelCallback callback_2,
|
||||
const uint64_t* args) {
|
||||
return Create(ctx, dst_gpu_addr, value, callback_1, callback_2, args);
|
||||
}
|
||||
|
||||
Label* LabelManager::Create32(GraphicContext* ctx, uint32_t* dst_gpu_addr, uint32_t value,
|
||||
LabelCallback callback_1, LabelCallback callback_2,
|
||||
const uint64_t* args) {
|
||||
return Create(ctx, dst_gpu_addr, value, callback_1, callback_2, args);
|
||||
}
|
||||
|
||||
bool LabelManager::Remove(Label* label) {
|
||||
EXIT_IF(label == nullptr);
|
||||
EXIT_IF(label->device == nullptr);
|
||||
|
||||
Common::LockGuard lock(m_mutex);
|
||||
|
||||
const auto it = std::find(m_labels.begin(), m_labels.end(), label);
|
||||
EXIT_NOT_IMPLEMENTED(it == m_labels.end());
|
||||
|
||||
EXIT_NOT_IMPLEMENTED(label->status != LabelStatus::NotActive &&
|
||||
label->status != LabelStatus::Active &&
|
||||
label->status != LabelStatus::ActiveDeleted);
|
||||
|
||||
if (!label->submissions.empty()) {
|
||||
label->status = LabelStatus::ActiveDeleted;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
m_labels.erase(it);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void LabelManager::Destroy(Label* label) {
|
||||
EXIT_IF(label == nullptr);
|
||||
EXIT_IF(label->device == nullptr);
|
||||
|
||||
EXIT_NOT_IMPLEMENTED(!label->submissions.empty());
|
||||
|
||||
delete label;
|
||||
}
|
||||
|
||||
void LabelManager::Delete(Label* label) {
|
||||
if (Remove(label)) {
|
||||
Destroy(label);
|
||||
}
|
||||
}
|
||||
|
||||
void LabelManager::Set(CommandBuffer* buffer, Label* label) {
|
||||
EXIT_IF(label == nullptr);
|
||||
EXIT_IF(buffer == nullptr);
|
||||
EXIT_IF(buffer->IsInvalid());
|
||||
EXIT_IF(label->device == nullptr);
|
||||
|
||||
Common::LockGuard lock(m_mutex);
|
||||
|
||||
const auto it = std::find(m_labels.begin(), m_labels.end(), label);
|
||||
EXIT_NOT_IMPLEMENTED(it == m_labels.end());
|
||||
|
||||
EXIT_NOT_IMPLEMENTED(label->status != LabelStatus::New &&
|
||||
label->status != LabelStatus::NotActive &&
|
||||
label->status != LabelStatus::Active);
|
||||
|
||||
label->status = LabelStatus::Active;
|
||||
|
||||
LabelSubmission submission {};
|
||||
submission.callbacks = label->callbacks;
|
||||
submission.completion = std::make_shared<LabelEvent>();
|
||||
submission.completion->device = label->device;
|
||||
|
||||
auto* vk_buffer = buffer->GetPool()->buffers[buffer->GetIndex()];
|
||||
|
||||
EXIT_NOT_IMPLEMENTED(vk_buffer == nullptr);
|
||||
|
||||
VkEventCreateInfo create_info {};
|
||||
create_info.sType = VK_STRUCTURE_TYPE_EVENT_CREATE_INFO;
|
||||
create_info.pNext = nullptr;
|
||||
create_info.flags = 0;
|
||||
|
||||
vkCreateEvent(label->device, &create_info, nullptr, &submission.completion->event);
|
||||
EXIT_NOT_IMPLEMENTED(submission.completion->event == nullptr);
|
||||
buffer->RetainResourceUntilFence(submission.completion);
|
||||
|
||||
// Labels can be reused before an earlier end-of-pipe event has been
|
||||
// observed by the polling thread. Capture a separate Vulkan event and
|
||||
// callback snapshot for each set so older writes are not lost.
|
||||
vkResetEvent(label->device, submission.completion->event);
|
||||
vkCmdSetEvent(vk_buffer, submission.completion->event, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT);
|
||||
|
||||
label->submissions.push_back(submission);
|
||||
|
||||
m_cond_var.SignalAll();
|
||||
}
|
||||
|
||||
void LabelManager::Drain() {
|
||||
m_mutex.Lock();
|
||||
for (;;) {
|
||||
const bool pending = std::any_of(m_labels.begin(), m_labels.end(), [](const Label* label) {
|
||||
return !label->submissions.empty();
|
||||
});
|
||||
if (!pending && m_callbacks_in_flight == 0) {
|
||||
m_mutex.Unlock();
|
||||
return;
|
||||
}
|
||||
m_cond_var.SignalAll();
|
||||
m_cond_var.Wait(&m_mutex);
|
||||
}
|
||||
}
|
||||
|
||||
void LabelInit() {
|
||||
EXIT_IF(g_label_manager != nullptr);
|
||||
|
||||
g_label_manager = new LabelManager;
|
||||
}
|
||||
|
||||
Label* LabelCreate64(GraphicContext* ctx, uint64_t* dst_gpu_addr, uint64_t value,
|
||||
LabelCallback callback_1, LabelCallback callback_2, const uint64_t* args) {
|
||||
EXIT_IF(g_label_manager == nullptr);
|
||||
|
||||
return g_label_manager->Create64(ctx, dst_gpu_addr, value, callback_1, callback_2, args);
|
||||
}
|
||||
|
||||
Label* LabelCreate32(GraphicContext* ctx, uint32_t* dst_gpu_addr, uint32_t value,
|
||||
LabelCallback callback_1, LabelCallback callback_2, const uint64_t* args) {
|
||||
EXIT_IF(g_label_manager == nullptr);
|
||||
|
||||
return g_label_manager->Create32(ctx, dst_gpu_addr, value, callback_1, callback_2, args);
|
||||
}
|
||||
|
||||
void LabelDelete(Label* label) {
|
||||
EXIT_IF(g_label_manager == nullptr);
|
||||
|
||||
g_label_manager->Delete(label);
|
||||
}
|
||||
|
||||
void LabelSet(CommandBuffer* buffer, Label* label) {
|
||||
EXIT_IF(g_label_manager == nullptr);
|
||||
|
||||
g_label_manager->Set(buffer, label);
|
||||
}
|
||||
|
||||
void LabelDrain() {
|
||||
EXIT_IF(g_label_manager == nullptr);
|
||||
g_label_manager->Drain();
|
||||
}
|
||||
|
||||
void LabelWriteGuestMemory(void* address, const void* data, uint64_t size) {
|
||||
if (address == nullptr || data == nullptr || size == 0) {
|
||||
EXIT("invalid guest label write\n");
|
||||
}
|
||||
if (!Libs::LibKernel::Memory::TryWriteBacking(reinterpret_cast<uint64_t>(address), data, size)) {
|
||||
std::memcpy(address, data, static_cast<size_t>(size));
|
||||
}
|
||||
}
|
||||
|
||||
bool LabelInCallback() noexcept {
|
||||
return g_in_label_callback;
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
@@ -0,0 +1,30 @@
|
||||
#ifndef EMULATOR_INCLUDE_EMULATOR_GRAPHICS_OBJECTS_LABEL_H_
|
||||
#define EMULATOR_INCLUDE_EMULATOR_GRAPHICS_OBJECTS_LABEL_H_
|
||||
|
||||
#include "common/abi.h"
|
||||
#include "common/common.h"
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
struct Label;
|
||||
class CommandBuffer;
|
||||
struct GraphicContext;
|
||||
|
||||
void LabelInit();
|
||||
|
||||
constexpr int LABEL_ARGS_MAX = 5;
|
||||
using LabelCallback = bool (*)(const uint64_t* args);
|
||||
|
||||
Label* LabelCreate64(GraphicContext* ctx, uint64_t* dst_gpu_addr, uint64_t value,
|
||||
LabelCallback callback_1, LabelCallback callback_2, const uint64_t* args);
|
||||
Label* LabelCreate32(GraphicContext* ctx, uint32_t* dst_gpu_addr, uint32_t value,
|
||||
LabelCallback callback_1, LabelCallback callback_2, const uint64_t* args);
|
||||
void LabelDelete(Label* label);
|
||||
void LabelSet(CommandBuffer* buffer, Label* label);
|
||||
void LabelDrain();
|
||||
void LabelWriteGuestMemory(void* address, const void* data, uint64_t size);
|
||||
[[nodiscard]] bool LabelInCallback() noexcept;
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
#endif /* EMULATOR_INCLUDE_EMULATOR_GRAPHICS_OBJECTS_LABEL_H_ */
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,140 @@
|
||||
#ifndef EMULATOR_INCLUDE_EMULATOR_GRAPHICS_OBJECTS_TEXTURECOMMON_H_
|
||||
#define EMULATOR_INCLUDE_EMULATOR_GRAPHICS_OBJECTS_TEXTURECOMMON_H_
|
||||
|
||||
#include "common/abi.h"
|
||||
#include "common/common.h"
|
||||
#include "graphics/guest_gpu/tile.h"
|
||||
#include "graphics/host_gpu/utils.h"
|
||||
|
||||
#include <vector>
|
||||
#include <vulkan/vulkan_core.h>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
struct GraphicContext;
|
||||
struct VulkanImage;
|
||||
struct VulkanMemory;
|
||||
|
||||
enum class TextureFormatUsage : uint32_t {
|
||||
None = 0,
|
||||
Sampled = 1u << 0u,
|
||||
Storage = 1u << 1u,
|
||||
};
|
||||
|
||||
constexpr TextureFormatUsage operator|(TextureFormatUsage lhs, TextureFormatUsage rhs) {
|
||||
return static_cast<TextureFormatUsage>(static_cast<uint32_t>(lhs) | static_cast<uint32_t>(rhs));
|
||||
}
|
||||
|
||||
constexpr TextureFormatUsage operator&(TextureFormatUsage lhs, TextureFormatUsage rhs) {
|
||||
return static_cast<TextureFormatUsage>(static_cast<uint32_t>(lhs) & static_cast<uint32_t>(rhs));
|
||||
}
|
||||
|
||||
constexpr TextureFormatUsage operator~(TextureFormatUsage usage) {
|
||||
return static_cast<TextureFormatUsage>(~static_cast<uint32_t>(usage));
|
||||
}
|
||||
|
||||
constexpr TextureFormatUsage& operator|=(TextureFormatUsage& lhs, TextureFormatUsage rhs) {
|
||||
lhs = lhs | rhs;
|
||||
return lhs;
|
||||
}
|
||||
|
||||
constexpr bool TextureHasFormatUsage(TextureFormatUsage usage, TextureFormatUsage flag) {
|
||||
return (static_cast<uint32_t>(usage & flag) == static_cast<uint32_t>(flag));
|
||||
}
|
||||
|
||||
enum class TextureUploadDestination { MipLevels, MipAtlas };
|
||||
|
||||
enum class TextureUploadSliceLayout { MipChainPerSlice, MipLevelPerSlice };
|
||||
|
||||
struct RenderTargetFormatInfo {
|
||||
VkFormat format = VK_FORMAT_UNDEFINED;
|
||||
uint32_t bytes_per_element = 0;
|
||||
};
|
||||
|
||||
struct TextureUploadLayout {
|
||||
uint32_t tile = 0;
|
||||
uint32_t pitch = 0;
|
||||
uint64_t slice_stride = 0;
|
||||
uint64_t source_slice_stride = 0;
|
||||
bool fmt_tiled_render_target = false;
|
||||
bool fmt_tiled_standard256b = false;
|
||||
bool fmt_tiled_standard4kb = false;
|
||||
bool fmt_tiled_standard64kb = false;
|
||||
bool fmt_tiled_depth = false;
|
||||
bool volume_texture = false;
|
||||
TileSizeOffset level_sizes[16] = {};
|
||||
TilePaddedSize padded_sizes[16] = {};
|
||||
};
|
||||
|
||||
struct TextureImageCreateParams {
|
||||
uint32_t fmt = 0;
|
||||
uint64_t width = 0;
|
||||
uint64_t height = 0;
|
||||
uint32_t base_level = 0;
|
||||
uint64_t levels = 1;
|
||||
uint32_t depth = 1;
|
||||
uint64_t type = 0;
|
||||
uint64_t swizzle = 0;
|
||||
TextureFormatUsage format_usage = TextureFormatUsage::Sampled;
|
||||
TextureFormatUsage required_format_usage = TextureFormatUsage::Sampled;
|
||||
TextureFormatUsage view_usage = TextureFormatUsage::Sampled;
|
||||
TextureUploadDestination image_layout = TextureUploadDestination::MipLevels;
|
||||
bool allow_cube_view = false;
|
||||
bool compatible_format_views = false;
|
||||
bool storage_swizzle_fallback = false;
|
||||
const char* owner = nullptr;
|
||||
};
|
||||
|
||||
VkComponentSwizzle TextureGetComponentSwizzle(uint8_t s);
|
||||
VkComponentMapping TextureGetComponentMapping(uint32_t swizzle);
|
||||
bool TextureCheckFormat(GraphicContext* ctx, VkImageCreateInfo* image_info);
|
||||
bool TextureCheckStorageSwizzle(VkImageCreateInfo* image_info, VkComponentMapping* components);
|
||||
VkImageUsageFlags TextureGetUsage(TextureFormatUsage usage);
|
||||
VkImageUsageFlags TextureGetViewUsage(TextureFormatUsage usage);
|
||||
VkFormat TextureGetFormat(uint32_t fmt, TextureFormatUsage usage);
|
||||
RenderTargetFormatInfo TextureGetRenderTargetFormat(uint32_t layout, uint32_t type, uint32_t order);
|
||||
uint32_t TextureGetAtlasSliceYStride(VkFormat format, uint32_t mip_height, uint32_t depth,
|
||||
uint64_t levels);
|
||||
uint32_t TextureCalcStackedImageHeight(VkFormat format, uint32_t height, uint32_t depth,
|
||||
uint64_t levels);
|
||||
uint32_t TextureCalcMipmapAtlasImageHeight(VkFormat format, uint32_t width, uint32_t height,
|
||||
uint32_t depth, uint64_t levels);
|
||||
bool TextureIs3DTexture(uint64_t type);
|
||||
bool TextureIsCubeTexture(uint64_t type);
|
||||
bool TextureIsLayeredTexture(uint64_t type);
|
||||
bool TextureCanCreateCubeView(uint64_t type, uint32_t base_array, uint32_t layer_count);
|
||||
VkComponentMapping TextureCreateImage(GraphicContext* ctx, VulkanImage* vk_obj, VulkanMemory* mem,
|
||||
const TextureImageCreateParams& params);
|
||||
void TextureCreateImageViews(GraphicContext* ctx, VulkanImage* vk_obj,
|
||||
VkComponentMapping components, uint64_t type, uint32_t base_array,
|
||||
uint32_t base_level, uint32_t level_count, uint32_t depth,
|
||||
bool allow_cube_view, TextureFormatUsage view_usage);
|
||||
TextureUploadLayout TextureCalcUploadLayout(uint32_t fmt, uint64_t width, uint64_t height,
|
||||
uint64_t levels, uint32_t depth, uint64_t pitch,
|
||||
uint64_t tile, uint64_t upload_size,
|
||||
bool allow_depth_tile,
|
||||
bool require_single_mip_small_tiles,
|
||||
bool volume_texture, const char* owner);
|
||||
uint64_t TextureUploadSliceSourceOffset(const TextureUploadLayout& layout, uint32_t level,
|
||||
uint32_t slice,
|
||||
TextureUploadSliceLayout source_slice_layout);
|
||||
uint64_t TextureCalcUploadSize(const TextureUploadLayout& layout,
|
||||
const std::vector<BufferImageCopy>& regions, uint64_t levels,
|
||||
uint32_t depth, TextureUploadSliceLayout source_slice_layout);
|
||||
std::vector<BufferImageCopy>
|
||||
TextureBuildUploadRegions(const TextureUploadLayout& layout, VkFormat image_format, uint32_t width,
|
||||
uint32_t height, uint32_t depth, uint64_t levels, bool array_texture,
|
||||
bool volume_texture, TextureUploadDestination destination,
|
||||
TextureUploadSliceLayout slice_layout);
|
||||
void TextureCopyBufferBytes(GraphicContext* ctx, VulkanBuffer* src_buffer,
|
||||
uint64_t src_buffer_offset, uint64_t copy_size, UtilScratchBuffer* dst);
|
||||
void TextureUploadGuestImage(GraphicContext* ctx, VulkanImage* vk_obj, const void* src_data,
|
||||
uint64_t size, const std::vector<BufferImageCopy>& regions,
|
||||
const TextureUploadLayout& layout, uint32_t fmt, uint64_t width,
|
||||
uint64_t height, uint32_t depth, uint64_t levels,
|
||||
TextureUploadSliceLayout source_slice_layout, const char* owner,
|
||||
uint64_t dst_layout);
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
#endif /* EMULATOR_INCLUDE_EMULATOR_GRAPHICS_OBJECTS_TEXTURECOMMON_H_ */
|
||||
@@ -0,0 +1,647 @@
|
||||
#include "graphics/host_gpu/pageManager.h"
|
||||
|
||||
#include "graphics/host_gpu/regionDefinitions.h"
|
||||
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <cstdarg>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
#include <windows.h>
|
||||
#undef min
|
||||
#undef max
|
||||
#endif
|
||||
|
||||
namespace Libs::Graphics {
|
||||
namespace {
|
||||
|
||||
constexpr uint64_t PAGE_SIZE = TRACKER_PAGE_SIZE;
|
||||
constexpr uint64_t REGION_SIZE = TRACKER_REGION_SIZE;
|
||||
constexpr uint64_t ADDRESS_SIZE = TRACKER_ADDRESS_SIZE;
|
||||
constexpr uint64_t REGION_COUNT = ADDRESS_SIZE / REGION_SIZE;
|
||||
constexpr uint64_t REGION_PAGES = REGION_SIZE / PAGE_SIZE;
|
||||
|
||||
thread_local bool g_in_fault_resolution = false;
|
||||
|
||||
[[noreturn]] void FailFast(const char* reason = nullptr) noexcept {
|
||||
std::fputs("PageManager fail-fast: ", stderr);
|
||||
std::fputs(reason != nullptr ? reason : "invalid page state", stderr);
|
||||
std::fputc('\n', stderr);
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
void* frames[16] {};
|
||||
const auto frame_count = CaptureStackBackTrace(0, static_cast<DWORD>(std::size(frames)), frames,
|
||||
nullptr);
|
||||
const auto image_base = reinterpret_cast<uintptr_t>(GetModuleHandleW(nullptr));
|
||||
for (uint16_t i = 0; i < frame_count; i++) {
|
||||
const auto address = reinterpret_cast<uintptr_t>(frames[i]);
|
||||
std::fprintf(stderr, " frame[%u]=0x%016" PRIxPTR " image_rva=0x%016" PRIxPTR "\n", i,
|
||||
address, address >= image_base ? address - image_base : 0);
|
||||
}
|
||||
#endif
|
||||
std::fflush(stderr);
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
TerminateProcess(GetCurrentProcess(), static_cast<UINT>(EXCEPTION_NONCONTINUABLE_EXCEPTION));
|
||||
#endif
|
||||
std::_Exit(322);
|
||||
}
|
||||
|
||||
[[noreturn]] void Fatal(const char* format, ...) {
|
||||
std::fputs("PageManager fatal: ", stderr);
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
std::vfprintf(stderr, format, args);
|
||||
va_end(args);
|
||||
std::fputc('\n', stderr);
|
||||
std::fflush(stderr);
|
||||
std::_Exit(322);
|
||||
}
|
||||
|
||||
uint32_t CurrentThread() noexcept {
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
return GetCurrentThreadId();
|
||||
#else
|
||||
FailFast();
|
||||
#endif
|
||||
}
|
||||
|
||||
class SpinGuard final {
|
||||
public:
|
||||
explicit SpinGuard(std::atomic_flag& lock): m_lock(lock) {
|
||||
while (m_lock.test_and_set(std::memory_order_acquire)) {
|
||||
std::atomic_signal_fence(std::memory_order_seq_cst);
|
||||
}
|
||||
}
|
||||
~SpinGuard() { m_lock.clear(std::memory_order_release); }
|
||||
KYTY_CLASS_NO_COPY(SpinGuard);
|
||||
|
||||
private:
|
||||
std::atomic_flag& m_lock;
|
||||
};
|
||||
|
||||
void ValidateRange(uint64_t vaddr, uint64_t size) {
|
||||
if (vaddr == 0 || size == 0 || vaddr >= ADDRESS_SIZE || size > ADDRESS_SIZE - vaddr) {
|
||||
Fatal("invalid range vaddr=0x%016" PRIx64 ", size=0x%016" PRIx64, vaddr, size);
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t PageStart(uint64_t vaddr) {
|
||||
return vaddr & ~(PAGE_SIZE - 1);
|
||||
}
|
||||
|
||||
uint64_t PageEnd(uint64_t vaddr, uint64_t size) {
|
||||
ValidateRange(vaddr, size);
|
||||
return PageStart(vaddr + size - 1) + PAGE_SIZE;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
struct PageManager::Impl {
|
||||
struct PageState {
|
||||
std::atomic_flag lock = ATOMIC_FLAG_INIT;
|
||||
uint32_t mappings = 0;
|
||||
uint32_t gpu_read_mappings = 0;
|
||||
uint32_t gpu_write_mappings = 0;
|
||||
uint32_t write_watchers = 0;
|
||||
uint32_t access_watchers = 0;
|
||||
uint32_t original_protection = 0;
|
||||
uint32_t backing_writer = 0;
|
||||
bool resolving = false;
|
||||
bool resolving_read_write = false;
|
||||
bool late_read_pending = false;
|
||||
bool late_write_pending = false;
|
||||
};
|
||||
|
||||
struct Region {
|
||||
std::array<PageState, REGION_PAGES> pages;
|
||||
};
|
||||
|
||||
Impl(PageFaultHandler handler, void* context): fault_handler(handler), fault_context(context) {
|
||||
if (fault_handler == nullptr) {
|
||||
Fatal("null fault handler");
|
||||
}
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
SYSTEM_INFO info {};
|
||||
GetSystemInfo(&info);
|
||||
if (info.dwPageSize != PAGE_SIZE) {
|
||||
Fatal("unsupported host page size 0x%08" PRIx32,
|
||||
static_cast<uint32_t>(info.dwPageSize));
|
||||
}
|
||||
#else
|
||||
Fatal("page-fault invalidation is not implemented on this platform");
|
||||
#endif
|
||||
regions = std::make_unique<std::atomic<Region*>[]>(REGION_COUNT);
|
||||
for (uint64_t i = 0; i < REGION_COUNT; i++) {
|
||||
regions[i].store(nullptr, std::memory_order_relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
~Impl() {
|
||||
for (const auto& region: region_storage) {
|
||||
for (auto& page: region->pages) {
|
||||
SpinGuard lock(page.lock);
|
||||
if (page.mappings != 0 || page.gpu_read_mappings != 0 ||
|
||||
page.gpu_write_mappings != 0 || page.write_watchers != 0 ||
|
||||
page.access_watchers != 0 || page.backing_writer != 0 || page.resolving) {
|
||||
FailFast("PageManager destroyed with live page state");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Region* FindRegion(uint64_t vaddr) const noexcept {
|
||||
return vaddr < ADDRESS_SIZE ? regions[vaddr / REGION_SIZE].load(std::memory_order_acquire)
|
||||
: nullptr;
|
||||
}
|
||||
|
||||
Region* GetOrCreateRegion(uint64_t vaddr) {
|
||||
const auto index = vaddr / REGION_SIZE;
|
||||
if (auto* region = regions[index].load(std::memory_order_acquire); region != nullptr) {
|
||||
return region;
|
||||
}
|
||||
std::lock_guard lock(region_mutex);
|
||||
if (auto* region = regions[index].load(std::memory_order_acquire); region != nullptr) {
|
||||
return region;
|
||||
}
|
||||
auto region = std::make_unique<Region>();
|
||||
auto* ptr = region.get();
|
||||
region_storage.push_back(std::move(region));
|
||||
regions[index].store(ptr, std::memory_order_release);
|
||||
return ptr;
|
||||
}
|
||||
|
||||
PageState& GetPage(Region& region, uint64_t vaddr) const {
|
||||
return region.pages[(vaddr % REGION_SIZE) / PAGE_SIZE];
|
||||
}
|
||||
|
||||
static uint32_t WatcherProtection(const PageState& page) {
|
||||
if (page.access_watchers != 0) {
|
||||
return PAGE_NOACCESS;
|
||||
}
|
||||
if (page.write_watchers != 0) {
|
||||
return PAGE_READONLY;
|
||||
}
|
||||
return page.original_protection;
|
||||
}
|
||||
|
||||
static void PublishDelayedFaults(PageState& page, uint32_t old_protection,
|
||||
uint32_t new_protection) {
|
||||
if (old_protection == PAGE_NOACCESS && new_protection != PAGE_NOACCESS) {
|
||||
page.late_read_pending = true;
|
||||
}
|
||||
if ((old_protection == PAGE_NOACCESS || old_protection == PAGE_READONLY) &&
|
||||
new_protection == PAGE_READWRITE) {
|
||||
page.late_write_pending = true;
|
||||
}
|
||||
}
|
||||
|
||||
static uint32_t QueryProtection(uint64_t vaddr) {
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
MEMORY_BASIC_INFORMATION info {};
|
||||
if (VirtualQuery(reinterpret_cast<const void*>(static_cast<uintptr_t>(vaddr)), &info,
|
||||
sizeof(info)) == 0 ||
|
||||
info.State != MEM_COMMIT || info.Protect != PAGE_READWRITE) {
|
||||
Fatal("basic path requires PAGE_READWRITE at 0x%016" PRIx64 " (state=0x%08" PRIx32
|
||||
", protection=0x%08" PRIx32 ")",
|
||||
vaddr, static_cast<uint32_t>(info.State), static_cast<uint32_t>(info.Protect));
|
||||
}
|
||||
return info.Protect;
|
||||
#else
|
||||
(void)vaddr;
|
||||
Fatal("page query is unsupported on this platform");
|
||||
#endif
|
||||
}
|
||||
|
||||
static bool AllowsAccess(uint64_t vaddr, PageFaultAccess access) noexcept {
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
MEMORY_BASIC_INFORMATION info {};
|
||||
if (VirtualQuery(reinterpret_cast<const void*>(static_cast<uintptr_t>(vaddr)), &info,
|
||||
sizeof(info)) == 0 ||
|
||||
info.State != MEM_COMMIT) {
|
||||
return false;
|
||||
}
|
||||
switch (access) {
|
||||
case PageFaultAccess::Read:
|
||||
return info.Protect == PAGE_READONLY || info.Protect == PAGE_READWRITE;
|
||||
case PageFaultAccess::Write: return info.Protect == PAGE_READWRITE;
|
||||
default: return false;
|
||||
}
|
||||
#else
|
||||
(void)vaddr;
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
static void Protect(uint64_t vaddr, uint32_t protection, uint32_t expected_old,
|
||||
bool fault_path) noexcept {
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
DWORD old_protection = 0;
|
||||
if (VirtualProtect(reinterpret_cast<void*>(static_cast<uintptr_t>(vaddr)), PAGE_SIZE,
|
||||
protection, &old_protection) == 0 ||
|
||||
old_protection != expected_old) {
|
||||
if (fault_path) {
|
||||
FailFast("VirtualProtect fault transition did not match expected protection");
|
||||
}
|
||||
Fatal("invalid protection transition at 0x%016" PRIx64 ", old=0x%08" PRIx32
|
||||
", expected=0x%08" PRIx32 ", new=0x%08" PRIx32,
|
||||
vaddr, static_cast<uint32_t>(old_protection), expected_old, protection);
|
||||
}
|
||||
#else
|
||||
(void)vaddr;
|
||||
(void)protection;
|
||||
(void)fault_path;
|
||||
FailFast("page protection is unsupported on this platform");
|
||||
#endif
|
||||
}
|
||||
|
||||
std::unique_ptr<std::atomic<Region*>[]> regions;
|
||||
std::vector<std::unique_ptr<Region>> region_storage;
|
||||
std::mutex region_mutex;
|
||||
PageFaultHandler fault_handler = nullptr;
|
||||
void* fault_context = nullptr;
|
||||
};
|
||||
|
||||
static_assert(std::atomic<void*>::is_always_lock_free);
|
||||
|
||||
PageManager::PageManager(PageFaultHandler fault_handler, void* fault_context)
|
||||
: m_impl(std::make_unique<Impl>(fault_handler, fault_context)) {}
|
||||
|
||||
PageManager::~PageManager() = default;
|
||||
|
||||
uint64_t PageManager::GetPageSize() const {
|
||||
if (g_in_fault_resolution) {
|
||||
FailFast("nested page fault while resolving a watched page");
|
||||
}
|
||||
return PAGE_SIZE;
|
||||
}
|
||||
|
||||
bool PageManager::IsTracked(uint64_t vaddr) const noexcept {
|
||||
if (g_in_fault_resolution) {
|
||||
FailFast("IsTracked called during fault resolution");
|
||||
}
|
||||
auto* region = m_impl->FindRegion(vaddr);
|
||||
if (region == nullptr) {
|
||||
return false;
|
||||
}
|
||||
auto& page = m_impl->GetPage(*region, vaddr);
|
||||
SpinGuard lock(page.lock);
|
||||
return page.write_watchers != 0 || page.access_watchers != 0;
|
||||
}
|
||||
|
||||
bool PageManager::IsMapped(uint64_t vaddr, uint64_t size) const noexcept {
|
||||
if (g_in_fault_resolution || vaddr == 0 || size == 0 || vaddr >= ADDRESS_SIZE ||
|
||||
size > ADDRESS_SIZE - vaddr) {
|
||||
return false;
|
||||
}
|
||||
const auto end = PageStart(vaddr + size - 1) + PAGE_SIZE;
|
||||
for (auto page_vaddr = PageStart(vaddr); page_vaddr < end; page_vaddr += PAGE_SIZE) {
|
||||
auto* region = m_impl->FindRegion(page_vaddr);
|
||||
if (region == nullptr) {
|
||||
return false;
|
||||
}
|
||||
auto& page = m_impl->GetPage(*region, page_vaddr);
|
||||
SpinGuard lock(page.lock);
|
||||
if (page.mappings == 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PageManager::HasGpuAccess(uint64_t vaddr, uint64_t size, GpuAccess access) const noexcept {
|
||||
if (access != GpuAccess::Read && access != GpuAccess::Write && access != GpuAccess::ReadWrite) {
|
||||
FailFast("HasGpuAccess received an invalid GPU access mode");
|
||||
}
|
||||
const bool need_read = access == GpuAccess::Read || access == GpuAccess::ReadWrite;
|
||||
const bool need_write = access == GpuAccess::Write || access == GpuAccess::ReadWrite;
|
||||
if (vaddr == 0 || size == 0 || vaddr >= ADDRESS_SIZE || size > ADDRESS_SIZE - vaddr) {
|
||||
return false;
|
||||
}
|
||||
const auto end = PageEnd(vaddr, size);
|
||||
for (auto addr = PageStart(vaddr); addr < end; addr += PAGE_SIZE) {
|
||||
auto* region = m_impl->FindRegion(addr);
|
||||
if (region == nullptr) {
|
||||
return false;
|
||||
}
|
||||
auto& page = m_impl->GetPage(*region, addr);
|
||||
SpinGuard lock(page.lock);
|
||||
if ((need_read && page.gpu_read_mappings == 0) ||
|
||||
(need_write && page.gpu_write_mappings == 0)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void PageManager::UpdatePageWatchers(bool track, uint64_t vaddr, uint64_t size,
|
||||
PageWatchMode mode) {
|
||||
if (g_in_fault_resolution) {
|
||||
FailFast("page watchers changed during fault resolution");
|
||||
}
|
||||
if (mode != PageWatchMode::Write && mode != PageWatchMode::ReadWrite) {
|
||||
Fatal("invalid watcher mode");
|
||||
}
|
||||
const auto end = PageEnd(vaddr, size);
|
||||
for (auto page_vaddr = PageStart(vaddr); page_vaddr < end; page_vaddr += PAGE_SIZE) {
|
||||
auto* region =
|
||||
track ? m_impl->GetOrCreateRegion(page_vaddr) : m_impl->FindRegion(page_vaddr);
|
||||
if (region == nullptr) {
|
||||
Fatal("untracking unknown page 0x%016" PRIx64, page_vaddr);
|
||||
}
|
||||
auto& page = m_impl->GetPage(*region, page_vaddr);
|
||||
SpinGuard lock(page.lock);
|
||||
if (page.resolving && track) {
|
||||
FailFast("new page watcher raced active fault resolution");
|
||||
}
|
||||
if (page.mappings == 0) {
|
||||
Fatal("watching unmapped page 0x%016" PRIx64, page_vaddr);
|
||||
}
|
||||
auto& watchers =
|
||||
(mode == PageWatchMode::ReadWrite ? page.access_watchers : page.write_watchers);
|
||||
if (track) {
|
||||
if (watchers == std::numeric_limits<uint32_t>::max()) {
|
||||
Fatal("watcher overflow at 0x%016" PRIx64, page_vaddr);
|
||||
}
|
||||
const bool first_watcher = page.write_watchers == 0 && page.access_watchers == 0;
|
||||
if (first_watcher) {
|
||||
page.original_protection = Impl::QueryProtection(page_vaddr);
|
||||
}
|
||||
const auto old_protection = Impl::WatcherProtection(page);
|
||||
watchers++;
|
||||
const auto new_protection = Impl::WatcherProtection(page);
|
||||
if (new_protection != old_protection) {
|
||||
Impl::Protect(page_vaddr, new_protection, old_protection, false);
|
||||
}
|
||||
switch (new_protection) {
|
||||
case PAGE_NOACCESS:
|
||||
page.late_read_pending = false;
|
||||
page.late_write_pending = false;
|
||||
break;
|
||||
case PAGE_READONLY: page.late_write_pending = false; break;
|
||||
default: break;
|
||||
}
|
||||
} else {
|
||||
if (watchers == 0) {
|
||||
Fatal("watcher underflow at 0x%016" PRIx64, page_vaddr);
|
||||
}
|
||||
if (page.backing_writer != 0 && page.backing_writer != CurrentThread()) {
|
||||
Fatal("backing write ownership changed at 0x%016" PRIx64, page_vaddr);
|
||||
}
|
||||
const auto old_protection = Impl::WatcherProtection(page);
|
||||
watchers--;
|
||||
const auto new_protection = Impl::WatcherProtection(page);
|
||||
if (page.backing_writer == 0 && new_protection != old_protection) {
|
||||
Impl::Protect(page_vaddr, new_protection, old_protection, false);
|
||||
}
|
||||
if (page.backing_writer == 0) {
|
||||
Impl::PublishDelayedFaults(page, old_protection, new_protection);
|
||||
}
|
||||
if (page.backing_writer == 0 && page.write_watchers == 0 && page.access_watchers == 0) {
|
||||
page.original_protection = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PageManager::OnGpuMap(uint64_t vaddr, uint64_t size, GpuAccess access) {
|
||||
if (g_in_fault_resolution) {
|
||||
FailFast("GPU mapping changed during fault resolution");
|
||||
}
|
||||
if (access != GpuAccess::Read && access != GpuAccess::Write && access != GpuAccess::ReadWrite) {
|
||||
FailFast("GPU map received an invalid access mode");
|
||||
}
|
||||
const bool gpu_read = access == GpuAccess::Read || access == GpuAccess::ReadWrite;
|
||||
const bool gpu_write = access == GpuAccess::Write || access == GpuAccess::ReadWrite;
|
||||
const auto end = PageEnd(vaddr, size);
|
||||
for (auto addr = PageStart(vaddr); addr < end; addr += PAGE_SIZE) {
|
||||
auto& page = m_impl->GetPage(*m_impl->GetOrCreateRegion(addr), addr);
|
||||
SpinGuard lock(page.lock);
|
||||
if (page.resolving || page.mappings == std::numeric_limits<uint32_t>::max() ||
|
||||
(gpu_read && page.gpu_read_mappings == std::numeric_limits<uint32_t>::max()) ||
|
||||
(gpu_write && page.gpu_write_mappings == std::numeric_limits<uint32_t>::max())) {
|
||||
Fatal("invalid map state at 0x%016" PRIx64, addr);
|
||||
}
|
||||
page.mappings++;
|
||||
page.gpu_read_mappings += gpu_read ? 1u : 0u;
|
||||
page.gpu_write_mappings += gpu_write ? 1u : 0u;
|
||||
}
|
||||
}
|
||||
|
||||
void PageManager::OnGpuUnmap(uint64_t vaddr, uint64_t size, GpuAccess access) {
|
||||
if (g_in_fault_resolution) {
|
||||
FailFast("GPU unmapping changed during fault resolution");
|
||||
}
|
||||
if (access != GpuAccess::Read && access != GpuAccess::Write && access != GpuAccess::ReadWrite) {
|
||||
FailFast("GPU unmap received an invalid access mode");
|
||||
}
|
||||
const bool gpu_read = access == GpuAccess::Read || access == GpuAccess::ReadWrite;
|
||||
const bool gpu_write = access == GpuAccess::Write || access == GpuAccess::ReadWrite;
|
||||
const auto end = PageEnd(vaddr, size);
|
||||
for (auto page_vaddr = PageStart(vaddr); page_vaddr < end; page_vaddr += PAGE_SIZE) {
|
||||
auto* region = m_impl->FindRegion(page_vaddr);
|
||||
if (region == nullptr) {
|
||||
Fatal("unmapping unknown page 0x%016" PRIx64, page_vaddr);
|
||||
}
|
||||
auto& page = m_impl->GetPage(*region, page_vaddr);
|
||||
SpinGuard lock(page.lock);
|
||||
if (page.resolving || page.mappings == 0 || (gpu_read && page.gpu_read_mappings == 0) ||
|
||||
(gpu_write && page.gpu_write_mappings == 0) ||
|
||||
(page.mappings == 1 && (page.write_watchers != 0 || page.access_watchers != 0))) {
|
||||
Fatal("invalid unmap state at 0x%016" PRIx64, page_vaddr);
|
||||
}
|
||||
page.mappings--;
|
||||
page.gpu_read_mappings -= gpu_read ? 1u : 0u;
|
||||
page.gpu_write_mappings -= gpu_write ? 1u : 0u;
|
||||
if (page.mappings == 0) {
|
||||
if (page.gpu_read_mappings != 0 || page.gpu_write_mappings != 0) {
|
||||
FailFast("GPU unmap left nonzero GPU mapping counts");
|
||||
}
|
||||
page.late_read_pending = false;
|
||||
page.late_write_pending = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PageManager::BackingWrite::BackingWrite(PageManager& manager, uint64_t vaddr,
|
||||
uint64_t size) noexcept
|
||||
: m_manager(manager), m_vaddr(vaddr), m_size(size) {
|
||||
m_manager.BeginBackingWrite(vaddr, size);
|
||||
}
|
||||
|
||||
PageManager::BackingWrite::~BackingWrite() {
|
||||
m_manager.EndBackingWrite(m_vaddr, m_size);
|
||||
}
|
||||
|
||||
void PageManager::BeginBackingWrite(uint64_t vaddr, uint64_t size) noexcept {
|
||||
if (g_in_fault_resolution) {
|
||||
FailFast("backing write began during fault resolution");
|
||||
}
|
||||
const auto end = PageEnd(vaddr, size);
|
||||
const auto writer = CurrentThread();
|
||||
for (auto address = PageStart(vaddr); address < end; address += PAGE_SIZE) {
|
||||
auto* region = m_impl->FindRegion(address);
|
||||
if (region == nullptr) {
|
||||
Fatal("backing write reserves an unknown page at 0x%016" PRIx64, address);
|
||||
}
|
||||
auto& page = m_impl->GetPage(*region, address);
|
||||
SpinGuard lock(page.lock);
|
||||
if (page.mappings == 0 || page.resolving || page.backing_writer != 0 ||
|
||||
page.access_watchers == 0) {
|
||||
Fatal("backing write races page resolution at 0x%016" PRIx64, address);
|
||||
}
|
||||
page.resolving = true;
|
||||
page.resolving_read_write = true;
|
||||
page.backing_writer = writer;
|
||||
}
|
||||
}
|
||||
|
||||
void PageManager::EndBackingWrite(uint64_t vaddr, uint64_t size) noexcept {
|
||||
if (g_in_fault_resolution) {
|
||||
FailFast("backing write ended during fault resolution");
|
||||
}
|
||||
const auto end = PageEnd(vaddr, size);
|
||||
const auto writer = CurrentThread();
|
||||
for (auto address = PageStart(vaddr); address < end; address += PAGE_SIZE) {
|
||||
auto* region = m_impl->FindRegion(address);
|
||||
if (region == nullptr) {
|
||||
FailFast("backing write ended for an unknown page");
|
||||
}
|
||||
auto& page = m_impl->GetPage(*region, address);
|
||||
SpinGuard lock(page.lock);
|
||||
if (!page.resolving || page.backing_writer != writer) {
|
||||
FailFast("backing write ended without matching owner and resolving state");
|
||||
}
|
||||
const auto old_protection = PAGE_NOACCESS;
|
||||
const auto new_protection = Impl::WatcherProtection(page);
|
||||
if (new_protection != old_protection) {
|
||||
Impl::Protect(address, new_protection, old_protection, false);
|
||||
}
|
||||
Impl::PublishDelayedFaults(page, old_protection, new_protection);
|
||||
if (page.write_watchers == 0 && page.access_watchers == 0) {
|
||||
page.original_protection = 0;
|
||||
}
|
||||
page.backing_writer = 0;
|
||||
page.resolving = false;
|
||||
page.resolving_read_write = false;
|
||||
}
|
||||
}
|
||||
|
||||
bool PageManager::HandleFault(PageFaultAccess access, uint64_t fault_vaddr) noexcept {
|
||||
if (g_in_fault_resolution) {
|
||||
FailFast("nested HandleFault call");
|
||||
}
|
||||
auto* region = m_impl->FindRegion(fault_vaddr);
|
||||
if (region == nullptr) {
|
||||
return false;
|
||||
}
|
||||
auto& page = m_impl->GetPage(*region, fault_vaddr);
|
||||
bool waited = false;
|
||||
while (true) {
|
||||
SpinGuard lock(page.lock);
|
||||
if (access == PageFaultAccess::Read && page.late_read_pending &&
|
||||
Impl::AllowsAccess(fault_vaddr, access)) {
|
||||
page.late_read_pending = false;
|
||||
return true;
|
||||
}
|
||||
if (access == PageFaultAccess::Write && page.late_write_pending &&
|
||||
Impl::AllowsAccess(fault_vaddr, access)) {
|
||||
page.late_write_pending = false;
|
||||
return true;
|
||||
}
|
||||
if (page.resolving) {
|
||||
if (page.backing_writer == CurrentThread()) {
|
||||
FailFast("backing writer faulted on its own reserved page");
|
||||
}
|
||||
if ((!page.resolving_read_write && access != PageFaultAccess::Write) ||
|
||||
(page.resolving_read_write && access != PageFaultAccess::Read &&
|
||||
access != PageFaultAccess::Write)) {
|
||||
FailFast("fault access is incompatible with the active resolver");
|
||||
}
|
||||
waited = true;
|
||||
continue;
|
||||
}
|
||||
if (page.write_watchers == 0 && page.access_watchers == 0) {
|
||||
if (access != PageFaultAccess::Read && access != PageFaultAccess::Write) {
|
||||
return false;
|
||||
}
|
||||
bool& pending = (access == PageFaultAccess::Read ? page.late_read_pending
|
||||
: page.late_write_pending);
|
||||
const bool allowed = Impl::AllowsAccess(fault_vaddr, access);
|
||||
pending = false;
|
||||
if (waited && !allowed) {
|
||||
FailFast("page remained inaccessible after waiting for its resolver");
|
||||
}
|
||||
// More than one CPU can fault before a protection transition becomes visible. The first
|
||||
// delayed fault consumes the hint bit; later faults must also resume once the mapped page
|
||||
// already permits the requested access. A genuinely read-only/no-access page still falls
|
||||
// through to the guest exception path.
|
||||
return allowed;
|
||||
}
|
||||
if ((access != PageFaultAccess::Read && access != PageFaultAccess::Write) ||
|
||||
(access == PageFaultAccess::Read && page.access_watchers == 0)) {
|
||||
FailFast("fault access is incompatible with active page watchers");
|
||||
}
|
||||
page.resolving = true;
|
||||
page.resolving_read_write = page.access_watchers != 0;
|
||||
break;
|
||||
}
|
||||
g_in_fault_resolution = true;
|
||||
const bool handled = m_impl->fault_handler(m_impl->fault_context, access, fault_vaddr, 1,
|
||||
PageFaultPhase::Invalidate);
|
||||
g_in_fault_resolution = false;
|
||||
{
|
||||
SpinGuard lock(page.lock);
|
||||
if (!handled || !page.resolving) {
|
||||
FailFast("fault invalidation did not preserve the resolving state");
|
||||
}
|
||||
}
|
||||
g_in_fault_resolution = true;
|
||||
const bool completed = m_impl->fault_handler(m_impl->fault_context, access, fault_vaddr, 1,
|
||||
PageFaultPhase::Complete);
|
||||
g_in_fault_resolution = false;
|
||||
{
|
||||
SpinGuard lock(page.lock);
|
||||
if (!completed || !page.resolving) {
|
||||
FailFast("fault completion did not preserve the resolving state");
|
||||
}
|
||||
if (page.write_watchers != 0 || page.access_watchers != 0) {
|
||||
const auto old_protection = Impl::WatcherProtection(page);
|
||||
const bool read_only_fault = access == PageFaultAccess::Read;
|
||||
if (read_only_fault && page.access_watchers == 0) {
|
||||
FailFast("read fault completed without a read/write watcher");
|
||||
}
|
||||
page.access_watchers = 0;
|
||||
if (!read_only_fault) {
|
||||
page.write_watchers = 0;
|
||||
}
|
||||
const auto restored_protection = Impl::WatcherProtection(page);
|
||||
Impl::Protect(PageStart(fault_vaddr), restored_protection, old_protection, true);
|
||||
if (page.write_watchers == 0) {
|
||||
page.original_protection = 0;
|
||||
}
|
||||
Impl::PublishDelayedFaults(page, old_protection, restored_protection);
|
||||
} else if (!Impl::AllowsAccess(fault_vaddr, access)) {
|
||||
FailFast("fault completion left the page inaccessible");
|
||||
}
|
||||
page.resolving = false;
|
||||
page.resolving_read_write = false;
|
||||
}
|
||||
g_in_fault_resolution = true;
|
||||
const bool released = m_impl->fault_handler(m_impl->fault_context, access, fault_vaddr, 1,
|
||||
PageFaultPhase::Release);
|
||||
g_in_fault_resolution = false;
|
||||
if (!released) {
|
||||
FailFast("fault release callback failed");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
@@ -0,0 +1,60 @@
|
||||
#ifndef EMULATOR_SRC_GRAPHICS_HOST_GPU_PAGEMANAGER_H_
|
||||
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_PAGEMANAGER_H_
|
||||
|
||||
#include "common/common.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
enum class PageFaultAccess { Read, Write, Execute, Unknown };
|
||||
enum class PageFaultPhase { Invalidate, Complete, Release };
|
||||
enum class PageWatchMode { Write, ReadWrite };
|
||||
enum class GpuAccess { Read, Write, ReadWrite };
|
||||
|
||||
using PageFaultHandler = bool (*)(void* context, PageFaultAccess access, uint64_t vaddr,
|
||||
uint64_t size, PageFaultPhase phase) noexcept;
|
||||
|
||||
class PageManager final {
|
||||
public:
|
||||
class BackingWrite final {
|
||||
public:
|
||||
BackingWrite(PageManager& manager, uint64_t vaddr, uint64_t size) noexcept;
|
||||
~BackingWrite();
|
||||
KYTY_CLASS_NO_COPY(BackingWrite);
|
||||
|
||||
private:
|
||||
PageManager& m_manager;
|
||||
uint64_t m_vaddr = 0;
|
||||
uint64_t m_size = 0;
|
||||
};
|
||||
|
||||
PageManager(PageFaultHandler fault_handler, void* fault_context);
|
||||
// The owner must stop all PageManager callers before destruction.
|
||||
~PageManager();
|
||||
|
||||
KYTY_CLASS_NO_COPY(PageManager);
|
||||
|
||||
[[nodiscard]] uint64_t GetPageSize() const;
|
||||
[[nodiscard]] bool IsTracked(uint64_t vaddr) const noexcept;
|
||||
[[nodiscard]] bool IsMapped(uint64_t vaddr, uint64_t size) const noexcept;
|
||||
[[nodiscard]] bool HasGpuAccess(uint64_t vaddr, uint64_t size, GpuAccess access) const noexcept;
|
||||
|
||||
void UpdatePageWatchers(bool track, uint64_t vaddr, uint64_t size,
|
||||
PageWatchMode mode = PageWatchMode::Write);
|
||||
void OnGpuMap(uint64_t vaddr, uint64_t size, GpuAccess access = GpuAccess::ReadWrite);
|
||||
void OnGpuUnmap(uint64_t vaddr, uint64_t size, GpuAccess access = GpuAccess::ReadWrite);
|
||||
|
||||
[[nodiscard]] bool HandleFault(PageFaultAccess access, uint64_t fault_vaddr) noexcept;
|
||||
|
||||
private:
|
||||
void BeginBackingWrite(uint64_t vaddr, uint64_t size) noexcept;
|
||||
void EndBackingWrite(uint64_t vaddr, uint64_t size) noexcept;
|
||||
|
||||
struct Impl;
|
||||
std::unique_ptr<Impl> m_impl;
|
||||
};
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
#endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_PAGEMANAGER_H_
|
||||
@@ -0,0 +1,93 @@
|
||||
#ifndef EMULATOR_SRC_GRAPHICS_HOST_GPU_RANGESET_H_
|
||||
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RANGESET_H_
|
||||
|
||||
#include "common/assert.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
class RangeSet final {
|
||||
public:
|
||||
struct Range {
|
||||
uint64_t address = 0;
|
||||
uint64_t size = 0;
|
||||
};
|
||||
|
||||
void Add(uint64_t address, uint64_t size) {
|
||||
const auto end = End(address, size);
|
||||
auto it = m_ranges.lower_bound(address);
|
||||
if (it != m_ranges.begin() && std::prev(it)->second >= address) {
|
||||
it = std::prev(it);
|
||||
}
|
||||
uint64_t begin = address;
|
||||
uint64_t last = end;
|
||||
while (it != m_ranges.end() && it->first <= last) {
|
||||
begin = std::min(begin, it->first);
|
||||
last = std::max(last, it->second);
|
||||
it = m_ranges.erase(it);
|
||||
}
|
||||
m_ranges.emplace(begin, last);
|
||||
}
|
||||
|
||||
void Subtract(uint64_t address, uint64_t size) {
|
||||
const auto end = End(address, size);
|
||||
auto it = m_ranges.lower_bound(address);
|
||||
if (it != m_ranges.begin() && std::prev(it)->second > address) {
|
||||
it = std::prev(it);
|
||||
}
|
||||
while (it != m_ranges.end() && it->first < end) {
|
||||
const auto begin = it->first;
|
||||
const auto last = it->second;
|
||||
it = m_ranges.erase(it);
|
||||
if (begin < address) {
|
||||
m_ranges.emplace(begin, address);
|
||||
}
|
||||
if (last > end) {
|
||||
m_ranges.emplace(end, last);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] std::vector<Range> Intersections(uint64_t address, uint64_t size) const {
|
||||
std::vector<Range> result;
|
||||
ForEachIntersection(address, size, [&result](Range range) { result.push_back(range); });
|
||||
return result;
|
||||
}
|
||||
|
||||
template <typename Func>
|
||||
void ForEachIntersection(uint64_t address, uint64_t size, Func&& func) const {
|
||||
const auto end = End(address, size);
|
||||
auto it = m_ranges.upper_bound(address);
|
||||
if (it != m_ranges.begin()) {
|
||||
--it;
|
||||
}
|
||||
for (; it != m_ranges.end() && it->first < end; ++it) {
|
||||
const auto begin = std::max(address, it->first);
|
||||
const auto last = std::min(end, it->second);
|
||||
if (begin < last) {
|
||||
func(Range {begin, last - begin});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] bool Empty() const { return m_ranges.empty(); }
|
||||
|
||||
private:
|
||||
static uint64_t End(uint64_t address, uint64_t size) {
|
||||
if (address == 0 || size == 0 || size > UINT64_MAX - address) {
|
||||
EXIT("invalid range-set address or size\n");
|
||||
}
|
||||
return address + size;
|
||||
}
|
||||
|
||||
std::map<uint64_t, uint64_t> m_ranges;
|
||||
};
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
#endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_RANGESET_H_
|
||||
@@ -0,0 +1,20 @@
|
||||
#ifndef EMULATOR_SRC_GRAPHICS_HOST_GPU_REGIONDEFINITIONS_H_
|
||||
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_REGIONDEFINITIONS_H_
|
||||
|
||||
#include "common/common.h"
|
||||
|
||||
#include <bitset>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
constexpr uint64_t TRACKER_PAGE_SIZE = 4ull * 1024ull;
|
||||
constexpr uint64_t TRACKER_REGION_SIZE = 4ull * 1024ull * 1024ull;
|
||||
constexpr uint64_t TRACKER_ADDRESS_SIZE = 1ull << 40u;
|
||||
constexpr size_t TRACKER_REGION_PAGES = TRACKER_REGION_SIZE / TRACKER_PAGE_SIZE;
|
||||
|
||||
enum class DirtySource { Cpu, Gpu };
|
||||
using RegionBits = std::bitset<TRACKER_REGION_PAGES>;
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
#endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_REGIONDEFINITIONS_H_
|
||||
@@ -0,0 +1,349 @@
|
||||
#ifndef EMULATOR_SRC_GRAPHICS_HOST_GPU_REGIONMANAGER_H_
|
||||
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_REGIONMANAGER_H_
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "graphics/host_gpu/pageManager.h"
|
||||
#include "graphics/host_gpu/regionDefinitions.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <mutex>
|
||||
#include <utility>
|
||||
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
#include <windows.h>
|
||||
#undef min
|
||||
#undef max
|
||||
#endif
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
enum class CpuFaultAction { Untracked, Continue, Download };
|
||||
|
||||
class TrackingSpinLock final {
|
||||
public:
|
||||
void lock() noexcept {
|
||||
const auto thread = CurrentThread();
|
||||
if (m_owner.load(std::memory_order_relaxed) == thread) {
|
||||
EXIT("recursive region tracking lock\n");
|
||||
}
|
||||
while (m_lock.test_and_set(std::memory_order_acquire)) {
|
||||
if (m_owner.load(std::memory_order_relaxed) == thread) {
|
||||
EXIT("recursive region tracking lock while contended\n");
|
||||
}
|
||||
std::atomic_signal_fence(std::memory_order_seq_cst);
|
||||
}
|
||||
m_owner.store(thread, std::memory_order_relaxed);
|
||||
}
|
||||
void unlock() noexcept {
|
||||
if (m_owner.load(std::memory_order_relaxed) != CurrentThread()) {
|
||||
EXIT("region tracking lock released by non-owner\n");
|
||||
}
|
||||
m_owner.store(0, std::memory_order_relaxed);
|
||||
m_lock.clear(std::memory_order_release);
|
||||
}
|
||||
|
||||
private:
|
||||
static uint32_t CurrentThread() noexcept {
|
||||
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
|
||||
return GetCurrentThreadId();
|
||||
#else
|
||||
EXIT("region tracking thread identity is unsupported on this platform\n");
|
||||
#endif
|
||||
}
|
||||
|
||||
std::atomic_flag m_lock = ATOMIC_FLAG_INIT;
|
||||
std::atomic_uint32_t m_owner {0};
|
||||
};
|
||||
|
||||
static_assert(std::atomic_uint32_t::is_always_lock_free);
|
||||
|
||||
class RegionManager final {
|
||||
public:
|
||||
RegionManager(PageManager& page_manager, uint64_t cpu_addr)
|
||||
: m_page_manager(page_manager), m_cpu_addr(cpu_addr) {
|
||||
if (m_cpu_addr % TRACKER_REGION_SIZE != 0) {
|
||||
EXIT("invalid region tracking manager construction\n");
|
||||
}
|
||||
m_cpu_dirty.set();
|
||||
m_writable.set();
|
||||
}
|
||||
|
||||
KYTY_CLASS_NO_COPY(RegionManager);
|
||||
|
||||
[[nodiscard]] uint64_t GetCpuAddr() const { return m_cpu_addr; }
|
||||
void Track(uint64_t vaddr, uint64_t size) {
|
||||
const auto [start, end] = GetPageRange(vaddr, size);
|
||||
for (auto page = start; page < end; page++) {
|
||||
m_tracked.set(page);
|
||||
}
|
||||
}
|
||||
void Untrack(uint64_t vaddr, uint64_t size) {
|
||||
const auto [start, end] = GetPageRange(vaddr, size);
|
||||
for (auto page = start; page < end; page++) {
|
||||
m_tracked.reset(page);
|
||||
}
|
||||
}
|
||||
template <DirtySource source>
|
||||
[[nodiscard]] bool IsModified(uint64_t offset, uint64_t size) const {
|
||||
const auto [start, end] = GetPageRange(m_cpu_addr + offset, size);
|
||||
const auto& bits = GetBits<source>();
|
||||
for (auto page = start; page < end; page++) {
|
||||
if (bits.test(page)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
template <DirtySource source>
|
||||
[[nodiscard]] bool IsFullyModified(uint64_t offset, uint64_t size) const {
|
||||
const auto [start, end] = GetPageRange(m_cpu_addr + offset, size);
|
||||
const auto& bits = GetBits<source>();
|
||||
for (auto page = start; page < end; page++) {
|
||||
if (!bits.test(page)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
template <DirtySource source, bool enable>
|
||||
RegionBits ChangeState(uint64_t vaddr, uint64_t size) {
|
||||
const auto [start, end] = GetPageRange(vaddr, size);
|
||||
if constexpr (source == DirtySource::Cpu && enable) {
|
||||
for (auto page = start; page < end; page++) {
|
||||
if (m_gpu_dirty.test(page) || m_fault_pending.test(page)) {
|
||||
EXIT("CPU dirty state conflicts with GPU dirty or pending fault state\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
if constexpr (source == DirtySource::Gpu && enable) {
|
||||
for (auto page = start; page < end; page++) {
|
||||
if (m_cpu_dirty.test(page) || m_fault_pending.test(page)) {
|
||||
EXIT("GPU dirty state conflicts with CPU dirty or pending fault state\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
auto& bits = GetBits<source>();
|
||||
auto changed = bits;
|
||||
for (auto page = start; page < end; page++) {
|
||||
bits.set(page, enable);
|
||||
}
|
||||
changed ^= bits;
|
||||
if constexpr (source == DirtySource::Cpu) {
|
||||
changed = m_cpu_dirty ^ m_writable;
|
||||
m_writable = m_cpu_dirty;
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
[[nodiscard]] CpuFaultAction BeginCpuFault(uint64_t vaddr, uint64_t size,
|
||||
PageFaultAccess access = PageFaultAccess::Write) {
|
||||
if (access != PageFaultAccess::Read && access != PageFaultAccess::Write) {
|
||||
EXIT("unsupported CPU fault access while beginning ownership transfer\n");
|
||||
}
|
||||
const auto [start, end] = GetPageRange(vaddr, size);
|
||||
const bool tracked = m_tracked.test(start);
|
||||
for (auto page = start; page < end; page++) {
|
||||
if (m_tracked.test(page) != tracked) {
|
||||
EXIT("CPU fault spans mixed tracked and untracked pages\n");
|
||||
}
|
||||
if (m_fault_pending.test(page)) {
|
||||
return CpuFaultAction::Untracked;
|
||||
}
|
||||
if (m_cpu_dirty.test(page) != m_writable.test(page) ||
|
||||
(m_gpu_dirty.test(page) && (m_cpu_dirty.test(page) || m_writable.test(page)))) {
|
||||
EXIT("inconsistent CPU fault page state\n");
|
||||
}
|
||||
}
|
||||
if (!tracked) {
|
||||
return CpuFaultAction::Untracked;
|
||||
}
|
||||
bool gpu_dirty = m_gpu_dirty.test(start);
|
||||
bool writable = m_writable.test(start);
|
||||
for (auto page = start + 1; page < end; page++) {
|
||||
if (m_gpu_dirty.test(page) != gpu_dirty || m_writable.test(page) != writable) {
|
||||
EXIT("CPU fault spans pages with incompatible dirty or writable state\n");
|
||||
}
|
||||
}
|
||||
for (auto page = start; page < end; page++) {
|
||||
if (!gpu_dirty && access == PageFaultAccess::Write) {
|
||||
m_cpu_dirty.set(page);
|
||||
m_writable.set(page);
|
||||
}
|
||||
m_fault_pending.set(page);
|
||||
}
|
||||
return gpu_dirty ? CpuFaultAction::Download : CpuFaultAction::Continue;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool CompleteCpuFault(uint64_t vaddr, uint64_t size, PageFaultAccess access,
|
||||
bool downloaded) {
|
||||
const auto [start, end] = GetPageRange(vaddr, size);
|
||||
for (auto page = start; page < end; page++) {
|
||||
if (!m_fault_pending.test(page)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for (auto page = start; page < end; page++) {
|
||||
const bool gpu_dirty = m_gpu_dirty.test(page);
|
||||
if (gpu_dirty != downloaded) {
|
||||
EXIT("CPU fault download result disagrees with GPU dirty state\n");
|
||||
}
|
||||
if (gpu_dirty) {
|
||||
m_gpu_dirty.reset(page);
|
||||
switch (access) {
|
||||
case PageFaultAccess::Read: break;
|
||||
case PageFaultAccess::Write:
|
||||
m_cpu_dirty.set(page);
|
||||
m_writable.set(page);
|
||||
break;
|
||||
default: EXIT("unsupported CPU fault access after GPU download\n");
|
||||
}
|
||||
}
|
||||
m_fault_pending.reset(page);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool HasPendingFault(uint64_t vaddr, uint64_t size) const {
|
||||
const auto [start, end] = GetPageRange(vaddr, size);
|
||||
for (auto page = start; page < end; page++) {
|
||||
if (m_fault_pending.test(page)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool CompleteVirtualGpuWrite(uint64_t vaddr, uint64_t size) {
|
||||
const auto [start, end] = GetPageRange(vaddr, size);
|
||||
for (auto page = start; page < end; page++) {
|
||||
if (!m_fault_pending.test(page)) {
|
||||
return false;
|
||||
}
|
||||
if (!m_gpu_dirty.test(page)) {
|
||||
EXIT("virtual GPU write completion found a non-GPU-dirty page\n");
|
||||
}
|
||||
}
|
||||
for (auto page = start; page < end; page++) {
|
||||
m_gpu_dirty.reset(page);
|
||||
m_cpu_dirty.set(page);
|
||||
m_writable.set(page);
|
||||
m_fault_pending.reset(page);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
template <DirtySource source, bool clear, typename Func>
|
||||
RegionBits ForEachModifiedRange(uint64_t vaddr, uint64_t size, Func&& func) {
|
||||
const auto [start, end] = GetPageRange(vaddr, size);
|
||||
auto mask = GetBits<source>();
|
||||
if constexpr (source == DirtySource::Cpu) {
|
||||
mask &= ~m_fault_pending;
|
||||
}
|
||||
for (auto page = 0u; page < start; page++) {
|
||||
mask.reset(page);
|
||||
}
|
||||
for (auto page = end; page < TRACKER_REGION_PAGES; page++) {
|
||||
mask.reset(page);
|
||||
}
|
||||
if constexpr (clear) {
|
||||
auto& bits = GetBits<source>();
|
||||
for (auto page = start; page < end; page++) {
|
||||
if (mask.test(page)) {
|
||||
bits.reset(page);
|
||||
}
|
||||
}
|
||||
}
|
||||
if constexpr (source == DirtySource::Cpu && clear) {
|
||||
auto changed = m_cpu_dirty ^ m_writable;
|
||||
m_writable = m_cpu_dirty;
|
||||
ApplyProtection(changed, true);
|
||||
ForEachRange(mask, std::forward<Func>(func));
|
||||
return changed;
|
||||
}
|
||||
ForEachRange(mask, std::forward<Func>(func));
|
||||
if constexpr (clear) {
|
||||
return mask;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
void ApplyProtection(const RegionBits& changed, bool track) {
|
||||
ForEachRange(changed, [this, track](uint64_t vaddr, uint64_t size) {
|
||||
m_page_manager.UpdatePageWatchers(track, vaddr, size);
|
||||
});
|
||||
}
|
||||
|
||||
void ApplyGpuProtection(const RegionBits& changed, bool track, PageWatchMode mode) {
|
||||
if (mode != PageWatchMode::Write && mode != PageWatchMode::ReadWrite) {
|
||||
EXIT("unsupported GPU page-watch mode\n");
|
||||
}
|
||||
ForEachRange(changed, [this, track, mode](uint64_t vaddr, uint64_t size) {
|
||||
m_page_manager.UpdatePageWatchers(track, vaddr, size, mode);
|
||||
});
|
||||
}
|
||||
|
||||
TrackingSpinLock lock;
|
||||
|
||||
private:
|
||||
template <DirtySource source>
|
||||
RegionBits& GetBits() {
|
||||
if constexpr (source == DirtySource::Cpu) {
|
||||
return m_cpu_dirty;
|
||||
} else {
|
||||
return m_gpu_dirty;
|
||||
}
|
||||
}
|
||||
|
||||
template <DirtySource source>
|
||||
const RegionBits& GetBits() const {
|
||||
if constexpr (source == DirtySource::Cpu) {
|
||||
return m_cpu_dirty;
|
||||
} else {
|
||||
return m_gpu_dirty;
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] std::pair<size_t, size_t> GetPageRange(uint64_t vaddr, uint64_t size) const {
|
||||
if (size == 0 || vaddr < m_cpu_addr || vaddr >= m_cpu_addr + TRACKER_REGION_SIZE ||
|
||||
size > m_cpu_addr + TRACKER_REGION_SIZE - vaddr) {
|
||||
EXIT("range lies outside its tracking region\n");
|
||||
}
|
||||
const auto offset = vaddr - m_cpu_addr;
|
||||
return {static_cast<size_t>(offset / TRACKER_PAGE_SIZE),
|
||||
static_cast<size_t>((offset + size + TRACKER_PAGE_SIZE - 1) / TRACKER_PAGE_SIZE)};
|
||||
}
|
||||
|
||||
template <typename Func>
|
||||
void ForEachRange(const RegionBits& bits, Func&& func) const {
|
||||
size_t page = 0;
|
||||
while (page < TRACKER_REGION_PAGES) {
|
||||
while (page < TRACKER_REGION_PAGES && !bits.test(page)) {
|
||||
page++;
|
||||
}
|
||||
const auto start = page;
|
||||
while (page < TRACKER_REGION_PAGES && bits.test(page)) {
|
||||
page++;
|
||||
}
|
||||
if (start != page) {
|
||||
func(m_cpu_addr + start * TRACKER_PAGE_SIZE, (page - start) * TRACKER_PAGE_SIZE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PageManager& m_page_manager;
|
||||
uint64_t m_cpu_addr = 0;
|
||||
RegionBits m_cpu_dirty;
|
||||
RegionBits m_gpu_dirty;
|
||||
RegionBits m_writable;
|
||||
RegionBits m_fault_pending;
|
||||
RegionBits m_tracked;
|
||||
};
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
#endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_REGIONMANAGER_H_
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,106 @@
|
||||
#ifndef EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_BUFFERCACHE_H_
|
||||
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_BUFFERCACHE_H_
|
||||
|
||||
#include "common/abi.h"
|
||||
#include "common/common.h"
|
||||
#include "common/threads.h"
|
||||
#include "graphics/host_gpu/memoryTracker.h"
|
||||
#include "graphics/host_gpu/rangeSet.h"
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
struct GraphicContext;
|
||||
struct VulkanBuffer;
|
||||
class CommandBuffer;
|
||||
class TextureCache;
|
||||
class ResourceMutex;
|
||||
|
||||
struct BufferImageCopySource {
|
||||
VulkanBuffer* buffer = nullptr;
|
||||
uint64_t offset = 0;
|
||||
uint64_t address = 0;
|
||||
uint64_t size = 0;
|
||||
bool cpu_current = false;
|
||||
};
|
||||
|
||||
struct BufferCacheRange {
|
||||
uint64_t address = 0;
|
||||
uint64_t size = 0;
|
||||
};
|
||||
|
||||
[[nodiscard]] bool MergeOverlappingBufferCacheRange(BufferCacheRange* merged,
|
||||
BufferCacheRange candidate) noexcept;
|
||||
[[nodiscard]] bool CanMergeBufferCacheQueueMask(uint64_t queue_mask, uint32_t queue) noexcept;
|
||||
[[nodiscard]] bool CanReadbackBufferCacheQueueMask(uint64_t queue_mask, uint32_t queue) noexcept;
|
||||
|
||||
class BufferCache {
|
||||
public:
|
||||
static constexpr uint64_t CACHING_PAGE_SIZE = 16ull * 1024ull;
|
||||
static constexpr uint64_t GetBufferOffset(uint64_t vaddr) {
|
||||
return vaddr & (CACHING_PAGE_SIZE - 1);
|
||||
}
|
||||
|
||||
BufferCache(PageManager& page_manager, ResourceMutex& resource_mutex);
|
||||
~BufferCache();
|
||||
KYTY_CLASS_NO_COPY(BufferCache);
|
||||
|
||||
[[nodiscard]] bool InvalidateMemory(PageFaultAccess access, uint64_t vaddr, uint64_t size,
|
||||
PageFaultPhase phase) noexcept;
|
||||
void UnmapMemory(uint64_t vaddr, uint64_t size);
|
||||
[[nodiscard]] std::pair<VulkanBuffer*, uint64_t>
|
||||
ObtainBuffer(CommandBuffer* command, GraphicContext* ctx, uint64_t vaddr, uint64_t size,
|
||||
bool is_written = false, bool is_read = true, bool is_formatted = false);
|
||||
// Emulator-owned, CPU-current scratch only. Guest ranges must use ObtainBuffer so page
|
||||
// ownership is resolved before any CPU access.
|
||||
[[nodiscard]] bool UploadHostData(CommandBuffer* command, GraphicContext* ctx, const void* src,
|
||||
uint64_t size, uint64_t alignment,
|
||||
VulkanBuffer** out_buffer, uint64_t* out_offset,
|
||||
uint64_t* out_range);
|
||||
[[nodiscard]] VulkanBuffer* ObtainNullBuffer(CommandBuffer* command,
|
||||
GraphicContext* ctx);
|
||||
[[nodiscard]] BufferImageCopySource ObtainBufferForImage(uint64_t vaddr, uint64_t size);
|
||||
void FillBuffer(CommandBuffer* command, GraphicContext* ctx, uint64_t vaddr, uint64_t size,
|
||||
uint32_t value);
|
||||
void CopyBuffer(CommandBuffer* command, GraphicContext* ctx, uint64_t dst_vaddr,
|
||||
uint64_t src_vaddr, uint64_t size);
|
||||
[[nodiscard]] bool HasPageOverlap(uint64_t vaddr, uint64_t size);
|
||||
[[nodiscard]] bool IsRegionCpuModified(uint64_t vaddr, uint64_t size);
|
||||
[[nodiscard]] bool IsRegionGpuModified(uint64_t vaddr, uint64_t size);
|
||||
void PublishImageBacking(uint64_t vaddr, uint64_t size);
|
||||
void ValidateGpuAccess(uint64_t vaddr, uint64_t size, bool is_read, bool is_written) const;
|
||||
void SetTextureCache(TextureCache& texture_cache);
|
||||
|
||||
void RegisterForDelete(VulkanBuffer* buffer);
|
||||
void DeleteAll(GraphicContext* ctx);
|
||||
|
||||
private:
|
||||
struct CachedBuffer;
|
||||
struct ReadbackWorker;
|
||||
struct CommandProcessorReadbackResources;
|
||||
void RequestCommandProcessorReadback(PageFaultAccess access, uint64_t vaddr, uint64_t size);
|
||||
[[nodiscard]] bool CompleteCommandProcessorReadback(PageFaultAccess access, uint64_t vaddr,
|
||||
uint64_t size) noexcept;
|
||||
[[nodiscard]] bool ReleaseCommandProcessorReadback(PageFaultAccess access, uint64_t vaddr,
|
||||
uint64_t size) noexcept;
|
||||
|
||||
Common::Mutex m_mutex;
|
||||
std::vector<VulkanBuffer*> m_delete_later;
|
||||
std::shared_ptr<VulkanBuffer> m_null_buffer;
|
||||
std::map<uint64_t, std::unique_ptr<CachedBuffer>> m_buffers;
|
||||
std::unique_ptr<ReadbackWorker> m_readback;
|
||||
std::unique_ptr<CommandProcessorReadbackResources> m_cp_readback_resources;
|
||||
RangeSet m_gpu_modified_ranges;
|
||||
MemoryTracker m_memory_tracker;
|
||||
PageManager& m_page_manager;
|
||||
TextureCache* m_texture_cache = nullptr;
|
||||
ResourceMutex& m_resource_mutex;
|
||||
};
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
#endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_BUFFERCACHE_H_
|
||||
@@ -0,0 +1,515 @@
|
||||
#include "common/assert.h"
|
||||
#include "common/common.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/profiler.h"
|
||||
#include "common/stringUtils.h"
|
||||
#include "common/threads.h"
|
||||
#include "graphics/guest_gpu/gpu_defs.h"
|
||||
#include "graphics/guest_gpu/hardwareContext.h"
|
||||
#include "graphics/guest_gpu/tile.h"
|
||||
#include "graphics/host_gpu/graphicContext.h"
|
||||
#include "graphics/host_gpu/objects/textureCommon.h"
|
||||
#include "graphics/host_gpu/renderer/descriptorCache.h"
|
||||
#include "graphics/host_gpu/renderer/framebufferCache.h"
|
||||
#include "graphics/host_gpu/renderer/render.h"
|
||||
#include "graphics/host_gpu/renderer/renderContext.h"
|
||||
#include "graphics/host_gpu/renderer/renderState.h"
|
||||
#include "graphics/host_gpu/utils.h"
|
||||
#include "graphics/presentation/displayBuffer.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <cmath>
|
||||
#include <cstdarg>
|
||||
#include <cstdio>
|
||||
#include <limits>
|
||||
#include <vulkan/vk_enum_string_helper.h>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
static std::atomic<uint32_t> g_render_color_log_count = 0;
|
||||
|
||||
static float ColorClearUnorm(uint32_t value, uint32_t bits) {
|
||||
return static_cast<float>(value) / static_cast<float>((1u << bits) - 1u);
|
||||
}
|
||||
|
||||
static float ColorClearSnorm(uint32_t value, uint32_t bits) {
|
||||
const uint32_t mask = (1u << bits) - 1u;
|
||||
const uint32_t sign_bit = 1u << (bits - 1u);
|
||||
value &= mask;
|
||||
|
||||
auto signed_value = static_cast<int32_t>(value);
|
||||
if ((value & sign_bit) != 0) {
|
||||
signed_value = static_cast<int32_t>(value | ~mask);
|
||||
}
|
||||
|
||||
const auto denominator = static_cast<float>((1u << (bits - 1u)) - 1u);
|
||||
return std::max(-1.0f, static_cast<float>(signed_value) / denominator);
|
||||
}
|
||||
|
||||
static float ColorClearF16(uint32_t value) {
|
||||
const uint32_t sign = (value >> 15u) & 0x1u;
|
||||
const uint32_t exponent = (value >> 10u) & 0x1fu;
|
||||
const uint32_t mantissa = value & 0x3ffu;
|
||||
const float sign_mul = (sign != 0 ? -1.0f : 1.0f);
|
||||
|
||||
if (exponent == 0) {
|
||||
if (mantissa == 0) {
|
||||
return sign_mul * 0.0f;
|
||||
}
|
||||
return sign_mul * std::ldexp(static_cast<float>(mantissa), -24);
|
||||
}
|
||||
|
||||
if (exponent == 0x1fu) {
|
||||
return mantissa == 0 ? sign_mul * std::numeric_limits<float>::infinity()
|
||||
: std::numeric_limits<float>::quiet_NaN();
|
||||
}
|
||||
|
||||
return sign_mul *
|
||||
std::ldexp(static_cast<float>(0x400u | mantissa), static_cast<int>(exponent) - 25);
|
||||
}
|
||||
|
||||
[[maybe_unused]] static VkClearColorValue ColorClearValue(const HW::RenderTarget& rt) {
|
||||
const uint32_t c0 = rt.clear_word0.word0;
|
||||
const uint32_t c1 = rt.clear_word1.word1;
|
||||
|
||||
VkClearColorValue color {};
|
||||
|
||||
switch (static_cast<Prospero::ChannelType>(rt.info.channel_type)) {
|
||||
case Prospero::ChannelType::kUNorm:
|
||||
case Prospero::ChannelType::kSrgb:
|
||||
// SRGB clear words are still encoded as normalized component values.
|
||||
switch (static_cast<Prospero::ChannelLayout>(rt.info.format)) {
|
||||
case Prospero::ChannelLayout::k8:
|
||||
color.float32[0] = ColorClearUnorm(c0 & 0xffu, 8);
|
||||
color.float32[1] = 0.0f;
|
||||
color.float32[2] = 0.0f;
|
||||
color.float32[3] = 1.0f;
|
||||
break;
|
||||
case Prospero::ChannelLayout::k8_8:
|
||||
color.float32[0] = ColorClearUnorm(c0 & 0xffu, 8);
|
||||
color.float32[1] = ColorClearUnorm((c0 >> 8u) & 0xffu, 8);
|
||||
color.float32[2] = 0.0f;
|
||||
color.float32[3] = 1.0f;
|
||||
break;
|
||||
case Prospero::ChannelLayout::k8_8_8_8:
|
||||
color.float32[0] = ColorClearUnorm(c0 & 0xffu, 8);
|
||||
color.float32[1] = ColorClearUnorm((c0 >> 8u) & 0xffu, 8);
|
||||
color.float32[2] = ColorClearUnorm((c0 >> 16u) & 0xffu, 8);
|
||||
color.float32[3] = ColorClearUnorm((c0 >> 24u) & 0xffu, 8);
|
||||
if (rt.info.channel_order ==
|
||||
Prospero::GpuEnumValue(Prospero::ChannelOrder::kAlt)) {
|
||||
std::swap(color.float32[0], color.float32[2]);
|
||||
}
|
||||
break;
|
||||
case Prospero::ChannelLayout::k11_11_10:
|
||||
color.float32[0] = 0.0f;
|
||||
color.float32[1] = 0.0f;
|
||||
color.float32[2] = 0.0f;
|
||||
color.float32[3] = 1.0f;
|
||||
break;
|
||||
case Prospero::ChannelLayout::k10_10_10_2:
|
||||
color.float32[0] = ColorClearUnorm(c0 & 0x3ffu, 10);
|
||||
color.float32[1] = ColorClearUnorm((c0 >> 10u) & 0x3ffu, 10);
|
||||
color.float32[2] = ColorClearUnorm((c0 >> 20u) & 0x3ffu, 10);
|
||||
color.float32[3] = ColorClearUnorm((c0 >> 30u) & 0x3u, 2);
|
||||
if (rt.info.channel_order ==
|
||||
Prospero::GpuEnumValue(Prospero::ChannelOrder::kAlt)) {
|
||||
std::swap(color.float32[0], color.float32[2]);
|
||||
}
|
||||
break;
|
||||
case Prospero::ChannelLayout::k16_16:
|
||||
color.float32[0] = 0.0f;
|
||||
color.float32[1] = 0.0f;
|
||||
color.float32[2] = 0.0f;
|
||||
color.float32[3] = 1.0f;
|
||||
break;
|
||||
case Prospero::ChannelLayout::k32_32_32_32:
|
||||
color.float32[0] = 0.0f;
|
||||
color.float32[1] = 0.0f;
|
||||
color.float32[2] = 0.0f;
|
||||
color.float32[3] = 1.0f;
|
||||
break;
|
||||
default:
|
||||
EXIT("%s\n unsupported color clear format\n",
|
||||
Common::Concat(rt_print("RenderTarget", rt), "").c_str());
|
||||
}
|
||||
break;
|
||||
case Prospero::ChannelType::kSNorm:
|
||||
switch (static_cast<Prospero::ChannelLayout>(rt.info.format)) {
|
||||
case Prospero::ChannelLayout::k16_16:
|
||||
color.float32[0] = ColorClearSnorm(c0 & 0xffffu, 16);
|
||||
color.float32[1] = ColorClearSnorm((c0 >> 16u) & 0xffffu, 16);
|
||||
color.float32[2] = 0.0f;
|
||||
color.float32[3] = 1.0f;
|
||||
break;
|
||||
case Prospero::ChannelLayout::k8_8_8_8:
|
||||
color.float32[0] = ColorClearSnorm(c0 & 0xffu, 8);
|
||||
color.float32[1] = ColorClearSnorm((c0 >> 8u) & 0xffu, 8);
|
||||
color.float32[2] = ColorClearSnorm((c0 >> 16u) & 0xffu, 8);
|
||||
color.float32[3] = ColorClearSnorm((c0 >> 24u) & 0xffu, 8);
|
||||
if (rt.info.channel_order ==
|
||||
Prospero::GpuEnumValue(Prospero::ChannelOrder::kAlt)) {
|
||||
std::swap(color.float32[0], color.float32[2]);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
EXIT("%s\n unsupported snorm color clear format\n",
|
||||
Common::Concat(rt_print("RenderTarget", rt), "").c_str());
|
||||
}
|
||||
break;
|
||||
case Prospero::ChannelType::kFloat:
|
||||
switch (static_cast<Prospero::ChannelLayout>(rt.info.format)) {
|
||||
case Prospero::ChannelLayout::k16:
|
||||
color.float32[0] = ColorClearF16(c0 & 0xffffu);
|
||||
color.float32[1] = 0.0f;
|
||||
color.float32[2] = 0.0f;
|
||||
color.float32[3] = 1.0f;
|
||||
break;
|
||||
case Prospero::ChannelLayout::k16_16:
|
||||
color.float32[0] = ColorClearF16(c0 & 0xffffu);
|
||||
color.float32[1] = ColorClearF16((c0 >> 16u) & 0xffffu);
|
||||
color.float32[2] = 0.0f;
|
||||
color.float32[3] = 1.0f;
|
||||
break;
|
||||
case Prospero::ChannelLayout::k16_16_16_16:
|
||||
color.float32[0] = ColorClearF16(c0 & 0xffffu);
|
||||
color.float32[1] = ColorClearF16((c0 >> 16u) & 0xffffu);
|
||||
color.float32[2] = ColorClearF16(c1 & 0xffffu);
|
||||
color.float32[3] = ColorClearF16((c1 >> 16u) & 0xffffu);
|
||||
break;
|
||||
case Prospero::ChannelLayout::k32:
|
||||
case Prospero::ChannelLayout::k11_11_10:
|
||||
case Prospero::ChannelLayout::k32_32_32_32:
|
||||
color.float32[0] = 0.0f;
|
||||
color.float32[1] = 0.0f;
|
||||
color.float32[2] = 0.0f;
|
||||
color.float32[3] = 1.0f;
|
||||
break;
|
||||
default:
|
||||
EXIT("%s\n unsupported float color clear format\n",
|
||||
Common::Concat(rt_print("RenderTarget", rt), "").c_str());
|
||||
}
|
||||
break;
|
||||
case Prospero::ChannelType::kUInt:
|
||||
case Prospero::ChannelType::kSInt:
|
||||
switch (static_cast<Prospero::ChannelLayout>(rt.info.format)) {
|
||||
case Prospero::ChannelLayout::k8:
|
||||
color.uint32[0] = c0;
|
||||
color.uint32[1] = 0;
|
||||
color.uint32[2] = 0;
|
||||
color.uint32[3] = 1;
|
||||
break;
|
||||
case Prospero::ChannelLayout::k16:
|
||||
color.uint32[0] = c0 & 0xffffu;
|
||||
color.uint32[1] = 0;
|
||||
color.uint32[2] = 0;
|
||||
color.uint32[3] = 1;
|
||||
break;
|
||||
case Prospero::ChannelLayout::k16_16:
|
||||
color.uint32[0] = c0 & 0xffffu;
|
||||
color.uint32[1] = (c0 >> 16u) & 0xffffu;
|
||||
color.uint32[2] = 0;
|
||||
color.uint32[3] = 1;
|
||||
break;
|
||||
case Prospero::ChannelLayout::k8_8_8_8:
|
||||
color.uint32[0] = c0 & 0xffu;
|
||||
color.uint32[1] = (c0 >> 8u) & 0xffu;
|
||||
color.uint32[2] = (c0 >> 16u) & 0xffu;
|
||||
color.uint32[3] = c0 >> 24u;
|
||||
if (rt.info.channel_order ==
|
||||
Prospero::GpuEnumValue(Prospero::ChannelOrder::kAlt)) {
|
||||
std::swap(color.uint32[0], color.uint32[2]);
|
||||
}
|
||||
break;
|
||||
case Prospero::ChannelLayout::k32_32:
|
||||
color.uint32[0] = c0;
|
||||
color.uint32[1] = c1;
|
||||
color.uint32[2] = 0;
|
||||
color.uint32[3] = 1;
|
||||
break;
|
||||
default:
|
||||
EXIT("%s\n unsupported integer color clear format\n",
|
||||
Common::Concat(rt_print("RenderTarget", rt), "").c_str());
|
||||
}
|
||||
break;
|
||||
default:
|
||||
EXIT("%s\n unsupported color clear number type\n",
|
||||
Common::Concat(rt_print("RenderTarget", rt), "").c_str());
|
||||
}
|
||||
|
||||
EXIT_NOT_IMPLEMENTED(
|
||||
c1 != 0 &&
|
||||
!(rt.info.channel_type == Prospero::GpuEnumValue(Prospero::ChannelType::kFloat) &&
|
||||
rt.info.format == Prospero::GpuEnumValue(Prospero::ChannelLayout::k16_16_16_16)) &&
|
||||
!((rt.info.channel_type == Prospero::GpuEnumValue(Prospero::ChannelType::kUInt) ||
|
||||
rt.info.channel_type == Prospero::GpuEnumValue(Prospero::ChannelType::kSInt)) &&
|
||||
rt.info.format == Prospero::GpuEnumValue(Prospero::ChannelLayout::k32_32)));
|
||||
|
||||
return color;
|
||||
}
|
||||
|
||||
static uint32_t RenderGetColorBpp(uint32_t dfmt) {
|
||||
switch (static_cast<Prospero::ChannelLayout>(dfmt)) {
|
||||
case Prospero::ChannelLayout::k8: return 8;
|
||||
case Prospero::ChannelLayout::k16:
|
||||
case Prospero::ChannelLayout::k8_8: return 16;
|
||||
case Prospero::ChannelLayout::k32:
|
||||
case Prospero::ChannelLayout::k16_16:
|
||||
case Prospero::ChannelLayout::k11_11_10:
|
||||
case Prospero::ChannelLayout::k10_10_10_2:
|
||||
case Prospero::ChannelLayout::k8_8_8_8: return 32;
|
||||
case Prospero::ChannelLayout::k32_32:
|
||||
case Prospero::ChannelLayout::k16_16_16_16: return 64;
|
||||
case Prospero::ChannelLayout::k32_32_32_32: return 128;
|
||||
case Prospero::ChannelLayout::k5_5_5_1:
|
||||
case Prospero::ChannelLayout::k4_4_4_4: return 16;
|
||||
default: EXIT("unsupported render-target channel layout: %u\n", dfmt);
|
||||
}
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(readability-function-cognitive-complexity)
|
||||
void ResolveRenderColorTarget(uint64_t submit_id, CommandBuffer* buffer, const HW::Context& hw,
|
||||
RenderColorInfo* r, uint32_t render_target_slice_offset,
|
||||
uint32_t render_target_slot, bool ignore_target_mask,
|
||||
bool reuse_existing_render_texture) {
|
||||
KYTY_PROFILER_FUNCTION();
|
||||
|
||||
EXIT_IF(r == nullptr);
|
||||
|
||||
const auto rt_slot = (render_target_slot == UINT32_MAX ? render_target_first_bound_slot(hw)
|
||||
: render_target_slot);
|
||||
const auto& rt = hw.GetRenderTarget(rt_slot);
|
||||
auto mask = render_target_mask_slot(hw.GetRenderTargetMask(), rt_slot);
|
||||
if (ignore_target_mask && rt.base.addr != 0 && mask == 0) {
|
||||
mask = 0x0f;
|
||||
}
|
||||
|
||||
r->target_slot = rt_slot;
|
||||
|
||||
if (rt.base.addr == 0 || mask == 0) {
|
||||
if (graphics_debug_dump_enabled()) {
|
||||
static std::atomic_uint log_count = 0;
|
||||
const auto log_id = log_count.fetch_add(1, std::memory_order_relaxed);
|
||||
if (log_id < 128) {
|
||||
LOGF("RenderColorTarget: no color output slot=%" PRIu32 " base=0x%010" PRIx64
|
||||
" slot_mask=0x%01" PRIx32 " target_mask=0x%08" PRIx32
|
||||
" rt_slice_offset=%" PRIu32 "\n",
|
||||
rt_slot, rt.base.addr, mask, hw.GetRenderTargetMask(),
|
||||
render_target_slice_offset);
|
||||
}
|
||||
}
|
||||
|
||||
// No color output
|
||||
r->type = RenderColorType::NoColorOutput;
|
||||
r->base_addr = 0;
|
||||
r->vulkan_buffer = nullptr;
|
||||
r->vulkan_view = nullptr;
|
||||
r->format = VK_FORMAT_UNDEFINED;
|
||||
r->extent = {};
|
||||
r->base_mip_level = 0;
|
||||
r->buffer_size = 0;
|
||||
r->color_clear_enable = false;
|
||||
r->color_clear_value = {};
|
||||
return;
|
||||
}
|
||||
const bool msaa_compat =
|
||||
color_msaa_single_sample_compatible(rt.attrib.num_samples, rt.attrib.num_fragments);
|
||||
if ((!msaa_compat && (rt.attrib.num_samples != 0 || rt.attrib.num_fragments != 0)) ||
|
||||
rt.view.base_array_slice_index != 0 || render_target_slice_offset != 0) {
|
||||
EXIT("multisampled or sliced render targets are unsupported\n");
|
||||
}
|
||||
const uint32_t levels = rt.attrib2.num_mip_levels + 1u;
|
||||
if (levels == 0 || levels > 16 || rt.view.current_mip_level >= levels) {
|
||||
EXIT("unsupported render-target mip range: current=%u levels=%u\n",
|
||||
rt.view.current_mip_level, levels);
|
||||
}
|
||||
if (msaa_compat) {
|
||||
static std::atomic<uint32_t> logged_fragments = 0;
|
||||
const uint32_t bit = 1u << rt.attrib.num_fragments;
|
||||
if ((logged_fragments.fetch_or(bit, std::memory_order_relaxed) & bit) == 0) {
|
||||
LOGF("RenderColorTarget: compatibility: rendering PS5 %ux samples/fragments as "
|
||||
"single-sample\n",
|
||||
bit);
|
||||
}
|
||||
}
|
||||
|
||||
if (graphics_debug_dump_enabled()) {
|
||||
static std::atomic_uint log_count = 0;
|
||||
const auto log_id = log_count.fetch_add(1, std::memory_order_relaxed);
|
||||
if (log_id < 128) {
|
||||
LOGF("RenderColorTarget: inspect slot=%" PRIu32 " base=0x%010" PRIx64
|
||||
" mask=0x%01" PRIx32 " attrib2_width=%" PRIu32 " attrib2_height=%" PRIu32
|
||||
" attrib3_tile=0x%08" PRIx32 " attrib3_dim=0x%08" PRIx32 " fmt=0x%08" PRIx32
|
||||
" nfmt=0x%08" PRIx32 " order=0x%08" PRIx32 "\n",
|
||||
rt_slot, rt.base.addr, mask, rt.attrib2.width, rt.attrib2.height,
|
||||
rt.attrib3.tile_mode, rt.attrib3.dimension, rt.info.format, rt.info.channel_type,
|
||||
rt.info.channel_order);
|
||||
}
|
||||
}
|
||||
|
||||
// CB_COLOR_CONTROL describes the color-buffer operation / ROP3 logic op.
|
||||
// ROP3 Copy is the normal color write path, not a render-target clear.
|
||||
// Fast color clears are metadata driven and must be handled explicitly when
|
||||
// that metadata path is implemented; render-pass load must preserve contents.
|
||||
r->color_clear_enable = false;
|
||||
r->color_clear_value = {};
|
||||
|
||||
uint32_t width = 0;
|
||||
uint32_t height = 0;
|
||||
uint32_t pitch = 0;
|
||||
uint64_t size = 0;
|
||||
bool tile = false;
|
||||
|
||||
switch (rt.attrib3.tile_mode) {
|
||||
case Prospero::GpuEnumValue(Prospero::TileMode::kLinear):
|
||||
case Prospero::GpuEnumValue(Prospero::TileMode::kRenderTarget):
|
||||
tile = !RenderIsColorTileModeLinear(rt.attrib3.tile_mode);
|
||||
break;
|
||||
default: EXIT("unknown tile mode: %u\n", rt.attrib3.tile_mode);
|
||||
}
|
||||
if (!tile && levels > 1) {
|
||||
EXIT("linear mipmapped render targets are unsupported\n");
|
||||
}
|
||||
|
||||
width = rt.attrib2.width + 1;
|
||||
height = rt.attrib2.height + 1;
|
||||
const auto bytes_per_element = RenderGetColorBpp(rt.info.format) / 8u;
|
||||
if (bytes_per_element == 0) {
|
||||
EXIT("render-target format has no valid element size\n");
|
||||
}
|
||||
if (rt.pitch.pitch_div8_minus1 != 0) {
|
||||
pitch = (rt.pitch.pitch_div8_minus1 + 1u) << 3u;
|
||||
} else if (tile) {
|
||||
pitch = TileGetRenderTargetPitch(width, bytes_per_element);
|
||||
if (pitch == 0) {
|
||||
EXIT("unsupported render-target pitch: width=%u bytes=%u\n", width, bytes_per_element);
|
||||
}
|
||||
} else {
|
||||
pitch = width;
|
||||
}
|
||||
|
||||
if (tile) {
|
||||
TileSizeAlign layout {};
|
||||
const bool valid_layout =
|
||||
levels == 1 ? TileGetRenderTargetSize(width, height, pitch, bytes_per_element, &layout)
|
||||
: TileGetRenderTargetMipLayout(width, height, pitch, bytes_per_element,
|
||||
levels, &layout, nullptr, nullptr);
|
||||
if (!valid_layout) {
|
||||
EXIT("unsupported render-target layout: %ux%u pitch=%u bytes=%u levels=%u\n", width,
|
||||
height, pitch, bytes_per_element, levels);
|
||||
}
|
||||
size = layout.size;
|
||||
if (rt.slice.slice_div64_minus1 != 0 &&
|
||||
(static_cast<uint64_t>(rt.slice.slice_div64_minus1) + 1u) * 64u != size) {
|
||||
EXIT("render-target slice span mismatch: encoded=0x%016" PRIx64 " derived=0x%016" PRIx64
|
||||
"\n",
|
||||
(static_cast<uint64_t>(rt.slice.slice_div64_minus1) + 1u) * 64u, size);
|
||||
}
|
||||
} else {
|
||||
size = static_cast<uint64_t>(pitch) * height * bytes_per_element;
|
||||
}
|
||||
if (size == 0 || size > TRACKER_ADDRESS_SIZE - rt.base.addr) {
|
||||
EXIT("render-target memory footprint is invalid\n");
|
||||
}
|
||||
|
||||
const auto effective_slice = rt.view.base_array_slice_index + render_target_slice_offset;
|
||||
const auto effective_base = rt.base.addr + static_cast<uint64_t>(size) * effective_slice;
|
||||
|
||||
auto video_image = Presentation::DisplayBufferFind(effective_base, true);
|
||||
bool render_to_texture = (video_image.image == nullptr);
|
||||
if (!render_to_texture && (levels != 1 || rt.view.current_mip_level != 0)) {
|
||||
EXIT("mipmapped display render targets are unsupported\n");
|
||||
}
|
||||
const VkExtent2D view_extent = {std::max(width >> rt.view.current_mip_level, 1u),
|
||||
std::max(height >> rt.view.current_mip_level, 1u)};
|
||||
|
||||
auto decision_log_id = g_render_color_log_count.fetch_add(1);
|
||||
if (decision_log_id < 128 || !render_to_texture) {
|
||||
LOGF("RenderColorTarget: slot=%" PRIu32 " addr=0x%010" PRIx64 " size=0x%016" PRIx64
|
||||
" extent=%ux%u view_mip=%u view_extent=%ux%u levels=%u pitch=%u"
|
||||
" fmt=0x%08" PRIx32 " nfmt=0x%08" PRIx32 " order=0x%08" PRIx32
|
||||
" tile=%s target=%s video_size=0x%016" PRIx64 " video_pitch=%" PRIu64 "\n",
|
||||
rt_slot, effective_base, size, width, height, rt.view.current_mip_level,
|
||||
view_extent.width, view_extent.height, levels, pitch, rt.info.format,
|
||||
rt.info.channel_type, rt.info.channel_order, tile ? "tiled" : "linear",
|
||||
render_to_texture ? "RenderTexture" : "DisplayBuffer", video_image.size,
|
||||
video_image.pitch);
|
||||
}
|
||||
|
||||
if (render_to_texture) {
|
||||
const auto rt_format = TextureGetRenderTargetFormat(rt.info.format, rt.info.channel_type,
|
||||
rt.info.channel_order);
|
||||
|
||||
(void)reuse_existing_render_texture;
|
||||
RenderTargetInfo target {};
|
||||
target.address = effective_base;
|
||||
target.size = size;
|
||||
target.format = rt_format.format;
|
||||
target.width = width;
|
||||
target.height = height;
|
||||
target.pitch = pitch;
|
||||
target.bytes_per_element = rt_format.bytes_per_element;
|
||||
target.tile_mode = rt.attrib3.tile_mode;
|
||||
target.levels = levels;
|
||||
auto* texture_cache = g_render_ctx->GetTextureCache();
|
||||
auto* buffer_vulkan =
|
||||
texture_cache->FindRenderTarget(buffer, g_render_ctx->GetGraphicCtx(), target);
|
||||
r->type = RenderColorType::RenderTexture;
|
||||
r->base_addr = effective_base;
|
||||
r->vulkan_buffer = buffer_vulkan;
|
||||
r->vulkan_view = texture_cache->GetRenderTargetAttachmentView(
|
||||
g_render_ctx->GetGraphicCtx(), buffer_vulkan, target.format, rt.view.current_mip_level);
|
||||
r->format = target.format;
|
||||
r->extent = view_extent;
|
||||
r->base_mip_level = rt.view.current_mip_level;
|
||||
r->buffer_size = size;
|
||||
} else {
|
||||
const auto layout = static_cast<Prospero::ChannelLayout>(rt.info.format);
|
||||
const auto type = static_cast<Prospero::ChannelType>(rt.info.channel_type);
|
||||
const auto order = static_cast<Prospero::ChannelOrder>(rt.info.channel_order);
|
||||
|
||||
bool supported_display_format =
|
||||
(layout == Prospero::ChannelLayout::k8_8_8_8 &&
|
||||
(type == Prospero::ChannelType::kSrgb || type == Prospero::ChannelType::kUNorm) &&
|
||||
(order == Prospero::ChannelOrder::kStandard ||
|
||||
order == Prospero::ChannelOrder::kAlt)) ||
|
||||
(layout == Prospero::ChannelLayout::k10_10_10_2 &&
|
||||
type == Prospero::ChannelType::kUNorm &&
|
||||
(order == Prospero::ChannelOrder::kStandard || order == Prospero::ChannelOrder::kAlt));
|
||||
|
||||
EXIT_NOT_IMPLEMENTED(!supported_display_format);
|
||||
|
||||
// Display buffer
|
||||
if (video_image.size != size) {
|
||||
LOGF("RenderColorTarget: display buffer size differs from render target span, "
|
||||
"video_size=0x%016" PRIx64 " render_size=0x%016" PRIx64 "\n",
|
||||
video_image.size, size);
|
||||
}
|
||||
EXIT_NOT_IMPLEMENTED(video_image.size < size);
|
||||
EXIT_NOT_IMPLEMENTED(video_image.pitch != pitch);
|
||||
r->type = RenderColorType::DisplayBuffer;
|
||||
r->base_addr = effective_base;
|
||||
r->vulkan_buffer = video_image.image;
|
||||
r->vulkan_view = video_image.image->image_view[VulkanImage::VIEW_DEFAULT];
|
||||
r->format = video_image.image->format;
|
||||
r->extent = video_image.image->extent;
|
||||
r->base_mip_level = 0;
|
||||
r->buffer_size = video_image.size;
|
||||
}
|
||||
}
|
||||
|
||||
void MarkRenderTargetGpuWritten(const RenderColorInfo& target) {
|
||||
const bool with_color = target.vulkan_buffer != nullptr;
|
||||
|
||||
if (with_color) {
|
||||
if (target.type == RenderColorType::RenderTexture ||
|
||||
target.type == RenderColorType::DisplayBuffer) {
|
||||
g_render_ctx->GetTextureCache()->MarkGpuWritten(target.vulkan_buffer);
|
||||
} else {
|
||||
EXIT("unknown writable render-color resource type\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
@@ -0,0 +1,674 @@
|
||||
#include "common/assert.h"
|
||||
#include "common/common.h"
|
||||
#include "common/emulatorConfig.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/profiler.h"
|
||||
#include "common/threads.h"
|
||||
#include "graphics/host_gpu/graphicContext.h"
|
||||
#include "graphics/host_gpu/renderer/descriptorCache.h"
|
||||
#include "graphics/host_gpu/renderer/framebufferCache.h"
|
||||
#include "graphics/host_gpu/renderer/render.h"
|
||||
#include "graphics/host_gpu/renderer/renderContext.h"
|
||||
#include "graphics/host_gpu/renderer/renderState.h"
|
||||
#include "graphics/host_gpu/utils.h"
|
||||
#include "graphics/host_gpu/vma.h"
|
||||
#include "graphics/presentation/window.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <vulkan/vk_enum_string_helper.h>
|
||||
namespace Libs::Graphics {
|
||||
static std::atomic<uint64_t> g_command_buffer_submit_seq = 0;
|
||||
class CommandPool {
|
||||
public:
|
||||
CommandPool() = default;
|
||||
~CommandPool() // NOLINT
|
||||
{
|
||||
// TODO(): check if destructor is called from std::_Exit()
|
||||
// DeleteAll();
|
||||
}
|
||||
|
||||
KYTY_CLASS_NO_COPY(CommandPool);
|
||||
|
||||
VulkanCommandPool* GetPool(int id) {
|
||||
if (m_pool[id] == nullptr) {
|
||||
Create(id);
|
||||
}
|
||||
return m_pool[id];
|
||||
}
|
||||
|
||||
private:
|
||||
void Create(int id);
|
||||
void DeleteAll();
|
||||
|
||||
VulkanCommandPool* m_pool[GraphicContext::QUEUES_NUM] = {};
|
||||
};
|
||||
|
||||
RenderContext* g_render_ctx = nullptr;
|
||||
static thread_local CommandPool g_command_pool;
|
||||
|
||||
FenceResourceRetainer::~FenceResourceRetainer() {
|
||||
if (!m_resources.empty()) {
|
||||
EXIT("fence resource retainer destroyed before release\n");
|
||||
}
|
||||
}
|
||||
|
||||
void FenceResourceRetainer::Retain(std::shared_ptr<void> resource) {
|
||||
if (resource == nullptr) {
|
||||
EXIT("cannot retain a null fence resource\n");
|
||||
}
|
||||
if (std::ranges::none_of(m_resources, [&resource](const auto& retained) {
|
||||
return retained.get() == resource.get();
|
||||
})) {
|
||||
m_resources.push_back(std::move(resource));
|
||||
}
|
||||
}
|
||||
|
||||
void FenceResourceRetainer::ReleaseAfterFence() noexcept {
|
||||
m_resources.clear();
|
||||
}
|
||||
|
||||
void GraphicsRenderInit() {
|
||||
EXIT_IF(g_render_ctx != nullptr);
|
||||
|
||||
g_render_ctx = new RenderContext;
|
||||
}
|
||||
|
||||
void GraphicsRenderCreateContext() {
|
||||
EXIT_IF(g_render_ctx == nullptr);
|
||||
|
||||
g_render_ctx->SetGraphicCtx(WindowGetGraphicContext());
|
||||
}
|
||||
|
||||
void CommandPool::Create(int id) {
|
||||
auto* ctx = g_render_ctx->GetGraphicCtx();
|
||||
|
||||
EXIT_IF(id < 0 || id >= GraphicContext::QUEUES_NUM);
|
||||
auto*& pool = m_pool[id];
|
||||
EXIT_IF(pool != nullptr);
|
||||
|
||||
EXIT_IF(ctx == nullptr);
|
||||
EXIT_IF(ctx->device == nullptr);
|
||||
EXIT_IF(ctx->queues[id].family == static_cast<uint32_t>(-1));
|
||||
|
||||
pool = new VulkanCommandPool;
|
||||
|
||||
VkCommandPoolCreateInfo pool_info {};
|
||||
pool_info.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
|
||||
pool_info.pNext = nullptr;
|
||||
pool_info.queueFamilyIndex = ctx->queues[id].family;
|
||||
pool_info.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
|
||||
|
||||
vkCreateCommandPool(ctx->device, &pool_info, nullptr, &pool->pool);
|
||||
|
||||
EXIT_NOT_IMPLEMENTED(pool->pool == nullptr);
|
||||
|
||||
pool->buffers_count = 8;
|
||||
pool->buffers = std::make_unique<VkCommandBuffer[]>(pool->buffers_count);
|
||||
pool->fences = std::make_unique<VkFence[]>(pool->buffers_count);
|
||||
pool->semaphores = std::make_unique<VkSemaphore[]>(pool->buffers_count);
|
||||
pool->busy = std::make_unique<bool[]>(pool->buffers_count);
|
||||
|
||||
VkCommandBufferAllocateInfo alloc_info {};
|
||||
alloc_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
|
||||
alloc_info.commandPool = pool->pool;
|
||||
alloc_info.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
|
||||
alloc_info.commandBufferCount = pool->buffers_count;
|
||||
|
||||
if (vkAllocateCommandBuffers(ctx->device, &alloc_info, pool->buffers.get()) != VK_SUCCESS) {
|
||||
EXIT("Can't allocate command buffers");
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < pool->buffers_count; i++) {
|
||||
pool->busy[i] = false;
|
||||
|
||||
VkFenceCreateInfo fence_info {};
|
||||
fence_info.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
|
||||
fence_info.pNext = nullptr;
|
||||
fence_info.flags = VK_FENCE_CREATE_SIGNALED_BIT;
|
||||
|
||||
if (vkCreateFence(ctx->device, &fence_info, nullptr, &pool->fences[i]) != VK_SUCCESS) {
|
||||
EXIT("Can't create fence");
|
||||
}
|
||||
|
||||
VkSemaphoreCreateInfo semaphore_info {};
|
||||
semaphore_info.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
|
||||
semaphore_info.pNext = nullptr;
|
||||
semaphore_info.flags = 0;
|
||||
|
||||
if (vkCreateSemaphore(ctx->device, &semaphore_info, nullptr, &pool->semaphores[i]) !=
|
||||
VK_SUCCESS) {
|
||||
EXIT("Can't create semaphore");
|
||||
}
|
||||
|
||||
EXIT_IF(pool->buffers[i] == nullptr);
|
||||
EXIT_IF(pool->fences[i] == nullptr);
|
||||
EXIT_IF(pool->semaphores[i] == nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
void CommandPool::DeleteAll() {
|
||||
auto* ctx = g_render_ctx->GetGraphicCtx();
|
||||
|
||||
for (auto& pool: m_pool) {
|
||||
if (pool != nullptr) {
|
||||
EXIT_IF(ctx == nullptr);
|
||||
EXIT_IF(ctx->device == nullptr);
|
||||
|
||||
for (uint32_t i = 0; i < pool->buffers_count; i++) {
|
||||
vkDestroySemaphore(ctx->device, pool->semaphores[i], nullptr);
|
||||
vkDestroyFence(ctx->device, pool->fences[i], nullptr);
|
||||
}
|
||||
|
||||
vkFreeCommandBuffers(ctx->device, pool->pool, pool->buffers_count, pool->buffers.get());
|
||||
|
||||
vkDestroyCommandPool(ctx->device, pool->pool, nullptr);
|
||||
|
||||
delete pool;
|
||||
pool = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool CommandBuffer::IsInvalid() const {
|
||||
EXIT_IF(g_render_ctx == nullptr);
|
||||
|
||||
if (m_pool != nullptr) {
|
||||
Common::LockGuard lock(m_pool->mutex);
|
||||
|
||||
return (m_index == static_cast<uint32_t>(-1) || m_index >= m_pool->buffers_count);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void CommandBuffer::Allocate() {
|
||||
EXIT_IF(!IsInvalid());
|
||||
|
||||
m_pool = g_command_pool.GetPool(m_queue);
|
||||
|
||||
Common::LockGuard lock(m_pool->mutex);
|
||||
|
||||
for (uint32_t i = 0; i < m_pool->buffers_count; i++) {
|
||||
if (!m_pool->busy[i]) {
|
||||
m_pool->busy[i] = true;
|
||||
vkResetCommandBuffer(m_pool->buffers[i], VK_COMMAND_BUFFER_RESET_RELEASE_RESOURCES_BIT);
|
||||
m_index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
EXIT_NOT_IMPLEMENTED(IsInvalid());
|
||||
}
|
||||
|
||||
void CommandBuffer::Free() {
|
||||
EXIT_IF(IsInvalid());
|
||||
|
||||
Common::LockGuard lock(m_pool->mutex);
|
||||
|
||||
WaitForFence();
|
||||
|
||||
m_host_stream.Release();
|
||||
|
||||
m_pool->busy[m_index] = false;
|
||||
vkResetCommandBuffer(m_pool->buffers[m_index], VK_COMMAND_BUFFER_RESET_RELEASE_RESOURCES_BIT);
|
||||
RecycleDescriptorsAfterFence();
|
||||
m_fence_resources.ReleaseAfterFence();
|
||||
m_index = static_cast<uint32_t>(-1);
|
||||
|
||||
EXIT_NOT_IMPLEMENTED(!IsInvalid());
|
||||
}
|
||||
|
||||
void CommandBuffer::DeleteAfterFence(VulkanBuffer* buffer) {
|
||||
EXIT_IF(buffer == nullptr);
|
||||
|
||||
m_delete_after_fence.push_back(buffer);
|
||||
}
|
||||
|
||||
void CommandBuffer::RetainResourceUntilFence(std::shared_ptr<void> resource) {
|
||||
if (IsInvalid() || m_execute) {
|
||||
EXIT("cannot retain a resource on an invalid or submitted command buffer\n");
|
||||
}
|
||||
m_fence_resources.Retain(std::move(resource));
|
||||
}
|
||||
|
||||
void CommandBuffer::RecycleDescriptorAfterFence(VulkanDescriptorSet* set) {
|
||||
EXIT_IF(set == nullptr);
|
||||
|
||||
m_descriptor_sets_after_fence.push_back(set);
|
||||
}
|
||||
|
||||
void CommandBuffer::RecycleDescriptorsAfterFence() {
|
||||
for (auto* set: m_descriptor_sets_after_fence) {
|
||||
g_render_ctx->GetDescriptorCache()->Recycle(set);
|
||||
}
|
||||
m_descriptor_sets_after_fence.clear();
|
||||
}
|
||||
|
||||
void CommandBuffer::Begin() const {
|
||||
EXIT_IF(IsInvalid());
|
||||
|
||||
auto* buffer = m_pool->buffers[m_index];
|
||||
|
||||
VkCommandBufferBeginInfo begin_info {};
|
||||
begin_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
|
||||
begin_info.pNext = nullptr;
|
||||
begin_info.flags = 0;
|
||||
begin_info.pInheritanceInfo = nullptr;
|
||||
|
||||
auto result = vkBeginCommandBuffer(buffer, &begin_info);
|
||||
|
||||
EXIT_NOT_IMPLEMENTED(result != VK_SUCCESS);
|
||||
}
|
||||
|
||||
void CommandBuffer::End() const {
|
||||
EXIT_IF(IsInvalid());
|
||||
|
||||
auto* buffer = m_pool->buffers[m_index];
|
||||
|
||||
auto result = vkEndCommandBuffer(buffer);
|
||||
|
||||
EXIT_NOT_IMPLEMENTED(result != VK_SUCCESS);
|
||||
}
|
||||
|
||||
void CommandBuffer::SetDebugInfo(uint32_t op, uint64_t submit_id, uint32_t arg0, uint32_t arg1,
|
||||
uint32_t arg2, uint32_t arg3, uint64_t arg4) {
|
||||
m_debug_op = op;
|
||||
m_debug_submit_id = submit_id;
|
||||
m_debug_arg0 = arg0;
|
||||
m_debug_arg1 = arg1;
|
||||
m_debug_arg2 = arg2;
|
||||
m_debug_arg3 = arg3;
|
||||
m_debug_arg4 = arg4;
|
||||
}
|
||||
|
||||
void CommandBuffer::Execute() {
|
||||
EXIT_IF(IsInvalid());
|
||||
EXIT_IF(m_execute);
|
||||
|
||||
auto* buffer = m_pool->buffers[m_index];
|
||||
auto* fence = m_pool->fences[m_index];
|
||||
|
||||
VkSubmitInfo submit_info {};
|
||||
submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
|
||||
submit_info.pNext = nullptr;
|
||||
submit_info.waitSemaphoreCount = 0;
|
||||
submit_info.pWaitSemaphores = nullptr;
|
||||
submit_info.pWaitDstStageMask = nullptr;
|
||||
submit_info.commandBufferCount = 1;
|
||||
submit_info.pCommandBuffers = &buffer;
|
||||
submit_info.signalSemaphoreCount = 0;
|
||||
submit_info.pSignalSemaphores = nullptr;
|
||||
|
||||
EXIT_IF(m_queue < 0 || m_queue >= GraphicContext::QUEUES_NUM);
|
||||
|
||||
const auto& queue = g_render_ctx->GetGraphicCtx()->queues[m_queue];
|
||||
|
||||
auto result = vkResetFences(g_render_ctx->GetGraphicCtx()->device, 1, &fence);
|
||||
if (result != VK_SUCCESS) {
|
||||
LOGF("vkResetFences failed before submit: %s (%d)\n", string_VkResult(result),
|
||||
static_cast<int>(result));
|
||||
}
|
||||
EXIT_NOT_IMPLEMENTED(result != VK_SUCCESS);
|
||||
|
||||
if (queue.mutex != nullptr) {
|
||||
queue.mutex->Lock();
|
||||
}
|
||||
|
||||
if (Config::GraphicsDebugDumpEnabled()) {
|
||||
LOGF("vkQueueSubmit begin: queue=%d index=%u debug_op=%u debug_submit=%" PRIu64
|
||||
" args=%u,%u,%u,%u,0x%016" PRIx64 "\n",
|
||||
m_queue, m_index, m_debug_op, m_debug_submit_id, m_debug_arg0, m_debug_arg1,
|
||||
m_debug_arg2, m_debug_arg3, m_debug_arg4);
|
||||
}
|
||||
|
||||
result = vkQueueSubmit(queue.vk_queue, 1, &submit_info, fence);
|
||||
|
||||
if (queue.mutex != nullptr) {
|
||||
queue.mutex->Unlock();
|
||||
}
|
||||
|
||||
m_execute = true;
|
||||
m_submit_seq = g_command_buffer_submit_seq.fetch_add(1, std::memory_order_relaxed) + 1;
|
||||
|
||||
if (result != VK_SUCCESS) {
|
||||
LOGF("vkQueueSubmit failed: %s (%d), queue=%d index=%u submit_seq=%" PRIu64
|
||||
" debug_op=%u debug_submit=%" PRIu64 " args=%u,%u,%u,%u,0x%016" PRIx64 "\n",
|
||||
string_VkResult(result), static_cast<int>(result), m_queue, m_index, m_submit_seq,
|
||||
m_debug_op, m_debug_submit_id, m_debug_arg0, m_debug_arg1, m_debug_arg2, m_debug_arg3,
|
||||
m_debug_arg4);
|
||||
}
|
||||
EXIT_NOT_IMPLEMENTED(result != VK_SUCCESS);
|
||||
}
|
||||
|
||||
void CommandBuffer::ExecuteWithSemaphore(VkSemaphore signal_semaphore) {
|
||||
EXIT_IF(IsInvalid());
|
||||
EXIT_IF(m_execute);
|
||||
|
||||
auto* buffer = m_pool->buffers[m_index];
|
||||
auto* fence = m_pool->fences[m_index];
|
||||
|
||||
if (signal_semaphore == nullptr) {
|
||||
signal_semaphore = m_pool->semaphores[m_index];
|
||||
}
|
||||
|
||||
VkSubmitInfo submit_info {};
|
||||
submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
|
||||
submit_info.pNext = nullptr;
|
||||
submit_info.waitSemaphoreCount = 0;
|
||||
submit_info.pWaitSemaphores = nullptr;
|
||||
submit_info.pWaitDstStageMask = nullptr;
|
||||
submit_info.commandBufferCount = 1;
|
||||
submit_info.pCommandBuffers = &buffer;
|
||||
submit_info.signalSemaphoreCount = 1;
|
||||
submit_info.pSignalSemaphores = &signal_semaphore;
|
||||
|
||||
EXIT_IF(m_queue < 0 || m_queue >= GraphicContext::QUEUES_NUM);
|
||||
|
||||
const auto& queue = g_render_ctx->GetGraphicCtx()->queues[m_queue];
|
||||
|
||||
auto result = vkResetFences(g_render_ctx->GetGraphicCtx()->device, 1, &fence);
|
||||
if (result != VK_SUCCESS) {
|
||||
LOGF("vkResetFences failed before submit: %s (%d)\n", string_VkResult(result),
|
||||
static_cast<int>(result));
|
||||
}
|
||||
EXIT_NOT_IMPLEMENTED(result != VK_SUCCESS);
|
||||
|
||||
if (queue.mutex != nullptr) {
|
||||
queue.mutex->Lock();
|
||||
}
|
||||
|
||||
if (Config::GraphicsDebugDumpEnabled()) {
|
||||
LOGF("vkQueueSubmit begin: queue=%d index=%u signal_semaphore=%p debug_op=%u"
|
||||
" debug_submit=%" PRIu64 " args=%u,%u,%u,%u,0x%016" PRIx64 "\n",
|
||||
m_queue, m_index, static_cast<void*>(signal_semaphore), m_debug_op, m_debug_submit_id,
|
||||
m_debug_arg0, m_debug_arg1, m_debug_arg2, m_debug_arg3, m_debug_arg4);
|
||||
}
|
||||
|
||||
result = vkQueueSubmit(queue.vk_queue, 1, &submit_info, fence);
|
||||
|
||||
if (queue.mutex != nullptr) {
|
||||
queue.mutex->Unlock();
|
||||
}
|
||||
|
||||
m_execute = true;
|
||||
m_submit_seq = g_command_buffer_submit_seq.fetch_add(1, std::memory_order_relaxed) + 1;
|
||||
|
||||
if (result != VK_SUCCESS) {
|
||||
LOGF("vkQueueSubmit failed: %s (%d), queue=%d index=%u submit_seq=%" PRIu64
|
||||
" debug_op=%u debug_submit=%" PRIu64 " args=%u,%u,%u,%u,0x%016" PRIx64 "\n",
|
||||
string_VkResult(result), static_cast<int>(result), m_queue, m_index, m_submit_seq,
|
||||
m_debug_op, m_debug_submit_id, m_debug_arg0, m_debug_arg1, m_debug_arg2, m_debug_arg3,
|
||||
m_debug_arg4);
|
||||
}
|
||||
EXIT_NOT_IMPLEMENTED(result != VK_SUCCESS);
|
||||
}
|
||||
|
||||
void CommandBuffer::ExecuteWithSemaphore(VkSemaphore wait_semaphore,
|
||||
VkPipelineStageFlags wait_stage,
|
||||
VkSemaphore signal_semaphore) {
|
||||
EXIT_IF(IsInvalid());
|
||||
EXIT_IF(m_execute);
|
||||
EXIT_IF(wait_semaphore == nullptr);
|
||||
|
||||
auto* buffer = m_pool->buffers[m_index];
|
||||
auto* fence = m_pool->fences[m_index];
|
||||
|
||||
if (signal_semaphore == nullptr) {
|
||||
signal_semaphore = m_pool->semaphores[m_index];
|
||||
}
|
||||
|
||||
VkSubmitInfo submit_info {};
|
||||
submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
|
||||
submit_info.pNext = nullptr;
|
||||
submit_info.waitSemaphoreCount = 1;
|
||||
submit_info.pWaitSemaphores = &wait_semaphore;
|
||||
submit_info.pWaitDstStageMask = &wait_stage;
|
||||
submit_info.commandBufferCount = 1;
|
||||
submit_info.pCommandBuffers = &buffer;
|
||||
submit_info.signalSemaphoreCount = 1;
|
||||
submit_info.pSignalSemaphores = &signal_semaphore;
|
||||
|
||||
EXIT_IF(m_queue < 0 || m_queue >= GraphicContext::QUEUES_NUM);
|
||||
|
||||
const auto& queue = g_render_ctx->GetGraphicCtx()->queues[m_queue];
|
||||
|
||||
auto result = vkResetFences(g_render_ctx->GetGraphicCtx()->device, 1, &fence);
|
||||
if (result != VK_SUCCESS) {
|
||||
LOGF("vkResetFences failed before submit: %s (%d)\n", string_VkResult(result),
|
||||
static_cast<int>(result));
|
||||
}
|
||||
EXIT_NOT_IMPLEMENTED(result != VK_SUCCESS);
|
||||
|
||||
if (queue.mutex != nullptr) {
|
||||
queue.mutex->Lock();
|
||||
}
|
||||
|
||||
if (Config::GraphicsDebugDumpEnabled()) {
|
||||
LOGF("vkQueueSubmit begin: queue=%d index=%u wait_semaphore=%p signal_semaphore=%p"
|
||||
" debug_op=%u debug_submit=%" PRIu64 " args=%u,%u,%u,%u,0x%016" PRIx64 "\n",
|
||||
m_queue, m_index, static_cast<void*>(wait_semaphore),
|
||||
static_cast<void*>(signal_semaphore), m_debug_op, m_debug_submit_id, m_debug_arg0,
|
||||
m_debug_arg1, m_debug_arg2, m_debug_arg3, m_debug_arg4);
|
||||
}
|
||||
|
||||
result = vkQueueSubmit(queue.vk_queue, 1, &submit_info, fence);
|
||||
|
||||
if (queue.mutex != nullptr) {
|
||||
queue.mutex->Unlock();
|
||||
}
|
||||
|
||||
m_execute = true;
|
||||
m_submit_seq = g_command_buffer_submit_seq.fetch_add(1, std::memory_order_relaxed) + 1;
|
||||
|
||||
if (result != VK_SUCCESS) {
|
||||
LOGF("vkQueueSubmit failed: %s (%d), queue=%d index=%u submit_seq=%" PRIu64
|
||||
" debug_op=%u debug_submit=%" PRIu64 " args=%u,%u,%u,%u,0x%016" PRIx64 "\n",
|
||||
string_VkResult(result), static_cast<int>(result), m_queue, m_index, m_submit_seq,
|
||||
m_debug_op, m_debug_submit_id, m_debug_arg0, m_debug_arg1, m_debug_arg2, m_debug_arg3,
|
||||
m_debug_arg4);
|
||||
}
|
||||
EXIT_NOT_IMPLEMENTED(result != VK_SUCCESS);
|
||||
}
|
||||
|
||||
void CommandBuffer::WaitForFence() {
|
||||
EXIT_IF(IsInvalid());
|
||||
|
||||
const bool was_executed = m_execute;
|
||||
if (m_execute) {
|
||||
auto* device = g_render_ctx->GetGraphicCtx()->device;
|
||||
|
||||
auto result = vkWaitForFences(device, 1, &m_pool->fences[m_index], VK_TRUE, UINT64_MAX);
|
||||
if (result != VK_SUCCESS) {
|
||||
LOGF("vkWaitForFences failed: %s (%d), wait=WaitForFence queue=%d index=%u "
|
||||
"submit_seq=%" PRIu64 " debug_op=%u debug_submit=%" PRIu64
|
||||
" args=%u,%u,%u,%u,0x%016" PRIx64 "\n",
|
||||
string_VkResult(result), static_cast<int>(result), m_queue, m_index, m_submit_seq,
|
||||
m_debug_op, m_debug_submit_id, m_debug_arg0, m_debug_arg1, m_debug_arg2,
|
||||
m_debug_arg3, m_debug_arg4);
|
||||
}
|
||||
EXIT_NOT_IMPLEMENTED(result != VK_SUCCESS);
|
||||
|
||||
m_execute = false;
|
||||
}
|
||||
|
||||
if (was_executed) {
|
||||
RecycleDescriptorsAfterFence();
|
||||
m_fence_resources.ReleaseAfterFence();
|
||||
}
|
||||
for (auto* buffer: m_delete_after_fence) {
|
||||
VulkanDeleteBuffer(g_render_ctx->GetGraphicCtx(), buffer);
|
||||
delete buffer;
|
||||
}
|
||||
m_delete_after_fence.clear();
|
||||
}
|
||||
|
||||
void CommandBuffer::WaitForFenceAndReset() {
|
||||
EXIT_IF(IsInvalid());
|
||||
|
||||
const bool was_executed = m_execute;
|
||||
if (m_execute) {
|
||||
auto* device = g_render_ctx->GetGraphicCtx()->device;
|
||||
|
||||
auto result = vkWaitForFences(device, 1, &m_pool->fences[m_index], VK_TRUE, UINT64_MAX);
|
||||
if (result != VK_SUCCESS) {
|
||||
LOGF("vkWaitForFences failed: %s (%d), wait=WaitForFenceAndReset queue=%d index=%u "
|
||||
"submit_seq=%" PRIu64 " debug_op=%u debug_submit=%" PRIu64
|
||||
" args=%u,%u,%u,%u,0x%016" PRIx64 "\n",
|
||||
string_VkResult(result), static_cast<int>(result), m_queue, m_index, m_submit_seq,
|
||||
m_debug_op, m_debug_submit_id, m_debug_arg0, m_debug_arg1, m_debug_arg2,
|
||||
m_debug_arg3, m_debug_arg4);
|
||||
}
|
||||
EXIT_NOT_IMPLEMENTED(result != VK_SUCCESS);
|
||||
|
||||
m_execute = false;
|
||||
|
||||
vkResetCommandBuffer(m_pool->buffers[m_index],
|
||||
VK_COMMAND_BUFFER_RESET_RELEASE_RESOURCES_BIT);
|
||||
}
|
||||
|
||||
m_host_stream.Reset();
|
||||
|
||||
if (was_executed) {
|
||||
RecycleDescriptorsAfterFence();
|
||||
m_fence_resources.ReleaseAfterFence();
|
||||
}
|
||||
for (auto* buffer: m_delete_after_fence) {
|
||||
VulkanDeleteBuffer(g_render_ctx->GetGraphicCtx(), buffer);
|
||||
delete buffer;
|
||||
}
|
||||
m_delete_after_fence.clear();
|
||||
}
|
||||
|
||||
void CommandBuffer::BeginRenderPass(VulkanFramebuffer* framebuffer, RenderColorInfo* colors,
|
||||
uint32_t requested_color_count, RenderDepthInfo* depth) const {
|
||||
EXIT_IF(IsInvalid());
|
||||
|
||||
auto* buffer = m_pool->buffers[m_index];
|
||||
|
||||
EXIT_IF(framebuffer == nullptr);
|
||||
EXIT_IF(colors == nullptr);
|
||||
EXIT_IF(requested_color_count > RENDER_COLOR_ATTACHMENTS_MAX);
|
||||
|
||||
bool with_depth = (depth->format != VK_FORMAT_UNDEFINED && depth->vulkan_buffer != nullptr);
|
||||
uint32_t color_count = 0;
|
||||
for (uint32_t i = 0; i < requested_color_count; i++) {
|
||||
if (colors[i].vulkan_buffer == nullptr) {
|
||||
break;
|
||||
}
|
||||
color_count++;
|
||||
}
|
||||
bool with_color = (color_count != 0);
|
||||
|
||||
EXIT_NOT_IMPLEMENTED(!with_depth && !with_color);
|
||||
|
||||
VkClearValue clears[RENDER_COLOR_ATTACHMENTS_MAX + 1] = {};
|
||||
for (uint32_t i = 0; i < color_count; i++) {
|
||||
clears[i].color = colors[i].color_clear_value;
|
||||
}
|
||||
clears[color_count].depthStencil = {depth->depth_clear_value, depth->stencil_clear_value};
|
||||
|
||||
VkExtent2D extent = (with_color ? colors[0].extent : depth->vulkan_buffer->extent);
|
||||
|
||||
VkRenderPassBeginInfo render_pass_info {};
|
||||
render_pass_info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
|
||||
render_pass_info.pNext = nullptr;
|
||||
render_pass_info.renderPass = framebuffer->render_pass;
|
||||
render_pass_info.framebuffer = framebuffer->framebuffer;
|
||||
render_pass_info.renderArea.offset = {0, 0};
|
||||
render_pass_info.renderArea.extent = extent;
|
||||
render_pass_info.clearValueCount = color_count + (with_depth ? 1u : 0u);
|
||||
render_pass_info.pClearValues = clears;
|
||||
|
||||
for (uint32_t i = 0; i < color_count; i++) {
|
||||
const auto color_initial_layout = framebuffer->color_layout[i];
|
||||
if (colors[i].vulkan_buffer->layout != color_initial_layout) {
|
||||
if (graphics_debug_dump_enabled()) {
|
||||
LOGF("BeginRenderPass: color%u initial barrier image=%p mem=%" PRIu64 " %s -> %s\n",
|
||||
i, reinterpret_cast<void*>(colors[i].vulkan_buffer->image),
|
||||
colors[i].vulkan_buffer->memory.unique_id,
|
||||
string_VkImageLayout(colors[i].vulkan_buffer->layout),
|
||||
string_VkImageLayout(color_initial_layout));
|
||||
}
|
||||
|
||||
VkImageMemoryBarrier image_memory_barrier {};
|
||||
image_memory_barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
|
||||
image_memory_barrier.pNext = nullptr;
|
||||
image_memory_barrier.srcAccessMask = 0;
|
||||
image_memory_barrier.dstAccessMask =
|
||||
VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
|
||||
image_memory_barrier.oldLayout = colors[i].vulkan_buffer->layout;
|
||||
image_memory_barrier.newLayout = color_initial_layout;
|
||||
image_memory_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
image_memory_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
image_memory_barrier.image = colors[i].vulkan_buffer->image;
|
||||
image_memory_barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
image_memory_barrier.subresourceRange.baseMipLevel = 0;
|
||||
image_memory_barrier.subresourceRange.levelCount = VK_REMAINING_MIP_LEVELS;
|
||||
image_memory_barrier.subresourceRange.baseArrayLayer = 0;
|
||||
image_memory_barrier.subresourceRange.layerCount = 1;
|
||||
|
||||
vkCmdPipelineBarrier(buffer, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
|
||||
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, 0, 0, nullptr, 0,
|
||||
nullptr, 1, &image_memory_barrier);
|
||||
|
||||
colors[i].vulkan_buffer->layout = image_memory_barrier.newLayout;
|
||||
} else if (graphics_debug_dump_enabled()) {
|
||||
LOGF("BeginRenderPass: color%u initial image=%p mem=%" PRIu64 " layout=%s\n", i,
|
||||
reinterpret_cast<void*>(colors[i].vulkan_buffer->image),
|
||||
colors[i].vulkan_buffer->memory.unique_id,
|
||||
string_VkImageLayout(colors[i].vulkan_buffer->layout));
|
||||
}
|
||||
}
|
||||
|
||||
const auto depth_layout =
|
||||
(with_depth && framebuffer != nullptr ? framebuffer->depth_layout
|
||||
: VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL);
|
||||
|
||||
if (with_depth && depth->vulkan_buffer->layout != depth_layout) {
|
||||
VkImageMemoryBarrier image_memory_barrier {};
|
||||
image_memory_barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
|
||||
image_memory_barrier.pNext = nullptr;
|
||||
image_memory_barrier.srcAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT;
|
||||
image_memory_barrier.dstAccessMask =
|
||||
(depth_layout == VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL
|
||||
? VK_ACCESS_MEMORY_READ_BIT
|
||||
: VK_ACCESS_MEMORY_WRITE_BIT);
|
||||
image_memory_barrier.oldLayout = depth->vulkan_buffer->layout;
|
||||
image_memory_barrier.newLayout = depth_layout;
|
||||
image_memory_barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
image_memory_barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||||
image_memory_barrier.image = depth->vulkan_buffer->image;
|
||||
image_memory_barrier.subresourceRange.aspectMask =
|
||||
DepthStencilAspectMask(depth->vulkan_buffer->format);
|
||||
image_memory_barrier.subresourceRange.baseMipLevel = 0;
|
||||
image_memory_barrier.subresourceRange.levelCount = 1;
|
||||
image_memory_barrier.subresourceRange.baseArrayLayer = 0;
|
||||
image_memory_barrier.subresourceRange.layerCount = 1;
|
||||
|
||||
vkCmdPipelineBarrier(
|
||||
buffer, VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT | VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
|
||||
VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT | VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, 0,
|
||||
nullptr, 0, nullptr, 1, &image_memory_barrier);
|
||||
|
||||
depth->vulkan_buffer->layout = image_memory_barrier.newLayout;
|
||||
}
|
||||
|
||||
vkCmdBeginRenderPass(buffer, &render_pass_info, VK_SUBPASS_CONTENTS_INLINE);
|
||||
|
||||
for (uint32_t i = 0; i < color_count; i++) {
|
||||
colors[i].vulkan_buffer->layout = RENDER_COLOR_IMAGE_LAYOUT;
|
||||
}
|
||||
}
|
||||
|
||||
void CommandBuffer::EndRenderPass() const {
|
||||
EXIT_IF(IsInvalid());
|
||||
|
||||
auto* buffer = m_pool->buffers[m_index];
|
||||
|
||||
vkCmdEndRenderPass(buffer);
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,357 @@
|
||||
#include "common/assert.h"
|
||||
#include "common/common.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/profiler.h"
|
||||
#include "common/stringUtils.h"
|
||||
#include "common/threads.h"
|
||||
#include "graphics/guest_gpu/gpu_defs.h"
|
||||
#include "graphics/guest_gpu/hardwareContext.h"
|
||||
#include "graphics/guest_gpu/tile.h"
|
||||
#include "graphics/host_gpu/graphicContext.h"
|
||||
#include "graphics/host_gpu/objects/textureCommon.h"
|
||||
#include "graphics/host_gpu/renderer/descriptorCache.h"
|
||||
#include "graphics/host_gpu/renderer/framebufferCache.h"
|
||||
#include "graphics/host_gpu/renderer/render.h"
|
||||
#include "graphics/host_gpu/renderer/renderContext.h"
|
||||
#include "graphics/host_gpu/renderer/renderState.h"
|
||||
#include "graphics/host_gpu/utils.h"
|
||||
#include "graphics/presentation/displayBuffer.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <cmath>
|
||||
#include <cstdarg>
|
||||
#include <cstdio>
|
||||
#include <limits>
|
||||
#include <vulkan/vk_enum_string_helper.h>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
[[noreturn]] static void DepthFatal(const char* format, ...) {
|
||||
std::fputs("Depth target fatal: ", stderr);
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
std::vfprintf(stderr, format, args);
|
||||
va_end(args);
|
||||
std::fputc('\n', stderr);
|
||||
std::fflush(stderr);
|
||||
EXIT("unsupported render state; details were printed above\n");
|
||||
}
|
||||
|
||||
static VkStencilOp ConvertStencilOp(uint8_t value) {
|
||||
switch (static_cast<Prospero::StencilOp>(value)) {
|
||||
case Prospero::StencilOp::kKeep: return VK_STENCIL_OP_KEEP;
|
||||
case Prospero::StencilOp::kZero: return VK_STENCIL_OP_ZERO;
|
||||
case Prospero::StencilOp::kReplaceTest:
|
||||
case Prospero::StencilOp::kReplaceOp: return VK_STENCIL_OP_REPLACE;
|
||||
case Prospero::StencilOp::kAddClamp: return VK_STENCIL_OP_INCREMENT_AND_CLAMP;
|
||||
case Prospero::StencilOp::kSubClamp: return VK_STENCIL_OP_DECREMENT_AND_CLAMP;
|
||||
case Prospero::StencilOp::kInvert: return VK_STENCIL_OP_INVERT;
|
||||
case Prospero::StencilOp::kAddWrap: return VK_STENCIL_OP_INCREMENT_AND_WRAP;
|
||||
case Prospero::StencilOp::kSubWrap: return VK_STENCIL_OP_DECREMENT_AND_WRAP;
|
||||
default: DepthFatal("unsupported stencil operation");
|
||||
}
|
||||
}
|
||||
|
||||
static bool UsesStencilOpValue(uint8_t fail, uint8_t pass, uint8_t depth_fail) {
|
||||
return fail == Prospero::GpuEnumValue(Prospero::StencilOp::kReplaceOp) ||
|
||||
pass == Prospero::GpuEnumValue(Prospero::StencilOp::kReplaceOp) ||
|
||||
depth_fail == Prospero::GpuEnumValue(Prospero::StencilOp::kReplaceOp);
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(readability-function-cognitive-complexity)
|
||||
void ResolveRenderDepthTarget(uint64_t submit_id, CommandBuffer* buffer, const HW::Context& hw,
|
||||
RenderDepthInfo* r) {
|
||||
KYTY_PROFILER_FUNCTION();
|
||||
EXIT_IF(r == nullptr);
|
||||
(void)submit_id;
|
||||
(void)buffer;
|
||||
const auto& z = hw.GetDepthRenderTarget();
|
||||
const auto& rc = hw.GetRenderControl();
|
||||
const auto& dc = hw.GetDepthControl();
|
||||
const auto& sc = hw.GetStencilControl();
|
||||
const auto& sm = hw.GetStencilMask();
|
||||
const bool depth_active =
|
||||
dc.z_enable || dc.z_write_enable || dc.depth_bounds_enable || rc.depth_clear_enable;
|
||||
const bool stencil_active = dc.stencil_enable || rc.stencil_clear_enable;
|
||||
if (!depth_active && !stencil_active) {
|
||||
return;
|
||||
}
|
||||
const bool attachment_unbound =
|
||||
z.z_info.format == Prospero::GpuEnumValue(Prospero::DepthFormat::kInvalid) &&
|
||||
z.stencil_info.format == Prospero::GpuEnumValue(Prospero::StencilFormat::kInvalid) &&
|
||||
z.z_info.tile_mode_index == 0 && z.z_info.num_samples == 0 &&
|
||||
z.z_info.zrange_precision <= 1 && !z.z_info.expclear_enabled &&
|
||||
!z.z_info.embedded_sample_locations && !z.z_info.partially_resident &&
|
||||
z.z_info.num_mip_levels == 0 && z.z_info.plane_compression == 0 &&
|
||||
z.stencil_info.tile_mode_index == 0 && z.stencil_info.tile_split == 0 &&
|
||||
!z.stencil_info.expclear_enabled && !z.stencil_info.texture_compatible_stencil &&
|
||||
!z.stencil_info.partially_resident && z.depth_view.slice_start == 0 &&
|
||||
z.depth_view.slice_max == 0 && z.depth_view.current_mip_level == 0 &&
|
||||
!z.depth_view.depth_write_disable && !z.depth_view.stencil_write_disable &&
|
||||
z.depth_info.addr5_swizzle_mask == 0 && z.depth_info.array_mode == 0 &&
|
||||
z.depth_info.pipe_config == 0 && z.depth_info.bank_width == 0 &&
|
||||
z.depth_info.bank_height == 0 && z.depth_info.macro_tile_aspect == 0 &&
|
||||
z.depth_info.num_banks == 0 && z.htile_surface.linear == 0 &&
|
||||
z.htile_surface.full_cache == 0 && z.htile_surface.htile_uses_preload_win == 0 &&
|
||||
z.htile_surface.preload == 0 && z.htile_surface.prefetch_width == 0 &&
|
||||
z.htile_surface.prefetch_height == 0 && z.htile_surface.dst_outside_zero_to_one == 0 &&
|
||||
z.z_read_base_addr == 0 && z.z_write_base_addr == 0 && z.stencil_read_base_addr == 0 &&
|
||||
z.stencil_write_base_addr == 0 && z.htile_data_base_addr == 0 &&
|
||||
!z.z_info.tile_surface_enable && !z.size.valid && !z.width_height_valid &&
|
||||
!z.pitch_height_valid && z.size.x_max == 0 && z.size.y_max == 0 &&
|
||||
z.pitch_div8_minus1 == 0 && z.height_div8_minus1 == 0 && z.slice_div64_minus1 == 0 &&
|
||||
z.width == 0 && z.height == 0;
|
||||
if (attachment_unbound) {
|
||||
static std::atomic_bool logged = false;
|
||||
if (!logged.exchange(true, std::memory_order_relaxed)) {
|
||||
LOGF("DepthTarget: ignoring enabled depth/stencil state without a bound attachment\n");
|
||||
}
|
||||
return;
|
||||
}
|
||||
const bool has_stencil =
|
||||
z.stencil_info.format != Prospero::GpuEnumValue(Prospero::StencilFormat::kInvalid);
|
||||
const bool has_htile = z.z_info.tile_surface_enable;
|
||||
const bool msaa_compat = depth_msaa_single_sample_compatible(z.z_info.num_samples);
|
||||
const bool htile_stencil_compat = depth_htile_stencil_acceleration_compatible(
|
||||
has_stencil, has_htile, z.stencil_info.tile_stencil_disable);
|
||||
// Prospero defines the compression-disable bits as tile writeback policy. Vulkan attachments
|
||||
// expose the same logical depth/stencil values regardless of the driver's backing compression.
|
||||
if ((stencil_active && !has_stencil) || rc.resummarize_enable || rc.copy_centroid ||
|
||||
rc.copy_sample != 0 || z.z_info.expclear_enabled || z.stencil_info.expclear_enabled ||
|
||||
z.z_info.embedded_sample_locations || z.z_info.partially_resident ||
|
||||
z.stencil_info.partially_resident || z.z_info.plane_compression != 0 ||
|
||||
(z.z_info.num_samples != 0 && !msaa_compat) || z.z_info.num_mip_levels != 0 ||
|
||||
z.z_info.tile_mode_index != 0 || z.z_info.zrange_precision > 1 ||
|
||||
z.depth_view.current_mip_level != 0 || z.depth_view.slice_start != 0 ||
|
||||
z.depth_view.slice_max != 0 || z.depth_info.addr5_swizzle_mask != 0 ||
|
||||
z.depth_info.array_mode != 0 || z.depth_info.pipe_config != 0 ||
|
||||
z.depth_info.bank_width != 0 || z.depth_info.bank_height != 0 ||
|
||||
z.depth_info.macro_tile_aspect != 0 || z.depth_info.num_banks != 0 ||
|
||||
z.htile_surface.linear != 0 || z.htile_surface.full_cache != 0 ||
|
||||
z.htile_surface.htile_uses_preload_win != 0 || z.htile_surface.preload != 0 ||
|
||||
z.htile_surface.prefetch_width != 0 || z.htile_surface.prefetch_height != 0 ||
|
||||
z.htile_surface.dst_outside_zero_to_one != 0 || z.z_read_base_addr == 0 ||
|
||||
z.z_write_base_addr != z.z_read_base_addr || (z.z_read_base_addr & 0xffffu) != 0 ||
|
||||
dc.zfunc > static_cast<uint8_t>(VK_COMPARE_OP_ALWAYS)) {
|
||||
DepthFatal("unsupported depth register state");
|
||||
}
|
||||
if (msaa_compat) {
|
||||
static std::atomic<uint32_t> logged_fragments = 0;
|
||||
const uint32_t bit = 1u << z.z_info.num_samples;
|
||||
if ((logged_fragments.fetch_or(bit, std::memory_order_relaxed) & bit) == 0) {
|
||||
LOGF("DepthTarget: compatibility: rendering PS5 %ux depth fragments as single-sample\n",
|
||||
bit);
|
||||
}
|
||||
}
|
||||
if (has_stencil) {
|
||||
// Prospero defines Hi-Stencil as HTile-backed acceleration of the logical stencil plane.
|
||||
// Keep the plane native in Vulkan while tracking HTile separately.
|
||||
if (z.stencil_info.format != Prospero::GpuEnumValue(Prospero::StencilFormat::k8UInt) ||
|
||||
z.stencil_info.tile_mode_index != 0 || z.stencil_info.tile_split != 0 ||
|
||||
!htile_stencil_compat || z.stencil_info.texture_compatible_stencil ||
|
||||
z.stencil_read_base_addr == 0 ||
|
||||
z.stencil_write_base_addr != z.stencil_read_base_addr ||
|
||||
(z.stencil_read_base_addr & 0xffffu) != 0 || z.depth_view.stencil_write_disable) {
|
||||
DepthFatal("unsupported stencil attachment state");
|
||||
}
|
||||
if (!z.stencil_info.tile_stencil_disable) {
|
||||
static std::atomic_bool logged = false;
|
||||
if (!logged.load(std::memory_order_relaxed) &&
|
||||
!logged.exchange(true, std::memory_order_relaxed)) {
|
||||
LOGF("DepthTarget: compatibility: using native stencil with PS5 HTILE "
|
||||
"acceleration\n");
|
||||
}
|
||||
}
|
||||
} else if (z.stencil_read_base_addr != 0 || z.stencil_write_base_addr != 0 ||
|
||||
z.stencil_info.tile_mode_index != 0 || z.stencil_info.tile_split != 0 ||
|
||||
!htile_stencil_compat || z.stencil_info.texture_compatible_stencil) {
|
||||
DepthFatal("stencil state without an active stencil attachment");
|
||||
}
|
||||
if (has_htile) {
|
||||
if (z.htile_data_base_addr == 0 || (z.htile_data_base_addr & 0x7fffu) != 0) {
|
||||
DepthFatal("invalid HTile metadata address");
|
||||
}
|
||||
} else if (z.htile_data_base_addr != 0) {
|
||||
DepthFatal("HTile address without an enabled tile surface");
|
||||
}
|
||||
const bool size_xy_valid = z.size.valid && (z.size.x_max != 0 || z.size.y_max != 0);
|
||||
const bool wh_valid = z.width_height_valid && z.width != 0 && z.height != 0;
|
||||
if (!size_xy_valid && !wh_valid) {
|
||||
DepthFatal("missing depth extent");
|
||||
}
|
||||
const uint32_t width = size_xy_valid ? static_cast<uint32_t>(z.size.x_max) + 1u : z.width;
|
||||
const uint32_t height = size_xy_valid ? static_cast<uint32_t>(z.size.y_max) + 1u : z.height;
|
||||
if (width > 16384 || height > 16384 ||
|
||||
(size_xy_valid && wh_valid && (width != z.width || height != z.height)) ||
|
||||
(!z.pitch_height_valid &&
|
||||
(z.pitch_div8_minus1 != 0 || z.height_div8_minus1 != 0 || z.slice_div64_minus1 != 0))) {
|
||||
DepthFatal("inconsistent depth extent or encoded layout");
|
||||
}
|
||||
uint32_t guest_format = 0;
|
||||
uint32_t bytes = 0;
|
||||
switch (static_cast<Prospero::DepthFormat>(z.z_info.format)) {
|
||||
case Prospero::DepthFormat::kZ16:
|
||||
if (has_stencil) {
|
||||
DepthFatal("Z16 plus stencil is unsupported");
|
||||
}
|
||||
r->format = VK_FORMAT_D16_UNORM;
|
||||
guest_format = Prospero::GpuEnumValue(Prospero::BufferFormat::k16UNorm);
|
||||
bytes = 2;
|
||||
break;
|
||||
case Prospero::DepthFormat::kZ32F:
|
||||
r->format = has_stencil ? VK_FORMAT_D32_SFLOAT_S8_UINT : VK_FORMAT_D32_SFLOAT;
|
||||
guest_format = Prospero::GpuEnumValue(Prospero::BufferFormat::k32Float);
|
||||
bytes = 4;
|
||||
break;
|
||||
default: DepthFatal("unsupported depth format");
|
||||
}
|
||||
const auto pitch = TileGetTexturePitch(guest_format, width, 1,
|
||||
Prospero::GpuEnumValue(Prospero::TileMode::kDepth));
|
||||
if (z.pitch_height_valid && ((static_cast<uint64_t>(z.pitch_div8_minus1) + 1u) * 8u != pitch ||
|
||||
(static_cast<uint64_t>(z.height_div8_minus1) + 1u) * 8u !=
|
||||
((static_cast<uint64_t>(height) + 7u) & ~7ull))) {
|
||||
DepthFatal("encoded depth pitch or height mismatch");
|
||||
}
|
||||
const uint32_t block_width = bytes == 2 ? 256u : 128u;
|
||||
const uint64_t padded_width =
|
||||
(static_cast<uint64_t>(pitch) + block_width - 1u) & ~(block_width - 1u);
|
||||
const uint64_t padded_height =
|
||||
(static_cast<uint64_t>(height) + 127u) & ~static_cast<uint64_t>(127u);
|
||||
if (padded_width > UINT64_MAX / padded_height ||
|
||||
padded_width * padded_height > UINT64_MAX / bytes) {
|
||||
DepthFatal("depth footprint overflow");
|
||||
}
|
||||
const uint64_t expected_size = padded_width * padded_height * bytes;
|
||||
TileSizeAlign depth_size {};
|
||||
TileSizeAlign stencil_size {};
|
||||
TileSizeAlign htile_size {};
|
||||
if (has_stencil || has_htile) {
|
||||
if (!TileGetDepthSize(width, height, 0, z.z_info.format, z.stencil_info.format, has_htile,
|
||||
&stencil_size, &htile_size, &depth_size) ||
|
||||
depth_size.align != 65536 || depth_size.size != expected_size ||
|
||||
(has_stencil != (stencil_size.align == 65536 && stencil_size.size != 0)) ||
|
||||
(has_htile != (htile_size.align == 32768 && htile_size.size != 0))) {
|
||||
DepthFatal("unsupported depth/stencil/HTile footprint");
|
||||
}
|
||||
if (has_htile) {
|
||||
g_render_ctx->GetTextureCache()->RegisterMeta(z.htile_data_base_addr, htile_size.size);
|
||||
}
|
||||
} else {
|
||||
TileGetTextureTotalSize(guest_format, width, height, 1, pitch, 1,
|
||||
Prospero::GpuEnumValue(Prospero::TileMode::kDepth), false,
|
||||
&depth_size);
|
||||
}
|
||||
if (expected_size == 0 || expected_size > UINT32_MAX || depth_size.align != 65536 ||
|
||||
depth_size.size != expected_size ||
|
||||
(z.pitch_height_valid &&
|
||||
(static_cast<uint64_t>(z.slice_div64_minus1) + 1u) * 64u != expected_size)) {
|
||||
DepthFatal("depth footprint mismatch: extent=%ux%u pitch=%u expected=0x%016" PRIx64
|
||||
" calculated=0x%016" PRIx64 "/0x%016" PRIx64
|
||||
" encoded_valid=%u encoded=0x%016" PRIx64,
|
||||
width, height, pitch, expected_size, depth_size.size, depth_size.align,
|
||||
z.pitch_height_valid ? 1u : 0u,
|
||||
(static_cast<uint64_t>(z.slice_div64_minus1) + 1u) * 64u);
|
||||
}
|
||||
r->htile = has_htile;
|
||||
r->width = width;
|
||||
r->height = height;
|
||||
r->depth_buffer_size = depth_size.size;
|
||||
r->depth_buffer_vaddr = z.z_read_base_addr;
|
||||
r->stencil_buffer_size = has_stencil ? stencil_size.size : 0;
|
||||
r->stencil_buffer_vaddr = has_stencil ? z.stencil_read_base_addr : 0;
|
||||
r->htile_buffer_size = has_htile ? htile_size.size : 0;
|
||||
r->htile_buffer_vaddr = has_htile ? z.htile_data_base_addr : 0;
|
||||
auto* cache = g_render_ctx->GetTextureCache();
|
||||
if (has_htile && rc.depth_clear_enable && !cache->ClearMeta(z.htile_data_base_addr)) {
|
||||
DepthFatal("failed to acquire HTile metadata for a depth clear");
|
||||
}
|
||||
const bool meta_clear =
|
||||
has_htile && cache->IsMetaCleared(z.htile_data_base_addr, z.depth_view.slice_start);
|
||||
r->depth_clear_enable = rc.depth_clear_enable;
|
||||
r->depth_meta_clear_enable = meta_clear;
|
||||
r->depth_load_clear_enable = r->depth_clear_enable || r->depth_meta_clear_enable;
|
||||
r->depth_clear_value = hw.GetDepthClearValue();
|
||||
r->depth_test_enable = dc.z_enable;
|
||||
r->depth_write_enable = dc.z_write_enable && !z.depth_view.depth_write_disable;
|
||||
r->depth_compare_op = static_cast<VkCompareOp>(dc.zfunc);
|
||||
|
||||
r->depth_bounds_test_enable = dc.depth_bounds_enable;
|
||||
r->depth_min_bounds = hw.GetDepthBoundsMin();
|
||||
r->depth_max_bounds = hw.GetDepthBoundsMax();
|
||||
|
||||
r->stencil_clear_enable = rc.stencil_clear_enable;
|
||||
r->stencil_clear_value = hw.GetStencilClearValue();
|
||||
r->stencil_test_enable = dc.stencil_enable;
|
||||
if (dc.stencil_enable) {
|
||||
if (dc.stencilfunc > static_cast<uint8_t>(VK_COMPARE_OP_ALWAYS) ||
|
||||
(dc.backface_enable &&
|
||||
dc.stencilfunc_bf > static_cast<uint8_t>(VK_COMPARE_OP_ALWAYS)) ||
|
||||
(UsesStencilOpValue(sc.stencil_fail, sc.stencil_zpass, sc.stencil_zfail) &&
|
||||
sm.stencil_opval != sm.stencil_testval) ||
|
||||
(dc.backface_enable &&
|
||||
UsesStencilOpValue(sc.stencil_fail_bf, sc.stencil_zpass_bf, sc.stencil_zfail_bf) &&
|
||||
sm.stencil_opval_bf != sm.stencil_testval_bf)) {
|
||||
DepthFatal("unsupported stencil compare or replacement state");
|
||||
}
|
||||
r->stencil_static_front = {
|
||||
ConvertStencilOp(sc.stencil_fail), ConvertStencilOp(sc.stencil_zpass),
|
||||
ConvertStencilOp(sc.stencil_zfail), static_cast<VkCompareOp>(dc.stencilfunc)};
|
||||
r->stencil_dynamic_front = {sm.stencil_mask,
|
||||
rc.stencil_clear_enable ? 0u : sm.stencil_writemask,
|
||||
sm.stencil_testval};
|
||||
if (dc.backface_enable) {
|
||||
r->stencil_static_back = {
|
||||
ConvertStencilOp(sc.stencil_fail_bf), ConvertStencilOp(sc.stencil_zpass_bf),
|
||||
ConvertStencilOp(sc.stencil_zfail_bf), static_cast<VkCompareOp>(dc.stencilfunc_bf)};
|
||||
r->stencil_dynamic_back = {sm.stencil_mask_bf,
|
||||
rc.stencil_clear_enable ? 0u : sm.stencil_writemask_bf,
|
||||
sm.stencil_testval_bf};
|
||||
} else {
|
||||
r->stencil_static_back = r->stencil_static_front;
|
||||
r->stencil_dynamic_back = r->stencil_dynamic_front;
|
||||
}
|
||||
}
|
||||
r->vaddr_num = has_stencil ? 2 : 1;
|
||||
r->vaddr[0] = r->depth_buffer_vaddr;
|
||||
r->size[0] = r->depth_buffer_size;
|
||||
if (has_stencil) {
|
||||
r->vaddr[1] = r->stencil_buffer_vaddr;
|
||||
r->size[1] = r->stencil_buffer_size;
|
||||
}
|
||||
DepthTargetInfo info {};
|
||||
info.address = r->depth_buffer_vaddr;
|
||||
info.size = r->depth_buffer_size;
|
||||
info.stencil_address = r->stencil_buffer_vaddr;
|
||||
info.stencil_size = r->stencil_buffer_size;
|
||||
info.htile_address = r->htile_buffer_vaddr;
|
||||
info.htile_size = r->htile_buffer_size;
|
||||
info.format = r->format;
|
||||
info.guest_format = guest_format;
|
||||
info.width = width;
|
||||
info.height = height;
|
||||
info.pitch = pitch;
|
||||
info.bytes_per_element = bytes;
|
||||
info.tile_mode = Prospero::GpuEnumValue(Prospero::TileMode::kDepth);
|
||||
info.depth_load_clear = r->depth_load_clear_enable;
|
||||
info.stencil_load_clear = rc.stencil_clear_enable;
|
||||
info.stencil_access =
|
||||
r->stencil_clear_enable ||
|
||||
(r->stencil_test_enable &&
|
||||
(stencil_face_accesses_attachment(r->stencil_static_front, r->stencil_dynamic_front) ||
|
||||
stencil_face_accesses_attachment(r->stencil_static_back, r->stencil_dynamic_back)));
|
||||
r->vulkan_buffer = cache->FindDepthTarget(buffer, g_render_ctx->GetGraphicCtx(), info);
|
||||
if (meta_clear && !cache->TouchMeta(z.htile_data_base_addr, z.depth_view.slice_start, false)) {
|
||||
DepthFatal("failed to consume HTile clear state");
|
||||
}
|
||||
}
|
||||
|
||||
void MarkRenderTargetGpuWritten(const RenderDepthInfo& target) {
|
||||
const bool with_depth = target.format != VK_FORMAT_UNDEFINED && target.vulkan_buffer != nullptr;
|
||||
|
||||
if (with_depth && !depth_attachment_read_only(&target)) {
|
||||
g_render_ctx->GetTextureCache()->MarkGpuWritten(target.vulkan_buffer);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
@@ -0,0 +1,303 @@
|
||||
#include "graphics/host_gpu/renderer/descriptorCache.h"
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/profiler.h"
|
||||
#include "graphics/host_gpu/renderer/renderContext.h"
|
||||
#include "graphics/shader/recompiler/ShaderIR.h"
|
||||
|
||||
#include <array>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
namespace {
|
||||
|
||||
using BindingKind = ShaderRecompiler::IR::DescriptorBindingKind;
|
||||
|
||||
VkShaderStageFlags StageFlags(DescriptorCache::Stage stage) {
|
||||
switch (stage) {
|
||||
case DescriptorCache::Stage::Vertex: return VK_SHADER_STAGE_VERTEX_BIT;
|
||||
case DescriptorCache::Stage::Pixel: return VK_SHADER_STAGE_FRAGMENT_BIT;
|
||||
case DescriptorCache::Stage::Compute: return VK_SHADER_STAGE_COMPUTE_BIT;
|
||||
default: EXIT("unknown descriptor stage\n");
|
||||
}
|
||||
}
|
||||
|
||||
bool IsSampledImage(BindingKind kind) {
|
||||
switch (kind) {
|
||||
case BindingKind::Sampled2D:
|
||||
case BindingKind::Sampled2DArray:
|
||||
case BindingKind::Sampled3D:
|
||||
case BindingKind::SampledUint2D:
|
||||
case BindingKind::SampledUint2DArray:
|
||||
case BindingKind::SampledUint3D: return true;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool IsStorageImage(BindingKind kind) {
|
||||
switch (kind) {
|
||||
case BindingKind::Storage2D:
|
||||
case BindingKind::Storage2DArray:
|
||||
case BindingKind::Storage3D:
|
||||
case BindingKind::StorageUint2D:
|
||||
case BindingKind::StorageUint2DArray:
|
||||
case BindingKind::StorageUint3D: return true;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
VkDescriptorType DescriptorType(BindingKind kind) {
|
||||
if (kind == BindingKind::Samplers) {
|
||||
return VK_DESCRIPTOR_TYPE_SAMPLER;
|
||||
}
|
||||
if (IsSampledImage(kind)) {
|
||||
return VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE;
|
||||
}
|
||||
if (IsStorageImage(kind)) {
|
||||
return VK_DESCRIPTOR_TYPE_STORAGE_IMAGE;
|
||||
}
|
||||
return VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
|
||||
}
|
||||
|
||||
uint32_t DescriptorCount(const ShaderRecompiler::IR::DescriptorBinding& binding) {
|
||||
return binding.resources.empty() ? 1u : static_cast<uint32_t>(binding.resources.size());
|
||||
}
|
||||
|
||||
std::vector<uint32_t> LayoutKey(DescriptorCache::Stage stage,
|
||||
const ShaderRecompiler::IR::Program& program) {
|
||||
std::vector<uint32_t> key;
|
||||
key.reserve(1u + program.bindings.descriptors.size() * 4u);
|
||||
key.push_back(static_cast<uint32_t>(stage));
|
||||
for (const auto& binding: program.bindings.descriptors) {
|
||||
key.push_back(static_cast<uint32_t>(binding.kind));
|
||||
key.push_back(binding.binding);
|
||||
key.push_back(DescriptorCount(binding));
|
||||
key.push_back(static_cast<uint32_t>(DescriptorType(binding.kind)));
|
||||
}
|
||||
return key;
|
||||
}
|
||||
|
||||
VkImageLayout SampledLayout(const VulkanImage* image) {
|
||||
EXIT_IF(image == nullptr);
|
||||
switch (image->layout) {
|
||||
case VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL:
|
||||
case VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL:
|
||||
case VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL: return VK_IMAGE_LAYOUT_GENERAL;
|
||||
default: return image->layout;
|
||||
}
|
||||
}
|
||||
|
||||
VkDescriptorBufferInfo BufferInfo(const BufferView& view) {
|
||||
EXIT_IF(view.buffer == nullptr || view.buffer->buffer == nullptr);
|
||||
return {view.buffer->buffer, view.offset, view.range};
|
||||
}
|
||||
|
||||
VkDescriptorImageInfo MakeDescriptorImageInfo(const DescriptorCache::TextureBinding& texture,
|
||||
VkImageLayout layout) {
|
||||
EXIT_IF(texture.image == nullptr || texture.view < 0 || texture.view >= VulkanImage::VIEW_MAX);
|
||||
const auto view = texture.image_view != nullptr ? texture.image_view
|
||||
: texture.image->image_view[texture.view];
|
||||
EXIT_IF(view == nullptr);
|
||||
return {nullptr, view, layout};
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void DescriptorCache::Init() {
|
||||
m_initialized = true;
|
||||
}
|
||||
|
||||
VkDescriptorSetLayout
|
||||
DescriptorCache::GetDescriptorSetLayoutInternal(GraphicContext* gctx, Stage stage,
|
||||
const ShaderRecompiler::IR::Program& program) {
|
||||
EXIT_IF(gctx == nullptr);
|
||||
Init();
|
||||
const auto key = LayoutKey(stage, program);
|
||||
if (const auto found = m_descriptor_set_layouts.find(key);
|
||||
found != m_descriptor_set_layouts.end()) {
|
||||
return found->second;
|
||||
}
|
||||
|
||||
std::vector<VkDescriptorSetLayoutBinding> bindings;
|
||||
bindings.reserve(program.bindings.descriptors.size());
|
||||
for (const auto& descriptor: program.bindings.descriptors) {
|
||||
bindings.push_back({descriptor.binding, DescriptorType(descriptor.kind),
|
||||
DescriptorCount(descriptor), StageFlags(stage), nullptr});
|
||||
}
|
||||
if (bindings.empty()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
VkDescriptorSetLayoutCreateInfo info {};
|
||||
info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
|
||||
info.bindingCount = static_cast<uint32_t>(bindings.size());
|
||||
info.pBindings = bindings.data();
|
||||
VkDescriptorSetLayout layout = nullptr;
|
||||
vkCreateDescriptorSetLayout(gctx->device, &info, nullptr, &layout);
|
||||
EXIT_NOT_IMPLEMENTED(layout == nullptr);
|
||||
m_descriptor_set_layouts.emplace(key, layout);
|
||||
return layout;
|
||||
}
|
||||
|
||||
void DescriptorCache::CreatePool(GraphicContext* gctx) {
|
||||
KYTY_PROFILER_FUNCTION();
|
||||
EXIT_IF(gctx == nullptr);
|
||||
constexpr uint32_t MaxSets = 512;
|
||||
const VkDescriptorPoolSize sizes[] = {
|
||||
{VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
|
||||
MaxSets * (ShaderRecompiler::IR::ShaderInfo::MaxBuffers +
|
||||
ShaderRecompiler::IR::ShaderInfo::MaxAddresses + 3u)},
|
||||
{VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, MaxSets * ShaderRecompiler::IR::ShaderInfo::MaxImages},
|
||||
{VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, MaxSets * ShaderRecompiler::IR::ShaderInfo::MaxImages},
|
||||
{VK_DESCRIPTOR_TYPE_SAMPLER, MaxSets * ShaderRecompiler::IR::ShaderInfo::MaxSamplers},
|
||||
};
|
||||
VkDescriptorPoolCreateInfo info {};
|
||||
info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
|
||||
info.poolSizeCount = static_cast<uint32_t>(std::size(sizes));
|
||||
info.pPoolSizes = sizes;
|
||||
info.maxSets = MaxSets;
|
||||
const auto pool_id = static_cast<int>(m_pools.size());
|
||||
auto& pool = m_pools.emplace_back();
|
||||
vkCreateDescriptorPool(gctx->device, &info, nullptr, &pool.pool);
|
||||
EXIT_NOT_IMPLEMENTED(pool.pool == nullptr);
|
||||
pool.next_free_pool = m_first_free_pool;
|
||||
m_first_free_pool = pool_id;
|
||||
}
|
||||
|
||||
VulkanDescriptorSet* DescriptorCache::Allocate(Stage stage,
|
||||
const ShaderRecompiler::IR::Program& program) {
|
||||
KYTY_PROFILER_FUNCTION();
|
||||
auto* gctx = g_render_ctx->GetGraphicCtx();
|
||||
EXIT_IF(gctx == nullptr);
|
||||
Common::LockGuard lock(m_mutex);
|
||||
const auto layout = GetDescriptorSetLayoutInternal(gctx, stage, program);
|
||||
EXIT_IF(layout == nullptr);
|
||||
auto& free_sets = m_free_sets_by_layout[layout];
|
||||
if (!free_sets.empty()) {
|
||||
auto* result = free_sets.back();
|
||||
free_sets.pop_back();
|
||||
return result;
|
||||
}
|
||||
|
||||
constexpr uint32_t Batch = 32;
|
||||
for (int attempt = 0; attempt < 2; attempt++) {
|
||||
for (int pool_id = m_first_free_pool; pool_id != -1;
|
||||
pool_id = m_pools[pool_id].next_free_pool) {
|
||||
auto& pool = m_pools[pool_id];
|
||||
std::array<VkDescriptorSetLayout, Batch> layouts;
|
||||
layouts.fill(layout);
|
||||
std::array<VkDescriptorSet, Batch> sets {};
|
||||
VkDescriptorSetAllocateInfo info {};
|
||||
info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
|
||||
info.descriptorPool = pool.pool;
|
||||
info.descriptorSetCount = Batch;
|
||||
info.pSetLayouts = layouts.data();
|
||||
if (vkAllocateDescriptorSets(gctx->device, &info, sets.data()) == VK_SUCCESS) {
|
||||
free_sets.reserve(free_sets.size() + Batch - 1u);
|
||||
for (uint32_t i = 0; i + 1u < Batch; i++) {
|
||||
free_sets.push_back(new VulkanDescriptorSet {sets[i], layout, pool_id});
|
||||
}
|
||||
return new VulkanDescriptorSet {sets.back(), layout, pool_id};
|
||||
}
|
||||
pool.free = false;
|
||||
m_first_free_pool = pool.next_free_pool;
|
||||
break;
|
||||
}
|
||||
CreatePool(gctx);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void DescriptorCache::Recycle(VulkanDescriptorSet* set) {
|
||||
EXIT_IF(set == nullptr || set->set == nullptr || set->layout == nullptr);
|
||||
Common::LockGuard lock(m_mutex);
|
||||
m_free_sets_by_layout[set->layout].push_back(set);
|
||||
}
|
||||
|
||||
VulkanDescriptorSet* DescriptorCache::GetDescriptor(Stage stage,
|
||||
const ShaderRecompiler::IR::Program& program,
|
||||
const NativeDescriptors& data) {
|
||||
KYTY_PROFILER_FUNCTION();
|
||||
EXIT_IF(data.buffers.size() != program.info.buffers.size() ||
|
||||
data.images.size() != program.info.images.size() ||
|
||||
data.samplers.size() != program.info.samplers.size() ||
|
||||
data.addresses.size() != program.info.addresses.size());
|
||||
auto* gctx = g_render_ctx->GetGraphicCtx();
|
||||
EXIT_IF(gctx == nullptr);
|
||||
auto* set = Allocate(stage, program);
|
||||
EXIT_NOT_IMPLEMENTED(set == nullptr);
|
||||
|
||||
const auto descriptor_count = program.info.buffers.size() + program.info.images.size() +
|
||||
program.info.samplers.size() + program.info.addresses.size() + 3u;
|
||||
std::vector<VkDescriptorBufferInfo> buffer_infos;
|
||||
std::vector<VkDescriptorImageInfo> image_infos;
|
||||
std::vector<VkWriteDescriptorSet> writes;
|
||||
buffer_infos.reserve(descriptor_count);
|
||||
image_infos.reserve(descriptor_count);
|
||||
writes.reserve(program.bindings.descriptors.size());
|
||||
|
||||
for (const auto& binding: program.bindings.descriptors) {
|
||||
VkWriteDescriptorSet write {};
|
||||
write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
|
||||
write.dstSet = set->set;
|
||||
write.dstBinding = binding.binding;
|
||||
write.descriptorType = DescriptorType(binding.kind);
|
||||
write.descriptorCount = DescriptorCount(binding);
|
||||
const auto buffer_start = buffer_infos.size();
|
||||
const auto image_start = image_infos.size();
|
||||
switch (binding.kind) {
|
||||
case BindingKind::Buffers:
|
||||
for (const auto resource: binding.resources) {
|
||||
buffer_infos.push_back(BufferInfo(data.buffers.at(resource)));
|
||||
}
|
||||
break;
|
||||
case BindingKind::AddressMemory:
|
||||
for (const auto resource: binding.resources) {
|
||||
buffer_infos.push_back(BufferInfo(data.addresses.at(resource)));
|
||||
}
|
||||
break;
|
||||
case BindingKind::FlattenedSrt:
|
||||
buffer_infos.push_back(BufferInfo(data.flattened_srt));
|
||||
break;
|
||||
case BindingKind::UserData: buffer_infos.push_back(BufferInfo(data.user_data)); break;
|
||||
case BindingKind::Gds: buffer_infos.push_back(BufferInfo(data.gds)); break;
|
||||
case BindingKind::Samplers:
|
||||
for (const auto resource: binding.resources) {
|
||||
const auto sampler = data.samplers.at(resource);
|
||||
EXIT_IF(sampler == nullptr);
|
||||
image_infos.push_back({sampler, nullptr, VK_IMAGE_LAYOUT_UNDEFINED});
|
||||
}
|
||||
break;
|
||||
default: {
|
||||
const auto layout = IsStorageImage(binding.kind) ? VK_IMAGE_LAYOUT_GENERAL
|
||||
: VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
for (const auto resource: binding.resources) {
|
||||
const auto& texture = data.images.at(resource);
|
||||
image_infos.push_back(MakeDescriptorImageInfo(
|
||||
texture,
|
||||
IsSampledImage(binding.kind) ? SampledLayout(texture.image) : layout));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (buffer_infos.size() != buffer_start) {
|
||||
write.pBufferInfo = buffer_infos.data() + buffer_start;
|
||||
}
|
||||
if (image_infos.size() != image_start) {
|
||||
write.pImageInfo = image_infos.data() + image_start;
|
||||
}
|
||||
writes.push_back(write);
|
||||
}
|
||||
vkUpdateDescriptorSets(gctx->device, static_cast<uint32_t>(writes.size()), writes.data(), 0,
|
||||
nullptr);
|
||||
return set;
|
||||
}
|
||||
|
||||
VkDescriptorSetLayout
|
||||
DescriptorCache::GetDescriptorSetLayout(Stage stage, const ShaderRecompiler::IR::Program& program) {
|
||||
auto* gctx = g_render_ctx->GetGraphicCtx();
|
||||
EXIT_IF(gctx == nullptr);
|
||||
Common::LockGuard lock(m_mutex);
|
||||
return GetDescriptorSetLayoutInternal(gctx, stage, program);
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
@@ -0,0 +1,118 @@
|
||||
#ifndef EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_DESCRIPTORCACHE_H_
|
||||
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_DESCRIPTORCACHE_H_
|
||||
|
||||
#include "common/abi.h"
|
||||
#include "common/assert.h"
|
||||
#include "common/common.h"
|
||||
#include "common/threads.h"
|
||||
#include "graphics/host_gpu/graphicContext.h"
|
||||
#include "graphics/shader/shaderBindings.h"
|
||||
|
||||
#include <map>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
#include <vulkan/vulkan_core.h>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
namespace ShaderRecompiler::IR {
|
||||
struct Program;
|
||||
}
|
||||
|
||||
class CommandBuffer;
|
||||
struct ShaderStageRuntime;
|
||||
|
||||
struct VulkanDescriptor {
|
||||
VkDescriptorSet descriptor_set = nullptr;
|
||||
};
|
||||
|
||||
struct VulkanDescriptorSet {
|
||||
VkDescriptorSet set = nullptr;
|
||||
VkDescriptorSetLayout layout = nullptr;
|
||||
int pool_id = -1;
|
||||
};
|
||||
|
||||
struct BufferView {
|
||||
VulkanBuffer* buffer = nullptr;
|
||||
VkDeviceSize offset = 0;
|
||||
VkDeviceSize range = VK_WHOLE_SIZE;
|
||||
};
|
||||
|
||||
struct ShaderAddressWriteRange {
|
||||
VulkanBuffer* buffer = nullptr;
|
||||
uint64_t address = 0;
|
||||
uint64_t size = 0;
|
||||
};
|
||||
|
||||
class DescriptorCache {
|
||||
public:
|
||||
enum class Stage { Unknown, Vertex, Pixel, Compute };
|
||||
|
||||
struct TextureBinding {
|
||||
VulkanImage* image = nullptr;
|
||||
int view = VulkanImage::VIEW_DEFAULT;
|
||||
VkImageView image_view = nullptr;
|
||||
};
|
||||
|
||||
enum class TextureVariant : int {
|
||||
Float2D = 0,
|
||||
Uint2D,
|
||||
FloatArray,
|
||||
UintArray,
|
||||
Float3D,
|
||||
Uint3D,
|
||||
};
|
||||
|
||||
struct NativeDescriptors {
|
||||
std::vector<BufferView> buffers;
|
||||
std::vector<TextureBinding> images;
|
||||
std::vector<VkSampler> samplers;
|
||||
std::vector<BufferView> addresses;
|
||||
BufferView gds;
|
||||
BufferView flattened_srt;
|
||||
BufferView user_data;
|
||||
};
|
||||
|
||||
DescriptorCache() { EXIT_NOT_IMPLEMENTED(!Common::Thread::IsMainThread()); }
|
||||
virtual ~DescriptorCache() { KYTY_NOT_IMPLEMENTED; }
|
||||
KYTY_CLASS_NO_COPY(DescriptorCache);
|
||||
|
||||
VkDescriptorSetLayout GetDescriptorSetLayout(Stage stage,
|
||||
const ShaderRecompiler::IR::Program& program);
|
||||
void Recycle(VulkanDescriptorSet* set);
|
||||
VulkanDescriptorSet* GetDescriptor(Stage stage, const ShaderRecompiler::IR::Program& program,
|
||||
const NativeDescriptors& descriptors);
|
||||
|
||||
private:
|
||||
struct Pool {
|
||||
VkDescriptorPool pool = nullptr;
|
||||
int next_free_pool = -1;
|
||||
bool free = true;
|
||||
};
|
||||
|
||||
void Init();
|
||||
void CreatePool(GraphicContext* gctx);
|
||||
VulkanDescriptorSet* Allocate(Stage stage, const ShaderRecompiler::IR::Program& program);
|
||||
VkDescriptorSetLayout
|
||||
GetDescriptorSetLayoutInternal(GraphicContext* gctx, Stage stage,
|
||||
const ShaderRecompiler::IR::Program& program);
|
||||
|
||||
Common::Mutex m_mutex;
|
||||
std::vector<Pool> m_pools;
|
||||
int m_first_free_pool = -1;
|
||||
std::unordered_map<VkDescriptorSetLayout, std::vector<VulkanDescriptorSet*>>
|
||||
m_free_sets_by_layout;
|
||||
std::map<std::vector<uint32_t>, VkDescriptorSetLayout> m_descriptor_set_layouts;
|
||||
bool m_initialized = false;
|
||||
};
|
||||
|
||||
const char* storage_usage_name(ShaderStorageUsage usage);
|
||||
VkImageAspectFlags DepthStencilAspectMask(VkFormat format);
|
||||
std::vector<ShaderAddressWriteRange>
|
||||
BindDescriptors(uint64_t submit_id, CommandBuffer* buffer, VkPipelineBindPoint pipeline_bind_point,
|
||||
VkPipelineLayout layout, const ShaderStageRuntime& runtime,
|
||||
VkShaderStageFlags vk_stage, DescriptorCache::Stage stage);
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
#endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_DESCRIPTORCACHE_H_
|
||||
@@ -0,0 +1,698 @@
|
||||
#include "graphics/host_gpu/renderer/descriptors.h"
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/common.h"
|
||||
#include "common/file.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/profiler.h"
|
||||
#include "common/stringUtils.h"
|
||||
#include "common/threads.h"
|
||||
#include "graphics/guest_gpu/gpu_defs.h"
|
||||
#include "graphics/guest_gpu/gpu_format.h"
|
||||
#include "graphics/guest_gpu/graphicsRun.h"
|
||||
#include "graphics/guest_gpu/hardwareContext.h"
|
||||
#include "graphics/guest_gpu/tile.h"
|
||||
#include "graphics/host_gpu/graphicContext.h"
|
||||
#include "graphics/host_gpu/hostMemory.h"
|
||||
#include "graphics/host_gpu/objects/textureCommon.h"
|
||||
#include "graphics/host_gpu/renderer/descriptorCache.h"
|
||||
#include "graphics/host_gpu/renderer/framebufferCache.h"
|
||||
#include "graphics/host_gpu/renderer/imageView.h"
|
||||
#include "graphics/host_gpu/renderer/render.h"
|
||||
#include "graphics/host_gpu/renderer/renderContext.h"
|
||||
#include "graphics/host_gpu/renderer/renderState.h"
|
||||
#include "graphics/host_gpu/renderer/shaderResourceBarrier.h"
|
||||
#include "graphics/host_gpu/utils.h"
|
||||
#include "graphics/host_gpu/vma.h"
|
||||
#include "graphics/presentation/displayBuffer.h"
|
||||
#include "graphics/shader/recompiler/BindingLayout.h"
|
||||
#include "graphics/shader/recompiler/ResourceMaterialization.h"
|
||||
#include "graphics/shader/recompiler/ShaderIR.h"
|
||||
#include "graphics/shader/shader.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <cstring>
|
||||
#include <fmt/format.h>
|
||||
#include <limits>
|
||||
#include <vulkan/vk_enum_string_helper.h>
|
||||
|
||||
#ifdef min
|
||||
#undef min
|
||||
#endif
|
||||
#ifdef max
|
||||
#undef max
|
||||
#endif
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
using TextureVariant = DescriptorCache::TextureVariant;
|
||||
|
||||
static void BindNullStorageBuffer(CommandBuffer* cmd_buffer, BufferView* dst) {
|
||||
EXIT_IF(cmd_buffer == nullptr || dst == nullptr);
|
||||
|
||||
dst->buffer =
|
||||
g_render_ctx->GetBufferCache()->ObtainNullBuffer(cmd_buffer, g_render_ctx->GetGraphicCtx());
|
||||
dst->offset = 0;
|
||||
dst->range = 16;
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(readability-function-cognitive-complexity)
|
||||
static const char* VulkanImageTypeName(VulkanImageType type) {
|
||||
switch (type) {
|
||||
case VulkanImageType::VideoOut: return "VideoOut";
|
||||
case VulkanImageType::DepthStencil: return "DepthStencil";
|
||||
case VulkanImageType::Texture: return "Texture";
|
||||
case VulkanImageType::StorageTexture: return "StorageTexture";
|
||||
case VulkanImageType::RenderTexture: return "RenderTexture";
|
||||
case VulkanImageType::Unknown:
|
||||
default: return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
VkImageAspectFlags DepthStencilAspectMask(VkFormat format) {
|
||||
auto ret = static_cast<VkImageAspectFlags>(VK_IMAGE_ASPECT_DEPTH_BIT);
|
||||
if (format == VK_FORMAT_D24_UNORM_S8_UINT || format == VK_FORMAT_D32_SFLOAT_S8_UINT) {
|
||||
ret |= VK_IMAGE_ASPECT_STENCIL_BIT;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int SampledArrayViewIndex(const VulkanImage* image, int view_index) {
|
||||
EXIT_IF(image == nullptr);
|
||||
|
||||
switch (view_index) {
|
||||
case VulkanImage::VIEW_DEFAULT: return VulkanImage::VIEW_DEFAULT_ARRAY;
|
||||
case VulkanImage::VIEW_BGRA: return VulkanImage::VIEW_BGRA_ARRAY;
|
||||
case VulkanImage::VIEW_DEPTH_TEXTURE: return VulkanImage::VIEW_DEPTH_TEXTURE_ARRAY;
|
||||
case VulkanImage::VIEW_STENCIL_TEXTURE: return VulkanImage::VIEW_STENCIL_TEXTURE_ARRAY;
|
||||
case VulkanImage::VIEW_R001: return VulkanImage::VIEW_R001_ARRAY;
|
||||
case VulkanImage::VIEW_RGB1: return VulkanImage::VIEW_RGB1_ARRAY;
|
||||
case VulkanImage::VIEW_R000: return VulkanImage::VIEW_R000_ARRAY;
|
||||
case VulkanImage::VIEW_RG01: return VulkanImage::VIEW_RG01_ARRAY;
|
||||
case VulkanImage::VIEW_000R: return VulkanImage::VIEW_000R_ARRAY;
|
||||
default: return view_index;
|
||||
}
|
||||
}
|
||||
|
||||
static int SelectSampledTextureArrayView(const VulkanImage* image, int base_view) {
|
||||
const int array_view = SampledArrayViewIndex(image, base_view);
|
||||
if (image->image_view[array_view] == nullptr) {
|
||||
EXIT("missing sampled array image view: image_type=%s base_view=%d array_view=%d "
|
||||
"layers=%u\n",
|
||||
VulkanImageTypeName(image->type), base_view, array_view, image->layers);
|
||||
}
|
||||
return array_view;
|
||||
}
|
||||
|
||||
static bool TextureVariantIsUint(TextureVariant variant) {
|
||||
return variant == TextureVariant::Uint2D || variant == TextureVariant::UintArray ||
|
||||
variant == TextureVariant::Uint3D;
|
||||
}
|
||||
|
||||
static bool TextureVariantIsArray(TextureVariant variant) {
|
||||
return variant == TextureVariant::FloatArray || variant == TextureVariant::UintArray;
|
||||
}
|
||||
|
||||
static bool TextureVariantIs3D(TextureVariant variant) {
|
||||
return variant == TextureVariant::Float3D || variant == TextureVariant::Uint3D;
|
||||
}
|
||||
|
||||
static int TextureVariantDefaultView(TextureVariant variant) {
|
||||
return TextureVariantIsArray(variant) ? VulkanImage::VIEW_DEFAULT_ARRAY
|
||||
: VulkanImage::VIEW_DEFAULT;
|
||||
}
|
||||
|
||||
static VulkanImage* GetDummySampledTexture(TextureVariant variant) {
|
||||
return g_render_ctx->GetTextureCache()->GetDummySampledTexture(TextureVariantIsUint(variant),
|
||||
TextureVariantIs3D(variant));
|
||||
}
|
||||
|
||||
static VulkanImage* GetDummyStorageTexture(TextureVariant variant) {
|
||||
return g_render_ctx->GetTextureCache()->GetDummyStorageTexture(TextureVariantIsUint(variant),
|
||||
TextureVariantIs3D(variant));
|
||||
}
|
||||
|
||||
static ShaderBufferResource
|
||||
NativeBufferDescriptor(const ShaderRecompiler::IR::DescriptorValue& descriptor) {
|
||||
ShaderBufferResource result;
|
||||
std::copy_n(descriptor.dwords.begin(), std::size(result.fields), result.fields);
|
||||
return result;
|
||||
}
|
||||
|
||||
static ShaderTextureResource
|
||||
NativeTextureDescriptor(const ShaderRecompiler::IR::DescriptorValue& descriptor) {
|
||||
ShaderTextureResource result;
|
||||
std::copy_n(descriptor.dwords.begin(), std::size(result.fields), result.fields);
|
||||
return result;
|
||||
}
|
||||
|
||||
static ShaderSamplerResource
|
||||
NativeSamplerDescriptor(const ShaderRecompiler::IR::DescriptorValue& descriptor) {
|
||||
ShaderSamplerResource result;
|
||||
std::copy_n(descriptor.dwords.begin(), std::size(result.fields), result.fields);
|
||||
return result;
|
||||
}
|
||||
|
||||
static BufferView NativeStorageBuffer(uint64_t submit_id, CommandBuffer* command_buffer,
|
||||
const ShaderBufferResource& descriptor,
|
||||
const ShaderRecompiler::IR::BufferResource& resource) {
|
||||
BufferView result;
|
||||
const auto address = descriptor.Base48();
|
||||
const auto stride = descriptor.Stride();
|
||||
const auto records = descriptor.NumRecords();
|
||||
if (stride != 0 && records > UINT64_MAX / stride) {
|
||||
EXIT("storage buffer descriptor footprint overflow\n");
|
||||
}
|
||||
const auto size = stride != 0 ? static_cast<uint64_t>(stride) * records : records;
|
||||
if (address == 0 || size == 0) {
|
||||
BindNullStorageBuffer(command_buffer, &result);
|
||||
return result;
|
||||
}
|
||||
auto* const ctx = g_render_ctx->GetGraphicCtx();
|
||||
const auto alignment = ctx->StorageMinAlignment();
|
||||
if (alignment == 0 || size > ctx->GetPhysicalDeviceProperties().limits.maxStorageBufferRange ||
|
||||
BufferCache::CACHING_PAGE_SIZE % alignment != 0) {
|
||||
EXIT("storage buffer range or device alignment is unsupported\n");
|
||||
}
|
||||
(void)submit_id;
|
||||
auto binding = g_render_ctx->GetBufferCache()->ObtainBuffer(
|
||||
command_buffer, ctx, address, size, resource.written, resource.read, resource.formatted);
|
||||
if (binding.second % alignment != 0) {
|
||||
EXIT("storage buffer binding is not device-aligned\n");
|
||||
}
|
||||
result.buffer = binding.first;
|
||||
result.offset = binding.second;
|
||||
result.range = static_cast<VkDeviceSize>(size);
|
||||
return result;
|
||||
}
|
||||
|
||||
static BufferView
|
||||
NativeAddressBuffer(uint64_t submit_id, CommandBuffer* command_buffer,
|
||||
const ShaderRecompiler::IR::AddressResource& resource,
|
||||
const ShaderRecompiler::IR::ResourceSnapshot::Address& address) {
|
||||
BufferView result;
|
||||
if (address.binding_base == 0) {
|
||||
BindNullStorageBuffer(command_buffer, &result);
|
||||
return result;
|
||||
}
|
||||
if (resource.written) {
|
||||
EXIT("writable address resources are unsupported\n");
|
||||
}
|
||||
const auto limit = resource.kind == ShaderRecompiler::IR::ResourceKind::Flat
|
||||
? ShaderRecompiler::IR::FlatAddressWindowSize
|
||||
: static_cast<uint64_t>(g_render_ctx->GetGraphicCtx()
|
||||
->GetPhysicalDeviceProperties()
|
||||
.limits.maxStorageBufferRange);
|
||||
uint64_t size = 0;
|
||||
const auto access = HostMemoryAccess::Read;
|
||||
if (!HostMemoryQueryRange(address.binding_base, limit, access, &size)) {
|
||||
EXIT("address resource is not host-accessible: base=0x%016" PRIx64 "\n",
|
||||
address.binding_base);
|
||||
}
|
||||
auto* const ctx = g_render_ctx->GetGraphicCtx();
|
||||
const auto alignment = ctx->StorageMinAlignment();
|
||||
if (alignment == 0 || size > ctx->GetPhysicalDeviceProperties().limits.maxStorageBufferRange ||
|
||||
BufferCache::GetBufferOffset(address.binding_base) % alignment != 0) {
|
||||
EXIT("address resource range or alignment is unsupported\n");
|
||||
}
|
||||
(void)submit_id;
|
||||
auto binding = g_render_ctx->GetBufferCache()->ObtainBuffer(command_buffer, ctx,
|
||||
address.binding_base, size);
|
||||
result.buffer = binding.first;
|
||||
result.offset = binding.second;
|
||||
result.range = static_cast<VkDeviceSize>(size);
|
||||
return result;
|
||||
}
|
||||
|
||||
static TextureVariant NativeTextureVariant(const ShaderRecompiler::IR::ImageResource& resource) {
|
||||
const bool uint_image = resource.kind == ShaderRecompiler::IR::ResourceKind::ImageUint ||
|
||||
resource.kind == ShaderRecompiler::IR::ResourceKind::StorageImageUint;
|
||||
switch (resource.dimension) {
|
||||
case ShaderRecompiler::Decoder::ImageDimension::Dim3D:
|
||||
return uint_image ? TextureVariant::Uint3D : TextureVariant::Float3D;
|
||||
case ShaderRecompiler::Decoder::ImageDimension::Dim2DArray:
|
||||
return uint_image ? TextureVariant::UintArray : TextureVariant::FloatArray;
|
||||
default: return uint_image ? TextureVariant::Uint2D : TextureVariant::Float2D;
|
||||
}
|
||||
}
|
||||
|
||||
static bool IsSupportedSampledColorResource(const ShaderRecompiler::IR::ImageResource& resource) {
|
||||
return resource.kind == ShaderRecompiler::IR::ResourceKind::Image &&
|
||||
resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2D &&
|
||||
resource.mip_mode == ShaderRecompiler::IR::ImageMipMode::None && resource.read &&
|
||||
!resource.written && !resource.atomic && !resource.depth_compare;
|
||||
}
|
||||
|
||||
static bool IsSupportedSampledDepthFormat(VkFormat image_format, uint32_t guest_format,
|
||||
VkFormat view_format) {
|
||||
const bool d16 = image_format == VK_FORMAT_D16_UNORM &&
|
||||
guest_format == Prospero::GpuEnumValue(Prospero::BufferFormat::k16UNorm) &&
|
||||
view_format == VK_FORMAT_R16_UNORM;
|
||||
const bool d32 =
|
||||
(image_format == VK_FORMAT_D32_SFLOAT || image_format == VK_FORMAT_D32_SFLOAT_S8_UINT) &&
|
||||
guest_format == Prospero::GpuEnumValue(Prospero::BufferFormat::k32Float) &&
|
||||
view_format == VK_FORMAT_R32_SFLOAT;
|
||||
return d16 || d32;
|
||||
}
|
||||
|
||||
bool IsSupportedDepthTargetDescriptor(const ShaderTextureResource& descriptor,
|
||||
const VulkanImage& image) {
|
||||
const auto width = static_cast<uint32_t>(descriptor.Width5()) + 1u;
|
||||
const auto height = static_cast<uint32_t>(descriptor.Height5()) + 1u;
|
||||
const auto pitch = TileGetTexturePitch(descriptor.Format(), width, 1, descriptor.TileMode());
|
||||
return image.type == VulkanImageType::DepthStencil && image.layers == 1 &&
|
||||
width == image.extent.width && height == image.extent.height &&
|
||||
descriptor.Depth() == 0 && descriptor.BaseLevel() == 0 && descriptor.LastLevel() == 0 &&
|
||||
descriptor.MaxMip() == 0 && descriptor.MinLod() == 0 && descriptor.BaseArray5() == 0 &&
|
||||
descriptor.TileMode() == Prospero::GpuEnumValue(Prospero::TileMode::kDepth) &&
|
||||
descriptor.Type() == Prospero::GpuEnumValue(Prospero::ImageType::kColor2D) &&
|
||||
descriptor.BCSwizzle() == 0 && !descriptor.MsaaDepth() && pitch >= width &&
|
||||
pitch == image.guest_pitch;
|
||||
}
|
||||
|
||||
static bool IsSupportedDepthTextureEncoding(const ShaderTextureResource& descriptor) {
|
||||
constexpr uint32_t field1_reserved_mask = 0x200fff00u;
|
||||
constexpr uint32_t field2_reserved_mask = 0xf0003000u;
|
||||
constexpr uint32_t field3_base = 0x91800000u;
|
||||
constexpr uint32_t field5_expected = 0x00700000u;
|
||||
return (descriptor.fields[1] & field1_reserved_mask) == 0 &&
|
||||
(descriptor.fields[2] & field2_reserved_mask) == 0 &&
|
||||
descriptor.fields[3] == (field3_base | descriptor.DstSelXYZW()) &&
|
||||
descriptor.fields[4] == 0 && descriptor.fields[5] == field5_expected &&
|
||||
descriptor.fields[6] == 0 && descriptor.fields[7] == 0;
|
||||
}
|
||||
|
||||
static void ValidateDepthTargetBinding(const ShaderRecompiler::IR::ImageResource& resource,
|
||||
const ShaderTextureResource& descriptor,
|
||||
const VulkanImage* image, VkFormat view_format,
|
||||
uint64_t size) {
|
||||
const bool resource_ok = IsSupportedSampledDepthResource(resource);
|
||||
const bool descriptor_ok =
|
||||
image != nullptr && IsSupportedDepthTargetDescriptor(descriptor, *image);
|
||||
const bool encoding_ok = IsSupportedDepthTextureEncoding(descriptor);
|
||||
const bool format_ok = image != nullptr && IsSupportedSampledDepthFormat(
|
||||
image->format, descriptor.Format(), view_format);
|
||||
if (resource_ok && descriptor_ok && encoding_ok && format_ok && size != 0) {
|
||||
return;
|
||||
}
|
||||
const auto descriptor_pitch =
|
||||
TileGetTexturePitch(descriptor.Format(), static_cast<uint32_t>(descriptor.Width5()) + 1u, 1,
|
||||
descriptor.TileMode());
|
||||
EXIT("unsupported sampled depth target: resource=%d descriptor=%d encoding=%d format=%d "
|
||||
"image_format=%d view_format=%d descriptor_pitch=%u target_pitch=%u "
|
||||
"addr=0x%016" PRIx64 " size=0x%016" PRIx64 "\n",
|
||||
resource_ok, descriptor_ok, encoding_ok, format_ok,
|
||||
image == nullptr ? static_cast<int>(VK_FORMAT_UNDEFINED) : static_cast<int>(image->format),
|
||||
static_cast<int>(view_format), descriptor_pitch,
|
||||
image == nullptr ? 0u : image->guest_pitch, descriptor.Base40(), size);
|
||||
}
|
||||
|
||||
static bool IsSupportedStorageTextureDescriptor(const ShaderRecompiler::IR::ImageResource& resource,
|
||||
const ShaderTextureResource& descriptor) {
|
||||
const auto tile = descriptor.TileMode();
|
||||
const bool is_2d = resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2D &&
|
||||
descriptor.Type() == Prospero::GpuEnumValue(Prospero::ImageType::kColor2D) &&
|
||||
descriptor.Depth() == 0;
|
||||
const bool is_2d_array =
|
||||
resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2DArray &&
|
||||
descriptor.Type() == Prospero::GpuEnumValue(Prospero::ImageType::kColor2DArray) &&
|
||||
descriptor.BaseArray5() <= descriptor.Depth();
|
||||
const bool is_3d = resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim3D &&
|
||||
descriptor.Type() == Prospero::GpuEnumValue(Prospero::ImageType::kColor3D);
|
||||
const bool supported_tile = tile == Prospero::GpuEnumValue(Prospero::TileMode::kLinear) ||
|
||||
tile == Prospero::GpuEnumValue(Prospero::TileMode::kRenderTarget);
|
||||
const bool supported_swizzle =
|
||||
IsSupportedStorageSwizzle(descriptor.Format(), descriptor.DstSelXYZW()) &&
|
||||
(descriptor.DstSelXYZW() == DstSel(4, 5, 6, 7) || !resource.read);
|
||||
return (is_2d || is_2d_array || is_3d) && supported_tile && descriptor.BaseLevel() == 0 &&
|
||||
descriptor.LastLevel() == 0 && descriptor.MaxMip() == 0 && descriptor.MinLod() == 0 &&
|
||||
descriptor.BaseArray5() == 0 && supported_swizzle && descriptor.BCSwizzle() == 0 &&
|
||||
!descriptor.MsaaDepth();
|
||||
}
|
||||
|
||||
static bool IsSupportedStorageTextureEncoding(const ShaderTextureResource& descriptor) {
|
||||
constexpr uint32_t field1_reserved_mask = 0x200fff00u;
|
||||
constexpr uint32_t field2_reserved_mask = 0xf0003000u;
|
||||
constexpr uint32_t field5_expected = 0x00700000u;
|
||||
const uint32_t expected_field3 = descriptor.DstSelXYZW() |
|
||||
(static_cast<uint32_t>(descriptor.TileMode()) << 20u) |
|
||||
(static_cast<uint32_t>(descriptor.Type()) << 28u);
|
||||
return (descriptor.fields[1] & field1_reserved_mask) == 0 &&
|
||||
(descriptor.fields[2] & field2_reserved_mask) == 0 &&
|
||||
descriptor.fields[3] == expected_field3 && descriptor.fields[4] == descriptor.Depth() &&
|
||||
descriptor.fields[5] == field5_expected && descriptor.fields[6] == 0 &&
|
||||
descriptor.fields[7] == 0;
|
||||
}
|
||||
|
||||
void ValidateStorageTexture(const ShaderRecompiler::IR::ImageResource& resource,
|
||||
const ShaderTextureResource& descriptor, uint64_t size) {
|
||||
const auto format = descriptor.Format();
|
||||
const bool resource_ok = IsSupportedStorageImageResource(resource);
|
||||
const bool descriptor_ok = IsSupportedStorageTextureDescriptor(resource, descriptor);
|
||||
const bool encoding_ok = IsSupportedStorageTextureEncoding(descriptor);
|
||||
const bool uint_resource =
|
||||
resource.kind == ShaderRecompiler::IR::ResourceKind::StorageImageUint;
|
||||
const bool format_ok = Prospero::IsSupportedTextureFormat(format) &&
|
||||
uint_resource == Prospero::IsUintTextureFormat(format);
|
||||
if (resource_ok && descriptor_ok && encoding_ok && format_ok && size != 0) {
|
||||
return;
|
||||
}
|
||||
EXIT("unsupported storage texture: resource=%d descriptor=%d encoding=%d format=%d "
|
||||
"addr=0x%016" PRIx64 " size=0x%016" PRIx64
|
||||
" extent=%ux%ux%u type=%u format=%u tile=%u swizzle=0x%03x read=%d written=%d\n",
|
||||
resource_ok, descriptor_ok, encoding_ok, format_ok, descriptor.Base40(), size,
|
||||
static_cast<uint32_t>(descriptor.Width5()) + 1u,
|
||||
static_cast<uint32_t>(descriptor.Height5()) + 1u,
|
||||
static_cast<uint32_t>(descriptor.Depth()) + 1u, descriptor.Type(), format,
|
||||
descriptor.TileMode(), descriptor.DstSelXYZW(), resource.read, resource.written);
|
||||
}
|
||||
|
||||
void ValidateMetadataReuseTexture(const ShaderRecompiler::IR::ImageResource& resource,
|
||||
const ShaderTextureResource& descriptor, uint64_t size) {
|
||||
constexpr uint32_t field1_reserved = 0x200fff00u;
|
||||
constexpr uint32_t field2_reserved = 0xf0003000u;
|
||||
const auto format = descriptor.Format();
|
||||
if (!IsSupportedSampledColorResource(resource) || size == 0 ||
|
||||
(descriptor.fields[1] & field1_reserved) != 0 ||
|
||||
(descriptor.fields[2] & field2_reserved) != 0 || descriptor.fields[3] != 0x90500facu ||
|
||||
descriptor.fields[4] != 0 || descriptor.fields[5] != 0x00700000u ||
|
||||
descriptor.fields[6] != 0 || descriptor.fields[7] != 0 ||
|
||||
!Prospero::IsSupportedTextureFormat(format) || Prospero::IsUintTextureFormat(format)) {
|
||||
EXIT("unsupported storage texture descriptor encoding\n");
|
||||
}
|
||||
}
|
||||
|
||||
static DescriptorCache::TextureBinding
|
||||
NativeTexture(uint64_t submit_id, CommandBuffer* command_buffer,
|
||||
const ShaderRecompiler::IR::ImageResource& resource,
|
||||
const ShaderRecompiler::IR::DescriptorValue& value) {
|
||||
auto descriptor = NativeTextureDescriptor(value);
|
||||
const bool storage = resource.kind == ShaderRecompiler::IR::ResourceKind::StorageImage ||
|
||||
resource.kind == ShaderRecompiler::IR::ResourceKind::StorageImageUint;
|
||||
const auto variant = NativeTextureVariant(resource);
|
||||
if (storage) {
|
||||
ValidateStorageImageResource(resource);
|
||||
}
|
||||
if (descriptor.IsNull()) {
|
||||
return storage ? DescriptorCache::TextureBinding {GetDummyStorageTexture(variant),
|
||||
TextureVariantDefaultView(variant)}
|
||||
: DescriptorCache::TextureBinding {GetDummySampledTexture(variant),
|
||||
TextureVariantDefaultView(variant)};
|
||||
}
|
||||
const auto address = descriptor.Base40();
|
||||
const auto width = static_cast<uint32_t>(descriptor.Width5()) + 1u;
|
||||
const auto height = static_cast<uint32_t>(descriptor.Height5()) + 1u;
|
||||
const auto base_level = descriptor.BaseLevel();
|
||||
const auto last_level = descriptor.LastLevel();
|
||||
const auto levels = static_cast<uint32_t>(descriptor.MaxMip()) + 1u;
|
||||
if (base_level > last_level || last_level >= levels) {
|
||||
EXIT("unsupported texture mip view: base=%u last=%u levels=%u\n", base_level, last_level,
|
||||
levels);
|
||||
}
|
||||
const auto view_levels = last_level - base_level + 1u;
|
||||
const auto depth = static_cast<uint32_t>(descriptor.Depth()) + 1u;
|
||||
const auto tile = descriptor.TileMode();
|
||||
const auto format = descriptor.Format();
|
||||
const auto view_format = TextureGetFormat(format, storage ? TextureFormatUsage::Storage
|
||||
: TextureFormatUsage::Sampled);
|
||||
const auto type = static_cast<Prospero::ImageType>(descriptor.Type());
|
||||
const auto pitch = TileGetTexturePitch(format, width, levels, tile);
|
||||
const auto swizzle = descriptor.DstSelXYZW();
|
||||
TileSizeAlign size;
|
||||
TileGetTextureTotalSize(format, width, height, depth, pitch, levels, tile,
|
||||
type == Prospero::ImageType::kColor3D, &size);
|
||||
EXIT_NOT_IMPLEMENTED(size.size == 0 ||
|
||||
(address & (static_cast<uint64_t>(size.align) - 1u)) != 0);
|
||||
if (storage) {
|
||||
ValidateStorageTexture(resource, descriptor, size.size);
|
||||
g_render_ctx->GetBufferCache()->ValidateGpuAccess(address, size.size, resource.read,
|
||||
resource.written);
|
||||
}
|
||||
|
||||
VulkanImage* image = nullptr;
|
||||
int view = VulkanImage::VIEW_DEFAULT;
|
||||
VkImageView image_view = nullptr;
|
||||
const bool check_depth = static_cast<Prospero::TileMode>(tile) == Prospero::TileMode::kDepth ||
|
||||
descriptor.MsaaDepth();
|
||||
if (image == nullptr) {
|
||||
if (check_depth) {
|
||||
image = g_render_ctx->GetTextureCache()->FindDepthTargetByRange(address, size.size);
|
||||
} else {
|
||||
image = g_render_ctx->GetTextureCache()->FindRenderTargetByRange(command_buffer,
|
||||
address, size.size);
|
||||
}
|
||||
if (image != nullptr) {
|
||||
if (check_depth) {
|
||||
ValidateDepthTargetBinding(resource, descriptor, image, view_format, size.size);
|
||||
view = SelectSampledDepthView(image->format, view_format, swizzle);
|
||||
if (graphics_debug_dump_enabled()) {
|
||||
static std::atomic_uint log_count {0};
|
||||
if (log_count.fetch_add(1, std::memory_order_relaxed) < 64) {
|
||||
LOGF("SampledDepthTarget: bind addr=0x%016" PRIx64 " size=0x%016" PRIx64
|
||||
" format=%d view=%d\n",
|
||||
address, size.size, static_cast<int>(image->format), view);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!(storage ? IsSupportedStorageImageResource(resource)
|
||||
: IsSupportedSampledColorResource(resource)) ||
|
||||
image->type != VulkanImageType::RenderTexture || image->layers != 1 ||
|
||||
width != image->extent.width || height != image->extent.height || depth != 1 ||
|
||||
(storage ? levels != 1 || base_level != 0
|
||||
: levels != image->mip_levels || base_level >= levels) ||
|
||||
descriptor.BaseArray5() != 0 || type != Prospero::ImageType::kColor2D) {
|
||||
EXIT("unsupported cached render-target image view: storage=%d resource=%u"
|
||||
" image_type=%u layers=%u extent=%ux%u/%ux%u depth=%u"
|
||||
" levels=%u/%u base_level=%u base_array=%u descriptor_type=%u\n",
|
||||
storage, static_cast<uint32_t>(resource.kind),
|
||||
static_cast<uint32_t>(image->type), image->layers, width, height,
|
||||
image->extent.width, image->extent.height, depth, levels,
|
||||
image->mip_levels, base_level, descriptor.BaseArray5(),
|
||||
static_cast<uint32_t>(type));
|
||||
}
|
||||
view = storage ? SelectStorageColorView(image->format, view_format, swizzle)
|
||||
: SelectSampledColorView(image->format, view_format, swizzle);
|
||||
if (storage) {
|
||||
image_view = g_render_ctx->GetTextureCache()->GetRenderTargetStorageView(
|
||||
g_render_ctx->GetGraphicCtx(),
|
||||
static_cast<RenderTextureVulkanImage*>(image), view_format, base_level,
|
||||
view_levels);
|
||||
} else {
|
||||
image_view = g_render_ctx->GetTextureCache()->GetRenderTargetSampledView(
|
||||
g_render_ctx->GetGraphicCtx(),
|
||||
static_cast<RenderTextureVulkanImage*>(image), view_format, view,
|
||||
base_level, view_levels);
|
||||
}
|
||||
}
|
||||
if (image_view == nullptr && image->image_view[view] == nullptr) {
|
||||
EXIT("required cached texture image view is missing\n");
|
||||
}
|
||||
if (storage) {
|
||||
g_render_ctx->GetTextureCache()->MarkGpuWritten(image);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (image == nullptr) {
|
||||
const auto video = Presentation::DisplayBufferFind(address);
|
||||
if (video.image != nullptr) {
|
||||
if (storage) {
|
||||
EXIT("storage access to a video-out surface is unsupported\n");
|
||||
}
|
||||
image = video.image;
|
||||
if (!storage && swizzle == DstSel(6, 5, 4, 7)) {
|
||||
view = VulkanImage::VIEW_BGRA;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (image == nullptr) {
|
||||
auto* texture_cache = g_render_ctx->GetTextureCache();
|
||||
const bool metadata_read = texture_cache->HasMetaOverlap(address, size.size);
|
||||
if (storage && metadata_read) {
|
||||
EXIT("storage texture overlaps surface metadata\n");
|
||||
}
|
||||
if (!storage && metadata_read) {
|
||||
ValidateMetadataReuseTexture(resource, descriptor, size.size);
|
||||
}
|
||||
(void)submit_id;
|
||||
(void)command_buffer;
|
||||
ImageInfo info {};
|
||||
info.address = address;
|
||||
info.size = size.size;
|
||||
info.format = format;
|
||||
info.width = width;
|
||||
info.height = height;
|
||||
info.pitch = pitch;
|
||||
info.base_level = base_level;
|
||||
info.levels = levels;
|
||||
info.view_levels = view_levels;
|
||||
info.tile = tile;
|
||||
info.swizzle = swizzle;
|
||||
info.depth = depth;
|
||||
info.type = descriptor.Type();
|
||||
info.base_array = descriptor.BaseArray5();
|
||||
if (storage) {
|
||||
image = texture_cache->FindStorageTexture(command_buffer, g_render_ctx->GetGraphicCtx(),
|
||||
info);
|
||||
view = VulkanImage::VIEW_DEFAULT;
|
||||
} else {
|
||||
image = texture_cache->FindTexture(command_buffer, g_render_ctx->GetGraphicCtx(), info,
|
||||
metadata_read);
|
||||
if (image->type == VulkanImageType::StorageTexture) {
|
||||
image_view = texture_cache->GetStorageTextureSampledView(
|
||||
g_render_ctx->GetGraphicCtx(), static_cast<StorageTextureVulkanImage*>(image),
|
||||
info);
|
||||
}
|
||||
}
|
||||
}
|
||||
EXIT_NOT_IMPLEMENTED(image == nullptr);
|
||||
if (resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim2DArray) {
|
||||
view = SelectSampledTextureArrayView(image, view);
|
||||
}
|
||||
return {image, view, image_view};
|
||||
}
|
||||
|
||||
static VkSampler NativeSampler(const ShaderRecompiler::IR::Program& program, uint32_t index,
|
||||
const ShaderRecompiler::IR::DescriptorValue& value) {
|
||||
auto descriptor = NativeSamplerDescriptor(value);
|
||||
const bool depth_compare = std::any_of(program.info.sampled_pairs.begin(),
|
||||
program.info.sampled_pairs.end(), [&](const auto& pair) {
|
||||
return pair.sampler == index &&
|
||||
pair.image < program.info.images.size() &&
|
||||
program.info.images[pair.image].depth_compare;
|
||||
});
|
||||
if (!depth_compare) {
|
||||
descriptor.fields[0] &= ~(0x7u << 12u);
|
||||
}
|
||||
return g_render_ctx->GetSamplerCache()->GetSampler(descriptor);
|
||||
}
|
||||
|
||||
static BufferView NativeUpload(CommandBuffer* command_buffer, std::span<const uint32_t> data) {
|
||||
EXIT_IF(data.empty());
|
||||
BufferView result;
|
||||
EXIT_IF(!g_render_ctx->GetBufferCache()->UploadHostData(
|
||||
command_buffer, g_render_ctx->GetGraphicCtx(), data.data(), data.size_bytes(), 256,
|
||||
&result.buffer, &result.offset, &result.range));
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<ShaderAddressWriteRange>
|
||||
BindDescriptors(uint64_t submit_id, CommandBuffer* buffer, VkPipelineBindPoint pipeline_bind_point,
|
||||
VkPipelineLayout layout, const ShaderStageRuntime& runtime,
|
||||
VkShaderStageFlags vk_stage, DescriptorCache::Stage stage) {
|
||||
KYTY_PROFILER_FUNCTION();
|
||||
EXIT_IF(buffer == nullptr || !runtime);
|
||||
const auto& program = *runtime.program;
|
||||
const auto& snapshot = *runtime.resources;
|
||||
std::string error;
|
||||
if (!ShaderRecompiler::IR::ValidateResourceSpecialization(program, snapshot, &error)) {
|
||||
EXIT("invalid native shader runtime snapshot: %s\n", error.c_str());
|
||||
}
|
||||
auto* vk_buffer = buffer->GetPool()->buffers[buffer->GetIndex()];
|
||||
const auto shader_stages = ShaderPipelineStages(vk_stage);
|
||||
|
||||
DescriptorCache::NativeDescriptors descriptors;
|
||||
descriptors.buffers.reserve(program.info.buffers.size());
|
||||
for (uint32_t i = 0; i < program.info.buffers.size(); i++) {
|
||||
descriptors.buffers.push_back(
|
||||
NativeStorageBuffer(submit_id, buffer, NativeBufferDescriptor(snapshot.buffers[i]),
|
||||
program.info.buffers[i]));
|
||||
}
|
||||
descriptors.images.reserve(program.info.images.size());
|
||||
for (uint32_t i = 0; i < program.info.images.size(); i++) {
|
||||
const auto kind = program.info.images[i].kind;
|
||||
if ((kind == ShaderRecompiler::IR::ResourceKind::StorageImage ||
|
||||
kind == ShaderRecompiler::IR::ResourceKind::StorageImageUint) &&
|
||||
vk_stage != VK_SHADER_STAGE_COMPUTE_BIT) {
|
||||
EXIT("storage images are unsupported outside compute shaders\n");
|
||||
}
|
||||
descriptors.images.push_back(
|
||||
NativeTexture(submit_id, buffer, program.info.images[i], snapshot.images[i]));
|
||||
}
|
||||
descriptors.samplers.reserve(program.info.samplers.size());
|
||||
for (uint32_t i = 0; i < program.info.samplers.size(); i++) {
|
||||
descriptors.samplers.push_back(NativeSampler(program, i, snapshot.samplers[i]));
|
||||
}
|
||||
descriptors.addresses.reserve(program.info.addresses.size());
|
||||
std::vector<ShaderAddressWriteRange> address_writes;
|
||||
for (uint32_t i = 0; i < program.info.addresses.size(); i++) {
|
||||
auto view = NativeAddressBuffer(submit_id, buffer, program.info.addresses[i],
|
||||
snapshot.addresses[i]);
|
||||
descriptors.addresses.push_back(view);
|
||||
if (program.info.addresses[i].written && snapshot.addresses[i].binding_base != 0) {
|
||||
EXIT_IF(view.buffer == nullptr || view.offset != 0 || view.range == VK_WHOLE_SIZE ||
|
||||
view.range == 0);
|
||||
address_writes.push_back({view.buffer, snapshot.addresses[i].binding_base,
|
||||
static_cast<uint64_t>(view.range)});
|
||||
}
|
||||
}
|
||||
if (ShaderRecompiler::IR::FindBinding(
|
||||
program.bindings, ShaderRecompiler::IR::DescriptorBindingKind::FlattenedSrt) !=
|
||||
nullptr) {
|
||||
descriptors.flattened_srt = NativeUpload(buffer, snapshot.flattened_srt);
|
||||
}
|
||||
|
||||
std::vector<uint32_t> user_data;
|
||||
user_data.reserve(program.bindings.user_data_registers.size());
|
||||
for (const auto reg: program.bindings.user_data_registers) {
|
||||
user_data.push_back(snapshot.user_data[reg - program.user_data_base]);
|
||||
}
|
||||
if (ShaderRecompiler::IR::FindBinding(
|
||||
program.bindings, ShaderRecompiler::IR::DescriptorBindingKind::UserData) != nullptr) {
|
||||
descriptors.user_data = NativeUpload(buffer, user_data);
|
||||
}
|
||||
if (ShaderRecompiler::IR::FindBinding(
|
||||
program.bindings, ShaderRecompiler::IR::DescriptorBindingKind::Gds) != nullptr) {
|
||||
descriptors.gds.buffer =
|
||||
g_render_ctx->GetGdsBuffer()->GetBuffer(g_render_ctx->GetGraphicCtx());
|
||||
const auto barrier = MakeGdsDependency(*descriptors.gds.buffer);
|
||||
vkCmdPipelineBarrier(vk_buffer,
|
||||
VK_PIPELINE_STAGE_HOST_BIT | VK_PIPELINE_STAGE_TRANSFER_BIT |
|
||||
VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT |
|
||||
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
|
||||
shader_stages, 0, 0, nullptr, 1, &barrier, 0, nullptr);
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < program.info.images.size(); i++) {
|
||||
auto* image = descriptors.images[i].image;
|
||||
const auto& resource = program.info.images[i];
|
||||
if (resource.kind == ShaderRecompiler::IR::ResourceKind::Image ||
|
||||
resource.kind == ShaderRecompiler::IR::ResourceKind::ImageUint) {
|
||||
switch (image->type) {
|
||||
case VulkanImageType::DepthStencil:
|
||||
GraphicsRenderDepthStencilBarrier(vk_buffer, image);
|
||||
break;
|
||||
case VulkanImageType::RenderTexture:
|
||||
case VulkanImageType::StorageTexture:
|
||||
GraphicsRenderTextureBarrier(vk_buffer, image);
|
||||
break;
|
||||
case VulkanImageType::VideoOut:
|
||||
GraphicsRenderColorImageBarrier(vk_buffer, image, RENDER_COLOR_IMAGE_LAYOUT);
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
} else {
|
||||
const auto barrier =
|
||||
MakeStorageImageDependency(*image, resource.read, resource.written);
|
||||
vkCmdPipelineBarrier(vk_buffer, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, shader_stages, 0, 0,
|
||||
nullptr, 0, nullptr, 1, &barrier);
|
||||
image->layout = VK_IMAGE_LAYOUT_GENERAL;
|
||||
}
|
||||
}
|
||||
|
||||
if (!program.bindings.descriptors.empty()) {
|
||||
auto* set = g_render_ctx->GetDescriptorCache()->GetDescriptor(stage, program, descriptors);
|
||||
EXIT_IF(set == nullptr);
|
||||
vkCmdBindDescriptorSets(vk_buffer, pipeline_bind_point, layout,
|
||||
program.bindings.descriptor_set, 1, &set->set, 0, nullptr);
|
||||
buffer->RecycleDescriptorAfterFence(set);
|
||||
}
|
||||
if (program.bindings.push_constant_size != 0) {
|
||||
EXIT_IF(program.bindings.push_constant_size != user_data.size() * sizeof(uint32_t));
|
||||
vkCmdPushConstants(vk_buffer, layout, vk_stage, program.bindings.push_constant_offset,
|
||||
program.bindings.push_constant_size, user_data.data());
|
||||
}
|
||||
return address_writes;
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
@@ -0,0 +1,22 @@
|
||||
#ifndef EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_DESCRIPTORS_H_
|
||||
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_DESCRIPTORS_H_
|
||||
|
||||
#include "graphics/shader/recompiler/ShaderIR.h"
|
||||
#include "graphics/shader/shaderBindings.h"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
struct VulkanImage;
|
||||
|
||||
[[nodiscard]] bool IsSupportedDepthTargetDescriptor(const ShaderTextureResource& descriptor,
|
||||
const VulkanImage& image);
|
||||
void ValidateMetadataReuseTexture(const ShaderRecompiler::IR::ImageResource& resource,
|
||||
const ShaderTextureResource& descriptor, uint64_t size);
|
||||
void ValidateStorageTexture(const ShaderRecompiler::IR::ImageResource& resource,
|
||||
const ShaderTextureResource& descriptor, uint64_t size);
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
#endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_DESCRIPTORS_H_
|
||||
@@ -0,0 +1,422 @@
|
||||
#include "graphics/host_gpu/renderer/framebufferCache.h"
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/profiler.h"
|
||||
#include "graphics/host_gpu/renderer/renderContext.h"
|
||||
#include "graphics/host_gpu/utils.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <vulkan/vk_enum_string_helper.h>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
VulkanFramebuffer* FramebufferCache::CreateFramebuffer(RenderColorInfo* colors,
|
||||
uint32_t requested_color_count,
|
||||
RenderDepthInfo* depth) {
|
||||
KYTY_PROFILER_FUNCTION();
|
||||
|
||||
Common::LockGuard lock(m_mutex);
|
||||
|
||||
EXIT_IF(colors == nullptr);
|
||||
EXIT_IF(depth == nullptr);
|
||||
EXIT_IF(g_render_ctx == nullptr);
|
||||
EXIT_IF(requested_color_count > RENDER_COLOR_ATTACHMENTS_MAX);
|
||||
|
||||
bool with_depth = (depth->format != VK_FORMAT_UNDEFINED && depth->vulkan_buffer != nullptr);
|
||||
bool with_color[RENDER_COLOR_ATTACHMENTS_MAX] = {};
|
||||
uint32_t color_count = 0;
|
||||
VulkanImage* first_color = nullptr;
|
||||
VkExtent2D first_color_extent = {};
|
||||
for (uint32_t i = 0; i < requested_color_count; i++) {
|
||||
with_color[i] = (colors[i].vulkan_buffer != nullptr);
|
||||
if (!with_color[i]) {
|
||||
break;
|
||||
}
|
||||
if (first_color == nullptr) {
|
||||
first_color = colors[i].vulkan_buffer;
|
||||
first_color_extent = colors[i].extent;
|
||||
} else if (colors[i].extent.width != first_color_extent.width ||
|
||||
colors[i].extent.height != first_color_extent.height) {
|
||||
LOGF("Framebuffer: temporary: dropping mismatched MRT%u attachment color0=%ux%u "
|
||||
"color%u=%ux%u\n",
|
||||
i, first_color_extent.width, first_color_extent.height, i, colors[i].extent.width,
|
||||
colors[i].extent.height);
|
||||
with_color[i] = false;
|
||||
break;
|
||||
}
|
||||
color_count++;
|
||||
}
|
||||
VkImageLayout color_layout[RENDER_COLOR_ATTACHMENTS_MAX] = {};
|
||||
for (auto& layout: color_layout) {
|
||||
layout = RENDER_COLOR_IMAGE_LAYOUT;
|
||||
}
|
||||
auto depth_layout = (with_depth ? depth_attachment_layout(depth)
|
||||
: VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL);
|
||||
auto depth_read_only =
|
||||
(with_depth && depth_layout == VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL);
|
||||
|
||||
if (with_depth && first_color != nullptr &&
|
||||
(first_color_extent.width != depth->vulkan_buffer->extent.width ||
|
||||
first_color_extent.height != depth->vulkan_buffer->extent.height)) {
|
||||
static std::atomic<uint32_t> log_count {0};
|
||||
if (log_count.fetch_add(1, std::memory_order_relaxed) < 16) {
|
||||
LOGF("Framebuffer: temporary: dropping mismatched PS5 depth attachment color=%ux%u "
|
||||
"depth=%ux%u format=%s\n",
|
||||
first_color_extent.width, first_color_extent.height,
|
||||
depth->vulkan_buffer->extent.width, depth->vulkan_buffer->extent.height,
|
||||
string_VkFormat(depth->format));
|
||||
}
|
||||
depth->format = VK_FORMAT_UNDEFINED;
|
||||
depth->vulkan_buffer = nullptr;
|
||||
depth->depth_test_enable = false;
|
||||
depth->depth_write_enable = false;
|
||||
depth->depth_bounds_test_enable = false;
|
||||
depth->stencil_test_enable = false;
|
||||
depth->depth_clear_enable = false;
|
||||
depth->depth_load_clear_enable = false;
|
||||
depth->stencil_clear_enable = false;
|
||||
with_depth = false;
|
||||
depth_layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
|
||||
depth_read_only = false;
|
||||
}
|
||||
|
||||
for (auto& f: m_framebuffers) {
|
||||
bool color_match = (f.framebuffer != nullptr);
|
||||
for (uint32_t i = 0; color_match && i < RENDER_COLOR_ATTACHMENTS_MAX; i++) {
|
||||
const uint64_t image_id =
|
||||
(i < color_count && with_color[i] ? colors[i].vulkan_buffer->memory.unique_id : 0);
|
||||
color_match =
|
||||
color_match && f.image_id[i] == image_id &&
|
||||
f.color_view[i] ==
|
||||
(i < color_count && with_color[i] ? colors[i].vulkan_view : nullptr) &&
|
||||
f.color_clear_enable[i] ==
|
||||
(i < color_count && with_color[i] && colors[i].color_clear_enable) &&
|
||||
f.color_layout[i] == color_layout[i];
|
||||
}
|
||||
if (color_match &&
|
||||
f.depth_id == (with_depth ? depth->vulkan_buffer->memory.unique_id : 0) &&
|
||||
f.depth_clear_enable == depth->depth_load_clear_enable &&
|
||||
f.stencil_clear_enable == depth->stencil_clear_enable &&
|
||||
f.depth_read_only == depth_read_only) {
|
||||
return f.framebuffer;
|
||||
}
|
||||
}
|
||||
|
||||
auto* framebuffer = new VulkanFramebuffer;
|
||||
framebuffer->render_pass = nullptr;
|
||||
framebuffer->framebuffer = nullptr;
|
||||
for (uint32_t i = 0; i < RENDER_COLOR_ATTACHMENTS_MAX; i++) {
|
||||
framebuffer->color_layout[i] = color_layout[i];
|
||||
}
|
||||
framebuffer->depth_layout = depth_layout;
|
||||
|
||||
auto* gctx = g_render_ctx->GetGraphicCtx();
|
||||
|
||||
EXIT_IF(gctx == nullptr);
|
||||
|
||||
if (!with_depth && color_count == 0) {
|
||||
LOGF("Framebuffer: warning: no color or depth attachment\n");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
EXIT_NOT_IMPLEMENTED(!with_depth && color_count == 0);
|
||||
EXIT_NOT_IMPLEMENTED(with_depth && first_color != nullptr &&
|
||||
(first_color_extent.width != depth->vulkan_buffer->extent.width ||
|
||||
first_color_extent.height != depth->vulkan_buffer->extent.height));
|
||||
|
||||
if (first_color == nullptr) {
|
||||
first_color = CreateDummyBuffer(VK_FORMAT_B8G8R8A8_SRGB, depth->vulkan_buffer->extent.width,
|
||||
depth->vulkan_buffer->extent.height);
|
||||
first_color_extent = first_color->extent;
|
||||
}
|
||||
|
||||
VkAttachmentDescription attachments[RENDER_COLOR_ATTACHMENTS_MAX + 1] = {};
|
||||
for (uint32_t i = 0; i < color_count; i++) {
|
||||
attachments[i].flags = 0;
|
||||
attachments[i].format = colors[i].format;
|
||||
attachments[i].samples = VK_SAMPLE_COUNT_1_BIT;
|
||||
attachments[i].loadOp = (colors[i].color_clear_enable ? VK_ATTACHMENT_LOAD_OP_CLEAR
|
||||
: VK_ATTACHMENT_LOAD_OP_LOAD);
|
||||
attachments[i].storeOp = VK_ATTACHMENT_STORE_OP_STORE;
|
||||
attachments[i].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
|
||||
attachments[i].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
|
||||
attachments[i].initialLayout = color_layout[i];
|
||||
attachments[i].finalLayout = RENDER_COLOR_IMAGE_LAYOUT;
|
||||
}
|
||||
|
||||
const uint32_t depth_attachment = color_count;
|
||||
attachments[depth_attachment].flags = 0;
|
||||
attachments[depth_attachment].format = depth->format;
|
||||
attachments[depth_attachment].samples = VK_SAMPLE_COUNT_1_BIT;
|
||||
attachments[depth_attachment].loadOp =
|
||||
(depth->depth_load_clear_enable ? VK_ATTACHMENT_LOAD_OP_CLEAR : VK_ATTACHMENT_LOAD_OP_LOAD);
|
||||
attachments[depth_attachment].storeOp = VK_ATTACHMENT_STORE_OP_STORE;
|
||||
attachments[depth_attachment].stencilLoadOp =
|
||||
(depth->stencil_clear_enable ? VK_ATTACHMENT_LOAD_OP_CLEAR : VK_ATTACHMENT_LOAD_OP_LOAD);
|
||||
attachments[depth_attachment].stencilStoreOp = VK_ATTACHMENT_STORE_OP_STORE;
|
||||
attachments[depth_attachment].initialLayout = depth_layout;
|
||||
attachments[depth_attachment].finalLayout = depth_layout;
|
||||
|
||||
VkAttachmentReference color_attachment_ref[RENDER_COLOR_ATTACHMENTS_MAX] = {};
|
||||
for (uint32_t i = 0; i < RENDER_COLOR_ATTACHMENTS_MAX; i++) {
|
||||
color_attachment_ref[i].attachment = (i < color_count ? i : VK_ATTACHMENT_UNUSED);
|
||||
color_attachment_ref[i].layout = RENDER_COLOR_IMAGE_LAYOUT;
|
||||
}
|
||||
|
||||
VkAttachmentReference depth_attachment_ref {};
|
||||
depth_attachment_ref.attachment = depth_attachment;
|
||||
depth_attachment_ref.layout = depth_layout;
|
||||
|
||||
VkSubpassDescription subpass {};
|
||||
subpass.flags = 0;
|
||||
subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
|
||||
subpass.inputAttachmentCount = 0;
|
||||
subpass.pInputAttachments = nullptr;
|
||||
subpass.colorAttachmentCount = color_count;
|
||||
subpass.pColorAttachments = (color_count > 0 ? color_attachment_ref : nullptr);
|
||||
subpass.pResolveAttachments = nullptr;
|
||||
subpass.pDepthStencilAttachment = (with_depth ? &depth_attachment_ref : nullptr);
|
||||
subpass.preserveAttachmentCount = 0;
|
||||
subpass.pPreserveAttachments = nullptr;
|
||||
|
||||
const auto attachment_stage_mask = static_cast<VkPipelineStageFlags>(
|
||||
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT |
|
||||
VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT);
|
||||
const auto attachment_access_mask = static_cast<VkAccessFlags>(
|
||||
VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT |
|
||||
VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT);
|
||||
|
||||
VkSubpassDependency dependencies[2] = {};
|
||||
dependencies[0].srcSubpass = VK_SUBPASS_EXTERNAL;
|
||||
dependencies[0].dstSubpass = 0;
|
||||
dependencies[0].srcStageMask = VK_PIPELINE_STAGE_ALL_COMMANDS_BIT;
|
||||
dependencies[0].dstStageMask = attachment_stage_mask;
|
||||
dependencies[0].srcAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT;
|
||||
dependencies[0].dstAccessMask = attachment_access_mask;
|
||||
dependencies[0].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
|
||||
|
||||
dependencies[1].srcSubpass = 0;
|
||||
dependencies[1].dstSubpass = VK_SUBPASS_EXTERNAL;
|
||||
dependencies[1].srcStageMask = attachment_stage_mask;
|
||||
dependencies[1].dstStageMask = VK_PIPELINE_STAGE_ALL_COMMANDS_BIT;
|
||||
dependencies[1].srcAccessMask = attachment_access_mask;
|
||||
dependencies[1].dstAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT;
|
||||
dependencies[1].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
|
||||
|
||||
VkRenderPassCreateInfo render_pass_info {};
|
||||
render_pass_info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
|
||||
render_pass_info.pNext = nullptr;
|
||||
render_pass_info.flags = 0;
|
||||
render_pass_info.attachmentCount = color_count + (with_depth ? 1u : 0u);
|
||||
render_pass_info.pAttachments = attachments;
|
||||
render_pass_info.subpassCount = 1;
|
||||
render_pass_info.pSubpasses = &subpass;
|
||||
render_pass_info.dependencyCount = 2;
|
||||
render_pass_info.pDependencies = dependencies;
|
||||
|
||||
auto result =
|
||||
vkCreateRenderPass(gctx->device, &render_pass_info, nullptr, &framebuffer->render_pass);
|
||||
EXIT_NOT_IMPLEMENTED(result != VK_SUCCESS);
|
||||
|
||||
VkFormat color_formats[RENDER_COLOR_ATTACHMENTS_MAX] = {};
|
||||
for (uint32_t i = 0; i < color_count; i++) {
|
||||
color_formats[i] = colors[i].format;
|
||||
}
|
||||
framebuffer->render_pass_id =
|
||||
render_pass_compat_id(color_count, color_formats, with_depth, depth->format, depth_layout);
|
||||
|
||||
EXIT_NOT_IMPLEMENTED(framebuffer->render_pass == nullptr);
|
||||
|
||||
VkImageView views[RENDER_COLOR_ATTACHMENTS_MAX + 1] = {};
|
||||
for (uint32_t i = 0; i < color_count; i++) {
|
||||
if (colors[i].vulkan_view == nullptr) {
|
||||
EXIT("Framebuffer: color attachment view is missing at slot %u\n", i);
|
||||
}
|
||||
views[i] = colors[i].vulkan_view;
|
||||
}
|
||||
if (with_depth) {
|
||||
views[color_count] = depth->vulkan_buffer->image_view[VulkanImage::VIEW_DEFAULT];
|
||||
}
|
||||
|
||||
VkFramebufferCreateInfo framebuffer_info {};
|
||||
framebuffer_info.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
|
||||
framebuffer_info.pNext = nullptr;
|
||||
framebuffer_info.flags = 0;
|
||||
framebuffer_info.renderPass = framebuffer->render_pass;
|
||||
framebuffer_info.attachmentCount = color_count + (with_depth ? 1u : 0u);
|
||||
framebuffer_info.pAttachments = views;
|
||||
framebuffer_info.width = first_color_extent.width;
|
||||
framebuffer_info.height = first_color_extent.height;
|
||||
framebuffer_info.layers = 1;
|
||||
|
||||
result =
|
||||
vkCreateFramebuffer(gctx->device, &framebuffer_info, nullptr, &framebuffer->framebuffer);
|
||||
EXIT_NOT_IMPLEMENTED(result != VK_SUCCESS);
|
||||
|
||||
EXIT_NOT_IMPLEMENTED(framebuffer->framebuffer == nullptr);
|
||||
|
||||
Framebuffer fnew;
|
||||
fnew.framebuffer = framebuffer;
|
||||
for (uint32_t i = 0; i < RENDER_COLOR_ATTACHMENTS_MAX; i++) {
|
||||
fnew.image_id[i] =
|
||||
(i < color_count && with_color[i] ? colors[i].vulkan_buffer->memory.unique_id : 0);
|
||||
fnew.color_view[i] = (i < color_count && with_color[i] ? colors[i].vulkan_view : nullptr);
|
||||
fnew.color_clear_enable[i] =
|
||||
(i < color_count && with_color[i] && colors[i].color_clear_enable);
|
||||
fnew.color_layout[i] = color_layout[i];
|
||||
}
|
||||
fnew.depth_id = (with_depth ? depth->vulkan_buffer->memory.unique_id : 0);
|
||||
fnew.depth_clear_enable = depth->depth_load_clear_enable;
|
||||
fnew.stencil_clear_enable = depth->stencil_clear_enable;
|
||||
fnew.depth_read_only = depth_read_only;
|
||||
|
||||
bool updated = false;
|
||||
|
||||
for (auto& f: m_framebuffers) {
|
||||
if (f.framebuffer == nullptr) {
|
||||
f = fnew;
|
||||
updated = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!updated) {
|
||||
m_framebuffers.push_back(fnew);
|
||||
}
|
||||
|
||||
return framebuffer;
|
||||
}
|
||||
|
||||
void FramebufferCache::FreeFramebufferByColor(VulkanImage* image) {
|
||||
EXIT_IF(g_render_ctx == nullptr);
|
||||
EXIT_IF(image == nullptr);
|
||||
|
||||
Common::LockGuard lock(m_mutex);
|
||||
|
||||
for (auto& f: m_framebuffers) {
|
||||
bool uses_image = false;
|
||||
for (auto image_id: f.image_id) {
|
||||
if (image_id == image->memory.unique_id) {
|
||||
uses_image = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (f.framebuffer != nullptr && uses_image) {
|
||||
auto* gctx = g_render_ctx->GetGraphicCtx();
|
||||
|
||||
EXIT_IF(gctx == nullptr);
|
||||
|
||||
vkDestroyFramebuffer(gctx->device, f.framebuffer->framebuffer, nullptr);
|
||||
|
||||
vkDestroyRenderPass(gctx->device, f.framebuffer->render_pass, nullptr);
|
||||
|
||||
delete f.framebuffer;
|
||||
|
||||
f.framebuffer = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FramebufferCache::FreeFramebufferByDepth(DepthStencilVulkanImage* image) {
|
||||
EXIT_IF(g_render_ctx == nullptr);
|
||||
EXIT_IF(image == nullptr);
|
||||
|
||||
Common::LockGuard lock(m_mutex);
|
||||
|
||||
for (auto& f: m_framebuffers) {
|
||||
if (f.framebuffer != nullptr && f.depth_id == image->memory.unique_id) {
|
||||
auto* gctx = g_render_ctx->GetGraphicCtx();
|
||||
|
||||
EXIT_IF(gctx == nullptr);
|
||||
|
||||
vkDestroyFramebuffer(gctx->device, f.framebuffer->framebuffer, nullptr);
|
||||
|
||||
vkDestroyRenderPass(gctx->device, f.framebuffer->render_pass, nullptr);
|
||||
|
||||
delete f.framebuffer;
|
||||
|
||||
f.framebuffer = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
VideoOutVulkanImage* FramebufferCache::CreateDummyBuffer(VkFormat format, uint32_t width,
|
||||
uint32_t height) {
|
||||
for (auto* b: m_dummy_buffers) {
|
||||
if (b->extent.width == width && b->extent.height == height && b->format == format) {
|
||||
return b;
|
||||
}
|
||||
}
|
||||
|
||||
auto* ctx = g_render_ctx->GetGraphicCtx();
|
||||
|
||||
EXIT_IF(ctx == nullptr);
|
||||
|
||||
auto* vk_obj = new VideoOutVulkanImage;
|
||||
|
||||
vk_obj->extent.width = width;
|
||||
vk_obj->extent.height = height;
|
||||
vk_obj->format = format;
|
||||
vk_obj->image = nullptr;
|
||||
vk_obj->layout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||||
|
||||
UtilResetImageViews(vk_obj);
|
||||
|
||||
VkImageCreateInfo image_info {};
|
||||
image_info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
|
||||
image_info.pNext = nullptr;
|
||||
image_info.flags = 0;
|
||||
image_info.imageType = VK_IMAGE_TYPE_2D;
|
||||
image_info.extent.width = vk_obj->extent.width;
|
||||
image_info.extent.height = vk_obj->extent.height;
|
||||
image_info.extent.depth = 1;
|
||||
image_info.mipLevels = 1;
|
||||
image_info.arrayLayers = 1;
|
||||
image_info.format = vk_obj->format;
|
||||
image_info.tiling = VK_IMAGE_TILING_OPTIMAL;
|
||||
image_info.initialLayout = vk_obj->layout;
|
||||
image_info.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
|
||||
image_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
||||
image_info.samples = VK_SAMPLE_COUNT_1_BIT;
|
||||
|
||||
VulkanMemory mem;
|
||||
|
||||
mem.property = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
|
||||
|
||||
bool created = VulkanCreateImage(ctx, &image_info, vk_obj, &mem);
|
||||
EXIT_NOT_IMPLEMENTED(!created);
|
||||
|
||||
vk_obj->memory = mem;
|
||||
|
||||
VkImageViewCreateInfo create_info {};
|
||||
create_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
|
||||
create_info.pNext = nullptr;
|
||||
create_info.flags = 0;
|
||||
create_info.image = vk_obj->image;
|
||||
create_info.viewType = VK_IMAGE_VIEW_TYPE_2D;
|
||||
create_info.format = vk_obj->format;
|
||||
create_info.components.r = VK_COMPONENT_SWIZZLE_IDENTITY;
|
||||
create_info.components.g = VK_COMPONENT_SWIZZLE_IDENTITY;
|
||||
create_info.components.b = VK_COMPONENT_SWIZZLE_IDENTITY;
|
||||
create_info.components.a = VK_COMPONENT_SWIZZLE_IDENTITY;
|
||||
create_info.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
create_info.subresourceRange.baseArrayLayer = 0;
|
||||
create_info.subresourceRange.baseMipLevel = 0;
|
||||
create_info.subresourceRange.layerCount = 1;
|
||||
create_info.subresourceRange.levelCount = 1;
|
||||
|
||||
vkCreateImageView(ctx->device, &create_info, nullptr,
|
||||
&vk_obj->image_view[VulkanImage::VIEW_DEFAULT]);
|
||||
|
||||
EXIT_NOT_IMPLEMENTED(vk_obj->image_view[VulkanImage::VIEW_DEFAULT] == nullptr);
|
||||
|
||||
UtilSetImageLayoutOptimal(vk_obj);
|
||||
|
||||
m_dummy_buffers.push_back(vk_obj);
|
||||
|
||||
return vk_obj;
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
@@ -0,0 +1,80 @@
|
||||
#ifndef EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_FRAMEBUFFERCACHE_H_
|
||||
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_FRAMEBUFFERCACHE_H_
|
||||
|
||||
#include "common/abi.h"
|
||||
#include "common/assert.h"
|
||||
#include "common/common.h"
|
||||
#include "common/threads.h"
|
||||
#include "graphics/host_gpu/graphicContext.h"
|
||||
#include "graphics/host_gpu/renderer/renderState.h"
|
||||
|
||||
#include <vector>
|
||||
#include <vulkan/vulkan_core.h>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
static constexpr VkImageLayout RENDER_COLOR_IMAGE_LAYOUT = VK_IMAGE_LAYOUT_GENERAL;
|
||||
|
||||
struct VulkanFramebuffer {
|
||||
VkRenderPass render_pass = nullptr;
|
||||
uint64_t render_pass_id = 0;
|
||||
VkFramebuffer framebuffer = nullptr;
|
||||
VkImageLayout color_layout[RENDER_COLOR_ATTACHMENTS_MAX] = {};
|
||||
VkImageLayout depth_layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
|
||||
};
|
||||
|
||||
inline uint64_t render_pass_compat_id(uint32_t color_count, const VkFormat* color_formats,
|
||||
bool with_depth, VkFormat depth_format,
|
||||
VkImageLayout depth_layout) {
|
||||
uint64_t id = 0xcbf29ce484222325ull;
|
||||
auto mix = [&id](uint64_t v) {
|
||||
id ^= v;
|
||||
id *= 0x100000001b3ull;
|
||||
};
|
||||
|
||||
mix(color_count);
|
||||
for (uint32_t i = 0; i < color_count; i++) {
|
||||
mix(static_cast<uint32_t>(color_formats[i]));
|
||||
}
|
||||
mix(with_depth ? 1u : 0u);
|
||||
mix(static_cast<uint32_t>(depth_format));
|
||||
mix(static_cast<uint32_t>(depth_layout));
|
||||
mix(VK_SAMPLE_COUNT_1_BIT);
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
class FramebufferCache {
|
||||
public:
|
||||
FramebufferCache() { EXIT_NOT_IMPLEMENTED(!Common::Thread::IsMainThread()); }
|
||||
virtual ~FramebufferCache() { KYTY_NOT_IMPLEMENTED; }
|
||||
KYTY_CLASS_NO_COPY(FramebufferCache);
|
||||
|
||||
VulkanFramebuffer* CreateFramebuffer(RenderColorInfo* colors, uint32_t color_count,
|
||||
RenderDepthInfo* depth);
|
||||
void FreeFramebufferByColor(VulkanImage* image);
|
||||
void FreeFramebufferByDepth(DepthStencilVulkanImage* image);
|
||||
|
||||
private:
|
||||
VideoOutVulkanImage* CreateDummyBuffer(VkFormat format, uint32_t width, uint32_t height);
|
||||
|
||||
struct Framebuffer {
|
||||
VulkanFramebuffer* framebuffer = nullptr;
|
||||
uint64_t image_id[RENDER_COLOR_ATTACHMENTS_MAX] = {};
|
||||
VkImageView color_view[RENDER_COLOR_ATTACHMENTS_MAX] = {};
|
||||
uint64_t depth_id = 0;
|
||||
bool color_clear_enable[RENDER_COLOR_ATTACHMENTS_MAX] = {};
|
||||
VkImageLayout color_layout[RENDER_COLOR_ATTACHMENTS_MAX] = {};
|
||||
bool depth_clear_enable = false;
|
||||
bool stencil_clear_enable = false;
|
||||
bool depth_read_only = false;
|
||||
};
|
||||
|
||||
Common::Mutex m_mutex;
|
||||
std::vector<Framebuffer> m_framebuffers;
|
||||
std::vector<VideoOutVulkanImage*> m_dummy_buffers;
|
||||
};
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
#endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_FRAMEBUFFERCACHE_H_
|
||||
@@ -0,0 +1,70 @@
|
||||
#include "graphics/host_gpu/renderer/gdsBuffer.h"
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "graphics/host_gpu/utils.h"
|
||||
#include "graphics/host_gpu/vma.h"
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
void GdsBuffer::Init(GraphicContext* ctx) {
|
||||
if (m_buffer == nullptr) {
|
||||
m_buffer = std::make_unique<VulkanBuffer>();
|
||||
|
||||
m_buffer->usage = VK_BUFFER_USAGE_STORAGE_BUFFER_BIT;
|
||||
m_buffer->memory.property = static_cast<uint32_t>(VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) |
|
||||
VK_MEMORY_PROPERTY_HOST_COHERENT_BIT |
|
||||
VK_MEMORY_PROPERTY_HOST_CACHED_BIT;
|
||||
VulkanCreateBuffer(ctx, DW_SIZE * 4, m_buffer.get());
|
||||
}
|
||||
}
|
||||
|
||||
void GdsBuffer::Clear(GraphicContext* ctx, uint64_t dw_offset, uint32_t dw_num,
|
||||
uint32_t clear_value) {
|
||||
EXIT_IF(ctx == nullptr);
|
||||
|
||||
Common::LockGuard lock(m_mutex);
|
||||
|
||||
Init(ctx);
|
||||
|
||||
EXIT_NOT_IMPLEMENTED(dw_offset >= DW_SIZE);
|
||||
EXIT_NOT_IMPLEMENTED(dw_offset + dw_num > DW_SIZE);
|
||||
|
||||
void* data = nullptr;
|
||||
VulkanMapMemory(ctx, &m_buffer->memory, &data);
|
||||
|
||||
for (uint32_t i = 0; i < dw_num; i++) {
|
||||
static_cast<uint32_t*>(data)[dw_offset + i] = clear_value;
|
||||
}
|
||||
|
||||
VulkanUnmapMemory(ctx, &m_buffer->memory);
|
||||
}
|
||||
|
||||
void GdsBuffer::Read(GraphicContext* ctx, uint32_t* dst, uint32_t dw_offset, uint32_t dw_size) {
|
||||
EXIT_IF(dst == nullptr);
|
||||
|
||||
Common::LockGuard lock(m_mutex);
|
||||
|
||||
Init(ctx);
|
||||
|
||||
EXIT_NOT_IMPLEMENTED(dw_offset >= DW_SIZE);
|
||||
EXIT_NOT_IMPLEMENTED(dw_offset + dw_size > DW_SIZE);
|
||||
|
||||
void* data = nullptr;
|
||||
VulkanMapMemory(ctx, &m_buffer->memory, &data);
|
||||
|
||||
for (uint32_t i = 0; i < dw_size; i++) {
|
||||
dst[i] = static_cast<uint32_t*>(data)[dw_offset + i];
|
||||
}
|
||||
|
||||
VulkanUnmapMemory(ctx, &m_buffer->memory);
|
||||
}
|
||||
|
||||
VulkanBuffer* GdsBuffer::GetBuffer(GraphicContext* ctx) {
|
||||
Common::LockGuard lock(m_mutex);
|
||||
|
||||
Init(ctx);
|
||||
|
||||
return m_buffer.get();
|
||||
}
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
@@ -0,0 +1,36 @@
|
||||
#ifndef EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_GDSBUFFER_H_
|
||||
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_GDSBUFFER_H_
|
||||
|
||||
#include "common/abi.h"
|
||||
#include "common/assert.h"
|
||||
#include "common/common.h"
|
||||
#include "common/threads.h"
|
||||
#include "graphics/host_gpu/graphicContext.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace Libs::Graphics {
|
||||
|
||||
class GdsBuffer {
|
||||
public:
|
||||
GdsBuffer() { EXIT_NOT_IMPLEMENTED(!Common::Thread::IsMainThread()); }
|
||||
virtual ~GdsBuffer() { KYTY_NOT_IMPLEMENTED; }
|
||||
KYTY_CLASS_NO_COPY(GdsBuffer);
|
||||
|
||||
void Clear(GraphicContext* ctx, uint64_t dw_offset, uint32_t dw_num, uint32_t clear_value);
|
||||
void Read(GraphicContext* ctx, uint32_t* dst, uint32_t dw_offset, uint32_t dw_size);
|
||||
|
||||
VulkanBuffer* GetBuffer(GraphicContext* ctx);
|
||||
|
||||
private:
|
||||
static constexpr uint64_t DW_SIZE = 0x3000;
|
||||
|
||||
void Init(GraphicContext* ctx);
|
||||
|
||||
Common::Mutex m_mutex;
|
||||
std::unique_ptr<VulkanBuffer> m_buffer;
|
||||
};
|
||||
|
||||
} // namespace Libs::Graphics
|
||||
|
||||
#endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_GDSBUFFER_H_
|
||||
Loaded 100 of 427 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user