prepare rexglue fork for Skate 3
This commit is contained in:
commit
3ec2b41a6f
1280 changed files with 459695 additions and 0 deletions
109
.clang-format
Normal file
109
.clang-format
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
---
|
||||
Language: Cpp
|
||||
BasedOnStyle: Google
|
||||
|
||||
# Layout
|
||||
ColumnLimit: 100
|
||||
IndentWidth: 2
|
||||
TabWidth: 2
|
||||
UseTab: Never
|
||||
ContinuationIndentWidth: 4
|
||||
ConstructorInitializerIndentWidth: 4
|
||||
|
||||
# Braces
|
||||
BreakBeforeBraces: Attach
|
||||
|
||||
# Alignment
|
||||
PointerAlignment: Left
|
||||
ReferenceAlignment: Left
|
||||
AccessModifierOffset: -1
|
||||
IndentAccessModifiers: false
|
||||
|
||||
# Namespaces
|
||||
NamespaceIndentation: None
|
||||
CompactNamespaces: false
|
||||
FixNamespaceComments: true
|
||||
|
||||
# Includes
|
||||
SortIncludes: Never
|
||||
IncludeBlocks: Regroup
|
||||
IncludeCategories:
|
||||
# 1. Main header (e.g. foo.cpp -> foo.h)
|
||||
- Regex: '^"'
|
||||
Priority: 0
|
||||
SortPriority: 0
|
||||
# 2. C system headers
|
||||
- Regex: '^<(assert|complex|ctype|errno|fenv|float|inttypes|iso646|limits|locale|math|setjmp|signal|stdalign|stdarg|stdatomic|stdbool|stddef|stdint|stdio|stdlib|stdnoreturn|string|tgmath|threads|time|uchar|wchar|wctype)\.h>'
|
||||
Priority: 1
|
||||
SortPriority: 1
|
||||
# 3. C++ standard library
|
||||
- Regex: '^<(algorithm|any|array|atomic|barrier|bit|bitset|cassert|cctype|cerrno|cfenv|cfloat|charconv|chrono|cinttypes|climits|clocale|cmath|compare|complex|concepts|condition_variable|coroutine|csetjmp|csignal|cstdarg|cstddef|cstdint|cstdio|cstdlib|cstring|ctgmath|ctime|cuchar|cwchar|cwctype|deque|exception|execution|expected|filesystem|format|forward_list|fstream|functional|future|initializer_list|iomanip|ios|iosfwd|iostream|istream|iterator|latch|limits|list|locale|map|mdspan|memory|memory_resource|mutex|new|numbers|numeric|optional|ostream|print|queue|random|ranges|ratio|regex|scoped_allocator|semaphore|set|shared_mutex|source_location|span|spanstream|sstream|stack|stacktrace|stdexcept|stop_token|streambuf|string|string_view|strstream|syncstream|system_error|thread|tuple|type_traits|typeindex|typeinfo|unordered_map|unordered_set|utility|valarray|variant|vector|version)>'
|
||||
Priority: 2
|
||||
SortPriority: 2
|
||||
# 4. POSIX / platform headers
|
||||
- Regex: '^<(sys/|unistd|fcntl|poll|pthread|dlfcn|netdb|netinet|arpa|windows|winnt|winbase|winsock|d3d|dxgi|vulkan|vk_)'
|
||||
Priority: 3
|
||||
SortPriority: 3
|
||||
# 5. Third-party headers
|
||||
- Regex: '^<(fmt|spdlog|SDL|imgui|toml|catch2|CLI|volk|glslang|spirv|renderdoc|simde|snappy|date|utfcpp|aes|mspack|xxhash|disruptorplus)/'
|
||||
Priority: 4
|
||||
SortPriority: 4
|
||||
# 6. Project headers
|
||||
- Regex: '^<rex/'
|
||||
Priority: 5
|
||||
SortPriority: 5
|
||||
# 7. Everything else
|
||||
- Regex: '.*'
|
||||
Priority: 6
|
||||
SortPriority: 6
|
||||
|
||||
# Short forms
|
||||
AllowShortFunctionsOnASingleLine: Inline
|
||||
AllowShortIfStatementsOnASingleLine: Never
|
||||
AllowShortLoopsOnASingleLine: false
|
||||
AllowShortBlocksOnASingleLine: Empty
|
||||
AllowShortEnumsOnASingleLine: true
|
||||
AllowShortLambdasOnASingleLine: All
|
||||
AllowShortCaseLabelsOnASingleLine: false
|
||||
|
||||
# Other
|
||||
AlignAfterOpenBracket: Align
|
||||
AlignConsecutiveAssignments: false
|
||||
AlignConsecutiveDeclarations: false
|
||||
AlignEscapedNewlines: Left
|
||||
AlignOperands: Align
|
||||
AlignTrailingComments: true
|
||||
AllowAllParametersOfDeclarationOnNextLine: true
|
||||
BinPackArguments: true
|
||||
BinPackParameters: true
|
||||
BreakConstructorInitializers: BeforeColon
|
||||
PackConstructorInitializers: NextLine
|
||||
DerivePointerAlignment: false
|
||||
EmptyLineAfterAccessModifier: Never
|
||||
EmptyLineBeforeAccessModifier: LogicalBlock
|
||||
IndentCaseBlocks: false
|
||||
IndentCaseLabels: true
|
||||
IndentPPDirectives: None
|
||||
IndentWrappedFunctionNames: false
|
||||
InsertBraces: false
|
||||
KeepEmptyLinesAtTheStartOfBlocks: false
|
||||
MaxEmptyLinesToKeep: 1
|
||||
ReflowComments: true
|
||||
SeparateDefinitionBlocks: Leave
|
||||
SpaceAfterCStyleCast: false
|
||||
SpaceAfterLogicalNot: false
|
||||
SpaceBeforeAssignmentOperators: true
|
||||
SpaceBeforeCaseColon: false
|
||||
SpaceBeforeParens: ControlStatements
|
||||
SpaceBeforeRangeBasedForLoopColon: true
|
||||
SpaceBeforeSquareBrackets: false
|
||||
SpaceInEmptyBlock: false
|
||||
SpaceInEmptyParentheses: false
|
||||
SpacesBeforeTrailingComments: 2
|
||||
SpacesInAngles: Never
|
||||
SpacesInConditionalStatement: false
|
||||
SpacesInContainerLiterals: false
|
||||
SpacesInParentheses: false
|
||||
SpacesInSquareBrackets: false
|
||||
Standard: Latest
|
||||
...
|
||||
101
.clang-tidy
Normal file
101
.clang-tidy
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
---
|
||||
Checks: >
|
||||
-*,
|
||||
bugprone-*,
|
||||
-bugprone-easily-swappable-parameters,
|
||||
-bugprone-narrowing-conversions,
|
||||
-bugprone-reserved-identifier,
|
||||
-bugprone-exception-escape,
|
||||
performance-*,
|
||||
-performance-avoid-endl,
|
||||
readability-identifier-naming,
|
||||
readability-redundant-*,
|
||||
readability-misleading-indentation,
|
||||
readability-container-size-empty,
|
||||
readability-make-member-function-const,
|
||||
misc-unused-using-decls,
|
||||
misc-redundant-expression,
|
||||
|
||||
WarningsAsErrors: ''
|
||||
|
||||
HeaderFilterRegex: 'include/rex/.*'
|
||||
|
||||
CheckOptions:
|
||||
# Classes and structs
|
||||
- key: readability-identifier-naming.ClassCase
|
||||
value: CamelCase
|
||||
- key: readability-identifier-naming.StructCase
|
||||
value: CamelCase
|
||||
|
||||
# Functions and methods
|
||||
- key: readability-identifier-naming.FunctionCase
|
||||
value: CamelCase
|
||||
- key: readability-identifier-naming.MethodCase
|
||||
value: CamelCase
|
||||
|
||||
# Variables
|
||||
- key: readability-identifier-naming.VariableCase
|
||||
value: lower_case
|
||||
- key: readability-identifier-naming.ParameterCase
|
||||
value: lower_case
|
||||
- key: readability-identifier-naming.LocalVariableCase
|
||||
value: lower_case
|
||||
|
||||
# Member variables
|
||||
- key: readability-identifier-naming.MemberCase
|
||||
value: lower_case
|
||||
- key: readability-identifier-naming.MemberSuffix
|
||||
value: '_'
|
||||
- key: readability-identifier-naming.PrivateMemberCase
|
||||
value: lower_case
|
||||
- key: readability-identifier-naming.PrivateMemberSuffix
|
||||
value: '_'
|
||||
- key: readability-identifier-naming.ProtectedMemberCase
|
||||
value: lower_case
|
||||
- key: readability-identifier-naming.ProtectedMemberSuffix
|
||||
value: '_'
|
||||
|
||||
# Constants
|
||||
- key: readability-identifier-naming.ConstexprVariableCase
|
||||
value: CamelCase
|
||||
- key: readability-identifier-naming.ConstexprVariablePrefix
|
||||
value: 'k'
|
||||
- key: readability-identifier-naming.StaticConstantCase
|
||||
value: CamelCase
|
||||
- key: readability-identifier-naming.StaticConstantPrefix
|
||||
value: 'k'
|
||||
- key: readability-identifier-naming.GlobalConstantCase
|
||||
value: CamelCase
|
||||
- key: readability-identifier-naming.GlobalConstantPrefix
|
||||
value: 'k'
|
||||
|
||||
# Enum constants
|
||||
- key: readability-identifier-naming.EnumConstantCase
|
||||
value: CamelCase
|
||||
- key: readability-identifier-naming.EnumConstantPrefix
|
||||
value: 'k'
|
||||
|
||||
# Enums
|
||||
- key: readability-identifier-naming.EnumCase
|
||||
value: CamelCase
|
||||
|
||||
# Namespaces
|
||||
- key: readability-identifier-naming.NamespaceCase
|
||||
value: lower_case
|
||||
|
||||
# Template parameters
|
||||
- key: readability-identifier-naming.TemplateParameterCase
|
||||
value: CamelCase
|
||||
- key: readability-identifier-naming.TypeTemplateParameterCase
|
||||
value: CamelCase
|
||||
|
||||
# Type aliases
|
||||
- key: readability-identifier-naming.TypeAliasCase
|
||||
value: CamelCase
|
||||
- key: readability-identifier-naming.TypedefCase
|
||||
value: CamelCase
|
||||
|
||||
# Macros
|
||||
- key: readability-identifier-naming.MacroDefinitionCase
|
||||
value: UPPER_CASE
|
||||
...
|
||||
19
.cmake-format.yaml
Normal file
19
.cmake-format.yaml
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
format:
|
||||
line_width: 100
|
||||
tab_size: 2
|
||||
use_tabchars: false
|
||||
fractional_tab_policy: use-space
|
||||
max_subgroups_hwrap: 2
|
||||
max_pargs_hwrap: 6
|
||||
max_rows_cmdline: 2
|
||||
separate_ctrl_name_with_space: true
|
||||
separate_fn_name_with_space: false
|
||||
dangle_parens: true
|
||||
dangle_align: prefix
|
||||
min_prefix_chars: 4
|
||||
max_prefix_chars: 10
|
||||
command_case: lower
|
||||
keyword_case: upper
|
||||
|
||||
markup:
|
||||
enable_markup: false
|
||||
36
.editorconfig
Normal file
36
.editorconfig
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.{cpp,h,hpp,c}]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
[CMakeLists.txt]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
[*.cmake]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
[*.{yaml,yml}]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
[*.{sh,bash}]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
end_of_line = lf
|
||||
|
||||
[*.ps1]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
end_of_line = lf
|
||||
17
.gitattributes
vendored
Normal file
17
.gitattributes
vendored
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
# Enforce LF line endings for all text files
|
||||
* text=auto eol=lf
|
||||
|
||||
# Explicitly mark binary files
|
||||
*.png binary
|
||||
*.jpg binary
|
||||
*.ico binary
|
||||
*.zip binary
|
||||
*.exe binary
|
||||
*.dll binary
|
||||
*.so binary
|
||||
*.dylib binary
|
||||
*.a binary
|
||||
*.lib binary
|
||||
*.obj binary
|
||||
*.o binary
|
||||
*.pdb binary
|
||||
2
.github/CODEOWNERS
vendored
Normal file
2
.github/CODEOWNERS
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
# Global owners
|
||||
* @tomcl7
|
||||
97
.github/ISSUE_TEMPLATE/bug-report.yml
vendored
Normal file
97
.github/ISSUE_TEMPLATE/bug-report.yml
vendored
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
name: Bug Report
|
||||
description: Report a bug or unexpected behavior
|
||||
title: "[Bug]: "
|
||||
labels: ["bug"]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for reporting a bug! Please fill out as much detail as possible.
|
||||
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: Description
|
||||
description: Clear description of the bug
|
||||
placeholder: When I do X, Y happens instead of Z.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: steps-to-reproduce
|
||||
attributes:
|
||||
label: Steps to Reproduce
|
||||
description: Minimal steps to reproduce the behavior
|
||||
placeholder: |
|
||||
1. Build with '...'
|
||||
2. Run '...'
|
||||
3. Observe error in log, set breakpoint here and inspect, etc.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: expected-behavior
|
||||
attributes:
|
||||
label: Expected Behavior
|
||||
description: What should happen instead?
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: actual-behavior
|
||||
attributes:
|
||||
label: Actual Behavior
|
||||
description: What actually happens? Include error messages, stack traces, or logs if available.
|
||||
placeholder: |
|
||||
The program crashes with:
|
||||
```
|
||||
error message here
|
||||
```
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
id: platform
|
||||
attributes:
|
||||
label: Platform
|
||||
description: What platform are you running on?
|
||||
options:
|
||||
- Windows
|
||||
- Linux
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: compiler
|
||||
attributes:
|
||||
label: Clang version
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: input
|
||||
id: version
|
||||
attributes:
|
||||
label: Version / Commit
|
||||
description: Version number or git commit hash
|
||||
placeholder: v0.1.0 or abc1234
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: textarea
|
||||
id: environment
|
||||
attributes:
|
||||
label: Additional Environment Info
|
||||
description: Any other relevant environment details (dev tool versions, GPU, etc.)
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: textarea
|
||||
id: related
|
||||
attributes:
|
||||
label: Related
|
||||
description: Links to related issues, code, or documentation
|
||||
placeholder: |
|
||||
- Possibly related to #69
|
||||
- Relevant code: `src/foo/bar.cpp:420`
|
||||
validations:
|
||||
required: false
|
||||
1
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
1
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
blank_issues_enabled: false
|
||||
93
.github/ISSUE_TEMPLATE/feature-request.yml
vendored
Normal file
93
.github/ISSUE_TEMPLATE/feature-request.yml
vendored
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
name: Feature Request
|
||||
description: Propose a new feature or enhancement
|
||||
title: "[Feature]: "
|
||||
labels: ["enhancement"]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for taking the time to propose a feature!
|
||||
|
||||
- type: textarea
|
||||
id: summary
|
||||
attributes:
|
||||
label: Summary
|
||||
description: Brief description of the feature and why it's needed
|
||||
placeholder: |
|
||||
Implement X to enable Y.
|
||||
|
||||
The current system lacks Z, which means we can't do A, B, or C.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: proposed-solution
|
||||
attributes:
|
||||
label: Proposed Solution
|
||||
description: How should this be implemented? Include key features and design decisions.
|
||||
placeholder: |
|
||||
Build on **library X** with a custom layer for Y.
|
||||
|
||||
### Key Features
|
||||
- **Feature A**: Description
|
||||
- **Feature B**: Description
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: requirements
|
||||
attributes:
|
||||
label: Requirements
|
||||
description: Checklist of requirements needed to implement this feature. Use `- [ ]` for checkboxes.
|
||||
placeholder: |
|
||||
### Requirement 1
|
||||
- [ ] Function A
|
||||
- [ ] Function B
|
||||
|
||||
### Requirement 2
|
||||
- [ ] Function C
|
||||
- [ ] Function D
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: out-of-scope
|
||||
attributes:
|
||||
label: Out of Scope
|
||||
description: What is explicitly NOT part of this feature request?
|
||||
placeholder: |
|
||||
- Feature X (separate effort)
|
||||
- Support for Y (deferred)
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: textarea
|
||||
id: dependencies
|
||||
attributes:
|
||||
label: Modified Dependencies
|
||||
description: Any new or changed dependencies required for this feature
|
||||
placeholder: |
|
||||
- **new_library** - For X functionality
|
||||
- **existing_lib** - Already in project
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: textarea
|
||||
id: acceptance-criteria
|
||||
attributes:
|
||||
label: Acceptance Criteria
|
||||
description: How do we know this feature is complete? Use `- [ ]` for checkboxes.
|
||||
placeholder: |
|
||||
- [ ] Feature X works as described
|
||||
- [ ] All tests pass
|
||||
- [ ] Documentation updated
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: related
|
||||
attributes:
|
||||
label: Related
|
||||
description: Links to design docs, related issues, or relevant code
|
||||
validations:
|
||||
required: false
|
||||
128
.github/ISSUE_TEMPLATE/new-instruction.yml
vendored
Normal file
128
.github/ISSUE_TEMPLATE/new-instruction.yml
vendored
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
name: New Instruction
|
||||
description: Add a new instruction to the rexglue codegen
|
||||
title: "instruction: "
|
||||
labels: ["instruction"]
|
||||
body:
|
||||
- type: input
|
||||
id: mnemonic
|
||||
attributes:
|
||||
label: Mnemonic
|
||||
description: Assembly mnemonic
|
||||
placeholder: "addze"
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: Description
|
||||
description: What the instruction does in plain language.
|
||||
placeholder: "Add to Zero Extended - adds the contents of rA and the CA bit of the XER to rT."
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
id: format
|
||||
attributes:
|
||||
label: Instruction Format
|
||||
options:
|
||||
- I (Branch - LI, AA, LK)
|
||||
- B (Conditional branch - BO, BI, BD, AA, LK)
|
||||
- D (Immediate - RT, RA, d/SIMM/UIMM)
|
||||
- DS (Double-word - RT, RA, DS, XO)
|
||||
- X (General - RT, RA, RB, XO, Rc)
|
||||
- XL (Branch to LR/CTR - BO, BI, XO, LK)
|
||||
- XFX (SPR access - RT, SPR, XO)
|
||||
- XO (Arithmetic w/ overflow - RT, RA, RB, OE, XO, Rc)
|
||||
- M (Rotate/mask - RS, RA, SH/RB, MB, ME, Rc)
|
||||
- MD (Rotate double-word - RS, RA, sh, mb, XO, Rc)
|
||||
- A (FP arithmetic - FRT, FRA, FRB, FRC, XO, Rc)
|
||||
- VA (Vector 4-operand - VRT, VRA, VRB, VRC, XO)
|
||||
- VX (Vector 3-operand - VRT, VRA, VRB, XO)
|
||||
- VXR (Vector w/ record - VRT, VRA, VRB, Rc, XO)
|
||||
- VMX128 (Xbox 360 extended vector)
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: dropdown
|
||||
id: group
|
||||
attributes:
|
||||
label: Opcode Group
|
||||
options:
|
||||
- General (ALU/logical)
|
||||
- Branch (control flow)
|
||||
- Memory (load/store)
|
||||
- Special (SPR access)
|
||||
- Sync (synchronization)
|
||||
- System (syscall/trap)
|
||||
- Float (floating-point)
|
||||
- Vector (VMX/VMX128)
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: primary_opcode
|
||||
attributes:
|
||||
label: Primary Opcode (bits 0-5)
|
||||
placeholder: "31"
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: extended_opcode
|
||||
attributes:
|
||||
label: Extended Opcode
|
||||
description: Leave blank if none.
|
||||
placeholder: "266"
|
||||
|
||||
- type: textarea
|
||||
id: decoding
|
||||
attributes:
|
||||
label: Bit Layout & Decoding
|
||||
description: Show the bit fields and how operands are extracted. Include all variants (Rc=0/1, OE=0/1).
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: simplified_mnemonics
|
||||
attributes:
|
||||
label: Simplified Mnemonics
|
||||
description: Any simplified forms this maps to/from.
|
||||
|
||||
- type: textarea
|
||||
id: operation
|
||||
attributes:
|
||||
label: Operation (pseudocode)
|
||||
description: Precise semantics including side effects (CR0, XER, etc).
|
||||
value: |
|
||||
```
|
||||
rT <- rA + rB
|
||||
if Rc then CR0 <- compare(rT, 0)
|
||||
if OE then XER[SO,OV] <- overflow
|
||||
```
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: checkboxes
|
||||
id: flags
|
||||
attributes:
|
||||
label: Instruction Properties
|
||||
options:
|
||||
- label: "Record form (Rc=1 sets CR0)"
|
||||
- label: "Overflow form (OE=1 sets XER[SO,OV])"
|
||||
- label: "Reads memory"
|
||||
- label: "Writes memory"
|
||||
- label: "Reads/writes LR"
|
||||
- label: "Reads/writes CTR"
|
||||
- label: "Reads/writes XER (carry, overflow)"
|
||||
- label: "Reads/writes FPSCR"
|
||||
- label: "Needs CSR flush mode change (FPU vs VMX)"
|
||||
- label: "Xbox 360 specific (not in standard PPC ISA)"
|
||||
|
||||
- type: textarea
|
||||
id: context
|
||||
attributes:
|
||||
label: Context
|
||||
description: What game was this encountered in? Provide context around how it is used, including example use cases.
|
||||
validations:
|
||||
required: true
|
||||
131
.github/workflows/_build-platform.yaml
vendored
Normal file
131
.github/workflows/_build-platform.yaml
vendored
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
name: Build platform (reusable)
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
platform:
|
||||
description: "win-amd64 | linux-amd64 | linux-arm64"
|
||||
required: true
|
||||
type: string
|
||||
ref:
|
||||
description: "Git ref (SHA or tag) to check out and build"
|
||||
required: true
|
||||
type: string
|
||||
publish_mode:
|
||||
description: "'release' to upload as a GitHub release asset, 'artifact' to upload as a workflow artifact"
|
||||
required: true
|
||||
type: string
|
||||
artifact_name:
|
||||
description: "Artifact name when publish_mode is 'artifact'"
|
||||
required: false
|
||||
type: string
|
||||
default: ""
|
||||
outputs:
|
||||
version:
|
||||
description: "Derived build version string (REXGLUE_VERSION_STRING)"
|
||||
value: ${{ jobs.build.outputs.version }}
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ${{ fromJSON('{"win-amd64":"windows-latest","linux-amd64":"ubuntu-latest","linux-arm64":"ubuntu-24.04-arm"}')[inputs.platform] }}
|
||||
outputs:
|
||||
version: ${{ steps.version.outputs.v }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ inputs.ref }}
|
||||
submodules: recursive
|
||||
fetch-depth: 0
|
||||
fetch-tags: true
|
||||
|
||||
- name: Install toolchain (Windows)
|
||||
if: inputs.platform == 'win-amd64'
|
||||
run: choco install llvm cmake ninja -y
|
||||
|
||||
- name: Install clang 20 (Linux)
|
||||
if: inputs.platform != 'win-amd64'
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y wget gnupg lsb-release
|
||||
wget https://apt.llvm.org/llvm.sh
|
||||
chmod +x llvm.sh
|
||||
sudo ./llvm.sh 20
|
||||
sudo apt-get install -y clang-20 lld-20
|
||||
sudo update-alternatives --install /usr/bin/clang clang /usr/bin/clang-20 200
|
||||
sudo update-alternatives --install /usr/bin/clang++ clang++ /usr/bin/clang++-20 200
|
||||
|
||||
- name: Install build deps (linux-amd64)
|
||||
if: inputs.platform == 'linux-amd64'
|
||||
run: |
|
||||
wget -qO- https://packages.lunarg.com/lunarg-signing-key-pub.asc | sudo tee /etc/apt/trusted.gpg.d/lunarg.asc
|
||||
sudo wget -qO /etc/apt/sources.list.d/lunarg-vulkan-noble.list http://packages.lunarg.com/vulkan/lunarg-vulkan-noble.list
|
||||
sudo apt update
|
||||
sudo apt install -y cmake ninja-build build-essential git curl unzip autoconf python3.12-venv \
|
||||
libgtk-3-dev libx11-xcb-dev wine vulkan-sdk \
|
||||
libasound2-dev libpulse-dev libpipewire-0.3-dev
|
||||
|
||||
- name: Install build deps (linux-arm64)
|
||||
if: inputs.platform == 'linux-arm64'
|
||||
run: |
|
||||
sudo apt update
|
||||
sudo apt install -y cmake ninja-build build-essential git curl unzip autoconf python3.12-venv \
|
||||
libgtk-3-dev libx11-xcb-dev libvulkan-dev glslc glslang-tools \
|
||||
libasound2-dev libpulse-dev libpipewire-0.3-dev
|
||||
|
||||
- name: Set up sccache
|
||||
uses: hendrikmuhs/ccache-action@v1.2.22
|
||||
with:
|
||||
variant: sccache
|
||||
key: ${{ inputs.platform }}
|
||||
|
||||
- name: Configure
|
||||
env:
|
||||
CC: ${{ inputs.platform == 'win-amd64' && '' || 'clang-20' }}
|
||||
CXX: ${{ inputs.platform == 'win-amd64' && '' || 'clang++-20' }}
|
||||
run: cmake --preset ${{ inputs.platform }} -DCMAKE_C_COMPILER_LAUNCHER=sccache -DCMAKE_CXX_COMPILER_LAUNCHER=sccache
|
||||
|
||||
- name: Build and install
|
||||
run: cmake --build out/build/${{ inputs.platform }} --target install --parallel
|
||||
|
||||
- name: sccache stats
|
||||
if: always()
|
||||
shell: bash
|
||||
run: sccache --show-stats || true
|
||||
|
||||
- name: Read version
|
||||
id: version
|
||||
shell: bash
|
||||
run: |
|
||||
v=$(sed -nE 's/^#define REXGLUE_VERSION_STRING "([^"]+)".*/\1/p' \
|
||||
out/build/${{ inputs.platform }}/include/rex/version.h)
|
||||
echo "v=${v}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Package (Windows)
|
||||
if: inputs.platform == 'win-amd64'
|
||||
shell: bash
|
||||
run: |
|
||||
cd out/install
|
||||
7z a "rexglue-sdk-${{ steps.version.outputs.v }}-${{ inputs.platform }}.zip" ${{ inputs.platform }}
|
||||
|
||||
- name: Package (Linux)
|
||||
if: inputs.platform != 'win-amd64'
|
||||
run: |
|
||||
cd out/install
|
||||
zip -r "rexglue-sdk-${{ steps.version.outputs.v }}-${{ inputs.platform }}.zip" ${{ inputs.platform }}
|
||||
|
||||
- name: Upload as release asset
|
||||
if: inputs.publish_mode == 'release'
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
files: out/install/rexglue-sdk-*-${{ inputs.platform }}.zip
|
||||
generate_release_notes: true
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Upload as workflow artifact
|
||||
if: inputs.publish_mode == 'artifact'
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: ${{ inputs.artifact_name }}
|
||||
path: out/install/rexglue-sdk-*-${{ inputs.platform }}.zip
|
||||
retention-days: 14
|
||||
19
.github/workflows/build-linux-aarch64.yaml
vendored
Normal file
19
.github/workflows/build-linux-aarch64.yaml
vendored
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
name: Build rexglue-sdk linux-arm64
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "v*"
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
actions: write
|
||||
|
||||
jobs:
|
||||
build:
|
||||
uses: ./.github/workflows/_build-platform.yaml
|
||||
with:
|
||||
platform: linux-arm64
|
||||
ref: ${{ github.ref }}
|
||||
publish_mode: release
|
||||
secrets: inherit
|
||||
19
.github/workflows/build-linux-amd64.yaml
vendored
Normal file
19
.github/workflows/build-linux-amd64.yaml
vendored
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
name: Build rexglue-sdk linux-amd64
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "v*"
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
actions: write
|
||||
|
||||
jobs:
|
||||
build:
|
||||
uses: ./.github/workflows/_build-platform.yaml
|
||||
with:
|
||||
platform: linux-amd64
|
||||
ref: ${{ github.ref }}
|
||||
publish_mode: release
|
||||
secrets: inherit
|
||||
19
.github/workflows/build-win-amd64.yaml
vendored
Normal file
19
.github/workflows/build-win-amd64.yaml
vendored
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
name: Build rexglue-sdk win-amd64
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "v*"
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
actions: write
|
||||
|
||||
jobs:
|
||||
build:
|
||||
uses: ./.github/workflows/_build-platform.yaml
|
||||
with:
|
||||
platform: win-amd64
|
||||
ref: ${{ github.ref }}
|
||||
publish_mode: release
|
||||
secrets: inherit
|
||||
28
.github/workflows/lint.yaml
vendored
Normal file
28
.github/workflows/lint.yaml
vendored
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
name: Lint & Format Check
|
||||
|
||||
on: [pull_request, push]
|
||||
|
||||
jobs:
|
||||
format-check:
|
||||
name: Format Check
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Install clang-format
|
||||
run: |
|
||||
wget https://apt.llvm.org/llvm.sh
|
||||
chmod +x llvm.sh
|
||||
sudo ./llvm.sh 20
|
||||
sudo apt-get install -y clang-format-20
|
||||
|
||||
# - name: Install cmake-format
|
||||
# run: pip install cmake-format
|
||||
|
||||
- name: Check C/C++ formatting
|
||||
run: |
|
||||
find include src tests \
|
||||
-type f \( -name '*.cpp' -o -name '*.h' -o -name '*.hpp' -o -name '*.c' \) \
|
||||
| xargs clang-format-20 --dry-run --Werror
|
||||
|
||||
152
.github/workflows/nightly.yaml
vendored
Normal file
152
.github/workflows/nightly.yaml
vendored
Normal file
|
|
@ -0,0 +1,152 @@
|
|||
name: Nightly rexglue-sdk
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: "0 6 * * *"
|
||||
workflow_dispatch: {}
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
actions: write
|
||||
|
||||
env:
|
||||
DEV_BRANCH: development
|
||||
NIGHTLY_TAG_PREFIX: nightly-
|
||||
NIGHTLY_RETENTION: 10
|
||||
RELEASE_LIST_LIMIT: 1000
|
||||
|
||||
jobs:
|
||||
check:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
should_build: ${{ steps.compare.outputs.should_build }}
|
||||
dev_sha: ${{ steps.compare.outputs.dev_sha }}
|
||||
prev_nightly_sha: ${{ steps.compare.outputs.prev_nightly_sha }}
|
||||
tag_name: ${{ steps.compare.outputs.tag_name }}
|
||||
steps:
|
||||
- name: Checkout dev branch
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ env.DEV_BRANCH }}
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Compare dev HEAD to most recent nightly
|
||||
id: compare
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
dev_sha=$(git rev-parse HEAD)
|
||||
short_sha="${dev_sha:0:8}"
|
||||
tag_name="${NIGHTLY_TAG_PREFIX}$(date -u +%Y%m%d)-${short_sha}"
|
||||
echo "dev_sha=${dev_sha}" >> "$GITHUB_OUTPUT"
|
||||
echo "tag_name=${tag_name}" >> "$GITHUB_OUTPUT"
|
||||
prev_release=$(gh release list --limit "${RELEASE_LIST_LIMIT}" \
|
||||
--json tagName,isPrerelease,createdAt \
|
||||
| jq -r --arg prefix "${NIGHTLY_TAG_PREFIX}" \
|
||||
'[.[] | select(.isPrerelease) | select(.tagName | startswith($prefix))]
|
||||
| sort_by(.createdAt) | last | .tagName // ""' \
|
||||
2>/dev/null || echo "")
|
||||
if [ -n "${prev_release}" ]; then
|
||||
prev_sha=$(gh release view "${prev_release}" --json targetCommitish -q .targetCommitish 2>/dev/null || echo "")
|
||||
else
|
||||
prev_sha=""
|
||||
fi
|
||||
echo "prev_nightly_sha=${prev_sha}" >> "$GITHUB_OUTPUT"
|
||||
if [ "${dev_sha}" = "${prev_sha}" ]; then
|
||||
echo "${DEV_BRANCH} unchanged since last nightly (${dev_sha}); skipping build."
|
||||
echo "should_build=false" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "${DEV_BRANCH} advanced (${prev_sha:-<no prior nightly>} -> ${dev_sha}); building as ${tag_name}."
|
||||
echo "should_build=true" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
build-win-amd64:
|
||||
needs: check
|
||||
if: needs.check.outputs.should_build == 'true'
|
||||
uses: ./.github/workflows/_build-platform.yaml
|
||||
with:
|
||||
platform: win-amd64
|
||||
ref: ${{ needs.check.outputs.dev_sha }}
|
||||
publish_mode: artifact
|
||||
artifact_name: nightly-win-amd64
|
||||
secrets: inherit
|
||||
|
||||
build-linux-amd64:
|
||||
needs: check
|
||||
if: needs.check.outputs.should_build == 'true'
|
||||
uses: ./.github/workflows/_build-platform.yaml
|
||||
with:
|
||||
platform: linux-amd64
|
||||
ref: ${{ needs.check.outputs.dev_sha }}
|
||||
publish_mode: artifact
|
||||
artifact_name: nightly-linux-amd64
|
||||
secrets: inherit
|
||||
|
||||
build-linux-arm64:
|
||||
needs: check
|
||||
if: needs.check.outputs.should_build == 'true'
|
||||
uses: ./.github/workflows/_build-platform.yaml
|
||||
with:
|
||||
platform: linux-arm64
|
||||
ref: ${{ needs.check.outputs.dev_sha }}
|
||||
publish_mode: artifact
|
||||
artifact_name: nightly-linux-arm64
|
||||
secrets: inherit
|
||||
|
||||
publish:
|
||||
needs: [check, build-win-amd64, build-linux-amd64, build-linux-arm64]
|
||||
if: needs.check.outputs.should_build == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ needs.check.outputs.dev_sha }}
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Download all artifacts
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
path: artifacts
|
||||
|
||||
- name: Publish nightly release
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
VERSION: ${{ needs.build-win-amd64.outputs.version }}
|
||||
DEV_SHA: ${{ needs.check.outputs.dev_sha }}
|
||||
PREV_SHA: ${{ needs.check.outputs.prev_nightly_sha }}
|
||||
TAG_NAME: ${{ needs.check.outputs.tag_name }}
|
||||
run: |
|
||||
if [ -n "${PREV_SHA}" ] && git cat-file -e "${PREV_SHA}^{commit}" 2>/dev/null; then
|
||||
commits=$(git log --no-merges --pretty='format:- %s (%h)' "${PREV_SHA}..${DEV_SHA}")
|
||||
range_header="Changes since previous nightly (\`${PREV_SHA:0:8}\`):"
|
||||
else
|
||||
commits=$(git log --no-merges --pretty='format:- %s (%h)' -20 "${DEV_SHA}")
|
||||
range_header="Latest commits on \`${DEV_BRANCH}\`:"
|
||||
fi
|
||||
if [ -z "${commits}" ]; then
|
||||
commits="- (no new commits)"
|
||||
fi
|
||||
notes=$(printf '%s\n\n%s\n' "${range_header}" "${commits}")
|
||||
gh release create "${TAG_NAME}" \
|
||||
--target "${DEV_SHA}" \
|
||||
--title "Nightly ${VERSION}" \
|
||||
--notes "${notes}" \
|
||||
--prerelease \
|
||||
artifacts/nightly-win-amd64/*.zip \
|
||||
artifacts/nightly-linux-amd64/*.zip \
|
||||
artifacts/nightly-linux-arm64/*.zip
|
||||
|
||||
- name: Prune old nightlies
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
gh release list --limit "${RELEASE_LIST_LIMIT}" \
|
||||
--json tagName,isPrerelease,createdAt \
|
||||
| jq -r --arg prefix "${NIGHTLY_TAG_PREFIX}" --argjson keep "${NIGHTLY_RETENTION}" \
|
||||
'[.[] | select(.isPrerelease) | select(.tagName | startswith($prefix))]
|
||||
| sort_by(.createdAt) | reverse | .[$keep:] | .[].tagName' \
|
||||
| while IFS= read -r tag; do
|
||||
[ -z "${tag}" ] && continue
|
||||
echo "Pruning ${tag}"
|
||||
gh release delete "${tag}" --yes --cleanup-tag
|
||||
done
|
||||
421
.gitignore
vendored
Normal file
421
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,421 @@
|
|||
## Ignore Visual Studio temporary files, build results, and
|
||||
## files generated by popular Visual Studio add-ons.
|
||||
##
|
||||
## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore
|
||||
|
||||
# User-specific files
|
||||
*.rsuser
|
||||
*.suo
|
||||
*.user
|
||||
*.userosscache
|
||||
*.sln.docstates
|
||||
*.env
|
||||
|
||||
# User-specific files (MonoDevelop/Xamarin Studio)
|
||||
*.userprefs
|
||||
|
||||
# Mono auto generated files
|
||||
mono_crash.*
|
||||
|
||||
# Build results
|
||||
[Dd]ebug/
|
||||
[Dd]ebugPublic/
|
||||
[Rr]elease/
|
||||
[Rr]eleases/
|
||||
x64/
|
||||
x86/
|
||||
[Ww][Ii][Nn]32/
|
||||
[Aa][Rr][Mm]/
|
||||
[Aa][Rr][Mm]64/
|
||||
[Aa][Rr][Mm]64[Ee][Cc]/
|
||||
bld/
|
||||
[Oo]bj/
|
||||
[Oo]ut/
|
||||
[Ll]og/
|
||||
[Ll]ogs/
|
||||
|
||||
# Build results on 'Bin' directories
|
||||
**/[Bb]in/*
|
||||
# Uncomment if you have tasks that rely on *.refresh files to move binaries
|
||||
# (https://github.com/github/gitignore/pull/3736)
|
||||
#!**/[Bb]in/*.refresh
|
||||
|
||||
# Visual Studio 2015/2017 cache/options directory
|
||||
.vs/
|
||||
# Uncomment if you have tasks that create the project's static files in wwwroot
|
||||
#wwwroot/
|
||||
|
||||
# Visual Studio 2017 auto generated files
|
||||
Generated\ Files/
|
||||
|
||||
# MSTest test Results
|
||||
[Tt]est[Rr]esult*/
|
||||
[Bb]uild[Ll]og.*
|
||||
*.trx
|
||||
|
||||
# NUnit
|
||||
*.VisualState.xml
|
||||
TestResult.xml
|
||||
nunit-*.xml
|
||||
|
||||
# Approval Tests result files
|
||||
*.received.*
|
||||
|
||||
# Build Results of an ATL Project
|
||||
[Dd]ebugPS/
|
||||
[Rr]eleasePS/
|
||||
dlldata.c
|
||||
|
||||
# Benchmark Results
|
||||
BenchmarkDotNet.Artifacts/
|
||||
|
||||
# .NET Core
|
||||
project.lock.json
|
||||
project.fragment.lock.json
|
||||
artifacts/
|
||||
|
||||
# ASP.NET Scaffolding
|
||||
ScaffoldingReadMe.txt
|
||||
|
||||
# StyleCop
|
||||
StyleCopReport.xml
|
||||
|
||||
# Files built by Visual Studio
|
||||
*_i.c
|
||||
*_p.c
|
||||
*_h.h
|
||||
*.ilk
|
||||
*.meta
|
||||
*.obj
|
||||
*.idb
|
||||
*.iobj
|
||||
*.pch
|
||||
*.pdb
|
||||
*.ipdb
|
||||
*.pgc
|
||||
*.pgd
|
||||
*.rsp
|
||||
# but not Directory.Build.rsp, as it configures directory-level build defaults
|
||||
!Directory.Build.rsp
|
||||
*.sbr
|
||||
*.tlb
|
||||
*.tli
|
||||
*.tlh
|
||||
*.tmp
|
||||
*.tmp_proj
|
||||
*_wpftmp.csproj
|
||||
*.log
|
||||
*.tlog
|
||||
*.vspscc
|
||||
*.vssscc
|
||||
.builds
|
||||
*.pidb
|
||||
*.svclog
|
||||
*.scc
|
||||
|
||||
# Chutzpah Test files
|
||||
_Chutzpah*
|
||||
|
||||
# Visual C++ cache files
|
||||
ipch/
|
||||
*.aps
|
||||
*.ncb
|
||||
*.opendb
|
||||
*.opensdf
|
||||
*.sdf
|
||||
*.cachefile
|
||||
*.VC.db
|
||||
*.VC.VC.opendb
|
||||
|
||||
# Visual Studio profiler
|
||||
*.psess
|
||||
*.vsp
|
||||
*.vspx
|
||||
*.sap
|
||||
|
||||
# Visual Studio Trace Files
|
||||
*.e2e
|
||||
|
||||
# TFS 2012 Local Workspace
|
||||
$tf/
|
||||
|
||||
# Guidance Automation Toolkit
|
||||
*.gpState
|
||||
|
||||
# ReSharper is a .NET coding add-in
|
||||
_ReSharper*/
|
||||
*.[Rr]e[Ss]harper
|
||||
*.DotSettings.user
|
||||
|
||||
# TeamCity is a build add-in
|
||||
_TeamCity*
|
||||
|
||||
# DotCover is a Code Coverage Tool
|
||||
*.dotCover
|
||||
|
||||
# AxoCover is a Code Coverage Tool
|
||||
.axoCover/*
|
||||
!.axoCover/settings.json
|
||||
|
||||
# Coverlet is a free, cross platform Code Coverage Tool
|
||||
coverage*.json
|
||||
coverage*.xml
|
||||
coverage*.info
|
||||
|
||||
# Visual Studio code coverage results
|
||||
*.coverage
|
||||
*.coveragexml
|
||||
|
||||
# NCrunch
|
||||
_NCrunch_*
|
||||
.NCrunch_*
|
||||
.*crunch*.local.xml
|
||||
nCrunchTemp_*
|
||||
|
||||
# MightyMoose
|
||||
*.mm.*
|
||||
AutoTest.Net/
|
||||
|
||||
# Web workbench (sass)
|
||||
.sass-cache/
|
||||
|
||||
# Installshield output folder
|
||||
[Ee]xpress/
|
||||
|
||||
# DocProject is a documentation generator add-in
|
||||
DocProject/buildhelp/
|
||||
DocProject/Help/*.HxT
|
||||
DocProject/Help/*.HxC
|
||||
DocProject/Help/*.hhc
|
||||
DocProject/Help/*.hhk
|
||||
DocProject/Help/*.hhp
|
||||
DocProject/Help/Html2
|
||||
DocProject/Help/html
|
||||
|
||||
# Click-Once directory
|
||||
publish/
|
||||
|
||||
# Publish Web Output
|
||||
*.[Pp]ublish.xml
|
||||
*.azurePubxml
|
||||
# Note: Comment the next line if you want to checkin your web deploy settings,
|
||||
# but database connection strings (with potential passwords) will be unencrypted
|
||||
*.pubxml
|
||||
*.publishproj
|
||||
|
||||
# Microsoft Azure Web App publish settings. Comment the next line if you want to
|
||||
# checkin your Azure Web App publish settings, but sensitive information contained
|
||||
# in these scripts will be unencrypted
|
||||
PublishScripts/
|
||||
|
||||
# NuGet Packages
|
||||
*.nupkg
|
||||
# NuGet Symbol Packages
|
||||
*.snupkg
|
||||
# The packages folder can be ignored because of Package Restore
|
||||
**/[Pp]ackages/*
|
||||
# except build/, which is used as an MSBuild target.
|
||||
!**/[Pp]ackages/build/
|
||||
# Uncomment if necessary however generally it will be regenerated when needed
|
||||
#!**/[Pp]ackages/repositories.config
|
||||
# NuGet v3's project.json files produces more ignorable files
|
||||
*.nuget.props
|
||||
*.nuget.targets
|
||||
|
||||
# Microsoft Azure Build Output
|
||||
csx/
|
||||
*.build.csdef
|
||||
|
||||
# Microsoft Azure Emulator
|
||||
ecf/
|
||||
rcf/
|
||||
|
||||
# Windows Store app package directories and files
|
||||
AppPackages/
|
||||
BundleArtifacts/
|
||||
Package.StoreAssociation.xml
|
||||
_pkginfo.txt
|
||||
*.appx
|
||||
*.appxbundle
|
||||
*.appxupload
|
||||
|
||||
# Visual Studio cache files
|
||||
# files ending in .cache can be ignored
|
||||
*.[Cc]ache
|
||||
# but keep track of directories ending in .cache
|
||||
!?*.[Cc]ache/
|
||||
|
||||
# Others
|
||||
ClientBin/
|
||||
~$*
|
||||
*~
|
||||
*.dbmdl
|
||||
*.dbproj.schemaview
|
||||
*.jfm
|
||||
*.pfx
|
||||
*.publishsettings
|
||||
orleans.codegen.cs
|
||||
|
||||
# Including strong name files can present a security risk
|
||||
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
|
||||
#*.snk
|
||||
|
||||
# Since there are multiple workflows, uncomment next line to ignore bower_components
|
||||
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
|
||||
#bower_components/
|
||||
|
||||
# RIA/Silverlight projects
|
||||
Generated_Code/
|
||||
|
||||
# Backup & report files from converting an old project file
|
||||
# to a newer Visual Studio version. Backup files are not needed,
|
||||
# because we have git ;-)
|
||||
_UpgradeReport_Files/
|
||||
Backup*/
|
||||
UpgradeLog*.XML
|
||||
UpgradeLog*.htm
|
||||
ServiceFabricBackup/
|
||||
*.rptproj.bak
|
||||
|
||||
# SQL Server files
|
||||
*.mdf
|
||||
*.ldf
|
||||
*.ndf
|
||||
|
||||
# Business Intelligence projects
|
||||
*.rdl.data
|
||||
*.bim.layout
|
||||
*.bim_*.settings
|
||||
*.rptproj.rsuser
|
||||
*- [Bb]ackup.rdl
|
||||
*- [Bb]ackup ([0-9]).rdl
|
||||
*- [Bb]ackup ([0-9][0-9]).rdl
|
||||
|
||||
# Microsoft Fakes
|
||||
FakesAssemblies/
|
||||
|
||||
# GhostDoc plugin setting file
|
||||
*.GhostDoc.xml
|
||||
|
||||
# Node.js Tools for Visual Studio
|
||||
.ntvs_analysis.dat
|
||||
node_modules/
|
||||
|
||||
# Visual Studio 6 build log
|
||||
*.plg
|
||||
|
||||
# Visual Studio 6 workspace options file
|
||||
*.opt
|
||||
|
||||
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
|
||||
*.vbw
|
||||
|
||||
# Visual Studio 6 auto-generated project file (contains which files were open etc.)
|
||||
*.vbp
|
||||
|
||||
# Visual Studio 6 workspace and project file (working project files containing files to include in project)
|
||||
*.dsw
|
||||
*.dsp
|
||||
|
||||
# Visual Studio 6 technical files
|
||||
*.ncb
|
||||
*.aps
|
||||
|
||||
# Visual Studio LightSwitch build output
|
||||
**/*.HTMLClient/GeneratedArtifacts
|
||||
**/*.DesktopClient/GeneratedArtifacts
|
||||
**/*.DesktopClient/ModelManifest.xml
|
||||
**/*.Server/GeneratedArtifacts
|
||||
**/*.Server/ModelManifest.xml
|
||||
_Pvt_Extensions
|
||||
|
||||
# Paket dependency manager
|
||||
**/.paket/paket.exe
|
||||
paket-files/
|
||||
|
||||
# FAKE - F# Make
|
||||
**/.fake/
|
||||
|
||||
# CodeRush personal settings
|
||||
**/.cr/personal
|
||||
|
||||
# Python Tools for Visual Studio (PTVS)
|
||||
**/__pycache__/
|
||||
*.pyc
|
||||
|
||||
# Cake - Uncomment if you are using it
|
||||
#tools/**
|
||||
#!tools/packages.config
|
||||
|
||||
# Tabs Studio
|
||||
*.tss
|
||||
|
||||
# Telerik's JustMock configuration file
|
||||
*.jmconfig
|
||||
|
||||
# BizTalk build output
|
||||
*.btp.cs
|
||||
*.btm.cs
|
||||
*.odx.cs
|
||||
*.xsd.cs
|
||||
|
||||
# OpenCover UI analysis results
|
||||
OpenCover/
|
||||
|
||||
# Azure Stream Analytics local run output
|
||||
ASALocalRun/
|
||||
|
||||
# MSBuild Binary and Structured Log
|
||||
*.binlog
|
||||
MSBuild_Logs/
|
||||
|
||||
# AWS SAM Build and Temporary Artifacts folder
|
||||
.aws-sam
|
||||
|
||||
# NVidia Nsight GPU debugger configuration file
|
||||
*.nvuser
|
||||
|
||||
# MFractors (Xamarin productivity tool) working folder
|
||||
**/.mfractor/
|
||||
|
||||
# Local History for Visual Studio
|
||||
**/.localhistory/
|
||||
|
||||
# Visual Studio History (VSHistory) files
|
||||
.vshistory/
|
||||
|
||||
# BeatPulse healthcheck temp database
|
||||
healthchecksdb
|
||||
|
||||
# Backup folder for Package Reference Convert tool in Visual Studio 2017
|
||||
MigrationBackup/
|
||||
|
||||
# Ionide (cross platform F# VS Code tools) working folder
|
||||
**/.ionide/
|
||||
|
||||
# Fody - auto-generated XML schema
|
||||
FodyWeavers.xsd
|
||||
|
||||
# VS Code files for those working on multiple tools
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
!.vscode/*.code-snippets
|
||||
|
||||
# Local History for Visual Studio Code
|
||||
.history/
|
||||
|
||||
# Built Visual Studio Code Extensions
|
||||
*.vsix
|
||||
|
||||
# Windows Installer files from build outputs
|
||||
*.cab
|
||||
*.msi
|
||||
*.msix
|
||||
*.msm
|
||||
*.msp
|
||||
|
||||
# CMake User Overrides
|
||||
CMakeUserPresets.json
|
||||
68
.gitmodules
vendored
Normal file
68
.gitmodules
vendored
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
[submodule "thirdparty/libmspack"]
|
||||
path = thirdparty/libmspack
|
||||
url = https://github.com/kyz/libmspack.git
|
||||
[submodule "thirdparty/glslang"]
|
||||
path = thirdparty/glslang
|
||||
url = https://github.com/KhronosGroup/glslang.git
|
||||
[submodule "thirdparty/FFmpeg"]
|
||||
path = thirdparty/FFmpeg
|
||||
url = https://github.com/wmarti/FFmpeg.git
|
||||
branch = xenia-ffmpeg-canary-full
|
||||
[submodule "thirdparty/tomlplusplus"]
|
||||
path = thirdparty/tomlplusplus
|
||||
url = https://github.com/marzer/tomlplusplus.git
|
||||
[submodule "thirdparty/simde"]
|
||||
path = thirdparty/simde
|
||||
url = https://github.com/simd-everywhere/simde.git
|
||||
[submodule "thirdparty/xxHash"]
|
||||
path = thirdparty/xxHash
|
||||
url = https://github.com/Cyan4973/xxHash.git
|
||||
[submodule "thirdparty/spdlog"]
|
||||
path = thirdparty/spdlog
|
||||
url = https://github.com/gabime/spdlog.git
|
||||
[submodule "thirdparty/fmt"]
|
||||
path = thirdparty/fmt
|
||||
url = https://github.com/fmtlib/fmt.git
|
||||
[submodule "thirdparty/catch2"]
|
||||
path = thirdparty/catch2
|
||||
url = https://github.com/catchorg/Catch2.git
|
||||
[submodule "thirdparty/snappy"]
|
||||
path = thirdparty/snappy
|
||||
url = https://github.com/google/snappy.git
|
||||
[submodule "thirdparty/utfcpp"]
|
||||
path = thirdparty/utfcpp
|
||||
url = https://github.com/nemtrif/utfcpp.git
|
||||
[submodule "thirdparty/volk"]
|
||||
path = thirdparty/volk
|
||||
url = https://github.com/zeux/volk.git
|
||||
[submodule "thirdparty/vulkan-headers"]
|
||||
path = thirdparty/vulkan-headers
|
||||
url = https://github.com/KhronosGroup/Vulkan-Headers.git
|
||||
[submodule "thirdparty/vulkan-memory-allocator"]
|
||||
path = thirdparty/vulkan-memory-allocator
|
||||
url = https://github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator.git
|
||||
[submodule "thirdparty/imgui"]
|
||||
path = thirdparty/imgui
|
||||
url = https://github.com/ocornut/imgui.git
|
||||
[submodule "thirdparty/spirv-tools"]
|
||||
path = thirdparty/spirv-tools
|
||||
url = https://github.com/KhronosGroup/SPIRV-Tools.git
|
||||
[submodule "thirdparty/spirv-headers"]
|
||||
path = thirdparty/spirv-headers
|
||||
url = https://github.com/KhronosGroup/SPIRV-Headers.git
|
||||
[submodule "thirdparty/cli11"]
|
||||
path = thirdparty/cli11
|
||||
url = https://github.com/CLIUtils/CLI11
|
||||
[submodule "thirdparty/o1heap"]
|
||||
path = thirdparty/o1heap
|
||||
url = https://github.com/pavel-kirienko/o1heap.git
|
||||
[submodule "thirdparty/sdl3"]
|
||||
path = thirdparty/sdl3
|
||||
url = https://github.com/libsdl-org/SDL
|
||||
branch = release-3.4.x
|
||||
[submodule "thirdparty/inja"]
|
||||
path = thirdparty/inja
|
||||
url = https://github.com/pantor/inja.git
|
||||
[submodule "thirdparty/tracy"]
|
||||
path = thirdparty/tracy
|
||||
url = https://github.com/wolfpld/tracy.git
|
||||
243
CMakeLists.txt
Normal file
243
CMakeLists.txt
Normal file
|
|
@ -0,0 +1,243 @@
|
|||
# ReXGlue SDK - Xbox 360 Recompilation Toolkit
|
||||
# Modern C++23 cross-platform SDK for recompiling Xbox 360 executables
|
||||
cmake_minimum_required(VERSION 3.25)
|
||||
|
||||
project(ReXGlue
|
||||
VERSION 0.8.0
|
||||
LANGUAGES C CXX
|
||||
DESCRIPTION "Xbox 360 Recompilation SDK"
|
||||
)
|
||||
# Floor only: bump MAJOR.MINOR for API changes. Patch is derived from git tags
|
||||
# in cmake/rex_version.cmake. See docs/RELEASING.md.
|
||||
|
||||
#==========================================================
|
||||
# Build Options
|
||||
#==========================================================
|
||||
option(REXGLUE_BUILD_TESTS "Build PPC instruction and unit tests" OFF)
|
||||
option(REXGLUE_ENABLE_SANITIZERS "Enable UndefinedBehaviorSanitizer" OFF)
|
||||
option(REXGLUE_ENABLE_FIDELITYFX "Enable optional AMD FidelityFX integration (experimental)" OFF)
|
||||
option(REXGLUE_ENABLE_TRACY "Enable Tracy profiler integration" ON)
|
||||
option(REXGLUE_ENABLE_PERF_COUNTERS "Enable lightweight perf counter registry and overlay" ON)
|
||||
# Propagate profiling defines globally (compiled out in Release)
|
||||
if(REXGLUE_ENABLE_TRACY)
|
||||
add_compile_definitions($<$<NOT:$<CONFIG:Release>>:REXGLUE_ENABLE_PROFILING>)
|
||||
endif()
|
||||
if(REXGLUE_ENABLE_PERF_COUNTERS)
|
||||
add_compile_definitions($<$<NOT:$<CONFIG:Release>>:REXGLUE_ENABLE_PERF_COUNTERS>)
|
||||
endif()
|
||||
|
||||
# Graphics backend selection
|
||||
# D3D12: Windows only, ON by default.
|
||||
# Vulkan: Cross-platform, ON by default on Linux, OFF by default on Windows.
|
||||
if(WIN32)
|
||||
option(REXGLUE_USE_D3D12 "Enable D3D12 graphics backend" ON)
|
||||
option(REXGLUE_USE_VULKAN "Enable Vulkan graphics backend" OFF)
|
||||
else()
|
||||
set(REXGLUE_USE_D3D12 OFF)
|
||||
option(REXGLUE_USE_VULKAN "Enable Vulkan graphics backend" ON)
|
||||
endif()
|
||||
|
||||
if(NOT REXGLUE_USE_D3D12 AND NOT REXGLUE_USE_VULKAN)
|
||||
message(FATAL_ERROR "At least one graphics backend must be enabled (REXGLUE_USE_D3D12 or REXGLUE_USE_VULKAN)")
|
||||
endif()
|
||||
|
||||
if (REXGLUE_ENABLE_FIDELITYFX AND
|
||||
NOT (REXGLUE_USE_VULKAN OR REXGLUE_USE_D3D12))
|
||||
message(FATAL_ERROR
|
||||
"REXGLUE_ENABLE_FIDELITYFX requires at least one graphics backend")
|
||||
endif()
|
||||
|
||||
# Root of the SDK source tree
|
||||
set(REXGLUE_ROOT "${CMAKE_CURRENT_SOURCE_DIR}")
|
||||
|
||||
# SDK install support
|
||||
include(GNUInstallDirs)
|
||||
include(CMakePackageConfigHelpers)
|
||||
|
||||
if(UNIX AND NOT APPLE)
|
||||
# Keep installed binaries self-contained by resolving shared libs relative
|
||||
# to the install prefix (for example, bin -> ../lib).
|
||||
set(CMAKE_BUILD_RPATH_USE_ORIGIN ON)
|
||||
list(APPEND CMAKE_INSTALL_RPATH
|
||||
"$ORIGIN"
|
||||
"$ORIGIN/../${CMAKE_INSTALL_LIBDIR}"
|
||||
)
|
||||
set(CMAKE_INSTALL_RPATH_USE_LINK_PATH ON)
|
||||
endif()
|
||||
|
||||
# Enforce Clang18 compiler
|
||||
if(NOT CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
|
||||
message(FATAL_ERROR "ReXGlue requires Clang compiler. Please set CMAKE_CXX_COMPILER to clang++")
|
||||
endif()
|
||||
|
||||
if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS "18.0")
|
||||
message(FATAL_ERROR "ReXGlue requires Clang 18 or newer (found ${CMAKE_CXX_COMPILER_VERSION})")
|
||||
endif()
|
||||
|
||||
# Enforce x64 architecture
|
||||
if(NOT CMAKE_SIZEOF_VOID_P EQUAL 8)
|
||||
message(FATAL_ERROR "ReXGlue only supports 64‑bit architectures (x86_64 or ARM64)")
|
||||
endif()
|
||||
|
||||
# Set C++23 standard
|
||||
set(CMAKE_CXX_STANDARD 23)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_CXX_EXTENSIONS OFF)
|
||||
set(CMAKE_DEBUG_POSTFIX d)
|
||||
set(CMAKE_RELWITHDEBINFO_POSTFIX rd)
|
||||
|
||||
if(NOT WIN32)
|
||||
# Use appropriate code model based on architecture
|
||||
if(CMAKE_SYSTEM_PROCESSOR MATCHES "AMD64|x86_64")
|
||||
# Large code model on x86_64 for linking with very large recompiled executables (35MB+)
|
||||
add_compile_options(-mcmodel=large)
|
||||
else()
|
||||
# Small code model for other architectures (ARM64, etc.)
|
||||
add_compile_options(-mcmodel=small)
|
||||
endif()
|
||||
# Position-independent code for all targets
|
||||
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
|
||||
endif()
|
||||
|
||||
# Global compile options
|
||||
add_compile_options(
|
||||
-fno-strict-aliasing
|
||||
-ffp-model=strict
|
||||
$<$<CONFIG:DEBUG>:-g>
|
||||
$<$<CONFIG:DEBUG>:-O0>
|
||||
$<$<CONFIG:RELEASE>:-O3>
|
||||
$<$<CONFIG:RELEASE>:-DNDEBUG>
|
||||
-fno-char8_t
|
||||
)
|
||||
|
||||
# Platform detection (supports AMD64 and ARM64)
|
||||
if(WIN32)
|
||||
if(CMAKE_SYSTEM_PROCESSOR MATCHES "AMD64|x86_64")
|
||||
set(REX_PLATFORM "win-amd64")
|
||||
elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64|ARM64")
|
||||
set(REX_PLATFORM "win-arm64")
|
||||
else()
|
||||
message(FATAL_ERROR "Unsupported Windows architecture: ${CMAKE_SYSTEM_PROCESSOR}")
|
||||
endif()
|
||||
add_compile_definitions(REX_PLATFORM_WINDOWS=1)
|
||||
elseif(UNIX AND NOT APPLE)
|
||||
if(CMAKE_SYSTEM_PROCESSOR MATCHES "AMD64|x86_64")
|
||||
set(REX_PLATFORM "linux-amd64")
|
||||
elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64|ARM64")
|
||||
set(REX_PLATFORM "linux-arm64")
|
||||
else()
|
||||
set(REX_PLATFORM "linux-${CMAKE_SYSTEM_PROCESSOR}")
|
||||
endif()
|
||||
add_compile_definitions(REX_PLATFORM_LINUX=1)
|
||||
else()
|
||||
message(FATAL_ERROR "Unsupported platform. ReXGlue supports Windows and Linux only.")
|
||||
endif()
|
||||
|
||||
# Build config as compile definition (multi-config generators don't know config at configure time)
|
||||
add_compile_definitions(REXGLUE_BUILD_CONFIG="$<CONFIG>")
|
||||
|
||||
include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/rex_version.cmake)
|
||||
rex_resolve_version(REXGLUE_FULL_VERSION
|
||||
FLOOR_MAJOR ${PROJECT_VERSION_MAJOR}
|
||||
FLOOR_MINOR ${PROJECT_VERSION_MINOR}
|
||||
SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
|
||||
# Strip the -id trailer (if any) for the numeric-only version.
|
||||
string(REGEX REPLACE "-.*$" "" REXGLUE_NUMERIC_VERSION "${REXGLUE_FULL_VERSION}")
|
||||
|
||||
# Parse up to 4 numeric components for consumers that want integer access.
|
||||
string(REGEX MATCH "^([0-9]+)\\.([0-9]+)(\\.([0-9]+))?(\\.([0-9]+))?" _ "${REXGLUE_NUMERIC_VERSION}")
|
||||
set(REXGLUE_DERIVED_MAJOR "${CMAKE_MATCH_1}")
|
||||
set(REXGLUE_DERIVED_MINOR "${CMAKE_MATCH_2}")
|
||||
if(CMAKE_MATCH_4)
|
||||
set(REXGLUE_DERIVED_PATCH "${CMAKE_MATCH_4}")
|
||||
else()
|
||||
set(REXGLUE_DERIVED_PATCH "0")
|
||||
endif()
|
||||
if(CMAKE_MATCH_6)
|
||||
set(REXGLUE_DERIVED_TWEAK "${CMAKE_MATCH_6}")
|
||||
else()
|
||||
set(REXGLUE_DERIVED_TWEAK "0")
|
||||
endif()
|
||||
|
||||
# Generate version header -- must run after REX_PLATFORM is set
|
||||
# Timestamp is set at configure time -- intentionally not updated on every rebuild
|
||||
string(TIMESTAMP REXGLUE_BUILD_TIMESTAMP "%Y%m%d_%H%M")
|
||||
configure_file(
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/rex/version.h.in
|
||||
${CMAKE_CURRENT_BINARY_DIR}/include/rex/version.h
|
||||
@ONLY
|
||||
)
|
||||
|
||||
# Output directories (multi-config appends /<Config>/ automatically)
|
||||
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${REXGLUE_ROOT}/out/${REX_PLATFORM}")
|
||||
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${REXGLUE_ROOT}/out/${REX_PLATFORM}")
|
||||
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${REXGLUE_ROOT}/out/${REX_PLATFORM}")
|
||||
|
||||
# Sanitizer support for debugging memory corruption and undefined behavior
|
||||
if(REXGLUE_ENABLE_SANITIZERS)
|
||||
message(STATUS "Enabling UndefinedBehaviorSanitizer (UBSan only - ASan conflicts with custom memory mapping)")
|
||||
# Note: ASan doesn't work well with the custom mmap at 0x100000000, so we only use UBSan
|
||||
add_compile_options(-fsanitize=undefined -fno-omit-frame-pointer -g)
|
||||
add_link_options(-fsanitize=undefined)
|
||||
endif()
|
||||
|
||||
if(REXGLUE_BUILD_TESTS)
|
||||
list(APPEND CMAKE_MODULE_PATH "${REXGLUE_ROOT}/cmake")
|
||||
list(APPEND CMAKE_MODULE_PATH "${REXGLUE_ROOT}/thirdparty/catch2/extras")
|
||||
endif()
|
||||
|
||||
# Third-party dependencies (vendored sources only)
|
||||
add_subdirectory(thirdparty)
|
||||
|
||||
# Enable warnings after thirdparty
|
||||
add_compile_options(
|
||||
-Wall
|
||||
-Wextra
|
||||
)
|
||||
|
||||
include_directories(${REXGLUE_ROOT}/include)
|
||||
|
||||
add_subdirectory(src/core)
|
||||
add_subdirectory(src/filesystem)
|
||||
add_subdirectory(src/ui)
|
||||
add_subdirectory(src/input)
|
||||
add_subdirectory(src/audio)
|
||||
add_subdirectory(src/graphics)
|
||||
add_subdirectory(src/system)
|
||||
add_subdirectory(src/kernel)
|
||||
add_subdirectory(src/codegen)
|
||||
add_subdirectory(src/rexglue)
|
||||
|
||||
# Add tests subdirectory if tests are enabled
|
||||
if(REXGLUE_BUILD_TESTS)
|
||||
enable_testing()
|
||||
add_subdirectory(tests)
|
||||
endif()
|
||||
|
||||
# Print configuration summary
|
||||
message(STATUS "===========================================")
|
||||
message(STATUS "ReXGlue Configuration Summary:")
|
||||
message(STATUS " Version: ${REXGLUE_FULL_VERSION}")
|
||||
message(STATUS " Platform: ${REX_PLATFORM}")
|
||||
message(STATUS " Compiler: ${CMAKE_CXX_COMPILER_ID} ${CMAKE_CXX_COMPILER_VERSION}")
|
||||
message(STATUS " Configurations: ${CMAKE_CONFIGURATION_TYPES}")
|
||||
message(STATUS " Output Dir: ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}")
|
||||
message(STATUS " C++ Standard: C++${CMAKE_CXX_STANDARD}")
|
||||
message(STATUS " Graphics: D3D12=${REXGLUE_USE_D3D12} Vulkan=${REXGLUE_USE_VULKAN}")
|
||||
message(STATUS " FidelityFX: ${REXGLUE_ENABLE_FIDELITYFX}")
|
||||
message(STATUS " Tracy: ${REXGLUE_ENABLE_TRACY}")
|
||||
message(STATUS " Perf Counters: ${REXGLUE_ENABLE_PERF_COUNTERS}")
|
||||
message(STATUS "===========================================")
|
||||
|
||||
# SDK install/export rules (skipped when consumed as a subdirectory)
|
||||
if(PROJECT_IS_TOP_LEVEL)
|
||||
include(cmake/rexglue_devenv.cmake)
|
||||
include(cmake/rexglue_install.cmake)
|
||||
else()
|
||||
# When consumed via add_subdirectory(), provide the same interface as find_package().
|
||||
# This sets REXGLUE_SHARE_DIR and makes rexglue_configure_target() available.
|
||||
set(REXGLUE_SHARE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/src/ui" PARENT_SCOPE)
|
||||
set(REXGLUE_SHARE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/src/ui")
|
||||
include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/rexglue_helpers.cmake)
|
||||
endif()
|
||||
222
CMakePresets.json
Normal file
222
CMakePresets.json
Normal file
|
|
@ -0,0 +1,222 @@
|
|||
{
|
||||
"version": 6,
|
||||
"cmakeMinimumRequired": {
|
||||
"major": 3,
|
||||
"minor": 25,
|
||||
"patch": 0
|
||||
},
|
||||
"configurePresets": [
|
||||
{
|
||||
"name": "windows-base",
|
||||
"hidden": true,
|
||||
"generator": "Ninja Multi-Config",
|
||||
"binaryDir": "${sourceDir}/out/build/${presetName}",
|
||||
"cacheVariables": {
|
||||
"CMAKE_C_COMPILER": "clang",
|
||||
"CMAKE_CXX_COMPILER": "clang++",
|
||||
"CMAKE_C_FLAGS": "-march=x86-64-v3",
|
||||
"CMAKE_CXX_FLAGS": "-march=x86-64-v3",
|
||||
"CMAKE_CXX_STANDARD": "23",
|
||||
"CMAKE_CONFIGURATION_TYPES": "Debug;Release;RelWithDebInfo",
|
||||
"CMAKE_CROSS_CONFIGS": "all",
|
||||
"CMAKE_DEFAULT_BUILD_TYPE": "Debug",
|
||||
"CMAKE_DEFAULT_CONFIGS": "all",
|
||||
"CMAKE_INSTALL_PREFIX": "${sourceDir}/out/install/${presetName}"
|
||||
},
|
||||
"condition": {
|
||||
"type": "equals",
|
||||
"lhs": "${hostSystemName}",
|
||||
"rhs": "Windows"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "linux-base",
|
||||
"hidden": true,
|
||||
"generator": "Ninja Multi-Config",
|
||||
"binaryDir": "${sourceDir}/out/build/${presetName}",
|
||||
"cacheVariables": {
|
||||
"CMAKE_C_COMPILER": "clang",
|
||||
"CMAKE_CXX_COMPILER": "clang++",
|
||||
"CMAKE_C_FLAGS": "-march=x86-64-v3",
|
||||
"CMAKE_CXX_FLAGS": "-march=x86-64-v3",
|
||||
"CMAKE_CXX_STANDARD": "23",
|
||||
"CMAKE_CONFIGURATION_TYPES": "Debug;Release;RelWithDebInfo",
|
||||
"CMAKE_CROSS_CONFIGS": "all",
|
||||
"CMAKE_DEFAULT_BUILD_TYPE": "Debug",
|
||||
"CMAKE_DEFAULT_CONFIGS": "all",
|
||||
"CMAKE_INSTALL_PREFIX": "${sourceDir}/out/install/${presetName}"
|
||||
},
|
||||
"condition": {
|
||||
"type": "equals",
|
||||
"lhs": "${hostSystemName}",
|
||||
"rhs": "Linux"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "windows-arm64-base",
|
||||
"hidden": true,
|
||||
"inherits": "windows-base",
|
||||
"cacheVariables": {
|
||||
"CMAKE_C_FLAGS": "-march=armv8-a",
|
||||
"CMAKE_CXX_FLAGS": "-march=armv8-a"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "linux-arm64-base",
|
||||
"hidden": true,
|
||||
"inherits": "linux-base",
|
||||
"cacheVariables": {
|
||||
"CMAKE_C_FLAGS": "-march=armv8-a",
|
||||
"CMAKE_CXX_FLAGS": "-march=armv8-a"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "win-amd64",
|
||||
"displayName": "Windows AMD64",
|
||||
"description": "Multi-config build for Windows AMD64 using Clang",
|
||||
"inherits": "windows-base"
|
||||
},
|
||||
{
|
||||
"name": "win-arm64",
|
||||
"displayName": "Windows ARM64",
|
||||
"description": "Multi-config build for Windows ARM64 using Clang",
|
||||
"inherits": "windows-arm64-base"
|
||||
},
|
||||
{
|
||||
"name": "linux-amd64",
|
||||
"displayName": "Linux AMD64",
|
||||
"description": "Multi-config build for Linux AMD64 using Clang",
|
||||
"inherits": "linux-base"
|
||||
},
|
||||
{
|
||||
"name": "linux-arm64",
|
||||
"displayName": "Linux ARM64",
|
||||
"description": "Multi-config build for Linux ARM64 using Clang",
|
||||
"inherits": "linux-arm64-base"
|
||||
}
|
||||
],
|
||||
"buildPresets": [
|
||||
{
|
||||
"name": "win-amd64-debug",
|
||||
"configurePreset": "win-amd64",
|
||||
"configuration": "Debug"
|
||||
},
|
||||
{
|
||||
"name": "win-amd64-release",
|
||||
"configurePreset": "win-amd64",
|
||||
"configuration": "Release"
|
||||
},
|
||||
{
|
||||
"name": "win-amd64-relwithdebinfo",
|
||||
"configurePreset": "win-amd64",
|
||||
"configuration": "RelWithDebInfo"
|
||||
},
|
||||
{
|
||||
"name": "win-arm64-debug",
|
||||
"configurePreset": "win-arm64",
|
||||
"configuration": "Debug"
|
||||
},
|
||||
{
|
||||
"name": "win-arm64-release",
|
||||
"configurePreset": "win-arm64",
|
||||
"configuration": "Release"
|
||||
},
|
||||
{
|
||||
"name": "win-arm64-relwithdebinfo",
|
||||
"configurePreset": "win-arm64",
|
||||
"configuration": "RelWithDebInfo"
|
||||
},
|
||||
{
|
||||
"name": "linux-amd64-debug",
|
||||
"configurePreset": "linux-amd64",
|
||||
"configuration": "Debug"
|
||||
},
|
||||
{
|
||||
"name": "linux-amd64-release",
|
||||
"configurePreset": "linux-amd64",
|
||||
"configuration": "Release"
|
||||
},
|
||||
{
|
||||
"name": "linux-amd64-relwithdebinfo",
|
||||
"configurePreset": "linux-amd64",
|
||||
"configuration": "RelWithDebInfo"
|
||||
},
|
||||
{
|
||||
"name": "linux-arm64-debug",
|
||||
"configurePreset": "linux-arm64",
|
||||
"configuration": "Debug"
|
||||
},
|
||||
{
|
||||
"name": "linux-arm64-release",
|
||||
"configurePreset": "linux-arm64",
|
||||
"configuration": "Release"
|
||||
},
|
||||
{
|
||||
"name": "linux-arm64-relwithdebinfo",
|
||||
"configurePreset": "linux-arm64",
|
||||
"configuration": "RelWithDebInfo"
|
||||
}
|
||||
],
|
||||
"testPresets": [
|
||||
{
|
||||
"name": "win-amd64-debug",
|
||||
"configurePreset": "win-amd64",
|
||||
"configuration": "Debug"
|
||||
},
|
||||
{
|
||||
"name": "win-amd64-release",
|
||||
"configurePreset": "win-amd64",
|
||||
"configuration": "Release"
|
||||
},
|
||||
{
|
||||
"name": "win-amd64-relwithdebinfo",
|
||||
"configurePreset": "win-amd64",
|
||||
"configuration": "RelWithDebInfo"
|
||||
},
|
||||
{
|
||||
"name": "win-arm64-debug",
|
||||
"configurePreset": "win-arm64",
|
||||
"configuration": "Debug"
|
||||
},
|
||||
{
|
||||
"name": "win-arm64-release",
|
||||
"configurePreset": "win-arm64",
|
||||
"configuration": "Release"
|
||||
},
|
||||
{
|
||||
"name": "win-arm64-relwithdebinfo",
|
||||
"configurePreset": "win-arm64",
|
||||
"configuration": "RelWithDebInfo"
|
||||
},
|
||||
{
|
||||
"name": "linux-amd64-debug",
|
||||
"configurePreset": "linux-amd64",
|
||||
"configuration": "Debug"
|
||||
},
|
||||
{
|
||||
"name": "linux-amd64-release",
|
||||
"configurePreset": "linux-amd64",
|
||||
"configuration": "Release"
|
||||
},
|
||||
{
|
||||
"name": "linux-amd64-relwithdebinfo",
|
||||
"configurePreset": "linux-amd64",
|
||||
"configuration": "RelWithDebInfo"
|
||||
},
|
||||
{
|
||||
"name": "linux-arm64-debug",
|
||||
"configurePreset": "linux-arm64",
|
||||
"configuration": "Debug"
|
||||
},
|
||||
{
|
||||
"name": "linux-arm64-release",
|
||||
"configurePreset": "linux-arm64",
|
||||
"configuration": "Release"
|
||||
},
|
||||
{
|
||||
"name": "linux-arm64-relwithdebinfo",
|
||||
"configurePreset": "linux-arm64",
|
||||
"configuration": "RelWithDebInfo"
|
||||
}
|
||||
]
|
||||
}
|
||||
9
CONTRIBUTING.md
Normal file
9
CONTRIBUTING.md
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
# Contributing
|
||||
|
||||
See the [Contributing Guide](https://github.com/rexglue/rexglue-sdk/wiki/Development/Contributing) in the wiki for:
|
||||
|
||||
- Build prerequisites and setup
|
||||
- Code style conventions
|
||||
- Formatting and linting instructions
|
||||
- Git setup (line endings, rebasing)
|
||||
- PR submission workflow
|
||||
32
LICENSE
Normal file
32
LICENSE
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
Copyright (c) 2026, Tom Clay <tomc@tctechstuff.com>
|
||||
|
||||
Portions of this software are derived from the Xenia project:
|
||||
Copyright (c) 2022, Ben Vanik and Xenia project contributors
|
||||
https://xenia.jp
|
||||
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
60
README.md
Normal file
60
README.md
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
> [!CAUTION]
|
||||
> This project is in early development. Expect things to not work quite right and there to be significant changes and breaking public API updates as development progresses. Contributions and feedback are welcome, but please be aware that the codebase is still evolving rapidly.
|
||||
|
||||
<h1 align="center">
|
||||
<br>
|
||||
<a href="https://github.com/rexglue/rexglue-sdk">
|
||||
<img src="https://github.com/rexglue/rexglue-media/blob/main/ReX_Banner.png" alt="ReXGlue banner">
|
||||
</a>
|
||||
<br>
|
||||
<br>
|
||||
<a href="https://discord.gg/CNTxwSNZfT">
|
||||
<img src="https://img.shields.io/badge/Discord-Join%20Server-5865F2?logo=discord&logoColor=white" alt="Discord">
|
||||
</a>
|
||||
<a href="https://github.com/rexglue/rexglue-sdk/stargazers">
|
||||
<img src="https://img.shields.io/github/stars/rexglue/rexglue-sdk" alt="rexglue-sdk stargazers">
|
||||
</a>
|
||||
</h1>
|
||||
|
||||
ReXGlue converts Xbox 360 PowerPC code into portable C++ that runs natively on modern platforms.
|
||||
|
||||
ReXGlue is heavily rooted on the foundations of [Xenia](https://github.com/xenia-project), the Xbox 360 emulator. Rather than interpreting or JIT-compiling PPC instructions at runtime, ReXGlue takes a different path: it generates C++ source code ahead of time, an approach inspired by [XenonRecomp](https://github.com/hedge-dev/XenonRecomp) and [rexdex's recompiler](https://github.com/rexdex/recompiler).
|
||||
|
||||
Latest SDK builds and releases are published on [GitHub Releases](https://github.com/rexglue/rexglue-sdk/releases). Join the [Discord server](https://discord.gg/CNTxwSNZfT) for updates and share what you have created.
|
||||
|
||||
## Builds
|
||||
|
||||
| Channel | CI | Download |
|
||||
| --- | --- | --- |
|
||||
| Release | [](https://github.com/rexglue/rexglue-sdk/actions/workflows/build-win-amd64.yaml) [](https://github.com/rexglue/rexglue-sdk/actions/workflows/build-linux-amd64.yaml) [](https://github.com/rexglue/rexglue-sdk/actions/workflows/build-linux-aarch64.yaml) | [Latest stable](https://github.com/rexglue/rexglue-sdk/releases/latest) |
|
||||
| Nightly | [](https://github.com/rexglue/rexglue-sdk/actions/workflows/nightly.yaml) | [Latest pre-release](https://github.com/rexglue/rexglue-sdk/releases?q=prerelease%3Atrue) |
|
||||
|
||||
## Quickstart
|
||||
|
||||
For quick start guide, full CLI reference, and config file options, see the [wiki](https://github.com/rexglue/rexglue-sdk/wiki).
|
||||
|
||||
# **Disclaimer**
|
||||
ReXGlue is not affiliated with nor endorsed by Microsoft or Xbox. It is an independent project created for educational and development purposes. All trademarks and copyrights belong to their respective owners.
|
||||
|
||||
This project is not intended to promote piracy nor unauthorized use of copyrighted material. Any misuse of this software to endorse or enable this type of activity is strictly prohibited.
|
||||
|
||||
|
||||
# Credits
|
||||
|
||||
## ReXGlue
|
||||
- [Tom (crack)](https://github.com/tomcl7) - Project Founder
|
||||
- [Loreaxe](https://github.com/Loreaxe) - Linux Contributor
|
||||
- [mystixor](https://github.com/Mystixor) - Windows Contributor
|
||||
- [Graine25](https://github.com/Graine25) - Project Support
|
||||
- [Carlos Estrague (mrcmunir)](https://github.com/mrcmunir) - Linux / ARM64 Contributor
|
||||
- [sanjay900](https://github.com/sanjay900) - Linux / SDL Contributor
|
||||
- [Toby](https://github.com/TbyDtch) - Project Support
|
||||
- [Roxxsen](https://github.com/Roxxsen) - CI/CD Contributor
|
||||
|
||||
The list above is not exhaustive. Thanks to everyone in the ReXGlue community who contributes code, files issues, tests builds, and keeps the project moving.
|
||||
|
||||
## Very Special Thank You:
|
||||
- [Project Xenia](https://github.com/xenia-project/xenia/tree/master/src/xenia) - Their invaluable work on Xbox 360 emulation laid the groundwork for ReXGlue's development. This project (and numerous others) would not exist without their hard work and dedication.
|
||||
- [XenonRecomp](https://github.com/hedge-dev/XenonRecomp) - For pioneering the modern static recompilation approach for Xbox 360. A lot of the codegen analysis logic and instruction translations are based on their work. Thank you!
|
||||
- [rexdex's recompiler](https://github.com/rexdex/recompiler) - The OG static recompiler for Xbox 360.
|
||||
- Many others in the Xbox 360 homebrew and modding communities whose work and research have contributed to the collective knowledge that makes projects like this possible.
|
||||
58
cmake/embed_templates.cmake
Normal file
58
cmake/embed_templates.cmake
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
if(NOT DEFINED TEMPLATE_SOURCE_DIR OR NOT DEFINED TEMPLATE_OUTPUT_DIR)
|
||||
message(FATAL_ERROR "embed_templates.cmake requires -DTEMPLATE_SOURCE_DIR and -DTEMPLATE_OUTPUT_DIR")
|
||||
endif()
|
||||
|
||||
file(GLOB_RECURSE INJA_TEMPLATE_FILES "${TEMPLATE_SOURCE_DIR}/*.inja")
|
||||
|
||||
set(FORBIDDEN_DELIMITER ")__TMPL__\"")
|
||||
|
||||
set(EMBEDDED_INCLUDES "")
|
||||
set(EMBEDDED_MAP_ENTRIES "")
|
||||
|
||||
foreach(TEMPLATE_FILE ${INJA_TEMPLATE_FILES})
|
||||
file(READ "${TEMPLATE_FILE}" TEMPLATE_CONTENT)
|
||||
|
||||
string(FIND "${TEMPLATE_CONTENT}" "${FORBIDDEN_DELIMITER}" DELIMITER_POS)
|
||||
if(NOT DELIMITER_POS EQUAL -1)
|
||||
message(FATAL_ERROR
|
||||
"Template file contains forbidden raw string delimiter ')__TMPL__\"':\n"
|
||||
" ${TEMPLATE_FILE}\n"
|
||||
"Please remove or rename this sequence in the template.")
|
||||
endif()
|
||||
|
||||
file(RELATIVE_PATH REL_PATH "${TEMPLATE_SOURCE_DIR}" "${TEMPLATE_FILE}")
|
||||
string(REGEX REPLACE "\\.inja$" "" CANONICAL_ID "${REL_PATH}")
|
||||
|
||||
string(REPLACE "/" "_" VAR_NAME "${CANONICAL_ID}")
|
||||
string(REPLACE "-" "_" VAR_NAME "${VAR_NAME}")
|
||||
|
||||
set(HEADER_FILE "${TEMPLATE_OUTPUT_DIR}/${VAR_NAME}.inja.h")
|
||||
file(WRITE "${HEADER_FILE}"
|
||||
"// Auto-generated from resources/templates/${REL_PATH} -- DO NOT EDIT\n"
|
||||
"#pragma once\n"
|
||||
"#include <string_view>\n\n"
|
||||
"namespace rex::codegen::embedded {\n"
|
||||
"inline constexpr std::string_view ${VAR_NAME} = R\"__TMPL__(${TEMPLATE_CONTENT})__TMPL__\";\n"
|
||||
"} // namespace rex::codegen::embedded\n"
|
||||
)
|
||||
|
||||
string(APPEND EMBEDDED_INCLUDES "#include \"${VAR_NAME}.inja.h\"\n")
|
||||
string(APPEND EMBEDDED_MAP_ENTRIES " {\"${CANONICAL_ID}\", embedded::${VAR_NAME}},\n")
|
||||
endforeach()
|
||||
|
||||
file(WRITE "${TEMPLATE_OUTPUT_DIR}/embedded_templates.h"
|
||||
"// Auto-generated master template index -- DO NOT EDIT\n"
|
||||
"#pragma once\n\n"
|
||||
"#include <string_view>\n"
|
||||
"#include <unordered_map>\n"
|
||||
"#include <string>\n\n"
|
||||
"${EMBEDDED_INCLUDES}\n"
|
||||
"namespace rex::codegen {\n\n"
|
||||
"inline const std::unordered_map<std::string, std::string_view>& embeddedTemplates() {\n"
|
||||
" static const std::unordered_map<std::string, std::string_view> map = {\n"
|
||||
"${EMBEDDED_MAP_ENTRIES}"
|
||||
" };\n"
|
||||
" return map;\n"
|
||||
"}\n\n"
|
||||
"} // namespace rex::codegen\n"
|
||||
)
|
||||
97
cmake/ppc_test_pipeline.cmake
Normal file
97
cmake/ppc_test_pipeline.cmake
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
# cmake/ppc_test_pipeline.cmake
|
||||
# PPC assembly test build pipeline
|
||||
# Provides ppc_add_test_binary() for assembling, linking, and generating
|
||||
# symbol maps from PPC assembly test files.
|
||||
|
||||
# Locate PPC toolchain in tools/ directory
|
||||
set(PPC_TOOLS_DIR "${PROJECT_SOURCE_DIR}/tools/binutils")
|
||||
|
||||
if(WIN32)
|
||||
set(PPC_ASSEMBLER "${PPC_TOOLS_DIR}/powerpc-none-elf-as.exe")
|
||||
set(PPC_LINKER "${PPC_TOOLS_DIR}/powerpc-none-elf-ld.exe")
|
||||
set(PPC_NM "${PPC_TOOLS_DIR}/powerpc-none-elf-nm.exe")
|
||||
else()
|
||||
set(PPC_ASSEMBLER "${PPC_TOOLS_DIR}/powerpc-none-elf-as")
|
||||
set(PPC_LINKER "${PPC_TOOLS_DIR}/powerpc-none-elf-ld")
|
||||
set(PPC_NM "${PPC_TOOLS_DIR}/powerpc-none-elf-nm")
|
||||
endif()
|
||||
|
||||
# Verify toolchain exists
|
||||
if(NOT EXISTS "${PPC_ASSEMBLER}")
|
||||
message(FATAL_ERROR "PPC assembler not found at ${PPC_ASSEMBLER}")
|
||||
endif()
|
||||
if(NOT EXISTS "${PPC_LINKER}")
|
||||
message(FATAL_ERROR "PPC linker not found at ${PPC_LINKER}")
|
||||
endif()
|
||||
|
||||
message(STATUS "PowerPC assembler: ${PPC_ASSEMBLER}")
|
||||
message(STATUS "PowerPC linker: ${PPC_LINKER}")
|
||||
|
||||
# Convert Windows paths to Cygwin paths for binutils (Windows only)
|
||||
function(_ppc_convert_to_cygwin_path WINDOWS_PATH OUTPUT_VAR)
|
||||
if(WIN32 AND WINDOWS_PATH MATCHES "^([A-Za-z]):")
|
||||
string(REGEX REPLACE "^([A-Za-z]):" "/cygdrive/\\1" CYGWIN_PATH "${WINDOWS_PATH}")
|
||||
string(SUBSTRING "${CYGWIN_PATH}" 10 1 DRIVE_LETTER)
|
||||
string(TOLOWER "${DRIVE_LETTER}" DRIVE_LOWER)
|
||||
string(REGEX REPLACE "^/cygdrive/[A-Za-z]" "/cygdrive/${DRIVE_LOWER}" CYGWIN_PATH "${CYGWIN_PATH}")
|
||||
string(REPLACE "\\" "/" CYGWIN_PATH "${CYGWIN_PATH}")
|
||||
set(${OUTPUT_VAR} "${CYGWIN_PATH}" PARENT_SCOPE)
|
||||
else()
|
||||
set(${OUTPUT_VAR} "${WINDOWS_PATH}" PARENT_SCOPE)
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
# Add a PPC test binary from an assembly file.
|
||||
# Assembles .s -> .o, links .o -> .bin, generates .o -> .map.
|
||||
# Appends output files to PPC_TEST_BINS, PPC_TEST_MAPS, PPC_TEST_OBJS
|
||||
# global properties.
|
||||
function(ppc_add_test_binary ASM_FILE OBJ_DIR BIN_DIR)
|
||||
get_filename_component(BASE_NAME ${ASM_FILE} NAME_WE)
|
||||
|
||||
set(OBJ_FILE ${OBJ_DIR}/${BASE_NAME}.o)
|
||||
set(BIN_FILE ${BIN_DIR}/${BASE_NAME}.bin)
|
||||
set(MAP_FILE ${BIN_DIR}/${BASE_NAME}.map)
|
||||
|
||||
_ppc_convert_to_cygwin_path("${OBJ_FILE}" OBJ_FILE_CYGWIN)
|
||||
_ppc_convert_to_cygwin_path("${BIN_FILE}" BIN_FILE_CYGWIN)
|
||||
_ppc_convert_to_cygwin_path("${MAP_FILE}" MAP_FILE_CYGWIN)
|
||||
_ppc_convert_to_cygwin_path("${ASM_FILE}" ASM_FILE_CYGWIN)
|
||||
|
||||
# Assemble .s to .o
|
||||
add_custom_command(
|
||||
OUTPUT ${OBJ_FILE}
|
||||
COMMAND ${PPC_ASSEMBLER}
|
||||
-a32 -be -mregnames -mpower7 -maltivec -mvsx -mvmx128 -R
|
||||
-o ${OBJ_FILE_CYGWIN}
|
||||
${ASM_FILE_CYGWIN}
|
||||
DEPENDS ${ASM_FILE}
|
||||
COMMENT "Assembling ${BASE_NAME}.s"
|
||||
VERBATIM
|
||||
)
|
||||
|
||||
# Link .o to .bin
|
||||
add_custom_command(
|
||||
OUTPUT ${BIN_FILE}
|
||||
COMMAND ${PPC_LINKER}
|
||||
-A powerpc:common32 -melf32ppc -EB -nostdlib
|
||||
--oformat=binary -Ttext=0x82010000 -e 0x82010000
|
||||
-o ${BIN_FILE_CYGWIN}
|
||||
${OBJ_FILE_CYGWIN}
|
||||
DEPENDS ${OBJ_FILE}
|
||||
COMMENT "Linking ${BASE_NAME}.bin"
|
||||
VERBATIM
|
||||
)
|
||||
|
||||
# Generate symbol map
|
||||
add_custom_command(
|
||||
OUTPUT ${MAP_FILE}
|
||||
COMMAND ${PPC_NM} --numeric-sort ${OBJ_FILE_CYGWIN} > ${MAP_FILE}
|
||||
DEPENDS ${OBJ_FILE}
|
||||
COMMENT "Generating symbol map for ${BASE_NAME}"
|
||||
VERBATIM
|
||||
)
|
||||
|
||||
set_property(GLOBAL APPEND PROPERTY PPC_TEST_BINS ${BIN_FILE})
|
||||
set_property(GLOBAL APPEND PROPERTY PPC_TEST_MAPS ${MAP_FILE})
|
||||
set_property(GLOBAL APPEND PROPERTY PPC_TEST_OBJS ${OBJ_FILE})
|
||||
endfunction()
|
||||
144
cmake/rex_version.cmake
Normal file
144
cmake/rex_version.cmake
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
#==========================================================
|
||||
# rex_compute_version(<out_var>
|
||||
# FLOOR_MAJOR <int>
|
||||
# FLOOR_MINOR <int>
|
||||
# GIT_DESCRIBE_LONG <string> # output of git describe --tags --long ...
|
||||
# GIT_DESCRIBE_EXACT <string> # output of git describe --tags --exact-match
|
||||
# BRANCH_NAME <string>) # output of git symbolic-ref --short HEAD
|
||||
#
|
||||
# Emits a CMake-style version string: MAJOR.MINOR[.PATCH[.TWEAK]][-id].
|
||||
# See https://cmake.org/cmake/help/latest/variable/CMAKE_VERSION.html
|
||||
#
|
||||
# Tagged commit (vX.Y[.Z[.W]]): emits the tag verbatim (without the v).
|
||||
# Untagged commit: emits MAJOR.<floor-minor>.<derived-patch>.<commit-count>-<id>
|
||||
# where id is "dev.gSHA" on any branch and "rc.gSHA" on a release/* branch.
|
||||
#==========================================================
|
||||
function(rex_compute_version out_var)
|
||||
set(one_value FLOOR_MAJOR FLOOR_MINOR GIT_DESCRIBE_LONG GIT_DESCRIBE_EXACT BRANCH_NAME)
|
||||
cmake_parse_arguments(ARG "" "${one_value}" "" ${ARGN})
|
||||
|
||||
if(NOT "${ARG_GIT_DESCRIBE_EXACT}" STREQUAL "")
|
||||
if(ARG_GIT_DESCRIBE_EXACT MATCHES "^v([0-9]+\\.[0-9]+(\\.[0-9]+)?(\\.[0-9]+)?)$")
|
||||
set(${out_var} "${CMAKE_MATCH_1}" PARENT_SCOPE)
|
||||
return()
|
||||
endif()
|
||||
message(FATAL_ERROR "rex_compute_version: unparseable exact tag '${ARG_GIT_DESCRIBE_EXACT}'")
|
||||
endif()
|
||||
|
||||
if("${ARG_GIT_DESCRIBE_LONG}" STREQUAL "")
|
||||
message(WARNING
|
||||
"rex_compute_version: no v* tag reachable from HEAD. "
|
||||
"Falling back to ${ARG_FLOOR_MAJOR}.${ARG_FLOOR_MINOR}.0.0-dev.unknown. "
|
||||
"If this is a CI build, ensure tags are fetched (fetch-depth: 0 + fetch-tags: true). "
|
||||
"If this is a fork, push tags with: git push <fork-remote> --tags")
|
||||
set(${out_var} "${ARG_FLOOR_MAJOR}.${ARG_FLOOR_MINOR}.0.0-dev.unknown" PARENT_SCOPE)
|
||||
return()
|
||||
endif()
|
||||
|
||||
if(NOT ARG_GIT_DESCRIBE_LONG MATCHES "^v([0-9]+)\\.([0-9]+)(\\.([0-9]+))?(\\.([0-9]+))?-([0-9]+)-g([0-9a-f]+)$")
|
||||
message(FATAL_ERROR "rex_compute_version: unparseable describe output '${ARG_GIT_DESCRIBE_LONG}'")
|
||||
endif()
|
||||
set(tag_minor ${CMAKE_MATCH_2})
|
||||
if(CMAKE_MATCH_4)
|
||||
set(tag_patch ${CMAKE_MATCH_4})
|
||||
else()
|
||||
set(tag_patch 0)
|
||||
endif()
|
||||
set(commit_count ${CMAKE_MATCH_7})
|
||||
set(short_sha ${CMAKE_MATCH_8})
|
||||
|
||||
# Floor minor must never go backwards.
|
||||
if(ARG_FLOOR_MINOR LESS tag_minor)
|
||||
message(FATAL_ERROR
|
||||
"rex_compute_version: floor minor (${ARG_FLOOR_MINOR}) is behind tag minor (${tag_minor}). "
|
||||
"The floor in CMakeLists.txt must not regress.")
|
||||
endif()
|
||||
|
||||
if(ARG_FLOOR_MINOR EQUAL tag_minor)
|
||||
math(EXPR derived_patch "${tag_patch} + 1")
|
||||
else()
|
||||
set(derived_patch 0)
|
||||
endif()
|
||||
|
||||
if(ARG_BRANCH_NAME MATCHES "^release/")
|
||||
set(id "rc.g${short_sha}")
|
||||
else()
|
||||
set(id "dev.g${short_sha}")
|
||||
endif()
|
||||
|
||||
set(${out_var} "${ARG_FLOOR_MAJOR}.${ARG_FLOOR_MINOR}.${derived_patch}.${commit_count}-${id}" PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
#==========================================================
|
||||
# rex_resolve_version(<out_var>
|
||||
# FLOOR_MAJOR <int>
|
||||
# FLOOR_MINOR <int>
|
||||
# [SOURCE_DIR <path>]) # defaults to CMAKE_SOURCE_DIR
|
||||
#
|
||||
# Runs git in the working tree, then delegates to rex_compute_version.
|
||||
#==========================================================
|
||||
function(rex_resolve_version out_var)
|
||||
set(one_value FLOOR_MAJOR FLOOR_MINOR SOURCE_DIR)
|
||||
cmake_parse_arguments(ARG "" "${one_value}" "" ${ARGN})
|
||||
|
||||
if(NOT ARG_SOURCE_DIR)
|
||||
set(ARG_SOURCE_DIR "${CMAKE_SOURCE_DIR}")
|
||||
endif()
|
||||
|
||||
find_program(GIT_EXECUTABLE git)
|
||||
if(NOT GIT_EXECUTABLE)
|
||||
rex_compute_version(result
|
||||
FLOOR_MAJOR ${ARG_FLOOR_MAJOR}
|
||||
FLOOR_MINOR ${ARG_FLOOR_MINOR}
|
||||
GIT_DESCRIBE_LONG ""
|
||||
GIT_DESCRIBE_EXACT ""
|
||||
BRANCH_NAME "")
|
||||
set(${out_var} "${result}" PARENT_SCOPE)
|
||||
return()
|
||||
endif()
|
||||
|
||||
execute_process(
|
||||
COMMAND ${GIT_EXECUTABLE} describe --tags --exact-match
|
||||
--match "v[0-9]*.[0-9]*.[0-9]*"
|
||||
--exclude "*-*"
|
||||
WORKING_DIRECTORY "${ARG_SOURCE_DIR}"
|
||||
OUTPUT_VARIABLE describe_exact
|
||||
ERROR_QUIET
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
RESULT_VARIABLE describe_exact_rc)
|
||||
if(NOT describe_exact_rc EQUAL 0)
|
||||
set(describe_exact "")
|
||||
endif()
|
||||
|
||||
execute_process(
|
||||
COMMAND ${GIT_EXECUTABLE} describe --tags --long
|
||||
--match "v[0-9]*.[0-9]*"
|
||||
--exclude "*-*"
|
||||
WORKING_DIRECTORY "${ARG_SOURCE_DIR}"
|
||||
OUTPUT_VARIABLE describe_long
|
||||
ERROR_QUIET
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
RESULT_VARIABLE describe_long_rc)
|
||||
if(NOT describe_long_rc EQUAL 0)
|
||||
set(describe_long "")
|
||||
endif()
|
||||
|
||||
execute_process(
|
||||
COMMAND ${GIT_EXECUTABLE} symbolic-ref --short HEAD
|
||||
WORKING_DIRECTORY "${ARG_SOURCE_DIR}"
|
||||
OUTPUT_VARIABLE branch_name
|
||||
ERROR_QUIET
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
RESULT_VARIABLE branch_rc)
|
||||
if(NOT branch_rc EQUAL 0)
|
||||
set(branch_name "")
|
||||
endif()
|
||||
|
||||
rex_compute_version(result
|
||||
FLOOR_MAJOR ${ARG_FLOOR_MAJOR}
|
||||
FLOOR_MINOR ${ARG_FLOOR_MINOR}
|
||||
GIT_DESCRIBE_LONG "${describe_long}"
|
||||
GIT_DESCRIBE_EXACT "${describe_exact}"
|
||||
BRANCH_NAME "${branch_name}")
|
||||
set(${out_var} "${result}" PARENT_SCOPE)
|
||||
endfunction()
|
||||
39
cmake/rexglueConfig.cmake.in
Normal file
39
cmake/rexglueConfig.cmake.in
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
@PACKAGE_INIT@
|
||||
|
||||
set(REXGLUE_VERSION_STRING "@REXGLUE_FULL_VERSION@")
|
||||
|
||||
set(REXGLUE_SHARE_DIR "${PACKAGE_PREFIX_DIR}/@CMAKE_INSTALL_DATADIR@/rexglue")
|
||||
|
||||
include(CMakeFindDependencyMacro)
|
||||
|
||||
include("${CMAKE_CURRENT_LIST_DIR}/rexglue_helpers.cmake")
|
||||
|
||||
list(PREPEND CMAKE_PREFIX_PATH "${PACKAGE_PREFIX_DIR}")
|
||||
|
||||
find_dependency(fmt CONFIG)
|
||||
find_dependency(spdlog CONFIG)
|
||||
find_dependency(Snappy CONFIG)
|
||||
find_dependency(utf8cpp CONFIG)
|
||||
find_dependency(SDL3 CONFIG)
|
||||
|
||||
set(REXGLUE_USE_VULKAN @REXGLUE_USE_VULKAN@)
|
||||
if(REXGLUE_USE_VULKAN)
|
||||
find_dependency(VulkanHeaders CONFIG)
|
||||
find_dependency(volk CONFIG)
|
||||
find_dependency(VulkanMemoryAllocator CONFIG)
|
||||
endif()
|
||||
|
||||
include("${CMAKE_CURRENT_LIST_DIR}/rexglueTargets.cmake")
|
||||
|
||||
if(TARGET rex::runtime AND NOT TARGET rex::system)
|
||||
add_library(rex::system ALIAS rex::runtime)
|
||||
endif()
|
||||
if(TARGET rex::runtime AND NOT TARGET rex::kernel)
|
||||
add_library(rex::kernel ALIAS rex::runtime)
|
||||
endif()
|
||||
|
||||
if(NOT rexglue_FIND_QUIETLY)
|
||||
message(STATUS "Found ReXGlue SDK ${REXGLUE_VERSION_STRING} at (${PACKAGE_PREFIX_DIR})")
|
||||
endif()
|
||||
|
||||
check_required_components(rexglue)
|
||||
13
cmake/rexglue_devenv.cmake
Normal file
13
cmake/rexglue_devenv.cmake
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
# ReXGlue developer environment setup target.
|
||||
# Provides the `config-rexdevenv` target that installs the pre-commit hook,
|
||||
# VS terminal profile, and verifies the toolchain.
|
||||
# Requires PowerShell 7 (pwsh) on all platforms.
|
||||
|
||||
add_custom_target(config-rexdevenv
|
||||
COMMAND pwsh -NoProfile -ExecutionPolicy Bypass -Command
|
||||
"Import-Module '${REXGLUE_ROOT}/scripts/PSReX' -Force; Invoke-ReXSetup -RepoRoot '${REXGLUE_ROOT}'"
|
||||
WORKING_DIRECTORY "${REXGLUE_ROOT}"
|
||||
COMMENT "Configuring ReXGlue developer environment..."
|
||||
VERBATIM
|
||||
USES_TERMINAL
|
||||
)
|
||||
133
cmake/rexglue_fidelityfx.cmake
Normal file
133
cmake/rexglue_fidelityfx.cmake
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
# rexglue_fidelityfx.cmake — Optional AMD FidelityFX integration via FetchContent
|
||||
#
|
||||
# Expects REXGLUE_ENABLE_FIDELITYFX to be set before inclusion.
|
||||
# On success, creates amd_fidelityfx_vk and/or amd_fidelityfx_dx12 targets
|
||||
# and sets REXGLUE_FIDELITYFX_SOURCE_DIR to the fetched SDK root.
|
||||
|
||||
if(NOT REXGLUE_ENABLE_FIDELITYFX)
|
||||
return()
|
||||
endif()
|
||||
|
||||
# ── Dependency validation ────────────────────────────────────────────────
|
||||
if(NOT WIN32)
|
||||
find_package(Vulkan QUIET)
|
||||
if(NOT Vulkan_FOUND)
|
||||
message(WARNING
|
||||
"REXGLUE_ENABLE_FIDELITYFX requires the Vulkan SDK but it was not found.\n"
|
||||
"Install the LunarG Vulkan SDK: https://vulkan.lunarg.com/sdk/home\n"
|
||||
"Disabling FidelityFX.")
|
||||
set(REXGLUE_ENABLE_FIDELITYFX OFF CACHE BOOL "" FORCE)
|
||||
return()
|
||||
elseif(Vulkan_VERSION VERSION_LESS "1.3.250")
|
||||
message(WARNING
|
||||
"REXGLUE_ENABLE_FIDELITYFX requires Vulkan SDK >= 1.3.250 "
|
||||
"(found ${Vulkan_VERSION}).\n"
|
||||
"Update via: https://vulkan.lunarg.com/sdk/home\n"
|
||||
"Disabling FidelityFX.")
|
||||
set(REXGLUE_ENABLE_FIDELITYFX OFF CACHE BOOL "" FORCE)
|
||||
return()
|
||||
else()
|
||||
find_program(_rexglue_glslc glslc)
|
||||
find_program(_rexglue_glslang glslangValidator)
|
||||
if(NOT _rexglue_glslc AND NOT _rexglue_glslang)
|
||||
message(WARNING
|
||||
"REXGLUE_ENABLE_FIDELITYFX requires Vulkan shader tools "
|
||||
"(glslc or glslangValidator) but neither was found.\n"
|
||||
"Install the full LunarG Vulkan SDK: https://vulkan.lunarg.com/sdk/home\n"
|
||||
"Disabling FidelityFX.")
|
||||
set(REXGLUE_ENABLE_FIDELITYFX OFF CACHE BOOL "" FORCE)
|
||||
unset(_rexglue_glslc CACHE)
|
||||
unset(_rexglue_glslang CACHE)
|
||||
return()
|
||||
endif()
|
||||
unset(_rexglue_glslc CACHE)
|
||||
unset(_rexglue_glslang CACHE)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# ── Fetch FidelityFX SDK ─────────────────────────────────────────────────
|
||||
include(FetchContent)
|
||||
FetchContent_Declare(
|
||||
fidelityfx
|
||||
GIT_REPOSITORY https://github.com/rexglue/FidelityFX-SDK.git
|
||||
GIT_TAG eee08db1688ac3d1275a70b728f4a8ba22914213
|
||||
GIT_SHALLOW OFF
|
||||
)
|
||||
FetchContent_GetProperties(fidelityfx)
|
||||
if(NOT fidelityfx_POPULATED)
|
||||
FetchContent_Populate(fidelityfx)
|
||||
endif()
|
||||
|
||||
set(REXGLUE_FIDELITYFX_SOURCE_DIR "${fidelityfx_SOURCE_DIR}" CACHE INTERNAL
|
||||
"Root of the fetched FidelityFX SDK source tree")
|
||||
|
||||
# ── Backend selection ────────────────────────────────────────────────────
|
||||
set(REXGLUE_FIDELITYFX_BACKEND "auto" CACHE STRING
|
||||
"FidelityFX backend to build (auto, vk, dx12)")
|
||||
set_property(CACHE REXGLUE_FIDELITYFX_BACKEND PROPERTY STRINGS auto vk dx12)
|
||||
set(_rexglue_fidelityfx_backend "${REXGLUE_FIDELITYFX_BACKEND}")
|
||||
string(TOLOWER "${_rexglue_fidelityfx_backend}" _rexglue_fidelityfx_backend)
|
||||
|
||||
if(_rexglue_fidelityfx_backend STREQUAL "auto")
|
||||
if(WIN32 AND REXGLUE_USE_D3D12)
|
||||
set(_rexglue_fidelityfx_backend "dx12")
|
||||
elseif(REXGLUE_USE_VULKAN)
|
||||
set(_rexglue_fidelityfx_backend "vk")
|
||||
elseif(REXGLUE_USE_D3D12)
|
||||
set(_rexglue_fidelityfx_backend "dx12")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(_rexglue_fidelityfx_backend STREQUAL "vk")
|
||||
if(NOT REXGLUE_USE_VULKAN)
|
||||
message(FATAL_ERROR
|
||||
"REXGLUE_FIDELITYFX_BACKEND=vk requires REXGLUE_USE_VULKAN=ON")
|
||||
endif()
|
||||
set(FFX_API_BACKEND VK_X64 CACHE STRING "" FORCE)
|
||||
elseif(_rexglue_fidelityfx_backend STREQUAL "dx12")
|
||||
if(NOT REXGLUE_USE_D3D12)
|
||||
message(FATAL_ERROR
|
||||
"REXGLUE_FIDELITYFX_BACKEND=dx12 requires REXGLUE_USE_D3D12=ON")
|
||||
endif()
|
||||
set(FFX_API_BACKEND DX12_X64 CACHE STRING "" FORCE)
|
||||
elseif(_rexglue_fidelityfx_backend STREQUAL "")
|
||||
message(FATAL_ERROR "FidelityFX requires a supported graphics backend")
|
||||
else()
|
||||
message(FATAL_ERROR
|
||||
"Invalid REXGLUE_FIDELITYFX_BACKEND='${REXGLUE_FIDELITYFX_BACKEND}' "
|
||||
"(expected auto, vk, or dx12)")
|
||||
endif()
|
||||
|
||||
if(REXGLUE_USE_VULKAN AND REXGLUE_USE_D3D12)
|
||||
message(STATUS
|
||||
"FidelityFX backend selected for this build: ${_rexglue_fidelityfx_backend}")
|
||||
endif()
|
||||
|
||||
# ── Build FidelityFX ─────────────────────────────────────────────────────
|
||||
set(FFX_API_ENABLE_FRAMEGEN_PROVIDER OFF CACHE BOOL "" FORCE)
|
||||
if(WIN32)
|
||||
set(FFX_API_AUTO_COMPILE_SHADERS ON CACHE BOOL "" FORCE)
|
||||
else()
|
||||
set(FFX_API_AUTO_COMPILE_SHADERS OFF CACHE BOOL "" FORCE)
|
||||
endif()
|
||||
add_subdirectory("${fidelityfx_SOURCE_DIR}/ffx-api" "${fidelityfx_BINARY_DIR}/ffx-api" EXCLUDE_FROM_ALL)
|
||||
|
||||
# The upstream FidelityFX targets expose source-tree include paths in
|
||||
# INTERFACE_INCLUDE_DIRECTORIES, which breaks our install export checks.
|
||||
# We only link against these targets internally, so no public includes are
|
||||
# needed on the exported interface.
|
||||
if(TARGET amd_fidelityfx_vk)
|
||||
set_target_properties(amd_fidelityfx_vk PROPERTIES
|
||||
INTERFACE_INCLUDE_DIRECTORIES ""
|
||||
)
|
||||
endif()
|
||||
if(TARGET amd_fidelityfx_dx12)
|
||||
set_target_properties(amd_fidelityfx_dx12 PROPERTIES
|
||||
INTERFACE_INCLUDE_DIRECTORIES ""
|
||||
)
|
||||
endif()
|
||||
|
||||
# FidelityFX's toolchain.cmake force-sets CMAKE_GENERATOR_PLATFORM (for VS generators).
|
||||
# With Ninja this variable is invalid and poisons every subsequent try_compile() call.
|
||||
# Clear it from the cache.
|
||||
unset(CMAKE_GENERATOR_PLATFORM CACHE)
|
||||
142
cmake/rexglue_helpers.cmake
Normal file
142
cmake/rexglue_helpers.cmake
Normal file
|
|
@ -0,0 +1,142 @@
|
|||
#==========================================================
|
||||
# rexglue_helpers.cmake
|
||||
#
|
||||
# Three helpers, each with a single responsibility:
|
||||
# rexglue_apply_target_settings(<target>) - common compile/platform flags
|
||||
# rexglue_configure_target(<target>) - host application
|
||||
# rexglue_configure_module_target(<target> ...) - guest DLL module
|
||||
#==========================================================
|
||||
|
||||
#==========================================================
|
||||
# rexglue_apply_target_settings(<target>) - Common flags
|
||||
#
|
||||
# Applied to both host apps and guest DLL modules. Compile/link flags only;
|
||||
# runtime DLL staging is the host's job (see rexglue_configure_target).
|
||||
#==========================================================
|
||||
function(rexglue_apply_target_settings target_name)
|
||||
if(UNIX AND NOT APPLE)
|
||||
find_package(PkgConfig REQUIRED)
|
||||
pkg_check_modules(GTK3 REQUIRED gtk+-3.0)
|
||||
target_include_directories(${target_name} PRIVATE ${GTK3_INCLUDE_DIRS})
|
||||
target_link_libraries(${target_name} PRIVATE ${GTK3_LIBRARIES})
|
||||
# Large executable support
|
||||
if(CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64|AMD64")
|
||||
target_link_options(${target_name} PRIVATE -Wl,--no-relax)
|
||||
target_compile_options(${target_name} PRIVATE -mcmodel=large)
|
||||
elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64|ARM64")
|
||||
target_compile_options(${target_name} PRIVATE -march=armv8-a)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(NOT MSVC)
|
||||
if(CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64|AMD64")
|
||||
target_compile_options(${target_name} PRIVATE -msse4.1)
|
||||
endif()
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
#==========================================================
|
||||
# rexglue_configure_target(<target>) - Host application
|
||||
#
|
||||
# Adds:
|
||||
# - Platform entry point source (windowed_app_main_*.cpp)
|
||||
# - ReXApp base class source (rex_app.cpp)
|
||||
# - Build-config define for the version stamp
|
||||
# - $ORIGIN RPATH on UNIX so the host finds librexruntime.so next to itself
|
||||
# - Windows POST_BUILD copy of TARGET_RUNTIME_DLLS and the FidelityFX DLLs.
|
||||
# Guest modules colocate with the host (see rexglue_configure_module_target),
|
||||
# so this single copy handles them transitively.
|
||||
#==========================================================
|
||||
function(rexglue_configure_target target_name)
|
||||
if(WIN32)
|
||||
target_sources(${target_name} PRIVATE
|
||||
${REXGLUE_SHARE_DIR}/windowed_app_main_win.cpp)
|
||||
else()
|
||||
target_sources(${target_name} PRIVATE
|
||||
${REXGLUE_SHARE_DIR}/windowed_app_main_posix.cpp)
|
||||
endif()
|
||||
|
||||
target_sources(${target_name} PRIVATE
|
||||
${REXGLUE_SHARE_DIR}/rex_app.cpp)
|
||||
|
||||
target_compile_definitions(${target_name} PRIVATE
|
||||
REXGLUE_BUILD_CONFIG="$<CONFIG>")
|
||||
|
||||
if(TARGET imgui::imgui)
|
||||
target_link_libraries(${target_name} PRIVATE imgui::imgui)
|
||||
endif()
|
||||
target_include_directories(${target_name} PRIVATE
|
||||
${CMAKE_CURRENT_BINARY_DIR}/rexglue-sdk/include)
|
||||
|
||||
if(UNIX AND NOT APPLE)
|
||||
set_target_properties(${target_name} PROPERTIES
|
||||
INSTALL_RPATH "$ORIGIN"
|
||||
BUILD_WITH_INSTALL_RPATH ON
|
||||
)
|
||||
endif()
|
||||
|
||||
rexglue_apply_target_settings(${target_name})
|
||||
|
||||
if(WIN32)
|
||||
# Stage runtime DLLs (rexruntime, TracyClient, etc.) next to the host
|
||||
# binary on every link. copy_if_different is a no-op when up to date.
|
||||
add_custom_command(TARGET ${target_name} POST_BUILD
|
||||
COMMAND "$<$<BOOL:$<TARGET_RUNTIME_DLLS:${target_name}>>:${CMAKE_COMMAND};-E;copy_if_different;$<TARGET_RUNTIME_DLLS:${target_name}>;$<TARGET_FILE_DIR:${target_name}>>"
|
||||
COMMAND_EXPAND_LISTS
|
||||
VERBATIM
|
||||
)
|
||||
# FidelityFX is linked PRIVATE by rexui (to avoid propagating DLL
|
||||
# requirements to tool-mode targets), so copy its DLLs explicitly.
|
||||
foreach(_fx amd_fidelityfx_vk amd_fidelityfx_dx12)
|
||||
if(TARGET ${_fx})
|
||||
add_custom_command(TARGET ${target_name} POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||
$<TARGET_FILE:${_fx}>
|
||||
$<TARGET_FILE_DIR:${target_name}>
|
||||
VERBATIM
|
||||
)
|
||||
endif()
|
||||
endforeach()
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
#==========================================================
|
||||
# rexglue_configure_module_target(<target> [HOST <host_target>])
|
||||
# - Guest DLL module
|
||||
#
|
||||
# Output is colocated with HOST (or CMAKE_RUNTIME_OUTPUT_DIRECTORY) so the
|
||||
# host's LoadUserModule finds it; the host is wired to depend on the module
|
||||
# so a top-level build of the host pulls in all guest DLLs. No runtime DLL
|
||||
# staging here; the host's POST_BUILD copy covers shared dependencies.
|
||||
#==========================================================
|
||||
function(rexglue_configure_module_target target_name)
|
||||
cmake_parse_arguments(ARG "" "HOST" "" ${ARGN})
|
||||
|
||||
if(ARG_HOST)
|
||||
set_target_properties(${target_name} PROPERTIES
|
||||
LIBRARY_OUTPUT_DIRECTORY $<TARGET_FILE_DIR:${ARG_HOST}>
|
||||
RUNTIME_OUTPUT_DIRECTORY $<TARGET_FILE_DIR:${ARG_HOST}>
|
||||
ARCHIVE_OUTPUT_DIRECTORY $<TARGET_FILE_DIR:${ARG_HOST}>
|
||||
)
|
||||
# Defer add_dependencies so the host target may be declared after
|
||||
# this module call. Wrapping in EVAL CODE expands the variables now,
|
||||
# which DEFER CALL otherwise treats as literal argument text.
|
||||
cmake_language(EVAL CODE
|
||||
"cmake_language(DEFER CALL add_dependencies ${ARG_HOST} ${target_name})")
|
||||
elseif(CMAKE_RUNTIME_OUTPUT_DIRECTORY)
|
||||
set_target_properties(${target_name} PROPERTIES
|
||||
LIBRARY_OUTPUT_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}
|
||||
RUNTIME_OUTPUT_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}
|
||||
ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}
|
||||
)
|
||||
endif()
|
||||
|
||||
if(UNIX AND NOT APPLE)
|
||||
set_target_properties(${target_name} PROPERTIES
|
||||
INSTALL_RPATH "$ORIGIN"
|
||||
BUILD_WITH_INSTALL_RPATH ON
|
||||
)
|
||||
endif()
|
||||
|
||||
rexglue_apply_target_settings(${target_name})
|
||||
endfunction()
|
||||
192
cmake/rexglue_install.cmake
Normal file
192
cmake/rexglue_install.cmake
Normal file
|
|
@ -0,0 +1,192 @@
|
|||
#==========================================================
|
||||
# SDK Install/Export Rules
|
||||
#==========================================================
|
||||
set_target_properties(rexcore PROPERTIES EXPORT_NAME core)
|
||||
set_target_properties(rexfilesystem PROPERTIES EXPORT_NAME filesystem)
|
||||
set_target_properties(rexui PROPERTIES EXPORT_NAME ui)
|
||||
set_target_properties(rexinput PROPERTIES EXPORT_NAME input)
|
||||
set_target_properties(rexaudio PROPERTIES EXPORT_NAME audio)
|
||||
set_target_properties(rexgraphics PROPERTIES EXPORT_NAME graphics)
|
||||
set_target_properties(rexruntime PROPERTIES EXPORT_NAME runtime)
|
||||
set_target_properties(rexcodegen PROPERTIES EXPORT_NAME codegen)
|
||||
|
||||
set(REXGLUE_INSTALL_TARGETS
|
||||
rexruntime
|
||||
disruptorplus renderdoc simde tomlplusplus
|
||||
aes128 mspack o1heap disasm xxhash
|
||||
libavcodec libavutil
|
||||
rexglue
|
||||
)
|
||||
|
||||
if(REXGLUE_USE_VULKAN)
|
||||
list(APPEND REXGLUE_INSTALL_TARGETS
|
||||
SPIRV glslang MachineIndependent GenericCodeGen OSDependent OGLCompiler # glslang
|
||||
SPIRV-Tools-static
|
||||
)
|
||||
endif()
|
||||
|
||||
if(REXGLUE_USE_D3D12)
|
||||
list(APPEND REXGLUE_INSTALL_TARGETS dxc-headers)
|
||||
endif()
|
||||
|
||||
if(REXGLUE_ENABLE_TRACY)
|
||||
list(APPEND REXGLUE_INSTALL_TARGETS TracyClient)
|
||||
endif()
|
||||
|
||||
set(REXGLUE_INSTALL_FIDELITYFX_TARGETS)
|
||||
if(TARGET amd_fidelityfx_vk)
|
||||
list(APPEND REXGLUE_INSTALL_FIDELITYFX_TARGETS amd_fidelityfx_vk)
|
||||
endif()
|
||||
|
||||
if(TARGET amd_fidelityfx_dx12)
|
||||
list(APPEND REXGLUE_INSTALL_FIDELITYFX_TARGETS amd_fidelityfx_dx12)
|
||||
endif()
|
||||
|
||||
install(TARGETS ${REXGLUE_INSTALL_TARGETS}
|
||||
EXPORT rexglueTargets
|
||||
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
|
||||
)
|
||||
|
||||
if(REXGLUE_INSTALL_FIDELITYFX_TARGETS)
|
||||
install(TARGETS ${REXGLUE_INSTALL_FIDELITYFX_TARGETS}
|
||||
EXPORT rexglueTargets
|
||||
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
|
||||
)
|
||||
endif()
|
||||
|
||||
# Install public headers
|
||||
install(DIRECTORY include/rex
|
||||
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
|
||||
)
|
||||
|
||||
# Install generated version header
|
||||
install(FILES
|
||||
${CMAKE_CURRENT_BINARY_DIR}/include/rex/version.h
|
||||
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/rex
|
||||
)
|
||||
|
||||
# Install vendored header-only library headers
|
||||
install(DIRECTORY thirdparty/disruptorplus/include/
|
||||
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/disruptorplus
|
||||
)
|
||||
install(DIRECTORY thirdparty/renderdoc/
|
||||
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/renderdoc
|
||||
FILES_MATCHING PATTERN "*.h"
|
||||
)
|
||||
install(DIRECTORY thirdparty/tomlplusplus/include/
|
||||
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
|
||||
)
|
||||
install(DIRECTORY thirdparty/simde/simde
|
||||
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
|
||||
FILES_MATCHING PATTERN "*.h"
|
||||
)
|
||||
install(FILES
|
||||
thirdparty/xxHash/xxhash.h
|
||||
thirdparty/xxHash/xxh3.h
|
||||
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
|
||||
)
|
||||
install(FILES
|
||||
thirdparty/o1heap/o1heap/o1heap.h
|
||||
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
|
||||
)
|
||||
install(FILES
|
||||
thirdparty/imgui/imgui.h
|
||||
thirdparty/imgui/imconfig.h
|
||||
thirdparty/imgui/imgui_internal.h
|
||||
thirdparty/imgui/imstb_rectpack.h
|
||||
thirdparty/imgui/imstb_textedit.h
|
||||
thirdparty/imgui/imstb_truetype.h
|
||||
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
|
||||
)
|
||||
|
||||
# Install SPIRV-Tools headers (only the public API, not opt/linker)
|
||||
if(REXGLUE_USE_VULKAN)
|
||||
install(FILES
|
||||
thirdparty/spirv-tools/include/spirv-tools/libspirv.h
|
||||
thirdparty/spirv-tools/include/spirv-tools/libspirv.hpp
|
||||
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/spirv-tools
|
||||
)
|
||||
endif()
|
||||
|
||||
# Install platform entry point sources and ReXApp for SDK consumers
|
||||
install(FILES
|
||||
src/ui/windowed_app_main_win.cpp
|
||||
src/ui/windowed_app_main_posix.cpp
|
||||
src/ui/rex_app.cpp
|
||||
DESTINATION ${CMAKE_INSTALL_DATADIR}/rexglue
|
||||
)
|
||||
|
||||
# Install DXC API headers (vendored, for D3D12 backend)
|
||||
if(REXGLUE_USE_D3D12)
|
||||
install(FILES
|
||||
thirdparty/dxc/include/DxbcConverter.h
|
||||
thirdparty/dxc/include/dxcapi.h
|
||||
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/dxc
|
||||
)
|
||||
endif()
|
||||
|
||||
# Generate and install package config files
|
||||
configure_package_config_file(
|
||||
cmake/rexglueConfig.cmake.in
|
||||
${CMAKE_CURRENT_BINARY_DIR}/rexglueConfig.cmake
|
||||
INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/rexglue
|
||||
)
|
||||
|
||||
write_basic_package_version_file(
|
||||
${CMAKE_CURRENT_BINARY_DIR}/rexglueConfigVersion.cmake
|
||||
VERSION ${REXGLUE_NUMERIC_VERSION}
|
||||
COMPATIBILITY SameMajorVersion
|
||||
)
|
||||
|
||||
install(FILES
|
||||
${CMAKE_CURRENT_BINARY_DIR}/rexglueConfig.cmake
|
||||
${CMAKE_CURRENT_BINARY_DIR}/rexglueConfigVersion.cmake
|
||||
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/rexglue
|
||||
)
|
||||
|
||||
install(FILES
|
||||
${CMAKE_SOURCE_DIR}/cmake/rexglue_helpers.cmake
|
||||
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/rexglue
|
||||
)
|
||||
|
||||
# Export targets with rex:: namespace
|
||||
install(EXPORT rexglueTargets
|
||||
FILE rexglueTargets.cmake
|
||||
NAMESPACE rex::
|
||||
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/rexglue
|
||||
)
|
||||
|
||||
# Register in the CMake User Package Registry after install.
|
||||
# This makes find_package(rexglue) work with no REXSDK env var or CMAKE_PREFIX_PATH.
|
||||
# Multiple SxS installs coexist. Each prefix gets a unique hash entry.
|
||||
#
|
||||
# Windows: HKCU\Software\Kitware\CMake\Packages\rexglue (REG_SZ, value name = MD5 hash)
|
||||
# Unix: ~/.cmake/packages/rexglue/<hash> (file containing prefix path)
|
||||
install(CODE [[
|
||||
# Normalize path casing on Windows before hashing to avoid duplicate entries
|
||||
if(CMAKE_HOST_WIN32)
|
||||
string(TOLOWER "${CMAKE_INSTALL_PREFIX}" _reg_key)
|
||||
else()
|
||||
set(_reg_key "${CMAKE_INSTALL_PREFIX}")
|
||||
endif()
|
||||
string(MD5 _hash "${_reg_key}")
|
||||
|
||||
if(CMAKE_HOST_WIN32)
|
||||
# Windows CMake User Package Registry lives in HKCU (not the filesystem)
|
||||
set(_reg_root "HKCU\\Software\\Kitware\\CMake\\Packages\\rexglue")
|
||||
execute_process(
|
||||
COMMAND reg add "${_reg_root}" /v "${_hash}" /t REG_SZ /d "${CMAKE_INSTALL_PREFIX}" /f
|
||||
OUTPUT_QUIET ERROR_QUIET
|
||||
)
|
||||
else()
|
||||
set(_reg_dir "$ENV{HOME}/.cmake/packages/rexglue")
|
||||
file(MAKE_DIRECTORY "${_reg_dir}")
|
||||
file(WRITE "${_reg_dir}/${_hash}" "${CMAKE_INSTALL_PREFIX}")
|
||||
endif()
|
||||
message(STATUS "Registered rexglue in CMake user package registry")
|
||||
message(STATUS " -> ${CMAKE_INSTALL_PREFIX}")
|
||||
]])
|
||||
124
docs/RELEASING.md
Normal file
124
docs/RELEASING.md
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
# Releasing ReXGlue SDK
|
||||
|
||||
## Branches
|
||||
|
||||
`main` always sits at the most recent stable tag. `development` is the working canary; all feature work merges here first. `release/X.Y.Z` is short-lived: branched from dev tip when a release is cut, merged into both dev and main when the release is published, then deleted.
|
||||
|
||||
## Versioning
|
||||
|
||||
The build version is computed automatically. Do not edit `CMakeLists.txt` to bump the version on a routine release.
|
||||
|
||||
`CMakeLists.txt` carries only the API floor as `PROJECT VERSION MAJOR.MINOR.0`. The patch field there is a placeholder CMake requires; only major and minor mean anything. Edit the floor only when you are introducing a public API change.
|
||||
|
||||
Version format follows [CMake's `CMAKE_VERSION`](https://cmake.org/cmake/help/latest/variable/CMAKE_VERSION.html) convention: `MAJOR.MINOR[.PATCH[.TWEAK]][-id]`.
|
||||
|
||||
The full version is derived at configure time:
|
||||
|
||||
- On a tagged commit: version is the tag verbatim (minus the leading `v`). Tags may be 2, 3, or 4 components (`v0.7`, `v0.7.5`, `v0.7.5.1`). Whatever you tag is what ships.
|
||||
- On a `release/*` branch: version is `MAJOR.<floor-minor>.<next-patch>.<commit-count>-rc.g<sha>`.
|
||||
- On `development` or anywhere else: version is `MAJOR.<floor-minor>.<next-patch>.<commit-count>-dev.g<sha>`.
|
||||
|
||||
`<next-patch>` is `last-tag-patch + 1` when the floor minor matches the most recent tag's minor, or `0` when the floor is ahead. `<commit-count>` is the number of commits since that tag (the TWEAK slot). The `-id` trailer identifies the build channel (`dev` or `rc`) and the short commit SHA.
|
||||
|
||||
Examples:
|
||||
|
||||
| Scenario | Version |
|
||||
| --- | --- |
|
||||
| `git tag v0.7.5` pushed | `0.7.5` |
|
||||
| `git tag v0.7.5.1` pushed (hotfix tweak) | `0.7.5.1` |
|
||||
| `development`, 38 commits past `v0.7.4` | `0.7.5.38-dev.gb27f3f74` |
|
||||
| `release/0.7.5`, 15 commits past `v0.7.4` | `0.7.5.15-rc.gdeadbee` |
|
||||
| Floor bumped to `0.8`, 3 commits past `v0.7.5` | `0.8.0.3-dev.gdef5678` |
|
||||
|
||||
A version with `-dev` or `-rc` is unstable. Only a version without an `-id` trailer is covered by the public API.
|
||||
|
||||
## Release flow
|
||||
|
||||
### Cutting the release
|
||||
|
||||
Figure out the next release version. On a normal release it is `MAJOR.<floor-minor>.<last-tag-patch + 1>`. If the release introduces a public API change, bump the floor first (see "Bumping the floor" below) and the next release becomes `MAJOR.<new-floor-minor>.0`.
|
||||
|
||||
Branch the release from dev tip and push it:
|
||||
|
||||
```
|
||||
git fetch origin
|
||||
git checkout -b release/X.Y.Z origin/development
|
||||
git push -u origin release/X.Y.Z
|
||||
```
|
||||
|
||||
Open a PR `release/X.Y.Z -> main`.
|
||||
|
||||
### While the release is open
|
||||
|
||||
Feature work keeps merging into `development` as normal. Review fixes for the open release land on `release/X.Y.Z` directly. Do not cherry-pick review fixes into dev; they flow back when the release is published.
|
||||
|
||||
### Merging and tagging
|
||||
|
||||
Merge the release branch into dev first, so review fixes flow back:
|
||||
|
||||
```
|
||||
git checkout development
|
||||
git pull
|
||||
git merge --no-ff release/X.Y.Z -m "Merge release/X.Y.Z into development"
|
||||
git push origin development
|
||||
```
|
||||
|
||||
Then merge into main. Use a real merge commit; do not squash, do not rebase:
|
||||
|
||||
```
|
||||
git checkout main
|
||||
git pull
|
||||
git merge --no-ff release/X.Y.Z -m "Release vX.Y.Z"
|
||||
git push origin main
|
||||
```
|
||||
|
||||
Tag the release branch tip (not `main` HEAD) and push.
|
||||
|
||||
```
|
||||
git tag vX.Y.Z release/X.Y.Z
|
||||
git push origin vX.Y.Z
|
||||
```
|
||||
|
||||
The tag push triggers `build-win-amd64`, `build-linux-amd64`, and `build-linux-aarch64`, which publish a GitHub release with the three platform zips.
|
||||
|
||||
Delete the release branch (after tagging):
|
||||
|
||||
```
|
||||
git push origin --delete release/X.Y.Z
|
||||
git branch -d release/X.Y.Z
|
||||
```
|
||||
|
||||
## Hotfixes
|
||||
|
||||
A hotfix follows the same flow as a normal release with two differences. The release branch starts from `main` (the last stable tag) instead of dev tip, so it carries the fix and nothing else. The version is the next patch off the most recent tag, same rule as a normal release.
|
||||
|
||||
```
|
||||
git fetch origin
|
||||
git checkout -b release/X.Y.Z origin/main
|
||||
```
|
||||
|
||||
Apply the fix on the branch, open the PR into main, then merge into dev first and into main with a merge commit, exactly like a normal release. Tag and push.
|
||||
|
||||
If a regular release branch is already open, close it without merging before cutting the hotfix; only one release branch may exist at a time. Re-cut the regular release from dev tip after the hotfix is released; its version will be the next patch off the hotfix tag.
|
||||
|
||||
## Bumping the floor
|
||||
|
||||
Only when the public API is changing. Single commit on `development`:
|
||||
|
||||
1. Edit `CMakeLists.txt` line 6: change `PROJECT VERSION 0.<old>.0` to `PROJECT VERSION 0.<new>.0`.
|
||||
2. Commit message: `chore: bump API floor to 0.<new>`.
|
||||
3. The next release branch is `release/0.<new>.0` and the tag is `v0.<new>.0`.
|
||||
|
||||
## Nightly builds
|
||||
|
||||
Scheduled workflow that builds dev tip when its SHA has advanced since the last nightly. Each run produces a prerelease tagged `<NIGHTLY_TAG_PREFIX>YYYYMMDD-<short_sha>` and prunes older ones beyond `NIGHTLY_RETENTION`.
|
||||
|
||||
## When something goes wrong
|
||||
|
||||
CI fails on a tag push: do not delete the tag. Investigate, fix on `development`, cut the next patch release when ready. Do not retag.
|
||||
|
||||
A bad release went out: cut a fix release. Do not delete the bad release.
|
||||
|
||||
Conflicts merging `release/X.Y.Z` into `development`: resolve in favor of dev for feature changes, in favor of the release branch for release fixes. Do not cherry-pick.
|
||||
|
||||
Two release branches accidentally open: close the older one without merging. Only one release branch at a time.
|
||||
BIN
docs/ppc/altivec_instructions.pdf
Normal file
BIN
docs/ppc/altivec_instructions.pdf
Normal file
Binary file not shown.
BIN
docs/ppc/core_instructions.pdf
Normal file
BIN
docs/ppc/core_instructions.pdf
Normal file
Binary file not shown.
610
docs/ppc/vmx128.txt
Normal file
610
docs/ppc/vmx128.txt
Normal file
|
|
@ -0,0 +1,610 @@
|
|||
2006/09/01 Revision 1.2
|
||||
-----------------------------------------------------------------
|
||||
|
||||
|
||||
This is a description of the VMX128-type opcodes found on
|
||||
the xbox360 processor. I figured this out by looking at
|
||||
various disassmblies, so there might some errors and
|
||||
missing instructions. Some instructions have unknown
|
||||
semantics for me.
|
||||
|
||||
See comments or corrections to sb#biallas.net
|
||||
|
||||
|
||||
=================================================================
|
||||
Conventions:
|
||||
VD128, VS128: 5 lower bits of a VMX128 vector register
|
||||
number
|
||||
VDh: upper 2 bits of VD128
|
||||
(so register number is (VDh << 5 | VD128))
|
||||
|
||||
VA128: same as VD128
|
||||
A: bit 6 of VA128
|
||||
a: bit 5 of VA128
|
||||
(so register number is (A<<6 | a<<5 | VA128))
|
||||
|
||||
VB128: same as VD128
|
||||
VBh: same as VDh
|
||||
|
||||
VC128: 3 bits of a VMX128 vector register number
|
||||
(you can only use vr0-vr7 here)
|
||||
|
||||
RA, RB: general purpose register number
|
||||
|
||||
UIMM: unsigned immediate value
|
||||
SIMM: signed immediate value
|
||||
|
||||
PERMh: upper 3 bits of a permutation
|
||||
PERMl: lower 5 bits of a permutation
|
||||
|
||||
x, y, z: unknown immediate values
|
||||
|
||||
=================================================================
|
||||
lvewx128 Load Vector128 Element Word Indexed
|
||||
|0 0 0 1 0 0| VD128 | RA | RB |0 0 0 1 0 0 0|VDh|1 1|
|
||||
|
||||
lvewx128 vr(VD128), r(RA), r(RB)
|
||||
|
||||
|
||||
=================================================================
|
||||
lvlx128 Load Vector128 Left Indexed
|
||||
|0 0 0 1 0 0| VD128 | RA | RB |1 0 0 0 0 0 0|VDh|1 1|
|
||||
|
||||
lvlx128 vr(VD128), r(RA), r(RB)
|
||||
|
||||
|
||||
=================================================================
|
||||
lvrx128 Load Vector128 Right Indexed
|
||||
|0 0 0 1 0 0| VD128 | RA | RB |1 0 0 0 1 0 0|VDh|1 1|
|
||||
|
||||
lvrx128 vr(VD128), r(RA), r(RB)
|
||||
|
||||
|
||||
=================================================================
|
||||
lvlxl128 Load Vector128 Left Indexed LRU
|
||||
|0 0 0 1 0 0| VD128 | RA | RB |1 1 0 0 0 0 0|VDh|1 1|
|
||||
|
||||
lvlxl128 vr(VD128), r(RA), r(RB)
|
||||
|
||||
|
||||
=================================================================
|
||||
lvrxl128 Load Vector128 Right Indexed LRU
|
||||
|0 0 0 1 0 0| VD128 | RA | RB |1 1 0 0 1 0 0|VDh|1 1|
|
||||
|
||||
lvrxl128 vr(VD128), r(RA), r(RB)
|
||||
|
||||
|
||||
=================================================================
|
||||
lvsl128 Load Vector128 for Shift Left
|
||||
|0 0 0 1 0 0| VD128 | RA | RB |0 0 0 0 0 0 0|VDh|1 1|
|
||||
|
||||
lvsl128 vr(VD128), r(RA), r(RB)
|
||||
|
||||
|
||||
=================================================================
|
||||
lvsr128 Load Vector128 for Shift Right
|
||||
|0 0 0 1 0 0| VD128 | RA | RB |0 0 0 0 1 0 0|VDh|1 1|
|
||||
|
||||
lvsr128 vr(VD128), r(RA), r(RB)
|
||||
|
||||
|
||||
=================================================================
|
||||
lvx128 Load Vector128 Indexed
|
||||
|0 0 0 1 0 0| VD128 | RA | RB |0 0 0 1 1 0 0|VDh|1 1|
|
||||
|
||||
lvx128 vr(VD128), r(RA), r(RB)
|
||||
|
||||
|
||||
=================================================================
|
||||
lvxl128 Load Vector128 Indexed LRU
|
||||
|0 0 0 1 0 0| VD128 | RA | RB |0 1 0 1 1 0 0|VDh|1 1|
|
||||
|
||||
lvxl128 vr(VD128), r(RA), r(RB)
|
||||
|
||||
|
||||
=================================================================
|
||||
stewx128 Store Vector128 Element Word Indexed
|
||||
|0 0 0 1 0 0| VS128 | RA | RB |0 1 1 0 0 0 0|VDh|1 1|
|
||||
|
||||
stvewx128 vr(VS128), r(RA), r(RB)
|
||||
|
||||
|
||||
=================================================================
|
||||
stvlx128 Store Vector128 Left Indexed
|
||||
|0 0 0 1 0 0| VS128 | RA | RB |1 0 1 0 0 0 0|VDh|1 1|
|
||||
|
||||
stvlx128 vr(VS128), r(RA), r(RB)
|
||||
|
||||
|
||||
=================================================================
|
||||
stvlxl128 Store Vector128 Left Indexed LRU
|
||||
|0 0 0 1 0 0| VS128 | RA | RB |1 1 1 0 0 0 0|VDh|1 1|
|
||||
|
||||
lvlxl128 vr(VS128), r(RA), r(RB)
|
||||
|
||||
|
||||
=================================================================
|
||||
stvrx128 Store Vector128 Right Indexed
|
||||
|0 0 0 1 0 0| VS128 | RA | RB |1 0 1 0 1 0 0|VDh|1 1|
|
||||
|
||||
stvrx128 vr(VS128), r(RA), r(RB)
|
||||
|
||||
|
||||
=================================================================
|
||||
stvrxl128 Store Vector128 Right Indexed LRU
|
||||
|0 0 0 1 0 0| VS128 | RA | RB |1 1 1 0 1 0 0|VDh|1 1|
|
||||
|
||||
stvrxl128 vr(VS128), r(RA), r(RB)
|
||||
|
||||
|
||||
=================================================================
|
||||
stvx128 Store Vector128 Indexed
|
||||
|0 0 0 1 0 0| VS128 | RA | RB |0 0 1 1 1 0 0|VDh|1 1|
|
||||
|
||||
stvx128 vr(VS128), r(RA), r(RB)
|
||||
|
||||
|
||||
=================================================================
|
||||
stvxl128 Store Vector128 Indexed LRU
|
||||
|0 0 0 1 0 0| VS128 | RA | RB |0 1 1 1 1 0 0|VDh|1 1|
|
||||
|
||||
stvxl128 vr(VS128), r(RA), r(RB)
|
||||
|
||||
|
||||
=================================================================
|
||||
vaddfp128 Vector128 Add Floating Point
|
||||
|0 0 0 1 0 1| VD128 | VA128 | VB128 |A|0 0 0 0|a|1|VDh|VBh|
|
||||
|
||||
vaddfp128 vr(VD128), vr(VA128), vr(VB128)
|
||||
|
||||
|
||||
=================================================================
|
||||
vand128 Vector128 Logical AND
|
||||
|0 0 0 1 0 1| VD128 | VA128 | VB128 |A|1 0 0 0|a|1|VDh|VBh|
|
||||
|
||||
vand128 vr(VD128), vr(VA128), vr(VB128)
|
||||
|
||||
|
||||
=================================================================
|
||||
vandc128 Vector128 Logical AND
|
||||
with Complement
|
||||
|0 0 0 1 0 1| VD128 | VA128 | VB128 |A|1 0 1 0|a|1|VDh|VBh|
|
||||
|
||||
vandc128 vr(VD128), vr(VA128), vr(VB128)
|
||||
|
||||
|
||||
=================================================================
|
||||
vcfpsxws128 Vector128 Convert From Floating-Point to
|
||||
Signed Fixed-Point Word Saturate
|
||||
|0 0 0 1 1 0| VD128 | SIMM | VB128 |0 1 0 0 0 1 1|VDh|VBh|
|
||||
|
||||
vcfpsxws128 vr(VD128), vr(VB128), SIMM
|
||||
|
||||
|
||||
=================================================================
|
||||
vcfpuxws128 Vector128 Convert From Floating-Point to
|
||||
Unsigned Fixed-Point Word Saturate
|
||||
|0 0 0 1 1 0| VD128 | UIMM | VB128 |0 1 0 0 1 1 1|VDh|VBh|
|
||||
|
||||
vcfpuxws128 vr(VD128), vr(VB128), UIMM
|
||||
|
||||
|
||||
=================================================================
|
||||
vcmpbfp128 Vector128 Compare Bounds
|
||||
Floating Point
|
||||
|0 0 0 1 1 0| VD128 | VA128 | VB128 |A|0 1 1|R|a|0|VDh|VBh|
|
||||
|
||||
vcmpbfp128 vr(VD128), vr(VA128), vr(VB128) (R == 0)
|
||||
vcmpbfp128. vr(VD128), vr(VA128), vr(VB128) (R == 1)
|
||||
|
||||
|
||||
=================================================================
|
||||
vcmpeqfp128 Vector128 Compare Equal-to
|
||||
Floating Point
|
||||
|0 0 0 1 1 0| VD128 | VA128 | VB128 |A|0 0 0|R|a|0|VDh|VBh|
|
||||
|
||||
vcmpeqfp128 vr(VD128), vr(VA128), vr(VB128) (R == 0)
|
||||
vcmpeqfp128. vr(VD128), vr(VA128), vr(VB128) (R == 1)
|
||||
|
||||
|
||||
=================================================================
|
||||
vcmpequw128 Vector128 Compare Equal-to
|
||||
Unsigned Word
|
||||
|0 0 0 1 1 0| VD128 | VA128 | VB128 |A|1 0 0|R|a|0|VDh|VBh|
|
||||
|
||||
vcmpequw128 vr(VD128), vr(VA128), vr(VB128) (R == 0)
|
||||
vcmpequw128. vr(VD128), vr(VA128), vr(VB128) (R == 1)
|
||||
|
||||
|
||||
=================================================================
|
||||
vcmpgefp128 Vector128 Compare Greater-Than-
|
||||
or-Equal-to Floating Point
|
||||
|0 0 0 1 1 0| VD128 | VA128 | VB128 |A|0 0 1|R|a|0|VDh|VBh|
|
||||
|
||||
vcmpgefp128 vr(VD128), vr(VA128), vr(VB128) (R == 0)
|
||||
vcmpgefp128. vr(VD128), vr(VA128), vr(VB128) (R == 1)
|
||||
|
||||
|
||||
=================================================================
|
||||
vcmpgtfp128 Vector128 Compare Greater-Than
|
||||
Floating-Point
|
||||
|0 0 0 1 1 0| VD128 | VA128 | VB128 |A|0 1 0|R|a|0|VDh|VBh|
|
||||
|
||||
vcmpgtfp128 vr(VD128), vr(VA128), vr(VB128) (R == 0)
|
||||
vcmpgtfp128. vr(VD128), vr(VA128), vr(VB128) (R == 1)
|
||||
|
||||
|
||||
=================================================================
|
||||
vcsxwfp128 Vector128 Convert From Signed Fixed-Point
|
||||
Word to Floating-Point
|
||||
|0 0 0 1 1 0| VD128 | UIMM | VB128 |0 1 0 1 0 1 1|VDh|VBh|
|
||||
|
||||
vcsxwfp128 vr(VD128), vr(VB128), SIMM
|
||||
|
||||
|
||||
=================================================================
|
||||
vcuxwfp128 Vector128 Convert From Unsigned Fixed-Point
|
||||
Word to Floating-Point
|
||||
|0 0 0 1 1 0| VD128 | UIMM | VB128 |0 1 0 1 1 1 1|VDh|VBh|
|
||||
|
||||
vcuxwfp128 vr(VD128), vr(VB128), UIMM
|
||||
|
||||
|
||||
=================================================================
|
||||
vexptefp128 Vector128 2 Raised to the Exponent
|
||||
Estimate Floating Point
|
||||
|0 0 0 1 1 0| VD128 |0 0 0 0 0| VB128 |1 1 0 1 0 1 1|VDh|VBh|
|
||||
|
||||
vexptefp128 vr(VD128), vr(VB128)
|
||||
|
||||
|
||||
=================================================================
|
||||
vlogefp128 Vector128 Log2 Estimate
|
||||
Floating Point
|
||||
|0 0 0 1 1 0| VD128 |0 0 0 0 0| VB128 |1 1 0 1 1 1 1|VDh|VBh|
|
||||
|
||||
vlogefp128 vr(VD128), vr(VB128)
|
||||
|
||||
|
||||
=================================================================
|
||||
vmaddcfp128 Vector128 Multiply Add
|
||||
Floating Point
|
||||
|0 0 0 1 0 1| VDS128 | VA128 | VB128 |A|0 1 0 0|a|1|VDh|VBh|
|
||||
|
||||
vmaddcfp128 vr(VDS128), vr(VA128), vr(VSD128), vr(VB128)
|
||||
|
||||
|
||||
=================================================================
|
||||
vmaddfp128 Vector128 Multiply Add
|
||||
Floating Point
|
||||
|0 0 0 1 0 1| VDS128 | VA128 | VB128 |A|0 0 1 1|a|1|VDh|VBh|
|
||||
|
||||
vmaddfp128 vr(VDS128), vr(VA128), vr(VB128), vr(VDS128)
|
||||
|
||||
|
||||
=================================================================
|
||||
vmaxfp128 Vector128 Maximum
|
||||
Floating Point
|
||||
|0 0 0 1 1 0| VD128 | VA128 | VB128 |A|1 0 1 0|a|0|VDh|VBh|
|
||||
|
||||
vmaxfp128 vr(VD128), vr(VA128), vr(VB128)
|
||||
|
||||
|
||||
=================================================================
|
||||
vminfp128 Vector128 Minimum
|
||||
Floating Point
|
||||
|0 0 0 1 1 0| VD128 | VA128 | VB128 |A|1 0 1 1|a|0|VDh|VBh|
|
||||
|
||||
vminfp128 vr(VD128), vr(VA128), vr(VB128)
|
||||
|
||||
|
||||
=================================================================
|
||||
vmrghw128 Vector128 Merge High Word
|
||||
|0 0 0 1 1 0| VD128 | VA128 | VB128 |A|1 1 0 0|a|0|VDh|VBh|
|
||||
|
||||
vmrghw128 vr(VD128), vr(VA128), vr(VB128)
|
||||
|
||||
|
||||
=================================================================
|
||||
vmrglw128 Vector128 Merge Low Word
|
||||
|0 0 0 1 1 0| VD128 | VA128 | VB128 |A|1 1 0 1|a|0|VDh|VBh|
|
||||
|
||||
vmrglw128 vr(VD128), vr(VA128), vr(VB128)
|
||||
|
||||
|
||||
=================================================================
|
||||
vmsum3fp128 Vector128 Multiply Sum 3-way
|
||||
Floating Point
|
||||
|0 0 0 1 0 1| VD128 | VA128 | VB128 |A|0 1 1 0|a|1|VDh|VBh|
|
||||
|
||||
vmsub3fp128 vr(VD128), vr(VA128), vr(VB128)
|
||||
|
||||
|
||||
=================================================================
|
||||
vmsum4fp128 Vector128 Multiply Sum 4-way
|
||||
Floating-Point
|
||||
|0 0 0 1 0 1| VD128 | VA128 | VB128 |A|0 1 1 1|a|1|VDh|VBh|
|
||||
|
||||
vmsub4fp128 vr(VD128), vr(VA128), vr(VB128)
|
||||
|
||||
|
||||
=================================================================
|
||||
vmulfp128 Vector128 Multiply
|
||||
Floating-Point
|
||||
|0 0 0 1 0 1| VD128 | VA128 | VB128 |A|0 0 1 0|a|1|VDh|VBh|
|
||||
|
||||
vmulfp128 vr(VD128), vr(VA128), vr(VB128)
|
||||
|
||||
|
||||
=================================================================
|
||||
vnmsubfp128 Vector128 Negative Multiply-Subtract
|
||||
Floating Point
|
||||
|0 0 0 1 0 1| VDS128 | VA128 | VB128 |A|0 1 0 1|a|1|VDh|VBh|
|
||||
|
||||
vnmsubfp128 vr(VDS128), vr(VA128), vr(VB128), vr(VDS128)
|
||||
|
||||
|
||||
=================================================================
|
||||
vnor128 Vector128 Logical NOR
|
||||
|0 0 0 1 0 1| VD128 | VA128 | VB128 |A|1 0 1 0|a|1|VDh|VBh|
|
||||
|
||||
vnor128 vr(VD128), vr(VA128), vr(VB128)
|
||||
|
||||
|
||||
=================================================================
|
||||
vor128 Vector128 Logical OR
|
||||
|0 0 0 1 0 1| VD128 | VA128 | VB128 |A|1 0 1 1|a|1|VDh|VBh|
|
||||
|
||||
vor128 vr(VD128), vr(VA128), vr(VB128)
|
||||
|
||||
|
||||
=================================================================
|
||||
vperm128 Vector128 Permutation
|
||||
|0 0 0 1 0 1| VD128 | VA128 | VB128 |A|0| VC |a|0|VDh|VBh|
|
||||
|
||||
vperm128 vr(VD128), vr(VA128), vr(VB128), vr(VC)
|
||||
|
||||
|
||||
=================================================================
|
||||
vpermwi128 Vector128 Permutate Word Immediate
|
||||
|0 0 0 1 1 0| VD128 | PERMl | VB128 |0|1|PERMh|0|1|VDh|VBh|
|
||||
|
||||
vpermwi128 vr(VD128), vr(VB128), (PERMh << 5 | PERMl)
|
||||
|
||||
|
||||
=================================================================
|
||||
vpkd3d128 Vector128 Pack D3Dtype, Rotate Left
|
||||
Immediate and Mask Insert
|
||||
|0 0 0 1 1 0| VD128 | x | y | VB128 |1 1 0| z |0 1|VDh|VBh|
|
||||
|
||||
vpkd3d128 vr(VD128), vr(VB128), x, y, z
|
||||
|
||||
|
||||
=================================================================
|
||||
vpkshss128 Vector128 Pack Signed Half Word
|
||||
Signed Saturate
|
||||
|0 0 0 1 0 1| VD128 | VA128 | VB128 |A|1 0 0 0|a|0|VDh|VBh|
|
||||
|
||||
vpkshss128 vr(VD128), vr(VA128), vr(VB128)
|
||||
|
||||
|
||||
=================================================================
|
||||
vpkshus128 Vector128 Pack Signed Half Word
|
||||
Unsigned Saturate
|
||||
|0 0 0 1 0 1| VD128 | VA128 | VB128 |A|1 0 0 1|a|0|VDh|VBh|
|
||||
|
||||
vpkshus128 vr(VD128), vr(VA128), vr(VB128)
|
||||
|
||||
|
||||
=================================================================
|
||||
vpkswss128 Vector128 Pack Signed Word
|
||||
Signed Saturate
|
||||
|0 0 0 1 0 1| VD128 | VA128 | VB128 |A|1 0 1 0|a|0|VDh|VBh|
|
||||
|
||||
vpkswss128 vr(VD128), vr(VA128), vr(VB128)
|
||||
|
||||
|
||||
=================================================================
|
||||
vpkswus128 Vector128 Pack Signed Word
|
||||
Unsigned Saturate
|
||||
|0 0 0 1 0 1| VD128 | VA128 | VB128 |A|1 0 1 1|a|0|VDh|VBh|
|
||||
|
||||
vpkswus128 vr(VD128), vr(VA128), vr(VB128)
|
||||
|
||||
|
||||
=================================================================
|
||||
vpkuhum128 Vector128 Pack Unsigned Half Word
|
||||
Unsigned Modulo
|
||||
|0 0 0 1 0 1| VD128 | VA128 | VB128 |A|1 1 0 0|a|0|VDh|VBh|
|
||||
|
||||
vpkuhum128 vr(VD128), vr(VA128), vr(VB128)
|
||||
|
||||
|
||||
=================================================================
|
||||
vpkuhus128 Vector128 Pack Unsigned Half Word
|
||||
Unsigned Saturate
|
||||
|0 0 0 1 0 1| VD128 | VA128 | VB128 |A|1 1 0 1|a|0|VDh|VBh|
|
||||
|
||||
vpkuhus128 vr(VD128), vr(VA128), vr(VB128)
|
||||
|
||||
|
||||
=================================================================
|
||||
vpkuwum128 Vector128 Pack Unsigned Word
|
||||
Unsigned Modulo
|
||||
|0 0 0 1 0 1| VD128 | VA128 | VB128 |A|1 1 1 0|a|0|VDh|VBh|
|
||||
|
||||
vpkuwum128 vr(VD128), vr(VA128), vr(VB128)
|
||||
|
||||
|
||||
=================================================================
|
||||
vpkuwus128 Vector128 Pack Unsigned Word
|
||||
Unsigned Saturate
|
||||
|0 0 0 1 0 1| VD128 | VA128 | VB128 |A|1 1 1 1|a|0|VDh|VBh|
|
||||
|
||||
vpkuwus128 vr(VD128), vr(VA128), vr(VB128)
|
||||
|
||||
|
||||
=================================================================
|
||||
vrefp128 Vector128 Reciprocal Estimate
|
||||
Floating Point
|
||||
|0 0 0 1 1 0| VD128 |0 0 0 0 0| VB128 |1 1 0 0 0 1 1|VDh|VBh|
|
||||
|
||||
vrefp128 vr(VD128), vr(VB128)
|
||||
|
||||
|
||||
=================================================================
|
||||
vrfim128 Vector128 Round to Floating-Point
|
||||
Integer toward -oo
|
||||
|0 0 0 1 1 0| VD128 |0 0 0 0 0| VB128 |0 1 1 0 0 1 1|VDh|VBh|
|
||||
|
||||
vrfim128 vr(VD128), vr(VB128)
|
||||
|
||||
|
||||
=================================================================
|
||||
vrfin128 Vector128 Round to Floating-Point
|
||||
Integer toward Nearest
|
||||
|0 0 0 1 1 0| VD128 |0 0 0 0 0| VB128 |0 1 1 0 1 1 1|VDh|VBh|
|
||||
|
||||
vrfin128 vr(VD128), vr(VB128)
|
||||
|
||||
|
||||
=================================================================
|
||||
vrfip128 Vector128 Round to Floating-Point
|
||||
Integer toward +oo
|
||||
|0 0 0 1 1 0| VD128 |0 0 0 0 0| VB128 |0 1 1 1 0 1 1|VDh|VBh|
|
||||
|
||||
vrfip128 vr(VD128), vr(VB128)
|
||||
|
||||
|
||||
=================================================================
|
||||
vrfiz128 Vector128 Round to Floating-Point
|
||||
Integer toward Zero
|
||||
|0 0 0 1 1 0| VD128 |0 0 0 0 0| VB128 |0 1 1 1 1 1 1|VDh|VBh|
|
||||
|
||||
vrfiz128 vr(VD128), vr(VB128)
|
||||
|
||||
|
||||
=================================================================
|
||||
vrlimi128 Vector128 Rotate Left Immediate
|
||||
and Mask Insert
|
||||
|0 0 0 1 1 0| VD128 | UIMM | VB128 |1 1 1| z |0 1|VDh|VBh|
|
||||
|
||||
vrlimi128 vr(VD128), vr(VB128), UIMM, z
|
||||
|
||||
|
||||
=================================================================
|
||||
vrlw128 Vector128 Rotate Left Word
|
||||
|0 0 0 1 0 1| VD128 | VA128 | VB128 |A|0 0 0 1|a|1|VDh|VBh|
|
||||
|
||||
vrlw128 vr(VD128), vr(VA128), vr(VB128)
|
||||
|
||||
|
||||
=================================================================
|
||||
vrsqrtefp128 Vector128 Reciprocal Square Root
|
||||
Estimate Floating Point
|
||||
|0 0 0 1 1 0| VD128 |0 0 0 0 0| VB128 |1 1 0 0 1 1 1|VDh|VBh|
|
||||
|
||||
vrsqrtefp128 vr(VD128), vr(VB128)
|
||||
|
||||
|
||||
=================================================================
|
||||
vsel128 Vector128 Select
|
||||
|0 0 0 1 0 1| VDS128 | VA128 | VB128 |A|1 1 0 1|a|1|VDh|VBh|
|
||||
|
||||
vsel128 vr(VDS128), vr(VA128), vr(VB128), vr(VDS128)
|
||||
|
||||
|
||||
=================================================================
|
||||
vsldoi128 Vector128 Shift Left Double
|
||||
by Octet Immediate
|
||||
|0 0 0 1 0 0| VD128 | VA128 | VB128 |A| SHB |a|1|VDh|VBh|
|
||||
|
||||
vsldoi128 vr(VD128), vr(VA128), vr(VB128), SHB
|
||||
|
||||
|
||||
=================================================================
|
||||
vslo128 Vector128 Shift Left Octet
|
||||
|0 0 0 1 0 1| VD128 | VA128 | VB128 |A|1 1 1 0|a|1|VDh|VBh|
|
||||
|
||||
vslo128 vr(VD128), vr(VA128), vr(VB128)
|
||||
|
||||
|
||||
=================================================================
|
||||
vslw128 Vector128 Shift Left Word
|
||||
|0 0 0 1 1 0| VD128 | VA128 | VB128 |A|0 0 1 1|a|1|VDh|VBh|
|
||||
|
||||
vslw128 vr(VD128), vr(VA128), vr(VB128)
|
||||
|
||||
|
||||
=================================================================
|
||||
vspltisw128 Vector128 Splat Immediate
|
||||
Signed Word
|
||||
|0 0 0 1 1 0| VD128 | SIMM | VB128 |1 1 1 0 1 1 1|VDh|VBh|
|
||||
|
||||
vspltisw128 vr(VD128), vr(VB128), SIMM
|
||||
|
||||
|
||||
=================================================================
|
||||
vspltw128 Vector128 Splat Word
|
||||
|0 0 0 1 1 0| VD128 | UIMM | VB128 |1 1 1 0 0 1 1|VDh|VBh|
|
||||
|
||||
vspltw128 vr(VD128), vr(VB128), UIMM
|
||||
|
||||
|
||||
=================================================================
|
||||
vsraw128 Vector128 Shift Right
|
||||
Arithmetic Word
|
||||
|0 0 0 1 1 0| VD128 | VA128 | VB128 |A|0 1 0 1|a|1|VDh|VBh|
|
||||
|
||||
vsraw128 vr(VD128), vr(VA128), vr(VB128)
|
||||
|
||||
|
||||
=================================================================
|
||||
vsro128 Vector128 Shift Right Octet
|
||||
|0 0 0 1 1 0| VD128 | VA128 | VB128 |A|1 1 1 1|a|1|VDh|VBh|
|
||||
|
||||
vsro128 vr(VD128), vr(VA128), vr(VB128)
|
||||
|
||||
|
||||
=================================================================
|
||||
vsrw128 Vector128 Shift Right Word
|
||||
|0 0 0 1 1 0| VD128 | VA128 | VB128 |A|0 1 1 1|a|1|VDh|VBh|
|
||||
|
||||
vsrw128 vr(VD128), vr(VA128), vr(VB128)
|
||||
|
||||
|
||||
=================================================================
|
||||
vsubfp128 Vector128 Subtract Floating Point
|
||||
|0 0 0 1 0 1| VD128 | VA128 | VB128 |A|0 0 0 1|a|1|VDh|VBh|
|
||||
|
||||
vsubfp128 vr(VD128), vr(VA128), vr(VB128)
|
||||
|
||||
|
||||
=================================================================
|
||||
vupkd3d128 Vector128 Unpack D3Dtype
|
||||
|0 0 0 1 1 0| VD128 | UIMM | VB128 |1 1 1 1 1 1 1|VDh|VBh|
|
||||
|
||||
vupkd3d128 vr(VD128), vr(VB128), UIMM
|
||||
|
||||
|
||||
=================================================================
|
||||
vupkhsb128 Vector128 Unpack
|
||||
High Signed Byte
|
||||
|0 0 0 1 1 0| VD128 |0 0 0 0 0| VB128 |0 1 1 1 0 0 0|VDh|VBh|
|
||||
|
||||
vupkhsb128 vr(VD128), vr(VB128)
|
||||
|
||||
|
||||
=================================================================
|
||||
vupklsb128 Vector128 Unpack
|
||||
Low Signed Byte
|
||||
|0 0 0 1 1 0| VD128 |0 0 0 0 0| VB128 |0 1 1 1 1 0 0|VDh|VBh|
|
||||
|
||||
vupkhsb128 vr(VD128), vr(VB128)
|
||||
|
||||
|
||||
=================================================================
|
||||
vxor128 Vector128 Logical XOR
|
||||
|0 0 0 1 0 1| VD128 | VA128 | VB128 |A|1 1 0 0|a|1|VDh|VBh|
|
||||
|
||||
vxor128 vr(VD128), vr(VA128), vr(VB128)
|
||||
130
include/rex/assert.h
Normal file
130
include/rex/assert.h
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2014 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*
|
||||
* @modified Tom Clay, 2026 - Adapted for ReXGlue runtime
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include <rex/platform.h>
|
||||
|
||||
namespace rex {
|
||||
|
||||
#define static_assert_size(type, size) \
|
||||
static_assert(sizeof(type) == size, "bad definition for " #type ": must be " #size " bytes")
|
||||
|
||||
#ifdef NDEBUG
|
||||
// References expr in an unevaluated short-circuit so -Wunused doesn't fire.
|
||||
#define rex_assert(expr) ((void)(false && (expr)))
|
||||
#else
|
||||
#define rex_assert(expr) assert(expr)
|
||||
#endif
|
||||
|
||||
#define __REX_EXPAND(x) x
|
||||
#define __REX_ARGC(...) \
|
||||
__REX_EXPAND(__REX_ARGC_IMPL(__VA_ARGS__, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0))
|
||||
#define __REX_ARGC_IMPL(x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15, N, ...) N
|
||||
#define __REX_MACRO_DISPATCH(func, ...) __REX_MACRO_DISPATCH_(func, __REX_ARGC(__VA_ARGS__))
|
||||
#define __REX_MACRO_DISPATCH_(func, nargs) __REX_MACRO_DISPATCH__(func, nargs)
|
||||
#define __REX_MACRO_DISPATCH__(func, nargs) func##nargs
|
||||
|
||||
/// Assert failure handler that logs the message before aborting
|
||||
/// @param file Source file (__FILE__)
|
||||
/// @param line Line number (__LINE__)
|
||||
/// @param expr Expression that failed (stringified)
|
||||
/// @param message User-provided message explaining the failure
|
||||
[[noreturn]] inline void rex_assert_fail(const char* file, int line, const char* expr,
|
||||
const char* message) {
|
||||
fprintf(stderr, "Assertion failed: %s\n Expression: %s\n Location: %s:%d\n", message, expr,
|
||||
file, line);
|
||||
fflush(stderr);
|
||||
std::abort();
|
||||
}
|
||||
|
||||
#define assert_always(...) rex_assert(false)
|
||||
|
||||
#define assert_true(...) __REX_MACRO_DISPATCH(assert_true, __VA_ARGS__)(__VA_ARGS__)
|
||||
#define assert_true1(expr) rex_assert(expr)
|
||||
#define assert_true2(expr, message) \
|
||||
do { \
|
||||
if (!(expr)) { \
|
||||
rex_assert_fail(__FILE__, __LINE__, #expr, message); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define assert_false(...) __REX_MACRO_DISPATCH(assert_false, __VA_ARGS__)(__VA_ARGS__)
|
||||
#define assert_false1(expr) rex_assert(!(expr))
|
||||
#define assert_false2(expr, message) \
|
||||
do { \
|
||||
if (!!(expr)) { \
|
||||
rex_assert_fail(__FILE__, __LINE__, "!(" #expr ")", message); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define assert_zero(...) __REX_MACRO_DISPATCH(assert_zero, __VA_ARGS__)(__VA_ARGS__)
|
||||
#define assert_zero1(expr) rex_assert((expr) == 0)
|
||||
#define assert_zero2(expr, message) \
|
||||
do { \
|
||||
if ((expr) != 0) { \
|
||||
rex_assert_fail(__FILE__, __LINE__, "(" #expr ") == 0", message); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define assert_not_zero(...) __REX_MACRO_DISPATCH(assert_not_zero, __VA_ARGS__)(__VA_ARGS__)
|
||||
#define assert_not_zero1(expr) rex_assert((expr) != 0)
|
||||
#define assert_not_zero2(expr, message) \
|
||||
do { \
|
||||
if ((expr) == 0) { \
|
||||
rex_assert_fail(__FILE__, __LINE__, "(" #expr ") != 0", message); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define assert_null(...) __REX_MACRO_DISPATCH(assert_null, __VA_ARGS__)(__VA_ARGS__)
|
||||
#define assert_null1(expr) rex_assert((expr) == nullptr)
|
||||
#define assert_null2(expr, message) \
|
||||
do { \
|
||||
if ((expr) != nullptr) { \
|
||||
rex_assert_fail(__FILE__, __LINE__, "(" #expr ") == nullptr", message); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define assert_not_null(...) __REX_MACRO_DISPATCH(assert_not_null, __VA_ARGS__)(__VA_ARGS__)
|
||||
#define assert_not_null1(expr) rex_assert((expr) != nullptr)
|
||||
#define assert_not_null2(expr, message) \
|
||||
do { \
|
||||
if ((expr) == nullptr) { \
|
||||
rex_assert_fail(__FILE__, __LINE__, "(" #expr ") != nullptr", message); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define assert_unhandled_case(variable) assert_always("unhandled switch(" #variable ") case")
|
||||
|
||||
/// Marks code as unreachable. Invokes undefined behavior if reached.
|
||||
/// Use in switch default cases and after exhaustive if-else chains.
|
||||
#define rex_unreachable() std::unreachable()
|
||||
|
||||
// Fatal error that terminates the program with a message
|
||||
[[noreturn]] inline void FatalError(const char* message) {
|
||||
// Print to stderr and terminate
|
||||
fprintf(stderr, "FATAL ERROR: %s\n", message);
|
||||
fflush(stderr);
|
||||
std::abort();
|
||||
}
|
||||
|
||||
[[noreturn]] inline void FatalError(const std::string& message) {
|
||||
FatalError(message.c_str());
|
||||
}
|
||||
|
||||
} // namespace rex
|
||||
34
include/rex/audio/audio_driver.h
Normal file
34
include/rex/audio/audio_driver.h
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2013 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*
|
||||
* @modified Tom Clay, 2026 - Adapted for ReXGlue runtime
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <rex/kernel.h>
|
||||
#include <rex/memory.h>
|
||||
|
||||
namespace rex::audio {
|
||||
|
||||
class AudioDriver {
|
||||
public:
|
||||
explicit AudioDriver(memory::Memory* memory);
|
||||
virtual ~AudioDriver();
|
||||
|
||||
virtual void SubmitFrame(uint32_t samples_ptr) = 0;
|
||||
|
||||
protected:
|
||||
inline uint8_t* TranslatePhysical(uint32_t guest_address) const {
|
||||
return memory_->TranslatePhysical(guest_address);
|
||||
}
|
||||
|
||||
memory::Memory* memory_ = nullptr;
|
||||
};
|
||||
|
||||
} // namespace rex::audio
|
||||
101
include/rex/audio/audio_system.h
Normal file
101
include/rex/audio/audio_system.h
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2013 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*
|
||||
* @modified Tom Clay, 2026 - Adapted for ReXGlue runtime
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <queue>
|
||||
|
||||
#include <rex/kernel.h>
|
||||
#include <rex/memory.h>
|
||||
#include <rex/system/interfaces/audio.h>
|
||||
#include <rex/system/function_dispatcher.h>
|
||||
#include <rex/system/xthread.h>
|
||||
#include <rex/thread.h>
|
||||
#include <rex/thread/mutex.h>
|
||||
|
||||
namespace rex::stream {
|
||||
class ByteStream;
|
||||
} // namespace rex::stream
|
||||
|
||||
namespace rex::audio {
|
||||
|
||||
constexpr memory::fourcc_t kAudioSaveSignature = memory::make_fourcc("XAUD");
|
||||
|
||||
class AudioDriver;
|
||||
class XmaDecoder;
|
||||
|
||||
class AudioSystem : public system::IAudioSystem {
|
||||
public:
|
||||
virtual ~AudioSystem();
|
||||
|
||||
memory::Memory* memory() const { return memory_; }
|
||||
runtime::FunctionDispatcher* function_dispatcher() const { return function_dispatcher_; }
|
||||
XmaDecoder* xma_decoder() const { return xma_decoder_.get(); }
|
||||
|
||||
virtual X_STATUS Setup(system::KernelState* kernel_state);
|
||||
virtual void Shutdown();
|
||||
|
||||
X_STATUS RegisterClient(uint32_t callback, uint32_t callback_arg, size_t* out_index);
|
||||
void UnregisterClient(size_t index);
|
||||
void SubmitFrame(size_t index, uint32_t samples_ptr);
|
||||
|
||||
bool Save(stream::ByteStream* stream);
|
||||
bool Restore(stream::ByteStream* stream);
|
||||
|
||||
bool is_paused() const { return paused_; }
|
||||
void Pause();
|
||||
void Resume();
|
||||
|
||||
protected:
|
||||
explicit AudioSystem(runtime::FunctionDispatcher* function_dispatcher);
|
||||
|
||||
virtual void Initialize();
|
||||
|
||||
void WorkerThreadMain();
|
||||
|
||||
virtual X_STATUS CreateDriver(size_t index, rex::thread::Semaphore* semaphore,
|
||||
AudioDriver** out_driver) = 0;
|
||||
virtual void DestroyDriver(AudioDriver* driver) = 0;
|
||||
|
||||
static constexpr size_t kMaximumQueuedFrames = 64;
|
||||
|
||||
memory::Memory* memory_ = nullptr;
|
||||
runtime::FunctionDispatcher* function_dispatcher_ = nullptr;
|
||||
std::unique_ptr<XmaDecoder> xma_decoder_;
|
||||
uint32_t queued_frames_;
|
||||
|
||||
std::atomic<bool> worker_running_ = {false};
|
||||
system::object_ref<system::XHostThread> worker_thread_;
|
||||
|
||||
rex::thread::global_critical_region global_critical_region_;
|
||||
static const size_t kMaximumClientCount = 8;
|
||||
struct {
|
||||
AudioDriver* driver;
|
||||
uint32_t callback;
|
||||
uint32_t callback_arg;
|
||||
uint32_t wrapped_callback_arg;
|
||||
bool in_use;
|
||||
} clients_[kMaximumClientCount];
|
||||
|
||||
int FindFreeClient();
|
||||
|
||||
std::unique_ptr<rex::thread::Semaphore> client_semaphores_[kMaximumClientCount];
|
||||
// Event is always there in case we have no clients.
|
||||
std::unique_ptr<rex::thread::Event> shutdown_event_;
|
||||
rex::thread::WaitHandle* wait_handles_[kMaximumClientCount + 1];
|
||||
|
||||
bool paused_ = false;
|
||||
rex::thread::Fence pause_fence_;
|
||||
std::unique_ptr<rex::thread::Event> resume_event_;
|
||||
};
|
||||
|
||||
} // namespace rex::audio
|
||||
103
include/rex/audio/conversion.h
Normal file
103
include/rex/audio/conversion.h
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2021 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*
|
||||
* @modified Tom Clay, 2026 - Adapted for ReXGlue runtime
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include <rex/platform.h>
|
||||
#include <rex/types.h>
|
||||
|
||||
namespace rex::audio::conversion {
|
||||
|
||||
#if REX_ARCH_AMD64
|
||||
inline void sequential_6_BE_to_interleaved_6_LE(float* output, const float* input,
|
||||
size_t ch_sample_count) {
|
||||
const uint32_t* in = reinterpret_cast<const uint32_t*>(input);
|
||||
uint32_t* out = reinterpret_cast<uint32_t*>(output);
|
||||
const __m128i byte_swap_shuffle =
|
||||
_mm_set_epi8(12, 13, 14, 15, 8, 9, 10, 11, 4, 5, 6, 7, 0, 1, 2, 3);
|
||||
for (size_t sample = 0; sample < ch_sample_count; sample++) {
|
||||
__m128i sample0 =
|
||||
_mm_set_epi32(in[3 * ch_sample_count + sample], in[2 * ch_sample_count + sample],
|
||||
in[1 * ch_sample_count + sample], in[0 * ch_sample_count + sample]);
|
||||
uint32_t sample1 = in[4 * ch_sample_count + sample];
|
||||
uint32_t sample2 = in[5 * ch_sample_count + sample];
|
||||
sample0 = _mm_shuffle_epi8(sample0, byte_swap_shuffle);
|
||||
_mm_storeu_si128(reinterpret_cast<__m128i*>(&out[sample * 6]), sample0);
|
||||
sample1 = rex::byte_swap(sample1);
|
||||
out[sample * 6 + 4] = sample1;
|
||||
sample2 = rex::byte_swap(sample2);
|
||||
out[sample * 6 + 5] = sample2;
|
||||
}
|
||||
}
|
||||
|
||||
inline void sequential_6_BE_to_interleaved_2_LE(float* output, const float* input,
|
||||
size_t ch_sample_count) {
|
||||
assert_true(ch_sample_count % 4 == 0);
|
||||
|
||||
const __m128i byte_swap_shuffle =
|
||||
_mm_set_epi8(12, 13, 14, 15, 8, 9, 10, 11, 4, 5, 6, 7, 0, 1, 2, 3);
|
||||
const __m128 half = _mm_set1_ps(0.5f);
|
||||
const __m128 two_fifths = _mm_set1_ps(1.0f / 2.5f);
|
||||
|
||||
// put center on left and right, discard low frequency
|
||||
for (size_t sample = 0; sample < ch_sample_count; sample += 4) {
|
||||
// load 4 samples from 6 channels each
|
||||
__m128 fl = _mm_loadu_ps(&input[0 * ch_sample_count + sample]);
|
||||
__m128 fr = _mm_loadu_ps(&input[1 * ch_sample_count + sample]);
|
||||
__m128 fc = _mm_loadu_ps(&input[2 * ch_sample_count + sample]);
|
||||
__m128 bl = _mm_loadu_ps(&input[4 * ch_sample_count + sample]);
|
||||
__m128 br = _mm_loadu_ps(&input[5 * ch_sample_count + sample]);
|
||||
// byte swap
|
||||
fl = _mm_castsi128_ps(_mm_shuffle_epi8(_mm_castps_si128(fl), byte_swap_shuffle));
|
||||
fr = _mm_castsi128_ps(_mm_shuffle_epi8(_mm_castps_si128(fr), byte_swap_shuffle));
|
||||
fc = _mm_castsi128_ps(_mm_shuffle_epi8(_mm_castps_si128(fc), byte_swap_shuffle));
|
||||
bl = _mm_castsi128_ps(_mm_shuffle_epi8(_mm_castps_si128(bl), byte_swap_shuffle));
|
||||
br = _mm_castsi128_ps(_mm_shuffle_epi8(_mm_castps_si128(br), byte_swap_shuffle));
|
||||
|
||||
__m128 center_halved = _mm_mul_ps(fc, half);
|
||||
__m128 left = _mm_add_ps(_mm_add_ps(fl, bl), center_halved);
|
||||
__m128 right = _mm_add_ps(_mm_add_ps(fr, br), center_halved);
|
||||
left = _mm_mul_ps(left, two_fifths);
|
||||
right = _mm_mul_ps(right, two_fifths);
|
||||
_mm_storeu_ps(&output[sample * 2], _mm_unpacklo_ps(left, right));
|
||||
_mm_storeu_ps(&output[(sample + 2) * 2], _mm_unpackhi_ps(left, right));
|
||||
}
|
||||
}
|
||||
#else
|
||||
inline void sequential_6_BE_to_interleaved_6_LE(float* output, const float* input,
|
||||
size_t ch_sample_count) {
|
||||
for (size_t sample = 0; sample < ch_sample_count; sample++) {
|
||||
for (size_t channel = 0; channel < 6; channel++) {
|
||||
output[sample * 6 + channel] = rex::byte_swap(input[channel * ch_sample_count + sample]);
|
||||
}
|
||||
}
|
||||
}
|
||||
inline void sequential_6_BE_to_interleaved_2_LE(float* output, const float* input,
|
||||
size_t ch_sample_count) {
|
||||
// Default 5.1 channel mapping is fl, fr, fc, lf, bl, br
|
||||
// https://docs.microsoft.com/en-us/windows/win32/xaudio2/xaudio2-default-channel-mapping
|
||||
for (size_t sample = 0; sample < ch_sample_count; sample++) {
|
||||
// put center on left and right, discard low frequency
|
||||
float fl = rex::byte_swap(input[0 * ch_sample_count + sample]);
|
||||
float fr = rex::byte_swap(input[1 * ch_sample_count + sample]);
|
||||
float fc = rex::byte_swap(input[2 * ch_sample_count + sample]);
|
||||
float br = rex::byte_swap(input[4 * ch_sample_count + sample]);
|
||||
float bl = rex::byte_swap(input[5 * ch_sample_count + sample]);
|
||||
float center_halved = fc * 0.5f;
|
||||
output[sample * 2] = (fl + bl + center_halved) * (1.0f / 2.5f);
|
||||
output[sample * 2 + 1] = (fr + br + center_halved) * (1.0f / 2.5f);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace rex::audio::conversion
|
||||
17
include/rex/audio/flags.h
Normal file
17
include/rex/audio/flags.h
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2013 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*
|
||||
* @modified Tom Clay, 2026 - Adapted for ReXGlue runtime
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <rex/cvar.h>
|
||||
|
||||
REXCVAR_DECLARE(bool, audio_mute);
|
||||
REXCVAR_DECLARE(bool, ffmpeg_verbose);
|
||||
32
include/rex/audio/nop/nop_audio_system.h
Normal file
32
include/rex/audio/nop/nop_audio_system.h
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2013 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*
|
||||
* @modified Tom Clay, 2026 - Adapted for ReXGlue runtime
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <rex/audio/audio_system.h>
|
||||
|
||||
namespace rex::audio::nop {
|
||||
|
||||
class NopAudioSystem : public AudioSystem {
|
||||
public:
|
||||
explicit NopAudioSystem(runtime::FunctionDispatcher* function_dispatcher);
|
||||
~NopAudioSystem() override;
|
||||
|
||||
static bool IsAvailable() { return true; }
|
||||
|
||||
static std::unique_ptr<AudioSystem> Create(runtime::FunctionDispatcher* function_dispatcher);
|
||||
|
||||
X_STATUS CreateDriver(size_t index, rex::thread::Semaphore* semaphore,
|
||||
AudioDriver** out_driver) override;
|
||||
void DestroyDriver(AudioDriver* driver) override;
|
||||
};
|
||||
|
||||
} // namespace rex::audio::nop
|
||||
54
include/rex/audio/sdl/sdl_audio_driver.h
Normal file
54
include/rex/audio/sdl/sdl_audio_driver.h
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2020 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*
|
||||
* @modified Tom Clay, 2026 - Adapted for ReXGlue runtime
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <mutex>
|
||||
#include <queue>
|
||||
#include <stack>
|
||||
|
||||
#include <rex/audio/audio_driver.h>
|
||||
#include <rex/thread.h>
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
namespace rex::audio::sdl {
|
||||
|
||||
class SDLAudioDriver : public AudioDriver {
|
||||
public:
|
||||
SDLAudioDriver(memory::Memory* memory, rex::thread::Semaphore* semaphore);
|
||||
~SDLAudioDriver() override;
|
||||
|
||||
bool Initialize();
|
||||
void SubmitFrame(uint32_t frame_ptr) override;
|
||||
void Shutdown();
|
||||
|
||||
protected:
|
||||
static void SDLCallback(void* userdata, SDL_AudioStream* stream, int additional_amount,
|
||||
int total_amount);
|
||||
|
||||
rex::thread::Semaphore* semaphore_ = nullptr;
|
||||
|
||||
SDL_AudioStream* sdl_stream_ = nullptr;
|
||||
bool sdl_initialized_ = false;
|
||||
uint8_t sdl_device_channels_ = 0;
|
||||
|
||||
static const uint32_t frame_frequency_ = 48000;
|
||||
static const uint32_t frame_channels_ = 6;
|
||||
static const uint32_t channel_samples_ = 256;
|
||||
static const uint32_t frame_samples_ = frame_channels_ * channel_samples_;
|
||||
static const uint32_t frame_size_ = sizeof(float) * frame_samples_;
|
||||
std::queue<float*> frames_queued_ = {};
|
||||
std::stack<float*> frames_unused_ = {};
|
||||
std::mutex frames_mutex_ = {};
|
||||
};
|
||||
|
||||
} // namespace rex::audio::sdl
|
||||
35
include/rex/audio/sdl/sdl_audio_system.h
Normal file
35
include/rex/audio/sdl/sdl_audio_system.h
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2020 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*
|
||||
* @modified Tom Clay, 2026 - Adapted for ReXGlue runtime
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <rex/audio/audio_system.h>
|
||||
|
||||
namespace rex::audio::sdl {
|
||||
|
||||
class SDLAudioSystem : public AudioSystem {
|
||||
public:
|
||||
explicit SDLAudioSystem(runtime::FunctionDispatcher* function_dispatcher);
|
||||
~SDLAudioSystem() override;
|
||||
|
||||
static bool IsAvailable() { return true; }
|
||||
|
||||
static std::unique_ptr<AudioSystem> Create(runtime::FunctionDispatcher* function_dispatcher);
|
||||
|
||||
X_STATUS CreateDriver(size_t index, rex::thread::Semaphore* semaphore,
|
||||
AudioDriver** out_driver) override;
|
||||
void DestroyDriver(AudioDriver* driver) override;
|
||||
|
||||
protected:
|
||||
void Initialize() override;
|
||||
};
|
||||
|
||||
} // namespace rex::audio::sdl
|
||||
285
include/rex/audio/xma/context.h
Normal file
285
include/rex/audio/xma/context.h
Normal file
|
|
@ -0,0 +1,285 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2021 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*
|
||||
* @modified Tom Clay, 2026 - Adapted for ReXGlue runtime
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <mutex>
|
||||
|
||||
#include <rex/kernel.h>
|
||||
#include <rex/memory.h>
|
||||
#include <rex/thread.h>
|
||||
|
||||
// XMA audio format:
|
||||
// From research, XMA appears to be based on WMA Pro with
|
||||
// a few (very slight) modifications.
|
||||
// XMA2 is fully backwards-compatible with XMA1.
|
||||
|
||||
// Helpful resources:
|
||||
// https://github.com/koolkdev/libertyv/blob/master/libav_wrapper/xma2dec.c
|
||||
// https://hcs64.com/mboard/forum.php?showthread=14818
|
||||
// https://github.com/hrydgard/minidx9/blob/master/Include/xma2defs.h
|
||||
|
||||
// Forward declarations
|
||||
struct AVCodec;
|
||||
struct AVCodecParserContext;
|
||||
struct AVCodecContext;
|
||||
struct AVFrame;
|
||||
struct AVPacket;
|
||||
|
||||
namespace rex::audio {
|
||||
|
||||
// This is stored in guest space in big-endian order.
|
||||
// We load and swap the whole thing to splat here so that we can
|
||||
// use bitfields.
|
||||
// This could be important:
|
||||
// https://www.fmod.org/questions/question/forum-15859
|
||||
// Appears to be dumped in order (for the most part)
|
||||
|
||||
struct XMA_CONTEXT_DATA {
|
||||
// DWORD 0
|
||||
uint32_t input_buffer_0_packet_count : 12; // XMASetInputBuffer0, number of
|
||||
// 2KB packets. Max 4095 packets.
|
||||
// These packets form a block.
|
||||
uint32_t loop_count : 8; // +12bit, XMASetLoopData NumLoops
|
||||
uint32_t input_buffer_0_valid : 1; // +20bit, XMAIsInputBuffer0Valid
|
||||
uint32_t input_buffer_1_valid : 1; // +21bit, XMAIsInputBuffer1Valid
|
||||
uint32_t output_buffer_block_count : 5; // +22bit SizeWrite 256byte blocks
|
||||
uint32_t output_buffer_write_offset : 5; // +27bit
|
||||
// XMAGetOutputBufferWriteOffset
|
||||
// AKA OffsetWrite
|
||||
|
||||
// DWORD 1
|
||||
uint32_t input_buffer_1_packet_count : 12; // XMASetInputBuffer1, number of
|
||||
// 2KB packets. Max 4095 packets.
|
||||
// These packets form a block.
|
||||
uint32_t loop_subframe_start : 2; // +12bit, XMASetLoopData
|
||||
uint32_t loop_subframe_end : 3; // +14bit, XMASetLoopData
|
||||
uint32_t loop_subframe_skip : 3; // +17bit, XMASetLoopData might be
|
||||
// subframe_decode_count
|
||||
uint32_t subframe_decode_count : 4; // +20bit
|
||||
uint32_t output_buffer_padding : 3; // +24bit, extra output buffer blocks
|
||||
// reserved per decoded frame
|
||||
uint32_t sample_rate : 2; // +27bit enum of sample rates
|
||||
uint32_t is_stereo : 1; // +29bit
|
||||
uint32_t unk_dword_1_c : 1; // +30bit
|
||||
uint32_t output_buffer_valid : 1; // +31bit, XMAIsOutputBufferValid
|
||||
|
||||
// DWORD 2
|
||||
uint32_t input_buffer_read_offset : 26; // XMAGetInputBufferReadOffset
|
||||
uint32_t error_status : 5; // ErrorStatus
|
||||
uint32_t error_set : 1; // ErrorSet
|
||||
|
||||
// DWORD 3
|
||||
uint32_t loop_start : 26; // XMASetLoopData LoopStartOffset
|
||||
// frame offset in bits
|
||||
uint32_t parser_error_status : 5; // ParserErrorStatus
|
||||
uint32_t parser_error_set : 1; // ParserErrorSet
|
||||
|
||||
// DWORD 4
|
||||
uint32_t loop_end : 26; // XMASetLoopData LoopEndOffset
|
||||
// frame offset in bits
|
||||
uint32_t packet_metadata : 5; // XMAGetPacketMetadata
|
||||
uint32_t current_buffer : 1; // ?
|
||||
|
||||
// DWORD 5
|
||||
uint32_t input_buffer_0_ptr; // physical address
|
||||
// DWORD 6
|
||||
uint32_t input_buffer_1_ptr; // physical address
|
||||
// DWORD 7
|
||||
uint32_t output_buffer_ptr; // physical address
|
||||
// DWORD 8
|
||||
uint32_t work_buffer_ptr; // PtrOverlapAdd(?)
|
||||
|
||||
// DWORD 9
|
||||
// +0bit, XMAGetOutputBufferReadOffset AKA WriteBufferOffsetRead
|
||||
uint32_t output_buffer_read_offset : 5;
|
||||
uint32_t : 25;
|
||||
uint32_t stop_when_done : 1; // +30bit
|
||||
uint32_t interrupt_when_done : 1; // +31bit
|
||||
|
||||
// DWORD 10-15
|
||||
uint32_t unk_dwords_10_15[6]; // reserved?
|
||||
|
||||
explicit XMA_CONTEXT_DATA(const void* ptr) {
|
||||
memory::copy_and_swap(reinterpret_cast<uint32_t*>(this), reinterpret_cast<const uint32_t*>(ptr),
|
||||
sizeof(XMA_CONTEXT_DATA) / 4);
|
||||
}
|
||||
|
||||
void Store(void* ptr) {
|
||||
memory::copy_and_swap(reinterpret_cast<uint32_t*>(ptr), reinterpret_cast<const uint32_t*>(this),
|
||||
sizeof(XMA_CONTEXT_DATA) / 4);
|
||||
}
|
||||
|
||||
bool IsInputBufferValid(uint8_t buffer_index) const {
|
||||
return buffer_index == 0 ? input_buffer_0_valid : input_buffer_1_valid;
|
||||
}
|
||||
|
||||
bool IsCurrentInputBufferValid() const { return IsInputBufferValid(current_buffer); }
|
||||
|
||||
bool IsAnyInputBufferValid() const { return input_buffer_0_valid || input_buffer_1_valid; }
|
||||
|
||||
uint32_t GetInputBufferAddress(uint8_t buffer_index) const {
|
||||
return buffer_index == 0 ? input_buffer_0_ptr : input_buffer_1_ptr;
|
||||
}
|
||||
|
||||
uint32_t GetCurrentInputBufferAddress() const { return GetInputBufferAddress(current_buffer); }
|
||||
|
||||
uint32_t GetInputBufferPacketCount(uint8_t buffer_index) const {
|
||||
return buffer_index == 0 ? input_buffer_0_packet_count : input_buffer_1_packet_count;
|
||||
}
|
||||
|
||||
uint32_t GetCurrentInputBufferPacketCount() const {
|
||||
return GetInputBufferPacketCount(current_buffer);
|
||||
}
|
||||
|
||||
bool IsConsumeOnlyContext() const {
|
||||
return (input_buffer_0_packet_count | input_buffer_1_packet_count) == 0;
|
||||
}
|
||||
};
|
||||
static_assert_size(XMA_CONTEXT_DATA, 64);
|
||||
|
||||
#pragma pack(push, 1)
|
||||
// XMA2WAVEFORMATEX
|
||||
struct Xma2ExtraData {
|
||||
uint8_t raw[34];
|
||||
};
|
||||
static_assert_size(Xma2ExtraData, 34);
|
||||
#pragma pack(pop)
|
||||
|
||||
struct kPacketInfo {
|
||||
uint8_t frame_count_ = 0;
|
||||
uint8_t current_frame_ = 0;
|
||||
uint32_t current_frame_size_ = 0;
|
||||
|
||||
bool isLastFrameInPacket() const {
|
||||
return frame_count_ == 0 || current_frame_ == frame_count_ - 1;
|
||||
}
|
||||
};
|
||||
|
||||
static constexpr int kIdToSampleRate[4] = {24000, 32000, 44100, 48000};
|
||||
|
||||
class XmaContext {
|
||||
public:
|
||||
static const uint32_t kBytesPerPacket = 2048;
|
||||
static const uint32_t kBitsPerPacket = kBytesPerPacket * 8;
|
||||
static const uint32_t kBitsPerPacketHeader = 32;
|
||||
static const uint32_t kBitsPerFrameHeader = 15;
|
||||
static const uint32_t kBytesPerPacketHeader = 4;
|
||||
static const uint32_t kBytesPerPacketData = kBytesPerPacket - kBytesPerPacketHeader;
|
||||
|
||||
static const uint32_t kBytesPerSample = 2;
|
||||
static const uint32_t kSamplesPerFrame = 512;
|
||||
static const uint32_t kSamplesPerSubframe = 128;
|
||||
static const uint32_t kBytesPerFrameChannel = kSamplesPerFrame * kBytesPerSample;
|
||||
static const uint32_t kBytesPerSubframeChannel = kSamplesPerSubframe * kBytesPerSample;
|
||||
|
||||
static const uint32_t kOutputBytesPerBlock = 256;
|
||||
static const uint32_t kOutputMaxSizeBytes = 31 * kOutputBytesPerBlock;
|
||||
static const uint32_t kMaxFrameSizeinBits = 0x4000 - kBitsPerPacketHeader;
|
||||
|
||||
explicit XmaContext();
|
||||
~XmaContext();
|
||||
|
||||
int Setup(uint32_t id, memory::Memory* memory, uint32_t guest_ptr);
|
||||
bool Work();
|
||||
|
||||
void Enable();
|
||||
bool Block(bool poll);
|
||||
void Clear();
|
||||
void Disable();
|
||||
void Release();
|
||||
|
||||
memory::Memory* memory() const { return memory_; }
|
||||
|
||||
uint32_t id() { return id_; }
|
||||
uint32_t guest_ptr() { return guest_ptr_; }
|
||||
bool is_allocated() { return is_allocated_.load(std::memory_order_acquire); }
|
||||
bool is_enabled() { return is_enabled_.load(std::memory_order_acquire); }
|
||||
|
||||
void set_is_allocated(bool is_allocated) {
|
||||
is_allocated_.store(is_allocated, std::memory_order_release);
|
||||
}
|
||||
void set_is_enabled(bool is_enabled) { is_enabled_.store(is_enabled, std::memory_order_release); }
|
||||
|
||||
void SignalWorkDone() {
|
||||
if (work_completion_event_) {
|
||||
work_completion_event_->Set();
|
||||
}
|
||||
}
|
||||
void WaitForWorkDone() {
|
||||
if (work_completion_event_) {
|
||||
rex::thread::Wait(work_completion_event_.get(), false);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
static void SwapInputBuffer(XMA_CONTEXT_DATA* data);
|
||||
static int GetSampleRate(int id);
|
||||
static int16_t GetPacketNumber(size_t size, size_t bit_offset);
|
||||
static uint32_t GetCurrentInputBufferSize(XMA_CONTEXT_DATA* data);
|
||||
|
||||
kPacketInfo GetPacketInfo(uint8_t* packet, uint32_t frame_offset);
|
||||
uint32_t GetAmountOfBitsToRead(uint32_t remaining_stream_bits, uint32_t frame_size);
|
||||
const uint8_t* GetNextPacket(XMA_CONTEXT_DATA* data, uint32_t next_packet_index,
|
||||
uint32_t current_input_packet_count);
|
||||
uint32_t GetNextPacketReadOffset(uint8_t* buffer, uint32_t next_packet_index,
|
||||
uint32_t current_input_packet_count);
|
||||
uint8_t* GetCurrentInputBuffer(XMA_CONTEXT_DATA* data);
|
||||
|
||||
void Decode(XMA_CONTEXT_DATA* data);
|
||||
void Consume(memory::RingBuffer* output_rb, const XMA_CONTEXT_DATA* data);
|
||||
void UpdateLoopStatus(XMA_CONTEXT_DATA* data);
|
||||
void ClearLocked(XMA_CONTEXT_DATA* data);
|
||||
|
||||
memory::RingBuffer PrepareOutputRingBuffer(XMA_CONTEXT_DATA* data);
|
||||
int PrepareDecoder(int sample_rate, bool is_two_channel);
|
||||
void PreparePacket(uint32_t frame_size, uint32_t frame_padding);
|
||||
bool DecodePacket(AVCodecContext* av_context, const AVPacket* av_packet, AVFrame* av_frame);
|
||||
|
||||
void StoreContextMerged(const XMA_CONTEXT_DATA& data, const XMA_CONTEXT_DATA& initial_data,
|
||||
uint8_t* context_ptr);
|
||||
|
||||
static void ConvertFrame(const uint8_t** samples, bool is_two_channel, uint8_t* output_buffer);
|
||||
|
||||
memory::Memory* memory_ = nullptr;
|
||||
std::unique_ptr<rex::thread::Event> work_completion_event_;
|
||||
|
||||
uint32_t id_ = 0;
|
||||
uint32_t guest_ptr_ = 0;
|
||||
std::mutex lock_;
|
||||
std::atomic<bool> is_allocated_ = false;
|
||||
std::atomic<bool> is_enabled_ = false;
|
||||
|
||||
// ffmpeg structures
|
||||
AVPacket* av_packet_ = nullptr;
|
||||
AVCodec* av_codec_ = nullptr;
|
||||
AVCodecContext* av_context_ = nullptr;
|
||||
AVFrame* av_frame_ = nullptr;
|
||||
|
||||
// Packet data buffer (two packets worth for split frame handling)
|
||||
std::array<uint8_t, kBytesPerPacketData * 2> input_buffer_;
|
||||
// First byte contains bit offset information
|
||||
std::array<uint8_t, 1 + 4096> xma_frame_;
|
||||
// Conversion buffer for up to 2-channel frame
|
||||
std::array<uint8_t, kBytesPerFrameChannel * 2> raw_frame_;
|
||||
|
||||
// Output buffer tracking
|
||||
int32_t remaining_subframe_blocks_in_output_buffer_ = 0;
|
||||
uint8_t current_frame_remaining_subframes_ = 0;
|
||||
|
||||
// Loop subframe precision state
|
||||
uint8_t loop_frame_output_limit_ = 0;
|
||||
bool loop_start_skip_pending_ = false;
|
||||
};
|
||||
|
||||
} // namespace rex::audio
|
||||
92
include/rex/audio/xma/decoder.h
Normal file
92
include/rex/audio/xma/decoder.h
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2021 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*
|
||||
* @modified Tom Clay, 2026 - Adapted for ReXGlue runtime
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <mutex>
|
||||
#include <queue>
|
||||
|
||||
#include <rex/audio/xma/context.h>
|
||||
#include <rex/audio/xma/register_file.h>
|
||||
#include <rex/bit.h>
|
||||
#include <rex/kernel.h>
|
||||
#include <rex/system/xthread.h>
|
||||
|
||||
namespace rex::runtime {
|
||||
class FunctionDispatcher;
|
||||
} // namespace rex::runtime
|
||||
|
||||
namespace rex::audio {
|
||||
|
||||
struct XMA_CONTEXT_DATA;
|
||||
|
||||
class XmaDecoder {
|
||||
public:
|
||||
explicit XmaDecoder(runtime::FunctionDispatcher* function_dispatcher);
|
||||
~XmaDecoder();
|
||||
|
||||
memory::Memory* memory() const { return memory_; }
|
||||
runtime::FunctionDispatcher* function_dispatcher() const { return function_dispatcher_; }
|
||||
|
||||
X_STATUS Setup(system::KernelState* kernel_state);
|
||||
void Shutdown();
|
||||
|
||||
uint32_t context_array_ptr() const { return register_file_[XmaRegister::ContextArrayAddress]; }
|
||||
|
||||
uint32_t AllocateContext();
|
||||
void ReleaseContext(uint32_t guest_ptr);
|
||||
bool BlockOnContext(uint32_t guest_ptr, bool poll);
|
||||
|
||||
uint32_t ReadRegister(uint32_t addr);
|
||||
void WriteRegister(uint32_t addr, uint32_t value);
|
||||
|
||||
bool is_paused() const { return paused_; }
|
||||
void Pause();
|
||||
void Resume();
|
||||
|
||||
protected:
|
||||
int GetContextId(uint32_t guest_ptr);
|
||||
|
||||
private:
|
||||
void WorkerThreadMain();
|
||||
|
||||
static uint32_t MMIOReadRegisterThunk(void* ppc_context, XmaDecoder* as, uint32_t addr) {
|
||||
return as->ReadRegister(addr);
|
||||
}
|
||||
static void MMIOWriteRegisterThunk(void* ppc_context, XmaDecoder* as, uint32_t addr,
|
||||
uint32_t value) {
|
||||
as->WriteRegister(addr, value);
|
||||
}
|
||||
|
||||
protected:
|
||||
memory::Memory* memory_ = nullptr;
|
||||
runtime::FunctionDispatcher* function_dispatcher_ = nullptr;
|
||||
|
||||
std::atomic<bool> worker_running_ = {false};
|
||||
system::object_ref<system::XHostThread> worker_thread_;
|
||||
std::unique_ptr<rex::thread::Event> work_event_ = nullptr;
|
||||
|
||||
std::atomic<bool> paused_ = false;
|
||||
rex::thread::Fence pause_fence_; // Signaled when worker paused.
|
||||
rex::thread::Fence resume_fence_; // Signaled when resume requested.
|
||||
|
||||
XmaRegisterFile register_file_;
|
||||
|
||||
static const uint32_t kContextCount = 320;
|
||||
XmaContext contexts_[kContextCount];
|
||||
bit::BitMap context_bitmap_;
|
||||
|
||||
uint32_t context_data_first_ptr_ = 0;
|
||||
uint32_t context_data_last_ptr_ = 0;
|
||||
};
|
||||
|
||||
} // namespace rex::audio
|
||||
46
include/rex/audio/xma/helpers.h
Normal file
46
include/rex/audio/xma/helpers.h
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2021 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*
|
||||
* @modified Tom Clay, 2026 - Adapted for ReXGlue runtime
|
||||
*/
|
||||
|
||||
// This file contains some functions used to help parse XMA data.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
namespace rex::audio::xma {
|
||||
|
||||
static constexpr uint32_t kMaxFrameLength = 0x7FFF;
|
||||
|
||||
// Get number of frames that /begin/ in this packet. Valid only for XMA2 packets.
|
||||
inline uint8_t GetPacketFrameCount(const uint8_t* packet) {
|
||||
return packet[0] >> 2;
|
||||
}
|
||||
|
||||
// Get the first frame offset in bits
|
||||
inline uint32_t GetPacketFrameOffset(const uint8_t* packet) {
|
||||
uint32_t val =
|
||||
static_cast<uint16_t>(((packet[0] & 0x3) << 13) | (packet[1] << 5) | (packet[2] >> 3));
|
||||
return val + 32;
|
||||
}
|
||||
|
||||
inline uint8_t GetPacketMetadata(const uint8_t* packet) {
|
||||
return packet[2] & 0x7;
|
||||
}
|
||||
|
||||
inline bool IsPacketXma2Type(const uint8_t* packet) {
|
||||
return GetPacketMetadata(packet) == 1;
|
||||
}
|
||||
|
||||
inline uint8_t GetPacketSkipCount(const uint8_t* packet) {
|
||||
return packet[3];
|
||||
}
|
||||
|
||||
} // namespace rex::audio::xma
|
||||
42
include/rex/audio/xma/register_file.h
Normal file
42
include/rex/audio/xma/register_file.h
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2021 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*
|
||||
* @modified Tom Clay, 2026 - Adapted for ReXGlue runtime
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
|
||||
namespace rex::audio {
|
||||
|
||||
struct XmaRegister {
|
||||
#define XE_XMA_REGISTER(index, name) static const uint32_t name = index;
|
||||
#include <rex/audio/xma/register_table.inc>
|
||||
#undef XE_XMA_REGISTER
|
||||
};
|
||||
|
||||
struct XmaRegisterInfo {
|
||||
const char* name;
|
||||
};
|
||||
|
||||
class XmaRegisterFile {
|
||||
public:
|
||||
XmaRegisterFile();
|
||||
|
||||
static const XmaRegisterInfo* GetRegisterInfo(uint32_t index);
|
||||
|
||||
static const size_t kRegisterCount = (0xFFFF + 1) / 4;
|
||||
uint32_t values[kRegisterCount];
|
||||
|
||||
uint32_t operator[](uint32_t reg) const { return values[reg]; }
|
||||
uint32_t& operator[](uint32_t reg) { return values[reg]; }
|
||||
};
|
||||
|
||||
} // namespace rex::audio
|
||||
81
include/rex/audio/xma/register_table.inc
Normal file
81
include/rex/audio/xma/register_table.inc
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2021 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*/
|
||||
|
||||
// This is a partial file designed to be included by other files when
|
||||
// constructing various tables.
|
||||
|
||||
#ifndef XE_XMA_REGISTER
|
||||
#define XE_XMA_REGISTER(index, name)
|
||||
#define __XE_XMA_REGISTER_UNSET
|
||||
#endif
|
||||
|
||||
#ifndef XE_XMA_REGISTER_CONTEXT_GROUP
|
||||
#define XE_XMA_REGISTER_CONTEXT_GROUP(index, suffix) \
|
||||
XE_XMA_REGISTER(index + 0, Context0##suffix) \
|
||||
XE_XMA_REGISTER(index + 1, Context1##suffix) \
|
||||
XE_XMA_REGISTER(index + 2, Context2##suffix) \
|
||||
XE_XMA_REGISTER(index + 3, Context3##suffix) \
|
||||
XE_XMA_REGISTER(index + 4, Context4##suffix) \
|
||||
XE_XMA_REGISTER(index + 5, Context5##suffix) \
|
||||
XE_XMA_REGISTER(index + 6, Context6##suffix) \
|
||||
XE_XMA_REGISTER(index + 7, Context7##suffix) \
|
||||
XE_XMA_REGISTER(index + 8, Context8##suffix) \
|
||||
XE_XMA_REGISTER(index + 9, Context9##suffix)
|
||||
#endif
|
||||
|
||||
// 0x0000..0x001F : ???
|
||||
// 0x0020..0x03FF : all 0xFFs?
|
||||
// 0x0400..0x043F : ???
|
||||
// 0x0440..0x047F : all 0xFFs?
|
||||
// 0x0480..0x048B : ???
|
||||
// 0x048C..0x04C0 : all 0xFFs?
|
||||
// 0x04C1..0x04CB : ???
|
||||
// 0x04CC..0x04FF : all 0xFFs?
|
||||
// 0x0500..0x051F : ???
|
||||
// 0x0520..0x057F : all 0xFFs?
|
||||
// 0x0580..0x058F : ???
|
||||
// 0x0590..0x05FF : all 0xFFs?
|
||||
|
||||
// XMA stuff is probably only 0x0600..0x06FF
|
||||
//---------------------------------------------------------------------------//
|
||||
|
||||
XE_XMA_REGISTER(0x0600, ContextArrayAddress)
|
||||
// 0x0601..0x0605 : ???
|
||||
XE_XMA_REGISTER(0x0606, CurrentContextIndex)
|
||||
XE_XMA_REGISTER(0x0607, NextContextIndex)
|
||||
// 0x0608 : ???
|
||||
// 0x0609..0x060F : zero?
|
||||
XE_XMA_REGISTER_CONTEXT_GROUP(0x0610, Unknown610)
|
||||
// 0x061A..0x061F : zero?
|
||||
XE_XMA_REGISTER_CONTEXT_GROUP(0x0620, Unknown620)
|
||||
// 0x062A..0x0641 : zero?
|
||||
// 0x0642..0x0644 : ???
|
||||
// 0x0645..0x064F : zero?
|
||||
XE_XMA_REGISTER_CONTEXT_GROUP(0x0650, Kick)
|
||||
// 0x065A..0x065F : zero?
|
||||
XE_XMA_REGISTER_CONTEXT_GROUP(0x0660, Unknown660)
|
||||
// 0x066A..0x0681 : zero?
|
||||
// 0x0682..0x0684 : ???
|
||||
// 0x0685..0x068F : zero?
|
||||
XE_XMA_REGISTER_CONTEXT_GROUP(0x0690, Lock)
|
||||
// 0x069A..0x069F : zero?
|
||||
XE_XMA_REGISTER_CONTEXT_GROUP(0x06A0, Clear)
|
||||
|
||||
//---------------------------------------------------------------------------//
|
||||
|
||||
// 0x0700..0x07FF : all 0xFFs?
|
||||
// 0x0800..0x17FF : ???
|
||||
// 0x1800..0x2FFF : all 0xFFs?
|
||||
// 0x3000..0x30FF : ???
|
||||
// 0x3100..0x3FFF : all 0xFFs?
|
||||
|
||||
#ifdef __XE_XMA_REGISTER_UNSET
|
||||
#undef __XE_XMA_REGISTER_UNSET
|
||||
#undef XE_XMA_REGISTER
|
||||
#endif
|
||||
145
include/rex/bit.h
Normal file
145
include/rex/bit.h
Normal file
|
|
@ -0,0 +1,145 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2015-2019 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*
|
||||
* @modified 2026 Tom Clay <tomc@tctechstuff.com>
|
||||
* Modified for rexglue - Xbox 360 recompilation framework
|
||||
*
|
||||
* @changes - Consolidated bit_map.h and bit_range.h into single header
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <climits>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <rex/math.h>
|
||||
|
||||
namespace rex::bit {
|
||||
|
||||
//=============================================================================
|
||||
// BitMap - Efficient lookup of free/used entries
|
||||
//=============================================================================
|
||||
|
||||
class BitMap {
|
||||
public:
|
||||
BitMap();
|
||||
|
||||
// Size is the number of entries, must be a multiple of 64.
|
||||
BitMap(size_t size_bits);
|
||||
|
||||
// Data does not have to be aligned to a 4-byte boundary, but it is
|
||||
// preferable.
|
||||
// Size is the number of entries, must be a multiple of 64.
|
||||
BitMap(uint64_t* data, size_t size_bits);
|
||||
|
||||
// (threadsafe) Acquires an entry and returns its index. Returns -1 if there
|
||||
// are no more free entries.
|
||||
size_t Acquire();
|
||||
|
||||
// (threadsafe) Releases an entry by an index.
|
||||
void Release(size_t index);
|
||||
|
||||
// Resize the bitmap. Size is the number of entries, must be a multiple of 64.
|
||||
void Resize(size_t new_size_bits);
|
||||
|
||||
// Sets all entries to free.
|
||||
void Reset();
|
||||
|
||||
const std::vector<uint64_t> data() const { return data_; }
|
||||
std::vector<uint64_t>& data() { return data_; }
|
||||
|
||||
private:
|
||||
const static size_t kDataSize = 8;
|
||||
const static size_t kDataSizeBits = kDataSize * 8;
|
||||
std::vector<uint64_t> data_;
|
||||
};
|
||||
|
||||
//=============================================================================
|
||||
// Bit Range Utilities
|
||||
//=============================================================================
|
||||
|
||||
// Provided length is in bits since the first. Returns <first, length> of the
|
||||
// range in bits, with length == 0 if not found.
|
||||
template <typename Block>
|
||||
std::pair<size_t, size_t> GetNextRangeUnset(const Block* bits, size_t first, size_t length) {
|
||||
if (!length) {
|
||||
return std::make_pair(size_t(first), size_t(0));
|
||||
}
|
||||
size_t last = first + length - 1;
|
||||
const size_t block_bits = sizeof(Block) * CHAR_BIT;
|
||||
size_t block_first = first / block_bits;
|
||||
size_t block_last = last / block_bits;
|
||||
size_t range_start = SIZE_MAX;
|
||||
for (size_t i = block_first; i <= block_last; ++i) {
|
||||
Block block = bits[i];
|
||||
// Ignore bits in the block outside the specified range by considering them
|
||||
// set.
|
||||
if (i == block_first) {
|
||||
block |= (Block(1) << (first & (block_bits - 1))) - 1;
|
||||
}
|
||||
if (i == block_last && (last & (block_bits - 1)) != block_bits - 1) {
|
||||
block |= ~((Block(1) << ((last & (block_bits - 1)) + 1)) - 1);
|
||||
}
|
||||
while (true) {
|
||||
uint32_t block_bit;
|
||||
if (range_start == SIZE_MAX) {
|
||||
// Check if need to open a new range.
|
||||
if (!rex::bit_scan_forward(~block, &block_bit)) {
|
||||
break;
|
||||
}
|
||||
range_start = i * block_bits + block_bit;
|
||||
} else {
|
||||
// Check if need to close the range.
|
||||
// Ignore the set bits before the beginning of the range.
|
||||
Block block_bits_set_from_start = block;
|
||||
if (i == range_start / block_bits) {
|
||||
block_bits_set_from_start &= ~((Block(1) << (range_start & (block_bits - 1))) - 1);
|
||||
}
|
||||
if (!rex::bit_scan_forward(block_bits_set_from_start, &block_bit)) {
|
||||
break;
|
||||
}
|
||||
return std::make_pair(range_start, (i * block_bits) + block_bit - range_start);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (range_start != SIZE_MAX) {
|
||||
return std::make_pair(range_start, last + size_t(1) - range_start);
|
||||
}
|
||||
return std::make_pair(first + length, size_t(0));
|
||||
}
|
||||
|
||||
template <typename Block>
|
||||
void SetRange(Block* bits, size_t first, size_t length) {
|
||||
if (!length) {
|
||||
return;
|
||||
}
|
||||
size_t last = first + length - 1;
|
||||
const size_t block_bits = sizeof(Block) * CHAR_BIT;
|
||||
size_t block_first = first / block_bits;
|
||||
size_t block_last = last / block_bits;
|
||||
Block set_first = ~((Block(1) << (first & (block_bits - 1))) - 1);
|
||||
Block set_last = ~Block(0);
|
||||
if ((last & (block_bits - 1)) != (block_bits - 1)) {
|
||||
set_last &= (Block(1) << ((last & (block_bits - 1)) + 1)) - 1;
|
||||
}
|
||||
if (block_first == block_last) {
|
||||
bits[block_first] |= set_first & set_last;
|
||||
return;
|
||||
}
|
||||
bits[block_first] |= set_first;
|
||||
if (block_first + 1 < block_last) {
|
||||
std::memset(bits + block_first + 1, CHAR_MAX, (block_last - (block_first + 1)) * sizeof(Block));
|
||||
}
|
||||
bits[block_last] |= set_last;
|
||||
}
|
||||
|
||||
} // namespace rex::bit
|
||||
165
include/rex/chrono/chrono.h
Normal file
165
include/rex/chrono/chrono.h
Normal file
|
|
@ -0,0 +1,165 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2022 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*
|
||||
* @modified Tom Clay, 2026 - Adapted for ReXGlue runtime
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
|
||||
#include <rex/chrono/clock.h>
|
||||
|
||||
namespace rex {
|
||||
using hundrednano = std::ratio<1, 10000000>;
|
||||
|
||||
namespace chrono {
|
||||
|
||||
using hundrednanoseconds = std::chrono::duration<int64_t, hundrednano>;
|
||||
|
||||
// TODO(JoelLinn) define xstead_clock xsystem_clock etc.
|
||||
|
||||
namespace detail {
|
||||
// Implementation detail: NtSystemClock template for Host/Guest time domains.
|
||||
// Trick to reduce code duplication and keep all the chrono template magic
|
||||
// working.
|
||||
enum class Domain {
|
||||
// boring host clock:
|
||||
Host,
|
||||
// adheres to guest scaling (differrent speed, changing clock drift etc):
|
||||
Guest
|
||||
};
|
||||
|
||||
template <Domain domain_>
|
||||
struct NtSystemClock {
|
||||
using rep = int64_t;
|
||||
using period = hundrednano;
|
||||
using duration = hundrednanoseconds;
|
||||
using time_point = std::chrono::time_point<NtSystemClock<domain_>>;
|
||||
// This really depends on the context the clock is used in:
|
||||
// static constexpr bool is_steady = false;
|
||||
|
||||
public:
|
||||
// The delta between std::chrono::system_clock (Jan 1 1970) and NT file
|
||||
// time (Jan 1 1601), in seconds. In the spec std::chrono::system_clock's
|
||||
// epoch is undefined, but C++20 cements it as Jan 1 1970.
|
||||
static constexpr std::chrono::seconds unix_epoch_delta() {
|
||||
using std::chrono::steady_clock;
|
||||
auto filetime_epoch = std::chrono::year{1601} / std::chrono::month{1} / std::chrono::day{1};
|
||||
auto system_clock_epoch = std::chrono::year{1970} / std::chrono::month{1} / std::chrono::day{1};
|
||||
auto fp =
|
||||
static_cast<std::chrono::sys_seconds>(static_cast<std::chrono::sys_days>(filetime_epoch));
|
||||
auto sp = static_cast<std::chrono::sys_seconds>(
|
||||
static_cast<std::chrono::sys_days>(system_clock_epoch));
|
||||
return fp.time_since_epoch() - sp.time_since_epoch();
|
||||
}
|
||||
|
||||
public:
|
||||
static constexpr uint64_t to_file_time(time_point const& tp) noexcept {
|
||||
return static_cast<uint64_t>(tp.time_since_epoch().count());
|
||||
}
|
||||
|
||||
static constexpr time_point from_file_time(uint64_t const& tp) noexcept {
|
||||
return time_point{duration{tp}};
|
||||
}
|
||||
|
||||
// To convert XSystemClock to sys, do clock_cast<WinSystemTime>(tp) first
|
||||
// Only available for Host domain (Guest time must be converted via clock_cast)
|
||||
static constexpr std::chrono::system_clock::time_point to_sys(const time_point& tp)
|
||||
requires(domain_ == Domain::Host)
|
||||
{
|
||||
using sys_duration = std::chrono::system_clock::duration;
|
||||
using sys_time = std::chrono::system_clock::time_point;
|
||||
|
||||
auto dp = tp;
|
||||
dp += unix_epoch_delta();
|
||||
auto cdp = std::chrono::time_point_cast<sys_duration>(dp);
|
||||
return sys_time{cdp.time_since_epoch()};
|
||||
}
|
||||
|
||||
static constexpr time_point from_sys(const std::chrono::system_clock::time_point& tp)
|
||||
requires(domain_ == Domain::Host)
|
||||
{
|
||||
auto ctp = std::chrono::time_point_cast<duration>(tp);
|
||||
auto dp = time_point{ctp.time_since_epoch()};
|
||||
dp -= unix_epoch_delta();
|
||||
return dp;
|
||||
}
|
||||
|
||||
[[nodiscard]] static time_point now() noexcept {
|
||||
if constexpr (domain_ == Domain::Host) {
|
||||
// QueryHostSystemTime() returns windows epoch times even on POSIX
|
||||
return from_file_time(Clock::QueryHostSystemTime());
|
||||
} else if constexpr (domain_ == Domain::Guest) {
|
||||
return from_file_time(Clock::QueryGuestSystemTime());
|
||||
}
|
||||
}
|
||||
};
|
||||
} // namespace detail
|
||||
|
||||
// Unscaled system clock which can be used for filetime <-> system_clock
|
||||
// conversion
|
||||
using WinSystemClock = detail::NtSystemClock<detail::Domain::Host>;
|
||||
|
||||
// Guest system clock, scaled
|
||||
using XSystemClock = detail::NtSystemClock<detail::Domain::Guest>;
|
||||
|
||||
} // namespace chrono
|
||||
} // namespace rex
|
||||
|
||||
namespace std::chrono {
|
||||
|
||||
template <>
|
||||
struct clock_time_conversion<::rex::chrono::WinSystemClock, ::rex::chrono::XSystemClock> {
|
||||
using WClock_ = ::rex::chrono::WinSystemClock;
|
||||
using XClock_ = ::rex::chrono::XSystemClock;
|
||||
|
||||
template <typename Duration>
|
||||
typename WClock_::time_point operator()(
|
||||
const std::chrono::time_point<XClock_, Duration>& t) const {
|
||||
// Consult chrono_steady_cast.h for explanation on this:
|
||||
std::atomic_thread_fence(std::memory_order_acq_rel);
|
||||
auto w_now = WClock_::now();
|
||||
auto x_now = XClock_::now();
|
||||
std::atomic_thread_fence(std::memory_order_acq_rel);
|
||||
|
||||
auto delta = (t - x_now);
|
||||
if (!REXCVAR_GET(clock_no_scaling)) {
|
||||
delta =
|
||||
std::chrono::floor<WClock_::duration>(delta * rex::chrono::Clock::guest_time_scalar());
|
||||
}
|
||||
return w_now + delta;
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct clock_time_conversion<::rex::chrono::XSystemClock, ::rex::chrono::WinSystemClock> {
|
||||
using WClock_ = ::rex::chrono::WinSystemClock;
|
||||
using XClock_ = ::rex::chrono::XSystemClock;
|
||||
|
||||
template <typename Duration>
|
||||
typename XClock_::time_point operator()(
|
||||
const std::chrono::time_point<WClock_, Duration>& t) const {
|
||||
// Consult chrono_steady_cast.h for explanation on this:
|
||||
std::atomic_thread_fence(std::memory_order_acq_rel);
|
||||
auto w_now = WClock_::now();
|
||||
auto x_now = XClock_::now();
|
||||
std::atomic_thread_fence(std::memory_order_acq_rel);
|
||||
|
||||
rex::chrono::hundrednanoseconds delta = (t - w_now);
|
||||
if (!REXCVAR_GET(clock_no_scaling)) {
|
||||
delta =
|
||||
std::chrono::floor<WClock_::duration>(delta / rex::chrono::Clock::guest_time_scalar());
|
||||
}
|
||||
return x_now + delta;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace std::chrono
|
||||
73
include/rex/chrono/chrono_steady_cast.h
Normal file
73
include/rex/chrono/chrono_steady_cast.h
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2022 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*
|
||||
* @modified Tom Clay, 2026 - Adapted for ReXGlue runtime
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
|
||||
#include <rex/chrono/chrono.h>
|
||||
|
||||
// This is in a separate header because casting to and from steady time points
|
||||
// usually doesn't make sense and is imprecise. However, NT uses the FileTime
|
||||
// epoch as a steady clock in waits. In such cases, include this header and use
|
||||
// clock_cast<>().
|
||||
|
||||
namespace std::chrono {
|
||||
|
||||
// This conveniently works only for Host time domain because Guest needs
|
||||
// additional scaling. Convert XSystemClock to WinSystemClock first if
|
||||
// necessary.
|
||||
template <>
|
||||
struct clock_time_conversion<::rex::chrono::WinSystemClock, std::chrono::steady_clock> {
|
||||
// using NtSystemClock_ = ::rex::chrono::internal::NtSystemClock<domain_>;
|
||||
using WinSystemClock_ = ::rex::chrono::WinSystemClock;
|
||||
using steady_clock_ = std::chrono::steady_clock;
|
||||
|
||||
template <typename Duration>
|
||||
typename WinSystemClock_::time_point operator()(
|
||||
const std::chrono::time_point<steady_clock_, Duration>& t) const {
|
||||
// Since there is no known epoch for steady_clock and even if, since it can
|
||||
// progress differently than other common clocks (e.g. stopping when the
|
||||
// computer is suspended), we need to use now() which introduces
|
||||
// imprecision.
|
||||
// Memory fences to keep the clock fetches close together to
|
||||
// minimize drift. This pattern was benchmarked to give the lowest
|
||||
// conversion error: error = sty_tpoint -
|
||||
// clock_cast<sty>(clock_cast<nt>(sty_tpoint));
|
||||
std::atomic_thread_fence(std::memory_order_acq_rel);
|
||||
auto steady_now = steady_clock_::now();
|
||||
auto nt_now = WinSystemClock_::now();
|
||||
std::atomic_thread_fence(std::memory_order_acq_rel);
|
||||
|
||||
auto delta = std::chrono::floor<WinSystemClock_::duration>(t - steady_now);
|
||||
return nt_now + delta;
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct clock_time_conversion<std::chrono::steady_clock, ::rex::chrono::WinSystemClock> {
|
||||
using WinSystemClock_ = ::rex::chrono::WinSystemClock;
|
||||
using steady_clock_ = std::chrono::steady_clock;
|
||||
|
||||
template <typename Duration>
|
||||
steady_clock_::time_point operator()(
|
||||
const std::chrono::time_point<WinSystemClock_, Duration>& t) const {
|
||||
std::atomic_thread_fence(std::memory_order_acq_rel);
|
||||
auto steady_now = steady_clock_::now();
|
||||
auto nt_now = WinSystemClock_::now();
|
||||
std::atomic_thread_fence(std::memory_order_acq_rel);
|
||||
|
||||
auto delta = t - nt_now;
|
||||
return steady_now + delta;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace std::chrono
|
||||
90
include/rex/chrono/clock.h
Normal file
90
include/rex/chrono/clock.h
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2022 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*
|
||||
* @modified Tom Clay, 2026 - Adapted for ReXGlue runtime
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
|
||||
#include <rex/cvar.h>
|
||||
#include <rex/platform.h>
|
||||
|
||||
REXCVAR_DECLARE(bool, clock_no_scaling);
|
||||
REXCVAR_DECLARE(bool, clock_source_raw);
|
||||
|
||||
#if REX_ARCH_AMD64
|
||||
// Raw clock source requires platform-specific implementation
|
||||
// Disable for now - use platform clock functions instead
|
||||
#define REX_CLOCK_RAW_AVAILABLE 0
|
||||
#endif
|
||||
|
||||
namespace rex::chrono {
|
||||
|
||||
class Clock {
|
||||
public:
|
||||
// Host ticks-per-second. Generally QueryHostTickFrequency should be used.
|
||||
// Either from platform suplied time source or from hardware directly.
|
||||
static uint64_t host_tick_frequency_platform();
|
||||
#if REX_CLOCK_RAW_AVAILABLE
|
||||
static uint64_t host_tick_frequency_raw();
|
||||
#endif
|
||||
// Host tick count. Generally QueryHostTickCount() should be used.
|
||||
static uint64_t host_tick_count_platform();
|
||||
#if REX_CLOCK_RAW_AVAILABLE
|
||||
static uint64_t host_tick_count_raw();
|
||||
#endif
|
||||
|
||||
// Queries the host tick frequency.
|
||||
static uint64_t QueryHostTickFrequency();
|
||||
// Queries the current host tick count.
|
||||
static uint64_t QueryHostTickCount();
|
||||
// Host time, in FILETIME format.
|
||||
static uint64_t QueryHostSystemTime();
|
||||
// Queries the milliseconds since the host began.
|
||||
static uint64_t QueryHostUptimeMillis();
|
||||
|
||||
// Guest time scalar.
|
||||
static double guest_time_scalar();
|
||||
// Sets the guest time scalar, adjusting tick and wall clock speed.
|
||||
// Ex: 1x=normal, 2x=double speed, 1/2x=half speed.
|
||||
static void set_guest_time_scalar(double scalar);
|
||||
// Get the tick ration between host and guest including time scaling if set.
|
||||
static std::pair<uint64_t, uint64_t> guest_tick_ratio();
|
||||
// Guest ticks-per-second.
|
||||
static uint64_t guest_tick_frequency();
|
||||
// Sets the guest ticks-per-second.
|
||||
static void set_guest_tick_frequency(uint64_t frequency);
|
||||
// Time based used for the guest system time.
|
||||
static uint64_t guest_system_time_base();
|
||||
// Sets the guest time base, used for computing the system time.
|
||||
// By default this is the current system time.
|
||||
static void set_guest_system_time_base(uint64_t time_base);
|
||||
|
||||
// Queries the current guest tick count, accounting for frequency adjustment
|
||||
// and scaling.
|
||||
static uint64_t QueryGuestTickCount();
|
||||
// Queries the guest time, in FILETIME format, accounting for scaling.
|
||||
static uint64_t QueryGuestSystemTime();
|
||||
// Queries the milliseconds since the guest began, accounting for scaling.
|
||||
static uint32_t QueryGuestUptimeMillis();
|
||||
|
||||
// Sets the system time of the guest.
|
||||
static void SetGuestSystemTime(uint64_t system_time);
|
||||
|
||||
// Scales a time duration in milliseconds, from guest time.
|
||||
static uint32_t ScaleGuestDurationMillis(uint32_t guest_ms);
|
||||
// Scales a time duration in 100ns ticks like FILETIME, from guest time.
|
||||
static int64_t ScaleGuestDurationFileTime(int64_t guest_file_time);
|
||||
// Scales a time duration represented as a timeval, from guest time.
|
||||
static void ScaleGuestDurationTimeval(int32_t* tv_sec, int32_t* tv_usec);
|
||||
};
|
||||
|
||||
} // namespace rex::chrono
|
||||
58
include/rex/codegen/analysis_errors.h
Normal file
58
include/rex/codegen/analysis_errors.h
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
/**
|
||||
* @file rexcodegen/analysis_errors.h
|
||||
* @brief Scoped error collection for analysis
|
||||
*
|
||||
* @copyright Copyright (c) 2026 Tom Clay <tomc@tctechstuff.com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* @license BSD 3-Clause License
|
||||
* See LICENSE file in the project root for full license text.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace rex::codegen {
|
||||
|
||||
/**
|
||||
* Collects errors during analysis. Replaces global g_validationState.
|
||||
*/
|
||||
class AnalysisErrors {
|
||||
public:
|
||||
enum class Category {
|
||||
UnresolvedCall,
|
||||
MissingJumpTable,
|
||||
JumpTargetOutOfBounds,
|
||||
DiscontinuousFunction,
|
||||
UnimplementedInsn,
|
||||
};
|
||||
|
||||
struct Entry {
|
||||
Category category;
|
||||
uint32_t address;
|
||||
uint32_t secondaryAddress;
|
||||
std::string message;
|
||||
};
|
||||
|
||||
void Add(Category cat, uint32_t addr, const std::string& msg);
|
||||
void Add(Category cat, uint32_t addr, uint32_t secondary, const std::string& msg);
|
||||
|
||||
bool HasErrors() const { return !entries_.empty(); }
|
||||
size_t Count() const { return entries_.size(); }
|
||||
size_t Count(Category cat) const;
|
||||
|
||||
const std::vector<Entry>& Entries() const { return entries_; }
|
||||
|
||||
void PrintReport() const;
|
||||
void Clear() { entries_.clear(); }
|
||||
|
||||
static const char* CategoryName(Category cat);
|
||||
|
||||
private:
|
||||
std::vector<Entry> entries_;
|
||||
};
|
||||
|
||||
} // namespace rex::codegen
|
||||
37
include/rex/codegen/analyze.h
Normal file
37
include/rex/codegen/analyze.h
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
/**
|
||||
* @file rex/codegen/analyze.h
|
||||
* @brief Function graph analysis (builds + validates)
|
||||
*
|
||||
* @copyright Copyright (c) 2026 Tom Clay <tomc@tctechstuff.com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* @license BSD 3-Clause License
|
||||
* See LICENSE file in the project root for full license text.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <rex/codegen/codegen_context.h>
|
||||
#include <rex/codegen/progress_reporter.h>
|
||||
#include <rex/result.h>
|
||||
|
||||
namespace rex::codegen {
|
||||
|
||||
/**
|
||||
* Build and validate the complete FunctionGraph.
|
||||
*
|
||||
* This function consolidates all analysis phases:
|
||||
* - Register: imports, helpers, PDATA functions, config functions
|
||||
* - Scan: code regions (null boundaries), data regions
|
||||
* - Discover: function blocks, calls, jump tables (iterative to fixed point)
|
||||
* - VTable: discover functions via RTTI vtables
|
||||
* - GapFill: claim orphaned code regions
|
||||
* - Merge: resolve all jumps, seal functions
|
||||
* - Validate: verify all calls resolve
|
||||
*
|
||||
* @param ctx CodegenContext with binary and config loaded
|
||||
* @return Success if graph is valid, error otherwise
|
||||
*/
|
||||
Result<void> Analyze(CodegenContext& ctx, ProgressReporter* reporter = nullptr);
|
||||
|
||||
} // namespace rex::codegen
|
||||
115
include/rex/codegen/binary_view.h
Normal file
115
include/rex/codegen/binary_view.h
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
/**
|
||||
* @file rex/codegen/binary_view.h
|
||||
* @brief Self-contained binary view that owns section data
|
||||
*
|
||||
* @copyright Copyright (c) 2026 Tom Clay <tomc@tctechstuff.com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* @license BSD 3-Clause License
|
||||
* See LICENSE file in the project root for full license text.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
namespace rex::runtime {
|
||||
class Module;
|
||||
}
|
||||
|
||||
namespace rex::codegen {
|
||||
|
||||
/// Lightweight view of a binary section (points into BinaryView's owned data)
|
||||
struct SectionView {
|
||||
std::string_view name;
|
||||
uint32_t baseAddress;
|
||||
uint32_t size;
|
||||
const uint8_t* data;
|
||||
bool executable;
|
||||
|
||||
bool contains(uint32_t addr) const { return addr >= baseAddress && addr < baseAddress + size; }
|
||||
|
||||
const uint8_t* translate(uint32_t addr) const {
|
||||
if (!contains(addr))
|
||||
return nullptr;
|
||||
return data + (addr - baseAddress);
|
||||
}
|
||||
|
||||
uint32_t end() const { return baseAddress + size; }
|
||||
};
|
||||
|
||||
/// Import symbol from binary (thunk address + name in "libname@ordinal" format)
|
||||
struct ImportSymbol {
|
||||
uint32_t address; ///< Thunk address (bl target)
|
||||
std::string name; ///< "libname@ordinal" format
|
||||
};
|
||||
|
||||
/// Self-contained binary view that owns all section data
|
||||
class BinaryView {
|
||||
public:
|
||||
/// Factory - copies all data from Module
|
||||
static BinaryView fromModule(const runtime::Module& module);
|
||||
|
||||
// Move-only (owns large buffers)
|
||||
BinaryView(BinaryView&&) noexcept = default;
|
||||
BinaryView& operator=(BinaryView&&) noexcept = default;
|
||||
BinaryView(const BinaryView&) = delete;
|
||||
BinaryView& operator=(const BinaryView&) = delete;
|
||||
|
||||
// Section access
|
||||
const uint8_t* translate(uint32_t addr) const;
|
||||
bool isExecutable(uint32_t addr) const;
|
||||
const SectionView* findSection(uint32_t addr) const;
|
||||
const SectionView* findSectionByName(std::string_view name) const;
|
||||
std::span<const SectionView> sections() const { return sections_; }
|
||||
|
||||
// Metadata access
|
||||
uint32_t baseAddress() const { return baseAddress_; }
|
||||
uint32_t imageSize() const { return imageSize_; }
|
||||
uint32_t entryPoint() const { return entryPoint_; }
|
||||
uint32_t exceptionDirectoryAddr() const { return exceptionDirectoryAddr_; }
|
||||
uint32_t exceptionDirectorySize() const { return exceptionDirectorySize_; }
|
||||
uint32_t exportTableAddr() const { return exportTableAddr_; }
|
||||
|
||||
/// Start of import thunk table (0 if not available)
|
||||
/// Everything from this address to end of .text is import/export tables, not code
|
||||
uint32_t importThunkTableStart() const { return importThunkTableStart_; }
|
||||
|
||||
/// Check if address is in the import thunk/export table range (not real code)
|
||||
/// This range is specifically within .text section, not other executable sections
|
||||
bool isInImportExportRange(uint32_t addr) const {
|
||||
return importThunkTableStart_ != 0 && addr >= importThunkTableStart_ &&
|
||||
addr < importExportRangeEnd_;
|
||||
}
|
||||
|
||||
/// Import symbols (thunk addresses + names)
|
||||
std::span<const ImportSymbol> importSymbols() const { return importSymbols_; }
|
||||
|
||||
public:
|
||||
BinaryView() = default;
|
||||
|
||||
private:
|
||||
// Owned section data
|
||||
std::vector<SectionView> sections_;
|
||||
std::vector<std::string> sectionNames_;
|
||||
std::vector<std::vector<uint8_t>> sectionData_;
|
||||
|
||||
// Metadata
|
||||
uint32_t baseAddress_ = 0;
|
||||
uint32_t imageSize_ = 0;
|
||||
uint32_t entryPoint_ = 0;
|
||||
uint32_t exceptionDirectoryAddr_ = 0;
|
||||
uint32_t exceptionDirectorySize_ = 0;
|
||||
uint32_t exportTableAddr_ = 0;
|
||||
uint32_t importThunkTableStart_ = 0; ///< Start of import thunk table
|
||||
uint32_t importExportRangeEnd_ = 0; ///< End of import/export range (end of .text)
|
||||
|
||||
// Import symbols
|
||||
std::vector<ImportSymbol> importSymbols_;
|
||||
};
|
||||
|
||||
} // namespace rex::codegen
|
||||
159
include/rex/codegen/code_emitter.h
Normal file
159
include/rex/codegen/code_emitter.h
Normal file
|
|
@ -0,0 +1,159 @@
|
|||
/**
|
||||
* @file rex/codegen/code_emitter.h
|
||||
* @brief Abstract code emission interface
|
||||
*
|
||||
* @copyright Copyright (c) 2026 Tom Clay <tomc@tctechstuff.com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* @license BSD 3-Clause License
|
||||
* See LICENSE file in the project root for full license text.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
#include <fmt/core.h>
|
||||
|
||||
namespace rex::codegen {
|
||||
|
||||
// Forward declarations
|
||||
struct RecompilerConfig;
|
||||
|
||||
/**
|
||||
* @brief CSR (Control/Status Register) state for FPU denormal handling.
|
||||
*
|
||||
* Tracks MXCSR configuration:
|
||||
* - Unknown: Initial or after function call
|
||||
* - Fpu: Denormals preserved (scalar FP)
|
||||
* - Vmx: Denormals flushed (vector FP)
|
||||
*/
|
||||
enum class CsrState {
|
||||
Unknown, // Need to check/set on next FP instruction
|
||||
Fpu, // Flush mode disabled (scalar FP)
|
||||
Vmx // Flush mode enabled (vector FP)
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Abstract interface for C++ code emission.
|
||||
*
|
||||
* Provides indentation management, formatted output, and CSR state tracking.
|
||||
* Implementations can write to strings, files, or streams.
|
||||
*
|
||||
* Example:
|
||||
* @code
|
||||
* StringEmitter emit;
|
||||
* emit.line("void foo() {{");
|
||||
* emit.indent();
|
||||
* emit.line("int x = {};", 42);
|
||||
* emit.dedent();
|
||||
* emit.line("}}");
|
||||
* @endcode
|
||||
*/
|
||||
class CodeEmitter {
|
||||
public:
|
||||
virtual ~CodeEmitter() = default;
|
||||
|
||||
//=========================================================================
|
||||
// Indentation
|
||||
//=========================================================================
|
||||
|
||||
/// Increase indentation level
|
||||
virtual void indent() = 0;
|
||||
|
||||
/// Decrease indentation level
|
||||
virtual void dedent() = 0;
|
||||
|
||||
/// Get current indentation string
|
||||
virtual std::string_view indentString() const = 0;
|
||||
|
||||
//=========================================================================
|
||||
// Output
|
||||
//=========================================================================
|
||||
|
||||
/// Write raw string (no indentation, no newline)
|
||||
virtual void raw(std::string_view text) = 0;
|
||||
|
||||
/// Write formatted line with indentation and newline
|
||||
template <typename... Args>
|
||||
void line(fmt::format_string<Args...> fmt, Args&&... args) {
|
||||
raw(indentString());
|
||||
raw(fmt::format(fmt, std::forward<Args>(args)...));
|
||||
raw("\n");
|
||||
}
|
||||
|
||||
/// Write empty line
|
||||
void blankLine() { raw("\n"); }
|
||||
|
||||
/// Write comment line
|
||||
void comment(std::string_view text) {
|
||||
raw(indentString());
|
||||
raw("// ");
|
||||
raw(text);
|
||||
raw("\n");
|
||||
}
|
||||
|
||||
//=========================================================================
|
||||
// CSR State Management
|
||||
//=========================================================================
|
||||
|
||||
/// Get current CSR state
|
||||
CsrState csrState() const { return csrState_; }
|
||||
|
||||
/// Set CSR state (called when mode is established)
|
||||
void setCsrState(CsrState state) { csrState_ = state; }
|
||||
|
||||
/// Ensure CSR is in required state, emitting code if needed
|
||||
virtual void ensureCsrState(CsrState required);
|
||||
|
||||
/// Reset CSR state to Unknown (call after function calls)
|
||||
void resetCsrState() { csrState_ = CsrState::Unknown; }
|
||||
|
||||
protected:
|
||||
CsrState csrState_ = CsrState::Unknown;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief CodeEmitter that writes to a string buffer.
|
||||
*
|
||||
* Useful for tests and building function bodies before output.
|
||||
*/
|
||||
class StringEmitter : public CodeEmitter {
|
||||
public:
|
||||
explicit StringEmitter(int indentWidth = 4) : indentWidth_(indentWidth) {}
|
||||
|
||||
void indent() override {
|
||||
indentLevel_++;
|
||||
updateIndentString();
|
||||
}
|
||||
|
||||
void dedent() override {
|
||||
if (indentLevel_ > 0)
|
||||
indentLevel_--;
|
||||
updateIndentString();
|
||||
}
|
||||
|
||||
std::string_view indentString() const override { return indentStr_; }
|
||||
|
||||
void raw(std::string_view text) override { buffer_.append(text); }
|
||||
|
||||
/// Get the accumulated output
|
||||
const std::string& str() const { return buffer_; }
|
||||
|
||||
/// Clear the buffer
|
||||
void clear() { buffer_.clear(); }
|
||||
|
||||
/// Move the buffer out
|
||||
std::string take() { return std::move(buffer_); }
|
||||
|
||||
private:
|
||||
void updateIndentString() { indentStr_ = std::string(indentLevel_ * indentWidth_, ' '); }
|
||||
|
||||
std::string buffer_;
|
||||
std::string indentStr_;
|
||||
int indentLevel_ = 0;
|
||||
int indentWidth_ = 4;
|
||||
};
|
||||
|
||||
} // namespace rex::codegen
|
||||
26
include/rex/codegen/code_region.h
Normal file
26
include/rex/codegen/code_region.h
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
/**
|
||||
* @file rex/codegen/code_region.h
|
||||
* @brief Lightweight code region struct (no PPC dependencies)
|
||||
*
|
||||
* @copyright Copyright (c) 2026 Tom Clay <tomc@tctechstuff.com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* @license BSD 3-Clause License
|
||||
* See LICENSE file in the project root for full license text.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace rex::codegen {
|
||||
|
||||
struct CodeRegion {
|
||||
uint32_t start;
|
||||
uint32_t end;
|
||||
|
||||
bool contains(uint32_t addr) const { return addr >= start && addr < end; }
|
||||
uint32_t size() const { return end - start; }
|
||||
};
|
||||
|
||||
} // namespace rex::codegen
|
||||
67
include/rex/codegen/codegen.h
Normal file
67
include/rex/codegen/codegen.h
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
/**
|
||||
* @file rex/codegen/codegen.h
|
||||
* @brief Codegen pipeline orchestrator
|
||||
*
|
||||
* @copyright Copyright (c) 2026 Tom Clay <tomc@tctechstuff.com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* @license BSD 3-Clause License
|
||||
* See LICENSE file in the project root for full license text.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
#include <memory>
|
||||
|
||||
#include <rex/codegen/codegen_context.h>
|
||||
#include <rex/result.h>
|
||||
|
||||
namespace rex {
|
||||
class Runtime;
|
||||
}
|
||||
|
||||
namespace rex::codegen {
|
||||
|
||||
/**
|
||||
* Pipeline orchestrator for code generation.
|
||||
*
|
||||
* Usage:
|
||||
* auto pipeline = CodegenPipeline::Create(configPath);
|
||||
* if (!pipeline) { handle error }
|
||||
* auto result = pipeline->Run();
|
||||
*/
|
||||
class CodegenPipeline {
|
||||
public:
|
||||
~CodegenPipeline();
|
||||
|
||||
// Non-copyable, movable
|
||||
CodegenPipeline(const CodegenPipeline&) = delete;
|
||||
CodegenPipeline& operator=(const CodegenPipeline&) = delete;
|
||||
CodegenPipeline(CodegenPipeline&&) noexcept;
|
||||
CodegenPipeline& operator=(CodegenPipeline&&) noexcept;
|
||||
|
||||
/**
|
||||
* Create pipeline from config file path.
|
||||
* Loads XEX, creates Runtime and CodegenContext.
|
||||
*
|
||||
* @param configPath Path to TOML config file
|
||||
* @return Pipeline on success, error on failure
|
||||
*/
|
||||
static Result<CodegenPipeline> Create(const std::filesystem::path& configPath);
|
||||
|
||||
Result<void> Run(bool force = false);
|
||||
Result<void> RunAnalyze();
|
||||
Result<void> RunWrite(bool force = false);
|
||||
|
||||
CodegenContext& context() { return *ctx_; }
|
||||
const CodegenContext& context() const { return *ctx_; }
|
||||
|
||||
private:
|
||||
CodegenPipeline() = default;
|
||||
|
||||
std::unique_ptr<Runtime> runtime_;
|
||||
std::unique_ptr<CodegenContext> ctx_;
|
||||
};
|
||||
|
||||
} // namespace rex::codegen
|
||||
179
include/rex/codegen/codegen_context.h
Normal file
179
include/rex/codegen/codegen_context.h
Normal file
|
|
@ -0,0 +1,179 @@
|
|||
/**
|
||||
* @file rexcodegen/codegen_context.h
|
||||
* @brief Unified context for codegen pipeline
|
||||
*
|
||||
* @copyright Copyright (c) 2026 Tom Clay <tomc@tctechstuff.com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* @license BSD 3-Clause License
|
||||
* See LICENSE file in the project root for full license text.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include <rex/codegen/analysis_errors.h>
|
||||
#include <rex/codegen/binary_view.h>
|
||||
#include <rex/codegen/code_region.h>
|
||||
#include <rex/codegen/config.h>
|
||||
#include <rex/codegen/function_graph.h>
|
||||
#include <rex/result.h>
|
||||
|
||||
namespace rex {
|
||||
class Runtime;
|
||||
|
||||
namespace runtime {
|
||||
class ExportResolver;
|
||||
}
|
||||
} // namespace rex
|
||||
|
||||
namespace rex::codegen {
|
||||
|
||||
// Forward declarations
|
||||
class DecodedBinary;
|
||||
struct SectionInfo;
|
||||
struct FunctionEntry;
|
||||
struct FunctionConfig;
|
||||
|
||||
/**
|
||||
* Analysis state holding binary-derived data and analysis results.
|
||||
* This data is populated during analysis and should not be mutated after.
|
||||
* Separates analysis state from user-provided config.
|
||||
*/
|
||||
struct AnalysisState {
|
||||
// Binary-derived (set once from BinaryView)
|
||||
std::string format; ///< "xex" or "elf"
|
||||
uint64_t loadAddress = 0; ///< Image base address
|
||||
uint64_t entryPoint = 0; ///< Entry point address
|
||||
uint64_t imageSize = 0; ///< Total image size
|
||||
|
||||
// Analysis results
|
||||
std::vector<SectionInfo> sections; ///< Sections from binary
|
||||
std::vector<FunctionEntry> analyzedFunctions; ///< Discovered functions
|
||||
std::unordered_map<uint32_t, std::vector<uint32_t>> chunksByParent; ///< Chunk lookup
|
||||
|
||||
// Auto-detected ABI helpers (0 = not found)
|
||||
uint32_t restGpr14Address = 0;
|
||||
uint32_t saveGpr14Address = 0;
|
||||
uint32_t restFpr14Address = 0;
|
||||
uint32_t saveFpr14Address = 0;
|
||||
uint32_t restVmx14Address = 0;
|
||||
uint32_t saveVmx14Address = 0;
|
||||
uint32_t restVmx64Address = 0;
|
||||
uint32_t saveVmx64Address = 0;
|
||||
|
||||
// Merged results (user hints + analysis-detected)
|
||||
std::unordered_map<uint32_t, uint32_t> invalidInstructions; ///< addr -> size
|
||||
std::unordered_set<uint32_t> knownIndirectCalls; ///< bctr addresses
|
||||
std::vector<uint32_t> exceptionHandlerFuncs; ///< Handler addresses
|
||||
std::vector<uint32_t> ehDiscoveredFuncs; ///< EH-discovered function addresses
|
||||
};
|
||||
|
||||
/**
|
||||
* Unified context for the entire codegen pipeline.
|
||||
*
|
||||
* This class owns all the core data structures used throughout analysis
|
||||
* and code generation. It replaces the previous scattered ownership where
|
||||
* Recompiler owned some data and AnalysisContext owned other data.
|
||||
*
|
||||
* Single source of truth for:
|
||||
* - Binary data (BinaryView)
|
||||
* - Function graph (all functions including imports)
|
||||
* - Configuration
|
||||
* - Analysis errors
|
||||
* - Scan artifacts
|
||||
*/
|
||||
class CodegenContext {
|
||||
public:
|
||||
// === FACTORY ===
|
||||
/**
|
||||
* Create a CodegenContext from config file path and Runtime.
|
||||
*
|
||||
* This is the primary way to create a context. It:
|
||||
* 1. Loads configuration from the TOML file
|
||||
* 2. Loads the XEX via Runtime
|
||||
* 3. Creates BinaryView from the loaded module
|
||||
*
|
||||
* @param configPath Path to the TOML config file
|
||||
* @param runtime Runtime instance (must be set up with correct content_root)
|
||||
* @return CodegenContext on success, error on failure
|
||||
*/
|
||||
static Result<CodegenContext> Create(const std::filesystem::path& configPath, Runtime& runtime);
|
||||
|
||||
/**
|
||||
* Create a CodegenContext from pre-loaded binary and config.
|
||||
* Primarily for testing where binary is loaded differently.
|
||||
*
|
||||
* @param binary Pre-loaded BinaryView (moved into context)
|
||||
* @param config Pre-loaded RecompilerConfig (moved into context)
|
||||
*/
|
||||
static CodegenContext Create(BinaryView binary, RecompilerConfig config);
|
||||
|
||||
// Non-copyable, movable
|
||||
CodegenContext(const CodegenContext&) = delete;
|
||||
CodegenContext& operator=(const CodegenContext&) = delete;
|
||||
CodegenContext(CodegenContext&&);
|
||||
CodegenContext& operator=(CodegenContext&&);
|
||||
~CodegenContext();
|
||||
|
||||
// === OWNED DATA (single source of truth) ===
|
||||
FunctionGraph graph; ///< All functions (including imports)
|
||||
AnalysisErrors errors; ///< Accumulated errors
|
||||
|
||||
/// Scan phase artifacts (passed to Discover for scanner setup)
|
||||
struct {
|
||||
std::vector<CodeRegion> codeRegions; ///< Null-delimited code regions
|
||||
std::vector<std::pair<uint32_t, uint32_t>> dataRegions;
|
||||
std::unordered_map<uint32_t, uint32_t> pdataSizes; // address -> size
|
||||
} scan;
|
||||
|
||||
// === ACCESSORS ===
|
||||
const BinaryView& binary() const { return binary_; }
|
||||
BinaryView& binary() { return binary_; }
|
||||
|
||||
/// Access the decoded binary (must call initDecoded() first)
|
||||
DecodedBinary& decoded();
|
||||
const DecodedBinary& decoded() const;
|
||||
|
||||
/// Initialize DecodedBinary after context is in final location
|
||||
/// Call this once after Create() before accessing decoded()
|
||||
void initDecoded();
|
||||
|
||||
bool hasDecoded() const { return decoded_ != nullptr; }
|
||||
|
||||
RecompilerConfig& Config() { return config_; }
|
||||
const RecompilerConfig& Config() const { return config_; }
|
||||
|
||||
AnalysisState& analysisState() { return analysisState_; }
|
||||
const AnalysisState& analysisState() const { return analysisState_; }
|
||||
|
||||
runtime::ExportResolver* resolver() const { return resolver_; }
|
||||
void setResolver(runtime::ExportResolver* r) { resolver_ = r; }
|
||||
|
||||
const std::filesystem::path& configDir() const { return configDir_; }
|
||||
void setConfigDir(const std::filesystem::path& dir) { configDir_ = dir; }
|
||||
|
||||
void setDllModule(bool is_dll) { is_dll_module_ = is_dll; }
|
||||
bool isDllModule() const { return is_dll_module_; }
|
||||
|
||||
void setHasDllModules(bool has) { has_dll_modules_ = has; }
|
||||
bool hasDllModules() const { return has_dll_modules_; }
|
||||
|
||||
private:
|
||||
CodegenContext() = default;
|
||||
|
||||
BinaryView binary_; ///< Binary data + sections (owned)
|
||||
RecompilerConfig config_; ///< User configuration (owned)
|
||||
AnalysisState analysisState_; ///< Analysis state (populated during analysis)
|
||||
std::unique_ptr<DecodedBinary> decoded_; ///< Decoded instructions (created via initDecoded())
|
||||
runtime::ExportResolver* resolver_ = nullptr; ///< For runtime resolution (borrowed)
|
||||
std::filesystem::path configDir_; ///< Directory containing config file (for relative paths)
|
||||
bool is_dll_module_ = false; ///< True if this module is a DLL (shared library output)
|
||||
bool has_dll_modules_ = false; ///< True if the project has DLL modules (multi-binary)
|
||||
};
|
||||
|
||||
} // namespace rex::codegen
|
||||
81
include/rex/codegen/codegen_writer.h
Normal file
81
include/rex/codegen/codegen_writer.h
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
/**
|
||||
* @file rex/codegen/codegen_writer.h
|
||||
* @brief Consolidated codegen output writer
|
||||
*
|
||||
* @copyright Copyright (c) 2026 Tom Clay <tomc@tctechstuff.com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* @license BSD 3-Clause License
|
||||
* See LICENSE file in the project root for full license text.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
#include <fmt/core.h>
|
||||
|
||||
#include <rex/codegen/codegen_context.h>
|
||||
|
||||
namespace rex {
|
||||
class Runtime;
|
||||
}
|
||||
|
||||
namespace rex::codegen {
|
||||
|
||||
class CodegenWriter {
|
||||
public:
|
||||
CodegenWriter(CodegenContext& ctx, Runtime* runtime = nullptr);
|
||||
|
||||
/// Run the full output pipeline: validate, clean old files, generate, flush.
|
||||
bool write(bool force);
|
||||
|
||||
/**
|
||||
* Basenames of files removed by the pre-emit cleanup sweep during write().
|
||||
* Populated only after write() completes. Empty otherwise.
|
||||
*/
|
||||
const std::vector<std::string>& deletedFiles() const { return deletedFiles_; }
|
||||
|
||||
/**
|
||||
* Basenames of files written to disk during write() (via FlushPendingWrites).
|
||||
* Populated only after write() completes. Empty otherwise.
|
||||
*/
|
||||
const std::vector<std::string>& writtenFiles() const { return writtenFiles_; }
|
||||
|
||||
private:
|
||||
CodegenContext& ctx_;
|
||||
Runtime* runtime_;
|
||||
|
||||
std::string out;
|
||||
size_t cppFileIndex = 0;
|
||||
std::vector<std::pair<std::string, std::string>> pendingWrites;
|
||||
std::vector<std::string> deletedFiles_;
|
||||
std::vector<std::string> writtenFiles_;
|
||||
|
||||
template <class... Args>
|
||||
void print(fmt::format_string<Args...> fmt, Args&&... args) {
|
||||
fmt::vformat_to(std::back_inserter(out), fmt.get(), fmt::make_format_args(args...));
|
||||
}
|
||||
|
||||
template <class... Args>
|
||||
void println(fmt::format_string<Args...> fmt, Args&&... args) {
|
||||
fmt::vformat_to(std::back_inserter(out), fmt.get(), fmt::make_format_args(args...));
|
||||
out += '\n';
|
||||
}
|
||||
|
||||
void SaveCurrentOutData(std::string_view name = {});
|
||||
void FlushPendingWrites();
|
||||
|
||||
// Convenience accessors
|
||||
FunctionGraph& graph();
|
||||
const FunctionGraph& graph() const;
|
||||
const BinaryView& binary() const;
|
||||
RecompilerConfig& config();
|
||||
const RecompilerConfig& config() const;
|
||||
AnalysisState& analysisState();
|
||||
const AnalysisState& analysisState() const;
|
||||
};
|
||||
|
||||
} // namespace rex::codegen
|
||||
161
include/rex/codegen/config.h
Normal file
161
include/rex/codegen/config.h
Normal file
|
|
@ -0,0 +1,161 @@
|
|||
/**
|
||||
* @file rexcodegen/internal/config.h
|
||||
* @brief Recompiler configuration types
|
||||
*
|
||||
* @copyright Copyright (c) 2026 Tom Clay <tomc@tctechstuff.com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* @license BSD 3-Clause License
|
||||
* See LICENSE file in the project root for full license text.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <filesystem>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
#include <toml++/toml.hpp>
|
||||
|
||||
#include <rex/codegen/function_graph.h> // For JumpTable
|
||||
|
||||
namespace rex::codegen {
|
||||
|
||||
struct MidAsmHook {
|
||||
std::string name;
|
||||
std::vector<std::string> registers;
|
||||
|
||||
bool ret = false;
|
||||
bool returnOnTrue = false;
|
||||
bool returnOnFalse = false;
|
||||
|
||||
uint32_t jumpAddress = 0;
|
||||
uint32_t jumpAddressOnTrue = 0;
|
||||
uint32_t jumpAddressOnFalse = 0;
|
||||
|
||||
bool afterInstruction = false;
|
||||
};
|
||||
|
||||
// Unified function/chunk configuration
|
||||
// A "chunk" is simply a function entry with a non-zero parent field
|
||||
struct FunctionConfig {
|
||||
uint32_t size = 0; // Explicit size in bytes (mutually exclusive with end)
|
||||
uint32_t end = 0; // End address, exclusive (mutually exclusive with size)
|
||||
std::string name; // Custom symbol name (empty = auto-generate sub_XXXXXXXX)
|
||||
uint32_t parent = 0; // Parent function address (0 = standalone, non-zero = chunk)
|
||||
|
||||
// Get effective size (prefers size over end)
|
||||
uint32_t getSize(uint32_t address) const {
|
||||
return size ? size : (end > address ? end - address : 0);
|
||||
}
|
||||
// Returns true if this is a discontinuous chunk belonging to a parent function
|
||||
bool isChunk() const { return parent != 0; }
|
||||
};
|
||||
|
||||
// Section info for analysis output
|
||||
struct SectionInfo {
|
||||
std::string name;
|
||||
uint64_t address = 0;
|
||||
uint64_t size = 0;
|
||||
std::string flags; // "rx", "rw", "r" etc.
|
||||
};
|
||||
|
||||
// Function entry for analysis output
|
||||
struct FunctionEntry {
|
||||
uint64_t address = 0;
|
||||
uint64_t size = 0;
|
||||
std::string name; // optional, defaults to "sub_XXXXXXXX"
|
||||
};
|
||||
|
||||
struct RecompilerConfig {
|
||||
// === Required user-provided fields ===
|
||||
std::string projectName = "rex"; ///< Project name for output files
|
||||
std::string filePath; ///< Path to XEX/ELF file
|
||||
std::string outDirectoryPath; ///< Output directory for generated code
|
||||
std::string templateDir; ///< Optional custom template directory for overrides
|
||||
|
||||
// Patch file paths (TODO: implement patching workflow)
|
||||
std::string patchFilePath;
|
||||
std::string patchedFilePath;
|
||||
|
||||
// === Code generation options (optional) ===
|
||||
bool skipLr = false;
|
||||
bool ctrAsLocalVariable = false;
|
||||
bool xerAsLocalVariable = false;
|
||||
bool reservedRegisterAsLocalVariable = false;
|
||||
bool skipMsr = false;
|
||||
bool crRegistersAsLocalVariables = false;
|
||||
bool nonArgumentRegistersAsLocalVariables = false;
|
||||
bool nonVolatileRegistersAsLocalVariables = false;
|
||||
bool generateExceptionHandlers = false; ///< Generate SEH exception handler wrappers
|
||||
|
||||
// === Analysis tuning (optional) ===
|
||||
uint32_t maxJumpExtension = 65536; ///< Max bytes to extend function for jump table targets
|
||||
uint32_t dataRegionThreshold = 16; ///< Consecutive invalid instructions to mark as data region
|
||||
uint32_t largeFunctionThreshold = 1048576; ///< 1MB - warn if function exceeds this size
|
||||
|
||||
// Optional override for DLL module flag. If unset, the orchestrator infers
|
||||
// from the module's position in the manifest (entrypoint = false, modules = true).
|
||||
std::optional<bool> isDll;
|
||||
|
||||
// === Manual overrides ===
|
||||
std::unordered_map<uint32_t, FunctionConfig> functions; ///< Function/chunk configuration
|
||||
std::unordered_map<uint32_t, JumpTable> switchTables;
|
||||
std::unordered_map<uint32_t, MidAsmHook> midAsmHooks;
|
||||
uint32_t longJmpAddress = 0;
|
||||
uint32_t setJmpAddress = 0;
|
||||
|
||||
// === rexcrt: CRT function address overrides ===
|
||||
// Maps function name -> guest address (e.g. "CreateFileA" -> 0x8248B780)
|
||||
// Parsed from [rexcrt] TOML table. Codegen generates rexcrt_<Name> entries.
|
||||
std::unordered_map<std::string, uint32_t> rexcrtFunctions;
|
||||
|
||||
// === User hints (merged with analysis results in AnalysisState) ===
|
||||
std::unordered_map<uint32_t, uint32_t> invalidInstructionHints; ///< addr -> size
|
||||
std::unordered_set<uint32_t>
|
||||
knownIndirectCallHints; ///< bctr addresses that are vtable/computed calls
|
||||
std::vector<uint32_t> exceptionHandlerFuncHints; ///< Additional exception handler addresses
|
||||
|
||||
/**
|
||||
* Load configuration from a TOML file.
|
||||
*
|
||||
* Supports an optional `includes` array for layered config. Paths in
|
||||
* `includes` resolve relative to the including file's directory. Merge
|
||||
* semantics: scalars last-wins, keyed tables additive (same key = last
|
||||
* wins), arrays-of-tables deduplicated by primary key, sets additive.
|
||||
*
|
||||
* @param configFilePath Path to the TOML config file
|
||||
* @return true on success, false on error (parse failure, circular
|
||||
* include, depth exceeded)
|
||||
*/
|
||||
bool Load(const std::string_view& configFilePath);
|
||||
|
||||
/**
|
||||
* Load configuration from an in-memory TOML table (e.g. an inline binary
|
||||
* entry inside a manifest). Includes referenced from the table resolve
|
||||
* relative to `base_dir`. Same merge semantics as Load().
|
||||
*/
|
||||
bool LoadFromTable(const toml::table& tbl, const std::filesystem::path& base_dir);
|
||||
|
||||
/// Validation result containing warnings and errors.
|
||||
struct ValidationResult {
|
||||
bool valid = true; ///< true if no errors (warnings OK)
|
||||
std::vector<std::string> warnings; ///< Non-fatal issues
|
||||
std::vector<std::string> errors; ///< Fatal issues that block codegen
|
||||
|
||||
explicit operator bool() const { return valid; }
|
||||
};
|
||||
|
||||
/**
|
||||
* Validate the loaded configuration.
|
||||
* Checks address alignment, required fields, and sanity constraints.
|
||||
* @return ValidationResult with warnings and errors
|
||||
*/
|
||||
ValidationResult Validate() const;
|
||||
};
|
||||
|
||||
} // namespace rex::codegen
|
||||
202
include/rex/codegen/function_graph.h
Normal file
202
include/rex/codegen/function_graph.h
Normal file
|
|
@ -0,0 +1,202 @@
|
|||
/**
|
||||
* @file rex/codegen/function_graph.h
|
||||
* @brief Function graph - reactive model for function discovery and resolution
|
||||
*
|
||||
* @copyright Copyright (c) 2026 Tom Clay <tomc@tctechstuff.com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* @license BSD 3-Clause License
|
||||
* See LICENSE file in the project root for full license text.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <rex/codegen/function_node.h>
|
||||
|
||||
namespace rex::codegen {
|
||||
|
||||
//=============================================================================
|
||||
// Function Graph
|
||||
//=============================================================================
|
||||
// Container for all function nodes. Manages resolution notifications.
|
||||
// Also handles vacancy checking for merge eligibility.
|
||||
//
|
||||
// Vacancy Rules - A region is vacant if ALL are true:
|
||||
// 1. No null dword at the boundary
|
||||
// 2. No chunk claims the region
|
||||
// 3. Target does not fall within a protected function's range:
|
||||
// - PDATA/CONFIG/HELPER: always protected (cannot merge into)
|
||||
// - DISCOVERED with xrefs: CAN be merged (treated as potential internal label)
|
||||
|
||||
class FunctionGraph {
|
||||
public:
|
||||
using MemoryReader = std::function<std::optional<uint32_t>(uint32_t addr)>;
|
||||
|
||||
//=========================================================================
|
||||
// Code Buffer Management
|
||||
//=========================================================================
|
||||
|
||||
// Add a code buffer (copies executable section data into graph)
|
||||
void addCodeBuffer(uint32_t baseAddress, const uint8_t* data, size_t size);
|
||||
|
||||
// Translate guest address to host pointer (searches all code buffers)
|
||||
const uint8_t* translateCode(uint32_t addr) const;
|
||||
|
||||
// Get all code buffers (for iteration/debugging)
|
||||
const std::vector<CodeBuffer>& codeBuffers() const { return codeBuffers_; }
|
||||
|
||||
// Update all function code pointers (call after loading code buffers)
|
||||
void updateFunctionCodePointers();
|
||||
|
||||
//=========================================================================
|
||||
// Function Management
|
||||
//=========================================================================
|
||||
|
||||
// Add a function to the graph.
|
||||
// Returns the new node, or existing node if already present (higher authority wins).
|
||||
// Notifies all PENDING functions to try resolution against the new entry.
|
||||
// hasXrefs: true if this is a known call target (bl target, etc.)
|
||||
FunctionNode* addFunction(uint32_t base, uint32_t size, FunctionAuthority authority,
|
||||
bool hasXrefs = false);
|
||||
|
||||
// Add a named function to the graph (convenience overload)
|
||||
FunctionNode* addFunction(uint32_t base, uint32_t size, FunctionAuthority authority,
|
||||
std::string_view name, bool hasXrefs = false);
|
||||
|
||||
// Add a resolved import as a callable function with __imp__ name
|
||||
// Address is the thunk address that bl instructions target
|
||||
FunctionNode* addImportFunction(uint32_t address, std::string_view resolvedName);
|
||||
|
||||
// Get function by entry point (O(1))
|
||||
FunctionNode* getFunction(uint32_t entryPoint);
|
||||
const FunctionNode* getFunction(uint32_t entryPoint) const;
|
||||
|
||||
// Remove function from graph (for cleanup of absorbed GAP_FILLs)
|
||||
bool removeFunction(uint32_t entryPoint);
|
||||
|
||||
// Get function containing address (O(log f) via sorted base index)
|
||||
FunctionNode* getFunctionContaining(uint32_t addr);
|
||||
const FunctionNode* getFunctionContaining(uint32_t addr) const;
|
||||
|
||||
// Check if address is a known entry point
|
||||
bool isEntryPoint(uint32_t addr) const;
|
||||
|
||||
// Check if address is an import (FunctionNode with IMPORT authority)
|
||||
bool isImport(uint32_t addr) const;
|
||||
|
||||
// Iterate all functions (includes imports with IMPORT authority)
|
||||
const std::unordered_map<uint32_t, std::unique_ptr<FunctionNode>>& functions() const {
|
||||
return functions_;
|
||||
}
|
||||
|
||||
// Get all PENDING functions
|
||||
std::vector<FunctionNode*> getPendingFunctions();
|
||||
|
||||
// Get all SEALED functions
|
||||
std::vector<FunctionNode*> getSealedFunctions();
|
||||
|
||||
// Statistics
|
||||
size_t functionCount() const { return functions_.size(); }
|
||||
size_t pendingCount() const;
|
||||
size_t sealedCount() const;
|
||||
|
||||
//=========================================================================
|
||||
// Function Setup (called during Discover phase)
|
||||
//=========================================================================
|
||||
|
||||
// Set function name
|
||||
void setFunctionName(uint32_t entry, std::string name);
|
||||
|
||||
// Set exception handler flag
|
||||
void setFunctionHasExceptionHandler(uint32_t entry, bool val);
|
||||
|
||||
// Set parsed exception info (SEH or C++ EH)
|
||||
void setFunctionExceptionInfo(uint32_t entry, ExceptionInfo info);
|
||||
|
||||
// Add a block to a function
|
||||
void addBlockToFunction(uint32_t entry, Block block);
|
||||
|
||||
// Add a label (internal branch target) to a function
|
||||
void addLabelToFunction(uint32_t entry, uint32_t label);
|
||||
|
||||
// Add a resolved call to a function
|
||||
void addCallToFunction(uint32_t entry, uint32_t site, CallTarget target);
|
||||
|
||||
// Add a resolved tail call to a function
|
||||
void addTailCallToFunction(uint32_t entry, uint32_t site, CallTarget target);
|
||||
|
||||
// Add a jump table to a function
|
||||
void addJumpTableToFunction(uint32_t entry, JumpTable jt);
|
||||
|
||||
// Add an unresolved jump to a function
|
||||
// isCall: true for bl (call), false for b (tail call)
|
||||
void addUnresolvedJumpToFunction(uint32_t entry, uint32_t site, uint32_t target, bool isCall,
|
||||
bool conditional);
|
||||
|
||||
//=========================================================================
|
||||
// Resolution and Expansion (called during Merge phase)
|
||||
//=========================================================================
|
||||
|
||||
// Try to resolve all unresolved jumps for a function
|
||||
// Checks against known functions, imports, and internal labels
|
||||
// Returns number of jumps resolved
|
||||
size_t tryResolveFunction(uint32_t entry);
|
||||
|
||||
// Absorb a region into a function (for vacancy expansion)
|
||||
void absorbRegionIntoFunction(uint32_t entry, uint32_t regionBase, uint32_t regionSize);
|
||||
|
||||
// Seal a function if it can be sealed
|
||||
// Returns true if function was sealed
|
||||
bool trySealFunction(uint32_t entry);
|
||||
|
||||
// Seal all functions that can be sealed
|
||||
// Returns number of functions sealed
|
||||
size_t sealAllReady();
|
||||
|
||||
// Seal all functions, throwing if any cannot be sealed
|
||||
// Use after discovery is complete to enforce all functions are ready
|
||||
void sealAll();
|
||||
|
||||
//=========================================================================
|
||||
// Vacancy Checking
|
||||
//=========================================================================
|
||||
|
||||
// Set the memory reader for null-dword checking
|
||||
void setMemoryReader(MemoryReader reader) { memoryReader_ = std::move(reader); }
|
||||
|
||||
// Register a chunk (address range claimed by config, blocks vacancy)
|
||||
void registerChunk(uint32_t base, uint32_t size);
|
||||
|
||||
// Check if a region is vacant for absorption
|
||||
// fromAddr: the address we're expanding from (to check for null boundary)
|
||||
// targetAddr: the start of the region we want to absorb
|
||||
bool isVacant(uint32_t fromAddr, uint32_t targetAddr) const;
|
||||
|
||||
// Check if target is a mergeable entry point (DISCOVERED with xrefs)
|
||||
bool isMergeableEntryPoint(uint32_t addr) const;
|
||||
|
||||
//=========================================================================
|
||||
// Target Classification (for code generation)
|
||||
//=========================================================================
|
||||
|
||||
// Classify a branch target for code generation.
|
||||
// target: address being branched to
|
||||
// callerAddr: address of the branch instruction
|
||||
// isCallInstruction: true for bl (expects return), false for b (no return)
|
||||
// Returns how the target should be treated during code generation.
|
||||
TargetKind classifyTarget(uint32_t target, uint32_t callerAddr, bool isCallInstruction) const;
|
||||
|
||||
private:
|
||||
std::vector<CodeBuffer> codeBuffers_;
|
||||
std::unordered_map<uint32_t, std::unique_ptr<FunctionNode>> functions_;
|
||||
std::map<uint32_t, FunctionNode*>
|
||||
functionsByBase_; // sorted by base for O(log f) interval lookup
|
||||
std::unordered_map<uint32_t, bool> functionHasXrefs_; // entry -> hasXrefs
|
||||
std::vector<std::pair<uint32_t, uint32_t>> chunks_; // base, size pairs
|
||||
MemoryReader memoryReader_;
|
||||
|
||||
// Notify all PENDING functions that a new function was added
|
||||
void notifyFunctionAdded(FunctionNode* newFunction);
|
||||
};
|
||||
|
||||
} // namespace rex::codegen
|
||||
203
include/rex/codegen/function_node.h
Normal file
203
include/rex/codegen/function_node.h
Normal file
|
|
@ -0,0 +1,203 @@
|
|||
/**
|
||||
* @file rex/codegen/function_node.h
|
||||
* @brief FunctionNode - core object representing a function in the graph
|
||||
*
|
||||
* @copyright Copyright (c) 2026 Tom Clay <tomc@tctechstuff.com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* @license BSD 3-Clause License
|
||||
* See LICENSE file in the project root for full license text.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <rex/codegen/function_types.h>
|
||||
|
||||
namespace rex::codegen {
|
||||
|
||||
//=============================================================================
|
||||
// Function Node
|
||||
//=============================================================================
|
||||
// Core object representing a function in the graph.
|
||||
// Manages its own state transitions and resolution.
|
||||
|
||||
class FunctionNode {
|
||||
friend class FunctionGraph; // Graph manages all mutations
|
||||
|
||||
public:
|
||||
FunctionNode(uint32_t base, uint32_t size, FunctionAuthority authority);
|
||||
|
||||
//=========================================================================
|
||||
// Read-only accessors - external code can inspect but not modify
|
||||
//=========================================================================
|
||||
|
||||
// Identity
|
||||
uint32_t base() const { return base_; }
|
||||
uint32_t size() const { return size_; }
|
||||
uint32_t end() const { return base_ + size_; }
|
||||
const std::string& name() const { return name_; }
|
||||
|
||||
// Code access - cached pointer to instruction bytes
|
||||
const uint8_t* code() const { return code_; }
|
||||
bool hasCode() const { return code_ != nullptr; }
|
||||
|
||||
// Authority and state
|
||||
FunctionAuthority authority() const { return authority_; }
|
||||
FunctionState state() const { return state_; }
|
||||
|
||||
// State queries
|
||||
bool isRegistered() const { return state_ == FunctionState::kRegistered; }
|
||||
bool isDiscovered() const { return state_ == FunctionState::kDiscovered; }
|
||||
bool isSealed() const { return state_ == FunctionState::kSealed; }
|
||||
|
||||
// Legacy aliases (PENDING maps to kRegistered OR kDiscovered - not sealed)
|
||||
bool isPending() const { return state_ != FunctionState::kSealed; }
|
||||
|
||||
// Special type checks
|
||||
bool isImport() const { return authority_ == FunctionAuthority::IMPORT; }
|
||||
bool isHelper() const { return authority_ == FunctionAuthority::HELPER; }
|
||||
|
||||
//=========================================================================
|
||||
// State Machine - New 3-state model
|
||||
//=========================================================================
|
||||
|
||||
/// Can transition from kRegistered to kDiscovered?
|
||||
bool canDiscover() const { return state_ == FunctionState::kRegistered; }
|
||||
|
||||
/// Transition kRegistered -> kDiscovered with blocks and instructions
|
||||
/// Precondition: canDiscover() returns true
|
||||
/// For non-imports: blocks must not be empty
|
||||
void discover(std::vector<Block> blocks,
|
||||
std::vector<rex::codegen::ppc::Instruction*> instructions,
|
||||
std::set<uint32_t> labels);
|
||||
|
||||
/// Transition kRegistered -> kDiscovered for import functions (no blocks)
|
||||
void discoverAsImport();
|
||||
|
||||
/// Can transition from kDiscovered to kSealed?
|
||||
/// Returns true if:
|
||||
/// - state == kDiscovered
|
||||
/// - imports: always OK (no blocks required)
|
||||
/// - non-imports: blocks not empty AND no unresolved branches
|
||||
bool canSeal() const;
|
||||
|
||||
/// Transition kDiscovered -> kSealed
|
||||
/// Computes FunctionAnalysis and sorts blocks
|
||||
void seal();
|
||||
|
||||
/// Get analysis result (only valid after seal)
|
||||
const FunctionAnalysis& analysis() const;
|
||||
|
||||
//=========================================================================
|
||||
// Code Emission (valid after seal)
|
||||
//=========================================================================
|
||||
|
||||
/// Emit C++ code for this function
|
||||
/// Requires: state() == kSealed
|
||||
/// For imports: emits REX_IMPORT macro
|
||||
/// For normal functions: emits REX_FUNC with blocks and instructions
|
||||
std::string emitCpp(const EmitContext& ctx) const;
|
||||
|
||||
//=========================================================================
|
||||
// Instruction access (valid after discover)
|
||||
//=========================================================================
|
||||
|
||||
/// Get owned instructions (pointers into DecodedBinary)
|
||||
std::span<rex::codegen::ppc::Instruction* const> instructions() const { return instructions_; }
|
||||
|
||||
// Blocks
|
||||
const std::vector<Block>& blocks() const { return blocks_; }
|
||||
bool containsAddress(uint32_t addr) const;
|
||||
|
||||
// Check if address is within overall function bounds (ignores blocks)
|
||||
// Use this for branch target detection where address may be in a gap between blocks
|
||||
bool isWithinBounds(uint32_t addr) const { return addr >= base_ && addr < base_ + size_; }
|
||||
|
||||
// Labels (internal branch targets within this function)
|
||||
const std::set<uint32_t>& labels() const { return labels_; }
|
||||
bool isLabel(uint32_t addr) const { return labels_.contains(addr); }
|
||||
|
||||
// Resolved calls (bl instructions)
|
||||
const std::vector<CallEdge>& calls() const { return calls_; }
|
||||
|
||||
// Resolved tail calls (b instructions to other functions)
|
||||
const std::vector<CallEdge>& tailCalls() const { return tailCalls_; }
|
||||
|
||||
// Jump tables
|
||||
const std::vector<JumpTable>& jumpTables() const { return jumpTables_; }
|
||||
|
||||
// Unresolved jumps (pending resolution)
|
||||
const std::vector<UnresolvedJump>& unresolvedJumps() const { return unresolvedJumps_; }
|
||||
|
||||
// Sealing state (legacy - use canSeal() with new semantics)
|
||||
bool hasUnresolvedJumps() const { return !unresolvedJumps_.empty(); }
|
||||
|
||||
// Validation
|
||||
bool hasExceptionHandler() const { return hasExceptionHandler_; }
|
||||
|
||||
// Exception info (SEH or C++ EH)
|
||||
const std::optional<ExceptionInfo>& exceptionInfo() const { return exceptionInfo_; }
|
||||
bool hasExceptionInfo() const { return exceptionInfo_.has_value() && exceptionInfo_->hasInfo(); }
|
||||
|
||||
void setName(std::string name) { name_ = std::move(name); }
|
||||
|
||||
private:
|
||||
//=========================================================================
|
||||
// Mutation methods - only FunctionGraph can call these
|
||||
//=========================================================================
|
||||
|
||||
void setCode(const uint8_t* ptr) { code_ = ptr; }
|
||||
void setHasExceptionHandler(bool val) { hasExceptionHandler_ = val; }
|
||||
void setExceptionInfo(ExceptionInfo info) { exceptionInfo_ = std::move(info); }
|
||||
|
||||
// Block/label management
|
||||
void addBlock(Block block);
|
||||
void addLabel(uint32_t addr);
|
||||
|
||||
// Call tracking
|
||||
void addCall(uint32_t site, CallTarget target);
|
||||
void addTailCall(uint32_t site, CallTarget target);
|
||||
void addJumpTable(JumpTable jt);
|
||||
void addUnresolvedJump(uint32_t site, uint32_t target, bool isCall, bool conditional);
|
||||
|
||||
// Resolution (reactive - called by graph on events)
|
||||
bool tryResolveAgainst(FunctionNode* newFunction);
|
||||
bool tryResolveAgainstImport(uint32_t importAddr, const std::string& importName);
|
||||
bool tryResolveAsInternalLabel(uint32_t target);
|
||||
|
||||
// Expansion
|
||||
void absorbRegion(uint32_t regionBase, uint32_t regionSize);
|
||||
|
||||
// Internal helper
|
||||
void removeUnresolvedJump(uint32_t site);
|
||||
|
||||
//=========================================================================
|
||||
// State
|
||||
//=========================================================================
|
||||
private:
|
||||
uint32_t base_;
|
||||
uint32_t size_;
|
||||
std::string name_;
|
||||
const uint8_t* code_ = nullptr; // Cached pointer to instruction bytes
|
||||
FunctionAuthority authority_;
|
||||
FunctionState state_ = FunctionState::kRegistered;
|
||||
bool hasExceptionHandler_ = false;
|
||||
|
||||
// Populated at discover()
|
||||
std::vector<Block> blocks_;
|
||||
std::vector<rex::codegen::ppc::Instruction*> instructions_; // Pointers into DecodedBinary
|
||||
std::set<uint32_t> labels_; // Branch targets within this function
|
||||
|
||||
std::vector<CallEdge> calls_;
|
||||
std::vector<CallEdge> tailCalls_;
|
||||
std::vector<JumpTable> jumpTables_;
|
||||
|
||||
std::vector<UnresolvedJump> unresolvedJumps_;
|
||||
|
||||
std::optional<ExceptionInfo> exceptionInfo_;
|
||||
|
||||
// Computed at seal()
|
||||
std::optional<FunctionAnalysis> analysis_;
|
||||
};
|
||||
|
||||
} // namespace rex::codegen
|
||||
283
include/rex/codegen/function_scanner.h
Normal file
283
include/rex/codegen/function_scanner.h
Normal file
|
|
@ -0,0 +1,283 @@
|
|||
/**
|
||||
* @file rex/codegen/function_scanner.h
|
||||
* @brief Function scanner and block discovery
|
||||
*
|
||||
* @copyright Copyright (c) 2026 Tom Clay <tomc@tctechstuff.com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* @license BSD 3-Clause License
|
||||
* See LICENSE file in the project root for full license text.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
#include <rex/codegen/config.h>
|
||||
#include <rex/codegen/function_graph.h> // For FunctionAuthority, TargetKind
|
||||
#include <rex/result.h>
|
||||
#include <rex/types.h>
|
||||
|
||||
namespace rex::codegen {
|
||||
|
||||
// Forward declarations
|
||||
struct CodeRegion;
|
||||
class BinaryView;
|
||||
class DecodedBinary;
|
||||
|
||||
// FunctionAuthority is defined in function_graph.h
|
||||
|
||||
//=============================================================================
|
||||
// Block-Based Discovery Types
|
||||
//=============================================================================
|
||||
|
||||
/**
|
||||
* Basic block discovered during recursive block discovery.
|
||||
* Used as temporary scanner state during discovery when function extent is unknown.
|
||||
* Converted to FunctionNode::Block when added to the graph.
|
||||
*
|
||||
* projectedSize: Size limit for conditional branch fall-through.
|
||||
* When a conditional branch is taken, the fall-through block gets a projectedSize
|
||||
* equal to the distance to the branch target. This prevents the fall-through from
|
||||
* consuming unrelated code beyond the branch target.
|
||||
*/
|
||||
struct DiscoveredBlock {
|
||||
rex::guest_addr_t base = 0; // Start address
|
||||
rex::guest_addr_t end = 0; // End address (exclusive, points after last instruction)
|
||||
bool has_terminator = false; // Ends with blr/bctr/unconditional branch
|
||||
int64_t projectedSize =
|
||||
-1; // Size limit (-1 = unlimited), set on conditional branch fall-through
|
||||
std::vector<rex::guest_addr_t> successors; // Branch targets (for CFG building)
|
||||
};
|
||||
|
||||
/**
|
||||
* Result of block-based function discovery.
|
||||
* Contains all blocks reachable from entry point.
|
||||
*/
|
||||
struct FunctionBlocks {
|
||||
rex::guest_addr_t entry = 0; // Function entry point
|
||||
std::vector<DiscoveredBlock> blocks; // All discovered blocks
|
||||
rex::u32 pdata_size = 0; // From pdata (0 if unknown)
|
||||
std::vector<JumpTable> jump_tables; // Detected jump tables
|
||||
std::vector<rex::guest_addr_t> external_calls; // bl targets outside this function
|
||||
std::vector<rex::guest_addr_t> tail_calls; // Unconditional branches to other functions
|
||||
};
|
||||
|
||||
//=============================================================================
|
||||
// Function Scanner
|
||||
//=============================================================================
|
||||
|
||||
/**
|
||||
* PowerPC function scanner
|
||||
*
|
||||
* Implements heuristics for function boundary detection:
|
||||
* - Linear sweep from entry point
|
||||
* - Furthest branch target tracking
|
||||
* - Return/indirect branch detection
|
||||
* - Prologue/epilogue pattern matching
|
||||
*
|
||||
* Uses BinaryView for binary introspection.
|
||||
*/
|
||||
class FunctionScanner {
|
||||
public:
|
||||
explicit FunctionScanner(const BinaryView& binary);
|
||||
|
||||
/**
|
||||
* Detect jump table pattern at bctr instruction
|
||||
* @param bctr_address Address of the bctr instruction
|
||||
* @return JumpTable on success, nullopt if no jump table detected
|
||||
*/
|
||||
std::optional<JumpTable> detect_jump_table(rex::guest_addr_t bctr_address);
|
||||
|
||||
/**
|
||||
* Discover all reachable blocks from entry point.
|
||||
* Uses recursive block discovery with pending stack.
|
||||
* @param entry_point Function entry point address
|
||||
* @param pdata_size Optional size from .pdata (0 if unknown)
|
||||
* @return FunctionBlocks containing all discovered blocks
|
||||
*/
|
||||
FunctionBlocks discover_blocks(rex::guest_addr_t entry_point, rex::u32 pdata_size = 0);
|
||||
|
||||
// Address translation via Module's Memory
|
||||
template <typename T>
|
||||
const T* translate_address(rex::guest_addr_t guest_addr) const;
|
||||
|
||||
// Check if address is in an executable section
|
||||
bool isExecutableSection(rex::guest_addr_t address) const;
|
||||
|
||||
// Set known switch table addresses (from config) to skip auto-detection
|
||||
void setKnownSwitchTables(const std::unordered_set<uint32_t>& addresses) {
|
||||
known_switch_tables_ = addresses;
|
||||
}
|
||||
|
||||
// Set discontinuous function chunks (chunk_addr -> FunctionConfig with parent set)
|
||||
void setChunks(const std::unordered_map<uint32_t, FunctionConfig>& chunks) { chunks_ = chunks; }
|
||||
|
||||
// Set bl targets that need resolution (for Fix 4: shared helper promotion)
|
||||
void setBlTargets(const std::unordered_set<uint32_t>& targets) { bl_targets_ = targets; }
|
||||
|
||||
// Get collected bl targets from discovery
|
||||
const std::unordered_set<uint32_t>& getBlTargets() const { return bl_targets_; }
|
||||
|
||||
void setKnownCallables(const std::unordered_set<uint32_t>& callables) {
|
||||
known_callables_ = callables;
|
||||
}
|
||||
|
||||
bool isKnownCallable(uint32_t address) const { return known_callables_.contains(address); }
|
||||
|
||||
void setDataRegions(const std::vector<std::pair<uint32_t, uint32_t>>& regions) {
|
||||
data_regions_ = regions;
|
||||
}
|
||||
|
||||
bool isInDataRegion(uint32_t address) const {
|
||||
for (const auto& [start, end] : data_regions_) {
|
||||
if (address >= start && address < end)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Set code regions for boundary checking (prevents cross-region merges)
|
||||
void setCodeRegions(const std::vector<CodeRegion>* regions) { code_regions_ = regions; }
|
||||
|
||||
// Find which code region contains the given address (nullptr if not found)
|
||||
const CodeRegion* findRegionContaining(uint32_t address) const;
|
||||
|
||||
/**
|
||||
* Check if a branch from currentAddr to targetAddr stays within the same
|
||||
* code region OR targets a configured chunk of the current function.
|
||||
* Used to determine if a branch is internal or a tail call.
|
||||
*
|
||||
* @param currentAddr Address of the branch instruction
|
||||
* @param targetAddr Branch target address
|
||||
* @param functionEntry Entry point of the current function being discovered
|
||||
* @return true if the branch should be treated as internal
|
||||
*/
|
||||
bool isInternalBranch(uint32_t currentAddr, uint32_t targetAddr, uint32_t functionEntry) const;
|
||||
|
||||
// Check if an address is within a chunk belonging to the given function
|
||||
bool isWithinChunk(uint32_t address, uint32_t function_entry) const {
|
||||
for (const auto& [chunk_start, cfg] : chunks_) {
|
||||
if (cfg.parent == function_entry) {
|
||||
uint32_t chunk_end =
|
||||
cfg.end ? cfg.end : (cfg.size ? chunk_start + cfg.size : chunk_start + 0x1000);
|
||||
if (address >= chunk_start && address < chunk_end) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Find chunk parent for an address (returns 0 if not in any chunk)
|
||||
uint32_t findChunkParent(uint32_t address) const {
|
||||
// First check exact chunk start matches
|
||||
auto it = chunks_.find(address);
|
||||
if (it != chunks_.end()) {
|
||||
return it->second.parent;
|
||||
}
|
||||
// Then check if address is within any chunk range
|
||||
for (const auto& [chunk_start, cfg] : chunks_) {
|
||||
uint32_t chunk_end =
|
||||
cfg.end ? cfg.end : (cfg.size ? chunk_start + cfg.size : chunk_start + 0x1000);
|
||||
if (address >= chunk_start && address < chunk_end) {
|
||||
return cfg.parent;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private:
|
||||
const BinaryView* binary_ = nullptr;
|
||||
std::unordered_set<uint32_t> known_switch_tables_;
|
||||
std::unordered_map<uint32_t, FunctionConfig> chunks_;
|
||||
std::unordered_set<uint32_t> bl_targets_;
|
||||
std::unordered_set<uint32_t> known_callables_;
|
||||
std::vector<std::pair<uint32_t, uint32_t>> data_regions_;
|
||||
const std::vector<CodeRegion>* code_regions_ = nullptr;
|
||||
|
||||
// Helper methods for function boundary detection
|
||||
bool is_prologue_pattern(rex::guest_addr_t address);
|
||||
bool is_epilogue_pattern(rex::guest_addr_t address);
|
||||
bool is_restgprlr_function(rex::guest_addr_t address);
|
||||
};
|
||||
|
||||
//=============================================================================
|
||||
// Block Discovery Result
|
||||
//=============================================================================
|
||||
|
||||
struct UnresolvedBranch {
|
||||
uint32_t site; // Address of branch instruction
|
||||
uint32_t target; // Target address
|
||||
bool isCall; // true = bl (call), false = b (tail/jump)
|
||||
bool isConditional; // true = bc/beq/etc, false = unconditional
|
||||
};
|
||||
|
||||
struct BlockDiscoveryResult {
|
||||
std::vector<Block> blocks;
|
||||
std::vector<UnresolvedBranch> unresolvedBranches;
|
||||
std::vector<JumpTable> jumpTables;
|
||||
std::set<uint32_t> labels;
|
||||
|
||||
// Collected instruction pointers (for FunctionNode ownership)
|
||||
std::vector<rex::codegen::ppc::Instruction*> instructions;
|
||||
|
||||
// External references found during discovery
|
||||
std::vector<uint32_t> externalCalls; // bl to unknown targets
|
||||
std::vector<uint32_t> tailCalls; // b to external targets
|
||||
};
|
||||
|
||||
//=============================================================================
|
||||
// Block Discovery Function
|
||||
//=============================================================================
|
||||
|
||||
/**
|
||||
* Discover all blocks belonging to a function starting at entryPoint.
|
||||
*
|
||||
* Algorithm:
|
||||
* - Worklist-based block discovery
|
||||
* - Linear sweep until terminator (blr, bctr, unconditional b)
|
||||
* - Follow both paths for conditional branches
|
||||
* - Detect jump tables at bctr instructions
|
||||
* - Stop at code region boundaries (null padding)
|
||||
*
|
||||
* @param decoded The decoded binary (single-pass decoded instructions)
|
||||
* @param entryPoint Starting address of the function
|
||||
* @param containingRegion Code region containing the entry point
|
||||
* @param knownFunctions Set of known function entry points (to detect tail calls)
|
||||
* @return BlockDiscoveryResult containing blocks, branches, and jump tables
|
||||
*/
|
||||
BlockDiscoveryResult discoverBlocks(DecodedBinary& decoded, uint32_t entryPoint,
|
||||
const CodeRegion& containingRegion,
|
||||
const std::unordered_set<uint32_t>& knownFunctions,
|
||||
uint32_t pdataSize = 0);
|
||||
|
||||
//=============================================================================
|
||||
// Jump Table Detection
|
||||
//=============================================================================
|
||||
|
||||
/**
|
||||
* Detect jump table at a bctr instruction.
|
||||
*
|
||||
* Patterns detected:
|
||||
* - ABSOLUTE: lwzx loads full 32-bit addresses
|
||||
* - COMPUTED: lbzx + rlwinm (byte offset with shift)
|
||||
* - BYTEOFFSET: lbzx + add (byte offset direct)
|
||||
* - SHORTOFFSET: lhzx + add (16-bit offset)
|
||||
*
|
||||
* @param decoded The decoded binary
|
||||
* @param bctrAddr Address of the bctr instruction
|
||||
* @param containingRegion Code region for validation
|
||||
* @return JumpTable if detected, empty optional otherwise
|
||||
*/
|
||||
std::optional<JumpTable> detectJumpTable(DecodedBinary& decoded, uint32_t bctrAddr,
|
||||
const CodeRegion& containingRegion, uint32_t funcStart,
|
||||
uint32_t funcEnd);
|
||||
|
||||
} // namespace rex::codegen
|
||||
295
include/rex/codegen/function_types.h
Normal file
295
include/rex/codegen/function_types.h
Normal file
|
|
@ -0,0 +1,295 @@
|
|||
/**
|
||||
* @file rex/codegen/function_types.h
|
||||
* @brief Types and structures used by FunctionNode and FunctionGraph
|
||||
*
|
||||
* @copyright Copyright (c) 2026 Tom Clay <tomc@tctechstuff.com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* @license BSD 3-Clause License
|
||||
* See LICENSE file in the project root for full license text.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <bitset>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <set>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
#include <rex/types.h>
|
||||
|
||||
namespace rex::codegen::ppc {
|
||||
struct Instruction;
|
||||
} // namespace rex::codegen::ppc
|
||||
|
||||
namespace rex::runtime {
|
||||
class ExportResolver;
|
||||
} // namespace rex::runtime
|
||||
|
||||
namespace rex::codegen {
|
||||
|
||||
// Forward declarations
|
||||
class FunctionGraph;
|
||||
class FunctionNode;
|
||||
class BinaryView;
|
||||
struct RecompilerConfig;
|
||||
|
||||
/// Lightweight context passed to FunctionNode::emitCpp() and BuilderContext.
|
||||
/// Non-owning references -- caller must ensure lifetimes.
|
||||
struct EmitContext {
|
||||
const BinaryView& binary;
|
||||
const RecompilerConfig& config;
|
||||
const FunctionGraph& graph;
|
||||
uint32_t entryPoint = 0; ///< For "xstart" naming
|
||||
runtime::ExportResolver* resolver = nullptr; ///< For import ordinal resolution (nullable)
|
||||
};
|
||||
|
||||
//=============================================================================
|
||||
// Authority Levels
|
||||
//=============================================================================
|
||||
// Determines boundary mutability and merge eligibility.
|
||||
// Only GAP_FILL can be absorbed during vacancy merging.
|
||||
// All others represent immutable entry points.
|
||||
enum class FunctionAuthority : uint8_t {
|
||||
GAP_FILL = 0, // Speculative - found in unclaimed gap, CAN be absorbed
|
||||
DISCOVERED = 1, // Found via bl/bcl - immutable entry point
|
||||
VTABLE = 2, // Found in vtable - immutable entry point
|
||||
HELPER = 3, // Save/restore helpers - fixed, overlaps allowed
|
||||
PDATA = 4, // From .pdata - entry fixed, can extend
|
||||
CONFIG = 5, // User config - exact boundaries, immutable
|
||||
IMPORT = 6, // Import thunk - external function, immutable
|
||||
};
|
||||
|
||||
//=============================================================================
|
||||
// Target Classification (for code generation)
|
||||
//=============================================================================
|
||||
enum class TargetKind {
|
||||
InternalLabel, // Target inside caller's function (PIC pattern)
|
||||
Function, // Target is a function entry point
|
||||
Import, // Target is an import
|
||||
Unknown, // Target not recognized
|
||||
};
|
||||
|
||||
const char* AuthorityName(FunctionAuthority auth);
|
||||
|
||||
//=============================================================================
|
||||
// Function State (3-state machine)
|
||||
//=============================================================================
|
||||
enum class FunctionState : uint8_t {
|
||||
kRegistered, // Entry point known, blocks/instructions not yet assigned
|
||||
kDiscovered, // Blocks and instructions assigned, may have unresolved branches
|
||||
kSealed, // All branches resolved, ready for code generation
|
||||
};
|
||||
|
||||
// Legacy aliases for compatibility during migration
|
||||
constexpr FunctionState PENDING = FunctionState::kRegistered; // Will be removed
|
||||
constexpr FunctionState SEALED = FunctionState::kSealed; // Will be removed
|
||||
|
||||
//=============================================================================
|
||||
// Exception Handling - SEH (Structured Exception Handling)
|
||||
//=============================================================================
|
||||
|
||||
struct SehScope {
|
||||
uint32_t tryStart; // [+0] Start of __try block
|
||||
uint32_t tryEnd; // [+4] End of __try block
|
||||
uint32_t handler; // [+8] Handler function (__finally or __except body)
|
||||
uint32_t filter; // [+C] Filter expression (0 for __finally, address for __except)
|
||||
};
|
||||
|
||||
struct SehExceptionInfo {
|
||||
uint32_t handlerThunk; // e.g. __C_specific_handler thunk address
|
||||
uint32_t scopeTableAddr; // Pointer to scope table in .rdata
|
||||
std::vector<SehScope> scopes;
|
||||
uint32_t frameSize = 0; // Stack frame size for r12 setup during unwind
|
||||
uint32_t restoreHelper = 0; // __restgprlr_N address to call on unwind
|
||||
};
|
||||
|
||||
//=============================================================================
|
||||
// Exception Handling - C++ EH (FuncInfo with magic 0x19930522)
|
||||
//=============================================================================
|
||||
|
||||
constexpr uint32_t CXX_EH_MAGIC = 0x19930522;
|
||||
|
||||
struct CxxUnwindEntry {
|
||||
int32_t toState; // Previous state (-1 = terminal)
|
||||
uint32_t action; // Cleanup/destructor function address
|
||||
};
|
||||
|
||||
struct CxxIPStateEntry {
|
||||
uint32_t ip; // Code address where state changes
|
||||
int32_t state; // State number at this IP
|
||||
};
|
||||
|
||||
struct CxxCatchHandler {
|
||||
uint32_t adjectives; // Catch type flags
|
||||
uint32_t typeDescriptor; // Pointer to type descriptor (RTTI)
|
||||
int32_t catchObjDisplacement; // Displacement of catch object
|
||||
uint32_t handlerAddress; // Catch handler function address
|
||||
};
|
||||
|
||||
struct CxxTryBlock {
|
||||
int32_t tryLow; // Lowest state in try
|
||||
int32_t tryHigh; // Highest state in try
|
||||
int32_t catchHigh; // Highest state in catch
|
||||
std::vector<CxxCatchHandler> handlers;
|
||||
};
|
||||
|
||||
struct CxxExceptionInfo {
|
||||
uint32_t handlerThunk; // Frame handler function
|
||||
uint32_t funcInfoAddr; // Address of FuncInfo in .rdata
|
||||
uint32_t maxState; // Number of unwind states
|
||||
std::vector<CxxUnwindEntry> unwindMap;
|
||||
std::vector<CxxTryBlock> tryBlocks;
|
||||
std::vector<CxxIPStateEntry> ipToStateMap;
|
||||
};
|
||||
|
||||
//=============================================================================
|
||||
// Combined Exception Info (variant of SEH or C++ EH)
|
||||
//=============================================================================
|
||||
|
||||
struct ExceptionInfo {
|
||||
std::variant<std::monostate, SehExceptionInfo, CxxExceptionInfo> data;
|
||||
|
||||
bool hasInfo() const { return !std::holds_alternative<std::monostate>(data); }
|
||||
bool isSeh() const { return std::holds_alternative<SehExceptionInfo>(data); }
|
||||
bool isCxx() const { return std::holds_alternative<CxxExceptionInfo>(data); }
|
||||
|
||||
const SehExceptionInfo* asSeh() const { return std::get_if<SehExceptionInfo>(&data); }
|
||||
const CxxExceptionInfo* asCxx() const { return std::get_if<CxxExceptionInfo>(&data); }
|
||||
|
||||
uint32_t handlerThunk() const {
|
||||
if (auto* seh = asSeh())
|
||||
return seh->handlerThunk;
|
||||
if (auto* cxx = asCxx())
|
||||
return cxx->handlerThunk;
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
//=============================================================================
|
||||
// Call Target - Resolved destination of a call/jump
|
||||
//=============================================================================
|
||||
struct CallTarget {
|
||||
struct ToFunction {
|
||||
FunctionNode* node;
|
||||
};
|
||||
struct ToImport {
|
||||
uint32_t address;
|
||||
std::string name;
|
||||
};
|
||||
struct Unresolved {
|
||||
uint32_t address;
|
||||
};
|
||||
|
||||
std::variant<ToFunction, ToImport, Unresolved> value;
|
||||
|
||||
bool isResolved() const { return !std::holds_alternative<Unresolved>(value); }
|
||||
bool isFunction() const { return std::holds_alternative<ToFunction>(value); }
|
||||
bool isImport() const { return std::holds_alternative<ToImport>(value); }
|
||||
|
||||
FunctionNode* asFunction() const {
|
||||
if (auto* f = std::get_if<ToFunction>(&value))
|
||||
return f->node;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static CallTarget function(FunctionNode* fn) { return {ToFunction{fn}}; }
|
||||
static CallTarget import(uint32_t addr, std::string name) {
|
||||
return {ToImport{addr, std::move(name)}};
|
||||
}
|
||||
static CallTarget unresolved(uint32_t addr) { return {Unresolved{addr}}; }
|
||||
};
|
||||
|
||||
//=============================================================================
|
||||
// Call Edge - A call site within a function
|
||||
//=============================================================================
|
||||
|
||||
struct CallEdge {
|
||||
uint32_t site; // Address of the bl/b instruction
|
||||
CallTarget target; // Resolved or unresolved target
|
||||
};
|
||||
|
||||
//=============================================================================
|
||||
// Basic Block
|
||||
//=============================================================================
|
||||
|
||||
struct Block {
|
||||
uint32_t base;
|
||||
uint32_t size;
|
||||
|
||||
uint32_t end() const { return base + size; }
|
||||
bool contains(uint32_t addr) const { return addr >= base && addr < end(); }
|
||||
};
|
||||
|
||||
//=============================================================================
|
||||
// Jump Table
|
||||
//=============================================================================
|
||||
|
||||
struct JumpTable {
|
||||
uint32_t bctrAddress; // Address of bctr instruction
|
||||
uint32_t tableAddress; // Address of jump table data
|
||||
uint8_t indexRegister; // Register holding switch index
|
||||
std::vector<uint32_t> targets; // Resolved case targets (internal labels)
|
||||
};
|
||||
|
||||
//=============================================================================
|
||||
// Function Analysis (computed at seal time)
|
||||
//=============================================================================
|
||||
|
||||
struct FunctionAnalysis {
|
||||
// CSR requirements (denormal handling)
|
||||
enum class CsrRequirement : uint8_t { None, Fpu, Vmx };
|
||||
|
||||
// Special register usage
|
||||
bool usesCtr = false;
|
||||
bool usesXer = false;
|
||||
bool usesCr = false;
|
||||
bool usesFpscr = false;
|
||||
|
||||
// CSR state needed
|
||||
CsrRequirement csrRequirement = CsrRequirement::None;
|
||||
};
|
||||
|
||||
//=============================================================================
|
||||
// Unresolved Jump - Internal jump awaiting resolution
|
||||
//=============================================================================
|
||||
|
||||
struct UnresolvedJump {
|
||||
uint32_t site; // Address of the branch instruction
|
||||
uint32_t target; // Target address
|
||||
bool isCall; // true = bl (call), false = b (tail call)
|
||||
bool isConditional; // true = bc/beq/bne/etc, false = b
|
||||
};
|
||||
|
||||
//=============================================================================
|
||||
// Code Buffer - Holds executable code for a section
|
||||
//=============================================================================
|
||||
// The graph owns code buffers so recompilation doesn't need module access.
|
||||
// Each buffer corresponds to one executable section.
|
||||
|
||||
struct CodeBuffer {
|
||||
std::vector<uint8_t> data;
|
||||
uint32_t baseAddress = 0;
|
||||
|
||||
uint32_t size() const { return static_cast<uint32_t>(data.size()); }
|
||||
uint32_t endAddress() const { return baseAddress + size(); }
|
||||
|
||||
bool contains(uint32_t addr) const { return addr >= baseAddress && addr < endAddress(); }
|
||||
|
||||
const uint8_t* translate(uint32_t addr) const {
|
||||
if (!contains(addr))
|
||||
return nullptr;
|
||||
return data.data() + (addr - baseAddress);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace rex::codegen
|
||||
70
include/rex/codegen/manifest.h
Normal file
70
include/rex/codegen/manifest.h
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
/**
|
||||
* @file rex/codegen/manifest.h
|
||||
* @brief Manifest TOML parser for multi-binary projects
|
||||
*
|
||||
* @copyright Copyright (c) 2026 Tom Clay <tomc@tctechstuff.com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* @license BSD 3-Clause License
|
||||
* See LICENSE file in the project root for full license text.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
#include <rex/codegen/config.h>
|
||||
|
||||
namespace rex::codegen {
|
||||
|
||||
/**
|
||||
* Codegen settings for a single binary inside a manifest. The entrypoint
|
||||
* uses an empty `guestPath`; module entries set it to the canonicalized
|
||||
* guest-visible path the host runtime resolves against.
|
||||
*/
|
||||
struct BinaryConfig {
|
||||
RecompilerConfig recompiler;
|
||||
std::string guestPath;
|
||||
};
|
||||
|
||||
/**
|
||||
* Canonicalize a module guest path: device-stripped, slashes/case normalized,
|
||||
* with `<project>/assets/` stripped when a matching project name is given.
|
||||
*/
|
||||
std::string CanonicalizeModuleGuestPath(std::string_view path, std::string_view project_name = {});
|
||||
|
||||
/**
|
||||
* Parsed manifest TOML. Construct via Load(); treat as read-only after.
|
||||
*/
|
||||
struct ManifestConfig {
|
||||
std::string projectName;
|
||||
std::optional<std::string> sdkVersion; ///< Last SDK that ran codegen on this project
|
||||
std::optional<std::string> gameRoot; ///< Game asset root, relative to manifestDir.
|
||||
///< Set by `rexglue init` to anchor DLL guest paths.
|
||||
std::filesystem::path manifestDir; ///< Directory containing the manifest
|
||||
BinaryConfig entrypoint; ///< Entrypoint codegen settings (inline)
|
||||
std::vector<BinaryConfig> modules; ///< DLL module codegen settings (inline)
|
||||
|
||||
/**
|
||||
* Load a manifest TOML file. Returns nullopt on parse failure.
|
||||
*/
|
||||
static std::optional<ManifestConfig> Load(const std::filesystem::path& path);
|
||||
|
||||
/**
|
||||
* True when `path` parses as a manifest (i.e. has a `[project]` section).
|
||||
*/
|
||||
static bool IsManifest(const std::filesystem::path& path);
|
||||
|
||||
/**
|
||||
* Insert or overwrite [project].sdk_version in the manifest file at `path`.
|
||||
* Preserves the rest of the file's content. Returns false on parse or write
|
||||
* failure.
|
||||
*/
|
||||
static bool WriteSdkVersionStamp(const std::filesystem::path& path, std::string_view version);
|
||||
};
|
||||
|
||||
} // namespace rex::codegen
|
||||
40
include/rex/codegen/phases.h
Normal file
40
include/rex/codegen/phases.h
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
/**
|
||||
* @file rexcodegen/phases.h
|
||||
* @brief Analysis phase entry points
|
||||
*
|
||||
* @copyright Copyright (c) 2026 Tom Clay <tomc@tctechstuff.com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* @license BSD 3-Clause License
|
||||
* See LICENSE file in the project root for full license text.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <rex/codegen/codegen_context.h>
|
||||
#include <rex/codegen/progress_reporter.h>
|
||||
#include <rex/result.h>
|
||||
|
||||
namespace rex::codegen::phases {
|
||||
|
||||
/// Register PDATA, CONFIG, helpers, imports into FunctionGraph
|
||||
VoidResult Register(CodegenContext& ctx, ProgressReporter* reporter = nullptr);
|
||||
|
||||
/// Scan binary for bl targets, thunks, bctr locations
|
||||
VoidResult Scan(CodegenContext& ctx, ProgressReporter* reporter = nullptr);
|
||||
|
||||
/// Discover function blocks from candidates
|
||||
VoidResult Discover(CodegenContext& ctx, ProgressReporter* reporter = nullptr);
|
||||
|
||||
/// Vacancy-based function expansion and sealing
|
||||
VoidResult Merge(CodegenContext& ctx, ProgressReporter* reporter = nullptr);
|
||||
|
||||
/// Find uncovered code regions and register them as functions
|
||||
VoidResult GapFill(CodegenContext& ctx, ProgressReporter* reporter = nullptr);
|
||||
|
||||
/// Validate all call targets resolve
|
||||
VoidResult Validate(CodegenContext& ctx, ProgressReporter* reporter = nullptr);
|
||||
|
||||
} // namespace rex::codegen::phases
|
||||
49
include/rex/codegen/progress_reporter.h
Normal file
49
include/rex/codegen/progress_reporter.h
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
/**
|
||||
* @file rex/codegen/progress_reporter.h
|
||||
* @brief Abstract callback interface for codegen pipeline progress
|
||||
*
|
||||
* @copyright Copyright (c) 2026 Tom Clay <tomc@tctechstuff.com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* @license BSD 3-Clause License
|
||||
* See LICENSE file in the project root for full license text.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
#include <cstddef>
|
||||
#include <string_view>
|
||||
|
||||
namespace rex::codegen {
|
||||
|
||||
/**
|
||||
* Optional progress callback invoked by the codegen pipeline at module
|
||||
* and phase boundaries. CLI consumers implement this to drive a progress
|
||||
* view; library-internal callers (tests, headless callers) can pass
|
||||
* nullptr to opt out.
|
||||
*
|
||||
* All methods are called from the thread that drove the pipeline; no
|
||||
* cross-thread synchronization is implied.
|
||||
*/
|
||||
class ProgressReporter {
|
||||
public:
|
||||
virtual ~ProgressReporter() = default;
|
||||
|
||||
/** A new module's analysis+write cycle is starting. `index` is 0-based. */
|
||||
virtual void moduleStarted(std::string_view name, std::size_t index, std::size_t total) = 0;
|
||||
|
||||
/** A named phase within the current module is starting. */
|
||||
virtual void phaseChanged(std::string_view name) = 0;
|
||||
|
||||
/** The current module finished successfully. */
|
||||
virtual void moduleFinished(std::chrono::milliseconds elapsed) = 0;
|
||||
|
||||
/** Project-level (non-module) emit phase started, e.g. "module_registry". */
|
||||
virtual void projectPhaseStarted(std::string_view name) = 0;
|
||||
|
||||
/** Project-level emit phase finished. */
|
||||
virtual void projectPhaseFinished() = 0;
|
||||
};
|
||||
|
||||
} // namespace rex::codegen
|
||||
53
include/rex/codegen/project_recompiler.h
Normal file
53
include/rex/codegen/project_recompiler.h
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
/**
|
||||
* @file rex/codegen/project_recompiler.h
|
||||
* @brief Project-level recompiler driving manifest-based multi-binary codegen
|
||||
*
|
||||
* @copyright Copyright (c) 2026 Tom Clay <tomc@tctechstuff.com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* @license BSD 3-Clause License
|
||||
* See LICENSE file in the project root for full license text.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <rex/codegen/manifest.h>
|
||||
#include <rex/codegen/progress_reporter.h>
|
||||
#include <rex/result.h>
|
||||
|
||||
namespace rex::codegen {
|
||||
|
||||
struct ProjectRecompilerOptions {
|
||||
std::vector<std::string> targets; // empty = all
|
||||
bool force = false;
|
||||
bool enableExceptionHandlers = false;
|
||||
ProgressReporter* reporter = nullptr;
|
||||
};
|
||||
|
||||
class ProjectRecompiler {
|
||||
public:
|
||||
explicit ProjectRecompiler(ManifestConfig manifest);
|
||||
Result<void> Run(const ProjectRecompilerOptions& opts);
|
||||
|
||||
/**
|
||||
* Aggregated basenames of files removed across all modules during the most
|
||||
* recent Run() call. Empty until Run() completes successfully.
|
||||
*/
|
||||
const std::vector<std::string>& deletedFiles() const { return deletedFiles_; }
|
||||
|
||||
/**
|
||||
* Aggregated basenames of files written across all modules during the most
|
||||
* recent Run() call. Empty until Run() completes successfully.
|
||||
*/
|
||||
const std::vector<std::string>& writtenFiles() const { return writtenFiles_; }
|
||||
|
||||
private:
|
||||
ManifestConfig manifest_;
|
||||
std::vector<std::string> deletedFiles_;
|
||||
std::vector<std::string> writtenFiles_;
|
||||
};
|
||||
|
||||
} // namespace rex::codegen
|
||||
67
include/rex/codegen/sig_scanner.h
Normal file
67
include/rex/codegen/sig_scanner.h
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
/**
|
||||
* @file rex/codegen/sig_scanner.h
|
||||
* @brief Signature scanner for pattern-based function discovery
|
||||
*
|
||||
* @copyright Copyright (c) 2026 Tom Clay <tomc@tctechstuff.com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* @license BSD 3-Clause License
|
||||
* See LICENSE file in the project root for full license text.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include <rex/system/module.h>
|
||||
|
||||
namespace rex::codegen {
|
||||
|
||||
//=============================================================================
|
||||
// Signature - Pattern definition for matching
|
||||
//=============================================================================
|
||||
// Dword-based patterns follow PPC's per-instruction size.
|
||||
// Each pattern word is matched against memory using the corresponding mask.
|
||||
// A mask of 0xFFFFFFFF means exact match; partial masks allow wildcards.
|
||||
|
||||
struct Signature {
|
||||
std::string name; // e.g., "__savegprlr_14"
|
||||
std::vector<uint32_t> pattern; // Instruction words to match
|
||||
std::vector<uint32_t> mask; // Bits that must match (0xFFFFFFFF = exact)
|
||||
size_t entryOffset = 0; // Offset from pattern start to entry point
|
||||
std::optional<size_t> size; // Known size, or nullopt to compute from pattern
|
||||
};
|
||||
|
||||
//=============================================================================
|
||||
// SigScanner - Pattern-based signature matcher
|
||||
//=============================================================================
|
||||
|
||||
class SigScanner {
|
||||
public:
|
||||
explicit SigScanner(const runtime::Module& module);
|
||||
|
||||
// Scan for a single signature, return all match entry points
|
||||
std::vector<uint32_t> scan(const Signature& sig);
|
||||
|
||||
// Scan for multiple signatures at once (more efficient - single pass)
|
||||
std::unordered_map<std::string, std::vector<uint32_t>> scanAll(
|
||||
const std::vector<Signature>& sigs);
|
||||
|
||||
// Built-in signature sets
|
||||
static std::vector<Signature> helperSignatures(); // __save/__restore helpers
|
||||
static std::vector<Signature> hleSignatures(); // memset, memmove, etc. (future)
|
||||
|
||||
private:
|
||||
const runtime::Module& module_;
|
||||
|
||||
// Scan a single range for a pattern
|
||||
std::vector<uint32_t> scanRange(uint32_t start, uint32_t end,
|
||||
const std::vector<uint32_t>& pattern,
|
||||
const std::vector<uint32_t>& mask, size_t entryOffset);
|
||||
};
|
||||
|
||||
} // namespace rex::codegen
|
||||
69
include/rex/codegen/template_registry.h
Normal file
69
include/rex/codegen/template_registry.h
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
/**
|
||||
* @file rex/codegen/template_registry.h
|
||||
* @brief Template registry for inja-based code generation
|
||||
*
|
||||
* @copyright Copyright (c) 2026 Tom Clay <tomc@tctechstuff.com>
|
||||
* @license BSD 3-Clause License
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace rex::codegen {
|
||||
|
||||
/// Exception thrown on template parse or render errors.
|
||||
class TemplateError : public std::runtime_error {
|
||||
public:
|
||||
TemplateError(const std::string& templateId, const std::string& message,
|
||||
const std::string& source = "embedded");
|
||||
|
||||
const std::string& templateId() const { return templateId_; }
|
||||
const std::string& source() const { return source_; }
|
||||
|
||||
private:
|
||||
std::string templateId_;
|
||||
std::string source_;
|
||||
};
|
||||
|
||||
/// Resolves and renders inja templates by canonical ID.
|
||||
/// Supports embedded defaults with optional filesystem overrides.
|
||||
///
|
||||
/// Uses pimpl to keep inja.hpp and nlohmann/json.hpp out of this header.
|
||||
/// The render() method accepts serialized JSON (const std::string&) to avoid
|
||||
/// exposing nlohmann::json in the public API.
|
||||
class TemplateRegistry {
|
||||
public:
|
||||
TemplateRegistry();
|
||||
~TemplateRegistry();
|
||||
|
||||
TemplateRegistry(const TemplateRegistry&) = delete;
|
||||
TemplateRegistry& operator=(const TemplateRegistry&) = delete;
|
||||
TemplateRegistry(TemplateRegistry&&) noexcept;
|
||||
TemplateRegistry& operator=(TemplateRegistry&&) noexcept;
|
||||
|
||||
/// Load overrides from a directory. Files that don't match
|
||||
/// a known canonical ID produce a warning log.
|
||||
void loadOverrides(const std::filesystem::path& dir);
|
||||
|
||||
/// Render a template by canonical ID with JSON data (as serialized string).
|
||||
/// @throws TemplateError on parse or render failure
|
||||
std::string render(const std::string& id, const std::string& jsonData);
|
||||
|
||||
/// Render from raw template string (for testing). Not tied to a canonical ID.
|
||||
std::string renderString(const std::string& templateContent, const std::string& jsonData);
|
||||
|
||||
/// List all registered canonical IDs.
|
||||
std::vector<std::string> registeredIds() const;
|
||||
|
||||
private:
|
||||
struct Impl;
|
||||
std::unique_ptr<Impl> impl_;
|
||||
};
|
||||
|
||||
} // namespace rex::codegen
|
||||
84
include/rex/codegen/test_support.h
Normal file
84
include/rex/codegen/test_support.h
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
/**
|
||||
* @file rex/codegen/test_support.h
|
||||
* @brief Test support utilities
|
||||
*
|
||||
* @copyright Copyright (c) 2026 Tom Clay <tomc@tctechstuff.com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* @license BSD 3-Clause License
|
||||
* See LICENSE file in the project root for full license text.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
#include <rex/system/module.h>
|
||||
|
||||
namespace rex::codegen {
|
||||
|
||||
/**
|
||||
* @brief Lightweight Module for loading raw binary test data
|
||||
*
|
||||
* TestModule provides the Module interface needed by FunctionScanner and
|
||||
* Recompiler without requiring a full Runtime/FunctionDispatcher setup. It accepts
|
||||
* raw binary data by reference (caller owns the buffer).
|
||||
*
|
||||
* Usage:
|
||||
* std::vector<uint8_t> data = load_binary_file(...);
|
||||
* TestModule module;
|
||||
* module.Load(0x82010000, data.data(), data.size());
|
||||
* recompiler.module_ = &module;
|
||||
*/
|
||||
class TestModule : public runtime::Module {
|
||||
public:
|
||||
TestModule();
|
||||
~TestModule() override = default;
|
||||
|
||||
/**
|
||||
* @brief Load binary data for analysis
|
||||
* @param base_address Guest virtual address where code is loaded
|
||||
* @param data Pointer to binary data (caller owns, must outlive TestModule usage)
|
||||
* @param size Size of binary data in bytes
|
||||
*/
|
||||
void Load(uint32_t base_address, const uint8_t* data, size_t size);
|
||||
|
||||
// Module interface overrides
|
||||
const std::string& name() const override { return name_; }
|
||||
void set_name(const std::string& name) { name_ = name; }
|
||||
bool is_executable() const override { return true; }
|
||||
uint32_t base_address() const override { return base_address_; }
|
||||
uint32_t image_size() const override { return size_; }
|
||||
uint32_t entry_point() const override { return base_address_; }
|
||||
bool ContainsAddress(uint32_t address) override;
|
||||
|
||||
private:
|
||||
std::string name_{"test"};
|
||||
uint32_t base_address_ = 0;
|
||||
uint32_t size_ = 0;
|
||||
};
|
||||
|
||||
class CodegenContext;
|
||||
|
||||
/**
|
||||
* @brief Analyze a test binary using map symbols as function entry points.
|
||||
*
|
||||
* Filters symbols to test_ prefixed entries, adds them as functions to
|
||||
* ctx.graph with single blocks, then scans for bl instructions and
|
||||
* registers call edges.
|
||||
*
|
||||
* @param ctx Codegen context whose graph will be populated
|
||||
* @param testName Stem name used for function naming (e.g. "addi")
|
||||
* @param symbols Address-to-name map from parse_map_file()
|
||||
* @param baseAddress Base address the binary was linked at
|
||||
* @param data Pointer to raw binary data
|
||||
* @param dataSize Size of binary data in bytes
|
||||
*/
|
||||
void AnalyzeTestBinary(CodegenContext& ctx, std::string_view testName,
|
||||
const std::map<size_t, std::string>& symbols, uint32_t baseAddress,
|
||||
const uint8_t* data, size_t dataSize);
|
||||
|
||||
} // namespace rex::codegen
|
||||
90
include/rex/codegen/vtable_scanner.h
Normal file
90
include/rex/codegen/vtable_scanner.h
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
/**
|
||||
* @file rex/codegen/vtable_scanner.h
|
||||
* @brief VTable scanner - RTTI-based vtable discovery
|
||||
*
|
||||
* @copyright Copyright (c) 2026 Tom Clay <tomc@tctechstuff.com>
|
||||
* All rights reserved.
|
||||
*
|
||||
* @license BSD 3-Clause License
|
||||
* See LICENSE file in the project root for full license text.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <rex/codegen/binary_view.h>
|
||||
|
||||
namespace rex::codegen {
|
||||
|
||||
//=============================================================================
|
||||
// RTTI Structures (MSVC-based, Xbox 360)
|
||||
//=============================================================================
|
||||
|
||||
// Type descriptor - contains mangled class name
|
||||
struct RTTITypeDescriptor {
|
||||
uint32_t pVFTable; // Always points to type_info vtable
|
||||
uint32_t spare; // Runtime use, always 0 in image
|
||||
// char name[]; // Mangled name: ".?AVClassName@@"
|
||||
};
|
||||
|
||||
// Complete Object Locator - links vtable to type info
|
||||
struct RTTICompleteObjectLocator {
|
||||
uint32_t signature; // Always 0 for 32-bit
|
||||
uint32_t offset; // Offset of this vtable in complete class
|
||||
uint32_t cdOffset; // Constructor displacement offset
|
||||
uint32_t pTypeDescriptor; // Pointer to TypeDescriptor
|
||||
uint32_t pClassHierarchy; // Pointer to class hierarchy descriptor
|
||||
};
|
||||
|
||||
//=============================================================================
|
||||
// VTableInfo - Result of vtable scanning
|
||||
//=============================================================================
|
||||
|
||||
struct VTableInfo {
|
||||
uint32_t vtableAddress; // Address of vtable[0]
|
||||
uint32_t colAddress; // RTTI Complete Object Locator address
|
||||
std::string className; // Demangled class name (optional)
|
||||
std::vector<uint32_t> slots; // Function addresses in vtable
|
||||
};
|
||||
|
||||
//=============================================================================
|
||||
// VTableScanner - RTTI-based vtable discovery
|
||||
//=============================================================================
|
||||
|
||||
class VTableScanner {
|
||||
public:
|
||||
explicit VTableScanner(const BinaryView& binary);
|
||||
|
||||
// Scan for all vtables via RTTI traversal
|
||||
std::vector<VTableInfo> scan();
|
||||
|
||||
private:
|
||||
const BinaryView& binary_;
|
||||
|
||||
// Find all Complete Object Locators in .rdata
|
||||
std::vector<uint32_t> findCompleteObjectLocators();
|
||||
|
||||
// Find the vtable that references a given COL (COL is at vtable[-1])
|
||||
std::optional<uint32_t> findVTableForCOL(uint32_t colAddr);
|
||||
|
||||
// Read all function slots from a vtable (until non-executable address)
|
||||
std::vector<uint32_t> readVTableSlots(uint32_t vtableStart);
|
||||
|
||||
// Extract class name from type descriptor
|
||||
std::string extractClassName(uint32_t colAddr);
|
||||
|
||||
// Check if an address points to executable code
|
||||
bool isExecutableAddress(uint32_t addr) const;
|
||||
|
||||
// Read a dword from the binary
|
||||
std::optional<uint32_t> readDword(uint32_t addr) const;
|
||||
|
||||
// Read a string from the binary
|
||||
std::string readString(uint32_t addr, size_t maxLen = 256) const;
|
||||
};
|
||||
|
||||
} // namespace rex::codegen
|
||||
18
include/rex/crypto/sha256.h
Normal file
18
include/rex/crypto/sha256.h
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
/**
|
||||
* SHA256 hashing utilities
|
||||
*
|
||||
* Provides SHA256 hashing for strings and files, used for cache invalidation.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
namespace rex::crypto {
|
||||
|
||||
std::string sha256(std::string_view data);
|
||||
std::string sha256_file(const std::filesystem::path& path);
|
||||
|
||||
} // namespace rex::crypto
|
||||
520
include/rex/cvar.h
Normal file
520
include/rex/cvar.h
Normal file
|
|
@ -0,0 +1,520 @@
|
|||
/**
|
||||
* @file cvar.h
|
||||
* @brief cvar (configuration variable) system
|
||||
*
|
||||
* @section cvar_defining Defining CVars
|
||||
*
|
||||
* Define cvars in a single .cpp file using REXCVAR_DEFINE_* macros:
|
||||
* @code
|
||||
* // In flags.cpp or your module's .cpp file
|
||||
* REXCVAR_DEFINE_BOOL(my_flag, false, "Category", "Description");
|
||||
* REXCVAR_DEFINE_INT32(my_int, 42, "Category", "An integer setting");
|
||||
* REXCVAR_DEFINE_STRING(my_string, "default", "Category", "A string setting");
|
||||
* @endcode
|
||||
*
|
||||
* Available types: BOOL, INT32, INT64, UINT32, UINT64, DOUBLE, STRING, COMMAND
|
||||
*
|
||||
* @section cvar_declaring Declaring CVars (for use in other files)
|
||||
*
|
||||
* @code
|
||||
* // In a header or other .cpp that needs access
|
||||
* REXCVAR_DECLARE(bool, my_flag);
|
||||
* REXCVAR_DECLARE(int32_t, my_int);
|
||||
* REXCVAR_DECLARE(std::string, my_string);
|
||||
* @endcode
|
||||
*
|
||||
* @section cvar_access Getting and Setting Values
|
||||
*
|
||||
* @code
|
||||
* // Type-safe access (preferred for known cvars)
|
||||
* bool value = REXCVAR_GET(my_flag);
|
||||
* REXCVAR_SET(my_flag, true);
|
||||
*
|
||||
* // String-based access (for dynamic/runtime lookup)
|
||||
* std::string str_val = rex::cvar::GetFlagByName("my_flag");
|
||||
* rex::cvar::SetFlagByName("my_flag", "true");
|
||||
* @endcode
|
||||
*
|
||||
* @section cvar_metadata Adding Metadata
|
||||
*
|
||||
* Chain metadata methods after the DEFINE macro:
|
||||
* @code
|
||||
* REXCVAR_DEFINE_INT32(scale, 1, "GPU", "Resolution scale")
|
||||
* .range(1, 8)
|
||||
* .lifecycle(rex::cvar::Lifecycle::kRequiresRestart);
|
||||
*
|
||||
* REXCVAR_DEFINE_STRING(backend, "d3d12", "GPU", "Render backend")
|
||||
* .allowed({"d3d12", "vulkan"})
|
||||
* .lifecycle(rex::cvar::Lifecycle::kInitOnly);
|
||||
*
|
||||
* REXCVAR_DEFINE_BOOL(debug_overlay, false, "Debug", "Show overlay")
|
||||
* .debug_only();
|
||||
* @endcode
|
||||
*
|
||||
* @section cvar_guidelines Metadata Guidelines
|
||||
*
|
||||
* - .lifecycle(kInitOnly) - Device/backend selection that cannot change after init
|
||||
* - .lifecycle(kRequiresRestart) - Settings that need restart to take effect
|
||||
* - .lifecycle(kHotReload) - Default; can change at runtime with immediate effect
|
||||
* - .range(min, max) - Numeric bounds validation
|
||||
* - .allowed({...}) - String enum validation
|
||||
* - .debug_only() - Mark as debug-only (for filtering in release UIs)
|
||||
* - .validator(fn) - Custom validation function
|
||||
*
|
||||
* @section cvar_query Querying CVars
|
||||
*
|
||||
* @code
|
||||
* // List all cvars
|
||||
* auto all = rex::cvar::ListFlags();
|
||||
*
|
||||
* // Query by category or lifecycle
|
||||
* auto gpu_flags = rex::cvar::ListFlagsByCategory("GPU");
|
||||
* auto init_only = rex::cvar::ListFlagsByLifecycle(rex::cvar::Lifecycle::kInitOnly);
|
||||
*
|
||||
* // Get metadata for a specific cvar
|
||||
* const auto* info = rex::cvar::GetFlagInfo("my_flag");
|
||||
* if (info) {
|
||||
* // Access info->lifecycle, info->constraints, info->description, etc.
|
||||
* }
|
||||
*
|
||||
* // Check for modified values
|
||||
* auto modified = rex::cvar::ListModifiedFlags();
|
||||
* @endcode
|
||||
*
|
||||
* @copyright Copyright (c) 2026 Tom Clay
|
||||
* @license BSD 3-Clause License
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <charconv>
|
||||
#include <cstdint>
|
||||
#include <filesystem>
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace rex::cvar {
|
||||
|
||||
//=============================================================================
|
||||
// Initialization API
|
||||
//=============================================================================
|
||||
|
||||
std::vector<std::string> Init(int argc, char** argv);
|
||||
void LoadConfig(const std::filesystem::path& config_path);
|
||||
void ApplyEnvironment();
|
||||
void FinalizeInit();
|
||||
bool IsFinalized();
|
||||
void SaveConfig(const std::filesystem::path& config_path);
|
||||
|
||||
//=============================================================================
|
||||
// Flag Registry
|
||||
//=============================================================================
|
||||
|
||||
enum class FlagType { Boolean, Int32, Int64, Uint32, Uint64, Double, String, Command };
|
||||
|
||||
// Lifecycle: when can this flag be modified?
|
||||
enum class Lifecycle {
|
||||
kInitOnly, // Can only be set during initialization (before FinalizeInit)
|
||||
kHotReload, // Can be changed at runtime with immediate effect
|
||||
kRequiresRestart // Can be changed, but only takes effect after restart
|
||||
};
|
||||
|
||||
// Validation constraints
|
||||
struct Constraints {
|
||||
std::optional<double> min;
|
||||
std::optional<double> max;
|
||||
std::vector<std::string> allowed_values;
|
||||
std::function<bool(std::string_view)> custom_validator;
|
||||
|
||||
bool HasRangeConstraint() const { return min.has_value() || max.has_value(); }
|
||||
bool HasAllowedValues() const { return !allowed_values.empty(); }
|
||||
};
|
||||
|
||||
struct FlagEntry {
|
||||
std::string name;
|
||||
FlagType type;
|
||||
std::string category;
|
||||
std::string description;
|
||||
std::function<bool(std::string_view)> setter;
|
||||
std::function<std::string()> getter;
|
||||
std::function<void()> command_callback;
|
||||
Lifecycle lifecycle = Lifecycle::kHotReload;
|
||||
Constraints constraints;
|
||||
std::string default_value;
|
||||
bool is_debug_only = false;
|
||||
};
|
||||
|
||||
std::vector<FlagEntry>& GetRegistry();
|
||||
|
||||
/**
|
||||
* Returns the registered entry's index, or nullopt if the name was already
|
||||
* registered (logged at ERROR).
|
||||
*/
|
||||
std::optional<size_t> RegisterFlag(FlagEntry entry);
|
||||
|
||||
/**
|
||||
* Removes a flag from the registry. Used by `FlagRegistrar`'s destructor so
|
||||
* that DLL unload tears down the lambdas captured in each FlagEntry.
|
||||
*/
|
||||
void UnregisterFlag(std::string_view name);
|
||||
|
||||
bool SetFlagByName(std::string_view name, std::string_view value);
|
||||
std::string GetFlagByName(std::string_view name);
|
||||
|
||||
// Typed registry query. Cross-DLL access path that does not require linking
|
||||
// the DLL where the cvar is defined. Slower than REXCVAR_GET (string parse +
|
||||
// hash lookup), so prefer REXCVAR_GET when the defining DLL is already on the
|
||||
// link line. Returns a value-initialized T when the cvar is missing or its
|
||||
// stored string fails to parse.
|
||||
template <typename T>
|
||||
T Query(std::string_view name);
|
||||
|
||||
template <>
|
||||
bool Query<bool>(std::string_view name);
|
||||
template <>
|
||||
int32_t Query<int32_t>(std::string_view name);
|
||||
template <>
|
||||
int64_t Query<int64_t>(std::string_view name);
|
||||
template <>
|
||||
uint32_t Query<uint32_t>(std::string_view name);
|
||||
template <>
|
||||
uint64_t Query<uint64_t>(std::string_view name);
|
||||
template <>
|
||||
double Query<double>(std::string_view name);
|
||||
template <>
|
||||
std::string Query<std::string>(std::string_view name);
|
||||
|
||||
std::vector<std::string> ListFlags();
|
||||
std::vector<std::string> ListFlagsByCategory(std::string_view category);
|
||||
std::vector<std::string> ListFlagsByLifecycle(Lifecycle lc);
|
||||
const FlagEntry* GetFlagInfo(std::string_view name);
|
||||
std::vector<std::string> GetPendingRestartFlags();
|
||||
void ClearPendingRestartFlags();
|
||||
void ResetToDefault(std::string_view name);
|
||||
void ResetAllToDefaults();
|
||||
bool HasNonDefaultValue(std::string_view name);
|
||||
std::vector<std::string> ListModifiedFlags();
|
||||
std::string SerializeToTOML();
|
||||
std::string SerializeToTOML(std::string_view category);
|
||||
|
||||
/// Callback invoked when a CVAR value changes
|
||||
/// @param name The CVAR name
|
||||
/// @param new_value The new value as a string
|
||||
using ChangeCallback = std::function<void(std::string_view name, std::string_view new_value)>;
|
||||
|
||||
/// Register a callback to be invoked when a specific CVAR changes
|
||||
void RegisterChangeCallback(std::string_view name, ChangeCallback callback);
|
||||
|
||||
/// Unregister all callbacks for a specific CVAR
|
||||
void UnregisterChangeCallbacks(std::string_view name);
|
||||
|
||||
/**
|
||||
* RAII handle for a registered flag. Destructor unregisters by name; on
|
||||
* duplicate-name registration `owned_name_` is empty so chain methods and
|
||||
* the destructor become no-ops and the original owner's entry is untouched.
|
||||
*/
|
||||
struct FlagRegistrar {
|
||||
std::string owned_name_; // empty when registration was rejected
|
||||
|
||||
explicit FlagRegistrar(FlagEntry e) {
|
||||
std::string name = e.name;
|
||||
if (RegisterFlag(std::move(e)).has_value()) {
|
||||
owned_name_ = std::move(name);
|
||||
}
|
||||
}
|
||||
|
||||
FlagRegistrar(FlagRegistrar&& other) noexcept : owned_name_(std::move(other.owned_name_)) {
|
||||
other.owned_name_.clear();
|
||||
}
|
||||
|
||||
~FlagRegistrar() {
|
||||
if (!owned_name_.empty()) {
|
||||
UnregisterFlag(owned_name_);
|
||||
}
|
||||
}
|
||||
|
||||
// Chain methods mutate the registered entry by name lookup.
|
||||
FlagRegistrar&& range(double min_val, double max_val) && {
|
||||
apply_([=](FlagEntry& entry) {
|
||||
entry.constraints.min = min_val;
|
||||
entry.constraints.max = max_val;
|
||||
});
|
||||
return std::move(*this);
|
||||
}
|
||||
|
||||
FlagRegistrar&& allowed(std::initializer_list<std::string> values) && {
|
||||
std::vector<std::string> vals(values);
|
||||
apply_([vals = std::move(vals)](FlagEntry& entry) { entry.constraints.allowed_values = vals; });
|
||||
return std::move(*this);
|
||||
}
|
||||
|
||||
FlagRegistrar&& lifecycle(Lifecycle lc) && {
|
||||
apply_([=](FlagEntry& entry) { entry.lifecycle = lc; });
|
||||
return std::move(*this);
|
||||
}
|
||||
|
||||
FlagRegistrar&& debug_only() && {
|
||||
apply_([](FlagEntry& entry) { entry.is_debug_only = true; });
|
||||
return std::move(*this);
|
||||
}
|
||||
|
||||
FlagRegistrar&& validator(std::function<bool(std::string_view)> fn) && {
|
||||
apply_([fn = std::move(fn)](FlagEntry& entry) mutable {
|
||||
entry.constraints.custom_validator = std::move(fn);
|
||||
});
|
||||
return std::move(*this);
|
||||
}
|
||||
|
||||
// Non-copyable (prevent double registration)
|
||||
FlagRegistrar(const FlagRegistrar&) = delete;
|
||||
FlagRegistrar& operator=(const FlagRegistrar&) = delete;
|
||||
FlagRegistrar& operator=(FlagRegistrar&&) = delete;
|
||||
|
||||
private:
|
||||
void apply_(std::function<void(FlagEntry&)> fn);
|
||||
};
|
||||
|
||||
inline bool ParseDouble(std::string_view s, double& out) {
|
||||
if (s.empty())
|
||||
return false;
|
||||
char* end = nullptr;
|
||||
std::string str(s);
|
||||
out = std::strtod(str.c_str(), &end);
|
||||
return end != str.c_str() && *end == '\0';
|
||||
}
|
||||
|
||||
} // namespace rex::cvar
|
||||
|
||||
//=============================================================================
|
||||
// CVar Macros
|
||||
//=============================================================================
|
||||
|
||||
// Declare a cvar (use in headers and TUs that need to read it).
|
||||
// The accessor function returns a reference to the cvar's storage. Storage
|
||||
// lives as a static-local inside whichever DLL contains the matching
|
||||
// REXCVAR_DEFINE_*. Cross-DLL access goes through the import lib.
|
||||
#define REXCVAR_DECLARE(type, name) type& FLAGS_##name##_storage_()
|
||||
|
||||
// Get a cvar value
|
||||
#define REXCVAR_GET(name) (FLAGS_##name##_storage_())
|
||||
|
||||
// Set a cvar value
|
||||
#define REXCVAR_SET(name, value) (FLAGS_##name##_storage_() = (value))
|
||||
|
||||
// Cross-module typed query that goes through the cvar registry by name.
|
||||
// Use this when the defining DLL is not on the consumer's link line (e.g.,
|
||||
// across one-way subsystem dependencies where adding the reverse link would
|
||||
// create a cycle). Slower than REXCVAR_GET; prefer REXCVAR_GET when possible.
|
||||
#define REXCVAR_QUERY(type, name) (::rex::cvar::Query<type>(#name))
|
||||
|
||||
// Define cvars (use in one .cpp file per cvar)
|
||||
// The FlagRegistrar registers the flag in its destructor, allowing method chaining.
|
||||
#define REXCVAR_DEFINE_BOOL(name, default_val, category, desc) \
|
||||
bool& FLAGS_##name##_storage_() { \
|
||||
static bool storage = (default_val); \
|
||||
return storage; \
|
||||
} \
|
||||
static auto _cvar_reg_##name = \
|
||||
::rex::cvar::FlagRegistrar({#name, \
|
||||
::rex::cvar::FlagType::Boolean, \
|
||||
category, \
|
||||
desc, \
|
||||
[](std::string_view v) { \
|
||||
bool val = (v == "true" || v == "1" || v == "yes"); \
|
||||
FLAGS_##name##_storage_() = val; \
|
||||
return true; \
|
||||
}, \
|
||||
[]() { return FLAGS_##name##_storage_() ? "true" : "false"; }, \
|
||||
[]() { return; }, \
|
||||
::rex::cvar::Lifecycle::kHotReload, \
|
||||
{}, \
|
||||
(default_val) ? "true" : "false", \
|
||||
false})
|
||||
|
||||
#define REXCVAR_DEFINE_INT32(name, default_val, category, desc) \
|
||||
int32_t& FLAGS_##name##_storage_() { \
|
||||
static int32_t storage = (default_val); \
|
||||
return storage; \
|
||||
} \
|
||||
static auto _cvar_reg_##name = \
|
||||
::rex::cvar::FlagRegistrar({#name, \
|
||||
::rex::cvar::FlagType::Int32, \
|
||||
category, \
|
||||
desc, \
|
||||
[](std::string_view v) { \
|
||||
int32_t val = 0; \
|
||||
auto [ptr, ec] = \
|
||||
std::from_chars(v.data(), v.data() + v.size(), val); \
|
||||
if (ec != std::errc()) \
|
||||
return false; \
|
||||
FLAGS_##name##_storage_() = val; \
|
||||
return true; \
|
||||
}, \
|
||||
[]() { return std::to_string(FLAGS_##name##_storage_()); }, \
|
||||
[]() { return; }, \
|
||||
::rex::cvar::Lifecycle::kHotReload, \
|
||||
{}, \
|
||||
std::to_string(default_val), \
|
||||
false})
|
||||
|
||||
#define REXCVAR_DEFINE_INT64(name, default_val, category, desc) \
|
||||
int64_t& FLAGS_##name##_storage_() { \
|
||||
static int64_t storage = (default_val); \
|
||||
return storage; \
|
||||
} \
|
||||
static auto _cvar_reg_##name = \
|
||||
::rex::cvar::FlagRegistrar({#name, \
|
||||
::rex::cvar::FlagType::Int64, \
|
||||
category, \
|
||||
desc, \
|
||||
[](std::string_view v) { \
|
||||
int64_t val = 0; \
|
||||
auto [ptr, ec] = \
|
||||
std::from_chars(v.data(), v.data() + v.size(), val); \
|
||||
if (ec != std::errc()) \
|
||||
return false; \
|
||||
FLAGS_##name##_storage_() = val; \
|
||||
return true; \
|
||||
}, \
|
||||
[]() { return std::to_string(FLAGS_##name##_storage_()); }, \
|
||||
[]() { return; }, \
|
||||
::rex::cvar::Lifecycle::kHotReload, \
|
||||
{}, \
|
||||
std::to_string(default_val), \
|
||||
false})
|
||||
|
||||
#define REXCVAR_DEFINE_UINT32(name, default_val, category, desc) \
|
||||
uint32_t& FLAGS_##name##_storage_() { \
|
||||
static uint32_t storage = (default_val); \
|
||||
return storage; \
|
||||
} \
|
||||
static auto _cvar_reg_##name = \
|
||||
::rex::cvar::FlagRegistrar({#name, \
|
||||
::rex::cvar::FlagType::Uint32, \
|
||||
category, \
|
||||
desc, \
|
||||
[](std::string_view v) { \
|
||||
uint32_t val = 0; \
|
||||
auto [ptr, ec] = \
|
||||
std::from_chars(v.data(), v.data() + v.size(), val); \
|
||||
if (ec != std::errc()) \
|
||||
return false; \
|
||||
FLAGS_##name##_storage_() = val; \
|
||||
return true; \
|
||||
}, \
|
||||
[]() { return std::to_string(FLAGS_##name##_storage_()); }, \
|
||||
[]() { return; }, \
|
||||
::rex::cvar::Lifecycle::kHotReload, \
|
||||
{}, \
|
||||
std::to_string(default_val), \
|
||||
false})
|
||||
|
||||
#define REXCVAR_DEFINE_UINT64(name, default_val, category, desc) \
|
||||
uint64_t& FLAGS_##name##_storage_() { \
|
||||
static uint64_t storage = (default_val); \
|
||||
return storage; \
|
||||
} \
|
||||
static auto _cvar_reg_##name = \
|
||||
::rex::cvar::FlagRegistrar({#name, \
|
||||
::rex::cvar::FlagType::Uint64, \
|
||||
category, \
|
||||
desc, \
|
||||
[](std::string_view v) { \
|
||||
uint64_t val = 0; \
|
||||
auto [ptr, ec] = \
|
||||
std::from_chars(v.data(), v.data() + v.size(), val); \
|
||||
if (ec != std::errc()) \
|
||||
return false; \
|
||||
FLAGS_##name##_storage_() = val; \
|
||||
return true; \
|
||||
}, \
|
||||
[]() { return std::to_string(FLAGS_##name##_storage_()); }, \
|
||||
[]() { return; }, \
|
||||
::rex::cvar::Lifecycle::kHotReload, \
|
||||
{}, \
|
||||
std::to_string(default_val), \
|
||||
false})
|
||||
|
||||
#define REXCVAR_DEFINE_DOUBLE(name, default_val, category, desc) \
|
||||
double& FLAGS_##name##_storage_() { \
|
||||
static double storage = (default_val); \
|
||||
return storage; \
|
||||
} \
|
||||
static auto _cvar_reg_##name = \
|
||||
::rex::cvar::FlagRegistrar({#name, \
|
||||
::rex::cvar::FlagType::Double, \
|
||||
category, \
|
||||
desc, \
|
||||
[](std::string_view v) { \
|
||||
double val = 0; \
|
||||
if (!::rex::cvar::ParseDouble(v, val)) \
|
||||
return false; \
|
||||
FLAGS_##name##_storage_() = val; \
|
||||
return true; \
|
||||
}, \
|
||||
[]() { return std::to_string(FLAGS_##name##_storage_()); }, \
|
||||
[]() { return; }, \
|
||||
::rex::cvar::Lifecycle::kHotReload, \
|
||||
{}, \
|
||||
std::to_string(default_val), \
|
||||
false})
|
||||
|
||||
#define REXCVAR_DEFINE_STRING(name, default_val, category, desc) \
|
||||
std::string& FLAGS_##name##_storage_() { \
|
||||
static std::string storage = (default_val); \
|
||||
return storage; \
|
||||
} \
|
||||
static auto _cvar_reg_##name = \
|
||||
::rex::cvar::FlagRegistrar({#name, \
|
||||
::rex::cvar::FlagType::String, \
|
||||
category, \
|
||||
desc, \
|
||||
[](std::string_view v) { \
|
||||
FLAGS_##name##_storage_() = std::string(v); \
|
||||
return true; \
|
||||
}, \
|
||||
[]() { return FLAGS_##name##_storage_(); }, \
|
||||
[]() { return; }, \
|
||||
::rex::cvar::Lifecycle::kHotReload, \
|
||||
{}, \
|
||||
default_val, \
|
||||
false})
|
||||
|
||||
#define REXCVAR_DEFINE_COMMAND(name, callback, category, desc) \
|
||||
std::function<void()>& FLAGS_##name##_storage_() { \
|
||||
static std::function<void()> storage = (callback); \
|
||||
return storage; \
|
||||
} \
|
||||
static auto _cvar_reg_##name = \
|
||||
::rex::cvar::FlagRegistrar({#name, \
|
||||
::rex::cvar::FlagType::Command, \
|
||||
category, \
|
||||
desc, \
|
||||
[](std::string_view) { return false; }, \
|
||||
[]() { return "<command>"; }, \
|
||||
callback, \
|
||||
::rex::cvar::Lifecycle::kHotReload, \
|
||||
{}, \
|
||||
"<command>", \
|
||||
false})
|
||||
|
||||
namespace rex::cvar {
|
||||
namespace testing {
|
||||
|
||||
class ScopedLifecycleOverride {
|
||||
public:
|
||||
ScopedLifecycleOverride();
|
||||
~ScopedLifecycleOverride();
|
||||
|
||||
ScopedLifecycleOverride(const ScopedLifecycleOverride&) = delete;
|
||||
ScopedLifecycleOverride& operator=(const ScopedLifecycleOverride&) = delete;
|
||||
};
|
||||
|
||||
void ResetAllForTesting();
|
||||
|
||||
} // namespace testing
|
||||
} // namespace rex::cvar
|
||||
97
include/rex/dbg.h
Normal file
97
include/rex/dbg.h
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2014 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*
|
||||
* @modified Tom Clay, 2026 - Adapted for ReXGlue runtime
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include <cstring>
|
||||
|
||||
namespace rex::debug {
|
||||
|
||||
// Returns true if a debugger is attached to this process.
|
||||
// The state may change at any time (attach after launch, etc), so do not
|
||||
// cache this value. Determining if the debugger is attached is expensive,
|
||||
// though, so avoid calling it frequently.
|
||||
bool IsDebuggerAttached();
|
||||
|
||||
// Breaks into the debugger if it is attached.
|
||||
// If no debugger is present, a signal will be raised.
|
||||
void Break();
|
||||
|
||||
namespace detail {
|
||||
void DebugPrint(const char* s);
|
||||
}
|
||||
|
||||
// Prints a message to the attached debugger.
|
||||
// This bypasses the normal logging mechanism. If no debugger is attached it's
|
||||
// likely to no-op.
|
||||
template <typename... Args>
|
||||
void DebugPrint(fmt::string_view format, const Args&... args) {
|
||||
detail::DebugPrint(fmt::vformat(format, fmt::make_format_args(args...)).c_str());
|
||||
}
|
||||
|
||||
} // namespace rex::debug
|
||||
|
||||
#ifdef REXGLUE_ENABLE_PROFILING
|
||||
|
||||
#include <tracy/Tracy.hpp>
|
||||
|
||||
// CPU profiling zones
|
||||
#define SCOPE_profile_cpu_f(name) ZoneNamedN(___tracy_cpu_zone, name, true)
|
||||
#define SCOPE_profile_cpu_i(name, detail) \
|
||||
ZoneNamedN(___tracy_cpu_zone_i, name, true); \
|
||||
ZoneTextV(___tracy_cpu_zone_i, detail, std::strlen(detail))
|
||||
|
||||
// GPU profiling stubs -- backend code uses TracyVkZone/TracyD3D12Zone directly.
|
||||
#define SCOPE_profile_gpu_f(name)
|
||||
#define SCOPE_profile_gpu_i(name, detail)
|
||||
|
||||
// Thread profiling
|
||||
#define PROFILE_THREAD_ENTER(name) tracy::SetThreadName(name)
|
||||
#define PROFILE_THREAD_EXIT()
|
||||
|
||||
// Fiber profiling
|
||||
#ifdef TRACY_FIBERS
|
||||
#define PROFILE_FIBER_ENTER(name) TracyFiberEnter(name)
|
||||
#define PROFILE_FIBER_LEAVE TracyFiberLeave
|
||||
#else
|
||||
#define PROFILE_FIBER_ENTER(name)
|
||||
#define PROFILE_FIBER_LEAVE
|
||||
#endif
|
||||
|
||||
// Counter profiling -- plot to Tracy
|
||||
#define COUNT_profile_set(name, value) TracyPlot(name, static_cast<int64_t>(value))
|
||||
|
||||
#else // !REXGLUE_ENABLE_PROFILING
|
||||
|
||||
// CPU profiling stubs
|
||||
#define SCOPE_profile_cpu_f(name)
|
||||
#define SCOPE_profile_cpu_i(name, detail)
|
||||
|
||||
// GPU profiling stubs
|
||||
#define SCOPE_profile_gpu_f(name)
|
||||
#define SCOPE_profile_gpu_i(name, detail)
|
||||
|
||||
// Thread profiling stubs
|
||||
#define PROFILE_THREAD_ENTER(name)
|
||||
#define PROFILE_THREAD_EXIT()
|
||||
|
||||
// Fiber profiling stubs
|
||||
#define PROFILE_FIBER_ENTER(name)
|
||||
#define PROFILE_FIBER_LEAVE
|
||||
|
||||
// Counter profiling stubs
|
||||
#define COUNT_profile_set(name, value)
|
||||
|
||||
#endif // REXGLUE_ENABLE_PROFILING
|
||||
534
include/rex/exception_handler.h
Normal file
534
include/rex/exception_handler.h
Normal file
|
|
@ -0,0 +1,534 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2022 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*
|
||||
* @modified Tom Clay, 2026 - Adapted for ReXGlue runtime
|
||||
*
|
||||
* @file exception_handler.h
|
||||
* @brief Unified exception handling, thread context, and register defs
|
||||
*
|
||||
* @remarks Flattened from arch/{arm64,amd64}/host_thread_context.h,
|
||||
* arch/host_thread_context.h, arch/{arm64,amd64}/exception_handler.h,
|
||||
* and arch/exception_handler.h.
|
||||
* Contains VIXL-derived ARM64 load/store decoding constants.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <rex/assert.h>
|
||||
#include <rex/platform.h>
|
||||
#include <rex/vec128.h>
|
||||
|
||||
#if REX_ARCH_AMD64
|
||||
#include <xmmintrin.h>
|
||||
#endif
|
||||
|
||||
namespace rex::arch {
|
||||
|
||||
//=============================================================================
|
||||
// ARM64 Register Definitions
|
||||
//=============================================================================
|
||||
|
||||
// NOTE: The order of the registers in the enumerations must match the order in
|
||||
// the string table in host_thread_context.cc, as well as remapping tables in
|
||||
// exception handler implementations.
|
||||
|
||||
enum class Arm64Register {
|
||||
kX0,
|
||||
kX1,
|
||||
kX2,
|
||||
kX3,
|
||||
kX4,
|
||||
kX5,
|
||||
kX6,
|
||||
kX7,
|
||||
kX8,
|
||||
kX9,
|
||||
kX10,
|
||||
kX11,
|
||||
kX12,
|
||||
kX13,
|
||||
kX14,
|
||||
kX15,
|
||||
kX16,
|
||||
kX17,
|
||||
kX18,
|
||||
kX19,
|
||||
kX20,
|
||||
kX21,
|
||||
kX22,
|
||||
kX23,
|
||||
kX24,
|
||||
kX25,
|
||||
kX26,
|
||||
kX27,
|
||||
kX28,
|
||||
// FP (frame pointer).
|
||||
kX29,
|
||||
// LR (link register).
|
||||
kX30,
|
||||
kSp,
|
||||
kPc,
|
||||
kPstate,
|
||||
kFpsr,
|
||||
kFpcr,
|
||||
// The whole 128 bits of a Vn register are also known as Qn (quadword).
|
||||
kV0,
|
||||
kV1,
|
||||
kV2,
|
||||
kV3,
|
||||
kV4,
|
||||
kV5,
|
||||
kV6,
|
||||
kV7,
|
||||
kV8,
|
||||
kV9,
|
||||
kV10,
|
||||
kV11,
|
||||
kV12,
|
||||
kV13,
|
||||
kV14,
|
||||
kV15,
|
||||
kV16,
|
||||
kV17,
|
||||
kV18,
|
||||
kV19,
|
||||
kV20,
|
||||
kV21,
|
||||
kV22,
|
||||
kV23,
|
||||
kV24,
|
||||
kV25,
|
||||
kV26,
|
||||
kV27,
|
||||
kV28,
|
||||
kV29,
|
||||
kV30,
|
||||
kV31,
|
||||
};
|
||||
|
||||
// ARM64 thread context structure members
|
||||
// Used within HostThreadContext class via #if REX_ARCH_ARM64
|
||||
struct Arm64ThreadContextMembers {
|
||||
uint64_t x[31];
|
||||
uint64_t sp;
|
||||
uint64_t pc;
|
||||
uint64_t pstate;
|
||||
uint32_t fpsr;
|
||||
uint32_t fpcr;
|
||||
vec128_t v[32];
|
||||
};
|
||||
|
||||
//=============================================================================
|
||||
// AMD64 Register Definitions
|
||||
//=============================================================================
|
||||
|
||||
#if REX_ARCH_AMD64
|
||||
|
||||
// NOTE: The order of the registers in the enumerations must match the order in
|
||||
// the string table in host_thread_context.cc, as well as remapping tables in
|
||||
// exception handler implementations.
|
||||
|
||||
enum class X64Register {
|
||||
kRip,
|
||||
kEflags,
|
||||
|
||||
kIntRegisterFirst,
|
||||
// The order matches the indices in the instruction encoding, as well as the
|
||||
// Windows CONTEXT structure.
|
||||
kRax = kIntRegisterFirst,
|
||||
kRcx,
|
||||
kRdx,
|
||||
kRbx,
|
||||
kRsp,
|
||||
kRbp,
|
||||
kRsi,
|
||||
kRdi,
|
||||
kR8,
|
||||
kR9,
|
||||
kR10,
|
||||
kR11,
|
||||
kR12,
|
||||
kR13,
|
||||
kR14,
|
||||
kR15,
|
||||
kIntRegisterLast = kR15,
|
||||
|
||||
kXmm0,
|
||||
kXmm1,
|
||||
kXmm2,
|
||||
kXmm3,
|
||||
kXmm4,
|
||||
kXmm5,
|
||||
kXmm6,
|
||||
kXmm7,
|
||||
kXmm8,
|
||||
kXmm9,
|
||||
kXmm10,
|
||||
kXmm11,
|
||||
kXmm12,
|
||||
kXmm13,
|
||||
kXmm14,
|
||||
kXmm15,
|
||||
};
|
||||
|
||||
// x86-64 thread context structure members
|
||||
// Used within HostThreadContext class via #if REX_ARCH_AMD64
|
||||
struct X64ThreadContextMembers {
|
||||
uint64_t rip;
|
||||
uint32_t eflags;
|
||||
union {
|
||||
struct {
|
||||
uint64_t rax;
|
||||
uint64_t rcx;
|
||||
uint64_t rdx;
|
||||
uint64_t rbx;
|
||||
uint64_t rsp;
|
||||
uint64_t rbp;
|
||||
uint64_t rsi;
|
||||
uint64_t rdi;
|
||||
uint64_t r8;
|
||||
uint64_t r9;
|
||||
uint64_t r10;
|
||||
uint64_t r11;
|
||||
uint64_t r12;
|
||||
uint64_t r13;
|
||||
uint64_t r14;
|
||||
uint64_t r15;
|
||||
};
|
||||
uint64_t int_registers[16];
|
||||
};
|
||||
union {
|
||||
struct {
|
||||
vec128_t xmm0;
|
||||
vec128_t xmm1;
|
||||
vec128_t xmm2;
|
||||
vec128_t xmm3;
|
||||
vec128_t xmm4;
|
||||
vec128_t xmm5;
|
||||
vec128_t xmm6;
|
||||
vec128_t xmm7;
|
||||
vec128_t xmm8;
|
||||
vec128_t xmm9;
|
||||
vec128_t xmm10;
|
||||
vec128_t xmm11;
|
||||
vec128_t xmm12;
|
||||
vec128_t xmm13;
|
||||
vec128_t xmm14;
|
||||
vec128_t xmm15;
|
||||
};
|
||||
vec128_t xmm_registers[16];
|
||||
};
|
||||
};
|
||||
|
||||
#endif // REX_ARCH_AMD64
|
||||
|
||||
//=============================================================================
|
||||
// Host Register Typedef
|
||||
//=============================================================================
|
||||
|
||||
#if REX_ARCH_AMD64
|
||||
using HostRegister = X64Register;
|
||||
#elif REX_ARCH_ARM64
|
||||
using HostRegister = Arm64Register;
|
||||
#else
|
||||
enum class HostRegister {};
|
||||
#endif // REX_ARCH
|
||||
|
||||
//=============================================================================
|
||||
// Host Thread Context
|
||||
//=============================================================================
|
||||
|
||||
class HostThreadContext {
|
||||
public:
|
||||
#if REX_ARCH_AMD64
|
||||
uint64_t rip;
|
||||
uint32_t eflags;
|
||||
union {
|
||||
struct {
|
||||
uint64_t rax;
|
||||
uint64_t rcx;
|
||||
uint64_t rdx;
|
||||
uint64_t rbx;
|
||||
uint64_t rsp;
|
||||
uint64_t rbp;
|
||||
uint64_t rsi;
|
||||
uint64_t rdi;
|
||||
uint64_t r8;
|
||||
uint64_t r9;
|
||||
uint64_t r10;
|
||||
uint64_t r11;
|
||||
uint64_t r12;
|
||||
uint64_t r13;
|
||||
uint64_t r14;
|
||||
uint64_t r15;
|
||||
};
|
||||
uint64_t int_registers[16];
|
||||
};
|
||||
union {
|
||||
struct {
|
||||
vec128_t xmm0;
|
||||
vec128_t xmm1;
|
||||
vec128_t xmm2;
|
||||
vec128_t xmm3;
|
||||
vec128_t xmm4;
|
||||
vec128_t xmm5;
|
||||
vec128_t xmm6;
|
||||
vec128_t xmm7;
|
||||
vec128_t xmm8;
|
||||
vec128_t xmm9;
|
||||
vec128_t xmm10;
|
||||
vec128_t xmm11;
|
||||
vec128_t xmm12;
|
||||
vec128_t xmm13;
|
||||
vec128_t xmm14;
|
||||
vec128_t xmm15;
|
||||
};
|
||||
vec128_t xmm_registers[16];
|
||||
};
|
||||
#elif REX_ARCH_ARM64
|
||||
uint64_t x[31];
|
||||
uint64_t sp;
|
||||
uint64_t pc;
|
||||
uint64_t pstate;
|
||||
uint32_t fpsr;
|
||||
uint32_t fpcr;
|
||||
vec128_t v[32];
|
||||
#endif // REX_ARCH
|
||||
|
||||
static const char* GetRegisterName(HostRegister reg);
|
||||
std::string GetStringFromValue(HostRegister reg, bool hex) const;
|
||||
};
|
||||
|
||||
//=============================================================================
|
||||
// ARM64 Load/Store Decoding (VIXL-derived)
|
||||
//=============================================================================
|
||||
|
||||
// AArch64 load and store decoding based on VIXL.
|
||||
// https://github.com/Linaro/vixl/blob/ae5957cd66517b3f31dbf37e9bf39db6594abfe3/src/aarch64/constants-aarch64.h
|
||||
//
|
||||
// Copyright 2015, VIXL authors
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright notice,
|
||||
// this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above copyright notice,
|
||||
// this list of conditions and the following disclaimer in the documentation
|
||||
// and/or other materials provided with the distribution.
|
||||
// * Neither the name of ARM Limited nor the names of its contributors may be
|
||||
// used to endorse or promote products derived from this software without
|
||||
// specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS CONTRIBUTORS "AS IS" AND
|
||||
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
||||
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
// `Instruction address + literal offset` loads.
|
||||
// This includes PRFM_lit.
|
||||
constexpr uint32_t kArm64LoadLiteralFMask = UINT32_C(0x3B000000);
|
||||
constexpr uint32_t kArm64LoadLiteralFixed = UINT32_C(0x18000000);
|
||||
|
||||
constexpr uint32_t kArm64LoadStoreAnyFMask = UINT32_C(0x0A000000);
|
||||
constexpr uint32_t kArm64LoadStoreAnyFixed = UINT32_C(0x08000000);
|
||||
|
||||
constexpr uint32_t kArm64LoadStorePairAnyFMask = UINT32_C(0x3A000000);
|
||||
constexpr uint32_t kArm64LoadStorePairAnyFixed = UINT32_C(0x28000000);
|
||||
constexpr uint32_t kArm64LoadStorePairLoadBit = UINT32_C(1) << 22;
|
||||
|
||||
constexpr uint32_t kArm64LoadStoreMask = UINT32_C(0xC4C00000);
|
||||
enum class Arm64LoadStoreOp : uint32_t {
|
||||
kSTRB_w = UINT32_C(0x00000000),
|
||||
kSTRH_w = UINT32_C(0x40000000),
|
||||
kSTR_w = UINT32_C(0x80000000),
|
||||
kSTR_x = UINT32_C(0xC0000000),
|
||||
kLDRB_w = UINT32_C(0x00400000),
|
||||
kLDRH_w = UINT32_C(0x40400000),
|
||||
kLDR_w = UINT32_C(0x80400000),
|
||||
kLDR_x = UINT32_C(0xC0400000),
|
||||
kLDRSB_x = UINT32_C(0x00800000),
|
||||
kLDRSH_x = UINT32_C(0x40800000),
|
||||
kLDRSW_x = UINT32_C(0x80800000),
|
||||
kLDRSB_w = UINT32_C(0x00C00000),
|
||||
kLDRSH_w = UINT32_C(0x40C00000),
|
||||
kSTR_b = UINT32_C(0x04000000),
|
||||
kSTR_h = UINT32_C(0x44000000),
|
||||
kSTR_s = UINT32_C(0x84000000),
|
||||
kSTR_d = UINT32_C(0xC4000000),
|
||||
kSTR_q = UINT32_C(0x04800000),
|
||||
kLDR_b = UINT32_C(0x04400000),
|
||||
kLDR_h = UINT32_C(0x44400000),
|
||||
kLDR_s = UINT32_C(0x84400000),
|
||||
kLDR_d = UINT32_C(0xC4400000),
|
||||
kLDR_q = UINT32_C(0x04C00000),
|
||||
kPRFM = UINT32_C(0xC0800000),
|
||||
};
|
||||
|
||||
constexpr uint32_t kArm64LoadStoreOffsetFMask = UINT32_C(0x3B200C00);
|
||||
enum class Arm64LoadStoreOffsetFixed : uint32_t {
|
||||
kUnscaledOffset = UINT32_C(0x38000000),
|
||||
kPostIndex = UINT32_C(0x38000400),
|
||||
kPreIndex = UINT32_C(0x38000C00),
|
||||
kRegisterOffset = UINT32_C(0x38200800),
|
||||
};
|
||||
|
||||
constexpr uint32_t kArm64LoadStoreUnsignedOffsetFMask = UINT32_C(0x3B000000);
|
||||
constexpr uint32_t kArm64LoadStoreUnsignedOffsetFixed = UINT32_C(0x39000000);
|
||||
|
||||
// Determines if an ARM64 instruction is a load, prefetch, or store operation.
|
||||
// Returns true if the instruction is one of these, with is_store_out set to
|
||||
// indicate whether it's a store operation.
|
||||
bool IsArm64LoadPrefetchStore(uint32_t instruction, bool& is_store_out);
|
||||
|
||||
//=============================================================================
|
||||
// Exception Class
|
||||
//=============================================================================
|
||||
|
||||
class Exception {
|
||||
public:
|
||||
enum class Code {
|
||||
kInvalidException = 0,
|
||||
kAccessViolation,
|
||||
kIllegalInstruction,
|
||||
};
|
||||
|
||||
enum class AccessViolationOperation {
|
||||
kUnknown,
|
||||
kRead,
|
||||
kWrite,
|
||||
};
|
||||
|
||||
void InitializeAccessViolation(HostThreadContext* thread_context, uint64_t fault_address,
|
||||
AccessViolationOperation operation) {
|
||||
code_ = Code::kAccessViolation;
|
||||
thread_context_ = thread_context;
|
||||
fault_address_ = fault_address;
|
||||
access_violation_operation_ = operation;
|
||||
}
|
||||
void InitializeIllegalInstruction(HostThreadContext* thread_context) {
|
||||
code_ = Code::kIllegalInstruction;
|
||||
thread_context_ = thread_context;
|
||||
}
|
||||
|
||||
Code code() const { return code_; }
|
||||
|
||||
// Returns the platform-specific thread context info.
|
||||
// Note that certain registers must be modified through Modify* proxy
|
||||
// functions rather than directly:
|
||||
// x86-64:
|
||||
// - General-purpose registers (r##, r8-r15).
|
||||
// - XMM registers.
|
||||
// AArch64:
|
||||
// - General-purpose registers (Xn), including FP and LR.
|
||||
// - SIMD and floating-point registers (Vn).
|
||||
HostThreadContext* thread_context() const { return thread_context_; }
|
||||
|
||||
// Returns the program counter where the exception occurred.
|
||||
uint64_t pc() const {
|
||||
#if REX_ARCH_AMD64
|
||||
return thread_context_->rip;
|
||||
#elif REX_ARCH_ARM64
|
||||
return thread_context_->pc;
|
||||
#else
|
||||
assert_always();
|
||||
return 0;
|
||||
#endif // REX_ARCH
|
||||
}
|
||||
|
||||
// Sets the program counter where execution will resume.
|
||||
void set_resume_pc(uint64_t pc) {
|
||||
#if REX_ARCH_AMD64
|
||||
thread_context_->rip = pc;
|
||||
#elif REX_ARCH_ARM64
|
||||
thread_context_->pc = pc;
|
||||
#else
|
||||
assert_always();
|
||||
#endif // REX_ARCH
|
||||
}
|
||||
|
||||
#if REX_ARCH_AMD64
|
||||
// The index is relative to X64Register::kIntRegisterFirst.
|
||||
uint64_t& ModifyIntRegister(uint32_t index) {
|
||||
assert_true(index <= 15);
|
||||
modified_int_registers_ |= UINT16_C(1) << index;
|
||||
return thread_context_->int_registers[index];
|
||||
}
|
||||
uint16_t modified_int_registers() const { return modified_int_registers_; }
|
||||
vec128_t& ModifyXmmRegister(uint32_t index) {
|
||||
assert_true(index <= 15);
|
||||
modified_xmm_registers_ |= UINT16_C(1) << index;
|
||||
return thread_context_->xmm_registers[index];
|
||||
}
|
||||
uint16_t modified_xmm_registers() const { return modified_xmm_registers_; }
|
||||
#elif REX_ARCH_ARM64
|
||||
uint64_t& ModifyXRegister(uint32_t index) {
|
||||
assert_true(index <= 30);
|
||||
modified_x_registers_ |= UINT32_C(1) << index;
|
||||
return thread_context_->x[index];
|
||||
}
|
||||
uint32_t modified_x_registers() const { return modified_x_registers_; }
|
||||
vec128_t& ModifyVRegister(uint32_t index) {
|
||||
assert_true(index <= 31);
|
||||
modified_v_registers_ |= UINT32_C(1) << index;
|
||||
return thread_context_->v[index];
|
||||
}
|
||||
uint32_t modified_v_registers() const { return modified_v_registers_; }
|
||||
#endif // REX_ARCH
|
||||
|
||||
// In case of AV, address that was read from/written to.
|
||||
uint64_t fault_address() const { return fault_address_; }
|
||||
|
||||
// In case of AV, what kind of operation caused it.
|
||||
AccessViolationOperation access_violation_operation() const {
|
||||
return access_violation_operation_;
|
||||
}
|
||||
|
||||
private:
|
||||
Code code_ = Code::kInvalidException;
|
||||
HostThreadContext* thread_context_ = nullptr;
|
||||
#if REX_ARCH_AMD64
|
||||
uint16_t modified_int_registers_ = 0;
|
||||
uint16_t modified_xmm_registers_ = 0;
|
||||
#elif REX_ARCH_ARM64
|
||||
uint32_t modified_x_registers_ = 0;
|
||||
uint32_t modified_v_registers_ = 0;
|
||||
#endif // REX_ARCH
|
||||
uint64_t fault_address_ = 0;
|
||||
AccessViolationOperation access_violation_operation_ = AccessViolationOperation::kUnknown;
|
||||
};
|
||||
|
||||
//=============================================================================
|
||||
// Exception Handler
|
||||
//=============================================================================
|
||||
|
||||
class ExceptionHandler {
|
||||
public:
|
||||
typedef bool (*Handler)(Exception* ex, void* data);
|
||||
|
||||
// Installs an exception handler.
|
||||
// Handlers are called in the order they are installed.
|
||||
static void Install(Handler fn, void* data);
|
||||
|
||||
// Uninstalls a previously-installed exception handler.
|
||||
static void Uninstall(Handler fn, void* data);
|
||||
};
|
||||
|
||||
} // namespace rex::arch
|
||||
136
include/rex/filesystem.h
Normal file
136
include/rex/filesystem.h
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2022 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*
|
||||
* @modified Tom Clay, 2026 - Adapted for ReXGlue runtime
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
#include <iterator>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
#include <rex/platform.h>
|
||||
#include <rex/string.h>
|
||||
|
||||
namespace rex {
|
||||
|
||||
std::string path_to_utf8(const std::filesystem::path& path);
|
||||
std::u16string path_to_utf16(const std::filesystem::path& path);
|
||||
std::filesystem::path to_path(const std::string_view source);
|
||||
std::filesystem::path to_path(const std::u16string_view source);
|
||||
|
||||
namespace filesystem {
|
||||
|
||||
// Get executable path.
|
||||
std::filesystem::path GetExecutablePath();
|
||||
|
||||
// Get executable folder.
|
||||
std::filesystem::path GetExecutableFolder();
|
||||
|
||||
// Get user folder.
|
||||
std::filesystem::path GetUserFolder();
|
||||
|
||||
// Creates the parent folder of the specified path if needed.
|
||||
// This can be used to ensure the destination path for a new file exists before
|
||||
// attempting to create it.
|
||||
bool CreateParentFolder(const std::filesystem::path& path);
|
||||
|
||||
// Creates an empty file at the given path, overwriting if it exists.
|
||||
bool CreateEmptyFile(const std::filesystem::path& path);
|
||||
|
||||
// Opens the file at the given path with the specified mode.
|
||||
// This behaves like fopen and the returned handle can be used with stdio.
|
||||
FILE* OpenFile(const std::filesystem::path& path, const std::string_view mode);
|
||||
|
||||
// Wrapper for the 64-bit version of fseek, returns true on success.
|
||||
bool Seek(FILE* file, int64_t offset, int origin);
|
||||
|
||||
// Wrapper for the 64-bit version of ftell, returns a positive value on success.
|
||||
int64_t Tell(FILE* file);
|
||||
|
||||
// Reduces the size of a stdio file opened for writing. The file pointer is
|
||||
// clamped. If this returns false, the size of the file and the file pointer are
|
||||
// undefined.
|
||||
bool TruncateStdioFile(FILE* file, uint64_t length);
|
||||
|
||||
struct FileAccess {
|
||||
// Implies kFileReadData.
|
||||
static const uint32_t kGenericRead = 0x80000000;
|
||||
// Implies kFileWriteData.
|
||||
static const uint32_t kGenericWrite = 0x40000000;
|
||||
static const uint32_t kGenericExecute = 0x20000000;
|
||||
static const uint32_t kGenericAll = 0x10000000;
|
||||
static const uint32_t kFileReadData = 0x00000001;
|
||||
static const uint32_t kFileWriteData = 0x00000002;
|
||||
static const uint32_t kFileAppendData = 0x00000004;
|
||||
};
|
||||
|
||||
class FileHandle {
|
||||
public:
|
||||
// Opens the file, failing if it doesn't exist.
|
||||
// The desired_access bitmask denotes the permissions on the file.
|
||||
static std::unique_ptr<FileHandle> OpenExisting(const std::filesystem::path& path,
|
||||
uint32_t desired_access);
|
||||
|
||||
virtual ~FileHandle() = default;
|
||||
|
||||
const std::filesystem::path& path() const { return path_; }
|
||||
|
||||
// Reads the requested number of bytes from the file starting at the given
|
||||
// offset. The total number of bytes read is returned only if the complete
|
||||
// read succeeds.
|
||||
virtual bool Read(size_t file_offset, void* buffer, size_t buffer_length,
|
||||
size_t* out_bytes_read) = 0;
|
||||
|
||||
// Writes the given buffer to the file starting at the given offset.
|
||||
// The total number of bytes written is returned only if the complete
|
||||
// write succeeds.
|
||||
virtual bool Write(size_t file_offset, const void* buffer, size_t buffer_length,
|
||||
size_t* out_bytes_written) = 0;
|
||||
|
||||
// Set length of the file in bytes.
|
||||
virtual bool SetLength(size_t length) = 0;
|
||||
|
||||
// Flushes any pending write buffers to the underlying filesystem.
|
||||
virtual void Flush() = 0;
|
||||
|
||||
protected:
|
||||
explicit FileHandle(const std::filesystem::path& path) : path_(path) {}
|
||||
|
||||
std::filesystem::path path_;
|
||||
};
|
||||
|
||||
struct FileInfo {
|
||||
enum class Type {
|
||||
kFile,
|
||||
kDirectory,
|
||||
};
|
||||
Type type;
|
||||
std::filesystem::path name;
|
||||
std::filesystem::path path;
|
||||
size_t total_size;
|
||||
uint64_t create_timestamp;
|
||||
uint64_t access_timestamp;
|
||||
uint64_t write_timestamp;
|
||||
};
|
||||
bool GetInfo(const std::filesystem::path& path, FileInfo* out_info);
|
||||
std::vector<FileInfo> ListFiles(const std::filesystem::path& path);
|
||||
|
||||
#if REX_PLATFORM_ANDROID
|
||||
void AndroidInitialize();
|
||||
void AndroidShutdown();
|
||||
bool IsAndroidContentUri(const std::string_view source);
|
||||
int OpenAndroidContentFileDescriptor(const std::string_view uri, const char* mode);
|
||||
#endif // REX_PLATFORM_ANDROID
|
||||
|
||||
} // namespace filesystem
|
||||
} // namespace rex
|
||||
50
include/rex/filesystem/device.h
Normal file
50
include/rex/filesystem/device.h
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2020 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*
|
||||
* @modified Tom Clay, 2026 - Adapted for ReXGlue runtime
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include <rex/filesystem/entry.h>
|
||||
#include <rex/string/buffer.h>
|
||||
#include <rex/thread/mutex.h>
|
||||
|
||||
namespace rex::filesystem {
|
||||
|
||||
class Device {
|
||||
public:
|
||||
explicit Device(const std::string_view mount_path);
|
||||
virtual ~Device();
|
||||
|
||||
virtual bool Initialize() = 0;
|
||||
|
||||
const std::string& mount_path() const { return mount_path_; }
|
||||
virtual bool is_read_only() const { return true; }
|
||||
|
||||
virtual void Dump(string::StringBuffer* string_buffer) = 0;
|
||||
virtual Entry* ResolvePath(const std::string_view path) = 0;
|
||||
|
||||
virtual const std::string& name() const = 0;
|
||||
virtual uint32_t attributes() const = 0;
|
||||
virtual uint32_t component_name_max_length() const = 0;
|
||||
|
||||
virtual uint32_t total_allocation_units() const = 0;
|
||||
virtual uint32_t available_allocation_units() const = 0;
|
||||
virtual uint32_t sectors_per_allocation_unit() const = 0;
|
||||
virtual uint32_t bytes_per_sector() const = 0;
|
||||
|
||||
protected:
|
||||
rex::thread::global_critical_region global_critical_region_;
|
||||
std::string mount_path_;
|
||||
};
|
||||
|
||||
} // namespace rex::filesystem
|
||||
89
include/rex/filesystem/devices/disc_image_device.h
Normal file
89
include/rex/filesystem/devices/disc_image_device.h
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2020 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*
|
||||
* @modified Tom Clay, 2026 - Adapted for ReXGlue runtime
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include <rex/filesystem/device.h>
|
||||
#include <rex/memory/mapped_memory.h>
|
||||
|
||||
namespace rex::filesystem {
|
||||
|
||||
class DiscImageEntry;
|
||||
|
||||
class DiscImageDevice : public Device {
|
||||
public:
|
||||
DiscImageDevice(const std::string_view mount_path, const std::filesystem::path& host_path);
|
||||
~DiscImageDevice() override;
|
||||
|
||||
bool Initialize() override;
|
||||
void Dump(string::StringBuffer* string_buffer) override;
|
||||
Entry* ResolvePath(const std::string_view path) override;
|
||||
|
||||
struct DiscInfo {
|
||||
size_t game_offset;
|
||||
size_t root_sector;
|
||||
size_t root_size;
|
||||
size_t host_size;
|
||||
};
|
||||
|
||||
const Entry* root() const { return root_entry_.get(); }
|
||||
uint64_t file_count() const { return file_count_; }
|
||||
uint64_t total_file_size() const { return total_file_size_; }
|
||||
const DiscInfo& disc_info() const { return disc_info_; }
|
||||
|
||||
const std::string& name() const override { return name_; }
|
||||
uint32_t attributes() const override { return 0; }
|
||||
uint32_t component_name_max_length() const override { return 255; }
|
||||
|
||||
uint32_t total_allocation_units() const override {
|
||||
return uint32_t(mmap_->size() / sectors_per_allocation_unit() / bytes_per_sector());
|
||||
}
|
||||
uint32_t available_allocation_units() const override { return 0; }
|
||||
uint32_t sectors_per_allocation_unit() const override { return 1; }
|
||||
uint32_t bytes_per_sector() const override { return 0x200; }
|
||||
|
||||
private:
|
||||
enum class Error {
|
||||
kSuccess = 0,
|
||||
kErrorOutOfMemory = -1,
|
||||
kErrorReadError = -10,
|
||||
kErrorFileMismatch = -30,
|
||||
kErrorDamagedFile = -31,
|
||||
};
|
||||
|
||||
std::string name_;
|
||||
std::filesystem::path host_path_;
|
||||
std::unique_ptr<Entry> root_entry_;
|
||||
std::unique_ptr<memory::MappedMemory> mmap_;
|
||||
DiscInfo disc_info_{};
|
||||
uint64_t file_count_ = 0;
|
||||
uint64_t total_file_size_ = 0;
|
||||
|
||||
typedef struct {
|
||||
uint8_t* ptr;
|
||||
size_t size; // Size (bytes) of total image.
|
||||
size_t game_offset; // Offset (bytes) of game partition.
|
||||
size_t root_sector; // Offset (sector) of root.
|
||||
size_t root_offset; // Offset (bytes) of root.
|
||||
size_t root_size; // Size (bytes) of root.
|
||||
} ParseState;
|
||||
|
||||
Error Verify(ParseState* state);
|
||||
bool VerifyMagic(ParseState* state, size_t offset);
|
||||
Error ReadAllEntries(ParseState* state, const uint8_t* root_buffer);
|
||||
bool ReadEntry(ParseState* state, const uint8_t* buffer, uint16_t entry_ordinal,
|
||||
DiscImageEntry* parent);
|
||||
};
|
||||
|
||||
} // namespace rex::filesystem
|
||||
52
include/rex/filesystem/devices/disc_image_entry.h
Normal file
52
include/rex/filesystem/devices/disc_image_entry.h
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2020 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*
|
||||
* @modified Tom Clay, 2026 - Adapted for ReXGlue runtime
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <rex/filesystem/entry.h>
|
||||
#include <rex/memory/mapped_memory.h>
|
||||
|
||||
namespace rex::filesystem {
|
||||
|
||||
class DiscImageDevice;
|
||||
|
||||
class DiscImageEntry : public Entry {
|
||||
public:
|
||||
DiscImageEntry(Device* device, Entry* parent, const std::string_view path,
|
||||
memory::MappedMemory* mmap);
|
||||
~DiscImageEntry() override;
|
||||
|
||||
static std::unique_ptr<DiscImageEntry> Create(Device* device, Entry* parent,
|
||||
const std::string_view name,
|
||||
memory::MappedMemory* mmap);
|
||||
|
||||
memory::MappedMemory* mmap() const { return mmap_; }
|
||||
size_t data_offset() const { return data_offset_; }
|
||||
size_t data_size() const { return data_size_; }
|
||||
|
||||
X_STATUS Open(uint32_t desired_access, File** out_file) override;
|
||||
|
||||
bool can_map() const override { return true; }
|
||||
std::unique_ptr<memory::MappedMemory> OpenMapped(memory::MappedMemory::Mode mode, size_t offset,
|
||||
size_t length) override;
|
||||
|
||||
private:
|
||||
friend class DiscImageDevice;
|
||||
|
||||
memory::MappedMemory* mmap_;
|
||||
size_t data_offset_;
|
||||
size_t data_size_;
|
||||
};
|
||||
|
||||
} // namespace rex::filesystem
|
||||
46
include/rex/filesystem/devices/disc_image_file.h
Normal file
46
include/rex/filesystem/devices/disc_image_file.h
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2013 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*
|
||||
* @modified Tom Clay, 2026 - Adapted for ReXGlue runtime
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <span>
|
||||
|
||||
#include <rex/filesystem/file.h>
|
||||
|
||||
namespace rex::filesystem {
|
||||
|
||||
class DiscImageEntry;
|
||||
|
||||
class DiscImageFile : public File {
|
||||
public:
|
||||
DiscImageFile(uint32_t file_access, DiscImageEntry* entry);
|
||||
~DiscImageFile() override;
|
||||
|
||||
void Destroy() override;
|
||||
|
||||
X_STATUS ReadSync(std::span<uint8_t> buffer, size_t byte_offset, size_t* out_bytes_read) override;
|
||||
X_STATUS WriteSync(std::span<const uint8_t> buffer, size_t byte_offset,
|
||||
size_t* out_bytes_written) override {
|
||||
(void)buffer;
|
||||
(void)byte_offset;
|
||||
(void)out_bytes_written;
|
||||
return X_STATUS_ACCESS_DENIED;
|
||||
}
|
||||
X_STATUS SetLength(size_t length) override {
|
||||
(void)length;
|
||||
return X_STATUS_ACCESS_DENIED;
|
||||
}
|
||||
|
||||
private:
|
||||
DiscImageEntry* entry_;
|
||||
};
|
||||
|
||||
} // namespace rex::filesystem
|
||||
53
include/rex/filesystem/devices/host_path_device.h
Normal file
53
include/rex/filesystem/devices/host_path_device.h
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2020 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*
|
||||
* @modified Tom Clay, 2026 - Adapted for ReXGlue runtime
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <rex/filesystem/device.h>
|
||||
|
||||
namespace rex::filesystem {
|
||||
|
||||
class HostPathEntry;
|
||||
|
||||
class HostPathDevice : public Device {
|
||||
public:
|
||||
HostPathDevice(const std::string_view mount_path, const std::filesystem::path& host_path,
|
||||
bool read_only);
|
||||
~HostPathDevice() override;
|
||||
|
||||
bool Initialize() override;
|
||||
void Dump(string::StringBuffer* string_buffer) override;
|
||||
Entry* ResolvePath(const std::string_view path) override;
|
||||
|
||||
bool is_read_only() const override { return read_only_; }
|
||||
const std::filesystem::path& host_path() const { return host_path_; }
|
||||
|
||||
const std::string& name() const override { return name_; }
|
||||
uint32_t attributes() const override { return 0; }
|
||||
uint32_t component_name_max_length() const override { return 255; }
|
||||
|
||||
uint32_t total_allocation_units() const override { return 128 * 1024; }
|
||||
uint32_t available_allocation_units() const override { return 128 * 1024; }
|
||||
uint32_t sectors_per_allocation_unit() const override { return 1; }
|
||||
uint32_t bytes_per_sector() const override { return 0x200; }
|
||||
|
||||
private:
|
||||
void PopulateEntry(HostPathEntry* parent_entry);
|
||||
|
||||
std::string name_;
|
||||
std::filesystem::path host_path_;
|
||||
std::unique_ptr<Entry> root_entry_;
|
||||
bool read_only_;
|
||||
};
|
||||
|
||||
} // namespace rex::filesystem
|
||||
58
include/rex/filesystem/devices/host_path_entry.h
Normal file
58
include/rex/filesystem/devices/host_path_entry.h
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2020 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*
|
||||
* @modified Tom Clay, 2026 - Adapted for ReXGlue runtime
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <rex/filesystem.h>
|
||||
#include <rex/filesystem/entry.h>
|
||||
|
||||
namespace rex::filesystem {
|
||||
|
||||
class HostPathDevice;
|
||||
|
||||
class HostPathEntry : public Entry {
|
||||
public:
|
||||
HostPathEntry(Device* device, Entry* parent, const std::string_view path,
|
||||
const std::filesystem::path& host_path);
|
||||
~HostPathEntry() override;
|
||||
|
||||
static HostPathEntry* Create(Device* device, Entry* parent,
|
||||
const std::filesystem::path& full_path,
|
||||
rex::filesystem::FileInfo file_info);
|
||||
|
||||
const std::filesystem::path& host_path() const { return host_path_; }
|
||||
|
||||
X_STATUS Open(uint32_t desired_access, File** out_file) override;
|
||||
bool Truncate() override;
|
||||
|
||||
bool can_map() const override { return true; }
|
||||
std::unique_ptr<memory::MappedMemory> OpenMapped(memory::MappedMemory::Mode mode, size_t offset,
|
||||
size_t length) override;
|
||||
void update() override;
|
||||
bool SetAttributes(uint64_t attributes) override;
|
||||
bool SetCreateTimestamp(uint64_t timestamp) override;
|
||||
bool SetAccessTimestamp(uint64_t timestamp) override;
|
||||
bool SetWriteTimestamp(uint64_t timestamp) override;
|
||||
|
||||
private:
|
||||
friend class HostPathDevice;
|
||||
|
||||
std::unique_ptr<Entry> CreateEntryInternal(const std::string_view name,
|
||||
uint32_t attributes) override;
|
||||
bool DeleteEntryInternal(Entry* entry) override;
|
||||
void RenameEntryInternal(const std::vector<std::string_view>& path_parts) override;
|
||||
|
||||
std::filesystem::path host_path_;
|
||||
};
|
||||
|
||||
} // namespace rex::filesystem
|
||||
41
include/rex/filesystem/devices/host_path_file.h
Normal file
41
include/rex/filesystem/devices/host_path_file.h
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2013 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*
|
||||
* @modified Tom Clay, 2026 - Adapted for ReXGlue runtime
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <span>
|
||||
#include <string>
|
||||
|
||||
#include <rex/filesystem.h>
|
||||
#include <rex/filesystem/file.h>
|
||||
|
||||
namespace rex::filesystem {
|
||||
|
||||
class HostPathEntry;
|
||||
|
||||
class HostPathFile : public File {
|
||||
public:
|
||||
HostPathFile(uint32_t file_access, HostPathEntry* entry,
|
||||
std::unique_ptr<rex::filesystem::FileHandle> file_handle);
|
||||
~HostPathFile() override;
|
||||
|
||||
void Destroy() override;
|
||||
|
||||
X_STATUS ReadSync(std::span<uint8_t> buffer, size_t byte_offset, size_t* out_bytes_read) override;
|
||||
X_STATUS WriteSync(std::span<const uint8_t> buffer, size_t byte_offset,
|
||||
size_t* out_bytes_written) override;
|
||||
X_STATUS SetLength(size_t length) override;
|
||||
|
||||
private:
|
||||
std::unique_ptr<rex::filesystem::FileHandle> file_handle_;
|
||||
};
|
||||
|
||||
} // namespace rex::filesystem
|
||||
53
include/rex/filesystem/devices/null_device.h
Normal file
53
include/rex/filesystem/devices/null_device.h
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2021 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*
|
||||
* @modified Tom Clay, 2026 - Adapted for ReXGlue runtime
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <rex/filesystem/device.h>
|
||||
|
||||
namespace rex::filesystem {
|
||||
|
||||
class NullEntry;
|
||||
|
||||
class NullDevice : public Device {
|
||||
public:
|
||||
NullDevice(const std::string& mount_path, const std::initializer_list<std::string>& null_paths);
|
||||
~NullDevice() override;
|
||||
|
||||
bool Initialize() override;
|
||||
void Dump(string::StringBuffer* string_buffer) override;
|
||||
Entry* ResolvePath(const std::string_view path) override;
|
||||
|
||||
bool is_read_only() const override { return false; }
|
||||
|
||||
const std::string& name() const override { return name_; }
|
||||
uint32_t attributes() const override { return 0; }
|
||||
uint32_t component_name_max_length() const override { return 40; }
|
||||
|
||||
uint32_t total_allocation_units() const override { return 0x10; }
|
||||
uint32_t available_allocation_units() const override { return 0x10; }
|
||||
|
||||
// STFC/cache code seems to require the product of the next two to equal
|
||||
// 0x10000
|
||||
uint32_t sectors_per_allocation_unit() const override { return 0x80; }
|
||||
|
||||
// STFC requires <= 0x1000
|
||||
uint32_t bytes_per_sector() const override { return 0x200; }
|
||||
|
||||
private:
|
||||
std::string name_;
|
||||
std::unique_ptr<Entry> root_entry_;
|
||||
std::vector<std::string> null_paths_;
|
||||
};
|
||||
|
||||
} // namespace rex::filesystem
|
||||
38
include/rex/filesystem/devices/null_entry.h
Normal file
38
include/rex/filesystem/devices/null_entry.h
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2021 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*
|
||||
* @modified Tom Clay, 2026 - Adapted for ReXGlue runtime
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <rex/filesystem.h>
|
||||
#include <rex/filesystem/entry.h>
|
||||
|
||||
namespace rex::filesystem {
|
||||
|
||||
class NullDevice;
|
||||
|
||||
class NullEntry : public Entry {
|
||||
public:
|
||||
NullEntry(Device* device, Entry* parent, std::string path);
|
||||
~NullEntry() override;
|
||||
|
||||
static NullEntry* Create(Device* device, Entry* parent, const std::string& path);
|
||||
|
||||
X_STATUS Open(uint32_t desired_access, File** out_file) override;
|
||||
|
||||
bool can_map() const override { return false; }
|
||||
|
||||
private:
|
||||
friend class NullDevice;
|
||||
};
|
||||
|
||||
} // namespace rex::filesystem
|
||||
37
include/rex/filesystem/devices/null_file.h
Normal file
37
include/rex/filesystem/devices/null_file.h
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2021 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*
|
||||
* @modified Tom Clay, 2026 - Adapted for ReXGlue runtime
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <span>
|
||||
#include <string>
|
||||
|
||||
#include <rex/filesystem.h>
|
||||
#include <rex/filesystem/file.h>
|
||||
|
||||
namespace rex::filesystem {
|
||||
|
||||
class NullEntry;
|
||||
|
||||
class NullFile : public File {
|
||||
public:
|
||||
NullFile(uint32_t file_access, NullEntry* entry);
|
||||
~NullFile() override;
|
||||
|
||||
void Destroy() override;
|
||||
|
||||
X_STATUS ReadSync(std::span<uint8_t> buffer, size_t byte_offset, size_t* out_bytes_read) override;
|
||||
X_STATUS WriteSync(std::span<const uint8_t> buffer, size_t byte_offset,
|
||||
size_t* out_bytes_written) override;
|
||||
X_STATUS SetLength(size_t length) override;
|
||||
};
|
||||
|
||||
} // namespace rex::filesystem
|
||||
142
include/rex/filesystem/devices/stfs_container_device.h
Normal file
142
include/rex/filesystem/devices/stfs_container_device.h
Normal file
|
|
@ -0,0 +1,142 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2020 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*
|
||||
* @modified Tom Clay, 2026 - Adapted for ReXGlue runtime
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <rex/filesystem/devices/stfs_xbox.h>
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
#include <rex/filesystem/device.h>
|
||||
#include <rex/math.h>
|
||||
#include <rex/string/util.h>
|
||||
|
||||
namespace rex::filesystem {
|
||||
|
||||
// https://free60project.github.io/wiki/STFS.html
|
||||
|
||||
class StfsContainerEntry;
|
||||
|
||||
class StfsContainerDevice : public Device {
|
||||
public:
|
||||
const static uint32_t kBlockSize = 0x1000;
|
||||
|
||||
StfsContainerDevice(const std::string_view mount_path, const std::filesystem::path& host_path);
|
||||
~StfsContainerDevice() override;
|
||||
|
||||
bool Initialize() override;
|
||||
|
||||
bool is_read_only() const override {
|
||||
return header_.metadata.volume_type != XContentVolumeType::kStfs ||
|
||||
header_.metadata.volume_descriptor.stfs.flags.bits.read_only_format;
|
||||
}
|
||||
|
||||
void Dump(string::StringBuffer* string_buffer) override;
|
||||
Entry* ResolvePath(const std::string_view path) override;
|
||||
|
||||
const std::string& name() const override { return name_; }
|
||||
uint32_t attributes() const override { return 0; }
|
||||
uint32_t component_name_max_length() const override { return 40; }
|
||||
const StfsHeader& header() const { return header_; }
|
||||
|
||||
// Reads and validates the StfsHeader from an STFS package file without
|
||||
// mounting the device. Returns nullptr if the file is missing, too small,
|
||||
// or has an invalid magic.
|
||||
static std::unique_ptr<StfsHeader> ReadPackageHeader(const std::filesystem::path& file_path);
|
||||
|
||||
uint32_t total_allocation_units() const override {
|
||||
if (header_.metadata.volume_type == XContentVolumeType::kStfs) {
|
||||
return header_.metadata.volume_descriptor.stfs.total_block_count;
|
||||
}
|
||||
|
||||
return uint32_t(data_size() / sectors_per_allocation_unit() / bytes_per_sector());
|
||||
}
|
||||
uint32_t available_allocation_units() const override {
|
||||
if (!is_read_only()) {
|
||||
auto& descriptor = header_.metadata.volume_descriptor.stfs;
|
||||
return kBlocksPerHashLevel[2] - (descriptor.total_block_count - descriptor.free_block_count);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
uint32_t sectors_per_allocation_unit() const override { return 8; }
|
||||
uint32_t bytes_per_sector() const override { return 0x200; }
|
||||
|
||||
size_t data_size() const {
|
||||
if (header_.header.header_size) {
|
||||
if (header_.metadata.volume_type == XContentVolumeType::kStfs) {
|
||||
return header_.metadata.volume_descriptor.stfs.total_block_count * kBlockSize;
|
||||
}
|
||||
return files_total_size_ - rex::round_up(header_.header.header_size, kBlockSize);
|
||||
}
|
||||
return files_total_size_ - sizeof(StfsHeader);
|
||||
}
|
||||
|
||||
private:
|
||||
const uint32_t kBlocksPerHashLevel[3] = {170, 28900, 4913000};
|
||||
const uint32_t kEndOfChain = 0xFFFFFF;
|
||||
const uint32_t kEntriesPerDirectoryBlock = kBlockSize / sizeof(StfsDirectoryEntry);
|
||||
|
||||
enum class Error {
|
||||
kSuccess = 0,
|
||||
kErrorOutOfMemory = -1,
|
||||
kErrorReadError = -10,
|
||||
kErrorFileMismatch = -30,
|
||||
kErrorDamagedFile = -31,
|
||||
kErrorTooSmall = -32,
|
||||
};
|
||||
|
||||
enum class SvodLayoutType {
|
||||
kUnknown = 0x0,
|
||||
kEnhancedGDF = 0x1,
|
||||
kXSF = 0x2,
|
||||
kSingleFile = 0x4,
|
||||
};
|
||||
|
||||
XContentPackageType ReadMagic(const std::filesystem::path& path);
|
||||
bool ResolveFromFolder(const std::filesystem::path& path);
|
||||
|
||||
Error OpenFiles();
|
||||
void CloseFiles();
|
||||
|
||||
Error ReadHeaderAndVerify(FILE* header_file);
|
||||
|
||||
Error ReadSVOD();
|
||||
Error ReadEntrySVOD(uint32_t sector, uint32_t ordinal, StfsContainerEntry* parent);
|
||||
void BlockToOffsetSVOD(size_t sector, size_t* address, size_t* file_index);
|
||||
|
||||
Error ReadSTFS();
|
||||
size_t BlockToOffsetSTFS(uint64_t block_index) const;
|
||||
uint32_t BlockToHashBlockNumberSTFS(uint32_t block_index, uint32_t hash_level) const;
|
||||
size_t BlockToHashBlockOffsetSTFS(uint32_t block_index, uint32_t hash_level) const;
|
||||
|
||||
const StfsHashEntry* GetBlockHash(uint32_t block_index);
|
||||
|
||||
std::string name_;
|
||||
std::filesystem::path host_path_;
|
||||
|
||||
std::map<size_t, FILE*> files_;
|
||||
size_t files_total_size_;
|
||||
|
||||
size_t svod_base_offset_;
|
||||
|
||||
std::unique_ptr<Entry> root_entry_;
|
||||
StfsHeader header_;
|
||||
SvodLayoutType svod_layout_;
|
||||
uint32_t blocks_per_hash_table_;
|
||||
uint32_t block_step[2];
|
||||
|
||||
std::unordered_map<size_t, StfsHashTable> cached_hash_tables_;
|
||||
};
|
||||
|
||||
} // namespace rex::filesystem
|
||||
60
include/rex/filesystem/devices/stfs_container_entry.h
Normal file
60
include/rex/filesystem/devices/stfs_container_entry.h
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2020 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*
|
||||
* @modified Tom Clay, 2026 - Adapted for ReXGlue runtime
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <rex/filesystem/entry.h>
|
||||
#include <rex/filesystem/file.h>
|
||||
|
||||
namespace rex::filesystem {
|
||||
typedef std::map<size_t, FILE*> MultiFileHandles;
|
||||
|
||||
class StfsContainerDevice;
|
||||
|
||||
class StfsContainerEntry : public Entry {
|
||||
public:
|
||||
StfsContainerEntry(Device* device, Entry* parent, const std::string_view path,
|
||||
MultiFileHandles* files);
|
||||
~StfsContainerEntry() override;
|
||||
|
||||
static std::unique_ptr<StfsContainerEntry> Create(Device* device, Entry* parent,
|
||||
const std::string_view name,
|
||||
MultiFileHandles* files);
|
||||
|
||||
MultiFileHandles* files() const { return files_; }
|
||||
size_t data_offset() const { return data_offset_; }
|
||||
size_t data_size() const { return data_size_; }
|
||||
size_t block() const { return block_; }
|
||||
|
||||
X_STATUS Open(uint32_t desired_access, File** out_file) override;
|
||||
|
||||
struct BlockRecord {
|
||||
size_t file;
|
||||
size_t offset;
|
||||
size_t length;
|
||||
};
|
||||
const std::vector<BlockRecord>& block_list() const { return block_list_; }
|
||||
|
||||
private:
|
||||
friend class StfsContainerDevice;
|
||||
|
||||
MultiFileHandles* files_;
|
||||
size_t data_offset_;
|
||||
size_t data_size_;
|
||||
size_t block_;
|
||||
std::vector<BlockRecord> block_list_;
|
||||
};
|
||||
|
||||
} // namespace rex::filesystem
|
||||
40
include/rex/filesystem/devices/stfs_container_file.h
Normal file
40
include/rex/filesystem/devices/stfs_container_file.h
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2014 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*
|
||||
* @modified Tom Clay, 2026 - Adapted for ReXGlue runtime
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <span>
|
||||
|
||||
#include <rex/filesystem/file.h>
|
||||
|
||||
namespace rex::filesystem {
|
||||
|
||||
class StfsContainerEntry;
|
||||
|
||||
class StfsContainerFile : public File {
|
||||
public:
|
||||
StfsContainerFile(uint32_t file_access, StfsContainerEntry* entry);
|
||||
~StfsContainerFile() override;
|
||||
|
||||
void Destroy() override;
|
||||
|
||||
X_STATUS ReadSync(std::span<uint8_t> buffer, size_t byte_offset, size_t* out_bytes_read) override;
|
||||
X_STATUS WriteSync(std::span<const uint8_t> /*buffer*/, size_t /*byte_offset*/,
|
||||
size_t* /*out_bytes_written*/) override {
|
||||
return X_STATUS_ACCESS_DENIED;
|
||||
}
|
||||
X_STATUS SetLength(size_t /*length*/) override { return X_STATUS_ACCESS_DENIED; }
|
||||
|
||||
private:
|
||||
StfsContainerEntry* entry_;
|
||||
};
|
||||
|
||||
} // namespace rex::filesystem
|
||||
479
include/rex/filesystem/devices/stfs_xbox.h
Normal file
479
include/rex/filesystem/devices/stfs_xbox.h
Normal file
|
|
@ -0,0 +1,479 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2021 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*
|
||||
* @modified Tom Clay, 2026 - Adapted for ReXGlue runtime
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <time.h>
|
||||
|
||||
#include <rex/assert.h>
|
||||
#include <rex/memory.h>
|
||||
#include <rex/platform.h>
|
||||
#include <rex/string/util.h>
|
||||
#include <rex/system/util/xex2_info.h>
|
||||
#include <rex/system/xcontent.h>
|
||||
#include <rex/types.h>
|
||||
|
||||
namespace rex::filesystem {
|
||||
|
||||
// Import kernel content types for STFS structures
|
||||
using rex::system::XContentType;
|
||||
using rex::system::XLanguage;
|
||||
|
||||
// Convert FAT timestamp to 100-nanosecond intervals since January 1, 1601 (UTC)
|
||||
inline uint64_t decode_fat_timestamp(const uint32_t date, const uint32_t time) {
|
||||
struct tm tm = {};
|
||||
// 80 is the difference between 1980 (FAT) and 1900 (tm);
|
||||
tm.tm_year = ((0xFE00 & date) >> 9) + 80;
|
||||
tm.tm_mon = ((0x01E0 & date) >> 5) - 1;
|
||||
tm.tm_mday = (0x001F & date) >> 0;
|
||||
tm.tm_hour = (0xF800 & time) >> 11;
|
||||
tm.tm_min = (0x07E0 & time) >> 5;
|
||||
tm.tm_sec = (0x001F & time) << 1; // the value stored in 2-seconds intervals
|
||||
tm.tm_isdst = 0;
|
||||
|
||||
#if REX_PLATFORM_WIN32
|
||||
time_t timet = _mkgmtime(&tm);
|
||||
#else
|
||||
time_t timet = timegm(&tm);
|
||||
#endif
|
||||
|
||||
if (timet == -1) {
|
||||
return 0;
|
||||
}
|
||||
// 11644473600LL is a difference between 1970 and 1601
|
||||
return (timet + 11644473600LL) * 10000000;
|
||||
}
|
||||
|
||||
// Structs used for interchange between Xenia and actual Xbox360 kernel/XAM
|
||||
|
||||
inline uint32_t load_uint24_be(const uint8_t* p) {
|
||||
return (uint32_t(p[0]) << 16) | (uint32_t(p[1]) << 8) | uint32_t(p[2]);
|
||||
}
|
||||
inline uint32_t load_uint24_le(const uint8_t* p) {
|
||||
return (uint32_t(p[2]) << 16) | (uint32_t(p[1]) << 8) | uint32_t(p[0]);
|
||||
}
|
||||
inline void store_uint24_le(uint8_t* p, uint32_t value) {
|
||||
p[2] = uint8_t((value >> 16) & 0xFF);
|
||||
p[1] = uint8_t((value >> 8) & 0xFF);
|
||||
p[0] = uint8_t(value & 0xFF);
|
||||
}
|
||||
|
||||
enum class XContentPackageType : uint32_t {
|
||||
kCon = 0x434F4E20,
|
||||
kPirs = 0x50495253,
|
||||
kLive = 0x4C495645,
|
||||
};
|
||||
|
||||
enum class XContentVolumeType : uint32_t {
|
||||
kStfs = 0,
|
||||
kSvod = 1,
|
||||
};
|
||||
|
||||
/* STFS structures */
|
||||
#pragma pack(push, 1)
|
||||
struct StfsVolumeDescriptor {
|
||||
uint8_t descriptor_length;
|
||||
uint8_t version;
|
||||
union {
|
||||
uint8_t as_byte;
|
||||
struct {
|
||||
uint8_t read_only_format : 1; // if set, only uses a single backing-block
|
||||
// per hash table (no resiliency),
|
||||
// otherwise uses two
|
||||
uint8_t root_active_index : 1; // if set, uses secondary backing-block
|
||||
// for the highest-level hash table
|
||||
|
||||
uint8_t directory_overallocated : 1;
|
||||
uint8_t directory_index_bounds_valid : 1;
|
||||
} bits;
|
||||
} flags;
|
||||
uint16_t file_table_block_count;
|
||||
uint8_t file_table_block_number_raw[3];
|
||||
uint8_t top_hash_table_hash[0x14];
|
||||
be<uint32_t> total_block_count;
|
||||
be<uint32_t> free_block_count;
|
||||
|
||||
uint32_t file_table_block_number() const { return load_uint24_le(file_table_block_number_raw); }
|
||||
|
||||
void set_file_table_block_number(uint32_t value) {
|
||||
store_uint24_le(file_table_block_number_raw, value);
|
||||
}
|
||||
|
||||
bool is_valid() const { return descriptor_length == sizeof(StfsVolumeDescriptor); }
|
||||
};
|
||||
static_assert_size(StfsVolumeDescriptor, 0x24);
|
||||
#pragma pack(pop)
|
||||
|
||||
enum class StfsHashState : uint8_t {
|
||||
kFree = 0, // unallocated but doesn't exist in package (needs to expand)?
|
||||
kFree2 = 1, // unallocated but exists in package?
|
||||
kInUse = 2,
|
||||
};
|
||||
|
||||
struct StfsHashEntry {
|
||||
uint8_t sha1[0x14];
|
||||
|
||||
rex::be<uint32_t> info_raw;
|
||||
|
||||
uint32_t level0_next_block() const { return info_raw & 0xFFFFFF; }
|
||||
void set_level0_next_block(uint32_t value) {
|
||||
info_raw = (info_raw & ~0xFFFFFF) | (value & 0xFFFFFF);
|
||||
}
|
||||
|
||||
StfsHashState level0_allocation_state() const {
|
||||
return StfsHashState(uint8_t(((info_raw & 0xC0000000) >> 30) & 0xFF));
|
||||
}
|
||||
void set_level0_allocation_state(StfsHashState value) {
|
||||
info_raw = (info_raw & ~0xC0000000) | (uint32_t(value) << 30);
|
||||
}
|
||||
|
||||
uint32_t levelN_num_blocks_free() const { return info_raw & 0x7FFF; }
|
||||
void set_levelN_num_blocks_free(uint32_t value) {
|
||||
info_raw = (info_raw & ~0x7FFF) | (value & 0x7FFF);
|
||||
}
|
||||
|
||||
uint32_t levelN_num_blocks_unk() const { return ((info_raw & 0x3FFF8000) >> 15) & 0x7FFF; }
|
||||
void set_levelN_num_blocks_unk(uint32_t value) {
|
||||
info_raw = (info_raw & ~0x3FFF8000) | ((value & 0x7FFF) << 15);
|
||||
}
|
||||
|
||||
bool levelN_active_index() const { return (info_raw & 0x40000000) != 0; }
|
||||
void set_levelN_active_index(bool value) {
|
||||
info_raw = (info_raw & ~0x40000000) | (value ? 0x40000000 : 0);
|
||||
}
|
||||
|
||||
bool levelN_writeable() const { return (info_raw & 0x80000000) != 0; }
|
||||
void set_levelN_writeable(bool value) {
|
||||
info_raw = (info_raw & ~0x80000000) | (value ? 0x80000000 : 0);
|
||||
}
|
||||
};
|
||||
static_assert_size(StfsHashEntry, 0x18);
|
||||
|
||||
struct StfsHashTable {
|
||||
StfsHashEntry entries[170];
|
||||
rex::be<uint32_t> num_blocks; // num L0 blocks covered by this table?
|
||||
uint8_t padding[12];
|
||||
};
|
||||
static_assert_size(StfsHashTable, 0x1000);
|
||||
|
||||
struct StfsDirectoryEntry {
|
||||
char name[40];
|
||||
|
||||
struct {
|
||||
uint8_t name_length : 6;
|
||||
uint8_t contiguous : 1;
|
||||
uint8_t directory : 1;
|
||||
} flags;
|
||||
|
||||
uint8_t valid_data_blocks_raw[3];
|
||||
uint8_t allocated_data_blocks_raw[3];
|
||||
uint8_t start_block_number_raw[3];
|
||||
|
||||
be<uint16_t> directory_index;
|
||||
|
||||
be<uint32_t> length;
|
||||
|
||||
be<uint16_t> create_date;
|
||||
be<uint16_t> create_time;
|
||||
be<uint16_t> modified_date;
|
||||
be<uint16_t> modified_time;
|
||||
|
||||
uint32_t valid_data_blocks() const { return load_uint24_le(valid_data_blocks_raw); }
|
||||
|
||||
void set_valid_data_blocks(uint32_t value) { store_uint24_le(valid_data_blocks_raw, value); }
|
||||
|
||||
uint32_t allocated_data_blocks() const { return load_uint24_le(allocated_data_blocks_raw); }
|
||||
|
||||
void set_allocated_data_blocks(uint32_t value) {
|
||||
store_uint24_le(allocated_data_blocks_raw, value);
|
||||
}
|
||||
|
||||
uint32_t start_block_number() const { return load_uint24_le(start_block_number_raw); }
|
||||
|
||||
void set_start_block_number(uint32_t value) { store_uint24_le(start_block_number_raw, value); }
|
||||
};
|
||||
static_assert_size(StfsDirectoryEntry, 0x40);
|
||||
|
||||
struct StfsDirectoryBlock {
|
||||
StfsDirectoryEntry entries[0x40];
|
||||
};
|
||||
static_assert_size(StfsDirectoryBlock, 0x1000);
|
||||
|
||||
/* SVOD structures */
|
||||
struct SvodDeviceDescriptor {
|
||||
uint8_t descriptor_length;
|
||||
uint8_t block_cache_element_count;
|
||||
uint8_t worker_thread_processor;
|
||||
uint8_t worker_thread_priority;
|
||||
uint8_t first_fragment_hash_entry[0x14];
|
||||
union {
|
||||
uint8_t as_byte;
|
||||
struct {
|
||||
uint8_t must_be_zero_for_future_usage : 6;
|
||||
uint8_t enhanced_gdf_layout : 1;
|
||||
uint8_t zero_for_downlevel_clients : 1;
|
||||
} bits;
|
||||
} features;
|
||||
uint8_t num_data_blocks_raw[3];
|
||||
uint8_t start_data_block_raw[3];
|
||||
uint8_t reserved[5];
|
||||
|
||||
uint32_t num_data_blocks() { return load_uint24_le(num_data_blocks_raw); }
|
||||
|
||||
uint32_t start_data_block() { return load_uint24_le(start_data_block_raw); }
|
||||
};
|
||||
static_assert_size(SvodDeviceDescriptor, 0x24);
|
||||
|
||||
/* XContent structures */
|
||||
struct XContentLicense {
|
||||
be<uint64_t> licensee_id;
|
||||
be<uint32_t> license_bits;
|
||||
be<uint32_t> license_flags;
|
||||
};
|
||||
static_assert_size(XContentLicense, 0x10);
|
||||
|
||||
struct XContentMediaData {
|
||||
uint8_t series_id[0x10];
|
||||
uint8_t season_id[0x10];
|
||||
be<uint16_t> season_number;
|
||||
be<uint16_t> episode_number;
|
||||
};
|
||||
static_assert_size(XContentMediaData, 0x24);
|
||||
|
||||
struct XContentAvatarAssetData {
|
||||
be<uint32_t> sub_category;
|
||||
be<uint32_t> colorizable;
|
||||
uint8_t asset_id[0x10];
|
||||
uint8_t skeleton_version_mask;
|
||||
uint8_t reserved[0xB];
|
||||
};
|
||||
static_assert_size(XContentAvatarAssetData, 0x24);
|
||||
|
||||
struct XContentAttributes {
|
||||
uint8_t profile_transfer : 1;
|
||||
uint8_t device_transfer : 1;
|
||||
uint8_t move_only_transfer : 1;
|
||||
uint8_t kinect_enabled : 1;
|
||||
uint8_t disable_network_storage : 1;
|
||||
uint8_t deep_link_supported : 1;
|
||||
uint8_t reserved : 2;
|
||||
};
|
||||
static_assert_size(XContentAttributes, 1);
|
||||
|
||||
#pragma pack(push, 1)
|
||||
struct XContentMetadata {
|
||||
static const uint32_t kThumbLengthV1 = 0x4000;
|
||||
static const uint32_t kThumbLengthV2 = 0x3D00;
|
||||
|
||||
static const uint32_t kNumLanguagesV1 = 9;
|
||||
// metadata_version 2 adds 3 languages inside thumbnail/title_thumbnail space
|
||||
static const uint32_t kNumLanguagesV2 = 12;
|
||||
|
||||
be<XContentType> content_type;
|
||||
be<uint32_t> metadata_version;
|
||||
be<uint64_t> content_size;
|
||||
xex2_opt_execution_info execution_info;
|
||||
uint8_t console_id[5];
|
||||
be<uint64_t> profile_id;
|
||||
union {
|
||||
StfsVolumeDescriptor stfs;
|
||||
SvodDeviceDescriptor svod;
|
||||
} volume_descriptor;
|
||||
be<uint32_t> data_file_count;
|
||||
be<uint64_t> data_file_size;
|
||||
be<XContentVolumeType> volume_type;
|
||||
be<uint64_t> online_creator;
|
||||
be<uint32_t> category;
|
||||
uint8_t reserved2[0x20];
|
||||
union {
|
||||
XContentMediaData media_data;
|
||||
XContentAvatarAssetData avatar_asset_data;
|
||||
} metadata_v2;
|
||||
uint8_t device_id[0x14];
|
||||
union {
|
||||
be<uint16_t> uint[kNumLanguagesV1][128];
|
||||
char16_t chars[kNumLanguagesV1][128];
|
||||
} display_name_raw;
|
||||
union {
|
||||
be<uint16_t> uint[kNumLanguagesV1][128];
|
||||
char16_t chars[kNumLanguagesV1][128];
|
||||
} description_raw;
|
||||
union {
|
||||
be<uint16_t> uint[64];
|
||||
char16_t chars[64];
|
||||
} publisher_raw;
|
||||
union {
|
||||
be<uint16_t> uint[64];
|
||||
char16_t chars[64];
|
||||
} title_name_raw;
|
||||
union {
|
||||
uint8_t as_byte;
|
||||
XContentAttributes bits;
|
||||
} flags;
|
||||
be<uint32_t> thumbnail_size;
|
||||
be<uint32_t> title_thumbnail_size;
|
||||
uint8_t thumbnail[kThumbLengthV2];
|
||||
union {
|
||||
be<uint16_t> uint[kNumLanguagesV2 - kNumLanguagesV1][128];
|
||||
char16_t chars[kNumLanguagesV2 - kNumLanguagesV1][128];
|
||||
} display_name_ex_raw;
|
||||
uint8_t title_thumbnail[kThumbLengthV2];
|
||||
union {
|
||||
be<uint16_t> uint[kNumLanguagesV2 - kNumLanguagesV1][128];
|
||||
char16_t chars[kNumLanguagesV2 - kNumLanguagesV1][128];
|
||||
} description_ex_raw;
|
||||
|
||||
std::u16string display_name(XLanguage language) const {
|
||||
uint32_t lang_id = uint32_t(language) - 1;
|
||||
|
||||
if (lang_id >= kNumLanguagesV2) {
|
||||
assert_always();
|
||||
// no room for this lang, read from english slot..
|
||||
lang_id = uint32_t(XLanguage::kEnglish) - 1;
|
||||
}
|
||||
|
||||
const be<uint16_t>* str = 0;
|
||||
if (lang_id >= 0 && lang_id < kNumLanguagesV1) {
|
||||
str = display_name_raw.uint[lang_id];
|
||||
} else if (lang_id >= kNumLanguagesV1 && lang_id < kNumLanguagesV2 && metadata_version >= 2) {
|
||||
str = display_name_ex_raw.uint[lang_id - kNumLanguagesV1];
|
||||
}
|
||||
|
||||
if (!str) {
|
||||
// Invalid language ID?
|
||||
assert_always();
|
||||
return u"";
|
||||
}
|
||||
|
||||
return memory::load_and_swap<std::u16string>(str);
|
||||
}
|
||||
|
||||
std::u16string description(XLanguage language) const {
|
||||
uint32_t lang_id = uint32_t(language) - 1;
|
||||
|
||||
if (lang_id >= kNumLanguagesV2) {
|
||||
assert_always();
|
||||
// no room for this lang, read from english slot..
|
||||
lang_id = uint32_t(XLanguage::kEnglish) - 1;
|
||||
}
|
||||
|
||||
const be<uint16_t>* str = 0;
|
||||
if (lang_id >= 0 && lang_id < kNumLanguagesV1) {
|
||||
str = description_raw.uint[lang_id];
|
||||
} else if (lang_id >= kNumLanguagesV1 && lang_id < kNumLanguagesV2 && metadata_version >= 2) {
|
||||
str = description_ex_raw.uint[lang_id - kNumLanguagesV1];
|
||||
}
|
||||
|
||||
if (!str) {
|
||||
// Invalid language ID?
|
||||
assert_always();
|
||||
return u"";
|
||||
}
|
||||
|
||||
return memory::load_and_swap<std::u16string>(str);
|
||||
}
|
||||
|
||||
std::u16string publisher() const {
|
||||
return memory::load_and_swap<std::u16string>(publisher_raw.uint);
|
||||
}
|
||||
|
||||
std::u16string title_name() const {
|
||||
return memory::load_and_swap<std::u16string>(title_name_raw.uint);
|
||||
}
|
||||
|
||||
bool set_display_name(XLanguage language, const std::u16string_view value) {
|
||||
uint32_t lang_id = uint32_t(language) - 1;
|
||||
|
||||
if (lang_id >= kNumLanguagesV2) {
|
||||
assert_always();
|
||||
// no room for this lang, store in english slot..
|
||||
lang_id = uint32_t(XLanguage::kEnglish) - 1;
|
||||
}
|
||||
|
||||
char16_t* str = 0;
|
||||
if (lang_id >= 0 && lang_id < kNumLanguagesV1) {
|
||||
str = display_name_raw.chars[lang_id];
|
||||
} else if (lang_id >= kNumLanguagesV1 && lang_id < kNumLanguagesV2 && metadata_version >= 2) {
|
||||
str = display_name_ex_raw.chars[lang_id - kNumLanguagesV1];
|
||||
}
|
||||
|
||||
if (!str) {
|
||||
// Invalid language ID?
|
||||
assert_always();
|
||||
return false;
|
||||
}
|
||||
|
||||
rex::string::util_copy_and_swap_truncating(str, value, countof(display_name_raw.chars[0]));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool set_description(XLanguage language, const std::u16string_view value) {
|
||||
uint32_t lang_id = uint32_t(language) - 1;
|
||||
|
||||
if (lang_id >= kNumLanguagesV2) {
|
||||
assert_always();
|
||||
// no room for this lang, store in english slot..
|
||||
lang_id = uint32_t(XLanguage::kEnglish) - 1;
|
||||
}
|
||||
|
||||
char16_t* str = 0;
|
||||
if (lang_id >= 0 && lang_id < kNumLanguagesV1) {
|
||||
str = description_raw.chars[lang_id];
|
||||
} else if (lang_id >= kNumLanguagesV1 && lang_id < kNumLanguagesV2 && metadata_version >= 2) {
|
||||
str = description_ex_raw.chars[lang_id - kNumLanguagesV1];
|
||||
}
|
||||
|
||||
if (!str) {
|
||||
// Invalid language ID?
|
||||
assert_always();
|
||||
return false;
|
||||
}
|
||||
|
||||
rex::string::util_copy_and_swap_truncating(str, value, countof(description_raw.chars[0]));
|
||||
return true;
|
||||
}
|
||||
|
||||
void set_publisher(const std::u16string_view value) {
|
||||
rex::string::util_copy_and_swap_truncating(publisher_raw.chars, value,
|
||||
countof(publisher_raw.chars));
|
||||
}
|
||||
|
||||
void set_title_name(const std::u16string_view value) {
|
||||
rex::string::util_copy_and_swap_truncating(title_name_raw.chars, value,
|
||||
countof(title_name_raw.chars));
|
||||
}
|
||||
};
|
||||
static_assert_size(XContentMetadata, 0x93D6);
|
||||
|
||||
struct XContentHeader {
|
||||
be<XContentPackageType> magic;
|
||||
uint8_t signature[0x228];
|
||||
XContentLicense licenses[0x10];
|
||||
uint8_t content_id[0x14];
|
||||
be<uint32_t> header_size;
|
||||
|
||||
bool is_magic_valid() const {
|
||||
return magic == XContentPackageType::kCon || magic == XContentPackageType::kLive ||
|
||||
magic == XContentPackageType::kPirs;
|
||||
}
|
||||
};
|
||||
static_assert_size(XContentHeader, 0x344);
|
||||
#pragma pack(pop)
|
||||
|
||||
struct StfsHeader {
|
||||
XContentHeader header;
|
||||
XContentMetadata metadata;
|
||||
// TODO: title/system updates contain more data after XContentMetadata, seems
|
||||
// to affect header.header_size
|
||||
};
|
||||
static_assert_size(StfsHeader, 0x971A);
|
||||
|
||||
} // namespace rex::filesystem
|
||||
166
include/rex/filesystem/entry.h
Normal file
166
include/rex/filesystem/entry.h
Normal file
|
|
@ -0,0 +1,166 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2020 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*
|
||||
* @modified Tom Clay, 2026 - Adapted for ReXGlue runtime
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <rex/filesystem.h>
|
||||
#include <rex/filesystem/wildcard.h>
|
||||
#include <rex/memory/mapped_memory.h>
|
||||
#include <rex/string/buffer.h>
|
||||
#include <rex/system/xtypes.h>
|
||||
#include <rex/thread/mutex.h>
|
||||
|
||||
namespace rex::system {
|
||||
class KernelState;
|
||||
class XFile;
|
||||
} // namespace rex::system
|
||||
|
||||
namespace rex::filesystem {
|
||||
|
||||
class Device;
|
||||
class File;
|
||||
|
||||
// Matches https://source.winehq.org/source/include/winternl.h#1591.
|
||||
enum class FileAction {
|
||||
kSuperseded = 0,
|
||||
kOpened = 1,
|
||||
kCreated = 2,
|
||||
kOverwritten = 3,
|
||||
kExists = 4,
|
||||
kDoesNotExist = 5,
|
||||
};
|
||||
|
||||
enum class FileDisposition {
|
||||
// If exist replace, else create.
|
||||
kSuperscede = 0,
|
||||
// If exist open, else error.
|
||||
kOpen = 1,
|
||||
// If exist error, else create.
|
||||
kCreate = 2,
|
||||
// If exist open, else create.
|
||||
kOpenIf = 3,
|
||||
// If exist open and overwrite, else error.
|
||||
kOverwrite = 4,
|
||||
// If exist open and overwrite, else create.
|
||||
kOverwriteIf = 5,
|
||||
};
|
||||
|
||||
// Reuse rex::filesystem definition.
|
||||
using FileAccess = rex::filesystem::FileAccess;
|
||||
|
||||
enum FileAttributeFlags : uint32_t {
|
||||
kFileAttributeNone = 0x0000,
|
||||
kFileAttributeReadOnly = 0x0001,
|
||||
kFileAttributeHidden = 0x0002,
|
||||
kFileAttributeSystem = 0x0004,
|
||||
kFileAttributeDirectory = 0x0010,
|
||||
kFileAttributeArchive = 0x0020,
|
||||
kFileAttributeDevice = 0x0040,
|
||||
kFileAttributeNormal = 0x0080,
|
||||
kFileAttributeTemporary = 0x0100,
|
||||
kFileAttributeCompressed = 0x0800,
|
||||
kFileAttributeEncrypted = 0x4000,
|
||||
};
|
||||
|
||||
class Entry {
|
||||
public:
|
||||
virtual ~Entry();
|
||||
|
||||
void Dump(rex::string::StringBuffer* string_buffer, int indent);
|
||||
|
||||
Device* device() const { return device_; }
|
||||
Entry* parent() const { return parent_; }
|
||||
const std::string& path() const { return path_; }
|
||||
const std::string& absolute_path() const { return absolute_path_; }
|
||||
const std::string& name() const { return name_; }
|
||||
uint32_t attributes() const { return attributes_; }
|
||||
size_t size() const { return size_; }
|
||||
size_t allocation_size() const { return allocation_size_; }
|
||||
uint64_t create_timestamp() const { return create_timestamp_; }
|
||||
uint64_t access_timestamp() const { return access_timestamp_; }
|
||||
uint64_t write_timestamp() const { return write_timestamp_; }
|
||||
bool delete_on_close() const { return delete_on_close_; }
|
||||
|
||||
virtual bool SetAttributes([[maybe_unused]] uint64_t attributes) { return false; }
|
||||
virtual bool SetCreateTimestamp([[maybe_unused]] uint64_t timestamp) { return false; }
|
||||
virtual bool SetAccessTimestamp([[maybe_unused]] uint64_t timestamp) { return false; }
|
||||
virtual bool SetWriteTimestamp([[maybe_unused]] uint64_t timestamp) { return false; }
|
||||
void SetForDeletion(bool delete_on_close) { delete_on_close_ = delete_on_close; }
|
||||
|
||||
bool is_read_only() const;
|
||||
|
||||
Entry* GetChild(const std::string_view name);
|
||||
Entry* ResolvePath(const std::string_view path);
|
||||
|
||||
const std::vector<std::unique_ptr<Entry>>& children() const { return children_; }
|
||||
size_t child_count() const { return children_.size(); }
|
||||
Entry* IterateChildren(const rex::filesystem::WildcardEngine& engine, size_t* current_index);
|
||||
|
||||
Entry* CreateEntry(const std::string_view name, uint32_t attributes);
|
||||
bool Delete(Entry* entry);
|
||||
bool Delete();
|
||||
virtual bool Truncate() { return false; }
|
||||
void Rename(const std::filesystem::path& file_path);
|
||||
void Touch();
|
||||
|
||||
// If successful, out_file points to a new file. When finished, call
|
||||
// file->Destroy()
|
||||
virtual X_STATUS Open(uint32_t desired_access, File** out_file) = 0;
|
||||
|
||||
virtual bool can_map() const { return false; }
|
||||
virtual std::unique_ptr<memory::MappedMemory> OpenMapped(memory::MappedMemory::Mode mode,
|
||||
size_t offset = 0, size_t length = 0) {
|
||||
(void)mode;
|
||||
(void)offset;
|
||||
(void)length;
|
||||
return nullptr;
|
||||
}
|
||||
virtual void update() { return; }
|
||||
|
||||
protected:
|
||||
Entry(Device* device, Entry* parent, const std::string_view path);
|
||||
|
||||
virtual std::unique_ptr<Entry> CreateEntryInternal(const std::string_view name,
|
||||
uint32_t attributes) {
|
||||
(void)name;
|
||||
(void)attributes;
|
||||
return nullptr;
|
||||
}
|
||||
virtual bool DeleteEntryInternal(Entry* entry) {
|
||||
(void)entry;
|
||||
return false;
|
||||
}
|
||||
virtual void RenameEntryInternal(const std::vector<std::string_view>& path_parts) {
|
||||
(void)path_parts;
|
||||
}
|
||||
|
||||
rex::thread::global_critical_region global_critical_region_;
|
||||
Device* device_;
|
||||
Entry* parent_;
|
||||
std::string path_;
|
||||
std::string absolute_path_;
|
||||
std::string name_;
|
||||
uint32_t attributes_; // FileAttributeFlags
|
||||
size_t size_;
|
||||
size_t allocation_size_;
|
||||
uint64_t create_timestamp_;
|
||||
uint64_t access_timestamp_;
|
||||
uint64_t write_timestamp_;
|
||||
std::vector<std::unique_ptr<Entry>> children_;
|
||||
bool delete_on_close_ = false;
|
||||
};
|
||||
|
||||
} // namespace rex::filesystem
|
||||
71
include/rex/filesystem/file.h
Normal file
71
include/rex/filesystem/file.h
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2015 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*
|
||||
* @modified Tom Clay, 2026 - Adapted for ReXGlue runtime
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <span>
|
||||
|
||||
#include <rex/system/xtypes.h>
|
||||
|
||||
namespace rex::filesystem {
|
||||
|
||||
class Entry;
|
||||
|
||||
class File {
|
||||
public:
|
||||
File(uint32_t file_access, Entry* entry) : file_access_(file_access), entry_(entry) {}
|
||||
virtual ~File() = default;
|
||||
|
||||
virtual void Destroy() = 0;
|
||||
|
||||
virtual X_STATUS ReadSync(std::span<uint8_t> buffer, size_t byte_offset,
|
||||
size_t* out_bytes_read) = 0;
|
||||
virtual X_STATUS WriteSync(std::span<const uint8_t> buffer, size_t byte_offset,
|
||||
size_t* out_bytes_written) = 0;
|
||||
|
||||
// TODO: Parameters
|
||||
virtual X_STATUS ReadAsync(std::span<uint8_t> buffer, size_t byte_offset,
|
||||
size_t* out_bytes_read) {
|
||||
(void)buffer;
|
||||
(void)byte_offset;
|
||||
(void)out_bytes_read;
|
||||
|
||||
return X_STATUS_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
// TODO: Parameters
|
||||
virtual X_STATUS WriteAsync(std::span<const uint8_t> buffer, size_t byte_offset,
|
||||
size_t* out_bytes_written) {
|
||||
(void)buffer;
|
||||
(void)byte_offset;
|
||||
(void)out_bytes_written;
|
||||
|
||||
return X_STATUS_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
virtual X_STATUS SetLength(size_t length) {
|
||||
(void)length;
|
||||
return X_STATUS_NOT_IMPLEMENTED;
|
||||
}
|
||||
|
||||
// rex::filesystem::FileAccess
|
||||
uint32_t file_access() const { return file_access_; }
|
||||
const Entry* entry() const { return entry_; }
|
||||
Entry* entry() { return entry_; }
|
||||
|
||||
protected:
|
||||
// rex::filesystem::FileAccess
|
||||
uint32_t file_access_ = 0;
|
||||
Entry* entry_ = nullptr;
|
||||
};
|
||||
|
||||
} // namespace rex::filesystem
|
||||
17
include/rex/filesystem/flags.h
Normal file
17
include/rex/filesystem/flags.h
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2013 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*
|
||||
* @modified Tom Clay, 2026 - Adapted for ReXGlue runtime
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <rex/cvar.h>
|
||||
|
||||
REXCVAR_DECLARE(std::string, dump_source);
|
||||
REXCVAR_DECLARE(std::string, dump_path);
|
||||
59
include/rex/filesystem/vfs.h
Normal file
59
include/rex/filesystem/vfs.h
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2013 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*
|
||||
* @modified Tom Clay, 2026 - Adapted for ReXGlue runtime
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include <rex/filesystem/device.h>
|
||||
#include <rex/filesystem/entry.h>
|
||||
#include <rex/filesystem/file.h>
|
||||
#include <rex/cvar.h>
|
||||
#include <rex/thread/mutex.h>
|
||||
|
||||
REXCVAR_DECLARE(bool, allow_game_relative_writes);
|
||||
|
||||
namespace rex::filesystem {
|
||||
|
||||
class VirtualFileSystem {
|
||||
public:
|
||||
VirtualFileSystem();
|
||||
~VirtualFileSystem();
|
||||
|
||||
bool RegisterDevice(std::unique_ptr<Device> device);
|
||||
bool UnregisterDevice(const std::string_view path);
|
||||
|
||||
bool RegisterSymbolicLink(const std::string_view path, const std::string_view target);
|
||||
bool UnregisterSymbolicLink(const std::string_view path);
|
||||
bool FindSymbolicLink(const std::string_view path, std::string& target);
|
||||
|
||||
Entry* ResolvePath(const std::string_view path);
|
||||
|
||||
Entry* CreatePath(const std::string_view path, uint32_t attributes);
|
||||
bool DeletePath(const std::string_view path);
|
||||
|
||||
X_STATUS OpenFile(Entry* root_entry, const std::string_view path,
|
||||
FileDisposition creation_disposition, uint32_t desired_access,
|
||||
bool is_directory, bool is_non_directory, File** out_file,
|
||||
FileAction* out_action);
|
||||
|
||||
private:
|
||||
rex::thread::global_critical_region global_critical_region_;
|
||||
std::vector<std::unique_ptr<Device>> devices_;
|
||||
std::unordered_map<std::string, std::string> symlinks_;
|
||||
|
||||
bool ResolveSymbolicLink(const std::string_view path, std::string& result);
|
||||
};
|
||||
|
||||
} // namespace rex::filesystem
|
||||
60
include/rex/filesystem/wildcard.h
Normal file
60
include/rex/filesystem/wildcard.h
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2020 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*
|
||||
* @modified Tom Clay, 2026 - Adapted for ReXGlue runtime
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <iterator>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <rex/string.h>
|
||||
|
||||
namespace rex {
|
||||
namespace filesystem {
|
||||
|
||||
class WildcardFlags {
|
||||
public:
|
||||
bool FromStart : 1, ToEnd : 1, ExactLength : 1;
|
||||
|
||||
WildcardFlags();
|
||||
WildcardFlags(bool start, bool end, bool exact_length);
|
||||
|
||||
static WildcardFlags FIRST;
|
||||
static WildcardFlags LAST;
|
||||
static WildcardFlags ANY;
|
||||
static WildcardFlags FIRST_AND_LAST;
|
||||
};
|
||||
|
||||
class WildcardRule {
|
||||
public:
|
||||
WildcardRule(const std::string_view match, const WildcardFlags& flags);
|
||||
bool Check(const std::string_view lower, std::string_view::size_type* offset) const;
|
||||
|
||||
private:
|
||||
std::string match_;
|
||||
WildcardFlags rules_;
|
||||
};
|
||||
|
||||
class WildcardEngine {
|
||||
public:
|
||||
void SetRule(const std::string_view pattern);
|
||||
|
||||
// Always ignoring case
|
||||
bool Match(const std::string_view str) const;
|
||||
|
||||
private:
|
||||
std::vector<WildcardRule> rules_;
|
||||
void PreparePattern(const std::string_view pattern);
|
||||
};
|
||||
|
||||
} // namespace filesystem
|
||||
} // namespace rex
|
||||
312
include/rex/graphics/command_processor.h
Normal file
312
include/rex/graphics/command_processor.h
Normal file
|
|
@ -0,0 +1,312 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2022 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*
|
||||
* @modified Tom Clay, 2026 - Adapted for ReXGlue runtime
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <cstring>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <queue>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include <rex/graphics/register_file.h>
|
||||
#include <rex/graphics/registers.h>
|
||||
#include <rex/graphics/trace_writer.h>
|
||||
#include <rex/graphics/xenos.h>
|
||||
#include <rex/memory.h>
|
||||
#include <rex/memory/ring_buffer.h>
|
||||
#include <rex/system/xthread.h>
|
||||
#include <rex/thread.h>
|
||||
#include <rex/ui/presenter.h>
|
||||
|
||||
namespace rex::stream {
|
||||
class ByteStream;
|
||||
} // namespace rex::stream
|
||||
|
||||
namespace rex::graphics {
|
||||
|
||||
class GraphicsSystem;
|
||||
class Shader;
|
||||
|
||||
enum class ReadbackResolveMode {
|
||||
kDisabled,
|
||||
kFast,
|
||||
kSome,
|
||||
kFull,
|
||||
};
|
||||
|
||||
struct SwapState {
|
||||
// Lock must be held when changing data in this structure.
|
||||
std::mutex mutex;
|
||||
// Dimensions of the framebuffer textures. Should match window size.
|
||||
uint32_t width = 0;
|
||||
uint32_t height = 0;
|
||||
// Current front buffer, being drawn to the screen.
|
||||
uintptr_t front_buffer_texture = 0;
|
||||
// Current back buffer, being updated by the CP.
|
||||
uintptr_t back_buffer_texture = 0;
|
||||
// Backend data
|
||||
void* backend_data = nullptr;
|
||||
// Whether the back buffer is dirty and a swap is pending.
|
||||
bool pending = false;
|
||||
};
|
||||
|
||||
enum class SwapMode {
|
||||
kNormal,
|
||||
kIgnored,
|
||||
};
|
||||
|
||||
enum class GammaRampType {
|
||||
kUnknown = 0,
|
||||
kTable,
|
||||
kPWL,
|
||||
};
|
||||
|
||||
class CommandProcessor {
|
||||
public:
|
||||
enum class SwapPostEffect {
|
||||
kNone,
|
||||
kFxaa,
|
||||
kFxaaExtreme,
|
||||
};
|
||||
|
||||
CommandProcessor(GraphicsSystem* graphics_system, system::KernelState* kernel_state);
|
||||
virtual ~CommandProcessor();
|
||||
|
||||
uint32_t counter() const { return counter_; }
|
||||
void increment_counter() { counter_++; }
|
||||
|
||||
Shader* active_vertex_shader() const { return active_vertex_shader_; }
|
||||
Shader* active_pixel_shader() const { return active_pixel_shader_; }
|
||||
|
||||
virtual bool Initialize();
|
||||
virtual void Shutdown();
|
||||
|
||||
void CallInThread(std::function<void()> fn);
|
||||
|
||||
virtual void ClearCaches();
|
||||
virtual void InvalidateGpuMemory();
|
||||
|
||||
// "Desired" is for the external thread managing the post-processing effect.
|
||||
SwapPostEffect GetDesiredSwapPostEffect() const { return swap_post_effect_desired_; }
|
||||
void SetDesiredSwapPostEffect(SwapPostEffect swap_post_effect);
|
||||
// Implementations must not make assumptions that the front buffer will
|
||||
// necessarily be a resolve destination - it may be a texture generated by any
|
||||
// means like written to by the CPU or loaded from a file (the disclaimer
|
||||
// screen right in the beginning of 4D530AA4 is not a resolved render target,
|
||||
// for instance).
|
||||
virtual void IssueSwap(uint32_t frontbuffer_ptr, uint32_t frontbuffer_width,
|
||||
uint32_t frontbuffer_height) = 0;
|
||||
|
||||
// May be called not only from the command processor thread when the command
|
||||
// processor is paused, and the termination of this function may be explicitly
|
||||
// awaited.
|
||||
virtual void InitializeShaderStorage(const std::filesystem::path& cache_root, uint32_t title_id,
|
||||
bool blocking);
|
||||
|
||||
virtual void RequestFrameTrace(const std::filesystem::path& root_path);
|
||||
virtual void BeginTracing(const std::filesystem::path& root_path);
|
||||
virtual void EndTracing();
|
||||
|
||||
virtual void TracePlaybackWroteMemory(uint32_t base_ptr, uint32_t length) = 0;
|
||||
|
||||
void RestoreRegisters(uint32_t first_register, const uint32_t* register_values,
|
||||
uint32_t register_count, bool execute_callbacks);
|
||||
void RestoreGammaRamp(const reg::DC_LUT_30_COLOR* new_gamma_ramp_256_entry_table,
|
||||
const reg::DC_LUT_PWL_DATA* new_gamma_ramp_pwl_rgb,
|
||||
uint32_t new_gamma_ramp_rw_component);
|
||||
virtual void RestoreEdramSnapshot(const void* snapshot) = 0;
|
||||
|
||||
void InitializeRingBuffer(uint32_t ptr, uint32_t size_log2);
|
||||
void EnableReadPointerWriteBack(uint32_t ptr, uint32_t block_size_log2);
|
||||
|
||||
void UpdateWritePointer(uint32_t value);
|
||||
|
||||
void ExecutePacket(uint32_t ptr, uint32_t count);
|
||||
|
||||
bool is_paused() const { return paused_; }
|
||||
void Pause();
|
||||
void Resume();
|
||||
|
||||
bool Save(::rex::stream::ByteStream* stream);
|
||||
bool Restore(::rex::stream::ByteStream* stream);
|
||||
|
||||
protected:
|
||||
struct IndexBufferInfo {
|
||||
xenos::IndexFormat format = xenos::IndexFormat::kInt16;
|
||||
xenos::Endian endianness = xenos::Endian::kNone;
|
||||
uint32_t count = 0;
|
||||
uint32_t guest_base = 0;
|
||||
size_t length = 0;
|
||||
};
|
||||
|
||||
void WorkerThreadMain();
|
||||
virtual bool SetupContext() = 0;
|
||||
virtual void ShutdownContext() = 0;
|
||||
|
||||
virtual void WriteRegister(uint32_t index, uint32_t value);
|
||||
uint32_t ReadRegisterValue(uint32_t index) const;
|
||||
virtual void WriteRegistersFromMem(uint32_t start_index, uint32_t* base, uint32_t num_registers);
|
||||
virtual void WriteRegisterRangeFromRing(memory::RingBuffer* ring, uint32_t base,
|
||||
uint32_t num_registers);
|
||||
void WriteALURangeFromRing(memory::RingBuffer* ring, uint32_t base, uint32_t num_registers);
|
||||
void WriteFetchRangeFromRing(memory::RingBuffer* ring, uint32_t base, uint32_t num_registers);
|
||||
void WriteBoolRangeFromRing(memory::RingBuffer* ring, uint32_t base, uint32_t num_registers);
|
||||
void WriteLoopRangeFromRing(memory::RingBuffer* ring, uint32_t base, uint32_t num_registers);
|
||||
void WriteREGISTERSRangeFromRing(memory::RingBuffer* ring, uint32_t base, uint32_t num_registers);
|
||||
void WriteALURangeFromMem(uint32_t start_index, uint32_t* base, uint32_t num_registers);
|
||||
void WriteFetchRangeFromMem(uint32_t start_index, uint32_t* base, uint32_t num_registers);
|
||||
void WriteBoolRangeFromMem(uint32_t start_index, uint32_t* base, uint32_t num_registers);
|
||||
void WriteLoopRangeFromMem(uint32_t start_index, uint32_t* base, uint32_t num_registers);
|
||||
void WriteREGISTERSRangeFromMem(uint32_t start_index, uint32_t* base, uint32_t num_registers);
|
||||
|
||||
const reg::DC_LUT_30_COLOR* gamma_ramp_256_entry_table() const {
|
||||
return gamma_ramp_256_entry_table_;
|
||||
}
|
||||
const reg::DC_LUT_PWL_DATA* gamma_ramp_pwl_rgb() const { return gamma_ramp_pwl_rgb_[0]; }
|
||||
virtual void OnGammaRamp256EntryTableValueWritten() {}
|
||||
virtual void OnGammaRampPWLValueWritten() {}
|
||||
|
||||
virtual void MakeCoherent();
|
||||
virtual void PrepareForWait();
|
||||
virtual void ReturnFromWait();
|
||||
|
||||
uint32_t ExecutePrimaryBuffer(uint32_t start_index, uint32_t end_index);
|
||||
virtual void OnPrimaryBufferEnd() {}
|
||||
void ExecuteIndirectBuffer(uint32_t ptr, uint32_t length);
|
||||
bool ExecutePacket(memory::RingBuffer* reader);
|
||||
bool ExecutePacketType0(memory::RingBuffer* reader, uint32_t packet);
|
||||
bool ExecutePacketType1(memory::RingBuffer* reader, uint32_t packet);
|
||||
bool ExecutePacketType2(memory::RingBuffer* reader, uint32_t packet);
|
||||
bool ExecutePacketType3(memory::RingBuffer* reader, uint32_t packet);
|
||||
bool ExecutePacketType3_ME_INIT(memory::RingBuffer* reader, uint32_t packet, uint32_t count);
|
||||
bool ExecutePacketType3_NOP(memory::RingBuffer* reader, uint32_t packet, uint32_t count);
|
||||
bool ExecutePacketType3_INTERRUPT(memory::RingBuffer* reader, uint32_t packet, uint32_t count);
|
||||
bool ExecutePacketType3_XE_SWAP(memory::RingBuffer* reader, uint32_t packet, uint32_t count);
|
||||
bool ExecutePacketType3_INDIRECT_BUFFER(memory::RingBuffer* reader, uint32_t packet,
|
||||
uint32_t count);
|
||||
bool ExecutePacketType3_WAIT_REG_MEM(memory::RingBuffer* reader, uint32_t packet, uint32_t count);
|
||||
bool ExecutePacketType3_REG_RMW(memory::RingBuffer* reader, uint32_t packet, uint32_t count);
|
||||
bool ExecutePacketType3_REG_TO_MEM(memory::RingBuffer* reader, uint32_t packet, uint32_t count);
|
||||
bool ExecutePacketType3_MEM_WRITE(memory::RingBuffer* reader, uint32_t packet, uint32_t count);
|
||||
bool ExecutePacketType3_COND_WRITE(memory::RingBuffer* reader, uint32_t packet, uint32_t count);
|
||||
bool ExecutePacketType3_EVENT_WRITE(memory::RingBuffer* reader, uint32_t packet, uint32_t count);
|
||||
bool ExecutePacketType3_EVENT_WRITE_SHD(memory::RingBuffer* reader, uint32_t packet,
|
||||
uint32_t count);
|
||||
bool ExecutePacketType3_EVENT_WRITE_EXT(memory::RingBuffer* reader, uint32_t packet,
|
||||
uint32_t count);
|
||||
virtual bool ExecutePacketType3_EVENT_WRITE_ZPD(memory::RingBuffer* reader, uint32_t packet,
|
||||
uint32_t count);
|
||||
bool ExecutePacketType3Draw(memory::RingBuffer* reader, uint32_t packet, const char* opcode_name,
|
||||
uint32_t viz_query_condition, uint32_t count_remaining);
|
||||
bool ExecutePacketType3_DRAW_INDX(memory::RingBuffer* reader, uint32_t packet, uint32_t count);
|
||||
bool ExecutePacketType3_DRAW_INDX_2(memory::RingBuffer* reader, uint32_t packet, uint32_t count);
|
||||
bool ExecutePacketType3_SET_CONSTANT(memory::RingBuffer* reader, uint32_t packet, uint32_t count);
|
||||
bool ExecutePacketType3_SET_CONSTANT2(memory::RingBuffer* reader, uint32_t packet,
|
||||
uint32_t count);
|
||||
bool ExecutePacketType3_LOAD_ALU_CONSTANT(memory::RingBuffer* reader, uint32_t packet,
|
||||
uint32_t count);
|
||||
bool ExecutePacketType3_SET_SHADER_CONSTANTS(memory::RingBuffer* reader, uint32_t packet,
|
||||
uint32_t count);
|
||||
bool ExecutePacketType3_IM_LOAD(memory::RingBuffer* reader, uint32_t packet, uint32_t count);
|
||||
bool ExecutePacketType3_IM_LOAD_IMMEDIATE(memory::RingBuffer* reader,
|
||||
|
||||
uint32_t packet, uint32_t count);
|
||||
bool ExecutePacketType3_INVALIDATE_STATE(memory::RingBuffer* reader, uint32_t packet,
|
||||
uint32_t count);
|
||||
bool ExecutePacketType3_VIZ_QUERY(memory::RingBuffer* reader, uint32_t packet, uint32_t count);
|
||||
|
||||
virtual Shader* LoadShader(xenos::ShaderType shader_type, uint32_t guest_address,
|
||||
const uint32_t* host_address, uint32_t dword_count) = 0;
|
||||
|
||||
virtual bool IssueDraw(xenos::PrimitiveType prim_type, uint32_t index_count,
|
||||
IndexBufferInfo* index_buffer_info, bool major_mode_explicit) = 0;
|
||||
virtual bool IssueCopy() = 0;
|
||||
|
||||
// "Actual" is for the command processor thread, to be read by the
|
||||
// implementations.
|
||||
SwapPostEffect GetActualSwapPostEffect() const { return swap_post_effect_actual_; }
|
||||
|
||||
virtual void InitializeTrace();
|
||||
|
||||
// Shared readback resolve mode with backend legacy-flag alias support.
|
||||
ReadbackResolveMode GetReadbackResolveMode(bool legacy_readback_resolve_enabled) const;
|
||||
// Shared memexport readback enable state with backend legacy-flag override support.
|
||||
bool IsReadbackMemexportEnabled(bool legacy_backend_flag) const;
|
||||
|
||||
memory::Memory* memory_ = nullptr;
|
||||
system::KernelState* kernel_state_ = nullptr;
|
||||
GraphicsSystem* graphics_system_ = nullptr;
|
||||
RegisterFile* register_file_ = nullptr;
|
||||
|
||||
TraceWriter trace_writer_;
|
||||
enum class TraceState {
|
||||
kDisabled,
|
||||
kStreaming,
|
||||
kSingleFrame,
|
||||
};
|
||||
TraceState trace_state_ = TraceState::kDisabled;
|
||||
std::filesystem::path trace_stream_path_;
|
||||
std::filesystem::path trace_frame_path_;
|
||||
|
||||
std::atomic<bool> worker_running_;
|
||||
system::object_ref<system::XHostThread> worker_thread_;
|
||||
|
||||
std::queue<std::function<void()>> pending_fns_;
|
||||
|
||||
// MicroEngine binary from PM4_ME_INIT
|
||||
std::vector<uint32_t> me_bin_;
|
||||
|
||||
uint32_t counter_ = 0;
|
||||
|
||||
uint32_t primary_buffer_ptr_ = 0;
|
||||
uint32_t primary_buffer_size_ = 0;
|
||||
|
||||
uint32_t read_ptr_index_ = 0;
|
||||
uint32_t read_ptr_update_freq_ = 0;
|
||||
uint32_t read_ptr_writeback_ptr_ = 0;
|
||||
|
||||
std::unique_ptr<rex::thread::Event> write_ptr_index_event_;
|
||||
std::atomic<uint32_t> write_ptr_index_;
|
||||
|
||||
// Some titles submit writes beyond the emulated register file range in PM4
|
||||
// packets. Preserve these values so dependent packet logic can still observe
|
||||
// them instead of dropping the write entirely.
|
||||
std::unordered_map<uint32_t, uint32_t> extended_register_values_;
|
||||
|
||||
uint64_t bin_select_ = 0xFFFFFFFFull;
|
||||
uint64_t bin_mask_ = 0xFFFFFFFFull;
|
||||
|
||||
Shader* active_vertex_shader_ = nullptr;
|
||||
Shader* active_pixel_shader_ = nullptr;
|
||||
|
||||
bool paused_ = false;
|
||||
|
||||
// By default (such as for tools), post-processing is disabled.
|
||||
// "Desired" is for the external thread managing the post-processing effect.
|
||||
SwapPostEffect swap_post_effect_desired_ = SwapPostEffect::kNone;
|
||||
SwapPostEffect swap_post_effect_actual_ = SwapPostEffect::kNone;
|
||||
|
||||
// Set by backend command processors to their legacy memexport readback cvar
|
||||
// name (for explicit-override compatibility).
|
||||
const char* legacy_readback_memexport_cvar_name_ = nullptr;
|
||||
|
||||
private:
|
||||
reg::DC_LUT_30_COLOR gamma_ramp_256_entry_table_[256] = {};
|
||||
reg::DC_LUT_PWL_DATA gamma_ramp_pwl_rgb_[128][3] = {};
|
||||
uint32_t gamma_ramp_rw_component_ = 0;
|
||||
};
|
||||
|
||||
} // namespace rex::graphics
|
||||
776
include/rex/graphics/d3d12/command_processor.h
Normal file
776
include/rex/graphics/d3d12/command_processor.h
Normal file
|
|
@ -0,0 +1,776 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2022 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*
|
||||
* @modified Tom Clay, 2026 - Adapted for ReXGlue runtime
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <deque>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <rex/assert.h>
|
||||
#include <rex/graphics/command_processor.h>
|
||||
#include <rex/graphics/d3d12/deferred_command_list.h>
|
||||
#include <rex/graphics/d3d12/graphics_system.h>
|
||||
#include <rex/graphics/d3d12/pipeline_cache.h>
|
||||
#include <rex/graphics/d3d12/primitive_processor.h>
|
||||
#include <rex/graphics/d3d12/render_target_cache.h>
|
||||
#include <rex/graphics/d3d12/shared_memory.h>
|
||||
#include <rex/graphics/d3d12/texture_cache.h>
|
||||
#include <rex/graphics/pipeline/shader/dxbc.h>
|
||||
#include <rex/graphics/pipeline/shader/dxbc_translator.h>
|
||||
#include <rex/graphics/registers.h>
|
||||
#include <rex/graphics/util/draw.h>
|
||||
#include <rex/graphics/xenos.h>
|
||||
#include <rex/system/kernel_state.h>
|
||||
#include <rex/ui/d3d12/d3d12_descriptor_heap_pool.h>
|
||||
#include <rex/ui/d3d12/d3d12_provider.h>
|
||||
#include <rex/ui/d3d12/d3d12_upload_buffer_pool.h>
|
||||
#include <rex/ui/d3d12/d3d12_util.h>
|
||||
|
||||
namespace rex::graphics::d3d12 {
|
||||
|
||||
class D3D12CommandProcessor : public CommandProcessor {
|
||||
public:
|
||||
explicit D3D12CommandProcessor(D3D12GraphicsSystem* graphics_system,
|
||||
system::KernelState* kernel_state);
|
||||
~D3D12CommandProcessor();
|
||||
|
||||
void ClearCaches() override;
|
||||
void InvalidateGpuMemory() override;
|
||||
|
||||
void InitializeShaderStorage(const std::filesystem::path& cache_root, uint32_t title_id,
|
||||
bool blocking) override;
|
||||
|
||||
void RequestFrameTrace(const std::filesystem::path& root_path) override;
|
||||
|
||||
void TracePlaybackWroteMemory(uint32_t base_ptr, uint32_t length) override;
|
||||
|
||||
void RestoreEdramSnapshot(const void* snapshot) override;
|
||||
|
||||
ui::d3d12::D3D12Provider& GetD3D12Provider() const {
|
||||
return *static_cast<ui::d3d12::D3D12Provider*>(graphics_system_->provider());
|
||||
}
|
||||
|
||||
// Returns the deferred drawing command list for the currently open
|
||||
// submission.
|
||||
DeferredCommandList& GetDeferredCommandList() {
|
||||
assert_true(submission_open_);
|
||||
return deferred_command_list_;
|
||||
}
|
||||
|
||||
uint64_t GetCurrentSubmission() const { return submission_current_; }
|
||||
uint64_t GetCompletedSubmission() const { return submission_completed_; }
|
||||
|
||||
// Must be called when a subsystem does something like UpdateTileMappings so
|
||||
// it can be awaited in CheckSubmissionFence(submission_current_) if it was
|
||||
// done after the latest ExecuteCommandLists + Signal.
|
||||
void NotifyQueueOperationsDoneDirectly() {
|
||||
queue_operations_done_since_submission_signal_ = true;
|
||||
}
|
||||
|
||||
uint64_t GetCurrentFrame() const { return frame_current_; }
|
||||
uint64_t GetCompletedFrame() const { return frame_completed_; }
|
||||
|
||||
// Returns true if the barrier has been inserted (the new state is different).
|
||||
bool PushTransitionBarrier(ID3D12Resource* resource, D3D12_RESOURCE_STATES old_state,
|
||||
D3D12_RESOURCE_STATES new_state,
|
||||
UINT subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES);
|
||||
void PushAliasingBarrier(ID3D12Resource* old_resource, ID3D12Resource* new_resource);
|
||||
void PushUAVBarrier(ID3D12Resource* resource);
|
||||
void SubmitBarriers();
|
||||
|
||||
// Finds or creates root signature for a pipeline.
|
||||
ID3D12RootSignature* GetRootSignature(const DxbcShader* vertex_shader,
|
||||
const DxbcShader* pixel_shader, bool tessellated);
|
||||
|
||||
ui::d3d12::D3D12UploadBufferPool& GetConstantBufferPool() const { return *constant_buffer_pool_; }
|
||||
|
||||
D3D12_CPU_DESCRIPTOR_HANDLE GetViewBindlessHeapCPUStart() const {
|
||||
assert_true(bindless_resources_used_);
|
||||
return view_bindless_heap_cpu_start_;
|
||||
}
|
||||
D3D12_GPU_DESCRIPTOR_HANDLE GetViewBindlessHeapGPUStart() const {
|
||||
assert_true(bindless_resources_used_);
|
||||
return view_bindless_heap_gpu_start_;
|
||||
}
|
||||
// Returns UINT32_MAX if no free descriptors. If the unbounded SRV range for
|
||||
// bindless resources is also used in the root signature of the draw /
|
||||
// dispatch referencing this descriptor, this must only be used to allocate
|
||||
// SRVs, otherwise it won't work on Nvidia Fermi (root signature creation will
|
||||
// fail)!
|
||||
uint32_t RequestPersistentViewBindlessDescriptor();
|
||||
void ReleaseViewBindlessDescriptorImmediately(uint32_t descriptor_index);
|
||||
// Request non-contiguous CBV/SRV/UAV descriptors for use only within the next
|
||||
// draw or dispatch command done for internal purposes. May change the current
|
||||
// descriptor heap. If the unbounded SRV range for bindless resources is also
|
||||
// used in the root signature of the draw / dispatch referencing these
|
||||
// descriptors, this must only be used to allocate SRVs, otherwise it won't
|
||||
// work on Nvidia Fermi (root signature creation will fail)!
|
||||
bool RequestOneUseSingleViewDescriptors(uint32_t count,
|
||||
ui::d3d12::util::DescriptorCpuGpuHandlePair* handles_out);
|
||||
// These are needed often, so they are always allocated.
|
||||
enum class SystemBindlessView : uint32_t {
|
||||
// Both may be bound as one root parameter.
|
||||
kSharedMemoryRawSRVAndNullRawUAVStart,
|
||||
kSharedMemoryRawSRV = kSharedMemoryRawSRVAndNullRawUAVStart,
|
||||
kNullRawUAV,
|
||||
|
||||
// Both may be bound as one root parameter.
|
||||
kNullRawSRVAndSharedMemoryRawUAVStart,
|
||||
kNullRawSRV = kNullRawSRVAndSharedMemoryRawUAVStart,
|
||||
kSharedMemoryRawUAV,
|
||||
|
||||
kSharedMemoryR32UintSRV,
|
||||
kSharedMemoryR32G32UintSRV,
|
||||
kSharedMemoryR32G32B32A32UintSRV,
|
||||
kSharedMemoryR32UintUAV,
|
||||
kSharedMemoryR32G32UintUAV,
|
||||
kSharedMemoryR32G32B32A32UintUAV,
|
||||
|
||||
kEdramRawSRV,
|
||||
kEdramR32UintSRV,
|
||||
kEdramR32G32UintSRV,
|
||||
kEdramR32G32B32A32UintSRV,
|
||||
kEdramRawUAV,
|
||||
kEdramR32UintUAV,
|
||||
kEdramR32G32UintUAV,
|
||||
kEdramR32G32B32A32UintUAV,
|
||||
|
||||
kGammaRampTableSRV,
|
||||
kGammaRampPWLSRV,
|
||||
|
||||
// Beyond this point, SRVs are accessible to shaders through an unbounded
|
||||
// range - no descriptors of other types bound to shaders alongside
|
||||
// unbounded ranges - must be located beyond this point.
|
||||
kUnboundedSRVsStart,
|
||||
kNullTexture2DArray = kUnboundedSRVsStart,
|
||||
kNullTexture3D,
|
||||
kNullTextureCube,
|
||||
|
||||
kCount,
|
||||
};
|
||||
ui::d3d12::util::DescriptorCpuGpuHandlePair GetSystemBindlessViewHandlePair(
|
||||
SystemBindlessView view) const;
|
||||
ui::d3d12::util::DescriptorCpuGpuHandlePair GetSharedMemoryUintPow2BindlessSRVHandlePair(
|
||||
uint32_t element_size_bytes_pow2) const;
|
||||
ui::d3d12::util::DescriptorCpuGpuHandlePair GetSharedMemoryUintPow2BindlessUAVHandlePair(
|
||||
uint32_t element_size_bytes_pow2) const;
|
||||
ui::d3d12::util::DescriptorCpuGpuHandlePair GetEdramUintPow2BindlessSRVHandlePair(
|
||||
uint32_t element_size_bytes_pow2) const;
|
||||
ui::d3d12::util::DescriptorCpuGpuHandlePair GetEdramUintPow2BindlessUAVHandlePair(
|
||||
uint32_t element_size_bytes_pow2) const;
|
||||
|
||||
// Returns a single temporary GPU-side buffer within a submission for tasks
|
||||
// like texture untiling and resolving.
|
||||
ID3D12Resource* RequestScratchGPUBuffer(uint32_t size, D3D12_RESOURCE_STATES state);
|
||||
// This must be called when done with the scratch buffer, to notify the
|
||||
// command processor about the new state in case the buffer was transitioned
|
||||
// by its user.
|
||||
void ReleaseScratchGPUBuffer(ID3D12Resource* buffer, D3D12_RESOURCE_STATES new_state);
|
||||
|
||||
// Returns a pipeline with deferred creation by its handle. May return nullptr
|
||||
// if failed to create the pipeline.
|
||||
ID3D12PipelineState* GetD3D12PipelineByHandle(void* handle) const {
|
||||
return pipeline_cache_->GetD3D12PipelineByHandle(handle);
|
||||
}
|
||||
|
||||
// Sets the current cached values to external ones. This is for cache
|
||||
// invalidation primarily. A submission must be open.
|
||||
void SetExternalPipeline(ID3D12PipelineState* pipeline);
|
||||
void SetExternalGraphicsRootSignature(ID3D12RootSignature* root_signature);
|
||||
void SetViewport(const D3D12_VIEWPORT& viewport);
|
||||
void SetScissorRect(const D3D12_RECT& scissor_rect);
|
||||
void SetStencilReference(uint32_t stencil_ref);
|
||||
void SetPrimitiveTopology(D3D12_PRIMITIVE_TOPOLOGY primitive_topology);
|
||||
|
||||
// Returns the text to display in the GPU backend name in the window title.
|
||||
std::string GetWindowTitleText() const;
|
||||
|
||||
protected:
|
||||
bool SetupContext() override;
|
||||
void ShutdownContext() override;
|
||||
|
||||
void WriteRegister(uint32_t index, uint32_t value) override;
|
||||
void WriteRegistersFromMem(uint32_t start_index, uint32_t* base, uint32_t num_registers) override;
|
||||
bool ExecutePacketType3_EVENT_WRITE_ZPD(memory::RingBuffer* reader, uint32_t packet,
|
||||
uint32_t count) override;
|
||||
|
||||
void OnGammaRamp256EntryTableValueWritten() override;
|
||||
void OnGammaRampPWLValueWritten() override;
|
||||
|
||||
void IssueSwap(uint32_t frontbuffer_ptr, uint32_t frontbuffer_width,
|
||||
uint32_t frontbuffer_height) override;
|
||||
|
||||
void OnPrimaryBufferEnd() override;
|
||||
|
||||
Shader* LoadShader(xenos::ShaderType shader_type, uint32_t guest_address,
|
||||
const uint32_t* host_address, uint32_t dword_count) override;
|
||||
|
||||
bool IssueDraw(xenos::PrimitiveType primitive_type, uint32_t index_count,
|
||||
IndexBufferInfo* index_buffer_info, bool major_mode_explicit) override;
|
||||
bool IssueCopy() override;
|
||||
|
||||
void InitializeTrace() override;
|
||||
|
||||
private:
|
||||
static constexpr uint32_t kQueueFrames = 3;
|
||||
|
||||
enum RootParameter : UINT {
|
||||
// Keep the size of the root signature at each stage 13 dwords or less
|
||||
// (better 12 or less) so it fits in user data on AMD. Descriptor tables are
|
||||
// 1 dword, root descriptors are 2 dwords (however, root descriptors require
|
||||
// less setup on the CPU - balance needs to be maintained).
|
||||
|
||||
// CBVs are set in both bindful and bindless cases via root descriptors.
|
||||
|
||||
// - Bindful resources - multiple root signatures depending on extra
|
||||
// parameters.
|
||||
|
||||
// These are always present.
|
||||
|
||||
// Very frequently changed, especially for UI draws, and for models drawn in
|
||||
// multiple parts - contains vertex and texture fetch constants.
|
||||
kRootParameter_Bindful_FetchConstants = 0, // +2 dwords = 2 in all.
|
||||
// Quite frequently changed (for one object drawn multiple times, for
|
||||
// instance - may contain projection matrices).
|
||||
kRootParameter_Bindful_FloatConstantsVertex, // +2 = 4 in VS.
|
||||
// Less frequently changed (per-material).
|
||||
kRootParameter_Bindful_FloatConstantsPixel, // +2 = 4 in PS.
|
||||
// May stay the same across many draws.
|
||||
kRootParameter_Bindful_SystemConstants, // +2 = 6 in all.
|
||||
// Pretty rarely used and rarely changed - flow control constants.
|
||||
kRootParameter_Bindful_BoolLoopConstants, // +2 = 8 in all.
|
||||
// Changed only when starting a new descriptor heap or when switching
|
||||
// between shared memory as SRV and UAV - shared memory byte address buffer
|
||||
// (as SRV and as UAV, either may be null if not used), and, if ROV is used
|
||||
// for EDRAM, EDRAM R32_UINT UAV.
|
||||
kRootParameter_Bindful_SharedMemoryAndEdram, // +1 = 9 in all.
|
||||
|
||||
kRootParameter_Bindful_Count_Base,
|
||||
|
||||
// Extra parameter that may or may not exist:
|
||||
// - Pixel textures (+1 = 10 in PS).
|
||||
// - Pixel samplers (+1 = 11 in PS).
|
||||
// - Vertex textures (+1 = 10 in VS).
|
||||
// - Vertex samplers (+1 = 11 in VS).
|
||||
|
||||
kRootParameter_Bindful_Count_Max = kRootParameter_Bindful_Count_Base + 4,
|
||||
|
||||
// - Bindless resources - two global root signatures (for non-tessellated
|
||||
// and tessellated drawing), so these are always present.
|
||||
|
||||
kRootParameter_Bindless_FetchConstants = 0, // +2 = 2 in all.
|
||||
kRootParameter_Bindless_FloatConstantsVertex, // +2 = 4 in VS.
|
||||
kRootParameter_Bindless_FloatConstantsPixel, // +2 = 4 in PS.
|
||||
// Changed per-material, texture and sampler descriptor indices.
|
||||
kRootParameter_Bindless_DescriptorIndicesPixel, // +2 = 6 in PS.
|
||||
kRootParameter_Bindless_DescriptorIndicesVertex, // +2 = 6 in VS.
|
||||
kRootParameter_Bindless_SystemConstants, // +2 = 8 in all.
|
||||
kRootParameter_Bindless_BoolLoopConstants, // +2 = 10 in all.
|
||||
// Changed only when switching between shared memory as SRV and UAV - shared
|
||||
// memory byte address buffer (as SRV and as UAV, either may be null if not
|
||||
// used).
|
||||
kRootParameter_Bindless_SharedMemory, // +1 = 11 in all.
|
||||
// Unbounded sampler descriptor table - changed in case of overflow.
|
||||
kRootParameter_Bindless_SamplerHeap, // +1 = 12 in all.
|
||||
// Unbounded SRV/UAV descriptor table - never changed.
|
||||
kRootParameter_Bindless_ViewHeap, // +1 = 13 in all.
|
||||
|
||||
kRootParameter_Bindless_Count,
|
||||
};
|
||||
|
||||
struct RootBindfulExtraParameterIndices {
|
||||
uint32_t textures_pixel;
|
||||
uint32_t samplers_pixel;
|
||||
uint32_t textures_vertex;
|
||||
uint32_t samplers_vertex;
|
||||
static constexpr uint32_t kUnavailable = UINT32_MAX;
|
||||
};
|
||||
// Gets the indices of optional root parameters. Returns the total parameter
|
||||
// count.
|
||||
static uint32_t GetRootBindfulExtraParameterIndices(
|
||||
const DxbcShader* vertex_shader, const DxbcShader* pixel_shader,
|
||||
RootBindfulExtraParameterIndices& indices_out);
|
||||
|
||||
// BeginSubmission and EndSubmission may be called at any time. If there's an
|
||||
// open non-frame submission, BeginSubmission(true) will promote it to a
|
||||
// frame. EndSubmission(true) will close the frame no matter whether the
|
||||
// submission has already been closed.
|
||||
// Submission (ExecuteCommandLists) boundaries are implicit full UAV and
|
||||
// aliasing barriers, and also result in common resource state promotion and
|
||||
// decay.
|
||||
|
||||
// Rechecks submission number and reclaims per-submission resources. Pass 0 as
|
||||
// the submission to await to simply check status, or pass submission_current_
|
||||
// to wait for all queue operations to be completed.
|
||||
void CheckSubmissionFence(uint64_t await_submission);
|
||||
// If is_guest_command is true, a new full frame - with full cleanup of
|
||||
// resources and, if needed, starting capturing - is opened if pending (as
|
||||
// opposed to simply resuming after mid-frame synchronization). Returns
|
||||
// whether a submission is open currently and the device is not removed.
|
||||
bool BeginSubmission(bool is_guest_command);
|
||||
// If is_swap is true, a full frame is closed - with, if needed, cache
|
||||
// clearing and stopping capturing. Returns whether the submission was done
|
||||
// successfully, if it has failed, leaves it open.
|
||||
bool EndSubmission(bool is_swap);
|
||||
// Checks if ending a submission right now would not cause potentially more
|
||||
// delay than it would reduce by making the GPU start working earlier - such
|
||||
// as when there are unfinished graphics pipeline creation requests that would
|
||||
// need to be fulfilled before actually submitting the command list.
|
||||
bool CanEndSubmissionImmediately() const;
|
||||
bool AwaitAllQueueOperationsCompletion() {
|
||||
CheckSubmissionFence(submission_current_);
|
||||
return submission_completed_ + 1 >= submission_current_;
|
||||
}
|
||||
void LogDeviceRemovalDiagnostics(ID3D12Device* device, HRESULT reason);
|
||||
|
||||
void UpdateDebugMarkersEnabled();
|
||||
void PushDebugMarker(const char* format, ...);
|
||||
void PopDebugMarker();
|
||||
void InsertDebugMarker(const char* format, ...);
|
||||
bool debug_markers_enabled() const { return debug_markers_enabled_; }
|
||||
|
||||
// Need to await submission completion before calling.
|
||||
void ClearCommandAllocatorCache();
|
||||
|
||||
// Request descriptors and automatically rebind the descriptor heap on the
|
||||
// draw command list. Refer to D3D12DescriptorHeapPool::Request for partial /
|
||||
// full update explanation. Doesn't work when bindless descriptors are used.
|
||||
uint64_t RequestViewBindfulDescriptors(uint64_t previous_heap_index,
|
||||
uint32_t count_for_partial_update,
|
||||
uint32_t count_for_full_update,
|
||||
D3D12_CPU_DESCRIPTOR_HANDLE& cpu_handle_out,
|
||||
D3D12_GPU_DESCRIPTOR_HANDLE& gpu_handle_out);
|
||||
uint64_t RequestSamplerBindfulDescriptors(uint64_t previous_heap_index,
|
||||
uint32_t count_for_partial_update,
|
||||
uint32_t count_for_full_update,
|
||||
D3D12_CPU_DESCRIPTOR_HANDLE& cpu_handle_out,
|
||||
D3D12_GPU_DESCRIPTOR_HANDLE& gpu_handle_out);
|
||||
|
||||
void UpdateFixedFunctionState(const draw_util::ViewportInfo& viewport_info,
|
||||
const draw_util::Scissor& scissor, bool primitive_polygonal,
|
||||
reg::RB_DEPTHCONTROL normalized_depth_control);
|
||||
void UpdateSystemConstantValues(bool shared_memory_is_uav, bool primitive_polygonal,
|
||||
uint32_t line_loop_closing_index, xenos::Endian index_endian,
|
||||
const draw_util::ViewportInfo& viewport_info,
|
||||
uint32_t used_texture_mask,
|
||||
reg::RB_DEPTHCONTROL normalized_depth_control,
|
||||
uint32_t normalized_color_mask);
|
||||
bool UpdateBindings(const D3D12Shader* vertex_shader, const D3D12Shader* pixel_shader,
|
||||
ID3D12RootSignature* root_signature, bool shared_memory_is_uav);
|
||||
bool IssueCopy_ReadbackResolvePath();
|
||||
bool IssueDraw_MemexportReadbackFullPath(uint32_t total_size);
|
||||
bool IssueDraw_MemexportReadbackFastPath(uint32_t total_size);
|
||||
|
||||
// Returns a buffer for reading GPU data back to the CPU. Assuming
|
||||
// synchronizing immediately after use. Always in COPY_DEST state.
|
||||
ID3D12Resource* RequestReadbackBuffer(uint32_t size);
|
||||
struct ReadbackBuffer {
|
||||
ID3D12Resource* buffers[2] = {nullptr, nullptr};
|
||||
uint32_t sizes[2] = {0, 0};
|
||||
void* mapped_data[2] = {nullptr, nullptr};
|
||||
uint64_t submission_written[2] = {0, 0};
|
||||
uint32_t written_size[2] = {0, 0};
|
||||
uint32_t current_index = 0;
|
||||
uint64_t last_used_frame = 0;
|
||||
};
|
||||
void EvictOldReadbackBuffers(std::unordered_map<uint64_t, ReadbackBuffer>& buffer_map);
|
||||
static constexpr uint32_t kReadbackBufferSizeIncrement = 16 * 1024 * 1024;
|
||||
static constexpr size_t kMaxReadbackBuffers = 256;
|
||||
static constexpr uint64_t kReadbackBufferEvictionAgeFrames = 60;
|
||||
static inline uint32_t AlignReadbackBufferSize(uint32_t size) {
|
||||
if (size < 1 * 1024 * 1024) {
|
||||
return rex::align(size, 256u * 1024u);
|
||||
}
|
||||
if (size < 4 * 1024 * 1024) {
|
||||
return rex::align(size, 1u * 1024u * 1024u);
|
||||
}
|
||||
return rex::align(size, kReadbackBufferSizeIncrement);
|
||||
}
|
||||
static inline uint64_t MakeReadbackResolveKey(uint32_t address, uint32_t length) {
|
||||
return (uint64_t(address) << 32) | uint64_t(length);
|
||||
}
|
||||
static inline uint64_t MakeMemexportReadbackKey(uint32_t first_base_address_dwords,
|
||||
uint32_t total_size) {
|
||||
return (uint64_t(first_base_address_dwords) << 32) | uint64_t(total_size);
|
||||
}
|
||||
|
||||
bool InitializeOcclusionQueryResources();
|
||||
void ShutdownOcclusionQueryResources();
|
||||
bool BeginGuestOcclusionQuery(uint32_t sample_count_address);
|
||||
bool EndGuestOcclusionQuery(uint32_t sample_count_address,
|
||||
xenos::xe_gpu_depth_sample_counts* sample_counts);
|
||||
bool AcquireOcclusionQueryIndex(uint32_t& host_index_out);
|
||||
void DisableHostOcclusionQueries();
|
||||
uint64_t NormalizeOcclusionSamples(uint64_t samples) const;
|
||||
void WriteGuestOcclusionResult(xenos::xe_gpu_depth_sample_counts* sample_counts,
|
||||
uint64_t samples);
|
||||
void InvalidateAllVertexBufferResidency();
|
||||
void InvalidateVertexBufferResidency(uint32_t vfetch_index);
|
||||
void InvalidateVertexBufferResidencyRange(uint32_t first_vfetch, uint32_t last_vfetch);
|
||||
|
||||
void WriteGammaRampSRV(bool is_pwl, D3D12_CPU_DESCRIPTOR_HANDLE handle) const;
|
||||
|
||||
bool device_removed_ = false;
|
||||
|
||||
bool cache_clear_requested_ = false;
|
||||
|
||||
HANDLE fence_completion_event_ = nullptr;
|
||||
|
||||
bool submission_open_ = false;
|
||||
// Values of submission_fence_.
|
||||
uint64_t submission_current_ = 1;
|
||||
uint64_t submission_completed_ = 0;
|
||||
ID3D12Fence* submission_fence_ = nullptr;
|
||||
|
||||
// For awaiting non-submission queue operations such as UpdateTileMappings in
|
||||
// AwaitAllQueueOperationsCompletion when they're queued after the latest
|
||||
// ExecuteCommandLists + Signal, thus won't be awaited by just awaiting the
|
||||
// submission.
|
||||
ID3D12Fence* queue_operations_since_submission_fence_ = nullptr;
|
||||
uint64_t queue_operations_since_submission_fence_last_ = 0;
|
||||
bool queue_operations_done_since_submission_signal_ = false;
|
||||
|
||||
bool frame_open_ = false;
|
||||
// Guest frame index, since some transient resources can be reused across
|
||||
// submissions. Values updated in the beginning of a frame.
|
||||
uint64_t frame_current_ = 1;
|
||||
uint64_t frame_completed_ = 0;
|
||||
// Submission indices of frames that have already been submitted.
|
||||
uint64_t closed_frame_submissions_[kQueueFrames] = {};
|
||||
|
||||
struct CommandAllocator {
|
||||
ID3D12CommandAllocator* command_allocator;
|
||||
uint64_t last_usage_submission;
|
||||
CommandAllocator* next;
|
||||
};
|
||||
CommandAllocator* command_allocator_writable_first_ = nullptr;
|
||||
CommandAllocator* command_allocator_writable_last_ = nullptr;
|
||||
CommandAllocator* command_allocator_submitted_first_ = nullptr;
|
||||
CommandAllocator* command_allocator_submitted_last_ = nullptr;
|
||||
ID3D12GraphicsCommandList* command_list_ = nullptr;
|
||||
ID3D12GraphicsCommandList1* command_list_1_ = nullptr;
|
||||
DeferredCommandList deferred_command_list_;
|
||||
|
||||
bool debug_markers_enabled_ = false;
|
||||
|
||||
// Viewport info caching - avoids redundant GetHostViewportInfo recalculation
|
||||
// when viewport-affecting register state hasn't changed between draws.
|
||||
struct ViewportCacheKey {
|
||||
uint32_t pa_cl_clip_cntl;
|
||||
uint32_t pa_cl_vte_cntl;
|
||||
uint32_t pa_su_sc_mode_cntl;
|
||||
uint32_t pa_su_vtx_cntl;
|
||||
uint32_t pa_sc_window_offset;
|
||||
uint32_t normalized_depth_control;
|
||||
uint32_t vport_regs[6]; // XSCALE, XOFFSET, YSCALE, YOFFSET, ZSCALE, ZOFFSET
|
||||
uint32_t flags; // packed: convert_z_to_float24, full_float24, ps_writes_depth
|
||||
bool operator==(const ViewportCacheKey&) const = default;
|
||||
};
|
||||
ViewportCacheKey previous_viewport_key_{};
|
||||
draw_util::ViewportInfo previous_viewport_info_{};
|
||||
bool viewport_cache_valid_ = false;
|
||||
|
||||
// Should bindless textures and samplers be used - many times faster
|
||||
// UpdateBindings than bindful (that becomes a significant bottleneck with
|
||||
// bindful - mainly because of CopyDescriptorsSimple, which takes the majority
|
||||
// of UpdateBindings time, and that's outside the emulator's control even).
|
||||
bool bindless_resources_used_ = false;
|
||||
|
||||
std::unique_ptr<D3D12SharedMemory> shared_memory_;
|
||||
|
||||
std::unique_ptr<D3D12RenderTargetCache> render_target_cache_;
|
||||
|
||||
std::unique_ptr<ui::d3d12::D3D12UploadBufferPool> constant_buffer_pool_;
|
||||
|
||||
static constexpr uint32_t kViewBindfulHeapSize = 32768;
|
||||
static_assert(kViewBindfulHeapSize <= D3D12_MAX_SHADER_VISIBLE_DESCRIPTOR_HEAP_SIZE_TIER_1);
|
||||
std::unique_ptr<ui::d3d12::D3D12DescriptorHeapPool> view_bindful_heap_pool_;
|
||||
// Currently bound descriptor heap - updated by RequestViewBindfulDescriptors.
|
||||
ID3D12DescriptorHeap* view_bindful_heap_current_;
|
||||
// Rationale: textures have 4 KB alignment in guest memory, and there can be
|
||||
// 512 MB / 4 KB in total of them at most, and multiply by 3 for different
|
||||
// swizzles, signedness, and multiple host textures for one guest texture, and
|
||||
// transient descriptors. Though in reality there will be a lot fewer of
|
||||
// course, this is just a "safe" value. The limit is 1000000 for resource
|
||||
// binding tier 2.
|
||||
static constexpr uint32_t kViewBindlessHeapSize = 262144;
|
||||
static_assert(kViewBindlessHeapSize <= D3D12_MAX_SHADER_VISIBLE_DESCRIPTOR_HEAP_SIZE_TIER_2);
|
||||
ID3D12DescriptorHeap* view_bindless_heap_ = nullptr;
|
||||
D3D12_CPU_DESCRIPTOR_HANDLE view_bindless_heap_cpu_start_;
|
||||
D3D12_GPU_DESCRIPTOR_HANDLE view_bindless_heap_gpu_start_;
|
||||
uint32_t view_bindless_heap_allocated_ = 0;
|
||||
std::vector<uint32_t> view_bindless_heap_free_;
|
||||
// <Descriptor index, submission where requested>, sorted by the submission
|
||||
// number.
|
||||
std::deque<std::pair<uint32_t, uint64_t>> view_bindless_one_use_descriptors_;
|
||||
|
||||
// Direct3D 12 only allows shader-visible heaps with no more than 2048
|
||||
// samplers (due to Nvidia addressing). However, there's also possibly a weird
|
||||
// bug in the Nvidia driver (tested on 440.97 and earlier on Windows 10 1803)
|
||||
// that caused the sampler with index 2047 not to work if a heap with 8 or
|
||||
// less samplers also exists - in case of Xenia, it's the immediate drawer's
|
||||
// sampler heap.
|
||||
// FIXME(Triang3l): Investigate the issue with the sampler 2047 on Nvidia.
|
||||
static constexpr uint32_t kSamplerHeapSize = 2000;
|
||||
static_assert(kSamplerHeapSize <= D3D12_MAX_SHADER_VISIBLE_SAMPLER_HEAP_SIZE);
|
||||
std::unique_ptr<ui::d3d12::D3D12DescriptorHeapPool> sampler_bindful_heap_pool_;
|
||||
ID3D12DescriptorHeap* sampler_bindful_heap_current_;
|
||||
ID3D12DescriptorHeap* sampler_bindless_heap_current_ = nullptr;
|
||||
D3D12_CPU_DESCRIPTOR_HANDLE sampler_bindless_heap_cpu_start_;
|
||||
D3D12_GPU_DESCRIPTOR_HANDLE sampler_bindless_heap_gpu_start_;
|
||||
// Currently the sampler heap is used only for texture cache samplers, so
|
||||
// individual samplers are never freed, and using a simple linear allocator
|
||||
// inside the current heap without a free list.
|
||||
uint32_t sampler_bindless_heap_allocated_ = 0;
|
||||
// <Heap, overflow submission number>, if total sampler count used so far
|
||||
// exceeds kSamplerHeapSize, and the heap has been switched (this is not a
|
||||
// totally impossible situation considering Direct3D 9 has sampler parameter
|
||||
// state instead of sampler objects, and having one "unimportant" parameter
|
||||
// changed may result in doubling of sampler count). Sorted by the submission
|
||||
// number (so checking if the first can be reused is enough).
|
||||
std::deque<std::pair<ID3D12DescriptorHeap*, uint64_t>> sampler_bindless_heaps_overflowed_;
|
||||
// D3D12TextureCache::SamplerParameters::value -> indices within the current
|
||||
// bindless sampler heap.
|
||||
std::unordered_map<uint32_t, uint32_t> texture_cache_bindless_sampler_map_;
|
||||
|
||||
// Root signatures for different descriptor counts.
|
||||
std::unordered_map<uint32_t, ID3D12RootSignature*> root_signatures_bindful_;
|
||||
ID3D12RootSignature* root_signature_bindless_vs_ = nullptr;
|
||||
ID3D12RootSignature* root_signature_bindless_ds_ = nullptr;
|
||||
|
||||
std::unique_ptr<D3D12PrimitiveProcessor> primitive_processor_;
|
||||
|
||||
std::unique_ptr<PipelineCache> pipeline_cache_;
|
||||
|
||||
std::unique_ptr<D3D12TextureCache> texture_cache_;
|
||||
|
||||
// Bytes 0x0...0x3FF - 256-entry gamma ramp table with B10G10R10X2 data (read
|
||||
// as R10G10B10X2 with swizzle).
|
||||
// Bytes 0x400...0x9FF - 128-entry PWL R16G16 gamma ramp (R - base, G - delta,
|
||||
// low 6 bits of each are zero, 3 elements per entry).
|
||||
Microsoft::WRL::ComPtr<ID3D12Resource> gamma_ramp_buffer_;
|
||||
D3D12_RESOURCE_STATES gamma_ramp_buffer_state_;
|
||||
// Upload buffer for an image that is the same as gamma_ramp_, but with
|
||||
// kQueueFrames array layers.
|
||||
Microsoft::WRL::ComPtr<ID3D12Resource> gamma_ramp_upload_buffer_;
|
||||
uint8_t* gamma_ramp_upload_buffer_mapping_ = nullptr;
|
||||
bool gamma_ramp_256_entry_table_up_to_date_ = false;
|
||||
bool gamma_ramp_pwl_up_to_date_ = false;
|
||||
|
||||
struct ApplyGammaConstants {
|
||||
uint32_t size[2];
|
||||
};
|
||||
enum class ApplyGammaRootParameter : UINT {
|
||||
kConstants,
|
||||
kDestination,
|
||||
kSource,
|
||||
kRamp,
|
||||
|
||||
kCount,
|
||||
};
|
||||
Microsoft::WRL::ComPtr<ID3D12RootSignature> apply_gamma_root_signature_;
|
||||
Microsoft::WRL::ComPtr<ID3D12PipelineState> apply_gamma_table_pipeline_;
|
||||
Microsoft::WRL::ComPtr<ID3D12PipelineState> apply_gamma_table_fxaa_luma_pipeline_;
|
||||
Microsoft::WRL::ComPtr<ID3D12PipelineState> apply_gamma_pwl_pipeline_;
|
||||
Microsoft::WRL::ComPtr<ID3D12PipelineState> apply_gamma_pwl_fxaa_luma_pipeline_;
|
||||
|
||||
struct FxaaConstants {
|
||||
uint32_t size[2];
|
||||
float size_inv[2];
|
||||
};
|
||||
enum class FxaaRootParameter : UINT {
|
||||
kConstants,
|
||||
kDestination,
|
||||
kSource,
|
||||
|
||||
kCount,
|
||||
};
|
||||
Microsoft::WRL::ComPtr<ID3D12RootSignature> fxaa_root_signature_;
|
||||
Microsoft::WRL::ComPtr<ID3D12PipelineState> fxaa_pipeline_;
|
||||
Microsoft::WRL::ComPtr<ID3D12PipelineState> fxaa_extreme_pipeline_;
|
||||
|
||||
struct ResolveDownscaleConstants {
|
||||
uint32_t scale_x;
|
||||
uint32_t scale_y;
|
||||
uint32_t pixel_size_log2;
|
||||
uint32_t tile_count;
|
||||
uint32_t half_pixel_offset;
|
||||
};
|
||||
enum class ResolveDownscaleRootParameter : UINT {
|
||||
kConstants,
|
||||
kSource,
|
||||
kDestination,
|
||||
|
||||
kCount,
|
||||
};
|
||||
Microsoft::WRL::ComPtr<ID3D12RootSignature> resolve_downscale_root_signature_;
|
||||
Microsoft::WRL::ComPtr<ID3D12PipelineState> resolve_downscale_pipeline_;
|
||||
Microsoft::WRL::ComPtr<ID3D12Resource> resolve_downscale_buffer_;
|
||||
uint32_t resolve_downscale_buffer_size_ = 0;
|
||||
|
||||
// PWL gamma ramp can result in values with more precision than 10bpc. Though
|
||||
// those sub-10bpc bits don't have any noticeable visual effect, so normally
|
||||
// R10G10B10A2_UNORM is enough. But what's the most important is that for the
|
||||
// original FXAA shader, the luma needs to be written to the alpha channel.
|
||||
// For simplicity (to avoid modifying the FXAA shader and adding more texture
|
||||
// fetches into it), and for the highest quality (preserving all 13 bits that
|
||||
// may be generated by applying the PWL gamma ramp with an increment of 2^3,
|
||||
// and also leaving some space for the result of applying fractional weights
|
||||
// to calculate the luma), using R16G16B16A16_UNORM instead of
|
||||
// R10G10B10X2_UNORM with a separate alpha texture.
|
||||
static constexpr DXGI_FORMAT kFxaaSourceTextureFormat = DXGI_FORMAT_R16G16B16A16_UNORM;
|
||||
// Kept in NON_PIXEL_SHADER_RESOURCE state.
|
||||
Microsoft::WRL::ComPtr<ID3D12Resource> fxaa_source_texture_;
|
||||
uint64_t fxaa_source_texture_submission_ = 0;
|
||||
|
||||
// Unsubmitted barrier batch.
|
||||
std::vector<D3D12_RESOURCE_BARRIER> barriers_;
|
||||
|
||||
// <Submission where requested, resource>, sorted by the submission number.
|
||||
std::deque<std::pair<uint64_t, ID3D12Resource*>> resources_for_deletion_;
|
||||
|
||||
static constexpr uint32_t kScratchBufferSizeIncrement = 16 * 1024 * 1024;
|
||||
ID3D12Resource* scratch_buffer_ = nullptr;
|
||||
uint32_t scratch_buffer_size_ = 0;
|
||||
D3D12_RESOURCE_STATES scratch_buffer_state_;
|
||||
bool scratch_buffer_used_ = false;
|
||||
|
||||
ID3D12Resource* readback_buffer_ = nullptr;
|
||||
uint32_t readback_buffer_size_ = 0;
|
||||
std::unordered_map<uint64_t, ReadbackBuffer> readback_buffers_;
|
||||
std::unordered_map<uint64_t, ReadbackBuffer> memexport_readback_buffers_;
|
||||
|
||||
static constexpr uint32_t kMaxOcclusionQueries = 8192;
|
||||
Microsoft::WRL::ComPtr<ID3D12QueryHeap> occlusion_query_heap_;
|
||||
Microsoft::WRL::ComPtr<ID3D12Resource> occlusion_query_readback_;
|
||||
uint64_t* occlusion_query_readback_mapping_ = nullptr;
|
||||
uint32_t occlusion_query_cursor_ = 0;
|
||||
bool occlusion_query_resources_available_ = false;
|
||||
struct ActiveOcclusionQuery {
|
||||
uint32_t sample_count_address = 0;
|
||||
uint32_t host_index = UINT32_MAX;
|
||||
bool valid = false;
|
||||
} active_occlusion_query_;
|
||||
struct VertexBufferState {
|
||||
uint32_t address = UINT32_MAX;
|
||||
uint32_t size = UINT32_MAX;
|
||||
};
|
||||
std::array<VertexBufferState, 96> vertex_buffer_states_{};
|
||||
uint64_t vertex_buffers_in_sync_[2] = {};
|
||||
|
||||
std::atomic<bool> pix_capture_requested_ = false;
|
||||
bool pix_capturing_;
|
||||
|
||||
// The current fixed-function drawing state.
|
||||
D3D12_VIEWPORT ff_viewport_;
|
||||
D3D12_RECT ff_scissor_;
|
||||
float ff_blend_factor_[4];
|
||||
uint32_t ff_stencil_ref_;
|
||||
bool ff_viewport_update_needed_;
|
||||
bool ff_scissor_update_needed_;
|
||||
bool ff_blend_factor_update_needed_;
|
||||
bool ff_stencil_ref_update_needed_;
|
||||
|
||||
// Currently bound pipeline, either a graphics pipeline from the pipeline
|
||||
// cache (with potentially deferred creation - current_external_pipeline_ is
|
||||
// nullptr in this case) or a non-Xenos graphics or compute pipeline
|
||||
// (current_guest_pipeline_ is nullptr in this case).
|
||||
void* current_guest_pipeline_;
|
||||
ID3D12PipelineState* current_external_pipeline_;
|
||||
|
||||
// Currently bound graphics root signature.
|
||||
ID3D12RootSignature* current_graphics_root_signature_;
|
||||
// Extra parameters which may or may not be present.
|
||||
RootBindfulExtraParameterIndices current_graphics_root_bindful_extras_;
|
||||
// Whether root parameters are up to date - reset if a new signature is bound.
|
||||
uint32_t current_graphics_root_up_to_date_;
|
||||
|
||||
// System shader constants.
|
||||
DxbcShaderTranslator::SystemConstants system_constants_;
|
||||
|
||||
// Float constant usage masks of the last draw call.
|
||||
uint64_t current_float_constant_map_vertex_[4];
|
||||
uint64_t current_float_constant_map_pixel_[4];
|
||||
|
||||
// Constant buffer bindings.
|
||||
struct ConstantBufferBinding {
|
||||
D3D12_GPU_VIRTUAL_ADDRESS address;
|
||||
bool up_to_date;
|
||||
};
|
||||
ConstantBufferBinding cbuffer_binding_system_;
|
||||
ConstantBufferBinding cbuffer_binding_float_vertex_;
|
||||
ConstantBufferBinding cbuffer_binding_float_pixel_;
|
||||
ConstantBufferBinding cbuffer_binding_bool_loop_;
|
||||
ConstantBufferBinding cbuffer_binding_fetch_;
|
||||
ConstantBufferBinding cbuffer_binding_descriptor_indices_vertex_;
|
||||
ConstantBufferBinding cbuffer_binding_descriptor_indices_pixel_;
|
||||
|
||||
// Whether the latest shared memory and EDRAM buffer binding contains the
|
||||
// shared memory UAV rather than the SRV.
|
||||
// Separate descriptor tables for the SRV and the UAV, even though only one is
|
||||
// accessed dynamically in the shaders, are used to prevent a validation
|
||||
// message about missing resource states in PIX.
|
||||
std::optional<bool> current_shared_memory_binding_is_uav_;
|
||||
|
||||
// Pages with the descriptors currently used for handling Xenos draw calls.
|
||||
uint64_t draw_view_bindful_heap_index_;
|
||||
uint64_t draw_sampler_bindful_heap_index_;
|
||||
|
||||
// Whether the last used texture sampler bindings have been written to the
|
||||
// current view descriptor heap.
|
||||
bool bindful_textures_written_vertex_;
|
||||
bool bindful_textures_written_pixel_;
|
||||
bool bindful_samplers_written_vertex_;
|
||||
bool bindful_samplers_written_pixel_;
|
||||
// Layout UIDs and last texture and sampler bindings written to the current
|
||||
// descriptor heaps (for bindful) or descriptor index constant buffer (for
|
||||
// bindless) with the last used descriptor layout. Valid only when:
|
||||
// - For bindful, when bindful_#_written_#_ is true.
|
||||
// - For bindless, when cbuffer_binding_descriptor_indices_#_.up_to_date is
|
||||
// true.
|
||||
size_t current_texture_layout_uid_vertex_;
|
||||
size_t current_texture_layout_uid_pixel_;
|
||||
size_t current_sampler_layout_uid_vertex_;
|
||||
size_t current_sampler_layout_uid_pixel_;
|
||||
// Size of these should be ignored when checking whether these are up to date,
|
||||
// layout UID should be checked first (they will be different for different
|
||||
// binding counts).
|
||||
std::vector<D3D12TextureCache::TextureSRVKey> current_texture_srv_keys_vertex_;
|
||||
std::vector<D3D12TextureCache::TextureSRVKey> current_texture_srv_keys_pixel_;
|
||||
std::vector<D3D12TextureCache::SamplerParameters> current_samplers_vertex_;
|
||||
std::vector<D3D12TextureCache::SamplerParameters> current_samplers_pixel_;
|
||||
std::vector<uint32_t> current_sampler_bindless_indices_vertex_;
|
||||
std::vector<uint32_t> current_sampler_bindless_indices_pixel_;
|
||||
|
||||
// Latest bindful descriptor handles used for handling Xenos draw calls.
|
||||
D3D12_GPU_DESCRIPTOR_HANDLE gpu_handle_shared_memory_srv_and_edram_;
|
||||
D3D12_GPU_DESCRIPTOR_HANDLE gpu_handle_shared_memory_uav_and_edram_;
|
||||
D3D12_GPU_DESCRIPTOR_HANDLE gpu_handle_textures_vertex_;
|
||||
D3D12_GPU_DESCRIPTOR_HANDLE gpu_handle_textures_pixel_;
|
||||
D3D12_GPU_DESCRIPTOR_HANDLE gpu_handle_samplers_vertex_;
|
||||
D3D12_GPU_DESCRIPTOR_HANDLE gpu_handle_samplers_pixel_;
|
||||
|
||||
// Current primitive topology.
|
||||
D3D_PRIMITIVE_TOPOLOGY primitive_topology_;
|
||||
|
||||
// Temporary storage for memexport stream constants used in the draw.
|
||||
std::vector<draw_util::MemExportRange> memexport_ranges_;
|
||||
};
|
||||
|
||||
} // namespace rex::graphics::d3d12
|
||||
649
include/rex/graphics/d3d12/deferred_command_list.h
Normal file
649
include/rex/graphics/d3d12/deferred_command_list.h
Normal file
|
|
@ -0,0 +1,649 @@
|
|||
/**
|
||||
******************************************************************************
|
||||
* Xenia : Xbox 360 Emulator Research Project *
|
||||
******************************************************************************
|
||||
* Copyright 2019 Ben Vanik. All rights reserved. *
|
||||
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||
******************************************************************************
|
||||
*
|
||||
* @modified Tom Clay, 2026 - Adapted for ReXGlue runtime
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <vector>
|
||||
|
||||
#include <rex/assert.h>
|
||||
#include <rex/literals.h>
|
||||
#include <rex/math.h>
|
||||
#include <rex/ui/d3d12/d3d12_api.h>
|
||||
|
||||
namespace rex::graphics::d3d12 {
|
||||
|
||||
using namespace ::rex::literals;
|
||||
|
||||
class D3D12CommandProcessor;
|
||||
|
||||
class DeferredCommandList {
|
||||
public:
|
||||
DeferredCommandList(const D3D12CommandProcessor& command_processor,
|
||||
size_t initial_size_bytes = 1_MiB);
|
||||
|
||||
void Reset();
|
||||
void Execute(ID3D12GraphicsCommandList* command_list, ID3D12GraphicsCommandList1* command_list_1);
|
||||
|
||||
D3D12_RECT* ClearDepthStencilViewAllocatedRects(D3D12_CPU_DESCRIPTOR_HANDLE depth_stencil_view,
|
||||
D3D12_CLEAR_FLAGS clear_flags, FLOAT depth,
|
||||
UINT8 stencil, UINT num_rects) {
|
||||
auto args = reinterpret_cast<ClearDepthStencilViewHeader*>(
|
||||
WriteCommand(Command::kD3DClearDepthStencilView,
|
||||
sizeof(ClearDepthStencilViewHeader) + num_rects * sizeof(D3D12_RECT)));
|
||||
args->depth_stencil_view = depth_stencil_view;
|
||||
args->clear_flags = clear_flags;
|
||||
args->depth = depth;
|
||||
args->stencil = stencil;
|
||||
args->num_rects = num_rects;
|
||||
return num_rects ? reinterpret_cast<D3D12_RECT*>(args + 1) : nullptr;
|
||||
}
|
||||
|
||||
void D3DClearDepthStencilView(D3D12_CPU_DESCRIPTOR_HANDLE depth_stencil_view,
|
||||
D3D12_CLEAR_FLAGS clear_flags, FLOAT depth, UINT8 stencil,
|
||||
UINT num_rects, const D3D12_RECT* rects) {
|
||||
D3D12_RECT* allocated_rects = ClearDepthStencilViewAllocatedRects(
|
||||
depth_stencil_view, clear_flags, depth, stencil, num_rects);
|
||||
if (num_rects) {
|
||||
assert_not_null(allocated_rects);
|
||||
std::memcpy(allocated_rects, rects, num_rects * sizeof(D3D12_RECT));
|
||||
}
|
||||
}
|
||||
|
||||
void D3DClearRenderTargetView(D3D12_CPU_DESCRIPTOR_HANDLE render_target_view,
|
||||
const FLOAT color_rgba[4], UINT num_rects,
|
||||
const D3D12_RECT* rects) {
|
||||
auto args = reinterpret_cast<ClearRenderTargetViewHeader*>(
|
||||
WriteCommand(Command::kD3DClearRenderTargetView,
|
||||
sizeof(ClearRenderTargetViewHeader) + num_rects * sizeof(D3D12_RECT)));
|
||||
args->render_target_view = render_target_view;
|
||||
std::memcpy(args->color_rgba, color_rgba, 4 * sizeof(FLOAT));
|
||||
args->num_rects = num_rects;
|
||||
if (num_rects != 0) {
|
||||
std::memcpy(args + 1, rects, num_rects * sizeof(D3D12_RECT));
|
||||
}
|
||||
}
|
||||
|
||||
void D3DClearUnorderedAccessViewUint(D3D12_GPU_DESCRIPTOR_HANDLE view_gpu_handle_in_current_heap,
|
||||
D3D12_CPU_DESCRIPTOR_HANDLE view_cpu_handle,
|
||||
ID3D12Resource* resource, const UINT values[4],
|
||||
UINT num_rects, const D3D12_RECT* rects) {
|
||||
auto args = reinterpret_cast<ClearUnorderedAccessViewHeader*>(
|
||||
WriteCommand(Command::kD3DClearUnorderedAccessViewUint,
|
||||
sizeof(ClearUnorderedAccessViewHeader) + num_rects * sizeof(D3D12_RECT)));
|
||||
args->view_gpu_handle_in_current_heap = view_gpu_handle_in_current_heap;
|
||||
args->view_cpu_handle = view_cpu_handle;
|
||||
args->resource = resource;
|
||||
std::memcpy(args->values_uint, values, 4 * sizeof(UINT));
|
||||
args->num_rects = num_rects;
|
||||
if (num_rects != 0) {
|
||||
std::memcpy(args + 1, rects, num_rects * sizeof(D3D12_RECT));
|
||||
}
|
||||
}
|
||||
|
||||
void D3DCopyBufferRegion(ID3D12Resource* dst_buffer, UINT64 dst_offset,
|
||||
ID3D12Resource* src_buffer, UINT64 src_offset, UINT64 num_bytes) {
|
||||
auto& args = *reinterpret_cast<D3DCopyBufferRegionArguments*>(
|
||||
WriteCommand(Command::kD3DCopyBufferRegion, sizeof(D3DCopyBufferRegionArguments)));
|
||||
args.dst_buffer = dst_buffer;
|
||||
args.dst_offset = dst_offset;
|
||||
args.src_buffer = src_buffer;
|
||||
args.src_offset = src_offset;
|
||||
args.num_bytes = num_bytes;
|
||||
}
|
||||
|
||||
void D3DCopyResource(ID3D12Resource* dst_resource, ID3D12Resource* src_resource) {
|
||||
auto& args = *reinterpret_cast<D3DCopyResourceArguments*>(
|
||||
WriteCommand(Command::kD3DCopyResource, sizeof(D3DCopyResourceArguments)));
|
||||
args.dst_resource = dst_resource;
|
||||
args.src_resource = src_resource;
|
||||
}
|
||||
|
||||
void CopyTexture(const D3D12_TEXTURE_COPY_LOCATION& dst, const D3D12_TEXTURE_COPY_LOCATION& src) {
|
||||
auto& args = *reinterpret_cast<CopyTextureArguments*>(
|
||||
WriteCommand(Command::kCopyTexture, sizeof(CopyTextureArguments)));
|
||||
std::memcpy(&args.dst, &dst, sizeof(D3D12_TEXTURE_COPY_LOCATION));
|
||||
std::memcpy(&args.src, &src, sizeof(D3D12_TEXTURE_COPY_LOCATION));
|
||||
}
|
||||
|
||||
void D3DCopyTextureRegion(const D3D12_TEXTURE_COPY_LOCATION* dst, UINT dst_x, UINT dst_y,
|
||||
UINT dst_z, const D3D12_TEXTURE_COPY_LOCATION* src,
|
||||
const D3D12_BOX* src_box) {
|
||||
assert_not_null(dst);
|
||||
assert_not_null(src);
|
||||
auto& args = *reinterpret_cast<D3DCopyTextureRegionArguments*>(
|
||||
WriteCommand(Command::kD3DCopyTextureRegion, sizeof(D3DCopyTextureRegionArguments)));
|
||||
std::memcpy(&args.dst, dst, sizeof(D3D12_TEXTURE_COPY_LOCATION));
|
||||
args.dst_x = dst_x;
|
||||
args.dst_y = dst_y;
|
||||
args.dst_z = dst_z;
|
||||
std::memcpy(&args.src, src, sizeof(D3D12_TEXTURE_COPY_LOCATION));
|
||||
if (src_box) {
|
||||
args.has_src_box = true;
|
||||
args.src_box = *src_box;
|
||||
} else {
|
||||
args.has_src_box = false;
|
||||
}
|
||||
}
|
||||
|
||||
void D3DDispatch(UINT thread_group_count_x, UINT thread_group_count_y,
|
||||
UINT thread_group_count_z) {
|
||||
auto& args = *reinterpret_cast<D3DDispatchArguments*>(
|
||||
WriteCommand(Command::kD3DDispatch, sizeof(D3DDispatchArguments)));
|
||||
args.thread_group_count_x = thread_group_count_x;
|
||||
args.thread_group_count_y = thread_group_count_y;
|
||||
args.thread_group_count_z = thread_group_count_z;
|
||||
}
|
||||
|
||||
void D3DDrawIndexedInstanced(UINT index_count_per_instance, UINT instance_count,
|
||||
UINT start_index_location, INT base_vertex_location,
|
||||
UINT start_instance_location) {
|
||||
auto& args = *reinterpret_cast<D3DDrawIndexedInstancedArguments*>(
|
||||
WriteCommand(Command::kD3DDrawIndexedInstanced, sizeof(D3DDrawIndexedInstancedArguments)));
|
||||
args.index_count_per_instance = index_count_per_instance;
|
||||
args.instance_count = instance_count;
|
||||
args.start_index_location = start_index_location;
|
||||
args.base_vertex_location = base_vertex_location;
|
||||
args.start_instance_location = start_instance_location;
|
||||
}
|
||||
|
||||
void D3DDrawInstanced(UINT vertex_count_per_instance, UINT instance_count,
|
||||
UINT start_vertex_location, UINT start_instance_location) {
|
||||
auto& args = *reinterpret_cast<D3DDrawInstancedArguments*>(
|
||||
WriteCommand(Command::kD3DDrawInstanced, sizeof(D3DDrawInstancedArguments)));
|
||||
args.vertex_count_per_instance = vertex_count_per_instance;
|
||||
args.instance_count = instance_count;
|
||||
args.start_vertex_location = start_vertex_location;
|
||||
args.start_instance_location = start_instance_location;
|
||||
}
|
||||
|
||||
void D3DBeginQuery(ID3D12QueryHeap* query_heap, D3D12_QUERY_TYPE type, UINT index) {
|
||||
auto& args = *reinterpret_cast<D3DQueryArguments*>(
|
||||
WriteCommand(Command::kD3DBeginQuery, sizeof(D3DQueryArguments)));
|
||||
args.query_heap = query_heap;
|
||||
args.type = type;
|
||||
args.index = index;
|
||||
}
|
||||
|
||||
void D3DEndQuery(ID3D12QueryHeap* query_heap, D3D12_QUERY_TYPE type, UINT index) {
|
||||
auto& args = *reinterpret_cast<D3DQueryArguments*>(
|
||||
WriteCommand(Command::kD3DEndQuery, sizeof(D3DQueryArguments)));
|
||||
args.query_heap = query_heap;
|
||||
args.type = type;
|
||||
args.index = index;
|
||||
}
|
||||
|
||||
void D3DResolveQueryData(ID3D12QueryHeap* query_heap, D3D12_QUERY_TYPE type, UINT start_index,
|
||||
UINT num_queries, ID3D12Resource* destination_buffer,
|
||||
UINT64 aligned_destination_buffer_offset) {
|
||||
auto& args = *reinterpret_cast<D3DResolveQueryDataArguments*>(
|
||||
WriteCommand(Command::kD3DResolveQueryData, sizeof(D3DResolveQueryDataArguments)));
|
||||
args.query_heap = query_heap;
|
||||
args.type = type;
|
||||
args.start_index = start_index;
|
||||
args.num_queries = num_queries;
|
||||
args.destination_buffer = destination_buffer;
|
||||
args.aligned_destination_buffer_offset = aligned_destination_buffer_offset;
|
||||
}
|
||||
|
||||
void D3DIASetIndexBuffer(const D3D12_INDEX_BUFFER_VIEW* view) {
|
||||
auto& args = *reinterpret_cast<D3D12_INDEX_BUFFER_VIEW*>(
|
||||
WriteCommand(Command::kD3DIASetIndexBuffer, sizeof(D3D12_INDEX_BUFFER_VIEW)));
|
||||
if (view != nullptr) {
|
||||
args.BufferLocation = view->BufferLocation;
|
||||
args.SizeInBytes = view->SizeInBytes;
|
||||
args.Format = view->Format;
|
||||
} else {
|
||||
args.BufferLocation = D3D12_GPU_VIRTUAL_ADDRESS(0);
|
||||
args.SizeInBytes = 0;
|
||||
args.Format = DXGI_FORMAT_UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
void D3DIASetPrimitiveTopology(D3D12_PRIMITIVE_TOPOLOGY primitive_topology) {
|
||||
auto& arg = *reinterpret_cast<D3D12_PRIMITIVE_TOPOLOGY*>(
|
||||
WriteCommand(Command::kD3DIASetPrimitiveTopology, sizeof(D3D12_PRIMITIVE_TOPOLOGY)));
|
||||
arg = primitive_topology;
|
||||
}
|
||||
|
||||
void D3DIASetVertexBuffers(UINT start_slot, UINT num_views,
|
||||
const D3D12_VERTEX_BUFFER_VIEW* views) {
|
||||
if (num_views == 0) {
|
||||
return;
|
||||
}
|
||||
static_assert(alignof(D3D12_VERTEX_BUFFER_VIEW) <= alignof(uintmax_t));
|
||||
const size_t header_size =
|
||||
::rex::align(sizeof(D3DIASetVertexBuffersHeader), alignof(D3D12_VERTEX_BUFFER_VIEW));
|
||||
auto args = reinterpret_cast<D3DIASetVertexBuffersHeader*>(
|
||||
WriteCommand(Command::kD3DIASetVertexBuffers,
|
||||
header_size + num_views * sizeof(D3D12_VERTEX_BUFFER_VIEW)));
|
||||
args->start_slot = start_slot;
|
||||
args->num_views = num_views;
|
||||
std::memcpy(reinterpret_cast<uint8_t*>(args) + header_size, views,
|
||||
sizeof(D3D12_VERTEX_BUFFER_VIEW) * num_views);
|
||||
}
|
||||
|
||||
void D3DOMSetBlendFactor(const FLOAT blend_factor[4]) {
|
||||
auto args =
|
||||
reinterpret_cast<FLOAT*>(WriteCommand(Command::kD3DOMSetBlendFactor, 4 * sizeof(FLOAT)));
|
||||
args[0] = blend_factor[0];
|
||||
args[1] = blend_factor[1];
|
||||
args[2] = blend_factor[2];
|
||||
args[3] = blend_factor[3];
|
||||
}
|
||||
|
||||
void D3DOMSetRenderTargets(UINT num_render_target_descriptors,
|
||||
const D3D12_CPU_DESCRIPTOR_HANDLE* render_target_descriptors,
|
||||
BOOL rts_single_handle_to_descriptor_range,
|
||||
const D3D12_CPU_DESCRIPTOR_HANDLE* depth_stencil_descriptor) {
|
||||
auto& args = *reinterpret_cast<D3DOMSetRenderTargetsArguments*>(
|
||||
WriteCommand(Command::kD3DOMSetRenderTargets, sizeof(D3DOMSetRenderTargetsArguments)));
|
||||
num_render_target_descriptors =
|
||||
std::min(num_render_target_descriptors, UINT(D3D12_SIMULTANEOUS_RENDER_TARGET_COUNT));
|
||||
args.num_render_target_descriptors = num_render_target_descriptors;
|
||||
args.rts_single_handle_to_descriptor_range = rts_single_handle_to_descriptor_range ? 1 : 0;
|
||||
if (num_render_target_descriptors != 0) {
|
||||
std::memcpy(args.render_target_descriptors, render_target_descriptors,
|
||||
(rts_single_handle_to_descriptor_range ? 1 : num_render_target_descriptors) *
|
||||
sizeof(D3D12_CPU_DESCRIPTOR_HANDLE));
|
||||
}
|
||||
args.depth_stencil = (depth_stencil_descriptor != nullptr) ? 1 : 0;
|
||||
if (depth_stencil_descriptor != nullptr) {
|
||||
args.depth_stencil_descriptor.ptr = depth_stencil_descriptor->ptr;
|
||||
}
|
||||
}
|
||||
|
||||
void D3DOMSetStencilRef(UINT stencil_ref) {
|
||||
auto& arg = *reinterpret_cast<UINT*>(WriteCommand(Command::kD3DOMSetStencilRef, sizeof(UINT)));
|
||||
arg = stencil_ref;
|
||||
}
|
||||
|
||||
void D3DResourceBarrier(UINT num_barriers, const D3D12_RESOURCE_BARRIER* barriers) {
|
||||
if (num_barriers == 0) {
|
||||
return;
|
||||
}
|
||||
static_assert(alignof(D3D12_RESOURCE_BARRIER) <= alignof(uintmax_t));
|
||||
const size_t header_size = ::rex::align(sizeof(UINT), alignof(D3D12_RESOURCE_BARRIER));
|
||||
uint8_t* args = reinterpret_cast<uint8_t*>(WriteCommand(
|
||||
Command::kD3DResourceBarrier, header_size + num_barriers * sizeof(D3D12_RESOURCE_BARRIER)));
|
||||
*reinterpret_cast<UINT*>(args) = num_barriers;
|
||||
std::memcpy(args + header_size, barriers, num_barriers * sizeof(D3D12_RESOURCE_BARRIER));
|
||||
}
|
||||
|
||||
void RSSetScissorRect(const D3D12_RECT& rect) {
|
||||
auto& arg = *reinterpret_cast<D3D12_RECT*>(
|
||||
WriteCommand(Command::kRSSetScissorRect, sizeof(D3D12_RECT)));
|
||||
arg = rect;
|
||||
}
|
||||
|
||||
void RSSetViewport(const D3D12_VIEWPORT& viewport) {
|
||||
auto& arg = *reinterpret_cast<D3D12_VIEWPORT*>(
|
||||
WriteCommand(Command::kRSSetViewport, sizeof(D3D12_VIEWPORT)));
|
||||
arg = viewport;
|
||||
}
|
||||
|
||||
void D3DSetComputeRoot32BitConstants(UINT root_parameter_index, UINT num_32bit_values_to_set,
|
||||
const void* src_data, UINT dest_offset_in_32bit_values) {
|
||||
if (num_32bit_values_to_set == 0) {
|
||||
return;
|
||||
}
|
||||
auto args = reinterpret_cast<SetRoot32BitConstantsHeader*>(WriteCommand(
|
||||
Command::kD3DSetComputeRoot32BitConstants,
|
||||
sizeof(SetRoot32BitConstantsHeader) + num_32bit_values_to_set * sizeof(uint32_t)));
|
||||
args->root_parameter_index = root_parameter_index;
|
||||
args->num_32bit_values_to_set = num_32bit_values_to_set;
|
||||
args->dest_offset_in_32bit_values = dest_offset_in_32bit_values;
|
||||
std::memcpy(args + 1, src_data, num_32bit_values_to_set * sizeof(uint32_t));
|
||||
}
|
||||
|
||||
void D3DSetGraphicsRoot32BitConstants(UINT root_parameter_index, UINT num_32bit_values_to_set,
|
||||
const void* src_data, UINT dest_offset_in_32bit_values) {
|
||||
if (num_32bit_values_to_set == 0) {
|
||||
return;
|
||||
}
|
||||
auto args = reinterpret_cast<SetRoot32BitConstantsHeader*>(WriteCommand(
|
||||
Command::kD3DSetGraphicsRoot32BitConstants,
|
||||
sizeof(SetRoot32BitConstantsHeader) + num_32bit_values_to_set * sizeof(uint32_t)));
|
||||
args->root_parameter_index = root_parameter_index;
|
||||
args->num_32bit_values_to_set = num_32bit_values_to_set;
|
||||
args->dest_offset_in_32bit_values = dest_offset_in_32bit_values;
|
||||
std::memcpy(args + 1, src_data, num_32bit_values_to_set * sizeof(uint32_t));
|
||||
}
|
||||
|
||||
void D3DSetComputeRootConstantBufferView(UINT root_parameter_index,
|
||||
D3D12_GPU_VIRTUAL_ADDRESS buffer_location) {
|
||||
auto& args = *reinterpret_cast<SetRootConstantBufferViewArguments*>(WriteCommand(
|
||||
Command::kD3DSetComputeRootConstantBufferView, sizeof(SetRootConstantBufferViewArguments)));
|
||||
args.root_parameter_index = root_parameter_index;
|
||||
args.buffer_location = buffer_location;
|
||||
}
|
||||
|
||||
void D3DSetGraphicsRootConstantBufferView(UINT root_parameter_index,
|
||||
D3D12_GPU_VIRTUAL_ADDRESS buffer_location) {
|
||||
auto& args = *reinterpret_cast<SetRootConstantBufferViewArguments*>(
|
||||
WriteCommand(Command::kD3DSetGraphicsRootConstantBufferView,
|
||||
sizeof(SetRootConstantBufferViewArguments)));
|
||||
args.root_parameter_index = root_parameter_index;
|
||||
args.buffer_location = buffer_location;
|
||||
}
|
||||
|
||||
void D3DSetComputeRootDescriptorTable(UINT root_parameter_index,
|
||||
D3D12_GPU_DESCRIPTOR_HANDLE base_descriptor) {
|
||||
auto& args = *reinterpret_cast<SetRootDescriptorTableArguments*>(WriteCommand(
|
||||
Command::kD3DSetComputeRootDescriptorTable, sizeof(SetRootDescriptorTableArguments)));
|
||||
args.root_parameter_index = root_parameter_index;
|
||||
args.base_descriptor.ptr = base_descriptor.ptr;
|
||||
}
|
||||
|
||||
void D3DSetGraphicsRootDescriptorTable(UINT root_parameter_index,
|
||||
D3D12_GPU_DESCRIPTOR_HANDLE base_descriptor) {
|
||||
auto& args = *reinterpret_cast<SetRootDescriptorTableArguments*>(WriteCommand(
|
||||
Command::kD3DSetGraphicsRootDescriptorTable, sizeof(SetRootDescriptorTableArguments)));
|
||||
args.root_parameter_index = root_parameter_index;
|
||||
args.base_descriptor.ptr = base_descriptor.ptr;
|
||||
}
|
||||
|
||||
void D3DSetComputeRootShaderResourceView(UINT root_parameter_index,
|
||||
D3D12_GPU_VIRTUAL_ADDRESS buffer_location) {
|
||||
auto& args = *reinterpret_cast<SetRootConstantBufferViewArguments*>(WriteCommand(
|
||||
Command::kD3DSetComputeRootShaderResourceView, sizeof(SetRootConstantBufferViewArguments)));
|
||||
args.root_parameter_index = root_parameter_index;
|
||||
args.buffer_location = buffer_location;
|
||||
}
|
||||
|
||||
void D3DSetGraphicsRootShaderResourceView(UINT root_parameter_index,
|
||||
D3D12_GPU_VIRTUAL_ADDRESS buffer_location) {
|
||||
auto& args = *reinterpret_cast<SetRootConstantBufferViewArguments*>(
|
||||
WriteCommand(Command::kD3DSetGraphicsRootShaderResourceView,
|
||||
sizeof(SetRootConstantBufferViewArguments)));
|
||||
args.root_parameter_index = root_parameter_index;
|
||||
args.buffer_location = buffer_location;
|
||||
}
|
||||
|
||||
void D3DSetComputeRootUnorderedAccessView(UINT root_parameter_index,
|
||||
D3D12_GPU_VIRTUAL_ADDRESS buffer_location) {
|
||||
auto& args = *reinterpret_cast<SetRootConstantBufferViewArguments*>(
|
||||
WriteCommand(Command::kD3DSetComputeRootUnorderedAccessView,
|
||||
sizeof(SetRootConstantBufferViewArguments)));
|
||||
args.root_parameter_index = root_parameter_index;
|
||||
args.buffer_location = buffer_location;
|
||||
}
|
||||
|
||||
void D3DSetGraphicsRootUnorderedAccessView(UINT root_parameter_index,
|
||||
D3D12_GPU_VIRTUAL_ADDRESS buffer_location) {
|
||||
auto& args = *reinterpret_cast<SetRootConstantBufferViewArguments*>(
|
||||
WriteCommand(Command::kD3DSetGraphicsRootUnorderedAccessView,
|
||||
sizeof(SetRootConstantBufferViewArguments)));
|
||||
args.root_parameter_index = root_parameter_index;
|
||||
args.buffer_location = buffer_location;
|
||||
}
|
||||
|
||||
void D3DSetComputeRootSignature(ID3D12RootSignature* root_signature) {
|
||||
auto& arg = *reinterpret_cast<ID3D12RootSignature**>(
|
||||
WriteCommand(Command::kD3DSetComputeRootSignature, sizeof(ID3D12RootSignature*)));
|
||||
arg = root_signature;
|
||||
}
|
||||
|
||||
void D3DSetGraphicsRootSignature(ID3D12RootSignature* root_signature) {
|
||||
auto& arg = *reinterpret_cast<ID3D12RootSignature**>(
|
||||
WriteCommand(Command::kD3DSetGraphicsRootSignature, sizeof(ID3D12RootSignature*)));
|
||||
arg = root_signature;
|
||||
}
|
||||
|
||||
void SetDescriptorHeaps(ID3D12DescriptorHeap* cbv_srv_uav_descriptor_heap,
|
||||
ID3D12DescriptorHeap* sampler_descriptor_heap) {
|
||||
auto& args = *reinterpret_cast<SetDescriptorHeapsArguments*>(
|
||||
WriteCommand(Command::kSetDescriptorHeaps, sizeof(SetDescriptorHeapsArguments)));
|
||||
args.cbv_srv_uav_descriptor_heap = cbv_srv_uav_descriptor_heap;
|
||||
args.sampler_descriptor_heap = sampler_descriptor_heap;
|
||||
}
|
||||
|
||||
void D3DSetPipelineState(ID3D12PipelineState* pipeline_state) {
|
||||
auto& arg = *reinterpret_cast<ID3D12PipelineState**>(
|
||||
WriteCommand(Command::kD3DSetPipelineState, sizeof(ID3D12PipelineState*)));
|
||||
arg = pipeline_state;
|
||||
}
|
||||
|
||||
void SetPipelineStateHandle(void* pipeline_state_handle) {
|
||||
auto& arg =
|
||||
*reinterpret_cast<void**>(WriteCommand(Command::kSetPipelineStateHandle, sizeof(void*)));
|
||||
arg = pipeline_state_handle;
|
||||
}
|
||||
|
||||
void D3DSetSamplePositions(UINT num_samples_per_pixel, UINT num_pixels,
|
||||
const D3D12_SAMPLE_POSITION* sample_positions) {
|
||||
auto& args = *reinterpret_cast<D3DSetSamplePositionsArguments*>(
|
||||
WriteCommand(Command::kD3DSetSamplePositions, sizeof(D3DSetSamplePositionsArguments)));
|
||||
args.num_samples_per_pixel = num_samples_per_pixel;
|
||||
args.num_pixels = num_pixels;
|
||||
std::memcpy(
|
||||
args.sample_positions, sample_positions,
|
||||
std::min(num_samples_per_pixel * num_pixels, UINT(16)) * sizeof(D3D12_SAMPLE_POSITION));
|
||||
}
|
||||
|
||||
void BeginDebugMarker(const char* label_name) {
|
||||
size_t label_len = std::strlen(label_name);
|
||||
uint8_t* args_ptr = reinterpret_cast<uint8_t*>(
|
||||
WriteCommand(Command::kBeginDebugMarker, sizeof(DebugMarkerHeader) + label_len + 1));
|
||||
auto& args = *reinterpret_cast<DebugMarkerHeader*>(args_ptr);
|
||||
args.label_length = static_cast<uint32_t>(label_len);
|
||||
std::memcpy(args_ptr + sizeof(DebugMarkerHeader), label_name, label_len + 1);
|
||||
}
|
||||
|
||||
void EndDebugMarker() { WriteCommand(Command::kEndDebugMarker, 0); }
|
||||
|
||||
void InsertDebugMarker(const char* label_name) {
|
||||
size_t label_len = std::strlen(label_name);
|
||||
uint8_t* args_ptr = reinterpret_cast<uint8_t*>(
|
||||
WriteCommand(Command::kInsertDebugMarker, sizeof(DebugMarkerHeader) + label_len + 1));
|
||||
auto& args = *reinterpret_cast<DebugMarkerHeader*>(args_ptr);
|
||||
args.label_length = static_cast<uint32_t>(label_len);
|
||||
std::memcpy(args_ptr + sizeof(DebugMarkerHeader), label_name, label_len + 1);
|
||||
}
|
||||
|
||||
private:
|
||||
enum class Command {
|
||||
kD3DClearDepthStencilView,
|
||||
kD3DClearRenderTargetView,
|
||||
kD3DClearUnorderedAccessViewUint,
|
||||
kD3DCopyBufferRegion,
|
||||
kD3DCopyResource,
|
||||
kCopyTexture,
|
||||
kD3DCopyTextureRegion,
|
||||
kD3DDispatch,
|
||||
kD3DDrawIndexedInstanced,
|
||||
kD3DDrawInstanced,
|
||||
kD3DBeginQuery,
|
||||
kD3DEndQuery,
|
||||
kD3DResolveQueryData,
|
||||
kD3DIASetIndexBuffer,
|
||||
kD3DIASetPrimitiveTopology,
|
||||
kD3DIASetVertexBuffers,
|
||||
kD3DOMSetBlendFactor,
|
||||
kD3DOMSetRenderTargets,
|
||||
kD3DOMSetStencilRef,
|
||||
kD3DResourceBarrier,
|
||||
kRSSetScissorRect,
|
||||
kRSSetViewport,
|
||||
kD3DSetComputeRoot32BitConstants,
|
||||
kD3DSetGraphicsRoot32BitConstants,
|
||||
kD3DSetComputeRootConstantBufferView,
|
||||
kD3DSetGraphicsRootConstantBufferView,
|
||||
kD3DSetComputeRootDescriptorTable,
|
||||
kD3DSetGraphicsRootDescriptorTable,
|
||||
kD3DSetComputeRootShaderResourceView,
|
||||
kD3DSetGraphicsRootShaderResourceView,
|
||||
kD3DSetComputeRootSignature,
|
||||
kD3DSetGraphicsRootSignature,
|
||||
kD3DSetComputeRootUnorderedAccessView,
|
||||
kD3DSetGraphicsRootUnorderedAccessView,
|
||||
kSetDescriptorHeaps,
|
||||
kD3DSetPipelineState,
|
||||
kSetPipelineStateHandle,
|
||||
kD3DSetSamplePositions,
|
||||
kBeginDebugMarker,
|
||||
kEndDebugMarker,
|
||||
kInsertDebugMarker,
|
||||
};
|
||||
|
||||
struct CommandHeader {
|
||||
Command command;
|
||||
uint32_t arguments_size_elements;
|
||||
};
|
||||
static constexpr size_t kCommandHeaderSizeElements =
|
||||
(sizeof(CommandHeader) + sizeof(uintmax_t) - 1) / sizeof(uintmax_t);
|
||||
|
||||
struct ClearDepthStencilViewHeader {
|
||||
D3D12_CPU_DESCRIPTOR_HANDLE depth_stencil_view;
|
||||
D3D12_CLEAR_FLAGS clear_flags;
|
||||
FLOAT depth;
|
||||
UINT8 stencil;
|
||||
UINT num_rects;
|
||||
};
|
||||
|
||||
struct ClearRenderTargetViewHeader {
|
||||
D3D12_CPU_DESCRIPTOR_HANDLE render_target_view;
|
||||
FLOAT color_rgba[4];
|
||||
UINT num_rects;
|
||||
};
|
||||
|
||||
struct ClearUnorderedAccessViewHeader {
|
||||
D3D12_GPU_DESCRIPTOR_HANDLE view_gpu_handle_in_current_heap;
|
||||
D3D12_CPU_DESCRIPTOR_HANDLE view_cpu_handle;
|
||||
ID3D12Resource* resource;
|
||||
union {
|
||||
FLOAT values_float[4];
|
||||
UINT values_uint[4];
|
||||
};
|
||||
UINT num_rects;
|
||||
};
|
||||
|
||||
struct D3DCopyBufferRegionArguments {
|
||||
ID3D12Resource* dst_buffer;
|
||||
UINT64 dst_offset;
|
||||
ID3D12Resource* src_buffer;
|
||||
UINT64 src_offset;
|
||||
UINT64 num_bytes;
|
||||
};
|
||||
|
||||
struct D3DCopyResourceArguments {
|
||||
ID3D12Resource* dst_resource;
|
||||
ID3D12Resource* src_resource;
|
||||
};
|
||||
|
||||
struct CopyTextureArguments {
|
||||
D3D12_TEXTURE_COPY_LOCATION dst;
|
||||
D3D12_TEXTURE_COPY_LOCATION src;
|
||||
};
|
||||
|
||||
struct D3DCopyTextureRegionArguments {
|
||||
D3D12_TEXTURE_COPY_LOCATION dst;
|
||||
UINT dst_x;
|
||||
UINT dst_y;
|
||||
UINT dst_z;
|
||||
D3D12_TEXTURE_COPY_LOCATION src;
|
||||
D3D12_BOX src_box;
|
||||
bool has_src_box;
|
||||
};
|
||||
|
||||
struct D3DDispatchArguments {
|
||||
UINT thread_group_count_x;
|
||||
UINT thread_group_count_y;
|
||||
UINT thread_group_count_z;
|
||||
};
|
||||
|
||||
struct D3DDrawIndexedInstancedArguments {
|
||||
UINT index_count_per_instance;
|
||||
UINT instance_count;
|
||||
UINT start_index_location;
|
||||
INT base_vertex_location;
|
||||
UINT start_instance_location;
|
||||
};
|
||||
|
||||
struct D3DDrawInstancedArguments {
|
||||
UINT vertex_count_per_instance;
|
||||
UINT instance_count;
|
||||
UINT start_vertex_location;
|
||||
UINT start_instance_location;
|
||||
};
|
||||
|
||||
struct D3DQueryArguments {
|
||||
ID3D12QueryHeap* query_heap;
|
||||
D3D12_QUERY_TYPE type;
|
||||
UINT index;
|
||||
};
|
||||
|
||||
struct D3DResolveQueryDataArguments {
|
||||
ID3D12QueryHeap* query_heap;
|
||||
D3D12_QUERY_TYPE type;
|
||||
UINT start_index;
|
||||
UINT num_queries;
|
||||
ID3D12Resource* destination_buffer;
|
||||
UINT64 aligned_destination_buffer_offset;
|
||||
};
|
||||
|
||||
struct D3DIASetVertexBuffersHeader {
|
||||
UINT start_slot;
|
||||
UINT num_views;
|
||||
};
|
||||
|
||||
struct D3DOMSetRenderTargetsArguments {
|
||||
uint8_t num_render_target_descriptors;
|
||||
bool rts_single_handle_to_descriptor_range;
|
||||
bool depth_stencil;
|
||||
D3D12_CPU_DESCRIPTOR_HANDLE
|
||||
render_target_descriptors[D3D12_SIMULTANEOUS_RENDER_TARGET_COUNT];
|
||||
D3D12_CPU_DESCRIPTOR_HANDLE depth_stencil_descriptor;
|
||||
};
|
||||
|
||||
struct SetRoot32BitConstantsHeader {
|
||||
UINT root_parameter_index;
|
||||
UINT num_32bit_values_to_set;
|
||||
UINT dest_offset_in_32bit_values;
|
||||
};
|
||||
|
||||
struct SetRootConstantBufferViewArguments {
|
||||
UINT root_parameter_index;
|
||||
D3D12_GPU_VIRTUAL_ADDRESS buffer_location;
|
||||
};
|
||||
|
||||
struct SetRootDescriptorTableArguments {
|
||||
UINT root_parameter_index;
|
||||
D3D12_GPU_DESCRIPTOR_HANDLE base_descriptor;
|
||||
};
|
||||
|
||||
struct SetDescriptorHeapsArguments {
|
||||
ID3D12DescriptorHeap* cbv_srv_uav_descriptor_heap;
|
||||
ID3D12DescriptorHeap* sampler_descriptor_heap;
|
||||
};
|
||||
|
||||
struct D3DSetSamplePositionsArguments {
|
||||
UINT num_samples_per_pixel;
|
||||
UINT num_pixels;
|
||||
D3D12_SAMPLE_POSITION sample_positions[16];
|
||||
};
|
||||
|
||||
struct DebugMarkerHeader {
|
||||
uint32_t label_length;
|
||||
// Followed by null-terminated label string.
|
||||
};
|
||||
|
||||
void* WriteCommand(Command command, size_t arguments_size_bytes);
|
||||
|
||||
const D3D12CommandProcessor& command_processor_;
|
||||
|
||||
// uintmax_t to ensure uint64_t and pointer alignment of all structures.
|
||||
std::vector<uintmax_t> command_stream_;
|
||||
};
|
||||
|
||||
} // namespace rex::graphics::d3d12
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue