Merge pull request #175 from SysRay/features

Features -> main
This commit is contained in:
SysRay 2024-06-01 17:09:37 +02:00 committed by GitHub
commit 156bb3a36d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
725 changed files with 14239 additions and 129404 deletions

View File

@ -8,10 +8,10 @@ AlignConsecutiveAssignments: Consecutive
AlignConsecutiveBitFields: Consecutive
AlignConsecutiveDeclarations: Consecutive
AlignEscapedNewlines: Right
AlignOperands: Align
AlignOperands: Align
AlignTrailingComments: true
AllowAllArgumentsOnNextLine: true
AllowAllConstructorInitializersOnNextLine: true
PackConstructorInitializers: NextLine
AllowAllParametersOfDeclarationOnNextLine: true
AllowShortEnumsOnASingleLine: true
AllowShortBlocksOnASingleLine: Never
@ -57,14 +57,12 @@ BreakConstructorInitializersBeforeComma: false
BreakConstructorInitializers: BeforeColon
BreakAfterJavaFieldAnnotations: false
BreakStringLiterals: true
ColumnLimit: 160
CommentPragmas: '^ IWYU pragma:'
ColumnLimit: 160
CompactNamespaces: false
ConstructorInitializerAllOnOneLineOrOnePerLine: false
ConstructorInitializerIndentWidth: 4
ContinuationIndentWidth: 4
Cpp11BracedListStyle: true
DeriveLineEnding: true
DerivePointerAlignment: false
DisableFormat: false
SeparateDefinitionBlocks: Always
@ -73,40 +71,17 @@ 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)/'
- Regex: '^<'
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: '^<'
Priority: 7
SortPriority: 0
CaseSensitive: false
- Regex: '.*'
Priority: 3
Priority: 1
SortPriority: 0
CaseSensitive: false
IncludeIsMainRegex: '$'
@ -117,21 +92,16 @@ IndentGotoLabels: true
IndentPPDirectives: None
IndentExternBlock: AfterExternBlock
IndentRequires: false
IndentWidth: 2
IndentWidth: 2
IndentWrappedFunctionNames: false
InsertTrailingCommas: None
JavaScriptQuotes: Leave
JavaScriptWrapImports: true
KeepEmptyLinesAtTheStartOfBlocks: true
MacroBlockBegin: ''
MacroBlockEnd: ''
MacroBlockEnd: ''
MaxEmptyLinesToKeep: 1
NamespaceIndentation: None
ObjCBinPackProtocolList: Auto
ObjCBlockIndentWidth: 2
ObjCBreakBeforeNestedBlockParam: true
ObjCSpaceAfterProperty: false
ObjCSpaceBeforeProtocolList: true
PenaltyBreakAssignment: 2
PenaltyBreakBeforeFirstCallParameter: 19
PenaltyBreakComment: 300
@ -142,8 +112,8 @@ PenaltyExcessCharacter: 1000000
PenaltyReturnTypeOnItsOwnLine: 60
PenaltyIndentedWhitespace: 0
PointerAlignment: Left
ReflowComments: true
SortIncludes: true
ReflowComments: true
SortIncludes: true
SortJavaStaticImport: Before
SortUsingDeclarations: true
SpaceAfterCStyleCast: false
@ -158,28 +128,21 @@ SpaceBeforeParens: ControlStatements
SpaceAroundPointerQualifiers: Default
SpaceBeforeRangeBasedForLoopColon: false
SpaceInEmptyBlock: false
SpaceInEmptyParentheses: false
SpacesInParensOptions:
InEmptyParentheses: false
InCStyleCasts: false
SpacesBeforeTrailingComments: 1
SpacesInAngles: false
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: Never
Standard: Latest
LineEnding: LF
UseTab: Never
WhitespaceSensitiveMacros:
- STRINGIZE
- PP_STRINGIZE
- BOOST_PP_STRINGIZE
- NS_SWIFT_NAME
- CF_SWIFT_NAME
...

View File

@ -1,2 +1 @@
**/third_party/**
**/vk_mem_alloc.h
**/wepoll.*

View File

@ -32,15 +32,43 @@ on:
jobs:
formatting-check:
permissions:
contents: write
name: Formatting check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: DoozyX/clang-format-lint-action@v0.17
- name: Checkout
uses: actions/checkout@v4
- name: Test formatting
uses: DoozyX/clang-format-lint-action@v0.17
with:
source: ./
extensions: h,hpp,cpp,c,cc
clangFormatVersion: 17
inplace: true
- name: Generate patch file
run: |
git diff > formatting.patch
find *.patch -type f -size 0 -delete
- name: Upload patch
uses: actions/upload-artifact@v4
with:
name: code-format
path: formatting.patch
- name: Error if fixed
run: |
if rm formatting.patch; then
echo "# Looks like you messed something up!" >> $GITHUB_STEP_SUMMARY
echo "1. Download the \`code-format\` artifact above" >> $GITHUB_STEP_SUMMARY
echo "2. Unpack the patch file" >> $GITHUB_STEP_SUMMARY
echo "3. Use \`git apply formatting.patch\` command to apply it to your repo" >> $GITHUB_STEP_SUMMARY
echo "4. Push the applied changes to your Pull Request" >> $GITHUB_STEP_SUMMARY
exit 1
fi
build:
needs: formatting-check
@ -52,19 +80,6 @@ jobs:
with:
fetch-depth: 0
- name: Restore cached deps
uses: actions/cache@v4
id: cache-thirdparty
with:
path: third_party
key: ${{ runner.os }}-submodules
- name: Update submodules
if: steps.cache-thirdparty.outputs.cache-hit != 'true'
uses: actions/checkout@v4
with:
submodules: recursive
- name: Detect MSVC
uses: ilammy/msvc-dev-cmd@v1
with:
@ -79,7 +94,7 @@ jobs:
- name: Configure
run: |
echo "::add-matcher::tools/matchers/cmake.json"
cmake.exe -S. -B_build/_Release -GNinja -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=_build/_Install -DCMAKE_C_COMPILER=clang-cl.exe -DCMAKE_CXX_COMPILER=clang-cl.exe
cmake.exe -S. -B_build/_Release -GNinja -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=_build/_Install -DCMAKE_C_COMPILER=clang-cl.exe -DCMAKE_CXX_COMPILER=clang-cl.exe -DTEST_BENCH=ON -DISACTION=1
- name: Build
run: |
@ -92,8 +107,15 @@ jobs:
echo "::add-matcher::tools/matchers/cmake.json"
cmake.exe --install _build/_Release
- name: Run some tests
shell: bash
run: echo "`node misc/testRunner`" >> $GITHUB_STEP_SUMMARY
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: psOff-libs
path: _build/_Install/*
name: psOff-emu
path: |
_build/_Install/*
!_build/_Install/vulkan-1.dll
!_build/_Install/*_test.exe

20
.github/workflows/release_notify.yml vendored Normal file
View File

@ -0,0 +1,20 @@
name: Discord release notify
on:
release:
types: [published]
jobs:
notifier:
runs-on: ubuntu-latest
steps:
- name: Clone repo
uses: actions/checkout@v4
- name: Run things
working-directory: misc/discordNotify
env:
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
run: |
npm i
node .

39
.github/workflows/upwiki.yml vendored Normal file
View File

@ -0,0 +1,39 @@
name: upload wiki
on:
push:
branches: [main]
paths:
- '.github/workflows/upwiki.yml'
- 'docs/wiki/**'
jobs:
update-wiki:
name: Update wiki
runs-on: ubuntu-latest
steps:
- name: Pull main repo
uses: actions/checkout@v4
with:
path: main_repo
- name: Setup git
run: |
git config --global user.name github-actions[bot];
git config --global user.email 41898282+github-actions[bot]@users.noreply.github.com;
- name: Pull wiki content
run: |
mkdir wiki_repo;
cd wiki_repo;
git init;
git pull https://${{ secrets.REPO_FULL_TOKEN }}@github.com/${{ github.repository }}.wiki.git
- name: Push wiki content
run: |
rsync -av --delete main_repo/docs/wiki/markdown/ wiki_repo/ --exclude .git;
cd wiki_repo;
git add .;
if git commit -m "Update wiki content"; then
git push -f --set-upstream https://${{ secrets.REPO_FULL_TOKEN }}@github.com/${{ github.repository }}.wiki.git master;
fi

1
.gitignore vendored
View File

@ -3,3 +3,4 @@ _*/
!.vscode
!.github
.vscode/c_cpp_properties.json
core/private

13
.gitmodules vendored
View File

@ -1,13 +0,0 @@
[submodule "third_party/optick"]
path = third_party/optick
url = https://github.com/bombomby/optick.git
[submodule "third_party/magic_enum"]
path = third_party/magic_enum
url = https://github.com/Neargye/magic_enum.git
[submodule "third_party/boost"]
path = third_party/boost
url = https://github.com/boostorg/boost.git
[submodule "third_party/SDL2"]
path = third_party/SDL2
url = https://github.com/libsdl-org/SDL.git
branch = SDL2

33
.vscode/launch.json vendored
View File

@ -1,16 +1,21 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "(Windows) Launch",
"type": "cppvsdbg",
"request": "launch",
"program": "${config:psoff.root-path}/emulator.exe",
"args": ["--file", "${config:psoff.game-path}/eboot.bin", "--d"],
"stopAtEntry": false,
"cwd": "${config:psoff.root-path}",
"environment": [],
"console": "integratedTerminal"
}
]
"version": "0.2.0",
"configurations": [
{
"name": "(Windows) Launch",
"type": "cppvsdbg",
"request": "launch",
"program": "${workspaceRoot}/_build/_Install/psoff.exe",
"args": [
"--d",
"${config:psoff.add-arg}",
"--file=${config:psoff.game-path}/eboot.bin",
],
"stopAtEntry": false,
"cwd": "${workspaceRoot}/_build/_Install",
"environment": [],
"console": "integratedTerminal",
"symbolSearchPath": "${workspaceRoot}/_build/_Install/debug"
}
]
}

12
.vscode/settings.json vendored
View File

@ -14,7 +14,7 @@
"-fexceptions"
],
"ccls.index.threads": 0,
"cmake.parallelJobs": 0,
"cmake.generator": "Ninja",
"cmake.sourceDirectory": "${workspaceFolder}",
"cmake.buildDirectory": "${workspaceFolder}/_build/_Release",
@ -24,16 +24,14 @@
"CMAKE_C_COMPILER": "clang-cl.exe",
"CMAKE_CXX_COMPILER": "clang-cl.exe"
},
"C_Cpp.formatting": "clangFormat",
"C_Cpp.clang_format_style": "file:${workspaceRoot}/.clang-format",
"C_Cpp.clang_format_fallbackStyle": "LLVM",
"C_Cpp.clang_format_path": "C:\\Program Files\\Microsoft Visual Studio\\2022\\Community\\VC\\Tools\\Llvm\\x64\\bin\\clang-format.exe",
"C_Cpp.default.compilerPath": "C:\\Program Files\\Microsoft Visual Studio\\2022\\Community\\VC\\Tools\\Llvm\\bin\\clang-cl.exe",
"C_Cpp.default.intelliSenseMode": "windows-clang-x64",
"C_Cpp.default.cppStandard": "c++20",
"C_Cpp.autoAddFileAssociations": false,
"editor.tabSize": 2,
"editor.insertSpaces": true,
"git.ignoredRepositories": [
"third_party/boost",
"third_party/optick",
"third_party/magic_enum",
"third_part/SDL2"
]
}

4
.vscode/tasks.json vendored
View File

@ -25,6 +25,7 @@
"-S${workspaceFolder}",
"-B_build/_Release",
"-GNinja",
//"-DBUILD_CORE=1",
"-DCMAKE_BUILD_TYPE=Release",
"-DCMAKE_INSTALL_PREFIX=_build/_Install",
"-DCMAKE_C_COMPILER=clang-cl.exe",
@ -63,6 +64,9 @@
"kind": "build",
"isDefault": true
},
"dependsOn": [
"Build"
],
"presentation": {},
},
{

View File

@ -1,4 +1,15 @@
cmake_minimum_required(VERSION 3.24)
include(ExternalProject)
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS OFF)
set(CMAKE_WINDOWS_SYMBOL_VISIBILITY_IN_STATIC_LIBRARIES OFF)
set(CMAKE_C_VISIBILITY_PRESET hidden)
set(CMAKE_CXX_VISIBILITY_PRESET hidden)
set(CMAKE_VISIBILITY_INLINES_HIDDEN 1)
set(PSOFF_LIB_VERSION v.0.3)
set(PSOFF_RENDER_VERSION v.0.5)
set(ProjectName psOff_${CMAKE_BUILD_TYPE})
project(${ProjectName} VERSION 0.0.1)
@ -7,19 +18,21 @@ unset(CMAKE_IMPORT_LIBRARY_SUFFIX)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_CXX_FLAGS_RELEASE "/MD /Zi /GS- /GF /Gy /GR /Oi -Ofast -fno-strict-aliasing")
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} /DEBUG /OPT:REF,ICF")
set(CMAKE_INSTALL_MESSAGE LAZY)
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)
set(CMAKE_WINDOWS_SYMBOL_VISIBILITY_IN_STATIC_LIBRARIES OFF)
if(ISDEBUG)
set(CMAKE_CXX_FLAGS_RELEASE "/MD /Zi /GS- /GF /Gy /GR /Oi -O0 -fno-strict-aliasing")
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} /DEBUG /OPT:REF,ICF")
else()
set(CMAKE_CXX_FLAGS_RELEASE "/MD /GS- /GF /Gy /GR /Oi -Ofast -fno-strict-aliasing")
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} /OPT:REF,ICF")
endif()
if(NOT PRJ_SRC_DIR)
set(PRJ_SRC_DIR ${CMAKE_CURRENT_SOURCE_DIR})
endif()
if(NOT DEFINED IMAGE_BASE)
set(IMAGE_BASE 0x10000000)
endif()
set(IMAGE_BASE 0x10000000)
# # Gather Infos
@ -33,6 +46,7 @@ message("Vulkan Path: ${VulkanPath}")
# # - Gather Infos
file(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/lib) # Move all libs here post-build, except third_party
add_definitions(-DPSOFF_RENDER_VERSION="${PSOFF_RENDER_VERSION}")
#
include_directories(BEFORE
@ -40,41 +54,130 @@ include_directories(BEFORE
${PRJ_SRC_DIR}/modules_include
${PRJ_SRC_DIR}/tools/logging
${PRJ_SRC_DIR}/tools/config_emu
${PRJ_SRC_DIR}/tools/gamereport
${PRJ_SRC_DIR}
${CMAKE_BINARY_DIR}/third_party/install/include
${CMAKE_BINARY_DIR}/third_party/src/third_party/include
${CMAKE_BINARY_DIR}/psoff_render/src/psoff_render/include
)
link_directories(BEFORE
${CMAKE_INSTALL_PREFIX}/development/lib
${CMAKE_BINARY_DIR}/third_party/install/lib
${CMAKE_BINARY_DIR}/core
${CMAKE_BINARY_DIR}/third_party/src/third_party/lib
${CMAKE_BINARY_DIR}/lib
${CMAKE_BINARY_DIR}/bin
${CMAKE_BINARY_DIR}/psoff_render/src/psoff_render/lib
${CMAKE_BINARY_DIR}/core/export/lib
)
add_subdirectory(tools/logging) # include before link_libraries
add_subdirectory(tools/config_emu) # include before link_libraries
ExternalProject_Add(third_party
CONFIGURE_COMMAND ""
BUILD_COMMAND ""
INSTALL_COMMAND ""
PREFIX ${CMAKE_BINARY_DIR}/third_party
BUILD_IN_SOURCE 1
URL https://github.com/SysRay/psOff_thirdParty/releases/download/${PSOFF_LIB_VERSION}/psOff-3rd.zip
)
if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR)
ExternalProject_Add(psoff_render
CONFIGURE_COMMAND ""
BUILD_COMMAND ""
INSTALL_COMMAND ""
PREFIX ${CMAKE_BINARY_DIR}/psoff_render
BUILD_IN_SOURCE 1
URL https://github.com/SysRay/psOff_Renderer/releases/download/${PSOFF_RENDER_VERSION}/psOff-renderer.zip
)
endif()
# set(TEST_BENCH OFF CACHE BOOL "Enable testing")
# if(TEST_BENCH)
# add_subdirectory(tests)
# endif()
# include before link_libraries
add_subdirectory(tools/logging)
add_subdirectory(tools/config_emu)
add_subdirectory(tools/dll2Nids)
add_subdirectory(asm)
add_dependencies(dll2Nids third_party)
# -
link_libraries(
logging.lib
)
add_compile_definitions(IMAGE_BASE=${IMAGE_BASE})
# # Projects
include("third_party/third_party.cmake")
# Internal Projects
add_subdirectory(modules)
add_subdirectory(core)
add_subdirectory(utility)
add_subdirectory(tools/gamereport)
if(BUILD_CORE)
add_subdirectory(core)
else()
add_custom_target(core)
endif()
# #- Projects
# Executable
add_executable(psoff
main.cpp
)
add_dependencies(psoff logging core psOff_utility gamereport)
target_link_libraries(psoff PRIVATE
core.lib
gamereport.lib
psOff_utility
libboost_filesystem
$<TARGET_OBJECTS:asmHelper>
)
target_link_options(psoff PUBLIC /DYNAMICBASE:NO --image-base=${IMAGE_BASE})
set_property(TARGET psoff PROPERTY VS_DPI_AWARE "PerMonitor") # SDL2 High dpi window
# # Install
install(DIRECTORY "${CMAKE_BINARY_DIR}/third_party/bin/" DESTINATION ${CMAKE_INSTALL_PREFIX}
install(DIRECTORY "${CMAKE_BINARY_DIR}/third_party/src/third_party/bin/" DESTINATION ${CMAKE_INSTALL_PREFIX}
FILES_MATCHING PATTERN "*.dll"
)
install(DIRECTORY "${CMAKE_BINARY_DIR}/third_party/install/bin/" DESTINATION ${CMAKE_INSTALL_PREFIX}
install(DIRECTORY "${CMAKE_BINARY_DIR}/bin/" DESTINATION ${CMAKE_INSTALL_PREFIX}
FILES_MATCHING PATTERN "*.dll"
)
install(DIRECTORY "${CMAKE_BINARY_DIR}/lib/" DESTINATION ${CMAKE_INSTALL_PREFIX}
FILES_MATCHING PATTERN "*.dll"
)
install(DIRECTORY "${PRJ_SRC_DIR}/docs/json/" DESTINATION ${CMAKE_INSTALL_PREFIX}/config/.schemas
FILES_MATCHING PATTERN "*.json"
)
if(DEFINED ISACTION)
file(GENERATE OUTPUT ${CMAKE_BINARY_DIR}/bin/vulkan-1.dll CONTENT "") # cmake needs to find the file in order to ignore it
endif()
install(TARGETS psoff
COMPONENT psoff
RUNTIME_DEPENDENCIES
PRE_EXCLUDE_REGEXES "api-ms-" "ext-ms-"
POST_EXCLUDE_REGEXES ".*system32/.*\\.dll" ".*vulkan-1\\.dll"
DIRECTORIES
$<TARGET_FILE_DIR:psoff>
${CMAKE_BINARY_DIR}/third_party/src/third_party/bin
${CMAKE_BINARY_DIR}/third_party/src/third_party/lib
${CMAKE_BINARY_DIR}/bin
${CMAKE_BINARY_DIR}/lib
${CMAKE_CURRENT_BINARY_DIR}/core
${CMAKE_BINARY_DIR}/psoff_render/src/psoff_render/lib
${CMAKE_BINARY_DIR}/core/export/lib
DESTINATION .
)
install(FILES $<TARGET_PDB_FILE:psoff> DESTINATION ${CMAKE_INSTALL_PREFIX}/debug OPTIONAL)

4
asm/CMakeLists.txt Normal file
View File

@ -0,0 +1,4 @@
enable_language(C ASM_MASM)
add_library(asmHelper OBJECT
entry.asm
)

30
asm/entry.asm Normal file
View File

@ -0,0 +1,30 @@
.CODE
PUBLIC jmpEntry
jmpEntry PROC
; Input
; RDI addr
; RSI EntryParams
; RDX exitHandler
mov RAX, RDI
; align stack
SUB RSP, 32
AND RSP, -10h
; copy entry param to stack
ADD RSP, 40
push [RSI+24]
push [RSI+16]
push [RSI+8]
push [RSI]
; -
lea rdi, [RSP] ; param 1 (EntryParam) is @ rsp
mov rsi, RDX ; param2 (atexit)
jmp RAX
jmpEntry ENDP
End

6
asm/helper.h Normal file
View File

@ -0,0 +1,6 @@
#pragma once
#include "utility/utility.h"
extern "C" {
SYSV_ABI void jmpEntry(uint64_t addr, void const* entryParams, void* exitHandler);
}

View File

@ -1,49 +0,0 @@
# Place before add_subdir -> define for all childs
add_compile_definitions(
BOOST_ALL_NO_LIB
)
# Add directories
add_subdirectory(kernel)
add_subdirectory(videoout)
add_subdirectory(initParams)
add_subdirectory(timer)
add_subdirectory(systemContent)
add_subdirectory(fileManager)
add_subdirectory(imports)
add_subdirectory(memory)
add_subdirectory(dmem)
add_subdirectory(unwinding)
# Build
add_library(core SHARED
$<TARGET_OBJECTS:kernel>
$<TARGET_OBJECTS:initParams>
$<TARGET_OBJECTS:timer>
$<TARGET_OBJECTS:systemContent>
$<TARGET_OBJECTS:videoout>
$<TARGET_OBJECTS:fileManager>
$<TARGET_OBJECTS:imports>
$<TARGET_OBJECTS:memory>
$<TARGET_OBJECTS:dmem>
$<TARGET_OBJECTS:unwinding>
)
add_dependencies(core logging boost)
target_link_libraries(core PRIVATE
libboost_thread
libboost_chrono
libboost_program_options
sdl2
OptickCore
psOff_utility
${Vulkan_LIBRARIES}
)
ADD_CUSTOM_COMMAND(TARGET core POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy
${CMAKE_CURRENT_BINARY_DIR}/core.lib
${CMAKE_BINARY_DIR}/lib)
install(FILES $<TARGET_PDB_FILE:core> DESTINATION debug OPTIONAL)
install(TARGETS core LIBRARY DESTINATION .)

View File

@ -1,9 +0,0 @@
add_library(dmem OBJECT
dmem.cpp
)
add_dependencies(dmem third_party boost psOff_utility)
target_include_directories(dmem PRIVATE
${PRJ_SRC_DIR}/third_party/magic_enum/include
)

View File

@ -1,266 +0,0 @@
#define __APICALL_EXTERN
#include "dmem.h"
#undef __APICALL_EXTERN
#include "core/memory/memory.h"
#include "core/videoout/videoout.h"
#include "logging.h"
#include "utility/utility.h"
#include <algorithm>
#include <boost/thread.hpp>
#include <magic_enum/magic_enum.hpp>
#include <memory>
#include <mutex>
#include <unordered_map>
#include <vector>
LOG_DEFINE_MODULE(MemoryManager);
namespace {
struct MemoryInfo {
bool isGpu = false;
size_t size = 0;
};
struct pImpl {
boost::mutex mutex_;
std::unordered_map<uint64_t, MappingType> mappings_;
};
auto getData() {
static pImpl obj;
return &obj;
}
} // namespace
// Allocates the sections (memory pool is done internally (in app))
class PhysicalMemory: public IPhysicalMemory {
std::mutex m_mutex_int;
std::unordered_map<uint64_t, MemoryInfo> m_objects;
public:
PhysicalMemory() = default;
virtual ~PhysicalMemory() { deinit(); }
uint64_t alloc(uint64_t vaddr, size_t len, int memoryTye) final;
bool reserve(uint64_t start, size_t len, size_t alignment, uint64_t* outAddr, int memoryType) final;
uintptr_t commit(uint64_t base, uint64_t offset, size_t len, size_t alignment, int prot) final;
bool Map(uint64_t vaddr, uint64_t physAddr, size_t len, int prot, bool allocFixed, size_t alignment, uint64_t* outAddr) final;
bool Release(uint64_t start, size_t len, uint64_t* vaddr, uint64_t* size) final;
bool Unmap(uint64_t vaddr, uint64_t size) final;
void deinit() final;
};
IPhysicalMemory& accessPysicalMemory() {
static PhysicalMemory inst;
return inst;
}
uint64_t PhysicalMemory::alloc(uint64_t vaddr, size_t len, int prot) {
LOG_USE_MODULE(MemoryManager);
m_allocSize += len;
LOG_DEBUG(L"Alloc: 0x%08llx len:0x%08llx prot:%d curSize=0x%08llx", vaddr, len, prot, m_allocSize);
return 0;
}
bool PhysicalMemory::reserve(uint64_t start, size_t len, size_t alignment, uint64_t* outAddr, int memoryType) {
LOG_USE_MODULE(MemoryManager);
auto const isGpu = memoryType == 3;
*outAddr = memory::reserve(start, len, alignment, isGpu);
{
std::unique_lock const lock(m_mutex_int);
m_availableSize -= len;
}
LOG_DEBUG(L"Reserve| start:0x%08llx size:%llu alignment:%llu memType:%d -> @%08llx", start, len, alignment, memoryType, *outAddr);
return *outAddr != 0;
}
uintptr_t PhysicalMemory::commit(uint64_t base, uint64_t vaddr, size_t len, size_t alignment, int prot) {
LOG_USE_MODULE(MemoryManager);
uintptr_t addr = 0;
auto [protCPU, protGPU] = util::getMemoryProtection(prot);
if (protGPU != 0) {
addr = memory::allocGPUMemory(base, 0, len, alignment);
} else {
addr = memory::commit(base, 0, len, alignment, prot);
}
m_objects[addr] = MemoryInfo {.isGpu = protGPU != 0, .size = len};
if (protGPU != 0) {
if (!accessVideoOut().notify_allocHeap(addr, len, prot)) {
LOG_ERR(L"Commit| Couldn't allocHeap| base:0x%08llx offset:0x%08llx size:%llu alignment:%llu prot:%d -> @%08llx", base, vaddr, len, alignment, prot,
addr);
return 0;
}
}
LOG_DEBUG(L"Commit| base:0x%08llx offset:0x%08llx size:%llu alignment:%llu prot:%d -> @%08llx", base, vaddr, len, alignment, prot, addr);
return addr;
}
bool PhysicalMemory::Map(uint64_t vaddr, uint64_t physAddr, size_t len, int prot, bool allocFixed, size_t alignment, uint64_t* outAddr) {
LOG_USE_MODULE(MemoryManager);
bool mapped = false;
*outAddr = 0;
{
std::unique_lock const lock(m_mutex_int);
auto [protCPU, protGPU] = util::getMemoryProtection(prot);
if (allocFixed) {
if (memory::allocFixed(physAddr, len, prot)) {
*outAddr = physAddr;
}
} else {
*outAddr = memory::allocAligned(physAddr, len, prot, alignment);
}
m_objects[*outAddr] = MemoryInfo {.isGpu = protGPU != 0, .size = len};
if (protGPU != 0) {
if (!accessVideoOut().notify_allocHeap(*outAddr, len, prot)) {
LOG_ERR(L"Map| Couldn't allocHeap vaddr:0x%08llx physAddr:0x%08llx len:0x%08llx prot:0x%x -> out:0x%08llx", vaddr, physAddr, len, prot, *outAddr);
return false;
}
}
if (*outAddr == NULL) {
return false;
}
m_availableSize -= len;
}
LOG_INFO(L"Map| vaddr:0x%08llx physAddr:0x%08llx len:0x%08llx prot:0x%x -> out:0x%08llx", vaddr, physAddr, len, prot, *outAddr);
return true;
}
bool PhysicalMemory::Release(uint64_t start, size_t len, uint64_t* vaddr, uint64_t* size) {
LOG_USE_MODULE(MemoryManager);
LOG_ERR(L"todo %S", __FUNCTION__);
m_allocSize -= len;
return true;
}
bool PhysicalMemory::Unmap(uint64_t vaddr, uint64_t size) {
LOG_USE_MODULE(MemoryManager);
if (auto it = m_objects.find(vaddr); it != m_objects.end()) {
if (it->second.isGpu) {
// if(isGPU) accessGpuMemory().freeHeap(vaddr); // todo, notify free (should free host memory aswell)
memory::free(vaddr);
} else {
memory::free(vaddr);
}
} else {
LOG_ERR(L"Unmap not in map: vaddr:0x%08llx len:%lld", vaddr, size);
memory::free(vaddr);
}
{
std::unique_lock const lock(m_mutex_int);
m_availableSize += size;
}
LOG_INFO(L"Unmap: vaddr:0x%08llx len:%lld", vaddr, size);
return true;
}
void PhysicalMemory::deinit() {
for (auto& item: m_objects) {
if (item.second.isGpu) {
// done by gpuMemoryManager
} else {
memory::free(item.first);
}
}
m_objects.clear();
}
class FlexibleMemory: public IFlexibleMemory {
uint64_t m_totalAllocated = 0;
std::mutex m_mutex_int;
public:
FlexibleMemory() = default;
virtual ~FlexibleMemory() = default;
uint64_t alloc(uint64_t vaddr, size_t len, int prot) final;
bool destroy(uint64_t vaddr, uint64_t size) final;
void release(uint64_t start, size_t len) final;
uint64_t available() final {
std::unique_lock const lock(m_mutex_int);
return (uint64_t)448 * 1024 * 1024 - m_totalAllocated; // todo get system ram
}
};
IFlexibleMemory& accessFlexibleMemory() {
static FlexibleMemory inst;
return inst;
}
uint64_t FlexibleMemory::alloc(uint64_t vaddr, size_t len, int prot) {
LOG_USE_MODULE(MemoryManager);
std::unique_lock const lock(m_mutex_int);
m_totalAllocated += len;
auto const outAddr = memory::alloc(vaddr, len, prot);
registerMapping(outAddr, MappingType::Flexible);
LOG_INFO(L"--> Heap| vaddr:0x%08llx len:%llu prot:0x%x total:0x%08llx -> @0x%08llx", vaddr, len, prot, m_totalAllocated, outAddr);
return outAddr;
}
bool FlexibleMemory::destroy(uint64_t vaddr, uint64_t size) {
LOG_USE_MODULE(MemoryManager);
std::unique_lock const lock(m_mutex_int);
m_totalAllocated -= size;
memory::free(vaddr);
LOG_INFO(L"<-- Heap| vaddr:0x%08llx len:%lld total:0x%08llx", vaddr, size, m_totalAllocated);
return true;
}
void FlexibleMemory::release(uint64_t start, size_t len) {
LOG_USE_MODULE(MemoryManager);
LOG_ERR(L"todo %S", __FUNCTION__);
}
void registerMapping(uint64_t vaddr, MappingType type) {
auto impl = getData();
boost::unique_lock lock(impl->mutex_);
impl->mappings_.emplace(std::make_pair(vaddr, type));
}
MappingType unregisterMapping(uint64_t vaddr) {
auto impl = getData();
boost::unique_lock lock(impl->mutex_);
if (auto it = impl->mappings_.find((uint64_t)vaddr); it != impl->mappings_.end()) {
auto type = it->second;
impl->mappings_.erase(it);
return it->second;
}
return MappingType::None;
}

View File

@ -1,87 +0,0 @@
#pragma once
#include "utility/utility.h"
enum class GpuMemoryMode { NoAccess, Read, Write, ReadWrite };
enum class MappingType { None, File, Flexible, Fixed };
class IPhysicalMemory {
CLASS_NO_COPY(IPhysicalMemory);
protected:
IPhysicalMemory() = default;
uint64_t m_availableSize = 5000000000llu; // todo get from system memory
size_t m_allocSize = 0;
public:
virtual ~IPhysicalMemory() = default;
void getAvailableSize(uint32_t start, uint32_t end, size_t alignment, uint32_t* startOut, size_t* sizeOut) {
*startOut = size() - m_availableSize;
*sizeOut = m_availableSize;
}
virtual uint64_t alloc(uint64_t vaddr, size_t len, int memoryType) = 0;
virtual bool reserve(uint64_t start, size_t len, size_t alignment, uint64_t* outAddr, int memoryType) = 0;
virtual uintptr_t commit(uint64_t base, uint64_t offset, size_t len, size_t alignment, int prot) = 0;
virtual bool Map(uint64_t vaddr, uint64_t physAddr, size_t len, int prot, bool allocFixed, size_t alignment, uint64_t* outAddr) = 0;
virtual bool Release(uint64_t start, size_t len, uint64_t* vaddr, uint64_t* size) = 0;
virtual bool Unmap(uint64_t vaddr, uint64_t size) = 0;
virtual void deinit() = 0;
uint64_t const size() const { return m_allocSize; } // use system ram
};
class IFlexibleMemory {
CLASS_NO_COPY(IFlexibleMemory);
uint64_t m_configuresSize = 448 * 1024 * 1024;
protected:
IFlexibleMemory() = default;
public:
virtual ~IFlexibleMemory() = default;
void setConfiguredSize(uint64_t size) { m_configuresSize = size; }
uint64_t size() const { return m_configuresSize; }
virtual uint64_t available() = 0;
virtual uint64_t alloc(uint64_t vaddr, size_t len, int prot) = 0;
virtual bool destroy(uint64_t vaddr, uint64_t size) = 0;
virtual void release(uint64_t start, size_t len) = 0;
};
#if defined(__APICALL_EXTERN)
#define __APICALL __declspec(dllexport)
#elif defined(__APICALL_IMPORT)
#define __APICALL __declspec(dllimport)
#else
#define __APICALL
#endif
/**
* @brief registers mapping (for mmap, mumap)
*
* @param vaddr
* @param type != None
* @return __APICALL
*/
__APICALL void registerMapping(uint64_t vaddr, MappingType type);
/**
* @brief Unregisters mapping and returns the type of the mappin
*
* @param vaddr
* @return None: Mapping didn't exist
*/
__APICALL MappingType unregisterMapping(uint64_t vaddr);
__APICALL IPhysicalMemory& accessPysicalMemory();
__APICALL IFlexibleMemory& accessFlexibleMemory();
#undef __APICALL

View File

@ -1,15 +0,0 @@
add_library(fileManager OBJECT
fileManager.cpp
types/type_in.cpp
types/type_out.cpp
types/type_zero.cpp
types/type_null.cpp
types/type_file.cpp
types/type_random.cpp
)
add_dependencies(fileManager third_party psOff_utility)
target_include_directories(fileManager PRIVATE
${PRJ_SRC_DIR}/third_party/magic_enum/include
)

View File

@ -1,272 +0,0 @@
#define __APICALL_EXTERN
#include "fileManager.h"
#undef __APICALL_EXTERN
#include "logging.h"
#include "magic_enum/magic_enum.hpp"
#include "types/type_in.h"
#include "types/type_null.h"
#include "types/type_out.h"
#include "types/type_zero.h"
#include "utility/utility.h"
#include <algorithm>
#include <fstream>
#include <memory>
#include <mutex>
#include <unordered_map>
#include <utility>
LOG_DEFINE_MODULE(FileManager);
struct UniData {
CLASS_NO_COPY(UniData);
enum class Type { File, Dir };
Type const m_type;
std::filesystem::path const m_path;
UniData(Type type, std::filesystem::path const& path): m_type(type), m_path(path) {}
virtual ~UniData() = default;
};
struct FileData: public UniData {
std::unique_ptr<IFile> const m_file;
FileData(std::unique_ptr<IFile>&& file, std::filesystem::path const& path): UniData(UniData::Type::File, path), m_file(std::move(file)) {}
virtual ~FileData() { m_file->sync(); }
};
struct DirData: public UniData {
std::unique_ptr<std::filesystem::directory_iterator> const m_file;
uint32_t count = 0;
DirData(std::unique_ptr<std::filesystem::directory_iterator>&& dirIt, std::filesystem::path const& path)
: UniData(UniData::Type::Dir, path), m_file(std::move(dirIt)) {}
virtual ~DirData() = default;
};
namespace {
int insertItem(std::vector<std::unique_ptr<UniData>>& container, UniData* item) {
int index = -1;
for (size_t n = 0; n < container.size(); ++n) {
if (!container[n]) {
container[n] = std::unique_ptr<UniData>(item);
index = n;
break;
}
}
if (index < 0) {
index = container.size();
container.push_back(std::unique_ptr<UniData>(item));
}
return index;
}
} // namespace
class FileManager: public IFileManager {
std::vector<std::unique_ptr<UniData>> m_openFiles;
std::mutex m_mutext_int;
/// First: mountpoint Second: RootDir
std::unordered_map<MountType, std::unordered_map<std::string, std::filesystem::path>> m_mountPointList;
std::filesystem::path m_dirGameFiles;
std::unordered_map<std::string, std::filesystem::path> m_mappedPaths;
bool m_updateSearch = false;
public:
FileManager() { init(); };
virtual ~FileManager() = default;
void init() {
// Order of the first three files is important, do not change it!
assert(addFile(createType_in(), "/dev/stdin") == 0);
assert(addFile(createType_out(SCE_TYPEOUT_ERROR), "/dev/stdout") == 1);
assert(addFile(createType_out(SCE_TYPEOUT_DEBUG), "/dev/stderr") == 2);
addFile(createType_zero(), "/dev/zero");
addFile(createType_null(), "/dev/null");
}
void addMountPoint(std::string_view mountPoint, std::filesystem::path const& root, MountType type) final {
LOG_USE_MODULE(FileManager);
std::unique_lock const lock(m_mutext_int);
m_mountPointList[type][std::string(mountPoint)] = root;
// Create root dir
std::filesystem::create_directories(root);
LOG_INFO(L"+ MountPoint: %S->%s", mountPoint.data(), root.c_str());
}
std::filesystem::path getMountPoint(MountType type, std::string mountPoint) final {
std::unique_lock const lock(m_mutext_int);
if (auto it = m_mountPointList[type].find(mountPoint); it != m_mountPointList[type].end()) return it->second;
return {};
}
void clearMountPoint(MountType type, std::string mountPoint) final {
LOG_USE_MODULE(FileManager);
std::unique_lock const lock(m_mutext_int);
LOG_INFO(L"- MountPoint for %S", magic_enum::enum_name(type).data());
m_mountPointList[type].erase(mountPoint);
}
std::optional<std::filesystem::path> getMappedPath(std::string_view path) final {
LOG_USE_MODULE(FileManager);
std::unique_lock const lock(m_mutext_int);
// Check Cache
{
auto it = m_mappedPaths.find(path.data());
if (it != m_mappedPaths.end()) {
return it->second;
}
}
//-
// Special case: Mounted Gamefiles
if (path[0] != '/') {
auto const mapped = m_dirGameFiles / path;
LOG_INFO(L"Gamefiles mapped:%s", mapped.c_str());
return mapped;
}
// -
uint8_t offset = (path[0] == '/' && path[1] == '/') ? 1 : 0; // Path can start with //app0 ?
for (auto const& mountType: m_mountPointList) {
for (auto const& item: mountType.second) {
auto const& mountPoint = item.first;
auto const& rootDir = item.second;
if (path.size() >= mountPoint.size() && std::mismatch(mountPoint.begin(), mountPoint.end(), path.begin() + offset).first == mountPoint.end()) {
if (path[mountPoint.size() + offset] == '/') ++offset; // path.substr() should return relative path
if (mountType.first == MountType::App && m_updateSearch) {
auto const& updateDir = m_mountPointList[MountType::Update].begin()->second;
auto const mapped = updateDir / path.substr(mountPoint.size() + offset);
if (std::filesystem::exists(mapped)) {
LOG_DEBUG(L"mapped: %s root:%s source:%S", mapped.c_str(), updateDir.c_str(), path.data());
m_mappedPaths[path.data()] = mapped;
return mapped;
}
}
auto const mapped = rootDir / path.substr(mountPoint.size() + offset);
LOG_DEBUG(L"mapped: %s root:%s source:%S", mapped.c_str(), rootDir.c_str(), path.data());
m_mappedPaths[path.data()] = mapped;
return mapped;
}
}
}
LOG_WARN(L"Unknown map:%S", path.data());
return {};
}
void setGameFilesDir(std::filesystem::path const& path) final {
std::unique_lock const lock(m_mutext_int);
m_dirGameFiles = path;
}
std::filesystem::path const& getGameFilesDir() const final { return m_dirGameFiles; }
int addFile(std::unique_ptr<IFile>&& file, std::filesystem::path const& path) final {
std::unique_lock const lock(m_mutext_int);
return insertItem(m_openFiles, std::make_unique<FileData>(std::move(file), path).release());
}
int addDirIterator(std::unique_ptr<std::filesystem::directory_iterator>&& it, std::filesystem::path const& path) final {
std::unique_lock const lock(m_mutext_int);
return insertItem(m_openFiles, std::make_unique<DirData>(std::move(it), path).release());
}
void remove(int handle) final {
std::unique_lock const lock(m_mutext_int);
m_openFiles[handle].reset();
}
IFile* accessFile(int handle) final {
std::unique_lock const lock(m_mutext_int);
if (handle < m_openFiles.size() && m_openFiles[handle] && m_openFiles[handle]->m_type == UniData::Type::File) {
return static_cast<FileData*>(m_openFiles[handle].get())->m_file.get();
}
return nullptr;
}
std::filesystem::path getPath(int handle) final {
std::unique_lock const lock(m_mutext_int);
if (handle < m_openFiles.size() && m_openFiles[handle]) {
return m_openFiles[handle]->m_path;
}
return {};
}
int getDents(int handle, char* buf, int nbytes, int64_t* basep) final {
LOG_USE_MODULE(FileManager);
std::unique_lock const lock(m_mutext_int);
auto& oFile = m_openFiles[handle];
if (oFile->m_type != UniData::Type::Dir) return -1;
auto dir = static_cast<DirData*>(oFile.get());
auto endDir = std::filesystem::directory_iterator();
if ((*dir->m_file) == endDir) return 0;
struct DataStruct {
uint32_t fileno;
uint16_t reclen;
uint8_t type;
uint8_t namlen;
char name[256];
};
auto count = dir->count;
int n = 0;
while (n < nbytes - sizeof(DataStruct)) {
auto item = (DataStruct*)(buf + n);
auto const filename = (*dir->m_file)->path().filename().string();
if (sizeof(DataStruct) + std::min(filename.size(), 255llu) >= nbytes) break;
item->fileno = 0;
item->type = ((*dir->m_file)->is_regular_file() ? 8 : 4);
item->namlen = filename.copy(item->name, 255);
item->name[item->namlen] = '\0';
n += sizeof(DataStruct);
item->reclen = sizeof(DataStruct);
LOG_DEBUG(L"KernelGetdirentries[%d]: %S %u offset:%u count:%u", handle, item->name, item->type, item->reclen, count);
std::error_code err;
(*dir->m_file).increment(err);
count = ++dir->count;
if ((*dir->m_file) == endDir || err) break;
};
if (basep != nullptr) {
*basep = count;
}
return n;
}
void enableUpdateSearch() final { m_updateSearch = true; }
};
IFileManager& accessFileManager() {
static FileManager inst;
return inst;
}

View File

@ -1,133 +0,0 @@
#pragma once
#include "ifile.h"
#include "utility/utility.h"
#include <filesystem>
#include <fstream>
#include <memory>
#include <optional>
#include <string>
enum class MountType { App, Update, Save, Temp, Data };
constexpr int FILE_DESCRIPTOR_MIN = 3;
constexpr std::string_view MOUNT_POINT_TEMP = "temp";
constexpr std::string_view MOUNT_POINT_DATA = "data";
constexpr std::string_view MOUNT_POINT_APP = "/app0/";
constexpr std::string_view MOUNT_POINT_UPDATE = "/app1/";
constexpr std::string_view SAVE_DATA_POINT = "/savedata";
class IFileManager {
CLASS_NO_COPY(IFileManager);
CLASS_NO_MOVE(IFileManager);
protected:
IFileManager() = default;
virtual ~IFileManager() = default;
public:
/**
* @brief Get the mapped path. (Resolves linux paths, mounts etc.)
*
* @param path
* @return std::optional<std::filesystem::path> on success, the mapped path
*/
virtual std::optional<std::filesystem::path> getMappedPath(std::string_view path) = 0;
/**
* @brief Adds a fake mount point. getMappedPath() uses those to resolve to the correct path.
* Just replaces both strings
*
* @param mountPoint e.g. /savedata
* @param root actual path e.g. C:/savedir
* @param type Used to group the mounts. normaly only one mount per type is used. (savedata uses multiple ...)
*/
virtual void addMountPoint(std::string_view mountPoint, std::filesystem::path const& root, MountType type) = 0;
/**
* @brief Get the actual path for a mount point
*
* @param type
* @param mountPoint e.g. /savedata
* @return std::filesystem::path empty on error
*/
virtual std::filesystem::path getMountPoint(MountType type, std::string mountPoint) = 0;
/**
* @brief Removes the mountpoint
*
* @param type
* @param mountPoint
*/
virtual void clearMountPoint(MountType type, std::string mountPoint) = 0;
/**
* @brief Sets the path where all games files should be located (savegames etc).
* Used by main to setup the emulator
* @param path
*/
virtual void setGameFilesDir(std::filesystem::path const& path) = 0;
/**
* @brief Get the Game Files Dir
*
* @return std::filesystem::path const&
*/
virtual std::filesystem::path const& getGameFilesDir() const = 0;
/**
* @brief Add a open file.
*
* @param file
* @param path the mapped path to the file/folder
* @return int handle of the fstream. used by getFile() etc.
*/
virtual int addFile(std::unique_ptr<IFile>&& file, std::filesystem::path const& path) = 0;
/**
* @brief Add a directory_iterator
*
* @param it
* @param path the mapped path to the folder
* @return int handle to the directory_iterator
*/
virtual int addDirIterator(std::unique_ptr<std::filesystem::directory_iterator>&& it, std::filesystem::path const& path) = 0;
/**
* @brief remove the file/folder with the associated handle.
* Only the internal mapping is removed!
*
* @param handle
*/
virtual void remove(int handle) = 0;
/**
* @brief Get access to the File
*
* @param handle
* @return std::fstream*
*/
virtual IFile* accessFile(int handle) = 0;
virtual int getDents(int handle, char* buf, int nbytes, int64_t* basep) = 0;
/**
* @brief Get the mapped path of a open file/folder
*
* @param handle
* @return std::filesystem::path
*/
virtual std::filesystem::path getPath(int handle) = 0;
virtual void enableUpdateSearch() = 0;
};
#if defined(__APICALL_EXTERN)
#define __APICALL __declspec(dllexport)
#elif defined(__APICALL_IMPORT)
#define __APICALL __declspec(dllimport)
#else
#define __APICALL
#endif
__APICALL IFileManager& accessFileManager();
#undef __APICALL

View File

@ -1,67 +0,0 @@
#pragma once
#include "utility/utility.h"
#include <stdint.h>
enum class FileType {
File,
Device,
};
enum class SceWhence : int {
beg,
cur,
end,
};
class IFile {
CLASS_NO_COPY(IFile);
CLASS_NO_MOVE(IFile);
FileType const m_type;
protected:
int32_t m_errCode = 0;
IFile(FileType type): m_type(type) {}
public:
virtual ~IFile() = default;
auto getType() const { return m_type; }
auto getErr() const { return m_errCode; }
void clearError() { m_errCode = 0; }
/**
* @brief read n bytes from file to (uint8_t)buf[nbytes]
*
* @param buf
* @param nbytes
* @return size_t number of bytes read, 0:empty
*/
virtual size_t read(void* buf, size_t nbytes) = 0;
/**
* @brief write n bytes from (uint8_t)buf[nbytes] to file
*
* @param buf
* @param nbytes
* @return size_t number of bytes written
*/
virtual size_t write(void* buf, size_t nbytes) = 0;
virtual void sync() = 0;
/**
* @brief Set the read/write position
*
* @param offset
* @param whence
* @return int64_t
*/
virtual int64_t lseek(int64_t offset, SceWhence whence) = 0;
virtual void* getNative() = 0;
};

View File

@ -1,8 +0,0 @@
## FileManager
Manages all Linux files (mounts etc.), converts paths to Host paths and is the owner of opened files.
<div align="center">
![](../../out/docs/uml/modules/fileManager.svg)
</div>

View File

@ -1,147 +0,0 @@
#include "type_file.h"
#include "logging.h"
#include <filesystem>
#include <fstream>
#include <windows.h>
LOG_DEFINE_MODULE(File);
namespace {
typedef HANDLE Handle_t;
std::pair<Handle_t, int32_t> open(std::filesystem::path path, filesystem::SceOpen mode) {
LOG_USE_MODULE(File);
DWORD shareMode = 0;
DWORD access = 0;
// Set acces and shareMode;
switch (mode.mode) {
case filesystem::SceOpenMode::RDONLY: {
access = GENERIC_READ;
if (mode.shlock) shareMode = FILE_SHARE_READ;
} break;
case filesystem::SceOpenMode::WRONLY: {
access = GENERIC_WRITE;
if (mode.shlock) shareMode = FILE_SHARE_WRITE;
} break;
case filesystem::SceOpenMode::RDWR: {
access = GENERIC_READ | GENERIC_WRITE;
if (mode.shlock) shareMode = FILE_SHARE_READ | FILE_SHARE_WRITE;
} break;
}
// -
DWORD flags = OPEN_EXISTING;
if (mode.trunc) flags = CREATE_ALWAYS;
// Create new if it doesn't exist
else if (mode.create) {
flags = CREATE_NEW;
}
auto pathString = path.wstring();
std::replace(pathString.begin(), pathString.end(), L'/', L'\\');
HANDLE file = CreateFileW(pathString.c_str(), access, shareMode, 0, flags, FILE_ATTRIBUTE_NORMAL, 0);
if (file == INVALID_HANDLE_VALUE) {
auto const lastErr = GetLastError();
LOG_WARN(L"open file: %s flags:0x%lx access:0x%lx shareMode:0x%lx mode:0x%llx(%u) error:0x%lx ", pathString.c_str(), flags, access, shareMode,
*(uint32_t*)(&mode), mode.mode, lastErr);
if (lastErr == 0x57) return {nullptr, (int)ErrCode::_EINVAL};
if (lastErr == 0x20) return {nullptr, (int)ErrCode::_EALREADY};
return {nullptr, Ok};
}
LOG_DEBUG(L"[%lld] opened file: %s flags:0x%lx access:0x%lx shareMode:0x%lx mode:0x%lx(%u)", file, pathString.c_str(), flags, access, shareMode,
*(uint32_t*)(&mode), mode.mode);
return {file, Ok};
}
} // namespace
class TypeFile: public IFile {
Handle_t m_file = INVALID_HANDLE_VALUE;
filesystem::SceOpen const m_mode;
bool m_isAppend = false;
public:
TypeFile(std::filesystem::path path, filesystem::SceOpen mode): IFile(FileType::File), m_mode(mode) {
auto [file, err] = open(path, mode);
m_file = file;
m_errCode = err;
m_isAppend = mode.append;
}
virtual ~TypeFile() {
if (m_file != INVALID_HANDLE_VALUE) {
CloseHandle(m_file);
sync();
}
}
// ### Interface
size_t read(void* buf, size_t nbytes) final;
size_t write(void* buf, size_t nbytes) final;
void sync() final;
int64_t lseek(int64_t offset, SceWhence whence) final;
virtual void* getNative() final { return m_file; }
};
std::unique_ptr<IFile> createType_file(std::filesystem::path path, filesystem::SceOpen mode) {
return std::make_unique<TypeFile>(path, mode);
}
size_t TypeFile::read(void* buf, size_t nbytes) {
DWORD numRead = 0;
if (!ReadFile(m_file, buf, nbytes, &numRead, nullptr)) {
auto const lastError = GetLastError();
if (lastError != ERROR_IO_PENDING) {
LOG_USE_MODULE(File);
LOG_WARN(L"Read error: 0x%lx", GetLastError());
m_errCode = (int)ErrCode::_EIO;
return 0;
}
}
return numRead;
}
size_t TypeFile::write(void* buf, size_t nbytes) {
// The file is opened in append mode. Before each write(2),
// the file offset is positioned at the end of the file
if (m_isAppend) {
lseek(0, SceWhence::end);
}
// -
DWORD numWrite = 0;
if (!WriteFile(m_file, buf, nbytes, &numWrite, nullptr)) {
LOG_USE_MODULE(File);
LOG_WARN(L"Write error: 0x%lx", GetLastError());
m_errCode = (int)ErrCode::_EIO;
return 0;
}
return numWrite;
}
void TypeFile::sync() {
FlushFileBuffers(m_file);
}
int64_t TypeFile::lseek(int64_t offset, SceWhence whence) {
static int _whence[] = {
FILE_BEGIN,
FILE_CURRENT,
FILE_END,
};
LONG upperHalf = offset >> 32;
DWORD lowerHalf = SetFilePointer(m_file, (uint32_t)offset, &upperHalf, _whence[(int)whence]);
return ((uint64_t)upperHalf << 32) | lowerHalf;
}

View File

@ -1,9 +0,0 @@
#pragma once
#include "../ifile.h"
#include "core/kernel/filesystem.h"
#include <filesystem>
#include <memory>
std::unique_ptr<IFile> createType_file(std::filesystem::path path, filesystem::SceOpen mode);

View File

@ -1,41 +0,0 @@
#include "type_in.h"
#include "logging.h"
#include "modules_include/common.h"
LOG_DEFINE_MODULE(filesystem);
class TypeIn: public IFile {
public:
TypeIn(): IFile(FileType::Device) {}
virtual ~TypeIn() {}
// ### Interface
size_t read(void* buf, size_t nbytes) final;
size_t write(void* buf, size_t nbytes) final;
int64_t lseek(int64_t offset, SceWhence whence) final;
void sync() final;
void* getNative() final { return nullptr; }
};
std::unique_ptr<IFile> createType_in() {
return std::make_unique<TypeIn>();
}
size_t TypeIn::read(void* buf, size_t nbytes) {
if (nbytes == 0) return 0;
printf("Emulator awaits your input: ");
return std::fread(buf, 1, nbytes, stdin);
}
size_t TypeIn::write(void* buf, size_t nbytes) {
return nbytes;
}
int64_t TypeIn::lseek(int64_t offset, SceWhence whence) {
return -1;
}
void TypeIn::sync() {}

View File

@ -1,7 +0,0 @@
#pragma once
#include "../ifile.h"
#include <memory>
std::unique_ptr<IFile> createType_in();

View File

@ -1,36 +0,0 @@
#include "type_null.h"
#include "modules_include/common.h"
class TypeNull: public IFile {
public:
TypeNull(): IFile(FileType::Device) {}
virtual ~TypeNull() {}
// ### Interface
size_t read(void* buf, size_t nbytes) final;
size_t write(void* buf, size_t nbytes) final;
int64_t lseek(int64_t offset, SceWhence whence) final;
void sync() final;
void* getNative() final { return nullptr; }
};
std::unique_ptr<IFile> createType_null() {
return std::make_unique<TypeNull>();
}
size_t TypeNull::read(void* buf, size_t nbytes) {
return nbytes;
}
size_t TypeNull::write(void* buf, size_t nbytes) {
return nbytes;
}
int64_t TypeNull::lseek(int64_t offset, SceWhence whence) {
return -1;
}
void TypeNull::sync() {}

View File

@ -1,7 +0,0 @@
#pragma once
#include "../ifile.h"
#include <memory>
std::unique_ptr<IFile> createType_null();

View File

@ -1,58 +0,0 @@
#include "type_out.h"
#include "logging.h"
LOG_DEFINE_MODULE(filesystem);
class TypeOut: public IFile {
const SceFileOutChannel m_channel = SCE_TYPEOUT_ERROR;
public:
TypeOut(SceFileOutChannel ch): IFile(FileType::Device), m_channel(ch) {}
virtual ~TypeOut() {}
// ### Interface
size_t read(void* buf, size_t nbytes) final;
size_t write(void* buf, size_t nbytes) final;
int64_t lseek(int64_t offset, SceWhence whence) final;
void sync() final;
void* getNative() final { return nullptr; }
};
std::unique_ptr<IFile> createType_out(SceFileOutChannel ch) {
return std::make_unique<TypeOut>(ch);
}
size_t TypeOut::read(void* buf, size_t nbytes) {
return 0;
}
size_t TypeOut::write(void* buf, size_t nbytes) {
LOG_USE_MODULE(filesystem);
std::string str((const char*)buf, nbytes);
while (str.back() == '\n')
str.pop_back();
switch (m_channel) {
case SCE_TYPEOUT_ERROR: {
printf("Console:%s\n", str.c_str());
LOG_DEBUG(L"%S", str.c_str());
} break;
case SCE_TYPEOUT_DEBUG: LOG_DEBUG(L"%S", str.c_str()); break;
default: LOG_CRIT(L"Unknown channel: %d", (uint32_t)m_channel); break;
}
return nbytes;
}
int64_t TypeOut::lseek(int64_t offset, SceWhence whence) {
return -1;
}
void TypeOut::sync() {}

View File

@ -1,12 +0,0 @@
#pragma once
#include "../ifile.h"
#include <memory>
enum SceFileOutChannel {
SCE_TYPEOUT_ERROR,
SCE_TYPEOUT_DEBUG,
};
std::unique_ptr<IFile> createType_out(SceFileOutChannel ch);

View File

@ -1,40 +0,0 @@
#include "type_random.h"
class TypeRandom: public IFile {
public:
TypeRandom(): IFile(FileType::Device) {}
virtual ~TypeRandom() {}
// ### Interface
size_t read(void* buf, size_t nbytes) final;
size_t write(void* buf, size_t nbytes) final;
int64_t lseek(int64_t offset, SceWhence whence) final;
void sync() final;
void* getNative() final { return nullptr; }
};
std::unique_ptr<IFile> createType_random() {
std::srand(std::time(nullptr));
return std::make_unique<TypeRandom>();
}
size_t TypeRandom::read(void* buf, size_t nbytes) {
auto cbuf = static_cast<char*>(buf);
for (size_t i = 0; i < nbytes; i++)
cbuf[i] = std::rand() & 0xFF;
return nbytes;
}
size_t TypeRandom::write(void* buf, size_t nbytes) {
return 0;
}
int64_t TypeRandom::lseek(int64_t offset, SceWhence whence) {
return -1;
}
void TypeRandom::sync() {}

View File

@ -1,7 +0,0 @@
#pragma once
#include "../ifile.h"
#include <memory>
std::unique_ptr<IFile> createType_random();

View File

@ -1,38 +0,0 @@
#include "type_zero.h"
class TypeZero: public IFile {
public:
TypeZero(): IFile(FileType::Device) {}
virtual ~TypeZero() {}
// ### Interface
size_t read(void* buf, size_t nbytes) final;
size_t write(void* buf, size_t nbytes) final;
int64_t lseek(int64_t offset, SceWhence whence) final;
void sync() final;
void* getNative() final { return nullptr; }
};
std::unique_ptr<IFile> createType_zero() {
return std::make_unique<TypeZero>();
}
size_t TypeZero::read(void* buf, size_t nbytes) {
for (size_t i = 0; i < nbytes; i++) {
((char*)buf)[i] = '\0';
}
return nbytes;
}
size_t TypeZero::write(void* buf, size_t nbytes) {
return 0;
}
int64_t TypeZero::lseek(int64_t offset, SceWhence whence) {
return -1;
}
void TypeZero::sync() {}

View File

@ -1,7 +0,0 @@
#pragma once
#include "../ifile.h"
#include <memory>
std::unique_ptr<IFile> createType_zero();

View File

@ -1,11 +0,0 @@
add_library(imports OBJECT
imports.cpp
exit.cpp
)
add_dependencies(imports third_party psOff_utility initParams)
target_include_directories(imports PRIVATE
${Vulkan_INCLUDE_DIRS}
${PRJ_SRC_DIR}/third_party/optick/src
${PRJ_SRC_DIR}
)

View File

@ -1,14 +0,0 @@
#define __APICALL_EXTERN
#include "imports_exit.h"
#undef __APICALL_EXTERN
#include "core/dmem/dmem.h"
/**
* @brief Exit handling of the core lib
*
*/
void core_exit() {
// signal handler context
}

View File

@ -1,164 +0,0 @@
#pragma once
#include "core/kernel/eventqueue_types.h"
#include "utility/utility.h"
#include "vulkan/vulkan_core.h"
#include <memory>
enum class GRAPHICS_EVENTS : int {
Compute0RelMem = 0,
Compute1RelMem,
Compute2RelMem,
Compute3RelMem,
Compute4RelMem,
Compute5RelMem,
Compute6RelMem,
EOP = 0x40,
};
namespace vulkan {
struct DeviceInfo;
enum class QueueType : uint8_t;
struct QueueInfo;
} // namespace vulkan
class IEventsGraphics {
public:
virtual void eventDoFlip(int handle, int index, int64_t flipArg, VkSemaphore waitSema, size_t waitValue) = 0;
virtual vulkan::DeviceInfo* getDeviceInfo() = 0;
virtual vulkan::QueueInfo* getQueue(vulkan::QueueType type) = 0;
};
class IGraphics {
CLASS_NO_COPY(IGraphics);
CLASS_NO_MOVE(IGraphics);
protected:
IGraphics() = default;
public:
virtual ~IGraphics() = default;
/**
* @brief Adds a GRAPHICS_EVENTS event
*
* @param event
* @param eq
* @return int result
*/
virtual int addEvent(Kernel::EventQueue::KernelEqueueEvent& event, Kernel::EventQueue::IKernelEqueue_t eq) = 0;
/**
* @brief Removes a GRAPHICS_EVENTS event
*
* @param eq
* @param ident
*/
virtual void removeEvent(Kernel::EventQueue::IKernelEqueue_t eq, int const ident) = 0;
/**
* @brief Add commandbuffer for preparsing.
* unregistered on submit
*
* @param cmdBufferClass
*/
virtual void registerCommandBuffer(void* cmdBufferClass) = 0;
/**
* @brief submits the command buffer to the command processor
*
* @param count
* @param drawBuffers
* @param numDrawDw
* @param constBuffers
* @param numConstDw
* @param handle
* @param index
* @param flipMode
* @param flipArg
* @param flip
*/
virtual void submitCmdBuffer(uint32_t count, uint32_t const* drawBuffers[], uint32_t const numDrawDw[], uint32_t const* constBuffers[],
uint32_t const numConstDw[], int const handle, int const index, int const flipMode, int64_t const flipArg, bool flip) = 0;
/**
* @brief Wait for all submits (and currently flips todo: check if needed)
*
*/
virtual void waitSubmitDone() = 0;
/**
* @brief Increase the counter in waitSubmitDone(). Adds a wait condition.
*
*/
virtual void submited() = 0;
/**
* @brief Notify waitSubmitDone(). Decreases the counter
*
*/
virtual void submitDone() = 0;
/**
* @brief Check if command processor is running (handling submits)
*
* @return true
* @return false
*/
virtual bool isRunning() const = 0;
/**
* @brief Pre deletion call
*
*/
virtual void deinit() = 0;
/**
* @brief Call before deinit, stops all incoming requests
*
*/
virtual void stop() = 0;
/**
* @brief Register a display buffer
*
* @param vaddr
* @param extent
* @param pitch
* @param format
* @return true
* @return false
*/
virtual bool registerDisplayBuffer(uint64_t vaddr, VkExtent2D extent, uint32_t pitch, VkFormat format) = 0;
/**
* @brief Notify a gpu visible memory range
*
* @return true: Memory has been allocated successfully
*/
virtual bool notify_allocHeap(uint64_t vaddr, uint64_t size, int memoryProtection) = 0;
/**
* @brief Checks if the vaddr is gpu memory (prev notify_allocHeap)
*
* @param vaddr
* @return true is gpu local memory
* @return false
*/
virtual bool isGPULocal(uint64_t vaddr) = 0;
/**
* @brief Records the copy commands for copying the displayBuffer to the dstImage
*
* @param vaddr
* @param cmdBuffer
* @param dstImage
* @param dstExtent
* @return true success
*/
virtual bool copyDisplayBuffer(uint64_t vaddr, VkCommandBuffer cmdBuffer, VkImage dstImage, VkExtent2D dstExtent) = 0;
};

View File

@ -1,35 +0,0 @@
#pragma once
#include <stdint.h>
namespace Pm4 {
#define PM4_GET(u, r, f) (((u) >> Pm4::r##_##f##_SHIFT) & Pm4::r##_##f##_MASK)
enum class Custom : uint32_t {
R_VS,
R_PS,
R_DRAW_INDEX,
R_DRAW_INDEX_AUTO,
R_DRAW_INDEX_OFFSET,
R_DRAW_RESET,
R_WAIT_FLIP_DONE,
R_CS,
R_DISPATCH_DIRECT,
R_DISPATCH_RESET,
R_DISPATCH_WAIT_MEM,
R_PUSH_MARKER,
R_POP_MARKER,
R_VS_EMBEDDED,
R_PS_EMBEDDED,
R_VS_UPDATE,
R_PS_UPDATE,
R_VGT_CONTROL,
R_NUM_COMMANDS, // used for array size
};
constexpr uint32_t create(uint16_t len, Custom baseIndex) {
return 0x40000000u | (((static_cast<uint16_t>(len) - 2u) & 0x3fffu) << 16u) | (uint32_t)baseIndex;
}
} // namespace Pm4

View File

@ -1,112 +0,0 @@
#pragma once
#include <stdint.h>
struct SceKernelMemParam {
uint64_t* sceKernelExtendedPageTable;
uint64_t* sceKernelFlexibleMemorySize;
uint8_t* sceKernelExtendedMemory1;
uint64_t const* sceKernelExtendedGpuPageTable;
uint8_t const* sceKernelExtendedMemory2;
uint64_t const* sceKernelExtendedCpuPageTable;
};
struct SceKernelFsParam {
uint64_t size;
uint64_t* sceKernelFsDupDent;
void* sceWorkspaceUpdateMode;
void* sceTraceAprNameMode;
};
struct SceMallocReplace {
uint64_t size = sizeof(SceMallocReplace);
uint64_t unknown1 = 1;
void* malloc_init = nullptr;
void* malloc_finalize = nullptr;
void* malloc = nullptr;
void* free = nullptr;
void* calloc = nullptr;
void* realloc = nullptr;
void* memalign = nullptr;
void* reallocalign = nullptr;
void* posix_memalign = nullptr;
void* malloc_stats = nullptr;
void* malloc_stats_fast = nullptr;
void* malloc_usable_size = nullptr;
};
struct SceLibcNewReplace {
uint64_t Size = sizeof(SceLibcNewReplace);
uint64_t Unknown1 = 0x2;
void* user_new = nullptr;
void* user_new_nothrow = nullptr;
void* user_new_array = nullptr;
void* user_new_array_nothrow = nullptr;
void* user_delete = nullptr;
void* user_delete_nothrow = nullptr;
void* user_delete_array = nullptr;
void* user_delete_array_nothrow = nullptr;
// optional
// void* user_delete_3= nullptr;
// void* user_delete_4= nullptr;
// void* user_delete_array_3= nullptr;
// void* user_delete_array_4= nullptr;
};
struct SceLibcParam0 {
uint64_t Size;
uint32_t entry_count;
};
struct SceLibcParam1: SceLibcParam0 {
uint32_t SceLibcInternalHeap; //(entry_count > 1)
uint32_t* sceLibcHeapSize;
uint32_t* sceLibcHeapDelayedAlloc;
uint32_t* sceLibcHeapExtendedAlloc;
uint32_t* sceLibcHeapInitialSize;
SceMallocReplace* _sceLibcMallocReplace;
SceLibcNewReplace* _sceLibcNewReplace;
};
struct SceLibcParam2: SceLibcParam1 {
uint64_t* sceLibcHeapHighAddressAlloc; //(entry_count > 2)
uint32_t* Need_sceLibc;
};
struct SceLibcParam3: SceLibcParam2 {
uint64_t* sceLibcHeapMemoryLock; //(entry_count > 4)
uint64_t* sceKernelInternalMemorySize;
uint64_t* _sceLibcMallocReplaceForTls;
};
struct SceLibcParam4: SceLibcParam3 {
uint64_t* sceLibcMaxSystemSize; //(entry_count > 7)
};
struct SceLibcParam5: SceLibcParam4 {
uint64_t* sceLibcHeapDebugFlags; //(entry_count > 8)
uint32_t* sceLibcStdThreadStackSize;
void* Unknown3;
uint32_t* sceKernelInternalMemoryDebugFlags;
uint32_t* sceLibcWorkerThreadNum;
uint32_t* sceLibcWorkerThreadPriority;
uint32_t* sceLibcThreadUnnamedObjects;
};
struct ProcParam {
struct {
uint64_t size; /// 0x50
uint32_t magic; /// "orbi" (0x4942524F)
uint32_t entryCount; /// >=1
uint64_t sdkVersion; /// 0x4508101
} header;
char const* sceProcessName;
char const* sceUserMainThreadName;
uint32_t const* sceUserMainThreadPriority;
uint32_t const* sceUserMainThreadStackSize;
SceLibcParam0* PSceLibcParam;
SceKernelMemParam* _sceKernelMemParam;
SceKernelFsParam* _sceKernelFsParam;
uint32_t* sceProcessPreloadEnabled;
uint64_t* unknown;
};

View File

@ -1,3 +0,0 @@
## Exports
Exports currently provided by the emulator itself.

View File

@ -1,125 +0,0 @@
#pragma once
#include "utility/utility.h"
#include <filesystem>
#include <stdint.h>
using cxa_destructor_func_t = void (*)(void*);
struct CxaDestructor {
cxa_destructor_func_t destructor_func;
void* destructor_object;
};
#pragma pack(1)
struct alignas(32) EntryParams {
int argc = 0;
uint32_t pad = 0;
const char* argv[3] = {0, 0, 0};
};
struct ModulInfo {
uint64_t address;
uint64_t size;
uint64_t procParamAddr;
};
struct SceKernelModuleSegmentInfo {
uint64_t address;
uint32_t size;
int prot;
};
struct SceKernelModuleInfo {
uint64_t size = sizeof(SceKernelModuleInfo);
char name[255];
SceKernelModuleSegmentInfo segments[3];
uint32_t segment_count;
uint32_t ref_count;
uint8_t fingerprint[20];
};
struct SceKernelModuleInfoEx {
uint64_t size = sizeof(SceKernelModuleInfoEx);
char name[255];
int id;
uint32_t tls_index;
void* tls_init_addr;
uint32_t tls_init_size;
uint32_t tls_size;
uint32_t tls_offset;
uint32_t tls_align;
uint64_t init_proc_addr;
uint64_t fini_proc_addr;
uint64_t reserved1 = 0;
uint64_t reserved2 = 0;
void* eh_frame_hdr_addr;
void* eh_frame_addr;
uint32_t eh_frame_hdr_size;
uint32_t eh_frame_size;
SceKernelModuleSegmentInfo segments[3];
uint32_t segment_count;
uint32_t ref_count;
};
#pragma pack()
class IRuntimeExport {
CLASS_NO_COPY(IRuntimeExport);
CLASS_NO_MOVE(IRuntimeExport);
protected:
IRuntimeExport() = default;
public:
virtual ~IRuntimeExport() = default;
virtual int loadStartModule(std::filesystem::path const& path, size_t args, const void* argp, int* pRes) = 0;
virtual void initTLS(uint8_t* obj) = 0;
virtual uint64_t getTLSStaticBlockSize() const = 0;
virtual EntryParams const* getEntryParams() const = 0;
virtual void cxa_add_atexit(CxaDestructor&&, int moduleId) = 0;
virtual void cxa_finalize(int moduleId) = 0;
virtual ModulInfo mainModuleInfo() const = 0;
virtual SceKernelModuleInfoEx const* getModuleInfoEx(uint64_t vaddr) const = 0;
virtual std::vector<int> getModules() const = 0;
/**
* @brief Get the symbols address
*
* @param symName nid
* @param libName
* @param modName
* @return void* address
*/
virtual void* getSymbol(std::string_view symName, std::string_view libName, std::string_view modName) const = 0;
/**
* @brief Searches the module for the symbol
*
* @param module moduleId
* @param symName
* @param isNid
* @return void*
*/
virtual void* getSymbol(int module, std::string_view symName, bool isNid) const = 0;
// ### THREAD LOCAL STORAGE
virtual void* getTLSAddr(uint32_t index, uint64_t offset) = 0;
virtual void setTLSKey(uint32_t key, const void* value) = 0;
virtual void* getTLSKey(uint32_t key) = 0;
virtual uint32_t createTLSKey(void* destructor) = 0;
virtual void deleteTLSKey(uint32_t key) = 0;
virtual void destroyTLSKeys(uint8_t* obj) = 0;
// - ### TLS
};

File diff suppressed because it is too large Load Diff

View File

@ -1,33 +0,0 @@
#define __APICALL_EXTERN
#include "imports.h"
#include "imports_func.h"
#include "imports_runtime.h"
#undef __APICALL_EXTERN
#include "core/imports/exports/runtimeExport.h"
#include "utility/utility.h"
#include <assert.h>
// Change if called from construct is needed
static createGraphics_t g_createGraphics = nullptr;
static runtimeExport_t g_runtimeExport = nullptr;
std::unique_ptr<IGraphics> createGraphics(IEventsGraphics& listener, VkDevice device, VkPhysicalDevice physDev, VkInstance instance) {
assert(g_createGraphics != nullptr);
return g_createGraphics(listener, device, physDev, instance);
}
IRuntimeExport* accessRuntimeExport() {
assert(g_runtimeExport != nullptr);
return g_runtimeExport;
}
void setCallback_createGraphics(createGraphics_t cb) {
g_createGraphics = cb;
}
void setCallback_accessRuntimeExport(runtimeExport_t cb) {
g_runtimeExport = cb;
}

View File

@ -1,25 +0,0 @@
#pragma once
#include "exports/graphics.h"
#include <memory>
#include <stdint.h>
#include <vulkan/vulkan_core.h>
#if defined(__APICALL_EXTERN)
#define __APICALL __declspec(dllexport)
#elif defined(__APICALL_IMPORT)
#define __APICALL __declspec(dllimport)
#else
#define __APICALL
#endif
using createGraphics_t = std::unique_ptr<IGraphics> (*)(IEventsGraphics& listener, VkDevice device, VkPhysicalDevice physDev, VkInstance instance);
using runtimeExport_t = class IRuntimeExport*;
__APICALL void setCallback_createGraphics(createGraphics_t);
__APICALL void setCallback_accessRuntimeExport(runtimeExport_t);
#undef __APICALL

View File

@ -1,16 +0,0 @@
#if defined(__APICALL_EXTERN)
#define __APICALL __declspec(dllexport)
#elif defined(__APICALL_IMPORT)
#define __APICALL __declspec(dllimport)
#else
#define __APICALL
#endif
/**
* @brief Called at exit
*
*/
__APICALL void core_exit();
#undef __APICALL

View File

@ -1,28 +0,0 @@
#include "exports/graphics.h"
#include <memory>
#include <stdint.h>
#include <vulkan/vulkan_core.h>
#if defined(__APICALL_EXTERN)
#define __APICALL __declspec(dllexport)
#elif defined(__APICALL_IMPORT)
#define __APICALL __declspec(dllimport)
#else
#define __APICALL
#endif
// All communication strictly goes through the core library. (May change later)
/**
* @brief Create a Graphics object.
*
* @param listener
* @param device
* @param physDev
* @param instance
* @return object
*/
__APICALL std::unique_ptr<IGraphics> createGraphics(IEventsGraphics& listener, VkDevice device, VkPhysicalDevice physDev, VkInstance instance);
#undef __APICALL

View File

@ -1,14 +0,0 @@
#pragma once
#include "exports/runtimeExport.h"
#if defined(__APICALL_EXTERN)
#define __APICALL __declspec(dllexport)
#elif defined(__APICALL_IMPORT)
#define __APICALL __declspec(dllimport)
#else
#define __APICALL
#endif
__APICALL IRuntimeExport* accessRuntimeExport();
#undef __APICALL

View File

@ -1,5 +0,0 @@
add_library(initParams OBJECT
initParams.cpp
)
add_dependencies(initParams third_party boost)

View File

@ -1,80 +0,0 @@
#define __APICALL_EXTERN
#include "initParams.h"
#undef __APICALL_EXTERN
#include <boost/program_options.hpp>
#include <iostream>
#include <memory>
struct InitParamsPimpl {
boost::program_options::variables_map m_vm;
InitParamsPimpl() = default;
};
InitParams::InitParams() {
_pImpl = std::make_unique<InitParamsPimpl>().release();
}
InitParams::~InitParams() {
delete _pImpl;
}
bool InitParams::init(int argc, char** argv) {
namespace po = boost::program_options;
po::options_description desc("Allowed options");
desc.add_options()
// clang-format off
("help,h", "Help")
("d", "Wait for debugger")
("vkValidation", "Enable vulkan validation layers")
("vsync", po::value<bool>()->default_value(true), "Enable vulkan validation layers")
("file", po::value<std::string>(), "fullpath to applications binary")
("root", po::value<std::string>(), "Applications root")
("update", po::value<std::string>(), "update files folder")
// clang-format on
;
auto& vm = _pImpl->m_vm;
po::store(po::parse_command_line(argc, argv, desc), vm);
po::notify(vm);
if (vm.count("help")) {
std::cout << desc << '\n';
return false;
}
if (!vm.count("file")) {
std::cout << "--file missing\n";
return false;
}
return true;
}
InitParams* accessInitParams() {
static InitParams obj;
return &obj;
}
bool InitParams::isDebug() {
return _pImpl->m_vm.count("d");
}
std::string InitParams::getApplicationPath() {
return _pImpl->m_vm.count("file") ? _pImpl->m_vm["file"].as<std::string>() : std::string();
}
std::string InitParams::getApplicationRoot() {
return _pImpl->m_vm.count("root") ? _pImpl->m_vm["root"].as<std::string>() : std::string();
}
std::string InitParams::getUpdateRoot() {
return _pImpl->m_vm.count("update") ? _pImpl->m_vm["update"].as<std::string>() : std::string();
}
bool InitParams::enableValidation() {
return _pImpl->m_vm.count("vkValidation");
}
bool InitParams::useVSYNC() {
return _pImpl->m_vm["vsync"].as<bool>();
}

View File

@ -1,40 +0,0 @@
#pragma once
#include "utility/utility.h"
#include <string>
struct InitParamsPimpl;
class InitParams {
CLASS_NO_COPY(InitParams);
CLASS_NO_MOVE(InitParams);
InitParamsPimpl* _pImpl = nullptr;
public:
InitParams();
bool init(int argc, char** argv);
// Functions
bool isDebug();
std::string getApplicationPath();
std::string getApplicationRoot();
std::string getUpdateRoot();
bool enableValidation();
bool useVSYNC();
~InitParams();
};
#if defined(__APICALL_EXTERN)
#define __APICALL __declspec(dllexport)
#elif defined(__APICALL_IMPORT)
#define __APICALL __declspec(dllimport)
#else
#define __APICALL
#endif
__APICALL InitParams* accessInitParams();
#undef __APICALL

View File

@ -1,10 +0,0 @@
add_library(kernel OBJECT
eventqueue.cpp
eventflag.cpp
errors.cpp
filesystem.cpp
pthread.cpp
semaphore.cpp
)
add_dependencies(kernel third_party boost)

View File

@ -1,13 +0,0 @@
#define __APICALL_EXTERN
#include "errors.h"
#undef __APICALL_EXTERN
int* getError_pthread() {
thread_local int g_errno = 0;
return &g_errno;
}
void setError_pthread(int error) {
// todo tracing stacktrace?
*getError_pthread() = error;
}

View File

@ -1,32 +0,0 @@
#pragma once
#include "modules_include/common.h"
#if defined(__APICALL_EXTERN)
#define __APICALL __declspec(dllexport)
#elif defined(__APICALL_IMPORT)
#define __APICALL __declspec(dllimport)
#else
#define __APICALL
#endif
__APICALL int* getError_pthread();
__APICALL void setError_pthread(int);
/**
* @brief Takes Kernel errors from gerErr(), converts and saves it
*
* @param result from gerErr()
* @return int32_t
*/
static int32_t POSIX_CALL(int32_t result) {
if (result >= 0) return result;
setError_pthread(result - (int32_t)0x80020000);
return -1;
}
static int32_t POSIX_SET(ErrCode error) {
setError_pthread((int32_t)error);
return -1;
}
#undef __APICALL

View File

@ -1,176 +0,0 @@
#define __APICALL_EXTERN
#include "eventflag.h"
#undef __APICALL_EXTERN
#include "logging.h"
#include "modules_include/common.h"
#include <boost/thread/condition.hpp>
#include <memory>
LOG_DEFINE_MODULE(KernelEventFlag);
namespace Kernel::EventFlag {
class KernelEventFlag: public IKernelEventFlag {
CLASS_NO_COPY(KernelEventFlag);
public:
KernelEventFlag(std::string const& name, bool single, bool /*fifo*/, uint64_t bits): m_singleThread(single), m_bits(bits), m_name(name) {};
virtual ~KernelEventFlag();
void set(uint64_t bits) final;
void clear(uint64_t bits) final;
void cancel(uint64_t bits, int* num_waiting_threads) final;
int wait(uint64_t bits, WaitMode wait_mode, ClearMode clear_mode, uint64_t* result, uint32_t* ptr_micros) final;
int poll(uint64_t bits, WaitMode wait_mode, ClearMode clear_mode, uint64_t* result) final {
uint32_t micros = 0;
return wait(bits, wait_mode, clear_mode, result, &micros);
}
private:
enum class Status { Set, Canceled, Deleted };
void setStatus(Status status) {
m_status = status;
if (status == Status::Set) m_cond_var_status.notify_all();
}
boost::mutex m_mutex_cond;
boost::condition_variable m_cond_var;
boost::condition_variable m_cond_var_status;
Status m_status = Status::Set;
int m_waitingThreads = 0;
bool m_singleThread = false;
std::string m_name;
uint64_t m_bits = 0;
};
KernelEventFlag::~KernelEventFlag() {
boost::unique_lock lock(m_mutex_cond);
m_cond_var_status.wait(lock, [this] { return m_status == Status::Set; });
m_status = Status::Deleted;
m_cond_var.notify_all();
m_cond_var_status.wait(lock, [this] { return m_waitingThreads == 0; });
}
void KernelEventFlag::set(uint64_t bits) {
boost::unique_lock lock(m_mutex_cond);
m_cond_var_status.wait(lock, [this] { return m_status == Status::Set; });
m_bits |= bits;
m_cond_var.notify_all();
}
void KernelEventFlag::clear(uint64_t bits) {
boost::unique_lock lock(m_mutex_cond);
m_cond_var_status.wait(lock, [this] { return m_status == Status::Set; });
m_bits &= bits;
}
void KernelEventFlag::cancel(uint64_t bits, int* num_waiting_threads) {
boost::unique_lock lock(m_mutex_cond);
m_cond_var_status.wait(lock, [this] { return m_status == Status::Set; });
if (num_waiting_threads != nullptr) {
*num_waiting_threads = m_waitingThreads;
}
setStatus(Status::Canceled);
m_bits = bits;
m_cond_var.notify_all();
m_cond_var_status.wait(lock, [this] { return m_waitingThreads == 0; });
setStatus(Status::Set);
}
int KernelEventFlag::wait(uint64_t bits, WaitMode wait_mode, ClearMode clear_mode, uint64_t* result, uint32_t* ptr_micros) {
LOG_USE_MODULE(KernelEventFlag);
boost::unique_lock lock(m_mutex_cond);
if (m_singleThread && m_waitingThreads > 0) {
return getErr(ErrCode::_EPERM);
}
uint32_t micros = 0;
bool infinitely = true;
if (ptr_micros != nullptr) {
micros = *ptr_micros;
infinitely = false;
}
auto const start = boost::chrono::system_clock::now();
++m_waitingThreads;
auto waitFunc = [this, wait_mode, bits] {
return (m_status == Status::Canceled || m_status == Status::Deleted || (wait_mode == WaitMode::And && (m_bits & bits) == bits) ||
(wait_mode == WaitMode::Or && (m_bits & bits) != 0));
};
if (infinitely) {
m_cond_var.wait(lock, waitFunc);
} else {
if (!m_cond_var.wait_for(lock, boost::chrono::microseconds(micros), waitFunc)) {
if (result != nullptr) {
*result = m_bits;
}
*ptr_micros = 0;
--m_waitingThreads;
return getErr(ErrCode::_ETIMEDOUT);
}
}
--m_waitingThreads;
m_cond_var_status.notify_all();
if (result != nullptr) {
*result = m_bits;
}
auto elapsed = boost::chrono::duration_cast<boost::chrono::microseconds>(boost::chrono::system_clock::now() - start).count();
if (result != nullptr) {
*result = m_bits;
}
if (ptr_micros != nullptr) {
*ptr_micros = (elapsed >= micros ? 0 : micros - elapsed);
// LOG_TRACE(L"wait %llu us -> %llu us", micros, elapsed);
}
// Aborted
if (m_status == Status::Canceled) {
return getErr(ErrCode::_ECANCELED);
} else if (m_status == Status::Deleted) {
return getErr(ErrCode::_EACCES);
}
// -
if (clear_mode == ClearMode::All) {
m_bits = 0;
} else if (clear_mode == ClearMode::Bits) {
m_bits &= ~bits;
}
return Ok;
}
IKernelEventFlag_t createEventFlag(std::string const& name, bool single, bool fifo, uint64_t bits) {
return std::make_unique<KernelEventFlag>(std::string(name), single, fifo, bits).release();
}
int deleteEventFlag(IKernelEventFlag_t ef) {
LOG_USE_MODULE(KernelEventFlag);
if (ef == nullptr) {
return getErr(ErrCode::_ESRCH);
}
LOG_INFO(L"Deleted ptr:0x%08llx", (uint64_t)ef);
delete ef;
return Ok;
}
} // namespace Kernel::EventFlag

View File

@ -1,44 +0,0 @@
#pragma once
#include "utility/utility.h"
namespace Kernel::EventFlag {
enum class ClearMode { None, All, Bits };
enum class WaitMode { And, Or };
class IKernelEventFlag {
CLASS_NO_COPY(IKernelEventFlag);
CLASS_NO_MOVE(IKernelEventFlag);
protected:
IKernelEventFlag() = default;
public:
virtual ~IKernelEventFlag() = default;
virtual void set(uint64_t bits) = 0;
virtual void clear(uint64_t bits) = 0;
virtual void cancel(uint64_t bits, int* num_waiting_threads) = 0;
virtual int wait(uint64_t bits, WaitMode wait_mode, ClearMode clear_mode, uint64_t* result, uint32_t* ptr_micros) = 0;
virtual int poll(uint64_t bits, WaitMode wait_mode, ClearMode clear_mode, uint64_t* result) = 0;
};
using IKernelEventFlag_t = IKernelEventFlag*;
#if defined(__APICALL_EXTERN)
#define __APICALL __declspec(dllexport)
#elif defined(__APICALL_IMPORT)
#define __APICALL __declspec(dllimport)
#else
#define __APICALL
#endif
__APICALL IKernelEventFlag_t createEventFlag(std::string const& name, bool single, bool fifo, uint64_t bits);
__APICALL int deleteEventFlag(IKernelEventFlag_t ef);
#undef __APICALL
} // namespace Kernel::EventFlag

View File

@ -1,296 +0,0 @@
#define __APICALL_EXTERN
#include "eventqueue.h"
#undef __APICALL_EXTERN
#include "logging.h"
#include "modules_include/common.h"
#include <algorithm>
#include <boost/chrono.hpp>
#include <boost/thread/condition.hpp>
#include <boost/thread/mutex.hpp>
#include <chrono>
#include <list>
#include <memory>
#include <mutex>
#include <string>
#include <unordered_map>
namespace Kernel {
namespace EventQueue {
LOG_DEFINE_MODULE(EventQueue);
class KernelEqueue: public IKernelEqueue {
public:
KernelEqueue() = default;
virtual ~KernelEqueue();
[[nodiscard]] std::string const& getName() const { return m_name; }
void setName(std::string&& name) { m_name.swap(name); }
int waitForEvents(KernelEvent_t ev, int num, SceKernelUseconds const* micros);
int pollEvents(KernelEvent_t ev, int num) {
std::unique_lock lock(m_mutex_cond);
auto ret = getTriggeredEvents(ev, num);
return ret;
}
int wait(KernelEvent_t ev, int num, int* out, const SceKernelUseconds* time) final;
int addUserEvent(int ident) final;
int addUserEventEdge(int ident) final;
int triggerUserEvent(uintptr_t ident, void* udata) final;
int addEvent(const KernelEqueueEvent& event) final;
int triggerEvent(uintptr_t ident, int16_t filter, void* trigger_data) final;
int deleteEvent(uintptr_t ident, int16_t filter) final;
private:
int getTriggeredEvents(KernelEvent_t ev, int num);
std::list<KernelEqueueEvent> m_events; // We dont ecpext many events ~5
boost::mutex m_mutex_cond;
std::mutex m_mutex_int;
boost::condition_variable m_cond_var;
std::string m_name;
bool m_closed = false;
};
KernelEqueue::~KernelEqueue() {
std::unique_lock const lock(m_mutex_cond);
/*
for (auto& ev: m_events) {
if (ev.filter.delete_event_func != nullptr) {
ev.filter.delete_event_func(this, &ev);
}
}*/
m_closed = true;
m_cond_var.notify_all();
}
int KernelEqueue::waitForEvents(KernelEvent_t ev, int num, SceKernelUseconds const* micros) {
LOG_USE_MODULE(EventQueue);
// LOG_TRACE(L"->waitForEvents: ident:0x%08llx num:%d", ev->ident, num);
boost::unique_lock lock(m_mutex_cond);
int ret = 0;
if (micros != nullptr && *micros > 0) {
using namespace std::chrono_literals;
if (m_cond_var.wait_for(lock, boost::chrono::microseconds(*(decltype(micros))micros), [&] {
ret = getTriggeredEvents(ev, num);
return (ret > 0) | m_closed;
}) == 0) {
// LOG_TRACE(L"waitForEvents timeout");
}
} else {
m_cond_var.wait(lock, [&] {
ret = getTriggeredEvents(ev, num);
return (ret > 0) | m_closed;
});
}
// LOG_TRACE(L"<-waitForEvents: ident:0x%08llx ret:%d", ev->ident, ret);
return ret;
}
int KernelEqueue::getTriggeredEvents(KernelEvent_t eventList, int num) {
LOG_USE_MODULE(EventQueue);
int countTriggered = 0;
for (auto& ev: m_events) {
if (ev.triggered) {
eventList[countTriggered++] = ev.event;
if ((((uint32_t)ev.event.flags & (uint32_t)EventFlags::EV_CLEAR) != 0) && ev.filter.reset_func != nullptr) {
ev.filter.reset_func(&ev);
}
LOG_TRACE(L"Event Triggered: ident:0x%08llx, filter:%d", (uint64_t)ev.event.ident, ev.event.filter);
if (countTriggered >= num) {
break;
}
}
}
return countTriggered;
}
void event_trigger_func(Kernel::EventQueue::KernelEqueueEvent* event, void* trigger_data) {
event->triggered = true;
event->event.fflags++;
event->event.udata = trigger_data;
}
void event_reset_func(Kernel::EventQueue::KernelEqueueEvent* event) {
event->triggered = false;
event->event.fflags = 0;
event->event.udata = 0;
}
void event_delete_func(Kernel::EventQueue::IKernelEqueue_t eq, Kernel::EventQueue::KernelEqueueEvent* event) {}
int createEqueue(IKernelEqueue_t* eq, const char* name) {
LOG_USE_MODULE(EventQueue);
if (eq == nullptr || name == nullptr) {
return getErr(ErrCode::_EINVAL);
}
auto item = std::make_unique<KernelEqueue>();
item->setName(std::string(name));
LOG_INFO(L"+EventQueue (%S)", item->getName().c_str());
*eq = item.release();
return Ok;
}
int deleteEqueue(IKernelEqueue_t eq) {
LOG_USE_MODULE(EventQueue);
if (eq == nullptr) {
return getErr(ErrCode::_EBADF);
}
LOG_INFO(L"-EventQueue (%S)", ((KernelEqueue*)eq)->getName().c_str());
delete (eq);
return Ok;
}
int KernelEqueue::wait(KernelEvent_t ev, int num, int* out, const SceKernelUseconds* time) {
if (ev == nullptr) {
return getErr(ErrCode::_EFAULT);
}
if (num < 1) {
return getErr(ErrCode::_EINVAL);
}
if (time == nullptr) {
*out = waitForEvents(ev, num, nullptr);
if (*out == 0) {
return getErr(ErrCode::_ETIMEDOUT);
}
} else {
*out = waitForEvents(ev, num, time);
if (*out == 0 && *time > 0) {
return getErr(ErrCode::_ETIMEDOUT);
}
}
return Ok;
}
int KernelEqueue::addUserEvent(int ident) {
return addEvent(Kernel::EventQueue::KernelEqueueEvent {.triggered = false,
.event =
{
.ident = ident,
.filter = Kernel::EventQueue::KERNEL_EVFILT_USER,
.flags = Kernel::EventQueue::EventFlags::EV_NONE,
.fflags = 0,
.data = 0,
.udata = 0,
},
.filter {
.data = nullptr,
.trigger_func = event_trigger_func,
.reset_func = event_reset_func,
.delete_event_func = event_delete_func,
}});
}
int KernelEqueue::addUserEventEdge(int ident) {
return addEvent(Kernel::EventQueue::KernelEqueueEvent {.triggered = false,
.event =
{
.ident = ident,
.filter = Kernel::EventQueue::KERNEL_EVFILT_USER,
.flags = Kernel::EventQueue::EventFlags::EV_CLEAR,
.fflags = 0,
.data = 0,
.udata = 0,
},
.filter {
.data = nullptr,
.trigger_func = event_trigger_func,
.reset_func = event_reset_func,
.delete_event_func = event_delete_func,
}});
}
int KernelEqueue::triggerUserEvent(uintptr_t ident, void* udata) {
LOG_USE_MODULE(EventQueue);
LOG_TRACE(L"triggerUserEvent: ident:0x%08llx", (uint64_t)ident);
return triggerEvent(ident, Kernel::EventQueue::KERNEL_EVFILT_USER, udata);
}
int KernelEqueue::addEvent(const KernelEqueueEvent& event) {
LOG_USE_MODULE(EventQueue);
LOG_INFO(L"(%S) Add Event: ident:0x%08llx, filter:%d", m_name.c_str(), (uint64_t)event.event.ident, event.event.filter);
std::unique_lock const lock(m_mutex_cond);
auto it = std::find_if(m_events.begin(), m_events.end(),
[=](KernelEqueueEvent const& ev) { return ev.event.ident == event.event.ident && ev.event.filter == event.event.filter; });
if (it != m_events.end()) {
*it = event;
} else {
m_events.push_back(event);
}
if (event.triggered) {
m_cond_var.notify_all();
}
return Ok;
}
int KernelEqueue::triggerEvent(uintptr_t ident, int16_t filter, void* trigger_data) {
LOG_USE_MODULE(EventQueue);
LOG_TRACE(L"triggerEvent: ident:0x%08llx, filter:%d", (uint64_t)ident, filter);
std::unique_lock const lock(m_mutex_cond);
auto it = std::find_if(m_events.begin(), m_events.end(), [=](KernelEqueueEvent const& ev) { return ev.event.ident == ident && ev.event.filter == filter; });
if (it != m_events.end()) {
if (it->filter.trigger_func != nullptr) {
it->filter.trigger_func(&(*it), trigger_data);
} else {
it->triggered = true;
}
m_cond_var.notify_all();
return Ok;
}
return getErr(ErrCode::_ENOENT);
}
int KernelEqueue::deleteEvent(uintptr_t ident, int16_t filter) {
LOG_USE_MODULE(EventQueue);
LOG_INFO(L"deleteEvent: ident:0x%08llx, filter:%llu", (uint64_t)ident, filter);
std::unique_lock const lock(m_mutex_cond);
auto it = std::find_if(m_events.begin(), m_events.end(), [=](KernelEqueueEvent const& ev) { return ev.event.ident == ident && ev.event.filter == filter; });
if (it != m_events.end()) {
if (it->filter.delete_event_func != nullptr) {
it->filter.delete_event_func(this, &(*it));
}
m_events.erase(it);
return Ok;
}
return getErr(ErrCode::_ENOENT);
}
} // namespace EventQueue
} // namespace Kernel

View File

@ -1,41 +0,0 @@
#pragma once
#include "eventqueue_types.h"
#include "modules_include/common.h"
#include "utility/utility.h"
namespace Kernel::EventQueue {
class IKernelEqueue {
CLASS_NO_COPY(IKernelEqueue);
CLASS_NO_MOVE(IKernelEqueue);
protected:
IKernelEqueue() = default;
public:
virtual ~IKernelEqueue() = default;
virtual int wait(KernelEvent_t ev, int num, int* out, const SceKernelUseconds* timo) = 0;
virtual int addUserEvent(int ident) = 0;
virtual int addUserEventEdge(int ident) = 0;
virtual int triggerUserEvent(uintptr_t ident, void* udata) = 0;
virtual int addEvent(const KernelEqueueEvent& event) = 0;
virtual int triggerEvent(uintptr_t ident, int16_t filter, void* trigger_data) = 0;
virtual int deleteEvent(uintptr_t ident, int16_t filter) = 0;
};
#if defined(__APICALL_EXTERN)
#define __APICALL __declspec(dllexport)
#elif defined(__APICALL_IMPORT)
#define __APICALL __declspec(dllimport)
#else
#define __APICALL
#endif
__APICALL int createEqueue(IKernelEqueue_t* eq, const char* name);
__APICALL int deleteEqueue(IKernelEqueue_t eq);
#undef __APICALL
} // namespace Kernel::EventQueue

View File

@ -1,55 +0,0 @@
#pragma once
#include <stdint.h>
namespace Kernel::EventQueue {
constexpr int16_t KERNEL_EVFILT_TIMER = -7;
constexpr int16_t KERNEL_EVFILT_READ = -1;
constexpr int16_t KERNEL_EVFILT_WRITE = -2;
constexpr int16_t KERNEL_EVFILT_USER = -11;
constexpr int16_t KERNEL_EVFILT_FILE = -4;
constexpr int16_t KERNEL_EVFILT_GRAPHICS = -14;
constexpr int16_t KERNEL_EVFILT_VIDEO_OUT = -13;
constexpr int16_t KERNEL_EVFILT_HRTIMER = -15;
enum class EventFlags : uint32_t {
EV_NONE = 0,
EV_ONESHOT = 0x10, // only report one occurrence
EV_CLEAR = 0x20, // clear event state after reporting
EV_RECEIPT = 0x40, // force EV_ERROR on success, data=0
EV_DISPATCH = 0x80, // disable event after reporting
EV_SYSFLAGS = 0xF000, // reserved by system
EV_FLAG1 = 0x2000, // filter-specific flag
};
struct KernelEvent {
int ident = 0;
int16_t filter = 0;
EventFlags flags = EventFlags::EV_NONE;
uint32_t fflags = 0;
intptr_t data = 0;
void* udata = nullptr;
};
struct KernelEqueueEvent;
class IKernelEqueue;
using IKernelEqueue_t = IKernelEqueue*;
using KernelEvent_t = KernelEvent*;
using trigger_func_t = void (*)(KernelEqueueEvent* event, void* trigger_data);
using reset_func_t = void (*)(KernelEqueueEvent* event);
using delete_func_t = void (*)(IKernelEqueue_t eq, KernelEqueueEvent* event);
struct KernelFilter {
void* data = nullptr;
trigger_func_t trigger_func = nullptr;
reset_func_t reset_func = nullptr;
delete_func_t delete_event_func = nullptr;
};
struct KernelEqueueEvent {
bool triggered = false;
KernelEvent event;
KernelFilter filter;
};
} // namespace Kernel::EventQueue

View File

@ -1,689 +0,0 @@
#include "filesystem.h"
#include "core/dmem/dmem.h"
#include "core/fileManager/fileManager.h"
#include "core/fileManager/types/type_file.h"
#include "core/fileManager/types/type_random.h"
#include "core/memory/memory.h"
#include "logging.h"
#include <assert.h>
#include <unordered_map>
#include <windows.h>
LOG_DEFINE_MODULE(filesystem);
namespace {
std::pair<uint32_t, uint32_t> convProtection(int prot) {
switch (prot & 0xf) {
case 0: return {PAGE_NOACCESS, 0};
case 1: return {PAGE_READONLY, FILE_MAP_READ};
case 2:
case 3: return {PAGE_READWRITE, FILE_MAP_ALL_ACCESS};
case 4: return {PAGE_EXECUTE, FILE_MAP_ALL_ACCESS | FILE_MAP_EXECUTE};
case 5: return {PAGE_EXECUTE_READ, FILE_MAP_EXECUTE | FILE_MAP_READ};
case 6:
case 7: return {PAGE_EXECUTE_READWRITE, FILE_MAP_ALL_ACCESS};
}
return {PAGE_NOACCESS, 0};
}
std::unique_ptr<IFile> createType_dev(std::filesystem::path path, std::ios_base::openmode mode) {
LOG_USE_MODULE(filesystem);
// todo: /dev/rng? No ioctl for now
if (path == "/dev/urandom" || path == "/dev/urandom") {
return createType_random();
} else { // todo: other devices
LOG_CRIT(L"%S: unknown device!", path.c_str());
}
return {};
}
int open_dev(const char* path, filesystem::SceOpen flags, filesystem::SceKernelMode kernelMode) {
LOG_USE_MODULE(filesystem);
std::ios_base::openmode mode = std::ios::binary;
switch (flags.mode) {
case filesystem::SceOpenMode::RDONLY: mode |= std::ios::in; break;
case filesystem::SceOpenMode::WRONLY: mode |= std::ios::out; break;
case filesystem::SceOpenMode::RDWR: mode |= std::ios::out | std::ios::in; break;
}
auto file = createType_dev(path, mode);
int const handle = accessFileManager().addFile(std::move(file), path);
LOG_INFO(L"OpenFile[%d]: %s mode:0x%lx(0x%lx)", handle, path, mode, kernelMode);
return handle;
}
} // namespace
namespace filesystem {
int mmap(void* addr, size_t len, int prot, SceMap flags, int fd, int64_t offset, void** res) {
LOG_USE_MODULE(filesystem);
// Mapping cases
if (flags.mode == SceMapMode::FIXED) {
LOG_ERR(L"todo: Mmap fixed 0x%08llx len:0x%08llx prot:%d flags:%d fd:%d offset:%lld", addr, len, prot, flags, fd, offset);
return getErr(ErrCode::_EINVAL);
}
if (flags.mode == SceMapMode::VOID_) {
// reserve memory
LOG_ERR(L"todo: Mmap void 0x%08llx len:0x%08llx prot:%d flags:%d fd:%d offset:%lld", addr, len, prot, flags, fd, offset);
return getErr(ErrCode::_EINVAL);
}
if (flags.type == SceMapType::ANON) {
*res = (void*)accessFlexibleMemory().alloc((uint64_t)addr, len, prot); // registers mapping (flexible)
LOG_DEBUG(L"Mmap anon addr:0x%08llx len:0x%08llx prot:%d flags:%d fd:%d offset:%lld -> out:0x%08llx", addr, len, prot, flags, fd, offset, *res);
return getErr(ErrCode::_EINVAL);
}
// -
// Do file mapping
if (fd < FILE_DESCRIPTOR_MIN) {
LOG_DEBUG(L"Mmap error addr:0x%08llx len:0x%08llx prot:%d flags:%08llx fd:%d offset:%lld -> out:0x%08llx", addr, len, prot, flags, fd, offset, *res);
return getErr(ErrCode::_EPERM);
}
auto const [fileProt, viewProt] = convProtection(prot);
auto file = accessFileManager().accessFile(fd);
if (file->getNative() == nullptr) {
LOG_ERR(L"Mmap fd:%d no nativeHandle", fd);
return getErr(ErrCode::_EMFILE);
}
uint64_t mappingLength = (fileProt == PAGE_READONLY) || (fileProt == PAGE_EXECUTE_READ) ? 0 : len;
auto lenSplit = std::bit_cast<std::array<uint32_t, 2>>(mappingLength);
HANDLE hFileMapping = CreateFileMapping(file->getNative(),
NULL, // default security
fileProt,
lenSplit[1], // maximum object size (high-order DWORD)
lenSplit[0], // maximum object size (low-order DWORD)
NULL); // name of mapping object
*res = nullptr;
if (hFileMapping == NULL) {
LOG_ERR(L"Mmap CreateFileMapping == NULL for| 0x%08llx len:0x%08llx prot:%d flags:%0lx fd:%d offset:%lld err:%04x", addr, mappingLength, prot, flags, fd,
offset, GetLastError());
} else {
auto offsetSplit = std::bit_cast<std::array<uint32_t, 2>>(offset);
*res = MapViewOfFile(hFileMapping, // handle to file mapping object
viewProt, // read/write permission
offsetSplit[1], // high offset
offsetSplit[0], // low offset
mappingLength); // number of bytes to map
if (*res == NULL) {
LOG_ERR(L"Mmap MapViewOfFile == NULL for| 0x%08llx len:0x%08llx prot:%d flags:%0lx fd:%d offset:%lld err:%04x", addr, mappingLength, prot, flags, fd,
offset, GetLastError());
} else {
LOG_DEBUG(L"Mmap addr:0x%08llx len:0x%08llx prot:%d flags:%0lx fd:%d offset:%lld -> out:0x%08llx", addr, mappingLength, prot, flags, fd, offset, *res);
registerMapping((uint64_t)*res, MappingType::File);
}
}
CloseHandle(hFileMapping); // is kept open internally until mapView is unmapped
if (*res == nullptr) {
return getErr(ErrCode::_EACCES);
}
return Ok;
}
int munmap(void* addr, size_t len) {
LOG_USE_MODULE(filesystem);
// unregister and then delete
auto type = unregisterMapping((uint64_t)addr);
switch (type) {
case MappingType::File: {
return UnmapViewOfFile(addr) != 0 ? Ok : -1;
} break;
case MappingType::Flexible: {
return accessFlexibleMemory().destroy((uint64_t)addr, len) != 0 ? Ok : -1;
} break;
case MappingType::Fixed: {
LOG_ERR(L"tod munmap Fixed 0x%08llx 0x%08llx", addr, len);
} break;
case MappingType::None: {
LOG_ERR(L"munmap unkown 0x%08llx 0x%08llx", addr, len);
} break;
}
return -1;
}
size_t read(int handle, void* buf, size_t nbytes) {
LOG_USE_MODULE(filesystem);
auto file = accessFileManager().accessFile(handle);
if (file == nullptr) {
return getErr(ErrCode::_EBADF);
}
auto const count = file->read((char*)buf, nbytes);
LOG_TRACE(L"KernelRead[%d]: 0x%08llx:%llu read(%lld)", handle, (uint64_t)buf, nbytes, count);
return count;
}
int64_t write(int handle, const void* buf, size_t nbytes) {
LOG_USE_MODULE(filesystem);
auto file = accessFileManager().accessFile(handle);
if (file == nullptr) {
LOG_ERR(L"KernelWrite[%d] file==nullptr: 0x%08llx:%llu", handle, (uint64_t)buf, nbytes);
return getErr(ErrCode::_EBADF);
}
size_t const count = file->write((char*)buf, nbytes);
LOG_TRACE(L"KernelWrite[%d]: 0x%08llx:%llu count:%llu", handle, (uint64_t)buf, nbytes, count);
if (auto err = file->getErr(); err != Ok) {
return getErr((ErrCode)err);
}
return count;
}
int open(const char* path, SceOpen flags, SceKernelMode kernelMode) {
LOG_USE_MODULE(filesystem);
if (path == nullptr) {
return getErr(ErrCode::_EINVAL);
}
if (std::string_view(path).starts_with("/dev")) {
return open_dev(path, flags, kernelMode);
}
assert(!flags.fsync && !flags.excl && !flags.dsync && !flags.direct);
auto mapped = accessFileManager().getMappedPath(path);
if (!mapped) {
return getErr(ErrCode::_EACCES);
}
auto const mappedPath = mapped.value();
// handle excl (file already exists)
if (flags.excl && flags.create && std::filesystem::exists(path)) {
return getErr(ErrCode::_EEXIST);
}
// -
bool isDir = std::filesystem::is_directory(mappedPath);
if (flags.directory || isDir) {
if (!isDir) {
LOG_WARN(L"Directory doesn't exist: %s", mappedPath.c_str());
return getErr(ErrCode::_ENOTDIR);
}
if (!std::filesystem::exists(mappedPath.parent_path())) std::filesystem::create_directories(mappedPath);
auto dirIt = std::make_unique<std::filesystem::directory_iterator>(mappedPath);
int const handle = accessFileManager().addDirIterator(std::move(dirIt), mappedPath);
LOG_INFO(L"OpenDir [%d]: %S (%s)", handle, path, mappedPath.c_str());
return handle;
} else {
// error if read only input file does not exist
if (flags.mode == SceOpenMode::RDONLY && !std::filesystem::exists(mappedPath)) {
LOG_WARN(L"File doesn't exist: %s mode:0x%lx", mappedPath.c_str(), flags.mode);
return getErr(ErrCode::_ENOENT);
}
auto file = createType_file(mappedPath, flags);
if (auto err = file->getErr(); err != Ok) {
return getErr((ErrCode)err);
}
int const handle = accessFileManager().addFile(std::move(file), mappedPath);
LOG_INFO(L"OpenFile[%d]: %s mode:0x%lx(0x%lx)", handle, mappedPath.c_str(), *(int*)&flags, kernelMode);
return handle;
}
}
int close(int handle) {
LOG_USE_MODULE(filesystem);
LOG_INFO(L"Closed[%d]", handle);
if (handle < FILE_DESCRIPTOR_MIN) {
return getErr(ErrCode::_EPERM);
}
accessFileManager().remove(handle);
return Ok;
}
int unlink(const char* path) {
LOG_USE_MODULE(filesystem);
if (path == nullptr) {
return getErr(ErrCode::_EINVAL);
}
auto const _mapped = accessFileManager().getMappedPath(path);
if (!_mapped) {
return getErr(ErrCode::_EACCES);
}
auto const mapped = _mapped.value();
if (std::filesystem::is_directory(mapped)) {
return getErr(ErrCode::_EPERM);
}
if (std::filesystem::remove(mapped)) {
LOG_INFO(L"Deleted: %S", path);
return Ok;
}
return getErr(ErrCode::_ENOENT);
}
int chmod(const char* path, SceKernelMode mode) {
LOG_USE_MODULE(filesystem);
LOG_ERR(L"TODO %S", __FUNCTION__);
return Ok;
}
int checkReachability(const char* path) {
LOG_USE_MODULE(filesystem);
LOG_ERR(L"todo %S %S", __FUNCTION__, path);
auto mapped = accessFileManager().getMappedPath(path);
if (!mapped) {
return getErr(ErrCode::_EACCES);
}
auto const mappedPath = mapped.value();
if (std::filesystem::exists(mappedPath.parent_path())) return Ok;
return getErr(ErrCode::_EACCES);
}
void sync(void) {
// todo: sync all open files?
}
int fsync(int handle) {
LOG_USE_MODULE(filesystem);
auto file = accessFileManager().accessFile(handle);
if (file == nullptr) {
LOG_ERR(L"KernelFsync[%d]", handle);
return getErr(ErrCode::_EBADF);
}
file->sync();
return Ok;
}
int fdatasync(int fd) {
LOG_USE_MODULE(filesystem);
LOG_ERR(L"todo %S %d", __FUNCTION__, fd);
return Ok;
}
int fcntl(int fd, int cmd, va_list args) {
LOG_USE_MODULE(filesystem);
LOG_ERR(L"todo %S %d", __FUNCTION__, fd);
return Ok;
}
size_t readv(int handle, const SceKernelIovec* iov, int iovcnt) {
LOG_USE_MODULE(filesystem);
auto file = accessFileManager().accessFile(handle);
if (file == nullptr) {
return getErr(ErrCode::_EBADF);
}
// todo: move it to IFile?
size_t count = 0;
for (int n = 0; n < iovcnt; ++n) {
auto* item = &iov[n];
if (item->iov_base == nullptr || item->iov_len == 0) continue;
auto const countTemp = file->read((char*)item->iov_base, item->iov_len);
LOG_TRACE(L"KernelRead[%d]: 0x%08llx:%llu read(%lld)", handle, (uint64_t)item->iov_base, item->iov_len, countTemp);
count += countTemp;
if (auto err = file->getErr(); err != Ok) {
LOG_TRACE(L"file end");
break;
}
}
return count;
}
size_t writev(int handle, const SceKernelIovec* iov, int iovcnt) {
LOG_USE_MODULE(filesystem);
auto file = accessFileManager().accessFile(handle);
if (file == nullptr) {
return getErr(ErrCode::_EBADF);
}
// todo: move it to IFile?
size_t count = 0;
for (int n = 0; n < iovcnt; n++) {
auto* item = &iov[n];
if (item->iov_base == nullptr || item->iov_len == 0) continue;
auto const countTemp = file->write(item->iov_base, item->iov_len);
LOG_TRACE(L"KernelWrite[%d]: 0x%08llx:%llu write(%lld)", handle, (uint64_t)item->iov_base, item->iov_len, countTemp);
count += countTemp;
if (auto err = file->getErr(); err != Ok) {
LOG_TRACE(L"file end");
break;
}
}
return count;
}
int fchmod(int fd, SceKernelMode mode) {
LOG_USE_MODULE(filesystem);
LOG_ERR(L"todo %S", __FUNCTION__);
return Ok;
}
int rename(const char* from, const char* to) {
auto mapped1 = accessFileManager().getMappedPath(from);
if (!mapped1) {
return getErr(ErrCode::_EACCES);
}
auto mapped2 = accessFileManager().getMappedPath(to);
if (!mapped2) {
return getErr(ErrCode::_EACCES);
}
std::filesystem::rename(*mapped1, *mapped2);
return Ok;
}
int mkdir(const char* path, SceKernelMode mode) {
auto _mapped = accessFileManager().getMappedPath(path);
if (!_mapped) {
return getErr(ErrCode::_EACCES);
}
auto const mapped = _mapped.value();
if (!std::filesystem::create_directory(mapped)) {
return getErr(ErrCode::_EIO);
}
return Ok;
}
int rmdir(const char* path) {
auto mapped = accessFileManager().getMappedPath(path);
if (!mapped) {
return getErr(ErrCode::_EACCES);
}
std::filesystem::remove_all(*mapped);
return Ok;
}
int utimes(const char* path, const SceKernelTimeval* times) {
return Ok;
}
int stat(const char* path, SceKernelStat* sb) {
LOG_USE_MODULE(filesystem);
LOG_TRACE(L"KernelStat: %S", path);
memset(sb, 0, sizeof(SceKernelStat));
auto _mapped = accessFileManager().getMappedPath(path);
if (!_mapped) {
return getErr(ErrCode::_EACCES);
}
auto const mapped = _mapped.value();
if (!std::filesystem::exists(mapped)) {
return getErr(ErrCode::_ENOENT);
}
bool const isDir = std::filesystem::is_directory(mapped);
sb->mode = 0000777u | (isDir ? 0040000u : 0100000u);
if (isDir) {
sb->size = 0;
sb->blksize = 512;
sb->blocks = 0;
} else {
sb->size = (int64_t)std::filesystem::file_size(mapped);
sb->blksize = 512;
sb->blocks = (sb->size + 511) / 512;
auto const lastW = std::filesystem::last_write_time(mapped);
auto const time = std::chrono::time_point_cast<std::chrono::nanoseconds>(lastW).time_since_epoch().count();
ns2timespec(&sb->aTime, time);
ns2timespec(&sb->mTime, time);
}
sb->cTime = sb->aTime;
sb->birthtime = sb->mTime;
return Ok;
}
int fstat(int fd, SceKernelStat* sb) {
auto mapped = accessFileManager().getPath(fd);
if (mapped.empty()) {
return getErr(ErrCode::_EACCES);
}
return stat(mapped.string().c_str(), sb);
}
int futimes(int fd, const SceKernelTimeval* times) {
return Ok;
}
int getdirentries(int fd, char* buf, int nbytes, long* basep) {
if (fd < FILE_DESCRIPTOR_MIN) {
return getErr(ErrCode::_EPERM);
}
if (buf == nullptr) {
return getErr(ErrCode::_EFAULT);
}
auto count = accessFileManager().getDents(fd, buf, nbytes, (int64_t*)basep);
if (count < 0) {
return getErr(ErrCode::_EINVAL);
}
return count;
}
int getdents(int fd, char* buf, int nbytes) {
return getdirentries(fd, buf, nbytes, nullptr);
}
size_t preadv(int handle, const SceKernelIovec* iov, int iovcnt, int64_t offset) {
LOG_USE_MODULE(filesystem);
LOG_TRACE(L"preadv [%d]", handle);
if (offset < 0) {
return getErr(ErrCode::_EINVAL);
}
auto file = accessFileManager().accessFile(handle);
if (file == nullptr) {
LOG_ERR(L"preadv[%d] file==nullptr", handle);
return getErr(ErrCode::_EBADF);
}
file->clearError();
file->lseek(offset, SceWhence::beg);
auto const count = readv(handle, iov, iovcnt);
if (auto err = file->getErr(); err != Ok) {
return getErr((ErrCode)err);
}
return count;
}
size_t pwritev(int handle, const SceKernelIovec* iov, int iovcnt, int64_t offset) {
LOG_USE_MODULE(filesystem);
LOG_TRACE(L"pwritev [%d]", handle);
if (offset < 0) {
return getErr(ErrCode::_EINVAL);
}
auto file = accessFileManager().accessFile(handle);
if (file == nullptr) {
LOG_ERR(L"pwritev[%d] file==nullptr", handle);
return getErr(ErrCode::_EBADF);
}
file->clearError();
file->lseek(offset, SceWhence::beg);
auto const count = writev(handle, iov, iovcnt);
if (auto err = file->getErr(); err != Ok) {
return getErr((ErrCode)err);
}
return count;
}
size_t pread(int handle, void* buf, size_t nbytes, int64_t offset) {
LOG_USE_MODULE(filesystem);
LOG_TRACE(L"pread [%d]", handle);
if (buf == nullptr) {
return getErr(ErrCode::_EFAULT);
}
if (offset < 0) {
return getErr(ErrCode::_EINVAL);
}
auto file = accessFileManager().accessFile(handle);
if (file == nullptr) {
LOG_ERR(L"pread[%d] file==nullptr: 0x%08llx:%llu", handle, (uint64_t)buf, nbytes);
return getErr(ErrCode::_EBADF);
}
file->clearError();
file->lseek(offset, SceWhence::beg);
auto const count = file->read((char*)buf, nbytes);
if (auto err = file->getErr(); err != Ok) {
return getErr((ErrCode)err);
}
LOG_TRACE(L"pread[%d]: 0x%08llx:%llu read(%lld) offset:0x%08llx", handle, (uint64_t)buf, nbytes, count, offset);
return count;
}
size_t pwrite(int handle, const void* buf, size_t nbytes, int64_t offset) {
LOG_USE_MODULE(filesystem);
LOG_TRACE(L"pwrite[%d]: 0x%08llx:%llu", handle, (uint64_t)buf, nbytes);
auto file = accessFileManager().accessFile(handle);
if (file == nullptr) {
LOG_ERR(L"write[%d] file==nullptr: 0x%08llx:%llu", handle, (uint64_t)buf, nbytes);
return getErr(ErrCode::_EBADF);
}
file->clearError();
file->lseek(offset, SceWhence::beg);
auto const count = file->write((char*)buf, nbytes);
if (auto err = file->getErr(); err != Ok) {
return getErr((ErrCode)err);
}
return count;
}
int64_t lseek(int handle, int64_t offset, int whence) {
LOG_USE_MODULE(filesystem);
LOG_TRACE(L"lseek [%d] 0x%08llx %d", handle, offset, whence);
if (whence > 2) return getErr(ErrCode::_EINVAL);
auto file = accessFileManager().accessFile(handle);
file->clearError();
auto const pos = file->lseek(offset, (SceWhence)whence);
if (auto err = file->getErr(); err != Ok) {
return getErr((ErrCode)err);
}
return pos;
}
int truncate(const char* path, int64_t length) {
auto mapped = accessFileManager().getMappedPath(path);
if (!mapped) {
return getErr(ErrCode::_EACCES);
}
std::filesystem::resize_file(*mapped, length);
return Ok;
}
int ftruncate(int fd, int64_t length) {
auto mapped = accessFileManager().getPath(fd);
if (mapped.empty()) {
return getErr(ErrCode::_EACCES);
}
return truncate(mapped.string().c_str(), length);
}
int setCompressionAttribute(int fd, int flag) {
LOG_USE_MODULE(filesystem);
LOG_ERR(L"todo %S", __FUNCTION__);
return Ok;
}
int lwfsSetAttribute(int fd, int flags) {
LOG_USE_MODULE(filesystem);
LOG_ERR(L"todo %S", __FUNCTION__);
return Ok;
}
int lwfsAllocateBlock(int fd, int64_t size) {
LOG_USE_MODULE(filesystem);
LOG_ERR(L"todo %S", __FUNCTION__);
return Ok;
}
int lwfsTrimBlock(int fd, int64_t size) {
LOG_USE_MODULE(filesystem);
LOG_ERR(L"todo %S", __FUNCTION__);
return Ok;
}
int64_t lwfsLseek(int fd, int64_t offset, int whence) {
LOG_USE_MODULE(filesystem);
LOG_ERR(L"seek [%d]", fd);
return Ok;
}
size_t lwfsWrite(int fd, const void* buf, size_t nbytes) {
return Ok;
}
} // namespace filesystem

View File

@ -1,137 +0,0 @@
#pragma once
#include "modules_include/common.h"
#include <stdint.h>
namespace filesystem {
enum class SceMapMode : uint16_t {
SHARED = 0x0001,
PRIVATE = 0x0002,
FIXED = 0x0010,
NO_OVERWRITE = 0x0080,
VOID_ = 0x0100,
};
enum class SceMapType : uint16_t {
FILE = 0x0,
ANON = 0x1,
SYSTEM = 0x2,
ALLAVAILABLE = 0x4,
};
struct SceMap {
SceMapMode mode : 12;
SceMapType type : 4;
};
enum class SceOpenMode : uint32_t {
RDONLY = 0,
WRONLY = 1,
RDWR = 2,
};
struct SceOpen {
SceOpenMode mode : 2;
uint32_t nonblock : 1;
uint32_t append : 1;
uint32_t shlock : 1;
uint32_t exlock : 1;
uint32_t async : 1;
uint32_t fsync : 1;
uint32_t nofollow : 1;
uint32_t create : 1;
uint32_t trunc : 1;
uint32_t excl : 1;
uint32_t dsync : 1;
uint32_t dummy0 : 1;
uint32_t fhaslock : 1;
uint32_t noctty : 1;
uint32_t direct : 1;
uint32_t directory : 1;
uint32_t exec : 1;
uint32_t tty_init : 1;
uint32_t cloexec : 1;
};
struct SceKernelStat {
uint32_t dev;
uint32_t ino;
uint16_t mode;
uint16_t nlink;
uint32_t uid;
uint32_t gid;
uint32_t rdev;
SceKernelTimespec aTime;
SceKernelTimespec mTime;
SceKernelTimespec cTime;
int64_t size;
int64_t blocks;
uint32_t blksize;
uint32_t flags;
uint32_t gen;
int32_t lspare;
SceKernelTimespec birthtime;
unsigned int: (8 / 2) * (16 - static_cast<int>(sizeof(SceKernelTimespec)));
unsigned int: (8 / 2) * (16 - static_cast<int>(sizeof(SceKernelTimespec)));
};
struct Sce_iovec {
void* iov_base;
size_t iov_len;
};
typedef uint16_t SceKernelMode; // todo needed?
typedef struct Sce_iovec SceKernelIovec;
#if defined(__APICALL_EXTERN)
#define __APICALL __declspec(dllexport)
#elif defined(__APICALL_IMPORT)
#define __APICALL __declspec(dllimport)
#else
#define __APICALL
#endif
__APICALL int mmap(void* addr, size_t len, int prot, SceMap flags, int fd, int64_t offset, void** res);
__APICALL int munmap(void* address, size_t len);
__APICALL size_t read(int handle, void* buf, size_t nbytes);
__APICALL int64_t write(int handle, const void* buf, size_t nbytes);
__APICALL int open(const char* path, SceOpen flags, SceKernelMode kernelMode);
__APICALL int close(int handle);
__APICALL int unlink(const char* path);
__APICALL int chmod(const char* path, SceKernelMode mode);
__APICALL int checkReachability(const char* path);
__APICALL void sync(void);
__APICALL int fsync(int handle);
__APICALL int fdatasync(int fd);
__APICALL int fcntl(int fd, int cmd, va_list args);
__APICALL size_t readv(int handle, const SceKernelIovec* iov, int iovcnt);
__APICALL size_t writev(int handle, const SceKernelIovec* iov, int iovcnt);
__APICALL int fchmod(int fd, SceKernelMode mode);
__APICALL int rename(const char* from, const char* to);
__APICALL int mkdir(const char* path, SceKernelMode mode);
__APICALL int rmdir(const char* path);
__APICALL int utimes(const char* path, const SceKernelTimeval* times);
__APICALL int stat(const char* path, SceKernelStat* sb);
__APICALL int fstat(int fd, SceKernelStat* sb);
__APICALL int futimes(int fd, const SceKernelTimeval* times);
__APICALL int getdirentries(int fd, char* buf, int nbytes, long* basep);
__APICALL int getdents(int fd, char* buf, int nbytes);
__APICALL size_t preadv(int handle, const SceKernelIovec* iov, int iovcnt, int64_t offset);
__APICALL size_t pwritev(int handle, const SceKernelIovec* iov, int iovcnt, int64_t offset);
__APICALL size_t pread(int handle, void* buf, size_t nbytes, int64_t offset);
__APICALL size_t pwrite(int handle, const void* buf, size_t nbytes, int64_t offset);
__APICALL int64_t lseek(int handle, int64_t offset, int whence);
__APICALL int truncate(const char* path, int64_t length);
__APICALL int ftruncate(int fd, int64_t length);
__APICALL int setCompressionAttribute(int fd, int flag);
__APICALL int lwfsSetAttribute(int fd, int flags);
__APICALL int lwfsAllocateBlock(int fd, int64_t size);
__APICALL int lwfsTrimBlock(int fd, int64_t size);
__APICALL int64_t lwfsLseek(int fd, int64_t offset, int whence);
__APICALL size_t lwfsWrite(int fd, const void* buf, size_t nbytes);
#undef __APICALL
}; // namespace filesystem

File diff suppressed because it is too large Load Diff

View File

@ -1,193 +0,0 @@
#pragma once
#include "pthread_types.h"
#include "utility/utility.h"
#include <modules_include/common.h>
#include <stdint.h>
struct PthreadAttrPrivate;
struct PthreadBarrierPrivate;
struct PthreadBarrierattrPrivate;
struct PthreadCondattrPrivate;
struct PthreadCondPrivate;
struct PthreadMutexPrivate;
struct PthreadMutexattrPrivate;
struct PthreadRwlockPrivate;
struct PthreadRwlockattrPrivate;
struct PthreadPrivate;
struct PthreadOnce;
using ScePthread_obj = uint8_t*;
using ScePthreadAttr = PthreadAttrPrivate*;
using ScePthreadBarrier = PthreadBarrierPrivate*;
using ScePthreadBarrierattr = PthreadBarrierattrPrivate*;
using ScePthreadCondattr = PthreadCondattrPrivate*;
using ScePthreadCond = PthreadCondPrivate*;
using ScePthreadMutex = PthreadMutexPrivate*;
using ScePthreadMutexattr = PthreadMutexattrPrivate*;
using ScePthreadRwlock = PthreadRwlockPrivate*;
using ScePthreadRwlockattr = PthreadRwlockattrPrivate*;
using ScePthread = PthreadPrivate*;
using ScePthreadOnce = PthreadOnce*;
using ScePthreadKey = int;
#if defined(__APICALL_EXTERN)
#define __APICALL __declspec(dllexport)
#elif defined(__APICALL_IMPORT)
#define __APICALL __declspec(dllimport)
#else
#define __APICALL
#endif
struct DTVKey;
namespace pthread {
__APICALL uint8_t* getTLSStaticBlock(ScePthread_obj obj);
__APICALL uint64_t* getDTV(ScePthread_obj obj);
__APICALL int getThreadId();
__APICALL int getThreadId(ScePthread_obj obj);
__APICALL ScePthread_obj& getSelf();
__APICALL int attrDestroy(ScePthreadAttr* attr);
__APICALL int attrGetstack(const ScePthreadAttr* attr, void** stackAddr, size_t* stackSize);
__APICALL int attrGetstacksize(const ScePthreadAttr* attr, size_t* stackSize);
__APICALL int attrGetguardsize(const ScePthreadAttr* attr, size_t* guardSize);
__APICALL int attrGetstackaddr(const ScePthreadAttr* attr, void** stackAddr);
__APICALL int attrGetdetachstate(const ScePthreadAttr* attr, SceDetachState* state);
__APICALL int attrSetstacksize(ScePthreadAttr* attr, size_t stackSize);
__APICALL int attrSetguardsize(ScePthreadAttr* attr, size_t guardSize);
__APICALL int attrSetstack(ScePthreadAttr* attr, void* addr, size_t size);
__APICALL int attrSetstackaddr(ScePthreadAttr* attr, void* addr);
__APICALL int attrSetdetachstate(ScePthreadAttr* attr, SceDetachState state);
__APICALL int barrierDestroy(ScePthreadBarrier* barrier);
__APICALL int barrierInit(ScePthreadBarrier* barrier, const ScePthreadBarrierattr* attr, unsigned count, const char* name);
__APICALL int barrierWait(ScePthreadBarrier* barrier);
__APICALL int barrierattrDestroy(ScePthreadBarrierattr* barrier);
__APICALL int barrierattrInit(ScePthreadBarrierattr* barrier);
__APICALL int barrierattrGetpshared(ScePthreadBarrierattr* barrier, int* pshared);
__APICALL int barrierattrSetpshared(ScePthreadBarrierattr* barrier, int pshared);
__APICALL int condattrDestroy(ScePthreadCondattr* attr);
__APICALL int condattrInit(ScePthreadCondattr* attr);
__APICALL int condInit(ScePthreadCond* cond, const ScePthreadCondattr* attr, const char* name);
__APICALL int condBroadcast(ScePthreadCond* cond);
__APICALL int condDestroy(ScePthreadCond* cond);
__APICALL int condSignal(ScePthreadCond* cond);
__APICALL int condSignalto(ScePthreadCond* cond, ScePthread_obj obj);
__APICALL int condTimedwait(ScePthreadCond* cond, ScePthreadMutex* mutex, SceKernelUseconds usec);
__APICALL int condTimedwait(ScePthreadCond* cond, ScePthreadMutex* mutex, const SceKernelTimespec* t);
__APICALL int condWait(ScePthreadCond* cond, ScePthreadMutex* mutex);
__APICALL int detach(ScePthread_obj obj);
__APICALL int equal(ScePthread_obj thread1, ScePthread_obj thread2);
__APICALL int getcpuclockid(ScePthread_obj thread, int* clock);
__APICALL int join(ScePthread_obj obj, void** value);
__APICALL int keyDelete(ScePthreadKey key);
__APICALL int keyCreate(ScePthreadKey* key, pthread_key_destructor_func_t destructor);
__APICALL int mutexattrInit(ScePthreadMutexattr* attr);
__APICALL int mutexattrDestroy(ScePthreadMutexattr* attr);
__APICALL int mutexattrGettype(ScePthreadMutexattr* attr, int* type);
__APICALL int mutexattrSettype(ScePthreadMutexattr* attr, int type);
__APICALL int mutexDestroy(ScePthreadMutex* mutex);
__APICALL int mutexInit(ScePthreadMutex* mutex, const ScePthreadMutexattr* attr, const char* name);
__APICALL int mutexLock(ScePthreadMutex* mutex);
__APICALL int mutexTrylock(ScePthreadMutex* mutex);
__APICALL int mutexTimedlock(ScePthreadMutex* mutex, SceKernelUseconds usec);
__APICALL int mutexTimedlock(ScePthreadMutex* mutex, const SceKernelTimespec* t);
__APICALL int mutexUnlock(ScePthreadMutex* mutex);
__APICALL int rwlockDestroy(ScePthreadRwlock* rwlock);
__APICALL int rwlockInit(ScePthreadRwlock* rwlock, const ScePthreadRwlockattr* attr, const char* name);
__APICALL int rwlockRdlock(ScePthreadRwlock* rwlock);
__APICALL int rwlockTimedrdlock(ScePthreadRwlock* rwlock, SceKernelUseconds usec);
__APICALL int rwlockTimedrdlock(ScePthreadRwlock* rwlock, const SceKernelTimespec* t);
__APICALL int rwlockTimedwrlock(ScePthreadRwlock* rwlock, SceKernelUseconds usec);
__APICALL int rwlockTimedwrlock(ScePthreadRwlock* rwlock, const SceKernelTimespec* t);
__APICALL int rwlockTryrdlock(ScePthreadRwlock* rwlock);
__APICALL int rwlockTrywrlock(ScePthreadRwlock* rwlock);
__APICALL int rwlockUnlock(ScePthreadRwlock* rwlock);
__APICALL int rwlockWrlock(ScePthreadRwlock* rwlock);
__APICALL int rwlockattrDestroy(ScePthreadRwlockattr* attr);
__APICALL int rwlockattrSettype(ScePthreadRwlockattr* attr, int type);
__APICALL int rwlockattrInit(ScePthreadRwlockattr* attr);
__APICALL int rwlockattrGettype(ScePthreadRwlockattr* attr, int* type);
__APICALL int setspecific(ScePthreadKey key, const void* value);
__APICALL void* getspecific(ScePthreadKey key);
__APICALL int cancel(ScePthread_obj obj);
__APICALL int setcancelstate(int state, int* oldState);
__APICALL int setcanceltype(int type, int* oldType);
__APICALL void testCancel(void);
__APICALL int getprio(ScePthread_obj obj, int* prio);
__APICALL int setprio(ScePthread_obj obj, int prio);
__APICALL void yield(void);
__APICALL int mutexattrGetprioceiling(ScePthreadMutexattr* attr, int* prio);
__APICALL int mutexattrSetprioceiling(ScePthreadMutexattr* attr, int prio);
__APICALL int mutexGetprioceiling(ScePthreadMutex* mutex, int* prio);
__APICALL int mutexSetprioceiling(ScePthreadMutex* mutex, int prio, int* oldPrio);
__APICALL int mutexattrGetprotocol(ScePthreadMutexattr* attr, int* protocol);
__APICALL int mutexattrSetprotocol(ScePthreadMutexattr* attr, int protocol);
__APICALL int attrGetinheritsched(const ScePthreadAttr* attr, SceInheritShed* inheritSched);
__APICALL int attrGetschedparam(const ScePthreadAttr* attr, SceSchedParam* param);
__APICALL int attrGetschedpolicy(const ScePthreadAttr* attr, SceShedPolicy* policy);
__APICALL int attrSetinheritsched(ScePthreadAttr* attr, SceInheritShed inheritSched);
__APICALL int attrSetschedparam(ScePthreadAttr* attr, const SceSchedParam* param);
__APICALL int attrSetschedpolicy(ScePthreadAttr* attr, SceShedPolicy policy);
__APICALL int getschedparam(ScePthread_obj obj, int* policy, SceSchedParam* param);
__APICALL int setschedparam(ScePthread_obj obj, int policy, const SceSchedParam* param);
__APICALL int attrGet(ScePthread_obj obj, ScePthreadAttr* attr);
__APICALL int attrGetaffinity(const ScePthreadAttr* attr, SceKernelCpumask* mask);
__APICALL int attrSetaffinity(ScePthreadAttr* attr, const SceKernelCpumask mask);
__APICALL int getaffinity(ScePthread_obj obj, SceKernelCpumask* mask);
__APICALL int setaffinity(ScePthread_obj obj, const SceKernelCpumask mask);
__APICALL int getthreadid(void);
__APICALL int once(ScePthreadOnce once_control, pthread_once_init init_routine);
__APICALL int rename(ScePthread_obj obj, const char* name);
__APICALL int getName(ScePthread_obj obj, char* name);
__APICALL void setThreadDtors(thread_dtors_func_t dtors);
__APICALL int attrInit(ScePthreadAttr* attr);
__APICALL void exit(void* value);
__APICALL void raise(ScePthread_obj obj, void* callback, int signo);
__APICALL int create(ScePthread_obj* obj, const ScePthreadAttr* attr, pthread_entry_func_t entry, void* arg, const char* name);
__APICALL int attrSetscope(ScePthreadAttr* attr, int flag);
__APICALL int attrGetscope(ScePthreadAttr* attr, int* flag);
__APICALL void cxa_finalize(void* /*p*/);
__APICALL void cleanup_push(thread_clean_func_t func, void* arg);
__APICALL void cleanup_pop(int execute);
} // namespace pthread
#undef __APICALL

View File

@ -1,168 +0,0 @@
#pragma once
#include "core/unwinding/unwind.h"
#include "modules_include/common.h"
#include "pthread_types.h"
#include "utility/utility.h"
#include <boost/thread/recursive_mutex.hpp>
#include <boost/thread/thread.hpp>
constexpr size_t DEFAULT_STACKSIZE = 16 * 1024 * 1024;
constexpr uint64_t XSAVE_CHK_GUARD = 0xDeadBeef5533CCAAu;
constexpr int KEYS_MAX = 256;
constexpr int DESTRUCTOR_ITERATIONS = 4;
constexpr size_t DTV_MAX_KEYS = 256;
struct PthreadAttrPrivate {
private:
size_t stackSize = DEFAULT_STACKSIZE;
void* stackAddr = 0; /// if != 0 use the custum stack
SceSchedParam shedParam = {.sched_priority = 700};
SceKernelCpumask affinity = 0x7f;
size_t guardSize = 0x1000;
SceShedPolicy policy = SceShedPolicy::FIFO;
int scope = 0;
SceDetachState detachState = SceDetachState::JOINABLE;
SceInheritShed inheritShed = SceInheritShed::INHERIT;
public:
PthreadAttrPrivate() = default;
auto getStackAddr() const noexcept { return stackAddr; }
void setStackAddr(decltype(PthreadAttrPrivate::stackAddr) param) noexcept { stackAddr = param; }
auto getStackSize() const noexcept { return stackSize; }
void setStackSize(decltype(PthreadAttrPrivate::stackSize) param) noexcept { stackSize = param; }
auto getGuardSize() const noexcept { return guardSize; }
void setGuardSize(decltype(PthreadAttrPrivate::guardSize) param) noexcept { guardSize = param; }
auto getAffinity() const noexcept { return affinity; }
void setAffinity(decltype(PthreadAttrPrivate::affinity) param) noexcept { affinity = param; }
auto getPolicy() const noexcept { return policy; }
void setPolicy(decltype(PthreadAttrPrivate::policy) param) noexcept { policy = param; }
auto getScope() const noexcept { return scope; }
void setScope(decltype(PthreadAttrPrivate::scope) param) noexcept { scope = param; }
auto getShedParam() const noexcept { return shedParam; }
void setShedParam(decltype(PthreadAttrPrivate::shedParam) param) noexcept { shedParam = param; }
auto getDetachState() const noexcept { return detachState; }
void setDetachState(decltype(PthreadAttrPrivate::detachState) param) noexcept { detachState = param; }
auto getInheritShed() const noexcept { return inheritShed; }
void setInheritShed(decltype(PthreadAttrPrivate::inheritShed) param) noexcept { inheritShed = param; }
};
struct PthreadMutexPrivate {
uint64_t initialized = 1;
uint8_t reserved[256]; // type is bigger on ps than here
boost::recursive_mutex p;
std::string name;
size_t id;
int prioCeiling;
SceMutexType type = SceMutexType::DEFAULT;
~PthreadMutexPrivate() {}
};
struct PthreadMutexattrPrivate {
uint64_t initialized = 1;
uint8_t reserved[64]; // type is bigger on ps than here
SceMutexType type = SceMutexType::DEFAULT;
SceMutexProtocol pprotocol = SceMutexProtocol::PRIO_NONE;
int prioCeiling;
};
struct DTVKey {
bool used = false;
pthread_key_destructor_func_t destructor = nullptr;
};
constexpr size_t DTV_SIZE = DTV_MAX_KEYS;
struct PthreadPrivate {
uint64_t dtv[DTV_SIZE];
std::string name;
boost::thread p;
PthreadAttrPrivate attr = {};
pthread_entry_func_t entry = nullptr;
void* arg = nullptr;
int unique_id = 0;
std::atomic_bool started = false;
int policy = 0;
std::vector<std::pair<thread_clean_func_t, void*>> cleanupFuncs;
bool detached = false; // fake detach
unwinding_jmp_buf _unwinding;
~PthreadPrivate() = default;
};
struct PthreadRwlockPrivate {
uint64_t initialized = 1;
uint8_t reserved[256]; // type is bigger on ps than here
boost::shared_mutex p;
std::string name;
size_t id;
bool isWrite = false;
~PthreadRwlockPrivate() {}
};
struct PthreadBarrierPrivate {};
struct PthreadBarrierattrPrivate {
int pshared;
};
struct PthreadCondattrPrivate {
uint64_t initialized = 1;
uint8_t reserved[64]; // type is bigger on ps than here
};
struct PthreadRwlockattrPrivate {
uint64_t initialized = 1;
uint8_t reserved[64]; // type is bigger on ps than here
int type;
};
struct PthreadCondPrivate {
uint64_t initialized = 1;
uint8_t reserved[64]; // type is bigger on ps than here
boost::condition_variable p;
std::string name;
~PthreadCondPrivate() {}
};
struct PthreadOnce {
int32_t isInit; // 0: no, 1: yes
int32_t _align;
PthreadMutexPrivate* mutex;
};

View File

@ -1,48 +0,0 @@
#pragma once
#include "utility/utility.h"
using pthread_entry_func_t = SYSV_ABI void* (*)(void*);
using pthread_key_destructor_func_t = SYSV_ABI void (*)(void*);
using thread_dtors_func_t = SYSV_ABI void (*)();
using thread_clean_func_t = SYSV_ABI void (*)(void*);
using pthread_once_init = SYSV_ABI void (*)();
struct SceSchedParam {
int sched_priority;
};
enum class SceMutexProtocol { PRIO_NONE, PRIO_INHERIT, PRIO_PROTECT };
enum class SceMutexType { DEFAULT, ERRORCHECK, RECURSIVE, NORMAL, ADAPTIVE_NP };
enum class SceDetachState {
JOINABLE,
DETACHED,
};
enum class SceShedPolicy {
OTHER,
FIFO,
RR,
};
enum class SceInheritShed {
EXPLICIT,
INHERIT = 4,
};
enum class SceCancelState {
ENABLE,
DISABLE,
};
enum class SceCancelType {
DEFERRED,
ASYNC = 4,
};
struct SceCleanInfo {
SceCleanInfo* prev;
thread_clean_func_t func;
void* arg;
int onHeap;
};

View File

@ -1,189 +0,0 @@
#include "semaphore.h"
#include "logging.h"
#include "modules_include/common.h"
#include <queue>
#include <boost/chrono.hpp>
#include <boost/thread/condition.hpp>
#include <boost/thread/mutex.hpp>
LOG_DEFINE_MODULE(Semaphore)
namespace {
size_t getUniqueId() {
static size_t count = 0;
return count++;
}
} // namespace
class Semaphore: public ISemaphore {
enum class Status { Set, Canceled, Deleted };
boost::mutex m_mutex;
boost::condition_variable m_condState;
std::queue<std::shared_ptr<boost::condition_variable>> m_condQueue; // todo: optimze
std::string m_name;
size_t m_waitCounter = 0;
size_t const m_id = getUniqueId();
int m_countThreads = 0;
Status m_state = Status::Set;
bool m_fifo;
int m_count;
int m_max_count;
public:
Semaphore(const std::string& name, bool fifo, int init_count, int max_count): m_name(name), m_fifo(fifo), m_count(init_count), m_max_count(max_count) {
if (!fifo) m_condQueue.push(std::make_shared<boost::condition_variable>());
};
virtual ~Semaphore();
int cancel(int setCount, int* numWaitingThreads) final;
int signal(int signalCount) final;
int wait(int needcount, uint32_t* pMicros) final;
int try_wait(int needcount, uint32_t* pMicros) final;
std::string_view const getName() const final { return m_name; }
size_t getId() const final { return m_id; }
private:
int wait_internal(int needCount, uint32_t* pMicros, boost::unique_lock<boost::mutex>& lock);
};
std::unique_ptr<ISemaphore> createSemaphore(const char* name, bool fifo, int initCount, int maxCount) {
return std::make_unique<Semaphore>(name == nullptr ? "" : name, fifo, initCount, maxCount);
}
Semaphore::~Semaphore() {
boost::unique_lock lock(m_mutex);
m_state = Status::Deleted;
while (!m_condQueue.empty()) {
m_condQueue.front()->notify_all();
m_condQueue.pop();
}
// Wait for Threads to leave wait
m_condState.wait(lock, [this] { return m_countThreads == 0; });
}
int Semaphore::cancel(int setCount, int* numWaitingThreads) {
boost::unique_lock lock(m_mutex);
m_state = Status::Canceled;
if (numWaitingThreads != nullptr) *numWaitingThreads = m_countThreads;
if (m_fifo) {
while (!m_condQueue.empty()) {
m_condQueue.front()->notify_one();
m_condQueue.pop();
}
} else {
m_condQueue.front()->notify_all();
}
// Wait for Threads to leave wait
m_condState.wait(lock, [this] { return m_countThreads == 0; });
m_state = Status::Set;
return Ok;
}
int Semaphore::signal(int signalCount) {
LOG_USE_MODULE(Semaphore);
boost::unique_lock lock(m_mutex);
LOG_TRACE(L"KernelSema(%llu) name:%S signal:%d count:%d", m_id, m_name.c_str(), signalCount, m_count);
m_condState.wait(lock, [this] { return m_state != Status::Canceled; });
if (m_state == Status::Deleted) {
return getErr(ErrCode::_EACCES);
}
if (m_count + signalCount > m_max_count) {
return getErr(ErrCode::_EINVAL);
}
if (signalCount > 0) {
m_count += signalCount;
if (!m_condQueue.empty()) m_condQueue.front()->notify_one();
}
return Ok;
}
int Semaphore::wait_internal(int needCount, uint32_t* pMicros, boost::unique_lock<boost::mutex>& lock) {
LOG_USE_MODULE(Semaphore);
std::chrono::time_point<std::chrono::system_clock> start = std::chrono::system_clock::now();
uint32_t const micros = pMicros != nullptr ? *pMicros : 0;
size_t waitCount = 0;
{
if (m_state == Status::Deleted) return getErr(ErrCode::_EACCES);
std::shared_ptr<boost::condition_variable> condVar;
if (m_fifo) {
condVar = std::make_shared<boost::condition_variable>();
m_condQueue.push(condVar);
} else {
condVar = m_condQueue.front();
}
waitCount = m_waitCounter++;
LOG_TRACE(L"-> KernelSema(%llu) name:%S waitCount:%llu need:%d count:%d time:%u us", m_id, m_name.c_str(), waitCount, needCount, m_count, micros);
m_countThreads++;
if (pMicros != nullptr) {
if (!condVar->wait_for(lock, boost::chrono::microseconds(micros),
[this, condVar, needCount] { return m_state != Status::Set || (m_condQueue.front() == condVar && m_count >= needCount); })) {
LOG_WARN(L"<- KernelSema(%llu) name:%S waitCount:%llu timeout", m_id, m_name.c_str(), waitCount);
*pMicros = 0;
m_countThreads--;
return getErr(ErrCode::_ETIMEDOUT);
}
} else {
condVar->wait(lock, [this, condVar, needCount] { return m_state != Status::Set || (m_condQueue.front() == condVar && m_count >= needCount); });
}
if (m_fifo) m_condQueue.pop();
m_count -= needCount;
m_countThreads--;
m_condState.notify_all();
if (m_count >= 0 && !m_condQueue.empty()) m_condQueue.front()->notify_one(); // notify next in queue if count is != 0
}
auto elapsed = std::chrono::duration_cast<std::chrono::microseconds>(std::chrono::system_clock::now() - start).count();
LOG_TRACE(L"<- KernelSema(%llu) name:%S waitCount:%llu count:%d waiting:%llu", m_id, m_name.c_str(), waitCount, m_count, m_condQueue.size());
if (pMicros != nullptr) {
*pMicros = (elapsed >= micros ? 0 : micros - elapsed);
}
if (m_state == Status::Canceled) {
return getErr(ErrCode::_ECANCELED);
}
if (m_state == Status::Deleted) {
return getErr(ErrCode::_EACCES);
}
return Ok;
}
int Semaphore::wait(int needCount, uint32_t* pMicros) {
boost::unique_lock lock(m_mutex);
return wait_internal(needCount, pMicros, lock);
}
int Semaphore::try_wait(int needCount, uint32_t* pMicros) {
boost::unique_lock lock(m_mutex);
if (m_countThreads > 0) return getErr(ErrCode::_EAGAIN);
return wait_internal(needCount, pMicros, lock);
}

View File

@ -1,41 +0,0 @@
#pragma once
#include "utility/utility.h"
#include <memory>
class ISemaphore {
CLASS_NO_COPY(ISemaphore);
CLASS_NO_MOVE(ISemaphore);
protected:
ISemaphore() = default;
public:
virtual ~ISemaphore() = default;
virtual int cancel(int setCount, int* numWaitingThreads) = 0;
virtual int signal(int signalCount) = 0;
virtual int wait(int needcount, uint32_t* pMicros) = 0;
virtual int try_wait(int needcount, uint32_t* pMicros) = 0;
int poll(int needCount) {
uint32_t micros = 0;
return wait(needCount, &micros);
}
virtual std::string_view const getName() const = 0;
virtual size_t getId() const = 0;
};
#if defined(__APICALL_EXTERN)
#define __APICALL __declspec(dllexport)
#elif defined(__APICALL_IMPORT)
#define __APICALL __declspec(dllimport)
#else
#define __APICALL
#endif
__APICALL std::unique_ptr<ISemaphore> createSemaphore(const char* name, bool fifo, int initCount, int maxCount);
#undef __APICALL

View File

@ -1,5 +0,0 @@
add_library(memory OBJECT
memory.cpp
)
add_dependencies(memory third_party psOff_utility)

View File

@ -1,305 +0,0 @@
#define __APICALL_EXTERN
#include "memory.h"
#undef __APICALL_EXTERN
#include "logging.h"
#include "utility/utility.h"
#include <windows.h>
LOG_DEFINE_MODULE(memory);
namespace {
bool checkIsGPU(int protection) {
return (protection & 0xf0) > 0;
}
DWORD convProtection(int prot) {
switch (prot & 0xf) {
case 0: return PAGE_NOACCESS;
case 1: return PAGE_READONLY;
case 2:
case 3: return PAGE_READWRITE;
case 4: return PAGE_EXECUTE;
case 5: return PAGE_EXECUTE_READ;
case 6:
case 7: return PAGE_EXECUTE_READWRITE;
}
if (checkIsGPU(prot)) {
return PAGE_READWRITE; // special case: cpu read/writes gpumemory on host memory
}
return PAGE_NOACCESS;
}
int convProtection(DWORD prot) {
switch (prot) {
case PAGE_NOACCESS: return 0;
case PAGE_READONLY: return SceProtRead;
case PAGE_READWRITE: return SceProtRead | SceProtWrite;
case PAGE_EXECUTE: return SceProtExecute;
case PAGE_EXECUTE_READ: return SceProtExecute | SceProtRead;
case PAGE_EXECUTE_READWRITE: return SceProtRead | SceProtWrite | SceProtExecute;
}
return 0;
}
using VirtualAlloc2_func_t = /*WINBASEAPI*/ PVOID WINAPI (*)(HANDLE, PVOID, SIZE_T, ULONG, ULONG, MEM_EXTENDED_PARAMETER*, ULONG);
VirtualAlloc2_func_t getVirtualAlloc2() {
static auto funcVirtualAlloc2 = [] {
LOG_USE_MODULE(memory);
HMODULE h = GetModuleHandle("KernelBase");
if (h != nullptr) {
auto const addr = reinterpret_cast<VirtualAlloc2_func_t>(GetProcAddress(h, "VirtualAlloc2"));
if (addr == nullptr) {
LOG_CRIT(L"virtual_alloc2 == nullptr");
}
return addr;
}
LOG_CRIT(L"KernelBase not found");
return (VirtualAlloc2_func_t) nullptr;
}();
return funcVirtualAlloc2;
}
} // namespace
namespace memory {
int getpagesize(void) {
SYSTEM_INFO si;
GetSystemInfo(&si);
return si.dwPageSize;
}
uint64_t getTotalSystemMemory() {
MEMORYSTATUSEX status;
status.dwLength = sizeof(status);
GlobalMemoryStatusEx(&status);
return status.ullTotalPhys;
}
uintptr_t reserve(uint64_t start, uint64_t size, uint64_t alignment, bool isGpu) {
LOG_USE_MODULE(memory);
MEM_ADDRESS_REQUIREMENTS requirements {
.LowestStartingAddress = (PVOID)start,
.HighestEndingAddress = (PVOID)0,
.Alignment = alignment,
};
MEM_EXTENDED_PARAMETER param {
.Type = MemExtendedParameterAddressRequirements,
.Pointer = &requirements,
};
auto ptr = (uintptr_t)getVirtualAlloc2()(GetCurrentProcess(), nullptr, size, isGpu ? MEM_RESERVE | MEM_WRITE_WATCH : MEM_RESERVE, PAGE_NOACCESS, &param, 1);
if (ptr == 0) {
auto err = static_cast<uint32_t>(GetLastError());
if (err != ERROR_INVALID_PARAMETER) {
LOG_ERR(L"reserve failed err:0x%08x| start:0x%08llx size:%llu alignment:%llu isGpu:%d", err, start, size, alignment, isGpu);
} else {
return reserve(start, size, alignment << 1, isGpu);
}
}
return ptr;
}
uint64_t commit(uintptr_t baseAddr, uint64_t offset, uint64_t size, uint64_t alignment, int prot) {
LOG_USE_MODULE(memory);
auto ptr = (uintptr_t)VirtualAlloc((LPVOID)(baseAddr + offset), size, MEM_COMMIT, convProtection(prot));
if (ptr == 0) {
auto err = static_cast<uint32_t>(GetLastError());
LOG_ERR(L"commit failed err:0x%0x| base:0x%08llx offset:0x%08llx size:%llu alignment:%llu prot:%d", err, baseAddr, offset, size, alignment, prot);
}
return ptr;
}
uint64_t allocGPUMemory(uintptr_t baseAddr, uint64_t offset, uint64_t size, uint64_t alignment) {
LOG_USE_MODULE(memory);
MEMORY_BASIC_INFORMATION im;
VirtualQuery((LPVOID)baseAddr, &im, sizeof(im));
VirtualFree(im.AllocationBase, 0, MEM_RELEASE);
auto ptrBase = reserve((uint64_t)im.AllocationBase, im.RegionSize, alignment, true);
if (ptrBase == 0) {
auto err = static_cast<uint32_t>(GetLastError());
LOG_ERR(L"allocGPUMemory failed err:0x%0x| addr:0x%08llx size:%llu", err, baseAddr, im.RegionSize);
return 0;
}
auto ptr = (uintptr_t)VirtualAlloc((LPVOID)(baseAddr + offset), size, MEM_COMMIT | MEM_WRITE_WATCH, PAGE_READWRITE);
if (ptr == 0) {
auto err = static_cast<uint32_t>(GetLastError());
LOG_ERR(L"allocGPUMemory failed err:0x%0x| addr:0x%08llx size:%llu", err, baseAddr, size);
return 0;
}
LOG_DEBUG(L"allocGPUMemory| base:0x%08llx(0x%08llx) addr:0x%08llx", ptrBase, baseAddr, ptr);
return ptr;
}
int64_t queryAlloc(uintptr_t addr, uintptr_t* start, uintptr_t* end, int* prot) {
MEMORY_BASIC_INFORMATION im;
if (VirtualQuery((LPVOID)addr, &im, sizeof(im)) == 0) {
return -1;
}
if (start != nullptr) *start = (uintptr_t)im.BaseAddress;
if (end != nullptr) *end = *start + im.RegionSize;
if (prot != nullptr) {
*prot = convProtection(im.Protect);
}
return (int64_t)im.AllocationBase;
}
uint64_t allocAligned(uint64_t address, uint64_t size, int prot, uint64_t alignment) {
LOG_USE_MODULE(memory);
constexpr uint64_t USER_MIN = DIRECTMEM_START;
constexpr uint64_t USER_MAX = 0xFBFFFFFFFFu;
MEM_ADDRESS_REQUIREMENTS req2 {};
MEM_EXTENDED_PARAMETER param2 {};
req2.LowestStartingAddress = (address == 0 ? reinterpret_cast<PVOID>(USER_MIN) : reinterpret_cast<PVOID>(address));
req2.HighestEndingAddress = reinterpret_cast<PVOID>(USER_MAX);
req2.Alignment = alignment;
param2.Type = MemExtendedParameterAddressRequirements;
param2.Pointer = &req2;
static auto virtual_alloc2 = getVirtualAlloc2();
if (virtual_alloc2 == nullptr) {
LOG_CRIT(L"virtual_alloc2 == nullptr");
return 0;
}
DWORD flags = static_cast<DWORD>(MEM_COMMIT) | static_cast<DWORD>(MEM_RESERVE);
if (checkIsGPU(prot)) flags |= MEM_WRITE_WATCH;
auto ptr = reinterpret_cast<uintptr_t>(virtual_alloc2(0, nullptr, size, flags, convProtection(prot), &param2, 1));
if (ptr == 0) {
auto err = static_cast<uint32_t>(GetLastError());
if (err != ERROR_INVALID_PARAMETER) {
LOG_ERR(L"VirtualAlloc2(alignment = 0x%08llx failed: 0x%04x", alignment, err);
} else {
return alloc(0, size, prot);
}
}
return ptr;
}
uint64_t alloc(uint64_t address, uint64_t size, int prot) {
LOG_USE_MODULE(memory);
DWORD flags = static_cast<DWORD>(MEM_COMMIT) | static_cast<DWORD>(MEM_RESERVE);
if (checkIsGPU(prot)) flags |= MEM_WRITE_WATCH;
auto ptr = reinterpret_cast<uintptr_t>(VirtualAlloc(reinterpret_cast<LPVOID>(static_cast<uintptr_t>(address)), size, flags, convProtection(prot)));
if (ptr == 0) {
auto err = static_cast<uint32_t>(GetLastError());
if (err != ERROR_INVALID_ADDRESS) {
LOG_ERR(L"VirtualAlloc() failed @0x%08llx:0x%08llx protection:0x%x, err:0x%04x", address, size, prot, err);
} else {
return allocAligned(address, size, prot, 0);
}
}
return ptr;
}
bool allocFixed(uint64_t address, uint64_t size, int protection) {
LOG_USE_MODULE(memory);
DWORD flags = static_cast<DWORD>(MEM_COMMIT) | static_cast<DWORD>(MEM_RESERVE);
if (checkIsGPU(protection)) flags |= MEM_WRITE_WATCH;
auto ptr = reinterpret_cast<uintptr_t>(VirtualAlloc(reinterpret_cast<LPVOID>(static_cast<uintptr_t>(address)), size, flags, convProtection(protection)));
if (ptr == 0) {
auto err = static_cast<uint32_t>(GetLastError());
LOG_ERR(L"VirtualAlloc() failed: 0x%04x", err);
return false;
}
if (ptr != address) {
LOG_ERR(L"VirtualAlloc() failed: wrong address");
VirtualFree(reinterpret_cast<LPVOID>(ptr), 0, MEM_RELEASE);
return false;
}
return true;
}
bool free(uint64_t address) {
LOG_USE_MODULE(memory);
if (VirtualFree(reinterpret_cast<LPVOID>(static_cast<uintptr_t>(address)), 0, MEM_RELEASE) == 0) {
LOG_ERR(L"VirtualFree() failed: 0x%04x", static_cast<uint32_t>(GetLastError()));
return false;
}
return true;
}
bool protect(uint64_t address, uint64_t size, int protection, int* oldProt) {
LOG_USE_MODULE(memory);
DWORD oldProtection;
if (VirtualProtect(reinterpret_cast<LPVOID>(static_cast<uintptr_t>(address)), size, convProtection(protection), &oldProtection) == 0) {
LOG_ERR(L"VirtualProtect() failed addr:0x%08llx size:0x%08llx prot:%d err:0x%04x", address, size, protection, static_cast<uint32_t>(GetLastError()));
return false;
}
if (oldProt != nullptr) *oldProt = (protection & 0xF0) | convProtection(oldProtection);
return true;
}
int getProtection(uint64_t address) {
LOG_USE_MODULE(memory);
MEMORY_BASIC_INFORMATION mbi;
if (!VirtualQuery((const void*)address, &mbi, sizeof(mbi))) {
LOG_ERR(L"Failed to query memory");
return 0;
}
return convProtection(mbi.Protect);
}
void installHook_long(uintptr_t dst, uintptr_t src, _t_hook& pGateway, size_t lenOpCodes) {
std::array<uint8_t, 14> codeGateway = {
0xFF, 0x25, 0x00, 0x00, 0x00, 0x00, // jmp qword
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // addr
};
// setup jmp back code
*(uint64_t*)&codeGateway[6] = (uint64_t)(src + lenOpCodes);
LOG_USE_MODULE(memory);
{
// Interceptor, jmp to dst
std::array code = codeGateway;
*(uint64_t*)&code[6] = (uint64_t)(dst);
int oldMode;
protect(src, code.size(), SceProtExecute | SceProtRead | SceProtWrite, &oldMode);
memcpy(pGateway.data.data(), (uint8_t*)src, lenOpCodes); // save code -> add to gateway
memcpy((uint8_t*)src, code.data(), code.size());
if (::FlushInstructionCache(GetCurrentProcess(), (void*)src, code.size()) == 0) {
LOG_ERR(L"FlushInstructionCache() failed: 0x%04x", static_cast<uint32_t>(GetLastError()));
return;
}
protect(src, code.size(), oldMode);
// - interceptor
}
memcpy(pGateway.data.data() + lenOpCodes, (uint8_t*)codeGateway.data(), codeGateway.size()); // cpy to gateway
protect((uintptr_t)pGateway.data.data(), pGateway.data.size(), SceProtExecute | SceProtRead, nullptr);
}
} // namespace memory

View File

@ -1,44 +0,0 @@
#pragma once
#include <array>
#include <stdint.h>
constexpr int SceProtRead = 1;
constexpr int SceProtWrite = 2;
constexpr int SceProtExecute = 4;
constexpr uint64_t DIRECTMEM_START = 0x100000000u;
#if defined(__APICALL_EXTERN)
#define __APICALL __declspec(dllexport)
#elif defined(__APICALL_IMPORT)
#define __APICALL __declspec(dllimport)
#else
#define __APICALL
#endif
namespace memory {
constexpr inline bool isExecute(int prot) {
return (prot & 0x4) > 0;
}
struct _t_hook {
std::array<uint8_t, 14 + 8> data; // Should be enough for inserting the hook (min 14 max 14+8)
};
__APICALL int getpagesize(void);
__APICALL uint64_t getTotalSystemMemory();
__APICALL uintptr_t reserve(uint64_t start, uint64_t size, uint64_t alignment, bool isGpu);
__APICALL uint64_t commit(uintptr_t baseAddr, uint64_t offset, uint64_t size, uint64_t alignment, int prot);
__APICALL uint64_t allocGPUMemory(uintptr_t baseAddr, uint64_t offset, uint64_t size, uint64_t alignment);
__APICALL int64_t queryAlloc(uintptr_t addr, uintptr_t* start, uintptr_t* end, int* prot);
__APICALL uint64_t alloc(uint64_t address, uint64_t size, int prot);
__APICALL uint64_t allocAligned(uint64_t address, uint64_t size, int prot, uint64_t alignment);
__APICALL bool allocFixed(uint64_t address, uint64_t size, int prot);
__APICALL bool free(uint64_t address);
__APICALL bool protect(uint64_t address, uint64_t size, int prot, int* oldMode = nullptr);
__APICALL int getProtection(uint64_t address);
__APICALL void installHook_long(uintptr_t dst, uintptr_t src, _t_hook& pGateway, size_t lenOpCodes);
} // namespace memory
#undef __APICALL

View File

@ -1,10 +0,0 @@
# Overview
<div align="center">
![](../out/docs/uml/modules/coreDeps.svg)
</div>
All communication goes through the core library. The callbacks to the emulator are set during startup, before loading and setting up the target library.
Modules should only communicate unidirectional with the core library. Everything that can't be done locally inside modules, goes here.

View File

@ -1,5 +0,0 @@
add_library(systemContent OBJECT
systemContent.cpp
)
add_dependencies(systemContent third_party psOff_utility)

View File

@ -1,145 +0,0 @@
#define __APICALL_EXTERN
#include "systemContent.h"
#undef __APICALL_EXTERN
#include "logging.h"
#include "utility/utility.h"
#include <filesystem>
#include <fstream>
#include <unordered_map>
LOG_DEFINE_MODULE(SYSTEMCONTENT);
namespace {
struct ParamInfo {
uint16_t type;
uint32_t size1;
uint32_t size2;
uint16_t nameOffset;
uint32_t valueOffset;
};
std::unordered_map<std::string, std::pair<uint16_t, std::vector<uint8_t>>> loadSFO(std::filesystem::path const& root) {
LOG_USE_MODULE(SYSTEMCONTENT);
auto file = util::openFile(root / L"param.sfo");
if (!file) {
LOG_ERR(L"No param.sfo");
return {};
}
// Check Magic
{
std::array<uint8_t, 8> buffer;
if (!util::readFile(file.get(), L"param.sfo", (char*)buffer.data(), buffer.size())) {
LOG_ERR(L"Couldn't read .sfo magic");
return {};
}
uint32_t const magic1 = *(uint32_t const*)&buffer[0];
uint32_t const magic2 = *(uint32_t const*)&buffer[4];
if (magic1 != 0x46535000 && magic2 != 0x00000101) {
LOG_ERR(L".sfo magic error| 0x%08x 0x%08x", magic1, magic2);
return {};
}
}
// - Magic
auto [nameOffset, valueOffset, numParams] = [&]() -> std::tuple<uint32_t, uint32_t, uint32_t> {
std::array<uint8_t, 12> buffer;
if (!util::readFile(file.get(), L"param.sfo", (char*)buffer.data(), buffer.size())) {
LOG_ERR(L"Couldn't read .sfo Header");
return {};
}
return std::make_tuple(*(uint32_t const*)&buffer[0], *(uint32_t const*)&buffer[4], *(uint32_t const*)&buffer[8]);
}();
// Read Params
std::vector<ParamInfo> params(numParams);
{
std::array<uint8_t, 16> buffer;
for (uint16_t n = 0; n < numParams; ++n) {
if (!util::readFile(file.get(), L"param.sfo", (char*)buffer.data(), buffer.size())) {
LOG_ERR(L"Couldn't read .sfo paramInfo %llu", n);
return {};
}
params[n] = ParamInfo {.type = *(uint16_t const*)&buffer[2],
.size1 = *(uint32_t const*)&buffer[4],
.size2 = *(uint32_t const*)&buffer[8],
.nameOffset = *(uint16_t const*)&buffer[0],
.valueOffset = *(uint32_t const*)&buffer[12]};
if (params[n].type != 0x0204 && params[n].type != 0x0404) {
LOG_ERR(L"unknown .sfo param %u type:%u", n, params[n].type);
return {};
}
}
}
// - params
// read nameList
const uint32_t nameListSize = valueOffset - nameOffset;
std::vector<uint8_t> nameList(nameListSize);
file->seekg(nameOffset);
if (!util::readFile(file.get(), L"param.sfo", (char*)nameList.data(), nameList.size())) {
LOG_ERR(L"Couldn't read .sfo nameList");
return {};
}
std::unordered_map<std::string, std::pair<uint16_t, std::vector<uint8_t>>> paramList;
for (uint16_t n = 0; n < numParams; ++n) {
auto const name = std::string((char*)&nameList[params[n].nameOffset]);
file->seekg(valueOffset + params[n].valueOffset, std::ios_base::beg);
std::vector<uint8_t> paramValue(params[n].size1);
if (!util::readFile(file.get(), L"param.sfo", (char*)paramValue.data(), paramValue.size())) {
LOG_ERR(L"Couldn't read .sfo param %u value");
return {};
}
if (params[n].type == 0x0404)
LOG_DEBUG(L"Read .sfo param[%u] %S| size:%u value(uint):%u", n, name.data(), paramValue.size(), *(uint32_t*)paramValue.data());
else if (params[n].type == 0x0204)
LOG_DEBUG(L"Read .sfo param[%u] %S| size:%u value(string):%S", n, name.data(), paramValue.size(), (char*)paramValue.data());
paramList[std::move(name)] = std::make_pair(params[n].type, std::move(paramValue));
}
return paramList;
}
} // namespace
class SystemContent: public ISystemContent {
private:
std::unordered_map<std::string, std::pair<uint16_t, std::vector<uint8_t>>> m_sfoParams;
public:
void init(std::filesystem::path const& path) final;
std::optional<uint32_t> getInt(std::string_view name) final {
if (auto it = m_sfoParams.find(name.data()); it != m_sfoParams.end()) {
if (it->second.first != 0x0404) return {};
return {*(uint32_t*)it->second.second.data()};
}
return {};
}
std::optional<std::string_view> getString(std::string_view name) {
if (auto it = m_sfoParams.find(name.data()); it != m_sfoParams.end()) {
if (it->second.first != 0x0204) return {};
return {(const char*)it->second.second.data()};
}
return {};
}
};
void SystemContent::init(std::filesystem::path const& root) {
m_sfoParams = loadSFO(root);
}
ISystemContent& accessSystemContent() {
static SystemContent inst;
return inst;
}

View File

@ -1,43 +0,0 @@
#pragma once
#include <optional>
#include <string_view>
namespace std::filesystem {
class path;
}
class ISystemContent {
public:
/**
* @brief Checks if the profided path contain "param.sfo" and reads it
*
* @param path
*/
virtual void init(std::filesystem::path const& path) = 0;
/**
* @brief Get an int value from the sfo
*
* @param name
* @return std::optional<uint32_t>
*/
virtual std::optional<uint32_t> getInt(std::string_view name) = 0;
/**
* @brief Get an string value from the sfo
*
* @param name
* @return std::optional<std::string_view>
*/
virtual std::optional<std::string_view> getString(std::string_view name) = 0;
};
#if defined(__APICALL_EXTERN)
#define __APICALL __declspec(dllexport)
#elif defined(__APICALL_IMPORT)
#define __APICALL __declspec(dllimport)
#else
#define __APICALL
#endif
__APICALL ISystemContent& accessSystemContent();
#undef __APICALL

View File

@ -1,5 +0,0 @@
add_library(timer OBJECT
timer.cpp
)
add_dependencies(timer third_party boost)

View File

@ -1,111 +0,0 @@
#pragma once
#include <cstdint>
#include <windows.h>
namespace System {
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 {
FILETIME time;
bool is_invalid;
};
// NOLINTNEXTLINE(google-runtime-references)
inline void sys_file_to_system_time_utc(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 sys_time_t_to_system(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;
sys_file_to_system_time_utc(ft, s);
}
// NOLINTNEXTLINE(google-runtime-references)
inline void sys_system_to_file_time_utc(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 sys_get_system_time(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 sys_get_system_time_utc(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 sys_query_performance_frequency(uint64_t* freq) {
LARGE_INTEGER f;
QueryPerformanceFrequency(&f);
*freq = f.QuadPart;
}
inline void sys_query_performance_counter(uint64_t* counter) {
LARGE_INTEGER c;
QueryPerformanceCounter(&c);
*counter = c.QuadPart;
}
} // namespace System

View File

@ -1,163 +0,0 @@
#define __APICALL_EXTERN
#include "timer.h"
#undef __APICALL_EXTERN
#include "SysWindowsTimer.h" // todo use boost
#include <boost/chrono.hpp>
#include <boost/thread.hpp>
namespace {
void time2timespec(SceKernelTimespec* ts, double sec) {
ts->tv_sec = static_cast<decltype(ts->tv_sec)>(sec);
ts->tv_nsec = static_cast<decltype(ts->tv_nsec)>((sec - static_cast<double>(ts->tv_sec)) * 1e9);
}
} // namespace
class Timer: public ITimer {
uint64_t m_startTime;
uint64_t m_PauseTime = 1;
uint64_t m_freq = 0;
public:
Timer() = default;
void init() final {
System::sys_query_performance_counter(&m_startTime);
m_PauseTime = 0;
System::sys_query_performance_frequency(&m_freq);
}
double getTimeS() final {
if (m_PauseTime > 0) {
return (static_cast<double>(m_PauseTime - m_startTime)) / static_cast<double>(m_freq);
}
uint64_t currentTime = 0;
System::sys_query_performance_counter(&currentTime);
return (static_cast<double>(currentTime - m_startTime)) / static_cast<double>(m_freq);
}
double getTimeMs() final { return 1e3 * getTimeS(); }
uint64_t getTicks() final {
if (m_PauseTime > 0) {
return (m_PauseTime - m_startTime);
}
uint64_t currentTime = 0;
System::sys_query_performance_counter(&currentTime);
return (currentTime - m_startTime);
}
uint64_t queryPerformance() final {
uint64_t ret = 0;
System::sys_query_performance_counter(&ret);
return ret;
}
void pause() final { System::sys_query_performance_counter(&m_PauseTime); }
void resume() final {
uint64_t currentTime = 0;
System::sys_query_performance_counter(&currentTime);
m_startTime += currentTime - m_PauseTime;
m_PauseTime = 0;
}
uint64_t getFrequency() final { return m_freq; }
bool getPaused() final { return m_PauseTime > 0; }
int getTime(SceKernelClockid id, SceKernelTimespec* tp) final;
int getTimeRes(SceKernelClockid id, SceKernelTimespec* tp) final;
int getTimeofDay(SceKernelTimeval* tp) final;
};
ITimer& accessTimer() {
static Timer inst;
return inst;
}
int Timer::getTime(SceKernelClockid id, SceKernelTimespec* tp) {
if (tp == nullptr) {
return getErr(ErrCode::_EFAULT);
}
using namespace boost::chrono;
auto func = [tp](auto const& now) {
tp->tv_sec = time_point_cast<seconds>(now).time_since_epoch().count();
tp->tv_nsec = time_point_cast<nanoseconds>(now).time_since_epoch().count() % 1000000000;
return Ok;
};
switch (id) {
case 0:
case 9:
case 10: return func(system_clock::now());
case 4:
case 11:
case 12:
case 13: return func(steady_clock::now());
case 1:
case 2:
case 5:
case 7:
case 8: {
double const ts = accessTimer().getTimeS();
time2timespec(tp, ts);
return Ok;
}
case 14: return func(thread_clock::now());
case 15: return func(process_cpu_clock::now());
}
return getErr(ErrCode::_EINVAL);
}
int Timer::getTimeRes(SceKernelClockid id, SceKernelTimespec* tp) {
if (tp == nullptr) {
return getErr(ErrCode::_EFAULT);
}
using namespace boost::chrono;
auto func = [tp](auto const& period) {
tp->tv_sec = static_cast<long>(period.num / period.den);
tp->tv_nsec = static_cast<long>((period.num % period.den) * 1e9 / period.den);
return Ok;
};
switch (id) {
case 0:
case 9:
case 10: return func(system_clock::period());
case 4:
case 11:
case 12: return func(steady_clock::period());
case 1:
case 2:
case 5:
case 7:
case 8: {
double const ts = 1.0 / accessTimer().getFrequency();
time2timespec(tp, ts);
return Ok;
}
case 14: return func(thread_clock::period());
case 15: return func(process_cpu_clock::period());
}
return getErr(ErrCode::_EINVAL);
}
int Timer::getTimeofDay(SceKernelTimeval* tp) {
if (tp == nullptr) {
return getErr(ErrCode::_EFAULT);
}
using namespace boost::chrono;
uint64_t const t = time_point_cast<microseconds>(system_clock::now()).time_since_epoch().count();
micro2timeval(tp, t);
return Ok;
}

View File

@ -1,108 +0,0 @@
#pragma once
#include "modules_include/common.h"
#include "utility/utility.h"
class ITimer {
CLASS_NO_COPY(ITimer);
protected:
ITimer() = default;
public:
/**
* @brief init the timer. Sets start time for relative time
*
*/
virtual void init() = 0;
/**
* @brief Get the relative ticks
*
* @return uint64_t
*/
virtual uint64_t getTicks() = 0;
/**
* @brief Get the relative Time in secounds
*
* @return double
*/
virtual double getTimeS() = 0;
/**
* @brief Get the relative Time in ms
*
* @return double
*/
virtual double getTimeMs() = 0;
/**
* @brief Get the absolute ticks
*
* @return uint64_t
*/
virtual uint64_t queryPerformance() = 0;
/**
* @brief Pauses the relative timer
*
*/
virtual void pause() = 0;
/**
* @brief Resumes the relative timer
*
*/
virtual void resume() = 0;
/**
* @brief Get the Frequency of the ticks
*
* @return uint64_t
*/
virtual uint64_t getFrequency() = 0;
/**
* @brief Check if paused
*
* @return true
* @return false
*/
virtual bool getPaused() = 0;
/**
* @brief Get the Time from clockId
*
* @param id
* @param tp
* @return int getErr()
*/
virtual int getTime(SceKernelClockid id, SceKernelTimespec* tp) = 0;
/**
* @brief Get the Time Resolution from clockId
*
* @param id
* @param tp
* @return int getErr()
*/
virtual int getTimeRes(SceKernelClockid id, SceKernelTimespec* tp) = 0;
/**
* @brief Get the timeOfDay
*
* @param tp
* @return int getErr()
*/
virtual int getTimeofDay(SceKernelTimeval* tp) = 0;
};
#if defined(__APICALL_EXTERN)
#define __APICALL __declspec(dllexport)
#elif defined(__APICALL_IMPORT)
#define __APICALL __declspec(dllimport)
#else
#define __APICALL
#endif
__APICALL ITimer& accessTimer();
#undef __APICALL

View File

@ -1,4 +0,0 @@
enable_language(C ASM_MASM)
add_library(unwinding OBJECT
unwind.asm
)

View File

@ -1,48 +0,0 @@
.CODE
PUBLIC unwinding_setjmp
unwinding_setjmp PROC
MOV RAX, RDI
MOV RDX ,qword ptr [RSP ]
MOV qword ptr [RAX ],RDX
MOV qword ptr [RAX + 8h ],RBX
MOV qword ptr [RAX + 10h ],RSP
MOV qword ptr [RAX + 18h ],RBP
MOV qword ptr [RAX + 20h ],R12
MOV qword ptr [RAX + 28h ],R13
MOV qword ptr [RAX + 30h ],R14
MOV qword ptr [RAX + 38h ],R15
FNSTCW word ptr [RAX + 40h ]
STMXCSR dword ptr [RAX + 44h ]
mov rax, 1
ret
unwinding_setjmp ENDP
PUBLIC unwinding_longjmp
unwinding_longjmp PROC
MOV RDX ,RDI
STMXCSR dword ptr [RSP - 4h ]
MOV EAX ,dword ptr [RDX + 44h ]
AND EAX , 0ffffffc0h
MOV EDI,dword ptr [RSP - 4h ]
AND EDI ,3fh
XOR EDI ,EAX
MOV dword ptr [RSP - 4h ],EDI
LDMXCSR dword ptr [RSP - 4h ]
MOV RCX ,qword ptr [RDX ]
MOV RBX ,qword ptr [RDX + 8h ]
MOV RSP ,qword ptr [RDX + 10h ]
MOV RBP ,qword ptr [RDX + 18h ]
MOV R12 ,qword ptr [RDX + 20h ]
MOV R13 ,qword ptr [RDX + 28h ]
MOV R14 ,qword ptr [RDX + 30h ]
MOV R15 ,qword ptr [RDX + 38h ]
FLDCW word ptr [RDX + 40h ]
MOV RAX ,0
MOV qword ptr [RSP ],RCX
ret
unwinding_longjmp ENDP
End

View File

@ -1,18 +0,0 @@
#pragma once
#include "utility/utility.h"
#include <array>
#include <stdint.h>
constexpr int _JBLEN_AMD = 20; /* Size of the jmp_buf on AMD64. */
struct unwinding_jmp_buf {
uint64_t _jb[_JBLEN_AMD];
};
extern "C" {
SYSV_ABI bool unwinding_setjmp(unwinding_jmp_buf* data);
SYSV_ABI void unwinding_longjmp(unwinding_jmp_buf* data);
}

View File

@ -1,14 +0,0 @@
add_library(videoout OBJECT
videoout.cpp
vulkan/vulkanSetup.cpp
vulkan/vulkanHelper.cpp
imageHandler.cpp
)
add_dependencies(videoout third_party psOff_utility initParams imports boost)
target_include_directories(videoout PRIVATE
${Vulkan_INCLUDE_DIRS}
${PRJ_SRC_DIR}/third_party/optick/src
${PRJ_SRC_DIR}/third_party/magic_enum/include
${PRJ_SRC_DIR}/third_party/SDL2/include
)

View File

@ -1,269 +0,0 @@
#include "imageHandler.h"
#include "core/initParams/initParams.h"
#include "logging.h"
#include "vulkan/vulkanSetup.h"
#include <boost/thread/thread.hpp>
#include <vulkan/vk_enum_string_helper.h>
LOG_DEFINE_MODULE(ImageHandler);
constexpr uint32_t NUM_DISPLAY_BUFFERS = 3;
class ImageHandler: public IImageHandler {
uint32_t m_maxImages = 1;
VkDevice m_device;
// Vulkan internal
VkSwapchainKHR m_swapchain = nullptr;
VkCommandPool m_commandPool = nullptr;
std::vector<VkSemaphore> m_semsImageReady;
std::vector<VkSemaphore> m_semsImageCopied;
std::vector<VkCommandBuffer> m_commandBuffer;
std::vector<VkFence> m_fenceSubmit;
size_t m_nextIndex = 0;
uint32_t m_countImages = 0;
std::vector<VkImage> m_scImages;
// - vulkan
boost::mutex m_mutexInt;
size_t m_waitId = 0;
size_t m_presentCount = 1;
boost::condition_variable m_present_condVar; /// regulates number of images
bool m_stop = false;
public:
ImageHandler(VkDevice vkDevice, VkExtent2D extentWindow, vulkan::QueueInfo* queue): IImageHandler(extentWindow, queue), m_device(vkDevice) {};
virtual ~ImageHandler() = default;
// ### Interace
void init(vulkan::VulkanObj* obj, VkSurfaceKHR surface) final;
void deinit() final;
void stop() final {
m_stop = true;
m_present_condVar.notify_all();
}
std::optional<ImageData> getImage_blocking() final;
void notify_done(ImageData const&) final;
VkSwapchainKHR getSwapchain() const final { return m_swapchain; }
};
std::unique_ptr<IImageHandler> createImageHandler(VkDevice vkDevice, VkExtent2D extentWindow, vulkan::QueueInfo* queue) {
return std::make_unique<ImageHandler>(vkDevice, extentWindow, queue);
}
std::optional<ImageData> ImageHandler::getImage_blocking() {
LOG_USE_MODULE(ImageHandler);
boost::unique_lock lock(m_mutexInt);
// Regulate max images
auto waitId = ++m_waitId;
LOG_DEBUG(L"waitId:%llu presentCount:%llu", waitId, m_presentCount);
m_present_condVar.wait(lock, [this, &waitId] { return m_stop || (waitId == m_presentCount && m_countImages < m_maxImages); });
if (m_stop) return {};
// -
ImageData imageData {
.semImageReady = m_semsImageReady[m_nextIndex],
.semImageCopied = m_semsImageCopied[m_nextIndex],
.cmdBuffer = m_commandBuffer[m_nextIndex],
.submitFence = m_fenceSubmit[m_nextIndex],
};
// Get swapchain image
{
int numTries = 2;
VkResult result = VK_SUCCESS;
for (; numTries >= 0; --numTries) {
if (result = vkAcquireNextImageKHR(m_device, m_swapchain, UINT64_MAX, imageData.semImageReady, VK_NULL_HANDLE, &imageData.index); result != VK_SUCCESS) {
if (result == VK_NOT_READY) {
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
} else
break;
}
if (numTries <= 0) {
LOG_ERR(L"vkAcquireNextImageKHR %S", string_VkResult(result));
++m_presentCount; // fake present
lock.unlock();
m_present_condVar.notify_all();
return {};
}
}
// - swapchain image
m_nextIndex = (++m_nextIndex) % m_maxImages;
++m_countImages;
imageData.swapchainImage = m_scImages[imageData.index];
imageData.extent = m_extentWindow;
{ // Begin commandbuffer| end is done by user
VkCommandBufferBeginInfo const beginInfo {
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
.flags = 0,
.pInheritanceInfo = nullptr,
};
if (auto result = vkBeginCommandBuffer(imageData.cmdBuffer, &beginInfo); result != VK_SUCCESS) {
LOG_CRIT(L"Error vkBeginCommandBuffer: %S", string_VkResult(result));
}
}
return imageData;
}
void ImageHandler::notify_done(ImageData const& imageData) {
LOG_USE_MODULE(ImageHandler);
// vkQueuePresentKHR: amd waits and nvidia doesn't -> manual wait
vkWaitForFences(m_device, 1, &imageData.submitFence, VK_TRUE, UINT64_MAX);
vkResetFences(m_device, 1, &imageData.submitFence);
vkResetCommandBuffer(imageData.cmdBuffer, VK_COMMAND_BUFFER_RESET_RELEASE_RESOURCES_BIT);
boost::unique_lock lock(m_mutexInt);
++m_presentCount;
--m_countImages;
lock.unlock();
m_present_condVar.notify_all();
}
void ImageHandler::init(vulkan::VulkanObj* obj, VkSurfaceKHR surface) {
LOG_USE_MODULE(ImageHandler);
{ // Create Display ready sems and fences
VkSemaphoreCreateInfo const semCreateInfo {
.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO,
.pNext = 0,
.flags = 0,
};
VkFenceCreateInfo const fenceCreateInfo {
.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO,
.pNext = 0,
.flags = 0,
};
m_semsImageReady.resize(m_maxImages);
m_semsImageCopied.resize(m_maxImages);
m_fenceSubmit.resize(m_maxImages);
for (size_t n = 0; n < m_maxImages; ++n) {
vkCreateSemaphore(m_device, &semCreateInfo, nullptr, &m_semsImageReady[n]);
vkCreateSemaphore(m_device, &semCreateInfo, nullptr, &m_semsImageCopied[n]);
vkCreateFence(obj->deviceInfo.device, &fenceCreateInfo, nullptr, &m_fenceSubmit[n]);
}
}
auto numBuffers = NUM_DISPLAY_BUFFERS;
{ // swapchain
numBuffers = std::clamp(numBuffers, obj->surfaceCapabilities.capabilities.minImageCount, obj->surfaceCapabilities.capabilities.maxImageCount);
m_extentWindow.width = std::clamp(m_extentWindow.width, obj->surfaceCapabilities.capabilities.minImageExtent.width,
obj->surfaceCapabilities.capabilities.maxImageExtent.width);
m_extentWindow.height = std::clamp(m_extentWindow.height, obj->surfaceCapabilities.capabilities.minImageExtent.height,
obj->surfaceCapabilities.capabilities.maxImageExtent.height);
auto [displayFormat, displayColorSpace] = getDisplayFormat(obj);
m_imageFormat = displayFormat;
bool const useVsync = accessInitParams()->useVSYNC();
VkSwapchainCreateInfoKHR const createInfo {
.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR,
.pNext = nullptr,
.flags = 0,
.surface = surface,
.minImageCount = numBuffers,
.imageFormat = m_imageFormat,
.imageColorSpace = displayColorSpace,
.imageExtent = m_extentWindow,
.imageArrayLayers = 1,
.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT,
.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE,
.queueFamilyIndexCount = 0,
.pQueueFamilyIndices = nullptr,
.preTransform = obj->surfaceCapabilities.capabilities.currentTransform,
.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR,
.presentMode = useVsync ? VK_PRESENT_MODE_FIFO_KHR : VK_PRESENT_MODE_IMMEDIATE_KHR,
.clipped = VK_TRUE,
.oldSwapchain = nullptr,
};
vkCreateSwapchainKHR(obj->deviceInfo.device, &createInfo, nullptr, &m_swapchain);
}
{ // swapchain images
uint32_t numImages = 0;
vkGetSwapchainImagesKHR(m_device, m_swapchain, &numImages, nullptr);
m_scImages.resize(numImages);
vkGetSwapchainImagesKHR(m_device, m_swapchain, &numImages, m_scImages.data());
}
{ // Command buffer
VkCommandPoolCreateInfo const poolInfo {
.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT,
.queueFamilyIndex = m_queue->family,
};
if (auto result = vkCreateCommandPool(obj->deviceInfo.device, &poolInfo, nullptr, &m_commandPool); result != VK_SUCCESS) {
LOG_CRIT(L"Couldn't create commandpool(graphics): %d", result);
}
m_commandBuffer.resize(m_maxImages);
VkCommandBufferAllocateInfo const allocInfo {
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
.commandPool = m_commandPool,
.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY,
.commandBufferCount = m_maxImages,
};
if (auto result = vkAllocateCommandBuffers(obj->deviceInfo.device, &allocInfo, m_commandBuffer.data()); result != VK_SUCCESS) {
LOG_CRIT(L"Couldn't create commandbuffers(graphics): %d", result);
}
}
}
void ImageHandler::deinit() {
printf("deinit ImageHandler\n");
for (auto& sem: m_semsImageReady) {
vkDestroySemaphore(m_device, sem, nullptr);
}
for (auto& sem: m_semsImageCopied) {
vkDestroySemaphore(m_device, sem, nullptr);
}
for (auto& fence: m_fenceSubmit) {
vkDestroyFence(m_device, fence, nullptr);
}
if (m_commandPool != nullptr) vkDestroyCommandPool(m_device, m_commandPool, nullptr);
if (m_swapchain != nullptr) vkDestroySwapchainKHR(m_device, m_swapchain, nullptr);
printf("deinit ImageHandler| done\n");
}

View File

@ -1,56 +0,0 @@
#pragma once
#include "utility/utility.h"
#include <optional>
#include <vulkan/vulkan_core.h>
struct ImageData {
VkImage swapchainImage = nullptr;
VkSemaphore semImageReady = nullptr; /// image is ready for operation
VkSemaphore semImageCopied = nullptr; /// Image has been copied, ready to swap
VkCommandBuffer cmdBuffer = nullptr; /// Commandbuffer to be used for transer etc. Already called begin!
VkFence submitFence; /// used for the cmdBuffer queue submit
VkExtent2D extent;
uint32_t index = 0;
};
namespace vulkan {
struct VulkanObj;
struct QueueInfo;
} // namespace vulkan
class IImageHandler {
CLASS_NO_COPY(IImageHandler);
CLASS_NO_MOVE(IImageHandler);
protected:
IImageHandler(VkExtent2D extentWindow, vulkan::QueueInfo* queue): m_extentWindow(extentWindow), m_queue(queue) {}
VkExtent2D m_extentWindow;
VkFormat m_imageFormat;
vulkan::QueueInfo* m_queue;
public:
virtual ~IImageHandler() = default;
auto getExtent() const { return m_extentWindow; }
auto getFormat() const { return m_imageFormat; }
auto getQueue() const { return m_queue; }
virtual void init(vulkan::VulkanObj* obj, VkSurfaceKHR surface) = 0;
virtual void deinit() = 0;
virtual void stop() = 0;
virtual std::optional<ImageData> getImage_blocking() = 0;
virtual void notify_done(ImageData const&) = 0;
virtual VkSwapchainKHR getSwapchain() const = 0;
};
std::unique_ptr<IImageHandler> createImageHandler(VkDevice vkDevice, VkExtent2D extentWindow, vulkan::QueueInfo* queue);

View File

@ -1,22 +0,0 @@
#pragma once
#include <vulkan/vulkan_core.h>
#if defined(__APICALL_EXTERN)
#define __APICALL __declspec(dllexport)
#elif defined(__APICALL_IMPORT)
#define __APICALL __declspec(dllimport)
#else
#define __APICALL
#endif
/**
* @brief Get the required memory alignment. Dont' call from constructor.
*
* @param format
* @param extent shouldn't matter if not 100% correct
* @return memory alignment
*/
__APICALL uint64_t getImageAlignment(VkFormat format, VkExtent3D const& extent);
#undef __APICALL

View File

@ -1,23 +0,0 @@
## Video out
* Manages the display buffers used in Linux/PlayStation.
* Setup of Vulkan (GPU detection etc.)
* Emits Kernel events: flip, vblank
<div align="center">
![](../../out/docs/uml/modules/videoout_class.svg)
</div>
### Swapchain image handling
User calls getImage_blocking() to retrieve the display image. After present notify_done() is called.
getImage_blocking() releases users FIFO. The whole swapchain images are handled by ImageHandler
<div align="center">
![](../../out/docs/uml/modules/videout_swapchain.svg)
</div>

View File

@ -1,808 +0,0 @@
#define __APICALL_EXTERN
#include "videoout.h"
#include "intern.h"
#undef __APICALL_EXTERN
#include "core/imports/exports/graphics.h"
#include "core/imports/imports_func.h"
#include "core/initParams/initParams.h"
#include "core/kernel/eventqueue.h"
#include "core/systemContent/systemContent.h"
#include "core/timer/timer.h"
#include "imageHandler.h"
#include "logging.h"
#include "modules/libSceVideoOut/codes.h"
#include "modules/libSceVideoOut/types.h"
#include "modules_include/common.h"
#include "vulkan/vulkanHelper.h"
#include <queue>
#include <SDL.h>
#include <algorithm>
#include <array>
#include <assert.h>
#include <format>
#include <functional>
#include <list>
#include <magic_enum/magic_enum.hpp>
#include <memory>
#include <mutex>
#include <optick.h>
#include <thread>
LOG_DEFINE_MODULE(VideoOut);
using namespace Kernel;
namespace {
size_t constexpr WindowsMAX = 2;
std::pair<uint32_t, uint32_t> getDisplayBufferSize(uint32_t width, uint32_t height, uint32_t pitch, bool tile, bool neo) {
if (pitch == 3840) {
if (width == 3840 && height == 2160 && tile && !neo) {
return {33423360, 32768};
}
if (width == 3840 && height == 2160 && tile && neo) {
return {33423360, 65536};
}
if (width == 3840 && height == 2160 && !tile && !neo) {
return {33177600, 256};
}
if (width == 3840 && height == 2160 && !tile && neo) {
return {33177600, 256};
}
}
if (pitch == 1920) {
if (width == 1920 && height == 1080 && tile && !neo) {
return {8355840, 32768};
}
if (width == 1920 && height == 1080 && tile && neo) {
return {8847360, 65536};
}
if (width == 1920 && height == 1080 && !tile && !neo) {
return {8294400, 256};
}
if (width == 1920 && height == 1080 && !tile && neo) {
return {8294400, 256};
}
}
if (pitch == 1280) {
if (width == 1280 && height == 720 && tile && !neo) {
return {3932160, 32768};
}
if (width == 1280 && height == 720 && tile && neo) {
return {3932160, 65536};
}
if (width == 1280 && height == 720 && !tile && !neo) {
return {3686400, 256};
}
if (width == 1280 && height == 720 && !tile && neo) {
return {3686400, 256};
}
}
return {0, 0};
}
struct VideoOutConfig {
SceVideoOutFlipStatus flipStatus;
SceVideoOutVblankStatus vblankStatus;
SceVideoOutResolutionStatus resolution;
std::array<vulkan::SwapchainData, 16> bufferSets;
std::array<int32_t, 16> buffers; // index to bufferSets
uint8_t buffersSetsCount = 0;
double fps = 0.0;
VideoOutConfig() { std::fill(buffers.begin(), buffers.end(), -1); }
};
enum class FlipRate {
_60Hz,
_30Hz,
_20Hz,
};
struct Context {
int userId = -1;
FlipRate fliprate = FlipRate::_60Hz;
VideoOutConfig config;
SDL_Window* window;
VkSurfaceKHR surface = nullptr;
std::list<EventQueue::IKernelEqueue_t> eventFlip;
std::list<EventQueue::IKernelEqueue_t> eventVblank;
};
enum class MessageType { open, close, flip };
struct Message {
MessageType type;
int handle = -1;
bool* done = nullptr;
int index = 0;
uint32_t setIndex = 0;
ImageData imageData = {};
};
std::string getTitle(int handle, uint64_t frame, size_t fps, FlipRate maxFPS) {
static auto title = [] {
auto title = accessSystemContent().getString("TITLE");
if (title) return title.value().data();
return "psOFF";
}();
return std::format("{}({}): frame={} fps={}(locked:{})", title, handle, frame, fps, magic_enum::enum_name(maxFPS).data());
}
} // namespace
class VideoOut: public IVideoOut, private IEventsGraphics {
std::array<Context, WindowsMAX> m_windows;
uint32_t const m_widthTotal = 1920, m_heightTotal = 1080; // todo: make config
mutable std::mutex m_mutexInt;
vulkan::VulkanObj* m_vulkanObj = nullptr;
std::unique_ptr<IGraphics> m_graphics;
std::unique_ptr<IImageHandler> m_imageHandler;
std::thread m_threadSDL2;
std::condition_variable m_condSDL2;
std::condition_variable m_condDone;
bool m_stop = false;
std::queue<Message> m_messages;
uint64_t m_vblankTime = (uint64_t)(1e6 / 59.0); // in us
void vblankEnd(int handle, uint64_t curTime, uint64_t curProcTime);
// Callback Graphics
void eventDoFlip(int handle, int index, int64_t flipArg, VkSemaphore waitSema, size_t waitValue) final {
OPTICK_EVENT();
LOG_USE_MODULE(VideoOut);
std::unique_lock lock(m_mutexInt);
m_graphics->submited();
auto& window = m_windows[handle - 1];
uint32_t const setIndex = window.config.buffers[index];
auto& swapchain = window.config.bufferSets[setIndex];
auto& displayBufferMeta = swapchain.buffers[index];
auto imageData = m_imageHandler->getImage_blocking();
auto& flipStatus = window.config.flipStatus;
++flipStatus.gcQueueNum;
flipStatus.flipArg = flipArg;
auto& timer = accessTimer();
auto const curTime = (uint64_t)(1e9 * timer.getTimeS());
flipStatus.submitTsc = curTime;
if (!imageData) {
LOG_ERR(L"<- submitFlip(%d):%u %d error", handle, setIndex, index);
m_graphics->submitDone();
doFlip(window, handle);
return;
}
LOG_DEBUG(L"-> eventDoFlip(%d):%u %d", handle, setIndex, index);
transferDisplay(*imageData, displayBufferMeta, waitSema, waitValue);
// window.config.flipStatus.currentBuffer = index; // set after flip, before vblank
m_messages.push({MessageType::flip, handle - 1, nullptr, index, setIndex, *imageData});
lock.unlock();
m_condSDL2.notify_one();
LOG_DEBUG(L"<- eventDoFlip(%d):%u %d", handle, setIndex, index);
}
vulkan::QueueInfo* getQueue(vulkan::QueueType type) final;
// -
void transferDisplay(ImageData const& imageData, vulkan::SwapchainData::DisplayBuffers& displayBufferMeta, VkSemaphore waitSema, size_t waitValue);
std::thread createSDLThread();
void doFlip(Context& ctx, int handle);
public:
VideoOut() = default;
virtual ~VideoOut();
/**
* @brief Preinit handle 1(index 0) with main window
* Needed for early vulkan init and with it all the managers
*/
void init() final;
int open(int userId) final;
void close(int handle) final;
void setFliprate(int handle, int rate) final {
LOG_USE_MODULE(VideoOut);
LOG_INFO(L"Fliprate:%d", rate);
std::unique_lock const lock(m_mutexInt);
m_windows[handle - 1].fliprate = (FlipRate)rate;
switch ((FlipRate)rate) {
case FlipRate::_60Hz: m_vblankTime = (uint64_t)(1e6 / 59.0); break;
case FlipRate::_30Hz: m_vblankTime = (uint64_t)(1e6 / 29.0); break;
case FlipRate::_20Hz: m_vblankTime = (uint64_t)(1e6 / 19.0); break;
}
}
vulkan::DeviceInfo* getDeviceInfo() final { return &m_vulkanObj->deviceInfo; }
int addEvent(int handle, EventQueue::KernelEqueueEvent const& event, Kernel::EventQueue::IKernelEqueue_t eq) final;
void removeEvent(int handle, Kernel::EventQueue::IKernelEqueue_t eq, int const ident) final;
void submitFlip(int handle, int index, int64_t flipArg) final; // -> Renderer
void getFlipStatus(int handle, void* status) final;
void getVBlankStatus(int handle, void* status) final;
void getResolution(int handle, void* status) final;
void getBufferAttribute(void* attribute, uint32_t pixel_format, int32_t tiling_mode, int32_t aspect_ratio, uint32_t width, uint32_t height,
uint32_t pitch_in_pixel) final;
int registerBuffers(int handle, int startIndex, void* const* addresses, int numBuffer, const void* attribute) final;
int getPendingFlips(int handle) final {
std::unique_lock const lock(m_mutexInt);
auto& flipStatus = m_windows[handle - 1].config.flipStatus;
return flipStatus.gcQueueNum;
}
IGraphics* getGraphics() final {
assert(m_graphics);
return m_graphics.get();
};
std::unique_lock<std::mutex> getSDLLock() const final { return std::unique_lock(m_mutexInt); }
// ### Gpu memory forwards
bool isGPULocal(uint64_t vaddr) final { return m_graphics->isGPULocal(vaddr); }
bool notify_allocHeap(uint64_t vaddr, uint64_t size, int memoryProtection) final { return m_graphics->notify_allocHeap(vaddr, size, memoryProtection); }
// --- Gpu memory forwards
};
IVideoOut& accessVideoOut() {
static VideoOut inst;
return inst;
}
VideoOut::~VideoOut() {
// Logging doesn't work here, even with flush
printf("shutting down VideoOut\n");
m_stop = true;
m_condSDL2.notify_one();
// printf("VideoOut| waiting on gpu idle\n");
m_imageHandler->stop();
m_graphics->stop();
vkQueueWaitIdle(m_imageHandler->getQueue()->queue);
// shutdown graphics first (uses vulkan)
m_graphics->deinit();
m_graphics.reset();
// printf("VideoOut| Destroy surface\n");
// for (auto& window: m_windows) {
// if (window.userId < 0) continue;
// if (window.surface != nullptr) vkDestroySurfaceKHR(m_vulkanObj->deviceInfo.instance, window.surface, nullptr);
// }
m_imageHandler->deinit();
m_imageHandler.reset();
printf("VideoOut| Destroy vulkan\n");
deinitVulkan(m_vulkanObj);
printf("shutdown VideoOut done\n");
}
void VideoOut::init() {
LOG_USE_MODULE(VideoOut);
LOG_DEBUG(L"createSDLThread()");
m_threadSDL2 = createSDLThread();
std::unique_lock lock(m_mutexInt);
static bool done = false;
m_messages.push(Message {MessageType::open, 0, &done});
lock.unlock();
m_condSDL2.notify_one();
lock.lock();
m_condDone.wait(lock, [=] { return done; });
}
int VideoOut::open(int userId) {
OPTICK_THREAD("libVideOut");
LOG_USE_MODULE(VideoOut);
std::unique_lock lock(m_mutexInt);
// Get Free handle
int const windowIndex = [&] {
for (int n = 0; n < m_windows.size(); ++n) {
if (m_windows[n].userId < 0) {
m_windows[n].userId = userId;
m_windows[n].config.flipStatus = {};
m_windows[n].config.vblankStatus = {};
return n;
}
}
return -1;
// -
}();
// -
if (windowIndex == 0) return 1; // Special case windows[0] is mainWindow
// Create new window
static bool done = false;
m_messages.push(Message {MessageType::open, windowIndex, &done});
lock.unlock();
m_condSDL2.notify_one();
lock.lock();
m_condDone.wait(lock, [=] { return done; });
return 1 + windowIndex; // handle starts with 1
}
void VideoOut::close(int handle) {
LOG_USE_MODULE(VideoOut);
LOG_INFO(L"<-- VideoOut Close(%d)", handle);
std::unique_lock lock(m_mutexInt);
auto& window = m_windows[handle - 1];
window.userId = -1;
for (auto& item: window.eventFlip) {
if (item != nullptr) {
(void)item->deleteEvent(VIDEO_OUT_EVENT_FLIP, EventQueue::KERNEL_EVFILT_VIDEO_OUT);
}
}
for (auto& item: window.eventVblank) {
if (item != nullptr) {
(void)item->deleteEvent(VIDEO_OUT_EVENT_VBLANK, EventQueue::KERNEL_EVFILT_VIDEO_OUT);
}
}
window.eventFlip.clear();
window.eventVblank.clear();
static bool done = false;
m_messages.push(Message {MessageType::close, handle, &done});
lock.unlock();
m_condSDL2.notify_one();
lock.lock();
m_condDone.wait(lock, [=] { return done; });
}
int VideoOut::addEvent(int handle, EventQueue::KernelEqueueEvent const& event, EventQueue::IKernelEqueue_t eq) {
LOG_USE_MODULE(VideoOut);
if (eq == nullptr) return getErr(ErrCode::_EINVAL);
int result = eq->addEvent(event);
switch (event.event.ident) {
case VIDEO_OUT_EVENT_FLIP: {
LOG_DEBUG(L"+VIDEO_OUT_EVENT_FLIP(%d)", handle);
std::unique_lock const lock(m_mutexInt);
m_windows[handle - 1].eventFlip.push_back(eq);
} break;
case VIDEO_OUT_EVENT_VBLANK: {
LOG_DEBUG(L"+VIDEO_OUT_EVENT_VBLANK(%d)", handle);
std::unique_lock const lock(m_mutexInt);
m_windows[handle - 1].eventVblank.push_back(eq);
} break;
default: LOG_ERR(L"undefinded:%d", event.event.ident); result = -1;
}
return result;
}
void VideoOut::removeEvent(int handle, Kernel::EventQueue::IKernelEqueue_t eq, int const ident) {
LOG_USE_MODULE(VideoOut);
switch (ident) {
case VIDEO_OUT_EVENT_FLIP: {
LOG_DEBUG(L"-VIDEO_OUT_EVENT_FLIP(%d)", handle);
std::unique_lock const lock(m_mutexInt);
m_windows[handle - 1].eventFlip.remove(eq);
} break;
case VIDEO_OUT_EVENT_VBLANK: {
LOG_DEBUG(L"-VIDEO_OUT_EVENT_VBLANK(%d)", handle);
std::unique_lock const lock(m_mutexInt);
m_windows[handle - 1].eventVblank.remove(eq);
} break;
default: LOG_CRIT(L"undefinded:%d", ident);
}
}
void VideoOut::transferDisplay(ImageData const& imageData, vulkan::SwapchainData::DisplayBuffers& displayBufferMeta, VkSemaphore waitSema, size_t waitValue) {
vulkan::transfer2Display(&displayBufferMeta, imageData, m_graphics.get());
vulkan::submitDisplayTransfer(&displayBufferMeta, imageData, m_imageHandler->getQueue(), waitSema, waitValue);
}
void VideoOut::submitFlip(int handle, int index, int64_t flipArg) {
OPTICK_EVENT();
LOG_USE_MODULE(VideoOut);
std::unique_lock lock(m_mutexInt);
m_graphics->submited(); // increase internal counter (wait for flip)
auto& window = m_windows[handle - 1];
uint32_t const setIndex = window.config.buffers[index];
auto& swapchain = window.config.bufferSets[setIndex];
auto& displayBufferMeta = swapchain.buffers[index];
LOG_DEBUG(L"-> submitFlip(%d):%u %d", handle, setIndex, index);
auto imageData = m_imageHandler->getImage_blocking();
auto& flipStatus = window.config.flipStatus;
++flipStatus.gcQueueNum;
flipStatus.flipArg = flipArg;
auto& timer = accessTimer();
auto const curTime = (uint64_t)(1e9 * timer.getTimeS());
flipStatus.submitTsc = curTime;
if (!imageData) {
LOG_ERR(L"<- submitFlip(%d):%u %d error", handle, setIndex, index);
m_graphics->submitDone();
doFlip(window, handle);
return;
}
transferDisplay(*imageData, displayBufferMeta, nullptr, 0);
// window.config.flipStatus.currentBuffer = index; // set after flip, before vblank
m_messages.push({MessageType::flip, handle - 1, nullptr, index, setIndex, *imageData});
lock.unlock();
m_condSDL2.notify_one();
LOG_DEBUG(L"<- submitFlip(%d):%u %d", handle, setIndex, index);
}
void VideoOut::doFlip(Context& ctx, int handle) {
auto& flipStatus = ctx.config.flipStatus;
auto& timer = accessTimer();
auto const curTime = (uint64_t)(1e6 * timer.getTimeS());
auto const procTime = timer.queryPerformance();
auto elapsed_us = curTime - flipStatus.processTime;
flipStatus.tsc = procTime;
flipStatus.processTime = curTime;
++flipStatus.count;
--flipStatus.gcQueueNum;
vblankEnd(handle, curTime, procTime);
// Trigger Event Flip
for (auto& item: ctx.eventFlip) {
(void)item->triggerEvent(VIDEO_OUT_EVENT_FLIP, EventQueue::KERNEL_EVFILT_VIDEO_OUT, reinterpret_cast<void*>(ctx.config.flipStatus.flipArg));
}
// - Flip event
}
void VideoOut::getFlipStatus(int handle, void* status) {
LOG_USE_MODULE(VideoOut);
LOG_TRACE(L"%S(%d)", __FUNCTION__, handle);
std::unique_lock const lock(m_mutexInt);
*(SceVideoOutFlipStatus*)status = m_windows[handle - 1].config.flipStatus;
}
void VideoOut::getVBlankStatus(int handle, void* status) {
LOG_USE_MODULE(VideoOut);
LOG_TRACE(L"%S(%d)", __FUNCTION__, handle);
std::unique_lock const lock(m_mutexInt);
auto& vblank = m_windows[handle - 1].config.vblankStatus;
*(SceVideoOutVblankStatus*)status = vblank;
}
void VideoOut::vblankEnd(int handle, uint64_t curTime, uint64_t curProcTime) {
OPTICK_EVENT();
auto& window = m_windows[handle - 1];
auto& vblank = window.config.vblankStatus;
// vblank.processTime = 0;
vblank.tsc = curProcTime;
vblank.processTime = curTime;
++vblank.count;
for (auto& item: window.eventFlip) {
(void)item->triggerEvent(VIDEO_OUT_EVENT_VBLANK, EventQueue::KERNEL_EVFILT_VIDEO_OUT, reinterpret_cast<void*>(window.config.vblankStatus.count));
}
}
void VideoOut::getResolution(int handle, void* status) {
LOG_USE_MODULE(VideoOut);
LOG_TRACE(L"%S(%d)", __FUNCTION__, handle);
std::unique_lock const lock(m_mutexInt);
*(SceVideoOutResolutionStatus*)status = m_windows[handle - 1].config.resolution;
}
void VideoOut::getBufferAttribute(void* attribute, uint32_t pixel_format, int32_t tiling_mode, int32_t aspect_ratio, uint32_t width, uint32_t height,
uint32_t pitchInPixel) {
LOG_USE_MODULE(VideoOut);
LOG_TRACE(L"%S", __FUNCTION__);
auto [displayFormat, _] = vulkan::getDisplayFormat(m_vulkanObj);
*(SceVideoOutBufferAttribute*)attribute = SceVideoOutBufferAttribute {
.pixelFormat = SceVideoOutPixelFormat::PIXEL_FORMAT_A8R8G8B8_SRGB, // todo get vulkan pixel_format?
.tilingMode = tiling_mode,
.aspectRatio = aspect_ratio,
.width = width,
.height = height,
.pitchInPixel = pitchInPixel,
};
}
int VideoOut::registerBuffers(int handle, int startIndex, void* const* addresses, int numBuffer, const void* attribute) {
LOG_USE_MODULE(VideoOut);
LOG_TRACE(L"%S(%d)", __FUNCTION__, handle);
auto& config = m_windows[handle - 1].config;
auto const setIndex = config.buffersSetsCount++;
[[unlikely]] if (setIndex > 15) {
LOG_CRIT(L"buffersSetsCount > 15");
return ::Err::VIDEO_OUT_ERROR_NO_EMPTY_SLOT;
}
auto& bufferSet = config.bufferSets[setIndex];
bufferSet.buffers.resize(numBuffer);
for (int i = startIndex; i < startIndex + numBuffer; ++i) {
if (config.buffers[i] >= 0) return ::Err::VIDEO_OUT_ERROR_SLOT_OCCUPIED;
config.buffers[i] = setIndex;
}
auto const* _att = (SceVideoOutBufferAttribute const*)attribute;
for (size_t n = 0; n < numBuffer; ++n) {
bufferSet.buffers[n].bufferVaddr = (uintptr_t)addresses[n];
auto const [displaySize, displaySizeAlign] = getDisplayBufferSize(_att->width, _att->height, _att->pitchInPixel, _att->tilingMode, false);
bufferSet.buffers[n].bufferSize = displaySize;
bufferSet.buffers[n].bufferAlign = displaySizeAlign;
LOG_INFO(L"+bufferset[%d] buffer:%d vaddr:0x%08llx-0x%08llx", setIndex, n, (uint64_t)addresses[n], (uint64_t)addresses[n] + displaySizeAlign);
auto [format, colorSpace] = vulkan::getDisplayFormat(m_vulkanObj);
if (!m_graphics->registerDisplayBuffer(bufferSet.buffers[n].bufferVaddr, VkExtent2D {.width = _att->width, .height = _att->height}, _att->pitchInPixel,
format))
return -1;
}
return setIndex;
}
vulkan::QueueInfo* VideoOut::getQueue(vulkan::QueueType type) {
std::unique_lock const lock(m_mutexInt);
auto& queueInfo = m_vulkanObj->queues.items[vulkan::getIndex(type)];
auto bestIt = queueInfo.begin();
// Search for least used
for (auto it = queueInfo.begin()++; it != queueInfo.end(); ++it) {
if ((*it)->useCount < (*bestIt)->useCount) {
bestIt = it;
}
}
// -
++(*bestIt)->useCount;
return bestIt->get();
}
void cbWindow_close(SDL_Window* window) {
const SDL_MessageBoxButtonData mbbd[2] {
{.flags = SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT, .buttonid = 0, .text = "Cancel"},
{.flags = 0, .buttonid = 1, .text = "Quit"},
};
const SDL_MessageBoxData mbd {
.flags = SDL_MESSAGEBOX_WARNING,
.window = window,
.title = "Are sure you want to quit?",
.message = "All your unsaved progress will be lost!",
.numbuttons = 2,
.buttons = mbbd,
.colorScheme = nullptr,
};
int buttonId = 0;
if (SDL_ShowMessageBox(&mbd, &buttonId) == 0 && buttonId != 1) return;
exit(0); // destructor cleans up.
}
std::thread VideoOut::createSDLThread() {
return std::thread([this] {
util::setThreadName("VideoOut");
OPTICK_THREAD("VideoOut");
LOG_USE_MODULE(VideoOut);
LOG_DEBUG(L"Init SDL2 video");
SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS);
// SDL polling helper
auto func_pollSDL = [](auto& window) {
SDL_Event event;
while (SDL_PollEvent(&event)) {
switch (event.type) {
case SDL_WINDOWEVENT:
switch (event.window.event) {
case SDL_WINDOWEVENT_CLOSE: cbWindow_close(window); break;
default: break;
}
case SDL_KEYUP:
if (event.key.keysym.scancode == SDL_SCANCODE_ESCAPE) {
cbWindow_close(window);
}
default: break;
}
}
};
// -
while (!m_stop) {
std::unique_lock lock(m_mutexInt);
m_condSDL2.wait_for(lock, std::chrono::microseconds(m_vblankTime), [this] { return m_stop || !m_messages.empty(); });
if (m_stop) break;
// Handle VBlank
if (m_messages.empty()) {
using namespace std::chrono;
func_pollSDL(m_windows[0].window); // check only main for now
auto& timer = accessTimer();
auto const curTime = (uint64_t)(1e6 * timer.getTimeS());
auto const procTime = timer.queryPerformance();
for (size_t n = 0; n < m_windows.size(); ++n) {
vblankEnd(1 + n, curTime, procTime);
}
continue;
}
// -
auto item = m_messages.front();
auto const handleIndex = item.handle;
auto& window = m_windows[handleIndex];
switch (item.type) {
case MessageType::open: {
auto const title = getTitle(handleIndex, 0, 0, window.fliprate);
window.window = SDL_CreateWindow(title.c_str(), SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, m_widthTotal, m_heightTotal,
SDL_WINDOW_VULKAN | SDL_WINDOW_SHOWN);
SDL_GetWindowSize(window.window, (int*)(&window.config.resolution.paneWidth), (int*)(&window.config.resolution.paneHeight));
LOG_INFO(L"--> VideoOut Open(%S)| %d:%d", title.c_str(), window.config.resolution.paneWidth, window.config.resolution.paneHeight);
if (m_vulkanObj == nullptr) {
m_vulkanObj = vulkan::initVulkan(window.window, window.surface, accessInitParams()->enableValidation());
auto& info = m_vulkanObj->deviceInfo;
m_graphics = createGraphics(*this, info.device, info.physicalDevice, info.instance);
auto queue = m_vulkanObj->queues.items[getIndex(vulkan::QueueType::present)][0].get(); // todo use getQeueu
m_imageHandler = createImageHandler(info.device, VkExtent2D {window.config.resolution.paneWidth, window.config.resolution.paneHeight}, queue);
m_imageHandler->init(m_vulkanObj, window.surface);
*item.done = true;
} else {
vulkan::createSurface(m_vulkanObj, window.window, window.surface);
}
m_condDone.notify_one();
} break;
case MessageType::close: {
SDL_DestroyWindow(window.window);
*item.done = true;
m_condDone.notify_one();
} break;
case MessageType::flip: {
LOG_DEBUG(L"-> flip(%d) set:%u buffer:%u", item.index, item.setIndex, item.imageData.index);
OPTICK_FRAME("VideoOut");
auto& flipStatus = window.config.flipStatus;
{
OPTICK_EVENT("Present");
presentImage(item.imageData, m_imageHandler->getSwapchain(), m_imageHandler->getQueue());
m_imageHandler->notify_done(item.imageData);
}
m_graphics->submitDone();
auto& timer = accessTimer();
auto const curTime = (uint64_t)(1e6 * timer.getTimeS());
auto const procTime = timer.queryPerformance();
auto elapsed_us = curTime - flipStatus.processTime;
doFlip(window, 1 + handleIndex);
double const fps = (window.config.fps * 5.0 + (1e6 / (double)elapsed_us)) / 6.0;
auto title = getTitle(1 + handleIndex, flipStatus.count, round(fps), window.fliprate);
window.config.fps = fps;
SDL_SetWindowTitle(window.window, title.c_str());
func_pollSDL(window.window);
LOG_DEBUG(L"<- flip(%d) set:%u buffer:%u", handleIndex, item.setIndex, item.imageData.index);
} break;
}
m_messages.pop();
}
SDL_Quit();
});
}
uint64_t getImageAlignment(VkFormat format, VkExtent3D const& extent) {
auto device = ((VideoOut&)accessVideoOut()).getDeviceInfo()->device;
VkImageCreateInfo const imageInfo {
.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
.pNext = nullptr,
.flags = 0,
.imageType = VK_IMAGE_TYPE_2D,
.format = format,
.extent = extent,
.mipLevels = 1,
.arrayLayers = 1,
.samples = VK_SAMPLE_COUNT_1_BIT,
.tiling = VK_IMAGE_TILING_OPTIMAL,
.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT,
.sharingMode = VK_SHARING_MODE_EXCLUSIVE,
.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
};
VkImage image;
VkMemoryRequirements reqs;
vkCreateImage(device, &imageInfo, nullptr, &image);
vkGetImageMemoryRequirements(device, image, &reqs);
vkDestroyImage(device, image, nullptr);
return reqs.alignment;
}

View File

@ -1,181 +0,0 @@
#pragma once
#include "core/kernel/eventqueue_types.h"
#include <mutex>
#include <utility/utility.h>
namespace vulkan {
struct SwapchainData;
}
constexpr int VIDEO_OUT_EVENT_FLIP = 0;
constexpr int VIDEO_OUT_EVENT_VBLANK = 1;
class IGraphics;
class IVideoOut {
CLASS_NO_COPY(IVideoOut);
CLASS_NO_MOVE(IVideoOut);
protected:
IVideoOut() = default;
public:
virtual ~IVideoOut() = default;
/**
* @brief init interals
*
*/
virtual void init() = 0;
/**
* @brief Open a window
*
* @param userId
* @return int internal handle of the window
*/
virtual int open(int userId) = 0;
/**
* @brief Closes the window
*
* @param handle
*/
virtual void close(int handle) = 0;
/**
* @brief Set the fps to use
*
* @param handle
* @param rate
*/
virtual void setFliprate(int handle, int rate) = 0;
/**
* @brief Add a VIDEO_OUT_EVENT for the window
*
* @param handle
* @param event
* @param eq
* @return int
*/
virtual int addEvent(int handle, Kernel::EventQueue::KernelEqueueEvent const& event, Kernel::EventQueue::IKernelEqueue_t eq) = 0;
/**
* @brief Removes a VIDEO_OUT_EVENT for the window
*
* @param handle
* @param eq
* @param ident
*/
virtual void removeEvent(int handle, Kernel::EventQueue::IKernelEqueue_t eq, int const ident) = 0;
/**
* @brief Submit flip video buffers to the queue
*
* @param index
* @param flipArg used by the flip event
*/
virtual void submitFlip(int handle, int index, int64_t flipArg) = 0;
/**
* @brief Get the Flip Status
*
* @param handle
* @param status
*/
virtual void getFlipStatus(int handle, void* status) = 0;
/**
* @brief Get the VBlank Status (currently faked by a timer)
*
* @param handle
* @param status
*/
virtual void getVBlankStatus(int handle, void* status) = 0;
/**
* @brief Get the current resolution
*
* @param handle
* @param status
*/
virtual void getResolution(int handle, void* status) = 0;
/**
* @brief Get the number of unhandled flip submits
*
* @param handle
* @return int
*/
virtual int getPendingFlips(int handle) = 0;
/**
* @brief Get the video Buffer Attributes
*
* @param attribute
* @param pixel_format
* @param tiling_mode
* @param aspect_ratio
* @param width
* @param height
* @param pitch_in_pixel
*/
virtual void getBufferAttribute(void* attribute, uint32_t pixel_format, int32_t tiling_mode, int32_t aspect_ratio, uint32_t width, uint32_t height,
uint32_t pitch_in_pixel) = 0;
/**
* @brief Registers a video buffer
*
* @param handle
* @param startIndex
* @param addresses
* @param numBuffer
* @param attribute
* @return int
*/
virtual int registerBuffers(int handle, int startIndex, void* const* addresses, int numBuffer, const void* attribute) = 0;
/**
* @brief Access the graphics interface
*
* @return IGraphics*
*/
virtual IGraphics* getGraphics() = 0;
/**
* @brief locks the sdlpoll
*
* @return std::unique_lock<std::mutex>
*/
virtual std::unique_lock<std::mutex> getSDLLock() const = 0;
/**
* @brief Notify a gpu visible memory range
*
* @return true: Memory has been allocated successfully
*/
virtual bool notify_allocHeap(uint64_t vaddr, uint64_t size, int memoryProtection) = 0;
/**
* @brief Checks if the vaddr is gpu memory (prev notify_allocHeap)
*
* @param vaddr
* @return true is gpu local memory
* @return false
*/
virtual bool isGPULocal(uint64_t vaddr) = 0;
};
#if defined(__APICALL_EXTERN)
#define __APICALL __declspec(dllexport)
#elif defined(__APICALL_IMPORT)
#define __APICALL __declspec(dllimport)
#else
#define __APICALL
#endif
__APICALL IVideoOut& accessVideoOut();
#undef __APICALL

View File

@ -1,220 +0,0 @@
#include "vulkanHelper.h"
#include "../imageHandler.h"
#include "core/imports/exports/graphics.h"
#include "logging.h"
#include "utility/utility.h"
#include <SDL.h>
#include <SDL_vulkan.h>
#include <algorithm>
#include <array>
#include <assert.h>
#include <boost/assert.hpp>
#include <chrono>
#include <format>
#include <mutex>
#include <optick.h>
#include <string>
#include <thread>
#include <vulkan/vk_enum_string_helper.h>
LOG_DEFINE_MODULE(vulkanHelper);
namespace vulkan {
void createSurface(VulkanObj* obj, SDL_Window* window, VkSurfaceKHR& surfaceOut) {
LOG_USE_MODULE(vulkanHelper);
if (SDL_Vulkan_CreateSurface(window, obj->deviceInfo.instance, &surfaceOut)) {
LOG_CRIT(L"Couldn't create surface");
}
}
std::pair<VkFormat, VkColorSpaceKHR> getDisplayFormat(VulkanObj* obj) {
if (obj->surfaceCapabilities.formats.empty()) {
return {VK_FORMAT_B8G8R8A8_UNORM, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR};
}
VkFormat format = obj->surfaceCapabilities.formats[0].format;
VkColorSpaceKHR imageColorSpace = obj->surfaceCapabilities.formats[0].colorSpace;
if (obj->surfaceCapabilities.format_unorm_bgra32) {
format = VK_FORMAT_B8G8R8A8_UNORM;
imageColorSpace = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR;
} else if (obj->surfaceCapabilities.format_srgb_bgra32) {
format = VK_FORMAT_B8G8R8A8_SRGB;
imageColorSpace = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR;
}
return {format, imageColorSpace};
}
void submitDisplayTransfer(SwapchainData::DisplayBuffers const* displayBuffer, ImageData const& imageData, QueueInfo const* queue, VkSemaphore waitSema,
size_t waitValue) {
LOG_USE_MODULE(vulkanHelper);
size_t waitValues[] = {0, waitValue};
uint32_t waitCount = waitSema != nullptr ? 2 : 1;
VkTimelineSemaphoreSubmitInfo const timelineInfo {
.sType = VK_STRUCTURE_TYPE_TIMELINE_SEMAPHORE_SUBMIT_INFO,
.waitSemaphoreValueCount = waitCount,
.pWaitSemaphoreValues = waitValues,
};
VkPipelineStageFlags waitStage[] = {VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT};
VkSemaphore sems[] = {imageData.semImageReady, waitSema};
VkSubmitInfo const submitInfo {
.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
.pNext = waitSema != nullptr ? &timelineInfo : nullptr,
.waitSemaphoreCount = waitCount,
.pWaitSemaphores = sems,
.pWaitDstStageMask = waitStage,
.commandBufferCount = 1,
.pCommandBuffers = &imageData.cmdBuffer,
.signalSemaphoreCount = 1,
.pSignalSemaphores = &imageData.semImageCopied,
};
{
std::unique_lock lock(queue->mutex);
if (VkResult result = vkQueueSubmit(queue->queue, 1, &submitInfo, imageData.submitFence); result != VK_SUCCESS) {
LOG_CRIT(L"Couldn't vkQueueSubmit Transfer %S", string_VkResult(result));
}
}
}
void transfer2Display(SwapchainData::DisplayBuffers const* displayBuffer, ImageData const& imageData, IGraphics* graphics) {
LOG_USE_MODULE(vulkanHelper);
{
VkImageMemoryBarrier const barrier {
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
.pNext = nullptr,
.srcAccessMask = 0,
.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED,
.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.image = imageData.swapchainImage,
.subresourceRange = {.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, .baseMipLevel = 0, .levelCount = 1, .baseArrayLayer = 0, .layerCount = 1}};
vkCmdPipelineBarrier(imageData.cmdBuffer, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, 1, &barrier);
}
graphics->copyDisplayBuffer(displayBuffer->bufferVaddr, imageData.cmdBuffer, imageData.swapchainImage, imageData.extent); // let gpumemorymanager decide
{
// Change to Present Layout
VkImageMemoryBarrier const barrier {
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
.pNext = nullptr,
.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
.dstAccessMask = 0,
.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
.newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.image = imageData.swapchainImage,
.subresourceRange = {.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, .baseMipLevel = 0, .levelCount = 1, .baseArrayLayer = 0, .layerCount = 1}};
vkCmdPipelineBarrier(imageData.cmdBuffer, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, 1, &barrier);
}
// - Present layout
// End CmdBuffer -> Submit
if (vkEndCommandBuffer(imageData.cmdBuffer) != VK_SUCCESS) {
LOG_CRIT(L"Couldn't end commandbuffer");
}
// -
}
void presentImage(ImageData const& imageData, VkSwapchainKHR swapchain, QueueInfo const* queue) {
LOG_USE_MODULE(vulkanHelper);
VkPresentInfoKHR const presentInfo {
.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR,
.pNext = nullptr,
.waitSemaphoreCount = 1,
.pWaitSemaphores = &imageData.semImageCopied,
.swapchainCount = 1,
.pSwapchains = &swapchain,
.pImageIndices = &imageData.index,
.pResults = nullptr,
};
{
OPTICK_GPU_FLIP(&swapchain);
OPTICK_CATEGORY("Present", Optick::Category::Wait);
std::unique_lock lock(queue->mutex);
vkQueuePresentKHR(queue->queue, &presentInfo);
}
}
} // namespace vulkan
// ### Vulkan EXT Function Definitions
VKAPI_ATTR VkResult VKAPI_CALL vkCreateShadersEXT(VkDevice device, uint32_t createInfoCount, const VkShaderCreateInfoEXT* pCreateInfos,
const VkAllocationCallbacks* pAllocator, VkShaderEXT* pShaders) {
static auto fn = (PFN_vkCreateShadersEXT)vkGetInstanceProcAddr(vulkan::getVkInstance(), "vkCreateShadersEXT");
return fn(device, createInfoCount, pCreateInfos, pAllocator, pShaders);
}
VKAPI_ATTR void VKAPI_CALL vkCmdSetDepthClipNegativeOneToOneEXT(VkCommandBuffer commandBuffer, VkBool32 negativeOneToOne) {
static auto fn = (PFN_vkCmdSetDepthClipNegativeOneToOneEXT)vkGetInstanceProcAddr(vulkan::getVkInstance(), "vkCmdSetDepthClipNegativeOneToOneEXT");
fn(commandBuffer, negativeOneToOne);
}
VKAPI_ATTR void VKAPI_CALL vkCmdSetColorWriteEnableEXT(VkCommandBuffer commandBuffer, uint32_t attachmentCount, const VkBool32* pColorWriteEnables) {
static auto fn = (PFN_vkCmdSetColorWriteEnableEXT)vkGetInstanceProcAddr(vulkan::getVkInstance(), "vkCmdSetColorWriteEnableEXT");
fn(commandBuffer, attachmentCount, pColorWriteEnables);
}
VKAPI_ATTR void VKAPI_CALL vkCmdSetColorWriteMaskEXT(VkCommandBuffer commandBuffer, uint32_t firstAttachment, uint32_t attachmentCount,
const VkColorComponentFlags* pColorWriteMasks) {
static auto fn = (PFN_vkCmdSetColorWriteMaskEXT)vkGetInstanceProcAddr(vulkan::getVkInstance(), "vkCmdSetColorWriteMaskEXT");
fn(commandBuffer, firstAttachment, attachmentCount, pColorWriteMasks);
}
VKAPI_ATTR void VKAPI_CALL vkCmdSetColorBlendEnableEXT(VkCommandBuffer commandBuffer, uint32_t firstAttachment, uint32_t attachmentCount,
const VkBool32* pColorBlendEnables) {
static auto fn = (PFN_vkCmdSetColorBlendEnableEXT)vkGetInstanceProcAddr(vulkan::getVkInstance(), "vkCmdSetColorBlendEnableEXT");
fn(commandBuffer, firstAttachment, attachmentCount, pColorBlendEnables);
}
VKAPI_ATTR void VKAPI_CALL vkCmdSetColorBlendEquationEXT(VkCommandBuffer commandBuffer, uint32_t firstAttachment, uint32_t attachmentCount,
const VkColorBlendEquationEXT* pColorBlendEquations) {
static auto fn = (PFN_vkCmdSetColorBlendEquationEXT)vkGetInstanceProcAddr(vulkan::getVkInstance(), "vkCmdSetColorBlendEquationEXT");
fn(commandBuffer, firstAttachment, attachmentCount, pColorBlendEquations);
}
VKAPI_ATTR void VKAPI_CALL vkCmdSetPolygonModeEXT(VkCommandBuffer commandBuffer, VkPolygonMode polygonMode) {
static auto fn = (PFN_vkCmdSetPolygonModeEXT)vkGetInstanceProcAddr(vulkan::getVkInstance(), "vkCmdSetPolygonModeEXT");
fn(commandBuffer, polygonMode);
}
VKAPI_ATTR void VKAPI_CALL vkCmdSetRasterizationSamplesEXT(VkCommandBuffer commandBuffer, VkSampleCountFlagBits rasterizationSamples) {
static auto fn = (PFN_vkCmdSetRasterizationSamplesEXT)vkGetInstanceProcAddr(vulkan::getVkInstance(), "vkCmdSetRasterizationSamplesEXT");
fn(commandBuffer, rasterizationSamples);
}
VKAPI_ATTR void VKAPI_CALL vkCmdSetAlphaToCoverageEnableEXT(VkCommandBuffer commandBuffer, VkBool32 alphaToCoverageEnable) {
static auto fn = (PFN_vkCmdSetAlphaToCoverageEnableEXT)vkGetInstanceProcAddr(vulkan::getVkInstance(), "vkCmdSetAlphaToCoverageEnableEXT");
fn(commandBuffer, alphaToCoverageEnable);
}
VKAPI_ATTR void VKAPI_CALL vkCmdSetAlphaToOneEnableEXT(VkCommandBuffer commandBuffer, VkBool32 alphaToOneEnable) {
static auto fn = (PFN_vkCmdSetAlphaToOneEnableEXT)vkGetInstanceProcAddr(vulkan::getVkInstance(), "vkCmdSetAlphaToOneEnableEXT");
fn(commandBuffer, alphaToOneEnable);
}
VKAPI_ATTR VkResult VKAPI_CALL vkGetMemoryHostPointerPropertiesEXT(VkDevice device, VkExternalMemoryHandleTypeFlagBits handleType, const void* pHostPointer,
VkMemoryHostPointerPropertiesEXT* pMemoryHostPointerProperties) {
static auto fn = (PFN_vkGetMemoryHostPointerPropertiesEXT)vkGetInstanceProcAddr(vulkan::getVkInstance(), "vkGetMemoryHostPointerPropertiesEXT");
return fn(device, handleType, pHostPointer, pMemoryHostPointerProperties);
}

View File

@ -1,27 +0,0 @@
#pragma once
#include "vulkanSetup.h"
#include <functional>
class IGraphics;
struct ImageData;
namespace vulkan {
struct PresentData {
VkImage swapchainImage = nullptr;
VkSemaphore displayReady = nullptr;
VkSemaphore presentReady = nullptr;
uint32_t index = 0;
};
std::pair<VkFormat, VkColorSpaceKHR> getDisplayFormat(VulkanObj* obj);
void submitDisplayTransfer(SwapchainData::DisplayBuffers const* displayBuffer, ImageData const& imageData, QueueInfo const* queue, VkSemaphore waitSema,
size_t waitValue);
void transfer2Display(SwapchainData::DisplayBuffers const* displayBuffer, ImageData const& imageData, IGraphics* graphics);
void presentImage(ImageData const& imageData, VkSwapchainKHR swapchain, QueueInfo const* queue);
} // namespace vulkan

View File

@ -1,777 +0,0 @@
#include "vulkanSetup.h"
#include "logging.h"
#include <SDL.h>
#include <SDL_vulkan.h>
#include <format>
#include <utility/utility.h>
#include <vulkan/vk_enum_string_helper.h>
LOG_DEFINE_MODULE(vulkanSetup);
constexpr std::array<VkValidationFeatureDisableEXT, 0> disabledValidationFeatures {};
constexpr std::array enabledValidationFeatures {VK_VALIDATION_FEATURE_ENABLE_DEBUG_PRINTF_EXT};
constexpr std::array requiredExtensions {
VK_KHR_SWAPCHAIN_EXTENSION_NAME, VK_EXT_COLOR_WRITE_ENABLE_EXTENSION_NAME, VK_KHR_MAINTENANCE_2_EXTENSION_NAME, VK_KHR_TIMELINE_SEMAPHORE_EXTENSION_NAME,
VK_KHR_SHADER_NON_SEMANTIC_INFO_EXTENSION_NAME, VK_KHR_DYNAMIC_RENDERING_EXTENSION_NAME, VK_KHR_BUFFER_DEVICE_ADDRESS_EXTENSION_NAME,
VK_EXT_DESCRIPTOR_INDEXING_EXTENSION_NAME, VK_EXT_DEPTH_CLIP_CONTROL_EXTENSION_NAME, VK_KHR_SAMPLER_MIRROR_CLAMP_TO_EDGE_EXTENSION_NAME,
VK_EXT_DEPTH_RANGE_UNRESTRICTED_EXTENSION_NAME, VK_EXT_SEPARATE_STENCIL_USAGE_EXTENSION_NAME, VK_EXT_EXTENDED_DYNAMIC_STATE_3_EXTENSION_NAME,
// VK_EXT_SHADER_OBJECT_EXTENSION_NAME,
VK_KHR_EXTERNAL_MEMORY_EXTENSION_NAME, "VK_KHR_external_memory_win32"};
namespace vulkan {
struct VulkanExtensions {
bool enableValidationLayers = true;
std::vector<const char*> requiredExtensions;
std::vector<VkExtensionProperties> availableExtensions;
std::vector<const char*> requiredLayers;
std::vector<VkLayerProperties> availableLayers;
};
struct VulkanQueues {
struct QueueInfo {
uint32_t family = 0;
uint32_t index = 0;
uint32_t count = 0;
bool graphics = false;
bool compute = false;
bool transfer = false;
bool present = false;
};
uint32_t familyCount = 0;
std::vector<uint32_t> familyUsed;
std::vector<QueueInfo> graphics;
std::vector<QueueInfo> compute;
std::vector<QueueInfo> transfer;
std::vector<QueueInfo> present;
};
VKAPI_ATTR VkResult VKAPI_CALL createDebugUtilsMessengerEXT(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* createInfo,
const VkAllocationCallbacks* allocator, VkDebugUtilsMessengerEXT* messenger) {
if (auto func = reinterpret_cast<PFN_vkCreateDebugUtilsMessengerEXT>(vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT")); func != nullptr) {
return func(instance, createInfo, allocator, messenger);
}
return VK_ERROR_EXTENSION_NOT_PRESENT;
}
VKAPI_ATTR VkBool32 VKAPI_CALL debugMessengerCallback(VkDebugUtilsMessageSeverityFlagBitsEXT message_severity, VkDebugUtilsMessageTypeFlagsEXT messageTypes,
const VkDebugUtilsMessengerCallbackDataEXT* callback_data, void* /*user_data*/) {
LOG_USE_MODULE(vulkanSetup);
const char* severityStr = nullptr;
bool skip = false;
bool error = false;
bool debugPrintf = false;
switch (message_severity) {
case VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT:
severityStr = "V";
skip = true;
break;
case VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT:
if ((messageTypes & VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT) != 0 && strcmp(callback_data->pMessageIdName, "UNASSIGNED-DEBUG-PRINTF") == 0) {
debugPrintf = true;
skip = true;
} else {
severityStr = "I";
}
break;
case VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT: severityStr = "W"; break;
case VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT:
severityStr = "E";
error = true;
break;
default: severityStr = "?";
}
if (error) {
LOG_WARN(L"[Vulkan][%S][%u]: %S", severityStr, static_cast<uint32_t>(messageTypes), callback_data->pMessage);
} else if (!skip) {
LOG_DEBUG(L"[Vulkan][%S][%u]: %S", severityStr, static_cast<uint32_t>(messageTypes), callback_data->pMessage);
}
if (debugPrintf) {
auto strs = util::splitString(std::string(callback_data->pMessage), U'|');
if (!strs.empty()) {
LOG_DEBUG(L"%S", std::string(strs.back()).data());
}
}
return VK_FALSE;
}
VulkanExtensions getExtensions(SDL_Window* window, bool enableValidation) {
LOG_USE_MODULE(vulkanSetup);
uint32_t countAvailableExtensions = 0;
uint32_t countAvailableLayers = 0;
uint32_t countRequiredExtensions = 0;
VulkanExtensions r = {.enableValidationLayers = enableValidation};
SDL_Vulkan_GetInstanceExtensions(window, &countRequiredExtensions, NULL);
auto extensions = static_cast<const char**>(SDL_malloc(sizeof(char*) * countRequiredExtensions));
SDL_Vulkan_GetInstanceExtensions(window, &countRequiredExtensions, extensions);
for (size_t n = 0; n < countRequiredExtensions; n++) {
r.requiredExtensions.push_back(extensions[n]);
}
SDL_free(extensions);
vkEnumerateInstanceExtensionProperties(nullptr, &countAvailableExtensions, nullptr);
r.availableExtensions = std::vector<VkExtensionProperties>(countAvailableExtensions, VkExtensionProperties {});
vkEnumerateInstanceExtensionProperties(nullptr, &countAvailableExtensions, r.availableExtensions.data());
if (std::find_if(r.availableExtensions.begin(), r.availableExtensions.end(),
[](VkExtensionProperties s) { return strcmp(s.extensionName, VK_EXT_DEBUG_UTILS_EXTENSION_NAME) == 0; }) != r.availableExtensions.end()) {
r.requiredExtensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
} else {
r.enableValidationLayers = false;
}
for (size_t n = 0; n < r.requiredExtensions.size(); ++n) {
LOG_INFO(L"SDL2 required extension: %S", r.requiredExtensions[n]);
}
for (const auto& ext: r.availableExtensions) {
LOG_DEBUG(L"Vulkan available extension: %S specVer:%u", ext.extensionName, ext.specVersion);
}
vkEnumerateInstanceLayerProperties(&countAvailableLayers, nullptr);
r.availableLayers = std::vector<VkLayerProperties>(countAvailableLayers, VkLayerProperties {});
vkEnumerateInstanceLayerProperties(&countAvailableLayers, r.availableLayers.data());
for (const auto& l: r.availableLayers) {
LOG_DEBUG(L"Vulkan available layer:%S specVer:%u.%u implVer:%u (%S)", l.layerName, VK_API_VERSION_MAJOR(l.specVersion), VK_API_VERSION_MINOR(l.specVersion),
l.implementationVersion, l.description);
}
if (r.enableValidationLayers) {
r.requiredLayers.push_back("VK_LAYER_KHRONOS_validation");
for (auto const l: r.requiredLayers) {
if (std::find_if(r.availableLayers.begin(), r.availableLayers.end(), [&l](auto s) { return strcmp(s.layerName, l) == 0; }) == r.availableLayers.end()) {
LOG_INFO(L"no validation layer:%S", l);
r.enableValidationLayers = false;
break;
}
}
}
if (r.enableValidationLayers) {
vkEnumerateInstanceExtensionProperties("VK_LAYER_KHRONOS_validation", &countAvailableExtensions, nullptr);
std::vector<VkExtensionProperties> availableExtensions(countAvailableExtensions, VkExtensionProperties {});
vkEnumerateInstanceExtensionProperties("VK_LAYER_KHRONOS_validation", &countAvailableExtensions, availableExtensions.data());
for (const auto& ext: availableExtensions) {
LOG_DEBUG(L"VK_LAYER_KHRONOS_validation available extension: %S version:%u", ext.extensionName, ext.specVersion);
}
if (std::find_if(availableExtensions.begin(), availableExtensions.end(),
[](auto s) { return strcmp(s.extensionName, "VK_EXT_validation_features") == 0; }) != availableExtensions.end()) {
r.requiredExtensions.push_back("VK_EXT_validation_features");
} else {
r.enableValidationLayers = false;
}
}
return r;
}
VulkanQueues findQueues(VkPhysicalDevice device, VkSurfaceKHR surface) {
LOG_USE_MODULE(vulkanSetup);
VulkanQueues qs;
vkGetPhysicalDeviceQueueFamilyProperties(device, &qs.familyCount, nullptr);
std::vector<VkQueueFamilyProperties> queueFamilies(qs.familyCount, VkQueueFamilyProperties {});
vkGetPhysicalDeviceQueueFamilyProperties(device, &qs.familyCount, queueFamilies.data());
std::vector<VulkanQueues::QueueInfo> queueInfos;
qs.familyUsed.resize(queueFamilies.size());
for (uint32_t n = 0; n < queueFamilies.size(); ++n) {
VkBool32 presentationSupported = VK_FALSE;
vkGetPhysicalDeviceSurfaceSupportKHR(device, n, surface, &presentationSupported);
auto const& queue = queueFamilies[n];
LOG_DEBUG(L"queue family[%u]: %S [count:%u] [present=%S]", n, string_VkQueueFlags(queue.queueFlags).c_str(), queue.queueCount,
util::getBoolStr(presentationSupported == VK_TRUE));
VulkanQueues::QueueInfo info {
.family = n,
.count = queue.queueCount,
.graphics = (queue.queueFlags & VK_QUEUE_GRAPHICS_BIT) != 0,
.compute = (queue.queueFlags & VK_QUEUE_COMPUTE_BIT) != 0,
.transfer = (queue.queueFlags & VK_QUEUE_TRANSFER_BIT) != 0,
.present = (bool)presentationSupported,
};
queueInfos.push_back(info);
}
// Prio
auto gather = [&queueInfos, &qs](uint32_t count, std::vector<VulkanQueues::QueueInfo>& queue, auto const cmp) {
for (uint32_t i = queue.size(); i < count; i++) {
if (auto it = std::find_if(queueInfos.begin(), queueInfos.end(), cmp); it != queueInfos.end()) {
it->index = 0;
auto& item = queue.emplace_back(*it);
if (it->count > qs.familyUsed[it->family]) {
item.index = qs.familyUsed[it->family];
qs.familyUsed[it->family]++;
}
} else {
break;
}
}
};
gather(1, qs.graphics, [](VulkanQueues::QueueInfo& q) { return q.graphics && q.transfer && q.present; });
gather(1, qs.present, [](VulkanQueues::QueueInfo& q) { return q.graphics && q.transfer && q.present; });
gather(1, qs.transfer, [](VulkanQueues::QueueInfo& q) { return q.transfer && !q.graphics && !q.compute; });
gather(1, qs.compute, [](VulkanQueues::QueueInfo& q) { return q.compute && !q.graphics; });
//-
// Fill in whatever is left
gather(1, qs.graphics, [](VulkanQueues::QueueInfo& q) { return q.transfer == true; });
gather(1, qs.present, [](VulkanQueues::QueueInfo& q) { return q.present == true; });
gather(1, qs.transfer, [](VulkanQueues::QueueInfo& q) { return q.graphics == true; });
gather(1, qs.compute, [](VulkanQueues::QueueInfo& q) { return q.compute == true; });
//
//-
return qs;
}
void dumpQueues(const VulkanQueues& qs) {
LOG_USE_MODULE(vulkanSetup);
std::string familyString;
for (auto u: qs.familyUsed) {
familyString += std::to_string(u) + std::string(", ");
}
LOG_INFO(L"\t familyUsed = [%S]", familyString.c_str());
LOG_INFO(L"\t graphics:");
for (const auto& q: qs.graphics) {
LOG_INFO(L"\t\t family:%u index:%u", q.family, q.index);
}
LOG_INFO(L"\t compute:");
for (const auto& q: qs.compute) {
LOG_INFO(L"\t\t family:%u index:%u", q.family, q.index);
}
LOG_INFO(L"\t transfer:");
for (const auto& q: qs.transfer) {
LOG_INFO(L"\t\t family:%u, index:%u", q.family, q.index);
}
LOG_INFO(L"\t present:");
for (const auto& q: qs.present) {
LOG_INFO(L"\t\t family:%u, index:%u", q.family, q.index);
}
}
void getSurfaceCapabilities(VkPhysicalDevice physicalDevice, VkSurfaceKHR surface, SurfaceCapabilities& r) {
LOG_USE_MODULE(vulkanSetup);
vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physicalDevice, surface, &r.capabilities);
uint32_t formats_count = 0;
vkGetPhysicalDeviceSurfaceFormatsKHR(physicalDevice, surface, &formats_count, nullptr);
r.formats = std::vector<VkSurfaceFormatKHR>(formats_count, VkSurfaceFormatKHR {});
vkGetPhysicalDeviceSurfaceFormatsKHR(physicalDevice, surface, &formats_count, r.formats.data());
uint32_t present_modes_count = 0;
vkGetPhysicalDeviceSurfacePresentModesKHR(physicalDevice, surface, &present_modes_count, nullptr);
r.presentModes = std::vector<VkPresentModeKHR>(present_modes_count, VkPresentModeKHR {});
vkGetPhysicalDeviceSurfacePresentModesKHR(physicalDevice, surface, &present_modes_count, r.presentModes.data());
r.format_srgb_bgra32 = false;
for (const auto& f: r.formats) {
if (f.format == VK_FORMAT_B8G8R8A8_SRGB && f.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) {
r.format_srgb_bgra32 = true;
break;
}
if (f.format == VK_FORMAT_B8G8R8A8_UNORM && f.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) {
r.format_unorm_bgra32 = true;
break;
}
}
}
bool checkFormat(VkPhysicalDevice device, VkFormat format, VkFormatFeatureFlags features) {
VkFormatProperties formatProps;
vkGetPhysicalDeviceFormatProperties(device, format, &formatProps);
if ((formatProps.optimalTilingFeatures & features) == features || (formatProps.linearTilingFeatures & features) == features) {
return true;
}
return false;
}
void findPhysicalDevice(VkInstance instance, VkSurfaceKHR surface, SurfaceCapabilities* outCapabilities, VkPhysicalDevice* outDevice, VulkanQueues* outQueues,
bool enableValidation) {
LOG_USE_MODULE(vulkanSetup);
uint32_t devicesCount = 0;
vkEnumeratePhysicalDevices(instance, &devicesCount, nullptr);
if (devicesCount == 0) {
LOG_CRIT(L"No GPUs found");
}
std::vector<VkPhysicalDevice> devices(devicesCount);
vkEnumeratePhysicalDevices(instance, &devicesCount, devices.data());
// Find Best
VkPhysicalDevice bestDevice = nullptr;
VulkanQueues bestQueues;
bool skipDevice = false;
for (const auto& device: devices) {
skipDevice = false;
VkPhysicalDeviceInlineUniformBlockFeaturesEXT uniBlock {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_INLINE_UNIFORM_BLOCK_FEATURES,
.pNext = nullptr,
};
VkPhysicalDeviceExtendedDynamicState3FeaturesEXT extendedDynamic3Features {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTENDED_DYNAMIC_STATE_3_FEATURES_EXT,
.pNext = &uniBlock,
};
VkPhysicalDeviceDepthClipControlFeaturesEXT depthClipControlFeatures {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DEPTH_CLIP_CONTROL_FEATURES_EXT,
.pNext = &extendedDynamic3Features,
.depthClipControl = VK_TRUE,
};
VkPhysicalDeviceDynamicRenderingFeatures dynamicRenderingFeature {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DYNAMIC_RENDERING_FEATURES,
.pNext = &depthClipControlFeatures,
.dynamicRendering = VK_TRUE,
};
VkPhysicalDeviceTimelineSemaphoreFeatures timelineSemaphoreFeature {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TIMELINE_SEMAPHORE_FEATURES,
.pNext = &dynamicRenderingFeature,
.timelineSemaphore = VK_TRUE,
};
VkPhysicalDeviceBufferDeviceAddressFeatures bufferDeviceAddress {.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_BUFFER_DEVICE_ADDRESS_FEATURES,
.pNext = &timelineSemaphoreFeature};
VkPhysicalDeviceDescriptorIndexingFeatures descIndexing {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_INDEXING_FEATURES,
.pNext = &bufferDeviceAddress,
};
VkPhysicalDeviceShaderObjectFeaturesEXT shaderObj {.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_OBJECT_FEATURES_EXT, .pNext = &descIndexing};
VkPhysicalDeviceDescriptorIndexingFeatures indexingFeature {.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_INDEXING_FEATURES_EXT,
.pNext = &shaderObj};
VkPhysicalDeviceColorWriteEnableFeaturesEXT colorWriteExt {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_COLOR_WRITE_ENABLE_FEATURES_EXT,
.pNext = &indexingFeature,
};
VkPhysicalDeviceFeatures2 deviceFeatures {.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2, .pNext = &colorWriteExt};
VkPhysicalDeviceProperties deviceProperties;
vkGetPhysicalDeviceProperties(device, &deviceProperties);
vkGetPhysicalDeviceFeatures2(device, &deviceFeatures);
LOG_INFO(L"Vulkan device: %S api:%u.%u:%u", deviceProperties.deviceName, VK_API_VERSION_MAJOR(deviceProperties.apiVersion),
VK_API_VERSION_MINOR(deviceProperties.apiVersion), VK_API_VERSION_PATCH(deviceProperties.apiVersion));
auto qs = findQueues(device, surface);
dumpQueues(qs);
if (shaderObj.shaderObject == VK_FALSE) {
LOG_ERR(L"shaderObject is not supported");
skipDevice = true;
}
if (qs.graphics.empty() || qs.compute.empty() || qs.transfer.empty() || qs.present.empty()) {
LOG_ERR(L"Not enough queues");
skipDevice = true;
}
if (colorWriteExt.colorWriteEnable != VK_TRUE) {
LOG_ERR(L"colorWriteEnable is not supported");
skipDevice = true;
}
if (deviceFeatures.features.fragmentStoresAndAtomics != VK_TRUE) {
LOG_ERR(L"fragmentStoresAndAtomics is not supported");
skipDevice = true;
}
if (deviceFeatures.features.samplerAnisotropy != VK_TRUE) {
LOG_ERR(L"samplerAnisotropy is not supported");
skipDevice = true;
}
if (!skipDevice) {
uint32_t numExt = 0;
vkEnumerateDeviceExtensionProperties(device, nullptr, &numExt, nullptr);
if (numExt == 0) {
LOG_CRIT(L"no extensions found");
}
std::vector<VkExtensionProperties> availableExt(numExt, VkExtensionProperties {});
vkEnumerateDeviceExtensionProperties(device, nullptr, &numExt, availableExt.data());
for (const char* ext: requiredExtensions) {
if (std::find_if(availableExt.begin(), availableExt.end(), [&ext](auto p) { return strcmp(p.extensionName, ext) == 0; }) == availableExt.end()) {
LOG_ERR(L"%S not supported", ext);
skipDevice = true;
break;
}
}
if (enableValidation) {
for (const auto& ext: availableExt) {
LOG_DEBUG(L"Vulkan available extension: %S, version:%u", ext.extensionName, ext.specVersion);
}
}
}
if (!skipDevice) {
getSurfaceCapabilities(device, surface, *outCapabilities);
if ((outCapabilities->capabilities.supportedUsageFlags & VK_IMAGE_USAGE_TRANSFER_DST_BIT) == 0) {
LOG_DEBUG(L"Surface cannot be destination of blit");
skipDevice = true;
}
}
if (!skipDevice && !checkFormat(device, VK_FORMAT_R8G8B8A8_SRGB, VK_FORMAT_FEATURE_BLIT_SRC_BIT)) {
LOG_DEBUG(L"Format VK_FORMAT_R8G8B8A8_SRGB cannot be used as transfer source");
skipDevice = true;
}
if (!skipDevice && !checkFormat(device, VK_FORMAT_D32_SFLOAT, VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT)) {
LOG_DEBUG(L"Format VK_FORMAT_D32_SFLOAT cannot be used as depth buffer");
skipDevice = true;
}
if (!skipDevice && !checkFormat(device, VK_FORMAT_D32_SFLOAT_S8_UINT, VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT)) {
LOG_DEBUG(L"Format VK_FORMAT_D32_SFLOAT_S8_UINT cannot be used as depth buffer");
skipDevice = true;
}
if (!skipDevice && !checkFormat(device, VK_FORMAT_D16_UNORM, VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT)) {
LOG_DEBUG(L"Format VK_FORMAT_D16_UNORM cannot be used as depth buffer");
skipDevice = true;
}
if (!skipDevice && !checkFormat(device, VK_FORMAT_D24_UNORM_S8_UINT, VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT)) {
LOG_DEBUG(L"Format VK_FORMAT_D24_UNORM_S8_UINT cannot be used as depth buffer");
// skipDevice = true;
}
if (!skipDevice && !checkFormat(device, VK_FORMAT_BC3_SRGB_BLOCK, VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)) {
LOG_DEBUG(L"Format VK_FORMAT_BC3_SRGB_BLOCK cannot be used as texture");
skipDevice = true;
}
if (!skipDevice && !checkFormat(device, VK_FORMAT_R8G8B8A8_SRGB, VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)) {
LOG_DEBUG(L"Format VK_FORMAT_R8G8B8A8_SRGB cannot be used as texture");
skipDevice = true;
}
if (!skipDevice && !checkFormat(device, VK_FORMAT_R8_UNORM, VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)) {
LOG_DEBUG(L"Format VK_FORMAT_R8_UNORM cannot be used as texture");
skipDevice = true;
}
if (!skipDevice && !checkFormat(device, VK_FORMAT_R8G8_UNORM, VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)) {
LOG_DEBUG(L"Format VK_FORMAT_R8G8_UNORM cannot be used as texture");
skipDevice = true;
}
if (!skipDevice && !checkFormat(device, VK_FORMAT_R8G8B8A8_SRGB, VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)) {
LOG_DEBUG(L"Format VK_FORMAT_R8G8B8A8_SRGB cannot be used as texture");
if (!skipDevice && !checkFormat(device, VK_FORMAT_R8G8B8A8_UNORM, VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)) {
LOG_DEBUG(L"Format VK_FORMAT_R8G8B8A8_UNORM cannot be used as texture");
skipDevice = true;
}
}
if (!skipDevice && !checkFormat(device, VK_FORMAT_B8G8R8A8_SRGB, VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)) {
LOG_DEBUG(L"Format VK_FORMAT_B8G8R8A8_SRGB cannot be used as texture");
if (!skipDevice && !checkFormat(device, VK_FORMAT_B8G8R8A8_UNORM, VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | VK_FORMAT_FEATURE_TRANSFER_DST_BIT)) {
LOG_DEBUG(L"Format VK_FORMAT_B8G8R8A8_UNORM cannot be used as texture");
skipDevice = true;
}
}
if (!skipDevice && deviceProperties.limits.maxSamplerAnisotropy < 16.0f) {
LOG_DEBUG(L"maxSamplerAnisotropy < 16.0f");
skipDevice = true;
}
if (skipDevice) {
continue;
}
if (bestDevice == nullptr || deviceProperties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU) {
bestDevice = device;
bestQueues = qs;
}
}
// -Find best
*outDevice = bestDevice;
*outQueues = bestQueues;
}
VkDevice createDevice(VkPhysicalDevice physicalDevice, VkSurfaceKHR surface, VulkanExtensions const extensions, VulkanQueues const& queues,
bool enableValidation) {
LOG_USE_MODULE(vulkanSetup);
std::vector<VkDeviceQueueCreateInfo> queueCreateInfo(queues.familyCount);
std::vector<std::vector<float>> queuePrio(queues.familyCount);
uint32_t numQueueCreateInfo = 0;
for (uint32_t i = 0; i < queues.familyCount; i++) {
if (queues.familyUsed[i] != 0) {
for (uint32_t pi = 0; pi < queues.familyUsed[i]; pi++) {
queuePrio[numQueueCreateInfo].push_back(1.0f);
}
queueCreateInfo[numQueueCreateInfo].sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
queueCreateInfo[numQueueCreateInfo].pNext = nullptr;
queueCreateInfo[numQueueCreateInfo].flags = 0;
queueCreateInfo[numQueueCreateInfo].queueFamilyIndex = i;
queueCreateInfo[numQueueCreateInfo].queueCount = queues.familyUsed[i];
queueCreateInfo[numQueueCreateInfo].pQueuePriorities = queuePrio[numQueueCreateInfo].data();
numQueueCreateInfo++;
}
}
VkPhysicalDeviceFeatures deviceFeatures {
.geometryShader = VK_TRUE, .fillModeNonSolid = VK_TRUE, .wideLines = VK_TRUE, .samplerAnisotropy = VK_TRUE, .fragmentStoresAndAtomics = VK_TRUE,
// deviceFeatures.shaderImageGatherExtended = VK_TRUE;
};
VkPhysicalDeviceInlineUniformBlockFeaturesEXT uniBlock {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_INLINE_UNIFORM_BLOCK_FEATURES, .pNext = nullptr,
//.inlineUniformBlock = VK_TRUE,
//.descriptorBindingInlineUniformBlockUpdateAfterBind = VK_TRUE,
};
VkPhysicalDeviceShaderObjectFeaturesEXT shaderObjectFeatures {.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_OBJECT_FEATURES_EXT, .shaderObject = VK_TRUE};
VkPhysicalDeviceExtendedDynamicState3FeaturesEXT extendedDynamic3Features {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTENDED_DYNAMIC_STATE_3_FEATURES_EXT,
.pNext = &uniBlock,
.extendedDynamicState3PolygonMode = VK_TRUE,
.extendedDynamicState3RasterizationSamples = VK_TRUE,
.extendedDynamicState3AlphaToCoverageEnable = VK_TRUE,
//.extendedDynamicState3AlphaToOneEnable = VK_TRUE,
.extendedDynamicState3ColorBlendEnable = VK_TRUE,
.extendedDynamicState3ColorBlendEquation = VK_TRUE,
.extendedDynamicState3ColorWriteMask = VK_TRUE,
.extendedDynamicState3DepthClipNegativeOneToOne = VK_TRUE,
};
VkPhysicalDeviceDepthClipControlFeaturesEXT depthClipControlFeatures {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DEPTH_CLIP_CONTROL_FEATURES_EXT,
.pNext = &extendedDynamic3Features,
.depthClipControl = VK_TRUE,
};
VkPhysicalDeviceDynamicRenderingFeatures dynamicRenderingFeature {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DYNAMIC_RENDERING_FEATURES,
.pNext = &depthClipControlFeatures,
.dynamicRendering = VK_TRUE,
};
VkPhysicalDeviceTimelineSemaphoreFeatures timelineSemaphoreFeature {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_TIMELINE_SEMAPHORE_FEATURES,
.pNext = &dynamicRenderingFeature,
.timelineSemaphore = VK_TRUE,
};
VkPhysicalDeviceColorWriteEnableFeaturesEXT colorWriteExt {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_COLOR_WRITE_ENABLE_FEATURES_EXT,
.pNext = &timelineSemaphoreFeature,
.colorWriteEnable = VK_TRUE,
};
VkPhysicalDeviceBufferDeviceAddressFeatures bufferDeviceAddress {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_BUFFER_DEVICE_ADDRESS_FEATURES, .pNext = &colorWriteExt, .bufferDeviceAddress = VK_TRUE,
//.bufferDeviceAddressCaptureReplay = enableValidation ? VK_TRUE : VK_FALSE,
};
VkPhysicalDeviceDescriptorIndexingFeatures const descIndexing {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_INDEXING_FEATURES,
.pNext = &bufferDeviceAddress,
.descriptorBindingUniformBufferUpdateAfterBind = VK_FALSE, // Todo: only optional!
.descriptorBindingSampledImageUpdateAfterBind = VK_TRUE,
.descriptorBindingStorageImageUpdateAfterBind = VK_TRUE,
.descriptorBindingStorageBufferUpdateAfterBind = VK_TRUE,
.descriptorBindingPartiallyBound = VK_TRUE,
.descriptorBindingVariableDescriptorCount = VK_TRUE,
.runtimeDescriptorArray = VK_TRUE,
};
VkDeviceCreateInfo const createInfo {
.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
.pNext = &descIndexing,
.flags = 0,
.queueCreateInfoCount = numQueueCreateInfo,
.pQueueCreateInfos = queueCreateInfo.data(),
.enabledLayerCount = (uint32_t)extensions.requiredLayers.size(),
.ppEnabledLayerNames = extensions.requiredLayers.data(),
.enabledExtensionCount = requiredExtensions.size(),
.ppEnabledExtensionNames = requiredExtensions.data(),
.pEnabledFeatures = &deviceFeatures,
};
VkDevice device = nullptr;
if (auto result = vkCreateDevice(physicalDevice, &createInfo, nullptr, &device); result != VK_SUCCESS) {
LOG_CRIT(L"Couldn't create vulkanDevice %S", string_VkResult(result));
}
return device;
}
static VkPhysicalDeviceProperties g_PhysicalDeviceProperties;
static VkInstance g_VkInstance;
VulkanObj* initVulkan(SDL_Window* window, VkSurfaceKHR& surface, bool enableValidation) {
LOG_USE_MODULE(vulkanSetup);
auto obj = new VulkanObj;
// Create Instance&Debug
VkDebugUtilsMessengerCreateInfoEXT dbgCreateInfo {
.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT,
.pNext = nullptr,
.flags = 0,
.messageSeverity =
static_cast<uint32_t>(VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT) | static_cast<uint32_t>(VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT) |
static_cast<uint32_t>(VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT) | static_cast<uint32_t>(VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT),
.messageType = static_cast<uint32_t>(VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT) |
static_cast<uint32_t>(VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT) |
static_cast<uint32_t>(VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT),
.pfnUserCallback = debugMessengerCallback,
.pUserData = nullptr,
};
VkApplicationInfo const appInfo {
.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO,
.pNext = nullptr,
.pApplicationName = "psOff",
.applicationVersion = 1,
.pEngineName = "psEngine",
.engineVersion = 1,
.apiVersion = VK_API_VERSION_1_3,
};
auto extensions = getExtensions(window, enableValidation);
VkInstanceCreateInfo const instInfo {
.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
.pNext = extensions.enableValidationLayers ? &dbgCreateInfo : nullptr,
.flags = 0,
.pApplicationInfo = &appInfo,
.enabledLayerCount = (uint32_t)extensions.requiredLayers.size(),
.ppEnabledLayerNames = extensions.requiredLayers.data(),
.enabledExtensionCount = (uint32_t)extensions.requiredExtensions.size(),
.ppEnabledExtensionNames = extensions.requiredExtensions.data(),
};
if (VkResult result = vkCreateInstance(&instInfo, nullptr, &obj->deviceInfo.instance); result != VK_SUCCESS) {
if (result == VK_ERROR_INCOMPATIBLE_DRIVER)
LOG_CRIT(L"vkCreateInstance() Error:VK_ERROR_INCOMPATIBLE_DRIVER");
else
LOG_CRIT(L"vkCreateInstance() Error:%S", string_VkResult(result));
}
g_VkInstance = obj->deviceInfo.instance;
if (extensions.enableValidationLayers) {
// dbgCreateInfo.pNext = nullptr;
if (auto result = createDebugUtilsMessengerEXT(obj->deviceInfo.instance, &dbgCreateInfo, nullptr, &obj->debugMessenger); result != VK_SUCCESS) {
LOG_CRIT(L"createDebugUtilsMessengerEXT() %S", string_VkResult(result));
}
}
// -
if (auto result = SDL_Vulkan_CreateSurface(window, obj->deviceInfo.instance, &surface); result != true) {
LOG_CRIT(L"SDL_Vulkan_CreateSurface() Error:%S", SDL_GetError());
}
VulkanQueues queues;
findPhysicalDevice(obj->deviceInfo.instance, surface, &obj->surfaceCapabilities, &obj->deviceInfo.physicalDevice, &queues, enableValidation);
if (obj->deviceInfo.physicalDevice == nullptr) {
LOG_CRIT(L"Couldn't find a suitable device");
}
vkGetPhysicalDeviceProperties(obj->deviceInfo.physicalDevice, &g_PhysicalDeviceProperties);
{
auto const text =
std::format("Selected GPU:{} api:{}.{}.{}", g_PhysicalDeviceProperties.deviceName, VK_API_VERSION_MAJOR(g_PhysicalDeviceProperties.apiVersion),
VK_API_VERSION_MINOR(g_PhysicalDeviceProperties.apiVersion), VK_API_VERSION_PATCH(g_PhysicalDeviceProperties.apiVersion));
printf("%s\n", text.data());
LOG_INFO(L"%S", text.data());
}
obj->deviceInfo.device = createDevice(obj->deviceInfo.physicalDevice, surface, extensions, queues, enableValidation);
// Create queues
{
auto queueFunc = [](VkDevice device, std::vector<VulkanQueues::QueueInfo> const queueInfos, std::vector<std::unique_ptr<QueueInfo>>& out) {
for (auto& item: queueInfos) {
VkQueue queue;
vkGetDeviceQueue(device, item.family, item.index, &queue);
out.push_back(std::make_unique<QueueInfo>(queue, item.family));
}
};
queueFunc(obj->deviceInfo.device, queues.compute, obj->queues.items[getIndex(QueueType::compute)]);
queueFunc(obj->deviceInfo.device, queues.present, obj->queues.items[getIndex(QueueType::present)]);
queueFunc(obj->deviceInfo.device, queues.transfer, obj->queues.items[getIndex(QueueType::transfer)]);
queueFunc(obj->deviceInfo.device, queues.graphics, obj->queues.items[getIndex(QueueType::graphics)]);
}
//-
return obj;
}
VkPhysicalDeviceLimits const* getPhysicalLimits() {
return &g_PhysicalDeviceProperties.limits;
}
VkInstance const getVkInstance() {
return g_VkInstance;
}
std::string_view const getGPUName() {
return g_PhysicalDeviceProperties.deviceName;
}
void deinitVulkan(VulkanObj* obj) {
vkDestroyInstance(obj->deviceInfo.instance, nullptr);
vkDestroyDevice(obj->deviceInfo.device, nullptr);
delete obj;
}
} // namespace vulkan

View File

@ -1,89 +0,0 @@
#pragma once
#include <array>
#include <boost/thread/thread.hpp>
#include <memory>
#include <mutex>
#include <string>
#include <type_traits>
#include <utility>
#include <vector>
#include <vulkan/vulkan_core.h>
struct SDL_Window;
namespace vulkan {
struct DeviceInfo {
VkInstance instance = nullptr;
VkPhysicalDevice physicalDevice = nullptr;
VkDevice device = nullptr;
};
enum class QueueType : uint8_t {
graphics = 0,
compute,
transfer,
present,
numTypes,
};
constexpr std::underlying_type<QueueType>::type getIndex(QueueType type) {
return (std::underlying_type<QueueType>::type)type;
}
struct QueueInfo {
VkQueue queue = nullptr;
uint32_t family = 0;
size_t useCount = 0;
mutable std::mutex mutex; // sync queue submit access
QueueInfo(VkQueue queue_, uint32_t family_): queue(queue_), family(family_) {}
};
struct Queues {
std::array<std::vector<std::unique_ptr<QueueInfo>>, getIndex(QueueType::numTypes)> items {}; /// first: VkQueue, second: familyindex
};
struct SwapchainData {
struct DisplayBuffers {
uint64_t bufferVaddr = 0;
uint32_t bufferSize = 0;
uint32_t bufferAlign = 0;
};
std::vector<DisplayBuffers> buffers;
};
struct SurfaceCapabilities {
VkSurfaceCapabilitiesKHR capabilities {};
std::vector<VkSurfaceFormatKHR> formats;
std::vector<VkPresentModeKHR> presentModes;
bool format_srgb_bgra32 = false;
bool format_unorm_bgra32 = false;
};
struct VulkanObj {
DeviceInfo deviceInfo;
VkDebugUtilsMessengerEXT debugMessenger = nullptr;
SurfaceCapabilities surfaceCapabilities;
Queues queues;
};
VulkanObj* initVulkan(SDL_Window* window, VkSurfaceKHR& surface, bool useValidation);
void deinitVulkan(VulkanObj* obj);
void createSurface(VulkanObj* obj, SDL_Window* window, VkSurfaceKHR& surfaceOut);
VkPhysicalDeviceLimits const* getPhysicalLimits();
VkInstance const getVkInstance();
std::string_view const getGPUName();
std::pair<VkFormat, VkColorSpaceKHR> getDisplayFormat(VulkanObj* obj);
} // namespace vulkan

23
docs/README.md Normal file
View File

@ -0,0 +1,23 @@
# Docs README
## Wiki update rules
### General rules
1. You should not put anything to root wiki directory (`docs/wiki/`);
2. All the `.md` files should go to `docs/wiki/markdown/` folder;
3. Your links should not lead to shady sites;
4. If you removed screenshot from the markdown code then delete the image itself in the `docs/wiki/markdown/images/` folder.
### Name your wiki files properly!
1. Do not use the following characters in your wiki page's titles: `\ / : * ? " < > |`;
2. Use `-` sign instead of space;
3. Too long name is not good at all, keep it ~32 chars long maximum;
4. It's cool that GitHub's markup engine supports so much markup languages, but keep it `md` for consistency, please. Markdowns are the easiest to maintain and most common pretty everywhere;
5. You should write wiki pages **only** in English. We also do not accept translated pages there since GitHub does not support them. The wiki would be a mess if we did accept these changes.
### Add screenshots if needed!
1. Don't do it too often;
2. Add screenshots that will actually help;
3. Draw eye directing arrows or circles of some kind on them, if possible and necessary;
4. Screenshots should be uploaded to this repository only, there are special folder for these: `docs/wiki/images/`, just use relative \(`/docs/wiki/images/image.png`\) links to add them in your markdown page;
5. Give your screenshots meaningful file names and put them in sub-directories so they don't get lost.

33
docs/json/audio.json Normal file
View File

@ -0,0 +1,33 @@
{
"type": "object",
"additionalProperties": false,
"properties": {
"$schema": {
"type": "string"
},
"device": {
"description": "The name of your sound output device e.g. \"Speakers (Realtek(R) Audio)\".\n* \"[default]\" means that emulator should use OS default audio device.\n* \"[null]\" means that emulator should not send the received data to any of audio devices.",
"type": "string"
},
"volume": {
"description": "Master volume for every emulator's sound output.",
"type": "number",
"minimum": 0.0,
"maximum": 1.0
},
"padspeakers": {
"type": "array",
"description": "Host audio devices for each of connected gamepads.\n* \"[null]\" means that emulator should not send the received data to any of audio devices.",
"minItems": 4,
"maxItems": 4,
"items": {
"type": "string"
}
}
},
"required": [
"device",
"volume",
"padspeakers"
]
}

194
docs/json/controls.json Normal file
View File

@ -0,0 +1,194 @@
{
"type": "object",
"additionalProperties": false,
"definitions": {
"button": {
"type": "string",
"description": "This gamepad button will be pressed when you press the speified button on the keyboard.\n\"gamepad_key\": \"keyboard_key\"\n\nYou can find keyboard key names there: <https://wiki.libsdl.org/SDL2/SDL_Scancode>."
},
"deadzone": {
"type": "object",
"additionalProperties": false,
"properties": {
"x": {
"type": "number",
"minimum": 0.0,
"maximum": 0.9
},
"y": {
"type": "number",
"minimum": 0.0,
"maximum": 0.9
}
},
"required": [
"x",
"y"
]
}
},
"properties": {
"$schema": {
"type": "string"
},
"keybinds": {
"type": "object",
"description": "Information about keybinds for gamepad emulation, hover your cursor on any key inside this object to get more info.",
"additionalProperties": false,
"properties": {
"gamereport.send": {
"$ref": "#/definitions/button"
},
"overlay.open": {
"$ref": "#/definitions/button"
},
"controller.circle": {
"$ref": "#/definitions/button"
},
"controller.cross": {
"$ref": "#/definitions/button"
},
"controller.dpad_down": {
"$ref": "#/definitions/button"
},
"controller.dpad_left": {
"$ref": "#/definitions/button"
},
"controller.dpad_right": {
"$ref": "#/definitions/button"
},
"controller.dpad_up": {
"$ref": "#/definitions/button"
},
"controller.l1": {
"$ref": "#/definitions/button"
},
"controller.l2": {
"$ref": "#/definitions/button"
},
"controller.l3": {
"$ref": "#/definitions/button"
},
"controller.lx+": {
"$ref": "#/definitions/button"
},
"controller.lx-": {
"$ref": "#/definitions/button"
},
"controller.ly+": {
"$ref": "#/definitions/button"
},
"controller.ly-": {
"$ref": "#/definitions/button"
},
"controller.options": {
"$ref": "#/definitions/button"
},
"controller.r1": {
"$ref": "#/definitions/button"
},
"controller.r2": {
"$ref": "#/definitions/button"
},
"controller.r3": {
"$ref": "#/definitions/button"
},
"controller.rx+": {
"$ref": "#/definitions/button"
},
"controller.rx-": {
"$ref": "#/definitions/button"
},
"controller.ry+": {
"$ref": "#/definitions/button"
},
"controller.ry-": {
"$ref": "#/definitions/button"
},
"controller.square": {
"$ref": "#/definitions/button"
},
"controller.touchpad": {
"$ref": "#/definitions/button"
},
"controller.triangle": {
"$ref": "#/definitions/button"
}
},
"required": [
"gamereport.send",
"controller.circle",
"controller.cross",
"controller.dpad_down",
"controller.dpad_left",
"controller.dpad_right",
"controller.dpad_up",
"controller.l1",
"controller.l2",
"controller.l3",
"controller.lx+",
"controller.lx-",
"controller.ly+",
"controller.ly-",
"controller.options",
"controller.r1",
"controller.r2",
"controller.r3",
"controller.rx+",
"controller.rx-",
"controller.ry+",
"controller.ry-",
"controller.square",
"controller.touchpad",
"controller.triangle"
]
},
"pads": {
"type": "array",
"description": "This array contains information about connected pads.",
"minItems": 4,
"maxItems": 4,
"items": {
"type": "object",
"description": "The pad information itself.",
"additionalProperties": false,
"properties": {
"deadzones": {
"type": "object",
"description": "Dead zones are WIP, not working for now.",
"additionalProperties": false,
"properties": {
"left_stick": {
"$ref": "#/definitions/deadzone"
},
"right_stick": {
"$ref": "#/definitions/deadzone"
}
},
"required": [
"left_stick",
"right_stick"
]
},
"type": {
"type": "string",
"description": "Backend for this gamepad. Possible values:\n* SDL (almost any gamepad): sdl\n* All Xbox-compatible gamepads: xinput\n* Gamepad emulation using keyboard: keyboard",
"enum": [
"sdl",
"xinput",
"keyboard"
]
}
},
"required": [
"type",
"deadzones"
]
}
}
},
"required": [
"keybinds",
"pads"
]
}

82
docs/json/general.json Normal file
View File

@ -0,0 +1,82 @@
{
"type": "object",
"additionalProperties": false,
"properties": {
"$schema": {
"type": "string"
},
"netEnabled": {
"type": "boolean",
"description": "Network subsystem state, it is not recomended to enable network, it may cause your emulator to crash."
},
"netMAC": {
"type": "string",
"description": "You should specify your host adapter MAC address here. This adapter will be used for network connection to game's servers. This option is ignored if \"netEnabled\" set to `false`.\n\n00:00:00:00:00:00 means \"first available adapter with IPv4 address\".",
"pattern": "(?:[0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}$"
},
"onlineUsers": {
"type": "integer",
"description": "This option specifies the number of logged in users. The maximum number of users is 4. Every user must have their own configured gamepad (see controls.json).",
"minimum": 1,
"maximum": 4
},
"profiles": {
"type": "array",
"description": "This array contains information about all the console users. The specified name and lightbar color will be sent to the game on request.",
"minItems": 4,
"maxItems": 4,
"items": {
"type": "object",
"additionalProperties": false,
"properties": {
"color": {
"type": "string",
"description": "The user color, probably unused.",
"enum": [
"blue",
"red",
"green",
"pink"
]
},
"name": {
"type": "string",
"description": "The displayed user name, the game may draw it somewhere.",
"minLength": 3,
"maxLength": 16
}
},
"required": [
"color",
"name"
]
}
},
"systemlang": {
"type": "integer",
"description": "The game language, defaults to EnglishUS. Game must support it.\n Languages list: <https://github.com/SysRay/psOff_public/wiki/Config-files#generaljson>.",
"minimum": 0,
"maximum": 29
},
"userIndex": {
"type": "number",
"description": "Index of the user who launched the game, should be less than \"onlineUsers\" or equal.",
"minimum": 1,
"maximum": 4
},
"trophyKey": {
"type": "string",
"description": "ERK trophy key in hex format. You should dump it from your console, otherwise trophies will not work.",
"pattern": "^(0x)?[0-9A-Fa-f]*$",
"maximum": 32
}
},
"required": [
"netEnabled",
"netMAC",
"profiles",
"systemlang",
"userIndex",
"trophyKey"
]
}

43
docs/json/graphics.json Normal file
View File

@ -0,0 +1,43 @@
{
"type": "object",
"additionalProperties": false,
"properties": {
"$schema": {
"type": "string"
},
"display": {
"type": "number",
"description": "The display index where emulator windows will be shown."
},
"fullscreen": {
"type": "boolean",
"description": "Wether emulator will run in fullscreen mode or not."
},
"width": {
"type": "number",
"description": "The emulator window width, ignored in fullscreen mode.",
"minimum": 400
},
"height": {
"type": "number",
"description": "The emulator window height, ignored in fullscreen mode.",
"minimum": 225
},
"xpos": {
"type": "number",
"description": "The emulator window horizontal position."
},
"ypos": {
"type": "number",
"description": "The emulator window vertical position."
}
},
"required": [
"display",
"fullscreen",
"width",
"height",
"xpos",
"ypos"
]
}

36
docs/json/logging.json Normal file
View File

@ -0,0 +1,36 @@
{
"type": "object",
"additionalProperties": false,
"properties": {
"$schema": {
"type": "string"
},
"sink": {
"type": "string",
"description": "Logging output.",
"enum": [
"Null",
"Auto",
"Syslog",
"Console",
"FileTxt",
"FileBin",
"Baical"
]
},
"verbosity": {
"type": "integer",
"description": "Verbosity level of logger.\nPossible values:\n0 - Trace\n1 - Debug\n2 - Warning\n3 - Error\n4 - Critical\n5 - None",
"minimum": 0,
"maximum": 5
},
"_customVerb": {
"type": "object",
"description": "Custom verbosity level for each module."
}
},
"required": [
"sink",
"verbosity"
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

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